# Django 中的 `gettext` 與 `gettext_lazy` 混淆終結(從評估時點理解差異) 在使用 Django i18n 時,常常會為該使用 `gettext()` 或 `gettext_lazy()` 而猶豫不決。大多數混亂源於「兩者差異」的術語記憶。 核心只有一點。 * **`gettext` 立即翻譯(即時評估,eager)** * **`gettext_lazy` 延遲翻譯(lazy 評估)** 只要掌握這一「評估時點」,大多數情況都能輕鬆決定。 --- ## 為什麼「翻譯何時」如此重要?{#sec-cd5e4068943f} Django 通常在每一次請求時切換語言。 ![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(代理)」。 * 在表單/模板/管理員渲染時,才會以當前語言翻譯。 > 一句話總結:**「渲染時決定語言」的地方,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} ```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 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` 或在翻譯字串內置換 只要掌握這些規則,就能在「概念不清」的狀態下自動選擇正確的函式。 --- **相關文章連結** - [gettext_lazy 在 JSON 鍵中使用時的問題與解決方法](/ko/whitedec/2025/4/26/gettext-lazy-json-key-problem-solution/)