Skip to content

SDK: React

@lionrapid/react wraps the core client for React. You build the core instance once, hand it to LionRapidProvider, and read translations in components through the useLionRapid hook — same keys, same generated types, plus automatic re-rendering when translations load or the language changes.

Install the React package alongside the core (see the Quickstart for registry access):

Terminal window
npm install @lionrapid/core @lionrapid/react

Create the instance and pass it to the provider via the instance prop:

import { LionRapidBuilder, MemoryRepository, NetworkRepository } from '@lionrapid/core';
import { coreFormattersPlugin } from '@lionrapid/formatters';
import { LionRapidProvider } from '@lionrapid/react';
import ReactDOM from 'react-dom/client';
import App from './App';
const lionRapid = await LionRapidBuilder.init({
defaultLocale: 'en',
namespace: 'app',
})
.use(new MemoryRepository({ enabled: true }))
.use(
new NetworkRepository({
enabled: true,
options: {
baseUrl: 'https://your-lionrapid-host',
apiKey: import.meta.env.VITE_LIONRAPID_INTEGRATION_KEY,
preloadNamespaces: [{ locale: 'en', namespace: 'app', eager: true }],
},
})
)
.use(coreFormattersPlugin)
.build();
ReactDOM.createRoot(document.getElementById('root')!).render(
<LionRapidProvider instance={lionRapid}>
<App />
</LionRapidProvider>
);

useLionRapid(namespace?, options?) is the main hook:

import { useLionRapid } from '@lionrapid/react';
function Welcome() {
const { t } = useLionRapid('app');
return <h1>{t('welcome')}</h1>;
}

It returns:

Field What it is
t Translation function — plain text, params + optional default
tHtml Like t but returns HTML with inline styles applied
locale The current locale
changeLanguage Switch locale (preloads the hook’s namespace by default)
ready true once the hook’s namespace is loaded
i18n The underlying core instance (i18n.bus.on(…), i18n.cache, …)

The t returned by the hook accepts a default value as either the second or third argument — t('key', 'Default') and t('key', { name: 'Ada' }, 'Hello {name}!') both work.

Options:

  • keyPrefix — prepended to every key, for deeply nested key families:

    const { t } = useLionRapid('app', { keyPrefix: 'user.settings' });
    t('title'); // resolves app:user.settings.title
  • autoRefresh (default true) — re-render when translations for the namespace load or the language changes. Disable for components that must never re-render from translation traffic.

  • useSuspense (default false) — see loading patterns below.

Translations arrive asynchronously; pick how components behave while they load.

Optimistic rendering (recommended). Just render. t() returns the default value or the key first, and the component re-renders automatically when the translation lands:

function Title() {
const { t } = useLionRapid('app');
return <h1>{t('welcome', 'Welcome!')}</h1>;
}

Non-blocking indicator. Render content immediately, show progress on the side:

function Page() {
const { t, ready } = useLionRapid('app');
return (
<div>
{!ready && <LoadingBar />}
<h1>{t('welcome')}</h1>
</div>
);
}

Blocking on ready. Only when untranslated content must never flash:

function Page() {
const { t, ready } = useLionRapid('app');
if (!ready) return <Skeleton />;
return <h1>{t('welcome')}</h1>;
}

Suspense mode (React 18+). The hook throws a promise until the namespace is loaded, so a <Suspense> boundary shows the fallback:

function Page() {
const { t } = useLionRapid('app', { useSuspense: true });
return <h1>{t('welcome')}</h1>;
}

Suspense mode has a 10-second safety timeout, after which the component renders with fallback values instead of suspending forever.

changeLanguage from the hook preloads the hook’s namespace by default, so components blocking on ready don’t deadlock after a switch:

await changeLanguage('fi');
// Lazy switch — skip the preload:
await changeLanguage('fi', { skipPreload: true });
// Preload several namespaces:
await changeLanguage('fi', { preloadNamespaces: ['app', 'errors'] });
  • useLocale() — the current locale, updated automatically on language change. For display components that don’t translate anything.

  • useChangeLanguage() — just the changeLanguage function, for language switchers:

    function LanguageSwitcher() {
    const changeLanguage = useChangeLanguage();
    return (
    <div>
    <button onClick={() => changeLanguage('en')}>English</button>
    <button onClick={() => changeLanguage('fi')}>Suomi</button>
    </div>
    );
    }
  • useTranslationReady(namespace?) — a boolean that flips to true when a namespace finishes loading (and resets on language change), for gating whole sections.

<Trans> renders translations that contain markup as real React elements — no dangerouslySetInnerHTML in your code.

Simple usage (no children): the translation’s inline styles are applied automatically:

<Trans i18nKey="welcome" />
<Trans i18nKey="greeting" values={{ name: 'Ada' }} />
<Trans i18nKey="terms" defaultValue="Read our <b>terms</b> first." />

JSX interpolation: when the translation contains indexed tags (the ContentUnit format), child elements are matched to tags by position — the first child renders <1>…</1>, the second <2>…</2>, and so on:

// Translation: "Hello <1>{name}</1>, you have <2>{count}</2> messages"
<Trans i18nKey="dashboard.welcome" values={{ name: 'Ada', count: 5 }}>
<strong />
<span className="badge" />
</Trans>

This is the JSX round-trip: translators see and move <1>…</1> markers, your code decides that marker 1 is a <strong> and marker 2 is a styled <span> — or a <Link>, or any component. ICU expressions inside the segments ({name}, plurals, select) are processed with values.

If you pass no child for an index, the tag falls back to the style stored on the server (bold<strong> via the default style mapper). Props supported: i18nKey, values, defaultValue, namespace, and children.

Under the hood <Trans> uses the exported JSXSerializer and buildComponentMap; both are available directly if you need custom rendering, and a custom style mapper can be injected via new JSXSerializer({ styleMapper }).

The types CLI augments the core’s TranslationKeys interface, and the hook’s t resolves against the same instance, so generated keys autocomplete in components. Import the generated declaration file once (for example in your entry point) and keep it inside your tsconfig.json include paths.

Components that translate only need a provider with a core instance. For unit tests, build an instance with just a memory repository and seed it — no network, no mocks of the SDK itself:

import { render } from '@testing-library/react';
import { LionRapidBuilder, MemoryRepository } from '@lionrapid/core';
import { LionRapidProvider } from '@lionrapid/react';
async function renderWithTranslations(ui: React.ReactElement) {
const memory = new MemoryRepository({ enabled: true });
memory.set('en:app:welcome', 'Welcome!');
const instance = await LionRapidBuilder.init({
defaultLocale: 'en',
namespace: 'app',
})
.use(memory)
.build();
return render(<LionRapidProvider instance={instance}>{ui}</LionRapidProvider>);
}

Seed keys use the cache format locale:namespace:key. Since there is no network handler, lookups either hit the seeded memory cache or fall back to defaults synchronously — tests stay deterministic.

The SDK repository ships a runnable react-simple example that exercises every feature on this page — the hooks, loading patterns, <Trans> showcases, and ContentUnit rendering — against a live translation server. It is a good reference for wiring order and for what re-renders when.