Skip to main content

23.1. gettext —多语言国际化服务

源代码: Lib/gettext.py


gettext 模块为您的Python模块和应用程序提供国际化(I18N)和本地化(L10N)服务。它支持GNU gettext 消息目录API和更高级别的基于类的API,可能更适合Python文件。下面描述的界面允许您以一种自然语言编写模块和应用程序消息,并提供用不同自然语言运行的翻译消息目录。

还给出了一些本地化您的Python模块和应用程序的提示。

23.1.1. GNU gettext API

gettext 模块定义了以下API,它与GNU gettext API非常相似。如果您使用此API,您将影响整个应用程序的全球翻译。通常这是你想要的,如果你的应用程序是单语,选择的语言取决于你的用户的语言环境。如果要本地化Python模块,或者如果应用程序需要即时切换语言,则可能需要使用基于类的API。

gettext.bindtextdomain(domain, localedir=None)

domain 绑定到语言环境目录 localedir。更具体地,gettext 将使用路径(在Unix上)寻找给定域的二进制 .mo 文件:localedir/language/LC_MESSAGES/domain.mo,其中分别在环境变量 LANGUAGELC_ALLLC_MESSAGESLANG 中搜索 languages

如果省略 localedirNone,则返回 domain 的当前绑定。 [1]

gettext.bind_textdomain_codeset(domain, codeset=None)

domain 绑定到 codeset,更改 gettext() 系列函数返回的字符串的编码。如果省略 codeset,则返回当前绑定。

gettext.textdomain(domain=None)

更改或查询当前全局域。如果 domainNone,则返回当前全局域,否则将全局域设置为 domain,返回。

gettext.gettext(message)

返回基于当前全局域,语言和区域设置目录的 message 的本地化转换。此函数通常在本地命名空间中称为 _() (参见下面的示例)。

gettext.lgettext(message)

等同于 gettext(),但是如果没有使用 bind_textdomain_codeset() 显式设置其他编码,则在首选系统编码中返回翻译。

gettext.dgettext(domain, message)

gettext(),但看在指定的 domain 中的消息。

gettext.ldgettext(domain, message)

等同于 dgettext(),但是如果没有使用 bind_textdomain_codeset() 显式设置其他编码,则在首选系统编码中返回翻译。

gettext.ngettext(singular, plural, n)

gettext(),但考虑复数形式。如果找到翻译,则将复数公式应用于 n,并返回结果消息(某些语言具有多于两个复数形式)。如果没有找到翻译,则返回 singular,如果 n 为1;否则返回 plural

多个公式取自目录标题。它是一个C或Python表达式,有一个自由变量 n;该表达式求值为目录中的复数的索引。有关在 .po 文件中使用的精确语法和各种语言的公式,请参见 GNU gettext文档

gettext.lngettext(singular, plural, n)

等同于 ngettext(),但是如果没有使用 bind_textdomain_codeset() 显式设置其他编码,则在首选系统编码中返回翻译。

gettext.dngettext(domain, singular, plural, n)

ngettext(),但看在指定的 domain 中的消息。

gettext.ldngettext(domain, singular, plural, n)

等同于 dngettext(),但是如果没有使用 bind_textdomain_codeset() 显式设置其他编码,则在首选系统编码中返回翻译。

注意GNU gettext 也定义了一个 dcgettext() 方法,但这被认为没有用,因此它目前未实现。

以下是此API的典型用法示例:

import gettext
gettext.bindtextdomain('myapplication', '/path/to/my/language/directory')
gettext.textdomain('myapplication')
_ = gettext.gettext
# ...
print(_('This is a translatable string.'))

23.1.2. 基于类的API

gettext 模块的基于类的API为您提供了比GNU gettext API更大的灵活性和更大的方便性。它是本地化您的Python应用程序和模块的推荐方式。 gettext 定义了一个“translations”类,它实现了GNU .mo 格式文件的解析,并且有返回字符串的方法。此“翻译”类的实例也可以将其自身安装在内置命名空间中作为函数 _()

gettext.find(domain, localedir=None, languages=None, all=False)

此函数实现标准的 .mo 文件搜索算法。它需要一个 domain,与 textdomain() 相同。可选 localedirbindtextdomain() 一样可选 languages 是字符串列表,其中每个字符串都是语言代码。

如果未给出 localedir,则使用缺省系统区域设置目录。 [2] 如果未给出 languages,则搜索以下环境变量:LANGUAGELC_ALLLC_MESSAGESLANG。第一个返回非空值的值用于 languages 变量。环境变量应该包含一个冒号分隔的语言列表,这些列表将在冒号上分割,以生成期望的语言代码字符串列表。

find() 然后扩展和规范化语言,然后遍历它们,搜索由这些组件构建的现有文件:

localedir/language/LC_MESSAGES/domain.mo

存在的第一个此类文件名由 find() 返回。如果没有找到这样的文件,则返回 None。如果给出了 all,它将按照它们在语言列表或环境变量中出现的顺序返回所有文件名的列表。

gettext.translation(domain, localedir=None, languages=None, class_=None, fallback=False, codeset=None)

返回基于 domainlocaledirlanguagesTranslations 实例,这些实例首先传递给 find() 以获取关联的 .mo 文件路径的列表。缓存具有相同 .mo 文件名的实例。实例化的实例类型是 class_ (如果提供的话),否则是 GNUTranslations。类的构造函数必须采用单个 file object 参数。如果提供,codeset 将更改用于在 lgettext()lngettext() 方法中对翻译后的字符串进行编码的字符集。

如果找到多个文件,则稍后的文件将用作早期文件的回退。为了允许设置回退,copy.copy() 用于从缓存中克隆每个翻译对象;实际的实例数据仍然与高速缓存共享。

如果没有找到 .mo 文件,则如果 fallback 为false(这是默认值),则此函数提升 OSError,如果 fallback 为真,则返回 NullTranslations 实例。

在 3.3 版更改: IOError 以前是升高而不是 OSError

gettext.install(domain, localedir=None, codeset=None, names=None)

这将在基于 domainlocaledircodeset 的Python内置命名空间中安装函数 _(),这些函数传递给函数 translation()

对于 names 参数,请参阅翻译对象的 install() 方法的描述。

如下图所示,您通常在应用程序中标记字符串作为翻译的候选对象,方法是将它们包装在对 _() 函数的调用中,如下所示:

print(_('This string will be translated.'))

为了方便起见,您希望 _() 函数安装在Python内置命名空间中,因此它可以在应用程序的所有模块中轻松访问。

23.1.2.1. NullTranslations

翻译类是实际实现原始源文件消息字符串到翻译的消息字符串的翻译。所有翻译类使用的基类是 NullTranslations;这提供了可以用来编写自己的专门翻译类的基本接口。以下是 NullTranslations 的方法:

class gettext.NullTranslations(fp=None)

采用可选的 file object fp,它被基类忽略。初始化通过派生类设置的“受保护”实例变量 _info_charset,以及通过 add_fallback() 设置的 _fallback。然后如果 fp 不是 None,则调用 self._parse(fp)

_parse(fp)

在基类中无操作,此方法接受文件对象 fp,并从文件读取数据,初始化其消息目录。如果您具有不受支持的消息目录文件格式,则应覆盖此方法以解析您的格式。

add_fallback(fallback)

fallback 添加为当前翻译对象的后备对象。如果翻译对象无法为给定的消息提供翻译,则应参考回退。

gettext(message)

如果已设置回退,则将 gettext() 转发到回退。否则,返回已翻译的消息。在派生类中重写。

lgettext(message)

如果已设置回退,则将 lgettext() 转发到回退。否则,返回已翻译的消息。在派生类中重写。

ngettext(singular, plural, n)

如果已设置回退,则将 ngettext() 转发到回退。否则,返回已翻译的消息。在派生类中重写。

lngettext(singular, plural, n)

如果已设置回退,则将 lngettext() 转发到回退。否则,返回已翻译的消息。在派生类中重写。

info()

返回“protected” _info 变量。

charset()

返回“protected” _charset 变量,它是消息目录文件的编码。

output_charset()

返回“protected” _output_charset 变量,它定义用于在 lgettext()lngettext() 中返回已翻译消息的编码。

set_output_charset(charset)

更改“protected” _output_charset 变量,它定义用于返回已翻译消息的编码。

install(names=None)

此方法将 self.gettext() 安装到内置命名空间中,将其绑定到 _

如果给出了 names 参数,则它必须是包含除了 _() 之外还要在builtins命名空间中安装的函数的名称的序列。支持的名称是 'gettext' (结合 self.gettext()),'ngettext' (结合 self.ngettext()),'lgettext''lngettext'

注意,这只是一种方法,虽然是最方便的方法,使 _() 功能可用于您的应用程序。因为它影响整个应用程序,特别是内置命名空间,本地化模块不应该安装 _()。相反,他们应该使用此代码使 _() 可用于他们的模块:

import gettext
t = gettext.translation('mymodule', ...)
_ = t.gettext

这将 _() 仅放在模块的全局命名空间中,因此仅影响此模块中的调用。

23.1.2.2. GNUTranslations

gettext 模块提供从 NullTranslations 派生的一个附加类:GNUTranslations。此类覆盖 _parse() 以启用以big-endian和little-endian格式读取GNU gettext 格式 .mo 文件。

GNUTranslations 解析翻译目录中的可选元数据。使用GNU gettext 来包含元数据作为空字符串的转换是惯例。这个元数据是 RFC 822 型的 key: value 对,并且应该包含 Project-Id-Version 密钥。如果找到密钥 Content-Type,则 charset 属性用于初始化“受保护的” _charset 实例变量,如果找不到,则默认为 None。如果指定了字符集编码,则从目录读取的所有消息标识和消息字符串都将使用此编码转换为Unicode,否则假定为ASCII编码。

由于消息ID也被读为Unicode字符串,所有 *gettext() 方法将假设消息id为Unicode字符串,而不是字节字符串。

整个键/值对集合被放置到字典中并设置为“protected” _info 实例变量。

如果 .mo 文件的幻数无效,主版本号是意外的,或者如果在读取文件时出现其他问题,则实例化 GNUTranslations 类可以提高 OSError

从基类实现覆盖以下方法:

GNUTranslations.gettext(message)

在目录中查找 message 标识,并返回相应的消息字符串作为Unicode字符串。如果目录中没有针对 message ID的条目,并且已设置回退,则将查找转发到回退的 gettext() 方法。否则,返回 message id。

GNUTranslations.lgettext(message)

等效于 gettext(),但是转换作为在选择的输出字符集中编码的字节码返回,或者如果未使用 set_output_charset() 明确设置编码,则在首选系统编码中返回。

GNUTranslations.ngettext(singular, plural, n)

执行消息id的复数形式查找。 singular 用作消息id以用于在目录中查找,而 n 用于确定使用哪个复数形式。返回的消息字符串是Unicode字符串。

如果在目录中找不到消息标识,并且指定了回退,则请求将转发到回退的 ngettext() 方法。否则,当 n 为1时,返回 singular,并且在所有其他情况下返回 plural

这里是一个例子:

n = len(os.listdir('.'))
cat = GNUTranslations(somefile)
message = cat.ngettext(
    'There is %(num)d file in this directory',
    'There are %(num)d files in this directory',
    n) % {'num': n}
GNUTranslations.lngettext(singular, plural, n)

等效于 gettext(),但是转换作为在选择的输出字符集中编码的字节码返回,或者如果未使用 set_output_charset() 明确设置编码,则在首选系统编码中返回。

23.1.2.3. Solaris消息目录支持

Solaris操作系统定义了自己的二进制 .mo 文件格式,但由于在此格式中找不到任何文档,因此目前不支持此格式。

23.1.2.4. 目录构造函数

GNOME使用James Henstridge的 gettext 模块的版本,但是这个版本有一个稍微不同的API。它的文档用法是:

import gettext
cat = gettext.Catalog(domain, localedir)
_ = cat.gettext
print(_('hello world'))

为了与该旧模块兼容,函数 Catalog() 是上述 translation() 函数的别名。

这个模块和Henstridge之间的一个区别:他的目录对象支持通过映射API访问,但是这似乎没有使用,因此目前不支持。

23.1.3. 使您的程序和模块国际化

国际化(I18N)是指使程序知道多种语言的操作。本地化(L10N)是指一旦国际化,您的程序适应当地语言和文化习惯。为了为您的Python程序提供多语言消息,您需要执行以下步骤:

  1. 通过专门标记可翻译字符串来准备您的程序或模块

  2. 在标记的文件上运行一套工具来生成原始消息目录

  3. 创建消息目录的语言特定翻译

  4. 请使用 gettext 模块,以便正确转换消息字符串

为了准备I18N的代码,您需要查看文件中的所有字符串。任何需要翻译的字符串都应该通过将它包装在 _('...') 中来标记 - 也就是说,调用函数 _()。例如:

filename = 'mylog.txt'
message = _('writing a log message')
fp = open(filename, 'w')
fp.write(message)
fp.close()

在该示例中,字符串 'writing a log message' 被标记为翻译的候选,而字符串 'mylog.txt''w' 不是。

有一些工具来提取字符串意味着翻译。原始的GNU gettext 仅支持C或C++源代码,但其扩展版本 xgettext 扫描用多种语言(包括Python)编写的代码,以查找标记为可翻译的字符串。 巴贝尔 是一个Python国际化库,其中包含一个 pybabel 脚本,用于提取和编译消息目录。 FrançoisPinard的节目 xpot 做了一个类似的工作,并作为他的 po-utils包 的一部分。

(Python还包括这些程序的纯Python版本,称为 pygettext.pymsgfmt.py;一些Python发行版将为您安装它们。pygettext.pyxgettext 类似,但只能理解Python源代码,不能处理其他编程语言,如C或C++。 pygettext.py 支持类似于 xgettext 的命令行界面;有关其使用的详细信息,请运行 pygettext.py --helpmsgfmt.py 与GNU msgfmt 是二进制兼容的。通过这两个程序,您可能不需要GNU gettext 包来使您的Python应用程序国际化。

xgettextpygettext 和类似工具生成作为消息目录的 .po 文件。它们是结构化的人类可读文件,包含源代码中的每个标记字符串,以及这些字符串的翻译版本的占位符。

然后将这些 .po 文件的副本交给为每种支持的自然语言编写翻译的个人翻译。它们将完成的语言特定版本作为 <language-name>.po 文件发回,使用 msgfmt 程序将其编译成机器可读的 .mo 二进制目录文件。 .mo 文件由 gettext 模块用于运行时的实际转换处理。

如何在代码中使用 gettext 模块取决于您是将单个模块或整个应用程序国际化。接下来的两节将讨论每个案例。

23.1.3.1. 本地化您的模块

如果您要本地化您的模块,则必须注意不要进行全局更改,例如到内置命名空间。您不应使用GNU gettext API,而应使用基于类的API。

假设您的模块被称为“垃圾邮件”,模块的各种自然语言翻译 .mo 文件驻留在 /usr/share/locale 中的GNU gettext 格式。这里是你会放在你的模块的顶部:

import gettext
t = gettext.translation('spam', '/usr/share/locale')
_ = t.lgettext

23.1.3.2. 本地化您的应用程序

如果要本地化应用程序,则可以将 _() 函数全局安装到内置命名空间中,通常在应用程序的主驱动程序文件中。这将使所有特定于应用程序的文件仅使用 _('...'),而不必在每个文件中显式安装。

在简单的情况下,你只需要将下面的代码添加到应用程序的主驱动程序文件中:

import gettext
gettext.install('myapplication')

如果您需要设置区域设置目录,您可以将其传递到 install() 功能:

import gettext
gettext.install('myapplication', '/usr/share/locale')

23.1.3.3. 在飞行中改变语言

如果你的程序需要同时支持多种语言,你可能需要创建多个翻译实例,然后明确地在它们之间切换,像这样:

import gettext

lang1 = gettext.translation('myapplication', languages=['en'])
lang2 = gettext.translation('myapplication', languages=['fr'])
lang3 = gettext.translation('myapplication', languages=['de'])

# start by using language1
lang1.install()

# ... time goes by, user selects language 2
lang2.install()

# ... more time goes by, user selects language 3
lang3.install()

23.1.3.4. 延期翻译

在大多数编码情况下,字符串在它们被编码的地方被转换。然而,偶尔,您需要标记字符串进行翻译,但推迟实际翻译直到稍后。一个典型的例子是:

animals = ['mollusk',
           'albatross',
           'rat',
           'penguin',
           'python', ]
# ...
for a in animals:
    print(a)

在这里,您想要将 animals 列表中的字符串标记为可翻译,但是您实际上不想翻译它们,直到它们打印。

这里有一种方法可以处理这种情况:

def _(message): return message

animals = [_('mollusk'),
           _('albatross'),
           _('rat'),
           _('penguin'),
           _('python'), ]

del _

# ...
for a in animals:
    print(_(a))

这是因为 _() 的虚拟定义简单地返回不变的字符串。并且这个虚拟定义将临时覆盖内置命名空间中的 _() 的任何定义(直到 del 命令)。注意,虽然如果您在本地命名空间中有以前的 _() 定义。

请注意,第二次使用 _() 不会将“a”标识为可转换为 gettext 程序,因为该参数不是字符串文字。

另一种处理这种情况的方法是使用以下示例:

def N_(message): return message

animals = [N_('mollusk'),
           N_('albatross'),
           N_('rat'),
           N_('penguin'),
           N_('python'), ]

# ...
for a in animals:
    print(_(a))

在这种情况下,您使用函数 N_() 标记可翻译字符串,这将不会与 _() 的任何定义冲突。但是,您将需要教您的消息提取程序以查找标有 N_() 的可翻译字符串。 xgettextpygettextpybabel extractxpot 都通过使用 -k 命令行开关支持这一点。这里的 N_() 的选择是完全任意的;它可以像 MarkThisStringForTranslation() 一样容易。

23.1.4. 致谢

以下人员提供了代码,反馈,设计建议,以前的实现,以及创建此模块的宝贵经验:

  • 彼得·福克

  • 詹姆斯亨斯特里奇

  • Juan DavidIbáñezPalomar

  • Marc-AndréLemburg

  • Martin vonLöwis

  • FrançoisPinard

  • 巴里华沙

  • Gustavo Niemeyer

脚注

[1]

默认语言环境目录是系统相关的;例如,在RedHat Linux上是 /usr/share/locale,但在Solaris上是 /usr/lib/localegettext 模块不尝试支持这些系统相关的默认值;而是默认为 sys.prefix/share/locale。因此,在应用程序开始时,最好使用显式绝对路径调用 bindtextdomain()

[2]

见上面 bindtextdomain() 的脚注。