Skip to main content

i18n en Django: guía de internacionalización y traducción

De settings.py a producción: configure el sistema de traducción de Django, escriba archivos PO, gestione plurales y automatice las traducciones con IA.

1

Instalar y extraer cadenas

El framework de i18n de Django está integrado: solo debe activarlo. Instale django-locale-chain para respaldos regionales inteligentes y utilice makemessages para extraer las cadenas traducibles del código Python y las plantillas a archivos PO.

Django utiliza GNU gettext internamente. El comando makemessages analiza el código en busca de llamadas a gettext y etiquetas de plantillas, y genera archivos .po. Tras traducirlos, compilemessages los convierte en archivos binarios .mo para realizar búsquedas rápidas durante la ejecución.
Terminal
pip install django-locale-chain
Terminal
# Extract all translatable strings from Python and template files
python manage.py makemessages -l de -l ja -l es -l fr

# After translating .po files, compile to .mo (binary)
python manage.py compilemessages

# Project structure after running makemessages:
# locale/
# ├── de/
# │   └── LC_MESSAGES/
# │       ├── django.po    <-- translate this
# │       └── django.mo    <-- compiled (auto-generated)
# ├── ja/
# │   └── LC_MESSAGES/
# │       ├── django.po
# │       └── django.mo
# └── es/
#     └── LC_MESSAGES/
#         ├── django.po
#         └── django.mo
2

Configurar ajustes y middleware

Active la internacionalización en settings.py mediante USE_I18N = True, defina la lista LANGUAGES admitida y añada LocaleMiddleware a MIDDLEWARE. LocaleMiddleware detecta el idioma del usuario por el prefijo de URL, la sesión, las cookies o la cabecera Accept-Language.

settings.py
# settings.py

from django.utils.translation import gettext_lazy as _

# Default language
LANGUAGE_CODE = 'en'

# Enable i18n
USE_I18N = True
USE_L10N = True

# Languages your site supports
LANGUAGES = [
    ('en', _('English')),
    ('de', _('German')),
    ('ja', _('Japanese')),
    ('es', _('Spanish')),
    ('fr', _('French')),
    ('pt-br', _('Brazilian Portuguese')),
]

# Where Django looks for .po files
LOCALE_PATHS = [
    BASE_DIR / 'locale',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.locale.LocaleMiddleware',       # <-- enables i18n
    'locale_chain.middleware.LocaleChainMiddleware',    # <-- smart fallbacks
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
LocaleMiddleware debe ir después de SessionMiddleware —lee la sesión— y antes de CommonMiddleware. Si está en el orden equivocado, la detección falla silenciosamente y todos los usuarios ven el LANGUAGE_CODE predeterminado.

Internacionalización de URL

Utilice i18n_patterns() para añadir automáticamente a las URL el prefijo del idioma activo. Así cada idioma obtiene su propio espacio de nombres —/en/about/ o /de/about/—, lo que mejora el SEO y permite compartir enlaces específicos.

urls.py
# urls.py
from django.conf.urls.i18n import i18n_patterns
from django.urls import path, include

urlpatterns = [
    # Non-localized URLs (API, admin, etc.)
    path('api/', include('api.urls')),
]

urlpatterns += i18n_patterns(
    # These get prefixed with the language code: /en/about/, /de/about/
    path('', include('myapp.urls')),
    path('admin/', admin.site.urls),
    prefix_default_language=False,  # Skip prefix for default language
)
3

Marcar cadenas para traducir

Django ofrece dos funciones principales: gettext() —con el alias _()— para cadenas evaluadas al recibir la solicitud y gettext_lazy() para las evaluadas al importar. En las plantillas, utilice las etiquetas {% trans %} y {% blocktrans %}.

En vistas (código Python)

views.py
from django.utils.translation import gettext as _
from django.utils.translation import ngettext
from django.http import HttpResponse

def greeting_view(request):
    # Simple translation
    welcome = _("Welcome to our site")

    # Translation with variables (Python string formatting)
    user_greeting = _("Hello, %(name)s!") % {"name": request.user.username}

    # Plurals
    count = request.user.order_set.count()
    order_text = ngettext(
        "You have %(count)d order.",
        "You have %(count)d orders.",
        count,
    ) % {"count": count}

    return HttpResponse(f"{welcome}<br>{user_greeting}<br>{order_text}")

En plantillas

templates/myapp/index.html
{# Load the i18n template tags #}
{% load i18n %}

{# Simple translation #}
<h1>{% trans "Welcome to our site" %}</h1>

{# Translation with variables #}
{% blocktrans with name=user.username %}
  Hello, {{ name }}!
{% endblocktrans %}

{# Plurals in templates #}
{% blocktrans count count=order_count %}
  You have {{ count }} order.
{% plural %}
  You have {{ count }} orders.
{% endblocktrans %}

{# Mark strings as translatable but don't output them (for attributes, etc.) #}
{% trans "Submit" as submit_label %}
<button type="submit">{{ submit_label }}</button>

En modelos y formularios

models.py
from django.db import models
from django.utils.translation import gettext_lazy as _

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

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

    def __str__(self):
        return self.name

# IMPORTANT: Use gettext_lazy (_) for anything evaluated at import time:
# - Model field labels, verbose_name, help_text
# - Form field labels
# - Class-level attributes
# Use gettext for anything evaluated at request time:
# - View functions, template tags
Utilizar gettext() en vez de gettext_lazy() en campos de modelos, etiquetas de formularios o atributos de clase hace que las cadenas se traduzcan una sola vez al importar —cuando se inicia el servidor— en vez de por solicitud. La traducción queda congelada en el idioma activo durante la importación, normalmente el LANGUAGE_CODE predeterminado.

Formato de archivos PO

Después de ejecutar makemessages, Django genera archivos .po (Portable Object) para cada idioma. Contienen pares msgid/msgstr. Traduzca los valores msgstr y ejecute compilemessages para crear los binarios .mo que Django lee durante la ejecución.

locale/de/LC_MESSAGES/django.po
# locale/de/LC_MESSAGES/django.po

msgid "Welcome to our site"
msgstr "Willkommen auf unserer Seite"

msgid "Hello, %(name)s!"
msgstr "Hallo, %(name)s!"

#, python-format
msgid "You have %(count)d order."
msgid_plural "You have %(count)d orders."
msgstr[0] "Sie haben %(count)d Bestellung."
msgstr[1] "Sie haben %(count)d Bestellungen."

msgid "product name"
msgstr "Produktname"

msgid "description"
msgstr "Beschreibung"

msgid "product"
msgstr "Produkt"

msgid "products"
msgstr "Produkte"

msgid "Submit"
msgstr "Absenden"
4

Gestionar plurales y variables

Django utiliza ngettext() para pluralizar conforme a las reglas de GNU gettext. Cada idioma define cuántas formas tiene y la fórmula para seleccionar la correcta. Los archivos PO lo declaran mediante una cabecera Plural-Forms.

Plural forms by language
# English: 2 forms (singular, plural)
msgid "%(count)d item"
msgid_plural "%(count)d items"
msgstr[0] "%(count)d item"
msgstr[1] "%(count)d items"

# German: 2 forms (singular, plural)
msgstr[0] "%(count)d Artikel"
msgstr[1] "%(count)d Artikel"

# Russian: 3 forms (one, few, many)
msgstr[0] "%(count)d товар"        # 1 item
msgstr[1] "%(count)d товара"       # 2-4 items
msgstr[2] "%(count)d товаров"      # 5+ items

# Arabic: 6 forms (zero, one, two, few, many, other)
msgstr[0] "لا عناصر"               # 0
msgstr[1] "عنصر واحد"              # 1
msgstr[2] "عنصران"                 # 2
msgstr[3] "%(count)d عناصر"        # 3-10
msgstr[4] "%(count)d عنصرًا"       # 11-99
msgstr[5] "%(count)d عنصر"         # 100+

# Japanese: 1 form (no plural distinction)
msgstr[0] "%(count)d個のアイテム"

# In Python code, always use ngettext:
from django.utils.translation import ngettext

msg = ngettext(
    "%(count)d item",
    "%(count)d items",
    count,
) % {"count": count}
Nunca utilice f-strings de Python ni .format() dentro de llamadas a gettext. El extractor no puede analizarlos. Use el formato antiguo %(): _('Hello, %(name)s!') % {'name': name}. Así también garantiza que los traductores vean los marcadores de variables en sus editores PO.

Automatizar la calidad de la traducción

Detecte las claves ausentes y los marcadores rotos antes de publicar con i18n-validate. Pruebe la interfaz con pseudotraducciones mediante i18n-pseudo antes de recibir las reales.

Errores habituales

Confusión entre gettext() y gettext_lazy()

Utilice gettext_lazy() —importado como _— para campos de modelos, etiquetas de formularios y cualquier atributo de clase evaluado al importar. Use gettext() en funciones de vista y código ejecutado al recibir solicitudes. Confundirlos congela las traducciones en el idioma de inicio del servidor.

Olvidar ejecutar compilemessages

Django lee archivos .mo —compilados—, no .po. Si edita un .po y no ejecuta 'python manage.py compilemessages', sus cambios no aparecerán. Añada compilemessages al script de implementación.

LOCALE_PATHS no está configurado

Si LOCALE_PATHS está vacío o apunta al directorio equivocado, makemessages crea los .po en otro lugar y Django no los encuentra durante la ejecución. Defina siempre LOCALE_PATHS = [BASE_DIR / 'locale'] y compruebe que exista el directorio.

Falta i18n_patterns en las URL

Sin i18n_patterns(), las URL no tienen prefijo de idioma y Django depende solo de cookies y cabeceras para detectarlo. Esto perjudica al SEO —no hay URL específicas— y rompe el cambio mediante URL. Envuelva los patrones públicos con i18n_patterns().

Respaldos de configuración regional inteligentes con django-locale-chain

El sistema de traducción de Django pasa directamente a LANGUAGE_CODE cuando falta una variante regional. Un usuario pt-BR ve inglés aunque existan traducciones pt-PT. django-locale-chain lo corrige instalando cadenas de respaldo de gettext: pt-BR prueba pt-PT y después pt antes del idioma predeterminado.

settings.py
# settings.py -- Smart fallback with django-locale-chain
# pip install django-locale-chain

MIDDLEWARE = [
    # ...
    'django.middleware.locale.LocaleMiddleware',
    'locale_chain.middleware.LocaleChainMiddleware',  # after LocaleMiddleware
    # ...
]

# That's it! 75 built-in fallback chains are now active:
# pt-BR user → tries pt-PT → tries pt → falls back to LANGUAGE_CODE
# es-MX user → tries es-419 → tries es → falls back to LANGUAGE_CODE
# fr-CA user → tries fr → falls back to LANGUAGE_CODE

# Optional: customize specific chains
LOCALE_FALLBACK_CHAINS = {
    "pt-BR": ["pt-PT", "pt"],
    "es-MX": ["es-419", "es"],
    "fr-CA": ["fr"],
}

# Or configure programmatically in AppConfig.ready():
from locale_chain import configure

class MyAppConfig(AppConfig):
    name = "myapp"

    def ready(self):
        configure(overrides={"zh-Hant-HK": ["zh-Hant-TW", "zh-Hant"]})

Estructura de proyecto recomendada

Project Structure
myproject/
├── myproject/
│   ├── settings.py          # i18n config, MIDDLEWARE, LANGUAGES
│   ├── urls.py              # i18n_patterns for URL prefixing
│   └── wsgi.py
├── myapp/
│   ├── models.py            # gettext_lazy for field labels
│   ├── views.py             # gettext for request-time strings
│   └── templates/
│       └── myapp/
│           └── index.html   # {% load i18n %}, {% trans %}, {% blocktrans %}
├── locale/                  # Created by makemessages
│   ├── de/
│   │   └── LC_MESSAGES/
│   │       ├── django.po    # German translations
│   │       └── django.mo    # Compiled binary
│   ├── ja/
│   │   └── LC_MESSAGES/
│   │       ├── django.po
│   │       └── django.mo
│   └── es/
│       └── LC_MESSAGES/
│           ├── django.po
│           └── django.mo
├── manage.py
└── requirements.txt

Pruebe i18n Agent ahora

Arrastre y suelte aquí su archivo de traducción

JSON, YAML, PO, XML, CSV, Markdown, Properties

o haga clic para seleccionar

Idiomas de destino

No es necesario registrarsePresupuesto al instante

Respaldo de configuración regional con django-locale-chain

Cuando falta una clave de traducción en una configuración regional como pt-BR, Django pasa directamente al idioma de la plantilla en vez de comprobar primero la configuración regional padre: pt.

Terminal
pip install django-locale-chain
Configuration
# settings.py
LOCALE_CHAINS = {
    'pt-BR': ['pt', 'es', 'en'],
    'zh-Hant-HK': ['zh-Hant', 'zh', 'en'],
}

MIDDLEWARE = [
    ...
    'django_locale_chain.middleware.LocaleChainMiddleware',
    ...
]

Consulte nuestra guía de respaldo de configuraciones regionales para ver todos los frameworks admitidos y las 75 cadenas integradas. Learn more →

Preguntas frecuentes