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

  将域绑定到语言环境目录localedir。More concretely, `gettext` will look for binary `.mo` files for the given domain using the path (on Unix): `localedir/language/LC_MESSAGES/domain.mo`, where languages is searched for in the environment variables `LANGUAGE`, `LC_ALL`, `LC_MESSAGES`, and `LANG` respectively.

  如果省略localedir或`None`，则返回域的当前绑定。[1]

`gettext.``bind_textdomain_codeset`(domain, codeset=None)

  将域绑定到代码集，更改由`gettext()`函数系列返回的字符串的编码。如果省略codeset，则返回当前绑定。

`gettext.``textdomain`(domain=None)

  更改或查询当前全局域。如果域为`None`，则返回当前全局域，否则将全局域设置为域，返回。

`gettext.``gettext`(message)

  根据当前的全局域，语言和区域设置目录返回消息的本地化转换。此函数通常在本地命名空间中以`_()`别名（参见下面的示例）。

`gettext.``lgettext`(message)

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

`gettext.``dgettext`(domain, message)

  像`gettext()`，但查看指定的域中的消息。

`gettext.``ldgettext`(domain, message)

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

`gettext.``ngettext`(singular, plural, n)

  像`gettext()`，但是考虑复数形式。如果找到翻译，则将复数公式应用于n，并返回结果消息（某些语言具有多个复数形式）。如果未找到翻译，则如果n为1，则返回奇异否则返回多个。

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

`gettext.``lngettext`(singular, plural, n)

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

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

  像`ngettext()`，但查看指定的域中的消息。

`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`文件搜索算法。它需要一个域，与`textdomain()`相同。可选localedir如`bindtextdomain()`可选languages是字符串列表，其中每个字符串都是语言代码。

  如果未指定localedir，则使用缺省系统区域设置目录。[2] t>如果未指定语言，则会搜索以下环境变量： `LANGUAGE`， `LC_ALL`， `LC_MESSAGES`和 `LANG`。第一个返回非空值的变量用于语言变量​​。环境变量应该包含一个冒号分隔的语言列表，它将在冒号上分割，以产生预期的语言代码字符串列表。

  `find()`然后展开并规范化语言，然后遍历它们，搜索由这些组件构成的现有文件：

  `localedir / language / LC_MESSAGES / domain .mo`

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

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

  返回基于域，localedir和语言的`Translations`实例，这些实例首先传递给`find()`获取关联的`.mo`文件路径的列表。具有相同`.mo`文件名的实例将被缓存。实际类实例是class _（如果提供），否则`GNUTranslations`。类的构造函数必须采用单个file object参数。如果提供，codeset将更改用于在`lgettext()`和`lngettext()`方法中对翻译后的字符串进行编码的字符集。

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

  如果找不到`.mo`文件，则此函数引发`OSError`；如果fallback为false（这是默认值），并返回`NullTranslations`实例if fallback为true。

  在版本3.3中更改： `IOError`已升级，而不是`OSError`。

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

  This installs the function `_()` in Python’s builtins namespace, based on domain, localedir, and codeset which are passed to the function `translation()`.

  对于名称参数，请参阅翻译对象的`install()`方法的描述。

  如下所示，通常在应用程序中标记作为翻译候选字符串的字符串，方法是将它们包含在对`_()`函数的调用中，如下所示：

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

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

### 23.1.2.1. `NullTranslations`类

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

class `gettext.``NullTranslations`(fp=None)

  使用可选的file object fp，这被基类忽略。Initializes “protected” instance variables _info and _charset which are set by derived classes, as well as _fallback, which is set through `add_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 - 样式`键： 值 并应包含`Project-Id-Version`键。`如果找到键`Content-Type`，则`charset`属性用于初始化“protected”`_charset`实例变量，默认为`None`（如果找不到）。如果指定了字符集编码，则从目录读取的所有消息标识和消息字符串都将使用此编码转换为Unicode，否则假定为ASCII编码。

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

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

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

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

`GNUTranslations.``gettext`(message)

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

`GNUTranslations.``lgettext`(message)

  等效于`gettext()`，但是如果未使用`set_output_charset()`

`GNUTranslations.``ngettext`(singular, plural, n)

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

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

  这里是一个例子：

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

在此示例中，字符串`'写入 a 日志 消息' ，而字符串`'mylog.txt'`和`'w'`则不是。`

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

（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应用程序国际化。）

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

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

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

### 23.1.3.1. Localizing your module

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

假设您的模块称为“垃圾邮件”，并且该模块的各种自然语言翻译`.mo`文件位于GNU gettext中的`/usr/share/locale` >格式。这里是你将放在模块的顶部：

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

### 23.1.3.2. 本地化您的应用程序

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

在简单的情况下，您只需要将以下位代码添加到应用程序的主驱动程序文件中：

    import gettext
    gettext.install('myapplication')

如果需要设置locale目录，可以将其传递到`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_()`的可翻译字符串。xgettext，pygettext，`pybabel 提取`和> all通过使用`-k`命令行开关支持此功能。这里的`N_()`的选择是完全任意的；它可以很容易地是`MarkThisStringForTranslation()`。

## 23.1.4. 致谢

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

- 彼得·福克
- 詹姆斯亨斯特里奇
- Juan DavidIbáñezPalomar
- Marc-AndréLemburg
- Martin vonLöwis
- FrançoisPinard
- 巴里华沙
- 古斯塔沃尼迈耶

脚注

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

[2] | 请参阅上面`bindtextdomain()`的脚注。