first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -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.