状況の概要

Djangoの開発を行っていると、gettext_lazyを使用して多言語対応を行うことがよくあります。
しかし、普段使い慣れたgettext_lazyが、ある時JSONレスポンスを作成する際にエラーを引き起こすことがあります。
この記事では、gettext_lazyがJSONシリアル化で問題を引き起こす理由と、どのように解決できるかを詳しく説明します。
正常動作 vs エラー発生: 状況比較
| ケース | 正常動作した場合 | エラーが発生した場合 |
|---|---|---|
LANGUAGE_MAP.get(...)の返り値位置 |
辞書のvalue | 辞書のkey |
| シリアル化可能か | 可能 (問題なし) | 不可能 (__proxy__ keyはJSON変換不可) |
問題の核心: なぜこのような差が生まれたのか?
gettext_lazyが返すオブジェクトは__proxy__タイプです。このオブジェクトは、文字列のように見えますが、実際には本物の文字列ではありません。
DjangoのJsonResponseやPythonのjson.dumps()は次のルールに従います:
# ✅ 可能: valueとしてはlazyオブジェクトを使える (自動的にstr変換)
json.dumps({'language': _('English')})
# ❌ 失敗: keyとしてはlazyオブジェクトを使えない
json.dumps({_('English'): 'language'})
つまり、辞書のkeyとして使用するためには、必ず本物の文字列でなければなりません。
__proxy__オブジェクトはkeyとして使われるときに自動変換されないため、エラーが発生します。
なぜvalueでは問題がなく、keyでは問題になるのか?
gettext_lazyオブジェクトは辞書のvalueとして使用される場合は問題がありません。DjangoやPythonはJSONシリアル化の過程でvalueを文字列に自動変換してくれるからです。
しかしkeyとして使われる場合、JSON標準はkeyを必ず文字列にすべきだと規定しています。このときgettext_lazyオブジェクトは自動変換されず、シリアル化エラーを引き起こすのです。
結論:
- valueとして使うときは内部でstr変換がなされ、問題なく通過する。
- keyとして使うときは変換が自動的に行われず、エラーが発生する。
解決方法
方法 1: gettextに切り替え (Lazyを使用しない)
最も確実な方法です。lazyを使わずに直接文字列に変換された値を使用します。
from django.utils.translation import gettext as _ # lazyではない!
LANGUAGE_MAP = {
"en": _("English"),
"ko": _("Korean"),
"ja": _("Japanese"),
}
- 利点: 追加処理が必要ない
- 欠点: 初回インポート時に翻訳が決定されるため、一部の動的翻訳要求と異なる可能性がある
方法 2: JSONシリアル化直前に強制的にstr()を適用
lazyオブジェクトを引き続き使用したい場合、JSONに入れる前にkeyをstr()で変換します。
lang = str(LANGUAGE_MAP.get(lang_code, lang_code.upper()))
- 利点: 既存のコードをほとんど変更せずに済む
- 欠点: 毎回str変換に気を付けなければならない
番外: クライアントで翻訳処理を行う
サーバーで全く多言語翻訳を行わず、フロントエンドで処理する方法もあります。
- サーバーは言語コードのみ送信し
- クライアントのJavaScriptで翻訳テーブルを管理します。
この方法はプロジェクトによって選択できます。
まとめると
gettext_lazyは絶対に辞書のkeyとして使用しないこと- JSONシリアル化の前に必ず文字列に変換すること
- 最も安全な方法は
gettextで即座に文字列変換すること - 既存のコードを維持する必要がある場合は
str(...)を忘れないこと
Jesseのコメント
この問題はDjangoにおける多言語処理とJSONレスポンス処理の非常に微妙な境界で発生する一般的なミスです。
これからは「以前はできたけれど、なぜできないのだろう?」ではなく、「以前は運が良かったのだ」と考え、コードをチェックしてください。
開発者のスキルが高まる瞬間は、このような「微妙さ」を理解できるようになったときです。この原理を知っていれば、Djangoの多言語処理とJSONレスポンスの両方をはるかに堅牢にすることができます!
コメントはありません。