pybabelとJinja2で多言語対応のwebapp2アプリケーションを作ってGAEへデプロイする手順。
-
webapp2: Python製Webアプリフレームワーク。
-
Jinja2: Python製テンプレートエンジン。
-
pybabel(Babel): 翻訳箇所のロケール別カタログを管理するツール。紛らわしいが、JSコンパイラのBabelではない。
環境
- OS X 10.12.3
- python 2.7.12
- pip 9.0.1
- pybabel 2.3.4
- デプロイ環境: GAE
ディレクトリ構成
最終的にはこうなる。
$ tree
.
├── app.yaml
├── appengine_config.py
├── appengine_config.pyc
├── babel.cfg
├── lib
│ ├── Babel-2.3.4.dist-info
│ ├── babel
│ ├── pytz
│ └── pytz-2016.10.dist-info
├── locale
│ ├── ja_JP
│ │ └── LC_MESSAGES
│ │ ├── messages.mo
│ │ └── messages.po
│ └── message.pot
├── main.py
├── main.pyc
├── requirements.txt
└── templates
└── index.html
33 directories, 1395 files
参考のためリポジトリを作成した。
アプリケーション作成
$ mkdir webapp2-example && cd webapp2-example
Babel設定
webapp2 i18n拡張が使うBabelのライブラリをプロジェクト内にインストールする
Babel==2.3.4
$ pip install -t lib -r requirements.txt
Collecting Babel==2.3.4 (from -r requirements.txt (line 1))
Using cached Babel-2.3.4-py2.py3-none-any.whl
Collecting pytz>=0a (from Babel==2.3.4->-r requirements.txt (line 1))
Using cached pytz-2016.10-py2.py3-none-any.whl
Installing collected packages: pytz, Babel
Successfully installed Babel-2.3.4 pytz-2016.10
from google.appengine.ext import vendor
vendor.add('lib')
GAE設定
application: webapp2-example
version: 1
runtime: python27
api_version: 1
threadsafe: yes
handlers:
- url: .*
script: main.app
libraries:
- name: webapp2
version: "2.5.2"
- name: jinja2
version: latest
アプリケーション
ロケールの取得はどうやってもよいが、ここではリクエストパラメータから読むようにする
import webapp2
from webapp2_extras import i18n
import os
import jinja2
JINJA_ENVIRONMENT = jinja2.Environment(
loader=jinja2.FileSystemLoader(os.path.dirname(__file__)),
extensions=['jinja2.ext.i18n', 'jinja2.ext.autoescape'],
autoescape=True)
JINJA_ENVIRONMENT.install_gettext_translations(i18n)
class HelloWorldHandler(webapp2.RequestHandler):
def get(self):
locale = self.request.GET.get('locale', 'en_US')
i18n.get_i18n().set_locale(locale)
print(locale)
template_values = {}
template = JINJA_ENVIRONMENT.get_template('templates/index.html')
self.response.write(template.render(template_values))
app = webapp2.WSGIApplication([
('/', HelloWorldHandler),
], debug=True)
def main():
app.run()
if __name__ == '__main__':
main()
テンプレート
$ mkdir templates
プレースホルダーを使うケースを書いてみる。
- New style gettext
_
はgettext
のエイリアスになるのでどちらを使ってもよい。
- Jinja2 i18n
<html>
<body>
{{ _("Hello, %(username)s", username='satzz') }}
</body>
</html>
ローカルでの動作確認
まだ多言語化していないが、いったんこの段階でGAE開発サーバーで動作確認する。
- Google Cloud SDK Documentation
- Using the Local Development Server
開発サーバー起動
$ dev_appserver.py .
INFO 2017-03-17 06:07:29,597 sdk_update_checker.py:229] Checking for updates to the SDK.
INFO 2017-03-17 06:07:30,185 api_server.py:204] Starting API server at: http://localhost:63035
INFO 2017-03-17 06:07:30,189 dispatcher.py:197] Starting module "default" running at: http://localhost:8080
INFO 2017-03-17 06:07:30,192 admin_server.py:118] Starting admin server at: http://localhost:8000
moduleが起動しているhttp://localhost:8080 へブラウザアクセス
GAEでの動作確認
i18nから少し逸れるがGAEでも動作確認しておく。
app.yaml
に書いたのと同じ名前でプロジェクト作成する。
アプリケーション作成
インストールしたGoogleAppEngineLauncherを開いてローカルで作ったアプリケーションを指定。Application IDは空でok
Cmd+DでデプロイしてログにDeployment successful
と出るのを待つ。
で動作確認できる。
ロケール追加
ここから本題のi18n。カタログを作成するためのpybabelコマンドをインストールする。
$ pip install babel
Collecting babel
Using cached Babel-2.3.4-py2.py3-none-any.whl
Requirement already satisfied: pytz>=0a in /System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python (from babel)
Installing collected packages: babel
Successfully installed babel-2.3.4
多言語化対象などを設定
- Extraction Method Mapping and Configuration
[jinja2: templates/**.html]
encoding = utf-8
$ mkdir locale
カタログ作成の流れは
- Jinja2 template(html) -> POT(PO Template) -> PO(Portable Object) -> MO(Machine Object)
となる。
Jinja2テンプレートをもとにPOTを作成する。
$ pybabel extract -F ./babel.cfg -o ./locale/message.pot .
extracting messages from templates/index.html (encoding="utf-8")
writing PO template file to ./locale/message.pot
POTを開くと、msgidとそれがどこで使われているのかが自動でリストされている。このPOTはテンプレートなのでいじらなくてもよい。
#: templates/index.html:3
#, python-format
msgid "Hello, %(username)s"
msgstr ""
POTをもとにロケールPOを作成する。(PO作成済の場合はpybabel update
)
$ pybabel init -l ja_JP -d ./locale -i ./locale/message.pot
creating catalog ./locale/ja_JP/LC_MESSAGES/messages.po based on ./locale/message.pot
ここでできたPOファイルを編集する。プレースホルダーを使う場合はこんな感じ。
msgid "Hello, %(username)s"
msgstr "%(username)sさん、こんにちは"
コンパイルする。作成されるMOファイルはバイナリファイルになる。
$ pybabel compile -f -d ./locale
compiling catalog ./locale/ja_JP/LC_MESSAGES/messages.po to ./locale/ja_JP/LC_MESSAGES/messages.mo
リクエストパラメータにロケールをつけてアクセスみる。
再度デプロイするとGAEでも挙動が確認できる。
この後は以下を繰り返してカタログを育てていく。
- 翻訳箇所やmsgidが更新 ->
pybabel extract
でPOT更新 - POTが更新 ->
pybabel update
でPO更新 - ロケール追加 ->
pybabel init
でPO作成 - POが作成・更新 ->
pybabel compile
でMO作成・更新