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:
@@ -76,24 +76,17 @@ void ShieldDialog::render(App* app)
|
||||
auto cancelBtn = S.button("dialogs.shield", "cancel-button");
|
||||
|
||||
const char* title = (s_mode == Mode::ShieldCoinbase)
|
||||
? "Shield Coinbase Rewards"
|
||||
: "Merge to Address";
|
||||
? TR("shield_title")
|
||||
: TR("merge_title");
|
||||
|
||||
if (material::BeginOverlayDialog(title, &s_open, win.width, 0.94f)) {
|
||||
const auto& state = app->getWalletState();
|
||||
|
||||
// Description
|
||||
if (s_mode == Mode::ShieldCoinbase) {
|
||||
ImGui::TextWrapped(
|
||||
"Shield your mining rewards by sending coinbase outputs from "
|
||||
"transparent addresses to a shielded address. This improves "
|
||||
"privacy by hiding your mining income."
|
||||
);
|
||||
ImGui::TextWrapped("%s", TR("shield_description"));
|
||||
} else {
|
||||
ImGui::TextWrapped(
|
||||
"Merge multiple UTXOs into a single shielded address. This can "
|
||||
"help reduce wallet size and improve privacy."
|
||||
);
|
||||
ImGui::TextWrapped("%s", TR("merge_description"));
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
@@ -102,18 +95,18 @@ void ShieldDialog::render(App* app)
|
||||
|
||||
// From address (for shield coinbase)
|
||||
if (s_mode == Mode::ShieldCoinbase) {
|
||||
ImGui::Text("From Address:");
|
||||
ImGui::Text("%s", TR("shield_from_address"));
|
||||
ImGui::SetNextItemWidth(-1);
|
||||
ImGui::InputText("##FromAddr", s_from_address, sizeof(s_from_address));
|
||||
ImGui::TextDisabled("Use '*' to shield from all transparent addresses");
|
||||
ImGui::TextDisabled("%s", TR("shield_wildcard_hint"));
|
||||
ImGui::Spacing();
|
||||
}
|
||||
|
||||
// To address (z-address dropdown)
|
||||
ImGui::Text("To Address (Shielded):");
|
||||
ImGui::Text("%s", TR("shield_to_address"));
|
||||
|
||||
// Get z-addresses for dropdown
|
||||
std::string to_display = s_to_address[0] ? s_to_address : "Select z-address...";
|
||||
std::string to_display = s_to_address[0] ? s_to_address : TR("shield_select_z");
|
||||
if (to_display.length() > static_cast<size_t>(addrLbl.truncate)) {
|
||||
to_display = to_display.substr(0, addrFrontLbl.truncate) + "..." + to_display.substr(to_display.length() - addrBackLbl.truncate);
|
||||
}
|
||||
@@ -142,7 +135,7 @@ void ShieldDialog::render(App* app)
|
||||
ImGui::Spacing();
|
||||
|
||||
// Fee
|
||||
ImGui::Text("Fee:");
|
||||
ImGui::Text("%s", TR("fee_label"));
|
||||
ImGui::SetNextItemWidth(feeInput.width);
|
||||
ImGui::InputDouble("##Fee", &s_fee, 0.0001, 0.001, "%.8f");
|
||||
ImGui::SameLine();
|
||||
@@ -151,11 +144,11 @@ void ShieldDialog::render(App* app)
|
||||
ImGui::Spacing();
|
||||
|
||||
// UTXO limit
|
||||
ImGui::Text("UTXO Limit:");
|
||||
ImGui::Text("%s", TR("shield_utxo_limit"));
|
||||
ImGui::SetNextItemWidth(utxoInput.width);
|
||||
ImGui::InputInt("##Limit", &s_utxo_limit);
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("Max UTXOs per operation");
|
||||
ImGui::TextDisabled("%s", TR("shield_max_utxos"));
|
||||
if (s_utxo_limit < 1) s_utxo_limit = 1;
|
||||
if (s_utxo_limit > 100) s_utxo_limit = 100;
|
||||
|
||||
@@ -178,7 +171,7 @@ void ShieldDialog::render(App* app)
|
||||
|
||||
if (!can_submit) ImGui::BeginDisabled();
|
||||
|
||||
const char* btn_label = (s_mode == Mode::ShieldCoinbase) ? "Shield Funds" : "Merge Funds";
|
||||
const char* btn_label = (s_mode == Mode::ShieldCoinbase) ? TR("shield_funds") : TR("merge_funds");
|
||||
if (material::StyledButton(btn_label, ImVec2(shieldBtn.width, 0), S.resolveFont(shieldBtn.font))) {
|
||||
s_operation_pending = true;
|
||||
s_status_message = "Submitting operation...";
|
||||
@@ -201,7 +194,7 @@ void ShieldDialog::render(App* app)
|
||||
if (error.empty()) {
|
||||
s_operation_id = result.value("opid", "");
|
||||
s_status_message = "Operation submitted: " + s_operation_id;
|
||||
Notifications::instance().success("Shield operation started");
|
||||
Notifications::instance().success(TR("shield_started"));
|
||||
} else {
|
||||
s_status_message = "Error: " + error;
|
||||
Notifications::instance().error("Shield failed: " + error);
|
||||
@@ -231,7 +224,7 @@ void ShieldDialog::render(App* app)
|
||||
if (error.empty()) {
|
||||
s_operation_id = result.value("opid", "");
|
||||
s_status_message = "Operation submitted: " + s_operation_id;
|
||||
Notifications::instance().success("Merge operation started");
|
||||
Notifications::instance().success(TR("merge_started"));
|
||||
} else {
|
||||
s_status_message = "Error: " + error;
|
||||
Notifications::instance().error("Merge failed: " + error);
|
||||
@@ -246,7 +239,7 @@ void ShieldDialog::render(App* app)
|
||||
|
||||
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))) {
|
||||
s_open = false;
|
||||
}
|
||||
|
||||
@@ -256,9 +249,9 @@ void ShieldDialog::render(App* app)
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("Operation ID: %s", s_operation_id.c_str());
|
||||
ImGui::Text(TR("shield_operation_id"), s_operation_id.c_str());
|
||||
|
||||
if (material::StyledButton("Check Status", ImVec2(0,0), S.resolveFont(shieldBtn.font))) {
|
||||
if (material::StyledButton(TR("shield_check_status"), ImVec2(0,0), S.resolveFont(shieldBtn.font))) {
|
||||
std::string opid = s_operation_id;
|
||||
if (app->worker()) {
|
||||
app->worker()->post([rpc = app->rpc(), opid]() -> rpc::RPCWorker::MainCb {
|
||||
@@ -276,14 +269,14 @@ void ShieldDialog::render(App* app)
|
||||
auto& op = result[0];
|
||||
std::string status = op.value("status", "unknown");
|
||||
if (status == "success") {
|
||||
s_status_message = "Operation completed successfully!";
|
||||
Notifications::instance().success("Shield/merge completed!");
|
||||
s_status_message = TR("shield_completed");
|
||||
Notifications::instance().success(TR("shield_merge_done"));
|
||||
} else if (status == "failed") {
|
||||
std::string errMsg = op.value("error", nlohmann::json{}).value("message", "Unknown error");
|
||||
s_status_message = "Operation failed: " + errMsg;
|
||||
Notifications::instance().error("Operation failed: " + errMsg);
|
||||
} else if (status == "executing") {
|
||||
s_status_message = "Operation in progress...";
|
||||
s_status_message = TR("shield_in_progress");
|
||||
} else {
|
||||
s_status_message = "Status: " + status;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user