Σημάδεψες τα strings για μετάφραση, δημιούργησες αρχεία .po, εκτέλεσες compilemessages, και η εφαρμογή σου εξακολουθεί να δείχνει αγγλικά. Δεν είσαι μόνος. Το i18n framework του Django είναι ισχυρό, αλλά έχει αιχμηρές γωνίες που πιάνουν ακόμα και έμπειρους προγραμματιστές.
Αυτός ο οδηγός καλύπτει τους 10 πιο συνηθισμένους λόγους που οι μεταφράσεις του Django αποτυγχάνουν σιωπηλά, με ακριβή συμπτώματα και διορθώσεις για κάθε έναν.
1. Ξεχνώντας να εκτελέσεις compilemessages μετά την επεξεργασία αρχείων .po
Επεξεργάστηκες ένα αρχείο .po (χειροκίνητα ή με εργαλείο), αλλά το μεταφρασμένο κείμενο δεν εμφανίζεται ποτέ. Η εφαρμογή συνεχίζει να δείχνει τα αρχικά αγγλικά strings.
Το Django δεν διαβάζει αρχεία .po κατά το runtime. Αντ' αυτού διαβάζει τα μεταγλωττισμένα δυαδικά αρχεία .mo (machine object). Αν επεξεργαστείς ένα αρχείο .po χωρίς να μεταγλωττίσεις ξανά, το Django δεν γνωρίζει ότι κάτι άλλαξε.
Εκτέλεσε compilemessages μετά από κάθε αλλαγή αρχείου .po:
python manage.py compilemessages
Αν αυτοματοποιείς τις μεταφράσεις σου με το TranslateBot, πρόσθεσε compilemessages ως τελευταίο βήμα στη ροή εργασίας σου:
python manage.py makemessages -a --no-obsolete
python manage.py translate
python manage.py compilemessages
2. Απουσία {% load i18n %} στα templates
Χρησιμοποιείς {% trans "Hello" %} σε ένα template, αλλά το Django πετάει TemplateSyntaxError. Ή χειρότερα, το tag σιωπηλά δεν κάνει τίποτα αν έχεις λανθασμένα ρυθμισμένο template engine.
Τα tags {% trans %} και {% blocktrans %} βρίσκονται στη βιβλιοθήκη template tags i18n του Django. Χωρίς να τη φορτώσεις, ο template engine δεν τα αναγνωρίζει.
Πρόσθεσε {% load i18n %} στην αρχή κάθε template που χρησιμοποιεί translation tags:
{% load i18n %}
<h1>{% trans "Welcome to our site" %}</h1>
<p>{% blocktrans with name=user.name %}Hello, {{ name }}!{% endblocktrans %}</p>
Αυτό είναι απαίτηση ανά template. Ακόμα κι αν ένα γονικό template φορτώνει i18n, τα θυγατρικά templates που χρησιμοποιούν translation tags χρειάζονται τη δική τους δήλωση {% load i18n %}.
3. Το LocaleMiddleware λείπει από το MIDDLEWARE ή βρίσκεται σε λάθος θέση
Το Django σερβίρει πάντα περιεχόμενο στην προεπιλεγμένη γλώσσα ανεξαρτήτως του header Accept-Language του browser, του URL prefix ή των ρυθμίσεων session.
Το LocaleMiddleware καθορίζει την ενεργή γλώσσα για κάθε αίτημα. Χωρίς αυτό, το Django χρησιμοποιεί προεπιλεγμένα το LANGUAGE_CODE και αγνοεί όλους τους μηχανισμούς επιλογής γλώσσας. Η θέση του στη στοίβα middleware επίσης μετράει, γιατί χρειάζεται πρόσβαση σε δεδομένα session και ανάλυση URL.
Πρόσθεσε LocaleMiddleware στη ρύθμιση MIDDLEWARE, μετά το SessionMiddleware και το 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",
]
Βεβαιώσου επίσης ότι το django.conf.urls.i18n περιλαμβάνεται στη ρύθμιση URL αν χρησιμοποιείς εναλλαγή γλώσσας βασισμένη σε URL:
from django.conf.urls.i18n import i18n_patterns
urlpatterns = i18n_patterns(
path("", include("myapp.urls")),
)
4. Αναντιστοιχίες κωδικών γλώσσας (π.χ. pt-br vs pt_BR)
Οι μεταφράσεις υπάρχουν στα αρχεία .po, το compilemessages πετυχαίνει, αλλά το Django αγνοεί τη μετάφραση για ορισμένα locales.
Το Django αναμένει οι φάκελοι locale να ακολουθούν τη μορφή <language>_<COUNTRY> με κάτω παύλα ως διαχωριστικό και κεφαλαίο κωδικό χώρας. Για παράδειγμα, pt_BR για βραζιλιάνικα πορτογαλικά. Αν ο φάκελός σου ονομάζεται pt-br, pt-BR ή ptBR, το Django δεν θα τον βρει. Το ίδιο ισχύει για τη ρύθμιση LANGUAGES: οι κωδικοί εκεί χρησιμοποιούν παύλες (pt-br), αλλά το σύστημα αρχείων χρησιμοποιεί κάτω παύλες (pt_BR).
Βεβαιώσου ότι η δομή φακέλων αντιστοιχεί στις προσδοκίες του Django:
locale/
pt_BR/
LC_MESSAGES/
django.po
django.mo
Και στις ρυθμίσεις, χρησιμοποίησε τη μορφή με παύλα:
LANGUAGES = [
("en", "English"),
("pt-br", "Brazilian Portuguese"),
("zh-hans", "Simplified Chinese"),
]
Κατά την εκτέλεση makemessages, χρησιμοποίησε τη μορφή με κάτω παύλα για τη σημαία locale:
python manage.py makemessages -l pt_BR
5. Ασαφείς (fuzzy) εγγραφές παραλείπονται σιωπηλά κατά τη μεταγλώττιση
Μια μετάφραση υπάρχει στο αρχείο .po, αλλά το Django δείχνει το αρχικό αγγλικό string κατά το runtime για τη συγκεκριμένη εγγραφή. Αυτό είναι ιδιαίτερα απογοητευτικό γιατί η μετάφραση βρίσκεται ακριβώς εκεί στο αρχείο.
Όταν το makemessages του Django ανιχνεύει ότι ένα πηγαίο string άλλαξε ελαφρώς, σημαδεύει την υπάρχουσα μετάφραση ως "fuzzy" (δηλαδή είναι εικασία που χρειάζεται ανθρώπινη αξιολόγηση). Η εντολή compilemessages παραλείπει όλες τις fuzzy εγγραφές, αντιμετωπίζοντάς τες ως αμετάφραστες. Έτσι η εγγραφή φαίνεται μεταφρασμένη στο αρχείο .po, αλλά το αρχείο .mo την εξαιρεί εντελώς.
Μια fuzzy εγγραφή μοιάζει με αυτό:
#, fuzzy
msgid "Welcome to our website!"
msgstr "Welkom op onze website!"
Αξιολόγησε τη μετάφραση, ενημέρωσε το msgstr αν χρειάζεται, και μετά αφαίρεσε τη σημαία #, fuzzy:
msgid "Welcome to our website!"
msgstr "Welkom op onze website!"
Μετά μεταγλώττισε ξανά:
python manage.py compilemessages
Σε μεγαλύτερο project, οι fuzzy εγγραφές συσσωρεύονται και είναι εύκολο να τις χάσεις. Η εντολή check_translations του TranslateBot τις εντοπίζει αυτόματα:
python manage.py check_translations
locale/nl/LC_MESSAGES/django.po: 0 untranslated, 3 fuzzy
CommandError: Translation check failed
Πρόσθεσε check_translations --makemessages στο CI pipeline σου και δεν θα στείλεις ποτέ ξανά fuzzy εγγραφή.
6. Το LOCALE_PATHS δεν έχει ρυθμιστεί ή δείχνει στον λάθος φάκελο
Το makemessages δημιουργεί αρχεία .po σε μια τοποθεσία, αλλά το Django τα ψάχνει αλλού. Οι μεταφράσεις υπάρχουν στο δίσκο αλλά δεν φορτώνονται ποτέ.
Το Django ψάχνει αρχεία μετάφρασης με συγκεκριμένη σειρά: πρώτα τους φακέλους LOCALE_PATHS, μετά τον φάκελο locale/ κάθε εφαρμογής, και τέλος τον φάκελο locale/ του project. Αν το LOCALE_PATHS δεν έχει οριστεί ή δείχνει στο λάθος μονοπάτι, το Django μπορεί να μη βρει ποτέ τα αρχεία .po σου.
Όρισε LOCALE_PATHS στις ρυθμίσεις σε απόλυτο μονοπάτι:
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
LOCALE_PATHS = [
BASE_DIR / "locale",
]
Επαλήθευσε ότι ο φάκελος υπάρχει και περιέχει την αναμενόμενη δομή:
locale/
de/
LC_MESSAGES/
django.po
django.mo
nl/
LC_MESSAGES/
django.po
django.mo
Ένα συνηθισμένο λάθος είναι να ορίσεις LOCALE_PATHS σε locale/ (σχετικό) αντί για απόλυτο μονοπάτι. Το Django δεν επιλύει σχετικά μονοπάτια από τη ρίζα του project σου. Εξαρτάται από τον φάκελο εργασίας της διεργασίας, που συχνά δεν είναι αυτός που περιμένεις.
7. Η cache σερβίρει παλιές μεταφράσεις
Ενημέρωσες και μεταγλώττισες τις μεταφράσεις, αλλά το παλιό κείμενο συνεχίζει να εμφανίζεται. Η επανεκκίνηση του server το διορθώνει.
Ο κατάλογος μεταφράσεων του Django φορτώνεται μία φορά ανά διεργασία. Στην παραγωγή, WSGI/ASGI servers όπως Gunicorn ή Uvicorn κρατούν τις διεργασίες worker ζωντανές για μεγάλα χρονικά διαστήματα. Το αρχείο .mo μπορεί να άλλαξε στο δίσκο, αλλά η τρέχουσα διεργασία έχει ακόμα τις παλιές μεταφράσεις στη μνήμη. Επιπλέον, αν χρησιμοποιείς το cache framework του Django ή reverse proxy όπως Nginx ή Cloudflare, οι cached απαντήσεις θα σερβίρουν παλιό περιεχόμενο μέχρι να λήξουν.
Κάνε επανεκκίνηση του application server μετά το deploy νέων μεταφράσεων:
# Gunicorn
kill -HUP $(cat /tmp/gunicorn.pid)
# Systemd
sudo systemctl restart myapp
# Docker
docker compose restart web
Για το cache framework του Django, καθάρισε την cache μετά την ενημέρωση μεταφράσεων:
from django.core.cache import cache
cache.clear()
Στην ανάπτυξη, ο runserver ξαναφορτώνει αυτόματα όταν αλλάζουν Python αρχεία αλλά δεν παρακολουθεί αρχεία .mo. Θα χρειαστεί να τον επανεκκινήσεις χειροκίνητα μετά την εκτέλεση compilemessages.
8. Strings που δεν είναι τυλιγμένα σε gettext (_() ή {% trans %})
Το makemessages δεν εξάγει ορισμένα strings, οπότε δεν εμφανίζονται ποτέ στα αρχεία .po και δεν μεταφράζονται ποτέ. Αυτό είναι το πιο βασικό πρόβλημα και επίσης το πιο εύκολο να παραβλεφθεί σε μεγάλη βάση κώδικα.
Η εντολή makemessages του Django χρησιμοποιεί xgettext για να σαρώσει τον πηγαίο κώδικα για δείκτες μετάφρασης. Αν ένα string δεν είναι τυλιγμένο σε gettext() (συνήθως με ψευδώνυμο _()), gettext_lazy(), {% trans %} ή {% blocktrans %}, είναι αόρατο στη διαδικασία εξαγωγής.
Τύλιξε κάθε string που βλέπει ο χρήστης:
# 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>
Χρησιμοποίησε τη σημαία --dry-run του TranslateBot για προεπισκόπηση ποια strings είναι αμετάφραστα αυτή τη στιγμή, ώστε να εντοπίσεις strings που χάθηκαν κατά την εξαγωγή:
python manage.py translate --target-lang de --dry-run
Αυτό δείχνει όλες τις αμετάφραστες εγγραφές στα αρχεία .po χωρίς να κάνει API κλήσεις ή αλλαγές.
9. Τα f-strings δεν μπορούν να μεταφραστούν (Περιορισμός Django)
Τυλίγεις ένα f-string σε _() και είτε παίρνεις syntax error, είτε το makemessages εξάγει ένα κατεστραμμένο/μερικό string που δεν μπορεί να μεταφραστεί.
Τα Python f-strings αξιολογούνται κατά το runtime. Το εργαλείο εξαγωγής xgettext αναλύει τον πηγαίο κώδικα στατικά, οπότε δεν μπορεί να αξιολογήσει Python εκφράσεις μέσα σε αγκύλες {}. Αυτό σημαίνει ότι _(f"Hello, {name}") εξάγεται ως string που περιέχει κυριολεκτική έκφραση {name} (ή αποτυγχάνει εντελώς στην εξαγωγή), και η εγγραφή .po που προκύπτει δεν θα αντιστοιχεί ποτέ στο runtime string.
Χρησιμοποίησε αντ' αυτού τη μορφοποίηση % του Django ή .format() με ονομασμένα placeholders:
# 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)
Αυτός δεν είναι περιορισμός του TranslateBot ή εργαλείου. Είναι θεμελιώδες στον τρόπο λειτουργίας του gettext. Το πηγαίο string πρέπει να είναι στατικό literal ώστε να μπορεί να εξαχθεί και να αναζητηθεί κατά το runtime.
Το TranslateBot διατηρεί όλες αυτές τις μορφές placeholder (%(name)s, {0}, %s, HTML tags) κατά τη μετάφραση, ώστε τα μεταφρασμένα strings να παραμένουν πλήρως λειτουργικά.
10. Σφάλματα format string placeholder που σπάνε τη μεταγλώττιση .po
Το compilemessages αποτυγχάνει με σφάλμα, ή το αρχείο .po έχει αναντιστοιχία σημαίας #, python-format, και η εγγραφή απορρίπτεται σιωπηλά.
Όταν ένα πηγαίο string περιέχει Python format placeholders όπως %(name)s, το Django σημαδεύει την εγγραφή .po με #, python-format. Αν η μετάφραση έχει διαφορετικά placeholders (τυπογραφικό λάθος όπως %(nome)s, ελλιπές placeholder ή επιπλέον), τα εργαλεία gettext μπορεί να απορρίψουν την εγγραφή ή το compilemessages μπορεί να αποτύχει. Αυτό συμβαίνει συχνά με χειροκίνητες μεταφράσεις ή με εργαλεία μετάφρασης AI που δεν κατανοούν τη σημασιολογία placeholder.
Μια κατεστραμμένη εγγραφή μοιάζει ως εξής:
#, python-format
msgid "Hello, %(name)s! You have %(count)d new messages."
msgstr "Hallo, %(naam)s! Je hebt %(count)d nieuwe berichten."
Εδώ %(naam)s πρέπει να είναι %(name)s. Τα placeholders πρέπει να αντιστοιχούν ακριβώς στην πηγή.
Βεβαιώσου ότι τα μεταφρασμένα strings περιέχουν ακριβώς τα ίδια placeholders με την πηγή. Έλεγξε για τυπογραφικά λάθη, ελλιπή placeholders και επιπλέον placeholders.
Αυτός είναι ένας τομέας όπου το TranslateBot παρέχει πραγματικό πλεονέκτημα. Η λογική διατήρησης placeholder διασφαλίζει ότι όλα τα format strings (%(name)s, {0}, %s) στο μεταφρασμένο output αντιστοιχούν ακριβώς στην πηγή. Ο χειρισμός placeholder καλύπτεται από 100% test coverage, οπότε τα σφάλματα format string από τη μετάφραση εξαλείφονται σε επίπεδο εργαλείου αντί να εντοπίζονται κατά τη μεταγλώττιση.
Αν μεταφράζεις χειροκίνητα ή με εργαλείο που δεν χειρίζεται placeholders, επικύρωσε τα αρχεία .po με:
msgfmt --check-format locale/de/LC_MESSAGES/django.po
Αυτό εκτελεί την επικύρωση format string του gettext και αναφέρει τυχόν αναντιστοιχίες.
Βάζοντας τα όλα μαζί: ένα αμυντικό workflow
Τα περισσότερα από αυτά τα ζητήματα μοιράζονται μια κοινή βασική αιτία: χειροκίνητα βήματα που είναι εύκολο να ξεχαστούν. Ορίστε ένα workflow που αποτρέπει και τα 10 προβλήματα:
# 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
Πρόσθεσε το βήμα 4 στο CI pipeline σου και τα αμετάφραστα strings, οι fuzzy εγγραφές και τα σφάλματα μορφοποίησης θα αποτυγχάνουν στο build πριν φτάσουν στην παραγωγή.
Πίνακας γρήγορης αναφοράς
| Αιτία | Σύμπτωμα | Διόρθωση σε μία γραμμή |
|---|---|---|
Χωρίς compilemessages |
Μεταφράσεις υπάρχουν αλλά δεν εμφανίζονται | python manage.py compilemessages |
Λείπει {% load i18n %} |
TemplateSyntaxError στο {% trans %} |
Πρόσθεσε {% load i18n %} στο template |
| Λείπει LocaleMiddleware | Η γλώσσα είναι πάντα προεπιλεγμένα αγγλικά | Πρόσθεσε django.middleware.locale.LocaleMiddleware στο MIDDLEWARE |
| Αναντιστοιχία κωδικού γλώσσας | Ο φάκελος locale δεν βρέθηκε | Χρησιμοποίησε pt_BR (κάτω παύλα) για φακέλους, pt-br (παύλα) για ρυθμίσεις |
| Fuzzy εγγραφές παραλείφθηκαν | Μετάφραση στο .po αλλά όχι στην εφαρμογή |
Αφαίρεσε τη σημαία #, fuzzy μετά την αξιολόγηση |
| Λάθος LOCALE_PATHS | Αρχεία .po υπάρχουν αλλά το Django τα αγνοεί |
Όρισε LOCALE_PATHS σε απόλυτο μονοπάτι |
| Cached μεταφράσεις | Παλιό κείμενο εμφανίζεται μετά την ενημέρωση | Κάνε επανεκκίνηση του application server |
| String εκτός gettext | String λείπει από τα αρχεία .po |
Τύλιξε σε _() ή {% trans %} |
| f-string σε gettext | Κατεστραμμένη εξαγωγή ή αναντιστοιχία runtime | Αντικατέστησε με placeholders % ή .format() |
| Αναντιστοιχία placeholder | compilemessages αποτυγχάνει ή εγγραφή απορρίπτεται |
Αντιστοίχησε τα placeholders ακριβώς μεταξύ πηγής και μετάφρασης |
Τα περισσότερα από αυτά τα προβλήματα εξαφανίζονται όταν αυτοματοποιήσεις το βήμα μετάφρασης και επιβάλεις ελέγχους στο CI. Η εντολή translate του TranslateBot χειρίζεται τη διατήρηση placeholder και τη σταδιακή μετάφραση, ενώ η check_translations πιάνει οτιδήποτε ξεφεύγει (αμετάφραστες εγγραφές, fuzzy σημαίες και ζητήματα format string) πριν φτάσουν στην παραγωγή.