first commit
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
# useLocaleDetection
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## 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
|
||||
|
||||
```tsx
|
||||
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
|
||||
|
||||
```tsx
|
||||
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`
|
||||
|
||||
```ts
|
||||
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.
|
||||
Reference in New Issue
Block a user