從C#世界邁入python總是令人有一種如釋重負之感,同樣的效果同樣的功能,只需要付出1/10不到的代價,可能正是Python所倡導的簡美哲學所帶來的威力。
我還深深地記得在ASP.NET中做全球化的經歷,可謂是苦不堪言。由於 .net 是采用xml格式的資源文件作為資源承載格式,導致對全球化資源的引用就必須要采用嚴格的合乎c#命名規范。這樣一來在資源的使用過程中就增加“命名”這一復雜度。以前從不認為有什么問題,不過一但轉譯成多國版本或者要對資源文件進行更新就會面臨着巨大的工作量。
而且ASP.NET官方推薦的全球化做法則更是坑人,簡直就是將“Quickly and duty”發揮到了極至,一但做了也只好陷入永不休止似的維護地獄之中。
還是直到在接觸 Web2Py 時才發現他們對全球化的做法有點像樣了,Web2Py中沒有了中間關鍵字命名,而是將自然用詞直接作為資源的搜索關鍵字,這對於長期用ASP.NET開發全球化項目的我無疑是一咱腦洞大開的過程。而且,制作默認語言模板的工作量還是巨大的這個過程仍然需要手工處理,因此我一直在尋找更好的應用方案。
直至在Flask 中遇到了 Flask-Babel 這個插件。花了10多分鍾就能上手了,看到它的用法簡直是讓人興奮不已——簡單、省事。
Flask-Babel 就是在Flask中對Babel的插件,它有幾個很讓人印象深刻的特色:
- 自動從代碼、頁面中搜索並提取出使用全球化資源的關鍵字並生成默認字典
- 提供一系列命令行工具去同步和翻譯全球化資源文件
- 資源文件可被編譯為通用的
*.mo
格式,能通過其它的編輯器來維護字典 - 代碼與頁面可以用一種方法訪問資源
gettext()
,_()
- 能自動切換當前區域化語言
Flask-Babel 的用法
將 Flask-Babel 加載至 Flask 的應用上下文
from flask import Flask
from flask.ext.babel import Babel
app = Flask(__name__)
app.config.from_pyfile('babel.cfg')
babel = Babel(app)
babel.cfg
配置文件
babel.cfg
是一個放置於Flask項目根目錄下的Babel配置文件,它是一個固定的配置,以下是官方推薦的寫法:
[python: **.py]
[jinja2: **/templates/**.html]
extensions=jinja2.ext.autoescape,jinja2.ext.with_
如果采用了 Flask-Assets 插件的話需要修改一下 extensions
的設置
[python: **.py]
[jinja2: **/templates/**.html]
extensions=jinja2.ext.autoescape,jinja2.ext.with_,webassets.ext.jinja2.AssetsExtension
gettext()
/_()
接下來就可以在python代碼內或者jinia頁面內使用 gettext()
方法引用全球化資源。其實此時我們並沒有任何的資源文件,但這正是Babel最吸引人的地方——先使用再生成資源。
在 python 代碼內可以這樣使用 gettext()
from flask import Flask, render_template
from flaskext.babel import Babel, gettext as _
app = Flask(__name__)
app.config['BABEL_DEFAULT_LOCALE'] = 'zh'
babel = Babel(app)
@app.route('/')
def hello():
s = _("Saturday")
return render_template('index.html', day=day)
if __name__ == '__main__':
app.debug = True
app.run()
以上代碼中_("Saturday")
就是從資源中獲取名為Saturday
的資源,如果沒有資源文件或者沒有找到對應的區域就會直接輸出"Saturday"
然后就是在 jinja 模板內使用:
<p>{{ _("Hello, world!") }}</p>
<p>{{ _("It's %(day)s today", day=day) }}</p>
同理,在其它的代碼和模塊內就是以這兩種方式使用全球化資源。
生成翻譯模板
這是很重要的一步,也是Babel最省時省力的一步。Babel可以從代碼和模板中抽出用了gettext()
的所有的資源名並生成到默認語言模板內。生成這個模板后就可以翻譯成各種需要的本地化語言。
只需要在命令行內鍵入以下命令
$ pybabel extract -F babel.cfg -o messages.pot .
就會在Flask的項目根目錄下生成 messages.pot
的默認語言模板
翻譯模板
接下來就是從默認模板翻譯成指定區域語言的資源文件了,也是通過命令行處理:
$ pybabel init -i messages.pot -d translations -l zh
這個指令的執行結果是按照messages.pot
將 中文(zh)資源文件(message.po
)生成至 translations
目錄。
目錄結構如下:
.
├── babel.cfg
├── messages.pot
├── static
├── templates
└── translations
└── zh
└── LC_MESSAGES
└─ message.po
message.po
就是目標資源文件,現在就可以打開並進行相關的翻譯工作了。*.po
文件只是一個文本可以直接編輯,或者可以選擇一些專用的po編程工具也成。我比較推薦使用POEdit
注 當指區域時需要使用區域簡寫而不是區域全名,如果指定
zh-CN
(簡體中文)的話就直接采用zh
否則指令會出錯。
message.po
文件
以下是 message.op
的內容
# Chinese translations for PROJECT.
# Copyright (C) 2015 ORGANIZATION
# This file is distributed under the same license as the PROJECT project.
# Ray <csharp2002@hotmail>, 2015.
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: csharp2002@hotmail.com\n"
"POT-Creation-Date: 2015-03-29 22:46+0800\n"
"PO-Revision-Date: 2015-03-29 21:49+0800\n"
"Last-Translator: Ray <csharp2002@hotmail.com>\n"
"Language-Team: zh <LL@li.org>\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 1.3\n"
#: views.py:103
#,fuzzy, python-format
msgid "Articles tagged with:%(value)s"
msgstr "標記%(value)s主題的文章"
注:
,fuzzy
這個關鍵字,如果需要編譯資源文件成為*.mo
的話則需要將它刪除,否則資源文件編譯器會直接忽略掉整個資源文件而不進行編譯。
編譯資源
編譯過程很簡單,只需要執行以下指令 translations
下所有的 *.po
文件就會被編譯成二進制的 *.mo
資源文件。
$ pybabel compile -d translations
更新默認模板
這可謂是 Babel 一個很為開發者着想的功能,因為我們的程序資源必定是需要變更與維護的,自然而然地資源文件的內容必定會有增減。當我們翻譯了N種語言副本之后如果沒有相關工具而是由手工來做的話那將是一種極為可怕的工作過程。幸運的是我們只需要執行以下的指令,babel將為更新默認模板和所有從此模板生成的所有資源文件的內容:
$ pybabel update -i messages.pot -d translations
區域切換
默認情況下 Flask-Babel 會讀取 flask.g.lang
自動切換當前請求上下文使用的語言區域。但在很多應用場景下我們需要手工改變當前的區域語言,這種情況下我們就需要增加一個 get_local()
函數:
from flask import g, request
@babel.localeselector
def get_locale():
# 如果在g對象內有登入的用戶對象則從用戶對象中讀取 locale 區域信息
user = getattr(g, 'user', None)
if user is not None:
return user.locale
# 此方法只需要返會一個區域字符串
return request.accept_languages.best_match(['de', 'fr', 'en'])
@babel.timezoneselector
def get_timezone():
"""此函數與 get_locale 類似,只是向babel提供獲取時區的設置"""
user = getattr(g, 'user', None)
if user is not None:
return user.timezone
當提供這兩個函數之后,在調用 gettext
時 Babel 會自動調用他們。這里是通過裝飾器 @babel.localeselector
和 @babel.timezoneselector
實現類似重寫的功能,但這個寫法代碼量會比重寫類更少。
小結
當然,Babel 提供的API不止本文中的這幾個,如果需要更詳細地了解可以仔細地閱讀 Flask-Babel 的文檔。在這里我旨在記錄 Babel 的最常規的用法以作備忘同時也分享給更多正在使用Flask的友人們。