Tornado 源码解读

Tornado 国际化(i18n)

2016-09-19  本文已影响1945人  人世间

以前玩游戏的时候,官方如果不出中文版,经常就寄希望于国内的大神们做汉化版。当时并不了解这种行为是软件的国际化。互联网扩张的几十年,网络服务已经不再是只针对一部分地区的访问用户,通常需要考虑到全球的用户,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。除此之外,还有xgettextgettext。看了下文档,略感复杂。

撰写一个翻译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界面如下:

00.png

设置默认环境和翻译文件

接下来就是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.jpg

CSV翻译模式

相比与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

上一篇下一篇

猜你喜欢

热点阅读