翻訳

概要

Django プロジェクトを翻訳可能にするためには、Python コードやテンプレートに、多少のフックを加筆しなければなりません。このフックは 翻訳文字列 と呼ばれます。翻訳文字列は Django に「このテキストに対する翻訳が有効な場合、このテキストはエンドユーザの言語に翻訳すべきだ」ということを通知します。翻訳可能な文字列をマークすることは開発者の責任です。システムが翻訳できるのは指定された文字列だけなのです。

Django は、翻訳文字列を メッセージファイル に抽出するユーティリティを提供します。このファイルがあるため、翻訳者が対象の言語において翻訳文字列に対応する文字列を用意することが簡単になります。翻訳者がメッセージファイルの中身を書き終わったら、それをコンパイルしなければなりません。このプロセスは GNU gettext ツー ルキットに依存しています。

これが済めば、 Django はユーザの言語設定に従って、オンザフライでアプリケーションを利用可能な言語に翻訳するようになります。

Django の国際化フックはデフォルトで有効になっており、これは、フレームワークの特定の箇所で i18n に関する小さなオーバーヘッドが発生することを意味します。国際化を使わない場合は、 USE_I18N = False を設定ファイルに記述してください。そうすれば Django は国際化の部品を読み込まないようにして多少の最適化をします。

注釈

プロジェクトで翻訳機能が有効になっていることを確認してください (最も早いのは、 MIDDLEWAREdjango.middleware.locale.LocaleMiddleware を含むかどうかチェックすることです) 。まだの場合は、Django はどうやって言語の優先順位を検出するのか? を参照してください。

Python コードでの国際化

標準的な翻訳

翻訳対象の文字列を関数 gettext() で指定します。入力の手間を省くため、この関数は短い別名 _ としてインポートするのが慣例です。

注釈

Python の標準ライブラリ gettext モジュールは、グローバルの名前空間に _() という名前で gettext() をインストールします。 Django ではこの方法に従っていませんが、それは以下の理由からです:

  1. 特定のファイルのデフォルトの翻訳メソッドとして gettext_lazy() を使うべき場合もあります。グローバル名前空間に _() がないと、開発者はどれが最も適切な翻訳関数かを考えなければなりません。

  2. アンダースコア文字 (_) は、 Python の対話シェルや doctest で書かれたテストの中では「直前の式の値」を表します。そのため、グローバル名前空間に _() をインストールすると、干渉して問題を引き起こします。 gettext()_() という名前で明示的に import すれば、この問題を回避できます。

_ として別名をつけられる関数は?

(makemessages で使われる) xgettext が動作する方法のせいで、単一の文字列引数を持つ関数のみが _ として import できます。

この例では、"Welcome to my site." というテキストが翻訳文字列としてマークされています:

from django.http import HttpResponse
from django.utils.translation import gettext as _


def my_view(request):
    output = _("Welcome to my site.")
    return HttpResponse(output)

別名を付けなくても同じコードを書くことができます。次の例は、上の例とまったく同一です:

from django.http import HttpResponse
from django.utils.translation import gettext


def my_view(request):
    output = gettext("Welcome to my site.")
    return HttpResponse(output)

翻訳は、計算済みの値に適用されます。次の例は、上の2つの例とまったく同一です:

def my_view(request):
    words = ["Welcome", "to", "my", "site."]
    output = _(" ".join(words))
    return HttpResponse(output)

翻訳は変数にも適用されます。次の例もまた、上の例とまったく同一です:

def my_view(request):
    sentence = "Welcome to my site."
    output = _(sentence)
    return HttpResponse(output)

(上の 2 つの例のように変数や計算済みの値を使う場合の注意点として、Django の翻訳文字列検出ユーティリティ django-admin makemessages はこれらの文字列を検出できません。詳しくは後の makemessages の節で説明します。)

_()gettext() に渡す文字列には、Pyton の標準の名前付き補完構文によるプレースホルダを使えます。例えば:

def my_view(request, m, d):
    output = _("Today is %(month)s %(day)s.") % {"month": m, "day": d}
    return HttpResponse(output)

このテクニックを使うと、言語ごとの翻訳でプレースホルダを並び替えられるようになります。例えば、英語の翻訳文 "Today is Nobember, 26." に対して、スペイン語では "Hoy es 26 de noviembre." となるように、月と日のプレースホルダの位置を入れ替えることができます。

この理由から、引数が複数ある場合には、固定引数を使った補完 (%s%d) ではなく、名前付きの補完 (%(day)s) を使ってください。固定引数による補完を使った場合、プレースホルダを正しい順番で翻訳できない場合があります。

文字列抽出は xgettext コマンドで行われるため、Django がサポートするのは gettext がサポートする構文のみです。特に、Python の f-strings はまだ xgettext でサポートされておらず、JavaScript テンプレート文字列は gettext 0.21+ が必要です。

翻訳者へのコメント

翻訳可能な文字列についてのヒントを翻訳者に示したい場合、文字列の直前の行で Translators キーワードでプレフィックスされたコメントを追加できます。例えば:

def my_view(request):
    # Translators: This message appears on the home page only
    output = gettext("Welcome to my site.")

コメントは、後に記述された翻訳可能な文章に関連付けられた、結果の .po ファイル内に表示され、ほとんどの翻訳ツールで表示されます。

注釈

完全を期すために示すと、以下が結果として生成される対応する .po ファイルの断片です。

#. Translators: This message appears on the home page only
# path/to/python/file.py:123
msgid "Welcome to my site."
msgstr ""

これはテンプレート内でも動作します。詳しくは テンプレートにおける翻訳者のためのコメント を参照してください。

no-op (何もしない) として文字列をマークする

翻訳しない文字列としてマークするためには、django.utils.translation.gettext_noop() を使ってください。文字列は、変数より後で翻訳されます。

ソース言語内に保持すべき構成文字列がある場合、これを使ってください。これは、システムやユーザー (たとえばデータベース内の文字列) を介して交換される一方で、最後の時点 (たとえばユーザに文字列が表示されるとき) で翻訳される必要があるからです。

複数形

複数形のメッセージを指定するには、関数 django.utils.translation.ngettext() を使ってください。

ngettext は 3 つの引数を取ります: 単数形の文字列、複数形の文字列、オブジェクトの数です。

この関数は、Django アプリケーションを数と 複数形 の複雑さが英語で 2 より大きい (その値にかかわらず、単数には 'object'、そして count が 1 ではない全ての場合に 'objects' となります。) 言語にローカライズさせる必要がある場合に役立ちます。

例:

from django.http import HttpResponse
from django.utils.translation import ngettext


def hello_world(request, count):
    page = ngettext(
        "there is %(count)d object",
        "there are %(count)d objects",
        count,
    ) % {
        "count": count,
    }
    return HttpResponse(page)

この例では、オブジェクトの数値は count 変数として翻訳言語に渡されます。

複数形は、複雑で言語によって異なる動作をすることに注意してください。count と 1 の比較は正しいルールではないことがあります。以下のコードは洗練されて見えますが、いくつかの言語に対しては誤った結果を生み出します:

from django.utils.translation import ngettext
from myapp.models import Report

count = Report.objects.count()
if count == 1:
    name = Report._meta.verbose_name
else:
    name = Report._meta.verbose_name_plural

text = ngettext(
    "There is %(count)d %(name)s available.",
    "There are %(count)d %(name)s available.",
    count,
) % {"count": count, "name": name}

あなた自身の単数複数ロジックを実装しないでください。おそらく正しくなりません。このようなケースでは、以下のようなコードを考えてみてください。

text = ngettext(
    "There is %(count)d %(name)s object available.",
    "There are %(count)d %(name)s objects available.",
    count,
) % {
    "count": count,
    "name": Report._meta.verbose_name,
}

注釈

ngettext() を使うときは、リテラルで挿入される全ての変数に対して単一の名前を使うようにしてください。以下の例で、2 つの翻訳文字列でどのように name Python 変数が使われるかに注意してください。この例は、上述した通りいくつかの言語で間違っている以外に、失敗するでしょう:

text = ngettext(
    "There is %(count)d %(name)s available.",
    "There are %(count)d %(plural_name)s available.",
    count,
) % {
    "count": Report.objects.count(),
    "name": Report._meta.verbose_name,
    "plural_name": Report._meta.verbose_name_plural,
}

django-admin compilemessages を実行すると、以下のエラーが発生するでしょう。

a format specification for argument 'name', as in 'msgstr[0]', doesn't exist in 'msgid'

文脈マーカー

単語は複数の意味を持っている場合があります。例えば、英語の "May" は、月名と動詞を指します。翻訳者が異なる文脈でこれらの単語を正しく翻訳できるようにするには、django.utils.translation.pgettext() 関数、もしくは文字列が複数形を必要とする場合は django.utils.translation.npgettext() 関数を使うことができます。両方とも、第 1 変数として文脈文字列を取ります。

結果の .po ファイルで、文字列は同じ文字列に対する異なる文脈マーカーがあるのと同じ頻度で出現し (コンテキストは msgctxt 行に出現します)、これらそれぞれに対して翻訳者が異なる翻訳ができるようにします。

例:

from django.utils.translation import pgettext

month = pgettext("month name", "May")

もしくは:

from django.db import models
from django.utils.translation import pgettext_lazy


class MyThing(models.Model):
    name = models.CharField(
        help_text=pgettext_lazy("help text for MyThing model", "This is the help text")
    )

これは .po ファイル内に以下のように出現します:

msgctxt "month name"
msgid "May"
msgstr ""

文脈マーカーは、translateblocktranslate テンプレートタグによってもサポートされています。

遅延 (Lazy な) 翻訳

Lazy に、つまり遅延的に、値が呼ばれたときではなくアクセスされたときに文字列を翻訳するためには、django.utils.translation (名前の lazy 接尾辞ですぐ分かります) の翻訳関数の lazy バージョンを使ってください、

これらの関数は、Lazy な文字列参照を保持します -- 実際の翻訳ではありません。翻訳自体はテンプレートレンダリング内のように、文字列が文字列コンテキスト内で使われるときに行われます。

これらの関数への呼び出しがモジュールのロード時に実行されるコードパスにある場合は、これは不可欠です。

これは、モデル、フォーム、モデルフォームを定義するときに簡単に発生することができるものです。これは、Django がフィールドを実際にクラスレベルの属性にするように実装するためです。 そのため、以下のような場合は lazy な翻訳を使用してください:

モデルフィールドとリレーションシップの verbose_namehelp_text オプションの値

例えば、以下のモデルの name フィールドのヘルプテキストを翻訳するためには、以下のようにします:

from django.db import models
from django.utils.translation import gettext_lazy as _


class MyThing(models.Model):
    name = models.CharField(help_text=_("This is the help text"))

verbose_name オプションを使えば、ForeignKeyManyToManyFieldOneToOneField リレーションシップの名前を翻訳可能にできます:

class MyThing(models.Model):
    kind = models.ForeignKey(
        ThingKind,
        on_delete=models.CASCADE,
        related_name="kinds",
        verbose_name=_("kind"),
    )

verbose_name と同様に、必要に応じて Django が自動的にタイトルケースに直すので、リレーションシップに対する小文字の冗長な名前テキストを提供する必要があります。

モデルの冗長な名前の値

フォールバックの英語中心とモデルのクラス名を見ることによって Django が動作する冗長な名前の純粋な決定に頼るよりも、明示的な verbose_nameverbose_name_plural オプションを指定することが推奨されます:

from django.db import models
from django.utils.translation import gettext_lazy as _


class MyThing(models.Model):
    name = models.CharField(_("name"), help_text=_("This is the help text"))

    class Meta:
        verbose_name = _("my thing")
        verbose_name_plural = _("my things")

モデルメソッド description@display デコレーターに渡す。

モデルメソッドに対しては、display() デコレータの short_description 引数で Djangoと admin サイトへの翻訳が提供できます。

from django.contrib import admin
from django.db import models
from django.utils.translation import gettext_lazy as _


class MyThing(models.Model):
    kind = models.ForeignKey(
        ThingKind,
        on_delete=models.CASCADE,
        related_name="kinds",
        verbose_name=_("kind"),
    )

    @admin.display(description=_("Is it a mouse?"))
    def is_mouse(self):
        return self.kind.type == MOUSE_TYPE

遅延 (Lazyな) 翻訳オブジェクトの使用方法

gettext_lazy() 呼び出しの結果は、他の Django コード内で文字列 (str オブジェクト) を使うときはいつでも使うことができますが、気まぐれな Python コードでうまく動作しないかもしれません。例えば、requests ライブラリが gettext_lazy オブジェクトを処理しないので、以下は動作しません:

body = gettext_lazy("I \u2764 Django")  # (Unicode :heart:)
requests.post("https://example.com/send", data={"body": body})

gettext_lazy() オブジェクトを非 Django コードに渡す前にテキスト文字列にキャストすることで、この問題を防ぐことができます:

requests.post("https://example.com/send", data={"body": str(body)})

gettext_lazy という長い名前を使いたくない場合は、次のように _ (アンダースコア) としてエイリアスできます。

from django.db import models
from django.utils.translation import gettext_lazy as _


class MyThing(models.Model):
    name = models.CharField(help_text=_("This is the help text"))

モデルやユーティリティ関数内で文字列に gettext_lazy()ngettext_lazy() を使用してマークすることはよく行われます。これらのオブジェクトをコードの他の場所で扱う際には、誤って文字列に変換しないよう注意する必要があります。なぜなら、これらの変換は可能な限り遅延されるべきだからです(正しいロケールが効果を発揮するため)。これには、次に説明するヘルパー関数の使用が必要です。

遅延翻訳と複数形

複数の文字列に対して遅延翻訳 (n[p]gettext_lazy) を使用する場合、一般的に文字列を定義する時点では number 引数を知りません。そのため、 number 引数に整数ではなくキー名を渡すことが許可されています。そうすると、文字列の補間時に number がそのキーの辞書で検索されます。以下に例を示します:

from django import forms
from django.core.exceptions import ValidationError
from django.utils.translation import ngettext_lazy


class MyForm(forms.Form):
    error_message = ngettext_lazy(
        "You only provided %(num)d argument",
        "You only provided %(num)d arguments",
        "num",
    )

    def clean(self):
        # ...
        if error:
            raise ValidationError(self.error_message % {"num": number})

文字列の中に名前のないプレースホルダが1つだけ含まれている場合は、 number 引数で直接補間できます:

class MyForm(forms.Form):
    error_message = ngettext_lazy(
        "You provided %d argument",
        "You provided %d arguments",
    )

    def clean(self):
        # ...
        if error:
            raise ValidationError(self.error_message % number)

文字列のフォーマット: format_lazy()

Python の str.format() メソッドは format_stringstr.format() の引数に遅延 (lazy) 翻訳オブジェクトが含まれている場合には動作しません。その代わりに django.utils.text.format_lazy() を使うことができます。これは結果が文字列に含まれるときだけ str.format() メソッドを実行する遅延オブジェクトを作成します。たとえば、次のようになります:

from django.utils.text import format_lazy
from django.utils.translation import gettext_lazy

...
name = gettext_lazy("John Lennon")
instrument = gettext_lazy("guitar")
result = format_lazy("{name}: {instrument}", name=name, instrument=instrument)

この場合、 result 内の遅延翻訳は、 result 自体が文字列で使用されるとき(通常はテンプレートのレンダリング時)にだけ文字列に変換されます。

その他の遅延翻訳の使い方

翻訳を遅らせたいが、翻訳可能な文字列を別の関数の引数として渡さなければならない場合、この関数を遅延 (Lazy) 呼び出しでラップできます。たとえば次のようにします:

from django.utils.functional import lazy
from django.utils.translation import gettext_lazy as _


def to_lower(string):
    return string.lower()


to_lower_lazy = lazy(to_lower, str)

その後、次のようにします:

lazy_string = to_lower_lazy(_("My STRING!"))

言語のローカライズされた名前

get_language_info(lang_code)[ソース]

get_language_info() 関数は、言語について詳細な情報を提供します:

>>> from django.utils.translation import activate, get_language_info
>>> activate("fr")
>>> li = get_language_info("de")
>>> print(li["name"], li["name_local"], li["name_translated"], li["bidi"])
German Deutsch Allemand False

ディクショナリの namename_localname_translated 属性は、言語の英語名、言語自体、カレントのアクティブ言語をそれぞれ含んでいます。bidi 属性は双方向性の言語に対してのみ True です。

言語情報のソースは django.conf.locale モジュールです。テンプレートコードでも、この情報に対して同じようにアクセスできます。以下を参照してください。

国際化: テンプレート内

Django テンプレート 内での翻訳は 2 つのテンプレートタグと Pthon コードとはやや異なるシンタックスを使います。テンプレートでこれらのタグを使えるようにするには、{% load i18n %} をテンプレートの先頭に対して設置してください。全てのテンプレートタグと同じように、すでに i18n をロードしているテンプレートを extend している場合であっても、翻訳を使用する全てのテンプレートでロードする必要があります。

警告

翻訳された文字列はテンプレートでレンダリングされるときにエスケープされません。これにより、たとえば強調のために HTML を翻訳に含めることができますが、潜在的に危険な文字 (" など) も変更されずにレンダリングされます。

translate テンプレートタグ

{% translate %} テンプレートタグは、(シングルクオートやダブルクオートで囲まれた) 定数文字列や変数コンテンツを翻訳します:

<title>{% translate "This is the title." %}</title>
<title>{% translate myvar %}</title>

noop オプションが存在する場合、変数はそのままですが翻訳の対象外となります。将来的に翻訳を必要とするコンテンツを "もみ消している" ときに役立ちます:

<title>{% translate "myvar" noop %}</title>

内部的には、インラインの翻訳は gettext() 呼び出しを使っています。

テンプレート var (上記の myvar) がタグに渡された場合、最初にランタイムで変数から文字列へと解決し、それからメッセージカタログ内でその文字列をルックアップします。

{% translate %} 内の文字列でテンプレート変数を混ぜることはできません。翻訳が変数 (プレースホルダ) とともに文字列を必要とする場合は、代わりに {% blocktranslate %} を使ってください。

翻訳された文字列を表示させずに取り出したい場合は、以下の構文が使用できます:

{% translate "This is the title" as the_title %}

<title>{{ the_title }}</title>
<meta name="description" content="{{ the_title }}">

実際には、テンプレート内の複数の場所で使用できる文字列を取得したり、他のテンプレートタグやフィルタリングするための引数として出力を使用したりする目的でこれを使うことになるでしょう:

{% translate "starting point" as start %}
{% translate "end point" as end %}
{% translate "La Grande Boucle" as race %}

<h1>
  <a href="/" title="{% blocktranslate %}Back to '{{ race }}' homepage{% endblocktranslate %}">{{ race }}</a>
</h1>
<p>
{% for stage in tour_stages %}
    {% cycle start end %}: {{ stage }}{% if forloop.counter|divisibleby:2 %}<br>{% else %}, {% endif %}
{% endfor %}
</p>

{% translate %} は、context キーワードを使う 文脈マーカー もサポートしています:

{% translate "May" context "month name" %}

blocktranslate テンプレートタグ

translate タグとは対照的に、 blocktranslate タグはプレースホルダーを使うことで、リテラルその他の内容からなる複雑な文章を翻訳できます:

{% blocktranslate %}This string will have {{ value }} inside.{% endblocktranslate %}

オブジェクトの object 属性にアクセスしたり、テンプレートフィルタを使ったりといったテンプレート式を翻訳するには、その式を翻訳ブロック内で使うローカル変数に結びつける必要があります。たとえば、次のようにします:

{% blocktranslate with amount=article.price %}
That will cost $ {{ amount }}.
{% endblocktranslate %}

{% blocktranslate with myvar=value|filter %}
This will have {{ myvar }} inside.
{% endblocktranslate %}

単一の blocktranslate タグ内で複数の式を使うこともできます:

{% blocktranslate with book_t=book|title author_t=author|title %}
This is {{ book_t }} by {{ author_t }}
{% endblocktranslate %}

注釈

次のような、冗長な古い書式もまだサポートされています: {% blocktranslate with book|title as book_t and author|title as author_t %}

他のブロックタグ (例えば {% for %}{% if %}) は blocktranslate タグの中では使えません。

ブロック引数の解決に失敗した場合、 blocktranslatedeactivate_all() 関数を用いて、現在アクティブな言語を一時的に非アクティブにすることで、デフォルトの言語にフォールバックします。

このタグは複数形にも対応しています。使用するには:

  • カウンタの値を count という名前で指定し結び付けます。この値は正しい複数形の選択に使用されます。

  • {% blocktranslate %}{% endblocktranslate %} のタグの中で単数形と複数形を {% plural %} タグで区切って指定してください。

例:

{% blocktranslate count counter=list|length %}
There is only one {{ name }} object.
{% plural %}
There are {{ counter }} {{ name }} objects.
{% endblocktranslate %}

より複雑な例:

{% blocktranslate with amount=article.price count years=i.length %}
That will cost $ {{ amount }} per year.
{% plural %}
That will cost $ {{ amount }} per {{ years }} years.
{% endblocktranslate %}

カウンターの値の追加に複数化機能とローカル変数への値のバインドの両方を使用する場合、 blocktranslate 構文は内部的に ngettext 呼び出しに変換されることに注意してください。つまり、 ngettext 変数に関する注意事項 と同じことに気を付ける必要があります。

URLの逆引きは blocktranslate 内では行えないので、事前に取得(保存)しておく必要があります:

{% url 'path.to.view' arg arg2 as the_url %}
{% blocktranslate %}
This is a URL: {{ the_url }}
{% endblocktranslate %}

翻訳された文字列を表示させずに取り出したい場合は、以下の構文が使用できます:

{% blocktranslate asvar the_title %}The title is {{ title }}.{% endblocktranslate %}
<title>{{ the_title }}</title>
<meta name="description" content="{{ the_title }}">

実際には、テンプレート内の複数の場所で使用できる文字列を取得したり、他のテンプレートタグやフィルタリングするための引数として出力を使用したりする目的でこれを使うことになるでしょう:

{% blocktranslate %} は、context キーワードを使う 文脈マーカー もサポートしています:

{% blocktranslate with name=user.username context "greeting" %}Hi {{ name }}{% endblocktranslate %}

{% blocktranslate %} タグのもう1つの機能は、trimmed オプションです。このオプションは、{% blocktranslate %} タグのコンテンツの先頭と末尾から改行文字を削除し、行の先頭と末尾の空白を置き換え、すべての行を空白文字で区切って1行に結合します。これは、{% blocktranslate %} タグのコンテンツをインデントしても、インデント文字が対応する .po ファイルのエントリに含まれないようにするのに非常に便利で、翻訳作業が簡単になります。

例えば、次の {% blocktranslate %} タグ:

{% blocktranslate trimmed %}
  First sentence.
  Second paragraph.
{% endblocktranslate %}

の出力結果は .po ファイル内では "First sentence. Second paragraph." となります。対照に、 trimmed オプションが指定されていない場合は "\n First sentence.\n Second paragraph.\n" となります。

タグとフィルタに渡される文字列リテラル

タグやフィルタの引数として渡された文字列リテラルは、見慣れた _() 構文を使って翻訳できます:

{% some_tag _("Page not found") value|yesno:_("yes,no") %}

この場合、タグもフィルタも翻訳された文字列を見るので、翻訳を意識する必要はありません。

注釈

この例では、個々の文字列 "yes""no" ではなく、"yes,no" という文字列が翻訳基盤に渡されます。翻訳された文字列は、フィルタをパースするコードが引数をどう分割するかを知るために、カンマを含む必要があります。たとえば、ドイツ語の翻訳者は文字列 "yes,no""ja,nein" (カンマはそのまま) と翻訳するでしょう。

テンプレートにおける翻訳者のためのコメント

Pythonコード の場合と同様に、これらの翻訳者向けのメモはコメントを使用して指定できます。comment タグを使用する場合がその一例です:

{% comment %}Translators: View verb{% endcomment %}
{% translate "View" %}

{% comment %}Translators: Short intro blurb{% endcomment %}
<p>{% blocktranslate %}A multiline translatable
literal.{% endblocktranslate %}</p>

または 一行コメント構文 {# ... ... #} で:

{# Translators: Label of a button that triggers search #}
<button type="submit">{% translate "Go" %}</button>

{# Translators: This is a text of the base template #}
{% blocktranslate %}Ambiguous translatable block of text{% endblocktranslate %}

注釈

完全を期すために示すと、以下が結果として生成される対応する .po ファイルの断片です。:

#. Translators: View verb
# path/to/template/file.html:10
msgid "View"
msgstr ""

#. Translators: Short intro blurb
# path/to/template/file.html:13
msgid ""
"A multiline translatable"
"literal."
msgstr ""

# ...

#. Translators: Label of a button that triggers search
# path/to/template/file.html:100
msgid "Go"
msgstr ""

#. Translators: This is a text of the base template
# path/to/template/file.html:103
msgid "Ambiguous translatable block of text"
msgstr ""

テンプレートで言語を切り替える

テンプレートの中で言語を選択したい場合、 language テンプレートタグが使用できます:

{% load i18n %}

{% get_current_language as LANGUAGE_CODE %}
<!-- Current language: {{ LANGUAGE_CODE }} -->
<p>{% translate "Welcome to our page" %}</p>

{% language 'en' %}
    {% get_current_language as LANGUAGE_CODE %}
    <!-- Current language: {{ LANGUAGE_CODE }} -->
    <p>{% translate "Welcome to our page" %}</p>
{% endlanguage %}

"Welcome to our page" は、1つめのコードでは現在の言語を使って表示されますが、2つめのコードでは常に英語になります。

その他のタグ

これらのタグには同時に {% load i18n %} が必要です。

get_available_languages

{% get_available_languages as LANGUAGES %} はタプルのリストを返します。最初の要素は言語コード language code で、2番目の要素は言語名 (現在アクティブなロケールに翻訳されたもの) です。

get_current_language

{% get_current_language as LANGUAGE_CODE %} は現在のユーザの優先言語を文字列として返します。たとえば en-us です。 Django はどうやって言語の優先順位を検出するのか? を参照してください。

get_current_language_bidi

{% get_current_language_bidi as LANGUAGE_BIDI %} は現在のロケールの文章を書く方向を返します。もし True なら、ヘブライ語やアラビア語などの右から左へ書く言語です。もし False なら、英語、フランス語、ドイツ語などの左から右へ書く言語です。

i18n コンテキストプロセッサ

もし django.template.context_processors.i18n コンテキストプロセッサを有効にすると、各 RequestContext は上で定義した LANGUAGES, LANGUAGE_CODE, LANGUAGE_BIDI にアクセスできるようになります。

get_language_info

また、提供されているテンプレートタグとフィルタを使えば、利用可能な言語の情報を取得することもできます。一つの言語に関する情報を取得するには {% get_language_info %} タグを使います:

{% get_language_info for LANGUAGE_CODE as lang %}
{% get_language_info for "pl" as lang %}

その後、下記のように情報にアクセスできます:

Language code: {{ lang.code }}<br>
Name of language: {{ lang.name_local }}<br>
Name in English: {{ lang.name }}<br>
Bi-directional: {{ lang.bidi }}
Name in the active language: {{ lang.name_translated }}

get_language_info_list

また、{% get_language_info_list %} のテンプレートタグを使えば、言語のリスト(例えば LANGUAGES で指定されたアクティブな言語)の情報を取得できます。{% get_language_info_list %} を使って言語セレクタを表示する方法の例は set_language リダイレクトビューのセクション を参照してください。

LANGUAGES 形式のタプルのリストに加え、{% get_language_info_list %} は言語コードのリストをサポートしています。これをビューで実行すると:

context = {"available_languages": ["en", "es", "fr"]}
return render(request, "mytemplate.html", context)

を使用すると、テンプレート内でそれらの言語のリストをイテレートできます:

{% get_language_info_list for available_languages as langs %}
{% for lang in langs %} ... {% endfor %}

テンプレートフィルタ

いくつかの便利なフィルタが使えます:

  • {{ LANGUAGE_CODE|language_name }} ("German")

  • {{ LANGUAGE_CODE|language_name_local }} ("Deutsch")

  • {{ LANGUAGE_CODE|language_bidi }} (False)

  • {{ LANGUAGE_CODE|language_name_translated }} ("německy", when active language is Czech)

国際化: JavaScript コード内

JavaScript に対する翻訳には、いくつか問題があります:

  • JavaScript コードは gettext 実行にアクセスできません。

  • JavaScript コードは .po.mo ファイルにアクセスできません; サーバーによって配布される必要があります。

  • JavaScript 用の翻訳カタログはできる限り小さくなければなりません。

Django には、これらの問題に対する総合的な解決策が用意されています: 翻訳を JavaScript に渡し、gettext などを JavaScript 内から呼び出せるようにする方法です。

主な解決策は以下の JavaScriptCatalog ビューです。これは、gettext を擬似的に再現する関数と翻訳文字列の配列で JavaScript コードライブラリを生成します。

JavaScriptCatalog ビュー

class JavaScriptCatalog[ソース]

gettext インターフェイスを擬似的に再現する関数と翻訳文字列の配列で、JavaScriptのコードライブラリを生成するビューです。

属性

domain

ビューの出力に追加する文字列を含む翻訳ドメインです。デフォルトは 'djangojs' です。

packages

インストールされたアプリケーション間の application names のリストです。これらのアプリケーションは locale ディレクトリを含んでいる必要があります。これらのカタログに加えて (常に含まれている) LOCALE_PATHS 内で見つかった全てのカタログは、1 つのカタログに統合されます。デフォルトは None で、全ての INSTALLED_APPS からの利用可能な翻訳は JavaScript の出力に渡されることを意味します。

デフォルト値を使った例:

from django.views.i18n import JavaScriptCatalog

urlpatterns = [
    path("jsi18n/", JavaScriptCatalog.as_view(), name="javascript-catalog"),
]

独自のパッケージを使った例:

urlpatterns = [
    path(
        "jsi18n/myapp/",
        JavaScriptCatalog.as_view(packages=["your.app.label"]),
        name="javascript-catalog",
    ),
]

ルートの URLconf が i18n_patterns()JavaScriptCatalog を使っている場合、カタログが正しく生成されるように i18n_patterns() によるラップも必要となります。

i18n_patterns() を使った例:

from django.conf.urls.i18n import i18n_patterns

urlpatterns = i18n_patterns(
    path("jsi18n/", JavaScriptCatalog.as_view(), name="javascript-catalog"),
)

翻訳の優先順位は、packages 引数で後に現れるパッケージの方が、最初に現れるものよりも高くなります。これは、同じリテラルに対する翻訳が衝突する場合に重要です。

サイト上で複数の JavaScriptCatalog ビューを使っていてそのうちのいくつかが同じ文字列を定義している場合、最後にロードされたカタログ内の文字列が優先されます。

JavaScript 翻訳カタログを使う

カタログを使うには、以下のように動的に生成されたスクリプトを参照してください:

<script src="{% url 'javascript-catalog' %}"></script>

これは、reverse URL を使用して、JavaScript カタログビューの URL をルックアップします。カタログがロードされると、JavaScript コードは以下のメソッドを使えるようになります:

  • gettext

  • ngettext

  • interpolate

  • get_format

  • gettext_noop

  • pgettext

  • npgettext

  • pluralidx

gettext

gettext 関数は、Python コード内の標準の gettext インターフェースと同じように動作します:

document.write(gettext("this is to be translated"))

ngettext

ngettext 関数は単語やフレーズを複数形にするインターフェースを提供します:

const objectCount = 1 // or 0, or 2, or 3, ...
const string = ngettext(
    'literal for the singular case',
    'literal for the plural case',
    objectCount
);

interpolate

interpolate 関数は表示形式の文字列の動的な記入をサポートします。補完の構文は Python を模しているので、interpolate 関数は位置および名前付きの補完の両方をサポートします。

  • 位置による補完: obj は、表示されるのと同じ順番で要素の値が一致する fmt プレースホルダ内で連続的に補完される JavaScript Array オブジェクトを含んでいます。例えば:

    const formats = ngettext(
      'There is %s object. Remaining: %s',
      'There are %s objects. Remaining: %s',
      11
    );
    const string = interpolate(formats, [11, 20]);
    // string is 'There are 11 objects. Remaining: 20'
    
  • 名前付きの補完: このモードは named パラメータを true として渡すことで選択されます。obj は JavaScript オブジェクトや結合配列を含みます。例えば:

    const data = {
      count: 10,
      total: 50
    };
    
    const formats = ngettext(
        'Total: %(total)s, there is %(count)s object',
        'there are %(count)s of a total of %(total)s objects',
        data.count
    );
    const string = interpolate(formats, data, true);
    

ただし文字列補間は、やり過ぎるべきではありません。これはまだ JavaScript なので、コードは何度も正規表現による置換を行う必要があります。これは Python の文字列補間ほど速くはないので、本当に必要な場合(例えば、ngettext と組み合わせて適切な複数形を生成する場合など)に限定してください。

get_format

get_format 関数は設定済みの i18n 表示形式設定にアクセスでき、与えられた設定名に対して表示形式文字列を取り出します:

document.write(get_format('DATE_FORMAT'));
// 'N j, Y'

以下の設定に対してアクセスします:

これは、Python によってレンダリングされる値と一貫性を持った表示形式を保守するために役立ちます。

gettext_noop

これは gettext 関数をエミュレートしますが、何もせず、渡されたものをそのまま返します:

document.write(gettext_noop("this will not be translated"))

これは、将来的に翻訳が必要なコードの部分をもみ消しておくのに役立ちます。

pgettext

pgettext 関数は Python の変数 (pgettext()) のように動作し、文脈的に翻訳される単語を提供します:

document.write(pgettext("month name", "May"))

npgettext

npgettext 関数も Python 変数 (npgettext()) のように動作し、複数形の 文脈的に翻訳される単語を提供します:

document.write(npgettext('group', 'party', 1));
// party
document.write(npgettext('group', 'party', 2));
// parties

pluralidx

pluralidx 関数は pluralize テンプレートフィルタと同じように動作し、与えられた count が単語の複数形を使うべきかどうかを決めます:

document.write(pluralidx(0));
// true
document.write(pluralidx(1));
// false
document.write(pluralidx(2));
// true

最も単純なケースでは、独自の複数形が必要ない場合、これは整数 1 に対して false を返し、他の数に対して true を返します。

ただし、複数形は全ての言語でこのようにシンプルではありません。言語が複数形をサポートしていない場合、空の値が渡されます。

加えて、複数形に関して複雑なルールが存在する場合、カタログビューは条件付きの表現をレンダリングします。これは、値を true (複数形) と false (複数形 ではない ) のどちらにするかを評価します。

JSONCatalog ビュー

class JSONCatalog[ソース]

他のクライアントサイドのライブラリを使って翻訳を扱うためには、JSONCatalog を利用するのがいいでしょう。JavaScriptCatalog と似ていますが、JSON のレスポンスを返します。

使える値を知るには、JavaScriptCatalog を参照してください。また、domainpackages 属性を使用してください。

レスポンスの形式は以下の通りです:

{
    "catalog": {
        # Translations catalog
    },
    "formats": {
        # Language formats for date, time, etc.
    },
    "plural": "..."  # Expression for plural forms, or null.
}

実行速度についての覚え書き

たくさんの JavaScript/JSON i18n ビューは全てのリクエストで .mo ファイルからカタログを生成します。出力は一定なので、少なくとも与えられたサイトのバージョンに対しては、キャッシュが有力な選択肢となります。

サーバーサイドのキャッシシュは CPU の負担を軽減します。 cache_page() デコレータを使うと簡単に実装できます。翻訳を変更したときにキャッシュの無効部分をトリガーするには、以下の例にあるようにバージョンに依存したキープレフィックスを使ってください。もしくは、バージョンに依存した URL でビューをマッピングしてください:

from django.views.decorators.cache import cache_page
from django.views.i18n import JavaScriptCatalog

# The value returned by get_version() must change when translations change.
urlpatterns = [
    path(
        "jsi18n/",
        cache_page(86400, key_prefix="jsi18n-%s" % get_version())(
            JavaScriptCatalog.as_view()
        ),
        name="javascript-catalog",
    ),
]

クライアントサイドのキャッシュは帯域を節約し、サイトの読み込みを速くします。ETags (ConditionalGetMiddleware) を使っている場合、すでに適用済みです。それ以外の場合は、条件付きデコレータ を参照してみてください。以下の例では、アプリケーションサーバを再起動するとキャッシュの効力がなくなります:

from django.utils import timezone
from django.views.decorators.http import last_modified
from django.views.i18n import JavaScriptCatalog

last_modified_date = timezone.now()

urlpatterns = [
    path(
        "jsi18n/",
        last_modified(lambda req, **kw: last_modified_date)(
            JavaScriptCatalog.as_view()
        ),
        name="javascript-catalog",
    ),
]

You can even pre-generate the JavaScript catalog as part of your deployment procedure and serve it as a static file. This radical technique is implemented in django-statici18n.

国際化: URL パターン内

Django には、URL パターンを国際化するための 2 つの仕組みが用意されています:

  • LocaleMiddleware が、リクエストされた URL からアクティブにする言語を検出できるようにするため、URL パターンのルートに言語のプレフィックスを追加します。

  • URL パターン自体を django.utils.translation.gettext_lazy() 関数を通じて翻訳可能にする。

警告

この 2 つのうちどちらを使う場合でも、各リクエストに対してアクティブな言語をセットすることが必要となります; 言い換えれば、MIDDLEWARE 設定内に django.middleware.locale.LocaleMiddleware を記述しておく必要があります。

URL パターンにおける言語プレフィックス

i18n_patterns(*urls, prefix_default_language=True)[ソース]

この関数は、ルートの URLconf で使うことができ、Django は自動的に現在アクティブな言語のコードを i18n_patterns() で定義された全ての URL パターンの先頭に追加します。

prefix_default_languageFalse にセットすると、デフォルト言語 (LANGUAGE_CODE) からプレフィックスを除去します。これは、現在の URL を変更しないよう既存のサイトに翻訳を追加するとき、役に立ちます。

URL パターンの例:

from django.conf.urls.i18n import i18n_patterns
from django.urls import include, path

from about import views as about_views
from news import views as news_views
from sitemap.views import sitemap

urlpatterns = [
    path("sitemap.xml", sitemap, name="sitemap-xml"),
]

news_patterns = (
    [
        path("", news_views.index, name="index"),
        path("category/<slug:slug>/", news_views.category, name="category"),
        path("<slug:slug>/", news_views.details, name="detail"),
    ],
    "news",
)

urlpatterns += i18n_patterns(
    path("about/", about_views.main, name="about"),
    path("news/", include(news_patterns, namespace="news")),
)

これらの URL パターンを定義すると、Django は自動的に i18n_patterns 関数で追加された URL パターンに言語プレフィックスを追加します。例:

>>> from django.urls import reverse
>>> from django.utils.translation import activate

>>> activate("en")
>>> reverse("sitemap-xml")
'/sitemap.xml'
>>> reverse("news:index")
'/en/news/'

>>> activate("nl")
>>> reverse("news:detail", kwargs={"slug": "news-slug"})
'/nl/news/news-slug/'

prefix_default_language=FalseLANGUAGE_CODE='en' を使うと、URL は以下のようになります:

>>> activate("en")
>>> reverse("news:index")
'/news/'

>>> activate("nl")
>>> reverse("news:index")
'/nl/news/'

警告

i18n_patterns() はルートの URLconf のみで使うことができます。インクルードされた URLconf で使うと ImproperlyConfigured 例外を投げます。

警告

自動的に追加される言語プレフィックスと重複するようなプレフィックスを持った URL パターンにしないように注意してください。

URL パターンを翻訳する

URL パターンは、gettext_lazy() 関数を使って、翻訳可能にマークすることもできます。例えば:

from django.conf.urls.i18n import i18n_patterns
from django.urls import include, path
from django.utils.translation import gettext_lazy as _

from about import views as about_views
from news import views as news_views
from sitemaps.views import sitemap

urlpatterns = [
    path("sitemap.xml", sitemap, name="sitemap-xml"),
]

news_patterns = (
    [
        path("", news_views.index, name="index"),
        path(_("category/<slug:slug>/"), news_views.category, name="category"),
        path("<slug:slug>/", news_views.details, name="detail"),
    ],
    "news",
)

urlpatterns += i18n_patterns(
    path(_("about/"), about_views.main, name="about"),
    path(_("news/"), include(news_patterns, namespace="news")),
)

翻訳を作成した後に、reverse() 関数はアクティブな言語で URL を返します。例えば:

>>> from django.urls import reverse
>>> from django.utils.translation import activate

>>> activate("en")
>>> reverse("news:category", kwargs={"slug": "recent"})
'/en/news/category/recent/'

>>> activate("nl")
>>> reverse("news:category", kwargs={"slug": "recent"})
'/nl/nieuws/categorie/recent/'

警告

ほとんどの場合、( i18n_patterns() を使用して)翻訳されたURLは、言語コードが接頭辞として付けられたパターンのブロック内でのみ使用することが最も良い選択です。不用意に翻訳された URL が翻訳されていない URL パターンと衝突するのを避けるためです。

テンプレート内で逆引きする

ローカライズされた URL がテンプレート内でリバースされる場合、常に現在の言語が使用されます。他の言語の URL にリンクを貼るには、language テンプレートタグを使ってください。これは、テンプレートの囲まれた範囲内で、指定された言語を有効化します:

{% load i18n %}

{% get_available_languages as languages %}

{% translate "View this category in:" %}
{% for lang_code, lang_name in languages %}
    {% language lang_code %}
    <a href="{% url 'category' slug=category.slug %}">{{ lang_name }}</a>
    {% endlanguage %}
{% endfor %}

language タグは引数のみとして言語コードを予期します。

ローカライズ: 言語ファイルを作成する方法

アプリケーションの文字列リテラルが後の翻訳にためにタグづけされ終わったら、翻訳自体を記述する (もしくは取得する) 必要があります。以下は、その方法です。

メッセージファイル

最初のステップは、新しい言語に対する message file を作成することです。メッセージファイルはプレーンテキストファイルで、単一の言語を記述します。このファイルは、全ての翻訳可能な文字列とそれらがどのように与えられた言語で表示されるかを含みます。メッセージファイルは .po ファイル拡張子を持ちます。

Djangoには django-admin makemessages というツールが用意してあり、これらのファイルの作成と保全を自動化します。

Gettext ユーティリティ

makemessages コマンド (と後述する compilemessages) は、GNU gettext ツールセットからのコマンドを使用します: xgettextmsgfmtmsgmergemsguniq です。

サポートされている gettext ユーティリティの最小バージョンは 0.15 です。

メッセージファイルを作成または更新するには、以下のコマンドを実行してください:

django-admin makemessages -l de

...ここでの de は作成したいメッセージファイルに対するロケール名 locale name です。例えば、ブラジル系のポルトガル語に対する pt_BR、オーストリア系のドイツ語に対する de_AT、インドネシアに対する id などです。

スクリプトは、2 つの場所のうち 1 つから実行する必要があります:

  • Django プロジェクトのルートディレクトリ (manage.py を含むところ)。

  • Django アプリケーションのルートディレクトリ。

スクリプトは、プロジェクトのソースツリーもしくはアプリケーションのソースツリーを通じて実行され、翻訳用にマークされた全ての文字列 (Djangoはどうやって翻訳を見つけるのか? を参照して LOCALE_PATHS を正しく設定してください) を抽出します。ディレクトリ locale/LANG/LC_MESSAGES にメッセージファイルを作成 (もしくは更新) します。de の例では、ファイルは locale/de/LC_MESSAGES/django.po となります。

プロジェクトのルートディレクトリから makemessages を実行したとき、抽出された文字列は自動的に適切なメッセージファイルへと分配されます。つまり、locale ディレクトリを含むアプリケーションのファイルから抽出された文字列は、そのディレクトリ下のメッセージファイルに格納されます。locale ディレクトリがないアプリケーションのファイルから抽出された文字列は、LOCALE_PATHS 内に最初にリストアップされたディレクトリ下にあるメッセージファイルか、もしくは LOCALE_PATHS が空の場合はエラーを生成します。

デフォルトでは django-admin makemessages.html.txt または py のファイル拡張子を持つファイルを全て検査します。このデフォルトの動作をオーバーライドしたい場合は、 --extension-e オプションを使って検査するファイル拡張子を指定します:

django-admin makemessages -l de -e txt

複数の拡張子をカンマで区切るか、-e または --extension を複数回使用してください:

django-admin makemessages -l de -e html,txt -e xml

警告

JavaScript のソースコードからメッセージファイルを作成する とき、 -e js ではなく 、特別な djangojs ドメインを使用する必要があります。

Jinja2 テンプレートを使う?

makemessages はJinja2テンプレートの構文を理解しません。Jinja2テンプレートを含むプロジェクトから文字列を抽出するには、代わりに BabelMessage Extracting を使ってください。

以下は babel.cfg 設定ファイルの例です:

# Extraction from Python source files
[python: **.py]

# Extraction from Jinja2 templates
[jinja2: **.jinja]
extensions = jinja2.ext.with_

使用しているすべての拡張機能をリストアップしてください!そうしないと、Babel はこれらの拡張機能によって定義されたタグを認識せず、それらを含む Jinja2 テンプレートを完全に無視します。

Babel は makemessages と似たような機能を提供し、一般的に置き換えることができ、 gettext に依存しません。より詳しい情報は メッセージカタログの操作 に関するドキュメントを参照してください。

gettext がない?

gettext ユーティリティがインストールされていない場合、 makemessages は空のファイルを作成します。そのような場合は、 gettext ユーティリティをインストールするか、英語のメッセージファイル (locale/ja/LC_MESSAGES/django.po) があれば、それをコピーして、空の翻訳ファイルから始めてください。

Windows で動作する?

あなたが Windows を使っていて、makemessages が動作するように GNU gettext ユーティリティをインストールする必要がある場合は、Windows での gettext で詳しい情報を参照してください。

.po ファイルは、翻訳保守者の連絡先情報などのわずかなメタデータを含みますが、大部分は メッセージ のリストです。これは、特定の言語に対する、翻訳文字列と実際の翻訳済みテキストのマッピングです。

例えば以下のように、Django アプリケーションが "Welcome to my site." に対する翻訳文字列を含んでいる場合、:

_("Welcome to my site.")

...それから django-admin makemessages.po ファイルを生成し、そのファイルは以下のようなスニペットを含みます -- メッセージ:

#: path/to/python/module.py:23
msgid "Welcome to my site."
msgstr ""

簡単に説明すると:

  • msgid は翻訳文字列で、ソース内に存在します。変更しないでください。

  • msgstr はあなたが言語に対応する翻訳を記述する場所です。これは空の状態から始まるので、変更するのはあなたの役割です。翻訳をクオートで囲むようにしてください。

  • 利便性のため、各メッセージは、# でプレフィックスされ msgid 行の直前に位置するコメント行の形式の中に、翻訳文字列がどこから拾い集められたかを示すファイル名と行数を含みます。

長いメッセージは特殊なケースです。そこでは、msgstr (もしくは msgid) 直後の最初の文字列は空の文字列です。それからコンテンツ自身が行ごとに 1 つの文字列として次の数行にわたって記述されます。これらの文字列は直接連結されます。文字列内で最後のスペースを忘れないでください; さもなくば、空白スペースなしで接合されることになります!

文字コードを気にしてください

gettext ツールが内部的に動作する方法および非 ASCII ソースの文字列を Django のコアとあなたのアプリケーション内で許容するために、 .po ファイルに対するエンコーディングとして UTF-8 ( .po ファイルが生成される際のデフォルトです) を 使わなければなりません。これは、全員が同じエンコーディングを使用することを意味し、Django が .po ファイルを処理する際に重要となります。

Fuzzy(あいまいな)エントリー

makemessages は翻訳が以前に翻訳された文字列から推測される場合などに、あいまい (fuzzy) としてマークされた翻訳エントリーを生成することがあります。デフォルトでは、あいまいなエントリは compilemessages によって処理されません。

新しい翻訳文字列に対して全てのコードとテンプレートを再検査して 全ての 言語に対する全てのメッセージファイルを更新するには、以下を実行してください:

django-admin makemessages -a

メッセージファイルをコンパイルする

メッセージファイルを作成した後 -- そして変更を加えたときそれぞれで -- gettext による使用のために、より効率的な形式にコンパイルする必要があります。django-admin compilemessages ユーティリティを利用してください。

このツールは、全ての有効な .po ファイルを通じて動作し、.mo ファイルを生成します。これは gettext による使用のために最適化されたバイナリファイルです。django-admin makemessages を実行した同じディレクトリで、以下のように django-admin compilemessages を実行してください:

django-admin compilemessages

これで終わりです。翻訳は使う準備ができました。

Windows で動作する?

あなたが Windows を使っていて、django-admin compilemessages が動作するように GNU gettext ユーティリティをインストールする必要がある場合は、Windows での gettext で詳しい情報を参照してください。

.po ファイル: エンコーディングと BOM の使用

Django は、UTF-8 でエンコードされ BOM (Byte Order Mark) を持たない .po ファイルのみをサポートします。そのため、テキストエディタがデフォルトでこれらのマークをファイルの最初に追加した場合、再構成する必要があります。

トラブルシューティング: gettext() がパーセント文字を伴う文字列内で python-format を誤って見つけてしまう

例えば、パーセント記号に続いて空白と 文字列変換タイプ がある文字列(例: _("10% interest"))などに、gettext() は誤って python-format で文字列をフラグ付けすることがあります。

誤ってフラグが立てられた文字列でメッセージファイルをコンパイルしようとすると、number of format specifications in 'msgid' and 'msgstr' does not match もしくは 'msgstr' is not a valid Python format string, unlike 'msgid'. といったエラーが発生します。

これを解決するには、パーセント文字を 2 つ重ねてエスケープします:

from django.utils.translation import gettext as _

output = _("10%% interest")

もしくは no-python-format を使ってパーセント文字がリテラルとして扱われるようにします:

# xgettext:no-python-format
output = _("10% interest")

JavaScript ソースコードからメッセージファイルを作成する

他の Django メッセージファイルと同じ方法で (django-admin makemessages ツールを使って) 作成や更新を行います。唯一の違いは、-d djangojs パラメータを提供することによって、gettext 用語がドメインであること (このケースでは djangojs ドメイン) を、以下のように明示的に指定する必要があることです:

django-admin makemessages -d djangojs -l de

これは、ドイツ語に対して JavaScript 用のメッセージファイルを作成または更新します。メッセージファイルの更新後、通常の Django ファイルと同様の方法で django-admin compilemessages を実行してください。

Windows での gettext

これはメッセージ ID を抽出したり、メッセージファイル(.po)をコンパイルしたい人だけが必要です。翻訳作業そのものは既存のこの種類のファイルを編集することになりますが、独自のメッセージファイルを作成したい場合や、変更したメッセージファイルをテストしたりコンパイルしたりしたい場合は、 コンパイル済みのバイナリインストーラ をダウンロードしてください。

xgettext --version コマンドが正しく動作する限り、他の場所で入手した gettext バイナリを使うこともできます。Windows のコマンドプロンプトでコマンド xgettext --version を入力すると、ポップアップウィンドウに "xgettext.exe has generated errors and will be closed by Windows" と表示される場合は、 gettext パッケージを使って Django の翻訳ユーティリティを使おうとしないでください。

makemessages コマンドをカスタマイズする

xgettext に追加のパラメータを渡したい場合、独自の makemessages コマンドを作成して、その xgettext_options 属性をオーバーライドする必要があります:

from django.core.management.commands import makemessages


class Command(makemessages.Command):
    xgettext_options = makemessages.Command.xgettext_options + ["--keyword=mytrans"]

もっと柔軟に使う必要があるとき場合には、makemessages コマンドに新しい引数を追加することもできます:

from django.core.management.commands import makemessages


class Command(makemessages.Command):
    def add_arguments(self, parser):
        super().add_arguments(parser)
        parser.add_argument(
            "--extra-keyword",
            dest="xgettext_keywords",
            action="append",
        )

    def handle(self, *args, **options):
        xgettext_keywords = options.pop("xgettext_keywords")
        if xgettext_keywords:
            self.xgettext_options = makemessages.Command.xgettext_options[:] + [
                "--keyword=%s" % kwd for kwd in xgettext_keywords
            ]
        super().handle(*args, **options)

その他

set_language リダイレクトビュー

set_language(request)[ソース]

利便性のために、 Django にはビュー django.views.i18n.set_language() が付属しており、 ユーザの言語設定を行い、指定された URL にリダイレクトします。デフォルトでは前のページに戻ります。

URLconfに以下の行を追加して、このビューを有効にします:

path("i18n/", include("django.conf.urls.i18n")),

(この例では /i18n/setlang/ でビューを利用できるようにしていることに注意してください)。

警告

上記の URL を i18n_patterns() 内に含めないようにしてください。正しく動作するためには、それ自身が言語に依存せず独立している必要があります。

このビューは POST メソッドで呼び出され、 language パラメータがリクエストに設定されることを想定しています。セッションサポートが有効な場合、ビューは言語選択をユーザのセッションに保存します。また、デフォルトでは django_language という名前のクッキーにも言語選択を保存します。(この名前は LANGUAGE_COOKIE_NAME 設定で変更できます)。

言語選択を設定した後、Django は POST または GET データの next パラメータを探します。もしそれが見つかり、 Django が安全な URL (つまり、別のホストを指しておらず、安全なスキームを使っている) と判断した場合、その URL へのリダイレクトが実行されます。そうでない場合、 Django はリクエストの性質に応じて、 Referer ヘッダの URL にリダイレクトしたり、ヘッダが設定されていない場合は / にリダイレクトしたりします:

  • リクエストが(Accept HTTPヘッダに基づいて)HTMLコンテンツを受け入れる場合、フォールバックは常に実行されます。

  • リクエストがHTMLを受け付けない場合、 next パラメータが指定されている場合のみフォールバックが実行されます。そうでない場合はステータスコード 204 (No Content) が返されます。

以下はHTMLテンプレートコードの例です:

{% load i18n %}

<form action="{% url 'set_language' %}" method="post">{% csrf_token %}
    <input name="next" type="hidden" value="{{ redirect_to }}">
    <select name="language">
        {% get_current_language as LANGUAGE_CODE %}
        {% get_available_languages as LANGUAGES %}
        {% get_language_info_list for LANGUAGES as languages %}
        {% for language in languages %}
            <option value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected{% endif %}>
                {{ language.name_local }} ({{ language.code }})
            </option>
        {% endfor %}
    </select>
    <input type="submit" value="Go">
</form>

この例では、 Django は redirect_to コンテキスト変数で、ユーザがリダイレクトされるページの URL を調べます。

明示的にアクティブな言語をセットする

明示的に、現在のセッションに対して、アクティブな言語をセットしたくなるかもしれません。例えば、他のシステムを活用すれば、ユーザの言語設定を取得できます。django.utils.translation.activate() はすでに紹介済みですね。これは現在のスレッドに対してのみ適用されます。セッション全体の言語をクッキーに恒久的に保持するには、レスポンスに LANGUAGE_COOKIE_NAME クッキーを設定します。

from django.conf import settings
from django.http import HttpResponse
from django.utils import translation

user_language = "fr"
translation.activate(user_language)
response = HttpResponse(...)
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, user_language)

通常、両方を使う必要があるでしょう: django.utils.translation.activate() はこのスレッドに対して言語を変更します。そして、クッキーをセットするとこの設定が将来のリクエストに恒久的に反映されます。

ビューやテンプレートの外で翻訳を使う

Django はビューやテンプレートで使うための豊富な国際化ツールセットを提供していますが、Django 固有のコードに使い方を限定しているわけではありません。Django の翻訳システムを使えば、任意のテキストを Django がサポートしている任意の言語に翻訳できます (もちろん、適切な翻訳カタログが存在する場合に限ります)。翻訳カタログを読み込んで有効にし、テキストを好きな言語に翻訳することも可能ですが、元の言語に戻すことを忘れないでください。翻訳カタログの有効化はスレッド単位で行われ、そのような変更は同じスレッドで実行されているコードに影響するからです。

例:

from django.utils import translation


def welcome_translated(language):
    cur_language = translation.get_language()
    try:
        translation.activate(language)
        text = translation.gettext("welcome")
    finally:
        translation.activate(cur_language)
    return text

この関数を 'de' という値で呼び出すと、 LANGUAGE_CODE やミドルウェアで設定されている言語に関係なく、 "Willkommen" が返されます。

特に注目すべき関数は、現在のスレッドで使われている言語を返す django.utils.translation.get_language() 、現在のスレッドの翻訳カタログを有効にする django.utils.translation.activate() 、与えられた言語が Django でサポートされているかどうかをチェックする django.utils.translation.check_for_language() です。

より簡潔なコードを書くために、コンテキストマネージャ django.utils.translation.override() も用意されています、これは実行時に現在の言語を保存し、終了時にそれを復元します。これを使うと、上の例は次のようになります:

from django.utils import translation


def welcome_translated(language):
    with translation.override(language):
        return translation.gettext("welcome")

実装時のためのノート

Djangoの翻訳の特徴

Django の翻訳システムは Python に付属している標準の gettext モジュールを使います。 gettext を知っていれば、Django が翻訳を行う方法について、以下のような特徴があることに気づくかもしれません:

  • 文字列ドメインは django または djangojs です。この文字列ドメインは、共通のメッセージファイルライブラリ (通常は /usr/share/locale/) にデータを保存する異なるプログラムを区別するために使用されます。 django ドメインは Python とテンプレートの翻訳文字列に使われ、グローバル翻訳カタログに読み込まれます。 djangojs ドメインはできるだけ小さくなるように、 JavaScript の翻訳カタログにだけ使われます。

  • Django は xgettext だけを使っているわけではありません。これは xgettextmsgfmt を Python でラップしたものです。これは主に利便性のためです。

Django はどうやって言語の優先順位を検出するのか?

翻訳を準備したら、あるいは Django に付属している翻訳を使いたい場合は、アプリの設定で翻訳を有効にする必要があります。

Djangoは、インストール全体、特定のユーザー、またはその両方に対して、使用する言語を決定する非常に柔軟なモデルを持っています。

インストール全体の優先言語を設定するには、 LANGUAGE_CODE を設定してください。Django はこの言語をデフォルトの翻訳として使います。ロケールミドルウェアが採用するメソッド (下記参照) を使って、より適切な翻訳が見つからなかった場合の最終的な翻訳です。

Django を母国語で動かしたいだけなら、 LANGUAGE_CODE を設定し、対応する メッセージファイル とそのコンパイルバージョン (.mo) が存在することを確認するだけです。

各ユーザに好きな言語を指定させたい場合は、 LocaleMiddleware を使用する必要があります。ロケール ミドルウェア LocaleMiddleware はリクエストのデータに基づいて言語を選択できるようにします。これは各ユーザのためにコンテンツをカスタマイズします。

LocaleMiddleware を使うには、django.middleware.locale.LocaleMiddleware'MIDDLEWARE 設定に追加します。ミドルウェアの順番は重要なので、以下のガイドラインに従ってください:

  • 最初にインストールされるミドルウェアの1つであることを確認してください。

  • なぜなら LocaleMiddleware はセッションデータを利用するからです。また、 CommonMiddleware はリクエストされたURLを解決するために有効な言語を必要とするので、 CommonMiddleware の前に来る必要があります。

  • CacheMiddleware を使用する場合は、その後ろに LocaleMiddleware を記述してください。

たとえば、MIDDLEWARE は次のようになります:

MIDDLEWARE = [
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.locale.LocaleMiddleware",
    "django.middleware.common.CommonMiddleware",
]

(ミドルウェアについては ミドルウェアのドキュメント を参照してください)。

LocaleMiddleware は以下のアルゴリズムに従ってユーザーの言語設定を決定しようとします:

  • 最初に、リクエストされたURLの言語プレフィックスを探します。 これはルートの URLconf で i18n_patterns 関数を使用している場合にだけ実行されます。言語プレフィックスとURLパターンの国際化方法についての詳細は 国際化: URL パターン内 を参照してください。

  • それに失敗すれば、クッキーを探します。

    使用されるクッキーの名前は LANGUAGE_COOKIE_NAME 設定によって設定されます。(デフォルトの名前は django_language です)。

  • それに失敗した場合は Accept-Language HTTPヘッダを調べます。このヘッダはブラウザから送られ、サーバにどの言語を優先するのかを伝えます。Django は、翻訳可能な言語が見つかるまで、ヘッダの各言語を試します。

  • それに失敗すると、グローバルな LANGUAGE_CODE 設定を使用します。

メモ:

  • これらの場所では、言語設定は標準の 言語コード 文字列で指定します。たとえば、ブラジルポルトガル語は pt-br です。

  • ベース言語が利用可能で、指定されたサブ言語が利用できない場合、 Django はベース言語を使います。たとえば、ユーザが de-at (オーストリアのドイツ語) を指定したが、 Django には de しかない場合、 Django は de を使います。

  • 選択できるのは LANGUAGES 設定にリストされている言語だけです。言語選択を提供されている言語のサブセットに制限したい場合(アプリケーションがすべての言語を提供していないため)、 LANGUAGES に言語のリストを設定します。たとえば、次のようにします:

    LANGUAGES = [
        ("de", _("German")),
        ("en", _("English")),
    ]
    

    この例では、自動選択できる言語をドイツ語と英語(および de-chen-us のようなサブ言語)に制限しています。

  • 前の箇所で説明したように、カスタムの LANGUAGES 設定を定義すれば、言語名を翻訳文字列としてマークできます。ただし、循環インポートを避けるために、 gettext() の代わりに gettext_lazy() を使ってください。

    以下はサンプルの設定ファイルです:

    from django.utils.translation import gettext_lazy as _
    
    LANGUAGES = [
        ("de", _("German")),
        ("en", _("English")),
    ]
    

LocaleMiddleware がユーザの設定を決定すると、その設定を HttpRequestrequest.LANGUAGE_CODE として利用できるようにします。この値はビューのコードで自由に読み込むことができます。以下に例を示します:

from django.http import HttpResponse


def hello_world(request, count):
    if request.LANGUAGE_CODE == "de-at":
        return HttpResponse("You prefer to read Austrian German.")
    else:
        return HttpResponse("You prefer to read another language.")

静的な(ミドルウェアを使わない)翻訳では、言語は settings.LANGUAGE_CODE にあり、動的な(ミドルウェアを使った)翻訳では request.LANGUAGE_CODE にあることに注意してください。

Djangoはどうやって翻訳を見つけるのか?

実行時に、 Django はリテラルと翻訳の統合カタログをメモリ内に構築します。これを実現するために、コンパイルされた メッセージファイル (.mo) を読み込むために異なるファイルパスを調べる順序と、同じリテラルに対して複数の翻訳がある場合の優先順位について、以下のアルゴリズムに従って翻訳を探します:

  1. LOCALE_PATHS に列挙されているディレクトリが最も優先され、最初に記述されているものが後に記述されているものよりも優先されます。

  2. 次に、 INSTALLED_APPS に列挙されている各インストール済みアプリの locale ディレクトリを探し、存在すればそれを使用します。 このとき、最初に指定されたものが後に指定されたものよりも優先されます。

  3. 最後に、 django/conf/locale にある Django が提供する基本翻訳がフォールバックとして使われます。

参考

JavaScriptアセットに含まれるリテラルの翻訳は同じようなアルゴリズムで検索されますが、同じではありません。詳細は JavaScriptCatalog を参照してください。

また、 FORMAT_MODULE_PATH を設定すれば、 カスタムフォーマットファイルLOCALE_PATHS ディレクトリに置くこともできます。

すべての場合において、翻訳を含むディレクトリの名前は locale name 表記で指定します。例えば dept_BRes_AR などです。地域言語バリアントの未翻訳の文字列は一般言語の翻訳を使用します。たとえば、未翻訳の pt_BR 文字列は pt の翻訳を使用します。

こうすることで、独自の翻訳を含むアプリケーションを書くことができ、プロジェクトでベースとなる翻訳を上書きできます。あるいは、いくつかのアプリから大きなプロジェクトを作り、すべての翻訳を、作成中のプロジェクトに特化した1つの大きな共通メッセージファイルにまとめることもできます。選択肢はあなたの自由です。

メッセージ・ファイルのリポジトリはすべて同じ構造になっています。それらは下記のようなものです:

  • 設定ファイルの LOCALE_PATHS にリストされているすべてのパスは、<language>/LC_MESSAGES/django.(po|mo) で検索されます。

  • $APPPATH/locale/<language>/LC_MESSAGES/django.(po|mo)

  • $PYTHONPATH/django/conf/locale/<language>/LC_MESSAGES/django.(po|mo)

メッセージファイルを作成するには django-admin makemessages ツールを使用します。また、 django-admin compilemessages を使って、 gettext が使用するバイナリファイル .mo を作成します。

また、 django-admin compilemessages --settings=path.to.settings を実行すると、コンパイラが LOCALE_PATHS 設定にあるすべてのディレクトリを処理するようになります。

英語以外の言語をベース言語として使う

Django は、翻訳可能なプロジェクト内の元の文字列は英語で書かれているという一般的な前提に立っています。他の言語を選ぶこともできますが、ある制限に注意する必要があります:

  • gettext はオリジナルのメッセージに対して2つの複数形しか提供しないので、もしベース言語の複数形のルールが英語と異なる場合は、すべての複数形を含むようにベース言語の翻訳も提供する必要があります。

  • 英語のバージョンが有効になっていて、英語の文字列がない場合、フォールバック言語はプロジェクトの LANGUAGE_CODE ではなく、オリジナルの文字列になります。たとえば、LANGUAGE_CODE がスペイン語に設定されていて、元の文字列がロシア語で書かれているサイトを訪れた英語のユーザは、スペイン語ではなくロシア語のテキストを見ることになります。

Back to Top