@intlayer/core 9.3.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/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/mergeDictionaries.cjs +4 -3
- package/dist/cjs/dictionaryManipulator/mergeDictionaries.cjs.map +1 -1
- package/dist/cjs/dictionaryManipulator/mergeQualifiedDictionaries.cjs.map +1 -1
- package/dist/cjs/dictionaryManipulator/qualifiedDictionary.cjs +0 -1
- package/dist/cjs/dictionaryManipulator/qualifiedDictionary.cjs.map +1 -1
- package/dist/cjs/dictionaryManipulator/qualifiedDictionary.test-d.cjs.map +1 -1
- 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/interpreter/getCondition.cjs +0 -1
- package/dist/cjs/interpreter/getCondition.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/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/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/mergeDictionaries.mjs +4 -3
- package/dist/esm/dictionaryManipulator/mergeDictionaries.mjs.map +1 -1
- 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/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/package.json +6 -6
|
@@ -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 * 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":"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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":"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"}
|
|
@@ -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"}
|
|
@@ -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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getCondition.cjs","names":[],"sources":["../../../src/interpreter/getCondition.ts"],"sourcesContent":["import type { ConditionContentStates } from '../transpiler';\n\n/**\n * Picks content based on a boolean condition.\n *\n * @param conditionContent - A map with 'true', 'false', and optionally 'fallback' keys.\n * @param state - The boolean state to match.\n * @returns The matching content.\n *\n * @example\n * ```ts\n * const content = getCondition({\n * 'true': 'The condition is validated',\n * 'false': 'The condition is not validated',\n * }, true);\n * // 'The condition is validated'\n * ```\n */\nexport const getCondition = <const Content>(\n conditionContent: ConditionContentStates<Content>,\n state?: boolean\n): Content => {\n const stateList = Object.keys(conditionContent);\n const fallbackState = stateList[\n stateList.length - 1\n ] as keyof typeof conditionContent;\n\n // Default or error handling if no keys match\n return (\n conditionContent[`${state}` as keyof typeof conditionContent] ??\n conditionContent.fallback ??\n (conditionContent[fallbackState] as Content)\n );\n};\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"getCondition.cjs","names":[],"sources":["../../../src/interpreter/getCondition.ts"],"sourcesContent":["import type { ConditionContentStates } from '../transpiler';\n\n/**\n * Picks content based on a boolean condition.\n *\n * @param conditionContent - A map with 'true', 'false', and optionally 'fallback' keys.\n * @param state - The boolean state to match.\n * @returns The matching content.\n *\n * @example\n * ```ts\n * const content = getCondition({\n * 'true': 'The condition is validated',\n * 'false': 'The condition is not validated',\n * }, true);\n * // 'The condition is validated'\n * ```\n */\nexport const getCondition = <const Content>(\n conditionContent: ConditionContentStates<Content>,\n state?: boolean\n): Content => {\n const stateList = Object.keys(conditionContent);\n const fallbackState = stateList[\n stateList.length - 1\n ] as keyof typeof conditionContent;\n\n // Default or error handling if no keys match\n return (\n conditionContent[`${state}` as keyof typeof conditionContent] ??\n conditionContent.fallback ??\n (conditionContent[fallbackState] as Content)\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAkBA,MAAa,gBACX,kBACA,UACY;CACZ,MAAM,YAAY,OAAO,KAAK,gBAAgB;CAC9C,MAAM,gBAAgB,UACpB,UAAU,SAAS;CAIrB,OACE,iBAAiB,GAAG,YACpB,iBAAiB,YAChB,iBAAiB;AAEtB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getEnumeration.cjs","names":[],"sources":["../../../src/interpreter/getEnumeration.ts"],"sourcesContent":["import type { EnterFormat, EnumerationContentState } from '../transpiler';\n\n/**\n * Find the matching condition for a quantity.\n *\n * Usage:\n *\n * ```ts\n * const key = findMatchingCondition({\n * '<=-2.3': 'You have less than -2.3',\n * '<1': 'You have less than one',\n * '2': 'You have two',\n * '>=3': 'You have three or more',\n * }, 2);\n * // '2'\n * ```\n *\n * The order of the keys will define the priority of the content.\n *\n * ```ts\n * const key = findMatchingCondition({\n * '<4': 'You have less than four',\n * '2': 'You have two',\n * }, 2);\n * // '<4'\n * ```\n *\n * If no keys match, the default key is '1'.\n */\nexport const findMatchingCondition = <const Content>(\n enumerationContent: EnumerationContentState<Content>,\n quantity: number\n): EnterFormat | undefined => {\n const numericKeys = Object.keys(enumerationContent);\n\n for (const key of numericKeys) {\n const isEqual =\n (!key.startsWith('>') &&\n !key.startsWith('<') &&\n !key.startsWith('=') &&\n parseFloat(key) === quantity) ||\n (key.startsWith('=') && parseFloat(key.slice(1)) === quantity);\n const isSuperior =\n key.startsWith('>') && quantity > parseFloat(key.slice(1));\n const isSuperiorOrEqual =\n key.startsWith('>=') && quantity >= parseFloat(key.slice(2));\n const isInferior =\n key.startsWith('<') && quantity < parseFloat(key.slice(1));\n const isInferiorOrEqual =\n key.startsWith('<=') && quantity <= parseFloat(key.slice(2));\n\n if (\n isEqual ||\n isSuperior ||\n isSuperiorOrEqual ||\n isInferior ||\n isInferiorOrEqual\n ) {\n return key as EnterFormat;\n }\n }\n};\n\n/**\n * Picks content from an enumeration map based on a provided quantity.\n *\n * Supported keys in the enumeration map:\n * - Specific numbers: '0', '1', '2'\n * - Comparison operators: '<5', '>=10', '<=2'\n * - Fallback: 'fallback'\n *\n * The first matching key in the object's iteration order will be selected.\n *\n * @param enumerationContent - A map of conditions/quantities to content.\n * @param quantity - The number to match against the conditions.\n * @returns The matching content.\n *\n * @example\n * ```ts\n * const content = getEnumeration({\n * '0': 'No items',\n * '1': 'One item',\n * '>1': 'Many items',\n * }, 5);\n * // 'Many items'\n * ```\n */\nexport const getEnumeration = <const Content>(\n enumerationContent: EnumerationContentState<Content>,\n quantity: number\n): Content => {\n const key =\n findMatchingCondition<Content>(enumerationContent, quantity) ?? 'fallback';\n\n // Default or error handling if no keys match\n return enumerationContent[key] as Content;\n};\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"getEnumeration.cjs","names":[],"sources":["../../../src/interpreter/getEnumeration.ts"],"sourcesContent":["import type { EnterFormat, EnumerationContentState } from '../transpiler';\n\n/**\n * Find the matching condition for a quantity.\n *\n * Usage:\n *\n * ```ts\n * const key = findMatchingCondition({\n * '<=-2.3': 'You have less than -2.3',\n * '<1': 'You have less than one',\n * '2': 'You have two',\n * '>=3': 'You have three or more',\n * }, 2);\n * // '2'\n * ```\n *\n * The order of the keys will define the priority of the content.\n *\n * ```ts\n * const key = findMatchingCondition({\n * '<4': 'You have less than four',\n * '2': 'You have two',\n * }, 2);\n * // '<4'\n * ```\n *\n * If no keys match, the default key is '1'.\n */\nexport const findMatchingCondition = <const Content>(\n enumerationContent: EnumerationContentState<Content>,\n quantity: number\n): EnterFormat | undefined => {\n const numericKeys = Object.keys(enumerationContent);\n\n for (const key of numericKeys) {\n const isEqual =\n (!key.startsWith('>') &&\n !key.startsWith('<') &&\n !key.startsWith('=') &&\n parseFloat(key) === quantity) ||\n (key.startsWith('=') && parseFloat(key.slice(1)) === quantity);\n const isSuperior =\n key.startsWith('>') && quantity > parseFloat(key.slice(1));\n const isSuperiorOrEqual =\n key.startsWith('>=') && quantity >= parseFloat(key.slice(2));\n const isInferior =\n key.startsWith('<') && quantity < parseFloat(key.slice(1));\n const isInferiorOrEqual =\n key.startsWith('<=') && quantity <= parseFloat(key.slice(2));\n\n if (\n isEqual ||\n isSuperior ||\n isSuperiorOrEqual ||\n isInferior ||\n isInferiorOrEqual\n ) {\n return key as EnterFormat;\n }\n }\n};\n\n/**\n * Picks content from an enumeration map based on a provided quantity.\n *\n * Supported keys in the enumeration map:\n * - Specific numbers: '0', '1', '2'\n * - Comparison operators: '<5', '>=10', '<=2'\n * - Fallback: 'fallback'\n *\n * The first matching key in the object's iteration order will be selected.\n *\n * @param enumerationContent - A map of conditions/quantities to content.\n * @param quantity - The number to match against the conditions.\n * @returns The matching content.\n *\n * @example\n * ```ts\n * const content = getEnumeration({\n * '0': 'No items',\n * '1': 'One item',\n * '>1': 'Many items',\n * }, 5);\n * // 'Many items'\n * ```\n */\nexport const getEnumeration = <const Content>(\n enumerationContent: EnumerationContentState<Content>,\n quantity: number\n): Content => {\n const key =\n findMatchingCondition<Content>(enumerationContent, quantity) ?? 'fallback';\n\n // Default or error handling if no keys match\n return enumerationContent[key] as Content;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,MAAa,yBACX,oBACA,aAC4B;CAC5B,MAAM,cAAc,OAAO,KAAK,kBAAkB;CAElD,KAAK,MAAM,OAAO,aAAa;EAC7B,MAAM,UACH,CAAC,IAAI,WAAW,GAAG,KAClB,CAAC,IAAI,WAAW,GAAG,KACnB,CAAC,IAAI,WAAW,GAAG,KACnB,WAAW,GAAG,MAAM,YACrB,IAAI,WAAW,GAAG,KAAK,WAAW,IAAI,MAAM,CAAC,CAAC,MAAM;EACvD,MAAM,aACJ,IAAI,WAAW,GAAG,KAAK,WAAW,WAAW,IAAI,MAAM,CAAC,CAAC;EAC3D,MAAM,oBACJ,IAAI,WAAW,IAAI,KAAK,YAAY,WAAW,IAAI,MAAM,CAAC,CAAC;EAC7D,MAAM,aACJ,IAAI,WAAW,GAAG,KAAK,WAAW,WAAW,IAAI,MAAM,CAAC,CAAC;EAC3D,MAAM,oBACJ,IAAI,WAAW,IAAI,KAAK,YAAY,WAAW,IAAI,MAAM,CAAC,CAAC;EAE7D,IACE,WACA,cACA,qBACA,cACA,mBAEA,OAAO;CAEX;AACF;;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,MAAa,kBACX,oBACA,aACY;CAKZ,OAAO,mBAHL,sBAA+B,oBAAoB,QAAQ,KAAK;AAIpE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getGender.cjs","names":[],"sources":["../../../src/interpreter/getGender.ts"],"sourcesContent":["import type { Gender, GenderContentStates } from '../transpiler';\n\ntype GenderEntry = Gender | 'm' | 'f';\n\nconst getGenderEntry = (gender: GenderEntry): Gender => {\n if (gender === 'm' || gender === 'male') return 'male';\n if (gender === 'f' || gender === 'female') return 'female';\n return 'fallback';\n};\n\n/**\n * Allow to pick a content based on a gender.\n *\n * Usage:\n *\n * ```ts\n * const content = getGender({\n * 'true': 'The gender is validated',\n * 'false': 'The gender is not validated',\n * }, true);\n * // 'The gender is validated'\n * ```\n *\n * The last key provided will be used as the fallback value.\n *\n * ```ts\n * const content = getGender({\n * 'false': 'The gender is not validated',\n * 'true': 'The gender is validated',\n * }, undefined);\n * // 'The gender is validated'\n */\nexport const getGender = <const Content>(\n genderContent: GenderContentStates<Content>,\n gender: GenderEntry\n): Content => {\n const stateList = Object.keys(genderContent);\n\n const fallbackState = stateList[\n stateList.length - 1\n ] as keyof typeof genderContent;\n\n const genderEntry = getGenderEntry(gender);\n\n // Default or error handling if no keys match\n return (\n genderContent[genderEntry as keyof typeof genderContent] ??\n genderContent.fallback ??\n (genderContent[fallbackState] as Content)\n );\n};\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"getGender.cjs","names":[],"sources":["../../../src/interpreter/getGender.ts"],"sourcesContent":["import type { Gender, GenderContentStates } from '../transpiler';\n\ntype GenderEntry = Gender | 'm' | 'f';\n\nconst getGenderEntry = (gender: GenderEntry): Gender => {\n if (gender === 'm' || gender === 'male') return 'male';\n if (gender === 'f' || gender === 'female') return 'female';\n return 'fallback';\n};\n\n/**\n * Allow to pick a content based on a gender.\n *\n * Usage:\n *\n * ```ts\n * const content = getGender({\n * 'true': 'The gender is validated',\n * 'false': 'The gender is not validated',\n * }, true);\n * // 'The gender is validated'\n * ```\n *\n * The last key provided will be used as the fallback value.\n *\n * ```ts\n * const content = getGender({\n * 'false': 'The gender is not validated',\n * 'true': 'The gender is validated',\n * }, undefined);\n * // 'The gender is validated'\n */\nexport const getGender = <const Content>(\n genderContent: GenderContentStates<Content>,\n gender: GenderEntry\n): Content => {\n const stateList = Object.keys(genderContent);\n\n const fallbackState = stateList[\n stateList.length - 1\n ] as keyof typeof genderContent;\n\n const genderEntry = getGenderEntry(gender);\n\n // Default or error handling if no keys match\n return (\n genderContent[genderEntry as keyof typeof genderContent] ??\n genderContent.fallback ??\n (genderContent[fallbackState] as Content)\n );\n};\n"],"mappings":";;AAIA,MAAM,kBAAkB,WAAgC;CACtD,IAAI,WAAW,OAAO,WAAW,QAAQ,OAAO;CAChD,IAAI,WAAW,OAAO,WAAW,UAAU,OAAO;CAClD,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;AAwBA,MAAa,aACX,eACA,WACY;CACZ,MAAM,YAAY,OAAO,KAAK,aAAa;CAE3C,MAAM,gBAAgB,UACpB,UAAU,SAAS;CAMrB,OACE,cAJkB,eAAe,MAIT,MACxB,cAAc,YACb,cAAc;AAEnB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getHTML.cjs","names":[],"sources":["../../../src/interpreter/getHTML.ts"],"sourcesContent":["/**\n * Component function that receives properly typed props\n * Props include:\n * - children?: any[] - Array of child elements (ReactNode[], VNode[], etc.)\n * - [key: string]: any - Any HTML attributes (className, style, href, etc.)\n */\ntype Component = (props: { children?: any[]; [key: string]: any }) => any;\n\n/**\n * Component object that delegates to another component\n */\ntype ComponentObject = {\n tag: string;\n props?: Record<string, any>;\n};\n\n/**\n * Components map:\n * - Function components receive typed props\n * - String components delegate to another tag\n * - Object components delegate with additional props\n */\ntype Components = Record<string, Component | string | ComponentObject>;\n\n/**\n * Helper to parse attributes from a tag string.\n */\nconst parseAttributes = (attributes: string): Record<string, string> => {\n const props: Record<string, string> = {};\n const attrRegex = /([a-zA-Z0-9-]+)=\"([^\"]*)\"/g;\n\n let match = attrRegex.exec(attributes);\n while (match !== null) {\n props[match[1]] = match[2];\n match = attrRegex.exec(attributes);\n }\n return props;\n};\n\ntype ASTNode =\n | string\n | {\n tagName: string;\n props: Record<string, string>;\n children: ASTNode[];\n };\n\nconst astCache = new Map<string, ASTNode[]>();\n\nconst parseHTML = (content: string): ASTNode[] => {\n if (astCache.has(content)) {\n return astCache.get(content)!;\n }\n\n if (typeof content !== 'string') {\n return [];\n }\n\n const tagRegex = /<(\\/)?([a-zA-Z0-9.-]+)([\\s\\S]*?)(\\/?)>/g;\n const elements: ASTNode[] = [];\n const stack: {\n tagName: string;\n children: ASTNode[];\n props: Record<string, string>;\n }[] = [];\n\n let lastIndex = 0;\n let match = tagRegex.exec(content);\n\n const appendChild = (child: ASTNode) => {\n const target =\n stack.length > 0 ? stack[stack.length - 1].children : elements;\n target.push(child);\n };\n\n while (match !== null) {\n const [fullMatch, isClosingRaw, tagName, attributesRaw, isSelfClosingRaw] =\n match;\n const matchIndex = match.index;\n\n if (matchIndex > lastIndex) {\n appendChild(content.slice(lastIndex, matchIndex));\n }\n\n const isClosing = isClosingRaw === '/';\n const isSelfClosing =\n isSelfClosingRaw === '/' ||\n attributesRaw.trim().endsWith('/') ||\n fullMatch.endsWith('/>');\n\n const cleanedAttributes = attributesRaw.trim().replace(/\\/$/, '').trim();\n\n if (isClosing) {\n const last = stack[stack.length - 1];\n\n // Only pop if the tag names match\n if (last && last.tagName === tagName) {\n const popped = stack.pop();\n if (popped) {\n appendChild({\n tagName: popped.tagName,\n props: popped.props,\n children: popped.children,\n });\n }\n }\n // If tags don't match or no open tag, silently ignore the closing tag\n } else if (isSelfClosing) {\n const tagProps = parseAttributes(cleanedAttributes);\n appendChild({\n tagName,\n props: tagProps,\n children: [],\n });\n } else {\n const tagProps = parseAttributes(cleanedAttributes);\n stack.push({ tagName, children: [], props: tagProps });\n }\n\n lastIndex = matchIndex + fullMatch.length;\n match = tagRegex.exec(content);\n }\n\n if (lastIndex < content.length) {\n appendChild(content.slice(lastIndex));\n }\n\n // Handle unclosed tags by appending them to the root or parent\n while (stack.length > 0) {\n const last = stack.pop();\n if (last) {\n appendChild({\n tagName: last.tagName,\n props: last.props,\n children: last.children,\n });\n }\n }\n\n astCache.set(content, elements);\n return elements;\n};\n\n/**\n * Interprets a string containing HTML-like tags and replaces them with provided components or strings.\n */\nexport const getHTML = (content: string, values: Components): any => {\n // Parse into AST (cached)\n const ast = parseHTML(content);\n\n // Render AST\n let keyCounter = 0;\n\n const renderASTNode = (node: ASTNode): any => {\n if (typeof node === 'string') {\n return node;\n }\n\n const { tagName, props, children } = node;\n const renderedChildren = children.flatMap(renderASTNode);\n const index = keyCounter++;\n\n let override = values[tagName];\n\n if (!override) {\n const lowerTagName = tagName.toLowerCase();\n const foundKey = Object.keys(values).find(\n (key) => key.toLowerCase() === lowerTagName\n );\n\n if (foundKey) override = values[foundKey];\n }\n\n const key = `html-tag-${tagName}-${index}`;\n\n if (typeof override === 'function') {\n return override({ ...props, children: renderedChildren, key });\n }\n\n if (typeof override === 'string') {\n const component = values[override];\n\n if (typeof component === 'function') {\n return component({ ...props, children: renderedChildren, key });\n }\n return renderedChildren;\n }\n\n if (\n typeof override === 'object' &&\n override !== null &&\n 'tag' in override\n ) {\n const { tag: targetTag, props: extraProps } = override as ComponentObject;\n const component = values[targetTag];\n\n if (typeof component === 'function') {\n return component({\n ...props,\n ...extraProps,\n children: renderedChildren,\n key,\n });\n }\n return renderedChildren;\n }\n\n // Default: Skip tag, render children\n return renderedChildren;\n };\n\n const result = ast.flatMap(renderASTNode);\n return result.length === 1 ? result[0] : result;\n};\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"getHTML.cjs","names":[],"sources":["../../../src/interpreter/getHTML.ts"],"sourcesContent":["/**\n * Component function that receives properly typed props\n * Props include:\n * - children?: any[] - Array of child elements (ReactNode[], VNode[], etc.)\n * - [key: string]: any - Any HTML attributes (className, style, href, etc.)\n */\ntype Component = (props: { children?: any[]; [key: string]: any }) => any;\n\n/**\n * Component object that delegates to another component\n */\ntype ComponentObject = {\n tag: string;\n props?: Record<string, any>;\n};\n\n/**\n * Components map:\n * - Function components receive typed props\n * - String components delegate to another tag\n * - Object components delegate with additional props\n */\ntype Components = Record<string, Component | string | ComponentObject>;\n\n/**\n * Helper to parse attributes from a tag string.\n */\nconst parseAttributes = (attributes: string): Record<string, string> => {\n const props: Record<string, string> = {};\n const attrRegex = /([a-zA-Z0-9-]+)=\"([^\"]*)\"/g;\n\n let match = attrRegex.exec(attributes);\n while (match !== null) {\n props[match[1]] = match[2];\n match = attrRegex.exec(attributes);\n }\n return props;\n};\n\ntype ASTNode =\n | string\n | {\n tagName: string;\n props: Record<string, string>;\n children: ASTNode[];\n };\n\nconst astCache = new Map<string, ASTNode[]>();\n\nconst parseHTML = (content: string): ASTNode[] => {\n if (astCache.has(content)) {\n return astCache.get(content)!;\n }\n\n if (typeof content !== 'string') {\n return [];\n }\n\n const tagRegex = /<(\\/)?([a-zA-Z0-9.-]+)([\\s\\S]*?)(\\/?)>/g;\n const elements: ASTNode[] = [];\n const stack: {\n tagName: string;\n children: ASTNode[];\n props: Record<string, string>;\n }[] = [];\n\n let lastIndex = 0;\n let match = tagRegex.exec(content);\n\n const appendChild = (child: ASTNode) => {\n const target =\n stack.length > 0 ? stack[stack.length - 1].children : elements;\n target.push(child);\n };\n\n while (match !== null) {\n const [fullMatch, isClosingRaw, tagName, attributesRaw, isSelfClosingRaw] =\n match;\n const matchIndex = match.index;\n\n if (matchIndex > lastIndex) {\n appendChild(content.slice(lastIndex, matchIndex));\n }\n\n const isClosing = isClosingRaw === '/';\n const isSelfClosing =\n isSelfClosingRaw === '/' ||\n attributesRaw.trim().endsWith('/') ||\n fullMatch.endsWith('/>');\n\n const cleanedAttributes = attributesRaw.trim().replace(/\\/$/, '').trim();\n\n if (isClosing) {\n const last = stack[stack.length - 1];\n\n // Only pop if the tag names match\n if (last && last.tagName === tagName) {\n const popped = stack.pop();\n if (popped) {\n appendChild({\n tagName: popped.tagName,\n props: popped.props,\n children: popped.children,\n });\n }\n }\n // If tags don't match or no open tag, silently ignore the closing tag\n } else if (isSelfClosing) {\n const tagProps = parseAttributes(cleanedAttributes);\n appendChild({\n tagName,\n props: tagProps,\n children: [],\n });\n } else {\n const tagProps = parseAttributes(cleanedAttributes);\n stack.push({ tagName, children: [], props: tagProps });\n }\n\n lastIndex = matchIndex + fullMatch.length;\n match = tagRegex.exec(content);\n }\n\n if (lastIndex < content.length) {\n appendChild(content.slice(lastIndex));\n }\n\n // Handle unclosed tags by appending them to the root or parent\n while (stack.length > 0) {\n const last = stack.pop();\n if (last) {\n appendChild({\n tagName: last.tagName,\n props: last.props,\n children: last.children,\n });\n }\n }\n\n astCache.set(content, elements);\n return elements;\n};\n\n/**\n * Interprets a string containing HTML-like tags and replaces them with provided components or strings.\n */\nexport const getHTML = (content: string, values: Components): any => {\n // Parse into AST (cached)\n const ast = parseHTML(content);\n\n // Render AST\n let keyCounter = 0;\n\n const renderASTNode = (node: ASTNode): any => {\n if (typeof node === 'string') {\n return node;\n }\n\n const { tagName, props, children } = node;\n const renderedChildren = children.flatMap(renderASTNode);\n const index = keyCounter++;\n\n let override = values[tagName];\n\n if (!override) {\n const lowerTagName = tagName.toLowerCase();\n const foundKey = Object.keys(values).find(\n (key) => key.toLowerCase() === lowerTagName\n );\n\n if (foundKey) override = values[foundKey];\n }\n\n const key = `html-tag-${tagName}-${index}`;\n\n if (typeof override === 'function') {\n return override({ ...props, children: renderedChildren, key });\n }\n\n if (typeof override === 'string') {\n const component = values[override];\n\n if (typeof component === 'function') {\n return component({ ...props, children: renderedChildren, key });\n }\n return renderedChildren;\n }\n\n if (\n typeof override === 'object' &&\n override !== null &&\n 'tag' in override\n ) {\n const { tag: targetTag, props: extraProps } = override as ComponentObject;\n const component = values[targetTag];\n\n if (typeof component === 'function') {\n return component({\n ...props,\n ...extraProps,\n children: renderedChildren,\n key,\n });\n }\n return renderedChildren;\n }\n\n // Default: Skip tag, render children\n return renderedChildren;\n };\n\n const result = ast.flatMap(renderASTNode);\n return result.length === 1 ? result[0] : result;\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getInsertion.cjs","names":[],"sources":["../../../src/interpreter/getInsertion.ts"],"sourcesContent":["import type { InsertionContent } from '../transpiler';\n\n/**\n * Allow to insert values in a string.\n *\n * Usage:\n *\n * ```ts\n * const content = getInsertion('Hello {{name}}!', {\n * name: 'John',\n * });\n * // 'Hello John!'\n * ```\n *\n */\nexport const getInsertion = (\n content: string,\n values: { [K in InsertionContent['fields'][number]]: string | number }\n) =>\n content.replace(/\\{\\{\\s*(.*?)\\s*\\}\\}/g, (_, key) => {\n const trimmedKey = key.trim();\n return (values[trimmedKey] ?? '').toString();\n });\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"getInsertion.cjs","names":[],"sources":["../../../src/interpreter/getInsertion.ts"],"sourcesContent":["import type { InsertionContent } from '../transpiler';\n\n/**\n * Allow to insert values in a string.\n *\n * Usage:\n *\n * ```ts\n * const content = getInsertion('Hello {{name}}!', {\n * name: 'John',\n * });\n * // 'Hello John!'\n * ```\n *\n */\nexport const getInsertion = (\n content: string,\n values: { [K in InsertionContent['fields'][number]]: string | number }\n) =>\n content.replace(/\\{\\{\\s*(.*?)\\s*\\}\\}/g, (_, key) => {\n const trimmedKey = key.trim();\n return (values[trimmedKey] ?? '').toString();\n });\n"],"mappings":";;;;;;;;;;;;;;;AAeA,MAAa,gBACX,SACA,WAEA,QAAQ,QAAQ,yBAAyB,GAAG,QAAQ;CAElD,QAAQ,OADW,IAAI,KACC,MAAM,GAAE,CAAE,SAAS;AAC7C,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getIntlayer.cjs","names":["parseDictionarySelector","getDictionarySelectorCacheKey","getDictionary"],"sources":["../../../src/interpreter/getIntlayer.ts"],"sourcesContent":["import { log } from '@intlayer/config/built';\nimport { colorizeKey, getAppLogger } from '@intlayer/config/logger';\nimport { getDictionaries } from '@intlayer/dictionaries-entry';\nimport type { DictionarySelector } from '@intlayer/types/dictionary';\nimport type {\n DeclaredLocales,\n DictionaryKeys,\n DictionaryRegistryResult,\n ExtractSelectorLocale,\n LocalesValues,\n} from '@intlayer/types/module_augmentation';\nimport {\n getDictionarySelectorCacheKey,\n parseDictionarySelector,\n} from '../dictionaryManipulator/qualifiedDictionary';\nimport type {\n DeepTransformContent,\n IInterpreterPluginState,\n Plugins,\n} from './getContent';\nimport { getDictionary } from './getDictionary';\n\n/**\n * Creates a Recursive Proxy that returns the path of the accessed key\n * stringified. This prevents the app from crashing on undefined access.\n */\nconst createSafeFallback = (path = ''): any => {\n return new Proxy({} as Record<string | symbol, unknown>, {\n get: (_target, prop) => {\n if (\n prop === 'toJSON' ||\n prop === Symbol.toPrimitive ||\n prop === 'toString' ||\n prop === 'valueOf'\n ) {\n return () => path;\n }\n if (prop === 'then') {\n return undefined; // Prevent it from being treated as a Promise\n }\n if (prop === Symbol.iterator) {\n return function* () {\n yield path;\n };\n }\n\n // Recursively build the path (e.g., \"myDictionary.home.title\")\n const nextPath = path ? `${path}.${String(prop)}` : String(prop);\n return createSafeFallback(nextPath);\n },\n });\n};\n\nconst dictionaryCache = new Map<string, any>();\nconst warnedMissingDictionaries = new Set<string>();\n\n/**\n * Picks one dictionary by its key and returns its content for the given\n * locale or selector.\n *\n * The second argument is either a locale (`'fr'`) or a selector object:\n * - `{ item: 2 }` — collection item (omit `item` to get every item as array)\n * - `{ variant: 'black-friday' }` — named variant (omit for the `default` one)\n * - `{ variant: { id: 'prod_abc', userId: '123' } }` — structured variant\n * - `locale` can be combined with any selector: `{ item: 2, locale: 'fr' }`\n */\nexport const getIntlayer = <\n const T extends DictionaryKeys,\n const A extends LocalesValues | DictionarySelector = DeclaredLocales,\n>(\n key: T,\n localeOrSelector?: A,\n plugins?: Plugins[]\n): DeepTransformContent<\n DictionaryRegistryResult<T, A>,\n IInterpreterPluginState,\n ExtractSelectorLocale<A>\n> => {\n const dictionaries = getDictionaries();\n const dictionary = dictionaries[key as T];\n\n if (!dictionary && process.env.NODE_ENV === 'development') {\n if (!warnedMissingDictionaries.has(key as string)) {\n // Log a warning instead of throwing (so developers know it's missing)\n const logger = getAppLogger({ log });\n logger(\n typeof window === 'undefined'\n ? `Dictionary ${colorizeKey(key)} was not found. Using fallback proxy.`\n : `Dictionary ${key} was not found. Using fallback proxy.`,\n {\n level: 'warn',\n }\n );\n warnedMissingDictionaries.add(key as string);\n }\n\n return createSafeFallback(key as string);\n }\n\n let locale: LocalesValues | undefined;\n let selectorCacheKey = '';\n\n if (process.env.INTLAYER_DICTIONARY_SELECTOR !== 'false') {\n const parsed = parseDictionarySelector(localeOrSelector);\n locale = parsed.locale;\n selectorCacheKey = getDictionarySelectorCacheKey(parsed.selector);\n } else {\n // Selectors are unused in this project (build-time flag): the second\n // argument can only be a locale, so the selector parsing is dead code.\n locale = localeOrSelector as LocalesValues | undefined;\n }\n\n const cacheKey = `${key}_${locale ?? 'default'}_${selectorCacheKey}_${plugins ? 'custom_plugins' : 'default_plugins'}`;\n\n if (dictionaryCache.has(cacheKey)) {\n return dictionaryCache.get(cacheKey);\n }\n\n const result = getDictionary(dictionary, localeOrSelector, plugins);\n\n dictionaryCache.set(cacheKey, result);\n\n return result as any;\n};\n"],"mappings":";;;;;;;;;;;;AA0BA,MAAM,sBAAsB,OAAO,OAAY;CAC7C,OAAO,IAAI,MAAM,CAAC,GAAuC,EACvD,MAAM,SAAS,SAAS;EACtB,IACE,SAAS,YACT,SAAS,OAAO,eAChB,SAAS,cACT,SAAS,WAET,aAAa;EAEf,IAAI,SAAS,QACX;EAEF,IAAI,SAAS,OAAO,UAClB,OAAO,aAAa;GAClB,MAAM;EACR;EAIF,MAAM,WAAW,OAAO,GAAG,KAAK,GAAG,OAAO,IAAI,MAAM,OAAO,IAAI;EAC/D,OAAO,mBAAmB,QAAQ;CACpC,EACF,CAAC;AACH;AAEA,MAAM,kCAAkB,IAAI,IAAiB;AAC7C,MAAM,4CAA4B,IAAI,IAAY;;;;;;;;;;;AAYlD,MAAa,eAIX,KACA,kBACA,YAKG;CAEH,MAAM
|
|
1
|
+
{"version":3,"file":"getIntlayer.cjs","names":["getDictionaries","getAppLogger","colorizeKey","parseDictionarySelector","getDictionarySelectorCacheKey","getDictionary"],"sources":["../../../src/interpreter/getIntlayer.ts"],"sourcesContent":["import { log } from '@intlayer/config/built';\nimport { colorizeKey, getAppLogger } from '@intlayer/config/logger';\nimport { getDictionaries } from '@intlayer/dictionaries-entry';\nimport type { DictionarySelector } from '@intlayer/types/dictionary';\nimport type {\n DeclaredLocales,\n DictionaryKeys,\n DictionaryRegistryResult,\n ExtractSelectorLocale,\n LocalesValues,\n} from '@intlayer/types/module_augmentation';\nimport {\n getDictionarySelectorCacheKey,\n parseDictionarySelector,\n} from '../dictionaryManipulator/qualifiedDictionary';\nimport type {\n DeepTransformContent,\n IInterpreterPluginState,\n Plugins,\n} from './getContent';\nimport { getDictionary } from './getDictionary';\n\n/**\n * Creates a Recursive Proxy that returns the path of the accessed key\n * stringified. This prevents the app from crashing on undefined access.\n */\nconst createSafeFallback = (path = ''): any => {\n return new Proxy({} as Record<string | symbol, unknown>, {\n get: (_target, prop) => {\n if (\n prop === 'toJSON' ||\n prop === Symbol.toPrimitive ||\n prop === 'toString' ||\n prop === 'valueOf'\n ) {\n return () => path;\n }\n if (prop === 'then') {\n return undefined; // Prevent it from being treated as a Promise\n }\n if (prop === Symbol.iterator) {\n return function* () {\n yield path;\n };\n }\n\n // Recursively build the path (e.g., \"myDictionary.home.title\")\n const nextPath = path ? `${path}.${String(prop)}` : String(prop);\n return createSafeFallback(nextPath);\n },\n });\n};\n\nconst dictionaryCache = new Map<string, any>();\nconst warnedMissingDictionaries = new Set<string>();\n\n/**\n * Picks one dictionary by its key and returns its content for the given\n * locale or selector.\n *\n * The second argument is either a locale (`'fr'`) or a selector object:\n * - `{ item: 2 }` — collection item (omit `item` to get every item as array)\n * - `{ variant: 'black-friday' }` — named variant (omit for the `default` one)\n * - `{ variant: { id: 'prod_abc', userId: '123' } }` — structured variant\n * - `locale` can be combined with any selector: `{ item: 2, locale: 'fr' }`\n */\nexport const getIntlayer = <\n const T extends DictionaryKeys,\n const A extends LocalesValues | DictionarySelector = DeclaredLocales,\n>(\n key: T,\n localeOrSelector?: A,\n plugins?: Plugins[]\n): DeepTransformContent<\n DictionaryRegistryResult<T, A>,\n IInterpreterPluginState,\n ExtractSelectorLocale<A>\n> => {\n const dictionaries = getDictionaries();\n const dictionary = dictionaries[key as T];\n\n if (!dictionary && process.env.NODE_ENV === 'development') {\n if (!warnedMissingDictionaries.has(key as string)) {\n // Log a warning instead of throwing (so developers know it's missing)\n const logger = getAppLogger({ log });\n logger(\n typeof window === 'undefined'\n ? `Dictionary ${colorizeKey(key)} was not found. Using fallback proxy.`\n : `Dictionary ${key} was not found. Using fallback proxy.`,\n {\n level: 'warn',\n }\n );\n warnedMissingDictionaries.add(key as string);\n }\n\n return createSafeFallback(key as string);\n }\n\n let locale: LocalesValues | undefined;\n let selectorCacheKey = '';\n\n if (process.env.INTLAYER_DICTIONARY_SELECTOR !== 'false') {\n const parsed = parseDictionarySelector(localeOrSelector);\n locale = parsed.locale;\n selectorCacheKey = getDictionarySelectorCacheKey(parsed.selector);\n } else {\n // Selectors are unused in this project (build-time flag): the second\n // argument can only be a locale, so the selector parsing is dead code.\n locale = localeOrSelector as LocalesValues | undefined;\n }\n\n const cacheKey = `${key}_${locale ?? 'default'}_${selectorCacheKey}_${plugins ? 'custom_plugins' : 'default_plugins'}`;\n\n if (dictionaryCache.has(cacheKey)) {\n return dictionaryCache.get(cacheKey);\n }\n\n const result = getDictionary(dictionary, localeOrSelector, plugins);\n\n dictionaryCache.set(cacheKey, result);\n\n return result as any;\n};\n"],"mappings":";;;;;;;;;;;;AA0BA,MAAM,sBAAsB,OAAO,OAAY;CAC7C,OAAO,IAAI,MAAM,CAAC,GAAuC,EACvD,MAAM,SAAS,SAAS;EACtB,IACE,SAAS,YACT,SAAS,OAAO,eAChB,SAAS,cACT,SAAS,WAET,aAAa;EAEf,IAAI,SAAS,QACX;EAEF,IAAI,SAAS,OAAO,UAClB,OAAO,aAAa;GAClB,MAAM;EACR;EAIF,MAAM,WAAW,OAAO,GAAG,KAAK,GAAG,OAAO,IAAI,MAAM,OAAO,IAAI;EAC/D,OAAO,mBAAmB,QAAQ;CACpC,EACF,CAAC;AACH;AAEA,MAAM,kCAAkB,IAAI,IAAiB;AAC7C,MAAM,4CAA4B,IAAI,IAAY;;;;;;;;;;;AAYlD,MAAa,eAIX,KACA,kBACA,YAKG;CAEH,MAAM,iBADeA,8CACS,CAAC,CAAC;CAEhC,IAAI,CAAC,cAAc,QAAQ,IAAI,aAAa,eAAe;EACzD,IAAI,CAAC,0BAA0B,IAAI,GAAa,GAAG;GAGjD,IADeC,sCAAa,EAAE,gCAAI,CAC7B,CAAC,CACJ,OAAO,WAAW,cACd,kBAAcC,qCAAY,GAAG,EAAE,yCAC/B,cAAc,IAAI,wCACtB,EACE,OAAO,OACT,CACF;GACA,0BAA0B,IAAI,GAAa;EAC7C;EAEA,OAAO,mBAAmB,GAAa;CACzC;CAEA,IAAI;CACJ,IAAI,mBAAmB;CAEvB,IAAI,QAAQ,IAAI,iCAAiC,SAAS;EACxD,MAAM,SAASC,0EAAwB,gBAAgB;EACvD,SAAS,OAAO;EAChB,mBAAmBC,gFAA8B,OAAO,QAAQ;CAClE,OAGE,SAAS;CAGX,MAAM,WAAW,GAAG,IAAI,GAAG,UAAU,UAAU,GAAG,iBAAiB,GAAG,UAAU,mBAAmB;CAEnG,IAAI,gBAAgB,IAAI,QAAQ,GAC9B,OAAO,gBAAgB,IAAI,QAAQ;CAGrC,MAAM,SAASC,gDAAc,YAAY,kBAAkB,OAAO;CAElE,gBAAgB,IAAI,UAAU,MAAM;CAEpC,OAAO;AACT"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getIntlayer.test-d.cjs","names":["getIntlayer"],"sources":["../../../src/interpreter/getIntlayer.test-d.ts"],"sourcesContent":["import { describe, expectTypeOf, it } from 'vitest';\nimport { getIntlayer } from './getIntlayer';\n\n/**\n * The second argument of `getIntlayer` is almost always a value the framework\n * hands over as a plain `string`: a router param (`params.locale`), a cookie, a\n * header. Constraining it to the declared locales made every such call site a\n * compile error, so the declared locales are offered as suggestions while any\n * string is still accepted.\n */\ndescribe('getIntlayer — locale argument', () => {\n it('should accept a locale literal', () => {\n expectTypeOf(getIntlayer('lesson', 'fr')).not.toBeNever();\n });\n\n it('should accept a `string | undefined` router param', () => {\n const routerLocale = 'fr' as string | undefined;\n\n expectTypeOf(getIntlayer('lesson', routerLocale)).not.toBeNever();\n });\n\n it('should accept a selector carrying a widened locale', () => {\n const routerLocale = 'fr' as string;\n\n expectTypeOf(\n getIntlayer('lesson', { locale: routerLocale, item: 1 })\n ).not.toBeNever();\n });\n});\n"],"mappings":";;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"getIntlayer.test-d.cjs","names":["describe","getIntlayer"],"sources":["../../../src/interpreter/getIntlayer.test-d.ts"],"sourcesContent":["import { describe, expectTypeOf, it } from 'vitest';\nimport { getIntlayer } from './getIntlayer';\n\n/**\n * The second argument of `getIntlayer` is almost always a value the framework\n * hands over as a plain `string`: a router param (`params.locale`), a cookie, a\n * header. Constraining it to the declared locales made every such call site a\n * compile error, so the declared locales are offered as suggestions while any\n * string is still accepted.\n */\ndescribe('getIntlayer — locale argument', () => {\n it('should accept a locale literal', () => {\n expectTypeOf(getIntlayer('lesson', 'fr')).not.toBeNever();\n });\n\n it('should accept a `string | undefined` router param', () => {\n const routerLocale = 'fr' as string | undefined;\n\n expectTypeOf(getIntlayer('lesson', routerLocale)).not.toBeNever();\n });\n\n it('should accept a selector carrying a widened locale', () => {\n const routerLocale = 'fr' as string;\n\n expectTypeOf(\n getIntlayer('lesson', { locale: routerLocale, item: 1 })\n ).not.toBeNever();\n });\n});\n"],"mappings":";;;;;;;;;;;IAUAA,iBAAS,uCAAuC;CAC9C,eAAG,wCAAwC;EACzC,yBAAaC,4CAAY,UAAU,IAAI,CAAC,CAAC,CAAC,IAAI,UAAU;CAC1D,CAAC;CAED,eAAG,2DAA2D;EAG5D,yBAAaA,4CAAY,UAAU,IAAY,CAAC,CAAC,CAAC,IAAI,UAAU;CAClE,CAAC;CAED,eAAG,4DAA4D;EAG7D,yBACEA,4CAAY,UAAU;GAAE,QAAQ;GAAc,MAAM;EAAE,CAAC,CACzD,CAAC,CAAC,IAAI,UAAU;CAClB,CAAC;AACH,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getSelect.cjs","names":[],"sources":["../../../src/interpreter/getSelect.ts"],"sourcesContent":["import type { SelectContentStates } from '../transpiler';\n\n/**\n * Picks content from a select map based on an arbitrary string value.\n *\n * Resolution order:\n * 1. Exact match on the provided value.\n * 2. The `fallback` key.\n * 3. The `other` key — kept for content imported from an ICU `select` message,\n * where the catch-all case is named `other`.\n * 4. The last declared key, so a select node never resolves to `undefined`.\n *\n * @param selectContent - A map of string cases to content.\n * @param value - The value to match against the declared cases.\n * @returns The matching content.\n *\n * @example\n * ```ts\n * const content = getSelect({\n * draft: 'This post is a draft',\n * published: 'This post is live',\n * fallback: 'Unknown status',\n * }, 'published');\n * // 'This post is live'\n * ```\n */\nexport const getSelect = <const Content>(\n selectContent: SelectContentStates<Content>,\n value?: string\n): Content => {\n const caseList = Object.keys(selectContent);\n const lastCase = caseList[caseList.length - 1] as keyof typeof selectContent;\n\n return (selectContent[value as keyof typeof selectContent] ??\n selectContent.fallback ??\n selectContent.other ??\n selectContent[lastCase]) as Content;\n};\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"getSelect.cjs","names":[],"sources":["../../../src/interpreter/getSelect.ts"],"sourcesContent":["import type { SelectContentStates } from '../transpiler';\n\n/**\n * Picks content from a select map based on an arbitrary string value.\n *\n * Resolution order:\n * 1. Exact match on the provided value.\n * 2. The `fallback` key.\n * 3. The `other` key — kept for content imported from an ICU `select` message,\n * where the catch-all case is named `other`.\n * 4. The last declared key, so a select node never resolves to `undefined`.\n *\n * @param selectContent - A map of string cases to content.\n * @param value - The value to match against the declared cases.\n * @returns The matching content.\n *\n * @example\n * ```ts\n * const content = getSelect({\n * draft: 'This post is a draft',\n * published: 'This post is live',\n * fallback: 'Unknown status',\n * }, 'published');\n * // 'This post is live'\n * ```\n */\nexport const getSelect = <const Content>(\n selectContent: SelectContentStates<Content>,\n value?: string\n): Content => {\n const caseList = Object.keys(selectContent);\n const lastCase = caseList[caseList.length - 1] as keyof typeof selectContent;\n\n return (selectContent[value as keyof typeof selectContent] ??\n selectContent.fallback ??\n selectContent.other ??\n selectContent[lastCase]) as Content;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,MAAa,aACX,eACA,UACY;CACZ,MAAM,WAAW,OAAO,KAAK,aAAa;CAC1C,MAAM,WAAW,SAAS,SAAS,SAAS;CAE5C,OAAQ,cAAc,UACpB,cAAc,YACd,cAAc,SACd,cAAc;AAClB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getTranslation.cjs","names":[],"sources":["../../../src/interpreter/getTranslation.ts"],"sourcesContent":["import type {\n LocalesValues,\n StrictModeLocaleMap,\n} from '@intlayer/types/module_augmentation';\n\n/**\n * Check if a value is a plain object that can be safely merged.\n * Returns false for Promises, React elements, class instances, etc.\n */\nconst isPlainObject = (value: unknown): boolean => {\n if (value === null || typeof value !== 'object') return false;\n if (typeof (value as any).then === 'function') return false;\n if (\n (value as any).$$typeof !== undefined ||\n (value as any).__v_isVNode !== undefined ||\n (value as any)._isVNode !== undefined ||\n (value as any).isJSX !== undefined\n ) {\n return false;\n }\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null || Array.isArray(value);\n};\n\n/**\n * Recursively merges two objects, skipping undefined source values.\n * First argument takes precedence. Arrays replace rather than merge.\n */\nconst deepMerge = (target: any, source: any): any => {\n if (target === undefined) return source;\n if (source === undefined) return target;\n if (Array.isArray(target)) return target;\n if (isPlainObject(target) && isPlainObject(source)) {\n const result = { ...target };\n\n for (const key of Object.keys(source)) {\n if (\n key === '__proto__' ||\n key === 'constructor' ||\n source[key] === undefined\n )\n continue;\n result[key] =\n target[key] !== undefined\n ? deepMerge(target[key], source[key])\n : source[key];\n }\n return result;\n }\n return target;\n};\n\n/**\n * Picks the appropriate content from a locale map based on the provided locale.\n *\n * It handles:\n * 1. Exact locale match (e.g., 'en-US').\n * 2. Generic locale fallback (e.g., 'en' if 'en-US' is not found).\n * 3. Explicit fallback locale.\n * 4. Deep merging of objects to ensure partial translations are complemented by fallbacks.\n *\n * @param languageContent - A map of locales to content.\n * @param locale - The target locale to retrieve.\n * @param fallback - Optional fallback locale if the target is not found.\n * @returns The translated content.\n *\n * @example\n * ```ts\n * const content = getTranslation({\n * en: 'Hello',\n * fr: 'Bonjour',\n * }, 'fr');\n * // 'Bonjour'\n * ```\n */\nexport const getTranslation = <const Content = string>(\n languageContent: StrictModeLocaleMap<Content>,\n locale: LocalesValues,\n fallback?: LocalesValues\n): Content => {\n const get = (loc: string): Content | undefined =>\n languageContent[loc as keyof typeof languageContent];\n\n // Build priority-ordered locale candidates (most specific first), deduped\n const seen = new Set<string>();\n const locales: string[] = [];\n const addLocale = (loc: string | undefined) => {\n if (loc && !seen.has(loc)) {\n seen.add(loc);\n locales.push(loc);\n }\n };\n\n addLocale(locale);\n if (locale.includes('-')) addLocale(locale.split('-')[0]);\n\n addLocale(fallback);\n if (fallback?.includes('-')) addLocale(fallback.split('-')[0]);\n\n // Collect results: strings exit early (if no higher-priority object was found),\n // objects are accumulated for deep merging.\n const results: Content[] = [];\n\n for (const loc of locales) {\n const val = get(loc);\n\n if (val === undefined) continue;\n if (typeof val === 'string') {\n if (results.length === 0) return val;\n continue; // an object at higher priority takes precedence\n }\n\n results.push(val);\n }\n\n if (results.length === 0) return undefined as Content;\n if (results.length === 1) return results[0];\n if (Array.isArray(results[0])) return results[0];\n\n // Merge objects: first result (most specific) takes precedence\n return (results as object[]).reduce((acc, curr) =>\n deepMerge(acc, curr)\n ) as Content;\n};\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"getTranslation.cjs","names":[],"sources":["../../../src/interpreter/getTranslation.ts"],"sourcesContent":["import type {\n LocalesValues,\n StrictModeLocaleMap,\n} from '@intlayer/types/module_augmentation';\n\n/**\n * Check if a value is a plain object that can be safely merged.\n * Returns false for Promises, React elements, class instances, etc.\n */\nconst isPlainObject = (value: unknown): boolean => {\n if (value === null || typeof value !== 'object') return false;\n if (typeof (value as any).then === 'function') return false;\n if (\n (value as any).$$typeof !== undefined ||\n (value as any).__v_isVNode !== undefined ||\n (value as any)._isVNode !== undefined ||\n (value as any).isJSX !== undefined\n ) {\n return false;\n }\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null || Array.isArray(value);\n};\n\n/**\n * Recursively merges two objects, skipping undefined source values.\n * First argument takes precedence. Arrays replace rather than merge.\n */\nconst deepMerge = (target: any, source: any): any => {\n if (target === undefined) return source;\n if (source === undefined) return target;\n if (Array.isArray(target)) return target;\n if (isPlainObject(target) && isPlainObject(source)) {\n const result = { ...target };\n\n for (const key of Object.keys(source)) {\n if (\n key === '__proto__' ||\n key === 'constructor' ||\n source[key] === undefined\n )\n continue;\n result[key] =\n target[key] !== undefined\n ? deepMerge(target[key], source[key])\n : source[key];\n }\n return result;\n }\n return target;\n};\n\n/**\n * Picks the appropriate content from a locale map based on the provided locale.\n *\n * It handles:\n * 1. Exact locale match (e.g., 'en-US').\n * 2. Generic locale fallback (e.g., 'en' if 'en-US' is not found).\n * 3. Explicit fallback locale.\n * 4. Deep merging of objects to ensure partial translations are complemented by fallbacks.\n *\n * @param languageContent - A map of locales to content.\n * @param locale - The target locale to retrieve.\n * @param fallback - Optional fallback locale if the target is not found.\n * @returns The translated content.\n *\n * @example\n * ```ts\n * const content = getTranslation({\n * en: 'Hello',\n * fr: 'Bonjour',\n * }, 'fr');\n * // 'Bonjour'\n * ```\n */\nexport const getTranslation = <const Content = string>(\n languageContent: StrictModeLocaleMap<Content>,\n locale: LocalesValues,\n fallback?: LocalesValues\n): Content => {\n const get = (loc: string): Content | undefined =>\n languageContent[loc as keyof typeof languageContent];\n\n // Build priority-ordered locale candidates (most specific first), deduped\n const seen = new Set<string>();\n const locales: string[] = [];\n const addLocale = (loc: string | undefined) => {\n if (loc && !seen.has(loc)) {\n seen.add(loc);\n locales.push(loc);\n }\n };\n\n addLocale(locale);\n if (locale.includes('-')) addLocale(locale.split('-')[0]);\n\n addLocale(fallback);\n if (fallback?.includes('-')) addLocale(fallback.split('-')[0]);\n\n // Collect results: strings exit early (if no higher-priority object was found),\n // objects are accumulated for deep merging.\n const results: Content[] = [];\n\n for (const loc of locales) {\n const val = get(loc);\n\n if (val === undefined) continue;\n if (typeof val === 'string') {\n if (results.length === 0) return val;\n continue; // an object at higher priority takes precedence\n }\n\n results.push(val);\n }\n\n if (results.length === 0) return undefined as Content;\n if (results.length === 1) return results[0];\n if (Array.isArray(results[0])) return results[0];\n\n // Merge objects: first result (most specific) takes precedence\n return (results as object[]).reduce((acc, curr) =>\n deepMerge(acc, curr)\n ) as Content;\n};\n"],"mappings":";;;;;;AASA,MAAM,iBAAiB,UAA4B;CACjD,IAAI,UAAU,QAAQ,OAAO,UAAU,UAAU,OAAO;CACxD,IAAI,OAAQ,MAAc,SAAS,YAAY,OAAO;CACtD,IACG,MAAc,aAAa,UAC3B,MAAc,gBAAgB,UAC9B,MAAc,aAAa,UAC3B,MAAc,UAAU,QAEzB,OAAO;CAET,MAAM,QAAQ,OAAO,eAAe,KAAK;CACzC,OAAO,UAAU,OAAO,aAAa,UAAU,QAAQ,MAAM,QAAQ,KAAK;AAC5E;;;;;AAMA,MAAM,aAAa,QAAa,WAAqB;CACnD,IAAI,WAAW,QAAW,OAAO;CACjC,IAAI,WAAW,QAAW,OAAO;CACjC,IAAI,MAAM,QAAQ,MAAM,GAAG,OAAO;CAClC,IAAI,cAAc,MAAM,KAAK,cAAc,MAAM,GAAG;EAClD,MAAM,SAAS,EAAE,GAAG,OAAO;EAE3B,KAAK,MAAM,OAAO,OAAO,KAAK,MAAM,GAAG;GACrC,IACE,QAAQ,eACR,QAAQ,iBACR,OAAO,SAAS,QAEhB;GACF,OAAO,OACL,OAAO,SAAS,SACZ,UAAU,OAAO,MAAM,OAAO,IAAI,IAClC,OAAO;EACf;EACA,OAAO;CACT;CACA,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,MAAa,kBACX,iBACA,QACA,aACY;CACZ,MAAM,OAAO,QACX,gBAAgB;CAGlB,MAAM,uBAAO,IAAI,IAAY;CAC7B,MAAM,UAAoB,CAAC;CAC3B,MAAM,aAAa,QAA4B;EAC7C,IAAI,OAAO,CAAC,KAAK,IAAI,GAAG,GAAG;GACzB,KAAK,IAAI,GAAG;GACZ,QAAQ,KAAK,GAAG;EAClB;CACF;CAEA,UAAU,MAAM;CAChB,IAAI,OAAO,SAAS,GAAG,GAAG,UAAU,OAAO,MAAM,GAAG,CAAC,CAAC,EAAE;CAExD,UAAU,QAAQ;CAClB,IAAI,UAAU,SAAS,GAAG,GAAG,UAAU,SAAS,MAAM,GAAG,CAAC,CAAC,EAAE;CAI7D,MAAM,UAAqB,CAAC;CAE5B,KAAK,MAAM,OAAO,SAAS;EACzB,MAAM,MAAM,IAAI,GAAG;EAEnB,IAAI,QAAQ,QAAW;EACvB,IAAI,OAAO,QAAQ,UAAU;GAC3B,IAAI,QAAQ,WAAW,GAAG,OAAO;GACjC;EACF;EAEA,QAAQ,KAAK,GAAG;CAClB;CAEA,IAAI,QAAQ,WAAW,GAAG,OAAO;CACjC,IAAI,QAAQ,WAAW,GAAG,OAAO,QAAQ;CACzC,IAAI,MAAM,QAAQ,QAAQ,EAAE,GAAG,OAAO,QAAQ;CAG9C,OAAQ,QAAqB,QAAQ,KAAK,SACxC,UAAU,KAAK,IAAI,CACrB;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"splitAndJoinInsertion.cjs","names":[],"sources":["../../../src/interpreter/splitAndJoinInsertion.ts"],"sourcesContent":["/**\n * Check if a value is a complex object (not a primitive)\n * Used to determine if values need to be wrapped in fragments\n */\nconst isComplexValue = (value: any): boolean =>\n value != null &&\n typeof value !== 'string' &&\n typeof value !== 'number' &&\n typeof value !== 'boolean';\n\n// Hoisted: replace/split do not suffer from the stateful lastIndex mutation of .exec()\nconst insertionRegex = /\\{\\{\\s*(.*?)\\s*\\}\\}/g;\n\nexport const splitInsertionTemplate = <T = any>(\n template: string,\n values: Record<string, T> = {}\n): { isSimple: boolean; parts: string | T[] } => {\n if (!Object.values(values).some(isComplexValue)) {\n return {\n isSimple: true,\n parts: template.replace(insertionRegex, (_, key) =>\n (values[key.trim()] ?? '').toString()\n ),\n };\n }\n\n const chunks = template.split(insertionRegex);\n const parts: any[] = [];\n\n for (let i = 0; i < chunks.length; i++) {\n if (i % 2 === 0) {\n if (chunks[i]) parts.push(chunks[i]);\n } else {\n const val = values[chunks[i].trim()];\n if (val != null) parts.push(val);\n }\n }\n\n return { isSimple: false, parts };\n};\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"splitAndJoinInsertion.cjs","names":[],"sources":["../../../src/interpreter/splitAndJoinInsertion.ts"],"sourcesContent":["/**\n * Check if a value is a complex object (not a primitive)\n * Used to determine if values need to be wrapped in fragments\n */\nconst isComplexValue = (value: any): boolean =>\n value != null &&\n typeof value !== 'string' &&\n typeof value !== 'number' &&\n typeof value !== 'boolean';\n\n// Hoisted: replace/split do not suffer from the stateful lastIndex mutation of .exec()\nconst insertionRegex = /\\{\\{\\s*(.*?)\\s*\\}\\}/g;\n\nexport const splitInsertionTemplate = <T = any>(\n template: string,\n values: Record<string, T> = {}\n): { isSimple: boolean; parts: string | T[] } => {\n if (!Object.values(values).some(isComplexValue)) {\n return {\n isSimple: true,\n parts: template.replace(insertionRegex, (_, key) =>\n (values[key.trim()] ?? '').toString()\n ),\n };\n }\n\n const chunks = template.split(insertionRegex);\n const parts: any[] = [];\n\n for (let i = 0; i < chunks.length; i++) {\n if (i % 2 === 0) {\n if (chunks[i]) parts.push(chunks[i]);\n } else {\n const val = values[chunks[i].trim()];\n if (val != null) parts.push(val);\n }\n }\n\n return { isSimple: false, parts };\n};\n"],"mappings":";;;;;;AAIA,MAAM,kBAAkB,UACtB,SAAS,QACT,OAAO,UAAU,YACjB,OAAO,UAAU,YACjB,OAAO,UAAU;AAGnB,MAAM,iBAAiB;AAEvB,MAAa,0BACX,UACA,SAA4B,CAAC,MACkB;CAC/C,IAAI,CAAC,OAAO,OAAO,MAAM,CAAC,CAAC,KAAK,cAAc,GAC5C,OAAO;EACL,UAAU;EACV,OAAO,SAAS,QAAQ,iBAAiB,GAAG,SACzC,OAAO,IAAI,KAAK,MAAM,GAAE,CAAE,SAAS,CACtC;CACF;CAGF,MAAM,SAAS,SAAS,MAAM,cAAc;CAC5C,MAAM,QAAe,CAAC;CAEtB,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KACjC,IAAI,IAAI,MAAM,GACZ;MAAI,OAAO,IAAI,MAAM,KAAK,OAAO,EAAE;CAAC,OAC/B;EACL,MAAM,MAAM,OAAO,OAAO,EAAE,CAAC,KAAK;EAClC,IAAI,OAAO,MAAM,MAAM,KAAK,GAAG;CACjC;CAGF,OAAO;EAAE,UAAU;EAAO;CAAM;AAClC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"domainUtils.cjs","names":[],"sources":["../../../src/localization/domainUtils.ts"],"sourcesContent":["import type { Locale } from '@intlayer/types/allLocales';\nimport type { RoutingConfig } from '@intlayer/types/config';\nimport type { LocalesValues } from '@intlayer/types/module_augmentation';\n\n/**\n * The locale → domain map configured under `routing.domains`\n * (e.g. `{ en: 'intlayer.org', zh: 'intlayer.cn' }`).\n */\nexport type LocaleDomainMap = RoutingConfig['domains'];\n\n/**\n * Strips the protocol from a domain string and returns only the hostname.\n *\n * Example:\n *\n * ```ts\n * getDomainHostname('https://intlayer.cn') // 'intlayer.cn'\n * getDomainHostname('intlayer.cn') // 'intlayer.cn'\n * ```\n *\n * @param domain - A domain value from `routing.domains`, with or without protocol.\n * @returns The bare hostname.\n */\nexport const getDomainHostname = (domain: string): string => {\n try {\n return /^https?:\\/\\//.test(domain) ? new URL(domain).hostname : domain;\n } catch {\n return domain;\n }\n};\n\n/**\n * Returns the absolute origin for a domain value, prepending `https://`\n * when no protocol is present.\n *\n * Example:\n *\n * ```ts\n * getDomainOrigin('intlayer.cn') // 'https://intlayer.cn'\n * getDomainOrigin('http://intlayer.cn') // 'http://intlayer.cn'\n * ```\n *\n * @param domain - A domain value from `routing.domains`, with or without protocol.\n * @returns The origin usable as a URL base.\n */\nexport const getDomainOrigin = (domain: string): string =>\n /^https?:\\/\\//.test(domain) ? domain : `https://${domain}`;\n\n/**\n * Returns the locale exclusively mapped to a given hostname via `routing.domains`,\n * or `undefined` when zero or more than one locale share that hostname.\n *\n * Example: with `domains = { zh: 'intlayer.cn', en: 'intlayer.org', fr: 'intlayer.org' }`\n *\n * ```ts\n * getLocaleFromDomain('intlayer.cn', domains) // 'zh'\n * getLocaleFromDomain('intlayer.org', domains) // undefined (en and fr share it)\n * getLocaleFromDomain('example.com', domains) // undefined (not mapped)\n * ```\n *\n * @param hostname - The bare hostname of the incoming request.\n * @param domains - The configured locale → domain map.\n * @returns The exclusively mapped locale, or `undefined`.\n */\nexport const getLocaleFromDomain = (\n hostname: string,\n domains: LocaleDomainMap\n): Locale | undefined => {\n if (!domains) return undefined;\n\n const matchingLocales = Object.entries(domains).filter(\n ([, domain]) =>\n typeof domain === 'string' && getDomainHostname(domain) === hostname\n );\n\n return matchingLocales.length === 1\n ? (matchingLocales[0]?.[0] as Locale)\n : undefined;\n};\n\n/**\n * Checks whether a locale is the only locale mapped to its configured domain.\n * When true, the domain alone identifies the locale and no URL prefix is needed.\n * Hostnames are compared after protocol stripping, so\n * `{ en: 'https://intlayer.org', fr: 'intlayer.org' }` counts as a shared domain.\n *\n * @param locale - The locale to check.\n * @param domains - The configured locale → domain map.\n * @returns `true` when the locale has a domain shared with no other locale.\n */\nexport const isLocaleExclusiveOnDomain = (\n locale: LocalesValues,\n domains: LocaleDomainMap\n): boolean => {\n const localeDomain = domains?.[locale];\n\n if (!localeDomain) return false;\n\n const localeHostname = getDomainHostname(localeDomain);\n\n const localesOnSameDomain = Object.values(domains).filter(\n (domain) =>\n typeof domain === 'string' && getDomainHostname(domain) === localeHostname\n ).length;\n\n return localesOnSameDomain === 1;\n};\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"domainUtils.cjs","names":[],"sources":["../../../src/localization/domainUtils.ts"],"sourcesContent":["import type { Locale } from '@intlayer/types/allLocales';\nimport type { RoutingConfig } from '@intlayer/types/config';\nimport type { LocalesValues } from '@intlayer/types/module_augmentation';\n\n/**\n * The locale → domain map configured under `routing.domains`\n * (e.g. `{ en: 'intlayer.org', zh: 'intlayer.cn' }`).\n */\nexport type LocaleDomainMap = RoutingConfig['domains'];\n\n/**\n * Strips the protocol from a domain string and returns only the hostname.\n *\n * Example:\n *\n * ```ts\n * getDomainHostname('https://intlayer.cn') // 'intlayer.cn'\n * getDomainHostname('intlayer.cn') // 'intlayer.cn'\n * ```\n *\n * @param domain - A domain value from `routing.domains`, with or without protocol.\n * @returns The bare hostname.\n */\nexport const getDomainHostname = (domain: string): string => {\n try {\n return /^https?:\\/\\//.test(domain) ? new URL(domain).hostname : domain;\n } catch {\n return domain;\n }\n};\n\n/**\n * Returns the absolute origin for a domain value, prepending `https://`\n * when no protocol is present.\n *\n * Example:\n *\n * ```ts\n * getDomainOrigin('intlayer.cn') // 'https://intlayer.cn'\n * getDomainOrigin('http://intlayer.cn') // 'http://intlayer.cn'\n * ```\n *\n * @param domain - A domain value from `routing.domains`, with or without protocol.\n * @returns The origin usable as a URL base.\n */\nexport const getDomainOrigin = (domain: string): string =>\n /^https?:\\/\\//.test(domain) ? domain : `https://${domain}`;\n\n/**\n * Returns the locale exclusively mapped to a given hostname via `routing.domains`,\n * or `undefined` when zero or more than one locale share that hostname.\n *\n * Example: with `domains = { zh: 'intlayer.cn', en: 'intlayer.org', fr: 'intlayer.org' }`\n *\n * ```ts\n * getLocaleFromDomain('intlayer.cn', domains) // 'zh'\n * getLocaleFromDomain('intlayer.org', domains) // undefined (en and fr share it)\n * getLocaleFromDomain('example.com', domains) // undefined (not mapped)\n * ```\n *\n * @param hostname - The bare hostname of the incoming request.\n * @param domains - The configured locale → domain map.\n * @returns The exclusively mapped locale, or `undefined`.\n */\nexport const getLocaleFromDomain = (\n hostname: string,\n domains: LocaleDomainMap\n): Locale | undefined => {\n if (!domains) return undefined;\n\n const matchingLocales = Object.entries(domains).filter(\n ([, domain]) =>\n typeof domain === 'string' && getDomainHostname(domain) === hostname\n );\n\n return matchingLocales.length === 1\n ? (matchingLocales[0]?.[0] as Locale)\n : undefined;\n};\n\n/**\n * Checks whether a locale is the only locale mapped to its configured domain.\n * When true, the domain alone identifies the locale and no URL prefix is needed.\n * Hostnames are compared after protocol stripping, so\n * `{ en: 'https://intlayer.org', fr: 'intlayer.org' }` counts as a shared domain.\n *\n * @param locale - The locale to check.\n * @param domains - The configured locale → domain map.\n * @returns `true` when the locale has a domain shared with no other locale.\n */\nexport const isLocaleExclusiveOnDomain = (\n locale: LocalesValues,\n domains: LocaleDomainMap\n): boolean => {\n const localeDomain = domains?.[locale];\n\n if (!localeDomain) return false;\n\n const localeHostname = getDomainHostname(localeDomain);\n\n const localesOnSameDomain = Object.values(domains).filter(\n (domain) =>\n typeof domain === 'string' && getDomainHostname(domain) === localeHostname\n ).length;\n\n return localesOnSameDomain === 1;\n};\n"],"mappings":";;;;;;;;;;;;;;;AAuBA,MAAa,qBAAqB,WAA2B;CAC3D,IAAI;EACF,OAAO,eAAe,KAAK,MAAM,IAAI,IAAI,IAAI,MAAM,CAAC,CAAC,WAAW;CAClE,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;;;;;;;;;AAgBA,MAAa,mBAAmB,WAC9B,eAAe,KAAK,MAAM,IAAI,SAAS,WAAW;;;;;;;;;;;;;;;;;AAkBpD,MAAa,uBACX,UACA,YACuB;CACvB,IAAI,CAAC,SAAS,OAAO;CAErB,MAAM,kBAAkB,OAAO,QAAQ,OAAO,CAAC,CAAC,QAC7C,GAAG,YACF,OAAO,WAAW,YAAY,kBAAkB,MAAM,MAAM,QAChE;CAEA,OAAO,gBAAgB,WAAW,IAC7B,gBAAgB,EAAE,GAAG,KACtB;AACN;;;;;;;;;;;AAYA,MAAa,6BACX,QACA,YACY;CACZ,MAAM,eAAe,UAAU;CAE/B,IAAI,CAAC,cAAc,OAAO;CAE1B,MAAM,iBAAiB,kBAAkB,YAAY;CAOrD,OAL4B,OAAO,OAAO,OAAO,CAAC,CAAC,QAChD,WACC,OAAO,WAAW,YAAY,kBAAkB,MAAM,MAAM,cAChE,CAAC,CAAC,WAE6B;AACjC"}
|