Arquitetura de internacionalização para apps criados por chat: defina chaves de string estáveis, regras de plural e um fluxo de tradução que se mantenha consistente na web e no mobile.

A primeira coisa que quebra não é o código. São as palavras.
Apps criados por chat frequentemente começam como um protótipo rápido: você digita “Adicione um botão que diga Salvar”, a UI aparece e você segue em frente. Semanas depois, você quer espanhol e alemão, e descobre que esses rótulos “temporários” estão espalhados por telas, componentes, e-mails e mensagens de erro.
Alterações de texto também acontecem com mais frequência que alterações de código. Nomes de produto são renomeados, textos legais mudam, onboarding é reescrito e suporte pede mensagens de erro mais claras. Se o texto vive diretamente no código da UI, cada pequena mudança vira um release arriscado, e você vai perder lugares onde a mesma ideia foi formulada de maneira diferente.
Aqui estão os sintomas iniciais que indicam que você está acumulando dívida de tradução:
Um exemplo realista: você constrói um CRM simples no Koder.ai. O app web diz “Deal stage”, o app mobile diz “Pipeline step” e um toast de erro diz “Invalid status”. Mesmo que os três estejam traduzidos, os usuários vão sentir que o app é inconsistente porque os conceitos não batem.
“Consistente” não significa “os mesmos caracteres em todo lugar”. Significa:
Quando você trata texto como dado do produto, não como decoração, adicionar idiomas deixa de ser um sufoco e vira parte rotineira do desenvolvimento.
Internationalization (i18n) é o trabalho para que um app suporte muitos idiomas sem reescritas. Localization (l10n) é o conteúdo real para uma língua e região específica, como francês (Canadá) com as palavras, formatos de data e tom corretos.
Um objetivo simples: todo texto voltado ao usuário deve ser selecionado por uma chave estável, não digitado diretamente no código da UI. Se você consegue mudar uma frase sem abrir um componente React ou um widget Flutter, você está no caminho certo. Esse é o núcleo de uma arquitetura de internacionalização para apps criados por chat, onde é fácil enviar cópias hard-coded geradas durante uma sessão de chat.
Texto voltado ao usuário é mais amplo do que a maioria das equipes imagina. Inclui botões, rótulos, erros de validação, estados vazios, dicas de onboarding, push notifications, e-mails, exportações em PDF e qualquer mensagem que um usuário possa ver ou ouvir. Normalmente não inclui logs internos, nomes de colunas no banco, IDs de eventos de analytics, feature flags ou saída de debug apenas para administradores.
Onde devem ficar as traduções? Na prática, muitas vezes é no frontend e no backend, com uma fronteira clara.
O erro a evitar é misturar responsabilidades. Se o backend retorna frases em inglês prontas para erros de UI, o frontend não consegue localizar isso de forma limpa. Um padrão melhor é: o backend retorna um código de erro (e talvez parâmetros seguros), e o cliente mapeia esse código para uma mensagem localizada.
A responsabilidade pelo texto é uma decisão de produto, não um detalhe técnico. Decida cedo quem pode mudar palavras e aprovar o tom.
Se produto controla o texto, trate traduções como conteúdo: versionamento, revisão e um jeito seguro para produto pedir mudanças. Se engenharia controla o texto, crie a regra de que qualquer string nova da UI deve vir com uma chave e uma tradução padrão antes de ir para produção.
Exemplo: se seu fluxo de cadastro diz “Create account” em três telas diferentes, faça uma única chave usada em todos os lugares. Isso mantém o significado consistente, acelera os tradutores e evita que pequenas mudanças se transformem em uma limpeza em várias telas depois.
Chaves são o contrato entre sua UI e suas traduções. Se esse contrato ficar mudando, você terá textos faltando, correções apressadas e escrita inconsistente entre web e mobile. Uma boa arquitetura de i18n para apps criados por chat começa com uma regra: chaves devem descrever significado, não a frase em inglês atual.
Use IDs estáveis como chaves (ex.: billing.invoice.payNow) em vez da cópia completa (ex.: "Pay now"). Chaves que são frases se quebram no momento em que alguém ajusta a palavra, adiciona pontuação ou muda a capitalização.
Um padrão prático e legível é: tela (ou domínio) + componente + intenção. Mantenha sem graça e previsível.
Exemplos:
auth.login.titleauth.login.emailLabelbilling.checkout.payButtonnav.settingserrors.network.offlineDecida quando reusar uma chave ou criar uma nova perguntando: “O significado é idêntico em todo lugar?” Reuse chaves para ações verdadeiramente genéricas, mas crie chaves separadas quando o contexto muda. Por exemplo, “Save” em uma tela de perfil pode ser uma ação simples, enquanto “Save” em um editor complexo pode precisar de um tom diferente em alguns idiomas.
Mantenha textos de UI compartilhados em namespaces dedicados para evitar duplicação entre telas. Baldes comuns que funcionam bem:
common.actions.* (save, cancel, delete)common.status.* (loading, success)common.fields.* (search, password)errors.* (validation, network)nav.* (tabs, menu items)Quando a redação muda mas o significado permanece, mantenha a chave e atualize apenas os valores traduzidos. Esse é o ponto das IDs estáveis. Se o significado mudar (mesmo sutilmente), crie uma nova chave e deixe a antiga no lugar até confirmar que não é usada. Isso evita incompatibilidades “silenciosas” onde uma tradução antiga ainda existe, mas agora está errada.
Um pequeno exemplo ao estilo Koder.ai: seu chat gera um app React web e um app Flutter mobile. Se ambos usam common.actions.save, você terá traduções consistentes. Mas se o web usar profile.save e o mobile account.saveButton, você vai divergir com o tempo, mesmo que o inglês pareça o mesmo hoje.
Trate sua língua fonte (geralmente inglês) como a única fonte de verdade. Mantenha-a em um lugar só, revise como código e evite deixar strings aparecerem em componentes aleatórios “só por enquanto”. Essa é a forma mais rápida de evitar cópia hard-coded e retrabalho depois.
Uma regra simples ajuda: o app só pode mostrar texto vindo do sistema i18n. Se alguém precisa de novo texto, adiciona uma chave e uma mensagem padrão primeiro, então usa essa chave na UI. Isso mantém sua arquitetura de i18n estável mesmo quando features mudam de lugar.
Se você entrega web e mobile, quer um catálogo compartilhado de chaves, mais espaço para equipes de features trabalharem sem conflito. Uma organização prática:
Mantenha chaves idênticas entre plataformas, mesmo se a implementação for diferente (React na web, Flutter no mobile). Se você usa uma plataforma como Koder.ai para gerar ambos a partir do chat, exportar código-fonte é mais fácil quando ambos os projetos apontam para os mesmos nomes de chaves e mesmo formato de mensagem.
Traduções mudam com o tempo. Trate mudanças como mudanças de produto: pequenas, revisadas e rastreáveis. Uma boa revisão foca em significado e reuso, não só em ortografia.
Para evitar que chaves divirjam entre equipes, faça chaves de propriedade de features (billing., auth.) e nunca renomeie chaves só porque a redação mudou. Atualize a mensagem, mantenha a chave. Chaves são identificadores, não cópia.
Regras de plural mudam por idioma, então o padrão simples do inglês (1 vs todo o resto) falha rápido. Algumas línguas têm formas separadas para 0, 1, 2-4 e muitas outras têm mudanças mais complexas. Se você colocar a lógica de plural no UI com if-else, vai duplicar texto e perder casos borda.
Uma abordagem mais segura é manter uma mensagem flexível por ideia e deixar a camada i18n escolher a forma certa. Mensagens no estilo ICU existem para isso. Elas mantêm decisões gramaticais na tradução, não nos componentes.
Aqui vai um pequeno exemplo que cobre casos que as pessoas esquecem:
itemsCount = "{count, plural, =0 {No items} one {# item} other {# items}}"
Essa única chave cobre 0, 1 e todo o resto. Tradutores podem substituí-la pelas formas plurais corretas para o idioma sem você tocar no código.
Quando precisar de flexão por gênero ou papel, evite criar chaves separadas como welcome_male e welcome_female a menos que o produto realmente exija. Use select para manter a sentença como uma unidade:
welcomeUser = "{gender, select, female {Welcome, Ms. {name}} male {Welcome, Mr. {name}} other {Welcome, {name}}}"
Para não se encrencar com casos gramaticais, mantenha sentenças o mais completas possível. Não junte fragmentos como "{count} " + t('items') porque muitos idiomas não conseguem reordenar palavras assim. Prefira uma mensagem única que inclua número, substantivo e palavras ao redor.
Uma regra simples que funciona bem em apps criados por chat (incluindo projetos Koder.ai) é: se uma frase contém um número, uma pessoa ou um status, faça-a como ICU desde o primeiro dia. Custa um pouco mais no início e evita muita dívida de tradução depois.
Se seu app React web e seu app Flutter mobile mantêm arquivos de tradução próprios, eles vão divergir. O mesmo botão acaba com redação diferente, uma chave significa uma coisa na web e outra no mobile, e tickets de suporte começam a mencionar “o app diz X mas o site diz Y”.
A solução mais simples e importante: escolha um formato de fonte de verdade e trate-o como código. Para a maioria das equipes, isso significa um único conjunto compartilhado de arquivos de locale (por exemplo, JSON usando mensagens no estilo ICU) que web e mobile consomem. Quando você constrói apps via chat e geradores, isso importa ainda mais, porque é fácil criar novo texto em dois lugares por engano.
Uma configuração prática é um pequeno “pacote i18n” ou pasta que contém:
React e Flutter então viram consumidores. Eles não devem inventar chaves locais. Em um fluxo ao estilo Koder.ai (React web, Flutter mobile), você pode gerar ambos clientes a partir do mesmo conjunto de chaves e manter mudanças sob revisão como qualquer outra alteração de código.
Alinhamento com o backend faz parte da mesma história. Erros, notificações e e-mails não devem ser frases em inglês escritas à mão em Go. Em vez disso, retorne códigos de erro estáveis (como auth.invalid_password) mais parâmetros seguros. Então os clientes mapeiam códigos para texto traduzido. Para e-mails enviados pelo servidor, o servidor pode renderizar templates usando as mesmas chaves e arquivos de locale.
Crie um pequeno manual e faça cumprir na revisão de código:
Para prevenir chaves duplicadas com significados diferentes, adicione um campo “descrição” (ou um arquivo de comentários) para tradutores e para você no futuro. Exemplo: billing.trial_days_left deve explicar se aparece como banner, e-mail ou ambos. Essa única frase costuma evitar o reuso “mais ou menos” que cria dívida de tradução.
Essa consistência é a espinha dorsal de uma arquitetura de i18n para apps criados por chat: um vocabulário compartilhado, muitas superfícies e nenhuma surpresa quando você lança um novo idioma.
Uma boa arquitetura de i18n para apps criados por chat começa simples: um conjunto de chaves, uma fonte de verdade para a cópia e as mesmas regras para web e mobile. Se você constrói rápido (por exemplo, com Koder.ai), essa estrutura mantém a velocidade sem criar dívida de tradução.
Escolha seus locais cedo e decida o que acontece quando uma tradução estiver faltando. Uma escolha comum: mostrar o idioma preferido do usuário quando disponível, caso contrário cair para o inglês, e registrar chaves faltantes para corrigir antes do próximo release.
Então implemente:
billing.plan_name.pro ou auth.error.invalid_password. Use as mesmas chaves em todo lugar.t("key") nos componentes. No Flutter, use um wrapper de localização e chame a mesma busca por chave nos widgets. O objetivo é as mesmas chaves, não a mesma biblioteca.if (count === 1) espalhadas.Finalmente, teste com um idioma com palavras mais longas (alemão é clássico) e um com pontuação diferente. Isso revela rapidamente botões que estouram, títulos que quebram mal e layouts que assumem o comprimento do inglês.
Se você mantém traduções em uma pasta compartilhada (ou pacote gerado) e trata mudanças de texto como mudanças de código, seus apps web e mobile permanecem consistentes mesmo quando features são construídas rapidamente em chat.
Strings traduzidas da UI são só metade do problema. A maioria dos apps também mostra valores mutáveis como datas, preços, contagens e nomes. Se você tratar esses valores como texto simples, terá formatos estranhos, fusos horários errados e frases que soam “esquisitas” em muitos idiomas.
Comece formatando números, moedas e datas pelas regras de locale, não com código customizado. Um usuário na França espera “1 234,50 €”, enquanto um nos EUA espera “$1,234.50”. O mesmo vale para datas: “03/04/2026” é ambíguo, mas formatação por locale deixa claro.
Fusos horários são a próxima armadilha. Servidores devem armazenar timestamps em uma forma neutra (normalmente UTC), mas usuários esperam ver horários no próprio fuso. Ex.: um pedido criado às 23:30 UTC pode ser “amanhã” para alguém em Tóquio. Decida uma regra por tela: mostrar hora local do usuário para eventos pessoais e um fuso fixo de negócio para janelas como retirada na loja (e rotule claramente).
Evite construir frases juntando fragmentos traduzidos. Isso quebra a gramática porque a ordem das palavras muda por idioma. Em vez de:
"{count} " + t("items") + " " + t("in_cart")
use uma única mensagem com placeholders, ex.: “{count} items in your cart”. O tradutor pode então reordenar palavras com segurança.
RTL não é só a direção do texto. O fluxo do layout inverte, alguns ícones precisam ser espelhados (como setas de voltar) e conteúdo misto (árabe com um código de produto em inglês) pode renderizar em ordem surpreendente. Teste telas reais, não só um rótulo, e garanta que seus componentes suportem mudança de direção.
Nunca traduza o que o usuário escreveu (nomes, endereços, tickets de suporte, mensagens de chat). Você pode traduzir rótulos ao redor e formatar metadados (datas, números), mas o conteúdo em si deve permanecer como está. Se você adicionar tradução automática depois, faça ser uma funcionalidade explícita com uma alternância clara “original/traduzido”.
Um exemplo prático: um app Koder.ai pode mostrar “{name} renewed on {date} for {amount}”. Mantenha como uma única mensagem, formate {date} e {amount} pelo locale e exiba no fuso do usuário. Esse padrão previne muita dívida de tradução.
Regras rápidas que previnem bugs:
Dívida de tradução geralmente começa como “só mais uma string rápida” e vira semanas de limpeza depois. Em projetos criados por chat, pode acontecer ainda mais rápido porque texto de UI é gerado dentro de componentes, formulários e até mensagens de backend.
Os problemas mais caros são os que se espalham pelo app e ficam difíceis de achar.
Imagine um app React web e um app Flutter mobile mostrando um banner de cobrança: “You have 1 free credit left”. Alguém ajusta o texto web para “You have one credit remaining” e deixa a chave sendo a própria frase inteira. O mobile ainda usa a chave antiga. Agora você tem duas chaves para um conceito, e tradutores veem ambas.
Um padrão melhor são chaves estáveis (ex.: billing.creditsRemaining) e pluralização com mensagens ICU para que a gramática fique correta em todos os idiomas. Se você usa uma ferramenta de vibe-coding como Koder.ai, adicione uma regra cedo: qualquer texto voltado ao usuário produzido no chat deve acabar em arquivos de tradução, não dentro de componentes ou erros de servidor. Esse pequeno hábito protege sua arquitetura de i18n conforme o projeto cresce.
Quando i18n parece bagunçado, geralmente é porque o básico nunca foi documentado. Uma checklist pequena e um exemplo concreto mantêm sua equipe (e você no futuro) longe da dívida de tradução.
Aqui vai uma checklist rápida para cada nova tela:
billing.invoice.paidStatus, não billing.greenLabel).Um exemplo simples: você vai lançar uma tela de cobrança em inglês, espanhol e japonês. A UI tem: “Invoice”, “Paid”, “Due in 3 days”, “1 payment method” / “2 payment methods” e um total como “$1,234.50”. Com uma arquitetura de i18n adequada, você define chaves uma vez (compartilhadas entre web e mobile) e cada idioma só preenche valores. “Due in {days} days” vira uma mensagem ICU, e formatação de dinheiro vem de um formatador sensível a locale, não de vírgulas hard-coded.
Implemente suporte a idiomas por feature, não como uma grande reescrita:
Documente duas coisas para que novas features permaneçam consistentes: suas regras de nomenclatura de chaves (com exemplos) e uma “definição de pronto” para strings (sem cópia hard-coded, ICU para plurais, formatação de datas/números, adicionado ao catálogo compartilhado).
Próximos passos: se você está construindo no Koder.ai, use o Modo de Planejamento para definir telas e chaves antes de gerar a UI. Depois use snapshots e rollback para iterar com segurança sobre cópia e traduções entre web e mobile sem arriscar um release quebrado.