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
+5 -4
View File
@@ -4,6 +4,7 @@ import { BRANDING } from "../../shared/branding";
import type { EntryType, TransferSnapshot, TransferState } from "../../shared/types";
import type { FileSystemProvider } from "../fs/provider";
import type { SessionRegistry } from "../fs/registry";
import { t } from "../i18n";
import { walkTree } from "./walk";
const PROGRESS_INTERVAL_MS = 100;
@@ -59,7 +60,7 @@ async function ensureDir(fs: FileSystemProvider, path: string): Promise<void> {
try {
const st = await fs.stat(path);
if (st.type === "dir") return;
throw new Error(`"${path}" exists and is not a directory`);
throw new Error(t("errors.notADirectory", { path }));
} catch {
await fs.mkdir(path);
}
@@ -235,7 +236,7 @@ export class TransferEngine {
if (sameFs) {
for (const file of files) {
if (file.srcPath === file.dstPath) {
throw new Error(`"${file.name}": source and destination are the same file`);
throw new Error(t("errors.sameFileTransfer", { name: file.name }));
}
}
}
@@ -343,10 +344,10 @@ export class TransferEngine {
if (samePath) {
throw new Error(
isDir ? "Cannot copy a folder onto itself" : "Source and destination are the same file",
isDir ? t("errors.folderOntoItself") : t("errors.sameFileNoName"),
);
}
throw new Error("Cannot copy a folder into itself");
throw new Error(t("errors.folderIntoItself"));
}
/**