gettext —多语言国际化服务

源代码: Lib/gettext.py


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

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

GNU gettext API

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

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

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

  • gettext. bind_textdomain_codeset(* domain codeset = None *)

从 3.8 版开始不推荐使用,将在 3.10 版中删除。

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

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

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

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

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

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

    • 类似于ngettext(),但是在指定的* domain *中查找消息。
  • gettext. pgettext(上下文消息)

  • gettext. dpgettext(上下文消息)

  • gettext. npgettext(上下文单数复数,* n *)

  • gettext. dnpgettext(* domain context singular plural n *)

3.8 版的新Function。

  • gettext. lgettext(* message *)

  • gettext. ldgettext(* domain message *)

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

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

Warning

在 Python 3 中应避免使用这些函数,因为它们返回编码的字节。最好使用替代方法来返回 Unicode 字符串,因为大多数 Python 应用程序都希望将人类可读的文本作为字符串而不是字节来处理。此外,如果翻译后的字符串存在编码问题,则可能会出现与 Unicode 相关的意外异常。

从 3.8 版开始不推荐使用,将在 3.10 版中删除。

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

Class-based API

与 GNU gettext API 相比,gettext模块的基于类的 API 为您提供了更大的灵 Active 和更大的便利性。这是本地化 Python 应用程序和模块的推荐方法。 gettext定义GNUTranslations类,该类实现 GNU .mo格式文件的解析,并具有用于返回字符串的方法。此类的实例也可以将自身作为函数_()安装在内置名称空间中。

  • gettext. find(* domain localedir = None languages = None all = False *)
    • 此函数实现标准的.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 = None languages = None class_ = None fallback = False codeset = None *)
    • 返回基于* domain localedir languages *Translations实例,它们首先传递给find()以获得相关的.mo文件路径的列表。具有相同.mo文件名的实例将被缓存。如果提供的话,实例化的实际类为 class_ ,否则为GNUTranslations。类的构造函数必须使用单个file object参数。如果提供, codeset *将更改用于对lgettext()lngettext()方法中的转换后的字符串进行编码的字符集。

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

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

在版本 3.3 中进行了更改:IOError曾经被提升,而不是OSError

从 3.8 版开始不推荐使用,将在 3.10 版中删除:* codeset *参数。

  • gettext. install(* domain localedir = None codeset = None names = None *)
    • 这会根据传递给函数translation()的* domain localedir codeset *,将函数_()安装在 Python 的内建名称空间中。

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

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

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

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

从 3.8 版开始不推荐使用,将在 3.10 版中删除:* codeset *参数。

NullTranslations 类

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

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

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

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

    • 如果设置了后备,请将gettext()转发到后备。否则,返回* message *。在派生类中重写。
  • ngettext(单数复数,* n *)

    • 如果设置了后备,请将ngettext()转发到后备。否则,如果* n 为 1,则返回 singular 。否则返回复数*。在派生类中重写。
  • pgettext((* context message *)

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

3.8 版的新Function。

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

3.8 版的新Function。

  • lgettext(消息)

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

    • 等效于gettext()ngettext(),但是如果未使用set_output_charset()显式设置编码,则转换将以首选系统编码中编码的字节字符串形式返回。在派生类中重写。

Warning

在 Python 3 中应避免使用这些方法。请参见lgettext()函数的警告。

从 3.8 版开始不推荐使用,将在 3.10 版中删除。

  • info ( )

    • 返回“受保护的” _info变量,该字典包含在消息目录文件中找到的元数据。
  • charset ( )

    • 返回消息目录文件的编码。
  • output_charset ( )

从 3.8 版开始不推荐使用,将在 3.10 版中删除。

  • set_output_charset(字符集)
    • 更改用于返回翻译后消息的编码。

从 3.8 版开始不推荐使用,将在 3.10 版中删除。

  • install(* names = None *)
    • 此方法将gettext()安装到内置名称空间中,并将其绑定到_

如果给出了* names *参数,则它必须是一个序列,其中除了_()之外还包含要在内置名称空间中安装的函数的名称。支持的名称是'gettext''ngettext''pgettext''npgettext''lgettext''lngettext'

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

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

  • 类别 gettext. GNUTranslations

    • 从 Base Class 实现中重写以下方法:
  • gettext(消息)

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

    • 对消息 ID 进行复数形式的查找。 单数用作消息 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 *)
    • 在目录中查找* context message * ID,并以 Unicode 字符串的形式返回相应的消息字符串。如果目录中没有* message * id 和* context 的条目,并且已设置回退,则将查找转发到回退的pgettext()方法。否则,返回 message * id。

3.8 版的新Function。

  • npgettext((* context singular plural n *)
    • 对消息 ID 进行复数形式的查找。 单数用作消息 ID,以便在目录中查找,而* n *用于确定使用哪种复数形式。

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

3.8 版的新Function。

  • lgettext(消息)

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

Warning

在 Python 3 中应避免使用这些方法。请参见lgettext()函数的警告。

从 3.8 版开始不推荐使用,将在 3.10 版中删除。

Solaris 消息目录支持

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

目录构造函数

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

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

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

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

国际化您的程序和模块

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

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

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

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

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

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

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çoisPinard)的程序 xpot 做类似的工作,可作为po-utils package的一部分使用。

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

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

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

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

本地化您的模块

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

假设您的模块称为“垃圾邮件”,并且该模块的各种自然语言翻译.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()

Deferred translations

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

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 程序,因为该参数不是字符串 Literals。

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

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 都pass使用-k命令行开关来支持此Function。这里N_()的选择完全是任意的;可能和MarkThisStringForTranslation()一样容易。

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.base_prefix/share/locale(请参见sys.base_prefix)。因此,始终最好在应用程序的开头使用明确的绝对路径调用bindtextdomain()
  • [2]