From 09f39f91839407d0e7478b57e469283bed9891f1 Mon Sep 17 00:00:00 2001 From: DanS Date: Sun, 18 Oct 2026 08:55:29 -0500 Subject: [PATCH] feat(i18n,errors): route node-action toasts through i18n + friendlyRpcError MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Finish the error-surfacing foundation across app_network.cpp (+ one in app_wizard.cpp): ~21 toasts that were hardcoded English — or raw daemon/RPC error text concatenated after an English prefix — now go through TR() keys, and every raw-error one maps the detail through util::friendlyRpcError() (warming up / locked / method-not-found / insufficient / unreachable → plain guidance, raw text as fallback). Covers mining start/stop, pool start, peer ban/unban/ clear, key export, rescan, wallet-switch guards, and the lite-unavailable notices. Co-Authored-By: Claude Opus 4.8 --- src/app_network.cpp | 55 +++++++++++++++++++++++---------------------- src/app_wizard.cpp | 2 +- src/util/i18n.cpp | 25 +++++++++++++++++++++ 3 files changed, 54 insertions(+), 28 deletions(-) diff --git a/src/app_network.cpp b/src/app_network.cpp index c9b5ad7..df5eaa2 100644 --- a/src/app_network.cpp +++ b/src/app_network.cpp @@ -1584,28 +1584,28 @@ void App::updateWalletIndexForActiveWallet(bool markOpened) void App::switchToWallet(const std::string& walletFile, bool stopDaemonConfirmed, bool salvage) { if (!supportsFullNodeLifecycleActions()) { - ui::Notifications::instance().warning("Full-node lifecycle actions are unavailable in lite build"); + ui::Notifications::instance().warning(TR("toast_lite_lifecycle_unavailable")); return; } if (walletFile.empty() || !settings_) return; if (settings_->getActiveWalletFile() == walletFile) { - ui::Notifications::instance().info("That wallet is already open."); + ui::Notifications::instance().info(TR("toast_wallet_already_open")); return; } if (daemon_restarting_) { - ui::Notifications::instance().warning("The node is busy restarting — try again in a moment."); + ui::Notifications::instance().warning(TR("toast_node_busy_restarting")); return; } // Don't race the other daemon-lifecycle operations. rescan/repair set state_.sync.rescanning; the // seed-migration flow drives its own daemon stop/start; an encryption restart sets daemon_restarting_. if (state_.sync.rescanning) { - ui::Notifications::instance().warning("A rescan or repair is in progress — try again once it finishes."); + ui::Notifications::instance().warning(TR("toast_rescan_in_progress")); return; } // W3-4: block switching while a migration is PENDING, not only while its dialog is open — closing // the dialog via "Later" mid-migration leaves the pending state but previously dropped this guard. if (show_seed_migration_ || (settings_ && settings_->getSeedMigrationPending())) { - ui::Notifications::instance().warning("Finish or cancel the seed migration before switching wallets."); + ui::Notifications::instance().warning(TR("toast_finish_migration_first")); return; } if (consolidation_.active()) { @@ -1613,11 +1613,11 @@ void App::switchToWallet(const std::string& walletFile, bool stopDaemonConfirmed return; } if (!isUsingEmbeddedDaemon()) { - ui::Notifications::instance().warning("Switching wallets needs the embedded daemon — stop any external dragonxd first."); + ui::Notifications::instance().warning(TR("toast_switch_needs_embedded")); return; } if (hasTransactionSendProgress()) { - ui::Notifications::instance().warning("Finish or cancel the pending send before switching wallets."); + ui::Notifications::instance().warning(TR("toast_finish_send_first")); return; } if (refuseIfNodeBusy()) return; @@ -2234,7 +2234,7 @@ void App::refreshCoreData(bool allowBalance) last_tx_block_height_ = -1; invalidateShieldedHistoryScanProgress(true); if (user_initiated_rescan_) { - ui::Notifications::instance().success("Blockchain rescan complete"); + ui::Notifications::instance().success(TR("toast_rescan_complete")); user_initiated_rescan_ = false; // surfaced once; not for background rebuilds } } @@ -3517,7 +3517,7 @@ void App::startMining(int threads) { if (!supportsSoloMining()) { (void)threads; - ui::Notifications::instance().warning("Solo mining is unavailable in lite build"); + ui::Notifications::instance().warning(TR("toast_solo_unavailable_lite")); return; } if (!state_.connected || !rpc_ || !worker_) return; @@ -3545,7 +3545,7 @@ void App::startMining(int threads) DEBUG_LOGF("Mining started with %d threads\n", threads); } else { DEBUG_LOGF("Failed to start mining: %s\n", errMsg.c_str()); - ui::Notifications::instance().error("Mining failed: " + errMsg); + ui::Notifications::instance().error(std::string(TR("toast_mining_failed")) + util::friendlyRpcError(errMsg)); } }; }); @@ -3577,7 +3577,7 @@ void App::stopMining() } else { // Don't silently leave generate=true as if it worked: tell the user and let the next // getmininginfo refresh reconcile the true daemon state. (M-07) - ui::Notifications::instance().error("Failed to stop mining: " + errMsg); + ui::Notifications::instance().error(std::string(TR("toast_stop_mining_failed")) + util::friendlyRpcError(errMsg)); } }; }); @@ -3587,7 +3587,7 @@ void App::startPoolMining(int threads) { if (!supportsPoolMining()) { (void)threads; - ui::Notifications::instance().warning("Pool mining is unavailable in this build"); + ui::Notifications::instance().warning(TR("toast_pool_unavailable")); return; } // Clamp to [1, logical cores] before the count reaches xmrig (M-06/M-08 pool path). @@ -3664,7 +3664,7 @@ void App::postPoolMinerJob(int threads, bool retargetOnly) if (cfg.wallet_address.empty()) { DEBUG_LOGF("[ERROR] Pool mining: No wallet address available\n"); - ui::Notifications::instance().error("No wallet address available — generate a Z address in the Receive tab"); + ui::Notifications::instance().error(TR("toast_no_address_generate_z")); if (retargetOnly) stopPoolMining(); return; } @@ -3707,7 +3707,7 @@ void App::postPoolMinerJob(int threads, bool retargetOnly) if (ok) { // Miner spawned — it still needs a few seconds to connect to the pool and start hashing. pool_starting_.store(true, std::memory_order_relaxed); - ui::Notifications::instance().info("Starting pool miner — connecting to the pool…"); + ui::Notifications::instance().info(TR("toast_pool_connecting")); } else { DEBUG_LOGF("[ERROR] Pool mining: %s\n", err.c_str()); // Windows Defender blocking (error 225 = ERROR_VIRUS_INFECTED) @@ -3718,7 +3718,7 @@ void App::postPoolMinerJob(int threads, bool retargetOnly) pending_antivirus_dialog_ = true; #endif } else { - ui::Notifications::instance().error("Failed to start pool miner: " + err); + ui::Notifications::instance().error(std::string(TR("toast_pool_start_failed")) + util::friendlyRpcError(err)); } } }; @@ -3754,7 +3754,7 @@ void App::banPeer(const std::string& ip, int duration_seconds) err = e.what(); } return [this, err]() { - if (!err.empty()) ui::Notifications::instance().error("Ban failed: " + err); + if (!err.empty()) ui::Notifications::instance().error(std::string(TR("toast_ban_failed")) + util::friendlyRpcError(err)); else refreshPeerInfo(); }; }); @@ -3772,7 +3772,7 @@ void App::unbanPeer(const std::string& ip) err = e.what(); } return [this, err]() { - if (!err.empty()) ui::Notifications::instance().error("Unban failed: " + err); + if (!err.empty()) ui::Notifications::instance().error(std::string(TR("toast_unban_failed")) + util::friendlyRpcError(err)); else refreshPeerInfo(); }; }); @@ -3790,7 +3790,7 @@ void App::clearBans() err = e.what(); } return [this, err]() { - if (!err.empty()) { ui::Notifications::instance().error("Clear bans failed: " + err); return; } + if (!err.empty()) { ui::Notifications::instance().error(std::string(TR("toast_clear_bans_failed")) + util::friendlyRpcError(err)); return; } state_.banned_peers.clear(); refreshPeerInfo(); }; @@ -4101,7 +4101,7 @@ void App::exportPrivateKey(const std::string& address, std::function bool App::runtimeRescan(int startHeight) { if (!supportsFullNodeLifecycleActions()) { - ui::Notifications::instance().warning("Full-node lifecycle actions are unavailable in lite build"); + ui::Notifications::instance().warning(TR("toast_lite_lifecycle_unavailable")); return false; } if (!state_.connected || !rpc_ || !rpc_->isConnected() || !worker_) { - ui::Notifications::instance().warning("Not connected to daemon"); + ui::Notifications::instance().warning(TR("toast_not_connected_daemon")); return false; } if (runtime_rescan_active_) return false; @@ -7395,8 +7395,9 @@ bool App::runtimeRescan(int startHeight) last_tx_block_height_ = -1; invalidateShieldedHistoryScanProgress(true); - ui::Notifications::instance().info("Rescanning blockchain from block " + std::to_string(startHeight) + - " — this can take a while."); + char rescanMsg[192]; + snprintf(rescanMsg, sizeof(rescanMsg), TR("toast_rescan_from_fmt"), static_cast(startHeight)); + ui::Notifications::instance().info(rescanMsg); const bool started = runLongWalletTask([this, startHeight](rpc::RPCClient& c) -> rpc::RPCWorker::MainCb { bool ok = false; @@ -7417,11 +7418,11 @@ bool App::runtimeRescan(int startHeight) noteWalletMutation(); last_tx_block_height_ = -1; invalidateShieldedHistoryScanProgress(true); - ui::Notifications::instance().success("Blockchain rescan complete"); + ui::Notifications::instance().success(TR("toast_rescan_complete")); refreshData(); } else { state_.sync.rescan_progress = 0.0f; - ui::Notifications::instance().error("Rescan failed: " + err); + ui::Notifications::instance().error(std::string(TR("toast_rescan_failed")) + util::friendlyRpcError(err)); } }; }); diff --git a/src/app_wizard.cpp b/src/app_wizard.cpp index 1fbd71e..6ebd4cd 100644 --- a/src/app_wizard.cpp +++ b/src/app_wizard.cpp @@ -73,7 +73,7 @@ WizardUiState s_wizardUi; void App::restartWizard() { if (!supportsFullNodeLifecycleActions()) { - ui::Notifications::instance().warning("Lite wallet lifecycle requests are available from Settings as dry-run readiness checks"); + ui::Notifications::instance().warning(TR("toast_wizard_lite_unavailable")); return; } diff --git a/src/util/i18n.cpp b/src/util/i18n.cpp index e9236ce..82ecfe5 100644 --- a/src/util/i18n.cpp +++ b/src/util/i18n.cpp @@ -1919,6 +1919,31 @@ void I18n::loadBuiltinEnglish() strings_["err_insufficient"] = "Not enough spendable balance to cover the amount and fee."; strings_["err_cant_reach"] = "Can't reach the node. Check that it's running and try again."; strings_["err_generic"] = "Something went wrong. Please try again."; + // Node/wallet action toasts (were hardcoded English in app_network.cpp). The *_failed keys are + // prefixes: the code appends a friendlyRpcError() detail after them. + strings_["toast_lite_lifecycle_unavailable"] = "Full-node actions aren't available in the lite wallet."; + strings_["toast_wallet_already_open"] = "That wallet is already open."; + strings_["toast_node_busy_restarting"] = "The node is busy restarting — try again in a moment."; + strings_["toast_rescan_in_progress"] = "A rescan or repair is in progress — try again once it finishes."; + strings_["toast_finish_migration_first"] = "Finish or cancel the seed migration before switching wallets."; + strings_["toast_switch_needs_embedded"] = "Switching wallets needs the embedded node — stop any external dragonxd first."; + strings_["toast_finish_send_first"] = "Finish or cancel the pending send before switching wallets."; + strings_["toast_rescan_complete"] = "Blockchain rescan complete."; + strings_["toast_rescan_from_fmt"] = "Rescanning the blockchain from block %d — this can take a while."; + strings_["toast_solo_unavailable_lite"] = "Solo mining isn't available in the lite wallet."; + strings_["toast_pool_unavailable"] = "Pool mining isn't available in this build."; + strings_["toast_no_address_generate_z"] = "No wallet address yet — generate a shielded (Z) address in the Receive tab."; + strings_["toast_pool_connecting"] = "Starting pool miner — connecting to the pool…"; + strings_["toast_not_connected_daemon"] = "Not connected to the node."; + strings_["toast_mining_failed"] = "Couldn't start mining: "; + strings_["toast_stop_mining_failed"] = "Couldn't stop mining: "; + strings_["toast_pool_start_failed"] = "Couldn't start the pool miner: "; + strings_["toast_ban_failed"] = "Couldn't ban the peer: "; + strings_["toast_unban_failed"] = "Couldn't unban the peer: "; + strings_["toast_clear_bans_failed"] = "Couldn't clear bans: "; + strings_["toast_key_export_failed"] = "Key export failed: "; + strings_["toast_rescan_failed"] = "Rescan failed: "; + strings_["toast_wizard_lite_unavailable"] = "Wallet setup actions aren't available in the lite wallet (see Settings for readiness checks)."; strings_["console_capturing_output"] = "Capturing daemon output..."; strings_["console_clear"] = "Clear"; strings_["console_clear_console"] = "Clear Console";