first commit
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
# `user-preference.ts` — backend-stored locale preference detector
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`userPreferenceDetector(options)` builds an async `LocaleDetector`
|
||||
that calls a backend endpoint to read (and later persist) the
|
||||
user's stored locale preference. This is the surface authenticated
|
||||
applications use to honour a user's saved-to-account choice across
|
||||
devices, ahead of cookie/localStorage which are device-bound.
|
||||
|
||||
Unlike most detectors in this package, user-preference is async-
|
||||
only — it has no `detect` and only contributes during the chain's
|
||||
async race phase. It runs in both server and browser tiers because
|
||||
the endpoint is typically a thin JSON API that accepts the
|
||||
session's auth header from either side.
|
||||
|
||||
## Features
|
||||
|
||||
- GET `options.endpoint` returning `{ locale }`; non-2xx,
|
||||
malformed, and rejected responses log a warn and return `null`.
|
||||
- POST `options.endpoint` with `{ locale }` JSON on
|
||||
`cache(locale, ctx)`; failures are non-fatal.
|
||||
- `options.extractAuth(ctx)` injects per-request auth headers
|
||||
(e.g. session cookie on SSR runs).
|
||||
- `options.fetch` injectable for tests.
|
||||
- `options.logger?: LoggerLike` — defaults to a no-op so the
|
||||
package keeps zero non-shared deps. Production consumers wire
|
||||
in `@suaveplan/logger`.
|
||||
- Threads `ctx.signal` into every fetch's `RequestInit.signal`.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { userPreferenceDetector } from "@suaveplan/i18n-detector/detectors/user-preference";
|
||||
|
||||
const detector = userPreferenceDetector({
|
||||
endpoint: "/api/me/locale",
|
||||
});
|
||||
|
||||
const locale = await detector.detectAsync?.({
|
||||
supportedLocales: ["en-GB", "fr-FR"],
|
||||
fallbackLocale: "en-GB",
|
||||
});
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { userPreferenceDetector } from "@suaveplan/i18n-detector/detectors/user-preference";
|
||||
import { ConsoleLogger } from "@suaveplan/logger";
|
||||
|
||||
const detector = userPreferenceDetector({
|
||||
endpoint: "/api/me/locale",
|
||||
logger: new ConsoleLogger({ tags: ["i18n.user-preference"] }),
|
||||
extractAuth: (ctx) => ({
|
||||
cookie: ctx.request?.headers.get("cookie") ?? "",
|
||||
}),
|
||||
fetch: globalThis.fetch,
|
||||
});
|
||||
|
||||
await detector.cache?.("fr-FR", ctx);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `userPreferenceDetector(options): LocaleDetector` — `environment: "both"`.
|
||||
- `UserPreferenceDetectorOptions.endpoint: string` — required.
|
||||
- `UserPreferenceDetectorOptions.extractAuth?: (ctx) => HeadersInit`.
|
||||
- `UserPreferenceDetectorOptions.cacheKey?: (ctx) => string`.
|
||||
- `UserPreferenceDetectorOptions.fetch?: typeof fetch`.
|
||||
- `UserPreferenceDetectorOptions.logger?: LoggerLike`.
|
||||
- `UserPreferenceResponseBody = { locale: string }` — wire shape.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- A pre-aborted `ctx.signal` typically causes the underlying
|
||||
`fetch` to reject; the rejection is swallowed and `null` is
|
||||
returned, never propagated into the chain.
|
||||
- `Content-Type: application/json` is set automatically on cache
|
||||
POSTs unless the auth headers already declare one.
|
||||
- The `cacheKey` field is exposed for consumers that want
|
||||
request-level dedup but is intentionally not consulted by the
|
||||
detector itself — caching is the consumer's concern.
|
||||
Reference in New Issue
Block a user