当在Django中将gettext_lazy用作JSON Key时令人头疼的情况



一句话总结: JSON Key必须是“真正的”字符串,但Django的gettext_lazy返回的实际上并不是字符串,而是一个名为__proxy__的对象。

1. 运行良好的代码为何突然出错了?

在Django开发中,为了处理多语言,使用gettext_lazy是家常便饭。然而,当你像往常一样使用它时,某一天在JSON响应(JsonResponse)过程中可能会突然爆发序列化(Serialization)错误。

通常,查看错误日志时,只会看到__proxy__之类的莫名其妙的信息,让人感到困惑。但深入探究原因,你会发现“罪魁祸首”隐藏在一个意想不到的简单地方。

gettext_lazy和JSON键问题的信息图

2. “罪魁祸首”在于“Key”



简而言之,当gettext_lazy对象作为字典的Value时,它会默默地通过;但一旦它作为Key,就会立即引发问题。

类别 用作Value时 用作Key时
运行状态 正常 (自动转换为str) 发生错误
原因 JSON序列化时支持自动类型转换 JSON Key必须是“真正的”字符串

3. 为何会出现这种差异?

gettext_lazy返回的并非真正的String,而是一个名为__proxy__的对象。顾名思义,它只是一个“稍后需要时再进行翻译”的承诺。

Python的json.dumps()或Django的JsonResponse在遍历字典的值时,会自动将这个“承诺”解析为字符串(Evaluation)。然而,对于字典的Key,情况就不同了。根据JSON标准,Key必须是字符串。在这个过程中,__proxy__对象无法自动转换,直接导致冲突并引发错误。

# ✅ 没有问题:Value会自动转换为str。
json.dumps({'language': _('Korean')})

# ❌ 错误:Key无法自行转换,被拒绝。
json.dumps({_('Korean'): 'language'})

4. 如何解决?

方法 1: 直接使用gettext (最简洁)

如果并非必须使用延迟(Lazy)方式,那么使用调用时即返回字符串的gettext会是更省心的选择。

from django.utils.translation import gettext as _ 

LANGUAGE_MAP = {
    "en": _("English"),
    "ko": _("Korean"),
}
  • 注意: 但这种方式的翻译是在应用程序加载时确定的,如果需要动态翻译环境,则需谨慎使用。

方法 2: 在序列化前强制使用str()转换

如果已经广泛使用了延迟对象,那么在将其传递给JSON之前,可以强制进行字符串转换。

# 在用作Key之前,用str()包裹一次,使其成为“真正的字符串”。
lang_key = str(LANGUAGE_MAP.get(code))

方法 3: 将翻译职责转移到客户端

如果前端不使用Django模板,而是采用独立的客户端(这或许是当前的趋势),那么Django(或DRF)服务器只需传递enko等代码,而实际显示在屏幕上的文本则由前端(如React、Vue等)的i18n库来处理。这种方式的优点是能减轻服务器逻辑的负担。


结语

如果你在使用json.dumps()gettext_lazy()时,遇到了“昨天还好好的,今天怎么就不行了?”的情况,那很可能昨天你侥幸只将其用作了Value,而今天却用作了Key。

gettext_lazy虽然方便,但我们始终需要牢记它并非“真正的字符串”。尤其是在处理JSON时,这一点更需注意。希望这篇分享能帮助到那些正在为类似错误而苦恼的朋友们。