6.0 KiB
useLocaleDetection
(no submodules)
Purpose
useLocaleDetection is the React surface that runs the configured
locale-detector chain on mount and exposes the result to descendants.
It works in tandem with <DetectorChainProvider> (paired in this
module) which carries the chain configuration and any
SSR-authoritative initialLocale.
The hook MUST be used inside both <TranslationProvider> and
<DetectorChainProvider> — it reads the active i18n instance from the
former and the chain detector list from the latter. The split lets the
foundation <TranslationProvider> remain decoupled from detector
concerns (the config schema does not yet carry a detectors field —
see Implementation Notes for the wiring story).
Features
- Runs the chain on mount and stores
{ detected, source, isPending } redetect()re-runs the chain (useful for switcher UIs and SSR override flows)- SSR contract: when
<DetectorChainProvider initialLocale=...>is set, the chain is NOT auto-run on mount —initialLocaleis treated as authoritative for the first render so client-side detectors do not produce a hydration mismatch - Calls
i18n.changeLanguage(detected)automatically when the chain resolves to a different locale than the currently active one - Subscribes via
useSyncExternalStoreso multiple components reading the hook receive consistent state on the same commit
Usage: Basic Example
import {
DetectorChainProvider,
useLocaleDetection,
} from "@suaveplan/react-i18n/hooks/use-locale-detection/use-locale-detection";
import { defaultDetectorChain } from "@suaveplan/react-i18n/detector/default-chain/default-chain";
import { TranslationProvider } from "@suaveplan/react-i18n";
function App() {
return (
<TranslationProvider config={{ defaultLocale: "en-GB" }}>
<DetectorChainProvider
detectors={defaultDetectorChain({ fallback: "en-GB" })}
>
<Page />
</DetectorChainProvider>
</TranslationProvider>
);
}
function Page() {
const { detected, source, isPending, redetect } = useLocaleDetection();
if (isPending) return <p>Detecting…</p>;
return (
<div>
<span>Locale: {detected ?? "unknown"}</span>
<small> (source: {source})</small>
<button type="button" onClick={() => redetect()}>
Re-run detection
</button>
</div>
);
}
Usage: Advanced Example
import { DetectorChainProvider } from "@suaveplan/react-i18n/hooks/use-locale-detection/use-locale-detection";
import { defaultDetectorChain } from "@suaveplan/react-i18n/detector/default-chain/default-chain";
// SSR scenario: pass `initialLocale` so the client-side detectors do
// NOT auto-run on mount. The user can still call `redetect()` from a
// switcher to override the SSR pick.
export function SsrTree(props: {
initialLocale: string;
initialSource: string;
children: ReactNode;
}) {
return (
<DetectorChainProvider
initialLocale={props.initialLocale}
initialSource={props.initialSource}
detectors={defaultDetectorChain({
fallback: "en-GB",
userPreferenceEndpoint: "/api/me/locale",
})}
buildContext={() => ({
supportedLocales: ["en-GB", "fr-FR", "ar-AE"],
fallbackLocale: "en-GB",
})}
>
{props.children}
</DetectorChainProvider>
);
}
API Reference
useLocaleDetection(): UseLocaleDetectionResult
Returns { detected, source, isPending, redetect }. Throws when
called outside <TranslationProvider> OR
<DetectorChainProvider>.
<DetectorChainProvider>
| Prop | Type | Description |
|---|---|---|
| detectors | LocaleDetector[] (req) |
Detectors fed to createDetectorChain |
| buildContext | () => DetectorContext |
Optional supplier producing a fresh context per chain run |
| initialLocale | string |
Authoritative SSR pick (skips auto-run on mount) |
| initialSource | string |
Optional detector name for the SSR pick |
| children | ReactNode |
Subtree rendered with the context in scope |
UseLocaleDetectionResult
interface UseLocaleDetectionResult {
readonly detected: string | null;
readonly source: string | null;
readonly isPending: boolean;
redetect(): Promise<void>;
}
Implementation Notes
- The hook is split from
<TranslationProvider>because the foundation Provider's config schema does not yet expose adetectorsfield. Sub-wave 1c (useLocale/setLocale) and a future provider-level detector option will fold this surface into a single<TranslationProvider detectors=…>mount; until then the paired<DetectorChainProvider>is the supported wiring. - The hook calls
i18n.changeLanguage(detected)automatically when the chain resolves to a different locale than the active one. The Provider's bus subscriptions handle the resulting React commit. redetect()always re-runs the chain — even after an SSRinitialLocalewas supplied — so a switcher-driven user action can override the SSR pick deterministically.- The internal store uses
useSyncExternalStoredirectly (not React state) so concurrent renders observe a consistent snapshot of{ detected, source, isPending }across the commit. - Switcher-side persisting-detector walking (cookieDetector.cache,
localStorageDetector.cache, userPreferenceDetector.cache) is the
responsibility of the future
setLocalesurface (sub-wave 1c). Until that lands, ad-hoc consumers can callcreateDetectorChain(detectors).cache(newLocale, ctx)directly to exercise the same surface.