i18n(wallet): translate the migrate-to-seed UI + document it in CLAUDE.md
Route the migration modal (renderSeedMigrationDialog) through TR() — 32 new keys (30 mig_* plus the previously-untranslated seed_migrate_button/ tt_seed_migrate), reusing existing common keys (cancel/close/refresh/ seed_backup_save*). Format-arg strings go through snprintf(buf, TR(...), v) so the %.8f/%d specifiers stay in the (validated) translations; plain strings use "%s", TR(...) to avoid -Wformat-security. Translated into de/es/fr/ja/ko/pt/ru/zh — additive only (972 -> 1004 keys per language, format specifiers verified preserved); CJK subset font rebuilt. CLAUDE.md gains a "Seed phrase & migrate-to-seed" section documenting the hd-transparent-keys daemon dependency, -usemnemonic, the backup screen, and the Phase 1 (isolated create) / Phase 2 (sweep + Confirming gate + adopt) flow that was live-verified on mainnet. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
110
src/app.cpp
110
src/app.cpp
@@ -3072,7 +3072,7 @@ void App::renderSeedMigrationDialog()
|
||||
|| seed_migration_step_ == SeedMigrationStep::Sweeping
|
||||
|| seed_migration_step_ == SeedMigrationStep::Adopting;
|
||||
|
||||
if (!ui::material::BeginOverlayDialog("Migrate to a seed wallet", &show_seed_migration_, 580.0f, 0.94f)) {
|
||||
if (!ui::material::BeginOverlayDialog(TR("mig_title"), &show_seed_migration_, 580.0f, 0.94f)) {
|
||||
if (!busy) wipeSeed();
|
||||
return;
|
||||
}
|
||||
@@ -3081,75 +3081,68 @@ void App::renderSeedMigrationDialog()
|
||||
// the post-EndOverlayDialog wipe below.
|
||||
if (!show_seed_migration_ && busy) show_seed_migration_ = true;
|
||||
|
||||
ui::material::Type().text(ui::material::TypeStyle::H6, "Migrate to a seed wallet");
|
||||
const ImVec4 kMedium = ImGui::ColorConvertU32ToFloat4(ui::material::OnSurfaceMedium());
|
||||
ui::material::Type().text(ui::material::TypeStyle::H6, TR("mig_title"));
|
||||
ImGui::Dummy(ImVec2(0, ui::Layout::spacingSm()));
|
||||
|
||||
switch (seed_migration_step_) {
|
||||
case SeedMigrationStep::Intro:
|
||||
ImGui::TextWrapped(
|
||||
"This creates a brand-new wallet backed by a 24-word seed phrase, in an isolated node, "
|
||||
"so you can move your funds into it. Your current wallet is NOT touched and NO funds "
|
||||
"move in this step — you'll back up the new seed first, then sweep your funds into it "
|
||||
"as a separate, confirmed step.");
|
||||
ImGui::TextWrapped("%s", TR("mig_intro"));
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
if (ui::material::StyledButton("Create seed wallet", ImVec2(180, 0)))
|
||||
if (ui::material::StyledButton(TR("mig_create"), ImVec2(180, 0)))
|
||||
beginCreateSeedWallet();
|
||||
ImGui::SameLine();
|
||||
if (ui::material::StyledButton("Cancel", ImVec2(120, 0)))
|
||||
if (ui::material::StyledButton(TR("cancel"), ImVec2(120, 0)))
|
||||
close();
|
||||
break;
|
||||
|
||||
case SeedMigrationStep::Working:
|
||||
ImGui::TextWrapped("Creating your new seed wallet in an isolated node — this can take a "
|
||||
"minute. Your main wallet keeps running.");
|
||||
ImGui::TextWrapped("%s", TR("mig_working"));
|
||||
ImGui::Spacing();
|
||||
if (!seed_migration_status_.empty())
|
||||
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(ui::material::OnSurfaceMedium()), "%s", seed_migration_status_.c_str());
|
||||
ImGui::TextColored(kMedium, "%s", seed_migration_status_.c_str());
|
||||
break;
|
||||
|
||||
case SeedMigrationStep::ShowSeed: {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ui::material::Error());
|
||||
ImGui::TextWrapped("Write these 24 words down in order and store them offline. They are the "
|
||||
"only backup of your new wallet — if you lose them, the funds you migrate "
|
||||
"are gone forever.");
|
||||
ImGui::TextWrapped("%s", TR("mig_seed_warning"));
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::Spacing();
|
||||
ui::RenderSeedWordGrid(ui::SplitSeedWords(seed_migration_seed_));
|
||||
ImGui::Spacing();
|
||||
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(ui::material::OnSurfaceMedium()), "New wallet receive address:");
|
||||
ImGui::TextColored(kMedium, "%s", TR("mig_receive_addr"));
|
||||
ImGui::TextWrapped("%s", seed_migration_dest_.c_str());
|
||||
ImGui::Spacing();
|
||||
if (ui::material::StyledButton("Copy seed", ImVec2(120, 0)))
|
||||
if (ui::material::StyledButton(TR("mig_copy_seed"), ImVec2(120, 0)))
|
||||
copySecretToClipboard(seed_migration_seed_);
|
||||
ImGui::SameLine();
|
||||
if (ui::material::StyledButton("Save to file", ImVec2(130, 0))) {
|
||||
if (ui::material::StyledButton(TR("seed_backup_save"), ImVec2(130, 0))) {
|
||||
std::string path = util::Platform::getConfigDir() + "/dragonx-seed-wallet-backup.txt";
|
||||
std::string content = seed_migration_seed_ + "\nAddress: " + seed_migration_dest_ + "\n";
|
||||
const bool ok = util::Platform::writeFileAtomically(path, content, /*restrict=*/true);
|
||||
sodium_memzero(&content[0], content.size());
|
||||
seed_migration_status_ = (ok ? "Saved to " : "Could not write ") + path;
|
||||
seed_migration_status_ = std::string(ok ? TR("seed_backup_saved") : TR("seed_backup_save_failed")) + path;
|
||||
}
|
||||
if (!seed_migration_status_.empty()) {
|
||||
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "%s", seed_migration_status_.c_str());
|
||||
}
|
||||
ImGui::Spacing();
|
||||
ImGui::Checkbox("I've written down my seed phrase", &seed_migration_backed_up_);
|
||||
ImGui::Checkbox(TR("mig_backed_up"), &seed_migration_backed_up_);
|
||||
ImGui::Spacing();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ui::material::OnSurfaceMedium());
|
||||
ImGui::TextWrapped("Step 1 of 2 complete — your funds have NOT moved yet. Sweeping them into "
|
||||
"this new wallet is the next step.");
|
||||
ImGui::TextWrapped("%s", TR("mig_step1_done"));
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::Separator();
|
||||
if (!seed_migration_backed_up_) ImGui::BeginDisabled();
|
||||
if (ui::material::StyledButton("Continue to sweep", ImVec2(170, 0))) {
|
||||
if (ui::material::StyledButton(TR("mig_continue_sweep"), ImVec2(170, 0))) {
|
||||
wipeSeed(); // the sweep/adopt steps only use the address, never the seed itself
|
||||
seed_migration_step_ = SeedMigrationStep::Sweep;
|
||||
refreshSeedMigrationBalance();
|
||||
}
|
||||
if (!seed_migration_backed_up_) ImGui::EndDisabled();
|
||||
ImGui::SameLine();
|
||||
if (ui::material::StyledButton("Discard migration", ImVec2(160, 0))) {
|
||||
if (ui::material::StyledButton(TR("mig_discard"), ImVec2(160, 0))) {
|
||||
// Throw away the isolated new wallet + clear the pending state.
|
||||
if (!seed_migration_temp_dir_.empty()) {
|
||||
std::error_code ec;
|
||||
@@ -3168,18 +3161,16 @@ void App::renderSeedMigrationDialog()
|
||||
break;
|
||||
}
|
||||
|
||||
case SeedMigrationStep::Sweep:
|
||||
ImGui::TextWrapped("Now sweep all funds from your current wallet into the new seed wallet. "
|
||||
"This sends a single transaction to the new wallet's address; your seed "
|
||||
"phrase (already backed up) controls the funds from here on.");
|
||||
case SeedMigrationStep::Sweep: {
|
||||
ImGui::TextWrapped("%s", TR("mig_sweep_intro"));
|
||||
ImGui::Spacing();
|
||||
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(ui::material::OnSurfaceMedium()),
|
||||
"Current wallet balance: %.8f DRGX (minus a small fee)", seed_migration_balance_);
|
||||
ImGui::TextWrapped("To: %s", seed_migration_dest_.c_str());
|
||||
char balbuf[96]; snprintf(balbuf, sizeof(balbuf), TR("mig_balance"), seed_migration_balance_);
|
||||
ImGui::TextColored(kMedium, "%s", balbuf);
|
||||
ImGui::TextWrapped("%s%s", TR("mig_to"), seed_migration_dest_.c_str());
|
||||
if (state_.isLocked()) {
|
||||
ImGui::Spacing();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ui::material::Error());
|
||||
ImGui::TextWrapped("Unlock your wallet first to spend from it.");
|
||||
ImGui::TextWrapped("%s", TR("mig_unlock_first"));
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
ImGui::Spacing();
|
||||
@@ -3187,80 +3178,77 @@ void App::renderSeedMigrationDialog()
|
||||
{
|
||||
const bool canSweep = !state_.isLocked() && seed_migration_balance_ > 0.0;
|
||||
if (!canSweep) ImGui::BeginDisabled();
|
||||
if (ui::material::StyledButton("Sweep all funds", ImVec2(170, 0)))
|
||||
if (ui::material::StyledButton(TR("mig_sweep_all"), ImVec2(170, 0)))
|
||||
beginSweepToSeedWallet();
|
||||
if (!canSweep) ImGui::EndDisabled();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ui::material::StyledButton("Refresh", ImVec2(110, 0)))
|
||||
if (ui::material::StyledButton(TR("refresh"), ImVec2(110, 0)))
|
||||
refreshSeedMigrationBalance();
|
||||
ImGui::SameLine();
|
||||
if (ui::material::StyledButton("Later", ImVec2(100, 0)))
|
||||
if (ui::material::StyledButton(TR("mig_later"), ImVec2(100, 0)))
|
||||
close(); // pending state persists; resumes here next time
|
||||
break;
|
||||
}
|
||||
|
||||
case SeedMigrationStep::Sweeping:
|
||||
ImGui::TextWrapped("Sweeping your funds into the new wallet…");
|
||||
ImGui::TextWrapped("%s", TR("mig_sweeping"));
|
||||
ImGui::Spacing();
|
||||
if (!seed_migration_status_.empty())
|
||||
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(ui::material::OnSurfaceMedium()),
|
||||
"%s", seed_migration_status_.c_str());
|
||||
ImGui::TextColored(kMedium, "%s", seed_migration_status_.c_str());
|
||||
break;
|
||||
|
||||
case SeedMigrationStep::Confirming: {
|
||||
const ImVec4 medium = ImGui::ColorConvertU32ToFloat4(ui::material::OnSurfaceMedium());
|
||||
const double dust = DRAGONX_DEFAULT_FEE; // treat <= one fee as "empty"
|
||||
const bool confirmed = seed_migration_sweep_confs_ >= 1;
|
||||
const bool legacyEmpty = seed_migration_legacy_remaining_ >= 0.0 &&
|
||||
seed_migration_legacy_remaining_ <= dust;
|
||||
ImGui::TextWrapped("Waiting for the sweep to confirm on-chain before switching wallets — "
|
||||
"this guarantees your funds have actually moved.");
|
||||
ImGui::TextWrapped("%s", TR("mig_confirming"));
|
||||
ImGui::Spacing();
|
||||
ImGui::TextColored(medium, "Sweep confirmations: %d", seed_migration_sweep_confs_);
|
||||
char cbuf[64]; snprintf(cbuf, sizeof(cbuf), TR("mig_confs"), seed_migration_sweep_confs_);
|
||||
ImGui::TextColored(kMedium, "%s", cbuf);
|
||||
if (!seed_migration_sweep_txid_.empty())
|
||||
ImGui::TextColored(medium, "txid: %s", seed_migration_sweep_txid_.c_str());
|
||||
if (seed_migration_legacy_remaining_ >= 0.0)
|
||||
ImGui::TextColored(medium, "Remaining in old wallet: %.8f DRGX",
|
||||
seed_migration_legacy_remaining_);
|
||||
ImGui::TextColored(kMedium, "%s%s", TR("mig_txid"), seed_migration_sweep_txid_.c_str());
|
||||
if (seed_migration_legacy_remaining_ >= 0.0) {
|
||||
char rbuf[96]; snprintf(rbuf, sizeof(rbuf), TR("mig_remaining"), seed_migration_legacy_remaining_);
|
||||
ImGui::TextColored(kMedium, "%s", rbuf);
|
||||
}
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
if (confirmed && legacyEmpty) {
|
||||
if (ui::material::StyledButton("Make this my wallet", ImVec2(180, 0)))
|
||||
if (ui::material::StyledButton(TR("mig_adopt"), ImVec2(180, 0)))
|
||||
beginAdoptSeedWallet();
|
||||
} else if (confirmed && !legacyEmpty) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ui::material::Error());
|
||||
ImGui::TextWrapped("Some funds are still in your old wallet (too many inputs for one "
|
||||
"transaction) — sweep the remainder before switching.");
|
||||
ImGui::TextWrapped("%s", TR("mig_remainder"));
|
||||
ImGui::PopStyleColor();
|
||||
if (ui::material::StyledButton("Sweep remaining", ImVec2(180, 0)))
|
||||
if (ui::material::StyledButton(TR("mig_sweep_remaining"), ImVec2(180, 0)))
|
||||
beginSweepToSeedWallet();
|
||||
} else {
|
||||
ImGui::TextColored(medium, "Waiting for the sweep transaction to be mined…");
|
||||
ImGui::TextColored(kMedium, "%s", TR("mig_waiting_mine"));
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ui::material::StyledButton("Refresh", ImVec2(110, 0)))
|
||||
if (ui::material::StyledButton(TR("refresh"), ImVec2(110, 0)))
|
||||
pollSweepStatus();
|
||||
ImGui::SameLine();
|
||||
if (ui::material::StyledButton("Later", ImVec2(100, 0)))
|
||||
if (ui::material::StyledButton(TR("mig_later"), ImVec2(100, 0)))
|
||||
close(); // pending state + txid persist; resumes here next time
|
||||
break;
|
||||
}
|
||||
|
||||
case SeedMigrationStep::Adopting:
|
||||
ImGui::TextWrapped("Installing your new wallet and rescanning…");
|
||||
ImGui::TextWrapped("%s", TR("mig_adopting"));
|
||||
ImGui::Spacing();
|
||||
if (!seed_migration_status_.empty())
|
||||
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(ui::material::OnSurfaceMedium()),
|
||||
"%s", seed_migration_status_.c_str());
|
||||
ImGui::TextColored(kMedium, "%s", seed_migration_status_.c_str());
|
||||
break;
|
||||
|
||||
case SeedMigrationStep::Done:
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.3f, 0.8f, 0.3f, 1.0f));
|
||||
ImGui::TextWrapped("Migration complete. Your wallet is now backed by your seed phrase.");
|
||||
ImGui::TextWrapped("%s", TR("mig_done"));
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::Spacing();
|
||||
ImGui::TextWrapped("The daemon is rescanning to show your funds — this can take a few "
|
||||
"minutes. Your previous wallet was saved as a .bak in the data folder.");
|
||||
ImGui::TextWrapped("%s", TR("mig_done_detail"));
|
||||
if (!seed_migration_status_.empty()) { // a non-fatal warning (e.g. the daemon didn't restart)
|
||||
ImGui::Spacing();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ui::material::Error());
|
||||
@@ -3269,7 +3257,7 @@ void App::renderSeedMigrationDialog()
|
||||
}
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
if (ui::material::StyledButton("Done", ImVec2(120, 0)))
|
||||
if (ui::material::StyledButton(TR("mig_done_btn"), ImVec2(120, 0)))
|
||||
close();
|
||||
break;
|
||||
|
||||
@@ -3279,7 +3267,7 @@ void App::renderSeedMigrationDialog()
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
if (ui::material::StyledButton("Close", ImVec2(120, 0)))
|
||||
if (ui::material::StyledButton(TR("close"), ImVec2(120, 0)))
|
||||
close();
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user