i18n(wizard): translate the first-run wizard (was all hardcoded English)

The first-run wizard predated the i18n system — every label, button, and help
line was hardcoded English. Route them all through TR():

- 62 literals in app_wizard.cpp replaced with TR(). Where a wizard string is a
  parallel of existing content, it reuses the existing key (the whole bootstrap_*
  family, plus theme / language / balance_layout / low_spec_mode / ui_opacity /
  console_scanline / download / retry / cancel) so it inherits their translations
  and stays consistent with Settings. Button labels drop their ##id suffix and use
  TR(key) directly (the app's established TactileButton(TR(...)) pattern; the
  wizard's phase buttons never share a frame, so no id collision).
- 55 new wiz_* English keys in i18n.cpp for the wizard-specific strings, and their
  translations back-filled across all 8 languages (es/de/fr/pt/ru/zh/ja/ko);
  format specifiers (%s, %zu) and newlines preserved. CJK subset rebuilt.

Verified on the sweep: English renders with correct labels in place (all keys
resolve), and a German run shows the reused + new keys translated end-to-end
(Darstellung / Acryl-Glaseffekte / Verschlüsseln & Weiter …).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-12 01:41:18 -05:00
parent 758ea06ab7
commit 09e0b962c3
11 changed files with 581 additions and 84 deletions

View File

@@ -189,14 +189,14 @@ void App::renderFirstRunWizard() {
headerCy += logoSize + 8.0f * dp;
{
const char* welcomeTitle = "Welcome to ObsidianDragon!";
const char* welcomeTitle = TR("wiz_welcome_title");
ImVec2 wts = titleFont->CalcTextSizeA(titleFont->LegacySize, FLT_MAX, 0, welcomeTitle);
dl->AddText(titleFont, titleFont->LegacySize,
ImVec2(winPos.x + (winSize.x - wts.x) * 0.5f, headerCy), textCol, welcomeTitle);
headerCy += wts.y + 6.0f * dp;
// Warmer, less-sparse header: a one-line subtitle under the welcome (dimmed body).
const char* welcomeSub = "A few quick choices and your full node is ready.";
const char* welcomeSub = TR("wiz_welcome_sub");
ImVec2 sts = bodyFont->CalcTextSizeA(bodyFont->LegacySize, FLT_MAX, 0, welcomeSub);
dl->AddText(bodyFont, bodyFont->LegacySize,
ImVec2(winPos.x + (winSize.x - sts.x) * 0.5f, headerCy), dimCol, welcomeSub);
@@ -274,13 +274,13 @@ void App::renderFirstRunWizard() {
{
float iconW = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, stepIcon(state)).x;
dl->AddText(iconFont, iconFont->LegacySize, ImVec2(cx, cy), dimCol, stepIcon(state));
dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx + iconW + 4.0f * dp, cy), dimCol, "Step 1");
dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx + iconW + 4.0f * dp, cy), dimCol, TR("wiz_step1"));
cy += captionFont->LegacySize + 6.0f * dp;
}
// Title
{
const char* t = "Appearance";
const char* t = TR("wiz_appearance");
dl->AddText(titleFont, titleFont->LegacySize, ImVec2(cx, cy), textCol, t);
cy += titleFont->LegacySize + 10.0f * dp;
}
@@ -336,14 +336,14 @@ void App::renderFirstRunWizard() {
for (const auto& skin : skins) {
if (skin.id == skinMgr.activeSkinId()) { activePreview = skin.name; break; }
}
dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy + 4.0f * dp), textCol, "Theme");
dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy + 4.0f * dp), textCol, TR("theme"));
float comboX = cx + 110.0f * dp;
float comboW = contentW - 110.0f * dp;
ImGui::SetCursorScreenPos(ImVec2(comboX, cy));
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 4.0f * dp);
ImGui::SetNextItemWidth(comboW);
if (ImGui::BeginCombo("##wiz_theme", activePreview.c_str())) {
ImGui::TextDisabled("Built-in");
ImGui::TextDisabled(TR("wiz_theme_builtin"));
ImGui::Separator();
for (const auto& skin : skins) {
if (!skin.bundled) continue;
@@ -359,7 +359,7 @@ void App::renderFirstRunWizard() {
for (const auto& skin : skins) { if (!skin.bundled) { hasCustom = true; break; } }
if (hasCustom) {
ImGui::Spacing();
ImGui::TextDisabled("Custom");
ImGui::TextDisabled(TR("wiz_theme_custom"));
ImGui::Separator();
for (const auto& skin : skins) {
if (skin.bundled) continue;
@@ -367,7 +367,7 @@ void App::renderFirstRunWizard() {
if (!skin.valid) {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1,0.3f,0.3f,1));
ImGui::BeginDisabled(true);
ImGui::Selectable((skin.name + " (invalid)").c_str(), false);
ImGui::Selectable((skin.name + TR("wiz_theme_invalid")).c_str(), false);
ImGui::EndDisabled();
ImGui::PopStyleColor();
} else {
@@ -395,7 +395,7 @@ void App::renderFirstRunWizard() {
for (const auto& l : layouts) {
if (l.id == wiz_balance_layout) { balPreview = l.name; break; }
}
dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy + 4.0f * dp), textCol, "Balance Layout");
dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy + 4.0f * dp), textCol, TR("balance_layout"));
float comboX = cx + 110.0f * dp;
float comboW = contentW - 110.0f * dp;
ImGui::SetCursorScreenPos(ImVec2(comboX, cy));
@@ -426,7 +426,7 @@ void App::renderFirstRunWizard() {
langNames.reserve(languages.size());
for (const auto& lang : languages) langNames.push_back(lang.second.c_str());
dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy + 4.0f * dp), textCol, "Language");
dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy + 4.0f * dp), textCol, TR("language"));
float comboX = cx + 110.0f * dp;
float comboW = contentW - 110.0f * dp;
ImGui::SetCursorScreenPos(ImVec2(comboX, cy));
@@ -497,10 +497,10 @@ void App::renderFirstRunWizard() {
ImGui::SameLine();
dl->AddText(bodyFont, bodyFont->LegacySize,
ImVec2(ImGui::GetCursorScreenPos().x, cy + 2.0f * dp), textCol,
"Low-spec mode");
TR("low_spec_mode"));
cy += bodyFont->LegacySize + 6.0f * dp;
dl->AddText(captionFont, captionFont->LegacySize,
ImVec2(cx + 28.0f * dp, cy), dimCol, "Disable all heavy visual effects");
ImVec2(cx + 28.0f * dp, cy), dimCol, TR("wiz_lowspec_desc"));
cy += captionFont->LegacySize + 16.0f * dp;
ImGui::BeginDisabled(wiz_low_spec);
@@ -508,15 +508,15 @@ void App::renderFirstRunWizard() {
// Acrylic blur slider
dl->AddText(bodyFont, bodyFont->LegacySize,
ImVec2(cx, cy + 2.0f * dp), textCol,
"Acrylic glass effects");
TR("wiz_acrylic"));
cy += bodyFont->LegacySize + 4.0f * dp;
dl->AddText(captionFont, captionFont->LegacySize,
ImVec2(cx, cy), dimCol, "Translucent blur on panels (Off disables)");
ImVec2(cx, cy), dimCol, TR("wiz_acrylic_desc"));
cy += captionFont->LegacySize + 10.0f * dp;
{
dl->AddText(captionFont, captionFont->LegacySize,
ImVec2(cx + 4.0f * dp, cy), textCol, "Level:");
ImVec2(cx + 4.0f * dp, cy), textCol, TR("wiz_level"));
ImGui::SetCursorScreenPos(ImVec2(cx + 72.0f * dp, cy - 2.0f * dp));
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 4.0f * dp);
float sliderW = contentW - 72.0f * dp;
@@ -524,7 +524,7 @@ void App::renderFirstRunWizard() {
{
char blur_fmt[16];
if (wiz_blur_amount < 0.01f)
snprintf(blur_fmt, sizeof(blur_fmt), "Off");
snprintf(blur_fmt, sizeof(blur_fmt), TR("wiz_off"));
else
snprintf(blur_fmt, sizeof(blur_fmt), "%.0f%%%%", wiz_blur_amount * 25.0f);
if (ImGui::SliderFloat("##wiz_blur", &wiz_blur_amount, 0.0f, 4.0f, blur_fmt,
@@ -558,19 +558,19 @@ void App::renderFirstRunWizard() {
ImGui::SameLine();
dl->AddText(bodyFont, bodyFont->LegacySize,
ImVec2(ImGui::GetCursorScreenPos().x, cy + 2.0f * dp), textCol,
"Theme visual effects");
TR("wiz_theme_effects"));
cy += bodyFont->LegacySize + 6.0f * dp;
dl->AddText(captionFont, captionFont->LegacySize,
ImVec2(cx + 28.0f * dp, cy), dimCol, "Animated borders, color wash");
ImVec2(cx + 28.0f * dp, cy), dimCol, TR("wiz_theme_effects_desc"));
cy += captionFont->LegacySize + 16.0f * dp;
// UI Opacity slider
dl->AddText(bodyFont, bodyFont->LegacySize,
ImVec2(cx, cy + 2.0f * dp), textCol,
"UI Opacity");
TR("ui_opacity"));
cy += bodyFont->LegacySize + 4.0f * dp;
dl->AddText(captionFont, captionFont->LegacySize,
ImVec2(cx, cy), dimCol, "Card & sidebar transparency (1.0 = solid)");
ImVec2(cx, cy), dimCol, TR("wiz_ui_opacity_desc"));
cy += captionFont->LegacySize + 10.0f * dp;
{
ImGui::SetCursorScreenPos(ImVec2(cx, cy - 2.0f * dp));
@@ -599,10 +599,10 @@ void App::renderFirstRunWizard() {
ImGui::SameLine();
dl->AddText(bodyFont, bodyFont->LegacySize,
ImVec2(ImGui::GetCursorScreenPos().x, cy + 2.0f * dp), textCol,
"Console scanline");
TR("console_scanline"));
cy += bodyFont->LegacySize + 6.0f * dp;
dl->AddText(captionFont, captionFont->LegacySize,
ImVec2(cx + 28.0f * dp, cy), dimCol, "CRT scanline effect in console");
ImVec2(cx + 28.0f * dp, cy), dimCol, TR("wiz_scanline_desc"));
cy += captionFont->LegacySize + 24.0f * dp;
ImGui::EndDisabled(); // low-spec
@@ -620,7 +620,7 @@ void App::renderFirstRunWizard() {
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::ColorConvertU32ToFloat4(ui::material::PrimaryVariant()));
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(ui::material::OnPrimary()));
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
if (ui::material::TactileButton("Continue##app", ImVec2(btnW, btnH))) {
if (ui::material::TactileButton(TR("wiz_continue"), ImVec2(btnW, btnH))) {
// Save appearance choices, advance to Bootstrap
settings_->setAcrylicEnabled(wiz_blur_amount > 0.001f);
settings_->setAcrylicQuality(wiz_blur_amount > 0.001f
@@ -667,23 +667,23 @@ void App::renderFirstRunWizard() {
float iconW = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, stepIcon(state)).x;
dl->AddText(iconFont, iconFont->LegacySize, ImVec2(cx, cy), dimCol, stepIcon(state));
float labelX = cx + iconW + 4.0f * dp;
dl->AddText(captionFont, captionFont->LegacySize, ImVec2(labelX, cy), dimCol, "Step 2");
float step2W = captionFont->CalcTextSizeA(captionFont->LegacySize, FLT_MAX, 0, "Step 2").x;
dl->AddText(captionFont, captionFont->LegacySize, ImVec2(labelX, cy), dimCol, TR("wiz_step2"));
float step2W = captionFont->CalcTextSizeA(captionFont->LegacySize, FLT_MAX, 0, TR("wiz_step2")).x;
float titleX = labelX + step2W + 12.0f * dp;
dl->AddText(bodyFont, bodyFont->LegacySize, ImVec2(titleX, cy), dimCol, "Bootstrap");
dl->AddText(bodyFont, bodyFont->LegacySize, ImVec2(titleX, cy), dimCol, TR("wiz_bootstrap"));
cy += captionFont->LegacySize + 4.0f * dp;
} else {
// Step indicator
{
float iconW = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, stepIcon(state)).x;
dl->AddText(iconFont, iconFont->LegacySize, ImVec2(cx, cy), dimCol, stepIcon(state));
dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx + iconW + 4.0f * dp, cy), dimCol, "Step 2");
dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx + iconW + 4.0f * dp, cy), dimCol, TR("wiz_step2"));
cy += captionFont->LegacySize + 4.0f * dp;
}
// Title
{
const char* t = "Bootstrap";
const char* t = TR("wiz_bootstrap");
dl->AddText(titleFont, titleFont->LegacySize, ImVec2(cx, cy), textCol, t);
cy += titleFont->LegacySize + 6.0f * dp;
}
@@ -706,11 +706,11 @@ void App::renderFirstRunWizard() {
const char* statusTitle;
if (prog.state == util::Bootstrap::State::Downloading)
statusTitle = "Downloading bootstrap...";
statusTitle = TR("bootstrap_downloading");
else if (prog.state == util::Bootstrap::State::Verifying)
statusTitle = "Verifying checksums...";
statusTitle = TR("bootstrap_verifying");
else
statusTitle = "Extracting blockchain data...";
statusTitle = TR("bootstrap_extracting");
dl->AddText(bodyFont, bodyFont->LegacySize, ImVec2(cx, cy), textCol, statusTitle);
cy += bodyFont->LegacySize + 12.0f * dp;
@@ -739,7 +739,7 @@ void App::renderFirstRunWizard() {
if (prog.state == util::Bootstrap::State::Extracting) {
dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy),
dimCol, "(wallet.dat is protected)");
dimCol, TR("bootstrap_wallet_protected"));
cy += captionFont->LegacySize + 6.0f * dp;
}
@@ -755,9 +755,9 @@ void App::renderFirstRunWizard() {
dl->AddCircleFilled(ImVec2(cx + dotR, cy + captionFont->LegacySize * 0.5f),
dotR, dotCol);
const char* label = daemonUp ? (dStatus.find("Stopping") != std::string::npos
? "Daemon stopping..."
: "Daemon running")
: "Daemon stopped";
? TR("bootstrap_daemon_stopping")
: TR("bootstrap_daemon_running"))
: TR("bootstrap_daemon_stopped");
dl->AddText(captionFont, captionFont->LegacySize,
ImVec2(cx + dotR * 2.0f + 6.0f * dp, cy),
(dimCol & 0x00FFFFFF) | IM_COL32(0,0,0,140), label);
@@ -772,7 +772,7 @@ void App::renderFirstRunWizard() {
float cancelBX = rightX + (colW - cancelW) * 0.5f;
ImGui::SetCursorScreenPos(ImVec2(cancelBX, cy));
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
if (ui::material::TactileButton("Cancel##bs", ImVec2(cancelW, cancelH))) {
if (ui::material::TactileButton(TR("cancel"), ImVec2(cancelW, cancelH))) {
bootstrap_->cancel();
}
ImGui::PopStyleVar();
@@ -799,10 +799,10 @@ void App::renderFirstRunWizard() {
errMsg = bootstrap_->getProgress().error;
bootstrap_.reset();
}
if (errMsg.empty()) errMsg = "Bootstrap failed";
if (errMsg.empty()) errMsg = TR("wiz_bootstrap_failed");
dl->AddText(bodyFont, bodyFont->LegacySize, ImVec2(cx, cy),
ui::material::Error(), "Download Failed");
ui::material::Error(), TR("wiz_download_failed"));
cy += bodyFont->LegacySize + 8.0f * dp;
dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy), textCol,
@@ -821,7 +821,7 @@ void App::renderFirstRunWizard() {
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(ui::material::OnPrimary()));
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
ImGui::BeginDisabled(!supportsFullNodeLifecycleActions());
if (ui::material::TactileButton("Retry##bs", ImVec2(btnW2, btnH2))) {
if (ui::material::TactileButton(TR("retry"), ImVec2(btnW2, btnH2))) {
// Stop embedded daemon before bootstrap to avoid chain data corruption
stopDaemonForBootstrap();
bootstrap_ = std::make_unique<util::Bootstrap>();
@@ -835,7 +835,7 @@ void App::renderFirstRunWizard() {
ImGui::SetCursorScreenPos(ImVec2(bx + btnW2 + 12.0f * dp, cy));
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
if (ui::material::TactileButton("Skip##bsfail", ImVec2(btnW2, btnH2))) {
if (ui::material::TactileButton(TR("wiz_skip"), ImVec2(btnW2, btnH2))) {
wizard_phase_ = WizardPhase::EncryptOffer;
}
ImGui::PopStyleVar();
@@ -878,11 +878,11 @@ void App::renderFirstRunWizard() {
{
float iw = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, ICON_MD_WARNING).x;
dl->AddText(iconFont, iconFont->LegacySize, ImVec2(cx, cy), warnCol, ICON_MD_WARNING);
dl->AddText(bodyFont, bodyFont->LegacySize, ImVec2(cx + iw + 4.0f * dp, cy), warnCol, "External daemon running");
dl->AddText(bodyFont, bodyFont->LegacySize, ImVec2(cx + iw + 4.0f * dp, cy), warnCol, TR("wiz_ext_daemon_running"));
}
cy += bodyFont->LegacySize + 4.0f * dp;
{
const char* warnBody = "It must be stopped before downloading a bootstrap, otherwise chain data could be corrupted.";
const char* warnBody = TR("wiz_ext_daemon_warning");
ImVec2 ws = captionFont->CalcTextSizeA(captionFont->LegacySize, FLT_MAX, contentW, warnBody);
dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy), textCol, warnBody, nullptr, contentW);
cy += ws.y + 12.0f * dp;
@@ -905,9 +905,9 @@ void App::renderFirstRunWizard() {
IM_COL32(220, 60, 60, 255)));
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 255));
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
if (ui::material::TactileButton("Stop Daemon##wiz", ImVec2(stopW, btnH2))) {
if (ui::material::TactileButton(TR("wiz_stop_daemon"), ImVec2(stopW, btnH2))) {
wizard_stopping_external_ = true;
wizard_stop_status_ = "Sending stop command...";
wizard_stop_status_ = TR("wiz_daemon_sending_stop");
async_tasks_.submit("wizard-stop-external-daemon", [this](const util::AsyncTaskManager::Token& token) {
auto config = rpc::Connection::autoDetectConfig();
if (!config.rpcuser.empty() && !config.rpcpassword.empty()) {
@@ -919,17 +919,17 @@ void App::renderFirstRunWizard() {
tmp_rpc->disconnect();
}
}
wizard_stop_status_ = "Waiting for daemon to shut down...";
wizard_stop_status_ = TR("wiz_daemon_waiting_stop");
for (int i = 0; i < 60 && !token.cancelled(); i++) {
std::this_thread::sleep_for(std::chrono::seconds(1));
if (!daemon::EmbeddedDaemon::isRpcPortInUse()) {
wizard_stop_status_ = "Daemon stopped.";
wizard_stop_status_ = TR("wiz_daemon_stopped_ok");
wizard_stopping_external_ = false;
return;
}
}
if (token.cancelled()) return;
wizard_stop_status_ = "Daemon did not stop — try manually.";
wizard_stop_status_ = TR("wiz_daemon_stop_failed");
wizard_stopping_external_ = false;
});
}
@@ -938,7 +938,7 @@ void App::renderFirstRunWizard() {
ImGui::SetCursorScreenPos(ImVec2(bx + stopW + 12.0f * dp, cy));
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
if (ui::material::TactileButton("Skip##extd", ImVec2(skipW2, btnH2))) {
if (ui::material::TactileButton(TR("wiz_skip"), ImVec2(skipW2, btnH2))) {
wizard_phase_ = WizardPhase::EncryptOffer;
}
ImGui::PopStyleVar();
@@ -947,7 +947,7 @@ void App::renderFirstRunWizard() {
} else {
// --- Normal bootstrap offer ---
{
const char* bsText = "Download a blockchain bootstrap to dramatically speed up initial sync.\n\nYour existing wallet.dat will NOT be modified or replaced.";
const char* bsText = TR("wiz_bootstrap_desc");
ImVec2 bsSize = bodyFont->CalcTextSizeA(bodyFont->LegacySize, FLT_MAX, contentW, bsText);
dl->AddText(bodyFont, bodyFont->LegacySize, ImVec2(cx, cy), textCol, bsText, nullptr, contentW);
cy += bsSize.y + 8.0f * dp;
@@ -960,7 +960,7 @@ void App::renderFirstRunWizard() {
ImU32 warnCol = (textCol & 0x00FFFFFF) | ((ImU32)(255 * warnOpacity) << 24);
float iw = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, ICON_MD_WARNING).x;
dl->AddText(iconFont, iconFont->LegacySize, ImVec2(cx, cy), warnCol, ICON_MD_WARNING);
const char* twText = "Only use bootstrap.dragonx.is or bootstrap2.dragonx.is. Using files from untrusted sources could compromise your node.";
const char* twText = TR("bootstrap_trust_warning");
float twWrap = contentW - iw - 4.0f * dp;
ImVec2 twSize = captionFont->CalcTextSizeA(captionFont->LegacySize, FLT_MAX, twWrap, twText);
dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx + iw + 4.0f * dp, cy), warnCol, twText, nullptr, twWrap);
@@ -979,9 +979,9 @@ void App::renderFirstRunWizard() {
dl->AddCircleFilled(ImVec2(cx + dotR, cy + captionFont->LegacySize * 0.5f),
dotR, dotCol);
const char* label = daemonUp ? (dStatus.find("Stopping") != std::string::npos
? "Daemon stopping..."
: "Daemon running")
: "Daemon stopped";
? TR("bootstrap_daemon_stopping")
: TR("bootstrap_daemon_running"))
: TR("bootstrap_daemon_stopped");
dl->AddText(captionFont, captionFont->LegacySize,
ImVec2(cx + dotR * 2.0f + 6.0f * dp, cy),
(dimCol & 0x00FFFFFF) | IM_COL32(0,0,0,140), label);
@@ -1004,7 +1004,7 @@ void App::renderFirstRunWizard() {
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(ui::material::OnPrimary()));
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
ImGui::BeginDisabled(!supportsFullNodeLifecycleActions());
if (ui::material::TactileButton("Download##bs", ImVec2(dlBtnW, btnH2))) {
if (ui::material::TactileButton(TR("download"), ImVec2(dlBtnW, btnH2))) {
// Stop embedded daemon before bootstrap to avoid chain data corruption
stopDaemonForBootstrap();
bootstrap_ = std::make_unique<util::Bootstrap>();
@@ -1023,7 +1023,7 @@ void App::renderFirstRunWizard() {
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(ui::material::OnSurface()));
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
ImGui::BeginDisabled(!supportsFullNodeLifecycleActions());
if (ui::material::TactileButton("Mirror##bs_mirror", ImVec2(mirrorW, btnH2))) {
if (ui::material::TactileButton(TR("bootstrap_mirror"), ImVec2(mirrorW, btnH2))) {
stopDaemonForBootstrap();
bootstrap_ = std::make_unique<util::Bootstrap>();
std::string dataDir = util::Platform::getDragonXDataDir();
@@ -1033,7 +1033,7 @@ void App::renderFirstRunWizard() {
}
ImGui::EndDisabled();
if (ImGui::IsItemHovered()) {
ui::material::Tooltip("Download from mirror (bootstrap2.dragonx.is).\nUse this if the main download is slow or failing.");
ui::material::Tooltip(TR("bootstrap_mirror_tooltip"));
}
ImGui::PopStyleVar();
ImGui::PopStyleColor(3);
@@ -1041,7 +1041,7 @@ void App::renderFirstRunWizard() {
// --- Skip button ---
ImGui::SetCursorScreenPos(ImVec2(bx + dlBtnW + 8.0f * dp + mirrorW + 8.0f * dp, cy));
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
if (ui::material::TactileButton("Skip##bs", ImVec2(skipW2, btnH2))) {
if (ui::material::TactileButton(TR("wiz_skip"), ImVec2(skipW2, btnH2))) {
wizard_phase_ = WizardPhase::EncryptOffer;
}
ImGui::PopStyleVar();
@@ -1093,14 +1093,14 @@ void App::renderFirstRunWizard() {
{
float iconW = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, stepIcon(state)).x;
dl->AddText(iconFont, iconFont->LegacySize, ImVec2(cx, cy), dimCol, stepIcon(state));
dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx + iconW + 4.0f * dp, cy), dimCol, "Step 3");
dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx + iconW + 4.0f * dp, cy), dimCol, TR("wiz_step3"));
cy += captionFont->LegacySize + 4.0f * dp;
}
// Title (changes for PinSetup sub-state)
{
const char* t = (isFocused && wizard_phase_ == WizardPhase::PinSetup)
? "Quick-Unlock PIN" : "Encryption";
? TR("wiz_pin_title") : TR("wiz_encryption");
dl->AddText(titleFont, titleFont->LegacySize, ImVec2(cx, cy), textCol, t);
cy += titleFont->LegacySize + 6.0f * dp;
}
@@ -1117,11 +1117,11 @@ void App::renderFirstRunWizard() {
ImU32 okCol = ui::material::Secondary();
float iw = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, ICON_MD_VERIFIED_USER).x;
dl->AddText(iconFont, iconFont->LegacySize, ImVec2(cx, cy), okCol, ICON_MD_VERIFIED_USER);
dl->AddText(bodyFont, bodyFont->LegacySize, ImVec2(cx + iw + 6.0f * dp, cy), okCol, "Wallet is already encrypted");
dl->AddText(bodyFont, bodyFont->LegacySize, ImVec2(cx + iw + 6.0f * dp, cy), okCol, TR("wiz_already_encrypted"));
cy += bodyFont->LegacySize + 12.0f * dp;
}
{
const char* desc = "Your wallet is protected with a passphrase. No further action is needed.";
const char* desc = TR("wiz_already_encrypted_desc");
ImVec2 ds = bodyFont->CalcTextSizeA(bodyFont->LegacySize, FLT_MAX, contentW, desc);
dl->AddText(bodyFont, bodyFont->LegacySize, ImVec2(cx, cy), textCol, desc, nullptr, contentW);
cy += ds.y + 20.0f * dp;
@@ -1136,7 +1136,7 @@ void App::renderFirstRunWizard() {
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::ColorConvertU32ToFloat4(ui::material::PrimaryVariant()));
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(ui::material::OnPrimary()));
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
if (ui::material::TactileButton("Continue##encok", ImVec2(btnW2, btnH2))) {
if (ui::material::TactileButton(TR("wiz_continue"), ImVec2(btnW2, btnH2))) {
wizard_phase_ = WizardPhase::Done;
settings_->setWizardCompleted(true);
settings_->save();
@@ -1148,7 +1148,7 @@ void App::renderFirstRunWizard() {
} else if (isFocused) {
// ---- Encryption offer + optional PIN (combined) ----
{
const char* encDesc = "Encrypt your wallet to protect private keys with a passphrase.";
const char* encDesc = TR("wiz_encrypt_desc");
ImVec2 edSize = bodyFont->CalcTextSizeA(bodyFont->LegacySize, FLT_MAX, contentW, encDesc);
dl->AddText(bodyFont, bodyFont->LegacySize, ImVec2(cx, cy), textCol, encDesc, nullptr, contentW);
cy += edSize.y + 6.0f * dp;
@@ -1157,7 +1157,7 @@ void App::renderFirstRunWizard() {
ImU32 warnCol2 = ui::material::Warning();
float iw = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, ICON_MD_WARNING).x;
dl->AddText(iconFont, iconFont->LegacySize, ImVec2(cx, cy), warnCol2, ICON_MD_WARNING);
const char* warnLoss = "If you lose your passphrase, you lose access to your funds.";
const char* warnLoss = TR("wiz_encrypt_warning");
float wlWrap = contentW - iw - 4.0f * dp;
ImVec2 wlSize = bodyFont->CalcTextSizeA(bodyFont->LegacySize, FLT_MAX, wlWrap, warnLoss);
dl->AddText(bodyFont, bodyFont->LegacySize, ImVec2(cx + iw + 4.0f * dp, cy), warnCol2, warnLoss, nullptr, wlWrap);
@@ -1165,7 +1165,7 @@ void App::renderFirstRunWizard() {
}
// Passphrase input
dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy), dimCol, "Passphrase:");
dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy), dimCol, TR("wiz_passphrase"));
cy += captionFont->LegacySize + 4.0f * dp;
ImGui::SetCursorScreenPos(ImVec2(cx, cy));
@@ -1177,7 +1177,7 @@ void App::renderFirstRunWizard() {
ImGui::PopItemWidth();
cy += 36.0f * dp + 6.0f * dp;
dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy), dimCol, "Confirm:");
dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy), dimCol, TR("wiz_confirm"));
cy += captionFont->LegacySize + 4.0f * dp;
ImGui::SetCursorScreenPos(ImVec2(cx, cy));
@@ -1192,16 +1192,16 @@ void App::renderFirstRunWizard() {
// Strength meter
{
size_t len = strlen(encrypt_pass_buf_);
const char* strengthLabel = "Weak";
const char* strengthLabel = TR("wiz_strength_weak");
ImU32 strengthCol = ui::material::Error();
float strengthPct = 0.25f;
if (len >= 16) {
strengthLabel = "Strong"; strengthCol = ui::material::Secondary(); strengthPct = 1.0f;
strengthLabel = TR("wiz_strength_strong"); strengthCol = ui::material::Secondary(); strengthPct = 1.0f;
} else if (len >= 12) {
strengthLabel = "Good"; strengthCol = ui::material::Secondary(); strengthPct = 0.75f;
strengthLabel = TR("wiz_strength_good"); strengthCol = ui::material::Secondary(); strengthPct = 0.75f;
} else if (len >= 8) {
strengthLabel = "Fair"; strengthCol = ui::material::Warning(); strengthPct = 0.5f;
strengthLabel = TR("wiz_strength_fair"); strengthCol = ui::material::Warning(); strengthPct = 0.5f;
}
float sBarH = 4.0f * dp, sBarR = 2.0f * dp;
@@ -1214,7 +1214,7 @@ void App::renderFirstRunWizard() {
cy += sBarH + 4.0f * dp;
char slabel[64];
snprintf(slabel, sizeof(slabel), "Strength: %s", strengthLabel);
snprintf(slabel, sizeof(slabel), TR("wiz_strength"), strengthLabel);
dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy), dimCol, slabel);
cy += captionFont->LegacySize + 10.0f * dp;
}
@@ -1224,14 +1224,14 @@ void App::renderFirstRunWizard() {
size_t pLen = strlen(encrypt_pass_buf_);
if (pLen > 0 && pLen < 8) {
char fb[80];
snprintf(fb, sizeof(fb), "Passphrase must be at least 8 characters (%zu/8)", pLen);
snprintf(fb, sizeof(fb), TR("wiz_pass_too_short"), pLen);
dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy),
ui::material::Error(), fb);
cy += captionFont->LegacySize + 6.0f * dp;
} else if (pLen >= 8 && strlen(encrypt_confirm_buf_) > 0 &&
strcmp(encrypt_pass_buf_, encrypt_confirm_buf_) != 0) {
dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy),
ui::material::Error(), "Passphrases do not match");
ui::material::Error(), TR("wiz_pass_mismatch"));
cy += captionFont->LegacySize + 6.0f * dp;
}
}
@@ -1243,12 +1243,12 @@ void App::renderFirstRunWizard() {
cy += 8.0f * dp;
{
const char* pinTitle = "Quick-Unlock PIN (optional)";
const char* pinTitle = TR("wiz_pin_optional");
dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy), textCol, pinTitle);
cy += captionFont->LegacySize + 4.0f * dp;
}
dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy), dimCol, "PIN (4-8 digits):");
dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy), dimCol, TR("wiz_pin_label"));
cy += captionFont->LegacySize + 4.0f * dp;
ImGui::SetCursorScreenPos(ImVec2(cx, cy));
@@ -1260,7 +1260,7 @@ void App::renderFirstRunWizard() {
ImGui::PopItemWidth();
cy += 36.0f * dp + 6.0f * dp;
dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy), dimCol, "Confirm PIN:");
dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy), dimCol, TR("wiz_pin_confirm"));
cy += captionFont->LegacySize + 4.0f * dp;
ImGui::SetCursorScreenPos(ImVec2(cx, cy));
@@ -1277,12 +1277,12 @@ void App::renderFirstRunWizard() {
std::string pinStr(wizard_pin_buf_);
if (!pinStr.empty() && !util::SecureVault::isValidPin(pinStr)) {
dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy),
ui::material::Error(), "PIN must be 4-8 digits");
ui::material::Error(), TR("wiz_pin_invalid"));
cy += captionFont->LegacySize + 6.0f * dp;
} else if (!pinStr.empty() && strlen(wizard_pin_confirm_buf_) > 0 &&
pinStr != std::string(wizard_pin_confirm_buf_)) {
dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy),
ui::material::Error(), "PINs do not match");
ui::material::Error(), TR("wiz_pin_mismatch"));
cy += captionFont->LegacySize + 6.0f * dp;
}
}
@@ -1303,7 +1303,7 @@ void App::renderFirstRunWizard() {
}
if (passEdgeSpace) {
dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy),
ui::material::Error(), "Passphrase has leading/trailing spaces — remove them");
ui::material::Error(), TR("wiz_pass_spaces"));
cy += captionFont->LegacySize + 6.0f * dp;
}
@@ -1333,7 +1333,7 @@ void App::renderFirstRunWizard() {
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(ui::material::OnPrimary()));
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
ImGui::BeginDisabled(!canEncrypt);
if (ui::material::TactileButton("Encrypt & Continue##wiz", ImVec2(encBtnW, btnH2))) {
if (ui::material::TactileButton(TR("wiz_encrypt_continue"), ImVec2(encBtnW, btnH2))) {
// Save passphrase + optional PIN for background processing
wallet_security_.beginDeferredEncryption(
std::string(encrypt_pass_buf_),
@@ -1356,7 +1356,7 @@ void App::renderFirstRunWizard() {
wizard_phase_ = WizardPhase::Done;
settings_->setWizardCompleted(true);
settings_->save();
ui::Notifications::instance().info("Encryption will complete in the background");
ui::Notifications::instance().info(TR("wiz_encrypt_bg"));
}
ImGui::EndDisabled();
ImGui::PopStyleVar();
@@ -1364,12 +1364,12 @@ void App::renderFirstRunWizard() {
ImGui::SetCursorScreenPos(ImVec2(bx + encBtnW + 12.0f * dp, cy));
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
if (ui::material::TactileButton("Skip##enc", ImVec2(skipW2, btnH2))) {
if (ui::material::TactileButton(TR("wiz_skip"), ImVec2(skipW2, btnH2))) {
static bool s_skipEncConfirm = false;
if (!s_skipEncConfirm) {
// Skipping stores private keys UNENCRYPTED — require a confirming second click.
s_skipEncConfirm = true;
encrypt_status_ = "Continue WITHOUT encryption? Keys will be stored unencrypted — click Skip again to confirm.";
encrypt_status_ = TR("wiz_skip_confirm");
} else {
s_skipEncConfirm = false;
wizard_phase_ = WizardPhase::Done;
@@ -1389,7 +1389,7 @@ void App::renderFirstRunWizard() {
}
} else {
// ---- Not focused: show static description ----
const char* encDesc = "Encrypt your wallet to protect private keys with a passphrase.";
const char* encDesc = TR("wiz_encrypt_desc");
ImVec2 edSize = bodyFont->CalcTextSizeA(bodyFont->LegacySize, FLT_MAX, contentW, encDesc);
dl->AddText(bodyFont, bodyFont->LegacySize, ImVec2(cx, cy), dimCol, encDesc, nullptr, contentW);
cy += edSize.y + 6.0f * dp;

View File

@@ -464,6 +464,63 @@ void I18n::loadBuiltinEnglish()
strings_["tt_reduce_motion"] = "Disable animated transitions and balance lerp for accessibility";
strings_["ago"] = "ago";
strings_["wizard_daemon_start_failed"] = "Failed to start daemon \xe2\x80\x94 it will be retried automatically";
// First-run wizard (app_wizard.cpp). Bootstrap/appearance labels reuse existing keys; these are
// the wizard-specific strings.
strings_["wiz_welcome_title"] = "Welcome to ObsidianDragon!";
strings_["wiz_welcome_sub"] = "A few quick choices and your full node is ready.";
strings_["wiz_step1"] = "Step 1";
strings_["wiz_step2"] = "Step 2";
strings_["wiz_step3"] = "Step 3";
strings_["wiz_appearance"] = "Appearance";
strings_["wiz_theme_builtin"] = "Built-in";
strings_["wiz_theme_custom"] = "Custom";
strings_["wiz_theme_invalid"] = " (invalid)";
strings_["wiz_lowspec_desc"] = "Disable all heavy visual effects";
strings_["wiz_acrylic"] = "Acrylic glass effects";
strings_["wiz_acrylic_desc"] = "Translucent blur on panels (Off disables)";
strings_["wiz_level"] = "Level:";
strings_["wiz_off"] = "Off";
strings_["wiz_theme_effects"] = "Theme visual effects";
strings_["wiz_theme_effects_desc"] = "Animated borders, color wash";
strings_["wiz_ui_opacity_desc"] = "Card & sidebar transparency (1.0 = solid)";
strings_["wiz_scanline_desc"] = "CRT scanline effect in console";
strings_["wiz_bootstrap"] = "Bootstrap";
strings_["wiz_bootstrap_desc"] = "Download a blockchain bootstrap to dramatically speed up initial sync.\n\nYour existing wallet.dat will NOT be modified or replaced.";
strings_["wiz_skip"] = "Skip";
strings_["wiz_bootstrap_failed"] = "Bootstrap failed";
strings_["wiz_download_failed"] = "Download Failed";
strings_["wiz_ext_daemon_running"] = "External daemon running";
strings_["wiz_ext_daemon_warning"] = "It must be stopped before downloading a bootstrap, otherwise chain data could be corrupted.";
strings_["wiz_stop_daemon"] = "Stop Daemon";
strings_["wiz_daemon_sending_stop"] = "Sending stop command...";
strings_["wiz_daemon_waiting_stop"] = "Waiting for daemon to shut down...";
strings_["wiz_daemon_stopped_ok"] = "Daemon stopped.";
strings_["wiz_daemon_stop_failed"] = "Daemon did not stop \xe2\x80\x94 try manually.";
strings_["wiz_encryption"] = "Encryption";
strings_["wiz_pin_title"] = "Quick-Unlock PIN";
strings_["wiz_already_encrypted"] = "Wallet is already encrypted";
strings_["wiz_already_encrypted_desc"] = "Your wallet is protected with a passphrase. No further action is needed.";
strings_["wiz_continue"] = "Continue";
strings_["wiz_encrypt_desc"] = "Encrypt your wallet to protect private keys with a passphrase.";
strings_["wiz_encrypt_warning"] = "If you lose your passphrase, you lose access to your funds.";
strings_["wiz_passphrase"] = "Passphrase:";
strings_["wiz_confirm"] = "Confirm:";
strings_["wiz_strength_weak"] = "Weak";
strings_["wiz_strength_fair"] = "Fair";
strings_["wiz_strength_good"] = "Good";
strings_["wiz_strength_strong"] = "Strong";
strings_["wiz_strength"] = "Strength: %s";
strings_["wiz_pass_too_short"] = "Passphrase must be at least 8 characters (%zu/8)";
strings_["wiz_pass_mismatch"] = "Passphrases do not match";
strings_["wiz_pass_spaces"] = "Passphrase has leading/trailing spaces \xe2\x80\x94 remove them";
strings_["wiz_encrypt_continue"] = "Encrypt & Continue";
strings_["wiz_encrypt_bg"] = "Encryption will complete in the background";
strings_["wiz_skip_confirm"] = "Continue WITHOUT encryption? Keys will be stored unencrypted \xe2\x80\x94 click Skip again to confirm.";
strings_["wiz_pin_optional"] = "Quick-Unlock PIN (optional)";
strings_["wiz_pin_label"] = "PIN (4-8 digits):";
strings_["wiz_pin_confirm"] = "Confirm PIN:";
strings_["wiz_pin_invalid"] = "PIN must be 4-8 digits";
strings_["wiz_pin_mismatch"] = "PINs do not match";
strings_["settings_data_dir"] = "Data Dir:";
strings_["settings_wallet_size_label"] = "Wallet Size:";
strings_["settings_debug_changed"] = "Debug categories changed \xe2\x80\x94 restart daemon to apply";