React
Comvi i18n integrates with React via the @comvi/react package. This guide covers installation, setup, hooks, and safe HTML interpolation with the <T> component.
Installation
Section titled “Installation”npm install @comvi/reactBootstrap
Section titled “Bootstrap”-
Create an i18n instance:
src/i18n.ts import { createI18n } from "@comvi/react";export const i18n = createI18n({locale: "en",fallbackLocale: "en",});// Register translationsi18n.registerLoader({en: () => import("./locales/en.json"),de: () => import("./locales/de.json"),"en:admin": () => import("./locales/admin/en.json"),}); -
Wrap your app in I18nProvider:
src/main.tsx import { createRoot } from "react-dom/client";import { I18nProvider } from "@comvi/react";import { i18n } from "./i18n";import App from "./App";createRoot(document.getElementById("root")!).render(<I18nProvider i18n={i18n}><App /></I18nProvider>);The provider auto-initializes the i18n instance on mount and uses
useSyncExternalStorefor tear-free state subscriptions. Optional props:autoInit?: boolean(defaulttrue) — skip if already initializedonError?: (err) => void— error handlerssrInitialLocale?: string— hydration locale (SSR)ssrInitialIsLoading?: boolean— hydration statessrInitialIsInitializing?: boolean— hydration state
-
Use in components:
src/App.tsx import { useI18n } from "@comvi/react";export default function App() {const { t, locale, setLocale } = useI18n();return (<div><h1>{t("home.title")}</h1><p>Current locale: {locale}</p><button onClick={() => setLocale("de")}>Deutsch</button></div>);}
useI18n Hook
Section titled “useI18n Hook”useI18n() returns translation methods and reactive state. Commonly used values:
interface UseI18nReturnPreview { t(key: string, params?: object): string; // flattened rich text as string locale: string; // current locale (read-only) setLocale(locale: string): Promise<void>; isLoading: boolean; isInitializing: boolean; onMissingKey( cb: (key: string, locale: string, namespace: string) => string | void, ): () => 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; dir: "ltr" | "rtl";}See the React API reference for the full return type.
Access a specific namespace:
import { useI18n } from "@comvi/react";
function DashboardPage() { const { t } = useI18n("admin"); return <h1>{t("dashboard.title")}</h1>;}Translation Function
Section titled “Translation Function”t() always returns a string. If a translation contains rich text tags, t() flattens them to text. Use the <T> component to render rich text with React elements.
import { useI18n } from "@comvi/react";
function Example() { const { t } = useI18n();
return ( <> {/* t() returns flattened string */} <h1>{t("home.title")}</h1> <p>{t("home.greeting", { name: "Alice" })}</p>
{/* Parameters, fallback, specific locale */} <p>{t("admin.section", { fallback: "Not found" })}</p> <p>{t("home.title", { locale: "de" })}</p> <p>{t("dashboard.title", { ns: "admin" })}</p> </> );}T Component
Section titled “T Component”The <T> component renders rich text (HTML tags, custom components) safely:
import { T } from "@comvi/react";
function LegalNotice() { return ( <> {/* Custom component function */} <T i18nKey="legal.tos" components={{ link: ({ children }) => ( <a href="/terms" className="text-blue-600"> {children} </a> ), }} />
{/* Element component */} <T i18nKey="legal.tos" components={{ link: <a href="/terms" className="text-blue-600" />, }} />
{/* With parameters */} <T i18nKey="welcome.message" params={{ name: "Alice", count: 5 }} ns="admin" />
{/* Children as fallback */} <T i18nKey="optional.key">Fallback text</T> </> );}T Props
Section titled “T Props”interface TProps { i18nKey: string; // Translation key (required) params?: Record<string, unknown>; // Parameters for interpolation ns?: string; // Namespace override locale?: string; // Locale override fallback?: string; // Fallback if key missing children?: React.ReactNode; // Used as fallback if key missing components?: { [tag: string]: string | React.ReactElement | (({ children }: { children: React.ReactNode }) => React.ReactElement); }; // Any other props are merged into params}useI18nContext Hook
Section titled “useI18nContext Hook”Access the provider’s context directly (rarely needed):
import { useI18nContext } from "@comvi/react";
function MyComponent() { const { i18n, locale, isLoading, isInitializing, translationCache } = useI18nContext(); // i18n is the underlying I18n instance}Locale Switching
Section titled “Locale Switching”Use setLocale() to await translation loading:
import { useI18n } from "@comvi/react";
function LanguageSwitcher() { const { locale, setLocale } = useI18n();
const handleChange = async (newLocale) => { await setLocale(newLocale); // Translations are guaranteed loaded here document.documentElement.lang = newLocale; document.documentElement.dir = newLocale === "ar" ? "rtl" : "ltr"; };
return ( <select value={locale} onChange={(e) => handleChange(e.target.value)}> <option value="en">English</option> <option value="de">Deutsch</option> <option value="ar">العربية</option> </select> );}Concurrent Rendering
Section titled “Concurrent Rendering”Comvi uses useSyncExternalStore internally, so it’s compatible with React 18+ concurrent features (Suspense, Transitions, useTransition) without tearing:
import { useI18n } from "@comvi/react";import { useTransition } from "react";
function LocaleSwitcher() { const { locale, setLocale } = useI18n(); const [isPending, startTransition] = useTransition();
const handleSwitch = (newLocale) => { startTransition(() => { void setLocale(newLocale); }); };
return ( <> <button onClick={() => handleSwitch("de")} disabled={isPending}> {isPending ? "Loading..." : "Switch to German"} </button> </> );}Common Patterns
Section titled “Common Patterns”Loading & Error States
Section titled “Loading & Error States”import { useI18n } from "@comvi/react";
function App() { const { t, isLoading, isInitializing } = useI18n();
if (isInitializing) return <div>Initializing...</div>; if (isLoading) return <div>{t("common.loading")}</div>;
return <div>Content loaded</div>;}Missing Key Handling
Section titled “Missing Key Handling”Register the callback inside useEffect so it runs once and is cleaned up on unmount:
import { useEffect } from "react";import { useI18n } from "@comvi/react";
function MissingKeyLogger() { const { onMissingKey } = useI18n();
useEffect(() => { return onMissingKey((key, locale, namespace) => { console.warn(`Missing: ${namespace}:${key} (${locale})`); return `[${key}]`; }); }, [onMissingKey]);
return null;}Plugins & Loaders
Section titled “Plugins & Loaders”import { createI18n } from "@comvi/react";import { FetchLoader } from "@comvi/plugin-fetch-loader";import { LocaleDetector } from "@comvi/plugin-locale-detector";
export const i18n = createI18n({ locale: "en", fallbackLocale: "en",}) .use( FetchLoader({ cdnUrl: "https://cdn.example.com/translations/", }) ) .use( LocaleDetector({ supportedLocales: ["en", "de", "fr"], order: ["localStorage", "navigator"], caches: ["localStorage"], }) );
i18n.registerLoader({ en: () => import("./locales/en.json"), de: () => import("./locales/de.json"),});SSR Hydration
Section titled “SSR Hydration”For framework SSR, prefer the Next.js guide. For custom React SSR, create an i18n instance per request on the server and pass matching ssrInitialLocale, ssrInitialIsLoading, and ssrInitialIsInitializing values to <I18nProvider> during hydration.