Wenn gettext_lazy in Django als JSON-Schlüssel Kopfschmerzen bereitet

Zusammenfassend lässt sich sagen: Ein JSON-Schlüssel muss ein 'echter' String sein, doch Django's gettext_lazy gibt tatsächlich kein String, sondern ein __proxy__-Objekt zurück.

1. Warum plötzlich Probleme mit funktionierendem Code?

Bei der Django-Entwicklung ist die Verwendung von gettext_lazy für die Mehrsprachigkeit Routine. Doch manchmal tritt plötzlich ein Serialisierungsfehler auf, wenn man eine JSON-Antwort (JsonResponse) erstellt, obwohl es zuvor immer reibungslos funktionierte.

Meistens verwirren die Fehlermeldungen im Log, die nur kryptische "proxy"-Hinweise enthalten. Doch bei genauerer Betrachtung liegt die Ursache oft an einer überraschend einfachen Stelle.

Infografik zum Problem mit gettext_lazy und JSON-Schlüsseln

2. Der Übeltäter: Der "Key"

Um es auf den Punkt zu bringen: Ein gettext_lazy-Objekt wird als Value in einem Dictionary problemlos akzeptiert, verursacht aber sofort Probleme, sobald es als Key verwendet wird.

Eigenschaft Bei Verwendung als Value Bei Verwendung als Key
Funktionsweise Normal (automatische String-Konvertierung) Fehler tritt auf
Grund Automatische Typumwandlung bei JSON-Serialisierung unterstützt JSON-Schlüssel muss ein 'echter' String sein
## 3. Warum gibt es diesen Unterschied?

Was gettext_lazy zurückgibt, ist kein echter String, sondern ein __proxy__-Objekt. Es ist, wie der Name schon sagt, ein Platzhalter, der verspricht: "Ich liefere die Übersetzung, wenn sie tatsächlich benötigt wird."

Pythons json.dumps() oder Djangos JsonResponse durchlaufen die Werte eines Dictionaries und lösen dieses "Versprechen" (Evaluation) automatisch in einen String auf. Beim Schlüssel eines Dictionaries sieht die Sache jedoch anders aus. Laut JSON-Standard muss ein Schlüssel immer ein String sein. Da das __proxy__-Objekt in diesem Prozess nicht automatisch konvertiert wird und direkt auf diese Anforderung trifft, kommt es zu einem Fehler.

# ✅ Kein Problem: Der Value wird automatisch in einen String umgewandelt.
json.dumps({'language': _('Korean')})

# ❌ Fehler: Der Key kann nicht selbst konvertiert werden und wird abgelehnt.
json.dumps({_('Korean'): 'language'})

4. Wie lässt sich das Problem lösen?

Methode 1: Einfach gettext verwenden (Die sauberste Lösung)

Wenn der Lazy-Ansatz nicht zwingend erforderlich ist, ist es am unkompliziertesten, gettext zu verwenden, das sofort einen String zurückgibt.

from django.utils.translation import gettext as _ 

LANGUAGE_MAP = {
    "en": _("English"),
    "ko": _("Korean"),
}
  • Hinweis: Bei dieser Methode wird die Übersetzung jedoch zum Zeitpunkt des App-Ladevorgangs festgelegt. Ist eine dynamische Übersetzungsumgebung wichtig, ist Vorsicht geboten.

Methode 2: str() direkt vor der Serialisierung einfügen

Wenn Sie Lazy-Objekte bereits weit verbreitet verwenden, wandeln Sie diese direkt vor der Übergabe an JSON erzwungenermaßen in Strings um.

# Vor der Verwendung als Key mit str() umschließen, um einen 'echten String' zu erhalten.
lang_key = str(LANGUAGE_MAP.get(code))

Methode 3: Die Übersetzungsverantwortung an den Client übertragen

Wenn Sie das Frontend nicht mit Django-Templates, sondern mit einem separaten Frontend-Client entwickeln (was heutzutage vielleicht ein Trend ist), kann der Django- (oder DRF-)Server lediglich Codes wie "en" oder "ko" übermitteln. Die eigentliche Textanzeige auf dem Bildschirm würde dann von einer i18n-Bibliothek im Frontend (z.B. React, Vue) übernommen. Dies hat den Vorteil, dass die Serverlogik schlanker wird.


Fazit

Wenn Sie bei der Verwendung von json.dumps() zusammen mit gettext_lazy auf die Situation stoßen: "Gestern ging es noch, warum heute nicht mehr?", liegt es wahrscheinlich daran, dass Sie es gestern glücklicherweise nur für Values verwendet haben, während Sie es heute als Key eingesetzt haben.

gettext_lazy ist zwar praktisch, doch man sollte immer bedenken, dass es letztendlich kein "echter String" ist. Dies gilt insbesondere beim Umgang mit JSON. Ich hoffe, dieser Artikel hilft all jenen, die sich mit ähnlichen Fehlern herumschlagen.