以前玩游戲的時候,官方如果不出中文版,經常就寄希望于國內的大神們做漢化版。當時并不了解這種行為是軟件的國際化。互聯網擴張的幾十年,網絡服務已經不再是只針對一部分地區的訪問用戶,通常需要考慮到全球的用戶,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()
請求效果如下:
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()
最終效果顯示如下:
相關文件源碼:
翻譯文件: en_US.po
po模式: main.py
csv模式: main.py