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

源代码: Lib/gettext.py


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

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

22.1.1. GNU gettext API

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

  • gettext. bindtextdomain(* domain * [,* localedir *])
    • 将* domain 绑定到语言环境目录 localedir 。更具体地说,gettext将使用路径(在 Unix 上)在给定域中查找二进制.mo文件:localedir/language/LC_MESSAGES/domain.mo,其中在环境变量 LANGUAGE LC_ALL LC_MESSAGES LANG中搜索 languages *。

如果Ellipsis* localedir None,则返回 domain *的当前绑定。 [1]

  • gettext. bind_textdomain_codeset(* domain * [,* codeset *])
    • 将* domain 绑定到 codeset ,更改gettext()系列函数返回的字符串的编码。如果Ellipsis codeset *,则返回当前绑定。

2.4 版的新Function。

  • gettext. textdomain([* domain *])

    • 更改或查询当前的全局域。如果* domain None,则返回当前的全局域,否则将全局域设置为 domain *,并返回。
  • gettext. gettext(* message *)

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

2.4 版的新Function。

  • gettext. dgettext(* domain message *)

    • 类似于gettext(),但是在指定的* domain *中查找消息。
  • gettext. ldgettext(* domain message *)

2.4 版的新Function。

  • gettext. ngettext(单数复数,* n *)
    • 类似于gettext(),但考虑复数形式。如果找到翻译,则将复数公式应用于* n ,并返回结果消息(某些语言具有两种以上的复数形式)。如果没有找到翻译,则如果 n 为 1,则返回 singular 。否则返回复数*。

复数公式取自目录标题。它是具有自由变量* n *的 C 或 Python 表达式;该表达式计算目录中的复数索引。有关在.po文件中使用的确切语法以及各种语言的公式,请参见 GNU gettext 文档。

2.3 版的新Function。

  • gettext. lngettext(单数复数,* n *)

2.4 版的新Function。

  • gettext. dngettext(单数复数,* n *)
    • 类似于ngettext(),但是在指定的* domain *中查找消息。

2.3 版的新Function。

  • gettext. ldngettext(单数复数,* n *)

2.4 版的新Function。

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

22.1.2. 基于类的 API

与 GNU gettext API 相比,gettext模块的基于类的 API 为您提供了更大的灵 Active 和更大的便利性。这是本地化 Python 应用程序和模块的推荐方法。 gettext定义了一个“ translations”类,该类实现了 GNU .mo格式文件的解析,并具有用于返回标准 8 位字符串或 Unicode 字符串的方法。此“ translations”类的实例也可以将自身安装为_()函数。

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

如果未提供* localedir ,则使用默认的系统区域设置目录。 [2]如果未提供 languages ,则搜索以下环境变量: LANGUAGE LC_ALL LC_MESSAGES LANG。返回非空值的第一个用于 languages *变量。环境变量应包含由冒号分隔的语言列表,这些列表将在冒号上拆分以产生预期的语言代码字符串列表。

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

localedir/language/LC_MESSAGES/domain.mo

find()返回存在的第一个此类文件名。如果找不到此类文件,则返回None。如果给出* all *,它将返回所有文件名的列表,并按它们在语言列表或环境变量中出现的 Sequences 排列。

  • gettext. translation(* domain * [,* localedir * [,语言 [,* class_ * [,* fallback * [,* codeset *]]]]]))
    • 返回基于* domain localedir languages Translations实例,这些实例首先传递给find()以获取关联的.mo文件路径的列表。具有相同.mo文件名的实例将被缓存。如果提供的话,实例化的实际类为 class_ ,否则为GNUTranslations。类的构造函数必须采用单个文件对象参数。如果提供, codeset *将更改用于编码翻译后的字符串的字符集。

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

如果未找到.mo文件,则如果* fallback 为 false(默认值),此函数将引发IOError,如果 fallback *为 true,则返回NullTranslations实例。

在版本 2.4 中更改:添加了* codeset *参数。

  • gettext. install(* domain * [,* localedir * [,* unicode * [,* codeset * [,* names *]]]])
    • 这将基于传递给函数translation()的* domain localedir codeset *在 Python 的内建名称空间中安装函数_()。 * unicode *标志被传递到结果转换对象的install()方法。

有关* names *参数,请参见翻译对象的install()方法的描述。

如下所示,通常pass将它们包装在对_()函数的调用中来标记应用程序中待翻译的字符串,如下所示:

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

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

在版本 2.4 中更改:添加了* codeset *参数。

在版本 2.5 中进行了更改:添加了* names *参数。

22.1.2.1. NullTranslations 类

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

  • 类别 gettext. NullTranslations([* fp *])

    • 接受一个可选文件对象* fp ,Base Class 将忽略它。初始化由派生类设置的“受保护”实例变量 _info _charset 以及passadd_fallback()设置的 _fallback 。如果 fp *不是None,它将调用self._parse(fp)
  • _parse(* fp *)

    • 在 Base Class 中不进行任何操作,此方法采用文件对象* fp *,并从文件中读取数据,从而初始化其消息目录。如果具有不受支持的消息目录文件格式,则应重写此方法以解析格式。
  • add_fallback(后备)

    • 添加* fallback *作为当前翻译对象的后备对象。如果翻译对象无法为给定消息提供翻译,则应咨询后备。
  • gettext(消息)

    • 如果设置了后备,请将gettext()转发到后备。否则,返回翻译后的消息。在派生类中重写。
  • lgettext(消息)

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

2.4 版的新Function。

  • ugettext(消息)

    • 如果设置了后备,请将ugettext()转发到后备。否则,将翻译后的消息作为 Unicode 字符串返回。在派生类中重写。
  • ngettext(单数复数,* n *)

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

2.3 版的新Function。

  • lngettext(单数复数,* n *)
    • 如果设置了后备,请将lngettext()转发到后备。否则,返回翻译后的消息。在派生类中重写。

2.4 版的新Function。

  • ungettext(单数复数,* n *)
    • 如果设置了后备,请将ungettext()转发到后备。否则,将翻译后的消息作为 Unicode 字符串返回。在派生类中重写。

2.3 版的新Function。

  • info ( )

    • 返回“受保护的” _info变量。
  • charset ( )

    • 返回“受保护的” _charset变量。
  • output_charset ( )

    • 返回“受保护的” _output_charset变量,该变量定义用于返回翻译后的消息的编码。

2.4 版的新Function。

  • set_output_charset(字符集)
    • 更改“受保护的” _output_charset变量,该变量定义用于返回翻译后的消息的编码。

2.4 版的新Function。

  • install([[* unicode * [,* names *]])
    • 如果* unicode 标志为 false,则此方法将self.gettext()安装到内置名称空间中,并将其绑定到_。如果 unicode 为 true,它将绑定self.ugettext()。默认情况下, unicode *为 false。

如果给出了* names 参数,则它必须是一个序列,其中除了_()之外还包含要在内置名称空间中安装的函数的名称。支持的名称是'gettext'(根据 unicode 标志绑定到self.gettext()self.ugettext()),'ngettext'(根据 unicode *标志绑定到self.ngettext()self.ungettext()),'lgettext''lngettext'

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

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

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

在版本 2.5 中进行了更改:添加了* names *参数。

22.1.2.2. GNUTranslations 类

gettext模块提供了一个另外一个从NullTranslations派生的类:GNUTranslations。此类覆盖_parse()以启用以大端和小端格式读取 GNU gettext 格式.mo文件。它还将消息 ID 和消息字符串都强制转换为 Unicode。

GNUTranslations从翻译目录中解析出可选的元数据。 GNU gettext 约定将元数据包含为空字符串的转换。此元数据是 RFC 822样式key: value对,并且应包含Project-Id-Version键。如果找到键Content-Type,则charset属性用于初始化“受保护的” _charset实例变量,如果未找到则默认为None。如果指定了字符集编码,则使用该编码将从目录中读取的所有消息 ID 和消息字符串转换为 Unicode。 ugettext()方法始终返回 Unicode,而gettext()方法返回编码的 8 位字符串。对于这两种方法的 message id 参数,可接受 Unicode 字符串或仅包含 US-ASCII 字符的 8 位字符串。请注意,建议将方法的 Unicode 版本(即ugettext()ungettext())用于国际化的 Python 程序。

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

如果.mo文件的幻数无效,或者在读取文件时发生其他问题,则实例化GNUTranslations类可以引发IOError

从 Base Class 实现中重写以下方法:

  • GNUTranslations. gettext(* message *)

    • 在目录中查找* message * id,并返回相应的消息字符串,该字符串为使用目录字符集编码(如果已知)编码的 8 位字符串。如果目录中没有* message * id 的条目,并且已设置后备,则查找将转发到后备的gettext()方法。否则,返回* message * id。
  • GNUTranslations. lgettext(* message *)

    • 等效于gettext(),但是如果没有使用set_output_charset()显式设置其他编码,则以首选系统编码返回转换。

2.4 版的新Function。

  • GNUTranslations. ugettext(* message *)

    • 在目录中查找* message * id,并以 Unicode 字符串的形式返回相应的消息字符串。如果目录中没有* message * id 的条目,并且已设置后备,则查找将转发到后备的ugettext()方法。否则,返回* message * id。
  • GNUTranslations. ngettext(单数复数,* n *)

    • 对消息 ID 进行复数形式的查找。 单数用作消息 ID,以便在目录中查找,而* n *用于确定使用哪种复数形式。返回的消息字符串是使用目录的字符集编码(如果已知)编码的 8 位字符串。

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

2.3 版的新Function。

  • GNUTranslations. lngettext(单数复数,* n *)
    • 等效于gettext(),但是如果没有使用set_output_charset()显式设置其他编码,则以首选系统编码返回转换。

2.4 版的新Function。

  • GNUTranslations. ungettext(单数复数,* n *)
    • 对消息 ID 进行复数形式的查找。 单数用作消息 ID,以便在目录中查找,而* n *用于确定使用哪种复数形式。返回的消息字符串是 Unicode 字符串。

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

这是一个例子:

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

2.3 版的新Function。

22.1.2.3. Solaris 消息目录支持

Solarisos 定义了自己的二进制.mo文件格式,但是由于找不到该格式的文档,因此目前不支持该格式。

22.1.2.4. 目录构造函数

GNOME 使用 James Henstridge 的gettext模块版本,但是该版本的 API 稍有不同。其记录的用法是:

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

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

该模块与 Henstridge 的模块之间的一个区别是:他的目录对象支持pass MapAPI 进行访问,但是它似乎未使用,因此当前不受支持。

22.1.3. 国际化您的程序和模块

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

  • pass特殊标记可翻译字符串来准备程序或模块

  • 对标记的文件运行一套工具,以生成原始消息目录

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

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

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

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

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

Python 发行版附带两个工具,这些工具可以帮助您在准备好源代码后生成消息目录。这些文件可能会或可能不会从二进制发行版中获得,但是可以在源发行版的Tools/i18n目录中找到它们。

pygettext [3]程序扫描您的所有 Python 源代码,以查找您先前标记为可翻译的字符串.它与 GNU gettext **程序相似,不同之处在于它可以理解 Python 源代码的所有复杂性,但对 C 或 C 源代码一无所知。除非您还将翻译 C 代码(例如 C 扩展模块),否则不需要 GNU gettext

pygettext 生成文本的 Uniforum 风格的人类可读消息目录.pot文件,本质上是结构化的人类可读文件,其中包含源代码中的每个标记字符串以及翻译字符串的占位符。 pygettext 是一个命令行脚本,它支持与 xgettext 类似的命令行界面;有关其使用的详细信息,请运行:

pygettext.py --help

然后,将这些.pot文件的副本移交给各个人工翻译,后者为每种受支持的自然语言编写特定于语言的版本。他们以.po文件的形式将填写好的特定语言版本发送回给您。使用 msgfmt.py [4]程序(在Tools/i18n目录中),从翻译器中获取.po文件,并生成机器可读的.mo二进制目录文件。 .mo文件是gettext模块在运行时用于实际翻译处理的文件。

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

22.1.3.1. 本地化模块

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

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

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

如果您的翻译人员在其.po文件中为您提供 Unicode 字符串,则您应该这样做:

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

22.1.3.2. 本地化您的应用程序

如果要本地化应用程序,则可以将_()函数全局安装到内置名称空间中,通常是在应用程序的主驱动程序文件中。这将使您所有与应用程序相关的文件仅使用_('...'),而不必在每个文件中都明确安装它。

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

import gettext
gettext.install('myapplication')

如果需要设置语言环境目录或* unicode *标志,可以将它们传递到install()函数中:

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

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

22.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”标识为可翻译为 pygettext 程序,因为它不是字符串。

解决此问题的另一种方法是以下示例:

def N_(message): return message

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

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

在这种情况下,您将使用函数N_()[5]标记可翻译的字符串,该函数不会与_()的任何定义冲突。但是,您将需要教您的消息提取程序来查找标记为N_()的可翻译字符串。 pygettextxpot 都pass使用命令行开关来支持。

22.1.3.5. gettext()与 lgettext()

在 Python 2.4 中,引入了lgettext()系列函数。这些Function的目的是提供一种更符合 GNU gettext 当前实现的替代方法。与gettext()不同,后者返回使用翻译文件中使用的相同代码集编码的字符串,而lgettext()将返回使用首选系统编码编码的字符串,如locale.getpreferredencoding()返回。还要注意,Python 2.4 引入了新函数来显式选择转换后的字符串中使用的代码集。如果显式设置了代码集,则lgettext()也会在请求的代码集中返回翻译后的字符串,这与 GNU gettext 实现中所期望的一样。

22.1.4. Acknowledgements

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

  • Peter Funk

  • James Henstridge

  • 胡安·大卫·伊巴涅斯·帕洛玛

  • Marc-André Lemburg

  • 马丁·冯·洛维斯

  • François Pinard

  • Barry Warsaw

  • Gustavo Niemeyer

Footnotes

  • [1]

    • 默认的语言环境目录是系统相关的。例如,在 RedHat Linux 上为/usr/share/locale,在 Solaris 上为/usr/lib/localegettext模块不try支持这些依赖于系统的默认值。相反,其默认值为sys.prefix/share/locale。因此,始终最好在应用程序的开头使用明确的绝对路径调用bindtextdomain()
  • [2]

  • [3]

    • FrançoisPinard 编写了一个名为 xpot 的程序,它的工作与此类似。它是他的po-utils package的一部分。
  • [4]

    • msgfmt.py 与 GNU msgfmt 二进制兼容,除了它提供了更简单的全 Python 实现。有了这个和 pygettext.py ,您通常不需要安装 GNU gettext 包来国际化您的 Python 应用程序。
  • [5]

    • 这里选择N_()完全是任意的;可能很容易成为MarkThisStringForTranslation()