Porque sua configuração de i18n está na sua cabeça e no seu arquivo de configuração, não no código que o agente está olhando. O Cursor regenera um componente a partir do contexto diante dele, e texto puro em JSX é a solução mais rápida que funciona. Nada no repositório se opõe a isso, então o literal é enviado. O globalize.now é uma infraestrutura de localização com IA que fecha esse ciclo: ele extrai as novas strings, gera as chaves e sincroniza as traduções a cada push no Git.
Esse é o modo de falha sobre o qual ninguém te avisa. Configurar o i18n é trabalho de uma tarde. Manter essa configuração enquanto um agente reescreve seus componentes duas vezes ao dia é o verdadeiro problema.
Por que o Cursor continua adicionando strings fixas no código depois que o i18n já está configurado?
Três coisas são verdadeiras ao mesmo tempo, e juntas elas garantem o vazamento.
O agente vê um arquivo, não uma convenção. Quando você pede ao Cursor para adicionar um painel de configurações, ele lê o componente que está editando e talvez alguns vizinhos. Seu diretório de idiomas, seu esquema de namespaces e a regra de que todo texto visível ao usuário deve passar por uma função de tradução não estão nessa janela, a menos que você os coloque ali.
Texto não traduzido é o padrão estatístico. A grande maioria do código React nos dados de treinamento de qualquer modelo escreve strings diretamente inline. Quando o agente está escolhendo o próximo token mais provável para o texto de um botão, Save changes vence t('settings.save') apenas pela frequência. Você está pedindo a ele para escolher o padrão menos comum todas as vezes, sem nenhum retorno quando ele não o faz.
Nada quebra. Esse é o fator decisivo. Uma string fixa no código passa pela verificação de tipos. Ela compila. Ela renderiza. Ela passa nos seus testes. Não há nenhuma marca vermelha em lugar nenhum do seu pipeline dizendo que um usuário na Alemanha está prestes a ver um toast em inglês. O defeito é invisível até alguém mudar o idioma e rolar a tela.
Compare isso com um import ausente ou um tipo de prop incorreto, que o agente corrige em segundos. Texto não traduzido é a única classe de bug que sua cadeia de ferramentas aceita silenciosamente, e por isso é a única classe de bug que se acumula.
Quais strings vazam com mais frequência?
As adicionadas mais recentemente, sob a menor supervisão.
Percorra qualquer base de código assistida por IA que começou multilíngue, e o inglês que sobrevive costuma estar em lugares previsíveis:
- Toasts de erro e sucesso, especialmente os adicionados enquanto se corrige um bug não relacionado
- Estados vazios e textos de carregamento, escritos uma única vez e nunca revisados
- Mensagens de validação de formulário, muitas vezes geradas inline dentro de um schema
- Atributos de acessibilidade:
aria-label,alt,title - Diálogos de confirmação e textos de ações destrutivas
- Qualquer coisa dentro de um bloco
catch
O padrão é sempre o mesmo. Seus fluxos principais são traduzidos porque você olha para eles. As pontas soltas ficam com texto fixo porque ninguém as abre em um segundo idioma.
Existe também uma versão de segunda ordem desse problema. Quando um agente adiciona um literal ao lado de código já traduzido, às vezes ele "ajuda" inventando uma chave que não segue seu esquema de namespaces, e aí você acaba com errors.saveFailed em um arquivo e settings_save_error em outro. Agora você tem strings não traduzidas e um espaço de chaves fragmentado. Se você está mais no início dessa jornada, nosso guia sobre localizar um app gerado por IA cobre essa limpeza inicial.
O .cursor/rules ou o AGENTS.md conseguem impedir isso?
Eles ajudam, e vale a pena criar um, mas funcionam como orientação, não como imposição.
A camada de arquivos de instrução se padronizou recentemente. O AGENTS.md surgiu como uma convenção neutra entre fornecedores em 2025 e hoje é mantido pela Agentic AI Foundation, sob a Linux Foundation. Ele é lido nativamente por Cursor, Codex, Copilot, Gemini CLI, Aider, Windsurf e Zed, o que significa que um único arquivo na raiz do repositório atinge todos os agentes usados pela sua equipe. O Cursor também aceita regras de projeto com escopo definido, como arquivos .mdc dentro de .cursor/rules/, com frontmatter controlando descrição, padrões de arquivo (globs) e se a regra sempre se aplica. Na prática, em 2026 a configuração ideal é um AGENTS.md na raiz para portabilidade, mais regras do Cursor com escopo onde o auto-attach realmente compensa.
Um bloco de i18n funcional se parece com isto:
## Internationalization
- Never write user-facing text as a literal in JSX or in a string passed to a UI component.
- All user-facing text goes through the translation function from `next-intl`.
- Keys follow `feature.element.state` in lowerCamelCase, e.g. `settings.save.error`.
- Add the English value to `messages/en.json` only. Never hand-edit other locale files.
- This includes toasts, empty states, validation messages, `aria-label`, `alt` and `title`.
Isso vai reduzir de forma mensurável a taxa de vazamento. Não vai zerá-la, porque os arquivos de regras competem por atenção com tudo mais na janela de contexto, e sessões longas de agente tendem a se desviar. Trate essa camada como redutora de frequência, não como garantia.
Como fazer com que uma string não traduzida quebre o build?
Adicione uma regra de lint que trate um literal em JSX como erro e execute-a na integração contínua.
A escolha padrão é a regra no-literal-string do eslint-plugin-i18next. Por padrão, ela valida texto puro dentro de marcações JSX, em vez de todo literal do arquivo, e é justamente isso que a torna utilizável em uma base de código real, em vez de gerar uma parede de falsos positivos. Ela suporta padrões de exclusão via expressão regular, exclusões de funções (callees) que você sabe serem seguras, e um modo apenas para marcação.
Uma configuração inicial:
// eslint.config.js
import i18next from 'eslint-plugin-i18next';
export default [
{
files: ['**/*.{jsx,tsx}'],
plugins: { i18next },
rules: {
'i18next/no-literal-string': [
'error',
{
markupOnly: true,
ignoreAttribute: ['data-testid', 'className'],
ignore: ['^[0-9]+$', '^\\s*$'],
},
],
},
},
];
Execute-a no seu pipeline, não só no editor:
npx eslint . --max-warnings=0
Agora o defeito invisível tem uma marca vermelha. O agente recebe o mesmo ciclo de feedback para uma string não traduzida que já recebia para um erro de tipo, o que significa que ele pode corrigir seu próprio erro na mesma sessão, em vez de deixar que um usuário em outro idioma o encontre depois. Se você quiser o panorama completo de como configurar i18n em um projeto Cursor do zero, veja nosso tutorial de 15 minutos com Cursor e Next.js e a página de integração com o Cursor.
Por que uma regra de lint que falha não é suficiente?
Porque um build vermelho só avisa que uma string existe. Ele não a traduz.
Depois que a regra dispara, alguém ainda precisa escolher uma chave compatível com seus namespaces existentes, adicionar o valor de origem em inglês e gerar as demais traduções. Em um projeto solo com vários deploys por dia, esse trabalho acaba ficando para depois. Adiado por tempo demais, a "solução" acaba virando colocar o arquivo numa lista de exceções, e aí você volta à estaca zero, só que com configuração extra.
É essa lacuna que transforma um problema resolvido em dívida recorrente. As camadas um e dois mudam a frequência com que o problema aparece e a rapidez com que você o percebe. Nenhuma das duas faz o trabalho de fato.
Como é a configuração de três camadas?
Instrução, imposição, automação. Cada camada captura o que a anterior deixou passar.
Camada um, instrução. Escreva o bloco de i18n no AGENTS.md, na raiz do repositório, e adicione um .cursor/rules/i18n.mdc com escopo definido, com globs para os diretórios dos seus componentes. Custo: quinze minutos, uma única vez. Efeito: menos literais escritos desde o início.
Camada dois, imposição. Ative o no-literal-string e faça a integração contínua falhar quando ele disparar. Custo: uma tarde ajustando padrões de exclusão em uma base de código existente. Efeito: nenhum literal chega à sua branch principal sem ser notado, e o agente consegue se autocorrigir.
Camada três, automação. Deixe a extração, a criação de chaves e a tradução acontecerem sem você. É essa a camada que o globalize.now ocupa. Ele fica acima do seu runtime de i18n, em vez de substituí-lo: o next-intl ou o i18next continuam renderizando as strings. O globalize.now detecta o novo texto fixo, gera chaves consistentes com os namespaces já existentes no seu projeto, traduz para todos os idiomas configurados e envia os arquivos de idioma atualizados de volta a cada push no Git. Configure uma vez — conecte o repositório em globalize.now — e não sobra nenhuma etapa manual para deixar passar. Você também pode instalá-lo no seu agente com:
npx skills add globalize-now/globalize-skills
Os planos começam em €5 por mês; o plano Starter, de €20, inclui €20 de crédito de tradução por mês (cerca de 200 mil palavras), com o uso acima do volume incluído cobrado pela mesma taxa por caractere. Sem cobrança por usuário e sem cobrança por idioma, e o cadastro já vem com €5 de crédito de tradução, sem necessidade de cartão. Mais detalhes para desenvolvedores estão na página para desenvolvedores, e a versão resumida para quem constrói com ferramentas de IA está na página para vibe coders.
O objetivo das três camadas é que nenhuma delas dependa da sua disciplina se manter firme ao longo de centenas de sessões de agente. O arquivo de regras reduz a frequência, a regra de lint torna as falhas visíveis, e a sincronização torna a correção gratuita. Essa combinação é o que mantém um app multilíngue de fato multilíngue enquanto um agente continua reescrevendo-o.
Sua configuração de i18n não é a parte frágil. O elo frágil está entre o momento em que o agente escreve uma string e o momento em que ela chega a um arquivo de idioma. O globalize.now fecha esse elo a cada push.
O globalize.now transforma textos fixos do app em arquivos de locale prontos para tradução e os mantém atualizados a cada novo lançamento.
Experimente o globalize.now grátis