Software-Lokalisierung

Der ultimative Guide zur Python-Lokalisierung

Mit dem Schritt-für-Schritt-Leitfaden zur Vorbereitung von Python-Apps für die Lokalisierung und den gängigen Modulen kann mehrsprachige Unterstützung für eine globale Benutzerbasis implementiert werden.
Python localization blog post featured image | Phrase

Die Lokalisierung von Python-Anwendungen ist ein Prozess mit vielen beweglichen Teilen. Aber wenn es richtig gemacht wird, kann die Zugänglichkeit verbessert, die Nutzerbindung erhöht und die Marktreichweite erweitert werden.

Um den Einstieg zu erleichtern, werden in diesem Tutorial bewährte Praktiken gezeigt, wie Python-Apps mit JSON- oder YAML-Dateien für die Lokalisierung vorbereitet werden können, und gängige Module besprochen, die dabei helfen.

Es bietet wertvolle Einblicke sowohl für Entwickler, die die globale Anziehungskraft verbessern möchten, als auch für Unternehmen, die in neue Märkte expandieren wollen.

UI-Strings mit Python internationalisieren – so geht’s.

Einige wichtige Fragen dazu, wie eine Python-App am besten für die Lokalisierung vorbereitet wird.

Warum werden Übersetzungsdateien für die Benutzeroberfläche verwendet?

Es gibt einige Vorteile bei der Verwendung von Übersetzungsdateien anstelle von fest codierten Übersetzungstexten in der Anwendung:

  • Es ist einfacher zu bearbeiten und zu übersetzen, da die meisten Übersetzer mit Code nicht vertraut sind.
  • Falsche Übersetzungstexte lassen sich leicht identifizieren, ohne im Code suchen zu müssen
  • Es ist nicht notwendig, Anwendungen neu zu erstellen, wenn es eine Änderung an einer Übersetzung gibt (abhängig von den verwendeten Programmiersprachen und Frameworks)

Im Kern können Übersetzungsdateien in verschiedenen Formaten vorliegen. Im folgenden Abschnitt wird gezeigt, wie die Internationalisierung einer Python-Anwendung erfolgen kann:

  • Benutzerdefinierte Implementierung mit JSON/YAML als Übersetzungsdateien
  • babel und gettext für die Arbeit mit PO- und MO-Übersetzungsdateien

Installation

Bevor es losgeht, sollten die folgenden Python-Pakete installiert werden:

  • babel: Tools für die Internationalisierung und Lokalisierung in Python
  • pyyaml: Unterstützung beim Lesen und Schreiben von YAML-Dateien

Die folgenden Befehle können in der Kommandozeile ausgeführt werden, um die Pakete zu installieren.

pip install pyyaml
pip install babel

JSON-Übersetzungsdateien laden – so geht’s

Eine der einfachsten Methoden, um Übersetzungsdateien zu speichern, ist per JSON. Zum Beispiel kann eine neue Datei namens en.json mit den folgenden Elementen erstellt werden:

{
    "title": "Dialogstation",
    "ques-name": "Namen eingeben:",
    "ques-age": "Bitte Alter eingeben:",
    "ans-name": "Hallo, $name!" Willkommen bei Phrase
    "ans-age": {
        "eins": "Du bist $count Jahr alt",
        "Sonstiges": "Du bist $count Jahre alt"
    },
    "ques-dob": "Bitte Geburtsdatum eingeben (JJJJ-MM-TT):",
    "ans-dob": Geboren am $dob
}Code-Sprache: JSON / JSON mit Kommentaren (json)

Dann einfach mit dem integrierten json Paket wie folgt laden:

import json

mit open('filename.json', 'r', encoding='utf8') als f:
    data = json.load(f)Code-Sprache: JavaScript (javascript)

YAML-Übersetzungsdateien laden – so geht’s

YAML-Dateien können anstelle von JSON verwendet werden. Das pyyaml-Paket wird benötigt, um YAML zu unterstützen.

Angenommen, en.yaml ist die Übersetzungsdatei für Englisch mit dem folgenden Inhalt:

title: Interaktives Terminal
Fragenname: "Name eingeben:"
Fragenalter: "Alter eingeben:"
ans-name: Hallo, $name! Willkommen bei Phrase
Antwortalter:
  eins: Du bist $count Jahre alt
  Sonstiges: Du bist $count Jahre alt
ques-dob: "Geburtsdatum eingeben (JJJJ-MM-TT):"
ans-dob: Du wurdest am $dob geboren

Ebenso sollte die deutsche Übersetzungsdatei de.yaml heißen:

title: Dialogstation
ques-name: "Name eingeben:"
ques-age: "Alter eingeben:"
ans-name: Hallo, $name! Willkommen bei Phrase
Antwort-Alter:
  eins: Man ist $count Jahre alt
  Sonstiges: Man ist $count Jahre alt
ques-dob: Geburtsdatum (JJJJ-MM-TT) eingeben:
ans-dob: Geburtsdatum ist der $dobCode-Sprache: PHP (php)

Es kann mit dem pyyaml Paket wie folgt geladen werden:

import yaml

mit open('filename.yml', 'r', encoding='utf8') als f:
    data = yaml.safe_load(f)import jsonCode-Sprache: JavaScript (javascript)

Translator-Klasse

Mit dem bisher Gelernten wird ein individuelles Modul mit einer Translator-Klasse mit den folgenden Features erstellt:

  • Übersetzungsdateien beim Start laden
  • Die aktive Locale wird festgelegt
  • Die Pluralregeln werden festgelegt
  • Der Übersetzungstext wird übersetzt und formatiert

Zusätzlich beinhaltet das Modul auch 2 weitere Funktionen, um:

  • Ein String wird in ein datetime-Objekt umgewandelt
  • Ein datetime-Objekt wird als String formatiert

Darauf achten, dass die Dateistruktur wie folgt aussieht:

.
├── data (Ordner mit JSON- oder YAML-Übersetzungsdateien)/
│   ├── en.json
│   ├── de.json
│   ├── en.yaml
│   └── de.yaml
├── i18n.py (benutzerdefiniertes Modul)
└── main.py (Hauptanwendung)Code-Sprache: Klartext (plaintext)

Eine neue Python-Datei namens i18n.py mit folgendem Code erstellen:

import json
import glob
import os
import yaml
from datetime import datetime
from babel.dates import format_datetime

supported_format = ['json', 'yaml']


class Translator():
    def __init__(self, translations_folder, file_format='json', default_locale='en'):
        # Initialisierung
        self.data = {}
        self.locale = 'en'

        # prüfen, ob das Format unterstützt wird
        wenn file_format in supported_format enthalten ist
            # Liste der Dateien mit spezifischen Erweiterungen bekommen
            files = glob.glob(os.path.join(translations_folder, f'*.{file_format}'))
            für fil in files:
                # Den Namen der Datei ohne Erweiterung holen, wird als Locale-Name verwendet
                loc = os.path.splitext(os.path.basename(fil))[0]
                mit open(fil, 'r', encoding='utf8') als f:
                    if file_format == 'json':
                        self.data[loc] = json.load(f)
                    elif file_format == 'yaml':
                        self.data[loc] = yaml.safe_load(f)

    def set_locale(self, loc):
        wenn loc in self.data:
            self.locale = loc
        else:
            print('Ungültiges Gebietsschema')

    def get_locale(self):
        return self.locale

    def translate(self, key):
        # Den Schlüssel statt des Übersetzungstextes zurückgeben, wenn das Gebietsschema nicht unterstützt wird
        if self.locale not in self.data:
            return key

        text = self.data[self.locale].get(key, key)
        
        return text


def str_to_datetime(dt_str, format='%Y-%m-%d'):
    return datetime.strptime(dt_str, format)


def datetime_to_str(dt, format='MMMM dd, yyyy', loc='en'):
    return format_datetime(dt, format=format, locale=loc)

Das glob-Modul wird verwendet, um die Übersetzungsdateien je nach file_format-Wert dynamisch zu laden. Es werden entweder json oder yaml akzeptiert:

    def __init__(self, folder, file_format='json', default_locale='en'):
        

        # Prüfen, ob das Format unterstützt wird
        wenn file_format in supported_format:
            # Liste von Dateien mit bestimmten Erweiterungen abrufen
            Dateien = glob.glob(os.path.join(Ordner, f'*.{file_format}'))
            für Datei in Dateien:
                # Den Namen der Datei ohne Erweiterung holen, der als Locale-Name verwendet wird
                Ort = os.path.splitext(os.path.basename(Datei))[0]
                mit open(Datei, 'r', encoding='utf8') als f:
                    wenn file_format == 'json':
                        self.data[Ort] = json.load(f)
                    elif file_format == 'yaml':
                        self.data[loc] = yaml.safe_load(f)Code-Sprache: PHP (php)

Die Kernfunktion ist die translate-Funktion, die den Übersetzungstext basierend auf der aktuell aktiven Locale zurückgibt:

    def translate(self, key):
        # stattdessen den Schlüssel zurückgeben
        wenn self.locale nicht in self.data enthalten ist:
            Schlüssel zurückgeben

        text = self.data[self.locale].get(key, key)

        Text zurückgebenCode-Sprache: PHP (php)

Das Modul enthält außerdem 2 zusätzliche globale Funktionen für die Datumsformatierung:

def str_to_datetime(dt_str, format='%Y-%m-%d'):
    return datetime.strptime(dt_str, format)

def datetime_to_str(dt, format='MMMM dd, yyyy', loc='en'):
    return format_datetime(dt, format=format, locale=loc)Code-Sprache: JavaScript (javascript)

Zur Translator-Klasse wird Interpolation hinzugefügt

Für die Zeichenfolgeninterpolation kann das integrierte Template-String-Modul verwendet werden. Folgende Import-Anweisung wird hinzugefügt:

from string import TemplateCode-Sprache: JavaScript (javascript)

Anstatt die Variable text direkt unter der Funktion translate zurückzugeben, wird sie mit der Klasse Template instanziiert und die Funktion safe_substitute wie folgt aufgerufen:

def translate(self, key, **kwargs):
    # Wenn das Gebietsschema nicht unterstützt wird, wird der Schlüssel anstelle des Übersetzungstextes zurückgegeben
    Falls self.locale nicht in self.data enthalten ist:
        Gibt den Schlüssel zurück

    text = self.data[self.locale].get(key, key)
    
    # String-Interpolation
    return Template(text).safe_substitute(**kwargs)Code-Sprache: PHP (php)

Pluralisierung zur Translator-Klasse hinzufügen

Die babel.plural.PluralRule Klasse kann für Pluralisierungsunterstützung importiert werden:

from babel.plural import PluralRuleCode-Sprache: JavaScript (javascript)

Unter der __init__-Funktion eine neue plural_rule-Variable wie folgt initialisieren:

    def __init__(self, folder, file_format='json', default_locale='en'):
        self.data = {}
        self.locale = default_locale
        self.plural_rule = PluralRule({'one': 'n ist Code-Sprache: PHP (php)
class Translator():
    

    def set_locale(self, loc):
        if loc in self.data:
            self.locale = loc
        else:
            print('Ungültige Locale')

    def get_locale(self):
        return self.locale

    def set_plural_rule(self, rule):
        versuchen:
            self.plural_rule = PluralRule(rule)
        except Exception:
            print('Ungültige Pluralregel')

    def get_plural_rule(self):
        return self.plural_rule

Änderung des Codes unter der translate Funktion zur Einbeziehung einer Pluralisierungsprüfung:

class Translator():
    ...

    def translate(self, key, **kwargs):
        # Rückgabe des Schlüssels statt des Übersetzungstextes, wenn das Gebietsschema nicht unterstützt wird
        if self.locale not in self.data:
            Schlüssel zurückgeben

        text = self.data[self.locale].get(key, key)

        # Typ dict steht für Schlüssel in Pluralform
        if type(text) == dict:
            count = kwargs.get('count', 1)
        # Anzahl in einen Integer umwandeln
        Versuchen:
            count = int(count)
        außer Exception:
            print('Ungültige Anzahl')
            Schlüssel zurückgeben

        text = text.get(self.plural_rule(count), key)
        Rückgabe von Template(text).safe_substitute(**kwargs)

Der komplette Code für die Translator-Klasse sieht wie folgt aus:

from babel.plural import PluralRule
import json
from string import Template
import glob
import os
import yaml
from datetime import datetime
from babel.dates import format_datetime

supported_format = ['json', 'yaml']


class Translator():
    def __init__(self, translations_folder, file_format='json', default_locale='en'):
        # Initialisierung
        self.data = {}
        self.locale = 'en'
        self.plural_rule = PluralRule({'one': 'n ist 1'})

        # prüfen, ob das Format unterstützt wird
        wenn file_format in supported_format:
            # Liste von Dateien mit bestimmten Erweiterungen anzeigen
            Dateien = glob.glob(os.path.join(translations_folder, f'*.{file_format}'))
            für Datei in Dateien:
                # Den Namen der Datei ohne Erweiterung holen, wird als Locale-Name genutzt
                loc = os.path.splitext(os.path.basename(fil))[0]
                mit open(Datei, 'r', encoding='utf8') als f:
                    wenn file_format == 'json':
                        self.data[loc] = json.load(f)
                    elif file_format == 'yaml':
                        self.data[loc] = yaml.safe_load(f)

    def set_locale(self, loc):
        if loc in self.data:
            self.locale = loc
        else:
            print('Ungültige Locale')

    def get_locale(self):
        return self.locale

    def set_plural_rule(self, rule):
        try:
            self.plural_rule = PluralRule(rule)
        except Exception:
            print('Ungültige Pluralregel')

    def get_plural_rule(self):
        return self.plural_rule

    def translate(self, key, **kwargs):
        # Den Schlüssel anstelle des Übersetzungstextes zurückgeben, wenn das Gebietsschema nicht unterstützt wird
        wenn self.locale nicht in self.data enthalten ist:
            Schlüssel zurückgeben

        text = self.data[self.locale].get(key, key)
        # Typ dict repräsentiert Schlüssel in der Pluralform
        wenn type(text) == dict:
            count = kwargs.get('count', 1)
            # parse count to int
            Versuch:
                count = int(count)
            außer Exception:
                print('Ungültige Anzahl')
                return key
            text = text.get(self.plural_rule(count), key)
        return Template(text).safe_substitute(**kwargs)


def parse_datetime(dt, input_format='%Y-%m-%d', output_format='MMMM dd, yyyy', output_locale='en'):
    dt = datetime.strptime(dt, input_format)
    return format_datetime(dt, format=output_format, locale=output_locale)

Einsatz des Translator-Moduls in der Anwendung

Nun kann die Übersetzer Klasse als Modul in der Anwendung genutzt werden. Schau dir das folgende Code-Schnipsel an, es dient als Referenz für die grundlegende Nutzung des individuellen Übersetzer Moduls:

# Modul importieren
import i18n

# Eine neue Übersetzer-Klasse mit dem Pfad zu den Daten erstellen
translator = i18n.Translator('data/')Code-Sprache: PHP (php)
name = 'John Doe'
print(translator.translate('ans-name', name=name))
# Hallo, John Doe! Willkommen bei Phrase

# Die aktive Locale auf de ändern
translator.set_locale('de')
print(translator.translate('ans-name', name=name))
# Hallo, John Doe! Willkommen bei Phrase

Alter = 30
print(translator.translate('ans-age', count=Alter))
# Du bist 30 Jahre alt

dob = '1992-01-01'
dob = i18n.parse_datetime(dob)

print(translator.translate('ans-dob', dob=dob))
# Am 01. Januar 1992 geboren Code-Sprache: PHP (php)

Es ist einfach, die Funktionalität des i18n Moduls zu erweitern, um mehr Funktionen je nach Bedarf zu unterstützen.

Wie kann die Python-UI mit gettext internationalisiert werden?

Alternativ kann das gettext Modul zur Internationalisierung von Python-Anwendungen genutzt werden. gettext verwendet PO (auch bekannt als POT) und MO-Nachrichtenkatalogdateien.

🗒 Hinweis » PO-Dateien stellen die von Menschen bearbeitbaren Übersetzungsdateien dar, während MO-Dateien maschinenlesbar für den Gebrauch durch gettext sind.

Zum Glück ergänzt das babel Paket das gettext Modul hervorragend. babel bietet folgende praktische Funktionen für die Arbeit mit Nachrichtenkatalogen:

  • extract: Nachrichten aus Quelldateien extrahieren, um eine POT-Datei zu generieren
  • init: neue Nachrichtenkataloge aus einer POT-Datei erstellen
  • update: vorhandene Nachrichtenkataloge in einer POT-Datei updaten
  • compile: POT-Dateien zu MO-Dateien kompilieren

Grundlegende Übersetzungsnachrichten

In der ersten Phase werden Strings in den Quelldateien als übersetzbar markiert. Einfach übersetzbare Strings mit der _() Funktion umschließen:

# unmarkierte Strings
print('Interaktives Terminal')
print('title')

# Als übersetzbar markierte Strings
print(_('Interaktives Terminal'))
print(_('title'))Code-Sprache: PHP (php)
pybabel extract -o data/messages.pot main_babel.py

Folgender Inhalt sollte in der Ausgabedatei data/messages.pot enthalten sein:

    msgid ""
    msgstr ""
    "Project-Id-Version: PROJEKT-VERSION\n"
    "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
    "POT-Erstellungsdatum: 2022-06-17 23:04+0800\n"
    "PO-Überarbeitungsdatum: JAHR-MO-TA STUNDE:MINUTE+ZONE\n"
    "Letzter Übersetzer: FULL NAME <EMAIL@ADDRESS>\n"
    "Language-Team: LANGUAGE <LL@li.org>\n"
    "MIME-Version: 1.0\n"
    "Content-Type: text/plain; charset=utf-8\n"
    "Content-Transfer-Encoding: 8bit\n"
    "Generated-By: Babel 2.10.1\n"

    #: main_babel.py:11
    msgid "title"
    msgstr ""

    #: main_babel.py:14
    msgid "ques-name"
    msgstr ""

    #Code-Sprache: PHP (php)

Die Grundübersetzungsdatei wird dargestellt. msgid ist der einzigartige Bezeichner für jede zu übersetzende Nachricht, während msgstr den Übersetzungstext repräsentiert. msgstr kann vorerst leer bleiben, da der eigentliche Übersetzungstext in den PO-Dateien stehen sollte.

Während der Initialisierungsphase erzeugt das babel Modul automatisch die relevanten Dateien für jede Locale. Durch Ausführen des folgenden Befehls für die en Locale kann ein Test durchgeführt werden.

pybabel init -l en -i data/messages.pot -d data/
pybabel init -l de -i data/messages.pot -d data/

Der Inhalt jeder messages.po Datei sollte derselbe wie der der Basis-POT-Datei sein. Als nächstes wird der entsprechende Übersetzungstext in die PO-Dateien eingefügt:

.
├── messages.pot
├── de/
│   └── LC_MESSAGES/
│       └── messages.po
└── en/
    └── LC_MESSAGES/
        └── messages.poCode-Sprache: Klartext (plaintext)
#: main_babel.py:11
msgid "title"
msgstr "Interaktives Terminal"

#: main_babel.py:14
msgid "ques-name"
msgstr "Name eingeben:"

#: main_babel.py:16
msgid "ans-name"
msgstr "Hallo, {name}!" Willkommen bei Phrase"

#: main_babel.py:19
msgid "ques-age"
msgstr "Alter eingeben:"

#: main_babel.py:21
msgid "ans-age"
msgid_plural "ans-age-plural"
msgstr[0] "Du bist {count} Jahre alt"
msgstr[1] "Es sind {count} Jahre vergangen"

Am Ende wird der folgende Befehl ausgeführt, um PO-Dateien in MO-Dateien zu kompilieren:

pybabel compile -d data/

Der Ordner data sollte wie folgt aussehen:

.
├── messages.pot
├── de/
│   └── LC_MESSAGES/
│       ├── messages.mo
│       └── messages.po
└── en/
    └── LC_MESSAGES/
        ├── messages.mo
        └── messages.poCode-Sprache: Klartext (plaintext)

Sobald das erledigt ist, kann mit der gettext.translation Funktion die Übersetzungsdateien geladen werden.

Zum Beispiel kann die install Funktion aufgerufen werden, um sie als die aktuell aktive Locale festzulegen und den Übersetzungstext wie folgt zu erhalten:

import gettext

# Initialisierung
lang_en = gettext.translation('messages', localedir='data', languages=['en'])

# aktuelle Sprache auf en setzen
lang_en.install()

print(_('ans-name'))
# Hallo, John! Willkommen bei PhraseCode-Sprache: PHP (php)
#: main_babel.py:16
msgid "ans-name"
msgstr "Hallo, {name}!" Willkommen bei PhraseCode-Sprache: PHP (php)
import gettext

# Initialisierung
lang_en = gettext.translation('messages', localedir='data', languages=['en'])

# aktuelle Sprache auf en setzen
lang_en.install()

print(_('ans-name').format(name='John'))
# Hallo, John! Willkommen bei Phrase

print(_('ans-name').format(name='Kelly'))
# Hallo, Kelly! Willkommen bei PhraseCode-Sprache: PHP (php)

Pluralisierungsunterstützung mit ngettext hinzufügen

Für Pluralisierungsunterstützung die ngettext Funktion nutzen. 3 Eingabeargumente werden akzeptiert:

  • singular: ID für Einzahlform
  • plural: ID für die Pluralform
  • n: Pluralbestimmer
print(ngettext('ans-age', 'ans-age-plural', age))Code-Sprache: PHP (php)

Wie die gettext-Funktion, gibt auch ngettext die übersetzte Zeichenfolge zurück. Mit einer Funktion wie format lässt sich im Ausgabestring einfach interpolieren:

print(ngettext('ans-age', 'ans-age-plural', age).format(count=age))Code-Sprache: PHP (php)

Nach der Extraktion zeigt sich eine andere Syntax in der Basisübersetzungsdatei (POT):

#: main_babel.py:21
msgid "ans-age"
msgid_plural "ans-age-plural"
msgstr[0] ""
msgstr[1] ""Code-Sprache: PHP (php)

Die Anzahl von msgstr basiert auf der Anzahl der für das Gebietsschema festgelegten Pluralformen. Verschiedene Sprachregionen haben unterschiedliche Anzahlen an Pluralformen: Im Englischen gibt es 2 Pluralformen, im Arabischen 6.

Ein Kommentar am Anfang der Datei zeigt, wie die Pluralform einer Nachricht für eine Sprachregion bestimmt wird. Zum Beispiel:

"Plural-Forms: nplurals=2; plural=(n != 1);\n"Code-Sprache: JSON / JSON mit Kommentaren (json)
# 2 Pluralformen
"Plural-Forms: nplurals=2; plural=(n != 1)\n"

# 3 plural forms
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n"

# 6 plural forms
"Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : "
"n%100>=3 && n%100<=10 ? 3 : n%100>=0 && n%100<=2 ? 4 : 5)\n"Code-Sprache: HTML, XML (xml)

Der pluralisierte Text kann wie folgt erhalten werden:

import gettext

# Initialisierung
lang_en = gettext.translation('messages', localedir='data', languages=['en'])

# aktuelle Sprache auf en setzen
lang_en.install(names=['gettext', 'ngettext'])

print(ngettext('ans-age', 'ans-age-plural', age).format(count=1))
# Man ist 1 Jahr alt

print(ngettext('ans-age', 'ans-age-plural', age).format(count=12))
# Man ist 12 Jahre altCode-Sprache: PHP (php)

Einsatz von gettext in der Anwendung

Anstatt jede Locale einzeln zu laden, können die Übersetzungsdateien dynamisch geladen und in einem dict Objekt wie folgt gespeichert werden:

import gettext

Übersetzungen = {}
supported_langs = ['en', 'de']

# Übersetzungsdateien dynamisch laden
für lang in supported_langs:
    translations[lang] = gettext.translation('messages', localedir='data', languages=[lang])Code-Sprache: PHP (php)

Einfach die gewünschte Locale festlegen und entweder die _ oder ngettext Funktionen aufrufen, um die gewünschten Übersetzungstexte zu bekommen:

import gettext

Übersetzungen = {}
supported_langs = ['en', 'de']

# Übersetzungsdateien dynamisch laden
für lang in supported_langs:
    translations[lang] = gettext.translation('messages', localedir='data', languages=[lang])

# aktive Sprache auf en einstellen
translations['en'].install(names=['gettext', 'ngettext'])

name = 'John Doe'
print(_('ans-name').format(name=name))
# Hallo, John Doe! Willkommen bei Phrase

# aktive Sprache auf de umstellen
translations['de'].install(names=['gettext', 'ngettext'])
print(_('ans-name').format(name=name))
# Hallo, John Doe! Willkommen bei Phrase

age = 30
text = ngettext('ans-age', 'ans-age-plural', age)
print(text)
# Du bist 30 Jahre alt

text = text.format(count=age)
print(text)
# Du bist 30 Jahre alt Code-Sprache: PHP (php)

Die Datei wie gewohnt ausführen, und es sollte die gleiche Ausgabe wie zuvor mit JSON oder YAML erscheinen.

🤿 Mehr erfahren » Schau dir unseren Leitfaden zum Übersetzen von Python-Anwendungen mit dem GNU gettext-Modul an, um mehr über das gettext Modul zu erfahren.

Wie kann eine Python-App Unterstützung für Sprachen von rechts nach links bieten?

Python kommt nicht mit einer sofort einsatzbereiten Implementierung zum Anzeigen von Text für Sprachen von rechts nach links, aber für solche Anwendungsfälle kann das python-bidi Modul genutzt werden. python-bidi ist eine Python-Umsetzung eines bidirektionalen (BiDi) Layouts. Es stellt eine praktische Funktion zur Verfügung, um Sprachen von rechts nach links darzustellen. Mit dem folgenden Befehl wird es installiert:

pip install python-bidi

Mit der get_display Funktion kann ein String bidirektional angezeigt werden:

from bidi.algorithm import get_display

get_display('اَلْعَرَبِيَّةُ')Code-Sprache: JavaScript (javascript)

You should get the following output when you print the result:

اَلْعَرَبِيَّةُ

How do I format localized dates and times in Python?

Python has its own built-in datetime module for manipulating dates and times. It supports arithmetic operations on objects, making it extremely useful for localization and internationalization.

How do I work with date and time duration?

A timedelta object represents the difference between 2 dates or times. You can think of it as a duration. It accepts the following input arguments:

datetime.timedelta(milliseconds=0, microseconds=0, seconds=0, minutes=0, hours=0, days=0, weeks=0)

Ein neues timedelta kann wie folgt erstellt werden:

from datetime import timedelta

delta = timedelta(
    seconds=27,
    minutes=5,
    hours=8,
    50 Tage,
    2 Wochen
)

64 Tage, 8:05:27Code-Sprache: JavaScript (javascript)
from datetime import datetime, timedelta, timezone

# naive datetime-Obj
dt = datetime.now()
# 2022-06-12 15:48:40.014838

# Aware-Datetime-Objekt (GMT+8)
tz = timezone(timedelta(hours=8))
dt = datetime.now(tz=tz)
# 2022-06-12 15:48:40.014838+08:00Code-Sprache: PHP (php)

Ein datetime-Objekt unterstützt die folgenden Methoden zur Konvertierung von Datum und Uhrzeit:

  • strftime: ein datetime-Objekt gemäß einem gegebenen Format in eine Zeichenfolge umwandeln
  • strptime: eine Zeichenfolge gemäß einem entsprechenden Format in ein datetime-Objekt parsen

Die strftime-Funktion akzeptiert eine Zeichenfolge, die das entsprechende Datums- und Uhrzeitformat angibt. Das ist praktisch, wenn der Inhalt einer Anwendung lokalisiert wird.

🤿 Tiefer eintauchen » In der strftime und strptime Dokumentation kann mehr darüber erfahren werden.

Schau dir den folgenden Code-Schnipsel an, der unterschiedliche String-Ausgaben je nach Eingabeformat-String anzeigt.

from datetime import datetime, timezone

dt = datetime(year=2022, month=6, day=12, hour=16, minute=32, second=45, tzinfo=timezone.utc)

dt.strftime('%Y-%m-%d %H:%M:%S')
# 2022-06-12 16:32:45

dt.strftime('%b %d, %Y')
# Jun 12, 2022

dt.strftime('%A (%I.%M %p)')
# Sonntag (16:32 Uhr)

dt.strftime('%c')
# So 12. Juni 16:32:45 2022Code-Sprache: PHP (php)

Das Format ist abhängig von der aktuellen Spracheinstellung der Anwendung. Mit dem eingebauten locale Modul lässt sich das ändern.

import locale

# aktuelle Spracheinstellung abrufen
locale.getlocale()
# ('English_Singapur', '1252')

locale.setlocale(locale.LC_ALL, 'de_DE')
# ('de_DE', 'ISO8859-1')Code-Sprache: PHP (php)
from datetime import datetime, timezone
import locale

locale.setlocale(locale.LC_ALL, 'de_DE')

dt = datetime(year=2022, month=6, day=12, hour=16, minute=32, second=45, tzinfo=timezone.utc)

dt.strftime('%Y-%m-%d %H:%M:%S')
# 2022-06-12 16:32:45

dt.strftime('%b %d, %Y')
# Jun 12, 2022

dt.strftime('%A (%I.%M %p)')
# Sonntag (04.32 )

dt.strftime('%c')
# 12.06.2022 16:32:45Code-Sprache: PHP (php)

Auf der anderen Seite nimmt die strptime-Funktion 2 Eingabeargumente entgegen:

  • date_string: eine Zeichenfolgen-Darstellung eines Datums, basierend auf dem 1989 C Standard.
  • format: das Format, um die Eingabe date_string zu parsen

🗒 Hinweis » Beim Einlesen der Eingabe berücksichtigt die strptime-Funktion die aktuelle Locale.

from datetime import datetime

text = '2022-06-12 16:32:45'
format = '%Y-%m-%d %H:%M:%S'

datetime.strptime(text, format)
# 2022-06-12 16:32:45Code-Sprache: PHP (php)

Wie lokalisierte Datumsangaben mit Babel formatiert werden, wird hier erklärt.

Alternativ kann das babel.dates Modul zur Formatierung von Datum und Uhrzeit genutzt werden.

Eine Anmerkung zu Babel

Das babel Modul bietet eine Reihe von Hilfsmitteln für l10n und i18n in Python. Es wird aktiv gepflegt und bietet folgende Funktionen:

  • Datums- und Zeitformatierung
  • Zahlenformatierung
  • Währungsformatierung
  • Nachrichtenkataloge (Übersetzungsdateien) erstellen

🤿 Mehr erfahren » Um mehr über das babel Modul zu erfahren, Babels i18n-Vorteile für mehrsprachige Apps anschauen.

OK, zurück zur Datumsformatierung. babel.dates kommt mit den folgenden Funktionen:

  • format_time
  • format_date
  • format_datetime

Hier ist ein Code-Schnipsel, der zeigt, wie die Formatierungsfunktionen von Babel genutzt werden:

from datetime import datetime, timezone
from babel.dates import format_time, format_date, format_datetime

dt = datetime(year=2022, month=6, day=12, hour=16, minute=32, second=45, tzinfo=timezone.utc)

# Nutzung des Standard-Medium-Formats
format_time(dt, locale='en_US')
# 16:32:45 Uhr

format_date(dt, locale='en_US')
# 12. Juni 2022

format_datetime(dt, locale='de_DE')
# 12. Juni 2022, 16:32:45

# Verwendung des vollständigen Formats
format_time(dt, format='full', locale='de_DE')
# 16:32:45 Uhr Koordinierte Weltzeit

format_date(dt, format='full', locale='de_DE')
# Sonntag, 12. Juni 2022

format_datetime(dt, format='full', locale='de_DE')
# Sonntag, 12. Juni 2022 um 16:32:45 Uhr Koordinierte Weltzeit

# Verwendung des deutschen Gebietsschemas
format_time(dt, format='full', locale='de_DE')
# 16:32:45 Koordinierte Weltzeit

format_date(dt, format='full', locale='de_DE')
# Sonntag, der 12. Juni 2022

format_datetime(dt, format='full', locale='de_DE')
# Sonntag, 12. Juni 2022 um 16:32:45 Koordinierte WeltzeitCode-Sprache: PHP (php)

Das format-Argument ist optional und kann eine der folgenden Optionen sein:

  • Kurz
  • Mittel (der Standardwert)
  • Lang
  • Komplett

🗒 Hinweis » Das endgültige Ergebnis hängt vom Eingabeargument locale ab.

🤿 Tiefer eintauchen » In der Dokumentation zu den babel.dates Feldern gibt es mehr Informationen über individuelle Muster.

Wie kann Babel zur Arbeit mit Zeitzonen verwendet werden?

Mit der get_timezone Funktion lässt sich ein neues Zeitzonenobjekt erstellen, das auf Zeitzonennamen wie US/Eastern oder Europe/Berlin basiert. Dann wird das Objekt als Eingabe für das tzinfo-Argument übergeben:

from datetime import datetime, timezone
from babel.dates import get_timezone, format_datetime

dt = datetime(year=2022, month=6, day=12, hour=16, minute=32, second=45, tzinfo=timezone.utc)

eastern = get_timezone('US/Eastern')
berlin = get_timezone('Europe/Berlin')

# Verwendung der Eastern Time Zone
format_datetime(dt, format='full', locale='en_US', tzinfo=eastern)
# Sonntag, 12. Juni 2022 um 12:32:45 PM Eastern Daylight Time

# Verwendung der Berliner Zeitzone
format_datetime(dt, format=format, locale='en_US', tzinfo=berlin)
# Sonntag, 12. Juni 2022 um 18:32:45 Uhr Mitteleuropäische SommerzeitCode-Sprache: PHP (php)

Lokalisierte Zahlen in Python formatieren – so geht’s.

Die Zahlenformatierung kann bei der Internationalisierung knifflig sein. Zum Beispiel hat der Text 12,345 unterschiedliche Bedeutungen für Englisch US (en_US) und Deutsch (de_DE). Das liegt hauptsächlich daran, dass in verschiedenen Sprachen unterschiedliche Symbole für Dezimalpunkte und Tausendertrennzeichen verwendet werden.

🤿 Tiefer eintauchen » Unser prägnanter Leitfaden zur Zahlenlokalisierung deckt Gruppierungen, Trennzeichen, Zahlensysteme und mehr ab.

Das eingebaute Locale-Modul verwenden, um Zahlen zu formatieren – so geht’s.

Für die Umwandlung von Zeichenfolgen in Ganzzahlen oder Fließkommazahlen ist das Modul locale eine gute Option. Es verfügt über die folgende eingebaute Funktion:

  • atoi: Umwandlung einer Zeichenfolge in eine Ganzzahl unter Verwendung der aktuellen lokalen Zahlenkonventionen
  • atof: Ein String wird nach den aktuellen lokalen numerischen Konventionen in eine Fließkommazahl umgewandelt

Wenn 12,345 der Eingabestring ist, sehen die Ergebnisse von atof für beide Sprachen so aus:

import locale

# English
locale.setlocale(locale.LC_ALL, 'en_US')
locale.atof('12,345')
# 12345.0

# Deutsch
locale.setlocale(locale.LC_ALL, 'de_DE')
locale.atof('12,345')
# 12.345

# English
locale.setlocale(locale.LC_ALL, 'en_US')
locale.atof('12.345')
# 12.345

# Deutsch
locale.setlocale(locale.LC_ALL, 'de_DE')
locale.atof('12.345')
# 12345.0Code-Sprache: PHP (php)

Andererseits kann die format_string Funktion genutzt werden, um eine Zahl in eine lokalisierte Zeichenfolge zu konvertieren. Folgendes Eingabe-Argument wird akzeptiert:

  • format: eine Zeichenfolge, die die Format-Spezifikation darstellt
  • val: eine Zahl
  • Gruppierung: ob die Gruppierung berücksichtigt wird. Gruppierung bezieht sich auf eine Zahlenfolge, die angibt, an welchen relativen Positionen das Tausendertrennzeichen erwartet wird. Standardmäßig ist False eingestellt.

Schau dir den folgenden Code-Ausschnitt an:

import locale

locale.setlocale(locale.LC_ALL, 'en_US')
locale.format_string('%10.2f', 123456.78)
# 123456.78

locale.format_string('%10.2f', 123456.78, grouping=True)
# 123.456,78

locale.setlocale(locale.LC_ALL, 'de_DE')
locale.format_string('%10.2f', 123456.78, grouping=True)
123.456,78

locale.format_string('%10.2f', 123456.78)
123.456,78Code-Sprache: PHP (php)

Lokalisierte Zahlen mit Babel formatieren – so geht’s

Alternativ lassen sich die folgenden lokalspezifischen Formatierungsfunktionen aus dem babel.numbers Modul nutzen:

  • format_decimal: formatiert eine gegebene Zahl basierend auf dem Eingabe-Locale
  • format_percent: formatiert eine gegebene Zahl prozentual basierend auf dem Eingabe-Locale
  • format_scientific: formatiert eine gegebene Zahl in wissenschaftlicher Notation basierend auf dem Eingabe-Locale. Es wird E als Notation für die Zehnerpotenz verwendet

Der folgende Code-Schnipsel veranschaulicht die Ausgabe für format_decimal bei Verwendung verschiedener Gebietsschemas:

from babel.numbers import format_decimal, format_percent, format_scientific

format_decimal(12345, locale='en_US')
# 12.345
format_decimal(12345.67, locale='en_US')
12.345,67
format_decimal(12345, locale='de_DE')
# 12.345
format_decimal(12345.67, locale='de_DE')
12.345,67

format_percent(0.34, locale='en_US')
# 34%
format_percent(0.34, locale='de_DE')
# 34 %

format_scientific(1234567, locale='en_US')
# 1.234567E6
format_scientific(1234567, locale='de_DE')
# 1.234567E6Code-Sprache: PHP (php)
import locale
from babel.numbers import format_currency

locale.setlocale(locale.LC_ALL, 'en_US')
locale.currency(1234.56)
# $1234.56
locale.currency(1234.56, international=True)
# USD1234.56

format_currency(1234.56, 'USD', locale='en_US')
# $1,234.56
# die Währung auf EURO setzen
format_currency(1234.56, 'EUR', locale='en_US')
# €1.234,56
# die Währung auf Japan YEN setzen
format_currency(1234.56, 'JPY', locale='en_US')
# JPY1.234,56



locale.setlocale(locale.LC_ALL, 'de_DE')
locale.currency(1234.56)
# 1234,56 €
locale.currency(1234.56, international=True)
# 1234,56 EUR

format_currency(1234.56, 'USD', locale='de_DE')
1.234,56 $
format_currency(1234.56, 'EUR', locale='de_DE')
1.234,56 €
format_currency(1234.56, 'JPY', locale='de_DE')
1.234,56 JPYCode-Sprache: PHP (php)