language.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. // src/middleware/language/language.ts
  2. import { setCookie, getCookie } from "../../helper/cookie/index.js";
  3. import { parseAccept } from "../../utils/accept.js";
  4. var DEFAULT_OPTIONS = {
  5. order: ["querystring", "cookie", "header"],
  6. lookupQueryString: "lang",
  7. lookupCookie: "language",
  8. lookupFromHeaderKey: "accept-language",
  9. lookupFromPathIndex: 0,
  10. caches: ["cookie"],
  11. ignoreCase: true,
  12. fallbackLanguage: "en",
  13. supportedLanguages: ["en"],
  14. cookieOptions: {
  15. sameSite: "Strict",
  16. secure: true,
  17. maxAge: 365 * 24 * 60 * 60,
  18. httpOnly: true
  19. },
  20. debug: false
  21. };
  22. function parseAcceptLanguage(header) {
  23. return parseAccept(header).map(({ type, q }) => ({ lang: type, q }));
  24. }
  25. var normalizeLanguage = (lang, options) => {
  26. if (!lang) {
  27. return void 0;
  28. }
  29. try {
  30. let normalizedLang = lang.trim();
  31. if (options.convertDetectedLanguage) {
  32. normalizedLang = options.convertDetectedLanguage(normalizedLang);
  33. }
  34. const compLang = options.ignoreCase ? normalizedLang.toLowerCase() : normalizedLang;
  35. const compSupported = options.supportedLanguages.map(
  36. (l) => options.ignoreCase ? l.toLowerCase() : l
  37. );
  38. const exactIndex = compSupported.indexOf(compLang);
  39. if (exactIndex !== -1) {
  40. return options.supportedLanguages[exactIndex];
  41. }
  42. const parts = compLang.split("-");
  43. for (let i = parts.length - 1; i > 0; i--) {
  44. const candidate = parts.slice(0, i).join("-");
  45. const prefixIndex = compSupported.indexOf(candidate);
  46. if (prefixIndex !== -1) {
  47. return options.supportedLanguages[prefixIndex];
  48. }
  49. }
  50. return void 0;
  51. } catch {
  52. return void 0;
  53. }
  54. };
  55. var detectFromQuery = (c, options) => {
  56. try {
  57. const query = c.req.query(options.lookupQueryString);
  58. return normalizeLanguage(query, options);
  59. } catch {
  60. return void 0;
  61. }
  62. };
  63. var detectFromCookie = (c, options) => {
  64. try {
  65. const cookie = getCookie(c, options.lookupCookie);
  66. return normalizeLanguage(cookie, options);
  67. } catch {
  68. return void 0;
  69. }
  70. };
  71. function detectFromHeader(c, options) {
  72. try {
  73. const acceptLanguage = c.req.header(options.lookupFromHeaderKey);
  74. if (!acceptLanguage) {
  75. return void 0;
  76. }
  77. const languages = parseAcceptLanguage(acceptLanguage);
  78. for (const { lang } of languages) {
  79. const normalizedLang = normalizeLanguage(lang, options);
  80. if (normalizedLang) {
  81. return normalizedLang;
  82. }
  83. }
  84. return void 0;
  85. } catch {
  86. return void 0;
  87. }
  88. }
  89. function detectFromPath(c, options) {
  90. try {
  91. const url = new URL(c.req.url);
  92. const pathSegments = url.pathname.split("/").filter(Boolean);
  93. const langSegment = pathSegments[options.lookupFromPathIndex];
  94. return normalizeLanguage(langSegment, options);
  95. } catch {
  96. return void 0;
  97. }
  98. }
  99. var detectors = {
  100. querystring: detectFromQuery,
  101. cookie: detectFromCookie,
  102. header: detectFromHeader,
  103. path: detectFromPath
  104. };
  105. function validateOptions(options) {
  106. if (!options.supportedLanguages.includes(options.fallbackLanguage)) {
  107. throw new Error("Fallback language must be included in supported languages");
  108. }
  109. if (options.lookupFromPathIndex < 0) {
  110. throw new Error("Path index must be non-negative");
  111. }
  112. if (!options.order.every((detector) => Object.keys(detectors).includes(detector))) {
  113. throw new Error("Invalid detector type in order array");
  114. }
  115. }
  116. function cacheLanguage(c, language, options) {
  117. if (!Array.isArray(options.caches) || !options.caches.includes("cookie")) {
  118. return;
  119. }
  120. try {
  121. setCookie(c, options.lookupCookie, language, options.cookieOptions);
  122. } catch (error) {
  123. if (options.debug) {
  124. console.error("Failed to cache language:", error);
  125. }
  126. }
  127. }
  128. var detectLanguage = (c, options) => {
  129. let detectedLang;
  130. for (const detectorName of options.order) {
  131. const detector = detectors[detectorName];
  132. if (!detector) {
  133. continue;
  134. }
  135. try {
  136. detectedLang = detector(c, options);
  137. if (detectedLang) {
  138. if (options.debug) {
  139. console.log(`Language detected from ${detectorName}: ${detectedLang}`);
  140. }
  141. break;
  142. }
  143. } catch (error) {
  144. if (options.debug) {
  145. console.error(`Error in ${detectorName} detector:`, error);
  146. }
  147. continue;
  148. }
  149. }
  150. const finalLang = detectedLang || options.fallbackLanguage;
  151. if (detectedLang && options.caches) {
  152. cacheLanguage(c, finalLang, options);
  153. }
  154. return finalLang;
  155. };
  156. var languageDetector = (userOptions) => {
  157. const options = {
  158. ...DEFAULT_OPTIONS,
  159. ...userOptions,
  160. cookieOptions: {
  161. ...DEFAULT_OPTIONS.cookieOptions,
  162. ...userOptions.cookieOptions
  163. }
  164. };
  165. validateOptions(options);
  166. return async function languageDetector2(ctx, next) {
  167. try {
  168. const lang = detectLanguage(ctx, options);
  169. ctx.set("language", lang);
  170. } catch (error) {
  171. if (options.debug) {
  172. console.error("Language detection failed:", error);
  173. }
  174. ctx.set("language", options.fallbackLanguage);
  175. }
  176. await next();
  177. };
  178. };
  179. export {
  180. DEFAULT_OPTIONS,
  181. detectFromCookie,
  182. detectFromHeader,
  183. detectFromPath,
  184. detectFromQuery,
  185. detectors,
  186. languageDetector,
  187. normalizeLanguage,
  188. parseAcceptLanguage,
  189. validateOptions
  190. };