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