使用gettext、jinja2和pyramid进行翻译%%

6

使用Jinja2和Pyramid进行Python国际化工作。似乎存在一个问题,无法确定应该如何翻译%%。

我开始怀疑问题出在Jinja2中。

所以我进行了更多的调查,发现问题更多地与gettext有关,如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

似乎这是一个鸡生蛋的问题。
- 如果 gettext 翻译 %% -> %,则在参数替换期间格式化程序将无法正常工作。 - 如果 gettext 不翻译 %% -> %,则当不调用格式化程序(没有要插入的参数)时,%% 将泄露出来。
所有这些都意味着翻译者(大多数人不是计算机程序员)必须非常小心地进行翻译,并且每个人在包含 % 的翻译中都需要非常小心。
似乎我们做错了什么(不知怎么回事),应该有一种更直接和统一的格式来完成此操作。目前,我们通过将百分号作为格式参数注入来解决这个问题。
是否有更好的方法来处理这个问题,还是现在的方法就是最好的方法?
底部有一个 .po 文件。
单元测试基本上说了一切,为什么最后一个断言失败了?这是 Jinja2 的 bug,还是我需要以不同的方式处理它。
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)

您需要将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)"
1个回答

0
据我理解,这是你最关心的问题:
所有这些意味着翻译人员(其中大多数不是计算机程序员)在进行翻译时必须非常小心,每个人都需要非常小心处理包含百分号的翻译。
简而言之:这就是为什么`msgfmt`有一个`--check`选项。该选项会导致`msgfmt`检查翻译是否安全,可以通过目标语言的字符串插值功能运行。所有这些问题的根源是C语言的`printf()`函数,当使用错误的参数调用时很容易崩溃。
printf("Bonjour, %s!");

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

假设代码如下:

printf(gettext("Hello, world!"));

只要gettext()生成的翻译不包含任何"%"字符,那就是安全的。但是,如果法语翻译者将"Hello, world!"翻译为"Bonjour, %s!",程序将会崩溃。
如果软件的维护者使用标准的翻译工作流程,那么它将不会崩溃。在这种情况下,xgettext(在Python中可能是类似"pybabel extract"的东西)将会在.po文件中生成以下条目:
#: filename.c:1
#, c-format
msgid "Hello, world!"
msgstr ""

将行“#,c-format”解读为“这是一个printf格式字符串”!
比如,法语翻译将其翻译为以下内容:
#: filename.c:1
#, c-format
msgid "Hello, world!"
msgstr "Bonjour, %s!"

如果你运行 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

这是因为GNU gettext支持的每种语言都实现了一个格式检查器,它会准确检查这个问题。它确保翻译不会引起运行时问题。
你可能会争辩说,一个恶意的翻译者会简单地从.po文件中删除"c-format"限定符。但是你的构建系统应该确保从外部来源返回的翻译总是与当前的消息集合合并,通常称为YOURPROJECT.pot,然后对.po文件的这种修改将被丢弃。
所以,从理论上讲,你没有道理。但实际上你可能有道理,因为有很多项目和软件直接使用.po文件进行运行时翻译。这是一个不好的主意,请参考我的回答中类似问题的解释。
我不知道这对你的问题有多大的适用性,因为你没有提到如何将字符串提取到你的.pot文件中,以及如何将其编译成二进制的.mo文件。上述解释应该清楚地表明这一点:提取步骤应该在.po文件中添加关于所使用的字符串插值方法的自动注释,并且将.po文件编译成.mo文件应该启用格式字符串检查。如果你没有做到这一点,那么你的构建系统存在缺陷。

我忘记了这个问题!希望他们给了你一个复活术士徽章!就连我自己都不记得我6年10个月前在做什么了。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接