Gestion multilingue dans Django : éviter le drame où "Polish" devient "polonais" (marqueurs contextuels)
Lorsque l’on prend la peine de bien supporter le multilingue (i18n), on fait parfois face à des situations embarrassantes.
- Un bouton affiche « Polish » (verbe : polir), mais la traduction devient « polonais ».
- Le composant de sélection de date traduit "May" comme un prénom dans certaines langues.
- Le libellé "Book" ne se traduit qu’en « livre » et pas en « réserver ».
La cause est simple.
L’ordinateur ne connaît pas le contexte. Si deux chaînes sont identiques, il les considère comme la même.
Dans cet article, nous allons voir comment utiliser les marqueurs contextuels de Django pour garder le code source inchangé tout en attachant des traductions différentes à la même chaîne.
Pourquoi ces problèmes surviennent ? Le fonctionnement de base de gettext
Le système de traduction de Django repose sur GNU gettext. qui fonctionne selon des règles très simples :
- La chaîne source →
msgid - La traduction →
msgstr - Si
msgidest identique, on utilise toujours le mêmemsgstr.
# django.po
msgid "Polish"
msgstr "polonais"
Une fois qu’une correspondance est créée dans le fichier .po, toute utilisation de "Polish" dans le projet utilisera la même traduction.
Ainsi, deux usages différents de "Polish" (verbe et nom de langue) ne peuvent pas être distingués.
Pour éviter cela, on pourrait modifier le code source :
<!-- À éviter -->
{% trans "Polish (verb)" %}
{% trans "Polish (language)" %}
Mais cela rendrait le texte affiché étrange ou difficile à réutiliser ailleurs.
Ce que l’on veut, c’est :
- Garder la chaîne source « Polish » intacte.
- Ajouter une description de son sens dans le contexte.
Les marqueurs contextuels sont exactement là pour ça.
Résolution dans les templates : {% translate %} + context
Dans les templates Django, on peut ajouter l’option context au tag {% translate %} (ou {% trans %}) pour distinguer une même chaîne selon le contexte.
1) Code original (collision)
{% load i18n %}
<button>{% translate "Polish" %}</button>
<span>{% translate "Polish" %}</span>
Dans le fichier .po, les deux lignes ont le même msgid "Polish", donc on ne peut choisir qu’une seule traduction.
2) Code amélioré (avec contexte)
{% load i18n %}
<button>
{% translate "Polish" context "verb: to refine UI" %}
</button>
<span>
{% translate "Polish" context "language name" %}
</span>
Points clés :
- Le texte après
contextn’apparaît pas à l’utilisateur. - C’est une information méta pour le système de traduction et le traducteur.
-
Il est conseillé de garder la description courte et explicite.
-
"verb: to refine UI" "language name""menu label""button text"etc.
3) Utilisation avec {% blocktranslate %}
Même pour des phrases longues, on peut ajouter un contexte :
{% load i18n %}
{% blocktranslate context "greeting message" with username=user.username %}
Hello {{ username }}
{% endblocktranslate %}
Ainsi, la même phrase « Hello %(username)s » peut être utilisée dans plusieurs contextes.
Résolution dans le code Python : pgettext
Dans les vues, modèles, formulaires, on utilise les fonctions pgettext et ses variantes.
Les trois principales :
pgettext(context, message)pgettext_lazy(context, message)– évaluation paresseuse (ex. champs de modèle)npgettext(context, singular, plural, number)– pluriel + contexte
1) Exemple de base
from django.utils.translation import pgettext
def my_view(request):
# 1. Nom du mois "May"
month = pgettext("month name", "May")
# 2. Prénom "May"
person = pgettext("person name", "May")
# 3. Verbe modal "may" (~peut-être)
verb = pgettext("auxiliary verb", "may")
Chaque cas a le même msgid mais un contexte différent, donc des traductions distinctes.
2) Utilisation de pgettext_lazy dans un modèle
Les champs de modèle, verbose_name, help_text, ou les libellés de choices peuvent aussi poser des problèmes de homonymie.
from django.db import models
from django.utils.translation import pgettext_lazy
class Order(models.Model):
# "Order" = commande
type = models.CharField(
verbose_name=pgettext_lazy("order model field", "Order type"),
max_length=20,
)
STATUS_CHOICES = [
# "Open" = état
("open", pgettext_lazy("order status", "Open")),
# "Open" = action (ouvrir)
("opened", pgettext_lazy("log action", "Open")),
]
status = models.CharField(
max_length=20,
choices=STATUS_CHOICES,
)
3) Pluriel + contexte : npgettext
from django.utils.translation import npgettext
def get_notification(count):
return npgettext(
"user notification", # contexte
"You have %(count)d message", # singulier
"You have %(count)d messages", # pluriel
count
) % {"count": count}
À quoi ressemble le fichier .po ?
Après extraction des messages :
python manage.py makemessages -l fr
Le fichier .po contient désormais un champ msgctxt.
# django.po
# 1) "Polish" = verbe (polir)
msgctxt "verb: to refine UI"
msgid "Polish"
msgstr "polir"
# 2) "Polish" = nom de langue
msgctxt "language name"
msgid "Polish"
msgstr "polonais"
Même si msgid est identique, le msgctxt différent permet aux outils de traduction de les traiter comme deux entrées distinctes.
Conseils pour rédiger de bons contextes
Le texte de contexte n’est pas affiché à l’utilisateur, mais il constitue la principale aide pour le traducteur.
1) Décrire le rôle
"button label"– texte d’un bouton"menu item"– élément de menu"tooltip"– texte d’infobulle"error message"– message d’erreur"form field label"– libellé de champ
Décrire le rôle de la chaîne dans l’interface garantit que la traduction reste cohérente.
2) Préciser le concept
Pour les mots à double sens :
"File""file menu item""uploaded file object""Order""e-commerce order""sorting order"
En ajoutant le concept, la traduction devient plus fiable.
3) Ne pas mettre la traduction dans le contexte
Le contexte doit rester dans la langue source (souvent l’anglais). Mettre la traduction rend la maintenance difficile et perturbe les systèmes de traduction automatique.
Quand utiliser les marqueurs contextuels ?
Si l’une des conditions suivantes est remplie, il est judicieux d’utiliser context/pgettext.
- Chaîne courte (1–2 mots) : texte de bouton, onglet, élément de menu.
- Même chaîne utilisée dans des sens différents dans l’UI :
"Open","Close","Save","Apply","Reset","Book","Order","Post". - Le traducteur ne voit pas l’interface : plateforme de traduction, fichiers
.poenvoyés à un prestataire.
Résumé

- Django associe par défaut un seul
msgstrà unmsgid. - Les homonymes comme "Polish", "May", "Book" posent des problèmes.
- Ne pas modifier la chaîne source : utilisez
{% translate "…" context "…" %}dans les templatespgettext,pgettext_lazy,npgettextdans le code Python- Le fichier
.pocontiendra alors unmsgctxt, permettant des traductions distinctes.
En suivant ces pratiques, vous conservez la lisibilité du code tout en améliorant la qualité et la maintenabilité des traductions.
Articles complémentaires
Aucun commentaire.