Voce marcou strings para traducao, gerou arquivos .po, executou compilemessages, e sua aplicacao ainda mostra ingles. Voce nao esta sozinho. O framework i18n do Django e poderoso, mas tem armadilhas que pegam ate desenvolvedores experientes.
Este guia cobre as 10 razoes mais comuns pelas quais as traducoes do Django falham silenciosamente, com sintomas exatos e correcoes para cada uma.
1. Esquecer de executar compilemessages apos editar arquivos .po
Voce editou um arquivo .po (manualmente ou com uma ferramenta), mas o texto traduzido nunca aparece. A aplicacao continua mostrando as strings originais em ingles.
O Django nao le arquivos .po em tempo de execucao. Ele le os arquivos binarios compilados .mo (machine object). Se voce editar um arquivo .po sem recompilar, o Django nao tem ideia de que algo mudou.
Execute compilemessages apos cada alteracao em um arquivo .po:
python manage.py compilemessages
Se voce automatiza suas traducoes com o TranslateBot, adicione compilemessages como a etapa final do seu fluxo de trabalho:
python manage.py makemessages -a --no-obsolete
python manage.py translate
python manage.py compilemessages
2. {% load i18n %} ausente nos templates
Voce usa {% trans "Hello" %} em um template, mas o Django lanca um TemplateSyntaxError. Ou pior, a tag silenciosamente nao faz nada se voce tiver um motor de template mal configurado.
As tags {% trans %} e {% blocktrans %} ficam na biblioteca de tags de template i18n do Django. Sem carrega-la, o motor de template nao as reconhece.
Adicione {% load i18n %} no topo de cada template que usa tags de traducao:
{% load i18n %}
<h1>{% trans "Welcome to our site" %}</h1>
<p>{% blocktrans with name=user.name %}Hello, {{ name }}!{% endblocktrans %}</p>
Este e um requisito por template. Mesmo que um template pai carregue i18n, templates filhos que usam tags de traducao precisam de sua propria declaracao {% load i18n %}.
3. LocaleMiddleware ausente no MIDDLEWARE ou na posicao errada
O Django sempre serve conteudo no idioma padrao, independentemente do cabecalho Accept-Language do navegador, prefixo de URL ou configuracoes de sessao.
O LocaleMiddleware determina o idioma ativo para cada requisicao. Sem ele, o Django usa o LANGUAGE_CODE padrao e ignora todos os mecanismos de selecao de idioma. Sua posicao na pilha de middleware tambem importa, pois precisa de acesso aos dados de sessao e resolucao de URL.
Adicione LocaleMiddleware a sua configuracao MIDDLEWARE, apos SessionMiddleware e CommonMiddleware:
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.locale.LocaleMiddleware", # Must be after SessionMiddleware
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
]
Tambem certifique-se de que django.conf.urls.i18n esta incluido na sua configuracao de URL se voce usa troca de idioma baseada em URL:
from django.conf.urls.i18n import i18n_patterns
urlpatterns = i18n_patterns(
path("", include("myapp.urls")),
)
4. Codigos de idioma incompativeis (ex. pt-br vs pt_BR)
As traducoes existem nos seus arquivos .po, compilemessages tem sucesso, mas o Django ignora a traducao para certos locales.
O Django espera que os diretorios de locale sigam o formato <language>_<COUNTRY> com separador de underscore e codigo de pais em maiusculas. Por exemplo, pt_BR para Portugues Brasileiro. Se seu diretorio se chama pt-br, pt-BR ou ptBR, o Django nao o encontrara. O mesmo se aplica a configuracao LANGUAGES: os codigos la usam hifens (pt-br), mas o sistema de arquivos usa underscores (pt_BR).
Certifique-se de que sua estrutura de diretorios corresponde as expectativas do Django:
locale/
pt_BR/
LC_MESSAGES/
django.po
django.mo
E nas suas configuracoes, use a forma com hifen:
LANGUAGES = [
("en", "English"),
("pt-br", "Brazilian Portuguese"),
("zh-hans", "Simplified Chinese"),
]
Ao executar makemessages, use a forma com underscore para a flag de locale:
python manage.py makemessages -l pt_BR
5. Entradas fuzzy silenciosamente ignoradas durante a compilacao
Uma traducao existe no arquivo .po, mas o Django mostra a string original em ingles em tempo de execucao para aquela entrada especifica. Isso e particularmente frustrante porque a traducao esta ali no arquivo.
Quando o makemessages do Django detecta que uma string fonte mudou ligeiramente, ele marca a traducao existente como "fuzzy" (significando que e uma estimativa que precisa de revisao humana). O comando compilemessages pula todas as entradas fuzzy, tratando-as como nao traduzidas. Entao a entrada parece traduzida no arquivo .po, mas o arquivo .mo a exclui inteiramente.
Uma entrada fuzzy se parece com isto:
#, fuzzy
msgid "Welcome to our website!"
msgstr "Welkom op onze website!"
Revise a traducao, atualize msgstr se necessario, e entao remova a flag #, fuzzy:
msgid "Welcome to our website!"
msgstr "Welkom op onze website!"
Depois recompile:
python manage.py compilemessages
Em um projeto maior, entradas fuzzy se acumulam e sao faceis de perder. O comando check_translations do TranslateBot as detecta automaticamente:
python manage.py check_translations
locale/nl/LC_MESSAGES/django.po: 0 untranslated, 3 fuzzy
CommandError: Translation check failed
Adicione-o ao seu pipeline de CI com check_translations --makemessages e voce nunca mais enviara uma entrada fuzzy.
6. LOCALE_PATHS nao configurado ou apontando para o diretorio errado
makemessages cria arquivos .po em um local, mas o Django os procura em outro lugar. As traducoes existem no disco mas nunca sao carregadas.
O Django busca arquivos de traducao em uma ordem especifica: diretorios de LOCALE_PATHS primeiro, depois o diretorio locale/ de cada app, e finalmente o diretorio locale/ do projeto. Se LOCALE_PATHS nao esta configurado ou aponta para o caminho errado, o Django pode nunca encontrar seus arquivos .po.
Configure LOCALE_PATHS nas suas configuracoes com um caminho absoluto:
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
LOCALE_PATHS = [
BASE_DIR / "locale",
]
Verifique se o diretorio existe e contem a estrutura esperada:
locale/
de/
LC_MESSAGES/
django.po
django.mo
nl/
LC_MESSAGES/
django.po
django.mo
Um erro comum e configurar LOCALE_PATHS como locale/ (relativo) em vez de um caminho absoluto. O Django nao resolve caminhos relativos a partir da raiz do seu projeto. Depende do diretorio de trabalho do processo, que muitas vezes nao e o que voce espera.
7. Cache servindo traducoes desatualizadas
Voce atualizou e compilou traducoes, mas o texto antigo continua aparecendo. Reiniciar o servidor resolve.
O catalogo de traducoes do Django e carregado uma vez por processo. Em producao, servidores WSGI/ASGI como Gunicorn ou Uvicorn mantem processos worker vivos por periodos estendidos. O arquivo .mo pode ter mudado no disco, mas o processo em execucao ainda tem as traducoes antigas na memoria. Alem disso, se voce usa o framework de cache do Django ou um proxy reverso como Nginx ou Cloudflare, respostas em cache servirao conteudo antigo ate expirarem.
Reinicie seu servidor de aplicacao apos implantar novas traducoes:
# Gunicorn
kill -HUP $(cat /tmp/gunicorn.pid)
# Systemd
sudo systemctl restart myapp
# Docker
docker compose restart web
Para o framework de cache do Django, limpe o cache apos atualizar as traducoes:
from django.core.cache import cache
cache.clear()
Em desenvolvimento, runserver recarrega automaticamente quando arquivos Python mudam, mas nao monitora arquivos .mo. Voce precisara reinicia-lo manualmente apos executar compilemessages.
8. Strings nao envolvidas em gettext (_() ou {% trans %})
makemessages nao extrai certas strings, entao elas nunca aparecem nos seus arquivos .po e nunca sao traduzidas. Este e o problema mais basico e tambem o mais facil de ignorar em uma base de codigo grande.
O comando makemessages do Django usa xgettext para escanear seu codigo-fonte em busca de marcadores de traducao. Se uma string nao esta envolvida em gettext() (comumente com alias _()), gettext_lazy(), {% trans %} ou {% blocktrans %}, ela e invisivel para o processo de extracao.
Envolva cada string voltada ao usuario:
# Python code
from django.utils.translation import gettext_lazy as _
class Article(models.Model):
class Meta:
verbose_name = _("article")
verbose_name_plural = _("articles")
# Views
from django.utils.translation import gettext as _
def my_view(request):
message = _("Your changes have been saved.")
return HttpResponse(message)
<!-- Templates -->
{% load i18n %}
<h1>{% trans "Welcome" %}</h1>
<p>{% blocktrans with count=items|length %}You have {{ count }} items.{% endblocktrans %}</p>
Use a flag --dry-run do TranslateBot para visualizar quais strings estao atualmente sem traducao, para que voce possa pegar strings que foram perdidas durante a extracao:
python manage.py translate --target-lang de --dry-run
Isso mostra todas as entradas nao traduzidas nos seus arquivos .po sem fazer nenhuma chamada de API ou alteracao.
9. f-strings nao podem ser traduzidas (limitacao do Django)
Voce envolve uma f-string em _() e obtem um erro de sintaxe, ou makemessages extrai uma string quebrada/parcial que nao pode ser traduzida.
F-strings em Python sao avaliadas em tempo de execucao. A ferramenta de extracao xgettext analisa o codigo-fonte estaticamente, entao nao pode avaliar expressoes Python dentro de chaves {}. Isso significa que _(f"Hello, {name}") e extraida como uma string contendo uma expressao literal {name} (ou falha completamente na extracao), e a entrada .po resultante nunca correspondera a string em tempo de execucao.
Use a formatacao % do Django ou .format() com placeholders nomeados:
# Wrong -- f-string cannot be extracted
message = _(f"Hello, {user.name}! You have {count} new messages.")
# Correct -- named placeholders
message = _("Hello, %(name)s! You have %(count)d new messages.") % {
"name": user.name,
"count": count,
}
# Also correct -- .format() with positional args
message = _("Hello, {0}! You have {1} new messages.").format(user.name, count)
Isso nao e uma limitacao do TranslateBot ou das ferramentas. E fundamental para como o gettext funciona. A string fonte deve ser um literal estatico para que possa ser extraida e consultada em tempo de execucao.
O TranslateBot preserva todos esses formatos de placeholder (%(name)s, {0}, %s, tags HTML) durante a traducao, entao as strings traduzidas permanecem totalmente funcionais.
10. Erros de formato de string de placeholder quebrando a compilacao .po
compilemessages falha com um erro, ou o arquivo .po tem uma incompatibilidade de flag #, python-format, e a entrada e silenciosamente descartada.
Quando uma string fonte contem placeholders de formato Python como %(name)s, o Django marca a entrada .po com #, python-format. Se a traducao tem placeholders diferentes (um erro de digitacao como %(nome)s, um placeholder faltante ou um extra), as ferramentas gettext podem rejeitar a entrada ou compilemessages pode falhar. Isso acontece comumente com traducoes manuais ou com ferramentas de traducao com IA que nao entendem a semantica de placeholders.
Uma entrada quebrada se parece com isto:
#, python-format
msgid "Hello, %(name)s! You have %(count)d new messages."
msgstr "Hallo, %(naam)s! Je hebt %(count)d nieuwe berichten."
Aqui %(naam)s deveria ser %(name)s. Os placeholders devem corresponder exatamente a fonte.
Certifique-se de que as strings traduzidas contenham exatamente os mesmos placeholders que a fonte. Verifique erros de digitacao, placeholders faltantes e placeholders extras.
Esta e uma area onde o TranslateBot oferece uma vantagem real. Sua logica de preservacao de placeholders garante que todas as strings de formato (%(name)s, {0}, %s) na saida traduzida correspondam exatamente a fonte. O tratamento de placeholders e coberto por 100% de cobertura de testes, entao erros de strings de formato de traducao sao eliminados no nivel da ferramenta em vez de serem detectados em tempo de compilacao.
Se voce esta traduzindo manualmente ou com uma ferramenta que nao trata placeholders, valide seus arquivos .po com:
msgfmt --check-format locale/de/LC_MESSAGES/django.po
Isso executa a validacao de strings de formato do gettext e relata quaisquer incompatibilidades.
Juntando tudo: um fluxo de trabalho defensivo
A maioria desses problemas compartilha uma causa raiz: etapas manuais que sao faceis de esquecer. Aqui esta um fluxo de trabalho que previne todos os 10 problemas:
# 1. Extract strings (catches #8 -- any new gettext-wrapped strings)
python manage.py makemessages -a --no-obsolete
# 2. Translate (catches #1, #5, #8, #9, #10 -- handles untranslated,
# fuzzy, and placeholder issues automatically)
python manage.py translate
# 3. Compile (catches #1 -- generates .mo files)
python manage.py compilemessages
# 4. Verify in CI (catches everything that slipped through)
python manage.py check_translations --makemessages
Adicione a etapa 4 ao seu pipeline de CI e strings nao traduzidas, entradas fuzzy e erros de formato farao o build falhar antes de chegarem a producao.
Tabela de referencia rapida
| Causa | Sintoma | Correcao em uma linha |
|---|---|---|
Sem compilemessages |
Traducoes existem mas nao aparecem | python manage.py compilemessages |
{% load i18n %} ausente |
TemplateSyntaxError em {% trans %} |
Adicionar {% load i18n %} ao template |
| LocaleMiddleware ausente | Idioma sempre padrao para ingles | Adicionar django.middleware.locale.LocaleMiddleware ao MIDDLEWARE |
| Codigo de idioma incompativel | Diretorio de locale nao encontrado | Usar pt_BR (underscore) para diretorios, pt-br (hifen) para configuracoes |
| Entradas fuzzy ignoradas | Traducao no .po mas nao na app |
Remover flag #, fuzzy apos revisao |
| LOCALE_PATHS errado | Arquivos .po existem mas Django os ignora |
Configurar LOCALE_PATHS com caminho absoluto |
| Traducoes em cache | Texto antigo aparece apos atualizacao | Reiniciar o servidor de aplicacao |
| String nao em gettext | String ausente dos arquivos .po |
Envolver em _() ou {% trans %} |
| f-string em gettext | Extracao quebrada ou incompatibilidade em tempo de execucao | Substituir por placeholders % ou .format() |
| Placeholders incompativeis | compilemessages falha ou entrada descartada |
Corresponder placeholders exatamente entre fonte e traducao |
A maioria desses problemas desaparece quando voce automatiza a etapa de traducao e aplica verificacoes no CI. O comando translate do TranslateBot lida com preservacao de placeholders e traducao incremental, enquanto check_translations captura qualquer coisa que passe despercebida (entradas nao traduzidas, flags fuzzy e problemas de strings de formato) antes de chegarem a producao.