开源项目国际化复盘:从单语言到i18n支持的工程改造与社区协作
一、国际化不是"把文本翻译了就行"
AgenFlow项目最初所有文档、代码注释、错误信息都是英文。当中国用户提出"能不能支持中文文档"时,第一反应是直接翻译README——但很快就意识到i18n远不止翻译:
- 日期格式(美国MM/DD/YYYY vs 中国YYYY-MM-DD)
- 时区处理(
time.Now()在不同时区返回不同值) - 代码中的硬编码英文文案(约200处)
- 文档的版本同步——英文文档更新了,中文还在旧版本
- CLI输出的对齐——中文是全角字符,英文的终端对齐在中文字符下会错位
二、代码i18n的工程实现
选择go-i18n:轻量、文件格式简单(TOML/YAML/JSON)、支持复数形式。
# locales/en-US.toml [greeting] one = "Hello" other = "Hello" [error.not_found] one = "{{.Resource}} not found" other = "{{.Resource}} not found"# locales/zh-CN.toml [greeting] one = "你好" other = "你好" [error.not_found] one = "未找到{{.Resource}}" other = "未找到{{.Resource}}"代码中消除硬编码:
import "github.com/nicksnyder/go-i18n/v2/i18n" type I18nBundle struct { bundle *i18n.Bundle } func (b *I18nBundle) T(lang string, messageID string, templateData map[string]interface{}) string { localizer := i18n.NewLocalizer(b.bundle, lang) msg, err := localizer.Localize(&i18n.LocalizeConfig{ MessageID: messageID, TemplateData: templateData, }) if err != nil { return messageID // fallback:返回messageID作为默认英文 } return msg } // 使用 func (s *Service) GetUser(ctx context.Context, id string) (*User, error) { user, err := s.repo.Find(ctx, id) if err != nil { lang := extractLang(ctx) msg := s.i18n.T(lang, "error.not_found", map[string]interface{}{ "Resource": "User", }) return nil, errors.New(msg) // "User not found" 或 "未找到User" } return user, nil }CLI输出的特殊处理:中文字符宽度是英文的2倍,在终端表格中需要手动计算对齐:
import "github.com/mattn/go-runewidth" func padRight(s string, width int) string { return s + strings.Repeat(" ", width-runewidth.StringWidth(s)) }三、文档i18n的社区协作
使用Crowdin做翻译管理平台(开源项目免费):
- 源语言文件(英文Markdown)自动推送到Crowdin
- 社区志愿者在Crowdin上翻译
- 翻译审核后自动PR回GitHub
- CI构建多语言文档站
# crowdin.yml files: - source: /docs/**/*.md translation: /i18n/%locale%/docs/**/%original_file_name% languages_mapping: locale: zh-CN: zh ja: ja贡献者激励:在CONTRIBUTORS.md中单独列出"翻译贡献者",每月在社区公告中致谢。翻译贡献也计入"贡献者阶梯"(提升为Reviewer的考虑因素之一)。
四、i18n的持续维护成本
| 维护项 | 频率 | 时间 |
|---|---|---|
| 新增文案的翻译 | 每次Release | 约30条 |
| 翻译质量Review | 每月 | 1小时 |
| Crowdin同步 | 自动 | 0 |
| 文档翻译同步 | 每次文档更新 | 2小时 |
关键挑战:英文文档更新后,中文翻译可能滞后。解决——Crowdin自动检测源文件变更,标记"需要更新的翻译"。在文档站顶部显示"此页面翻译更新时间:YYYY-MM-DD"。
五、总结
开源项目i18n的核心经验:
- go-i18n处理代码文案,Crowdin管理翻译协作——两者分离
- 错误信息用messageID代替硬编码文案——messageID也是英文fallback
- CLI输出注意中文字符宽度——go-runewidth解决对齐问题
- 文档翻译通过Crowdin + 社区志愿者完成——降低维护者翻译负担
- 翻译贡献者也需要激励和认可——"翻译贡献者"列表
当前支持3种语言(英文、简体中文、日文)。日文是社区贡献者自发完成的(1位日本开发者翻译了全部文档),成为项目在日语社区增长的关键推力。
最大的教训:i18n不是"一次性翻译"而是"持续维护"。每次Release新增的文案需要翻译,每次文档更新需要同步翻译。如果没有自动化工具(Crowdin)和社区志愿者,i18n的维护成本会很快超过维护者的承受能力。i18n的核心不是翻译能力,是翻译流程的自动化。