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

@@ -113,15 +113,14 @@ void ImportKeyDialog::render(App* app)
auto importBtn = S.button("dialogs.import-key", "import-button");
auto closeBtn = S.button("dialogs.import-key", "close-button");
if (material::BeginOverlayDialog("Import Private Key", &s_open, win.width, 0.94f)) {
if (material::BeginOverlayDialog(TR("import_key_title"), &s_open, win.width, 0.94f)) {
// Warning
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.8f, 0.0f, 1.0f));
ImGui::PushFont(material::Type().iconSmall());
ImGui::Text(ICON_MD_WARNING);
ImGui::PopFont();
ImGui::SameLine(0, 4.0f);
ImGui::TextWrapped("Warning: Never share your private keys! "
"Importing keys from untrusted sources can compromise your wallet.");
ImGui::TextWrapped("%s", TR("import_key_warning"));
ImGui::PopStyleColor();
ImGui::Spacing();
@@ -129,13 +128,11 @@ void ImportKeyDialog::render(App* app)
ImGui::Spacing();
// Key input
ImGui::Text("Private Key(s):");
ImGui::Text("%s", TR("import_key_label"));
ImGui::SameLine();
ImGui::TextDisabled("(?)");
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Enter one or more private keys, one per line.\n"
"Supports both z-address and t-address keys.\n"
"Lines starting with # are treated as comments.");
ImGui::SetTooltip("%s", TR("import_key_tooltip"));
}
if (s_importing) {
@@ -147,7 +144,7 @@ void ImportKeyDialog::render(App* app)
ImVec2(-1, keyInput.height > 0 ? keyInput.height : 150), ImGuiInputTextFlags_AllowTabInput);
// Paste button
if (material::StyledButton("Paste from Clipboard", ImVec2(0,0), S.resolveFont(importBtn.font))) {
if (material::StyledButton(TR("paste_from_clipboard"), ImVec2(0,0), S.resolveFont(importBtn.font))) {
const char* clipboard = ImGui::GetClipboardText();
if (clipboard) {
strncpy(s_key_input, clipboard, sizeof(s_key_input) - 1);
@@ -155,24 +152,24 @@ void ImportKeyDialog::render(App* app)
}
ImGui::SameLine();
if (material::StyledButton("Clear", ImVec2(0,0), S.resolveFont(importBtn.font))) {
if (material::StyledButton(TR("clear"), ImVec2(0,0), S.resolveFont(importBtn.font))) {
s_key_input[0] = '\0';
}
ImGui::Spacing();
// Rescan options
ImGui::Checkbox("Rescan blockchain after import", &s_rescan);
ImGui::Checkbox(TR("import_key_rescan"), &s_rescan);
if (s_rescan) {
ImGui::Indent();
ImGui::Text("Start height:");
ImGui::Text("%s", TR("import_key_start_height"));
ImGui::SameLine();
ImGui::SetNextItemWidth(rescanInput.width);
ImGui::InputInt("##RescanHeight", &s_rescan_height);
if (s_rescan_height < 0) s_rescan_height = 0;
ImGui::SameLine();
ImGui::TextDisabled("(0 = full rescan)");
ImGui::TextDisabled("%s", TR("import_key_full_rescan"));
ImGui::Unindent();
}
@@ -189,13 +186,13 @@ void ImportKeyDialog::render(App* app)
ImGui::BeginDisabled();
}
if (material::StyledButton("Import Key(s)", ImVec2(importBtn.width, 0), S.resolveFont(importBtn.font))) {
if (material::StyledButton(TR("import_key_btn"), ImVec2(importBtn.width, 0), S.resolveFont(importBtn.font))) {
auto keys = splitKeys(s_key_input);
if (keys.empty()) {
Notifications::instance().warning("No valid keys found in input");
Notifications::instance().warning(TR("import_key_no_valid"));
} else if (!app->rpc() || !app->rpc()->isConnected()) {
Notifications::instance().error("Not connected to daemon");
Notifications::instance().error(TR("not_connected"));
} else {
s_importing = true;
s_total_keys = static_cast<int>(keys.size());
@@ -237,7 +234,7 @@ void ImportKeyDialog::render(App* app)
imported, failed);
s_status = buf;
if (imported > 0) {
Notifications::instance().success("Keys imported successfully");
Notifications::instance().success(TR("import_key_success"));
}
};
});
@@ -248,11 +245,11 @@ void ImportKeyDialog::render(App* app)
if (s_importing) {
ImGui::EndDisabled();
ImGui::SameLine();
ImGui::TextDisabled("Importing %d/%d...", s_imported_keys + s_failed_keys, s_total_keys);
ImGui::TextDisabled(TR("import_key_progress"), s_imported_keys + s_failed_keys, s_total_keys);
}
ImGui::SameLine();
if (material::StyledButton("Close", ImVec2(closeBtn.width, 0), S.resolveFont(closeBtn.font))) {
if (material::StyledButton(TR("close"), ImVec2(closeBtn.width, 0), S.resolveFont(closeBtn.font))) {
s_open = false;
}
@@ -271,9 +268,9 @@ void ImportKeyDialog::render(App* app)
ImGui::Spacing();
// Help text
ImGui::TextDisabled("Supported key formats:");
ImGui::BulletText("Z-address spending keys (secret-extended-key-...)");
ImGui::BulletText("T-address WIF private keys");
ImGui::TextDisabled("%s", TR("import_key_formats"));
ImGui::BulletText("%s", TR("import_key_z_format"));
ImGui::BulletText("%s", TR("import_key_t_format"));
material::EndOverlayDialog();
}
}