Multilingualize webapp2 applications with pybabel and Jinja2

Procedure to create a multilingual webapp2 application with pybabel and Jinja2 and deploy it to GAE.

--webapp2: Python web app framework. - https://webapp2.readthedocs.io/en/latest/

--Jinja2: Python template engine. - http://jinja.pocoo.org/docs/2.9/ --pybabel (Babel): A tool for managing the locale-specific catalog of translated parts. It's confusing, but it's not the JS compiler Babel. - http://babel.pocoo.org/en/latest/cmdline.html

environment

Directory structure

Eventually it will be like this.

$ 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

I created a repository for reference.

Application creation

$ mkdir webapp2-example && cd webapp2-example

Babel settings

Install the Babel library used by the webapp2 i18n extension in your project

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 settings

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

application

It doesn't matter how you get the locale, but here we will read it from the request parameters.

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()

template

$ mkdir templates

Let's write a case that uses a placeholder.

_ is an alias for gettext, so you can use either one.

templates/index.html


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

Local operation check

It has not been multilingualized yet, but at this stage we will check the operation on the GAE development server.

Start 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

Browser access to http: // localhost: 8080 where module is running

Screen Shot 2017-03-17 at 14.27.33.png

Operation check on GAE

Although it deviates a little from i18n, check the operation with GAE.

Create a project with the same name as written in ʻapp.yaml`.

Screen Shot 2017-03-17 at 15.08.51.png

Application creation

Screen Shot 2017-03-17 at 15.11.23.png

Screen Shot 2017-03-17 at 15.14.42.png

Open the installed Google App Engine Launcher and specify the locally created application. Application ID is empty and ok

Screen Shot 2017-03-17 at 15.13.17.png

Confirm addition (2nd line) Screen Shot 2017-03-17 at 15.13.39.png

Deploy with Cmd + D and wait for Deployment successful to appear in the log. Screen Shot 2017-03-17 at 15.14.20.png

You can check the operation with.

Add locale

From here i18n of the main subject. Install the pybabel command to create a catalog.

$ 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

Set multilingual target etc.

babel.cfg


[jinja2: templates/**.html]
encoding = utf-8
$ mkdir locale

The flow of catalog creation

Will be.

Create a POT based on the Jinja2 template.

$ 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

When you open the POT, it automatically lists the msgid and where it is used. This POT is a template so you don't have to mess with it.

locale/message.pot


#: templates/index.html:3
#, python-format
msgid "Hello, %(username)s"
msgstr ""

Create a locale PO based on the POT. (Pybabel update if PO has been created)

$ 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

Edit the PO file created here. When using a placeholder, it looks like this.

locale/ja_JP/LC_MESSAGES/messages.po


msgid "Hello, %(username)s"
msgstr "%(username)s's, Hello"

Compile. The created MO file will be a binary file.

$ pybabel compile -f -d ./locale
compiling catalog ./locale/ja_JP/LC_MESSAGES/messages.po to ./locale/ja_JP/LC_MESSAGES/messages.mo

Add a locale to the request parameter and try accessing it.

Screen Shot 2017-03-17 at 14.35.30.png

If you deploy it again, you can see the behavior on GAE.

After this, repeat the following to grow the catalog.

--Translation and msgid are updated-> POT update with pybabel extract --POT updated-> PO update with pybabel update --Add locale-> Create PO with pybabel init --Created / updated by PO-> Create / update MO with pybabel compile

Recommended Posts

Multilingualize webapp2 applications with pybabel and Jinja2
Template network config generation with Python and Jinja2
With and without WSGI
Create applications, register data, and share with a single email