lazygit 国际化工作流:翻译文件的生成、Crowdin 同步与运行时加载机制详解
本文围绕 pkg/i18n/translations/README.md 展开,讲解 lazygit 多语言翻译体系的完整闭环:英文源文本如何从 Go 代码导出为 JSON 并上传到 Crowdin 翻译平台、社区翻译成果如何通过脚本回灌到仓库、以及翻译文件如何被编译进二进制并在运行时按需加载。读完本文,你将掌握维护 lazygit 翻译文件的规范操作、理解 en.json 与 pkg/i18n/english.go 的主从关系,并能从源码层面解释语言回退(fallback)与自动检测语言的实现原理。
核心原则:翻译 JSON 是机器生成的,禁止手工编辑
阅读 pkg/i18n/translations/README.md 的第一句话就能抓住整个目录的纪律:
The JSON files in this directory are machine-generated; please do not edit.
也就是说,pkg/i18n/translations/ 目录下的 ja.json、ko.json、zh-CN.json、zh-TW.json 等文件全部由工具链生成,任何人(包括贡献者)都不应直接修改它们。这条原则背后有两个事实源:
- 英文源文本:所有界面文本的英文版本维护在 pkg/i18n/english.go 中。该文件定义了一个巨大的
TranslationSet结构体(约 2300 行),每个字段对应一条界面文案,如FilesTitle、CommitTooltip、CredentialsPassword等,并提供了EnglishTranslationSet()构造函数。 - 其他语言的译文:存放在 Crowdin 翻译平台(lazygit 项目)上,由社区译者在线完成,最终通过脚本导出回本仓库的
translations/目录。
这种"代码即源文本 + 平台即译文"的设计,保证了英文文案随功能开发自然演进,而翻译进度与代码仓库解耦。
运行时如何加载翻译:embed 编译 + 英文兜底合并
README 只讲了文件从哪来,而 pkg/i18n/i18n.go 则回答了文件如何被使用。
翻译文件编译进二进制
关键在第 69~70 行:
//go:embed translations/*.json
var embedFS embed.FS
通过 Go 的 embed 包,所有翻译 JSON 在编译期直接嵌入二进制,运行时不需要外部文件。getSupportedLanguageCodes() 从内嵌文件系统中列出 translations/ 目录下的 JSON 文件名(去掉 .json 后缀)得到受支持的语言码列表;readLanguageFile() 则按 translations/%s.json 读取并反序列化为 TranslationSet。
语言选择与回退逻辑
NewTranslationSetFromConfig() 实现了完整的语言决策链(见 i18n.go 第 18~48 行):
language: auto:调用detectLanguage(),基于jibber_jabber库检测 IETF 语言标签(读取LANG/LC_ALL等环境变量)。若检测结果与任一已支持语言码前缀匹配则使用该语言;检测出未翻译语言不视为错误,直接回退英文。language: en:直接返回英文翻译集。- 显式指定某语言码:若该码在支持列表内则加载对应文件;指定了未翻译的语言则返回错误(
Language not found: <code>),提示用户配置有误。
newTranslationSet() 中有一个关键细节(第 50~67 行):非英文翻译集加载后,会用 mergo.Merge(baseSet, *translationSet, mergo.WithOverride) 与英文基础集合并。这意味着翻译文件中缺失的键会自动用英文值填充——这正是后文"必须删除空字符串"操作在运行时层面的意义:JSON 中只保留已翻译的条目,未翻译条目靠英文兜底。
测试验证的语言场景
i18n_test.go 用表驱动测试完整覆盖了上述分支:
| 场景 | 配置 language |
环境变量 LANG |
预期结果 |
|---|---|---|---|
| 显式指定支持语言 | nl |
en_US |
加载荷兰语 |
| 显式指定不支持语言 | xy |
en_US |
报错 |
| 自动检测且无 LANG | auto |
空 | 回退英文 |
| 自动检测荷兰语 | auto |
nl_NL |
加载 nl |
| 自动检测简体中文 | auto |
zh-CN |
加载 zh-CN |
| 自动检测未翻译语言 | auto |
xy_XY |
回退英文 |
这些用例同时印证了"auto 下检测失败是静默回退、显式配置错误才会报错"的双轨语义(该测试在 Windows 上会跳过,因为 Windows 不遵循 LANG 环境变量约定)。
上传英文源文件到 Crowdin
README 的"Uploading the English file to Crowdin"一节给出了明确流程:当 pkg/i18n/english.go 中的文本发生变化(新增功能、修改文案)后,需要把最新英文版同步到 Crowdin,供译者参照翻译。
操作步骤:
go run cmd/i18n/main.go
这一命令的行为定义在 cmd/i18n/main.go 中:调用 i18n.EnglishTranslationSet() 取得英文翻译集,经 json.MarshalIndent 格式化为两空格缩进的 JSON,追加一个尾部换行符,写入仓库根目录的 en.json。
需要注意 README 中强调的两个后续动作:
- 将生成的
en.json上传到 Crowdin 的 lazygit 项目源文件区; - 上传完成后必须把
en.json从工作副本中删除——它是一个非版本化(unversioned)的临时产物,不应出现在提交中。
这种"临时导出文件 + 人工上传"的方式,是因为 Crowdin 的 CLI 工具在该项目上尚未打通(scripts/update_language_files.sh 头部的注释也提到 crowdin-cli 一直没能跑通,因此流程偏手工)。
从 Crowdin 拉取翻译:update_language_files.sh 全流程
这是 README 的核心章节,对应实现为 scripts/update_language_files.sh。标准流程三步:
- 从 Crowdin 下载全部翻译的 zip 包;
- 解压到一个临时目录;
- 将解压目录作为唯一参数运行脚本:
scripts/update_language_files.sh <download_dir>
脚本内部做了四件关键事情(见脚本第 21~38 行):
第一步:语言码归一化。 Crowdin 导出葡萄牙语目录名是 pt-PT,但 lazygit 统一使用 pt(同时覆盖巴西葡语与欧洲葡语)。脚本中无法在 Crowdin 端修改该命名,于是在本地重命名:
[ -d "$download_dir/pt-PT" ] && mv "$download_dir/pt-PT" "$download_dir/pt"
第二步:剔除未翻译的空字符串。 对每个语言目录执行:
jq 'del(..|select(. == ""))' < "$d/en.json" > pkg/i18n/translations/$(basename "$d").json
这条 jq 表达式递归删除 JSON 中所有值为空字符串的字段。注释解释了原因:未翻译的条目在导出时就是空串,Crowdin 虽有"导出时跳过空条目"的选项,但对 JSON 格式不生效,只能本地清洗。这一步与运行时 mergo 合并逻辑严丝合缝——留下的都是有效译文,缺省键由英文兜底。(脚本要求 jq 1.7 或更高版本。)
第三步:重新生成下游自动文件。 翻译变化会波及按键绑定速查表,因此脚本最后执行:
go generate ./...
第四步:容错设置。 脚本以 set -e 开头,任何一步失败都会立即中止;参数数量不为 1 时打印用法并退出码为 2。
下游产物:按键速查表的多语言生成
go generate ./... 触发的核心逻辑在 pkg/cheatsheet/generate.go。它调用 i18n.GetTranslationSets()(该函数除英文外逐一读取每个语言 JSON,返回以语言码为键的完整映射),然后对每种语言分别构造一个应用实例,把 gui.language 配置设为该语言,取回本地化后的标题与按键描述,渲染出 Keybindings_<lang>.md 写入 docs-master/keybindings/ 目录——这正是仓库中能看到 Keybindings_ja.md、Keybindings_zh-CN.md 等多份速查表的来源。
这也解释了为什么 README 要求翻译更新后必须跑 go generate:翻译 JSON 与按键文档是"一源多产物"的关系,只更新 JSON 不重新生成会导致速查表滞后。
用户侧配置:language 选项
对普通用户而言,上述流程的终点体现在配置项上。pkg/config/user_config.go 定义了该字段:
Language string `yaml:"language" jsonschema:"enum=auto,enum=en,enum=zh-TW,enum=zh-CN,enum=pl,enum=nl,enum=ja,enum=ko,enum=ru"`
docs/Config.md 中给出的默认值为 language: auto。结合源码可确认其取值语义:
auto:按LANG/LC_ALL等环境变量自动检测,检测不到或未收录时静默使用英文;en或其他已支持语言码(zh-CN、zh-TW、ja、ko、nl、pl、ru、pt等,以translations/目录实际包含的 JSON 为准):强制使用对应语言,配置了不支持的语言码会在启动时报错。
维护者操作速查
| 场景 | 操作 | 依据 |
|---|---|---|
| 修改/新增英文界面文本 | 直接编辑 pkg/i18n/english.go,再执行 go run cmd/i18n/main.go 生成 en.json,上传 Crowdin 后删除该临时文件 |
README"Uploading"节、cmd/i18n/main.go |
| 同步社区最新翻译 | 从 Crowdin 下载 zip → 解压 → scripts/update_language_files.sh <解压目录>(脚本内部自动完成 pt-PT→pt 重命名、jq 清洗空串、go generate) |
README"Updating"节、scripts/update_language_files.sh |
| 验证语言加载行为 | 运行 go test ./pkg/i18n/,重点看 TestNewTranslationSetFromConfig 的六种场景 |
pkg/i18n/i18n_test.go |
| 确认某语言是否受支持 | 查看 pkg/i18n/translations/ 目录下的 JSON 文件名,或 config.schema 中 language 的枚举 |
pkg/i18n/i18n.go 的 getSupportedLanguageCodes() |
小结
lazygit 的国际化体系是一条清晰的双向流水线:英文文本以 Go 结构体为唯一源头,经 cmd/i18n/main.go 导出为 en.json 送往 Crowdin;社区译文再以 zip 形式回流,经 update_language_files.sh 清洗空串、归一语言码后落盘为机器生成的 JSON,最后 go generate 刷新多语言按键速查表。运行时则通过 go:embed 将全部译文编译进二进制,以英文集为兜底底版做 mergo 合并,并配合 auto 自动检测与显式配置的差异化错误处理。对贡献者最重要的纪律只有一条:translations/ 目录下的 JSON 一律不许手改——一切修改都应走"改 english.go 或同步 Crowdin"两条正道。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00