@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.
<I18nProvider>
Section titled “<I18nProvider>”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().
useI18n(ns?)
Section titled “useI18n(ns?)”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> );}Signature
Section titled “Signature”useI18n(ns?: string)Pass an optional namespace string to scope all t() calls to that namespace. To load a namespace on demand, call addActiveNamespace().
Returned Values
Section titled “Returned Values”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 plainstring(rich text is flattened). Use<T>to render rich text with React elements.tRaw()returns the structuredTranslationResultfor 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.
Changing Language
Section titled “Changing Language”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> );}Namespace Scoping
Section titled “Namespace Scoping”function Dashboard() { // Load and scope to 'dashboard' namespace const { t } = useI18n('dashboard');
return <h1>{t('page.title')}</h1>;}useI18nContext()
Section titled “useI18nContext()”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>;}When to Use useI18nContext() vs useI18n()
Section titled “When to Use useI18nContext() vs useI18n()”| Use Case | Hook |
|---|---|
| Translate keys in components | useI18n() |
| Read language / loading state | useI18n() |
| Subscribe to i18n events | useI18n() (via on) or useI18nContext() |
| Add translations at runtime | useI18n() (via addTranslations) or useI18nContext() |
| Access instance methods directly | useI18nContext() |
useLocale()
Section titled “useLocale()”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>;}Signature
Section titled “Signature”useLocale(): stringuseIsLoading()
Section titled “useIsLoading()”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}</>;}Signature
Section titled “Signature”useIsLoading(): { isLoading: boolean; isInitializing: boolean }useSetLocaleTransition()
Section titled “useSetLocaleTransition()”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> );}Signature
Section titled “Signature”useSetLocaleTransition(): { isPending: boolean; setLocale: (locale: string) => void }useFormatters()
Section titled “useFormatters()”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>;}Signature
Section titled “Signature”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;}<T> Component
Section titled “<T> Component”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— requiredcomponents— tag map. Function form receives{ children }; string handlers such as'strong'render as plain HTML tagschildren— fallback content if key missing
Basic Usage
Section titled “Basic Usage”// Translation: "Hello, {name}!"<T i18nKey="greeting" params={{ name: 'Alice' }} />Rich Text with Components
Section titled “Rich Text with Components”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 />, }}/>Fallback Content
Section titled “Fallback Content”Provide fallback content as children, displayed when the key is missing:
<T i18nKey="maybe.missing"> Default fallback text</T>Concurrent Rendering
Section titled “Concurrent Rendering”@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> );}Core Exports
Section titled “Core Exports”@comvi/react re-exports the core factory, class, and types used by the React binding:
createI18nI18n- all
@comvi/coretypes, including events and plugin types
Import other core value exports, such as TranslationCache or createElement, from @comvi/core directly.