跳转至

提取

提取是把源代码中每一条被标记的消息收集进供翻译者使用的 .pot 模板的步骤—— 即教程循环中的第 3 步。本页是该步骤的参考:配置、自定义函数名、 CI 的 strict 模式,以及此后守护目录的各项检查。

提取需要 babel extra:

python -m pip install "gettext-tstrings[babel]"

工作流

创建 babel.cfg

[gettext_tstrings: **.py]
encoding = utf-8

然后使用普通 Babel 命令:

pybabel extract -F babel.cfg -c "Translators:" -o locales/messages.pot .
pybabel init -i locales/messages.pot -d locales -l ja
pybabel compile -d locales

init 每种语言只运行一次;此后由 pybabel update 把每份新模板并入既有目录。 这个反复出现的周期——以及其中的 fuzzy 条目对一次发布意味着什么——在 生产实践中完整走过一遍。

gettext_tstrings extractor 也处理普通的 _()gettext()ngettext() 调用,所以一个 mapping 就能覆盖混合 codebase。它识别 _()、四个标准 gettext 名称、tr() / ntr() 别名,以及延迟翻译用的 lazy_gettext() / lazy_pgettext()

-c 打开翻译者注释

与普通 gettext 调用完全一样,只有向 pybabel extract 传入 -c "Translators:" 才会收集翻译者注释。不加它,提取照样能跑——只是这些注释 永远到不了目录,而在目录里它们是整个工作流中成本最低的质量 杠杆

注册自定义函数名

[gettext_tstrings: **.py]
tr_functions = tr translate
ntr_functions = ntr
[[mappings]]
method = "gettext_tstrings"
pattern = "**.py"
tr_functions = ["tr", "translate"]
ntr_functions = ["ntr"]

ini 文件提供一个字符串,TOML mapping 提供一个列表;字符串内可用空格或逗号分隔 名称。四种写法都可使用。

可配置项包括 tr_functionsntr_functionsgettext_functionsngettext_functionspgettext_functionsnpgettext_functions

-k 无法到达 t-string

mytr(t"…") 这样的自定义 helper 必须在上述选项之一中注册。Babel 的 --keyword 机制无法读取 t-string literal,因此 pybabel extract -k mytr 什么也找不到,也不会发出提示——消息只会缺席于 POT。 对同时提取的普通 gettext 调用,-k 仍然有效。

仅支持标准参数顺序:普通调用先放 message;pgettext 依次为 context、message; npgettext 依次为 context、单数、复数。

本地宽容,CI 严格

默认情况下,一个坏文件不会终止整个提取过程:

  • extractor 拒绝的 t-string——属性访问、表达式、错误参数——会被报告为警告并跳过。
  • 无法 parse 的文件也以同样方式跳过。
  • ast 接受但只有 tokenize 拒绝的文件同样会跳过,否则 Babel 自身的 pass 会 因此中止。

这在你正编辑代码时很方便,在你不看着它时却很危险:被跳过的消息就是在 POT 中 缺席,于是它永远不会被翻译,而且没有任何东西会提醒你。凡是提取过程没有人盯着 的地方,请在 mapping 选项中设置 strict = true

[gettext_tstrings: **.py]
encoding = utf-8
strict = true
[[mappings]]
method = "gettext_tstrings"
pattern = "**.py"
strict = true

这样一来,上面的每一条警告都会变成 hard failure。请把它当作生产环境的设置, 而把默认值当作本地开发的设置。

现有 toolchain 会验证这些目录

Babel 为每条提取消息添加一个标准标志,这一行会激活现有工具中的占位符检查:

#. gettext-tstrings
#: app.py:4
#, python-brace-format
msgid "Hello {name}"
msgstr ""

将它翻译为 こんにちは {nombre} 后,无需配置就能捕获错误:

$ msgfmt --check-format -o /dev/null locales/ja/LC_MESSAGES/messages.po
locales/ja/LC_MESSAGES/messages.po:25: a format specification for argument
'name' doesn't exist in 'msgstr'
msgfmt: found 1 fatal error

Weblate 将同一检查记录为 Python brace format,商业平台也有基于 同一标志的占位符 QA。每个平台的行为由它自己负责;下面两个工具才是本项目明确验证的。

此外,本包还注册了一个 Babel checker,因此 pybabel compile 会把规范规则 应用到每一条带有 gettext-tstrings marker 注释的消息:

$ pybabel compile -d locales -l ja
error: locales/ja/LC_MESSAGES/messages.po:24: translation does not match the
source placeholders: {name} is missing; {nombre} is not in the source message
1 errors encountered.

对于复数消息,指针会指出具体形式,因为 Babel 报告的是 msgid 行号,而俄语 block 在它下面有三个 msgstr

error: locales/ru/LC_MESSAGES/messages.po:31: msgstr[1]: translation does not
match the source placeholders: {n} is missing

pybabel compile 仍会写出 .mo

上面的错误会被报告,退出状态为 1,但损坏的目录仍会被编译。只有这个退出 状态才能阻止 pipeline 发布它;CI 把守什么展示了 实现这一点的构建步骤。

两种检查并不重复。本包的 checker 至少在两种情形下更加严格:

  • 如果 msgid 中只有转义花括号(Config {{raw}} only),就不会获得 python-brace-format 标志,因此外部工具完全不会验证它。
  • 复数形式会逐个检查。msgfmt --check-format 读取上面的文件会返回 0;某个形式 删除了兄弟形式保留的占位符,msgfmt 会接受,而本 checker 会拒绝。

msgfmt 只检查能按 Python brace format 解析的占位符名称。使用 ASCII 名称,可以 让链中的每个工具都验证消息;本库自身接受所有满足 str.isidentifier() 的名称。

Template 和其他工具

t-string 是 Python 语法,因此本库覆盖 Python source。template 语言继续使用各自的 i18n——Jinja2 的 {% trans %}、Django template tag——以及对应的 Babel extractor。 所有内容进入同一个 PO 目录,因此混合 codebase 仍可使用一套翻译工作流。

目前 pygettext 无法 parse t-string,所以提取通过 Babel 完成。该约定已写入 规范,以便其他 extractor 或未来的 pygettext 实现同一目标。