feat: Full UI internationalization, pool hashrate stats, and layout caching

- Replace all hardcoded English strings with TR() translation keys across
  every tab, dialog, and component (~20 UI files)
- Expand all 8 language files (de, es, fr, ja, ko, pt, ru, zh) with
  complete translations (~37k lines added)
- Improve i18n loader with exe-relative path fallback and English base
  fallback for missing keys
- Add pool-side hashrate polling via pool stats API in xmrig_manager
- Introduce Layout::beginFrame() per-frame caching and refresh balance
  layout config only on schema generation change
- Offload daemon output parsing to worker thread
- Add CJK subset fallback font for Chinese/Japanese/Korean glyphs
This commit is contained in:
2026-03-11 00:40:50 -05:00
parent f416ff3d09
commit 2c5a658ea5
71 changed files with 43567 additions and 5267 deletions

View File

@@ -244,6 +244,18 @@ bool XmrigManager::start(const Config& cfg) {
}
stats_ = PoolStats{};
// Extract pool hostname for stats API queries
{
std::string url = cfg.pool_url;
// Strip protocol prefix if present
auto pos = url.find("://");
if (pos != std::string::npos) url = url.substr(pos + 3);
// Strip port suffix
pos = url.find(':');
if (pos != std::string::npos) url = url.substr(0, pos);
pool_host_ = url;
}
// Find binary
std::string binary = findXmrigBinary();
if (binary.empty()) {
@@ -572,6 +584,7 @@ void XmrigManager::monitorProcess() {
}
int poll_counter = 0;
int pool_api_counter = 0;
while (!should_stop_) {
drainOutput();
@@ -605,6 +618,12 @@ void XmrigManager::monitorProcess() {
fetchStatsHttp();
}
// Poll pool-side stats every ~30 seconds (300 * 100ms)
if (++pool_api_counter >= 300) {
pool_api_counter = 0;
fetchPoolApiStats();
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
drainOutput(); // Final drain
@@ -711,5 +730,53 @@ void XmrigManager::fetchStatsHttp() {
}
}
// ============================================================================
// Pool-side stats (hashrate reported by the pool)
// ============================================================================
void XmrigManager::fetchPoolApiStats() {
if (state_ != State::Running || pool_host_.empty()) return;
// Query the pool's public stats API
std::string url = "https://" + pool_host_ + "/api/stats";
std::string responseData;
CURL* curl = curl_easy_init();
if (!curl) return;
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlWriteCb);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &responseData);
curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, 5000L);
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, 3000L);
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
CURLcode res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
if (res != CURLE_OK) return;
try {
json resp = json::parse(responseData);
// Pool stats API format: { "pools": { "<name>": { "hashrate": ... } } }
double poolHR = 0;
if (resp.contains("pools") && resp["pools"].is_object()) {
for (auto& [key, pool] : resp["pools"].items()) {
if (pool.contains("hashrate") && pool["hashrate"].is_number()) {
poolHR = pool["hashrate"].get<double>();
break; // Use the first pool entry
}
}
}
std::lock_guard<std::mutex> lk(stats_mutex_);
stats_.pool_hashrate = poolHR;
} catch (...) {
// Malformed response — ignore
}
}
} // namespace daemon
} // namespace dragonx