gettext — 多语言国际化服务

源代码: Lib/gettext.py


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

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

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,其中 language 将在环境变量 LANGUAGELC_ALLLC_MESSAGESLANG 中依次查找。

如果省略 localedir 或为 None,则返回 domain 的当前绑定。[1]

gettext.textdomain(domain=None)

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

gettext.gettext(message)

根据当前的全局域、语言和语言环境目录,返回 message 的本地化翻译。此函数通常在本地命名空间中别名为 _()(请参阅下面的示例)。

gettext.dgettext(domain, message)

gettext() 类似,但在指定的 domain 中查找消息。

gettext.ngettext(singular, plural, n)

gettext() 类似,但考虑复数形式。如果找到翻译,则将复数公式应用于 n,并返回结果消息(某些语言有不止两种复数形式)。如果找不到翻译,则当 n 为 1 时返回 singular;否则返回 plural

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

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

ngettext() 类似,但在指定的 domain 中查找消息。

gettext.pgettext(context, message)
gettext.dpgettext(domain, context, message)
gettext.npgettext(context, singular, plural, n)
gettext.dnpgettext(domain, context, singular, plural, n)

与前缀中没有 p 的相应函数(即 gettext()dgettext()ngettext()dngettext())类似,但翻译仅限于给定的消息上下文

3.8 版本新增。

请注意,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.'))

基于类的 API

gettext 模块的基于类的 API 比 GNU gettext API 提供了更大的灵活性和便利性。这是本地化 Python 应用程序和模块的推荐方法。gettext 定义了一个 GNUTranslations 类,该类实现了 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)

基于 domainlocaledirlanguages 返回 *Translations 实例,它们首先传递给 find() 以获取关联的 .mo 文件路径列表。具有相同 .mo 文件名的实例会被缓存。如果提供了 class_,则实例化的实际类是 class_,否则为 GNUTranslations。该类的构造函数必须接受单个 文件对象 参数。

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

如果找不到 .mo 文件,则当 fallback 为 false(这是默认值)时,此函数会引发 OSError,如果 fallback 为 true,则返回 NullTranslations 实例。

在 3.3 版本中更改: IOError 过去会被引发,它现在是 OSError 的别名。

在 3.11 版本中更改: 删除了 codeset 参数。

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

这会基于 domainlocaledir 将函数 _() 安装到 Python 的 builtins 命名空间中,它们会传递给函数 translation()

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

如下所示,你通常会通过将应用程序中需要翻译的字符串包装在 _() 函数的调用中来标记它们,如下所示

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

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

在 3.11 版本中更改: names 现在是仅限关键字的参数。

NullTranslations

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

class gettext.NullTranslations(fp=None)

接受一个可选的文件对象 fp,基类会忽略它。初始化“受保护的”实例变量 _info_charset,它们由派生类设置,以及 _fallback,它通过 add_fallback() 设置。如果 fp 不是 None,则调用 self._parse(fp)

_parse(fp)

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

add_fallback(fallback)

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

gettext(message)

如果已设置回退,则将 gettext() 转发到回退。否则,返回 message。在派生类中被覆盖。

ngettext(singular, plural, n)

如果已设置回退,则将 ngettext() 转发到回退。否则,如果 n 为 1,则返回 singular;否则返回 plural。在派生类中被覆盖。

pgettext(context, message)

如果已设置回退,则将 pgettext() 转发到回退。否则,返回翻译后的消息。在派生类中被覆盖。

3.8 版本新增。

npgettext(context, singular, plural, n)

如果已设置回退,则将 npgettext() 转发到回退。否则,返回翻译后的消息。在派生类中被覆盖。

3.8 版本新增。

info()

返回一个字典,其中包含在消息目录文件中找到的元数据。

charset()

返回消息目录文件的编码。

install(names=None)

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

如果提供了 names 参数,则它必须是一个序列,其中包含您想要安装到内置命名空间中的函数名称,除了 _() 之外。支持的名称是 'gettext''ngettext''pgettext''npgettext'

请注意,这只是使 _() 函数可用于您的应用程序的一种方式,尽管是最方便的方式。因为它会全局影响整个应用程序,特别是内置命名空间,所以本地化模块永远不应该安装 _()。相反,它们应该使用此代码使 _() 可用于其模块

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

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

在 3.8 版本中更改:添加了 'pgettext''npgettext'

GNUTranslations

gettext 模块提供了一个从 NullTranslations 派生的附加类:GNUTranslations。此类覆盖 _parse() 以便能够读取大端和小端格式的 GNU gettext 格式的 .mo 文件。

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

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

整个键/值对集被放入字典中,并设置为“受保护的” _info 实例变量。

如果 .mo 文件的魔数无效,主版本号出乎意料,或者在读取文件时发生其他问题,则实例化 GNUTranslations 类可能会引发 OSError

class gettext.GNUTranslations

以下方法是从基类实现中覆盖的

gettext(message)

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

ngettext(singular, plural, n)

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

如果目录中未找到消息 ID,并且指定了回退,则请求将转发到回退的 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}
pgettext(context, message)

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

3.8 版本新增。

npgettext(context, singular, plural, n)

执行消息 id 的复数形式查找。singular 用作目录中查找的消息 id,而 n 用于确定使用哪个复数形式。

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

3.8 版本新增。

Solaris 消息目录支持

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

Catalog 构造函数

GNOME 使用 James Henstridge 版本的 gettext 模块,但此版本具有略有不同的 API。其记录的用法是

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

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

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

使您的程序和模块国际化

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

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

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

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

  4. 使用 gettext 模块,以便正确翻译消息字符串

为了准备您的代码以进行 I18N,您需要查看文件中的所有字符串。任何需要翻译的字符串都应该通过将其包装在 _('...') 中进行标记 — 即,调用函数 _。例如

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

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

有一些工具可以提取用于翻译的字符串。最初的 GNU gettext 仅支持 C 或 C++ 源代码,但其扩展版本 xgettext 会扫描多种语言(包括 Python)编写的代码,以查找标记为可翻译的字符串。Babel 是一个 Python 国际化库,其中包含一个 pybabel 脚本来提取和编译消息目录。François Pinard 的程序名为 xpot,它执行类似的工作,并且可以作为他的 po-utils 包的一部分使用。

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

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

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

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

本地化您的模块

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

假设您的模块名为“spam”,并且该模块的各种自然语言翻译 .mo 文件以 GNU gettext 格式位于 /usr/share/locale 中。以下是您要放在模块顶部的代码

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

本地化您的应用程序

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

因此,在简单情况下,您只需将以下代码添加到应用程序的主驱动程序文件中

import gettext
gettext.install('myapplication')

如果需要设置区域设置目录,则可以将其传递给 install() 函数

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

动态更改语言

如果您的程序需要同时支持多种语言,您可能需要创建多个翻译实例,然后在它们之间显式切换,如下所示

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()

延迟翻译

在大多数编码情况下,字符串在编码位置进行翻译。但是,有时您需要标记字符串以进行翻译,但将实际翻译延迟到以后。一个经典的例子是

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()

致谢

以下人员为本模块的创建贡献了代码、反馈、设计建议、先前的实现以及宝贵的经验

  • Peter Funk

  • James Henstridge

  • Juan David Ibáñez Palomar

  • Marc-André Lemburg

  • Martin von Löwis

  • François Pinard

  • Barry Warsaw

  • Gustavo Niemeyer

脚注