Saltar al contenido

Inicio

Migrar a Verbaly

Salir de otra librería de i18n es sobre todo mecánico, y no hace falta hacerlo de golpe: los dos runtimes conviven mientras vas fichero a fichero. Las tres secciones que siguen a la primera valen vengas de donde vengas; la primera es para i18next, que es de donde llega casi todo el mundo.

Vienes de i18next: qué equivale a qué

El modelo mental se traslada uno a uno, y lo único que cambia es la sintaxis:

i18nextVerbaly
i18next.init({ … })npx verbaly init + un plugin de build
t('inbox.title')t('inbox.title'): la misma llamada, con las keys ya tipadas
{{name}}{name}
messages_one / messages_other{count | one: … | other: # …}: opcional, las keys con sufijo siguen funcionando tal cual
format: 'currency' (interpolación){price:currency/EUR} (Intl nativo)
i18next-browser-languagedetectorresolveLocale() + persistLocale()
i18next-http-backend (lazy load)per-locale code-splitting, built into the plugin
useTranslation() (react-i18next)useT() (@verbaly/react)
Trans componentTrans component: values / components map over
i18next.changeLanguage('es')await setLocale('es')
saveMissing, runtime fallbacksnpx verbaly check: CI fails instead

Quédate con tus catálogos: entran tal como están

Tus catálogos entran sin cambiar de forma. Los grupos anidados siguen anidados, _one y _other siguen funcionando como las keys que son, y un namespace con dos puntos sigue siendo una key. Una sola cosa tiene que cambiar, y migrate la hace: {{name}} pasa a {name}, y una llave que i18next mostraba como texto se dobla, porque una llave doblada es como Verbaly escribe una llave literal.

npx verbaly migrate             # what it would port
npx verbaly migrate --write     # apply it

Primero reporta, reescribe solo las llaves, y te lista lo que no va a adivinar para que eso lo decidas tú. Esto es lo que le hace al fichero:

i18nextlocales/en.json
Verbalylocales/en.json
{
{
"inbox": { "title": "Inbox" },
"inbox": { "title": "Inbox" },
"messages_one": "one message",
"messages_one": "one message",
"messages_other": "{{count}} messages"
"messages_other": "{count} messages"
}
}

Dos mejoras que puedes dejar para después, y ninguna hace falta para funcionar. Fusionar _one y _other en un solo mensaje con variantes ({count | one: … | other: # …}) es lo que te da la selección automática de plural, y el # pinta el número ya localizado, sin un formattedCount aparte. La segunda es menor: los puntos son una convención de nombres, no un formato. Una clave se lee igual tanto si tu archivo la escribe "inbox.title" como si la pone de title dentro de un grupo inbox.

Cambia el runtime

Las keys existentes siguen funcionando vía t('key', params), así que los componentes migrados apenas cambian. En React:

react-i18next
@verbaly/react
import { useTranslation } from 'react-i18next';
import { useT } from '@verbaly/react';
const { t } = useTranslation();
const t = useT();
t('inbox.title');
t('inbox.title');
t('messages', { count });
t('messages', { count }); // params now typed

Si tus componentes llaman a un hook propio, como un useTranslate alrededor de useTranslation, consérvalo y haz que devuelva useT(): nada de lo que lo llama cambia. Un tipo de clave construido desde tu JSON, como un NestedKeyOf, también lista las rutas de los grupos, así que conviértelo en VerbalyKey de virtual:verbaly. Para esperar el idioma del visitante antes del primer pintado, como hacía un límite de Suspense, renderiza dentro de setLocale(locale).then(…): un await de nivel superior en tu entrada parte el bundle en muchos chunks.

El bundle lo nota. Una app de React con i18next y react-i18next embarca unos 23 KB de runtime; la misma app con Verbaly embarca unos 4. Sin framework son 13,6 contra 3,3. Los cuatro números son gzip y están medidos, no estimados.

Medido en Memos, una app de código abierto en 46 idiomas: 20 archivos cambiados a mano, su build y sus tests como antes, y unos 11 KB menos para cada visitante en la primera carga.

El copy nuevo se salta la ceremonia de keys: escribe el texto como tagged template y deja que el compilador lo extraiga, o conserva ids legibles con t.id. Los adapters de Vue y Svelte siguen el mismo patrón, y las páginas de HTML plano pueden bindear con data-verbaly.

src/app.tsx
t`Hola ${name}`;                 // key + types generated
t.id('inbox.title')`Inbox`;      // readable id, i18next-style

¿Vienes sin i18n? Deja que wrap haga la parte aburrida

Si tu app tiene texto hardcodeado en vez de una librería de i18n, npx verbaly wrap escanea tu JSX, encuentra las strings visibles al usuario y las envuelve en t`…` por ti. Solo reporta hasta que pases --write, y lo ambiguo (como una frase mezclada con markup) se lista para que lo envuelvas a mano en vez de adivinarse.

npx verbaly wrap
  1. would wrap 37 texts in 12 files (report only, use --write to apply)
  2. src/App.tsx:12 "Welcome back"
  3. src/Nav.tsx:8 aria-label → "Open menu"
npx verbaly wrap --write   # then: add t to scope and run npx verbaly extract

Detección de idioma y carga lazy

El plugin detector y el backend HTTP desaparecen. resolveLocale lee storage → navigator.languages → fallback (SSR-safe, con narrowing BCP-47), persistLocale guarda la elección y sincroniza <html lang>, y setLocale carga el chunk de ese idioma bajo demanda:

src/main.ts
import { resolveLocale, persistLocale } from 'verbaly';
import { setLocale, getLocale } from 'virtual:verbaly';

await setLocale(resolveLocale({ supported: ['en', 'es', 'pt'] }));
persistLocale(getLocale());

Migra incremental

No hace falta un big-bang: el runtime de Verbaly pesa ~3 KB, así que ambas librerías pueden convivir mientras migras una página o namespace a la vez. Cuando los catálogos estén convertidos, dale a i18next interpolation: { prefix: '{', suffix: '}' } para que también los lea. Un orden pragmático:

  1. Convive con i18next

    npx verbaly init y el plugin de build, así el toolchain corre junto a i18next desde el día uno. Apunta el config a los catálogos que ya tienes, estén donde estén.

  2. Pasa los catálogos

    npx verbaly migrate --write. Convierte {{name}} en {name}, dobla una llave que i18next mostraba como texto para que siga siendo texto, y recuerda qué idiomas convirtió, así que volver a correrlo es seguro.

  3. Cambia los hooks página a página

    Sustituye useTranslation por useT y borra cada namespace de i18next cuando quede vacío.

  4. Compruébalo en el CI

    npx verbaly check en CI, y npx verbaly pseudo para cazar strings hardcodeados que la migración pasó por alto.

↑↓para moverteEnterpara abrir
Copiado en el portapapeles