@intlayer/core 9.2.0 → 9.3.1
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.
- package/README.md +24 -24
- package/dist/cjs/_virtual/_rolldown/runtime.cjs +1 -2
- package/dist/cjs/deepTransformPlugins/getEditedContent.cjs +10 -6
- package/dist/cjs/deepTransformPlugins/getEditedContent.cjs.map +1 -1
- package/dist/cjs/deepTransformPlugins/getReplacedValuesContent.cjs +2 -1
- package/dist/cjs/deepTransformPlugins/getReplacedValuesContent.cjs.map +1 -1
- package/dist/cjs/deepTransformPlugins/insertContentInDictionary.cjs +41 -32
- package/dist/cjs/deepTransformPlugins/insertContentInDictionary.cjs.map +1 -1
- package/dist/cjs/dictionaryManipulator/editDictionaryByKeyPath.cjs +6 -5
- package/dist/cjs/dictionaryManipulator/editDictionaryByKeyPath.cjs.map +1 -1
- package/dist/cjs/dictionaryManipulator/getNestedDictionaryReferences.cjs +153 -0
- package/dist/cjs/dictionaryManipulator/getNestedDictionaryReferences.cjs.map +1 -0
- package/dist/cjs/dictionaryManipulator/index.cjs +16 -9
- package/dist/cjs/dictionaryManipulator/mergeDictionaries.cjs +4 -3
- package/dist/cjs/dictionaryManipulator/mergeDictionaries.cjs.map +1 -1
- package/dist/cjs/dictionaryManipulator/mergeQualifiedDictionaries.cjs +4 -3
- package/dist/cjs/dictionaryManipulator/mergeQualifiedDictionaries.cjs.map +1 -1
- package/dist/cjs/dictionaryManipulator/qualifiedDictionary.cjs +1 -171
- package/dist/cjs/dictionaryManipulator/qualifiedDictionary.cjs.map +1 -1
- package/dist/cjs/dictionaryManipulator/qualifiedDictionary.test-d.cjs.map +1 -1
- package/dist/cjs/dictionaryManipulator/qualifiedDictionaryDeclaration.cjs +69 -0
- package/dist/cjs/dictionaryManipulator/qualifiedDictionaryDeclaration.cjs.map +1 -0
- package/dist/cjs/dictionaryManipulator/qualifiedDynamicLoader.cjs +115 -0
- package/dist/cjs/dictionaryManipulator/qualifiedDynamicLoader.cjs.map +1 -0
- package/dist/cjs/dictionaryManipulator/removeContentNodeByKeyPath.cjs +4 -2
- package/dist/cjs/dictionaryManipulator/removeContentNodeByKeyPath.cjs.map +1 -1
- package/dist/cjs/dictionaryManipulator/renameContentNodeByKeyPath.cjs +11 -9
- package/dist/cjs/dictionaryManipulator/renameContentNodeByKeyPath.cjs.map +1 -1
- package/dist/cjs/formatters/date.cjs +2 -1
- package/dist/cjs/formatters/date.cjs.map +1 -1
- package/dist/cjs/formatters/relativeTime.cjs +3 -3
- package/dist/cjs/formatters/relativeTime.cjs.map +1 -1
- package/dist/cjs/index.cjs +16 -9
- package/dist/cjs/interpreter/getCondition.cjs +0 -1
- package/dist/cjs/interpreter/getCondition.cjs.map +1 -1
- package/dist/cjs/interpreter/getContent/plugins.cjs +12 -1
- package/dist/cjs/interpreter/getContent/plugins.cjs.map +1 -1
- package/dist/cjs/interpreter/getDictionary.cjs +2 -1
- package/dist/cjs/interpreter/getDictionary.cjs.map +1 -1
- package/dist/cjs/interpreter/getEnumeration.cjs +0 -1
- package/dist/cjs/interpreter/getEnumeration.cjs.map +1 -1
- package/dist/cjs/interpreter/getGender.cjs +0 -1
- package/dist/cjs/interpreter/getGender.cjs.map +1 -1
- package/dist/cjs/interpreter/getHTML.cjs +0 -1
- package/dist/cjs/interpreter/getHTML.cjs.map +1 -1
- package/dist/cjs/interpreter/getInsertion.cjs +0 -1
- package/dist/cjs/interpreter/getInsertion.cjs.map +1 -1
- package/dist/cjs/interpreter/getIntlayer.cjs.map +1 -1
- package/dist/cjs/interpreter/getIntlayer.test-d.cjs.map +1 -1
- package/dist/cjs/interpreter/getNesting.optimized.cjs +59 -0
- package/dist/cjs/interpreter/getNesting.optimized.cjs.map +1 -0
- package/dist/cjs/interpreter/getSelect.cjs +0 -1
- package/dist/cjs/interpreter/getSelect.cjs.map +1 -1
- package/dist/cjs/interpreter/getTranslation.cjs +0 -1
- package/dist/cjs/interpreter/getTranslation.cjs.map +1 -1
- package/dist/cjs/interpreter/splitAndJoinInsertion.cjs +0 -1
- package/dist/cjs/interpreter/splitAndJoinInsertion.cjs.map +1 -1
- package/dist/cjs/localization/domainUtils.cjs +0 -1
- package/dist/cjs/localization/domainUtils.cjs.map +1 -1
- package/dist/cjs/localization/getBrowserLocale.cjs +2 -1
- package/dist/cjs/localization/getBrowserLocale.cjs.map +1 -1
- package/dist/cjs/localization/getHTMLTextDir.cjs +0 -1
- package/dist/cjs/localization/getHTMLTextDir.cjs.map +1 -1
- package/dist/cjs/localization/getLocale.cjs +2 -1
- package/dist/cjs/localization/getLocale.cjs.map +1 -1
- package/dist/cjs/localization/getLocaleLang.cjs +0 -1
- package/dist/cjs/localization/getLocaleLang.cjs.map +1 -1
- package/dist/cjs/localization/localeDetector.cjs +2 -1
- package/dist/cjs/localization/localeDetector.cjs.map +1 -1
- package/dist/cjs/localization/proxyMode.cjs.map +1 -1
- package/dist/cjs/localization/rewriteUtils.cjs +0 -1
- package/dist/cjs/localization/rewriteUtils.cjs.map +1 -1
- package/dist/cjs/markdown/compiler.cjs +9 -6
- package/dist/cjs/markdown/compiler.cjs.map +1 -1
- package/dist/cjs/markdown/constants.cjs +0 -1
- package/dist/cjs/markdown/constants.cjs.map +1 -1
- package/dist/cjs/markdown/utils.cjs.map +1 -1
- package/dist/cjs/messageFormat/ICU.cjs +17 -14
- package/dist/cjs/messageFormat/ICU.cjs.map +1 -1
- package/dist/cjs/messageFormat/i18next.cjs +4 -2
- package/dist/cjs/messageFormat/i18next.cjs.map +1 -1
- package/dist/cjs/messageFormat/navigatePath.cjs +0 -1
- package/dist/cjs/messageFormat/navigatePath.cjs.map +1 -1
- package/dist/cjs/messageFormat/resolveMessage.cjs +2 -1
- package/dist/cjs/messageFormat/resolveMessage.cjs.map +1 -1
- package/dist/cjs/messageFormat/verify-icu-format.cjs +3 -2
- package/dist/cjs/messageFormat/verify-icu-format.cjs.map +1 -1
- package/dist/cjs/messageFormat/vue-i18n.cjs +2 -1
- package/dist/cjs/messageFormat/vue-i18n.cjs.map +1 -1
- package/dist/cjs/transpiler/condition/condition.cjs.map +1 -1
- package/dist/cjs/transpiler/enumeration/enumeration.cjs.map +1 -1
- package/dist/cjs/transpiler/file/file.cjs +2 -1
- package/dist/cjs/transpiler/file/file.cjs.map +1 -1
- package/dist/cjs/transpiler/file/fileBrowser.cjs +0 -1
- package/dist/cjs/transpiler/file/fileBrowser.cjs.map +1 -1
- package/dist/cjs/transpiler/gender/gender.cjs.map +1 -1
- package/dist/cjs/transpiler/html/getHTMLCustomComponents.cjs +0 -1
- package/dist/cjs/transpiler/html/getHTMLCustomComponents.cjs.map +1 -1
- package/dist/cjs/transpiler/html/html.cjs.map +1 -1
- package/dist/cjs/transpiler/html/htmlTags.cjs +0 -1
- package/dist/cjs/transpiler/html/htmlTags.cjs.map +1 -1
- package/dist/cjs/transpiler/html/validateHTML.cjs +12 -12
- package/dist/cjs/transpiler/html/validateHTML.cjs.map +1 -1
- package/dist/cjs/transpiler/insertion/getInsertionValues.cjs +0 -1
- package/dist/cjs/transpiler/insertion/getInsertionValues.cjs.map +1 -1
- package/dist/cjs/transpiler/insertion/insertion.cjs.map +1 -1
- package/dist/cjs/transpiler/markdown/getMarkdownMetadata.cjs +2 -1
- package/dist/cjs/transpiler/markdown/getMarkdownMetadata.cjs.map +1 -1
- package/dist/cjs/transpiler/markdown/markdown.cjs +2 -1
- package/dist/cjs/transpiler/markdown/markdown.cjs.map +1 -1
- package/dist/cjs/transpiler/markdown/validateMarkdown.cjs +7 -6
- package/dist/cjs/transpiler/markdown/validateMarkdown.cjs.map +1 -1
- package/dist/cjs/transpiler/nesting/nesting.cjs.map +1 -1
- package/dist/cjs/transpiler/plural/plural.cjs.map +1 -1
- package/dist/cjs/transpiler/select/select.cjs.map +1 -1
- package/dist/cjs/transpiler/translation/translation.cjs.map +1 -1
- package/dist/cjs/utils/checkIsURLAbsolute.cjs +0 -1
- package/dist/cjs/utils/checkIsURLAbsolute.cjs.map +1 -1
- package/dist/cjs/utils/cookieExpiry.cjs +0 -1
- package/dist/cjs/utils/cookieExpiry.cjs.map +1 -1
- package/dist/cjs/utils/getCookie.cjs +0 -1
- package/dist/cjs/utils/getCookie.cjs.map +1 -1
- package/dist/cjs/utils/isSameKeyPath.cjs +0 -1
- package/dist/cjs/utils/isSameKeyPath.cjs.map +1 -1
- package/dist/cjs/utils/isValidReactElement.cjs +0 -1
- package/dist/cjs/utils/isValidReactElement.cjs.map +1 -1
- package/dist/cjs/utils/parseYaml.cjs +0 -1
- package/dist/cjs/utils/parseYaml.cjs.map +1 -1
- package/dist/cjs/utils/stringifyYaml.cjs +0 -1
- package/dist/cjs/utils/stringifyYaml.cjs.map +1 -1
- package/dist/esm/deepTransformPlugins/getEditedContent.mjs +10 -6
- package/dist/esm/deepTransformPlugins/getEditedContent.mjs.map +1 -1
- package/dist/esm/deepTransformPlugins/getReplacedValuesContent.mjs +2 -1
- package/dist/esm/deepTransformPlugins/getReplacedValuesContent.mjs.map +1 -1
- package/dist/esm/deepTransformPlugins/insertContentInDictionary.mjs +41 -32
- package/dist/esm/deepTransformPlugins/insertContentInDictionary.mjs.map +1 -1
- package/dist/esm/dictionaryManipulator/editDictionaryByKeyPath.mjs +6 -5
- package/dist/esm/dictionaryManipulator/editDictionaryByKeyPath.mjs.map +1 -1
- package/dist/esm/dictionaryManipulator/getNestedDictionaryReferences.mjs +150 -0
- package/dist/esm/dictionaryManipulator/getNestedDictionaryReferences.mjs.map +1 -0
- package/dist/esm/dictionaryManipulator/index.mjs +5 -2
- package/dist/esm/dictionaryManipulator/mergeDictionaries.mjs +4 -3
- package/dist/esm/dictionaryManipulator/mergeDictionaries.mjs.map +1 -1
- package/dist/esm/dictionaryManipulator/mergeQualifiedDictionaries.mjs +2 -1
- package/dist/esm/dictionaryManipulator/mergeQualifiedDictionaries.mjs.map +1 -1
- package/dist/esm/dictionaryManipulator/qualifiedDictionary.mjs +1 -162
- package/dist/esm/dictionaryManipulator/qualifiedDictionary.mjs.map +1 -1
- package/dist/esm/dictionaryManipulator/qualifiedDictionaryDeclaration.mjs +64 -0
- package/dist/esm/dictionaryManipulator/qualifiedDictionaryDeclaration.mjs.map +1 -0
- package/dist/esm/dictionaryManipulator/qualifiedDynamicLoader.mjs +111 -0
- package/dist/esm/dictionaryManipulator/qualifiedDynamicLoader.mjs.map +1 -0
- package/dist/esm/dictionaryManipulator/removeContentNodeByKeyPath.mjs +4 -2
- package/dist/esm/dictionaryManipulator/removeContentNodeByKeyPath.mjs.map +1 -1
- package/dist/esm/dictionaryManipulator/renameContentNodeByKeyPath.mjs +11 -9
- package/dist/esm/dictionaryManipulator/renameContentNodeByKeyPath.mjs.map +1 -1
- package/dist/esm/formatters/date.mjs +2 -1
- package/dist/esm/formatters/date.mjs.map +1 -1
- package/dist/esm/formatters/relativeTime.mjs +3 -3
- package/dist/esm/formatters/relativeTime.mjs.map +1 -1
- package/dist/esm/index.mjs +5 -2
- package/dist/esm/interpreter/getContent/plugins.mjs +12 -1
- package/dist/esm/interpreter/getContent/plugins.mjs.map +1 -1
- package/dist/esm/interpreter/getDictionary.mjs +2 -1
- package/dist/esm/interpreter/getDictionary.mjs.map +1 -1
- package/dist/esm/interpreter/getNesting.optimized.mjs +58 -0
- package/dist/esm/interpreter/getNesting.optimized.mjs.map +1 -0
- package/dist/esm/localization/getBrowserLocale.mjs +2 -1
- package/dist/esm/localization/getBrowserLocale.mjs.map +1 -1
- package/dist/esm/localization/getLocale.mjs +2 -1
- package/dist/esm/localization/getLocale.mjs.map +1 -1
- package/dist/esm/localization/localeDetector.mjs +2 -1
- package/dist/esm/localization/localeDetector.mjs.map +1 -1
- package/dist/esm/markdown/compiler.mjs +9 -6
- package/dist/esm/markdown/compiler.mjs.map +1 -1
- package/dist/esm/markdown/utils.mjs.map +1 -1
- package/dist/esm/messageFormat/ICU.mjs +17 -14
- package/dist/esm/messageFormat/ICU.mjs.map +1 -1
- package/dist/esm/messageFormat/i18next.mjs +4 -2
- package/dist/esm/messageFormat/i18next.mjs.map +1 -1
- package/dist/esm/messageFormat/resolveMessage.mjs +2 -1
- package/dist/esm/messageFormat/resolveMessage.mjs.map +1 -1
- package/dist/esm/messageFormat/verify-icu-format.mjs +3 -2
- package/dist/esm/messageFormat/verify-icu-format.mjs.map +1 -1
- package/dist/esm/messageFormat/vue-i18n.mjs +2 -1
- package/dist/esm/messageFormat/vue-i18n.mjs.map +1 -1
- package/dist/esm/transpiler/file/file.mjs +2 -1
- package/dist/esm/transpiler/file/file.mjs.map +1 -1
- package/dist/esm/transpiler/html/validateHTML.mjs +12 -11
- package/dist/esm/transpiler/html/validateHTML.mjs.map +1 -1
- package/dist/esm/transpiler/markdown/getMarkdownMetadata.mjs +2 -1
- package/dist/esm/transpiler/markdown/getMarkdownMetadata.mjs.map +1 -1
- package/dist/esm/transpiler/markdown/markdown.mjs +2 -1
- package/dist/esm/transpiler/markdown/markdown.mjs.map +1 -1
- package/dist/esm/transpiler/markdown/validateMarkdown.mjs +7 -6
- package/dist/esm/transpiler/markdown/validateMarkdown.mjs.map +1 -1
- package/dist/esm/utils/parseYaml.mjs.map +1 -1
- package/dist/types/deepTransformPlugins/getEditedContent.d.ts.map +1 -1
- package/dist/types/deepTransformPlugins/getFilterMissingTranslationsContent.d.ts +1 -0
- package/dist/types/deepTransformPlugins/getFilterTranslationsOnlyContent.d.ts +1 -0
- package/dist/types/deepTransformPlugins/getFilteredLocalesContent.d.ts +1 -0
- package/dist/types/dictionaryManipulator/getNestedDictionaryReferences.d.ts +62 -0
- package/dist/types/dictionaryManipulator/getNestedDictionaryReferences.d.ts.map +1 -0
- package/dist/types/dictionaryManipulator/index.d.ts +5 -2
- package/dist/types/dictionaryManipulator/mergeQualifiedDictionaries.d.ts.map +1 -1
- package/dist/types/dictionaryManipulator/qualifiedDictionary.d.ts +18 -126
- package/dist/types/dictionaryManipulator/qualifiedDictionary.d.ts.map +1 -1
- package/dist/types/dictionaryManipulator/qualifiedDictionaryDeclaration.d.ts +41 -0
- package/dist/types/dictionaryManipulator/qualifiedDictionaryDeclaration.d.ts.map +1 -0
- package/dist/types/dictionaryManipulator/qualifiedDynamicLoader.d.ts +93 -0
- package/dist/types/dictionaryManipulator/qualifiedDynamicLoader.d.ts.map +1 -0
- package/dist/types/index.d.ts +5 -2
- package/dist/types/interpreter/getContent/plugins.d.ts +10 -0
- package/dist/types/interpreter/getContent/plugins.d.ts.map +1 -1
- package/dist/types/interpreter/getNesting.optimized.d.ts +33 -0
- package/dist/types/interpreter/getNesting.optimized.d.ts.map +1 -0
- package/package.json +7 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"qualifiedDictionary.cjs","names":[],"sources":["../../../src/dictionaryManipulator/qualifiedDictionary.ts"],"sourcesContent":["import type {\n Dictionary,\n DictionaryQualifierType,\n DictionarySelector,\n DictionaryVariantChain,\n DictionaryVariantValue,\n ProviderVariant,\n ProviderVariantMap,\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 * Identity of the implicit fallback variant. A selector that pins no variant\n * resolves to it, and a variant that declares no entry of its own falls back to\n * it — so a key only has to ship the entries that actually differ.\n */\nexport const DEFAULT_VARIANT_ID = 'default';\n\n/**\n * Characters kept verbatim in an encoded qualifier segment. Everything else is\n * percent-encoded so a segment can never contain the composite-id separator\n * (`/`), path-hostile characters (`\\` `:` `*` `?` `\"` `<` `>` `|`, control\n * chars), or characters that would break the generated loader modules (`'`).\n */\nconst SEGMENT_UNSAFE_CHARS = /[^A-Za-z0-9._&=-]/g;\n\n/**\n * Stricter set for the components of an object variant: also encodes `&` and\n * `=` so the `field=value&field=value` serialization stays unambiguous.\n */\nconst COMPONENT_UNSAFE_CHARS = /[^A-Za-z0-9._-]/g;\n\n/** Percent-encodes one UTF-16 code unit as a fixed-width `%XXXX` run. */\nconst percentEncodeChar = (char: string): string =>\n `%${char.charCodeAt(0).toString(16).toUpperCase().padStart(4, '0')}`;\n\nconst encodeSegmentText = (raw: string, unsafeChars: RegExp): string => {\n // Bare '%' cannot be produced by encoding (every encoded run is %XXXX),\n // so it is a safe stand-in for the empty string.\n if (raw === '') return '%';\n\n const encoded = raw.replace(unsafeChars, percentEncodeChar);\n\n // '.' and '..' are path navigation on every filesystem — encode the dots.\n if (encoded === '.' || encoded === '..') {\n return encoded.replace(/\\./g, '%002E');\n }\n\n return encoded;\n};\n\n/**\n * Canonical serialization of a single variant value into its identity string —\n * the variant segment of a composite id, the chunk directory name in dynamic\n * mode, 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 * Characters that are unsafe in file paths or generated code are\n * percent-encoded (fixed-width `%XXXX` runs, injective). Common names —\n * letters, digits, `-` `_` `.` — are left untouched. Both the declaration and\n * the selector go through this function, so encoding never affects matching.\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: DictionaryVariantValue | undefined\n): string => {\n if (variant === undefined) return DEFAULT_VARIANT_ID;\n if (typeof variant === 'string') {\n return encodeSegmentText(variant, SEGMENT_UNSAFE_CHARS);\n }\n\n return Object.keys(variant)\n .sort()\n .map(\n (field) =>\n `${encodeSegmentText(field, COMPONENT_UNSAFE_CHARS)}=${encodeSegmentText(String(variant[field]), COMPONENT_UNSAFE_CHARS)}`\n )\n .join('&');\n};\n\n/**\n * Normalizes the `variant` field of a dictionary into the list of variant ids\n * the declaration registers under. A single value yields one id; an **array**\n * fans out into one id per element (duplicates collapsed). Returns `undefined`\n * when the dictionary does not declare the variant dimension (no `variant`\n * field, or an empty array).\n */\nexport const getVariantIds = (\n variant: Dictionary['variant']\n): string[] | undefined => {\n if (variant === undefined) return undefined;\n\n const values = Array.isArray(variant) ? variant : [variant];\n if (values.length === 0) return undefined;\n\n return [...new Set(values.map(serializeVariant))];\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 (getVariantIds(dictionary.variant) !== undefined) {\n declaredQualifiers.push('variant');\n }\n if (typeof dictionary.item === 'number') declaredQualifiers.push('item');\n\n return declaredQualifiers;\n};\n\n/**\n * Returns the qualifier identifiers of a dictionary for the given qualifier\n * dimension — the candidate segments of the composite entry ids.\n *\n * - 'variant' → the serialized variant id(s); an array variant yields one id\n * per element (declaration-side fan-out)\n * - 'item' → the item index as a single-element list\n */\nexport const getDictionaryQualifierIds = (\n dictionary: Dictionary,\n qualifierType: DictionaryQualifierType\n): string[] | undefined => {\n if (qualifierType === 'variant') {\n return getVariantIds(dictionary.variant);\n }\n return dictionary.item === undefined ? undefined : [String(dictionary.item)];\n};\n\n/**\n * Builds every composite entry id of a dictionary — the cartesian product of\n * its per-dimension id lists, joined in canonical order. A dictionary with a\n * plain (non-array) variant yields exactly one id; an array variant fans out\n * into one id per element. `undefined` when a dimension of the set is missing.\n */\nexport const getDictionaryCompositeIds = (\n dictionary: Dictionary,\n qualifierTypes: DictionaryQualifierType[]\n): string[] | undefined => {\n let compositeIds: string[] = [''];\n\n for (const qualifierType of qualifierTypes) {\n const ids = getDictionaryQualifierIds(dictionary, qualifierType);\n if (ids === undefined) return undefined;\n\n compositeIds = compositeIds.flatMap((prefix) =>\n ids.map((id) =>\n prefix === '' ? id : `${prefix}${COMPOSITE_ID_SEPARATOR}${id}`\n )\n );\n }\n\n return compositeIds;\n};\n\n/**\n * Serializes the variant coordinate of a selector into the ordered list of\n * candidate ids to try, so a single value and a preference chain share one code\n * path downstream.\n *\n * - `undefined` → `['default']`\n * - a single value → its one serialization\n * - a chain → one serialization per entry, order preserved\n *\n * An empty chain is treated as \"no variant pinned\" (`['default']`) rather than\n * as an unsatisfiable request.\n */\nexport const serializeVariantChain = (\n variant: DictionaryVariantChain | undefined\n): string[] => {\n if (!Array.isArray(variant)) {\n return [serializeVariant(variant as DictionaryVariantValue | undefined)];\n }\n\n if (variant.length === 0) return [DEFAULT_VARIANT_ID];\n\n return variant.map(serializeVariant);\n};\n\n/**\n * Resolves the variant id a selector actually targets among the ids a key\n * declares — the sparse-override fallback.\n *\n * Candidates are tried in order and the first one the key declares wins.\n * Otherwise the key falls back to its `default` entry, so a variant only has to\n * be declared where its wording differs. When the key declares no `default`\n * either, the first candidate is returned unchanged and the caller resolves to\n * `null` / `[]`.\n *\n * @param requestedVariantIds - The serialized ids the selector asks for, in\n * preference order (a single value is a 1-element\n * list).\n * @param isVariantIdDeclared - Whether the key declares an entry for an id.\n */\nconst resolveEffectiveVariantId = (\n requestedVariantIds: string[],\n isVariantIdDeclared: (variantId: string) => boolean\n): string => {\n for (const requestedVariantId of requestedVariantIds) {\n if (isVariantIdDeclared(requestedVariantId)) return requestedVariantId;\n }\n\n return isVariantIdDeclared(DEFAULT_VARIANT_ID)\n ? DEFAULT_VARIANT_ID\n : (requestedVariantIds[0] ?? DEFAULT_VARIANT_ID);\n};\n\n/**\n * Tests whether a composite entry id matches a selector across every declared\n * dimension. Segments are compared in their encoded form (both the stored id\n * and the selector go through {@link serializeVariant}). The `item` dimension\n * matches any value when the selector does not provide one (open collection\n * axis); the `variant` dimension is compared against the already-resolved\n * effective id, so the fallback is applied consistently across dimensions.\n */\nconst compositeIdMatchesSelector = (\n compositeId: string,\n qualifierTypes: DictionaryQualifierType[],\n selector: DictionarySelector | undefined,\n effectiveVariantId: string\n): boolean => {\n const segments = compositeId.split(COMPOSITE_ID_SEPARATOR);\n\n return qualifierTypes.every((qualifierType, index) => {\n if (qualifierType === 'variant') {\n return segments[index] === effectiveVariantId;\n }\n\n // qualifierType === 'item'\n return (\n selector?.item === undefined || segments[index] === String(selector.item)\n );\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 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 (encoded) form, e.g. `'id=abc&userId=123'` — matching happens\n * on the composite id segments, never on this reconstructed field.\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, and falls back\n * to it when the selected variant declares no entry of its own; an object\n * variant resolves only when the selector provides an equal object (or, again,\n * through the `default` fallback)\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 compositeIds = Object.keys(content);\n const variantIndex = qualifierTypes.indexOf('variant');\n\n const effectiveVariantId =\n variantIndex === -1\n ? DEFAULT_VARIANT_ID\n : resolveEffectiveVariantId(\n serializeVariantChain(selector?.variant),\n (variantId) =>\n compositeIds.some(\n (compositeId) =>\n compositeId.split(COMPOSITE_ID_SEPARATOR)[variantIndex] ===\n variantId\n )\n );\n\n const matchedEntries = compositeIds\n .filter((compositeId) =>\n compositeIdMatchesSelector(\n compositeId,\n qualifierTypes,\n selector,\n effectiveVariantId\n )\n )\n .map((compositeId) =>\n reconstructQualifiedEntry(dictionaryOrGroup, compositeId)\n );\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 * Resolves the variant a provider pins for one dictionary key.\n *\n * A string or a chain applies to every key as-is. A plain object is the per-key\n * map: the entry for `dictionaryKey` wins, falling back to the reserved\n * `default` entry, and `undefined` when neither is present (the key then\n * resolves to its own `default` variant, i.e. the behaviour without a provider\n * variant at all).\n *\n * A plain object is **always** the map here — never a structured variant value,\n * which is why a structured variant has to be nested (`{ default: { id } }`).\n *\n * @param providerVariant - The `variant` prop of the surrounding provider.\n * @param dictionaryKey - The key being read.\n */\nexport const resolveProviderVariant = (\n providerVariant: ProviderVariant | undefined,\n dictionaryKey: string\n): DictionaryVariantChain | undefined => {\n if (providerVariant === undefined) return undefined;\n\n if (typeof providerVariant === 'string' || Array.isArray(providerVariant)) {\n return providerVariant as DictionaryVariantChain;\n }\n\n const variantMap = providerVariant as ProviderVariantMap;\n\n return variantMap[dictionaryKey] ?? variantMap[DEFAULT_VARIANT_ID];\n};\n\n/**\n * Builds the effective second argument of a dictionary read by layering the\n * provider defaults under the call-site one — the single place the `locale` and\n * `variant` context defaults are applied, shared by every framework binding.\n *\n * Precedence, per dimension independently:\n * - a call-site selector always wins; `{ variant: 'x' }` **replaces** the\n * provider chain rather than extending it\n * - otherwise the provider value applies\n *\n * Returns a bare locale (not a selector object) whenever no variant is in play,\n * so the existing fast path — and the cache keys built from it — are unchanged\n * for projects that never use variants.\n */\nexport const resolveDictionaryArgument = (params: {\n localeOrSelector?: LocalesValues | DictionarySelector;\n contextLocale?: LocalesValues;\n contextVariant?: ProviderVariant;\n dictionaryKey: string;\n}): LocalesValues | DictionarySelector | undefined => {\n const { localeOrSelector, contextLocale, contextVariant, dictionaryKey } =\n params;\n\n const callSelector =\n typeof localeOrSelector === 'object' && localeOrSelector !== null\n ? localeOrSelector\n : undefined;\n\n const callLocale = callSelector\n ? callSelector.locale\n : (localeOrSelector as LocalesValues | undefined);\n\n // The context locale is typed as widely as the runtime allows, while a\n // selector narrows `locale` to the declared ones — the value is the same.\n const locale = (callLocale ?? contextLocale) as DictionarySelector['locale'];\n\n // A call-site variant is authoritative; the provider only fills the gap.\n const variant =\n callSelector?.variant ??\n resolveProviderVariant(contextVariant, dictionaryKey);\n\n if (variant === undefined) {\n // Nothing to add: keep the argument in its original shape so the identity\n // built from it stays byte-identical to the pre-variant behaviour.\n return callSelector\n ? { ...callSelector, locale }\n : (locale as LocalesValues | undefined);\n }\n\n return { ...callSelector, locale, variant };\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 ? serializeVariantChain(\n value as DictionaryVariantChain | undefined\n ).join(',')\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 // A variant with no chunk of its own falls back to the `default` chunk, so\n // only the entries that actually differ need to be emitted.\n const segment =\n dimension === 'variant'\n ? resolveEffectiveVariantId(\n serializeVariantChain(selector?.variant),\n (variantId) => tree[variantId] !== undefined\n )\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` descends by the serialized id, falling back to\n * `default` when the selected variant has no chunk of its own, and `item`\n * either narrows to the selected index or — when no item is given — expands\n * into every sibling chunk (the collection axis). 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.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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"}
|
|
1
|
+
{"version":3,"file":"qualifiedDictionary.cjs","names":[],"sources":["../../../src/dictionaryManipulator/qualifiedDictionary.ts"],"sourcesContent":["import type {\n Dictionary,\n DictionaryQualifierType,\n DictionarySelector,\n DictionaryVariantChain,\n DictionaryVariantValue,\n ProviderVariant,\n ProviderVariantMap,\n QualifiedDictionaryGroup,\n} from '@intlayer/types/dictionary';\nimport type { LocalesValues } from '@intlayer/types/module_augmentation';\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 * Identity of the implicit fallback variant. A selector that pins no variant\n * resolves to it, and a variant that declares no entry of its own falls back to\n * it — so a key only has to ship the entries that actually differ.\n */\nexport const DEFAULT_VARIANT_ID = 'default';\n\n/**\n * Characters kept verbatim in an encoded qualifier segment. Everything else is\n * percent-encoded so a segment can never contain the composite-id separator\n * (`/`), path-hostile characters (`\\` `:` `*` `?` `\"` `<` `>` `|`, control\n * chars), or characters that would break the generated loader modules (`'`).\n */\nconst SEGMENT_UNSAFE_CHARS = /[^A-Za-z0-9._&=-]/g;\n\n/**\n * Stricter set for the components of an object variant: also encodes `&` and\n * `=` so the `field=value&field=value` serialization stays unambiguous.\n */\nconst COMPONENT_UNSAFE_CHARS = /[^A-Za-z0-9._-]/g;\n\n/** Percent-encodes one UTF-16 code unit as a fixed-width `%XXXX` run. */\nconst percentEncodeChar = (char: string): string =>\n `%${char.charCodeAt(0).toString(16).toUpperCase().padStart(4, '0')}`;\n\nconst encodeSegmentText = (raw: string, unsafeChars: RegExp): string => {\n // Bare '%' cannot be produced by encoding (every encoded run is %XXXX),\n // so it is a safe stand-in for the empty string.\n if (raw === '') return '%';\n\n const encoded = raw.replace(unsafeChars, percentEncodeChar);\n\n // '.' and '..' are path navigation on every filesystem — encode the dots.\n if (encoded === '.' || encoded === '..') {\n return encoded.replace(/\\./g, '%002E');\n }\n\n return encoded;\n};\n\n/**\n * Canonical serialization of a single variant value into its identity string —\n * the variant segment of a composite id, the chunk directory name in dynamic\n * mode, 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 * Characters that are unsafe in file paths or generated code are\n * percent-encoded (fixed-width `%XXXX` runs, injective). Common names —\n * letters, digits, `-` `_` `.` — are left untouched. Both the declaration and\n * the selector go through this function, so encoding never affects matching.\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: DictionaryVariantValue | undefined\n): string => {\n if (variant === undefined) return DEFAULT_VARIANT_ID;\n if (typeof variant === 'string') {\n return encodeSegmentText(variant, SEGMENT_UNSAFE_CHARS);\n }\n\n return Object.keys(variant)\n .sort()\n .map(\n (field) =>\n `${encodeSegmentText(field, COMPONENT_UNSAFE_CHARS)}=${encodeSegmentText(String(variant[field]), COMPONENT_UNSAFE_CHARS)}`\n )\n .join('&');\n};\n\n/**\n * Serializes the variant coordinate of a selector into the ordered list of\n * candidate ids to try, so a single value and a preference chain share one code\n * path downstream.\n *\n * - `undefined` → `['default']`\n * - a single value → its one serialization\n * - a chain → one serialization per entry, order preserved\n *\n * An empty chain is treated as \"no variant pinned\" (`['default']`) rather than\n * as an unsatisfiable request.\n */\nexport const serializeVariantChain = (\n variant: DictionaryVariantChain | undefined\n): string[] => {\n if (!Array.isArray(variant)) {\n return [serializeVariant(variant as DictionaryVariantValue | undefined)];\n }\n\n if (variant.length === 0) return [DEFAULT_VARIANT_ID];\n\n return variant.map(serializeVariant);\n};\n\n/**\n * Resolves the variant id a selector actually targets among the ids a key\n * declares — the sparse-override fallback.\n *\n * Candidates are tried in order and the first one the key declares wins.\n * Otherwise the key falls back to its `default` entry, so a variant only has to\n * be declared where its wording differs. When the key declares no `default`\n * either, the first candidate is returned unchanged and the caller resolves to\n * `null` / `[]`.\n *\n * @param requestedVariantIds - The serialized ids the selector asks for, in\n * preference order (a single value is a 1-element\n * list).\n * @param isVariantIdDeclared - Whether the key declares an entry for an id.\n */\nexport const resolveEffectiveVariantId = (\n requestedVariantIds: string[],\n isVariantIdDeclared: (variantId: string) => boolean\n): string => {\n for (const requestedVariantId of requestedVariantIds) {\n if (isVariantIdDeclared(requestedVariantId)) return requestedVariantId;\n }\n\n return isVariantIdDeclared(DEFAULT_VARIANT_ID)\n ? DEFAULT_VARIANT_ID\n : (requestedVariantIds[0] ?? DEFAULT_VARIANT_ID);\n};\n\n/**\n * Tests whether a composite entry id matches a selector across every declared\n * dimension. Segments are compared in their encoded form (both the stored id\n * and the selector go through {@link serializeVariant}). The `item` dimension\n * matches any value when the selector does not provide one (open collection\n * axis); the `variant` dimension is compared against the already-resolved\n * effective id, so the fallback is applied consistently across dimensions.\n */\nconst compositeIdMatchesSelector = (\n compositeId: string,\n qualifierTypes: DictionaryQualifierType[],\n selector: DictionarySelector | undefined,\n effectiveVariantId: string\n): boolean => {\n const segments = compositeId.split(COMPOSITE_ID_SEPARATOR);\n\n return qualifierTypes.every((qualifierType, index) => {\n if (qualifierType === 'variant') {\n return segments[index] === effectiveVariantId;\n }\n\n // qualifierType === 'item'\n return (\n selector?.item === undefined || segments[index] === String(selector.item)\n );\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 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 (encoded) form, e.g. `'id=abc&userId=123'` — matching happens\n * on the composite id segments, never on this reconstructed field.\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, and falls back\n * to it when the selected variant declares no entry of its own; an object\n * variant resolves only when the selector provides an equal object (or, again,\n * through the `default` fallback)\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 compositeIds = Object.keys(content);\n const variantIndex = qualifierTypes.indexOf('variant');\n\n const effectiveVariantId =\n variantIndex === -1\n ? DEFAULT_VARIANT_ID\n : resolveEffectiveVariantId(\n serializeVariantChain(selector?.variant),\n (variantId) =>\n compositeIds.some(\n (compositeId) =>\n compositeId.split(COMPOSITE_ID_SEPARATOR)[variantIndex] ===\n variantId\n )\n );\n\n const matchedEntries = compositeIds\n .filter((compositeId) =>\n compositeIdMatchesSelector(\n compositeId,\n qualifierTypes,\n selector,\n effectiveVariantId\n )\n )\n .map((compositeId) =>\n reconstructQualifiedEntry(dictionaryOrGroup, compositeId)\n );\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 * Resolves the variant a provider pins for one dictionary key.\n *\n * A string or a chain applies to every key as-is. A plain object is the per-key\n * map: the entry for `dictionaryKey` wins, falling back to the reserved\n * `default` entry, and `undefined` when neither is present (the key then\n * resolves to its own `default` variant, i.e. the behaviour without a provider\n * variant at all).\n *\n * A plain object is **always** the map here — never a structured variant value,\n * which is why a structured variant has to be nested (`{ default: { id } }`).\n *\n * @param providerVariant - The `variant` prop of the surrounding provider.\n * @param dictionaryKey - The key being read.\n */\nexport const resolveProviderVariant = (\n providerVariant: ProviderVariant | undefined,\n dictionaryKey: string\n): DictionaryVariantChain | undefined => {\n if (providerVariant === undefined) return undefined;\n\n if (typeof providerVariant === 'string' || Array.isArray(providerVariant)) {\n return providerVariant as DictionaryVariantChain;\n }\n\n const variantMap = providerVariant as ProviderVariantMap;\n\n return variantMap[dictionaryKey] ?? variantMap[DEFAULT_VARIANT_ID];\n};\n\n/**\n * Builds the effective second argument of a dictionary read by layering the\n * provider defaults under the call-site one — the single place the `locale` and\n * `variant` context defaults are applied, shared by every framework binding.\n *\n * Precedence, per dimension independently:\n * - a call-site selector always wins; `{ variant: 'x' }` **replaces** the\n * provider chain rather than extending it\n * - otherwise the provider value applies\n *\n * Returns a bare locale (not a selector object) whenever no variant is in play,\n * so the existing fast path — and the cache keys built from it — are unchanged\n * for projects that never use variants.\n */\nexport const resolveDictionaryArgument = (params: {\n localeOrSelector?: LocalesValues | DictionarySelector;\n contextLocale?: LocalesValues;\n contextVariant?: ProviderVariant;\n dictionaryKey: string;\n}): LocalesValues | DictionarySelector | undefined => {\n const { localeOrSelector, contextLocale, contextVariant, dictionaryKey } =\n params;\n\n const callSelector =\n typeof localeOrSelector === 'object' && localeOrSelector !== null\n ? localeOrSelector\n : undefined;\n\n const callLocale = callSelector\n ? callSelector.locale\n : (localeOrSelector as LocalesValues | undefined);\n\n // The context locale is typed as widely as the runtime allows, while a\n // selector narrows `locale` to the declared ones — the value is the same.\n const locale = (callLocale ?? contextLocale) as DictionarySelector['locale'];\n\n // A call-site variant is authoritative; the provider only fills the gap.\n const variant =\n callSelector?.variant ??\n resolveProviderVariant(contextVariant, dictionaryKey);\n\n if (variant === undefined) {\n // Nothing to add: keep the argument in its original shape so the identity\n // built from it stays byte-identical to the pre-variant behaviour.\n return callSelector\n ? { ...callSelector, locale }\n : (locale as LocalesValues | undefined);\n }\n\n return { ...callSelector, locale, variant };\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 ? serializeVariantChain(\n value as DictionaryVariantChain | undefined\n ).join(',')\n : String(value);\n return `${selectorKey}:${serialized}`;\n })\n .join('|');\n};\n"],"mappings":";;;;;;AAgBA,MAAa,yBAAyB;;;;;;AAOtC,MAAa,qBAAqB;;;;;;;AAQlC,MAAM,uBAAuB;;;;;AAM7B,MAAM,yBAAyB;;AAG/B,MAAM,qBAAqB,SACzB,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,YAAY,CAAC,CAAC,SAAS,GAAG,GAAG;AAEnE,MAAM,qBAAqB,KAAa,gBAAgC;CAGtE,IAAI,QAAQ,IAAI,OAAO;CAEvB,MAAM,UAAU,IAAI,QAAQ,aAAa,iBAAiB;CAG1D,IAAI,YAAY,OAAO,YAAY,MACjC,OAAO,QAAQ,QAAQ,OAAO,OAAO;CAGvC,OAAO;AACT;;;;;;;;;;;;;;;;;;;AAoBA,MAAa,oBACX,YACW;CACX,IAAI,YAAY,QAAW,OAAO;CAClC,IAAI,OAAO,YAAY,UACrB,OAAO,kBAAkB,SAAS,oBAAoB;CAGxD,OAAO,OAAO,KAAK,OAAO,CAAC,CACxB,KAAK,CAAC,CACN,KACE,UACC,GAAG,kBAAkB,OAAO,sBAAsB,EAAE,GAAG,kBAAkB,OAAO,QAAQ,MAAM,GAAG,sBAAsB,GAC3H,CAAC,CACA,KAAK,GAAG;AACb;;;;;;;;;;;;;AAcA,MAAa,yBACX,YACa;CACb,IAAI,CAAC,MAAM,QAAQ,OAAO,GACxB,OAAO,CAAC,iBAAiB,OAA6C,CAAC;CAGzE,IAAI,QAAQ,WAAW,GAAG,OAAO,CAAC,kBAAkB;CAEpD,OAAO,QAAQ,IAAI,gBAAgB;AACrC;;;;;;;;;;;;;;;;AAiBA,MAAa,6BACX,qBACA,wBACW;CACX,KAAK,MAAM,sBAAsB,qBAC/B,IAAI,oBAAoB,kBAAkB,GAAG,OAAO;CAGtD,OAAO,6BAAsC,IACzC,qBACC,oBAAoB;AAC3B;;;;;;;;;AAUA,MAAM,8BACJ,aACA,gBACA,UACA,uBACY;CACZ,MAAM,WAAW,YAAY,SAA4B;CAEzD,OAAO,eAAe,OAAO,eAAe,UAAU;EACpD,IAAI,kBAAkB,WACpB,OAAO,SAAS,WAAW;EAI7B,OACE,UAAU,SAAS,UAAa,SAAS,WAAW,OAAO,SAAS,IAAI;CAE5E,CAAC;AACH;;;;;;AAOA,MAAa,8BACX,UAEA,OAAO,UAAU,YACjB,UAAU,QACV,oBAAoB,SACpB,MAAM,QAAS,MAAsC,cAAc,KACnE,aAAa;;;;;;;;;;;;AAaf,MAAa,6BACX,OACA,gBACe;CACf,MAAM,WAAW,YAAY,SAA4B;CAEzD,MAAM,QAAQ;EACZ,KAAK,MAAM;EACX,SAAS,MAAM,QAAQ;CACzB;CAEA,MAAM,eAAe,SAAS,eAAe,UAAU;EACrD,IAAI,kBAAkB,WACpB,MAAM,UAAU,SAAS;OACpB,IAAI,kBAAkB,QAC3B,MAAM,OAAO,OAAO,SAAS,MAAM;CAEvC,CAAC;CAED,OAAO;AACT;;;;;;;;;;;;;;;;AAiBA,MAAa,8BACX,mBACA,aACqC;CACrC,IAAI,CAAC,2BAA2B,iBAAiB,GAC/C,OAAO;CAGT,MAAM,EAAE,gBAAgB,YAAY;CAEpC,MAAM,eACJ,eAAe,SAAS,MAAM,KAAK,UAAU,SAAS;CAExD,MAAM,eAAe,OAAO,KAAK,OAAO;CACxC,MAAM,eAAe,eAAe,QAAQ,SAAS;CAErD,MAAM,qBACJ,iBAAiB,KACb,qBACA,0BACE,sBAAsB,UAAU,OAAO,IACtC,cACC,aAAa,MACV,gBACC,YAAY,SAA4B,CAAC,CAAC,kBAC1C,SACJ,CACJ;CAEN,MAAM,iBAAiB,aACpB,QAAQ,gBACP,2BACE,aACA,gBACA,UACA,kBACF,CACF,CAAC,CACA,KAAK,gBACJ,0BAA0B,mBAAmB,WAAW,CAC1D;CAEF,IAAI,cACF,OAAO,eAAe,MACnB,MAAM,WAAW,KAAK,QAAQ,MAAM,MAAM,QAAQ,EACrD;CAGF,OAAO,eAAe,MAAM;AAC9B;;;;;AAMA,MAAa,2BACX,qBACkD;CAClD,IAAI,OAAO,qBAAqB,YAAY,qBAAqB,MAC/D,OAAO;EACL,QAAQ,iBAAiB;EACzB,UAAU;CACZ;CAGF,OAAO,EAAE,QAAQ,iBAAiB;AACpC;;;;;;;;;;;;;;;;AAiBA,MAAa,0BACX,iBACA,kBACuC;CACvC,IAAI,oBAAoB,QAAW,OAAO;CAE1C,IAAI,OAAO,oBAAoB,YAAY,MAAM,QAAQ,eAAe,GACtE,OAAO;CAGT,MAAM,aAAa;CAEnB,OAAO,WAAW,kBAAkB;AACtC;;;;;;;;;;;;;;;AAgBA,MAAa,6BAA6B,WAKY;CACpD,MAAM,EAAE,kBAAkB,eAAe,gBAAgB,kBACvD;CAEF,MAAM,eACJ,OAAO,qBAAqB,YAAY,qBAAqB,OACzD,mBACA;CAQN,MAAM,UANa,eACf,aAAa,SACZ,qBAIyB;CAG9B,MAAM,UACJ,cAAc,WACd,uBAAuB,gBAAgB,aAAa;CAEtD,IAAI,YAAY,QAGd,OAAO,eACH;EAAE,GAAG;EAAc;CAAO,IACzB;CAGP,OAAO;EAAE,GAAG;EAAc;EAAQ;CAAQ;AAC5C;;;;;AAMA,MAAa,iCACX,aACW;CACX,IAAI,CAAC,UAAU,OAAO;CAEtB,OAAO,OAAO,KAAK,QAAQ,CAAC,CACzB,QAAQ,gBAAgB,gBAAgB,QAAQ,CAAC,CACjD,KAAK,CAAC,CACN,KAAK,gBAAgB;EACpB,MAAM,QAAQ,SAAS;EAOvB,OAAO,GAAG,YAAY,GALpB,gBAAgB,YACZ,sBACE,KACF,CAAC,CAAC,KAAK,GAAG,IACV,OAAO,KAAK;CAEpB,CAAC,CAAC,CACD,KAAK,GAAG;AACb"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"qualifiedDictionary.test-d.cjs","names":[],"sources":["../../../src/dictionaryManipulator/qualifiedDictionary.test-d.ts"],"sourcesContent":["import type {\n DictionarySelectorForGroup,\n ResolveQualifiedDictionaryContent,\n} from '@intlayer/types/dictionary';\nimport { describe, expectTypeOf, it } from 'vitest';\n\n/**\n * Compile-time counterpart of `qualifiedDictionary.test.ts`. The runtime\n * resolver and `ResolveQualifiedDictionaryContent` implement the same rules\n * twice — once in value space, once in type space — so a divergence between\n * them is invisible to the runtime suite. These assertions pin the type side.\n *\n * Groups are written the way `createTypes` emits them (`as const`, hence\n * readonly tuples of qualifier dimensions and literal composite-id keys).\n */\n\ntype LessonGroup = {\n key: 'lesson';\n qualifierTypes: readonly ['variant'];\n content: {\n default: { title: 'Lesson'; teacher: 'Teacher' };\n preschool: { title: 'Lesson'; teacher: 'Pedagogue' };\n };\n};\n\ntype NoDefaultGroup = {\n key: 'promoOnly';\n qualifierTypes: readonly ['variant'];\n content: { promo: { title: 'Promo' } };\n};\n\ntype BannerGroup = {\n key: 'banner';\n qualifierTypes: readonly ['variant', 'item'];\n content: {\n 'default/1': { title: 'D1' };\n 'promo/1': { title: 'P1' };\n 'promo/2': { title: 'P2' };\n };\n};\n\ntype FaqGroup = {\n key: 'faq';\n qualifierTypes: readonly ['item'];\n content: { '1': { question: 'Q1' }; '2': { question: 'Q2' } };\n};\n\ntype ProductGroup = {\n key: 'product';\n qualifierTypes: readonly ['variant'];\n content: { 'id=abc&userId=123': { name: 'ABC' } };\n};\n\ndescribe('ResolveQualifiedDictionaryContent', () => {\n it('should resolve a plain dictionary to its content, ignoring the selector', () => {\n expectTypeOf<\n ResolveQualifiedDictionaryContent<{ key: 'home'; content: { a: 'b' } }>\n >().toEqualTypeOf<{ a: 'b' }>();\n });\n\n describe('variant', () => {\n it('should resolve to the default entry when no variant is selected', () => {\n expectTypeOf<\n ResolveQualifiedDictionaryContent<LessonGroup>\n >().toEqualTypeOf<{ title: 'Lesson'; teacher: 'Teacher' }>();\n });\n\n it('should resolve a locale-only selector like no selector', () => {\n expectTypeOf<\n ResolveQualifiedDictionaryContent<LessonGroup, { locale: 'sv' }>\n >().toEqualTypeOf<{ title: 'Lesson'; teacher: 'Teacher' }>();\n });\n\n it('should resolve a declared variant to its own entry', () => {\n expectTypeOf<\n ResolveQualifiedDictionaryContent<LessonGroup, { variant: 'preschool' }>\n >().toEqualTypeOf<{ title: 'Lesson'; teacher: 'Pedagogue' }>();\n });\n\n it('should fall back to the default entry for an undeclared variant', () => {\n expectTypeOf<\n ResolveQualifiedDictionaryContent<\n LessonGroup,\n { variant: 'upperSecondary' }\n >\n >().toEqualTypeOf<{ title: 'Lesson'; teacher: 'Teacher' }>();\n });\n\n it('should resolve to null when no default entry is declared', () => {\n expectTypeOf<\n ResolveQualifiedDictionaryContent<\n NoDefaultGroup,\n { variant: 'unknown' }\n >\n >().toEqualTypeOf<null>();\n });\n\n it('should resolve an object variant to its entry', () => {\n expectTypeOf<\n ResolveQualifiedDictionaryContent<\n ProductGroup,\n { variant: { id: 'abc'; userId: '123' } }\n >\n >().toEqualTypeOf<{ name: 'ABC' }>();\n });\n\n it('should resolve to null when an object-variant key has no default', () => {\n expectTypeOf<\n ResolveQualifiedDictionaryContent<ProductGroup>\n >().toEqualTypeOf<null>();\n });\n });\n\n describe('item', () => {\n it('should resolve to an array of every item when the axis is left open', () => {\n expectTypeOf<ResolveQualifiedDictionaryContent<FaqGroup>>().toEqualTypeOf<\n ({ question: 'Q1' } | { question: 'Q2' })[]\n >();\n });\n\n it('should narrow to the selected item', () => {\n expectTypeOf<\n ResolveQualifiedDictionaryContent<FaqGroup, { item: 2 }>\n >().toEqualTypeOf<{ question: 'Q2' }>();\n });\n });\n\n describe('composite (variant × item)', () => {\n it('should narrow to a single entry when both dimensions are pinned', () => {\n expectTypeOf<\n ResolveQualifiedDictionaryContent<\n BannerGroup,\n { variant: 'promo'; item: 2 }\n >\n >().toEqualTypeOf<{ title: 'P2' }>();\n });\n\n it('should fan the item axis out for the selected variant', () => {\n expectTypeOf<\n ResolveQualifiedDictionaryContent<BannerGroup, { variant: 'promo' }>\n >().toEqualTypeOf<({ title: 'P1' } | { title: 'P2' })[]>();\n });\n\n it('should fall back to the default variant then fan out its items', () => {\n expectTypeOf<\n ResolveQualifiedDictionaryContent<BannerGroup, { variant: 'unknown' }>\n >().toEqualTypeOf<{ title: 'D1' }[]>();\n });\n });\n});\n\ndescribe('DictionarySelectorForGroup', () => {\n /** Stands in for the project-wide variant vocabulary (`DeclaredVariants`). */\n type ProjectVariants = 'default' | 'preschool' | 'promo';\n\n type LessonSelector = DictionarySelectorForGroup<\n LessonGroup,\n ProjectVariants\n >;\n\n it('should accept a variant this key declares', () => {\n expectTypeOf<{ variant: 'preschool' }>().toExtend<LessonSelector>();\n });\n\n it('should accept a variant declared elsewhere in the project', () => {\n // `LessonGroup` has no `promo` entry — it resolves to `default` at runtime,\n // which is what makes one session-wide variant usable across every key.\n expectTypeOf<{ variant: 'promo' }>().toExtend<LessonSelector>();\n });\n\n it('should reject a variant no dictionary declares', () => {\n expectTypeOf<{ variant: 'promoo' }>().not.toExtend<LessonSelector>();\n });\n\n it('should reject an object variant on a key that declares none', () => {\n expectTypeOf<{ variant: { id: 'abc' } }>().not.toExtend<LessonSelector>();\n });\n\n describe('object variants', () => {\n // `ProductGroup` stores `'id=abc&userId=123'` — the serialized form of\n // `{ id: 'abc', userId: '123' }`.\n type ProductSelector = DictionarySelectorForGroup<\n ProductGroup,\n ProjectVariants\n >;\n\n it('should accept the object the key declares', () => {\n expectTypeOf<{\n variant: { id: 'abc'; userId: '123' };\n }>().toExtend<ProductSelector>();\n });\n\n it('should reject the serialized form as a string', () => {\n // `'id=abc&userId=123'` is a storage encoding, not part of the API.\n expectTypeOf<{\n variant: 'id=abc&userId=123';\n }>().not.toExtend<ProductSelector>();\n });\n\n it('should reject a partial or mismatched object', () => {\n expectTypeOf<{\n variant: { id: 'abc' };\n }>().not.toExtend<ProductSelector>();\n expectTypeOf<{\n variant: { id: 'abc'; userId: 'other' };\n }>().not.toExtend<ProductSelector>();\n });\n });\n\n describe('locale', () => {\n it('should accept a declared locale', () => {\n expectTypeOf<{ locale: 'fr' }>().toExtend<LessonSelector>();\n });\n\n it('should accept a widened `string` locale', () => {\n // A locale usually reaches this API as a router param (`params.locale`)\n // or a stored value, both typed `string`. Rejecting those would force a\n // cast at every call site, so the declared locales are suggestions only.\n expectTypeOf<{ locale: string }>().toExtend<LessonSelector>();\n });\n });\n});\n\ndescribe('DictionarySelectorForGroup — keys without a default entry', () => {\n type ProjectVariants = 'default' | 'preschool' | 'promo';\n\n // Declares only object variants: an undeclared name resolves to `null`, so\n // the project vocabulary must not be accepted here.\n type ProductSelector = DictionarySelectorForGroup<\n ProductGroup,\n ProjectVariants\n >;\n\n // Declares `promo` but no `default` — same reasoning.\n type NoDefaultSelector = DictionarySelectorForGroup<\n NoDefaultGroup,\n ProjectVariants\n >;\n\n it('should reject a project variant on a key with no default entry', () => {\n expectTypeOf<{ variant: 'promo' }>().not.toExtend<ProductSelector>();\n expectTypeOf<{ variant: 'default' }>().not.toExtend<ProductSelector>();\n expectTypeOf<{ variant: 'preschool' }>().not.toExtend<NoDefaultSelector>();\n });\n\n it('should still accept the names such a key declares itself', () => {\n expectTypeOf<{ variant: 'promo' }>().toExtend<NoDefaultSelector>();\n });\n});\n"],"mappings":";;;qBAqDS,2CAA2C;CAClD,eAAG,iFAAiF;EAClF,yBAEE,CAAC,CAAC,cAA0B;CAChC,CAAC;CAED,qBAAS,iBAAiB;EACxB,eAAG,yEAAyE;GAC1E,yBAEE,CAAC,CAAC,cAAuD;EAC7D,CAAC;EAED,eAAG,gEAAgE;GACjE,yBAEE,CAAC,CAAC,cAAuD;EAC7D,CAAC;EAED,eAAG,4DAA4D;GAC7D,yBAEE,CAAC,CAAC,cAAyD;EAC/D,CAAC;EAED,eAAG,yEAAyE;GAC1E,yBAKE,CAAC,CAAC,cAAuD;EAC7D,CAAC;EAED,eAAG,kEAAkE;GACnE,yBAKE,CAAC,CAAC,cAAoB;EAC1B,CAAC;EAED,eAAG,uDAAuD;GACxD,yBAKE,CAAC,CAAC,cAA+B;EACrC,CAAC;EAED,eAAG,0EAA0E;GAC3E,yBAEE,CAAC,CAAC,cAAoB;EAC1B,CAAC;CACH,CAAC;CAED,qBAAS,cAAc;EACrB,eAAG,6EAA6E;GAC9E,yBAA0D,CAAC,CAAC,cAE1D;EACJ,CAAC;EAED,eAAG,4CAA4C;GAC7C,yBAEE,CAAC,CAAC,cAAkC;EACxC,CAAC;CACH,CAAC;CAED,qBAAS,oCAAoC;EAC3C,eAAG,yEAAyE;GAC1E,yBAKE,CAAC,CAAC,cAA+B;EACrC,CAAC;EAED,eAAG,+DAA+D;GAChE,yBAEE,CAAC,CAAC,cAAqD;EAC3D,CAAC;EAED,eAAG,wEAAwE;GACzE,yBAEE,CAAC,CAAC,cAAiC;EACvC,CAAC;CACH,CAAC;AACH,CAAC;qBAEQ,oCAAoC;CAS3C,eAAG,mDAAmD;EACpD,yBAAuC,CAAC,CAAC,SAAyB;CACpE,CAAC;CAED,eAAG,mEAAmE;EAGpE,yBAAmC,CAAC,CAAC,SAAyB;CAChE,CAAC;CAED,eAAG,wDAAwD;EACzD,yBAAoC,CAAC,CAAC,IAAI,SAAyB;CACrE,CAAC;CAED,eAAG,qEAAqE;EACtE,yBAAyC,CAAC,CAAC,IAAI,SAAyB;CAC1E,CAAC;CAED,qBAAS,yBAAyB;EAQhC,eAAG,mDAAmD;GACpD,yBAEG,CAAC,CAAC,SAA0B;EACjC,CAAC;EAED,eAAG,uDAAuD;GAExD,yBAEG,CAAC,CAAC,IAAI,SAA0B;EACrC,CAAC;EAED,eAAG,sDAAsD;GACvD,yBAEG,CAAC,CAAC,IAAI,SAA0B;GACnC,yBAEG,CAAC,CAAC,IAAI,SAA0B;EACrC,CAAC;CACH,CAAC;CAED,qBAAS,gBAAgB;EACvB,eAAG,yCAAyC;GAC1C,yBAA+B,CAAC,CAAC,SAAyB;EAC5D,CAAC;EAED,eAAG,iDAAiD;GAIlD,yBAAiC,CAAC,CAAC,SAAyB;EAC9D,CAAC;CACH,CAAC;AACH,CAAC;qBAEQ,mEAAmE;CAgB1E,eAAG,wEAAwE;EACzE,yBAAmC,CAAC,CAAC,IAAI,SAA0B;EACnE,yBAAqC,CAAC,CAAC,IAAI,SAA0B;EACrE,yBAAuC,CAAC,CAAC,IAAI,SAA4B;CAC3E,CAAC;CAED,eAAG,kEAAkE;EACnE,yBAAmC,CAAC,CAAC,SAA4B;CACnE,CAAC;AACH,CAAC"}
|
|
1
|
+
{"version":3,"file":"qualifiedDictionary.test-d.cjs","names":["describe"],"sources":["../../../src/dictionaryManipulator/qualifiedDictionary.test-d.ts"],"sourcesContent":["import type {\n DictionarySelectorForGroup,\n ResolveQualifiedDictionaryContent,\n} from '@intlayer/types/dictionary';\nimport { describe, expectTypeOf, it } from 'vitest';\n\n/**\n * Compile-time counterpart of `qualifiedDictionary.test.ts`. The runtime\n * resolver and `ResolveQualifiedDictionaryContent` implement the same rules\n * twice — once in value space, once in type space — so a divergence between\n * them is invisible to the runtime suite. These assertions pin the type side.\n *\n * Groups are written the way `createTypes` emits them (`as const`, hence\n * readonly tuples of qualifier dimensions and literal composite-id keys).\n */\n\ntype LessonGroup = {\n key: 'lesson';\n qualifierTypes: readonly ['variant'];\n content: {\n default: { title: 'Lesson'; teacher: 'Teacher' };\n preschool: { title: 'Lesson'; teacher: 'Pedagogue' };\n };\n};\n\ntype NoDefaultGroup = {\n key: 'promoOnly';\n qualifierTypes: readonly ['variant'];\n content: { promo: { title: 'Promo' } };\n};\n\ntype BannerGroup = {\n key: 'banner';\n qualifierTypes: readonly ['variant', 'item'];\n content: {\n 'default/1': { title: 'D1' };\n 'promo/1': { title: 'P1' };\n 'promo/2': { title: 'P2' };\n };\n};\n\ntype FaqGroup = {\n key: 'faq';\n qualifierTypes: readonly ['item'];\n content: { '1': { question: 'Q1' }; '2': { question: 'Q2' } };\n};\n\ntype ProductGroup = {\n key: 'product';\n qualifierTypes: readonly ['variant'];\n content: { 'id=abc&userId=123': { name: 'ABC' } };\n};\n\ndescribe('ResolveQualifiedDictionaryContent', () => {\n it('should resolve a plain dictionary to its content, ignoring the selector', () => {\n expectTypeOf<\n ResolveQualifiedDictionaryContent<{ key: 'home'; content: { a: 'b' } }>\n >().toEqualTypeOf<{ a: 'b' }>();\n });\n\n describe('variant', () => {\n it('should resolve to the default entry when no variant is selected', () => {\n expectTypeOf<\n ResolveQualifiedDictionaryContent<LessonGroup>\n >().toEqualTypeOf<{ title: 'Lesson'; teacher: 'Teacher' }>();\n });\n\n it('should resolve a locale-only selector like no selector', () => {\n expectTypeOf<\n ResolveQualifiedDictionaryContent<LessonGroup, { locale: 'sv' }>\n >().toEqualTypeOf<{ title: 'Lesson'; teacher: 'Teacher' }>();\n });\n\n it('should resolve a declared variant to its own entry', () => {\n expectTypeOf<\n ResolveQualifiedDictionaryContent<LessonGroup, { variant: 'preschool' }>\n >().toEqualTypeOf<{ title: 'Lesson'; teacher: 'Pedagogue' }>();\n });\n\n it('should fall back to the default entry for an undeclared variant', () => {\n expectTypeOf<\n ResolveQualifiedDictionaryContent<\n LessonGroup,\n { variant: 'upperSecondary' }\n >\n >().toEqualTypeOf<{ title: 'Lesson'; teacher: 'Teacher' }>();\n });\n\n it('should resolve to null when no default entry is declared', () => {\n expectTypeOf<\n ResolveQualifiedDictionaryContent<\n NoDefaultGroup,\n { variant: 'unknown' }\n >\n >().toEqualTypeOf<null>();\n });\n\n it('should resolve an object variant to its entry', () => {\n expectTypeOf<\n ResolveQualifiedDictionaryContent<\n ProductGroup,\n { variant: { id: 'abc'; userId: '123' } }\n >\n >().toEqualTypeOf<{ name: 'ABC' }>();\n });\n\n it('should resolve to null when an object-variant key has no default', () => {\n expectTypeOf<\n ResolveQualifiedDictionaryContent<ProductGroup>\n >().toEqualTypeOf<null>();\n });\n });\n\n describe('item', () => {\n it('should resolve to an array of every item when the axis is left open', () => {\n expectTypeOf<ResolveQualifiedDictionaryContent<FaqGroup>>().toEqualTypeOf<\n ({ question: 'Q1' } | { question: 'Q2' })[]\n >();\n });\n\n it('should narrow to the selected item', () => {\n expectTypeOf<\n ResolveQualifiedDictionaryContent<FaqGroup, { item: 2 }>\n >().toEqualTypeOf<{ question: 'Q2' }>();\n });\n });\n\n describe('composite (variant × item)', () => {\n it('should narrow to a single entry when both dimensions are pinned', () => {\n expectTypeOf<\n ResolveQualifiedDictionaryContent<\n BannerGroup,\n { variant: 'promo'; item: 2 }\n >\n >().toEqualTypeOf<{ title: 'P2' }>();\n });\n\n it('should fan the item axis out for the selected variant', () => {\n expectTypeOf<\n ResolveQualifiedDictionaryContent<BannerGroup, { variant: 'promo' }>\n >().toEqualTypeOf<({ title: 'P1' } | { title: 'P2' })[]>();\n });\n\n it('should fall back to the default variant then fan out its items', () => {\n expectTypeOf<\n ResolveQualifiedDictionaryContent<BannerGroup, { variant: 'unknown' }>\n >().toEqualTypeOf<{ title: 'D1' }[]>();\n });\n });\n});\n\ndescribe('DictionarySelectorForGroup', () => {\n /** Stands in for the project-wide variant vocabulary (`DeclaredVariants`). */\n type ProjectVariants = 'default' | 'preschool' | 'promo';\n\n type LessonSelector = DictionarySelectorForGroup<\n LessonGroup,\n ProjectVariants\n >;\n\n it('should accept a variant this key declares', () => {\n expectTypeOf<{ variant: 'preschool' }>().toExtend<LessonSelector>();\n });\n\n it('should accept a variant declared elsewhere in the project', () => {\n // `LessonGroup` has no `promo` entry — it resolves to `default` at runtime,\n // which is what makes one session-wide variant usable across every key.\n expectTypeOf<{ variant: 'promo' }>().toExtend<LessonSelector>();\n });\n\n it('should reject a variant no dictionary declares', () => {\n expectTypeOf<{ variant: 'promoo' }>().not.toExtend<LessonSelector>();\n });\n\n it('should reject an object variant on a key that declares none', () => {\n expectTypeOf<{ variant: { id: 'abc' } }>().not.toExtend<LessonSelector>();\n });\n\n describe('object variants', () => {\n // `ProductGroup` stores `'id=abc&userId=123'` — the serialized form of\n // `{ id: 'abc', userId: '123' }`.\n type ProductSelector = DictionarySelectorForGroup<\n ProductGroup,\n ProjectVariants\n >;\n\n it('should accept the object the key declares', () => {\n expectTypeOf<{\n variant: { id: 'abc'; userId: '123' };\n }>().toExtend<ProductSelector>();\n });\n\n it('should reject the serialized form as a string', () => {\n // `'id=abc&userId=123'` is a storage encoding, not part of the API.\n expectTypeOf<{\n variant: 'id=abc&userId=123';\n }>().not.toExtend<ProductSelector>();\n });\n\n it('should reject a partial or mismatched object', () => {\n expectTypeOf<{\n variant: { id: 'abc' };\n }>().not.toExtend<ProductSelector>();\n expectTypeOf<{\n variant: { id: 'abc'; userId: 'other' };\n }>().not.toExtend<ProductSelector>();\n });\n });\n\n describe('locale', () => {\n it('should accept a declared locale', () => {\n expectTypeOf<{ locale: 'fr' }>().toExtend<LessonSelector>();\n });\n\n it('should accept a widened `string` locale', () => {\n // A locale usually reaches this API as a router param (`params.locale`)\n // or a stored value, both typed `string`. Rejecting those would force a\n // cast at every call site, so the declared locales are suggestions only.\n expectTypeOf<{ locale: string }>().toExtend<LessonSelector>();\n });\n });\n});\n\ndescribe('DictionarySelectorForGroup — keys without a default entry', () => {\n type ProjectVariants = 'default' | 'preschool' | 'promo';\n\n // Declares only object variants: an undeclared name resolves to `null`, so\n // the project vocabulary must not be accepted here.\n type ProductSelector = DictionarySelectorForGroup<\n ProductGroup,\n ProjectVariants\n >;\n\n // Declares `promo` but no `default` — same reasoning.\n type NoDefaultSelector = DictionarySelectorForGroup<\n NoDefaultGroup,\n ProjectVariants\n >;\n\n it('should reject a project variant on a key with no default entry', () => {\n expectTypeOf<{ variant: 'promo' }>().not.toExtend<ProductSelector>();\n expectTypeOf<{ variant: 'default' }>().not.toExtend<ProductSelector>();\n expectTypeOf<{ variant: 'preschool' }>().not.toExtend<NoDefaultSelector>();\n });\n\n it('should still accept the names such a key declares itself', () => {\n expectTypeOf<{ variant: 'promo' }>().toExtend<NoDefaultSelector>();\n });\n});\n"],"mappings":";;;IAqDAA,iBAAS,2CAA2C;CAClD,eAAG,iFAAiF;EAClF,yBAEE,CAAC,CAAC,cAA0B;CAChC,CAAC;CAED,qBAAS,iBAAiB;EACxB,eAAG,yEAAyE;GAC1E,yBAEE,CAAC,CAAC,cAAuD;EAC7D,CAAC;EAED,eAAG,gEAAgE;GACjE,yBAEE,CAAC,CAAC,cAAuD;EAC7D,CAAC;EAED,eAAG,4DAA4D;GAC7D,yBAEE,CAAC,CAAC,cAAyD;EAC/D,CAAC;EAED,eAAG,yEAAyE;GAC1E,yBAKE,CAAC,CAAC,cAAuD;EAC7D,CAAC;EAED,eAAG,kEAAkE;GACnE,yBAKE,CAAC,CAAC,cAAoB;EAC1B,CAAC;EAED,eAAG,uDAAuD;GACxD,yBAKE,CAAC,CAAC,cAA+B;EACrC,CAAC;EAED,eAAG,0EAA0E;GAC3E,yBAEE,CAAC,CAAC,cAAoB;EAC1B,CAAC;CACH,CAAC;CAED,qBAAS,cAAc;EACrB,eAAG,6EAA6E;GAC9E,yBAA0D,CAAC,CAAC,cAE1D;EACJ,CAAC;EAED,eAAG,4CAA4C;GAC7C,yBAEE,CAAC,CAAC,cAAkC;EACxC,CAAC;CACH,CAAC;CAED,qBAAS,oCAAoC;EAC3C,eAAG,yEAAyE;GAC1E,yBAKE,CAAC,CAAC,cAA+B;EACrC,CAAC;EAED,eAAG,+DAA+D;GAChE,yBAEE,CAAC,CAAC,cAAqD;EAC3D,CAAC;EAED,eAAG,wEAAwE;GACzE,yBAEE,CAAC,CAAC,cAAiC;EACvC,CAAC;CACH,CAAC;AACH,CAAC;IAEDA,iBAAS,oCAAoC;CAS3C,eAAG,mDAAmD;EACpD,yBAAuC,CAAC,CAAC,SAAyB;CACpE,CAAC;CAED,eAAG,mEAAmE;EAGpE,yBAAmC,CAAC,CAAC,SAAyB;CAChE,CAAC;CAED,eAAG,wDAAwD;EACzD,yBAAoC,CAAC,CAAC,IAAI,SAAyB;CACrE,CAAC;CAED,eAAG,qEAAqE;EACtE,yBAAyC,CAAC,CAAC,IAAI,SAAyB;CAC1E,CAAC;CAED,qBAAS,yBAAyB;EAQhC,eAAG,mDAAmD;GACpD,yBAEG,CAAC,CAAC,SAA0B;EACjC,CAAC;EAED,eAAG,uDAAuD;GAExD,yBAEG,CAAC,CAAC,IAAI,SAA0B;EACrC,CAAC;EAED,eAAG,sDAAsD;GACvD,yBAEG,CAAC,CAAC,IAAI,SAA0B;GACnC,yBAEG,CAAC,CAAC,IAAI,SAA0B;EACrC,CAAC;CACH,CAAC;CAED,qBAAS,gBAAgB;EACvB,eAAG,yCAAyC;GAC1C,yBAA+B,CAAC,CAAC,SAAyB;EAC5D,CAAC;EAED,eAAG,iDAAiD;GAIlD,yBAAiC,CAAC,CAAC,SAAyB;EAC9D,CAAC;CACH,CAAC;AACH,CAAC;IAEDA,iBAAS,mEAAmE;CAgB1E,eAAG,wEAAwE;EACzE,yBAAmC,CAAC,CAAC,IAAI,SAA0B;EACnE,yBAAqC,CAAC,CAAC,IAAI,SAA0B;EACrE,yBAAuC,CAAC,CAAC,IAAI,SAA4B;CAC3E,CAAC;CAED,eAAG,kEAAkE;EACnE,yBAAmC,CAAC,CAAC,SAA4B;CACnE,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
const require_dictionaryManipulator_qualifiedDictionary = require('./qualifiedDictionary.cjs');
|
|
3
|
+
|
|
4
|
+
//#region src/dictionaryManipulator/qualifiedDictionaryDeclaration.ts
|
|
5
|
+
/**
|
|
6
|
+
* Canonical order of qualifier dimensions. A key that declares both dimensions
|
|
7
|
+
* always nests them in this order, with `item` innermost so it can act as the
|
|
8
|
+
* collection (array) axis.
|
|
9
|
+
*/
|
|
10
|
+
const QUALIFIER_ORDER = ["variant", "item"];
|
|
11
|
+
/**
|
|
12
|
+
* Normalizes the `variant` field of a dictionary into the list of variant ids
|
|
13
|
+
* the declaration registers under. A single value yields one id; an **array**
|
|
14
|
+
* fans out into one id per element (duplicates collapsed). Returns `undefined`
|
|
15
|
+
* when the dictionary does not declare the variant dimension (no `variant`
|
|
16
|
+
* field, or an empty array).
|
|
17
|
+
*/
|
|
18
|
+
const getVariantIds = (variant) => {
|
|
19
|
+
if (variant === void 0) return void 0;
|
|
20
|
+
const values = Array.isArray(variant) ? variant : [variant];
|
|
21
|
+
if (values.length === 0) return void 0;
|
|
22
|
+
return [...new Set(values.map(require_dictionaryManipulator_qualifiedDictionary.serializeVariant))];
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Returns the qualifier dimensions declared on a dictionary, in canonical
|
|
26
|
+
* order (`variant → item`). Empty when the dictionary is unqualified
|
|
27
|
+
* (plain dictionary or shared base content of a qualified group).
|
|
28
|
+
*/
|
|
29
|
+
const getDictionaryQualifierTypes = (dictionary) => {
|
|
30
|
+
const declaredQualifiers = [];
|
|
31
|
+
if (getVariantIds(dictionary.variant) !== void 0) declaredQualifiers.push("variant");
|
|
32
|
+
if (typeof dictionary.item === "number") declaredQualifiers.push("item");
|
|
33
|
+
return declaredQualifiers;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Returns the qualifier identifiers of a dictionary for the given qualifier
|
|
37
|
+
* dimension — the candidate segments of the composite entry ids.
|
|
38
|
+
*
|
|
39
|
+
* - 'variant' → the serialized variant id(s); an array variant yields one id
|
|
40
|
+
* per element (declaration-side fan-out)
|
|
41
|
+
* - 'item' → the item index as a single-element list
|
|
42
|
+
*/
|
|
43
|
+
const getDictionaryQualifierIds = (dictionary, qualifierType) => {
|
|
44
|
+
if (qualifierType === "variant") return getVariantIds(dictionary.variant);
|
|
45
|
+
return dictionary.item === void 0 ? void 0 : [String(dictionary.item)];
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Builds every composite entry id of a dictionary — the cartesian product of
|
|
49
|
+
* its per-dimension id lists, joined in canonical order. A dictionary with a
|
|
50
|
+
* plain (non-array) variant yields exactly one id; an array variant fans out
|
|
51
|
+
* into one id per element. `undefined` when a dimension of the set is missing.
|
|
52
|
+
*/
|
|
53
|
+
const getDictionaryCompositeIds = (dictionary, qualifierTypes) => {
|
|
54
|
+
let compositeIds = [""];
|
|
55
|
+
for (const qualifierType of qualifierTypes) {
|
|
56
|
+
const ids = getDictionaryQualifierIds(dictionary, qualifierType);
|
|
57
|
+
if (ids === void 0) return void 0;
|
|
58
|
+
compositeIds = compositeIds.flatMap((prefix) => ids.map((id) => prefix === "" ? id : `${prefix}${"/"}${id}`));
|
|
59
|
+
}
|
|
60
|
+
return compositeIds;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
//#endregion
|
|
64
|
+
exports.QUALIFIER_ORDER = QUALIFIER_ORDER;
|
|
65
|
+
exports.getDictionaryCompositeIds = getDictionaryCompositeIds;
|
|
66
|
+
exports.getDictionaryQualifierIds = getDictionaryQualifierIds;
|
|
67
|
+
exports.getDictionaryQualifierTypes = getDictionaryQualifierTypes;
|
|
68
|
+
exports.getVariantIds = getVariantIds;
|
|
69
|
+
//# sourceMappingURL=qualifiedDictionaryDeclaration.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qualifiedDictionaryDeclaration.cjs","names":["serializeVariant"],"sources":["../../../src/dictionaryManipulator/qualifiedDictionaryDeclaration.ts"],"sourcesContent":["import type {\n Dictionary,\n DictionaryQualifierType,\n} from '@intlayer/types/dictionary';\nimport {\n COMPOSITE_ID_SEPARATOR,\n serializeVariant,\n} from './qualifiedDictionary';\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 * Normalizes the `variant` field of a dictionary into the list of variant ids\n * the declaration registers under. A single value yields one id; an **array**\n * fans out into one id per element (duplicates collapsed). Returns `undefined`\n * when the dictionary does not declare the variant dimension (no `variant`\n * field, or an empty array).\n */\nexport const getVariantIds = (\n variant: Dictionary['variant']\n): string[] | undefined => {\n if (variant === undefined) return undefined;\n\n const values = Array.isArray(variant) ? variant : [variant];\n if (values.length === 0) return undefined;\n\n return [...new Set(values.map(serializeVariant))];\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 (getVariantIds(dictionary.variant) !== undefined) {\n declaredQualifiers.push('variant');\n }\n if (typeof dictionary.item === 'number') declaredQualifiers.push('item');\n\n return declaredQualifiers;\n};\n\n/**\n * Returns the qualifier identifiers of a dictionary for the given qualifier\n * dimension — the candidate segments of the composite entry ids.\n *\n * - 'variant' → the serialized variant id(s); an array variant yields one id\n * per element (declaration-side fan-out)\n * - 'item' → the item index as a single-element list\n */\nexport const getDictionaryQualifierIds = (\n dictionary: Dictionary,\n qualifierType: DictionaryQualifierType\n): string[] | undefined => {\n if (qualifierType === 'variant') {\n return getVariantIds(dictionary.variant);\n }\n return dictionary.item === undefined ? undefined : [String(dictionary.item)];\n};\n\n/**\n * Builds every composite entry id of a dictionary — the cartesian product of\n * its per-dimension id lists, joined in canonical order. A dictionary with a\n * plain (non-array) variant yields exactly one id; an array variant fans out\n * into one id per element. `undefined` when a dimension of the set is missing.\n */\nexport const getDictionaryCompositeIds = (\n dictionary: Dictionary,\n qualifierTypes: DictionaryQualifierType[]\n): string[] | undefined => {\n let compositeIds: string[] = [''];\n\n for (const qualifierType of qualifierTypes) {\n const ids = getDictionaryQualifierIds(dictionary, qualifierType);\n if (ids === undefined) return undefined;\n\n compositeIds = compositeIds.flatMap((prefix) =>\n ids.map((id) =>\n prefix === '' ? id : `${prefix}${COMPOSITE_ID_SEPARATOR}${id}`\n )\n );\n }\n\n return compositeIds;\n};\n"],"mappings":";;;;;;;;;AAcA,MAAa,kBAAkB,CAC7B,WACA,MACF;;;;;;;;AASA,MAAa,iBACX,YACyB;CACzB,IAAI,YAAY,QAAW,OAAO;CAElC,MAAM,SAAS,MAAM,QAAQ,OAAO,IAAI,UAAU,CAAC,OAAO;CAC1D,IAAI,OAAO,WAAW,GAAG,OAAO;CAEhC,OAAO,CAAC,GAAG,IAAI,IAAI,OAAO,IAAIA,kEAAgB,CAAC,CAAC;AAClD;;;;;;AAOA,MAAa,+BACX,eAC8B;CAC9B,MAAM,qBAAgD,CAAC;CAEvD,IAAI,cAAc,WAAW,OAAO,MAAM,QACxC,mBAAmB,KAAK,SAAS;CAEnC,IAAI,OAAO,WAAW,SAAS,UAAU,mBAAmB,KAAK,MAAM;CAEvE,OAAO;AACT;;;;;;;;;AAUA,MAAa,6BACX,YACA,kBACyB;CACzB,IAAI,kBAAkB,WACpB,OAAO,cAAc,WAAW,OAAO;CAEzC,OAAO,WAAW,SAAS,SAAY,SAAY,CAAC,OAAO,WAAW,IAAI,CAAC;AAC7E;;;;;;;AAQA,MAAa,6BACX,YACA,mBACyB;CACzB,IAAI,eAAyB,CAAC,EAAE;CAEhC,KAAK,MAAM,iBAAiB,gBAAgB;EAC1C,MAAM,MAAM,0BAA0B,YAAY,aAAa;EAC/D,IAAI,QAAQ,QAAW,OAAO;EAE9B,eAAe,aAAa,SAAS,WACnC,IAAI,KAAK,OACP,WAAW,KAAK,KAAK,GAAG,eAAkC,IAC5D,CACF;CACF;CAEA,OAAO;AACT"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
const require_dictionaryManipulator_qualifiedDictionary = require('./qualifiedDictionary.cjs');
|
|
3
|
+
|
|
4
|
+
//#region src/dictionaryManipulator/qualifiedDynamicLoader.ts
|
|
5
|
+
/**
|
|
6
|
+
* Marker property carrying the ordered qualifier dimensions on a dynamic loader
|
|
7
|
+
* map. Its presence distinguishes a qualified group loader map (a nested tree
|
|
8
|
+
* of chunks) from a plain dynamic loader map (one chunk per `locale`). Prefixed
|
|
9
|
+
* and unlikely to collide with a real locale code.
|
|
10
|
+
*/
|
|
11
|
+
const QUALIFIER_DYNAMIC_TYPES_KEY = "__intlayerQualifierTypes";
|
|
12
|
+
/**
|
|
13
|
+
* Type guard discriminating a qualified dynamic loader map (collections /
|
|
14
|
+
* variants, possibly combined) from a plain dynamic loader map.
|
|
15
|
+
*/
|
|
16
|
+
const isQualifiedDynamicLoaderMap = (value) => typeof value === "object" && value !== null && "__intlayerQualifierTypes" in value;
|
|
17
|
+
/**
|
|
18
|
+
* Walks the loader tree following the selector and collects the chunk loaders
|
|
19
|
+
* it targets — shared by the sync ({@link resolveQualifiedDynamicContent}) and
|
|
20
|
+
* async ({@link resolveQualifiedDynamicContentAsync}) resolvers.
|
|
21
|
+
*/
|
|
22
|
+
const collectQualifiedChunks = (loaderMap, key, locale, selector) => {
|
|
23
|
+
const qualifierTypes = loaderMap[QUALIFIER_DYNAMIC_TYPES_KEY];
|
|
24
|
+
const localeTree = loaderMap[locale];
|
|
25
|
+
const itemAxisOpen = qualifierTypes.includes("item") && selector?.item === void 0;
|
|
26
|
+
if (!localeTree) return {
|
|
27
|
+
itemAxisOpen,
|
|
28
|
+
missed: true,
|
|
29
|
+
chunks: []
|
|
30
|
+
};
|
|
31
|
+
const chunks = [];
|
|
32
|
+
const walk = (node, dimensions, segments) => {
|
|
33
|
+
if (dimensions.length === 0) {
|
|
34
|
+
chunks.push({
|
|
35
|
+
cacheKey: `${key}.${locale}.${segments.join("/")}`,
|
|
36
|
+
loader: node
|
|
37
|
+
});
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
const [dimension, ...rest] = dimensions;
|
|
41
|
+
const tree = node;
|
|
42
|
+
if (dimension === "item" && selector?.item === void 0) {
|
|
43
|
+
for (const segment of Object.keys(tree).sort((left, right) => Number(left) - Number(right))) walk(tree[segment], rest, [...segments, segment]);
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
const segment = dimension === "variant" ? require_dictionaryManipulator_qualifiedDictionary.resolveEffectiveVariantId(require_dictionaryManipulator_qualifiedDictionary.serializeVariantChain(selector?.variant), (variantId) => tree[variantId] !== void 0) : String(selector?.item);
|
|
47
|
+
const child = tree[segment];
|
|
48
|
+
if (!child) return false;
|
|
49
|
+
return walk(child, rest, [...segments, segment]);
|
|
50
|
+
};
|
|
51
|
+
return {
|
|
52
|
+
itemAxisOpen,
|
|
53
|
+
missed: !walk(localeTree, qualifierTypes, []),
|
|
54
|
+
chunks
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Resolves the content of a qualified dynamic loader map against a selector,
|
|
59
|
+
* loading only the chunk(s) the selector actually targets.
|
|
60
|
+
*
|
|
61
|
+
* Walks the nested loader tree one dimension at a time (canonical order
|
|
62
|
+
* `variant → item`): `variant` descends by the serialized id, falling back to
|
|
63
|
+
* `default` when the selected variant has no chunk of its own, and `item`
|
|
64
|
+
* either narrows to the selected index or — when no item is given — expands
|
|
65
|
+
* into every sibling chunk (the collection axis). Semantics mirror
|
|
66
|
+
* `resolveQualifiedDictionary` so dynamic and static modes behave alike.
|
|
67
|
+
*
|
|
68
|
+
* The Suspense mechanism is injected through `loadChunk` so the same logic
|
|
69
|
+
* serves both the client (suspender cache) and the server (`react.use`). Every
|
|
70
|
+
* targeted loader is started before the first chunk is read, so sibling chunks
|
|
71
|
+
* load in parallel rather than waterfalling.
|
|
72
|
+
*
|
|
73
|
+
* @param loaderMap - The qualified dynamic loader map (entry point default export).
|
|
74
|
+
* @param key - The dictionary key (used to build stable chunk cache keys).
|
|
75
|
+
* @param locale - The resolved locale to load chunks for.
|
|
76
|
+
* @param selector - The selector splitting the qualifier dimensions.
|
|
77
|
+
* @param loadChunk - Reads a started chunk promise, suspending until it resolves.
|
|
78
|
+
* @param transform - Turns a resolved chunk dictionary into final content.
|
|
79
|
+
*/
|
|
80
|
+
const resolveQualifiedDynamicContent = (params) => {
|
|
81
|
+
const { loaderMap, key, locale, selector, loadChunk, transform } = params;
|
|
82
|
+
const { itemAxisOpen, missed, chunks } = collectQualifiedChunks(loaderMap, key, locale, selector);
|
|
83
|
+
if (missed) return itemAxisOpen ? [] : null;
|
|
84
|
+
const dictionaries = chunks.map(({ cacheKey, loader }) => loadChunk(cacheKey, loader()));
|
|
85
|
+
if (itemAxisOpen) return dictionaries.map(transform);
|
|
86
|
+
const [dictionary] = dictionaries;
|
|
87
|
+
return dictionary ? transform(dictionary) : null;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Async counterpart of {@link resolveQualifiedDynamicContent} for frameworks
|
|
91
|
+
* that load dictionaries with `await` instead of Suspense (Vue, Svelte, Lit,
|
|
92
|
+
* vanilla). Awaits every targeted chunk in parallel, then resolves identically.
|
|
93
|
+
*
|
|
94
|
+
* @param loaderMap - The qualified dynamic loader map.
|
|
95
|
+
* @param key - The dictionary key (used to build stable chunk cache keys).
|
|
96
|
+
* @param locale - The resolved locale to load chunks for.
|
|
97
|
+
* @param selector - The selector splitting the qualifier dimensions.
|
|
98
|
+
* @param transform - Turns a resolved chunk dictionary into final content.
|
|
99
|
+
*/
|
|
100
|
+
const resolveQualifiedDynamicContentAsync = async (params) => {
|
|
101
|
+
const { loaderMap, key, locale, selector, transform } = params;
|
|
102
|
+
const { itemAxisOpen, missed, chunks } = collectQualifiedChunks(loaderMap, key, locale, selector);
|
|
103
|
+
if (missed) return itemAxisOpen ? [] : null;
|
|
104
|
+
const dictionaries = await Promise.all(chunks.map(({ loader }) => loader()));
|
|
105
|
+
if (itemAxisOpen) return dictionaries.map(transform);
|
|
106
|
+
const [dictionary] = dictionaries;
|
|
107
|
+
return dictionary ? transform(dictionary) : null;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
//#endregion
|
|
111
|
+
exports.QUALIFIER_DYNAMIC_TYPES_KEY = QUALIFIER_DYNAMIC_TYPES_KEY;
|
|
112
|
+
exports.isQualifiedDynamicLoaderMap = isQualifiedDynamicLoaderMap;
|
|
113
|
+
exports.resolveQualifiedDynamicContent = resolveQualifiedDynamicContent;
|
|
114
|
+
exports.resolveQualifiedDynamicContentAsync = resolveQualifiedDynamicContentAsync;
|
|
115
|
+
//# sourceMappingURL=qualifiedDynamicLoader.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qualifiedDynamicLoader.cjs","names":["resolveEffectiveVariantId","serializeVariantChain"],"sources":["../../../src/dictionaryManipulator/qualifiedDynamicLoader.ts"],"sourcesContent":["import type {\n Dictionary,\n DictionaryQualifierType,\n DictionarySelector,\n} from '@intlayer/types/dictionary';\nimport {\n COMPOSITE_ID_SEPARATOR,\n resolveEffectiveVariantId,\n serializeVariantChain,\n} from './qualifiedDictionary';\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/** 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 // A variant with no chunk of its own falls back to the `default` chunk, so\n // only the entries that actually differ need to be emitted.\n const segment =\n dimension === 'variant'\n ? resolveEffectiveVariantId(\n serializeVariantChain(selector?.variant),\n (variantId) => tree[variantId] !== undefined\n )\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` descends by the serialized id, falling back to\n * `default` when the selected variant has no chunk of its own, and `item`\n * either narrows to the selected index or — when no item is given — expands\n * into every sibling chunk (the collection axis). Semantics mirror\n * `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.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":";;;;;;;;;;AAiBA,MAAa,8BAA8B;;;;;AAoC3C,MAAa,+BACX,UAEA,OAAO,UAAU,YACjB,UAAU,sCACqB;;;;;;AAsBjC,MAAM,0BACJ,WACA,KACA,QACA,aACoB;CACpB,MAAM,iBAAiB,UAAU;CACjC,MAAM,aAAa,UAAU;CAI7B,MAAM,eACJ,eAAe,SAAS,MAAM,KAAK,UAAU,SAAS;CAExD,IAAI,CAAC,YAAY,OAAO;EAAE;EAAc,QAAQ;EAAM,QAAQ,CAAC;CAAE;CAEjE,MAAM,SAA2B,CAAC;CAElC,MAAM,QACJ,MACA,YACA,aACY;EACZ,IAAI,WAAW,WAAW,GAAG;GAC3B,OAAO,KAAK;IACV,UAAU,GAAG,IAAI,GAAG,OAAO,GAAG,SAAS,QAA2B;IAClE,QAAQ;GACV,CAAC;GACD,OAAO;EACT;EAEA,MAAM,CAAC,WAAW,GAAG,QAAQ;EAC7B,MAAM,OAAO;EAEb,IAAI,cAAc,UAAU,UAAU,SAAS,QAAW;GAExD,KAAK,MAAM,WAAW,OAAO,KAAK,IAAI,CAAC,CAAC,MACrC,MAAM,UAAU,OAAO,IAAI,IAAI,OAAO,KAAK,CAC9C,GACE,KAAK,KAAK,UAAW,MAAM,CAAC,GAAG,UAAU,OAAO,CAAC;GAEnD,OAAO;EACT;EAIA,MAAM,UACJ,cAAc,YACVA,4EACEC,wEAAsB,UAAU,OAAO,IACtC,cAAc,KAAK,eAAe,MACrC,IACA,OAAO,UAAU,IAAI;EAE3B,MAAM,QAAQ,KAAK;EACnB,IAAI,CAAC,OAAO,OAAO;EAEnB,OAAO,KAAK,OAAO,MAAM,CAAC,GAAG,UAAU,OAAO,CAAC;CACjD;CAIA,OAAO;EAAE;EAAc,QAAQ,CAFjB,KAAK,YAAY,gBAAgB,CAAC,CAEZ;EAAG;CAAO;AAChD;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,MAAa,kCAA2C,WAOtB;CAChC,MAAM,EAAE,WAAW,KAAK,QAAQ,UAAU,WAAW,cAAc;CAEnE,MAAM,EAAE,cAAc,QAAQ,WAAW,uBACvC,WACA,KACA,QACA,QACF;CAEA,IAAI,QAAQ,OAAO,eAAe,CAAC,IAAI;CAGvC,MAAM,eAAe,OAAO,KAAK,EAAE,UAAU,aAC3C,UAAU,UAAU,OAAO,CAAC,CAC9B;CAEA,IAAI,cAAc,OAAO,aAAa,IAAI,SAAS;CAEnD,MAAM,CAAC,cAAc;CACrB,OAAO,aAAa,UAAU,UAAU,IAAI;AAC9C;;;;;;;;;;;;AAaA,MAAa,sCAAsC,OAAgB,WAMxB;CACzC,MAAM,EAAE,WAAW,KAAK,QAAQ,UAAU,cAAc;CAExD,MAAM,EAAE,cAAc,QAAQ,WAAW,uBACvC,WACA,KACA,QACA,QACF;CAEA,IAAI,QAAQ,OAAO,eAAe,CAAC,IAAI;CAEvC,MAAM,eAAe,MAAM,QAAQ,IAAI,OAAO,KAAK,EAAE,aAAa,OAAO,CAAC,CAAC;CAE3E,IAAI,cAAc,OAAO,aAAa,IAAI,SAAS;CAEnD,MAAM,CAAC,cAAc;CACrB,OAAO,aAAa,UAAU,UAAU,IAAI;AAC9C"}
|
|
@@ -23,8 +23,10 @@ const removeContentNodeByKeyPath = (dictionaryContent, keyPath) => {
|
|
|
23
23
|
currentValue = currentValue[keyObj.type];
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
-
if (parentValue && lastKey !== null)
|
|
27
|
-
|
|
26
|
+
if (parentValue && lastKey !== null) {
|
|
27
|
+
if (Array.isArray(parentValue)) parentValue.splice(lastKey, 1);
|
|
28
|
+
else delete parentValue[lastKey];
|
|
29
|
+
}
|
|
28
30
|
return dictionaryContent;
|
|
29
31
|
};
|
|
30
32
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"removeContentNodeByKeyPath.cjs","names":["NodeTypes"],"sources":["../../../src/dictionaryManipulator/removeContentNodeByKeyPath.ts"],"sourcesContent":["import type { ContentNode } from '@intlayer/types/dictionary';\nimport type { KeyPath } from '@intlayer/types/keyPath';\n\nimport * as NodeTypes from '@intlayer/types/nodeType';\n\nexport const removeContentNodeByKeyPath = (\n dictionaryContent: ContentNode,\n keyPath: KeyPath[]\n): ContentNode => {\n let currentValue: any = dictionaryContent;\n let parentValue: any = null;\n let lastKey: string | number | null = null;\n\n for (const keyObj of keyPath) {\n parentValue = currentValue;\n\n if (keyObj.type === NodeTypes.OBJECT || keyObj.type === NodeTypes.ARRAY) {\n lastKey = keyObj.key;\n currentValue = currentValue[keyObj.key];\n }\n\n if (\n keyObj.type === NodeTypes.TRANSLATION ||\n keyObj.type === NodeTypes.ENUMERATION ||\n keyObj.type === NodeTypes.PLURAL ||\n keyObj.type === NodeTypes.CONDITION ||\n keyObj.type === NodeTypes.GENDER ||\n keyObj.type === NodeTypes.SELECT\n ) {\n lastKey = keyObj.type;\n currentValue = currentValue[keyObj.type][keyObj.key];\n }\n\n if (\n keyObj.type === NodeTypes.MARKDOWN ||\n keyObj.type === NodeTypes.REACT_NODE ||\n keyObj.type === NodeTypes.HTML ||\n keyObj.type === NodeTypes.INSERTION ||\n keyObj.type === NodeTypes.FILE\n ) {\n lastKey = keyObj.type;\n currentValue = currentValue[keyObj.type];\n }\n }\n\n if (parentValue && lastKey !== null) {\n if (Array.isArray(parentValue)) {\n parentValue.splice(lastKey as unknown as number, 1);\n } else {\n delete parentValue[lastKey];\n }\n }\n\n return dictionaryContent;\n};\n"],"mappings":";;;;;;AAKA,MAAa,8BACX,mBACA,YACgB;CAChB,IAAI,eAAoB;CACxB,IAAI,cAAmB;CACvB,IAAI,UAAkC;CAEtC,KAAK,MAAM,UAAU,SAAS;EAC5B,cAAc;EAEd,IAAI,OAAO,SAASA,yBAAU,UAAU,OAAO,SAASA,yBAAU,OAAO;GACvE,UAAU,OAAO;GACjB,eAAe,aAAa,OAAO;EACrC;EAEA,IACE,OAAO,SAASA,yBAAU,eAC1B,OAAO,SAASA,yBAAU,eAC1B,OAAO,SAASA,yBAAU,UAC1B,OAAO,SAASA,yBAAU,aAC1B,OAAO,SAASA,yBAAU,UAC1B,OAAO,SAASA,yBAAU,QAC1B;GACA,UAAU,OAAO;GACjB,eAAe,aAAa,OAAO,KAAK,CAAC,OAAO;EAClD;EAEA,IACE,OAAO,SAASA,yBAAU,YAC1B,OAAO,SAASA,yBAAU,cAC1B,OAAO,SAASA,yBAAU,QAC1B,OAAO,SAASA,yBAAU,aAC1B,OAAO,SAASA,yBAAU,MAC1B;GACA,UAAU,OAAO;GACjB,eAAe,aAAa,OAAO;EACrC;CACF;CAEA,IAAI,eAAe,YAAY,
|
|
1
|
+
{"version":3,"file":"removeContentNodeByKeyPath.cjs","names":["NodeTypes"],"sources":["../../../src/dictionaryManipulator/removeContentNodeByKeyPath.ts"],"sourcesContent":["import type { ContentNode } from '@intlayer/types/dictionary';\nimport type { KeyPath } from '@intlayer/types/keyPath';\n\nimport * as NodeTypes from '@intlayer/types/nodeType';\n\nexport const removeContentNodeByKeyPath = (\n dictionaryContent: ContentNode,\n keyPath: KeyPath[]\n): ContentNode => {\n let currentValue: any = dictionaryContent;\n let parentValue: any = null;\n let lastKey: string | number | null = null;\n\n for (const keyObj of keyPath) {\n parentValue = currentValue;\n\n if (keyObj.type === NodeTypes.OBJECT || keyObj.type === NodeTypes.ARRAY) {\n lastKey = keyObj.key;\n currentValue = currentValue[keyObj.key];\n }\n\n if (\n keyObj.type === NodeTypes.TRANSLATION ||\n keyObj.type === NodeTypes.ENUMERATION ||\n keyObj.type === NodeTypes.PLURAL ||\n keyObj.type === NodeTypes.CONDITION ||\n keyObj.type === NodeTypes.GENDER ||\n keyObj.type === NodeTypes.SELECT\n ) {\n lastKey = keyObj.type;\n currentValue = currentValue[keyObj.type][keyObj.key];\n }\n\n if (\n keyObj.type === NodeTypes.MARKDOWN ||\n keyObj.type === NodeTypes.REACT_NODE ||\n keyObj.type === NodeTypes.HTML ||\n keyObj.type === NodeTypes.INSERTION ||\n keyObj.type === NodeTypes.FILE\n ) {\n lastKey = keyObj.type;\n currentValue = currentValue[keyObj.type];\n }\n }\n\n if (parentValue && lastKey !== null) {\n if (Array.isArray(parentValue)) {\n parentValue.splice(lastKey as unknown as number, 1);\n } else {\n delete parentValue[lastKey];\n }\n }\n\n return dictionaryContent;\n};\n"],"mappings":";;;;;;AAKA,MAAa,8BACX,mBACA,YACgB;CAChB,IAAI,eAAoB;CACxB,IAAI,cAAmB;CACvB,IAAI,UAAkC;CAEtC,KAAK,MAAM,UAAU,SAAS;EAC5B,cAAc;EAEd,IAAI,OAAO,SAASA,yBAAU,UAAU,OAAO,SAASA,yBAAU,OAAO;GACvE,UAAU,OAAO;GACjB,eAAe,aAAa,OAAO;EACrC;EAEA,IACE,OAAO,SAASA,yBAAU,eAC1B,OAAO,SAASA,yBAAU,eAC1B,OAAO,SAASA,yBAAU,UAC1B,OAAO,SAASA,yBAAU,aAC1B,OAAO,SAASA,yBAAU,UAC1B,OAAO,SAASA,yBAAU,QAC1B;GACA,UAAU,OAAO;GACjB,eAAe,aAAa,OAAO,KAAK,CAAC,OAAO;EAClD;EAEA,IACE,OAAO,SAASA,yBAAU,YAC1B,OAAO,SAASA,yBAAU,cAC1B,OAAO,SAASA,yBAAU,QAC1B,OAAO,SAASA,yBAAU,aAC1B,OAAO,SAASA,yBAAU,MAC1B;GACA,UAAU,OAAO;GACjB,eAAe,aAAa,OAAO;EACrC;CACF;CAEA,IAAI,eAAe,YAAY,MAAM;EACnC,IAAI,MAAM,QAAQ,WAAW,GAC3B,YAAY,OAAO,SAA8B,CAAC;OAElD,OAAO,YAAY;CAEvB;CAEA,OAAO;AACT"}
|
|
@@ -23,15 +23,17 @@ const renameContentNodeByKeyPath = (dictionaryContent, newKey, keyPath) => {
|
|
|
23
23
|
currentValue = currentValue[keyObj.type];
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
-
if (parentValue && lastKey !== null)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
26
|
+
if (parentValue && lastKey !== null) {
|
|
27
|
+
if (Array.isArray(parentValue)) parentValue[lastKey] = currentValue;
|
|
28
|
+
else {
|
|
29
|
+
const newParentValue = {};
|
|
30
|
+
for (const key of Object.keys(parentValue)) if (key === lastKey && typeof newKey !== "undefined") newParentValue[newKey] = currentValue;
|
|
31
|
+
else newParentValue[key] = parentValue[key];
|
|
32
|
+
Object.keys(parentValue).forEach((key) => {
|
|
33
|
+
delete parentValue[key];
|
|
34
|
+
});
|
|
35
|
+
Object.assign(parentValue, newParentValue);
|
|
36
|
+
}
|
|
35
37
|
}
|
|
36
38
|
return dictionaryContent;
|
|
37
39
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renameContentNodeByKeyPath.cjs","names":["NodeTypes"],"sources":["../../../src/dictionaryManipulator/renameContentNodeByKeyPath.ts"],"sourcesContent":["import type { ContentNode } from '@intlayer/types/dictionary';\nimport type { KeyPath } from '@intlayer/types/keyPath';\n\nimport * as NodeTypes from '@intlayer/types/nodeType';\n\nexport const renameContentNodeByKeyPath = (\n dictionaryContent: ContentNode,\n newKey: KeyPath['key'],\n keyPath: KeyPath[]\n): ContentNode => {\n let currentValue: any = dictionaryContent;\n let parentValue: any = null;\n let lastKey: string | number | null = null;\n\n for (const keyObj of keyPath) {\n parentValue = currentValue;\n\n if (keyObj.type === NodeTypes.OBJECT || keyObj.type === NodeTypes.ARRAY) {\n lastKey = keyObj.key;\n currentValue = currentValue[keyObj.key];\n }\n\n if (\n keyObj.type === NodeTypes.TRANSLATION ||\n keyObj.type === NodeTypes.ENUMERATION ||\n keyObj.type === NodeTypes.PLURAL ||\n keyObj.type === NodeTypes.CONDITION ||\n keyObj.type === NodeTypes.GENDER ||\n keyObj.type === NodeTypes.SELECT\n ) {\n lastKey = keyObj.type;\n currentValue = currentValue[keyObj.type][keyObj.key];\n }\n\n if (\n keyObj.type === NodeTypes.MARKDOWN ||\n keyObj.type === NodeTypes.REACT_NODE ||\n keyObj.type === NodeTypes.HTML ||\n keyObj.type === NodeTypes.INSERTION ||\n keyObj.type === NodeTypes.FILE\n ) {\n lastKey = keyObj.type;\n currentValue = currentValue[keyObj.type];\n }\n }\n\n // Assign the new value to the last key of the parent while preserving the order\n if (parentValue && lastKey !== null) {\n if (Array.isArray(parentValue)) {\n parentValue[lastKey as number] = currentValue;\n } else {\n const newParentValue: any = {};\n for (const key of Object.keys(parentValue)) {\n if (key === lastKey && typeof newKey !== 'undefined') {\n newParentValue[newKey] = currentValue;\n } else {\n newParentValue[key] = parentValue[key];\n }\n }\n // Replace the contents of parentValue with newParentValue\n Object.keys(parentValue).forEach((key) => {\n delete parentValue[key];\n });\n Object.assign(parentValue, newParentValue);\n }\n }\n\n return dictionaryContent;\n};\n"],"mappings":";;;;;;AAKA,MAAa,8BACX,mBACA,QACA,YACgB;CAChB,IAAI,eAAoB;CACxB,IAAI,cAAmB;CACvB,IAAI,UAAkC;CAEtC,KAAK,MAAM,UAAU,SAAS;EAC5B,cAAc;EAEd,IAAI,OAAO,SAASA,yBAAU,UAAU,OAAO,SAASA,yBAAU,OAAO;GACvE,UAAU,OAAO;GACjB,eAAe,aAAa,OAAO;EACrC;EAEA,IACE,OAAO,SAASA,yBAAU,eAC1B,OAAO,SAASA,yBAAU,eAC1B,OAAO,SAASA,yBAAU,UAC1B,OAAO,SAASA,yBAAU,aAC1B,OAAO,SAASA,yBAAU,UAC1B,OAAO,SAASA,yBAAU,QAC1B;GACA,UAAU,OAAO;GACjB,eAAe,aAAa,OAAO,KAAK,CAAC,OAAO;EAClD;EAEA,IACE,OAAO,SAASA,yBAAU,YAC1B,OAAO,SAASA,yBAAU,cAC1B,OAAO,SAASA,yBAAU,QAC1B,OAAO,SAASA,yBAAU,aAC1B,OAAO,SAASA,yBAAU,MAC1B;GACA,UAAU,OAAO;GACjB,eAAe,aAAa,OAAO;EACrC;CACF;CAGA,IAAI,eAAe,YAAY,
|
|
1
|
+
{"version":3,"file":"renameContentNodeByKeyPath.cjs","names":["NodeTypes"],"sources":["../../../src/dictionaryManipulator/renameContentNodeByKeyPath.ts"],"sourcesContent":["import type { ContentNode } from '@intlayer/types/dictionary';\nimport type { KeyPath } from '@intlayer/types/keyPath';\n\nimport * as NodeTypes from '@intlayer/types/nodeType';\n\nexport const renameContentNodeByKeyPath = (\n dictionaryContent: ContentNode,\n newKey: KeyPath['key'],\n keyPath: KeyPath[]\n): ContentNode => {\n let currentValue: any = dictionaryContent;\n let parentValue: any = null;\n let lastKey: string | number | null = null;\n\n for (const keyObj of keyPath) {\n parentValue = currentValue;\n\n if (keyObj.type === NodeTypes.OBJECT || keyObj.type === NodeTypes.ARRAY) {\n lastKey = keyObj.key;\n currentValue = currentValue[keyObj.key];\n }\n\n if (\n keyObj.type === NodeTypes.TRANSLATION ||\n keyObj.type === NodeTypes.ENUMERATION ||\n keyObj.type === NodeTypes.PLURAL ||\n keyObj.type === NodeTypes.CONDITION ||\n keyObj.type === NodeTypes.GENDER ||\n keyObj.type === NodeTypes.SELECT\n ) {\n lastKey = keyObj.type;\n currentValue = currentValue[keyObj.type][keyObj.key];\n }\n\n if (\n keyObj.type === NodeTypes.MARKDOWN ||\n keyObj.type === NodeTypes.REACT_NODE ||\n keyObj.type === NodeTypes.HTML ||\n keyObj.type === NodeTypes.INSERTION ||\n keyObj.type === NodeTypes.FILE\n ) {\n lastKey = keyObj.type;\n currentValue = currentValue[keyObj.type];\n }\n }\n\n // Assign the new value to the last key of the parent while preserving the order\n if (parentValue && lastKey !== null) {\n if (Array.isArray(parentValue)) {\n parentValue[lastKey as number] = currentValue;\n } else {\n const newParentValue: any = {};\n for (const key of Object.keys(parentValue)) {\n if (key === lastKey && typeof newKey !== 'undefined') {\n newParentValue[newKey] = currentValue;\n } else {\n newParentValue[key] = parentValue[key];\n }\n }\n // Replace the contents of parentValue with newParentValue\n Object.keys(parentValue).forEach((key) => {\n delete parentValue[key];\n });\n Object.assign(parentValue, newParentValue);\n }\n }\n\n return dictionaryContent;\n};\n"],"mappings":";;;;;;AAKA,MAAa,8BACX,mBACA,QACA,YACgB;CAChB,IAAI,eAAoB;CACxB,IAAI,cAAmB;CACvB,IAAI,UAAkC;CAEtC,KAAK,MAAM,UAAU,SAAS;EAC5B,cAAc;EAEd,IAAI,OAAO,SAASA,yBAAU,UAAU,OAAO,SAASA,yBAAU,OAAO;GACvE,UAAU,OAAO;GACjB,eAAe,aAAa,OAAO;EACrC;EAEA,IACE,OAAO,SAASA,yBAAU,eAC1B,OAAO,SAASA,yBAAU,eAC1B,OAAO,SAASA,yBAAU,UAC1B,OAAO,SAASA,yBAAU,aAC1B,OAAO,SAASA,yBAAU,UAC1B,OAAO,SAASA,yBAAU,QAC1B;GACA,UAAU,OAAO;GACjB,eAAe,aAAa,OAAO,KAAK,CAAC,OAAO;EAClD;EAEA,IACE,OAAO,SAASA,yBAAU,YAC1B,OAAO,SAASA,yBAAU,cAC1B,OAAO,SAASA,yBAAU,QAC1B,OAAO,SAASA,yBAAU,aAC1B,OAAO,SAASA,yBAAU,MAC1B;GACA,UAAU,OAAO;GACjB,eAAe,aAAa,OAAO;EACrC;CACF;CAGA,IAAI,eAAe,YAAY,MAAM;EACnC,IAAI,MAAM,QAAQ,WAAW,GAC3B,YAAY,WAAqB;OAC5B;GACL,MAAM,iBAAsB,CAAC;GAC7B,KAAK,MAAM,OAAO,OAAO,KAAK,WAAW,GACvC,IAAI,QAAQ,WAAW,OAAO,WAAW,aACvC,eAAe,UAAU;QAEzB,eAAe,OAAO,YAAY;GAItC,OAAO,KAAK,WAAW,CAAC,CAAC,SAAS,QAAQ;IACxC,OAAO,YAAY;GACrB,CAAC;GACD,OAAO,OAAO,aAAa,cAAc;EAC3C;CACF;CAEA,OAAO;AACT"}
|
|
@@ -51,7 +51,8 @@ const presets = {
|
|
|
51
51
|
const date = (date, options) => {
|
|
52
52
|
const dateTime = new Date(date);
|
|
53
53
|
const resolvedOptions = typeof options === "string" ? presets[options] ?? {} : options;
|
|
54
|
-
|
|
54
|
+
const locale = (typeof options === "object" ? options?.locale : void 0) ?? _intlayer_config_built.internationalization?.defaultLocale;
|
|
55
|
+
return require_utils_intl.getCachedIntl("DateTimeFormat", locale, resolvedOptions).format(dateTime);
|
|
55
56
|
};
|
|
56
57
|
|
|
57
58
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"date.cjs","names":["
|
|
1
|
+
{"version":3,"file":"date.cjs","names":["internationalization","getCachedIntl"],"sources":["../../../src/formatters/date.ts"],"sourcesContent":["import { internationalization } from '@intlayer/config/built';\nimport type { LocalesValues } from '@intlayer/types/module_augmentation';\nimport { getCachedIntl } from '../utils/intl';\n\nexport type DateTimePreset =\n | 'short'\n | 'long'\n | 'dateOnly'\n | 'timeOnly'\n | 'full';\n\nexport const presets: Record<DateTimePreset, Intl.DateTimeFormatOptions> = {\n short: {\n year: '2-digit',\n month: '2-digit',\n day: '2-digit',\n hour: '2-digit',\n minute: '2-digit',\n },\n long: {\n year: 'numeric',\n month: 'long',\n day: 'numeric',\n hour: 'numeric',\n minute: 'numeric',\n },\n full: {\n year: 'numeric',\n month: 'long',\n day: 'numeric',\n hour: 'numeric',\n minute: 'numeric',\n hour12: false,\n },\n dateOnly: {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n },\n timeOnly: {\n hour: 'numeric',\n minute: 'numeric',\n second: 'numeric',\n },\n};\n\n/**\n * Formats a date/time value into a localized string using Intl.DateTimeFormat.\n *\n * @example\n * date(new Date('2025-08-02T14:30:00Z'), { year: '2-digit', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' });\n * // \"08/02/25, 14:30\"\n *\n * @example\n * date(\"2025-08-02T14:30:00Z\", { locale: Locales.FRENCH, month: \"long\", day: \"numeric\" });\n * // \"2 août\"\n */\nexport const date = (\n date: Date | string | number,\n options?:\n | (Intl.DateTimeFormatOptions & { locale?: LocalesValues })\n | DateTimePreset\n): string => {\n const dateTime = new Date(date);\n\n const resolvedOptions =\n typeof options === 'string' ? (presets[options] ?? {}) : options;\n\n const locale =\n (typeof options === 'object' ? options?.locale : undefined) ??\n internationalization?.defaultLocale;\n\n const formatter = getCachedIntl('DateTimeFormat', locale, resolvedOptions);\n\n return formatter.format(dateTime);\n};\n"],"mappings":";;;;;AAWA,MAAa,UAA8D;CACzE,OAAO;EACL,MAAM;EACN,OAAO;EACP,KAAK;EACL,MAAM;EACN,QAAQ;CACV;CACA,MAAM;EACJ,MAAM;EACN,OAAO;EACP,KAAK;EACL,MAAM;EACN,QAAQ;CACV;CACA,MAAM;EACJ,MAAM;EACN,OAAO;EACP,KAAK;EACL,MAAM;EACN,QAAQ;EACR,QAAQ;CACV;CACA,UAAU;EACR,MAAM;EACN,OAAO;EACP,KAAK;CACP;CACA,UAAU;EACR,MAAM;EACN,QAAQ;EACR,QAAQ;CACV;AACF;;;;;;;;;;;;AAaA,MAAa,QACX,MACA,YAGW;CACX,MAAM,WAAW,IAAI,KAAK,IAAI;CAE9B,MAAM,kBACJ,OAAO,YAAY,WAAY,QAAQ,YAAY,CAAC,IAAK;CAE3D,MAAM,UACH,OAAO,YAAY,WAAW,SAAS,SAAS,WACjDA,6CAAsB;CAIxB,OAFkBC,iCAAc,kBAAkB,QAAQ,eAE3C,CAAC,CAAC,OAAO,QAAQ;AAClC"}
|
|
@@ -13,9 +13,9 @@ const diffInUnit = (from, to, unit) => {
|
|
|
13
13
|
case "minute": return sec / 60;
|
|
14
14
|
case "hour": return sec / 3600;
|
|
15
15
|
case "day": return sec / 86400;
|
|
16
|
-
case "month": return sec /
|
|
17
|
-
case "quarter": return sec /
|
|
18
|
-
case "year": return sec /
|
|
16
|
+
case "month": return sec / 2592e3;
|
|
17
|
+
case "quarter": return sec / 7776e3;
|
|
18
|
+
case "year": return sec / 31536e3;
|
|
19
19
|
default: return sec;
|
|
20
20
|
}
|
|
21
21
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"relativeTime.cjs","names":["getCachedIntl","internationalization"],"sources":["../../../src/formatters/relativeTime.ts"],"sourcesContent":["import { internationalization } from '@intlayer/config/built';\nimport type { LocalesValues } from '@intlayer/types/module_augmentation';\nimport { getCachedIntl } from '../utils/intl';\n\ntype RelativeTimeUnit = Intl.RelativeTimeFormatUnit;\n\n/**\n * Calculate the difference between 2 dates in the given unit.\n */\nconst diffInUnit = (from: Date, to: Date, unit: RelativeTimeUnit): number => {\n const msDiff = to.getTime() - from.getTime();\n const sec = msDiff / 1000;\n\n switch (unit) {\n case 'second':\n return sec;\n case 'minute':\n return sec / 60;\n case 'hour':\n return sec / 3600;\n case 'day':\n return sec / 86400;\n case 'month':\n return sec / (30 * 86400); // approx\n case 'quarter':\n return sec / (3 * 30 * 86400); // 3 months approx\n case 'year':\n return sec / (365 * 86400); // approx\n default:\n return sec;\n }\n};\n\n/**\n * Formats the difference between two dates as a localized relative time string.\n *\n * @example\n * relativeTime(new Date(Date.now() - 30000)); // \"30 seconds ago\"\n *\n * @example\n * relativeTime(\"2025-01-01\", new Date(), { locale: Locales.FRENCH, unit: \"day\" });\n * // \"il y a 443 jours\"\n */\nexport const relativeTime = (\n from: Date | string | number,\n to: Date | string | number = new Date(),\n options?: Intl.RelativeTimeFormatOptions & {\n locale?: LocalesValues;\n unit?: RelativeTimeUnit;\n }\n): string => {\n const fromDate = new Date(from);\n const toDate = new Date(to);\n const unit = options?.unit ?? 'second';\n\n const value = diffInUnit(fromDate, toDate, unit);\n\n return getCachedIntl(\n 'RelativeTimeFormat',\n options?.locale ?? internationalization?.defaultLocale,\n\n options\n ).format(Math.round(value), unit);\n};\n"],"mappings":";;;;;;;;AASA,MAAM,cAAc,MAAY,IAAU,SAAmC;CAE3E,MAAM,OADS,GAAG,QAAQ,IAAI,KAAK,QAAQ,KACtB;CAErB,QAAQ,MAAR;EACE,KAAK,UACH,OAAO;EACT,KAAK,UACH,OAAO,MAAM;EACf,KAAK,QACH,OAAO,MAAM;EACf,KAAK,OACH,OAAO,MAAM;EACf,KAAK,SACH,OAAO,
|
|
1
|
+
{"version":3,"file":"relativeTime.cjs","names":["getCachedIntl","internationalization"],"sources":["../../../src/formatters/relativeTime.ts"],"sourcesContent":["import { internationalization } from '@intlayer/config/built';\nimport type { LocalesValues } from '@intlayer/types/module_augmentation';\nimport { getCachedIntl } from '../utils/intl';\n\ntype RelativeTimeUnit = Intl.RelativeTimeFormatUnit;\n\n/**\n * Calculate the difference between 2 dates in the given unit.\n */\nconst diffInUnit = (from: Date, to: Date, unit: RelativeTimeUnit): number => {\n const msDiff = to.getTime() - from.getTime();\n const sec = msDiff / 1000;\n\n switch (unit) {\n case 'second':\n return sec;\n case 'minute':\n return sec / 60;\n case 'hour':\n return sec / 3600;\n case 'day':\n return sec / 86400;\n case 'month':\n return sec / (30 * 86400); // approx\n case 'quarter':\n return sec / (3 * 30 * 86400); // 3 months approx\n case 'year':\n return sec / (365 * 86400); // approx\n default:\n return sec;\n }\n};\n\n/**\n * Formats the difference between two dates as a localized relative time string.\n *\n * @example\n * relativeTime(new Date(Date.now() - 30000)); // \"30 seconds ago\"\n *\n * @example\n * relativeTime(\"2025-01-01\", new Date(), { locale: Locales.FRENCH, unit: \"day\" });\n * // \"il y a 443 jours\"\n */\nexport const relativeTime = (\n from: Date | string | number,\n to: Date | string | number = new Date(),\n options?: Intl.RelativeTimeFormatOptions & {\n locale?: LocalesValues;\n unit?: RelativeTimeUnit;\n }\n): string => {\n const fromDate = new Date(from);\n const toDate = new Date(to);\n const unit = options?.unit ?? 'second';\n\n const value = diffInUnit(fromDate, toDate, unit);\n\n return getCachedIntl(\n 'RelativeTimeFormat',\n options?.locale ?? internationalization?.defaultLocale,\n\n options\n ).format(Math.round(value), unit);\n};\n"],"mappings":";;;;;;;;AASA,MAAM,cAAc,MAAY,IAAU,SAAmC;CAE3E,MAAM,OADS,GAAG,QAAQ,IAAI,KAAK,QAAQ,KACtB;CAErB,QAAQ,MAAR;EACE,KAAK,UACH,OAAO;EACT,KAAK,UACH,OAAO,MAAM;EACf,KAAK,QACH,OAAO,MAAM;EACf,KAAK,OACH,OAAO,MAAM;EACf,KAAK,SACH,OAAO,MAAO;EAChB,KAAK,WACH,OAAO,MAAO;EAChB,KAAK,QACH,OAAO,MAAO;EAChB,SACE,OAAO;CACX;AACF;;;;;;;;;;;AAYA,MAAa,gBACX,MACA,qBAA6B,IAAI,KAAK,GACtC,YAIW;CACX,MAAM,WAAW,IAAI,KAAK,IAAI;CAC9B,MAAM,SAAS,IAAI,KAAK,EAAE;CAC1B,MAAM,OAAO,SAAS,QAAQ;CAE9B,MAAM,QAAQ,WAAW,UAAU,QAAQ,IAAI;CAE/C,OAAOA,iCACL,sBACA,SAAS,UAAUC,6CAAsB,eAEzC,OACF,CAAC,CAAC,OAAO,KAAK,MAAM,KAAK,GAAG,IAAI;AAClC"}
|