Internationalization: 7 languages with live switching

Custom signal-based runtime (no i18n dependency): shared src/shared/i18n
dictionaries, ui I18nService.t() reactive by construction so language
switches live, and a backend i18n.ts for native menus + thrown errors.
Every user-facing string keyed across templates, toasts, dialogs, file
kinds, columns, macOS menu/context menus, and errors. Language picker in
Options (auto-detects OS locale, falls back to English); switching
relabels the webview and native chrome instantly. English/Spanish/French/
German fully drafted; Chinese/Japanese/Basque drafted, flagged for native
review. Docs updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-14 15:52:54 +02:00
co-authored by Claude Fable 5
parent d8cfc54049
commit a0f56822cd
43 changed files with 2755 additions and 322 deletions
+3 -2
View File
@@ -1,3 +1,4 @@
import { t } from "../i18n";
import { LocalProvider } from "./local";
import type { FileSystemProvider } from "./provider";
@@ -15,7 +16,7 @@ export class SessionRegistry {
get(sessionId: string): FileSystemProvider {
const provider = this.sessions.get(sessionId);
if (!provider) throw new Error(`Session "${sessionId}" is not connected`);
if (!provider) throw new Error(t("errors.sessionNotConnected", { id: sessionId }));
return provider;
}
@@ -27,7 +28,7 @@ export class SessionRegistry {
async remove(sessionId: string): Promise<void> {
if (sessionId === LOCAL_SESSION_ID)
throw new Error("The local session cannot be disconnected");
throw new Error(t("errors.localCannotDisconnect"));
const provider = this.sessions.get(sessionId);
if (!provider) return;
this.sessions.delete(sessionId);
+3 -2
View File
@@ -3,6 +3,7 @@ import { readFileSync } from "node:fs";
import path from "node:path";
import { Client, type ConnectConfig, type SFTPWrapper } from "ssh2";
import type { ConnectionConfig, EntryType, FileEntry } from "../../shared/types";
import { t } from "../i18n";
import { expandTilde } from "../store/ssh-config";
import type { FileSystemProvider } from "./provider";
@@ -63,7 +64,7 @@ export class SftpProvider implements FileSystemProvider {
connectConfig.password = config.auth.password;
break;
case "agent":
if (!agentSock) throw new Error("ssh-agent not available (SSH_AUTH_SOCK is not set)");
if (!agentSock) throw new Error(t("errors.sshAgentUnavailable"));
connectConfig.agent = agentSock;
break;
case "key": {
@@ -73,7 +74,7 @@ export class SftpProvider implements FileSystemProvider {
connectConfig.privateKey = readFileSync(expandTilde(config.auth.privateKeyPath));
} catch {
if (!agentSock)
throw new Error(`identity file not found: ${config.auth.privateKeyPath}`);
throw new Error(t("errors.identityFileNotFound", { path: config.auth.privateKeyPath }));
}
if (config.auth.passphrase) connectConfig.passphrase = config.auth.passphrase;
else if (agentSock) connectConfig.agent = agentSock;