7
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Flaskアプリでi18nをしてみた作業メモ

Last updated at Posted at 2017-06-14

注意: これはただのメモです。有用な情報がなくても許してくださぃ。


はじめに

趣味でFlaskのアプリを書いているわけですが、できれば職場でも役立てたいと思ってます。
が、そこで問題になるのが日本語です。
GitHubとかGitLabとかで公開するのには英語の方が良いわけですが、職場で使おうとすると英語だと使っていただけないという悲しい現実が待ち構えているわけです。

そこで、i18nです。たぶん。
i18nは、ご存知のとおりInternationalizationの略(?)です。
ちなみに、ワタクシはHyper Nikki Systemが好きでしたが、公式サイトのドメインの h14m.org はそこから来てるんですよね? (脱線)
ひとまず、i18nってどうやるのか以前から興味はあったので、これを機会に試してみた、というのが本記事の内容となります。

準備

この記事は、基本的には こちらのサイト(英語)こちらのサイト(日本語,Qiita) を参考にさせていただきました。
PythonのFlaskでi18nな事をするためには、 Babel を使うのが一般的(と言ってよいのかな?)なようです。
babelっていうと、Emacs使いの人やJSの人は別のものを思い浮かべるかと思いますが(脱線)。
というか、ややこしくなりそうなのでタグにBabelって書けませんでした……!!

まずは、Babel自体をインストールしておきます。

% pip3 install --user Babel flask_babel

ってな具合でした。

まずはどんなものか一度試してみる

こちらのサイト(日本語,Qiita) のgithubのリポジトリをクローンして動かしてみます。
まずは、なにもかんがえずにtelnetで話しかけてみる。

% 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.

いんぐりっしゅでした。はろー。
順番が逆になりますが、言語の切替はhttpのリクエストヘッダでAccept-Languageを送ることによって行なうことができるようです。
ということで、今度はみんな大好き日本語で返ってくるようにお願いしてみました。

% 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

こんにちわ世界Connection closed by foreign host.

予定通り、日本語で返事をしてくれました。なるほど、こういう仕組みなのですね。
なお、Accept-Languageは重み付きのリストで渡すこともできるようです。

自分のFlaskアプリにBabelをつっこんでみる

では、いよいよi18n対応への第一歩を踏み出してみます。
Flaskアプリに以下の記述を追加しました。
参考サイト(英語) では __init__.py に書いてますが、どれがよいのざましょ。

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

つづけて、以下のファイルを用意しました。

config.py
# -*- coding: utf-8 -*-
#
# available languages
LANGUAGES = {
    'en': 'English',
    'ja': '日本語'
}

このようにlocaleselector デコレータを使うと Accept-Language に反応できるようになるようです。
そしてもうひとつ、Babel自体の設定ファイルも必要のようなので、以下のように作成しました。
1,2行目は翻訳対象となるファイル、3行目はBabelの拡張の有効化みたいです。
拡張について詳細は調べてません。

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

実際に翻訳してみる

テンプレートをさわる

まずは、テンプレートの方をいじってみます。この場合、 _()gettext() のエイリアスになってるようなので、以下のように、英語では "Name" となるところを細工しました。

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

Pythonコードをさわる (2017-06-17追記)

flashされるメッセージを翻訳する場合、Python側のコードをいじらないといけないのですが、以下のようにして実装できました。

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

上記では、テンプレート側と揃えるために、gettextを_としてimportしてます。
なお、数字等を入れたい場合は

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

としてやると良いようです。

翻訳ファイルを作成する

次に、以下のコマンドを走らせました。

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

これにより、テンプレートファイルができあがるようです。
出力された 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 <LL@li.org>\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 ""

といった感じでした。
msgidのところに、先程gettextをかました "Name" がいる模様。
さらに、このファイルをベースにして翻訳ファイルを作るようです。
具体的には、以下のコマンドを叩きました。

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

poファイルは、参考サイト(英語) にて紹介されているPoeditを使うと簡単にできそうでした。
生成されたmessage.poを開いて、以下のように"Name"は"名前"と入れてあげました。

poedit.png

編集されたファイルを見ると、更新日が書きかわり、もちろんmsgstrも書きかわり、X-Generatorで自己主張なさっておられました。
本来は、このあと pybabel compile とやるようですが、Poeditを使うと、保存した際に自動的にコンパイルされたmoファイルを作成してくれるようでした(使用バージョン、1.8.11)。

この状態にて、ブラウザでアプリの表示をさせると、無事に ”Name” のところが ”名前” と置き換わりました。
さらに、ブラウザの設定で英語の優先順位を上げると再び "Name" と表示されるのも確認できました。

その後の更新は?

更新する場合は、以下の手順でpoファイルを更新するようです。

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

感想

文章とかになるとかなり大変そうな感じですが、単語程度であれば意外にラクチンにできちゃうんですね、というのが感想です。
更新時にコマンドたたくのが面倒そうですが、なんか良い手があるのかな?

とにもかくにも、これで職場利用もGitHub公開も問題ないですね!!
まぁ、最大の問題はアプリ自体の開発の方が進まないと意味がないってことなんですけども!!

参考文献

7
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?