Framework

React

The official React 18/19 package for Verbaly. Hooks over the same reactive core, a compiler-aware <Trans> for rich text, and keys + params fully typed.

What you can accomplish:

Installation

Install the package with your preferred package manager.

 pnpm add @verbaly/react

Setup & hooks

App.tsx
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>;
}

Rich text with <Trans>

Write the source text in place, and since 0.6.0 the compiler extracts it, exactly like t`…`:

Agree.tsx
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' /> }} />

Next.js

Server Components render already translated in the visitor's language, and Client Components hydrate with the same locale and catalog: no flash of untranslated text, no hydration mismatch.

Next.js needs the integration (@verbaly/next) alongside the React bindings:

 pnpm add verbaly @verbaly/next @verbaly/react

Wire it up

Wrap your config with withVerbaly. It wires live extraction in dev, the message compiler on Turbopack and webpack, and blocks next build on missing translations:

next.config.ts
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'] });

In the root layout, getVerbalyProps serializes the negotiated locale and its catalog, and VerbalyProvider seeds the client from them:

app/layout.tsx
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 and Client Components

Server Components get a request-scoped t with await getT(), negotiated once per request (cookie, then Accept-Language, then your fallback):

app/page.tsx
import { getT } from '@verbaly/next/server';

export default async function Page() {
  const t = await getT();
  return <h1>{t`Welcome back`}</h1>;
}

Client Components use the React bindings, re-exported from @verbaly/next/client:

Counter.tsx
'use client';
import { useT } from '@verbaly/next/client';

export function Counter() {
  const t = useT();
  return <p>{t`You have new messages`}</p>;
}

Switching languages

useSwitchLocale switches the client live, writes the cookie the server reads, and re-renders Server Components through router.refresh():

LangPicker.tsx
'use client';
import { useSwitchLocale } from '@verbaly/next/client';

export function LangPicker() {
  const switchLocale = useSwitchLocale();
  return <button onClick={() => switchLocale('es')}>Español</button>;
}
Copied to clipboard