backend intermediate

Localization with Flask-Babel

min read Frederick Tubiermont

Localization with Flask-Babel

Flask-Babel makes it straightforward to translate your app into multiple languages using the industry-standard GNU gettext system.

Installation

pip install Flask-Babel

Add to requirements.txt:

Flask-Babel==4.0.0

Setup

# app.py
from flask import Flask, request, session
from flask_babel import Babel

app = Flask(__name__)
app.config["BABEL_DEFAULT_LOCALE"] = "en"
app.config["BABEL_SUPPORTED_LOCALES"] = ["en", "fr", "es"]

def get_locale():
    """Determine locale from session, then browser preference."""
    if "lang" in session:
        return session["lang"]
    # Auto-detect from browser Accept-Language header
    return request.accept_languages.best_match(["en", "fr", "es"])

babel = Babel(app, locale_selector=get_locale)

Marking Strings for Translation

In Python files, wrap strings with _():

from flask_babel import _

@app.route("/")
def home():
    greeting = _("Welcome to Flask Vibe")
    return render_template("home.html", greeting=greeting)

In Jinja2 templates, use {{ _("...") }}:

<!-- templates/home.html -->
<h1>{{ _("Welcome to Flask Vibe") }}</h1>
<p>{{ _("Build apps you can understand.") }}</p>
<button>{{ _("Get Started") }}</button>

For strings with variables:

# Python
message = _("Hello, %(name)s!", name=user["name"])
<!-- Template -->
<p>{{ _("%(count)s tutorials available", count=tutorial_count) }}</p>

Configuration File

Create babel.cfg in your project root:

[python: **.py]
encoding = utf-8

[jinja2: **/templates/**.html]
encoding = utf-8

Extracting Strings

Run this to scan your code and templates for translatable strings:

pybabel extract -F babel.cfg -o messages.pot .

This creates messages.pot — a template with all strings found.

Creating Translation Files

For each language you support:

# First time — creates the directory structure
pybabel init -i messages.pot -d translations -l fr
pybabel init -i messages.pot -d translations -l es

This creates:

translations/
├── fr/
│   └── LC_MESSAGES/
│       └── messages.po
└── es/
    └── LC_MESSAGES/
        └── messages.po

Editing Translations

Open translations/fr/LC_MESSAGES/messages.po and fill in translations:

#: templates/home.html:3
msgid "Welcome to Flask Vibe"
msgstr "Bienvenue sur Flask Vibe"

#: templates/home.html:4
msgid "Build apps you can understand."
msgstr "Construisez des applications que vous comprenez."

#: templates/home.html:5
msgid "Get Started"
msgstr "Commencer"

Compiling Translations

After editing .po files, compile them to binary .mo files:

pybabel compile -d translations

Run this every time you update translations.

Workflow for Adding New Strings

When you add new translatable strings to your code:

# 1. Re-extract (updates messages.pot)
pybabel extract -F babel.cfg -o messages.pot .

# 2. Update existing .po files with new strings
pybabel update -i messages.pot -d translations

# 3. Edit the .po files to add translations

# 4. Recompile
pybabel compile -d translations

Language Switcher Route

@app.route("/set-language/<lang>")
def set_language(lang):
    supported = ["en", "fr", "es"]
    if lang in supported:
        session["lang"] = lang
    return redirect(request.referrer or "/")
<!-- In your navbar -->
<div class="language-switcher">
    <a href="/set-language/en">EN</a>
    <a href="/set-language/fr">FR</a>
    <a href="/set-language/es">ES</a>
</div>

Formatting Dates and Numbers

Flask-Babel also localizes dates and numbers:

from flask_babel import format_date, format_number, format_currency

# In a route
formatted_date = format_date(post["created_at"], format="long")
# English: "January 15, 2026"
# French:  "15 janvier 2026"
<!-- In templates -->
{{ post.created_at | datetimeformat }}

Project Structure

myapp/
├── babel.cfg
├── messages.pot          # Auto-generated, commit to git
└── translations/
    ├── fr/
    │   └── LC_MESSAGES/
    │       ├── messages.po   # Commit to git
    │       └── messages.mo   # Auto-generated, add to .gitignore
    └── es/
        └── LC_MESSAGES/
            ├── messages.po
            └── messages.mo

Was this helpful?

Get More Flask Vibe Tutorials

Join 1,000+ developers getting weekly Flask tips and AI-friendly code patterns.

No spam. Unsubscribe anytime.