@intlayer/core 9.0.0-canary.1 → 9.0.0-canary.10

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 (69) hide show
  1. package/dist/cjs/dictionaryManipulator/index.cjs +1 -0
  2. package/dist/cjs/dictionaryManipulator/mergeQualifiedDictionaries.cjs +1 -5
  3. package/dist/cjs/dictionaryManipulator/mergeQualifiedDictionaries.cjs.map +1 -1
  4. package/dist/cjs/dictionaryManipulator/qualifiedDictionary.cjs +48 -74
  5. package/dist/cjs/dictionaryManipulator/qualifiedDictionary.cjs.map +1 -1
  6. package/dist/cjs/index.cjs +4 -0
  7. package/dist/cjs/interpreter/getContent/plugins.cjs +8 -8
  8. package/dist/cjs/interpreter/getContent/plugins.cjs.map +1 -1
  9. package/dist/cjs/interpreter/getDictionary.cjs +5 -5
  10. package/dist/cjs/interpreter/getDictionary.cjs.map +1 -1
  11. package/dist/cjs/interpreter/getIntlayer.cjs +8 -3
  12. package/dist/cjs/interpreter/getIntlayer.cjs.map +1 -1
  13. package/dist/cjs/localization/comparePaths.cjs +75 -0
  14. package/dist/cjs/localization/comparePaths.cjs.map +1 -0
  15. package/dist/cjs/localization/getLocalizedUrl.cjs +4 -4
  16. package/dist/cjs/localization/getLocalizedUrl.cjs.map +1 -1
  17. package/dist/cjs/localization/getPathWithoutLocale.cjs +2 -2
  18. package/dist/cjs/localization/getPathWithoutLocale.cjs.map +1 -1
  19. package/dist/cjs/localization/getPrefix.cjs +2 -2
  20. package/dist/cjs/localization/getPrefix.cjs.map +1 -1
  21. package/dist/cjs/localization/index.cjs +3 -0
  22. package/dist/cjs/localization/rewriteUtils.cjs +1 -1
  23. package/dist/cjs/localization/rewriteUtils.cjs.map +1 -1
  24. package/dist/cjs/localization/validatePrefix.cjs +2 -2
  25. package/dist/cjs/localization/validatePrefix.cjs.map +1 -1
  26. package/dist/cjs/utils/localeStorage.cjs +4 -4
  27. package/dist/cjs/utils/localeStorage.cjs.map +1 -1
  28. package/dist/esm/dictionaryManipulator/index.mjs +2 -2
  29. package/dist/esm/dictionaryManipulator/mergeQualifiedDictionaries.mjs +1 -5
  30. package/dist/esm/dictionaryManipulator/mergeQualifiedDictionaries.mjs.map +1 -1
  31. package/dist/esm/dictionaryManipulator/qualifiedDictionary.mjs +48 -75
  32. package/dist/esm/dictionaryManipulator/qualifiedDictionary.mjs.map +1 -1
  33. package/dist/esm/index.mjs +3 -2
  34. package/dist/esm/interpreter/getContent/plugins.mjs +8 -8
  35. package/dist/esm/interpreter/getContent/plugins.mjs.map +1 -1
  36. package/dist/esm/interpreter/getDictionary.mjs +5 -5
  37. package/dist/esm/interpreter/getDictionary.mjs.map +1 -1
  38. package/dist/esm/interpreter/getIntlayer.mjs +8 -3
  39. package/dist/esm/interpreter/getIntlayer.mjs.map +1 -1
  40. package/dist/esm/localization/comparePaths.mjs +73 -0
  41. package/dist/esm/localization/comparePaths.mjs.map +1 -0
  42. package/dist/esm/localization/getLocalizedUrl.mjs +4 -4
  43. package/dist/esm/localization/getLocalizedUrl.mjs.map +1 -1
  44. package/dist/esm/localization/getPathWithoutLocale.mjs +2 -2
  45. package/dist/esm/localization/getPathWithoutLocale.mjs.map +1 -1
  46. package/dist/esm/localization/getPrefix.mjs +2 -2
  47. package/dist/esm/localization/getPrefix.mjs.map +1 -1
  48. package/dist/esm/localization/index.mjs +2 -1
  49. package/dist/esm/localization/rewriteUtils.mjs +1 -1
  50. package/dist/esm/localization/rewriteUtils.mjs.map +1 -1
  51. package/dist/esm/localization/validatePrefix.mjs +2 -2
  52. package/dist/esm/localization/validatePrefix.mjs.map +1 -1
  53. package/dist/esm/utils/localeStorage.mjs +4 -4
  54. package/dist/esm/utils/localeStorage.mjs.map +1 -1
  55. package/dist/types/deepTransformPlugins/getFilterMissingTranslationsContent.d.ts +1 -2
  56. package/dist/types/deepTransformPlugins/getFilterTranslationsOnlyContent.d.ts +1 -2
  57. package/dist/types/deepTransformPlugins/getFilteredLocalesContent.d.ts +1 -2
  58. package/dist/types/dictionaryManipulator/index.d.ts +2 -2
  59. package/dist/types/dictionaryManipulator/mergeQualifiedDictionaries.d.ts +1 -1
  60. package/dist/types/dictionaryManipulator/mergeQualifiedDictionaries.d.ts.map +1 -1
  61. package/dist/types/dictionaryManipulator/qualifiedDictionary.d.ts +33 -20
  62. package/dist/types/dictionaryManipulator/qualifiedDictionary.d.ts.map +1 -1
  63. package/dist/types/index.d.ts +3 -2
  64. package/dist/types/interpreter/getDictionary.d.ts +5 -5
  65. package/dist/types/interpreter/getIntlayer.d.ts +1 -1
  66. package/dist/types/localization/comparePaths.d.ts +66 -0
  67. package/dist/types/localization/comparePaths.d.ts.map +1 -0
  68. package/dist/types/localization/index.d.ts +2 -1
  69. package/package.json +7 -7
@@ -5,11 +5,11 @@ const require_localization_getPrefix = require('./getPrefix.cjs');
5
5
  /**
6
6
  * True when the build-time routing mode is known and is NOT 'no-prefix'.
7
7
  */
8
- const TREE_SHAKE_NO_PREFIX = process.env["INTLAYER_ROUTING_MODE"] && process.env["INTLAYER_ROUTING_MODE"] !== "no-prefix";
8
+ const TREE_SHAKE_NO_PREFIX = process.env.INTLAYER_ROUTING_MODE && process.env.INTLAYER_ROUTING_MODE !== "no-prefix";
9
9
  /**
10
10
  * True when the build-time routing mode is known and is NOT 'search-params'.
11
11
  */
12
- const TREE_SHAKE_SEARCH_PARAMS = process.env["INTLAYER_ROUTING_MODE"] && process.env["INTLAYER_ROUTING_MODE"] !== "search-params";
12
+ const TREE_SHAKE_SEARCH_PARAMS = process.env.INTLAYER_ROUTING_MODE && process.env.INTLAYER_ROUTING_MODE !== "search-params";
13
13
  /**
14
14
  * Checks whether a given locale is valid based on the configured locales.
15
15
  *
@@ -1 +1 @@
1
- {"version":3,"file":"validatePrefix.cjs","names":["resolveRoutingConfig","getPrefix"],"sources":["../../../src/localization/validatePrefix.ts"],"sourcesContent":["// ── Tree-shake constants ──────────────────────────────────────────────────────\n// When these env vars are injected at build time, bundlers eliminate the\n// branches guarded by these constants.\n\n/**\n * True when the build-time routing mode is known and is NOT 'no-prefix'.\n */\nconst TREE_SHAKE_NO_PREFIX =\n process.env['INTLAYER_ROUTING_MODE'] &&\n process.env['INTLAYER_ROUTING_MODE'] !== 'no-prefix';\n\n/**\n * True when the build-time routing mode is known and is NOT 'search-params'.\n */\nconst TREE_SHAKE_SEARCH_PARAMS =\n process.env['INTLAYER_ROUTING_MODE'] &&\n process.env['INTLAYER_ROUTING_MODE'] !== 'search-params';\n\nimport type { LocalesValues } from '@intlayer/types/module_augmentation';\nimport {\n getPrefix,\n type RoutingOptions,\n resolveRoutingConfig,\n} from './getPrefix';\n\nexport type ValidatePrefixResult = {\n isValid: boolean;\n localePrefix: string | undefined;\n};\n\n/**\n * Checks whether a given locale is valid based on the configured locales.\n *\n * @param locale - The locale value to validate. Can be `undefined` or `null`.\n * @param options - Optional configuration to override default settings.\n * @param options.locales - Array of valid locales. Defaults to the configured internationalization locales.\n * @param options.defaultLocale - The default locale to use as fallback. Defaults to the configured default locale.\n * @param options.mode - The routing mode (`'prefix'`, `'prefix-all'`, or `'no-prefix'`). Defaults to the configured routing mode.\n * @returns An object containing the validation result and the locale prefix.\n *\n * @example\n * // Check if 'en' is a valid locale\n * const { isValid, localePrefix } = validatePrefix('en');\n *\n * @example\n * // Check with custom options\n * const { isValid, localePrefix } = validatePrefix('fr', {\n * locales: ['en', 'fr', 'es'],\n * defaultLocale: 'en',\n * mode: 'prefix-all',\n * });\n */\nexport const validatePrefix = (\n locale: LocalesValues | undefined | null,\n options?: RoutingOptions\n): ValidatePrefixResult => {\n const { defaultLocale, mode, locales } = resolveRoutingConfig(options);\n\n if (\n (!TREE_SHAKE_NO_PREFIX && mode === 'no-prefix') ||\n (!TREE_SHAKE_SEARCH_PARAMS && mode === 'search-params')\n ) {\n return { isValid: true, localePrefix: undefined };\n }\n\n const { localePrefix } = getPrefix(locale || defaultLocale, {\n mode,\n locales,\n defaultLocale,\n });\n\n if (localePrefix === locale && locale === undefined) {\n return { isValid: true, localePrefix: undefined };\n }\n\n // A segment that doesn't match a locale code pattern (e.g. 'concept', 'compiler')\n // is a content slug from a default-locale URL, not an invalid locale attempt.\n if (locale && !/^[a-z]{2,3}(-[a-zA-Z]{2,4})?$/.test(locale)) {\n return { isValid: true, localePrefix: undefined };\n }\n\n const isValid = locales.some((localeEl) => localeEl === locale);\n\n return { isValid, localePrefix };\n};\n"],"mappings":";;;;;;;AAOA,MAAM,uBACJ,QAAQ,IAAI,4BACZ,QAAQ,IAAI,6BAA6B;;;;AAK3C,MAAM,2BACJ,QAAQ,IAAI,4BACZ,QAAQ,IAAI,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;AAoC3C,MAAa,kBACX,QACA,YACyB;CACzB,MAAM,EAAE,eAAe,MAAM,YAAYA,oDAAqB,QAAQ;AAEtE,KACG,CAAC,wBAAwB,SAAS,eAClC,CAAC,4BAA4B,SAAS,gBAEvC,QAAO;EAAE,SAAS;EAAM,cAAc;EAAW;CAGnD,MAAM,EAAE,iBAAiBC,yCAAU,UAAU,eAAe;EAC1D;EACA;EACA;EACD,CAAC;AAEF,KAAI,iBAAiB,UAAU,WAAW,OACxC,QAAO;EAAE,SAAS;EAAM,cAAc;EAAW;AAKnD,KAAI,UAAU,CAAC,gCAAgC,KAAK,OAAO,CACzD,QAAO;EAAE,SAAS;EAAM,cAAc;EAAW;AAKnD,QAAO;EAAE,SAFO,QAAQ,MAAM,aAAa,aAAa,OAExC;EAAE;EAAc"}
1
+ {"version":3,"file":"validatePrefix.cjs","names":["resolveRoutingConfig","getPrefix"],"sources":["../../../src/localization/validatePrefix.ts"],"sourcesContent":["// ── Tree-shake constants ──────────────────────────────────────────────────────\n// When these env vars are injected at build time, bundlers eliminate the\n// branches guarded by these constants.\n\n/**\n * True when the build-time routing mode is known and is NOT 'no-prefix'.\n */\nconst TREE_SHAKE_NO_PREFIX =\n process.env.INTLAYER_ROUTING_MODE &&\n process.env.INTLAYER_ROUTING_MODE !== 'no-prefix';\n\n/**\n * True when the build-time routing mode is known and is NOT 'search-params'.\n */\nconst TREE_SHAKE_SEARCH_PARAMS =\n process.env.INTLAYER_ROUTING_MODE &&\n process.env.INTLAYER_ROUTING_MODE !== 'search-params';\n\nimport type { LocalesValues } from '@intlayer/types/module_augmentation';\nimport {\n getPrefix,\n type RoutingOptions,\n resolveRoutingConfig,\n} from './getPrefix';\n\nexport type ValidatePrefixResult = {\n isValid: boolean;\n localePrefix: string | undefined;\n};\n\n/**\n * Checks whether a given locale is valid based on the configured locales.\n *\n * @param locale - The locale value to validate. Can be `undefined` or `null`.\n * @param options - Optional configuration to override default settings.\n * @param options.locales - Array of valid locales. Defaults to the configured internationalization locales.\n * @param options.defaultLocale - The default locale to use as fallback. Defaults to the configured default locale.\n * @param options.mode - The routing mode (`'prefix'`, `'prefix-all'`, or `'no-prefix'`). Defaults to the configured routing mode.\n * @returns An object containing the validation result and the locale prefix.\n *\n * @example\n * // Check if 'en' is a valid locale\n * const { isValid, localePrefix } = validatePrefix('en');\n *\n * @example\n * // Check with custom options\n * const { isValid, localePrefix } = validatePrefix('fr', {\n * locales: ['en', 'fr', 'es'],\n * defaultLocale: 'en',\n * mode: 'prefix-all',\n * });\n */\nexport const validatePrefix = (\n locale: LocalesValues | undefined | null,\n options?: RoutingOptions\n): ValidatePrefixResult => {\n const { defaultLocale, mode, locales } = resolveRoutingConfig(options);\n\n if (\n (!TREE_SHAKE_NO_PREFIX && mode === 'no-prefix') ||\n (!TREE_SHAKE_SEARCH_PARAMS && mode === 'search-params')\n ) {\n return { isValid: true, localePrefix: undefined };\n }\n\n const { localePrefix } = getPrefix(locale || defaultLocale, {\n mode,\n locales,\n defaultLocale,\n });\n\n if (localePrefix === locale && locale === undefined) {\n return { isValid: true, localePrefix: undefined };\n }\n\n // A segment that doesn't match a locale code pattern (e.g. 'concept', 'compiler')\n // is a content slug from a default-locale URL, not an invalid locale attempt.\n if (locale && !/^[a-z]{2,3}(-[a-zA-Z]{2,4})?$/.test(locale)) {\n return { isValid: true, localePrefix: undefined };\n }\n\n const isValid = locales.some((localeEl) => localeEl === locale);\n\n return { isValid, localePrefix };\n};\n"],"mappings":";;;;;;;AAOA,MAAM,uBACJ,QAAQ,IAAI,yBACZ,QAAQ,IAAI,0BAA0B;;;;AAKxC,MAAM,2BACJ,QAAQ,IAAI,yBACZ,QAAQ,IAAI,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;AAoCxC,MAAa,kBACX,QACA,YACyB;CACzB,MAAM,EAAE,eAAe,MAAM,YAAYA,oDAAqB,QAAQ;AAEtE,KACG,CAAC,wBAAwB,SAAS,eAClC,CAAC,4BAA4B,SAAS,gBAEvC,QAAO;EAAE,SAAS;EAAM,cAAc;EAAW;CAGnD,MAAM,EAAE,iBAAiBC,yCAAU,UAAU,eAAe;EAC1D;EACA;EACA;EACD,CAAC;AAEF,KAAI,iBAAiB,UAAU,WAAW,OACxC,QAAO;EAAE,SAAS;EAAM,cAAc;EAAW;AAKnD,KAAI,UAAU,CAAC,gCAAgC,KAAK,OAAO,CACzD,QAAO;EAAE,SAAS;EAAM,cAAc;EAAW;AAKnD,QAAO;EAAE,SAFO,QAAQ,MAAM,aAAa,aAAa,OAExC;EAAE;EAAc"}
@@ -8,19 +8,19 @@ let _intlayer_config_built = require("@intlayer/config/built");
8
8
  /**
9
9
  * True when cookie storage is explicitly disabled at build time.
10
10
  */
11
- const TREE_SHAKE_STORAGE_COOKIES = process.env["INTLAYER_ROUTING_STORAGE_COOKIES"] === "false";
11
+ const TREE_SHAKE_STORAGE_COOKIES = process.env.INTLAYER_ROUTING_STORAGE_COOKIES === "false";
12
12
  /**
13
13
  * True when localStorage is explicitly disabled at build time.
14
14
  */
15
- const TREE_SHAKE_STORAGE_LOCAL_STORAGE = process.env["INTLAYER_ROUTING_STORAGE_LOCALSTORAGE"] === "false";
15
+ const TREE_SHAKE_STORAGE_LOCAL_STORAGE = process.env.INTLAYER_ROUTING_STORAGE_LOCALSTORAGE === "false";
16
16
  /**
17
17
  * True when sessionStorage is explicitly disabled at build time.
18
18
  */
19
- const TREE_SHAKE_STORAGE_SESSION_STORAGE = process.env["INTLAYER_ROUTING_STORAGE_SESSIONSTORAGE"] === "false";
19
+ const TREE_SHAKE_STORAGE_SESSION_STORAGE = process.env.INTLAYER_ROUTING_STORAGE_SESSIONSTORAGE === "false";
20
20
  /**
21
21
  * True when header storage is explicitly disabled at build time.
22
22
  */
23
- const TREE_SHAKE_STORAGE_HEADERS = process.env["INTLAYER_ROUTING_STORAGE_HEADERS"] === "false";
23
+ const TREE_SHAKE_STORAGE_HEADERS = process.env.INTLAYER_ROUTING_STORAGE_HEADERS === "false";
24
24
  const localeStorageOptions = {
25
25
  getCookie: (name) => document.cookie.split(";").find((c) => c.trim().startsWith(`${name}=`))?.split("=")[1],
26
26
  getLocaleStorage: (name) => localStorage.getItem(name),
@@ -1 +1 @@
1
- {"version":3,"file":"localeStorage.cjs","names":["internationalization","routing","resolveExpiresToTimestamp","buildCookieString","getCookie"],"sources":["../../../src/utils/localeStorage.ts"],"sourcesContent":["import { internationalization, routing } from '@intlayer/config/built';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { LocalesValues } from '@intlayer/types/module_augmentation';\nimport { buildCookieString, resolveExpiresToTimestamp } from './cookieExpiry';\nimport { getCookie } from './getCookie';\n\n// ── Tree-shake constants ──────────────────────────────────────────────────────\n// When these env vars are injected at build time, bundlers eliminate the\n// branches guarded by these constants.\n\n/**\n * True when cookie storage is explicitly disabled at build time.\n */\nconst TREE_SHAKE_STORAGE_COOKIES =\n process.env['INTLAYER_ROUTING_STORAGE_COOKIES'] === 'false';\n\n/**\n * True when localStorage is explicitly disabled at build time.\n */\nconst TREE_SHAKE_STORAGE_LOCAL_STORAGE =\n process.env['INTLAYER_ROUTING_STORAGE_LOCALSTORAGE'] === 'false';\n\n/**\n * True when sessionStorage is explicitly disabled at build time.\n */\nconst TREE_SHAKE_STORAGE_SESSION_STORAGE =\n process.env['INTLAYER_ROUTING_STORAGE_SESSIONSTORAGE'] === 'false';\n\n/**\n * True when header storage is explicitly disabled at build time.\n */\nconst TREE_SHAKE_STORAGE_HEADERS =\n process.env['INTLAYER_ROUTING_STORAGE_HEADERS'] === 'false';\n\n// ============================================================================\n// Shared types\n// ============================================================================\n\nexport type CookieBuildAttributes = {\n domain?: string;\n path?: string;\n secure?: boolean;\n httpOnly?: boolean;\n sameSite?: 'strict' | 'lax' | 'none';\n /**\n * Absolute expiry as milliseconds since epoch, ready for `cookieStore.set()`.\n * Already resolved from the normalized `expires` by\n * `resolveExpiresToTimestamp`.\n */\n expires?: number | undefined;\n};\n\n// ============================================================================\n// Client-specific types and functions\n// (cookies via browser APIs, localStorage, sessionStorage — no headers)\n// ============================================================================\n\nexport type LocaleStorageClientOptions = {\n overwrite?: boolean;\n isCookieEnabled?: boolean;\n setCookieStore?: (\n name: string,\n value: string,\n cookie: CookieBuildAttributes\n ) => void;\n setCookieString?: (name: string, cookie: string) => void;\n getCookie?: (name: string) => string | undefined | null;\n setSessionStorage?: (name: string, value: string) => void;\n getSessionStorage?: (name: string) => string | undefined | null;\n setLocaleStorage?: (name: string, value: string) => void;\n getLocaleStorage?: (name: string) => string | undefined | null;\n};\n\n// cookieStore is part of the experimental Cookie Store API\ndeclare const cookieStore: any;\n\nexport const localeStorageOptions: LocaleStorageClientOptions = {\n getCookie: (name: string) =>\n document.cookie\n .split(';')\n .find((c) => c.trim().startsWith(`${name}=`))\n ?.split('=')[1],\n getLocaleStorage: (name: string) => localStorage.getItem(name),\n getSessionStorage: (name: string) => sessionStorage.getItem(name),\n isCookieEnabled: true,\n setCookieStore: (name, value, attributes) =>\n cookieStore.set({\n name,\n value,\n path: attributes.path,\n domain: attributes.domain,\n expires: attributes.expires,\n sameSite: attributes.sameSite,\n }),\n setCookieString: (_name, cookie) => {\n // biome-ignore lint/suspicious/noDocumentCookie: set cookie fallback\n document.cookie = cookie;\n },\n setSessionStorage: (name, value) => sessionStorage.setItem(name, value),\n setLocaleStorage: (name, value) => localStorage.setItem(name, value),\n};\n\n/**\n * Retrieves the locale from browser storage mechanisms\n * (cookies, localStorage, sessionStorage).\n * Does not read from headers — use `getLocaleFromStorageServer` for that.\n */\nexport const getLocaleFromStorageClient = (\n options: LocaleStorageClientOptions = localeStorageOptions\n): Locale | undefined => {\n const { locales } = internationalization;\n\n if (options?.isCookieEnabled === false) return undefined;\n\n const isValidLocale = (value: string | null | undefined): value is Locale =>\n !!value && locales.includes(value as Locale);\n\n if (!TREE_SHAKE_STORAGE_COOKIES) {\n for (let i = 0; i < (routing.storage.cookies ?? []).length; i++) {\n try {\n const value = options?.getCookie?.(routing.storage.cookies![i].name);\n if (isValidLocale(value)) return value;\n } catch {}\n }\n }\n\n if (!TREE_SHAKE_STORAGE_LOCAL_STORAGE) {\n for (let i = 0; i < (routing.storage.localStorage ?? []).length; i++) {\n try {\n const value = options?.getLocaleStorage?.(\n routing.storage.localStorage![i].name\n );\n if (isValidLocale(value)) return value;\n } catch {}\n }\n }\n\n if (!TREE_SHAKE_STORAGE_SESSION_STORAGE && routing.storage.sessionStorage) {\n for (let i = 0; i < routing.storage.sessionStorage.length; i++) {\n try {\n const value = options?.getSessionStorage?.(\n routing.storage.sessionStorage[i].name\n );\n if (isValidLocale(value)) return value;\n } catch {}\n }\n }\n};\n\n/**\n * Stores the locale in browser storage mechanisms\n * (cookies, localStorage, sessionStorage).\n * Does not write to headers — use `setLocaleInStorageServer` for that.\n */\nexport const setLocaleInStorageClient = (\n locale: LocalesValues,\n options?: LocaleStorageClientOptions\n): void => {\n if (options?.isCookieEnabled === false) return;\n\n if (!TREE_SHAKE_STORAGE_COOKIES && routing.storage.cookies) {\n for (let i = 0; i < routing.storage.cookies.length; i++) {\n const { name, attributes } = routing.storage.cookies[i];\n try {\n if (options?.setCookieStore) {\n options.setCookieStore(name, locale, {\n ...attributes,\n expires: resolveExpiresToTimestamp(attributes.expires),\n });\n }\n } catch {\n try {\n if (options?.setCookieString) {\n options.setCookieString(\n name,\n buildCookieString(name, locale, attributes)\n );\n }\n } catch {}\n }\n }\n }\n\n if (\n !TREE_SHAKE_STORAGE_LOCAL_STORAGE &&\n routing.storage.localStorage &&\n options?.setLocaleStorage\n ) {\n for (let i = 0; i < routing.storage.localStorage.length; i++) {\n const { name } = routing.storage.localStorage[i];\n try {\n if (!(options?.overwrite ?? true) && options?.getLocaleStorage) {\n if (options.getLocaleStorage(name)) continue;\n }\n options.setLocaleStorage(name, locale);\n } catch {}\n }\n }\n\n if (\n !TREE_SHAKE_STORAGE_SESSION_STORAGE &&\n routing.storage.sessionStorage &&\n options?.setSessionStorage\n ) {\n for (let i = 0; i < routing.storage.sessionStorage.length; i++) {\n const { name } = routing.storage.sessionStorage[i];\n try {\n if (!(options?.overwrite ?? true) && options?.getSessionStorage) {\n if (options.getSessionStorage(name)) continue;\n }\n options.setSessionStorage(name, locale);\n } catch {}\n }\n }\n};\n\n/**\n * Client-side locale storage utility.\n * Handles cookies (browser), localStorage and sessionStorage.\n * Does not access headers.\n *\n * @example\n * ```ts\n * const storage = LocaleStorageClient(localeStorageOptions);\n * const locale = storage.getLocale();\n * storage.setLocale('fr');\n * ```\n */\nexport const LocaleStorageClient = (options: LocaleStorageClientOptions) => ({\n getLocale: () => getLocaleFromStorageClient(options),\n setLocale: (locale: LocalesValues) =>\n setLocaleInStorageClient(locale, options),\n});\n\n// ============================================================================\n// Server-specific types and functions\n// (cookies via injected getter/setter, headers — no localStorage/sessionStorage)\n// ============================================================================\n\nexport type LocaleStorageServerOptions = {\n overwrite?: boolean;\n isCookieEnabled?: boolean;\n setCookieStore?: (\n name: string,\n value: string,\n cookie: CookieBuildAttributes\n ) => void;\n setCookieString?: (name: string, cookie: string) => void;\n getCookie?: (name: string) => string | undefined | null;\n getHeader?: (name: string) => string | undefined | null;\n setHeader?: (name: string, value: string) => void;\n};\n\n/**\n * Retrieves the locale from server-side storage mechanisms (cookies, headers).\n * Does not access localStorage or sessionStorage.\n * No browser cookie fallback — the caller must provide `getCookie`.\n */\nexport const getLocaleFromStorageServer = (\n options: LocaleStorageServerOptions\n): Locale | undefined => {\n const { locales } = internationalization;\n\n if (options?.isCookieEnabled === false) return undefined;\n\n const isValidLocale = (value: string | null | undefined): value is Locale =>\n !!value && locales.includes(value as Locale);\n\n if (!TREE_SHAKE_STORAGE_COOKIES && routing.storage.cookies) {\n for (let i = 0; i < routing.storage.cookies.length; i++) {\n try {\n const value = options?.getCookie?.(routing.storage.cookies[i].name);\n if (isValidLocale(value)) return value;\n } catch {}\n }\n }\n\n if (!TREE_SHAKE_STORAGE_HEADERS && routing.storage.headers) {\n for (let i = 0; i < routing.storage.headers.length; i++) {\n try {\n const value = options?.getHeader?.(routing.storage.headers[i].name);\n if (isValidLocale(value)) return value;\n } catch {}\n }\n }\n};\n\n/**\n * Stores the locale in server-side storage mechanisms (cookies, headers).\n * Does not write to localStorage or sessionStorage.\n */\nexport const setLocaleInStorageServer = (\n locale: LocalesValues,\n options?: LocaleStorageServerOptions\n): void => {\n if (options?.isCookieEnabled === false) return;\n\n if (!TREE_SHAKE_STORAGE_COOKIES && routing.storage.cookies) {\n for (let i = 0; i < routing.storage.cookies.length; i++) {\n const { name, attributes } = routing.storage.cookies[i];\n\n try {\n if (options?.setCookieStore) {\n options.setCookieStore(name, locale, {\n ...attributes,\n expires: resolveExpiresToTimestamp(attributes.expires),\n });\n }\n } catch {\n try {\n if (options?.setCookieString) {\n options.setCookieString(\n name,\n buildCookieString(name, locale, attributes)\n );\n }\n } catch {}\n }\n }\n }\n\n if (\n !TREE_SHAKE_STORAGE_HEADERS &&\n routing.storage.headers &&\n options?.setHeader\n ) {\n for (let i = 0; i < routing.storage.headers.length; i++) {\n try {\n options.setHeader(routing.storage.headers[i].name, locale);\n } catch {}\n }\n }\n};\n\n/**\n * Server-side locale storage utility.\n * Handles cookies (via injected getter/setter) and headers.\n * Does not access localStorage or sessionStorage.\n *\n * @example\n * ```ts\n * const storage = LocaleStorageServer({\n * getCookie: (name) => req.cookies[name],\n * setCookieStore: (name, value, attrs) => res.cookie(name, value, attrs),\n * getHeader: (name) => req.headers[name],\n * setHeader: (name, value) => res.setHeader(name, value),\n * });\n * const locale = storage.getLocale();\n * storage.setLocale('fr');\n * ```\n */\nexport const LocaleStorageServer = (options: LocaleStorageServerOptions) => ({\n getLocale: () => getLocaleFromStorageServer(options),\n setLocale: (locale: LocalesValues) =>\n setLocaleInStorageServer(locale, options),\n});\n\n// ============================================================================\n// Deprecated: combined LocaleStorage\n// Use LocaleStorageClient or LocaleStorageServer instead\n// ============================================================================\n\n/**\n * @deprecated Use {@link LocaleStorageClientOptions} or {@link LocaleStorageServerOptions} instead.\n */\nexport type LocaleStorageOptions = LocaleStorageClientOptions &\n LocaleStorageServerOptions;\n\n/**\n * Retrieves the locale from all storage mechanisms\n * (cookies, localStorage, sessionStorage, headers).\n *\n * @deprecated Use {@link getLocaleFromStorageClient} (browser) or\n * {@link getLocaleFromStorageServer} (server) instead.\n */\nexport const getLocaleFromStorage = (\n options: Pick<\n LocaleStorageOptions,\n | 'getCookie'\n | 'getSessionStorage'\n | 'getLocaleStorage'\n | 'getHeader'\n | 'isCookieEnabled'\n >\n): Locale | undefined => {\n const { locales } = internationalization;\n\n if (options?.isCookieEnabled === false) return undefined;\n\n const isValidLocale = (value: string | null | undefined): value is Locale =>\n !!value && locales.includes(value as Locale);\n\n const readCookie = (name: string): string | undefined => {\n try {\n const fromOption = options?.getCookie?.(name);\n if (fromOption !== null && fromOption !== undefined) return fromOption;\n } catch {}\n // Browser fallback kept for backward compatibility\n return getCookie(name);\n };\n\n if (!TREE_SHAKE_STORAGE_COOKIES && routing.storage.cookies) {\n for (let i = 0; i < routing.storage.cookies.length; i++) {\n const value = readCookie(routing.storage.cookies[i].name);\n if (isValidLocale(value)) return value;\n }\n }\n\n if (!TREE_SHAKE_STORAGE_LOCAL_STORAGE && routing.storage.localStorage) {\n for (let i = 0; i < routing.storage.localStorage.length; i++) {\n try {\n const value = options?.getLocaleStorage?.(\n routing.storage.localStorage[i].name\n );\n if (isValidLocale(value)) return value;\n } catch {}\n }\n }\n\n if (!TREE_SHAKE_STORAGE_SESSION_STORAGE && routing.storage.sessionStorage) {\n for (let i = 0; i < routing.storage.sessionStorage.length; i++) {\n try {\n const value = options?.getSessionStorage?.(\n routing.storage.sessionStorage[i].name\n );\n if (isValidLocale(value)) return value;\n } catch {}\n }\n }\n\n if (!TREE_SHAKE_STORAGE_HEADERS && routing.storage.headers) {\n for (let i = 0; i < routing.storage.headers.length; i++) {\n try {\n const value = options?.getHeader?.(routing.storage.headers[i].name);\n if (isValidLocale(value)) return value;\n } catch {}\n }\n }\n};\n\n/**\n * Stores the locale in all configured storage mechanisms\n * (cookies, localStorage, sessionStorage, headers).\n *\n * @deprecated Use {@link setLocaleInStorageClient} (browser) or\n * {@link setLocaleInStorageServer} (server) instead.\n */\nexport const setLocaleInStorage = (\n locale: LocalesValues,\n options?: LocaleStorageOptions\n): void => {\n if (options?.isCookieEnabled === false) return;\n\n if (!TREE_SHAKE_STORAGE_COOKIES && routing.storage.cookies) {\n for (let i = 0; i < routing.storage.cookies.length; i++) {\n const { name, attributes } = routing.storage.cookies[i];\n try {\n if (options?.setCookieStore) {\n options.setCookieStore(name, locale, {\n ...attributes,\n expires: resolveExpiresToTimestamp(attributes.expires),\n });\n }\n } catch {\n try {\n if (options?.setCookieString) {\n options.setCookieString(\n name,\n buildCookieString(name, locale, attributes)\n );\n }\n } catch {}\n }\n }\n }\n\n if (\n !TREE_SHAKE_STORAGE_LOCAL_STORAGE &&\n routing.storage.localStorage &&\n options?.setLocaleStorage\n ) {\n for (let i = 0; i < routing.storage.localStorage.length; i++) {\n const { name } = routing.storage.localStorage[i];\n try {\n if (!(options?.overwrite ?? true) && options?.getLocaleStorage) {\n if (options.getLocaleStorage(name)) continue;\n }\n options.setLocaleStorage(name, locale);\n } catch {}\n }\n }\n\n if (\n !TREE_SHAKE_STORAGE_SESSION_STORAGE &&\n routing.storage.sessionStorage &&\n options?.setSessionStorage\n ) {\n for (let i = 0; i < routing.storage.sessionStorage.length; i++) {\n const { name } = routing.storage.sessionStorage[i];\n try {\n if (!(options?.overwrite ?? true) && options?.getSessionStorage) {\n if (options.getSessionStorage(name)) continue;\n }\n options.setSessionStorage(name, locale);\n } catch {}\n }\n }\n\n if (\n !TREE_SHAKE_STORAGE_HEADERS &&\n routing.storage.headers &&\n options?.setHeader\n ) {\n for (let i = 0; i < routing.storage.headers.length; i++) {\n try {\n options.setHeader(routing.storage.headers[i].name, locale);\n } catch {}\n }\n }\n};\n\n/**\n * Utility object to get and set the locale in storage based on configuration.\n *\n * @deprecated Use {@link LocaleStorageClient} (browser) or\n * {@link LocaleStorageServer} (server) instead.\n */\nexport const LocaleStorage = (options: LocaleStorageOptions) => ({\n getLocale: () => getLocaleFromStorage(options),\n setLocale: (locale: LocalesValues) => setLocaleInStorage(locale, options),\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"localeStorage.cjs","names":["internationalization","routing","resolveExpiresToTimestamp","buildCookieString","getCookie"],"sources":["../../../src/utils/localeStorage.ts"],"sourcesContent":["import { internationalization, routing } from '@intlayer/config/built';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { LocalesValues } from '@intlayer/types/module_augmentation';\nimport { buildCookieString, resolveExpiresToTimestamp } from './cookieExpiry';\nimport { getCookie } from './getCookie';\n\n// ── Tree-shake constants ──────────────────────────────────────────────────────\n// When these env vars are injected at build time, bundlers eliminate the\n// branches guarded by these constants.\n\n/**\n * True when cookie storage is explicitly disabled at build time.\n */\nconst TREE_SHAKE_STORAGE_COOKIES =\n process.env.INTLAYER_ROUTING_STORAGE_COOKIES === 'false';\n\n/**\n * True when localStorage is explicitly disabled at build time.\n */\nconst TREE_SHAKE_STORAGE_LOCAL_STORAGE =\n process.env.INTLAYER_ROUTING_STORAGE_LOCALSTORAGE === 'false';\n\n/**\n * True when sessionStorage is explicitly disabled at build time.\n */\nconst TREE_SHAKE_STORAGE_SESSION_STORAGE =\n process.env.INTLAYER_ROUTING_STORAGE_SESSIONSTORAGE === 'false';\n\n/**\n * True when header storage is explicitly disabled at build time.\n */\nconst TREE_SHAKE_STORAGE_HEADERS =\n process.env.INTLAYER_ROUTING_STORAGE_HEADERS === 'false';\n\n// ============================================================================\n// Shared types\n// ============================================================================\n\nexport type CookieBuildAttributes = {\n domain?: string;\n path?: string;\n secure?: boolean;\n httpOnly?: boolean;\n sameSite?: 'strict' | 'lax' | 'none';\n /**\n * Absolute expiry as milliseconds since epoch, ready for `cookieStore.set()`.\n * Already resolved from the normalized `expires` by\n * `resolveExpiresToTimestamp`.\n */\n expires?: number | undefined;\n};\n\n// ============================================================================\n// Client-specific types and functions\n// (cookies via browser APIs, localStorage, sessionStorage — no headers)\n// ============================================================================\n\nexport type LocaleStorageClientOptions = {\n overwrite?: boolean;\n isCookieEnabled?: boolean;\n setCookieStore?: (\n name: string,\n value: string,\n cookie: CookieBuildAttributes\n ) => void;\n setCookieString?: (name: string, cookie: string) => void;\n getCookie?: (name: string) => string | undefined | null;\n setSessionStorage?: (name: string, value: string) => void;\n getSessionStorage?: (name: string) => string | undefined | null;\n setLocaleStorage?: (name: string, value: string) => void;\n getLocaleStorage?: (name: string) => string | undefined | null;\n};\n\n// cookieStore is part of the experimental Cookie Store API\ndeclare const cookieStore: any;\n\nexport const localeStorageOptions: LocaleStorageClientOptions = {\n getCookie: (name: string) =>\n document.cookie\n .split(';')\n .find((c) => c.trim().startsWith(`${name}=`))\n ?.split('=')[1],\n getLocaleStorage: (name: string) => localStorage.getItem(name),\n getSessionStorage: (name: string) => sessionStorage.getItem(name),\n isCookieEnabled: true,\n setCookieStore: (name, value, attributes) =>\n cookieStore.set({\n name,\n value,\n path: attributes.path,\n domain: attributes.domain,\n expires: attributes.expires,\n sameSite: attributes.sameSite,\n }),\n setCookieString: (_name, cookie) => {\n // biome-ignore lint/suspicious/noDocumentCookie: set cookie fallback\n document.cookie = cookie;\n },\n setSessionStorage: (name, value) => sessionStorage.setItem(name, value),\n setLocaleStorage: (name, value) => localStorage.setItem(name, value),\n};\n\n/**\n * Retrieves the locale from browser storage mechanisms\n * (cookies, localStorage, sessionStorage).\n * Does not read from headers — use `getLocaleFromStorageServer` for that.\n */\nexport const getLocaleFromStorageClient = (\n options: LocaleStorageClientOptions = localeStorageOptions\n): Locale | undefined => {\n const { locales } = internationalization;\n\n if (options?.isCookieEnabled === false) return undefined;\n\n const isValidLocale = (value: string | null | undefined): value is Locale =>\n !!value && locales.includes(value as Locale);\n\n if (!TREE_SHAKE_STORAGE_COOKIES) {\n for (let i = 0; i < (routing.storage.cookies ?? []).length; i++) {\n try {\n const value = options?.getCookie?.(routing.storage.cookies![i].name);\n if (isValidLocale(value)) return value;\n } catch {}\n }\n }\n\n if (!TREE_SHAKE_STORAGE_LOCAL_STORAGE) {\n for (let i = 0; i < (routing.storage.localStorage ?? []).length; i++) {\n try {\n const value = options?.getLocaleStorage?.(\n routing.storage.localStorage![i].name\n );\n if (isValidLocale(value)) return value;\n } catch {}\n }\n }\n\n if (!TREE_SHAKE_STORAGE_SESSION_STORAGE && routing.storage.sessionStorage) {\n for (let i = 0; i < routing.storage.sessionStorage.length; i++) {\n try {\n const value = options?.getSessionStorage?.(\n routing.storage.sessionStorage[i].name\n );\n if (isValidLocale(value)) return value;\n } catch {}\n }\n }\n};\n\n/**\n * Stores the locale in browser storage mechanisms\n * (cookies, localStorage, sessionStorage).\n * Does not write to headers — use `setLocaleInStorageServer` for that.\n */\nexport const setLocaleInStorageClient = (\n locale: LocalesValues,\n options?: LocaleStorageClientOptions\n): void => {\n if (options?.isCookieEnabled === false) return;\n\n if (!TREE_SHAKE_STORAGE_COOKIES && routing.storage.cookies) {\n for (let i = 0; i < routing.storage.cookies.length; i++) {\n const { name, attributes } = routing.storage.cookies[i];\n try {\n if (options?.setCookieStore) {\n options.setCookieStore(name, locale, {\n ...attributes,\n expires: resolveExpiresToTimestamp(attributes.expires),\n });\n }\n } catch {\n try {\n if (options?.setCookieString) {\n options.setCookieString(\n name,\n buildCookieString(name, locale, attributes)\n );\n }\n } catch {}\n }\n }\n }\n\n if (\n !TREE_SHAKE_STORAGE_LOCAL_STORAGE &&\n routing.storage.localStorage &&\n options?.setLocaleStorage\n ) {\n for (let i = 0; i < routing.storage.localStorage.length; i++) {\n const { name } = routing.storage.localStorage[i];\n try {\n if (!(options?.overwrite ?? true) && options?.getLocaleStorage) {\n if (options.getLocaleStorage(name)) continue;\n }\n options.setLocaleStorage(name, locale);\n } catch {}\n }\n }\n\n if (\n !TREE_SHAKE_STORAGE_SESSION_STORAGE &&\n routing.storage.sessionStorage &&\n options?.setSessionStorage\n ) {\n for (let i = 0; i < routing.storage.sessionStorage.length; i++) {\n const { name } = routing.storage.sessionStorage[i];\n try {\n if (!(options?.overwrite ?? true) && options?.getSessionStorage) {\n if (options.getSessionStorage(name)) continue;\n }\n options.setSessionStorage(name, locale);\n } catch {}\n }\n }\n};\n\n/**\n * Client-side locale storage utility.\n * Handles cookies (browser), localStorage and sessionStorage.\n * Does not access headers.\n *\n * @example\n * ```ts\n * const storage = LocaleStorageClient(localeStorageOptions);\n * const locale = storage.getLocale();\n * storage.setLocale('fr');\n * ```\n */\nexport const LocaleStorageClient = (options: LocaleStorageClientOptions) => ({\n getLocale: () => getLocaleFromStorageClient(options),\n setLocale: (locale: LocalesValues) =>\n setLocaleInStorageClient(locale, options),\n});\n\n// ============================================================================\n// Server-specific types and functions\n// (cookies via injected getter/setter, headers — no localStorage/sessionStorage)\n// ============================================================================\n\nexport type LocaleStorageServerOptions = {\n overwrite?: boolean;\n isCookieEnabled?: boolean;\n setCookieStore?: (\n name: string,\n value: string,\n cookie: CookieBuildAttributes\n ) => void;\n setCookieString?: (name: string, cookie: string) => void;\n getCookie?: (name: string) => string | undefined | null;\n getHeader?: (name: string) => string | undefined | null;\n setHeader?: (name: string, value: string) => void;\n};\n\n/**\n * Retrieves the locale from server-side storage mechanisms (cookies, headers).\n * Does not access localStorage or sessionStorage.\n * No browser cookie fallback — the caller must provide `getCookie`.\n */\nexport const getLocaleFromStorageServer = (\n options: LocaleStorageServerOptions\n): Locale | undefined => {\n const { locales } = internationalization;\n\n if (options?.isCookieEnabled === false) return undefined;\n\n const isValidLocale = (value: string | null | undefined): value is Locale =>\n !!value && locales.includes(value as Locale);\n\n if (!TREE_SHAKE_STORAGE_COOKIES && routing.storage.cookies) {\n for (let i = 0; i < routing.storage.cookies.length; i++) {\n try {\n const value = options?.getCookie?.(routing.storage.cookies[i].name);\n if (isValidLocale(value)) return value;\n } catch {}\n }\n }\n\n if (!TREE_SHAKE_STORAGE_HEADERS && routing.storage.headers) {\n for (let i = 0; i < routing.storage.headers.length; i++) {\n try {\n const value = options?.getHeader?.(routing.storage.headers[i].name);\n if (isValidLocale(value)) return value;\n } catch {}\n }\n }\n};\n\n/**\n * Stores the locale in server-side storage mechanisms (cookies, headers).\n * Does not write to localStorage or sessionStorage.\n */\nexport const setLocaleInStorageServer = (\n locale: LocalesValues,\n options?: LocaleStorageServerOptions\n): void => {\n if (options?.isCookieEnabled === false) return;\n\n if (!TREE_SHAKE_STORAGE_COOKIES && routing.storage.cookies) {\n for (let i = 0; i < routing.storage.cookies.length; i++) {\n const { name, attributes } = routing.storage.cookies[i];\n\n try {\n if (options?.setCookieStore) {\n options.setCookieStore(name, locale, {\n ...attributes,\n expires: resolveExpiresToTimestamp(attributes.expires),\n });\n }\n } catch {\n try {\n if (options?.setCookieString) {\n options.setCookieString(\n name,\n buildCookieString(name, locale, attributes)\n );\n }\n } catch {}\n }\n }\n }\n\n if (\n !TREE_SHAKE_STORAGE_HEADERS &&\n routing.storage.headers &&\n options?.setHeader\n ) {\n for (let i = 0; i < routing.storage.headers.length; i++) {\n try {\n options.setHeader(routing.storage.headers[i].name, locale);\n } catch {}\n }\n }\n};\n\n/**\n * Server-side locale storage utility.\n * Handles cookies (via injected getter/setter) and headers.\n * Does not access localStorage or sessionStorage.\n *\n * @example\n * ```ts\n * const storage = LocaleStorageServer({\n * getCookie: (name) => req.cookies[name],\n * setCookieStore: (name, value, attrs) => res.cookie(name, value, attrs),\n * getHeader: (name) => req.headers[name],\n * setHeader: (name, value) => res.setHeader(name, value),\n * });\n * const locale = storage.getLocale();\n * storage.setLocale('fr');\n * ```\n */\nexport const LocaleStorageServer = (options: LocaleStorageServerOptions) => ({\n getLocale: () => getLocaleFromStorageServer(options),\n setLocale: (locale: LocalesValues) =>\n setLocaleInStorageServer(locale, options),\n});\n\n// ============================================================================\n// Deprecated: combined LocaleStorage\n// Use LocaleStorageClient or LocaleStorageServer instead\n// ============================================================================\n\n/**\n * @deprecated Use {@link LocaleStorageClientOptions} or {@link LocaleStorageServerOptions} instead.\n */\nexport type LocaleStorageOptions = LocaleStorageClientOptions &\n LocaleStorageServerOptions;\n\n/**\n * Retrieves the locale from all storage mechanisms\n * (cookies, localStorage, sessionStorage, headers).\n *\n * @deprecated Use {@link getLocaleFromStorageClient} (browser) or\n * {@link getLocaleFromStorageServer} (server) instead.\n */\nexport const getLocaleFromStorage = (\n options: Pick<\n LocaleStorageOptions,\n | 'getCookie'\n | 'getSessionStorage'\n | 'getLocaleStorage'\n | 'getHeader'\n | 'isCookieEnabled'\n >\n): Locale | undefined => {\n const { locales } = internationalization;\n\n if (options?.isCookieEnabled === false) return undefined;\n\n const isValidLocale = (value: string | null | undefined): value is Locale =>\n !!value && locales.includes(value as Locale);\n\n const readCookie = (name: string): string | undefined => {\n try {\n const fromOption = options?.getCookie?.(name);\n if (fromOption !== null && fromOption !== undefined) return fromOption;\n } catch {}\n // Browser fallback kept for backward compatibility\n return getCookie(name);\n };\n\n if (!TREE_SHAKE_STORAGE_COOKIES && routing.storage.cookies) {\n for (let i = 0; i < routing.storage.cookies.length; i++) {\n const value = readCookie(routing.storage.cookies[i].name);\n if (isValidLocale(value)) return value;\n }\n }\n\n if (!TREE_SHAKE_STORAGE_LOCAL_STORAGE && routing.storage.localStorage) {\n for (let i = 0; i < routing.storage.localStorage.length; i++) {\n try {\n const value = options?.getLocaleStorage?.(\n routing.storage.localStorage[i].name\n );\n if (isValidLocale(value)) return value;\n } catch {}\n }\n }\n\n if (!TREE_SHAKE_STORAGE_SESSION_STORAGE && routing.storage.sessionStorage) {\n for (let i = 0; i < routing.storage.sessionStorage.length; i++) {\n try {\n const value = options?.getSessionStorage?.(\n routing.storage.sessionStorage[i].name\n );\n if (isValidLocale(value)) return value;\n } catch {}\n }\n }\n\n if (!TREE_SHAKE_STORAGE_HEADERS && routing.storage.headers) {\n for (let i = 0; i < routing.storage.headers.length; i++) {\n try {\n const value = options?.getHeader?.(routing.storage.headers[i].name);\n if (isValidLocale(value)) return value;\n } catch {}\n }\n }\n};\n\n/**\n * Stores the locale in all configured storage mechanisms\n * (cookies, localStorage, sessionStorage, headers).\n *\n * @deprecated Use {@link setLocaleInStorageClient} (browser) or\n * {@link setLocaleInStorageServer} (server) instead.\n */\nexport const setLocaleInStorage = (\n locale: LocalesValues,\n options?: LocaleStorageOptions\n): void => {\n if (options?.isCookieEnabled === false) return;\n\n if (!TREE_SHAKE_STORAGE_COOKIES && routing.storage.cookies) {\n for (let i = 0; i < routing.storage.cookies.length; i++) {\n const { name, attributes } = routing.storage.cookies[i];\n try {\n if (options?.setCookieStore) {\n options.setCookieStore(name, locale, {\n ...attributes,\n expires: resolveExpiresToTimestamp(attributes.expires),\n });\n }\n } catch {\n try {\n if (options?.setCookieString) {\n options.setCookieString(\n name,\n buildCookieString(name, locale, attributes)\n );\n }\n } catch {}\n }\n }\n }\n\n if (\n !TREE_SHAKE_STORAGE_LOCAL_STORAGE &&\n routing.storage.localStorage &&\n options?.setLocaleStorage\n ) {\n for (let i = 0; i < routing.storage.localStorage.length; i++) {\n const { name } = routing.storage.localStorage[i];\n try {\n if (!(options?.overwrite ?? true) && options?.getLocaleStorage) {\n if (options.getLocaleStorage(name)) continue;\n }\n options.setLocaleStorage(name, locale);\n } catch {}\n }\n }\n\n if (\n !TREE_SHAKE_STORAGE_SESSION_STORAGE &&\n routing.storage.sessionStorage &&\n options?.setSessionStorage\n ) {\n for (let i = 0; i < routing.storage.sessionStorage.length; i++) {\n const { name } = routing.storage.sessionStorage[i];\n try {\n if (!(options?.overwrite ?? true) && options?.getSessionStorage) {\n if (options.getSessionStorage(name)) continue;\n }\n options.setSessionStorage(name, locale);\n } catch {}\n }\n }\n\n if (\n !TREE_SHAKE_STORAGE_HEADERS &&\n routing.storage.headers &&\n options?.setHeader\n ) {\n for (let i = 0; i < routing.storage.headers.length; i++) {\n try {\n options.setHeader(routing.storage.headers[i].name, locale);\n } catch {}\n }\n }\n};\n\n/**\n * Utility object to get and set the locale in storage based on configuration.\n *\n * @deprecated Use {@link LocaleStorageClient} (browser) or\n * {@link LocaleStorageServer} (server) instead.\n */\nexport const LocaleStorage = (options: LocaleStorageOptions) => ({\n getLocale: () => getLocaleFromStorage(options),\n setLocale: (locale: LocalesValues) => setLocaleInStorage(locale, options),\n});\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { COMPOSITE_ID_SEPARATOR, QUALIFIER_DYNAMIC_TYPES_KEY, QUALIFIER_ORDER, getDictionaryCompositeId, getDictionaryQualifierId, getDictionaryQualifierSegments, getDictionaryQualifierTypes, getDictionarySelectorCacheKey, isQualifiedDictionaryGroup, isQualifiedDynamicLoaderMap, parseDictionarySelector, reconstructQualifiedEntry, resolveQualifiedDictionary, resolveQualifiedDynamicContent, resolveQualifiedDynamicContentAsync } from "./qualifiedDictionary.mjs";
1
+ import { COMPOSITE_ID_SEPARATOR, QUALIFIER_DYNAMIC_TYPES_KEY, QUALIFIER_ORDER, getDictionaryCompositeId, getDictionaryQualifierId, getDictionaryQualifierSegments, getDictionaryQualifierTypes, getDictionarySelectorCacheKey, isQualifiedDictionaryGroup, isQualifiedDynamicLoaderMap, parseDictionarySelector, reconstructQualifiedEntry, resolveQualifiedDictionary, resolveQualifiedDynamicContent, resolveQualifiedDynamicContentAsync, serializeVariant } from "./qualifiedDictionary.mjs";
2
2
  import { editDictionaryByKeyPath } from "./editDictionaryByKeyPath.mjs";
3
3
  import { getContentNodeByKeyPath } from "./getContentNodeByKeyPath.mjs";
4
4
  import { getDefaultNode } from "./getDefaultNode.mjs";
@@ -13,4 +13,4 @@ import { removeContentNodeByKeyPath } from "./removeContentNodeByKeyPath.mjs";
13
13
  import { renameContentNodeByKeyPath } from "./renameContentNodeByKeyPath.mjs";
14
14
  import { updateNodeChildren } from "./updateNodeChildren.mjs";
15
15
 
16
- export { COMPOSITE_ID_SEPARATOR, QUALIFIER_DYNAMIC_TYPES_KEY, QUALIFIER_ORDER, editDictionaryByKeyPath, getContentNodeByKeyPath, getDefaultNode, getDictionaryCompositeId, getDictionaryQualifierId, getDictionaryQualifierSegments, getDictionaryQualifierTypes, getDictionarySelectorCacheKey, getEmptyNode, getNodeChildren, getNodeType, isQualifiedDictionaryGroup, isQualifiedDynamicLoaderMap, mergeDictionaries, mergeQualifiedDictionaries, normalizeDictionaries, normalizeDictionary, orderDictionaries, parseDictionarySelector, reconstructQualifiedEntry, removeContentNodeByKeyPath, renameContentNodeByKeyPath, resolveQualifiedDictionary, resolveQualifiedDynamicContent, resolveQualifiedDynamicContentAsync, updateNodeChildren };
16
+ export { COMPOSITE_ID_SEPARATOR, QUALIFIER_DYNAMIC_TYPES_KEY, QUALIFIER_ORDER, editDictionaryByKeyPath, getContentNodeByKeyPath, getDefaultNode, getDictionaryCompositeId, getDictionaryQualifierId, getDictionaryQualifierSegments, getDictionaryQualifierTypes, getDictionarySelectorCacheKey, getEmptyNode, getNodeChildren, getNodeType, isQualifiedDictionaryGroup, isQualifiedDynamicLoaderMap, mergeDictionaries, mergeQualifiedDictionaries, normalizeDictionaries, normalizeDictionary, orderDictionaries, parseDictionarySelector, reconstructQualifiedEntry, removeContentNodeByKeyPath, renameContentNodeByKeyPath, resolveQualifiedDictionary, resolveQualifiedDynamicContent, resolveQualifiedDynamicContentAsync, serializeVariant, updateNodeChildren };
@@ -11,7 +11,7 @@ import { colorizeKey, getAppLogger } from "@intlayer/config/logger";
11
11
  * `mergeDictionaries` (single merged dictionary).
12
12
  * - At least one dictionary declares a qualifier → the group's dimension set is
13
13
  * the union of every declared dimension (in canonical order
14
- * `variant → meta → item`). Dictionaries are grouped by their composite id
14
+ * `variant → item`). Dictionaries are grouped by their composite id
15
15
  * (one segment per dimension), merged within each group (locale completion /
16
16
  * priority overrides preserved), and a `QualifiedDictionaryGroup` is returned.
17
17
  * Unqualified siblings act as shared base content merged into every entry.
@@ -43,13 +43,10 @@ const mergeQualifiedDictionaries = (dictionaries) => {
43
43
  entriesDictionaries.set(compositeId, existingEntries);
44
44
  });
45
45
  const content = {};
46
- const metaByCompositeId = {};
47
- const declaresMeta = groupQualifierTypes.includes("meta");
48
46
  let importMode;
49
47
  for (const [compositeId, qualifiedDictionaries] of entriesDictionaries) {
50
48
  content[compositeId] = mergeDictionaries([...qualifiedDictionaries, ...baseDictionaries]).content;
51
49
  const [firstQualified] = qualifiedDictionaries;
52
- if (declaresMeta && firstQualified?.meta !== void 0) metaByCompositeId[compositeId] = firstQualified.meta;
53
50
  importMode ??= firstQualified?.importMode;
54
51
  }
55
52
  const localIds = Array.from(new Set(dictionaries.filter((dictionary) => dictionary.localId).map((dictionary) => dictionary.localId)));
@@ -57,7 +54,6 @@ const mergeQualifiedDictionaries = (dictionaries) => {
57
54
  key: dictionaries[0].key,
58
55
  qualifierTypes: groupQualifierTypes,
59
56
  content,
60
- ...declaresMeta && { meta: metaByCompositeId },
61
57
  ...importMode !== void 0 && { importMode },
62
58
  localIds
63
59
  };
@@ -1 +1 @@
1
- {"version":3,"file":"mergeQualifiedDictionaries.mjs","names":[],"sources":["../../../src/dictionaryManipulator/mergeQualifiedDictionaries.ts"],"sourcesContent":["import { log } from '@intlayer/config/built';\nimport { colorizeKey, getAppLogger } from '@intlayer/config/logger';\nimport type {\n Dictionary,\n DictionaryMeta,\n DictionaryQualifierType,\n QualifiedDictionaryGroup,\n} from '@intlayer/types/dictionary';\nimport { mergeDictionaries } from './mergeDictionaries';\nimport {\n getDictionaryCompositeId,\n getDictionaryQualifierTypes,\n QUALIFIER_ORDER,\n} from './qualifiedDictionary';\n\n/**\n * Merges sibling dictionaries sharing the same key, honouring qualifiers.\n *\n * - No dictionary declares a qualifier → behaves exactly like\n * `mergeDictionaries` (single merged dictionary).\n * - At least one dictionary declares a qualifier → the group's dimension set is\n * the union of every declared dimension (in canonical order\n * `variant → meta → item`). Dictionaries are grouped by their composite id\n * (one segment per dimension), merged within each group (locale completion /\n * priority overrides preserved), and a `QualifiedDictionaryGroup` is returned.\n * Unqualified siblings act as shared base content merged into every entry.\n *\n * Every qualified entry must declare ALL dimensions of the group; an entry that\n * declares only a subset is ambiguous and is rejected with an error log.\n */\nexport const mergeQualifiedDictionaries = (\n dictionaries: Dictionary[]\n): Dictionary | QualifiedDictionaryGroup => {\n const perDictionaryTypes = dictionaries.map(getDictionaryQualifierTypes);\n\n const declaredDimensions = new Set<DictionaryQualifierType>();\n for (const types of perDictionaryTypes) {\n for (const type of types) declaredDimensions.add(type);\n }\n\n // Canonical order, restricted to the dimensions actually declared.\n const groupQualifierTypes = QUALIFIER_ORDER.filter((qualifierType) =>\n declaredDimensions.has(qualifierType)\n );\n\n if (groupQualifierTypes.length === 0) {\n return mergeDictionaries(dictionaries);\n }\n\n const appLogger = getAppLogger({ log });\n\n const baseDictionaries: Dictionary[] = [];\n const entriesDictionaries = new Map<string, Dictionary[]>();\n\n dictionaries.forEach((dictionary, index) => {\n if (perDictionaryTypes[index].length === 0) {\n baseDictionaries.push(dictionary);\n return;\n }\n\n const compositeId = getDictionaryCompositeId(\n dictionary,\n groupQualifierTypes\n );\n\n if (compositeId === undefined) {\n appLogger(\n `Dictionary ${colorizeKey(dictionary.key)} declares (${perDictionaryTypes[index].join(', ')}) but the key's dimensions are (${groupQualifierTypes.join(', ')}); every entry must declare all of them. Entry ignored${dictionary.filePath ? ` - ${dictionary.filePath}` : ''}.`,\n { level: 'error' }\n );\n return;\n }\n\n const existingEntries = entriesDictionaries.get(compositeId) ?? [];\n existingEntries.push(dictionary);\n entriesDictionaries.set(compositeId, existingEntries);\n });\n\n // `content` maps each composite id to its resolved content node directly; the\n // qualifier coordinates live in the key, not in a per-entry wrapper. `meta`\n // preserves the declared meta fields (composite id only encodes `meta.id`).\n const content: Record<string, unknown> = {};\n const metaByCompositeId: Record<string, DictionaryMeta> = {};\n const declaresMeta = groupQualifierTypes.includes('meta');\n\n let importMode: Dictionary['importMode'];\n\n for (const [compositeId, qualifiedDictionaries] of entriesDictionaries) {\n // Unqualified siblings act as shared base content: appended last so the\n // qualified entries take precedence (mergeDictionaries prefers first).\n const mergedEntry = mergeDictionaries([\n ...qualifiedDictionaries,\n ...baseDictionaries,\n ]);\n\n content[compositeId] = mergedEntry.content;\n\n const [firstQualified] = qualifiedDictionaries;\n\n if (declaresMeta && firstQualified?.meta !== undefined) {\n metaByCompositeId[compositeId] = firstQualified.meta;\n }\n\n importMode ??= firstQualified?.importMode;\n }\n\n const localIds = Array.from(\n new Set(\n dictionaries\n .filter((dictionary) => dictionary.localId)\n .map((dictionary) => dictionary.localId!)\n )\n );\n\n return {\n key: dictionaries[0]!.key,\n qualifierTypes: groupQualifierTypes,\n content,\n ...(declaresMeta && { meta: metaByCompositeId }),\n ...(importMode !== undefined && { importMode }),\n localIds,\n };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA8BA,MAAa,8BACX,iBAC0C;CAC1C,MAAM,qBAAqB,aAAa,IAAI,4BAA4B;CAExE,MAAM,qCAAqB,IAAI,KAA8B;AAC7D,MAAK,MAAM,SAAS,mBAClB,MAAK,MAAM,QAAQ,MAAO,oBAAmB,IAAI,KAAK;CAIxD,MAAM,sBAAsB,gBAAgB,QAAQ,kBAClD,mBAAmB,IAAI,cAAc,CACtC;AAED,KAAI,oBAAoB,WAAW,EACjC,QAAO,kBAAkB,aAAa;CAGxC,MAAM,YAAY,aAAa,EAAE,KAAK,CAAC;CAEvC,MAAM,mBAAiC,EAAE;CACzC,MAAM,sCAAsB,IAAI,KAA2B;AAE3D,cAAa,SAAS,YAAY,UAAU;AAC1C,MAAI,mBAAmB,OAAO,WAAW,GAAG;AAC1C,oBAAiB,KAAK,WAAW;AACjC;;EAGF,MAAM,cAAc,yBAClB,YACA,oBACD;AAED,MAAI,gBAAgB,QAAW;AAC7B,aACE,cAAc,YAAY,WAAW,IAAI,CAAC,aAAa,mBAAmB,OAAO,KAAK,KAAK,CAAC,kCAAkC,oBAAoB,KAAK,KAAK,CAAC,wDAAwD,WAAW,WAAW,MAAM,WAAW,aAAa,GAAG,IAC5Q,EAAE,OAAO,SAAS,CACnB;AACD;;EAGF,MAAM,kBAAkB,oBAAoB,IAAI,YAAY,IAAI,EAAE;AAClE,kBAAgB,KAAK,WAAW;AAChC,sBAAoB,IAAI,aAAa,gBAAgB;GACrD;CAKF,MAAM,UAAmC,EAAE;CAC3C,MAAM,oBAAoD,EAAE;CAC5D,MAAM,eAAe,oBAAoB,SAAS,OAAO;CAEzD,IAAI;AAEJ,MAAK,MAAM,CAAC,aAAa,0BAA0B,qBAAqB;AAQtE,UAAQ,eALY,kBAAkB,CACpC,GAAG,uBACH,GAAG,iBACJ,CAEiC,CAAC;EAEnC,MAAM,CAAC,kBAAkB;AAEzB,MAAI,gBAAgB,gBAAgB,SAAS,OAC3C,mBAAkB,eAAe,eAAe;AAGlD,iBAAe,gBAAgB;;CAGjC,MAAM,WAAW,MAAM,KACrB,IAAI,IACF,aACG,QAAQ,eAAe,WAAW,QAAQ,CAC1C,KAAK,eAAe,WAAW,QAAS,CAC5C,CACF;AAED,QAAO;EACL,KAAK,aAAa,GAAI;EACtB,gBAAgB;EAChB;EACA,GAAI,gBAAgB,EAAE,MAAM,mBAAmB;EAC/C,GAAI,eAAe,UAAa,EAAE,YAAY;EAC9C;EACD"}
1
+ {"version":3,"file":"mergeQualifiedDictionaries.mjs","names":[],"sources":["../../../src/dictionaryManipulator/mergeQualifiedDictionaries.ts"],"sourcesContent":["import { log } from '@intlayer/config/built';\nimport { colorizeKey, getAppLogger } from '@intlayer/config/logger';\nimport type {\n Dictionary,\n DictionaryQualifierType,\n QualifiedDictionaryGroup,\n} from '@intlayer/types/dictionary';\nimport { mergeDictionaries } from './mergeDictionaries';\nimport {\n getDictionaryCompositeId,\n getDictionaryQualifierTypes,\n QUALIFIER_ORDER,\n} from './qualifiedDictionary';\n\n/**\n * Merges sibling dictionaries sharing the same key, honouring qualifiers.\n *\n * - No dictionary declares a qualifier → behaves exactly like\n * `mergeDictionaries` (single merged dictionary).\n * - At least one dictionary declares a qualifier → the group's dimension set is\n * the union of every declared dimension (in canonical order\n * `variant → item`). Dictionaries are grouped by their composite id\n * (one segment per dimension), merged within each group (locale completion /\n * priority overrides preserved), and a `QualifiedDictionaryGroup` is returned.\n * Unqualified siblings act as shared base content merged into every entry.\n *\n * Every qualified entry must declare ALL dimensions of the group; an entry that\n * declares only a subset is ambiguous and is rejected with an error log.\n */\nexport const mergeQualifiedDictionaries = (\n dictionaries: Dictionary[]\n): Dictionary | QualifiedDictionaryGroup => {\n const perDictionaryTypes = dictionaries.map(getDictionaryQualifierTypes);\n\n const declaredDimensions = new Set<DictionaryQualifierType>();\n for (const types of perDictionaryTypes) {\n for (const type of types) declaredDimensions.add(type);\n }\n\n // Canonical order, restricted to the dimensions actually declared.\n const groupQualifierTypes = QUALIFIER_ORDER.filter((qualifierType) =>\n declaredDimensions.has(qualifierType)\n );\n\n if (groupQualifierTypes.length === 0) {\n return mergeDictionaries(dictionaries);\n }\n\n const appLogger = getAppLogger({ log });\n\n const baseDictionaries: Dictionary[] = [];\n const entriesDictionaries = new Map<string, Dictionary[]>();\n\n dictionaries.forEach((dictionary, index) => {\n if (perDictionaryTypes[index].length === 0) {\n baseDictionaries.push(dictionary);\n return;\n }\n\n const compositeId = getDictionaryCompositeId(\n dictionary,\n groupQualifierTypes\n );\n\n if (compositeId === undefined) {\n appLogger(\n `Dictionary ${colorizeKey(dictionary.key)} declares (${perDictionaryTypes[index].join(', ')}) but the key's dimensions are (${groupQualifierTypes.join(', ')}); every entry must declare all of them. Entry ignored${dictionary.filePath ? ` - ${dictionary.filePath}` : ''}.`,\n { level: 'error' }\n );\n return;\n }\n\n const existingEntries = entriesDictionaries.get(compositeId) ?? [];\n existingEntries.push(dictionary);\n entriesDictionaries.set(compositeId, existingEntries);\n });\n\n // `content` maps each composite id to its resolved content node directly; the\n // qualifier coordinates live in the key, not in a per-entry wrapper. For an\n // object variant the variant segment is the canonical serialization of the\n // object, so it fully identifies the entry — no side-map is needed.\n const content: Record<string, unknown> = {};\n\n let importMode: Dictionary['importMode'];\n\n for (const [compositeId, qualifiedDictionaries] of entriesDictionaries) {\n // Unqualified siblings act as shared base content: appended last so the\n // qualified entries take precedence (mergeDictionaries prefers first).\n const mergedEntry = mergeDictionaries([\n ...qualifiedDictionaries,\n ...baseDictionaries,\n ]);\n\n content[compositeId] = mergedEntry.content;\n\n const [firstQualified] = qualifiedDictionaries;\n\n importMode ??= firstQualified?.importMode;\n }\n\n const localIds = Array.from(\n new Set(\n dictionaries\n .filter((dictionary) => dictionary.localId)\n .map((dictionary) => dictionary.localId!)\n )\n );\n\n return {\n key: dictionaries[0]!.key,\n qualifierTypes: groupQualifierTypes,\n content,\n ...(importMode !== undefined && { importMode }),\n localIds,\n };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA6BA,MAAa,8BACX,iBAC0C;CAC1C,MAAM,qBAAqB,aAAa,IAAI,4BAA4B;CAExE,MAAM,qCAAqB,IAAI,KAA8B;AAC7D,MAAK,MAAM,SAAS,mBAClB,MAAK,MAAM,QAAQ,MAAO,oBAAmB,IAAI,KAAK;CAIxD,MAAM,sBAAsB,gBAAgB,QAAQ,kBAClD,mBAAmB,IAAI,cAAc,CACtC;AAED,KAAI,oBAAoB,WAAW,EACjC,QAAO,kBAAkB,aAAa;CAGxC,MAAM,YAAY,aAAa,EAAE,KAAK,CAAC;CAEvC,MAAM,mBAAiC,EAAE;CACzC,MAAM,sCAAsB,IAAI,KAA2B;AAE3D,cAAa,SAAS,YAAY,UAAU;AAC1C,MAAI,mBAAmB,OAAO,WAAW,GAAG;AAC1C,oBAAiB,KAAK,WAAW;AACjC;;EAGF,MAAM,cAAc,yBAClB,YACA,oBACD;AAED,MAAI,gBAAgB,QAAW;AAC7B,aACE,cAAc,YAAY,WAAW,IAAI,CAAC,aAAa,mBAAmB,OAAO,KAAK,KAAK,CAAC,kCAAkC,oBAAoB,KAAK,KAAK,CAAC,wDAAwD,WAAW,WAAW,MAAM,WAAW,aAAa,GAAG,IAC5Q,EAAE,OAAO,SAAS,CACnB;AACD;;EAGF,MAAM,kBAAkB,oBAAoB,IAAI,YAAY,IAAI,EAAE;AAClE,kBAAgB,KAAK,WAAW;AAChC,sBAAoB,IAAI,aAAa,gBAAgB;GACrD;CAMF,MAAM,UAAmC,EAAE;CAE3C,IAAI;AAEJ,MAAK,MAAM,CAAC,aAAa,0BAA0B,qBAAqB;AAQtE,UAAQ,eALY,kBAAkB,CACpC,GAAG,uBACH,GAAG,iBACJ,CAEiC,CAAC;EAEnC,MAAM,CAAC,kBAAkB;AAEzB,iBAAe,gBAAgB;;CAGjC,MAAM,WAAW,MAAM,KACrB,IAAI,IACF,aACG,QAAQ,eAAe,WAAW,QAAQ,CAC1C,KAAK,eAAe,WAAW,QAAS,CAC5C,CACF;AAED,QAAO;EACL,KAAK,aAAa,GAAI;EACtB,gBAAgB;EAChB;EACA,GAAI,eAAe,UAAa,EAAE,YAAY;EAC9C;EACD"}
@@ -1,37 +1,40 @@
1
1
  //#region src/dictionaryManipulator/qualifiedDictionary.ts
2
2
  /**
3
- * Selector keys that are reserved for dictionary resolution and therefore
4
- * excluded from meta field matching.
3
+ * Canonical order of qualifier dimensions. A key that declares both dimensions
4
+ * always nests them in this order, with `item` innermost so it can act as the
5
+ * collection (array) axis.
5
6
  */
6
- const RESERVED_SELECTOR_KEYS = [
7
- "locale",
8
- "item",
9
- "variant"
10
- ];
11
- /**
12
- * Canonical order of qualifier dimensions. A key that declares several
13
- * dimensions always nests them in this order, with `item` innermost so it can
14
- * act as the collection (array) axis.
15
- */
16
- const QUALIFIER_ORDER = [
17
- "variant",
18
- "meta",
19
- "item"
20
- ];
7
+ const QUALIFIER_ORDER = ["variant", "item"];
21
8
  /**
22
9
  * Separator joining per-dimension ids into a composite entry id. Also used as
23
10
  * the chunk path separator in dynamic mode.
24
11
  */
25
12
  const COMPOSITE_ID_SEPARATOR = "/";
26
13
  /**
14
+ * Canonical serialization of a variant value into its identity string — the
15
+ * variant segment of a composite id and the runtime matching key.
16
+ *
17
+ * - `undefined` → `'default'` (the implicit fallback variant)
18
+ * - a string → the string itself (a named variant)
19
+ * - an object → its sorted `key=value` pairs joined by `&`
20
+ * (e.g. `{ userId: '123', id: 'abc' }` → `'id=abc&userId=123'`)
21
+ *
22
+ * Two variants resolve to the same entry iff their serializations are equal, so
23
+ * an object variant in a selector must equal the one declared on the dictionary.
24
+ */
25
+ const serializeVariant = (variant) => {
26
+ if (variant === void 0) return "default";
27
+ if (typeof variant === "string") return variant;
28
+ return Object.keys(variant).sort().map((field) => `${field}=${variant[field]}`).join("&");
29
+ };
30
+ /**
27
31
  * Returns the qualifier dimensions declared on a dictionary, in canonical
28
- * order (`variant → meta → item`). Empty when the dictionary is unqualified
32
+ * order (`variant → item`). Empty when the dictionary is unqualified
29
33
  * (plain dictionary or shared base content of a qualified group).
30
34
  */
31
35
  const getDictionaryQualifierTypes = (dictionary) => {
32
36
  const declaredQualifiers = [];
33
- if (typeof dictionary.variant === "string") declaredQualifiers.push("variant");
34
- if (dictionary.meta !== void 0) declaredQualifiers.push("meta");
37
+ if (dictionary.variant !== void 0) declaredQualifiers.push("variant");
35
38
  if (typeof dictionary.item === "number") declaredQualifiers.push("item");
36
39
  return declaredQualifiers;
37
40
  };
@@ -39,16 +42,11 @@ const getDictionaryQualifierTypes = (dictionary) => {
39
42
  * Returns the qualifier identifier of a dictionary for the given qualifier
40
43
  * dimension — one segment of the composite entry id.
41
44
  *
42
- * - 'variant' → the variant name
43
- * - 'meta' → the `meta.id` discriminator
45
+ * - 'variant' → the serialized variant (named string or object identity)
44
46
  * - 'item' → the item index as string
45
47
  */
46
48
  const getDictionaryQualifierId = (dictionary, qualifierType) => {
47
- if (qualifierType === "variant") return dictionary.variant;
48
- if (qualifierType === "meta") {
49
- const metaId = dictionary.meta?.id;
50
- return metaId === void 0 ? void 0 : String(metaId);
51
- }
49
+ if (qualifierType === "variant") return dictionary.variant === void 0 ? void 0 : serializeVariant(dictionary.variant);
52
50
  return dictionary.item === void 0 ? void 0 : String(dictionary.item);
53
51
  };
54
52
  /**
@@ -71,27 +69,13 @@ const getDictionaryQualifierSegments = (dictionary, qualifierTypes) => {
71
69
  */
72
70
  const getDictionaryCompositeId = (dictionary, qualifierTypes) => getDictionaryQualifierSegments(dictionary, qualifierTypes)?.join("/");
73
71
  /**
74
- * Checks that every declared meta field is provided and equal in the selector.
75
- * Reserved keys (`locale`, `item`, `variant`) are skipped; `meta.id` is part of
76
- * the equality check.
77
- */
78
- const metaFieldsMatch = (meta, selector) => {
79
- if (!meta) return false;
80
- return Object.entries(meta).every(([metaField, declaredValue]) => {
81
- if (RESERVED_SELECTOR_KEYS.includes(metaField)) return true;
82
- const providedValue = selector?.[metaField];
83
- return providedValue !== void 0 && String(providedValue) === String(declaredValue);
84
- });
85
- };
86
- /**
87
72
  * Tests whether a group entry matches a selector across every declared
88
73
  * dimension. The `item` dimension matches any value when the selector does not
89
74
  * provide one (open collection axis).
90
75
  */
91
76
  const entryMatchesSelector = (entry, qualifierTypes, selector) => qualifierTypes.every((qualifierType) => {
92
- if (qualifierType === "variant") return entry.variant === (selector?.variant ?? "default");
93
- if (qualifierType === "item") return selector?.item === void 0 || String(entry.item) === String(selector.item);
94
- return metaFieldsMatch(entry.meta, selector);
77
+ if (qualifierType === "variant") return serializeVariant(entry.variant) === serializeVariant(selector?.variant);
78
+ return selector?.item === void 0 || String(entry.item) === String(selector.item);
95
79
  });
96
80
  /**
97
81
  * Type guard discriminating a `QualifiedDictionaryGroup` (merge output of a
@@ -102,12 +86,13 @@ const isQualifiedDictionaryGroup = (value) => typeof value === "object" && value
102
86
  /**
103
87
  * Reconstructs a resolvable {@link Dictionary} from a single entry of a
104
88
  * qualified group: the content node stored under its composite id, plus the
105
- * qualifier coordinates decoded from that id (`variant`, `item`) and the
106
- * preserved `meta` object for the meta dimension.
89
+ * qualifier coordinates decoded from that id (`variant`, `item`).
107
90
  *
108
91
  * This keeps the resolver's matching/transform code unchanged: it still sees a
109
- * `{ key, content, variant?, item?, meta? }` shape, even though the stored
110
- * format no longer duplicates those fields per entry.
92
+ * `{ key, content, variant?, item? }` shape, even though the stored format no
93
+ * longer duplicates those fields per entry. The `variant` coordinate stays in
94
+ * its serialized form (e.g. `'id=abc&userId=123'`), which round-trips through
95
+ * {@link serializeVariant} during matching.
111
96
  */
112
97
  const reconstructQualifiedEntry = (group, compositeId) => {
113
98
  const segments = compositeId.split("/");
@@ -119,10 +104,6 @@ const reconstructQualifiedEntry = (group, compositeId) => {
119
104
  if (qualifierType === "variant") entry.variant = segments[index];
120
105
  else if (qualifierType === "item") entry.item = Number(segments[index]);
121
106
  });
122
- if (group.qualifierTypes.includes("meta")) {
123
- const metaIndex = group.qualifierTypes.indexOf("meta");
124
- entry.meta = group.meta?.[compositeId] ?? { id: segments[metaIndex] };
125
- }
126
107
  return entry;
127
108
  };
128
109
  /**
@@ -132,8 +113,8 @@ const reconstructQualifiedEntry = (group, compositeId) => {
132
113
  * - Plain dictionary → returned as-is (selector ignored)
133
114
  * - `item` declared but not selected → every matching entry ordered by index
134
115
  * - `item` selected → the matching entry or null
135
- * - `variant` defaults to the `default` entry when not selected
136
- * - `meta` requires `{ id }` and every declared meta field to match
116
+ * - `variant` defaults to the `default` entry when not selected; an object
117
+ * variant resolves only when the selector provides an equal object
137
118
  *
138
119
  * Dimensions compose: e.g. a variant × item key with `{ variant: 'promo' }`
139
120
  * returns every promo item as an array; adding `{ item: 2 }` narrows to one.
@@ -141,7 +122,6 @@ const reconstructQualifiedEntry = (group, compositeId) => {
141
122
  const resolveQualifiedDictionary = (dictionaryOrGroup, selector) => {
142
123
  if (!isQualifiedDictionaryGroup(dictionaryOrGroup)) return dictionaryOrGroup;
143
124
  const { qualifierTypes, content } = dictionaryOrGroup;
144
- if (qualifierTypes.includes("meta") && selector?.id === void 0) return null;
145
125
  const itemAxisOpen = qualifierTypes.includes("item") && selector?.item === void 0;
146
126
  const matchedEntries = Object.keys(content).map((compositeId) => reconstructQualifiedEntry(dictionaryOrGroup, compositeId)).filter((entry) => entryMatchesSelector(entry, qualifierTypes, selector));
147
127
  if (itemAxisOpen) return matchedEntries.sort((left, right) => (left.item ?? 0) - (right.item ?? 0));
@@ -164,7 +144,10 @@ const parseDictionarySelector = (localeOrSelector) => {
164
144
  */
165
145
  const getDictionarySelectorCacheKey = (selector) => {
166
146
  if (!selector) return "";
167
- return Object.keys(selector).filter((selectorKey) => selectorKey !== "locale").sort().map((selectorKey) => `${selectorKey}:${String(selector[selectorKey])}`).join("|");
147
+ return Object.keys(selector).filter((selectorKey) => selectorKey !== "locale").sort().map((selectorKey) => {
148
+ const value = selector[selectorKey];
149
+ return `${selectorKey}:${selectorKey === "variant" ? serializeVariant(value) : String(value)}`;
150
+ }).join("|");
168
151
  };
169
152
  /**
170
153
  * Marker property carrying the ordered qualifier dimensions on a dynamic loader
@@ -175,7 +158,7 @@ const getDictionarySelectorCacheKey = (selector) => {
175
158
  const QUALIFIER_DYNAMIC_TYPES_KEY = "__intlayerQualifierTypes";
176
159
  /**
177
160
  * Type guard discriminating a qualified dynamic loader map (collections /
178
- * variants / meta records, possibly combined) from a plain dynamic loader map.
161
+ * variants, possibly combined) from a plain dynamic loader map.
179
162
  */
180
163
  const isQualifiedDynamicLoaderMap = (value) => typeof value === "object" && value !== null && "__intlayerQualifierTypes" in value;
181
164
  /**
@@ -192,11 +175,6 @@ const collectQualifiedChunks = (loaderMap, key, locale, selector) => {
192
175
  missed: true,
193
176
  chunks: []
194
177
  };
195
- if (qualifierTypes.includes("meta") && selector?.id === void 0) return {
196
- itemAxisOpen,
197
- missed: true,
198
- chunks: []
199
- };
200
178
  const chunks = [];
201
179
  const walk = (node, dimensions, segments) => {
202
180
  if (dimensions.length === 0) {
@@ -212,7 +190,7 @@ const collectQualifiedChunks = (loaderMap, key, locale, selector) => {
212
190
  for (const segment of Object.keys(tree).sort((left, right) => Number(left) - Number(right))) walk(tree[segment], rest, [...segments, segment]);
213
191
  return true;
214
192
  }
215
- const segment = dimension === "variant" ? selector?.variant ?? "default" : dimension === "meta" ? String(selector?.id) : String(selector?.item);
193
+ const segment = dimension === "variant" ? serializeVariant(selector?.variant) : String(selector?.item);
216
194
  const child = tree[segment];
217
195
  if (!child) return false;
218
196
  return walk(child, rest, [...segments, segment]);
@@ -224,20 +202,15 @@ const collectQualifiedChunks = (loaderMap, key, locale, selector) => {
224
202
  };
225
203
  };
226
204
  /**
227
- * Whether a loaded chunk satisfies the selector's meta fields (no-op unless the
228
- * key declares a `meta` dimension).
229
- */
230
- const chunkMatchesMeta = (loaderMap, dictionary, selector) => !loaderMap["__intlayerQualifierTypes"].includes("meta") || metaFieldsMatch(dictionary.meta, selector);
231
- /**
232
205
  * Resolves the content of a qualified dynamic loader map against a selector,
233
206
  * loading only the chunk(s) the selector actually targets.
234
207
  *
235
208
  * Walks the nested loader tree one dimension at a time (canonical order
236
- * `variant → meta → item`): `variant` defaults to `default`, `meta` descends by
237
- * `id`, and `item` either narrows to the selected index or — when no item is
238
- * given — expands into every sibling chunk (the collection axis). Meta-equality
239
- * is verified on the loaded chunk. Semantics mirror
240
- * {@link resolveQualifiedDictionary} so dynamic and static modes behave alike.
209
+ * `variant → item`): `variant` defaults to `default` (or descends by the
210
+ * serialized object identity), and `item` either narrows to the selected index
211
+ * or — when no item is given — expands into every sibling chunk (the collection
212
+ * axis). Semantics mirror {@link resolveQualifiedDictionary} so dynamic and
213
+ * static modes behave alike.
241
214
  *
242
215
  * The Suspense mechanism is injected through `loadChunk` so the same logic
243
216
  * serves both the client (suspender cache) and the server (`react.use`). Every
@@ -255,7 +228,7 @@ const resolveQualifiedDynamicContent = (params) => {
255
228
  const { loaderMap, key, locale, selector, loadChunk, transform } = params;
256
229
  const { itemAxisOpen, missed, chunks } = collectQualifiedChunks(loaderMap, key, locale, selector);
257
230
  if (missed) return itemAxisOpen ? [] : null;
258
- const dictionaries = chunks.map(({ cacheKey, loader }) => loadChunk(cacheKey, loader())).filter((dictionary) => chunkMatchesMeta(loaderMap, dictionary, selector));
231
+ const dictionaries = chunks.map(({ cacheKey, loader }) => loadChunk(cacheKey, loader()));
259
232
  if (itemAxisOpen) return dictionaries.map(transform);
260
233
  const [dictionary] = dictionaries;
261
234
  return dictionary ? transform(dictionary) : null;
@@ -275,12 +248,12 @@ const resolveQualifiedDynamicContentAsync = async (params) => {
275
248
  const { loaderMap, key, locale, selector, transform } = params;
276
249
  const { itemAxisOpen, missed, chunks } = collectQualifiedChunks(loaderMap, key, locale, selector);
277
250
  if (missed) return itemAxisOpen ? [] : null;
278
- const dictionaries = (await Promise.all(chunks.map(({ loader }) => loader()))).filter((dictionary) => chunkMatchesMeta(loaderMap, dictionary, selector));
251
+ const dictionaries = await Promise.all(chunks.map(({ loader }) => loader()));
279
252
  if (itemAxisOpen) return dictionaries.map(transform);
280
253
  const [dictionary] = dictionaries;
281
254
  return dictionary ? transform(dictionary) : null;
282
255
  };
283
256
 
284
257
  //#endregion
285
- export { COMPOSITE_ID_SEPARATOR, QUALIFIER_DYNAMIC_TYPES_KEY, QUALIFIER_ORDER, getDictionaryCompositeId, getDictionaryQualifierId, getDictionaryQualifierSegments, getDictionaryQualifierTypes, getDictionarySelectorCacheKey, isQualifiedDictionaryGroup, isQualifiedDynamicLoaderMap, parseDictionarySelector, reconstructQualifiedEntry, resolveQualifiedDictionary, resolveQualifiedDynamicContent, resolveQualifiedDynamicContentAsync };
258
+ export { COMPOSITE_ID_SEPARATOR, QUALIFIER_DYNAMIC_TYPES_KEY, QUALIFIER_ORDER, getDictionaryCompositeId, getDictionaryQualifierId, getDictionaryQualifierSegments, getDictionaryQualifierTypes, getDictionarySelectorCacheKey, isQualifiedDictionaryGroup, isQualifiedDynamicLoaderMap, parseDictionarySelector, reconstructQualifiedEntry, resolveQualifiedDictionary, resolveQualifiedDynamicContent, resolveQualifiedDynamicContentAsync, serializeVariant };
286
259
  //# sourceMappingURL=qualifiedDictionary.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"qualifiedDictionary.mjs","names":[],"sources":["../../../src/dictionaryManipulator/qualifiedDictionary.ts"],"sourcesContent":["import type {\n Dictionary,\n DictionaryQualifierType,\n DictionarySelector,\n QualifiedDictionaryGroup,\n} from '@intlayer/types/dictionary';\nimport type { LocalesValues } from '@intlayer/types/module_augmentation';\n\n/**\n * Selector keys that are reserved for dictionary resolution and therefore\n * excluded from meta field matching.\n */\nconst RESERVED_SELECTOR_KEYS = ['locale', 'item', 'variant'] as const;\n\n/**\n * Canonical order of qualifier dimensions. A key that declares several\n * dimensions always nests them in this order, with `item` innermost so it can\n * act as the collection (array) axis.\n */\nexport const QUALIFIER_ORDER = [\n 'variant',\n 'meta',\n 'item',\n] as const satisfies readonly DictionaryQualifierType[];\n\n/**\n * Separator joining per-dimension ids into a composite entry id. Also used as\n * the chunk path separator in dynamic mode.\n */\nexport const COMPOSITE_ID_SEPARATOR = '/';\n\n/**\n * Returns the qualifier dimensions declared on a dictionary, in canonical\n * order (`variant → meta → item`). Empty when the dictionary is unqualified\n * (plain dictionary or shared base content of a qualified group).\n */\nexport const getDictionaryQualifierTypes = (\n dictionary: Dictionary\n): DictionaryQualifierType[] => {\n const declaredQualifiers: DictionaryQualifierType[] = [];\n\n if (typeof dictionary.variant === 'string')\n declaredQualifiers.push('variant');\n if (dictionary.meta !== undefined) declaredQualifiers.push('meta');\n if (typeof dictionary.item === 'number') declaredQualifiers.push('item');\n\n return declaredQualifiers;\n};\n\n/**\n * Returns the qualifier identifier of a dictionary for the given qualifier\n * dimension — one segment of the composite entry id.\n *\n * - 'variant' → the variant name\n * - 'meta' → the `meta.id` discriminator\n * - 'item' → the item index as string\n */\nexport const getDictionaryQualifierId = (\n dictionary: Dictionary,\n qualifierType: DictionaryQualifierType\n): string | undefined => {\n if (qualifierType === 'variant') return dictionary.variant;\n if (qualifierType === 'meta') {\n const metaId = dictionary.meta?.id;\n return metaId === undefined ? undefined : String(metaId);\n }\n return dictionary.item === undefined ? undefined : String(dictionary.item);\n};\n\n/**\n * Returns the per-dimension id segments of a dictionary for the given ordered\n * dimension set, or `undefined` when the dictionary does not declare every\n * dimension of the set.\n */\nexport const getDictionaryQualifierSegments = (\n dictionary: Dictionary,\n qualifierTypes: DictionaryQualifierType[]\n): string[] | undefined => {\n const segments: string[] = [];\n\n for (const qualifierType of qualifierTypes) {\n const id = getDictionaryQualifierId(dictionary, qualifierType);\n if (id === undefined) return undefined;\n segments.push(id);\n }\n\n return segments;\n};\n\n/**\n * Builds the composite entry id of a dictionary — its per-dimension id segments\n * joined in canonical order. `undefined` when a dimension is missing.\n */\nexport const getDictionaryCompositeId = (\n dictionary: Dictionary,\n qualifierTypes: DictionaryQualifierType[]\n): string | undefined =>\n getDictionaryQualifierSegments(dictionary, qualifierTypes)?.join(\n COMPOSITE_ID_SEPARATOR\n );\n\n/**\n * Checks that every declared meta field is provided and equal in the selector.\n * Reserved keys (`locale`, `item`, `variant`) are skipped; `meta.id` is part of\n * the equality check.\n */\nconst metaFieldsMatch = (\n meta: Dictionary['meta'] | undefined,\n selector: DictionarySelector | undefined\n): boolean => {\n if (!meta) return false;\n\n return Object.entries(meta).every(([metaField, declaredValue]) => {\n if ((RESERVED_SELECTOR_KEYS as readonly string[]).includes(metaField)) {\n return true;\n }\n\n const providedValue = selector?.[metaField];\n\n return (\n providedValue !== undefined &&\n String(providedValue) === String(declaredValue)\n );\n });\n};\n\n/**\n * Tests whether a group entry matches a selector across every declared\n * dimension. The `item` dimension matches any value when the selector does not\n * provide one (open collection axis).\n */\nconst entryMatchesSelector = (\n entry: Dictionary,\n qualifierTypes: DictionaryQualifierType[],\n selector: DictionarySelector | undefined\n): boolean =>\n qualifierTypes.every((qualifierType) => {\n if (qualifierType === 'variant') {\n return entry.variant === (selector?.variant ?? 'default');\n }\n\n if (qualifierType === 'item') {\n return (\n selector?.item === undefined ||\n String(entry.item) === String(selector.item)\n );\n }\n\n // qualifierType === 'meta'\n return metaFieldsMatch(entry.meta, selector);\n });\n\n/**\n * Type guard discriminating a `QualifiedDictionaryGroup` (merge output of a\n * qualified key) from a plain `Dictionary`. Both carry a `content` field; only\n * the group declares `qualifierTypes`, which is therefore the discriminator.\n */\nexport const isQualifiedDictionaryGroup = (\n value: unknown\n): value is QualifiedDictionaryGroup =>\n typeof value === 'object' &&\n value !== null &&\n 'qualifierTypes' in value &&\n Array.isArray((value as { qualifierTypes: unknown }).qualifierTypes) &&\n 'content' in value;\n\n/**\n * Reconstructs a resolvable {@link Dictionary} from a single entry of a\n * qualified group: the content node stored under its composite id, plus the\n * qualifier coordinates decoded from that id (`variant`, `item`) and the\n * preserved `meta` object for the meta dimension.\n *\n * This keeps the resolver's matching/transform code unchanged: it still sees a\n * `{ key, content, variant?, item?, meta? }` shape, even though the stored\n * format no longer duplicates those fields per entry.\n */\nexport const reconstructQualifiedEntry = (\n group: QualifiedDictionaryGroup,\n compositeId: string\n): Dictionary => {\n const segments = compositeId.split(COMPOSITE_ID_SEPARATOR);\n\n const entry = {\n key: group.key,\n content: group.content[compositeId],\n } as Dictionary;\n\n group.qualifierTypes.forEach((qualifierType, index) => {\n if (qualifierType === 'variant') {\n entry.variant = segments[index];\n } else if (qualifierType === 'item') {\n entry.item = Number(segments[index]);\n }\n });\n\n if (group.qualifierTypes.includes('meta')) {\n const metaIndex = group.qualifierTypes.indexOf('meta');\n entry.meta = group.meta?.[compositeId] ?? { id: segments[metaIndex] };\n }\n\n return entry;\n};\n\n/**\n * Resolves a dictionary (or qualified dictionary group) against a selector,\n * across every declared dimension.\n *\n * - Plain dictionary → returned as-is (selector ignored)\n * - `item` declared but not selected → every matching entry ordered by index\n * - `item` selected → the matching entry or null\n * - `variant` defaults to the `default` entry when not selected\n * - `meta` requires `{ id }` and every declared meta field to match\n *\n * Dimensions compose: e.g. a variant × item key with `{ variant: 'promo' }`\n * returns every promo item as an array; adding `{ item: 2 }` narrows to one.\n */\nexport const resolveQualifiedDictionary = (\n dictionaryOrGroup: Dictionary | QualifiedDictionaryGroup,\n selector?: DictionarySelector\n): Dictionary | Dictionary[] | null => {\n if (!isQualifiedDictionaryGroup(dictionaryOrGroup)) {\n return dictionaryOrGroup;\n }\n\n const { qualifierTypes, content } = dictionaryOrGroup;\n\n // The meta dimension cannot resolve without an id discriminator.\n if (qualifierTypes.includes('meta') && selector?.id === undefined) {\n return null;\n }\n\n const itemAxisOpen =\n qualifierTypes.includes('item') && selector?.item === undefined;\n\n const matchedEntries = Object.keys(content)\n .map((compositeId) =>\n reconstructQualifiedEntry(dictionaryOrGroup, compositeId)\n )\n .filter((entry) => entryMatchesSelector(entry, qualifierTypes, selector));\n\n if (itemAxisOpen) {\n return matchedEntries.sort(\n (left, right) => (left.item ?? 0) - (right.item ?? 0)\n );\n }\n\n return matchedEntries[0] ?? null;\n};\n\n/**\n * Splits the second argument of `getIntlayer` / `getDictionary` into the\n * effective locale and the selector object (if any).\n */\nexport const parseDictionarySelector = <L extends LocalesValues>(\n localeOrSelector?: L | DictionarySelector\n): { locale?: L; selector?: DictionarySelector } => {\n if (typeof localeOrSelector === 'object' && localeOrSelector !== null) {\n return {\n locale: localeOrSelector.locale as L | undefined,\n selector: localeOrSelector,\n };\n }\n\n return { locale: localeOrSelector };\n};\n\n/**\n * Builds a stable string identity of a selector (excluding `locale`), suitable\n * for cache keys and memoization dependencies.\n */\nexport const getDictionarySelectorCacheKey = (\n selector?: DictionarySelector\n): string => {\n if (!selector) return '';\n\n return Object.keys(selector)\n .filter((selectorKey) => selectorKey !== 'locale')\n .sort()\n .map((selectorKey) => `${selectorKey}:${String(selector[selectorKey])}`)\n .join('|');\n};\n\n/**\n * Marker property carrying the ordered qualifier dimensions on a dynamic loader\n * map. Its presence distinguishes a qualified group loader map (a nested tree\n * of chunks) from a plain dynamic loader map (one chunk per `locale`). Prefixed\n * and unlikely to collide with a real locale code.\n */\nexport const QUALIFIER_DYNAMIC_TYPES_KEY = '__intlayerQualifierTypes';\n\n/**\n * A lazily-imported per-locale dictionary chunk loader.\n */\nexport type DynamicDictionaryLoader = () => Promise<Dictionary>;\n\n/**\n * Nested tree of chunk loaders: one nesting level per declared dimension (in\n * canonical order), leaves are loaders.\n */\nexport type QualifiedDynamicLoaderTree = {\n [segment: string]: QualifiedDynamicLoaderTree | DynamicDictionaryLoader;\n};\n\n/**\n * Default export shape of a generated dynamic entry point for a qualified key.\n * One nesting level per dimension under each locale, plus the dimension marker.\n *\n * ```ts\n * {\n * __intlayerQualifierTypes: ['variant', 'item'],\n * en: { promo: { '1': () => import('./json/x/promo/1/en.json'), … }, … },\n * fr: { … },\n * }\n * ```\n */\nexport type QualifiedDynamicLoaderMap = {\n [QUALIFIER_DYNAMIC_TYPES_KEY]: DictionaryQualifierType[];\n [locale: string]: QualifiedDynamicLoaderTree | DictionaryQualifierType[];\n};\n\n/**\n * Type guard discriminating a qualified dynamic loader map (collections /\n * variants / meta records, possibly combined) from a plain dynamic loader map.\n */\nexport const isQualifiedDynamicLoaderMap = (\n value: unknown\n): value is QualifiedDynamicLoaderMap =>\n typeof value === 'object' &&\n value !== null &&\n QUALIFIER_DYNAMIC_TYPES_KEY in value;\n\n/**\n * Resolves the content of a qualified dynamic loader map against a selector,\n * loading only the chunk(s) the selector actually targets.\n *\n * Walks the nested loader tree one dimension at a time (canonical order\n * `variant → meta → item`): `variant` defaults to `default`, `meta` descends by\n * `id`, and `item` either narrows to the selected index or — when no item is\n * given — expands into every sibling chunk (the collection axis). Meta-equality\n * is verified on the loaded chunk. Semantics mirror\n * {@link resolveQualifiedDictionary} so dynamic and static modes behave alike.\n *\n * The Suspense mechanism is injected through `loadChunk` so the same logic\n * serves both the client (suspender cache) and the server (`react.use`). Every\n * targeted loader is started before the first chunk is read, so sibling chunks\n * load in parallel rather than waterfalling.\n *\n * @param loaderMap - The qualified dynamic loader map (entry point default export).\n * @param key - The dictionary key (used to build stable chunk cache keys).\n * @param locale - The resolved locale to load chunks for.\n * @param selector - The selector splitting the qualifier dimensions.\n * @param loadChunk - Reads a started chunk promise, suspending until it resolves.\n * @param transform - Turns a resolved chunk dictionary into final content.\n */\n/** One targeted chunk: its stable cache key and lazy loader. */\ntype CollectedChunk = {\n cacheKey: string;\n loader: DynamicDictionaryLoader;\n};\n\ntype CollectedChunks = {\n /** True when the `item` axis is open (collection result → array). */\n itemAxisOpen: boolean;\n /** True when a required coordinate is absent (result → [] or null). */\n missed: boolean;\n /** The chunks the selector targets (in collection order for the item axis). */\n chunks: CollectedChunk[];\n};\n\n/**\n * Walks the loader tree following the selector and collects the chunk loaders\n * it targets — shared by the sync ({@link resolveQualifiedDynamicContent}) and\n * async ({@link resolveQualifiedDynamicContentAsync}) resolvers.\n */\nconst collectQualifiedChunks = (\n loaderMap: QualifiedDynamicLoaderMap,\n key: string,\n locale: string,\n selector: DictionarySelector | undefined\n): CollectedChunks => {\n const qualifierTypes = loaderMap[QUALIFIER_DYNAMIC_TYPES_KEY];\n const localeTree = loaderMap[locale] as\n | QualifiedDynamicLoaderTree\n | undefined;\n\n const itemAxisOpen =\n qualifierTypes.includes('item') && selector?.item === undefined;\n\n if (!localeTree) return { itemAxisOpen, missed: true, chunks: [] };\n\n // The meta dimension cannot resolve without an id discriminator.\n if (qualifierTypes.includes('meta') && selector?.id === undefined) {\n return { itemAxisOpen, missed: true, chunks: [] };\n }\n\n const chunks: CollectedChunk[] = [];\n\n const walk = (\n node: QualifiedDynamicLoaderTree | DynamicDictionaryLoader,\n dimensions: DictionaryQualifierType[],\n segments: string[]\n ): boolean => {\n if (dimensions.length === 0) {\n chunks.push({\n cacheKey: `${key}.${locale}.${segments.join(COMPOSITE_ID_SEPARATOR)}`,\n loader: node as DynamicDictionaryLoader,\n });\n return true;\n }\n\n const [dimension, ...rest] = dimensions;\n const tree = node as QualifiedDynamicLoaderTree;\n\n if (dimension === 'item' && selector?.item === undefined) {\n // Open collection axis: fan out into every sibling chunk, ordered.\n for (const segment of Object.keys(tree).sort(\n (left, right) => Number(left) - Number(right)\n )) {\n walk(tree[segment]!, rest, [...segments, segment]);\n }\n return true;\n }\n\n const segment =\n dimension === 'variant'\n ? (selector?.variant ?? 'default')\n : dimension === 'meta'\n ? String(selector?.id)\n : String(selector?.item);\n\n const child = tree[segment];\n if (!child) return false;\n\n return walk(child, rest, [...segments, segment]);\n };\n\n const found = walk(localeTree, qualifierTypes, []);\n\n return { itemAxisOpen, missed: !found, chunks };\n};\n\n/**\n * Whether a loaded chunk satisfies the selector's meta fields (no-op unless the\n * key declares a `meta` dimension).\n */\nconst chunkMatchesMeta = (\n loaderMap: QualifiedDynamicLoaderMap,\n dictionary: Dictionary,\n selector: DictionarySelector | undefined\n): boolean =>\n !loaderMap[QUALIFIER_DYNAMIC_TYPES_KEY].includes('meta') ||\n metaFieldsMatch(dictionary.meta, selector);\n\n/**\n * Resolves the content of a qualified dynamic loader map against a selector,\n * loading only the chunk(s) the selector actually targets.\n *\n * Walks the nested loader tree one dimension at a time (canonical order\n * `variant → meta → item`): `variant` defaults to `default`, `meta` descends by\n * `id`, and `item` either narrows to the selected index or — when no item is\n * given — expands into every sibling chunk (the collection axis). Meta-equality\n * is verified on the loaded chunk. Semantics mirror\n * {@link resolveQualifiedDictionary} so dynamic and static modes behave alike.\n *\n * The Suspense mechanism is injected through `loadChunk` so the same logic\n * serves both the client (suspender cache) and the server (`react.use`). Every\n * targeted loader is started before the first chunk is read, so sibling chunks\n * load in parallel rather than waterfalling.\n *\n * @param loaderMap - The qualified dynamic loader map (entry point default export).\n * @param key - The dictionary key (used to build stable chunk cache keys).\n * @param locale - The resolved locale to load chunks for.\n * @param selector - The selector splitting the qualifier dimensions.\n * @param loadChunk - Reads a started chunk promise, suspending until it resolves.\n * @param transform - Turns a resolved chunk dictionary into final content.\n */\nexport const resolveQualifiedDynamicContent = <Content>(params: {\n loaderMap: QualifiedDynamicLoaderMap;\n key: string;\n locale: string;\n selector: DictionarySelector | undefined;\n loadChunk: (cacheKey: string, promise: Promise<Dictionary>) => Dictionary;\n transform: (dictionary: Dictionary) => Content;\n}): Content | Content[] | null => {\n const { loaderMap, key, locale, selector, loadChunk, transform } = params;\n\n const { itemAxisOpen, missed, chunks } = collectQualifiedChunks(\n loaderMap,\n key,\n locale,\n selector\n );\n\n if (missed) return itemAxisOpen ? [] : null;\n\n // Start every loader before reading, so siblings load in parallel.\n const dictionaries = chunks\n .map(({ cacheKey, loader }) => loadChunk(cacheKey, loader()))\n .filter((dictionary) => chunkMatchesMeta(loaderMap, dictionary, selector));\n\n if (itemAxisOpen) return dictionaries.map(transform);\n\n const [dictionary] = dictionaries;\n return dictionary ? transform(dictionary) : null;\n};\n\n/**\n * Async counterpart of {@link resolveQualifiedDynamicContent} for frameworks\n * that load dictionaries with `await` instead of Suspense (Vue, Svelte, Lit,\n * vanilla). Awaits every targeted chunk in parallel, then resolves identically.\n *\n * @param loaderMap - The qualified dynamic loader map.\n * @param key - The dictionary key (used to build stable chunk cache keys).\n * @param locale - The resolved locale to load chunks for.\n * @param selector - The selector splitting the qualifier dimensions.\n * @param transform - Turns a resolved chunk dictionary into final content.\n */\nexport const resolveQualifiedDynamicContentAsync = async <Content>(params: {\n loaderMap: QualifiedDynamicLoaderMap;\n key: string;\n locale: string;\n selector: DictionarySelector | undefined;\n transform: (dictionary: Dictionary) => Content;\n}): Promise<Content | Content[] | null> => {\n const { loaderMap, key, locale, selector, transform } = params;\n\n const { itemAxisOpen, missed, chunks } = collectQualifiedChunks(\n loaderMap,\n key,\n locale,\n selector\n );\n\n if (missed) return itemAxisOpen ? [] : null;\n\n const dictionaries = (\n await Promise.all(chunks.map(({ loader }) => loader()))\n ).filter((dictionary) => chunkMatchesMeta(loaderMap, dictionary, selector));\n\n if (itemAxisOpen) return dictionaries.map(transform);\n\n const [dictionary] = dictionaries;\n return dictionary ? transform(dictionary) : null;\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"qualifiedDictionary.mjs","names":[],"sources":["../../../src/dictionaryManipulator/qualifiedDictionary.ts"],"sourcesContent":["import type {\n Dictionary,\n DictionaryQualifierType,\n DictionarySelector,\n QualifiedDictionaryGroup,\n} from '@intlayer/types/dictionary';\nimport type { LocalesValues } from '@intlayer/types/module_augmentation';\n\n/**\n * Canonical order of qualifier dimensions. A key that declares both dimensions\n * always nests them in this order, with `item` innermost so it can act as the\n * collection (array) axis.\n */\nexport const QUALIFIER_ORDER = [\n 'variant',\n 'item',\n] as const satisfies readonly DictionaryQualifierType[];\n\n/**\n * Separator joining per-dimension ids into a composite entry id. Also used as\n * the chunk path separator in dynamic mode.\n */\nexport const COMPOSITE_ID_SEPARATOR = '/';\n\n/**\n * Canonical serialization of a variant value into its identity string — the\n * variant segment of a composite id and the runtime matching key.\n *\n * - `undefined` → `'default'` (the implicit fallback variant)\n * - a string → the string itself (a named variant)\n * - an object → its sorted `key=value` pairs joined by `&`\n * (e.g. `{ userId: '123', id: 'abc' }` → `'id=abc&userId=123'`)\n *\n * Two variants resolve to the same entry iff their serializations are equal, so\n * an object variant in a selector must equal the one declared on the dictionary.\n */\nexport const serializeVariant = (\n variant: string | Record<string, string | number> | undefined\n): string => {\n if (variant === undefined) return 'default';\n if (typeof variant === 'string') return variant;\n\n return Object.keys(variant)\n .sort()\n .map((field) => `${field}=${variant[field]}`)\n .join('&');\n};\n\n/**\n * Returns the qualifier dimensions declared on a dictionary, in canonical\n * order (`variant → item`). Empty when the dictionary is unqualified\n * (plain dictionary or shared base content of a qualified group).\n */\nexport const getDictionaryQualifierTypes = (\n dictionary: Dictionary\n): DictionaryQualifierType[] => {\n const declaredQualifiers: DictionaryQualifierType[] = [];\n\n if (dictionary.variant !== undefined) declaredQualifiers.push('variant');\n if (typeof dictionary.item === 'number') declaredQualifiers.push('item');\n\n return declaredQualifiers;\n};\n\n/**\n * Returns the qualifier identifier of a dictionary for the given qualifier\n * dimension — one segment of the composite entry id.\n *\n * - 'variant' → the serialized variant (named string or object identity)\n * - 'item' → the item index as string\n */\nexport const getDictionaryQualifierId = (\n dictionary: Dictionary,\n qualifierType: DictionaryQualifierType\n): string | undefined => {\n if (qualifierType === 'variant') {\n return dictionary.variant === undefined\n ? undefined\n : serializeVariant(dictionary.variant);\n }\n return dictionary.item === undefined ? undefined : String(dictionary.item);\n};\n\n/**\n * Returns the per-dimension id segments of a dictionary for the given ordered\n * dimension set, or `undefined` when the dictionary does not declare every\n * dimension of the set.\n */\nexport const getDictionaryQualifierSegments = (\n dictionary: Dictionary,\n qualifierTypes: DictionaryQualifierType[]\n): string[] | undefined => {\n const segments: string[] = [];\n\n for (const qualifierType of qualifierTypes) {\n const id = getDictionaryQualifierId(dictionary, qualifierType);\n if (id === undefined) return undefined;\n segments.push(id);\n }\n\n return segments;\n};\n\n/**\n * Builds the composite entry id of a dictionary — its per-dimension id segments\n * joined in canonical order. `undefined` when a dimension is missing.\n */\nexport const getDictionaryCompositeId = (\n dictionary: Dictionary,\n qualifierTypes: DictionaryQualifierType[]\n): string | undefined =>\n getDictionaryQualifierSegments(dictionary, qualifierTypes)?.join(\n COMPOSITE_ID_SEPARATOR\n );\n\n/**\n * Tests whether a group entry matches a selector across every declared\n * dimension. The `item` dimension matches any value when the selector does not\n * provide one (open collection axis).\n */\nconst entryMatchesSelector = (\n entry: Dictionary,\n qualifierTypes: DictionaryQualifierType[],\n selector: DictionarySelector | undefined\n): boolean =>\n qualifierTypes.every((qualifierType) => {\n if (qualifierType === 'variant') {\n return (\n serializeVariant(entry.variant) === serializeVariant(selector?.variant)\n );\n }\n\n // qualifierType === 'item'\n return (\n selector?.item === undefined ||\n String(entry.item) === String(selector.item)\n );\n });\n\n/**\n * Type guard discriminating a `QualifiedDictionaryGroup` (merge output of a\n * qualified key) from a plain `Dictionary`. Both carry a `content` field; only\n * the group declares `qualifierTypes`, which is therefore the discriminator.\n */\nexport const isQualifiedDictionaryGroup = (\n value: unknown\n): value is QualifiedDictionaryGroup =>\n typeof value === 'object' &&\n value !== null &&\n 'qualifierTypes' in value &&\n Array.isArray((value as { qualifierTypes: unknown }).qualifierTypes) &&\n 'content' in value;\n\n/**\n * Reconstructs a resolvable {@link Dictionary} from a single entry of a\n * qualified group: the content node stored under its composite id, plus the\n * qualifier coordinates decoded from that id (`variant`, `item`).\n *\n * This keeps the resolver's matching/transform code unchanged: it still sees a\n * `{ key, content, variant?, item? }` shape, even though the stored format no\n * longer duplicates those fields per entry. The `variant` coordinate stays in\n * its serialized form (e.g. `'id=abc&userId=123'`), which round-trips through\n * {@link serializeVariant} during matching.\n */\nexport const reconstructQualifiedEntry = (\n group: QualifiedDictionaryGroup,\n compositeId: string\n): Dictionary => {\n const segments = compositeId.split(COMPOSITE_ID_SEPARATOR);\n\n const entry = {\n key: group.key,\n content: group.content[compositeId],\n } as Dictionary;\n\n group.qualifierTypes.forEach((qualifierType, index) => {\n if (qualifierType === 'variant') {\n entry.variant = segments[index];\n } else if (qualifierType === 'item') {\n entry.item = Number(segments[index]);\n }\n });\n\n return entry;\n};\n\n/**\n * Resolves a dictionary (or qualified dictionary group) against a selector,\n * across every declared dimension.\n *\n * - Plain dictionary → returned as-is (selector ignored)\n * - `item` declared but not selected → every matching entry ordered by index\n * - `item` selected → the matching entry or null\n * - `variant` defaults to the `default` entry when not selected; an object\n * variant resolves only when the selector provides an equal object\n *\n * Dimensions compose: e.g. a variant × item key with `{ variant: 'promo' }`\n * returns every promo item as an array; adding `{ item: 2 }` narrows to one.\n */\nexport const resolveQualifiedDictionary = (\n dictionaryOrGroup: Dictionary | QualifiedDictionaryGroup,\n selector?: DictionarySelector\n): Dictionary | Dictionary[] | null => {\n if (!isQualifiedDictionaryGroup(dictionaryOrGroup)) {\n return dictionaryOrGroup;\n }\n\n const { qualifierTypes, content } = dictionaryOrGroup;\n\n const itemAxisOpen =\n qualifierTypes.includes('item') && selector?.item === undefined;\n\n const matchedEntries = Object.keys(content)\n .map((compositeId) =>\n reconstructQualifiedEntry(dictionaryOrGroup, compositeId)\n )\n .filter((entry) => entryMatchesSelector(entry, qualifierTypes, selector));\n\n if (itemAxisOpen) {\n return matchedEntries.sort(\n (left, right) => (left.item ?? 0) - (right.item ?? 0)\n );\n }\n\n return matchedEntries[0] ?? null;\n};\n\n/**\n * Splits the second argument of `getIntlayer` / `getDictionary` into the\n * effective locale and the selector object (if any).\n */\nexport const parseDictionarySelector = <L extends LocalesValues>(\n localeOrSelector?: L | DictionarySelector\n): { locale?: L; selector?: DictionarySelector } => {\n if (typeof localeOrSelector === 'object' && localeOrSelector !== null) {\n return {\n locale: localeOrSelector.locale as L | undefined,\n selector: localeOrSelector,\n };\n }\n\n return { locale: localeOrSelector };\n};\n\n/**\n * Builds a stable string identity of a selector (excluding `locale`), suitable\n * for cache keys and memoization dependencies.\n */\nexport const getDictionarySelectorCacheKey = (\n selector?: DictionarySelector\n): string => {\n if (!selector) return '';\n\n return Object.keys(selector)\n .filter((selectorKey) => selectorKey !== 'locale')\n .sort()\n .map((selectorKey) => {\n const value = selector[selectorKey as keyof DictionarySelector];\n const serialized =\n selectorKey === 'variant'\n ? serializeVariant(value as Parameters<typeof serializeVariant>[0])\n : String(value);\n return `${selectorKey}:${serialized}`;\n })\n .join('|');\n};\n\n/**\n * Marker property carrying the ordered qualifier dimensions on a dynamic loader\n * map. Its presence distinguishes a qualified group loader map (a nested tree\n * of chunks) from a plain dynamic loader map (one chunk per `locale`). Prefixed\n * and unlikely to collide with a real locale code.\n */\nexport const QUALIFIER_DYNAMIC_TYPES_KEY = '__intlayerQualifierTypes';\n\n/**\n * A lazily-imported per-locale dictionary chunk loader.\n */\nexport type DynamicDictionaryLoader = () => Promise<Dictionary>;\n\n/**\n * Nested tree of chunk loaders: one nesting level per declared dimension (in\n * canonical order), leaves are loaders.\n */\nexport type QualifiedDynamicLoaderTree = {\n [segment: string]: QualifiedDynamicLoaderTree | DynamicDictionaryLoader;\n};\n\n/**\n * Default export shape of a generated dynamic entry point for a qualified key.\n * One nesting level per dimension under each locale, plus the dimension marker.\n *\n * ```ts\n * {\n * __intlayerQualifierTypes: ['variant', 'item'],\n * en: { promo: { '1': () => import('./json/x/promo/1/en.json'), … }, … },\n * fr: { … },\n * }\n * ```\n */\nexport type QualifiedDynamicLoaderMap = {\n [QUALIFIER_DYNAMIC_TYPES_KEY]: DictionaryQualifierType[];\n [locale: string]: QualifiedDynamicLoaderTree | DictionaryQualifierType[];\n};\n\n/**\n * Type guard discriminating a qualified dynamic loader map (collections /\n * variants, possibly combined) from a plain dynamic loader map.\n */\nexport const isQualifiedDynamicLoaderMap = (\n value: unknown\n): value is QualifiedDynamicLoaderMap =>\n typeof value === 'object' &&\n value !== null &&\n QUALIFIER_DYNAMIC_TYPES_KEY in value;\n\n/**\n/** One targeted chunk: its stable cache key and lazy loader. */\ntype CollectedChunk = {\n cacheKey: string;\n loader: DynamicDictionaryLoader;\n};\n\ntype CollectedChunks = {\n /** True when the `item` axis is open (collection result → array). */\n itemAxisOpen: boolean;\n /** True when a required coordinate is absent (result → [] or null). */\n missed: boolean;\n /** The chunks the selector targets (in collection order for the item axis). */\n chunks: CollectedChunk[];\n};\n\n/**\n * Walks the loader tree following the selector and collects the chunk loaders\n * it targets — shared by the sync ({@link resolveQualifiedDynamicContent}) and\n * async ({@link resolveQualifiedDynamicContentAsync}) resolvers.\n */\nconst collectQualifiedChunks = (\n loaderMap: QualifiedDynamicLoaderMap,\n key: string,\n locale: string,\n selector: DictionarySelector | undefined\n): CollectedChunks => {\n const qualifierTypes = loaderMap[QUALIFIER_DYNAMIC_TYPES_KEY];\n const localeTree = loaderMap[locale] as\n | QualifiedDynamicLoaderTree\n | undefined;\n\n const itemAxisOpen =\n qualifierTypes.includes('item') && selector?.item === undefined;\n\n if (!localeTree) return { itemAxisOpen, missed: true, chunks: [] };\n\n const chunks: CollectedChunk[] = [];\n\n const walk = (\n node: QualifiedDynamicLoaderTree | DynamicDictionaryLoader,\n dimensions: DictionaryQualifierType[],\n segments: string[]\n ): boolean => {\n if (dimensions.length === 0) {\n chunks.push({\n cacheKey: `${key}.${locale}.${segments.join(COMPOSITE_ID_SEPARATOR)}`,\n loader: node as DynamicDictionaryLoader,\n });\n return true;\n }\n\n const [dimension, ...rest] = dimensions;\n const tree = node as QualifiedDynamicLoaderTree;\n\n if (dimension === 'item' && selector?.item === undefined) {\n // Open collection axis: fan out into every sibling chunk, ordered.\n for (const segment of Object.keys(tree).sort(\n (left, right) => Number(left) - Number(right)\n )) {\n walk(tree[segment]!, rest, [...segments, segment]);\n }\n return true;\n }\n\n const segment =\n dimension === 'variant'\n ? serializeVariant(selector?.variant)\n : String(selector?.item);\n\n const child = tree[segment];\n if (!child) return false;\n\n return walk(child, rest, [...segments, segment]);\n };\n\n const found = walk(localeTree, qualifierTypes, []);\n\n return { itemAxisOpen, missed: !found, chunks };\n};\n\n/**\n * Resolves the content of a qualified dynamic loader map against a selector,\n * loading only the chunk(s) the selector actually targets.\n *\n * Walks the nested loader tree one dimension at a time (canonical order\n * `variant → item`): `variant` defaults to `default` (or descends by the\n * serialized object identity), and `item` either narrows to the selected index\n * or — when no item is given — expands into every sibling chunk (the collection\n * axis). Semantics mirror {@link resolveQualifiedDictionary} so dynamic and\n * static modes behave alike.\n *\n * The Suspense mechanism is injected through `loadChunk` so the same logic\n * serves both the client (suspender cache) and the server (`react.use`). Every\n * targeted loader is started before the first chunk is read, so sibling chunks\n * load in parallel rather than waterfalling.\n *\n * @param loaderMap - The qualified dynamic loader map (entry point default export).\n * @param key - The dictionary key (used to build stable chunk cache keys).\n * @param locale - The resolved locale to load chunks for.\n * @param selector - The selector splitting the qualifier dimensions.\n * @param loadChunk - Reads a started chunk promise, suspending until it resolves.\n * @param transform - Turns a resolved chunk dictionary into final content.\n */\nexport const resolveQualifiedDynamicContent = <Content>(params: {\n loaderMap: QualifiedDynamicLoaderMap;\n key: string;\n locale: string;\n selector: DictionarySelector | undefined;\n loadChunk: (cacheKey: string, promise: Promise<Dictionary>) => Dictionary;\n transform: (dictionary: Dictionary) => Content;\n}): Content | Content[] | null => {\n const { loaderMap, key, locale, selector, loadChunk, transform } = params;\n\n const { itemAxisOpen, missed, chunks } = collectQualifiedChunks(\n loaderMap,\n key,\n locale,\n selector\n );\n\n if (missed) return itemAxisOpen ? [] : null;\n\n // Start every loader before reading, so siblings load in parallel.\n const dictionaries = chunks.map(({ cacheKey, loader }) =>\n loadChunk(cacheKey, loader())\n );\n\n if (itemAxisOpen) return dictionaries.map(transform);\n\n const [dictionary] = dictionaries;\n return dictionary ? transform(dictionary) : null;\n};\n\n/**\n * Async counterpart of {@link resolveQualifiedDynamicContent} for frameworks\n * that load dictionaries with `await` instead of Suspense (Vue, Svelte, Lit,\n * vanilla). Awaits every targeted chunk in parallel, then resolves identically.\n *\n * @param loaderMap - The qualified dynamic loader map.\n * @param key - The dictionary key (used to build stable chunk cache keys).\n * @param locale - The resolved locale to load chunks for.\n * @param selector - The selector splitting the qualifier dimensions.\n * @param transform - Turns a resolved chunk dictionary into final content.\n */\nexport const resolveQualifiedDynamicContentAsync = async <Content>(params: {\n loaderMap: QualifiedDynamicLoaderMap;\n key: string;\n locale: string;\n selector: DictionarySelector | undefined;\n transform: (dictionary: Dictionary) => Content;\n}): Promise<Content | Content[] | null> => {\n const { loaderMap, key, locale, selector, transform } = params;\n\n const { itemAxisOpen, missed, chunks } = collectQualifiedChunks(\n loaderMap,\n key,\n locale,\n selector\n );\n\n if (missed) return itemAxisOpen ? [] : null;\n\n const dictionaries = await Promise.all(chunks.map(({ loader }) => loader()));\n\n if (itemAxisOpen) return dictionaries.map(transform);\n\n const [dictionary] = dictionaries;\n return dictionary ? transform(dictionary) : null;\n};\n"],"mappings":"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"}