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

@@ -99,12 +99,12 @@ void BlockInfoDialog::render(App* app)
auto hashBackLbl = S.label("dialogs.block-info", "hash-back-label");
auto closeBtn = S.button("dialogs.block-info", "close-button");
if (material::BeginOverlayDialog("Block Information", &s_open, win.width, 0.94f)) {
if (material::BeginOverlayDialog(TR("block_info_title"), &s_open, win.width, 0.94f)) {
auto* rpc = app->rpc();
const auto& state = app->getWalletState();
// Height input
ImGui::Text("Block Height:");
ImGui::Text("%s", TR("block_height"));
ImGui::SetNextItemWidth(heightInput.width);
ImGui::InputInt("##Height", &s_height);
if (s_height < 1) s_height = 1;
@@ -123,7 +123,7 @@ void BlockInfoDialog::render(App* app)
ImGui::BeginDisabled();
}
if (material::StyledButton("Get Block Info", ImVec2(0,0), S.resolveFont(closeBtn.font))) {
if (material::StyledButton(TR("block_get_info"), ImVec2(0,0), S.resolveFont(closeBtn.font))) {
if (rpc && rpc->isConnected()) {
s_loading = true;
s_error.clear();
@@ -138,7 +138,7 @@ void BlockInfoDialog::render(App* app)
if (s_loading) {
ImGui::EndDisabled();
ImGui::SameLine();
ImGui::TextDisabled("Loading...");
ImGui::TextDisabled("%s", TR("loading"));
}
ImGui::Spacing();
@@ -155,23 +155,23 @@ void BlockInfoDialog::render(App* app)
// Block info display
if (s_has_data) {
// Block hash
ImGui::Text("Block Hash:");
ImGui::Text("%s", TR("block_hash"));
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6f, 0.8f, 1.0f, 1.0f));
ImGui::TextWrapped("%s", s_block_hash.c_str());
ImGui::PopStyleColor();
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Click to copy");
ImGui::SetTooltip("%s", TR("click_to_copy"));
}
if (ImGui::IsItemClicked()) {
ImGui::SetClipboardText(s_block_hash.c_str());
Notifications::instance().success("Block hash copied");
Notifications::instance().success(TR("block_hash_copied"));
}
ImGui::Spacing();
// Timestamp
ImGui::Text("Timestamp:");
ImGui::Text("%s", TR("block_timestamp"));
ImGui::SameLine(lbl.position);
if (s_block_time > 0) {
std::time_t t = static_cast<std::time_t>(s_block_time);
@@ -179,21 +179,21 @@ void BlockInfoDialog::render(App* app)
std::strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", std::localtime(&t));
ImGui::Text("%s", time_buf);
} else {
ImGui::TextDisabled("Unknown");
ImGui::TextDisabled("%s", TR("unknown"));
}
// Confirmations
ImGui::Text("Confirmations:");
ImGui::Text("%s", TR("confirmations"));
ImGui::SameLine(lbl.position);
ImGui::Text("%d", s_confirmations);
// Transaction count
ImGui::Text("Transactions:");
ImGui::Text("%s", TR("block_transactions"));
ImGui::SameLine(lbl.position);
ImGui::Text("%d", s_tx_count);
// Size
ImGui::Text("Size:");
ImGui::Text("%s", TR("block_size"));
ImGui::SameLine(lbl.position);
if (s_block_size > 1024 * 1024) {
ImGui::Text("%.2f MB", s_block_size / (1024.0 * 1024.0));
@@ -204,12 +204,12 @@ void BlockInfoDialog::render(App* app)
}
// Difficulty
ImGui::Text("Difficulty:");
ImGui::Text("%s", TR("difficulty"));
ImGui::SameLine(lbl.position);
ImGui::Text("%.4f", s_difficulty);
// Bits
ImGui::Text("Bits:");
ImGui::Text("%s", TR("block_bits"));
ImGui::SameLine(lbl.position);
ImGui::Text("%s", s_bits.c_str());
@@ -218,7 +218,7 @@ void BlockInfoDialog::render(App* app)
ImGui::Spacing();
// Merkle root
ImGui::Text("Merkle Root:");
ImGui::Text("%s", TR("block_merkle_root"));
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.7f, 0.7f, 0.7f, 1.0f));
ImGui::TextWrapped("%s", s_merkle_root.c_str());
ImGui::PopStyleColor();
@@ -227,7 +227,7 @@ void BlockInfoDialog::render(App* app)
// Previous block
if (!s_prev_hash.empty()) {
ImGui::Text("Previous Block:");
ImGui::Text("%s", TR("block_previous"));
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6f, 0.8f, 1.0f, 1.0f));
// Truncate for display
@@ -239,7 +239,7 @@ void BlockInfoDialog::render(App* app)
ImGui::PopStyleColor();
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Click to view previous block");
ImGui::SetTooltip("%s", TR("block_click_prev"));
}
if (ImGui::IsItemClicked() && s_height > 1) {
s_height--;
@@ -249,7 +249,7 @@ void BlockInfoDialog::render(App* app)
// Next block
if (!s_next_hash.empty()) {
ImGui::Text("Next Block:");
ImGui::Text("%s", TR("block_next"));
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6f, 0.8f, 1.0f, 1.0f));
// Truncate for display
@@ -261,7 +261,7 @@ void BlockInfoDialog::render(App* app)
ImGui::PopStyleColor();
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Click to view next block");
ImGui::SetTooltip("%s", TR("block_click_next"));
}
if (ImGui::IsItemClicked()) {
s_height++;
@@ -276,7 +276,7 @@ void BlockInfoDialog::render(App* app)
// Navigation buttons
if (s_has_data) {
if (s_height > 1) {
if (material::StyledButton("<< Previous", ImVec2(0,0), S.resolveFont(closeBtn.font))) {
if (material::StyledButton(TR("block_nav_prev"), ImVec2(0,0), S.resolveFont(closeBtn.font))) {
s_height--;
s_has_data = false;
s_error.clear();
@@ -285,7 +285,7 @@ void BlockInfoDialog::render(App* app)
}
if (!s_next_hash.empty()) {
if (material::StyledButton("Next >>", ImVec2(0,0), S.resolveFont(closeBtn.font))) {
if (material::StyledButton(TR("block_nav_next"), ImVec2(0,0), S.resolveFont(closeBtn.font))) {
s_height++;
s_has_data = false;
s_error.clear();
@@ -295,7 +295,7 @@ void BlockInfoDialog::render(App* app)
// Close button at bottom
ImGui::SetCursorPosY(ImGui::GetWindowHeight() - 40);
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;
}
material::EndOverlayDialog();