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。除此之外,還有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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 9.2 添加國際化和本地化 Django提供了完整的國際化和本地化支持。它允許你把應用翻譯為多種語言,它會處理特定...
    lakerszhy閱讀 1,174評論 0 1
  • 譯者說 Tornado 4.3于2015年11月6日發布,該版本正式支持Python3.5的async/await...
    TaoBeier閱讀 2,013評論 1 7
  • 第一章 引子 蒼冥大陸的南方小鎮“米多麗”,這里以盛產一種稀有的...
    白月櫻閱讀 447評論 7 1
  • 一個男人對于女生怎么樣才能算是紅顏,互相傾盡所有事情這就算知己了嗎?有些人說紅顏知己當然要長得漂亮能夠訴說心事的就...
    世傾簡艾閱讀 256評論 0 0
  • 在科技加速發展的時代,公司及個人的競爭越來越激烈。最直觀的證據是,公司的平均壽命在降低,人們變換職業的頻率在增高。...
    馬烈視界閱讀 127評論 0 0