美文网首页Tornado 源码解读
Tornado 国际化(i18n)

Tornado 国际化(i18n)

作者: 人世间 | 来源:发表于2016-09-19 13:43 被阅读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

    相关文章

      网友评论

        本文标题:Tornado 国际化(i18n)

        本文链接:https://www.haomeiwen.com/subject/jvqnettx.html