Skip to content

@comvi/react API Reference

@comvi/react provides React hooks, an <I18nProvider> for context-based access, and a <T> component for safe rich-text interpolation. It uses useSyncExternalStore internally for tear-free concurrent rendering. It builds on top of @comvi/core and re-exports all core APIs.

Wraps your component tree and provides the i18n instance via React context. Required for both useI18n() and useI18nContext().

import { I18nProvider } from '@comvi/react';
import { i18n } from './i18n';
function App() {
return (
<I18nProvider i18n={i18n}>
<MyApp />
</I18nProvider>
);
}
interface I18nProviderProps {
i18n: I18n; // required
autoInit?: boolean; // default true
onError?: (err: Error) => void;
ssrInitialLocale?: string;
ssrInitialIsLoading?: boolean;
ssrInitialIsInitializing?: boolean;
children: React.ReactNode;
}

autoInit (default true) skips if already initialized/initializing. Provider is required for useI18nContext().

The primary hook for translating strings and reading i18n state. Returns an object with the translation function, current language, and loading state.

import { useI18n } from '@comvi/react';
function MyComponent() {
const { t, locale, setLocale, isLoading, isInitializing } = useI18n();
if (isLoading) return <div>Loading...</div>;
return (
<div>
<h1>{t('hello.world')}</h1>
<p>{t('greeting', { name: 'Alice' })}</p>
<p>Current locale: {locale}</p>
</div>
);
}
useI18n(ns?: string)

Pass an optional namespace string to scope all t() calls to that namespace. To load a namespace on demand, call addActiveNamespace().

interface UseI18nReturn {
t(key: string, params?: TranslationParams): string; // rich-text flattened
tRaw(key: string, params?: TranslationParams): TranslationResult; // structured (for VirtualNodes)
locale: string;
setLocale(locale: string): Promise<void>;
translationCache: ReadonlyMap<string, FlattenedTranslations>;
isLoading: boolean;
isInitializing: boolean;
addTranslations(translations: Record<string, Record<string, TranslationValue>>): void;
addActiveNamespace(namespace: string): Promise<void>;
setFallbackLocale(locales: string | string[]): void;
onMissingKey(
cb: (key: string, locale: string, namespace: string) => string | void,
): () => void;
onLoadError(cb: (locale: string, namespace: string, error: Error) => void): () => void;
clearTranslations(locale?: string, namespace?: string): void;
reloadTranslations(locale?: string, namespace?: string): Promise<void>;
hasLocale(locale: string, namespace?: string): boolean;
hasTranslation(key: string, locale?: string, namespace?: string, checkFallbacks?: boolean): boolean;
getLoadedLocales(): string[];
getActiveNamespaces(): string[];
getDefaultNamespace(): string;
on<E extends I18nEvent>(event: E, cb: (payload: I18nEventData[E]) => void): () => void;
reportError(error: unknown, context?: ErrorReportContext): void;
formatNumber(value: number, options?: Intl.NumberFormatOptions): string;
formatDate(value: Date | number, options?: Intl.DateTimeFormatOptions): string;
formatCurrency(value: number, currency: string, options?: Intl.NumberFormatOptions): string;
formatRelativeTime(
value: number,
unit: Intl.RelativeTimeFormatUnit,
options?: Intl.RelativeTimeFormatOptions,
): string;
dir: "ltr" | "rtl";
}

Notes:

  • t() always returns plain string (rich text is flattened). Use <T> to render rich text with React elements.
  • tRaw() returns the structured TranslationResult for advanced integrations. Do not render it directly in JSX unless you convert VirtualNodes yourself.
  • All returned values are plain JavaScript (no refs or wrappers); the provider re-renders consumers via useSyncExternalStore.
import { useI18n } from '@comvi/react';
function LanguageSwitcher() {
const { locale, setLocale } = useI18n();
async function handleChange(locale: string) {
await setLocale(locale);
}
return (
<select
value={locale}
onChange={(e) => void handleChange(e.target.value)}
>
<option value="en">English</option>
<option value="de">Deutsch</option>
<option value="fr">Francais</option>
</select>
);
}
function Dashboard() {
// Load and scope to 'dashboard' namespace
const { t } = useI18n('dashboard');
return <h1>{t('page.title')}</h1>;
}

Returns the i18n context value from the nearest <I18nProvider>. The returned object contains the i18n instance along with reactive state. Use this when you need direct access to the i18n instance for imperative operations.

import { useI18nContext } from '@comvi/react';
import { useEffect } from 'react';
function AdvancedComponent() {
const { i18n } = useI18nContext();
useEffect(() => {
const unsub = i18n.on('localeChanged', ({ from, to }) => {
console.log(`Language changed from ${from} to ${to}`);
});
return unsub;
}, [i18n]);
return <div>...</div>;
}
Use CaseHook
Translate keys in componentsuseI18n()
Read language / loading stateuseI18n()
Subscribe to i18n eventsuseI18n() (via on) or useI18nContext()
Add translations at runtimeuseI18n() (via addTranslations) or useI18nContext()
Access instance methods directlyuseI18nContext()

The <T> component renders translations containing rich content (HTML tags, React components, links) safely. Always prefer <T> over injecting raw HTML for translations.

import { T } from '@comvi/react';
interface TProps {
i18nKey: string;
params?: TranslationParams;
ns?: string;
locale?: string;
fallback?: string;
components?: {
[tag: string]: string | React.ReactElement | (({ children }: { children: React.ReactNode }) => React.ReactElement);
}
children?: React.ReactNode; // used as fallback if key missing
// Any other props are merged into params
}
  • i18nKey — required
  • components — tag map. Function form receives { children }; string handlers such as 'strong' render as plain HTML tags
  • children — fallback content if key missing
// Translation: "Hello, {name}!"
<T i18nKey="greeting" params={{ name: 'Alice' }} />

Pass a components map to replace tagged sections in translation values with React elements:

// Translation: "Read our <link>terms of service</link>"
<T
i18nKey="legal.tos"
components={{
link: <a href="/terms" />,
}}
/>
// Translation: "This is <bold>important</bold> and <italic>urgent</italic>"
<T
i18nKey="notice"
components={{
bold: <strong />,
italic: <em />,
}}
/>

Provide fallback content as children, displayed when the key is missing:

<T i18nKey="maybe.missing">
Default fallback text
</T>

@comvi/react uses useSyncExternalStore internally to subscribe to the i18n instance. This means it is fully compatible with React’s concurrent features:

  • Suspense — no tearing during suspended renders
  • Transitions — language changes can be wrapped in startTransition
  • Strict Mode — safe to use in <StrictMode>
import { useTransition } from 'react';
import { useI18n } from '@comvi/react';
function LanguageSwitcher() {
const { setLocale } = useI18n();
const [isPending, startTransition] = useTransition();
function handleSwitch(lang: string) {
startTransition(() => {
void setLocale(lang);
});
}
return (
<button onClick={() => handleSwitch('de')} disabled={isPending}>
{isPending ? 'Switching…' : 'Deutsch'}
</button>
);
}

@comvi/react re-exports the core factory, class, and types used by the React binding:

  • createI18n
  • I18n
  • all @comvi/core types, including events and plugin types

Import other core value exports, such as TranslationCache or createElement, from @comvi/core directly.