Djangoで国際化のメモ

先日Bargain Netの英語対応を行いました。何度か英語の対応はやってるはずなんですけど、毎回覚えてなくて調べ直すのでメモ書きです。

settings.py

まずsettings.pyの設定です。USE_I18N等の設定に加えて、LOCALE_PATHSに翻訳ファイルのサーチパスを通しておく必要があります。毎回こんな設定あったっけと思います。

USE_I18N = True
USE_L10N = True
MIDDLEWARE_CLASSES = (
    ...
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.locale.LocaleMiddleware',
	...
)

LOCALE_PATHS = ['%s/../myapp/locale/' % (PROJECT_ROOT,)]

翻訳の流れ

以下が基本的な流れです。

transタグをHTMLを埋め込む。

<!-- テンプレートファイル(hoge.html) -->
<h1>{% trans 'hello' %}</h1>

テンプレートファイルにtransタグを埋め込めたらmakemessagesコマンドで翻訳ファイル(django.po)を出力。

$ django-admin.py makemessages -l en
processing language en
$ find locale
locale
locale/en
locale/en/LC_MESSAGES
locale/en/LC_MESSAGES/django.po

次に出力された翻訳ファイルを編集。

#: templates/hoge.html:79
msgid "hello"
msgstr "Hello English World"

編集し終わったら、翻訳ファイルをコンパイルするとdjango.moファイルが生成されます。後は再起動すれば翻訳が適用されています。

$ django-admin.py compilemessages
processing file django.po in /path/to/project/myapp/locale/en/LC_MESSAGES

なおtransタグと同様の効果が_(“hello”)でも得られるのですが、どうも処理は別っぽいです。transタグはtemplates/i18n.pyでテンプレートタグとして実装されていますが、_()はtemplate/base.pyでテンプレートにビルトインされています。ちらっと見ただけなのですが、何となくtransタグの方が翻訳処理部分がきちんと切り離されてる気がするので、transタグを使っています。

翻訳テキスト中の変数

翻訳テキストの中に変数を入れたい場合があります。

Bargain Netの例で言うと、商品のレートを表示する部分で「星5つのうち3」という文言は、英語に直すと「3 out of 5 star」になります。語順が違うので数字を除いて翻訳できません。3を変数として扱いたいですね。

こういう場合はblocktransタグを使います。先ほどの例でいうと下記になります。なお見やすいように改行を入れてますが、実際には改行はありません。

{% blocktrans with rate_info.avr as avr %}
review_avr_text_{{avr}}
{% endblocktrans %}

これが翻訳ファイルにはこのように出力されます。テンプレート中の変数({{avr}})が、Pythonのフォーマット文字列として置き換えられています。後はmsgstr中で%(avr)sを使用して翻訳を行います。

#, python-format
msgid "review_avr_text_%(avr)s" # これが出力
msgstr "%(avr)s out of 5 stars" # この行を編集する

キーに変数を含む場合

翻訳テキストではなく、翻訳キー中に変数を入れたい場合があります。Bargain Netの例で言うとカテゴリ名などです。category_name_1、category_name_2の用にキーを作りたいという事ですね。僕はカテゴリオブジェクト(model.Model)に、下記のメソッドを追加してみました。

# models.py
def get_translated_name(self):
    return _('category_name_%s' % (self.category_id))

ところが当然と言えば当然ですが、これはmakemessagesコマンドで正しく処理されません。翻訳ファイルには%sがそのまま出てきます。

msgid "category_name_%s"
msgstr ""

現状ではこれを上手く処理する仕組みは無いようで、手動で翻訳ファイルに項目を追加する必要があるという事です。また残念ながら手で追加した項目はmakemessagesコマンドを使うと、上書きされたりコメントアウトされるため、手動での項目追加とmakemessagesは共存させられません。なので僕は途中からはmakemessagesを使わず、項目の追加も手で行いました。

JavaScript中の翻訳

基本的には避けた方が良いと思いますし、ViewとControllerを分けていればあまり発生しないはずです。

とは言ってもJS側で翻訳したい時もあります。Bargain NetでいうとJavaScriptで動的に行っている金額の表示などです。英語だと$1、日本語だと1ドルと語順が違うため、数字だけプレースホルダーにして埋め込む事が出来ません。この為だけにclassを追加してdisplay:noneをトグルみたいな処理を入れるくらいならJavaScriptを翻訳した方がマシです。

JS中の翻訳はgettextメソッドを使います。

# hoge.js
var price_format = function(value, locale) {
	locale = locale || 'jp';
	var format = '';
	if (locale == 'jp') {
		format = gettext('yen_format');
	}
	else {
		format = gettext('dollar_format');
	}
	return sprintf(format, value);
};

JSファイルをgettextで置き換えたら、makemessagesでJS用の翻訳ファイルを出力します。-dオプションでdjangojsを指定します。するとdjangojs.poという翻訳ファイルが出来るので、後は同じように翻訳を行います。

$ django-admin.py makemessages -d djangojs -l ja

次に翻訳をJSに適用します。プロジェクトのurls.pyにJSの翻訳ファイルへのURLを追加します。このURLにアクセスすると、作成した翻訳ファイルが実行可能なJSコードとして出力されます。この中に上記で使用したgettext等も定義されています。一度アクセスしてみるとイメージがつかめると思います。

# urls.py
urlpatterns = ...

js_info_dict = {
    'packages': ('myapp',),
}
urlpatterns += patterns('',
    (r'^jsi18n/$', 'django.views.i18n.javascript_catalog', js_info_dict),
)

後はテンプレートにこのURLへのリンクを張って完了です。

<script type="text/javascript" src="{% url django.views.i18n.javascript_catalog %}"></script>

runserverが翻訳ファイルを編集してもリロードされない

翻訳作業をしていて一番辛いのがこの件です。毎回コンパイルして再起動とかやってられません。仕方が無いのでdjangoのコマンドを書きました。コードはこれです。myapp/management/commands/reloader.pyとして保存して使います。

使い方は以下の通りです。runserverと一緒に使うと、poファイルが編集されたら、自動的にコンパイルしてrunserverを再起動してくれます。

$ ./manage.py reloader myapp

本当はrunserverにオプションを追加したかったのですが、思ってたのと仕組みが違っていて面倒だったのでとりあえずコマンドにして逃げました。

以上、翻訳作業のメモでした。あと複数形の話も書こうと思いましたが疲れたのでこの辺にしておきます。

Leave a Reply

Your email address will not be published. Required fields are marked *