@intlayer/core 9.5.12 → 9.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/dist/cjs/formatters/compact.cjs +1 -2
  2. package/dist/cjs/formatters/compact.cjs.map +1 -1
  3. package/dist/cjs/formatters/currency.cjs +1 -2
  4. package/dist/cjs/formatters/currency.cjs.map +1 -1
  5. package/dist/cjs/formatters/date.cjs +1 -2
  6. package/dist/cjs/formatters/date.cjs.map +1 -1
  7. package/dist/cjs/formatters/list.cjs +1 -2
  8. package/dist/cjs/formatters/list.cjs.map +1 -1
  9. package/dist/cjs/formatters/number.cjs +1 -2
  10. package/dist/cjs/formatters/number.cjs.map +1 -1
  11. package/dist/cjs/formatters/percentage.cjs +1 -2
  12. package/dist/cjs/formatters/percentage.cjs.map +1 -1
  13. package/dist/cjs/formatters/relativeTime.cjs +1 -2
  14. package/dist/cjs/formatters/relativeTime.cjs.map +1 -1
  15. package/dist/cjs/formatters/units.cjs +1 -2
  16. package/dist/cjs/formatters/units.cjs.map +1 -1
  17. package/dist/cjs/index.cjs +1 -0
  18. package/dist/cjs/localization/localeDetector.cjs +60 -22
  19. package/dist/cjs/localization/localeDetector.cjs.map +1 -1
  20. package/dist/cjs/markdown/constants.cjs +5 -2
  21. package/dist/cjs/markdown/constants.cjs.map +1 -1
  22. package/dist/cjs/transpiler/index.cjs +1 -0
  23. package/dist/cjs/transpiler/plural/index.cjs +1 -0
  24. package/dist/cjs/transpiler/plural/plural.cjs +20 -1
  25. package/dist/cjs/transpiler/plural/plural.cjs.map +1 -1
  26. package/dist/cjs/utils/intl.cjs +2 -27
  27. package/dist/cjs/utils/intl.cjs.map +1 -1
  28. package/dist/esm/formatters/compact.mjs +1 -2
  29. package/dist/esm/formatters/compact.mjs.map +1 -1
  30. package/dist/esm/formatters/currency.mjs +1 -2
  31. package/dist/esm/formatters/currency.mjs.map +1 -1
  32. package/dist/esm/formatters/date.mjs +1 -2
  33. package/dist/esm/formatters/date.mjs.map +1 -1
  34. package/dist/esm/formatters/list.mjs +1 -2
  35. package/dist/esm/formatters/list.mjs.map +1 -1
  36. package/dist/esm/formatters/number.mjs +1 -2
  37. package/dist/esm/formatters/number.mjs.map +1 -1
  38. package/dist/esm/formatters/percentage.mjs +1 -2
  39. package/dist/esm/formatters/percentage.mjs.map +1 -1
  40. package/dist/esm/formatters/relativeTime.mjs +1 -2
  41. package/dist/esm/formatters/relativeTime.mjs.map +1 -1
  42. package/dist/esm/formatters/units.mjs +1 -2
  43. package/dist/esm/formatters/units.mjs.map +1 -1
  44. package/dist/esm/index.mjs +2 -2
  45. package/dist/esm/localization/localeDetector.mjs +60 -22
  46. package/dist/esm/localization/localeDetector.mjs.map +1 -1
  47. package/dist/esm/markdown/constants.mjs +5 -2
  48. package/dist/esm/markdown/constants.mjs.map +1 -1
  49. package/dist/esm/transpiler/index.mjs +2 -2
  50. package/dist/esm/transpiler/plural/index.mjs +2 -2
  51. package/dist/esm/transpiler/plural/plural.mjs +20 -2
  52. package/dist/esm/transpiler/plural/plural.mjs.map +1 -1
  53. package/dist/esm/utils/intl.mjs +2 -27
  54. package/dist/esm/utils/intl.mjs.map +1 -1
  55. package/dist/types/formatters/compact.d.ts.map +1 -1
  56. package/dist/types/formatters/currency.d.ts.map +1 -1
  57. package/dist/types/formatters/date.d.ts.map +1 -1
  58. package/dist/types/formatters/list.d.ts.map +1 -1
  59. package/dist/types/formatters/number.d.ts.map +1 -1
  60. package/dist/types/formatters/percentage.d.ts.map +1 -1
  61. package/dist/types/formatters/relativeTime.d.ts.map +1 -1
  62. package/dist/types/formatters/units.d.ts.map +1 -1
  63. package/dist/types/index.d.ts +2 -2
  64. package/dist/types/localization/localeDetector.d.ts +2 -0
  65. package/dist/types/localization/localeDetector.d.ts.map +1 -1
  66. package/dist/types/markdown/constants.d.ts +4 -1
  67. package/dist/types/markdown/constants.d.ts.map +1 -1
  68. package/dist/types/transpiler/index.d.ts +2 -2
  69. package/dist/types/transpiler/plural/index.d.ts +2 -2
  70. package/dist/types/transpiler/plural/plural.d.ts +31 -2
  71. package/dist/types/transpiler/plural/plural.d.ts.map +1 -1
  72. package/dist/types/utils/intl.d.ts +1 -1
  73. package/dist/types/utils/intl.d.ts.map +1 -1
  74. package/package.json +7 -7
@@ -1 +1 @@
1
- {"version":3,"file":"plural.cjs","names":["formatNodeType","PLURAL"],"sources":["../../../../src/transpiler/plural/plural.ts"],"sourcesContent":["import type { TypedNodeModel } from '@intlayer/types/nodeType';\nimport { formatNodeType, PLURAL } from '@intlayer/types/nodeType';\n\nexport type PluralCategory = 'zero' | 'one' | 'two' | 'few' | 'many' | 'other';\n\nexport type PluralContentState<Content> = Partial<\n Record<PluralCategory, Content>\n> & {\n other: Content;\n};\n\nexport type PluralContent<Content = unknown> = TypedNodeModel<\n typeof PLURAL,\n PluralContentState<Content>\n>;\n\n/**\n * Function intended to be used to build intlayer dictionaries.\n *\n * Allow to pick a content based on a quantity using CLDR pluralization rules\n * (`Intl.PluralRules`). The selected category depends on the active locale.\n *\n * Supported categories: `zero`, `one`, `two`, `few`, `many`, `other`.\n * `other` is required as the fallback.\n *\n * The string content can include a `{{count}}` placeholder, which is\n * automatically replaced with the provided count.\n *\n * Usage:\n *\n * ```ts\n * plural({\n * one: '{{count}} вакансия',\n * few: '{{count}} вакансии',\n * many: '{{count}} вакансий',\n * other: '{{count}} вакансий',\n * });\n * ```\n */\nconst plural = <Content = unknown>(\n content: PluralContentState<Content>\n): PluralContent<Content> => formatNodeType(PLURAL, content);\n\nexport { plural };\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCA,MAAM,UACJ,gBAC2BA,yCAAeC,iCAAQ,OAAO"}
1
+ {"version":3,"file":"plural.cjs","names":["formatNodeType","PLURAL"],"sources":["../../../../src/transpiler/plural/plural.ts"],"sourcesContent":["import type { TypedNodeModel } from '@intlayer/types/nodeType';\nimport { formatNodeType, PLURAL } from '@intlayer/types/nodeType';\n\nexport type PluralCategory = 'zero' | 'one' | 'two' | 'few' | 'many' | 'other';\n\nexport type PluralContentState<Content> = Partial<\n Record<PluralCategory, Content>\n> & {\n other: Content;\n};\n\n/** Name of the value a plural selects on when no `variable` is declared. */\nexport const DEFAULT_PLURAL_VARIABLE = 'count';\n\nexport type PluralOptions<Variable extends string = string> = {\n /**\n * Name of the value the plural category is selected on.\n *\n * Defaults to `count`. Set it to nest plurals over independent quantities:\n * the resolved function then takes `{ [variable]: number, … }`.\n */\n variable?: Variable;\n};\n\nexport type PluralContent<\n Content = unknown,\n Variable extends string = typeof DEFAULT_PLURAL_VARIABLE,\n> = TypedNodeModel<\n typeof PLURAL,\n PluralContentState<Content>,\n { variable?: Variable }\n>;\n\n/**\n * Function intended to be used to build intlayer dictionaries.\n *\n * Allow to pick a content based on a quantity using CLDR pluralization rules\n * (`Intl.PluralRules`). The selected category depends on the active locale.\n *\n * Supported categories: `zero`, `one`, `two`, `few`, `many`, `other`.\n * `other` is required as the fallback.\n *\n * The string content can include a `{{count}}` placeholder, which is\n * automatically replaced with the provided count.\n *\n * Usage:\n *\n * ```ts\n * plural({\n * one: '{{count}} вакансия',\n * few: '{{count}} вакансии',\n * many: '{{count}} вакансий',\n * other: '{{count}} вакансий',\n * });\n * ```\n *\n * Nest plurals declaring a `variable` to select on several quantities:\n *\n * ```ts\n * plural(\n * {\n * one: plural(\n * { one: '{{files}} file in {{folders}} folder', other: '…' },\n * { variable: 'folders' }\n * ),\n * other: plural({ … }, { variable: 'folders' }),\n * },\n * { variable: 'files' }\n * );\n * // content.filesInFolders({ files: 1, folders: 2 })\n * ```\n */\nconst plural = <\n Content = unknown,\n const Variable extends string = typeof DEFAULT_PLURAL_VARIABLE,\n>(\n content: PluralContentState<Content>,\n options?: PluralOptions<Variable>\n): PluralContent<Content, Variable> =>\n formatNodeType(\n PLURAL,\n content,\n options?.variable ? { variable: options.variable } : undefined\n );\n\nexport { plural };\n"],"mappings":";;;;;AAYA,MAAa,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4DvC,MAAM,UAIJ,SACA,gBAEAA,yCACEC,iCACA,SACA,SAAS,WAAW,EAAE,UAAU,QAAQ,SAAS,IAAI,MACvD"}
@@ -1,32 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
- let _intlayer_config_built = require("@intlayer/config/built");
2
+ const require_interpreter_resolveInterpreterLocale = require('../interpreter/resolveInterpreterLocale.cjs');
3
3
 
4
4
  //#region src/utils/intl.ts
5
- /**
6
- * Cached Intl helper – drop‑in replacement for the global `Intl` object.
7
- * ‑‑‑
8
- * • Uses a `Proxy` to lazily wrap every *constructor* hanging off `Intl` (NumberFormat, DateTimeFormat, …).
9
- * • Each wrapped constructor keeps an in‑memory cache keyed by `[locales, options]` so that identical requests
10
- * reuse the same heavy instance instead of reparsing CLDR data every time.
11
- * • A polyfill warning for `Intl.DisplayNames` is emitted only once and only in dev.
12
- * • The public API is fully type‑safe and mirrors the native `Intl` surface exactly –
13
- * you can treat `CachedIntl` just like the built‑in `Intl`.
14
- *
15
- * Usage @example:
16
- * ---------------
17
- * ```ts
18
- * import { CachedIntl } from "./cached-intl";
19
- *
20
- * const nf = CachedIntl.NumberFormat("en-US", { style: "currency", currency: "USD" });
21
- * console.log(nf.format(1234));
22
- *
23
- * const dn = CachedIntl.DisplayNames(["fr"], { type: "language" });
24
- * console.log(dn.of("en")); * → "anglais"
25
- *
26
- * You can also spin up an isolated instance with its own caches (handy in test suites):
27
- * const TestIntl = createCachedIntl();
28
- * ```
29
- */
30
5
  const MAX_CACHE_SIZE = 50;
31
6
  const cache = /* @__PURE__ */ new Map();
32
7
  const alreadyWarnedConstructors = /* @__PURE__ */ new Set();
@@ -106,7 +81,7 @@ const resolveIntlConstructor = (constructorName) => {
106
81
  return intlConstructorFallbacks[constructorName];
107
82
  };
108
83
  function getCachedIntl(intlConstructor, locale, options) {
109
- const resLoc = locale ?? _intlayer_config_built.internationalization?.defaultLocale;
84
+ const resLoc = require_interpreter_resolveInterpreterLocale.resolveInterpreterLocale(locale);
110
85
  const key = `${resLoc}|${options ? JSON.stringify(options) : ""}`;
111
86
  const cacheKey = intlConstructor;
112
87
  let ctorCache = cache.get(cacheKey);
@@ -1 +1 @@
1
- {"version":3,"file":"intl.cjs","names":["internationalization"],"sources":["../../../src/utils/intl.ts"],"sourcesContent":["/**\n * Cached Intl helper – drop‑in replacement for the global `Intl` object.\n * ‑‑‑\n * • Uses a `Proxy` to lazily wrap every *constructor* hanging off `Intl` (NumberFormat, DateTimeFormat, …).\n * • Each wrapped constructor keeps an in‑memory cache keyed by `[locales, options]` so that identical requests\n * reuse the same heavy instance instead of reparsing CLDR data every time.\n * • A polyfill warning for `Intl.DisplayNames` is emitted only once and only in dev.\n * • The public API is fully type‑safe and mirrors the native `Intl` surface exactly –\n * you can treat `CachedIntl` just like the built‑in `Intl`.\n *\n * Usage @example:\n * ---------------\n * ```ts\n * import { CachedIntl } from \"./cached-intl\";\n *\n * const nf = CachedIntl.NumberFormat(\"en-US\", { style: \"currency\", currency: \"USD\" });\n * console.log(nf.format(1234));\n *\n * const dn = CachedIntl.DisplayNames([\"fr\"], { type: \"language\" });\n * console.log(dn.of(\"en\")); * → \"anglais\"\n *\n * You can also spin up an isolated instance with its own caches (handy in test suites):\n * const TestIntl = createCachedIntl();\n * ```\n */\n\nimport { internationalization } from '@intlayer/config/built';\nimport type { LocalesValues } from '@intlayer/types/module_augmentation';\n\nconst MAX_CACHE_SIZE = 50;\nconst cache = new Map<any, Map<string, any>>();\n\n/**\n * Minimal `Intl.ListFormat` surface, declared locally so consumers do not need\n * the `ES2021.Intl` lib in their tsconfig.\n */\ntype ListFormatInstance = {\n format(list: Iterable<string>): string;\n formatToParts(\n list: Iterable<string>\n ): { type: 'element' | 'literal'; value: string }[];\n};\n\n/**\n * Minimal `Intl.Segmenter` surface, declared locally so consumers do not need\n * the `ES2022.Intl` lib in their tsconfig.\n */\ntype SegmenterInstance = {\n segment(input: string): Iterable<{ segment: string; index: number }>;\n};\n\n/**\n * Instance type produced by each `Intl` constructor this module can build.\n */\ntype IntlInstanceByName = {\n Collator: Intl.Collator;\n DateTimeFormat: Intl.DateTimeFormat;\n DisplayNames: Intl.DisplayNames;\n ListFormat: ListFormatInstance;\n Locale: Intl.Locale;\n NumberFormat: Intl.NumberFormat;\n PluralRules: Intl.PluralRules;\n RelativeTimeFormat: Intl.RelativeTimeFormat;\n Segmenter: SegmenterInstance;\n};\n\n/**\n * Names of the `Intl` constructors this module knows how to instantiate.\n */\nexport type IntlConstructorName = keyof IntlInstanceByName;\n\nconst alreadyWarnedConstructors = new Set<string>();\n\n/**\n * Warns once per missing `Intl` constructor, in development only.\n */\nconst warnMissingIntlConstructor = (constructorName: string): void => {\n if (process.env.NODE_ENV === 'production') return;\n if (alreadyWarnedConstructors.has(constructorName)) return;\n\n alreadyWarnedConstructors.add(constructorName);\n\n console.warn(\n `[intlayer] \\`Intl.${constructorName}\\` is not available in this JavaScript engine. ` +\n `A degraded fallback is used instead. ` +\n `On React Native, load a polyfill (e.g. \\`@formatjs/intl-${constructorName.toLowerCase()}/polyfill\\`) before rendering your app.`\n );\n};\n\n/**\n * Minimal stand-ins used when the engine ships without the matching `Intl`\n * constructor. Hermes — the default React Native engine — omits\n * `DisplayNames`, `ListFormat` and `Segmenter` in release builds unless the\n * app opts into the full ICU build or loads a polyfill. Without these shims,\n * `new Intl.DisplayNames(...)` throws\n * `TypeError: undefined cannot be used as a constructor` and takes the whole\n * app down at render time.\n *\n * Each shim mirrors only the surface Intlayer relies on, and returns the\n * un-localized input so rendering degrades rather than crashing.\n */\nconst intlConstructorFallbacks: Partial<\n Record<IntlConstructorName, new (locale?: any, options?: any) => any>\n> = {\n DisplayNames: class DisplayNamesFallback {\n /** Returns the requested code unchanged, since no CLDR data is available. */\n public of(code: string): string {\n return code;\n }\n },\n ListFormat: class ListFormatFallback {\n /** Joins the list with a comma, the most neutral separator available. */\n public format(list: Iterable<string>): string {\n return Array.from(list).join(', ');\n }\n\n /** Mirrors `Intl.ListFormat.prototype.formatToParts` shape. */\n public formatToParts(\n list: Iterable<string>\n ): { type: 'element' | 'literal'; value: string }[] {\n return Array.from(list).flatMap((value, index) =>\n index === 0\n ? [{ type: 'element' as const, value }]\n : [\n { type: 'literal' as const, value: ', ' },\n { type: 'element' as const, value },\n ]\n );\n }\n },\n Segmenter: class SegmenterFallback {\n /** Segments by code point, the closest approximation without ICU data. */\n public segment(input: string): { segment: string; index: number }[] {\n let index = 0;\n\n return Array.from(input).map((segment) => {\n const segmentStart = index;\n index += segment.length;\n\n return { segment, index: segmentStart };\n });\n }\n },\n};\n\n/**\n * Resolves an `Intl` constructor by name at call time, falling back to a\n * degraded shim when the engine does not provide it.\n *\n * Resolution is deliberately lazy: React Native apps install their polyfills\n * at startup, which may run after this module is first imported.\n */\nconst resolveIntlConstructor = (\n constructorName: IntlConstructorName\n): (new (locale?: any, options?: any) => any) | undefined => {\n const nativeConstructor = (\n Intl as unknown as Record<string, new (...args: any[]) => any>\n )[constructorName];\n\n if (typeof nativeConstructor === 'function') return nativeConstructor;\n\n warnMissingIntlConstructor(constructorName);\n\n return intlConstructorFallbacks[constructorName];\n};\n\ntype IntlConstructors = {\n [K in keyof typeof Intl as (typeof Intl)[K] extends new (\n ...args: any\n ) => any\n ? K\n : never]: (typeof Intl)[K];\n};\n\ntype ReplaceLocaleWithLocalesValues<T> = T extends new (\n locales: any,\n options?: infer Options\n) => infer Instance\n ? {\n new (locales?: LocalesValues, options?: Options): Instance;\n new (options?: Options & { locale?: LocalesValues }): Instance;\n (locales?: LocalesValues, options?: Options): Instance;\n (options?: Options & { locale?: LocalesValues }): Instance;\n }\n : T extends new (\n locales: any\n ) => infer Instance\n ? {\n new (locales?: LocalesValues): Instance;\n new (options?: { locale?: LocalesValues }): Instance;\n (locales?: LocalesValues): Instance;\n (options?: { locale?: LocalesValues }): Instance;\n }\n : T;\n\nexport type WrappedIntl = {\n [K in keyof typeof Intl]: K extends keyof IntlConstructors\n ? ReplaceLocaleWithLocalesValues<(typeof Intl)[K]>\n : (typeof Intl)[K];\n};\n\n/**\n * Generic caching instantiator for Intl constructors.\n *\n * Prefer passing the constructor *name* (e.g. `'DisplayNames'`) rather than the\n * constructor itself: the name is resolved lazily against the global `Intl`, so\n * late-installed polyfills are picked up and missing constructors degrade to a\n * fallback instead of throwing.\n */\nexport function getCachedIntl<Name extends IntlConstructorName>(\n constructorName: Name,\n locale?: LocalesValues | string,\n options?: any\n): IntlInstanceByName[Name];\nexport function getCachedIntl<T extends new (...args: any[]) => any>(\n Ctor: T,\n locale?: LocalesValues | string,\n options?: any\n): InstanceType<T>;\nexport function getCachedIntl(\n intlConstructor: IntlConstructorName | (new (...args: any[]) => any),\n locale?: LocalesValues | string,\n options?: any\n): any {\n const resLoc = locale ?? internationalization?.defaultLocale;\n\n const optKey = options ? JSON.stringify(options) : '';\n const key = `${resLoc}|${optKey}`;\n\n // Cache per name when available, so two constructors that are both missing\n // from the engine do not collide on a single `undefined` cache bucket.\n const cacheKey = intlConstructor;\n\n let ctorCache = cache.get(cacheKey);\n\n if (!ctorCache) {\n ctorCache = new Map();\n cache.set(cacheKey, ctorCache);\n }\n\n let instance = ctorCache.get(key);\n\n if (!instance) {\n const ResolvedConstructor =\n typeof intlConstructor === 'string'\n ? resolveIntlConstructor(intlConstructor)\n : intlConstructor;\n\n if (typeof ResolvedConstructor !== 'function') {\n throw new Error(\n `[intlayer] \\`Intl.${String(intlConstructor)}\\` is not available in this JavaScript engine and has no fallback. ` +\n `Load the matching polyfill before formatting.`\n );\n }\n\n if (ctorCache.size > MAX_CACHE_SIZE) ctorCache.clear();\n instance = new ResolvedConstructor(resLoc, options);\n ctorCache.set(key, instance);\n }\n return instance;\n}\n\n/**\n * Optional: Keep bindIntl if your library exports it publicly.\n * It now uses the much smaller getCachedIntl under the hood.\n */\nexport const bindIntl = (boundLocale: LocalesValues): WrappedIntl => {\n const bindWrap = (constructorName: IntlConstructorName) =>\n // function is used as a constructor, do not change in arrow function\n function intlConstructor(locales?: any, options?: any) {\n const isOptsFirst =\n locales !== null &&\n typeof locales === 'object' &&\n !Array.isArray(locales);\n const resOpts = isOptsFirst ? locales : options;\n const resLoc = isOptsFirst\n ? (resOpts as any).locale || boundLocale\n : locales || boundLocale;\n\n return getCachedIntl(constructorName, resLoc, resOpts);\n };\n\n return {\n ...Intl,\n Collator: bindWrap('Collator'),\n DateTimeFormat: bindWrap('DateTimeFormat'),\n DisplayNames: bindWrap('DisplayNames'),\n ListFormat: bindWrap('ListFormat'),\n NumberFormat: bindWrap('NumberFormat'),\n PluralRules: bindWrap('PluralRules'),\n RelativeTimeFormat: bindWrap('RelativeTimeFormat'),\n Locale: bindWrap('Locale'),\n Segmenter: bindWrap('Segmenter'),\n } as unknown as WrappedIntl;\n};\n\n// Add this to the bottom of utils/intl.ts ONLY if required for public API compatibility.\nexport const CachedIntl = {\n // function is used as a constructor, do not change in arrow function\n Collator: function Collator(locales?: any, options?: any) {\n return getCachedIntl('Collator', locales, options);\n },\n DateTimeFormat: function DateTimeFormat(locales?: any, options?: any) {\n return getCachedIntl('DateTimeFormat', locales, options);\n },\n DisplayNames: function DisplayNames(locales?: any, options?: any) {\n return getCachedIntl('DisplayNames', locales, options);\n },\n ListFormat: function ListFormat(locales?: any, options?: any) {\n return getCachedIntl('ListFormat', locales, options);\n },\n NumberFormat: function NumberFormat(locales?: any, options?: any) {\n return getCachedIntl('NumberFormat', locales, options);\n },\n PluralRules: function PluralRules(locales?: any, options?: any) {\n return getCachedIntl('PluralRules', locales, options);\n },\n RelativeTimeFormat: function RelativeTimeFormat(\n locales?: any,\n options?: any\n ) {\n return getCachedIntl('RelativeTimeFormat', locales, options);\n },\n Segmenter: function Segmenter(locales?: any, options?: any) {\n return getCachedIntl('Segmenter', locales, options);\n },\n} as any; // Cast to 'any' internally to avoid TS readonly errors\n\nexport { CachedIntl as Intl };\n"],"mappings":"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"}
1
+ {"version":3,"file":"intl.cjs","names":["resolveInterpreterLocale"],"sources":["../../../src/utils/intl.ts"],"sourcesContent":["/**\n * Cached Intl helper – drop‑in replacement for the global `Intl` object.\n * ‑‑‑\n * • Uses a `Proxy` to lazily wrap every *constructor* hanging off `Intl` (NumberFormat, DateTimeFormat, …).\n * • Each wrapped constructor keeps an in‑memory cache keyed by `[locales, options]` so that identical requests\n * reuse the same heavy instance instead of reparsing CLDR data every time.\n * • A polyfill warning for `Intl.DisplayNames` is emitted only once and only in dev.\n * • The public API is fully type‑safe and mirrors the native `Intl` surface exactly –\n * you can treat `CachedIntl` just like the built‑in `Intl`.\n *\n * Usage @example:\n * ---------------\n * ```ts\n * import { CachedIntl } from \"./cached-intl\";\n *\n * const nf = CachedIntl.NumberFormat(\"en-US\", { style: \"currency\", currency: \"USD\" });\n * console.log(nf.format(1234));\n *\n * const dn = CachedIntl.DisplayNames([\"fr\"], { type: \"language\" });\n * console.log(dn.of(\"en\")); * → \"anglais\"\n *\n * You can also spin up an isolated instance with its own caches (handy in test suites):\n * const TestIntl = createCachedIntl();\n * ```\n */\n\nimport type { LocalesValues } from '@intlayer/types/module_augmentation';\nimport { resolveInterpreterLocale } from '../interpreter/resolveInterpreterLocale';\n\nconst MAX_CACHE_SIZE = 50;\nconst cache = new Map<any, Map<string, any>>();\n\n/**\n * Minimal `Intl.ListFormat` surface, declared locally so consumers do not need\n * the `ES2021.Intl` lib in their tsconfig.\n */\ntype ListFormatInstance = {\n format(list: Iterable<string>): string;\n formatToParts(\n list: Iterable<string>\n ): { type: 'element' | 'literal'; value: string }[];\n};\n\n/**\n * Minimal `Intl.Segmenter` surface, declared locally so consumers do not need\n * the `ES2022.Intl` lib in their tsconfig.\n */\ntype SegmenterInstance = {\n segment(input: string): Iterable<{ segment: string; index: number }>;\n};\n\n/**\n * Instance type produced by each `Intl` constructor this module can build.\n */\ntype IntlInstanceByName = {\n Collator: Intl.Collator;\n DateTimeFormat: Intl.DateTimeFormat;\n DisplayNames: Intl.DisplayNames;\n ListFormat: ListFormatInstance;\n Locale: Intl.Locale;\n NumberFormat: Intl.NumberFormat;\n PluralRules: Intl.PluralRules;\n RelativeTimeFormat: Intl.RelativeTimeFormat;\n Segmenter: SegmenterInstance;\n};\n\n/**\n * Names of the `Intl` constructors this module knows how to instantiate.\n */\nexport type IntlConstructorName = keyof IntlInstanceByName;\n\nconst alreadyWarnedConstructors = new Set<string>();\n\n/**\n * Warns once per missing `Intl` constructor, in development only.\n */\nconst warnMissingIntlConstructor = (constructorName: string): void => {\n if (process.env.NODE_ENV === 'production') return;\n if (alreadyWarnedConstructors.has(constructorName)) return;\n\n alreadyWarnedConstructors.add(constructorName);\n\n console.warn(\n `[intlayer] \\`Intl.${constructorName}\\` is not available in this JavaScript engine. ` +\n `A degraded fallback is used instead. ` +\n `On React Native, load a polyfill (e.g. \\`@formatjs/intl-${constructorName.toLowerCase()}/polyfill\\`) before rendering your app.`\n );\n};\n\n/**\n * Minimal stand-ins used when the engine ships without the matching `Intl`\n * constructor. Hermes — the default React Native engine — omits\n * `DisplayNames`, `ListFormat` and `Segmenter` in release builds unless the\n * app opts into the full ICU build or loads a polyfill. Without these shims,\n * `new Intl.DisplayNames(...)` throws\n * `TypeError: undefined cannot be used as a constructor` and takes the whole\n * app down at render time.\n *\n * Each shim mirrors only the surface Intlayer relies on, and returns the\n * un-localized input so rendering degrades rather than crashing.\n */\nconst intlConstructorFallbacks: Partial<\n Record<IntlConstructorName, new (locale?: any, options?: any) => any>\n> = {\n DisplayNames: class DisplayNamesFallback {\n /** Returns the requested code unchanged, since no CLDR data is available. */\n public of(code: string): string {\n return code;\n }\n },\n ListFormat: class ListFormatFallback {\n /** Joins the list with a comma, the most neutral separator available. */\n public format(list: Iterable<string>): string {\n return Array.from(list).join(', ');\n }\n\n /** Mirrors `Intl.ListFormat.prototype.formatToParts` shape. */\n public formatToParts(\n list: Iterable<string>\n ): { type: 'element' | 'literal'; value: string }[] {\n return Array.from(list).flatMap((value, index) =>\n index === 0\n ? [{ type: 'element' as const, value }]\n : [\n { type: 'literal' as const, value: ', ' },\n { type: 'element' as const, value },\n ]\n );\n }\n },\n Segmenter: class SegmenterFallback {\n /** Segments by code point, the closest approximation without ICU data. */\n public segment(input: string): { segment: string; index: number }[] {\n let index = 0;\n\n return Array.from(input).map((segment) => {\n const segmentStart = index;\n index += segment.length;\n\n return { segment, index: segmentStart };\n });\n }\n },\n};\n\n/**\n * Resolves an `Intl` constructor by name at call time, falling back to a\n * degraded shim when the engine does not provide it.\n *\n * Resolution is deliberately lazy: React Native apps install their polyfills\n * at startup, which may run after this module is first imported.\n */\nconst resolveIntlConstructor = (\n constructorName: IntlConstructorName\n): (new (locale?: any, options?: any) => any) | undefined => {\n const nativeConstructor = (\n Intl as unknown as Record<string, new (...args: any[]) => any>\n )[constructorName];\n\n if (typeof nativeConstructor === 'function') return nativeConstructor;\n\n warnMissingIntlConstructor(constructorName);\n\n return intlConstructorFallbacks[constructorName];\n};\n\ntype IntlConstructors = {\n [K in keyof typeof Intl as (typeof Intl)[K] extends new (\n ...args: any\n ) => any\n ? K\n : never]: (typeof Intl)[K];\n};\n\ntype ReplaceLocaleWithLocalesValues<T> = T extends new (\n locales: any,\n options?: infer Options\n) => infer Instance\n ? {\n new (locales?: LocalesValues, options?: Options): Instance;\n new (options?: Options & { locale?: LocalesValues }): Instance;\n (locales?: LocalesValues, options?: Options): Instance;\n (options?: Options & { locale?: LocalesValues }): Instance;\n }\n : T extends new (\n locales: any\n ) => infer Instance\n ? {\n new (locales?: LocalesValues): Instance;\n new (options?: { locale?: LocalesValues }): Instance;\n (locales?: LocalesValues): Instance;\n (options?: { locale?: LocalesValues }): Instance;\n }\n : T;\n\nexport type WrappedIntl = {\n [K in keyof typeof Intl]: K extends keyof IntlConstructors\n ? ReplaceLocaleWithLocalesValues<(typeof Intl)[K]>\n : (typeof Intl)[K];\n};\n\n/**\n * Generic caching instantiator for Intl constructors.\n *\n * Prefer passing the constructor *name* (e.g. `'DisplayNames'`) rather than the\n * constructor itself: the name is resolved lazily against the global `Intl`, so\n * late-installed polyfills are picked up and missing constructors degrade to a\n * fallback instead of throwing.\n */\nexport function getCachedIntl<Name extends IntlConstructorName>(\n constructorName: Name,\n locale?: LocalesValues | string,\n options?: any\n): IntlInstanceByName[Name];\nexport function getCachedIntl<T extends new (...args: any[]) => any>(\n Ctor: T,\n locale?: LocalesValues | string,\n options?: any\n): InstanceType<T>;\nexport function getCachedIntl(\n intlConstructor: IntlConstructorName | (new (...args: any[]) => any),\n locale?: LocalesValues | string,\n options?: any\n): any {\n // No locale given: request locale → browser storage → default locale\n const resLoc = resolveInterpreterLocale(locale as LocalesValues | undefined);\n\n const optKey = options ? JSON.stringify(options) : '';\n const key = `${resLoc}|${optKey}`;\n\n // Cache per name when available, so two constructors that are both missing\n // from the engine do not collide on a single `undefined` cache bucket.\n const cacheKey = intlConstructor;\n\n let ctorCache = cache.get(cacheKey);\n\n if (!ctorCache) {\n ctorCache = new Map();\n cache.set(cacheKey, ctorCache);\n }\n\n let instance = ctorCache.get(key);\n\n if (!instance) {\n const ResolvedConstructor =\n typeof intlConstructor === 'string'\n ? resolveIntlConstructor(intlConstructor)\n : intlConstructor;\n\n if (typeof ResolvedConstructor !== 'function') {\n throw new Error(\n `[intlayer] \\`Intl.${String(intlConstructor)}\\` is not available in this JavaScript engine and has no fallback. ` +\n `Load the matching polyfill before formatting.`\n );\n }\n\n if (ctorCache.size > MAX_CACHE_SIZE) ctorCache.clear();\n instance = new ResolvedConstructor(resLoc, options);\n ctorCache.set(key, instance);\n }\n return instance;\n}\n\n/**\n * Optional: Keep bindIntl if your library exports it publicly.\n * It now uses the much smaller getCachedIntl under the hood.\n */\nexport const bindIntl = (boundLocale?: LocalesValues): WrappedIntl => {\n const bindWrap = (constructorName: IntlConstructorName) =>\n // function is used as a constructor, do not change in arrow function\n function intlConstructor(locales?: any, options?: any) {\n const isOptsFirst =\n locales !== null &&\n typeof locales === 'object' &&\n !Array.isArray(locales);\n const resOpts = isOptsFirst ? locales : options;\n const resLoc = isOptsFirst\n ? (resOpts as any).locale || boundLocale\n : locales || boundLocale;\n\n return getCachedIntl(constructorName, resLoc, resOpts);\n };\n\n return {\n ...Intl,\n Collator: bindWrap('Collator'),\n DateTimeFormat: bindWrap('DateTimeFormat'),\n DisplayNames: bindWrap('DisplayNames'),\n ListFormat: bindWrap('ListFormat'),\n NumberFormat: bindWrap('NumberFormat'),\n PluralRules: bindWrap('PluralRules'),\n RelativeTimeFormat: bindWrap('RelativeTimeFormat'),\n Locale: bindWrap('Locale'),\n Segmenter: bindWrap('Segmenter'),\n } as unknown as WrappedIntl;\n};\n\n// Add this to the bottom of utils/intl.ts ONLY if required for public API compatibility.\nexport const CachedIntl = {\n // function is used as a constructor, do not change in arrow function\n Collator: function Collator(locales?: any, options?: any) {\n return getCachedIntl('Collator', locales, options);\n },\n DateTimeFormat: function DateTimeFormat(locales?: any, options?: any) {\n return getCachedIntl('DateTimeFormat', locales, options);\n },\n DisplayNames: function DisplayNames(locales?: any, options?: any) {\n return getCachedIntl('DisplayNames', locales, options);\n },\n ListFormat: function ListFormat(locales?: any, options?: any) {\n return getCachedIntl('ListFormat', locales, options);\n },\n NumberFormat: function NumberFormat(locales?: any, options?: any) {\n return getCachedIntl('NumberFormat', locales, options);\n },\n PluralRules: function PluralRules(locales?: any, options?: any) {\n return getCachedIntl('PluralRules', locales, options);\n },\n RelativeTimeFormat: function RelativeTimeFormat(\n locales?: any,\n options?: any\n ) {\n return getCachedIntl('RelativeTimeFormat', locales, options);\n },\n Segmenter: function Segmenter(locales?: any, options?: any) {\n return getCachedIntl('Segmenter', locales, options);\n },\n} as any; // Cast to 'any' internally to avoid TS readonly errors\n\nexport { CachedIntl as Intl };\n"],"mappings":"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"}
@@ -1,5 +1,4 @@
1
1
  import { getCachedIntl } from "../utils/intl.mjs";
2
- import { internationalization } from "@intlayer/config/built";
3
2
 
4
3
  //#region src/formatters/compact.ts
5
4
  /**
@@ -13,7 +12,7 @@ import { internationalization } from "@intlayer/config/built";
13
12
  * compact("1000000", { locale: Locales.FRENCH, compactDisplay: "long" });
14
13
  * // "1 million"
15
14
  */
16
- const compact = (value, options) => getCachedIntl("NumberFormat", options?.locale ?? internationalization?.defaultLocale, {
15
+ const compact = (value, options) => getCachedIntl("NumberFormat", options?.locale, {
17
16
  ...options,
18
17
  notation: "compact"
19
18
  }).format(Number(value));
@@ -1 +1 @@
1
- {"version":3,"file":"compact.mjs","names":[],"sources":["../../../src/formatters/compact.ts"],"sourcesContent":["import { internationalization } from '@intlayer/config/built';\nimport type { LocalesValues } from '@intlayer/types/module_augmentation';\nimport { getCachedIntl } from '../utils/intl';\n\n/**\n * Formats a numeric value using compact notation (e.g., 1K, 1M, 1B)\n * based on locale and formatting options.\n *\n * @example\n * compact(1200); // \"1.2K\"\n *\n * @example\n * compact(\"1000000\", { locale: Locales.FRENCH, compactDisplay: \"long\" });\n * // \"1 million\"\n */\nexport const compact = (\n value: string | number,\n options?: Intl.NumberFormatOptions & { locale?: LocalesValues }\n): string =>\n getCachedIntl(\n 'NumberFormat',\n options?.locale ?? internationalization?.defaultLocale,\n\n {\n ...options,\n notation: 'compact',\n }\n ).format(Number(value));\n"],"mappings":";;;;;;;;;;;;;;;AAeA,MAAa,WACX,OACA,YAEA,cACE,gBACA,SAAS,UAAU,sBAAsB,eAEzC;CACE,GAAG;CACH,UAAU;AACZ,CACF,CAAC,CAAC,OAAO,OAAO,KAAK,CAAC"}
1
+ {"version":3,"file":"compact.mjs","names":[],"sources":["../../../src/formatters/compact.ts"],"sourcesContent":["import type { LocalesValues } from '@intlayer/types/module_augmentation';\nimport { getCachedIntl } from '../utils/intl';\n\n/**\n * Formats a numeric value using compact notation (e.g., 1K, 1M, 1B)\n * based on locale and formatting options.\n *\n * @example\n * compact(1200); // \"1.2K\"\n *\n * @example\n * compact(\"1000000\", { locale: Locales.FRENCH, compactDisplay: \"long\" });\n * // \"1 million\"\n */\nexport const compact = (\n value: string | number,\n options?: Intl.NumberFormatOptions & { locale?: LocalesValues }\n): string =>\n getCachedIntl('NumberFormat', options?.locale, {\n ...options,\n notation: 'compact',\n }).format(Number(value));\n"],"mappings":";;;;;;;;;;;;;;AAcA,MAAa,WACX,OACA,YAEA,cAAc,gBAAgB,SAAS,QAAQ;CAC7C,GAAG;CACH,UAAU;AACZ,CAAC,CAAC,CAAC,OAAO,OAAO,KAAK,CAAC"}
@@ -1,5 +1,4 @@
1
1
  import { getCachedIntl } from "../utils/intl.mjs";
2
- import { internationalization } from "@intlayer/config/built";
3
2
 
4
3
  //#region src/formatters/currency.ts
5
4
  /**
@@ -13,7 +12,7 @@ import { internationalization } from "@intlayer/config/built";
13
12
  * currency("5000", { locale: Locales.FRENCH, currency: "CAD", currencyDisplay: "code" });
14
13
  * // "5 000,00 CAD"
15
14
  */
16
- const currency = (value, options) => getCachedIntl("NumberFormat", options?.locale ?? internationalization?.defaultLocale, {
15
+ const currency = (value, options) => getCachedIntl("NumberFormat", options?.locale, {
17
16
  style: "currency",
18
17
  currency: options?.currency ?? "USD",
19
18
  currencyDisplay: options?.currencyDisplay ?? "symbol",
@@ -1 +1 @@
1
- {"version":3,"file":"currency.mjs","names":[],"sources":["../../../src/formatters/currency.ts"],"sourcesContent":["import { internationalization } from '@intlayer/config/built';\nimport type { LocalesValues } from '@intlayer/types/module_augmentation';\nimport { getCachedIntl } from '../utils/intl';\n\n/**\n * Formats a numeric or string value into a localized currency string using the Intl API.\n *\n * @example\n * currency(1234.5, { currency: 'EUR' });\n * // \"€1,234.50\"\n *\n * @example\n * currency(\"5000\", { locale: Locales.FRENCH, currency: \"CAD\", currencyDisplay: \"code\" });\n * // \"5 000,00 CAD\"\n */\nexport const currency = (\n value: string | number,\n options?: Intl.NumberFormatOptions & { locale?: LocalesValues }\n): string =>\n getCachedIntl(\n 'NumberFormat',\n options?.locale ?? internationalization?.defaultLocale,\n\n {\n style: 'currency',\n currency: options?.currency ?? 'USD',\n currencyDisplay: options?.currencyDisplay ?? 'symbol',\n minimumFractionDigits: options?.minimumFractionDigits ?? 2,\n maximumFractionDigits: options?.maximumFractionDigits ?? 2,\n ...options,\n }\n ).format(Number(value));\n"],"mappings":";;;;;;;;;;;;;;;AAeA,MAAa,YACX,OACA,YAEA,cACE,gBACA,SAAS,UAAU,sBAAsB,eAEzC;CACE,OAAO;CACP,UAAU,SAAS,YAAY;CAC/B,iBAAiB,SAAS,mBAAmB;CAC7C,uBAAuB,SAAS,yBAAyB;CACzD,uBAAuB,SAAS,yBAAyB;CACzD,GAAG;AACL,CACF,CAAC,CAAC,OAAO,OAAO,KAAK,CAAC"}
1
+ {"version":3,"file":"currency.mjs","names":[],"sources":["../../../src/formatters/currency.ts"],"sourcesContent":["import type { LocalesValues } from '@intlayer/types/module_augmentation';\nimport { getCachedIntl } from '../utils/intl';\n\n/**\n * Formats a numeric or string value into a localized currency string using the Intl API.\n *\n * @example\n * currency(1234.5, { currency: 'EUR' });\n * // \"€1,234.50\"\n *\n * @example\n * currency(\"5000\", { locale: Locales.FRENCH, currency: \"CAD\", currencyDisplay: \"code\" });\n * // \"5 000,00 CAD\"\n */\nexport const currency = (\n value: string | number,\n options?: Intl.NumberFormatOptions & { locale?: LocalesValues }\n): string =>\n getCachedIntl('NumberFormat', options?.locale, {\n style: 'currency',\n currency: options?.currency ?? 'USD',\n currencyDisplay: options?.currencyDisplay ?? 'symbol',\n minimumFractionDigits: options?.minimumFractionDigits ?? 2,\n maximumFractionDigits: options?.maximumFractionDigits ?? 2,\n ...options,\n }).format(Number(value));\n"],"mappings":";;;;;;;;;;;;;;AAcA,MAAa,YACX,OACA,YAEA,cAAc,gBAAgB,SAAS,QAAQ;CAC7C,OAAO;CACP,UAAU,SAAS,YAAY;CAC/B,iBAAiB,SAAS,mBAAmB;CAC7C,uBAAuB,SAAS,yBAAyB;CACzD,uBAAuB,SAAS,yBAAyB;CACzD,GAAG;AACL,CAAC,CAAC,CAAC,OAAO,OAAO,KAAK,CAAC"}
@@ -1,5 +1,4 @@
1
1
  import { getCachedIntl } from "../utils/intl.mjs";
2
- import { internationalization } from "@intlayer/config/built";
3
2
 
4
3
  //#region src/formatters/date.ts
5
4
  const presets = {
@@ -50,7 +49,7 @@ const presets = {
50
49
  const date = (date, options) => {
51
50
  const dateTime = new Date(date);
52
51
  const resolvedOptions = typeof options === "string" ? presets[options] ?? {} : options;
53
- const locale = (typeof options === "object" ? options?.locale : void 0) ?? internationalization?.defaultLocale;
52
+ const locale = typeof options === "object" ? options?.locale : void 0;
54
53
  return getCachedIntl("DateTimeFormat", locale, resolvedOptions).format(dateTime);
55
54
  };
56
55
 
@@ -1 +1 @@
1
- {"version":3,"file":"date.mjs","names":[],"sources":["../../../src/formatters/date.ts"],"sourcesContent":["import { internationalization } from '@intlayer/config/built';\nimport type { LocalesValues } from '@intlayer/types/module_augmentation';\nimport { getCachedIntl } from '../utils/intl';\n\nexport type DateTimePreset =\n | 'short'\n | 'long'\n | 'dateOnly'\n | 'timeOnly'\n | 'full';\n\nexport const presets: Record<DateTimePreset, Intl.DateTimeFormatOptions> = {\n short: {\n year: '2-digit',\n month: '2-digit',\n day: '2-digit',\n hour: '2-digit',\n minute: '2-digit',\n },\n long: {\n year: 'numeric',\n month: 'long',\n day: 'numeric',\n hour: 'numeric',\n minute: 'numeric',\n },\n full: {\n year: 'numeric',\n month: 'long',\n day: 'numeric',\n hour: 'numeric',\n minute: 'numeric',\n hour12: false,\n },\n dateOnly: {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n },\n timeOnly: {\n hour: 'numeric',\n minute: 'numeric',\n second: 'numeric',\n },\n};\n\n/**\n * Formats a date/time value into a localized string using Intl.DateTimeFormat.\n *\n * @example\n * date(new Date('2025-08-02T14:30:00Z'), { year: '2-digit', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' });\n * // \"08/02/25, 14:30\"\n *\n * @example\n * date(\"2025-08-02T14:30:00Z\", { locale: Locales.FRENCH, month: \"long\", day: \"numeric\" });\n * // \"2 août\"\n */\nexport const date = (\n date: Date | string | number,\n options?:\n | (Intl.DateTimeFormatOptions & { locale?: LocalesValues })\n | DateTimePreset\n): string => {\n const dateTime = new Date(date);\n\n const resolvedOptions =\n typeof options === 'string' ? (presets[options] ?? {}) : options;\n\n const locale =\n (typeof options === 'object' ? options?.locale : undefined) ??\n internationalization?.defaultLocale;\n\n const formatter = getCachedIntl('DateTimeFormat', locale, resolvedOptions);\n\n return formatter.format(dateTime);\n};\n"],"mappings":";;;;AAWA,MAAa,UAA8D;CACzE,OAAO;EACL,MAAM;EACN,OAAO;EACP,KAAK;EACL,MAAM;EACN,QAAQ;CACV;CACA,MAAM;EACJ,MAAM;EACN,OAAO;EACP,KAAK;EACL,MAAM;EACN,QAAQ;CACV;CACA,MAAM;EACJ,MAAM;EACN,OAAO;EACP,KAAK;EACL,MAAM;EACN,QAAQ;EACR,QAAQ;CACV;CACA,UAAU;EACR,MAAM;EACN,OAAO;EACP,KAAK;CACP;CACA,UAAU;EACR,MAAM;EACN,QAAQ;EACR,QAAQ;CACV;AACF;;;;;;;;;;;;AAaA,MAAa,QACX,MACA,YAGW;CACX,MAAM,WAAW,IAAI,KAAK,IAAI;CAE9B,MAAM,kBACJ,OAAO,YAAY,WAAY,QAAQ,YAAY,CAAC,IAAK;CAE3D,MAAM,UACH,OAAO,YAAY,WAAW,SAAS,SAAS,WACjD,sBAAsB;CAIxB,OAFkB,cAAc,kBAAkB,QAAQ,eAE3C,CAAC,CAAC,OAAO,QAAQ;AAClC"}
1
+ {"version":3,"file":"date.mjs","names":[],"sources":["../../../src/formatters/date.ts"],"sourcesContent":["import type { LocalesValues } from '@intlayer/types/module_augmentation';\nimport { getCachedIntl } from '../utils/intl';\n\nexport type DateTimePreset =\n | 'short'\n | 'long'\n | 'dateOnly'\n | 'timeOnly'\n | 'full';\n\nexport const presets: Record<DateTimePreset, Intl.DateTimeFormatOptions> = {\n short: {\n year: '2-digit',\n month: '2-digit',\n day: '2-digit',\n hour: '2-digit',\n minute: '2-digit',\n },\n long: {\n year: 'numeric',\n month: 'long',\n day: 'numeric',\n hour: 'numeric',\n minute: 'numeric',\n },\n full: {\n year: 'numeric',\n month: 'long',\n day: 'numeric',\n hour: 'numeric',\n minute: 'numeric',\n hour12: false,\n },\n dateOnly: {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n },\n timeOnly: {\n hour: 'numeric',\n minute: 'numeric',\n second: 'numeric',\n },\n};\n\n/**\n * Formats a date/time value into a localized string using Intl.DateTimeFormat.\n *\n * @example\n * date(new Date('2025-08-02T14:30:00Z'), { year: '2-digit', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' });\n * // \"08/02/25, 14:30\"\n *\n * @example\n * date(\"2025-08-02T14:30:00Z\", { locale: Locales.FRENCH, month: \"long\", day: \"numeric\" });\n * // \"2 août\"\n */\nexport const date = (\n date: Date | string | number,\n options?:\n | (Intl.DateTimeFormatOptions & { locale?: LocalesValues })\n | DateTimePreset\n): string => {\n const dateTime = new Date(date);\n\n const resolvedOptions =\n typeof options === 'string' ? (presets[options] ?? {}) : options;\n\n const locale = typeof options === 'object' ? options?.locale : undefined;\n\n const formatter = getCachedIntl('DateTimeFormat', locale, resolvedOptions);\n\n return formatter.format(dateTime);\n};\n"],"mappings":";;;AAUA,MAAa,UAA8D;CACzE,OAAO;EACL,MAAM;EACN,OAAO;EACP,KAAK;EACL,MAAM;EACN,QAAQ;CACV;CACA,MAAM;EACJ,MAAM;EACN,OAAO;EACP,KAAK;EACL,MAAM;EACN,QAAQ;CACV;CACA,MAAM;EACJ,MAAM;EACN,OAAO;EACP,KAAK;EACL,MAAM;EACN,QAAQ;EACR,QAAQ;CACV;CACA,UAAU;EACR,MAAM;EACN,OAAO;EACP,KAAK;CACP;CACA,UAAU;EACR,MAAM;EACN,QAAQ;EACR,QAAQ;CACV;AACF;;;;;;;;;;;;AAaA,MAAa,QACX,MACA,YAGW;CACX,MAAM,WAAW,IAAI,KAAK,IAAI;CAE9B,MAAM,kBACJ,OAAO,YAAY,WAAY,QAAQ,YAAY,CAAC,IAAK;CAE3D,MAAM,SAAS,OAAO,YAAY,WAAW,SAAS,SAAS;CAI/D,OAFkB,cAAc,kBAAkB,QAAQ,eAE3C,CAAC,CAAC,OAAO,QAAQ;AAClC"}
@@ -1,5 +1,4 @@
1
1
  import { getCachedIntl } from "../utils/intl.mjs";
2
- import { internationalization } from "@intlayer/config/built";
3
2
 
4
3
  //#region src/formatters/list.ts
5
4
  /**
@@ -17,7 +16,7 @@ import { internationalization } from "@intlayer/config/built";
17
16
  * list([1, 2, 3], { type: 'unit' });
18
17
  * // "1, 2, 3"
19
18
  */
20
- const list = (values, options) => getCachedIntl("ListFormat", options?.locale ?? internationalization?.defaultLocale, {
19
+ const list = (values, options) => getCachedIntl("ListFormat", options?.locale, {
21
20
  type: options?.type ?? "conjunction",
22
21
  style: options?.style ?? "long",
23
22
  ...options
@@ -1 +1 @@
1
- {"version":3,"file":"list.mjs","names":[],"sources":["../../../src/formatters/list.ts"],"sourcesContent":["import { internationalization } from '@intlayer/config/built';\nimport type { LocalesValues } from '@intlayer/types/module_augmentation';\nimport { getCachedIntl } from '../utils/intl';\n\n/** Locally defined subset of Intl.ListFormatOptions so consumers don't need ES2021.Intl in their lib. */\nexport type ListFormatOptions = {\n localeMatcher?: 'lookup' | 'best fit';\n type?: 'conjunction' | 'disjunction' | 'unit';\n style?: 'long' | 'short' | 'narrow';\n};\n\n/**\n * Formats an array of values into a localized list string using the Intl API.\n *\n * @example\n * list(['apple', 'banana', 'orange']);\n * // \"apple, banana, and orange\"\n *\n * @example\n * list(['red', 'green', 'blue'], { locale: Locales.FRENCH, type: 'disjunction' });\n * // \"rouge, vert ou bleu\"\n *\n * @example\n * list([1, 2, 3], { type: 'unit' });\n * // \"1, 2, 3\"\n */\nexport const list = (\n values: (string | number)[],\n options?: ListFormatOptions & { locale?: LocalesValues }\n): string =>\n getCachedIntl(\n 'ListFormat',\n options?.locale ?? internationalization?.defaultLocale,\n\n {\n type: options?.type ?? 'conjunction',\n style: options?.style ?? 'long',\n ...options,\n }\n ).format(values.map(String));\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA0BA,MAAa,QACX,QACA,YAEA,cACE,cACA,SAAS,UAAU,sBAAsB,eAEzC;CACE,MAAM,SAAS,QAAQ;CACvB,OAAO,SAAS,SAAS;CACzB,GAAG;AACL,CACF,CAAC,CAAC,OAAO,OAAO,IAAI,MAAM,CAAC"}
1
+ {"version":3,"file":"list.mjs","names":[],"sources":["../../../src/formatters/list.ts"],"sourcesContent":["import type { LocalesValues } from '@intlayer/types/module_augmentation';\nimport { getCachedIntl } from '../utils/intl';\n\n/** Locally defined subset of Intl.ListFormatOptions so consumers don't need ES2021.Intl in their lib. */\nexport type ListFormatOptions = {\n localeMatcher?: 'lookup' | 'best fit';\n type?: 'conjunction' | 'disjunction' | 'unit';\n style?: 'long' | 'short' | 'narrow';\n};\n\n/**\n * Formats an array of values into a localized list string using the Intl API.\n *\n * @example\n * list(['apple', 'banana', 'orange']);\n * // \"apple, banana, and orange\"\n *\n * @example\n * list(['red', 'green', 'blue'], { locale: Locales.FRENCH, type: 'disjunction' });\n * // \"rouge, vert ou bleu\"\n *\n * @example\n * list([1, 2, 3], { type: 'unit' });\n * // \"1, 2, 3\"\n */\nexport const list = (\n values: (string | number)[],\n options?: ListFormatOptions & { locale?: LocalesValues }\n): string =>\n getCachedIntl('ListFormat', options?.locale, {\n type: options?.type ?? 'conjunction',\n style: options?.style ?? 'long',\n ...options,\n }).format(values.map(String));\n"],"mappings":";;;;;;;;;;;;;;;;;;AAyBA,MAAa,QACX,QACA,YAEA,cAAc,cAAc,SAAS,QAAQ;CAC3C,MAAM,SAAS,QAAQ;CACvB,OAAO,SAAS,SAAS;CACzB,GAAG;AACL,CAAC,CAAC,CAAC,OAAO,OAAO,IAAI,MAAM,CAAC"}
@@ -1,5 +1,4 @@
1
1
  import { getCachedIntl } from "../utils/intl.mjs";
2
- import { internationalization } from "@intlayer/config/built";
3
2
 
4
3
  //#region src/formatters/number.ts
5
4
  /**
@@ -11,7 +10,7 @@ import { internationalization } from "@intlayer/config/built";
11
10
  * @example
12
11
  * number("1000000", { locale: Locales.FRENCH }); // "1 000 000"
13
12
  */
14
- const number = (value, { locale, ...options } = {}) => getCachedIntl("NumberFormat", locale ?? internationalization?.defaultLocale, options).format(Number(value));
13
+ const number = (value, { locale, ...options } = {}) => getCachedIntl("NumberFormat", locale, options).format(Number(value));
15
14
 
16
15
  //#endregion
17
16
  export { number };
@@ -1 +1 @@
1
- {"version":3,"file":"number.mjs","names":[],"sources":["../../../src/formatters/number.ts"],"sourcesContent":["import { internationalization } from '@intlayer/config/built';\nimport type { LocalesValues } from '@intlayer/types/module_augmentation';\nimport { getCachedIntl } from '../utils/intl';\n\n/**\n * Formats a numeric value using locale-aware formatting.\n *\n * @example\n * number(123456.789); // \"123,456.789\"\n *\n * @example\n * number(\"1000000\", { locale: Locales.FRENCH }); // \"1 000 000\"\n */\nexport const number = (\n value: string | number,\n {\n locale,\n ...options\n }: Intl.NumberFormatOptions & { locale?: LocalesValues } = {}\n): string =>\n getCachedIntl(\n 'NumberFormat',\n locale ?? internationalization?.defaultLocale,\n\n options\n ).format(Number(value));\n"],"mappings":";;;;;;;;;;;;;AAaA,MAAa,UACX,OACA,EACE,QACA,GAAG,YACsD,CAAC,MAE5D,cACE,gBACA,UAAU,sBAAsB,eAEhC,OACF,CAAC,CAAC,OAAO,OAAO,KAAK,CAAC"}
1
+ {"version":3,"file":"number.mjs","names":[],"sources":["../../../src/formatters/number.ts"],"sourcesContent":["import type { LocalesValues } from '@intlayer/types/module_augmentation';\nimport { getCachedIntl } from '../utils/intl';\n\n/**\n * Formats a numeric value using locale-aware formatting.\n *\n * @example\n * number(123456.789); // \"123,456.789\"\n *\n * @example\n * number(\"1000000\", { locale: Locales.FRENCH }); // \"1 000 000\"\n */\nexport const number = (\n value: string | number,\n {\n locale,\n ...options\n }: Intl.NumberFormatOptions & { locale?: LocalesValues } = {}\n): string =>\n getCachedIntl('NumberFormat', locale, options).format(Number(value));\n"],"mappings":";;;;;;;;;;;;AAYA,MAAa,UACX,OACA,EACE,QACA,GAAG,YACsD,CAAC,MAE5D,cAAc,gBAAgB,QAAQ,OAAO,CAAC,CAAC,OAAO,OAAO,KAAK,CAAC"}
@@ -1,5 +1,4 @@
1
1
  import { getCachedIntl } from "../utils/intl.mjs";
2
- import { internationalization } from "@intlayer/config/built";
3
2
 
4
3
  //#region src/formatters/percentage.ts
5
4
  /**
@@ -14,7 +13,7 @@ import { internationalization } from "@intlayer/config/built";
14
13
  const percentage = (value, { locale, ...options } = {}) => {
15
14
  let numericValue = Number(value);
16
15
  if (numericValue > 1) numericValue /= 100;
17
- return getCachedIntl("NumberFormat", locale ?? internationalization?.defaultLocale, {
16
+ return getCachedIntl("NumberFormat", locale, {
18
17
  style: "percent",
19
18
  ...options
20
19
  }).format(Number(numericValue));
@@ -1 +1 @@
1
- {"version":3,"file":"percentage.mjs","names":[],"sources":["../../../src/formatters/percentage.ts"],"sourcesContent":["import { internationalization } from '@intlayer/config/built';\nimport type { LocalesValues } from '@intlayer/types/module_augmentation';\nimport { getCachedIntl } from '../utils/intl';\n\n/**\n * Formats a number as a percentage string (e.g., 0.25 → \"25%\").\n *\n * @example\n * percentage(0.25); // \"25%\"\n *\n * @example\n * percentage(0.25, { minimumFractionDigits: 2 }); // \"25.00%\"\n */\nexport const percentage = (\n value: string | number,\n {\n locale,\n ...options\n }: Intl.NumberFormatOptions & { locale?: LocalesValues } = {}\n): string => {\n let numericValue = Number(value);\n\n // Normalize: if user passes 10, treat it as 10% instead of 1000%\n if (numericValue > 1) {\n numericValue /= 100;\n }\n\n const formatter = getCachedIntl(\n 'NumberFormat',\n locale ?? internationalization?.defaultLocale,\n\n {\n style: 'percent',\n ...options,\n }\n );\n\n return formatter.format(Number(numericValue));\n};\n"],"mappings":";;;;;;;;;;;;;AAaA,MAAa,cACX,OACA,EACE,QACA,GAAG,YACsD,CAAC,MACjD;CACX,IAAI,eAAe,OAAO,KAAK;CAG/B,IAAI,eAAe,GACjB,gBAAgB;CAalB,OAVkB,cAChB,gBACA,UAAU,sBAAsB,eAEhC;EACE,OAAO;EACP,GAAG;CACL,CAGa,CAAC,CAAC,OAAO,OAAO,YAAY,CAAC;AAC9C"}
1
+ {"version":3,"file":"percentage.mjs","names":[],"sources":["../../../src/formatters/percentage.ts"],"sourcesContent":["import type { LocalesValues } from '@intlayer/types/module_augmentation';\nimport { getCachedIntl } from '../utils/intl';\n\n/**\n * Formats a number as a percentage string (e.g., 0.25 → \"25%\").\n *\n * @example\n * percentage(0.25); // \"25%\"\n *\n * @example\n * percentage(0.25, { minimumFractionDigits: 2 }); // \"25.00%\"\n */\nexport const percentage = (\n value: string | number,\n {\n locale,\n ...options\n }: Intl.NumberFormatOptions & { locale?: LocalesValues } = {}\n): string => {\n let numericValue = Number(value);\n\n // Normalize: if user passes 10, treat it as 10% instead of 1000%\n if (numericValue > 1) {\n numericValue /= 100;\n }\n\n const formatter = getCachedIntl('NumberFormat', locale, {\n style: 'percent',\n ...options,\n });\n\n return formatter.format(Number(numericValue));\n};\n"],"mappings":";;;;;;;;;;;;AAYA,MAAa,cACX,OACA,EACE,QACA,GAAG,YACsD,CAAC,MACjD;CACX,IAAI,eAAe,OAAO,KAAK;CAG/B,IAAI,eAAe,GACjB,gBAAgB;CAQlB,OALkB,cAAc,gBAAgB,QAAQ;EACtD,OAAO;EACP,GAAG;CACL,CAEe,CAAC,CAAC,OAAO,OAAO,YAAY,CAAC;AAC9C"}
@@ -1,5 +1,4 @@
1
1
  import { getCachedIntl } from "../utils/intl.mjs";
2
- import { internationalization } from "@intlayer/config/built";
3
2
 
4
3
  //#region src/formatters/relativeTime.ts
5
4
  /**
@@ -33,7 +32,7 @@ const relativeTime = (from, to = /* @__PURE__ */ new Date(), options) => {
33
32
  const toDate = new Date(to);
34
33
  const unit = options?.unit ?? "second";
35
34
  const value = diffInUnit(fromDate, toDate, unit);
36
- return getCachedIntl("RelativeTimeFormat", options?.locale ?? internationalization?.defaultLocale, options).format(Math.round(value), unit);
35
+ return getCachedIntl("RelativeTimeFormat", options?.locale, options).format(Math.round(value), unit);
37
36
  };
38
37
 
39
38
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"relativeTime.mjs","names":[],"sources":["../../../src/formatters/relativeTime.ts"],"sourcesContent":["import { internationalization } from '@intlayer/config/built';\nimport type { LocalesValues } from '@intlayer/types/module_augmentation';\nimport { getCachedIntl } from '../utils/intl';\n\ntype RelativeTimeUnit = Intl.RelativeTimeFormatUnit;\n\n/**\n * Calculate the difference between 2 dates in the given unit.\n */\nconst diffInUnit = (from: Date, to: Date, unit: RelativeTimeUnit): number => {\n const msDiff = to.getTime() - from.getTime();\n const sec = msDiff / 1000;\n\n switch (unit) {\n case 'second':\n return sec;\n case 'minute':\n return sec / 60;\n case 'hour':\n return sec / 3600;\n case 'day':\n return sec / 86400;\n case 'month':\n return sec / (30 * 86400); // approx\n case 'quarter':\n return sec / (3 * 30 * 86400); // 3 months approx\n case 'year':\n return sec / (365 * 86400); // approx\n default:\n return sec;\n }\n};\n\n/**\n * Formats the difference between two dates as a localized relative time string.\n *\n * @example\n * relativeTime(new Date(Date.now() - 30000)); // \"30 seconds ago\"\n *\n * @example\n * relativeTime(\"2025-01-01\", new Date(), { locale: Locales.FRENCH, unit: \"day\" });\n * // \"il y a 443 jours\"\n */\nexport const relativeTime = (\n from: Date | string | number,\n to: Date | string | number = new Date(),\n options?: Intl.RelativeTimeFormatOptions & {\n locale?: LocalesValues;\n unit?: RelativeTimeUnit;\n }\n): string => {\n const fromDate = new Date(from);\n const toDate = new Date(to);\n const unit = options?.unit ?? 'second';\n\n const value = diffInUnit(fromDate, toDate, unit);\n\n return getCachedIntl(\n 'RelativeTimeFormat',\n options?.locale ?? internationalization?.defaultLocale,\n\n options\n ).format(Math.round(value), unit);\n};\n"],"mappings":";;;;;;;AASA,MAAM,cAAc,MAAY,IAAU,SAAmC;CAE3E,MAAM,OADS,GAAG,QAAQ,IAAI,KAAK,QAAQ,KACtB;CAErB,QAAQ,MAAR;EACE,KAAK,UACH,OAAO;EACT,KAAK,UACH,OAAO,MAAM;EACf,KAAK,QACH,OAAO,MAAM;EACf,KAAK,OACH,OAAO,MAAM;EACf,KAAK,SACH,OAAO,MAAO;EAChB,KAAK,WACH,OAAO,MAAO;EAChB,KAAK,QACH,OAAO,MAAO;EAChB,SACE,OAAO;CACX;AACF;;;;;;;;;;;AAYA,MAAa,gBACX,MACA,qBAA6B,IAAI,KAAK,GACtC,YAIW;CACX,MAAM,WAAW,IAAI,KAAK,IAAI;CAC9B,MAAM,SAAS,IAAI,KAAK,EAAE;CAC1B,MAAM,OAAO,SAAS,QAAQ;CAE9B,MAAM,QAAQ,WAAW,UAAU,QAAQ,IAAI;CAE/C,OAAO,cACL,sBACA,SAAS,UAAU,sBAAsB,eAEzC,OACF,CAAC,CAAC,OAAO,KAAK,MAAM,KAAK,GAAG,IAAI;AAClC"}
1
+ {"version":3,"file":"relativeTime.mjs","names":[],"sources":["../../../src/formatters/relativeTime.ts"],"sourcesContent":["import type { LocalesValues } from '@intlayer/types/module_augmentation';\nimport { getCachedIntl } from '../utils/intl';\n\ntype RelativeTimeUnit = Intl.RelativeTimeFormatUnit;\n\n/**\n * Calculate the difference between 2 dates in the given unit.\n */\nconst diffInUnit = (from: Date, to: Date, unit: RelativeTimeUnit): number => {\n const msDiff = to.getTime() - from.getTime();\n const sec = msDiff / 1000;\n\n switch (unit) {\n case 'second':\n return sec;\n case 'minute':\n return sec / 60;\n case 'hour':\n return sec / 3600;\n case 'day':\n return sec / 86400;\n case 'month':\n return sec / (30 * 86400); // approx\n case 'quarter':\n return sec / (3 * 30 * 86400); // 3 months approx\n case 'year':\n return sec / (365 * 86400); // approx\n default:\n return sec;\n }\n};\n\n/**\n * Formats the difference between two dates as a localized relative time string.\n *\n * @example\n * relativeTime(new Date(Date.now() - 30000)); // \"30 seconds ago\"\n *\n * @example\n * relativeTime(\"2025-01-01\", new Date(), { locale: Locales.FRENCH, unit: \"day\" });\n * // \"il y a 443 jours\"\n */\nexport const relativeTime = (\n from: Date | string | number,\n to: Date | string | number = new Date(),\n options?: Intl.RelativeTimeFormatOptions & {\n locale?: LocalesValues;\n unit?: RelativeTimeUnit;\n }\n): string => {\n const fromDate = new Date(from);\n const toDate = new Date(to);\n const unit = options?.unit ?? 'second';\n\n const value = diffInUnit(fromDate, toDate, unit);\n\n return getCachedIntl('RelativeTimeFormat', options?.locale, options).format(\n Math.round(value),\n unit\n );\n};\n"],"mappings":";;;;;;AAQA,MAAM,cAAc,MAAY,IAAU,SAAmC;CAE3E,MAAM,OADS,GAAG,QAAQ,IAAI,KAAK,QAAQ,KACtB;CAErB,QAAQ,MAAR;EACE,KAAK,UACH,OAAO;EACT,KAAK,UACH,OAAO,MAAM;EACf,KAAK,QACH,OAAO,MAAM;EACf,KAAK,OACH,OAAO,MAAM;EACf,KAAK,SACH,OAAO,MAAO;EAChB,KAAK,WACH,OAAO,MAAO;EAChB,KAAK,QACH,OAAO,MAAO;EAChB,SACE,OAAO;CACX;AACF;;;;;;;;;;;AAYA,MAAa,gBACX,MACA,qBAA6B,IAAI,KAAK,GACtC,YAIW;CACX,MAAM,WAAW,IAAI,KAAK,IAAI;CAC9B,MAAM,SAAS,IAAI,KAAK,EAAE;CAC1B,MAAM,OAAO,SAAS,QAAQ;CAE9B,MAAM,QAAQ,WAAW,UAAU,QAAQ,IAAI;CAE/C,OAAO,cAAc,sBAAsB,SAAS,QAAQ,OAAO,CAAC,CAAC,OACnE,KAAK,MAAM,KAAK,GAChB,IACF;AACF"}
@@ -1,5 +1,4 @@
1
1
  import { getCachedIntl } from "../utils/intl.mjs";
2
- import { internationalization } from "@intlayer/config/built";
3
2
 
4
3
  //#region src/formatters/units.ts
5
4
  /**
@@ -9,7 +8,7 @@ import { internationalization } from "@intlayer/config/built";
9
8
  * units(5, { unit: "kilometer", unitDisplay: "long", locale: "en-GB" });
10
9
  * // "5 kilometers"
11
10
  */
12
- const units = (value, options) => getCachedIntl("NumberFormat", options?.locale ?? internationalization?.defaultLocale, {
11
+ const units = (value, options) => getCachedIntl("NumberFormat", options?.locale, {
13
12
  style: "unit",
14
13
  unit: options?.unit ?? "day",
15
14
  unitDisplay: options?.unitDisplay ?? "short",
@@ -1 +1 @@
1
- {"version":3,"file":"units.mjs","names":[],"sources":["../../../src/formatters/units.ts"],"sourcesContent":["import { internationalization } from '@intlayer/config/built';\nimport type { LocalesValues } from '@intlayer/types/module_augmentation';\nimport { getCachedIntl } from '../utils/intl';\n\n/**\n * Formats a numeric value as a localized unit string.\n *\n * @example\n * units(5, { unit: \"kilometer\", unitDisplay: \"long\", locale: \"en-GB\" });\n * // \"5 kilometers\"\n */\nexport const units = (\n value: number | string,\n options?: Intl.NumberFormatOptions & { locale?: LocalesValues }\n): string =>\n getCachedIntl(\n 'NumberFormat',\n options?.locale ?? internationalization?.defaultLocale,\n\n {\n style: 'unit',\n unit: options?.unit ?? 'day',\n unitDisplay: options?.unitDisplay ?? 'short',\n useGrouping: options?.useGrouping ?? false,\n }\n ).format(Number(value));\n"],"mappings":";;;;;;;;;;;AAWA,MAAa,SACX,OACA,YAEA,cACE,gBACA,SAAS,UAAU,sBAAsB,eAEzC;CACE,OAAO;CACP,MAAM,SAAS,QAAQ;CACvB,aAAa,SAAS,eAAe;CACrC,aAAa,SAAS,eAAe;AACvC,CACF,CAAC,CAAC,OAAO,OAAO,KAAK,CAAC"}
1
+ {"version":3,"file":"units.mjs","names":[],"sources":["../../../src/formatters/units.ts"],"sourcesContent":["import type { LocalesValues } from '@intlayer/types/module_augmentation';\nimport { getCachedIntl } from '../utils/intl';\n\n/**\n * Formats a numeric value as a localized unit string.\n *\n * @example\n * units(5, { unit: \"kilometer\", unitDisplay: \"long\", locale: \"en-GB\" });\n * // \"5 kilometers\"\n */\nexport const units = (\n value: number | string,\n options?: Intl.NumberFormatOptions & { locale?: LocalesValues }\n): string =>\n getCachedIntl('NumberFormat', options?.locale, {\n style: 'unit',\n unit: options?.unit ?? 'day',\n unitDisplay: options?.unitDisplay ?? 'short',\n useGrouping: options?.useGrouping ?? false,\n }).format(Number(value));\n"],"mappings":";;;;;;;;;;AAUA,MAAa,SACX,OACA,YAEA,cAAc,gBAAgB,SAAS,QAAQ;CAC7C,OAAO;CACP,MAAM,SAAS,QAAQ;CACvB,aAAa,SAAS,eAAe;CACrC,aAAa,SAAS,eAAe;AACvC,CAAC,CAAC,CAAC,OAAO,OAAO,KAAK,CAAC"}
@@ -48,7 +48,7 @@ import { getMarkdownMetadata } from "./transpiler/markdown/getMarkdownMetadata.m
48
48
  import { validateMarkdown } from "./transpiler/markdown/validateMarkdown.mjs";
49
49
  import { md as markdown } from "./transpiler/markdown/markdown.mjs";
50
50
  import { nest as nesting } from "./transpiler/nesting/nesting.mjs";
51
- import { plural } from "./transpiler/plural/plural.mjs";
51
+ import { DEFAULT_PLURAL_VARIABLE, plural } from "./transpiler/plural/plural.mjs";
52
52
  import { select } from "./transpiler/select/select.mjs";
53
53
  import { t as translation } from "./transpiler/translation/translation.mjs";
54
54
  import { filterMissingTranslationsOnlyPlugin, getFilterMissingTranslationsContent, getFilterMissingTranslationsDictionary } from "./deepTransformPlugins/getFilterMissingTranslationsContent.mjs";
@@ -116,4 +116,4 @@ import { getIntlayerNodePrototype } from "./utils/intlayerNodePrototype.mjs";
116
116
  import { isSameKeyPath } from "./utils/isSameKeyPath.mjs";
117
117
  import { stringifyYaml } from "./utils/stringifyYaml.mjs";
118
118
 
119
- export { ATTRIBUTES_TO_SANITIZE, ATTRIBUTE_NAME_START_R, ATTRIBUTE_TO_NODE_PROP_MAP, ATTR_EXTRACTOR_R, BLOCKQUOTE_ALERT_R, BLOCKQUOTE_R, BLOCKQUOTE_TRIM_LEFT_MULTILINE_R, BLOCK_END_R, BREAK_LINE_R, BREAK_THEMATIC_R, CAPTURE_LETTER_AFTER_HYPHEN, CODE_BLOCK_FENCED_R, CODE_BLOCK_R, CODE_INLINE_R, COMPOSITE_ID_SEPARATOR, CONSECUTIVE_NEWLINE_R, CR_NEWLINE_R, CachedIntl, CachedIntl as Intl, DEFAULT_VARIANT_ID, DO_NOT_PROCESS_HTML_ELEMENTS, FENCE_DELIMITER_R, FOOTNOTE_R, FOOTNOTE_REFERENCE_R, FORMFEED_R, FRONT_MATTER_R, GFM_TASK_R, HEADING_ATX_COMPLIANT_R, HEADING_R, HEADING_SETEXT_R, HTML_CHAR_CODE_R, HTML_COMMENT_R, HTML_CUSTOM_ATTR_R, HTML_LEFT_TRIM_AMOUNT_R, HTML_TAGS, INLINE_SKIP_R, INTERPOLATION_R, LINK_AUTOLINK_BARE_URL_R, LINK_AUTOLINK_R, LOOKAHEAD, LocaleStorage, LocaleStorageClient, LocaleStorageServer, NAMED_CODES_TO_UNICODE, NP_TABLE_R, ORDERED, ORDERED_LIST_BULLET, ORDERED_LIST_ITEM_PREFIX, ORDERED_LIST_ITEM_PREFIX_R, ORDERED_LIST_ITEM_R, ORDERED_LIST_R, PARAGRAPH_R, PRELOADED_DYNAMIC_KEY, Priority, QUALIFIER_DYNAMIC_TYPES_KEY, QUALIFIER_ORDER, REFERENCE_IMAGE_OR_LINK, REFERENCE_IMAGE_R, REFERENCE_LINK_R, RuleScope, RuleType, SCOPE_BLOCK, SCOPE_BOTH, SCOPE_INLINE, SHORTCODE_R, SHOULD_RENDER_AS_BLOCK_R, TABLE_CENTER_ALIGN, TABLE_LEFT_ALIGN, TABLE_RIGHT_ALIGN, TABLE_TRIM_PIPES, TAB_R, TEXT_BOLD_R, TEXT_EMPHASIZED_R, TEXT_ESCAPED_R, TEXT_MARKED_R, TEXT_STRIKETHROUGHED_R, TRIM_STARTING_NEWLINES, UNESCAPE_R, UNORDERED, UNORDERED_LIST_BULLET, UNORDERED_LIST_ITEM_PREFIX, UNORDERED_LIST_ITEM_PREFIX_R, UNORDERED_LIST_ITEM_R, UNORDERED_LIST_R, VOID_HTML_ELEMENTS, allowInline, anyScopeRegex, attributeValueToNodePropValue, bindInsertedValues, bindIntl, blockRegex, buildMaskPlugin, captureNothing, checkIsURLAbsolute, checkMissingLocalesPlugin, compact, comparePaths, compile, compileWithOptions, condition as cond, conditionPlugin, createCompiler, createMessageResolver, createRenderer, currency, cx, date, deepTransformNode, editDictionaryByKeyPath, enumeration as enu, enumerationPlugin, fallbackPlugin, filePlugin, filterMissingTranslationsOnlyPlugin, filterTranslationsOnlyPlugin, findMatchingCondition, findPlainDynamicLoader, formatProxyEnabledMessage, gender, genderPlugin, generateListItemPrefix, generateListItemPrefixRegex, generateListItemRegex, generateListRegex, generateSitemap, generateSitemapUrl, get, getBasePlugins, getBrowserLocale, getCachedIntl, getCachedLocaleFromStorageClient, getCanonicalPath, getCondition, getContent, getContentNodeByKeyPath, getCookie, getDefaultNode, getDictionary, getDictionaryAsync, getDictionaryCompositeIds, getDictionaryQualifierIds, getDictionaryQualifierTypes, getDictionarySelectorCacheKey, getDictionaryTransformCacheKey, getDomainHostname, getDomainOrigin, getEditedContent, getEditedDictionary, getEmptyNode, getEnumeration, getFilterMissingTranslationsContent, getFilterMissingTranslationsDictionary, getFilterTranslationsOnlyContent, getFilterTranslationsOnlyDictionary, getFilteredLocalesContent, getFilteredLocalesDictionary, getGender, getHTML, getHTMLTextDir, getInsertion, getInsertionValues, getInternalPath, getInterpolableContent, getIntlayer, getIntlayerAsync, getIntlayerNodePrototype, getLocale, getLocaleFromDomain, getLocaleFromPath, getLocaleFromRequest, getLocaleFromStorage, getLocaleFromStorageClient, getLocaleFromStorageServer, getLocaleLang, getLocaleName, getLocalizedContent, getLocalizedPath, getLocalizedUrl, getMarkdownMetadata, getMaskContent, getMissingLocalesContent, getMissingLocalesContentFromDictionary, getMultilingualDictionary, getMultilingualUrls, getNestedDictionaryGraph, getNestedDictionaryKeys, getNestedDictionaryReferences, getNesting, getNodeChildren, getNodeType, getPathWithoutLocale, getPerLocaleDictionary, getPluginsCacheKey, getPlural, getPrefix, getPreloadLocale, getPreloadedDictionary, getReplacedValuesContent, getRewritePath, getRewriteRules, getRuleSet, getSelect, getSplittedContent, getSplittedDictionaryContent, getTranslation, getVariantIds, html, i18nextToIntlayerFormatter, icuToIntlayerFormatter, inlineRegex, insertion as insert, insertContentInDictionary, insertionPlugin, interpolateMessage, intlayerToI18nextFormatter, intlayerToICUFormatter, intlayerToPortableObjectFormatter, intlayerToVueI18nFormatter, isDeclaredLocale, isInterpolableWrapperNode, isLocaleExclusiveOnDomain, isProxyStorageLocaleEnabled, isQualifiedDictionaryGroup, isQualifiedDynamicLoaderMap, isSameKeyPath, isValidElement, list, localeDetector, localeFlatMap, localeMap, localeRecord, localeResolver, localeStorageOptions, markdown as md, mergeDictionaries, mergeQualifiedDictionaries, navigatePath, nesting as nest, nestedPlugin, normalizeAttributeKey, normalizeDictionaries, normalizeDictionary, normalizePath, normalizeWhitespace, number, orderDictionaries, parseBlock, parseCaptureInline, parseDictionarySelector, parseInline, parseMarkdown, parseSimpleInline, parseStyleAttribute, parseTableAlign, parseTableAlignCapture, parseTableCells, parseTableRow, parseTaggedMessage, parseYaml, parserFor, percentage, plural, pluralPlugin, portableObjectToIntlayerFormatter, presets, qualifies, readTransformCache, rebuildInterpolableContent, reconstructQualifiedEntry, registerAmbientLocaleResolver, registerAsyncAmbientLocaleResolver, relativeTime, removeContentNodeByKeyPath, renameContentNodeByKeyPath, renderFor, renderMarkdownAst, renderNothing, resolveBrowserLocale, resolveDictionaryArgument, resolveEffectiveVariantId, resolveInsertedSelector, resolveInterpreterLocale, resolveInterpreterLocaleAsync, resolveLocalizedPath, resolveMessage, resolveMessageNode, resolveMessageNodeToString, resolvePlainDynamicLoader, resolveProviderVariant, resolveProxyMode, resolveQualifiedDictionary, resolveQualifiedDynamicContent, resolveQualifiedDynamicContentAsync, sanitizer, select, selectPlugin, serializeVariant, serializeVariantChain, setLocaleInStorage, setLocaleInStorageClient, setLocaleInStorageServer, simpleInlineRegex, slugify, some, splitInsertionTemplate, startsWith, stringifyYaml, translation as t, transformInterpolableNode, translationPlugin, trimEnd, trimLeadingWhitespaceOutsideFences, unescapeString, units, unquote, updateNodeChildren, validateHTML, validateMarkdown, validatePrefix, vueI18nToIntlayerFormatter, writeTransformCache };
119
+ export { ATTRIBUTES_TO_SANITIZE, ATTRIBUTE_NAME_START_R, ATTRIBUTE_TO_NODE_PROP_MAP, ATTR_EXTRACTOR_R, BLOCKQUOTE_ALERT_R, BLOCKQUOTE_R, BLOCKQUOTE_TRIM_LEFT_MULTILINE_R, BLOCK_END_R, BREAK_LINE_R, BREAK_THEMATIC_R, CAPTURE_LETTER_AFTER_HYPHEN, CODE_BLOCK_FENCED_R, CODE_BLOCK_R, CODE_INLINE_R, COMPOSITE_ID_SEPARATOR, CONSECUTIVE_NEWLINE_R, CR_NEWLINE_R, CachedIntl, CachedIntl as Intl, DEFAULT_PLURAL_VARIABLE, DEFAULT_VARIANT_ID, DO_NOT_PROCESS_HTML_ELEMENTS, FENCE_DELIMITER_R, FOOTNOTE_R, FOOTNOTE_REFERENCE_R, FORMFEED_R, FRONT_MATTER_R, GFM_TASK_R, HEADING_ATX_COMPLIANT_R, HEADING_R, HEADING_SETEXT_R, HTML_CHAR_CODE_R, HTML_COMMENT_R, HTML_CUSTOM_ATTR_R, HTML_LEFT_TRIM_AMOUNT_R, HTML_TAGS, INLINE_SKIP_R, INTERPOLATION_R, LINK_AUTOLINK_BARE_URL_R, LINK_AUTOLINK_R, LOOKAHEAD, LocaleStorage, LocaleStorageClient, LocaleStorageServer, NAMED_CODES_TO_UNICODE, NP_TABLE_R, ORDERED, ORDERED_LIST_BULLET, ORDERED_LIST_ITEM_PREFIX, ORDERED_LIST_ITEM_PREFIX_R, ORDERED_LIST_ITEM_R, ORDERED_LIST_R, PARAGRAPH_R, PRELOADED_DYNAMIC_KEY, Priority, QUALIFIER_DYNAMIC_TYPES_KEY, QUALIFIER_ORDER, REFERENCE_IMAGE_OR_LINK, REFERENCE_IMAGE_R, REFERENCE_LINK_R, RuleScope, RuleType, SCOPE_BLOCK, SCOPE_BOTH, SCOPE_INLINE, SHORTCODE_R, SHOULD_RENDER_AS_BLOCK_R, TABLE_CENTER_ALIGN, TABLE_LEFT_ALIGN, TABLE_RIGHT_ALIGN, TABLE_TRIM_PIPES, TAB_R, TEXT_BOLD_R, TEXT_EMPHASIZED_R, TEXT_ESCAPED_R, TEXT_MARKED_R, TEXT_STRIKETHROUGHED_R, TRIM_STARTING_NEWLINES, UNESCAPE_R, UNORDERED, UNORDERED_LIST_BULLET, UNORDERED_LIST_ITEM_PREFIX, UNORDERED_LIST_ITEM_PREFIX_R, UNORDERED_LIST_ITEM_R, UNORDERED_LIST_R, VOID_HTML_ELEMENTS, allowInline, anyScopeRegex, attributeValueToNodePropValue, bindInsertedValues, bindIntl, blockRegex, buildMaskPlugin, captureNothing, checkIsURLAbsolute, checkMissingLocalesPlugin, compact, comparePaths, compile, compileWithOptions, condition as cond, conditionPlugin, createCompiler, createMessageResolver, createRenderer, currency, cx, date, deepTransformNode, editDictionaryByKeyPath, enumeration as enu, enumerationPlugin, fallbackPlugin, filePlugin, filterMissingTranslationsOnlyPlugin, filterTranslationsOnlyPlugin, findMatchingCondition, findPlainDynamicLoader, formatProxyEnabledMessage, gender, genderPlugin, generateListItemPrefix, generateListItemPrefixRegex, generateListItemRegex, generateListRegex, generateSitemap, generateSitemapUrl, get, getBasePlugins, getBrowserLocale, getCachedIntl, getCachedLocaleFromStorageClient, getCanonicalPath, getCondition, getContent, getContentNodeByKeyPath, getCookie, getDefaultNode, getDictionary, getDictionaryAsync, getDictionaryCompositeIds, getDictionaryQualifierIds, getDictionaryQualifierTypes, getDictionarySelectorCacheKey, getDictionaryTransformCacheKey, getDomainHostname, getDomainOrigin, getEditedContent, getEditedDictionary, getEmptyNode, getEnumeration, getFilterMissingTranslationsContent, getFilterMissingTranslationsDictionary, getFilterTranslationsOnlyContent, getFilterTranslationsOnlyDictionary, getFilteredLocalesContent, getFilteredLocalesDictionary, getGender, getHTML, getHTMLTextDir, getInsertion, getInsertionValues, getInternalPath, getInterpolableContent, getIntlayer, getIntlayerAsync, getIntlayerNodePrototype, getLocale, getLocaleFromDomain, getLocaleFromPath, getLocaleFromRequest, getLocaleFromStorage, getLocaleFromStorageClient, getLocaleFromStorageServer, getLocaleLang, getLocaleName, getLocalizedContent, getLocalizedPath, getLocalizedUrl, getMarkdownMetadata, getMaskContent, getMissingLocalesContent, getMissingLocalesContentFromDictionary, getMultilingualDictionary, getMultilingualUrls, getNestedDictionaryGraph, getNestedDictionaryKeys, getNestedDictionaryReferences, getNesting, getNodeChildren, getNodeType, getPathWithoutLocale, getPerLocaleDictionary, getPluginsCacheKey, getPlural, getPrefix, getPreloadLocale, getPreloadedDictionary, getReplacedValuesContent, getRewritePath, getRewriteRules, getRuleSet, getSelect, getSplittedContent, getSplittedDictionaryContent, getTranslation, getVariantIds, html, i18nextToIntlayerFormatter, icuToIntlayerFormatter, inlineRegex, insertion as insert, insertContentInDictionary, insertionPlugin, interpolateMessage, intlayerToI18nextFormatter, intlayerToICUFormatter, intlayerToPortableObjectFormatter, intlayerToVueI18nFormatter, isDeclaredLocale, isInterpolableWrapperNode, isLocaleExclusiveOnDomain, isProxyStorageLocaleEnabled, isQualifiedDictionaryGroup, isQualifiedDynamicLoaderMap, isSameKeyPath, isValidElement, list, localeDetector, localeFlatMap, localeMap, localeRecord, localeResolver, localeStorageOptions, markdown as md, mergeDictionaries, mergeQualifiedDictionaries, navigatePath, nesting as nest, nestedPlugin, normalizeAttributeKey, normalizeDictionaries, normalizeDictionary, normalizePath, normalizeWhitespace, number, orderDictionaries, parseBlock, parseCaptureInline, parseDictionarySelector, parseInline, parseMarkdown, parseSimpleInline, parseStyleAttribute, parseTableAlign, parseTableAlignCapture, parseTableCells, parseTableRow, parseTaggedMessage, parseYaml, parserFor, percentage, plural, pluralPlugin, portableObjectToIntlayerFormatter, presets, qualifies, readTransformCache, rebuildInterpolableContent, reconstructQualifiedEntry, registerAmbientLocaleResolver, registerAsyncAmbientLocaleResolver, relativeTime, removeContentNodeByKeyPath, renameContentNodeByKeyPath, renderFor, renderMarkdownAst, renderNothing, resolveBrowserLocale, resolveDictionaryArgument, resolveEffectiveVariantId, resolveInsertedSelector, resolveInterpreterLocale, resolveInterpreterLocaleAsync, resolveLocalizedPath, resolveMessage, resolveMessageNode, resolveMessageNodeToString, resolvePlainDynamicLoader, resolveProviderVariant, resolveProxyMode, resolveQualifiedDictionary, resolveQualifiedDynamicContent, resolveQualifiedDynamicContentAsync, sanitizer, select, selectPlugin, serializeVariant, serializeVariantChain, setLocaleInStorage, setLocaleInStorageClient, setLocaleInStorageServer, simpleInlineRegex, slugify, some, splitInsertionTemplate, startsWith, stringifyYaml, translation as t, transformInterpolableNode, translationPlugin, trimEnd, trimLeadingWhitespaceOutsideFences, unescapeString, units, unquote, updateNodeChildren, validateHTML, validateMarkdown, validatePrefix, vueI18nToIntlayerFormatter, writeTransformCache };
@@ -7,6 +7,24 @@ import { localeResolver } from "./localeResolver.mjs";
7
7
  const LANGUAGE_FORMAT_REGULAR_EXPRESSION = /^\s*([^\s\-;]+)(?:-([^\s;]+))?\s*(?:;(.*))?$/;
8
8
  const DEFAULT_QUALITY_SCORE = 1;
9
9
  /**
10
+ * Upper bound of the memoization caches below. Browsers send a handful of
11
+ * distinct `Accept-Language` values, so a small cache holds the working set.
12
+ */
13
+ const MAX_CACHE_ENTRIES = 512;
14
+ /**
15
+ * Longer headers are resolved without being memoized, so a client sending
16
+ * arbitrary headers cannot grow the cache memory.
17
+ */
18
+ const MAX_CACHEABLE_HEADER_LENGTH = 128;
19
+ /**
20
+ * Inserts an entry in a bounded cache, evicting the oldest entry when full.
21
+ */
22
+ const setBoundedCacheEntry = (cache, key, value) => {
23
+ if (cache.size >= MAX_CACHE_ENTRIES) cache.delete(cache.keys().next().value);
24
+ cache.set(key, value);
25
+ return value;
26
+ };
27
+ /**
10
28
  * Parses a single language tag string from the Accept-Language header.
11
29
  * Example input: "en-US;q=0.8"
12
30
  */
@@ -30,11 +48,26 @@ const parseLanguageTag = (tagString, index) => {
30
48
  regionCode,
31
49
  qualityScore,
32
50
  originalIndex: index,
33
- fullLocale
51
+ fullLocale,
52
+ fullLocaleLower: fullLocale.toLowerCase(),
53
+ languageCodeLower: languageCode.toLowerCase()
34
54
  };
35
55
  };
36
56
  /**
37
- * Parses the entire Accept-Language header string into a list of preferences.
57
+ * Parsed available languages. They come from the configuration, so the same
58
+ * few tags are parsed on every request: parse each one once.
59
+ */
60
+ const availableLanguageCache = /* @__PURE__ */ new Map();
61
+ /**
62
+ * Parses an available language, reusing the previous parse of the same tag.
63
+ */
64
+ const parseAvailableLanguage = (language) => {
65
+ const cachedLanguage = availableLanguageCache.get(language);
66
+ if (cachedLanguage !== void 0) return cachedLanguage;
67
+ return setBoundedCacheEntry(availableLanguageCache, language, parseLanguageTag(language, 0));
68
+ };
69
+ /**
70
+ * Parses the full Accept-Language header into a list of language preferences.
38
71
  */
39
72
  const parseAcceptLanguageHeader = (headerValue) => {
40
73
  const rawTags = headerValue.split(",");
@@ -49,19 +82,13 @@ const parseAcceptLanguageHeader = (headerValue) => {
49
82
  return preferences;
50
83
  };
51
84
  /**
52
- * Calculates the specificity of a match between a provided language and a requested preference.
85
+ * Determines how well a specific available language matches a requested language preference.
53
86
  */
54
- const calculateMatchSpecificity = (providedLanguage, preference, providedIndex) => {
55
- const parsedProvided = parseLanguageTag(providedLanguage, providedIndex);
56
- if (!parsedProvided) return null;
87
+ const calculateMatchSpecificity = (parsedProvided, preference, providedIndex) => {
57
88
  let specificityScore = 0;
58
- const preferenceFullLower = preference.fullLocale.toLowerCase();
59
- const preferencePrefixLower = preference.languageCode.toLowerCase();
60
- const providedFullLower = parsedProvided.fullLocale.toLowerCase();
61
- const providedPrefixLower = parsedProvided.languageCode.toLowerCase();
62
- if (preferenceFullLower === providedFullLower) specificityScore |= 4;
63
- else if (preferencePrefixLower === providedFullLower) specificityScore |= 2;
64
- else if (preferenceFullLower === providedPrefixLower) specificityScore |= 1;
89
+ if (preference.fullLocaleLower === parsedProvided.fullLocaleLower) specificityScore |= 4;
90
+ else if (preference.languageCodeLower === parsedProvided.fullLocaleLower) specificityScore |= 2;
91
+ else if (preference.fullLocaleLower === parsedProvided.languageCodeLower) specificityScore |= 1;
65
92
  else if (preference.fullLocale !== "*") return null;
66
93
  return {
67
94
  providedIndex,
@@ -71,7 +98,7 @@ const calculateMatchSpecificity = (providedLanguage, preference, providedIndex)
71
98
  };
72
99
  };
73
100
  /**
74
- * Determines the best match for a specific available language against the list of user accepted languages.
101
+ * Finds the best matching preference from the header for a specific available language.
75
102
  */
76
103
  const getBestMatchForLanguage = (providedLanguage, acceptedPreferences, providedIndex) => {
77
104
  let bestMatch = {
@@ -80,8 +107,10 @@ const getBestMatchForLanguage = (providedLanguage, acceptedPreferences, provided
80
107
  specificityScore: 0,
81
108
  providedIndex
82
109
  };
110
+ const parsedProvided = parseAvailableLanguage(providedLanguage);
111
+ if (!parsedProvided) return bestMatch;
83
112
  for (const preference of acceptedPreferences) {
84
- const matchSpec = calculateMatchSpecificity(providedLanguage, preference, providedIndex);
113
+ const matchSpec = calculateMatchSpecificity(parsedProvided, preference, providedIndex);
85
114
  if (matchSpec) {
86
115
  if ((bestMatch.specificityScore - matchSpec.specificityScore || bestMatch.qualityScore - matchSpec.qualityScore || bestMatch.headerIndex - matchSpec.headerIndex) < 0) bestMatch = matchSpec;
87
116
  }
@@ -89,12 +118,8 @@ const getBestMatchForLanguage = (providedLanguage, acceptedPreferences, provided
89
118
  return bestMatch;
90
119
  };
91
120
  /**
92
- * Comparator function to sort language matches.
93
- * Sorting order:
94
- * 1. Quality Score (Descending)
95
- * 2. Specificity Score (Descending)
96
- * 3. Order in Header (Ascending - lower index is better)
97
- * 4. Order in Provided List (Ascending)
121
+ * Comparator function to sort MatchResults.
122
+ * Order: Quality (desc) -> Specificity (desc) -> Header Order (asc) -> Provided Order (asc)
98
123
  */
99
124
  const compareMatchResults = (a, b) => {
100
125
  return b.qualityScore - a.qualityScore || b.specificityScore - a.specificityScore || a.headerIndex - b.headerIndex || a.providedIndex - b.providedIndex || 0;
@@ -109,15 +134,28 @@ const getPreferredLanguages = (acceptHeader, availableLanguages) => {
109
134
  return availableLanguages.map((language, index) => getBestMatchForLanguage(language, acceptedPreferences, index)).filter((result) => result.qualityScore > 0).sort(compareMatchResults).map((result) => availableLanguages[result.providedIndex]);
110
135
  };
111
136
  /**
137
+ * Detected locales, keyed by header, available locales and default locale.
138
+ */
139
+ const detectedLocaleCache = /* @__PURE__ */ new Map();
140
+ /**
112
141
  * Detects the locale from the request headers.
113
142
  *
114
143
  * Headers are provided by the browser/client and can be used to determine the user's preferred language.
115
144
  * This function intersects the user's `Accept-Language` header with the application's available locales.
145
+ *
146
+ * The result is memoized, as it only depends on its arguments.
116
147
  */
117
148
  const localeDetector = (headers, availableLocales, defaultLocale) => {
118
149
  const acceptLanguageHeader = headers["accept-language"];
150
+ const cacheKey = (acceptLanguageHeader?.length ?? 0) <= MAX_CACHEABLE_HEADER_LENGTH ? `${acceptLanguageHeader === void 0 ? "\0" : `=${acceptLanguageHeader}`}\u0001${availableLocales?.join(",") ?? "\0"}\u0001${defaultLocale ?? "\0"}` : void 0;
151
+ if (cacheKey !== void 0) {
152
+ const cachedLocale = detectedLocaleCache.get(cacheKey);
153
+ if (cachedLocale !== void 0) return cachedLocale;
154
+ }
119
155
  const preferredLocaleStrings = getPreferredLanguages(acceptLanguageHeader, availableLocales);
120
- return localeResolver(preferredLocaleStrings, availableLocales, defaultLocale);
156
+ const detectedLocale = localeResolver(preferredLocaleStrings, availableLocales, defaultLocale);
157
+ if (cacheKey !== void 0) setBoundedCacheEntry(detectedLocaleCache, cacheKey, detectedLocale);
158
+ return detectedLocale;
121
159
  };
122
160
 
123
161
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"localeDetector.mjs","names":[],"sources":["../../../src/localization/localeDetector.ts"],"sourcesContent":["import type { Locale } from '@intlayer/types/allLocales';\nimport type { DeclaredLocales } from '@intlayer/types/module_augmentation';\nimport { localeResolver } from './localeResolver';\n\n/**\n * Constants\n */\nconst LANGUAGE_FORMAT_REGULAR_EXPRESSION =\n /^\\s*([^\\s\\-;]+)(?:-([^\\s;]+))?\\s*(?:;(.*))?$/;\nconst DEFAULT_QUALITY_SCORE = 1;\n\n/**\n * Enumeration for specificity weights.\n * Higher values indicate a more precise match.\n */\nenum SpecificityWeight {\n None = 0,\n Broad = 1, // Matches prefix (e.g., 'en' matches 'en-US')\n Prefix = 2, // Matches prefix in reverse (e.g., 'en-US' matches 'en')\n Exact = 4, // Matches exact string (e.g., 'en-US' matches 'en-US')\n}\n\n/**\n * Represents a parsed language tag from the header.\n */\ntype LanguagePreference = {\n languageCode: string;\n regionCode?: string;\n fullLocale: string;\n qualityScore: number;\n originalIndex: number;\n};\n\n/**\n * Represents the result of matching a requested language against an available language.\n */\ntype MatchResult = {\n providedIndex: number;\n headerIndex: number;\n qualityScore: number;\n specificityScore: number;\n};\n\n/**\n * Parses a single language tag string from the Accept-Language header.\n * Example input: \"en-US;q=0.8\"\n */\nconst parseLanguageTag = (\n tagString: string,\n index: number\n): LanguagePreference | null => {\n const match = LANGUAGE_FORMAT_REGULAR_EXPRESSION.exec(tagString);\n if (!match) {\n return null;\n }\n\n const languageCode = match[1] as string;\n const regionCode = match[2];\n const parameters = match[3];\n\n // Construct the full locale string (e.g., \"en-US\" or \"en\")\n const fullLocale = regionCode\n ? `${languageCode}-${regionCode}`\n : languageCode;\n\n let qualityScore = DEFAULT_QUALITY_SCORE;\n\n // Parse parameters to find the quality score (\"q\")\n if (parameters) {\n const parameterList = parameters.split(';');\n\n for (const parameter of parameterList) {\n const [key, value] = parameter.split('=');\n\n if (key === 'q') {\n qualityScore = parseFloat(value as string);\n }\n }\n }\n\n return {\n languageCode,\n regionCode,\n qualityScore,\n originalIndex: index,\n fullLocale,\n };\n};\n\n/**\n * Parses the entire Accept-Language header string into a list of preferences.\n */\nconst parseAcceptLanguageHeader = (\n headerValue: string\n): LanguagePreference[] => {\n const rawTags = headerValue.split(',');\n const preferences: LanguagePreference[] = [];\n\n for (let index = 0; index < rawTags.length; index++) {\n const tag = rawTags[index]?.trim();\n\n if (tag) {\n const parsedLanguage = parseLanguageTag(tag, index);\n\n if (parsedLanguage) {\n preferences.push(parsedLanguage);\n }\n }\n }\n\n return preferences;\n};\n\n/**\n * Calculates the specificity of a match between a provided language and a requested preference.\n */\nconst calculateMatchSpecificity = (\n providedLanguage: string,\n preference: LanguagePreference,\n providedIndex: number\n): MatchResult | null => {\n const parsedProvided = parseLanguageTag(providedLanguage, providedIndex);\n if (!parsedProvided) {\n return null;\n }\n\n let specificityScore = SpecificityWeight.None;\n\n const preferenceFullLower = preference.fullLocale.toLowerCase();\n const preferencePrefixLower = preference.languageCode.toLowerCase();\n const providedFullLower = parsedProvided.fullLocale.toLowerCase();\n const providedPrefixLower = parsedProvided.languageCode.toLowerCase();\n\n if (preferenceFullLower === providedFullLower) {\n specificityScore |= SpecificityWeight.Exact;\n } else if (preferencePrefixLower === providedFullLower) {\n specificityScore |= SpecificityWeight.Prefix;\n } else if (preferenceFullLower === providedPrefixLower) {\n specificityScore |= SpecificityWeight.Broad;\n } else if (preference.fullLocale !== '*') {\n return null;\n }\n\n return {\n providedIndex,\n headerIndex: preference.originalIndex,\n qualityScore: preference.qualityScore,\n specificityScore,\n };\n};\n\n/**\n * Determines the best match for a specific available language against the list of user accepted languages.\n */\nconst getBestMatchForLanguage = (\n providedLanguage: string,\n acceptedPreferences: LanguagePreference[],\n providedIndex: number\n): MatchResult => {\n // Initialize with a non-match priority\n let bestMatch: MatchResult = {\n headerIndex: -1,\n qualityScore: 0,\n specificityScore: 0,\n providedIndex,\n };\n\n for (const preference of acceptedPreferences) {\n const matchSpec = calculateMatchSpecificity(\n providedLanguage,\n preference,\n providedIndex\n );\n\n if (matchSpec) {\n // Compare current best match with new match\n const scoreDifference =\n bestMatch.specificityScore - matchSpec.specificityScore ||\n bestMatch.qualityScore - matchSpec.qualityScore ||\n bestMatch.headerIndex - matchSpec.headerIndex;\n\n // If the new match is better (difference < 0), update priority\n if (scoreDifference < 0) {\n bestMatch = matchSpec;\n }\n }\n }\n\n return bestMatch;\n};\n\n/**\n * Comparator function to sort language matches.\n * Sorting order:\n * 1. Quality Score (Descending)\n * 2. Specificity Score (Descending)\n * 3. Order in Header (Ascending - lower index is better)\n * 4. Order in Provided List (Ascending)\n */\nconst compareMatchResults = (a: MatchResult, b: MatchResult): number => {\n return (\n b.qualityScore - a.qualityScore ||\n b.specificityScore - a.specificityScore ||\n a.headerIndex - b.headerIndex ||\n a.providedIndex - b.providedIndex ||\n 0\n );\n};\n\n/**\n * Derives the list of preferred languages based on the Accept-Language header\n * and an optional list of available languages.\n */\nexport const getPreferredLanguages = (\n acceptHeader: string | undefined,\n availableLanguages?: string[]\n): string[] => {\n // RFC 2616 sec 14.4: no header implies '*'\n const headerValue = acceptHeader === undefined ? '*' : acceptHeader || '';\n const acceptedPreferences = parseAcceptLanguageHeader(headerValue);\n\n // If no specific languages are provided to filter against, return the header languages sorted by quality\n if (!availableLanguages) {\n return acceptedPreferences\n .filter((preference) => preference.qualityScore > 0)\n .sort((a, b) => b.qualityScore - a.qualityScore) // Simple sort by quality\n .map((preference) => preference.fullLocale);\n }\n\n // Map available languages to their match priority against the header\n const matchResults = availableLanguages.map((language, index) =>\n getBestMatchForLanguage(language, acceptedPreferences, index)\n );\n\n return matchResults\n .filter((result) => result.qualityScore > 0)\n .sort(compareMatchResults)\n .map((result) => availableLanguages[result.providedIndex]) as string[];\n};\n\n/**\n * Detects the locale from the request headers.\n *\n * Headers are provided by the browser/client and can be used to determine the user's preferred language.\n * This function intersects the user's `Accept-Language` header with the application's available locales.\n */\nexport const localeDetector = (\n headers: Record<string, string | undefined>,\n availableLocales?: Locale[],\n defaultLocale?: Locale\n): DeclaredLocales => {\n const acceptLanguageHeader = headers['accept-language'];\n\n const preferredLocaleStrings = getPreferredLanguages(\n acceptLanguageHeader,\n availableLocales as string[]\n );\n\n return localeResolver(\n preferredLocaleStrings as Locale[],\n availableLocales,\n defaultLocale\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"localeDetector.mjs","names":[],"sources":["../../../src/localization/localeDetector.ts"],"sourcesContent":["import type { Locale } from '@intlayer/types/allLocales';\nimport type { DeclaredLocales } from '@intlayer/types/module_augmentation';\nimport { localeResolver } from './localeResolver';\n\n/**\n * Constants\n */\nconst LANGUAGE_FORMAT_REGULAR_EXPRESSION =\n /^\\s*([^\\s\\-;]+)(?:-([^\\s;]+))?\\s*(?:;(.*))?$/;\nconst DEFAULT_QUALITY_SCORE = 1;\n\n/**\n * Upper bound of the memoization caches below. Browsers send a handful of\n * distinct `Accept-Language` values, so a small cache holds the working set.\n */\nconst MAX_CACHE_ENTRIES = 512;\n\n/**\n * Longer headers are resolved without being memoized, so a client sending\n * arbitrary headers cannot grow the cache memory.\n */\nconst MAX_CACHEABLE_HEADER_LENGTH = 128;\n\n/**\n * Enumeration for specificity weights.\n * Higher values indicate a more precise match.\n */\nenum SpecificityWeight {\n None = 0,\n Broad = 1, // Matches prefix (e.g., 'en' matches 'en-US')\n Prefix = 2, // Matches prefix in reverse (e.g., 'en-US' matches 'en')\n Exact = 4, // Matches exact string (e.g., 'en-US' matches 'en-US')\n}\n\n/**\n * Represents a parsed language tag from the header.\n */\ntype LanguagePreference = {\n languageCode: string;\n regionCode?: string;\n fullLocale: string;\n /** `fullLocale` lower-cased once, for case-insensitive matching */\n fullLocaleLower: string;\n /** `languageCode` lower-cased once, for case-insensitive matching */\n languageCodeLower: string;\n qualityScore: number;\n originalIndex: number;\n};\n\n/**\n * Represents the result of matching a requested language against an available language.\n */\ntype MatchResult = {\n providedIndex: number;\n headerIndex: number;\n qualityScore: number;\n specificityScore: number;\n};\n\n/**\n * Inserts an entry in a bounded cache, evicting the oldest entry when full.\n */\nconst setBoundedCacheEntry = <Value>(\n cache: Map<string, Value>,\n key: string,\n value: Value\n): Value => {\n if (cache.size >= MAX_CACHE_ENTRIES) {\n cache.delete(cache.keys().next().value as string);\n }\n\n cache.set(key, value);\n\n return value;\n};\n\n/**\n * Parses a single language tag string from the Accept-Language header.\n * Example input: \"en-US;q=0.8\"\n */\nconst parseLanguageTag = (\n tagString: string,\n index: number\n): LanguagePreference | null => {\n const match = LANGUAGE_FORMAT_REGULAR_EXPRESSION.exec(tagString);\n if (!match) {\n return null;\n }\n\n const languageCode = match[1] as string;\n const regionCode = match[2];\n const parameters = match[3];\n\n // Construct the full locale string (e.g., \"en-US\" or \"en\")\n const fullLocale = regionCode\n ? `${languageCode}-${regionCode}`\n : languageCode;\n\n let qualityScore = DEFAULT_QUALITY_SCORE;\n\n // Parse parameters to find the quality score (\"q\")\n if (parameters) {\n const parameterList = parameters.split(';');\n\n for (const parameter of parameterList) {\n const [key, value] = parameter.split('=');\n\n if (key === 'q') {\n qualityScore = parseFloat(value as string);\n }\n }\n }\n\n return {\n languageCode,\n regionCode,\n qualityScore,\n originalIndex: index,\n fullLocale,\n fullLocaleLower: fullLocale.toLowerCase(),\n languageCodeLower: languageCode.toLowerCase(),\n };\n};\n\n/**\n * Parsed available languages. They come from the configuration, so the same\n * few tags are parsed on every request: parse each one once.\n */\nconst availableLanguageCache = new Map<string, LanguagePreference | null>();\n\n/**\n * Parses an available language, reusing the previous parse of the same tag.\n */\nconst parseAvailableLanguage = (\n language: string\n): LanguagePreference | null => {\n const cachedLanguage = availableLanguageCache.get(language);\n\n if (cachedLanguage !== undefined) return cachedLanguage;\n\n return setBoundedCacheEntry(\n availableLanguageCache,\n language,\n parseLanguageTag(language, 0)\n );\n};\n\n/**\n * Parses the full Accept-Language header into a list of language preferences.\n */\nconst parseAcceptLanguageHeader = (\n headerValue: string\n): LanguagePreference[] => {\n const rawTags = headerValue.split(',');\n const preferences: LanguagePreference[] = [];\n\n for (let index = 0; index < rawTags.length; index++) {\n const tag = rawTags[index]?.trim();\n\n if (tag) {\n const parsedLanguage = parseLanguageTag(tag, index);\n\n if (parsedLanguage) {\n preferences.push(parsedLanguage);\n }\n }\n }\n\n return preferences;\n};\n\n/**\n * Determines how well a specific available language matches a requested language preference.\n */\nconst calculateMatchSpecificity = (\n parsedProvided: LanguagePreference,\n preference: LanguagePreference,\n providedIndex: number\n): MatchResult | null => {\n let specificityScore = SpecificityWeight.None;\n\n if (preference.fullLocaleLower === parsedProvided.fullLocaleLower) {\n specificityScore |= SpecificityWeight.Exact;\n } else if (preference.languageCodeLower === parsedProvided.fullLocaleLower) {\n specificityScore |= SpecificityWeight.Prefix;\n } else if (preference.fullLocaleLower === parsedProvided.languageCodeLower) {\n specificityScore |= SpecificityWeight.Broad;\n } else if (preference.fullLocale !== '*') {\n return null;\n }\n\n return {\n providedIndex,\n headerIndex: preference.originalIndex,\n qualityScore: preference.qualityScore,\n specificityScore,\n };\n};\n\n/**\n * Finds the best matching preference from the header for a specific available language.\n */\nconst getBestMatchForLanguage = (\n providedLanguage: string,\n acceptedPreferences: LanguagePreference[],\n providedIndex: number\n): MatchResult => {\n // Initialize with a non-match priority\n let bestMatch: MatchResult = {\n headerIndex: -1,\n qualityScore: 0,\n specificityScore: 0,\n providedIndex,\n };\n\n const parsedProvided = parseAvailableLanguage(providedLanguage);\n\n if (!parsedProvided) {\n return bestMatch;\n }\n\n for (const preference of acceptedPreferences) {\n const matchSpec = calculateMatchSpecificity(\n parsedProvided,\n preference,\n providedIndex\n );\n\n if (matchSpec) {\n // Compare current best match with new match\n const scoreDifference =\n bestMatch.specificityScore - matchSpec.specificityScore ||\n bestMatch.qualityScore - matchSpec.qualityScore ||\n bestMatch.headerIndex - matchSpec.headerIndex;\n\n // If the new match is better (difference < 0), update priority\n if (scoreDifference < 0) {\n bestMatch = matchSpec;\n }\n }\n }\n\n return bestMatch;\n};\n\n/**\n * Comparator function to sort MatchResults.\n * Order: Quality (desc) -> Specificity (desc) -> Header Order (asc) -> Provided Order (asc)\n */\nconst compareMatchResults = (a: MatchResult, b: MatchResult): number => {\n return (\n b.qualityScore - a.qualityScore ||\n b.specificityScore - a.specificityScore ||\n a.headerIndex - b.headerIndex ||\n a.providedIndex - b.providedIndex ||\n 0\n );\n};\n\n/**\n * Derives the list of preferred languages based on the Accept-Language header\n * and an optional list of available languages.\n */\nexport const getPreferredLanguages = (\n acceptHeader: string | undefined,\n availableLanguages?: string[]\n): string[] => {\n // RFC 2616 sec 14.4: no header implies '*'\n const headerValue = acceptHeader === undefined ? '*' : acceptHeader || '';\n const acceptedPreferences = parseAcceptLanguageHeader(headerValue);\n\n // If no specific languages are provided to filter against, return the header languages sorted by quality\n if (!availableLanguages) {\n return acceptedPreferences\n .filter((preference) => preference.qualityScore > 0)\n .sort((a, b) => b.qualityScore - a.qualityScore) // Simple sort by quality\n .map((preference) => preference.fullLocale);\n }\n\n // Map available languages to their match priority against the header\n const matchResults = availableLanguages.map((language, index) =>\n getBestMatchForLanguage(language, acceptedPreferences, index)\n );\n\n return matchResults\n .filter((result) => result.qualityScore > 0)\n .sort(compareMatchResults)\n .map((result) => availableLanguages[result.providedIndex]) as string[];\n};\n\n/**\n * Detected locales, keyed by header, available locales and default locale.\n */\nconst detectedLocaleCache = new Map<string, DeclaredLocales>();\n\n/**\n * Detects the locale from the request headers.\n *\n * Headers are provided by the browser/client and can be used to determine the user's preferred language.\n * This function intersects the user's `Accept-Language` header with the application's available locales.\n *\n * The result is memoized, as it only depends on its arguments.\n */\nexport const localeDetector = (\n headers: Record<string, string | undefined>,\n availableLocales?: Locale[],\n defaultLocale?: Locale\n): DeclaredLocales => {\n const acceptLanguageHeader = headers['accept-language'];\n\n const isCacheable =\n (acceptLanguageHeader?.length ?? 0) <= MAX_CACHEABLE_HEADER_LENGTH;\n\n // A missing header (`*`) and an empty header resolve differently\n const cacheKey = isCacheable\n ? `${acceptLanguageHeader === undefined ? '\\u0000' : `=${acceptLanguageHeader}`}\\u0001${availableLocales?.join(',') ?? '\\u0000'}\\u0001${defaultLocale ?? '\\u0000'}`\n : undefined;\n\n if (cacheKey !== undefined) {\n const cachedLocale = detectedLocaleCache.get(cacheKey);\n\n if (cachedLocale !== undefined) return cachedLocale;\n }\n\n const preferredLocaleStrings = getPreferredLanguages(\n acceptLanguageHeader,\n availableLocales as string[]\n );\n\n const detectedLocale = localeResolver(\n preferredLocaleStrings as Locale[],\n availableLocales,\n defaultLocale\n );\n\n if (cacheKey !== undefined) {\n setBoundedCacheEntry(detectedLocaleCache, cacheKey, detectedLocale);\n }\n\n return detectedLocale;\n};\n"],"mappings":"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"}