開発・技術選定

多言語対応を後から入れる — Django i18n の現実的な進め方

「英語対応もしたい」は後から出てくる要望の定番です。
すべてを一度に翻訳するのは現実的でないので、段階的に進めます。

有効化

USE_I18N = True
LANGUAGE_CODE = "ja"
LANGUAGES = [("ja", "日本語"), ("en", "English")]
LOCALE_PATHS = [BASE_DIR / "locale"]

MIDDLEWARE = [
    ...,
    "django.middleware.locale.LocaleMiddleware",   # Session の後、Common の前
    ...,
]

LocaleMiddleware位置が決まっている点に注意します。
順序を間違えると言語切り替えが効きません。

テンプレートの文言

{% load i18n %}
<h1>{% translate "お問い合わせ" %}</h1>
<p>{% blocktranslate %}{{ count }}件の結果{% endblocktranslate %}</p>

Python 側では、遅延評価を使うのが安全です。

from django.utils.translation import gettext_lazy as _

class Order(models.Model):
    status = models.CharField(_("ステータス"), max_length=20)

モジュール読み込み時に翻訳を確定させないため、
モデル定義やフォームでは必ず _lazy を使います。

翻訳ファイルの運用

python manage.py makemessages -l en -i venv -i node_modules
# locale/en/LC_MESSAGES/django.po を編集
python manage.py compilemessages

.po(編集用)はリポジトリに入れ、.mo(バイナリ)は
デプロイ時に生成する運用が扱いやすいです。

DBに入っているデータの翻訳

ここが本番です。静的な文言と違い、記事タイトルや商品名は DB にあります。
取れる方法は主に2つです。

A. カラムを増やす

title = models.CharField(max_length=200)
title_en = models.CharField(max_length=200, blank=True)

単純で分かりやすく、言語が2つなら十分です。
3言語以上になるとカラムが増えすぎます。

B. 翻訳テーブルを分ける

class ProductTranslation(models.Model):
    product = models.ForeignKey(Product, related_name="translations", ...)
    language = models.CharField(max_length=5)
    name = models.CharField(max_length=200)

    class Meta:
        unique_together = [("product", "language")]

言語が増えても構造が変わりません。代わりに JOIN が増えます。

2言語なら A、3言語以上または将来増えるなら B を選んでいます。

未翻訳のときの挙動を決める

翻訳が無い場合に「空欄」を出すのが最悪です。元の言語にフォールバックさせます。

def localized_name(self, lang):
    t = self.translations.filter(language=lang).first()
    return t.name if t and t.name else self.name

URL 設計

urlpatterns += i18n_patterns(
    path("", include("apps.pages.urls")),
    prefix_default_language=False,     # 日本語は / のまま
)

/about/(日本語)と /en/about/(英語)になります。
検索エンジン向けに hreflang も出しておきます。

<link rel="alternate" hreflang="ja" href="https://example.com/about/">
<link rel="alternate" hreflang="en" href="https://example.com/en/about/">

まとめ

  • LocaleMiddleware の位置に注意
  • モデル・フォームでは gettext_lazy
  • DB データは 2言語ならカラム追加、3言語以上なら翻訳テーブル
  • 未翻訳は必ず元の言語にフォールバック
  • i18n_patterns + hreflang で検索エンジンにも伝える