# useI18n _(no submodules)_ ## Purpose `useI18n()` is the foundation hook of `@suaveplan/react-i18n`. It returns the active `I18nInstance` carried by the nearest `` ancestor. Every other hook in the package (`useTranslation`, `useLocale`, `useDate`, `useNumber`, `useCurrency`, `useRelativeTime`, `useList`, `usePlural`, `useDirection`, `useErrorMessage`) destructures from this base so the rules about missing-provider behaviour live in one place. The hook intentionally splits its behaviour by environment so the configuration error has different escalation paths in the developer's day-to-day loop and in production traffic. In dev (and tests) the hook throws `ReactI18nError(REACT_I18N.NO_PROVIDER)` so the bug is visible immediately. In production the hook returns `null` and emits a single `warn-once-per-process` log entry — never throwing while real users are trying to read your UI. ## Features - Returns the active `I18nInstance` from `TranslationContext`. - Dev-mode (NODE_ENV !== "production"): throws `ReactI18nError(REACT_I18N.NO_PROVIDER)` outside a Provider. - Production-mode: returns `null` plus a one-shot `console.warn`. - The Provider re-renders subscribers on `languageChanged` / `loaded` via `useSyncExternalStore`, so hooks reading `i18n.locale` see the update on the same React commit cycle as the bus emission. - Zero allocations per render in the common (in-Provider) path. ## Usage: Basic Example ```tsx import { useI18n, TranslationProvider } from "@suaveplan/react-i18n"; import { createI18n } from "@suaveplan/i18n/plugin"; function HelloLocale() { const i18n = useI18n(); return {i18n?.locale ?? "no-provider"}; } const i18n = createI18n({ defaultLocale: "en-GB", messages: { "en-GB": { greeting: "Hello" } }, }); export function App() { return ( ); } ``` ## Usage: Advanced Example `useI18n` returns the live instance, so a downstream hook can access plugin slots, subscribe to bus events directly, or call `i18n.changeLanguage` without going through `useLocale`. This is the recommended composition path for custom hooks layered on top of the package. ```tsx import { useI18n } from "@suaveplan/react-i18n"; import { useEffect, useState } from "react"; export function useNamespaceLoadCount(namespace: string): number { const i18n = useI18n(); const [count, setCount] = useState(0); useEffect(() => { if (i18n === null) return; return i18n.events.on("loaded", (payload) => { if (payload.namespace === namespace) { setCount((prev) => prev + 1); } }); }, [i18n, namespace]); return count; } ``` ## API Reference ### `useI18n(): I18nInstance | null` Returns the active `I18nInstance` from `TranslationContext`. - **Returns**: `I18nInstance` when a `` ancestor is in scope; `null` outside a Provider in production. - **Throws**: `ReactI18nError(REACT_I18N.NO_PROVIDER)` outside a Provider when `process.env.NODE_ENV !== "production"`. ## Implementation Notes - The `null` return in production is intentional. Higher-level hooks (`useTranslation`, `useLocale`, …) destructure this and switch to identity / no-op fallbacks so end-users never see a runtime crash from a missing Provider. - The dedup key for the warn-once-per-process call is `"react-i18n.use-i18n.no-provider"` — module-level so even multiple call sites and multiple components share the same one-shot. - The hook intentionally does NOT subscribe to the bus itself. The Provider holds the only `useSyncExternalStore` subscription; descendants re-render via that subscription's change-notification cascade. This keeps the hook's per-render cost equal to a single `useContext` read. - `process.env.NODE_ENV` is read at call-time (via `isProductionEnv()`) so test suites that swap the env between cases see the swap. Bundler-stamped literals also work because `globalThis.process.env.NODE_ENV` is the lookup target.