@intlayer/core 9.1.2 → 9.1.3
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/dictionaryManipulator/qualifiedDictionary.test-d.cjs +7 -2
- package/dist/cjs/dictionaryManipulator/qualifiedDictionary.test-d.cjs.map +1 -1
- package/dist/cjs/interpreter/getDictionary.cjs.map +1 -1
- package/dist/cjs/interpreter/getIntlayer.cjs.map +1 -1
- package/dist/cjs/interpreter/getIntlayer.test-d.cjs +28 -0
- package/dist/cjs/interpreter/getIntlayer.test-d.cjs.map +1 -0
- package/dist/esm/dictionaryManipulator/qualifiedDictionary.test-d.mjs +7 -2
- package/dist/esm/dictionaryManipulator/qualifiedDictionary.test-d.mjs.map +1 -1
- package/dist/esm/interpreter/getDictionary.mjs.map +1 -1
- package/dist/esm/interpreter/getIntlayer.mjs.map +1 -1
- package/dist/esm/interpreter/getIntlayer.test-d.mjs +28 -0
- package/dist/esm/interpreter/getIntlayer.test-d.mjs.map +1 -0
- package/dist/types/interpreter/getDictionary.d.ts +2 -2
- package/dist/types/interpreter/getDictionary.d.ts.map +1 -1
- package/dist/types/interpreter/getIntlayer.d.ts +2 -2
- package/dist/types/interpreter/getIntlayer.d.ts.map +1 -1
- package/dist/types/interpreter/getIntlayer.test-d.d.ts +1 -0
- package/package.json +6 -6
|
@@ -73,8 +73,13 @@ let vitest = require("vitest");
|
|
|
73
73
|
(0, vitest.expectTypeOf)().not.toExtend();
|
|
74
74
|
});
|
|
75
75
|
});
|
|
76
|
-
(0, vitest.
|
|
77
|
-
(0, vitest.
|
|
76
|
+
(0, vitest.describe)("locale", () => {
|
|
77
|
+
(0, vitest.it)("should accept a declared locale", () => {
|
|
78
|
+
(0, vitest.expectTypeOf)().toExtend();
|
|
79
|
+
});
|
|
80
|
+
(0, vitest.it)("should accept a widened `string` locale", () => {
|
|
81
|
+
(0, vitest.expectTypeOf)().toExtend();
|
|
82
|
+
});
|
|
78
83
|
});
|
|
79
84
|
});
|
|
80
85
|
(0, vitest.describe)("DictionarySelectorForGroup — keys without a default entry", () => {
|
|
@@ -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 it('should reject an undeclared locale', () => {\n expectTypeOf<{ locale: 'not-a-locale' }>().not.toExtend<LessonSelector>();\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,eAAG,4CAA4C;EAC7C,yBAAyC,CAAC,CAAC,IAAI,SAAyB;CAC1E,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":[],"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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getDictionary.cjs","names":["parseDictionarySelector","getBasePlugins","resolveQualifiedDictionary","getContent"],"sources":["../../../src/interpreter/getDictionary.ts"],"sourcesContent":["import type {\n Dictionary,\n DictionarySelector,\n QualifiedDictionaryGroup,\n ResolveQualifiedDictionaryContent,\n} from '@intlayer/types/dictionary';\nimport type {\n DeclaredLocales,\n ExtractSelectorLocale,\n} from '@intlayer/types/module_augmentation';\nimport {\n parseDictionarySelector,\n resolveQualifiedDictionary,\n} from '../dictionaryManipulator/qualifiedDictionary';\nimport type {\n DeepTransformContent,\n IInterpreterPluginState,\n NodeProps,\n Plugins,\n} from './getContent';\nimport { getBasePlugins, getContent } from './getContent/getContent';\n\n/**\n * Transforms a dictionary in a single pass, applying each plugin as needed.\n *\n * Also accepts a `QualifiedDictionaryGroup` (collections, variants) together\n * with a selector as second argument — the group is resolved to a single entry\n * (or an ordered array of entries for collections without an `item` selector)\n * before transformation.\n *\n * @param dictionary The dictionary (or qualified dictionary group) to transform.\n * @param localeOrSelector The locale, or a selector object (`{ item }`,\n * `{ variant }`, optionally with `locale`).\n * @param plugins An array of NodeTransformer that define how to transform recognized nodes.\n * If omitted, we’ll use a default set of plugins.\n */\nexport const getDictionary = <\n const T extends Dictionary | QualifiedDictionaryGroup,\n const A extends
|
|
1
|
+
{"version":3,"file":"getDictionary.cjs","names":["parseDictionarySelector","getBasePlugins","resolveQualifiedDictionary","getContent"],"sources":["../../../src/interpreter/getDictionary.ts"],"sourcesContent":["import type {\n Dictionary,\n DictionarySelector,\n QualifiedDictionaryGroup,\n ResolveQualifiedDictionaryContent,\n} from '@intlayer/types/dictionary';\nimport type {\n DeclaredLocales,\n ExtractSelectorLocale,\n LocalesValues,\n} from '@intlayer/types/module_augmentation';\nimport {\n parseDictionarySelector,\n resolveQualifiedDictionary,\n} from '../dictionaryManipulator/qualifiedDictionary';\nimport type {\n DeepTransformContent,\n IInterpreterPluginState,\n NodeProps,\n Plugins,\n} from './getContent';\nimport { getBasePlugins, getContent } from './getContent/getContent';\n\n/**\n * Transforms a dictionary in a single pass, applying each plugin as needed.\n *\n * Also accepts a `QualifiedDictionaryGroup` (collections, variants) together\n * with a selector as second argument — the group is resolved to a single entry\n * (or an ordered array of entries for collections without an `item` selector)\n * before transformation.\n *\n * @param dictionary The dictionary (or qualified dictionary group) to transform.\n * @param localeOrSelector The locale, or a selector object (`{ item }`,\n * `{ variant }`, optionally with `locale`).\n * @param plugins An array of NodeTransformer that define how to transform recognized nodes.\n * If omitted, we’ll use a default set of plugins.\n */\nexport const getDictionary = <\n const T extends Dictionary | QualifiedDictionaryGroup,\n const A extends LocalesValues | DictionarySelector = DeclaredLocales,\n>(\n dictionary: T,\n localeOrSelector?: A,\n plugins?: Plugins[]\n): DeepTransformContent<\n ResolveQualifiedDictionaryContent<T, A>,\n IInterpreterPluginState,\n ExtractSelectorLocale<A>\n> => {\n const { locale, selector } = parseDictionarySelector(localeOrSelector);\n const appliedPlugins = plugins ?? getBasePlugins(locale);\n\n const resolved = resolveQualifiedDictionary(dictionary, selector);\n\n const transformDictionary = (resolvedDictionary: Dictionary) => {\n const props: NodeProps = {\n dictionaryKey: resolvedDictionary.key,\n dictionaryPath: resolvedDictionary.filePath,\n keyPath: [],\n plugins: appliedPlugins,\n };\n\n return getContent(resolvedDictionary.content, props, appliedPlugins);\n };\n\n if (resolved === null) return null as any;\n\n if (Array.isArray(resolved)) {\n return resolved.map(transformDictionary) as any;\n }\n\n return transformDictionary(resolved) as any;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAqCA,MAAa,iBAIX,YACA,kBACA,YAKG;CACH,MAAM,EAAE,QAAQ,aAAaA,0EAAwB,gBAAgB;CACrE,MAAM,iBAAiB,WAAWC,yDAAe,MAAM;CAEvD,MAAM,WAAWC,6EAA2B,YAAY,QAAQ;CAEhE,MAAM,uBAAuB,uBAAmC;EAC9D,MAAM,QAAmB;GACvB,eAAe,mBAAmB;GAClC,gBAAgB,mBAAmB;GACnC,SAAS,CAAC;GACV,SAAS;EACX;EAEA,OAAOC,qDAAW,mBAAmB,SAAS,OAAO,cAAc;CACrE;CAEA,IAAI,aAAa,MAAM,OAAO;CAE9B,IAAI,MAAM,QAAQ,QAAQ,GACxB,OAAO,SAAS,IAAI,mBAAmB;CAGzC,OAAO,oBAAoB,QAAQ;AACrC"}
|
|
@@ -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
|
|
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,+DAAwB,CAAC,CAAC;CAEhC,IAAI,CAAC,cAAc,QAAQ,IAAI,aAAa,eAAe;EACzD,IAAI,CAAC,0BAA0B,IAAI,GAAa,GAAG;GAGjD,0CAD4B,EAAE,gCAAI,CAC7B,CAAC,CACJ,OAAO,WAAW,cACd,uDAA0B,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,SAASA,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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const require_interpreter_getIntlayer = require('./getIntlayer.cjs');
|
|
2
|
+
let vitest = require("vitest");
|
|
3
|
+
|
|
4
|
+
//#region src/interpreter/getIntlayer.test-d.ts
|
|
5
|
+
/**
|
|
6
|
+
* The second argument of `getIntlayer` is almost always a value the framework
|
|
7
|
+
* hands over as a plain `string`: a router param (`params.locale`), a cookie, a
|
|
8
|
+
* header. Constraining it to the declared locales made every such call site a
|
|
9
|
+
* compile error, so the declared locales are offered as suggestions while any
|
|
10
|
+
* string is still accepted.
|
|
11
|
+
*/
|
|
12
|
+
(0, vitest.describe)("getIntlayer — locale argument", () => {
|
|
13
|
+
(0, vitest.it)("should accept a locale literal", () => {
|
|
14
|
+
(0, vitest.expectTypeOf)(require_interpreter_getIntlayer.getIntlayer("lesson", "fr")).not.toBeNever();
|
|
15
|
+
});
|
|
16
|
+
(0, vitest.it)("should accept a `string | undefined` router param", () => {
|
|
17
|
+
(0, vitest.expectTypeOf)(require_interpreter_getIntlayer.getIntlayer("lesson", "fr")).not.toBeNever();
|
|
18
|
+
});
|
|
19
|
+
(0, vitest.it)("should accept a selector carrying a widened locale", () => {
|
|
20
|
+
(0, vitest.expectTypeOf)(require_interpreter_getIntlayer.getIntlayer("lesson", {
|
|
21
|
+
locale: "fr",
|
|
22
|
+
item: 1
|
|
23
|
+
})).not.toBeNever();
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
//#endregion
|
|
28
|
+
//# sourceMappingURL=getIntlayer.test-d.cjs.map
|
|
@@ -0,0 +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":";;;;;;;;;;;qBAUS,uCAAuC;CAC9C,eAAG,wCAAwC;EACzC,yBAAaA,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"}
|
|
@@ -73,8 +73,13 @@ describe("DictionarySelectorForGroup", () => {
|
|
|
73
73
|
expectTypeOf().not.toExtend();
|
|
74
74
|
});
|
|
75
75
|
});
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
describe("locale", () => {
|
|
77
|
+
it("should accept a declared locale", () => {
|
|
78
|
+
expectTypeOf().toExtend();
|
|
79
|
+
});
|
|
80
|
+
it("should accept a widened `string` locale", () => {
|
|
81
|
+
expectTypeOf().toExtend();
|
|
82
|
+
});
|
|
78
83
|
});
|
|
79
84
|
});
|
|
80
85
|
describe("DictionarySelectorForGroup — keys without a default entry", () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"qualifiedDictionary.test-d.mjs","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 it('should reject an undeclared locale', () => {\n expectTypeOf<{ locale: 'not-a-locale' }>().not.toExtend<LessonSelector>();\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":";;;AAqDA,SAAS,2CAA2C;CAClD,GAAG,iFAAiF;EAClF,aAEE,CAAC,CAAC,cAA0B;CAChC,CAAC;CAED,SAAS,iBAAiB;EACxB,GAAG,yEAAyE;GAC1E,aAEE,CAAC,CAAC,cAAuD;EAC7D,CAAC;EAED,GAAG,gEAAgE;GACjE,aAEE,CAAC,CAAC,cAAuD;EAC7D,CAAC;EAED,GAAG,4DAA4D;GAC7D,aAEE,CAAC,CAAC,cAAyD;EAC/D,CAAC;EAED,GAAG,yEAAyE;GAC1E,aAKE,CAAC,CAAC,cAAuD;EAC7D,CAAC;EAED,GAAG,kEAAkE;GACnE,aAKE,CAAC,CAAC,cAAoB;EAC1B,CAAC;EAED,GAAG,uDAAuD;GACxD,aAKE,CAAC,CAAC,cAA+B;EACrC,CAAC;EAED,GAAG,0EAA0E;GAC3E,aAEE,CAAC,CAAC,cAAoB;EAC1B,CAAC;CACH,CAAC;CAED,SAAS,cAAc;EACrB,GAAG,6EAA6E;GAC9E,aAA0D,CAAC,CAAC,cAE1D;EACJ,CAAC;EAED,GAAG,4CAA4C;GAC7C,aAEE,CAAC,CAAC,cAAkC;EACxC,CAAC;CACH,CAAC;CAED,SAAS,oCAAoC;EAC3C,GAAG,yEAAyE;GAC1E,aAKE,CAAC,CAAC,cAA+B;EACrC,CAAC;EAED,GAAG,+DAA+D;GAChE,aAEE,CAAC,CAAC,cAAqD;EAC3D,CAAC;EAED,GAAG,wEAAwE;GACzE,aAEE,CAAC,CAAC,cAAiC;EACvC,CAAC;CACH,CAAC;AACH,CAAC;AAED,SAAS,oCAAoC;CAS3C,GAAG,mDAAmD;EACpD,aAAuC,CAAC,CAAC,SAAyB;CACpE,CAAC;CAED,GAAG,mEAAmE;EAGpE,aAAmC,CAAC,CAAC,SAAyB;CAChE,CAAC;CAED,GAAG,wDAAwD;EACzD,aAAoC,CAAC,CAAC,IAAI,SAAyB;CACrE,CAAC;CAED,GAAG,qEAAqE;EACtE,aAAyC,CAAC,CAAC,IAAI,SAAyB;CAC1E,CAAC;CAED,SAAS,yBAAyB;EAQhC,GAAG,mDAAmD;GACpD,aAEG,CAAC,CAAC,SAA0B;EACjC,CAAC;EAED,GAAG,uDAAuD;GAExD,aAEG,CAAC,CAAC,IAAI,SAA0B;EACrC,CAAC;EAED,GAAG,sDAAsD;GACvD,aAEG,CAAC,CAAC,IAAI,SAA0B;GACnC,aAEG,CAAC,CAAC,IAAI,SAA0B;EACrC,CAAC;CACH,CAAC;CAED,GAAG,4CAA4C;EAC7C,aAAyC,CAAC,CAAC,IAAI,SAAyB;CAC1E,CAAC;AACH,CAAC;AAED,SAAS,mEAAmE;CAgB1E,GAAG,wEAAwE;EACzE,aAAmC,CAAC,CAAC,IAAI,SAA0B;EACnE,aAAqC,CAAC,CAAC,IAAI,SAA0B;EACrE,aAAuC,CAAC,CAAC,IAAI,SAA4B;CAC3E,CAAC;CAED,GAAG,kEAAkE;EACnE,aAAmC,CAAC,CAAC,SAA4B;CACnE,CAAC;AACH,CAAC"}
|
|
1
|
+
{"version":3,"file":"qualifiedDictionary.test-d.mjs","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":";;;AAqDA,SAAS,2CAA2C;CAClD,GAAG,iFAAiF;EAClF,aAEE,CAAC,CAAC,cAA0B;CAChC,CAAC;CAED,SAAS,iBAAiB;EACxB,GAAG,yEAAyE;GAC1E,aAEE,CAAC,CAAC,cAAuD;EAC7D,CAAC;EAED,GAAG,gEAAgE;GACjE,aAEE,CAAC,CAAC,cAAuD;EAC7D,CAAC;EAED,GAAG,4DAA4D;GAC7D,aAEE,CAAC,CAAC,cAAyD;EAC/D,CAAC;EAED,GAAG,yEAAyE;GAC1E,aAKE,CAAC,CAAC,cAAuD;EAC7D,CAAC;EAED,GAAG,kEAAkE;GACnE,aAKE,CAAC,CAAC,cAAoB;EAC1B,CAAC;EAED,GAAG,uDAAuD;GACxD,aAKE,CAAC,CAAC,cAA+B;EACrC,CAAC;EAED,GAAG,0EAA0E;GAC3E,aAEE,CAAC,CAAC,cAAoB;EAC1B,CAAC;CACH,CAAC;CAED,SAAS,cAAc;EACrB,GAAG,6EAA6E;GAC9E,aAA0D,CAAC,CAAC,cAE1D;EACJ,CAAC;EAED,GAAG,4CAA4C;GAC7C,aAEE,CAAC,CAAC,cAAkC;EACxC,CAAC;CACH,CAAC;CAED,SAAS,oCAAoC;EAC3C,GAAG,yEAAyE;GAC1E,aAKE,CAAC,CAAC,cAA+B;EACrC,CAAC;EAED,GAAG,+DAA+D;GAChE,aAEE,CAAC,CAAC,cAAqD;EAC3D,CAAC;EAED,GAAG,wEAAwE;GACzE,aAEE,CAAC,CAAC,cAAiC;EACvC,CAAC;CACH,CAAC;AACH,CAAC;AAED,SAAS,oCAAoC;CAS3C,GAAG,mDAAmD;EACpD,aAAuC,CAAC,CAAC,SAAyB;CACpE,CAAC;CAED,GAAG,mEAAmE;EAGpE,aAAmC,CAAC,CAAC,SAAyB;CAChE,CAAC;CAED,GAAG,wDAAwD;EACzD,aAAoC,CAAC,CAAC,IAAI,SAAyB;CACrE,CAAC;CAED,GAAG,qEAAqE;EACtE,aAAyC,CAAC,CAAC,IAAI,SAAyB;CAC1E,CAAC;CAED,SAAS,yBAAyB;EAQhC,GAAG,mDAAmD;GACpD,aAEG,CAAC,CAAC,SAA0B;EACjC,CAAC;EAED,GAAG,uDAAuD;GAExD,aAEG,CAAC,CAAC,IAAI,SAA0B;EACrC,CAAC;EAED,GAAG,sDAAsD;GACvD,aAEG,CAAC,CAAC,IAAI,SAA0B;GACnC,aAEG,CAAC,CAAC,IAAI,SAA0B;EACrC,CAAC;CACH,CAAC;CAED,SAAS,gBAAgB;EACvB,GAAG,yCAAyC;GAC1C,aAA+B,CAAC,CAAC,SAAyB;EAC5D,CAAC;EAED,GAAG,iDAAiD;GAIlD,aAAiC,CAAC,CAAC,SAAyB;EAC9D,CAAC;CACH,CAAC;AACH,CAAC;AAED,SAAS,mEAAmE;CAgB1E,GAAG,wEAAwE;EACzE,aAAmC,CAAC,CAAC,IAAI,SAA0B;EACnE,aAAqC,CAAC,CAAC,IAAI,SAA0B;EACrE,aAAuC,CAAC,CAAC,IAAI,SAA4B;CAC3E,CAAC;CAED,GAAG,kEAAkE;EACnE,aAAmC,CAAC,CAAC,SAA4B;CACnE,CAAC;AACH,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getDictionary.mjs","names":[],"sources":["../../../src/interpreter/getDictionary.ts"],"sourcesContent":["import type {\n Dictionary,\n DictionarySelector,\n QualifiedDictionaryGroup,\n ResolveQualifiedDictionaryContent,\n} from '@intlayer/types/dictionary';\nimport type {\n DeclaredLocales,\n ExtractSelectorLocale,\n} from '@intlayer/types/module_augmentation';\nimport {\n parseDictionarySelector,\n resolveQualifiedDictionary,\n} from '../dictionaryManipulator/qualifiedDictionary';\nimport type {\n DeepTransformContent,\n IInterpreterPluginState,\n NodeProps,\n Plugins,\n} from './getContent';\nimport { getBasePlugins, getContent } from './getContent/getContent';\n\n/**\n * Transforms a dictionary in a single pass, applying each plugin as needed.\n *\n * Also accepts a `QualifiedDictionaryGroup` (collections, variants) together\n * with a selector as second argument — the group is resolved to a single entry\n * (or an ordered array of entries for collections without an `item` selector)\n * before transformation.\n *\n * @param dictionary The dictionary (or qualified dictionary group) to transform.\n * @param localeOrSelector The locale, or a selector object (`{ item }`,\n * `{ variant }`, optionally with `locale`).\n * @param plugins An array of NodeTransformer that define how to transform recognized nodes.\n * If omitted, we’ll use a default set of plugins.\n */\nexport const getDictionary = <\n const T extends Dictionary | QualifiedDictionaryGroup,\n const A extends
|
|
1
|
+
{"version":3,"file":"getDictionary.mjs","names":[],"sources":["../../../src/interpreter/getDictionary.ts"],"sourcesContent":["import type {\n Dictionary,\n DictionarySelector,\n QualifiedDictionaryGroup,\n ResolveQualifiedDictionaryContent,\n} from '@intlayer/types/dictionary';\nimport type {\n DeclaredLocales,\n ExtractSelectorLocale,\n LocalesValues,\n} from '@intlayer/types/module_augmentation';\nimport {\n parseDictionarySelector,\n resolveQualifiedDictionary,\n} from '../dictionaryManipulator/qualifiedDictionary';\nimport type {\n DeepTransformContent,\n IInterpreterPluginState,\n NodeProps,\n Plugins,\n} from './getContent';\nimport { getBasePlugins, getContent } from './getContent/getContent';\n\n/**\n * Transforms a dictionary in a single pass, applying each plugin as needed.\n *\n * Also accepts a `QualifiedDictionaryGroup` (collections, variants) together\n * with a selector as second argument — the group is resolved to a single entry\n * (or an ordered array of entries for collections without an `item` selector)\n * before transformation.\n *\n * @param dictionary The dictionary (or qualified dictionary group) to transform.\n * @param localeOrSelector The locale, or a selector object (`{ item }`,\n * `{ variant }`, optionally with `locale`).\n * @param plugins An array of NodeTransformer that define how to transform recognized nodes.\n * If omitted, we’ll use a default set of plugins.\n */\nexport const getDictionary = <\n const T extends Dictionary | QualifiedDictionaryGroup,\n const A extends LocalesValues | DictionarySelector = DeclaredLocales,\n>(\n dictionary: T,\n localeOrSelector?: A,\n plugins?: Plugins[]\n): DeepTransformContent<\n ResolveQualifiedDictionaryContent<T, A>,\n IInterpreterPluginState,\n ExtractSelectorLocale<A>\n> => {\n const { locale, selector } = parseDictionarySelector(localeOrSelector);\n const appliedPlugins = plugins ?? getBasePlugins(locale);\n\n const resolved = resolveQualifiedDictionary(dictionary, selector);\n\n const transformDictionary = (resolvedDictionary: Dictionary) => {\n const props: NodeProps = {\n dictionaryKey: resolvedDictionary.key,\n dictionaryPath: resolvedDictionary.filePath,\n keyPath: [],\n plugins: appliedPlugins,\n };\n\n return getContent(resolvedDictionary.content, props, appliedPlugins);\n };\n\n if (resolved === null) return null as any;\n\n if (Array.isArray(resolved)) {\n return resolved.map(transformDictionary) as any;\n }\n\n return transformDictionary(resolved) as any;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAqCA,MAAa,iBAIX,YACA,kBACA,YAKG;CACH,MAAM,EAAE,QAAQ,aAAa,wBAAwB,gBAAgB;CACrE,MAAM,iBAAiB,WAAW,eAAe,MAAM;CAEvD,MAAM,WAAW,2BAA2B,YAAY,QAAQ;CAEhE,MAAM,uBAAuB,uBAAmC;EAC9D,MAAM,QAAmB;GACvB,eAAe,mBAAmB;GAClC,gBAAgB,mBAAmB;GACnC,SAAS,CAAC;GACV,SAAS;EACX;EAEA,OAAO,WAAW,mBAAmB,SAAS,OAAO,cAAc;CACrE;CAEA,IAAI,aAAa,MAAM,OAAO;CAE9B,IAAI,MAAM,QAAQ,QAAQ,GACxB,OAAO,SAAS,IAAI,mBAAmB;CAGzC,OAAO,oBAAoB,QAAQ;AACrC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getIntlayer.mjs","names":[],"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
|
|
1
|
+
{"version":3,"file":"getIntlayer.mjs","names":[],"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,aADe,gBACS,CAAC,CAAC;CAEhC,IAAI,CAAC,cAAc,MAAwC;EACzD,IAAI,CAAC,0BAA0B,IAAI,GAAa,GAAG;GAGjD,AADe,aAAa,EAAE,IAAI,CAC7B,CAAC,CACJ,OAAO,WAAW,cACd,cAAc,YAAY,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,SAAS,wBAAwB,gBAAgB;EACvD,SAAS,OAAO;EAChB,mBAAmB,8BAA8B,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,SAAS,cAAc,YAAY,kBAAkB,OAAO;CAElE,gBAAgB,IAAI,UAAU,MAAM;CAEpC,OAAO;AACT"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { getIntlayer } from "./getIntlayer.mjs";
|
|
2
|
+
import { describe, expectTypeOf, it } from "vitest";
|
|
3
|
+
|
|
4
|
+
//#region src/interpreter/getIntlayer.test-d.ts
|
|
5
|
+
/**
|
|
6
|
+
* The second argument of `getIntlayer` is almost always a value the framework
|
|
7
|
+
* hands over as a plain `string`: a router param (`params.locale`), a cookie, a
|
|
8
|
+
* header. Constraining it to the declared locales made every such call site a
|
|
9
|
+
* compile error, so the declared locales are offered as suggestions while any
|
|
10
|
+
* string is still accepted.
|
|
11
|
+
*/
|
|
12
|
+
describe("getIntlayer — locale argument", () => {
|
|
13
|
+
it("should accept a locale literal", () => {
|
|
14
|
+
expectTypeOf(getIntlayer("lesson", "fr")).not.toBeNever();
|
|
15
|
+
});
|
|
16
|
+
it("should accept a `string | undefined` router param", () => {
|
|
17
|
+
expectTypeOf(getIntlayer("lesson", "fr")).not.toBeNever();
|
|
18
|
+
});
|
|
19
|
+
it("should accept a selector carrying a widened locale", () => {
|
|
20
|
+
expectTypeOf(getIntlayer("lesson", {
|
|
21
|
+
locale: "fr",
|
|
22
|
+
item: 1
|
|
23
|
+
})).not.toBeNever();
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
//#endregion
|
|
28
|
+
//# sourceMappingURL=getIntlayer.test-d.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getIntlayer.test-d.mjs","names":[],"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":";;;;;;;;;;;AAUA,SAAS,uCAAuC;CAC9C,GAAG,wCAAwC;EACzC,aAAa,YAAY,UAAU,IAAI,CAAC,CAAC,CAAC,IAAI,UAAU;CAC1D,CAAC;CAED,GAAG,2DAA2D;EAG5D,aAAa,YAAY,UAAU,IAAY,CAAC,CAAC,CAAC,IAAI,UAAU;CAClE,CAAC;CAED,GAAG,4DAA4D;EAG7D,aACE,YAAY,UAAU;GAAE,QAAQ;GAAc,MAAM;EAAE,CAAC,CACzD,CAAC,CAAC,IAAI,UAAU;CAClB,CAAC;AACH,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { DeepTransformContent, IInterpreterPluginState, Plugins } from "./getContent/plugins.js";
|
|
2
2
|
import "./getContent/index.js";
|
|
3
3
|
import { Dictionary, DictionarySelector, QualifiedDictionaryGroup, ResolveQualifiedDictionaryContent } from "@intlayer/types/dictionary";
|
|
4
|
-
import { DeclaredLocales, ExtractSelectorLocale } from "@intlayer/types/module_augmentation";
|
|
4
|
+
import { DeclaredLocales, ExtractSelectorLocale, LocalesValues } from "@intlayer/types/module_augmentation";
|
|
5
5
|
//#region src/interpreter/getDictionary.d.ts
|
|
6
6
|
/**
|
|
7
7
|
* Transforms a dictionary in a single pass, applying each plugin as needed.
|
|
@@ -17,7 +17,7 @@ import { DeclaredLocales, ExtractSelectorLocale } from "@intlayer/types/module_a
|
|
|
17
17
|
* @param plugins An array of NodeTransformer that define how to transform recognized nodes.
|
|
18
18
|
* If omitted, we’ll use a default set of plugins.
|
|
19
19
|
*/
|
|
20
|
-
declare const getDictionary: <const T extends Dictionary | QualifiedDictionaryGroup, const A extends
|
|
20
|
+
declare const getDictionary: <const T extends Dictionary | QualifiedDictionaryGroup, const A extends LocalesValues | DictionarySelector = DeclaredLocales>(dictionary: T, localeOrSelector?: A, plugins?: Plugins[]) => DeepTransformContent<ResolveQualifiedDictionaryContent<T, A>, IInterpreterPluginState, ExtractSelectorLocale<A>>;
|
|
21
21
|
//#endregion
|
|
22
22
|
export { getDictionary };
|
|
23
23
|
//# sourceMappingURL=getDictionary.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getDictionary.d.ts","names":[],"sources":["../../../src/interpreter/getDictionary.ts"],"mappings":";;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"getDictionary.d.ts","names":[],"sources":["../../../src/interpreter/getDictionary.ts"],"mappings":";;;;;;;;;;;;;;;;;;;cAqCa,sBACL,UAAU,aAAa,gCACvB,UAAU,gBAAgB,qBAAqB,iBAAe,YAExD,GAAC,mBACM,GAAC,UACV,cACT,qBACD,kCAAkC,GAAG,IACrC,yBACA,sBAAsB"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { DeepTransformContent, IInterpreterPluginState, Plugins } from "./getContent/plugins.js";
|
|
2
2
|
import "./getContent/index.js";
|
|
3
3
|
import { DictionarySelector } from "@intlayer/types/dictionary";
|
|
4
|
-
import { DeclaredLocales, DictionaryKeys, DictionaryRegistryResult, ExtractSelectorLocale } from "@intlayer/types/module_augmentation";
|
|
4
|
+
import { DeclaredLocales, DictionaryKeys, DictionaryRegistryResult, ExtractSelectorLocale, LocalesValues } from "@intlayer/types/module_augmentation";
|
|
5
5
|
//#region src/interpreter/getIntlayer.d.ts
|
|
6
6
|
/**
|
|
7
7
|
* Picks one dictionary by its key and returns its content for the given
|
|
@@ -13,7 +13,7 @@ import { DeclaredLocales, DictionaryKeys, DictionaryRegistryResult, ExtractSelec
|
|
|
13
13
|
* - `{ variant: { id: 'prod_abc', userId: '123' } }` — structured variant
|
|
14
14
|
* - `locale` can be combined with any selector: `{ item: 2, locale: 'fr' }`
|
|
15
15
|
*/
|
|
16
|
-
declare const getIntlayer: <const T extends DictionaryKeys, const A extends
|
|
16
|
+
declare const getIntlayer: <const T extends DictionaryKeys, const A extends LocalesValues | DictionarySelector = DeclaredLocales>(key: T, localeOrSelector?: A, plugins?: Plugins[]) => DeepTransformContent<DictionaryRegistryResult<T, A>, IInterpreterPluginState, ExtractSelectorLocale<A>>;
|
|
17
17
|
//#endregion
|
|
18
18
|
export { getIntlayer };
|
|
19
19
|
//# sourceMappingURL=getIntlayer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getIntlayer.d.ts","names":[],"sources":["../../../src/interpreter/getIntlayer.ts"],"mappings":";;;;;;;;;;;;;;;cAkEa,oBACL,UAAU,sBACV,UAAU,
|
|
1
|
+
{"version":3,"file":"getIntlayer.d.ts","names":[],"sources":["../../../src/interpreter/getIntlayer.ts"],"mappings":";;;;;;;;;;;;;;;cAkEa,oBACL,UAAU,sBACV,UAAU,gBAAgB,qBAAqB,iBAAe,KAE/D,GAAC,mBACa,GAAC,UACV,cACT,qBACD,yBAAyB,GAAG,IAC5B,yBACA,sBAAsB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intlayer/core",
|
|
3
|
-
"version": "9.1.
|
|
3
|
+
"version": "9.1.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Includes core Intlayer functions like translation, dictionary, and utility functions shared across multiple packages.",
|
|
6
6
|
"keywords": [
|
|
@@ -172,11 +172,11 @@
|
|
|
172
172
|
"typecheck": "tsc --noEmit --project tsconfig.types.json"
|
|
173
173
|
},
|
|
174
174
|
"dependencies": {
|
|
175
|
-
"@intlayer/api": "9.1.
|
|
176
|
-
"@intlayer/config": "9.1.
|
|
177
|
-
"@intlayer/dictionaries-entry": "9.1.
|
|
178
|
-
"@intlayer/types": "9.1.
|
|
179
|
-
"@intlayer/unmerged-dictionaries-entry": "9.1.
|
|
175
|
+
"@intlayer/api": "9.1.3",
|
|
176
|
+
"@intlayer/config": "9.1.3",
|
|
177
|
+
"@intlayer/dictionaries-entry": "9.1.3",
|
|
178
|
+
"@intlayer/types": "9.1.3",
|
|
179
|
+
"@intlayer/unmerged-dictionaries-entry": "9.1.3",
|
|
180
180
|
"defu": "6.1.7"
|
|
181
181
|
},
|
|
182
182
|
"devDependencies": {
|