Files
ipal-kit/dist/modules/i18n/negotiateLocale.js
T

68 lines
2.7 KiB
JavaScript

import { getLocaleCodes, isValidLocale } from './helpers.js';
/** Cookie name the template uses to persist a visitor's locale choice. */ export const LOCALE_COOKIE_NAME = 'ipal-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
//# sourceMappingURL=negotiateLocale.js.map