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

@@ -11,6 +11,7 @@
#include "layout.h"
#include "schema/ui_schema.h"
#include "../embedded/IconsMaterialDesign.h"
#include "../util/i18n.h"
#include <cstdio>
#include <cmath>
@@ -34,25 +35,35 @@ enum class NavPage {
};
struct NavItem {
const char* label;
const char* label; // fallback label (English)
NavPage page;
const char* section_label; // if non-null, render section label above this item
const char* section_label; // if non-null, render section label above this item
const char* tr_key; // i18n key for label
const char* section_tr_key; // i18n key for section_label
};
inline const NavItem kNavItems[] = {
{ "Overview", NavPage::Overview, nullptr },
{ "Send", NavPage::Send, nullptr },
{ "Receive", NavPage::Receive, nullptr },
{ "History", NavPage::History, nullptr },
{ "Mining", NavPage::Mining, "TOOLS" },
{ "Market", NavPage::Market, nullptr },
{ "Console", NavPage::Console, "ADVANCED" },
{ "Network", NavPage::Peers, nullptr },
{ "Settings", NavPage::Settings, nullptr },
{ "Overview", NavPage::Overview, nullptr, "overview", nullptr },
{ "Send", NavPage::Send, nullptr, "send", nullptr },
{ "Receive", NavPage::Receive, nullptr, "receive", nullptr },
{ "History", NavPage::History, nullptr, "history", nullptr },
{ "Mining", NavPage::Mining, "TOOLS", "mining", "tools" },
{ "Market", NavPage::Market, nullptr, "market", nullptr },
{ "Console", NavPage::Console, "ADVANCED","console", "advanced" },
{ "Network", NavPage::Peers, nullptr, "network", nullptr },
{ "Settings", NavPage::Settings, nullptr, "settings", nullptr },
};
static_assert(sizeof(kNavItems) / sizeof(kNavItems[0]) == (int)NavPage::Count_,
"kNavItems must match NavPage::Count_");
// Get translated nav label at runtime
inline const char* NavLabel(const NavItem& item) {
return item.tr_key ? TR(item.tr_key) : item.label;
}
inline const char* NavSectionLabel(const NavItem& item) {
return item.section_tr_key ? TR(item.section_tr_key) : item.section_label;
}
// Get the Material Design icon string for a navigation page.
inline const char* GetNavIconMD(NavPage page)
{
@@ -541,7 +552,7 @@ inline bool RenderSidebar(NavPage& current, float sidebarWidth, float contentHei
float olFsz = ScaledFontSize(olFont);
dl->AddText(olFont, olFsz,
ImVec2(wp.x + sbSectionLabelPadLeft, labelY),
ImGui::ColorConvertFloat4ToU32(olCol), item.section_label);
ImGui::ColorConvertFloat4ToU32(olCol), NavSectionLabel(item));
ImGui::Dummy(ImVec2(0, olFsz + 2.0f));
} else if (item.section_label && !showLabels) {
// Collapsed: thin separator instead of label
@@ -609,11 +620,19 @@ inline bool RenderSidebar(NavPage& current, float sidebarWidth, float contentHei
ImU32 textCol = selected ? Primary() : OnSurfaceMedium();
if (showLabels) {
// Measure total width of icon + gap + label, then center
// Measure total width of icon + gap + label, then center.
// If the translated label is too wide, shrink the font to fit.
ImFont* font = selected ? Type().subtitle2() : Type().body2();
float gap = iconLabelGap;
float lblFsz = ScaledFontSize(font);
ImVec2 labelSz = font->CalcTextSizeA(lblFsz, 1000.0f, 0.0f, item.label);
float btnW = indMax.x - indMin.x;
float maxLabelW = btnW - iconS * 2.0f - gap - Layout::spacingXs() * 2;
ImVec2 labelSz = font->CalcTextSizeA(lblFsz, 1000.0f, 0.0f, NavLabel(item));
if (labelSz.x > maxLabelW && maxLabelW > 0) {
float shrink = maxLabelW / labelSz.x;
lblFsz *= shrink;
labelSz = font->CalcTextSizeA(lblFsz, 1000.0f, 0.0f, NavLabel(item));
}
float totalW = iconS * 2.0f + gap + labelSz.x;
float btnCX = (indMin.x + indMax.x) * 0.5f;
float startX = btnCX - totalW * 0.5f;
@@ -625,7 +644,7 @@ inline bool RenderSidebar(NavPage& current, float sidebarWidth, float contentHei
ImVec4 lc = ImGui::ColorConvertU32ToFloat4(textCol);
lc.w *= expandFrac;
dl->AddText(font, lblFsz, ImVec2(labelX, textY),
ImGui::ColorConvertFloat4ToU32(lc), item.label);
ImGui::ColorConvertFloat4ToU32(lc), NavLabel(item));
} else {
float iconCX = (indMin.x + indMax.x) * 0.5f;
DrawNavIcon(dl, item.page, iconCX, iconCY, iconS, textCol);
@@ -633,7 +652,7 @@ inline bool RenderSidebar(NavPage& current, float sidebarWidth, float contentHei
// Tooltip when collapsed + hovered
if (!showLabels && hovered) {
ImGui::SetTooltip("%s", item.label);
ImGui::SetTooltip("%s", NavLabel(item));
}
// ---- Badge indicator ----