読者です 読者をやめる 読者になる 読者になる

pybabelコマンドを使ってアプリケーションを国際化する

Python

昨日から引き続きBabelに触ってみます。今日はアプリケーション国際化の流れをざっと追ってみます。(アプリケーションといっても、ここで例に出しているのは非常にtrivialなものですが・・・)

プロジェクトを作成

pasterを使って新規プロジェクトmybabelを作成します。

$ paster create mybabel
Selected and implied templates:
  PasteScript#basic_package  A basic setuptools-enabled package

Variables:
  egg:      mybabel
  package:  mybabel
  project:  mybabel
Enter version (Version (like 0.1)) ['']:
# テストなのでEnterを押してどんどん進めていく
$ cd mybabel
$ ls
mybabel                 setup.cfg
mybabel.egg-info        setup.py


メッセージファイルを置くディレクトリをあらかじめ作っておきます。ここではmybebel/localeとしました。

$ mkdir mybabel/locale


mybabel/__init__.pyを編集して、テスト用のコードを追加します。

# -*- coding: utf-8 -*-
# mybabel/__init__.py
from babel.support import Translations

t = Translations.load('mybabel/locale', ['ja'])
print t.gettext('I love spam.')

このプログラムはまだ国際化されていません。

$ python mybabel/__init__.py
I love spam.

potを作成する

pybabel extractコマンドを使って、mybabelディレクトリから翻訳対象文字列を抽出します。出力するpotはmybabel/locale/messages.potに置きました。

$ pybabel extract -o mybabel/locale/messages.pot mybabel

メッセージファイルを初期化する

pybabel initコマンドを使って、メッセージファイルを初期化します。-lオプションで日本語(ja)を指定し、-dオプションでベースディレクトリを指定しています。
pybabelのデフォルトのドメイン名はmessagesなので、何も指定しない時にはmessages.poというpoファイルが作られます。

$ pybabel init -l ja -i mybabel/locale/messages.pot -d mybabel/locale
creating catalog 'mybabel/locale/ja/LC_MESSAGES/messages.po' based on 'mybabel/locale/messages.pot'

mybabel/locale/ja/LC_MESSAGES/messages.poを編集します。

# Japanese translations for ....
# ... 略

#: mybabel/__init__.py:5
msgid "I love spam."
msgstr "スパム大好き!"

コンパイルする

pybabel compileコマンドを使ってpoからmoを生成します。

$ pybabel compile -d mybabel/locale
compiling catalog 'mybabel/locale/ja/LC_MESSAGES/messages.po' to 'mybabel/locale/ja/LC_MESSAGES/messages.mo'


前述のプログラムを実行してみると、無事国際化されているのが分かります。

$ python mybabel/__init__.py       
スパム大好き!

カタログをアップデートする

mybabel/__init__.pyを編集して、文字列を追加しました。

# -*- coding: utf-8 -*-
# mybabel/__init__.py
from babel.support import Translations

t = Translations.load('mybabel/locale', ['ja'])
print t.gettext('I love spam.')
print t.gettext('I love spam spam spam spam egg!')

potとアップデートし、さらにpybabel updateコマンドを使って、poを更新します。

$ pybabel extract -o mybabel/locale/messages.pot mybabel
$ pybabel update -l ja -i mybabel/locale/messages.pot -d mybabel/locale
updating catalog 'mybabel/locale/ja/LC_MESSAGES/messages.po' based on 'mybabel/locale/messages.pot'

mybabel/locale/ja/LC_MESSAGES/messages.poも更新します。

# ...略

#: mybabel/__init__.py:6
msgid "I love spam spam spam spam egg!"
msgstr "スパムスパムスパムスパムエッグ大好き!"

カタログを再度コンパイルします。

$ pybabel compile -d mybabel/locale
compiling catalog 'mybabel/locale/ja/LC_MESSAGES/messages.po' to 'mybabel/locale/ja/LC_MESSAGES/messages.mo'


プログラムを実行して出力を確認します。

$ python mybabel/__init__.py       
スパム大好き!
スパムスパムスパムスパムエッグ大好き!

Babelの印象

Babelでできること、できないことを一通り眺めてみたのですが、全体としては好印象です。例えば、Djangoを使った開発に限定してしまうとすると、DjangoのI18N, L10Nのレベルの高さにはかなわない気がしますが、PylonsI18N, L10Nの「なんだこれ?」感にくらべると、遥かにまともです。

  • この程度のプログラムにpasterを使ったのは理由があって、次はsetuptoolsを使ったカタログのメンテナンスを試してみようと考えているからです。
  • babel.support.LazyProxyというクラスが用意されているので、Webアプリケーションに要求されるような翻訳の遅延を、ある程度簡単に書けそうです。
    • django.utils.functionalにあるlazyよりあっさり書かれているのが気になりますが、まだ深くコードを検討していないのでなんとも言えません。
  • babel.support.Translationsは複数のカタログをマージする機能をサポートしています。(素のGNU gettextでもできなくはないのですが。)
  • Babelのキモは、自分のプロジェクト用のメッセージ抽出の関数を簡単に追加できることだと思うのですが、これもまだ試していません。
  • Webアプリケーションで使うには、Djangoでやっているように、翻訳オブジェクトをthreadlocalで管理するような仕組みが必要だと思います。ぱっと見た感じでは、これは自分で用意する必要がありそうです。