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:
+28
-27
@@ -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));
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
+1
-1
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user