Django 中的 gettextgettext_lazy 区别彻底搞清楚(从评估时点来理解)

在使用 Django i18n 时,常常会犹豫到底应该使用 gettext() 还是 gettext_lazy()。大多数混淆源于试图记住两者的“差别”术语。

核心只有一个。

  • gettext:立即翻译(即时评估,eager)
  • gettext_lazy:延迟翻译(懒惰评估,lazy)

只要掌握这一个“评估时点”,几乎所有情况都能理清。


为什么“翻译何时决定”如此重要?{#sec-cd5e4068943f}



Django 通常在每个请求(request)时切换语言。

Django 中翻译时点比较流程

  • 中间件根据请求调用 activate("ko") / activate("en"),激活当前线程/上下文的语言
  • 在模板渲染、表单渲染、admin 界面渲染时,必须使用该语言进行翻译

也就是说,字符串何时被翻译决定了最终结果。


gettext():立即翻译并返回字符串{#sec-9ff53b7dc3ad}

from django.utils.translation import gettext as _

def view(request):
    message = _("Welcome")   # 这一行执行时使用当前激活语言翻译
    return HttpResponse(message)
  • 在函数/视图内部(即请求处理期间)调用时,通常按预期工作。
  • 但在模块导入时调用会出现问题。

常见陷阱:在模块常量中使用 gettext()

# app/constants.py
from django.utils.translation import gettext as _

WELCOME = _("Welcome")  # ❌ 服务器启动/模块导入时的语言会被固定

此时 WELCOME 会被固定为导入时的语言,即使后续语言切换也无法改变(尤其在只导入一次的环境中)。


gettext_lazy():返回将在以后翻译的代理对象{#sec-a90df9111315}



from django.utils.translation import gettext_lazy as _

WELCOME = _("Welcome")  # ✅ 不是实际字符串,而是需要时才翻译的对象

gettext_lazy() 返回的是一个“lazy object(代理)”。

  • 在表单/模板/admin 渲染时将其转换为字符串
  • 当时激活的语言决定翻译结果

一句话总结:在渲染时点决定语言的地方,lazy 通常是正确的选择。


实战规则:何时使用哪一个{#sec-4a8a3cbed930}

1) “立即生成界面/响应” → gettext

  • 在视图/服务逻辑中立即生成字符串用于响应或日志
from django.utils.translation import gettext as _

def signup_done(request):
    return JsonResponse({"message": _("Signup completed.")})

2) “类属性/模型元/表单定义等在导入时评估” → gettext_lazy

  • 模型的 verbose_namehelp_text
  • 表单字段的 labelhelp_text
  • DRF 序列化器字段的 label
  • admin 的 list_display 描述等
from django.db import models
from django.utils.translation import gettext_lazy as _

class Article(models.Model):
    title = models.CharField(_("title"), max_length=100)
    status = models.CharField(
        _("status"),
        max_length=20,
        choices=[
            ("draft", _("Draft")),
            ("published", _("Published")),
        ],
        help_text=_("Visibility of the article."),
    )

3) “模块级常量/choices 等可复用值” → 通常使用 gettext_lazy

  • 复用常量最好保持懒惰,以避免固定语言
from django.utils.translation import gettext_lazy as _

STATUS_CHOICES = [
    ("draft", _("Draft")),
    ("published", _("Published")),
]

4) “发送给外部系统的字符串(日志/第三方 API/头部等)” → 使用 gettext 或强制评估 lazy{#sec-f04525bb1d99}

将 lazy 对象直接传递可能导致意外的类型/序列化问题。

from django.utils.translation import gettext_lazy as _
from django.utils.encoding import force_str

msg = _("Welcome")
logger.info(force_str(msg))  # ✅ 转为实际字符串后使用

5) “包含字符串拼接/格式化” → 考虑使用 lazy 专用工具{#sec-67d42a88924b}

将 lazy 消息与 f-string/str.format() 混用会导致评估时点混乱。Django 提供 format_lazy

from django.utils.translation import gettext_lazy as _
from django.utils.text import format_lazy

title = format_lazy("{}: {}", _("Error"), _("Invalid token"))

或者使用 Python % 格式,便于管理翻译字符串。

from django.utils.translation import gettext as _

message = _("Hello, %(name)s!") % {"name": user.username}

最常见的三大错误{#sec-273d53d27224}

错误 1) 在模块导入时使用 gettext() 固定翻译{#sec-7f4b5a25d9d0}

  • 若存在“常量/choices”,先考虑使用 lazy。

错误 2) 将 lazy 对象直接放入 JSON 序列化/日志{#sec-7a741de5c1d8}

  • 先用 force_str() 转为字符串。

错误 3) 用 f-string 组装翻译字符串{#sec-76316b53a754}

# ❌ 不推荐
_("Hello") + f" {user.username}"
  • 这样会拆分翻译单元(不同语言的语序不同),并使评估时点更复杂。
  • 推荐在翻译字符串内部进行变量替换。
# ✅ 推荐
_("Hello, %(name)s!") % {"name": user.username}

减少混淆的技巧{#sec-79205bb8a9a7}

最有效的办法是根据文件类型统一 _ 的含义。

  • models.pyforms.pyadmin.py 等“定义先行”的文件: from django.utils.translation import gettext_lazy as _
  • views.pyservices.py 等“执行先行”的文件: from django.utils.translation import gettext as _

这样就形成了“此文件默认使用 lazy”的规则,错误率大幅下降。


简要总结{#sec-3aa43e207283}

  • 即时评估安全的地方(运行时逻辑)gettext
  • 稍后渲染的地方(定义/元/常量/choices/标签)gettext_lazy
  • 外部输出字符串:必要时使用 force_str
  • lazy + 格式化:使用 format_lazy 或在翻译字符串内部完成占位符替换

只要遵循这些准则,就能在 i18n 实现中避免大多数常见混淆。


相关链接 - 在JSON键中使用gettext_lazy时遇到的问题及解决方案