在构建多语言网站或应用时,使用 gettext 工具链处理 .po 文件和 .pot 模板是标准流程。但随着项目规模扩大,你可能会发现 msgmerge 处理大文件非常慢,且翻译条目行为有些“诡异”:明明写了翻译,实际却显示原文。本文从开发者角度深入拆解这些坑,并提供一整套高效、可靠的处理方案。
gettext 工作流简述
典型工作流:
- 从源码生成模板文件
.pot(通过xgettext) - 将
.pot与已有.po合并(通过msgmerge) - 使用翻译工具修改
.po - 编译为
.mo文件供程序使用(msgfmt)
本文重点关注第 2 步:msgmerge 合并行为及其性能与正确性问题。

问题一:msgmerge 合并太慢,怎么办?
当 .po 文件有成千上万个条目时,默认合并速度可能非常慢。原因在于:
- 默认启用 fuzzy 匹配,gettext 会对每个
msgid做模糊比对; - fuzzy 比对代价高(字符串距离、近似匹配、语义相似度);
- 模糊结果还会被标记为
#, fuzzy,需要人工审核。
解决方案:关闭 fuzzy 匹配
使用 --no-fuzzy-matching 可大幅提升速度,跳过所有模糊匹配逻辑,只保留精确匹配项:
msgmerge --no-fuzzy-matching old.po template.pot -o merged.po
那 fuzzy 匹配有什么用?为什么默认启用?
fuzzy 是 gettext 系统的“翻译保守机制”:
- 当模板中的句子发生轻微改动(如
"Save"改为"Save file"),会尝试找到旧翻译; - 系统自动标记为
#, fuzzy,表示 “可能对,但需要人类确认”。
#, fuzzy
msgid "Save file"
msgstr "保存"
重点:fuzzy 条目不会在实际运行中生效,gettext 会忽略其 msgstr!
那我是不是可以干脆不开 fuzzy?
是的!特别适合以下场景:
- 个人开发者 / 独立站点:效率优先,旧翻译价值低;
- 翻译内容经常变动:旧句子参考意义小,保留反而污染;
- 不依赖 Poedit 或专业翻译工具:人工清洗 fuzzy 成本高。
推荐做法:
msgmerge --no-fuzzy-matching old.po template.pot -o merged.po
搭配清理工具:
msgattrib --no-obsolete merged.po -o clean.po
fuzzy 条目的运行行为再总结一下:
| 条目状态 | 实际显示内容 | 使用 msgstr 吗? |
|---|---|---|
| 正常翻译 | 翻译内容 | ✅ 是 |
#, fuzzy 标记 | 原文(msgid) | ❌ 否 |
空的 msgstr | 原文(msgid) | ❌ 否 |
误解警告:我看有些文章,包括 AI 回复都是不对的。--previous 不会提升性能,反而略微拖慢。
实际作用是:
把旧的
msgid作为注释保留下来,供翻译者参考。
例子:
#| msgid "Save"
msgid "Save file"
msgstr "保存文件"
适合在关闭 fuzzy 后仍保留一点上下文提示,但它会:
- 增加合并逻辑复杂度
- 增加
.po文件体积 - 对自动化流程没帮助
仅建议用于人工翻译或 GUI 工具(如 Poedit)环境下。
那旧的 msgid 会不会残留在 .po 文件里?
是的,但是以“废弃条目”的形式存在:
#~ msgid "Save"
#~ msgstr "保存"
这些是 gettext 特有的 obsolete 条目,不会影响翻译,但会让 .po 文件臃肿。
可用 msgattrib 清理:
msgattrib --no-obsolete merged.po -o clean.po
是否可以直接覆盖同一个文件输出?
msgattrib --no-obsolete merged.po -o merged.po # 是否可以?
不推荐!
虽然工具允许,但:
- 会触发系统层“先清空再写入”风险;
- 容易造成文件半写入、损坏或丢失;
- 调试困难,流程不安全。
推荐更安全的做法:
msgattrib --no-obsolete merged.po -o tmp.po && mv tmp.po merged.po
或使用临时文件自动生成:
tmp=$(mktemp)
msgattrib --no-obsolete merged.po -o "$tmp" && mv "$tmp" merged.po
推荐合并 + 清理脚本流程(适合开发者)
#!/bin/bash
set -e
OLD_PO=$1
POT=$2
OUT_PO=${3:-merged.po}
TMP_MERGED=$(mktemp)
TMP_CLEANED=$(mktemp)
# 合并并禁用 fuzzy
msgmerge --no-fuzzy-matching "$OLD_PO" "$POT" -o "$TMP_MERGED"
# 清理废弃条目
msgattrib --no-obsolete "$TMP_MERGED" -o "$TMP_CLEANED"
# 移动到目标文件
mv "$TMP_CLEANED" "$OUT_PO"
总结
| 问题/目标 | 推荐做法 |
|---|---|
| 合并过慢 | 使用 --no-fuzzy-matching |
| 想保留旧 msgid 提示信息 | 使用 --previous |
| fuzzy 条目被显示为原文 | 清除 fuzzy 或人工确认后移除 fuzzy 标记 |
| 旧 msgid 仍留在文件中 | 使用 msgattrib --no-obsolete 清理 |
安全写入 .po 文件 | 输出到临时文件后 mv 覆盖 |
适用判断表
| 场景 | 是否关闭 fuzzy | 是否使用 previous | 是否清理 obsolete |
|---|---|---|---|
| 个人项目 / 自动化 | ✅ 是 | ❌ 否 | ✅ 是 |
| 需要上下文提示 / GUI 翻译工具 | ❌ 否 | ✅ 是 | 可选 |
| 内容频繁变化 / 快速迭代项目 | ✅ 是 | 可选 | ✅ 是 |
| 专业翻译团队 / 翻译一致性要求 | ❌ 否 | ✅ 是 | 可选 |
如果你正在构建国际化支持的 CI/CD 流程、开发自动翻译工具链、或者维护一个翻译量大的项目,希望这篇文章能为你节省大量时间,并避免一些不易察觉的陷阱。








