Volver al blog

Localización en Django: la guía completa para hacer tu aplicación multilingüe

2026-07-10 9 min de lectura

Tiene una aplicación Django que funciona en inglés. Ahora necesita que funcione en otros idiomas también. Esta guía cubre el proceso completo, desde marcar cadenas en su código hasta servir un sitio completamente localizado, con ejemplos prácticos en cada paso.

El framework de internacionalizción de Django (generalmente llamado i18n) es uno de los mejores que existen. Maneja idiomas de derecha a izquierda, reglas de pluralización, formatos de fecha y todas las demás cosas que hacen que la localización sea genuinamente difícil. El inconveniente es que la configuración involucra varias partes móviles, y la mayoría de los tutoriales solo cubren una o dos de ellas.

Qué implica realmente la localización

La localización es más que solo traducir cadenas. Una aplicación Django correctamente localizada maneja:

  • Traducción de texto (plantillas, vistas, modelos, JavaScript)
  • Enrutamiento de URL por idioma (/en/about/, /de/about/)
  • Formato de fechas, horas y números (14/03/2026 vs 03/14/2026)
  • Reglas de pluralización (el inglés tiene 2 formas, el árabe tiene 6, el japonés tiene 1)
  • Diseño de derecha a izquierda para árabe, hebreo, farsi
  • Conciencia de zona horaria

La mayoría de los proyectos Django solo necesitan los primeros tres. Pero vale la pena saber qué puede hacer el framework antes de comenzar, para no quedar atrapado en una esquina.

Paso 1: configure sus ajustes

Comience con los ajustes de internacionalización en settings.py:

from django.utils.translation import gettext_lazy as _

LANGUAGE_CODE = "en"

USE_I18N = True
USE_L10N = True
USE_TZ = True

LANGUAGES = [
    ("en", _("English")),
    ("de", _("German")),
    ("fr", _("French")),
    ("ja", _("Japanese")),
    ("es", _("Spanish")),
]

LOCALE_PATHS = [
    BASE_DIR / "locale",
]

LANGUAGES define qué idiomas soporta su sitio. Solo incluya idiomas que realmente planea traducir. Agregar 30 idiomas aquí sin traducirlos solo crea archivos .po vacíos y confunde su estrucutra de URL.

LOCALE_PATHS le indica a Django dónde buscar los archivos de traducción. El valor predeterminado es un directorio locale/ en cada aplicación, pero un único directorio locale/ a nivel de proyecto es más fácil de administrar.

Paso 2: agregar middleware y configuración de URL

Agregue el middleware de localización a su configuración de MIDDLEWARE. El orden importa aquí:

MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.locale.LocaleMiddleware",  # after SessionMiddleware
    "django.middleware.common.CommonMiddleware",
    # ...
]

LocaleMiddleware debe ir después de SessionMiddleware (necesita la sesión para recordar las preferecnias de idioma) y antes de CommonMiddleware. Poner este orden mal es una de las razones más comunes por las que las traducciones de Django fallan silenciosamente.

Luego envuelva sus patrones de URL con i18n_patterns:

from django.conf.urls.i18n import i18n_patterns

urlpatterns = [
    path("admin/", admin.site.urls),
]

urlpatterns += i18n_patterns(
    path("", include("myapp.urls")),
)

Esto agrega un prefijo a todas sus URL con el código de idioma: /en/about/, /de/about/, /fr/about/. El admin permanece sin prefijo ya que no necesita localización en la mayoría de los proyectos.

Paso 3: marcar cadenas para traduccón

Aquí es donde ocurre la mayor parte del trabajo. Cada cadena visible para el usuario en su código necesita estar envuelta en una función de traducción.

En código Python

from django.utils.translation import gettext as _

def my_view(request):
    message = _("Your order has been placed.")
    return render(request, "order.html", {"message": message})

Use gettext (con alias _) en vistas y funciones. Para cadenas a nivel de módulo como etiquetas de campos de modelo, use gettext_lazy:

from django.utils.translation import gettext_lazy as _

class Product(models.Model):
    name = models.CharField(_("product name"), max_length=200)
    description = models.TextField(_("description"))

    class Meta:
        verbose_name = _("product")
        verbose_name_plural = _("products")

La diferencia importa: gettext_lazy retrasa la búsqueda de la traducción hasta que la cadena se renderiza realmente, lo cual es necesraio para cualquier cosa evaluada en tiempo de importación (definiciones de modelos, campos de formularios, nombres de URL). Usar gettext regular a nivel de módulo traducirá una vez al inicio y servirá el idioma incorrecto a todos.

En plantillas

{% load i18n %}

<h1>{% trans "Welcome to our store" %}</h1>

<p>
    {% blocktrans count items=cart.item_count %}
    You have {{ items }} item in your cart.
    {% plural %}
    You have {{ items }} items in your cart.
    {% endblocktrans %}
</p>

{% trans %} maneja cadenas simples. {% blocktrans %} maneja cadenas con variables o pluralización. No olvide {% load i18n %} al inicio de cada plantilla que use estas etiquetas.

En JavaScript

Django puede generar un catálogo JavaScript de sus traducciones:

from django.views.i18n import JavaScriptCatalog

urlpatterns += i18n_patterns(
    path("jsi18n/", JavaScriptCatalog.as_view(), name="javascript-catalog"),
)

Luego en su JavaScript:

document.getElementById("status").textContent = gettext("Loading...");

Incluya el script del catálogo antes de cualquier JavaScript que use gettext:

<script src="{% url 'javascript-catalog' %}"></script>

Paso 4: generar y traducir archivos .po

Una vez que sus cadenas están marcadas, genere los archivos de traducción:

python manage.py makemessages -l de -l fr -l ja -l es

Esto crea archivos .po en su directorio locale/:

locale/
  de/LC_MESSAGES/django.po
  fr/LC_MESSAGES/django.po
  ja/LC_MESSAGES/django.po
  es/LC_MESSAGES/django.po

Cada archivo .po contiene entradas como esta:

#: myapp/views.py:12
msgid "Your order has been placed."
msgstr ""

El msgstr es donde va la traducción. Puede completarlos manualmente, contratar traductores o automatizar el proceso con herramientas de traducción con IA.

Después de traducir, compile los archivos .po en archivos binarios .mo que Django puede leer de forma eficiente:

python manage.py compilemessages

Ejecute compilemessages cada vez que actualice traducciones. Olvidar este paso es otra razón común por la que las traducciones no aparecen. Django lee los archivos .mo, no los archivos .po.

Paso 5: manejar el formato de fechas, números y horas

La configuración L10N de Django controla cómo se formatean las fechas y los números. Con USE_L10N = True, Django usa automaticamnte formato con reconocimiento de localización:

{% load l10n %}

<p>Order date: {{ order.created_at }}</p>
<p>Total: {{ order.total }}</p>

Una fecha que se muestra como "July 10, 2026" en inglés se mostrará automáticamente como "10. Juli 2026" en alemán y "2026年7月10日" en japonés. Los números siguen reglas similares: 1,234.56 en inglés se convierte en 1.234,56 en alemán.

Si necesita anular el formato para valores específicos, use los filtros localize y unlocalize:

{% load l10n %}

<!-- Always use locale formatting -->
<p>{{ value|localize }}</p>

<!-- Always use the default (English) format -->
<p>{{ value|unlocalize }}</p>

Paso 6: agregar un selector de idioma

Los usuarios necesitan una forma de cambiar de idioma. Aquí hay un selector de idioma simple que funciona con la vista set_language de Django:

{% load i18n %}

<form action="{% url 'set_language' %}" method="post">
    {% csrf_token %}
    <input type="hidden" name="next" value="{{ request.path }}">
    <select name="language" onchange="this.form.submit()">
        {% get_current_language as LANGUAGE_CODE %}
        {% get_available_languages as LANGUAGES %}
        {% for lang_code, lang_name in LANGUAGES %}
            <option value="{{ lang_code }}"
                {% if lang_code == LANGUAGE_CODE %}selected{% endif %}>
                {{ lang_name }}
            </option>
        {% endfor %}
    </select>
</form>

Asegúrese de que la URL de set_language esté incluida en su configuración de URL:

urlpatterns = [
    path("i18n/", include("django.conf.urls.i18n")),
    # ...
]

Esto debe estar fuera de i18n_patterns ya que el selector de idioma en sí no necesita un prefijo de idioma.

Errores comunes y cómo evitarlos

Usar gettext en lugar de gettext_lazy en modelos

Este es el error de localización más común en proyectos Django. Los campos de modelo, las etiquetas de formularios y cualquier otra cosa definida a nivel de módulo necesita gettext_lazy, no gettext. El síntoma: todos los usuarios ven las traducciones en cualquier idioma con el que se inició el proceso del servidor.

Olvidar ejecutar compilemessages

Editó el archivo .po, reinició el servidor, pero las traducciones aún no aparecen. El archivo .mo es lo que Django lee, y no se actualiza automáticamente. Agregue compilemessages a su pipeline de despliege.

Orden incorrecto del middleware

LocaleMiddleware después de CommonMiddleware significa que Django no puede detectar el idioma desde el prefijo de la URL. La solicitud cae al idioma predeterminado cada vez. Colóquelo entre SessionMiddleware y CommonMiddleware.

Cadenas codificadas directamente en plantillas

Es fácil pasar por alto cadenas que deberían ser traducidas. Busque en sus plantillas texto sin envolver fuera de las etiquetas {% trans %} o {% blocktrans %}. Los culpables comunes: etiquetas de botones, placeholders de campos de formulario, mensajes de eror y texto del pie de página.

No manejar la pluralizacón

El inglés tiene dos formas plurales (1 item, 2 items). El ruso tiene tres. El árabe tiene seis. Si codifica "item(s)" directamente en lugar de usar ngettext o {% blocktrans count %}, sus traducciones se leerán de forma extraña o serán gramaticalmente incorrectas en otros idiomas.

Automatización del flujo de trabajo de traducción

Traducir archivos .po manualmente funciona para proyectos pequeños, pero no escala. Un proyecto Django típico con 200 cadenas traducibles en 5 idiomas significa 1,000 traducciones por administrar. Cada vez que agrega o cambia una cadena, necesita actualizar todos los idiomas.

El flujo de trabajo generalmente se ve así:

  1. Marcar nuevas cadenas con _() o {% trans %}
  2. Ejecutar makemessages para extraerlas
  3. Traducir las nuevas entradas en cada archivo .po
  4. Ejecutar compilemessages
  5. Hacer commit de todo

Los pasos 3 y 4 son donde las cosas se ralentizan. Los traductores humanos agregan latencia y costo. Copiar y pegar manualmente en servicios de traducción es tedioso y propenso a errores, especialmente cuando necesita preservar los formatos de marcadores de posición de Django como %(name)s o {count}.

Las herramientas de traducción con IA pueden automatizar el paso 3 mientras mantienen el paso 4 en su pipeline de CI/CD. La compensación es que las traducciones con IA pueden necesitar revisión para matices y terminología, pero para la mayoría de las cadenas de interfaz la calidad es lo suficientemente buena para enviar directamente.

Pruebas de su localización

Antes de desplegar, verifique que su localización realmente funciona:

# Generate messages for all configured languages
python manage.py makemessages -a

# Check for missing translations
python manage.py compilemessages

# Start the dev server and switch languages
python manage.py runserver

Cosas a verificar:

  • Cambiar entre todos los idiomas soportados usando el selector de idioma
  • Verificar que las URL incluyan el prefijo de idioma correcto
  • Comprobar que las fechas y números se formateen correctamente por localización
  • Buscar cadenas sin traducir (aparecerán en inglés)
  • Probar idiomas de derecha a izquierda si los soporta
  • Verificar que la pluralización funcione correctamente (probar con 0, 1, 2 y más de 5 elementos)

Considere agregar una verificación de CI que falle si algún archivo .po tiene entradas sin traducir o difusas. Esto previene despliegues accidentales con traducciones faltantes.

Estructura del proyecto

Una configuración de localización bien organizada se ve así:

myproject/
  locale/
    de/LC_MESSAGES/django.po
    fr/LC_MESSAGES/django.po
    ja/LC_MESSAGES/django.po
    es/LC_MESSAGES/django.po
  myapp/
    templates/
    views.py
    models.py
  settings.py
  urls.py
  TRANSLATING.md

El archivo TRANSLATING.md es opcional pero útil. Documenta sus preferencias de terminología, directrices de tono y cualquier regla específica de traducción para su proyecto. Si utiliza herramientas de traducción con IA que soporten archivos de contexto, pueden leer esto y aplicar sus preferencias automáticamente.

Qué hacer a continuación

Si está comenzando desde cero:

  1. Agregue los ajustes y middleware de los pasos 1 y 2
  2. Elija su plantilla más importante y marque sus cadenas (paso 3)
  3. Genere archivos .po para un idioma primero (paso 4)
  4. Traduzca ese idioma y verifique que funcione de extremo a extremo
  5. Luego expanda a más plantillas e idiomas

Si ya tiene una aplicación parcialmente localizada:

  1. Ejecute makemessages -a para regenerar todos los archivos .po
  2. Busque cadenas difusas o sin traducir
  3. Complete las traducciones faltantes
  4. Configure una verificación de CI para que las nuevas cadenas sin traducir se detecten antes de que lleguen a producción

Comenzar en pequeño y expandir es mejor que intentar localizar todo de una vez. Consiga que un idioma funcione completamnte, luego agregue más.

Deja de editar archivos .po manualmente

TranslateBot automatiza las traducciones de Django con IA. Un comando, todos tus idiomas, centavos por traducción.