first commit
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
# `bcp47-negotiate.ts` — default BCP-47 negotiator
|
||||
|
||||
## Purpose
|
||||
|
||||
`bcp47Negotiate(raw, supported, fallback)` is the default negotiator
|
||||
wired into `createDetectorChain` when no `options.negotiate` is
|
||||
supplied. It maps a detector's raw locale output onto a value the
|
||||
application actually supports without forcing the configured
|
||||
fallback prematurely. Returning `null` on miss is intentional — the
|
||||
chain advances to the next detector instead of committing to the
|
||||
fallback before every detector has been consulted.
|
||||
|
||||
The implementation handles the three cases the upstream
|
||||
`LocaleResolver.resolve()` happy path covered: exact match,
|
||||
short-code expansion via `LOCALE_BCP47_MAP` from
|
||||
`@suaveplan/i18n-shared`, and language-subtag fallback.
|
||||
|
||||
## Features
|
||||
|
||||
- Exact-match short-circuit: returns the raw input unchanged when it
|
||||
appears in `supported`.
|
||||
- Short-code expansion via `LOCALE_BCP47_MAP` (`"en"` → `"en-GB"`).
|
||||
- Language-subtag matching falls back to the first supported entry
|
||||
whose primary language matches the raw input's primary language.
|
||||
- Strict null on miss — the negotiator never invents a value.
|
||||
- Pure function, no internal state, safe to call from any context.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { bcp47Negotiate } from "@suaveplan/i18n-detector/chain";
|
||||
|
||||
bcp47Negotiate("fr-FR", ["en-GB", "fr-FR"], "en-GB"); // "fr-FR"
|
||||
bcp47Negotiate("en", ["en-GB", "fr-FR"], "en-GB"); // "en-GB"
|
||||
bcp47Negotiate("zh-Hant-TW", ["zh-CN", "en-GB"], "en-GB"); // "zh-CN"
|
||||
bcp47Negotiate("xx-XX", ["en-GB"], "en-GB"); // null
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
bcp47Negotiate,
|
||||
createDetectorChain,
|
||||
type NegotiateFn,
|
||||
} from "@suaveplan/i18n-detector";
|
||||
|
||||
// Wrap the default with a per-request override map.
|
||||
const negotiate: NegotiateFn = (raw, supported, fallback) => {
|
||||
if (raw === "zh-Hans") return "zh-CN";
|
||||
return bcp47Negotiate(raw, supported, fallback);
|
||||
};
|
||||
|
||||
const chain = createDetectorChain(detectors, { negotiate });
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `bcp47Negotiate: NegotiateFn` — pure function;
|
||||
`(raw: string, supported: readonly string[], fallback: string) => string | null`.
|
||||
- Returns `null` when input is empty, non-string, or no supported
|
||||
match exists.
|
||||
- The `fallback` argument is accepted for parity with the
|
||||
`NegotiateFn` signature but is intentionally unused — the chain
|
||||
decides the fallback policy.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- Lookup order: exact → lower-cased short-code expansion → original
|
||||
short-code expansion → language-subtag scan.
|
||||
- `LOCALE_BCP47_MAP` is consulted twice (`lowered` first, raw form
|
||||
as a fallback) so mixed-case inputs like `"En-GB"` resolve via the
|
||||
same lookup path as `"en"`.
|
||||
- Whitespace-only and empty inputs return `null` before any lookup
|
||||
runs.
|
||||
Reference in New Issue
Block a user