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

Hier besprechen wir, was nötig ist, um eine Python-App am besten für die Lokalisierung vorzubereiten.

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:

  • Sie lassen sich einfacher bearbeiten und ü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

JSON bietet eine der einfachsten Methoden, Übersetzungsdateien zu speichern. 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": {
        "one": "Du bist $count Jahr alt",
        "other": "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)

Danach lädt man die Datei wie folgt mit dem integrierten json Paket hoch:

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

Statt JSON-Dateien lassen sich auch YAML-Dateien verwenden. Dafür wird das pyyaml-Paket benötigt, um YAML zu unterstützen.

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

title: Interaktives Terminal
ques-name: "Name eingeben:"
ques-age: "Alter eingeben:"
ans-name: Hallo $name! Willkommen bei Phrase
ans-age:
  one: Du bist $count Jahr alt
  other: Du bist $count Jahre alt
ques-dob: "Bitte Geburtsdatum eingeben (JJJJ-MM-TT):",
ans-dob: Geboren am: $dobCode-Sprache: PHP (php)

Entsprechend 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
ans-age:
  one: Du bist $count Jahr alt
  other: Du bis $count Jahre alt
ques-dob: "Bitte Geburtsdatum eingeben (JJJJ-MM-TT):",
ans-dob: Geboren am: $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 noch zwei weitere Funktionen:

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

Man sollte darauf aachten, 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
        if file_format in supported_format:
            # Liste der Dateien mit spezifischen Erweiterungen abrufen
            files = glob.glob(os.path.join(translations_folder, f'*.{file_format}'))
            for fil in files:
                # Den Namen der Datei ohne Erweiterung abrufen, 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):
        if loc in self.data:
            self.locale = loc
        else:
            print('Invalid locale')

    def get_locale(self):
        return self.locale

    def translate(self, key):
        # Den Key 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
        if file_format in supported_format:
            # Liste der Dateien mit spezifischen Erweiterungen abrufen
            Dateien = glob.glob(os.path.join(Ordner, f'*.{file_format}'))
            for fil in files:
                # Den Namen der Datei ohne Erweiterung abrufen, 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)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
        if self.locale not in self.data:
            return key

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

        return textCode-Sprache: PHP (php)

Das Modul enthält außerdem zwei 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)

Interpolation zur Translator-Klasse hinzufügen

Für die Interpolation von Zeichenfolfen 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):
    # Den Key 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)
    
    # String-Interpolation
    return Template(text).safe_substitute(**kwargs)Code-Sprache: PHP (php)

Pluralisierung zur Translator-Klasse hinzufügen

Die Klasse babel.plural.PluralRule kann für die Unterstützung der Pluralisierung importiert werden:

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

Unter der __init__-Funktion lässt sich 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():
    <code></code><//code>

    def set_locale(self, loc):
        if loc in self.data:
            self.locale = loc
        else:
            print('Invalid 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_ruleCode-Sprache: HTML, XML (xml)

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

class Translator():
    <code></code><//code>

    def translate(self, key, **kwargs):
        # Den Key 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)

        # Typ dict steht für Schlüssel in Pluralform
        if type(text) == dict:
            count = kwargs.get('count', 1)
        # Anzahl in einen Integer umwandeln
        try:
            count = int(count)
        except Exception:
            print('Invalid count')
            return key

        text = text.get(self.plural_rule(count), key)
        return Template(text).safe_substitute(**kwargs)Code-Sprache: HTML, XML (xml)

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
        if file_format in supported_format:
            # Liste der Dateien mit spezifischen Erweiterungen abrufen
            files = glob.glob(os.path.join(translations_folder, f'*.{file_format}'))
            for fil in files:
                # Den Namen der Datei ohne Erweiterung abrufen, 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):
        if loc in self.data:
            self.locale = loc
        else:
            print('Invalid 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 Key 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)
        # Typ dict steht für Schlüssel in Pluralform
        if type(text) == dict:
            count = kwargs.get('count', 1)
            # Anzahl in einen Integer umwandeln
            try:
                count = int(count)
            except Exception:
                print('Invalid count')
                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 Klasser Translator als Modul in der Anwendung genutzt werden. Der folgende Code dient als Referenz für die grundlegende Nutzung des individuellen Translator-Moduls:

# Modul importieren
import i18n

# Eine neue Translator-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

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

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

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

Die Funktionalität des i18n Moduls lässt sich problemlos erweitern, um je nach Bedarf weitere Funktionen 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. Dabei lassen sich übersetzbare Strings einfach mit der Funktion _() umschließen:

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

# Als übersetzbar markierte Strings
print(_('Interactive 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: PROJECT VERSION\n"
    "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
    "POT-Creation-Date: 2022-06-17 23:04+0800\n"
    "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
    "Last-Translator: FULL NAME &lt;EMAIL@ADDRESS&gt;\n"
    "Language-Team: LANGUAGE &lt;LL@li.org&gt;\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)

Dies bildet die Grundübersetzungsdatei. msgid ist der einzigartige Identifier 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 Locale 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 sein wie der der Basis-POT-Datei. 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 "Interactive 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] "Du bist {count} Jahre alt"Code-Sprache: PHP (php)

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, können mit der Funktion gettext.translation 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 abzurufen:

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)

Pluralisierung mit ngettext hinzufügen

Für die Unterstützung einer Pluralisierung lässt sich die Funktion ngettext nutzen. Dabei werden drei Eingabeargumente 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. So gibt es im Englischen zwei, im Arabischen sechs Pluralformen.

Ein Kommentar am Anfang der Datei zeigt, wie die Pluralform einer Nachricht für eine Locale bestimmt wird. Ein 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 Pluralformen
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10&gt;=2 &amp;&amp; "
"n%10&lt;=4 &amp;&amp; (n%100&lt;10 || n%100&gt;=20) ? 1 : 2)\n"

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

Der pluralisierte Text kann wie folgt abgerufen 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))
# Du bist 1 Jahr alt

print(ngettext('ans-age', 'ans-age-plural', age).format(count=12))
# Du bist 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

translations = {}
supported_langs = ['en', 'de']

# Übersetzungsdateien dynamisch laden
for 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 Funktionen _ oder ngettext aufrufen, um die gewünschten Übersetzungstexte abzurufen:

import gettext

translations = {}
supported_langs = ['en', 'de']

# Übersetzungsdateien dynamisch laden
for 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

# Die aktive Locale auf de ändern
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)

Dann einfach 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 wird nicht mit einer sofort einsatzbereiten Implementierung zum Anzeigen von Text für Sprachen von rechts nach links geliefert. 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. Das Modul wird mit dem folgenden Befehl installiert:

pip install python-bidi

Mit der Funktion get_display 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 lässt sich wie folgt erstellen:

from datetime import timedelta

delta = timedelta(
    seconds=27,
    minutes=5,
    hours=8,
    days=50,
    weeks=2
)

# 64 Tage, 8:05:27Code-Sprache: PHP (php)
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.

🤿 Mehr erfahren » In der strftime und strptime-Dokumentation finden sich genauere Informationen.

Der folgende Code zeigt unterschiedliche String-Ausgaben je nach Eingabeformat-String an:

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')
# 12. Jun 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')
# 12. Jun 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 akzeptiert die strptime-Funktion zwei Eingabeargumente:

  • 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)

Hier wird erklärt, wie sich lokalisierte Datumsangaben mit Babel formatieren lassen.

Alternativ kann das Modul babel.dates 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 » Mehr Informationenüber das babel-Modul findest du unter Babels i18n-Vorteile für mehrsprachige Apps.

Okay, zurück zur Datumsformatierung. babel.dates bietet die folgenden Funktionen:

  • format_time
  • format_date
  • format_datetime

Dieser Code zeigt, wie sich die Formatierungsfunktionen von Babel nutzen lassen

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. Jun 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 der deutschen Locale
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, der 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.

🤿 Mehr erfahren » In der Dokumentation zu den babel.dates-Feldern gibt es mehr Informationen über individuelle Muster.

Wie lässt sich Babel für den Umgang mit Zeitzonen nutzen?

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 amerikanisches Englisch (en_US) und Deutsch (de_DE). Das liegt hauptsächlich daran, dass in verschiedenen Sprachen unterschiedliche Symbole für Dezimalpunkte und Tausendertrennzeichen verwendet werden.

🤿 Mehr erfahren » Unser umfassender Leitfaden zur Zahlenlokalisierung deckt Gruppierungen, Trennzeichen, Zahlensysteme und mehr ab.

Das Locale-Modul zur Zahlenformatierung verwenden – 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

# Englisch
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

# Englisch
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 Funktion format_string 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)
123.456,78

locale.format_string('%10.2f', 123456.78, grouping=True)
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. E wird als Notation für die Zehnerpotenz verwendet

Der folgende Code 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)