python
46 lines · 8 steps
Per-request localization in Flask with Babel
How Flask-Babel picks a locale per request and localizes both messages and datetimes.
Explained by
highlit
1from flask import Flask, request, g, jsonify
2from flask_babel import Babel, gettext as _, format_datetime
3from datetime import datetime
4
5app = Flask(__name__)
6app.config["BABEL_DEFAULT_LOCALE"] = "en"
7app.config["BABEL_DEFAULT_TIMEZONE"] = "UTC"
8
9SUPPORTED_LOCALES = ["en", "es", "fr", "de", "ja"]
10
11
12def select_locale():
13 requested = request.args.get("lang")
14 if requested in SUPPORTED_LOCALES:
15 return requested
16
17 header = request.headers.get("X-User-Locale")
18 if header in SUPPORTED_LOCALES:
19 return header
20
21 return request.accept_languages.best_match(SUPPORTED_LOCALES) or app.config["BABEL_DEFAULT_LOCALE"]
22
23
24babel = Babel(app, locale_selector=select_locale)
25
26
27@app.before_request
28def store_locale():
29 g.locale = str(select_locale())
30
31
32@app.route("/api/greeting")
33def greeting():
34 name = request.args.get("name", _("friend"))
35 return jsonify(
36 locale=g.locale,
37 message=_("Hello, %(name)s! Welcome back.", name=name),
38 server_time=format_datetime(datetime.utcnow(), format="long"),
39 )
40
41
42@app.after_request
43def expose_locale(response):
44 response.headers["Content-Language"] = g.get("locale", app.config["BABEL_DEFAULT_LOCALE"])
45 response.headers.add("Vary", "Accept-Language")
46 return response
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Locale selection should fall back through explicit choice, custom headers, then the client's Accept-Language.
- 2Storing the resolved locale on g makes it available across the whole request lifecycle without recomputing it.
- 3Advertising Content-Language and Vary lets caches and clients handle localized responses correctly.
Related explainers
python
import hashlib from collections import defaultdict from pathlib import Path
Finding duplicate files by size then hash
hashing
file-io
deduplication
Intermediate
7 steps
java
@Component public class HeaderMergeFilter { private static final String PER_REQUEST_HEADERS = HeaderMergeFilter.class.getName() + ".headers";
Merging default and per-request headers in Spring
webclient
filters
http-headers
Intermediate
8 steps
python
from django.core.cache import cache from django.core.cache.utils import make_template_fragment_key from django.db.models.signals import post_save, post_delete from django.dispatch import receiver
Busting template fragment caches in Django
caching
signals
cache-invalidation
Intermediate
4 steps
python
from datetime import datetime, timezone _INTERVALS = ( ("year", 60 * 60 * 24 * 365),
Building a human-friendly time_ago helper
datetime
timezones
formatting
Intermediate
5 steps
python
import time import threading from flask import Flask, request, jsonify, g
A token-bucket rate limiter in Flask
rate-limiting
token-bucket
middleware
Intermediate
7 steps
python
from functools import wraps from flask import Blueprint, abort, jsonify from flask_login import current_user, login_required
Building an admin-only decorator in Flask
decorators
authorization
access-control
Intermediate
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/per-request-localization-in-flask-with-babel-explained-python-3cc5/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.