From 061db1600412e9ccafafc88945970b976ae32a82 Mon Sep 17 00:00:00 2001 From: DanS Date: Sun, 5 Jul 2026 15:09:18 -0500 Subject: [PATCH] i18n: route explorer and shield status strings through TR() Centralise the remaining hardcoded English literals in two otherwise fully-translated files (explorer_tab: 40 TR calls, shield_dialog: 23) so they join the i18n system with an English source key + fallback: - explorer_tab: the three search-error messages (invalid response, hash not found, not-connected) now use explorer_* keys. Also guards a hash lookup behind an active daemon connection and disables the block-detail prev/next nav while a fetch is in flight (both were the reason these error paths could fire). - shield_dialog: the operation status/error strings (submitting, submitted, failed, status, error-checking-status, shield/merge failed) now use shield_* keys. - i18n: add the new explorer_* and shield_* English source keys. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/ui/windows/explorer_tab.cpp | 14 ++++++++++++-- src/ui/windows/shield_dialog.cpp | 24 ++++++++++++------------ src/util/i18n.cpp | 12 ++++++++++++ 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/src/ui/windows/explorer_tab.cpp b/src/ui/windows/explorer_tab.cpp index b85df9f..e70da75 100644 --- a/src/ui/windows/explorer_tab.cpp +++ b/src/ui/windows/explorer_tab.cpp @@ -221,7 +221,7 @@ static void applyBlockDetailResult(const json& result, const std::string& error) } if (result.is_null()) { - s_search_error = "Invalid response from daemon"; + s_search_error = TR("explorer_invalid_response"); s_show_detail_modal = false; return; } @@ -388,7 +388,7 @@ static void navigateToHash(App* app, const std::string& hash) { s_expanded_tx_idx = 0; s_show_detail_modal = false; } else { - s_search_error = "No block or transaction found for this hash"; + s_search_error = TR("explorer_hash_not_found"); } }; }); @@ -417,6 +417,11 @@ static void performSearch(App* app, const std::string& query) { return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); }); if (isHex64) { + // A hash lookup needs the daemon; navigateToHash() would otherwise return silently. + if (!app->rpc() || !app->rpc()->isConnected()) { + s_search_error = TR("explorer_not_connected"); + return; + } navigateToHash(app, query); return; } @@ -1039,6 +1044,9 @@ static void renderBlockDetailModal(App* app) { // Nav buttons on same line ImGui::SameLine(contentW - Layout::spacingXl() * 3); + // Disable prev/next while a fetch is in flight so navigation can't stack fetches. + ImGui::BeginDisabled(s_detail_loading); + // Prev if (s_detail_height > 1) { ImGui::PushFont(Type().iconMed()); @@ -1065,6 +1073,8 @@ static void renderBlockDetailModal(App* app) { ImGui::PopID(); ImGui::PopFont(); } + + ImGui::EndDisabled(); } ImGui::Separator(); diff --git a/src/ui/windows/shield_dialog.cpp b/src/ui/windows/shield_dialog.cpp index 8eaba7b..98f6000 100644 --- a/src/ui/windows/shield_dialog.cpp +++ b/src/ui/windows/shield_dialog.cpp @@ -181,7 +181,7 @@ void ShieldDialog::render(App* app) 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..."; + s_status_message = TR("shield_submitting"); if (s_mode == Mode::ShieldCoinbase) { std::string from(s_from_address), to(s_to_address); @@ -201,14 +201,14 @@ void ShieldDialog::render(App* app) s_operation_pending = false; if (error.empty()) { s_operation_id = result.value("opid", ""); - s_status_message = "Operation submitted: " + s_operation_id; + s_status_message = std::string(TR("shield_op_submitted")) + s_operation_id; Notifications::instance().success(TR("shield_started")); // Register with the shared poller so an async failure is // surfaced (and balances refresh) even after this dialog closes. app->trackOperation(s_operation_id); } else { - s_status_message = "Error: " + error; - Notifications::instance().error("Shield failed: " + error); + s_status_message = std::string(TR("shield_error_prefix")) + error; + Notifications::instance().error(std::string(TR("shield_send_failed")) + error); } }; }); @@ -235,14 +235,14 @@ void ShieldDialog::render(App* app) s_operation_pending = false; if (error.empty()) { s_operation_id = result.value("opid", ""); - s_status_message = "Operation submitted: " + s_operation_id; + s_status_message = std::string(TR("shield_op_submitted")) + s_operation_id; Notifications::instance().success(TR("merge_started")); // Register with the shared poller so an async failure is // surfaced (and balances refresh) even after this dialog closes. app->trackOperation(s_operation_id); } else { - s_status_message = "Error: " + error; - Notifications::instance().error("Merge failed: " + error); + s_status_message = std::string(TR("shield_error_prefix")) + error; + Notifications::instance().error(std::string(TR("merge_send_failed")) + error); } }; }); @@ -293,16 +293,16 @@ void ShieldDialog::render(App* app) 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); + std::string errMsg = op.value("error", nlohmann::json{}).value("message", TR("shield_unknown_error")); + s_status_message = std::string(TR("shield_op_failed")) + errMsg; + Notifications::instance().error(std::string(TR("shield_op_failed")) + errMsg); } else if (status == "executing") { s_status_message = TR("shield_in_progress"); } else { - s_status_message = "Status: " + status; + s_status_message = std::string(TR("shield_status_label")) + status; } } else if (!error.empty()) { - s_status_message = "Error checking status: " + error; + s_status_message = std::string(TR("shield_status_check_error")) + error; } }; }); diff --git a/src/util/i18n.cpp b/src/util/i18n.cpp index 59579a3..2382c9d 100644 --- a/src/util/i18n.cpp +++ b/src/util/i18n.cpp @@ -1501,6 +1501,15 @@ void I18n::loadBuiltinEnglish() strings_["shield_to_address"] = "To Address (Shielded):"; strings_["shield_utxo_limit"] = "UTXO Limit:"; strings_["shield_wildcard_hint"] = "Use '*' to shield from all transparent addresses"; + strings_["shield_submitting"] = "Submitting operation..."; + strings_["shield_op_submitted"] = "Operation submitted: "; + strings_["shield_op_failed"] = "Operation failed: "; + strings_["shield_error_prefix"] = "Error: "; + strings_["shield_status_label"] = "Status: "; + strings_["shield_status_check_error"] = "Error checking status: "; + strings_["shield_unknown_error"] = "Unknown error"; + strings_["shield_send_failed"] = "Shield failed: "; + strings_["merge_send_failed"] = "Merge failed: "; strings_["merge_description"] = "Merge multiple UTXOs into a single shielded address. This can help reduce wallet size and improve privacy."; strings_["merge_funds"] = "Merge Funds"; strings_["merge_started"] = "Merge operation started"; @@ -1568,6 +1577,9 @@ void I18n::loadBuiltinEnglish() strings_["explorer_tx_outputs"] = "Outputs"; strings_["explorer_tx_size"] = "Size"; strings_["explorer_invalid_query"] = "Enter a block height or 64-character hash"; + strings_["explorer_invalid_response"] = "Invalid response from daemon"; + strings_["explorer_hash_not_found"] = "No block or transaction found for this hash"; + strings_["explorer_not_connected"] = "Not connected to daemon — cannot look up a block or transaction hash"; strings_["explorer_no_results"] = "No matching cached blocks"; }