Search K
翻译指南
SnapOtter 开箱即支持 21 种语言。i18n 系统采用轻量级自定义运行时,具备受 TypeScript 强制约束的语言完整性和动态代码拆分能力。
支持的语言
| Code | Language | Native Name | Direction |
|---|---|---|---|
en | English | English | LTR |
zh-CN | Chinese (Simplified) | 简体中文 | LTR |
zh-TW | Chinese (Traditional) | 繁體中文 | LTR |
ja | Japanese | 日本語 | LTR |
ko | Korean | 한국어 | LTR |
es | Spanish | Español | LTR |
fr | French | Français | LTR |
it | Italian | Italiano | LTR |
pt-BR | Portuguese (Brazil) | Português (Brasil) | LTR |
de | German | Deutsch | LTR |
nl | Dutch | Nederlands | LTR |
sv | Swedish | Svenska | LTR |
ru | Russian | Русский | LTR |
pl | Polish | Polski | LTR |
uk | Ukrainian | Українська | LTR |
ar | Arabic | العربية | RTL |
tr | Turkish | Türkçe | LTR |
hi | Hindi | हिन्दी | LTR |
vi | Vietnamese | Tiếng Việt | LTR |
id | Indonesian | Bahasa Indonesia | LTR |
th | Thai | ไทย | LTR |
语言检测的工作原理
SnapOtter 采用三层解析顺序:
- 用户偏好 - 存储在
localStorage("snapotter-locale")中,并在用户已登录时同步到用户设置 - 浏览器自动检测 - 遍历
navigator.languages数组,采用 BCP 47 前缀匹配 - 实例默认值 - 管理员的
DEFAULT_LOCALE环境变量(从GET /api/v1/config/locale获取) - 英语回退 - 始终可用
用户可以通过以下方式更改语言:
- 页脚地球图标选择器(桌面端,始终可见)
- 登录页语言选择器(登录前)
- 设置 > 通用部分(每位用户的偏好)
- 移动端侧边栏语言下拉菜单
- 设置 > 系统部分设置全实例范围的默认值(仅限管理员)
翻译的工作原理
所有 UI 字符串都位于 packages/shared/src/i18n/ 中。参考文件是 en.ts,它导出一个带类型的对象,包含应用使用的每个字符串(约 1500 个键)。其他语言是导出相同结构的独立文件(例如 de.ts、fr.ts)。
TranslationKeys 类型使用 DeepStringRecord 来接受任意字符串值,同时强制约束键结构。TypeScript 会在编译时捕获任何翻译文件中缺失的键。
运行时仅通过动态 import() 加载当前激活的语言,从而保持主包体积精简。
在组件中使用翻译
tsx
import { useTranslation } from "@/contexts/i18n-context";
import { format, plural } from "@/lib/format";
function MyComponent() {
const { t, locale, setLocale } = useTranslation();
return (
<div>
<h1>{t.common.settings}</h1>
<p>{format(t.settings.people.deleteConfirm, { username: "admin" })}</p>
<p>{plural(count, t.automate.fileCount, t.automate.fileCountPlural)}</p>
</div>
);
}贡献翻译
我们欢迎直接提交翻译 PR。你可以改进现有语言,也可以添加新语言。
若要在不提交代码的情况下报告误译,请提交一个 GitHub Issue,附上语言、错误的字符串以及建议的修正。
TIP
翻译 PR 无需事先批准。Fork 仓库、进行修改并提交 PR 即可。完整的 PR 流程和 CLA 要求参见 贡献指南。
如何创建或更新翻译
1. Fork 并克隆
bash
git clone https://github.com/<your-username>/snapotter.git
cd snapotter
pnpm install2. 复制参考文件(仅限新语言)
如果你是在改进现有翻译,请跳过此步骤。
bash
cp packages/shared/src/i18n/en.ts packages/shared/src/i18n/XX.ts3. 翻译字符串
打开新文件并翻译每个字符串值。保持对象结构和键完全不变。
ts
import type { TranslationKeys } from "./en.js";
export const xx: TranslationKeys = {
common: {
upload: "Your translation here",
// ... translate all entries
},
// ... translate all sections
} as const;规则:
- 不要翻译对象键,只翻译字符串值
- 将
as const保留在末尾 - 从
./en.js导入TranslationKeys并为你的导出添加类型 - 将
{variable}占位符原样保留 - 数组(
rotatingPhrases、progressMessages)必须有相同数量的条目 - 不要翻译:SnapOtter、JPEG、PNG、WebP、EXIF、API 及其他技术术语
4. 注册语言(仅限新语言)
将你的语言添加到 packages/shared/src/i18n/index.ts 中的 SUPPORTED_LOCALES:
ts
{ code: "xx", name: "Language Name", nativeName: "Native Name", dir: "ltr" },5. 验证
bash
pnpm typecheck # catches missing or mistyped keys
pnpm lint # formatting check
pnpm dev # manually verify strings appear correctly6. 提交
针对 main 提交一个 PR,标题类似 feat(i18n): add Swedish translation 或 fix(i18n): correct German typos。CLA 机器人会在你首次贡献时要求你签署。
添加新的翻译键
当添加需要新 UI 字符串的新功能时:
- 首先将新键添加到
en.ts(参考文件) - 运行
pnpm typecheck- 缺少新键的每个语言文件都会失败 - 将新键添加到所有语言文件(使用英语作为临时回退)
配置
通过环境变量设置实例默认语言:
yaml
DEFAULT_LOCALE: "de" # German as the default for all new users文件参考
| File | Purpose |
|---|---|
packages/shared/src/i18n/en.ts | 英语字符串(参考语言,约 1500 个键) |
packages/shared/src/i18n/index.ts | SUPPORTED_LOCALES、loadTranslations()、类型导出 |
packages/shared/src/i18n/<locale>.ts | 各语言翻译文件 |
apps/web/src/contexts/i18n-context.tsx | I18nProvider、useTranslation() 钩子 |
apps/web/src/lib/format.ts | format()、plural()、formatFileSize() 辅助函数 |
apps/api/src/routes/config.ts | GET /api/v1/config/locale 公共端点 |
翻译网站、文档和 API 参考
上述 21 种语言支持涵盖的是应用。公共网站(snapotter.com)、本文档站点以及 REST API 参考也都被翻译成全部 21 种语言,由一条独立的哈希门控流水线完成,它复用了来自 packages/shared/src/i18n 的相同工具名称和描述,因此术语在各处保持一致。
默认采用机器翻译
网站和文档中的每个非英语页面在首轮都是机器翻译的(由 Claude Code 会话完成,而非第三方服务),并带有一条小巧、可关闭的横幅进行说明,附有返回本页的链接。这是有意为之:它能快速而诚实地交付全部 21 种语言,然后邀请社区去精修最重要的页面。机器翻译能传达含义;人工审阅让文字读起来更自然。
流水线如何决定翻译内容
每个可翻译的英语源单元都会被哈希,哈希值与其翻译存放在一起。每次运行时,流水线会:
- 翻译任何尚无翻译的单元,
- 跳过任何存储哈希仍与英语源匹配的单元,
- 当机器单元的英语源发生变化时重新翻译它,
- 并在经过人工精修的单元其英语源发生变化时将其标记为
stale(需要审阅),而不是覆盖你的成果。
通过 PR 精修网页翻译
改进网站、文档或 API 参考翻译的方式,与改进应用语言相同:编辑生成的文件并提交 PR。
- 找到你所用语言的生成翻译:
- 网站 UI 字符串:
apps/landing/src/i18n/<locale>.json - 文档页面:
apps/docs/<locale>/**.md - API 参考:
apps/api/src/openapi.<locale>.yaml
- 网站 UI 字符串:
- 编辑文本。将代码、链接、
{placeholders}以及任何⸤I18N…⸥标记完全原样保留;流水线的验证器会拒绝丢弃或重新排序它们的翻译。 - 提交 PR。编辑一个单元会将其来源从
machine翻转为human,因此流水线在后续运行时绝不会覆盖它。如果之后英语源发生变化,你的单元会被标记为stale以待审阅,而不是被悄悄替换。
若要在不提交代码的情况下报告误译,请提交一个 GitHub Issue,附上页面 URL、语言、错误的文本以及你建议的修正。
TIP
维护者会运行翻译流水线;你无需 API 密钥即可贡献。只需编辑生成的文件并提交 PR。流水线的运行方式参见 scripts/i18n/README.md。
