5 Commits

Author SHA1 Message Date
09e0b962c3 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>
2026-07-12 01:41:18 -05:00
758ea06ab7 feat(wizard): modernize the first-run wizard to the current design language
The first-run wizard predated the modal-unification pass and was the least
on-brand surface left — a flat Surface() slab with a 3-card masonry whose
inactive steps were heavily greyed (alpha 165) and whose focused card used a
hard 2px border + a hand-drawn offset shadow.

Keeping the working phase logic and the "see all steps at once" masonry:
- Reveal the skin backdrop: the app already paints its marble / gradient /
  acrylic backdrop behind every window (drawWindowBackdrop), so drop the opaque
  Surface() fill (transparent WindowBg) and lay a gentle theme-tinted scrim over
  it. The wizard now sits on the same rich backdrop as the rest of the app.
- Focused card lifts with the shared DrawCardDropShadow (the uniform, non-
  clipping card shadow) instead of a bespoke offset rect, and a softer 1.5px
  accent ring.
- Inactive cards get lighter veils (upcoming 165->115, completed 110->70) so
  they read as "waiting" / "done" rather than disabled-grey.
- Warmer header: a one-line subtitle under the welcome title.

Verified on the headless sweep across all four phases (appearance / bootstrap /
encrypt / pin) on marble + obsidian at 1.0 and 1.5x DPI — backdrop shows, cards
float cleanly, geometry scales, nothing clipped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 22:48:22 -05:00
d189ee5ddb fix(ui): readable error text on dark modal backdrops
The Material --error reds are muted by design, so error text drawn with the
theme Error() color read low-contrast on the (now deeper) dark modal backdrops —
the audit flagged the seed-migration warning/error lines on dark / dragonx.

Add material::ReadableError(): the theme error lifted toward a legible light red
on dark themes, unchanged on light themes (where it's already a dark red that
reads on a light background). Apply it to the modal error-TEXT sites (lite
first-run, seed backup, seed migration). This is targeted — it never touches the
app-wide Error() used by toasts / badges / status dots — so nothing else shifts.

Verified on the sweep: the "node too old" warning (dark) and "daemon did not
respond" error (dragonx) now read clearly; light themes are unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 22:27:44 -05:00
32d6e34c45 fix(ui): theme-aware success green for modal status text
Modal success/status lines (import, backup, seed backup, seed migration) used a
hardcoded ImVec4(0.3, 0.8, 0.3) green that ignores the theme — fine on dark, but
faint on the light skins, where the audit flagged it as low-contrast. Switch
those 8 sites to ui::material::SuccessVec4(), so light themes get their tuned
dark green (#3D7A42 etc., readable on white) and dark themes their light green
(#81C784). The 2 status-bar mining indicators keep the vivid attention-green.

Verified on the sweep: the "already has a seed phrase" / "migration complete"
greens are now clearly readable on light, marble, and color-pop-light.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 22:18:48 -05:00
088e3371d9 feat(modals): stronger backdrop blur + dim, and anchor floating modals on light skins
The modal audit found card-less (floating) dialogs read slightly unanchored on
the light skins: the blur backdrop's dim is a theme-tinted frost, which darkens
on dark themes (content pops) but WHITENS on light themes — so sparse content
floated in a near-white expanse with no figure/ground separation.

- Light themes now get a neutral dark veil instead of the whitening frost, which
  darkens the surround and makes the modal's fields/buttons stand out.
- Dark themes keep the theme-tinted frost, a touch stronger (alpha 70 -> 90).
- Full-window blur deepened (64 -> 96) so the backdrop reads clearly frosted.

Verified on the headless sweep across light + dark: backup / debug-gate now
read as anchored groups; content-heavy modals (daemon-update, settings, wallets)
are unaffected (their own surfaces stand out), and dark-skin text contrast
improves for free from the deeper dim.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 22:18:48 -05:00
13 changed files with 647 additions and 120 deletions

Binary file not shown.

View File

@@ -1369,6 +1369,61 @@
"warning_upper": "WARNUNG!",
"website": "Webseite",
"window_opacity": "Fenster-Transparenz",
"wiz_acrylic": "Acryl-Glaseffekte",
"wiz_acrylic_desc": "Transparente Unschärfe auf Panels (Aus deaktiviert)",
"wiz_already_encrypted": "Wallet ist bereits verschlüsselt",
"wiz_already_encrypted_desc": "Ihr Wallet ist mit einer Passphrase geschützt. Es sind keine weiteren Schritte erforderlich.",
"wiz_appearance": "Darstellung",
"wiz_bootstrap": "Bootstrap",
"wiz_bootstrap_desc": "Laden Sie ein Blockchain-Bootstrap herunter, um die anfängliche Synchronisierung erheblich zu beschleunigen.\n\nIhre vorhandene wallet.dat wird NICHT verändert oder ersetzt.",
"wiz_bootstrap_failed": "Bootstrap fehlgeschlagen",
"wiz_confirm": "Bestätigen:",
"wiz_continue": "Weiter",
"wiz_daemon_sending_stop": "Stopp-Befehl wird gesendet...",
"wiz_daemon_stop_failed": "Daemon wurde nicht gestoppt — manuell versuchen.",
"wiz_daemon_stopped_ok": "Daemon gestoppt.",
"wiz_daemon_waiting_stop": "Warten, bis der Daemon herunterfährt...",
"wiz_download_failed": "Download fehlgeschlagen",
"wiz_encrypt_bg": "Die Verschlüsselung wird im Hintergrund abgeschlossen",
"wiz_encrypt_continue": "Verschlüsseln & Weiter",
"wiz_encrypt_desc": "Verschlüsseln Sie Ihr Wallet, um private Schlüssel mit einer Passphrase zu schützen.",
"wiz_encrypt_warning": "Wenn Sie Ihre Passphrase verlieren, verlieren Sie den Zugriff auf Ihr Guthaben.",
"wiz_encryption": "Verschlüsselung",
"wiz_ext_daemon_running": "Externer Daemon läuft",
"wiz_ext_daemon_warning": "Er muss gestoppt werden, bevor ein Bootstrap heruntergeladen wird, sonst könnten Chain-Daten beschädigt werden.",
"wiz_level": "Stufe:",
"wiz_lowspec_desc": "Alle aufwendigen visuellen Effekte deaktivieren",
"wiz_off": "Aus",
"wiz_pass_mismatch": "Passphrasen stimmen nicht überein",
"wiz_pass_spaces": "Passphrase hat führende/nachfolgende Leerzeichen — entfernen Sie diese",
"wiz_pass_too_short": "Passphrase muss mindestens 8 Zeichen lang sein (%zu/8)",
"wiz_passphrase": "Passphrase:",
"wiz_pin_confirm": "PIN bestätigen:",
"wiz_pin_invalid": "PIN muss 4-8 Ziffern haben",
"wiz_pin_label": "PIN (4-8 Ziffern):",
"wiz_pin_mismatch": "PINs stimmen nicht überein",
"wiz_pin_optional": "Schnellentsperr-PIN (optional)",
"wiz_pin_title": "Schnellentsperr-PIN",
"wiz_scanline_desc": "CRT-Scanline-Effekt in der Konsole",
"wiz_skip": "Überspringen",
"wiz_skip_confirm": "OHNE Verschlüsselung fortfahren? Schlüssel werden unverschlüsselt gespeichert — klicken Sie erneut auf Überspringen, um zu bestätigen.",
"wiz_step1": "Schritt 1",
"wiz_step2": "Schritt 2",
"wiz_step3": "Schritt 3",
"wiz_stop_daemon": "Daemon stoppen",
"wiz_strength": "Stärke: %s",
"wiz_strength_fair": "Mäßig",
"wiz_strength_good": "Gut",
"wiz_strength_strong": "Stark",
"wiz_strength_weak": "Schwach",
"wiz_theme_builtin": "Integriert",
"wiz_theme_custom": "Benutzerdefiniert",
"wiz_theme_effects": "Visuelle Theme-Effekte",
"wiz_theme_effects_desc": "Animierte Ränder, Farbverlauf",
"wiz_theme_invalid": " (ungültig)",
"wiz_ui_opacity_desc": "Transparenz von Karten & Seitenleiste (1.0 = deckend)",
"wiz_welcome_sub": "Ein paar schnelle Einstellungen und Ihr Full Node ist bereit.",
"wiz_welcome_title": "Willkommen bei ObsidianDragon!",
"wizard_daemon_start_failed": "Daemon-Start fehlgeschlagen — wird automatisch wiederholt",
"xmrig_browse_releases": "Alle Releases durchsuchen…",
"xmrig_checking": "Nach dem neuesten Miner wird gesucht…",

View File

@@ -1369,6 +1369,61 @@
"warning_upper": "¡ADVERTENCIA!",
"website": "Sitio Web",
"window_opacity": "Opacidad de ventana",
"wiz_acrylic": "Efectos de vidrio acrílico",
"wiz_acrylic_desc": "Desenfoque translúcido en paneles (Desactivado lo apaga)",
"wiz_already_encrypted": "La cartera ya está cifrada",
"wiz_already_encrypted_desc": "Tu cartera está protegida con una frase de contraseña. No es necesaria ninguna acción adicional.",
"wiz_appearance": "Apariencia",
"wiz_bootstrap": "Bootstrap",
"wiz_bootstrap_desc": "Descarga un bootstrap de la cadena de bloques para acelerar drásticamente la sincronización inicial.\\n\\nTu wallet.dat existente NO se modificará ni se reemplazará.",
"wiz_bootstrap_failed": "Error en el bootstrap",
"wiz_confirm": "Confirmar:",
"wiz_continue": "Continuar",
"wiz_daemon_sending_stop": "Enviando comando de detención...",
"wiz_daemon_stop_failed": "El daemon no se detuvo — inténtalo manualmente.",
"wiz_daemon_stopped_ok": "Daemon detenido.",
"wiz_daemon_waiting_stop": "Esperando a que el daemon se cierre...",
"wiz_download_failed": "Error de descarga",
"wiz_encrypt_bg": "El cifrado se completará en segundo plano",
"wiz_encrypt_continue": "Cifrar y continuar",
"wiz_encrypt_desc": "Cifra tu cartera para proteger las claves privadas con una frase de contraseña.",
"wiz_encrypt_warning": "Si pierdes tu frase de contraseña, pierdes el acceso a tus fondos.",
"wiz_encryption": "Cifrado",
"wiz_ext_daemon_running": "Daemon externo en ejecución",
"wiz_ext_daemon_warning": "Debe detenerse antes de descargar un bootstrap; de lo contrario, los datos de la cadena podrían corromperse.",
"wiz_level": "Nivel:",
"wiz_lowspec_desc": "Desactivar todos los efectos visuales pesados",
"wiz_off": "Desactivado",
"wiz_pass_mismatch": "Las frases de contraseña no coinciden",
"wiz_pass_spaces": "La frase de contraseña tiene espacios al inicio o al final — elimínalos",
"wiz_pass_too_short": "La frase de contraseña debe tener al menos 8 caracteres (%zu/8)",
"wiz_passphrase": "Frase de contraseña:",
"wiz_pin_confirm": "Confirmar PIN:",
"wiz_pin_invalid": "El PIN debe tener entre 4 y 8 dígitos",
"wiz_pin_label": "PIN (4-8 dígitos):",
"wiz_pin_mismatch": "Los PIN no coinciden",
"wiz_pin_optional": "PIN de desbloqueo rápido (opcional)",
"wiz_pin_title": "PIN de desbloqueo rápido",
"wiz_scanline_desc": "Efecto de líneas de barrido CRT en la consola",
"wiz_skip": "Omitir",
"wiz_skip_confirm": "¿Continuar SIN cifrado? Las claves se almacenarán sin cifrar — haz clic en Omitir de nuevo para confirmar.",
"wiz_step1": "Paso 1",
"wiz_step2": "Paso 2",
"wiz_step3": "Paso 3",
"wiz_stop_daemon": "Detener daemon",
"wiz_strength": "Seguridad: %s",
"wiz_strength_fair": "Aceptable",
"wiz_strength_good": "Buena",
"wiz_strength_strong": "Fuerte",
"wiz_strength_weak": "Débil",
"wiz_theme_builtin": "Integrado",
"wiz_theme_custom": "Personalizado",
"wiz_theme_effects": "Efectos visuales del tema",
"wiz_theme_effects_desc": "Bordes animados, capa de color",
"wiz_theme_invalid": " (no válido)",
"wiz_ui_opacity_desc": "Transparencia de tarjetas y barra lateral (1.0 = sólido)",
"wiz_welcome_sub": "Unas cuantas opciones rápidas y tu nodo completo estará listo.",
"wiz_welcome_title": "¡Bienvenido a ObsidianDragon!",
"wizard_daemon_start_failed": "Error al iniciar el daemon — se reintentará automáticamente",
"xmrig_browse_releases": "Ver todos los lanzamientos…",
"xmrig_checking": "Buscando el minero más reciente…",

View File

@@ -1369,6 +1369,61 @@
"warning_upper": "ATTENTION !",
"website": "Site web",
"window_opacity": "Opacité de la fenêtre",
"wiz_acrylic": "Effets de verre acrylique",
"wiz_acrylic_desc": "Flou translucide sur les panneaux (Désactivé les coupe)",
"wiz_already_encrypted": "Le portefeuille est déjà chiffré",
"wiz_already_encrypted_desc": "Votre portefeuille est protégé par une phrase secrète. Aucune autre action n'est nécessaire.",
"wiz_appearance": "Apparence",
"wiz_bootstrap": "Amorçage",
"wiz_bootstrap_desc": "Téléchargez un amorçage de la blockchain pour accélérer considérablement la synchronisation initiale.\n\nVotre fichier wallet.dat existant NE sera PAS modifié ni remplacé.",
"wiz_bootstrap_failed": "Échec de l'amorçage",
"wiz_confirm": "Confirmer :",
"wiz_continue": "Continuer",
"wiz_daemon_sending_stop": "Envoi de la commande d'arrêt...",
"wiz_daemon_stop_failed": "Le démon ne s'est pas arrêté — essayez manuellement.",
"wiz_daemon_stopped_ok": "Démon arrêté.",
"wiz_daemon_waiting_stop": "En attente de l'arrêt du démon...",
"wiz_download_failed": "Échec du téléchargement",
"wiz_encrypt_bg": "Le chiffrement se terminera en arrière-plan",
"wiz_encrypt_continue": "Chiffrer et continuer",
"wiz_encrypt_desc": "Chiffrez votre portefeuille pour protéger les clés privées avec une phrase secrète.",
"wiz_encrypt_warning": "Si vous perdez votre phrase secrète, vous perdez l'accès à vos fonds.",
"wiz_encryption": "Chiffrement",
"wiz_ext_daemon_running": "Démon externe en cours d'exécution",
"wiz_ext_daemon_warning": "Il doit être arrêté avant de télécharger un amorçage, sinon les données de la chaîne pourraient être corrompues.",
"wiz_level": "Niveau :",
"wiz_lowspec_desc": "Désactiver tous les effets visuels lourds",
"wiz_off": "Désactivé",
"wiz_pass_mismatch": "Les phrases secrètes ne correspondent pas",
"wiz_pass_spaces": "La phrase secrète comporte des espaces en début/fin — supprimez-les",
"wiz_pass_too_short": "La phrase secrète doit comporter au moins 8 caractères (%zu/8)",
"wiz_passphrase": "Phrase secrète :",
"wiz_pin_confirm": "Confirmer le PIN :",
"wiz_pin_invalid": "Le PIN doit comporter 4 à 8 chiffres",
"wiz_pin_label": "PIN (4 à 8 chiffres) :",
"wiz_pin_mismatch": "Les PIN ne correspondent pas",
"wiz_pin_optional": "PIN de déverrouillage rapide (facultatif)",
"wiz_pin_title": "PIN de déverrouillage rapide",
"wiz_scanline_desc": "Effet de lignes de balayage CRT dans la console",
"wiz_skip": "Ignorer",
"wiz_skip_confirm": "Continuer SANS chiffrement ? Les clés seront stockées non chiffrées — cliquez à nouveau sur Ignorer pour confirmer.",
"wiz_step1": "Étape 1",
"wiz_step2": "Étape 2",
"wiz_step3": "Étape 3",
"wiz_stop_daemon": "Arrêter le démon",
"wiz_strength": "Robustesse : %s",
"wiz_strength_fair": "Moyenne",
"wiz_strength_good": "Bonne",
"wiz_strength_strong": "Forte",
"wiz_strength_weak": "Faible",
"wiz_theme_builtin": "Intégré",
"wiz_theme_custom": "Personnalisé",
"wiz_theme_effects": "Effets visuels du thème",
"wiz_theme_effects_desc": "Bordures animées, voile de couleur",
"wiz_theme_invalid": " (non valide)",
"wiz_ui_opacity_desc": "Transparence des cartes et de la barre latérale (1.0 = opaque)",
"wiz_welcome_sub": "Quelques choix rapides et votre nœud complet est prêt.",
"wiz_welcome_title": "Bienvenue dans ObsidianDragon !",
"wizard_daemon_start_failed": "Échec du démarrage du daemon — sera réessayé automatiquement",
"xmrig_browse_releases": "Parcourir toutes les versions…",
"xmrig_checking": "Recherche du dernier mineur…",

View File

@@ -1369,6 +1369,61 @@
"warning_upper": "警告!",
"website": "ウェブサイト",
"window_opacity": "ウィンドウ透明度",
"wiz_acrylic": "アクリルガラス効果",
"wiz_acrylic_desc": "パネルの半透明ぼかし(オフで無効化)",
"wiz_already_encrypted": "ウォレットはすでに暗号化されています",
"wiz_already_encrypted_desc": "ウォレットはパスフレーズで保護されています。これ以上の操作は不要です。",
"wiz_appearance": "外観",
"wiz_bootstrap": "ブートストラップ",
"wiz_bootstrap_desc": "ブロックチェーンのブートストラップをダウンロードして初回同期を大幅に高速化します。\\n\\n既存の wallet.dat は変更も置き換えもされません。",
"wiz_bootstrap_failed": "ブートストラップに失敗しました",
"wiz_confirm": "確認:",
"wiz_continue": "続行",
"wiz_daemon_sending_stop": "停止コマンドを送信中...",
"wiz_daemon_stop_failed": "デーモンが停止しませんでした — 手動でお試しください。",
"wiz_daemon_stopped_ok": "デーモンを停止しました。",
"wiz_daemon_waiting_stop": "デーモンの終了を待機中...",
"wiz_download_failed": "ダウンロードに失敗しました",
"wiz_encrypt_bg": "暗号化はバックグラウンドで完了します",
"wiz_encrypt_continue": "暗号化して続行",
"wiz_encrypt_desc": "ウォレットを暗号化して、パスフレーズで秘密鍵を保護します。",
"wiz_encrypt_warning": "パスフレーズを紛失すると、資金にアクセスできなくなります。",
"wiz_encryption": "暗号化",
"wiz_ext_daemon_running": "外部デーモンが実行中です",
"wiz_ext_daemon_warning": "ブートストラップをダウンロードする前に停止する必要があります。停止しないとチェーンデータが破損する可能性があります。",
"wiz_level": "レベル:",
"wiz_lowspec_desc": "重い視覚効果をすべて無効にする",
"wiz_off": "オフ",
"wiz_pass_mismatch": "パスフレーズが一致しません",
"wiz_pass_spaces": "パスフレーズの先頭または末尾に空白があります — 削除してください",
"wiz_pass_too_short": "パスフレーズは 8 文字以上にしてください(%zu/8",
"wiz_passphrase": "パスフレーズ:",
"wiz_pin_confirm": "PIN の確認:",
"wiz_pin_invalid": "PIN は 4〜8 桁にしてください",
"wiz_pin_label": "PIN4〜8 桁):",
"wiz_pin_mismatch": "PIN が一致しません",
"wiz_pin_optional": "クイックロック解除 PIN任意",
"wiz_pin_title": "クイックロック解除 PIN",
"wiz_scanline_desc": "コンソールの CRT 走査線効果",
"wiz_skip": "スキップ",
"wiz_skip_confirm": "暗号化せずに続行しますか? 鍵は暗号化されずに保存されます — もう一度スキップをクリックして確認してください。",
"wiz_step1": "ステップ 1",
"wiz_step2": "ステップ 2",
"wiz_step3": "ステップ 3",
"wiz_stop_daemon": "デーモンを停止",
"wiz_strength": "強度:%s",
"wiz_strength_fair": "まずまず",
"wiz_strength_good": "良い",
"wiz_strength_strong": "強い",
"wiz_strength_weak": "弱い",
"wiz_theme_builtin": "内蔵",
"wiz_theme_custom": "カスタム",
"wiz_theme_effects": "テーマの視覚効果",
"wiz_theme_effects_desc": "枠のアニメーション、カラーウォッシュ",
"wiz_theme_invalid": " (無効)",
"wiz_ui_opacity_desc": "カードとサイドバーの透明度1.0 = 不透明)",
"wiz_welcome_sub": "いくつか選ぶだけでフルノードの準備が整います。",
"wiz_welcome_title": "ObsidianDragon へようこそ!",
"wizard_daemon_start_failed": "デーモンの起動に失敗しました — 自動的に再試行されます",
"xmrig_browse_releases": "すべてのリリースを表示…",
"xmrig_checking": "最新のマイナーを確認中…",

View File

@@ -1369,6 +1369,61 @@
"warning_upper": "경고!",
"website": "웹사이트",
"window_opacity": "창 투명도",
"wiz_acrylic": "아크릴 유리 효과",
"wiz_acrylic_desc": "패널에 반투명 블러 적용 (끄면 비활성화)",
"wiz_already_encrypted": "지갑이 이미 암호화되어 있습니다",
"wiz_already_encrypted_desc": "지갑이 암호로 보호되어 있습니다. 추가 작업이 필요하지 않습니다.",
"wiz_appearance": "모양",
"wiz_bootstrap": "부트스트랩",
"wiz_bootstrap_desc": "블록체인 부트스트랩을 다운로드하여 초기 동기화 속도를 크게 높입니다.\\n\\n기존 wallet.dat은 수정되거나 교체되지 않습니다.",
"wiz_bootstrap_failed": "부트스트랩 실패",
"wiz_confirm": "확인:",
"wiz_continue": "계속",
"wiz_daemon_sending_stop": "중지 명령 전송 중...",
"wiz_daemon_stop_failed": "데몬이 중지되지 않았습니다 — 수동으로 시도하세요.",
"wiz_daemon_stopped_ok": "데몬이 중지되었습니다.",
"wiz_daemon_waiting_stop": "데몬 종료를 기다리는 중...",
"wiz_download_failed": "다운로드 실패",
"wiz_encrypt_bg": "암호화는 백그라운드에서 완료됩니다",
"wiz_encrypt_continue": "암호화 후 계속",
"wiz_encrypt_desc": "지갑을 암호화하여 암호로 개인 키를 보호합니다.",
"wiz_encrypt_warning": "암호를 분실하면 자금에 접근할 수 없게 됩니다.",
"wiz_encryption": "암호화",
"wiz_ext_daemon_running": "외부 데몬 실행 중",
"wiz_ext_daemon_warning": "부트스트랩을 다운로드하기 전에 데몬을 중지해야 합니다. 그렇지 않으면 체인 데이터가 손상될 수 있습니다.",
"wiz_level": "수준:",
"wiz_lowspec_desc": "모든 무거운 시각 효과 끄기",
"wiz_off": "끔",
"wiz_pass_mismatch": "암호가 일치하지 않습니다",
"wiz_pass_spaces": "암호 앞뒤에 공백이 있습니다 — 제거하세요",
"wiz_pass_too_short": "암호는 8자 이상이어야 합니다 (%zu/8)",
"wiz_passphrase": "암호:",
"wiz_pin_confirm": "PIN 확인:",
"wiz_pin_invalid": "PIN은 4-8자리여야 합니다",
"wiz_pin_label": "PIN (4-8자리):",
"wiz_pin_mismatch": "PIN이 일치하지 않습니다",
"wiz_pin_optional": "빠른 잠금 해제 PIN (선택 사항)",
"wiz_pin_title": "빠른 잠금 해제 PIN",
"wiz_scanline_desc": "콘솔의 CRT 스캔라인 효과",
"wiz_skip": "건너뛰기",
"wiz_skip_confirm": "암호화 없이 계속하시겠습니까? 키가 암호화되지 않은 채로 저장됩니다 — 확인하려면 건너뛰기를 다시 클릭하세요.",
"wiz_step1": "1단계",
"wiz_step2": "2단계",
"wiz_step3": "3단계",
"wiz_stop_daemon": "데몬 중지",
"wiz_strength": "강도: %s",
"wiz_strength_fair": "보통",
"wiz_strength_good": "양호",
"wiz_strength_strong": "강함",
"wiz_strength_weak": "약함",
"wiz_theme_builtin": "기본 제공",
"wiz_theme_custom": "사용자 지정",
"wiz_theme_effects": "테마 시각 효과",
"wiz_theme_effects_desc": "애니메이션 테두리, 컬러 워시",
"wiz_theme_invalid": " (잘못됨)",
"wiz_ui_opacity_desc": "카드 및 사이드바 투명도 (1.0 = 불투명)",
"wiz_welcome_sub": "몇 가지만 선택하면 풀 노드가 준비됩니다.",
"wiz_welcome_title": "ObsidianDragon에 오신 것을 환영합니다!",
"wizard_daemon_start_failed": "데몬 시작 실패 — 자동으로 재시도됩니다",
"xmrig_browse_releases": "모든 릴리스 보기…",
"xmrig_checking": "최신 채굴기를 확인하는 중…",

View File

@@ -1369,6 +1369,61 @@
"warning_upper": "AVISO!",
"website": "Website",
"window_opacity": "Opacidade da Janela",
"wiz_acrylic": "Efeitos de vidro acrílico",
"wiz_acrylic_desc": "Desfoque translúcido nos painéis (Desativado remove)",
"wiz_already_encrypted": "A carteira já está encriptada",
"wiz_already_encrypted_desc": "A sua carteira está protegida com uma frase-passe. Não é necessária nenhuma ação adicional.",
"wiz_appearance": "Aparência",
"wiz_bootstrap": "Bootstrap",
"wiz_bootstrap_desc": "Baixe um bootstrap da blockchain para acelerar drasticamente a sincronização inicial.\\n\\nO seu wallet.dat existente NÃO será modificado nem substituído.",
"wiz_bootstrap_failed": "Falha no bootstrap",
"wiz_confirm": "Confirmar:",
"wiz_continue": "Continuar",
"wiz_daemon_sending_stop": "A enviar comando de parada...",
"wiz_daemon_stop_failed": "O daemon não parou — tente manualmente.",
"wiz_daemon_stopped_ok": "Daemon parado.",
"wiz_daemon_waiting_stop": "A aguardar o encerramento do daemon...",
"wiz_download_failed": "Falha no download",
"wiz_encrypt_bg": "A encriptação será concluída em segundo plano",
"wiz_encrypt_continue": "Encriptar e continuar",
"wiz_encrypt_desc": "Encripte a sua carteira para proteger as chaves privadas com uma frase-passe.",
"wiz_encrypt_warning": "Se perder a sua frase-passe, perde o acesso aos seus fundos.",
"wiz_encryption": "Encriptação",
"wiz_ext_daemon_running": "Daemon externo em execução",
"wiz_ext_daemon_warning": "Deve ser parado antes de baixar um bootstrap, caso contrário os dados da cadeia podem ficar corrompidos.",
"wiz_level": "Nível:",
"wiz_lowspec_desc": "Desativar todos os efeitos visuais pesados",
"wiz_off": "Desativado",
"wiz_pass_mismatch": "As frases-passe não coincidem",
"wiz_pass_spaces": "A frase-passe tem espaços no início/fim — remova-os",
"wiz_pass_too_short": "A frase-passe deve ter pelo menos 8 caracteres (%zu/8)",
"wiz_passphrase": "Frase-passe:",
"wiz_pin_confirm": "Confirmar PIN:",
"wiz_pin_invalid": "O PIN deve ter 4-8 dígitos",
"wiz_pin_label": "PIN (4-8 dígitos):",
"wiz_pin_mismatch": "Os PINs não coincidem",
"wiz_pin_optional": "PIN de desbloqueio rápido (opcional)",
"wiz_pin_title": "PIN de desbloqueio rápido",
"wiz_scanline_desc": "Efeito de scanline CRT na consola",
"wiz_skip": "Ignorar",
"wiz_skip_confirm": "Continuar SEM encriptação? As chaves serão armazenadas sem encriptação — clique em Ignorar novamente para confirmar.",
"wiz_step1": "Passo 1",
"wiz_step2": "Passo 2",
"wiz_step3": "Passo 3",
"wiz_stop_daemon": "Parar daemon",
"wiz_strength": "Força: %s",
"wiz_strength_fair": "Razoável",
"wiz_strength_good": "Boa",
"wiz_strength_strong": "Forte",
"wiz_strength_weak": "Fraca",
"wiz_theme_builtin": "Integrado",
"wiz_theme_custom": "Personalizado",
"wiz_theme_effects": "Efeitos visuais do tema",
"wiz_theme_effects_desc": "Bordas animadas, matiz de cor",
"wiz_theme_invalid": " (inválido)",
"wiz_ui_opacity_desc": "Transparência de cartões e barra lateral (1.0 = sólido)",
"wiz_welcome_sub": "Algumas escolhas rápidas e o seu full node estará pronto.",
"wiz_welcome_title": "Bem-vindo ao ObsidianDragon!",
"wizard_daemon_start_failed": "Falha ao iniciar o daemon — será tentado novamente automaticamente",
"xmrig_browse_releases": "Navegar por todos os lançamentos…",
"xmrig_checking": "Verificando o minerador mais recente…",

View File

@@ -1369,6 +1369,61 @@
"warning_upper": "ПРЕДУПРЕЖДЕНИЕ!",
"website": "Веб-сайт",
"window_opacity": "Прозрачность окна",
"wiz_acrylic": "Эффект акрилового стекла",
"wiz_acrylic_desc": "Полупрозрачное размытие панелей (Выкл. отключает)",
"wiz_already_encrypted": "Кошелёк уже зашифрован",
"wiz_already_encrypted_desc": "Ваш кошелёк защищён парольной фразой. Дополнительных действий не требуется.",
"wiz_appearance": "Внешний вид",
"wiz_bootstrap": "Начальная загрузка",
"wiz_bootstrap_desc": "Загрузите начальный образ блокчейна, чтобы значительно ускорить первичную синхронизацию.\\n\\nВаш существующий файл wallet.dat НЕ будет изменён или заменён.",
"wiz_bootstrap_failed": "Ошибка начальной загрузки",
"wiz_confirm": "Подтверждение:",
"wiz_continue": "Продолжить",
"wiz_daemon_sending_stop": "Отправка команды остановки...",
"wiz_daemon_stop_failed": "Демон не остановился — попробуйте вручную.",
"wiz_daemon_stopped_ok": "Демон остановлен.",
"wiz_daemon_waiting_stop": "Ожидание завершения работы демона...",
"wiz_download_failed": "Ошибка загрузки",
"wiz_encrypt_bg": "Шифрование завершится в фоновом режиме",
"wiz_encrypt_continue": "Зашифровать и продолжить",
"wiz_encrypt_desc": "Зашифруйте кошелёк, чтобы защитить приватные ключи парольной фразой.",
"wiz_encrypt_warning": "Если вы потеряете парольную фразу, вы потеряете доступ к своим средствам.",
"wiz_encryption": "Шифрование",
"wiz_ext_daemon_running": "Запущен внешний демон",
"wiz_ext_daemon_warning": "Его необходимо остановить перед загрузкой начального образа, иначе данные цепочки могут быть повреждены.",
"wiz_level": "Уровень:",
"wiz_lowspec_desc": "Отключить все тяжёлые визуальные эффекты",
"wiz_off": "Выкл.",
"wiz_pass_mismatch": "Парольные фразы не совпадают",
"wiz_pass_spaces": "В парольной фразе есть пробелы в начале или конце — удалите их",
"wiz_pass_too_short": "Парольная фраза должна содержать не менее 8 символов (%zu/8)",
"wiz_passphrase": "Парольная фраза:",
"wiz_pin_confirm": "Подтвердите PIN:",
"wiz_pin_invalid": "PIN должен содержать 48 цифр",
"wiz_pin_label": "PIN (48 цифр):",
"wiz_pin_mismatch": "PIN-коды не совпадают",
"wiz_pin_optional": "PIN быстрой разблокировки (необязательно)",
"wiz_pin_title": "PIN быстрой разблокировки",
"wiz_scanline_desc": "Эффект строк развёртки CRT в консоли",
"wiz_skip": "Пропустить",
"wiz_skip_confirm": "Продолжить БЕЗ шифрования? Ключи будут храниться незашифрованными — нажмите «Пропустить» ещё раз для подтверждения.",
"wiz_step1": "Шаг 1",
"wiz_step2": "Шаг 2",
"wiz_step3": "Шаг 3",
"wiz_stop_daemon": "Остановить демон",
"wiz_strength": "Надёжность: %s",
"wiz_strength_fair": "Средняя",
"wiz_strength_good": "Хорошая",
"wiz_strength_strong": "Надёжная",
"wiz_strength_weak": "Слабая",
"wiz_theme_builtin": "Встроенная",
"wiz_theme_custom": "Своя",
"wiz_theme_effects": "Визуальные эффекты темы",
"wiz_theme_effects_desc": "Анимированные рамки, цветовая заливка",
"wiz_theme_invalid": " (недопустимая)",
"wiz_ui_opacity_desc": "Прозрачность карточек и боковой панели (1.0 = непрозрачно)",
"wiz_welcome_sub": "Несколько быстрых настроек — и ваш полный узел готов.",
"wiz_welcome_title": "Добро пожаловать в ObsidianDragon!",
"wizard_daemon_start_failed": "Не удалось запустить демон — будет повторено автоматически",
"xmrig_browse_releases": "Просмотреть все релизы…",
"xmrig_checking": "Проверка наличия последней версии майнера…",

View File

@@ -1369,6 +1369,61 @@
"warning_upper": "警告!",
"website": "网站",
"window_opacity": "窗口透明度",
"wiz_acrylic": "亚克力玻璃效果",
"wiz_acrylic_desc": "面板上的半透明模糊(关闭则禁用)",
"wiz_already_encrypted": "钱包已加密",
"wiz_already_encrypted_desc": "您的钱包已使用密码短语进行保护。无需进一步操作。",
"wiz_appearance": "外观",
"wiz_bootstrap": "引导数据",
"wiz_bootstrap_desc": "下载区块链引导数据以大幅加快初始同步速度。\n\n您现有的 wallet.dat 不会被修改或替换。",
"wiz_bootstrap_failed": "引导数据失败",
"wiz_confirm": "确认:",
"wiz_continue": "继续",
"wiz_daemon_sending_stop": "正在发送停止命令…",
"wiz_daemon_stop_failed": "守护进程未停止——请尝试手动停止。",
"wiz_daemon_stopped_ok": "守护进程已停止。",
"wiz_daemon_waiting_stop": "正在等待守护进程关闭…",
"wiz_download_failed": "下载失败",
"wiz_encrypt_bg": "加密将在后台完成",
"wiz_encrypt_continue": "加密并继续",
"wiz_encrypt_desc": "加密您的钱包,用密码短语保护私钥。",
"wiz_encrypt_warning": "如果您丢失密码短语,将无法访问您的资金。",
"wiz_encryption": "加密",
"wiz_ext_daemon_running": "外部守护进程正在运行",
"wiz_ext_daemon_warning": "下载引导数据前必须先停止它,否则链数据可能会损坏。",
"wiz_level": "级别:",
"wiz_lowspec_desc": "禁用所有重度视觉效果",
"wiz_off": "关闭",
"wiz_pass_mismatch": "密码短语不匹配",
"wiz_pass_spaces": "密码短语含有首尾空格——请将其删除",
"wiz_pass_too_short": "密码短语至少需要 8 个字符 (%zu/8)",
"wiz_passphrase": "密码短语:",
"wiz_pin_confirm": "确认 PIN",
"wiz_pin_invalid": "PIN 必须为 4-8 位数字",
"wiz_pin_label": "PIN4-8 位数字):",
"wiz_pin_mismatch": "PIN 不匹配",
"wiz_pin_optional": "快速解锁 PIN可选",
"wiz_pin_title": "快速解锁 PIN",
"wiz_scanline_desc": "控制台中的 CRT 扫描线效果",
"wiz_skip": "跳过",
"wiz_skip_confirm": "确定不加密继续吗?私钥将以未加密方式存储——请再次点击“跳过”以确认。",
"wiz_step1": "第 1 步",
"wiz_step2": "第 2 步",
"wiz_step3": "第 3 步",
"wiz_stop_daemon": "停止守护进程",
"wiz_strength": "强度:%s",
"wiz_strength_fair": "一般",
"wiz_strength_good": "良好",
"wiz_strength_strong": "强",
"wiz_strength_weak": "弱",
"wiz_theme_builtin": "内置",
"wiz_theme_custom": "自定义",
"wiz_theme_effects": "主题视觉效果",
"wiz_theme_effects_desc": "动态边框、色彩渲染",
"wiz_theme_invalid": " (无效)",
"wiz_ui_opacity_desc": "卡片与侧边栏透明度1.0 = 不透明)",
"wiz_welcome_sub": "只需几项快速设置,即可启用您的全节点。",
"wiz_welcome_title": "欢迎使用 ObsidianDragon",
"wizard_daemon_start_failed": "启动守护进程失败 — 将自动重试",
"xmrig_browse_releases": "浏览所有版本…",
"xmrig_checking": "正在检查最新的矿工程序…",

View File

@@ -2471,7 +2471,7 @@ void App::renderLiteFirstRunPrompt()
ImGui::PopFont();
ImGui::Spacing();
ImGui::PushTextWrapPos(ImGui::GetCursorPosX() + 380.0f);
ImGui::PushStyleColor(ImGuiCol_Text, ui::material::Error());
ImGui::PushStyleColor(ImGuiCol_Text, ui::material::ReadableError());
ImGui::TextUnformatted("These 24 words are the ONLY way to restore your wallet. "
"Write them down in order, store them offline, and never share "
"them. If you lose them, your funds are gone forever.");
@@ -2517,7 +2517,7 @@ void App::renderLiteFirstRunPrompt()
if (skipConfirm) {
ImGui::Spacing();
ImGui::PushTextWrapPos(ImGui::GetCursorPosX() + 380.0f);
ImGui::PushStyleColor(ImGuiCol_Text, ui::material::Error());
ImGui::PushStyleColor(ImGuiCol_Text, ui::material::ReadableError());
ImGui::TextUnformatted("You have not backed up your seed — funds could be lost. "
"Skip anyway?");
ImGui::PopStyleColor();
@@ -2537,7 +2537,7 @@ void App::renderLiteFirstRunPrompt()
ImGui::TextDisabled("Progress: %d / %d", progress, (int)words.size());
if (ImGui::GetTime() < wrongFlashUntil) {
ImGui::SameLine();
ImGui::PushStyleColor(ImGuiCol_Text, ui::material::Error());
ImGui::PushStyleColor(ImGuiCol_Text, ui::material::ReadableError());
ImGui::TextUnformatted(" — that's not the next word");
ImGui::PopStyleColor();
}
@@ -2583,7 +2583,7 @@ void App::renderLiteFirstRunPrompt()
if (skipConfirm) {
ImGui::Spacing();
ImGui::PushTextWrapPos(ImGui::GetCursorPosX() + 380.0f);
ImGui::PushStyleColor(ImGuiCol_Text, ui::material::Error());
ImGui::PushStyleColor(ImGuiCol_Text, ui::material::ReadableError());
ImGui::TextUnformatted("You have not backed up your seed — funds could be lost. "
"Skip anyway?");
ImGui::PopStyleColor();
@@ -2623,7 +2623,7 @@ void App::renderLiteFirstRunPrompt()
if (!restoreErr.empty()) {
ImGui::Spacing();
ImGui::PushStyleColor(ImGuiCol_Text, ui::material::Error());
ImGui::PushStyleColor(ImGuiCol_Text, ui::material::ReadableError());
ImGui::PushTextWrapPos(ImGui::GetCursorPosX() + 380.0f);
ImGui::TextUnformatted(restoreErr.c_str());
ImGui::PopTextWrapPos();
@@ -2788,10 +2788,10 @@ void App::renderImportKeyDialog()
if (!keyTrim.empty()) {
if (keyRecognized) {
ImGui::PushFont(ui::material::Type().iconSmall());
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), ICON_MD_CHECK_CIRCLE);
ImGui::TextColored(ui::material::SuccessVec4(), ICON_MD_CHECK_CIRCLE);
ImGui::PopFont();
ImGui::SameLine(0, 4.0f);
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "%s",
ImGui::TextColored(ui::material::SuccessVec4(), "%s",
keyIsZ ? "Shielded spending key" : "Transparent private key");
} else {
ImGui::PushFont(ui::material::Type().iconSmall());
@@ -2806,7 +2806,7 @@ void App::renderImportKeyDialog()
if (!import_status_.empty()) {
if (import_success_) {
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "%s", import_status_.c_str());
ImGui::TextColored(ui::material::SuccessVec4(), "%s", import_status_.c_str());
} else {
ImGui::TextColored(ImVec4(0.8f, 0.3f, 0.3f, 1.0f), "%s", import_status_.c_str());
}
@@ -2936,7 +2936,7 @@ void App::renderBackupDialog()
if (!backup_status_.empty()) {
if (backup_success_) {
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "%s", backup_status_.c_str());
ImGui::TextColored(ui::material::SuccessVec4(), "%s", backup_status_.c_str());
} else {
ImGui::TextColored(ImVec4(0.8f, 0.3f, 0.3f, 1.0f), "%s", backup_status_.c_str());
}
@@ -3080,14 +3080,14 @@ void App::renderSeedBackupDialog()
} else if (!seed_backup_phrase_.empty()) {
ImGui::TextWrapped("%s", TR("seed_backup_intro"));
ImGui::Spacing();
ImGui::PushStyleColor(ImGuiCol_Text, ui::material::Error());
ImGui::PushStyleColor(ImGuiCol_Text, ui::material::ReadableError());
ImGui::TextWrapped("%s", TR("seed_backup_warning"));
ImGui::PopStyleColor();
ImGui::Spacing();
ui::RenderSeedWordGrid(ui::SplitSeedWords(seed_backup_phrase_));
ImGui::Spacing();
if (!seed_backup_status_.empty()) {
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "%s", seed_backup_status_.c_str());
ImGui::TextColored(ui::material::SuccessVec4(), "%s", seed_backup_status_.c_str());
ImGui::Spacing();
}
ImGui::Separator();
@@ -3176,7 +3176,7 @@ void App::renderSeedMigrationDialog()
ImGui::TextColored(kMedium, "%s%s", TR("mig_precheck_checking"), ui::material::LoadingDots());
break;
case SeedMigrationPrecheck::AlreadyMnemonic:
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.3f, 0.8f, 0.3f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_Text, ui::material::SuccessVec4());
ImGui::TextWrapped("%s", TR("mig_already_mnemonic"));
ImGui::PopStyleColor();
ImGui::Spacing(); ImGui::Separator();
@@ -3188,7 +3188,7 @@ void App::renderSeedMigrationDialog()
if (ui::material::StyledButton(TR("close"), ImVec2(120 * dp, 0))) close();
break;
case SeedMigrationPrecheck::DaemonTooOld:
ImGui::PushStyleColor(ImGuiCol_Text, ui::material::Error());
ImGui::PushStyleColor(ImGuiCol_Text, ui::material::ReadableError());
ImGui::TextWrapped("%s", TR("mig_daemon_too_old"));
ImGui::PopStyleColor();
ImGui::Spacing(); ImGui::Separator();
@@ -3223,7 +3223,7 @@ void App::renderSeedMigrationDialog()
break;
case SeedMigrationStep::ShowSeed: {
ImGui::PushStyleColor(ImGuiCol_Text, ui::material::Error());
ImGui::PushStyleColor(ImGuiCol_Text, ui::material::ReadableError());
ImGui::TextWrapped("%s", TR("mig_seed_warning"));
ImGui::PopStyleColor();
ImGui::Spacing();
@@ -3243,7 +3243,7 @@ void App::renderSeedMigrationDialog()
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::TextColored(ui::material::SuccessVec4(), "%s", seed_migration_status_.c_str());
}
ImGui::Spacing();
ImGui::Checkbox(TR("mig_backed_up"), &seed_migration_backed_up_);
@@ -3320,7 +3320,7 @@ void App::renderSeedMigrationDialog()
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::PushStyleColor(ImGuiCol_Text, ui::material::ReadableError());
ImGui::TextWrapped("%s", TR("mig_unlock_first"));
ImGui::PopStyleColor();
}
@@ -3369,7 +3369,7 @@ void App::renderSeedMigrationDialog()
if (ui::material::StyledButton(TR("mig_adopt"), ImVec2(180 * dp, 0)))
beginAdoptSeedWallet();
} else if (confirmed && !legacyEmpty) {
ImGui::PushStyleColor(ImGuiCol_Text, ui::material::Error());
ImGui::PushStyleColor(ImGuiCol_Text, ui::material::ReadableError());
ImGui::TextWrapped("%s", TR("mig_remainder"));
ImGui::PopStyleColor();
if (ui::material::StyledButton(TR("mig_sweep_remaining"), ImVec2(180 * dp, 0)))
@@ -3397,14 +3397,14 @@ void App::renderSeedMigrationDialog()
break;
case SeedMigrationStep::Done:
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.3f, 0.8f, 0.3f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_Text, ui::material::SuccessVec4());
ImGui::TextWrapped("%s", TR("mig_done"));
ImGui::PopStyleColor();
ImGui::Spacing();
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());
ImGui::PushStyleColor(ImGuiCol_Text, ui::material::ReadableError());
ImGui::TextWrapped("%s", seed_migration_status_.c_str());
ImGui::PopStyleColor();
}
@@ -3415,7 +3415,7 @@ void App::renderSeedMigrationDialog()
break;
case SeedMigrationStep::Error:
ImGui::PushStyleColor(ImGuiCol_Text, ui::material::Error());
ImGui::PushStyleColor(ImGuiCol_Text, ui::material::ReadableError());
ImGui::TextWrapped("%s", seed_migration_status_.c_str());
ImGui::PopStyleColor();
ImGui::Spacing();

View File

@@ -121,16 +121,21 @@ void App::renderFirstRunWizard() {
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse;
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0)); // reveal the skin backdrop behind
ImGui::Begin("##FirstRunWizard", nullptr, flags);
ImGui::PopStyleColor();
ImGui::PopStyleVar();
ImDrawList* dl = ImGui::GetWindowDrawList();
ImVec2 winPos = ImGui::GetWindowPos();
ImVec2 winSize = ImGui::GetWindowSize();
// Background fill
ImU32 bgCol = ui::material::Surface();
dl->AddRectFilled(winPos, ImVec2(winPos.x + winSize.x, winPos.y + winSize.y), bgCol);
// The app's skin backdrop (marble / gradient / acrylic) is already painted behind every window by
// drawWindowBackdrop(); reveal it here instead of a flat Surface() slab, under a gentle theme-tinted
// scrim so the wizard cards and text keep their contrast on busy skins.
ImU32 bgCol = ui::material::Surface(); // still used by the completed / not-reached card overlays
dl->AddRectFilled(winPos, ImVec2(winPos.x + winSize.x, winPos.y + winSize.y),
ui::material::WithAlpha(ui::material::Background(), 120));
// --- Determine which of the 3 masonry sections is focused ---
// 0 = Appearance, 1 = Bootstrap, 2 = Encrypt + PIN
@@ -184,11 +189,18 @@ 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 + 16.0f * dp;
headerCy += wts.y + 6.0f * dp;
// Warmer, less-sparse header: a one-line subtitle under the welcome (dimmed body).
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);
headerCy += sts.y + 16.0f * dp;
}
// --- Masonry: 2 columns ---
@@ -223,11 +235,8 @@ void App::renderFirstRunWizard() {
// Background (channel 0)
dl->ChannelsSetCurrent(0);
if (state == 1) {
// Focused card: subtle drop shadow
float shadowOff = 3.0f * dp;
dl->AddRectFilled(
ImVec2(cMin.x + shadowOff, cMin.y + shadowOff), ImVec2(cMax.x + shadowOff, cMax.y + shadowOff),
IM_COL32(0, 0, 0, 35), cardRound);
// Focused card lifts off the backdrop with the app's uniform card shadow (not a hard offset).
ui::material::DrawCardDropShadow(dl, cMin, cMax, cardRound);
}
// Use DrawGlassPanel for proper acrylic/opacity/noise/theme effects
ui::material::GlassPanelSpec glass;
@@ -237,14 +246,14 @@ void App::renderFirstRunWizard() {
// Overlays & borders (channel 2)
dl->ChannelsSetCurrent(2);
if (state == 1) {
// Focused: accent border
dl->AddRect(cMin, cMax, ui::material::Primary(), cardRound, 0, 2.0f * dp);
// Focused: soft accent ring
dl->AddRect(cMin, cMax, ui::material::Primary(), cardRound, 0, 1.5f * dp);
} else if (state == 2) {
// Completed: dim overlay (preserves color)
dl->AddRectFilled(cMin, cMax, (bgCol & 0x00FFFFFF) | IM_COL32(0, 0, 0, 110), cardRound);
// Completed: a light veil — reads as "done", not disabled.
dl->AddRectFilled(cMin, cMax, (bgCol & 0x00FFFFFF) | IM_COL32(0, 0, 0, 70), cardRound);
} else {
// Not reached: heavy overlay (creates greyscale look)
dl->AddRectFilled(cMin, cMax, (bgCol & 0x00FFFFFF) | IM_COL32(0, 0, 0, 165), cardRound);
// Upcoming: a gentle veil — reads as "waiting", not greyed-out.
dl->AddRectFilled(cMin, cMax, (bgCol & 0x00FFFFFF) | IM_COL32(0, 0, 0, 115), cardRound);
}
dl->ChannelsSetCurrent(1);
@@ -265,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;
}
@@ -327,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;
@@ -350,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;
@@ -358,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 {
@@ -386,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));
@@ -417,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));
@@ -488,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);
@@ -499,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;
@@ -515,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,
@@ -549,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));
@@ -590,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
@@ -611,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
@@ -658,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;
}
@@ -697,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;
@@ -730,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;
}
@@ -746,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);
@@ -763,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();
@@ -790,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,
@@ -812,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>();
@@ -826,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();
@@ -869,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;
@@ -896,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()) {
@@ -910,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;
});
}
@@ -929,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();
@@ -938,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;
@@ -951,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);
@@ -970,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);
@@ -995,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>();
@@ -1014,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();
@@ -1024,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);
@@ -1032,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();
@@ -1084,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;
}
@@ -1108,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;
@@ -1127,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();
@@ -1139,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;
@@ -1148,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);
@@ -1156,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));
@@ -1168,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));
@@ -1183,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;
@@ -1205,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;
}
@@ -1215,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;
}
}
@@ -1234,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));
@@ -1251,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));
@@ -1268,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;
}
}
@@ -1294,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;
}
@@ -1324,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_),
@@ -1347,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();
@@ -1355,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;
@@ -1380,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

@@ -44,6 +44,19 @@ inline bool IsLightTheme() {
return (0.299f * r + 0.587f * g + 0.114f * b) > 0.5f;
}
// Theme error color tuned for use as TEXT. The Material --error reds are muted, so on a dark modal
// backdrop they read low-contrast; lift them toward a legible light red on dark themes. Light themes
// keep the theme error (already a dark red readable on a light background).
inline ImU32 ReadableError() {
ImU32 e = Error();
if (IsLightTheme()) return e;
const float m = 0.35f; // blend toward white
int r = (int)(((e >> IM_COL32_R_SHIFT) & 0xFF) * (1 - m) + 255 * m);
int g = (int)(((e >> IM_COL32_G_SHIFT) & 0xFF) * (1 - m) + 255 * m);
int b = (int)(((e >> IM_COL32_B_SHIFT) & 0xFF) * (1 - m) + 255 * m);
return IM_COL32(r, g, b, (e >> IM_COL32_A_SHIFT) & 0xFF);
}
// Animated "loading" ellipsis: "", ".", "..", "..." cycling on a ~3Hz phase.
inline const char* LoadingDots() {
int n = ((int)(ImGui::GetTime() * 3.0f)) % 4;
@@ -729,10 +742,18 @@ inline void DrawFullWindowBlurBackdrop(ImDrawList* dl, const ImVec2& pMin, const
// panel skips acrylic (IsFullWindowBlurOverlayActive), so the backdrop is the SOLE applyBlur
// caller — no other radius contends for the shared, radius-keyed blur cache.
auto params = GetCurrentAcrylicTheme().card;
params.blurRadius = 64.0f; // strong full-window blur
params.blurRadius = 96.0f; // strong full-window blur (deeper than panels)
params.fallbackColor.w = 1.0f; // full-strength blur so the live content reads
effects::ImGuiAcrylic::DrawAcrylicRect(dl, pMin, pMax, params, 0.0f);
dl->AddRectFilled(pMin, pMax, WithAlpha(Background(), 70)); // subtle theme-tinted frost so the card reads as foreground
// Dim over the blur so the floating (card-less) modal reads as foreground. On DARK themes the
// theme-tinted frost darkens the backdrop and the light content pops. On LIGHT themes that same
// tint whitens the backdrop and leaves card-less content with no figure/ground separation — so
// light themes get a neutral dark veil instead, which also makes the modal's fields/buttons stand
// out from the (now slightly greyed) surround. Both a touch stronger than before (was alpha 70).
if (IsLightTheme())
dl->AddRectFilled(pMin, pMax, IM_COL32(16, 18, 24, 56)); // neutral dark veil anchors on white
else
dl->AddRectFilled(pMin, pMax, WithAlpha(Background(), 90)); // theme-tinted darken, stronger
}
}

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";