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;
// The core callback may return TranslationResult | void; the React hook narrows it to 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()

Returns the current locale string. Subscribes only to locale changes — components that don’t need translation output (e.g. navigation, <html lang>) prefer this over useI18n() to avoid re-renders on loading-state or cache updates.

import { useLocale } from '@comvi/react';
function LocaleLabel() {
const locale = useLocale();
return <span lang={locale}>{locale}</span>;
}
useLocale(): string

Returns { isLoading, isInitializing }. Subscribes only to loading-state changes — skips re-renders on locale changes and cache updates.

import { useIsLoading } from '@comvi/react';
function LoadingGuard({ children }: { children: React.ReactNode }) {
const { isInitializing } = useIsLoading();
if (isInitializing) return <div>Loading translations…</div>;
return <>{children}</>;
}
useIsLoading(): { isLoading: boolean; isInitializing: boolean }

Wraps i18n.setLocaleAsync() in a React useTransition so the old UI stays interactive while the new locale loads. isPending is true from the moment setLocale() is called until the async fetch and React transition both resolve.

import { useSetLocaleTransition } from '@comvi/react';
function LangSwitcher() {
const { isPending, setLocale } = useSetLocaleTransition();
return (
<button onClick={() => setLocale('fr')} disabled={isPending}>
{isPending ? 'Loading…' : 'Français'}
</button>
);
}
useSetLocaleTransition(): { isPending: boolean; setLocale: (locale: string) => void }

Returns Intl formatter helpers bound to the React-tracked locale. The returned object is memoized and a new one is only created when the locale changes, so it is safe to pass as a dependency to useEffect or useMemo.

import { useFormatters } from '@comvi/react';
function PriceDisplay({ amount }: { amount: number }) {
const { formatCurrency } = useFormatters();
return <p>{formatCurrency(amount, 'USD')}</p>;
}
useFormatters(): {
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;
}

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.