# Django 中的 `gettext` 与 `gettext_lazy` 区别彻底搞清楚(从评估时点来理解) 在使用 Django i18n 时,常常会犹豫到底应该使用 `gettext()` 还是 `gettext_lazy()`。大多数混淆源于试图记住两者的“差别”术语。 核心只有一个。 * **`gettext`**:立即翻译(即时评估,eager) * **`gettext_lazy`**:延迟翻译(懒惰评估,lazy) 只要掌握这一个“评估时点”,几乎所有情况都能理清。 --- ## 为什么“翻译何时决定”如此重要?{#sec-cd5e4068943f} Django 通常在每个请求(request)时切换语言。 ![Django 中翻译时点比较流程](/media/editor_temp/6/29f9dbe8-44b7-47ed-bef6-4cc0cc62bb79.png) * 中间件根据请求调用 `activate("ko")` / `activate("en")`,激活当前线程/上下文的语言 * 在模板渲染、表单渲染、admin 界面渲染时,必须使用该语言进行翻译 也就是说,**字符串何时被翻译**决定了最终结果。 --- ## `gettext()`:立即翻译并返回字符串{#sec-9ff53b7dc3ad} ```python from django.utils.translation import gettext as _ def view(request): message = _("Welcome") # 这一行执行时使用当前激活语言翻译 return HttpResponse(message) ``` * 在函数/视图内部(即请求处理期间)调用时,通常按预期工作。 * 但在模块导入时调用会出现问题。 ### 常见陷阱:在模块常量中使用 `gettext()`{#sec-7c331db329fb} ```python # app/constants.py from django.utils.translation import gettext as _ WELCOME = _("Welcome") # ❌ 服务器启动/模块导入时的语言会被固定 ``` 此时 `WELCOME` 会被固定为导入时的语言,即使后续语言切换也无法改变(尤其在只导入一次的环境中)。 --- ## `gettext_lazy()`:返回将在以后翻译的代理对象{#sec-a90df9111315} ```python from django.utils.translation import gettext_lazy as _ WELCOME = _("Welcome") # ✅ 不是实际字符串,而是需要时才翻译的对象 ``` `gettext_lazy()` 返回的是一个“lazy object(代理)”。 * 在表单/模板/admin 渲染时将其转换为字符串 * 当时激活的语言决定翻译结果 > 一句话总结:**在渲染时点决定语言的地方,lazy 通常是正确的选择。** --- ## 实战规则:何时使用哪一个{#sec-4a8a3cbed930} ### 1) “立即生成界面/响应” → `gettext`{#sec-197c23c83765} * 在视图/服务逻辑中立即生成字符串用于响应或日志 ```python from django.utils.translation import gettext as _ def signup_done(request): return JsonResponse({"message": _("Signup completed.")}) ``` ### 2) “类属性/模型元/表单定义等在导入时评估” → `gettext_lazy`{#sec-b545464ef5b7} * 模型的 `verbose_name`、`help_text` * 表单字段的 `label`、`help_text` * DRF 序列化器字段的 `label` * admin 的 `list_display` 描述等 ```python 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`{#sec-9ea28af08fa3} * **复用常量**最好保持懒惰,以避免固定语言 ```python from django.utils.translation import gettext_lazy as _ STATUS_CHOICES = [ ("draft", _("Draft")), ("published", _("Published")), ] ``` ### 4) “发送给外部系统的字符串(日志/第三方 API/头部等)” → 使用 `gettext` 或强制评估 lazy{#sec-f04525bb1d99} 将 lazy 对象直接传递可能导致意外的类型/序列化问题。 ```python 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`。 ```python from django.utils.translation import gettext_lazy as _ from django.utils.text import format_lazy title = format_lazy("{}: {}", _("Error"), _("Invalid token")) ``` 或者使用 Python `%` 格式,便于管理翻译字符串。 ```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} ```python # ❌ 不推荐 _("Hello") + f" {user.username}" ``` * 这样会拆分翻译单元(不同语言的语序不同),并使评估时点更复杂。 * 推荐在翻译字符串内部进行变量替换。 ```python # ✅ 推荐 _("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 实现中避免大多数常见混淆。 --- **相关链接** - [gettext_lazy 在 JSON 键中使用时出现的问题与解决方案](/ko/whitedec/2025/4/26/gettext-lazy-json-key-problem-solution/)