@intlayer/core 8.12.5-canary.0 → 9.0.0-canary.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/deepTransformPlugins/getEditedContent.cjs +96 -0
- package/dist/cjs/deepTransformPlugins/getEditedContent.cjs.map +1 -0
- package/dist/cjs/deepTransformPlugins/index.cjs +3 -0
- package/dist/cjs/dictionaryManipulator/index.cjs +18 -0
- package/dist/cjs/dictionaryManipulator/mergeDictionaries.cjs +4 -1
- package/dist/cjs/dictionaryManipulator/mergeDictionaries.cjs.map +1 -1
- package/dist/cjs/dictionaryManipulator/mergeQualifiedDictionaries.cjs +70 -0
- package/dist/cjs/dictionaryManipulator/mergeQualifiedDictionaries.cjs.map +1 -0
- package/dist/cjs/dictionaryManipulator/qualifiedDictionary.cjs +302 -0
- package/dist/cjs/dictionaryManipulator/qualifiedDictionary.cjs.map +1 -0
- package/dist/cjs/index.cjs +26 -0
- package/dist/cjs/interpreter/getCollection.cjs +25 -0
- package/dist/cjs/interpreter/getCollection.cjs.map +1 -0
- package/dist/cjs/interpreter/getDictionary.cjs +26 -11
- package/dist/cjs/interpreter/getDictionary.cjs.map +1 -1
- package/dist/cjs/interpreter/getIntlayer.cjs +16 -3
- package/dist/cjs/interpreter/getIntlayer.cjs.map +1 -1
- package/dist/cjs/interpreter/getVariant.cjs +30 -0
- package/dist/cjs/interpreter/getVariant.cjs.map +1 -0
- package/dist/cjs/localization/getBrowserLocale.cjs +1 -1
- package/dist/cjs/localization/getBrowserLocale.cjs.map +1 -1
- package/dist/cjs/messageFormat/ICU.cjs +32 -2
- package/dist/cjs/messageFormat/ICU.cjs.map +1 -1
- package/dist/cjs/messageFormat/i18next.cjs +1 -1
- package/dist/cjs/messageFormat/i18next.cjs.map +1 -1
- package/dist/cjs/messageFormat/index.cjs +5 -0
- package/dist/cjs/messageFormat/resolveMessage.cjs +183 -0
- package/dist/cjs/messageFormat/resolveMessage.cjs.map +1 -0
- package/dist/cjs/messageFormat/vue-i18n.cjs +8 -9
- package/dist/cjs/messageFormat/vue-i18n.cjs.map +1 -1
- package/dist/cjs/transpiler/collection/collection.cjs +32 -0
- package/dist/cjs/transpiler/collection/collection.cjs.map +1 -0
- package/dist/cjs/transpiler/collection/index.cjs +4 -0
- package/dist/cjs/transpiler/markdown/getMarkdownMetadata.cjs +2 -3
- package/dist/cjs/transpiler/markdown/getMarkdownMetadata.cjs.map +1 -1
- package/dist/cjs/transpiler/variant/index.cjs +4 -0
- package/dist/cjs/transpiler/variant/variant.cjs +35 -0
- package/dist/cjs/transpiler/variant/variant.cjs.map +1 -0
- package/dist/cjs/utils/parseYaml.cjs +37 -17
- package/dist/cjs/utils/parseYaml.cjs.map +1 -1
- package/dist/esm/deepTransformPlugins/getEditedContent.mjs +92 -0
- package/dist/esm/deepTransformPlugins/getEditedContent.mjs.map +1 -0
- package/dist/esm/deepTransformPlugins/index.mjs +2 -1
- package/dist/esm/dictionaryManipulator/index.mjs +3 -1
- package/dist/esm/dictionaryManipulator/mergeDictionaries.mjs +4 -1
- package/dist/esm/dictionaryManipulator/mergeDictionaries.mjs.map +1 -1
- package/dist/esm/dictionaryManipulator/mergeQualifiedDictionaries.mjs +68 -0
- package/dist/esm/dictionaryManipulator/mergeQualifiedDictionaries.mjs.map +1 -0
- package/dist/esm/dictionaryManipulator/qualifiedDictionary.mjs +286 -0
- package/dist/esm/dictionaryManipulator/qualifiedDictionary.mjs.map +1 -0
- package/dist/esm/index.mjs +5 -1
- package/dist/esm/interpreter/getCollection.mjs +23 -0
- package/dist/esm/interpreter/getCollection.mjs.map +1 -0
- package/dist/esm/interpreter/getDictionary.mjs +26 -11
- package/dist/esm/interpreter/getDictionary.mjs.map +1 -1
- package/dist/esm/interpreter/getIntlayer.mjs +16 -3
- package/dist/esm/interpreter/getIntlayer.mjs.map +1 -1
- package/dist/esm/interpreter/getVariant.mjs +28 -0
- package/dist/esm/interpreter/getVariant.mjs.map +1 -0
- package/dist/esm/localization/getBrowserLocale.mjs +1 -1
- package/dist/esm/localization/getBrowserLocale.mjs.map +1 -1
- package/dist/esm/messageFormat/ICU.mjs +32 -2
- package/dist/esm/messageFormat/ICU.mjs.map +1 -1
- package/dist/esm/messageFormat/i18next.mjs +1 -1
- package/dist/esm/messageFormat/i18next.mjs.map +1 -1
- package/dist/esm/messageFormat/index.mjs +2 -1
- package/dist/esm/messageFormat/resolveMessage.mjs +177 -0
- package/dist/esm/messageFormat/resolveMessage.mjs.map +1 -0
- package/dist/esm/messageFormat/vue-i18n.mjs +8 -9
- package/dist/esm/messageFormat/vue-i18n.mjs.map +1 -1
- package/dist/esm/transpiler/collection/collection.mjs +30 -0
- package/dist/esm/transpiler/collection/collection.mjs.map +1 -0
- package/dist/esm/transpiler/collection/index.mjs +3 -0
- package/dist/esm/transpiler/markdown/getMarkdownMetadata.mjs +2 -3
- package/dist/esm/transpiler/markdown/getMarkdownMetadata.mjs.map +1 -1
- package/dist/esm/transpiler/variant/index.mjs +3 -0
- package/dist/esm/transpiler/variant/variant.mjs +33 -0
- package/dist/esm/transpiler/variant/variant.mjs.map +1 -0
- package/dist/esm/utils/parseYaml.mjs +37 -17
- package/dist/esm/utils/parseYaml.mjs.map +1 -1
- package/dist/types/@intlayer/core/dist/types/formatters/compact.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/formatters/currency.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/formatters/date.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/formatters/index.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/formatters/list.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/formatters/number.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/formatters/percentage.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/formatters/relativeTime.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/formatters/units.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/interpreter/getCondition.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/interpreter/getContent/deepTransform.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/interpreter/getContent/getContent.d.ts +2 -0
- package/dist/types/@intlayer/core/dist/types/interpreter/getContent/plugins.d.ts +4 -0
- package/dist/types/@intlayer/core/dist/types/interpreter/getDictionary.d.ts +2 -0
- package/dist/types/@intlayer/core/dist/types/interpreter/getEnumeration.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/interpreter/getIntlayer.d.ts +2 -0
- package/dist/types/@intlayer/core/dist/types/interpreter/getNesting.d.ts +2 -0
- package/dist/types/@intlayer/core/dist/types/interpreter/getPlural.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/interpreter/getTranslation.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/interpreter/getVariant.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/interpreter/index.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/intlayer/dist/types/index.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/localization/generateSitemap.d.ts +2 -0
- package/dist/types/@intlayer/core/dist/types/localization/getBrowserLocale.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/localization/getHTMLTextDir.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/localization/getLocale.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/localization/getLocaleFromPath.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/localization/getLocaleLang.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/localization/getLocaleName.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/localization/getLocalizedUrl.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/localization/getMultilingualUrls.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/localization/getPathWithoutLocale.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/localization/getPrefix.d.ts +3 -0
- package/dist/types/@intlayer/core/dist/types/localization/index.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/localization/localeDetector.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/localization/localeMapper.d.ts +2 -0
- package/dist/types/@intlayer/core/dist/types/localization/localeResolver.d.ts +2 -0
- package/dist/types/@intlayer/core/dist/types/localization/rewriteUtils.d.ts +3 -0
- package/dist/types/@intlayer/core/dist/types/localization/validatePrefix.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/markdown/index.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/transpiler/collection/collection.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/transpiler/condition/condition.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/transpiler/enumeration/enumeration.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/transpiler/file/file.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/transpiler/gender/gender.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/transpiler/html/html.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/transpiler/index.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/transpiler/insertion/insertion.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/transpiler/markdown/markdown.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/transpiler/nesting/nesting.d.ts +2 -0
- package/dist/types/@intlayer/core/dist/types/transpiler/plural/plural.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/transpiler/translation/translation.d.ts +2 -0
- package/dist/types/@intlayer/core/dist/types/transpiler/variant/variant.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/utils/index.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/utils/intl.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/utils/isSameKeyPath.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/utils/localeStorage.d.ts +2 -0
- package/dist/types/deepTransformPlugins/getEditedContent.d.ts +33 -0
- package/dist/types/deepTransformPlugins/getEditedContent.d.ts.map +1 -0
- package/dist/types/deepTransformPlugins/getFilterMissingTranslationsContent.d.ts +6 -3
- package/dist/types/deepTransformPlugins/getFilterMissingTranslationsContent.d.ts.map +1 -1
- package/dist/types/deepTransformPlugins/getFilterTranslationsOnlyContent.d.ts +6 -3
- package/dist/types/deepTransformPlugins/getFilterTranslationsOnlyContent.d.ts.map +1 -1
- package/dist/types/deepTransformPlugins/getFilteredLocalesContent.d.ts +4 -1
- package/dist/types/deepTransformPlugins/index.d.ts +2 -1
- package/dist/types/dictionaryManipulator/index.d.ts +3 -1
- package/dist/types/dictionaryManipulator/mergeQualifiedDictionaries.d.ts +22 -0
- package/dist/types/dictionaryManipulator/mergeQualifiedDictionaries.d.ts.map +1 -0
- package/dist/types/dictionaryManipulator/qualifiedDictionary.d.ts +176 -0
- package/dist/types/dictionaryManipulator/qualifiedDictionary.d.ts.map +1 -0
- package/dist/types/index.d.ts +6 -2
- package/dist/types/interpreter/getCollection.d.ts +19 -0
- package/dist/types/interpreter/getCollection.d.ts.map +1 -0
- package/dist/types/interpreter/getDictionary.d.ts +13 -7
- package/dist/types/interpreter/getDictionary.d.ts.map +1 -1
- package/dist/types/interpreter/getIntlayer.d.ts +13 -2
- package/dist/types/interpreter/getIntlayer.d.ts.map +1 -1
- package/dist/types/interpreter/getPlural.d.ts +1 -1
- package/dist/types/interpreter/getVariant.d.ts +26 -0
- package/dist/types/interpreter/getVariant.d.ts.map +1 -0
- package/dist/types/intlayer/dist/types/index.d.ts +4 -0
- package/dist/types/messageFormat/ICU.d.ts.map +1 -1
- package/dist/types/messageFormat/i18next.d.ts.map +1 -1
- package/dist/types/messageFormat/index.d.ts +2 -1
- package/dist/types/messageFormat/resolveMessage.d.ts +72 -0
- package/dist/types/messageFormat/resolveMessage.d.ts.map +1 -0
- package/dist/types/messageFormat/vue-i18n.d.ts.map +1 -1
- package/dist/types/transpiler/collection/collection.d.ts +34 -0
- package/dist/types/transpiler/collection/collection.d.ts.map +1 -0
- package/dist/types/transpiler/collection/index.d.ts +2 -0
- package/dist/types/transpiler/variant/index.d.ts +2 -0
- package/dist/types/transpiler/variant/variant.d.ts +43 -0
- package/dist/types/transpiler/variant/variant.d.ts.map +1 -0
- package/dist/types/utils/index.d.ts +2 -2
- package/dist/types/utils/parseYaml.d.ts +12 -2
- package/dist/types/utils/parseYaml.d.ts.map +1 -1
- package/package.json +7 -7
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
const require_runtime = require('../_virtual/_rolldown/runtime.cjs');
|
|
3
|
+
let _intlayer_types_nodeType = require("@intlayer/types/nodeType");
|
|
4
|
+
_intlayer_types_nodeType = require_runtime.__toESM(_intlayer_types_nodeType);
|
|
5
|
+
|
|
6
|
+
//#region src/deepTransformPlugins/getEditedContent.ts
|
|
7
|
+
const isTranslationNode = (node) => typeof node === "object" && node !== null && node.nodeType === _intlayer_types_nodeType.TRANSLATION;
|
|
8
|
+
const isTypedNode = (node) => typeof node === "object" && node !== null && !Array.isArray(node) && typeof node.nodeType === "string";
|
|
9
|
+
/** Structural equality for leaf source values (string / number / nested objects). */
|
|
10
|
+
const isSameValue = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
11
|
+
/**
|
|
12
|
+
* Recursively compares `next` against `previous` and returns the sub-tree of
|
|
13
|
+
* `next` that holds only the nodes whose source-locale value was added or
|
|
14
|
+
* changed. Returns `undefined` when nothing changed at this node.
|
|
15
|
+
*/
|
|
16
|
+
const getEditedNode = (previous, next, defaultLocale) => {
|
|
17
|
+
if (isTranslationNode(next)) {
|
|
18
|
+
const nextValue = next[_intlayer_types_nodeType.TRANSLATION]?.[defaultLocale];
|
|
19
|
+
const previousValue = isTranslationNode(previous) ? previous[_intlayer_types_nodeType.TRANSLATION]?.[defaultLocale] : void 0;
|
|
20
|
+
if (typeof nextValue === "undefined") return;
|
|
21
|
+
if (isSameValue(previousValue, nextValue)) return;
|
|
22
|
+
return {
|
|
23
|
+
nodeType: _intlayer_types_nodeType.TRANSLATION,
|
|
24
|
+
[_intlayer_types_nodeType.TRANSLATION]: { [defaultLocale]: nextValue }
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
if (isTypedNode(next)) {
|
|
28
|
+
const { nodeType } = next;
|
|
29
|
+
const editedInner = getEditedNode(isTypedNode(previous) ? previous[nodeType] : void 0, next[nodeType], defaultLocale);
|
|
30
|
+
if (typeof editedInner === "undefined") return;
|
|
31
|
+
return {
|
|
32
|
+
nodeType,
|
|
33
|
+
[nodeType]: editedInner
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
if (Array.isArray(next)) {
|
|
37
|
+
const previousArray = Array.isArray(previous) ? previous : [];
|
|
38
|
+
let hasChange = false;
|
|
39
|
+
const result = next.map((child, index) => {
|
|
40
|
+
const editedChild = getEditedNode(previousArray[index], child, defaultLocale);
|
|
41
|
+
if (typeof editedChild !== "undefined") {
|
|
42
|
+
hasChange = true;
|
|
43
|
+
return editedChild;
|
|
44
|
+
}
|
|
45
|
+
return child;
|
|
46
|
+
});
|
|
47
|
+
return hasChange ? result : void 0;
|
|
48
|
+
}
|
|
49
|
+
if (typeof next === "object" && next !== null) {
|
|
50
|
+
const previousObject = typeof previous === "object" && previous !== null && !Array.isArray(previous) ? previous : void 0;
|
|
51
|
+
const result = {};
|
|
52
|
+
let hasChange = false;
|
|
53
|
+
for (const key of Object.keys(next)) {
|
|
54
|
+
const editedChild = getEditedNode(previousObject?.[key], next[key], defaultLocale);
|
|
55
|
+
if (typeof editedChild !== "undefined") {
|
|
56
|
+
result[key] = editedChild;
|
|
57
|
+
hasChange = true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return hasChange ? result : void 0;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Returns the partial content holding only the `translation` nodes whose
|
|
65
|
+
* source (`defaultLocale`) value was added or changed between `previousContent`
|
|
66
|
+
* and `newContent`. Each changed node is reduced to its source locale so that
|
|
67
|
+
* every target locale is regenerated when the partial is translated.
|
|
68
|
+
*
|
|
69
|
+
* Returns `{}` (empty content) when no source value changed.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* // default value of `title` changed → only `title` is returned, source-only
|
|
73
|
+
* getEditedContent(
|
|
74
|
+
* { title: t({ en: 'Old', fr: 'Vieux' }), body: t({ en: 'B', fr: 'B' }) },
|
|
75
|
+
* { title: t({ en: 'New', fr: 'Vieux' }), body: t({ en: 'B', fr: 'B' }) },
|
|
76
|
+
* 'en'
|
|
77
|
+
* ); // → { title: { nodeType: 'translation', translation: { en: 'New' } } }
|
|
78
|
+
*/
|
|
79
|
+
const getEditedContent = (previousContent, newContent, defaultLocale) => getEditedNode(previousContent, newContent, defaultLocale) ?? {};
|
|
80
|
+
/**
|
|
81
|
+
* Dictionary-level wrapper around {@link getEditedContent}. Returns a partial
|
|
82
|
+
* dictionary (same `key`) whose `content` holds only the changed source nodes.
|
|
83
|
+
*
|
|
84
|
+
* @param previousDictionary - Dictionary state before the edit.
|
|
85
|
+
* @param newDictionary - Dictionary state after the edit.
|
|
86
|
+
* @param defaultLocale - The source locale to diff against.
|
|
87
|
+
*/
|
|
88
|
+
const getEditedDictionary = (previousDictionary, newDictionary, defaultLocale) => ({
|
|
89
|
+
...newDictionary,
|
|
90
|
+
content: getEditedContent(previousDictionary?.content, newDictionary.content, defaultLocale)
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
//#endregion
|
|
94
|
+
exports.getEditedContent = getEditedContent;
|
|
95
|
+
exports.getEditedDictionary = getEditedDictionary;
|
|
96
|
+
//# sourceMappingURL=getEditedContent.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getEditedContent.cjs","names":["NodeTypes"],"sources":["../../../src/deepTransformPlugins/getEditedContent.ts"],"sourcesContent":["import type { ContentNode, Dictionary } from '@intlayer/types/dictionary';\nimport type { LocalesValues } from '@intlayer/types/module_augmentation';\nimport * as NodeTypes from '@intlayer/types/nodeType';\n\n/** A `translation` node, e.g. `{ nodeType: 'translation', translation: { en, fr } }`. */\ntype TranslationNode = {\n nodeType: typeof NodeTypes.TRANSLATION;\n [NodeTypes.TRANSLATION]: Record<string, unknown>;\n};\n\nconst isTranslationNode = (node: unknown): node is TranslationNode =>\n typeof node === 'object' &&\n node !== null &&\n (node as { nodeType?: unknown }).nodeType === NodeTypes.TRANSLATION;\n\nconst isTypedNode = (\n node: unknown\n): node is { nodeType: string; [key: string]: unknown } =>\n typeof node === 'object' &&\n node !== null &&\n !Array.isArray(node) &&\n typeof (node as { nodeType?: unknown }).nodeType === 'string';\n\n/** Structural equality for leaf source values (string / number / nested objects). */\nconst isSameValue = (a: unknown, b: unknown): boolean =>\n JSON.stringify(a) === JSON.stringify(b);\n\n/**\n * Recursively compares `next` against `previous` and returns the sub-tree of\n * `next` that holds only the nodes whose source-locale value was added or\n * changed. Returns `undefined` when nothing changed at this node.\n */\nconst getEditedNode = (\n previous: ContentNode | undefined,\n next: ContentNode,\n defaultLocale: LocalesValues\n): ContentNode | undefined => {\n // Translation node: compare the default-locale leaf only.\n if (isTranslationNode(next)) {\n const nextValue = next[NodeTypes.TRANSLATION]?.[defaultLocale as string];\n const previousValue = isTranslationNode(previous)\n ? previous[NodeTypes.TRANSLATION]?.[defaultLocale as string]\n : undefined;\n\n // Nothing to (re)translate if the source locale is absent.\n if (typeof nextValue === 'undefined') {\n return undefined;\n }\n\n // Include only when the source value was added or changed.\n if (isSameValue(previousValue, nextValue)) {\n return undefined;\n }\n\n // Reduce to the source locale only, so every target locale is regenerated.\n return {\n nodeType: NodeTypes.TRANSLATION,\n [NodeTypes.TRANSLATION]: { [defaultLocale as string]: nextValue },\n } as ContentNode;\n }\n\n // Other typed nodes (enumeration, condition, nested, …): recurse into the\n // wrapped value while preserving the wrapper so the partial stays valid.\n if (isTypedNode(next)) {\n const { nodeType } = next;\n const previousInner = isTypedNode(previous)\n ? (previous[nodeType] as ContentNode | undefined)\n : undefined;\n const editedInner = getEditedNode(\n previousInner,\n next[nodeType] as ContentNode,\n defaultLocale\n );\n\n if (typeof editedInner === 'undefined') {\n return undefined;\n }\n\n return {\n nodeType,\n [nodeType]: editedInner,\n } as ContentNode;\n }\n\n // Arrays: keep changed items reduced to source-only, unchanged items as-is to\n // preserve index alignment for `mergeDictionaries` (which merges by index).\n if (Array.isArray(next)) {\n const previousArray = Array.isArray(previous) ? previous : [];\n let hasChange = false;\n\n const result = next.map((child, index) => {\n const editedChild = getEditedNode(\n previousArray[index] as ContentNode | undefined,\n child as ContentNode,\n defaultLocale\n );\n if (typeof editedChild !== 'undefined') {\n hasChange = true;\n return editedChild;\n }\n // Unchanged item: keep the full node. It already has every locale, so the\n // complete-mode translation pass skips it and the merge leaves it intact.\n return child;\n });\n\n return hasChange ? (result as ContentNode) : undefined;\n }\n\n // Plain objects: recurse into each key, keeping only changed branches.\n if (typeof next === 'object' && next !== null) {\n const previousObject =\n typeof previous === 'object' &&\n previous !== null &&\n !Array.isArray(previous)\n ? (previous as Record<string, ContentNode>)\n : undefined;\n\n const result: Record<string, ContentNode> = {};\n let hasChange = false;\n\n for (const key of Object.keys(next as Record<string, ContentNode>)) {\n const editedChild = getEditedNode(\n previousObject?.[key],\n (next as Record<string, ContentNode>)[key],\n defaultLocale\n );\n if (typeof editedChild !== 'undefined') {\n result[key] = editedChild;\n hasChange = true;\n }\n }\n\n return hasChange ? (result as ContentNode) : undefined;\n }\n\n // Primitive leaves are not locale-aware on their own — nothing to translate.\n return undefined;\n};\n\n/**\n * Returns the partial content holding only the `translation` nodes whose\n * source (`defaultLocale`) value was added or changed between `previousContent`\n * and `newContent`. Each changed node is reduced to its source locale so that\n * every target locale is regenerated when the partial is translated.\n *\n * Returns `{}` (empty content) when no source value changed.\n *\n * @example\n * // default value of `title` changed → only `title` is returned, source-only\n * getEditedContent(\n * { title: t({ en: 'Old', fr: 'Vieux' }), body: t({ en: 'B', fr: 'B' }) },\n * { title: t({ en: 'New', fr: 'Vieux' }), body: t({ en: 'B', fr: 'B' }) },\n * 'en'\n * ); // → { title: { nodeType: 'translation', translation: { en: 'New' } } }\n */\nexport const getEditedContent = (\n previousContent: ContentNode | undefined,\n newContent: ContentNode,\n defaultLocale: LocalesValues\n): ContentNode =>\n getEditedNode(previousContent, newContent, defaultLocale) ??\n ({} as ContentNode);\n\n/**\n * Dictionary-level wrapper around {@link getEditedContent}. Returns a partial\n * dictionary (same `key`) whose `content` holds only the changed source nodes.\n *\n * @param previousDictionary - Dictionary state before the edit.\n * @param newDictionary - Dictionary state after the edit.\n * @param defaultLocale - The source locale to diff against.\n */\nexport const getEditedDictionary = (\n previousDictionary: Dictionary | undefined,\n newDictionary: Dictionary,\n defaultLocale: LocalesValues\n): Dictionary => ({\n ...newDictionary,\n content: getEditedContent(\n previousDictionary?.content,\n newDictionary.content,\n defaultLocale\n ),\n});\n"],"mappings":";;;;;;AAUA,MAAM,qBAAqB,SACzB,OAAO,SAAS,YAChB,SAAS,QACR,KAAgC,aAAaA,yBAAU;AAE1D,MAAM,eACJ,SAEA,OAAO,SAAS,YAChB,SAAS,QACT,CAAC,MAAM,QAAQ,KAAK,IACpB,OAAQ,KAAgC,aAAa;;AAGvD,MAAM,eAAe,GAAY,MAC/B,KAAK,UAAU,EAAE,KAAK,KAAK,UAAU,EAAE;;;;;;AAOzC,MAAM,iBACJ,UACA,MACA,kBAC4B;AAE5B,KAAI,kBAAkB,KAAK,EAAE;EAC3B,MAAM,YAAY,KAAKA,yBAAU,eAAe;EAChD,MAAM,gBAAgB,kBAAkB,SAAS,GAC7C,SAASA,yBAAU,eAAe,iBAClC;AAGJ,MAAI,OAAO,cAAc,YACvB;AAIF,MAAI,YAAY,eAAe,UAAU,CACvC;AAIF,SAAO;GACL,UAAUA,yBAAU;IACnBA,yBAAU,cAAc,GAAG,gBAA0B,WAAW;GAClE;;AAKH,KAAI,YAAY,KAAK,EAAE;EACrB,MAAM,EAAE,aAAa;EAIrB,MAAM,cAAc,cAHE,YAAY,SAAS,GACtC,SAAS,YACV,QAGF,KAAK,WACL,cACD;AAED,MAAI,OAAO,gBAAgB,YACzB;AAGF,SAAO;GACL;IACC,WAAW;GACb;;AAKH,KAAI,MAAM,QAAQ,KAAK,EAAE;EACvB,MAAM,gBAAgB,MAAM,QAAQ,SAAS,GAAG,WAAW,EAAE;EAC7D,IAAI,YAAY;EAEhB,MAAM,SAAS,KAAK,KAAK,OAAO,UAAU;GACxC,MAAM,cAAc,cAClB,cAAc,QACd,OACA,cACD;AACD,OAAI,OAAO,gBAAgB,aAAa;AACtC,gBAAY;AACZ,WAAO;;AAIT,UAAO;IACP;AAEF,SAAO,YAAa,SAAyB;;AAI/C,KAAI,OAAO,SAAS,YAAY,SAAS,MAAM;EAC7C,MAAM,iBACJ,OAAO,aAAa,YACpB,aAAa,QACb,CAAC,MAAM,QAAQ,SAAS,GACnB,WACD;EAEN,MAAM,SAAsC,EAAE;EAC9C,IAAI,YAAY;AAEhB,OAAK,MAAM,OAAO,OAAO,KAAK,KAAoC,EAAE;GAClE,MAAM,cAAc,cAClB,iBAAiB,MAChB,KAAqC,MACtC,cACD;AACD,OAAI,OAAO,gBAAgB,aAAa;AACtC,WAAO,OAAO;AACd,gBAAY;;;AAIhB,SAAO,YAAa,SAAyB;;;;;;;;;;;;;;;;;;;AAuBjD,MAAa,oBACX,iBACA,YACA,kBAEA,cAAc,iBAAiB,YAAY,cAAc,IACxD,EAAE;;;;;;;;;AAUL,MAAa,uBACX,oBACA,eACA,mBACgB;CAChB,GAAG;CACH,SAAS,iBACP,oBAAoB,SACpB,cAAc,SACd,cACD;CACF"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
const require_deepTransformPlugins_getEditedContent = require('./getEditedContent.cjs');
|
|
2
3
|
const require_deepTransformPlugins_getFilteredLocalesContent = require('./getFilteredLocalesContent.cjs');
|
|
3
4
|
const require_deepTransformPlugins_getFilterMissingTranslationsContent = require('./getFilterMissingTranslationsContent.cjs');
|
|
4
5
|
const require_deepTransformPlugins_getFilterTranslationsOnlyContent = require('./getFilterTranslationsOnlyContent.cjs');
|
|
@@ -14,6 +15,8 @@ exports.buildMaskPlugin = require_deepTransformPlugins_getMaskContent.buildMaskP
|
|
|
14
15
|
exports.checkMissingLocalesPlugin = require_deepTransformPlugins_getMissingLocalesContent.checkMissingLocalesPlugin;
|
|
15
16
|
exports.filterMissingTranslationsOnlyPlugin = require_deepTransformPlugins_getFilterMissingTranslationsContent.filterMissingTranslationsOnlyPlugin;
|
|
16
17
|
exports.filterTranslationsOnlyPlugin = require_deepTransformPlugins_getFilterTranslationsOnlyContent.filterTranslationsOnlyPlugin;
|
|
18
|
+
exports.getEditedContent = require_deepTransformPlugins_getEditedContent.getEditedContent;
|
|
19
|
+
exports.getEditedDictionary = require_deepTransformPlugins_getEditedContent.getEditedDictionary;
|
|
17
20
|
exports.getFilterMissingTranslationsContent = require_deepTransformPlugins_getFilterMissingTranslationsContent.getFilterMissingTranslationsContent;
|
|
18
21
|
exports.getFilterMissingTranslationsDictionary = require_deepTransformPlugins_getFilterMissingTranslationsContent.getFilterMissingTranslationsDictionary;
|
|
19
22
|
exports.getFilterTranslationsOnlyContent = require_deepTransformPlugins_getFilterTranslationsOnlyContent.getFilterTranslationsOnlyContent;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
const require_dictionaryManipulator_qualifiedDictionary = require('./qualifiedDictionary.cjs');
|
|
2
3
|
const require_dictionaryManipulator_editDictionaryByKeyPath = require('./editDictionaryByKeyPath.cjs');
|
|
3
4
|
const require_dictionaryManipulator_getContentNodeByKeyPath = require('./getContentNodeByKeyPath.cjs');
|
|
4
5
|
const require_dictionaryManipulator_getDefaultNode = require('./getDefaultNode.cjs');
|
|
@@ -6,22 +7,39 @@ const require_dictionaryManipulator_getEmptyNode = require('./getEmptyNode.cjs')
|
|
|
6
7
|
const require_dictionaryManipulator_getNodeChildren = require('./getNodeChildren.cjs');
|
|
7
8
|
const require_dictionaryManipulator_getNodeType = require('./getNodeType.cjs');
|
|
8
9
|
const require_dictionaryManipulator_mergeDictionaries = require('./mergeDictionaries.cjs');
|
|
10
|
+
const require_dictionaryManipulator_mergeQualifiedDictionaries = require('./mergeQualifiedDictionaries.cjs');
|
|
9
11
|
const require_dictionaryManipulator_orderDictionaries = require('./orderDictionaries.cjs');
|
|
10
12
|
const require_dictionaryManipulator_normalizeDictionary = require('./normalizeDictionary.cjs');
|
|
11
13
|
const require_dictionaryManipulator_removeContentNodeByKeyPath = require('./removeContentNodeByKeyPath.cjs');
|
|
12
14
|
const require_dictionaryManipulator_renameContentNodeByKeyPath = require('./renameContentNodeByKeyPath.cjs');
|
|
13
15
|
const require_dictionaryManipulator_updateNodeChildren = require('./updateNodeChildren.cjs');
|
|
14
16
|
|
|
17
|
+
exports.COMPOSITE_ID_SEPARATOR = require_dictionaryManipulator_qualifiedDictionary.COMPOSITE_ID_SEPARATOR;
|
|
18
|
+
exports.QUALIFIER_DYNAMIC_TYPES_KEY = require_dictionaryManipulator_qualifiedDictionary.QUALIFIER_DYNAMIC_TYPES_KEY;
|
|
19
|
+
exports.QUALIFIER_ORDER = require_dictionaryManipulator_qualifiedDictionary.QUALIFIER_ORDER;
|
|
15
20
|
exports.editDictionaryByKeyPath = require_dictionaryManipulator_editDictionaryByKeyPath.editDictionaryByKeyPath;
|
|
16
21
|
exports.getContentNodeByKeyPath = require_dictionaryManipulator_getContentNodeByKeyPath.getContentNodeByKeyPath;
|
|
17
22
|
exports.getDefaultNode = require_dictionaryManipulator_getDefaultNode.getDefaultNode;
|
|
23
|
+
exports.getDictionaryCompositeId = require_dictionaryManipulator_qualifiedDictionary.getDictionaryCompositeId;
|
|
24
|
+
exports.getDictionaryQualifierId = require_dictionaryManipulator_qualifiedDictionary.getDictionaryQualifierId;
|
|
25
|
+
exports.getDictionaryQualifierSegments = require_dictionaryManipulator_qualifiedDictionary.getDictionaryQualifierSegments;
|
|
26
|
+
exports.getDictionaryQualifierTypes = require_dictionaryManipulator_qualifiedDictionary.getDictionaryQualifierTypes;
|
|
27
|
+
exports.getDictionarySelectorCacheKey = require_dictionaryManipulator_qualifiedDictionary.getDictionarySelectorCacheKey;
|
|
18
28
|
exports.getEmptyNode = require_dictionaryManipulator_getEmptyNode.getEmptyNode;
|
|
19
29
|
exports.getNodeChildren = require_dictionaryManipulator_getNodeChildren.getNodeChildren;
|
|
20
30
|
exports.getNodeType = require_dictionaryManipulator_getNodeType.getNodeType;
|
|
31
|
+
exports.isQualifiedDictionaryGroup = require_dictionaryManipulator_qualifiedDictionary.isQualifiedDictionaryGroup;
|
|
32
|
+
exports.isQualifiedDynamicLoaderMap = require_dictionaryManipulator_qualifiedDictionary.isQualifiedDynamicLoaderMap;
|
|
21
33
|
exports.mergeDictionaries = require_dictionaryManipulator_mergeDictionaries.mergeDictionaries;
|
|
34
|
+
exports.mergeQualifiedDictionaries = require_dictionaryManipulator_mergeQualifiedDictionaries.mergeQualifiedDictionaries;
|
|
22
35
|
exports.normalizeDictionaries = require_dictionaryManipulator_normalizeDictionary.normalizeDictionaries;
|
|
23
36
|
exports.normalizeDictionary = require_dictionaryManipulator_normalizeDictionary.normalizeDictionary;
|
|
24
37
|
exports.orderDictionaries = require_dictionaryManipulator_orderDictionaries.orderDictionaries;
|
|
38
|
+
exports.parseDictionarySelector = require_dictionaryManipulator_qualifiedDictionary.parseDictionarySelector;
|
|
39
|
+
exports.reconstructQualifiedEntry = require_dictionaryManipulator_qualifiedDictionary.reconstructQualifiedEntry;
|
|
25
40
|
exports.removeContentNodeByKeyPath = require_dictionaryManipulator_removeContentNodeByKeyPath.removeContentNodeByKeyPath;
|
|
26
41
|
exports.renameContentNodeByKeyPath = require_dictionaryManipulator_renameContentNodeByKeyPath.renameContentNodeByKeyPath;
|
|
42
|
+
exports.resolveQualifiedDictionary = require_dictionaryManipulator_qualifiedDictionary.resolveQualifiedDictionary;
|
|
43
|
+
exports.resolveQualifiedDynamicContent = require_dictionaryManipulator_qualifiedDictionary.resolveQualifiedDynamicContent;
|
|
44
|
+
exports.resolveQualifiedDynamicContentAsync = require_dictionaryManipulator_qualifiedDictionary.resolveQualifiedDynamicContentAsync;
|
|
27
45
|
exports.updateNodeChildren = require_dictionaryManipulator_updateNodeChildren.updateNodeChildren;
|
|
@@ -49,11 +49,14 @@ const arrayMerge = (destinationArray, sourceArray) => {
|
|
|
49
49
|
};
|
|
50
50
|
const mergeDictionaries = (dictionaries) => {
|
|
51
51
|
const localIds = Array.from(new Set(dictionaries.filter((dict) => dict.localId).map((dict) => dict.localId)));
|
|
52
|
+
if (!dictionaries[0] || dictionaries.length <= 1) return dictionaries[0];
|
|
52
53
|
const dictionariesKeys = dictionaries.map((dict) => dict.key);
|
|
53
54
|
if (new Set(dictionariesKeys).size !== 1) throw new Error("All dictionaries must have the same key");
|
|
54
55
|
let mergedContent = dictionaries[0].content;
|
|
55
56
|
for (let i = 1; i < dictionaries.length; i++) {
|
|
56
|
-
const
|
|
57
|
+
const dict = dictionaries[i];
|
|
58
|
+
if (!dict) continue;
|
|
59
|
+
const currentDictionary = require_deepTransformPlugins_getMultilingualDictionary.getMultilingualDictionary(dict);
|
|
57
60
|
checkTypesMatch(mergedContent, currentDictionary.content, currentDictionary.localId, currentDictionary.key, []);
|
|
58
61
|
mergedContent = customMerge(mergedContent, currentDictionary.content);
|
|
59
62
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mergeDictionaries.cjs","names":["getNodeType","getMultilingualDictionary"],"sources":["../../../src/dictionaryManipulator/mergeDictionaries.ts"],"sourcesContent":["import { log } from '@intlayer/config/built';\nimport { colorizeKey, getAppLogger } from '@intlayer/config/logger';\nimport type {\n ContentNode,\n Dictionary,\n LocalDictionaryId,\n} from '@intlayer/types/dictionary';\nimport { getMultilingualDictionary } from '../deepTransformPlugins';\nimport { getNodeType } from './getNodeType';\n\n// Extended type that includes arrays for internal merge operations\ntype MergeableContent = ContentNode | ContentNode[];\n\nconst checkTypesMatch = (\n object1: ContentNode,\n object2: ContentNode,\n object2LocalId: LocalDictionaryId | undefined,\n dictionaryKey: string,\n path: string[] = []\n): void => {\n const appLogger = getAppLogger({ log });\n\n // If either side is missing/undefined, allow merge without error\n if (\n object1 === undefined ||\n object1 === null ||\n object2 === undefined ||\n object2 === null\n )\n return;\n\n const type1 = getNodeType(object1);\n const type2 = getNodeType(object2);\n\n // Unknown types are treated as flexible; skip strict mismatch reporting\n if (type1 === 'unknown' || type2 === 'unknown') return;\n\n if (type1 !== type2) {\n appLogger(\n [\n `Error: Dictionary ${colorizeKey(dictionaryKey)} has a multiple content files with type mismatch at path \"${path.join('.')}\": Cannot merge ${type1} with ${type2} while merging ${object2LocalId}`,\n ],\n {\n level: 'error',\n }\n );\n\n return;\n }\n};\n\n// Custom merge function that prefers destination (first dictionary) values\nconst customMerge = (\n destination: ContentNode,\n source: ContentNode\n): MergeableContent => {\n // If destination is undefined/null, use source\n if (destination === undefined || destination === null) {\n return source;\n }\n\n // If source is undefined/null, use destination\n if (source === undefined || source === null) {\n return destination;\n }\n\n // For primitive values, prefer destination (first dictionary)\n if (typeof destination !== 'object' || typeof source !== 'object') {\n return destination;\n }\n\n // For arrays, use our custom array merge\n if (Array.isArray(destination) && Array.isArray(source)) {\n return arrayMerge(\n destination as ContentNode[],\n source as ContentNode[]\n ) as MergeableContent;\n }\n\n // For objects, recursively merge with our custom logic\n if (typeof destination === 'object' && typeof source === 'object') {\n const result: Record<string, MergeableContent> = {};\n const allKeys = new Set([\n ...Object.keys(destination as unknown as Record<string, ContentNode>),\n ...Object.keys(source as unknown as Record<string, ContentNode>),\n ]);\n\n for (const key of allKeys) {\n result[key] = customMerge(\n (destination as unknown as Record<string, ContentNode>)[key],\n (source as unknown as Record<string, ContentNode>)[key]\n );\n }\n\n return result as unknown as MergeableContent;\n }\n\n // Fallback to destination\n return destination;\n};\n\n// Custom array merge strategy that merges arrays by key when present, otherwise by index\nconst arrayMerge = (\n destinationArray: ContentNode[],\n sourceArray: ContentNode[]\n): MergeableContent[] => {\n // Check if both arrays contain only primitives\n const destHasOnlyPrimitives = destinationArray.every(\n (item) => typeof item !== 'object' || item === null\n );\n const sourceHasOnlyPrimitives = sourceArray.every(\n (item) => typeof item !== 'object' || item === null\n );\n\n // If both arrays contain only primitives, use the source array (second dictionary)\n if (destHasOnlyPrimitives && sourceHasOnlyPrimitives) {\n return sourceArray;\n }\n\n // Otherwise, merge by index with object merging logic\n const result: MergeableContent[] = [];\n const maxLength = Math.max(destinationArray.length, sourceArray.length);\n\n for (let i = 0; i < maxLength; i++) {\n const destItem = destinationArray[i];\n const sourceItem = sourceArray[i];\n\n if (destItem === undefined && sourceItem === undefined) {\n } else if (destItem === undefined) {\n // Only source exists, add it\n result.push(sourceItem);\n } else if (sourceItem === undefined) {\n // Only destination exists, add it\n result.push(destItem);\n } else {\n // Both exist, merge them\n if (\n typeof destItem === 'object' &&\n typeof sourceItem === 'object' &&\n destItem !== null &&\n sourceItem !== null\n ) {\n // Check if both objects have a 'key' property for keyed merging\n if (\n 'key' in destItem &&\n 'key' in sourceItem &&\n (destItem as Record<string, string>).key ===\n (sourceItem as Record<string, string>).key\n ) {\n // Merge objects with same key, preferring destination (first dictionary) values\n result.push(customMerge(destItem, sourceItem));\n } else {\n // Merge objects by index, preferring destination (first dictionary) values\n result.push(customMerge(destItem, sourceItem));\n }\n } else {\n // For primitives or non-objects, use destination value (first dictionary)\n result.push(destItem);\n }\n }\n }\n\n return result;\n};\n\nexport const mergeDictionaries = (dictionaries: Dictionary[]): Dictionary => {\n const localIds = Array.from(\n new Set<LocalDictionaryId>(\n dictionaries.filter((dict) => dict.localId).map((dict) => dict.localId!)\n )\n );\n\n const dictionariesKeys = dictionaries.map((dict) => dict.key);\n\n // Check if all dictionaries have the same key\n if (new Set(dictionariesKeys).size !== 1) {\n throw new Error('All dictionaries must have the same key');\n }\n\n let mergedContent: Dictionary['content'] = dictionaries[0].content;\n\n for (let i = 1; i < dictionaries.length; i++) {\n // If the dictionary is a per-locale dictionary, transform it to a partial multilingual dictionary\n const currentDictionary = getMultilingualDictionary(
|
|
1
|
+
{"version":3,"file":"mergeDictionaries.cjs","names":["getNodeType","getMultilingualDictionary"],"sources":["../../../src/dictionaryManipulator/mergeDictionaries.ts"],"sourcesContent":["import { log } from '@intlayer/config/built';\nimport { colorizeKey, getAppLogger } from '@intlayer/config/logger';\nimport type {\n ContentNode,\n Dictionary,\n LocalDictionaryId,\n} from '@intlayer/types/dictionary';\nimport { getMultilingualDictionary } from '../deepTransformPlugins';\nimport { getNodeType } from './getNodeType';\n\n// Extended type that includes arrays for internal merge operations\ntype MergeableContent = ContentNode | ContentNode[];\n\nconst checkTypesMatch = (\n object1: ContentNode,\n object2: ContentNode,\n object2LocalId: LocalDictionaryId | undefined,\n dictionaryKey: string,\n path: string[] = []\n): void => {\n const appLogger = getAppLogger({ log });\n\n // If either side is missing/undefined, allow merge without error\n if (\n object1 === undefined ||\n object1 === null ||\n object2 === undefined ||\n object2 === null\n )\n return;\n\n const type1 = getNodeType(object1);\n const type2 = getNodeType(object2);\n\n // Unknown types are treated as flexible; skip strict mismatch reporting\n if (type1 === 'unknown' || type2 === 'unknown') return;\n\n if (type1 !== type2) {\n appLogger(\n [\n `Error: Dictionary ${colorizeKey(dictionaryKey)} has a multiple content files with type mismatch at path \"${path.join('.')}\": Cannot merge ${type1} with ${type2} while merging ${object2LocalId}`,\n ],\n {\n level: 'error',\n }\n );\n\n return;\n }\n};\n\n// Custom merge function that prefers destination (first dictionary) values\nconst customMerge = (\n destination: ContentNode,\n source: ContentNode\n): MergeableContent => {\n // If destination is undefined/null, use source\n if (destination === undefined || destination === null) {\n return source;\n }\n\n // If source is undefined/null, use destination\n if (source === undefined || source === null) {\n return destination;\n }\n\n // For primitive values, prefer destination (first dictionary)\n if (typeof destination !== 'object' || typeof source !== 'object') {\n return destination;\n }\n\n // For arrays, use our custom array merge\n if (Array.isArray(destination) && Array.isArray(source)) {\n return arrayMerge(\n destination as ContentNode[],\n source as ContentNode[]\n ) as MergeableContent;\n }\n\n // For objects, recursively merge with our custom logic\n if (typeof destination === 'object' && typeof source === 'object') {\n const result: Record<string, MergeableContent> = {};\n const allKeys = new Set([\n ...Object.keys(destination as unknown as Record<string, ContentNode>),\n ...Object.keys(source as unknown as Record<string, ContentNode>),\n ]);\n\n for (const key of allKeys) {\n result[key] = customMerge(\n (destination as unknown as Record<string, ContentNode>)[key],\n (source as unknown as Record<string, ContentNode>)[key]\n );\n }\n\n return result as unknown as MergeableContent;\n }\n\n // Fallback to destination\n return destination;\n};\n\n// Custom array merge strategy that merges arrays by key when present, otherwise by index\nconst arrayMerge = (\n destinationArray: ContentNode[],\n sourceArray: ContentNode[]\n): MergeableContent[] => {\n // Check if both arrays contain only primitives\n const destHasOnlyPrimitives = destinationArray.every(\n (item) => typeof item !== 'object' || item === null\n );\n const sourceHasOnlyPrimitives = sourceArray.every(\n (item) => typeof item !== 'object' || item === null\n );\n\n // If both arrays contain only primitives, use the source array (second dictionary)\n if (destHasOnlyPrimitives && sourceHasOnlyPrimitives) {\n return sourceArray;\n }\n\n // Otherwise, merge by index with object merging logic\n const result: MergeableContent[] = [];\n const maxLength = Math.max(destinationArray.length, sourceArray.length);\n\n for (let i = 0; i < maxLength; i++) {\n const destItem = destinationArray[i];\n const sourceItem = sourceArray[i];\n\n if (destItem === undefined && sourceItem === undefined) {\n } else if (destItem === undefined) {\n // Only source exists, add it\n result.push(sourceItem);\n } else if (sourceItem === undefined) {\n // Only destination exists, add it\n result.push(destItem);\n } else {\n // Both exist, merge them\n if (\n typeof destItem === 'object' &&\n typeof sourceItem === 'object' &&\n destItem !== null &&\n sourceItem !== null\n ) {\n // Check if both objects have a 'key' property for keyed merging\n if (\n 'key' in destItem &&\n 'key' in sourceItem &&\n (destItem as Record<string, string>).key ===\n (sourceItem as Record<string, string>).key\n ) {\n // Merge objects with same key, preferring destination (first dictionary) values\n result.push(customMerge(destItem, sourceItem));\n } else {\n // Merge objects by index, preferring destination (first dictionary) values\n result.push(customMerge(destItem, sourceItem));\n }\n } else {\n // For primitives or non-objects, use destination value (first dictionary)\n result.push(destItem);\n }\n }\n }\n\n return result;\n};\n\nexport const mergeDictionaries = (dictionaries: Dictionary[]): Dictionary => {\n const localIds = Array.from(\n new Set<LocalDictionaryId>(\n dictionaries.filter((dict) => dict.localId).map((dict) => dict.localId!)\n )\n );\n\n if (!dictionaries[0] || dictionaries.length <= 1) {\n return dictionaries[0]!;\n }\n\n const dictionariesKeys = dictionaries.map((dict) => dict.key);\n\n // Check if all dictionaries have the same key\n if (new Set(dictionariesKeys).size !== 1) {\n throw new Error('All dictionaries must have the same key');\n }\n\n let mergedContent: Dictionary['content'] = dictionaries[0].content;\n\n for (let i = 1; i < dictionaries.length; i++) {\n const dict = dictionaries[i];\n\n if (!dict) continue;\n\n // If the dictionary is a per-locale dictionary, transform it to a partial multilingual dictionary\n const currentDictionary = getMultilingualDictionary(dict);\n\n // Check types before merging\n checkTypesMatch(\n mergedContent,\n currentDictionary.content,\n currentDictionary.localId,\n currentDictionary.key,\n []\n );\n\n mergedContent = customMerge(\n mergedContent,\n currentDictionary.content\n ) as ContentNode;\n }\n\n const mergedDictionary: Dictionary = {\n key: dictionaries[0].key,\n content: mergedContent,\n localIds,\n };\n\n return mergedDictionary;\n};\n"],"mappings":";;;;;;;;AAaA,MAAM,mBACJ,SACA,SACA,gBACA,eACA,OAAiB,EAAE,KACV;CACT,MAAM,sDAAyB,EAAE,iCAAK,CAAC;AAGvC,KACE,YAAY,UACZ,YAAY,QACZ,YAAY,UACZ,YAAY,KAEZ;CAEF,MAAM,QAAQA,sDAAY,QAAQ;CAClC,MAAM,QAAQA,sDAAY,QAAQ;AAGlC,KAAI,UAAU,aAAa,UAAU,UAAW;AAEhD,KAAI,UAAU,OAAO;AACnB,YACE,CACE,8DAAiC,cAAc,CAAC,4DAA4D,KAAK,KAAK,IAAI,CAAC,kBAAkB,MAAM,QAAQ,MAAM,iBAAiB,iBACnL,EACD,EACE,OAAO,SACR,CACF;AAED;;;AAKJ,MAAM,eACJ,aACA,WACqB;AAErB,KAAI,gBAAgB,UAAa,gBAAgB,KAC/C,QAAO;AAIT,KAAI,WAAW,UAAa,WAAW,KACrC,QAAO;AAIT,KAAI,OAAO,gBAAgB,YAAY,OAAO,WAAW,SACvD,QAAO;AAIT,KAAI,MAAM,QAAQ,YAAY,IAAI,MAAM,QAAQ,OAAO,CACrD,QAAO,WACL,aACA,OACD;AAIH,KAAI,OAAO,gBAAgB,YAAY,OAAO,WAAW,UAAU;EACjE,MAAM,SAA2C,EAAE;EACnD,MAAM,UAAU,IAAI,IAAI,CACtB,GAAG,OAAO,KAAK,YAAsD,EACrE,GAAG,OAAO,KAAK,OAAiD,CACjE,CAAC;AAEF,OAAK,MAAM,OAAO,QAChB,QAAO,OAAO,YACX,YAAuD,MACvD,OAAkD,KACpD;AAGH,SAAO;;AAIT,QAAO;;AAIT,MAAM,cACJ,kBACA,gBACuB;CAEvB,MAAM,wBAAwB,iBAAiB,OAC5C,SAAS,OAAO,SAAS,YAAY,SAAS,KAChD;CACD,MAAM,0BAA0B,YAAY,OACzC,SAAS,OAAO,SAAS,YAAY,SAAS,KAChD;AAGD,KAAI,yBAAyB,wBAC3B,QAAO;CAIT,MAAM,SAA6B,EAAE;CACrC,MAAM,YAAY,KAAK,IAAI,iBAAiB,QAAQ,YAAY,OAAO;AAEvE,MAAK,IAAI,IAAI,GAAG,IAAI,WAAW,KAAK;EAClC,MAAM,WAAW,iBAAiB;EAClC,MAAM,aAAa,YAAY;AAE/B,MAAI,aAAa,UAAa,eAAe,QAAW,YAC7C,aAAa,OAEtB,QAAO,KAAK,WAAW;WACd,eAAe,OAExB,QAAO,KAAK,SAAS;WAInB,OAAO,aAAa,YACpB,OAAO,eAAe,YACtB,aAAa,QACb,eAAe,KAGf,KACE,SAAS,YACT,SAAS,cACR,SAAoC,QAClC,WAAsC,IAGzC,QAAO,KAAK,YAAY,UAAU,WAAW,CAAC;MAG9C,QAAO,KAAK,YAAY,UAAU,WAAW,CAAC;MAIhD,QAAO,KAAK,SAAS;;AAK3B,QAAO;;AAGT,MAAa,qBAAqB,iBAA2C;CAC3E,MAAM,WAAW,MAAM,KACrB,IAAI,IACF,aAAa,QAAQ,SAAS,KAAK,QAAQ,CAAC,KAAK,SAAS,KAAK,QAAS,CACzE,CACF;AAED,KAAI,CAAC,aAAa,MAAM,aAAa,UAAU,EAC7C,QAAO,aAAa;CAGtB,MAAM,mBAAmB,aAAa,KAAK,SAAS,KAAK,IAAI;AAG7D,KAAI,IAAI,IAAI,iBAAiB,CAAC,SAAS,EACrC,OAAM,IAAI,MAAM,0CAA0C;CAG5D,IAAI,gBAAuC,aAAa,GAAG;AAE3D,MAAK,IAAI,IAAI,GAAG,IAAI,aAAa,QAAQ,KAAK;EAC5C,MAAM,OAAO,aAAa;AAE1B,MAAI,CAAC,KAAM;EAGX,MAAM,oBAAoBC,iFAA0B,KAAK;AAGzD,kBACE,eACA,kBAAkB,SAClB,kBAAkB,SAClB,kBAAkB,KAClB,EAAE,CACH;AAED,kBAAgB,YACd,eACA,kBAAkB,QACnB;;AASH,QAAO;EALL,KAAK,aAAa,GAAG;EACrB,SAAS;EACT;EAGqB"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
const require_runtime = require('../_virtual/_rolldown/runtime.cjs');
|
|
3
|
+
const require_dictionaryManipulator_qualifiedDictionary = require('./qualifiedDictionary.cjs');
|
|
4
|
+
const require_dictionaryManipulator_mergeDictionaries = require('./mergeDictionaries.cjs');
|
|
5
|
+
let _intlayer_config_built = require("@intlayer/config/built");
|
|
6
|
+
let _intlayer_config_logger = require("@intlayer/config/logger");
|
|
7
|
+
|
|
8
|
+
//#region src/dictionaryManipulator/mergeQualifiedDictionaries.ts
|
|
9
|
+
/**
|
|
10
|
+
* Merges sibling dictionaries sharing the same key, honouring qualifiers.
|
|
11
|
+
*
|
|
12
|
+
* - No dictionary declares a qualifier → behaves exactly like
|
|
13
|
+
* `mergeDictionaries` (single merged dictionary).
|
|
14
|
+
* - At least one dictionary declares a qualifier → the group's dimension set is
|
|
15
|
+
* the union of every declared dimension (in canonical order
|
|
16
|
+
* `variant → meta → item`). Dictionaries are grouped by their composite id
|
|
17
|
+
* (one segment per dimension), merged within each group (locale completion /
|
|
18
|
+
* priority overrides preserved), and a `QualifiedDictionaryGroup` is returned.
|
|
19
|
+
* Unqualified siblings act as shared base content merged into every entry.
|
|
20
|
+
*
|
|
21
|
+
* Every qualified entry must declare ALL dimensions of the group; an entry that
|
|
22
|
+
* declares only a subset is ambiguous and is rejected with an error log.
|
|
23
|
+
*/
|
|
24
|
+
const mergeQualifiedDictionaries = (dictionaries) => {
|
|
25
|
+
const perDictionaryTypes = dictionaries.map(require_dictionaryManipulator_qualifiedDictionary.getDictionaryQualifierTypes);
|
|
26
|
+
const declaredDimensions = /* @__PURE__ */ new Set();
|
|
27
|
+
for (const types of perDictionaryTypes) for (const type of types) declaredDimensions.add(type);
|
|
28
|
+
const groupQualifierTypes = require_dictionaryManipulator_qualifiedDictionary.QUALIFIER_ORDER.filter((qualifierType) => declaredDimensions.has(qualifierType));
|
|
29
|
+
if (groupQualifierTypes.length === 0) return require_dictionaryManipulator_mergeDictionaries.mergeDictionaries(dictionaries);
|
|
30
|
+
const appLogger = (0, _intlayer_config_logger.getAppLogger)({ log: _intlayer_config_built.log });
|
|
31
|
+
const baseDictionaries = [];
|
|
32
|
+
const entriesDictionaries = /* @__PURE__ */ new Map();
|
|
33
|
+
dictionaries.forEach((dictionary, index) => {
|
|
34
|
+
if (perDictionaryTypes[index].length === 0) {
|
|
35
|
+
baseDictionaries.push(dictionary);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const compositeId = require_dictionaryManipulator_qualifiedDictionary.getDictionaryCompositeId(dictionary, groupQualifierTypes);
|
|
39
|
+
if (compositeId === void 0) {
|
|
40
|
+
appLogger(`Dictionary ${(0, _intlayer_config_logger.colorizeKey)(dictionary.key)} declares (${perDictionaryTypes[index].join(", ")}) but the key's dimensions are (${groupQualifierTypes.join(", ")}); every entry must declare all of them. Entry ignored${dictionary.filePath ? ` - ${dictionary.filePath}` : ""}.`, { level: "error" });
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const existingEntries = entriesDictionaries.get(compositeId) ?? [];
|
|
44
|
+
existingEntries.push(dictionary);
|
|
45
|
+
entriesDictionaries.set(compositeId, existingEntries);
|
|
46
|
+
});
|
|
47
|
+
const content = {};
|
|
48
|
+
const metaByCompositeId = {};
|
|
49
|
+
const declaresMeta = groupQualifierTypes.includes("meta");
|
|
50
|
+
let importMode;
|
|
51
|
+
for (const [compositeId, qualifiedDictionaries] of entriesDictionaries) {
|
|
52
|
+
content[compositeId] = require_dictionaryManipulator_mergeDictionaries.mergeDictionaries([...qualifiedDictionaries, ...baseDictionaries]).content;
|
|
53
|
+
const [firstQualified] = qualifiedDictionaries;
|
|
54
|
+
if (declaresMeta && firstQualified?.meta !== void 0) metaByCompositeId[compositeId] = firstQualified.meta;
|
|
55
|
+
importMode ??= firstQualified?.importMode;
|
|
56
|
+
}
|
|
57
|
+
const localIds = Array.from(new Set(dictionaries.filter((dictionary) => dictionary.localId).map((dictionary) => dictionary.localId)));
|
|
58
|
+
return {
|
|
59
|
+
key: dictionaries[0].key,
|
|
60
|
+
qualifierTypes: groupQualifierTypes,
|
|
61
|
+
content,
|
|
62
|
+
...declaresMeta && { meta: metaByCompositeId },
|
|
63
|
+
...importMode !== void 0 && { importMode },
|
|
64
|
+
localIds
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
//#endregion
|
|
69
|
+
exports.mergeQualifiedDictionaries = mergeQualifiedDictionaries;
|
|
70
|
+
//# sourceMappingURL=mergeQualifiedDictionaries.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mergeQualifiedDictionaries.cjs","names":["getDictionaryQualifierTypes","QUALIFIER_ORDER","mergeDictionaries","getDictionaryCompositeId"],"sources":["../../../src/dictionaryManipulator/mergeQualifiedDictionaries.ts"],"sourcesContent":["import { log } from '@intlayer/config/built';\nimport { colorizeKey, getAppLogger } from '@intlayer/config/logger';\nimport type {\n Dictionary,\n DictionaryMeta,\n DictionaryQualifierType,\n QualifiedDictionaryGroup,\n} from '@intlayer/types/dictionary';\nimport { mergeDictionaries } from './mergeDictionaries';\nimport {\n getDictionaryCompositeId,\n getDictionaryQualifierTypes,\n QUALIFIER_ORDER,\n} from './qualifiedDictionary';\n\n/**\n * Merges sibling dictionaries sharing the same key, honouring qualifiers.\n *\n * - No dictionary declares a qualifier → behaves exactly like\n * `mergeDictionaries` (single merged dictionary).\n * - At least one dictionary declares a qualifier → the group's dimension set is\n * the union of every declared dimension (in canonical order\n * `variant → meta → item`). Dictionaries are grouped by their composite id\n * (one segment per dimension), merged within each group (locale completion /\n * priority overrides preserved), and a `QualifiedDictionaryGroup` is returned.\n * Unqualified siblings act as shared base content merged into every entry.\n *\n * Every qualified entry must declare ALL dimensions of the group; an entry that\n * declares only a subset is ambiguous and is rejected with an error log.\n */\nexport const mergeQualifiedDictionaries = (\n dictionaries: Dictionary[]\n): Dictionary | QualifiedDictionaryGroup => {\n const perDictionaryTypes = dictionaries.map(getDictionaryQualifierTypes);\n\n const declaredDimensions = new Set<DictionaryQualifierType>();\n for (const types of perDictionaryTypes) {\n for (const type of types) declaredDimensions.add(type);\n }\n\n // Canonical order, restricted to the dimensions actually declared.\n const groupQualifierTypes = QUALIFIER_ORDER.filter((qualifierType) =>\n declaredDimensions.has(qualifierType)\n );\n\n if (groupQualifierTypes.length === 0) {\n return mergeDictionaries(dictionaries);\n }\n\n const appLogger = getAppLogger({ log });\n\n const baseDictionaries: Dictionary[] = [];\n const entriesDictionaries = new Map<string, Dictionary[]>();\n\n dictionaries.forEach((dictionary, index) => {\n if (perDictionaryTypes[index].length === 0) {\n baseDictionaries.push(dictionary);\n return;\n }\n\n const compositeId = getDictionaryCompositeId(\n dictionary,\n groupQualifierTypes\n );\n\n if (compositeId === undefined) {\n appLogger(\n `Dictionary ${colorizeKey(dictionary.key)} declares (${perDictionaryTypes[index].join(', ')}) but the key's dimensions are (${groupQualifierTypes.join(', ')}); every entry must declare all of them. Entry ignored${dictionary.filePath ? ` - ${dictionary.filePath}` : ''}.`,\n { level: 'error' }\n );\n return;\n }\n\n const existingEntries = entriesDictionaries.get(compositeId) ?? [];\n existingEntries.push(dictionary);\n entriesDictionaries.set(compositeId, existingEntries);\n });\n\n // `content` maps each composite id to its resolved content node directly; the\n // qualifier coordinates live in the key, not in a per-entry wrapper. `meta`\n // preserves the declared meta fields (composite id only encodes `meta.id`).\n const content: Record<string, unknown> = {};\n const metaByCompositeId: Record<string, DictionaryMeta> = {};\n const declaresMeta = groupQualifierTypes.includes('meta');\n\n let importMode: Dictionary['importMode'];\n\n for (const [compositeId, qualifiedDictionaries] of entriesDictionaries) {\n // Unqualified siblings act as shared base content: appended last so the\n // qualified entries take precedence (mergeDictionaries prefers first).\n const mergedEntry = mergeDictionaries([\n ...qualifiedDictionaries,\n ...baseDictionaries,\n ]);\n\n content[compositeId] = mergedEntry.content;\n\n const [firstQualified] = qualifiedDictionaries;\n\n if (declaresMeta && firstQualified?.meta !== undefined) {\n metaByCompositeId[compositeId] = firstQualified.meta;\n }\n\n importMode ??= firstQualified?.importMode;\n }\n\n const localIds = Array.from(\n new Set(\n dictionaries\n .filter((dictionary) => dictionary.localId)\n .map((dictionary) => dictionary.localId!)\n )\n );\n\n return {\n key: dictionaries[0]!.key,\n qualifierTypes: groupQualifierTypes,\n content,\n ...(declaresMeta && { meta: metaByCompositeId }),\n ...(importMode !== undefined && { importMode }),\n localIds,\n };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA8BA,MAAa,8BACX,iBAC0C;CAC1C,MAAM,qBAAqB,aAAa,IAAIA,8EAA4B;CAExE,MAAM,qCAAqB,IAAI,KAA8B;AAC7D,MAAK,MAAM,SAAS,mBAClB,MAAK,MAAM,QAAQ,MAAO,oBAAmB,IAAI,KAAK;CAIxD,MAAM,sBAAsBC,kEAAgB,QAAQ,kBAClD,mBAAmB,IAAI,cAAc,CACtC;AAED,KAAI,oBAAoB,WAAW,EACjC,QAAOC,kEAAkB,aAAa;CAGxC,MAAM,sDAAyB,EAAE,iCAAK,CAAC;CAEvC,MAAM,mBAAiC,EAAE;CACzC,MAAM,sCAAsB,IAAI,KAA2B;AAE3D,cAAa,SAAS,YAAY,UAAU;AAC1C,MAAI,mBAAmB,OAAO,WAAW,GAAG;AAC1C,oBAAiB,KAAK,WAAW;AACjC;;EAGF,MAAM,cAAcC,2EAClB,YACA,oBACD;AAED,MAAI,gBAAgB,QAAW;AAC7B,aACE,uDAA0B,WAAW,IAAI,CAAC,aAAa,mBAAmB,OAAO,KAAK,KAAK,CAAC,kCAAkC,oBAAoB,KAAK,KAAK,CAAC,wDAAwD,WAAW,WAAW,MAAM,WAAW,aAAa,GAAG,IAC5Q,EAAE,OAAO,SAAS,CACnB;AACD;;EAGF,MAAM,kBAAkB,oBAAoB,IAAI,YAAY,IAAI,EAAE;AAClE,kBAAgB,KAAK,WAAW;AAChC,sBAAoB,IAAI,aAAa,gBAAgB;GACrD;CAKF,MAAM,UAAmC,EAAE;CAC3C,MAAM,oBAAoD,EAAE;CAC5D,MAAM,eAAe,oBAAoB,SAAS,OAAO;CAEzD,IAAI;AAEJ,MAAK,MAAM,CAAC,aAAa,0BAA0B,qBAAqB;AAQtE,UAAQ,eALYD,kEAAkB,CACpC,GAAG,uBACH,GAAG,iBACJ,CAEiC,CAAC;EAEnC,MAAM,CAAC,kBAAkB;AAEzB,MAAI,gBAAgB,gBAAgB,SAAS,OAC3C,mBAAkB,eAAe,eAAe;AAGlD,iBAAe,gBAAgB;;CAGjC,MAAM,WAAW,MAAM,KACrB,IAAI,IACF,aACG,QAAQ,eAAe,WAAW,QAAQ,CAC1C,KAAK,eAAe,WAAW,QAAS,CAC5C,CACF;AAED,QAAO;EACL,KAAK,aAAa,GAAI;EACtB,gBAAgB;EAChB;EACA,GAAI,gBAAgB,EAAE,MAAM,mBAAmB;EAC/C,GAAI,eAAe,UAAa,EAAE,YAAY;EAC9C;EACD"}
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
|
|
3
|
+
//#region src/dictionaryManipulator/qualifiedDictionary.ts
|
|
4
|
+
/**
|
|
5
|
+
* Selector keys that are reserved for dictionary resolution and therefore
|
|
6
|
+
* excluded from meta field matching.
|
|
7
|
+
*/
|
|
8
|
+
const RESERVED_SELECTOR_KEYS = [
|
|
9
|
+
"locale",
|
|
10
|
+
"item",
|
|
11
|
+
"variant"
|
|
12
|
+
];
|
|
13
|
+
/**
|
|
14
|
+
* Canonical order of qualifier dimensions. A key that declares several
|
|
15
|
+
* dimensions always nests them in this order, with `item` innermost so it can
|
|
16
|
+
* act as the collection (array) axis.
|
|
17
|
+
*/
|
|
18
|
+
const QUALIFIER_ORDER = [
|
|
19
|
+
"variant",
|
|
20
|
+
"meta",
|
|
21
|
+
"item"
|
|
22
|
+
];
|
|
23
|
+
/**
|
|
24
|
+
* Separator joining per-dimension ids into a composite entry id. Also used as
|
|
25
|
+
* the chunk path separator in dynamic mode.
|
|
26
|
+
*/
|
|
27
|
+
const COMPOSITE_ID_SEPARATOR = "/";
|
|
28
|
+
/**
|
|
29
|
+
* Returns the qualifier dimensions declared on a dictionary, in canonical
|
|
30
|
+
* order (`variant → meta → item`). Empty when the dictionary is unqualified
|
|
31
|
+
* (plain dictionary or shared base content of a qualified group).
|
|
32
|
+
*/
|
|
33
|
+
const getDictionaryQualifierTypes = (dictionary) => {
|
|
34
|
+
const declaredQualifiers = [];
|
|
35
|
+
if (typeof dictionary.variant === "string") declaredQualifiers.push("variant");
|
|
36
|
+
if (dictionary.meta !== void 0) declaredQualifiers.push("meta");
|
|
37
|
+
if (typeof dictionary.item === "number") declaredQualifiers.push("item");
|
|
38
|
+
return declaredQualifiers;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Returns the qualifier identifier of a dictionary for the given qualifier
|
|
42
|
+
* dimension — one segment of the composite entry id.
|
|
43
|
+
*
|
|
44
|
+
* - 'variant' → the variant name
|
|
45
|
+
* - 'meta' → the `meta.id` discriminator
|
|
46
|
+
* - 'item' → the item index as string
|
|
47
|
+
*/
|
|
48
|
+
const getDictionaryQualifierId = (dictionary, qualifierType) => {
|
|
49
|
+
if (qualifierType === "variant") return dictionary.variant;
|
|
50
|
+
if (qualifierType === "meta") {
|
|
51
|
+
const metaId = dictionary.meta?.id;
|
|
52
|
+
return metaId === void 0 ? void 0 : String(metaId);
|
|
53
|
+
}
|
|
54
|
+
return dictionary.item === void 0 ? void 0 : String(dictionary.item);
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Returns the per-dimension id segments of a dictionary for the given ordered
|
|
58
|
+
* dimension set, or `undefined` when the dictionary does not declare every
|
|
59
|
+
* dimension of the set.
|
|
60
|
+
*/
|
|
61
|
+
const getDictionaryQualifierSegments = (dictionary, qualifierTypes) => {
|
|
62
|
+
const segments = [];
|
|
63
|
+
for (const qualifierType of qualifierTypes) {
|
|
64
|
+
const id = getDictionaryQualifierId(dictionary, qualifierType);
|
|
65
|
+
if (id === void 0) return void 0;
|
|
66
|
+
segments.push(id);
|
|
67
|
+
}
|
|
68
|
+
return segments;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Builds the composite entry id of a dictionary — its per-dimension id segments
|
|
72
|
+
* joined in canonical order. `undefined` when a dimension is missing.
|
|
73
|
+
*/
|
|
74
|
+
const getDictionaryCompositeId = (dictionary, qualifierTypes) => getDictionaryQualifierSegments(dictionary, qualifierTypes)?.join("/");
|
|
75
|
+
/**
|
|
76
|
+
* Checks that every declared meta field is provided and equal in the selector.
|
|
77
|
+
* Reserved keys (`locale`, `item`, `variant`) are skipped; `meta.id` is part of
|
|
78
|
+
* the equality check.
|
|
79
|
+
*/
|
|
80
|
+
const metaFieldsMatch = (meta, selector) => {
|
|
81
|
+
if (!meta) return false;
|
|
82
|
+
return Object.entries(meta).every(([metaField, declaredValue]) => {
|
|
83
|
+
if (RESERVED_SELECTOR_KEYS.includes(metaField)) return true;
|
|
84
|
+
const providedValue = selector?.[metaField];
|
|
85
|
+
return providedValue !== void 0 && String(providedValue) === String(declaredValue);
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Tests whether a group entry matches a selector across every declared
|
|
90
|
+
* dimension. The `item` dimension matches any value when the selector does not
|
|
91
|
+
* provide one (open collection axis).
|
|
92
|
+
*/
|
|
93
|
+
const entryMatchesSelector = (entry, qualifierTypes, selector) => qualifierTypes.every((qualifierType) => {
|
|
94
|
+
if (qualifierType === "variant") return entry.variant === (selector?.variant ?? "default");
|
|
95
|
+
if (qualifierType === "item") return selector?.item === void 0 || String(entry.item) === String(selector.item);
|
|
96
|
+
return metaFieldsMatch(entry.meta, selector);
|
|
97
|
+
});
|
|
98
|
+
/**
|
|
99
|
+
* Type guard discriminating a `QualifiedDictionaryGroup` (merge output of a
|
|
100
|
+
* qualified key) from a plain `Dictionary`. Both carry a `content` field; only
|
|
101
|
+
* the group declares `qualifierTypes`, which is therefore the discriminator.
|
|
102
|
+
*/
|
|
103
|
+
const isQualifiedDictionaryGroup = (value) => typeof value === "object" && value !== null && "qualifierTypes" in value && Array.isArray(value.qualifierTypes) && "content" in value;
|
|
104
|
+
/**
|
|
105
|
+
* Reconstructs a resolvable {@link Dictionary} from a single entry of a
|
|
106
|
+
* qualified group: the content node stored under its composite id, plus the
|
|
107
|
+
* qualifier coordinates decoded from that id (`variant`, `item`) and the
|
|
108
|
+
* preserved `meta` object for the meta dimension.
|
|
109
|
+
*
|
|
110
|
+
* This keeps the resolver's matching/transform code unchanged: it still sees a
|
|
111
|
+
* `{ key, content, variant?, item?, meta? }` shape, even though the stored
|
|
112
|
+
* format no longer duplicates those fields per entry.
|
|
113
|
+
*/
|
|
114
|
+
const reconstructQualifiedEntry = (group, compositeId) => {
|
|
115
|
+
const segments = compositeId.split("/");
|
|
116
|
+
const entry = {
|
|
117
|
+
key: group.key,
|
|
118
|
+
content: group.content[compositeId]
|
|
119
|
+
};
|
|
120
|
+
group.qualifierTypes.forEach((qualifierType, index) => {
|
|
121
|
+
if (qualifierType === "variant") entry.variant = segments[index];
|
|
122
|
+
else if (qualifierType === "item") entry.item = Number(segments[index]);
|
|
123
|
+
});
|
|
124
|
+
if (group.qualifierTypes.includes("meta")) {
|
|
125
|
+
const metaIndex = group.qualifierTypes.indexOf("meta");
|
|
126
|
+
entry.meta = group.meta?.[compositeId] ?? { id: segments[metaIndex] };
|
|
127
|
+
}
|
|
128
|
+
return entry;
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* Resolves a dictionary (or qualified dictionary group) against a selector,
|
|
132
|
+
* across every declared dimension.
|
|
133
|
+
*
|
|
134
|
+
* - Plain dictionary → returned as-is (selector ignored)
|
|
135
|
+
* - `item` declared but not selected → every matching entry ordered by index
|
|
136
|
+
* - `item` selected → the matching entry or null
|
|
137
|
+
* - `variant` defaults to the `default` entry when not selected
|
|
138
|
+
* - `meta` requires `{ id }` and every declared meta field to match
|
|
139
|
+
*
|
|
140
|
+
* Dimensions compose: e.g. a variant × item key with `{ variant: 'promo' }`
|
|
141
|
+
* returns every promo item as an array; adding `{ item: 2 }` narrows to one.
|
|
142
|
+
*/
|
|
143
|
+
const resolveQualifiedDictionary = (dictionaryOrGroup, selector) => {
|
|
144
|
+
if (!isQualifiedDictionaryGroup(dictionaryOrGroup)) return dictionaryOrGroup;
|
|
145
|
+
const { qualifierTypes, content } = dictionaryOrGroup;
|
|
146
|
+
if (qualifierTypes.includes("meta") && selector?.id === void 0) return null;
|
|
147
|
+
const itemAxisOpen = qualifierTypes.includes("item") && selector?.item === void 0;
|
|
148
|
+
const matchedEntries = Object.keys(content).map((compositeId) => reconstructQualifiedEntry(dictionaryOrGroup, compositeId)).filter((entry) => entryMatchesSelector(entry, qualifierTypes, selector));
|
|
149
|
+
if (itemAxisOpen) return matchedEntries.sort((left, right) => (left.item ?? 0) - (right.item ?? 0));
|
|
150
|
+
return matchedEntries[0] ?? null;
|
|
151
|
+
};
|
|
152
|
+
/**
|
|
153
|
+
* Splits the second argument of `getIntlayer` / `getDictionary` into the
|
|
154
|
+
* effective locale and the selector object (if any).
|
|
155
|
+
*/
|
|
156
|
+
const parseDictionarySelector = (localeOrSelector) => {
|
|
157
|
+
if (typeof localeOrSelector === "object" && localeOrSelector !== null) return {
|
|
158
|
+
locale: localeOrSelector.locale,
|
|
159
|
+
selector: localeOrSelector
|
|
160
|
+
};
|
|
161
|
+
return { locale: localeOrSelector };
|
|
162
|
+
};
|
|
163
|
+
/**
|
|
164
|
+
* Builds a stable string identity of a selector (excluding `locale`), suitable
|
|
165
|
+
* for cache keys and memoization dependencies.
|
|
166
|
+
*/
|
|
167
|
+
const getDictionarySelectorCacheKey = (selector) => {
|
|
168
|
+
if (!selector) return "";
|
|
169
|
+
return Object.keys(selector).filter((selectorKey) => selectorKey !== "locale").sort().map((selectorKey) => `${selectorKey}:${String(selector[selectorKey])}`).join("|");
|
|
170
|
+
};
|
|
171
|
+
/**
|
|
172
|
+
* Marker property carrying the ordered qualifier dimensions on a dynamic loader
|
|
173
|
+
* map. Its presence distinguishes a qualified group loader map (a nested tree
|
|
174
|
+
* of chunks) from a plain dynamic loader map (one chunk per `locale`). Prefixed
|
|
175
|
+
* and unlikely to collide with a real locale code.
|
|
176
|
+
*/
|
|
177
|
+
const QUALIFIER_DYNAMIC_TYPES_KEY = "__intlayerQualifierTypes";
|
|
178
|
+
/**
|
|
179
|
+
* Type guard discriminating a qualified dynamic loader map (collections /
|
|
180
|
+
* variants / meta records, possibly combined) from a plain dynamic loader map.
|
|
181
|
+
*/
|
|
182
|
+
const isQualifiedDynamicLoaderMap = (value) => typeof value === "object" && value !== null && "__intlayerQualifierTypes" in value;
|
|
183
|
+
/**
|
|
184
|
+
* Walks the loader tree following the selector and collects the chunk loaders
|
|
185
|
+
* it targets — shared by the sync ({@link resolveQualifiedDynamicContent}) and
|
|
186
|
+
* async ({@link resolveQualifiedDynamicContentAsync}) resolvers.
|
|
187
|
+
*/
|
|
188
|
+
const collectQualifiedChunks = (loaderMap, key, locale, selector) => {
|
|
189
|
+
const qualifierTypes = loaderMap[QUALIFIER_DYNAMIC_TYPES_KEY];
|
|
190
|
+
const localeTree = loaderMap[locale];
|
|
191
|
+
const itemAxisOpen = qualifierTypes.includes("item") && selector?.item === void 0;
|
|
192
|
+
if (!localeTree) return {
|
|
193
|
+
itemAxisOpen,
|
|
194
|
+
missed: true,
|
|
195
|
+
chunks: []
|
|
196
|
+
};
|
|
197
|
+
if (qualifierTypes.includes("meta") && selector?.id === void 0) return {
|
|
198
|
+
itemAxisOpen,
|
|
199
|
+
missed: true,
|
|
200
|
+
chunks: []
|
|
201
|
+
};
|
|
202
|
+
const chunks = [];
|
|
203
|
+
const walk = (node, dimensions, segments) => {
|
|
204
|
+
if (dimensions.length === 0) {
|
|
205
|
+
chunks.push({
|
|
206
|
+
cacheKey: `${key}.${locale}.${segments.join("/")}`,
|
|
207
|
+
loader: node
|
|
208
|
+
});
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
const [dimension, ...rest] = dimensions;
|
|
212
|
+
const tree = node;
|
|
213
|
+
if (dimension === "item" && selector?.item === void 0) {
|
|
214
|
+
for (const segment of Object.keys(tree).sort((left, right) => Number(left) - Number(right))) walk(tree[segment], rest, [...segments, segment]);
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
const segment = dimension === "variant" ? selector?.variant ?? "default" : dimension === "meta" ? String(selector?.id) : String(selector?.item);
|
|
218
|
+
const child = tree[segment];
|
|
219
|
+
if (!child) return false;
|
|
220
|
+
return walk(child, rest, [...segments, segment]);
|
|
221
|
+
};
|
|
222
|
+
return {
|
|
223
|
+
itemAxisOpen,
|
|
224
|
+
missed: !walk(localeTree, qualifierTypes, []),
|
|
225
|
+
chunks
|
|
226
|
+
};
|
|
227
|
+
};
|
|
228
|
+
/**
|
|
229
|
+
* Whether a loaded chunk satisfies the selector's meta fields (no-op unless the
|
|
230
|
+
* key declares a `meta` dimension).
|
|
231
|
+
*/
|
|
232
|
+
const chunkMatchesMeta = (loaderMap, dictionary, selector) => !loaderMap["__intlayerQualifierTypes"].includes("meta") || metaFieldsMatch(dictionary.meta, selector);
|
|
233
|
+
/**
|
|
234
|
+
* Resolves the content of a qualified dynamic loader map against a selector,
|
|
235
|
+
* loading only the chunk(s) the selector actually targets.
|
|
236
|
+
*
|
|
237
|
+
* Walks the nested loader tree one dimension at a time (canonical order
|
|
238
|
+
* `variant → meta → item`): `variant` defaults to `default`, `meta` descends by
|
|
239
|
+
* `id`, and `item` either narrows to the selected index or — when no item is
|
|
240
|
+
* given — expands into every sibling chunk (the collection axis). Meta-equality
|
|
241
|
+
* is verified on the loaded chunk. Semantics mirror
|
|
242
|
+
* {@link resolveQualifiedDictionary} so dynamic and static modes behave alike.
|
|
243
|
+
*
|
|
244
|
+
* The Suspense mechanism is injected through `loadChunk` so the same logic
|
|
245
|
+
* serves both the client (suspender cache) and the server (`react.use`). Every
|
|
246
|
+
* targeted loader is started before the first chunk is read, so sibling chunks
|
|
247
|
+
* load in parallel rather than waterfalling.
|
|
248
|
+
*
|
|
249
|
+
* @param loaderMap - The qualified dynamic loader map (entry point default export).
|
|
250
|
+
* @param key - The dictionary key (used to build stable chunk cache keys).
|
|
251
|
+
* @param locale - The resolved locale to load chunks for.
|
|
252
|
+
* @param selector - The selector splitting the qualifier dimensions.
|
|
253
|
+
* @param loadChunk - Reads a started chunk promise, suspending until it resolves.
|
|
254
|
+
* @param transform - Turns a resolved chunk dictionary into final content.
|
|
255
|
+
*/
|
|
256
|
+
const resolveQualifiedDynamicContent = (params) => {
|
|
257
|
+
const { loaderMap, key, locale, selector, loadChunk, transform } = params;
|
|
258
|
+
const { itemAxisOpen, missed, chunks } = collectQualifiedChunks(loaderMap, key, locale, selector);
|
|
259
|
+
if (missed) return itemAxisOpen ? [] : null;
|
|
260
|
+
const dictionaries = chunks.map(({ cacheKey, loader }) => loadChunk(cacheKey, loader())).filter((dictionary) => chunkMatchesMeta(loaderMap, dictionary, selector));
|
|
261
|
+
if (itemAxisOpen) return dictionaries.map(transform);
|
|
262
|
+
const [dictionary] = dictionaries;
|
|
263
|
+
return dictionary ? transform(dictionary) : null;
|
|
264
|
+
};
|
|
265
|
+
/**
|
|
266
|
+
* Async counterpart of {@link resolveQualifiedDynamicContent} for frameworks
|
|
267
|
+
* that load dictionaries with `await` instead of Suspense (Vue, Svelte, Lit,
|
|
268
|
+
* vanilla). Awaits every targeted chunk in parallel, then resolves identically.
|
|
269
|
+
*
|
|
270
|
+
* @param loaderMap - The qualified dynamic loader map.
|
|
271
|
+
* @param key - The dictionary key (used to build stable chunk cache keys).
|
|
272
|
+
* @param locale - The resolved locale to load chunks for.
|
|
273
|
+
* @param selector - The selector splitting the qualifier dimensions.
|
|
274
|
+
* @param transform - Turns a resolved chunk dictionary into final content.
|
|
275
|
+
*/
|
|
276
|
+
const resolveQualifiedDynamicContentAsync = async (params) => {
|
|
277
|
+
const { loaderMap, key, locale, selector, transform } = params;
|
|
278
|
+
const { itemAxisOpen, missed, chunks } = collectQualifiedChunks(loaderMap, key, locale, selector);
|
|
279
|
+
if (missed) return itemAxisOpen ? [] : null;
|
|
280
|
+
const dictionaries = (await Promise.all(chunks.map(({ loader }) => loader()))).filter((dictionary) => chunkMatchesMeta(loaderMap, dictionary, selector));
|
|
281
|
+
if (itemAxisOpen) return dictionaries.map(transform);
|
|
282
|
+
const [dictionary] = dictionaries;
|
|
283
|
+
return dictionary ? transform(dictionary) : null;
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
//#endregion
|
|
287
|
+
exports.COMPOSITE_ID_SEPARATOR = COMPOSITE_ID_SEPARATOR;
|
|
288
|
+
exports.QUALIFIER_DYNAMIC_TYPES_KEY = QUALIFIER_DYNAMIC_TYPES_KEY;
|
|
289
|
+
exports.QUALIFIER_ORDER = QUALIFIER_ORDER;
|
|
290
|
+
exports.getDictionaryCompositeId = getDictionaryCompositeId;
|
|
291
|
+
exports.getDictionaryQualifierId = getDictionaryQualifierId;
|
|
292
|
+
exports.getDictionaryQualifierSegments = getDictionaryQualifierSegments;
|
|
293
|
+
exports.getDictionaryQualifierTypes = getDictionaryQualifierTypes;
|
|
294
|
+
exports.getDictionarySelectorCacheKey = getDictionarySelectorCacheKey;
|
|
295
|
+
exports.isQualifiedDictionaryGroup = isQualifiedDictionaryGroup;
|
|
296
|
+
exports.isQualifiedDynamicLoaderMap = isQualifiedDynamicLoaderMap;
|
|
297
|
+
exports.parseDictionarySelector = parseDictionarySelector;
|
|
298
|
+
exports.reconstructQualifiedEntry = reconstructQualifiedEntry;
|
|
299
|
+
exports.resolveQualifiedDictionary = resolveQualifiedDictionary;
|
|
300
|
+
exports.resolveQualifiedDynamicContent = resolveQualifiedDynamicContent;
|
|
301
|
+
exports.resolveQualifiedDynamicContentAsync = resolveQualifiedDynamicContentAsync;
|
|
302
|
+
//# sourceMappingURL=qualifiedDictionary.cjs.map
|