O texto da interface se mantém consistente quando toda string visível ao usuário fica em um único catálogo no repo, é referenciada por chave e só muda por meio de um diff revisado. É a mesma disciplina que você já aplica à lógica, aplicada à redação. Todo o resto desta página é a prática que faz isso pegar.

Este é o guia operacional. A arquitetura por trás dele, e o argumento de por que a base de código deve ser a fonte da verdade para os textos do produto, estão em textos de produto nativos do código. Leia lá se quiser saber o porquê. Continue por aqui se quiser saber o como.

Onde o texto da interface deve ficar?

Em um arquivo de catálogo por idioma de origem, dentro do repositório, junto do código que o renderiza. Não em uma planilha, não em um arquivo de design, não em uma página de wiki que estava correta em março. O catálogo é o lugar onde desenvolvedor, revisor, tradutor e agente de codificação olham quando querem saber o que uma tela diz.

As regras práticas que decorrem disso:

  • Um catálogo, um locale de origem. Um único en.json (ou .po, .ftl, .strings, o que sua biblioteca de i18n ler) é o texto de referência. Se você tem vários apps em um monorepo, cada app ganha o seu, e as strings compartilhadas ganham um catálogo compartilhado que ambos importam. O que você evita são dois arquivos que possam conter a mesma mensagem.
  • Agrupe por tela ou funcionalidade, não por componente. Chaves como checkout.summary.total envelhecem melhor do que SummaryCard.totalLabel, porque componentes são renomeados e divididos, enquanto a tela e a mensagem permanecem.
  • Nomeie a chave pela mensagem, não pela redação. checkout.submit sobrevive à troca de "Fazer pedido" para "Comprar agora". checkout.placeOrderButton não sobrevive, e a divergência entre chave e texto é o que leva um revisor, mais tarde, a achar que são duas strings diferentes.
  • Mantenha o catálogo no mesmo pull request do código que o usa. Uma string adicionada em um PR e o componente em outro é uma string que será perdida, esquecida ou duplicada.

Se as suas strings ainda são literais dentro dos componentes, o catálogo é a primeira coisa a criar. Fazer isso à mão é um projeto à parte, e é por isso que vale conectar o repositório e deixar a conversão fazer o trabalho, assunto tratado mais abaixo.

Por que referenciar por chave é melhor do que buscar o literal?

Porque uma chave só resolve para uma string, e um literal pode ser digitado de tantas formas quanto há desenvolvedores. "Entrar", "ENTRAR", "Fazer login" e "Login" são quatro strings para uma ferramenta de busca e uma única intenção para o usuário. Quando todo componente chama t("auth.signIn"), a pergunta sobre qual redação está correta é respondida uma vez, em um só lugar, e a resposta vale em todo lugar onde a chave é usada.

Dois hábitos mantêm isso funcionando:

  • Reaproveite antes de adicionar. Antes de criar uma chave, procure a mensagem no catálogo. Se common.cancel já existe, uma nova dialog.cancelButton com o mesmo texto é o começo da divergência, não uma duplicata inofensiva. As duas serão editadas de forma independente assim que alguém mexer em uma delas.
  • Não concatene. t("cart.items") + " " + count produz um texto que nenhuma linha do catálogo descreve. Use a interpolação e as formas de plural da sua biblioteca para que a frase inteira seja uma única unidade revisável.

A exceção a que todo mundo recorre é "essa string só aparece uma vez, então um literal não tem problema". Ela aparece uma vez hoje. O componente vai ser copiado, a tela vai ganhar uma tela irmã, e o literal vai junto e depois diverge.

Como você revisa mudanças de texto em um pull request?

Trate uma linha alterada do catálogo como você trata uma assinatura de função alterada: algo que o revisor lê, questiona e aprova. Essa é a prática que transforma a consistência de um problema de memória em um problema de revisão, e a revisão é o único momento em que alguém está, de forma confiável, prestando atenção.

O que faz a revisão do catálogo funcionar:

  • Uma mudança de redação é uma linha própria no diff. Isso só vale se a string estiver no catálogo. Uma mudança de redação dentro de um componente também aparece como linha no diff, mas se esconde entre mudanças de lógica e é ignorada como "só texto".
  • Direcione as mudanças do catálogo a um responsável pelo texto. Uma entrada CODEOWNERS no caminho do catálogo faz com que quem é dono da redação, seja uma pessoa de produto, um content designer ou o desenvolvedor que mais se importa, seja solicitado em toda mudança nele. O restante do PR segue a revisão normal.
  • Faça três perguntas por linha alterada. Este é o termo aprovado para esta coisa? Combina com as strings ao redor em tom e capitalização? O nome da chave ainda descreve a mensagem? Um revisor que faz essas três perguntas pega a maior parte do que um guia de estilo existe para prevenir.
  • Rejeite renomeações silenciosas. Um PR que muda auth.signIn de "Entrar" para "Fazer login" sem explicação é uma decisão de texto tomada por quem por acaso estava editando. Ele precisa de uma frase na descrição do PR dizendo o porquê, ou precisa ser revertido.

O modo de falha que isso previne tem nome. Copy drift é o texto visível ao usuário divergindo da sua fonte aprovada entre telas e ao longo do tempo, sem uma única fonte da verdade da qual divergir. O que é copy drift trata das causas; a revisão do catálogo no diff é a prática que barra a maioria delas.

Como impedir a entrada de novas strings hardcoded?

Faça um novo literal quebrar o build. Regras e revisões pegam o que as pessoas lembram de procurar. Uma regra de lint pega o resto, em todo pull request, sem que ninguém precise lembrar.

A configuração é pequena:

  • Ative uma regra no-literal-string. eslint-plugin-i18next traz uma para projetos JavaScript e TypeScript. Restrinja-a a textos JSX e a atributos voltados ao usuário, como dicas de input, title, aria-label e alt, e marque arquivos de teste e stories do Storybook como isentos para que a regra continue com credibilidade.
  • Rode onde os merges acontecem. Um hook de pre-commit é conveniente; uma etapa de CI é a que conta, porque roda no pull request independentemente de o editor do autor estar configurado.
  • Adicione uma verificação de chaves não usadas e ausentes. A maioria das bibliotecas de i18n tem uma ferramenta auxiliar que lista chaves referenciadas no código mas ausentes do catálogo, e chaves do catálogo que não são referenciadas em lugar nenhum. Rode-a no CI também. Chaves não usadas são onde a redação desatualizada se esconde.
  • Comece rígido e permita exceções por comentário. Uma regra desativada para a pasta components/ inteira não é uma regra. Uma regra desativada em uma linha, com justificativa, é documentação.

Times que desenvolvem com ferramentas de codificação com IA sofrem mais com isso, porque as ferramentas regeneram componentes a partir do contexto local e trazem os literais de volta junto. Por que o Cursor AI continua adicionando strings hardcoded detalha a regra de lint, incluindo a configuração em três camadas: arquivo de regras, linter e CI.

O que deve entrar em um guia de estilo de texto para desenvolvedores?

Um arquivo curto no repositório, que caiba em uma tela, defina a voz, fixe os termos e diga onde as strings entram. Guias de estilo longos vivem em uma wiki e são lidos uma vez. Um curto vive junto do código e é lido por pessoas e ferramentas toda vez que uma string é adicionada.

Um COPY.md funcional (ou uma seção no seu guia de contribuição existente) cobre:

  1. Voz, em três frases. Com quem o produto se parece ao falar, quão formal é, se diz "você" ou "o usuário". O suficiente para encerrar a maioria das discussões.
  2. Termos que nunca variam. O nome que o produto dá a cada funcionalidade, os verbos das ações principais (é "Salvar" ou "Atualizar"? "Excluir" ou "Remover"?) e as palavras que você decidiu não usar.
  3. Regras de capitalização e pontuação. Maiúscula só na primeira palavra ou em todas as palavras em botões e títulos, ponto final em tooltips ou não, como números e datas são escritos.
  4. Onde as strings entram e como as chaves são nomeadas. O caminho do catálogo, a regra de agrupamento, a regra de nomenclatura de chaves e "busque antes de adicionar".
  5. O que fazer na dúvida. Quem consultar, ou qual chave reaproveitar enquanto isso.

Mantenha-o no repositório para que um arquivo de regras possa apontar para ele, um revisor possa linkar para ele em um comentário e um agente de codificação possa lê-lo antes de escrever um rótulo.

Como evitar que agentes de codificação com IA quebrem a consistência?

Dê ao agente uma fonte da verdade para ler e uma barreira que o detecte quando ele não a seguir. Um agente gerando um formulário escreve "Enviar" em uma tela e "Submeter" na seguinte, porque cada uma era a palavra localmente mais provável. Ele não está sendo descuidado; simplesmente não tem nada com que ser consistente.

Dois movimentos resolvem a maior parte. Primeiro, um arquivo de regras (AGENTS.md, .cursor/rules, CLAUDE.md, o que suas ferramentas lerem) dizendo: texto visível ao usuário vai no catálogo, reaproveite chaves existentes e leia COPY.md antes de escrever um rótulo. Segundo, a regra de lint da seção anterior, porque um arquivo de regras reduz o chute e uma regra de lint pega os chutes que passaram.

Isso é tudo o que esta página tem a dizer sobre agentes. O problema específico de um agente reescrevendo strings que já existiam, e como tornar essa edição visível em vez de silenciosa, é um tema à parte: como impedir agentes de codificação com IA de reescrever o texto da sua interface.

Onde a localização entra nisso?

Consistência e localização dependem da mesma coisa, uma fonte acordada, então o catálogo que dá uma dá a outra. Um time que não sabe dizer qual de três redações é a aprovada também não consegue traduzir nenhuma delas corretamente. Corrija a fonte e a tradução passa a ser um artefato derivado do catálogo, em vez de um projeto separado com a sua própria cópia da verdade.

Na prática, isso significa que o catálogo que você montou para a consistência já é o arquivo a partir do qual um tradutor ou uma ferramenta de tradução trabalha. Cada chave ganha seus equivalentes em cada idioma de destino, os mesmos nomes de chave valem e uma mudança de redação no catálogo de origem fica visível como uma alteração que as traduções precisam acompanhar. Quando essas traduções são deixadas para trás em relação à origem, surge a classe de problema descrita em por que os arquivos de tradução saem de sincronia, e a solução é a mesma disciplina de novo: um catálogo, chaves, mudanças no diff.

Onde a globalize.now se encaixa

A globalize.now é um sistema de gestão de textos nativo do código com localização embutida, e cada prática desta página é o que ela pressupõe sobre o seu repositório. Você conecta um repositório do GitHub ou GitLab no app. Um scan identifica o que existe. Se as strings são literais dentro dos componentes, uma conversão única as move para trás de chaves em um catálogo e adiciona a configuração de i18n, caso não haja nenhuma. Se você já usa next-intl, react-i18next, i18next ou Lingui, ela trabalha com o que você tem. O resultado chega como um pull request em uma branch própria, então o primeiro catálogo passa exatamente pela revisão descrita acima. Depois disso, as novas strings de origem que você adiciona são traduzidas por push jobs que abrem um pull request com o resultado. O repositório continua sendo a única fonte da verdade.

Estamos construindo para que a mesma disciplina valha no site institucional, na landing page e no produto, a partir de uma única fonte, de modo que uma funcionalidade seja descrita da mesma forma em todo lugar onde o usuário a encontra. É para lá que estamos indo, não algo para contratar hoje. Os preços estão na página de preços.

Um checklist que você pode adotar esta semana

  1. Crie um catálogo por idioma de origem, no repositório, e defina a regra de nomenclatura de chaves.
  2. Mova para ele as strings das telas que você mais mexe; referencie por chave.
  3. Ative uma regra de lint no-literal-string, restrita ao texto voltado ao usuário, e rode-a no CI.
  4. Adicione uma linha CODEOWNERS no caminho do catálogo.
  5. Escreva COPY.md: voz, termos fixos, regras de capitalização, onde as strings entram. Uma tela.
  6. Aponte o arquivo de regras do seu agente para o catálogo e para COPY.md.
  7. Rejeite, no code review, qualquer mudança de texto que não tenha uma justificativa na descrição do PR.

Se o segundo passo é o que parece dar um mês de trabalho, conecte um repositório, leia o plano que ele propõe e revise o pull request que ele abre.

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