feat(ui): implement UI/UX audit — i18n, HiDPI, theme, destructive-action & overflow fixes

Implements all 26 confirmed UI/UX audit findings plus the 4 dashboard/timeline
tile labels. Verified across full-node + Lite + Windows builds (ctest green) and
an adversarial diff review (one market-tab delete regression caught + fixed).

i18n coverage:
- Balance hero/quick-actions/toasts + dashboard & timeline tiles (Total Balance,
  Shielded, Transparent, Quick Send, Quick Receive, Click to open, Market)
- Send: Review Send / Cancel / Paste / view-only tooltip / memo byte counter
- Settings: Lite lifecycle errors, plaintext-RPC security warning, 8 toasts
- Mining pool-payout tooltip
- Resolve daemon_update_title double-assignment collision (new daemon_update_prompt_title)
- 23 new keys translated into all 8 locales; CJK subset font rebuilt

HiDPI: DPI-scale the Send amount bar, mining thread-tile clamp bounds, receive
loading skeleton, and sidebar notification-badge insets.

Light theme: theme-aware material::SurfaceOverlay() for balance bar tracks and
row-hover highlights; contacts active-pill foreground uses OnPrimary().

Destructive actions: arm/confirm for portfolio-group delete, saved pool/worker
remove, and avatar-image delete.

Interaction/overflow: route balance star/eye buttons and the console fold-toggle
through popup-safe guards; clip peer addr/subver; truncate the balance custom
label; measure Settings tool-button widths; mining stepper disabled-state
feedback; +/- stepper buttons restyled to match the thread tiles.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-18 17:54:15 -05:00
parent 0de44569b7
commit e24ca015d1
24 changed files with 599 additions and 148 deletions

Binary file not shown.

View File

@@ -92,7 +92,10 @@
"balance": "Guthaben", "balance": "Guthaben",
"balance_history_collecting": "Guthabenverlauf — Daten werden gesammelt...", "balance_history_collecting": "Guthabenverlauf — Daten werden gesammelt...",
"balance_layout": "Guthaben-Layout", "balance_layout": "Guthaben-Layout",
"balance_layout_switched": "Layout: %s",
"balance_mining_rate": "Schürfe %s",
"balance_shielded_fmt": "Abgeschirmt: %.8f", "balance_shielded_fmt": "Abgeschirmt: %.8f",
"balance_syncing_pct": "Synchronisiere %.1f%%",
"balance_transparent_fmt": "Transparent: %.8f", "balance_transparent_fmt": "Transparent: %.8f",
"ban": "Sperren", "ban": "Sperren",
"banned_peers": "Gesperrte Peers", "banned_peers": "Gesperrte Peers",
@@ -132,6 +135,7 @@
"bootstrap_verifying": "Prüfsummen werden überprüft...", "bootstrap_verifying": "Prüfsummen werden überprüft...",
"bootstrap_wallet_protected": "(wallet.dat ist geschützt)", "bootstrap_wallet_protected": "(wallet.dat ist geschützt)",
"bootstrap_warning": "Vorhandene Blockdaten (blocks, chainstate, notarizations) werden gelöscht und ersetzt. Ihre wallet.dat wird NICHT verändert oder gelöscht.", "bootstrap_warning": "Vorhandene Blockdaten (blocks, chainstate, notarizations) werden gelöscht und ersetzt. Ihre wallet.dat wird NICHT verändert oder gelöscht.",
"byte_count_fmt": "%zu / %zu Byte",
"cancel": "Abbrechen", "cancel": "Abbrechen",
"change_pass_confirm": "Neue bestätigen:", "change_pass_confirm": "Neue bestätigen:",
"change_pass_current": "Aktuelle Passphrase:", "change_pass_current": "Aktuelle Passphrase:",
@@ -441,6 +445,7 @@
"daemon_update_latest": "Neueste:", "daemon_update_latest": "Neueste:",
"daemon_update_loading": "Releases werden geladen…", "daemon_update_loading": "Releases werden geladen…",
"daemon_update_now": "Jetzt aktualisieren", "daemon_update_now": "Jetzt aktualisieren",
"daemon_update_prompt_title": "Node-Daemon aktualisieren?",
"daemon_update_reinstall": "Neu installieren", "daemon_update_reinstall": "Neu installieren",
"daemon_update_restart_note": "Starten Sie den Daemon neu, um die neue Version auszuführen.", "daemon_update_restart_note": "Starten Sie den Daemon neu, um die neue Version auszuführen.",
"daemon_update_restart_now": "Daemon jetzt neu starten", "daemon_update_restart_now": "Daemon jetzt neu starten",
@@ -657,6 +662,7 @@
"light": "Hell", "light": "Hell",
"lite_account_label": "Konto", "lite_account_label": "Konto",
"lite_action": "Aktion", "lite_action": "Aktion",
"lite_backend_unavailable": "Lite-Wallet-Backend nicht verfügbar",
"lite_backup_keys": "Sicherung & Schlüssel", "lite_backup_keys": "Sicherung & Schlüssel",
"lite_birthday_backup": "Geburtstag: %llu (auch diesen sichern)", "lite_birthday_backup": "Geburtstag: %llu (auch diesen sichern)",
"lite_birthday_hint": "Blockhöhe, ab der gescannt werden soll. Bei 0 belassen, falls unbekannt (langsamerer vollständiger Scan).", "lite_birthday_hint": "Blockhöhe, ab der gescannt werden soll. Bei 0 belassen, falls unbekannt (langsamerer vollständiger Scan).",
@@ -664,9 +670,12 @@
"lite_console_backend_commands": "Backend-Befehle:", "lite_console_backend_commands": "Backend-Befehle:",
"lite_console_help_passthrough": "Jede andere Eingabe wird als Lite-Wallet-Konsolenbefehl ausgeführt.", "lite_console_help_passthrough": "Jede andere Eingabe wird als Lite-Wallet-Konsolenbefehl ausgeführt.",
"lite_copy": "Kopieren", "lite_copy": "Kopieren",
"lite_could_not_start": "Der Vorgang konnte nicht gestartet werden",
"lite_could_not_write": "Konnte nicht schreiben ", "lite_could_not_write": "Konnte nicht schreiben ",
"lite_encrypt_wallet": "Wallet verschlüsseln", "lite_encrypt_wallet": "Wallet verschlüsseln",
"lite_encryption_removed": "Verschlüsselung entfernt", "lite_encryption_removed": "Verschlüsselung entfernt",
"lite_enter_all_seed_words": "Alle 24 Seed-Wörter zur Wiederherstellung eingeben (%d erhalten)",
"lite_enter_wallet_path": "Wallet-Pfad eingeben",
"lite_hide_wipe": "Ausblenden & löschen", "lite_hide_wipe": "Ausblenden & löschen",
"lite_import": "Importieren", "lite_import": "Importieren",
"lite_import_key_label": "Schlüssel importieren", "lite_import_key_label": "Schlüssel importieren",
@@ -920,6 +929,7 @@
"mining_pool": "Pool", "mining_pool": "Pool",
"mining_pool_fee": "Gebühr", "mining_pool_fee": "Gebühr",
"mining_pool_hashrate": "Pool-Hashrate", "mining_pool_hashrate": "Pool-Hashrate",
"mining_pool_needs_payout_tooltip": "Zuerst eine Auszahlungsadresse eingeben (Z-Adresse erzeugen)",
"mining_pool_url": "Pool-URL", "mining_pool_url": "Pool-URL",
"mining_pools_header": "POOLS", "mining_pools_header": "POOLS",
"mining_recent_blocks": "LETZTE BLÖCKE", "mining_recent_blocks": "LETZTE BLÖCKE",
@@ -1121,6 +1131,8 @@
"qr_failed": "QR-Code konnte nicht generiert werden", "qr_failed": "QR-Code konnte nicht generiert werden",
"qr_title": "QR-Code", "qr_title": "QR-Code",
"qr_unavailable": "QR nicht verfügbar", "qr_unavailable": "QR nicht verfügbar",
"quick_receive": "Schnell empfangen",
"quick_send": "Schnell senden",
"ram_daemon_gb": "Daemon: %.1f GB (%s)", "ram_daemon_gb": "Daemon: %.1f GB (%s)",
"ram_daemon_mb": "Daemon: %.0f MB (%s)", "ram_daemon_mb": "Daemon: %.0f MB (%s)",
"ram_system_gb": "System: %.1f / %.0f GB", "ram_system_gb": "System: %.1f / %.0f GB",
@@ -1170,6 +1182,7 @@
"rpc_connection": "RPC-Verbindung...", "rpc_connection": "RPC-Verbindung...",
"rpc_host": "RPC-Host", "rpc_host": "RPC-Host",
"rpc_pass": "Passwort", "rpc_pass": "Passwort",
"rpc_plaintext_remote_warning": "Die Remote-RPC-Verbindung verwendet unverschlüsseltes HTTP. Füge rpctls=1 zur DRAGONX.conf hinzu, falls dein Daemon TLS unterstützt.",
"rpc_port": "Port", "rpc_port": "Port",
"rpc_user": "Benutzername", "rpc_user": "Benutzername",
"save": "Speichern", "save": "Speichern",
@@ -1279,6 +1292,7 @@
"send_tooltip_not_connected": "Nicht mit Daemon verbunden", "send_tooltip_not_connected": "Nicht mit Daemon verbunden",
"send_tooltip_select_source": "Wählen Sie zuerst eine Quelladresse", "send_tooltip_select_source": "Wählen Sie zuerst eine Quelladresse",
"send_tooltip_syncing": "Warten Sie auf die Blockchain-Synchronisierung", "send_tooltip_syncing": "Warten Sie auf die Blockchain-Synchronisierung",
"send_tooltip_view_only": "Nur-Lese-Adresse — kein Spending Key, Senden nicht möglich",
"send_total": "Gesamt", "send_total": "Gesamt",
"send_transaction": "Transaktion senden", "send_transaction": "Transaktion senden",
"send_tx_failed": "Transaktion fehlgeschlagen", "send_tx_failed": "Transaktion fehlgeschlagen",
@@ -1342,6 +1356,7 @@
"settings_locked": "Gesperrt", "settings_locked": "Gesperrt",
"settings_merge_to_address": "An Adresse zusammenführen...", "settings_merge_to_address": "An Adresse zusammenführen...",
"settings_noise_opacity": "Rauschdichte:", "settings_noise_opacity": "Rauschdichte:",
"settings_not_connected": "Nicht mit dem Daemon verbunden",
"settings_not_encrypted": "Nicht verschlüsselt", "settings_not_encrypted": "Nicht verschlüsselt",
"settings_not_found": "Nicht gefunden", "settings_not_found": "Nicht gefunden",
"settings_open_app_dir": "App-Ordner öffnen", "settings_open_app_dir": "App-Ordner öffnen",
@@ -1352,18 +1367,23 @@
"settings_privacy": "Datenschutz", "settings_privacy": "Datenschutz",
"settings_quick_unlock_pin": "Schnell-Entsperr-PIN", "settings_quick_unlock_pin": "Schnell-Entsperr-PIN",
"settings_reduce_transparency": "Transparenz reduzieren", "settings_reduce_transparency": "Transparenz reduzieren",
"settings_reloaded": "Einstellungen von der Festplatte neu geladen",
"settings_remove_encryption": "Verschlüsselung entfernen", "settings_remove_encryption": "Verschlüsselung entfernen",
"settings_remove_pin": "PIN entfernen", "settings_remove_pin": "PIN entfernen",
"settings_request_payment": "Zahlung anfordern...", "settings_request_payment": "Zahlung anfordern...",
"settings_rescan_desc": "Blockchain nach fehlenden Transaktionen neu scannen", "settings_rescan_desc": "Blockchain nach fehlenden Transaktionen neu scannen",
"settings_restart_daemon": "Daemon neu starten", "settings_restart_daemon": "Daemon neu starten",
"settings_rpc_connection": "RPC-Verbindung", "settings_rpc_connection": "RPC-Verbindung",
"settings_rpc_error_prefix": "RPC-Fehler: ",
"settings_rpc_note": "Hinweis: Verbindungseinstellungen werden automatisch aus DRAGONX.conf erkannt", "settings_rpc_note": "Hinweis: Verbindungseinstellungen werden automatisch aus DRAGONX.conf erkannt",
"settings_rpc_ok": "RPC-Verbindung OK",
"settings_save_shielded_desc": "Speichert z-addr Transaktionen in einer lokalen Datei zur Ansicht", "settings_save_shielded_desc": "Speichert z-addr Transaktionen in einer lokalen Datei zur Ansicht",
"settings_save_shielded_local": "Geschirmten Transaktionsverlauf lokal speichern", "settings_save_shielded_local": "Geschirmten Transaktionsverlauf lokal speichern",
"settings_saved": "Einstellungen gespeichert",
"settings_set_pin": "PIN festlegen", "settings_set_pin": "PIN festlegen",
"settings_shield_mining": "Mining abschirmen...", "settings_shield_mining": "Mining abschirmen...",
"settings_solid_colors_desc": "Feste Farben anstelle von Unschärfe-Effekten verwenden (Barrierefreiheit)", "settings_solid_colors_desc": "Feste Farben anstelle von Unschärfe-Effekten verwenden (Barrierefreiheit)",
"settings_theme_refreshed": "Themenliste aktualisiert",
"settings_tor_desc": "Alle Verbindungen für erhöhte Privatsphäre über Tor leiten", "settings_tor_desc": "Alle Verbindungen für erhöhte Privatsphäre über Tor leiten",
"settings_unlocked": "Entsperrt", "settings_unlocked": "Entsperrt",
"settings_use_tor_network": "Tor für Netzwerkverbindungen verwenden", "settings_use_tor_network": "Tor für Netzwerkverbindungen verwenden",
@@ -1375,6 +1395,8 @@
"settings_wallet_maintenance": "Wallet-Wartung", "settings_wallet_maintenance": "Wallet-Wartung",
"settings_wallet_not_found": "Wallet-Datei nicht gefunden", "settings_wallet_not_found": "Wallet-Datei nicht gefunden",
"settings_wallet_size_label": "Wallet-Größe:", "settings_wallet_size_label": "Wallet-Größe:",
"settings_ztx_cleared": "Z-Transaktionsverlauf gelöscht",
"settings_ztx_not_found": "Keine Verlaufsdatei gefunden",
"setup_wizard": "Einrichtungsassistent", "setup_wizard": "Einrichtungsassistent",
"share": "Teilen", "share": "Teilen",
"shield_check_status": "Status prüfen", "shield_check_status": "Status prüfen",
@@ -1457,6 +1479,7 @@
"theme": "Design", "theme": "Design",
"theme_effects": "Design-Effekte", "theme_effects": "Design-Effekte",
"theme_language": "THEMA & SPRACHE", "theme_language": "THEMA & SPRACHE",
"tile_click_to_open": "Zum Öffnen klicken",
"time_days_ago": "vor %d Tagen", "time_days_ago": "vor %d Tagen",
"time_hours_ago": "vor %d Stunden", "time_hours_ago": "vor %d Stunden",
"time_minutes_ago": "vor %d Minuten", "time_minutes_ago": "vor %d Minuten",
@@ -1472,6 +1495,7 @@
"tools": "WERKZEUGE", "tools": "WERKZEUGE",
"tools_actions": "Werkzeuge & Aktionen...", "tools_actions": "Werkzeuge & Aktionen...",
"total": "Gesamt", "total": "Gesamt",
"total_balance_label": "Gesamtguthaben",
"transaction_id": "TRANSAKTIONS-ID", "transaction_id": "TRANSAKTIONS-ID",
"transaction_sent": "Transaktion erfolgreich gesendet", "transaction_sent": "Transaktion erfolgreich gesendet",
"transaction_sent_msg": "Transaktion gesendet!", "transaction_sent_msg": "Transaktion gesendet!",

View File

@@ -92,7 +92,10 @@
"balance": "Saldo", "balance": "Saldo",
"balance_history_collecting": "Historial de saldo — recopilando datos...", "balance_history_collecting": "Historial de saldo — recopilando datos...",
"balance_layout": "Diseño de Saldo", "balance_layout": "Diseño de Saldo",
"balance_layout_switched": "Diseño: %s",
"balance_mining_rate": "Minando %s",
"balance_shielded_fmt": "Protegido: %.8f", "balance_shielded_fmt": "Protegido: %.8f",
"balance_syncing_pct": "Sincronizando %.1f%%",
"balance_transparent_fmt": "Transparente: %.8f", "balance_transparent_fmt": "Transparente: %.8f",
"ban": "Bloquear", "ban": "Bloquear",
"banned_peers": "Nodos Bloqueados", "banned_peers": "Nodos Bloqueados",
@@ -132,6 +135,7 @@
"bootstrap_verifying": "Verificando sumas de comprobación...", "bootstrap_verifying": "Verificando sumas de comprobación...",
"bootstrap_wallet_protected": "(wallet.dat está protegido)", "bootstrap_wallet_protected": "(wallet.dat está protegido)",
"bootstrap_warning": "Los datos de bloques existentes (blocks, chainstate, notarizations) se eliminarán y reemplazarán. Su wallet.dat NO será modificado ni eliminado.", "bootstrap_warning": "Los datos de bloques existentes (blocks, chainstate, notarizations) se eliminarán y reemplazarán. Su wallet.dat NO será modificado ni eliminado.",
"byte_count_fmt": "%zu / %zu bytes",
"cancel": "Cancelar", "cancel": "Cancelar",
"change_pass_confirm": "Confirmar nueva:", "change_pass_confirm": "Confirmar nueva:",
"change_pass_current": "Frase de contraseña actual:", "change_pass_current": "Frase de contraseña actual:",
@@ -441,6 +445,7 @@
"daemon_update_latest": "Más reciente:", "daemon_update_latest": "Más reciente:",
"daemon_update_loading": "Cargando versiones…", "daemon_update_loading": "Cargando versiones…",
"daemon_update_now": "Actualizar ahora", "daemon_update_now": "Actualizar ahora",
"daemon_update_prompt_title": "¿Actualizar el daemon del nodo?",
"daemon_update_reinstall": "Reinstalar", "daemon_update_reinstall": "Reinstalar",
"daemon_update_restart_note": "Reinicia el daemon para empezar a ejecutar la nueva versión.", "daemon_update_restart_note": "Reinicia el daemon para empezar a ejecutar la nueva versión.",
"daemon_update_restart_now": "Reiniciar el daemon ahora", "daemon_update_restart_now": "Reiniciar el daemon ahora",
@@ -657,6 +662,7 @@
"light": "Claro", "light": "Claro",
"lite_account_label": "Cuenta", "lite_account_label": "Cuenta",
"lite_action": "Acción", "lite_action": "Acción",
"lite_backend_unavailable": "Backend de billetera Lite no disponible",
"lite_backup_keys": "Respaldo y claves", "lite_backup_keys": "Respaldo y claves",
"lite_birthday_backup": "Cumpleaños: %llu (respalda esto también)", "lite_birthday_backup": "Cumpleaños: %llu (respalda esto también)",
"lite_birthday_hint": "Altura de bloque desde la que empezar a escanear. Deja 0 si se desconoce (escaneo completo más lento).", "lite_birthday_hint": "Altura de bloque desde la que empezar a escanear. Deja 0 si se desconoce (escaneo completo más lento).",
@@ -664,9 +670,12 @@
"lite_console_backend_commands": "Comandos del backend:", "lite_console_backend_commands": "Comandos del backend:",
"lite_console_help_passthrough": "Cualquier otra entrada se ejecuta como un comando de consola de la cartera lite.", "lite_console_help_passthrough": "Cualquier otra entrada se ejecuta como un comando de consola de la cartera lite.",
"lite_copy": "Copiar", "lite_copy": "Copiar",
"lite_could_not_start": "No se pudo iniciar la operación",
"lite_could_not_write": "No se pudo escribir ", "lite_could_not_write": "No se pudo escribir ",
"lite_encrypt_wallet": "Cifrar cartera", "lite_encrypt_wallet": "Cifrar cartera",
"lite_encryption_removed": "Cifrado eliminado", "lite_encryption_removed": "Cifrado eliminado",
"lite_enter_all_seed_words": "Ingrese las 24 palabras semilla para restaurar (se obtuvieron %d)",
"lite_enter_wallet_path": "Ingrese una ruta de billetera",
"lite_hide_wipe": "Ocultar y borrar", "lite_hide_wipe": "Ocultar y borrar",
"lite_import": "Importar", "lite_import": "Importar",
"lite_import_key_label": "Importar clave", "lite_import_key_label": "Importar clave",
@@ -920,6 +929,7 @@
"mining_pool": "Pool", "mining_pool": "Pool",
"mining_pool_fee": "Comisión", "mining_pool_fee": "Comisión",
"mining_pool_hashrate": "Hashrate del Pool", "mining_pool_hashrate": "Hashrate del Pool",
"mining_pool_needs_payout_tooltip": "Ingrese primero una dirección de pago (genere una dirección Z)",
"mining_pool_url": "URL del Pool", "mining_pool_url": "URL del Pool",
"mining_pools_header": "POOLS", "mining_pools_header": "POOLS",
"mining_recent_blocks": "BLOQUES RECIENTES", "mining_recent_blocks": "BLOQUES RECIENTES",
@@ -1121,6 +1131,8 @@
"qr_failed": "Error al generar código QR", "qr_failed": "Error al generar código QR",
"qr_title": "Código QR", "qr_title": "Código QR",
"qr_unavailable": "QR no disponible", "qr_unavailable": "QR no disponible",
"quick_receive": "Recepción rápida",
"quick_send": "Envío rápido",
"ram_daemon_gb": "Daemon: %.1f GB (%s)", "ram_daemon_gb": "Daemon: %.1f GB (%s)",
"ram_daemon_mb": "Daemon: %.0f MB (%s)", "ram_daemon_mb": "Daemon: %.0f MB (%s)",
"ram_system_gb": "Sistema: %.1f / %.0f GB", "ram_system_gb": "Sistema: %.1f / %.0f GB",
@@ -1170,6 +1182,7 @@
"rpc_connection": "Conexión RPC...", "rpc_connection": "Conexión RPC...",
"rpc_host": "Host RPC", "rpc_host": "Host RPC",
"rpc_pass": "Contraseña", "rpc_pass": "Contraseña",
"rpc_plaintext_remote_warning": "El RPC remoto está usando HTTP sin cifrar. Agregue rpctls=1 a DRAGONX.conf si su daemon admite TLS.",
"rpc_port": "Puerto", "rpc_port": "Puerto",
"rpc_user": "Usuario", "rpc_user": "Usuario",
"save": "Guardar", "save": "Guardar",
@@ -1279,6 +1292,7 @@
"send_tooltip_not_connected": "No conectado al daemon", "send_tooltip_not_connected": "No conectado al daemon",
"send_tooltip_select_source": "Selecciona una dirección de origen primero", "send_tooltip_select_source": "Selecciona una dirección de origen primero",
"send_tooltip_syncing": "Espera a que se sincronice el blockchain", "send_tooltip_syncing": "Espera a que se sincronice el blockchain",
"send_tooltip_view_only": "Dirección de solo vista — sin clave de gasto, no se puede enviar",
"send_total": "Total", "send_total": "Total",
"send_transaction": "Enviar Transacción", "send_transaction": "Enviar Transacción",
"send_tx_failed": "Error en la transacción", "send_tx_failed": "Error en la transacción",
@@ -1342,6 +1356,7 @@
"settings_locked": "Bloqueado", "settings_locked": "Bloqueado",
"settings_merge_to_address": "Fusionar a dirección...", "settings_merge_to_address": "Fusionar a dirección...",
"settings_noise_opacity": "Opacidad de ruido:", "settings_noise_opacity": "Opacidad de ruido:",
"settings_not_connected": "No conectado al daemon",
"settings_not_encrypted": "Sin cifrar", "settings_not_encrypted": "Sin cifrar",
"settings_not_found": "No encontrado", "settings_not_found": "No encontrado",
"settings_open_app_dir": "Abrir carpeta de la aplicación", "settings_open_app_dir": "Abrir carpeta de la aplicación",
@@ -1352,18 +1367,23 @@
"settings_privacy": "Privacidad", "settings_privacy": "Privacidad",
"settings_quick_unlock_pin": "PIN de desbloqueo rápido", "settings_quick_unlock_pin": "PIN de desbloqueo rápido",
"settings_reduce_transparency": "Reducir transparencia", "settings_reduce_transparency": "Reducir transparencia",
"settings_reloaded": "Configuración recargada desde el disco",
"settings_remove_encryption": "Quitar cifrado", "settings_remove_encryption": "Quitar cifrado",
"settings_remove_pin": "Quitar PIN", "settings_remove_pin": "Quitar PIN",
"settings_request_payment": "Solicitar pago...", "settings_request_payment": "Solicitar pago...",
"settings_rescan_desc": "Reescanear la cadena de bloques en busca de transacciones faltantes", "settings_rescan_desc": "Reescanear la cadena de bloques en busca de transacciones faltantes",
"settings_restart_daemon": "Reiniciar daemon", "settings_restart_daemon": "Reiniciar daemon",
"settings_rpc_connection": "Conexión RPC", "settings_rpc_connection": "Conexión RPC",
"settings_rpc_error_prefix": "Error de RPC: ",
"settings_rpc_note": "Nota: Los ajustes de conexión se detectan automáticamente desde DRAGONX.conf", "settings_rpc_note": "Nota: Los ajustes de conexión se detectan automáticamente desde DRAGONX.conf",
"settings_rpc_ok": "Conexión RPC correcta",
"settings_save_shielded_desc": "Almacena transacciones z-addr en un archivo local para visualización", "settings_save_shielded_desc": "Almacena transacciones z-addr en un archivo local para visualización",
"settings_save_shielded_local": "Guardar historial de transacciones blindadas localmente", "settings_save_shielded_local": "Guardar historial de transacciones blindadas localmente",
"settings_saved": "Configuración guardada",
"settings_set_pin": "Establecer PIN", "settings_set_pin": "Establecer PIN",
"settings_shield_mining": "Blindar minería...", "settings_shield_mining": "Blindar minería...",
"settings_solid_colors_desc": "Usar colores sólidos en lugar de efectos de desenfoque (accesibilidad)", "settings_solid_colors_desc": "Usar colores sólidos en lugar de efectos de desenfoque (accesibilidad)",
"settings_theme_refreshed": "Lista de temas actualizada",
"settings_tor_desc": "Enrutar todas las conexiones a través de Tor para mayor privacidad", "settings_tor_desc": "Enrutar todas las conexiones a través de Tor para mayor privacidad",
"settings_unlocked": "Desbloqueado", "settings_unlocked": "Desbloqueado",
"settings_use_tor_network": "Usar Tor para conexiones de red", "settings_use_tor_network": "Usar Tor para conexiones de red",
@@ -1375,6 +1395,8 @@
"settings_wallet_maintenance": "Mantenimiento de billetera", "settings_wallet_maintenance": "Mantenimiento de billetera",
"settings_wallet_not_found": "Archivo de billetera no encontrado", "settings_wallet_not_found": "Archivo de billetera no encontrado",
"settings_wallet_size_label": "Tamaño de billetera:", "settings_wallet_size_label": "Tamaño de billetera:",
"settings_ztx_cleared": "Historial de transacciones Z borrado",
"settings_ztx_not_found": "No se encontró archivo de historial",
"setup_wizard": "Asistente de Configuración", "setup_wizard": "Asistente de Configuración",
"share": "Compartir", "share": "Compartir",
"shield_check_status": "Verificar Estado", "shield_check_status": "Verificar Estado",
@@ -1457,6 +1479,7 @@
"theme": "Tema", "theme": "Tema",
"theme_effects": "Efectos de tema", "theme_effects": "Efectos de tema",
"theme_language": "TEMA E IDIOMA", "theme_language": "TEMA E IDIOMA",
"tile_click_to_open": "Clic para abrir",
"time_days_ago": "hace %d días", "time_days_ago": "hace %d días",
"time_hours_ago": "hace %d horas", "time_hours_ago": "hace %d horas",
"time_minutes_ago": "hace %d minutos", "time_minutes_ago": "hace %d minutos",
@@ -1472,6 +1495,7 @@
"tools": "HERRAMIENTAS", "tools": "HERRAMIENTAS",
"tools_actions": "Herramientas y Acciones...", "tools_actions": "Herramientas y Acciones...",
"total": "Total", "total": "Total",
"total_balance_label": "Saldo Total",
"transaction_id": "ID DE TRANSACCIÓN", "transaction_id": "ID DE TRANSACCIÓN",
"transaction_sent": "Transacción enviada exitosamente", "transaction_sent": "Transacción enviada exitosamente",
"transaction_sent_msg": "¡Transacción enviada!", "transaction_sent_msg": "¡Transacción enviada!",

View File

@@ -92,7 +92,10 @@
"balance": "Solde", "balance": "Solde",
"balance_history_collecting": "Historique du solde — collecte des données...", "balance_history_collecting": "Historique du solde — collecte des données...",
"balance_layout": "Disposition du solde", "balance_layout": "Disposition du solde",
"balance_layout_switched": "Disposition : %s",
"balance_mining_rate": "Minage %s",
"balance_shielded_fmt": "Blindé : %.8f", "balance_shielded_fmt": "Blindé : %.8f",
"balance_syncing_pct": "Synchronisation %.1f%%",
"balance_transparent_fmt": "Transparent : %.8f", "balance_transparent_fmt": "Transparent : %.8f",
"ban": "Bannir", "ban": "Bannir",
"banned_peers": "Pairs bannis", "banned_peers": "Pairs bannis",
@@ -132,6 +135,7 @@
"bootstrap_verifying": "Vérification des sommes de contrôle...", "bootstrap_verifying": "Vérification des sommes de contrôle...",
"bootstrap_wallet_protected": "(wallet.dat est protégé)", "bootstrap_wallet_protected": "(wallet.dat est protégé)",
"bootstrap_warning": "Les données de blocs existantes (blocks, chainstate, notarizations) seront supprimées et remplacées. Votre wallet.dat ne sera PAS modifié ni supprimé.", "bootstrap_warning": "Les données de blocs existantes (blocks, chainstate, notarizations) seront supprimées et remplacées. Votre wallet.dat ne sera PAS modifié ni supprimé.",
"byte_count_fmt": "%zu / %zu octets",
"cancel": "Annuler", "cancel": "Annuler",
"change_pass_confirm": "Confirmer la nouvelle :", "change_pass_confirm": "Confirmer la nouvelle :",
"change_pass_current": "Phrase secrète actuelle :", "change_pass_current": "Phrase secrète actuelle :",
@@ -441,6 +445,7 @@
"daemon_update_latest": "Dernière :", "daemon_update_latest": "Dernière :",
"daemon_update_loading": "Chargement des versions…", "daemon_update_loading": "Chargement des versions…",
"daemon_update_now": "Mettre à jour", "daemon_update_now": "Mettre à jour",
"daemon_update_prompt_title": "Mettre à jour le démon du nœud ?",
"daemon_update_reinstall": "Réinstaller", "daemon_update_reinstall": "Réinstaller",
"daemon_update_restart_note": "Redémarrez le daemon pour lancer la nouvelle version.", "daemon_update_restart_note": "Redémarrez le daemon pour lancer la nouvelle version.",
"daemon_update_restart_now": "Redémarrer le daemon maintenant", "daemon_update_restart_now": "Redémarrer le daemon maintenant",
@@ -657,6 +662,7 @@
"light": "Clair", "light": "Clair",
"lite_account_label": "Compte", "lite_account_label": "Compte",
"lite_action": "Action", "lite_action": "Action",
"lite_backend_unavailable": "Backend du portefeuille léger indisponible",
"lite_backup_keys": "Sauvegarde et clés", "lite_backup_keys": "Sauvegarde et clés",
"lite_birthday_backup": "Date de création : %llu (à sauvegarder également)", "lite_birthday_backup": "Date de création : %llu (à sauvegarder également)",
"lite_birthday_hint": "Hauteur de bloc à partir de laquelle commencer l'analyse. Laissez 0 si inconnue (analyse complète plus lente).", "lite_birthday_hint": "Hauteur de bloc à partir de laquelle commencer l'analyse. Laissez 0 si inconnue (analyse complète plus lente).",
@@ -664,9 +670,12 @@
"lite_console_backend_commands": "Commandes du backend :", "lite_console_backend_commands": "Commandes du backend :",
"lite_console_help_passthrough": "Toute autre entrée est exécutée comme une commande de la console du portefeuille lite.", "lite_console_help_passthrough": "Toute autre entrée est exécutée comme une commande de la console du portefeuille lite.",
"lite_copy": "Copier", "lite_copy": "Copier",
"lite_could_not_start": "Impossible de démarrer l'opération",
"lite_could_not_write": "Impossible d'écrire ", "lite_could_not_write": "Impossible d'écrire ",
"lite_encrypt_wallet": "Chiffrer le portefeuille", "lite_encrypt_wallet": "Chiffrer le portefeuille",
"lite_encryption_removed": "Chiffrement supprimé", "lite_encryption_removed": "Chiffrement supprimé",
"lite_enter_all_seed_words": "Entrez les 24 mots de la phrase de récupération (%d obtenus)",
"lite_enter_wallet_path": "Entrez un chemin de portefeuille",
"lite_hide_wipe": "Masquer et effacer", "lite_hide_wipe": "Masquer et effacer",
"lite_import": "Importer", "lite_import": "Importer",
"lite_import_key_label": "Importer une clé", "lite_import_key_label": "Importer une clé",
@@ -920,6 +929,7 @@
"mining_pool": "Pool", "mining_pool": "Pool",
"mining_pool_fee": "Frais", "mining_pool_fee": "Frais",
"mining_pool_hashrate": "Hashrate du pool", "mining_pool_hashrate": "Hashrate du pool",
"mining_pool_needs_payout_tooltip": "Entrez d'abord une adresse de paiement (générez une adresse Z)",
"mining_pool_url": "URL du pool", "mining_pool_url": "URL du pool",
"mining_pools_header": "POOLS", "mining_pools_header": "POOLS",
"mining_recent_blocks": "BLOCS RÉCENTS", "mining_recent_blocks": "BLOCS RÉCENTS",
@@ -1121,6 +1131,8 @@
"qr_failed": "Échec de la génération du code QR", "qr_failed": "Échec de la génération du code QR",
"qr_title": "Code QR", "qr_title": "Code QR",
"qr_unavailable": "QR indisponible", "qr_unavailable": "QR indisponible",
"quick_receive": "Réception rapide",
"quick_send": "Envoi rapide",
"ram_daemon_gb": "Daemon : %.1f Go (%s)", "ram_daemon_gb": "Daemon : %.1f Go (%s)",
"ram_daemon_mb": "Daemon : %.0f Mo (%s)", "ram_daemon_mb": "Daemon : %.0f Mo (%s)",
"ram_system_gb": "Système : %.1f / %.0f Go", "ram_system_gb": "Système : %.1f / %.0f Go",
@@ -1170,6 +1182,7 @@
"rpc_connection": "Connexion RPC...", "rpc_connection": "Connexion RPC...",
"rpc_host": "Hôte RPC", "rpc_host": "Hôte RPC",
"rpc_pass": "Mot de passe", "rpc_pass": "Mot de passe",
"rpc_plaintext_remote_warning": "Le RPC distant utilise du HTTP en clair. Ajoutez rpctls=1 à DRAGONX.conf si votre démon prend en charge TLS.",
"rpc_port": "Port", "rpc_port": "Port",
"rpc_user": "Nom d'utilisateur", "rpc_user": "Nom d'utilisateur",
"save": "Enregistrer", "save": "Enregistrer",
@@ -1279,6 +1292,7 @@
"send_tooltip_not_connected": "Non connecté au daemon", "send_tooltip_not_connected": "Non connecté au daemon",
"send_tooltip_select_source": "Sélectionnez d'abord une adresse source", "send_tooltip_select_source": "Sélectionnez d'abord une adresse source",
"send_tooltip_syncing": "Attendez la synchronisation de la blockchain", "send_tooltip_syncing": "Attendez la synchronisation de la blockchain",
"send_tooltip_view_only": "Adresse en lecture seule — pas de clé de dépense, envoi impossible",
"send_total": "Total", "send_total": "Total",
"send_transaction": "Envoyer la transaction", "send_transaction": "Envoyer la transaction",
"send_tx_failed": "Transaction échouée", "send_tx_failed": "Transaction échouée",
@@ -1342,6 +1356,7 @@
"settings_locked": "Verrouillé", "settings_locked": "Verrouillé",
"settings_merge_to_address": "Fusionner vers l'adresse...", "settings_merge_to_address": "Fusionner vers l'adresse...",
"settings_noise_opacity": "Opacité du bruit :", "settings_noise_opacity": "Opacité du bruit :",
"settings_not_connected": "Non connecté au démon",
"settings_not_encrypted": "Non chiffré", "settings_not_encrypted": "Non chiffré",
"settings_not_found": "Non trouvé", "settings_not_found": "Non trouvé",
"settings_open_app_dir": "Ouvrir le dossier de l'application", "settings_open_app_dir": "Ouvrir le dossier de l'application",
@@ -1352,18 +1367,23 @@
"settings_privacy": "Confidentialité", "settings_privacy": "Confidentialité",
"settings_quick_unlock_pin": "PIN de déverrouillage rapide", "settings_quick_unlock_pin": "PIN de déverrouillage rapide",
"settings_reduce_transparency": "Réduire la transparence", "settings_reduce_transparency": "Réduire la transparence",
"settings_reloaded": "Paramètres rechargés depuis le disque",
"settings_remove_encryption": "Supprimer le chiffrement", "settings_remove_encryption": "Supprimer le chiffrement",
"settings_remove_pin": "Supprimer le PIN", "settings_remove_pin": "Supprimer le PIN",
"settings_request_payment": "Demander un paiement...", "settings_request_payment": "Demander un paiement...",
"settings_rescan_desc": "Rescanner la blockchain pour les transactions manquantes", "settings_rescan_desc": "Rescanner la blockchain pour les transactions manquantes",
"settings_restart_daemon": "Redémarrer le daemon", "settings_restart_daemon": "Redémarrer le daemon",
"settings_rpc_connection": "Connexion RPC", "settings_rpc_connection": "Connexion RPC",
"settings_rpc_error_prefix": "Erreur RPC : ",
"settings_rpc_note": "Remarque : Les paramètres de connexion sont généralement détectés automatiquement depuis DRAGONX.conf", "settings_rpc_note": "Remarque : Les paramètres de connexion sont généralement détectés automatiquement depuis DRAGONX.conf",
"settings_rpc_ok": "Connexion RPC OK",
"settings_save_shielded_desc": "Stocke les transactions z-addr dans un fichier local pour consultation", "settings_save_shielded_desc": "Stocke les transactions z-addr dans un fichier local pour consultation",
"settings_save_shielded_local": "Enregistrer l'historique des transactions blindées localement", "settings_save_shielded_local": "Enregistrer l'historique des transactions blindées localement",
"settings_saved": "Paramètres enregistrés",
"settings_set_pin": "Définir le PIN", "settings_set_pin": "Définir le PIN",
"settings_shield_mining": "Blindage minage...", "settings_shield_mining": "Blindage minage...",
"settings_solid_colors_desc": "Utiliser des couleurs unies au lieu des effets de flou (accessibilité)", "settings_solid_colors_desc": "Utiliser des couleurs unies au lieu des effets de flou (accessibilité)",
"settings_theme_refreshed": "Liste des thèmes actualisée",
"settings_tor_desc": "Acheminer toutes les connexions via Tor pour une confidentialité renforcée", "settings_tor_desc": "Acheminer toutes les connexions via Tor pour une confidentialité renforcée",
"settings_unlocked": "Déverrouillé", "settings_unlocked": "Déverrouillé",
"settings_use_tor_network": "Utiliser Tor pour les connexions réseau", "settings_use_tor_network": "Utiliser Tor pour les connexions réseau",
@@ -1375,6 +1395,8 @@
"settings_wallet_maintenance": "Maintenance du portefeuille", "settings_wallet_maintenance": "Maintenance du portefeuille",
"settings_wallet_not_found": "Fichier portefeuille introuvable", "settings_wallet_not_found": "Fichier portefeuille introuvable",
"settings_wallet_size_label": "Taille du portefeuille :", "settings_wallet_size_label": "Taille du portefeuille :",
"settings_ztx_cleared": "Historique des transactions Z effacé",
"settings_ztx_not_found": "Aucun fichier d'historique trouvé",
"setup_wizard": "Assistant de configuration", "setup_wizard": "Assistant de configuration",
"share": "Partager", "share": "Partager",
"shield_check_status": "Vérifier le statut", "shield_check_status": "Vérifier le statut",
@@ -1457,6 +1479,7 @@
"theme": "Thème", "theme": "Thème",
"theme_effects": "Effets de thème", "theme_effects": "Effets de thème",
"theme_language": "THÈME & LANGUE", "theme_language": "THÈME & LANGUE",
"tile_click_to_open": "Cliquer pour ouvrir",
"time_days_ago": "il y a %d jours", "time_days_ago": "il y a %d jours",
"time_hours_ago": "il y a %d heures", "time_hours_ago": "il y a %d heures",
"time_minutes_ago": "il y a %d minutes", "time_minutes_ago": "il y a %d minutes",
@@ -1472,6 +1495,7 @@
"tools": "OUTILS", "tools": "OUTILS",
"tools_actions": "Outils & Actions...", "tools_actions": "Outils & Actions...",
"total": "Total", "total": "Total",
"total_balance_label": "Solde total",
"transaction_id": "ID DE TRANSACTION", "transaction_id": "ID DE TRANSACTION",
"transaction_sent": "Transaction envoyée avec succès", "transaction_sent": "Transaction envoyée avec succès",
"transaction_sent_msg": "Transaction envoyée !", "transaction_sent_msg": "Transaction envoyée !",

View File

@@ -92,7 +92,10 @@
"balance": "残高", "balance": "残高",
"balance_history_collecting": "残高履歴 — データを収集中...", "balance_history_collecting": "残高履歴 — データを収集中...",
"balance_layout": "残高レイアウト", "balance_layout": "残高レイアウト",
"balance_layout_switched": "レイアウト: %s",
"balance_mining_rate": "マイニング中 %s",
"balance_shielded_fmt": "シールド: %.8f", "balance_shielded_fmt": "シールド: %.8f",
"balance_syncing_pct": "同期中 %.1f%%",
"balance_transparent_fmt": "透明: %.8f", "balance_transparent_fmt": "透明: %.8f",
"ban": "ブロック", "ban": "ブロック",
"banned_peers": "ブロック済みピア", "banned_peers": "ブロック済みピア",
@@ -132,6 +135,7 @@
"bootstrap_verifying": "チェックサムを検証中...", "bootstrap_verifying": "チェックサムを検証中...",
"bootstrap_wallet_protected": "(wallet.dat は保護されています)", "bootstrap_wallet_protected": "(wallet.dat は保護されています)",
"bootstrap_warning": "既存のブロックデータblocks、chainstate、notarizationsは削除され置き換えられます。wallet.dat は変更・削除されません。", "bootstrap_warning": "既存のブロックデータblocks、chainstate、notarizationsは削除され置き換えられます。wallet.dat は変更・削除されません。",
"byte_count_fmt": "%zu / %zu バイト",
"cancel": "キャンセル", "cancel": "キャンセル",
"change_pass_confirm": "新しいパスフレーズ(確認):", "change_pass_confirm": "新しいパスフレーズ(確認):",
"change_pass_current": "現在のパスフレーズ:", "change_pass_current": "現在のパスフレーズ:",
@@ -441,6 +445,7 @@
"daemon_update_latest": "最新:", "daemon_update_latest": "最新:",
"daemon_update_loading": "リリースを読み込み中…", "daemon_update_loading": "リリースを読み込み中…",
"daemon_update_now": "今すぐ更新", "daemon_update_now": "今すぐ更新",
"daemon_update_prompt_title": "ノードデーモンを更新しますか?",
"daemon_update_reinstall": "再インストール", "daemon_update_reinstall": "再インストール",
"daemon_update_restart_note": "新しいバージョンを実行するにはデーモンを再起動してください。", "daemon_update_restart_note": "新しいバージョンを実行するにはデーモンを再起動してください。",
"daemon_update_restart_now": "今すぐデーモンを再起動", "daemon_update_restart_now": "今すぐデーモンを再起動",
@@ -657,6 +662,7 @@
"light": "ライト", "light": "ライト",
"lite_account_label": "アカウント", "lite_account_label": "アカウント",
"lite_action": "アクション", "lite_action": "アクション",
"lite_backend_unavailable": "ライトウォレットのバックエンドが利用できません",
"lite_backup_keys": "バックアップと鍵", "lite_backup_keys": "バックアップと鍵",
"lite_birthday_backup": "誕生日:%llu (これもバックアップしてください)", "lite_birthday_backup": "誕生日:%llu (これもバックアップしてください)",
"lite_birthday_hint": "スキャンを開始するブロック高。不明な場合は0のままにしてください完全スキャンが遅くなります。", "lite_birthday_hint": "スキャンを開始するブロック高。不明な場合は0のままにしてください完全スキャンが遅くなります。",
@@ -664,9 +670,12 @@
"lite_console_backend_commands": "バックエンドコマンド:", "lite_console_backend_commands": "バックエンドコマンド:",
"lite_console_help_passthrough": "その他の入力はライトウォレットのコンソールコマンドとして実行されます。", "lite_console_help_passthrough": "その他の入力はライトウォレットのコンソールコマンドとして実行されます。",
"lite_copy": "コピー", "lite_copy": "コピー",
"lite_could_not_start": "操作を開始できませんでした",
"lite_could_not_write": "書き込めませんでした ", "lite_could_not_write": "書き込めませんでした ",
"lite_encrypt_wallet": "ウォレットを暗号化", "lite_encrypt_wallet": "ウォレットを暗号化",
"lite_encryption_removed": "暗号化を解除しました", "lite_encryption_removed": "暗号化を解除しました",
"lite_enter_all_seed_words": "復元するには24個のシードワードをすべて入力してください現在 %d 個)",
"lite_enter_wallet_path": "ウォレットのパスを入力してください",
"lite_hide_wipe": "非表示にして消去", "lite_hide_wipe": "非表示にして消去",
"lite_import": "インポート", "lite_import": "インポート",
"lite_import_key_label": "鍵をインポート", "lite_import_key_label": "鍵をインポート",
@@ -920,6 +929,7 @@
"mining_pool": "プール", "mining_pool": "プール",
"mining_pool_fee": "手数料", "mining_pool_fee": "手数料",
"mining_pool_hashrate": "プールハッシュレート", "mining_pool_hashrate": "プールハッシュレート",
"mining_pool_needs_payout_tooltip": "先に支払い先アドレスを入力してくださいZアドレスを生成",
"mining_pool_url": "プールURL", "mining_pool_url": "プールURL",
"mining_pools_header": "プール", "mining_pools_header": "プール",
"mining_recent_blocks": "最近のブロック", "mining_recent_blocks": "最近のブロック",
@@ -1121,6 +1131,8 @@
"qr_failed": "QRコードの生成に失敗しました", "qr_failed": "QRコードの生成に失敗しました",
"qr_title": "QRコード", "qr_title": "QRコード",
"qr_unavailable": "QR利用不可", "qr_unavailable": "QR利用不可",
"quick_receive": "クイック受取",
"quick_send": "クイック送金",
"ram_daemon_gb": "デーモン:%.1f GB (%s)", "ram_daemon_gb": "デーモン:%.1f GB (%s)",
"ram_daemon_mb": "デーモン:%.0f MB (%s)", "ram_daemon_mb": "デーモン:%.0f MB (%s)",
"ram_system_gb": "システム:%.1f / %.0f GB", "ram_system_gb": "システム:%.1f / %.0f GB",
@@ -1170,6 +1182,7 @@
"rpc_connection": "RPC接続...", "rpc_connection": "RPC接続...",
"rpc_host": "RPCホスト", "rpc_host": "RPCホスト",
"rpc_pass": "パスワード", "rpc_pass": "パスワード",
"rpc_plaintext_remote_warning": "リモートRPCは暗号化されていないHTTPを使用しています。デーモンがTLSに対応している場合は、DRAGONX.confにrpctls=1を追加してください。",
"rpc_port": "ポート", "rpc_port": "ポート",
"rpc_user": "ユーザー名", "rpc_user": "ユーザー名",
"save": "保存", "save": "保存",
@@ -1276,6 +1289,7 @@
"send_tooltip_not_connected": "デーモンに未接続", "send_tooltip_not_connected": "デーモンに未接続",
"send_tooltip_select_source": "まず送信元アドレスを選択してください", "send_tooltip_select_source": "まず送信元アドレスを選択してください",
"send_tooltip_syncing": "ブロックチェーンの同期をお待ちください", "send_tooltip_syncing": "ブロックチェーンの同期をお待ちください",
"send_tooltip_view_only": "閲覧専用アドレス — 送金鍵がないため送金できません",
"send_total": "合計", "send_total": "合計",
"send_transaction": "取引を送信", "send_transaction": "取引を送信",
"send_tx_failed": "取引に失敗しました", "send_tx_failed": "取引に失敗しました",
@@ -1339,6 +1353,7 @@
"settings_locked": "ロック済み", "settings_locked": "ロック済み",
"settings_merge_to_address": "アドレスにマージ...", "settings_merge_to_address": "アドレスにマージ...",
"settings_noise_opacity": "ノイズ不透明度:", "settings_noise_opacity": "ノイズ不透明度:",
"settings_not_connected": "デーモンに接続されていません",
"settings_not_encrypted": "暗号化されていません", "settings_not_encrypted": "暗号化されていません",
"settings_not_found": "見つかりません", "settings_not_found": "見つかりません",
"settings_open_app_dir": "アプリフォルダを開く", "settings_open_app_dir": "アプリフォルダを開く",
@@ -1349,18 +1364,23 @@
"settings_privacy": "プライバシー", "settings_privacy": "プライバシー",
"settings_quick_unlock_pin": "クイックアンロック PIN", "settings_quick_unlock_pin": "クイックアンロック PIN",
"settings_reduce_transparency": "透明度を下げる", "settings_reduce_transparency": "透明度を下げる",
"settings_reloaded": "ディスクから設定を再読み込みしました",
"settings_remove_encryption": "暗号化を解除", "settings_remove_encryption": "暗号化を解除",
"settings_remove_pin": "PIN を削除", "settings_remove_pin": "PIN を削除",
"settings_request_payment": "支払い請求...", "settings_request_payment": "支払い請求...",
"settings_rescan_desc": "欠落したトランザクションのためにブロックチェーンを再スキャン", "settings_rescan_desc": "欠落したトランザクションのためにブロックチェーンを再スキャン",
"settings_restart_daemon": "デーモンを再起動", "settings_restart_daemon": "デーモンを再起動",
"settings_rpc_connection": "RPC 接続", "settings_rpc_connection": "RPC 接続",
"settings_rpc_error_prefix": "RPCエラー: ",
"settings_rpc_note": "注意:接続設定は通常 DRAGONX.conf から自動検出されます", "settings_rpc_note": "注意:接続設定は通常 DRAGONX.conf から自動検出されます",
"settings_rpc_ok": "RPC接続は正常です",
"settings_save_shielded_desc": "z-addr トランザクションをローカルファイルに保存して表示", "settings_save_shielded_desc": "z-addr トランザクションをローカルファイルに保存して表示",
"settings_save_shielded_local": "シールドトランザクション履歴をローカルに保存", "settings_save_shielded_local": "シールドトランザクション履歴をローカルに保存",
"settings_saved": "設定を保存しました",
"settings_set_pin": "PIN を設定", "settings_set_pin": "PIN を設定",
"settings_shield_mining": "マイニングシールド...", "settings_shield_mining": "マイニングシールド...",
"settings_solid_colors_desc": "ぼかし効果の代わりに単色を使用(アクセシビリティ)", "settings_solid_colors_desc": "ぼかし効果の代わりに単色を使用(アクセシビリティ)",
"settings_theme_refreshed": "テーマ一覧を更新しました",
"settings_tor_desc": "プライバシー向上のため全接続を Tor 経由にする", "settings_tor_desc": "プライバシー向上のため全接続を Tor 経由にする",
"settings_unlocked": "ロック解除", "settings_unlocked": "ロック解除",
"settings_use_tor_network": "ネットワーク接続に Tor を使用", "settings_use_tor_network": "ネットワーク接続に Tor を使用",
@@ -1372,6 +1392,8 @@
"settings_wallet_maintenance": "ウォレットメンテナンス", "settings_wallet_maintenance": "ウォレットメンテナンス",
"settings_wallet_not_found": "ウォレットファイルが見つかりません", "settings_wallet_not_found": "ウォレットファイルが見つかりません",
"settings_wallet_size_label": "ウォレットサイズ:", "settings_wallet_size_label": "ウォレットサイズ:",
"settings_ztx_cleared": "Zトランザクション履歴を消去しました",
"settings_ztx_not_found": "履歴ファイルが見つかりません",
"setup_wizard": "セットアップウィザード", "setup_wizard": "セットアップウィザード",
"share": "共有", "share": "共有",
"shield_check_status": "ステータスを確認", "shield_check_status": "ステータスを確認",
@@ -1454,6 +1476,7 @@
"theme": "テーマ", "theme": "テーマ",
"theme_effects": "テーマ効果", "theme_effects": "テーマ効果",
"theme_language": "テーマと言語", "theme_language": "テーマと言語",
"tile_click_to_open": "クリックして開く",
"time_days_ago": "%d日前", "time_days_ago": "%d日前",
"time_hours_ago": "%d時間前", "time_hours_ago": "%d時間前",
"time_minutes_ago": "%d分前", "time_minutes_ago": "%d分前",
@@ -1469,6 +1492,7 @@
"tools": "ツール", "tools": "ツール",
"tools_actions": "ツールとアクション...", "tools_actions": "ツールとアクション...",
"total": "合計", "total": "合計",
"total_balance_label": "総残高",
"transaction_id": "取引ID", "transaction_id": "取引ID",
"transaction_sent": "取引の送信に成功しました", "transaction_sent": "取引の送信に成功しました",
"transaction_sent_msg": "取引を送信しました!", "transaction_sent_msg": "取引を送信しました!",

View File

@@ -92,7 +92,10 @@
"balance": "잔액", "balance": "잔액",
"balance_history_collecting": "잔액 내역 — 데이터 수집 중...", "balance_history_collecting": "잔액 내역 — 데이터 수집 중...",
"balance_layout": "잔액 레이아웃", "balance_layout": "잔액 레이아웃",
"balance_layout_switched": "레이아웃: %s",
"balance_mining_rate": "채굴 중 %s",
"balance_shielded_fmt": "차폐: %.8f", "balance_shielded_fmt": "차폐: %.8f",
"balance_syncing_pct": "동기화 중 %.1f%%",
"balance_transparent_fmt": "투명: %.8f", "balance_transparent_fmt": "투명: %.8f",
"ban": "차단", "ban": "차단",
"banned_peers": "차단된 피어", "banned_peers": "차단된 피어",
@@ -132,6 +135,7 @@
"bootstrap_verifying": "체크섬 확인 중...", "bootstrap_verifying": "체크섬 확인 중...",
"bootstrap_wallet_protected": "(wallet.dat 보호됨)", "bootstrap_wallet_protected": "(wallet.dat 보호됨)",
"bootstrap_warning": "기존 블록 데이터(blocks, chainstate, notarizations)가 삭제되고 교체됩니다. wallet.dat는 수정되거나 삭제되지 않습니다.", "bootstrap_warning": "기존 블록 데이터(blocks, chainstate, notarizations)가 삭제되고 교체됩니다. wallet.dat는 수정되거나 삭제되지 않습니다.",
"byte_count_fmt": "%zu / %zu 바이트",
"cancel": "취소", "cancel": "취소",
"change_pass_confirm": "새 암호 확인:", "change_pass_confirm": "새 암호 확인:",
"change_pass_current": "현재 암호:", "change_pass_current": "현재 암호:",
@@ -441,6 +445,7 @@
"daemon_update_latest": "최신:", "daemon_update_latest": "최신:",
"daemon_update_loading": "릴리스 불러오는 중…", "daemon_update_loading": "릴리스 불러오는 중…",
"daemon_update_now": "지금 업데이트", "daemon_update_now": "지금 업데이트",
"daemon_update_prompt_title": "노드 데몬을 업데이트하시겠습니까?",
"daemon_update_reinstall": "다시 설치", "daemon_update_reinstall": "다시 설치",
"daemon_update_restart_note": "새 버전을 실행하려면 데몬을 재시작하세요.", "daemon_update_restart_note": "새 버전을 실행하려면 데몬을 재시작하세요.",
"daemon_update_restart_now": "지금 데몬 재시작", "daemon_update_restart_now": "지금 데몬 재시작",
@@ -657,6 +662,7 @@
"light": "라이트", "light": "라이트",
"lite_account_label": "계정", "lite_account_label": "계정",
"lite_action": "작업", "lite_action": "작업",
"lite_backend_unavailable": "라이트 지갑 백엔드를 사용할 수 없습니다",
"lite_backup_keys": "백업 및 키", "lite_backup_keys": "백업 및 키",
"lite_birthday_backup": "생성 블록: %llu (이 값도 백업하세요)", "lite_birthday_backup": "생성 블록: %llu (이 값도 백업하세요)",
"lite_birthday_hint": "스캔을 시작할 블록 높이입니다. 모르면 0으로 두세요(전체 스캔이 느려짐).", "lite_birthday_hint": "스캔을 시작할 블록 높이입니다. 모르면 0으로 두세요(전체 스캔이 느려짐).",
@@ -664,9 +670,12 @@
"lite_console_backend_commands": "백엔드 명령:", "lite_console_backend_commands": "백엔드 명령:",
"lite_console_help_passthrough": "그 외 입력은 라이트 지갑 콘솔 명령으로 실행됩니다.", "lite_console_help_passthrough": "그 외 입력은 라이트 지갑 콘솔 명령으로 실행됩니다.",
"lite_copy": "복사", "lite_copy": "복사",
"lite_could_not_start": "작업을 시작할 수 없습니다",
"lite_could_not_write": "쓸 수 없습니다: ", "lite_could_not_write": "쓸 수 없습니다: ",
"lite_encrypt_wallet": "지갑 암호화", "lite_encrypt_wallet": "지갑 암호화",
"lite_encryption_removed": "암호화가 제거되었습니다", "lite_encryption_removed": "암호화가 제거되었습니다",
"lite_enter_all_seed_words": "복구하려면 24개의 시드 단어를 모두 입력하세요 (현재 %d개)",
"lite_enter_wallet_path": "지갑 경로를 입력하세요",
"lite_hide_wipe": "숨기고 삭제", "lite_hide_wipe": "숨기고 삭제",
"lite_import": "가져오기", "lite_import": "가져오기",
"lite_import_key_label": "키 가져오기", "lite_import_key_label": "키 가져오기",
@@ -919,6 +928,7 @@
"mining_pool": "풀", "mining_pool": "풀",
"mining_pool_fee": "수수료", "mining_pool_fee": "수수료",
"mining_pool_hashrate": "풀 해시레이트", "mining_pool_hashrate": "풀 해시레이트",
"mining_pool_needs_payout_tooltip": "먼저 지급 주소를 입력하세요 (Z 주소를 생성하세요)",
"mining_pool_url": "풀 URL", "mining_pool_url": "풀 URL",
"mining_pools_header": "풀", "mining_pools_header": "풀",
"mining_recent_blocks": "최근 블록", "mining_recent_blocks": "최근 블록",
@@ -1120,6 +1130,8 @@
"qr_failed": "QR 코드 생성 실패", "qr_failed": "QR 코드 생성 실패",
"qr_title": "QR 코드", "qr_title": "QR 코드",
"qr_unavailable": "QR 사용 불가", "qr_unavailable": "QR 사용 불가",
"quick_receive": "빠른 받기",
"quick_send": "빠른 보내기",
"ram_daemon_gb": "데몬: %.1f GB (%s)", "ram_daemon_gb": "데몬: %.1f GB (%s)",
"ram_daemon_mb": "데몬: %.0f MB (%s)", "ram_daemon_mb": "데몬: %.0f MB (%s)",
"ram_system_gb": "시스템: %.1f / %.0f GB", "ram_system_gb": "시스템: %.1f / %.0f GB",
@@ -1169,6 +1181,7 @@
"rpc_connection": "RPC 연결...", "rpc_connection": "RPC 연결...",
"rpc_host": "RPC 호스트", "rpc_host": "RPC 호스트",
"rpc_pass": "비밀번호", "rpc_pass": "비밀번호",
"rpc_plaintext_remote_warning": "원격 RPC가 암호화되지 않은 HTTP를 사용하고 있습니다. 데몬이 TLS를 지원하면 DRAGONX.conf에 rpctls=1을 추가하세요.",
"rpc_port": "포트", "rpc_port": "포트",
"rpc_user": "사용자명", "rpc_user": "사용자명",
"save": "저장", "save": "저장",
@@ -1278,6 +1291,7 @@
"send_tooltip_not_connected": "데몬에 연결되지 않음", "send_tooltip_not_connected": "데몬에 연결되지 않음",
"send_tooltip_select_source": "먼저 보낼 주소를 선택하세요", "send_tooltip_select_source": "먼저 보낼 주소를 선택하세요",
"send_tooltip_syncing": "블록체인 동기화를 기다려 주세요", "send_tooltip_syncing": "블록체인 동기화를 기다려 주세요",
"send_tooltip_view_only": "조회 전용 주소 — 지출 키가 없어 보낼 수 없습니다",
"send_total": "합계", "send_total": "합계",
"send_transaction": "거래 전송", "send_transaction": "거래 전송",
"send_tx_failed": "거래 실패", "send_tx_failed": "거래 실패",
@@ -1341,6 +1355,7 @@
"settings_locked": "잠김", "settings_locked": "잠김",
"settings_merge_to_address": "주소로 병합...", "settings_merge_to_address": "주소로 병합...",
"settings_noise_opacity": "노이즈 불투명도:", "settings_noise_opacity": "노이즈 불투명도:",
"settings_not_connected": "데몬에 연결되지 않음",
"settings_not_encrypted": "암호화되지 않음", "settings_not_encrypted": "암호화되지 않음",
"settings_not_found": "찾을 수 없음", "settings_not_found": "찾을 수 없음",
"settings_open_app_dir": "앱 폴더 열기", "settings_open_app_dir": "앱 폴더 열기",
@@ -1351,18 +1366,23 @@
"settings_privacy": "개인 정보", "settings_privacy": "개인 정보",
"settings_quick_unlock_pin": "빠른 잠금 해제 PIN", "settings_quick_unlock_pin": "빠른 잠금 해제 PIN",
"settings_reduce_transparency": "투명도 줄이기", "settings_reduce_transparency": "투명도 줄이기",
"settings_reloaded": "디스크에서 설정을 다시 불러왔습니다",
"settings_remove_encryption": "암호화 제거", "settings_remove_encryption": "암호화 제거",
"settings_remove_pin": "PIN 제거", "settings_remove_pin": "PIN 제거",
"settings_request_payment": "결제 요청...", "settings_request_payment": "결제 요청...",
"settings_rescan_desc": "누락된 거래를 찾기 위해 블록체인 재스캔", "settings_rescan_desc": "누락된 거래를 찾기 위해 블록체인 재스캔",
"settings_restart_daemon": "데몬 재시작", "settings_restart_daemon": "데몬 재시작",
"settings_rpc_connection": "RPC 연결", "settings_rpc_connection": "RPC 연결",
"settings_rpc_error_prefix": "RPC 오류: ",
"settings_rpc_note": "참고: 연결 설정은 보통 DRAGONX.conf에서 자동 감지됩니다", "settings_rpc_note": "참고: 연결 설정은 보통 DRAGONX.conf에서 자동 감지됩니다",
"settings_rpc_ok": "RPC 연결 정상",
"settings_save_shielded_desc": "z-addr 거래를 로컬 파일에 저장하여 조회", "settings_save_shielded_desc": "z-addr 거래를 로컬 파일에 저장하여 조회",
"settings_save_shielded_local": "차폐 거래 기록을 로컬에 저장", "settings_save_shielded_local": "차폐 거래 기록을 로컬에 저장",
"settings_saved": "설정이 저장되었습니다",
"settings_set_pin": "PIN 설정", "settings_set_pin": "PIN 설정",
"settings_shield_mining": "채굴 차폐...", "settings_shield_mining": "채굴 차폐...",
"settings_solid_colors_desc": "블러 효과 대신 단색 사용 (접근성)", "settings_solid_colors_desc": "블러 효과 대신 단색 사용 (접근성)",
"settings_theme_refreshed": "테마 목록을 새로고침했습니다",
"settings_tor_desc": "향상된 개인 정보 보호를 위해 모든 연결을 Tor를 통해 라우팅", "settings_tor_desc": "향상된 개인 정보 보호를 위해 모든 연결을 Tor를 통해 라우팅",
"settings_unlocked": "잠금 해제", "settings_unlocked": "잠금 해제",
"settings_use_tor_network": "네트워크 연결에 Tor 사용", "settings_use_tor_network": "네트워크 연결에 Tor 사용",
@@ -1374,6 +1394,8 @@
"settings_wallet_maintenance": "지갑 유지보수", "settings_wallet_maintenance": "지갑 유지보수",
"settings_wallet_not_found": "지갑 파일을 찾을 수 없음", "settings_wallet_not_found": "지갑 파일을 찾을 수 없음",
"settings_wallet_size_label": "지갑 크기:", "settings_wallet_size_label": "지갑 크기:",
"settings_ztx_cleared": "Z-거래 내역이 삭제되었습니다",
"settings_ztx_not_found": "내역 파일을 찾을 수 없습니다",
"setup_wizard": "설정 마법사", "setup_wizard": "설정 마법사",
"share": "공유", "share": "공유",
"shield_check_status": "상태 확인", "shield_check_status": "상태 확인",
@@ -1456,6 +1478,7 @@
"theme": "테마", "theme": "테마",
"theme_effects": "테마 효과", "theme_effects": "테마 효과",
"theme_language": "테마 및 언어", "theme_language": "테마 및 언어",
"tile_click_to_open": "클릭하여 열기",
"time_days_ago": "%d일 전", "time_days_ago": "%d일 전",
"time_hours_ago": "%d시간 전", "time_hours_ago": "%d시간 전",
"time_minutes_ago": "%d분 전", "time_minutes_ago": "%d분 전",
@@ -1471,6 +1494,7 @@
"tools": "도구", "tools": "도구",
"tools_actions": "도구 및 작업...", "tools_actions": "도구 및 작업...",
"total": "합계", "total": "합계",
"total_balance_label": "총 잔액",
"transaction_id": "거래 ID", "transaction_id": "거래 ID",
"transaction_sent": "거래 전송 성공", "transaction_sent": "거래 전송 성공",
"transaction_sent_msg": "거래가 전송되었습니다!", "transaction_sent_msg": "거래가 전송되었습니다!",

View File

@@ -92,7 +92,10 @@
"balance": "Saldo", "balance": "Saldo",
"balance_history_collecting": "Histórico de saldo — coletando dados...", "balance_history_collecting": "Histórico de saldo — coletando dados...",
"balance_layout": "Layout do Saldo", "balance_layout": "Layout do Saldo",
"balance_layout_switched": "Layout: %s",
"balance_mining_rate": "Minerando %s",
"balance_shielded_fmt": "Blindado: %.8f", "balance_shielded_fmt": "Blindado: %.8f",
"balance_syncing_pct": "Sincronizando %.1f%%",
"balance_transparent_fmt": "Transparente: %.8f", "balance_transparent_fmt": "Transparente: %.8f",
"ban": "Banir", "ban": "Banir",
"banned_peers": "Pares Banidos", "banned_peers": "Pares Banidos",
@@ -132,6 +135,7 @@
"bootstrap_verifying": "Verificando somas de verificação...", "bootstrap_verifying": "Verificando somas de verificação...",
"bootstrap_wallet_protected": "(wallet.dat está protegido)", "bootstrap_wallet_protected": "(wallet.dat está protegido)",
"bootstrap_warning": "Os dados de blocos existentes (blocks, chainstate, notarizations) serão excluídos e substituídos. Seu wallet.dat NÃO será modificado ou excluído.", "bootstrap_warning": "Os dados de blocos existentes (blocks, chainstate, notarizations) serão excluídos e substituídos. Seu wallet.dat NÃO será modificado ou excluído.",
"byte_count_fmt": "%zu / %zu bytes",
"cancel": "Cancelar", "cancel": "Cancelar",
"change_pass_confirm": "Confirmar nova:", "change_pass_confirm": "Confirmar nova:",
"change_pass_current": "Senha atual:", "change_pass_current": "Senha atual:",
@@ -441,6 +445,7 @@
"daemon_update_latest": "Mais recente:", "daemon_update_latest": "Mais recente:",
"daemon_update_loading": "Carregando versões…", "daemon_update_loading": "Carregando versões…",
"daemon_update_now": "Atualizar agora", "daemon_update_now": "Atualizar agora",
"daemon_update_prompt_title": "Atualizar o daemon do node?",
"daemon_update_reinstall": "Reinstalar", "daemon_update_reinstall": "Reinstalar",
"daemon_update_restart_note": "Reinicie o daemon para começar a executar a nova versão.", "daemon_update_restart_note": "Reinicie o daemon para começar a executar a nova versão.",
"daemon_update_restart_now": "Reiniciar daemon agora", "daemon_update_restart_now": "Reiniciar daemon agora",
@@ -657,6 +662,7 @@
"light": "Claro", "light": "Claro",
"lite_account_label": "Conta", "lite_account_label": "Conta",
"lite_action": "Ação", "lite_action": "Ação",
"lite_backend_unavailable": "Backend da carteira lite indisponível",
"lite_backup_keys": "Backup e chaves", "lite_backup_keys": "Backup e chaves",
"lite_birthday_backup": "Aniversário: %llu (faça o backup disto também)", "lite_birthday_backup": "Aniversário: %llu (faça o backup disto também)",
"lite_birthday_hint": "Altura do bloco a partir da qual começar a escanear. Deixe 0 se desconhecida (escaneamento completo mais lento).", "lite_birthday_hint": "Altura do bloco a partir da qual começar a escanear. Deixe 0 se desconhecida (escaneamento completo mais lento).",
@@ -664,9 +670,12 @@
"lite_console_backend_commands": "Comandos do backend:", "lite_console_backend_commands": "Comandos do backend:",
"lite_console_help_passthrough": "Qualquer outra entrada é executada como um comando de console da carteira leve.", "lite_console_help_passthrough": "Qualquer outra entrada é executada como um comando de console da carteira leve.",
"lite_copy": "Copiar", "lite_copy": "Copiar",
"lite_could_not_start": "Não foi possível iniciar a operação",
"lite_could_not_write": "Não foi possível gravar ", "lite_could_not_write": "Não foi possível gravar ",
"lite_encrypt_wallet": "Criptografar carteira", "lite_encrypt_wallet": "Criptografar carteira",
"lite_encryption_removed": "Criptografia removida", "lite_encryption_removed": "Criptografia removida",
"lite_enter_all_seed_words": "Informe todas as 24 palavras-semente para restaurar (obtidas %d)",
"lite_enter_wallet_path": "Informe um caminho para a carteira",
"lite_hide_wipe": "Ocultar e apagar", "lite_hide_wipe": "Ocultar e apagar",
"lite_import": "Importar", "lite_import": "Importar",
"lite_import_key_label": "Importar chave", "lite_import_key_label": "Importar chave",
@@ -920,6 +929,7 @@
"mining_pool": "Pool", "mining_pool": "Pool",
"mining_pool_fee": "Taxa", "mining_pool_fee": "Taxa",
"mining_pool_hashrate": "Hashrate do Pool", "mining_pool_hashrate": "Hashrate do Pool",
"mining_pool_needs_payout_tooltip": "Informe primeiro um endereço de pagamento (gere um endereço Z)",
"mining_pool_url": "URL do Pool", "mining_pool_url": "URL do Pool",
"mining_pools_header": "POOLS", "mining_pools_header": "POOLS",
"mining_recent_blocks": "BLOCOS RECENTES", "mining_recent_blocks": "BLOCOS RECENTES",
@@ -1121,6 +1131,8 @@
"qr_failed": "Falha ao gerar código QR", "qr_failed": "Falha ao gerar código QR",
"qr_title": "Código QR", "qr_title": "Código QR",
"qr_unavailable": "QR indisponível", "qr_unavailable": "QR indisponível",
"quick_receive": "Recebimento rápido",
"quick_send": "Envio rápido",
"ram_daemon_gb": "Daemon: %.1f GB (%s)", "ram_daemon_gb": "Daemon: %.1f GB (%s)",
"ram_daemon_mb": "Daemon: %.0f MB (%s)", "ram_daemon_mb": "Daemon: %.0f MB (%s)",
"ram_system_gb": "Sistema: %.1f / %.0f GB", "ram_system_gb": "Sistema: %.1f / %.0f GB",
@@ -1170,6 +1182,7 @@
"rpc_connection": "Conexão RPC...", "rpc_connection": "Conexão RPC...",
"rpc_host": "Host RPC", "rpc_host": "Host RPC",
"rpc_pass": "Senha", "rpc_pass": "Senha",
"rpc_plaintext_remote_warning": "O RPC remoto está usando HTTP em texto simples. Adicione rpctls=1 ao DRAGONX.conf se o seu daemon suportar TLS.",
"rpc_port": "Porta", "rpc_port": "Porta",
"rpc_user": "Usuário", "rpc_user": "Usuário",
"save": "Salvar", "save": "Salvar",
@@ -1279,6 +1292,7 @@
"send_tooltip_not_connected": "Não conectado ao daemon", "send_tooltip_not_connected": "Não conectado ao daemon",
"send_tooltip_select_source": "Selecione primeiro um endereço de origem", "send_tooltip_select_source": "Selecione primeiro um endereço de origem",
"send_tooltip_syncing": "Aguarde a sincronização da blockchain", "send_tooltip_syncing": "Aguarde a sincronização da blockchain",
"send_tooltip_view_only": "Endereço somente visualização — sem chave de gasto, não é possível enviar",
"send_total": "Total", "send_total": "Total",
"send_transaction": "Enviar Transação", "send_transaction": "Enviar Transação",
"send_tx_failed": "Transação falhou", "send_tx_failed": "Transação falhou",
@@ -1342,6 +1356,7 @@
"settings_locked": "Bloqueado", "settings_locked": "Bloqueado",
"settings_merge_to_address": "Fundir para endereço...", "settings_merge_to_address": "Fundir para endereço...",
"settings_noise_opacity": "Opacidade do ruído:", "settings_noise_opacity": "Opacidade do ruído:",
"settings_not_connected": "Não conectado ao daemon",
"settings_not_encrypted": "Não encriptado", "settings_not_encrypted": "Não encriptado",
"settings_not_found": "Não encontrado", "settings_not_found": "Não encontrado",
"settings_open_app_dir": "Abrir pasta do aplicativo", "settings_open_app_dir": "Abrir pasta do aplicativo",
@@ -1352,18 +1367,23 @@
"settings_privacy": "Privacidade", "settings_privacy": "Privacidade",
"settings_quick_unlock_pin": "PIN de desbloqueio rápido", "settings_quick_unlock_pin": "PIN de desbloqueio rápido",
"settings_reduce_transparency": "Reduzir transparência", "settings_reduce_transparency": "Reduzir transparência",
"settings_reloaded": "Configurações recarregadas do disco",
"settings_remove_encryption": "Remover encriptação", "settings_remove_encryption": "Remover encriptação",
"settings_remove_pin": "Remover PIN", "settings_remove_pin": "Remover PIN",
"settings_request_payment": "Solicitar pagamento...", "settings_request_payment": "Solicitar pagamento...",
"settings_rescan_desc": "Reescanear a blockchain em busca de transações ausentes", "settings_rescan_desc": "Reescanear a blockchain em busca de transações ausentes",
"settings_restart_daemon": "Reiniciar daemon", "settings_restart_daemon": "Reiniciar daemon",
"settings_rpc_connection": "Conexão RPC", "settings_rpc_connection": "Conexão RPC",
"settings_rpc_error_prefix": "Erro de RPC: ",
"settings_rpc_note": "Nota: As configurações de conexão são normalmente detectadas automaticamente do DRAGONX.conf", "settings_rpc_note": "Nota: As configurações de conexão são normalmente detectadas automaticamente do DRAGONX.conf",
"settings_rpc_ok": "Conexão RPC OK",
"settings_save_shielded_desc": "Armazena transações z-addr em um arquivo local para visualização", "settings_save_shielded_desc": "Armazena transações z-addr em um arquivo local para visualização",
"settings_save_shielded_local": "Salvar histórico de transações blindadas localmente", "settings_save_shielded_local": "Salvar histórico de transações blindadas localmente",
"settings_saved": "Configurações salvas",
"settings_set_pin": "Definir PIN", "settings_set_pin": "Definir PIN",
"settings_shield_mining": "Blindar mineração...", "settings_shield_mining": "Blindar mineração...",
"settings_solid_colors_desc": "Usar cores sólidas em vez de efeitos de desfoque (acessibilidade)", "settings_solid_colors_desc": "Usar cores sólidas em vez de efeitos de desfoque (acessibilidade)",
"settings_theme_refreshed": "Lista de temas atualizada",
"settings_tor_desc": "Rotear todas as conexões através do Tor para maior privacidade", "settings_tor_desc": "Rotear todas as conexões através do Tor para maior privacidade",
"settings_unlocked": "Desbloqueado", "settings_unlocked": "Desbloqueado",
"settings_use_tor_network": "Usar Tor para conexões de rede", "settings_use_tor_network": "Usar Tor para conexões de rede",
@@ -1375,6 +1395,8 @@
"settings_wallet_maintenance": "Manutenção da carteira", "settings_wallet_maintenance": "Manutenção da carteira",
"settings_wallet_not_found": "Arquivo da carteira não encontrado", "settings_wallet_not_found": "Arquivo da carteira não encontrado",
"settings_wallet_size_label": "Tamanho da carteira:", "settings_wallet_size_label": "Tamanho da carteira:",
"settings_ztx_cleared": "Histórico de transações Z limpo",
"settings_ztx_not_found": "Nenhum arquivo de histórico encontrado",
"setup_wizard": "Assistente de Configuração", "setup_wizard": "Assistente de Configuração",
"share": "Compartilhar", "share": "Compartilhar",
"shield_check_status": "Verificar Status", "shield_check_status": "Verificar Status",
@@ -1457,6 +1479,7 @@
"theme": "Tema", "theme": "Tema",
"theme_effects": "Efeitos de tema", "theme_effects": "Efeitos de tema",
"theme_language": "TEMA E IDIOMA", "theme_language": "TEMA E IDIOMA",
"tile_click_to_open": "Clique para abrir",
"time_days_ago": "há %d dias", "time_days_ago": "há %d dias",
"time_hours_ago": "há %d horas", "time_hours_ago": "há %d horas",
"time_minutes_ago": "há %d minutos", "time_minutes_ago": "há %d minutos",
@@ -1472,6 +1495,7 @@
"tools": "FERRAMENTAS", "tools": "FERRAMENTAS",
"tools_actions": "Ferramentas e Ações...", "tools_actions": "Ferramentas e Ações...",
"total": "Total", "total": "Total",
"total_balance_label": "Saldo Total",
"transaction_id": "ID DA TRANSAÇÃO", "transaction_id": "ID DA TRANSAÇÃO",
"transaction_sent": "Transação enviada com sucesso", "transaction_sent": "Transação enviada com sucesso",
"transaction_sent_msg": "Transação enviada!", "transaction_sent_msg": "Transação enviada!",

View File

@@ -92,7 +92,10 @@
"balance": "Баланс", "balance": "Баланс",
"balance_history_collecting": "История баланса — сбор данных...", "balance_history_collecting": "История баланса — сбор данных...",
"balance_layout": "Макет баланса", "balance_layout": "Макет баланса",
"balance_layout_switched": "Раскладка: %s",
"balance_mining_rate": "Майнинг %s",
"balance_shielded_fmt": "Экранировано: %.8f", "balance_shielded_fmt": "Экранировано: %.8f",
"balance_syncing_pct": "Синхронизация %.1f%%",
"balance_transparent_fmt": "Прозрачный: %.8f", "balance_transparent_fmt": "Прозрачный: %.8f",
"ban": "Заблокировать", "ban": "Заблокировать",
"banned_peers": "Заблокированные узлы", "banned_peers": "Заблокированные узлы",
@@ -132,6 +135,7 @@
"bootstrap_verifying": "Проверка контрольных сумм...", "bootstrap_verifying": "Проверка контрольных сумм...",
"bootstrap_wallet_protected": "(wallet.dat защищён)", "bootstrap_wallet_protected": "(wallet.dat защищён)",
"bootstrap_warning": "Существующие данные блоков (blocks, chainstate, notarizations) будут удалены и заменены. Ваш wallet.dat НЕ будет изменён или удалён.", "bootstrap_warning": "Существующие данные блоков (blocks, chainstate, notarizations) будут удалены и заменены. Ваш wallet.dat НЕ будет изменён или удалён.",
"byte_count_fmt": "%zu / %zu байт",
"cancel": "Отмена", "cancel": "Отмена",
"change_pass_confirm": "Подтвердите новый:", "change_pass_confirm": "Подтвердите новый:",
"change_pass_current": "Текущий пароль:", "change_pass_current": "Текущий пароль:",
@@ -441,6 +445,7 @@
"daemon_update_latest": "Последняя:", "daemon_update_latest": "Последняя:",
"daemon_update_loading": "Загрузка релизов…", "daemon_update_loading": "Загрузка релизов…",
"daemon_update_now": "Обновить сейчас", "daemon_update_now": "Обновить сейчас",
"daemon_update_prompt_title": "Обновить демон узла?",
"daemon_update_reinstall": "Переустановить", "daemon_update_reinstall": "Переустановить",
"daemon_update_restart_note": "Перезапустите daemon, чтобы начать работу с новой версией.", "daemon_update_restart_note": "Перезапустите daemon, чтобы начать работу с новой версией.",
"daemon_update_restart_now": "Перезапустить демон сейчас", "daemon_update_restart_now": "Перезапустить демон сейчас",
@@ -657,6 +662,7 @@
"light": "Светлая", "light": "Светлая",
"lite_account_label": "Аккаунт", "lite_account_label": "Аккаунт",
"lite_action": "Действие", "lite_action": "Действие",
"lite_backend_unavailable": "Бэкенд облегчённого кошелька недоступен",
"lite_backup_keys": "Резервная копия и ключи", "lite_backup_keys": "Резервная копия и ключи",
"lite_birthday_backup": "Дата рождения: %llu (сохраните её тоже)", "lite_birthday_backup": "Дата рождения: %llu (сохраните её тоже)",
"lite_birthday_hint": "Высота блока, с которой начинать сканирование. Оставьте 0, если неизвестно (медленное полное сканирование).", "lite_birthday_hint": "Высота блока, с которой начинать сканирование. Оставьте 0, если неизвестно (медленное полное сканирование).",
@@ -664,9 +670,12 @@
"lite_console_backend_commands": "Команды бэкенда:", "lite_console_backend_commands": "Команды бэкенда:",
"lite_console_help_passthrough": "Любой другой ввод выполняется как команда консоли лайт-кошелька.", "lite_console_help_passthrough": "Любой другой ввод выполняется как команда консоли лайт-кошелька.",
"lite_copy": "Копировать", "lite_copy": "Копировать",
"lite_could_not_start": "Не удалось запустить операцию",
"lite_could_not_write": "Не удалось записать ", "lite_could_not_write": "Не удалось записать ",
"lite_encrypt_wallet": "Зашифровать кошелёк", "lite_encrypt_wallet": "Зашифровать кошелёк",
"lite_encryption_removed": "Шифрование удалено", "lite_encryption_removed": "Шифрование удалено",
"lite_enter_all_seed_words": "Введите все 24 слова seed-фразы для восстановления (введено %d)",
"lite_enter_wallet_path": "Введите путь к кошельку",
"lite_hide_wipe": "Скрыть и стереть", "lite_hide_wipe": "Скрыть и стереть",
"lite_import": "Импорт", "lite_import": "Импорт",
"lite_import_key_label": "Импортировать ключ", "lite_import_key_label": "Импортировать ключ",
@@ -920,6 +929,7 @@
"mining_pool": "Пул", "mining_pool": "Пул",
"mining_pool_fee": "Комиссия", "mining_pool_fee": "Комиссия",
"mining_pool_hashrate": "Хешрейт пула", "mining_pool_hashrate": "Хешрейт пула",
"mining_pool_needs_payout_tooltip": "Сначала введите адрес для выплат (создайте Z-адрес)",
"mining_pool_url": "URL пула", "mining_pool_url": "URL пула",
"mining_pools_header": "ПУЛЫ", "mining_pools_header": "ПУЛЫ",
"mining_recent_blocks": "ПОСЛЕДНИЕ БЛОКИ", "mining_recent_blocks": "ПОСЛЕДНИЕ БЛОКИ",
@@ -1121,6 +1131,8 @@
"qr_failed": "Не удалось сгенерировать QR-код", "qr_failed": "Не удалось сгенерировать QR-код",
"qr_title": "QR-код", "qr_title": "QR-код",
"qr_unavailable": "QR недоступен", "qr_unavailable": "QR недоступен",
"quick_receive": "Быстрый приём",
"quick_send": "Быстрая отправка",
"ram_daemon_gb": "Демон: %.1f ГБ (%s)", "ram_daemon_gb": "Демон: %.1f ГБ (%s)",
"ram_daemon_mb": "Демон: %.0f МБ (%s)", "ram_daemon_mb": "Демон: %.0f МБ (%s)",
"ram_system_gb": "Система: %.1f / %.0f ГБ", "ram_system_gb": "Система: %.1f / %.0f ГБ",
@@ -1170,6 +1182,7 @@
"rpc_connection": "RPC-подключение...", "rpc_connection": "RPC-подключение...",
"rpc_host": "RPC-хост", "rpc_host": "RPC-хост",
"rpc_pass": "Пароль", "rpc_pass": "Пароль",
"rpc_plaintext_remote_warning": "Удалённый RPC использует незашифрованный HTTP. Добавьте rpctls=1 в DRAGONX.conf, если ваш демон поддерживает TLS.",
"rpc_port": "Порт", "rpc_port": "Порт",
"rpc_user": "Имя пользователя", "rpc_user": "Имя пользователя",
"save": "Сохранить", "save": "Сохранить",
@@ -1279,6 +1292,7 @@
"send_tooltip_not_connected": "Не подключено к daemon", "send_tooltip_not_connected": "Не подключено к daemon",
"send_tooltip_select_source": "Сначала выберите адрес-источник", "send_tooltip_select_source": "Сначала выберите адрес-источник",
"send_tooltip_syncing": "Дождитесь синхронизации блокчейна", "send_tooltip_syncing": "Дождитесь синхронизации блокчейна",
"send_tooltip_view_only": "Адрес только для просмотра — нет ключа расходования, отправка невозможна",
"send_total": "Итого", "send_total": "Итого",
"send_transaction": "Отправить транзакцию", "send_transaction": "Отправить транзакцию",
"send_tx_failed": "Транзакция не удалась", "send_tx_failed": "Транзакция не удалась",
@@ -1342,6 +1356,7 @@
"settings_locked": "Заблокирован", "settings_locked": "Заблокирован",
"settings_merge_to_address": "Объединить на адрес...", "settings_merge_to_address": "Объединить на адрес...",
"settings_noise_opacity": "Непрозрачность шума:", "settings_noise_opacity": "Непрозрачность шума:",
"settings_not_connected": "Нет соединения с демоном",
"settings_not_encrypted": "Не зашифрован", "settings_not_encrypted": "Не зашифрован",
"settings_not_found": "Не найден", "settings_not_found": "Не найден",
"settings_open_app_dir": "Открыть папку приложения", "settings_open_app_dir": "Открыть папку приложения",
@@ -1352,18 +1367,23 @@
"settings_privacy": "Конфиденциальность", "settings_privacy": "Конфиденциальность",
"settings_quick_unlock_pin": "Быстрый PIN-код разблокировки", "settings_quick_unlock_pin": "Быстрый PIN-код разблокировки",
"settings_reduce_transparency": "Уменьшить прозрачность", "settings_reduce_transparency": "Уменьшить прозрачность",
"settings_reloaded": "Настройки перезагружены с диска",
"settings_remove_encryption": "Удалить шифрование", "settings_remove_encryption": "Удалить шифрование",
"settings_remove_pin": "Удалить PIN", "settings_remove_pin": "Удалить PIN",
"settings_request_payment": "Запросить платёж...", "settings_request_payment": "Запросить платёж...",
"settings_rescan_desc": "Пересканировать блокчейн для поиска пропущенных транзакций", "settings_rescan_desc": "Пересканировать блокчейн для поиска пропущенных транзакций",
"settings_restart_daemon": "Перезапустить демон", "settings_restart_daemon": "Перезапустить демон",
"settings_rpc_connection": "RPC-соединение", "settings_rpc_connection": "RPC-соединение",
"settings_rpc_error_prefix": "Ошибка RPC: ",
"settings_rpc_note": "Примечание: Настройки подключения обычно определяются автоматически из DRAGONX.conf", "settings_rpc_note": "Примечание: Настройки подключения обычно определяются автоматически из DRAGONX.conf",
"settings_rpc_ok": "RPC-соединение в порядке",
"settings_save_shielded_desc": "Сохраняет z-addr транзакции в локальном файле для просмотра", "settings_save_shielded_desc": "Сохраняет z-addr транзакции в локальном файле для просмотра",
"settings_save_shielded_local": "Сохранять историю защищённых транзакций локально", "settings_save_shielded_local": "Сохранять историю защищённых транзакций локально",
"settings_saved": "Настройки сохранены",
"settings_set_pin": "Установить PIN", "settings_set_pin": "Установить PIN",
"settings_shield_mining": "Экранировать майнинг...", "settings_shield_mining": "Экранировать майнинг...",
"settings_solid_colors_desc": "Использовать сплошные цвета вместо эффектов размытия (доступность)", "settings_solid_colors_desc": "Использовать сплошные цвета вместо эффектов размытия (доступность)",
"settings_theme_refreshed": "Список тем обновлён",
"settings_tor_desc": "Маршрутизировать все соединения через Tor для повышения конфиденциальности", "settings_tor_desc": "Маршрутизировать все соединения через Tor для повышения конфиденциальности",
"settings_unlocked": "Разблокирован", "settings_unlocked": "Разблокирован",
"settings_use_tor_network": "Использовать Tor для сетевых подключений", "settings_use_tor_network": "Использовать Tor для сетевых подключений",
@@ -1375,6 +1395,8 @@
"settings_wallet_maintenance": "Обслуживание кошелька", "settings_wallet_maintenance": "Обслуживание кошелька",
"settings_wallet_not_found": "Файл кошелька не найден", "settings_wallet_not_found": "Файл кошелька не найден",
"settings_wallet_size_label": "Размер кошелька:", "settings_wallet_size_label": "Размер кошелька:",
"settings_ztx_cleared": "История Z-транзакций очищена",
"settings_ztx_not_found": "Файл истории не найден",
"setup_wizard": "Мастер настройки", "setup_wizard": "Мастер настройки",
"share": "Поделиться", "share": "Поделиться",
"shield_check_status": "Проверить статус", "shield_check_status": "Проверить статус",
@@ -1457,6 +1479,7 @@
"theme": "Тема", "theme": "Тема",
"theme_effects": "Эффекты темы", "theme_effects": "Эффекты темы",
"theme_language": "ТЕМА И ЯЗЫК", "theme_language": "ТЕМА И ЯЗЫК",
"tile_click_to_open": "Нажмите, чтобы открыть",
"time_days_ago": "%d дней назад", "time_days_ago": "%d дней назад",
"time_hours_ago": "%d часов назад", "time_hours_ago": "%d часов назад",
"time_minutes_ago": "%d минут назад", "time_minutes_ago": "%d минут назад",
@@ -1472,6 +1495,7 @@
"tools": "УТИЛИТЫ", "tools": "УТИЛИТЫ",
"tools_actions": "Инструменты и действия...", "tools_actions": "Инструменты и действия...",
"total": "Итого", "total": "Итого",
"total_balance_label": "Общий баланс",
"transaction_id": "ID ТРАНЗАКЦИИ", "transaction_id": "ID ТРАНЗАКЦИИ",
"transaction_sent": "Транзакция успешно отправлена", "transaction_sent": "Транзакция успешно отправлена",
"transaction_sent_msg": "Транзакция отправлена!", "transaction_sent_msg": "Транзакция отправлена!",

View File

@@ -92,7 +92,10 @@
"balance": "余额", "balance": "余额",
"balance_history_collecting": "余额历史——正在收集数据…", "balance_history_collecting": "余额历史——正在收集数据…",
"balance_layout": "余额布局", "balance_layout": "余额布局",
"balance_layout_switched": "布局:%s",
"balance_mining_rate": "挖矿中 %s",
"balance_shielded_fmt": "屏蔽:%.8f", "balance_shielded_fmt": "屏蔽:%.8f",
"balance_syncing_pct": "同步中 %.1f%%",
"balance_transparent_fmt": "透明:%.8f", "balance_transparent_fmt": "透明:%.8f",
"ban": "封禁", "ban": "封禁",
"banned_peers": "已封禁节点", "banned_peers": "已封禁节点",
@@ -132,6 +135,7 @@
"bootstrap_verifying": "正在验证校验和...", "bootstrap_verifying": "正在验证校验和...",
"bootstrap_wallet_protected": "(wallet.dat 已受保护)", "bootstrap_wallet_protected": "(wallet.dat 已受保护)",
"bootstrap_warning": "现有区块数据blocks、chainstate、notarizations将被删除并替换。您的 wallet.dat 不会被修改或删除。", "bootstrap_warning": "现有区块数据blocks、chainstate、notarizations将被删除并替换。您的 wallet.dat 不会被修改或删除。",
"byte_count_fmt": "%zu / %zu 字节",
"cancel": "取消", "cancel": "取消",
"change_pass_confirm": "确认新密码:", "change_pass_confirm": "确认新密码:",
"change_pass_current": "当前密码短语:", "change_pass_current": "当前密码短语:",
@@ -441,6 +445,7 @@
"daemon_update_latest": "最新:", "daemon_update_latest": "最新:",
"daemon_update_loading": "正在加载版本…", "daemon_update_loading": "正在加载版本…",
"daemon_update_now": "立即更新", "daemon_update_now": "立即更新",
"daemon_update_prompt_title": "是否更新节点守护进程?",
"daemon_update_reinstall": "重新安装", "daemon_update_reinstall": "重新安装",
"daemon_update_restart_note": "重启守护进程以开始运行新版本。", "daemon_update_restart_note": "重启守护进程以开始运行新版本。",
"daemon_update_restart_now": "立即重启守护进程", "daemon_update_restart_now": "立即重启守护进程",
@@ -657,6 +662,7 @@
"light": "浅色", "light": "浅色",
"lite_account_label": "账户", "lite_account_label": "账户",
"lite_action": "操作", "lite_action": "操作",
"lite_backend_unavailable": "轻量钱包后端不可用",
"lite_backup_keys": "备份与密钥", "lite_backup_keys": "备份与密钥",
"lite_birthday_backup": "生日区块:%llu (也请一并备份)", "lite_birthday_backup": "生日区块:%llu (也请一并备份)",
"lite_birthday_hint": "开始扫描的区块高度。如未知请保留 0完整扫描更慢。", "lite_birthday_hint": "开始扫描的区块高度。如未知请保留 0完整扫描更慢。",
@@ -664,9 +670,12 @@
"lite_console_backend_commands": "后端命令:", "lite_console_backend_commands": "后端命令:",
"lite_console_help_passthrough": "其他任何输入都将作为轻钱包控制台命令运行。", "lite_console_help_passthrough": "其他任何输入都将作为轻钱包控制台命令运行。",
"lite_copy": "复制", "lite_copy": "复制",
"lite_could_not_start": "无法启动该操作",
"lite_could_not_write": "无法写入 ", "lite_could_not_write": "无法写入 ",
"lite_encrypt_wallet": "加密钱包", "lite_encrypt_wallet": "加密钱包",
"lite_encryption_removed": "已移除加密", "lite_encryption_removed": "已移除加密",
"lite_enter_all_seed_words": "请输入全部 24 个助记词以恢复(已输入 %d 个)",
"lite_enter_wallet_path": "请输入钱包路径",
"lite_hide_wipe": "隐藏并清除", "lite_hide_wipe": "隐藏并清除",
"lite_import": "导入", "lite_import": "导入",
"lite_import_key_label": "导入密钥", "lite_import_key_label": "导入密钥",
@@ -919,6 +928,7 @@
"mining_pool": "矿池", "mining_pool": "矿池",
"mining_pool_fee": "费用", "mining_pool_fee": "费用",
"mining_pool_hashrate": "矿池算力", "mining_pool_hashrate": "矿池算力",
"mining_pool_needs_payout_tooltip": "请先输入收款地址(生成一个 Z 地址)",
"mining_pool_url": "矿池 URL", "mining_pool_url": "矿池 URL",
"mining_pools_header": "矿池", "mining_pools_header": "矿池",
"mining_recent_blocks": "最近区块", "mining_recent_blocks": "最近区块",
@@ -1120,6 +1130,8 @@
"qr_failed": "无法生成二维码", "qr_failed": "无法生成二维码",
"qr_title": "二维码", "qr_title": "二维码",
"qr_unavailable": "二维码不可用", "qr_unavailable": "二维码不可用",
"quick_receive": "快速接收",
"quick_send": "快速发送",
"ram_daemon_gb": "守护进程:%.1f GB (%s)", "ram_daemon_gb": "守护进程:%.1f GB (%s)",
"ram_daemon_mb": "守护进程:%.0f MB (%s)", "ram_daemon_mb": "守护进程:%.0f MB (%s)",
"ram_system_gb": "系统:%.1f / %.0f GB", "ram_system_gb": "系统:%.1f / %.0f GB",
@@ -1169,6 +1181,7 @@
"rpc_connection": "RPC 连接...", "rpc_connection": "RPC 连接...",
"rpc_host": "RPC 主机", "rpc_host": "RPC 主机",
"rpc_pass": "密码", "rpc_pass": "密码",
"rpc_plaintext_remote_warning": "远程 RPC 正在使用明文 HTTP。如果您的守护进程支持 TLS请在 DRAGONX.conf 中添加 rpctls=1。",
"rpc_port": "端口", "rpc_port": "端口",
"rpc_user": "用户名", "rpc_user": "用户名",
"save": "保存", "save": "保存",
@@ -1277,6 +1290,7 @@
"send_tooltip_not_connected": "未连接到守护进程", "send_tooltip_not_connected": "未连接到守护进程",
"send_tooltip_select_source": "请先选择来源地址", "send_tooltip_select_source": "请先选择来源地址",
"send_tooltip_syncing": "请等待区块链同步", "send_tooltip_syncing": "请等待区块链同步",
"send_tooltip_view_only": "仅查看地址 — 无花费密钥,无法发送",
"send_total": "合计", "send_total": "合计",
"send_transaction": "发送交易", "send_transaction": "发送交易",
"send_tx_failed": "交易失败", "send_tx_failed": "交易失败",
@@ -1340,6 +1354,7 @@
"settings_locked": "已锁定", "settings_locked": "已锁定",
"settings_merge_to_address": "合并到地址...", "settings_merge_to_address": "合并到地址...",
"settings_noise_opacity": "噪点不透明度:", "settings_noise_opacity": "噪点不透明度:",
"settings_not_connected": "未连接到守护进程",
"settings_not_encrypted": "未加密", "settings_not_encrypted": "未加密",
"settings_not_found": "未找到", "settings_not_found": "未找到",
"settings_open_app_dir": "打开应用文件夹", "settings_open_app_dir": "打开应用文件夹",
@@ -1350,18 +1365,23 @@
"settings_privacy": "隐私", "settings_privacy": "隐私",
"settings_quick_unlock_pin": "快速解锁 PIN", "settings_quick_unlock_pin": "快速解锁 PIN",
"settings_reduce_transparency": "降低透明度", "settings_reduce_transparency": "降低透明度",
"settings_reloaded": "已从磁盘重新加载设置",
"settings_remove_encryption": "移除加密", "settings_remove_encryption": "移除加密",
"settings_remove_pin": "移除 PIN", "settings_remove_pin": "移除 PIN",
"settings_request_payment": "请求付款...", "settings_request_payment": "请求付款...",
"settings_rescan_desc": "重新扫描区块链以查找丢失的交易", "settings_rescan_desc": "重新扫描区块链以查找丢失的交易",
"settings_restart_daemon": "重启守护进程", "settings_restart_daemon": "重启守护进程",
"settings_rpc_connection": "RPC 连接", "settings_rpc_connection": "RPC 连接",
"settings_rpc_error_prefix": "RPC 错误:",
"settings_rpc_note": "注意:连接设置通常从 DRAGONX.conf 自动检测", "settings_rpc_note": "注意:连接设置通常从 DRAGONX.conf 自动检测",
"settings_rpc_ok": "RPC 连接正常",
"settings_save_shielded_desc": "将 z-addr 交易存储在本地文件中以供查看", "settings_save_shielded_desc": "将 z-addr 交易存储在本地文件中以供查看",
"settings_save_shielded_local": "将屏蔽交易历史保存到本地", "settings_save_shielded_local": "将屏蔽交易历史保存到本地",
"settings_saved": "设置已保存",
"settings_set_pin": "设置 PIN", "settings_set_pin": "设置 PIN",
"settings_shield_mining": "屏蔽挖矿...", "settings_shield_mining": "屏蔽挖矿...",
"settings_solid_colors_desc": "使用纯色代替模糊效果(无障碍功能)", "settings_solid_colors_desc": "使用纯色代替模糊效果(无障碍功能)",
"settings_theme_refreshed": "主题列表已刷新",
"settings_tor_desc": "通过 Tor 路由所有连接以增强隐私", "settings_tor_desc": "通过 Tor 路由所有连接以增强隐私",
"settings_unlocked": "已解锁", "settings_unlocked": "已解锁",
"settings_use_tor_network": "使用 Tor 进行网络连接", "settings_use_tor_network": "使用 Tor 进行网络连接",
@@ -1373,6 +1393,8 @@
"settings_wallet_maintenance": "钱包维护", "settings_wallet_maintenance": "钱包维护",
"settings_wallet_not_found": "未找到钱包文件", "settings_wallet_not_found": "未找到钱包文件",
"settings_wallet_size_label": "钱包大小:", "settings_wallet_size_label": "钱包大小:",
"settings_ztx_cleared": "Z 交易历史记录已清除",
"settings_ztx_not_found": "未找到历史记录文件",
"setup_wizard": "设置向导", "setup_wizard": "设置向导",
"share": "分享", "share": "分享",
"shield_check_status": "检查状态", "shield_check_status": "检查状态",
@@ -1455,6 +1477,7 @@
"theme": "主题", "theme": "主题",
"theme_effects": "主题效果", "theme_effects": "主题效果",
"theme_language": "主题与语言", "theme_language": "主题与语言",
"tile_click_to_open": "点击打开",
"time_days_ago": "%d 天前", "time_days_ago": "%d 天前",
"time_hours_ago": "%d 小时前", "time_hours_ago": "%d 小时前",
"time_minutes_ago": "%d 分钟前", "time_minutes_ago": "%d 分钟前",
@@ -1470,6 +1493,7 @@
"tools": "工具", "tools": "工具",
"tools_actions": "工具与操作...", "tools_actions": "工具与操作...",
"total": "合计", "total": "合计",
"total_balance_label": "总余额",
"transaction_id": "交易 ID", "transaction_id": "交易 ID",
"transaction_sent": "交易发送成功", "transaction_sent": "交易发送成功",
"transaction_sent_msg": "交易已发送!", "transaction_sent_msg": "交易已发送!",

View File

@@ -3908,7 +3908,7 @@ void App::maybeOfferDaemonUpdate()
void App::renderDaemonUpdatePrompt() void App::renderDaemonUpdatePrompt()
{ {
ui::material::OverlayDialogSpec ov; ui::material::OverlayDialogSpec ov;
ov.title = TR("daemon_update_title"); ov.p_open = &show_daemon_update_prompt_; ov.title = TR("daemon_update_prompt_title"); ov.p_open = &show_daemon_update_prompt_;
ov.style = ui::material::OverlayStyle::BlurFloat; ov.style = ui::material::OverlayStyle::BlurFloat;
ov.cardWidth = 520.0f; ov.idSuffix = "daemonupdprompt"; ov.cardWidth = 520.0f; ov.idSuffix = "daemonupdprompt";
if (!ui::material::BeginOverlayDialog(ov)) if (!ui::material::BeginOverlayDialog(ov))

View File

@@ -116,6 +116,26 @@ inline ImVec4 WarningVec4() { return ImGui::ColorConvertU32ToFloat4(Warni
// Convenience Functions for Common Patterns // Convenience Functions for Common Patterns
// ============================================================================ // ============================================================================
/**
* @brief Theme-aware translucent overlay for tracks / hover fills / dividers.
*
* A raw white overlay (IM_COL32(255,255,255,a)) reads on dark skins but vanishes
* on light/pastel skins (white-on-white). This picks a dark overlay on light
* themes and a white overlay on dark themes so the alpha reads either way.
* (Self-contained luminance check so colors.h stays free of draw_helpers.h.)
*
* @param alpha 0-255 opacity of the overlay
*/
inline ImU32 SurfaceOverlay(int alpha)
{
ImU32 bg = Background();
float r = ((bg >> IM_COL32_R_SHIFT) & 0xFF) / 255.0f;
float g = ((bg >> IM_COL32_G_SHIFT) & 0xFF) / 255.0f;
float b = ((bg >> IM_COL32_B_SHIFT) & 0xFF) / 255.0f;
bool light = (0.299f * r + 0.587f * g + 0.114f * b) > 0.5f;
return light ? IM_COL32(0, 0, 0, alpha) : IM_COL32(255, 255, 255, alpha);
}
/** /**
* @brief Get color with applied state overlay * @brief Get color with applied state overlay
* *

View File

@@ -309,7 +309,7 @@ static void evaluateLiteLifecycleRequestFromPageState(App* app) {
std::string trimmedPath(s_settingsState.lite_wallet_path); std::string trimmedPath(s_settingsState.lite_wallet_path);
const auto first = trimmedPath.find_first_not_of(" \t\r\n"); const auto first = trimmedPath.find_first_not_of(" \t\r\n");
if (first == std::string::npos) { if (first == std::string::npos) {
s_settingsState.lite_lifecycle_status = "Enter a wallet path"; s_settingsState.lite_lifecycle_status = TR("lite_enter_wallet_path");
s_settingsState.lite_lifecycle_summary.clear(); s_settingsState.lite_lifecycle_summary.clear();
return; return;
} }
@@ -320,8 +320,9 @@ static void evaluateLiteLifecycleRequestFromPageState(App* app) {
if (input.request.operation == wallet::LiteWalletLifecycleOperation::RestoreFromSeed) { if (input.request.operation == wallet::LiteWalletLifecycleOperation::RestoreFromSeed) {
const int words = liteSeedWordCount(s_settingsState.lite_restore_seed); const int words = liteSeedWordCount(s_settingsState.lite_restore_seed);
if (!dragonx::util::isCompleteRecoveryPhrase(words)) { if (!dragonx::util::isCompleteRecoveryPhrase(words)) {
s_settingsState.lite_lifecycle_status = char seedBuf[128];
"Enter all 24 seed words to restore (got " + std::to_string(words) + ")"; snprintf(seedBuf, sizeof(seedBuf), TR("lite_enter_all_seed_words"), words);
s_settingsState.lite_lifecycle_status = seedBuf;
s_settingsState.lite_lifecycle_summary.clear(); s_settingsState.lite_lifecycle_summary.clear();
return; return;
} }
@@ -353,7 +354,7 @@ static void evaluateLiteLifecycleRequestFromPageState(App* app) {
// Rejected before any thread launched (wallet already open, an attempt in flight, or no // Rejected before any thread launched (wallet already open, an attempt in flight, or no
// usable server). The controller's status carries the reason. // usable server). The controller's status carries the reason.
s_settingsState.lite_lifecycle_status = lite->status().message.empty() s_settingsState.lite_lifecycle_status = lite->status().message.empty()
? "Could not start the operation" ? TR("lite_could_not_start")
: lite->status().message; : lite->status().message;
Notifications::instance().warning(s_settingsState.lite_lifecycle_status); Notifications::instance().warning(s_settingsState.lite_lifecycle_status);
} }
@@ -364,7 +365,7 @@ static void evaluateLiteLifecycleRequestFromPageState(App* app) {
// failed to load / rollout-disabled). The live path above returns when a backend is // failed to load / rollout-disabled). The live path above returns when a backend is
// present, so reaching here means there is nothing to run. // present, so reaching here means there is nothing to run.
s_settingsState.lite_lifecycle_summary.clear(); s_settingsState.lite_lifecycle_summary.clear();
s_settingsState.lite_lifecycle_status = "Lite wallet backend unavailable"; s_settingsState.lite_lifecycle_status = TR("lite_backend_unavailable");
Notifications::instance().warning(s_settingsState.lite_lifecycle_status); Notifications::instance().warning(s_settingsState.lite_lifecycle_status);
} }
@@ -819,7 +820,7 @@ void RenderSettingsPage(App* app) {
ImGui::SameLine(0, Layout::spacingSm()); ImGui::SameLine(0, Layout::spacingSm());
if (TactileButton(TR("refresh"), ImVec2(refreshBtnW, 0), S.resolveFont("button"))) { if (TactileButton(TR("refresh"), ImVec2(refreshBtnW, 0), S.resolveFont("button"))) {
schema::SkinManager::instance().refresh(); schema::SkinManager::instance().refresh();
Notifications::instance().info("Theme list refreshed"); Notifications::instance().info(TR("settings_theme_refreshed"));
} }
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered()) {
material::Tooltip(TR("tt_scan_themes"), material::Tooltip(TR("tt_scan_themes"),
@@ -1064,7 +1065,7 @@ void RenderSettingsPage(App* app) {
ImGui::SameLine(); ImGui::SameLine();
if (TactileButton(TR("refresh"), ImVec2(refreshBtnW, 0), S.resolveFont("button"))) { if (TactileButton(TR("refresh"), ImVec2(refreshBtnW, 0), S.resolveFont("button"))) {
schema::SkinManager::instance().refresh(); schema::SkinManager::instance().refresh();
Notifications::instance().info("Theme list refreshed"); Notifications::instance().info(TR("settings_theme_refreshed"));
} }
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered()) {
material::Tooltip(TR("tt_scan_themes"), material::Tooltip(TR("tt_scan_themes"),
@@ -1386,6 +1387,30 @@ void RenderSettingsPage(App* app) {
float bw = (contentW - btnSpacing * (btnsPerRow - 1)) / btnsPerRow; float bw = (contentW - btnSpacing * (btnsPerRow - 1)) / btnsPerRow;
float minBtnW = S.drawElement("components.settings-page", "wallet-btn-min-width").sizeOr(100.0f); float minBtnW = S.drawElement("components.settings-page", "wallet-btn-min-width").sizeOr(100.0f);
bw = std::max(minBtnW, bw); bw = std::max(minBtnW, bw);
// Grow the column so the longest translated label (e.g. German) isn't clipped inside
// ImGui::Button. Measure every label with the actual button font (LegacySize is already
// DPI-scaled — don't scale it again) and add the button's own FramePadding on both sides;
// if that exceeds bw, drop to fewer columns rather than overflow the card width.
{
ImFont* toolsFont = S.resolveFont("button");
if (!toolsFont) toolsFont = Type().button();
const char* toolLabels[] = {
TR("settings_address_book"), TR("settings_validate_address"),
TR("settings_request_payment"), TR("settings_shield_mining"),
TR("settings_merge_to_address"), TR("settings_clear_ztx"),
};
float widestLabel = 0.0f;
for (const char* lbl : toolLabels)
widestLabel = std::max(widestLabel,
toolsFont->CalcTextSizeA(toolsFont->LegacySize, FLT_MAX, 0, lbl).x);
const float needW = widestLabel + ImGui::GetStyle().FramePadding.x * 2.0f
+ 8.0f * Layout::dpiScale();
// Shed columns until the widest label fits (or we're down to a single column).
while (btnsPerRow > 1 &&
((contentW - btnSpacing * (btnsPerRow - 1)) / btnsPerRow) < needW)
--btnsPerRow;
bw = std::max({minBtnW, (contentW - btnSpacing * (btnsPerRow - 1)) / btnsPerRow, needW});
}
if (TactileButton(TR("settings_address_book"), ImVec2(bw, 0), S.resolveFont("button"))) if (TactileButton(TR("settings_address_book"), ImVec2(bw, 0), S.resolveFont("button")))
app->setCurrentPage(ui::NavPage::Contacts); // now a top-level tab app->setCurrentPage(ui::NavPage::Contacts); // now a top-level tab
@@ -2225,7 +2250,7 @@ void RenderSettingsPage(App* app) {
if (s_settingsState.rpc_plaintext_remote) { if (s_settingsState.rpc_plaintext_remote) {
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(Warning())); ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(Warning()));
ImGui::PushTextWrapPos(sectionOrigin.x + contentW); ImGui::PushTextWrapPos(sectionOrigin.x + contentW);
ImGui::TextWrapped("Remote RPC is using plaintext HTTP. Add rpctls=1 to DRAGONX.conf if your daemon supports TLS."); ImGui::TextWrapped("%s", TR("rpc_plaintext_remote_warning"));
ImGui::PopTextWrapPos(); ImGui::PopTextWrapPos();
ImGui::PopStyleColor(); ImGui::PopStyleColor();
} }
@@ -2367,14 +2392,14 @@ void RenderSettingsPage(App* app) {
try { try {
rpc::RPCClient::TraceScope trace("Settings / Test connection"); rpc::RPCClient::TraceScope trace("Settings / Test connection");
rpc->call("getinfo"); rpc->call("getinfo");
return []() { Notifications::instance().success("RPC connection OK"); }; return []() { Notifications::instance().success(TR("settings_rpc_ok")); };
} catch (const std::exception& e) { } catch (const std::exception& e) {
std::string err = e.what(); std::string err = e.what();
return [err]() { Notifications::instance().error("RPC error: " + err); }; return [err]() { Notifications::instance().error(std::string(TR("settings_rpc_error_prefix")) + err); };
} }
}); });
} else { } else {
Notifications::instance().warning("Not connected to daemon"); Notifications::instance().warning(TR("settings_not_connected"));
} }
} }
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) material::Tooltip("%s", TR("tt_test_conn")); if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) material::Tooltip("%s", TR("tt_test_conn"));
@@ -2587,14 +2612,14 @@ void RenderSettingsPage(App* app) {
if (aboutGrid) ImGui::Dummy(ImVec2(0, Layout::spacingXs())); else ImGui::SameLine(0, Layout::spacingMd()); if (aboutGrid) ImGui::Dummy(ImVec2(0, Layout::spacingXs())); else ImGui::SameLine(0, Layout::spacingMd());
if (TactileButton(TrId("save_settings", "about_save").c_str(), ImVec2(aboutBtnW, 0), S.resolveFont("button"))) { if (TactileButton(TrId("save_settings", "about_save").c_str(), ImVec2(aboutBtnW, 0), S.resolveFont("button"))) {
saveSettingsPageState(app->settings()); saveSettingsPageState(app->settings());
Notifications::instance().success("Settings saved"); Notifications::instance().success(TR("settings_saved"));
} }
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_save_settings")); if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_save_settings"));
ImGui::SameLine(0, Layout::spacingMd()); ImGui::SameLine(0, Layout::spacingMd());
if (TactileButton(TrId("reset_to_defaults", "about_reset").c_str(), ImVec2(aboutBtnW, 0), S.resolveFont("button"))) { if (TactileButton(TrId("reset_to_defaults", "about_reset").c_str(), ImVec2(aboutBtnW, 0), S.resolveFont("button"))) {
if (app->settings()) { if (app->settings()) {
loadSettingsPageState(app->settings()); loadSettingsPageState(app->settings());
Notifications::instance().info("Settings reloaded from disk"); Notifications::instance().info(TR("settings_reloaded"));
} }
} }
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_reset_settings")); if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_reset_settings"));
@@ -2913,9 +2938,9 @@ void RenderSettingsPage(App* app) {
if (doConfirm) { if (doConfirm) {
std::string ztx_file = util::Platform::getDragonXDataDir() + "ztx_history.json"; std::string ztx_file = util::Platform::getDragonXDataDir() + "ztx_history.json";
if (util::Platform::deleteFile(ztx_file)) { if (util::Platform::deleteFile(ztx_file)) {
Notifications::instance().success("Z-transaction history cleared"); Notifications::instance().success(TR("settings_ztx_cleared"));
} else { } else {
Notifications::instance().info("No history file found"); Notifications::instance().info(TR("settings_ztx_not_found"));
} }
s_settingsState.confirm_clear_ztx = false; s_settingsState.confirm_clear_ztx = false;
} }

View File

@@ -722,8 +722,10 @@ inline bool RenderSidebar(NavPage& current, float sidebarWidth, float contentHei
if (badgeCount > 0 || dotOnly) { if (badgeCount > 0 || dotOnly) {
float badgeR = dotOnly ? badgeRadiusDot : badgeRadiusNumber; float badgeR = dotOnly ? badgeRadiusDot : badgeRadiusNumber;
float bx = indMax.x - badgeR - 6.0f; float badgeInsetX = sde("badge-inset-x", 6.0f);
float by = indMin.y + badgeR + 5.0f; float badgeInsetY = sde("badge-inset-y", 5.0f);
float bx = indMax.x - badgeR - badgeInsetX;
float by = indMin.y + badgeR + badgeInsetY;
dl->AddCircleFilled(ImVec2(bx, by), badgeR, badgeCol); dl->AddCircleFilled(ImVec2(bx, by), badgeR, badgeCol);
if (!dotOnly && showLabels) { if (!dotOnly && showLabels) {
char buf[16]; char buf[16];

View File

@@ -466,7 +466,8 @@ void RenderSharedAddressList(App* app, float listH, float availW,
{ {
const auto& starRect = rowLayout.favoriteButton; const auto& starRect = rowLayout.favoriteButton;
ImVec2 bMin(starRect.x, starRect.y), bMax(starRect.x + starRect.width, starRect.y + starRect.height); ImVec2 bMin(starRect.x, starRect.y), bMax(starRect.x + starRect.width, starRect.y + starRect.height);
bool bHov = ImGui::IsMouseHoveringRect(bMin, bMax); // material::IsRectHovered so the button inherits overlay/popup input blocking
bool bHov = material::IsRectHovered(bMin, bMax);
dl->AddRectFilled(bMin, bMax, row.favorite ? favGoldFill : (bHov ? btnFillHov : btnFill), btnRound); dl->AddRectFilled(bMin, bMax, row.favorite ? favGoldFill : (bHov ? btnFillHov : btnFill), btnRound);
dl->AddRect(bMin, bMax, row.favorite ? favGoldBorder : (bHov ? btnBorderHov : btnBorder), btnRound, 0, 1.0f * dp); dl->AddRect(bMin, bMax, row.favorite ? favGoldBorder : (bHov ? btnBorderHov : btnBorder), btnRound, 0, 1.0f * dp);
ImFont* iconFont = Type().iconSmall(); ImFont* iconFont = Type().iconSmall();
@@ -492,7 +493,8 @@ void RenderSharedAddressList(App* app, float listH, float availW,
if (showEye) { if (showEye) {
const auto& eyeRect = rowLayout.visibilityButton; const auto& eyeRect = rowLayout.visibilityButton;
ImVec2 bMin(eyeRect.x, eyeRect.y), bMax(eyeRect.x + eyeRect.width, eyeRect.y + eyeRect.height); ImVec2 bMin(eyeRect.x, eyeRect.y), bMax(eyeRect.x + eyeRect.width, eyeRect.y + eyeRect.height);
bool bHov = ImGui::IsMouseHoveringRect(bMin, bMax); // material::IsRectHovered so the button inherits overlay/popup input blocking
bool bHov = material::IsRectHovered(bMin, bMax);
dl->AddRectFilled(bMin, bMax, bHov ? btnFillHov : btnFill, btnRound); dl->AddRectFilled(bMin, bMax, bHov ? btnFillHov : btnFill, btnRound);
dl->AddRect(bMin, bMax, bHov ? btnBorderHov : btnBorder, btnRound, 0, 1.0f * dp); dl->AddRect(bMin, bMax, bHov ? btnBorderHov : btnBorder, btnRound, 0, 1.0f * dp);
ImFont* iconFont = Type().iconSmall(); ImFont* iconFont = Type().iconSmall();
@@ -539,12 +541,24 @@ void RenderSharedAddressList(App* app, float listH, float availW,
snprintf(typeBuf, sizeof(typeBuf), "%s%s%s", typeLabel, hiddenTag, miningTag); snprintf(typeBuf, sizeof(typeBuf), "%s%s%s", typeLabel, hiddenTag, miningTag);
dl->AddText(capFont, capFont->LegacySize, ImVec2(labelX, cy), typeCol, typeBuf); dl->AddText(capFont, capFont->LegacySize, ImVec2(labelX, cy), typeCol, typeBuf);
// User label next to type // User label next to type — clip to the gap before the right-aligned
// balance so a long custom label can't overrun the balance on this line
// (mirrors the address-line width guard below).
if (!row.label.empty()) { if (!row.label.empty()) {
float typeLabelW = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, typeBuf).x; float typeLabelW = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, typeBuf).x;
dl->AddText(capFont, capFont->LegacySize, float userLabelX = labelX + typeLabelW + Layout::spacingLg();
ImVec2(labelX + typeLabelW + Layout::spacingLg(), cy), // Balance is drawn right-aligned at contentRight; recompute its left edge here.
OnSurfaceMedium(), row.label.c_str()); char balBufPeek[32];
snprintf(balBufPeek, sizeof(balBufPeek), "%.8f", addr.balance);
float balW = body2->CalcTextSizeA(body2->LegacySize, FLT_MAX, 0, balBufPeek).x;
float userLabelAvailW = (contentRight - balW - Layout::spacingMd()) - userLabelX;
std::string userLabel = material::TruncateToWidth(
row.label, capFont, capFont->LegacySize, userLabelAvailW);
if (userLabelAvailW > 0.0f) {
dl->AddText(capFont, capFont->LegacySize,
ImVec2(userLabelX, cy),
OnSurfaceMedium(), userLabel.c_str());
}
} }
} }
@@ -841,7 +855,7 @@ void RenderSharedRecentTx(App* app, float recentH, float availW, float hs, float
float rowW = ImGui::GetContentRegionAvail().x; float rowW = ImGui::GetContentRegionAvail().x;
ImVec2 rowEnd(rowPos.x + rowW, rowPos.y + rowH); ImVec2 rowEnd(rowPos.x + rowW, rowPos.y + rowH);
if (material::IsRectHovered(rowPos, rowEnd)) { if (material::IsRectHovered(rowPos, rowEnd)) {
dl->AddRectFilled(rowPos, rowEnd, IM_COL32(255, 255, 255, 15), S.drawElement("tabs.balance", "row-hover-rounding").sizeOr(4.0f)); dl->AddRectFilled(rowPos, rowEnd, SurfaceOverlay(15), S.drawElement("tabs.balance", "row-hover-rounding").sizeOr(4.0f));
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
if (ImGui::IsMouseClicked(0)) if (ImGui::IsMouseClicked(0))
app->setCurrentPage(NavPage::History); app->setCurrentPage(NavPage::History);
@@ -868,7 +882,7 @@ void RenderSyncBar(App* app, ImDrawList* dl, float vs) {
ImVec2 barPos = ImGui::GetCursorScreenPos(); ImVec2 barPos = ImGui::GetCursorScreenPos();
dl->AddRectFilled(barPos, dl->AddRectFilled(barPos,
ImVec2(barPos.x + barW, barPos.y + barH), ImVec2(barPos.x + barW, barPos.y + barH),
IM_COL32(255, 255, 255, 15), 1.0f * dp); SurfaceOverlay(15), 1.0f * dp);
dl->AddRectFilled(barPos, dl->AddRectFilled(barPos,
ImVec2(barPos.x + barW * prog, barPos.y + barH), ImVec2(barPos.x + barW * prog, barPos.y + barH),
WithAlpha(Warning(), 200), 1.0f * dp); WithAlpha(Warning(), 200), 1.0f * dp);

View File

@@ -31,6 +31,7 @@
#include "imgui.h" #include "imgui.h"
#include <toml++/toml.hpp> #include <toml++/toml.hpp>
#include <algorithm> #include <algorithm>
#include <cctype>
#include <cstring> #include <cstring>
#include <ctime> #include <ctime>
#include <cmath> #include <cmath>
@@ -196,7 +197,9 @@ void RenderBalanceTab(App* app)
for (const auto& l : allLayouts) { for (const auto& l : allLayouts) {
if (l.id == layoutId) { displayName = l.name; break; } if (l.id == layoutId) { displayName = l.name; break; }
} }
Notifications::instance().info("Layout: " + displayName); char layoutToast[128];
snprintf(layoutToast, sizeof(layoutToast), TR("balance_layout_switched"), displayName.c_str());
Notifications::instance().info(layoutToast);
} }
} }
} }
@@ -359,8 +362,11 @@ static void RenderBalanceClassic(App* app)
IM_COL32(255, 255, 255, (int)S.drawElement("tabs.balance.classic", "logo-opacity").sizeOr(180.0f))); IM_COL32(255, 255, 255, (int)S.drawElement("tabs.balance.classic", "logo-opacity").sizeOr(180.0f)));
} }
std::string totalLabelUpper = TR("total_balance_label");
std::transform(totalLabelUpper.begin(), totalLabelUpper.end(), totalLabelUpper.begin(),
[](unsigned char c){ return (char)std::toupper(c); });
dl->AddText(ovFont, ovFont->LegacySize, ImVec2(cx, cy), dl->AddText(ovFont, ovFont->LegacySize, ImVec2(cx, cy),
OnSurfaceMedium(), "TOTAL BALANCE"); OnSurfaceMedium(), totalLabelUpper.c_str());
cy += ovFont->LegacySize + ovGap; cy += ovFont->LegacySize + ovGap;
snprintf(buf, sizeof(buf), "%.8f", s_dispTotal); snprintf(buf, sizeof(buf), "%.8f", s_dispTotal);
@@ -387,7 +393,7 @@ static void RenderBalanceClassic(App* app)
// Sync progress or mining indicator (whichever fits) // Sync progress or mining indicator (whichever fits)
if (state.sync.syncing && state.sync.headers > 0) { if (state.sync.syncing && state.sync.headers > 0) {
float pct = static_cast<float>(state.sync.verification_progress) * 100.0f; float pct = static_cast<float>(state.sync.verification_progress) * 100.0f;
snprintf(buf, sizeof(buf), "Syncing %.1f%%", pct); snprintf(buf, sizeof(buf), TR("balance_syncing_pct"), pct);
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, cy), dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, cy),
Warning(), buf); Warning(), buf);
@@ -401,7 +407,7 @@ static void RenderBalanceClassic(App* app)
dl->PushClipRect(ImVec2(cMin.x, barTop), cMax, true); dl->PushClipRect(ImVec2(cMin.x, barTop), cMax, true);
// Background track // Background track
dl->AddRectFilled(cMin, cMax, dl->AddRectFilled(cMin, cMax,
IM_COL32(255, 255, 255, 15), cardSpec.rounding); SurfaceOverlay(15), cardSpec.rounding);
// Progress fill — additional horizontal clip // Progress fill — additional horizontal clip
float progRight = cMin.x + (cMax.x - cMin.x) * prog; float progRight = cMin.x + (cMax.x - cMin.x) * prog;
dl->PushClipRect(ImVec2(cMin.x, barTop), ImVec2(progRight, cMax.y), true); dl->PushClipRect(ImVec2(cMin.x, barTop), ImVec2(progRight, cMax.y), true);
@@ -418,7 +424,11 @@ static void RenderBalanceClassic(App* app)
dl->AddCircleFilled(ImVec2(cx + 4 * dp, cy + capFont->LegacySize * 0.5f), dl->AddCircleFilled(ImVec2(cx + 4 * dp, cy + capFont->LegacySize * 0.5f),
S.drawElement("tabs.balance.classic", "mining-dot-radius").sizeOr(3.0f), mineCol); S.drawElement("tabs.balance.classic", "mining-dot-radius").sizeOr(3.0f), mineCol);
double hr = state.mining.localHashrate; double hr = state.mining.localHashrate;
snprintf(buf, sizeof(buf), " Mining %s", FormatHashrate(hr).c_str()); // Leading indent clears the mining dot drawn at cx+4dp; the text
// itself starts at cx+12dp so keep the two spaces for spacing parity.
char mineFmt[64];
snprintf(mineFmt, sizeof(mineFmt), " %s", TR("balance_mining_rate"));
snprintf(buf, sizeof(buf), mineFmt, FormatHashrate(hr).c_str());
dl->AddText(capFont, capFont->LegacySize, dl->AddText(capFont, capFont->LegacySize,
ImVec2(cx + 12 * dp, cy), ImVec2(cx + 12 * dp, cy),
WithAlpha(Success(), 200), buf); WithAlpha(Success(), 200), buf);
@@ -574,15 +584,18 @@ static void RenderBalanceClassic(App* app)
ImVec2 usdSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, "USD"); ImVec2 usdSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, "USD");
// Measure widest text line to determine sparkline left edge // Measure widest text line to determine sparkline left edge
std::string marketLabel = TR("market");
std::transform(marketLabel.begin(), marketLabel.end(), marketLabel.begin(),
[](unsigned char c){ return (char)std::toupper(c); });
float textW = std::max(pSz.x + tickGap + usdSz.x, float textW = std::max(pSz.x + tickGap + usdSz.x,
ovFont->CalcTextSizeA(ovFont->LegacySize, 10000, 0, "MARKET").x); ovFont->CalcTextSizeA(ovFont->LegacySize, 10000, 0, marketLabel.c_str()).x);
float sparkGap = S.drawElement("tabs.balance.classic", "sparkline-gap").sizeOr(12.0f); float sparkGap = S.drawElement("tabs.balance.classic", "sparkline-gap").sizeOr(12.0f);
float sparkLeft = cx + textW + sparkGap; float sparkLeft = cx + textW + sparkGap;
float sparkRight = cMax.x - cardPadLg; float sparkRight = cMax.x - cardPadLg;
// Left side: label + price + 24h change // Left side: label + price + 24h change
dl->AddText(ovFont, ovFont->LegacySize, ImVec2(cx, cy), dl->AddText(ovFont, ovFont->LegacySize, ImVec2(cx, cy),
OnSurfaceMedium(), "MARKET"); OnSurfaceMedium(), marketLabel.c_str());
cy += ovFont->LegacySize + ovGap; cy += ovFont->LegacySize + ovGap;
dl->AddText(sub1, sub1->LegacySize, ImVec2(cx, cy), dl->AddText(sub1, sub1->LegacySize, ImVec2(cx, cy),
@@ -682,7 +695,7 @@ static void RenderBalanceDonut(App* app) {
else else
ImGui::Dummy(ImVec2(0, S.drawElement("tabs.balance.donut", "hero-pad-ratio").sizeOr(8.0f) * vs)); ImGui::Dummy(ImVec2(0, S.drawElement("tabs.balance.donut", "hero-pad-ratio").sizeOr(8.0f) * vs));
{ {
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "TOTAL BALANCE"); Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("total_balance_label"));
ImGui::Dummy(ImVec2(0, 2 * dp)); ImGui::Dummy(ImVec2(0, 2 * dp));
snprintf(buf, sizeof(buf), "%.8f", s_dispTotal); snprintf(buf, sizeof(buf), "%.8f", s_dispTotal);
ImFont* heroFont = Type().h2(); ImFont* heroFont = Type().h2();
@@ -947,10 +960,13 @@ static void RenderBalanceConsolidated(App* app) {
// Shielded bar // Shielded bar
float shieldX = cardMin.x + pad; float shieldX = cardMin.x + pad;
dl->AddText(ovFont, ovFont->LegacySize, ImVec2(shieldX, barY), Success(), "SHIELDED"); std::string shieldLabelUpper = TR("shielded");
std::transform(shieldLabelUpper.begin(), shieldLabelUpper.end(), shieldLabelUpper.begin(),
[](unsigned char c){ return (char)std::toupper(c); });
dl->AddText(ovFont, ovFont->LegacySize, ImVec2(shieldX, barY), Success(), shieldLabelUpper.c_str());
barY += ovFont->LegacySize + 4 * dp; barY += ovFont->LegacySize + 4 * dp;
dl->AddRectFilled(ImVec2(shieldX, barY), ImVec2(shieldX + halfW, barY + barH), dl->AddRectFilled(ImVec2(shieldX, barY), ImVec2(shieldX + halfW, barY + barH),
IM_COL32(255, 255, 255, 15), barH * 0.5f); SurfaceOverlay(15), barH * 0.5f);
dl->AddRectFilled(ImVec2(shieldX, barY), ImVec2(shieldX + halfW * shieldRatio, barY + barH), dl->AddRectFilled(ImVec2(shieldX, barY), ImVec2(shieldX + halfW * shieldRatio, barY + barH),
WithAlpha(Success(), 180), barH * 0.5f); WithAlpha(Success(), 180), barH * 0.5f);
barY += barH + 2 * dp; barY += barH + 2 * dp;
@@ -960,10 +976,13 @@ static void RenderBalanceConsolidated(App* app) {
// Transparent bar // Transparent bar
float transX = cardMin.x + pad * 2 + halfW; float transX = cardMin.x + pad * 2 + halfW;
barY = divY + Layout::spacingSm(); barY = divY + Layout::spacingSm();
dl->AddText(ovFont, ovFont->LegacySize, ImVec2(transX, barY), Warning(), "TRANSPARENT"); std::string transLabelUpper = TR("transparent");
std::transform(transLabelUpper.begin(), transLabelUpper.end(), transLabelUpper.begin(),
[](unsigned char c){ return (char)std::toupper(c); });
dl->AddText(ovFont, ovFont->LegacySize, ImVec2(transX, barY), Warning(), transLabelUpper.c_str());
barY += ovFont->LegacySize + 4 * dp; barY += ovFont->LegacySize + 4 * dp;
dl->AddRectFilled(ImVec2(transX, barY), ImVec2(transX + halfW, barY + barH), dl->AddRectFilled(ImVec2(transX, barY), ImVec2(transX + halfW, barY + barH),
IM_COL32(255, 255, 255, 15), barH * 0.5f); SurfaceOverlay(15), barH * 0.5f);
dl->AddRectFilled(ImVec2(transX, barY), ImVec2(transX + halfW * transRatio, barY + barH), dl->AddRectFilled(ImVec2(transX, barY), ImVec2(transX + halfW * transRatio, barY + barH),
WithAlpha(Warning(), 180), barH * 0.5f); WithAlpha(Warning(), 180), barH * 0.5f);
barY += barH + 2 * dp; barY += barH + 2 * dp;
@@ -982,7 +1001,7 @@ static void RenderBalanceConsolidated(App* app) {
dl->PushClipRect(ImVec2(cardMin.x, syncBarTop), cardMax, true); dl->PushClipRect(ImVec2(cardMin.x, syncBarTop), cardMax, true);
// Background track // Background track
dl->AddRectFilled(cardMin, cardMax, dl->AddRectFilled(cardMin, cardMax,
IM_COL32(255, 255, 255, 15), glassRound); SurfaceOverlay(15), glassRound);
// Progress fill — additional horizontal clip // Progress fill — additional horizontal clip
float progRight = cardMin.x + (cardMax.x - cardMin.x) * prog; float progRight = cardMin.x + (cardMax.x - cardMin.x) * prog;
dl->PushClipRect(ImVec2(cardMin.x, syncBarTop), ImVec2(progRight, cardMax.y), true); dl->PushClipRect(ImVec2(cardMin.x, syncBarTop), ImVec2(progRight, cardMax.y), true);
@@ -1075,11 +1094,20 @@ static void RenderBalanceDashboard(App* app) {
snprintf(shBuf, sizeof(shBuf), "%.8f", s_dispShielded); snprintf(shBuf, sizeof(shBuf), "%.8f", s_dispShielded);
snprintf(trBuf, sizeof(trBuf), "%.8f", s_dispTransparent); snprintf(trBuf, sizeof(trBuf), "%.8f", s_dispTransparent);
// Localized captions — raw dl->AddText (no auto-uppercase), so uppercase to preserve the caption look.
auto upperTR = [](const char* key) {
std::string s = TR(key);
std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c){ return (char)std::toupper(c); });
return s;
};
std::string lblShielded = upperTR("shielded"), lblTransparent = upperTR("transparent");
std::string lblQuickSend = upperTR("quick_send"), lblQuickReceive = upperTR("quick_receive");
TileInfo tiles[4] = { TileInfo tiles[4] = {
{"SHIELDED", shBuf, S.resolveColor("var(--accent-shielded)", Success()), ICON_MD_SHIELD, NavPage::Receive, false}, {lblShielded.c_str(), shBuf, S.resolveColor("var(--accent-shielded)", Success()), ICON_MD_SHIELD, NavPage::Receive, false},
{"TRANSPARENT", trBuf, S.resolveColor("var(--accent-transparent)", Warning()), ICON_MD_CIRCLE, NavPage::Receive, false}, {lblTransparent.c_str(), trBuf, S.resolveColor("var(--accent-transparent)", Warning()), ICON_MD_CIRCLE, NavPage::Receive, false},
{"QUICK SEND", "Send", S.resolveColor("var(--accent-action)", Primary()), ICON_MD_CALL_MADE, NavPage::Send, true}, {lblQuickSend.c_str(), "Send", S.resolveColor("var(--accent-action)", Primary()), ICON_MD_CALL_MADE, NavPage::Send, true},
{"QUICK RECEIVE", "Receive", S.resolveColor("var(--accent-action)", Primary()), ICON_MD_CALL_RECEIVED, NavPage::Receive, true}, {lblQuickReceive.c_str(), "Receive", S.resolveColor("var(--accent-action)", Primary()), ICON_MD_CALL_RECEIVED, NavPage::Receive, true},
}; };
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
@@ -1125,7 +1153,7 @@ static void RenderBalanceDashboard(App* app) {
tiles[i].accent, tiles[i].value); tiles[i].accent, tiles[i].value);
} else { } else {
dl->AddText(capFont, capFont->LegacySize, ImVec2(tMin.x + tilePad, py), dl->AddText(capFont, capFont->LegacySize, ImVec2(tMin.x + tilePad, py),
OnSurfaceMedium(), "Click to open"); OnSurfaceMedium(), TR("tile_click_to_open"));
} }
// Click // Click
@@ -1279,7 +1307,7 @@ static void RenderBalanceVerticalStack(App* app) {
float barW = barRight - barLeft; float barW = barRight - barLeft;
float barY = rowPos.y + (rowH - barH) * 0.5f; float barY = rowPos.y + (rowH - barH) * 0.5f;
dl->AddRectFilled(ImVec2(barLeft, barY), ImVec2(barRight, barY + barH), dl->AddRectFilled(ImVec2(barLeft, barY), ImVec2(barRight, barY + barH),
IM_COL32(255, 255, 255, 15), barH * 0.5f); SurfaceOverlay(15), barH * 0.5f);
dl->AddRectFilled(ImVec2(barLeft, barY), dl->AddRectFilled(ImVec2(barLeft, barY),
ImVec2(barLeft + barW * rowInfos[i].ratio, barY + barH), ImVec2(barLeft + barW * rowInfos[i].ratio, barY + barH),
WithAlpha(rowInfos[i].accent, 180), barH * 0.5f); WithAlpha(rowInfos[i].accent, 180), barH * 0.5f);
@@ -1473,7 +1501,7 @@ static void RenderBalanceVertical2x2(App* app) {
float barX = cellMax.x - amtSz.x - rowPad - barW - Layout::spacingSm(); float barX = cellMax.x - amtSz.x - rowPad - barW - Layout::spacingSm();
float barY = cellMin.y + (rowH - barH) * 0.5f; float barY = cellMin.y + (rowH - barH) * 0.5f;
dl->AddRectFilled(ImVec2(barX, barY), ImVec2(barX + barW, barY + barH), dl->AddRectFilled(ImVec2(barX, barY), ImVec2(barX + barW, barY + barH),
IM_COL32(255, 255, 255, 15), barH * 0.5f); SurfaceOverlay(15), barH * 0.5f);
dl->AddRectFilled(ImVec2(barX, barY), dl->AddRectFilled(ImVec2(barX, barY),
ImVec2(barX + barW * cell.ratio, barY + barH), ImVec2(barX + barW * cell.ratio, barY + barH),
WithAlpha(cell.accent, 180), barH * 0.5f); WithAlpha(cell.accent, 180), barH * 0.5f);
@@ -1551,7 +1579,7 @@ static void RenderBalanceShield(App* app) {
else else
ImGui::Dummy(ImVec2(0, S.drawElement("tabs.balance", "compact-hero-pad").sizeOr(8.0f) * vs)); ImGui::Dummy(ImVec2(0, S.drawElement("tabs.balance", "compact-hero-pad").sizeOr(8.0f) * vs));
{ {
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "TOTAL BALANCE"); Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("total_balance_label"));
ImGui::Dummy(ImVec2(0, 2 * dp)); ImGui::Dummy(ImVec2(0, 2 * dp));
snprintf(buf, sizeof(buf), "%.8f", s_dispTotal); snprintf(buf, sizeof(buf), "%.8f", s_dispTotal);
ImFont* heroFont = Type().h2(); ImFont* heroFont = Type().h2();
@@ -1665,13 +1693,19 @@ static void RenderBalanceShield(App* app) {
float infoY = panelMin.y + shieldPad; float infoY = panelMin.y + shieldPad;
ImFont* ovFont = Type().overline(); ImFont* ovFont = Type().overline();
dl->AddText(ovFont, ovFont->LegacySize, ImVec2(infoX, infoY), Success(), "SHIELDED"); std::string shieldLabelUpper = TR("shielded");
std::transform(shieldLabelUpper.begin(), shieldLabelUpper.end(), shieldLabelUpper.begin(),
[](unsigned char c){ return (char)std::toupper(c); });
std::string transLabelUpper = TR("transparent");
std::transform(transLabelUpper.begin(), transLabelUpper.end(), transLabelUpper.begin(),
[](unsigned char c){ return (char)std::toupper(c); });
dl->AddText(ovFont, ovFont->LegacySize, ImVec2(infoX, infoY), Success(), shieldLabelUpper.c_str());
infoY += ovFont->LegacySize + 2 * dp; infoY += ovFont->LegacySize + 2 * dp;
snprintf(buf, sizeof(buf), "%.8f", s_dispShielded); snprintf(buf, sizeof(buf), "%.8f", s_dispShielded);
dl->AddText(capFont, capFont->LegacySize, ImVec2(infoX, infoY), Success(), buf); dl->AddText(capFont, capFont->LegacySize, ImVec2(infoX, infoY), Success(), buf);
infoY += capFont->LegacySize + 6 * dp; infoY += capFont->LegacySize + 6 * dp;
dl->AddText(ovFont, ovFont->LegacySize, ImVec2(infoX, infoY), Warning(), "TRANSPARENT"); dl->AddText(ovFont, ovFont->LegacySize, ImVec2(infoX, infoY), Warning(), transLabelUpper.c_str());
infoY += ovFont->LegacySize + 2 * dp; infoY += ovFont->LegacySize + 2 * dp;
snprintf(buf, sizeof(buf), "%.8f", s_dispTransparent); snprintf(buf, sizeof(buf), "%.8f", s_dispTransparent);
dl->AddText(capFont, capFont->LegacySize, ImVec2(infoX, infoY), Warning(), buf); dl->AddText(capFont, capFont->LegacySize, ImVec2(infoX, infoY), Warning(), buf);
@@ -1727,7 +1761,7 @@ static void RenderBalanceTimeline(App* app) {
else else
ImGui::Dummy(ImVec2(0, S.drawElement("tabs.balance", "compact-hero-pad").sizeOr(8.0f) * vs)); ImGui::Dummy(ImVec2(0, S.drawElement("tabs.balance", "compact-hero-pad").sizeOr(8.0f) * vs));
{ {
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "TOTAL BALANCE"); Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("total_balance_label"));
ImGui::Dummy(ImVec2(0, 2 * dp)); ImGui::Dummy(ImVec2(0, 2 * dp));
snprintf(buf, sizeof(buf), "%.8f", s_dispTotal); snprintf(buf, sizeof(buf), "%.8f", s_dispTotal);
ImFont* heroFont = Type().h2(); ImFont* heroFont = Type().h2();
@@ -1816,10 +1850,16 @@ static void RenderBalanceTimeline(App* app) {
spec.rounding = glassRound; spec.rounding = glassRound;
struct SumCard { const char* label; ImU32 col; double val; bool isMoney; }; struct SumCard { const char* label; ImU32 col; double val; bool isMoney; };
auto upperTR = [](const char* key) {
std::string s = TR(key);
std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c){ return (char)std::toupper(c); });
return s;
};
std::string cShielded = upperTR("shielded"), cTransparent = upperTR("transparent"), cMarket = upperTR("market");
SumCard cards[3] = { SumCard cards[3] = {
{"SHIELDED", Success(), s_dispShielded, false}, {cShielded.c_str(), Success(), s_dispShielded, false},
{"TRANSPARENT", Warning(), s_dispTransparent, false}, {cTransparent.c_str(), Warning(), s_dispTransparent, false},
{"MARKET", Primary(), state.market.price_usd, true}, {cMarket.c_str(), Primary(), state.market.price_usd, true},
}; };
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
ImVec2 cMin(origin.x + i * (cardW + cGap), origin.y); ImVec2 cMin(origin.x + i * (cardW + cGap), origin.y);
@@ -1887,13 +1927,13 @@ static void RenderBalanceTwoRow(App* app) {
ImFont* capFont = Type().caption(); ImFont* capFont = Type().caption();
if (state.sync.syncing && state.sync.headers > 0) { if (state.sync.syncing && state.sync.headers > 0) {
float pct = static_cast<float>(state.sync.verification_progress) * 100.0f; float pct = static_cast<float>(state.sync.verification_progress) * 100.0f;
snprintf(buf, sizeof(buf), "Syncing %.1f%%", pct); snprintf(buf, sizeof(buf), TR("balance_syncing_pct"), pct);
Type().textColored(TypeStyle::Caption, Warning(), buf); Type().textColored(TypeStyle::Caption, Warning(), buf);
ImGui::SameLine(); ImGui::SameLine();
} }
if (state.mining.generate) { if (state.mining.generate) {
double hr = state.mining.localHashrate; double hr = state.mining.localHashrate;
snprintf(buf, sizeof(buf), "Mining %s", FormatHashrate(hr).c_str()); snprintf(buf, sizeof(buf), TR("balance_mining_rate"), FormatHashrate(hr).c_str());
Type().textColored(TypeStyle::Caption, WithAlpha(Success(), 200), buf); Type().textColored(TypeStyle::Caption, WithAlpha(Success(), 200), buf);
ImGui::SameLine(); ImGui::SameLine();
} }
@@ -1902,11 +1942,15 @@ static void RenderBalanceTwoRow(App* app) {
float btnW = S.drawElement("tabs.balance.two-row", "action-btn-width").sizeOr(80.0f); float btnW = S.drawElement("tabs.balance.two-row", "action-btn-width").sizeOr(80.0f);
float rightEdge = ImGui::GetWindowWidth() - Layout::spacingLg(); float rightEdge = ImGui::GetWindowWidth() - Layout::spacingLg();
ImGui::SameLine(rightEdge - btnW * 2 - Layout::spacingSm()); ImGui::SameLine(rightEdge - btnW * 2 - Layout::spacingSm());
if (TactileButton("Send", ImVec2(btnW, 0), S.resolveFont("button"))) { // Stable ## ids keep the button identity fixed across translations.
char sendBtn[64], recvBtn[64];
snprintf(sendBtn, sizeof(sendBtn), "%s##tworow-send", TR("send"));
snprintf(recvBtn, sizeof(recvBtn), "%s##tworow-receive", TR("receive"));
if (TactileButton(sendBtn, ImVec2(btnW, 0), S.resolveFont("button"))) {
app->setCurrentPage(NavPage::Send); app->setCurrentPage(NavPage::Send);
} }
ImGui::SameLine(); ImGui::SameLine();
if (TactileButton("Receive", ImVec2(btnW, 0), S.resolveFont("button"))) { if (TactileButton(recvBtn, ImVec2(btnW, 0), S.resolveFont("button"))) {
app->setCurrentPage(NavPage::Receive); app->setCurrentPage(NavPage::Receive);
} }
} }

View File

@@ -1050,8 +1050,11 @@ void ConsoleTab::drawVisibleLines(float padX, float lineHeight, bool hasTextFilt
ImVec2(cx, cy + sz * 0.7f), triCol); // ▼ expanded ImVec2(cx, cy + sz * 0.7f), triCol); // ▼ expanded
} }
// Click anywhere in the gutter cell for this line's first row toggles the fold. // Click anywhere in the gutter cell for this line's first row toggles the fold.
// Guard against a click that is actually dismissing the ConsoleContextMenu (or any
// popup) — the same popup guard the text-selection path uses (see mouse_in_output).
ImVec2 mp = ImGui::GetIO().MousePos; ImVec2 mp = ImGui::GetIO().MousePos;
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) && if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) &&
!ImGui::IsPopupOpen("", ImGuiPopupFlags_AnyPopup) &&
mp.x >= output_origin_.x - padX && mp.x < output_origin_.x && mp.x >= output_origin_.x - padX && mp.x < output_origin_.x &&
mp.y >= lineOrigin.y && mp.y < lineOrigin.y + lineHeight) { mp.y >= lineOrigin.y && mp.y < lineOrigin.y + lineHeight) {
pendingFoldToggle = i; pendingFoldToggle = i;

View File

@@ -40,6 +40,7 @@ static bool s_show_edit_dialog = false;
static bool s_show_contacts_settings = false; // Contacts customization modal (gear button) static bool s_show_contacts_settings = false; // Contacts customization modal (gear button)
static bool s_focus_edit_field = false; // focus the first field the frame the add/edit dialog opens static bool s_focus_edit_field = false; // focus the first field the frame the add/edit dialog opens
static int s_confirm_delete_idx = -1; // armed storage index; a 2nd Delete confirms static int s_confirm_delete_idx = -1; // armed storage index; a 2nd Delete confirms
static int s_confirm_avatar_del_idx = -1; // armed avatar-library index; a 2nd badge click confirms the (irreversible) file delete
static char s_edit_label[128] = ""; static char s_edit_label[128] = "";
static char s_edit_address[512] = ""; static char s_edit_address[512] = "";
static char s_edit_notes[512] = ""; static char s_edit_notes[512] = "";
@@ -555,7 +556,7 @@ void RenderContactsTab(App* app)
sdl->AddRectFilled(ImVec2(cMin.x + 2.0f * dp, cMin.y + 2.0f * dp), sdl->AddRectFilled(ImVec2(cMin.x + 2.0f * dp, cMin.y + 2.0f * dp),
ImVec2(cMax.x - 2.0f * dp, cMax.y - 2.0f * dp), ImVec2(cMax.x - 2.0f * dp, cMax.y - 2.0f * dp),
material::WithAlpha(material::Primary(), 210), (segH - 4.0f * dp) * 0.5f); material::WithAlpha(material::Primary(), 210), (segH - 4.0f * dp) * 0.5f);
ImU32 fg = active ? IM_COL32(255, 255, 255, 255) : (hov ? material::OnSurface() : material::OnSurfaceMedium()); ImU32 fg = active ? material::OnPrimary() : (hov ? material::OnSurface() : material::OnSurfaceMedium());
float igW = segIcoF->CalcTextSizeA(segIcoF->LegacySize, FLT_MAX, 0, segIco[i]).x; float igW = segIcoF->CalcTextSizeA(segIcoF->LegacySize, FLT_MAX, 0, segIco[i]).x;
float lbW = btnFont->CalcTextSizeA(btnFont->LegacySize, FLT_MAX, 0, segLbl[i]).x; float lbW = btnFont->CalcTextSizeA(btnFont->LegacySize, FLT_MAX, 0, segLbl[i]).x;
float gapI = 5.0f * dp; float gapI = 5.0f * dp;
@@ -747,12 +748,16 @@ void RenderContactsTab(App* app)
float dr = std::max(7.0f * dp, cell * 0.15f); float dr = std::max(7.0f * dp, cell * 0.15f);
ImVec2 dcc(mx.x - dr - 3.0f * dp, mn.y + dr + 3.0f * dp); ImVec2 dcc(mx.x - dr - 3.0f * dp, mn.y + dr + 3.0f * dp);
bool dhov = ImGui::IsMouseHoveringRect(ImVec2(dcc.x-dr, dcc.y-dr), ImVec2(dcc.x+dr, dcc.y+dr)); bool dhov = ImGui::IsMouseHoveringRect(ImVec2(dcc.x-dr, dcc.y-dr), ImVec2(dcc.x+dr, dcc.y+dr));
const int libIdx = n - 1;
const bool delArmed = (s_confirm_avatar_del_idx == libIdx);
ImGui::PushID(n); ImGui::PushID(n);
bool thumbClicked = ImGui::InvisibleButton("##avthumb", ImVec2(cell, cell)); bool thumbClicked = ImGui::InvisibleButton("##avthumb", ImVec2(cell, cell));
bool thumbHov = ImGui::IsItemHovered(); bool thumbHov = ImGui::IsItemHovered();
ImGui::PopID(); ImGui::PopID();
if (thumbHov || sel || hov) { // draw the delete badge if (thumbHov || sel || hov || delArmed) { // draw the delete badge (persist while armed)
gdl->AddCircleFilled(dcc, dr, dhov ? material::ReadableError() : IM_COL32(0, 0, 0, 175)); gdl->AddCircleFilled(dcc, dr, (dhov || delArmed) ? material::ReadableError() : IM_COL32(0, 0, 0, 175));
// While armed, ring the badge so the "click again to delete" state is unmistakable.
if (delArmed) gdl->AddCircle(dcc, dr + 1.5f * dp, material::ReadableError(), 0, 1.5f * dp);
ImFont* xf = material::Type().iconSmall(); ImFont* xf = material::Type().iconSmall();
float xsz = dr * 1.35f; float xsz = dr * 1.35f;
ImVec2 xs = xf->CalcTextSizeA(xsz, FLT_MAX, 0, ICON_MD_CLOSE); ImVec2 xs = xf->CalcTextSizeA(xsz, FLT_MAX, 0, ICON_MD_CLOSE);
@@ -760,11 +765,18 @@ void RenderContactsTab(App* app)
} }
if (dhov) { // badge takes priority over selecting the thumbnail if (dhov) { // badge takes priority over selecting the thumbnail
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
material::Tooltip("%s", TR("delete")); // Two-stage confirm: the first click arms this badge; a second click on the SAME
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) pendingDelete = n - 1; // badge deletes the image file (fs::remove is irreversible — no undo).
material::Tooltip("%s", delArmed ? TR("address_book_confirm_delete") : TR("delete"));
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
if (delArmed) { pendingDelete = libIdx; s_confirm_avatar_del_idx = -1; }
else s_confirm_avatar_del_idx = libIdx; // arm; requires a 2nd deliberate click
}
} else { } else {
if (thumbHov) ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); if (thumbHov) ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
if (thumbClicked) s_edit_avatar = "img:" + path; // Any click that lands off this badge (selecting the thumbnail or elsewhere)
// disarms it, so a stale armed badge can't be confirmed by an unrelated click.
if (thumbClicked) { s_edit_avatar = "img:" + path; s_confirm_avatar_del_idx = -1; }
} }
} }
col = (col + 1) % cols; col = (col + 1) % cols;

View File

@@ -137,6 +137,12 @@ struct PfEditState {
}; };
static PfEditState s_pfEdit; static PfEditState s_pfEdit;
// Two-stage delete arm/confirm for the master-list per-row delete icon (mirrors the
// contacts tab's s_confirm_delete_idx): first click on a row's trash arms it (icon turns
// red); a second click on the SAME row within the loop confirms the erase. Hovering/clicking
// a different row, or clicking off the icons, disarms. -1 = nothing armed.
static int s_pfConfirmDelIdx = -1;
// The pure price-series math lives in data/market_series.h; the selected chart range is // The pure price-series math lives in data/market_series.h; the selected chart range is
// s_mkt.chartInterval (0=Live 1=1H 2=1D 3=1W 4=1M, session-scoped, default 1M). // s_mkt.chartInterval (0=Live 1=1H 2=1D 3=1W 4=1M, session-scoped, default 1M).
@@ -837,6 +843,7 @@ static void RenderPortfolioEditor(App* app)
float rowH = 46.0f * dp; float rowH = 46.0f * dp;
float delSlot = 36.0f * dp; // reserved trailing space for the delete icon + margin float delSlot = 36.0f * dp; // reserved trailing space for the delete icon + margin
int clickedSel = -999, delRow = -1; // deferred: don't mutate `entries` mid-loop int clickedSel = -999, delRow = -1; // deferred: don't mutate `entries` mid-loop
bool armedThisFrame = false; // an arm-click also fires the row Selectable; don't let it disarm
for (int vi = 0; vi < (int)vis.size(); vi++) { for (int vi = 0; vi < (int)vis.size(); vi++) {
int i = vis[vi]; int i = vis[vi];
ImGui::PushID(i); ImGui::PushID(i);
@@ -877,6 +884,7 @@ static void RenderPortfolioEditor(App* app)
// Per-row delete icon (larger, inset from the edge). Hit-tested manually so it is // Per-row delete icon (larger, inset from the edge). Hit-tested manually so it is
// NOT an overlapping ImGui item over the row Selectable (which asserted on hover). // NOT an overlapping ImGui item over the row Selectable (which asserted on hover).
if (rowHov || selRow) { if (rowHov || selRow) {
bool armed = (s_pfConfirmDelIdx == i); // this row is arm-confirmed
ImFont* delFont = Type().iconMed(); ImFont* delFont = Type().iconMed();
float ds = delFont->LegacySize; float ds = delFont->LegacySize;
ImVec2 dc(rmx.x - Layout::spacingMd() - ds * 0.5f, rmn.y + rowH * 0.5f); ImVec2 dc(rmx.x - Layout::spacingMd() - ds * 0.5f, rmn.y + rowH * 0.5f);
@@ -884,11 +892,20 @@ static void RenderPortfolioEditor(App* app)
bool dhov = ImGui::IsMouseHoveringRect(dmn, dmx); bool dhov = ImGui::IsMouseHoveringRect(dmn, dmx);
if (dhov) { if (dhov) {
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) delRow = i; // Hovering a *different* row's trash disarms the previously armed one.
if (!armed && s_pfConfirmDelIdx >= 0) s_pfConfirmDelIdx = -1;
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
if (armed) delRow = i; // 2nd click on the armed row erases
else { s_pfConfirmDelIdx = i; // 1st click arms this row...
armedThisFrame = true; } // ...and must survive the row-select this same click triggers
}
} }
ImVec2 isz = delFont->CalcTextSizeA(ds, FLT_MAX, 0, ICON_MD_DELETE_OUTLINE); // A filled trash glyph while armed (vs outline) reinforces the recolor.
const char* delGlyph = armed ? ICON_MD_DELETE : ICON_MD_DELETE_OUTLINE;
ImU32 delCol = armed ? ReadableError() : (dhov ? Error() : OnSurfaceMedium());
ImVec2 isz = delFont->CalcTextSizeA(ds, FLT_MAX, 0, delGlyph);
mdl->AddText(delFont, ds, ImVec2(dc.x - isz.x * 0.5f, dc.y - isz.y * 0.5f), mdl->AddText(delFont, ds, ImVec2(dc.x - isz.x * 0.5f, dc.y - isz.y * 0.5f),
dhov ? Error() : OnSurfaceMedium(), ICON_MD_DELETE_OUTLINE); delCol, delGlyph);
} }
ImGui::PopID(); ImGui::PopID();
} }
@@ -905,10 +922,16 @@ static void RenderPortfolioEditor(App* app)
else if (delRow == s_pfEdit.sel) s_pfEdit.sel = std::min(delRow, (int)es.size() - 1); else if (delRow == s_pfEdit.sel) s_pfEdit.sel = std::min(delRow, (int)es.size() - 1);
PortfolioBeginEdit(app, s_pfEdit.sel); PortfolioBeginEdit(app, s_pfEdit.sel);
} }
} else if (clickedSel != -999) { s_pfConfirmDelIdx = -1; // erase done — storage indices shifted, drop the arm
} else if (clickedSel != -999 && !armedThisFrame) {
pfCommitIfNeeded(app); // auto-save the current group before switching pfCommitIfNeeded(app); // auto-save the current group before switching
PortfolioBeginEdit(app, clickedSel); PortfolioBeginEdit(app, clickedSel);
s_pfConfirmDelIdx = -1; // switching rows disarms any pending delete
} }
// A left-click that hit no row/action (empty list space) disarms a pending delete.
if (ImGui::IsWindowHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Left) &&
!ImGui::IsAnyItemHovered())
s_pfConfirmDelIdx = -1;
} }
ImGui::EndChild(); ImGui::EndChild();
ImGui::Dummy(ImVec2(0, Layout::spacingSm())); ImGui::Dummy(ImVec2(0, Layout::spacingSm()));

View File

@@ -83,7 +83,7 @@ void RenderMiningControls(App* app, const WalletState& state, const MiningInfo&
// over-estimates the grid width and lets the thread cells overflow the card (e.g. at 150%). // over-estimates the grid width and lets the thread cells overflow the card (e.g. at 150%).
float estControlsW = availWidth - std::min(schema::UI().drawElement("tabs.mining", "button-max-width-clamp").size * dp, miningBtnMaxW) - miningBtnGap; float estControlsW = availWidth - std::min(schema::UI().drawElement("tabs.mining", "button-max-width-clamp").size * dp, miningBtnMaxW) - miningBtnGap;
float innerW = estControlsW - pad * 2; float innerW = estControlsW - pad * 2;
float cellSz = std::clamp(schema::UI().drawElement("tabs.mining", "cell-size").size * vs, schema::UI().drawElement("tabs.mining", "cell-min-size").size, schema::UI().drawElement("tabs.mining", "cell-max-size").sizeOr(42.0f)); float cellSz = std::clamp(schema::UI().drawElement("tabs.mining", "cell-size").size * vs, schema::UI().drawElement("tabs.mining", "cell-min-size").size * dp, schema::UI().drawElement("tabs.mining", "cell-max-size").sizeOr(42.0f) * dp);
float cellGap = std::max(schema::UI().drawElement("tabs.mining", "cell-gap-min").size, cellSz * schema::UI().drawElement("tabs.mining", "cell-gap-ratio").size); float cellGap = std::max(schema::UI().drawElement("tabs.mining", "cell-gap-min").size, cellSz * schema::UI().drawElement("tabs.mining", "cell-gap-ratio").size);
int cols = std::max(1, std::min(nOpts, (int)(innerW / (cellSz + cellGap)))); int cols = std::max(1, std::min(nOpts, (int)(innerW / (cellSz + cellGap))));
int rows = (nOpts + cols - 1) / cols; int rows = (nOpts + cols - 1) / cols;
@@ -162,14 +162,35 @@ void RenderMiningControls(App* app, const WalletState& state, const MiningInfo&
const float sy = curY; const float sy = curY;
ImVec2 savedCur = ImGui::GetCursorScreenPos(); ImVec2 savedCur = ImGui::GetCursorScreenPos();
material::IconButtonStyle sideStyle; // -/+ buttons drawn in the same style as the thread tiles (rounded rect + border + centered
sideStyle.color = OnSurfaceMedium(); // glyph, same fill/border/rounding). Hit-tested with a real InvisibleButton so they stay
sideStyle.hoverBg = StateHover(); // popup-safe.
const float stepRound = schema::UI().drawElement("tabs.mining", "cell-rounding").size;
// atBound: the stepper can't move further (already at 1 for "-" or max for "+"). When
// at the bound the control is a no-op, so render it disabled: dim glyph/border, no hand
// cursor, no tooltip — and swallow the click so it reads as inert.
auto rectStepBtn = [&](const char* id, const char* glyph, float x, const char* tip, bool atBound) -> bool {
ImGui::SetCursorScreenPos(ImVec2(x, sy));
ImGui::InvisibleButton(id, ImVec2(sideW, fieldH));
const bool hov = ImGui::IsItemHovered() && !atBound;
const bool clk = ImGui::IsItemClicked() && !atBound;
const ImVec2 mn(x, sy), mx(x + sideW, sy + fieldH);
dl->AddRectFilled(mn, mx, hov ? WithAlpha(OnSurface(), 25) : WithAlpha(OnSurface(), 8), stepRound);
dl->AddRect(mn, mx, WithAlpha(OnSurface(), atBound ? 15 : (hov ? 80 : 35)), stepRound);
const ImVec2 gsz = stepFont->CalcTextSizeA(stepFont->LegacySize, FLT_MAX, 0, glyph);
dl->AddText(stepFont, stepFont->LegacySize,
ImVec2(x + (sideW - gsz.x) * 0.5f, sy + (fieldH - gsz.y) * 0.5f),
WithAlpha(OnSurface(), atBound ? 30 : (hov ? 160 : 80)), glyph);
if (hov) {
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
if (tip && tip[0]) material::Tooltip("%s", tip);
}
return clk;
};
// "-" button // "-" button (disabled at 1 thread)
sideStyle.tooltip = TR("mining_threads_minus_tooltip"); if (rectStepBtn("##ThreadMinus", ICON_MD_REMOVE, sx, TR("mining_threads_minus_tooltip"),
ImGui::SetCursorScreenPos(ImVec2(sx, sy)); s_selected_threads <= 1))
if (material::IconButton("##ThreadMinus", ICON_MD_REMOVE, stepFont, ImVec2(sideW, fieldH), sideStyle))
applyThreads(s_selected_threads - 1); applyThreads(s_selected_threads - 1);
sx += sideW + g; sx += sideW + g;
@@ -188,10 +209,9 @@ void RenderMiningControls(App* app, const WalletState& state, const MiningInfo&
ImGui::PopStyleVar(); ImGui::PopStyleVar();
sx += fieldW + g; sx += fieldW + g;
// "+" button // "+" button (disabled at max_threads)
sideStyle.tooltip = TR("mining_threads_plus_tooltip"); if (rectStepBtn("##ThreadPlus", ICON_MD_ADD, sx, TR("mining_threads_plus_tooltip"),
ImGui::SetCursorScreenPos(ImVec2(sx, sy)); s_selected_threads >= max_threads))
if (material::IconButton("##ThreadPlus", ICON_MD_ADD, stepFont, ImVec2(sideW, fieldH), sideStyle))
applyThreads(s_selected_threads + 1); applyThreads(s_selected_threads + 1);
ImGui::SetCursorScreenPos(savedCur); ImGui::SetCursorScreenPos(savedCur);
@@ -738,15 +758,9 @@ void RenderMiningControls(App* app, const WalletState& state, const MiningInfo&
dl->AddRect(cMin, cMax, WithAlpha(Primary(), (int)(160 + 60 * glow)), rounding, 0, schema::UI().drawElement("tabs.mining", "active-cell-border-thickness").size); dl->AddRect(cMin, cMax, WithAlpha(Primary(), (int)(160 + 60 * glow)), rounding, 0, schema::UI().drawElement("tabs.mining", "active-cell-border-thickness").size);
} else if (active) { } else if (active) {
// Active but not mining: solid primary fill // Active but not mining: solid primary fill
ImU32 pri = Primary(); ImU32 fillCol = material::WithAlpha(Primary(), hovered ? 220 : 180);
int priR = (pri >> 0) & 0xFF;
int priG = (pri >> 8) & 0xFF;
int priB = (pri >> 16) & 0xFF;
ImU32 fillCol = hovered
? IM_COL32(priR, priG, priB, 220)
: IM_COL32(priR, priG, priB, 180);
dl->AddRectFilled(cMin, cMax, fillCol, rounding); dl->AddRectFilled(cMin, cMax, fillCol, rounding);
dl->AddRect(cMin, cMax, IM_COL32(priR, priG, priB, 255), rounding, 0, schema::UI().drawElement("tabs.mining", "cell-border-thickness").size); dl->AddRect(cMin, cMax, material::WithAlpha(Primary(), 255), rounding, 0, schema::UI().drawElement("tabs.mining", "cell-border-thickness").size);
} else { } else {
// Inactive: dim outline // Inactive: dim outline
ImU32 fillCol = hovered ImU32 fillCol = hovered
@@ -938,7 +952,7 @@ void RenderMiningControls(App* app, const WalletState& state, const MiningInfo&
else if (poolBlockedBySolo) else if (poolBlockedBySolo)
material::Tooltip("%s", TR("mining_stop_solo_for_pool")); material::Tooltip("%s", TR("mining_stop_solo_for_pool"));
else if (poolNeedsPayout) else if (poolNeedsPayout)
material::Tooltip("%s", "Enter a payout address first (generate a Z address)"); material::Tooltip("%s", TR("mining_pool_needs_payout_tooltip"));
else else
material::Tooltip("%s", isMiningActive ? TR("stop_mining") : TR("start_mining")); material::Tooltip("%s", isMiningActive ? TR("stop_mining") : TR("start_mining"));
} }

View File

@@ -42,6 +42,20 @@ void RenderMiningModeToggle(App* app, const WalletState& state, const MiningInfo
bool& s_pool_mode, char (&s_pool_url)[256], char (&s_pool_worker)[256], bool& s_pool_mode, char (&s_pool_url)[256], char (&s_pool_worker)[256],
bool& s_pool_settings_dirty) bool& s_pool_settings_dirty)
{ {
// Arm/confirm for the destructive X (delete) on saved pools & workers: a first click on a
// row's X arms it (the X turns red + relabels via the trash glyph) and a second click within
// a short window actually removes it; moving to another row or letting the window lapse disarms.
// Same idiom as contacts_tab.cpp's s_confirm_delete_idx, keyed by row index within each list.
static int s_armed_pool_idx = -1; // armed saved-pool row (-1 = none)
static int s_armed_worker_idx = -1; // armed saved-worker row (-1 = none)
static double s_armed_pool_time = 0.0;
static double s_armed_worker_time = 0.0;
const double kArmWindowSecs = 3.0; // second click must land within this window
const double nowTime = ImGui::GetTime();
// Lapse the arm if the confirm window elapsed.
if (s_armed_pool_idx >= 0 && nowTime - s_armed_pool_time > kArmWindowSecs) s_armed_pool_idx = -1;
if (s_armed_worker_idx >= 0 && nowTime - s_armed_worker_time > kArmWindowSecs) s_armed_worker_idx = -1;
const bool soloMiningAvailable = app->supportsSoloMining(); const bool soloMiningAvailable = app->supportsSoloMining();
float toggleW = schema::UI().drawElement("tabs.mining", "mode-toggle-width").size * hs; float toggleW = schema::UI().drawElement("tabs.mining", "mode-toggle-width").size * hs;
float toggleH = schema::UI().drawElement("tabs.mining", "mode-toggle-height").size; float toggleH = schema::UI().drawElement("tabs.mining", "mode-toggle-height").size;
@@ -296,9 +310,11 @@ void RenderMiningModeToggle(App* app, const WalletState& state, const MiningInfo
ImFont* rowFont = ImGui::GetFont(); ImFont* rowFont = ImGui::GetFont();
float rowFontSz = ImGui::GetFontSize(); float rowFontSz = ImGui::GetFontSize();
float rowH = ImGui::GetFrameHeight(); float rowH = ImGui::GetFrameHeight();
int rowIdx = 0;
for (const auto& url : savedUrls) { for (const auto& url : savedUrls) {
ImGui::PushID(url.c_str()); ImGui::PushID(url.c_str());
bool isCurrent = (std::string(s_pool_url) == url); bool isCurrent = (std::string(s_pool_url) == url);
bool armed = (s_armed_pool_idx == rowIdx); // this row's X is arm-confirmed
ImVec2 rowMin = ImGui::GetCursorScreenPos(); ImVec2 rowMin = ImGui::GetCursorScreenPos();
ImVec2 rowMax(rowMin.x + popupInnerW, rowMin.y + rowH); ImVec2 rowMax(rowMin.x + popupInnerW, rowMin.y + rowH);
ImGui::InvisibleButton("##row", ImVec2(popupInnerW, rowH)); ImGui::InvisibleButton("##row", ImVec2(popupInnerW, rowH));
@@ -319,40 +335,46 @@ void RenderMiningModeToggle(App* app, const WalletState& state, const MiningInfo
pdl->AddText(rowFont, rowFontSz, pdl->AddText(rowFont, rowFontSz,
ImVec2(rowMin.x + textPadX, textY), ImVec2(rowMin.x + textPadX, textY),
isCurrent ? Primary() : OnSurface(), urlDisp.c_str()); isCurrent ? Primary() : OnSurface(), urlDisp.c_str());
// X button — flush with right edge, icon centered // X (delete) button — flush with right edge, icon centered. First click on the X
// arms it (filled trash glyph + readable-error tint); a second click within the
// arm window actually removes. Armed row stays lit even when not hovered so the
// pending-delete state is visible.
{ {
ImVec2 xMin(rowMax.x - xZoneW, rowMin.y); ImVec2 xMin(rowMax.x - xZoneW, rowMin.y);
ImVec2 xMax(rowMax.x, rowMax.y); ImVec2 xMax(rowMax.x, rowMax.y);
if (inXZone) { if (inXZone) {
pdl->AddRectFilled(xMin, xMax, IM_COL32(255, 80, 80, 30)); pdl->AddRectFilled(xMin, xMax, IM_COL32(255, 80, 80, 30));
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
material::Tooltip("%s", TR("mining_remove")); material::Tooltip("%s", armed ? TR("address_book_confirm_delete")
} else if (rowHov) { : TR("mining_remove"));
// Show faint X when row is hovered
ImFont* icoF = Type().iconSmall();
const char* xIcon = ICON_MD_CLOSE;
ImVec2 iSz = icoF->CalcTextSizeA(icoF->LegacySize, FLT_MAX, 0, xIcon);
ImVec2 xCenter((xMin.x + xMax.x) * 0.5f, (xMin.y + xMax.y) * 0.5f);
pdl->AddText(icoF, icoF->LegacySize,
ImVec2(xCenter.x - iSz.x * 0.5f, xCenter.y - iSz.y * 0.5f),
OnSurfaceDisabled(), xIcon);
} }
// Always draw icon when hovering X zone if (armed || inXZone || rowHov) {
if (inXZone) {
ImFont* icoF = Type().iconSmall(); ImFont* icoF = Type().iconSmall();
const char* xIcon = ICON_MD_CLOSE; // Filled trash while armed reinforces the recolor; otherwise a plain X.
const char* xIcon = armed ? ICON_MD_DELETE : ICON_MD_CLOSE;
ImU32 xCol = armed ? ReadableError()
: (inXZone ? Error() : OnSurfaceDisabled());
ImVec2 iSz = icoF->CalcTextSizeA(icoF->LegacySize, FLT_MAX, 0, xIcon); ImVec2 iSz = icoF->CalcTextSizeA(icoF->LegacySize, FLT_MAX, 0, xIcon);
ImVec2 xCenter((xMin.x + xMax.x) * 0.5f, (xMin.y + xMax.y) * 0.5f); ImVec2 xCenter((xMin.x + xMax.x) * 0.5f, (xMin.y + xMax.y) * 0.5f);
pdl->AddText(icoF, icoF->LegacySize, pdl->AddText(icoF, icoF->LegacySize,
ImVec2(xCenter.x - iSz.x * 0.5f, xCenter.y - iSz.y * 0.5f), ImVec2(xCenter.x - iSz.x * 0.5f, xCenter.y - iSz.y * 0.5f),
Error(), xIcon); xCol, xIcon);
} }
} }
// Hovering a *different* row's X disarms the previously armed one.
if (inXZone && !armed && s_armed_pool_idx >= 0)
s_armed_pool_idx = -1;
// Click handling // Click handling
if (rowClk) { if (rowClk) {
if (inXZone) { if (inXZone) {
urlToRemove = url; if (armed) {
urlToRemove = url; // 2nd click on the armed row removes
} else {
s_armed_pool_idx = rowIdx; // 1st click arms this row
s_armed_pool_time = nowTime;
}
} else { } else {
s_armed_pool_idx = -1; // selecting a row disarms
strncpy(s_pool_url, url.c_str(), sizeof(s_pool_url) - 1); strncpy(s_pool_url, url.c_str(), sizeof(s_pool_url) - 1);
s_pool_url[sizeof(s_pool_url) - 1] = '\0'; s_pool_url[sizeof(s_pool_url) - 1] = '\0';
s_pool_settings_dirty = true; s_pool_settings_dirty = true;
@@ -362,10 +384,12 @@ void RenderMiningModeToggle(App* app, const WalletState& state, const MiningInfo
if (rowHov && !inXZone) if (rowHov && !inXZone)
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
ImGui::PopID(); ImGui::PopID();
rowIdx++;
} }
if (!urlToRemove.empty()) { if (!urlToRemove.empty()) {
app->settings()->removeSavedPoolUrl(urlToRemove); app->settings()->removeSavedPoolUrl(urlToRemove);
app->settings()->save(); app->settings()->save();
s_armed_pool_idx = -1; // removal shifts indices — drop the arm
} }
} }
ImGui::EndPopup(); ImGui::EndPopup();
@@ -460,9 +484,11 @@ void RenderMiningModeToggle(App* app, const WalletState& state, const MiningInfo
ImFont* wRowFont = ImGui::GetFont(); ImFont* wRowFont = ImGui::GetFont();
float wRowFontSz = ImGui::GetFontSize(); float wRowFontSz = ImGui::GetFontSize();
float wRowH = ImGui::GetFrameHeight(); float wRowH = ImGui::GetFrameHeight();
int wRowIdx = 0;
for (const auto& addr : savedWorkers) { for (const auto& addr : savedWorkers) {
ImGui::PushID(addr.c_str()); ImGui::PushID(addr.c_str());
bool isCurrent = (std::string(s_pool_worker) == addr); bool isCurrent = (std::string(s_pool_worker) == addr);
bool armed = (s_armed_worker_idx == wRowIdx); // this row's X is arm-confirmed
ImVec2 rowMin = ImGui::GetCursorScreenPos(); ImVec2 rowMin = ImGui::GetCursorScreenPos();
ImVec2 rowMax(rowMin.x + wPopupInnerW, rowMin.y + wRowH); ImVec2 rowMax(rowMin.x + wPopupInnerW, rowMin.y + wRowH);
ImGui::InvisibleButton("##row", ImVec2(wPopupInnerW, wRowH)); ImGui::InvisibleButton("##row", ImVec2(wPopupInnerW, wRowH));
@@ -486,38 +512,46 @@ void RenderMiningModeToggle(App* app, const WalletState& state, const MiningInfo
// Tooltip for long addresses // Tooltip for long addresses
if (rowHov && !inXZone) if (rowHov && !inXZone)
material::Tooltip("%s", addr.c_str()); material::Tooltip("%s", addr.c_str());
// X button — flush with right edge, icon centered // X (delete) button — flush with right edge, icon centered. First click on the X
// arms it (filled trash glyph + readable-error tint); a second click within the
// arm window actually removes. Armed row stays lit even when not hovered so the
// pending-delete state is visible.
{ {
ImVec2 xMin(rowMax.x - wXZoneW, rowMin.y); ImVec2 xMin(rowMax.x - wXZoneW, rowMin.y);
ImVec2 xMax(rowMax.x, rowMax.y); ImVec2 xMax(rowMax.x, rowMax.y);
if (inXZone) { if (inXZone) {
pdl->AddRectFilled(xMin, xMax, IM_COL32(255, 80, 80, 30)); pdl->AddRectFilled(xMin, xMax, IM_COL32(255, 80, 80, 30));
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
material::Tooltip("%s", TR("mining_remove")); material::Tooltip("%s", armed ? TR("address_book_confirm_delete")
} else if (rowHov) { : TR("mining_remove"));
ImFont* icoF = Type().iconSmall();
const char* xIcon = ICON_MD_CLOSE;
ImVec2 iSz = icoF->CalcTextSizeA(icoF->LegacySize, FLT_MAX, 0, xIcon);
ImVec2 xCenter((xMin.x + xMax.x) * 0.5f, (xMin.y + xMax.y) * 0.5f);
pdl->AddText(icoF, icoF->LegacySize,
ImVec2(xCenter.x - iSz.x * 0.5f, xCenter.y - iSz.y * 0.5f),
OnSurfaceDisabled(), xIcon);
} }
if (inXZone) { if (armed || inXZone || rowHov) {
ImFont* icoF = Type().iconSmall(); ImFont* icoF = Type().iconSmall();
const char* xIcon = ICON_MD_CLOSE; // Filled trash while armed reinforces the recolor; otherwise a plain X.
const char* xIcon = armed ? ICON_MD_DELETE : ICON_MD_CLOSE;
ImU32 xCol = armed ? ReadableError()
: (inXZone ? Error() : OnSurfaceDisabled());
ImVec2 iSz = icoF->CalcTextSizeA(icoF->LegacySize, FLT_MAX, 0, xIcon); ImVec2 iSz = icoF->CalcTextSizeA(icoF->LegacySize, FLT_MAX, 0, xIcon);
ImVec2 xCenter((xMin.x + xMax.x) * 0.5f, (xMin.y + xMax.y) * 0.5f); ImVec2 xCenter((xMin.x + xMax.x) * 0.5f, (xMin.y + xMax.y) * 0.5f);
pdl->AddText(icoF, icoF->LegacySize, pdl->AddText(icoF, icoF->LegacySize,
ImVec2(xCenter.x - iSz.x * 0.5f, xCenter.y - iSz.y * 0.5f), ImVec2(xCenter.x - iSz.x * 0.5f, xCenter.y - iSz.y * 0.5f),
Error(), xIcon); xCol, xIcon);
} }
} }
// Hovering a *different* row's X disarms the previously armed one.
if (inXZone && !armed && s_armed_worker_idx >= 0)
s_armed_worker_idx = -1;
// Click handling // Click handling
if (rowClk) { if (rowClk) {
if (inXZone) { if (inXZone) {
addrToRemove = addr; if (armed) {
addrToRemove = addr; // 2nd click on the armed row removes
} else {
s_armed_worker_idx = wRowIdx; // 1st click arms this row
s_armed_worker_time = nowTime;
}
} else { } else {
s_armed_worker_idx = -1; // selecting a row disarms
strncpy(s_pool_worker, addr.c_str(), sizeof(s_pool_worker) - 1); strncpy(s_pool_worker, addr.c_str(), sizeof(s_pool_worker) - 1);
s_pool_worker[sizeof(s_pool_worker) - 1] = '\0'; s_pool_worker[sizeof(s_pool_worker) - 1] = '\0';
s_pool_settings_dirty = true; s_pool_settings_dirty = true;
@@ -527,10 +561,12 @@ void RenderMiningModeToggle(App* app, const WalletState& state, const MiningInfo
if (rowHov && !inXZone) if (rowHov && !inXZone)
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
ImGui::PopID(); ImGui::PopID();
wRowIdx++;
} }
if (!addrToRemove.empty()) { if (!addrToRemove.empty()) {
app->settings()->removeSavedPoolWorker(addrToRemove); app->settings()->removeSavedPoolWorker(addrToRemove);
app->settings()->save(); app->settings()->save();
s_armed_worker_idx = -1; // removal shifts indices — drop the arm
} }
} }
ImGui::EndPopup(); ImGui::EndPopup();

View File

@@ -665,14 +665,26 @@ void RenderPeersTab(App* app)
dl->AddCircleFilled(ImVec2(cx + S.drawElement("tabs.peers", "ping-dot-x-offset").size, cy + body2->LegacySize * 0.5f), pingDotR, dotCol); dl->AddCircleFilled(ImVec2(cx + S.drawElement("tabs.peers", "ping-dot-x-offset").size, cy + body2->LegacySize * 0.5f), pingDotR, dotCol);
float addrX = cx + S.drawElement("tabs.peers", "address-x-offset").size; float addrX = cx + S.drawElement("tabs.peers", "address-x-offset").size;
// Reserve the line-1 trailing zone (ping + direction pill live on the far right,
// the nearest being the ping at innerW - pingW - spacingXl*3). A long IPv6+port
// addr must clip before it so it can't run under those. Mirror the ping formula
// conservatively and leave a gap.
float line1RightLimit = rowPos.x + innerW - Layout::spacingXl() * 3 -
capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, "0000ms").x -
Layout::spacingSm();
dl->PushClipRect(ImVec2(addrX, rowPos.y), ImVec2(line1RightLimit, rowEnd.y), true);
dl->AddText(body2, body2->LegacySize, ImVec2(addrX, cy), OnSurface(), peer.addr.c_str()); dl->AddText(body2, body2->LegacySize, ImVec2(addrX, cy), OnSurface(), peer.addr.c_str());
dl->PopClipRect();
// Seed node icon — rendered right after the IP address // Seed node icon — rendered right after the IP address, but never past the
// reserved trailing zone (a long addr would otherwise push it into the ping text).
if (IsSeedNode(peer.addr)) { if (IsSeedNode(peer.addr)) {
ImVec2 addrSz = body2->CalcTextSizeA(body2->LegacySize, FLT_MAX, 0, peer.addr.c_str()); ImVec2 addrSz = body2->CalcTextSizeA(body2->LegacySize, FLT_MAX, 0, peer.addr.c_str());
ImFont* iconFont = Type().iconSmall(); ImFont* iconFont = Type().iconSmall();
float iconY = cy + (body2->LegacySize - iconFont->LegacySize) * 0.5f; float iconY = cy + (body2->LegacySize - iconFont->LegacySize) * 0.5f;
dl->AddText(iconFont, iconFont->LegacySize, ImVec2(addrX + addrSz.x + Layout::spacingSm(), iconY), WithAlpha(Success(), 200), ICON_MD_GRASS); float seedIconX = std::min(addrX + addrSz.x + Layout::spacingSm(),
line1RightLimit - iconFont->LegacySize);
dl->AddText(iconFont, iconFont->LegacySize, ImVec2(seedIconX, iconY), WithAlpha(Success(), 200), ICON_MD_GRASS);
} }
{ {
@@ -696,30 +708,55 @@ void RenderPeersTab(App* app)
} }
float cy2 = cy + body2->LegacySize + Layout::spacingXs(); float cy2 = cy + body2->LegacySize + Layout::spacingXs();
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx + S.drawElement("tabs.peers", "address-x-offset").size, cy2), float subverX = cx + S.drawElement("tabs.peers", "address-x-offset").size;
OnSurfaceDisabled(), peer.subver.c_str());
// Reserve the line-2 trailing widths up front so an untrusted, arbitrarily long
// remote subver cannot push the TLS badge / ban-score off the row. Order right→left:
// ban-score pinned far right, then the TLS/no-TLS badge, then the clipped subver text.
float line2RightLimit = rowPos.x + innerW - Layout::spacingLg();
float banScoreLeftX = line2RightLimit;
char banBuf[16];
ImU32 banCol = 0;
bool haveBanScore = peer.banscore > 0;
if (haveBanScore) {
snprintf(banBuf, sizeof(banBuf), TR("peers_ban_score"), peer.banscore);
banCol = peer.banscore > 50 ? Error() : Warning();
float banW = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, banBuf).x;
banScoreLeftX = line2RightLimit - banW;
// The TLS/no-TLS badge sits to the LEFT of the ban score with a gap.
line2RightLimit = banScoreLeftX - Layout::spacingLg();
}
float tlsBadgeW = std::max(S.drawElement("tabs.peers", "tls-badge-min-width").size, S.drawElement("tabs.peers", "tls-badge-width").size * hs);
float noTlsW = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, TR("peers_no_tls")).x;
float badgeW = peer.tls_cipher.empty() ? noTlsW : tlsBadgeW;
// Hard right boundary for the subver text column, leaving room for the badge.
float subverMaxX = line2RightLimit - badgeW - Layout::spacingSm();
if (subverMaxX < subverX) subverMaxX = subverX;
float verW = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, peer.subver.c_str()).x; float verW = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, peer.subver.c_str()).x;
float tlsBadgeW = std::max(S.drawElement("tabs.peers", "tls-badge-min-width").size, S.drawElement("tabs.peers", "tls-badge-width").size * hs); dl->PushClipRect(ImVec2(subverX, rowPos.y), ImVec2(subverMaxX, rowEnd.y), true);
dl->AddText(capFont, capFont->LegacySize, ImVec2(subverX, cy2),
OnSurfaceDisabled(), peer.subver.c_str());
dl->PopClipRect();
// Pin the badge just after the (clipped) subver, but never past the reserved zone.
float badgeX = std::min(subverX + verW + Layout::spacingSm(), subverMaxX + Layout::spacingSm());
if (!peer.tls_cipher.empty()) { if (!peer.tls_cipher.empty()) {
ImU32 tlsBg = WithAlpha(Success(), 25); ImU32 tlsBg = WithAlpha(Success(), 25);
ImU32 tlsFg = WithAlpha(Success(), 200); ImU32 tlsFg = WithAlpha(Success(), 200);
ImVec2 tlsMin(cx + S.drawElement("tabs.peers", "address-x-offset").size + verW + Layout::spacingSm(), cy2); ImVec2 tlsMin(badgeX, cy2);
ImVec2 tlsMax(tlsMin.x + tlsBadgeW, tlsMin.y + capFont->LegacySize + 2); ImVec2 tlsMax(tlsMin.x + tlsBadgeW, tlsMin.y + capFont->LegacySize + 2);
dl->AddRectFilled(tlsMin, tlsMax, tlsBg, S.drawElement("tabs.peers", "tls-badge-rounding").size); dl->AddRectFilled(tlsMin, tlsMax, tlsBg, S.drawElement("tabs.peers", "tls-badge-rounding").size);
dl->AddText(capFont, capFont->LegacySize, ImVec2(tlsMin.x + 4, cy2 + 1), tlsFg, "TLS"); dl->AddText(capFont, capFont->LegacySize, ImVec2(tlsMin.x + 4, cy2 + 1), tlsFg, "TLS");
} else { } else {
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx + S.drawElement("tabs.peers", "address-x-offset").size + verW + Layout::spacingSm(), cy2), dl->AddText(capFont, capFont->LegacySize, ImVec2(badgeX, cy2),
WithAlpha(Error(), 140), TR("peers_no_tls")); WithAlpha(Error(), 140), TR("peers_no_tls"));
} }
if (peer.banscore > 0) { if (haveBanScore) {
char banBuf[16];
snprintf(banBuf, sizeof(banBuf), TR("peers_ban_score"), peer.banscore);
ImU32 banCol = peer.banscore > 50 ? Error() : Warning();
ImVec2 banSz = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, banBuf);
dl->AddText(capFont, capFont->LegacySize, dl->AddText(capFont, capFont->LegacySize,
ImVec2(rowPos.x + innerW - banSz.x - Layout::spacingLg(), cy2), banCol, banBuf); ImVec2(banScoreLeftX, cy2), banCol, banBuf);
} }
ImGui::InvisibleButton("##peerRow", ImVec2(innerW, rowH)); ImGui::InvisibleButton("##peerRow", ImVec2(innerW, rowH));

View File

@@ -514,21 +514,25 @@ void RenderReceiveTab(App* app)
if (state.addresses.empty()) { if (state.addresses.empty()) {
ImVec2 emptyMin = ImGui::GetCursorScreenPos(); ImVec2 emptyMin = ImGui::GetCursorScreenPos();
float emptyH = S.drawElement("tabs.receive", "skeleton-height").size; // Hand-drawn skeleton geometry is absolute px — scale heights/offsets/rounding by
// dpiScale() so it doesn't render native-thin at >100% (width ratios multiply formW, leave as-is).
float dp = Layout::dpiScale();
float emptyH = S.drawElement("tabs.receive", "skeleton-height").size * dp;
ImVec2 emptyMax(emptyMin.x + formW, emptyMin.y + emptyH); ImVec2 emptyMax(emptyMin.x + formW, emptyMin.y + emptyH);
DrawGlassPanel(dl, emptyMin, emptyMax, glassSpec); DrawGlassPanel(dl, emptyMin, emptyMax, glassSpec);
float alpha = (float)(schema::UI().drawElement("animations", "skeleton-base").size + schema::UI().drawElement("animations", "skeleton-amp").size * std::sin(ImGui::GetTime() * schema::UI().drawElement("animations", "pulse-speed-slow").size)); float alpha = (float)(schema::UI().drawElement("animations", "skeleton-base").size + schema::UI().drawElement("animations", "skeleton-amp").size * std::sin(ImGui::GetTime() * schema::UI().drawElement("animations", "pulse-speed-slow").size));
ImU32 skelCol = IM_COL32(255, 255, 255, (int)(alpha * 255)); ImU32 skelCol = IM_COL32(255, 255, 255, (int)(alpha * 255));
float skelRound = schema::UI().drawElement("tabs.receive", "skeleton-rounding").size * dp;
dl->AddRectFilled( dl->AddRectFilled(
ImVec2(emptyMin.x + Layout::spacingLg(), emptyMin.y + Layout::spacingLg()), ImVec2(emptyMin.x + Layout::spacingLg(), emptyMin.y + Layout::spacingLg()),
ImVec2(emptyMin.x + formW * S.drawElement("tabs.receive", "skeleton-bar1-width-ratio").size, emptyMin.y + Layout::spacingLg() + S.drawElement("tabs.receive", "skeleton-bar1-height").size), ImVec2(emptyMin.x + formW * S.drawElement("tabs.receive", "skeleton-bar1-width-ratio").size, emptyMin.y + Layout::spacingLg() + S.drawElement("tabs.receive", "skeleton-bar1-height").size * dp),
skelCol, schema::UI().drawElement("tabs.receive", "skeleton-rounding").size); skelCol, skelRound);
dl->AddRectFilled( dl->AddRectFilled(
ImVec2(emptyMin.x + Layout::spacingLg(), emptyMin.y + Layout::spacingLg() + S.drawElement("tabs.receive", "skeleton-bar2-top").size), ImVec2(emptyMin.x + Layout::spacingLg(), emptyMin.y + Layout::spacingLg() + S.drawElement("tabs.receive", "skeleton-bar2-top").size * dp),
ImVec2(emptyMin.x + formW * S.drawElement("tabs.receive", "skeleton-bar2-width-ratio").size, emptyMin.y + Layout::spacingLg() + S.drawElement("tabs.receive", "skeleton-bar2-bottom").size), ImVec2(emptyMin.x + formW * S.drawElement("tabs.receive", "skeleton-bar2-width-ratio").size, emptyMin.y + Layout::spacingLg() + S.drawElement("tabs.receive", "skeleton-bar2-bottom").size * dp),
skelCol, schema::UI().drawElement("tabs.receive", "skeleton-rounding").size); skelCol, skelRound);
dl->AddText(capFont, capFont->LegacySize, dl->AddText(capFont, capFont->LegacySize,
ImVec2(emptyMin.x + Layout::spacingLg(), emptyMin.y + emptyH - S.drawElement("tabs.receive", "skeleton-text-bottom-offset").size), ImVec2(emptyMin.x + Layout::spacingLg(), emptyMin.y + emptyH - S.drawElement("tabs.receive", "skeleton-text-bottom-offset").size * dp),
OnSurfaceDisabled(), TR("loading_addresses")); OnSurfaceDisabled(), TR("loading_addresses"));
ImGui::Dummy(ImVec2(formW, emptyH)); ImGui::Dummy(ImVec2(formW, emptyH));
ImGui::EndGroup(); ImGui::EndGroup();
@@ -778,7 +782,7 @@ void RenderReceiveTab(App* app)
size_t memo_len = strlen(s_request_memo); size_t memo_len = strlen(s_request_memo);
size_t memoMax = (size_t)S.drawElement("business", "memo-max-length").size; size_t memoMax = (size_t)S.drawElement("business", "memo-max-length").size;
bool memoAtCap = memo_len + 1 >= memoMax; bool memoAtCap = memo_len + 1 >= memoMax;
snprintf(buf, sizeof(buf), "%zu / %zu bytes", memo_len, memoMax); snprintf(buf, sizeof(buf), TR("byte_count_fmt"), memo_len, memoMax);
Type().textColored(TypeStyle::Caption, memoAtCap ? Warning() : OnSurfaceDisabled(), buf); Type().textColored(TypeStyle::Caption, memoAtCap ? Warning() : OnSurfaceDisabled(), buf);
} }

View File

@@ -424,11 +424,13 @@ static void RenderAmountBar(ImDrawList* dl, double available, float innerW,
? std::clamp((float)((s_amount + s_fee) / available), 0.0f, 1.0f) ? std::clamp((float)((s_amount + s_fee) / available), 0.0f, 1.0f)
: 0.0f; : 0.0f;
float maxBtnW = schema::UI().drawElement("tabs.send", "amount-bar-max-btn-width").size; const float dp = Layout::dpiScale();
float maxBtnW = schema::UI().drawElement("tabs.send", "amount-bar-max-btn-width").size * dp;
float gap = Layout::spacingMd(); float gap = Layout::spacingMd();
float barW = innerW - maxBtnW - gap; float barW = innerW - maxBtnW - gap;
if (barW < schema::UI().drawElement("tabs.send", "progress-bar-min-width").size) barW = schema::UI().drawElement("tabs.send", "progress-bar-min-width").size; float minBarW = schema::UI().drawElement("tabs.send", "progress-bar-min-width").size * dp;
float barH = schema::UI().drawElement("tabs.send", "amount-bar-height").size; if (barW < minBarW) barW = minBarW;
float barH = schema::UI().drawElement("tabs.send", "amount-bar-height").size * dp;
float barRound = barH * 0.5f; float barRound = barH * 0.5f;
ImVec2 barMin = ImGui::GetCursorScreenPos(); ImVec2 barMin = ImGui::GetCursorScreenPos();
@@ -1000,7 +1002,7 @@ static void RenderActionButtons(App* app, float width, float vScale,
ImGui::BeginDisabled(!can_send); ImGui::BeginDisabled(!can_send);
char sendId[64]; char sendId[64];
snprintf(sendId, sizeof(sendId), "Review Send%s", suffix); snprintf(sendId, sizeof(sendId), "%s##ReviewSend%s", TR("review_send"), suffix);
if (TactileButton(sendId, ImVec2(sendBtnW, btnH), S.resolveFont(S.button("tabs.send", "send-button").font))) { if (TactileButton(sendId, ImVec2(sendBtnW, btnH), S.resolveFont(S.button("tabs.send", "send-button").font))) {
s_show_confirm = true; s_show_confirm = true;
} }
@@ -1020,7 +1022,7 @@ static void RenderActionButtons(App* app, float width, float vScale,
else if (total > available) else if (total > available)
material::Tooltip("%s", TR("send_tooltip_exceeds_balance")); material::Tooltip("%s", TR("send_tooltip_exceeds_balance"));
else if (!sourceSpendable) else if (!sourceSpendable)
material::Tooltip("%s", "View-only address — no spending key, cannot send"); material::Tooltip("%s", TR("send_tooltip_view_only"));
else if (s_sending) else if (s_sending)
material::Tooltip("%s", TR("send_tooltip_in_progress")); material::Tooltip("%s", TR("send_tooltip_in_progress"));
} }
@@ -1033,7 +1035,7 @@ static void RenderActionButtons(App* app, float width, float vScale,
ImGui::PushStyleColor(ImGuiCol_Border, ImGui::ColorConvertU32ToFloat4(OnSurfaceDisabled())); ImGui::PushStyleColor(ImGuiCol_Border, ImGui::ColorConvertU32ToFloat4(OnSurfaceDisabled()));
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, S.drawElement("tabs.send", "cancel-btn-border-size").size); ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, S.drawElement("tabs.send", "cancel-btn-border-size").size);
char clearId[64]; char clearId[64];
snprintf(clearId, sizeof(clearId), "Cancel%s", suffix); snprintf(clearId, sizeof(clearId), "%s##Cancel%s", TR("cancel"), suffix);
if (TactileButton(clearId, ImVec2(cancelBtnW, btnH), S.resolveFont(S.button("tabs.send", "clear-button").font))) { if (TactileButton(clearId, ImVec2(cancelBtnW, btnH), S.resolveFont(S.button("tabs.send", "clear-button").font))) {
if (FormHasData()) { if (FormHasData()) {
s_clear_confirm_pending = true; s_clear_confirm_pending = true;
@@ -1377,7 +1379,7 @@ void RenderSendTab(App* app)
s_preview_text.c_str(), s_preview_text.c_str() + std::min(s_preview_text.size(), (size_t)S.drawElement("tabs.send", "paste-preview-max-chars").size)); s_preview_text.c_str(), s_preview_text.c_str() + std::min(s_preview_text.size(), (size_t)S.drawElement("tabs.send", "paste-preview-max-chars").size));
} }
if (TactileButton("Paste##to", ImVec2(pasteW, 0), S.resolveFont(S.button("tabs.send", "paste-button").font))) { if (TactileButton((std::string(TR("paste")) + "##to").c_str(), ImVec2(pasteW, 0), S.resolveFont(S.button("tabs.send", "paste-button").font))) {
if (s_paste_previewing) { if (s_paste_previewing) {
// Commit the preview // Commit the preview
snprintf(s_to_address, sizeof(s_to_address), "%s", s_preview_text.c_str()); snprintf(s_to_address, sizeof(s_to_address), "%s", s_preview_text.c_str());
@@ -1547,7 +1549,7 @@ void RenderSendTab(App* app)
size_t memo_len = strlen(s_memo); size_t memo_len = strlen(s_memo);
size_t memoMax = (size_t)S.drawElement("business", "memo-max-length").size; size_t memoMax = (size_t)S.drawElement("business", "memo-max-length").size;
bool memoAtCap = memo_len + 1 >= memoMax; bool memoAtCap = memo_len + 1 >= memoMax;
snprintf(buf, sizeof(buf), "%zu / %zu bytes", memo_len, memoMax); snprintf(buf, sizeof(buf), TR("byte_count_fmt"), memo_len, memoMax);
Type().textColored(TypeStyle::Caption, memoAtCap ? Warning() : OnSurfaceDisabled(), buf); Type().textColored(TypeStyle::Caption, memoAtCap ? Warning() : OnSurfaceDisabled(), buf);
} }

View File

@@ -823,7 +823,7 @@ void I18n::loadBuiltinEnglish()
strings_["confirm_reinstall_daemon_title"] = "Install Bundled Daemon"; strings_["confirm_reinstall_daemon_title"] = "Install Bundled Daemon";
strings_["confirm_reinstall_daemon_msg"] = "This stops the daemon, overwrites the installed dragonxd (and dragonx-cli/dragonx-tx) with the versions bundled in this wallet build, then restarts the node. Use this to recover or update the node binary."; strings_["confirm_reinstall_daemon_msg"] = "This stops the daemon, overwrites the installed dragonxd (and dragonx-cli/dragonx-tx) with the versions bundled in this wallet build, then restarts the node. Use this to recover or update the node binary.";
strings_["confirm_reinstall_daemon_safe"] = "Your wallet, keys and blockchain data are not touched — only the daemon program files are replaced."; strings_["confirm_reinstall_daemon_safe"] = "Your wallet, keys and blockchain data are not touched — only the daemon program files are replaced.";
strings_["daemon_update_title"] = "Update the node daemon?"; strings_["daemon_update_prompt_title"] = "Update the node daemon?";
strings_["daemon_update_body"] = "This wallet build bundles a newer DragonX node than the one currently installed. Updating replaces the installed dragonxd (and dragonx-cli/dragonx-tx), then stops and restarts the node so the new version takes effect. Recommended — a newer node can add features (e.g. seed-phrase support) the old one lacks."; strings_["daemon_update_body"] = "This wallet build bundles a newer DragonX node than the one currently installed. Updating replaces the installed dragonxd (and dragonx-cli/dragonx-tx), then stops and restarts the node so the new version takes effect. Recommended — a newer node can add features (e.g. seed-phrase support) the old one lacks.";
strings_["daemon_update_safe"] = "Your wallet, keys and blockchain data are not touched — only the daemon program files are replaced. If you deliberately run a custom node, choose Keep current."; strings_["daemon_update_safe"] = "Your wallet, keys and blockchain data are not touched — only the daemon program files are replaced. If you deliberately run a custom node, choose Keep current.";
strings_["daemon_update_now"] = "Update now"; strings_["daemon_update_now"] = "Update now";
@@ -855,6 +855,10 @@ void I18n::loadBuiltinEnglish()
strings_["lite_passphrase_label"] = "Passphrase"; strings_["lite_passphrase_label"] = "Passphrase";
strings_["lite_validate"] = "Validate"; strings_["lite_validate"] = "Validate";
strings_["lite_working"] = "Working…"; strings_["lite_working"] = "Working…";
strings_["lite_enter_wallet_path"] = "Enter a wallet path";
strings_["lite_enter_all_seed_words"] = "Enter all 24 seed words to restore (got %d)";
strings_["lite_could_not_start"] = "Could not start the operation";
strings_["lite_backend_unavailable"] = "Lite wallet backend unavailable";
strings_["lite_wallet_ready"] = "Wallet ready"; strings_["lite_wallet_ready"] = "Wallet ready";
strings_["lite_backup_keys"] = "Backup & keys"; strings_["lite_backup_keys"] = "Backup & keys";
strings_["lite_show_seed"] = "Show seed"; strings_["lite_show_seed"] = "Show seed";
@@ -1101,6 +1105,14 @@ void I18n::loadBuiltinEnglish()
strings_["balance_history_collecting"] = "Balance history — collecting data..."; strings_["balance_history_collecting"] = "Balance history — collecting data...";
strings_["balance_shielded_fmt"] = "Shielded: %.8f"; strings_["balance_shielded_fmt"] = "Shielded: %.8f";
strings_["balance_transparent_fmt"] = "Transparent: %.8f"; strings_["balance_transparent_fmt"] = "Transparent: %.8f";
strings_["total_balance_label"] = "Total Balance";
strings_["balance_syncing_pct"] = "Syncing %.1f%%";
strings_["balance_mining_rate"] = "Mining %s";
strings_["balance_layout_switched"] = "Layout: %s";
strings_["byte_count_fmt"] = "%zu / %zu bytes";
strings_["quick_send"] = "Quick Send";
strings_["quick_receive"] = "Quick Receive";
strings_["tile_click_to_open"] = "Click to open";
strings_["your_addresses"] = "Your Addresses"; strings_["your_addresses"] = "Your Addresses";
strings_["z_addresses"] = "Z-Addresses"; strings_["z_addresses"] = "Z-Addresses";
strings_["t_addresses"] = "T-Addresses"; strings_["t_addresses"] = "T-Addresses";
@@ -1428,9 +1440,18 @@ void I18n::loadBuiltinEnglish()
strings_["loading_stall_body"] = "The daemon has been initializing for %.0fs. This can be normal after an update or on first launch (loading the block index or rescanning) — it will connect automatically once ready."; strings_["loading_stall_body"] = "The daemon has been initializing for %.0fs. This can be normal after an update or on first launch (loading the block index or rescanning) — it will connect automatically once ready.";
strings_["loading_stall_hint"] = "Still stuck? Open Settings and use Restart Daemon, or check the Console for details."; strings_["loading_stall_hint"] = "Still stuck? Open Settings and use Restart Daemon, or check the Console for details.";
strings_["sb_plaintext_remote_blocked"] = "Refusing to send RPC credentials over plaintext to a remote host. Add rpcallowplaintext=1 to DRAGONX.conf to allow it, or enable TLS with rpctls=1."; strings_["sb_plaintext_remote_blocked"] = "Refusing to send RPC credentials over plaintext to a remote host. Add rpcallowplaintext=1 to DRAGONX.conf to allow it, or enable TLS with rpctls=1.";
strings_["rpc_plaintext_remote_warning"] = "Remote RPC is using plaintext HTTP. Add rpctls=1 to DRAGONX.conf if your daemon supports TLS.";
strings_["settings_open_log_folder"] = "Open log folder"; strings_["settings_open_log_folder"] = "Open log folder";
strings_["settings_copy_diagnostics"] = "Copy diagnostics"; strings_["settings_copy_diagnostics"] = "Copy diagnostics";
strings_["settings_diagnostics_copied"] = "Diagnostics copied to clipboard"; strings_["settings_diagnostics_copied"] = "Diagnostics copied to clipboard";
strings_["settings_rpc_ok"] = "RPC connection OK";
strings_["settings_rpc_error_prefix"] = "RPC error: ";
strings_["settings_not_connected"] = "Not connected to daemon";
strings_["settings_theme_refreshed"] = "Theme list refreshed";
strings_["settings_saved"] = "Settings saved";
strings_["settings_reloaded"] = "Settings reloaded from disk";
strings_["settings_ztx_cleared"] = "Z-transaction history cleared";
strings_["settings_ztx_not_found"] = "No history file found";
strings_["tt_open_log_folder"] = "Open the folder containing the debug and crash logs"; strings_["tt_open_log_folder"] = "Open the folder containing the debug and crash logs";
strings_["tt_copy_diagnostics"] = "Copy a support snapshot (version, daemon/wallet/log state — no secrets) to the clipboard"; strings_["tt_copy_diagnostics"] = "Copy a support snapshot (version, daemon/wallet/log state — no secrets) to the clipboard";
strings_["sb_dragonxd_running"] = "dragonxd running"; strings_["sb_dragonxd_running"] = "dragonxd running";
@@ -1886,6 +1907,7 @@ void I18n::loadBuiltinEnglish()
strings_["mining_payout_invalid"] = "Not a valid DragonX address — fix it before starting, or mining rewards are lost."; strings_["mining_payout_invalid"] = "Not a valid DragonX address — fix it before starting, or mining rewards are lost.";
strings_["mining_est_daily_pool_sub"] = "rough solo-equivalent, before pool fee"; strings_["mining_est_daily_pool_sub"] = "rough solo-equivalent, before pool fee";
strings_["mining_generate_z_address_hint"] = "Generate a Z address in the Receive tab to use as your payout address"; strings_["mining_generate_z_address_hint"] = "Generate a Z address in the Receive tab to use as your payout address";
strings_["mining_pool_needs_payout_tooltip"] = "Enter a payout address first (generate a Z address)";
strings_["mining_pool"] = "Pool"; strings_["mining_pool"] = "Pool";
strings_["mining_payout_foreign"] = "⚠ This payout address isn't in your current wallet — mined rewards would go to a different wallet. Update it if you switched wallets."; strings_["mining_payout_foreign"] = "⚠ This payout address isn't in your current wallet — mined rewards would go to a different wallet. Update it if you switched wallets.";
strings_["mining_pool_hashrate"] = "Pool Hashrate"; strings_["mining_pool_hashrate"] = "Pool Hashrate";
@@ -2155,6 +2177,7 @@ void I18n::loadBuiltinEnglish()
strings_["send_tooltip_not_connected"] = "Not connected to daemon"; strings_["send_tooltip_not_connected"] = "Not connected to daemon";
strings_["send_tooltip_select_source"] = "Select a source address first"; strings_["send_tooltip_select_source"] = "Select a source address first";
strings_["send_tooltip_syncing"] = "Wait for blockchain to sync"; strings_["send_tooltip_syncing"] = "Wait for blockchain to sync";
strings_["send_tooltip_view_only"] = "View-only address — no spending key, cannot send";
strings_["send_total"] = "Total"; strings_["send_total"] = "Total";
strings_["send_tx_failed"] = "Transaction failed"; strings_["send_tx_failed"] = "Transaction failed";
strings_["send_tx_sent"] = "Transaction sent!"; strings_["send_tx_sent"] = "Transaction sent!";