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 dieselbe msgstr
# 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 evaluation
  • npgettext(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

Entwickler mit verwirrenden Mehrdeutigkeiten

  • 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, npgettext in Python
  • Dadurch erhält man in der .po-Datei msgctxt, 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