首先i18n的文档介绍可以参照官方说明:①使用介绍https://docs.openstack.org/oslo.i18n/ocata/usage.html。②类型介绍https://docs.openstack.org/oslo.i18n/latest/reference/index.html#oslo_i18n.translate
本篇介绍操作步骤及实现i18n的两种方式(①是直接使用gettext模块生成一个翻译函数,输出国际化结果;②可以理解为一种懒加载模式,如果你直接在代码中导入_):
①准备po和mo文件
介绍:PO 是 Portable Object (可移植对象)的缩写形式;MO 是 Machine Object (机器对象) 的缩写形式。PO 文件是面向翻译人员的、提取于源代码的一种资源文件。当软件升级的时候,通过使用 gettext 软件包处理 PO 文件,可以在一定程度上使翻译成果得以继承,减轻翻译人员的负担。MO 文件是面向计算机的、由 PO 文件通过 gettext 软件包编译而成的二进制文件。程序通过读取 MO 文件使自身的界面转换成用户使用的语言。
po文件和mo文件通过msgfmt工具和pygettext转化。
1)创建po文件:在Python安装目录下的 ./Tools/i18n/ 中找到pygettext.py运行之,生成翻译文件模版messages.pot。内容如下:
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR , YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2018-03-13 11:01+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=cp936\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
将charset改为charset=UTF-8,其余的可以不用改动。其中的msgid为键值,对应你程序里写的文本,如:_("New File"),而msgstr为翻译后的值。添加翻译语句:
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR , YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2018-03-13 11:01+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
msgid " Hello world!"
msgstr "世界你好!"
msgid " Python is a good Language."
msgstr "Python 是门好语言."
保存该文件,并重命名为messages.po
2)创建mo文件:在Python安装目录下的 ./Tools/i18n/ 中找到msgfmt.py,在Python模式下,注意messages.po存在的路径:>>>python msgfmt.py messages.po
将生成一个messages.mo文件。
3)建立翻译文件路径:在src目录下创建/locale/zh_CN/LC_MESSAGES/,将messages.po和messages.mo文件拷贝其中。
即:./src/locale/zh_CN/LC_MESSAGES/messages.po
./src/locale/zh_CN/LC_MESSAGES/messages.mo
4)建立demo.py,Python通过gettext模块支持国际化(i18n),可以实现程序的多语言界面的支持,如下引入gettext模块:
# -*- coding: utf-8 -*-
#!/usr/bin/env python
import gettext
gettext.install('messages', './locale', codeset=False)
gettext.translation('messages', './locale', languages=['zh_CN']).install(True)
print(_("Hello world!"))
print(_("Python is a good Language."))
一切工作准备就绪,运行demo.py,查看是否输出中文:世界你好! Python是门好语言.
注意: # -*- coding: utf-8 -*- 一定要写在前两行,写第三行都不会生效
②另外可以借助工具生成.po和.mo文件,比如Poedit、Zenata等。以下介绍Poedit:
下载并安装Poedit,打开Poedit,上方工具栏File,新建,在弹出的弹框填入名称messages,确定后按ctrl+s,就创建了messages.po模板文件。将要翻译的语言写入,如下:
# Copyright (C) 2018 THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# Automatically generated, 2018.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-02-26 12:20+0800\n"
"PO-Revision-Date: 2018-02-26 15:58+0800\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: zh_CN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.0.6\n"
msgid "Hello world!"
msgstr "世界你好!"
msgid "Python is a good Language."
msgstr "Python是门好语言."
保存后,用Poedit打开po文件,并在File中的下拉框“编译为mo文件”,生成messages.mo文件。生成两个文件后拷贝在①中的/locale/zh_CN/LC_MESSAGES/。
1)重写i18n的实现。i18n.py:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import os
import gettext
import threading
localedir = os.path.join(os.path.dirname(__file__), 'locale')
domain = 'messages'
threadLocalData = threading.local()
threadLocalData.locale = 'en_US'
# find out all supported locales in locale directory
locales = []
for dirpath, dirnames, filenames in os.walk(localedir):
for dirname in dirnames:
locales.append(dirname)
break
AllTranslations = {}
for locale in locales:
AllTranslations[locale] = gettext.translation(domain, localedir, [locale])
def gettext(message):
return AllTranslations[ threadLocalData.locale ].gettext(message)
def ugettext(message):
return AllTranslations[ threadLocalData.locale ].ugettext(message)
def ngettext(singular, plural, n):
return AllTranslations[ threadLocalData.locale ].ngettext(singular, plural, n)
def ungettext(singular, plural, n):
return AllTranslations[ threadLocalData.locale ].ungettext(singular, plural, n)
def setLocale(locale):
if locale in locales:
threadLocalData.locale = locale
if __name__ == '__main__':
#for test purpose
for dirpath, dirnames, filenames in os.walk(localedir):
for dirname in dirnames:
print(dirname)
break
demo2.py:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import i18n
if __name__ == '__main__':
i18n.setLocale("zh_CN")
print(i18n.gettext("Hello world!"))
print(i18n.gettext("Python is a good Language."))
保存,运行demo2.py,查看结果!
③国际化支持在OpenStack各项目中使用频繁,oslo.i18n为了使用更加的方便,定义了_表示一个TranslatorFactory对象的primary()方法,因此在使用国际化之前,首先需要导入_;另外,在为日志支持国际化时也特意定义了_LI、_LW、_LE、_LC等不同级别的国际化支持方法。官方的i18n.py实现(稍加改动):
$ pip install oslo.i18n oslo.log
import oslo_i18n
DOMAIN = "nova"
_translators = oslo_i18n.TranslatorFactory(domain=DOMAIN)
# The primary translation function using the well-known name "_"
_ = _translators.primary
# The contextual translation function using the name "_C"
# requires oslo.i18n >=2.1.0
_C = _translators.contextual_form
# The plural translation function using the name "_P"
# requires oslo.i18n >=2.1.0
_P = _translators.plural_form
# Translators for log levels.
#
# The abbreviated names are meant to reflect the usual use of a short
# name like '_'. The "L" is for "log" and the other letter comes from
# the level.
_LI = _translators.log_info
_LW = _translators.log_warning
_LE = _translators.log_error
_LC = _translators.log_critical
def get_available_languages():
return oslo_i18n.get_available_languages(DOMAIN)
_ 是用于字符串显示的国际化的,简单理解解释显示成个个国家自己的语言。
_LI = _translators.log_info
_LW = _translators.log_warning
_LE = _translators.log_error
_LC = _translators.log_critical
_LI、_LW、_LE、_LC
用于显示不同的log
接下来就是引用这些标记函数:
demo3.py:
from i18n import _, _LW, _LE
from oslo_log import log
# ...
LOG = log.getLogger(__name__)
variable = "openstack"
LOG.warning(_LW('warning message: %s'), variable)
# ...
try:
# ...
except AnException1:
# Log only
LOG.exception(_LE('exception message'))
except AnException2:
# Raise only
raise RuntimeError(_('exception message'))
else:
# Log and Raise
msg = _('Unexpected error message')
LOG.exception(msg)
raise RuntimeError(msg)
如上面的一段代码中,Nova组件分别使用_LE,_LW方法对一条异常信息和警示信息进行了国际化打印的支持。当然在③中的domain='nova',所以在/locale/zh_CN/LC_MESSAGES/目录下的翻译文件应为nova.po和nova.mo
关于nova的i18n解释见:https://docs.openstack.org/nova/pike/reference/i18n.html
Note:
调用python安装目录的 Tools/i18n/pygettext.py抽取所需翻译的模板
>>> pygettext.py path/to/yourfile.py
将生成一个名为messages.pot的文件
2.生成模板文件后,修改这个模板文件,其中的msgid为键值,对应你程序里写的文本,如:_("New File"),而msgstr为翻译后的值。还有就是注意修改文件头部分Content-Type的charset为合适的编码,比如utf8
3.编写好模板后,把扩展名修改为.po,运行Tools/i18n/msgfmt.py,生成二进制的资源文件
>>> python msgfmt.py messages.po
将生成一个名为messages.mo的文件
4.把这个mo文件放在正确的位置.
比如你在程序中是这样写的:
gettext.install('messages', './locale', codeset=True)
gettext.translation('messages', './locale', languages=['cn']).install(True)
那么你的程序目录下需要存在./local/zh_CN/LC_MESSAGES/messages.mo
这样程序启动时就会读取这个资源文件,替换对应的文本,实现国际化了。
注意:如果使用utf格式保存,po文件不能有BOM头。cn目录是所对应的语言,LC_MESSAGES目录是gettext.py里要求的,mo文件必须和所定义的域同名,见
gettext.py的mofile = os.path.join(localedir, lang, 'LC_MESSAGES', '%s.mo' % domain)