Zurück zum Blog

Warum Django-Übersetzungen fehlschlagen (und wie man die 10 häufigsten Ursachen behebt)

2026-02-04 9 Min. Lesezeit
Warum Django-Übersetzungen fehlschlagen (und wie man die 10 häufigsten Ursachen behebt)

Du hast Strings zur Uebersetzung markiert, .po-Dateien generiert, compilemessages ausgefuehrt, und deine App zeigt immer noch Englisch an. Du bist nicht allein. Djangos i18n-Framework ist maechtig, aber es hat scharfe Kanten, die selbst erfahrene Entwickler erwischen.

Dieser Leitfaden behandelt die 10 haeufigsten Gruende, warum Django-Uebersetzungen stillschweigend fehlschlagen, mit genauen Symptomen und Loesungen fuer jeden Fall.

1. Vergessen, compilemessages nach dem Bearbeiten von .po-Dateien auszufuehren

Du hast eine .po-Datei bearbeitet (manuell oder mit einem Tool), aber der uebersetzte Text erscheint nie. Die App zeigt weiterhin die urspruenglichen englischen Strings an.

Django liest keine .po-Dateien zur Laufzeit. Es liest stattdessen die kompilierten .mo (Machine Object) Binaerdateien. Wenn du eine .po-Datei ohne Neukompilierung bearbeitest, weiss Django nicht, dass sich etwas geaendert hat.

Fuehre compilemessages nach jeder .po-Dateiaenderung aus:

python manage.py compilemessages

Wenn du deine Uebersetzungen mit TranslateBot automatisierst, fuege compilemessages als letzten Schritt in deinem Workflow hinzu:

python manage.py makemessages -a --no-obsolete
python manage.py translate
python manage.py compilemessages

2. Fehlendes {% load i18n %} in Templates

Du verwendest {% trans "Hello" %} in einem Template, aber Django wirft einen TemplateSyntaxError. Oder schlimmer, der Tag tut stillschweigend nichts, wenn du eine falsch konfigurierte Template-Engine hast.

Die Tags {% trans %} und {% blocktrans %} befinden sich in Djangos i18n-Template-Tag-Bibliothek. Ohne sie zu laden, erkennt die Template-Engine sie nicht.

Fuege {% load i18n %} am Anfang jedes Templates hinzu, das Uebersetzungs-Tags verwendet:

{% load i18n %}

<h1>{% trans "Welcome to our site" %}</h1>
<p>{% blocktrans with name=user.name %}Hello, {{ name }}!{% endblocktrans %}</p>

Dies ist eine Anforderung pro Template. Selbst wenn ein Eltern-Template i18n laedt, benoetigen Kind-Templates, die Uebersetzungs-Tags verwenden, ihre eigene {% load i18n %}-Deklaration.

3. LocaleMiddleware nicht in MIDDLEWARE oder an falscher Position

Django liefert immer Inhalte in der Standardsprache, unabhaengig vom Accept-Language-Header des Browsers, URL-Praefix oder Session-Einstellungen.

LocaleMiddleware bestimmt die aktive Sprache fuer jede Anfrage. Ohne es verwendet Django standardmaessig LANGUAGE_CODE und ignoriert alle Sprachauswahlmechanismen. Seine Position im Middleware-Stack ist ebenfalls wichtig, da es Zugriff auf Session-Daten und URL-Aufloesung benoetigt.

Fuege LocaleMiddleware zu deiner MIDDLEWARE-Einstellung hinzu, nach SessionMiddleware und CommonMiddleware:

MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.locale.LocaleMiddleware",  # Must be after SessionMiddleware
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
]

Stelle auch sicher, dass django.conf.urls.i18n in deiner URL-Konfiguration enthalten ist, wenn du URL-basierte Sprachumschaltung verwendest:

from django.conf.urls.i18n import i18n_patterns

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

4. Sprachcode-Abweichungen (z.B. pt-br vs pt_BR)

Uebersetzungen existieren in deinen .po-Dateien, compilemessages ist erfolgreich, aber Django ignoriert die Uebersetzung fuer bestimmte Locales.

Django erwartet, dass Locale-Verzeichnisse dem Format <language>_<COUNTRY> mit Unterstrich-Trennzeichen und Grossbuchstaben-Laendercode folgen. Zum Beispiel pt_BR fuer brasilianisches Portugiesisch. Wenn dein Verzeichnis pt-br, pt-BR oder ptBR heisst, wird Django es nicht finden. Dasselbe gilt fuer die LANGUAGES-Einstellung: Die Codes dort verwenden Bindestriche (pt-br), aber das Dateisystem verwendet Unterstriche (pt_BR).

Stelle sicher, dass deine Verzeichnisstruktur den Erwartungen von Django entspricht:

locale/
    pt_BR/
        LC_MESSAGES/
            django.po
            django.mo

Und in deinen Einstellungen verwende die Form mit Bindestrich:

LANGUAGES = [
    ("en", "English"),
    ("pt-br", "Brazilian Portuguese"),
    ("zh-hans", "Simplified Chinese"),
]

Beim Ausfuehren von makemessages verwende die Unterstrich-Form fuer das Locale-Flag:

python manage.py makemessages -l pt_BR

5. Fuzzy-Eintraege werden bei der Kompilierung stillschweigend uebersprungen

Eine Uebersetzung existiert in der .po-Datei, aber Django zeigt zur Laufzeit fuer diesen bestimmten Eintrag den urspruenglichen englischen String an. Das ist besonders frustrierend, weil die Uebersetzung direkt in der Datei steht.

Wenn Djangos makemessages erkennt, dass sich ein Quellstring leicht geaendert hat, markiert es die bestehende Uebersetzung als "fuzzy" (was bedeutet, dass es eine Vermutung ist, die menschliche Ueberpruefung erfordert). Der compilemessages-Befehl ueberspringt alle Fuzzy-Eintraege und behandelt sie als unuebersetzt. Der Eintrag sieht also in der .po-Datei uebersetzt aus, aber die .mo-Datei schliesst ihn vollstaendig aus.

Ein Fuzzy-Eintrag sieht so aus:

#, fuzzy
msgid "Welcome to our website!"
msgstr "Welkom op onze website!"

Ueberpruefe die Uebersetzung, aktualisiere msgstr bei Bedarf und entferne dann das #, fuzzy-Flag:

msgid "Welcome to our website!"
msgstr "Welkom op onze website!"

Dann neu kompilieren:

python manage.py compilemessages

In einem groesseren Projekt haeufen sich Fuzzy-Eintraege und sind leicht zu uebersehen. TranslateBots check_translations-Befehl erkennt diese automatisch:

python manage.py check_translations
locale/nl/LC_MESSAGES/django.po: 0 untranslated, 3 fuzzy
CommandError: Translation check failed

Fuege ihn zu deiner CI-Pipeline mit check_translations --makemessages hinzu, und du wirst nie wieder einen Fuzzy-Eintrag ausliefern.

6. LOCALE_PATHS nicht konfiguriert oder zeigt auf das falsche Verzeichnis

makemessages erstellt .po-Dateien an einem Ort, aber Django sucht sie woanders. Uebersetzungen existieren auf der Festplatte, werden aber nie geladen.

Django sucht nach Uebersetzungsdateien in einer bestimmten Reihenfolge: zuerst LOCALE_PATHS-Verzeichnisse, dann das locale/-Verzeichnis jeder App und schliesslich das locale/-Verzeichnis des Projekts. Wenn LOCALE_PATHS nicht gesetzt ist oder auf den falschen Pfad zeigt, findet Django deine .po-Dateien moeglicherweise nie.

Setze LOCALE_PATHS in deinen Einstellungen auf einen absoluten Pfad:

from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent.parent

LOCALE_PATHS = [
    BASE_DIR / "locale",
]

Ueberpruefe, ob das Verzeichnis existiert und die erwartete Struktur enthaelt:

locale/
    de/
        LC_MESSAGES/
            django.po
            django.mo
    nl/
        LC_MESSAGES/
            django.po
            django.mo

Ein haeufiger Fehler ist, LOCALE_PATHS auf locale/ (relativ) statt auf einen absoluten Pfad zu setzen. Django loest relative Pfade nicht vom Projekt-Root auf. Es haengt vom Arbeitsverzeichnis des Prozesses ab, das oft nicht das ist, was du erwartest.

7. Cache liefert veraltete Uebersetzungen

Du hast Uebersetzungen aktualisiert und kompiliert, aber der alte Text erscheint weiterhin. Ein Neustart des Servers behebt es.

Djangos Uebersetzungskatalog wird einmal pro Prozess geladen. In der Produktion halten WSGI/ASGI-Server wie Gunicorn oder Uvicorn Worker-Prozesse ueber laengere Zeitraeume am Leben. Die .mo-Datei hat sich moeglicherweise auf der Festplatte geaendert, aber der laufende Prozess hat immer noch die alten Uebersetzungen im Speicher. Wenn du zusaetzlich Djangos Cache-Framework oder einen Reverse-Proxy wie Nginx oder Cloudflare verwendest, liefern gecachte Antworten alten Inhalt, bis sie ablaufen.

Starte deinen Anwendungsserver nach dem Deployment neuer Uebersetzungen neu:

# Gunicorn
kill -HUP $(cat /tmp/gunicorn.pid)

# Systemd
sudo systemctl restart myapp

# Docker
docker compose restart web

Fuer Djangos Cache-Framework, leere den Cache nach dem Aktualisieren der Uebersetzungen:

from django.core.cache import cache
cache.clear()

In der Entwicklung laedt runserver automatisch neu, wenn sich Python-Dateien aendern, ueberwacht aber keine .mo-Dateien. Du musst es nach dem Ausfuehren von compilemessages manuell neu starten.

8. Strings nicht in gettext (_() oder {% trans %}) eingeschlossen

makemessages extrahiert bestimmte Strings nicht, daher erscheinen sie nie in deinen .po-Dateien und werden nie uebersetzt. Dies ist das grundlegendste Problem und auch das am leichtesten zu uebersehende in einer grossen Codebasis.

Djangos makemessages-Befehl verwendet xgettext, um deinen Quellcode nach Uebersetzungsmarkierungen zu durchsuchen. Wenn ein String nicht in gettext() (ueblicherweise als _() aliasiert), gettext_lazy(), {% trans %} oder {% blocktrans %} eingeschlossen ist, ist er fuer den Extraktionsprozess unsichtbar.

Schliesse jeden benutzersichtbaren String ein:

# Python code
from django.utils.translation import gettext_lazy as _

class Article(models.Model):
    class Meta:
        verbose_name = _("article")
        verbose_name_plural = _("articles")

# Views
from django.utils.translation import gettext as _

def my_view(request):
    message = _("Your changes have been saved.")
    return HttpResponse(message)
<!-- Templates -->
{% load i18n %}
<h1>{% trans "Welcome" %}</h1>
<p>{% blocktrans with count=items|length %}You have {{ count }} items.{% endblocktrans %}</p>

Verwende TranslateBots --dry-run-Flag, um eine Vorschau der aktuell unuebersetzten Strings zu erhalten, damit du Strings erkennen kannst, die bei der Extraktion uebersehen wurden:

python manage.py translate --target-lang de --dry-run

Dies zeigt alle unuebersetzten Eintraege in deinen .po-Dateien an, ohne API-Aufrufe oder Aenderungen vorzunehmen.

9. f-Strings koennen nicht uebersetzt werden (Django-Einschraenkung)

Du schliesst einen f-String in _() ein und bekommst entweder einen Syntax-Fehler, oder makemessages extrahiert einen defekten/unvollstaendigen String, der nicht uebersetzt werden kann.

Python f-Strings werden zur Laufzeit ausgewertet. Das Extraktionstool xgettext parst Quellcode statisch und kann daher Python-Ausdruecke innerhalb von {}-Klammern nicht auswerten. Das bedeutet, dass _(f"Hello, {name}") als String mit einem woertlichen {name}-Ausdruck extrahiert wird (oder die Extraktion komplett fehlschlaegt), und der resultierende .po-Eintrag wird nie mit dem Laufzeit-String uebereinstimmen.

Verwende stattdessen Djangos %-Formatierung oder .format() mit benannten Platzhaltern:

# Wrong -- f-string cannot be extracted
message = _(f"Hello, {user.name}! You have {count} new messages.")

# Correct -- named placeholders
message = _("Hello, %(name)s! You have %(count)d new messages.") % {
    "name": user.name,
    "count": count,
}

# Also correct -- .format() with positional args
message = _("Hello, {0}! You have {1} new messages.").format(user.name, count)

Dies ist keine Einschraenkung von TranslateBot oder der Tools. Es ist grundlegend fuer die Funktionsweise von gettext. Der Quellstring muss ein statisches Literal sein, damit er zur Laufzeit extrahiert und nachgeschlagen werden kann.

TranslateBot bewahrt alle diese Platzhalterformate (%(name)s, {0}, %s, HTML-Tags) waehrend der Uebersetzung, sodass die uebersetzten Strings voll funktionsfaehig bleiben.

10. Platzhalter-Formatstring-Fehler brechen die .po-Kompilierung

compilemessages schlaegt mit einem Fehler fehl, oder die .po-Datei hat eine #, python-format-Flag-Abweichung, und der Eintrag wird stillschweigend verworfen.

Wenn ein Quellstring Python-Format-Platzhalter wie %(name)s enthaelt, markiert Django den .po-Eintrag mit #, python-format. Wenn die Uebersetzung andere Platzhalter hat (ein Tippfehler wie %(nome)s, ein fehlender Platzhalter oder ein zusaetzlicher), koennen die gettext-Tools den Eintrag ablehnen oder compilemessages kann fehlschlagen. Dies passiert haeufig bei manuellen Uebersetzungen oder bei KI-Uebersetzungstools, die die Platzhalter-Semantik nicht verstehen.

Ein defekter Eintrag sieht so aus:

#, python-format
msgid "Hello, %(name)s! You have %(count)d new messages."
msgstr "Hallo, %(naam)s! Je hebt %(count)d nieuwe berichten."

Hier sollte %(naam)s %(name)s sein. Platzhalter muessen exakt mit der Quelle uebereinstimmen.

Stelle sicher, dass uebersetzte Strings genau die gleichen Platzhalter wie die Quelle enthalten. Pruefe auf Tippfehler, fehlende Platzhalter und zusaetzliche Platzhalter.

Dies ist ein Bereich, in dem TranslateBot einen echten Vorteil bietet. Seine Platzhalter-Erhaltungslogik stellt sicher, dass alle Formatstrings (%(name)s, {0}, %s) in der uebersetzten Ausgabe exakt mit der Quelle uebereinstimmen. Die Platzhalter-Behandlung ist durch 100% Testabdeckung gesichert, sodass Formatstring-Fehler aus der Uebersetzung auf Tool-Ebene eliminiert werden, anstatt erst zur Kompilierzeit erkannt zu werden.

Wenn du manuell oder mit einem Tool uebersetzt, das Platzhalter nicht behandelt, validiere deine .po-Dateien mit:

msgfmt --check-format locale/de/LC_MESSAGES/django.po

Dies fuehrt gettexts Formatstring-Validierung aus und meldet etwaige Abweichungen.

Alles zusammenfuegen: Ein defensiver Workflow

Die meisten dieser Probleme teilen eine gemeinsame Ursache: manuelle Schritte, die leicht vergessen werden. Hier ist ein Workflow, der alle 10 Probleme verhindert:

# 1. Extract strings (catches #8 -- any new gettext-wrapped strings)
python manage.py makemessages -a --no-obsolete

# 2. Translate (catches #1, #5, #8, #9, #10 -- handles untranslated,
#    fuzzy, and placeholder issues automatically)
python manage.py translate

# 3. Compile (catches #1 -- generates .mo files)
python manage.py compilemessages

# 4. Verify in CI (catches everything that slipped through)
python manage.py check_translations --makemessages

Fuege Schritt 4 zu deiner CI-Pipeline hinzu, und unuebersetzte Strings, Fuzzy-Eintraege und Formatfehler werden den Build zum Scheitern bringen, bevor sie die Produktion erreichen.

Kurzreferenztabelle

Ursache Symptom Einzeiler-Fix
Kein compilemessages Uebersetzungen existieren, erscheinen aber nicht python manage.py compilemessages
Fehlendes {% load i18n %} TemplateSyntaxError bei {% trans %} {% load i18n %} zum Template hinzufuegen
LocaleMiddleware fehlt Sprache ist immer Englisch django.middleware.locale.LocaleMiddleware zu MIDDLEWARE hinzufuegen
Sprachcode-Abweichung Locale-Verzeichnis nicht gefunden pt_BR (Unterstrich) fuer Verzeichnisse, pt-br (Bindestrich) fuer Einstellungen verwenden
Fuzzy-Eintraege uebersprungen Uebersetzung in .po, aber nicht in der App #, fuzzy-Flag nach Ueberpruefung entfernen
Falscher LOCALE_PATHS .po-Dateien existieren, aber Django ignoriert sie LOCALE_PATHS auf absoluten Pfad setzen
Gecachte Uebersetzungen Alter Text erscheint nach Update Anwendungsserver neu starten
String nicht in gettext String fehlt in .po-Dateien In _() oder {% trans %} einschliessen
f-String in gettext Defekte Extraktion oder Laufzeit-Abweichung Durch %- oder .format()-Platzhalter ersetzen
Platzhalter-Abweichung compilemessages schlaegt fehl oder Eintrag verworfen Platzhalter zwischen Quelle und Uebersetzung exakt abgleichen

Die meisten dieser Probleme verschwinden, wenn du den Uebersetzungsschritt automatisierst und Pruefungen in der CI durchsetzt. TranslateBots translate-Befehl behandelt Platzhalter-Erhaltung und inkrementelle Uebersetzung, waehrend check_translations alles auffaengt, was durchrutscht (unuebersetzte Eintraege, Fuzzy-Flags und Formatstring-Probleme), bevor es die Produktion erreicht.

Schluss mit der manuellen Bearbeitung von .po-Dateien

TranslateBot automatisiert Django-Übersetzungen mit KI. Ein Befehl, alle Sprachen, Cent pro Übersetzung.