UNPKG

hono

Version:

Web framework built on Web Standards

179 lines (178 loc) 4.92 kB
// src/middleware/language/language.ts import { setCookie, getCookie } from "../../helper/cookie/index.js"; import { parseAccept } from "../../utils/accept.js"; var DEFAULT_OPTIONS = { order: ["querystring", "cookie", "header"], lookupQueryString: "lang", lookupCookie: "language", lookupFromHeaderKey: "accept-language", lookupFromPathIndex: 0, caches: ["cookie"], ignoreCase: true, fallbackLanguage: "en", supportedLanguages: ["en"], cookieOptions: { sameSite: "Strict", secure: true, maxAge: 365 * 24 * 60 * 60, httpOnly: true }, debug: false }; function parseAcceptLanguage(header) { return parseAccept(header).map(({ type, q }) => ({ lang: type, q })); } var normalizeLanguage = (lang, options) => { if (!lang) { return void 0; } try { let normalizedLang = lang.trim(); if (options.convertDetectedLanguage) { normalizedLang = options.convertDetectedLanguage(normalizedLang); } const compLang = options.ignoreCase ? normalizedLang.toLowerCase() : normalizedLang; const compSupported = options.supportedLanguages.map( (l) => options.ignoreCase ? l.toLowerCase() : l ); const matchedLang = compSupported.find((l) => l === compLang); return matchedLang ? options.supportedLanguages[compSupported.indexOf(matchedLang)] : void 0; } catch { return void 0; } }; var detectFromQuery = (c, options) => { try { const query = c.req.query(options.lookupQueryString); return normalizeLanguage(query, options); } catch { return void 0; } }; var detectFromCookie = (c, options) => { try { const cookie = getCookie(c, options.lookupCookie); return normalizeLanguage(cookie, options); } catch { return void 0; } }; function detectFromHeader(c, options) { try { const acceptLanguage = c.req.header(options.lookupFromHeaderKey); if (!acceptLanguage) { return void 0; } const languages = parseAcceptLanguage(acceptLanguage); for (const { lang } of languages) { const normalizedLang = normalizeLanguage(lang, options); if (normalizedLang) { return normalizedLang; } } return void 0; } catch { return void 0; } } function detectFromPath(c, options) { try { const pathSegments = c.req.path.split("/").filter(Boolean); const langSegment = pathSegments[options.lookupFromPathIndex]; return normalizeLanguage(langSegment, options); } catch { return void 0; } } var detectors = { querystring: detectFromQuery, cookie: detectFromCookie, header: detectFromHeader, path: detectFromPath }; function validateOptions(options) { if (!options.supportedLanguages.includes(options.fallbackLanguage)) { throw new Error("Fallback language must be included in supported languages"); } if (options.lookupFromPathIndex < 0) { throw new Error("Path index must be non-negative"); } if (!options.order.every((detector) => Object.keys(detectors).includes(detector))) { throw new Error("Invalid detector type in order array"); } } function cacheLanguage(c, language, options) { if (!Array.isArray(options.caches) || !options.caches.includes("cookie")) { return; } try { setCookie(c, options.lookupCookie, language, options.cookieOptions); } catch (error) { if (options.debug) { console.error("Failed to cache language:", error); } } } var detectLanguage = (c, options) => { let detectedLang; for (const detectorName of options.order) { const detector = detectors[detectorName]; if (!detector) { continue; } try { detectedLang = detector(c, options); if (detectedLang) { if (options.debug) { console.log(`Language detected from ${detectorName}: ${detectedLang}`); } break; } } catch (error) { if (options.debug) { console.error(`Error in ${detectorName} detector:`, error); } continue; } } const finalLang = detectedLang || options.fallbackLanguage; if (detectedLang && options.caches) { cacheLanguage(c, finalLang, options); } return finalLang; }; var languageDetector = (userOptions) => { const options = { ...DEFAULT_OPTIONS, ...userOptions, cookieOptions: { ...DEFAULT_OPTIONS.cookieOptions, ...userOptions.cookieOptions } }; validateOptions(options); return async function languageDetector2(ctx, next) { try { const lang = detectLanguage(ctx, options); ctx.set("language", lang); } catch (error) { if (options.debug) { console.error("Language detection failed:", error); } ctx.set("language", options.fallbackLanguage); } await next(); }; }; export { DEFAULT_OPTIONS, detectFromCookie, detectFromHeader, detectFromPath, detectFromQuery, detectors, languageDetector, normalizeLanguage, parseAcceptLanguage, validateOptions };