Files
skills/docs/localization/i18n/react-i18n/src/hooks/use-locale-detection/use-locale-detection.md
2026-05-29 08:20:56 +01:00

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 — initialLocale is 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 useSyncExternalStore so 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 a detectors field. 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 SSR initialLocale was supplied — so a switcher-driven user action can override the SSR pick deterministically.
  • The internal store uses useSyncExternalStore directly (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 setLocale surface (sub-wave 1c). Until that lands, ad-hoc consumers can call createDetectorChain(detectors).cache(newLocale, ctx) directly to exercise the same surface.