UNPKG

@intlayer/core

Version:

Includes core Intlayer functions like translation, dictionary, and utility functions shared across multiple packages.

132 lines (130 loc) 5.59 kB
const require_localization_localeResolver = require('./localeResolver.cjs'); //#region src/localization/localeDetector.ts /** * Constants */ const LANGUAGE_FORMAT_REGULAR_EXPRESSION = /^\s*([^\s\-;]+)(?:-([^\s;]+))?\s*(?:;(.*))?$/; const DEFAULT_QUALITY_SCORE = 1; /** * Enumeration for specificity weights. * Higher values indicate a more precise match. */ var SpecificityWeight = /* @__PURE__ */ function(SpecificityWeight$1) { SpecificityWeight$1[SpecificityWeight$1["None"] = 0] = "None"; SpecificityWeight$1[SpecificityWeight$1["Broad"] = 1] = "Broad"; SpecificityWeight$1[SpecificityWeight$1["Prefix"] = 2] = "Prefix"; SpecificityWeight$1[SpecificityWeight$1["Exact"] = 4] = "Exact"; return SpecificityWeight$1; }(SpecificityWeight || {}); /** * Parses a single language tag string from the Accept-Language header. * Example input: "en-US;q=0.8" */ const parseLanguageTag = (tagString, index) => { const match = LANGUAGE_FORMAT_REGULAR_EXPRESSION.exec(tagString); if (!match) return null; const languageCode = match[1]; const regionCode = match[2]; const parameters = match[3]; const fullLocale = regionCode ? `${languageCode}-${regionCode}` : languageCode; let qualityScore = DEFAULT_QUALITY_SCORE; if (parameters) { const parameterList = parameters.split(";"); for (const parameter of parameterList) { const [key, value] = parameter.split("="); if (key === "q") qualityScore = parseFloat(value); } } return { languageCode, regionCode, qualityScore, originalIndex: index, fullLocale }; }; /** * Parses the entire Accept-Language header string into a list of preferences. */ const parseAcceptLanguageHeader = (headerValue) => { const rawTags = headerValue.split(","); const preferences = []; for (let index = 0; index < rawTags.length; index++) { const parsedLanguage = parseLanguageTag(rawTags[index].trim(), index); if (parsedLanguage) preferences.push(parsedLanguage); } return preferences; }; /** * Calculates the specificity of a match between a provided language and a requested preference. */ const calculateMatchSpecificity = (providedLanguage, preference, providedIndex) => { const parsedProvided = parseLanguageTag(providedLanguage, providedIndex); if (!parsedProvided) return null; let specificityScore = SpecificityWeight.None; const preferenceFullLower = preference.fullLocale.toLowerCase(); const preferencePrefixLower = preference.languageCode.toLowerCase(); const providedFullLower = parsedProvided.fullLocale.toLowerCase(); const providedPrefixLower = parsedProvided.languageCode.toLowerCase(); if (preferenceFullLower === providedFullLower) specificityScore |= SpecificityWeight.Exact; else if (preferencePrefixLower === providedFullLower) specificityScore |= SpecificityWeight.Prefix; else if (preferenceFullLower === providedPrefixLower) specificityScore |= SpecificityWeight.Broad; else if (preference.fullLocale !== "*") return null; return { providedIndex, headerIndex: preference.originalIndex, qualityScore: preference.qualityScore, specificityScore }; }; /** * Determines the best match for a specific available language against the list of user accepted languages. */ const getBestMatchForLanguage = (providedLanguage, acceptedPreferences, providedIndex) => { let bestMatch = { headerIndex: -1, qualityScore: 0, specificityScore: 0, providedIndex }; for (const preference of acceptedPreferences) { const matchSpec = calculateMatchSpecificity(providedLanguage, preference, providedIndex); if (matchSpec) { if ((bestMatch.specificityScore - matchSpec.specificityScore || bestMatch.qualityScore - matchSpec.qualityScore || bestMatch.headerIndex - matchSpec.headerIndex) < 0) bestMatch = matchSpec; } } return bestMatch; }; /** * Comparator function to sort language matches. * Sorting order: * 1. Quality Score (Descending) * 2. Specificity Score (Descending) * 3. Order in Header (Ascending - lower index is better) * 4. Order in Provided List (Ascending) */ const compareMatchResults = (a, b) => { return b.qualityScore - a.qualityScore || b.specificityScore - a.specificityScore || a.headerIndex - b.headerIndex || a.providedIndex - b.providedIndex || 0; }; /** * Derives the list of preferred languages based on the Accept-Language header * and an optional list of available languages. */ const getPreferredLanguages = (acceptHeader, availableLanguages) => { const acceptedPreferences = parseAcceptLanguageHeader(acceptHeader === void 0 ? "*" : acceptHeader || ""); if (!availableLanguages) return acceptedPreferences.filter((preference) => preference.qualityScore > 0).sort((a, b) => b.qualityScore - a.qualityScore).map((preference) => preference.fullLocale); return availableLanguages.map((language, index) => getBestMatchForLanguage(language, acceptedPreferences, index)).filter((result) => result.qualityScore > 0).sort(compareMatchResults).map((result) => availableLanguages[result.providedIndex]); }; /** * Detects the locale from the request headers. * * Headers are provided by the browser/client and can be used to determine the user's preferred language. * This function intersects the user's `Accept-Language` header with the application's available locales. */ const localeDetector = (headers, availableLocales, defaultLocale) => { const acceptLanguageHeader = headers["accept-language"]; return require_localization_localeResolver.localeResolver(getPreferredLanguages(acceptLanguageHeader, availableLocales), availableLocales, defaultLocale); }; //#endregion exports.localeDetector = localeDetector; //# sourceMappingURL=localeDetector.cjs.map