Git 源码本地化(l10n)全流程指南:从 po/XX.po 翻译到核心协作规范
本篇技术指南以 Git 源码树中 po/README.md 为核心,系统讲解 Git 核心本地化(l10n)的完整工作流:语言代码规范、PO/POT 文件的动态生成与更新、fuzzy 翻译处理、git 过滤属性(clean filter)提交规范,以及 C/Shell/Perl 三种语言中标记可翻译字符串的接口。读完本文,你可以独立完成一份 po/XX.po 翻译文件的初始化、更新、校验与提交准备,并理解上游如何从源码中提取、合并与编译本地化消息。
核心概念:语言代码与语言团队
po/ 目录保存 Git 核心的翻译文件。本指南中用 XX 作为语言代码占位符(如 po/XX.po 指代某个具体语言的翻译文件)。需要注意,语言代码并不总是两个字母,而是有两种形式:
ll:ISO 639 双字母语言代码,如de(德语);ll_CC:ISO 639 语言代码 + ISO 3166 国家/地区代码,如zh_CN(简体中文)。
当前仓库中实际收录的翻译文件可在 po/ 目录下看到,例如 po/zh_CN.po、po/de.po、po/pt_PT.po 等,其命名即遵循上述 ll 或 ll_CC 规范。
为已有语言贡献翻译
作为语言 XX 的贡献者,第一步是检查 po/TEAMS 文件,确认你的语言是否已存在专门(dedicated)的翻译仓库。如果存在,fork 该专门仓库并开始工作。po/TEAMS 按语言字段字母序维护各语言团队的仓库与负责人信息,例如德语团队维护在专门仓库中、简体中文(zh_CN)与繁体中文(zh_TW)各有独立的 leader 与成员列表。
另外需要留意:如果你使用的 Git 发行版(如 Ubuntu 等发行版自带版本)的翻译与 Git 官方版本差异很大,通常是因为这些发行版有自己的 l10n 工作流——错误的翻译应当通过发行版自己的工作流报告与修复,而不是提交到上游。
创建新的语言翻译
如果你是语言 XX 的首位贡献者:
- fork 本仓库;
- 准备并(或)更新翻译文件
po/XX.po; - 请 l10n 协调者(coordinator)从你的分支拉取(pull)。
如果同一语言有多位贡献者,请先在彼此间协调并推举一名团队负责人,使 l10n 协调者只需针对每个语言与一个人对接。
翻译流程的数据流
整个本地化协作的数据流如下图所示(原文档 po/README.md 中的示意图):
+-------------------+ +------------------+
| Git source code | ----(2)---> | L10n coordinator |
| repository | <---(5)---- | repository |
+-------------------+ +------------------+
| | ^
(1) (3) (4)
V v |
+----------------------------------+
| Language Team XX |
+----------------------------------+
各步骤含义:
- 可翻译字符串在源码中被标记(见后文“标记可翻译字符串”一节);
- 语言团队可以随时开始翻译迭代,即使 l10n 窗口尚未开启:
- 从源码 master 分支拉取(步骤 1);
- 运行
make po-update PO_FILE=po/XX.po更新消息文件; - 翻译
po/XX.po;
- L10n 协调者从源码拉取,并宣布 l10n 窗口开启(步骤 2);
- 语言团队从 l10n 协调者仓库拉取,针对协调者的树开始新一轮翻译迭代(步骤 3):
- 运行
git pull --rebase从协调者处拉取; - 运行
make po-update PO_FILE=po/XX.po更新消息文件; - 翻译
po/XX.po; - 用
git rebase -i压缩琐碎的 l10n 提交;
- 运行
- 语言团队向 l10n 协调者发送 pull request(步骤 4);协调者检查并合并;随后协调者请求上游源码仓库拉取结果(步骤 5)。
动态生成的 POT 模板文件
POT 文件是 l10n 贡献者创建或更新翻译文件的模板。历史上曾存在由 l10n 协调者生成并纳入版本控制的 po/git.pot 文件,但该文件已从代码树中移除,如今两个 POT 文件都按需动态生成:
po/git.pot:完整消息模板,包含 5000 多条消息,供 l10n 贡献者为各语言准备翻译。贡献者使用它但不应修改它。手动生成命令:
make po/git.pot
po/git-core.pot:核心翻译(core translation)模板。核心翻译是为一个新语言完成翻译所需的“最小工作量集合”——完整模板有 5000 多条消息,对新语言贡献者而言是全量翻译并不轻松,因此上游提供这一最小集合作为起点。手动生成命令:
make po/git-core.pot
从构建系统看,这两个目标的实现位于顶层 Makefile:
po/git.pot目标(Makefile)以.build/pot/git.header与所有已本地化文件的中间 PO($(LOCALIZED_ALL_GEN_PO))为依赖,通过$(MSGCAT)合并生成;po-update目标(Makefile)依赖po/git.pot,先通过check_po_file_envvar宏校验PO_FILE必须匹配po/%.po模式,若文件不存在则提示改用make po-init,随后执行$(MSGMERGE) $(MSGMERGE_FLAGS) $(PO_FILE) po/git.pot;po/git-core.pot目标(Makefile)仅依赖“核心”消息集合(LOCALIZED_C_CORE_GEN_PO),即注释中所说“用翻译全部文件来界定 core 只是粗糙的启发式”的产物;- 此外还有一个
check-pot目标(Makefile)用于强制重建全部中间 POT 产物,常用来发现遗漏的新字符串。
初始化一个新的 "XX.po" 文件
(由语言团队执行。)如果你的语言尚没有 po/XX.po,第一次添加翻译的命令是:
make po-init PO_FILE=po/XX.po
其中 XX 为 locale,如 de、is、pt_BR、zh_CN 等。
从 Makefile 可以看到 po-init 目标的实际行为:它依赖 po/git-core.pot,若目标文件已存在则报错退出(error: $(PO_FILE) exists already),否则调用 msginit --input=po/git-core.pot --output=$(PO_FILE) 生成新文件。因此新生成的 po/XX.po 基于核心模板,只包含最小消息集合,是新手语言贡献的良好起点。完成测试(见后文)后,提交结果并请 l10n 协调者拉取。
更新一个 "XX.po" 文件
(由语言团队执行。)分两种情况:
- 如果只是修改已有
XX.po中的翻译字符串以改进译文,直接编辑文件即可; - 如果要把上游源码中新增的可翻译字符串传播到
po/XX.po,运行:
make po-update PO_FILE=po/XX.po
该命令会依次完成:
- 调用
make po/git.pot生成新的完整模板; - 调用
msgmerge --add-location --backup=off -U po/XX.po po/git.pot更新你的po/XX.po; - 其中
--add-location选项会写入位置注释行(如#: builtin/commit.c:123),帮助翻译工具快速定位翻译上下文。
这与 Makefile 中 po-update 目标的实现一一对应:$(MSGMERGE) $(MSGMERGE_FLAGS) $(PO_FILE) po/git.pot,其中 MSGMERGE_FLAGS 即 --add-location --backup=off -U 这类参数。
Fuzzy(模糊)翻译
Fuzzy 翻译是指被注释 #, fuzzy 标记的译文:它提示你因为 msgid 已改变,该翻译已过期。使用 msgfmt 编译时,fuzzy 翻译会被忽略。fuzzy 标记可以手工打上,但大多数情况下是在运行 msgmerge 更新 XX.po 时自动标记的。修正相应翻译后,必须去掉注释中的 fuzzy 标记,否则该条目在编译时仍会被跳过。
测试你的修改
(语言团队在创建或更新 XX.po 之后执行。)提交前回到顶层目录执行:
make
在具备 GNU gettext 的系统上(即 Solaris 之外),这会用 msgfmt --check 编译你修改过的 PO 文件。--check 选项会标出许多常见错误,例如:缺失的 printf 格式串、翻译消息与原文在首尾换行符上是否一致出现偏差等。
L10n 协调者还会用辅助程序 git-po-helper 检查你的贡献:
git-po-helper check-po po/XX.po
git-po-helper check-commits <rev-list-opts>
提交前的 PO 文件整理:Git clean filter
翻译测试完成后,建议提交一个不带位置信息(location-less)的 po/XX.po 文件,以节省仓库空间并让审阅补丁更友好。
第一步,检查你的 po/XX.po 文件配置了哪个 filter:
git check-attr filter po/XX.po
filter 配置定义在 po/.gitattributes 中。该文件(po/.gitattributes)的实际规则是:
- 默认对所有
*.po应用gettext-no-location:提交时同时剥离文件名与行号(#: main.c:123整行消失); - 少数维护较少的历史文件(
el.po、is.po、it.po、ko.po、pl.po、pt_PT.po)被显式禁用 filter(-filter),因为它们仍保留位置注释,禁用可避免索引与工作树不一致; ca.po、id.po、zh_CN.po、zh_TW.po使用gettext-no-line-number:保留文件名、仅去掉行号(#: main.c:123变为#: main.c),需要 gettext 0.20 及以上。
第二步,为你的 filter 配置驱动(driver)。多数语言使用 gettext-no-location(剥离文件名与行号):
git config --global filter.gettext-no-location.clean \
"msgcat --no-location -"
部分 PO 文件使用 gettext-no-line-number(保留文件名、剥离行号)。它要求 gettext 0.20 或更高版本,唯一收益是:当 .po 文件未通过 make po-update 从 POT 更新时,仍可从位置注释定位源文件:
git config --global filter.gettext-no-line-number.clean \
"msgcat --add-location=file -"
设置完成即可以请求 l10n 协调者从你的分支拉取。
标记可翻译字符串(核心开发者)
(由核心开发者执行。)字符串在被翻译之前必须先被标记为可翻译。Git 使用一套封装系统 gettext 库的国际化接口,因此 GNU gettext 文档中的大部分建议都适用(GNU 系统上终端运行 info gettext)。通用建议:
- 不要标记所有字符串:只有会被人直接阅读的字符串(porcelain 接口)才应翻译。Git 的 plumbing 工具输出主要供程序消费,一旦在非 C locale 下翻译会破坏脚本。plumbing 字符串不应翻译,因为它们是 Git API 的一部分;
- 调整字符串使其易于翻译,
info '(gettext)Preparing Strings'中的大部分建议在此适用; - 引用数量(number of items)的字符串可能需要拆分为单数/复数形式,见下文 C 部分
Q_()的示例; - 对不清晰或有歧义的内容,可用
TRANSLATORS注释告知译者如何处理。这些注释会被xgettext(1)提取并写入po/*.po文件。例如 git-am.sh 中的 shell 示例:
# TRANSLATORS: Make sure to include [y], [n], [e], [v] and [a]
# in your translation. The program will only accept English
# input at this point.
gettext "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
或 builtin/revert.c 中的 C 示例:
/* TRANSLATORS: %s will be "revert" or "cherry-pick" */
die(_("%s: Unable to write new index file"), action_name(opts));
Git 为 C、Shell 与 Perl 三类程序提供封装接口。
C 语言接口
在文件顶部包含 builtin.h,它会引入 gettext.h(该头文件定义 gettext 接口;若需直接使用 gettext.h 请先与邮件列表确认)。C 接口是标准 GNU gettext 接口的一个子集,当前导出:
_():标记并翻译字符串,例如:
printf(_("HEAD is now at %s"), hex);
Q_():标记并翻译复数字符串,例如:
printf(Q_("%d commit", "%d commits", number_of_commits));
它只是 ngettext() 的封装。
N_():作用于静态初始化中的“空操作”直通宏,仅做标记。例如 builtin/reset.c 一类用法:
static const char *reset_type_names[] = {
N_("mixed"), N_("soft"), N_("hard"), N_("merge"), N_("keep"), NULL
};
然后稍后:
die(_("%s reset is not allowed in a bare repository"),
_(reset_type_names[reset_type]));
此时 _() 无法在静态期确定翻译串是什么,但由于该字符串已用 N_() 标记,消息目录中的查找仍能成功。从源码看,N_ 的定义正是直通宏——gettext.h 中 #define N_(msgid) msgid,即“为翻译标记 msgid 但不翻译它”。
Shell 接口
Git 的 gettext shell 接口本质是 gettext.sh 的封装。在 git-sh-setup 之后立即导入:
. git-sh-setup
. git-sh-i18n
然后使用 gettext 或 eval_gettext 函数:
# 常量界面消息:
gettext "A message for the user"; echo
# 需要插值变量:
details="oh noes"
eval_gettext "An error occurred: $details"; echo
此外还有针对以换行结尾消息的封装,即上面代码可写成:
# 常量界面消息:
gettextln "A message for the user"
# 需要插值变量:
details="oh noes"
eval_gettextln "An error occurred: $details"
这些封装在 git-sh-i18n.sh 中实现:gettextln 即 gettext "$1"; echo,eval_gettextln 同理(见 git-sh-i18n.sh)。更多接口文档见 GNU info 页 info '(gettext)sh';阅读 git-am.sh(第一个被翻译的 shell 命令)的实例也很有帮助:
git log --reverse -p --grep=i18n git-am.sh
Perl 接口
Git::I18N 模块提供 Locale::Messages 功能的一个有限子集:
use Git::I18N;
print __("Welcome to Git!\n");
printf __("The following error occurred: %s\n"), $error;
模块位于 perl/Git/I18N.pm,运行 perldoc perl/Git/I18N.pm 可查看更多文档。
标记字符串的测试策略
Git 的测试都在 LANG=C LC_ALL=C 下运行,因此随着翻译的增加,测试本身无需为本地化做任何调整——这也保证了测试基线不受 locale 影响。
AI 辅助翻译与 git-po-helper
po/AGENTS.md 描述了 AI 编码助手辅助 Git 本地化的可选工作流:更新模板与 PO 文件、翻译 po/XX.po、评审翻译质量。这些工作流通常组合使用 git-po-helper 与 gettext 工具。官方立场是:AI 助手是可选的,其输出应视为草稿,必须由熟悉 Git 与目标语言的贡献者评审。
在向编码助手提示时,请显式提及该文件,例如:“Translate po/XX.po with reference to po/AGENTS.md”(把 XX 替换为你的语言代码)。
git-po-helper 本身是面向 l10n 协调者与贡献者的辅助程序,它自动化检查贡献是否符合项目约定(PO 语法、提交信息、允许修改的路径等),并可配合 AI 编码代理完成新条目翻译与翻译评审等任务。从 po/AGENTS.md 可看到它提供的具体能力:
git-po-helper msg-select:按条目索引切分大 PO 文件(--range "1-50"、--head N、--tail N、--since N),支持按状态过滤(--translated、--untranslated、--fuzzy、--no-obsolete),并可输出其内部定义的 GETTEXT JSON 格式(--json),便于 AI 批处理翻译;git-po-helper compare:以完整条目上下文展示 PO 变更(不同于git diff),支持--commit、--since、-r x..y等范围参数,--msgid模式用于检测 msgid 是否被篡改(无输出即一致);git-po-helper msg-cat:合并 PO/POT/GETTEXT JSON 输入,重复msgid保留文件中首次出现者,--unset-fuzzy可清除 fuzzy 标记,常用于把翻译后的 JSON 转回 PO。
翻译工作流(Task 3)的核心循环是:提取待翻译条目(po/l10n-pending.po)→ 分批(默认每批约 100 条)→ 翻译 → 校验(git-po-helper compare -q --msgid --assert-no-changes 确认 msgid 未被改动、msgfmt --check 校验格式)→ 用 msgcat --use-first 合并回 po/XX.po → 循环直到无待翻译条目。评审工作流(Task 4)则要求只使用 git-po-helper compare 提取变更(禁用 git diff/git show,因其会破坏 PO 上下文),按批产出评分(0–3)与 suggest_msgstr 建议,最终由 git-po-helper agent-run review --report po 汇总。其中值得译者借鉴的细节规则包括:
- PO 首条头部条目(
msgid "")的msgstr存放项目、语言、复数规则等元数据,不得在翻译时修改; - 文件头部的词表(glossary)注释块必须阅读并遵循,且不得改动;
- 占位符必须原样保留;仅在需要重排顺序时使用位置参数语法(如
%1$s、%3$.*2$s),未重排时不得引入%n$; - 语言特有的弯引号(„、”、«»等)绝不可转换为 ASCII 直引号,否则 PO 解析器会把 U+0022 当作字符串分隔符,造成截断与
msgfmt --check语法错误; - 未翻译条目不能用
grep '^msgstr ""'查找(多行条目会误报),应使用msgattrib --untranslated --no-obsolete po/XX.po。
提交约定(Conventions)
l10n 贡献者必须遵守以下约定:
-
每个 l10n 提交的标题(subject)必须以
l10n:前缀开头; -
提交标题中不要使用非 ASCII 字符;
-
提交标题(commit log 首行)长度不超过 50 个字符,其余行不超过 72 个字符;
-
为提交添加
Signed-off-bytrailer,与其他 Git 提交一致,可用以下命令自动添加:git commit -s -
创建提交前用
msgfmt或以下命令检查语法:git-po-helper check-po <XX.po> -
压缩琐碎提交,保持历史清晰;
-
不要编辑
po/目录之外的文件; -
其他子系统(
git-gui、gitk与 Git 本身)各有自己的工作流,参见 Documentation/SubmittingPatches 了解如何向这些子系统提交补丁。
为新语言贡献还须遵循额外约定:
-
按 ISO 639 / ISO 3166 规范初始化正确的
XX.po文件名; -
必须基于“核心翻译”(Core translation)完成最小翻译(见前文“动态生成的 POT 文件”与“初始化”两节);
-
在 po/TEAMS 文件中按正确格式添加新条目,并运行以下命令校验
po/TEAMS语法:git-po-helper team --check
小结:一条可复制的上手路径
综合本文,一个新语言贡献者可执行的最短路径为:在 po/TEAMS 中确认无现成团队 → make po-init PO_FILE=po/XX.po 基于核心模板初始化 → 逐批翻译并清理 #, fuzzy 标记 → make(触发 msgfmt --check)与 git-po-helper check-po po/XX.po 双重校验 → 配置 filter.gettext-no-location.clean 得到无位置信息的提交 → 以 l10n: 前缀、git commit -s 签名提交 → 向 l10n 协调者发起拉取请求。源码侧的字符串提取、C/Shell/Perl 标记接口与 make po-update 的 msgmerge 更新机制则保证了模板与翻译之间的持续同步。更多本地化背景(如编译期消息目录的安装与运行时 locale 选择)可进一步参阅 Documentation/i18n.adoc。
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