# `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.