Неприятная ситуация: gettext_lazy Django в качестве ключа JSON
Краткий вывод: ключ JSON всегда должен быть «настоящей» строкой, но
gettext_lazyDjango возвращает не строку, а объект__proxy__.
1. Почему внезапно перестал работать код, который отлично функционировал?
Использование gettext_lazy для обработки многоязычности в процессе разработки Django — обычное дело. Однако иногда, когда вы используете его как обычно, внезапно может возникнуть ошибка сериализации в процессе ответа JSON (JsonResponse).
Обычно в логах ошибок отображаются непонятные сообщения вроде __proxy__ и т.п., что сбивает с толку. Но если разобраться в причине, то виновник окажется на удивление простым.

2. Виновник был в "ключе"
Сразу к делу: объект gettext_lazy без проблем проходит, когда используется в качестве Value словаря, но вызывает проблемы, как только попадает в Key.
| Категория | При использовании в качестве Value | При использовании в качестве Key |
|---|---|---|
| Работает ли | Да (автоматически преобразуется в str) | Возникает ошибка |
| Причина | Поддерживается автоматическое преобразование типов при сериализации JSON | Ключ JSON всегда должен быть 'настоящей' строкой |
3. Почему возникает такая разница?
gettext_lazy возвращает не настоящую строку, а объект __proxy__. Как следует из названия, это просто обещание: "Я переведу это, когда понадобится".
json.dumps() в Python или JsonResponse в Django просматривают значения словаря и самостоятельно разрешают это 'обещание' в строку (Evaluation). Но с ключом словаря дело обстоит иначе. Согласно стандарту JSON, ключ должен быть строкой. В этом процессе объект __proxy__ не преобразуется автоматически и вызывает ошибку.
# ✅ Без проблем: Value автоматически преобразуется в str.
json.dumps({'language': _('Korean')})
# ❌ Ошибка: Key не может быть преобразован автоматически и отклоняется.
json.dumps({_('Korean'): 'language'})
4. Как решить проблему?
Способ 1: Просто использовать gettext (самый чистый)
Если ленивый подход не является строго необходимым, то гораздо проще использовать gettext, который возвращает строку немедленно при вызове.
from django.utils.translation import gettext as _
LANGUAGE_MAP = {
"en": _("English"),
"ko": _("Korean"),
}
- Примечание: Однако этот метод определяет перевод в момент загрузки приложения, поэтому требуется осторожность, если важна динамическая среда перевода.
Способ 2: Вставить str() непосредственно перед сериализацией
Если вы уже широко используете ленивые объекты, принудительно преобразуйте их в строку непосредственно перед передачей в JSON.
# Перед использованием в качестве ключа оберните его в str(), чтобы получить 'настоящую строку'.
lang_key = str(LANGUAGE_MAP.get(code))
Способ 3: Передать ответственность за перевод клиенту
Если вы используете отдельный клиентский фронтенд, а не шаблоны Django (возможно, это сейчас тренд), то сервер Django (или DRF) просто отправляет коды, такие как en или ko, а фактический текст для отображения на экране обрабатывается библиотеками i18n на стороне фронтенда (React, Vue и т.д.). Преимущество этого подхода в том, что логика сервера становится легче.
В заключение
Если вы столкнулись с ситуацией, когда при использовании json.dumps() с gettext_lazy() вы задаетесь вопросом: "Вчера работало, а сегодня почему-то нет?", то, скорее всего, вчера вам повезло, и вы использовали его только в качестве Value, а сегодня — в качестве Key.
gettext_lazy удобен, но всегда следует помнить, что это не "настоящая строка". Особенно при работе с JSON. Надеюсь, эта статья поможет тем, кто столкнулся с похожими ошибками.
Комментариев нет.