From 8d8e523a2629be8d9020018d99d7885f7db63d90 Mon Sep 17 00:00:00 2001 From: DanS Date: Mon, 19 Oct 2026 08:44:55 -0500 Subject: [PATCH] i18n(migrate,sweep): translate status/error strings + add a hygiene regression guard - The import-key sweep and migrate-to-seed flows set several user-visible status/error strings (shown via sweep_status_ / seed_migration_status_ / rescan_status) as bare English; routed them through TR() with additive keys. - Extended scripts/check-source-hygiene.sh with two low-false-positive content guards so this debt can't regrow: a bare-English migrate/sweep status assignment, and a Tactile button sized with a raw (unscaled) pixel literal. Passes clean on the tree (362 files); the dev sweep harness is allowlisted. Co-Authored-By: Claude Opus 4.8 --- scripts/check-source-hygiene.sh | 30 ++++++++++++++++++++++++++++++ src/app_network.cpp | 32 ++++++++++++++++---------------- src/util/i18n.cpp | 15 +++++++++++++++ 3 files changed, 61 insertions(+), 16 deletions(-) diff --git a/scripts/check-source-hygiene.sh b/scripts/check-source-hygiene.sh index cc1b0de..5c8a0fa 100755 --- a/scripts/check-source-hygiene.sh +++ b/scripts/check-source-hygiene.sh @@ -48,6 +48,36 @@ for f in "${files[@]}"; do fi done +# ── Content guards (UX-hygiene threads) ────────────────────────────────────── +# Low-false-positive regression checks for debt the UX audit cleared, so it can't +# silently regrow. Each is deliberately narrow; widen only with an allowlist. +for f in "${files[@]}"; do + [[ "$f" == *.cpp ]] || continue + [[ -f "$f" ]] || continue + # app_sweep.cpp is the dev screenshot-sweep harness: its fixture strings are never shipped UI. + [[ "$(basename "$f")" == "app_sweep.cpp" ]] && continue + + # (1) Tactile button sized with a raw multi-digit pixel literal that ignores HiDPI. + # Real sizes multiply by ui::Layout::dpiScale() / dp / hScale, or come from schema. + if hits=$(grep -nE 'Tactile(Small)?Button\([^;]*ImVec2\([1-9][0-9]+(\.[0-9]+f?)?,' "$f" \ + | grep -vE 'dpiScale|\* *dp\b|\* *hdpi|hScale|schema|Scale\('); then + while IFS= read -r line; do + echo "✗ unscaled button size (multiply the px by ui::Layout::dpiScale()): $f:$line" >&2 + fail=1 + done <<< "$hits" + fi + + # (2) User-visible migrate/sweep status assigned a bare English literal instead of TR(...). + # "__SENTINEL__" comparisons and empty strings are fine. + if hits=$(grep -nE '(sweep_status_|seed_migration_status_)[[:space:]]*=[[:space:]]*"[A-Za-z]' "$f" \ + | grep -vE 'TR\(|"__'); then + while IFS= read -r line; do + echo "✗ untranslated status string (route through TR(...)): $f:$line" >&2 + fail=1 + done <<< "$hits" + fi +done + if (( fail )); then echo "" >&2 echo "Source hygiene check failed. See docs in scripts/check-source-hygiene.sh." >&2 diff --git a/src/app_network.cpp b/src/app_network.cpp index d305760..fdaffcd 100644 --- a/src/app_network.cpp +++ b/src/app_network.cpp @@ -5635,7 +5635,7 @@ void App::sweepPrivateKey(const std::string& rawKey, int startHeight, int destMo const std::string& destExisting) { if (!state_.connected || !rpc_ || !worker_) { - sweep_status_ = "Not connected to the daemon."; + sweep_status_ = TR("err_not_connected_daemon"); sweep_step_ = SweepStep::Error; return; } @@ -5647,7 +5647,7 @@ void App::sweepPrivateKey(const std::string& rawKey, int startHeight, int destMo // Sweeping requires a SPENDING key (transparent WIF or shielded z-spending key) — a viewing key // can't sign, and this must never accidentally route to z_importviewingkey. if (!services::WalletSecurityController::isRecognizedPrivateKey(key)) { - sweep_status_ = "Enter a spending key (a viewing key can't move funds)."; + sweep_status_ = TR("sweep_err_need_spending_key"); sweep_step_ = SweepStep::Error; return; } @@ -5665,7 +5665,7 @@ void App::sweepPrivateKey(const std::string& rawKey, int startHeight, int destMo } sweep_step_ = SweepStep::Running; - sweep_status_ = "Importing key & rescanning…"; + sweep_status_ = TR("sweep_status_importing"); sweep_txid_.clear(); sweep_dest_shown_.clear(); @@ -5759,7 +5759,7 @@ void App::sweepPrivateKey(const std::string& rawKey, int startHeight, int destMo } catch (const std::exception& e) { err = e.what(); } catch (...) { - err = "Sweep failed (unknown error)"; + err = TR("sweep_err_unknown"); } // Scrub the worker's copy of the spending key now that the request has been sent (all paths). if (!key.empty()) sodium_memzero(&key[0], key.size()); @@ -5767,16 +5767,16 @@ void App::sweepPrivateKey(const std::string& rawKey, int startHeight, int destMo invalidateAddressValidationCache(); refreshAddresses(); if (!err.empty()) { - if (err == "__NOFUNDS__") sweep_status_ = "This key holds no funds to sweep."; - else if (err == "__UNCONFIRMED__") sweep_status_ = "This key's funds are still unconfirmed — try again shortly."; - else if (err == "__ALREADY__") sweep_status_ = "This key is already in your wallet — use Send to move its funds."; - else if (err == "__DUST__") sweep_status_ = "This key's balance is too small to cover the network fee."; + if (err == "__NOFUNDS__") sweep_status_ = TR("sweep_err_nofunds"); + else if (err == "__UNCONFIRMED__") sweep_status_ = TR("sweep_err_unconfirmed"); + else if (err == "__ALREADY__") sweep_status_ = TR("sweep_err_already"); + else if (err == "__DUST__") sweep_status_ = TR("sweep_err_dust"); else sweep_status_ = err; sweep_step_ = SweepStep::Error; return; } sweep_dest_shown_ = dest; - sweep_status_ = "Sweeping funds to your address…"; + sweep_status_ = TR("sweep_status_sweeping"); // Send all funds from the swept address to the destination through the tested z_sendmany // wrapper (fixed-decimal amount + async-op tracking). z_sendmany needs every input in one // transaction to cover the full balance, so it fails loudly rather than partial-sweeping. @@ -6283,7 +6283,7 @@ void App::showSeedMigrationDialog() pending_send_callbacks_[sweepOpid] = makeSweepCompletionCallback(/*resumed=*/true); trackOperation(sweepOpid); seed_migration_step_ = SeedMigrationStep::Sweeping; - seed_migration_status_ = "Checking on the previous sweep…"; + seed_migration_status_ = TR("migrate_status_checking_sweep"); break; case MigrationResume::SweepGate: // No txid, and either no opid or not connected to re-track it (a persisted opid is left in @@ -6361,15 +6361,15 @@ void App::beginSweepToSeedWallet() return; } if (!rpc_ || !worker_ || !state_.connected) { - seed_migration_status_ = "Not connected to the daemon."; + seed_migration_status_ = TR("err_not_connected_daemon"); seed_migration_step_ = SeedMigrationStep::Error; return; } if (seed_migration_dest_.empty()) { - seed_migration_status_ = "No destination address for the sweep."; + seed_migration_status_ = TR("migrate_err_no_dest"); seed_migration_step_ = SeedMigrationStep::Error; return; } seed_migration_step_ = SeedMigrationStep::Sweeping; - seed_migration_status_ = "Building the sweep transaction…"; + seed_migration_status_ = TR("migrate_status_building"); // Mark the sweep as submitted BEFORE broadcasting (persisted synchronously). If the app or daemon // then dies after the tx reaches the mempool but before its opid/txid is persisted, a resumed // migration still knows a sweep is in flight and won't mistake the resulting ~0 legacy balance for @@ -6409,7 +6409,7 @@ void App::beginSweepToSeedWallet() } pending_send_callbacks_[opid] = makeSweepCompletionCallback(/*resumed=*/false); trackOperation(opid); - seed_migration_status_ = "Waiting for the sweep transaction to be accepted…"; + seed_migration_status_ = TR("migrate_status_waiting_accept"); }; }); } @@ -6568,7 +6568,7 @@ void App::beginAdoptSeedWallet() { if (refuseIfNodeBusy()) return; // stays on its current step; the user retries once the node is free seed_migration_step_ = SeedMigrationStep::Adopting; - seed_migration_status_ = "Restarting with your new wallet — this rescan can take a while…"; + seed_migration_status_ = TR("migrate_status_restarting"); { std::lock_guard lk(seed_migration_mutex_); seed_migration_adopt_done_ = false; } // Gate the main-loop reconnect so tryConnect can't start the daemon while we swap wallet.dat. daemon_restarting_ = true; @@ -7410,7 +7410,7 @@ bool App::runtimeRescan(int startHeight) rescan_confirmed_active_ = true; user_initiated_rescan_ = true; // user clicked Rescan → completion should toast state_.sync.rescan_progress = 0.0f; - state_.sync.rescan_status = "Rescanning from block " + std::to_string(startHeight) + "..."; + { char rb[64]; snprintf(rb, sizeof(rb), TR("rescan_status_from_block"), startHeight); state_.sync.rescan_status = rb; } markTransactionsDirty(); last_tx_block_height_ = -1; invalidateShieldedHistoryScanProgress(true); diff --git a/src/util/i18n.cpp b/src/util/i18n.cpp index 88bef7d..60a4290 100644 --- a/src/util/i18n.cpp +++ b/src/util/i18n.cpp @@ -482,6 +482,21 @@ void I18n::loadBuiltinEnglish() strings_["mig_receive_addr"] = "New wallet receive address:"; strings_["mig_copy_seed"] = "Copy seed"; strings_["mig_backed_up"] = "I've written down my seed phrase"; + strings_["err_not_connected_daemon"] = "Not connected to the daemon."; + strings_["sweep_err_need_spending_key"] = "Enter a spending key (a viewing key can't move funds)."; + strings_["sweep_status_importing"] = "Importing key & rescanning…"; + strings_["sweep_err_unknown"] = "Sweep failed (unknown error)"; + strings_["sweep_err_nofunds"] = "This key holds no funds to sweep."; + strings_["sweep_err_unconfirmed"] = "This key's funds are still unconfirmed — try again shortly."; + strings_["sweep_err_already"] = "This key is already in your wallet — use Send to move its funds."; + strings_["sweep_err_dust"] = "This key's balance is too small to cover the network fee."; + strings_["sweep_status_sweeping"] = "Sweeping funds to your address…"; + strings_["migrate_status_checking_sweep"] = "Checking on the previous sweep…"; + strings_["migrate_err_no_dest"] = "No destination address for the sweep."; + strings_["migrate_status_building"] = "Building the sweep transaction…"; + strings_["migrate_status_waiting_accept"] = "Waiting for the sweep transaction to be accepted…"; + strings_["migrate_status_restarting"] = "Restarting with your new wallet — this rescan can take a while…"; + strings_["rescan_status_from_block"] = "Rescanning from block %d..."; strings_["mig_understood"] = "I understand that once I continue, my funds will be moved into this new wallet and can only be recovered with this seed phrase"; strings_["mig_ack_required"] = "Confirm both statements above to continue"; strings_["mig_step1_done"] = "Step 1 of 2 complete — your funds have NOT moved yet. Sweeping them into this new wallet is the next step.";