pybabelとJinja2でwebapp2アプリケーションを多言語化する

  • 0
    いいね
  • 0
    コメント

    pybabelとJinja2で多言語対応のwebapp2アプリケーションを作ってGAEへデプロイする手順。

    環境

    • 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のライブラリをプロジェクト内にインストールする

    requirements.txt
    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
    
    appengine_config.py
    from google.appengine.ext import vendor
    vendor.add('lib')
    

    GAE設定

    app.yaml
    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
    

    アプリケーション

    ロケールの取得はどうやってもよいが、ここではリクエストパラメータから読むようにする

    main.py
    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
    

    プレースホルダーを使うケースを書いてみる。

    _gettextのエイリアスになるのでどちらを使ってもよい。

    templates/index.html
    <html>
      <body>
        {{ _("Hello, %(username)s", username='satzz') }}
      </body>
    </html>
    

    ローカルでの動作確認

    まだ多言語化していないが、いったんこの段階でGAE開発サーバーで動作確認する。

    開発サーバー起動

    $ 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 へブラウザアクセス

    Screen Shot 2017-03-17 at 14.27.33.png

    GAEでの動作確認

    i18nから少し逸れるがGAEでも動作確認しておく。

    app.yamlに書いたのと同じ名前でプロジェクト作成する。

    Screen Shot 2017-03-17 at 15.08.51.png

    アプリケーション作成

    Screen Shot 2017-03-17 at 15.11.23.png

    Screen Shot 2017-03-17 at 15.14.42.png

    インストールしたGoogleAppEngineLauncherを開いてローカルで作ったアプリケーションを指定。Application IDは空でok

    Screen Shot 2017-03-17 at 15.13.17.png

    追加を確認(2行目)
    Screen Shot 2017-03-17 at 15.13.39.png

    Cmd+DでデプロイしてログにDeployment successfulと出るのを待つ。
    Screen Shot 2017-03-17 at 15.14.20.png

    で動作確認できる。

    ロケール追加

    ここから本題の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
    

    多言語化対象などを設定

    babel.cfg
    [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はテンプレートなのでいじらなくてもよい。

    locale/message.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ファイルを編集する。プレースホルダーを使う場合はこんな感じ。

    locale/ja_JP/LC_MESSAGES/messages.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
    

    リクエストパラメータにロケールをつけてアクセスみる。

    Screen Shot 2017-03-17 at 14.35.30.png

    再度デプロイするとGAEでも挙動が確認できる。

    この後は以下を繰り返してカタログを育てていく。

    • 翻訳箇所やmsgidが更新 -> pybabel extractでPOT更新
    • POTが更新 -> pybabel updateでPO更新
    • ロケール追加 -> pybabel initでPO作成
    • POが作成・更新 -> pybabel compileでMO作成・更新