Skip to main content

Flask i18n: Byg en flersproget app med Flask-Babel

Fra det grundlæggende i gettext til udrulning i produktion: Internationaliser din Flask-app med Flask-Babel, PO-filer og automatiseret AI-oversættelse.

1

Installer Flask-Babel

Flask-Babel er standardudvidelsen til internationalisering i Flask. Den integrerer GNU gettext med Flask- og Jinja2-skabeloner og leverer oversættelsesfunktioner, locale-valg og understøttelse af tidszoner fra starten.

Flask-Babel omslutter Babel (Python-biblioteket til i18n) og integrerer det med Flasks requestlivscyklus. Det giver dig funktionerne gettext(), ngettext() og lazy_gettext() samt automatisk locale-registrering fra browserheaders.
Terminal
pip install Flask-Babel
2

Konfigurer Babel

Opret en babel.cfg-fil for at fortælle pybabel, hvor der skal søges efter strenge, som kan oversættes. Initialiser derefter Flask-Babel med en locale-vælgerfunktion, der bestemmer, hvilket sprog der skal vises ved hver request.

babel.cfg
# babel.cfg — tells pybabel where to find translatable strings
[python: **.py]
[jinja2: **/templates/**.html]
extensions=jinja2.ext.autoescape,jinja2.ext.with_
app.py
from flask import Flask, request
from flask_babel import Babel

app = Flask(__name__)
app.config['BABEL_DEFAULT_LOCALE'] = 'en'
app.config['BABEL_DEFAULT_TIMEZONE'] = 'UTC'
# Directory where translations live (default: "translations")
app.config['BABEL_TRANSLATION_DIRECTORIES'] = 'translations'

def get_locale():
    # 1. Check URL parameter or session
    # 2. Fall back to browser Accept-Language header
    return request.accept_languages.best_match(['en', 'de', 'ja', 'es', 'fr'])

babel = Babel(app, locale_selector=get_locale)
Funktionen locale_selector kaldes ved hver request. Hvis den returnerer en locale uden en kompileret .mo-fil, går Flask-Babel tilbage til standardlocale uden en fejlmeddelelse. Der udløses ingen fejl — strengene vises blot uden oversættelse.
3

Markér strenge til oversættelse

Omgiv alle brugervendte strenge med gettext() i Python-kode og _() i Jinja2-skabeloner. Brug lazy_gettext() til strenge, der defineres, når modulet indlæses (f.eks. formularetiketter og konfiguration) og som først skal oversættes senere ved en request.

app.py
from flask_babel import gettext, ngettext, lazy_gettext

# In views — gettext() for immediate translation
@app.route('/')
def index():
    flash(gettext('Your profile has been updated.'))
    return render_template('index.html',
        title=gettext('Home'))

# In forms/config — lazy_gettext() for deferred translation
class LoginForm(FlaskForm):
    username = StringField(lazy_gettext('Username'))
    password = PasswordField(lazy_gettext('Password'))
    submit = SubmitField(lazy_gettext('Sign In'))
templates/index.html
{# In Jinja2 templates, use _() shorthand for gettext #}
<h1>{{ _('Welcome to our app') }}</h1>

<p>{{ _('Hello, %(name)s!', name=user.name) }}</p>

<footer>
  {{ _('Copyright %(year)s Example Corp.', year=2026) }}
</footer>
Brug _() i Jinja2-skabeloner i stedet for gettext() — det er gettexts almindelige kortform og holder dine skabeloner ryddelige. Flask-Babel registrerer automatisk _() som en global Jinja2-funktion.
4

Udtræk meddelelser

Kør pybabel extract for at gennemgå din kildekode og dine skabeloner for strenge, der kan oversættes. Det opretter en .pot-fil (Portable Object Template). Initialiser derefter kataloger for hvert målsprog eller opdater eksisterende kataloger, når kildestrengene ændres.

Terminal
# Extract translatable strings from source code
pybabel extract -F babel.cfg -k lazy_gettext -o messages.pot .

# Initialize a new language (first time only)
pybabel init -i messages.pot -d translations -l de
pybabel init -i messages.pot -d translations -l ja
pybabel init -i messages.pot -d translations -l es

# Update existing catalogs when source strings change
pybabel update -i messages.pot -d translations
Kør altid pybabel update (ikke init) efter den første udtrækning. Hvis init køres på en eksisterende sprogmappe, overskrives alle eksisterende oversættelser. Kommandoen update fletter nye strenge ind og bevarer eksisterende oversættelser.
5

Oversæt PO-filer

Åbn de genererede .po-filer og udfyld msgstr-værdierne for hver msgid. PO-filer er almindelig tekst — du kan redigere dem direkte, bruge en PO-editor som Poedit eller automatisere oversættelsen med AI-værktøjer.

translations/de/LC_MESSAGES/messages.po
# translations/de/LC_MESSAGES/messages.po
msgid "Welcome to our app"
msgstr "Willkommen in unserer App"

msgid "Hello, %(name)s!"
msgstr "Hallo, %(name)s!"

msgid "Your profile has been updated."
msgstr "Ihr Profil wurde aktualisiert."

msgid "Username"
msgstr "Benutzername"

msgid "Password"
msgstr "Passwort"

msgid "Sign In"
msgstr "Anmelden"
PO-filer indeholder kontekstkommentarer (linjer, der begynder med #:), som viser, hvor hver streng bruges i din kildekode. Oversættere bruger dem til at forstå konteksten. Behold dem — de genereres automatisk af pybabel extract.
6

Kompilér oversættelser

Kompilér dine .po-filer til binære .mo-filer med pybabel compile. Flask-Babel læser .mo-filer under kørsel — den kan ikke læse .po-filer direkte. Du skal kompilere igen efter hver opdatering af oversættelserne.

Terminal
# Compile .po files to binary .mo files (required at runtime)
pybabel compile -d translations

# Flask-Babel reads .mo files, not .po files.
# You MUST compile after every translation update.
Hvis oversættelser ikke vises, efter du har redigeret en .po-fil, har du næsten helt sikkert glemt at køre pybabel compile. Det er det mest almindelige problem med Flask-Babel. Føj kompileringstrinnet til dit udrulningsscript for at undgå det i produktion.
7

Håndter flertalsformer og variabler

Brug ngettext() til strenge, der afhænger af flertal. Funktionen modtager en entalsform, en flertalsform og antallet. Babel bruger automatisk den korrekte flertalsregel for hvert sprog — engelsk har 2 former, mens russisk har 3, arabisk har 6 og japansk har 1.

app.py
from flask_babel import ngettext

@app.route('/cart')
def cart():
    count = len(session.get('cart_items', []))
    message = ngettext(
        '%(num)d item in your cart',     # singular
        '%(num)d items in your cart',    # plural
        count                             # determines which form
    )
    return render_template('cart.html', message=message)
Plural forms in .po files
# English: 2 forms (nplurals=2)
msgid "%(num)d item in your cart"
msgid_plural "%(num)d items in your cart"
msgstr[0] "%(num)d item in your cart"
msgstr[1] "%(num)d items in your cart"

# German: 2 forms (nplurals=2)
msgid "%(num)d item in your cart"
msgid_plural "%(num)d items in your cart"
msgstr[0] "%(num)d Artikel in Ihrem Warenkorb"
msgstr[1] "%(num)d Artikel in Ihrem Warenkorb"

# Japanese: 1 form (nplurals=1)
msgid "%(num)d item in your cart"
msgid_plural "%(num)d items in your cart"
msgstr[0] "カートに%(num)d個の商品があります"

# Russian: 3 forms (nplurals=3)
msgid "%(num)d item in your cart"
msgid_plural "%(num)d items in your cart"
msgstr[0] "%(num)d товар в вашей корзине"
msgstr[1] "%(num)d товара в вашей корзине"
msgstr[2] "%(num)d товаров в вашей корзине"
Brug aldrig if count == 1 til flertalslogik. Sprog som fransk behandler 0 som ental. Russisk og arabisk har former, som engelsk ikke har. Lad ngettext() og Babels CLDR-flertalsregler håndtere valget.
8

Tilføj skift af locale

Byg en sprogvælger, der gemmer brugerens valg i Flask-sessionen. Opdater din locale_selector-funktion, så den først kontrollerer sessionen og derefter går tilbage til browserregistrering.

app.py
from flask import session, redirect, url_for, request
from flask_babel import refresh

@app.route('/set-language/<lang>')
def set_language(lang):
    session['lang'] = lang
    refresh()  # Force Flask-Babel to re-read the locale
    return redirect(request.referrer or url_for('index'))

# Update get_locale to check session first
def get_locale():
    # 1. Explicit user choice (stored in session)
    if 'lang' in session:
        return session['lang']
    # 2. Browser Accept-Language header
    return request.accept_languages.best_match(
        ['en', 'de', 'ja', 'es', 'fr']
    )
templates/components/language_switcher.html
{# Language switcher component #}
<nav class="language-switcher">
  {% for lang, name in [('en','English'),('de','Deutsch'),
                         ('ja','日本語'),('es','Español'),
                         ('fr','Français')] %}
    <a href="{{ url_for('set_language', lang=lang) }}"
       class="{{ 'active' if get_locale() == lang }}">
      {{ name }}
    </a>
  {% endfor %}
</nav>
Når sessionens locale er ændret, skal du kalde flask_babel.refresh() for at tvinge Flask-Babel til at genindlæse locale for den aktuelle request. Uden refresh() bevares den gamle locale indtil næste request.
9

Automatiser oversættelser

Når din Flask-Babel-opsætning er klar, kan du oversætte dine PO-filer med AI. Automatiser cyklussen udtræk-oversæt-kompilér i din CI/CD-pipeline for at holde oversættelserne synkroniseret med kildekoden.

Terminal
# Translate your PO files with AI directly from your IDE
# or use the CLI in CI/CD:
npx i18n-agent translate translations/de/LC_MESSAGES/messages.po \
  --source-lang en --target-lang de

# Bulk translate all languages at once:
npx i18n-agent translate messages.pot --lang de,ja,es,fr

# Then compile:
pybabel compile -d translations
Oversæt trinvist — når du tilføjer nye strenge og kører pybabel update, skal du kun oversætte de nye poster uden oversættelse (tomme msgstr-værdier) i stedet for at generere alt igen. Det bevarer oversættelser, som mennesker har gennemgået.

Ekstra: Intelligent locale-fallback med flask-babel-locale-chain

Som standard går Flask-Babel direkte tilbage til standardlocale, når brugerens foretrukne locale ikke er tilgængelig. En pt-BR-bruger med kun pt-PT-oversættelser ser engelsk i stedet for portugisisk. flask-babel-locale-chain tilføjer konfigurerbare fallbackkæder, så beslægtede locales bruges i en naturlig rækkefølge.

app.py
# pip install flask-babel-locale-chain
from flask_babel_locale_chain import LocaleChain

# Define fallback chains: pt-BR falls back to pt before en
locale_chain = LocaleChain({
    'pt-BR': ['pt-BR', 'pt', 'en'],
    'pt-PT': ['pt-PT', 'pt', 'en'],
    'zh-Hant': ['zh-Hant', 'zh-Hans', 'en'],
    'en-GB': ['en-GB', 'en', 'en-US'],
})

def get_locale():
    requested = request.accept_languages.best_match(
        ['en', 'pt-BR', 'pt', 'zh-Hant', 'zh-Hans']
    )
    # Returns the best available locale from the chain
    return locale_chain.resolve(requested)
flask-babel-locale-chain er en open source-Python-pakke. Se den på GitHub på github.com/i18n-agent/flask-babel-locale-chain.

Automatiser oversættelseskvaliteten

Find manglende nøgler og ødelagte placeholders med i18n-validate, før de udgives. Test din brugergrænseflade med pseudooversættelser ved hjælp af i18n-pseudo, før de rigtige oversættelser er klar.

Almindelige faldgruber

Glemte at kompilere .po til .mo

Flask-Babel læser kompilerede .mo-filer, ikke .po-filer. Hvis oversættelserne ikke vises efter redigering af .po-filer, skal du køre pybabel compile -d translations. Føj dette trin til dit udrulningsscript.

Brug af gettext() på modulniveau

gettext() kræver en requestkontekst. Hvis du definerer oversatte strenge på modulniveau (klasseattributter, konstanter), skal du bruge lazy_gettext() i stedet. Den udskyder oversættelsen, indtil strengen faktisk renderes i en request.

Strenge mangler ved udtrækning

pybabel extract gennemgår kun filer, der matcher mønstrene i babel.cfg. Hvis strenge ikke udtrækkes: Kontrollér, at mønstrene i babel.cfg passer til din filstruktur, føj -k lazy_gettext til for at udtrække lazy-kald og sørg for, at Jinja2-skabeloner bruger den korrekte filendelse (.html, .jinja2).

Kodningsfejl i PO-filer

PO-filer skal være kodet med UTF-8. Hvis du ser UnicodeDecodeError, skal du kontrollere Content-Type-headeren i din .po-fil: Den skal angive charset=UTF-8. Nogle editorer gemmer med andre kodninger — kontrollér altid efter redigering.

Anbefalet filstruktur

Project Structure
my-flask-app/
├── app.py                          # Flask app with Babel config
├── babel.cfg                       # Extraction config
├── messages.pot                    # Template (extracted strings)
├── translations/
│   ├── de/
│   │   └── LC_MESSAGES/
│   │       ├── messages.po         # German translations (editable)
│   │       └── messages.mo         # Compiled binary (generated)
│   ├── ja/
│   │   └── LC_MESSAGES/
│   │       ├── messages.po
│   │       └── messages.mo
│   └── es/
│       └── LC_MESSAGES/
│           ├── messages.po
│           └── messages.mo
├── templates/
│   ├── base.html
│   ├── index.html
│   └── components/
│       └── language_switcher.html
├── requirements.txt
└── venv/

Prøv i18n Agent nu

Slip din oversættelsesfil her

JSON, YAML, PO, XML, CSV, Markdown, Properties

eller klik for at vælge en fil

Målsprog

Kræver ingen tilmeldingEstimat med det samme

Ofte stillede spørgsmål om Flask i18n