Skip to main content

i18n em Flask: criar uma aplicação multilingue com Flask-Babel

Dos princípios de gettext à implementação em produção: internacionalize a sua aplicação Flask com Flask-Babel, ficheiros PO e tradução automatizada com IA.

1

Instalar Flask-Babel

Flask-Babel é a extensão normal de internacionalização para Flask. Integra GNU gettext com Flask e modelos Jinja2 e oferece de origem funções de tradução, seleção regional e compatibilidade com fusos horários.

Flask-Babel envolve Babel —a biblioteca de i18n para Python— e integra-o no ciclo dos pedidos Flask. Disponibiliza gettext(), ngettext() e lazy_gettext(), além de deteção automática da região pelos cabeçalhos do navegador.
Terminal
pip install Flask-Babel
2

Configurar Babel

Crie babel.cfg para indicar a pybabel onde procurar cadeias traduzíveis e inicialize Flask-Babel com uma função de seleção regional que determine o idioma de cada pedido.

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)
locale_selector é chamado em todos os pedidos. Se devolver uma região sem ficheiro .mo compilado, Flask-Babel recorre silenciosamente à predefinida. Não apresenta erros: as cadeias aparecem simplesmente por traduzir.
3

Marcar cadeias para tradução

Envolva todas as cadeias destinadas aos utilizadores com gettext() no código Python e _() nos modelos Jinja2. Utilize lazy_gettext() nas cadeias definidas ao carregar o módulo —como etiquetas de formulários e configurações— que serão traduzidas mais tarde, durante um pedido.

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>
Utilize _() nos modelos Jinja2 em vez de gettext(): é a abreviatura normal e simplifica os modelos. Flask-Babel regista automaticamente _() como elemento global de Jinja2.
4

Extrair mensagens

Execute pybabel extract para procurar cadeias traduzíveis no código-fonte e nos modelos. Cria um ficheiro .pot (Portable Object Template). Depois, inicialize catálogos para cada idioma de destino ou atualize os existentes quando mudarem as cadeias de origem.

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
Execute sempre pybabel update —não init— após a primeira extração. Executar init num diretório de idioma existente substitui todas as traduções. update combina as novas cadeias e preserva as existentes.
5

Traduzir ficheiros PO

Abra os ficheiros .po gerados e preencha os valores msgstr de cada msgid. São texto simples: pode editá-los diretamente, utilizar um editor PO como Poedit ou automatizar a tradução com IA.

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"
Os ficheiros PO incluem comentários de contexto —linhas iniciadas por #:— que indicam onde cada cadeia é utilizada no código-fonte. Os tradutores utilizam-nos para compreender o contexto. Mantenha-os; pybabel extract gera-os automaticamente.
6

Compilar traduções

Compile os ficheiros .po em ficheiros binários .mo através de pybabel compile. Flask-Babel lê .mo durante a execução, não consegue ler diretamente .po. Tem de voltar a compilar após cada atualização.

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.
Se as traduções não aparecerem após editar um .po, quase certamente se esqueceu de executar pybabel compile. É o problema mais frequente do Flask-Babel. Adicione esta etapa ao script de implementação para o evitar em produção.
7

Tratar plurais e variáveis

Utilize ngettext() em cadeias sensíveis ao plural. Recebe uma forma singular, uma plural e o número. Babel aplica automaticamente a regra correta: o inglês tem 2 formas, o russo 3, o árabe 6 e o japonês 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 товаров в вашей корзине"
Nunca utilize if count == 1 na lógica de plural. Idiomas como o francês consideram 0 singular. O russo e o árabe têm formas inexistentes em inglês. Deixe ngettext() e as regras CLDR de Babel fazer a seleção.
8

Adicionar seleção regional

Crie um seletor de idioma que guarde a escolha do utilizador na sessão Flask. Atualize locale_selector para verificar primeiro a sessão e depois recorrer à deteção no navegador.

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>
Após alterar a região da sessão, chame flask_babel.refresh() para forçar Flask-Babel a voltar a ler a região do pedido atual. Sem refresh(), a antiga permanece até ao pedido seguinte.
9

Automatizar as traduções

Depois de concluir a configuração de Flask-Babel, traduza os ficheiros PO com IA. Automatize o ciclo extrair-traduzir-compilar no pipeline de CI/CD para manter as traduções sincronizadas com o código-fonte.

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
Traduza de forma incremental: quando adicionar cadeias e executar pybabel update, traduza apenas as novas entradas por traduzir —valores msgstr vazios— em vez de gerar tudo novamente. Assim preserva traduções revistas por pessoas.

Extra: recurso regional inteligente com flask-babel-locale-chain

Por predefinição, Flask-Babel recorre diretamente à região predefinida quando a preferida não está disponível. Um utilizador pt-BR com apenas traduções pt-PT vê inglês em vez de português. flask-babel-locale-chain acrescenta cadeias configuráveis para as regiões relacionadas recorrerem naturalmente umas às outras.

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 é um pacote Python de código aberto. Consulte-o no GitHub em github.com/i18n-agent/flask-babel-locale-chain.

Automatizar a qualidade das traduções

Detete chaves em falta e marcadores danificados antes da publicação com i18n-validate. Teste a interface com pseudotraduções através de i18n-pseudo antes de chegarem as traduções reais.

Erros frequentes

Esquecer-se de compilar .po em .mo

Flask-Babel lê ficheiros .mo compilados, não .po. Se as traduções não aparecerem após editar os .po, execute pybabel compile -d translations. Adicione esta etapa ao script de implementação.

Utilizar gettext() ao nível do módulo

gettext() exige o contexto de um pedido. Se definir cadeias traduzidas ao nível do módulo —atributos de classe ou constantes—, utilize lazy_gettext(). A tradução é adiada até a cadeia ser apresentada num pedido.

A extração não encontra cadeias

pybabel extract só analisa ficheiros correspondentes aos padrões de babel.cfg. Se não extrair cadeias, verifique se os padrões correspondem à estrutura, adicione -k lazy_gettext e confirme se os modelos Jinja2 utilizam a extensão correta —.html ou .jinja2—.

Erros de codificação nos ficheiros PO

Os ficheiros PO têm de utilizar UTF-8. Se vir UnicodeDecodeError, verifique o cabeçalho Content-Type do .po: deve indicar charset=UTF-8. Alguns editores guardam noutras codificações; confirme sempre após a edição.

Estrutura de ficheiros recomendada

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/

Experimente já o i18n Agent

Largue aqui o seu ficheiro de tradução

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

ou clique para selecionar

Idiomas de destino

Sem registoEstimativa imediata

Perguntas frequentes sobre i18n em Flask