在构建多语言网站或应用时,使用 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 流程、开发自动翻译工具链、或者维护一个翻译量大的项目,希望这篇文章能为你节省大量时间,并避免一些不易察觉的陷阱。