本文目录导读:

开源项目的多语言适配(国际化,通常简称为i18n)是一项系统工程,它不仅仅是翻译文本,还涉及代码架构、文化习惯、日期格式等多个方面。
下面是一套针对开源项目的、从零开始的多语言适配最佳实践指南:
核心原则
- 分离:这是最核心的原则,永远不要把用户能看到的文本(UI字符串、错误提示、文档)硬编码在代码里。
- 选择成熟的标准:尽量使用社区广泛认可的库和格式,而不是自己造轮子。
- 从第一天开始做:即使项目初期只支持一种语言,也要按照国际化框架来组织代码,后期再“追加”国际化,技术债务会非常重。
第一步:选择技术栈与库
根据你的前端框架选择最主流的库:
- React:
react-intl(FormatJS) 或i18next(后者更通用,生态更丰富) - Vue:
vue-i18n(官方推荐,与Vue深度集成) - Angular:
@angular/localize(官方方案) 或ngx-translate - 后端/通用 JS:
i18next或globalize - Python (Flask/Django):
Flask-Babel或Django自带的makemessages/compilemessages - Go:
golang.org/x/text或go-i18n
强烈推荐 i18next:它是最成熟、支持最广泛的JS国际化库,有React、Vue、Angular甚至原生JS的绑定,处理复数、变量、上下文非常强大。
第二步:构建翻译文件
你需要一个统一的格式来存放翻译,最流行的是 JSON 和 YAML。
文件结构示例:
locales/ ├── en/ │ └── common.json # 通用术语 │ └── home.json # 首页页面 ├── zh-CN/ │ └── common.json │ └── home.json ├── ja/ │ └── common.json │ └── home.json └── index.js # (可选) 聚合导出所有语言
翻译文件内容 (JSON):
// locales/zh-CN/common.json
{
"app": {: "我的开源项目",
"description": "这是一个功能强大的工具。"
},
"nav": {
"home": "首页",
"about": "quot;,
"login": "登录"
},
"errors": {
"not_found": "页面未找到",
"server_error": "服务器错误,请稍后重试。"
}
}
关键设计原则:
- 扁平化 vs 嵌套:推荐扁平化(如
app.title),避免深层嵌套带来的查找和维护困难。 - 命名空间:按模块(页面、组件)拆分文件,避免单个文件过大。
- 使用占位符:不要拼接字符串,用变量。
- 好:
"greeting": "你好,{{name}}!" - 坏:直接写
"greeting": "你好," + name + "!"(翻译人员无法理解上下文)
- 好:
第三步:在代码中替换原始字符串
假设你使用的是 i18next + React:
初始化:
// i18n.js
import i18next from 'i18next';
import { initReactI18next } from 'react-i18next';
import en from './locales/en/common.json';
import zhCN from './locales/zh-CN/common.json';
i18next.use(initReactI18next).init({
resources: {
en: { translation: en },
'zh-CN': { translation: zhCN },
},
lng: 'zh-CN', // 默认语言
fallbackLng: 'en', // 如果找不到翻译,回退到英文
interpolation: {
escapeValue: false, // React已经做了安全处理
},
});
export default i18next;
在组件中使用:
import { useTranslation } from 'react-i18next';
function Greeting({ userName }) {
const { t } = useTranslation();
return (
<div>
{/* 直接使用翻译键 */}
<h1>{t('app.title')}</h1>
{/* 使用带变量的翻译 */}
<p>{t('greeting', { name: userName })}</p>
</div>
);
}
第四步:处理更复杂的情况
- 复数形式 (Pluralization):
- 翻译文件:
"itemCount": "你有 {{count}} 个项目"(英文),"itemCount_plural": "你有 {{count}} 个项目"(中文,通常不需要区分单复数) - i18next 会自动匹配。
- 翻译文件:
- 日期、数字、货币:
- 不要手动格式化,使用库的
formatDate或Intl.DateTimeFormat。 new Intl.DateTimeFormat('zh-CN').format(new Date())会自动输出“2024/1/15”。
- 不要手动格式化,使用库的
- 带 HTML 的翻译:
- 如果翻译里需要包含加粗、链接,使用
<Trans>组件或dangerouslySetInnerHTML(谨慎使用)。 - 更好的做法:翻译变量,代码里用组件包裹变量。
- 如果翻译里需要包含加粗、链接,使用
- 上下文 (Context):
- 同一个词在不同位置意思不同(如“打开”按钮 vs “打开”状态),使用上下文键:
"open"(按钮) 和"open_state"(状态)。
- 同一个词在不同位置意思不同(如“打开”按钮 vs “打开”状态),使用上下文键:
第五步:自动化与协作
这是开源项目最关键的加分项,不要手动做翻译!
- 自动化提取:使用工具从代码中自动提取所有
t('...')调用的key,生成一个基础的 JSON 文件。- 工具:
i18next-scanner,react-i18next-scanner,babel-plugin-react-intl
- 工具:
- 翻译管理平台 (TMS):
- 免费/轻量:使用 Git + 手动编辑 JSON,适用于小型、贡献者单一的项目。
- 专业:使用 Crowdin 或 Lokalise。
- 为什么需要它?
- 提供Web编辑器,非技术人员(翻译者)可以直接编辑。
- 自动同步Git仓库的翻译文件。
- 提供翻译记忆、机器翻译、审校流程。
- 极大地降低多语言维护成本。
- 流程:代码推送 -> CI触发提取 -> 上传到Crowdin -> 翻译者翻译 -> 下载完成后的翻译文件 -> 合并到代码库。
第六步:测试与质量控制
- 假语言测试:创建一个
qps-ploc语言包(把英文字母替换为带有重音符号的变体,并拉长字符串),这可以暴露出UI布局因文本长度变化而崩坏的问题。 - 截图测试:对每种语言渲染的页面截图进行回归测试。
- Lint:检查是否有遗漏的
t()调用(硬编码的字符串)。
开源项目多语言适配清单
| 阶段 | 行动项 | 工具/库 | 备注 |
|---|---|---|---|
| 架构 | 选择国际化库 | i18next, vue-i18n | 优先使用生态最广的 |
| 提取 | 从代码提取key | i18next-scanner | 自动化,避免遗漏 |
| 存储 | 标准化翻译文件 | JSON/YAML | 按命名空间拆分 |
| 翻译 | 建立翻译流程 | Crowdin, Lokalise | 降低翻译者门槛 |
| 本地化 | 处理日期/数字/复数 | Intl API, ICU MessageFormat | 统一格式化 |
| 测试 | 假语言/截图测试 | 自定义脚本/视觉回归工具 | 发现布局问题 |
| 贡献文档 | 写 CONTRIBUTING.md |
- | 新人如何贡献翻译 |
避坑指南(常见错误)
- ❌ 不要 在字符串里写 HTML 标签(如
<b>),除非你在<Trans>组件里。 - ❌ 不要 把英文句子拆成单词再拼接(“Hello” + “World”),这会破坏其他语言的语序。
- ❌ 不要 依赖谷歌翻译,至少需要母语者审校,因为机器翻译对特定领域术语(如“提交”、“索引”)不准确。
- ✅ 一定要 在翻译文件中保留英文作为
fallbackLng,这样哪怕某语言翻译缺失,用户也不会看到key。
遵循这个流程,你的开源项目就能高效、专业地支持多语言,吸引全球用户和贡献者。