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 ist django_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 der settings/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.