/** * Locale primitives with NO dependency on the message catalogues, so the detection rules * can be exercised on their own (see scripts/test-registration.mjs → section 8). */ export const LOCALES = ['de', 'en'] as const export type Locale = (typeof LOCALES)[number] /** German is the default and the fallback everywhere. */ export const DEFAULT_LOCALE: Locale = 'de' export const isLocale = (value: unknown): value is Locale => value === 'de' || value === 'en' /** * Picks a locale from an Accept-Language header. * * German wins whenever the header is absent, unparsable or ambiguous: English is only * chosen when it is present with a STRICTLY higher quality value than German (which * includes the case where German is not listed at all). A bare `*` is ignored — a * wildcard states no preference, and no preference means German. * * "de-DE,de;q=0.9,en;q=0.8" → de (German preferred) * "en-GB,en;q=0.9" → en (no German at all) * "en;q=0.9,de;q=0.9" → de (tie → German) * "*" / "" / "fr-FR" → de (ambiguous or unsupported) */ export const localeFromAcceptLanguage = (header: string): Locale => { let deQ = -1 let enQ = -1 for (const part of String(header || '').split(',')) { const [rawTag, ...params] = part.trim().split(';') const tag = String(rawTag || '').trim().toLowerCase() if (!tag || tag === '*') continue let q = 1 for (const param of params) { const [key, value] = param.split('=') if (String(key || '').trim().toLowerCase() === 'q') { const parsed = Number.parseFloat(String(value || '').trim()) if (Number.isFinite(parsed)) q = parsed } } if (q <= 0) continue const base = tag.split('-')[0] if (base === 'de') deQ = Math.max(deQ, q) else if (base === 'en') enQ = Math.max(enQ, q) } return enQ > deQ ? 'en' : DEFAULT_LOCALE }