Una situación frustrante al usar gettext_lazy de Django como clave JSON
En resumen: una clave JSON debe ser una cadena de texto 'real', pero
gettext_lazyde Django no devuelve una cadena, sino un objeto__proxy__.
1. ¿Por qué un código que funcionaba bien de repente falla?
Durante el desarrollo con Django, usar gettext_lazy para el manejo de la internacionalización (i18n o multilingüe) es algo común. Sin embargo, puede ocurrir que, mientras lo usas sin problemas, de repente salte un error de serialización (Serialization) durante el proceso de respuesta JSON (JsonResponse).
Normalmente, el registro de errores solo muestra mensajes confusos como __proxy__ o similar, lo que suele generar frustración. Pero si analizamos la causa, encontraremos que el culpable está en un lugar sorprendentemente simple.

2. El culpable estaba en la "clave"
Para ir directo al grano, el objeto gettext_lazy se comporta bien cuando se usa como Value en un diccionario, pero causa problemas en el momento en que se utiliza como Key.
| Categoría | Al usar como Value | Al usar como Key |
|---|---|---|
| Funcionamiento | Normal (conversión automática a str) |
Error |
| Razón | La serialización JSON soporta la conversión automática de tipos | La clave JSON debe ser una cadena de texto 'real' |
3. ¿Por qué esta diferencia?
Lo que devuelve gettext_lazy no es una cadena de texto (String) real, sino un objeto llamado __proxy__. Como su nombre indica, es un objeto que 'promete' proporcionar la traducción cuando sea necesaria en el futuro.
Funciones como json.dumps() de Python o JsonResponse de Django recorren los valores del diccionario y 'despliegan' (evalúan) automáticamente esta 'promesa' en una cadena de texto. Sin embargo, la situación es diferente para las claves de un diccionario. Según el estándar JSON, las claves deben ser obligatoriamente cadenas de texto, y en este proceso, el objeto __proxy__ no se convierte automáticamente, lo que provoca el error.
# ✅ Sin problema: el valor se convierte automáticamente a str.
json.dumps({'language': _('Korean')})
# ❌ Error: la clave no se convierte automáticamente y es rechazada.
json.dumps({_('Korean'): 'language'})
4. ¿Cómo solucionarlo?
Opción 1: Usar gettext directamente (la más limpia)
Si el enfoque lazy no es estrictamente necesario, la forma más sencilla es usar gettext, que devuelve la cadena de texto inmediatamente al ser invocada.
from django.utils.translation import gettext as _
LANGUAGE_MAP = {
"en": _("English"),
"ko": _("Korean"),
}
- Nota: Sin embargo, con este método, la traducción se determina en el momento en que se carga la aplicación, por lo que se debe tener precaución si un entorno de traducción dinámico es crucial.
Opción 2: Insertar str() justo antes de la serialización
Si ya estás utilizando objetos lazy de forma extensiva, puedes forzar la conversión a cadena de texto justo antes de pasarlos a JSON.
# Antes de usarlo como clave, envuélvelo con str() para convertirlo en una 'cadena de texto real'.
lang_key = str(LANGUAGE_MAP.get(code))
Opción 3: Delegar la traducción al cliente
Si no utilizas las plantillas de Django para el frontend y, en su lugar, empleas un cliente frontend independiente (quizás esta sea la tendencia actual), el servidor Django (o DRF) solo enviaría códigos como en o ko. El texto real que se muestra en pantalla sería gestionado por las librerías de i18n del frontend (React, Vue, etc.). Esto tiene la ventaja de aligerar la lógica del servidor.
Conclusión
Si alguna vez te encuentras usando json.dumps() junto con gettext_lazy() y piensas: "¿Por qué funcionaba ayer y hoy no?", es muy probable que ayer lo hayas usado por suerte solo como Value y hoy como Key.
gettext_lazy es una herramienta útil, pero siempre debemos recordar que no es una 'cadena de texto real'. Esto es especialmente crucial al trabajar con JSON. Espero que este artículo sea de ayuda para quienes estén lidiando con errores similares.