Início
Migrar para o Verbaly
Sair de outra biblioteca de i18n é sobretudo mecânico, e não é preciso fazê-lo de uma vez: os dois runtimes convivem enquanto você vai arquivo a arquivo. As três seções a seguir à primeira valem venha de onde vier; a primeira é para o i18next, que é de onde chega quase todo mundo.
Você vem do i18next: o que equivale a quê
O modelo mental se transfere um a um, e a única coisa que muda é a sintaxe:
| i18next | Verbaly |
|---|---|
i18next.init({ … }) | npx verbaly init + um plugin de build |
t('inbox.title') | t('inbox.title'): a mesma chamada, com as keys já tipadas |
{{name}} | {name} |
messages_one / messages_other | {count | one: … | other: # …}: opcional, as keys com sufixo continuam funcionando tal como estão |
format: 'currency' (interpolação) | {price:currency/EUR} (Intl nativo) |
i18next-browser-languagedetector | resolveLocale() + persistLocale() |
i18next-http-backend (lazy load) | per-locale code-splitting, built into the plugin |
useTranslation() (react-i18next) | useT() (@verbaly/react) |
| Trans component | Trans component: values / components map over |
i18next.changeLanguage('es') | await setLocale('es') |
saveMissing, runtime fallbacks | npx verbaly check: CI fails instead |
Fique com os seus catálogos: eles entram tal como estão
Os seus catálogos entram sem mudar de forma. Os grupos aninhados continuam aninhados, _one e _other continuam funcionando como as keys que são, e um namespace com dois pontos continua sendo uma key. Uma só coisa tem de mudar, e o migrate a faz: {{name}} passa a {name}, e uma chave que o i18next mostrava como texto é duplicada, porque uma chave duplicada é como o Verbaly escreve uma chave literal.
Primeiro reporta, reescreve só as chaves, e lista o que não vai adivinhar para que você decida. Isto é o que faz ao arquivo:
Duas melhorias que você pode deixar para depois, e nenhuma é precisa para funcionar. Juntar _one e _other numa só mensagem com variantes ({count | one: … | other: # …}) é o que te dá a seleção automática de plural, e o # pinta o número já localizado, sem um formattedCount à parte. A segunda é menor: os pontos são uma convenção de nomes, não um formato. Uma chave se lê igual se o seu arquivo a escreve como "inbox.title" ou a coloca como title dentro de um grupo inbox.
Troque o runtime
As keys existentes continuam funcionando via t('key', params), então os componentes migrados quase não mudam. Em React:
Se os seus componentes chamam um hook próprio, como um useTranslate em volta do useTranslation, mantenha-o e faça-o devolver useT(): nada do que o chama muda. Um tipo de chave construído a partir do seu JSON, como um NestedKeyOf, também lista os caminhos dos grupos, então transforme-o em VerbalyKey de virtual:verbaly. Para esperar o idioma do visitante antes da primeira pintura, como fazia um limite de Suspense, renderize dentro de setLocale(locale).then(…): um await de nível superior na sua entrada parte o bundle em muitos chunks.
O bundle nota. Uma app React com i18next e react-i18next embarca cerca de 23 KB de runtime; a mesma app com o Verbaly embarca cerca de 4. Sem framework são 13,6 contra 3,3. Os quatro números são gzip e estão medidos, não estimados.
Medido no Memos, um app de código aberto em 46 idiomas: 20 arquivos alterados à mão, o build e os testes como antes, e cerca de 11 KB a menos para cada visitante na primeira carga.
O texto novo pula a cerimônia de keys: escreva como tagged template e deixe o compilador extrair, ou mantenha ids legíveis com t.id. Os adapters de Vue e Svelte seguem o mesmo padrão, e páginas de HTML puro podem fazer bind com data-verbaly.
t`Hola ${name}`; // key + types generated
t.id('inbox.title')`Inbox`; // readable id, i18next-styleVindo sem i18n nenhum? Deixe o wrap fazer a parte chata
Se seu app tem texto hardcoded em vez de uma biblioteca de i18n, npx verbaly wrap escaneia seu JSX, encontra as strings visíveis ao usuário e as envolve em t`…` por você. Ele só reporta até você passar --write, e o que for ambíguo (como uma frase misturada com markup) é listado para você envolver à mão em vez de adivinhado.
npx verbaly wrap- would wrap 37 texts in 12 files (report only, use
--writeto apply) - src/App.tsx:12 "Welcome back"
- src/Nav.tsx:8 aria-label → "Open menu"
Detecção de idioma e carga lazy
O plugin detector e o backend HTTP desaparecem. resolveLocale lê o storage → navigator.languages → fallback (SSR-safe, com narrowing BCP-47), persistLocale guarda a escolha e sincroniza <html lang>, e setLocale carrega o chunk daquele idioma sob demanda:
import { resolveLocale, persistLocale } from 'verbaly';
import { setLocale, getLocale } from 'virtual:verbaly';
await setLocale(resolveLocale({ supported: ['en', 'es', 'pt'] }));
persistLocale(getLocale());Migre incrementalmente
Não precisa de big-bang: o runtime do Verbaly pesa ~3 KB, então as duas bibliotecas podem conviver enquanto você migra uma página ou namespace por vez. Quando os catálogos estiverem convertidos, dê ao i18next interpolation: { prefix: '{', suffix: '}' } para que ele também os leia. Uma ordem pragmática:
Rode os dois lado a lado
npx verbaly inite o plugin de build, então o toolchain roda ao lado do i18next desde o dia um. Ele aponta o config para os catálogos que você já tem, estejam onde estiverem.Passe os catálogos
npx verbaly migrate --write. Transforma{{name}}em{name}, duplica uma chave que o i18next mostrava como texto para que continue texto, e lembra quais idiomas converteu, então rodá-lo de novo é seguro.Troque os hooks página por página
Substitua
useTranslationporuseTe apague cada namespace do i18next quando esvaziar.Verifique no CI
npx verbaly checkno CI, enpx verbaly pseudopara caçar strings hardcoded que a migração deixou passar.