Tornado 国际化(i18n)
以前玩游戏的时候,官方如果不出中文版,经常就寄希望于国内的大神们做汉化版。当时并不了解这种行为是软件的国际化。互联网扩张的几十年,网络服务已经不再是只针对一部分地区的访问用户,通常需要考虑到全球的用户,Goolge,Twitter之类国际化需求显而易见。正好目前有一个项目需要提供国际化版本,即提供给app的API返回的文案需要有中英文两种语言。由于API服务是使用tornado,并且Tornado也支持i18n国际化。
可惜tornado官网对于国际化的文档写得有点随意,使用方式还得边看源码才能实现。Tornado官网中介绍了两种使用国际化的方式,一种是使用po等翻译文件,另外一种是使用csv文件。下面就两种模式,做简单的介绍。文末提供了一些文件的gist地址。
po翻译模式
国际化的内容,通常是软件的文字,即对用户可以的文案。对于web,一种则是api的返回值中的文案,另外一种则是写在模板里,或者动态渲染到模板中的字串。两种场景都需要顾及。
项目结构
新建一个文件夹tornado-i18n
,搭建一个简单的tornado项目目录,具体目录如下:
☁ tornado-i18n tree
.
├── locales
│ └── en_US
│ └── LC_MESSAGES
│ ├── en_US.mo
│ └── en_US.po
├── main.py
└── templates
└── template.html
4 directories, 4 files
locales
文件夹所存放的就是翻译文件,其中en_US.po则是翻译源文件,en_US.mo则是根据en_US.po所编译生成的二进制翻译文件,tornado读取的翻译模板也是mo文件。po文件有一定的攥写规则,编译po文件的工具很多,我使用的是Poedit。除此之外,还有xgettext
和 gettext
。看了下文档,略感复杂。
撰写一个翻译po文件
po文件和pot文件都是用于翻译的源文件,后者是一个模板。po文件也是文本文件,大概内容如下:
en_US.po
msgid ""
msgstr ""
"Project-Id-Version: tornado-i18n\n"
"POT-Creation-Date: 2016-09-19 11:45+0000\n"
"PO-Revision-Date: 2016-09-19 10:21+0800\n"
"Last-Translator: Rsj217 <rsj217@gmail.com>\n"
"Language-Team: rsj217 <rsj217@gmail.com>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: zh_CN\n"
"X-Generator: Poedit 1.8.9\n"
"X-Poedit-SourceCharset: UTF-8\n"
msgid "你好 世界"
msgstr "Hello world"
msgid "你好,世界"
msgstr "Hello, world"
msgid "登录"
msgstr "Sign in"
msgid
表示原文,msgstr
表示译文。还有一些文件头,大概就是翻译作者或team的信息。撰写完毕翻译的字符串对之后,使用poedit编译即可生成mo文件。 poedit界面如下:
设置默认环境和翻译文件
接下来就是Tornado中调用了,tornado提供locales模块,用于读取编译后的mo文件,不同的机器默认的语言环境不一样,这里我们假设默认的为中文环境。使用tornado.locale.set_default_locale('zh_CN')
设置为默认的语言。其次通过tornado.locale.load_gettext_translations
方法加载翻译文件,该函数的第一个参数为locales
文件夹的绝对路径,第二个参数为翻译文件文件名。如果按照前面的目录结构组织文件,tornado就能通过locales文件下的LC_MESSAGES
找到en_US.mo
文件。
tornado.locale 载入的是mo文件,但是并不用写
.mo
这个扩展名。
语言选择
接口请求或者模板渲染的时候,具体渲染什么语言,可以通过客户端的请求参数或者服务的部署环境来定义。这里我们使用客户端的请求参数来指定渲染的语言。客户端参数 lang 如果为 zh(或者为空),则提供中文字符,如果为en
则提供英文字符。
class BaseHandler(tornado.web.RequestHandler):
def get_user_locale(self):
user_locale = self.get_argument('lang', None)
if user_locale == 'en':
return tornado.locale.get('en_US')
tornado.web.RequestHandler提供了一个get_user_locale方法,用于返回一个 locale对象,这个对象会读取相应的翻译文件。self.locale.translate方法则可以对字符串进行翻译。通常喜欢使用_
来标记翻译,因此将self.locale.translate绑定给_
。然后就能使用_(待翻译的字符串)
就能翻译啦。
翻译字串
翻译中文的时候,由于python2的编码问题,所有中文都必须使用unicode
,不然无法翻译。
class ApiHandler(BaseHandler):
def get(self, *args, **kwargs):
_ = self.locale.translate
text = (u"原文: {} <br /><br /> 译文: {} <p></p>"
u"原文: {} <br /><br /> 译文: {} <p></p>"
u"原文: {} <br /><br /> 译文: {} <p></p>").format(u"你好 世界", _(u"你好 世界"),
u"你好,世界", _(u"你好,世界"),
u"登录", _(u"登录"))
self.finish(text)
对于模板中使用也比较简单,直接使用_
函数即可:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Template</title>
</head>
<body>
<p><label>原文</label>:你好 世界</p>
<p><label>译文</label>:{{ _(u"你好 世界") }}</p>
<p><label>原文</label>:你好,世界</p>
<p><label>译文</label>:{{ _(u"你好,世界") }}</p>
<p><label>原文</label>:登录</p>
<p><label>译文</label>:{{ _(text) }}</p>
</body>
</html>
完整的代码如下:
main.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import os
import tornado.httpserver
import tornado.ioloop
import tornado.locale
import tornado.web
class BaseHandler(tornado.web.RequestHandler):
def get_user_locale(self):
user_locale = self.get_argument('lang', None)
if user_locale == 'en':
return tornado.locale.get('en_US')
class ApiHandler(BaseHandler):
def get(self, *args, **kwargs):
_ = self.locale.translate
text = (u"原文: {} <br /><br /> 译文: {} <p></p>"
u"原文: {} <br /><br /> 译文: {} <p></p>"
u"原文: {} <br /><br /> 译文: {} <p></p>").format(u"你好 世界", _(u"你好 世界"),
u"你好,世界", _(u"你好,世界"),
u"登录", _(u"登录"))
self.finish(text)
class TemplateHandler(BaseHandler):
def get(self, *args, **kwargs):
text = u"登录"
self.render('template.html', text=text)
class Application(tornado.web.Application):
def __init__(self):
super(Application, self).__init__(handlers=[
(r'/api', ApiHandler),
(r'/template', TemplateHandler),
],
template_path=os.path.join(os.path.dirname(__file__), 'templates'),
debug=True)
if __name__ == '__main__':
app = Application()
# 设置语言环境和翻译文件位置
i18n_path = os.path.join(os.path.dirname(__file__), 'locales')
tornado.locale.load_gettext_translations(i18n_path, 'en_US')
tornado.locale.set_default_locale('zh_CN')
server = tornado.httpserver.HTTPServer(app, xheaders=True)
server.listen(8000)
tornado.ioloop.IOLoop.current().start()
请求效果如下:
111.jpgCSV翻译模式
相比与po文件模式,po文件的生成和编译,都比较麻烦,CSV文件要简单很多,而且更灵活。csv的文件名和传给tornado.locale.get的参数一致即可。下面是csv模式的目录
☁ tornado-i18n tree
.
├── locales
│ ├── en_US.csv
│ ├── ja_JP.csv
│ └── zh_TW.csv
├── main.py
└── templates
└── template.html
2 directories, 5 files
csv翻译文件
csv的格式比较熟悉,使用""
和,
进行切分成一个个单元格。翻译文件中,每一行为一个翻译对,第一个单元格为原文,第二个单元格为译文:
en_US.csv
"你好 世界","Hello world"
"你好,世界","Hello,world"
"登录","Sign in"
默认环境配置与翻译环境
与po模式类似,csv模式也需要设置一个默认的语言环境,同时在服务启动的时候,载入指定位置的csv文件。只需要将locales文件夹绝对路径传给tornado.locale.load_translations
即可。
if __name__ == '__main__':
app = Application()
i18n_path = os.path.join(os.path.dirname(__file__), 'locales')
# tornado.locale.load_gettext_translations(i18n_path, 'en_US')
tornado.locale.load_translations(i18n_path)
tornado.locale.set_default_locale('zh_CN')
server = tornado.httpserver.HTTPServer(app, xheaders=True)
server.listen(8000)
tornado.ioloop.IOLoop.current().start()
get_user_locale 方法,再添加几个别的语言的选项,就能切换不同的语言了。
def get_user_locale(self):
user_locale = self.get_argument('lang', None)
if user_locale == 'en':
return tornado.locale.get('en_US')
elif user_locale == 'tw':
return tornado.locale.get('zh_TW')
elif user_locale == 'jp':
return tornado.locale.get('ja_JP')
具体的使用方式和模板都没有变,只是变更了服务初始化所在于的语言环境而已。完整的实现代码如下:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import os
import tornado.httpserver
import tornado.ioloop
import tornado.locale
import tornado.web
class BaseHandler(tornado.web.RequestHandler):
def get_user_locale(self):
user_locale = self.get_argument('lang', None)
if user_locale == 'en':
return tornado.locale.get('en_US')
elif user_locale == 'tw':
return tornado.locale.get('zh_TW')
elif user_locale == 'jp':
return tornado.locale.get('ja_JP')
class ApiHandler(BaseHandler):
def get(self, *args, **kwargs):
_ = self.locale.translate
text = (u"原文: {} <br /><br /> 译文: {} <p></p>"
u"原文: {} <br /><br /> 译文: {} <p></p>"
u"原文: {} <br /><br /> 译文: {} <p></p>").format(u"你好 世界", _(u"你好 世界"),
u"你好,世界", _(u"你好,世界"),
u"登录", _(u"登录"))
self.finish(text)
class TemplateHandler(BaseHandler):
def get(self, *args, **kwargs):
text = u"登录"
self.render('template.html', text=text)
class Application(tornado.web.Application):
def __init__(self):
super(Application, self).__init__(handlers=[
(r'/api', ApiHandler),
(r'/template', TemplateHandler),
],
template_path=os.path.join(os.path.dirname(__file__), 'templates'),
debug=True)
if __name__ == '__main__':
app = Application()
i18n_path = os.path.join(os.path.dirname(__file__), 'locales')
# tornado.locale.load_gettext_translations(i18n_path, 'en_US')
tornado.locale.load_translations(i18n_path)
tornado.locale.set_default_locale('zh_CN')
server = tornado.httpserver.HTTPServer(app, xheaders=True)
server.listen(8000)
tornado.ioloop.IOLoop.current().start()
最终效果显示如下:
222.jpg相关文件源码:
翻译文件: en_US.po
po模式: main.py
csv模式: main.py