feat(i18n,errors): route node-action toasts through i18n + friendlyRpcError

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 <noreply@anthropic.com>
This commit is contained in:
2026-10-18 08:55:29 -05:00
co-authored by Claude Opus 4.8
parent 5cfa713be1
commit 09f39f9183
3 changed files with 54 additions and 28 deletions
+28 -27
View File
@@ -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<void(const
return [callback, key, err]() {
if (!err.empty()) {
DEBUG_LOGF("Export key error: %s\n", err.c_str());
ui::Notifications::instance().error("Key export failed: " + err);
ui::Notifications::instance().error(std::string(TR("toast_key_export_failed")) + util::friendlyRpcError(err));
if (callback) callback("");
} else if (callback) {
callback(key);
@@ -6657,7 +6657,7 @@ void App::beginAdoptSeedWallet()
void App::restoreOriginalWallet()
{
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 (daemon_restarting_) {
@@ -6832,7 +6832,7 @@ bool App::walletRebuildAvailable() const { return !findWalletRebuildHelper().emp
void App::rebuildWalletDatabase()
{
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 (daemon_restarting_) { ui::Notifications::instance().warning(TR("wallet_restore_busy")); return; }
@@ -7354,11 +7354,11 @@ void App::detectLowestAvailableBlockHeight(std::function<void(bool, int, bool)>
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<int>(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));
}
};
});