diff --git a/res/fonts/NotoSansCJK-Subset.ttf b/res/fonts/NotoSansCJK-Subset.ttf index 9f05a88..9455b8d 100644 Binary files a/res/fonts/NotoSansCJK-Subset.ttf and b/res/fonts/NotoSansCJK-Subset.ttf differ diff --git a/res/lang/de.json b/res/lang/de.json index 6ff00a3..a11b880 100644 --- a/res/lang/de.json +++ b/res/lang/de.json @@ -373,10 +373,18 @@ "export_keys_danger": "ACHTUNG: Dies exportiert ALLE privaten Schlüssel aus Ihrer Wallet! Jeder mit Zugriff auf diese Datei kann Ihre Gelder stehlen. Sicher aufbewahren und nach Gebrauch löschen.", "export_keys_include_t": "T-Adressen einschließen (transparent)", "export_keys_include_z": "Z-Adressen einschließen (abgeschirmt)", + "export_keys_none_addrs": "Keine Adressen zum Exportieren", + "export_keys_none_result": "Keine Schlüssel exportiert (0 von %d) — entsperren Sie die Wallet und versuchen Sie es erneut.", + "export_keys_none_toast": "Es konnten keine Schlüssel exportiert werden — ist die Wallet entsperrt?", + "export_keys_not_connected": "Nicht mit dem Daemon verbunden", "export_keys_options": "Export-Optionen:", + "export_keys_partial": "%d von %d Schlüsseln exportiert — unvollständig (einige hatten keinen Ausgabeschlüssel oder die Wallet ist gesperrt).", + "export_keys_partial_toast": "Teilexport: %d von %d Schlüsseln", "export_keys_progress": "Exportiere %d/%d...", + "export_keys_select_type": "Wählen Sie mindestens einen Adresstyp", "export_keys_success": "Schlüssel erfolgreich exportiert", "export_keys_title": "Alle privaten Schlüssel exportieren", + "export_keys_write_fail": "Schlüsseldatei konnte nicht geschrieben werden.", "export_private_key": "Privaten Schlüssel exportieren", "export_tx_count": "%zu Transaktionen als CSV exportieren.", "export_tx_file_fail": "CSV-Datei konnte nicht erstellt werden", diff --git a/res/lang/es.json b/res/lang/es.json index b54ecfa..d81b4a5 100644 --- a/res/lang/es.json +++ b/res/lang/es.json @@ -373,10 +373,18 @@ "export_keys_danger": "PELIGRO: ¡Esto exportará TODAS las claves privadas de tu cartera! Cualquiera con acceso a este archivo puede robar tus fondos. Guárdalo de forma segura y elimínalo después de usar.", "export_keys_include_t": "Incluir direcciones T (transparentes)", "export_keys_include_z": "Incluir direcciones Z (protegidas)", + "export_keys_none_addrs": "No hay direcciones para exportar", + "export_keys_none_result": "No se exportaron claves (0 de %d): desbloquea el monedero e inténtalo de nuevo.", + "export_keys_none_toast": "No se pudieron exportar claves: ¿está desbloqueado el monedero?", + "export_keys_not_connected": "Sin conexión con el daemon", "export_keys_options": "Opciones de exportación:", + "export_keys_partial": "Se exportaron %d de %d claves: incompleto (algunas no tenían clave de gasto o el monedero está bloqueado).", + "export_keys_partial_toast": "Exportación parcial: %d de %d claves", "export_keys_progress": "Exportando %d/%d...", + "export_keys_select_type": "Selecciona al menos un tipo de dirección", "export_keys_success": "Claves exportadas exitosamente", "export_keys_title": "Exportar Todas las Claves Privadas", + "export_keys_write_fail": "No se pudo escribir el archivo de claves.", "export_private_key": "Exportar Clave Privada", "export_tx_count": "Exportar %zu transacciones a archivo CSV.", "export_tx_file_fail": "Error al crear archivo CSV", diff --git a/res/lang/fr.json b/res/lang/fr.json index 00ff346..a97a864 100644 --- a/res/lang/fr.json +++ b/res/lang/fr.json @@ -373,10 +373,18 @@ "export_keys_danger": "DANGER : Ceci exportera TOUTES les clés privées de votre portefeuille ! Toute personne ayant accès à ce fichier peut voler vos fonds. Conservez-le en sécurité et supprimez-le après utilisation.", "export_keys_include_t": "Inclure les adresses T (transparentes)", "export_keys_include_z": "Inclure les adresses Z (blindées)", + "export_keys_none_addrs": "Aucune adresse à exporter", + "export_keys_none_result": "Aucune clé exportée (0 sur %d) — déverrouillez le portefeuille et réessayez.", + "export_keys_none_toast": "Aucune clé n'a pu être exportée — le portefeuille est-il déverrouillé ?", + "export_keys_not_connected": "Non connecté au daemon", "export_keys_options": "Options d'exportation :", + "export_keys_partial": "%d clés sur %d exportées — incomplet (certaines sans clé de dépense, ou le portefeuille est verrouillé).", + "export_keys_partial_toast": "Export partiel : %d clés sur %d", "export_keys_progress": "Exportation %d/%d...", + "export_keys_select_type": "Sélectionnez au moins un type d'adresse", "export_keys_success": "Clés exportées avec succès", "export_keys_title": "Exporter toutes les clés privées", + "export_keys_write_fail": "Échec de l'écriture du fichier de clés.", "export_private_key": "Exporter la clé privée", "export_tx_count": "Exporter %zu transactions en fichier CSV.", "export_tx_file_fail": "Impossible de créer le fichier CSV", diff --git a/res/lang/ja.json b/res/lang/ja.json index 3260680..8dbe0e5 100644 --- a/res/lang/ja.json +++ b/res/lang/ja.json @@ -373,10 +373,18 @@ "export_keys_danger": "危険:ウォレットからすべての秘密鍵がエクスポートされます!このファイルにアクセスできる人は誰でもあなたの資金を盗めます。安全に保管し、使用後は削除してください。", "export_keys_include_t": "Tアドレスを含める(透明)", "export_keys_include_z": "Zアドレスを含める(シールド)", + "export_keys_none_addrs": "エクスポートするアドレスがありません", + "export_keys_none_result": "鍵をエクスポートできませんでした(%d 件中 0 件)。ウォレットのロックを解除して再試行してください。", + "export_keys_none_toast": "鍵をエクスポートできませんでした。ウォレットはロック解除されていますか?", + "export_keys_not_connected": "デーモンに接続されていません", "export_keys_options": "エクスポートオプション:", + "export_keys_partial": "%d 件中 %d 件の鍵をエクスポート — 不完全(支払い鍵がない、またはウォレットがロックされています)。", + "export_keys_partial_toast": "部分エクスポート: %d 件中 %d 件", "export_keys_progress": "エクスポート中 %d/%d...", + "export_keys_select_type": "アドレスの種類を少なくとも1つ選択してください", "export_keys_success": "鍵のエクスポートに成功しました", "export_keys_title": "すべての秘密鍵をエクスポート", + "export_keys_write_fail": "鍵ファイルの書き込みに失敗しました。", "export_private_key": "秘密鍵をエクスポート", "export_tx_count": "%zu件の取引をCSVファイルにエクスポート。", "export_tx_file_fail": "CSVファイルの作成に失敗しました", diff --git a/res/lang/ko.json b/res/lang/ko.json index 7bc70cf..3f8f958 100644 --- a/res/lang/ko.json +++ b/res/lang/ko.json @@ -373,10 +373,18 @@ "export_keys_danger": "위험: 지갑의 모든 개인 키가 내보내집니다! 이 파일에 접근할 수 있는 사람은 누구나 자금을 훔칠 수 있습니다. 안전하게 보관하고 사용 후 삭제하세요.", "export_keys_include_t": "T 주소 포함 (투명)", "export_keys_include_z": "Z 주소 포함 (차폐)", + "export_keys_none_addrs": "내보낼 주소가 없습니다", + "export_keys_none_result": "키를 내보내지 못했습니다(%d개 중 0개). 지갑 잠금을 해제하고 다시 시도하세요.", + "export_keys_none_toast": "키를 내보낼 수 없습니다. 지갑이 잠금 해제되어 있나요?", + "export_keys_not_connected": "데몬에 연결되어 있지 않습니다", "export_keys_options": "내보내기 옵션:", + "export_keys_partial": "%d개 중 %d개 키 내보냄 — 불완전(일부는 지출 키가 없거나 지갑이 잠겨 있음).", + "export_keys_partial_toast": "부분 내보내기: %d개 중 %d개 키", "export_keys_progress": "내보내는 중 %d/%d...", + "export_keys_select_type": "주소 유형을 하나 이상 선택하세요", "export_keys_success": "키 내보내기 성공", "export_keys_title": "모든 개인 키 내보내기", + "export_keys_write_fail": "키 파일을 쓰지 못했습니다.", "export_private_key": "개인 키 내보내기", "export_tx_count": "%zu건의 거래를 CSV 파일로 내보냈습니다.", "export_tx_file_fail": "CSV 파일 생성 실패", diff --git a/res/lang/pt.json b/res/lang/pt.json index 5dff42c..d7aac26 100644 --- a/res/lang/pt.json +++ b/res/lang/pt.json @@ -373,10 +373,18 @@ "export_keys_danger": "PERIGO: Isto exportará TODAS as chaves privadas da sua carteira! Qualquer pessoa com acesso a este arquivo pode roubar seus fundos. Guarde com segurança e exclua após o uso.", "export_keys_include_t": "Incluir endereços T (transparentes)", "export_keys_include_z": "Incluir endereços Z (blindados)", + "export_keys_none_addrs": "Nenhum endereço para exportar", + "export_keys_none_result": "Nenhuma chave exportada (0 de %d) — desbloqueie a carteira e tente novamente.", + "export_keys_none_toast": "Não foi possível exportar chaves — a carteira está desbloqueada?", + "export_keys_not_connected": "Não conectado ao daemon", "export_keys_options": "Opções de exportação:", + "export_keys_partial": "Exportadas %d de %d chaves — incompleto (algumas sem chave de gasto, ou a carteira está bloqueada).", + "export_keys_partial_toast": "Exportação parcial: %d de %d chaves", "export_keys_progress": "Exportando %d/%d...", + "export_keys_select_type": "Selecione ao menos um tipo de endereço", "export_keys_success": "Chaves exportadas com sucesso", "export_keys_title": "Exportar Todas as Chaves Privadas", + "export_keys_write_fail": "Falha ao gravar o arquivo de chaves.", "export_private_key": "Exportar Chave Privada", "export_tx_count": "Exportar %zu transações para arquivo CSV.", "export_tx_file_fail": "Falha ao criar arquivo CSV", diff --git a/res/lang/ru.json b/res/lang/ru.json index 0ff7901..d212ccc 100644 --- a/res/lang/ru.json +++ b/res/lang/ru.json @@ -373,10 +373,18 @@ "export_keys_danger": "ОПАСНОСТЬ: Будут экспортированы ВСЕ приватные ключи из вашего кошелька! Любой, кто получит доступ к этому файлу, сможет украсть ваши средства. Храните его в безопасности и удалите после использования.", "export_keys_include_t": "Включить T-адреса (прозрачные)", "export_keys_include_z": "Включить Z-адреса (экранированные)", + "export_keys_none_addrs": "Нет адресов для экспорта", + "export_keys_none_result": "Ключи не экспортированы (0 из %d) — разблокируйте кошелёк и повторите попытку.", + "export_keys_none_toast": "Не удалось экспортировать ключи — кошелёк разблокирован?", + "export_keys_not_connected": "Нет подключения к демону", "export_keys_options": "Параметры экспорта:", + "export_keys_partial": "Экспортировано %d из %d ключей — неполно (у некоторых нет ключа расходования или кошелёк заблокирован).", + "export_keys_partial_toast": "Частичный экспорт: %d из %d ключей", "export_keys_progress": "Экспорт %d/%d...", + "export_keys_select_type": "Выберите хотя бы один тип адреса", "export_keys_success": "Ключи успешно экспортированы", "export_keys_title": "Экспорт всех приватных ключей", + "export_keys_write_fail": "Не удалось записать файл ключей.", "export_private_key": "Экспорт приватного ключа", "export_tx_count": "Экспортировать %zu транзакций в файл CSV.", "export_tx_file_fail": "Не удалось создать файл CSV", diff --git a/res/lang/zh.json b/res/lang/zh.json index de8a4b7..6ba2683 100644 --- a/res/lang/zh.json +++ b/res/lang/zh.json @@ -373,10 +373,18 @@ "export_keys_danger": "危险:这将导出您钱包中的所有私钥!任何获得此文件的人都可以窃取您的资金。请安全保管并在使用后删除。", "export_keys_include_t": "包含 T 地址(透明)", "export_keys_include_z": "包含 Z 地址(屏蔽)", + "export_keys_none_addrs": "没有可导出的地址", + "export_keys_none_result": "未导出任何密钥(%d 个中 0 个)——请解锁钱包后重试。", + "export_keys_none_toast": "无法导出密钥——钱包已解锁吗?", + "export_keys_not_connected": "未连接到守护进程", "export_keys_options": "导出选项:", + "export_keys_partial": "已导出 %d/%d 个密钥——不完整(部分没有花费密钥,或钱包已锁定)。", + "export_keys_partial_toast": "部分导出:%d/%d 个密钥", "export_keys_progress": "正在导出 %d/%d...", + "export_keys_select_type": "请至少选择一种地址类型", "export_keys_success": "密钥导出成功", "export_keys_title": "导出所有私钥", + "export_keys_write_fail": "写入密钥文件失败。", "export_private_key": "导出私钥", "export_tx_count": "导出 %zu 笔交易到 CSV 文件。", "export_tx_file_fail": "无法创建 CSV 文件", diff --git a/src/app_sweep.cpp b/src/app_sweep.cpp index 55f3558..610bb65 100644 --- a/src/app_sweep.cpp +++ b/src/app_sweep.cpp @@ -20,6 +20,7 @@ #include "ui/windows/send_tab.h" #include "ui/windows/wallets_dialog.h" #include "ui/windows/export_transactions_dialog.h" +#include "ui/windows/export_all_keys_dialog.h" #include "ui/windows/daemon_download_dialog.h" #include "ui/windows/xmrig_download_dialog.h" #include "ui/pages/settings_page.h" @@ -269,6 +270,8 @@ void App::buildSweepCatalog() [](App& a) { a.show_export_key_ = true; }, [](App& a) { a.show_export_key_ = false; }); add("modal-export-transactions", ui::NavPage::Settings, [](App&) { ui::ExportTransactionsDialog::show(); }, [](App&) { ui::ExportTransactionsDialog::hide(); }); + add("modal-export-all-keys", ui::NavPage::Settings, + [](App&) { ui::ExportAllKeysDialog::show(); }, [](App&) { ui::ExportAllKeysDialog::hide(); }); add("modal-backup", ui::NavPage::Overview, [](App& a) { a.show_backup_ = true; }, [](App& a) { a.show_backup_ = false; a.backup_status_.clear(); }); add("modal-about", ui::NavPage::Overview, diff --git a/src/ui/windows/export_all_keys_dialog.cpp b/src/ui/windows/export_all_keys_dialog.cpp index d33fd29..1821bb8 100644 --- a/src/ui/windows/export_all_keys_dialog.cpp +++ b/src/ui/windows/export_all_keys_dialog.cpp @@ -12,6 +12,7 @@ #include "../schema/ui_schema.h" #include "../material/draw_helpers.h" #include "../material/type.h" +#include "../widgets/copy_field.h" #include "../theme.h" #include "../../embedded/IconsMaterialDesign.h" #include "imgui.h" @@ -29,7 +30,8 @@ using json = nlohmann::json; // Static state static bool s_open = false; static bool s_exporting = false; -static std::string s_status; +static std::string s_status; // error / partial message (non-empty => red text) +static std::string s_saved_path; // full path on full success (=> green check + copy field) static std::string s_exported_keys; static int s_total_addresses = 0; static int s_exported_count = 0; @@ -42,6 +44,7 @@ void ExportAllKeysDialog::show() s_open = true; s_exporting = false; s_status.clear(); + s_saved_path.clear(); s_exported_keys.clear(); s_total_addresses = 0; s_exported_count = 0; @@ -60,74 +63,77 @@ bool ExportAllKeysDialog::isOpen() return s_open; } +void ExportAllKeysDialog::hide() +{ + s_open = false; + s_status.clear(); + s_saved_path.clear(); +} + void ExportAllKeysDialog::render(App* app) { if (!s_open) return; + namespace m = material; - auto& S = schema::UI(); - auto win = S.window("dialogs.export-all-keys"); - auto exportBtn = S.button("dialogs.export-all-keys", "export-button"); - auto closeBtn = S.button("dialogs.export-all-keys", "close-button"); + m::OverlayDialogSpec ov; + ov.title = TR("export_keys_title"); + ov.p_open = &s_open; + ov.style = m::OverlayStyle::BlurFloat; + ov.cardWidth = 600.0f; + ov.idSuffix = "exportallkeys"; + if (m::BeginOverlayDialog(ov)) { + m::DialogWarningHeader(TR("export_keys_danger")); + ImGui::Spacing(); - material::OverlayDialogSpec ov; - ov.title = TR("export_keys_title"); ov.p_open = &s_open; - ov.style = material::OverlayStyle::BlurFloat; // floating content on the blur, plain heading - ov.cardWidth = win.width; ov.cardBottomViewportRatio = 0.94f; // keep authored width - if (material::BeginOverlayDialog(ov)) { - // Warning - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.4f, 0.4f, 1.0f)); - ImGui::PushFont(material::Type().iconSmall()); - ImGui::Text(ICON_MD_WARNING); - ImGui::PopFont(); - ImGui::SameLine(0, 4.0f); - ImGui::TextWrapped("%s", TR("export_keys_danger")); - ImGui::PopStyleColor(); - - ImGui::Spacing(); - ImGui::Separator(); - ImGui::Spacing(); - - if (s_exporting) { - ImGui::BeginDisabled(); - } - - // Options - ImGui::Text("%s", TR("export_keys_options")); + ImGui::BeginDisabled(s_exporting); + ImGui::TextUnformatted(TR("export_keys_options")); ImGui::Checkbox(TR("export_keys_include_z"), &s_include_z); ImGui::Checkbox(TR("export_keys_include_t"), &s_include_t); - ImGui::Spacing(); - - // Filename - material::LabeledInput(TR("output_filename"), "##Filename", s_filename, sizeof(s_filename)); - + m::LabeledInput(TR("output_filename"), "##Filename", s_filename, sizeof(s_filename)); ImGui::Spacing(); - ImGui::TextDisabled("%s", TR("file_save_location")); - + ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(m::OnSurfaceDisabled()), "%s", TR("file_save_location")); + ImGui::EndDisabled(); + + // Progress / result. if (s_exporting) { - ImGui::EndDisabled(); + ImGui::Spacing(); + ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(m::Primary()), TR("export_keys_progress"), + s_exported_count, s_total_addresses); + ImGui::SameLine(0, 0); + ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(m::Primary()), "%s", m::LoadingDots()); + } else if (!s_saved_path.empty()) { + ImGui::Spacing(); + ImGui::PushFont(m::Type().iconSmall()); + ImGui::TextColored(m::SuccessVec4(), ICON_MD_CHECK_CIRCLE); + ImGui::PopFont(); + ImGui::SameLine(0, 4.0f * Layout::dpiScale()); + ImGui::TextColored(m::SuccessVec4(), "%s", TR("export_keys_success")); + widgets::AddressCopyField("##exportedkeyspath", s_saved_path); + } else if (!s_status.empty()) { + ImGui::Spacing(); + ImGui::PushTextWrapPos(0.0f); + ImGui::TextColored(ImVec4(0.8f, 0.3f, 0.3f, 1.0f), "%s", s_status.c_str()); + ImGui::PopTextWrapPos(); } - + ImGui::Spacing(); - ImGui::Separator(); - ImGui::Spacing(); - - // Export button - if (s_exporting) { - ImGui::BeginDisabled(); - } - - if (material::StyledButton(TR("export_keys_btn"), ImVec2(exportBtn.width, 0), S.resolveFont(exportBtn.font))) { + + bool doExport = false, doClose = false; + m::DialogActionFooter(TR("export_keys_btn"), !s_exporting, TR("close"), doExport, doClose); + if (doClose) s_open = false; + if (doExport) { if (!s_include_z && !s_include_t) { - Notifications::instance().warning("Select at least one address type"); + Notifications::instance().warning(TR("export_keys_select_type")); } else if (!app->rpc() || !app->rpc()->isConnected()) { - Notifications::instance().error("Not connected to daemon"); + Notifications::instance().error(TR("export_keys_not_connected")); } else { s_exporting = true; s_exported_keys.clear(); s_exported_count = 0; - s_status = "Exporting keys..."; - + s_status.clear(); + s_saved_path.clear(); + const auto& state = app->getWalletState(); // Count total addresses to export @@ -137,7 +143,8 @@ void ExportAllKeysDialog::render(App* app) if (s_total_addresses == 0) { s_exporting = false; - s_status = "No addresses to export"; + s_status = TR("export_keys_none_addrs"); + m::EndOverlayDialog(); return; } @@ -216,21 +223,21 @@ void ExportAllKeysDialog::render(App* app) return [exported, total, filepath, writeOk]() { s_exported_count = exported; s_exporting = false; + char buf[320]; if (exported == 0) { - s_status = "No keys exported (0 of " + std::to_string(total) + - ") — unlock the wallet (if encrypted) and try again."; - Notifications::instance().error("No keys could be exported — is the wallet unlocked?"); + snprintf(buf, sizeof(buf), TR("export_keys_none_result"), total); + s_status = buf; + Notifications::instance().error(TR("export_keys_none_toast")); } else if (!writeOk) { - s_status = "Failed to write file"; - Notifications::instance().error("Failed to save key file"); + s_status = TR("export_keys_write_fail"); + Notifications::instance().error(TR("export_keys_write_fail")); } else if (exported < total) { - s_status = "Exported " + std::to_string(exported) + " of " + - std::to_string(total) + " keys to: " + filepath + - " (INCOMPLETE — some addresses had no spending key or the wallet is locked)"; - Notifications::instance().warning("Partial export: " + std::to_string(exported) + - " of " + std::to_string(total) + " keys"); + snprintf(buf, sizeof(buf), TR("export_keys_partial"), exported, total); + s_status = buf; + snprintf(buf, sizeof(buf), TR("export_keys_partial_toast"), exported, total); + Notifications::instance().warning(buf); } else { - s_status = "Exported to: " + filepath; + s_saved_path = filepath; Notifications::instance().success(TR("export_keys_success"), 5.0f); } }; @@ -238,24 +245,8 @@ void ExportAllKeysDialog::render(App* app) } } } - - if (s_exporting) { - ImGui::EndDisabled(); - ImGui::SameLine(); - ImGui::TextDisabled(TR("export_keys_progress"), s_exported_count, s_total_addresses); - } - - ImGui::SameLine(); - if (material::StyledButton(TR("close"), ImVec2(closeBtn.width, 0), S.resolveFont(closeBtn.font))) { - s_open = false; - } - - // Status - if (!s_status.empty()) { - ImGui::Spacing(); - ImGui::TextWrapped("%s", s_status.c_str()); - } - material::EndOverlayDialog(); + + m::EndOverlayDialog(); } } diff --git a/src/ui/windows/export_all_keys_dialog.h b/src/ui/windows/export_all_keys_dialog.h index 7d23897..cad6150 100644 --- a/src/ui/windows/export_all_keys_dialog.h +++ b/src/ui/windows/export_all_keys_dialog.h @@ -27,6 +27,9 @@ public: // Check if dialog is open static bool isOpen(); + + // Close the dialog (used by the UI sweep teardown) + static void hide(); }; } // namespace ui diff --git a/src/util/i18n.cpp b/src/util/i18n.cpp index 5ce8a88..89dde18 100644 --- a/src/util/i18n.cpp +++ b/src/util/i18n.cpp @@ -1327,6 +1327,14 @@ void I18n::loadBuiltinEnglish() strings_["export_keys_include_z"] = "Include Z-addresses (shielded)"; strings_["export_keys_options"] = "Export options:"; strings_["export_keys_success"] = "Keys exported successfully"; + strings_["export_keys_select_type"] = "Select at least one address type"; + strings_["export_keys_not_connected"] = "Not connected to the daemon"; + strings_["export_keys_none_addrs"] = "No addresses to export"; + strings_["export_keys_none_result"] = "No keys exported (0 of %d) \xE2\x80\x94 unlock the wallet and try again."; + strings_["export_keys_none_toast"] = "No keys could be exported \xE2\x80\x94 is the wallet unlocked?"; + strings_["export_keys_write_fail"] = "Failed to write the key file."; + strings_["export_keys_partial"] = "Exported %d of %d keys \xE2\x80\x94 incomplete (some had no spending key, or the wallet is locked)."; + strings_["export_keys_partial_toast"] = "Partial export: %d of %d keys"; strings_["export_keys_title"] = "Export All Private Keys"; // --- Export Transactions Dialog ---