9 Commits
Author SHA1 Message Date
DanS 24fa921f8c fix: console not connected when fast-lane RPC still connecting
The console tab was passed fast_rpc_ even before its async connection
completed, causing 'Not connected to daemon' errors despite the main
RPC being connected and sync data flowing. Fall back to the main
rpc_/worker_ until fast_rpc_ reports isConnected().
2026-04-03 11:34:32 -05:00
DanS b25723c739 build: macOS universal binary (arm64+x86_64) with deployment target 11.0
- Set CMAKE_OSX_DEPLOYMENT_TARGET and CMAKE_OSX_ARCHITECTURES before
  project() so they propagate to all FetchContent dependencies (SDL3, etc.)
- build.sh: native mac release builds universal binary, detects and
  rebuilds single-arch libsodium, verifies with lipo, exports
  MACOSX_DEPLOYMENT_TARGET; dev build uses correct build/mac directory
- fetch-libsodium.sh: build arm64 and x86_64 separately then merge with
  lipo on native macOS; fix sha256sum unavailable on macOS (use shasum)
2026-04-03 10:55:07 -05:00
DanS 6951e6113e feat: thread benchmark, GPU-aware idle mining, thread scaling fix
- Add pool mining thread benchmark: cycles through thread counts with
  20s warmup + 10s measurement to find optimal setting for CPU
- Add GPU-aware idle detection: GPU utilization >= 10% (video, games)
  treats system as active; toggle in mining tab header (default: on)
  Supports AMD sysfs, NVIDIA nvidia-smi, Intel freq ratio; -1 on macOS
- Fix idle thread scaling: use getRequestedThreads() for immediate
  thread count instead of xmrig API threads_active which lags on restart
- Apply active thread count on initial mining start when user is active
- Skip idle mining adjustments while benchmark is running
- Disable thread grid drag-to-select during benchmark
- Add idle_gpu_aware setting with JSON persistence (default: true)
- Add 7 i18n English strings for benchmark and GPU-aware tooltips
2026-04-01 17:06:05 -05:00
DanS bc1f7aa13a update build output filenames to include version info 2026-03-25 11:24:21 -05:00
DanS dcd6564f7d update hardcoded version for mac dmg build 2026-03-25 11:18:03 -05:00
DanS 74691fa255 feat: track shielded send txids via z_viewtransaction
Extract txids from completed z_sendmany operations and store in
send_txids_ so pure shielded sends are discoverable. The network
thread includes them in the enrichment set, calls z_viewtransaction,
caches results in viewtx_cache_, and removes them from send_txids_.
2026-03-25 11:06:09 -05:00
DanS e130ebc6da fix: macOS block index corruption, dbcache auto-sizing, import key rescan height
- Shutdown: 3-phase stop (wait for RPC stop → SIGTERM → SIGKILL) prevents
  LevelDB flush interruption on macOS/APFS that caused full re-sync on restart
- dbcache: auto-detect RAM and set -dbcache to 12.5% (clamped 450-4096 MB)
  on macOS (sysctl), Linux (sysconf), and Windows (GlobalMemoryStatusEx)
- Import key: pass user-entered start height to z_importkey and trigger
  rescanblockchain from that height for t-key imports
- Bump version to 1.1.1
2026-03-25 11:00:14 -05:00
DanS 7a37f68e9b update mac icons 2026-03-19 14:46:33 -05:00
M eac5f8ee08 macOS port: build, rendering, daemon, and mining fixes
Build & setup:
- Fix setup.sh and build.sh for macOS (bundle daemon, xmrig, sapling params, asmap.dat into .app)
- Fix CMakeLists.txt libsodium linking for macOS
- Fix incbin.h to use __DATA,__const section on macOS
- Remove vendored libsodium-1.0.18 source tree (use fetch script instead)
- Remove prebuilt-binaries/xmrig (replaced by xmrig-hac)
- Add .DS_Store to .gitignore

Rendering & UI:
- Use GLSL #version 150 and OpenGL 3.2 Core Profile on macOS
- Force dpiScale=1.0 on macOS to fix Retina double-scaling
- Set default window/UI opacity to 100% on Mac/Linux
- Add scroll fade shader guard for macOS GL compatibility
- Add ImGui error recovery around render loop and mining tab

Daemon & bootstrap:
- Fix getDragonXDataDir() to return ~/Library/Application Support/Hush/DRAGONX/ on macOS
- Fix isPortInUse() with connect() fallback (no /proc/net/tcp on macOS)
- Increase daemon watchdog timeout from 3s to 15s
- Add daemon status indicator (colored dot + label) in wizard bootstrap phases

Mining tab:
- Fix EmbeddedDaemon::getMemoryUsageMB() crash on macOS (was using Linux /proc)
- Fix XmrigManager::getMemoryUsageMB() to use ps on macOS instead of /proc
- Restructure RenderMiningTab with wrapper pattern for exception safety
- Fix default pool URL to include port (pool.dragonx.is:3433)
2026-03-19 14:26:04 -05:00
67 changed files with 25808 additions and 36160 deletions
-1
View File
@@ -37,7 +37,6 @@ asmap.dat
/memory
/todo.md
/.github/
/ObsidianDragon-agent/
# macOS
.DS_Store
+1 -18
View File
@@ -15,14 +15,11 @@ if(APPLE)
endif()
project(ObsidianDragon
VERSION 1.2.0
VERSION 1.1.1
LANGUAGES C CXX
DESCRIPTION "DragonX Cryptocurrency Wallet"
)
# Pre-release suffix (e.g. "-rc1", "-beta2"). Leave empty for stable releases.
set(DRAGONX_VERSION_SUFFIX "-rc1")
# C++17 standard
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
@@ -422,20 +419,6 @@ configure_file(
@ONLY
)
# INCBIN uses .incbin assembler directives that reference font files at
# assembly time — CMake doesn't track these implicit dependencies.
# Tell CMake that the generated source depends on the actual font binaries
# so a font file change triggers recompilation.
set_source_files_properties(
${CMAKE_BINARY_DIR}/generated/embedded_fonts.cpp
PROPERTIES OBJECT_DEPENDS
"${CMAKE_SOURCE_DIR}/res/fonts/Ubuntu-R.ttf;\
${CMAKE_SOURCE_DIR}/res/fonts/Ubuntu-Light.ttf;\
${CMAKE_SOURCE_DIR}/res/fonts/Ubuntu-Medium.ttf;\
${CMAKE_SOURCE_DIR}/res/fonts/MaterialIcons-Regular.ttf;\
${CMAKE_SOURCE_DIR}/res/fonts/NotoSansCJK-Subset.ttf"
)
add_executable(ObsidianDragon
${APP_SOURCES}
${CMAKE_BINARY_DIR}/generated/embedded_fonts.cpp
+1 -1
View File
@@ -20,7 +20,7 @@
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
VERSION="1.2.0-rc1"
VERSION="1.1.1"
# ── Colours ──────────────────────────────────────────────────────────────────
RED='\033[0;31m'
+1 -1
View File
@@ -5,7 +5,7 @@
<assemblyIdentity
type="win32"
name="DragonX.ObsidianDragon.Wallet"
version="1.2.0.0"
version="1.1.1.0"
processorArchitecture="amd64"
/>
Binary file not shown.
Binary file not shown.
+2 -143
View File
@@ -43,8 +43,6 @@
"address_url": "Adress-URL",
"addresses_appear_here": "Ihre Empfangsadressen erscheinen hier, sobald Sie verbunden sind.",
"advanced": "ERWEITERT",
"advanced_effects": "Erweiterte Effekte...",
"ago": "her",
"all_filter": "Alle",
"allow_custom_fees": "Benutzerdefinierte Gebühren erlauben",
"amount": "Betrag",
@@ -92,30 +90,12 @@
"block_timestamp": "Zeitstempel:",
"block_transactions": "Transaktionen:",
"blockchain_syncing": "Blockchain synchronisiert (%.1f%%)... Guthaben könnten ungenau sein.",
"bootstrap_daemon_running": "Daemon läuft",
"bootstrap_daemon_stopped": "Daemon gestoppt",
"bootstrap_daemon_stopping": "Daemon wird gestoppt...",
"bootstrap_desc": "Laden Sie einen Blockchain-Bootstrap herunter, um die anfängliche Synchronisierung drastisch zu beschleunigen. Dies lädt einen Snapshot der Blockchain herunter und extrahiert ihn in Ihr Datenverzeichnis.",
"bootstrap_downloading": "Bootstrap wird heruntergeladen...",
"bootstrap_extracting": "Blockchain-Daten werden extrahiert...",
"bootstrap_failed": "Bootstrap fehlgeschlagen",
"bootstrap_mirror": "Spiegel",
"bootstrap_mirror_tooltip": "Vom Spiegel herunterladen (bootstrap2.dragonx.is).\nVerwenden Sie dies, wenn der Hauptdownload langsam ist oder fehlschlägt.",
"bootstrap_restart_daemon": "Daemon neu starten",
"bootstrap_success": "Bootstrap abgeschlossen",
"bootstrap_success_desc": "Blockchain-Daten wurden erfolgreich extrahiert. Starten Sie den Daemon, um ab dem Bootstrap-Punkt zu synchronisieren.",
"bootstrap_trust_warning": "Verwenden Sie nur bootstrap.dragonx.is oder bootstrap2.dragonx.is. Die Verwendung von Dateien aus nicht vertrauenswürdigen Quellen könnte Ihren Knoten gefährden.",
"bootstrap_verifying": "Prüfsummen werden überprüft...",
"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.",
"cancel": "Abbrechen",
"characters": "Zeichen",
"choose_icon": "Symbol wählen",
"clear": "Leeren",
"clear_all_bans": "Alle Sperren aufheben",
"clear_anyway": "Trotzdem löschen",
"clear_form_confirm": "Alle Formularfelder leeren?",
"clear_icon": "Symbol entfernen",
"clear_request": "Anfrage leeren",
"click_copy_address": "Klicken zum Kopieren der Adresse",
"click_copy_uri": "Klicken zum Kopieren der URI",
@@ -126,12 +106,8 @@
"confirm_clear_ztx_title": "Z-Tx-Verlauf löschen bestätigen",
"confirm_clear_ztx_warning1": "Das Löschen des Z-Transaktionsverlaufs kann dazu führen, dass Ihr geschirmtes Guthaben als 0 angezeigt wird, bis ein Wallet-Rescan durchgeführt wird.",
"confirm_clear_ztx_warning2": "Wenn dies geschieht, müssen Sie Ihre Z-Adresse-Privatschlüssel mit aktiviertem Rescan neu importieren, um Ihr Guthaben wiederherzustellen.",
"confirm_delete_blockchain_msg": "Dies stoppt den Daemon, löscht alle Blockchain-Daten (blocks, chainstate, peers) und startet eine neue Synchronisierung. Dies kann mehrere Stunden dauern.",
"confirm_delete_blockchain_safe": "Ihre wallet.dat, Konfiguration und Transaktionshistorie sind sicher und werden nicht gelöscht.",
"confirm_delete_blockchain_title": "Blockchain-Daten löschen",
"confirm_send": "Senden bestätigen",
"confirm_transaction": "Transaktion bestätigen",
"confirm_transfer": "Überweisung bestätigen",
"confirmations": "Bestätigungen",
"confirmations_display": "%d Bestätigungen | %s",
"confirmed": "Bestätigt",
@@ -196,7 +172,6 @@
"console_welcome": "Willkommen bei ObsidianDragon Konsole",
"console_zoom_in": "Vergrößern",
"console_zoom_out": "Verkleinern",
"copied": "Kopiert!",
"copy": "Kopieren",
"copy_address": "Vollständige Adresse kopieren",
"copy_error": "Fehler kopieren",
@@ -205,45 +180,22 @@
"copy_uri": "URI kopieren",
"current_price": "Aktueller Preis",
"custom_fees": "Benutzerdefinierte Gebühren",
"daemon_version": "Daemon",
"dark": "Dunkel",
"date": "Datum",
"date_label": "Datum:",
"debug_logging": "FEHLERPROTOKOLLIERUNG",
"delete": "Löschen",
"delete_blockchain": "Blockchain löschen",
"delete_blockchain_confirm": "Löschen & Neu synchronisieren",
"deshielding_warning": "Warnung: Dies wird Gelder von einer privaten (Z) Adresse auf eine transparente (T) Adresse ent-schirmen.",
"difficulty": "Schwierigkeit",
"disconnected": "Getrennt",
"dismiss": "Verwerfen",
"display": "Anzeige",
"download": "Herunterladen",
"download_bootstrap": "Bootstrap herunterladen",
"dragonx_green": "DragonX (Grün)",
"edit": "Bearbeiten",
"error": "Fehler",
"error_format": "Fehler: %s",
"est_time_to_block": "Gesch. Zeit bis Block",
"exit": "Beenden",
"explorer": "Explorer",
"explorer_block_detail": "Block",
"explorer_block_hash": "Hash",
"explorer_block_height": "Höhe",
"explorer_block_merkle": "Merkle-Wurzel",
"explorer_block_size": "Größe",
"explorer_block_time": "Zeit",
"explorer_block_txs": "Transaktionen",
"explorer_chain_stats": "Kette",
"explorer_invalid_query": "Geben Sie eine Blockhöhe oder einen 64-stelligen Hash ein",
"explorer_mempool": "Mempool",
"explorer_mempool_size": "Größe",
"explorer_mempool_txs": "Transaktionen",
"explorer_recent_blocks": "Letzte Blöcke",
"explorer_search": "Suchen",
"explorer_section": "EXPLORER",
"explorer_tx_outputs": "Ausgaben",
"explorer_tx_size": "Größe",
"explorer": "EXPLORER",
"export": "Exportieren",
"export_csv": "CSV exportieren",
"export_keys_btn": "Schlüssel exportieren",
@@ -272,22 +224,14 @@
"fetch_prices": "Preise abrufen",
"file": "Datei",
"file_save_location": "Datei wird gespeichert in: ~/.config/ObsidianDragon/",
"filter": "Filtern...",
"font_scale": "Schriftgröße",
"force_quit": "Sofort beenden",
"force_quit_confirm_msg": "Dies wird den Daemon sofort beenden ohne sauberes Herunterfahren.\nDies kann den Blockchain-Index beschädigen und eine Neusynchronisierung erfordern.",
"force_quit_confirm_title": "Sofort beenden?",
"force_quit_warning": "Dies wird den Daemon sofort beenden ohne sauberes Herunterfahren. Kann eine Neusynchronisierung der Blockchain erfordern.",
"force_quit_yes": "Sofort beenden",
"from": "Von",
"from_upper": "VON",
"full_details": "Alle Details",
"general": "Allgemein",
"generating": "Wird generiert",
"go_to_receive": "Zum Empfangen",
"height": "Höhe",
"help": "Hilfe",
"hidden_tag": " (versteckt)",
"hide": "Ausblenden",
"hide_address": "Adresse ausblenden",
"hide_zero_balances": "Nullsalden ausblenden",
@@ -309,9 +253,6 @@
"import_key_warning": "Warnung: Teilen Sie niemals Ihre privaten Schlüssel! Das Importieren von Schlüsseln aus nicht vertrauenswürdigen Quellen kann Ihr Wallet gefährden.",
"import_key_z_format": "Z-Adresse Ausgabeschlüssel (secret-extended-key-...)",
"import_private_key": "Privaten Schlüssel importieren...",
"incorrect_passphrase": "Falsches Passwort",
"incorrect_pin": "Falsche PIN",
"insufficient_funds": "Unzureichendes Guthaben für diesen Betrag plus Gebühr.",
"invalid_address": "Ungültiges Adressformat",
"ip_address": "IP-Adresse",
"keep": "Behalten",
@@ -325,7 +266,6 @@
"key_export_viewing_keys_zonly": "Anzeigeschlüssel sind nur für geschirmte (z) Adressen verfügbar",
"key_export_viewing_warning": "Dieser Betrachtungsschlüssel ermöglicht es anderen, Ihre eingehenden Transaktionen und Ihr Guthaben zu sehen, aber NICHT Ihre Gelder auszugeben. Teilen Sie ihn nur mit vertrauenswürdigen Parteien.",
"label": "Bezeichnung:",
"label_placeholder": "z.B. Ersparnisse, Mining...",
"language": "Sprache",
"light": "Hell",
"loading": "Laden...",
@@ -371,13 +311,6 @@
"mining_address_copied": "Mining-Adresse kopiert",
"mining_all_time": "Gesamt",
"mining_already_saved": "Pool-URL bereits gespeichert",
"mining_benchmark_cancel": "Benchmark abbrechen",
"mining_benchmark_cooling": "Abkühlen",
"mining_benchmark_dismiss": "Schließen",
"mining_benchmark_result": "Optimal",
"mining_benchmark_stabilizing": "Stabilisierung",
"mining_benchmark_testing": "Testen",
"mining_benchmark_tooltip": "Optimale Thread-Anzahl für diese CPU finden",
"mining_block_copied": "Block-Hash kopiert",
"mining_chart_1m_ago": "vor 1m",
"mining_chart_5m_ago": "vor 5m",
@@ -397,15 +330,8 @@
"mining_filter_tip_all": "Alle Einnahmen anzeigen",
"mining_filter_tip_pool": "Nur Pool-Einnahmen anzeigen",
"mining_filter_tip_solo": "Nur Solo-Einnahmen anzeigen",
"mining_generate_z_address_hint": "Generieren Sie eine Z-Adresse im Empfangen-Tab als Auszahlungsadresse",
"mining_idle_gpu_off_tooltip": "Uneingeschränkt: EIN\nNur Tastatur-/Mauseingabe bestimmt den Leerlauf\nKlicken für GPU-bewusste Erkennung",
"mining_idle_gpu_on_tooltip": "GPU-bewusst: EIN\nGPU-Aktivität (Video, Spiele) verhindert Leerlauf-Mining\nKlicken für uneingeschränkten Modus",
"mining_idle_off_tooltip": "Leerlauf-Mining aktivieren",
"mining_idle_on_tooltip": "Leerlauf-Mining deaktivieren",
"mining_idle_scale_off_tooltip": "Start/Stopp-Modus: EIN\nKlicken zum Wechsel auf Thread-Skalierung",
"mining_idle_scale_on_tooltip": "Thread-Skalierung: EIN\nKlicken zum Wechsel auf Start/Stopp-Modus",
"mining_idle_threads_active_tooltip": "Threads bei Benutzeraktivität",
"mining_idle_threads_idle_tooltip": "Threads im Leerlauf",
"mining_local_hashrate": "Lokale Hashrate",
"mining_mine": "Minen",
"mining_mining_addr": "Mining-Adr.",
@@ -462,7 +388,6 @@
"no_addresses_available": "Keine Adressen verfügbar",
"no_addresses_match": "Keine Adressen passen zum Filter",
"no_addresses_with_balance": "Keine Adressen mit Guthaben",
"no_addresses_yet": "Noch keine Adressen",
"no_matching": "Keine passenden Transaktionen",
"no_recent_receives": "Keine kürzlichen Empfänge",
"no_recent_sends": "Keine kürzlichen Sendungen",
@@ -528,7 +453,6 @@
"peers_upper": "PEERS",
"peers_version": "Version",
"pending": "Ausstehend",
"pin_not_set": "PIN nicht gesetzt. Verwenden Sie das Passwort zum Entsperren.",
"ping": "Ping",
"price_chart": "Preisdiagramm",
"qr_code": "QR-Code",
@@ -549,9 +473,7 @@
"recent_received": "KÜRZLICH EMPFANGEN",
"recent_sends": "KÜRZLICH GESENDET",
"recipient": "EMPFÄNGER",
"recipient_balance": "Empfänger: %.8f → %.8f DRGX",
"recv_type": "Empf.",
"reduce_motion": "Bewegung reduzieren",
"refresh": "Aktualisieren",
"refresh_now": "Jetzt aktualisieren",
"remove_favorite": "Favorit entfernen",
@@ -571,10 +493,7 @@
"request_uri_copied": "Zahlungs-URI in Zwischenablage kopiert",
"rescan": "Neu scannen",
"reset_to_defaults": "Standardwerte zurücksetzen",
"restarting_after_encryption": "Daemon wird nach Verschlüsselung neu gestartet...",
"restore_address": "Adresse wiederherstellen",
"result_preview": "Ergebnisvorschau",
"retry": "Wiederholen",
"review_send": "Senden prüfen",
"rpc_host": "RPC-Host",
"rpc_pass": "Passwort",
@@ -583,39 +502,6 @@
"save": "Speichern",
"save_settings": "Einstellungen speichern",
"save_z_transactions": "Z-Tx in Tx-Liste speichern",
"sb_auth_failed": "Authentifizierung fehlgeschlagen — rpcuser/rpcpassword prüfen",
"sb_block": "Block: %d",
"sb_connecting_daemon": "Verbindung zu dragonxd...",
"sb_connecting_err": "Verbindung zum Daemon — %s",
"sb_connecting_external": "Verbindung zu externem Daemon...",
"sb_connecting_generic": "Verbindung zum Daemon...",
"sb_daemon_crashed": "Daemon ist %d mal abgestürzt",
"sb_daemon_not_found": "Daemon nicht gefunden",
"sb_dragonxd_running": "dragonxd läuft",
"sb_dragonxd_stopped": "dragonxd gestoppt",
"sb_dragonxd_stopping": "dragonxd wird gestoppt...",
"sb_extracting_sapling": "Sapling-Parameter werden extrahiert...",
"sb_importing_keys": "Schlüssel importieren",
"sb_loading_config": "Konfiguration laden...",
"sb_mining_hs": "%.1f H/s",
"sb_net_ghs": "Netz: %.2f GH/s",
"sb_net_hs": "Netz: %.1f H/s",
"sb_net_khs": "Netz: %.2f KH/s",
"sb_net_mhs": "Netz: %.2f MH/s",
"sb_no_conf": "DRAGONX.conf nicht gefunden",
"sb_peers": "Peers: %zu",
"sb_rescanning": "Neuscan",
"sb_rescanning_pct": "Neuscan %.0f%%",
"sb_restarting_daemon": "Daemon wird neu gestartet...",
"sb_sapling_failed": "Sapling-Parameter-Extraktion fehlgeschlagen.",
"sb_sapling_not_found": "Sapling-Parameter nicht gefunden.",
"sb_starting_daemon": "dragonxd wird gestartet...",
"sb_syncing_basic": "Synchronisierung %.1f%% (%d übrig)",
"sb_syncing_eta": "Synchronisierung %.1f%% (%d übrig, %.0f Blk/s, ~%s)",
"sb_waiting_config": "Warten auf Daemon-Konfiguration...",
"sb_waiting_daemon": "Warten auf dragonxd...",
"sb_waiting_daemon_err": "Warten auf dragonxd — %s",
"sb_warming_up": "Aufwärmen...",
"search_placeholder": "Suchen...",
"security": "SICHERHEIT",
"select_address": "Adresse auswählen...",
@@ -667,15 +553,12 @@
"send_valid_transparent": "Gültige transparente Adresse",
"send_wallet_empty": "Ihre Wallet ist leer",
"send_yes_clear": "Ja, leeren",
"sender_balance": "Absender: %.8f → %.8f DRGX",
"sending": "Transaktion wird gesendet",
"sending_from": "SENDEN VON",
"sends_full_balance_warning": "Dies sendet das gesamte Guthaben. Die Sendeadresse wird ein Nullguthaben haben.",
"sent": "gesendet",
"sent_filter": "Gesendet",
"sent_type": "Gesendet",
"sent_upper": "GESENDET",
"set_label": "Label setzen...",
"settings": "Einstellungen",
"settings_about_text": "Eine geschirmte Kryptowährungs-Wallet für DragonX (DRGX), erstellt mit Dear ImGui für ein leichtes, portables Erlebnis.",
"settings_acrylic_level": "Acrylstufe:",
@@ -764,17 +647,13 @@
"shield_utxo_limit": "UTXO-Limit:",
"shield_wildcard_hint": "Verwenden Sie '*' um von allen transparenten Adressen abzuschirmen",
"shielded": "Abgeschirmt",
"shielded_address": "Geschirmte Adresse",
"shielded_to": "ABGESCHIRMT AN",
"shielded_type": "Abgeschirmt",
"shielding_notice": "Hinweis: Dies wird Gelder von einer transparenten (T) Adresse auf eine private (Z) Adresse schirmen.",
"show": "Anzeigen",
"show_hidden": "Ausgeblendete anzeigen (%d)",
"show_qr_code": "QR-Code anzeigen",
"showing_transactions": "Zeige %d–%d von %d Transaktionen (gesamt: %zu)",
"showing_x_of_y": "%d von %d Adressen angezeigt",
"showing_transactions": "Zeige %d–%d von %d Transaktionen (gesamt: %zu)",
"simple_background": "Einfacher Hintergrund",
"slider_off": "Aus",
"start_mining": "Mining starten",
"status": "Status",
"stop_external": "Externen Daemon stoppen",
@@ -783,26 +662,17 @@
"success": "Erfolg",
"summary": "Zusammenfassung",
"syncing": "Synchronisiere...",
"t_address": "T-Adresse",
"t_addresses": "T-Adressen",
"test_connection": "Testen",
"theme": "Design",
"theme_effects": "Design-Effekte",
"theme_language": "THEMA & SPRACHE",
"time_days_ago": "vor %d Tagen",
"time_hours_ago": "vor %d Stunden",
"time_minutes_ago": "vor %d Minuten",
"time_seconds_ago": "vor %d Sekunden",
"timeout_15min": "15 Min",
"timeout_1hour": "1 Stunde",
"timeout_1min": "1 Min",
"timeout_30min": "30 Min",
"timeout_5min": "5 Min",
"timeout_off": "Aus",
"to": "An",
"to_upper": "AN",
"tools": "WERKZEUGE",
"tools_actions": "Werkzeuge & Aktionen...",
"total": "Gesamt",
"transaction_id": "TRANSAKTIONS-ID",
"transaction_sent": "Transaktion erfolgreich gesendet",
@@ -810,13 +680,7 @@
"transaction_url": "Transaktions-URL",
"transactions": "Transaktionen",
"transactions_upper": "TRANSAKTIONEN",
"transfer_failed": "Überweisung fehlgeschlagen",
"transfer_funds": "Geld überweisen",
"transfer_sent": "Überweisung gesendet",
"transfer_sent_desc": "Ihre Überweisung wurde an das Netzwerk gesendet.",
"transfer_to": "Überweisen an:",
"transparent": "Transparent",
"transparent_address": "Transparente Adresse",
"tt_addr_url": "Basis-URL zum Anzeigen von Adressen in einem Block-Explorer",
"tt_address_book": "Gespeicherte Adressen für schnelles Senden verwalten",
"tt_auto_lock": "Wallet nach dieser Inaktivitätszeit sperren",
@@ -831,8 +695,6 @@
"tt_custom_theme": "Benutzerdefiniertes Theme aktiv",
"tt_debug_collapse": "Debug-Protokollierungsoptionen einklappen",
"tt_debug_expand": "Debug-Protokollierungsoptionen ausklappen",
"tt_delete_blockchain": "Alle Blockchain-Daten löschen und neu synchronisieren. wallet.dat und Konfiguration bleiben erhalten.",
"tt_download_bootstrap": "Blockchain-Bootstrap herunterladen, um die Synchronisierung zu beschleunigen\nVorhandene Blockdaten werden ersetzt",
"tt_encrypt": "wallet.dat mit einer Passphrase verschlüsseln",
"tt_export_all": "Alle privaten Schlüssel in eine Datei exportieren",
"tt_export_csv": "Transaktionsverlauf als CSV-Tabelle exportieren",
@@ -850,7 +712,6 @@
"tt_mine_idle": "Mining automatisch starten, wenn das\\nSystem inaktiv ist (keine Tastatur-/Mauseingabe)",
"tt_noise": "Körnungstextur-Intensität (0%% = aus, 100%% = maximum)",
"tt_open_dir": "Klicken, um im Dateimanager zu öffnen",
"tt_reduce_motion": "Animierte Übergänge und Saldo-Lerp für Barrierefreiheit deaktivieren",
"tt_remove_encrypt": "Verschlüsselung entfernen und Wallet ungeschützt speichern",
"tt_remove_pin": "PIN entfernen und Passphrase zum Entsperren erfordern",
"tt_report_bug": "Ein Problem im Projekt-Tracker melden",
@@ -928,9 +789,7 @@
"warning_upper": "WARNUNG!",
"website": "Webseite",
"window_opacity": "Fenster-Transparenz",
"wizard_daemon_start_failed": "Daemon-Start fehlgeschlagen — wird automatisch wiederholt",
"yes_clear": "Ja, leeren",
"your_addresses": "Ihre Adressen",
"z_address": "Z-Adresse",
"z_addresses": "Z-Adressen"
}
+2 -143
View File
@@ -43,8 +43,6 @@
"address_url": "URL de Dirección",
"addresses_appear_here": "Tus direcciones de recepción aparecerán aquí una vez conectado.",
"advanced": "AVANZADO",
"advanced_effects": "Efectos Avanzados...",
"ago": "atrás",
"all_filter": "Todos",
"allow_custom_fees": "Permitir comisiones personalizadas",
"amount": "Cantidad",
@@ -92,30 +90,12 @@
"block_timestamp": "Fecha y Hora:",
"block_transactions": "Transacciones:",
"blockchain_syncing": "Sincronizando blockchain (%.1f%%)... Los saldos pueden ser inexactos.",
"bootstrap_daemon_running": "Daemon ejecutándose",
"bootstrap_daemon_stopped": "Daemon detenido",
"bootstrap_daemon_stopping": "Deteniendo daemon...",
"bootstrap_desc": "Descarga un bootstrap de la blockchain para acelerar drásticamente la sincronización inicial. Esto descarga una instantánea de la blockchain y la extrae en tu directorio de datos.",
"bootstrap_downloading": "Descargando bootstrap...",
"bootstrap_extracting": "Extrayendo datos de blockchain...",
"bootstrap_failed": "Error en Bootstrap",
"bootstrap_mirror": "Espejo",
"bootstrap_mirror_tooltip": "Descargar desde espejo (bootstrap2.dragonx.is).\nUsa esto si la descarga principal es lenta o falla.",
"bootstrap_restart_daemon": "Reiniciar Daemon",
"bootstrap_success": "Bootstrap Completado",
"bootstrap_success_desc": "Los datos de la blockchain se han extraído correctamente. Inicie el daemon para comenzar a sincronizar desde el punto del bootstrap.",
"bootstrap_trust_warning": "Solo use bootstrap.dragonx.is o bootstrap2.dragonx.is. Usar archivos de fuentes no confiables podría comprometer su nodo.",
"bootstrap_verifying": "Verificando sumas de comprobación...",
"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.",
"cancel": "Cancelar",
"characters": "caracteres",
"choose_icon": "Elegir Icono",
"clear": "Limpiar",
"clear_all_bans": "Limpiar Todos los Bloqueos",
"clear_anyway": "Limpiar de todos modos",
"clear_form_confirm": "¿Limpiar todos los campos del formulario?",
"clear_icon": "Borrar Icono",
"clear_request": "Limpiar Solicitud",
"click_copy_address": "Clic para copiar dirección",
"click_copy_uri": "Clic para copiar URI",
@@ -126,12 +106,8 @@
"confirm_clear_ztx_title": "Confirmar limpieza del historial Z-Tx",
"confirm_clear_ztx_warning1": "Limpiar el historial de z-transacciones puede hacer que su saldo blindado se muestre como 0 hasta que se realice un reescaneo de la billetera.",
"confirm_clear_ztx_warning2": "Si esto sucede, deberá reimportar las claves privadas de su dirección z con el reescaneo habilitado para recuperar su saldo.",
"confirm_delete_blockchain_msg": "Esto detendrá el daemon, eliminará todos los datos de la blockchain (blocks, chainstate, peers) y comenzará una nueva sincronización desde cero. Esto puede tardar varias horas.",
"confirm_delete_blockchain_safe": "Su wallet.dat, configuración e historial de transacciones están seguros y no se eliminarán.",
"confirm_delete_blockchain_title": "Eliminar Datos de Blockchain",
"confirm_send": "Confirmar Envío",
"confirm_transaction": "Confirmar Transacción",
"confirm_transfer": "Confirmar Transferencia",
"confirmations": "Confirmaciones",
"confirmations_display": "%d confirmaciones | %s",
"confirmed": "Confirmada",
@@ -196,7 +172,6 @@
"console_welcome": "Bienvenido a la Consola de ObsidianDragon",
"console_zoom_in": "Acercar",
"console_zoom_out": "Alejar",
"copied": "¡Copiado!",
"copy": "Copiar",
"copy_address": "Copiar Dirección Completa",
"copy_error": "Copiar Error",
@@ -205,45 +180,22 @@
"copy_uri": "Copiar URI",
"current_price": "Precio Actual",
"custom_fees": "Comisiones personalizadas",
"daemon_version": "Daemon",
"dark": "Oscuro",
"date": "Fecha",
"date_label": "Fecha:",
"debug_logging": "REGISTRO DE DEPURACIÓN",
"delete": "Eliminar",
"delete_blockchain": "Eliminar Blockchain",
"delete_blockchain_confirm": "Eliminar y Resincronizar",
"deshielding_warning": "Advertencia: Esto des-protegerá fondos de una dirección privada (Z) a una dirección transparente (T).",
"difficulty": "Dificultad",
"disconnected": "Desconectado",
"dismiss": "Descartar",
"display": "Pantalla",
"download": "Descargar",
"download_bootstrap": "Descargar Bootstrap",
"dragonx_green": "DragonX (Verde)",
"edit": "Editar",
"error": "Error",
"error_format": "Error: %s",
"est_time_to_block": "Tiempo Est. al Bloque",
"exit": "Salir",
"explorer": "Explorador",
"explorer_block_detail": "Bloque",
"explorer_block_hash": "Hash",
"explorer_block_height": "Altura",
"explorer_block_merkle": "Raíz Merkle",
"explorer_block_size": "Tamaño",
"explorer_block_time": "Hora",
"explorer_block_txs": "Transacciones",
"explorer_chain_stats": "Cadena",
"explorer_invalid_query": "Ingrese una altura de bloque o un hash de 64 caracteres",
"explorer_mempool": "Mempool",
"explorer_mempool_size": "Tamaño",
"explorer_mempool_txs": "Transacciones",
"explorer_recent_blocks": "Bloques Recientes",
"explorer_search": "Buscar",
"explorer_section": "EXPLORADOR",
"explorer_tx_outputs": "Salidas",
"explorer_tx_size": "Tamaño",
"explorer": "EXPLORADOR",
"export": "Exportar",
"export_csv": "Exportar CSV",
"export_keys_btn": "Exportar Claves",
@@ -272,22 +224,14 @@
"fetch_prices": "Obtener precios",
"file": "Archivo",
"file_save_location": "El archivo se guardará en: ~/.config/ObsidianDragon/",
"filter": "Filtrar...",
"font_scale": "Escala de fuente",
"force_quit": "Forzar Salida",
"force_quit_confirm_msg": "Esto matará inmediatamente el daemon sin un apagado limpio.\nEsto puede corromper el índice de la blockchain y requerir una resincronización.",
"force_quit_confirm_title": "¿Forzar Salida?",
"force_quit_warning": "Esto matará inmediatamente el daemon sin un apagado limpio. Puede requerir una resincronización de la blockchain.",
"force_quit_yes": "Forzar Salida",
"from": "Desde",
"from_upper": "DESDE",
"full_details": "Detalles Completos",
"general": "General",
"generating": "Generando",
"go_to_receive": "Ir a Recibir",
"height": "Altura",
"help": "Ayuda",
"hidden_tag": " (oculto)",
"hide": "Ocultar",
"hide_address": "Ocultar dirección",
"hide_zero_balances": "Ocultar saldos 0",
@@ -309,9 +253,6 @@
"import_key_warning": "Advertencia: ¡Nunca compartas tus claves privadas! Importar claves de fuentes no confiables puede comprometer tu cartera.",
"import_key_z_format": "Claves de gasto de direcciones Z (secret-extended-key-...)",
"import_private_key": "Importar Clave Privada...",
"incorrect_passphrase": "Contraseña incorrecta",
"incorrect_pin": "PIN incorrecto",
"insufficient_funds": "Fondos insuficientes para este monto más la comisión.",
"invalid_address": "Formato de dirección inválido",
"ip_address": "Dirección IP",
"keep": "Mantener",
@@ -325,7 +266,6 @@
"key_export_viewing_keys_zonly": "Las claves de visualización solo están disponibles para direcciones blindadas (z)",
"key_export_viewing_warning": "Esta clave de vista permite a otros ver tus transacciones entrantes y saldo, pero NO gastar tus fondos. Comparte solo con personas de confianza.",
"label": "Etiqueta:",
"label_placeholder": "ej. Ahorros, Minería...",
"language": "Idioma",
"light": "Claro",
"loading": "Cargando...",
@@ -371,13 +311,6 @@
"mining_address_copied": "Dirección de minería copiada",
"mining_all_time": "Todo el Tiempo",
"mining_already_saved": "URL del pool ya guardada",
"mining_benchmark_cancel": "Cancelar benchmark",
"mining_benchmark_cooling": "Enfriando",
"mining_benchmark_dismiss": "Cerrar",
"mining_benchmark_result": "Óptimo",
"mining_benchmark_stabilizing": "Estabilizando",
"mining_benchmark_testing": "Probando",
"mining_benchmark_tooltip": "Encontrar el número óptimo de hilos para esta CPU",
"mining_block_copied": "Hash de bloque copiado",
"mining_chart_1m_ago": "hace 1m",
"mining_chart_5m_ago": "hace 5m",
@@ -397,15 +330,8 @@
"mining_filter_tip_all": "Mostrar todas las ganancias",
"mining_filter_tip_pool": "Mostrar solo ganancias del pool",
"mining_filter_tip_solo": "Mostrar solo ganancias solo",
"mining_generate_z_address_hint": "Genere una dirección Z en la pestaña Recibir para usarla como dirección de pago",
"mining_idle_gpu_off_tooltip": "Sin restricción: ACTIVADO\nSolo la entrada de teclado/ratón determina el estado inactivo\nClic para activar detección de GPU",
"mining_idle_gpu_on_tooltip": "GPU-consciente: ACTIVADO\nLa actividad de GPU (video, juegos) previene la minería inactiva\nClic para modo sin restricción",
"mining_idle_off_tooltip": "Activar minería en reposo",
"mining_idle_on_tooltip": "Desactivar minería en reposo",
"mining_idle_scale_off_tooltip": "Modo inicio/parada: ACTIVADO\nClic para cambiar al modo de escala de hilos",
"mining_idle_scale_on_tooltip": "Escala de hilos: ACTIVADO\nClic para cambiar al modo de inicio/parada",
"mining_idle_threads_active_tooltip": "Hilos cuando el usuario está activo",
"mining_idle_threads_idle_tooltip": "Hilos cuando el sistema está inactivo",
"mining_local_hashrate": "Hashrate Local",
"mining_mine": "Minar",
"mining_mining_addr": "Dir. Minería",
@@ -462,7 +388,6 @@
"no_addresses_available": "No hay direcciones disponibles",
"no_addresses_match": "No hay direcciones que coincidan con el filtro",
"no_addresses_with_balance": "No hay direcciones con saldo",
"no_addresses_yet": "Aún no hay direcciones",
"no_matching": "No hay transacciones coincidentes",
"no_recent_receives": "No hay recepciones recientes",
"no_recent_sends": "No hay envíos recientes",
@@ -528,7 +453,6 @@
"peers_upper": "NODOS",
"peers_version": "Versión",
"pending": "Pendiente",
"pin_not_set": "PIN no configurado. Use la contraseña para desbloquear.",
"ping": "Ping",
"price_chart": "Gráfico de Precios",
"qr_code": "Código QR",
@@ -549,9 +473,7 @@
"recent_received": "RECIBIDOS RECIENTES",
"recent_sends": "ENVÍOS RECIENTES",
"recipient": "DESTINATARIO",
"recipient_balance": "Destinatario: %.8f → %.8f DRGX",
"recv_type": "Recibido",
"reduce_motion": "Reducir Movimiento",
"refresh": "Actualizar",
"refresh_now": "Actualizar Ahora",
"remove_favorite": "Quitar favorito",
@@ -571,10 +493,7 @@
"request_uri_copied": "URI de pago copiada al portapapeles",
"rescan": "Re-escanear",
"reset_to_defaults": "Restablecer Valores",
"restarting_after_encryption": "Reiniciando daemon después del cifrado...",
"restore_address": "Restaurar dirección",
"result_preview": "Vista previa del resultado",
"retry": "Reintentar",
"review_send": "Revisar Envío",
"rpc_host": "Host RPC",
"rpc_pass": "Contraseña",
@@ -583,39 +502,6 @@
"save": "Guardar",
"save_settings": "Guardar Configuración",
"save_z_transactions": "Guardar Z-tx en lista",
"sb_auth_failed": "Autenticación fallida — verifique rpcuser/rpcpassword",
"sb_block": "Bloque: %d",
"sb_connecting_daemon": "Conectando a dragonxd...",
"sb_connecting_err": "Conectando al daemon — %s",
"sb_connecting_external": "Conectando a daemon externo...",
"sb_connecting_generic": "Conectando al daemon...",
"sb_daemon_crashed": "El daemon se bloqueó %d veces",
"sb_daemon_not_found": "Daemon no encontrado",
"sb_dragonxd_running": "dragonxd ejecutándose",
"sb_dragonxd_stopped": "dragonxd detenido",
"sb_dragonxd_stopping": "Deteniendo dragonxd...",
"sb_extracting_sapling": "Extrayendo parámetros Sapling...",
"sb_importing_keys": "Importando claves",
"sb_loading_config": "Cargando configuración...",
"sb_mining_hs": "%.1f H/s",
"sb_net_ghs": "Red: %.2f GH/s",
"sb_net_hs": "Red: %.1f H/s",
"sb_net_khs": "Red: %.2f KH/s",
"sb_net_mhs": "Red: %.2f MH/s",
"sb_no_conf": "DRAGONX.conf no encontrado",
"sb_peers": "Pares: %zu",
"sb_rescanning": "Reescaneando",
"sb_rescanning_pct": "Reescaneando %.0f%%",
"sb_restarting_daemon": "Reiniciando daemon...",
"sb_sapling_failed": "Error al extraer parámetros Sapling.",
"sb_sapling_not_found": "Parámetros Sapling no encontrados.",
"sb_starting_daemon": "Iniciando dragonxd...",
"sb_syncing_basic": "Sincronizando %.1f%% (%d restantes)",
"sb_syncing_eta": "Sincronizando %.1f%% (%d restantes, %.0f blk/s, ~%s)",
"sb_waiting_config": "Esperando configuración del daemon...",
"sb_waiting_daemon": "Esperando a dragonxd...",
"sb_waiting_daemon_err": "Esperando a dragonxd — %s",
"sb_warming_up": "Calentando...",
"search_placeholder": "Buscar...",
"security": "SEGURIDAD",
"select_address": "Seleccionar dirección...",
@@ -667,16 +553,13 @@
"send_valid_transparent": "Dirección transparente válida",
"send_wallet_empty": "Tu cartera está vacía",
"send_yes_clear": "Sí, Limpiar",
"sender_balance": "Remitente: %.8f → %.8f DRGX",
"sending": "Enviando transacción",
"sending_from": "ENVIANDO DESDE",
"sends_full_balance_warning": "Esto envía el saldo completo. La dirección de envío tendrá saldo cero.",
"sent": "enviado",
"sent_filter": "Enviado",
"sent_type": "Enviado",
"sent_upper": "ENVIADO",
"set_label": "Establecer Etiqueta...",
"settings": "Ajustes",
"settings": "Configuración",
"settings_about_text": "Una billetera de criptomonedas blindada para DragonX (DRGX), creada con Dear ImGui para una experiencia ligera y portátil.",
"settings_acrylic_level": "Nivel de acrílico:",
"settings_address_book": "Libreta de direcciones...",
@@ -764,17 +647,13 @@
"shield_utxo_limit": "Límite UTXO:",
"shield_wildcard_hint": "Usa '*' para proteger desde todas las direcciones transparentes",
"shielded": "Blindada",
"shielded_address": "Dirección Protegida",
"shielded_to": "PROTEGIDA PARA",
"shielded_type": "Protegido",
"shielding_notice": "Nota: Esto blindará fondos de una dirección transparente (T) a una dirección privada (Z).",
"show": "Mostrar",
"show_hidden": "Mostrar ocultos (%d)",
"show_qr_code": "Mostrar Código QR",
"showing_transactions": "Mostrando %d–%d de %d transacciones (total: %zu)",
"showing_x_of_y": "Mostrando %d de %d direcciones",
"simple_background": "Fondo simple",
"slider_off": "Apagado",
"start_mining": "Iniciar Minería",
"status": "Estado",
"stop_external": "Detener daemon externo",
@@ -783,26 +662,17 @@
"success": "Éxito",
"summary": "Resumen",
"syncing": "Sincronizando...",
"t_address": "Dirección T",
"t_addresses": "Direcciones T",
"test_connection": "Probar",
"theme": "Tema",
"theme_effects": "Efectos de tema",
"theme_language": "TEMA E IDIOMA",
"time_days_ago": "hace %d días",
"time_hours_ago": "hace %d horas",
"time_minutes_ago": "hace %d minutos",
"time_seconds_ago": "hace %d segundos",
"timeout_15min": "15 min",
"timeout_1hour": "1 hora",
"timeout_1min": "1 min",
"timeout_30min": "30 min",
"timeout_5min": "5 min",
"timeout_off": "Apagado",
"to": "Para",
"to_upper": "PARA",
"tools": "HERRAMIENTAS",
"tools_actions": "Herramientas y Acciones...",
"total": "Total",
"transaction_id": "ID DE TRANSACCIÓN",
"transaction_sent": "Transacción enviada exitosamente",
@@ -810,13 +680,7 @@
"transaction_url": "URL de Transacción",
"transactions": "Transacciones",
"transactions_upper": "TRANSACCIONES",
"transfer_failed": "Transferencia Fallida",
"transfer_funds": "Transferir Fondos",
"transfer_sent": "Transferencia Enviada",
"transfer_sent_desc": "Su transferencia ha sido enviada a la red.",
"transfer_to": "Transferir a:",
"transparent": "Transparente",
"transparent_address": "Dirección Transparente",
"tt_addr_url": "URL base para ver direcciones en un explorador de bloques",
"tt_address_book": "Administrar direcciones guardadas para envío rápido",
"tt_auto_lock": "Bloquear billetera después de este tiempo de inactividad",
@@ -831,8 +695,6 @@
"tt_custom_theme": "Tema personalizado activo",
"tt_debug_collapse": "Colapsar opciones de registro de depuración",
"tt_debug_expand": "Expandir opciones de registro de depuración",
"tt_delete_blockchain": "Eliminar todos los datos de la blockchain e iniciar una nueva sincronización. Se preservan wallet.dat y la configuración.",
"tt_download_bootstrap": "Descargar bootstrap de blockchain para acelerar la sincronización\nLos datos de bloques existentes serán reemplazados",
"tt_encrypt": "Cifrar wallet.dat con una contraseña",
"tt_export_all": "Exportar todas las claves privadas a un archivo",
"tt_export_csv": "Exportar historial de transacciones como hoja de cálculo CSV",
@@ -850,7 +712,6 @@
"tt_mine_idle": "Iniciar minería automáticamente cuando el\\nsistema esté inactivo (sin entrada de teclado/ratón)",
"tt_noise": "Intensidad de textura granulada (0%% = apagado, 100%% = máximo)",
"tt_open_dir": "Clic para abrir en explorador de archivos",
"tt_reduce_motion": "Desactivar transiciones animadas y lerp de saldo para accesibilidad",
"tt_remove_encrypt": "Quitar cifrado y almacenar la billetera sin protección",
"tt_remove_pin": "Quitar PIN y requerir contraseña para desbloquear",
"tt_report_bug": "Reportar un problema en el rastreador del proyecto",
@@ -928,9 +789,7 @@
"warning_upper": "¡ADVERTENCIA!",
"website": "Sitio Web",
"window_opacity": "Opacidad de ventana",
"wizard_daemon_start_failed": "Error al iniciar el daemon — se reintentará automáticamente",
"yes_clear": "Sí, Limpiar",
"your_addresses": "Sus Direcciones",
"z_address": "Dirección Z",
"z_addresses": "Direcciones Z"
}
+2 -143
View File
@@ -43,8 +43,6 @@
"address_url": "URL de l'adresse",
"addresses_appear_here": "Vos adresses de réception apparaîtront ici une fois connecté.",
"advanced": "AVANCÉ",
"advanced_effects": "Effets avancés...",
"ago": "passé",
"all_filter": "Tout",
"allow_custom_fees": "Autoriser les frais personnalisés",
"amount": "Montant",
@@ -92,30 +90,12 @@
"block_timestamp": "Horodatage :",
"block_transactions": "Transactions :",
"blockchain_syncing": "Synchronisation de la blockchain (%.1f%%)... Les soldes peuvent être inexacts.",
"bootstrap_daemon_running": "Daemon en cours",
"bootstrap_daemon_stopped": "Daemon arrêté",
"bootstrap_daemon_stopping": "Arrêt du daemon...",
"bootstrap_desc": "Téléchargez un bootstrap de la blockchain pour accélérer considérablement la synchronisation initiale. Cela télécharge un instantané de la blockchain et l'extrait dans votre répertoire de données.",
"bootstrap_downloading": "Téléchargement du bootstrap...",
"bootstrap_extracting": "Extraction des données blockchain...",
"bootstrap_failed": "Échec du Bootstrap",
"bootstrap_mirror": "Miroir",
"bootstrap_mirror_tooltip": "Télécharger depuis le miroir (bootstrap2.dragonx.is).\nUtilisez ceci si le téléchargement principal est lent ou échoue.",
"bootstrap_restart_daemon": "Redémarrer le Daemon",
"bootstrap_success": "Bootstrap terminé",
"bootstrap_success_desc": "Les données de la blockchain ont été extraites avec succès. Démarrez le daemon pour commencer la synchronisation à partir du point de bootstrap.",
"bootstrap_trust_warning": "N'utilisez que bootstrap.dragonx.is ou bootstrap2.dragonx.is. L'utilisation de fichiers provenant de sources non fiables pourrait compromettre votre nœud.",
"bootstrap_verifying": "Vérification des sommes de contrôle...",
"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é.",
"cancel": "Annuler",
"characters": "caractères",
"choose_icon": "Choisir une icône",
"clear": "Effacer",
"clear_all_bans": "Lever tous les bannissements",
"clear_anyway": "Effacer quand même",
"clear_form_confirm": "Effacer tous les champs du formulaire ?",
"clear_icon": "Effacer l'icône",
"clear_request": "Effacer la demande",
"click_copy_address": "Cliquez pour copier l'adresse",
"click_copy_uri": "Cliquez pour copier l'URI",
@@ -126,12 +106,8 @@
"confirm_clear_ztx_title": "Confirmer l'effacement de l'historique Z-Tx",
"confirm_clear_ztx_warning1": "L'effacement de l'historique des z-transactions peut faire apparaître votre solde blindé à 0 jusqu'à ce qu'un rescan du portefeuille soit effectué.",
"confirm_clear_ztx_warning2": "Si cela se produit, vous devrez réimporter les clés privées de votre adresse z avec le rescan activé pour récupérer votre solde.",
"confirm_delete_blockchain_msg": "Cela arrêtera le daemon, supprimera toutes les données de la blockchain (blocks, chainstate, peers) et démarrera une nouvelle synchronisation. Cela peut prendre plusieurs heures.",
"confirm_delete_blockchain_safe": "Votre wallet.dat, votre configuration et votre historique de transactions sont en sécurité et ne seront pas supprimés.",
"confirm_delete_blockchain_title": "Supprimer les données Blockchain",
"confirm_send": "Confirmer l'envoi",
"confirm_transaction": "Confirmer la transaction",
"confirm_transfer": "Confirmer le transfert",
"confirmations": "Confirmations",
"confirmations_display": "%d confirmations | %s",
"confirmed": "Confirmé",
@@ -196,7 +172,6 @@
"console_welcome": "Bienvenue dans la console ObsidianDragon",
"console_zoom_in": "Agrandir",
"console_zoom_out": "Réduire",
"copied": "Copié !",
"copy": "Copier",
"copy_address": "Copier l'adresse complète",
"copy_error": "Copier l'erreur",
@@ -205,45 +180,22 @@
"copy_uri": "Copier l'URI",
"current_price": "Prix actuel",
"custom_fees": "Frais personnalisés",
"daemon_version": "Daemon",
"dark": "Sombre",
"date": "Date",
"date_label": "Date :",
"debug_logging": "JOURNALISATION DE DÉBOGAGE",
"delete": "Supprimer",
"delete_blockchain": "Supprimer Blockchain",
"delete_blockchain_confirm": "Supprimer & Resynchroniser",
"deshielding_warning": "Attention : Cela va déblinder des fonds d'une adresse privée (Z) vers une adresse transparente (T).",
"difficulty": "Difficulté",
"disconnected": "Déconnecté",
"dismiss": "Ignorer",
"display": "Affichage",
"download": "Télécharger",
"download_bootstrap": "Télécharger Bootstrap",
"dragonx_green": "DragonX (Vert)",
"edit": "Modifier",
"error": "Erreur",
"error_format": "Erreur : %s",
"est_time_to_block": "Temps est. par bloc",
"exit": "Quitter",
"explorer": "Explorateur",
"explorer_block_detail": "Bloc",
"explorer_block_hash": "Hash",
"explorer_block_height": "Hauteur",
"explorer_block_merkle": "Racine Merkle",
"explorer_block_size": "Taille",
"explorer_block_time": "Heure",
"explorer_block_txs": "Transactions",
"explorer_chain_stats": "Chaîne",
"explorer_invalid_query": "Entrez une hauteur de bloc ou un hash de 64 caractères",
"explorer_mempool": "Mempool",
"explorer_mempool_size": "Taille",
"explorer_mempool_txs": "Transactions",
"explorer_recent_blocks": "Blocs récents",
"explorer_search": "Rechercher",
"explorer_section": "EXPLORATEUR",
"explorer_tx_outputs": "Sorties",
"explorer_tx_size": "Taille",
"explorer": "EXPLORATEUR",
"export": "Exporter",
"export_csv": "Exporter en CSV",
"export_keys_btn": "Exporter les clés",
@@ -272,22 +224,14 @@
"fetch_prices": "Récupérer les prix",
"file": "Fichier",
"file_save_location": "Le fichier sera enregistré dans : ~/.config/ObsidianDragon/",
"filter": "Filtrer...",
"font_scale": "Taille de police",
"force_quit": "Forcer la fermeture",
"force_quit_confirm_msg": "Cela tuera immédiatement le daemon sans arrêt propre.\nCela peut corrompre l'index de la blockchain et nécessiter une resynchronisation.",
"force_quit_confirm_title": "Forcer la fermeture ?",
"force_quit_warning": "Cela tuera immédiatement le daemon sans arrêt propre. Peut nécessiter une resynchronisation de la blockchain.",
"force_quit_yes": "Forcer la fermeture",
"from": "De",
"from_upper": "DE",
"full_details": "Tous les détails",
"general": "Général",
"generating": "Génération",
"go_to_receive": "Aller à Recevoir",
"height": "Hauteur",
"help": "Aide",
"hidden_tag": " (masqué)",
"hide": "Masquer",
"hide_address": "Masquer l'adresse",
"hide_zero_balances": "Masquer les soldes à 0",
@@ -309,9 +253,6 @@
"import_key_warning": "Attention : Ne partagez jamais vos clés privées ! L'importation de clés provenant de sources non fiables peut compromettre votre portefeuille.",
"import_key_z_format": "Clés de dépenses z-adresse (secret-extended-key-...)",
"import_private_key": "Importer une clé privée...",
"incorrect_passphrase": "Mot de passe incorrect",
"incorrect_pin": "PIN incorrect",
"insufficient_funds": "Fonds insuffisants pour ce montant plus les frais.",
"invalid_address": "Format d'adresse invalide",
"ip_address": "Adresse IP",
"keep": "Conserver",
@@ -325,7 +266,6 @@
"key_export_viewing_keys_zonly": "Les clés de visualisation ne sont disponibles que pour les adresses blindées (z)",
"key_export_viewing_warning": "Cette clé de visualisation permet à d'autres de voir vos transactions entrantes et votre solde, mais PAS de dépenser vos fonds. Ne la partagez qu'avec des personnes de confiance.",
"label": "Libellé :",
"label_placeholder": "ex. Épargne, Minage...",
"language": "Langue",
"light": "Clair",
"loading": "Chargement...",
@@ -371,13 +311,6 @@
"mining_address_copied": "Adresse de minage copiée",
"mining_all_time": "Tout le temps",
"mining_already_saved": "URL du pool déjà enregistrée",
"mining_benchmark_cancel": "Annuler le benchmark",
"mining_benchmark_cooling": "Refroidissement",
"mining_benchmark_dismiss": "Fermer",
"mining_benchmark_result": "Optimal",
"mining_benchmark_stabilizing": "Stabilisation",
"mining_benchmark_testing": "Test",
"mining_benchmark_tooltip": "Trouver le nombre optimal de threads pour ce CPU",
"mining_block_copied": "Hash du bloc copié",
"mining_chart_1m_ago": "il y a 1m",
"mining_chart_5m_ago": "il y a 5m",
@@ -397,15 +330,8 @@
"mining_filter_tip_all": "Afficher tous les gains",
"mining_filter_tip_pool": "Afficher uniquement les gains du pool",
"mining_filter_tip_solo": "Afficher uniquement les gains solo",
"mining_generate_z_address_hint": "Générez une adresse Z dans l'onglet Recevoir pour l'utiliser comme adresse de paiement",
"mining_idle_gpu_off_tooltip": "Sans restriction : ACTIVÉ\nSeule l'entrée clavier/souris détermine l'inactivité\nCliquez pour activer la détection GPU",
"mining_idle_gpu_on_tooltip": "GPU-conscient : ACTIVÉ\nL'activité GPU (vidéo, jeux) empêche le minage inactif\nCliquez pour le mode sans restriction",
"mining_idle_off_tooltip": "Activer le minage au repos",
"mining_idle_on_tooltip": "Désactiver le minage au repos",
"mining_idle_scale_off_tooltip": "Mode démarrage/arrêt : ACTIVÉ\nCliquez pour passer au mode mise à l'échelle des threads",
"mining_idle_scale_on_tooltip": "Mise à l'échelle des threads : ACTIVÉ\nCliquez pour passer au mode démarrage/arrêt",
"mining_idle_threads_active_tooltip": "Threads quand l'utilisateur est actif",
"mining_idle_threads_idle_tooltip": "Threads quand le système est inactif",
"mining_local_hashrate": "Hashrate local",
"mining_mine": "Miner",
"mining_mining_addr": "Adr. minage",
@@ -462,7 +388,6 @@
"no_addresses_available": "Aucune adresse disponible",
"no_addresses_match": "Aucune adresse ne correspond au filtre",
"no_addresses_with_balance": "Aucune adresse avec solde",
"no_addresses_yet": "Pas encore d'adresses",
"no_matching": "Aucune transaction correspondante",
"no_recent_receives": "Aucune réception récente",
"no_recent_sends": "Aucun envoi récent",
@@ -528,7 +453,6 @@
"peers_upper": "PAIRS",
"peers_version": "Version",
"pending": "En attente",
"pin_not_set": "PIN non défini. Utilisez le mot de passe pour déverrouiller.",
"ping": "Ping",
"price_chart": "Graphique des prix",
"qr_code": "Code QR",
@@ -549,9 +473,7 @@
"recent_received": "REÇUS RÉCENTS",
"recent_sends": "ENVOIS RÉCENTS",
"recipient": "DESTINATAIRE",
"recipient_balance": "Destinataire : %.8f → %.8f DRGX",
"recv_type": "Reçu",
"reduce_motion": "Réduire les animations",
"refresh": "Actualiser",
"refresh_now": "Actualiser maintenant",
"remove_favorite": "Retirer des favoris",
@@ -571,10 +493,7 @@
"request_uri_copied": "URI de paiement copiée dans le presse-papiers",
"rescan": "Re-scanner",
"reset_to_defaults": "Réinitialiser les paramètres",
"restarting_after_encryption": "Redémarrage du daemon après chiffrement...",
"restore_address": "Restaurer l'adresse",
"result_preview": "Aperçu du résultat",
"retry": "Réessayer",
"review_send": "Vérifier l'envoi",
"rpc_host": "Hôte RPC",
"rpc_pass": "Mot de passe",
@@ -583,39 +502,6 @@
"save": "Enregistrer",
"save_settings": "Enregistrer les paramètres",
"save_z_transactions": "Enregistrer les Z-tx dans la liste",
"sb_auth_failed": "Authentification échouée — vérifiez rpcuser/rpcpassword",
"sb_block": "Bloc : %d",
"sb_connecting_daemon": "Connexion à dragonxd...",
"sb_connecting_err": "Connexion au daemon — %s",
"sb_connecting_external": "Connexion au daemon externe...",
"sb_connecting_generic": "Connexion au daemon...",
"sb_daemon_crashed": "Le daemon a planté %d fois",
"sb_daemon_not_found": "Daemon introuvable",
"sb_dragonxd_running": "dragonxd en cours",
"sb_dragonxd_stopped": "dragonxd arrêté",
"sb_dragonxd_stopping": "Arrêt de dragonxd...",
"sb_extracting_sapling": "Extraction des paramètres Sapling...",
"sb_importing_keys": "Importation des clés",
"sb_loading_config": "Chargement de la configuration...",
"sb_mining_hs": "%.1f H/s",
"sb_net_ghs": "Rés: %.2f GH/s",
"sb_net_hs": "Rés: %.1f H/s",
"sb_net_khs": "Rés: %.2f KH/s",
"sb_net_mhs": "Rés: %.2f MH/s",
"sb_no_conf": "DRAGONX.conf introuvable",
"sb_peers": "Pairs : %zu",
"sb_rescanning": "Rescan",
"sb_rescanning_pct": "Rescan %.0f%%",
"sb_restarting_daemon": "Redémarrage du daemon...",
"sb_sapling_failed": "Échec de l'extraction des paramètres Sapling.",
"sb_sapling_not_found": "Paramètres Sapling introuvables.",
"sb_starting_daemon": "Démarrage de dragonxd...",
"sb_syncing_basic": "Synchronisation %.1f%% (%d restants)",
"sb_syncing_eta": "Synchronisation %.1f%% (%d restants, %.0f blk/s, ~%s)",
"sb_waiting_config": "En attente de la configuration du daemon...",
"sb_waiting_daemon": "En attente de dragonxd...",
"sb_waiting_daemon_err": "En attente de dragonxd — %s",
"sb_warming_up": "Démarrage...",
"search_placeholder": "Rechercher...",
"security": "SÉCURITÉ",
"select_address": "Sélectionner une adresse...",
@@ -667,15 +553,12 @@
"send_valid_transparent": "Adresse transparente valide",
"send_wallet_empty": "Votre portefeuille est vide",
"send_yes_clear": "Oui, effacer",
"sender_balance": "Expéditeur : %.8f → %.8f DRGX",
"sending": "Envoi de la transaction",
"sending_from": "ENVOI DEPUIS",
"sends_full_balance_warning": "Cela envoie le solde complet. L'adresse d'envoi aura un solde nul.",
"sent": "envoyé",
"sent_filter": "Envoyé",
"sent_type": "Envoyé",
"sent_upper": "ENVOYÉ",
"set_label": "Définir le libellé...",
"settings": "Paramètres",
"settings_about_text": "Un portefeuille de cryptomonnaie blindé pour DragonX (DRGX), construit avec Dear ImGui pour une expérience légère et portable.",
"settings_acrylic_level": "Niveau acrylique :",
@@ -764,17 +647,13 @@
"shield_utxo_limit": "Limite UTXO :",
"shield_wildcard_hint": "Utilisez '*' pour blinder depuis toutes les adresses transparentes",
"shielded": "Blindé",
"shielded_address": "Adresse protégée",
"shielded_to": "BLINDÉ VERS",
"shielded_type": "Blindé",
"shielding_notice": "Note : Cela blindera des fonds d'une adresse transparente (T) vers une adresse privée (Z).",
"show": "Afficher",
"show_hidden": "Afficher masqués (%d)",
"show_qr_code": "Afficher le code QR",
"showing_transactions": "Affichage %d–%d sur %d transactions (total : %zu)",
"showing_x_of_y": "Affichage de %d sur %d adresses",
"showing_transactions": "Affichage %d–%d sur %d transactions (total : %zu)",
"simple_background": "Arrière-plan simple",
"slider_off": "Désactivé",
"start_mining": "Démarrer le minage",
"status": "Statut",
"stop_external": "Arrêter le daemon externe",
@@ -783,26 +662,17 @@
"success": "Succès",
"summary": "Résumé",
"syncing": "Synchronisation...",
"t_address": "Adresse T",
"t_addresses": "Adresses T",
"test_connection": "Tester",
"theme": "Thème",
"theme_effects": "Effets de thème",
"theme_language": "THÈME & LANGUE",
"time_days_ago": "il y a %d jours",
"time_hours_ago": "il y a %d heures",
"time_minutes_ago": "il y a %d minutes",
"time_seconds_ago": "il y a %d secondes",
"timeout_15min": "15 min",
"timeout_1hour": "1 heure",
"timeout_1min": "1 min",
"timeout_30min": "30 min",
"timeout_5min": "5 min",
"timeout_off": "Désactivé",
"to": "À",
"to_upper": "À",
"tools": "OUTILS",
"tools_actions": "Outils & Actions...",
"total": "Total",
"transaction_id": "ID DE TRANSACTION",
"transaction_sent": "Transaction envoyée avec succès",
@@ -810,13 +680,7 @@
"transaction_url": "URL de transaction",
"transactions": "Transactions",
"transactions_upper": "TRANSACTIONS",
"transfer_failed": "Échec du transfert",
"transfer_funds": "Transférer des fonds",
"transfer_sent": "Transfert envoyé",
"transfer_sent_desc": "Votre transfert a été soumis au réseau.",
"transfer_to": "Transférer à :",
"transparent": "Transparent",
"transparent_address": "Adresse transparente",
"tt_addr_url": "URL de base pour consulter les adresses dans un explorateur de blocs",
"tt_address_book": "Gérer les adresses enregistrées pour un envoi rapide",
"tt_auto_lock": "Verrouiller le portefeuille après cette durée d'inactivité",
@@ -831,8 +695,6 @@
"tt_custom_theme": "Thème personnalisé actif",
"tt_debug_collapse": "Réduire les options de journalisation de débogage",
"tt_debug_expand": "Développer les options de journalisation de débogage",
"tt_delete_blockchain": "Supprimer toutes les données de la blockchain et démarrer une nouvelle synchronisation. wallet.dat et la configuration sont préservés.",
"tt_download_bootstrap": "Télécharger le bootstrap blockchain pour accélérer la synchronisation\nLes données de blocs existantes seront remplacées",
"tt_encrypt": "Chiffrer wallet.dat avec une phrase secrète",
"tt_export_all": "Exporter toutes les clés privées dans un fichier",
"tt_export_csv": "Exporter l'historique des transactions en feuille de calcul CSV",
@@ -850,7 +712,6 @@
"tt_mine_idle": "Démarrer le minage automatiquement quand le\\nsystème est inactif (aucune entrée clavier/souris)",
"tt_noise": "Intensité de texture grainée (0%% = désactivé, 100%% = maximum)",
"tt_open_dir": "Cliquer pour ouvrir dans l'explorateur de fichiers",
"tt_reduce_motion": "Désactiver les transitions animées et le lerp de solde pour l'accessibilité",
"tt_remove_encrypt": "Supprimer le chiffrement et stocker le portefeuille sans protection",
"tt_remove_pin": "Supprimer le PIN et exiger la phrase secrète pour déverrouiller",
"tt_report_bug": "Signaler un problème dans le suivi de projet",
@@ -928,9 +789,7 @@
"warning_upper": "ATTENTION !",
"website": "Site web",
"window_opacity": "Opacité de la fenêtre",
"wizard_daemon_start_failed": "Échec du démarrage du daemon — sera réessayé automatiquement",
"yes_clear": "Oui, effacer",
"your_addresses": "Vos adresses",
"z_address": "Adresse Z",
"z_addresses": "Adresses Z"
}
+1 -142
View File
@@ -43,8 +43,6 @@
"address_url": "アドレスURL",
"addresses_appear_here": "接続後、受信アドレスがここに表示されます。",
"advanced": "詳細設定",
"advanced_effects": "高度なエフェクト...",
"ago": "前",
"all_filter": "すべて",
"allow_custom_fees": "カスタム手数料を許可",
"amount": "金額",
@@ -92,30 +90,12 @@
"block_timestamp": "タイムスタンプ:",
"block_transactions": "トランザクション:",
"blockchain_syncing": "ブロックチェーン同期中 (%.1f%%)... 残高が不正確な場合があります。",
"bootstrap_daemon_running": "デーモン実行中",
"bootstrap_daemon_stopped": "デーモン停止",
"bootstrap_daemon_stopping": "デーモン停止中...",
"bootstrap_desc": "ブロックチェーンブートストラップをダウンロードして初期同期を劇的に高速化します。ブロックチェーンのスナップショットをダウンロードしてデータディレクトリに展開します。",
"bootstrap_downloading": "ブートストラップをダウンロード中...",
"bootstrap_extracting": "ブロックチェーンデータを展開中...",
"bootstrap_failed": "ブートストラップ失敗",
"bootstrap_mirror": "ミラー",
"bootstrap_mirror_tooltip": "ミラーからダウンロード (bootstrap2.dragonx.is)。\nメインのダウンロードが遅い場合や失敗する場合に使用してください。",
"bootstrap_restart_daemon": "デーモンを再起動",
"bootstrap_success": "ブートストラップ完了",
"bootstrap_success_desc": "ブロックチェーンデータが正常に展開されました。デーモンを起動してブートストラップポイントから同期を開始してください。",
"bootstrap_trust_warning": "bootstrap.dragonx.is または bootstrap2.dragonx.is のみを使用してください。信頼できないソースのファイルを使用するとノードが危険にさらされる可能性があります。",
"bootstrap_verifying": "チェックサムを検証中...",
"bootstrap_wallet_protected": "(wallet.dat は保護されています)",
"bootstrap_warning": "既存のブロックデータ(blocks、chainstate、notarizations)は削除され置き換えられます。wallet.dat は変更・削除されません。",
"cancel": "キャンセル",
"characters": "文字",
"choose_icon": "アイコンを選択",
"clear": "クリア",
"clear_all_bans": "すべてのブロックを解除",
"clear_anyway": "それでもクリア",
"clear_form_confirm": "すべてのフォームフィールドをクリアしますか?",
"clear_icon": "アイコンをクリア",
"clear_request": "リクエストをクリア",
"click_copy_address": "クリックしてアドレスをコピー",
"click_copy_uri": "クリックしてURIをコピー",
@@ -126,12 +106,8 @@
"confirm_clear_ztx_title": "Z-Tx 履歴クリアの確認",
"confirm_clear_ztx_warning1": "z-トランザクション履歴をクリアすると、ウォレットの再スキャンが実行されるまでシールド残高が0と表示される場合があります。",
"confirm_clear_ztx_warning2": "これが発生した場合、残高を回復するにはz-アドレスの秘密鍵を再スキャンを有効にして再インポートする必要があります。",
"confirm_delete_blockchain_msg": "デーモンを停止し、すべてのブロックチェーンデータ(blocks、chainstate、peers)を削除して、最初から再同期を開始します。数時間かかる場合があります。",
"confirm_delete_blockchain_safe": "wallet.dat、設定、トランザクション履歴は安全で削除されません。",
"confirm_delete_blockchain_title": "ブロックチェーンデータを削除",
"confirm_send": "送金を確認",
"confirm_transaction": "取引を確認",
"confirm_transfer": "送金を確認",
"confirmations": "確認数",
"confirmations_display": "%d 確認 | %s",
"confirmed": "確認済み",
@@ -196,7 +172,6 @@
"console_welcome": "ObsidianDragonコンソールへようこそ",
"console_zoom_in": "拡大",
"console_zoom_out": "縮小",
"copied": "コピーしました!",
"copy": "コピー",
"copy_address": "完全なアドレスをコピー",
"copy_error": "エラーをコピー",
@@ -205,21 +180,15 @@
"copy_uri": "URIをコピー",
"current_price": "現在の価格",
"custom_fees": "カスタム手数料",
"daemon_version": "デーモン",
"dark": "ダーク",
"date": "日付",
"date_label": "日付:",
"debug_logging": "デバッグログ",
"delete": "削除",
"delete_blockchain": "ブロックチェーンを削除",
"delete_blockchain_confirm": "削除して再同期",
"deshielding_warning": "警告:プライベート (Z) アドレスからトランスペアレント (T) アドレスへ資金をデシールドします。",
"difficulty": "難易度",
"disconnected": "切断済み",
"dismiss": "閉じる",
"display": "表示",
"download": "ダウンロード",
"download_bootstrap": "ブートストラップをダウンロード",
"dragonx_green": "DragonX(グリーン)",
"edit": "編集",
"error": "エラー",
@@ -227,23 +196,6 @@
"est_time_to_block": "予測ブロック時間",
"exit": "終了",
"explorer": "エクスプローラー",
"explorer_block_detail": "ブロック",
"explorer_block_hash": "ハッシュ",
"explorer_block_height": "高さ",
"explorer_block_merkle": "マークルルート",
"explorer_block_size": "サイズ",
"explorer_block_time": "時刻",
"explorer_block_txs": "トランザクション",
"explorer_chain_stats": "チェーン",
"explorer_invalid_query": "ブロック高さまたは64文字のハッシュを入力してください",
"explorer_mempool": "メモリプール",
"explorer_mempool_size": "サイズ",
"explorer_mempool_txs": "トランザクション",
"explorer_recent_blocks": "最近のブロック",
"explorer_search": "検索",
"explorer_section": "エクスプローラー",
"explorer_tx_outputs": "出力",
"explorer_tx_size": "サイズ",
"export": "エクスポート",
"export_csv": "CSVエクスポート",
"export_keys_btn": "鍵をエクスポート",
@@ -272,22 +224,14 @@
"fetch_prices": "価格を取得",
"file": "ファイル",
"file_save_location": "ファイルの保存先:~/.config/ObsidianDragon/",
"filter": "フィルター...",
"font_scale": "フォントサイズ",
"force_quit": "強制終了",
"force_quit_confirm_msg": "クリーンシャットダウンなしでデーモンを即座に終了します。\nブロックチェーンインデックスが破損し、再同期が必要になる可能性があります。",
"force_quit_confirm_title": "強制終了しますか?",
"force_quit_warning": "クリーンシャットダウンなしでデーモンを即座に終了します。ブロックチェーンの再同期が必要になる場合があります。",
"force_quit_yes": "強制終了",
"from": "送信元",
"from_upper": "送信元",
"full_details": "詳細情報",
"general": "一般",
"generating": "生成中",
"go_to_receive": "受信へ移動",
"height": "高さ",
"help": "ヘルプ",
"hidden_tag": " (非表示)",
"hide": "非表示",
"hide_address": "アドレスを非表示",
"hide_zero_balances": "残高0を非表示",
@@ -309,9 +253,6 @@
"import_key_warning": "警告:秘密鍵を決して共有しないでください!信頼できないソースからの鍵のインポートはウォレットを危険にさらす可能性があります。",
"import_key_z_format": "Zアドレス支出鍵 (secret-extended-key-...)",
"import_private_key": "秘密鍵をインポート...",
"incorrect_passphrase": "パスフレーズが正しくありません",
"incorrect_pin": "PINが正しくありません",
"insufficient_funds": "この金額と手数料に対して残高が不足しています。",
"invalid_address": "無効なアドレス形式",
"ip_address": "IPアドレス",
"keep": "保持",
@@ -325,7 +266,6 @@
"key_export_viewing_keys_zonly": "ビューイングキーはシールド (z) アドレスでのみ利用可能です",
"key_export_viewing_warning": "この閲覧鍵を使うと、他者があなたの受信取引と残高を見ることができますが、資金を使うことはできません。信頼できる相手とのみ共有してください。",
"label": "ラベル:",
"label_placeholder": "例: 貯金、マイニング...",
"language": "言語",
"light": "ライト",
"loading": "読み込み中...",
@@ -371,13 +311,6 @@
"mining_address_copied": "マイニングアドレスをコピーしました",
"mining_all_time": "全期間",
"mining_already_saved": "プールURLは既に保存済みです",
"mining_benchmark_cancel": "ベンチマークをキャンセル",
"mining_benchmark_cooling": "クーリング",
"mining_benchmark_dismiss": "閉じる",
"mining_benchmark_result": "最適",
"mining_benchmark_stabilizing": "安定化中",
"mining_benchmark_testing": "テスト中",
"mining_benchmark_tooltip": "このCPUに最適なスレッド数を検出",
"mining_block_copied": "ブロックハッシュをコピーしました",
"mining_chart_1m_ago": "1分前",
"mining_chart_5m_ago": "5分前",
@@ -397,15 +330,8 @@
"mining_filter_tip_all": "すべての収益を表示",
"mining_filter_tip_pool": "プール収益のみ表示",
"mining_filter_tip_solo": "ソロ収益のみ表示",
"mining_generate_z_address_hint": "受信タブでZアドレスを生成して支払いアドレスとして使用してください",
"mining_idle_gpu_off_tooltip": "制限なし: オン\nキーボード/マウス入力のみがアイドル状態を決定\nGPU検出を有効にするにはクリック",
"mining_idle_gpu_on_tooltip": "GPU対応: オン\nGPUアクティビティ(動画、ゲーム)がアイドルマイニングを防止\n制限なしモードに切り替えるにはクリック",
"mining_idle_off_tooltip": "アイドルマイニングを有効にする",
"mining_idle_on_tooltip": "アイドルマイニングを無効にする",
"mining_idle_scale_off_tooltip": "開始/停止モード: オン\nスレッドスケーリングモードに切り替えるにはクリック",
"mining_idle_scale_on_tooltip": "スレッドスケーリング: オン\n開始/停止モードに切り替えるにはクリック",
"mining_idle_threads_active_tooltip": "ユーザーアクティブ時のスレッド数",
"mining_idle_threads_idle_tooltip": "システムアイドル時のスレッド数",
"mining_local_hashrate": "ローカルハッシュレート",
"mining_mine": "マイニング",
"mining_mining_addr": "マイニングアドレス",
@@ -462,7 +388,6 @@
"no_addresses_available": "利用可能なアドレスがありません",
"no_addresses_match": "フィルタに一致するアドレスがありません",
"no_addresses_with_balance": "残高のあるアドレスがありません",
"no_addresses_yet": "アドレスがまだありません",
"no_matching": "一致する取引がありません",
"no_recent_receives": "最近の受信がありません",
"no_recent_sends": "最近の送信がありません",
@@ -528,7 +453,6 @@
"peers_upper": "ピア",
"peers_version": "バージョン",
"pending": "保留中",
"pin_not_set": "PINが設定されていません。パスフレーズで解除してください。",
"ping": "Ping",
"price_chart": "価格チャート",
"qr_code": "QRコード",
@@ -549,9 +473,7 @@
"recent_received": "最近の受信",
"recent_sends": "最近の送信",
"recipient": "受取人",
"recipient_balance": "受取人: %.8f → %.8f DRGX",
"recv_type": "受信",
"reduce_motion": "モーションを減らす",
"refresh": "更新",
"refresh_now": "今すぐ更新",
"remove_favorite": "お気に入りを削除",
@@ -571,10 +493,7 @@
"request_uri_copied": "支払いURIをクリップボードにコピーしました",
"rescan": "再スキャン",
"reset_to_defaults": "デフォルトにリセット",
"restarting_after_encryption": "暗号化後にデーモンを再起動中...",
"restore_address": "アドレスを復元",
"result_preview": "結果プレビュー",
"retry": "再試行",
"review_send": "送金を確認",
"rpc_host": "RPCホスト",
"rpc_pass": "パスワード",
@@ -583,39 +502,6 @@
"save": "保存",
"save_settings": "設定を保存",
"save_z_transactions": "Z取引を取引リストに保存",
"sb_auth_failed": "認証失敗 — rpcuser/rpcpassword を確認してください",
"sb_block": "ブロック: %d",
"sb_connecting_daemon": "dragonxd に接続中...",
"sb_connecting_err": "デーモンに接続中 — %s",
"sb_connecting_external": "外部デーモンに接続中...",
"sb_connecting_generic": "デーモンに接続中...",
"sb_daemon_crashed": "デーモンが %d 回クラッシュしました",
"sb_daemon_not_found": "デーモンが見つかりません",
"sb_dragonxd_running": "dragonxd 実行中",
"sb_dragonxd_stopped": "dragonxd 停止",
"sb_dragonxd_stopping": "dragonxd を停止中...",
"sb_extracting_sapling": "Sapling パラメータを展開中...",
"sb_importing_keys": "鍵をインポート中",
"sb_loading_config": "設定を読み込み中...",
"sb_mining_hs": "%.1f H/s",
"sb_net_ghs": "ネット: %.2f GH/s",
"sb_net_hs": "ネット: %.1f H/s",
"sb_net_khs": "ネット: %.2f KH/s",
"sb_net_mhs": "ネット: %.2f MH/s",
"sb_no_conf": "DRAGONX.conf が見つかりません",
"sb_peers": "ピア: %zu",
"sb_rescanning": "再スキャン",
"sb_rescanning_pct": "再スキャン %.0f%%",
"sb_restarting_daemon": "デーモンを再起動中...",
"sb_sapling_failed": "Sapling パラメータの展開に失敗しました。",
"sb_sapling_not_found": "Sapling パラメータが見つかりません。",
"sb_starting_daemon": "dragonxd を起動中...",
"sb_syncing_basic": "同期中 %.1f%% (残り %d)",
"sb_syncing_eta": "同期中 %.1f%% (残り %d, %.0f ブロック/秒, ~%s)",
"sb_waiting_config": "デーモン設定を待機中...",
"sb_waiting_daemon": "dragonxd を待機中...",
"sb_waiting_daemon_err": "dragonxd を待機中 — %s",
"sb_warming_up": "ウォームアップ中...",
"search_placeholder": "検索...",
"security": "セキュリティ",
"select_address": "アドレスを選択...",
@@ -667,15 +553,12 @@
"send_valid_transparent": "有効な透明アドレス",
"send_wallet_empty": "ウォレットは空です",
"send_yes_clear": "はい、クリア",
"sender_balance": "送信者: %.8f → %.8f DRGX",
"sending": "取引を送信中",
"sending_from": "送信元",
"sends_full_balance_warning": "全残高を送信します。送信アドレスの残高はゼロになります。",
"sent": "送信済み",
"sent_filter": "送信済み",
"sent_type": "送信済み",
"sent_upper": "送信済み",
"set_label": "ラベルを設定...",
"settings": "設定",
"settings_about_text": "DragonX (DRGX) 用のシールド暗号通貨ウォレット。Dear ImGui で構築された軽量でポータブルな体験。",
"settings_acrylic_level": "アクリルレベル:",
@@ -764,17 +647,13 @@
"shield_utxo_limit": "UTXO制限:",
"shield_wildcard_hint": "'*' を使用してすべての透明アドレスからシールド",
"shielded": "シールド",
"shielded_address": "シールドアドレス",
"shielded_to": "シールド先",
"shielded_type": "シールド",
"shielding_notice": "注意:トランスペアレント (T) アドレスからプライベート (Z) アドレスへ資金をシールドします。",
"show": "表示",
"show_hidden": "非表示を表示 (%d)",
"show_qr_code": "QRコードを表示",
"showing_transactions": "%d–%d / %d 件の取引を表示中(合計:%zu)",
"showing_x_of_y": "%d / %d アドレスを表示",
"showing_transactions": "%d–%d / %d 件の取引を表示中(合計:%zu)",
"simple_background": "シンプル背景",
"slider_off": "オフ",
"start_mining": "マイニング開始",
"status": "ステータス",
"stop_external": "外部デーモンを停止",
@@ -783,26 +662,17 @@
"success": "成功",
"summary": "概要",
"syncing": "同期中...",
"t_address": "Tアドレス",
"t_addresses": "Tアドレス",
"test_connection": "テスト",
"theme": "テーマ",
"theme_effects": "テーマ効果",
"theme_language": "テーマと言語",
"time_days_ago": "%d日前",
"time_hours_ago": "%d時間前",
"time_minutes_ago": "%d分前",
"time_seconds_ago": "%d秒前",
"timeout_15min": "15分",
"timeout_1hour": "1時間",
"timeout_1min": "1分",
"timeout_30min": "30分",
"timeout_5min": "5分",
"timeout_off": "オフ",
"to": "宛先",
"to_upper": "宛先",
"tools": "ツール",
"tools_actions": "ツールとアクション...",
"total": "合計",
"transaction_id": "取引ID",
"transaction_sent": "取引の送信に成功しました",
@@ -810,13 +680,7 @@
"transaction_url": "取引URL",
"transactions": "取引",
"transactions_upper": "取引",
"transfer_failed": "送金失敗",
"transfer_funds": "資金を送金",
"transfer_sent": "送金完了",
"transfer_sent_desc": "送金がネットワークに送信されました。",
"transfer_to": "送金先:",
"transparent": "透明",
"transparent_address": "トランスペアレントアドレス",
"tt_addr_url": "ブロックエクスプローラーでアドレスを表示するためのベース URL",
"tt_address_book": "クイック送信用の保存済みアドレスを管理",
"tt_auto_lock": "この無操作時間後にウォレットをロック",
@@ -831,8 +695,6 @@
"tt_custom_theme": "カスタムテーマがアクティブ",
"tt_debug_collapse": "デバッグログオプションを折りたたむ",
"tt_debug_expand": "デバッグログオプションを展開",
"tt_delete_blockchain": "すべてのブロックチェーンデータを削除して新規同期を開始します。wallet.dat と設定は保持されます。",
"tt_download_bootstrap": "ブロックチェーンブートストラップをダウンロードして同期を高速化\n既存のブロックデータは置き換えられます",
"tt_encrypt": "パスフレーズで wallet.dat を暗号化",
"tt_export_all": "すべての秘密鍵をファイルにエクスポート",
"tt_export_csv": "トランザクション履歴を CSV スプレッドシートとしてエクスポート",
@@ -850,7 +712,6 @@
"tt_mine_idle": "システムがアイドル状態(キーボード/マウス入力なし)\\nのとき自動的にマイニングを開始",
"tt_noise": "グレインテクスチャ強度(0%% = オフ、100%% = 最大)",
"tt_open_dir": "クリックしてファイルエクスプローラーで開く",
"tt_reduce_motion": "アクセシビリティのためにアニメーション遷移と残高補間を無効にする",
"tt_remove_encrypt": "暗号化を解除してウォレットを保護なしで保存",
"tt_remove_pin": "PIN を削除しアンロックにパスフレーズを要求",
"tt_report_bug": "プロジェクトトラッカーで問題を報告",
@@ -928,9 +789,7 @@
"warning_upper": "警告!",
"website": "ウェブサイト",
"window_opacity": "ウィンドウ透明度",
"wizard_daemon_start_failed": "デーモンの起動に失敗しました — 自動的に再試行されます",
"yes_clear": "はい、クリア",
"your_addresses": "あなたのアドレス",
"z_address": "Zアドレス",
"z_addresses": "Zアドレス"
}
+1 -142
View File
@@ -43,8 +43,6 @@
"address_url": "주소 URL",
"addresses_appear_here": "연결 후 수신 주소가 여기에 표시됩니다.",
"advanced": "고급 설정",
"advanced_effects": "고급 효과...",
"ago": "전",
"all_filter": "전체",
"allow_custom_fees": "사용자 정의 수수료 허용",
"amount": "금액",
@@ -92,30 +90,12 @@
"block_timestamp": "타임스탬프:",
"block_transactions": "트랜잭션:",
"blockchain_syncing": "블록체인 동기화 중 (%.1f%%)... 잔액이 정확하지 않을 수 있습니다.",
"bootstrap_daemon_running": "데몬 실행 중",
"bootstrap_daemon_stopped": "데몬 중지됨",
"bootstrap_daemon_stopping": "데몬 중지 중...",
"bootstrap_desc": "블록체인 부트스트랩을 다운로드하여 초기 동기화를 대폭 가속합니다. 블록체인 스냅샷을 다운로드하고 데이터 디렉토리에 추출합니다.",
"bootstrap_downloading": "부트스트랩 다운로드 중...",
"bootstrap_extracting": "블록체인 데이터 추출 중...",
"bootstrap_failed": "부트스트랩 실패",
"bootstrap_mirror": "미러",
"bootstrap_mirror_tooltip": "미러에서 다운로드 (bootstrap2.dragonx.is).\n메인 다운로드가 느리거나 실패할 경우 사용하세요.",
"bootstrap_restart_daemon": "데몬 재시작",
"bootstrap_success": "부트스트랩 완료",
"bootstrap_success_desc": "블록체인 데이터가 성공적으로 추출되었습니다. 데몬을 시작하여 부트스트랩 지점부터 동기화를 시작하세요.",
"bootstrap_trust_warning": "bootstrap.dragonx.is 또는 bootstrap2.dragonx.is만 사용하세요. 신뢰할 수 없는 출처의 파일을 사용하면 노드가 손상될 수 있습니다.",
"bootstrap_verifying": "체크섬 확인 중...",
"bootstrap_wallet_protected": "(wallet.dat 보호됨)",
"bootstrap_warning": "기존 블록 데이터(blocks, chainstate, notarizations)가 삭제되고 교체됩니다. wallet.dat는 수정되거나 삭제되지 않습니다.",
"cancel": "취소",
"characters": "문자",
"choose_icon": "아이콘 선택",
"clear": "지우기",
"clear_all_bans": "모든 차단 해제",
"clear_anyway": "그래도 삭제",
"clear_form_confirm": "모든 양식 필드를 지우시겠습니까?",
"clear_icon": "아이콘 지우기",
"clear_request": "요청 지우기",
"click_copy_address": "클릭하여 주소 복사",
"click_copy_uri": "클릭하여 URI 복사",
@@ -126,12 +106,8 @@
"confirm_clear_ztx_title": "Z-Tx 기록 삭제 확인",
"confirm_clear_ztx_warning1": "z-트랜잭션 기록을 삭제하면 지갑 재스캔이 수행될 때까지 차폐 잔액이 0으로 표시될 수 있습니다.",
"confirm_clear_ztx_warning2": "이런 경우, 잔액을 복구하려면 재스캔을 활성화하여 z-주소 개인키를 다시 가져와야 합니다.",
"confirm_delete_blockchain_msg": "데몬을 중지하고 모든 블록체인 데이터(blocks, chainstate, peers)를 삭제한 후 처음부터 다시 동기화합니다. 몇 시간이 걸릴 수 있습니다.",
"confirm_delete_blockchain_safe": "wallet.dat, 설정 및 거래 내역은 안전하며 삭제되지 않습니다.",
"confirm_delete_blockchain_title": "블록체인 데이터 삭제",
"confirm_send": "전송 확인",
"confirm_transaction": "거래 확인",
"confirm_transfer": "이체 확인",
"confirmations": "확인 수",
"confirmations_display": "%d 확인 | %s",
"confirmed": "확인됨",
@@ -196,7 +172,6 @@
"console_welcome": "ObsidianDragon 콘솔에 오신 것을 환영합니다",
"console_zoom_in": "확대",
"console_zoom_out": "축소",
"copied": "복사됨!",
"copy": "복사",
"copy_address": "전체 주소 복사",
"copy_error": "오류 복사",
@@ -205,21 +180,15 @@
"copy_uri": "URI 복사",
"current_price": "현재 가격",
"custom_fees": "사용자 정의 수수료",
"daemon_version": "데몬",
"dark": "다크",
"date": "날짜",
"date_label": "날짜:",
"debug_logging": "디버그 로깅",
"delete": "삭제",
"delete_blockchain": "블록체인 삭제",
"delete_blockchain_confirm": "삭제 후 재동기화",
"deshielding_warning": "경고: 프라이빗 (Z) 주소에서 투명 (T) 주소로 자금을 디실딩합니다.",
"difficulty": "난이도",
"disconnected": "연결 끊김",
"dismiss": "닫기",
"display": "디스플레이",
"download": "다운로드",
"download_bootstrap": "부트스트랩 다운로드",
"dragonx_green": "DragonX(그린)",
"edit": "편집",
"error": "오류",
@@ -227,23 +196,6 @@
"est_time_to_block": "예상 블록 시간",
"exit": "종료",
"explorer": "탐색기",
"explorer_block_detail": "블록",
"explorer_block_hash": "해시",
"explorer_block_height": "높이",
"explorer_block_merkle": "머클 루트",
"explorer_block_size": "크기",
"explorer_block_time": "시간",
"explorer_block_txs": "트랜잭션",
"explorer_chain_stats": "체인",
"explorer_invalid_query": "블록 높이 또는 64자 해시를 입력하세요",
"explorer_mempool": "멤풀",
"explorer_mempool_size": "크기",
"explorer_mempool_txs": "트랜잭션",
"explorer_recent_blocks": "최근 블록",
"explorer_search": "검색",
"explorer_section": "탐색기",
"explorer_tx_outputs": "출력",
"explorer_tx_size": "크기",
"export": "내보내기",
"export_csv": "CSV 내보내기",
"export_keys_btn": "키 내보내기",
@@ -272,22 +224,14 @@
"fetch_prices": "가격 조회",
"file": "파일",
"file_save_location": "파일 저장 위치: ~/.config/ObsidianDragon/",
"filter": "필터...",
"font_scale": "글꼴 크기",
"force_quit": "강제 종료",
"force_quit_confirm_msg": "정상 종료 없이 데몬을 즉시 종료합니다.\n블록체인 인덱스가 손상되어 재동기화가 필요할 수 있습니다.",
"force_quit_confirm_title": "강제 종료하시겠습니까?",
"force_quit_warning": "정상 종료 없이 데몬을 즉시 종료합니다. 블록체인 재동기화가 필요할 수 있습니다.",
"force_quit_yes": "강제 종료",
"from": "보낸 곳",
"from_upper": "보낸 곳",
"full_details": "전체 세부 정보",
"general": "일반",
"generating": "생성 중",
"go_to_receive": "수신으로 이동",
"height": "높이",
"help": "도움말",
"hidden_tag": " (숨김)",
"hide": "숨기기",
"hide_address": "주소 숨기기",
"hide_zero_balances": "잔액 0 숨기기",
@@ -309,9 +253,6 @@
"import_key_warning": "경고: 개인 키를 절대 공유하지 마세요! 신뢰할 수 없는 소스의 키를 가져오면 지갑이 위험해질 수 있습니다.",
"import_key_z_format": "Z 주소 지출 키 (secret-extended-key-...)",
"import_private_key": "개인 키 가져오기...",
"incorrect_passphrase": "잘못된 암호",
"incorrect_pin": "잘못된 PIN",
"insufficient_funds": "이 금액과 수수료를 위한 잔액이 부족합니다.",
"invalid_address": "잘못된 주소 형식",
"ip_address": "IP 주소",
"keep": "유지",
@@ -325,7 +266,6 @@
"key_export_viewing_keys_zonly": "보기 키는 차폐 (z) 주소에만 사용할 수 있습니다",
"key_export_viewing_warning": "이 조회 키를 사용하면 다른 사람이 수신 거래와 잔액을 볼 수 있지만 자금을 사용할 수는 없습니다. 신뢰할 수 있는 사람에게만 공유하세요.",
"label": "라벨:",
"label_placeholder": "예: 저축, 채굴...",
"language": "언어",
"light": "라이트",
"loading": "로딩 중...",
@@ -371,13 +311,6 @@
"mining_address_copied": "채굴 주소가 복사되었습니다",
"mining_all_time": "전체 기간",
"mining_already_saved": "풀 URL이 이미 저장되어 있습니다",
"mining_benchmark_cancel": "벤치마크 취소",
"mining_benchmark_cooling": "쿨링",
"mining_benchmark_dismiss": "닫기",
"mining_benchmark_result": "최적",
"mining_benchmark_stabilizing": "안정화 중",
"mining_benchmark_testing": "테스트 중",
"mining_benchmark_tooltip": "이 CPU에 최적의 스레드 수 찾기",
"mining_block_copied": "블록 해시가 복사되었습니다",
"mining_chart_1m_ago": "1분 전",
"mining_chart_5m_ago": "5분 전",
@@ -397,15 +330,8 @@
"mining_filter_tip_all": "모든 수익 표시",
"mining_filter_tip_pool": "풀 수익만 표시",
"mining_filter_tip_solo": "솔로 수익만 표시",
"mining_generate_z_address_hint": "수신 탭에서 Z 주소를 생성하여 지급 주소로 사용하세요",
"mining_idle_gpu_off_tooltip": "무제한: 켜짐\n키보드/마우스 입력만 유휴 상태를 결정\nGPU 감지를 활성화하려면 클릭",
"mining_idle_gpu_on_tooltip": "GPU 감지: 켜짐\nGPU 활동(비디오, 게임)이 유휴 채굴을 방지\n무제한 모드로 전환하려면 클릭",
"mining_idle_off_tooltip": "유휴 채굴 활성화",
"mining_idle_on_tooltip": "유휴 채굴 비활성화",
"mining_idle_scale_off_tooltip": "시작/중지 모드: 켜짐\n스레드 스케일링 모드로 전환하려면 클릭",
"mining_idle_scale_on_tooltip": "스레드 스케일링: 켜짐\n시작/중지 모드로 전환하려면 클릭",
"mining_idle_threads_active_tooltip": "사용자 활성 시 스레드",
"mining_idle_threads_idle_tooltip": "시스템 유휴 시 스레드",
"mining_local_hashrate": "로컬 해시레이트",
"mining_mine": "채굴",
"mining_mining_addr": "채굴 주소",
@@ -462,7 +388,6 @@
"no_addresses_available": "사용 가능한 주소 없음",
"no_addresses_match": "필터와 일치하는 주소가 없습니다",
"no_addresses_with_balance": "잔액이 있는 주소가 없습니다",
"no_addresses_yet": "아직 주소가 없습니다",
"no_matching": "일치하는 거래가 없습니다",
"no_recent_receives": "최근 수신 내역 없음",
"no_recent_sends": "최근 전송 내역 없음",
@@ -528,7 +453,6 @@
"peers_upper": "피어",
"peers_version": "버전",
"pending": "대기 중",
"pin_not_set": "PIN이 설정되지 않았습니다. 암호를 사용하여 잠금 해제하세요.",
"ping": "Ping",
"price_chart": "가격 차트",
"qr_code": "QR 코드",
@@ -549,9 +473,7 @@
"recent_received": "최근 수신",
"recent_sends": "최근 전송",
"recipient": "수신자",
"recipient_balance": "수신자: %.8f → %.8f DRGX",
"recv_type": "수신",
"reduce_motion": "모션 줄이기",
"refresh": "새로고침",
"refresh_now": "지금 새로고침",
"remove_favorite": "즐겨찾기 제거",
@@ -571,10 +493,7 @@
"request_uri_copied": "결제 URI가 클립보드에 복사되었습니다",
"rescan": "재스캔",
"reset_to_defaults": "기본값으로 재설정",
"restarting_after_encryption": "암호화 후 데몬 재시작 중...",
"restore_address": "주소 복원",
"result_preview": "결과 미리보기",
"retry": "재시도",
"review_send": "전송 검토",
"rpc_host": "RPC 호스트",
"rpc_pass": "비밀번호",
@@ -583,39 +502,6 @@
"save": "저장",
"save_settings": "설정 저장",
"save_z_transactions": "Z 거래를 거래 목록에 저장",
"sb_auth_failed": "인증 실패 — rpcuser/rpcpassword를 확인하세요",
"sb_block": "블록: %d",
"sb_connecting_daemon": "dragonxd에 연결 중...",
"sb_connecting_err": "데몬 연결 중 — %s",
"sb_connecting_external": "외부 데몬에 연결 중...",
"sb_connecting_generic": "데몬에 연결 중...",
"sb_daemon_crashed": "데몬이 %d회 충돌함",
"sb_daemon_not_found": "데몬을 찾을 수 없음",
"sb_dragonxd_running": "dragonxd 실행 중",
"sb_dragonxd_stopped": "dragonxd 중지됨",
"sb_dragonxd_stopping": "dragonxd 중지 중...",
"sb_extracting_sapling": "Sapling 매개변수 추출 중...",
"sb_importing_keys": "키 가져오기 중",
"sb_loading_config": "설정 불러오는 중...",
"sb_mining_hs": "%.1f H/s",
"sb_net_ghs": "네트: %.2f GH/s",
"sb_net_hs": "네트: %.1f H/s",
"sb_net_khs": "네트: %.2f KH/s",
"sb_net_mhs": "네트: %.2f MH/s",
"sb_no_conf": "DRAGONX.conf를 찾을 수 없음",
"sb_peers": "피어: %zu",
"sb_rescanning": "재스캔",
"sb_rescanning_pct": "재스캔 %.0f%%",
"sb_restarting_daemon": "데몬 재시작 중...",
"sb_sapling_failed": "Sapling 매개변수 추출 실패.",
"sb_sapling_not_found": "Sapling 매개변수를 찾을 수 없음.",
"sb_starting_daemon": "dragonxd 시작 중...",
"sb_syncing_basic": "동기화 %.1f%% (%d 남음)",
"sb_syncing_eta": "동기화 %.1f%% (%d 남음, %.0f 블록/초, ~%s)",
"sb_waiting_config": "데몬 설정 대기 중...",
"sb_waiting_daemon": "dragonxd 대기 중...",
"sb_waiting_daemon_err": "dragonxd 대기 중 — %s",
"sb_warming_up": "워밍업 중...",
"search_placeholder": "검색...",
"security": "보안",
"select_address": "주소 선택...",
@@ -667,15 +553,12 @@
"send_valid_transparent": "유효한 투명 주소",
"send_wallet_empty": "지갑이 비어 있습니다",
"send_yes_clear": "예, 지우기",
"sender_balance": "발신자: %.8f → %.8f DRGX",
"sending": "거래 전송 중",
"sending_from": "보내는 곳",
"sends_full_balance_warning": "전체 잔액을 전송합니다. 보내는 주소의 잔액이 0이 됩니다.",
"sent": "전송됨",
"sent_filter": "전송됨",
"sent_type": "전송됨",
"sent_upper": "전송됨",
"set_label": "라벨 설정...",
"settings": "설정",
"settings_about_text": "DragonX (DRGX)용 차폐 암호화폐 지갑으로, Dear ImGui로 제작되어 가볍고 휴대 가능합니다.",
"settings_acrylic_level": "아크릴 레벨:",
@@ -764,17 +647,13 @@
"shield_utxo_limit": "UTXO 제한:",
"shield_wildcard_hint": "'*'를 사용하여 모든 투명 주소에서 차폐",
"shielded": "차폐",
"shielded_address": "보호 주소",
"shielded_to": "차폐 대상",
"shielded_type": "차폐",
"shielding_notice": "참고: 투명 (T) 주소에서 프라이빗 (Z) 주소로 자금을 실딩합니다.",
"show": "표시",
"show_hidden": "숨겨진 항목 표시 (%d)",
"show_qr_code": "QR 코드 표시",
"showing_transactions": "%d–%d / %d건의 거래 표시 중 (총: %zu)",
"showing_x_of_y": "%d / %d 주소 표시",
"showing_transactions": "%d–%d / %d건의 거래 표시 중 (총: %zu)",
"simple_background": "단순 배경",
"slider_off": "끔",
"start_mining": "채굴 시작",
"status": "상태",
"stop_external": "외부 데몬 중지",
@@ -783,26 +662,17 @@
"success": "성공",
"summary": "요약",
"syncing": "동기화 중...",
"t_address": "T 주소",
"t_addresses": "T 주소",
"test_connection": "테스트",
"theme": "테마",
"theme_effects": "테마 효과",
"theme_language": "테마 및 언어",
"time_days_ago": "%d일 전",
"time_hours_ago": "%d시간 전",
"time_minutes_ago": "%d분 전",
"time_seconds_ago": "%d초 전",
"timeout_15min": "15분",
"timeout_1hour": "1시간",
"timeout_1min": "1분",
"timeout_30min": "30분",
"timeout_5min": "5분",
"timeout_off": "끔",
"to": "받는 곳",
"to_upper": "받는 곳",
"tools": "도구",
"tools_actions": "도구 및 작업...",
"total": "합계",
"transaction_id": "거래 ID",
"transaction_sent": "거래 전송 성공",
@@ -810,13 +680,7 @@
"transaction_url": "거래 URL",
"transactions": "거래",
"transactions_upper": "거래",
"transfer_failed": "이체 실패",
"transfer_funds": "자금 이체",
"transfer_sent": "이체 전송됨",
"transfer_sent_desc": "이체가 네트워크에 제출되었습니다.",
"transfer_to": "이체 대상:",
"transparent": "투명",
"transparent_address": "투명 주소",
"tt_addr_url": "블록 탐색기에서 주소를 보기 위한 기본 URL",
"tt_address_book": "빠른 전송을 위해 저장된 주소 관리",
"tt_auto_lock": "이 비활성 시간 후 지갑 잠금",
@@ -831,8 +695,6 @@
"tt_custom_theme": "사용자 지정 테마 활성화됨",
"tt_debug_collapse": "디버그 로깅 옵션 접기",
"tt_debug_expand": "디버그 로깅 옵션 펼치기",
"tt_delete_blockchain": "모든 블록체인 데이터를 삭제하고 새로 동기화합니다. wallet.dat 및 설정은 보존됩니다.",
"tt_download_bootstrap": "블록체인 부트스트랩을 다운로드하여 동기화 가속\n기존 블록 데이터가 교체됩니다",
"tt_encrypt": "비밀번호로 wallet.dat 암호화",
"tt_export_all": "모든 개인키를 파일로 내보내기",
"tt_export_csv": "거래 내역을 CSV 스프레드시트로 내보내기",
@@ -850,7 +712,6 @@
"tt_mine_idle": "시스템이 유휴 상태(키보드/마우스 입력 없음)일 때\\n자동으로 채굴 시작",
"tt_noise": "그레인 텍스처 강도 (0%% = 끔, 100%% = 최대)",
"tt_open_dir": "파일 탐색기에서 열려면 클릭",
"tt_reduce_motion": "접근성을 위해 애니메이션 전환 및 잔액 보간 비활성화",
"tt_remove_encrypt": "암호화를 제거하고 지갑을 보호 없이 저장",
"tt_remove_pin": "PIN을 제거하고 잠금 해제 시 비밀번호 요구",
"tt_report_bug": "프로젝트 트래커에서 문제 보고",
@@ -928,9 +789,7 @@
"warning_upper": "경고!",
"website": "웹사이트",
"window_opacity": "창 투명도",
"wizard_daemon_start_failed": "데몬 시작 실패 — 자동으로 재시도됩니다",
"yes_clear": "예, 지우기",
"your_addresses": "내 주소",
"z_address": "Z 주소",
"z_addresses": "Z 주소"
}
+4 -145
View File
@@ -43,8 +43,6 @@
"address_url": "URL do Endereço",
"addresses_appear_here": "Seus endereços de recebimento aparecerão aqui após a conexão.",
"advanced": "AVANÇADO",
"advanced_effects": "Efeitos Avançados...",
"ago": "atrás",
"all_filter": "Todos",
"allow_custom_fees": "Permitir taxas personalizadas",
"amount": "Valor",
@@ -92,30 +90,12 @@
"block_timestamp": "Carimbo de Data:",
"block_transactions": "Transações:",
"blockchain_syncing": "Blockchain sincronizando (%.1f%%)... Os saldos podem ser imprecisos.",
"bootstrap_daemon_running": "Daemon em execução",
"bootstrap_daemon_stopped": "Daemon parado",
"bootstrap_daemon_stopping": "Parando daemon...",
"bootstrap_desc": "Baixe um bootstrap da blockchain para acelerar drasticamente a sincronização inicial. Isso baixa um instantâneo da blockchain e o extrai no seu diretório de dados.",
"bootstrap_downloading": "Baixando bootstrap...",
"bootstrap_extracting": "Extraindo dados da blockchain...",
"bootstrap_failed": "Falha no Bootstrap",
"bootstrap_mirror": "Espelho",
"bootstrap_mirror_tooltip": "Baixar do espelho (bootstrap2.dragonx.is).\nUse isto se o download principal estiver lento ou falhando.",
"bootstrap_restart_daemon": "Reiniciar Daemon",
"bootstrap_success": "Bootstrap Completo",
"bootstrap_success_desc": "Os dados da blockchain foram extraídos com sucesso. Inicie o daemon para começar a sincronizar a partir do ponto do bootstrap.",
"bootstrap_trust_warning": "Use apenas bootstrap.dragonx.is ou bootstrap2.dragonx.is. Usar arquivos de fontes não confiáveis pode comprometer seu nó.",
"bootstrap_verifying": "Verificando somas de verificação...",
"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.",
"cancel": "Cancelar",
"characters": "caracteres",
"choose_icon": "Escolher Ícone",
"clear": "Limpar",
"clear_all_bans": "Remover Todos os Banimentos",
"clear_anyway": "Limpar mesmo assim",
"clear_form_confirm": "Limpar todos os campos do formulário?",
"clear_icon": "Limpar Ícone",
"clear_request": "Limpar Solicitação",
"click_copy_address": "Clique para copiar o endereço",
"click_copy_uri": "Clique para copiar a URI",
@@ -126,12 +106,8 @@
"confirm_clear_ztx_title": "Confirmar limpeza do histórico Z-Tx",
"confirm_clear_ztx_warning1": "Limpar o histórico de z-transações pode fazer com que seu saldo blindado apareça como 0 até que um reescaneamento da carteira seja realizado.",
"confirm_clear_ztx_warning2": "Se isso acontecer, você precisará reimportar as chaves privadas do seu endereço z com reescaneamento habilitado para recuperar seu saldo.",
"confirm_delete_blockchain_msg": "Isso irá parar o daemon, excluir todos os dados da blockchain (blocks, chainstate, peers) e iniciar uma nova sincronização do zero. Isso pode levar várias horas.",
"confirm_delete_blockchain_safe": "Seu wallet.dat, configuração e histórico de transações estão seguros e não serão excluídos.",
"confirm_delete_blockchain_title": "Excluir Dados da Blockchain",
"confirm_send": "Confirmar Envio",
"confirm_transaction": "Confirmar Transação",
"confirm_transfer": "Confirmar Transferência",
"confirmations": "Confirmações",
"confirmations_display": "%d confirmações | %s",
"confirmed": "Confirmado",
@@ -196,7 +172,6 @@
"console_welcome": "Bem-vindo ao Console ObsidianDragon",
"console_zoom_in": "Aumentar zoom",
"console_zoom_out": "Diminuir zoom",
"copied": "Copiado!",
"copy": "Copiar",
"copy_address": "Copiar Endereço Completo",
"copy_error": "Copiar Erro",
@@ -205,45 +180,22 @@
"copy_uri": "Copiar URI",
"current_price": "Preço Atual",
"custom_fees": "Taxas personalizadas",
"daemon_version": "Daemon",
"dark": "Escuro",
"date": "Data",
"date_label": "Data:",
"debug_logging": "REGISTRO DE DEPURAÇÃO",
"delete": "Excluir",
"delete_blockchain": "Excluir Blockchain",
"delete_blockchain_confirm": "Excluir e Ressincronizar",
"deshielding_warning": "Aviso: Isso irá des-blindar fundos de um endereço privado (Z) para um endereço transparente (T).",
"difficulty": "Dificuldade",
"disconnected": "Desconectado",
"dismiss": "Dispensar",
"display": "Exibição",
"download": "Baixar",
"download_bootstrap": "Baixar Bootstrap",
"dragonx_green": "DragonX (Verde)",
"edit": "Editar",
"error": "Erro",
"error_format": "Erro: %s",
"est_time_to_block": "Tempo Est. por Bloco",
"exit": "Sair",
"explorer": "Explorador",
"explorer_block_detail": "Bloco",
"explorer_block_hash": "Hash",
"explorer_block_height": "Altura",
"explorer_block_merkle": "Raiz Merkle",
"explorer_block_size": "Tamanho",
"explorer_block_time": "Hora",
"explorer_block_txs": "Transações",
"explorer_chain_stats": "Cadeia",
"explorer_invalid_query": "Insira uma altura de bloco ou um hash de 64 caracteres",
"explorer_mempool": "Mempool",
"explorer_mempool_size": "Tamanho",
"explorer_mempool_txs": "Transações",
"explorer_recent_blocks": "Blocos Recentes",
"explorer_search": "Pesquisar",
"explorer_section": "EXPLORADOR",
"explorer_tx_outputs": "Saídas",
"explorer_tx_size": "Tamanho",
"explorer": "EXPLORADOR",
"export": "Exportar",
"export_csv": "Exportar CSV",
"export_keys_btn": "Exportar Chaves",
@@ -272,22 +224,14 @@
"fetch_prices": "Buscar preços",
"file": "Arquivo",
"file_save_location": "O arquivo será salvo em: ~/.config/ObsidianDragon/",
"filter": "Filtrar...",
"font_scale": "Escala da Fonte",
"force_quit": "Forçar Saída",
"force_quit_confirm_msg": "Isso matará imediatamente o daemon sem um desligamento limpo.\nIsso pode corromper o índice da blockchain e exigir uma ressincronização.",
"force_quit_confirm_title": "Forçar Saída?",
"force_quit_warning": "Isso matará imediatamente o daemon sem um desligamento limpo. Pode exigir uma ressincronização da blockchain.",
"force_quit_yes": "Forçar Saída",
"from": "De",
"from_upper": "DE",
"full_details": "Detalhes Completos",
"general": "Geral",
"generating": "Gerando",
"go_to_receive": "Ir para Receber",
"height": "Altura",
"help": "Ajuda",
"hidden_tag": " (oculto)",
"hide": "Ocultar",
"hide_address": "Ocultar endereço",
"hide_zero_balances": "Ocultar saldos zero",
@@ -309,9 +253,6 @@
"import_key_warning": "Aviso: Nunca compartilhe suas chaves privadas! Importar chaves de fontes não confiáveis pode comprometer sua carteira.",
"import_key_z_format": "Chaves de gasto de z-endereço (secret-extended-key-...)",
"import_private_key": "Importar Chave Privada...",
"incorrect_passphrase": "Senha incorreta",
"incorrect_pin": "PIN incorreto",
"insufficient_funds": "Fundos insuficientes para este valor mais taxa.",
"invalid_address": "Formato de endereço inválido",
"ip_address": "Endereço IP",
"keep": "Manter",
@@ -325,7 +266,6 @@
"key_export_viewing_keys_zonly": "As chaves de visualização estão disponíveis apenas para endereços blindados (z)",
"key_export_viewing_warning": "Esta chave de visualização permite que outros vejam suas transações recebidas e saldo, mas NÃO gastem seus fundos. Compartilhe apenas com partes confiáveis.",
"label": "Rótulo:",
"label_placeholder": "ex. Poupança, Mineração...",
"language": "Idioma",
"light": "Claro",
"loading": "Carregando...",
@@ -371,13 +311,6 @@
"mining_address_copied": "Endereço de mineração copiado",
"mining_all_time": "Todo o Tempo",
"mining_already_saved": "URL do pool já salva",
"mining_benchmark_cancel": "Cancelar benchmark",
"mining_benchmark_cooling": "Resfriando",
"mining_benchmark_dismiss": "Fechar",
"mining_benchmark_result": "Ótimo",
"mining_benchmark_stabilizing": "Estabilizando",
"mining_benchmark_testing": "Testando",
"mining_benchmark_tooltip": "Encontrar o número ideal de threads para esta CPU",
"mining_block_copied": "Hash do bloco copiado",
"mining_chart_1m_ago": "1m atrás",
"mining_chart_5m_ago": "5m atrás",
@@ -397,15 +330,8 @@
"mining_filter_tip_all": "Mostrar todos os ganhos",
"mining_filter_tip_pool": "Mostrar apenas ganhos do pool",
"mining_filter_tip_solo": "Mostrar apenas ganhos solo",
"mining_generate_z_address_hint": "Gere um endereço Z na aba Receber para usar como endereço de pagamento",
"mining_idle_gpu_off_tooltip": "Sem restrição: ATIVADO\nApenas entrada de teclado/mouse determina o estado ocioso\nClique para ativar detecção de GPU",
"mining_idle_gpu_on_tooltip": "GPU-consciente: ATIVADO\nAtividade de GPU (vídeo, jogos) impede mineração ociosa\nClique para modo sem restrição",
"mining_idle_off_tooltip": "Ativar mineração ociosa",
"mining_idle_on_tooltip": "Desativar mineração ociosa",
"mining_idle_scale_off_tooltip": "Modo iniciar/parar: ATIVADO\nClique para mudar para modo de escala de threads",
"mining_idle_scale_on_tooltip": "Escala de threads: ATIVADO\nClique para mudar para modo iniciar/parar",
"mining_idle_threads_active_tooltip": "Threads quando o usuário está ativo",
"mining_idle_threads_idle_tooltip": "Threads quando o sistema está ocioso",
"mining_local_hashrate": "Hashrate Local",
"mining_mine": "Minerar",
"mining_mining_addr": "End. Mineração",
@@ -462,7 +388,6 @@
"no_addresses_available": "Nenhum endereço disponível",
"no_addresses_match": "Nenhum endereço corresponde ao filtro",
"no_addresses_with_balance": "Nenhum endereço com saldo",
"no_addresses_yet": "Nenhum endereço ainda",
"no_matching": "Nenhuma transação correspondente",
"no_recent_receives": "Nenhum recebimento recente",
"no_recent_sends": "Nenhum envio recente",
@@ -475,7 +400,7 @@
"notes": "Notas",
"notes_optional": "Notas (opcional):",
"output_filename": "Nome do arquivo de saída:",
"overview": "Resumo",
"overview": "Visão Geral",
"paste": "Colar",
"paste_from_clipboard": "Colar da Área de Transferência",
"pay_from": "Pagar de",
@@ -528,7 +453,6 @@
"peers_upper": "PARES",
"peers_version": "Versão",
"pending": "Pendente",
"pin_not_set": "PIN não definido. Use a senha para desbloquear.",
"ping": "Ping",
"price_chart": "Gráfico de Preços",
"qr_code": "Código QR",
@@ -549,9 +473,7 @@
"recent_received": "RECEBIDOS RECENTES",
"recent_sends": "ENVIOS RECENTES",
"recipient": "DESTINATÁRIO",
"recipient_balance": "Destinatário: %.8f → %.8f DRGX",
"recv_type": "Receb.",
"reduce_motion": "Reduzir Movimento",
"refresh": "Atualizar",
"refresh_now": "Atualizar Agora",
"remove_favorite": "Remover favorito",
@@ -571,10 +493,7 @@
"request_uri_copied": "URI de pagamento copiada para a área de transferência",
"rescan": "Reescanear",
"reset_to_defaults": "Redefinir Padrões",
"restarting_after_encryption": "Reiniciando daemon após criptografia...",
"restore_address": "Restaurar endereço",
"result_preview": "Pré-visualização do resultado",
"retry": "Tentar novamente",
"review_send": "Revisar Envio",
"rpc_host": "Host RPC",
"rpc_pass": "Senha",
@@ -583,39 +502,6 @@
"save": "Salvar",
"save_settings": "Salvar Configurações",
"save_z_transactions": "Salvar Z-tx na lista de tx",
"sb_auth_failed": "Autenticação falhou — verifique rpcuser/rpcpassword",
"sb_block": "Bloco: %d",
"sb_connecting_daemon": "Conectando ao dragonxd...",
"sb_connecting_err": "Conectando ao daemon — %s",
"sb_connecting_external": "Conectando ao daemon externo...",
"sb_connecting_generic": "Conectando ao daemon...",
"sb_daemon_crashed": "O daemon travou %d vezes",
"sb_daemon_not_found": "Daemon não encontrado",
"sb_dragonxd_running": "dragonxd em execução",
"sb_dragonxd_stopped": "dragonxd parado",
"sb_dragonxd_stopping": "Parando dragonxd...",
"sb_extracting_sapling": "Extraindo parâmetros Sapling...",
"sb_importing_keys": "Importando chaves",
"sb_loading_config": "Carregando configuração...",
"sb_mining_hs": "%.1f H/s",
"sb_net_ghs": "Rede: %.2f GH/s",
"sb_net_hs": "Rede: %.1f H/s",
"sb_net_khs": "Rede: %.2f KH/s",
"sb_net_mhs": "Rede: %.2f MH/s",
"sb_no_conf": "DRAGONX.conf não encontrado",
"sb_peers": "Pares: %zu",
"sb_rescanning": "Reescaneando",
"sb_rescanning_pct": "Reescaneando %.0f%%",
"sb_restarting_daemon": "Reiniciando daemon...",
"sb_sapling_failed": "Falha ao extrair parâmetros Sapling.",
"sb_sapling_not_found": "Parâmetros Sapling não encontrados.",
"sb_starting_daemon": "Iniciando dragonxd...",
"sb_syncing_basic": "Sincronizando %.1f%% (%d restantes)",
"sb_syncing_eta": "Sincronizando %.1f%% (%d restantes, %.0f blk/s, ~%s)",
"sb_waiting_config": "Aguardando configuração do daemon...",
"sb_waiting_daemon": "Aguardando dragonxd...",
"sb_waiting_daemon_err": "Aguardando dragonxd — %s",
"sb_warming_up": "Aquecendo...",
"search_placeholder": "Pesquisar...",
"security": "SEGURANÇA",
"select_address": "Selecionar endereço...",
@@ -667,16 +553,13 @@
"send_valid_transparent": "Endereço transparente válido",
"send_wallet_empty": "Sua carteira está vazia",
"send_yes_clear": "Sim, Limpar",
"sender_balance": "Remetente: %.8f → %.8f DRGX",
"sending": "Enviando transação",
"sending_from": "ENVIANDO DE",
"sends_full_balance_warning": "Isso envia o saldo total. O endereço de envio terá saldo zero.",
"sent": "enviado",
"sent_filter": "Enviado",
"sent_type": "Enviado",
"sent_upper": "ENVIADO",
"set_label": "Definir Rótulo...",
"settings": "Ajustes",
"settings": "Configurações",
"settings_about_text": "Uma carteira de criptomoeda blindada para DragonX (DRGX), criada com Dear ImGui para uma experiência leve e portátil.",
"settings_acrylic_level": "Nível acrílico:",
"settings_address_book": "Livro de endereços...",
@@ -764,17 +647,13 @@
"shield_utxo_limit": "Limite UTXO:",
"shield_wildcard_hint": "Use '*' para blindar de todos os endereços transparentes",
"shielded": "Blindado",
"shielded_address": "Endereço Blindado",
"shielded_to": "BLINDADO PARA",
"shielded_type": "Blindado",
"shielding_notice": "Nota: Isso blindará fundos de um endereço transparente (T) para um endereço privado (Z).",
"show": "Mostrar",
"show_hidden": "Mostrar ocultos (%d)",
"show_qr_code": "Mostrar Código QR",
"showing_transactions": "Mostrando %d–%d de %d transações (total: %zu)",
"showing_x_of_y": "Mostrando %d de %d endereços",
"showing_transactions": "Mostrando %d–%d de %d transações (total: %zu)",
"simple_background": "Fundo simples",
"slider_off": "Desligado",
"start_mining": "Iniciar Mineração",
"status": "Status",
"stop_external": "Parar daemon externo",
@@ -783,26 +662,17 @@
"success": "Sucesso",
"summary": "Resumo",
"syncing": "Sincronizando...",
"t_address": "Endereço T",
"t_addresses": "Endereços T",
"test_connection": "Testar",
"theme": "Tema",
"theme_effects": "Efeitos de tema",
"theme_language": "TEMA E IDIOMA",
"time_days_ago": "há %d dias",
"time_hours_ago": "há %d horas",
"time_minutes_ago": "há %d minutos",
"time_seconds_ago": "há %d segundos",
"timeout_15min": "15 min",
"timeout_1hour": "1 hora",
"timeout_1min": "1 min",
"timeout_30min": "30 min",
"timeout_5min": "5 min",
"timeout_off": "Desligado",
"to": "Para",
"to_upper": "PARA",
"tools": "FERRAMENTAS",
"tools_actions": "Ferramentas e Ações...",
"total": "Total",
"transaction_id": "ID DA TRANSAÇÃO",
"transaction_sent": "Transação enviada com sucesso",
@@ -810,13 +680,7 @@
"transaction_url": "URL da Transação",
"transactions": "Transações",
"transactions_upper": "TRANSAÇÕES",
"transfer_failed": "Transferência Falhou",
"transfer_funds": "Transferir Fundos",
"transfer_sent": "Transferência Enviada",
"transfer_sent_desc": "Sua transferência foi enviada à rede.",
"transfer_to": "Transferir para:",
"transparent": "Transparente",
"transparent_address": "Endereço Transparente",
"tt_addr_url": "URL base para visualizar endereços em um explorador de blocos",
"tt_address_book": "Gerenciar endereços salvos para envio rápido",
"tt_auto_lock": "Bloquear carteira após este tempo de inatividade",
@@ -831,8 +695,6 @@
"tt_custom_theme": "Tema personalizado ativo",
"tt_debug_collapse": "Recolher opções de registro de depuração",
"tt_debug_expand": "Expandir opções de registro de depuração",
"tt_delete_blockchain": "Excluir todos os dados da blockchain e iniciar uma nova sincronização. wallet.dat e configuração são preservados.",
"tt_download_bootstrap": "Baixar bootstrap da blockchain para acelerar a sincronização\nDados de blocos existentes serão substituídos",
"tt_encrypt": "Encriptar wallet.dat com uma frase secreta",
"tt_export_all": "Exportar todas as chaves privadas para um arquivo",
"tt_export_csv": "Exportar histórico de transações como planilha CSV",
@@ -850,7 +712,6 @@
"tt_mine_idle": "Iniciar mineração automaticamente quando o\\nsistema estiver ocioso (sem entrada de teclado/mouse)",
"tt_noise": "Intensidade de textura granulada (0%% = desligado, 100%% = máximo)",
"tt_open_dir": "Clique para abrir no explorador de arquivos",
"tt_reduce_motion": "Desativar transições animadas e lerp de saldo para acessibilidade",
"tt_remove_encrypt": "Remover encriptação e armazenar a carteira desprotegida",
"tt_remove_pin": "Remover PIN e exigir frase secreta para desbloquear",
"tt_report_bug": "Reportar um problema no rastreador do projeto",
@@ -928,9 +789,7 @@
"warning_upper": "AVISO!",
"website": "Website",
"window_opacity": "Opacidade da Janela",
"wizard_daemon_start_failed": "Falha ao iniciar o daemon — será tentado novamente automaticamente",
"yes_clear": "Sim, Limpar",
"your_addresses": "Seus Endereços",
"z_address": "Endereço Z",
"z_addresses": "Endereços Z"
}
+4 -145
View File
@@ -42,9 +42,7 @@
"address_upper": "АДРЕС",
"address_url": "URL адреса",
"addresses_appear_here": "Ваши адреса для получения появятся здесь после подключения.",
"advanced": "ПРОЧЕЕ",
"advanced_effects": "Расширенные эффекты...",
"ago": "назад",
"advanced": "РАСШИРЕННЫЕ",
"all_filter": "Все",
"allow_custom_fees": "Разрешить пользовательские комиссии",
"amount": "Сумма",
@@ -92,30 +90,12 @@
"block_timestamp": "Временная метка:",
"block_transactions": "Транзакции:",
"blockchain_syncing": "Синхронизация блокчейна (%.1f%%)... Балансы могут быть неточными.",
"bootstrap_daemon_running": "Демон запущен",
"bootstrap_daemon_stopped": "Демон остановлен",
"bootstrap_daemon_stopping": "Остановка демона...",
"bootstrap_desc": "Загрузите бутстрап блокчейна для значительного ускорения начальной синхронизации. Это загружает снимок блокчейна и извлекает его в ваш каталог данных.",
"bootstrap_downloading": "Загрузка бутстрапа...",
"bootstrap_extracting": "Извлечение данных блокчейна...",
"bootstrap_failed": "Ошибка бутстрапа",
"bootstrap_mirror": "Зеркало",
"bootstrap_mirror_tooltip": "Скачать с зеркала (bootstrap2.dragonx.is).\nИспользуйте, если основная загрузка медленная или не работает.",
"bootstrap_restart_daemon": "Перезапустить демон",
"bootstrap_success": "Бутстрап завершён",
"bootstrap_success_desc": "Данные блокчейна успешно извлечены. Запустите демон для начала синхронизации с точки бутстрапа.",
"bootstrap_trust_warning": "Используйте только bootstrap.dragonx.is или bootstrap2.dragonx.is. Использование файлов из ненадёжных источников может скомпрометировать ваш узел.",
"bootstrap_verifying": "Проверка контрольных сумм...",
"bootstrap_wallet_protected": "(wallet.dat защищён)",
"bootstrap_warning": "Существующие данные блоков (blocks, chainstate, notarizations) будут удалены и заменены. Ваш wallet.dat НЕ будет изменён или удалён.",
"cancel": "Отмена",
"characters": "символов",
"choose_icon": "Выбрать иконку",
"clear": "Очистить",
"clear_all_bans": "Снять все блокировки",
"clear_anyway": "Всё равно очистить",
"clear_form_confirm": "Очистить все поля формы?",
"clear_icon": "Удалить иконку",
"clear_request": "Очистить запрос",
"click_copy_address": "Нажмите, чтобы скопировать адрес",
"click_copy_uri": "Нажмите, чтобы скопировать URI",
@@ -126,12 +106,8 @@
"confirm_clear_ztx_title": "Подтвердить очистку истории Z-Tx",
"confirm_clear_ztx_warning1": "Очистка истории z-транзакций может привести к отображению защищённого баланса как 0, пока не будет выполнено пересканирование кошелька.",
"confirm_clear_ztx_warning2": "Если это произойдёт, вам потребуется повторно импортировать приватные ключи вашего z-адреса с включённым пересканированием для восстановления баланса.",
"confirm_delete_blockchain_msg": "Это остановит демон, удалит все данные блокчейна (blocks, chainstate, peers) и начнёт синхронизацию с нуля. Это может занять несколько часов.",
"confirm_delete_blockchain_safe": "Ваш wallet.dat, конфигурация и история транзакций в безопасности и не будут удалены.",
"confirm_delete_blockchain_title": "Удалить данные блокчейна",
"confirm_send": "Подтвердить отправку",
"confirm_transaction": "Подтвердить транзакцию",
"confirm_transfer": "Подтвердить перевод",
"confirmations": "Подтверждения",
"confirmations_display": "%d подтверждений | %s",
"confirmed": "Подтверждено",
@@ -196,7 +172,6 @@
"console_welcome": "Добро пожаловать в консоль ObsidianDragon",
"console_zoom_in": "Увеличить",
"console_zoom_out": "Уменьшить",
"copied": "Скопировано!",
"copy": "Копировать",
"copy_address": "Копировать полный адрес",
"copy_error": "Копировать ошибку",
@@ -205,45 +180,22 @@
"copy_uri": "Копировать URI",
"current_price": "Текущая цена",
"custom_fees": "Пользовательские комиссии",
"daemon_version": "Демон",
"dark": "Тёмная",
"date": "Дата",
"date_label": "Дата:",
"debug_logging": "ЖУРНАЛ ОТЛАДКИ",
"delete": "Удалить",
"delete_blockchain": "Удалить блокчейн",
"delete_blockchain_confirm": "Удалить и пересинхронизировать",
"deshielding_warning": "Внимание: это переведёт средства из приватного (Z) адреса на прозрачный (T) адрес.",
"difficulty": "Сложность",
"disconnected": "Отключено",
"dismiss": "Отклонить",
"display": "Отображение",
"download": "Скачать",
"download_bootstrap": "Скачать бутстрап",
"dragonx_green": "DragonX (Зелёная)",
"edit": "Редактировать",
"error": "Ошибка",
"error_format": "Ошибка: %s",
"est_time_to_block": "Расч. время до блока",
"exit": "Выход",
"explorer": "Проводник",
"explorer_block_detail": "Блок",
"explorer_block_hash": "Хеш",
"explorer_block_height": "Высота",
"explorer_block_merkle": "Корень Меркла",
"explorer_block_size": "Размер",
"explorer_block_time": "Время",
"explorer_block_txs": "Транзакции",
"explorer_chain_stats": "Цепочка",
"explorer_invalid_query": "Введите высоту блока или 64-символьный хеш",
"explorer_mempool": "Мемпул",
"explorer_mempool_size": "Размер",
"explorer_mempool_txs": "Транзакции",
"explorer_recent_blocks": "Последние блоки",
"explorer_search": "Поиск",
"explorer_section": "ОБОЗРЕВАТЕЛЬ",
"explorer_tx_outputs": "Выходы",
"explorer_tx_size": "Размер",
"explorer": "ОБОЗРЕВАТЕЛЬ",
"export": "Экспорт",
"export_csv": "Экспорт в CSV",
"export_keys_btn": "Экспорт ключей",
@@ -272,22 +224,14 @@
"fetch_prices": "Получить цены",
"file": "Файл",
"file_save_location": "Файл будет сохранён в: ~/.config/ObsidianDragon/",
"filter": "Фильтр...",
"font_scale": "Масштаб шрифта",
"force_quit": "Принудительный выход",
"force_quit_confirm_msg": "Это немедленно завершит демон без корректного завершения.\nЭто может повредить индекс блокчейна и потребовать повторной синхронизации.",
"force_quit_confirm_title": "Принудительный выход?",
"force_quit_warning": "Это немедленно завершит демон без корректного завершения. Может потребоваться повторная синхронизация блокчейна.",
"force_quit_yes": "Принудительный выход",
"from": "От",
"from_upper": "ОТ",
"full_details": "Полные детали",
"general": "Общие",
"generating": "Генерация",
"go_to_receive": "Перейти к получению",
"height": "Высота",
"help": "Справка",
"hidden_tag": " (скрыт)",
"hide": "Скрыть",
"hide_address": "Скрыть адрес",
"hide_zero_balances": "Скрыть нулевые балансы",
@@ -309,9 +253,6 @@
"import_key_warning": "Предупреждение: Никогда не делитесь своими приватными ключами! Импорт ключей из ненадёжных источников может скомпрометировать ваш кошелёк.",
"import_key_z_format": "Ключи расходования z-адресов (secret-extended-key-...)",
"import_private_key": "Импорт приватного ключа...",
"incorrect_passphrase": "Неверный пароль",
"incorrect_pin": "Неверный PIN",
"insufficient_funds": "Недостаточно средств для этой суммы плюс комиссия.",
"invalid_address": "Неверный формат адреса",
"ip_address": "IP-адрес",
"keep": "Сохранить",
@@ -325,7 +266,6 @@
"key_export_viewing_keys_zonly": "Ключи просмотра доступны только для экранированных (z) адресов",
"key_export_viewing_warning": "Этот ключ просмотра позволяет другим видеть входящие транзакции и баланс, но НЕ тратить ваши средства. Делитесь только с доверенными лицами.",
"label": "Метка:",
"label_placeholder": "напр. Накопления, Майнинг...",
"language": "Язык",
"light": "Светлая",
"loading": "Загрузка...",
@@ -371,13 +311,6 @@
"mining_address_copied": "Адрес майнинга скопирован",
"mining_all_time": "За всё время",
"mining_already_saved": "URL пула уже сохранён",
"mining_benchmark_cancel": "Отменить тест",
"mining_benchmark_cooling": "Охлаждение",
"mining_benchmark_dismiss": "Закрыть",
"mining_benchmark_result": "Оптимально",
"mining_benchmark_stabilizing": "Стабилизация",
"mining_benchmark_testing": "Тестирование",
"mining_benchmark_tooltip": "Найти оптимальное количество потоков для этого процессора",
"mining_block_copied": "Хэш блока скопирован",
"mining_chart_1m_ago": "1м назад",
"mining_chart_5m_ago": "5м назад",
@@ -397,15 +330,8 @@
"mining_filter_tip_all": "Показать все доходы",
"mining_filter_tip_pool": "Показать только доходы пула",
"mining_filter_tip_solo": "Показать только доходы соло",
"mining_generate_z_address_hint": "Создайте Z-адрес на вкладке «Получить» для использования в качестве адреса выплат",
"mining_idle_gpu_off_tooltip": "Без ограничений: ВКЛ\nТолько ввод с клавиатуры/мыши определяет состояние простоя\nНажмите для GPU-контроля",
"mining_idle_gpu_on_tooltip": "GPU-контроль: ВКЛ\nАктивность GPU (видео, игры) предотвращает майнинг в простое\nНажмите для режима без ограничений",
"mining_idle_off_tooltip": "Включить майнинг в простое",
"mining_idle_on_tooltip": "Отключить майнинг в простое",
"mining_idle_scale_off_tooltip": "Режим старт/стоп: ВКЛ\nНажмите для переключения на масштабирование потоков",
"mining_idle_scale_on_tooltip": "Масштабирование потоков: ВКЛ\nНажмите для переключения на режим старт/стоп",
"mining_idle_threads_active_tooltip": "Потоки при активности пользователя",
"mining_idle_threads_idle_tooltip": "Потоки при простое системы",
"mining_local_hashrate": "Локальный хешрейт",
"mining_mine": "Майнить",
"mining_mining_addr": "Адрес майн.",
@@ -462,7 +388,6 @@
"no_addresses_available": "Нет доступных адресов",
"no_addresses_match": "Нет адресов, соответствующих фильтру",
"no_addresses_with_balance": "Нет адресов с балансом",
"no_addresses_yet": "Пока нет адресов",
"no_matching": "Нет подходящих транзакций",
"no_recent_receives": "Нет недавних получений",
"no_recent_sends": "Нет недавних отправлений",
@@ -528,7 +453,6 @@
"peers_upper": "УЗЛЫ",
"peers_version": "Версия",
"pending": "Ожидание",
"pin_not_set": "PIN не установлен. Используйте пароль для разблокировки.",
"ping": "Пинг",
"price_chart": "График цен",
"qr_code": "QR-код",
@@ -549,9 +473,7 @@
"recent_received": "НЕДАВНО ПОЛУЧЕНО",
"recent_sends": "НЕДАВНО ОТПРАВЛЕНО",
"recipient": "ПОЛУЧАТЕЛЬ",
"recipient_balance": "Получатель: %.8f → %.8f DRGX",
"recv_type": "Получ.",
"reduce_motion": "Уменьшить анимацию",
"refresh": "Обновить",
"refresh_now": "Обновить сейчас",
"remove_favorite": "Удалить из избранного",
@@ -571,10 +493,7 @@
"request_uri_copied": "URI платежа скопирован в буфер обмена",
"rescan": "Пересканировать",
"reset_to_defaults": "Сбросить настройки",
"restarting_after_encryption": "Перезапуск демона после шифрования...",
"restore_address": "Восстановить адрес",
"result_preview": "Предпросмотр результата",
"retry": "Повторить",
"review_send": "Проверить отправку",
"rpc_host": "RPC-хост",
"rpc_pass": "Пароль",
@@ -583,39 +502,6 @@
"save": "Сохранить",
"save_settings": "Сохранить настройки",
"save_z_transactions": "Сохранять Z-tx в списке транзакций",
"sb_auth_failed": "Ошибка авторизации — проверьте rpcuser/rpcpassword",
"sb_block": "Блок: %d",
"sb_connecting_daemon": "Подключение к dragonxd...",
"sb_connecting_err": "Подключение к демону — %s",
"sb_connecting_external": "Подключение к внешнему демону...",
"sb_connecting_generic": "Подключение к демону...",
"sb_daemon_crashed": "Демон упал %d раз",
"sb_daemon_not_found": "Демон не найден",
"sb_dragonxd_running": "dragonxd запущен",
"sb_dragonxd_stopped": "dragonxd остановлен",
"sb_dragonxd_stopping": "Остановка dragonxd...",
"sb_extracting_sapling": "Извлечение параметров Sapling...",
"sb_importing_keys": "Импорт ключей",
"sb_loading_config": "Загрузка конфигурации...",
"sb_mining_hs": "%.1f H/s",
"sb_net_ghs": "Сеть: %.2f GH/s",
"sb_net_hs": "Сеть: %.1f H/s",
"sb_net_khs": "Сеть: %.2f KH/s",
"sb_net_mhs": "Сеть: %.2f MH/s",
"sb_no_conf": "DRAGONX.conf не найден",
"sb_peers": "Пиры: %zu",
"sb_rescanning": "Пересканирование",
"sb_rescanning_pct": "Пересканирование %.0f%%",
"sb_restarting_daemon": "Перезапуск демона...",
"sb_sapling_failed": "Ошибка извлечения параметров Sapling.",
"sb_sapling_not_found": "Параметры Sapling не найдены.",
"sb_starting_daemon": "Запуск dragonxd...",
"sb_syncing_basic": "Синхронизация %.1f%% (%d осталось)",
"sb_syncing_eta": "Синхронизация %.1f%% (%d осталось, %.0f блк/с, ~%s)",
"sb_waiting_config": "Ожидание конфигурации демона...",
"sb_waiting_daemon": "Ожидание dragonxd...",
"sb_waiting_daemon_err": "Ожидание dragonxd — %s",
"sb_warming_up": "Прогрев...",
"search_placeholder": "Поиск...",
"security": "БЕЗОПАСНОСТЬ",
"select_address": "Выбрать адрес...",
@@ -667,15 +553,12 @@
"send_valid_transparent": "Действительный прозрачный адрес",
"send_wallet_empty": "Ваш кошелёк пуст",
"send_yes_clear": "Да, очистить",
"sender_balance": "Отправитель: %.8f → %.8f DRGX",
"sending": "Отправка транзакции",
"sending_from": "ОТПРАВКА С",
"sends_full_balance_warning": "Это отправит весь баланс. Адрес отправителя останется с нулевым балансом.",
"sent": "отправлено",
"sent_filter": "Отправлено",
"sent_type": "Отправлено",
"sent_upper": "ОТПРАВЛЕНО",
"set_label": "Установить метку...",
"settings": "Настройки",
"settings_about_text": "Защищённый криптовалютный кошелёк для DragonX (DRGX), созданный на Dear ImGui для лёгкого и портативного использования.",
"settings_acrylic_level": "Уровень акрила:",
@@ -764,17 +647,13 @@
"shield_utxo_limit": "Лимит UTXO:",
"shield_wildcard_hint": "Используйте '*' для экранирования со всех прозрачных адресов",
"shielded": "Экранированный",
"shielded_address": "Экранированный адрес",
"shielded_to": "ЭКРАНИРОВАНО НА",
"shielded_type": "Экранированный",
"shielding_notice": "Примечание: это переведёт средства с прозрачного (T) адреса на приватный (Z) адрес.",
"show": "Показать",
"show_hidden": "Показать скрытые (%d)",
"show_qr_code": "Показать QR-код",
"showing_transactions": "Показано %d–%d из %d транзакций (всего: %zu)",
"showing_x_of_y": "Показано %d из %d адресов",
"showing_transactions": "Показано %d–%d из %d транзакций (всего: %zu)",
"simple_background": "Простой фон",
"slider_off": "Выкл",
"start_mining": "Начать майнинг",
"status": "Статус",
"stop_external": "Остановить внешний daemon",
@@ -783,26 +662,17 @@
"success": "Успешно",
"summary": "Итоги",
"syncing": "Синхронизация...",
"t_address": "T-адрес",
"t_addresses": "T-адреса",
"test_connection": "Тест",
"theme": "Тема",
"theme_effects": "Эффекты темы",
"theme_language": "ТЕМА И ЯЗЫК",
"time_days_ago": "%d дней назад",
"time_hours_ago": "%d часов назад",
"time_minutes_ago": "%d минут назад",
"time_seconds_ago": "%d секунд назад",
"timeout_15min": "15 мин",
"timeout_1hour": "1 час",
"timeout_1min": "1 мин",
"timeout_30min": "30 мин",
"timeout_5min": "5 мин",
"timeout_off": "Выкл",
"to": "Кому",
"to_upper": "КОМУ",
"tools": "УТИЛИТЫ",
"tools_actions": "Инструменты и действия...",
"tools": "ИНСТРУМЕНТЫ",
"total": "Итого",
"transaction_id": "ID ТРАНЗАКЦИИ",
"transaction_sent": "Транзакция успешно отправлена",
@@ -810,13 +680,7 @@
"transaction_url": "URL транзакции",
"transactions": "Транзакции",
"transactions_upper": "ТРАНЗАКЦИИ",
"transfer_failed": "Ошибка перевода",
"transfer_funds": "Перевести средства",
"transfer_sent": "Перевод отправлен",
"transfer_sent_desc": "Ваш перевод отправлен в сеть.",
"transfer_to": "Перевести на:",
"transparent": "Прозрачный",
"transparent_address": "Прозрачный адрес",
"tt_addr_url": "Базовый URL для просмотра адресов в обозревателе блоков",
"tt_address_book": "Управление сохранёнными адресами для быстрой отправки",
"tt_auto_lock": "Заблокировать кошелёк после этого времени бездействия",
@@ -831,8 +695,6 @@
"tt_custom_theme": "Пользовательская тема активна",
"tt_debug_collapse": "Свернуть параметры журнала отладки",
"tt_debug_expand": "Развернуть параметры журнала отладки",
"tt_delete_blockchain": "Удалить все данные блокчейна и начать синхронизацию заново. wallet.dat и конфигурация сохраняются.",
"tt_download_bootstrap": "Скачать бутстрап блокчейна для ускорения синхронизации\nСуществующие данные блоков будут заменены",
"tt_encrypt": "Зашифровать wallet.dat паролем",
"tt_export_all": "Экспортировать все приватные ключи в файл",
"tt_export_csv": "Экспортировать историю транзакций в виде таблицы CSV",
@@ -850,7 +712,6 @@
"tt_mine_idle": "Автоматически начать майнинг при\\nпростое системы (нет ввода с клавиатуры/мыши)",
"tt_noise": "Интенсивность зернистой текстуры (0%% = выкл., 100%% = максимум)",
"tt_open_dir": "Нажмите, чтобы открыть в проводнике",
"tt_reduce_motion": "Отключить анимированные переходы и плавное изменение баланса для доступности",
"tt_remove_encrypt": "Удалить шифрование и хранить кошелёк без защиты",
"tt_remove_pin": "Удалить PIN и требовать пароль для разблокировки",
"tt_report_bug": "Сообщить о проблеме в трекере проекта",
@@ -928,9 +789,7 @@
"warning_upper": "ПРЕДУПРЕЖДЕНИЕ!",
"website": "Веб-сайт",
"window_opacity": "Прозрачность окна",
"wizard_daemon_start_failed": "Не удалось запустить демон — будет повторено автоматически",
"yes_clear": "Да, очистить",
"your_addresses": "Ваши адреса",
"z_address": "Z-адрес",
"z_addresses": "Z-адреса"
}
+1 -142
View File
@@ -43,8 +43,6 @@
"address_url": "地址 URL",
"addresses_appear_here": "连接后,您的接收地址将显示在此处。",
"advanced": "高级",
"advanced_effects": "高级特效...",
"ago": "前",
"all_filter": "全部",
"allow_custom_fees": "允许自定义手续费",
"amount": "金额",
@@ -92,30 +90,12 @@
"block_timestamp": "时间戳:",
"block_transactions": "交易:",
"blockchain_syncing": "区块链同步中 (%.1f%%)... 余额可能不准确。",
"bootstrap_daemon_running": "守护进程运行中",
"bootstrap_daemon_stopped": "守护进程已停止",
"bootstrap_daemon_stopping": "正在停止守护进程...",
"bootstrap_desc": "下载区块链引导程序以大幅加速初始同步。这将下载区块链快照并将其提取到您的数据目录中。",
"bootstrap_downloading": "正在下载引导程序...",
"bootstrap_extracting": "正在提取区块链数据...",
"bootstrap_failed": "引导程序失败",
"bootstrap_mirror": "镜像",
"bootstrap_mirror_tooltip": "从镜像下载 (bootstrap2.dragonx.is)。\n如果主下载速度慢或失败,请使用此选项。",
"bootstrap_restart_daemon": "重启守护进程",
"bootstrap_success": "引导程序完成",
"bootstrap_success_desc": "区块链数据已成功提取。启动守护进程以从引导点开始同步。",
"bootstrap_trust_warning": "仅使用 bootstrap.dragonx.is 或 bootstrap2.dragonx.is。使用不受信任来源的文件可能会危及您的节点。",
"bootstrap_verifying": "正在验证校验和...",
"bootstrap_wallet_protected": "(wallet.dat 已受保护)",
"bootstrap_warning": "现有区块数据(blocks、chainstate、notarizations)将被删除并替换。您的 wallet.dat 不会被修改或删除。",
"cancel": "取消",
"characters": "字符",
"choose_icon": "选择图标",
"clear": "清除",
"clear_all_bans": "解除所有封禁",
"clear_anyway": "仍然清除",
"clear_form_confirm": "清除所有表单字段?",
"clear_icon": "清除图标",
"clear_request": "清除请求",
"click_copy_address": "点击复制地址",
"click_copy_uri": "点击复制 URI",
@@ -126,12 +106,8 @@
"confirm_clear_ztx_title": "确认清除 Z-Tx 历史",
"confirm_clear_ztx_warning1": "清除 z-交易历史可能导致您的屏蔽余额显示为 0,直到执行钱包重新扫描。",
"confirm_clear_ztx_warning2": "如果发生这种情况,您需要在启用重新扫描的情况下重新导入 z-地址私钥以恢复余额。",
"confirm_delete_blockchain_msg": "这将停止守护进程,删除所有区块链数据(blocks、chainstate、peers),并从头开始重新同步。这可能需要几个小时。",
"confirm_delete_blockchain_safe": "您的 wallet.dat、配置和交易历史是安全的,不会被删除。",
"confirm_delete_blockchain_title": "删除区块链数据",
"confirm_send": "确认发送",
"confirm_transaction": "确认交易",
"confirm_transfer": "确认转账",
"confirmations": "确认数",
"confirmations_display": "%d 次确认 | %s",
"confirmed": "已确认",
@@ -196,7 +172,6 @@
"console_welcome": "欢迎使用 ObsidianDragon 控制台",
"console_zoom_in": "放大",
"console_zoom_out": "缩小",
"copied": "已复制!",
"copy": "复制",
"copy_address": "复制完整地址",
"copy_error": "复制错误",
@@ -205,21 +180,15 @@
"copy_uri": "复制 URI",
"current_price": "当前价格",
"custom_fees": "自定义手续费",
"daemon_version": "守护进程",
"dark": "深色",
"date": "日期",
"date_label": "日期:",
"debug_logging": "调试日志",
"delete": "删除",
"delete_blockchain": "删除区块链",
"delete_blockchain_confirm": "删除并重新同步",
"deshielding_warning": "警告:这将把资金从隐私 (Z) 地址转移到透明 (T) 地址。",
"difficulty": "难度",
"disconnected": "已断开",
"dismiss": "关闭",
"display": "显示",
"download": "下载",
"download_bootstrap": "下载引导程序",
"dragonx_green": "DragonX(绿色)",
"edit": "编辑",
"error": "错误",
@@ -227,23 +196,6 @@
"est_time_to_block": "预计出块时间",
"exit": "退出",
"explorer": "浏览器",
"explorer_block_detail": "区块",
"explorer_block_hash": "哈希",
"explorer_block_height": "高度",
"explorer_block_merkle": "Merkle 根",
"explorer_block_size": "大小",
"explorer_block_time": "时间",
"explorer_block_txs": "交易",
"explorer_chain_stats": "链",
"explorer_invalid_query": "输入区块高度或64位哈希",
"explorer_mempool": "内存池",
"explorer_mempool_size": "大小",
"explorer_mempool_txs": "交易",
"explorer_recent_blocks": "最近区块",
"explorer_search": "搜索",
"explorer_section": "浏览器",
"explorer_tx_outputs": "输出",
"explorer_tx_size": "大小",
"export": "导出",
"export_csv": "导出 CSV",
"export_keys_btn": "导出密钥",
@@ -272,22 +224,14 @@
"fetch_prices": "获取价格",
"file": "文件",
"file_save_location": "文件将保存至:~/.config/ObsidianDragon/",
"filter": "筛选...",
"font_scale": "字体大小",
"force_quit": "强制退出",
"force_quit_confirm_msg": "这将立即终止守护进程而不进行正常关闭。\n这可能会损坏区块链索引并需要重新同步。",
"force_quit_confirm_title": "强制退出?",
"force_quit_warning": "这将立即终止守护进程而不进行正常关闭。可能需要重新同步区块链。",
"force_quit_yes": "强制退出",
"from": "从",
"from_upper": "从",
"full_details": "完整详情",
"general": "常规",
"generating": "正在生成",
"go_to_receive": "前往接收",
"height": "高度",
"help": "帮助",
"hidden_tag": " (已隐藏)",
"hide": "隐藏",
"hide_address": "隐藏地址",
"hide_zero_balances": "隐藏零余额",
@@ -309,9 +253,6 @@
"import_key_warning": "警告:切勿分享您的私钥!从不可信来源导入密钥可能会危及您的钱包安全。",
"import_key_z_format": "Z 地址花费密钥 (secret-extended-key-...)",
"import_private_key": "导入私钥...",
"incorrect_passphrase": "密码错误",
"incorrect_pin": "PIN 错误",
"insufficient_funds": "余额不足以支付此金额加手续费。",
"invalid_address": "无效的地址格式",
"ip_address": "IP 地址",
"keep": "保留",
@@ -325,7 +266,6 @@
"key_export_viewing_keys_zonly": "查看密钥仅适用于屏蔽 (z) 地址",
"key_export_viewing_warning": "此查看密钥允许他人查看您的入账交易和余额,但不能花费您的资金。仅与信任的人分享。",
"label": "标签:",
"label_placeholder": "例如 储蓄、挖矿...",
"language": "语言",
"light": "浅色",
"loading": "加载中...",
@@ -371,13 +311,6 @@
"mining_address_copied": "挖矿地址已复制",
"mining_all_time": "所有时间",
"mining_already_saved": "矿池 URL 已保存",
"mining_benchmark_cancel": "取消基准测试",
"mining_benchmark_cooling": "冷却中",
"mining_benchmark_dismiss": "关闭",
"mining_benchmark_result": "最佳",
"mining_benchmark_stabilizing": "稳定中",
"mining_benchmark_testing": "测试中",
"mining_benchmark_tooltip": "为此 CPU 找到最佳线程数",
"mining_block_copied": "区块哈希已复制",
"mining_chart_1m_ago": "1分钟前",
"mining_chart_5m_ago": "5分钟前",
@@ -397,15 +330,8 @@
"mining_filter_tip_all": "显示所有收益",
"mining_filter_tip_pool": "仅显示矿池收益",
"mining_filter_tip_solo": "仅显示单人收益",
"mining_generate_z_address_hint": "在接收标签页生成一个 Z 地址作为支付地址",
"mining_idle_gpu_off_tooltip": "无限制:开启\n仅键盘/鼠标输入决定空闲状态\n点击启用GPU感知检测",
"mining_idle_gpu_on_tooltip": "GPU感知:开启\nGPU活动(视频、游戏)阻止空闲挖矿\n点击切换到无限制模式",
"mining_idle_off_tooltip": "启用空闲挖矿",
"mining_idle_on_tooltip": "禁用空闲挖矿",
"mining_idle_scale_off_tooltip": "启动/停止模式:开启\n点击切换到线程缩放模式",
"mining_idle_scale_on_tooltip": "线程缩放:开启\n点击切换到启动/停止模式",
"mining_idle_threads_active_tooltip": "用户活跃时的线程数",
"mining_idle_threads_idle_tooltip": "系统空闲时的线程数",
"mining_local_hashrate": "本地算力",
"mining_mine": "挖矿",
"mining_mining_addr": "挖矿地址",
@@ -462,7 +388,6 @@
"no_addresses_available": "无可用地址",
"no_addresses_match": "没有匹配过滤器的地址",
"no_addresses_with_balance": "没有有余额的地址",
"no_addresses_yet": "暂无地址",
"no_matching": "没有匹配的交易",
"no_recent_receives": "没有最近的接收",
"no_recent_sends": "没有最近的发送",
@@ -528,7 +453,6 @@
"peers_upper": "节点",
"peers_version": "版本",
"pending": "待处理",
"pin_not_set": "未设置 PIN。使用密码解锁。",
"ping": "延迟",
"price_chart": "价格图表",
"qr_code": "二维码",
@@ -549,9 +473,7 @@
"recent_received": "最近接收",
"recent_sends": "最近发送",
"recipient": "收款方",
"recipient_balance": "接收方: %.8f → %.8f DRGX",
"recv_type": "接收",
"reduce_motion": "减少动画",
"refresh": "刷新",
"refresh_now": "立即刷新",
"remove_favorite": "移除收藏",
@@ -571,10 +493,7 @@
"request_uri_copied": "付款 URI 已复制到剪贴板",
"rescan": "重新扫描",
"reset_to_defaults": "重置为默认值",
"restarting_after_encryption": "加密后重启守护进程...",
"restore_address": "恢复地址",
"result_preview": "结果预览",
"retry": "重试",
"review_send": "审核发送",
"rpc_host": "RPC 主机",
"rpc_pass": "密码",
@@ -583,39 +502,6 @@
"save": "保存",
"save_settings": "保存设置",
"save_z_transactions": "将 Z 交易保存到列表",
"sb_auth_failed": "认证失败 — 请检查 rpcuser/rpcpassword",
"sb_block": "区块: %d",
"sb_connecting_daemon": "正在连接 dragonxd...",
"sb_connecting_err": "连接守护进程 — %s",
"sb_connecting_external": "正在连接外部守护进程...",
"sb_connecting_generic": "正在连接守护进程...",
"sb_daemon_crashed": "守护进程崩溃 %d 次",
"sb_daemon_not_found": "未找到守护进程",
"sb_dragonxd_running": "dragonxd 运行中",
"sb_dragonxd_stopped": "dragonxd 已停止",
"sb_dragonxd_stopping": "正在停止 dragonxd...",
"sb_extracting_sapling": "正在提取 Sapling 参数...",
"sb_importing_keys": "正在导入密钥",
"sb_loading_config": "正在加载配置...",
"sb_mining_hs": "%.1f H/s",
"sb_net_ghs": "网络: %.2f GH/s",
"sb_net_hs": "网络: %.1f H/s",
"sb_net_khs": "网络: %.2f KH/s",
"sb_net_mhs": "网络: %.2f MH/s",
"sb_no_conf": "未找到 DRAGONX.conf",
"sb_peers": "节点: %zu",
"sb_rescanning": "重新扫描",
"sb_rescanning_pct": "重新扫描 %.0f%%",
"sb_restarting_daemon": "正在重启守护进程...",
"sb_sapling_failed": "提取 Sapling 参数失败。",
"sb_sapling_not_found": "未找到 Sapling 参数。",
"sb_starting_daemon": "正在启动 dragonxd...",
"sb_syncing_basic": "同步中 %.1f%% (剩余 %d)",
"sb_syncing_eta": "同步中 %.1f%% (剩余 %d, %.0f 块/秒, ~%s)",
"sb_waiting_config": "等待守护进程配置...",
"sb_waiting_daemon": "等待 dragonxd 启动...",
"sb_waiting_daemon_err": "等待 dragonxd — %s",
"sb_warming_up": "正在预热...",
"search_placeholder": "搜索...",
"security": "安全",
"select_address": "选择地址...",
@@ -667,15 +553,12 @@
"send_valid_transparent": "有效的透明地址",
"send_wallet_empty": "您的钱包是空的",
"send_yes_clear": "是,清除",
"sender_balance": "发送方: %.8f → %.8f DRGX",
"sending": "正在发送交易",
"sending_from": "发送来源",
"sends_full_balance_warning": "这将发送全部余额。发送地址将变为零余额。",
"sent": "已发送",
"sent_filter": "已发送",
"sent_type": "已发送",
"sent_upper": "已发送",
"set_label": "设置标签...",
"settings": "设置",
"settings_about_text": "DragonX (DRGX) 屏蔽加密货币钱包,使用 Dear ImGui 构建,提供轻量、便携的体验。",
"settings_acrylic_level": "亚克力级别:",
@@ -764,17 +647,13 @@
"shield_utxo_limit": "UTXO 限制:",
"shield_wildcard_hint": "使用 '*' 从所有透明地址屏蔽",
"shielded": "屏蔽",
"shielded_address": "隐蔽地址",
"shielded_to": "屏蔽至",
"shielded_type": "屏蔽",
"shielding_notice": "注意:这将把资金从透明 (T) 地址转移到隐私 (Z) 地址。",
"show": "显示",
"show_hidden": "显示已隐藏 (%d)",
"show_qr_code": "显示二维码",
"showing_transactions": "显示第 %d–%d 笔,共 %d 笔交易(总计:%zu)",
"showing_x_of_y": "显示 %d / %d 个地址",
"showing_transactions": "显示第 %d–%d 笔,共 %d 笔交易(总计:%zu)",
"simple_background": "简单背景",
"slider_off": "关闭",
"start_mining": "开始挖矿",
"status": "状态",
"stop_external": "停止外部守护进程",
@@ -783,26 +662,17 @@
"success": "成功",
"summary": "摘要",
"syncing": "同步中...",
"t_address": "T 地址",
"t_addresses": "T 地址",
"test_connection": "测试",
"theme": "主题",
"theme_effects": "主题效果",
"theme_language": "主题与语言",
"time_days_ago": "%d 天前",
"time_hours_ago": "%d 小时前",
"time_minutes_ago": "%d 分钟前",
"time_seconds_ago": "%d 秒前",
"timeout_15min": "15分钟",
"timeout_1hour": "1小时",
"timeout_1min": "1分钟",
"timeout_30min": "30分钟",
"timeout_5min": "5分钟",
"timeout_off": "关闭",
"to": "至",
"to_upper": "至",
"tools": "工具",
"tools_actions": "工具与操作...",
"total": "合计",
"transaction_id": "交易 ID",
"transaction_sent": "交易发送成功",
@@ -810,13 +680,7 @@
"transaction_url": "交易 URL",
"transactions": "交易",
"transactions_upper": "交易",
"transfer_failed": "转账失败",
"transfer_funds": "转账",
"transfer_sent": "转账已发送",
"transfer_sent_desc": "您的转账已提交到网络。",
"transfer_to": "转账至:",
"transparent": "透明",
"transparent_address": "透明地址",
"tt_addr_url": "在区块浏览器中查看地址的基础 URL",
"tt_address_book": "管理已保存的地址以快速发送",
"tt_auto_lock": "在此不活动时间后锁定钱包",
@@ -831,8 +695,6 @@
"tt_custom_theme": "自定义主题已激活",
"tt_debug_collapse": "折叠调试日志选项",
"tt_debug_expand": "展开调试日志选项",
"tt_delete_blockchain": "删除所有区块链数据并重新同步。wallet.dat 和配置将被保留。",
"tt_download_bootstrap": "下载区块链引导程序以加速同步\n现有区块数据将被替换",
"tt_encrypt": "使用密码加密 wallet.dat",
"tt_export_all": "将所有私钥导出到文件",
"tt_export_csv": "将交易历史导出为 CSV 电子表格",
@@ -850,7 +712,6 @@
"tt_mine_idle": "系统空闲时自动开始挖矿\\n(无键盘/鼠标输入)",
"tt_noise": "颗粒纹理强度(0%% = 关闭,100%% = 最大)",
"tt_open_dir": "点击在文件管理器中打开",
"tt_reduce_motion": "禁用动画过渡和余额渐变以提高无障碍性",
"tt_remove_encrypt": "移除加密并以未受保护状态存储钱包",
"tt_remove_pin": "移除 PIN 并要求密码解锁",
"tt_report_bug": "在项目跟踪器中报告问题",
@@ -928,9 +789,7 @@
"warning_upper": "警告!",
"website": "网站",
"window_opacity": "窗口透明度",
"wizard_daemon_start_failed": "启动守护进程失败 — 将自动重试",
"yes_clear": "是,清除",
"your_addresses": "您的地址",
"z_address": "Z 地址",
"z_addresses": "Z 地址"
}
+3 -4
View File
@@ -1230,9 +1230,8 @@ width = { size = 140.0 }
collapsed-width = { size = 64.0 }
collapse-anim-speed = { size = 10.0 }
auto-collapse-threshold = { size = 800.0 }
section-gap = { size = 8.0 }
section-gap = { size = 4.0 }
section-label-pad-left = { size = 16.0 }
section-label-pad-bottom = { size = 4.0 }
item-height = { size = 36.0 }
item-pad-x = { size = 8.0 }
min-height = { size = 360.0 }
@@ -1249,8 +1248,8 @@ icon-half-size = { size = 7.0 }
icon-label-gap = { size = 8.0 }
badge-radius-dot = { size = 4.0 }
badge-radius-number = { size = 8.0 }
button-spacing = { size = 6.0 }
bottom-padding = { size = 4.0 }
button-spacing = { size = 4.0 }
bottom-padding = { size = 0.0 }
exit-icon-gap = { size = 4.0 }
cutout-shadow-alpha = { size = 55 }
cutout-highlight-alpha = { size = 8 }
-825
View File
@@ -1275,831 +1275,6 @@ TRANSLATIONS = {
"pt": "Clique para copiar", "ru": "Нажмите для копирования", "zh": "点击复制",
"ja": "クリックしてコピー", "ko": "복사하려면 클릭"
},
# ── NEW BATCH: 100 missing keys ──────────────────────────────────────
"advanced_effects": {
"es": "Efectos Avanzados...", "de": "Erweiterte Effekte...", "fr": "Effets avancés...",
"pt": "Efeitos Avançados...", "ru": "Расширенные эффекты...", "zh": "高级特效...",
"ja": "高度なエフェクト...", "ko": "고급 효과..."
},
"ago": {
"es": "atrás", "de": "her", "fr": "passé",
"pt": "atrás", "ru": "назад", "zh": "前",
"ja": "前", "ko": "전"
},
"bootstrap_daemon_running": {
"es": "Daemon ejecutándose", "de": "Daemon läuft", "fr": "Daemon en cours",
"pt": "Daemon em execução", "ru": "Демон запущен", "zh": "守护进程运行中",
"ja": "デーモン実行中", "ko": "데몬 실행 중"
},
"bootstrap_daemon_stopped": {
"es": "Daemon detenido", "de": "Daemon gestoppt", "fr": "Daemon arrêté",
"pt": "Daemon parado", "ru": "Демон остановлен", "zh": "守护进程已停止",
"ja": "デーモン停止", "ko": "데몬 중지됨"
},
"bootstrap_daemon_stopping": {
"es": "Deteniendo daemon...", "de": "Daemon wird gestoppt...", "fr": "Arrêt du daemon...",
"pt": "Parando daemon...", "ru": "Остановка демона...", "zh": "正在停止守护进程...",
"ja": "デーモン停止中...", "ko": "데몬 중지 중..."
},
"bootstrap_desc": {
"es": "Descarga un bootstrap de la blockchain para acelerar drásticamente la sincronización inicial. Esto descarga una instantánea de la blockchain y la extrae en tu directorio de datos.",
"de": "Laden Sie einen Blockchain-Bootstrap herunter, um die anfängliche Synchronisierung drastisch zu beschleunigen. Dies lädt einen Snapshot der Blockchain herunter und extrahiert ihn in Ihr Datenverzeichnis.",
"fr": "Téléchargez un bootstrap de la blockchain pour accélérer considérablement la synchronisation initiale. Cela télécharge un instantané de la blockchain et l'extrait dans votre répertoire de données.",
"pt": "Baixe um bootstrap da blockchain para acelerar drasticamente a sincronização inicial. Isso baixa um instantâneo da blockchain e o extrai no seu diretório de dados.",
"ru": "Загрузите бутстрап блокчейна для значительного ускорения начальной синхронизации. Это загружает снимок блокчейна и извлекает его в ваш каталог данных.",
"zh": "下载区块链引导程序以大幅加速初始同步。这将下载区块链快照并将其提取到您的数据目录中。",
"ja": "ブロックチェーンブートストラップをダウンロードして初期同期を劇的に高速化します。ブロックチェーンのスナップショットをダウンロードしてデータディレクトリに展開します。",
"ko": "블록체인 부트스트랩을 다운로드하여 초기 동기화를 대폭 가속합니다. 블록체인 스냅샷을 다운로드하고 데이터 디렉토리에 추출합니다."
},
"bootstrap_downloading": {
"es": "Descargando bootstrap...", "de": "Bootstrap wird heruntergeladen...", "fr": "Téléchargement du bootstrap...",
"pt": "Baixando bootstrap...", "ru": "Загрузка бутстрапа...", "zh": "正在下载引导程序...",
"ja": "ブートストラップをダウンロード中...", "ko": "부트스트랩 다운로드 중..."
},
"bootstrap_extracting": {
"es": "Extrayendo datos de blockchain...", "de": "Blockchain-Daten werden extrahiert...", "fr": "Extraction des données blockchain...",
"pt": "Extraindo dados da blockchain...", "ru": "Извлечение данных блокчейна...", "zh": "正在提取区块链数据...",
"ja": "ブロックチェーンデータを展開中...", "ko": "블록체인 데이터 추출 중..."
},
"bootstrap_failed": {
"es": "Error en Bootstrap", "de": "Bootstrap fehlgeschlagen", "fr": "Échec du Bootstrap",
"pt": "Falha no Bootstrap", "ru": "Ошибка бутстрапа", "zh": "引导程序失败",
"ja": "ブートストラップ失敗", "ko": "부트스트랩 실패"
},
"bootstrap_mirror": {
"es": "Espejo", "de": "Spiegel", "fr": "Miroir",
"pt": "Espelho", "ru": "Зеркало", "zh": "镜像",
"ja": "ミラー", "ko": "미러"
},
"bootstrap_mirror_tooltip": {
"es": "Descargar desde espejo (bootstrap2.dragonx.is).\nUsa esto si la descarga principal es lenta o falla.",
"de": "Vom Spiegel herunterladen (bootstrap2.dragonx.is).\nVerwenden Sie dies, wenn der Hauptdownload langsam ist oder fehlschlägt.",
"fr": "Télécharger depuis le miroir (bootstrap2.dragonx.is).\nUtilisez ceci si le téléchargement principal est lent ou échoue.",
"pt": "Baixar do espelho (bootstrap2.dragonx.is).\nUse isto se o download principal estiver lento ou falhando.",
"ru": "Скачать с зеркала (bootstrap2.dragonx.is).\nИспользуйте, если основная загрузка медленная или не работает.",
"zh": "从镜像下载 (bootstrap2.dragonx.is)。\n如果主下载速度慢或失败,请使用此选项。",
"ja": "ミラーからダウンロード (bootstrap2.dragonx.is)。\nメインのダウンロードが遅い場合や失敗する場合に使用してください。",
"ko": "미러에서 다운로드 (bootstrap2.dragonx.is).\n메인 다운로드가 느리거나 실패할 경우 사용하세요."
},
"bootstrap_restart_daemon": {
"es": "Reiniciar Daemon", "de": "Daemon neu starten", "fr": "Redémarrer le Daemon",
"pt": "Reiniciar Daemon", "ru": "Перезапустить демон", "zh": "重启守护进程",
"ja": "デーモンを再起動", "ko": "데몬 재시작"
},
"bootstrap_success": {
"es": "Bootstrap Completado", "de": "Bootstrap abgeschlossen", "fr": "Bootstrap terminé",
"pt": "Bootstrap Completo", "ru": "Бутстрап завершён", "zh": "引导程序完成",
"ja": "ブートストラップ完了", "ko": "부트스트랩 완료"
},
"bootstrap_success_desc": {
"es": "Los datos de la blockchain se han extraído correctamente. Inicie el daemon para comenzar a sincronizar desde el punto del bootstrap.",
"de": "Blockchain-Daten wurden erfolgreich extrahiert. Starten Sie den Daemon, um ab dem Bootstrap-Punkt zu synchronisieren.",
"fr": "Les données de la blockchain ont été extraites avec succès. Démarrez le daemon pour commencer la synchronisation à partir du point de bootstrap.",
"pt": "Os dados da blockchain foram extraídos com sucesso. Inicie o daemon para começar a sincronizar a partir do ponto do bootstrap.",
"ru": "Данные блокчейна успешно извлечены. Запустите демон для начала синхронизации с точки бутстрапа.",
"zh": "区块链数据已成功提取。启动守护进程以从引导点开始同步。",
"ja": "ブロックチェーンデータが正常に展開されました。デーモンを起動してブートストラップポイントから同期を開始してください。",
"ko": "블록체인 데이터가 성공적으로 추출되었습니다. 데몬을 시작하여 부트스트랩 지점부터 동기화를 시작하세요."
},
"bootstrap_trust_warning": {
"es": "Solo use bootstrap.dragonx.is o bootstrap2.dragonx.is. Usar archivos de fuentes no confiables podría comprometer su nodo.",
"de": "Verwenden Sie nur bootstrap.dragonx.is oder bootstrap2.dragonx.is. Die Verwendung von Dateien aus nicht vertrauenswürdigen Quellen könnte Ihren Knoten gefährden.",
"fr": "N'utilisez que bootstrap.dragonx.is ou bootstrap2.dragonx.is. L'utilisation de fichiers provenant de sources non fiables pourrait compromettre votre nœud.",
"pt": "Use apenas bootstrap.dragonx.is ou bootstrap2.dragonx.is. Usar arquivos de fontes não confiáveis pode comprometer seu nó.",
"ru": "Используйте только bootstrap.dragonx.is или bootstrap2.dragonx.is. Использование файлов из ненадёжных источников может скомпрометировать ваш узел.",
"zh": "仅使用 bootstrap.dragonx.is 或 bootstrap2.dragonx.is。使用不受信任来源的文件可能会危及您的节点。",
"ja": "bootstrap.dragonx.is または bootstrap2.dragonx.is のみを使用してください。信頼できないソースのファイルを使用するとノードが危険にさらされる可能性があります。",
"ko": "bootstrap.dragonx.is 또는 bootstrap2.dragonx.is만 사용하세요. 신뢰할 수 없는 출처의 파일을 사용하면 노드가 손상될 수 있습니다."
},
"bootstrap_verifying": {
"es": "Verificando sumas de comprobación...", "de": "Prüfsummen werden überprüft...", "fr": "Vérification des sommes de contrôle...",
"pt": "Verificando somas de verificação...", "ru": "Проверка контрольных сумм...", "zh": "正在验证校验和...",
"ja": "チェックサムを検証中...", "ko": "체크섬 확인 중..."
},
"bootstrap_wallet_protected": {
"es": "(wallet.dat está protegido)", "de": "(wallet.dat ist geschützt)", "fr": "(wallet.dat est protégé)",
"pt": "(wallet.dat está protegido)", "ru": "(wallet.dat защищён)", "zh": "(wallet.dat 已受保护)",
"ja": "(wallet.dat は保護されています)", "ko": "(wallet.dat 보호됨)"
},
"bootstrap_warning": {
"es": "Los datos de bloques existentes (blocks, chainstate, notarizations) se eliminarán y reemplazarán. Su wallet.dat NO será modificado ni eliminado.",
"de": "Vorhandene Blockdaten (blocks, chainstate, notarizations) werden gelöscht und ersetzt. Ihre wallet.dat wird NICHT verändert oder gelöscht.",
"fr": "Les données de blocs existantes (blocks, chainstate, notarizations) seront supprimées et remplacées. Votre wallet.dat ne sera PAS modifié ni supprimé.",
"pt": "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.",
"ru": "Существующие данные блоков (blocks, chainstate, notarizations) будут удалены и заменены. Ваш wallet.dat НЕ будет изменён или удалён.",
"zh": "现有区块数据(blocks、chainstate、notarizations)将被删除并替换。您的 wallet.dat 不会被修改或删除。",
"ja": "既存のブロックデータ(blocks、chainstate、notarizations)は削除され置き換えられます。wallet.dat は変更・削除されません。",
"ko": "기존 블록 데이터(blocks, chainstate, notarizations)가 삭제되고 교체됩니다. wallet.dat는 수정되거나 삭제되지 않습니다."
},
"choose_icon": {
"es": "Elegir Icono", "de": "Symbol wählen", "fr": "Choisir une icône",
"pt": "Escolher Ícone", "ru": "Выбрать иконку", "zh": "选择图标",
"ja": "アイコンを選択", "ko": "아이콘 선택"
},
"clear_icon": {
"es": "Borrar Icono", "de": "Symbol entfernen", "fr": "Effacer l'icône",
"pt": "Limpar Ícone", "ru": "Удалить иконку", "zh": "清除图标",
"ja": "アイコンをクリア", "ko": "아이콘 지우기"
},
"confirm_delete_blockchain_msg": {
"es": "Esto detendrá el daemon, eliminará todos los datos de la blockchain (blocks, chainstate, peers) y comenzará una nueva sincronización desde cero. Esto puede tardar varias horas.",
"de": "Dies stoppt den Daemon, löscht alle Blockchain-Daten (blocks, chainstate, peers) und startet eine neue Synchronisierung. Dies kann mehrere Stunden dauern.",
"fr": "Cela arrêtera le daemon, supprimera toutes les données de la blockchain (blocks, chainstate, peers) et démarrera une nouvelle synchronisation. Cela peut prendre plusieurs heures.",
"pt": "Isso irá parar o daemon, excluir todos os dados da blockchain (blocks, chainstate, peers) e iniciar uma nova sincronização do zero. Isso pode levar várias horas.",
"ru": "Это остановит демон, удалит все данные блокчейна (blocks, chainstate, peers) и начнёт синхронизацию с нуля. Это может занять несколько часов.",
"zh": "这将停止守护进程,删除所有区块链数据(blocks、chainstate、peers),并从头开始重新同步。这可能需要几个小时。",
"ja": "デーモンを停止し、すべてのブロックチェーンデータ(blocks、chainstate、peers)を削除して、最初から再同期を開始します。数時間かかる場合があります。",
"ko": "데몬을 중지하고 모든 블록체인 데이터(blocks, chainstate, peers)를 삭제한 후 처음부터 다시 동기화합니다. 몇 시간이 걸릴 수 있습니다."
},
"confirm_delete_blockchain_safe": {
"es": "Su wallet.dat, configuración e historial de transacciones están seguros y no se eliminarán.",
"de": "Ihre wallet.dat, Konfiguration und Transaktionshistorie sind sicher und werden nicht gelöscht.",
"fr": "Votre wallet.dat, votre configuration et votre historique de transactions sont en sécurité et ne seront pas supprimés.",
"pt": "Seu wallet.dat, configuração e histórico de transações estão seguros e não serão excluídos.",
"ru": "Ваш wallet.dat, конфигурация и история транзакций в безопасности и не будут удалены.",
"zh": "您的 wallet.dat、配置和交易历史是安全的,不会被删除。",
"ja": "wallet.dat、設定、トランザクション履歴は安全で削除されません。",
"ko": "wallet.dat, 설정 및 거래 내역은 안전하며 삭제되지 않습니다."
},
"confirm_delete_blockchain_title": {
"es": "Eliminar Datos de Blockchain", "de": "Blockchain-Daten löschen", "fr": "Supprimer les données Blockchain",
"pt": "Excluir Dados da Blockchain", "ru": "Удалить данные блокчейна", "zh": "删除区块链数据",
"ja": "ブロックチェーンデータを削除", "ko": "블록체인 데이터 삭제"
},
"confirm_transfer": {
"es": "Confirmar Transferencia", "de": "Überweisung bestätigen", "fr": "Confirmer le transfert",
"pt": "Confirmar Transferência", "ru": "Подтвердить перевод", "zh": "确认转账",
"ja": "送金を確認", "ko": "이체 확인"
},
"copied": {
"es": "¡Copiado!", "de": "Kopiert!", "fr": "Copié !",
"pt": "Copiado!", "ru": "Скопировано!", "zh": "已复制!",
"ja": "コピーしました!", "ko": "복사됨!"
},
"daemon_version": {
"es": "Daemon", "de": "Daemon", "fr": "Daemon",
"pt": "Daemon", "ru": "Демон", "zh": "守护进程",
"ja": "デーモン", "ko": "데몬"
},
"delete_blockchain": {
"es": "Eliminar Blockchain", "de": "Blockchain löschen", "fr": "Supprimer Blockchain",
"pt": "Excluir Blockchain", "ru": "Удалить блокчейн", "zh": "删除区块链",
"ja": "ブロックチェーンを削除", "ko": "블록체인 삭제"
},
"delete_blockchain_confirm": {
"es": "Eliminar y Resincronizar", "de": "Löschen & Neu synchronisieren", "fr": "Supprimer & Resynchroniser",
"pt": "Excluir e Ressincronizar", "ru": "Удалить и пересинхронизировать", "zh": "删除并重新同步",
"ja": "削除して再同期", "ko": "삭제 후 재동기화"
},
"deshielding_warning": {
"es": "Advertencia: Esto des-protegerá fondos de una dirección privada (Z) a una dirección transparente (T).",
"de": "Warnung: Dies wird Gelder von einer privaten (Z) Adresse auf eine transparente (T) Adresse ent-schirmen.",
"fr": "Attention : Cela va déblinder des fonds d'une adresse privée (Z) vers une adresse transparente (T).",
"pt": "Aviso: Isso irá des-blindar fundos de um endereço privado (Z) para um endereço transparente (T).",
"ru": "Внимание: это переведёт средства из приватного (Z) адреса на прозрачный (T) адрес.",
"zh": "警告:这将把资金从隐私 (Z) 地址转移到透明 (T) 地址。",
"ja": "警告:プライベート (Z) アドレスからトランスペアレント (T) アドレスへ資金をデシールドします。",
"ko": "경고: 프라이빗 (Z) 주소에서 투명 (T) 주소로 자금을 디실딩합니다."
},
"download": {
"es": "Descargar", "de": "Herunterladen", "fr": "Télécharger",
"pt": "Baixar", "ru": "Скачать", "zh": "下载",
"ja": "ダウンロード", "ko": "다운로드"
},
"download_bootstrap": {
"es": "Descargar Bootstrap", "de": "Bootstrap herunterladen", "fr": "Télécharger Bootstrap",
"pt": "Baixar Bootstrap", "ru": "Скачать бутстрап", "zh": "下载引导程序",
"ja": "ブートストラップをダウンロード", "ko": "부트스트랩 다운로드"
},
"explorer_block_detail": {
"es": "Bloque", "de": "Block", "fr": "Bloc",
"pt": "Bloco", "ru": "Блок", "zh": "区块",
"ja": "ブロック", "ko": "블록"
},
"explorer_block_hash": {
"es": "Hash", "de": "Hash", "fr": "Hash",
"pt": "Hash", "ru": "Хеш", "zh": "哈希",
"ja": "ハッシュ", "ko": "해시"
},
"explorer_block_height": {
"es": "Altura", "de": "Höhe", "fr": "Hauteur",
"pt": "Altura", "ru": "Высота", "zh": "高度",
"ja": "高さ", "ko": "높이"
},
"explorer_block_merkle": {
"es": "Raíz Merkle", "de": "Merkle-Wurzel", "fr": "Racine Merkle",
"pt": "Raiz Merkle", "ru": "Корень Меркла", "zh": "Merkle 根",
"ja": "マークルルート", "ko": "머클 루트"
},
"explorer_block_size": {
"es": "Tamaño", "de": "Größe", "fr": "Taille",
"pt": "Tamanho", "ru": "Размер", "zh": "大小",
"ja": "サイズ", "ko": "크기"
},
"explorer_block_time": {
"es": "Hora", "de": "Zeit", "fr": "Heure",
"pt": "Hora", "ru": "Время", "zh": "时间",
"ja": "時刻", "ko": "시간"
},
"explorer_block_txs": {
"es": "Transacciones", "de": "Transaktionen", "fr": "Transactions",
"pt": "Transações", "ru": "Транзакции", "zh": "交易",
"ja": "トランザクション", "ko": "트랜잭션"
},
"explorer_chain_stats": {
"es": "Cadena", "de": "Kette", "fr": "Chaîne",
"pt": "Cadeia", "ru": "Цепочка", "zh": "链",
"ja": "チェーン", "ko": "체인"
},
"explorer_invalid_query": {
"es": "Ingrese una altura de bloque o un hash de 64 caracteres",
"de": "Geben Sie eine Blockhöhe oder einen 64-stelligen Hash ein",
"fr": "Entrez une hauteur de bloc ou un hash de 64 caractères",
"pt": "Insira uma altura de bloco ou um hash de 64 caracteres",
"ru": "Введите высоту блока или 64-символьный хеш",
"zh": "输入区块高度或64位哈希",
"ja": "ブロック高さまたは64文字のハッシュを入力してください",
"ko": "블록 높이 또는 64자 해시를 입력하세요"
},
"explorer_mempool": {
"es": "Mempool", "de": "Mempool", "fr": "Mempool",
"pt": "Mempool", "ru": "Мемпул", "zh": "内存池",
"ja": "メモリプール", "ko": "멤풀"
},
"explorer_mempool_size": {
"es": "Tamaño", "de": "Größe", "fr": "Taille",
"pt": "Tamanho", "ru": "Размер", "zh": "大小",
"ja": "サイズ", "ko": "크기"
},
"explorer_mempool_txs": {
"es": "Transacciones", "de": "Transaktionen", "fr": "Transactions",
"pt": "Transações", "ru": "Транзакции", "zh": "交易",
"ja": "トランザクション", "ko": "트랜잭션"
},
"explorer_recent_blocks": {
"es": "Bloques Recientes", "de": "Letzte Blöcke", "fr": "Blocs récents",
"pt": "Blocos Recentes", "ru": "Последние блоки", "zh": "最近区块",
"ja": "最近のブロック", "ko": "최근 블록"
},
"explorer_search": {
"es": "Buscar", "de": "Suchen", "fr": "Rechercher",
"pt": "Pesquisar", "ru": "Поиск", "zh": "搜索",
"ja": "検索", "ko": "검색"
},
"explorer_tx_outputs": {
"es": "Salidas", "de": "Ausgaben", "fr": "Sorties",
"pt": "Saídas", "ru": "Выходы", "zh": "输出",
"ja": "出力", "ko": "출력"
},
"explorer_tx_size": {
"es": "Tamaño", "de": "Größe", "fr": "Taille",
"pt": "Tamanho", "ru": "Размер", "zh": "大小",
"ja": "サイズ", "ko": "크기"
},
"filter": {
"es": "Filtrar...", "de": "Filtern...", "fr": "Filtrer...",
"pt": "Filtrar...", "ru": "Фильтр...", "zh": "筛选...",
"ja": "フィルター...", "ko": "필터..."
},
"force_quit": {
"es": "Forzar Salida", "de": "Sofort beenden", "fr": "Forcer la fermeture",
"pt": "Forçar Saída", "ru": "Принудительный выход", "zh": "强制退出",
"ja": "強制終了", "ko": "강제 종료"
},
"force_quit_confirm_msg": {
"es": "Esto matará inmediatamente el daemon sin un apagado limpio.\nEsto puede corromper el índice de la blockchain y requerir una resincronización.",
"de": "Dies wird den Daemon sofort beenden ohne sauberes Herunterfahren.\nDies kann den Blockchain-Index beschädigen und eine Neusynchronisierung erfordern.",
"fr": "Cela tuera immédiatement le daemon sans arrêt propre.\nCela peut corrompre l'index de la blockchain et nécessiter une resynchronisation.",
"pt": "Isso matará imediatamente o daemon sem um desligamento limpo.\nIsso pode corromper o índice da blockchain e exigir uma ressincronização.",
"ru": "Это немедленно завершит демон без корректного завершения.\nЭто может повредить индекс блокчейна и потребовать повторной синхронизации.",
"zh": "这将立即终止守护进程而不进行正常关闭。\n这可能会损坏区块链索引并需要重新同步。",
"ja": "クリーンシャットダウンなしでデーモンを即座に終了します。\nブロックチェーンインデックスが破損し、再同期が必要になる可能性があります。",
"ko": "정상 종료 없이 데몬을 즉시 종료합니다.\n블록체인 인덱스가 손상되어 재동기화가 필요할 수 있습니다."
},
"force_quit_confirm_title": {
"es": "¿Forzar Salida?", "de": "Sofort beenden?", "fr": "Forcer la fermeture ?",
"pt": "Forçar Saída?", "ru": "Принудительный выход?", "zh": "强制退出?",
"ja": "強制終了しますか?", "ko": "강제 종료하시겠습니까?"
},
"force_quit_warning": {
"es": "Esto matará inmediatamente el daemon sin un apagado limpio. Puede requerir una resincronización de la blockchain.",
"de": "Dies wird den Daemon sofort beenden ohne sauberes Herunterfahren. Kann eine Neusynchronisierung der Blockchain erfordern.",
"fr": "Cela tuera immédiatement le daemon sans arrêt propre. Peut nécessiter une resynchronisation de la blockchain.",
"pt": "Isso matará imediatamente o daemon sem um desligamento limpo. Pode exigir uma ressincronização da blockchain.",
"ru": "Это немедленно завершит демон без корректного завершения. Может потребоваться повторная синхронизация блокчейна.",
"zh": "这将立即终止守护进程而不进行正常关闭。可能需要重新同步区块链。",
"ja": "クリーンシャットダウンなしでデーモンを即座に終了します。ブロックチェーンの再同期が必要になる場合があります。",
"ko": "정상 종료 없이 데몬을 즉시 종료합니다. 블록체인 재동기화가 필요할 수 있습니다."
},
"force_quit_yes": {
"es": "Forzar Salida", "de": "Sofort beenden", "fr": "Forcer la fermeture",
"pt": "Forçar Saída", "ru": "Принудительный выход", "zh": "强制退出",
"ja": "強制終了", "ko": "강제 종료"
},
"generating": {
"es": "Generando", "de": "Wird generiert", "fr": "Génération",
"pt": "Gerando", "ru": "Генерация", "zh": "正在生成",
"ja": "生成中", "ko": "생성 중"
},
"hidden_tag": {
"es": " (oculto)", "de": " (versteckt)", "fr": " (masqué)",
"pt": " (oculto)", "ru": " (скрыт)", "zh": " (已隐藏)",
"ja": " (非表示)", "ko": " (숨김)"
},
"incorrect_passphrase": {
"es": "Contraseña incorrecta", "de": "Falsches Passwort", "fr": "Mot de passe incorrect",
"pt": "Senha incorreta", "ru": "Неверный пароль", "zh": "密码错误",
"ja": "パスフレーズが正しくありません", "ko": "잘못된 암호"
},
"incorrect_pin": {
"es": "PIN incorrecto", "de": "Falsche PIN", "fr": "PIN incorrect",
"pt": "PIN incorreto", "ru": "Неверный PIN", "zh": "PIN 错误",
"ja": "PINが正しくありません", "ko": "잘못된 PIN"
},
"insufficient_funds": {
"es": "Fondos insuficientes para este monto más la comisión.", "de": "Unzureichendes Guthaben für diesen Betrag plus Gebühr.",
"fr": "Fonds insuffisants pour ce montant plus les frais.", "pt": "Fundos insuficientes para este valor mais taxa.",
"ru": "Недостаточно средств для этой суммы плюс комиссия.", "zh": "余额不足以支付此金额加手续费。",
"ja": "この金額と手数料に対して残高が不足しています。", "ko": "이 금액과 수수료를 위한 잔액이 부족합니다."
},
"label_placeholder": {
"es": "ej. Ahorros, Minería...", "de": "z.B. Ersparnisse, Mining...", "fr": "ex. Épargne, Minage...",
"pt": "ex. Poupança, Mineração...", "ru": "напр. Накопления, Майнинг...", "zh": "例如 储蓄、挖矿...",
"ja": "例: 貯金、マイニング...", "ko": "예: 저축, 채굴..."
},
"mining_benchmark_cancel": {
"es": "Cancelar benchmark", "de": "Benchmark abbrechen", "fr": "Annuler le benchmark",
"pt": "Cancelar benchmark", "ru": "Отменить тест", "zh": "取消基准测试",
"ja": "ベンチマークをキャンセル", "ko": "벤치마크 취소"
},
"mining_benchmark_cooling": {
"es": "Enfriando", "de": "Abkühlen", "fr": "Refroidissement",
"pt": "Resfriando", "ru": "Охлаждение", "zh": "冷却中",
"ja": "クーリング", "ko": "쿨링"
},
"mining_benchmark_dismiss": {
"es": "Cerrar", "de": "Schließen", "fr": "Fermer",
"pt": "Fechar", "ru": "Закрыть", "zh": "关闭",
"ja": "閉じる", "ko": "닫기"
},
"mining_benchmark_result": {
"es": "Óptimo", "de": "Optimal", "fr": "Optimal",
"pt": "Ótimo", "ru": "Оптимально", "zh": "最佳",
"ja": "最適", "ko": "최적"
},
"mining_benchmark_stabilizing": {
"es": "Estabilizando", "de": "Stabilisierung", "fr": "Stabilisation",
"pt": "Estabilizando", "ru": "Стабилизация", "zh": "稳定中",
"ja": "安定化中", "ko": "안정화 중"
},
"mining_benchmark_testing": {
"es": "Probando", "de": "Testen", "fr": "Test",
"pt": "Testando", "ru": "Тестирование", "zh": "测试中",
"ja": "テスト中", "ko": "테스트 중"
},
"mining_benchmark_tooltip": {
"es": "Encontrar el número óptimo de hilos para esta CPU",
"de": "Optimale Thread-Anzahl für diese CPU finden",
"fr": "Trouver le nombre optimal de threads pour ce CPU",
"pt": "Encontrar o número ideal de threads para esta CPU",
"ru": "Найти оптимальное количество потоков для этого процессора",
"zh": "为此 CPU 找到最佳线程数",
"ja": "このCPUに最適なスレッド数を検出",
"ko": "이 CPU에 최적의 스레드 수 찾기"
},
"mining_generate_z_address_hint": {
"es": "Genere una dirección Z en la pestaña Recibir para usarla como dirección de pago",
"de": "Generieren Sie eine Z-Adresse im Empfangen-Tab als Auszahlungsadresse",
"fr": "Générez une adresse Z dans l'onglet Recevoir pour l'utiliser comme adresse de paiement",
"pt": "Gere um endereço Z na aba Receber para usar como endereço de pagamento",
"ru": "Создайте Z-адрес на вкладке «Получить» для использования в качестве адреса выплат",
"zh": "在接收标签页生成一个 Z 地址作为支付地址",
"ja": "受信タブでZアドレスを生成して支払いアドレスとして使用してください",
"ko": "수신 탭에서 Z 주소를 생성하여 지급 주소로 사용하세요"
},
"mining_idle_gpu_off_tooltip": {
"es": "Sin restricción: ACTIVADO\nSolo la entrada de teclado/ratón determina el estado inactivo\nClic para activar detección de GPU",
"de": "Uneingeschränkt: EIN\nNur Tastatur-/Mauseingabe bestimmt den Leerlauf\nKlicken für GPU-bewusste Erkennung",
"fr": "Sans restriction : ACTIVÉ\nSeule l'entrée clavier/souris détermine l'inactivité\nCliquez pour activer la détection GPU",
"pt": "Sem restrição: ATIVADO\nApenas entrada de teclado/mouse determina o estado ocioso\nClique para ativar detecção de GPU",
"ru": "Без ограничений: ВКЛ\nТолько ввод с клавиатуры/мыши определяет состояние простоя\nНажмите для GPU-контроля",
"zh": "无限制:开启\n仅键盘/鼠标输入决定空闲状态\n点击启用GPU感知检测",
"ja": "制限なし: オン\nキーボード/マウス入力のみがアイドル状態を決定\nGPU検出を有効にするにはクリック",
"ko": "무제한: 켜짐\n키보드/마우스 입력만 유휴 상태를 결정\nGPU 감지를 활성화하려면 클릭"
},
"mining_idle_gpu_on_tooltip": {
"es": "GPU-consciente: ACTIVADO\nLa actividad de GPU (video, juegos) previene la minería inactiva\nClic para modo sin restricción",
"de": "GPU-bewusst: EIN\nGPU-Aktivität (Video, Spiele) verhindert Leerlauf-Mining\nKlicken für uneingeschränkten Modus",
"fr": "GPU-conscient : ACTIVÉ\nL'activité GPU (vidéo, jeux) empêche le minage inactif\nCliquez pour le mode sans restriction",
"pt": "GPU-consciente: ATIVADO\nAtividade de GPU (vídeo, jogos) impede mineração ociosa\nClique para modo sem restrição",
"ru": "GPU-контроль: ВКЛ\nАктивность GPU (видео, игры) предотвращает майнинг в простое\nНажмите для режима без ограничений",
"zh": "GPU感知:开启\nGPU活动(视频、游戏)阻止空闲挖矿\n点击切换到无限制模式",
"ja": "GPU対応: オン\nGPUアクティビティ(動画、ゲーム)がアイドルマイニングを防止\n制限なしモードに切り替えるにはクリック",
"ko": "GPU 감지: 켜짐\nGPU 활동(비디오, 게임)이 유휴 채굴을 방지\n무제한 모드로 전환하려면 클릭"
},
"mining_idle_scale_off_tooltip": {
"es": "Modo inicio/parada: ACTIVADO\nClic para cambiar al modo de escala de hilos",
"de": "Start/Stopp-Modus: EIN\nKlicken zum Wechsel auf Thread-Skalierung",
"fr": "Mode démarrage/arrêt : ACTIVÉ\nCliquez pour passer au mode mise à l'échelle des threads",
"pt": "Modo iniciar/parar: ATIVADO\nClique para mudar para modo de escala de threads",
"ru": "Режим старт/стоп: ВКЛ\nНажмите для переключения на масштабирование потоков",
"zh": "启动/停止模式:开启\n点击切换到线程缩放模式",
"ja": "開始/停止モード: オン\nスレッドスケーリングモードに切り替えるにはクリック",
"ko": "시작/중지 모드: 켜짐\n스레드 스케일링 모드로 전환하려면 클릭"
},
"mining_idle_scale_on_tooltip": {
"es": "Escala de hilos: ACTIVADO\nClic para cambiar al modo de inicio/parada",
"de": "Thread-Skalierung: EIN\nKlicken zum Wechsel auf Start/Stopp-Modus",
"fr": "Mise à l'échelle des threads : ACTIVÉ\nCliquez pour passer au mode démarrage/arrêt",
"pt": "Escala de threads: ATIVADO\nClique para mudar para modo iniciar/parar",
"ru": "Масштабирование потоков: ВКЛ\nНажмите для переключения на режим старт/стоп",
"zh": "线程缩放:开启\n点击切换到启动/停止模式",
"ja": "スレッドスケーリング: オン\n開始/停止モードに切り替えるにはクリック",
"ko": "스레드 스케일링: 켜짐\n시작/중지 모드로 전환하려면 클릭"
},
"mining_idle_threads_active_tooltip": {
"es": "Hilos cuando el usuario está activo", "de": "Threads bei Benutzeraktivität",
"fr": "Threads quand l'utilisateur est actif", "pt": "Threads quando o usuário está ativo",
"ru": "Потоки при активности пользователя", "zh": "用户活跃时的线程数",
"ja": "ユーザーアクティブ時のスレッド数", "ko": "사용자 활성 시 스레드"
},
"mining_idle_threads_idle_tooltip": {
"es": "Hilos cuando el sistema está inactivo", "de": "Threads im Leerlauf",
"fr": "Threads quand le système est inactif", "pt": "Threads quando o sistema está ocioso",
"ru": "Потоки при простое системы", "zh": "系统空闲时的线程数",
"ja": "システムアイドル時のスレッド数", "ko": "시스템 유휴 시 스레드"
},
"no_addresses_yet": {
"es": "Aún no hay direcciones", "de": "Noch keine Adressen", "fr": "Pas encore d'adresses",
"pt": "Nenhum endereço ainda", "ru": "Пока нет адресов", "zh": "暂无地址",
"ja": "アドレスがまだありません", "ko": "아직 주소가 없습니다"
},
"pin_not_set": {
"es": "PIN no configurado. Use la contraseña para desbloquear.",
"de": "PIN nicht gesetzt. Verwenden Sie das Passwort zum Entsperren.",
"fr": "PIN non défini. Utilisez le mot de passe pour déverrouiller.",
"pt": "PIN não definido. Use a senha para desbloquear.",
"ru": "PIN не установлен. Используйте пароль для разблокировки.",
"zh": "未设置 PIN。使用密码解锁。",
"ja": "PINが設定されていません。パスフレーズで解除してください。",
"ko": "PIN이 설정되지 않았습니다. 암호를 사용하여 잠금 해제하세요."
},
"recipient_balance": {
"es": "Destinatario: %.8f → %.8f DRGX", "de": "Empfänger: %.8f → %.8f DRGX",
"fr": "Destinataire : %.8f → %.8f DRGX", "pt": "Destinatário: %.8f → %.8f DRGX",
"ru": "Получатель: %.8f → %.8f DRGX", "zh": "接收方: %.8f → %.8f DRGX",
"ja": "受取人: %.8f → %.8f DRGX", "ko": "수신자: %.8f → %.8f DRGX"
},
"reduce_motion": {
"es": "Reducir Movimiento", "de": "Bewegung reduzieren", "fr": "Réduire les animations",
"pt": "Reduzir Movimento", "ru": "Уменьшить анимацию", "zh": "减少动画",
"ja": "モーションを減らす", "ko": "모션 줄이기"
},
"restarting_after_encryption": {
"es": "Reiniciando daemon después del cifrado...", "de": "Daemon wird nach Verschlüsselung neu gestartet...",
"fr": "Redémarrage du daemon après chiffrement...", "pt": "Reiniciando daemon após criptografia...",
"ru": "Перезапуск демона после шифрования...", "zh": "加密后重启守护进程...",
"ja": "暗号化後にデーモンを再起動中...", "ko": "암호화 후 데몬 재시작 중..."
},
"result_preview": {
"es": "Vista previa del resultado", "de": "Ergebnisvorschau", "fr": "Aperçu du résultat",
"pt": "Pré-visualização do resultado", "ru": "Предпросмотр результата", "zh": "结果预览",
"ja": "結果プレビュー", "ko": "결과 미리보기"
},
"retry": {
"es": "Reintentar", "de": "Wiederholen", "fr": "Réessayer",
"pt": "Tentar novamente", "ru": "Повторить", "zh": "重试",
"ja": "再試行", "ko": "재시도"
},
"sender_balance": {
"es": "Remitente: %.8f → %.8f DRGX", "de": "Absender: %.8f → %.8f DRGX",
"fr": "Expéditeur : %.8f → %.8f DRGX", "pt": "Remetente: %.8f → %.8f DRGX",
"ru": "Отправитель: %.8f → %.8f DRGX", "zh": "发送方: %.8f → %.8f DRGX",
"ja": "送信者: %.8f → %.8f DRGX", "ko": "발신자: %.8f → %.8f DRGX"
},
"sends_full_balance_warning": {
"es": "Esto envía el saldo completo. La dirección de envío tendrá saldo cero.",
"de": "Dies sendet das gesamte Guthaben. Die Sendeadresse wird ein Nullguthaben haben.",
"fr": "Cela envoie le solde complet. L'adresse d'envoi aura un solde nul.",
"pt": "Isso envia o saldo total. O endereço de envio terá saldo zero.",
"ru": "Это отправит весь баланс. Адрес отправителя останется с нулевым балансом.",
"zh": "这将发送全部余额。发送地址将变为零余额。",
"ja": "全残高を送信します。送信アドレスの残高はゼロになります。",
"ko": "전체 잔액을 전송합니다. 보내는 주소의 잔액이 0이 됩니다."
},
"set_label": {
"es": "Establecer Etiqueta...", "de": "Label setzen...", "fr": "Définir le libellé...",
"pt": "Definir Rótulo...", "ru": "Установить метку...", "zh": "设置标签...",
"ja": "ラベルを設定...", "ko": "라벨 설정..."
},
"shielded_address": {
"es": "Dirección Protegida", "de": "Geschirmte Adresse", "fr": "Adresse protégée",
"pt": "Endereço Blindado", "ru": "Экранированный адрес", "zh": "隐蔽地址",
"ja": "シールドアドレス", "ko": "보호 주소"
},
"shielding_notice": {
"es": "Nota: Esto blindará fondos de una dirección transparente (T) a una dirección privada (Z).",
"de": "Hinweis: Dies wird Gelder von einer transparenten (T) Adresse auf eine private (Z) Adresse schirmen.",
"fr": "Note : Cela blindera des fonds d'une adresse transparente (T) vers une adresse privée (Z).",
"pt": "Nota: Isso blindará fundos de um endereço transparente (T) para um endereço privado (Z).",
"ru": "Примечание: это переведёт средства с прозрачного (T) адреса на приватный (Z) адрес.",
"zh": "注意:这将把资金从透明 (T) 地址转移到隐私 (Z) 地址。",
"ja": "注意:トランスペアレント (T) アドレスからプライベート (Z) アドレスへ資金をシールドします。",
"ko": "참고: 투명 (T) 주소에서 프라이빗 (Z) 주소로 자금을 실딩합니다."
},
"showing_x_of_y": {
"es": "Mostrando %d de %d direcciones", "de": "%d von %d Adressen angezeigt",
"fr": "Affichage de %d sur %d adresses", "pt": "Mostrando %d de %d endereços",
"ru": "Показано %d из %d адресов", "zh": "显示 %d / %d 个地址",
"ja": "%d / %d アドレスを表示", "ko": "%d / %d 주소 표시"
},
"t_address": {
"es": "Dirección T", "de": "T-Adresse", "fr": "Adresse T",
"pt": "Endereço T", "ru": "T-адрес", "zh": "T 地址",
"ja": "Tアドレス", "ko": "T 주소"
},
"theme_language": {
"es": "TEMA E IDIOMA", "de": "THEMA & SPRACHE", "fr": "THÈME & LANGUE",
"pt": "TEMA E IDIOMA", "ru": "ТЕМА И ЯЗЫК", "zh": "主题与语言",
"ja": "テーマと言語", "ko": "테마 및 언어"
},
"tools_actions": {
"es": "Herramientas y Acciones...", "de": "Werkzeuge & Aktionen...", "fr": "Outils & Actions...",
"pt": "Ferramentas e Ações...", "ru": "Инструменты и действия...", "zh": "工具与操作...",
"ja": "ツールとアクション...", "ko": "도구 및 작업..."
},
"transfer_failed": {
"es": "Transferencia Fallida", "de": "Überweisung fehlgeschlagen", "fr": "Échec du transfert",
"pt": "Transferência Falhou", "ru": "Ошибка перевода", "zh": "转账失败",
"ja": "送金失敗", "ko": "이체 실패"
},
"transfer_funds": {
"es": "Transferir Fondos", "de": "Geld überweisen", "fr": "Transférer des fonds",
"pt": "Transferir Fundos", "ru": "Перевести средства", "zh": "转账",
"ja": "資金を送金", "ko": "자금 이체"
},
"transfer_sent": {
"es": "Transferencia Enviada", "de": "Überweisung gesendet", "fr": "Transfert envoyé",
"pt": "Transferência Enviada", "ru": "Перевод отправлен", "zh": "转账已发送",
"ja": "送金完了", "ko": "이체 전송됨"
},
"transfer_sent_desc": {
"es": "Su transferencia ha sido enviada a la red.",
"de": "Ihre Überweisung wurde an das Netzwerk gesendet.",
"fr": "Votre transfert a été soumis au réseau.",
"pt": "Sua transferência foi enviada à rede.",
"ru": "Ваш перевод отправлен в сеть.",
"zh": "您的转账已提交到网络。",
"ja": "送金がネットワークに送信されました。",
"ko": "이체가 네트워크에 제출되었습니다."
},
"transfer_to": {
"es": "Transferir a:", "de": "Überweisen an:", "fr": "Transférer à :",
"pt": "Transferir para:", "ru": "Перевести на:", "zh": "转账至:",
"ja": "送金先:", "ko": "이체 대상:"
},
"transparent_address": {
"es": "Dirección Transparente", "de": "Transparente Adresse", "fr": "Adresse transparente",
"pt": "Endereço Transparente", "ru": "Прозрачный адрес", "zh": "透明地址",
"ja": "トランスペアレントアドレス", "ko": "투명 주소"
},
"tt_delete_blockchain": {
"es": "Eliminar todos los datos de la blockchain e iniciar una nueva sincronización. Se preservan wallet.dat y la configuración.",
"de": "Alle Blockchain-Daten löschen und neu synchronisieren. wallet.dat und Konfiguration bleiben erhalten.",
"fr": "Supprimer toutes les données de la blockchain et démarrer une nouvelle synchronisation. wallet.dat et la configuration sont préservés.",
"pt": "Excluir todos os dados da blockchain e iniciar uma nova sincronização. wallet.dat e configuração são preservados.",
"ru": "Удалить все данные блокчейна и начать синхронизацию заново. wallet.dat и конфигурация сохраняются.",
"zh": "删除所有区块链数据并重新同步。wallet.dat 和配置将被保留。",
"ja": "すべてのブロックチェーンデータを削除して新規同期を開始します。wallet.dat と設定は保持されます。",
"ko": "모든 블록체인 데이터를 삭제하고 새로 동기화합니다. wallet.dat 및 설정은 보존됩니다."
},
"tt_download_bootstrap": {
"es": "Descargar bootstrap de blockchain para acelerar la sincronización\nLos datos de bloques existentes serán reemplazados",
"de": "Blockchain-Bootstrap herunterladen, um die Synchronisierung zu beschleunigen\nVorhandene Blockdaten werden ersetzt",
"fr": "Télécharger le bootstrap blockchain pour accélérer la synchronisation\nLes données de blocs existantes seront remplacées",
"pt": "Baixar bootstrap da blockchain para acelerar a sincronização\nDados de blocos existentes serão substituídos",
"ru": "Скачать бутстрап блокчейна для ускорения синхронизации\nСуществующие данные блоков будут заменены",
"zh": "下载区块链引导程序以加速同步\n现有区块数据将被替换",
"ja": "ブロックチェーンブートストラップをダウンロードして同期を高速化\n既存のブロックデータは置き換えられます",
"ko": "블록체인 부트스트랩을 다운로드하여 동기화 가속\n기존 블록 데이터가 교체됩니다"
},
"tt_reduce_motion": {
"es": "Desactivar transiciones animadas y lerp de saldo para accesibilidad",
"de": "Animierte Übergänge und Saldo-Lerp für Barrierefreiheit deaktivieren",
"fr": "Désactiver les transitions animées et le lerp de solde pour l'accessibilité",
"pt": "Desativar transições animadas e lerp de saldo para acessibilidade",
"ru": "Отключить анимированные переходы и плавное изменение баланса для доступности",
"zh": "禁用动画过渡和余额渐变以提高无障碍性",
"ja": "アクセシビリティのためにアニメーション遷移と残高補間を無効にする",
"ko": "접근성을 위해 애니메이션 전환 및 잔액 보간 비활성화"
},
"wizard_daemon_start_failed": {
"es": "Error al iniciar el daemon — se reintentará automáticamente",
"de": "Daemon-Start fehlgeschlagen — wird automatisch wiederholt",
"fr": "Échec du démarrage du daemon — sera réessayé automatiquement",
"pt": "Falha ao iniciar o daemon — será tentado novamente automaticamente",
"ru": "Не удалось запустить демон — будет повторено автоматически",
"zh": "启动守护进程失败 — 将自动重试",
"ja": "デーモンの起動に失敗しました — 自動的に再試行されます",
"ko": "데몬 시작 실패 — 자동으로 재시도됩니다"
},
"z_address": {
"es": "Dirección Z", "de": "Z-Adresse", "fr": "Adresse Z",
"pt": "Endereço Z", "ru": "Z-адрес", "zh": "Z 地址",
"ja": "Zアドレス", "ko": "Z 주소"
},
# ── Status bar strings ───────────────────────────────────────────────
"sb_warming_up": {
"es": "Calentando...", "de": "Aufwärmen...", "fr": "Démarrage...",
"pt": "Aquecendo...", "ru": "Прогрев...", "zh": "正在预热...",
"ja": "ウォームアップ中...", "ko": "워밍업 중..."
},
"sb_block": {
"es": "Bloque: %d", "de": "Block: %d", "fr": "Bloc : %d",
"pt": "Bloco: %d", "ru": "Блок: %d", "zh": "区块: %d",
"ja": "ブロック: %d", "ko": "블록: %d"
},
"sb_peers": {
"es": "Pares: %zu", "de": "Peers: %zu", "fr": "Pairs : %zu",
"pt": "Pares: %zu", "ru": "Пиры: %zu", "zh": "节点: %zu",
"ja": "ピア: %zu", "ko": "피어: %zu"
},
"sb_net_ghs": {
"es": "Red: %.2f GH/s", "de": "Netz: %.2f GH/s", "fr": "Rés: %.2f GH/s",
"pt": "Rede: %.2f GH/s", "ru": "Сеть: %.2f GH/s", "zh": "网络: %.2f GH/s",
"ja": "ネット: %.2f GH/s", "ko": "네트: %.2f GH/s"
},
"sb_net_mhs": {
"es": "Red: %.2f MH/s", "de": "Netz: %.2f MH/s", "fr": "Rés: %.2f MH/s",
"pt": "Rede: %.2f MH/s", "ru": "Сеть: %.2f MH/s", "zh": "网络: %.2f MH/s",
"ja": "ネット: %.2f MH/s", "ko": "네트: %.2f MH/s"
},
"sb_net_khs": {
"es": "Red: %.2f KH/s", "de": "Netz: %.2f KH/s", "fr": "Rés: %.2f KH/s",
"pt": "Rede: %.2f KH/s", "ru": "Сеть: %.2f KH/s", "zh": "网络: %.2f KH/s",
"ja": "ネット: %.2f KH/s", "ko": "네트: %.2f KH/s"
},
"sb_net_hs": {
"es": "Red: %.1f H/s", "de": "Netz: %.1f H/s", "fr": "Rés: %.1f H/s",
"pt": "Rede: %.1f H/s", "ru": "Сеть: %.1f H/s", "zh": "网络: %.1f H/s",
"ja": "ネット: %.1f H/s", "ko": "네트: %.1f H/s"
},
"sb_mining_hs": {
"es": "%.1f H/s", "de": "%.1f H/s", "fr": "%.1f H/s",
"pt": "%.1f H/s", "ru": "%.1f H/s", "zh": "%.1f H/s",
"ja": "%.1f H/s", "ko": "%.1f H/s"
},
"sb_syncing_eta": {
"es": "Sincronizando %.1f%% (%d restantes, %.0f blk/s, ~%s)",
"de": "Synchronisierung %.1f%% (%d übrig, %.0f Blk/s, ~%s)",
"fr": "Synchronisation %.1f%% (%d restants, %.0f blk/s, ~%s)",
"pt": "Sincronizando %.1f%% (%d restantes, %.0f blk/s, ~%s)",
"ru": "Синхронизация %.1f%% (%d осталось, %.0f блк/с, ~%s)",
"zh": "同步中 %.1f%% (剩余 %d, %.0f 块/秒, ~%s)",
"ja": "同期中 %.1f%% (残り %d, %.0f ブロック/秒, ~%s)",
"ko": "동기화 %.1f%% (%d 남음, %.0f 블록/초, ~%s)"
},
"sb_syncing_basic": {
"es": "Sincronizando %.1f%% (%d restantes)",
"de": "Synchronisierung %.1f%% (%d übrig)",
"fr": "Synchronisation %.1f%% (%d restants)",
"pt": "Sincronizando %.1f%% (%d restantes)",
"ru": "Синхронизация %.1f%% (%d осталось)",
"zh": "同步中 %.1f%% (剩余 %d)",
"ja": "同期中 %.1f%% (残り %d)",
"ko": "동기화 %.1f%% (%d 남음)"
},
"sb_rescanning_pct": {
"es": "Reescaneando %.0f%%", "de": "Neuscan %.0f%%", "fr": "Rescan %.0f%%",
"pt": "Reescaneando %.0f%%", "ru": "Пересканирование %.0f%%", "zh": "重新扫描 %.0f%%",
"ja": "再スキャン %.0f%%", "ko": "재스캔 %.0f%%"
},
"sb_rescanning": {
"es": "Reescaneando", "de": "Neuscan", "fr": "Rescan",
"pt": "Reescaneando", "ru": "Пересканирование", "zh": "重新扫描",
"ja": "再スキャン", "ko": "재스캔"
},
"sb_importing_keys": {
"es": "Importando claves", "de": "Schlüssel importieren", "fr": "Importation des clés",
"pt": "Importando chaves", "ru": "Импорт ключей", "zh": "正在导入密钥",
"ja": "鍵をインポート中", "ko": "키 가져오기 중"
},
"sb_daemon_not_found": {
"es": "Daemon no encontrado", "de": "Daemon nicht gefunden", "fr": "Daemon introuvable",
"pt": "Daemon não encontrado", "ru": "Демон не найден", "zh": "未找到守护进程",
"ja": "デーモンが見つかりません", "ko": "데몬을 찾을 수 없음"
},
"sb_loading_config": {
"es": "Cargando configuración...", "de": "Konfiguration laden...", "fr": "Chargement de la configuration...",
"pt": "Carregando configuração...", "ru": "Загрузка конфигурации...", "zh": "正在加载配置...",
"ja": "設定を読み込み中...", "ko": "설정 불러오는 중..."
},
"sb_waiting_config": {
"es": "Esperando configuración del daemon...", "de": "Warten auf Daemon-Konfiguration...", "fr": "En attente de la configuration du daemon...",
"pt": "Aguardando configuração do daemon...", "ru": "Ожидание конфигурации демона...", "zh": "等待守护进程配置...",
"ja": "デーモン設定を待機中...", "ko": "데몬 설정 대기 중..."
},
"sb_no_conf": {
"es": "DRAGONX.conf no encontrado", "de": "DRAGONX.conf nicht gefunden", "fr": "DRAGONX.conf introuvable",
"pt": "DRAGONX.conf não encontrado", "ru": "DRAGONX.conf не найден", "zh": "未找到 DRAGONX.conf",
"ja": "DRAGONX.conf が見つかりません", "ko": "DRAGONX.conf를 찾을 수 없음"
},
"sb_starting_daemon": {
"es": "Iniciando dragonxd...", "de": "dragonxd wird gestartet...", "fr": "Démarrage de dragonxd...",
"pt": "Iniciando dragonxd...", "ru": "Запуск dragonxd...", "zh": "正在启动 dragonxd...",
"ja": "dragonxd を起動中...", "ko": "dragonxd 시작 중..."
},
"sb_connecting_daemon": {
"es": "Conectando a dragonxd...", "de": "Verbindung zu dragonxd...", "fr": "Connexion à dragonxd...",
"pt": "Conectando ao dragonxd...", "ru": "Подключение к dragonxd...", "zh": "正在连接 dragonxd...",
"ja": "dragonxd に接続中...", "ko": "dragonxd에 연결 중..."
},
"sb_auth_failed": {
"es": "Autenticación fallida — verifique rpcuser/rpcpassword",
"de": "Authentifizierung fehlgeschlagen — rpcuser/rpcpassword prüfen",
"fr": "Authentification échouée — vérifiez rpcuser/rpcpassword",
"pt": "Autenticação falhou — verifique rpcuser/rpcpassword",
"ru": "Ошибка авторизации — проверьте rpcuser/rpcpassword",
"zh": "认证失败 — 请检查 rpcuser/rpcpassword",
"ja": "認証失敗 — rpcuser/rpcpassword を確認してください",
"ko": "인증 실패 — rpcuser/rpcpassword를 확인하세요"
},
"sb_waiting_daemon": {
"es": "Esperando a dragonxd...", "de": "Warten auf dragonxd...", "fr": "En attente de dragonxd...",
"pt": "Aguardando dragonxd...", "ru": "Ожидание dragonxd...", "zh": "等待 dragonxd 启动...",
"ja": "dragonxd を待機中...", "ko": "dragonxd 대기 중..."
},
"sb_waiting_daemon_err": {
"es": "Esperando a dragonxd — %s", "de": "Warten auf dragonxd — %s", "fr": "En attente de dragonxd — %s",
"pt": "Aguardando dragonxd — %s", "ru": "Ожидание dragonxd — %s", "zh": "等待 dragonxd — %s",
"ja": "dragonxd を待機中 — %s", "ko": "dragonxd 대기 중 — %s"
},
"sb_connecting_external": {
"es": "Conectando a daemon externo...", "de": "Verbindung zu externem Daemon...", "fr": "Connexion au daemon externe...",
"pt": "Conectando ao daemon externo...", "ru": "Подключение к внешнему демону...", "zh": "正在连接外部守护进程...",
"ja": "外部デーモンに接続中...", "ko": "외부 데몬에 연결 중..."
},
"sb_connecting_generic": {
"es": "Conectando al daemon...", "de": "Verbindung zum Daemon...", "fr": "Connexion au daemon...",
"pt": "Conectando ao daemon...", "ru": "Подключение к демону...", "zh": "正在连接守护进程...",
"ja": "デーモンに接続中...", "ko": "데몬에 연결 중..."
},
"sb_connecting_err": {
"es": "Conectando al daemon — %s", "de": "Verbindung zum Daemon — %s", "fr": "Connexion au daemon — %s",
"pt": "Conectando ao daemon — %s", "ru": "Подключение к демону — %s", "zh": "连接守护进程 — %s",
"ja": "デーモンに接続中 — %s", "ko": "데몬 연결 중 — %s"
},
"sb_daemon_crashed": {
"es": "El daemon se bloqueó %d veces", "de": "Daemon ist %d mal abgestürzt", "fr": "Le daemon a planté %d fois",
"pt": "O daemon travou %d vezes", "ru": "Демон упал %d раз", "zh": "守护进程崩溃 %d 次",
"ja": "デーモンが %d 回クラッシュしました", "ko": "데몬이 %d회 충돌함"
},
"sb_extracting_sapling": {
"es": "Extrayendo parámetros Sapling...", "de": "Sapling-Parameter werden extrahiert...", "fr": "Extraction des paramètres Sapling...",
"pt": "Extraindo parâmetros Sapling...", "ru": "Извлечение параметров Sapling...", "zh": "正在提取 Sapling 参数...",
"ja": "Sapling パラメータを展開中...", "ko": "Sapling 매개변수 추출 중..."
},
"sb_sapling_failed": {
"es": "Error al extraer parámetros Sapling.", "de": "Sapling-Parameter-Extraktion fehlgeschlagen.", "fr": "Échec de l'extraction des paramètres Sapling.",
"pt": "Falha ao extrair parâmetros Sapling.", "ru": "Ошибка извлечения параметров Sapling.", "zh": "提取 Sapling 参数失败。",
"ja": "Sapling パラメータの展開に失敗しました。", "ko": "Sapling 매개변수 추출 실패."
},
"sb_sapling_not_found": {
"es": "Parámetros Sapling no encontrados.", "de": "Sapling-Parameter nicht gefunden.", "fr": "Paramètres Sapling introuvables.",
"pt": "Parâmetros Sapling não encontrados.", "ru": "Параметры Sapling не найдены.", "zh": "未找到 Sapling 参数。",
"ja": "Sapling パラメータが見つかりません。", "ko": "Sapling 매개변수를 찾을 수 없음."
},
"sb_dragonxd_running": {
"es": "dragonxd ejecutándose", "de": "dragonxd läuft", "fr": "dragonxd en cours",
"pt": "dragonxd em execução", "ru": "dragonxd запущен", "zh": "dragonxd 运行中",
"ja": "dragonxd 実行中", "ko": "dragonxd 실행 중"
},
"sb_dragonxd_stopping": {
"es": "Deteniendo dragonxd...", "de": "dragonxd wird gestoppt...", "fr": "Arrêt de dragonxd...",
"pt": "Parando dragonxd...", "ru": "Остановка dragonxd...", "zh": "正在停止 dragonxd...",
"ja": "dragonxd を停止中...", "ko": "dragonxd 중지 중..."
},
"sb_dragonxd_stopped": {
"es": "dragonxd detenido", "de": "dragonxd gestoppt", "fr": "dragonxd arrêté",
"pt": "dragonxd parado", "ru": "dragonxd остановлен", "zh": "dragonxd 已停止",
"ja": "dragonxd 停止", "ko": "dragonxd 중지됨"
},
"sb_restarting_daemon": {
"es": "Reiniciando daemon...", "de": "Daemon wird neu gestartet...", "fr": "Redémarrage du daemon...",
"pt": "Reiniciando daemon...", "ru": "Перезапуск демона...", "zh": "正在重启守护进程...",
"ja": "デーモンを再起動中...", "ko": "데몬 재시작 중..."
},
# --- Sidebar / section label fixes ---
"explorer_section": {
"es": "EXPLORADOR", "de": "EXPLORER", "fr": "EXPLORATEUR",
"pt": "EXPLORADOR", "ru": "ОБОЗРЕВАТЕЛЬ", "zh": "浏览器",
"ja": "エクスプローラー", "ko": "탐색기"
},
}
def main():
-131
View File
@@ -1,131 +0,0 @@
#!/usr/bin/env python3
"""
Build a NotoSansCJK subset font containing all characters used by
the zh, ja, and ko translation files, plus common CJK punctuation
and symbols.
Usage:
python3 scripts/build_cjk_subset.py
Requires: pip install fonttools brotli
"""
import json
import os
from fontTools.ttLib import TTFont
from fontTools import subset as ftsubset
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
LANG_DIR = os.path.join(ROOT, 'res', 'lang')
SOURCE_FONT = '/tmp/NotoSansCJKsc-Regular.otf'
OUTPUT_FONT = os.path.join(ROOT, 'res', 'fonts', 'NotoSansCJK-Subset.ttf')
# Collect all characters used in CJK translation files
needed = set()
for lang in ['zh', 'ja', 'ko']:
path = os.path.join(LANG_DIR, f'{lang}.json')
if not os.path.exists(path):
continue
with open(path, 'r', encoding='utf-8') as f:
data = json.load(f)
for v in data.values():
if isinstance(v, str):
for c in v:
cp = ord(c)
if cp > 0x7F: # non-ASCII only (ASCII handled by Ubuntu font)
needed.add(cp)
# Also add common CJK ranges that future translations might use:
# - CJK punctuation and symbols (3000-303F)
# - Hiragana (3040-309F)
# - Katakana (30A0-30FF)
# - Bopomofo (3100-312F)
# - CJK quotation marks, brackets
for cp in range(0x3000, 0x3100):
needed.add(cp)
for cp in range(0x3100, 0x3130):
needed.add(cp)
# Fullwidth ASCII variants (commonly mixed in CJK text)
for cp in range(0xFF01, 0xFF5F):
needed.add(cp)
print(f"Total non-ASCII characters to include: {len(needed)}")
# Check which of these the source font supports
font = TTFont(SOURCE_FONT)
cmap = font.getBestCmap()
supportable = needed & set(cmap.keys())
unsupported = needed - set(cmap.keys())
print(f"Supported by source font: {len(supportable)}")
if unsupported:
print(f"Not in source font (will use fallback): {len(unsupported)}")
for cp in sorted(unsupported)[:10]:
print(f" U+{cp:04X} {chr(cp)}")
# Build the subset using pyftsubset CLI-style API
args = [
SOURCE_FONT,
f'--output-file={OUTPUT_FONT}',
f'--unicodes={",".join(f"U+{cp:04X}" for cp in sorted(supportable))}',
'--no-hinting',
'--desubroutinize',
]
ftsubset.main(args)
# Convert CFF outlines to TrueType (glyf) outlines.
# stb_truetype (used by ImGui) doesn't handle CID-keyed CFF fonts properly.
from fontTools.pens.cu2quPen import Cu2QuPen
from fontTools.pens.ttGlyphPen import TTGlyphPen
from fontTools.ttLib import newTable
tmp_otf = OUTPUT_FONT + '.tmp.otf'
os.rename(OUTPUT_FONT, tmp_otf)
conv = TTFont(tmp_otf)
if 'CFF ' in conv:
print("Converting CFF -> TrueType outlines...")
glyphOrder = conv.getGlyphOrder()
glyphSet = conv.getGlyphSet()
glyf_table = newTable("glyf")
glyf_table.glyphs = {}
glyf_table.glyphOrder = glyphOrder
loca_table = newTable("loca")
from fontTools.ttLib.tables._g_l_y_f import Glyph as TTGlyph
for gname in glyphOrder:
try:
ttPen = TTGlyphPen(glyphSet)
cu2quPen = Cu2QuPen(ttPen, max_err=1.0, reverse_direction=True)
glyphSet[gname].draw(cu2quPen)
glyf_table.glyphs[gname] = ttPen.glyph()
except Exception:
glyf_table.glyphs[gname] = TTGlyph()
del conv['CFF ']
if 'VORG' in conv:
del conv['VORG']
conv['glyf'] = glyf_table
conv['loca'] = loca_table
conv['head'].indexToLocFormat = 1
if 'maxp' in conv:
conv['maxp'].version = 0x00010000
conv.sfntVersion = "\x00\x01\x00\x00"
conv.save(OUTPUT_FONT)
conv.close()
os.remove(tmp_otf)
size = os.path.getsize(OUTPUT_FONT)
print(f"\nOutput: {OUTPUT_FONT}")
print(f"Size: {size / 1024:.0f} KB")
# Verify
verify = TTFont(OUTPUT_FONT)
verify_cmap = set(verify.getBestCmap().keys())
still_missing = needed - verify_cmap
print(f"Verified glyphs in subset: {len(verify_cmap)}")
if still_missing:
# These are chars not in the source font - expected for some Hangul/Hiragana
print(f"Not coverable by this font: {len(still_missing)} (need additional font)")
for cp in sorted(still_missing)[:10]:
print(f" U+{cp:04X} {chr(cp)}")
else:
print("All needed characters are covered!")
-64
View File
@@ -1,64 +0,0 @@
#!/usr/bin/env python3
"""Check which characters in translation files fall outside the font glyph ranges."""
import json
import unicodedata
import glob
import os
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
LANG_DIR = os.path.join(SCRIPT_DIR, '..', 'res', 'lang')
# Glyph ranges from typography.cpp (regular font + CJK merge)
RANGES = [
# Regular font ranges
(0x0020, 0x00FF), # Basic Latin + Latin-1 Supplement
(0x0100, 0x024F), # Latin Extended-A + B
(0x0370, 0x03FF), # Greek and Coptic
(0x0400, 0x04FF), # Cyrillic
(0x0500, 0x052F), # Cyrillic Supplement
(0x2000, 0x206F), # General Punctuation
(0x2190, 0x21FF), # Arrows
(0x2200, 0x22FF), # Mathematical Operators
(0x2600, 0x26FF), # Miscellaneous Symbols
# CJK ranges
(0x2E80, 0x2FDF), # CJK Radicals
(0x3000, 0x30FF), # CJK Symbols, Hiragana, Katakana
(0x3100, 0x312F), # Bopomofo
(0x31F0, 0x31FF), # Katakana Extensions
(0x3400, 0x4DBF), # CJK Extension A
(0x4E00, 0x9FFF), # CJK Unified Ideographs
(0xAC00, 0xD7AF), # Hangul Syllables
(0xFF00, 0xFFEF), # Fullwidth Forms
]
def in_ranges(cp):
return any(lo <= cp <= hi for lo, hi in RANGES)
for path in sorted(glob.glob(os.path.join(LANG_DIR, '*.json'))):
lang = os.path.basename(path).replace('.json', '')
with open(path, 'r', encoding='utf-8') as f:
data = json.load(f)
missing = {}
for key, val in data.items():
if not isinstance(val, str):
continue
for c in val:
cp = ord(c)
if cp > 0x7F and not in_ranges(cp):
if c not in missing:
missing[c] = []
missing[c].append(key)
if missing:
print(f"\n=== {lang}.json: {len(missing)} missing characters ===")
for c in sorted(missing, key=lambda x: ord(x)):
cp = ord(c)
name = unicodedata.name(c, 'UNKNOWN')
keys = missing[c][:3]
key_str = ', '.join(keys)
if len(missing[c]) > 3:
key_str += f' (+{len(missing[c])-3} more)'
print(f" U+{cp:04X} {c} ({name}) — used in: {key_str}")
else:
print(f"=== {lang}.json: OK (all characters covered) ===")
-47
View File
@@ -1,47 +0,0 @@
#!/usr/bin/env python3
"""Check which characters needed by translations are missing from bundled fonts."""
import json
import os
from fontTools.ttLib import TTFont
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
FONTS_DIR = os.path.join(ROOT, 'res', 'fonts')
LANG_DIR = os.path.join(ROOT, 'res', 'lang')
# Load font cmaps
cjk = TTFont(os.path.join(FONTS_DIR, 'NotoSansCJK-Subset.ttf'))
cjk_cmap = set(cjk.getBestCmap().keys())
ubuntu = TTFont(os.path.join(FONTS_DIR, 'Ubuntu-R.ttf'))
ubuntu_cmap = set(ubuntu.getBestCmap().keys())
combined = cjk_cmap | ubuntu_cmap
print(f"CJK subset font glyphs: {len(cjk_cmap)}")
print(f"Ubuntu font glyphs: {len(ubuntu_cmap)}")
print(f"Combined: {len(combined)}")
print()
for lang in ['zh', 'ja', 'ko', 'ru', 'de', 'es', 'fr', 'pt']:
path = os.path.join(LANG_DIR, f'{lang}.json')
if not os.path.exists(path):
continue
with open(path, 'r', encoding='utf-8') as f:
data = json.load(f)
needed = set()
for v in data.values():
if isinstance(v, str):
for c in v:
needed.add(ord(c))
missing = sorted(needed - combined)
if missing:
print(f"{lang}.json: {len(needed)} chars needed, {len(missing)} MISSING")
for cp in missing[:20]:
c = chr(cp)
print(f" U+{cp:04X} {c}")
if len(missing) > 20:
print(f" ... and {len(missing) - 20} more")
else:
print(f"{lang}.json: OK ({len(needed)} chars, all covered)")
-214
View File
@@ -1,214 +0,0 @@
#!/usr/bin/env python3
"""Convert CJK subset from CID-keyed CFF/OTF to TrueType/TTF.
stb_truetype (used by ImGui) doesn't handle CID-keyed CFF fonts properly,
so we need glyf-based TrueType outlines instead.
Two approaches:
1. Direct CFF->TTF conversion via cu2qu (fontTools)
2. Download NotoSansSC-Regular.ttf (already TTF) and re-subset
This script tries approach 1 first, falls back to approach 2.
"""
import os
import sys
import json
import glob
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
PROJECT_ROOT = os.path.dirname(SCRIPT_DIR)
FONT_DIR = os.path.join(PROJECT_ROOT, "res", "fonts")
LANG_DIR = os.path.join(PROJECT_ROOT, "res", "lang")
SRC_OTF = os.path.join(FONT_DIR, "NotoSansCJK-Subset.otf")
DST_TTF = os.path.join(FONT_DIR, "NotoSansCJK-Subset.ttf")
def get_needed_codepoints():
"""Collect all unique codepoints from CJK translation files."""
codepoints = set()
for lang_file in glob.glob(os.path.join(LANG_DIR, "*.json")):
with open(lang_file, "r", encoding="utf-8") as f:
data = json.load(f)
for value in data.values():
if isinstance(value, str):
for ch in value:
cp = ord(ch)
# Include CJK + Hangul + fullwidth + CJK symbols/kana
if cp >= 0x2E80:
codepoints.add(cp)
return codepoints
def convert_cff_to_ttf():
"""Convert existing OTF/CFF font to TTF using fontTools cu2qu."""
from fontTools.ttLib import TTFont
from fontTools.pens.cu2quPen import Cu2QuPen
from fontTools.pens.ttGlyphPen import TTGlyphPen
print(f"Loading {SRC_OTF}...")
font = TTFont(SRC_OTF)
# Verify it's CFF
if "CFF " not in font:
print("Font is not CFF, skipping conversion")
return False
cff = font["CFF "]
top = cff.cff.topDictIndex[0]
print(f"ROS: {getattr(top, 'ROS', None)}")
print(f"CID-keyed: {getattr(top, 'FDSelect', None) is not None}")
glyphOrder = font.getGlyphOrder()
print(f"Glyphs: {len(glyphOrder)}")
# Use fontTools' built-in otf2ttf if available
try:
from fontTools.otf2ttf import otf_to_ttf
otf_to_ttf(font)
font.save(DST_TTF)
print(f"Saved TTF: {DST_TTF} ({os.path.getsize(DST_TTF)} bytes)")
font.close()
return True
except ImportError:
pass
# Manual conversion using cu2qu
print("Using manual CFF->TTF conversion with cu2qu...")
from fontTools.pens.recordingPen import RecordingPen
from fontTools.pens.pointPen import SegmentToPointPen
from fontTools import ttLib
from fontTools.ttLib.tables._g_l_y_f import Glyph as TTGlyph
import struct
# Get glyph set
glyphSet = font.getGlyphSet()
# Create new glyf table
from fontTools.ttLib import newTable
glyf_table = newTable("glyf")
glyf_table.glyphs = {}
glyf_table.glyphOrder = glyphOrder
loca_table = newTable("loca")
max_error = 1.0 # em-units tolerance for cubic->quadratic
for gname in glyphOrder:
try:
ttPen = TTGlyphPen(glyphSet)
cu2quPen = Cu2QuPen(ttPen, max_err=max_error, reverse_direction=True)
glyphSet[gname].draw(cu2quPen)
glyf_table.glyphs[gname] = ttPen.glyph()
except Exception as e:
# Fallback: empty glyph
glyf_table.glyphs[gname] = TTGlyph()
# Replace CFF with glyf
del font["CFF "]
if "VORG" in font:
del font["VORG"]
font["glyf"] = glyf_table
font["loca"] = loca_table
# Add required tables for TTF
# head table needs indexToLocFormat
font["head"].indexToLocFormat = 1 # long format
# Create maxp for TrueType
if "maxp" in font:
font["maxp"].version = 0x00010000
# Update sfntVersion
font.sfntVersion = "\x00\x01\x00\x00" # TrueType
font.save(DST_TTF)
print(f"Saved TTF: {DST_TTF} ({os.path.getsize(DST_TTF)} bytes)")
font.close()
return True
def download_and_subset():
"""Download NotoSansSC-Regular.ttf and subset it."""
import urllib.request
from fontTools.ttLib import TTFont
from fontTools import subset
# Google Fonts provides static TTF files
url = "https://github.com/notofonts/noto-cjk/raw/main/Sans/SubsetOTF/SC/NotoSansSC-Regular.otf"
# Actually, we want TTF. Let's try the variable font approach.
# Or better: use google-fonts API for static TTF
# NotoSansSC static TTF from Google Fonts CDN
tmp_font = "/tmp/NotoSansSC-Regular.ttf"
if not os.path.exists(tmp_font):
print(f"Downloading NotoSansSC-Regular.ttf...")
url = "https://github.com/notofonts/noto-cjk/raw/main/Sans/OTC/NotoSansCJK-Regular.ttc"
# This is a TTC (font collection), too large.
# Use the OTF we already have and convert it.
return False
print(f"Using {tmp_font}")
font = TTFont(tmp_font)
cmap = font.getBestCmap()
print(f"Source has {len(cmap)} cmap entries")
needed = get_needed_codepoints()
print(f"Need {len(needed)} CJK codepoints")
# Subset
subsetter = subset.Subsetter()
subsetter.populate(unicodes=needed)
subsetter.subset(font)
font.save(DST_TTF)
print(f"Saved: {DST_TTF} ({os.path.getsize(DST_TTF)} bytes)")
font.close()
return True
def verify_result():
"""Verify the output TTF has glyf outlines and correct characters."""
from fontTools.ttLib import TTFont
font = TTFont(DST_TTF)
cmap = font.getBestCmap()
print(f"\n--- Verification ---")
print(f"Format: {font.sfntVersion!r}")
print(f"Has glyf: {'glyf' in font}")
print(f"Has CFF: {'CFF ' in font}")
print(f"Cmap entries: {len(cmap)}")
# Check key characters
test_chars = {
"历": 0x5386, "史": 0x53F2, # Chinese: history
"概": 0x6982, "述": 0x8FF0, # Chinese: overview
"设": 0x8BBE, "置": 0x7F6E, # Chinese: settings
}
for name, cp in test_chars.items():
status = "YES" if cp in cmap else "NO"
print(f" {name} (U+{cp:04X}): {status}")
size = os.path.getsize(DST_TTF)
print(f"File size: {size} bytes ({size/1024:.1f} KB)")
font.close()
if __name__ == "__main__":
print("=== CJK Font CFF -> TTF Converter ===\n")
if convert_cff_to_ttf():
verify_result()
else:
print("Direct conversion failed, trying download approach...")
if download_and_subset():
verify_result()
else:
print("ERROR: Could not convert font")
sys.exit(1)
+1 -1
View File
@@ -8,7 +8,7 @@ set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BUILD_DIR="${SCRIPT_DIR}/build/linux"
APPDIR="${BUILD_DIR}/AppDir"
VERSION="1.2.0"
VERSION="1.1.1"
# Colors
GREEN='\033[0;32m'
-36
View File
@@ -1,36 +0,0 @@
#!/usr/bin/env python3
"""Fix mojibake en-dash (and other common patterns) in translation JSON files."""
import os
import glob
LANG_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'res', 'lang')
# Common mojibake patterns: UTF-8 bytes interpreted as Latin-1
MOJIBAKE_FIXES = {
'\u00e2\u0080\u0093': '\u2013', # en dash
'\u00e2\u0080\u0094': '\u2014', # em dash
'\u00e2\u0080\u0099': '\u2019', # right single quote
'\u00e2\u0080\u009c': '\u201c', # left double quote
'\u00e2\u0080\u009d': '\u201d', # right double quote
'\u00e2\u0080\u00a6': '\u2026', # ellipsis
}
total_fixed = 0
for path in sorted(glob.glob(os.path.join(LANG_DIR, '*.json'))):
with open(path, 'r', encoding='utf-8') as f:
raw = f.read()
original = raw
for bad, good in MOJIBAKE_FIXES.items():
if bad in raw:
count = raw.count(bad)
raw = raw.replace(bad, good)
lang = os.path.basename(path)
print(f" {lang}: fixed {count} x {repr(good)}")
total_fixed += count
if raw != original:
with open(path, 'w', encoding='utf-8') as f:
f.write(raw)
print(f"\nTotal fixes: {total_fixed}")
+1 -1
View File
@@ -7,7 +7,7 @@ set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BUILD_DIR="${SCRIPT_DIR}/build/linux"
VERSION="1.2.0"
VERSION="1.1.1"
# Colors for output
RED='\033[0;31m'
+54 -255
View File
@@ -1,9 +1,6 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
//
// app.cpp — Main application: init, shutdown, ImGui render loop, NavPage
// dispatch, dialog rendering, and frame-level state management.
#include "app.h"
#include "config/version.h"
@@ -35,9 +32,6 @@
#include "ui/windows/block_info_dialog.h"
#include "ui/windows/export_all_keys_dialog.h"
#include "ui/windows/export_transactions_dialog.h"
#include "ui/windows/address_label_dialog.h"
#include "ui/windows/address_transfer_dialog.h"
#include "ui/windows/bootstrap_download_dialog.h"
#include "ui/windows/console_tab.h"
#include "ui/pages/settings_page.h"
#include "ui/theme.h"
@@ -319,8 +313,7 @@ void App::update()
// Track user interaction for auto-lock
if (io.MouseDelta.x != 0 || io.MouseDelta.y != 0 ||
io.MouseClicked[0] || io.MouseClicked[1] ||
io.InputQueueCharacters.Size > 0 ||
ImGui::IsAnyItemActive()) {
io.InputQueueCharacters.Size > 0) {
last_interaction_ = std::chrono::steady_clock::now();
}
@@ -347,10 +340,7 @@ void App::update()
}
// Update timers
core_timer_ += io.DeltaTime;
address_timer_ += io.DeltaTime;
transaction_timer_ += io.DeltaTime;
peer_timer_ += io.DeltaTime;
refresh_timer_ += io.DeltaTime;
price_timer_ += io.DeltaTime;
fast_refresh_timer_ += io.DeltaTime;
tx_age_timer_ += io.DeltaTime;
@@ -598,41 +588,20 @@ void App::update()
transactions_dirty_ = true;
addresses_dirty_ = true;
last_tx_block_height_ = -1;
core_timer_ = active_core_interval_;
transaction_timer_ = active_tx_interval_;
address_timer_ = active_addr_interval_;
refresh_timer_ = REFRESH_INTERVAL;
}
};
});
}
// Per-category refresh with tab-aware intervals
// Regular refresh every 5 seconds
// Skip when wallet is locked — same reason as above.
if (state_.connected && !state_.isLocked()) {
if (core_timer_ >= active_core_interval_) {
core_timer_ = 0.0f;
refreshCoreData();
}
// Skip balance/tx/address refresh during warmup — RPC calls fail with -28
if (!state_.warming_up) {
if (transaction_timer_ >= active_tx_interval_) {
transaction_timer_ = 0.0f;
refreshTransactionData();
}
if (address_timer_ >= active_addr_interval_) {
address_timer_ = 0.0f;
refreshAddressData();
}
}
if (peer_timer_ >= active_peer_interval_) {
peer_timer_ = 0.0f;
refreshPeerInfo();
}
} else if (core_timer_ >= active_core_interval_) {
core_timer_ = 0.0f;
if (!connection_in_progress_ &&
wizard_phase_ == WizardPhase::None &&
!bootstrap_downloading_) {
if (refresh_timer_ >= REFRESH_INTERVAL) {
refresh_timer_ = 0.0f;
if (state_.connected && !state_.isLocked()) {
refreshData();
} else if (!connection_in_progress_ &&
wizard_phase_ == WizardPhase::None) {
tryConnect();
}
}
@@ -647,7 +616,7 @@ void App::update()
// Keyboard shortcut: Ctrl+, to open Settings page
if (io.KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_Comma)) {
setCurrentPage(ui::NavPage::Settings);
current_page_ = ui::NavPage::Settings;
}
// Keyboard shortcut: Ctrl+Left/Right to cycle themes
@@ -1032,8 +1001,7 @@ void App::render()
bool prevCollapsed = sidebar_collapsed_;
{
PERF_SCOPE("Render.Sidebar");
ui::RenderSidebar(current_page_, sidebarW, sidebarH, sbStatus, sidebar_collapsed_,
state_.isLocked());
ui::RenderSidebar(current_page_, sidebarW, sidebarH, sbStatus, sidebar_collapsed_);
}
if (sbStatus.exitClicked) {
requestQuit();
@@ -1048,7 +1016,7 @@ void App::render()
// Page transition: detect change, ramp alpha
if (current_page_ != prev_page_) {
page_alpha_ = (ui::effects::isLowSpecMode() || (settings_ && settings_->getReduceMotion())) ? 1.0f : 0.0f;
page_alpha_ = ui::effects::isLowSpecMode() ? 1.0f : 0.0f;
prev_page_ = current_page_;
}
if (page_alpha_ < 1.0f) {
@@ -1085,17 +1053,16 @@ void App::render()
int fgVtxStart = fgDL->VtxBuffer.Size;
// ---------------------------------------------------------------
// Loading overlay — show only on tabs that truly need wallet data.
// Tabs that work without full daemon connection:
// Console, Peers, Settings, Market, Mining
// Tabs that need balance/transaction data (show overlay):
// Overview, Send, Receive, History
// Loading overlay — show only while daemon is not yet connected.
// Once connected, all tabs render normally; individual tabs show
// inline sync-progress indicators when the chain is still syncing.
// Pages that remain accessible even without a connection:
// Console, Peers, Settings
// ---------------------------------------------------------------
bool pageNeedsWalletData = (current_page_ == ui::NavPage::Overview ||
current_page_ == ui::NavPage::Send ||
current_page_ == ui::NavPage::Receive ||
current_page_ == ui::NavPage::History);
bool daemonReady = state_.connected && !state_.warming_up;
bool pageNeedsDaemon = (current_page_ != ui::NavPage::Console &&
current_page_ != ui::NavPage::Peers &&
current_page_ != ui::NavPage::Settings);
bool daemonReady = state_.connected; // don't gate on sync state
// Don't show lock screen while pool mining — xmrig runs independently
// of the wallet and locking would block the mining UI needlessly.
@@ -1103,24 +1070,7 @@ void App::render()
if (state_.isLocked() && !poolMiningActive) {
// Lock screen — covers tab content just like the loading overlay
renderLockScreen();
} else if (pageNeedsWalletData && state_.warming_up) {
// Daemon is reachable but still initializing — show warmup overlay
// only on wallet-data tabs. Other tabs render normally.
lock_screen_was_visible_ = false;
renderLoadingOverlay(contentH);
} else if (pageNeedsWalletData && (!daemonReady || (state_.connected && !state_.encryption_state_known))) {
// Track how long we've been waiting for encryption state
if (state_.connected && !state_.encryption_state_known) {
encryption_check_timer_ += ImGui::GetIO().DeltaTime;
if (encryption_check_timer_ >= 15.0f) {
DEBUG_LOGF("[App] Encryption state check timed out after 15s — assuming unencrypted\n");
state_.encryption_state_known = true;
state_.encrypted = false;
encryption_check_timer_ = 0.0f;
}
} else {
encryption_check_timer_ = 0.0f;
}
} else if (pageNeedsDaemon && (!daemonReady || (state_.connected && !state_.encryption_state_known))) {
// Reset lock screen focus flag so it auto-focuses next time
lock_screen_was_visible_ = false;
// Show loading overlay instead of tab content
@@ -1351,16 +1301,7 @@ void App::render()
// Export transactions to CSV dialog (triggered from File menu)
ui::ExportTransactionsDialog::render(this);
// Address label/icon editor
ui::AddressLabelDialog::render();
// Address-to-address transfer confirmation
ui::AddressTransferDialog::render();
// Bootstrap download from settings
ui::BootstrapDownloadDialog::render();
// Windows Defender antivirus help dialog
renderAntivirusHelpDialog();
@@ -1443,34 +1384,25 @@ void App::renderStatusBar()
// Connection status
float dotOpacity = S.drawElement("components.status-bar", "connection-dot").opacity;
if (dotOpacity < 0.0f) dotOpacity = 1.0f;
if (state_.warming_up) {
ImGui::PushFont(ui::material::Type().iconSmall());
ImGui::TextColored(ImVec4(1.0f, 0.7f, 0.0f, dotOpacity), ICON_MD_CIRCLE);
ImGui::PopFont();
ImGui::SameLine(0, sbIconTextGap);
// Show truncated warmup status (e.g. "Activating best chain... (Block 12345)")
const char* warmupText = state_.warmup_status.empty()
? TR("sb_warming_up") : state_.warmup_status.c_str();
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.0f, 1.0f), "%s", warmupText);
} else if (state_.connected) {
if (state_.connected) {
ImGui::PushFont(ui::material::Type().iconSmall());
ImGui::TextColored(ImVec4(0.2f, 0.8f, 0.2f, dotOpacity), ICON_MD_CIRCLE);
ImGui::PopFont();
ImGui::SameLine(0, sbIconTextGap);
ImGui::Text("%s", TR("connected"));
ImGui::Text("Connected");
} else {
ImGui::PushFont(ui::material::Type().iconSmall());
ImGui::TextColored(ImVec4(0.8f, 0.2f, 0.2f, dotOpacity), ICON_MD_CIRCLE);
ImGui::PopFont();
ImGui::SameLine(0, sbIconTextGap);
ImGui::Text("%s", TR("disconnected"));
ImGui::Text("Disconnected");
}
// Block height
ImGui::SameLine(0, sbSectionGap);
ImGui::TextDisabled("|");
ImGui::SameLine(0, sbSeparatorGap);
ImGui::Text(TR("sb_block"), state_.sync.blocks);
ImGui::Text("Block: %d", state_.sync.blocks);
// Sync status or peer count
ImGui::SameLine(0, sbSectionGap);
@@ -1480,62 +1412,22 @@ void App::renderStatusBar()
// Show rescan progress (takes priority over sync)
// Use animated dots if progress is unknown (0%)
if (state_.sync.rescan_progress > 0.01f) {
ImGui::TextColored(ImVec4(0.6f, 0.8f, 1.0f, 1.0f), TR("sb_rescanning_pct"),
ImGui::TextColored(ImVec4(0.6f, 0.8f, 1.0f, 1.0f), "Rescanning %.0f%%",
state_.sync.rescan_progress * 100.0f);
} else {
// Animated "Rescanning..." with pulsing dots
int dots = (int)(ImGui::GetTime() * 2.0f) % 4;
const char* dotStr = (dots == 0) ? "." : (dots == 1) ? ".." : (dots == 2) ? "..." : "";
ImGui::TextColored(ImVec4(0.6f, 0.8f, 1.0f, 1.0f), "%s%s", TR("sb_rescanning"), dotStr);
ImGui::TextColored(ImVec4(0.6f, 0.8f, 1.0f, 1.0f), "Rescanning%s", dotStr);
}
} else if (state_.sync.syncing) {
int chainTip = state_.longestchain > 0 ? state_.longestchain : state_.sync.headers;
int blocksLeft = chainTip - state_.sync.blocks;
if (blocksLeft < 0) blocksLeft = 0;
// Calculate sync speed (blocks/sec) using a smoothed rolling average
static int s_prev_blocks = 0;
static double s_prev_time = 0.0;
static double s_blocks_per_sec = 0.0;
double now = ImGui::GetTime();
if (s_prev_time == 0.0) {
// First sample — seed baseline, don't compute rate yet
s_prev_blocks = state_.sync.blocks;
s_prev_time = now;
} else if (state_.sync.blocks > s_prev_blocks) {
double dt = now - s_prev_time;
if (dt > 0.5) {
double raw = (state_.sync.blocks - s_prev_blocks) / dt;
// Exponential smoothing (alpha ~0.3 per update)
s_blocks_per_sec = s_blocks_per_sec > 0.0
? s_blocks_per_sec * 0.7 + raw * 0.3
: raw;
s_prev_blocks = state_.sync.blocks;
s_prev_time = now;
}
} else if (now - s_prev_time > 10.0) {
// No new blocks for 10s — decay the displayed rate
s_blocks_per_sec *= 0.5;
s_prev_time = now;
}
if (s_blocks_per_sec > 0.1) {
int eta_sec = (int)(blocksLeft / s_blocks_per_sec);
char eta[32];
if (eta_sec >= 3600)
snprintf(eta, sizeof(eta), "%dh %dm", eta_sec / 3600, (eta_sec % 3600) / 60);
else if (eta_sec >= 60)
snprintf(eta, sizeof(eta), "%dm %ds", eta_sec / 60, eta_sec % 60);
else
snprintf(eta, sizeof(eta), "%ds", eta_sec);
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.0f, 1.0f), TR("sb_syncing_eta"),
state_.sync.verification_progress * 100.0, blocksLeft, s_blocks_per_sec, eta);
} else {
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.0f, 1.0f), TR("sb_syncing_basic"),
state_.sync.verification_progress * 100.0, blocksLeft);
}
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.0f, 1.0f), "Syncing %.1f%% (%d left)",
state_.sync.verification_progress * 100.0, blocksLeft);
} else if (state_.connected) {
ImGui::Text(TR("sb_peers"), state_.peers.size());
ImGui::Text("Peers: %zu", state_.peers.size());
}
// Network hashrate (if connected and have data)
@@ -1544,13 +1436,13 @@ void App::renderStatusBar()
ImGui::TextDisabled("|");
ImGui::SameLine(0, sbSeparatorGap);
if (state_.mining.networkHashrate >= 1e9) {
ImGui::Text(TR("sb_net_ghs"), state_.mining.networkHashrate / 1e9);
ImGui::Text("Net: %.2f GH/s", state_.mining.networkHashrate / 1e9);
} else if (state_.mining.networkHashrate >= 1e6) {
ImGui::Text(TR("sb_net_mhs"), state_.mining.networkHashrate / 1e6);
ImGui::Text("Net: %.2f MH/s", state_.mining.networkHashrate / 1e6);
} else if (state_.mining.networkHashrate >= 1e3) {
ImGui::Text(TR("sb_net_khs"), state_.mining.networkHashrate / 1e3);
ImGui::Text("Net: %.2f KH/s", state_.mining.networkHashrate / 1e3);
} else {
ImGui::Text(TR("sb_net_hs"), state_.mining.networkHashrate);
ImGui::Text("Net: %.1f H/s", state_.mining.networkHashrate);
}
}
@@ -1567,7 +1459,7 @@ void App::renderStatusBar()
double displayHashrate = state_.pool_mining.xmrig_running
? state_.pool_mining.hashrate_10s
: state_.mining.localHashrate;
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), TR("sb_mining_hs"),
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "%.1f H/s",
displayHashrate);
}
@@ -1583,7 +1475,7 @@ void App::renderStatusBar()
ImGui::SameLine(0, sbIconTextGap);
int dots = (int)(ImGui::GetTime() * 2.0f) % 4;
const char* dotStr = (dots == 0) ? "." : (dots == 1) ? ".." : (dots == 2) ? "..." : "";
ImGui::TextColored(ImVec4(0.6f, 0.8f, 1.0f, 1.0f), "%s%s", TR("sb_importing_keys"), dotStr);
ImGui::TextColored(ImVec4(0.6f, 0.8f, 1.0f, 1.0f), "Importing keys%s", dotStr);
}
// Right side: version always at far right, connection status to its left.
@@ -1605,7 +1497,7 @@ void App::renderStatusBar()
ImGui::SameLine(statusX);
ImGui::TextDisabled("%s", connection_status_.c_str());
} else if (!daemon_status_.empty() && daemon_status_.find("Error") != std::string::npos) {
const char* errText = TR("sb_daemon_not_found");
const char* errText = "Daemon not found";
float statusW = ImGui::CalcTextSize(errText).x;
float statusX = versionX - statusW - gap;
ImGui::SameLine(statusX);
@@ -1991,11 +1883,7 @@ void App::renderAntivirusHelpDialog()
void App::refreshNow()
{
// Trigger immediate refresh on all categories
core_timer_ = active_core_interval_;
transaction_timer_ = active_tx_interval_;
address_timer_ = active_addr_interval_;
peer_timer_ = active_peer_interval_;
refresh_timer_ = REFRESH_INTERVAL; // Trigger immediate refresh
transactions_dirty_ = true; // Force transaction list update
addresses_dirty_ = true; // Force address/balance update
last_tx_block_height_ = -1; // Reset tx cache
@@ -2018,7 +1906,7 @@ void App::handlePaymentURI(const std::string& uri)
pending_label_ = payment.label;
// Switch to Send page
setCurrentPage(ui::NavPage::Send);
current_page_ = ui::NavPage::Send;
// Notify user
std::string msg = "Payment request loaded";
@@ -2045,7 +1933,7 @@ void App::setCurrentTab(int tab) {
ui::NavPage::Settings, // 9 = Settings
};
if (tab >= 0 && tab < static_cast<int>(sizeof(kTabMap)/sizeof(kTabMap[0])))
setCurrentPage(kTabMap[tab]);
current_page_ = kTabMap[tab];
}
bool App::startEmbeddedDaemon()
@@ -2062,12 +1950,12 @@ bool App::startEmbeddedDaemon()
// Try to extract embedded resources if available
if (resources::hasEmbeddedResources()) {
DEBUG_LOGF("Extracting embedded Sapling params...\n");
daemon_status_ = TR("sb_extracting_sapling");
daemon_status_ = "Extracting Sapling parameters...";
resources::extractEmbeddedResources();
// Check again after extraction
if (!rpc::Connection::verifySaplingParams()) {
daemon_status_ = TR("sb_sapling_failed");
daemon_status_ = "Failed to extract Sapling parameters.";
DEBUG_LOGF("Sapling params still not found after extraction!\n");
DEBUG_LOGF("Expected location: %s\n", rpc::Connection::getSaplingParamsDir().c_str());
return false;
@@ -2113,7 +2001,7 @@ bool App::startEmbeddedDaemon()
if (copied && rpc::Connection::verifySaplingParams()) {
DEBUG_LOGF("Sapling params copied from exe directory successfully\n");
} else {
daemon_status_ = TR("sb_sapling_not_found");
daemon_status_ = "Sapling parameters not found. They should be in: " + rpc::Connection::getSaplingParamsDir();
DEBUG_LOGF("Sapling params not found and no embedded resources available!\n");
DEBUG_LOGF("Expected location: %s\n", rpc::Connection::getSaplingParamsDir().c_str());
return false;
@@ -2163,16 +2051,16 @@ bool App::startEmbeddedDaemon()
embedded_daemon_->setStateCallback([this](daemon::EmbeddedDaemon::State state, const std::string& msg) {
switch (state) {
case daemon::EmbeddedDaemon::State::Starting:
daemon_status_ = TR("sb_starting_daemon");
daemon_status_ = "Starting dragonxd...";
break;
case daemon::EmbeddedDaemon::State::Running:
daemon_status_ = TR("sb_dragonxd_running");
daemon_status_ = "dragonxd running";
break;
case daemon::EmbeddedDaemon::State::Stopping:
daemon_status_ = TR("sb_dragonxd_stopping");
daemon_status_ = "Stopping dragonxd...";
break;
case daemon::EmbeddedDaemon::State::Stopped:
daemon_status_ = TR("sb_dragonxd_stopped");
daemon_status_ = "dragonxd stopped";
break;
case daemon::EmbeddedDaemon::State::Error:
daemon_status_ = "Error: " + msg;
@@ -2184,7 +2072,6 @@ bool App::startEmbeddedDaemon()
// Sync debug logging categories from user settings
if (settings_) {
embedded_daemon_->setDebugCategories(settings_->getDebugCategories());
embedded_daemon_->setMaxConnections(settings_->getMaxConnections());
}
return embedded_daemon_->start();
@@ -2362,20 +2249,6 @@ void App::deleteBlockchainData()
}).detach();
}
bool App::stopDaemonForBootstrap()
{
bool wasRunning = isEmbeddedDaemonRunning();
if (wasRunning) {
DEBUG_LOGF("[App] Stopping embedded daemon for bootstrap download...\n");
if (rpc_ && rpc_->isConnected()) {
try { rpc_->call("stop"); } catch (...) {}
rpc_->disconnect();
}
onDisconnected("Bootstrap");
}
return wasRunning;
}
double App::getDaemonMemoryUsageMB() const
{
// If we have an embedded daemon with a tracked process handle, use it
@@ -2503,14 +2376,11 @@ void App::renderShutdownScreen()
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.06f, 0.06f, 0.08f, 0.92f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
ImGuiWindowFlags shutdownFlags =
ImGui::Begin("##ShutdownOverlay", nullptr,
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoSavedSettings;
// Allow input after 10s so Force Quit button is clickable
if (shutdown_timer_ < 10.0f)
shutdownFlags |= ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoNav;
ImGui::Begin("##ShutdownOverlay", nullptr, shutdownFlags);
ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoNav |
ImGuiWindowFlags_NoSavedSettings);
ImDrawList* dl = ImGui::GetWindowDrawList();
// Convert local centre to screen coords for draw-list primitives
@@ -2608,63 +2478,6 @@ void App::renderShutdownScreen()
ImGui::PopFont();
}
// -------------------------------------------------------------------
// 4b. Force Quit button — appears after 10 seconds
// -------------------------------------------------------------------
if (shutdown_timer_ >= 10.0f) {
ImGui::Spacing();
ImGui::Spacing();
const char* forceLabel = TR("force_quit");
ImVec2 btnSize(ImGui::CalcTextSize(forceLabel).x + 32.0f, 0);
ImGui::SetCursorPosX(cx - btnSize.x * 0.5f);
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.6f, 0.15f, 0.15f, 0.9f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.75f, 0.2f, 0.2f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.5f, 0.1f, 0.1f, 1.0f));
if (ImGui::Button(forceLabel, btnSize)) {
force_quit_confirm_ = true;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("%s", TR("force_quit_warning"));
}
ImGui::PopStyleColor(3);
}
// Force Quit confirmation popup
if (force_quit_confirm_) {
ImGui::OpenPopup("##ForceQuitConfirm");
force_quit_confirm_ = false;
}
ImVec2 popupCenter(wp.x + vp_size.x * 0.5f, wp.y + vp_size.y * 0.5f);
ImGui::SetNextWindowPos(popupCenter, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
if (ImGui::BeginPopupModal("##ForceQuitConfirm", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar)) {
ImGui::PushFont(Type().subtitle1());
ImGui::TextUnformatted(TR("force_quit_confirm_title"));
ImGui::PopFont();
ImGui::Spacing();
ImGui::TextUnformatted(TR("force_quit_confirm_msg"));
ImGui::Spacing();
ImGui::Spacing();
float btnW = 120.0f;
float totalW = btnW * 2 + ImGui::GetStyle().ItemSpacing.x;
ImGui::SetCursorPosX((ImGui::GetWindowWidth() - totalW) * 0.5f);
if (ImGui::Button(TR("cancel"), ImVec2(btnW, 0))) {
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.6f, 0.15f, 0.15f, 0.9f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.75f, 0.2f, 0.2f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.5f, 0.1f, 0.1f, 1.0f));
if (ImGui::Button(TR("force_quit_yes"), ImVec2(btnW, 0))) {
DEBUG_LOGF("Force quit confirmed by user after %.0fs\n", shutdown_timer_);
shutdown_complete_ = true;
ImGui::CloseCurrentPopup();
}
ImGui::PopStyleColor(3);
ImGui::EndPopup();
}
ImGui::Spacing();
ImGui::Spacing();
@@ -2827,20 +2640,6 @@ void App::renderLoadingOverlay(float contentH)
curY += ts.y + gap;
}
// -------------------------------------------------------------------
// 2b. Warmup description (subtitle explaining what's happening)
// -------------------------------------------------------------------
if (state_.warming_up && !state_.warmup_description.empty()) {
const char* descText = state_.warmup_description.c_str();
ImFont* capFont = Type().caption();
if (!capFont) capFont = ImGui::GetFont();
ImVec2 ts = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0.0f, descText);
dl->AddText(capFont, capFont->LegacySize,
ImVec2(wp.x + cx - ts.x * 0.5f, curY),
IM_COL32(160, 160, 160, 200), descText);
curY += ts.y + gap;
}
// -------------------------------------------------------------------
// 3. Sync progress bar (if connected and syncing)
// -------------------------------------------------------------------
@@ -3105,7 +2904,7 @@ void App::restartDaemon()
}
DEBUG_LOGF("[App] Restarting embedded daemon...\n");
connection_status_ = TR("sb_restarting_daemon");
connection_status_ = "Restarting daemon...";
// Disconnect RPC so the loading overlay appears
if (rpc_ && rpc_->isConnected()) {
+14 -60
View File
@@ -202,16 +202,6 @@ public:
void unfavoriteAddress(const std::string& addr);
bool isAddressFavorite(const std::string& addr) const;
// Address metadata (labels, icons, custom ordering)
void setAddressLabel(const std::string& addr, const std::string& label);
void setAddressIcon(const std::string& addr, const std::string& icon);
std::string getAddressLabel(const std::string& addr) const;
std::string getAddressIcon(const std::string& addr) const;
int getAddressSortOrder(const std::string& addr) const;
void setAddressSortOrder(const std::string& addr, int order);
int getNextSortOrder() const;
void swapAddressOrder(const std::string& a, const std::string& b);
// Key export/import
void exportPrivateKey(const std::string& address, std::function<void(const std::string&)> callback);
void exportAllKeys(std::function<void(const std::string&)> callback);
@@ -231,19 +221,13 @@ public:
void refreshPeerInfo();
void refreshMarketData();
/// @brief Per-category refresh intervals, adjusted by active tab
struct RefreshIntervals {
float core; // balance + sync status
float transactions; // tx list + enrichment
float addresses; // address lists + balances
float peers; // peer info (0 = disabled)
};
/// @brief Get recommended refresh intervals for a given page
static RefreshIntervals getIntervalsForPage(ui::NavPage page);
// UI navigation
void setCurrentPage(ui::NavPage page);
void setCurrentPage(ui::NavPage page) {
if (page != current_page_) {
current_page_ = page;
if (page == ui::NavPage::Peers) refreshPeerInfo();
}
}
ui::NavPage getCurrentPage() const { return current_page_; }
// Dialog triggers (used by settings page to open modal dialogs)
@@ -273,9 +257,6 @@ public:
void setUseEmbeddedDaemon(bool use) { use_embedded_daemon_ = use; }
void rescanBlockchain(); // restart daemon with -rescan flag
void deleteBlockchainData(); // stop daemon, delete chain data, restart fresh
bool stopDaemonForBootstrap(); // stop daemon + disconnect for bootstrap, returns true if was running
bool isBootstrapDownloading() const { return bootstrap_downloading_; }
void setBootstrapDownloading(bool v) { bootstrap_downloading_ = v; }
// Get daemon memory usage in MB (uses embedded daemon handle if available,
// falls back to platform-level process scan for external daemons)
@@ -381,20 +362,17 @@ private:
// Shutdown state
std::atomic<bool> shutting_down_{false};
std::atomic<bool> shutdown_complete_{false};
std::atomic<bool> refresh_in_progress_{false};
bool address_list_dirty_ = false; // P8: dedup rebuildAddressList
std::string shutdown_status_;
std::thread shutdown_thread_;
float shutdown_timer_ = 0.0f;
bool force_quit_confirm_ = false;
std::chrono::steady_clock::time_point shutdown_start_time_;
// Daemon restart (e.g. after changing debug log categories)
std::atomic<bool> daemon_restarting_{false};
std::thread daemon_restart_thread_;
// Encryption state check timeout
float encryption_check_timer_ = 0.0f;
// UI State
bool quit_requested_ = false;
bool show_demo_window_ = false;
@@ -460,33 +438,16 @@ private:
std::string pending_memo_;
std::string pending_label_;
// Per-category timers (in seconds since last refresh)
float core_timer_ = 0.0f; // balance + sync status
float address_timer_ = 0.0f; // address lists
float transaction_timer_ = 0.0f; // transaction list
float peer_timer_ = 0.0f; // peer info
// Timers (in seconds since last update)
float refresh_timer_ = 0.0f;
float price_timer_ = 0.0f;
float fast_refresh_timer_ = 0.0f; // For mining stats
// Default refresh intervals (seconds)
static constexpr float CORE_INTERVAL_DEFAULT = 5.0f;
static constexpr float ADDRESS_INTERVAL_DEFAULT = 15.0f;
static constexpr float TX_INTERVAL_DEFAULT = 10.0f;
static constexpr float PEER_INTERVAL_DEFAULT = 10.0f;
// Refresh intervals (seconds)
static constexpr float REFRESH_INTERVAL = 5.0f;
static constexpr float PRICE_INTERVAL = 60.0f;
static constexpr float FAST_REFRESH_INTERVAL = 1.0f;
// Active intervals — adjusted by tab priority via applyRefreshPolicy()
float active_core_interval_ = CORE_INTERVAL_DEFAULT;
float active_tx_interval_ = TX_INTERVAL_DEFAULT;
float active_addr_interval_ = ADDRESS_INTERVAL_DEFAULT;
float active_peer_interval_ = PEER_INTERVAL_DEFAULT;
// Per-category refresh guards (prevent worker queue pileup)
std::atomic<bool> core_refresh_in_progress_{false};
std::atomic<bool> address_refresh_in_progress_{false};
std::atomic<bool> tx_refresh_in_progress_{false};
// Mining refresh guard (prevents worker queue pileup)
std::atomic<bool> mining_refresh_in_progress_{false};
int mining_slow_counter_ = 0; // counts fast ticks; fires slow refresh every N
@@ -545,7 +506,6 @@ private:
// First-run wizard state
WizardPhase wizard_phase_ = WizardPhase::None;
std::unique_ptr<util::Bootstrap> bootstrap_;
bool bootstrap_downloading_ = false; // true while settings bootstrap dialog is active
std::string wizard_pending_passphrase_; // held until daemon connects
std::string wizard_saved_passphrase_; // held until PinSetup completes/skipped
@@ -644,17 +604,11 @@ private:
void applyDefaultBanlist();
// Private methods - data refresh
void refreshData(); // Orchestrator: dispatches per-category refreshes
void refreshCoreData(); // Balance + blockchain info (can use fast_worker_)
void refreshAddressData(); // Address lists + balances
void refreshTransactionData(); // Transaction list + z_viewtransaction enrichment
void refreshEncryptionState(); // Wallet encryption/lock state
void refreshBalance(); // Legacy: balance-only refresh (used by specific callers)
void refreshAddresses(); // Legacy: standalone address refresh
void refreshData();
void refreshBalance();
void refreshAddresses();
void refreshPrice();
void refreshWalletEncryptionState();
void applyRefreshPolicy(ui::NavPage page);
bool shouldRefreshTransactions() const;
void checkAutoLock();
void checkIdleMining();
};
+607 -652
View File
File diff suppressed because it is too large Load Diff
+8 -18
View File
@@ -69,13 +69,10 @@ void App::encryptWalletWithPassphrase(const std::string& passphrase) {
}
ui::Notifications::instance().info(
"Wallet encrypted successfully", 5.0f);
"Wallet encrypted successfully");
// The daemon shuts itself down after encryptwallet.
// Update connection_status_ so the loading overlay
// explains why the daemon is restarting.
// The daemon shuts itself down after encryptwallet
if (isUsingEmbeddedDaemon()) {
connection_status_ = TR("restarting_after_encryption");
// Give daemon a moment to shut down, then restart
// (do this off the main thread to avoid stalling the UI)
std::thread([this]() {
@@ -167,13 +164,13 @@ void App::processDeferredEncryption() {
if (pinStored) {
settings_->setPinEnabled(true);
settings_->save();
ui::Notifications::instance().info("Wallet encrypted & PIN set", 5.0f);
ui::Notifications::instance().info("Wallet encrypted & PIN set");
} else {
ui::Notifications::instance().warning(
"Wallet encrypted but PIN vault failed");
}
} else {
ui::Notifications::instance().info("Wallet encrypted successfully", 5.0f);
ui::Notifications::instance().info("Wallet encrypted successfully");
}
// Securely clear deferred state
@@ -267,7 +264,7 @@ void App::unlockWallet(const std::string& passphrase, int timeout) {
state_.unlocked_until = std::time(nullptr) + timeout;
} else {
lock_attempts_++;
lock_error_msg_ = TR("incorrect_passphrase");
lock_error_msg_ = "Incorrect passphrase";
lock_error_timer_ = 3.0f;
memset(lock_passphrase_buf_, 0, sizeof(lock_passphrase_buf_));
@@ -791,17 +788,10 @@ void App::renderLockScreen() {
bool vaultOk = vault_ && vault_->retrieve(pin, passphrase);
if (!vaultOk) {
bool noVault = !vault_ || !vault_->hasVault();
return [this, noVault]() {
return [this]() {
lock_unlock_in_progress_ = false;
if (noVault) {
// Vault file missing — switch to passphrase mode
lock_error_msg_ = TR("pin_not_set");
lock_use_pin_ = false;
} else {
lock_attempts_++;
lock_error_msg_ = TR("incorrect_pin");
}
lock_attempts_++;
lock_error_msg_ = "Incorrect PIN";
lock_error_timer_ = 3.0f;
float baseDelay = ui::schema::UI().drawElement("security", "lockout-base-delay").sizeOr(2.0f);
+2 -8
View File
@@ -1294,10 +1294,7 @@ void App::renderFirstRunWizard() {
// Start daemon + finish wizard immediately
if (!isEmbeddedDaemonRunning() && isUsingEmbeddedDaemon()) {
if (!startEmbeddedDaemon()) {
ui::Notifications::instance().warning(
TR("wizard_daemon_start_failed"));
}
startEmbeddedDaemon();
}
tryConnect();
wizard_phase_ = WizardPhase::Done;
@@ -1316,10 +1313,7 @@ void App::renderFirstRunWizard() {
settings_->setWizardCompleted(true);
settings_->save();
if (!isEmbeddedDaemonRunning() && isUsingEmbeddedDaemon()) {
if (!startEmbeddedDaemon()) {
ui::Notifications::instance().warning(
TR("wizard_daemon_start_failed"));
}
startEmbeddedDaemon();
}
tryConnect();
}
-32
View File
@@ -1,9 +1,6 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
//
// settings.cpp — JSON settings persistence. Loads/saves user preferences
// to ~/.config/ObsidianDragon/settings.json (Linux/macOS) or %APPDATA% (Windows).
#include "settings.h"
#include "version.h"
@@ -126,26 +123,12 @@ bool Settings::load(const std::string& path)
for (const auto& a : j["favorite_addresses"])
if (a.is_string()) favorite_addresses_.insert(a.get<std::string>());
}
if (j.contains("address_meta") && j["address_meta"].is_object()) {
address_meta_.clear();
for (auto& [addr, meta] : j["address_meta"].items()) {
AddressMeta m;
if (meta.contains("label") && meta["label"].is_string())
m.label = meta["label"].get<std::string>();
if (meta.contains("icon") && meta["icon"].is_string())
m.icon = meta["icon"].get<std::string>();
if (meta.contains("order") && meta["order"].is_number_integer())
m.sortOrder = meta["order"].get<int>();
address_meta_[addr] = m;
}
}
if (j.contains("wizard_completed")) wizard_completed_ = j["wizard_completed"].get<bool>();
if (j.contains("auto_lock_timeout")) auto_lock_timeout_ = j["auto_lock_timeout"].get<int>();
if (j.contains("unlock_duration")) unlock_duration_ = j["unlock_duration"].get<int>();
if (j.contains("pin_enabled")) pin_enabled_ = j["pin_enabled"].get<bool>();
if (j.contains("keep_daemon_running")) keep_daemon_running_ = j["keep_daemon_running"].get<bool>();
if (j.contains("stop_external_daemon")) stop_external_daemon_ = j["stop_external_daemon"].get<bool>();
if (j.contains("max_connections")) max_connections_ = j["max_connections"].get<int>();
if (j.contains("verbose_logging")) verbose_logging_ = j["verbose_logging"].get<bool>();
if (j.contains("debug_categories") && j["debug_categories"].is_array()) {
debug_categories_.clear();
@@ -154,7 +137,6 @@ bool Settings::load(const std::string& path)
}
if (j.contains("theme_effects_enabled")) theme_effects_enabled_ = j["theme_effects_enabled"].get<bool>();
if (j.contains("low_spec_mode")) low_spec_mode_ = j["low_spec_mode"].get<bool>();
if (j.contains("reduce_motion")) reduce_motion_ = j["reduce_motion"].get<bool>();
if (j.contains("selected_exchange")) selected_exchange_ = j["selected_exchange"].get<std::string>();
if (j.contains("selected_pair")) selected_pair_ = j["selected_pair"].get<std::string>();
if (j.contains("pool_url")) pool_url_ = j["pool_url"].get<std::string>();
@@ -243,32 +225,18 @@ bool Settings::save(const std::string& path)
j["favorite_addresses"] = json::array();
for (const auto& addr : favorite_addresses_)
j["favorite_addresses"].push_back(addr);
{
json meta_obj = json::object();
for (const auto& [addr, m] : address_meta_) {
if (m.label.empty() && m.icon.empty() && m.sortOrder < 0) continue;
json entry = json::object();
if (!m.label.empty()) entry["label"] = m.label;
if (!m.icon.empty()) entry["icon"] = m.icon;
if (m.sortOrder >= 0) entry["order"] = m.sortOrder;
meta_obj[addr] = entry;
}
j["address_meta"] = meta_obj;
}
j["wizard_completed"] = wizard_completed_;
j["auto_lock_timeout"] = auto_lock_timeout_;
j["unlock_duration"] = unlock_duration_;
j["pin_enabled"] = pin_enabled_;
j["keep_daemon_running"] = keep_daemon_running_;
j["stop_external_daemon"] = stop_external_daemon_;
j["max_connections"] = max_connections_;
j["verbose_logging"] = verbose_logging_;
j["debug_categories"] = json::array();
for (const auto& cat : debug_categories_)
j["debug_categories"].push_back(cat);
j["theme_effects_enabled"] = theme_effects_enabled_;
j["low_spec_mode"] = low_spec_mode_;
j["reduce_motion"] = reduce_motion_;
j["selected_exchange"] = selected_exchange_;
j["selected_pair"] = selected_pair_;
j["pool_url"] = pool_url_;
-45
View File
@@ -5,7 +5,6 @@
#pragma once
#include <algorithm>
#include <map>
#include <string>
#include <set>
#include <vector>
@@ -142,39 +141,6 @@ public:
void unfavoriteAddress(const std::string& addr) { favorite_addresses_.erase(addr); }
int getFavoriteAddressCount() const { return (int)favorite_addresses_.size(); }
// Address metadata (labels, icons, custom ordering)
struct AddressMeta {
std::string label;
std::string icon; // material icon name, e.g. "savings"
int sortOrder = -1; // -1 = auto (use default sort)
};
const AddressMeta& getAddressMeta(const std::string& addr) const {
static const AddressMeta empty{};
auto it = address_meta_.find(addr);
return it != address_meta_.end() ? it->second : empty;
}
void setAddressLabel(const std::string& addr, const std::string& label) {
address_meta_[addr].label = label;
}
void setAddressIcon(const std::string& addr, const std::string& icon) {
address_meta_[addr].icon = icon;
}
void setAddressSortOrder(const std::string& addr, int order) {
address_meta_[addr].sortOrder = order;
}
int getNextSortOrder() const {
int mx = -1;
for (const auto& [k, v] : address_meta_)
if (v.sortOrder > mx) mx = v.sortOrder;
return mx + 1;
}
void swapAddressOrder(const std::string& a, const std::string& b) {
int oa = address_meta_[a].sortOrder;
int ob = address_meta_[b].sortOrder;
address_meta_[a].sortOrder = ob;
address_meta_[b].sortOrder = oa;
}
// First-run wizard
bool getWizardCompleted() const { return wizard_completed_; }
void setWizardCompleted(bool v) { wizard_completed_ = v; }
@@ -199,10 +165,6 @@ public:
bool getStopExternalDaemon() const { return stop_external_daemon_; }
void setStopExternalDaemon(bool v) { stop_external_daemon_ = v; }
// Daemon — maximum peer connections (0 = daemon default)
int getMaxConnections() const { return max_connections_; }
void setMaxConnections(int v) { max_connections_ = std::max(0, v); }
// Verbose diagnostic logging (connection attempts, daemon state, port owner, etc.)
bool getVerboseLogging() const { return verbose_logging_; }
void setVerboseLogging(bool v) { verbose_logging_ = v; }
@@ -224,10 +186,6 @@ public:
bool getLowSpecMode() const { return low_spec_mode_; }
void setLowSpecMode(bool v) { low_spec_mode_ = v; }
// Reduce motion — disables animated transitions for accessibility
bool getReduceMotion() const { return reduce_motion_; }
void setReduceMotion(bool v) { reduce_motion_ = v; }
// Market — last selected exchange + pair
std::string getSelectedExchange() const { return selected_exchange_; }
void setSelectedExchange(const std::string& v) { selected_exchange_ = v; }
@@ -335,19 +293,16 @@ private:
bool scanline_enabled_ = true;
std::set<std::string> hidden_addresses_;
std::set<std::string> favorite_addresses_;
std::map<std::string, AddressMeta> address_meta_;
bool wizard_completed_ = false;
int auto_lock_timeout_ = 900; // 15 minutes
int unlock_duration_ = 600; // 10 minutes
bool pin_enabled_ = false;
bool keep_daemon_running_ = false;
bool stop_external_daemon_ = false;
int max_connections_ = 0; // 0 = daemon default
bool verbose_logging_ = false;
std::set<std::string> debug_categories_;
bool theme_effects_enabled_ = true;
bool low_spec_mode_ = false;
bool reduce_motion_ = false;
std::string selected_exchange_ = "TradeOgre";
std::string selected_pair_ = "DRGX/BTC";
+3 -3
View File
@@ -7,10 +7,10 @@
// !! DO NOT EDIT version.h — it is generated from version.h.in by CMake.
// !! Change the version in CMakeLists.txt: project(... VERSION x.y.z ...)
#define DRAGONX_VERSION "1.2.0-rc1"
#define DRAGONX_VERSION "1.1.1"
#define DRAGONX_VERSION_MAJOR 1
#define DRAGONX_VERSION_MINOR 2
#define DRAGONX_VERSION_PATCH 0
#define DRAGONX_VERSION_MINOR 1
#define DRAGONX_VERSION_PATCH 1
#define DRAGONX_APP_NAME "ObsidianDragon"
#define DRAGONX_ORG_NAME "Hush"
+1 -1
View File
@@ -7,7 +7,7 @@
// !! DO NOT EDIT version.h — it is generated from version.h.in by CMake.
// !! Change the version in CMakeLists.txt: project(... VERSION x.y.z ...)
#define DRAGONX_VERSION "@PROJECT_VERSION@@DRAGONX_VERSION_SUFFIX@"
#define DRAGONX_VERSION "@PROJECT_VERSION@"
#define DRAGONX_VERSION_MAJOR @PROJECT_VERSION_MAJOR@
#define DRAGONX_VERSION_MINOR @PROJECT_VERSION_MINOR@
#define DRAGONX_VERSION_PATCH @PROJECT_VERSION_PATCH@
+1 -13
View File
@@ -1,9 +1,6 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
//
// embedded_daemon.cpp — Manages the dragonxd child process lifecycle:
// binary discovery, process spawn, stdout/stderr monitoring, crash recovery.
#include "embedded_daemon.h"
#include "../config/version.h"
@@ -205,11 +202,7 @@ std::vector<std::string> EmbeddedDaemon::getChainParams()
"-ac_reward=300000000",
"-ac_blocktime=36",
"-ac_private=1",
"-addnode=node.dragonx.is",
"-addnode=node1.dragonx.is",
"-addnode=node2.dragonx.is",
"-addnode=node3.dragonx.is",
"-addnode=node4.dragonx.is",
"-addnode=176.126.87.241",
"-experimentalfeatures",
"-developerencryptwallet",
dbcache_arg
@@ -477,11 +470,6 @@ bool EmbeddedDaemon::start(const std::string& binary_path)
args.push_back("-debug=" + cat);
}
// Append max connections if configured (0 = daemon default)
if (max_connections_ > 0) {
args.push_back("-maxconnections=" + std::to_string(max_connections_));
}
// Add -rescan flag if requested (one-shot)
if (rescan_on_next_start_.exchange(false)) {
DEBUG_LOGF("[INFO] Adding -rescan flag for blockchain rescan\n");
-26
View File
@@ -116,26 +116,6 @@ public:
* @brief Get last N lines of daemon output (thread-safe snapshot)
*/
std::vector<std::string> getRecentLines(int maxLines = 8) const;
/**
* @brief Extract the latest block height from daemon output (thread-safe).
* Parses the last "height=N" from UpdateTip lines without copying
* the entire output buffer. Returns -1 if no UpdateTip found.
*/
int getLastBlockHeight() const {
std::lock_guard<std::mutex> lk(output_mutex_);
// Search backwards from the end for "height="
size_t pos = process_output_.rfind("height=");
if (pos == std::string::npos) return -1;
pos += 7; // skip "height="
int h = 0;
for (size_t i = pos; i < process_output_.size(); ++i) {
char c = process_output_[i];
if (c >= '0' && c <= '9') h = h * 10 + (c - '0');
else break;
}
return h > 0 ? h : -1;
}
/**
* @brief Whether start() detected an existing daemon on the RPC port.
@@ -172,11 +152,6 @@ public:
void setDebugCategories(const std::set<std::string>& cats) { debug_categories_ = cats; }
const std::set<std::string>& getDebugCategories() const { return debug_categories_; }
/**
* @brief Set maximum peer connections (0 = use daemon default)
*/
void setMaxConnections(int v) { max_connections_ = v; }
/**
* @brief Request a blockchain rescan on the next daemon start
*/
@@ -219,7 +194,6 @@ private:
std::thread monitor_thread_;
std::atomic<bool> should_stop_{false};
std::set<std::string> debug_categories_;
int max_connections_ = 0; // 0 = daemon default
std::atomic<int> crash_count_{0}; // consecutive crash counter
std::atomic<bool> rescan_on_next_start_{false}; // -rescan flag for next start
};
-3
View File
@@ -1,9 +1,6 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
//
// xmrig_manager.cpp — Pool mining process management via xmrig-hac.
// Spawns xmrig, monitors via HTTP API, tracks hashrate and shares.
#include "xmrig_manager.h"
#include "../resources/embedded_resources.h"
-7
View File
@@ -134,7 +134,6 @@ struct MarketInfo {
double change_24h = 0.0;
double market_cap = 0.0;
std::string last_updated;
std::chrono::steady_clock::time_point last_fetch_time{};
// Price history for chart
std::vector<double> price_history;
@@ -180,9 +179,6 @@ struct PoolMiningState {
struct WalletState {
// Connection
bool connected = false;
bool warming_up = false; // daemon reachable but in RPC warmup (error -28)
std::string warmup_status; // user-friendly title, e.g. "Processing blocks..."
std::string warmup_description; // subtitle explaining the stage
int daemon_version = 0;
std::string daemon_subversion;
int protocol_version = 0;
@@ -253,9 +249,6 @@ struct WalletState {
void clear() {
connected = false;
warming_up = false;
warmup_status.clear();
warmup_description.clear();
daemon_version = 0;
daemon_subversion.clear();
protocol_version = 0;
+1 -1
View File
@@ -12,4 +12,4 @@ INCBIN(ubuntu_regular, "@CMAKE_SOURCE_DIR@/res/fonts/Ubuntu-R.ttf");
INCBIN(ubuntu_light, "@CMAKE_SOURCE_DIR@/res/fonts/Ubuntu-Light.ttf");
INCBIN(ubuntu_medium, "@CMAKE_SOURCE_DIR@/res/fonts/Ubuntu-Medium.ttf");
INCBIN(material_icons, "@CMAKE_SOURCE_DIR@/res/fonts/MaterialIcons-Regular.ttf");
INCBIN(noto_cjk_subset, "@CMAKE_SOURCE_DIR@/res/fonts/NotoSansCJK-Subset.ttf");
INCBIN(noto_cjk_subset, "@CMAKE_SOURCE_DIR@/res/fonts/NotoSansCJK-Subset.otf");
+2826 -3563
View File
File diff suppressed because it is too large Load Diff
+2757 -3493
View File
File diff suppressed because it is too large Load Diff
+2897 -3655
View File
File diff suppressed because it is too large Load Diff
+3534 -4381
View File
File diff suppressed because it is too large Load Diff
+2894 -3625
View File
File diff suppressed because it is too large Load Diff
+3003 -3729
View File
File diff suppressed because it is too large Load Diff
+4103 -5076
View File
File diff suppressed because it is too large Load Diff
+2563 -3229
View File
File diff suppressed because it is too large Load Diff
+2 -5
View File
@@ -234,11 +234,8 @@ bool Connection::createDefaultConfig(const std::string& path)
file << "exportdir=" << dataDir << "\n";
file << "experimentalfeatures=1\n";
file << "developerencryptwallet=1\n";
file << "addnode=node.dragonx.is\n";
file << "addnode=node1.dragonx.is\n";
file << "addnode=node2.dragonx.is\n";
file << "addnode=node3.dragonx.is\n";
file << "addnode=node4.dragonx.is\n";
file << "addnode=195.201.20.230\n";
file << "addnode=195.201.137.219\n";
file.close();
+3 -28
View File
@@ -1,9 +1,6 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
//
// rpc_client.cpp — JSON-RPC client over HTTPS using libcurl.
// All calls are blocking; run on RPCWorker threads, never on main thread.
#include "rpc_client.h"
#include "../config/version.h"
@@ -73,16 +70,6 @@ bool RPCClient::connect(const std::string& host, const std::string& port,
impl_->url = "http://" + host + ":" + port + "/";
VERBOSE_LOGF("Connecting to dragonxd at %s\n", impl_->url.c_str());
// Clean up previous curl handle/headers to avoid leaks on retries
if (impl_->headers) {
curl_slist_free_all(impl_->headers);
impl_->headers = nullptr;
}
if (impl_->curl) {
curl_easy_cleanup(impl_->curl);
impl_->curl = nullptr;
}
// Initialize curl handle
impl_->curl = curl_easy_init();
if (!impl_->curl) {
@@ -91,7 +78,7 @@ bool RPCClient::connect(const std::string& host, const std::string& port,
}
// Set up headers - daemon expects text/plain, not application/json
impl_->headers = curl_slist_append(nullptr, "Content-Type: text/plain");
impl_->headers = curl_slist_append(impl_->headers, "Content-Type: text/plain");
std::string auth_header = "Authorization: Basic " + auth_;
impl_->headers = curl_slist_append(impl_->headers, auth_header.c_str());
@@ -107,8 +94,6 @@ bool RPCClient::connect(const std::string& host, const std::string& port,
json result = call("getinfo");
if (result.contains("version")) {
connected_ = true;
warming_up_ = false;
warmup_status_.clear();
last_connect_error_.clear();
DEBUG_LOGF("Connected to dragonxd v%d\n", result["version"].get<int>());
return true;
@@ -116,9 +101,7 @@ bool RPCClient::connect(const std::string& host, const std::string& port,
} catch (const std::exception& e) {
last_connect_error_ = e.what();
// Daemon warmup messages (Loading block index, Verifying blocks, etc.)
// are normal startup progress — the daemon is reachable and auth works,
// it just hasn't finished initializing yet. Mark as connected+warmup
// so the wallet can show the UI instead of a blocking overlay.
// are normal startup progress — don't label them "Connection failed".
std::string msg = e.what();
bool isWarmup = (msg.find("Loading") != std::string::npos ||
msg.find("Verifying") != std::string::npos ||
@@ -127,19 +110,13 @@ bool RPCClient::connect(const std::string& host, const std::string& port,
msg.find("Rescanning") != std::string::npos ||
msg.find("Pruning") != std::string::npos);
if (isWarmup) {
connected_ = true;
warming_up_ = true;
warmup_status_ = msg;
DEBUG_LOGF("Daemon warming up: %s\n", msg.c_str());
return true;
DEBUG_LOGF("Daemon starting: %s\n", msg.c_str());
} else {
DEBUG_LOGF("Connection failed: %s\n", msg.c_str());
}
}
connected_ = false;
warming_up_ = false;
warmup_status_.clear();
return false;
}
@@ -147,8 +124,6 @@ void RPCClient::disconnect()
{
std::lock_guard<std::recursive_mutex> lk(curl_mutex_);
connected_ = false;
warming_up_ = false;
warmup_status_.clear();
if (impl_->curl) {
curl_easy_cleanup(impl_->curl);
impl_->curl = nullptr;
-15
View File
@@ -53,19 +53,6 @@ public:
*/
bool isConnected() const { return connected_; }
/**
* @brief True if the last connect() succeeded but daemon returned a warmup error.
* The curl handle is valid and auth succeeded — RPC calls will throw warmup errors
* until the daemon finishes initializing.
*/
bool isWarmingUp() const { return warming_up_; }
/**
* @brief The warmup status message (e.g. "Activating best chain...").
* Empty when not in warmup.
*/
const std::string& getWarmupStatus() const { return warmup_status_; }
/**
* @brief Get the error message from the last failed connect() attempt.
*/
@@ -195,8 +182,6 @@ private:
std::string port_;
std::string auth_; // Base64 encoded "user:password"
bool connected_ = false;
bool warming_up_ = false;
std::string warmup_status_;
std::string last_connect_error_;
mutable std::recursive_mutex curl_mutex_; // serializes all curl handle access
-3
View File
@@ -1,9 +1,6 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
//
// rpc_worker.cpp — Background work queue. Executes WorkFn on its own thread,
// returns MainCb callbacks drained each frame on the main thread.
#include "rpc_worker.h"
#include <cstdio>
+1 -8
View File
@@ -300,14 +300,7 @@ ImFont* Typography::loadFont(ImGuiIO& io, int weight, float size, const char* na
cjkCfg.GlyphRanges = cjkRanges;
snprintf(cjkCfg.Name, sizeof(cjkCfg.Name), "NotoSansCJK %.0fpx (merge)", size);
ImFont* mergeResult = io.Fonts->AddFontFromMemoryTTF(cjkCopy, g_noto_cjk_subset_size, size, &cjkCfg);
if (mergeResult) {
DEBUG_LOGF("Typography: Merged CJK (%u bytes) into %s OK\n",
g_noto_cjk_subset_size, name);
} else {
DEBUG_LOGF("Typography: WARNING — CJK merge FAILED for %s (size=%u)\n",
name, g_noto_cjk_subset_size);
}
io.Fonts->AddFontFromMemoryTTF(cjkCopy, g_noto_cjk_subset_size, size, &cjkCfg);
}
} else {
DEBUG_LOGF("Typography: Failed to load %s\n", name);
+13 -38
View File
@@ -32,7 +32,6 @@
#include "../windows/block_info_dialog.h"
#include "../windows/export_all_keys_dialog.h"
#include "../windows/export_transactions_dialog.h"
#include "../windows/bootstrap_download_dialog.h"
#include "../../embedded/IconsMaterialDesign.h"
#include "imgui.h"
#include <nlohmann/json.hpp>
@@ -94,9 +93,6 @@ static bool sp_gradient_background = false;
// Low-spec mode
static bool sp_low_spec_mode = false;
// Reduce motion (accessibility)
static bool sp_reduce_motion = false;
// Font scale (user accessibility, 1.0–3.0)
static float sp_font_scale = 1.0f;
@@ -183,7 +179,6 @@ static void loadSettingsPageState(config::Settings* settings) {
sp_theme_effects_enabled = settings->getThemeEffectsEnabled();
sp_low_spec_mode = settings->getLowSpecMode();
effects::setLowSpecMode(sp_low_spec_mode);
sp_reduce_motion = settings->getReduceMotion();
sp_font_scale = settings->getFontScale();
Layout::setUserFontScale(sp_font_scale); // sync with Layout on load
sp_keep_daemon_running = settings->getKeepDaemonRunning();
@@ -237,7 +232,6 @@ static void saveSettingsPageState(config::Settings* settings) {
settings->setScanlineEnabled(sp_scanline_enabled);
settings->setThemeEffectsEnabled(sp_theme_effects_enabled);
settings->setLowSpecMode(sp_low_spec_mode);
settings->setReduceMotion(sp_reduce_motion);
settings->setFontScale(sp_font_scale);
settings->setKeepDaemonRunning(sp_keep_daemon_running);
settings->setStopExternalDaemon(sp_stop_external_daemon);
@@ -678,12 +672,6 @@ void RenderSettingsPage(App* app) {
}
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_simple_bg"));
ImGui::SameLine(0, Layout::spacingLg());
if (ImGui::Checkbox(TrId("reduce_motion", "reduce_motion").c_str(), &sp_reduce_motion)) {
saveSettingsPageState(app->settings());
}
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_reduce_motion"));
ImGui::BeginDisabled(sp_low_spec_mode);
ImGui::SameLine(0, Layout::spacingLg());
@@ -714,7 +702,7 @@ void RenderSettingsPage(App* app) {
{
char blur_fmt[16];
if (sp_blur_amount < 0.01f)
snprintf(blur_fmt, sizeof(blur_fmt), "%s", TR("slider_off"));
snprintf(blur_fmt, sizeof(blur_fmt), "Off");
else
snprintf(blur_fmt, sizeof(blur_fmt), "%.0f%%%%", sp_blur_amount * 25.0f);
if (ImGui::SliderFloat("##AcrylicBlur", &sp_blur_amount, 0.0f, 4.0f, blur_fmt,
@@ -736,7 +724,7 @@ void RenderSettingsPage(App* app) {
{
char noise_fmt[16];
if (sp_noise_opacity < 0.01f)
snprintf(noise_fmt, sizeof(noise_fmt), "%s", TR("slider_off"));
snprintf(noise_fmt, sizeof(noise_fmt), "Off");
else
snprintf(noise_fmt, sizeof(noise_fmt), "%.0f%%%%", sp_noise_opacity * 100.0f);
if (ImGui::SliderFloat("##NoiseOpacity", &sp_noise_opacity, 0.0f, 1.0f, noise_fmt,
@@ -974,11 +962,6 @@ void RenderSettingsPage(App* app) {
}
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_simple_bg_alt"));
if (ImGui::Checkbox(TrId("reduce_motion", "reduce_motion").c_str(), &sp_reduce_motion)) {
saveSettingsPageState(app->settings());
}
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_reduce_motion"));
ImGui::BeginDisabled(sp_low_spec_mode);
if (ImGui::Checkbox(TrId("console_scanline", "scanline").c_str(), &sp_scanline_enabled)) {
@@ -1002,7 +985,7 @@ void RenderSettingsPage(App* app) {
{
char blur_fmt[16];
if (sp_blur_amount < 0.01f)
snprintf(blur_fmt, sizeof(blur_fmt), "%s", TR("slider_off"));
snprintf(blur_fmt, sizeof(blur_fmt), "Off");
else
snprintf(blur_fmt, sizeof(blur_fmt), "%.0f%%%%", sp_blur_amount * 25.0f);
if (ImGui::SliderFloat("##AcrylicBlur", &sp_blur_amount, 0.0f, 4.0f, blur_fmt,
@@ -1020,7 +1003,7 @@ void RenderSettingsPage(App* app) {
{
char noise_fmt[16];
if (sp_noise_opacity < 0.01f)
snprintf(noise_fmt, sizeof(noise_fmt), "%s", TR("slider_off"));
snprintf(noise_fmt, sizeof(noise_fmt), "Off");
else
snprintf(noise_fmt, sizeof(noise_fmt), "%.0f%%%%", sp_noise_opacity * 100.0f);
if (ImGui::SliderFloat("##NoiseOpacity", &sp_noise_opacity, 0.0f, 1.0f, noise_fmt,
@@ -1237,7 +1220,6 @@ void RenderSettingsPage(App* app) {
TR("tt_export_csv")
};
const char* wizLabel = TR("setup_wizard");
const char* bsLabel = TR("download_bootstrap");
float sp = Layout::spacingSm();
ImFont* btnFont = S.resolveFont("button");
@@ -1246,8 +1228,7 @@ void RenderSettingsPage(App* app) {
for (int i = 0; i < 5; i++)
naturalW += ImGui::CalcTextSize(r1[i]).x + btnPadX;
float wizW = ImGui::CalcTextSize(wizLabel).x + btnPadX;
float bsW = ImGui::CalcTextSize(bsLabel).x + btnPadX;
float totalW = naturalW + wizW + bsW + sp * 7;
float totalW = naturalW + wizW + sp * 6;
float scale = (totalW > contentW) ? contentW / totalW : 1.0f;
if (scale < 1.0f) ImGui::SetWindowFontScale(scale);
@@ -1274,24 +1255,18 @@ void RenderSettingsPage(App* app) {
ExportTransactionsDialog::show();
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", t1[4]);
// Right-align Setup Wizard + Download Bootstrap
float framePadX2 = ImGui::GetStyle().FramePadding.x * 2.0f;
// Right-align Setup Wizard
float curX = ImGui::GetCursorScreenPos().x;
float wizBtnW = ImGui::CalcTextSize(wizLabel).x + framePadX2;
float bsBtnW = ImGui::CalcTextSize(bsLabel).x + framePadX2;
float wizBtnW = ImGui::CalcTextSize(wizLabel).x + btnPadX;
if (scale < 1.0f) wizBtnW *= scale;
float rightEdge = cardMin.x + availWidth - pad;
float rightGroupW = bsBtnW + scaledSp + wizBtnW;
float groupX = rightEdge - rightGroupW;
if (groupX > curX) {
float wizX = rightEdge - wizBtnW;
if (wizX > curX) {
ImGui::SameLine(0, 0);
ImGui::SetCursorScreenPos(ImVec2(groupX, ImGui::GetCursorScreenPos().y));
ImGui::SetCursorScreenPos(ImVec2(wizX, ImGui::GetCursorScreenPos().y));
} else {
ImGui::SameLine(0, scaledSp);
}
if (TactileButton(bsLabel, ImVec2(0, 0), btnFont))
BootstrapDownloadDialog::show(app);
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_download_bootstrap"));
ImGui::SameLine(0, scaledSp);
if (TactileButton(wizLabel, ImVec2(0, 0), btnFont))
app->restartWizard();
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_wizard"));
@@ -1595,7 +1570,7 @@ void RenderSettingsPage(App* app) {
float comboW = S.drawElement("components.settings-page", "security-combo-width").sizeOr(120.0f);
int timeout = app->settings()->getAutoLockTimeout();
const char* timeoutLabels[] = { TR("timeout_off"), TR("timeout_1min"), TR("timeout_5min"), TR("timeout_15min"), TR("timeout_30min"), TR("timeout_1hour") };
const char* timeoutLabels[] = { "Off", "1 min", "5 min", "15 min", "30 min", "1 hour" };
int timeoutValues[] = { 0, 60, 300, 900, 1800, 3600 };
int selTimeout = 0;
for (int i = 0; i < 6; i++) {
@@ -1675,7 +1650,7 @@ void RenderSettingsPage(App* app) {
// EXPLORER & OPTIONS — full-width card
// ====================================================================
{
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("explorer_section"));
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("explorer"));
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
ImVec2 cardMin = ImGui::GetCursorScreenPos();
+251 -198
View File
@@ -380,7 +380,7 @@ inline void DrawGlassBevelButton(ImDrawList* dl, ImVec2 mn, ImVec2 mx,
// collapsed: when true, sidebar is in icon-only mode (narrow width).
// The caller can toggle collapsed via a reference if a toggle button is desired.
inline bool RenderSidebar(NavPage& current, float sidebarWidth, float contentHeight,
SidebarStatus& status, bool& collapsed, bool locked = false)
SidebarStatus& status, bool& collapsed)
{
using namespace material;
bool changed = false;
@@ -415,13 +415,46 @@ inline bool RenderSidebar(NavPage& current, float sidebarWidth, float contentHei
const float bottomPadding = sde("bottom-padding", 6.0f);
const float exitIconGap = sde("exit-icon-gap", 4.0f);
const float sbSectionLabelPadLeft = sde("section-label-pad-left", 16.0f);
const float sbItemPadX = sde("item-pad-x", 8.0f);
// Base values for responsive scaling
const float baseItemHeight = sde("item-height", 46.0f);
const float baseNavGap = sde("nav-gap", 20.0f);
const float baseSectionGap = sde("section-gap", 8.0f);
const float baseButtonSpacing = sde("button-spacing", 6.0f);
const float sectionLabelPadBot = sde("section-label-pad-bottom", 4.0f);
const float baseSectionGap = sde("section-gap", 12.0f);
const float baseButtonSpacing = sde("button-spacing", 3.0f);
// Estimate total sidebar content height at base sizes to detect overflow.
// Fixed chrome: top margin + collapse strip + exit strip + bottom padding
const float fixedChrome = glassMarginY + stripH + stripH + bottomPadding;
// Section labels: 2 sections × (gap + label height + pad)
const float sectionLabelH = 2.0f * (baseSectionGap + 13.0f * dp);
// Nav items: 9 items × (height + spacing)
const float navItemsH = (float)(int)NavPage::Count_ * (baseItemHeight + baseButtonSpacing);
const float baseContentH = fixedChrome + baseNavGap + navItemsH + sectionLabelH;
// Responsive shrink: if content would overflow, scale down flexible sizes.
// Clamp scale so buttons never shrink below what fits in the minimum
// sidebar height defined in ui.toml ("min-height").
float sidebarMinHeight = sde("min-height", 360.0f);
float scaleFloor = 0.55f;
if (sidebarMinHeight > fixedChrome) {
float flexH = baseContentH - fixedChrome;
if (flexH > 0.0f) {
float minFlex = sidebarMinHeight - fixedChrome;
scaleFloor = std::max(0.55f, minFlex / flexH);
}
}
float sidebarScale = 1.0f;
if (baseContentH > contentHeight && contentHeight > fixedChrome) {
float flexH = baseContentH - fixedChrome;
float availFlex = contentHeight - fixedChrome;
sidebarScale = std::max(scaleFloor, availFlex / flexH);
}
const float sbItemHeight = baseItemHeight * sidebarScale;
const float navGap = baseNavGap * sidebarScale;
const float sbSectionGap = baseSectionGap * sidebarScale;
const float buttonSpacing = baseButtonSpacing * sidebarScale;
// How "expanded" are we? 0.0 = fully collapsed, 1.0 = fully expanded
float expandFrac = (sbWidth > sbCollapsedWidth)
@@ -429,10 +462,7 @@ inline bool RenderSidebar(NavPage& current, float sidebarWidth, float contentHei
: 1.0f;
if (expandFrac < 0.0f) expandFrac = 0.0f;
if (expandFrac > 1.0f) expandFrac = 1.0f;
bool showLabels = expandFrac > 0.3f;
// Font size for section labels (fixed, doesn't scale)
float olFsz = ScaledFontSize(Type().overline());
bool showLabels = expandFrac > 0.3f; // hide labels during early part of animation
// Glass panel rounding from responsive schema
float glassRounding = [&]() {
@@ -440,165 +470,128 @@ inline bool RenderSidebar(NavPage& current, float sidebarWidth, float contentHei
return (v >= 0 ? v : 8.0f) * dp;
}();
// ===================================================================
// PASS 1: Compute layout — all Y positions relative to panel top
// ===================================================================
// Separate fixed parts (don't scale) from flex parts (scale when tight).
float fixedH = stripH; // collapse strip
for (int i = 0; i < (int)NavPage::Count_; ++i)
if (kNavItems[i].section_label && showLabels)
fixedH += olFsz + 2.0f + sectionLabelPadBot; // section label + pad below
fixedH += bottomPadding + stripH; // exit area
float baseFlexH = baseNavGap;
for (int i = 0; i < (int)NavPage::Count_; ++i) {
if (kNavItems[i].section_label) {
if (showLabels) baseFlexH += baseSectionGap;
else baseFlexH += baseSectionGap * 0.8f;
}
baseFlexH += baseItemHeight + baseButtonSpacing;
}
// Responsive shrink (only scales flex parts)
float sidebarMinHeight = sde("min-height", 360.0f);
float scaleFloor = (sidebarMinHeight > fixedH && baseFlexH > 0.0f)
? std::max(0.55f, (sidebarMinHeight - fixedH) / baseFlexH) : 0.55f;
float sidebarScale = 1.0f;
if (fixedH + baseFlexH > contentHeight && baseFlexH > 0.0f)
sidebarScale = std::max(scaleFloor, (contentHeight - fixedH) / baseFlexH);
const float itemH = baseItemHeight * sidebarScale;
const float navGap = baseNavGap * sidebarScale;
const float sectionGap = baseSectionGap * sidebarScale;
const float btnSpacing = baseButtonSpacing * sidebarScale;
// Compute Y position for every element (relative to panel top)
float itemY[(int)NavPage::Count_];
float sectionLabelY[4] = {}; int nSectionLabels = 0;
float separatorY[4] = {}; int nSeparators = 0;
float curY = stripH + navGap; // after collapse strip + gap
for (int i = 0; i < (int)NavPage::Count_; ++i) {
if (kNavItems[i].section_label) {
if (showLabels) {
curY += sectionGap;
if (nSectionLabels < 4) sectionLabelY[nSectionLabels++] = curY;
curY += olFsz + 2.0f + sectionLabelPadBot;
} else {
curY += sectionGap * 0.4f;
if (nSeparators < 4) separatorY[nSeparators++] = curY;
curY += sectionGap * 0.4f;
}
}
itemY[i] = curY;
curY += itemH + btnSpacing;
}
float exitRelY = curY + bottomPadding;
float panelH = exitRelY + stripH;
// Vertical centering — offset so panel is centered in the child window
float centerOffset = std::max(glassMarginY, (contentHeight - panelH) * 0.5f);
if (centerOffset + panelH > contentHeight)
centerOffset = std::max(0.0f, contentHeight - panelH);
// ===================================================================
// PASS 2: Render using computed positions
// ===================================================================
ImGui::BeginChild("##Sidebar", ImVec2(sidebarWidth, contentHeight), false,
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoBackground);
ImDrawList* dl = ImGui::GetWindowDrawList();
ImVec2 wp = ImGui::GetWindowPos();
float panelLeft = wp.x + glassMarginL;
float panelRight = wp.x + sidebarWidth - glassMarginR;
float panelTopY = wp.y + centerOffset;
float panelBotY = std::min(panelTopY + panelH, wp.y + contentHeight - glassMarginY);
// Glass card background — inset with rounded corners, matching content cards
float sidebarMarginY = glassMarginY; // top & bottom inset
float sidebarMarginL = glassMarginL; // left inset
float sidebarMarginR = glassMarginR; // right inset (tighter on content side)
// Panel bounds
float panelLeft = wp.x + sidebarMarginL;
float panelRight = wp.x + sidebarWidth - sidebarMarginR;
// Draw list splitter: channel 0 = glass background, channel 1 = content
// Defer glass panel drawing until we know content height (channel 0 = background)
ImDrawListSplitter splitter;
splitter.Split(dl, 2);
splitter.SetCurrentChannel(dl, 1);
// ---- Collapse toggle strip (panel top) ----
// Top padding (just the margin — collapse button sits at panel top)
ImGui::Dummy(ImVec2(0, sidebarMarginY));
// ---- Collapse toggle — flush strip at top of sidebar panel ----
{
ImVec2 savedCursor = ImGui::GetCursorScreenPos();
float rnd = glassRounding;
// Strip spans full panel width, sits inside top of panel
float stripX = panelLeft;
float stripW = panelRight - panelLeft;
ImVec2 stripMin(stripX, panelTopY);
ImVec2 stripMax(stripX + stripW, panelTopY + stripH);
float stripY = wp.y + sidebarMarginY;
ImVec2 stripMin(stripX, stripY);
ImVec2 stripMax(stripX + stripW, stripY + stripH);
ImGui::SetCursorScreenPos(stripMin);
if (ImGui::InvisibleButton("##SidebarCollapse", ImVec2(stripW, stripH)))
if (ImGui::InvisibleButton("##SidebarCollapse", ImVec2(stripW, stripH))) {
collapsed = !collapsed;
}
bool btnHover = ImGui::IsItemHovered();
// Draw strip background — flush with panel top (no extra rounding, blends in)
ImU32 stripBg = btnHover ? schema::UI().resolveColor("var(--sidebar-hover)", IM_COL32(255, 255, 255, 25)) : IM_COL32(0, 0, 0, 0);
if (btnHover) {
dl->AddRectFilled(stripMin, stripMax,
schema::UI().resolveColor("var(--sidebar-hover)", IM_COL32(255, 255, 255, 25)),
glassRounding, ImDrawFlags_RoundCornersTop);
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
dl->AddRectFilled(stripMin, stripMax, stripBg,
rnd, ImDrawFlags_RoundCornersTop);
}
dl->AddLine(ImVec2(stripMin.x + glassRounding * 0.5f, stripMax.y),
ImVec2(stripMax.x - glassRounding * 0.5f, stripMax.y),
// Subtle bottom separator
dl->AddLine(ImVec2(stripMin.x + rnd * 0.5f, stripMax.y),
ImVec2(stripMax.x - rnd * 0.5f, stripMax.y),
schema::UI().resolveColor("var(--sidebar-divider)", IM_COL32(255, 255, 255, 15)), 1.0f);
if (btnHover) ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
// Chevron icon centered in strip
ImU32 iconCol = btnHover ? OnSurface() : schema::UI().resolveColor("var(--sidebar-icon)", IM_COL32(255, 255, 255, 60));
float cx = stripX + stripW * 0.5f;
float cy = panelTopY + stripH * 0.5f;
ImFont* iconFont = Type().iconSmall();
const char* chevIcon = collapsed ? ICON_MD_CHEVRON_RIGHT : ICON_MD_CHEVRON_LEFT;
float chevFsz = ScaledFontSize(iconFont);
ImVec2 chevSz = iconFont->CalcTextSizeA(chevFsz, 1000.0f, 0.0f, chevIcon);
dl->AddText(iconFont, chevFsz,
ImVec2(cx - chevSz.x * 0.5f, cy - chevSz.y * 0.5f), iconCol, chevIcon);
float cy = stripY + stripH * 0.5f;
{
ImFont* iconFont = Type().iconSmall();
const char* chevIcon = collapsed ? ICON_MD_CHEVRON_RIGHT : ICON_MD_CHEVRON_LEFT;
float chevFsz = ScaledFontSize(iconFont);
ImVec2 chevSz = iconFont->CalcTextSizeA(chevFsz, 1000.0f, 0.0f, chevIcon);
dl->AddText(iconFont, chevFsz,
ImVec2(cx - chevSz.x * 0.5f, cy - chevSz.y * 0.5f), iconCol, chevIcon);
}
ImGui::SetCursorScreenPos(savedCursor); // restore — toggle is an overlay
}
// ---- Section labels / separators ----
if (showLabels) {
ImFont* olFont = Type().overline();
float olFontSz = ScaledFontSize(olFont);
float maxSectionW = panelRight - wp.x - sbSectionLabelPadLeft;
int si = 0;
for (int i = 0; i < (int)NavPage::Count_; ++i) {
if (kNavItems[i].section_label && si < nSectionLabels) {
float ly = panelTopY + sectionLabelY[si++];
const char* sLabel = NavSectionLabel(kNavItems[i]);
float drawFsz = olFontSz;
ImVec2 sLabelSz = olFont->CalcTextSizeA(drawFsz, 1000.0f, 0.0f, sLabel);
if (sLabelSz.x > maxSectionW && maxSectionW > 0)
drawFsz *= maxSectionW / sLabelSz.x;
ImVec4 olCol = ImGui::ColorConvertU32ToFloat4(OnSurfaceMedium());
olCol.w *= expandFrac;
dl->AddText(olFont, drawFsz,
ImVec2(wp.x + sbSectionLabelPadLeft, ly),
ImGui::ColorConvertFloat4ToU32(olCol), sLabel);
}
}
} else {
for (int si = 0; si < nSeparators; ++si) {
float sy = panelTopY + separatorY[si];
dl->AddLine(ImVec2(wp.x + btnPadCollapsed, sy),
ImVec2(wp.x + sidebarWidth - btnPadCollapsed, sy),
Divider(), 1.0f);
}
}
// Gap between collapse divider and first nav item
ImGui::Dummy(ImVec2(0, navGap));
// ---- Navigation items ----
float btnPadX = collapsed ? btnPadCollapsed : btnPadExpanded;
for (int i = 0; i < (int)NavPage::Count_; ++i) {
const NavItem& item = kNavItems[i];
// Section label (only when expanded)
if (item.section_label && showLabels) {
ImGui::Dummy(ImVec2(0, sbSectionGap));
ImFont* olFont = Type().overline();
float labelY = ImGui::GetCursorScreenPos().y;
ImVec4 olCol = ImGui::ColorConvertU32ToFloat4(OnSurfaceMedium());
olCol.w *= expandFrac;
float olFsz = ScaledFontSize(olFont);
dl->AddText(olFont, olFsz,
ImVec2(wp.x + sbSectionLabelPadLeft, labelY),
ImGui::ColorConvertFloat4ToU32(olCol), NavSectionLabel(item));
ImGui::Dummy(ImVec2(0, olFsz + 2.0f));
} else if (item.section_label && !showLabels) {
// Collapsed: thin separator instead of label
ImGui::Dummy(ImVec2(0, sbSectionGap * 0.4f));
float sepY2 = ImGui::GetCursorScreenPos().y;
dl->AddLine(ImVec2(wp.x + btnPadCollapsed, sepY2), ImVec2(wp.x + sidebarWidth - btnPadCollapsed, sepY2),
Divider(), 1.0f);
ImGui::Dummy(ImVec2(0, sbSectionGap * 0.4f));
}
bool selected = (current == item.page);
float btnY = panelTopY + itemY[i];
float btnRnd = itemH * 0.22f;
ImVec2 indMin(panelLeft + btnPadX, btnY);
ImVec2 indMax(panelRight - btnPadX, btnY + itemH);
ImGui::PushID(i);
float itemH = sbItemHeight;
float btnRnd = itemH * 0.22f; // moderate rounding
float btnPadX = collapsed ? btnPadCollapsed : btnPadExpanded; // tighter padding when collapsed
ImVec2 cursor = ImGui::GetCursorScreenPos();
// Keep button height constant (itemH) so sidebar content height doesn't
// change during collapse animation, which would destabilize centering.
float btnH = itemH;
// Item bounds for icon/label placement (inset)
ImVec2 itemMin(wp.x + sbItemPadX, cursor.y);
ImVec2 itemMax(wp.x + sidebarWidth - sbItemPadX, cursor.y + btnH);
// Button bounds — inset from panel edges for spacing
ImVec2 indMin(panelLeft + btnPadX, cursor.y);
ImVec2 indMax(panelRight - btnPadX, cursor.y + btnH);
// All buttons are embossed; hover presses halfway, selected presses fully
bool hovered = material::IsRectHovered(indMin, indMax);
// Glass bevel + theme effects
{
float btnDepth = selected ? 1.0f : (hovered ? 0.5f : 0.0f);
float btnDepth = 0.0f;
if (selected) btnDepth = 1.0f;
else if (hovered) btnDepth = 0.5f;
// Theme effects behind active button
if (selected) {
auto& fx = effects::ThemeEffects::instance();
fx.drawGlowPulse(dl, indMin, indMax, btnRnd);
@@ -607,77 +600,92 @@ inline bool RenderSidebar(NavPage& current, float sidebarWidth, float contentHei
fx.drawShimmer(dl, indMin, indMax, btnRnd);
fx.drawGradientBorderShift(dl, indMin, indMax, btnRnd);
}
DrawGlassCutout(dl, indMin, indMax, btnRnd, 1.5f);
DrawGlassBevelButton(dl, indMin, indMax, btnRnd, btnDepth, 18);
buttonRects.push_back({indMin, indMax, btnRnd});
}
if (hovered) ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
if (hovered) {
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
}
// Click detection
bool pageNeedsUnlock = locked &&
item.page != NavPage::Console &&
item.page != NavPage::Peers &&
item.page != NavPage::Settings;
if (hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Left) && !pageNeedsUnlock) {
if (hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
current = item.page;
changed = true;
}
// Icon + label
float iconS = iconHalfSize;
float iconCY = btnY + itemH * 0.5f;
float textY = btnY + (itemH - ImGui::GetTextLineHeight()) * 0.5f;
ImU32 textCol = selected ? Primary() : (pageNeedsUnlock ? OnSurfaceDisabled() : OnSurfaceMedium());
// Icon + label — centered horizontally within button bounds
float iconS = iconHalfSize; // icon half-size in pixels
float iconCY = cursor.y + btnH * 0.5f;
float textY = cursor.y + (btnH - ImGui::GetTextLineHeight()) * 0.5f;
ImU32 textCol = selected ? Primary() : OnSurfaceMedium();
if (showLabels) {
// Measure total width of icon + gap + label, then center.
// If the translated label is too wide, shrink the font to fit.
ImFont* font = selected ? Type().subtitle2() : Type().body2();
float gap = iconLabelGap;
float lblFsz = ScaledFontSize(font);
float btnW = indMax.x - indMin.x;
float maxLabelW = btnW - iconS * 2.0f - iconLabelGap - Layout::spacingXs() * 2;
float maxLabelW = btnW - iconS * 2.0f - gap - Layout::spacingXs() * 2;
ImVec2 labelSz = font->CalcTextSizeA(lblFsz, 1000.0f, 0.0f, NavLabel(item));
if (labelSz.x > maxLabelW && maxLabelW > 0) {
lblFsz *= maxLabelW / labelSz.x;
float shrink = maxLabelW / labelSz.x;
lblFsz *= shrink;
labelSz = font->CalcTextSizeA(lblFsz, 1000.0f, 0.0f, NavLabel(item));
}
float totalW = iconS * 2.0f + iconLabelGap + labelSz.x;
float totalW = iconS * 2.0f + gap + labelSz.x;
float btnCX = (indMin.x + indMax.x) * 0.5f;
float startX = btnCX - totalW * 0.5f;
DrawNavIcon(dl, item.page, startX + iconS, iconCY, iconS, textCol);
float iconCX = startX + iconS;
DrawNavIcon(dl, item.page, iconCX, iconCY, iconS, textCol);
float labelX = startX + iconS * 2.0f + gap;
ImVec4 lc = ImGui::ColorConvertU32ToFloat4(textCol);
lc.w *= expandFrac;
dl->AddText(font, lblFsz,
ImVec2(startX + iconS * 2.0f + iconLabelGap, textY),
ImGui::ColorConvertFloat4ToU32(lc), NavLabel(item));
dl->AddText(font, lblFsz, ImVec2(labelX, textY),
ImGui::ColorConvertFloat4ToU32(lc), NavLabel(item));
} else {
float iconCX = (indMin.x + indMax.x) * 0.5f;
DrawNavIcon(dl, item.page, iconCX, iconCY, iconS, textCol);
if (hovered) ImGui::SetTooltip("%s", NavLabel(item));
}
// Badge indicator
// Tooltip when collapsed + hovered
if (!showLabels && hovered) {
ImGui::SetTooltip("%s", NavLabel(item));
}
// ---- Badge indicator ----
{
int badgeCount = 0;
bool dotOnly = false;
bool dotOnly = false;
ImU32 badgeCol = Primary();
ImU32 badgeTextCol = OnPrimary();
if (item.page == NavPage::History && status.unconfirmedTxCount > 0) {
badgeCount = status.unconfirmedTxCount;
badgeCol = Warning(); badgeTextCol = OnWarning();
badgeCol = Warning();
badgeTextCol = OnWarning();
} else if (item.page == NavPage::Mining && status.miningActive) {
dotOnly = true; badgeCol = Success();
dotOnly = true;
badgeCol = Success();
} else if (item.page == NavPage::Peers && status.peerCount > 0) {
badgeCount = status.peerCount;
}
if (badgeCount > 0 || dotOnly) {
float badgeR = dotOnly ? badgeRadiusDot : badgeRadiusNumber;
float bx = indMax.x - badgeR - 6.0f;
float by = indMin.y + badgeR + 5.0f;
dl->AddCircleFilled(ImVec2(bx, by), badgeR, badgeCol);
float badgeX, badgeY;
// Upper-right corner of button, offset from edges
badgeX = indMax.x - badgeR - 6.0f;
badgeY = indMin.y + badgeR + 5.0f;
dl->AddCircleFilled(ImVec2(badgeX, badgeY), badgeR, badgeCol);
if (!dotOnly && showLabels) {
char buf[16];
snprintf(buf, sizeof(buf), "%d", badgeCount > 99 ? 99 : badgeCount);
@@ -685,81 +693,120 @@ inline bool RenderSidebar(NavPage& current, float sidebarWidth, float contentHei
float capFsz = ScaledFontSize(capFont);
ImVec2 ts = capFont->CalcTextSizeA(capFsz, 1000.0f, 0.0f, buf);
dl->AddText(capFont, capFsz,
ImVec2(bx - ts.x * 0.5f, by - ts.y * 0.5f), badgeTextCol, buf);
ImVec2(badgeX - ts.x * 0.5f, badgeY - ts.y * 0.5f),
badgeTextCol, buf);
}
}
}
ImGui::Dummy(ImVec2(sidebarWidth, btnH + buttonSpacing)); // extra vertical spacing between buttons
ImGui::PopID();
}
// (Pill indicator removed — glass bevel on active button is sufficient)
// Reserve space for exit strip at panel bottom (drawn as overlay below)
ImGui::Dummy(ImVec2(0, stripH));
// ---- Keyboard navigation ----
if (!ImGui::GetIO().WantTextInput && !ImGui::GetIO().KeyCtrl) {
int idx = static_cast<int>(current);
bool nav = false;
if (ImGui::IsKeyPressed(ImGuiKey_UpArrow) || ImGui::IsKeyPressed(ImGuiKey_K))
if (ImGui::IsKeyPressed(ImGuiKey_UpArrow) || ImGui::IsKeyPressed(ImGuiKey_K)) {
if (idx > 0) { idx--; nav = true; }
if (ImGui::IsKeyPressed(ImGuiKey_DownArrow) || ImGui::IsKeyPressed(ImGuiKey_J))
}
if (ImGui::IsKeyPressed(ImGuiKey_DownArrow) || ImGui::IsKeyPressed(ImGuiKey_J)) {
if (idx < (int)NavPage::Count_ - 1) { idx++; nav = true; }
if (ImGui::IsKeyPressed(ImGuiKey_LeftBracket))
}
// Toggle collapse with [ key
if (ImGui::IsKeyPressed(ImGuiKey_LeftBracket)) {
collapsed = !collapsed;
if (nav) { current = static_cast<NavPage>(idx); changed = true; }
}
if (nav) {
current = static_cast<NavPage>(idx);
changed = true;
}
}
// ---- Glass panel background (channel 0) ----
// Bottom padding
ImGui::Dummy(ImVec2(0, bottomPadding));
// Measure total sidebar content height
float totalContentEndY = ImGui::GetCursorScreenPos().y;
// Draw glass panel background — capped to sidebar bounds so corners are visible
float panelBottomY = totalContentEndY;
float panelTopY = wp.y + sidebarMarginY;
// Clamp bottom so rounded corners are never clipped by the child window
float maxPanelBottom = wp.y + contentHeight - sidebarMarginY;
if (panelBottomY > maxPanelBottom) panelBottomY = maxPanelBottom;
splitter.SetCurrentChannel(dl, 0);
{
float rnd = glassRounding;
ImVec2 panelMin(panelLeft, panelTopY);
ImVec2 panelMax(panelRight, panelBotY);
GlassPanelSpec glass;
glass.rounding = glassRounding;
glass.fillAlpha = 14;
glass.borderAlpha = 25;
glass.borderWidth = 1.0f;
int vtx0 = dl->VtxBuffer.Size;
DrawGlassPanel(dl, panelMin, panelMax, glass);
int vtx1 = dl->VtxBuffer.Size;
ImVec2 panelMax(panelRight, panelBottomY);
GlassPanelSpec sidebarGlass;
sidebarGlass.rounding = rnd;
sidebarGlass.fillAlpha = 14;
sidebarGlass.borderAlpha = 25;
sidebarGlass.borderWidth = 1.0f;
// Record vertex range before drawing the glass panel fill
int glassVtx0 = dl->VtxBuffer.Size;
DrawGlassPanel(dl, panelMin, panelMax, sidebarGlass);
int glassVtx1 = dl->VtxBuffer.Size;
// Punch holes in glass where buttons are to avoid double opacity
for (int vi = vtx0; vi < vtx1; vi++) {
// Punch holes: zero out glass panel vertices that fall inside
// any button rect so the button area doesn't double-up opacity.
for (int vi = glassVtx0; vi < glassVtx1; vi++) {
ImDrawVert& v = dl->VtxBuffer[vi];
for (int bi = 0; bi < buttonRects.Size; bi++) {
const Rect& r = buttonRects[bi];
// Inset test slightly (0.5px) to avoid killing edge pixels
if (v.pos.x > r.mn.x + 0.5f && v.pos.x < r.mx.x - 0.5f &&
v.pos.y > r.mn.y + 0.5f && v.pos.y < r.mx.y - 0.5f) {
v.col = (v.col & 0x00FFFFFFu);
v.col = (v.col & 0x00FFFFFFu); // zero alpha
break;
}
}
}
}
// ---- Exit button strip (panel bottom) ----
// ---- Exit button — flush strip at bottom of sidebar panel ----
splitter.SetCurrentChannel(dl, 1);
{
float exitY = panelTopY + exitRelY;
if (exitY + stripH > panelBotY) exitY = panelBotY - stripH;
float exitX = panelLeft;
float exitW = panelRight - panelLeft;
ImVec2 exitMin(exitX, exitY);
ImVec2 exitMax(exitX + exitW, exitY + stripH);
float rnd = glassRounding;
float exitStripH = stripH;
float exitStripX = panelLeft;
float exitStripW = panelRight - panelLeft;
float exitStripY = panelBottomY - exitStripH;
ImVec2 exitMin(exitStripX, exitStripY);
ImVec2 exitMax(exitStripX + exitStripW, exitStripY + exitStripH);
ImGui::SetCursorScreenPos(exitMin);
if (ImGui::InvisibleButton("##ExitBtn", ImVec2(exitW, stripH)))
if (ImGui::InvisibleButton("##ExitBtn", ImVec2(exitStripW, exitStripH))) {
status.exitClicked = true;
}
bool exitHover = ImGui::IsItemHovered();
// Hover highlight with rounded bottom corners (matching panel)
if (exitHover) {
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
dl->AddRectFilled(exitMin, exitMax,
schema::UI().resolveColor("var(--sidebar-hover)", IM_COL32(255, 255, 255, 25)),
glassRounding, ImDrawFlags_RoundCornersBottom);
dl->AddRectFilled(exitMin, exitMax, schema::UI().resolveColor("var(--sidebar-hover)", IM_COL32(255, 255, 255, 25)),
rnd, ImDrawFlags_RoundCornersBottom);
}
dl->AddLine(ImVec2(exitMin.x + glassRounding * 0.5f, exitMin.y),
ImVec2(exitMax.x - glassRounding * 0.5f, exitMin.y),
// Subtle top separator
dl->AddLine(ImVec2(exitMin.x + rnd * 0.5f, exitMin.y),
ImVec2(exitMax.x - rnd * 0.5f, exitMin.y),
schema::UI().resolveColor("var(--sidebar-divider)", IM_COL32(255, 255, 255, 15)), 1.0f);
// Exit icon (+ label when expanded) centered in strip
ImU32 exitCol = exitHover ? Error() : schema::UI().resolveColor("var(--sidebar-icon)", IM_COL32(255, 255, 255, 60));
float cx = exitX + exitW * 0.5f;
float cy = exitY + stripH * 0.5f;
float cx = exitStripX + exitStripW * 0.5f;
float cy = exitStripY + exitStripH * 0.5f;
if (showLabels) {
ImFont* iconFont = Type().iconSmall();
@@ -768,25 +815,31 @@ inline bool RenderSidebar(NavPage& current, float sidebarWidth, float contentHei
float eIconFsz = ScaledFontSize(iconFont);
float eLblFsz = ScaledFontSize(font);
ImVec2 iconSz = iconFont->CalcTextSizeA(eIconFsz, 1000.0f, 0.0f, exitIcon);
const char* exitLabel = TR("exit");
ImVec2 labelSz = font->CalcTextSizeA(eLblFsz, 1000.0f, 0.0f, exitLabel);
float totalW = iconSz.x + exitIconGap + labelSz.x;
ImVec2 labelSz = font->CalcTextSizeA(eLblFsz, 1000.0f, 0.0f, "Exit");
float gap = exitIconGap;
float totalW = iconSz.x + gap + labelSz.x;
float startX = cx - totalW * 0.5f;
dl->AddText(iconFont, eIconFsz,
ImVec2(startX, cy - iconSz.y * 0.5f), exitCol, exitIcon);
ImVec2(startX, cy - iconSz.y * 0.5f), exitCol, exitIcon);
ImVec4 lc = ImGui::ColorConvertU32ToFloat4(exitCol);
lc.w *= expandFrac;
dl->AddText(font, eLblFsz,
ImVec2(startX + iconSz.x + exitIconGap, cy - labelSz.y * 0.5f),
ImGui::ColorConvertFloat4ToU32(lc), exitLabel);
ImVec2(startX + iconSz.x + gap, cy - labelSz.y * 0.5f),
ImGui::ColorConvertFloat4ToU32(lc), "Exit");
} else {
ImFont* iconFont = Type().iconSmall();
const char* exitIcon = ICON_MD_EXIT_TO_APP;
float eIconFsz = ScaledFontSize(iconFont);
ImVec2 iconSz = iconFont->CalcTextSizeA(eIconFsz, 1000.0f, 0.0f, exitIcon);
dl->AddText(iconFont, eIconFsz,
ImVec2(cx - iconSz.x * 0.5f, cy - iconSz.y * 0.5f), exitCol, exitIcon);
if (exitHover) ImGui::SetTooltip("%s", TR("exit"));
ImVec2(cx - iconSz.x * 0.5f, cy - iconSz.y * 0.5f),
exitCol, exitIcon);
}
if (!showLabels && exitHover) {
ImGui::SetTooltip("Exit");
}
}
-184
View File
@@ -1,184 +0,0 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include <string>
#include <cstring>
#include "../../app.h"
#include "../../util/i18n.h"
#include "../material/draw_helpers.h"
#include "../theme.h"
#include "../../embedded/IconsMaterialDesign.h"
#include "imgui.h"
namespace dragonx {
namespace ui {
class AddressLabelDialog {
public:
static void show(App* app, const std::string& address, bool isZ) {
s_open = true;
s_app = app;
s_address = address;
s_isZ = isZ;
s_selectedIcon = -1;
// Pre-fill from existing metadata
std::string existing = app->getAddressLabel(address);
std::strncpy(s_label, existing.c_str(), sizeof(s_label) - 1);
s_label[sizeof(s_label) - 1] = '\0';
std::string existingIcon = app->getAddressIcon(address);
for (int i = 0; i < kIconCount; ++i) {
if (kIconNames[i] == existingIcon) {
s_selectedIcon = i;
break;
}
}
}
static void render() {
if (!s_open || !s_app) return;
using namespace material;
if (BeginOverlayDialog(TR("set_label"), &s_open, 420.0f, 0.92f)) {
float dp = Layout::dpiScale();
// Address preview
Type().textColored(TypeStyle::Caption, OnSurfaceMedium(),
s_isZ ? TR("shielded_address") : TR("transparent_address"));
ImGui::Spacing();
{
std::string display = s_address;
if (display.size() > 42) display = display.substr(0, 20) + "..." + display.substr(display.size() - 16);
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), display.c_str());
}
ImGui::Spacing();
ImGui::Spacing();
// Label input
Type().text(TypeStyle::Subtitle2, TR("label"));
ImGui::SetNextItemWidth(-1);
ImGui::InputTextWithHint("##AddrLabel", TR("label_placeholder"), s_label, sizeof(s_label));
ImGui::Spacing();
ImGui::Spacing();
// Icon picker grid
Type().text(TypeStyle::Subtitle2, TR("choose_icon"));
ImGui::Spacing();
ImFont* iconFont = Type().iconMed();
float iconFsz = ScaledFontSize(iconFont);
float cellSz = iconFsz + 16.0f * dp;
float avail = ImGui::GetContentRegionAvail().x;
int cols = std::max(1, (int)(avail / (cellSz + 4.0f * dp)));
ImDrawList* dl = ImGui::GetWindowDrawList();
for (int i = 0; i < kIconCount; ++i) {
if (i % cols != 0) ImGui::SameLine(0, 4.0f * dp);
ImVec2 pos = ImGui::GetCursorScreenPos();
ImVec2 mn = pos;
ImVec2 mx(pos.x + cellSz, pos.y + cellSz);
bool hov = ImGui::IsMouseHoveringRect(mn, mx);
bool sel = (s_selectedIcon == i);
// Background
if (sel) {
dl->AddRectFilled(mn, mx, WithAlpha(Primary(), 40), 6.0f * dp);
dl->AddRect(mn, mx, WithAlpha(Primary(), 120), 6.0f * dp, 0, 1.5f * dp);
} else if (hov) {
dl->AddRectFilled(mn, mx, IM_COL32(255, 255, 255, 20), 6.0f * dp);
}
// Icon centered in cell
ImVec2 iSz = iconFont->CalcTextSizeA(iconFsz, 1000.0f, 0.0f, kIconGlyphs[i]);
dl->AddText(iconFont, iconFsz,
ImVec2(mn.x + (cellSz - iSz.x) * 0.5f, mn.y + (cellSz - iSz.y) * 0.5f),
sel ? Primary() : (hov ? OnSurface() : OnSurfaceMedium()),
kIconGlyphs[i]);
ImGui::PushID(i);
ImGui::InvisibleButton("##icon", ImVec2(cellSz, cellSz));
if (ImGui::IsItemClicked()) s_selectedIcon = i;
if (hov) ImGui::SetTooltip("%s", kIconNames[i]);
ImGui::PopID();
}
// "No icon" option
ImGui::Spacing();
if (s_selectedIcon >= 0) {
if (ImGui::SmallButton(TR("clear_icon"))) {
s_selectedIcon = -1;
}
}
ImGui::Spacing();
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
// Buttons
float btnW = 120.0f;
float totalW = btnW * 2 + Layout::spacingMd();
ImGui::SetCursorPosX((ImGui::GetContentRegionAvail().x - totalW) * 0.5f);
if (TactileButton(TR("cancel"), ImVec2(btnW, 0))) {
s_open = false;
}
ImGui::SameLine(0, Layout::spacingMd());
if (TactileButton(TR("save"), ImVec2(btnW, 0))) {
// Apply changes
s_app->setAddressLabel(s_address, s_label);
if (s_selectedIcon >= 0)
s_app->setAddressIcon(s_address, kIconNames[s_selectedIcon]);
else
s_app->setAddressIcon(s_address, "");
s_open = false;
}
EndOverlayDialog();
}
}
static bool isOpen() { return s_open; }
private:
static inline bool s_open = false;
static inline App* s_app = nullptr;
static inline std::string s_address;
static inline bool s_isZ = false;
static inline char s_label[128] = {};
static inline int s_selectedIcon = -1;
// Icon palette — wallet-relevant Material Design icons
static constexpr int kIconCount = 20;
static inline const char* kIconNames[kIconCount] = {
"savings", "account_balance", "wallet", "payments",
"diamond", "shield", "lock", "swap_horiz",
"store", "home", "work", "rocket_launch",
"favorite", "bolt", "token", "category",
"label", "coffee", "volunteer", "star"
};
static inline const char* kIconGlyphs[kIconCount] = {
ICON_MD_SAVINGS, ICON_MD_ACCOUNT_BALANCE, ICON_MD_WALLET, ICON_MD_PAYMENTS,
ICON_MD_DIAMOND, ICON_MD_SHIELD, ICON_MD_LOCK, ICON_MD_SWAP_HORIZ,
ICON_MD_STORE, ICON_MD_HOME, ICON_MD_WORK, ICON_MD_ROCKET_LAUNCH,
ICON_MD_FAVORITE, ICON_MD_BOLT, ICON_MD_TOKEN, ICON_MD_CATEGORY,
ICON_MD_LABEL, ICON_MD_LOCAL_CAFE, ICON_MD_VOLUNTEER_ACTIVISM, ICON_MD_STAR
};
public:
// Expose for the address list to look up icon glyphs by name
static const char* iconGlyphForName(const std::string& name) {
for (int i = 0; i < kIconCount; ++i)
if (kIconNames[i] == name) return kIconGlyphs[i];
return nullptr;
}
};
} // namespace ui
} // namespace dragonx
-293
View File
@@ -1,293 +0,0 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include <string>
#include <cstdio>
#include <cstring>
#include <cmath>
#include "../../app.h"
#include "../../util/i18n.h"
#include "../material/draw_helpers.h"
#include "../theme.h"
#include "imgui.h"
namespace dragonx {
namespace ui {
/**
* @brief Modal dialog for transferring funds between two addresses.
*
* Shown when a user drags one address row onto another in the address list.
* Displays from/to addresses, balances, amount input, fee, and a preview of
* the resulting balances after transfer.
*/
struct AddressTransferInfo {
std::string fromAddr;
std::string toAddr;
double fromBalance = 0.0;
double toBalance = 0.0;
bool fromIsZ = false;
bool toIsZ = false;
};
class AddressTransferDialog {
public:
using TransferInfo = AddressTransferInfo;
static void show(App* app, const TransferInfo& info) {
s_open = true;
s_app = app;
s_info = info;
s_sending = false;
s_resultMsg.clear();
s_success = false;
// Pre-fill amount with full source balance
snprintf(s_amount, sizeof(s_amount), "%.8f", info.fromBalance);
// Default fee
s_fee = 0.0001;
}
static void render() {
if (!s_open || !s_app) return;
using namespace material;
if (BeginOverlayDialog(TR("transfer_funds"), &s_open, 480.0f, 0.94f)) {
float dp = Layout::dpiScale();
ImDrawList* dl = ImGui::GetWindowDrawList();
// If we got a result, show it
if (!s_resultMsg.empty()) {
renderResult();
EndOverlayDialog();
return;
}
// Shielding / deshielding warning
if (s_info.fromIsZ && !s_info.toIsZ) {
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(Warning()));
ImGui::TextWrapped("%s", TR("deshielding_warning"));
ImGui::PopStyleColor();
ImGui::Spacing();
} else if (!s_info.fromIsZ && s_info.toIsZ) {
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(Success()));
ImGui::TextWrapped("%s", TR("shielding_notice"));
ImGui::PopStyleColor();
ImGui::Spacing();
}
// From address
Type().text(TypeStyle::Overline, TR("from"));
renderAddressRow(s_info.fromAddr, s_info.fromBalance, s_info.fromIsZ, dp);
ImGui::Spacing();
// Arrow
{
float arrowCX = ImGui::GetContentRegionAvail().x * 0.5f;
ImGui::SetCursorPosX(arrowCX - 8.0f);
ImFont* iconFont = Type().iconMed();
float fsz = ScaledFontSize(iconFont);
ImVec2 pos = ImGui::GetCursorScreenPos();
dl->AddText(iconFont, fsz, pos, OnSurfaceMedium(), ICON_MD_ARROW_DOWNWARD);
ImGui::Dummy(ImVec2(fsz, fsz));
}
ImGui::Spacing();
// To address
Type().text(TypeStyle::Overline, TR("to"));
renderAddressRow(s_info.toAddr, s_info.toBalance, s_info.toIsZ, dp);
ImGui::Spacing();
ImGui::Spacing();
// Amount input
Type().text(TypeStyle::Subtitle2, TR("amount"));
ImGui::SetNextItemWidth(-1);
ImGui::InputText("##TransferAmt", s_amount, sizeof(s_amount),
ImGuiInputTextFlags_CharsDecimal);
// Max button
ImGui::SameLine();
if (ImGui::SmallButton(TR("max"))) {
double maxAmt = s_info.fromBalance - s_fee;
if (maxAmt < 0) maxAmt = 0;
snprintf(s_amount, sizeof(s_amount), "%.8f", maxAmt);
}
ImGui::Spacing();
// Fee display
{
char feeBuf[64];
snprintf(feeBuf, sizeof(feeBuf), "%s: %.4f DRGX", TR("fee"), s_fee);
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), feeBuf);
}
ImGui::Spacing();
// Preview after transfer
double amount = atof(s_amount);
double totalDeduct = amount + s_fee;
double newFromBal = s_info.fromBalance - totalDeduct;
double newToBal = s_info.toBalance + amount;
bool amountValid = amount > 0 && totalDeduct <= s_info.fromBalance;
ImGui::Separator();
ImGui::Spacing();
Type().text(TypeStyle::Subtitle2, TR("result_preview"));
ImGui::Spacing();
{
char buf[128];
snprintf(buf, sizeof(buf), "%s: %.8f DRGX → %.8f DRGX",
TR("sender_balance"), s_info.fromBalance, amountValid ? newFromBal : s_info.fromBalance);
Type().textColored(TypeStyle::Caption,
(amountValid && newFromBal < 1e-9) ? Warning() : OnSurfaceMedium(), buf);
}
{
char buf[128];
snprintf(buf, sizeof(buf), "%s: %.8f DRGX → %.8f DRGX",
TR("recipient_balance"), s_info.toBalance, amountValid ? newToBal : s_info.toBalance);
Type().textColored(TypeStyle::Caption, OnSurfaceMedium(), buf);
}
// Warnings
if (!amountValid && amount > 0) {
ImGui::Spacing();
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(Error()));
ImGui::TextWrapped("%s", TR("insufficient_funds"));
ImGui::PopStyleColor();
}
if (amountValid && newFromBal < 1e-9) {
ImGui::Spacing();
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(Warning()));
ImGui::TextWrapped("%s", TR("sends_full_balance_warning"));
ImGui::PopStyleColor();
}
ImGui::Spacing();
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
// Buttons
float btnW = 140.0f;
float totalW = btnW * 2 + Layout::spacingMd();
ImGui::SetCursorPosX((ImGui::GetContentRegionAvail().x - totalW) * 0.5f);
if (TactileButton(TR("cancel"), ImVec2(btnW, 0))) {
s_open = false;
}
ImGui::SameLine(0, Layout::spacingMd());
ImGui::BeginDisabled(!amountValid || s_sending);
if (TactileButton(s_sending ? TR("sending") : TR("confirm_transfer"), ImVec2(btnW, 0))) {
s_sending = true;
s_app->sendTransaction(s_info.fromAddr, s_info.toAddr,
amount, s_fee, "",
[](bool ok, const std::string& result) {
s_sending = false;
s_success = ok;
if (ok)
s_resultMsg = result; // opid
else
s_resultMsg = result; // error message
});
}
ImGui::EndDisabled();
EndOverlayDialog();
}
}
static bool isOpen() { return s_open; }
static void close() { s_open = false; }
private:
static void renderAddressRow(const std::string& addr, double balance, bool isZ, float dp) {
using namespace material;
ImDrawList* dl = ImGui::GetWindowDrawList();
ImVec2 pos = ImGui::GetCursorScreenPos();
float w = ImGui::GetContentRegionAvail().x;
float h = 40.0f * dp;
ImVec2 mn = pos, mx(pos.x + w, pos.y + h);
GlassPanelSpec g;
g.rounding = 8.0f * dp;
g.fillAlpha = 18;
DrawGlassPanel(dl, mn, mx, g);
float pad = 12.0f * dp;
// Type icon
ImFont* iconFont = Type().iconSmall();
const char* icon = isZ ? ICON_MD_SHIELD : ICON_MD_CIRCLE;
ImU32 col = isZ ? Success() : Warning();
float iconFsz = ScaledFontSize(iconFont);
ImVec2 iSz = iconFont->CalcTextSizeA(iconFsz, 1000.0f, 0.0f, icon);
dl->AddText(iconFont, iconFsz, ImVec2(mn.x + pad, mn.y + (h - iSz.y) * 0.5f), col, icon);
// Address (truncated)
float textX = mn.x + pad + iSz.x + 8.0f * dp;
std::string display = addr;
if (display.size() > 42) display = display.substr(0, 18) + "..." + display.substr(display.size() - 14);
ImFont* capFont = Type().caption();
float capFsz = ScaledFontSize(capFont);
dl->AddText(capFont, capFsz, ImVec2(textX, mn.y + (h * 0.5f - capFsz)), OnSurfaceMedium(), display.c_str());
// Balance (right-aligned)
char balBuf[32];
snprintf(balBuf, sizeof(balBuf), "%.8f DRGX", balance);
ImFont* body = Type().body2();
float bodyFsz = ScaledFontSize(body);
ImVec2 balSz = body->CalcTextSizeA(bodyFsz, 1000.0f, 0.0f, balBuf);
dl->AddText(body, bodyFsz, ImVec2(mx.x - pad - balSz.x, mn.y + (h - balSz.y) * 0.5f),
balance > 0 ? OnSurface() : OnSurfaceDisabled(), balBuf);
ImGui::Dummy(ImVec2(w, h));
}
static void renderResult() {
using namespace material;
if (s_success) {
ImGui::Spacing();
Type().text(TypeStyle::H6, TR("transfer_sent"));
ImGui::Spacing();
Type().textColored(TypeStyle::Caption, OnSurfaceMedium(), TR("transfer_sent_desc"));
ImGui::Spacing();
{
char buf[128];
snprintf(buf, sizeof(buf), "Operation ID: %s",
s_resultMsg.size() > 40
? (s_resultMsg.substr(0, 20) + "..." + s_resultMsg.substr(s_resultMsg.size() - 10)).c_str()
: s_resultMsg.c_str());
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), buf);
}
} else {
ImGui::Spacing();
Type().textColored(TypeStyle::H6, Error(), TR("transfer_failed"));
ImGui::Spacing();
ImGui::TextWrapped("%s", s_resultMsg.c_str());
}
ImGui::Spacing();
ImGui::Spacing();
float btnW = 120.0f;
ImGui::SetCursorPosX((ImGui::GetContentRegionAvail().x - btnW) * 0.5f);
if (TactileButton(TR("close"), ImVec2(btnW, 0))) {
s_open = false;
}
}
static inline bool s_open = false;
static inline App* s_app = nullptr;
static inline TransferInfo s_info;
static inline char s_amount[64] = {};
static inline double s_fee = 0.0001;
static inline bool s_sending = false;
static inline bool s_success = false;
static inline std::string s_resultMsg;
};
} // namespace ui
} // namespace dragonx
+1 -1
View File
@@ -144,7 +144,7 @@ void BackupWalletDialog::render(App* app)
s_status = statusMsg;
s_backing_up = false;
if (success) {
Notifications::instance().success(TR("backup_created"), 5.0f);
Notifications::instance().success(TR("backup_created"));
} else {
Notifications::instance().warning(statusMsg);
}
+183 -396
View File
@@ -5,8 +5,6 @@
#include "balance_tab.h"
#include "key_export_dialog.h"
#include "qr_popup_dialog.h"
#include "address_label_dialog.h"
#include "address_transfer_dialog.h"
#include "send_tab.h"
#include "../../app.h"
#include "../../config/settings.h"
@@ -328,7 +326,7 @@ static void RenderBalanceClassic(App* app)
// Lerp displayed balances toward actual values
{
float dt = ImGui::GetIO().DeltaTime;
float speed = (app->settings() && app->settings()->getReduceMotion()) ? 999.0f : kBalanceLerpSpeed;
float speed = kBalanceLerpSpeed;
auto lerp = [](double& disp, double target, float dt, float spd) {
double diff = target - disp;
if (std::abs(diff) < 1e-9) { disp = target; return; }
@@ -1280,7 +1278,7 @@ static void UpdateBalanceLerp(App* app) {
const float kBalanceLerpSpeed = S.drawElement("tabs.balance", "balance-lerp-speed").sizeOr(8.0f);
const auto& state = app->state();
float dt = ImGui::GetIO().DeltaTime;
float speed = (app->settings() && app->settings()->getReduceMotion()) ? 999.0f : kBalanceLerpSpeed;
float speed = kBalanceLerpSpeed;
auto lerp = [](double& disp, double target, float dt, float spd) {
double diff = target - disp;
if (std::abs(diff) < 1e-9) { disp = target; return; }
@@ -1362,70 +1360,50 @@ static void RenderSharedAddressList(App* app, float listH, float availW,
Type().text(TypeStyle::H6, TR("your_addresses"));
ImGui::Spacing();
// ---- Persistent state ----
static char addr_search[128] = "";
static bool s_hideZeroBalances = true;
static bool s_showHidden = false;
// Drag state
static int s_dragIdx = -1; // row being dragged (-1 = none)
static float s_dragOffsetY = 0.0f; // mouse offset from row top
static float s_dragStartY = 0.0f; // mouse Y at drag start
static bool s_dragActive = false; // drag distance threshold passed
static int s_dropTargetIdx = -1; // row hovered during drag
// Copy feedback
static int s_copiedRow = -1;
static float s_copiedTime = 0.0f;
// ---- Build and filter address rows ----
struct AddrRow {
const AddressInfo* info;
bool isZ, hidden, favorite;
std::string label, icon;
int sortOrder;
};
struct AddrRow { const AddressInfo* info; bool isZ; bool hidden; bool favorite; };
std::vector<AddrRow> rows;
rows.reserve(state.z_addresses.size() + state.t_addresses.size());
auto addRows = [&](const auto& addrs, bool isZ) {
for (const auto& a : addrs) {
std::string filter(addr_search);
std::string addrLabel = app->getAddressLabel(a.address);
if (!containsIgnoreCase(a.address, filter) &&
!containsIgnoreCase(a.label, filter) &&
!containsIgnoreCase(addrLabel, filter)) continue;
bool isHidden = app->isAddressHidden(a.address);
if (isHidden && !s_showHidden) continue;
bool isFav = app->isAddressFavorite(a.address);
if (s_hideZeroBalances && a.balance < 1e-9 && !isHidden && !isFav) continue;
rows.push_back({&a, isZ, isHidden, isFav,
addrLabel, app->getAddressIcon(a.address),
app->getAddressSortOrder(a.address)});
}
};
addRows(state.z_addresses, true);
addRows(state.t_addresses, false);
// Sort: custom order (if any) → favorites → Z first → balance desc
for (const auto& a : state.z_addresses) {
std::string filter(addr_search);
if (!containsIgnoreCase(a.address, filter) &&
!containsIgnoreCase(a.label, filter)) continue;
bool isHidden = app->isAddressHidden(a.address);
if (isHidden && !s_showHidden) continue;
bool isFav = app->isAddressFavorite(a.address);
if (s_hideZeroBalances && a.balance < 1e-9 && !isHidden && !isFav) continue;
rows.push_back({&a, true, isHidden, isFav});
}
for (const auto& a : state.t_addresses) {
std::string filter(addr_search);
if (!containsIgnoreCase(a.address, filter) &&
!containsIgnoreCase(a.label, filter)) continue;
bool isHidden = app->isAddressHidden(a.address);
if (isHidden && !s_showHidden) continue;
bool isFav = app->isAddressFavorite(a.address);
if (s_hideZeroBalances && a.balance < 1e-9 && !isHidden && !isFav) continue;
rows.push_back({&a, false, isHidden, isFav});
}
static int s_sortCol = 3;
static bool s_sortAsc = false;
std::sort(rows.begin(), rows.end(),
[](const AddrRow& a, const AddrRow& b) -> bool {
bool aHasOrder = a.sortOrder >= 0;
bool bHasOrder = b.sortOrder >= 0;
if (aHasOrder && bHasOrder) return a.sortOrder < b.sortOrder;
if (aHasOrder != bHasOrder) return aHasOrder > bHasOrder;
if (a.favorite != b.favorite) return a.favorite > b.favorite;
if (a.isZ != b.isZ) return a.isZ > b.isZ;
return a.info->balance > b.info->balance;
if (s_sortAsc) return a.info->balance < b.info->balance;
else return a.info->balance > b.info->balance;
});
// ---- Toolbar: search + checkboxes + create buttons ----
// Search + create buttons row
float avail = ImGui::GetContentRegionAvail().x;
float schemaMaxW = (searchIn.maxWidth >= 0) ? searchIn.maxWidth : 250.0f;
float schemaRatio = (searchIn.widthRatio >= 0) ? searchIn.widthRatio : 0.30f;
float searchW = std::min(schemaMaxW * hs, avail * schemaRatio);
ImGui::SetNextItemWidth(searchW);
ImGui::InputTextWithHint("##AddrSearch", TR("filter"), addr_search, sizeof(addr_search));
ImGui::InputTextWithHint("##AddrSearch", "Filter...", addr_search, sizeof(addr_search));
ImGui::SameLine(0, Layout::spacingLg());
ImGui::Checkbox(TrId("hide_zero_balances", "hide0_v2").c_str(), &s_hideZeroBalances);
{
@@ -1477,7 +1455,6 @@ static void RenderSharedAddressList(App* app, float listH, float availW,
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
// ---- Glass panel container ----
float addrListH = listH;
if (addrListH < 40.0f * dp) addrListH = 40.0f * dp;
@@ -1500,7 +1477,6 @@ static void RenderSharedAddressList(App* app, float listH, float availW,
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
// ---- Empty states ----
if (!app->isConnected()) {
ImGui::Dummy(ImVec2(0, 16 * dp));
float cw = ImGui::GetContentRegionAvail().x;
@@ -1511,17 +1487,23 @@ static void RenderSharedAddressList(App* app, float listH, float availW,
float cw = ImGui::GetContentRegionAvail().x;
float ch = ImGui::GetContentRegionAvail().y;
if (ch < 60) ch = 60;
const char* emptyMsg = addr_search[0] ? TR("no_addresses_match") : TR("no_addresses_yet");
ImVec2 msgSz = ImGui::CalcTextSize(emptyMsg);
ImGui::SetCursorPosX((cw - msgSz.x) * 0.5f);
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ch * 0.25f);
ImGui::TextDisabled("%s", emptyMsg);
if (addr_search[0]) {
ImVec2 textSz = ImGui::CalcTextSize(TR("no_addresses_match"));
ImGui::SetCursorPosX((cw - textSz.x) * 0.5f);
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ch * 0.25f);
ImGui::TextDisabled("%s", TR("no_addresses_match"));
} else {
const char* msg = "No addresses yet";
ImVec2 msgSz = ImGui::CalcTextSize(msg);
ImGui::SetCursorPosX((cw - msgSz.x) * 0.5f);
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ch * 0.25f);
ImGui::TextDisabled("%s", msg);
}
} else {
// ---- PASS 1: Compute row layout ----
ImDrawList* dl = ImGui::GetWindowDrawList();
ImFont* capFont = Type().caption();
ImFont* body2 = Type().body2();
float rowH = body2->LegacySize + capFont->LegacySize + Layout::spacingLg() + Layout::spacingMd() + Layout::spacingMd();
float rowH = body2->LegacySize + capFont->LegacySize + Layout::spacingLg() + Layout::spacingMd();
static int selected_row = -1;
addrScrollY = ImGui::GetScrollY();
addrScrollMaxY = ImGui::GetScrollMaxY();
@@ -1533,346 +1515,181 @@ static void RenderSharedAddressList(App* app, float listH, float availW,
S.drawElement("tabs.balance", "address-icon-size").size * hs);
float innerW = ImGui::GetContentRegionAvail().x;
// Theme colors for buttons (resolved once)
ImU32 favGoldFill = S.resolveColor("var(--favorite-fill)", IM_COL32(255, 200, 50, 40));
ImU32 favGoldBorder = S.resolveColor("var(--favorite-border)", IM_COL32(255, 200, 50, 100));
ImU32 favGoldIcon = S.resolveColor("var(--favorite-icon)", IM_COL32(255, 200, 50, 255));
ImU32 favGoldOutline= S.resolveColor("var(--favorite-outline)", IM_COL32(255, 200, 50, 120));
ImU32 btnFill = S.resolveColor("var(--row-button-fill)", IM_COL32(255, 255, 255, 12));
ImU32 btnFillHov = S.resolveColor("var(--row-button-fill-hover)", IM_COL32(255, 255, 255, 25));
ImU32 btnBorder = S.resolveColor("var(--row-button-border)", IM_COL32(255, 255, 255, 25));
ImU32 btnBorderHov = S.resolveColor("var(--row-button-border-hover)", IM_COL32(255, 255, 255, 50));
ImU32 rowHoverCol = S.resolveColor("var(--row-hover)", IM_COL32(255, 255, 255, 15));
ImU32 rowSelectCol = S.resolveColor("var(--row-select)", IM_COL32(255, 255, 255, 20));
ImU32 dividerCol = S.resolveColor("var(--sidebar-divider)", IM_COL32(255, 255, 255, 15));
// Compute Y positions for all rows (pass 1)
std::vector<float> rowY(rows.size());
float cursorStartY = ImGui::GetCursorScreenPos().y;
for (int i = 0; i < (int)rows.size(); ++i)
rowY[i] = cursorStartY + i * rowH;
// Drag logic — detect drag start, compute drag position
ImVec2 mousePos = ImGui::GetMousePos();
bool mouseDown = ImGui::IsMouseDown(ImGuiMouseButton_Left);
bool mouseClicked = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
// Reset drop target each frame — it gets set only if mouse is over a row.
// Preserve the drop target on the release frame so the drop handler can use it.
if (s_dragActive && mouseDown) s_dropTargetIdx = -1;
if (s_dragIdx >= 0 && !mouseDown) {
// Mouse released — copy if it was a click (no drag activated)
if (!s_dragActive && s_dragIdx >= 0 && s_dragIdx < (int)rows.size()) {
const auto& clickRow = rows[s_dragIdx];
ImGui::SetClipboardText(clickRow.info->address.c_str());
selected_row = s_dragIdx;
s_copiedRow = s_dragIdx;
s_copiedTime = (float)ImGui::GetTime();
}
// Drop
if (s_dragActive && s_dropTargetIdx >= 0 && s_dropTargetIdx != s_dragIdx) {
const auto& srcRow = rows[s_dragIdx];
const auto& dstRow = rows[s_dropTargetIdx];
// Transfer: if dropped on another row and drag was active
if (srcRow.info->balance > 1e-9) {
AddressTransferDialog::TransferInfo ti;
ti.fromAddr = srcRow.info->address;
ti.toAddr = dstRow.info->address;
ti.fromBalance = srcRow.info->balance;
ti.toBalance = dstRow.info->balance;
ti.fromIsZ = srcRow.isZ;
ti.toIsZ = dstRow.isZ;
AddressTransferDialog::show(app, ti);
}
} else if (s_dragActive && s_dropTargetIdx < 0) {
// Reorder: dropped in gap — compute insert position from mouseY
int insertIdx = 0;
for (int i = 0; i < (int)rows.size(); ++i) {
if (mousePos.y > rowY[i] + rowH * 0.5f) insertIdx = i + 1;
}
if (insertIdx != s_dragIdx && insertIdx != s_dragIdx + 1) {
int targetIdx = (insertIdx > s_dragIdx) ? insertIdx - 1 : insertIdx;
if (targetIdx >= 0 && targetIdx < (int)rows.size()) {
app->swapAddressOrder(rows[s_dragIdx].info->address,
rows[targetIdx].info->address);
}
}
}
s_dragIdx = -1;
s_dragActive = false;
s_dropTargetIdx = -1;
}
// ---- PASS 2: Render rows ----
for (int row_idx = 0; row_idx < (int)rows.size(); row_idx++) {
const auto& row = rows[row_idx];
const auto& addr = *row.info;
bool isDragged = (s_dragIdx == row_idx && s_dragActive);
// For dragged row, offset Y to follow mouse
float drawY = rowY[row_idx];
if (isDragged) {
drawY = mousePos.y - s_dragOffsetY;
}
ImVec2 rowPos(ImGui::GetCursorScreenPos().x, drawY);
ImVec2 rowEnd(rowPos.x + innerW, drawY + rowH);
ImVec2 rowPos = ImGui::GetCursorScreenPos();
ImVec2 rowEnd(rowPos.x + innerW, rowPos.y + rowH);
ImU32 typeCol = row.isZ ? greenCol : goldCol;
if (row.hidden) typeCol = OnSurfaceDisabled();
// Dragged row: draw with semi-transparent elevation
if (isDragged) {
dl->AddRectFilled(ImVec2(rowPos.x - 2*dp, rowPos.y - 2*dp),
ImVec2(rowEnd.x + 2*dp, rowEnd.y + 2*dp),
IM_COL32(0, 0, 0, 30), 6.0f * dp);
dl->AddRectFilled(rowPos, rowEnd, IM_COL32(30, 30, 40, 120), 4.0f * dp);
// Tooltip following cursor — show transfer intent if over a target row
if (s_dropTargetIdx >= 0 && s_dropTargetIdx < (int)rows.size()) {
const auto& target = rows[s_dropTargetIdx];
ImGui::SetTooltip("%s\n%s\n\n%s %s",
truncateAddress(addr.address, 32).c_str(),
row.isZ ? TR("shielded") : TR("transparent"),
TR("transfer_to"),
truncateAddress(target.info->address, 32).c_str());
} else {
ImGui::SetTooltip("%s\n%s",
truncateAddress(addr.address, 32).c_str(),
row.isZ ? TR("shielded") : TR("transparent"));
}
}
// Golden border for favorites — inset so it doesn't touch container
// Golden border for favorites
if (row.favorite) {
float inset = 2.0f * dp;
dl->AddRect(ImVec2(rowPos.x + inset, rowPos.y + inset),
ImVec2(rowEnd.x - inset, rowEnd.y - inset),
favGoldOutline, 4.0f * dp, 0, 1.5f * dp);
ImU32 favBorder = IM_COL32(255, 200, 50, 120);
dl->AddRect(rowPos, rowEnd, favBorder, 4.0f * dp, 0, 1.5f * dp);
}
// Selection highlight
if (selected_row == row_idx && !isDragged) {
if (selected_row == row_idx) {
ImDrawFlags accentFlags = 0;
float accentRound = 2.0f * dp;
if (row_idx == 0) { accentFlags = ImDrawFlags_RoundCornersTopLeft; accentRound = glassRound; }
if (row_idx == (int)rows.size() - 1) { accentFlags |= ImDrawFlags_RoundCornersBottomLeft; accentRound = glassRound; }
dl->AddRectFilled(rowPos, ImVec2(rowPos.x + 3 * dp, rowEnd.y), typeCol, accentRound, accentFlags);
dl->AddRectFilled(rowPos, rowEnd, rowSelectCol, 4.0f * dp);
dl->AddRectFilled(rowPos, rowEnd, IM_COL32(255, 255, 255, 20), 4.0f * dp);
}
// Hover effects
bool hovered = !isDragged && material::IsRectHovered(rowPos, rowEnd);
// Drop target highlight when dragging another row over this one
if (s_dragActive && s_dragIdx != row_idx && material::IsRectHovered(rowPos, rowEnd)) {
s_dropTargetIdx = row_idx;
ImU32 dropCol = WithAlpha(Primary(), 40);
dl->AddRectFilled(rowPos, rowEnd, dropCol, 4.0f * dp);
dl->AddRect(rowPos, rowEnd, WithAlpha(Primary(), 140), 4.0f * dp, 0, 2.0f * dp);
}
if (hovered && selected_row != row_idx && !s_dragActive) {
dl->AddRectFilled(rowPos, rowEnd, rowHoverCol, 4.0f * dp);
bool hovered = material::IsRectHovered(rowPos, rowEnd);
if (hovered && selected_row != row_idx) {
dl->AddRectFilled(rowPos, rowEnd, IM_COL32(255, 255, 255, 15), 4.0f * dp);
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
}
float cx = rowPos.x + rowPadLeft;
float cy = rowPos.y + Layout::spacingMd();
// ---- Button zone (right edge): [eye] [star] ----
float btnH = rowH - Layout::spacingMd() * 2.0f;
// --- Button zone (right edge): [eye] [star] ---
float btnH = rowH - Layout::spacingSm() * 2.0f;
float btnW = btnH;
float btnGap = Layout::spacingSm();
float btnY = rowPos.y + (rowH - btnH) * 0.5f;
float rightEdge = rowPos.x + innerW;
float starX = rightEdge - btnW - Layout::spacingXs();
float starX = rightEdge - btnW - rowPadRight;
float eyeX = starX - btnGap - btnW;
float btnRound = 6.0f * dp;
bool btnClicked = false;
if (!isDragged) {
// Star button
{
ImVec2 bMin(starX, btnY), bMax(starX + btnW, btnY + btnH);
bool bHov = ImGui::IsMouseHoveringRect(bMin, bMax);
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);
ImFont* iconFont = Type().iconSmall();
const char* starIcon = row.favorite ? ICON_MD_STAR : ICON_MD_STAR_BORDER;
ImVec2 iSz = iconFont->CalcTextSizeA(iconFont->LegacySize, 1000.0f, 0.0f, starIcon);
ImU32 starCol = row.favorite ? favGoldIcon : (bHov ? OnSurface() : OnSurfaceDisabled());
dl->AddText(iconFont, iconFont->LegacySize,
ImVec2(starX + (btnW - iSz.x) * 0.5f, btnY + (btnH - iSz.y) * 0.5f), starCol, starIcon);
if (bHov && mouseClicked) {
if (row.favorite) app->unfavoriteAddress(addr.address);
else app->favoriteAddress(addr.address);
btnClicked = true;
}
if (bHov) ImGui::SetTooltip("%s", row.favorite ? TR("remove_favorite") : TR("favorite_address"));
// Star button (always shown, rightmost)
{
ImVec2 bMin(starX, btnY), bMax(starX + btnW, btnY + btnH);
bool bHov = ImGui::IsMouseHoveringRect(bMin, bMax);
ImU32 starFill = row.favorite ? IM_COL32(255, 200, 50, 40) : IM_COL32(255, 255, 255, bHov ? 25 : 12);
ImU32 starBorder = row.favorite ? IM_COL32(255, 200, 50, 100) : IM_COL32(255, 255, 255, bHov ? 50 : 25);
dl->AddRectFilled(bMin, bMax, starFill, btnRound);
dl->AddRect(bMin, bMax, starBorder, btnRound, 0, 1.0f * dp);
ImFont* iconFont = material::Type().iconSmall();
const char* starIcon = row.favorite ? ICON_MD_STAR : ICON_MD_STAR_BORDER;
ImVec2 iSz = iconFont->CalcTextSizeA(iconFont->LegacySize, 1000.0f, 0.0f, starIcon);
ImU32 starCol = row.favorite ? IM_COL32(255, 200, 50, 255) : (bHov ? OnSurface() : OnSurfaceDisabled());
dl->AddText(iconFont, iconFont->LegacySize,
ImVec2(starX + (btnW - iSz.x) * 0.5f, btnY + (btnH - iSz.y) * 0.5f), starCol, starIcon);
if (bHov && ImGui::IsMouseClicked(0)) {
if (row.favorite) app->unfavoriteAddress(addr.address);
else app->favoriteAddress(addr.address);
btnClicked = true;
}
// Eye button (zero balance or hidden)
bool showEye = true;
// Always reserve space for both buttons so content doesn't shift
float contentRight = eyeX - Layout::spacingSm();
if (showEye) {
ImVec2 bMin(eyeX, btnY), bMax(eyeX + btnW, btnY + btnH);
bool bHov = ImGui::IsMouseHoveringRect(bMin, bMax);
dl->AddRectFilled(bMin, bMax, bHov ? btnFillHov : btnFill, btnRound);
dl->AddRect(bMin, bMax, bHov ? btnBorderHov : btnBorder, btnRound, 0, 1.0f * dp);
ImFont* iconFont = Type().iconSmall();
const char* hideIcon = row.hidden ? ICON_MD_VISIBILITY : ICON_MD_VISIBILITY_OFF;
ImVec2 iSz = iconFont->CalcTextSizeA(iconFont->LegacySize, 1000.0f, 0.0f, hideIcon);
ImU32 iconCol = bHov ? OnSurface() : OnSurfaceDisabled();
dl->AddText(iconFont, iconFont->LegacySize,
ImVec2(eyeX + (btnW - iSz.x) * 0.5f, btnY + (btnH - iSz.y) * 0.5f), iconCol, hideIcon);
if (bHov && mouseClicked) {
if (row.hidden) app->unhideAddress(addr.address);
else app->hideAddress(addr.address);
btnClicked = true;
}
if (bHov) ImGui::SetTooltip("%s", row.hidden ? TR("restore_address") : TR("hide_address"));
}
// ---- Type icon or custom icon ----
float iconCx = cx + rowIconSz;
float iconCy = cy + body2->LegacySize * 0.5f;
{
const char* customGlyph = row.icon.empty() ? nullptr : AddressLabelDialog::iconGlyphForName(row.icon);
ImFont* iconFont = Type().iconSmall();
const char* glyph = customGlyph ? customGlyph : (row.isZ ? ICON_MD_SHIELD : ICON_MD_CIRCLE);
ImU32 icCol = customGlyph ? OnSurfaceMedium() : typeCol;
ImVec2 iSz = iconFont->CalcTextSizeA(iconFont->LegacySize, 1000.0f, 0.0f, glyph);
dl->AddText(iconFont, iconFont->LegacySize,
ImVec2(iconCx - iSz.x * 0.5f, iconCy - iSz.y * 0.5f), icCol, glyph);
}
// ---- Type label (first line) ----
float labelX = cx + rowIconSz * 2.0f + Layout::spacingMd();
{
const char* typeLabel = row.isZ ? TR("shielded") : TR("transparent");
const char* hiddenTag = row.hidden ? TR("hidden_tag") : "";
char typeBuf[64];
snprintf(typeBuf, sizeof(typeBuf), "%s%s", typeLabel, hiddenTag);
dl->AddText(capFont, capFont->LegacySize, ImVec2(labelX, cy), typeCol, typeBuf);
// User label next to type
if (!row.label.empty()) {
float typeLabelW = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, typeBuf).x;
dl->AddText(capFont, capFont->LegacySize,
ImVec2(labelX + typeLabelW + Layout::spacingLg(), cy),
OnSurfaceMedium(), row.label.c_str());
}
}
// ---- Address (second line) ----
{
float addrAvailW = contentRight - labelX;
ImVec2 fullAddrSz = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0.0f, addr.address.c_str());
std::string display_addr;
if (fullAddrSz.x <= addrAvailW) {
display_addr = addr.address;
} else {
int addrTruncLen = (addrTable.columns.count("address") && addrTable.columns.at("address").truncate > 0)
? addrTable.columns.at("address").truncate : 32;
display_addr = truncateAddress(addr.address, addrTruncLen);
}
dl->AddText(capFont, capFont->LegacySize,
ImVec2(labelX, cy + body2->LegacySize + Layout::spacingXs()),
OnSurfaceMedium(), display_addr.c_str());
}
// ---- Balance (right-aligned, first line) ----
{
char balBuf[32];
snprintf(balBuf, sizeof(balBuf), "%.8f", addr.balance);
ImVec2 balSz = body2->CalcTextSizeA(body2->LegacySize, FLT_MAX, 0, balBuf);
float balX = contentRight - balSz.x;
ImU32 balCol = addr.balance > 0.0
? (row.isZ ? greenCol : OnSurface())
: OnSurfaceDisabled();
if (row.hidden) balCol = OnSurfaceDisabled();
DrawTextShadow(dl, body2, body2->LegacySize, ImVec2(balX, cy), balCol, balBuf);
}
// ---- USD value (second line, right-aligned) ----
{
double priceUsd = state.market.price_usd;
if (priceUsd > 0.0 && addr.balance > 0.0) {
char usdBuf[32];
double usdVal = addr.balance * priceUsd;
if (usdVal >= 1.0) snprintf(usdBuf, sizeof(usdBuf), "$%.2f", usdVal);
else if (usdVal >= 0.01) snprintf(usdBuf, sizeof(usdBuf), "$%.4f", usdVal);
else snprintf(usdBuf, sizeof(usdBuf), "$%.6f", usdVal);
ImVec2 usdSz = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, usdBuf);
dl->AddText(capFont, capFont->LegacySize,
ImVec2(contentRight - usdSz.x,
cy + body2->LegacySize + Layout::spacingXs()),
OnSurfaceDisabled(), usdBuf);
}
}
// ---- Copy feedback flash ----
if (s_copiedRow == row_idx) {
float elapsed = (float)ImGui::GetTime() - s_copiedTime;
if (elapsed < 1.0f) {
float alpha = 1.0f - elapsed;
ImFont* capF = Type().caption();
const char* copiedTxt = TR("copied");
ImVec2 sz = capF->CalcTextSizeA(capF->LegacySize, 1000.0f, 0.0f, copiedTxt);
ImVec4 fc = ImGui::ColorConvertU32ToFloat4(Success());
fc.w = alpha;
float tx = rowPos.x + (innerW - sz.x) * 0.5f;
float ty = rowPos.y + (rowH - sz.y) * 0.5f;
dl->AddText(capF, capF->LegacySize, ImVec2(tx, ty),
ImGui::ColorConvertFloat4ToU32(fc), copiedTxt);
} else {
s_copiedRow = -1;
}
}
// ---- Click: begin drag tracking (copy deferred to mouse-up) ----
if (hovered && mouseClicked && !btnClicked) {
s_dragIdx = row_idx;
s_dragOffsetY = mousePos.y - rowPos.y;
s_dragStartY = mousePos.y;
s_dragActive = false;
}
// Activate drag after a small threshold
if (s_dragIdx == row_idx && !s_dragActive && mouseDown) {
if (std::abs(mousePos.y - s_dragStartY) > 6.0f * dp) {
s_dragActive = true;
s_dropTargetIdx = -1;
}
}
} // end !isDragged
// Advance cursor for interaction area
ImGui::PushID(row_idx);
ImGui::SetCursorScreenPos(ImVec2(rowPos.x, rowY[row_idx]));
ImGui::InvisibleButton("##addr", ImVec2(innerW, rowH));
if (ImGui::IsItemHovered() && !s_dragActive) {
ImGui::SetTooltip("%s", addr.address.c_str());
if (bHov) ImGui::SetTooltip("%s", row.favorite ? TR("remove_favorite") : TR("favorite_address"));
}
// Context menu
// Eye button (zero balance or hidden)
if (addr.balance < 1e-9 || row.hidden) {
ImVec2 bMin(eyeX, btnY), bMax(eyeX + btnW, btnY + btnH);
bool bHov = ImGui::IsMouseHoveringRect(bMin, bMax);
ImU32 eyeFill = IM_COL32(255, 255, 255, bHov ? 25 : 12);
ImU32 eyeBorder = IM_COL32(255, 255, 255, bHov ? 50 : 25);
dl->AddRectFilled(bMin, bMax, eyeFill, btnRound);
dl->AddRect(bMin, bMax, eyeBorder, btnRound, 0, 1.0f * dp);
ImFont* iconFont = material::Type().iconSmall();
const char* hideIcon = row.hidden ? ICON_MD_VISIBILITY : ICON_MD_VISIBILITY_OFF;
ImVec2 iSz = iconFont->CalcTextSizeA(iconFont->LegacySize, 1000.0f, 0.0f, hideIcon);
ImU32 iconCol = bHov ? OnSurface() : OnSurfaceDisabled();
dl->AddText(iconFont, iconFont->LegacySize,
ImVec2(eyeX + (btnW - iSz.x) * 0.5f, btnY + (btnH - iSz.y) * 0.5f), iconCol, hideIcon);
if (bHov && ImGui::IsMouseClicked(0)) {
if (row.hidden) app->unhideAddress(addr.address);
else app->hideAddress(addr.address);
btnClicked = true;
}
if (bHov) ImGui::SetTooltip("%s", row.hidden ? TR("restore_address") : TR("hide_address"));
}
// Content zone ends before buttons
float contentRight = eyeX - Layout::spacingSm();
float iconCx = cx + rowIconSz;
float iconCy = cy + body2->LegacySize * 0.5f;
if (row.isZ) {
ImFont* iconFont = material::Type().iconSmall();
const char* shieldIcon = ICON_MD_SHIELD;
ImVec2 iSz = iconFont->CalcTextSizeA(iconFont->LegacySize, 1000.0f, 0.0f, shieldIcon);
dl->AddText(iconFont, iconFont->LegacySize,
ImVec2(iconCx - iSz.x * 0.5f, iconCy - iSz.y * 0.5f), typeCol, shieldIcon);
} else {
ImFont* iconFont = material::Type().iconSmall();
const char* circIcon = ICON_MD_CIRCLE;
ImVec2 iSz = iconFont->CalcTextSizeA(iconFont->LegacySize, 1000.0f, 0.0f, circIcon);
dl->AddText(iconFont, iconFont->LegacySize,
ImVec2(iconCx - iSz.x * 0.5f, iconCy - iSz.y * 0.5f), typeCol, circIcon);
}
float labelX = cx + rowIconSz * 2.0f + Layout::spacingSm();
const char* typeLabel = row.isZ ? "Shielded" : "Transparent";
const char* hiddenTag = row.hidden ? " (hidden)" : "";
char typeBuf[64];
snprintf(typeBuf, sizeof(typeBuf), "%s%s", typeLabel, hiddenTag);
dl->AddText(capFont, capFont->LegacySize, ImVec2(labelX, cy), typeCol, typeBuf);
if (!addr.label.empty()) {
float typeLabelW = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, typeBuf).x;
dl->AddText(capFont, capFont->LegacySize,
ImVec2(labelX + typeLabelW + Layout::spacingLg(), cy),
OnSurfaceMedium(), addr.label.c_str());
}
// Address (second line) — show full if it fits, otherwise truncate
float addrAvailW = contentRight - labelX;
ImVec2 fullAddrSz = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0.0f, addr.address.c_str());
std::string display_addr;
if (fullAddrSz.x <= addrAvailW) {
display_addr = addr.address;
} else {
int addrTruncLen = (addrTable.columns.count("address") && addrTable.columns.at("address").truncate > 0)
? addrTable.columns.at("address").truncate : 32;
display_addr = truncateAddress(addr.address, addrTruncLen);
}
dl->AddText(capFont, capFont->LegacySize,
ImVec2(labelX, cy + body2->LegacySize + Layout::spacingXs()),
OnSurfaceMedium(), display_addr.c_str());
// Balance (right-aligned within content zone)
char balBuf[32];
snprintf(balBuf, sizeof(balBuf), "%.8f", addr.balance);
ImVec2 balSz = body2->CalcTextSizeA(body2->LegacySize, FLT_MAX, 0, balBuf);
float balX = contentRight - balSz.x;
ImU32 balCol = addr.balance > 0.0
? (row.isZ ? greenCol : OnSurface())
: OnSurfaceDisabled();
if (row.hidden) balCol = OnSurfaceDisabled();
DrawTextShadow(dl, body2, body2->LegacySize, ImVec2(balX, cy), balCol, balBuf);
double priceUsd = state.market.price_usd;
if (priceUsd > 0.0 && addr.balance > 0.0) {
char usdBuf[32];
double usdVal = addr.balance * priceUsd;
if (usdVal >= 1.0) snprintf(usdBuf, sizeof(usdBuf), "$%.2f", usdVal);
else if (usdVal >= 0.01) snprintf(usdBuf, sizeof(usdBuf), "$%.4f", usdVal);
else snprintf(usdBuf, sizeof(usdBuf), "$%.6f", usdVal);
ImVec2 usdSz = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, usdBuf);
dl->AddText(capFont, capFont->LegacySize,
ImVec2(contentRight - usdSz.x,
cy + body2->LegacySize + Layout::spacingXs()),
OnSurfaceDisabled(), usdBuf);
}
if (hovered && ImGui::IsMouseClicked(0) && !btnClicked) {
ImGui::SetClipboardText(addr.address.c_str());
selected_row = row_idx;
}
ImGui::PushID(row_idx);
ImGui::InvisibleButton("##addr", ImVec2(innerW, rowH));
if (ImGui::IsItemHovered() && !btnClicked) ImGui::SetTooltip("%s", addr.address.c_str());
const auto& acrTheme = GetCurrentAcrylicTheme();
if (effects::ImGuiAcrylic::BeginAcrylicContextItem("AddressContext", 0, acrTheme.menu)) {
if (ImGui::MenuItem(TR("copy_address"))) {
ImGui::SetClipboardText(addr.address.c_str());
s_copiedRow = row_idx;
s_copiedTime = (float)ImGui::GetTime();
}
if (ImGui::MenuItem(TR("copy_address"))) ImGui::SetClipboardText(addr.address.c_str());
if (ImGui::MenuItem(TR("send_from_this_address"))) {
SetSendFromAddress(addr.address);
app->setCurrentPage(NavPage::Send);
}
ImGui::Separator();
if (ImGui::MenuItem(TR("set_label"))) {
AddressLabelDialog::show(app, addr.address, row.isZ);
}
if (ImGui::MenuItem(TR("export_private_key")))
KeyExportDialog::show(addr.address, KeyExportDialog::KeyType::Private);
if (row.isZ) {
@@ -1880,7 +1697,7 @@ static void RenderSharedAddressList(App* app, float listH, float availW,
KeyExportDialog::show(addr.address, KeyExportDialog::KeyType::Viewing);
}
if (ImGui::MenuItem(TR("show_qr_code")))
QRPopupDialog::show(addr.address, row.isZ ? TR("z_address") : TR("t_address"));
QRPopupDialog::show(addr.address, row.isZ ? "Z-Address" : "T-Address");
ImGui::Separator();
if (row.hidden) {
if (ImGui::MenuItem(TR("restore_address")))
@@ -1900,49 +1717,20 @@ static void RenderSharedAddressList(App* app, float listH, float availW,
}
ImGui::PopID();
// Separator between rows
if (row_idx < (int)rows.size() - 1 && selected_row != row_idx) {
float sepY = rowY[row_idx] + rowH;
ImVec2 divStart = ImGui::GetCursorScreenPos();
dl->AddLine(
ImVec2(rowPos.x + rowPadLeft + rowIconSz * 2.0f, sepY),
ImVec2(rowPos.x + innerW - Layout::spacingLg(), sepY),
dividerCol);
ImVec2(divStart.x + rowPadLeft + rowIconSz * 2.0f, divStart.y),
ImVec2(divStart.x + innerW - Layout::spacingLg(), divStart.y),
IM_COL32(255, 255, 255, 15));
}
}
// Advance cursor past all rows
ImGui::SetCursorScreenPos(ImVec2(ImGui::GetCursorScreenPos().x,
cursorStartY + (float)rows.size() * rowH));
// ---- Keyboard navigation ----
if (!ImGui::GetIO().WantTextInput && !ImGui::GetIO().KeyCtrl && !s_dragActive) {
bool nav = false;
if (ImGui::IsKeyPressed(ImGuiKey_UpArrow) || ImGui::IsKeyPressed(ImGuiKey_K)) {
if (selected_row > 0) { selected_row--; nav = true; }
else if (selected_row < 0 && !rows.empty()) { selected_row = 0; nav = true; }
}
if (ImGui::IsKeyPressed(ImGuiKey_DownArrow) || ImGui::IsKeyPressed(ImGuiKey_J)) {
if (selected_row < (int)rows.size() - 1) { selected_row++; nav = true; }
else if (selected_row < 0 && !rows.empty()) { selected_row = 0; nav = true; }
}
if (ImGui::IsKeyPressed(ImGuiKey_Enter) && selected_row >= 0 && selected_row < (int)rows.size()) {
ImGui::SetClipboardText(rows[selected_row].info->address.c_str());
s_copiedRow = selected_row;
s_copiedTime = (float)ImGui::GetTime();
}
if (ImGui::IsKeyPressed(ImGuiKey_F2) && selected_row >= 0 && selected_row < (int)rows.size()) {
const auto& r = rows[selected_row];
AddressLabelDialog::show(app, r.info->address, r.isZ);
}
(void)nav;
}
}
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
ImGui::EndChild();
ImGui::PopStyleVar();
// Scroll fade
{
float fadeZone = std::min(
(Type().body2()->LegacySize + Type().caption()->LegacySize +
@@ -1952,12 +1740,11 @@ static void RenderSharedAddressList(App* app, float listH, float availW,
listPanelMin.y, listPanelMax.y, fadeZone, addrScrollY, addrScrollMaxY);
}
// Address count
{
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
char countBuf[128];
int totalAddrs = (int)(state.z_addresses.size() + state.t_addresses.size());
snprintf(countBuf, sizeof(countBuf), TR("showing_x_of_y"),
snprintf(countBuf, sizeof(countBuf), "Showing %d of %d addresses",
(int)rows.size(), totalAddrs);
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), countBuf);
}
-309
View File
@@ -1,309 +0,0 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#pragma once
#include <string>
#include <memory>
#include "../../app.h"
#include "../../util/bootstrap.h"
#include "../../util/platform.h"
#include "../../util/i18n.h"
#include "../material/draw_helpers.h"
#include "../material/type.h"
#include "../material/colors.h"
#include "../theme.h"
#include "../../embedded/IconsMaterialDesign.h"
#include "imgui.h"
namespace dragonx {
namespace ui {
/**
* @brief Modal dialog for downloading blockchain bootstrap from Settings.
*
* Reuses the existing Bootstrap class for download/verify/extract.
* States: Confirm → Downloading → Done / Failed
*/
class BootstrapDownloadDialog {
public:
static void show(App* app) {
s_open = true;
s_app = app;
s_state = State::Confirm;
s_bootstrap.reset();
s_errorMsg.clear();
s_wasDaemonRunning = false;
}
static bool isOpen() { return s_open; }
static void render() {
if (!s_app) return;
if (!s_open) {
// Dialog was closed — ensure flag is cleared
if (s_app->isBootstrapDownloading() && s_state != State::Downloading) {
s_app->setBootstrapDownloading(false);
}
return;
}
using namespace material;
const float dp = Layout::dpiScale();
if (BeginOverlayDialog(TR("download_bootstrap"), &s_open, 500.0f, 0.94f)) {
if (s_state == State::Confirm) {
renderConfirm(dp);
} else if (s_state == State::Downloading) {
renderProgress(dp);
} else if (s_state == State::Done) {
renderDone(dp);
} else if (s_state == State::Failed) {
renderFailed(dp);
}
EndOverlayDialog();
}
}
private:
enum class State { Confirm, Downloading, Done, Failed };
// ---- Confirm screen ----
static void renderConfirm(float dp) {
using namespace material;
ImGui::Spacing();
// Description
ImGui::TextWrapped("%s", TR("bootstrap_desc"));
ImGui::Spacing();
// Warning card
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.9f, 0.6f, 0.0f, 0.08f));
ImGui::BeginChild("##bsWarn", ImVec2(0, 0),
ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysUseWindowPadding,
ImGuiWindowFlags_NoScrollbar);
{
ImFont* iconFont = Type().iconSmall();
ImGui::PushFont(iconFont);
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(Warning()), "%s", ICON_MD_WARNING);
ImGui::PopFont();
ImGui::SameLine();
ImGui::TextWrapped("%s", TR("bootstrap_warning"));
}
ImGui::EndChild();
ImGui::PopStyleColor();
ImGui::Spacing();
// Trust warning
{
ImFont* iconFont = Type().iconSmall();
ImGui::PushFont(iconFont);
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(OnSurfaceDisabled()), "%s", ICON_MD_VERIFIED_USER);
ImGui::PopFont();
ImGui::SameLine();
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(OnSurfaceMedium()));
ImGui::TextWrapped("%s", TR("bootstrap_trust_warning"));
ImGui::PopStyleColor();
}
ImGui::Spacing();
ImGui::Spacing();
// Buttons: Download | Mirror | Cancel
float btnW = 140.0f * dp;
float btnSm = 90.0f * dp;
if (TactileButton(TR("download"), ImVec2(btnW, 0))) {
startDownload("");
}
ImGui::SameLine();
if (TactileButton(TR("bootstrap_mirror"), ImVec2(btnW, 0))) {
std::string mirrorUrl = std::string(util::Bootstrap::kMirrorUrl) + "/" + util::Bootstrap::kZipName;
startDownload(mirrorUrl);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("%s", TR("bootstrap_mirror_tooltip"));
}
ImGui::SameLine();
if (TactileButton(TR("cancel"), ImVec2(btnSm, 0))) {
s_open = false;
}
}
// ---- Progress screen ----
static void renderProgress(float dp) {
using namespace material;
if (!s_bootstrap) {
s_state = State::Failed;
s_errorMsg = "Bootstrap not initialized";
return;
}
auto prog = s_bootstrap->getProgress();
// Status title
const char* statusTitle;
if (prog.state == util::Bootstrap::State::Downloading)
statusTitle = TR("bootstrap_downloading");
else if (prog.state == util::Bootstrap::State::Verifying)
statusTitle = TR("bootstrap_verifying");
else
statusTitle = TR("bootstrap_extracting");
Type().text(TypeStyle::Subtitle2, statusTitle);
ImGui::Spacing();
// Progress bar
float barH = 8.0f * dp;
float barW = ImGui::GetContentRegionAvail().x;
ImVec2 barMin = ImGui::GetCursorScreenPos();
ImVec2 barMax(barMin.x + barW, barMin.y + barH);
ImDrawList* dl = ImGui::GetWindowDrawList();
dl->AddRectFilled(barMin, barMax, IM_COL32(255, 255, 255, 30), 4.0f * dp);
float fillW = barW * (prog.percent / 100.0f);
if (fillW > 0) {
dl->AddRectFilled(barMin, ImVec2(barMin.x + fillW, barMax.y),
Primary(), 4.0f * dp);
}
ImGui::Dummy(ImVec2(0, barH));
ImGui::Spacing();
// Percent + status text
{
char pctBuf[32];
snprintf(pctBuf, sizeof(pctBuf), "%.1f%%", prog.percent);
float pctW = ImGui::CalcTextSize(pctBuf).x;
ImGui::Text("%s", prog.status_text.c_str());
ImGui::SameLine(ImGui::GetContentRegionAvail().x - pctW + ImGui::GetCursorPosX());
ImGui::Text("%s", pctBuf);
}
// wallet.dat protection notice during extraction
if (prog.state == util::Bootstrap::State::Extracting) {
Type().textColored(TypeStyle::Caption, OnSurfaceMedium(), TR("bootstrap_wallet_protected"));
}
ImGui::Spacing();
// Daemon status indicator
{
bool daemonUp = s_app->isEmbeddedDaemonRunning();
const std::string& dStatus = s_app->getDaemonStatus();
ImU32 dotCol = daemonUp ? IM_COL32(76, 175, 80, 200)
: IM_COL32(120, 120, 120, 160);
if (dStatus.find("Stopping") != std::string::npos)
dotCol = IM_COL32(255, 167, 38, 200);
const char* label = daemonUp ? (dStatus.find("Stopping") != std::string::npos
? TR("bootstrap_daemon_stopping")
: TR("bootstrap_daemon_running"))
: TR("bootstrap_daemon_stopped");
ImDrawList* ddl = ImGui::GetWindowDrawList();
float dotR = 3.5f * dp;
ImVec2 cp = ImGui::GetCursorScreenPos();
ddl->AddCircleFilled(ImVec2(cp.x + dotR, cp.y + ImGui::GetTextLineHeight() * 0.5f),
dotR, dotCol);
ImGui::Indent(dotR * 2.0f + 6.0f * dp);
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), label);
ImGui::Unindent(dotR * 2.0f + 6.0f * dp);
}
ImGui::Spacing();
ImGui::Spacing();
// Cancel button
float btnW = 100.0f * dp;
if (TactileButton(TR("cancel"), ImVec2(btnW, 0))) {
s_bootstrap->cancel();
}
// Check completion
if (s_bootstrap->isDone()) {
auto finalProg = s_bootstrap->getProgress();
if (finalProg.state == util::Bootstrap::State::Completed) {
s_state = State::Done;
} else {
s_errorMsg = finalProg.error;
if (s_errorMsg.empty()) s_errorMsg = "Bootstrap failed";
s_state = State::Failed;
}
s_bootstrap.reset();
}
}
// ---- Done screen ----
static void renderDone(float dp) {
using namespace material;
ImGui::Spacing();
Type().textColored(TypeStyle::H6, Success(), TR("bootstrap_success"));
ImGui::Spacing();
ImGui::TextWrapped("%s", TR("bootstrap_success_desc"));
ImGui::Spacing();
ImGui::Spacing();
float btnW = 140.0f * dp;
if (s_wasDaemonRunning) {
if (TactileButton(TR("bootstrap_restart_daemon"), ImVec2(btnW, 0))) {
s_app->setBootstrapDownloading(false);
s_app->startEmbeddedDaemon();
s_open = false;
}
ImGui::SameLine();
}
if (TactileButton(TR("close"), ImVec2(90.0f * dp, 0))) {
s_app->setBootstrapDownloading(false);
s_open = false;
}
}
// ---- Failed screen ----
static void renderFailed(float dp) {
using namespace material;
ImGui::Spacing();
Type().textColored(TypeStyle::H6, Error(), TR("bootstrap_failed"));
ImGui::Spacing();
ImGui::TextWrapped("%s", s_errorMsg.c_str());
ImGui::Spacing();
ImGui::Spacing();
float btnW = 120.0f * dp;
if (TactileButton(TR("retry"), ImVec2(btnW, 0))) {
startDownload("");
}
ImGui::SameLine();
if (TactileButton(TR("close"), ImVec2(90.0f * dp, 0))) {
s_app->setBootstrapDownloading(false);
s_open = false;
}
}
// ---- Shared: kick off download ----
static void startDownload(const std::string& url) {
s_wasDaemonRunning = s_app->stopDaemonForBootstrap();
s_app->setBootstrapDownloading(true);
s_bootstrap = std::make_unique<util::Bootstrap>();
std::string dataDir = util::Platform::getDragonXDataDir();
if (url.empty())
s_bootstrap->start(dataDir);
else
s_bootstrap->start(dataDir, url);
s_state = State::Downloading;
s_errorMsg.clear();
}
static inline bool s_open = false;
static inline App* s_app = nullptr;
static inline State s_state = State::Confirm;
static inline std::unique_ptr<util::Bootstrap> s_bootstrap;
static inline bool s_wasDaemonRunning = false;
static inline std::string s_errorMsg;
};
} // namespace ui
} // namespace dragonx
-3
View File
@@ -1,9 +1,6 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
//
// console_tab.cpp — Interactive RPC console with command history,
// tab completion, daemon log display, and color-coded output.
#include "console_tab.h"
#include "../material/colors.h"
+1 -1
View File
@@ -213,7 +213,7 @@ void ExportAllKeysDialog::render(App* app)
s_exporting = false;
if (writeOk) {
s_status = "Exported to: " + filepath;
Notifications::instance().success(TR("export_keys_success"), 5.0f);
Notifications::instance().success(TR("export_keys_success"));
} else {
s_status = "Failed to write file";
Notifications::instance().error("Failed to save key file");
@@ -142,7 +142,7 @@ void ExportTransactionsDialog::render(App* app)
s_status = "Exported " + std::to_string(state.transactions.size()) +
" transactions to: " + filepath;
Notifications::instance().success(TR("export_tx_success"), 5.0f);
Notifications::instance().success(TR("export_tx_success"));
}
}
}
+1 -1
View File
@@ -363,7 +363,7 @@ void ImportKeyDialog::render(App* app)
imported, failed);
s_status = buf;
if (imported > 0) {
Notifications::instance().success(TR("import_key_success"), 5.0f);
Notifications::instance().success(TR("import_key_success"));
}
};
});
-20
View File
@@ -237,26 +237,6 @@ void RenderMarketTab(App* app)
ImVec2(centerX - valSz.x * 0.5f, valY), stats[i].valueCol, stats[i].value.c_str());
}
// ---- STALENESS INDICATOR ----
{
auto fetchTime = market.last_fetch_time;
if (fetchTime.time_since_epoch().count() > 0) {
auto elapsed = std::chrono::steady_clock::now() - fetchTime;
int ageSecs = (int)std::chrono::duration_cast<std::chrono::seconds>(elapsed).count();
bool stale = ageSecs > 300; // 5 minutes
if (ageSecs < 60)
snprintf(buf, sizeof(buf), "%s %ds %s", stale ? ICON_MD_WARNING : "", ageSecs, TR("ago"));
else
snprintf(buf, sizeof(buf), "%s %dm %s", stale ? ICON_MD_WARNING : "", ageSecs / 60, TR("ago"));
ImFont* staleFont = capFont;
ImU32 staleCol = stale ? Warning() : WithAlpha(OnSurface(), 100);
float staleY = statsY + ovFont->LegacySize + Layout::spacingXs() + sub1->LegacySize + Layout::spacingSm();
ImVec2 staleSz = staleFont->CalcTextSizeA(staleFont->LegacySize, FLT_MAX, 0, buf);
dl->AddText(staleFont, staleFont->LegacySize,
ImVec2(cardMin.x + Layout::spacingLg(), staleY), staleCol, buf);
}
}
// ---- TRADE BUTTON (top-right of card) ----
if (!currentExchange.pairs.empty()) {
const char* pairName = currentExchange.pairs[s_pair_idx].displayName.c_str();
+38 -170
View File
@@ -45,7 +45,7 @@ static int s_earnings_filter = 0;
// Thread benchmark state
struct ThreadBenchmark {
enum class Phase { Idle, Starting, WarmingUp, Measuring, Advancing, CoolingDown, Done };
enum class Phase { Idle, Starting, WarmingUp, Measuring, Advancing, Done };
Phase phase = Phase::Idle;
std::vector<int> candidates;
@@ -58,94 +58,51 @@ struct ThreadBenchmark {
std::vector<Result> results;
float phase_timer = 0.0f;
// Warmup: wait at least MIN then check for hashrate stability; cap at MAX.
// Laptops need 90s+ for thermal throttling to fully manifest.
static constexpr float MIN_WARMUP_SECS = 90.0f;
static constexpr float MAX_WARMUP_SECS = 300.0f;
static constexpr float MEASURE_SECS = 30.0f;
static constexpr float COOLDOWN_SECS = 5.0f;
// Stability detection — compare rolling 10s hashrate windows.
// Require STABLE_WINDOWS_NEEDED consecutive stable readings.
static constexpr float STABILITY_WINDOW_SECS = 10.0f;
static constexpr float STABILITY_THRESHOLD = 0.05f; // 5% change → stable
static constexpr int STABLE_WINDOWS_NEEDED = 3;
double prev_window_avg = 0.0;
double window_sum = 0.0;
int window_samples = 0;
float window_timer = 0.0f;
int consecutive_stable = 0; // count of consecutive stable windows
// Measurement: average-based (sustained performance, not peak burst)
double measure_sum = 0.0;
int measure_samples = 0;
static constexpr float WARMUP_SECS = 20.0f;
static constexpr float MEASURE_SECS = 10.0f;
double best_sample = 0.0; // best hashrate_10s during current measurement window
int sample_count = 0; // number of non-zero hashrate samples collected
int optimal_threads = 0;
double optimal_hashrate = 0.0;
bool was_pool_running = false;
int prev_threads = 0;
// Track actual warmup durations for better time estimates
float total_warmup_secs = 0.0f;
void reset() {
phase = Phase::Idle;
candidates.clear();
current_index = 0;
results.clear();
phase_timer = 0.0f;
prev_window_avg = 0.0;
window_sum = 0.0;
window_samples = 0;
window_timer = 0.0f;
consecutive_stable = 0;
measure_sum = 0.0;
measure_samples = 0;
best_sample = 0.0;
sample_count = 0;
optimal_threads = 0;
optimal_hashrate = 0.0;
was_pool_running = false;
prev_threads = 0;
total_warmup_secs = 0.0f;
}
void buildCandidates(int max_threads) {
candidates.clear();
// Start at half the cores — lower counts are rarely optimal and
// testing them first would waste time warming up the CPU before
// reaching the thread counts that actually matter.
int start = std::max(1, max_threads / 2);
for (int t = start; t <= max_threads; t++)
candidates.push_back(t);
}
/// Average warmup duration based on tests completed so far
float avgWarmupSecs() const {
if (current_index > 0)
return total_warmup_secs / (float)current_index;
return (MIN_WARMUP_SECS + MAX_WARMUP_SECS) * 0.5f; // initial estimate
}
/// Estimated seconds per test (uses observed warmup average)
float perTestSecs() const {
return avgWarmupSecs() + MEASURE_SECS;
if (max_threads <= 16) {
for (int t = 1; t <= max_threads; t++)
candidates.push_back(t);
} else {
// Sample: 1, then every ceil(max/10) step, always including max
int step = std::max(1, (max_threads + 9) / 10);
for (int t = 1; t <= max_threads; t += step)
candidates.push_back(t);
if (candidates.back() != max_threads)
candidates.push_back(max_threads);
}
}
float totalEstimatedSecs() const {
int n = (int)candidates.size();
if (n <= 0) return 0.0f;
// Completed tests use actual time; remaining use estimate
float completed_time = total_warmup_secs
+ (float)current_index * (MEASURE_SECS + COOLDOWN_SECS);
int remaining = n - current_index;
float remaining_time = (float)remaining * (avgWarmupSecs() + MEASURE_SECS)
+ (float)std::max(0, remaining - 1) * COOLDOWN_SECS;
return completed_time + remaining_time;
return (float)candidates.size() * (WARMUP_SECS + MEASURE_SECS);
}
float elapsedSecs() const {
float completed = total_warmup_secs
+ (float)current_index * (MEASURE_SECS + COOLDOWN_SECS);
float completed = (float)current_index * (WARMUP_SECS + MEASURE_SECS);
return completed + phase_timer;
}
@@ -153,14 +110,6 @@ struct ThreadBenchmark {
float total = totalEstimatedSecs();
return (total > 0.0f) ? std::min(1.0f, elapsedSecs() / total) : 0.0f;
}
void resetStabilityTracking() {
prev_window_avg = 0.0;
window_sum = 0.0;
window_samples = 0;
window_timer = 0.0f;
consecutive_stable = 0;
}
};
static ThreadBenchmark s_benchmark;
@@ -390,75 +339,34 @@ static void RenderMiningTabContent(App* app)
app->startPoolMining(t);
s_benchmark.phase = ThreadBenchmark::Phase::WarmingUp;
s_benchmark.phase_timer = 0.0f;
s_benchmark.resetStabilityTracking();
s_benchmark.measure_sum = 0.0;
s_benchmark.measure_samples = 0;
s_benchmark.best_sample = 0.0;
s_benchmark.sample_count = 0;
} else {
s_benchmark.phase = ThreadBenchmark::Phase::Done;
}
break;
case ThreadBenchmark::Phase::WarmingUp: {
// Adaptive warmup: wait for hashrate to stabilize (thermal steady state).
// After MIN_WARMUP (90s), compare rolling 10s hashrate windows.
// Require 3 consecutive windows within 5% to confirm equilibrium.
// Laptops can take 2-3+ minutes for thermal throttling to fully
// manifest, so a single stable window isn't sufficient.
bool past_min = s_benchmark.phase_timer >= ThreadBenchmark::MIN_WARMUP_SECS;
bool past_max = s_benchmark.phase_timer >= ThreadBenchmark::MAX_WARMUP_SECS;
// Accumulate samples into current window
if (state.pool_mining.hashrate_10s > 0.0) {
s_benchmark.window_sum += state.pool_mining.hashrate_10s;
s_benchmark.window_samples++;
}
s_benchmark.window_timer += dt;
bool stable = false;
if (past_min && s_benchmark.window_timer >= ThreadBenchmark::STABILITY_WINDOW_SECS
&& s_benchmark.window_samples > 0) {
double current_avg = s_benchmark.window_sum / s_benchmark.window_samples;
if (s_benchmark.prev_window_avg > 0.0) {
double change = std::abs(current_avg - s_benchmark.prev_window_avg)
/ s_benchmark.prev_window_avg;
if (change < ThreadBenchmark::STABILITY_THRESHOLD)
s_benchmark.consecutive_stable++;
else
s_benchmark.consecutive_stable = 0; // reset on instability
if (s_benchmark.consecutive_stable >= ThreadBenchmark::STABLE_WINDOWS_NEEDED)
stable = true;
}
// Shift window
s_benchmark.prev_window_avg = current_avg;
s_benchmark.window_sum = 0.0;
s_benchmark.window_samples = 0;
s_benchmark.window_timer = 0.0f;
}
if (stable || past_max) {
s_benchmark.total_warmup_secs += s_benchmark.phase_timer;
case ThreadBenchmark::Phase::WarmingUp:
if (s_benchmark.phase_timer >= ThreadBenchmark::WARMUP_SECS) {
s_benchmark.phase = ThreadBenchmark::Phase::Measuring;
s_benchmark.phase_timer = 0.0f;
s_benchmark.measure_sum = 0.0;
s_benchmark.measure_samples = 0;
s_benchmark.best_sample = 0.0;
s_benchmark.sample_count = 0;
}
break;
}
case ThreadBenchmark::Phase::Measuring:
// Sample average hashrate — reflects sustained (thermally throttled) performance
// Sample hashrate during measurement window
if (state.pool_mining.hashrate_10s > 0.0) {
s_benchmark.measure_sum += state.pool_mining.hashrate_10s;
s_benchmark.measure_samples++;
s_benchmark.sample_count++;
if (state.pool_mining.hashrate_10s > s_benchmark.best_sample)
s_benchmark.best_sample = state.pool_mining.hashrate_10s;
}
if (s_benchmark.phase_timer >= ThreadBenchmark::MEASURE_SECS) {
int t = s_benchmark.candidates[s_benchmark.current_index];
double avg = (s_benchmark.measure_samples > 0)
? s_benchmark.measure_sum / s_benchmark.measure_samples
: 0.0;
s_benchmark.results.push_back({t, avg});
if (avg > s_benchmark.optimal_hashrate) {
s_benchmark.optimal_hashrate = avg;
s_benchmark.results.push_back({t, s_benchmark.best_sample});
if (s_benchmark.best_sample > s_benchmark.optimal_hashrate) {
s_benchmark.optimal_hashrate = s_benchmark.best_sample;
s_benchmark.optimal_threads = t;
}
s_benchmark.phase = ThreadBenchmark::Phase::Advancing;
@@ -470,9 +378,7 @@ static void RenderMiningTabContent(App* app)
app->stopPoolMining();
s_benchmark.current_index++;
if (s_benchmark.current_index < (int)s_benchmark.candidates.size()) {
// Cool down before next test to reduce thermal throttling bias
s_benchmark.phase = ThreadBenchmark::Phase::CoolingDown;
s_benchmark.phase_timer = 0.0f;
s_benchmark.phase = ThreadBenchmark::Phase::Starting;
} else {
// Done — apply optimal thread count
s_benchmark.phase = ThreadBenchmark::Phase::Done;
@@ -488,14 +394,6 @@ static void RenderMiningTabContent(App* app)
}
break;
case ThreadBenchmark::Phase::CoolingDown:
// Idle pause — let CPU temps drop before starting next test
if (s_benchmark.phase_timer >= ThreadBenchmark::COOLDOWN_SECS) {
s_benchmark.phase = ThreadBenchmark::Phase::Starting;
s_benchmark.phase_timer = 0.0f;
}
break;
default:
break;
}
@@ -1399,40 +1297,10 @@ static void RenderMiningTabContent(App* app)
// Status text above bar
int ct = s_benchmark.current_index < (int)s_benchmark.candidates.size()
? s_benchmark.candidates[s_benchmark.current_index] : 0;
// Estimated remaining time (uses observed warmup for better accuracy)
int remaining_tests = (int)s_benchmark.candidates.size() - s_benchmark.current_index;
float elapsed_in_phase = s_benchmark.phase_timer;
float phase_total;
if (s_benchmark.phase == ThreadBenchmark::Phase::WarmingUp)
phase_total = s_benchmark.avgWarmupSecs(); // adaptive estimate
else if (s_benchmark.phase == ThreadBenchmark::Phase::CoolingDown)
phase_total = ThreadBenchmark::COOLDOWN_SECS;
else
phase_total = ThreadBenchmark::MEASURE_SECS;
float remaining_in_current = std::max(0.0f, phase_total - elapsed_in_phase);
// Remaining tests after current each need warmup + measure + cooldown
float est_secs = remaining_in_current
+ (remaining_tests - 1) * (s_benchmark.avgWarmupSecs() + ThreadBenchmark::MEASURE_SECS + ThreadBenchmark::COOLDOWN_SECS);
int est_min = (int)(est_secs / 60.0f);
int est_sec = (int)est_secs % 60;
const char* phase_label;
if (s_benchmark.phase == ThreadBenchmark::Phase::CoolingDown)
phase_label = TR("mining_benchmark_cooling");
else if (s_benchmark.phase == ThreadBenchmark::Phase::WarmingUp
&& s_benchmark.phase_timer >= ThreadBenchmark::MIN_WARMUP_SECS)
phase_label = TR("mining_benchmark_stabilizing");
else
phase_label = TR("mining_benchmark_testing");
if (est_min > 0)
snprintf(buf, sizeof(buf), "%s %d/%d (%dt) ~%dm%ds",
phase_label,
s_benchmark.current_index + 1,
(int)s_benchmark.candidates.size(), ct, est_min, est_sec);
else
snprintf(buf, sizeof(buf), "%s %d/%d (%dt) ~%ds",
phase_label,
s_benchmark.current_index + 1,
(int)s_benchmark.candidates.size(), ct, est_sec);
snprintf(buf, sizeof(buf), "%s %d/%d (%dt)",
TR("mining_benchmark_testing"),
s_benchmark.current_index + 1,
(int)s_benchmark.candidates.size(), ct);
ImVec2 txtSz = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, buf);
dl->AddText(capFont, capFont->LegacySize,
ImVec2(barX + (barW - txtSz.x) * 0.5f, barY - txtSz.y - 2.0f * dp),
+1 -5
View File
@@ -550,13 +550,9 @@ void RenderPeersTab(App* app)
float iconSz = iconFont->LegacySize;
const char* label = isRefreshing ? TR("peers_refreshing") : TR("peers_refresh");
ImVec2 lblSz = ovFont->CalcTextSizeA(ovFont->LegacySize, FLT_MAX, 0, label);
// Use the wider label so button doesn't resize between states
ImVec2 altSz = ovFont->CalcTextSizeA(ovFont->LegacySize, FLT_MAX, 0,
isRefreshing ? TR("peers_refresh") : TR("peers_refreshing"));
float maxLblW = std::max(lblSz.x, altSz.x);
float padH = Layout::spacingSm();
float padV = Layout::spacingSm();
float btnW = padH + iconSz + Layout::spacingXs() + maxLblW + padH;
float btnW = padH + iconSz + Layout::spacingXs() + lblSz.x + padH;
float btnH = std::max(iconSz, lblSz.y) + padV * 2;
float btnX = ImGui::GetWindowPos().x + availWidth - btnW - Layout::spacingSm();
float btnY = toggleY + (toggleH - btnH) * 0.5f;
+12 -7
View File
@@ -11,9 +11,7 @@
#include "receive_tab.h"
#include "send_tab.h"
#include "../../app.h"
#include "../../config/settings.h"
#include "../../util/i18n.h"
#include "../../util/platform.h"
#include "../../config/version.h"
#include "../../data/wallet_state.h"
#include "../../ui/widgets/qr_code.h"
@@ -47,7 +45,7 @@ static std::string TrId(const char* key, const char* id) {
// ============================================================================
static int s_selected_address_idx = -1;
static double s_request_amount = 0.0;
static char s_request_memo[512] = "";
static char s_request_memo[256] = "";
static double s_request_usd_amount = 0.0;
static bool s_request_usd_mode = false;
static std::string s_cached_qr_data;
@@ -78,9 +76,16 @@ static std::string TruncateAddress(const std::string& addr, size_t maxLen = 40)
return addr.substr(0, halfLen) + "..." + addr.substr(addr.length() - halfLen);
}
static void OpenExplorerURL(App* app, const std::string& address) {
std::string url = app->settings()->getAddressExplorerUrl() + address;
dragonx::util::Platform::openUrl(url);
static void OpenExplorerURL(const std::string& address) {
std::string url = "https://explorer.dragonx.com/address/" + address;
#ifdef _WIN32
std::string cmd = "start \"\" \"" + url + "\"";
#elif __APPLE__
std::string cmd = "open \"" + url + "\"";
#else
std::string cmd = "xdg-open \"" + url + "\"";
#endif
system(cmd.c_str());
}
// ============================================================================
@@ -973,7 +978,7 @@ void RenderReceiveTab(App* app)
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, S.drawElement("tabs.receive", "explorer-btn-border-size").size);
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(PrimaryLight()));
if (TactileButton(TrId("explorer", "recv").c_str(), ImVec2(otherBtnW, btnH), S.resolveFont("button"))) {
OpenExplorerURL(app, selected.address);
OpenExplorerURL(selected.address);
}
ImGui::PopStyleVar(); // FrameBorderSize
ImGui::PopStyleColor(4);
+2 -2
View File
@@ -195,7 +195,7 @@ static void RenderSyncBanner(const WalletState& state) {
? (float)state.sync.blocks / state.sync.headers * 100.0f : 0.0f;
char syncBuf[128];
snprintf(syncBuf, sizeof(syncBuf),
TR("blockchain_syncing"), syncPct);
"Blockchain syncing (%.1f%%)... Balances may be inaccurate.", syncPct);
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::ColorConvertU32ToFloat4(schema::UI().resolveColor(schema::UI().drawElement("tabs.send", "sync-banner-bg-color").color)));
float syncH = std::max(schema::UI().drawElement("tabs.send", "sync-banner-min-height").size, schema::UI().drawElement("tabs.send", "sync-banner-height").size * Layout::vScale());
ImGui::BeginChild("##SyncBanner", ImVec2(ImGui::GetContentRegionAvail().x, syncH),
@@ -804,7 +804,7 @@ void RenderSendConfirmPopup(App* app) {
s_tx_status = TR("send_tx_sent");
s_result_txid = result;
s_status_success = true;
Notifications::instance().success(TR("send_tx_success"), 5.0f);
Notifications::instance().success(TR("send_tx_success"));
s_to_address[0] = '\0';
s_amount = 0.0;
s_memo[0] = '\0';
+5 -108
View File
@@ -33,13 +33,13 @@ I18n::I18n()
// Cyrillic glyphs are included in the Ubuntu font glyph ranges.
registerLanguage("en", "English");
registerLanguage("es", "Español");
registerLanguage("zh", "中文");
registerLanguage("ru", "Русский");
registerLanguage("zh", "Chinese");
registerLanguage("ru", "Russian");
registerLanguage("de", "Deutsch");
registerLanguage("fr", "Français");
registerLanguage("pt", "Português");
registerLanguage("ja", "日本語");
registerLanguage("ko", "한국어");
registerLanguage("ja", "Japanese");
registerLanguage("ko", "Korean");
// Load default English strings (built-in fallback)
loadLanguage("en");
@@ -181,7 +181,7 @@ void I18n::loadBuiltinEnglish()
strings_["node_security"] = "NODE & SECURITY";
strings_["node"] = "NODE";
strings_["security"] = "SECURITY";
strings_["explorer_section"] = "EXPLORER";
strings_["explorer"] = "EXPLORER";
strings_["about"] = "About";
strings_["backup_data"] = "BACKUP & DATA";
strings_["balance_layout"] = "Balance Layout";
@@ -256,19 +256,6 @@ void I18n::loadBuiltinEnglish()
strings_["settings_quick_unlock_pin"] = "Quick-unlock PIN";
strings_["settings_pin_active"] = "PIN";
strings_["settings_encrypt_first_pin"] = "Encrypt wallet first to enable PIN";
strings_["incorrect_pin"] = "Incorrect PIN";
strings_["incorrect_passphrase"] = "Incorrect passphrase";
strings_["pin_not_set"] = "PIN not set. Use passphrase to unlock.";
strings_["restarting_after_encryption"] = "Restarting daemon after encryption...";
strings_["force_quit"] = "Force Quit";
strings_["force_quit_warning"] = "This will immediately kill the daemon without a clean shutdown. May require a blockchain resync.";
strings_["force_quit_confirm_title"] = "Force Quit?";
strings_["force_quit_confirm_msg"] = "This will immediately kill the daemon without a clean shutdown.\nThis may corrupt the blockchain index and require a resync.";
strings_["force_quit_yes"] = "Force Quit";
strings_["reduce_motion"] = "Reduce Motion";
strings_["tt_reduce_motion"] = "Disable animated transitions and balance lerp for accessibility";
strings_["ago"] = "ago";
strings_["wizard_daemon_start_failed"] = "Failed to start daemon \xe2\x80\x94 it will be retried automatically";
strings_["settings_data_dir"] = "Data Dir:";
strings_["settings_wallet_size_label"] = "Wallet Size:";
strings_["settings_debug_changed"] = "Debug categories changed \xe2\x80\x94 restart daemon to apply";
@@ -285,13 +272,6 @@ void I18n::loadBuiltinEnglish()
strings_["settings_block_explorer_urls"] = "Block Explorer URLs";
strings_["settings_configure_explorer"] = "Configure external block explorer links";
strings_["settings_auto_lock"] = "AUTO-LOCK";
strings_["timeout_off"] = "Off";
strings_["timeout_1min"] = "1 min";
strings_["timeout_5min"] = "5 min";
strings_["timeout_15min"] = "15 min";
strings_["timeout_30min"] = "30 min";
strings_["timeout_1hour"] = "1 hour";
strings_["slider_off"] = "Off";
strings_["settings_wallet_file_size"] = "Wallet file size: %s";
strings_["settings_wallet_location"] = "Wallet location: %s";
strings_["settings_rpc_note"] = "Note: Connection settings are usually auto-detected from DRAGONX.conf";
@@ -334,26 +314,6 @@ void I18n::loadBuiltinEnglish()
strings_["tt_mine_idle"] = "Automatically start mining when the\nsystem is idle (no keyboard/mouse input)";
strings_["tt_idle_delay"] = "How long to wait before starting mining";
strings_["tt_wizard"] = "Re-run the initial setup wizard\nDaemon will be restarted";
strings_["tt_download_bootstrap"] = "Download blockchain bootstrap to speed up sync\nExisting block data will be replaced";
strings_["download_bootstrap"] = "Download Bootstrap";
strings_["download"] = "Download";
strings_["retry"] = "Retry";
strings_["bootstrap_desc"] = "Download a blockchain bootstrap to dramatically speed up initial sync. This downloads a snapshot of the blockchain and extracts it into your data directory.";
strings_["bootstrap_warning"] = "Existing block data (blocks, chainstate, notarizations) will be deleted and replaced. Your wallet.dat will NOT be modified or deleted.";
strings_["bootstrap_trust_warning"] = "Only use bootstrap.dragonx.is or bootstrap2.dragonx.is. Using files from untrusted sources could compromise your node.";
strings_["bootstrap_mirror"] = "Mirror";
strings_["bootstrap_mirror_tooltip"] = "Download from mirror (bootstrap2.dragonx.is).\nUse this if the main download is slow or failing.";
strings_["bootstrap_downloading"] = "Downloading bootstrap...";
strings_["bootstrap_verifying"] = "Verifying checksums...";
strings_["bootstrap_extracting"] = "Extracting blockchain data...";
strings_["bootstrap_wallet_protected"] = "(wallet.dat is protected)";
strings_["bootstrap_daemon_stopping"] = "Daemon stopping...";
strings_["bootstrap_daemon_running"] = "Daemon running";
strings_["bootstrap_daemon_stopped"] = "Daemon stopped";
strings_["bootstrap_success"] = "Bootstrap Complete";
strings_["bootstrap_success_desc"] = "Blockchain data has been extracted successfully. Start the daemon to begin syncing from the bootstrap point.";
strings_["bootstrap_restart_daemon"] = "Restart Daemon";
strings_["bootstrap_failed"] = "Bootstrap Failed";
strings_["tt_open_dir"] = "Click to open in file explorer";
strings_["tt_rpc_host"] = "Hostname of the DragonX daemon";
strings_["tt_rpc_user"] = "RPC authentication username";
@@ -421,32 +381,6 @@ void I18n::loadBuiltinEnglish()
strings_["remove_favorite"] = "Remove Favorite";
strings_["favorite_address"] = "Favorite Address";
strings_["show_hidden"] = "Show Hidden (%d)";
strings_["filter"] = "Filter...";
strings_["no_addresses_yet"] = "No addresses yet";
strings_["showing_x_of_y"] = "Showing %d of %d addresses";
strings_["set_label"] = "Set Label...";
strings_["copied"] = "Copied!";
strings_["hidden_tag"] = " (hidden)";
strings_["z_address"] = "Z-Address";
strings_["t_address"] = "T-Address";
strings_["shielded_address"] = "Shielded Address";
strings_["transparent_address"] = "Transparent Address";
strings_["label_placeholder"] = "e.g. Savings, Mining...";
strings_["choose_icon"] = "Choose Icon";
strings_["clear_icon"] = "Clear Icon";
strings_["transfer_funds"] = "Transfer Funds";
strings_["transfer_to"] = "Transfer to:";
strings_["deshielding_warning"] = "Warning: This will de-shield funds from a private (Z) address to a transparent (T) address.";
strings_["shielding_notice"] = "Note: This will shield funds from a transparent (T) address to a private (Z) address.";
strings_["result_preview"] = "Result Preview";
strings_["sender_balance"] = "Sender: %.8f \xe2\x86\x92 %.8f DRGX";
strings_["recipient_balance"] = "Recipient: %.8f \xe2\x86\x92 %.8f DRGX";
strings_["insufficient_funds"] = "Insufficient funds for this amount plus fee.";
strings_["sends_full_balance_warning"] = "This sends the full balance. The sending address will have a zero balance.";
strings_["confirm_transfer"] = "Confirm Transfer";
strings_["transfer_sent"] = "Transfer Sent";
strings_["transfer_sent_desc"] = "Your transfer has been submitted to the network.";
strings_["transfer_failed"] = "Transfer Failed";
// Misc dialog strings
strings_["shield_operation_id"] = "Operation ID: %s";
@@ -671,41 +605,6 @@ void I18n::loadBuiltinEnglish()
strings_["syncing"] = "Syncing...";
strings_["block"] = "Block";
strings_["no_addresses_available"] = "No addresses available";
// Status bar
strings_["sb_warming_up"] = "Warming up...";
strings_["sb_block"] = "Block: %d";
strings_["sb_peers"] = "Peers: %zu";
strings_["sb_net_ghs"] = "Net: %.2f GH/s";
strings_["sb_net_mhs"] = "Net: %.2f MH/s";
strings_["sb_net_khs"] = "Net: %.2f KH/s";
strings_["sb_net_hs"] = "Net: %.1f H/s";
strings_["sb_mining_hs"] = "%.1f H/s";
strings_["sb_syncing_eta"] = "Syncing %.1f%% (%d left, %.0f blk/s, ~%s)";
strings_["sb_syncing_basic"] = "Syncing %.1f%% (%d left)";
strings_["sb_rescanning_pct"] = "Rescanning %.0f%%";
strings_["sb_rescanning"] = "Rescanning";
strings_["sb_importing_keys"] = "Importing keys";
strings_["sb_daemon_not_found"] = "Daemon not found";
strings_["sb_loading_config"] = "Loading configuration...";
strings_["sb_waiting_config"] = "Waiting for daemon config...";
strings_["sb_no_conf"] = "No DRAGONX.conf found";
strings_["sb_starting_daemon"] = "Starting dragonxd...";
strings_["sb_connecting_daemon"] = "Connecting to dragonxd...";
strings_["sb_auth_failed"] = "Auth failed — check rpcuser/rpcpassword";
strings_["sb_waiting_daemon"] = "Waiting for dragonxd to start...";
strings_["sb_waiting_daemon_err"] = "Waiting for dragonxd — %s";
strings_["sb_connecting_external"] = "Connecting to external daemon...";
strings_["sb_connecting_generic"] = "Connecting to daemon...";
strings_["sb_connecting_err"] = "Connecting to daemon — %s";
strings_["sb_daemon_crashed"] = "Daemon crashed %d times";
strings_["sb_extracting_sapling"] = "Extracting Sapling parameters...";
strings_["sb_sapling_failed"] = "Failed to extract Sapling parameters.";
strings_["sb_sapling_not_found"] = "Sapling parameters not found.";
strings_["sb_dragonxd_running"] = "dragonxd running";
strings_["sb_dragonxd_stopping"] = "Stopping dragonxd...";
strings_["sb_dragonxd_stopped"] = "dragonxd stopped";
strings_["sb_restarting_daemon"] = "Restarting daemon...";
// Errors & Messages
strings_["error"] = "Error";
@@ -971,8 +870,6 @@ void I18n::loadBuiltinEnglish()
strings_["mining_reset_defaults"] = "Reset Defaults";
strings_["mining_benchmark_tooltip"] = "Find optimal thread count for this CPU";
strings_["mining_benchmark_testing"] = "Testing";
strings_["mining_benchmark_cooling"] = "Cooling";
strings_["mining_benchmark_stabilizing"] = "Stabilizing";
strings_["mining_benchmark_cancel"] = "Cancel benchmark";
strings_["mining_benchmark_result"] = "Optimal";
strings_["mining_benchmark_dismiss"] = "Dismiss";