23.1. gettext — 多语言国际化服务 — Python 文档
23.1. 获取文本 — 多语言国际化服务
gettext 模块为 Python 模块和应用程序提供国际化 (I18N) 和本地化 (L10N) 服务。 它支持 GNU gettext
消息目录 API 和更高级别的基于类的 API,后者可能更适合 Python 文件。 下面描述的接口允许您用一种自然语言编写模块和应用程序消息,并提供用于在不同自然语言下运行的翻译消息目录。
还提供了一些有关本地化 Python 模块和应用程序的提示。
23.1.1. GNU 获取文本应用程序接口
gettext 模块定义了以下 API,它与 GNU gettext API 非常相似。 如果您使用此 API,您将在全局范围内影响整个应用程序的翻译。 如果您的应用程序是单语的,并且语言的选择取决于用户的语言环境,那么这通常就是您想要的。 如果您正在本地化 Python 模块,或者您的应用程序需要即时切换语言,您可能希望改用基于类的 API。
- gettext.bindtextdomain(domain, localedir=None)
将 域 绑定到语言环境目录 localedir。 更具体地说,gettext 将使用路径(在 Unix 上)为给定域查找二进制
.mo
文件:localedir/language/LC_MESSAGES/domain.mo
,其中搜索 languages对于环境变量LANGUAGE
、LC_ALL
、LC_MESSAGES
和 [X227] ]LANG
分别。如果省略 localedir 或
None
,则返回 域 的当前绑定。 1
- gettext.bind_textdomain_codeset(domain, codeset=None)
- 将域绑定到codeset,改变lgettext()、ldgettext()、返回的字节串的编码lngettext() 和 ldngettext() 函数。 如果省略 codeset,则返回当前绑定。
- gettext.textdomain(domain=None)
- 更改或查询当前全局域。 如果domain为
None
,则返回当前全局域,否则全局域设置为domain,返回。
- gettext.gettext(message)
- 根据当前全局域、语言和区域设置目录返回 message 的本地化翻译。 该函数在本地命名空间中通常别名为
_()
(参见下面的示例)。
- gettext.dgettext(domain, message)
- 类似于 gettext(),但在指定的 域 中查找消息。
- gettext.ngettext(singular, plural, n)
类似于 gettext(),但考虑复数形式。 如果找到翻译,将复数公式应用于 n,并返回结果消息(某些语言有两个以上的复数形式)。 如果没有找到翻译,如果 n 为 1,则返回 singular; 否则返回 复数 。
复数公式取自目录标题。 它是一个具有自由变量 n 的 C 或 Python 表达式; 表达式计算为目录中复数的索引。 请参阅 GNU gettext 文档 ,了解
.po
文件中使用的精确语法和各种语言的公式。
- gettext.dngettext(domain, singular, plural, n)
- 类似于 ngettext(),但在指定的 域 中查找消息。
- gettext.lgettext(message)
- gettext.ldgettext(domain, message)
- gettext.lngettext(singular, plural, n)
- gettext.ldngettext(domain, singular, plural, n)
等价于不带
l
前缀的对应函数(gettext()、dgettext()、ngettext()和dngettext( )),但如果没有使用 bind_textdomain_codeset() 显式设置其他编码,则翻译将作为在首选系统编码中编码的字节字符串返回。警告
在 Python 3 中应该避免使用这些函数,因为它们返回编码的字节。 最好使用返回 Unicode 字符串的替代方法,因为大多数 Python 应用程序都希望将人类可读的文本作为字符串而不是字节进行操作。 此外,如果翻译的字符串存在编码问题,您可能会收到意外的 Unicode 相关异常。
l*()
函数在未来的 Python 版本中可能会由于其固有的问题和限制而被弃用。
请注意,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 如果没有给出 languages,则搜索以下环境变量:
LANGUAGE
, [ X136X]、LC_MESSAGES
和LANG
。 第一个返回非空值的值用于 languages 变量。 环境变量应包含以冒号分隔的语言列表,它将在冒号上拆分以生成预期的语言代码字符串列表。find() 然后扩展和规范化语言,然后遍历它们,搜索由这些组件构建的现有文件:
localedir/language/LC_MESSAGES/domain.mo
find() 返回存在的第一个这样的文件名。 如果没有找到这样的文件,则返回
None
。 如果给出 all,它会返回所有文件名的列表,按照它们在语言列表或环境变量中出现的顺序。
- gettext.translation(domain, localedir=None, languages=None, class_=None, fallback=False, codeset=None)
返回基于 domain、localedir 和 languages 的
Translations
实例,这些实例首先传递给 find() ] 获取相关.mo
文件路径的列表。 缓存具有相同.mo
文件名的实例。 实例化的实际类要么是 class_(如果提供),否则是 GNUTranslations。 类的构造函数必须采用单个 文件对象 参数。 如果提供,codeset 将更改用于编码 lgettext() 和 lngettext() 方法中已翻译字符串的字符集。如果找到多个文件,则使用较晚的文件作为较早文件的后备。 为了允许设置回退,copy.copy() 用于从缓存中克隆每个翻译对象; 实际实例数据仍与缓存共享。
如果没有找到
.mo
文件,如果 fallback 为 false(这是默认值),此函数会引发 OSError,并返回一个 NullTranslations 实例如果 fallback 为真。
- gettext.install(domain, localedir=None, codeset=None, names=None)
这会在 Python 的内置命名空间中安装函数
_()
,基于传递给函数 的 domain、localedir 和 codeset翻译()。names参数请参见翻译对象的install()方法的说明。
如下所示,您通常通过将它们包装在对
_()
函数的调用中来标记应用程序中的候选字符串,如下所示:print(_('This string will be translated.'))
为方便起见,您希望将
_()
函数安装在 Python 的内置命名空间中,以便在应用程序的所有模块中轻松访问它。
23.1.2.1. 这空翻译班级
翻译类是真正实现将原始源文件消息字符串翻译成翻译消息字符串的类。 所有翻译类使用的基类是NullTranslations; 这提供了可用于编写自己的专用翻译类的基本接口。 这里是NullTranslations
的方法:
- class gettext.NullTranslations(fp=None)
采用可选的 文件对象 fp,它被基类忽略。 初始化由派生类设置的“受保护”实例变量 _info 和 _charset,以及通过 add_fallback()[ 设置的 _fallback X184X]。 然后,如果 fp 不是
None
,它会调用self._parse(fp)
。- _parse(fp)
在基类中没有操作,此方法获取文件对象 fp,并从文件中读取数据,初始化其消息目录。 如果您的消息目录文件格式不受支持,则应覆盖此方法以解析您的格式。
- add_fallback(fallback)
添加 fallback 作为当前翻译对象的回退对象。 如果翻译对象无法为给定消息提供翻译,则它应该咨询回退。
- gettext(message)
如果已设置回退,则将
gettext()
转发到回退。 否则,返回 message。 在派生类中重写。
- ngettext(singular, plural, n)
如果已设置回退,则将
ngettext()
转发到回退。 否则,如果 n 为 1,则返回 singular; 否则返回 复数 。 在派生类中重写。
- lgettext(message)
- lngettext(singular, plural, n)
等效于 gettext() 和 ngettext(),但如果没有使用 set_output_charset() 显式设置编码,则翻译将作为首选系统编码中编码的字节字符串返回。 在派生类中重写。
警告
在 Python 3 中应该避免使用这些方法。 请参阅 lgettext() 函数的警告。
- info()
返回“受保护的”
_info
变量。
- charset()
返回消息目录文件的编码。
- output_charset()
返回用于在 lgettext() 和 lngettext() 中返回已翻译消息的编码。
- set_output_charset(charset)
更改用于返回已翻译消息的编码。
- install(names=None)
此方法将 gettext() 安装到内置命名空间中,并将其绑定到
_
。如果给出了 names 参数,它必须是一个序列,除了
_()
之外,还包含要安装在 builtins 命名空间中的函数的名称。 支持的名称为'gettext'
、'ngettext'
、'lgettext'
和'lngettext'
。请注意,这只是使
_()
功能可用于您的应用程序的一种方式,尽管是最方便的方式。 因为它会影响整个应用程序的全局,特别是内置的命名空间,本地化的模块不应该安装_()
。 相反,他们应该使用此代码使_()
可用于他们的模块:import gettext t = gettext.translation('mymodule', ...) _ = t.gettext
这将
_()
仅放在模块的全局命名空间中,因此仅影响该模块内的调用。
23.1.2.2. 这 GNU翻译班级
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
。 如果指定了字符集编码,则从目录中读取的所有消息 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}
- lgettext(message)
- lngettext(singular, plural, n)
等效于 gettext() 和 ngettext(),但如果没有使用 set_output_charset() 显式设置编码,则翻译将作为首选系统编码中编码的字节字符串返回。
警告
在 Python 3 中应该避免使用这些方法。 请参阅 lgettext() 函数的警告。
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 程序提供多语言消息,您需要执行以下步骤:
- 通过专门标记可翻译字符串来准备您的程序或模块
- 在标记的文件上运行一套工具以生成原始消息目录
- 创建消息目录的特定语言翻译
- 使用 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)编写的代码,以查找标记为可翻译的字符串。 Babel 是一个 Python 国际化库,包括一个 pybabel
脚本来提取和编译消息目录。 François Pinard 的名为 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
文件发回,该文件使用 msgfmt 程序编译成机器可读的 .mo
二进制目录文件。 .mo
文件由 gettext 模块用于运行时的实际翻译处理。
您如何在代码中使用 gettext 模块取决于您是对单个模块还是整个应用程序进行国际化。 接下来的两节将讨论每种情况。
23.1.3.1. 本地化你的模块
如果您正在本地化您的模块,您必须注意不要进行全局更改,例如 到内置命名空间。 您不应使用 GNU gettext
API,而应使用基于类的 API。
假设您的模块被称为“垃圾邮件”,并且该模块的各种自然语言翻译 .mo
文件位于 GNU gettext 格式的 /usr/share/locale
中。 以下是您将放在模块顶部的内容:
import gettext
t = gettext.translation('spam', '/usr/share/locale')
_ = t.gettext
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 extract
和 xpot 都通过使用 -k
命令行开关来支持这一点。 这里N_()
的选择完全是任意的; 它也可以很容易地变成 MarkThisStringForTranslation()
。
23.1.4. 致谢
以下人员为本模块的创建贡献了代码、反馈、设计建议、以前的实现和宝贵的经验:
- 彼得·芬克
- 詹姆斯·亨斯屈奇
- 胡安·大卫·伊巴涅斯·帕洛马尔
- 马克-安德烈·伦堡
- 马丁·冯·洛维斯
- 弗朗索瓦·皮纳尔
- 巴里华沙
- 古斯塔沃·尼迈耶
脚注
- 1
- 默认的语言环境目录是系统相关的; 例如,在 RedHat Linux 上它是
/usr/share/locale
,但在 Solaris 上它是/usr/lib/locale
。 gettext 模块不会尝试支持这些依赖于系统的默认值; 相反,它的默认值为sys.prefix/share/locale
。 因此,最好在应用程序开始时使用显式绝对路径调用 bindtextdomain()。 - 2
- 请参阅上面 bindtextdomain() 的脚注。