Django 中的 gettext 与 gettext_lazy 区别彻底搞清楚(从评估时点来理解)
在使用 Django i18n 时,常常会犹豫到底应该使用 gettext() 还是 gettext_lazy()。大多数混淆源于试图记住两者的“差别”术语。
核心只有一个。
gettext:立即翻译(即时评估,eager)gettext_lazy:延迟翻译(懒惰评估,lazy)
只要掌握这一个“评估时点”,几乎所有情况都能理清。
为什么“翻译何时决定”如此重要?{#sec-cd5e4068943f}
Django 通常在每个请求(request)时切换语言。

- 中间件根据请求调用
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_name、help_text - 表单字段的
label、help_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.py、forms.py、admin.py等“定义先行”的文件:from django.utils.translation import gettext_lazy as _views.py、services.py等“执行先行”的文件:from django.utils.translation import gettext as _
这样就形成了“此文件默认使用 lazy”的规则,错误率大幅下降。
简要总结{#sec-3aa43e207283}
- 即时评估安全的地方(运行时逻辑):
gettext - 稍后渲染的地方(定义/元/常量/choices/标签):
gettext_lazy - 外部输出字符串:必要时使用
force_str - lazy + 格式化:使用
format_lazy或在翻译字符串内部完成占位符替换
只要遵循这些准则,就能在 i18n 实现中避免大多数常见混淆。