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:
@@ -27,6 +27,14 @@
|
||||
namespace dragonx {
|
||||
namespace ui {
|
||||
|
||||
// Helper: build "TranslatedLabel##id" for ImGui widgets that use label as ID
|
||||
static std::string TrId(const char* tr_key, const char* id) {
|
||||
std::string s = TR(tr_key);
|
||||
s += "##";
|
||||
s += id;
|
||||
return s;
|
||||
}
|
||||
|
||||
// Settings state - these get loaded from Settings on window open
|
||||
static bool s_initialized = false;
|
||||
static int s_language_index = 0;
|
||||
@@ -141,17 +149,17 @@ void RenderSettingsWindow(App* app, bool* p_open)
|
||||
auto saveBtn = S.button("dialogs.settings", "save-button");
|
||||
auto cancelBtn = S.button("dialogs.settings", "cancel-button");
|
||||
|
||||
if (!material::BeginOverlayDialog("Settings", p_open, win.width, 0.94f)) {
|
||||
if (!material::BeginOverlayDialog(TR("settings"), p_open, win.width, 0.94f)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ImGui::BeginTabBar("SettingsTabs")) {
|
||||
// General settings tab
|
||||
if (ImGui::BeginTabItem("General")) {
|
||||
if (ImGui::BeginTabItem(TR("general"))) {
|
||||
ImGui::Spacing();
|
||||
|
||||
// Skin/theme selection
|
||||
ImGui::Text("Theme:");
|
||||
ImGui::Text("%s", TR("theme"));
|
||||
ImGui::SameLine(lbl.position);
|
||||
|
||||
// Active skin combo (populated from SkinManager)
|
||||
@@ -172,7 +180,7 @@ void RenderSettingsWindow(App* app, bool* p_open)
|
||||
ImGui::SetNextItemWidth(cmb.width);
|
||||
if (ImGui::BeginCombo("##Theme", active_preview.c_str())) {
|
||||
// Bundled themes header
|
||||
ImGui::TextDisabled("Built-in");
|
||||
ImGui::TextDisabled("%s", TR("settings_builtin"));
|
||||
ImGui::Separator();
|
||||
for (size_t i = 0; i < skins.size(); i++) {
|
||||
const auto& skin = skins[i];
|
||||
@@ -198,7 +206,7 @@ void RenderSettingsWindow(App* app, bool* p_open)
|
||||
}
|
||||
if (has_custom) {
|
||||
ImGui::Spacing();
|
||||
ImGui::TextDisabled("Custom");
|
||||
ImGui::TextDisabled("%s", TR("settings_custom"));
|
||||
ImGui::Separator();
|
||||
for (size_t i = 0; i < skins.size(); i++) {
|
||||
const auto& skin = skins[i];
|
||||
@@ -236,7 +244,7 @@ void RenderSettingsWindow(App* app, bool* p_open)
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("Hotkey: Ctrl+Left/Right to cycle themes");
|
||||
ImGui::SetTooltip("%s", TR("tt_theme_hotkey"));
|
||||
|
||||
// Show indicator if custom theme is active
|
||||
if (active_is_custom) {
|
||||
@@ -245,7 +253,7 @@ void RenderSettingsWindow(App* app, bool* p_open)
|
||||
ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), ICON_CUSTOM_THEME);
|
||||
ImGui::PopFont();
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Custom theme active");
|
||||
ImGui::SetTooltip("%s", TR("tt_custom_theme"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,14 +265,14 @@ void RenderSettingsWindow(App* app, bool* p_open)
|
||||
}
|
||||
ImGui::PopFont();
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Scan for new themes.\nPlace theme folders in:\n%s",
|
||||
ImGui::SetTooltip(TR("tt_scan_themes"),
|
||||
schema::SkinManager::getUserSkinsDirectory().c_str());
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
// Language selection
|
||||
ImGui::Text("Language:");
|
||||
ImGui::Text("%s", TR("language"));
|
||||
ImGui::SameLine(lbl.position);
|
||||
auto& i18n = util::I18n::instance();
|
||||
const auto& languages = i18n.getAvailableLanguages();
|
||||
@@ -283,17 +291,17 @@ void RenderSettingsWindow(App* app, bool* p_open)
|
||||
std::advance(it, s_language_index);
|
||||
i18n.loadLanguage(it->first);
|
||||
}
|
||||
ImGui::TextDisabled(" Note: Some text requires restart to update");
|
||||
ImGui::TextDisabled(" %s", TR("settings_language_note"));
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
// Acrylic Effects settings
|
||||
ImGui::Text("Visual Effects");
|
||||
ImGui::Text("%s", TR("settings_visual_effects"));
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("Acrylic Level:");
|
||||
ImGui::Text("%s", TR("settings_acrylic_level"));
|
||||
ImGui::SameLine(lbl.position);
|
||||
ImGui::SetNextItemWidth(cmb.width);
|
||||
{
|
||||
@@ -309,10 +317,10 @@ void RenderSettingsWindow(App* app, bool* p_open)
|
||||
effects::ImGuiAcrylic::ApplyBlurAmount(s_blur_amount);
|
||||
}
|
||||
}
|
||||
ImGui::TextDisabled(" Blur amount (0%% = off, 100%% = maximum)");
|
||||
ImGui::TextDisabled(" %s", TR("tt_blur"));
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Text("Noise Opacity:");
|
||||
ImGui::Text("%s", TR("settings_noise_opacity"));
|
||||
ImGui::SameLine(lbl.position);
|
||||
ImGui::SetNextItemWidth(cmb.width);
|
||||
{
|
||||
@@ -326,86 +334,86 @@ void RenderSettingsWindow(App* app, bool* p_open)
|
||||
effects::ImGuiAcrylic::SetNoiseOpacity(s_noise_opacity);
|
||||
}
|
||||
}
|
||||
ImGui::TextDisabled(" Grain texture intensity (0%% = off, 100%% = maximum)");
|
||||
ImGui::TextDisabled(" %s", TR("tt_noise"));
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
// Accessibility: Reduced transparency
|
||||
if (ImGui::Checkbox("Reduce transparency", &s_reduced_transparency)) {
|
||||
if (ImGui::Checkbox(TrId("settings_reduce_transparency", "reduce_trans").c_str(), &s_reduced_transparency)) {
|
||||
effects::ImGuiAcrylic::SetReducedTransparency(s_reduced_transparency);
|
||||
}
|
||||
ImGui::TextDisabled(" Use solid colors instead of blur effects (accessibility)");
|
||||
ImGui::TextDisabled(" %s", TR("settings_solid_colors_desc"));
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
if (ImGui::Checkbox("Simple background", &s_gradient_background)) {
|
||||
if (ImGui::Checkbox(TrId("simple_background", "simple_bg").c_str(), &s_gradient_background)) {
|
||||
schema::SkinManager::instance().setGradientMode(s_gradient_background);
|
||||
}
|
||||
ImGui::TextDisabled(" Replace textured backgrounds with smooth gradients");
|
||||
ImGui::TextDisabled(" %s", TR("settings_gradient_desc"));
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
// Privacy settings
|
||||
ImGui::Text("Privacy");
|
||||
ImGui::Text("%s", TR("settings_privacy"));
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Checkbox("Save shielded transaction history locally", &s_save_ztxs);
|
||||
ImGui::TextDisabled(" Stores z-addr transactions in a local file for viewing");
|
||||
ImGui::Checkbox(TrId("settings_save_shielded_local", "save_ztx_w").c_str(), &s_save_ztxs);
|
||||
ImGui::TextDisabled(" %s", TR("settings_save_shielded_desc"));
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Checkbox("Auto-shield transparent funds", &s_auto_shield);
|
||||
ImGui::TextDisabled(" Automatically move transparent funds to shielded addresses");
|
||||
ImGui::Checkbox(TrId("settings_auto_shield_funds", "auto_shld_w").c_str(), &s_auto_shield);
|
||||
ImGui::TextDisabled(" %s", TR("settings_auto_shield_desc"));
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Checkbox("Use Tor for network connections", &s_use_tor);
|
||||
ImGui::TextDisabled(" Route all connections through Tor for enhanced privacy");
|
||||
ImGui::Checkbox(TrId("settings_use_tor_network", "tor_w").c_str(), &s_use_tor);
|
||||
ImGui::TextDisabled(" %s", TR("settings_tor_desc"));
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
// Other settings
|
||||
ImGui::Text("Other");
|
||||
ImGui::Text("%s", TR("settings_other"));
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Checkbox("Allow custom transaction fees", &s_allow_custom_fees);
|
||||
ImGui::Checkbox("Fetch price data from CoinGecko", &s_fetch_prices);
|
||||
ImGui::Checkbox(TrId("custom_fees", "fees_w").c_str(), &s_allow_custom_fees);
|
||||
ImGui::Checkbox(TrId("fetch_prices", "prices_w").c_str(), &s_fetch_prices);
|
||||
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
// Connection settings tab
|
||||
if (ImGui::BeginTabItem("Connection")) {
|
||||
if (ImGui::BeginTabItem(TR("settings_connection"))) {
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("RPC Connection");
|
||||
ImGui::TextDisabled("Configure connection to dragonxd daemon");
|
||||
ImGui::Text("%s", TR("settings_rpc_connection"));
|
||||
ImGui::TextDisabled("%s", TR("settings_configure_rpc"));
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("Host:");
|
||||
ImGui::Text("%s", TR("rpc_host"));
|
||||
ImGui::SameLine(connLbl.position);
|
||||
ImGui::SetNextItemWidth(cmb.width);
|
||||
ImGui::InputText("##RPCHost", s_rpc_host, sizeof(s_rpc_host));
|
||||
|
||||
ImGui::Text("Port:");
|
||||
ImGui::Text("%s", TR("rpc_port"));
|
||||
ImGui::SameLine(connLbl.position);
|
||||
ImGui::SetNextItemWidth(portInput.width);
|
||||
ImGui::InputText("##RPCPort", s_rpc_port, sizeof(s_rpc_port));
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("Username:");
|
||||
ImGui::Text("%s", TR("rpc_user"));
|
||||
ImGui::SameLine(connLbl.position);
|
||||
ImGui::SetNextItemWidth(cmb.width);
|
||||
ImGui::InputText("##RPCUser", s_rpc_user, sizeof(s_rpc_user));
|
||||
|
||||
ImGui::Text("Password:");
|
||||
ImGui::Text("%s", TR("rpc_pass"));
|
||||
ImGui::SameLine(connLbl.position);
|
||||
ImGui::SetNextItemWidth(cmb.width);
|
||||
ImGui::InputText("##RPCPassword", s_rpc_password, sizeof(s_rpc_password),
|
||||
@@ -415,11 +423,11 @@ void RenderSettingsWindow(App* app, bool* p_open)
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::TextDisabled("Note: Connection settings are usually auto-detected from DRAGONX.conf");
|
||||
ImGui::TextDisabled("%s", TR("settings_rpc_note"));
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
if (material::StyledButton("Test Connection", ImVec2(0,0), S.resolveFont("button"))) {
|
||||
if (material::StyledButton(TR("test_connection"), ImVec2(0,0), S.resolveFont("button"))) {
|
||||
if (app->rpc()) {
|
||||
app->rpc()->getInfo([](const nlohmann::json& result, const std::string& error) {
|
||||
if (error.empty()) {
|
||||
@@ -439,15 +447,15 @@ void RenderSettingsWindow(App* app, bool* p_open)
|
||||
}
|
||||
|
||||
// Wallet tab
|
||||
if (ImGui::BeginTabItem("Wallet")) {
|
||||
if (ImGui::BeginTabItem(TR("wallet"))) {
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("Wallet Maintenance");
|
||||
ImGui::Text("%s", TR("settings_wallet_maintenance"));
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
if (material::StyledButton("Rescan Blockchain", ImVec2(walletBtn.width, 0), S.resolveFont(walletBtn.font))) {
|
||||
if (material::StyledButton(TR("rescan"), ImVec2(walletBtn.width, 0), S.resolveFont(walletBtn.font))) {
|
||||
if (app->rpc()) {
|
||||
// Start rescan from block 0
|
||||
app->rpc()->rescanBlockchain(0, [](const nlohmann::json& result, const std::string& error) {
|
||||
@@ -465,45 +473,41 @@ void RenderSettingsWindow(App* app, bool* p_open)
|
||||
Notifications::instance().error("RPC client not initialized");
|
||||
}
|
||||
}
|
||||
ImGui::TextDisabled(" Rescan blockchain for missing transactions");
|
||||
ImGui::TextDisabled(" %s", TR("settings_rescan_desc"));
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
static bool s_confirm_clear_ztx = false;
|
||||
if (material::StyledButton("Clear Saved Z-Transaction History", ImVec2(walletBtn.width, 0), S.resolveFont(walletBtn.font))) {
|
||||
if (material::StyledButton(TR("settings_clear_ztx_long"), ImVec2(walletBtn.width, 0), S.resolveFont(walletBtn.font))) {
|
||||
s_confirm_clear_ztx = true;
|
||||
}
|
||||
ImGui::TextDisabled(" Delete locally stored shielded transaction data");
|
||||
ImGui::TextDisabled(" %s", TR("settings_clear_ztx_desc"));
|
||||
|
||||
// Confirmation dialog
|
||||
if (s_confirm_clear_ztx) {
|
||||
if (material::BeginOverlayDialog("Confirm Clear Z-Tx History", &s_confirm_clear_ztx, 480.0f, 0.94f)) {
|
||||
if (material::BeginOverlayDialog(TR("confirm_clear_ztx_title"), &s_confirm_clear_ztx, 480.0f, 0.94f)) {
|
||||
ImGui::PushFont(material::Type().iconLarge());
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), ICON_MD_WARNING);
|
||||
ImGui::PopFont();
|
||||
ImGui::SameLine();
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), "Warning");
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), "%s", TR("warning"));
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::TextWrapped(
|
||||
"Clearing z-transaction history may cause your shielded balance to show as 0 "
|
||||
"until a wallet rescan is performed.");
|
||||
ImGui::TextWrapped("%s", TR("confirm_clear_ztx_warning1"));
|
||||
ImGui::Spacing();
|
||||
ImGui::TextWrapped(
|
||||
"If this happens, you will need to re-import your z-address private keys with "
|
||||
"rescan enabled to recover your balance.");
|
||||
ImGui::TextWrapped("%s", TR("confirm_clear_ztx_warning2"));
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
float btnW = (ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x) * 0.5f;
|
||||
if (ImGui::Button("Cancel", ImVec2(btnW, 40))) {
|
||||
if (ImGui::Button(TR("cancel"), ImVec2(btnW, 40))) {
|
||||
s_confirm_clear_ztx = false;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.8f, 0.2f, 0.2f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.9f, 0.3f, 0.3f, 1.0f));
|
||||
if (ImGui::Button("Clear Anyway", ImVec2(btnW, 40))) {
|
||||
if (ImGui::Button(TrId("clear_anyway", "clear_ztx_w").c_str(), ImVec2(btnW, 40))) {
|
||||
std::string ztx_file = util::Platform::getDragonXDataDir() + "ztx_history.json";
|
||||
if (util::Platform::deleteFile(ztx_file)) {
|
||||
Notifications::instance().success("Z-transaction history cleared");
|
||||
@@ -521,7 +525,7 @@ void RenderSettingsWindow(App* app, bool* p_open)
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("Wallet Info");
|
||||
ImGui::Text("%s", TR("settings_wallet_info"));
|
||||
ImGui::Spacing();
|
||||
|
||||
// Get actual wallet size
|
||||
@@ -529,35 +533,35 @@ void RenderSettingsWindow(App* app, bool* p_open)
|
||||
uint64_t wallet_size = util::Platform::getFileSize(wallet_path);
|
||||
if (wallet_size > 0) {
|
||||
std::string size_str = util::Platform::formatFileSize(wallet_size);
|
||||
ImGui::Text("Wallet file size: %s", size_str.c_str());
|
||||
ImGui::Text(TR("settings_wallet_file_size"), size_str.c_str());
|
||||
} else {
|
||||
ImGui::TextDisabled("Wallet file not found");
|
||||
ImGui::TextDisabled("%s", TR("settings_wallet_not_found"));
|
||||
}
|
||||
ImGui::Text("Wallet location: %s", wallet_path.c_str());
|
||||
ImGui::Text(TR("settings_wallet_location"), wallet_path.c_str());
|
||||
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
// Explorer tab
|
||||
if (ImGui::BeginTabItem("Explorer")) {
|
||||
if (ImGui::BeginTabItem(TR("explorer"))) {
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("Block Explorer URLs");
|
||||
ImGui::TextDisabled("Configure external block explorer links");
|
||||
ImGui::Text("%s", TR("settings_block_explorer_urls"));
|
||||
ImGui::TextDisabled("%s", TR("settings_configure_explorer"));
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("Transaction URL:");
|
||||
ImGui::Text("%s", TR("transaction_url"));
|
||||
ImGui::SetNextItemWidth(-1);
|
||||
ImGui::InputText("##TxExplorer", s_tx_explorer, sizeof(s_tx_explorer));
|
||||
|
||||
ImGui::Text("Address URL:");
|
||||
ImGui::Text("%s", TR("address_url"));
|
||||
ImGui::SetNextItemWidth(-1);
|
||||
ImGui::InputText("##AddrExplorer", s_addr_explorer, sizeof(s_addr_explorer));
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::TextDisabled("URLs should include a trailing slash. The txid/address will be appended.");
|
||||
ImGui::TextDisabled("%s", TR("settings_explorer_hint"));
|
||||
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
@@ -570,13 +574,13 @@ void RenderSettingsWindow(App* app, bool* p_open)
|
||||
ImGui::Spacing();
|
||||
|
||||
// Save/Cancel buttons
|
||||
if (material::StyledButton("Save", ImVec2(saveBtn.width, 0), S.resolveFont(saveBtn.font))) {
|
||||
if (material::StyledButton(TR("save"), ImVec2(saveBtn.width, 0), S.resolveFont(saveBtn.font))) {
|
||||
saveSettingsFromUI(app->settings());
|
||||
Notifications::instance().success("Settings saved");
|
||||
*p_open = false;
|
||||
}
|
||||
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))) {
|
||||
// Reload settings to revert changes
|
||||
loadSettingsToUI(app->settings());
|
||||
// Revert skin to what was active when settings opened
|
||||
|
||||
Reference in New Issue
Block a user