i18n a solo: 1993 chaves, 6 idiomas, 9 passes LLM
Pontos-chave a reter: Internacionalizar uma aplicação móvel com 1993 chaves de tradução em 6 idiomas é viável sozinho graças à automação LLM. O script npm run translate deteta o delta, traduz apenas as novas chaves via OpenRouter e mantém a sincronização em tempo quase real por alguns cêntimos por passagem. Resultado: um canal de aquisição multiplicado por 6 sem esforço humano contínuo.
TAMSIV nasceu em francês. Cada string, cada mensagem de erro, cada placeholder — tudo estava codificado diretamente no JSX. Quando decidi suportar 6 idiomas (FR, EN, DE, ES, IT, PT), não imaginava que isso representaria 1993 chaves de tradução distribuídas por 35 ficheiros. E muito menos que precisaria de 9 passagens sucessivas para chegar a um resultado estável.
Aqui está o relato completo desta aventura i18n, desde o primeiro t('key') até ao pipeline automatizado que funciona hoje.
Porquê internacionalizar uma aplicação indie a solo?
A resposta curta: aquisição de utilizadores. Uma aplicação disponível apenas em francês priva-se de 95% do mercado global. Mesmo na Europa, limitar a sua audiência ao francês significa ignorar a Alemanha (83M de habitantes), Espanha (47M), Itália (60M), Portugal (10M) e, claro, todo o mundo anglófono.
Mas para além do mercado, há uma razão técnica: as lojas (Google Play, App Store) indexam os metadados em cada idioma. Uma ficha traduzida em 6 idiomas é 6 vezes mais superfície de descoberta orgânica. É uma alavanca recomendada pelo próprio Google.
Para um desenvolvedor solo como eu, é também uma vantagem competitiva face às grandes aplicações que negligenciam a localização para além do inglês. Se um utilizador alemão procurar "Aufgaben-App mit Spracheingabe" e TAMSIV for a única a ter uma ficha em alemão, está ganho.
Como extrair 1993 chaves de tradução sem perder a cabeça?
Primeira etapa: substituir cada string francesa codificada por uma chamada t('key'). Em 35 ficheiros. À mão. Não existe um atalho fiável para esta etapa — cada string tem um contexto, e o nome da chave deve refletir esse contexto.
Estruturei as chaves por ecrã e por secção:
feed.empty_state— a mensagem quando o feed está vazioagenda.filter.participating— o filtro "Participa" na agendaprofile.settings.language— o seletor de idioma nas definiçõesdictaphone.recording.title— o título do ecrã de gravaçãogroups.hierarchy.depth_limit— a mensagem de limite de profundidade dos grupos
Esta convenção ecrã.secção.elemento é crucial. Sem ela, rapidamente nos encontramos com chaves do tipo button_text_3 que não significam nada três meses depois. Segui as recomendações de i18next sobre a nomenclatura hierárquica.
Conselho prático: comecei pelos ecrãs mais visitados (Dictaphone, Feed, Agenda) antes de abordar as definições e os ecrãs secundários. Isso permite testar a tradução nos percursos principais rapidamente.
Que ferramenta de tradução automática escolher quando se está sozinho?
Traduzir 1993 chaves manualmente em 5 idiomas? Quase 10 000 traduções individuais. Impossível para um desenvolvedor solo. Os serviços clássicos como Google Translate API ou DeepL carecem de contexto de aplicação — "Memo vocal" tornar-se-ia "Vocal memo" em vez de "Voice memo".
Escrevi um script npm run translate que envia as chaves francesas para OpenRouter (Gemini 2.5 Flash como modelo principal). O LLM compreende o contexto da aplicação e produz traduções naturais:
- "Memo vocal" → "Voice memo" (EN), "Sprachnotiz" (DE), "Nota de voz" (ES)
- "Ajouter une tache" → "Add a task", "Aufgabe hinzufugen", "Agregar una tarea"
- "Tout voir" → "See all", "Alle anzeigen", "Ver todo"
O script envia as chaves em lotes de 50, com o contexto da aplicação no system prompt: "Tu traduzes as strings de uma aplicação móvel de gestão de tarefas por voz com IA." Este contexto faz toda a diferença na qualidade.
Porquê 9 passagens e não apenas uma?
A primeira passagem traduziu a maior parte do corpus — cerca de 1600 chaves. Mas uma aplicação viva evolui. Cada nova funcionalidade adiciona chaves:
- Passagem 2: gamificação — 47 novas chaves (níveis, emblemas, sequências)
- Passagem 3: agenda — 62 chaves (eventos, filtros, recorrência)
- Passagem 4: grupos colaborativos — 89 chaves (hierarquia, permissões, atribuições)
- Passagem 5-7: correções contextuais e ajustes após feedback dos utilizadores
- Passagem 8: onboarding e ecrãs de primeira utilização
- Passagem 9: website (tamsiv.com) — um segundo corpus de tradução separado
O script deteta as chaves em falta comparando fr.json (a referência) com cada ficheiro alvo. Ele retraduz apenas o delta — as chaves novas ou modificadas. Sem desperdício de tokens, sem risco de sobrescrever uma tradução já validada.
Como funciona o pipeline de tradução concretamente?
O pipeline completo segue 4 etapas:
- Diff: o script compara
fr.jsoncomen.json,de.json,es.json,it.json,pt.json. Cada chave presente em FR mas ausente num alvo é marcada como "a traduzir". - Lote: as chaves a traduzir são agrupadas em pacotes de 50 (para permanecer dentro dos limites de tokens do LLM).
- Tradução: cada lote é enviado para OpenRouter com um prompt estruturado. O LLM retorna um JSON válido com as traduções.
- Merge: as traduções são fundidas nos ficheiros alvo, preservando a ordem alfabética e a estrutura existente.
O custo? Cerca de 0.02 a 0.05 EUR por passagem com Gemini 2.5 Flash via OpenRouter. Para 5 idiomas. É quase gratuito comparado com os serviços de tradução profissional (que cobram 0.10-0.20 EUR por palavra).
Como gerir a deteção automática de idioma no primeiro lançamento?
No primeiro lançamento, TAMSIV deteta o idioma do sistema via getLocales() do React Native. Se o idioma for suportado (FR, EN, DE, ES, IT, PT), é usado diretamente. Caso contrário, fallback para o inglês.
A escolha é então guardada na base de dados (Supabase) no perfil do utilizador. Isso permite recuperar a preferência mesmo ao mudar de telefone. E evita o problema clássico: o utilizador muda o idioma do seu SO por uma razão X, e todas as suas aplicações mudam sem aviso.
Também adicionei um seletor de idioma nas definições, para que o utilizador possa escolher independentemente do idioma do sistema. É um detalhe, mas faz a diferença para bilingues ou expatriados.
Quais são as armadilhas da tradução automática por LLM?
A tradução por LLM não é perfeita. Aqui estão as armadilhas que encontrei:
- Os falsos amigos: "Classeur" traduzido como "Binder" em vez de "Folder" — o LLM carecia de contexto de aplicação no início.
- O género gramatical: em alemão, "die Aufgabe" (feminino) vs "der Memo" (masculino) — os artigos devem corresponder.
- Os plurais: algumas línguas têm regras de plural complexas (português, alemão). É preciso fornecer as formas singular/plural explicitamente.
- O comprimento: o alemão produz palavras 30-50% mais longas que o francês. "Einstellungen" vs "Parametres". Isso quebra os layouts se o design UI não for flexível.
- Os caracteres especiais: as aspas variam consoante os idiomas (" " em inglês, « » em francês, „ “ em alemão).
A solução: um system prompt detalhado que especifica o domínio da aplicação, exemplos de traduções validadas em few-shot, e uma passagem de revisão automática onde o LLM verifica a coerência das suas próprias traduções.
Como manter a sincronização a longo prazo?
O mais difícil não é a tradução inicial — é a manutenção. Cada modificação em francês deve ser propagada. Se eu mudar "Ajouter une tache" para "Creer une tache", os outros 5 idiomas devem seguir.
O meu sistema de sincronização:
- Deteção da mudança: um hash SHA256 de cada valor FR é armazenado. Se o hash mudar, a chave é marcada para retradução.
- CI automático: via GitHub Actions, a cada push para
mainque modificafr.json, o script executa e cria um commit com as traduções atualizadas. - Verificação manual: eu revejo os diffs de tradução no PR para detetar erros óbvios.
6 idiomas mantidos em tempo quase real por alguns cêntimos por passagem. A relação esforço/impacto é imbatível.
Que impacto concreto na aquisição de utilizadores?
Os números falam por si. Após a internacionalização:
- Superfície de descoberta x6: a ficha da Play Store é indexada em 6 idiomas, o que multiplica as consultas de pesquisa cobertas.
- Taxa de conversão da loja: um utilizador que vê uma descrição no seu idioma tem 2 a 3 vezes mais chances de instalar.
- Retenção: a experiência in-app no idioma materno reduz drasticamente o churn nos primeiros 7 dias.
- SEO do website: o site tamsiv.com está disponível em 6 idiomas com URLs localizadas (
/fr/,/en/,/de/, etc.), o que impulsiona o referenciamento internacional.
Para um desenvolvedor solo, é a alavanca de aquisição com a melhor relação custo/eficácia. Sem orçamento de publicidade, sem growth hacking obscuro — apenas tradução automatizada inteligente.
Como aplicar esta abordagem ao teu próprio projeto?
Se tu desenvolves uma aplicação e hesitas em internacionalizar, aqui está o meu conselho: faz isso cedo. Quanto mais esperares, mais strings terás para extrair. Aqui estão as etapas chave:
- Estrutura as tuas chaves desde o início com uma convenção
ecrã.secção.elemento. - Escolhe os teus idiomas alvo em função do teu mercado. Para a Europa, FR/EN/DE/ES/IT/PT cobrem a maioria.
- Automatiza a tradução com um LLM via API (OpenRouter, OpenAI, etc.). O custo é negligenciável.
- Integra na tua CI para que a sincronização seja automática a cada push.
- Testa com utilizadores reais nativos, se possível — mesmo uma rápida olhada é melhor do que nada.
O percurso build in public de TAMSIV mostra que mesmo a solo, é possível atingir um nível de localização comparável às grandes equipas.
FAQ
Quanto tempo leva a internacionalização de uma aplicação React Native?
Para TAMSIV, a extração inicial das 1993 chaves levou cerca de 3 dias de trabalho concentrado. A configuração do script de tradução automática, meio dia. As passagens seguintes levam alguns minutos cada, graças à automação. A maior parte do trabalho está na extração — não na tradução.
OpenRouter é fiável para a tradução automática?
Sim, com as devidas precauções. O system prompt deve incluir o contexto da aplicação, exemplos de traduções validadas e instruções sobre o tom. Gemini 2.5 Flash via OpenRouter produz traduções de qualidade superior ao Google Translate para strings de interface, porque compreende o contexto. O fallback entre modelos garante a disponibilidade.
É preciso traduzir os conteúdos gerados pelo utilizador?
Não. As tarefas, memorandos e eventos permanecem no idioma do utilizador. Apenas a interface (botões, rótulos, mensagens do sistema, notificações) é traduzida. Traduzir o conteúdo do utilizador seria dispendioso e fonte de confusão.
Como gerir idiomas com alfabetos diferentes (árabe, japonês)?
TAMSIV concentra-se em idiomas latinos por enquanto. Idiomas RTL (árabe, hebraico) ou ideogramas (chinês, japonês) exigem adaptações de layout adicionais (direção do texto, fontes, espaçamento). Isso está previsto para uma fase posterior, mas cada alfabeto é um projeto à parte.
O script de tradução pode ser reutilizado noutro projeto?
Absolutamente. O script é genérico: ele recebe um ficheiro JSON de origem, uma lista de idiomas alvo e um endpoint LLM. Basta adaptar o system prompt ao contexto da tua aplicação. O padrão delta-only (traduzir apenas as novas chaves) funciona para qualquer projeto que utilize ficheiros JSON de tradução.