一个强大的工具一般都支持扩展或插件的开发功能,来允许第三方通过开发新扩展或插件,扩充工具本身功能,并可以贡献给社区。Jinja2也不例外,Jinja2本身提供了一部分扩展,你可以在程序中启用。同时,你还可以创建自己的扩展,来扩充模板引擎功能。本篇会先介绍Jinja2自带的扩展”jinja2.ext.i18n”的使用,自定义扩展的开发会放在下一篇阐述。

系列文章

  1. Flask中Jinja2模板引擎详解(一)-控制语句和表达式
  2. Flask中Jinja2模板引擎详解(二)-上下文环境
  3. Flask中Jinja2模板引擎详解(三)-过滤器
  4. Flask中Jinja2模板引擎详解(四)-测试器
  5. Flask中Jinja2模板引擎详解(五)-全局函数
  6. Flask中Jinja2模板引擎详解(六)-块和宏
  7. Flask中Jinja2模板引擎详解(七)-本地化
  8. Flask中Jinja2模板引擎详解(八)-自定义扩展

在Flask中启用Jinja2扩展

任何时候使用Jinja2时,都需要先创建Jinja2环境,所以启用扩展的方法就是在创建环境时指定:

from jinja2 import Environment
jinja_env = Environment(extensions=['jinja2.ext.i18n','jinja2.ext.do'])

但是你在使用Flask时,其已经有了一个Jinja2环境,你不能再创建一个,所以你需要想办法添加扩展。Flask对于扩展不像过滤器或测试器那样封装了添加方法和装饰器,这样你就只能直接访问Flask中的Jinja2环境变量来添加。

from flask import Flask
app = Flask(__name__)
app.jinja_env.add_extension('jinja2.ext.i18n')
app.jinja_env.add_extension('jinja2.ext.do')

注:Flask默认已加载了”jinja2.ext.autoescape”和”jinja2.ext.with_“扩展。

Jinja2内置扩展

本系列第一篇中,我们已经介绍了四个Jinja2内置扩展的使用:”jinja2.ext.autoescape”, “jinja2.ext.with_“, “jinja2.ext.do”和”jinja2.ext.loopcontrols”。除了这几个以外,Jinja2还有一个非常重要的扩展,就是提供本地化功能的”jinja2.ext.i18n”。它可以与”gettext”或”babel”联合使用,接下来我们采用”gettext”来介绍怎么使用这个本地化扩展。

创建本地化翻译文件

建议大家先去了解下Python gettext相关知识,篇幅关系本文就不准备细讲。这里我们使用Python源代码(记住不是安装包)中”Tools/i18n”目录下的工具来创建翻译文件。

  • 第一步,我们先生成翻译文件模板,在”Tools/i18n”目录中找到”pygettext.py”并运行

    $ python pygettext.py
    

上述命令会在当前目录下生成一个名为”message.pot”的翻译文件模板,内容如下:

# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2016-02-22 21:45+CST\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: ENCODING\n"
"Generated-By: pygettext.py 1.5\n"
  • 第二步,将”message.pot”中”CHARSET”和”ENCODING”替换成”UTF-8”。同时你可以更改注释信息
# Jinja2 i18n Extention Sample
# Copyright (C) 2016 bjhee.com
# Billy J. Hee <billy@bjhee.com>, 2016.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2016-02-22 21:45+CST\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: UTF-8\n"
"Generated-By: pygettext.py 1.5\n"

修改完后,将其另存为翻译文件”lang.po”。

  • 第三步,在”lang.po”中添加你要翻译的文字,比如
msgid "Hello World!"
msgstr "世界,你好!"

将其加在文件末尾。这里”msgid”指定了待翻译的文字,而”msgstr”就是翻译后的文字。

  • 第四步,生成”lang.mo”文件
    我们依然使用”Tools/i18n”目录提供的工具,”msgfmt.py”:

    $ python msgfmt.py lang.po
    

执行完后,当前目录生成了”lang.mo”文件。注意,只有这个*.mo文件才能被应用程序识别。另外,推荐一个工具Poedit,很强的图形化po编辑工具,也可以用来生成mo文件,非常好用,Mac和Windows下都能用。

  • 第五步,将po, mo文件加入应用
    我们在当前Flask工程下创建子目录”locale/zh_CN/LC_MESSAGES/“,并将刚才生成的”lang.po”和”lang.mo”文件放到这个目录下。这里”locale”子目录的名字可以更改,其他的个人建议不要改。

在模板中使用本地化

让我们在Flask应用代码中启用”jinja2.ext.i18n”,并加载刚创建的翻译文件。

#coding:utf8
import gettext
from flask import Flask,render_template

app = Flask(__name__)
# 加载扩展
app.jinja_env.add_extension('jinja2.ext.i18n')

# 'lang'表示翻译文件名为"lang.mo",'locale'表示所有翻译文件都在"locale"子目录下,
# 'zh_CN'表示二级子目录,含义上讲就是加载中文翻译。所以下面的代码会加载文件:
# "locale/zh_CN/LC_MESSAGES/lang.mo"
gettext.install('lang', 'locale', unicode=True)
translations = gettext.translation('lang', 'locale', languages=['zh_CN'])
translations.install(True)
app.jinja_env.install_gettext_translations(translations)

这个install_gettext_translations()方法就是Jinja2提供来加载”gettext”翻译文件对象的。加载完后,你就可以在模板中使用本地化功能了。方法有两种:{% trans %}语句或gettext()方法。我们先来试下{% trans %}语句,在模板文件中,我们加上:

<h1>{% trans %}Hello World!{% endtrans %}</h1>

运行下,有没有看到页面上打印了”世界,你好!”,恭喜你,成功了!使用gettext()方法如下,效果同{% trans %}语句一样。

<h1>{{ gettext('Hello World!') }}</h1>

Jinja2还提供了_( )方法来替代gettext( ),代码看起来很简洁,个人推荐使用这个方法。

<h1>{{ _('Hello World!') }}</h1>

上面的例子是在程序中指定本地化语言,你也可以在请求上下文中判断请求头Accept-Language的内容,来动态的设置本地化语言。

翻译内容带参数

有时候,待翻译的文字内有一个变量必须在运行时才能确定,怎么办?我可以在翻译文字上加参数。首先,你要在po文件中定义带参数的翻译文字,并生成mo文件:

msgid "Hello %(user)s!"
msgstr "%(user)s,你好!"

然后,你就可以在模板中,使用{% trans %}语句或gettext()方法来显示它:

<h1>{% trans user=name %}Hello {{ user }}!{% endtrans %}</h1>
<h1>{{ _('Hello %(user)s!')|format(user=name) }}</h1>

上例中,我们把模板中的变量”name”赋给了翻译文字中的变量”user”。翻译文字上可以有多个变量。

新样式 (Newstyle)

Jinja2从2.5版本开始,支持新的gettext样式,使得带参数的本地化更简洁,上面的例子在新样式中可以写成:

<h1>{{ _('Hello %(user)s!', user=name) }}</h1>

不过使用新样式前,你必须先启用它。还记得我们介绍过Jinja2加载翻译文件的方法吗?对,就是install_gettext_translations()。调用它时,加上newstyle=True参数即可。

app.jinja_env.install_gettext_translations(translations, newstyle=True)

单/复数支持

英文有个特点就是名词有单/复数形式,一般复数都是单数后面加s,而中文就不区分了,哎,老外就是麻烦。所谓外国人创造的Python gettext,自然也对单/复数提供了特殊的支持。让我们现在po文件中,加上下面的内容,并生成mo文件:

msgid "%(num)d item"
msgid_plural "%(num)d items"
msgstr[0] "%(num)d个物品"
msgstr[1] "%(num)d个物品集"

什么意思呢,这个msgid_plural就是指定了它上面msgid文字的复数形式。而msgstr[0], [1]分别对应了单/复数形式翻译后的内容。为什么这么写?你别管了,照着写就是了。

在模板中,我们加上下面的代码:

  {% set items = [1,2,3,4,5] %}
  {{ ngettext('%(num)d item', '%(num)d items', items|count) }}
  {{ ngettext('%(num)d item', '%(num)d items', items|first) }}

你会很惊奇的发现,当num变量为5时,页面显示”5个物品集”;而当num变量为1时,页面显示”1个物品”。也就是程序自动匹配单/复数。很神奇吧!

本来准备在本篇把扩展都介绍完的,发现单写个”i18n”后篇幅就很长了,只好把自定义扩展部分另起一篇

本篇中的示例代码可以在这里下载