Django: Despejando la confusión entre gettext y gettext_lazy (entendiendo el momento de evaluación)

Cuando trabajas con i18n en Django, a menudo te encuentras indeciso sobre cuándo usar gettext() y cuándo usar gettext_lazy(). La mayoría de la confusión surge al intentar memorizar la diferencia entre ambos términos.

El núcleo de la cuestión es una sola idea.

  • gettext traduce ahora (evaluación inmediata, eager)
  • gettext_lazy traduce después (evaluación perezosa, lazy)

Con este único concepto de momento de evaluación, la mayoría de los casos se resuelven.


¿Por qué es importante cuándo se decide la traducción?



En Django, el idioma suele cambiar con cada solicitud (request).

Flujo comparativo de los momentos de traducción en Django

  • El middleware observa la solicitud y activa el idioma con activate("ko") / activate("en"), activando el idioma del hilo/contexto actual.
  • Cuando se renderizan plantillas, formularios o la interfaz de administración, la traducción debe ocurrir en ese idioma.

En otras palabras, el momento en que traduces una cadena determina el resultado.


gettext(): traduce ahora y devuelve la cadena

from django.utils.translation import gettext as _

def view(request):
    message = _("Welcome")   # Se traduce con el idioma activo en este instante
    return HttpResponse(message)
  • Si se llama dentro de una vista o función (runtime), normalmente funciona como se espera.
  • Pero si se llama al momento de importar el módulo, surgen problemas.

Trampa común: usar gettext() en constantes de módulo

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

WELCOME = _("Welcome")  # ❌ Se fija al idioma del momento de importación del servidor

En este caso, WELCOME queda fija con la traducción del idioma en el que se importó el módulo, incluso si el idioma cambia después (especialmente en entornos donde la importación ocurre una sola vez).


gettext_lazy(): devuelve un proxy que se traduce más tarde



from django.utils.translation import gettext_lazy as _

WELCOME = _("Welcome")  # ✅ No es la cadena real, sino un objeto que se traduce cuando sea necesario

gettext_lazy() devuelve típicamente un objeto proxy perezoso.

  • Cuando formularios, plantillas o la administración convierten el valor a cadena,
  • Se traduce con el idioma activo en ese momento.

Resumen de una línea: En lugares donde el idioma se decide al momento de renderizar, el lazy suele ser la opción correcta.


¿Dónde usar qué? Reglas prácticas

1) “Estoy construyendo la respuesta ahora” → gettext

  • Cuando la lógica de la vista o servicio genera una cadena inmediatamente para la respuesta o el registro.
from django.utils.translation import gettext as _

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

2) “Definición que se evalúa al importar, renderización más tarde” → gettext_lazy

  • verbose_name, help_text de modelos.
  • label, help_text de campos de formularios.
  • label de campos de serializadores DRF.
  • Descripciones en list_display de la administración.
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) “Constantes reutilizables a nivel de módulo” → normalmente gettext_lazy

  • Las constantes que se usan en varios lugares deben mantenerse perezosas.
from django.utils.translation import gettext_lazy as _

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

4) “Enviando cadenas a sistemas externos (logs, APIs, cabeceras)” → gettext o fuerza la evaluación del lazy

El objeto lazy puede causar tipos inesperados o problemas de serialización.

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

msg = _("Welcome")
logger.info(force_str(msg))  # ✅ Se convierte a cadena real antes de usarla

5) “Se incluye formateo o concatenación de cadenas” → considera herramientas específicas para lazy

  • Mezclar f-strings o str.format() con mensajes lazy puede confundir el momento de evaluación. Django ofrece format_lazy.
from django.utils.translation import gettext_lazy as _
from django.utils.text import format_lazy

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

O usar el formato % de Python para mantener la cadena traducida.

from django.utils.translation import gettext as _

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

Los tres errores más comunes

Error 1) Crear traducción fija con gettext() al importar el módulo

  • Si tienes constantes o elecciones, sospecha primero de usar lazy.

Error 2) Incluir objetos lazy directamente en JSON o logs

  • Convierte a cadena con force_str() antes de usarlo.

Error 3) Construir cadenas con f-strings y traducción

# ❌ No recomendado
_("Hello") + f" {user.username}"
  • La unidad de traducción se fragmenta y el orden de palabras varía por idioma.
  • Reemplaza con sustitución dentro de la cadena traducida.
# ✅ Recomendado
_("Hello, %(name)s!") % {"name": user.username}

Tips para reducir la confusión

La forma más efectiva de evitar confusiones es unificar el significado de _ según el tipo de archivo.

  • En models.py, forms.py, admin.py (definición primero): from django.utils.translation import gettext_lazy as _
  • En views.py, services.py (ejecución primero): from django.utils.translation import gettext as _

Con esta convención, lazy se vuelve la opción por defecto en los archivos de definición, reduciendo errores.


Resumen rápido

  • Evaluación inmediata segura (lógica de tiempo de ejecución): gettext
  • Evaluación perezosa (definición/meta/constantes/etiquetas): gettext_lazy
  • Cadenas que salen al exterior: usa force_str si es necesario
  • Lazy + formateo: format_lazy o sustitución dentro de la cadena traducida

Con estos criterios claros, la elección se vuelve automática y la confusión desaparece.


Enlaces relacionados
- Problemas y soluciones al usar gettext_lazy con claves JSON