@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; 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() |
<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.