Skip to main content

Flask i18n: skapa en flerspråkig app med Flask-Babel

Från grunderna i gettext till driftsättning i produktion: internationalisera Flask-appen med Flask-Babel, PO-filer och automatiserad AI-översättning.

1

Installera Flask-Babel

Flask-Babel är standardtillägget för internationalisering i Flask. Det integrerar GNU gettext med Flask och Jinja2-mallar och ger direkt tillgång till översättningsfunktioner, val av språkvariant och stöd för tidszoner.

Flask-Babel omsluter Babel (Pythons i18n-bibliotek) och integrerar det med Flasks livscykel för begäranden. Det ger dig funktionerna gettext(), ngettext() och lazy_gettext() samt automatisk identifiering av språkvariant från webbläsarrubriker.
Terminal
pip install Flask-Babel
2

Konfigurera Babel

Skapa filen babel.cfg för att ange var pybabel ska söka efter översättningsbara strängar och initiera sedan Flask-Babel med en funktion som väljer vilken språkvariant som ska användas för varje begäran.

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 anropas vid varje begäran. Om den returnerar en språkvariant som saknar en kompilerad .mo-fil går Flask-Babel tillbaka till den förvalda språkvarianten utan felmeddelande. Inget fel utlöses – strängarna visas bara oöversatta.
3

Markera strängar för översättning

Omslut alla användarsynliga strängar med gettext() i Python-kod och _() i Jinja2-mallar. Använd lazy_gettext() för exempelvis formuläretiketter och konfiguration som definieras när modulen läses in och ska översättas senare under en begäran.

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>
Använd _() i Jinja2-mallar i stället för gettext() – det är gettexts vanliga kortform och håller mallarna lättlästa. Flask-Babel registrerar automatiskt _() som en global Jinja2-funktion.
4

Extrahera meddelanden

Kör pybabel extract för att söka igenom källkoden och mallarna efter översättningsbara strängar. Det skapar en .pot-fil (Portable Object Template). Initiera sedan kataloger för varje målspråk eller uppdatera befintliga kataloger när källsträngarna ändras.

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 alltid pybabel update (inte init) efter den första extraheringen. Om init körs för en befintlig språkkatalog skrivs alla befintliga översättningar över. Kommandot update sammanfogar nya strängar och bevarar befintliga översättningar.
5

Översätt PO-filer

Öppna de genererade .po-filerna och fyll i msgstr-värdena för varje msgid. PO-filer är vanliga textfiler – du kan redigera dem direkt, använda en PO-redigerare som Poedit eller automatisera översättningen med AI-verktyg.

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 innehåller kontextkommentarer (rader som börjar med #:) som visar var varje sträng används i källkoden. Översättare använder dem för att förstå sammanhanget. Behåll dem – de genereras automatiskt av pybabel extract.
6

Kompilera översättningar

Kompilera .po-filerna till binära .mo-filer med pybabel compile. Flask-Babel läser .mo-filer under körning och kan inte läsa .po-filer direkt. Du måste kompilera om efter varje översättningsuppdatering.

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.
Om översättningarna inte visas efter att en .po-fil har redigerats har du nästan säkert glömt att köra pybabel compile. Det är det vanligaste problemet med Flask-Babel. Lägg till kompileringssteget i driftsättningsskriptet för att undvika det i produktion.
7

Hantera pluralformer och variabler

Använd ngettext() för strängar som påverkas av pluralformer. Funktionen tar en singularform, en pluralform och antalet. Babel använder automatiskt rätt pluralregel för varje språk – engelska har 2 former, medan ryska har 3, arabiska 6 och japanska 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 товаров в вашей корзине"
Använd aldrig if count == 1 för plurallogik. Språk som franska behandlar 0 som singular. Ryska och arabiska har former som saknas i engelskan. Låt ngettext() och Babels CLDR-pluralregler sköta valet.
8

Lägg till språkbyte

Skapa en språkväljare som lagrar användarens val i Flask-sessionen. Uppdatera funktionen locale_selector så att den först kontrollerar sessionen och därefter går vidare till webbläsaridentifiering.

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 du har ändrat sessionens språkvariant anropar du flask_babel.refresh() för att tvinga Flask-Babel att läsa in språkvarianten på nytt för den aktuella begäran. Utan refresh() ligger den gamla språkvarianten kvar till nästa begäran.
9

Automatisera översättningar

När Flask-Babel-konfigurationen är klar kan du översätta PO-filerna med AI. Automatisera cykeln extrahera–översätt–kompilera i CI/CD-pipelinen så att översättningarna hålls synkroniserade med källkoden.

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
Översätt stegvis – när du lägger till nya strängar och kör pybabel update översätter du bara de nya oöversatta posterna (tomma msgstr-värden) i stället för att generera om allt. Då bevaras översättningar som har granskats av människor.

Bonus: smarta reservspråk med flask-babel-locale-chain

Som standard går Flask-Babel direkt till den förvalda språkvarianten när användarens föredragna variant inte är tillgänglig. En användare med pt-BR och enbart pt-PT-översättningar ser engelska i stället för portugisiska. flask-babel-locale-chain lägger till konfigurerbara reservkedjor så att närliggande språkvarianter används i en naturlig ordning.

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 är ett Python-paket med öppen källkod. Se det på GitHub på github.com/i18n-agent/flask-babel-locale-chain.

Automatisera kvalitetskontrollen av översättningar

Upptäck saknade nycklar och trasiga platshållare före lansering med i18n-validate. Testa gränssnittet med pseudoöversättningar via i18n-pseudo innan de riktiga översättningarna är klara.

Vanliga fallgropar

.po har inte kompilerats till .mo

Flask-Babel läser kompilerade .mo-filer, inte .po-filer. Om översättningarna inte visas efter att .po-filer har redigerats kör du pybabel compile -d translations. Lägg till detta steg i driftsättningsskriptet.

gettext() används på modulnivå

gettext() kräver en begärandekontext. Om du definierar översatta strängar på modulnivå, till exempel klassattribut eller konstanter, ska du använda lazy_gettext() i stället. Funktionen väntar med översättningen tills strängen faktiskt renderas i en begäran.

Strängar missas vid extraheringen

pybabel extract söker endast igenom filer som matchar mönstren i babel.cfg. Om strängar inte extraheras: kontrollera att mönstren i babel.cfg motsvarar filstrukturen, lägg till -k lazy_gettext för att extrahera lata anrop och se till att Jinja2-mallarna använder rätt filändelse (.html, .jinja2).

Kodningsfel i PO-filer

PO-filer måste vara kodade med UTF-8. Om du ser UnicodeDecodeError kontrollerar du Content-Type-rubriken i .po-filen: där ska charset=UTF-8 anges. Vissa redigerare sparar med andra kodningar – kontrollera alltid kodningen efter redigering.

Rekommenderad 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/

Prova i18n Agent nu

Släpp din översättningsfil här

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

eller klicka för att välja en fil

Målspråk

Ingen registrering krävsPrisuppskattning direkt

Vanliga frågor om Flask i18n