Skip to main content

Flask: Adding Multilingual Support

Flask: Adding Multilingual Support

Adding multilingual support to a Flask application enables it to serve content in multiple languages, enhancing accessibility and user experience for a global audience. By leveraging tools like Flask-Babel, Flask can handle translations, locale detection, and internationalization (i18n) effectively. This guide explores adding multilingual support in Flask, covering key techniques, best practices, and practical applications for building localized web applications.


01. Why Add Multilingual Support in Flask?

Multilingual support is crucial for applications targeting diverse user bases, such as e-commerce platforms, educational tools, or social networks. It allows dynamic content translation, locale-specific formatting (e.g., dates, numbers), and improved user engagement. Flask-Babel, combined with NumPy Array Operations for data-driven localization (e.g., analytics in multiple languages), makes Flask a robust choice for internationalized applications.

Example: Basic Multilingual Flask App

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

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

babel = Babel(app)

@babel.localeselector
def get_locale():
    return request.accept_languages.best_match(app.config['BABEL_SUPPORTED_LOCALES'])

@app.route('/')
def index():
    return render_template('index.html', title=_('Welcome'), message=_('Hello, World!'))

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)
# templates/index.html
<!DOCTYPE html>
<html>
<head>
    <title>{{ title }}</title>
</head>
<body>
    <h1>{{ title }}</h1>
    <p>{{ message }}</p>
    <p>Current locale: {{ g.locale }}</p>
</body>
</html>
# translations/es/LC_MESSAGES/messages.po
msgid "Welcome"
msgstr "Bienvenido"

msgid "Hello, World!"
msgstr "¡Hola, Mundo!"
# translations/fr/LC_MESSAGES/messages.po
msgid "Welcome"
msgstr "Bienvenue"

msgid "Hello, World!"
msgstr "Bonjour le monde !"

Compile Translations:

pybabel compile -d translations

Output (browser http://localhost:5000 with Spanish browser settings):

Page:
Bienvenido
¡Hola, Mundo!
Current locale: es

Explanation:

  • Flask-Babel - Manages translations and locale selection.
  • @babel.localeselector - Dynamically selects locale based on browser settings.
  • _() - Marks strings for translation.

02. Key Multilingual Support Techniques

Adding multilingual support in Flask involves setting up translations, detecting user locales, and formatting locale-specific data. The table below summarizes key techniques and their applications:

Technique Description Use Case
Translation Setup Configure Flask-Babel and translation files Basic i18n support
Locale Detection Automatically select user’s preferred language Dynamic language switching
Dynamic Translations Translate dynamic content User-generated or variable text
Date/Number Formatting Locale-specific formatting Currency, dates, numbers
Template Integration Use translations in Jinja2 templates Localized UI


2.1 Translation Setup

Example: Setting Up Translations

# app.py
from flask import Flask, render_template
from flask_babel import Babel, _

app = Flask(__name__)
app.config['BABEL_DEFAULT_LOCALE'] = 'en'
babel = Babel(app)

@app.route('/')
def index():
    return render_template('index.html', greeting=_('Hello'))

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

Extract Strings:

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

Create Translation Files:

pybabel init -i messages.pot -d translations -l es

Edit Translation:

# translations/es/LC_MESSAGES/messages.po
msgid "Hello"
msgstr "Hola"

Compile Translations:

pybabel compile -d translations

Output (browser http://localhost:5000 with Spanish locale):

Hola

Explanation:

  • pybabel extract - Extracts translatable strings.
  • pybabel init - Creates translation files for each locale.
  • Requires flask-babel and babel (pip install flask-babel).

2.2 Locale Detection

Example: Dynamic Locale Selection

# app.py
from flask import Flask, render_template, request, g
from flask_babel import Babel, _

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

babel = Babel(app)

@babel.localeselector
def get_locale():
    # Check URL parameter, session, or browser settings
    lang = request.args.get('lang') or request.accept_languages.best_match(app.config['BABEL_SUPPORTED_LOCALES'])
    g.locale = lang
    return lang

@app.route('/')
def index():
    return render_template('index.html', title=_('Home'))

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)
# templates/index.html
<!DOCTYPE html>
<html>
<head>
    <title>{{ title }}</title>
</head>
<body>
    <h1>{{ title }}</h1>
    <p>Locale: {{ g.locale }}</p>
    <a href="?lang=en">English</a> | <a href="?lang=es">Spanish</a> | <a href="?lang=fr">French</a>
</body>
</html>

Translation Files (es):

# translations/es/LC_MESSAGES/messages.po
msgid "Home"
msgstr "Inicio"

Output (browser http://localhost:5000?lang=es):

Page:
Inicio
Locale: es

Explanation:

  • request.args.get('lang') - Allows manual locale selection via URL.
  • request.accept_languages - Fallback to browser language preferences.

2.3 Dynamic Translations

Example: Translating Dynamic Content

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

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

babel = Babel(app)

@babel.localeselector
def get_locale():
    return request.accept_languages.best_match(app.config['BABEL_SUPPORTED_LOCALES'])

@app.route('/greet/<name>')
def greet(name):
    message = _('Hello, %(name)s!', name=name)
    return render_template('greet.html', message=message)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)
# templates/greet.html
<!DOCTYPE html>
<html>
<head>
    <title>Greet</title>
</head>
<body>
    <p>{{ message }}</p>
</body>
</html>

Translation Files (es):

# translations/es/LC_MESSAGES/messages.po
msgid "Hello, %(name)s!"
msgstr "¡Hola, %(name)s!"

Output (browser http://localhost:5000/greet/Alice with Spanish locale):

¡Hola, Alice!

Explanation:

  • _('Hello, %(name)s!', name=name) - Translates dynamic strings with placeholders.
  • Supports variable content like usernames.

2.4 Date/Number Formatting

Example: Locale-Specific Formatting

# app.py
from flask import Flask, render_template, request
from flask_babel import Babel, _, format_datetime, format_number
from datetime import datetime

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

babel = Babel(app)

@babel.localeselector
def get_locale():
    return request.accept_languages.best_match(app.config['BABEL_SUPPORTED_LOCALES'])

@app.route('/')
def index():
    now = datetime.now()
    formatted_date = format_datetime(now, 'full')
    formatted_number = format_number(12345.67)
    return render_template('index.html', date=formatted_date, number=formatted_number)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)
# templates/index.html
<!DOCTYPE html>
<html>
<head>
    <title>Formatting</title>
</head>
<body>
    <p>Date: {{ date }}</p>
    <p>Number: {{ number }}</p>
</body>
</html>

Output (browser http://localhost:5000 with Spanish locale):

Date: domingo, 11 de mayo de 2025, 10:00:00
Number: 12.345,67

Explanation:

  • format_datetime - Formats dates per locale conventions.
  • format_number - Formats numbers with locale-specific separators.

2.5 Template Integration

Example: Localized Templates

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

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

babel = Babel(app)

@babel.localeselector
def get_locale():
    return request.accept_languages.best_match(app.config['BABEL_SUPPORTED_LOCALES'])

@app.route('/')
def index():
    return render_template('index.html')

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)
# templates/index.html
{% from 'macros.html' import translate %}
<!DOCTYPE html>
<html>
<head>
    <title>{{ translate('Welcome') }}</title>
</head>
<body>
    <h1>{{ translate('Welcome') }}</h1>
    <p>{{ translate('This is a test') }}</p>
</body>
</html>
# templates/macros.html
{% macro translate(text) %}
    {{ _(text) }}
{% endmacro %}

Translation Files (es):

# translations/es/LC_MESSAGES/messages.po
msgid "Welcome"
msgstr "Bienvenido"

msgid "This is a test"
msgstr "Esto es una prueba"

Output (browser http://localhost:5000 with Spanish locale):

Page:
Bienvenido
Esto es una prueba

Explanation:

  • {{ translate('text') }} - Uses a macro to simplify translation in templates.
  • Improves maintainability for complex UIs.

2.6 Incorrect Multilingual Approach

Example: Hardcoding Translations

# app.py (Incorrect)
from flask import Flask, render_template, request

app = Flask(__name__)

@app.route('/')
def index():
    lang = request.args.get('lang', 'en')
    translations = {
        'en': {'title': 'Welcome', 'message': 'Hello, World!'},
        'es': {'title': 'Bienvenido', 'message': '¡Hola, Mundo!'}
    }
    return render_template('index.html', **translations.get(lang, translations['en']))

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)
# templates/index.html
<!DOCTYPE html>
<html>
<head>
    <title>{{ title }}</title>
</head>
<body>
    <h1>{{ title }}</h1>
    <p>{{ message }}</p>
</body>
</html>

Output (browser http://localhost:5000?lang=es):

Bienvenido
¡Hola, Mundo!

Explanation:

  • Hardcoding translations is unscalable and error-prone.
  • Solution: Use Flask-Babel for standardized i18n.

03. Effective Usage

3.1 Recommended Practices

  • Structure translations with Flask-Babel and modular templates.

Example: Scalable Multilingual App

# myapp/__init__.py
from flask import Flask
from flask_babel import Babel

babel = Babel()

def create_app():
    app = Flask(__name__)
    app.config['BABEL_DEFAULT_LOCALE'] = 'en'
    app.config['BABEL_SUPPORTED_LOCALES'] = ['en', 'es', 'fr']
    
    babel.init_app(app)
    
    from myapp.routes import bp
    app.register_blueprint(bp)
    
    return app

# myapp/routes.py
from flask import Blueprint, render_template, request, g
from flask_babel import _

bp = Blueprint('main', __name__)

@bp.route('/')
def index():
    g.locale = request.accept_languages.best_match(bp.app.config['BABEL_SUPPORTED_LOCALES'])
    return render_template('index.html', title=_('Dashboard'), stats=_('Statistics'))

@bp.route('/report/<int:value>')
def report(value):
    g.locale = request.accept_languages.best_match(bp.app.config['BABEL_SUPPORTED_LOCALES'])
    from flask_babel import format_number
    formatted_value = format_number(value)
    return render_template('report.html', value=formatted_value)

# myapp/babel.py
from flask_babel import Babel
from flask import request, g

babel = Babel()

@babel.localeselector
def get_locale():
    lang = request.args.get('lang') or request.accept_languages.best_match(request.app.config['BABEL_SUPPORTED_LOCALES'])
    g.locale = lang
    return lang
# myapp/templates/index.html
{% from 'macros.html' import translate %}
<!DOCTYPE html>
<html>
<head>
    <title>{{ translate(title) }}</title>
</head>
<body>
    <h1>{{ translate(title) }}</h1>
    <p>{{ translate(stats) }}</p>
    <p>Locale: {{ g.locale }}</p>
    <a href="?lang=en">English</a> | <a href="?lang=es">Spanish</a> | <a href="?lang=fr">French</a>
</body>
</html>
# myapp/templates/report.html
{% from 'macros.html' import translate %}
<!DOCTYPE html>
<html>
<head>
    <title>{{ translate('Report') }}</title>
</head>
<body>
    <h1>{{ translate('Report') }}</h1>
    <p>Value: {{ value }}</p>
</body>
</html>
# myapp/templates/macros.html
{% macro translate(text) %}
    {{ _(text) }}
{% endmacro %}

Translation Files (es):

# myapp/translations/es/LC_MESSAGES/messages.po
msgid "Dashboard"
msgstr "Tablero"

msgid "Statistics"
msgstr "Estadísticas"

msgid "Report"
msgstr "Informe"

Output (browser http://localhost:5000?lang=es):

Page:
Tablero
Estadísticas
Locale: es

Output (browser http://localhost:5000/report/12345?lang=es):

Informe
Value: 12.345

  • Application factory organizes Flask-Babel setup.
  • Dynamic locale selection and template macros.
  • Locale-specific number formatting for reports.

3.2 Practices to Avoid

  • Avoid manual translation dictionaries.

Example: Manual Translation Dictionary

# app.py (Incorrect)
from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def index():
    lang = request.args.get('lang', 'en')
    translations = {
        'en': {'title': 'Home'},
        'es': {'title': 'Inicio'}
    }
    return render_template('index.html', **translations.get(lang, translations['en']))

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

Output (browser http://localhost:5000?lang=es):

Inicio

  • Manual dictionaries are hard to maintain for large apps.
  • Solution: Use Flask-Babel with .po files.

04. Common Use Cases

4.1 Localized E-Commerce Platform

Serve product descriptions and prices in multiple languages.

Example: Multilingual Product Page

# app.py
from flask import Flask, render_template, request
from flask_babel import Babel, _, format_currency

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

babel = Babel(app)

@babel.localeselector
def get_locale():
    return request.accept_languages.best_match(app.config['BABEL_SUPPORTED_LOCALES'])

@app.route('/product')
def product():
    price = format_currency(99.99, 'USD')
    return render_template('product.html', name=_('Laptop'), price=price)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)
# templates/product.html
<!DOCTYPE html>
<html>
<head>
    <title>{{ name }}</title>
</head>
<body>
    <h1>{{ name }}</h1>
    <p>Price: {{ price }}</p>
</body>
</html>

Translation Files (es):

# translations/es/LC_MESSAGES/messages.po
msgid "Laptop"
msgstr "Portátil"

Output (browser http://localhost:5000/product with Spanish locale):

Portátil
Price: 99,99 USD

Explanation:

  • format_currency - Formats prices per locale.
  • Translated product names enhance user experience.

4.2 Multilingual Analytics Dashboard

Display analytics in user-preferred languages.

Example: Localized Dashboard

# app.py
from flask import Flask, render_template, request
from flask_babel import Babel, _, format_number
import numpy as np

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

babel = Babel(app)

@babel.localeselector
def get_locale():
    return request.accept_languages.best_match(app.config['BABEL_SUPPORTED_LOCALES'])

@app.route('/dashboard')
def dashboard():
    data = np.random.rand(5).sum()
    formatted_data = format_number(data)
    return render_template('dashboard.html', title=_('Analytics'), data=formatted_data)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)
# templates/dashboard.html
<!DOCTYPE html>
<html>
<head>
    <title>{{ title }}</title>
</head>
<body>
    <h1>{{ title }}</h1>
    <p>Total: {{ data }}</p>
</body>
</html>

Translation Files (es):

# translations/es/LC_MESSAGES/messages.po
msgid "Analytics"
msgstr "Análisis"

Output (browser http://localhost:5000/dashboard with Spanish locale):

Análisis
Total: 3,45

Explanation:

  • NumPy processes analytics data.
  • Flask-Babel translates UI and formats numbers.

Conclusion

Adding multilingual support in Flask with Flask-Babel enables global accessibility and seamless user experiences. Key takeaways:

  • Configure Flask-Babel for translation management.
  • Detect locales dynamically via browser settings or URL parameters.
  • Support dynamic translations and locale-specific formatting.
  • Integrate translations in templates with macros.
  • Avoid hardcoding translations for scalability.

With Flask-Babel, Flask applications can deliver localized content efficiently, making them ideal for international audiences!

Comments