import { getLocaleCodes, isValidLocale } from './helpers.js'; /** Cookie name the template uses to persist a visitor's locale choice. */ /** * Cookie name for the persisted locale choice. Uses NEXT_LOCALE — the convention * Next.js and its i18n ecosystem expect — so the cookie is interoperable with * other libraries that read the active locale (instead of a plugin-specific * name). Written only under functional consent; cleared when that consent is * withdrawn (see consent cookieMap). */ export const LOCALE_COOKIE_NAME = 'NEXT_LOCALE'; /** * Resolves which locale to serve, in priority order: * 1. Cookie (explicit prior choice) * 2. Accept-Language header (best match against configured locales) * 3. Configured default locale * * Pure function — the template feeds it request data and acts on the result * (redirect, cookie set). No Next.js or request objects here. */ export function negotiateLocale({ acceptLanguage, config, cookieLocale }) { // 1. Explicit prior choice wins if (cookieLocale && isValidLocale(cookieLocale, config)) { return cookieLocale; } // 2. Best match from Accept-Language const fromHeader = matchAcceptLanguage(acceptLanguage, config); if (fromHeader) { return fromHeader; } // 3. Fall back to configured default return config.defaultLocale; } /** * Parses an Accept-Language header and returns the best-matching configured * locale, or undefined if none match. * * Matches on the primary subtag (e.g. "en-US" matches configured "en"), * respecting the header's quality-value ordering. */ export function matchAcceptLanguage(acceptLanguage, config) { if (!acceptLanguage) { return undefined; } const available = getLocaleCodes(config); const ranked = parseAcceptLanguage(acceptLanguage); for (const tag of ranked){ // Exact match (e.g. "pt-BR" === "pt-BR") const exact = available.find((code)=>code.toLowerCase() === tag); if (exact) { return exact; } // Primary-subtag match (e.g. "en-us" → "en") const primary = tag.split('-')[0]; const partial = available.find((code)=>code.toLowerCase().split('-')[0] === primary); if (partial) { return partial; } } return undefined; } /** * Parses an Accept-Language header into locale tags ordered by descending * quality value. Tags are lowercased for comparison. * * "en-US,en;q=0.9,pl;q=0.8" → ["en-us", "en", "pl"] */ function parseAcceptLanguage(header) { return header.split(',').map((part)=>{ const [tag, ...params] = part.trim().split(';'); const qParam = params.find((p)=>p.trim().startsWith('q=')); const quality = qParam ? parseFloat(qParam.split('=')[1]) : 1; return { quality: Number.isNaN(quality) ? 0 : quality, tag: tag.trim().toLowerCase() }; }).filter((entry)=>entry.tag && entry.tag !== '*').sort((a, b)=>b.quality - a.quality).map((entry)=>entry.tag); } //# sourceMappingURL=negotiateLocale.js.map