Steven
Steven9 min de leitura

Publicar trinta idiomas sem rede de segurança

A GeekBye v1.8.3 cabeou react-i18next e traduziu todo o app para 30 idiomas. A parte da biblioteca foi rotina. O interessante é o que faltava: sem checagem de paridade de chaves nem extrator de strings — então inglês hardcoded vazou, um erro de digitação em dinamarquês foi publicado e o changelog não conseguia concordar se o app falava 28, 29 ou 30 idiomas.

Engenharia
i18n
Desktop
Lançamentos da GeekBye
Publicar trinta idiomas sem rede de segurança

Alguns lançamentos são uma feature. A GeekBye v1.8.3 parece um — «o app agora fala 30 idiomas» — mas a feature era a parte fácil, e não é sobre isso que este post trata. Entre 17 e 19 de fevereiro de 2026, dois pull requests cabearam react-i18next no app, refatoraram cada string em inglês hardcoded em uma chamada de tradução e empacotaram trinta arquivos de idioma. Isso é trabalho de verdade, mas é o tipo de trabalho que uma biblioteca é projetada para tornar rotineiro. A engenharia interessante é tudo o que o lançamento não construiu — a rede de segurança que teria pego o que ele publicou quebrado.

O cabeamento era os 80% fáceis

O commit de infraestrutura adicionou react-i18next em cima do i18next e fez o convencional: um i18n/index.ts que chama initReactI18next, importa estaticamente os trinta JSONs de idioma e os registra sob um único objeto resources. Sem lazy loading — cada idioma vai no bundle. As strings de tradução vivem em src/i18n/locales/*.json como mapas planos com espaços de nome por pontos: sidebar.meetings, startMeeting.start, uma chave para uma string.

A migração do inglês hardcoded para t('key') foi feita da maneira honesta, grupo de componentes por grupo de componentes, e você pode ler a varredura no log de commits: primeiro sidebar, navegação e chat; depois configurações, login e onboarding; depois reuniões, dashboard, toasts e tooltips da navbar. Cada commit substituiu um lote de strings literais por chamadas de tradução. Vinte e nove arquivos não ingleses aterrissaram em um único commit — uma queda de nove mil linhas que traduziu a fonte em inglês para tudo, do búlgaro ao vietnamita.

Nada disso é difícil de acertar, porque errar é barulhento: um provider faltando crasha, um JSON malformado não parseia, um import errado quebra a build. A biblioteca te dá grades de proteção para os erros mecânicos. O que ela não te dá é uma grade para o erro que de fato aconteceu.

Três ajustes de idioma, e por que isso está correto

Antes de chegarmos ao que quebrou, uma coisa que a v1.8.3 acertou exatamente, porque é uma distinção que muitos apps achatam e não deveriam. Depois deste lançamento, a GeekBye tem três ajustes de idioma independentes:

  • Idioma de resposta da IA (output_language) — em que o assistente responde. O mais antigo dos três, de novembro de 2025.
  • Idioma de transcrição (transcriptLanguage) — como o motor de voz transcreve o seu microfone. Ligado alguns lançamentos antes, na v1.7.6.
  • Idioma do app (app_language) — o idioma dos botões, dos menus e do onboarding. Novo na v1.8.3.

São eixos genuinamente distintos. Um desenvolvedor em Berlim poderia querer uma interface em alemão, respostas da IA em inglês (porque essa é a língua da documentação que ele está colando) e transcrição em inglês (porque a reunião é em inglês). Colapsar isso em um único ajuste de «idioma» seria errado para essa pessoa, e o passo de onboarding adicionado neste lançamento apresenta os três como dropdowns separados exatamente por essa razão.

O toque bacana é o caminho de atualização. Uma migração de uma única vez copia o output_language existente de cada usuário para a nova chave app_language no primeiro início após atualizar, e o provider resolve o idioma por uma cadeia de fallback — app_language, depois output_language, depois 'en'. Assim, os usuários que já tinham dito ao app que queriam respostas da IA em espanhol acordaram com uma interface em espanhol, em vez de serem resetados para inglês e obrigados a caçar o ajuste. Semear uma nova preferência a partir de uma antiga relacionada é uma pequena gentileza que custa uma migração e poupa a cada usuário que atualiza um pequeno aborrecimento.

O re-render de onboarding que não queria

Há um bug genuíno que vale a pena tirar do trabalho de i18n, porque é uma armadilha de React em que qualquer um que faça troca de idioma em tempo de execução vai esbarrar. O assistente de onboarding constrói algumas de suas listas de opções dentro de um useMemo. Quando você muda o seu idioma no próprio passo de idioma, a UI deveria se redesenhar no novo idioma imediatamente — e não fazia, porque t não estava no array de dependências do memo. A função de tradução muda de identidade quando o idioma muda; se o seu valor memoizado é construído a partir de t mas não depende dele, o React te entrega feliz da vida a versão velha, a do idioma anterior. A correção aparece duas vezes no log — uma para o assistente, outra para a lista dropdown das configurações — o que te diz que é o tipo de erro que você comete em todo lugar onde memoiza conteúdo traduzido até aprender a sempre listar t.

O onboarding também carregava uma dobra que o changelog não menciona: como este é um codebase white-label, há duas árvores de onboarding para traduzir — um assistente da GeekBye e um da Pavleur — vivendo sob os espaços de nome de chave paralelos onboarding.* e onboarding.pavleur.*. Cada string de onboarding foi traduzida duas vezes, em duas vozes de marca, através dos vinte e nove idiomas.

O que a build não sabia que estava faltando

Aqui está a parte que torna isto uma lição em vez de uma checklist. A v1.8.3 não tinha jeito automático de extrair strings hardcoded nem checagem na CI de paridade de chaves entre idiomas. Então a definição de «pronto» era «alguém visitou os componentes de que lembrava e envolveu as strings que viu». Essa definição faz água.

Duas semanas após o lançamento, um commit de limpeza aterrissou com a mensagem «translate all hardcoded English strings across 10 components into 30 languages». Dez componentes — o formulário de reporte de problemas, as abas de resumo e métricas, a aba de relatório, o conteúdo de transcrição e chat, o aviso de limite de uso, as mensagens de chat, o botão de copiar código, os botões de ação flutuantes — tinham sido publicados na v1.8.3 ainda falando inglês para todo mundo, em todos os idiomas. A build de ninguém falhou. Nenhum teste ficou vermelho. Um usuário alemão simplesmente viu inglês na aba de relatório, e o único detector foi um humano que acabou olhando.

A mesma ausência produziu os constrangimentos menores. Uma tradução para o dinamarquês foi publicada com a palavra errada — Luk application helt em vez de Luk applikationen helt — e foi corrigida num lançamento posterior, porque não havia nem filtro de revisor nativo nem jeito de o pipeline saber. As chaves de «fechar app» faltavam no lote inicial e foram preenchidas dias depois. E cada feature publicada depois — um preset de tecla modificadora, mutar o microfone, chips de recap, opacidade do overlay — arrastou atrás de si o seu próprio commit de «traduzir as novas chaves para 29 idiomas», para sempre, porque nada impunha o invariante automaticamente.

Você pode até medir a desordem nas mensagens de commit. Os mesmos trinta arquivos de idioma são descritos, lançamento após lançamento, como «30 idiomas», «29 idiomas» e — em um commit — «28 locales». Nada foi adicionado ou removido; há trinta arquivos em cada tag da v1.8.3 até a v2. O número deriva porque não havia uma única fonte de verdade para «quantos idiomas nós publicamos», então cada autor de commit contava na mão, às vezes incluindo o inglês como um idioma e às vezes como a fonte. Um changelog que não consegue manter firme a própria contagem é um sinal pequeno e honesto de que a coisa que ele descreve não está sendo imposta por uma máquina.

A mina da pluralização

Mais uma coisa que vale sinalizar, não porque quebrou barulhentamente mas porque é o tipo de bug que esse arranjo convida e sobre o qual não vai te avisar. A pluralização nesses arquivos de idioma é feita à mão, com chaves emparelhadas: meetings.meetingCount e meetings.meetingCountPlural, profiles.filesAttached e filesAttachedPlural. É uma divisão singular/plural limpa do inglês. Também é errada para uma parte dos idiomas que o app agora afirma suportar. O russo, o polonês e o tcheco têm três ou quatro formas plurais dependendo do número — «1 arquivo», «2 arquivos», «5 arquivos» e «21 arquivos» podem levar terminações diferentes. Um esquema de duas chaves singular/plural não consegue expressar isso, então essas contagens são renderizadas gramaticalmente tortas. O i18next tem maquinário plural de verdade para exatamente isto; os pares manuais o contornam, e nenhum commit reconhece a lacuna. É o equivalente, na pluralização, ao vazamento das strings hardcoded: invisível para a build, visível só para um falante nativo.

Três coisas que este lançamento nos ensinou

  1. A biblioteca são os 80% fáceis; a ferramenta são os 20% difíceis. Cabear react-i18next e traduzir strings em massa é um problema resolvido. Manter trinta arquivos em paridade para sempre não é — e isso precisa de um extrator de strings hardcoded e de uma checagem na CI de chaves faltantes, nenhum dos quais uma biblioteca te entrega. Construa-os antes de proclamar «i18n completa», ou você terá proclamado um invariante que não tem como sustentar.
  2. Separe os ajustes de idioma que de fato são diferentes. Idioma da interface, idioma de transcrição e idioma de resposta da IA são três eixos, e a v1.8.3 acertou ao dar a cada um a sua própria chave — e acertou mais ainda ao semear o novo a partir de um antigo relacionado via migração, de modo que atualizar não custou nada aos usuários.
  3. A contagem do seu changelog é uma métrica de disciplina. Quando o mesmo conjunto de arquivos é 28, 29 e 30 idiomas ao longo de três commits, o número não é o problema — a fonte de verdade que falta é. Se uma máquina não está contando os seus idiomas, ninguém os está contando igual duas vezes.

Para o capítulo anterior da história v1, redesenhar o dashboard em torno de um gráfico de onda (v1.8.2); e para todo o arco, a anatomia de entregar software até a perfeição.