Flask 开发全球化应用

C#世界迈入python老是使人有一种如释重负之感,一样的效果一样的功能,只须要付出1/10不到的代价,可能正是Python所倡导的简美哲学所带来的威力。html

我还深深地记得在ASP.NET中作全球化的经历,可谓是苦不堪言。因为 .net 是采用xml格式的资源文件做为资源承载格式,致使对全球化资源的引用就必需要采用严格的合乎c#命名规范。这样一来在资源的使用过程当中就增长“命名”这一复杂度。之前从不认为有什么问题,不过一但转译成多国版本或者要对资源文件进行更新就会面临着巨大的工做量。
并且ASP.NET官方推荐的全球化作法则更是坑人,简直就是将“Quickly and duty”发挥到了极至,一但作了也只好陷入永不休止似的维护地狱之中。python

仍是直到在接触 Web2Py 时才发现他们对全球化的作法有点像样了,Web2Py中没有了中间关键字命名,而是将天然用词直接做为资源的搜索关键字,这对于长期用ASP.NET开发全球化项目的我无疑是一咱脑洞大开的过程。并且,制做默认语言模板的工做量仍是巨大的这个过程仍然须要手工处理,所以我一直在寻找更好的应用方案。web

直至在Flask 中遇到了 Flask-Babel 这个插件。花了10多分钟就能上手了,看到它的用法简直是让人兴奋不已——简单、省事。django

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配置文件,它是一个固定的配置,如下是官方推荐的写法:flask

[python: **.py]
[jinja2: **/templates/**.html]
extensions=jinja2.ext.autoescape,jinja2.ext.with_

若是采用了 Flask-Assets 插件的话须要修改一下 extensions 的设置c#

[python: **.py]
[jinja2: **/templates/**.html]
extensions=jinja2.ext.autoescape,jinja2.ext.with_,webassets.ext.jinja2.AssetsExtension

gettext()/_()

接下来就能够在python代码内或者jinia页面内使用 gettext() 方法引用全球化资源。其实此时咱们并无任何的资源文件,但这正是Babel最吸引人的地方——先使用再生成资源。bash

在 python 代码内能够这样使用 gettext()babel

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"app

而后就是在 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的友人们。

相关文章
相关标签/搜索