Work memo that I tried i18n with Flask app

** Note: ** This is just a note. Please forgive me even if I don't have any useful information.


Introduction

I'm writing a Flask app as a hobby, but I'd like to make use of it at work if possible. However, the problem is Japanese. English is better for publishing on GitHub or GitLab, but the sad reality is that if you try to use it at work, you can not use it in English.

So i18n. maybe. i18n is an abbreviation for Internationalization (?) As you know. By the way, I liked the Hyper Nikki System, but the official website domain h14m.org comes from there, right? (Derailment) For the time being, I've always been interested in how to do i18n, so I took this opportunity to try it, which is the content of this article.

Preparation

This article is basically based on [this site (English)] link-1 and [this site (Japanese, Qiita)] link-3. It seems that it is common (or can I say?) To use [Babel] link-2 to do i18n things with Python Flask. When you think of babel, people who use Emacs and people who use JS may think of something else (derailment). Or rather, I couldn't write Babel on the tag because it seems to be confusing ... !!

First, install Babel itself.

% pip3 install --user Babel flask_babel

It was like that.

First try what it looks like

[This site (Japanese, Qiita)] Try cloning the github repository of link-3 and running it. First, try talking on telnet without thinking about anything.

% telnet localhost 5000
Trying ::1...
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET / HTTP/1.1

HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 11
Server: Werkzeug/0.12.2 Python/3.6.1+
Date: Wed, 14 Jun 2017 11:10:09 GMT

Hello WORLDConnection closed by foreign host.

It was an ingurishu. Hello. The order is reversed, but it seems that language switching can be done by sending Accept-Language in the http request header. So, this time, I asked everyone to come back in their favorite Japanese.

% telnet localhost 5000
Trying ::1...
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET / HTTP/1.1
Accept-Language: ja

HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 21
Server: Werkzeug/0.12.2 Python/3.6.1+
Date: Wed, 14 Jun 2017 11:14:39 GMT

Hello World Connection closed by foreign host.

As planned, he replied in Japanese. I see, this is how it works. It seems that Accept-Language can also be passed as a weighted list.

Put Babel in your Flask app

Now, let's take the first step toward i18n support. The following description has been added to the Flask app. [Reference site (English)] In link-1, it is written in __init__.py, but which one is better?

app/hoge.py


app = Flask(__name__)
app.config.from_object(__name__)

from flask_babel import Babel
babel = Babel(app)

from config import LANGUAGES

@babel.localeselector
def get_locale():
    return request.accept_languages.best_match(LANGUAGES.keys())

Next, I prepared the following files.

config.py


# -*- coding: utf-8 -*-
#
# available languages
LANGUAGES = {
    'en': 'English',
    'ja': 'Japanese'
}

Using the localeselector decorator like this seems to be able to react to Accept-Language. And one more thing, Babel itself seems to need a configuration file, so I created it as follows. The first and second lines are the files to be translated, and the third line seems to enable Babel extension. I haven't looked into the details of the extension.

babel.cfg


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

Actually translate

Touch the template

First, let's play with the template. In this case, _ () seems to be an alias for gettext (), so I modified the place where it becomes "Name" in English as follows.

show_person.tpl.html


      <tr>
        <td>{{ _('Name') }}</td>
        <td>{{ person.name }}</td>
      </tr>

Touch the Python code (Added 2017-06-17)

When translating a message to be flashed, I had to mess with the code on the Python side, but I was able to implement it as follows.

hoge.py


from flask import flash

from flask_babel import gettext as _

@app.route('/logout')
def logout():

    session.pop('logged_in', None)
    flash(_("You were logged out"), 'success')

    return redirect(url_for('show_hoge'))

In the above, gettext is imported as _ to align with the template side. If you want to enter numbers etc.

hoge.py


        flash(_("No Such Item with ID %(item_id)d", item_id=item_id), 'error')

It seems good to do it as.

Create a translation file

Next, I ran the following command.

% pybabel extract -F babel.cfg -o message.pot hoge

This seems to create a template file. Looking at the contents of the output message.pot

message.pot


# Translations template for PROJECT.
# Copyright (C) 2017 ORGANIZATION
# This file is distributed under the same license as the PROJECT project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2017.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2017-06-14 20:54+0900\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <[email protected]>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.4.0\n"

#: hoge/templates/show_person.tpl.html:10
msgid "Name"
msgstr ""

It was like that. It seems that there is "Name" in the place of msgid. Furthermore, it seems to make a translation file based on this file. Specifically, I hit the following command.

% pybabel init -i message.pot -d hoge/translations -l ja
creating catalog hoge/translations/ja/LC_MESSAGES/messages.po based on message.pot

The po file seemed to be easy to do using Poedit poedit introduced in [Reference site (English)] link-1. I opened the generated message.po and entered "Name" as "Name" as shown below.

poedit.png

Looking at the edited file, the update date was rewritten, and of course msgstr was also rewritten, and X-Generator made a self-assertion. Originally, it seems to do pybabel compile after this, but Poedit seems to create a mo file that is automatically compiled when it is saved (version used, 1.8.11).

In this state, when the application was displayed on the browser, "Name" was successfully replaced with "Name". In addition, I was able to confirm that "Name" is displayed again when the English priority is raised in the browser settings.

What about subsequent updates?

When updating, it seems that the po file is updated by the following procedure.

pybabel extract -F babel.cfg -o message.pot hoge
pybabel update -i message.pot -d hoge/translations

Impressions

When it comes to sentences, it seems to be quite difficult, but my impression is that if it's about words, it's surprisingly easy to make. It seems to be troublesome to hit the command when updating, but is there any good way?

Anyway, there is no problem with using it at work or publishing it on GitHub !! Well, the biggest problem is that it is meaningless unless the development of the application itself progresses !!

References

Recommended Posts

Work memo that I tried i18n with Flask app
I tried linebot with flask (anaconda) + heroku
[Python] A memo that I tried to get started with asyncio
A story that didn't work when I tried to log in with the Python requests module
A memo that I wrote a quicksort in Python
I tried to integrate with Keras in TFv1.1
I tried Flask with Remote-Containers of VS Code
[Memo] I tried a pivot table in Python
I want to transition with a button in flask
I want to work with a robot in python.
A memo that I touched the Datastore with python
Note that I dealt with HTML in Beautiful Soup
I tried to make a memo app that can be pomodoro, but a reflection record
I tried fp-growth with python
I tried scraping with Python
I tried Learning-to-Rank with Elasticsearch!
I tried using easydict (memo).
I tried clustering with PyCaret
I tried gRPC with Python
I tried scraping with python
Mezzanine introduction memo that I got stuck in the flow
I tried "a program that removes duplicate statements in Python"
I tried to create an article in Wiki.js with SQLAlchemy
I tried to describe the traffic in real time with WebSocket
I tried trimming efficiently with OpenCV
Movement that changes direction in the coordinate system I tried Python 3
I tried summarizing sentences with summpy
[Memo] Links for developing with Flask
I tried machine learning with liblinear
I tried web scraping with python.
Run the app with Flask + Heroku
I tried moving food with SinGAN
I tried to process the image in "sketch style" with OpenCV
Creating a simple app with flask
I tried implementing DeepPose with PyTorch
I tried running GAN in Colaboratory
I tried face detection with MTCNN
Hello World in Flask [Appropriate memo]
I tried to process the image in "pencil style" with OpenCV
I tried a tool that imitates Van Gogh's pattern with AI
I tried to improve the efficiency of daily work with Python
I tried running prolog with python 3.8.2.
I tried Line notification in Python
I tried SMTP communication with Python
I tried sentence generation with GPT-2
I tried to log in to twitter automatically with selenium (RPA, scraping)
I tried learning LightGBM with Yellowbrick
I tried Philips Hue that fluctuates in seven colors at 1 / f
I tried face recognition with OpenCV
I tried to implement deep learning that is not deep with only NumPy
I tried to implement a blockchain that actually works with about 170 lines
I tried to develop a Formatter that outputs Python logs in JSON
I got stuck in a flask application redirect with a reverse proxy in between
I tried to implement PLSA in Python
Azure table storage with PTVS Flask app
I tried multiple regression analysis with polynomial regression
I tried sending an SMS with Twilio
I tried using Amazon SQS with django-celery
I tried to implement Autoencoder with TensorFlow
I tried to implement permutation in Python
Deploy flask app with mod_wsgi (using pipenv)