Framework
React
El paquete oficial de React 18/19 para Verbaly. Hooks sobre el mismo core reactivo, un <Trans> consciente del compilador para rich text, y claves + params totalmente tipados.
Qué puedes lograr:
- Traduce con hooks (useT, useLocale) que re-renderizan exactamente cuando cambia el idioma
- Escribe rich text en el lugar: el compilador extrae los children de
<Trans>como cualquiert`…` - Mantén claves y params totalmente tipados, con autocompletado desde tus propios mensajes
- Concurrent-safe por construcción: las suscripciones van sobre
useSyncExternalStore
Instalación
Instala el paquete con tu gestor de paquetes preferido.
pnpm add @verbaly/reactSetup y hooks
import { VerbalyProvider, useT, useLocale } from '@verbaly/react';
import { verbaly } from 'virtual:verbaly';
<VerbalyProvider instance={verbaly}>
<App />
</VerbalyProvider>
function Inbox() {
const t = useT();
const [locale, setLocale] = useLocale();
return <p>{t('inbox', { count: 3 })}</p>;
}- Re-renderiza vía
useSyncExternalStore: concurrent-safe, amigable con SSR. useVerbaly()expone la instancia completa cuando la necesitas.
Rich text con <Trans>
Escribe el texto fuente en el lugar, y desde 0.6.0 el compilador lo extrae, exactamente como t`…`:
import { Trans } from '@verbaly/react';
// you write:
<Trans>Read the <a href='/terms'>terms</a> before continuing</Trans>
// the compiler extracts "Read the <a>terms</a> before continuing" and rewrites to:
<Trans id='x7Ka9q2f' components={{ a: <a href='/terms' /> }} />- Los atributos de los hijos se preservan; los params (
{count}) se vuelvenvalues; el anidado funciona; los tags desconocidos caen a su texto interno. - Los tags de la whitelist se renderizan de verdad (
0.23.0):<em>,<code>y compañía en un mensaje se vuelven elementos reales, la misma whitelist segura del intérprete de DOM.richTagsreemplaza la lista; una lista vacía restaura el comportamiento anterior de aplanar a texto. - Claves legibles (
0.8.0): escribe<Trans id="inbox.title">…</Trans>con children y se extrae bajo ese id en vez de un hash. - Runtime-first sigue funcionando: pasa
idsin children (o con tus propiosvalues/components) y el compilador no toca el elemento. - Aplican las reglas de whitespace de JSX: un salto de línea entre un elemento y texto no renderiza espacio; usa el idiom
{' '}, la extracción es fiel a lo que React renderiza. - Links nombrados (
0.11.0): el proplinksmapea nombres de tag a hrefs:<docs>…</docs>se renderiza como un<a>real sin componente custom.componentsgana sobrelinkscon el mismo nombre; los hrefsjavascript:se bloquean. - ¿Sin provider? Pasa el prop
instance(0.23.0) y<Trans>renderiza directo desde ahí, re-renderizando en cada cambio de idioma.
Next.js
Los Server Components se renderizan ya traducidos al idioma del visitante, y los Client Components hidratan con el mismo locale y el mismo catálogo: sin flash de texto sin traducir ni mismatch de hidratación.
- Cada página se renderiza en el idioma del visitante, elegido en cada request desde su cookie o su header
Accept-Language - Sin flash de texto sin traducir: el HTML que envía el servidor y la página ya hidratada son idénticos, catálogo incluido
- Cada request tiene su propia instancia, así que dos visitantes simultáneos nunca ven el idioma del otro
- Turbopack y webpack, ambos de primera: el compilador de
tcorre como loader en cualquiera de los dos bundlers
Next.js necesita la integración (@verbaly/next) junto a los bindings de React:
pnpm add verbaly @verbaly/next @verbaly/reactConéctalo
Envuelve tu config con withVerbaly. Cablea la extracción en vivo en dev, el compilador de mensajes en Turbopack y webpack, y bloquea next build si faltan traducciones:
import { withVerbaly } from '@verbaly/next';
export default withVerbaly({ /* your Next config */ });
// locales live in your verbaly.config (npx verbaly init), or inline:
export default withVerbaly({}, { locales: ['en', 'es', 'pt'] });En el layout raíz, getVerbalyProps serializa el locale negociado y su catálogo, y VerbalyProvider siembra el cliente desde ellos:
import { getRequestLocale, getVerbalyProps } from '@verbaly/next/server';
import { VerbalyProvider } from '@verbaly/next/client';
export default async function RootLayout({ children }) {
const locale = await getRequestLocale();
const props = await getVerbalyProps();
return (
<html lang={locale}>
<body>
<VerbalyProvider {...props}>{children}</VerbalyProvider>
</body>
</html>
);
}Server y Client Components
Los Server Components obtienen un t por request con await getT(), negociado una vez por request (cookie, luego Accept-Language, luego tu fallback):
import { getT } from '@verbaly/next/server';
export default async function Page() {
const t = await getT();
return <h1>{t`Welcome back`}</h1>;
}Los Client Components usan los bindings de React, re-exportados desde @verbaly/next/client:
'use client';
import { useT } from '@verbaly/next/client';
export function Counter() {
const t = useT();
return <p>{t`You have new messages`}</p>;
}Cambiar de idioma
useSwitchLocale cambia el cliente en vivo, escribe la cookie que el servidor lee, y re-renderiza los Server Components vía router.refresh():
'use client';
import { useSwitchLocale } from '@verbaly/next/client';
export function LangPicker() {
const switchLocale = useSwitchLocale();
return <button onClick={() => switchLocale('es')}>Español</button>;
}- La cookie por defecto es
verbaly-locale(la misma identidad que la storage key del core);cookie: falseen las opciones la desactiva, paraAccept-Languagepuro. fallbackpor defecto es tu locale fuente; la negociación entiende q-values y estrecha regiones (es-PEmatcheaes).next buildse bloquea si faltan traducciones, con las keys exactas a completar;failOnMissing: falselo desactiva.- La negociación lee headers del request, así que las páginas negociadas se renderizan dinámicamente. ¿Sitio totalmente estático? Prefiere salida pre-traducida por locale con verbaly render.