Você pediu ao Claude Code para adicionar um estado de carregamento ao formulário de login. O estado de carregamento funciona. O botão também passou a dizer “Log in” em vez de “Sign in”, a mensagem de erro ficou mais simpática, e um dos títulos, de repente, está em caixa de frase. Ninguém pediu nada disso, todos os testes passam e, a menos que alguém leia o diff inteiro palavra por palavra, isso vai para produção.
Este post é sobre essa falha específica: texto já existente e aprovado sendo silenciosamente reescrito por um agente de codificação com IA, e o fluxo de trabalho que evita isso. Strings que os agentes criam já hardcoded e sem chave são outro problema, abordado em por que ferramentas de codificação com IA continuam adicionando strings hardcoded. Onde o texto do produto deve viver, de forma geral, é uma questão de arquitetura, abordada em texto de produto nativo do código. Este aqui foca no incidente — aquela segunda-feira de manhã em que o texto mudou e ninguém decidiu que deveria mudar. O globalize.now é um sistema de gestão de texto nativo do código, com localização integrada, e esse incidente é um dos motivos pelos quais ele existe.
Por que agentes de codificação com IA reescrevem o texto da interface?
Agentes de codificação com IA reescrevem o texto da interface porque enxergam prosa dentro dos componentes; uma vez que esse texto passa a viver atrás de chaves em um arquivo de idioma de origem, alterá-lo se torna uma edição explícita e revisável, em vez de um efeito colateral de outra tarefa qualquer.
Dentro de um componente, “Sign in” não tem nenhum status especial. É uma string literal em um atributo JSX, indistinguível de um className ou de um test id. O agente foi treinado em milhões de diffs nos quais melhorar código ao redor era um comportamento desejável, e, para um modelo, tornar um rótulo “mais claro” é o mesmo tipo de ação que renomear uma variável. Não há erro de tipo à espreita, nem teste falhando, nem anotação de responsável sobre as palavras. Reescrever não custa nada ao agente.
A mudança então sobrevive à revisão pelo mesmo motivo. Revisores leem lógica. Uma mudança de texto fica no meio de um diff que, legitimamente, trata de um estado de carregamento, compila, renderiza, e se um teste de snapshot reclama, a resposta de rotina é atualizar o snapshot — o que registra o novo texto como se tivesse sido aprovado.
Arquivos de regras como o AGENTS.md realmente impedem isso?
Arquivos de regras de agentes reduzem a frequência; eles não tornam o texto da interface intocável. É a arquitetura que torna a edição visível.
Tenha o arquivo de regras de qualquer forma — é a camada mais barata e funciona com frequência suficiente para valer trinta segundos. Este bloco funciona no AGENTS.md, no CLAUDE.md e nas regras do Cursor (.cursorrules ou .cursor/rules/):
## UI copy policy — do not modify user-facing text
- Never change user-visible strings (button labels, headings, empty states,
error messages, tooltips, placeholder text) unless the task explicitly
asks for a copy change.
- User-visible text lives in the source locale file (e.g. locales/en.json).
Reference it by key. Never inline a new user-facing string in a component.
- If a task needs new user-facing text, add a key to the source locale file,
reference it, and list the new key in your summary for copy review.
- Any diff to the source locale file is a copy change. Call it out
separately — it needs its own approval, apart from code review.
Agora, a parte honesta. Uma regra é contexto, e contexto compete por espaço. Em uma tarefa longa, a instrução é uma linha entre milhares de tokens de código, e às vezes ela perde — para uma compactação, para um subagente que nunca a viu, para um padrão forte no restante do arquivo. Pior: o agente que quebra a regra não avisa que quebrou. Você não recebe nenhuma notificação de que seu texto mudou; você recebe um diff no qual ele, por acaso, mudou. Uma convenção não é uma verificação.
Por isso o arquivo de regras é a primeira camada, e a camada em que você não deve confiar sozinha.
Como tornar uma edição de texto visível em vez de silenciosa?
Mova toda string visível ao usuário para fora dos componentes e coloque-a atrás de chaves em um arquivo de idioma de origem. A partir daí, um agente que queira mudar o seu texto precisa editar um arquivo cuja função inteira é o texto.
Essa é a etapa que o arquivo de regras não consegue cumprir, e o mecanismo é simples. Quando os componentes referenciam t('auth.signIn') em vez de reescrever “Sign in”, duas coisas mudam ao mesmo tempo. Os diffs de componentes deixam de conter texto, então o agente trabalhando no seu estado de carregamento não tem nada para reescrever ali onde está atuando. E toda mudança de texto, seja de agente ou de humano, cai em um único arquivo — onde uma linha alterada é o evento que importa, não ruído ao redor dele. Analisar o diff de um arquivo de idioma leva segundos. Reverter uma reescrita ruim é uma linha só.
Para ser preciso sobre o que isso traz: o agente ainda pode editar o arquivo de idioma. O ponto não é que as palavras se tornem intocáveis — é que mexer nelas deixa de ser um efeito colateral e passa a ser um ato legível e revisável. Essa é a diferença entre descobrir pelo diff e descobrir por um cliente.
O argumento completo para manter a fonte de verdade no código — em vez de em uma ferramenta de design que sincroniza com ele — é o pilar texto de produto nativo do código. Este post só precisa de uma consequência: texto com chaves é o que transforma o seu arquivo de regras de um pedido em algo verificável.
Como é o ponto de revisão na prática?
Trate qualquer diff no arquivo de idioma de origem como uma mudança de texto, e encaminhe-a para quem é responsável pelas palavras.
Na prática: coloque o diretório de locales no CODEOWNERS, para que um humano responsável pelo texto seja solicitado em qualquer mudança nele. Adicione uma linha ao template de PR — “este PR muda algum texto voltado ao usuário, e essa era a tarefa?”. E mantenha, no arquivo de regras, a exigência de que o agente liste as chaves alteradas no resumo dele; quando ele cumpre isso, a revisão é instantânea, e quando não cumpre, o hook do CODEOWNERS pega mesmo assim.
Em uma equipe pequena, isso é mais leve do que parece. O diff do arquivo de idioma é curto, se lê em linguagem simples, e “esse botão deveria dizer Log in agora?” é uma decisão que alguém pode tomar em cinco segundos — desde que a pergunta seja de fato colocada diante dessa pessoa, o que é justamente o trabalho do ponto de revisão.
E se o agente já reescreveu o seu texto?
Primeiro, restaure o texto aprovado; depois, coloque as strings em chaves para que a próxima reescrita apareça como um diff.
Encontrar o estrago é um exercício de git: compare os componentes afetados com o último commit em que você confia e leia apenas as strings literais. Restaure o que mudou. Se você não conseguir dizer com confiança qual era o texto aprovado — o botão diz “Log in”, a página de marketing diz “Sign in”, e nenhum registro indica qual está correto —, você esbarrou no problema mais profundo que alimenta esse incidente. Deriva de texto é quando o texto visível ao usuário se afasta da sua fonte aprovada entre superfícies e ao longo do tempo, sem uma única fonte de verdade da qual se afastar. Esse termo, suas causas e seus efeitos a jusante são tratados em uma peça própria: o que é deriva de texto.
Depois, coloque em chaves o que você restaurou, começando pelas strings que o agente tocou. Elas são, comprovadamente, as que estiveram na linha de fogo.
Onde a localização entra nisso?
Uma reescrita silenciosa é um incômodo em um idioma e um passivo em dez: toda tradução derivada do texto antigo agora está desatualizada, e nada avisa que é preciso retraduzir.
É aqui que o incidente do agente se agrava ainda mais. Publique o texto em inglês reescrito, e o seu alemão, japonês e espanhol continuam dizendo a coisa antiga — as superfícies agora divergem entre idiomas, não só entre telas. Catálogos que saem de sincronia são um modo de falha próprio, com soluções próprias, abordado em arquivos de tradução fora de sincronia.
O texto com chaves também fecha esse ciclo, porque uma string de origem alterada é um evento visível ao qual as traduções ficam vinculadas, em vez de um literal que se moveu silenciosamente. Esse é o design por trás do globalize.now: o texto vive no código, com fonte única atrás de chaves, e a localização é a mesma operação, não um complemento — conecte um repositório e todos os idiomas derivam dessa única fonte. Equipes que publicam com ferramentas de codificação com IA são as primeiras a esbarrar nisso, por isso escrevemos para desenvolvedores e para pessoas que constroem com ferramentas de IA.
Da próxima vez que um agente estiver mexendo nos seus componentes, a questão é se um rótulo alterado vai aparecer como uma decisão ou como uma surpresa. O globalize.now centraliza suas strings visíveis ao usuário no repositório que você conecta, e deriva todos os idiomas a partir dessa única fonte.
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