从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
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: [email protected]\n"
"POT-Creation-Date: 2015-03-29 22:46+0800\n"
"PO-Revision-Date: 2015-03-29 21:49+0800\n"
"Last-Translator: Ray <[email protected]>\n"
"Language-Team: zh <[email protected]>\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主题的文章"
编译资源
编译过程很简单,只需要执行以下指令 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的友人们。