# Djangoで `gettext` vs `gettext_lazy` の混乱を解消する(評価時点で理解する) Django i18n を使うとき、`gettext()` と `gettext_lazy()` のどちらを使うべきか迷ってしまいます。多くの混乱は「両者の違い」を言葉で覚えようとして起こります。 核心はひとつです。 * **`gettext` は今すぐ翻訳する(即時評価、eager)** * **`gettext_lazy` は後で翻訳する(遅延評価、lazy)** この「評価時点」だけでほとんどのケースが整理できます。 --- ## 翻訳が「いつ」決まるかが重要な理由 Django は通常、リクエストごとに言語が変わります。 ![Djangoで翻訳時点を比較するフロー](/media/editor_temp/6/29f9dbe8-44b7-47ed-bef6-4cc0cc62bb79.png) * ミドルウェアがリクエストを見て `activate("ko")` / `activate("en")` などで **現在のスレッド/コンテキストの言語を有効化** * テンプレートレンダリング、フォームレンダリング、admin 画面レンダリングの時点でその言語で翻訳される必要があります つまり、**文字列を「いつ」翻訳するか** が結果を分けます。 --- ## `gettext()` : 「今」翻訳して文字列を返す ```python from django.utils.translation import gettext as _ def view(request): message = _("Welcome") # この行が実行される瞬間の有効言語で翻訳される return HttpResponse(message) ``` * 関数/ビュー内部のように **リクエスト処理中(runtime)** に呼び出すと期待通りに動作します。 * しかし **モジュール import 時点** に呼び出すと問題が生じます。 ### よくある罠:モジュール定数で `gettext()` を使う ```python # app/constants.py from django.utils.translation import gettext as _ WELCOME = _("Welcome") # ❌ サーバ起動/モジュール import 時点の言語で「固定」される可能性がある ``` この場合 `WELCOME` は「後で」言語が変わってもすでに翻訳された文字列で固定されてしまいます(特に import が一度しか行われない環境で)。 --- ## `gettext_lazy()` : 「後で」翻訳されるプロキシ(Proxy)を返す ```python from django.utils.translation import gettext_lazy as _ WELCOME = _("Welcome") # ✅ 実際の文字列ではなく、必要になったときに翻訳されるオブジェクト ``` `gettext_lazy()` が返すのは通常「lazy object(プロキシ)」です。 * フォーム/テンプレート/admin が値を **文字列としてレンダリングする時点** に * その時点で有効化された言語で翻訳されます。 > 一行でまとめると:**「レンダリング時点で言語が決まる場所」では lazy が正解になることが多い**。 --- ## どこに何を使えばいいか:実践ルール ### 1) 「今すぐ画面/応答を作っている」 → `gettext` * ビュー/サービスロジックで即時に文字列を作って応答やログに出力する場合 ```python from django.utils.translation import gettext as _ def signup_done(request): return JsonResponse({"message": _("Signup completed.")}) ``` ### 2) 「クラス属性/モデルメタ/フォーム定義のように、import 時点で評価される可能性がある」 → `gettext_lazy` * モデルの `verbose_name`、`help_text` * フォームフィールドの `label`、`help_text` * DRF serializer フィールドの `label`(同様の理由) * admin の `list_display` 説明など「定義は先、レンダリングは後」の場所 ```python from django.db import models from django.utils.translation import gettext_lazy as _ class Article(models.Model): title = models.CharField(_("title"), max_length=100) # verbose_name の位置 status = models.CharField( _("status"), max_length=20, choices=[ ("draft", _("Draft")), ("published", _("Published")), ], help_text=_("Visibility of the article."), ) ``` ### 3) 「モジュールレベル定数/choices のような再利用値」 → 通常 `gettext_lazy` * **再利用される定数は特に** lazy にしておくと安全です。 ```python from django.utils.translation import gettext_lazy as _ STATUS_CHOICES = [ ("draft", _("Draft")), ("published", _("Published")), ] ``` ### 4) 「外部システムへ送る文字列(ログ/サードパーティ API/ヘッダーなど)」 → `gettext` か lazy を強制評価 lazy オブジェクトをそのまま渡すと予期せぬ型/シリアライズ問題を引き起こす可能性があります。 ```python from django.utils.translation import gettext_lazy as _ from django.utils.encoding import force_str msg = _("Welcome") logger.info(force_str(msg)) # ✅ 実際の文字列に変換して使用 ``` ### 5) 「文字列の結合/フォーマットが含まれる」 → lazy 専用ツールも検討 lazy メッセージに f-string/`str.format()` を混ぜると評価時点が混乱します。Django はこれを解決するために `format_lazy` を提供しています。 ```python from django.utils.translation import gettext_lazy as _ from django.utils.text import format_lazy title = format_lazy("{}: {}", _("Error"), _("Invalid token")) ``` または Python の `%` フォーマットを使うと翻訳文字列管理が楽になります。 ```python from django.utils.translation import gettext as _ message = _("Hello, %(name)s!") % {"name": user.username} ``` --- ## よくあるミス 3 つ ### ミス 1) モジュール import 時点で `gettext()` で「固定翻訳」を作ってしまう * 「定数/choices」がある場合はまず lazy を疑ってください。 ### ミス 2) lazy オブジェクトを JSON シリアライズ/ログにそのまま入れる * `force_str()` で文字列に変換してから使用してください。 ### ミス 3) f-string で翻訳文字列を組み立てる ```python # ❌ 推奨しない _("Hello") + f" {user.username}" ``` * 翻訳単位が分断され(言語ごとに語順が異なる)、評価時点も複雑になります。 * **翻訳文字列内で変数置換** を行いましょう。 ```python # ✅ 推奨 _("Hello, %(name)s!") % {"name": user.username} ``` --- ## 混乱を減らす Tips 混乱を減らす最も効果的な方法は **ファイルの性質に応じて `_` の意味を統一** することです。 * `models.py`、`forms.py`、`admin.py` のように「定義が先」のファイル: `from django.utils.translation import gettext_lazy as _` * `views.py`、`services.py` のように「実行が先」のファイル: `from django.utils.translation import gettext as _` こうすれば「ここでは lazy がデフォルト」という基準が生まれ、ミスが減ります。 --- ## 簡単まとめ * **即時評価が安全な場所(ランタイムロジック)**: `gettext` * **後でレンダリングされるもの(定義/メタ/定数/choices/ラベル)**: `gettext_lazy` * **外部へ出る文字列**: 必要なら `force_str` * **lazy + フォーマット**: `format_lazy` か翻訳文字列内で置換 この基準を押さえれば「概念が定まっていないから混乱する」状態から脱却し、ほぼ自動で選択できるようになります。 --- **関連記事リンク** - [gettext_lazyをJSONキーに使うと起こる問題と解決方法](/ko/whitedec/2025/4/26/gettext-lazy-json-key-problem-solution/)