A seção de i18n abaixo é a que você deve colar no AGENTS.md: dez regras curtas que mandam o agente de código passar toda string visível ao usuário pela função de tradução, nomear a chave por funcionalidade, nunca concatenar fragmentos, nunca simular plural com ternário e nunca tocar em um arquivo de locale gerado. A globalize.now é uma infraestrutura de localização com IA, então lemos muito código de i18n escrito por agentes, e o mesmo punhado de erros responde por quase tudo. Cada regra corresponde a um deles.
A segunda metade deste post fala de onde o arquivo deixa de funcionar. Duas das superfícies de código mais usadas por devs nunca o leem, e nenhuma redação resolve isso.
O que a seção de i18n do AGENTS.md deve dizer?
Este bloco. Ajuste os três caminhos de arquivo e o comando de lint para o seu projeto e deixe o resto como está.
## Internationalization
- Every user-visible string goes through the translation function. That
includes JSX text, input hint text, `title`, `aria-label`, `alt`, toast and
error messages, empty states and the document `<title>`.
- The source locale is `en`. The catalog is `locales/en.json`. Add the key in
the same change that uses it.
- Keys are namespaced by feature: `checkout.summary.total`, not `total3`.
Search the catalog for an existing key before creating one.
- Never concatenate translated fragments. Use interpolation:
`t('cart.items', { count })`, not `t('cart.you_have') + count`.
- Plurals use the library's plural forms (`_one` / `_other` keys, or ICU
`{count, plural, ...}`). Never `count === 1 ? 'item' : 'items'`.
- Dates, numbers and currency go through `Intl.DateTimeFormat` and
`Intl.NumberFormat` with the active locale. Never a bare
`toLocaleString()`, never a currency symbol glued to a number.
- Do not hardcode the locale list, language names or text direction in
components. Read them from `i18n/config.ts`.
- Do not translate brand names, code identifiers, URLs or environment values.
- Do not edit locale files other than `en`. They are generated. If a
translation looks wrong, fix the source key or the glossary, not the file.
- Before finishing, run `npm run lint`. The `i18next/no-literal-string`
rule must pass.
Dez regras é uma escolha deliberada. A própria documentação de regras do Cursor recomenda manter as regras focadas em padrões que você usa com frequência e usar um linter em vez de colar um guia de estilo inteiro, e a orientação do Claude Code é a mesma: escreva instruções concretas o bastante para serem verificadas e mantenha o arquivo curto, porque a aderência cai conforme ele cresce. Uma seção de i18n com quarenta tópicos acaba sendo lida na diagonal.
Por que essas dez regras e não apenas "use i18n"?
Porque "use i18n" é exatamente o que o agente já acredita estar fazendo quando escreve <Button>Save changes</Button>. Instruções vagas são cumpridas segundo a ideia de conformidade do próprio agente. Cada regra abaixo nomeia uma saída específica e a proíbe.
Literais em atributos. Os agentes aprendem que os filhos do JSX precisam ser traduzidos e depois escrevem aria-label="Close" e alt="Company logo" como strings simples, porque atributos parecem configuração e não texto de interface. Listar os atributos pelo nome fecha essa brecha; um "traduza tudo" genérico não fecha.
Concatenação. t('greeting') + ' ' + name + '!' funciona bem em inglês, mas não dá para traduzir em nenhum idioma que coloque o nome primeiro ou que o flexione. A interpolação entrega ao tradutor uma única string com uma única variável para posicionar.
Plurais com ternário. count === 1 ? 'item' : 'items' é o que mais vemos um agente escrever depois que o i18n já está configurado, e está errado para qualquer idioma com mais de duas formas de plural. A regra dá nome ao padrão para que o agente consiga reconhecê-lo. Se o seu catálogo usa ICU, o que é ICU MessageFormat e onde ele quebra em apps gerados por IA cobre a sintaxe.
Arquivos de locale gerados. Esta regra existe porque agentes são prestativos. Se você pede para corrigir um erro de digitação em alemão, o agente abre locales/de.json e edita, o que funciona até a próxima tarefa de tradução regenerar o arquivo a partir da fonte. Avisar que os arquivos que não são o idioma-fonte são gerados, e onde a correção de verdade deve ser feita, evita uma classe de regressões silenciosas.
Editar en na mesma alteração. Sem isso, o agente adiciona t('checkout.summary.total') e segue em frente, e a chave aparece na tela com o próprio nome até alguém notar. A entrada no catálogo e o primeiro uso dela pertencem ao mesmo diff.
Onde cada ferramenta realmente lê o arquivo?
O mesmo conteúdo vai para lugares diferentes conforme o agente, e o local define se a regra fica sempre no contexto ou só é carregada quando importa.
AGENTS.md na raiz do repositório é o local compartilhado. O formato é Markdown puro, sem campos obrigatórios, mantido pela Agentic AI Foundation, sob a Linux Foundation, e lido por Cursor, Codex, pelo agente de código do Copilot e por uma longa lista de outros. A especificação aceita arquivos aninhados, e o mais próximo do arquivo editado prevalece.
O Cursor lê AGENTS.md diretamente, inclusive os aninhados, e também tem seu próprio formato com escopo. Uma regra com escopo para arquivos de UI fica fora do contexto até que um arquivo correspondente seja aberto:
---
description: i18n rules for user-visible strings
globs: src/**/*.tsx, src/**/*.jsx
alwaysApply: false
---
(paste the Internationalization section here)
O arquivo precisa terminar em .mdc e ficar em .cursor/rules/; um .md simples nessa pasta é ignorado porque não tem frontmatter.
O GitHub Copilot no VS Code tem três opções. .github/copilot-instructions.md vale para toda requisição de chat. Um arquivo .github/instructions/i18n.instructions.md com applyTo: "**/*.tsx,**/*.jsx" no frontmatter só vale quando esses arquivos são criados ou modificados. E o AGENTS.md na raiz do repositório é lido pelo agente Local quando a configuração chat.useAgentsMdFile está ativada; arquivos AGENTS.md aninhados em subpastas ficam atrás de uma configuração experimental à parte, chat.useNestedAgentsMdFiles, que vem desativada por padrão. O comportamento específico do Copilot está em como adicionar i18n a um app feito com GitHub Copilot.
O Claude Code lê CLAUDE.md e, desde a v2.1.277, lê por conta própria um AGENTS.md na raiz quando o projeto não tem um CLAUDE.md acima do diretório de trabalho. Se você mantém os dois, o padrão documentado é uma importação @AGENTS.md no topo do CLAUDE.md, para que o arquivo seja carregado uma única vez. Para definir escopo, um .claude/rules/i18n.md com uma lista paths: no frontmatter só é carregado quando o Claude lê um arquivo correspondente. O fluxo completo do Claude Code está em como localizar um app gerado pelo Claude Code.
O arranjo prático para um repositório em que vários agentes atuam: as dez regras em um arquivo com escopo para o agente que seu time mais usa e uma referência de duas linhas no AGENTS.md ("Strings de UI: veja as regras de i18n em .cursor/rules/i18n.mdc e siga-as"), para que qualquer agente sem suporte a escopo ainda receba a instrução.
Quais superfícies de código nunca leem o AGENTS.md?
As sugestões inline. Isso está documentado pelos dois fornecedores e é o motivo de o arquivo de regras não poder ser a resposta completa.
O FAQ de regras do Cursor responde "As regras afetam o Cursor Tab ou outros recursos de IA?" com um não direto. As regras alimentam o Agent; o Tab, o autocomplete que completa a linha que você está digitando, não as enxerga. A página de instruções personalizadas do VS Code traz a mesma observação para o Copilot: as instruções não são levadas em conta nas sugestões inline enquanto você digita no editor.
Ou seja, a superfície em que o dev digita <p>No results found</p> e aceita o ghost text é justamente a que as regras nunca alcançam. O chat e o modo agente vão criar a chave para a string; o autocomplete que terminou a linha enquanto você pensava em outra coisa, não. Ao longo de uma semana de trabalho normal, o código acumula literais vindos do único caminho que o arquivo de instruções não cobre, e o dev, que escreveu um arquivo de regras com todo o cuidado, conclui que o agente o está ignorando.
O Claude Code não tem superfície de autocomplete inline, mas a documentação dele faz o ponto equivalente pelo outro lado: o conteúdo do CLAUDE.md é contexto, não configuração imposta, e para bloquear uma ação independentemente do que o modelo decidir você usa um hook. Esse é o modelo mental correto para todas as ferramentas aqui. Arquivos de instruções mudam probabilidades. Eles não impõem nada.
O que realmente faz a regra valer?
Uma regra de lint no CI, e é uma adição de uma linha quando o arquivo de regras já manda o agente rodá-la. O eslint-plugin-i18next traz o no-literal-string; ative-o nos diretórios de componentes e um literal no JSX quebra o build em vez de receber um lembrete educado. A configuração, as opções da regra e por que instrução e imposição são duas camadas diferentes estão explicadas em por que o Cursor continua adicionando strings hardcoded depois da configuração do i18n, então este post não vai repetir isso.
Duas coisas que a camada de lint entrega e o arquivo de regras não. Ela pega a saída do autocomplete inline, porque roda sobre o arquivo e não sobre a conversa. E transforma o próprio loop do agente na correção: a última regra do bloco acima manda o agente rodar o lint antes de terminar, e um agente que vê o no-literal-string falhar vai criar a chave da string sozinho na mesma sessão.
No caso específico do Claude Code, um hook PreToolUse ou de pós-edição que roda o linter no arquivo recém-escrito é o mecanismo de imposição para o qual a documentação aponta. Hooks rodam como comandos de shell em pontos fixos e valem independentemente de o modelo ter decidido seguir a regra.
O que acontece com as strings que ainda passam?
Elas precisam ser extraídas, ganhar chaves nos namespaces existentes e ser traduzidas, e é essa a parte que vale automatizar em vez de ficar repetindo prompts. Um build vermelho avisa que existe um literal. Alguém ainda precisa transformá-lo em chave, adicioná-lo ao en e levá-lo para todos os outros locales.
É nessa camada que o globalize.now atua. A conversão é feita uma única vez, dentro do app: você conecta o repositório e o código é convertido de uma vez, com o catálogo entregue em um pull request para você revisar. Depois disso, jobs de push traduzem as novas unidades do catálogo conforme elas chegam, então uma chave adicionada no PR de terça já tem as traduções no PR de quarta. A biblioteca de runtime, o formato do catálogo e o arquivo de regras acima são todos seus; a referência para desenvolvedores explica como os agentes no Cursor, no Claude Code, no Codex e no Copilot assumem o papel pós-configuração, e a integração com o Cursor é o passo a passo mais curto.
Dois modos de falha relacionados têm posts próprios. Se o problema é o agente reescrevendo um texto que já tinha chave, veja como impedir que agentes de IA reescrevam o texto da sua UI; a solução é a mesma disciplina de idioma-fonte da regra nove acima. Se os locales existem, mas continuam divergindo, por que arquivos de tradução saem de sincronia explica a falha de roteamento por trás disso.
Por onde você começa?
Cole o bloco, restrinja o escopo aos seus diretórios de UI e ative o no-literal-string no CI hoje mesmo. Depois observe o que a regra de lint pega ao longo de uma semana; essa é a medida de quanto o arquivo de regras ia conseguir fazer sozinho. Se você está lançando um app feito com IA e quer que a extração e a tradução de tudo o que ainda escapa sejam resolvidas para você, em vez de mantidas por conta própria, a página para vibe coders traz a visão geral e os preços ficam numa página à parte.
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