Internationalisierung
Mehrsprachige Web-Anwendungen sind heute eher die Regel als die Ausnahme. Wir als Entwickler müssen zwei Arten von Daten unterscheiden:
statische Daten (Felder, Fehlermeldungen, Überschriften, etc)
dynamische Daten, Userdaten (Texte)
Internationalization vs Localization
Internationalisierung und Lokalisierung ermöglichen uns, den Inhalt einer Webanwendung für verschiedene Gebietsschemas bereitzustellen.
Internationalisierung, dargestellt durch i18n (18 ist die Anzahl der Buchstaben zwischen i und n), ist die Entwicklung der Anwendung, damit sie von verschiedenen Gebietsschemas verwendet werden kann. Dieser Prozess wird im Allgemeinen von Entwicklern durchgeführt.
Die Lokalisierung, dargestellt durch l10n (10 ist die Anzahl der
Buchstaben zwischen l und n), ist andererseits der Prozess der
Übersetzung der Anwendung in eine bestimmte Sprache und ein bestimmtes
Gebietsschema
. Dies wird in der Regel von Übersetzern erledigt.
Die Anzeige des deutschen Datums ist ein Beispiel für die Lokalisierung.
Django-Translation-Api
Die Django-Übersetzungs-API bietet mehrere Dienstprogrammfunktionen,
die bei der Übersetzung einer Anwendung unterstützen.
Sie sind alle im Modul django.utils.translation
verfügbar.
In den meisten Fällen wird gettext()
und gettext_lazy()
verwendet.
Installation von GNU gettext
Es muss GNU gettext installiert sein, um die nötigen Dateien zur Internationalisierung zu erzeugen.
GNU gettext
GNU gettext ist eines der am weitesten verbreiteten Werkzeuge zur Internationalisierung von Software. Es bietet eine einfache und dennoch flexible Möglichkeit, die Software zu lokalisieren.
LANGUAGE.po
Dateien enthalten Strings in der Übersetzung in eine bestimmte
Sprache. Es handelt sich um einfache Text-Dateien, die mit einem Editor
bearbeitet werden können. Es ist sinnvoll, diese Dateien mit in die Versionskontrolle aufzunehmen, damit andere Entwickler kompilieren können.
LANGUAGE.mo
Dateien sind die binäre Repräsentation der po-Dateien und
werden zur Laufzeit genutzt. Sie werden -anders als die po-Dateien- nicht
versioniert.
Django bietet durch die manage.py Kommandos makemessages
und
compilemessages
die Erzeugung und kompilierung dieser Dateien.
Installation unter Linux
sudo apt update sudo apt install gettext
Installation unter Windows
Für Windows lässt sich das Programm hier als .exe runterladen: https://mlocati.github.io/articles/gettext-iconv-windows.html
Installation des gettext-Moduls für Python
pip install python-gettext
Settings.base.py anpassen
Attribute für Lokalisierung und Internationalisierung setzen:
from django.utils.translation import gettext_lazy as _
LANGUAGE_CODE = "de"
USE_I18N = True
USE_L10N = True
USE_TZ = True
LANGUAGE_CODE
ist die Default-Sprache, TIME_ZONE
ist die Sprache,
in der Zeitangaben gerendert werden, USE_I18N
ermöglicht den Umgang mit
mehreren Sprachen, USE_L10N
ermöglicht die Lokalisierung von Daten
(Format Von Zeit und Zahlen),
USE_TZ
speichert ein Datetime immer in UTC, um Probleme mit anderen Zeitzonen und Sommerzeiten zu vermeiden.
Einschränken der Sprachen
Wir fügen die Sprachen, die wir für die Übersetzung zur Verfügung stellen möchten,
in die Datei event_manager/event_manager/settings/base.py
ein.
Dies schränkt die Sprachen ein, in die ein Benutzer
die Website übersetzen lassen kann. Mit dem Präfix _()
stellen wir auch die Texte zur Sprachauswahl zum Übersetzen zur Verfügung.``
LANGUAGES = (
('en', _('English')),
('de', _('German')),
)
Standardmäßig wählt Django die Sprache aus, die in LANGUAGE_CODE
in den
settings.py
hinterlegt ist. Damit aber der User selbst eine Sprache wählen
kann, muss die LocaleMiddleware
eingebunden werden.
Middleware einbinden
Um zu ermöglichen, dass der User seine Sprache selbst definieren kann, zb. per Dropdown-Menü, muss eine Middleware eingebunden werden.
Die LocaleMiddleware
sollte nach der Session-Middleware
und -falls vorhanden-
nach der CachingMiddleware
eingebaut werden.
Wir ändern nun unsere Datei event_manager/event_manager/settings/base.py
wie folgt ab:
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.locale.LocaleMiddleware", # <= das hier einbinden
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
Wie wählt LocaleMiddleware die Sprache?
Zunächst wird in der angeforderten URL nach dem Sprachpräfix gesucht. Dies wird nur durchgeführt, wenn die Funktion
i18n_patterns
in der Root-URLconf verwendet wird.Andernfalls wird in der Session des aktuellen Benutzers nach dem Schlüssel
LANGUAGE_SESSION_KEY
gesucht.Andernfalls wird nach einem Cookie gesucht. Der Name des verwendeten Cookies wird durch die Einstellung
LANGUAGE_COOKIE_NAME
festgelegt. (Der Standardname des Cookies istdjango_language
).Andernfalls wird der
HTTP-Header Accept-Language
überprüft. Dieser Header wird von Ihrem Browser gesendet und teilt dem Server mit, welche Sprache(n) Sie bevorzugen, nach Priorität geordnet. Django probiert jede Sprache im Header aus, bis es eine mit verfügbaren Übersetzungen findet.Andernfalls wird die globale Einstellung
LANGUAGE_CODE
aus dersettings/base.py
verwendet.
Was ist der Unterschied zwischen gettext und gettext_lazy?
gettext verweist auf den übersetzten String, gettext_lazy auf eine Referenz des Übersetzungsstring.
Viele Teile im Django-Code werden nur einmal beim Start von Django
ausgeführt. Dazu gehören zum Beispiel Models
oder ModelForms
.
Wenn wir im Model jetzt gettext verwenden würden, würde der User auch nach
Sprachwechsel den schon übersetzen String sehen. Bei gettext_lazy ändert
sich die Referenz und er sieht die richtige Übersetzung.
In views hingegen kann man immer die gettext-Version nehmen. Mehr dazu in den Docs: ‚<https://docs.djangoproject.com/en/ref/topics/i18n/translation/>`_
Übersetzungen im Code
Um die Möglichkeit der Übersetzung im Pythoncode zu bieten, müssen wir gettext
aus den django.utils
importieren. Für gewöhnlich gibt man gettext_lazy
den Alias
_
.
Wir können einen Hilfetext für die Eingabefelder in der Admin (und Frontend)
mit dem Attribut help_text
in den models angeben:
from django.utils.translation import gettext_lazy as _
... Model Code
description = models.TextField(
null=True, blank=True, help_text=_("Die Beschreibung der Kategorie")
)
man beachte das Attribut help_text=_("Die Beschreibung der Kategorie")
Der Wert des Attributs steht innerhalb der Funktion _()
, die der Alias ist für gettext_lazy
.
Mehr Infos in der Doku https://docs.djangoproject.com/en/ref/topics/i18n/translation/ https://simpleisbetterthancomplex.com/tips/2016/10/17/django-tip-18-translations.html
Übersetzung in den Views
In den Views nehmen wir den trans
-Tag bzw. den blocktranslate
-Tag,
um Worte zu übersetzen. Dazu müssen die Templatetags mit {% load i18n %}
erst geladen werden.
{% load i18n %}
{% trans Kategorie %}
{% blocktranslate %}This string will have {{ value }} inside.{% endblocktranslate %}
{% blocktranslate with event_name=event|title author_t=author|title %}
This is {{ event_name }} by {{ author_t }}
{% endblocktranslate %}
Übersetzungsdateien erzeugen
Für projektweite Übersetzungen ein Verzeichnis locale
unter
event_manager
erstellen und für die App events
ebenfalls ein locale
unter
event_manager/events
.
django-admin makemessages --all
Dadurch wird folgende Verzeichnis-Struktur für die events
erstellt:
events
..
│ ├───locale
│ │ ├───de
│ │ │ └───LC_MESSAGES
│ │ └───en
│ │ └───LC_MESSAGES
│ ├───templates
msgid
Dieses Feld ist die ID, idealerweise der Text in der Sprache, die dem LANGUAGE_CODE
aus den settings.py
entspricht. Der Grund ist: wenn wir für diese Sprache kein
Übersetzungs-File haben, wird als Default dieser Wert genommen.
msgstr
msgstr
ist die Übersetzung in der entsprechenden Sprache.
Beispiel
die Zeichenkette „Hallo Welt“ fungiert als msgid
in den po-Dateien. Soll für die englische Sprache eine Übersetzung eingetragen werden, wird in der
englischen po-Datei der msgstr
entsrpechend gefüllt.
x = _("Hallo Welt")
in der po-Datei (EN):
#: .\events\models.py:56
msgid "Hallo Welt"
msgstr "hello world"
Übersetzungen in den Templates
<h1>{% trans 'Eventübersicht' %}</h1>
Po-Datei EN:
#: .\events\templates\events\category_detail.html:14
msgid "Eventübersicht"
msgstr "Event Overview"
Po-Datei DE:
#: .\events\templates\events\category_detail.html:14
msgid "Eventübersicht"
msgstr "Übersicht der Events"
Mo-Dateien erzeugen
Wenn die Übersetzung abgeschlossen ist, mo-Dateien
erzeugen, gilt für Proekt und App
django-admin compilemessages
django-admin compilemessages
erstellt .mo-Dateien
, die laut der Django-Dokumentation binäre Dateien sind, die für die Verwendung durch gettext optimiert sind.
Best Practice
Es sollte davon abgesehen werden, die binären .mo-Dateien
in die Versionierung aufzunehmen.
Anders hingegen die .po-Dateien, diese werden versioniert, da es sich
um Text-Dateien handelt. Diese werden dann auf dem Zielsystem kompiliert.
Der folgende Eintrag in die .gitignore-Datei ist also sinnvoll:
*.mo
in beiden locale
Verzeichnissen wurden jetzt die entsprechenden
django.mo
-Dateien angelegt.
Nachdem die Messages kompiliert wurden, muss der Runserver neu gestartet werden.
Mit curl Accept-Language Header testen
curl -L http://127.0.0.1:8000/events/category/talk -H "Accept-Language: en"
Django Shell
Wir können auch auf der Shell prüfen, ob Übersetzungen für eine Sprache vorhanden sind:
from django.utils import translation
translation.activate("de")
translation.gettext("Event")
User Sprache ändern lassen per Formular
In den event_manager/urls.py
ein urlpattern hinzufügen:
path('i18n/', include('django.conf.urls.i18n')),
im Template base.html
bauen wir jetzt noch folgendes Formular ein:
<form action="{% url 'set_language' %}" method="post">{% csrf_token %}
<input name="next" type="hidden" value="{{ redirect_to }}">
<select name="language">
{% get_current_language as LANGUAGE_CODE %}
{% get_available_languages as LANGUAGES %}
{% get_language_info_list for LANGUAGES as languages %}
{% for language in languages %}
<option value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected{% endif %}>
{{ language.name_local }} ({{ language.code }})
</option>
{% endfor %}
</select>
<input type="submit" value="Go">
</form>
Wenn der User den Wert des Formulars ändert, findet ein Redirect auf die aktuelle Seite, allerdings in anderer Sprache statt.
Dynamische Daten übersetzen
Bisher hatten wir nur statische Daten übersetzt. Dies geschah entweder im
PythonCode, also zum Beispiel in den Models oder Views oder in den Templates
per trans
.
Hier bleiben uns zwei Möglicheiten: entsprechene Model-Felder anlegen für unterschiedliche Sprachen oder einen externen Übersetzungsdienst wie Transiflex nutzen.