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.totalenvelhecem melhor do queSummaryCard.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.submitsobrevive à troca de "Fazer pedido" para "Comprar agora".checkout.placeOrderButtonnã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.canceljá existe, uma novadialog.cancelButtoncom 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") + " " + countproduz 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
CODEOWNERSno 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.signInde "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-i18nexttraz uma para projetos JavaScript e TypeScript. Restrinja-a a textos JSX e a atributos voltados ao usuário, como dicas de input,title,aria-labelealt, 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:
- 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.
- 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.
- 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.
- 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".
- 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
- Crie um catálogo por idioma de origem, no repositório, e defina a regra de nomenclatura de chaves.
- Mova para ele as strings das telas que você mais mexe; referencie por chave.
- Ative uma regra de lint no-literal-string, restrita ao texto voltado ao usuário, e rode-a no CI.
- Adicione uma linha
CODEOWNERSno caminho do catálogo. - Escreva
COPY.md: voz, termos fixos, regras de capitalização, onde as strings entram. Uma tela. - Aponte o arquivo de regras do seu agente para o catálogo e para
COPY.md. - 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