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:
dan_s
2026-03-11 00:40:50 -05:00
parent cc617dd5be
commit 96c27bb949
71 changed files with 43567 additions and 5267 deletions

View File

@@ -76,24 +76,17 @@ void ShieldDialog::render(App* app)
auto cancelBtn = S.button("dialogs.shield", "cancel-button");
const char* title = (s_mode == Mode::ShieldCoinbase)
? "Shield Coinbase Rewards"
: "Merge to Address";
? TR("shield_title")
: TR("merge_title");
if (material::BeginOverlayDialog(title, &s_open, win.width, 0.94f)) {
const auto& state = app->getWalletState();
// Description
if (s_mode == Mode::ShieldCoinbase) {
ImGui::TextWrapped(
"Shield your mining rewards by sending coinbase outputs from "
"transparent addresses to a shielded address. This improves "
"privacy by hiding your mining income."
);
ImGui::TextWrapped("%s", TR("shield_description"));
} else {
ImGui::TextWrapped(
"Merge multiple UTXOs into a single shielded address. This can "
"help reduce wallet size and improve privacy."
);
ImGui::TextWrapped("%s", TR("merge_description"));
}
ImGui::Spacing();
@@ -102,18 +95,18 @@ void ShieldDialog::render(App* app)
// From address (for shield coinbase)
if (s_mode == Mode::ShieldCoinbase) {
ImGui::Text("From Address:");
ImGui::Text("%s", TR("shield_from_address"));
ImGui::SetNextItemWidth(-1);
ImGui::InputText("##FromAddr", s_from_address, sizeof(s_from_address));
ImGui::TextDisabled("Use '*' to shield from all transparent addresses");
ImGui::TextDisabled("%s", TR("shield_wildcard_hint"));
ImGui::Spacing();
}
// To address (z-address dropdown)
ImGui::Text("To Address (Shielded):");
ImGui::Text("%s", TR("shield_to_address"));
// Get z-addresses for dropdown
std::string to_display = s_to_address[0] ? s_to_address : "Select z-address...";
std::string to_display = s_to_address[0] ? s_to_address : TR("shield_select_z");
if (to_display.length() > static_cast<size_t>(addrLbl.truncate)) {
to_display = to_display.substr(0, addrFrontLbl.truncate) + "..." + to_display.substr(to_display.length() - addrBackLbl.truncate);
}
@@ -142,7 +135,7 @@ void ShieldDialog::render(App* app)
ImGui::Spacing();
// Fee
ImGui::Text("Fee:");
ImGui::Text("%s", TR("fee_label"));
ImGui::SetNextItemWidth(feeInput.width);
ImGui::InputDouble("##Fee", &s_fee, 0.0001, 0.001, "%.8f");
ImGui::SameLine();
@@ -151,11 +144,11 @@ void ShieldDialog::render(App* app)
ImGui::Spacing();
// UTXO limit
ImGui::Text("UTXO Limit:");
ImGui::Text("%s", TR("shield_utxo_limit"));
ImGui::SetNextItemWidth(utxoInput.width);
ImGui::InputInt("##Limit", &s_utxo_limit);
ImGui::SameLine();
ImGui::TextDisabled("Max UTXOs per operation");
ImGui::TextDisabled("%s", TR("shield_max_utxos"));
if (s_utxo_limit < 1) s_utxo_limit = 1;
if (s_utxo_limit > 100) s_utxo_limit = 100;
@@ -178,7 +171,7 @@ void ShieldDialog::render(App* app)
if (!can_submit) ImGui::BeginDisabled();
const char* btn_label = (s_mode == Mode::ShieldCoinbase) ? "Shield Funds" : "Merge Funds";
const char* btn_label = (s_mode == Mode::ShieldCoinbase) ? TR("shield_funds") : TR("merge_funds");
if (material::StyledButton(btn_label, ImVec2(shieldBtn.width, 0), S.resolveFont(shieldBtn.font))) {
s_operation_pending = true;
s_status_message = "Submitting operation...";
@@ -201,7 +194,7 @@ void ShieldDialog::render(App* app)
if (error.empty()) {
s_operation_id = result.value("opid", "");
s_status_message = "Operation submitted: " + s_operation_id;
Notifications::instance().success("Shield operation started");
Notifications::instance().success(TR("shield_started"));
} else {
s_status_message = "Error: " + error;
Notifications::instance().error("Shield failed: " + error);
@@ -231,7 +224,7 @@ void ShieldDialog::render(App* app)
if (error.empty()) {
s_operation_id = result.value("opid", "");
s_status_message = "Operation submitted: " + s_operation_id;
Notifications::instance().success("Merge operation started");
Notifications::instance().success(TR("merge_started"));
} else {
s_status_message = "Error: " + error;
Notifications::instance().error("Merge failed: " + error);
@@ -246,7 +239,7 @@ void ShieldDialog::render(App* app)
ImGui::SameLine();
if (material::StyledButton("Cancel", ImVec2(cancelBtn.width, 0), S.resolveFont(cancelBtn.font))) {
if (material::StyledButton(TR("cancel"), ImVec2(cancelBtn.width, 0), S.resolveFont(cancelBtn.font))) {
s_open = false;
}
@@ -256,9 +249,9 @@ void ShieldDialog::render(App* app)
ImGui::Separator();
ImGui::Spacing();
ImGui::Text("Operation ID: %s", s_operation_id.c_str());
ImGui::Text(TR("shield_operation_id"), s_operation_id.c_str());
if (material::StyledButton("Check Status", ImVec2(0,0), S.resolveFont(shieldBtn.font))) {
if (material::StyledButton(TR("shield_check_status"), ImVec2(0,0), S.resolveFont(shieldBtn.font))) {
std::string opid = s_operation_id;
if (app->worker()) {
app->worker()->post([rpc = app->rpc(), opid]() -> rpc::RPCWorker::MainCb {
@@ -276,14 +269,14 @@ void ShieldDialog::render(App* app)
auto& op = result[0];
std::string status = op.value("status", "unknown");
if (status == "success") {
s_status_message = "Operation completed successfully!";
Notifications::instance().success("Shield/merge completed!");
s_status_message = TR("shield_completed");
Notifications::instance().success(TR("shield_merge_done"));
} else if (status == "failed") {
std::string errMsg = op.value("error", nlohmann::json{}).value("message", "Unknown error");
s_status_message = "Operation failed: " + errMsg;
Notifications::instance().error("Operation failed: " + errMsg);
} else if (status == "executing") {
s_status_message = "Operation in progress...";
s_status_message = TR("shield_in_progress");
} else {
s_status_message = "Status: " + status;
}