Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
24fa921f8c | ||
|
|
b25723c739 | ||
|
|
6951e6113e | ||
|
|
bc1f7aa13a | ||
|
|
dcd6564f7d | ||
|
|
74691fa255 | ||
|
|
e130ebc6da | ||
|
|
7a37f68e9b | ||
|
|
eac5f8ee08 |
@@ -37,7 +37,6 @@ asmap.dat
|
||||
/memory
|
||||
/todo.md
|
||||
/.github/
|
||||
/ObsidianDragon-agent/
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
+1
-18
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 }
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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!")
|
||||
@@ -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) ===")
|
||||
@@ -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)")
|
||||
@@ -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)
|
||||
@@ -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'
|
||||
|
||||
@@ -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}")
|
||||
@@ -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
@@ -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()) {
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
+8
-18
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,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");
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
+2757
-3493
File diff suppressed because it is too large
Load Diff
+2897
-3655
File diff suppressed because it is too large
Load Diff
+3534
-4381
File diff suppressed because it is too large
Load Diff
+2894
-3625
File diff suppressed because it is too large
Load Diff
+3003
-3729
File diff suppressed because it is too large
Load Diff
+4103
-5076
File diff suppressed because it is too large
Load Diff
+2563
-3229
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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"
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -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
@@ -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),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user