feat(wallets): open external wallets in place via datadir link (no copy)

Replace the "Import" action — which copied an out-of-datadir wallet into the
datadir and cluttered the list with hard-to-tell-apart duplicates — with an
in-place "Open" that links the real file into the datadir under a stable
per-target name (wallet-ip-<FNV8>.dat) and switches to it. The daemon only
loads a bare filename from its own datadir, so a link is the minimal bridge:
hard link first (no privileges, same volume — covers non-admin Windows),
symlink fallback (cross-volume), then an error. Never a copy (that would fork
the wallet) and never a delete of a real file.

Also:
- show each external wallet's originating sub-directory (…/Backups/2021)
- add a per-row "open folder location" button
- widen the modal (780 → 860) for the extra button
- guard the daemon against a dangling link (external file moved / USB
  unplugged) by falling back to wallet.dat rather than creating an empty one

Per-target link names (not one shared name) make switching between two
external wallets a real -wallet switch, so the rescan/cache index tracks each
correctly. Drops the now-dead wallets_import* i18n keys and back-fills the new
open/folder strings across all 8 languages + rebuilds the CJK subset.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-11 21:21:30 -05:00
parent b765d62e00
commit b7da6335d1
13 changed files with 166 additions and 94 deletions

Binary file not shown.

View File

@@ -1344,20 +1344,18 @@
"wallets_creating": "Wallet wird erstellt — der Node wird neu gestartet…",
"wallets_current": "aktuell",
"wallets_exists": "Eine Wallet mit diesem Namen existiert bereits.",
"wallets_external_tt": "Außerhalb deines Datenverzeichnisses — mit Import hineinkopieren.",
"wallets_external_tt": "Außerhalb deines Datenverzeichnisses — Öffnen verlinkt sie an ihrem Ort (keine Kopie).",
"wallets_folder_hint": "/pfad/zum/ordner mit .dat-Wallet-Dateien",
"wallets_folder_invalid": "Dieser Ordner existiert nicht.",
"wallets_import": "Importieren",
"wallets_import_failed": "Diese Wallet-Datei konnte nicht importiert werden.",
"wallets_import_hint": "Zum Öffnen importieren",
"wallets_import_tt": "Diese Wallet befindet sich außerhalb des Datenverzeichnisses. Beim Öffnen wird sie zuerst hineinkopiert (demnächst verfügbar).",
"wallets_imported": "Wallet importiert — wird gewechselt…",
"wallets_intro": "Wallet-Dateien in Ihrem Datenverzeichnis (und in beliebigen Ordnern, die Sie hinzufügen). Öffnen Sie eine, um zu wechseln — der Node wird neu gestartet, um sie zu laden, und jede Wallet behält ihre eigenen Daten.",
"wallets_name_invalid": "Bitte geben Sie einen gültigen Wallet-Namen ein.",
"wallets_never": "Nie geöffnet",
"wallets_new_hint": "Name (z. B. Ersparnisse)",
"wallets_new_label": "Eine neue Wallet erstellen:",
"wallets_open": "Öffnen",
"wallets_open_failed": "Diese Wallet konnte nicht an ihrem Ort geöffnet werden. Sie liegt vermutlich auf einem anderen Laufwerk als dein Datenverzeichnis — verschiebe sie auf dasselbe Laufwerk (unter Windows erlaubt auch der aktivierte Entwicklermodus laufwerkübergreifende Verknüpfungen).",
"wallets_open_folder": "Ordnerpfad öffnen",
"wallets_open_inplace_tt": "Öffnet diese Wallet an ihrem Ort — im Datenverzeichnis verlinkt (keine Kopie)",
"wallets_reveal": "Ordner anzeigen",
"wallets_sort_addresses": "Adressen",
"wallets_sort_asc": "Aufsteigend (ältestes / wenigste / kleinstes zuerst)",

View File

@@ -1344,20 +1344,18 @@
"wallets_creating": "Creando cartera — el nodo se reiniciará…",
"wallets_current": "actual",
"wallets_exists": "Ya existe una cartera con ese nombre.",
"wallets_external_tt": "Fuera de tu directorio de datos — usa Importar para copiarlo.",
"wallets_external_tt": "Fuera de tu directorio de datos — Abrir lo enlaza en su lugar (sin copiar).",
"wallets_folder_hint": "/ruta/a/carpeta con archivos .dat de cartera",
"wallets_folder_invalid": "Esa carpeta no existe.",
"wallets_import": "Importar",
"wallets_import_failed": "No se pudo importar ese archivo de cartera.",
"wallets_import_hint": "Importar para abrir",
"wallets_import_tt": "Esta cartera está fuera del directorio de datos. Al abrirla se copiará primero dentro (próximamente).",
"wallets_imported": "Cartera importada — cambiando…",
"wallets_intro": "Archivos de cartera en tu directorio de datos (y en las carpetas que agregues). Abre uno para cambiar: el nodo se reinicia para cargarlo y cada cartera conserva sus propios datos.",
"wallets_name_invalid": "Introduce un nombre de cartera válido.",
"wallets_never": "Nunca abierta",
"wallets_new_hint": "Nombre (p. ej. ahorros)",
"wallets_new_label": "Crear una nueva cartera:",
"wallets_open": "Abrir",
"wallets_open_failed": "No se pudo abrir esta cartera en su ubicación. Probablemente está en una unidad distinta a tu directorio de datos: muévela a la misma unidad (en Windows, activar el Modo de desarrollador también permite enlazar entre unidades).",
"wallets_open_folder": "Abrir ubicación de la carpeta",
"wallets_open_inplace_tt": "Abre esta cartera donde está, enlazándola al directorio de datos (sin copiar)",
"wallets_reveal": "Mostrar carpeta",
"wallets_sort_addresses": "Direcciones",
"wallets_sort_asc": "Ascendente (más antiguo / menos / más pequeño primero)",

View File

@@ -1344,20 +1344,18 @@
"wallets_creating": "Création du portefeuille — le nœud va redémarrer…",
"wallets_current": "actuel",
"wallets_exists": "Un portefeuille portant ce nom existe déjà.",
"wallets_external_tt": "Hors de votre répertoire de données — Importer pour le copier.",
"wallets_external_tt": "Hors de votre répertoire de données — Ouvrir le lie sur place (sans copie).",
"wallets_folder_hint": "/chemin/vers/le/dossier contenant les fichiers .dat",
"wallets_folder_invalid": "Ce dossier n'existe pas.",
"wallets_import": "Importer",
"wallets_import_failed": "Impossible d'importer ce fichier de portefeuille.",
"wallets_import_hint": "Importer pour ouvrir",
"wallets_import_tt": "Ce portefeuille se trouve hors du répertoire de données. L'ouvrir le copiera d'abord dedans (bientôt disponible).",
"wallets_imported": "Portefeuille importé — changement en cours…",
"wallets_intro": "Fichiers de portefeuille dans votre répertoire de données (et tout dossier que vous ajoutez). Ouvrez-en un pour changer — le nœud redémarre pour le charger, et chaque portefeuille conserve ses propres données.",
"wallets_name_invalid": "Veuillez saisir un nom de portefeuille valide.",
"wallets_never": "Jamais ouvert",
"wallets_new_hint": "Nom (par ex. épargne)",
"wallets_new_label": "Créer un nouveau portefeuille :",
"wallets_open": "Ouvrir",
"wallets_open_failed": "Impossible d'ouvrir ce portefeuille à son emplacement. Il se trouve probablement sur un lecteur différent de votre répertoire de données — déplacez-le sur le même lecteur (sous Windows, activer le mode développeur permet aussi de créer des liens entre lecteurs).",
"wallets_open_folder": "Ouvrir l'emplacement du dossier",
"wallets_open_inplace_tt": "Ouvre ce portefeuille à son emplacement — lié au répertoire de données (sans copie)",
"wallets_reveal": "Afficher le dossier",
"wallets_sort_addresses": "Adresses",
"wallets_sort_asc": "Croissant (plus ancien / moins / plus petit d'abord)",

View File

@@ -1344,20 +1344,18 @@
"wallets_creating": "ウォレットを作成中 — ノードが再起動します…",
"wallets_current": "現在",
"wallets_exists": "その名前のウォレットは既に存在します。",
"wallets_external_tt": "データディレクトリの外にあります — インポートでコピーします。",
"wallets_external_tt": "データディレクトリの外 —「開く」はその場でリンクします(コピーなし)。",
"wallets_folder_hint": "wallet .dat ファイルのあるフォルダのパス",
"wallets_folder_invalid": "そのフォルダは存在しません。",
"wallets_import": "インポート",
"wallets_import_failed": "そのウォレットファイルをインポートできませんでした。",
"wallets_import_hint": "インポートして開く",
"wallets_import_tt": "このウォレットはデータディレクトリの外にあります。開くには先にコピーが必要です(近日対応予定)。",
"wallets_imported": "ウォレットをインポートしました — 切り替え中…",
"wallets_intro": "データディレクトリ(および追加したフォルダ)内のウォレットファイルです。開いて切り替えると、それを読み込むためにノードが再起動します。各ウォレットは個別のデータを保持します。",
"wallets_name_invalid": "有効なウォレット名を入力してください。",
"wallets_never": "未使用",
"wallets_new_hint": "名前savings",
"wallets_new_label": "新しいウォレットを作成:",
"wallets_open": "開く",
"wallets_open_failed": "このウォレットをその場で開けませんでした。データディレクトリとは別のドライブにある可能性があります — 同じドライブに移動してくださいWindows では開発者モードを有効にするとドライブ間のリンクも可能になります)。",
"wallets_open_folder": "フォルダーの場所を開く",
"wallets_open_inplace_tt": "このウォレットをその場で開きます — データディレクトリにリンク(コピーなし)",
"wallets_reveal": "フォルダを開く",
"wallets_sort_addresses": "アドレス",
"wallets_sort_asc": "昇順(古い/少ない/小さい順)",

View File

@@ -1344,20 +1344,18 @@
"wallets_creating": "지갑을 만드는 중 — 노드가 재시작됩니다…",
"wallets_current": "현재",
"wallets_exists": "같은 이름의 지갑이 이미 존재합니다.",
"wallets_external_tt": "데이터 디렉터리 외부에 있음 — 가져오기로 복사합니다.",
"wallets_external_tt": "데이터 디렉터리 밖 — '열기'는 제자리에 링크합니다 (복사 없음).",
"wallets_folder_hint": "wallet-*.dat 파일이 있는 폴더 경로",
"wallets_folder_invalid": "해당 폴더가 존재하지 않습니다.",
"wallets_import": "가져오기",
"wallets_import_failed": "해당 지갑 파일을 가져올 수 없습니다.",
"wallets_import_hint": "가져와서 열기",
"wallets_import_tt": "이 지갑은 데이터 디렉터리 밖에 있습니다. 열면 먼저 안으로 복사됩니다(출시 예정).",
"wallets_imported": "지갑을 가져왔습니다 — 전환 중…",
"wallets_intro": "데이터 디렉터리(및 추가한 폴더)에 있는 지갑 파일입니다. 하나를 열면 전환됩니다 — 노드가 재시작되어 해당 지갑을 불러오며, 각 지갑은 고유한 데이터를 유지합니다.",
"wallets_name_invalid": "유효한 지갑 이름을 입력하세요.",
"wallets_never": "미사용",
"wallets_new_hint": "이름(예: savings)",
"wallets_new_label": "새 지갑 만들기:",
"wallets_open": "열기",
"wallets_open_failed": "이 지갑을 제자리에서 열 수 없습니다. 데이터 디렉터리와 다른 드라이브에 있을 가능성이 높습니다 — 같은 드라이브로 옮기세요 (Windows에서는 개발자 모드를 켜면 드라이브 간 링크도 가능합니다).",
"wallets_open_folder": "폴더 위치 열기",
"wallets_open_inplace_tt": "이 지갑을 있는 자리에서 엽니다 — 데이터 디렉터리에 링크 (복사 없음)",
"wallets_reveal": "폴더 열기",
"wallets_sort_addresses": "주소",
"wallets_sort_asc": "오름차순 (오래된/적은/작은 순)",

View File

@@ -1344,20 +1344,18 @@
"wallets_creating": "Criando carteira — o nó será reiniciado…",
"wallets_current": "atual",
"wallets_exists": "Já existe uma carteira com esse nome.",
"wallets_external_tt": "Fora do seu diretório de dados — use Importar para copiá-lo.",
"wallets_external_tt": "Fora do seu diretório de dados — Abrir o vincula no lugar (sem cópia).",
"wallets_folder_hint": "/caminho/para/pasta com arquivos .dat de carteira",
"wallets_folder_invalid": "Essa pasta não existe.",
"wallets_import": "Importar",
"wallets_import_failed": "Não foi possível importar esse arquivo de carteira.",
"wallets_import_hint": "Importar para abrir",
"wallets_import_tt": "Esta carteira está fora do diretório de dados. Ao abri-la, ela será copiada para lá primeiro (em breve).",
"wallets_imported": "Carteira importada — alternando…",
"wallets_intro": "Arquivos de carteira no seu diretório de dados (e em quaisquer pastas que você adicionar). Abra um para alternar — o nó reinicia para carregá-lo, e cada carteira mantém seus próprios dados.",
"wallets_name_invalid": "Insira um nome de carteira válido.",
"wallets_never": "Nunca aberta",
"wallets_new_hint": "Nome (ex.: poupança)",
"wallets_new_label": "Criar uma nova carteira:",
"wallets_open": "Abrir",
"wallets_open_failed": "Não foi possível abrir esta carteira no lugar. Provavelmente está em uma unidade diferente do seu diretório de dados — mova-a para a mesma unidade (no Windows, ativar o Modo de Desenvolvedor também permite vincular entre unidades).",
"wallets_open_folder": "Abrir local da pasta",
"wallets_open_inplace_tt": "Abre esta carteira onde está — vinculada ao diretório de dados (sem cópia)",
"wallets_reveal": "Mostrar pasta",
"wallets_sort_addresses": "Endereços",
"wallets_sort_asc": "Crescente (mais antigo / menos / menor primeiro)",

View File

@@ -1344,20 +1344,18 @@
"wallets_creating": "Создание кошелька — узел будет перезапущен…",
"wallets_current": "текущий",
"wallets_exists": "Кошелёк с таким именем уже существует.",
"wallets_external_tt": "Вне вашего каталога данных — «Импорт» скопирует его.",
"wallets_external_tt": "Вне каталога данных — «Открыть» создаёт ссылку на месте (без копирования).",
"wallets_folder_hint": "/путь/к/папке с файлами кошельков .dat",
"wallets_folder_invalid": "Такой папки не существует.",
"wallets_import": "Импорт",
"wallets_import_failed": "Не удалось импортировать этот файл кошелька.",
"wallets_import_hint": "Импортировать для открытия",
"wallets_import_tt": "Этот кошелёк находится вне папки данных. При открытии он сначала будет скопирован в неё (скоро).",
"wallets_imported": "Кошелёк импортирован — переключение…",
"wallets_intro": "Файлы кошельков в вашей папке данных (и в любых добавленных вами папках). Откройте один, чтобы переключиться — узел перезапустится, чтобы загрузить его, и у каждого кошелька свои данные.",
"wallets_name_invalid": "Введите допустимое имя кошелька.",
"wallets_never": "Не открывался",
"wallets_new_hint": "Имя (например, savings)",
"wallets_new_label": "Создать новый кошелёк:",
"wallets_open": "Открыть",
"wallets_open_failed": "Не удалось открыть этот кошелёк на месте. Вероятно, он на другом диске, чем каталог данных — переместите его на тот же диск (в Windows включённый режим разработчика также позволяет создавать ссылки между дисками).",
"wallets_open_folder": "Открыть расположение папки",
"wallets_open_inplace_tt": "Открывает этот кошелёк на месте — по ссылке в каталоге данных (без копирования)",
"wallets_reveal": "Открыть папку",
"wallets_sort_addresses": "Адреса",
"wallets_sort_asc": "По возрастанию (сначала старые / меньше / меньший)",

View File

@@ -1344,20 +1344,18 @@
"wallets_creating": "正在创建钱包——节点将重启…",
"wallets_current": "当前",
"wallets_exists": "已存在同名钱包。",
"wallets_external_tt": "位于数据目录之外 — 导入以将其复制进来。",
"wallets_external_tt": "数据目录之外 —「打开」会就地链接(不复制)。",
"wallets_folder_hint": "/含 .dat 钱包文件的文件夹路径",
"wallets_folder_invalid": "该文件夹不存在。",
"wallets_import": "导入",
"wallets_import_failed": "无法导入该钱包文件。",
"wallets_import_hint": "导入以打开",
"wallets_import_tt": "此钱包位于数据目录之外。打开它会先将其复制进来(即将推出)。",
"wallets_imported": "钱包已导入——正在切换…",
"wallets_intro": "位于您数据目录(以及您添加的任何文件夹)中的钱包文件。打开一个即可切换——节点将重启以加载它,且每个钱包各自保留其数据。",
"wallets_name_invalid": "请输入有效的钱包名称。",
"wallets_never": "从未打开",
"wallets_new_hint": "名称(例如 savings",
"wallets_new_label": "创建新钱包:",
"wallets_open": "打开",
"wallets_open_failed": "无法在原位置打开此钱包。它可能与数据目录位于不同的驱动器上 — 请将其移动到同一驱动器(在 Windows 上,启用开发者模式也可跨驱动器链接)。",
"wallets_open_folder": "打开文件夹位置",
"wallets_open_inplace_tt": "在原位置打开此钱包 — 链接到数据目录(不复制)",
"wallets_reveal": "打开文件夹",
"wallets_sort_addresses": "地址",
"wallets_sort_asc": "升序(最早/最少/最小优先)",