Django 中的 gettextgettext_lazy 混淆終結(從評估時點理解差異)

在使用 Django i18n 時,常常會為該使用 gettext()gettext_lazy() 而猶豫不決。大多數混亂源於「兩者差異」的術語記憶。

核心只有一點。

  • gettext 立即翻譯(即時評估,eager)
  • gettext_lazy 延遲翻譯(lazy 評估)

只要掌握這一「評估時點」,大多數情況都能輕鬆決定。


為什麼「翻譯何時」如此重要?{#sec-cd5e4068943f}

Django 通常在每一次請求時切換語言。

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(代理)」。

  • 在表單/模板/管理員渲染時,才會以當前語言翻譯。

一句話總結:「渲染時決定語言」的地方,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}

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"))

或使用舊式 % 格式化,便於管理翻譯字串。

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 或在翻譯字串內置換

只要掌握這些規則,就能在「概念不清」的狀態下自動選擇正確的函式。


相關文章連結 - gettext_lazy 在 JSON 鍵中使用時的問題與解決方法