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)
|
void App::switchToWallet(const std::string& walletFile, bool stopDaemonConfirmed, bool salvage)
|
||||||
{
|
{
|
||||||
if (!supportsFullNodeLifecycleActions()) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
if (walletFile.empty() || !settings_) return;
|
if (walletFile.empty() || !settings_) return;
|
||||||
if (settings_->getActiveWalletFile() == walletFile) {
|
if (settings_->getActiveWalletFile() == walletFile) {
|
||||||
ui::Notifications::instance().info("That wallet is already open.");
|
ui::Notifications::instance().info(TR("toast_wallet_already_open"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (daemon_restarting_) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
// Don't race the other daemon-lifecycle operations. rescan/repair set state_.sync.rescanning; the
|
// 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_.
|
// seed-migration flow drives its own daemon stop/start; an encryption restart sets daemon_restarting_.
|
||||||
if (state_.sync.rescanning) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
// W3-4: block switching while a migration is PENDING, not only while its dialog is open — closing
|
// 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.
|
// the dialog via "Later" mid-migration leaves the pending state but previously dropped this guard.
|
||||||
if (show_seed_migration_ || (settings_ && settings_->getSeedMigrationPending())) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
if (consolidation_.active()) {
|
if (consolidation_.active()) {
|
||||||
@@ -1613,11 +1613,11 @@ void App::switchToWallet(const std::string& walletFile, bool stopDaemonConfirmed
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!isUsingEmbeddedDaemon()) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
if (hasTransactionSendProgress()) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
if (refuseIfNodeBusy()) return;
|
if (refuseIfNodeBusy()) return;
|
||||||
@@ -2234,7 +2234,7 @@ void App::refreshCoreData(bool allowBalance)
|
|||||||
last_tx_block_height_ = -1;
|
last_tx_block_height_ = -1;
|
||||||
invalidateShieldedHistoryScanProgress(true);
|
invalidateShieldedHistoryScanProgress(true);
|
||||||
if (user_initiated_rescan_) {
|
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
|
user_initiated_rescan_ = false; // surfaced once; not for background rebuilds
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3517,7 +3517,7 @@ void App::startMining(int threads)
|
|||||||
{
|
{
|
||||||
if (!supportsSoloMining()) {
|
if (!supportsSoloMining()) {
|
||||||
(void)threads;
|
(void)threads;
|
||||||
ui::Notifications::instance().warning("Solo mining is unavailable in lite build");
|
ui::Notifications::instance().warning(TR("toast_solo_unavailable_lite"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!state_.connected || !rpc_ || !worker_) 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);
|
DEBUG_LOGF("Mining started with %d threads\n", threads);
|
||||||
} else {
|
} else {
|
||||||
DEBUG_LOGF("Failed to start mining: %s\n", errMsg.c_str());
|
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 {
|
} else {
|
||||||
// Don't silently leave generate=true as if it worked: tell the user and let the next
|
// 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)
|
// 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()) {
|
if (!supportsPoolMining()) {
|
||||||
(void)threads;
|
(void)threads;
|
||||||
ui::Notifications::instance().warning("Pool mining is unavailable in this build");
|
ui::Notifications::instance().warning(TR("toast_pool_unavailable"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Clamp to [1, logical cores] before the count reaches xmrig (M-06/M-08 pool path).
|
// 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()) {
|
if (cfg.wallet_address.empty()) {
|
||||||
DEBUG_LOGF("[ERROR] Pool mining: No wallet address available\n");
|
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();
|
if (retargetOnly) stopPoolMining();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -3707,7 +3707,7 @@ void App::postPoolMinerJob(int threads, bool retargetOnly)
|
|||||||
if (ok) {
|
if (ok) {
|
||||||
// Miner spawned — it still needs a few seconds to connect to the pool and start hashing.
|
// Miner spawned — it still needs a few seconds to connect to the pool and start hashing.
|
||||||
pool_starting_.store(true, std::memory_order_relaxed);
|
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 {
|
} else {
|
||||||
DEBUG_LOGF("[ERROR] Pool mining: %s\n", err.c_str());
|
DEBUG_LOGF("[ERROR] Pool mining: %s\n", err.c_str());
|
||||||
// Windows Defender blocking (error 225 = ERROR_VIRUS_INFECTED)
|
// Windows Defender blocking (error 225 = ERROR_VIRUS_INFECTED)
|
||||||
@@ -3718,7 +3718,7 @@ void App::postPoolMinerJob(int threads, bool retargetOnly)
|
|||||||
pending_antivirus_dialog_ = true;
|
pending_antivirus_dialog_ = true;
|
||||||
#endif
|
#endif
|
||||||
} else {
|
} 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();
|
err = e.what();
|
||||||
}
|
}
|
||||||
return [this, err]() {
|
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();
|
else refreshPeerInfo();
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -3772,7 +3772,7 @@ void App::unbanPeer(const std::string& ip)
|
|||||||
err = e.what();
|
err = e.what();
|
||||||
}
|
}
|
||||||
return [this, err]() {
|
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();
|
else refreshPeerInfo();
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -3790,7 +3790,7 @@ void App::clearBans()
|
|||||||
err = e.what();
|
err = e.what();
|
||||||
}
|
}
|
||||||
return [this, err]() {
|
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();
|
state_.banned_peers.clear();
|
||||||
refreshPeerInfo();
|
refreshPeerInfo();
|
||||||
};
|
};
|
||||||
@@ -4101,7 +4101,7 @@ void App::exportPrivateKey(const std::string& address, std::function<void(const
|
|||||||
return [callback, key, err]() {
|
return [callback, key, err]() {
|
||||||
if (!err.empty()) {
|
if (!err.empty()) {
|
||||||
DEBUG_LOGF("Export key error: %s\n", err.c_str());
|
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("");
|
if (callback) callback("");
|
||||||
} else if (callback) {
|
} else if (callback) {
|
||||||
callback(key);
|
callback(key);
|
||||||
@@ -6657,7 +6657,7 @@ void App::beginAdoptSeedWallet()
|
|||||||
void App::restoreOriginalWallet()
|
void App::restoreOriginalWallet()
|
||||||
{
|
{
|
||||||
if (!supportsFullNodeLifecycleActions()) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
if (daemon_restarting_) {
|
if (daemon_restarting_) {
|
||||||
@@ -6832,7 +6832,7 @@ bool App::walletRebuildAvailable() const { return !findWalletRebuildHelper().emp
|
|||||||
void App::rebuildWalletDatabase()
|
void App::rebuildWalletDatabase()
|
||||||
{
|
{
|
||||||
if (!supportsFullNodeLifecycleActions()) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
if (daemon_restarting_) { ui::Notifications::instance().warning(TR("wallet_restore_busy")); 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)
|
bool App::runtimeRescan(int startHeight)
|
||||||
{
|
{
|
||||||
if (!supportsFullNodeLifecycleActions()) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
if (!state_.connected || !rpc_ || !rpc_->isConnected() || !worker_) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
if (runtime_rescan_active_) return false;
|
if (runtime_rescan_active_) return false;
|
||||||
@@ -7395,8 +7395,9 @@ bool App::runtimeRescan(int startHeight)
|
|||||||
last_tx_block_height_ = -1;
|
last_tx_block_height_ = -1;
|
||||||
invalidateShieldedHistoryScanProgress(true);
|
invalidateShieldedHistoryScanProgress(true);
|
||||||
|
|
||||||
ui::Notifications::instance().info("Rescanning blockchain from block " + std::to_string(startHeight) +
|
char rescanMsg[192];
|
||||||
" — this can take a while.");
|
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 {
|
const bool started = runLongWalletTask([this, startHeight](rpc::RPCClient& c) -> rpc::RPCWorker::MainCb {
|
||||||
bool ok = false;
|
bool ok = false;
|
||||||
@@ -7417,11 +7418,11 @@ bool App::runtimeRescan(int startHeight)
|
|||||||
noteWalletMutation();
|
noteWalletMutation();
|
||||||
last_tx_block_height_ = -1;
|
last_tx_block_height_ = -1;
|
||||||
invalidateShieldedHistoryScanProgress(true);
|
invalidateShieldedHistoryScanProgress(true);
|
||||||
ui::Notifications::instance().success("Blockchain rescan complete");
|
ui::Notifications::instance().success(TR("toast_rescan_complete"));
|
||||||
refreshData();
|
refreshData();
|
||||||
} else {
|
} else {
|
||||||
state_.sync.rescan_progress = 0.0f;
|
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()
|
void App::restartWizard()
|
||||||
{
|
{
|
||||||
if (!supportsFullNodeLifecycleActions()) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1919,6 +1919,31 @@ void I18n::loadBuiltinEnglish()
|
|||||||
strings_["err_insufficient"] = "Not enough spendable balance to cover the amount and fee.";
|
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_cant_reach"] = "Can't reach the node. Check that it's running and try again.";
|
||||||
strings_["err_generic"] = "Something went wrong. Please 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_capturing_output"] = "Capturing daemon output...";
|
||||||
strings_["console_clear"] = "Clear";
|
strings_["console_clear"] = "Clear";
|
||||||
strings_["console_clear_console"] = "Clear Console";
|
strings_["console_clear_console"] = "Clear Console";
|
||||||
|
|||||||
Reference in New Issue
Block a user