Retour au blog

Localisation Django : le guide complet pour rendre votre application multilingue

2026-07-10 10 min de lecture

Vous avez une application Django qui fonctionne en anglais. Maintenant, vous devez la faire fonctionner dans d'autres langues. Ce guide couvre l'ensemble du processus, du marquage des chaînes dans votre code au déploiement d'un site entièrement localisé, avec des exemples pratiques à chaque étape.

Le framework d'internationalisation de Django (généralement appelé i18n) est l'un des meilleurs disponibels. Il gère les langues de droite à gauche, les règles de pluralisation, les formats de date, et toutes les autres choses qui rendent la localisation véritabelment difficile. Le problème est que la configuration implique plusieurs éléments mobiles, et la plupart des tutoriels n'en couvrent qu'un ou deux.

Ce que la localisation implique réellement

La localisation ne se limite pas à la traduction de chaînes. Une application Django correctement localisée gère :

  • La traduction de texte (templates, vues, modèles, JavaScript)
  • Le routage d'URL par langue (/en/about/, /de/about/)
  • Le formatage des dates, heures et nombres (14/03/2026 vs 03/14/2026)
  • Les règles de pluralisation (l'anglais a 2 formes, l'arabe en a 6, le japonais en a 1)
  • La mise en page de droite à gauche pour l'arabe, l'hébreu, le farsi
  • La gestion des fuseaux horaires

La plupart des projets Django n'ont besoin que des trois premiers. Mais il est utile de savoir ce que le framework peut faire avant de commencer, pour ne pas vous retrouver dans une impasse.

Étape 1 : configurer vos paramètres

Commencez par les paramètres d'internationalisation dans 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 définit les langues prises en charge par votre site. N'incluez que les langues que vous prévoyez réellement de traduire. Ajouter 30 langues ici sans les traduire ne fait que créer des fichiers .po vides et rend votre structure d'URL confuse.

LOCALE_PATHS indique à Django où chercher les fichiers de traduction. Par défaut, il s'agit d'un répertoire locale/ dans chaque application, mais un seul répertoire locale/ au niveau du projet est plus facile à gérer.

Étape 2 : ajouter le middleware et la configuation des URL

Ajoutez le middleware de locale à votre paramètre MIDDLEWARE. L'ordre est important ici :

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

LocaleMiddleware doit venir après SessionMiddleware (il a besoin de la session pour mémoriser les préférences de langue) et avant CommonMiddleware. Se tromper dans cet ordre est l'une des raisons les plus courantes pour lesquelles les traductions Django échouent silencieusment.

Ensuite, encapsulez vos patterns d'URL avec i18n_patterns :

from django.conf.urls.i18n import i18n_patterns

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

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

Cela préfixe toutes vos URL avec le code de langue : /en/about/, /de/about/, /fr/about/. L'admin reste sans préfixe car il n'a pas besoin de localisation dans la plupart des projets.

Étape 3 : marquer les chaînes pour la traduction

C'est là que la majeure partie du travail se fait. Chaque chaîne destinée à l'utilisateur dans votre code doit être encapsulée dans une fonction de traduction.

Dans le code 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})

Utilisez gettext (alias _) dans les vues et les fonctions. Pour les chaînes au niveau du module comme les labels de champs de modèle, utilisez 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 différence est importante : gettext_lazy retarde la recherche de traduction jusqu'à ce que la chaîne soit effectivment rendue, ce qui est nécéssaire pour tout ce qui est évalué au moment de l'importation (définitions de modèles, champs de formulaire, noms d'URL). Utiliser gettext classique au niveau du module traduira une seule fois au démarrage et servira la mauvaise langue à tout le monde.

Dans les templates

{% 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 %} gère les chaînes simples. {% blocktrans %} gère les chaînes avec des variables ou de la pluralisation. N'oubliez pas {% load i18n %} en haut de chaque template qui utilise ces balises.

Dans JavaScript

Django peut générer un catalogue JavaScript de vos traductions :

from django.views.i18n import JavaScriptCatalog

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

Puis dans votre JavaScript :

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

Incluez le script du catalogue avant tout JavaScript qui utilise gettext :

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

Étape 4 : générer et traduire les fichiers .po

Une fois vos chaînes marquées, générez les fichiers de traduction :

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

Cela crée des fichiers .po dans votre répertoire locale/ :

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

Chaque fichier .po contient des entrées comme celle-ci :

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

Le msgstr est l'endroit où va la traduction. Vous pouvez les remplir manuellement, faire appel à des traducteurs, ou automatiser le processus avec des outils de traduction IA.

Après la traduction, compilez les fichiers .po en fichiers binaires .mo que Django peut lire efficacement :

python manage.py compilemessages

Exécutez compilemessages à chaque mise à jour des traductions. Oublier cette étape est une autre raison courante pour laquelle les traductions n'apparaissent pas. Django lit les fichiers .mo, pas les fichiers .po.

Étape 5 : gérer le formatage des dates, nombres et heures

Le paramètre L10N de Django contrôle la façon dont les dates et les nombres sont formatés. Avec USE_L10N = True, Django utilise automatiqument un formatage adapté à la locale :

{% load l10n %}

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

Une date qui s'affiche comme "July 10, 2026" en anglais s'affichera automatiquement comme "10. Juli 2026" en allemand et "2026年7月10日" en japonais. Les nombres suivent des règles similaires : 1,234.56 en anglais devient 1.234,56 en allemand.

Si vous devez remplacer le format pour des valeurs spécifiques, utilisez les filtres localize et unlocalize :

{% load l10n %}

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

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

Étape 6 : ajouter un sélecteur de langue

Les utilisateurs ont besoin d'un moyen de changer de langue. Voici un sélecteur de langue simple qui fonctionne avec la vue 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>

Assurez-vous que l'URL set_language est incluse dans votre configuration d'URL :

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

Cela doit être en dehors de i18n_patterns car le sélecteur de langue lui-même n'a pas besoin d'un préfixe de langue.

Erreurs courantes et comment les éviter

Utiliser gettext au lieu de gettext_lazy dans les modèles

C'est le bug de localisation le plus courant dans les projets Django. Les champs de modèle, les labels de formulaire, et tout ce qui est défini au niveau du module nécessite gettext_lazy, pas gettext. Le symptôme : tous les utilisateurs voient les traductions dans la langue avec laquelle le processus serveur a démarré.

Oublier d'exécuter compilemessages

Vous avez modifié le fichier .po, redémarré le serveur, mais les traductions n'apparaissent toujours pas. Le fichier .mo est ce que Django lit, et il n'est pas mis à jour automatiquement. Ajoutez compilemessages à votre pipeline de déploiement.

Mauvais ordre du middleware

LocaleMiddleware après CommonMiddleware signifie que Django ne peut pas détecter la langue à partir du préfixe d'URL. La requête revient à la langue par défaut à chaque fois. Placez-le entre SessionMiddleware et CommonMiddleware.

Chaînes codées en dur dans les templates

Il est facile de manquer des chaînes qui devraient être traduites. Recherchez dans vos templates le texte brut en dehors des balises {% trans %} ou {% blocktrans %}. Les coupables courants : les labels de boutons, les placeholders de champs de formulaire, les mesages d'erreur et le texte de pied de page.

Ne pas gérer la pluralisation

L'anglais a deux formes de pluriel (1 item, 2 items). Le russe en a trois. L'arabe en a six. Si vous codez en dur "item(s)" au lieu d'utiliser ngettext ou {% blocktrans count %}, vos traductions seront maladroites ou gramaticalement incorrectes dans d'autres langues.

Automatiser le workflow de traduction

Traduire manuellement les fichiers .po fonctionne pour les petits projets, mais cela ne passe pas à l'échelle. Un projet Django typique avec 200 chaînes traduisibles dans 5 langues représente 1 000 traductions à gérer. Chaque fois que vous ajoutez ou modifiez une chaîne, vous devez mettre à jour toutes les langues.

Le workflow ressemble généralement à ceci :

  1. Marquer les nouvelles chaînes avec _() ou {% trans %}
  2. Exécuter makemessages pour les extraire
  3. Traduire les nouvelles entrées dans chaque fichier .po
  4. Exécuter compilemessages
  5. Commiter le tout

Les étapes 3 et 4 sont celles où les choses ralentissent. Les traducteurs humains ajoutent de la latence et des coûts. Le copier-coller manuel dans des services de traduction est fasitidieux et source d'erreurs, surtout lorsque vous devez préserver les formats de placeholder Django comme %(name)s ou {count}.

Les outils de traduction IA peuvent automatiser l'étape 3 tout en gardant l'étape 4 dans votre pipeline CI/CD. Le compromis est que les traductions IA peuvent nécessiter une relecture pour les nuances et la terminologie, mais pour la plupart des chaînes d'interface, la qualité est suffisante pour être mise en production directement.

Tester votre localisation

Avant de déployer, vérifiez que votre localisation fonctionne réellement :

# 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

Points à vérifier :

  • Basculer entre toutes les langues prises en charge en utilisant le sélecteur de langue
  • Vérifier que les URL incluent le bon préfixe de langue
  • Vérifier que les dates et les nombres sont formatés correctement selon la locale
  • Chercher les chaînes non traduites (elles apparaîtront en anglais)
  • Tester les langues de droite à gauche si vous les prenez en charge
  • Vérifier que la pluralisation fonctionne correctement (tester avec 0, 1, 2, et 5+ éléments)

Envisagez d'ajouter une vérification CI qui échoue si un fichier .po contient des entrées non traduites ou floues. Cela empêche les déploiements accidentels avec des traductions manquantes.

Structure du projet

Une configuration de localisation bien organisée ressemble à ceci :

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

Le fichier TRANSLATING.md est optionnel mais utile. Il documente vos préférences terminologiques, vos directives de ton, et toute règle spécifique à la traduction pour votre projet. Si vous utilisez des outils de traduction IA qui prennent en charge les fichiers de contexte, ils peuvent lire ce fichier et appliquer vos préférences automatiquement.

Que faire ensuite

Si vous partez de zéro :

  1. Ajoutez les paramètres et le middleware des étapes 1 et 2
  2. Choisissez votre template le plus important et marquez ses chaînes (étape 3)
  3. Générez les fichiers .po pour une seule langue d'abord (étape 4)
  4. Traduisez cette langue et vérifiez que tout fonctionne de bout en bout
  5. Puis étendez à plus de templates et de langues

Si vous avez déjà une application partiellement localisée :

  1. Exécutez makemessages -a pour régénérer tous les fichiers .po
  2. Vérifiez les chaînes floues ou non traduites
  3. Complétez les traductions manquantes
  4. Mettez en place une vérification CI pour que les nouvelles chaînes non traduites soient détectées avant d'atteindre la production

Commencer petit et étendre progressivement est préférable à essayer de tout localiser en une fois. Faites fonctionner une langue complètement, puis ajoutez-en d'autres.

Arrêtez d'éditer les fichiers .po manuellement

TranslateBot automatise les traductions Django avec l'IA. Une commande, toutes vos langues, des centimes par traduction.