Djangoのテンプレートで、includeテンプレートタグのwithで渡す値を国際化(i18n)対応する

Djangoのテンプレートにて、

{% include 'translation/parts.html' with value='tsugaru' %}

と、分割した先のテンプレート parts.html に渡した文字列 tsugaru を国際化しようとした時に悩んだのでメモを残します。

 
目次

 

環境

 

Djangoテンプレートでの国際化対応について

本題に入る前に、Djangoテンプレートの国際化(i18n)対応について、簡単に流れを書いておきます。

Djangoテンプレートの国際化で必要なものは、主に

の3つです。

 
それらを用意する流れは以下の通りです。

  1. settings.pyに設定
  2. テンプレートにて、テンプレートタグを使ってマーキング
  3. メッセージファイルを生成
  4. メッセージファイルを編集
  5. メッセージファイルをコンパイル
  6. 表示

 
ここでは「Djangoのテンプレートにて fuji という文字列を日本語の場合は フジ と表示する」を例に、流れをメモしておきます。

 

settings.pyに設定

Djangoで使うデフォルト言語は、settings.pyの LANGUAGE_CODE に指定します。
https://docs.djangoproject.com/en/3.0/ref/settings/#language-code

LANGUAGE_CODE = 'ja'

の場合、日本語がデフォルト言語となります。

 
また、ミドルウェア LocaleMiddleware を使うことで複数の言語を扱えるようになります。

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',

    # i18n用ミドルウェア
    'django.middleware.locale.LocaleMiddleware',

    'django.middleware.common.CommonMiddleware',
    ...
]

 
例えば、URLに言語コードを指定することで特定の言語を表示させたい場合

は、上記ミドルウェアに加え、 urls.py にて

from django.conf.urls.i18n import i18n_patterns

urlpatterns = i18n_patterns(
    path('translation', include('translation.urls')),
)

i18n_patterns を使えばよいです。
https://docs.djangoproject.com/en/3.0/topics/i18n/translation/#django.conf.urls.i18n.i18n_patterns

 
他に、 LOCALE_PATHS を使い、i18n用のメッセージファイル置き場も指定できます。
https://docs.djangoproject.com/en/3.0/ref/settings/#locale-paths

LOCALE_PATHS = (
    os.path.join(BASE_DIR, 'locale'),
)

の場合、プロジェクトルート下の locale ディレクトリにメッセージファイルが格納されます。

 

テンプレートにて、テンプレートタグを使ってマーキング

テンプレートにて

{% load i18n %}

<p>[Index - trans版]{% trans 'fuji' %}</p>

と、

  • load i18n でロード
  • マーキングとして trans テンプレートタグの引数に翻訳したい文字列を指定
    • マーキング

を行います。
https://docs.djangoproject.com/en/3.0/topics/i18n/translation/#trans-template-tag

 

メッセージファイルの生成

django-admin makemessages コマンドでメッセージファイルを生成します。
https://docs.djangoproject.com/en/3.0/topics/i18n/translation/#message-files

$ django-admin makemessages -l ja
processing locale ja

とすると、 locale/ja/LC_MESSAGES/django.po ファイルが作成されます。

 

メッセージファイルの編集

django.poファイルを開くと、どのファイルのどの箇所でどれが使われているかが設定されています。

#: templates/translation/index.html:7
msgid "fuji"
msgstr ""

は、templates/translation/index.htmlの7行目に fuji という文字列がマーキングされています。

そこで、翻訳後の文字列を msgstr に指定します。

#: templates/translation/index.html:7
msgid "fuji"
msgstr "フジ"

の場合、 fuji という文字列が フジ に翻訳されます。

 

コンパイル

django-admin compilemessages コマンドでコンパイルします。
https://docs.djangoproject.com/en/3.0/topics/i18n/translation/#compiling-message-files

$ django-admin compilemessages
...
processing file django.po in /path/to/env/lib/python3.8/site-packages/django/contrib/flatpages/locale/es/LC_MESSAGES

コンパイル後、django.po ファイルと同じディレクトリに、 django.mo ファイルが生成されます。

 

動作確認

Djangoを起動して確認します。

http://localhost:8000/ja/translation にアクセスすると、django.mo ファイルのある日本語 (ja) の場合は翻訳されています。

f:id:thinkAmi:20200531180335p:plain:w300

 
一方、http://localhost:8000/ja/translation にアクセスすると django.mo ファイルのない英語 (en) の場合は、テンプレートのままになっています。

f:id:thinkAmi:20200531180457p:plain:w300

 
以上が、Djangoにおける国際化 (i18n) 対応の簡単な流れです。

 

includeテンプレートタグのwithに渡す値の国際化

本題です。

Djangoテンプレートタグの include では with を使うことで読み込むテンプレートに値を渡せます。
https://docs.djangoproject.com/en/3.0/ref/templates/builtins/#include

例えば、 parts.html

<p>[Parts - そのまま版]{{ value }}</p>

を書き、それをindex.htmlで読み込むようにします。

その際、 with value='tsugaru' とすることで、parts.htmlのテンプレート変数 value に、 tsugaru という値を渡せます。

{% include 'translation/parts.html' with value='tsugaru' %}

 
今回は tsugaru という文字列を国際化したいとします。

 

うまくいかない方法
テンプレートタグをネストする

まず、単純にテンプレートタグをネストするだけでは、Djangoテンプレートのシンタックスエラーになります。

{% include 'translation/parts.html' with value={% trans 'hello' %} %} 

エラー

TemplateSyntaxError at /en/translation

Could not parse the remainder: '{%' from '{%'

 

includeされる側で翻訳する

続いて、index.html側では

{% include 'translation/parts.html' with value='tsugaru' %}

として、parts.html側で翻訳することを考えます。

 
まず

<p>{% trans {{ value }} %}</p>

としてもエラーになります。

Could not parse the remainder: '{{' from '{{'

trans テンプレートタグの中では変数を展開できないためです。

 
その代わりに transblock テンプレートタグを使います。
https://docs.djangoproject.com/en/3.0/topics/i18n/translation/#blocktrans-template-tag

<p>[Parts - blocktrans版]{% blocktrans %}{{ value }}{% endblocktrans %}</p>

 
次に、django-admin makemessages -l ja コマンドで翻訳ファイルを更新します。すると

#: templates/translation/parts.html:4
#, python-format
msgid "%(value)s"
msgstr ""

が追加されます。

ただ、この部分を

#: templates/translation/parts.html:4
#, python-format
msgid "%(value)s"
msgstr "foo"

とした場合、 django-admin compilemessages でのコンパイルに失敗します。

$ django-admin compilemessages
...
processing file django.po in /path/to/django project/locale/ja/LC_MESSAGES
Execution of msgfmt failed: /path/to/django project/locale/ja/LC_MESSAGES/django.po:1212: 引数 'value' に対する形式指定' に存在しません
msgfmt: 1 個の致命的エラーが見つかりました
...
CommandError: compilemessages generated one or more errors.

 
エラーは msgstr "foo %(value)s" のように変数valueの値 %(value)s を含めれば解消されます。

しかし、これでは value に渡された値は翻訳されません。

f:id:thinkAmi:20200531183446p:plain:w300

 
 

うまくいく方法

includeする側 (例の場合だと index.html側) で翻訳し、includeされる側に翻訳後の値を渡せば良いです。

翻訳する方法としては以下の2つがあります。

  • transで翻訳後、 as で変数に保存する
  • _() を使う

 
なお、渡される側(parts.html) は

<p>[Parts - そのまま版]{{ value }}</p>

と、値をそのまま表示します。

 

transで翻訳後、 as で変数に保存する

テンプレートタグ trans には、翻訳後の値を変数に入れておくために as が使えます。
https://docs.djangoproject.com/en/3.0/topics/i18n/translation/#trans-template-tag

 
今回の例では

{# akibaeを翻訳し、 ringo に入れる #} 
{% trans 'akibae' as ringo %}

{# 翻訳後の値をparts.htmlに渡す #}
{% include 'translation/parts.html' with value=ringo %}

となります。

 

_() を使う

今回のように文字列リテラルの場合には、 _() も使えます。
https://docs.djangoproject.com/en/3.0/topics/i18n/translation/#string-literals-passed-to-tags-and-filters

{# shinano-dulce を _() で翻訳する #}
{% include 'translation/parts.html' with value=_('shinano-dulce') %}

 

動作確認

このように、両方とも翻訳できました。

f:id:thinkAmi:20200531201628p:plain:w300

 

ソースコード

GItHubに上げました。 translation アプリが今回のファイルです。
https://github.com/thinkAmi-sandbox/django_30-sample