# 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 {#sec-aff43a618865}
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 `msgid` est identique, on utilise toujours le même `msgstr`.
```po
# 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 :
```html
{% 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` {#sec-e28151952ba2}
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) {#sec-40afc992a96b}
```html
{% load i18n %}
{% translate "Polish" %}
```
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) {#sec-4fa05f04b9d3}
```html
{% load i18n %}
{% translate "Polish" context "language name" %}
```
Points clés :
* Le texte après `context` n’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 %}` {#sec-c2a9f27dea01}
Même pour des phrases longues, on peut ajouter un contexte :
```html
{% 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` {#sec-3a9a008d3f99}
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 {#sec-27f89a09cea2}
```python
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 {#sec-11832a96275e}
Les champs de modèle, `verbose_name`, `help_text`, ou les libellés de `choices` peuvent aussi poser des problèmes de homonymie.
```python
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` {#sec-ed84f3cf8d15}
```python
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` ? {#sec-fb610023c81a}
Après extraction des messages :
```bash
python manage.py makemessages -l fr
```
Le fichier `.po` contient désormais un champ `msgctxt`.
```po
# 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 {#sec-df37b35ab408}
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 {#sec-9dfe29490c86}
* `"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 {#sec-135dce4b280b}
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 {#sec-9197b1a38f27}
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 ? {#sec-58e84d2a2b4c}
Si l’une des conditions suivantes est remplie, il est judicieux d’utiliser `context`/`pgettext`.
1. Chaîne courte (1–2 mots) : texte de bouton, onglet, élément de menu.
2. Même chaîne utilisée dans des sens différents dans l’UI : `"Open"`, `"Close"`, `"Save"`, `"Apply"`, `"Reset"`, `"Book"`, `"Order"`, `"Post"`.
3. Le traducteur ne voit pas l’interface : plateforme de traduction, fichiers `.po` envoyés à un prestataire.
---
## Résumé {#sec-8cc5759d0812}

* Django associe par défaut un seul `msgstr` à un `msgid`.
* Les homonymes comme "Polish", "May", "Book" posent des problèmes.
* Ne pas modifier la chaîne source : utilisez
* `{% translate "…" context "…" %}` dans les templates
* `pgettext`, `pgettext_lazy`, `npgettext` dans le code Python
* Le fichier `.po` contiendra alors un `msgctxt`, 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**
- [Django : différencier gettext et gettext_lazy : comprendre le moment d’évaluation](/ko/whitedec/2026/1/5/django-gettext-vs-gettext-lazy/)
- [Problèmes et solutions lorsqu’on utilise gettext_lazy comme clé JSON](/ko/whitedec/2025/4/26/gettext-lazy-json-key-problem-solution/)