Overview
While developing with Django, there are many occasions where we use gettext_lazy
for multilingual support. However, there are times when gettext_lazy
, which we usually rely on, suddenly causes errors when creating JSON responses.
This article explains why gettext_lazy
causes issues in JSON serialization and how to resolve them in detail.
Normal Operation vs Error Occurrence: Situation Comparison
Case | Normal Operation | Error Occurred |
---|---|---|
LANGUAGE_MAP.get(...) Return Value Location |
Value of the dictionary | Key of the dictionary |
Serialization Feasibility | Possible (No issues) | Not possible (__proxy__ key cannot be converted to JSON) |
Core of the Problem: Why Does This Difference Exist?
The object returned by gettext_lazy
is of type __proxy__
. This object looks like a string but is not an actual string.
Django's JsonResponse
and Python's json.dumps()
follow the next rules:
# ✅ Possible: lazy objects can be used as values (automatically converts to str)
json.dumps({'language': _('English')})
# ❌ Failure: lazy objects cannot be used as keys
json.dumps({_('English'): 'language'})
This means that to be used as a dictionary key, it must be a real string. The __proxy__
object does not automatically convert when used as a key, leading to an error.
Why Is There No Problem with Value but Issues with Key?
The gettext_lazy
object does not pose issues when used as a value in a dictionary. This is because Django or Python automatically converts the value to a string during JSON serialization.
However, when used as a key, the JSON standard enforces that keys must be strings. In this case, the gettext_lazy
object does not convert automatically, resulting in a serialization error.
Conclusion:
- There are no problems when used as a value because it is internally converted to str.
- When used as a key, conversion does not happen automatically, causing errors.
Solutions
Method 1: Switch to gettext
(Not using Lazy)
This is the most straightforward solution. Use values that are converted directly to strings without using lazy.
from django.utils.translation import gettext as _ # not lazy!
LANGUAGE_MAP = {
"en": _("English"),
"ko": _("Korean"),
"ja": _("Japanese"),
}
- Advantage: No additional processing needed
- Disadvantage: Translations are determined at the initial import time, which may not meet some dynamic translation requirements
Method 2: Force str() Conversion Just Before JSON Serialization
If you want to continue using lazy objects, convert the key using str()
before placing it in JSON.
lang = str(LANGUAGE_MAP.get(lang_code, lang_code.upper()))
- Advantage: Minimal changes to existing code
- Disadvantage: Must pay attention to str conversion every time
Bonus: Handle Translation on Client Side
Another method is to not perform multilingual translations on the server and instead handle them on the frontend.
- The server only sends the language code
- Client-side JavaScript manages the translation table.
This method can be chosen depending on the project.
In Summary
- Never use
gettext_lazy
as a dictionary key - Always convert to string before JSON serialization
- The safest method is immediate string conversion using
gettext
- If retaining existing code, don't forget to use
str(...)
Comments from Jesse
This issue is a common mistake that occurs at the very subtle boundary between multilingual handling in Django and JSON response processing.
Instead of asking, "It used to work, why doesn't it now?", consider that "It was just luck before" and review your code.
The moment a developer's skills accumulate is when they understand such 'subtleties'. With this principle in mind, you can make both Django multilingual handling and JSON responses much more robust!
Add a New Comment