85점
Django Mehrsprachigkeit: „banco / carta / cura“ sauber übersetzen mit Contextual Markers
Wenn man i18n ernsthaft implementiert, stolpert man früher oder später über Mehrdeutigkeiten:
- Ein Button zeigt „banco“, aber die Übersetzung lautet „bank“, obwohl eigentlich „Sitzbank“ gemeint ist.
- In einer Restaurant-Ansicht steht „carta“, wird aber als „Brief“ übersetzt, obwohl hier „Speisekarte“ gemeint ist.
- In einem religiösen Kontext erscheint „cura“, wird jedoch als „Heilung“ übersetzt statt als „Priester“.
Die Ursache ist simpel.
Computer verstehen keinen Kontext. Identische Zeichenketten werden immer gleich behandelt.
In diesem Beitrag zeige ich, wie man mit Djangos Contextual Markers dieselbe Zeichenkette je nach Bedeutung unterschiedlich übersetzen kann, ohne den Quellcode künstlich zu verbiegen.
Warum passiert das? Grundlegendes Verhalten von gettext
Django nutzt intern GNU gettext. Es arbeitet nach sehr einfachen Regeln:
- Originaltext →
msgid - Übersetzung →
msgstr - Gleiche
msgid→ immer dieselbemsgstr
# django.po
msgid "banco"
msgstr "bank"
Sobald eine solche Zuordnung existiert, wird überall, wo „banco“ auftaucht, dieselbe Übersetzung verwendet — auch dann, wenn im UI eigentlich „Sitzbank“ gemeint ist.
Damit können diese beiden Verwendungen nicht unterschieden werden:
- „banco“ = Sitzbank (Möbel)
- „banco“ = Bank (Finanzinstitut)
Eine Möglichkeit, das zu umgehen, wäre, den Quellcode zu ändern:
<!-- zu vermeiden -->
{% trans "banco (bench)" %}
{% trans "banco (bank)" %}
Das funktioniert zwar, führt aber zu unnötig künstlichen Strings und erschwert die Wiederverwendung.
Wir wollen:
- Den Originaltext "banco" beibehalten
- Und gleichzeitig angeben, welche Bedeutung gerade verwendet wird
Hier kommen Contextual Markers ins Spiel.
Lösung in Templates: {% translate %} + context
In Django-Templates kann man dem {% translate %}-Tag (oder {% trans %}) ein context-Attribut hinzufügen, um die gleiche Zeichenkette kontextabhängig zu unterscheiden.
1) Aktueller Code (Kollision)
{% load i18n %}
<button>{% translate "banco" %}</button>
<span>{% translate "banco" %}</span>
Im .po-File sind beide msgid "banco", daher kann nur eine Übersetzung existieren.
2) Verbesserter Code (mit Kontext)
{% load i18n %}
<button>
{% translate "banco" context "seat: bench" %}
</button>
<span>
{% translate "banco" context "financial institution" %}
</span>
Wichtig:
- Der Kontext-Text ist für den Nutzer unsichtbar.
- Er dient ausschließlich dem Übersetzungssystem und den Übersetzern.
-
Er sollte kurz und prägnant sein, z. B.:
-
"seat: bench" "financial institution""restaurant menu""letter (mail)"
3) Auch bei {% blocktranslate %} möglich
{% load i18n %}
{% blocktranslate context "greeting message" with username=user.username %}
Hello {{ username }}
{% endblocktranslate %}
Damit kann dieselbe Zeichenkette in unterschiedlichen Kontexten mehrfach verwendet werden.
Lösung in Python-Code: pgettext
In Views, Models, Forms usw. verwendet man statt gettext die pgettext-Familie.
pgettext(context, message)pgettext_lazy(context, message)– lazy evaluationnpgettext(context, singular, plural, number)– Plural + Kontext
1) Grundbeispiel
from django.utils.translation import pgettext
def my_view(request):
bench = pgettext("seat: bench", "banco")
bank = pgettext("financial institution", "banco")
menu = pgettext("restaurant menu", "carta")
letter = pgettext("letter (mail)", "carta")
priest = pgettext("religion: priest", "cura")
cure = pgettext("medical: cure", "cura")
2) pgettext_lazy in Modellen
from django.db import models
from django.utils.translation import pgettext_lazy
class Order(models.Model):
type = models.CharField(
verbose_name=pgettext_lazy("order model field", "Order type"),
max_length=20,
)
STATUS_CHOICES = [
("open", pgettext_lazy("order status", "Open")),
("opened", pgettext_lazy("log action", "Open")),
]
status = models.CharField(
max_length=20,
choices=STATUS_CHOICES,
)
3) Pluralformen mit npgettext
from django.utils.translation import npgettext
def get_notification(count):
return npgettext(
"user notification",
"You have %(count)d message",
"You have %(count)d messages",
count
) % {"count": count}
Wie sieht das in der .po-Datei aus?
Nach dem Ausführen von python manage.py makemessages -l <locale>:
# django.po
# 1) "banco" = Sitzbank
msgctxt "seat: bench"
msgid "banco"
msgstr "bench"
# 2) "banco" = Bank (Finanzinstitut)
msgctxt "financial institution"
msgid "banco"
msgstr "bank"
# 3) "carta" = Speisekarte
msgctxt "restaurant menu"
msgid "carta"
msgstr "menu"
# 4) "carta" = Brief
msgctxt "letter (mail)"
msgid "carta"
msgstr "letter"
# 5) "cura" = Priester
msgctxt "religion: priest"
msgid "cura"
msgstr "priest"
# 6) "cura" = Heilung
msgctxt "medical: cure"
msgid "cura"
msgstr "cure"
msgid bleibt gleich, aber msgctxt unterscheidet die Einträge.
Tipps für gute Kontext-Strings
- Rolle beschreiben:
"button label","menu item","tooltip","error message","form field label" - Konzept ergänzen:
"File"→"file menu item"/"uploaded file object" - Übersetzungen nicht im Kontext-String: Kontext beschreibt Bedeutung/Rolle, nicht das Ergebnis.
Wann sollte man Contextual Markers einsetzen?
- Kurze Zeichenketten (1–2 Wörter) wie Button-Text, Tab-Name, Menü-Eintrag
- Wiederverwendete Zeichenketten mit unterschiedlichen Bedeutungen
- Szenarien, in denen Übersetzer keinen Zugriff auf die UI haben
Fazit

- Django bindet standardmäßig eine Übersetzung pro
msgid. - Mehrdeutige Strings wie „banco“, „carta“, „cura“ führen häufig zu Konflikten.
-
Statt den Quelltext zu verändern, nutzt man:
-
{% translate "…" context "…" %}in Templates pgettext,pgettext_lazy,npgettextin Python- Dadurch erhält man in der
.po-Dateimsgctxt, was unterschiedliche Übersetzungen ermöglicht.
Die Code-Lesbarkeit bleibt erhalten, die Übersetzungsqualität und Wartbarkeit steigen deutlich.
In einer Welt, in der Mehrsprachigkeit immer wichtiger wird, sind Contextual Markers ein unverzichtbares Werkzeug für Django-Entwickler.
Weitere nützliche Artikel
Es sind keine Kommentare vorhanden.