首页
/ Git 源码本地化(l10n)全流程指南:从 po/XX.po 翻译到核心协作规范

Git 源码本地化(l10n)全流程指南:从 po/XX.po 翻译到核心协作规范

2026-09-04 23:52:56作者:江焘钦

本篇技术指南以 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.popo/de.popo/pt_PT.po 等,其命名即遵循上述 llll_CC 规范。

为已有语言贡献翻译

作为语言 XX 的贡献者,第一步是检查 po/TEAMS 文件,确认你的语言是否已存在专门(dedicated)的翻译仓库。如果存在,fork 该专门仓库并开始工作。po/TEAMS 按语言字段字母序维护各语言团队的仓库与负责人信息,例如德语团队维护在专门仓库中、简体中文(zh_CN)与繁体中文(zh_TW)各有独立的 leader 与成员列表。

另外需要留意:如果你使用的 Git 发行版(如 Ubuntu 等发行版自带版本)的翻译与 Git 官方版本差异很大,通常是因为这些发行版有自己的 l10n 工作流——错误的翻译应当通过发行版自己的工作流报告与修复,而不是提交到上游。

创建新的语言翻译

如果你是语言 XX 的首位贡献者:

  1. fork 本仓库;
  2. 准备并(或)更新翻译文件 po/XX.po
  3. 请 l10n 协调者(coordinator)从你的分支拉取(pull)。

如果同一语言有多位贡献者,请先在彼此间协调并推举一名团队负责人,使 l10n 协调者只需针对每个语言与一个人对接。

翻译流程的数据流

整个本地化协作的数据流如下图所示(原文档 po/README.md 中的示意图):

+-------------------+             +------------------+
| Git source code   | ----(2)---> | L10n coordinator |
| repository        | <---(5)---- | repository       |
+-------------------+             +------------------+
                  |                     |    ^
                 (1)                   (3)  (4)
                  V                     v    |
             +----------------------------------+
             |        Language Team XX          |
             +----------------------------------+

各步骤含义:

  1. 可翻译字符串在源码中被标记(见后文“标记可翻译字符串”一节);
  2. 语言团队可以随时开始翻译迭代,即使 l10n 窗口尚未开启:
    • 从源码 master 分支拉取(步骤 1);
    • 运行 make po-update PO_FILE=po/XX.po 更新消息文件;
    • 翻译 po/XX.po
  3. L10n 协调者从源码拉取,并宣布 l10n 窗口开启(步骤 2);
  4. 语言团队从 l10n 协调者仓库拉取,针对协调者的树开始新一轮翻译迭代(步骤 3):
    • 运行 git pull --rebase 从协调者处拉取;
    • 运行 make po-update PO_FILE=po/XX.po 更新消息文件;
    • 翻译 po/XX.po
    • git rebase -i 压缩琐碎的 l10n 提交;
  5. 语言团队向 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,如 deispt_BRzh_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

该命令会依次完成:

  1. 调用 make po/git.pot 生成新的完整模板;
  2. 调用 msgmerge --add-location --backup=off -U po/XX.po po/git.pot 更新你的 po/XX.po
  3. 其中 --add-location 选项会写入位置注释行(如 #: builtin/commit.c:123),帮助翻译工具快速定位翻译上下文。

这与 Makefilepo-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.pois.poit.poko.popl.popt_PT.po)被显式禁用 filter(-filter),因为它们仍保留位置注释,禁用可避免索引与工作树不一致;
  • ca.poid.pozh_CN.pozh_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

然后使用 gettexteval_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 中实现:gettextlngettext "$1"; echoeval_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-by trailer,与其他 Git 提交一致,可用以下命令自动添加:

    git commit -s
    
  • 创建提交前用 msgfmt 或以下命令检查语法:

    git-po-helper check-po <XX.po>
    
  • 压缩琐碎提交,保持历史清晰;

  • 不要编辑 po/ 目录之外的文件;

  • 其他子系统(git-guigitk 与 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

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
982
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384