使用 gettext 和 jinja2 以及金字塔翻译 %%

boa*_*der 6 gettext internationalization jinja2 python-2.7 pyramid

使用 Jinja2 和 Pyramid 与 Python 进行 i18n 工作。似乎不知道如何翻译%%。 我开始怀疑这个 bug 是在 Jinja2 中。


所以我做了更多的调查,看来问题更多的是 gettext 而不是 jinja2,如 repl 所示

>>>gettext.gettext("98%% off %s sale") % ('holiday')
'98% off holiday sale'
>>>gettext.gettext("98%% off sale")
'98%% off sale'

>>>gettext.gettext("98% off %s sale") % ('holiday')
Traceback (most recent call last):
  Python Shell, prompt 13, line 1
TypeError: %o format: a number is required, not str
Run Code Online (Sandbox Code Playgroud)

这似乎是一个先有鸡还是先有蛋的问题。

  • 如果 gettext 翻译 %% -> % 则格式化程序会在参数替换期间对其进行处理。
  • 如果 gettext 不翻译 %% -> % 那么当未调用格式化程序(没有要插入的参数)时 %% 会泄漏。

所有这些意味着翻译人员(其中大多数不是计算机程序员)必须非常小心地进行翻译,每个人都需要非常小心包含 % 的翻译。

似乎我们(不知何故)做错了,应该有一个更简单和统一的格式来做到这一点。现在我们通过简单地注入 % 作为格式参数来应对。

有没有更好的方法来做到这一点,或者这已经是最好的方法了吗?


底部有一个.po文件

单元测试几乎说明了一切,为什么最后一个断言失败了?这是 Jinja2 的错误吗,还是我需要以不同的方式处理这个问题。

class Jinja2Tests(TestCase):

    def test_percent_percent(self):
        """ i18n(gettext) expresses  98% as 98%% only in some versions of jinja2 that has not
            worked as expected.  This is to make sure that it is working. """
        env = Environment(extensions=['jinja2.ext.i18n'])
        lang = gettext.translation('messages', path.abspath(path.join(path.dirname(__file__), 'data')))
        env.install_gettext_translations(lang)

        template = env.from_string(source="{{ _('98%% off %(name)s sale') | format(name='holiday') }}")
        result = template.render()
        self.assertEqual('98% off holiday sale(translated)', result)

        template = env.from_string(source="{{ _('98%% off sale') }}")
        result = template.render()

        # THE LINE BELOW FAILS WITH:
        # AssertionError: '98% off sale(translated)' != u'98%% off sale(translated)'
        self.assertEqual('98% off sale(translated)', result)
Run Code Online (Sandbox Code Playgroud)

并且您必须将 MO 文件编译为 PO 文件才能运行上述代码。

# This file is distributed under the same license as the Uniregistrar project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2016.
#
msgid ""
msgstr ""
"Project-Id-Version: Uniregistrar 1.0\n"
"Report-Msgid-Bugs-To: mark@uniregistry.com\n"
"POT-Creation-Date: 2016-12-22 15:22-0500\n"
"PO-Revision-Date: 2016-11-14 16:42-0500\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: en\n"
"Language-Team: en <LL@li.org>\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.3.3\n"

#: uniregistrar/constants.py:90
msgid "98%% off sale"
msgstr "98%% off sale(translated)"

#: uniregistrar/constants.py:90
msgid "98%% off %(name)s sale"
msgstr "98%% off %(name)s sale(translated)"
Run Code Online (Sandbox Code Playgroud)

Gui*_*ohr 0

据我了解这个问题,这是您主要关心的问题:

所有这些意味着翻译人员(其中大多数不是计算机程序员)必须非常小心地进行翻译,每个人都需要非常小心包含 % 的翻译。

tl;dr:这就是为什么msgfmt有一个选项--check。该选项会msgfmt检查翻译是否可以安全地通过目标语言的字符串插值工具运行。所有这些问题的根源是 C 语言printf(),当使用错误的参数调用时,它很容易崩溃:

printf("Bonjour, %s!");
Run Code Online (Sandbox Code Playgroud)

printf()函数是一个可变参数函数。这%s会导致它从堆栈中弹出另一个参数。在上面的示例中,除了字符串文字之外没有其他参数。这意味着插入的字符串%s可以被认为来自任意地址,例如 0。在大多数现代语言中,这将是空指针异常。在 C 中,它是一个空指针取消引用,经常被利用来运行任意代码,这很糟糕。

我们假设代码如下所示:

printf(gettext("Hello, world!"));
Run Code Online (Sandbox Code Playgroud)

只要gettext()提供的翻译不包含任何“%”字符,那就是安全的。但如果法语翻译翻译“你好,世界!” 与“你好,%s!” 程序会崩溃。

好吧,如果软件的维护者使用标准的翻译工作流程,它就不会崩溃。在这种情况下,xgettext(在 Python 中,它可能类似于“pybabel extract”)会在文件中生成以下条目.po

#: filename.c:1
#, c-format
msgid "Hello, world!"
msgstr ""
Run Code Online (Sandbox Code Playgroud)

将“#, c-format”行读取为“这是一个 printf 格式字符串”!

比如说,法语翻译将其翻译成以下条目:

#: filename.c:1
#, c-format
msgid "Hello, world!"
msgstr "Bonjour, %s!"
Run Code Online (Sandbox Code Playgroud)

如果您执行此操作,msgfmt whatever.po它将被接受。但这不是推荐的工作流程。你应该把它运行一遍msgfmt --check whatever.po。现在你得到一个错误:

messages.po:23: number of format specifications in 'msgid' and 'msgstr' does not match
msgfmt: found 1 fatal error
Run Code Online (Sandbox Code Playgroud)

这是因为对于 GNU gettext 支持的每种语言,都会实现一个格式检查器来准确检查该问题。它确保翻译不会导致运行时问题。

您现在可能会争辩说,恶意翻译器会简单地从文件中删除“c-format”限定符.po。但是您的构建系统应该确保从外部源返回的翻译始终与当前的消息集(通常称为 )合并YOURPROJECT.pot,然后对.po文件的此类修改将被简单地丢弃。

所以,理论上你说的没有道理。实际上,您可能会有一个,因为有很多项目和软件.po直接使用文件进行运行时翻译。这是一个坏主意,请参阅我对类似问题的回答。

我不知道这在多大程度上适用于您的问题,因为您没有提到如何将字符串提取到文件中.pot以及如何将其编译到二进制.mo文件中。上面的解释应该清楚地表明这一点至关重要:提取步骤应该向.po文件添加有关所使用的字符串插值方法的自动注释,并且将文件编译.po.mo文件中应该启用格式字符串检查。如果你做不到这一点,那么你的构建系统就有缺陷。