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
+299
View File
@@ -0,0 +1,299 @@
// Simplified Chinese translations. Fall back to English for missing keys.
// TODO(i18n): AI draft — needs native-speaker review.
export const zh = {
common: {
cancel: "取消",
save: "保存",
close: "关闭",
delete: "删除",
rename: "重命名",
create: "创建",
apply: "应用",
choose: "选择…",
connect: "连接",
local: "本地",
},
app: {
notConnectedTitle: "未连接到应用",
notConnectedBody: "此页面运行在普通浏览器中。请启动桌面应用来使用。",
managerOpen: "连接管理器已打开。",
showConnections: "显示连接",
},
tabBar: {
connections: "连接",
manageConnections: "管理连接",
newTab: "新建标签页 ({mod}T)",
closeTab: "关闭标签页 ({mod}W)",
options: "选项",
},
options: {
title: "选项",
language: "语言",
system: "系统",
theme: { label: "主题", system: "系统", light: "浅色", dark: "深色" },
tone: { label: "色调", dark: "深色", darker: "更深" },
accent: { label: "强调色" },
accentNames: {
graphite: "石墨",
blue: "蓝色",
indigo: "靛蓝",
purple: "紫色",
rose: "玫红",
orange: "橙色",
green: "绿色",
teal: "青色",
},
fileIcons: { label: "文件图标", plain: "简约", multi: "彩色" },
interface: { label: "界面", compact: "紧凑", default: "默认", large: "大" },
speedUnit: { label: "速度单位" },
transfers: { label: "传输", single: "单个", multi: "多个" },
atOnce: "同时",
editor: { label: "编辑器", builtin: "内置", custom: "自定义" },
systemDefault: "系统默认",
resetDefault: "恢复为系统默认",
finishedTransfers: { label: "已完成传输", autoClear: "自动清除", keep: "保留" },
},
kinds: {
folder: "文件夹",
symlink: "符号链接",
file: "文件",
image: "图像",
audio: "音频",
video: "视频",
archive: "压缩包",
code: "代码",
document: "文档",
},
columns: {
name: "名称",
size: "大小",
mtime: "修改时间",
kind: "类型",
rights: "权限",
owner: "所有者",
group: "组",
},
panel: {
choosePanel: "选择此面板显示的内容",
copy: "复制",
copyToRight: "复制到右侧面板 (F5)",
copyToLeft: "复制到左侧面板 (F5)",
edit: "编辑",
editFile: "编辑文件 (F4)",
rename: "重命名 (F2)",
newFolder: "新建文件夹 (F7)",
newFile: "新建文件",
permissions: "权限",
delete: "删除",
deleteHint: "删除 (F8 / Del)",
parentDir: "上级目录 (退格)",
refresh: "刷新",
clearFilter: "清除筛选 (Esc)",
sshConfig: "~/.ssh/config",
connectingTo: "正在连接 {name}…",
emptyDir: "空目录",
noMatches: "无匹配项",
matches: { other: "{count} 个匹配 · Esc" },
},
connMgr: {
title: "连接",
importWinscp: "从 WinSCP 导入…",
importWinscpHint: "从 WinSCP.ini 导出文件导入会话",
newConnection: "连接",
newConnectionHint: "新建连接",
newStack: "分组",
newStackHint: "新建分组",
expand: "展开",
collapse: "折叠",
renameStack: "重命名分组",
deleteStack: "删除分组",
openStack: "在标签页中打开分组",
empty: "空",
connectionsHeader: "连接",
noneTitle: "尚无已保存的连接。",
noneBody: "创建一个,或导入你的 WinSCP 会话。",
name: "名称",
namePlaceholder: "我的服务器",
host: "主机",
port: "端口",
stack: "分组",
stackNone: "(无)",
username: "用户名",
remotePath: "远程路径(可选)",
remotePathPlaceholder: "主目录",
auth: "认证",
authPassword: "密码",
authKey: "私钥",
password: "密码",
privateKeyFile: "私钥文件",
passphrase: "密钥口令(可选)",
localDir: "本地目录(可选)",
localDirPlaceholder: "~/projects/my-server",
localDirHint: "当另一个面板处于本地且未浏览过时,将在此打开。",
more: "更多…",
less: "收起…",
selectPrompt: "选择一个连接或新建一个。",
newStackTitle: "新建分组",
newStackPrompt: "分组名称",
renameStackTitle: "重命名分组",
deleteStackTitle: "删除分组",
deleteStackBody: '删除分组 "{name}"?其中的连接将保留但不再分组。',
deleteConnectionTitle: "删除连接",
deleteConnectionBody: '删除 "{name}"?',
},
editor: {
noFileTitle: "没有要编辑的文件",
noFileBody: "此窗口在没有编辑器上下文的情况下打开。请从应用中打开文件。",
wrap: "换行",
wrapHint: "长行自动换行 — 隐藏行号 ({alt}Z)",
findHint: "查找 ({mod}F)",
findPlaceholder: "在文件中查找",
prevMatch: "上一个匹配 (⇧↵)",
nextMatch: "下一个匹配 (↵)",
closeHint: "关闭 (Esc)",
disconnected: "已从 {name} 断开 — 保存将失败,但你的文本会保留在此窗口中。",
cantOpen: "无法打开 {name}",
closeWindow: "关闭窗口",
shortcuts: "{mod}S 保存 · {mod}F 查找 · {mod}W 关闭",
lines: { other: "{count} 行" },
close: "关闭",
save: "保存",
saveChangesTitle: '保存对 "{name}" 的更改?',
saveChangesBody: "如果不保存,你的更改将丢失。",
dontSave: "不保存",
countLabel: "第 {current} / {total}",
noMatches: "无匹配项",
},
transfer: {
summary: "{active} 进行中 · {finished} 已完成",
clearFinished: "清除已完成",
cancel: "取消",
dismiss: "忽略",
showDetails: "显示传输详情",
copying: "正在复制 {name} · {percent}%{speed}",
queued: "{count} 个排队中",
failed: { other: "{count} 个传输失败" },
finished: "传输已完成",
ready: "就绪",
items: "{count} 项",
selected: "已选 {count} 项",
queuedState: "排队中",
scanning: "扫描中…",
failedState: "失败",
cancelled: "已取消",
completedFiles: { other: "{count} 个文件 · {size}" },
left: "剩余 {eta}",
},
dialogs: {
chmodTitle: "权限",
chmodItems: "{count} 项",
read: "读",
write: "写",
execute: "执行",
roleOwner: "所有者",
roleGroup: "组",
roleOthers: "其他",
octal: "八进制",
mergeFolder: "合并文件夹",
replace: "替换",
merge: "合并",
mergeAll: "全部合并",
replaceAll: "全部替换",
skip: "跳过",
skipAll: "全部跳过",
ofTotal: "第 {index} / {total}",
overwriteDirBody: "目标位置已存在同名文件夹。合并可能会替换其中的文件。",
overwriteFileBody: "目标位置已存在同名文件。",
source: "源",
destination: "目标",
newer: "较新",
},
toasts: {
movedToTrash: '已将 "{name}" 移到废纸篓',
editingExternal: "正在编辑 {name} — 保存后会自动上传",
nothingToCopy: "没有可复制的内容(符号链接将被跳过)",
nothingToCopyAllSkipped: "没有可复制的内容 — 所有冲突项都被跳过",
sameFolder: "两个面板显示的是同一个文件夹",
sameFile: '已跳过 "{name}" — 它是同一个文件',
typeMismatch: '已跳过 "{name}" — 目标处是不同类型的项目',
transferFailed: '传输 "{name}" 失败:{error}',
unknownError: "未知错误",
saved: "已保存 {name}",
disconnectedReason: "{title}:已断开({reason})",
imported: { other: "已导入 {count} 个连接" },
importedIntoStacks: { other: ",归入 {count} 个新分组" },
importedSkipped: "(跳过 {skipped} 个)",
leftoverTitle: "残留的已编辑文件",
leftoverMore: ",还有 {count} 个",
leftoverBody: {
other: "上一次会话遗留了 {count} 个临时编辑文件({names})。要清除吗?",
},
leftoverClear: "清除",
},
fileActions: {
deleteTitle: "删除",
deleteBody: "删除 {what}?文件夹将被递归删除。此操作无法撤销。",
deleteConfirm: "删除",
renameTitle: '重命名 "{name}"',
renameConfirm: "重命名",
newFolderTitle: "新建文件夹",
newFolderPlaceholder: "文件夹名称",
newFileTitle: "新建文件",
newFilePlaceholder: "文件名称",
createConfirm: "创建",
itemsCount: "{count} 项",
},
menu: {
about: "关于 {app}",
quit: "退出",
file: "文件",
newTab: "新建标签页",
closeTab: "关闭标签页",
newFolder: "新建文件夹",
newFile: "新建文件",
editFile: "编辑文件",
connect: "连接",
connectionManager: "连接管理器…",
edit: "编辑",
view: "查看",
refresh: "刷新",
window: "窗口",
},
ctx: {
size: "大小",
modified: "修改时间",
kind: "类型",
rights: "权限",
owner: "所有者",
group: "组",
revealFinder: "在访达中显示",
revealExplorer: "在资源管理器中显示",
revealFileManager: "在文件管理器中显示",
moveToTrash: "移到废纸篓",
moveToRecycleBin: "移到回收站",
newFolder: "新建文件夹",
newFile: "新建文件",
refresh: "刷新",
open: "打开",
openDefault: "用默认应用打开",
edit: "编辑",
copyToOther: "复制到另一个面板",
rename: "重命名",
permissions: "权限",
copyPath: "复制路径",
delete: "删除",
columns: "列",
trashTooltip: "从此处移入废纸篓的项目无法使用访达的放回原处功能",
},
errors: {
sshAgentUnavailable: "ssh-agent 不可用(未设置 SSH_AUTH_SOCK)",
identityFileNotFound: "找不到身份文件:{path}",
sessionNotConnected: '会话 "{id}" 未连接',
localCannotDisconnect: "无法断开本地会话",
notADirectory: '"{path}" 已存在且不是文件夹',
sameFileTransfer: '"{name}":源和目标是同一个文件',
sameFileNoName: "源和目标是同一个文件",
folderOntoItself: "无法将文件夹复制到其自身之上",
folderIntoItself: "无法将文件夹复制到其自身内部",
},
} as const;