Manejo multilingüe en Django: Evitar que "Polish" se convierta en "polaco" (Marcadores contextuales)

Cuando se implementa el soporte multilingüe (i18n) de manera adecuada, es común encontrarse con situaciones como:

  • Un botón con “Polish” (refinar la UI) se traduce como “polaco”
  • El componente de selección de fechas muestra “May” que se traduce como un nombre propio en algunos idiomas
  • El menú presenta “Book” solo como “libro”, sin la opción de “reservar”

La causa es sencilla:

Las computadoras no comprenden el contexto. Si dos cadenas son idénticas, se tratan como la misma.

En este artículo, utilizaremos los Marcadores contextuales de Django para mantener el código fuente intacto y asignar traducciones distintas a la misma cadena.


¿Por qué ocurre esto? Funcionamiento básico de gettext

El sistema de traducción de Django se basa en GNU gettext. Su lógica es muy simple:

  • Texto original → msgid
  • Texto traducido → msgstr
  • Si msgid es idéntico, siempre se usa el mismo msgstr
# django.po

msgid "Polish"
msgstr "polaco"

Una vez que se crea esta asociación, cualquier aparición de “Polish” en el proyecto usará la misma traducción. Por eso, los dos usos de “Polish” (verbo y nombre de idioma) no se distinguen.

Para evitarlo, algunos desarrolladores modifican el código fuente:

<!-- Código a evitar -->
{% trans "Polish (verb)" %}
{% trans "Polish (language)" %}

Aunque la traducción sea correcta, el texto visible para el usuario se vuelve extraño y la reutilización se complica.

Lo que buscamos es:

  • Mantener la cadena original "Polish"
  • Añadir una explicación de su significado en el contexto

Los Marcadores contextuales son la solución.


Solución en plantillas: {% translate %} + context

En las plantillas de Django, el tag {% translate %} (o su versión antigua {% trans %}) acepta la opción context para diferenciar la misma cadena según su uso.

1) Código original (colisión)

{% load i18n %}

<button>{% translate "Polish" %}</button>
<span>{% translate "Polish" %}</span>

En el archivo .po, ambas líneas comparten msgid "Polish", por lo que solo se puede traducir una vez.

2) Código mejorado (con contexto)

{% load i18n %}

<button>
  {% translate "Polish" context "verb: to refine UI" %}
</button>

<span>
  {% translate "Polish" context "language name" %}
</span>

Puntos clave:

  • El texto después de context no se muestra al usuario.
  • Es información meta para el sistema de traducción y los traductores.
  • Se recomienda usar descripciones breves y claras.

3) También funciona con {% blocktranslate %}

Para frases largas, se puede usar {% blocktranslate %} con context.

{% load i18n %}

{% blocktranslate context "greeting message" with username=user.username %}
  Hello {{ username }}
{% endblocktranslate %}

Esto permite reutilizar la misma frase con diferentes contextos.


Solución en código Python: pgettext

En vistas, modelos, formularios y demás código Python, se utilizan las funciones de la familia pgettext.

  • pgettext(context, message)
  • pgettext_lazy(context, message) – evaluación diferida
  • npgettext(context, singular, plural, number) – plural + contexto

1) Ejemplo básico

from django.utils.translation import pgettext

def my_view(request):
    # 1. Mes "May"
    month = pgettext("month name", "May")

    # 2. Nombre propio "May"
    person = pgettext("person name", "May")

    # 3. Verbo modal "may"
    verb = pgettext("auxiliary verb", "may")

Aunque el msgid sea el mismo, el context permite distintas traducciones.

2) Uso de pgettext_lazy en modelos

from django.db import models
from django.utils.translation import pgettext_lazy

class Order(models.Model):
    type = models.CharField(
        verbose_name=pgettext_lazy("order model field", "Order type"),
        max_length=20,
    )

    STATUS_CHOICES = [
        ("open",  pgettext_lazy("order status", "Open")),
        ("opened", pgettext_lazy("log action", "Open")),
    ]

    status = models.CharField(
        max_length=20,
        choices=STATUS_CHOICES,
    )

3) Manejo de plurales con npgettext

from django.utils.translation import npgettext

def get_notification(count):
    return npgettext(
        "user notification",
        "You have %(count)d message",
        "You have %(count)d messages",
        count
    ) % {"count": count}

¿Cómo se ve en el archivo .po?

Después de usar context, el comando de extracción añade msgctxt.

python manage.py makemessages -l ko

El archivo .po resultante:

# django.po

# 1) "Polish" = verbo de refinar la UI
msgctxt "verb: to refine UI"
msgid "Polish"
msgstr "refinar"

# 2) "Polish" = idioma
msgctxt "language name"
msgid "Polish"
msgstr "polaco"

Aunque msgid sea idéntico, el msgctxt diferencia las entradas, lo que facilita la traducción y la gestión.


Consejos para crear buenos contextos

El contexto no se muestra al usuario, pero es la pista más valiosa para el traductor.

1) Describir el rol en la UI

  • "button label"
  • "menu item"
  • "tooltip"
  • "error message"
  • "form field label"

2) Añadir el concepto cuando sea necesario

  • "File""file menu item", "uploaded file object"
  • "Order""e-commerce order", "sorting order"

3) No incluir la traducción en el contexto

El contexto debe ser breve y en el idioma original (usualmente inglés).


Cuándo usar marcadores contextuales

Considera usar context/pgettext cuando:

  1. La cadena es corta (1–2 palabras) y aparece en varios lugares.
  2. Se reutiliza con significados distintos (ej. "Open", "Book").
  3. Los traductores no tienen acceso a la pantalla.

Resumen

Desenfoque de un desarrollador con homónimos

  • Django asigna una única traducción a cada msgid.
  • Los homónimos como "Polish", "May", "Book" suelen causar conflictos.
  • No cambies la cadena original; usa {% translate "…" context "…" %} en plantillas y pgettext/pgettext_lazy/npgettext en Python.
  • Así, el archivo .po tendrá msgctxt, permitiendo distintas traducciones y mejorando la calidad y mantenibilidad.

En la era del soporte multilingüe, los marcadores contextuales son una herramienta esencial para los desarrolladores de Django.


Artículos relacionados