Djangoでgettext_lazyをJSON Keyとして使うと頭を抱える状況
結論を先に一言でまとめると:JSON Keyは必ず「本物の」文字列でなければならない。しかし、Djangoの
gettext_lazyが返すのは、実際には文字列ではなく__proxy__というオブジェクトです。
1. 順調に動いていたコードがなぜ突然?
Django開発において、多言語対応のためにgettext_lazyを使うのは日常的なことです。ところが、普段通りに使っていたのに、ある日突然JSONレスポンス(JsonResponse)の過程で直列化(Serialization)エラーが発生することがあります。
通常、エラーログを見ても__proxy__といったような、意味不明なメッセージしか表示されないため、戸惑うものです。しかし、原因を紐解いてみると、意外にも単純なところに犯人がいます。

2. 犯人は「Key」にあった
結論から言うと、gettext_lazyオブジェクトは辞書のValueとして使う場合は問題なく処理されますが、Keyとして使った瞬間に問題を引き起こします。
| 区分 | Valueで使った場合 | Keyで使った場合 |
|---|---|---|
| 動作可否 | 正常(自動でstrに変換される) | エラー発生 |
| 理由 | JSON直列化時に自動型変換をサポート | JSON Keyは必ず「本物の」文字列でなければならない |
3. なぜこのような違いが生じるのか?
gettext_lazyが返すのは、実際のStringではなく__proxy__というオブジェクトです。その名の通り、「後で必要になったら翻訳して渡すよ」と約束だけしている状態です。
Pythonのjson.dumps()やDjangoのJsonResponseは、辞書内の値を走査しながら、この「約束」を自動的に文字列に展開(評価)してくれます。しかし、辞書のKeyとなると話は別です。JSONの標準ではKeyは必ず文字列でなければなりませんが、この過程で__proxy__オブジェクトが自動的に変換されず、そのまま衝突してしまうためエラーが発生するのです。
# ✅ 問題なし: Valueは自動的にstrに変換されます。
json.dumps({'language': _('Korean')})
# ❌ エラー: Keyは自動的に変換されず、拒否されます。
json.dumps({_('Korean'): 'language'})
4. どのように解決するか?
方法1: 単純にgettextを使う(最もシンプル)
Lazy方式がどうしても必要な状況でなければ、呼び出しと同時に文字列を返すgettextを使うのが最も手軽です。
from django.utils.translation import gettext as _
LANGUAGE_MAP = {
"en": _("English"),
"ko": _("Korean"),
}
- 参考: ただし、この方式はアプリがロードされる時点で翻訳が決定されるため、動的な翻訳環境が重要である場合は注意が必要です。
方法2: 直列化直前にstr()を挟む
すでにLazyオブジェクトを広範囲で使っている場合は、JSONに渡す直前に強制的に文字列変換を行います。
# Keyとして利用する前にstr()で一度囲み、「本物の文字列」にします。
lang_key = str(LANGUAGE_MAP.get(code))
方法3: 翻訳の主体をクライアントに任せる
もしフロントエンドでDjangoのテンプレートを使わず、別のフロントエンドクライアントを使用する方式であれば(最近ではこの方式がトレンドかもしれませんが)、Django(あるいはDRF)サーバーはen、koのようなコード(Code)だけを返し、実際に画面に表示するテキストはフロントエンド(React、Vueなど)のi18nライブラリで処理する方式です。サーバーロジックが軽量化されるという利点があります。
まとめ
json.dumps()とgettext_lazy()を組み合わせて使用していて、「昨日は動いたのに、なぜ今日は動かないんだろう?」という状況に遭遇したなら、おそらく昨日は運良くValueにのみ使用し、今日はKeyに誤って使用した可能性が高いです。
gettext_lazyは便利ですが、結局のところ「本物の文字列」ではないという点を常に念頭に置いておく必要があります。特にJSONを扱う際にはなおさらです。同様のエラーで苦労されている方々の一助となれば幸いです。
コメントはありません。