Files
SnowLuma/.pnpm-store/v11/files/45/e8d5fcd73ae2497a7ecb12bcf28ee0c22bc32d059ba100aa30ff47fe7ddcfb3c49e4deac851c06a7b82d7833f11b62daf1acbb0c5e400cf161909d1674e8f6
T
TanTan 2a69b23b47
Dev Build / cleanup (push) Waiting to run
Dev Build / typecheck (push) Waiting to run
docs: 仓库链接指向本镜像仓库
2026-11-13 09:27:07 +08:00

209 lines
6.2 KiB
Plaintext

Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const require_utils_accept = require("../../utils/accept.js");
const require_helper_cookie_index = require("../../helper/cookie/index.js");
//#region src/middleware/language/language.ts
const 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: 31536e3,
httpOnly: true
},
debug: false
};
/**
* Parse Accept-Language header values with quality scores
* @param header Accept-Language header string
* @returns Array of parsed languages with quality scores
*/
function parseAcceptLanguage(header) {
return require_utils_accept.parseAccept(header).map(({ type, q }) => ({
lang: type,
q
}));
}
/**
* Validate and normalize language codes
* @param lang Language code to normalize
* @param options Detector options
* @returns Normalized language code or undefined
*/
const normalizeLanguage = (lang, options) => {
if (!lang) return;
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 exactIndex = compSupported.indexOf(compLang);
if (exactIndex !== -1) return options.supportedLanguages[exactIndex];
let longestMatchIndex = -1;
let longestMatchLength = -1;
for (let i = 0; i < compSupported.length; i++) {
const candidate = compSupported[i];
if (candidate.length < compLang.length && candidate.length > longestMatchLength && compLang.startsWith(candidate) && compLang[candidate.length] === "-") {
longestMatchIndex = i;
longestMatchLength = candidate.length;
}
}
if (longestMatchIndex !== -1) return options.supportedLanguages[longestMatchIndex];
return;
} catch {
return;
}
};
/**
* Detects language from query parameter
*/
const detectFromQuery = (c, options) => {
const query = c.req.query(options.lookupQueryString);
return normalizeLanguage(query, options);
};
/**
* Detects language from cookie
*/
const detectFromCookie = (c, options) => {
const cookie = require_helper_cookie_index.getCookie(c, options.lookupCookie);
return normalizeLanguage(cookie, options);
};
/**
* Detects language from Accept-Language header
*/
function detectFromHeader(c, options) {
try {
const acceptLanguage = c.req.header(options.lookupFromHeaderKey);
if (!acceptLanguage) return;
const languages = parseAcceptLanguage(acceptLanguage);
for (const { lang, q } of languages) {
if (q === 0) continue;
const normalizedLang = normalizeLanguage(lang, options);
if (normalizedLang) return normalizedLang;
}
return;
} catch {
return;
}
}
/**
* Detects language from URL path
*/
function detectFromPath(c, options) {
const langSegment = new URL(c.req.url).pathname.split("/").filter(Boolean)[options.lookupFromPathIndex];
return normalizeLanguage(langSegment, options);
}
/**
* Collection of all language detection strategies
*/
const detectors = {
querystring: detectFromQuery,
cookie: detectFromCookie,
header: detectFromHeader,
path: detectFromPath
};
/**
* Validate detector options
* @param options Detector options to validate
* @throws Error if options are invalid
*/
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");
}
/**
* Cache detected language
*/
function cacheLanguage(c, language, options) {
if (!Array.isArray(options.caches) || !options.caches.includes("cookie")) return;
try {
require_helper_cookie_index.setCookie(c, options.lookupCookie, language, options.cookieOptions);
} catch (error) {
if (options.debug) console.error("Failed to cache language:", error);
}
}
/**
* Detect language from request
*/
const detectLanguage = (c, options) => {
let detectedLang;
for (const detectorName of options.order) {
const detector = detectors[detectorName];
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;
};
/**
* Language detector middleware factory
* @param userOptions Configuration options for the language detector
* @returns Hono middleware function
*
* @example
* ```ts
* type Variables = LanguageVariables
* const app = new Hono<{ Variables: Variables }>()
*
* app.use(
* languageDetector({
* supportedLanguages: ['en', 'ja'], // Must include fallback
* fallbackLanguage: 'en', // Required
* })
* )
*
* app.get('/', (c) => {
* const lang = c.get('language')
* return c.text(`Current language: ${lang}`)
* })
* ```
*/
const languageDetector = (userOptions) => {
const options = {
...DEFAULT_OPTIONS,
...userOptions,
cookieOptions: {
...DEFAULT_OPTIONS.cookieOptions,
...userOptions.cookieOptions
}
};
validateOptions(options);
return async function languageDetector(ctx, next) {
const lang = detectLanguage(ctx, options);
ctx.set("language", lang);
await next();
};
};
//#endregion
exports.DEFAULT_OPTIONS = DEFAULT_OPTIONS;
exports.detectFromCookie = detectFromCookie;
exports.detectFromHeader = detectFromHeader;
exports.detectFromPath = detectFromPath;
exports.detectFromQuery = detectFromQuery;
exports.detectors = detectors;
exports.languageDetector = languageDetector;
exports.normalizeLanguage = normalizeLanguage;
exports.parseAcceptLanguage = parseAcceptLanguage;
exports.validateOptions = validateOptions;