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

@@ -6,6 +6,7 @@
#include "../../app.h"
#include "../../rpc/rpc_client.h"
#include "../../rpc/rpc_worker.h"
#include "../../util/i18n.h"
#include "../schema/ui_schema.h"
#include "../material/draw_helpers.h"
#include "../theme.h"
@@ -52,15 +53,15 @@ void ValidateAddressDialog::render(App* app)
auto lbl = S.label("dialogs.validate-address", "label");
auto closeBtn = S.button("dialogs.validate-address", "close-button");
if (material::BeginOverlayDialog("Validate Address", &s_open, win.width, 0.94f)) {
ImGui::TextWrapped("Enter a DragonX address to check if it's valid and whether it belongs to this wallet.");
if (material::BeginOverlayDialog(TR("validate_title"), &s_open, win.width, 0.94f)) {
ImGui::TextWrapped("%s", TR("validate_description"));
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
// Address input
ImGui::Text("Address:");
ImGui::Text("%s", TR("address_label"));
ImGui::SetNextItemWidth(-1);
bool enter_pressed = ImGui::InputText("##ValidateAddr", s_address_input, sizeof(s_address_input),
ImGuiInputTextFlags_EnterReturnsTrue);
@@ -74,7 +75,7 @@ void ValidateAddressDialog::render(App* app)
ImGui::BeginDisabled();
}
if (material::StyledButton("Validate", ImVec2(valBtn.width, 0), S.resolveFont(valBtn.font)) || (enter_pressed && can_validate)) {
if (material::StyledButton(TR("validate_btn"), ImVec2(valBtn.width, 0), S.resolveFont(valBtn.font)) || (enter_pressed && can_validate)) {
s_validating = true;
s_validated = false;
s_error_message.clear();
@@ -100,7 +101,7 @@ void ValidateAddressDialog::render(App* app)
if (error.empty()) {
s_is_valid = valid;
s_is_mine = mine;
s_address_type = "Shielded (z-address)";
s_address_type = TR("validate_shielded_type");
} else {
s_error_message = error;
s_is_valid = false;
@@ -126,7 +127,7 @@ void ValidateAddressDialog::render(App* app)
if (error.empty()) {
s_is_valid = valid;
s_is_mine = mine;
s_address_type = "Transparent (t-address)";
s_address_type = TR("validate_transparent_type");
} else {
s_error_message = error;
s_is_valid = false;
@@ -145,7 +146,7 @@ void ValidateAddressDialog::render(App* app)
ImGui::SameLine();
if (material::StyledButton("Paste", ImVec2(pasteBtn.width, 0), S.resolveFont(pasteBtn.font))) {
if (material::StyledButton(TR("paste"), ImVec2(pasteBtn.width, 0), S.resolveFont(pasteBtn.font))) {
const char* clipboard = ImGui::GetClipboardText();
if (clipboard) {
strncpy(s_address_input, clipboard, sizeof(s_address_input) - 1);
@@ -156,7 +157,7 @@ void ValidateAddressDialog::render(App* app)
if (s_validating) {
ImGui::SameLine();
ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "Validating...");
ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "%s", TR("validating"));
}
ImGui::Spacing();
@@ -165,39 +166,39 @@ void ValidateAddressDialog::render(App* app)
// Results
if (s_validated) {
ImGui::Text("Results:");
ImGui::Text("%s", TR("validate_results"));
ImGui::Spacing();
if (!s_error_message.empty()) {
ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "Error: %s", s_error_message.c_str());
} else {
// Valid/Invalid indicator
ImGui::Text("Status:");
ImGui::Text("%s", TR("validate_status"));
ImGui::SameLine(lbl.position);
if (s_is_valid) {
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "VALID");
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "%s", TR("validate_valid"));
} else {
ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "INVALID");
ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "%s", TR("validate_invalid"));
}
if (s_is_valid) {
// Address type
ImGui::Text("Type:");
ImGui::Text("%s", TR("validate_type"));
ImGui::SameLine(lbl.position);
ImGui::Text("%s", s_address_type.c_str());
// Is mine?
ImGui::Text("Ownership:");
ImGui::Text("%s", TR("validate_ownership"));
ImGui::SameLine(lbl.position);
if (s_is_mine) {
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "This wallet owns this address");
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "%s", TR("validate_is_mine"));
} else {
ImGui::TextDisabled("Not owned by this wallet");
ImGui::TextDisabled("%s", TR("validate_not_mine"));
}
}
}
} else if (!app->isConnected()) {
ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), "Not connected to daemon");
ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), "%s", TR("not_connected"));
}
ImGui::Spacing();
@@ -205,7 +206,7 @@ void ValidateAddressDialog::render(App* app)
// Close button at bottom
float button_width = closeBtn.width;
ImGui::SetCursorPosX((ImGui::GetWindowWidth() - button_width) / 2.0f);
if (material::StyledButton("Close", ImVec2(button_width, 0), S.resolveFont(closeBtn.font))) {
if (material::StyledButton(TR("close"), ImVec2(button_width, 0), S.resolveFont(closeBtn.font))) {
s_open = false;
}
material::EndOverlayDialog();