SEO//14 Min. Lesezeit

Python i18n: Von gettext zu modernen Übersetzungs-Workflows

Eray Gündoğmuş
Teilen

Python i18n: Von gettext zu modernen Übersetzungs-Workflows

Python verfügt über eines der reichhaltigsten i18n-Ökosysteme aller Programmiersprachen, das durch jahrzehntelange Investitionen aus großen mehrsprachigen Anwendungen – insbesondere Djangos weltweit eingesetztem Web-Framework und der weit verbreiteten Nutzung von Python in wissenschaftlichen Berechnungen und Datenwerkzeugen, die internationale Gemeinschaften bedienen müssen – geprägt wurde.

Dieser Leitfaden deckt die gesamte Python-i18n-Landschaft ab: vom klassischen gettext-Ansatz der Standardbibliothek über Babels umfassende Locale-Datenbibliothek bis hin zu framework-spezifischen Lösungen für Django und Flask sowie modernen Ansätzen mit Fluent.

Python i18n-Optionen im Überblick

Bibliothek / AnsatzAm besten geeignet für
gettext (stdlib)Einfache Skripte und Anwendungen
BabelZahlen-, Datums-, Währungsformatierung; Verwaltung von PO-Dateien
Django i18nDjango-Webanwendungen
Flask-BabelFlask-Webanwendungen
fluent.runtimeAnwendungen, die Mozillas Fluent-Format verwenden
Babel + gettextDie meisten produktiven Python-Webdienste

gettext: Pythons eingebautes i18n

Pythons Standardbibliothek enthält gettext, das die GNU-gettext-Internationalisierungs-API implementiert. Es ist das Fundament, auf dem viele übergeordnete Bibliotheken aufbauen.

Wie gettext funktioniert

gettext verwendet .po-Dateien (Portable Object) zur Übersetzungsspeicherung und kompiliert diese in binäre .mo-Dateien (Machine Object) für das Laden zur Laufzeit.

Ablauf:

  1. Zeichenketten im Quellcode mit _() oder gettext() markieren
  2. Markierte Zeichenketten mit xgettext oder pybabel in eine .pot-Vorlage extrahieren
  3. Locale-spezifische .po-Dateien aus der Vorlage erstellen
  4. Übersetzer füllen die Übersetzungen aus
  5. .po-Dateien zu .mo-Dateien kompilieren
  6. Zur Laufzeit basierend auf der Benutzer-Locale laden

Grundlegende gettext-Verwendung

import gettext
import locale

def setup_i18n(lang: str) -> gettext.GNUTranslations:
    """Load translations for the given language."""
    translation = gettext.translation(
        domain='messages',
        localedir='locales',
        languages=[lang],
        fallback=True  # Fall back to msgid (source string) if not found
    )
    return translation

# Setup for the application
trans = setup_i18n('fr')
_ = trans.gettext
ngettext = trans.ngettext  # Plural-aware translation

# Usage
print(_("Hello, world!"))
print(_("Welcome, %(name)s!") % {"name": "Alice"})

# Plural forms
count = 3
print(ngettext(
    "%(count)d item",    # singular form
    "%(count)d items",   # plural form
    count                # the number that determines form
) % {"count": count})

Zeichenketten zur Extraktion markieren

# Direct marking with _() 
title = _("Dashboard")
error = _("An error occurred: %(message)s") % {"message": str(e)}

# For strings that need to be defined before i18n is set up,
# use a deferred translation pattern:
def _(s):
    return s  # No-op at definition time

# These strings are extracted but not translated until runtime
ERROR_MESSAGES = {
    "not_found": _("Resource not found"),
    "unauthorized": _("You are not authorized to perform this action"),
}

# At runtime, translate with the actual _ function:
def get_error_message(key: str, translation_func) -> str:
    return translation_func(ERROR_MESSAGES[key])

Babel: Die umfassende Python-i18n-Bibliothek

Babel erweitert gettext um vollständige CLDR-basierte Locale-Daten für Zahlen, Währungen, Datumsangaben und mehr. Es ist die umfassendste Python-i18n-Bibliothek.

pip install Babel

Zahlen- und Währungsformatierung

from babel.numbers import format_number, format_currency, format_percent
from babel import Locale

# Locale objects
en_us = Locale('en', 'US')
de_de = Locale('de', 'DE')
ja_jp = Locale('ja', 'JP')

# Number formatting
amount = 1234567.89

print(format_number(amount, locale='en_US'))  # 1,234,567.89
print(format_number(amount, locale='de_DE'))  # 1.234.567,89
print(format_number(amount, locale='en_IN'))  # 12,34,567.89

# Currency formatting
print(format_currency(1234.56, 'USD', locale='en_US'))  # $1,234.56
print(format_currency(1234.56, 'EUR', locale='de_DE'))  # 1.234,56 €
print(format_currency(1234.56, 'JPY', locale='ja_JP'))  # ¥1,235 (no decimals)

# Percentage
print(format_percent(0.8527, locale='en_US'))  # 85%
print(format_percent(0.8527, '#.##%', locale='de_DE'))  # 85,27%

Datums- und Zeitformatierung

from babel.dates import format_date, format_datetime, format_time, get_timezone
from datetime import datetime, date

dt = datetime(2024, 3, 15, 14, 30, 0)
d = date(2024, 3, 15)

# Date formats
print(format_date(d, locale='en_US'))            # Mar 15, 2024
print(format_date(d, locale='de_DE'))            # 15.03.2024
print(format_date(d, locale='ja_JP'))            # 2024/03/15
print(format_date(d, format='full', locale='fr_FR'))  # vendredi 15 mars 2024

# Datetime with timezone
tz = get_timezone('Europe/Berlin')
print(format_datetime(dt, locale='de_DE', tzinfo=tz))

# Relative time (e.g., "3 hours ago")
from babel.dates import format_timedelta
from datetime import timedelta

delta = timedelta(hours=-3)
print(format_timedelta(delta, locale='en_US', add_direction=True))  # 3 hours ago
print(format_timedelta(delta, locale='fr_FR', add_direction=True))  # il y a 3 heures

PO-Dateien mit Babel verwalten

Babel stellt pybabel bereit, ein Kommandozeilenwerkzeug zur Verwaltung von Übersetzungsdateien:

# 1. Extract translatable strings from Python source
pybabel extract -F babel.cfg -o messages.pot .

# babel.cfg configures which files to extract from:
# [python: **.py]
# [jinja2: **/templates/**.html]

# 2. Initialize a new language (creates locales/fr/LC_MESSAGES/messages.po)
pybabel init -i messages.pot -d locales -l fr

# 3. After making source changes, update existing .po files
pybabel update -i messages.pot -d locales

# 4. Compile .po files to .mo files for runtime use
pybabel compile -d locales

Pluralformen in Babel/gettext

# In .po file (French):
# msgid "%(count)d item"
# msgid_plural "%(count)d items"
# msgstr[0] "%(count)d élément"
# msgstr[1] "%(count)d éléments"

# In Python:
from babel.support import Translations

translations = Translations.load('locales', ['fr'])
ngettext = translations.ngettext

for count in [0, 1, 2, 10]:
    msg = ngettext("%(count)d item", "%(count)d items", count) % {"count": count}
    print(msg)

Django i18n

Django verfügt über umfassende eingebaute i18n-Unterstützung, die tief in Templates, Modelle und das ORM integriert ist.

Django i18n-Einrichtung

# settings.py
LANGUAGE_CODE = 'en-us'

LANGUAGES = [
    ('en', 'English'),
    ('fr', 'Français'),
    ('de', 'Deutsch'),
    ('ja', '日本語'),
    ('ar', 'العربية'),
]

USE_I18N = True
USE_L10N = True  # Locale-aware number/date formatting
USE_TZ = True

LOCALE_PATHS = [BASE_DIR / 'locale']

MIDDLEWARE = [
    'django.middleware.locale.LocaleMiddleware',  # Sets language from request
    # ... other middleware
]

TEMPLATES = [{
    'OPTIONS': {
        'context_processors': [
            'django.template.context_processors.i18n',
            # ...
        ],
    },
}]

Django-Übersetzung in Python-Code

from django.utils.translation import gettext as _, ngettext, gettext_lazy as _lazy

# Eager translation (evaluated immediately)
message = _("Welcome!")

# Lazy translation (evaluated when string is accessed - for model fields and class attrs)
class Article(models.Model):
    title = models.CharField(max_length=200)
    
    class Meta:
        verbose_name = _lazy("article")
        verbose_name_plural = _lazy("articles")

# Plural forms
def item_message(count: int) -> str:
    return ngettext(
        "You have %(count)d item",
        "You have %(count)d items",
        count
    ) % {"count": count}

# String formatting - use named parameters for translatability
def welcome(name: str) -> str:
    return _("Welcome, %(name)s!") % {"name": name}

# Context-sensitive translation (same string, different meaning)
from django.utils.translation import pgettext
month = pgettext("month name", "May")  # vs "May" as a verb

Django-Template-Übersetzung

{% load i18n %}

{# Simple translation #}
<h1>{% trans "Dashboard" %}</h1>

{# Translation with variables #}
{% blocktrans with name=user.first_name %}
  Welcome, {{ name }}!
{% endblocktrans %}

{# Plural forms in templates #}
{% blocktrans count count=items|length %}
  You have {{ count }} item.
{% plural %}
  You have {{ count }} items.
{% endblocktrans %}

{# Language switcher #}
{% get_available_languages as LANGUAGES %}
<ul>
  {% for lang_code, lang_name in LANGUAGES %}
    <li>
      <a href="/{{ lang_code }}/">{{ lang_name }}</a>
    </li>
  {% endfor %}
</ul>

Django URL i18n-Muster

# urls.py
from django.conf.urls.i18n import i18n_patterns
from django.urls import path, include

urlpatterns = [
    path('i18n/', include('django.conf.urls.i18n')),  # Language switcher endpoint
]

urlpatterns += i18n_patterns(
    # These URLs get a language prefix: /en/about/, /fr/about/
    path('about/', views.about, name='about'),
    path('products/', include('products.urls')),
    prefix_default_language=False,  # /about/ redirects to /en/about/
)

Flask i18n mit Flask-Babel

Flask-Babel bringt Babels Leistungsfähigkeit in Flask-Anwendungen:

pip install Flask-Babel
# app.py
from flask import Flask, g, request
from flask_babel import Babel, _, ngettext, format_currency, format_datetime

app = Flask(__name__)
app.config['BABEL_DEFAULT_LOCALE'] = 'en'
app.config['BABEL_DEFAULT_TIMEZONE'] = 'UTC'
app.config['BABEL_TRANSLATION_DIRECTORIES'] = 'translations'

def get_locale():
    # 1. Check URL parameter
    lang = request.args.get('lang')
    if lang:
        return lang
    
    # 2. Check user preference in session/database
    if hasattr(g, 'current_user') and g.current_user.language:
        return g.current_user.language
    
    # 3. Use Accept-Language header
    return request.accept_languages.best_match(['en', 'fr', 'de', 'ja'])

babel = Babel(app, locale_selector=get_locale)

@app.route('/products/<int:product_id>')
def product_detail(product_id):
    product = Product.query.get_or_404(product_id)
    
    # These use the current locale automatically
    price = format_currency(product.price, 'USD')
    created = format_datetime(product.created_at, format='medium')
    
    return render_template('product.html', 
        product=product,
        price=price,
        created=created,
        title=_("Product: %(name)s") % {"name": product.name}
    )

Moderner Ansatz: Fluent für Python

Mozillas Fluent ist in Python über fluent.runtime verfügbar:

pip install fluent.runtime
from fluent.runtime import FluentBundle, FluentResource

# Load and use Fluent files
def create_bundle(locale: str, ftl_content: str) -> FluentBundle:
    bundle = FluentBundle([locale])
    resource = FluentResource(ftl_content)
    errors = bundle.add_resource(resource)
    if errors:
        raise ValueError(f"FTL errors: {errors}")
    return bundle

# FTL file content
en_ftl = """
welcome = Welcome to our app!
greeting = Hello, { $name }!
items =
    { $count ->
        [0]    No items
        [one]  { $count } item
       *[other] { $count } items
    }
"""

bundle = create_bundle("en-US", en_ftl)

def translate(bundle: FluentBundle, message_id: str, **kwargs) -> str:
    msg = bundle.get_message(message_id)
    if not msg or not msg.value:
        return message_id
    
    value, errors = bundle.format_pattern(msg.value, kwargs)
    return value

print(translate(bundle, "welcome"))
print(translate(bundle, "greeting", name="Alice"))
print(translate(bundle, "items", count=0))
print(translate(bundle, "items", count=1))
print(translate(bundle, "items", count=5))

Integration in CI/CD

Python-i18n-Workflows lassen sich problemlos in kontinuierliche Lokalisierungs-Pipelines integrieren:

# .github/workflows/i18n.yml
name: i18n

on:
  push:
    paths:
      - '**.py'
      - '**/templates/**.html'

jobs:
  extract-and-update:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Install Babel
        run: pip install Babel
      
      - name: Extract strings
        run: pybabel extract -F babel.cfg -o messages.pot .
      
      - name: Update translation files
        run: pybabel update -i messages.pot -d locales
      
      - name: Push to translation platform
        run: |
          # Upload messages.pot to your TMS
          curl -X POST https://api.better-i18n.com/upload \
            -F "file=@messages.pot" \
            -H "Authorization: Bearer ${{ secrets.BETTER_I18N_API_KEY }}"

Umfassende CI/CD-i18n-Muster finden Sie unter i18n CI/CD pipeline automation.

Best Practices für die Locale-Erkennung

from babel import Locale, UnknownLocaleError
from typing import Optional

SUPPORTED_LOCALES = ['en', 'fr', 'de', 'ja', 'ar', 'pt-BR']

def parse_accept_language(header: str) -> list[str]:
    """Parse Accept-Language header into ordered list of language codes."""
    locales = []
    for part in header.split(','):
        parts = part.strip().split(';')
        lang = parts[0].strip()
        # Extract quality (q=0.9) or default to 1.0
        q = 1.0
        for param in parts[1:]:
            if param.strip().startswith('q='):
                try:
                    q = float(param.strip()[2:])
                except ValueError:
                    pass
        locales.append((lang, q))
    
    # Sort by quality, highest first
    locales.sort(key=lambda x: x[1], reverse=True)
    return [lang for lang, _ in locales]

def negotiate_locale(accept_language: str, supported: list[str] = SUPPORTED_LOCALES) -> str:
    """Find the best matching supported locale for the given Accept-Language header."""
    requested = parse_accept_language(accept_language)
    
    for requested_lang in requested:
        # Exact match first
        if requested_lang in supported:
            return requested_lang
        
        # Language-only match (en-US → en)
        base = requested_lang.split('-')[0]
        if base in supported:
            return base
        
        # Find any supported locale with the same base
        for supported_locale in supported:
            if supported_locale.startswith(base + '-'):
                return supported_locale
    
    return 'en'  # Default fallback

Eine ausführlichere Behandlung von Pluralisierungsregeln in verschiedenen Sprachen finden Sie unter pluralization rules across languages. Einen Überblick über das Ökosystem der Übersetzungsmanagementsysteme bietet translation management systems.


Bringen Sie Ihre App mit better-i18n auf den globalen Markt

better-i18n kombiniert KI-gestützte Übersetzungen, git-native Workflows und globale CDN-Auslieferung in einer entwicklerfreundlichen Plattform. Hören Sie auf, Tabellenkalkulationen zu verwalten, und beginnen Sie in jeder Sprache auszuliefern.

Kostenlos starten → · Funktionen entdecken · Dokumentation lesen

Comments

Loading comments...