首先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 <EMAIL@ADDRESS>, 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 <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <[email protected]>\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 <EMAIL@ADDRESS>, 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 <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <[email protected]>\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)