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 sua aplicação Flask com Flask-Babel, arquivos PO e tradução automatizada com IA.

1

Instalar Flask-Babel

Flask-Babel é a extensão padrão de internacionalização para Flask. Integra GNU gettext com Flask e modelos Jinja2 e oferece nativamente 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 o integra no ciclo das requisições Flask. Disponibiliza gettext(), ngettext() e lazy_gettext(), além de detecção automática da localidade pelos cabeçalhos do navegador.
Terminal
pip install Flask-Babel
2

Configurar Babel

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

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 todas as requisições. Se devolver uma localidade sem arquivo .mo compilado, Flask-Babel recorre silenciosamente à localidade padrão. Não apresenta erros: as strings simplesmente aparecem sem tradução.
3

Marcar strings para tradução

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

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 registra automaticamente _() como elemento global de Jinja2.
4

Extrair mensagens

Execute pybabel extract para procurar strings traduzíveis no código-fonte e nos modelos. Cria um arquivo .pot (Portable Object Template). Depois, inicialize catálogos para cada idioma de destino ou atualize os existentes quando mudarem as strings 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 em um diretório de idioma existente substitui todas as traduções. update combina as novas strings e preserva as existentes.
5

Traduzir arquivos PO

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

Compilar traduções

Compile os arquivos .po em arquivos binários .mo através de pybabel compile. Flask-Babel lê .mo durante a execução, não consegue ler diretamente .po. Deve 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, você quase certamente esqueceu de executar pybabel compile. É o problema mais frequente do Flask-Babel. Adicione esta etapa ao script de implementação para evitar esse problema em produção.
7

Tratar plurais e variáveis

Utilize ngettext() em strings 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 usuário na sessão Flask. Atualize locale_selector para verificar primeiro a sessão e depois recorrer à detecçã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 localidade da sessão, chame flask_babel.refresh() para forçar Flask-Babel a voltar a ler a localidade da requisição atual. Sem refresh(), a antiga permanece até a requisição seguinte.
9

Automatizar as traduções

Depois de concluir a configuração de Flask-Babel, traduza os arquivos 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 strings e executar pybabel update, traduza apenas as novas entradas não traduzidas —valores msgstr vazios— em vez de gerar tudo novamente. Assim preserva traduções revisadas por pessoas.

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

Por padrão, Flask-Babel recorre diretamente à localidade predefinida quando a preferida não está disponível. Um usuário pt-BR com apenas traduções pt-PT vê inglês em vez de português. flask-babel-locale-chain adiciona cadeias configuráveis para as localidades 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

Detecte 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 de compilar .po em .mo

Flask-Babel lê arquivos .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() no nível do módulo

gettext() exige o contexto de uma requisição. Se definir strings traduzidas no nível do módulo —atributos de classe ou constantes—, utilize lazy_gettext(). A tradução é adiada até a string ser apresentada em uma requisição.

A extração não encontra strings

pybabel extract só analisa arquivos correspondentes aos padrões de babel.cfg. Se não extrair strings, 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 arquivos PO

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

Estrutura de arquivos 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

Solte aqui seu arquivo de tradução

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

ou clique para selecionar

Idiomas de destino

Sem cadastroEstimativa imediata

Perguntas frequentes sobre i18n em Flask