@intlayer/chokidar 9.0.0-canary.7 → 9.0.0-canary.9
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/buildIntlayerDictionary/buildIntlayerDictionary.cjs.map +1 -1
- package/dist/cjs/buildIntlayerDictionary/writeDynamicDictionary.cjs +3 -3
- package/dist/cjs/buildIntlayerDictionary/writeDynamicDictionary.cjs.map +1 -1
- package/dist/cjs/buildIntlayerDictionary/writeMergedDictionary.cjs.map +1 -1
- package/dist/cjs/init/frameworkSetup/nextAppRouter/transforms.cjs +6 -21
- package/dist/cjs/init/frameworkSetup/nextAppRouter/transforms.cjs.map +1 -1
- package/dist/cjs/init/frameworkSetup/tanstackStart/transforms.cjs +33 -20
- package/dist/cjs/init/frameworkSetup/tanstackStart/transforms.cjs.map +1 -1
- package/dist/cjs/init/index.cjs +15 -5
- package/dist/cjs/init/index.cjs.map +1 -1
- package/dist/cjs/init/utils/astImports.cjs +56 -0
- package/dist/cjs/init/utils/astImports.cjs.map +1 -0
- package/dist/cjs/init/utils/configManipulation.cjs +28 -14
- package/dist/cjs/init/utils/configManipulation.cjs.map +1 -1
- package/dist/cjs/init/utils/fileSystem.cjs +50 -0
- package/dist/cjs/init/utils/fileSystem.cjs.map +1 -1
- package/dist/cjs/init/utils/index.cjs +5 -0
- package/dist/cjs/init/utils/packageManager.cjs +17 -8
- package/dist/cjs/init/utils/packageManager.cjs.map +1 -1
- package/dist/esm/buildIntlayerDictionary/buildIntlayerDictionary.mjs.map +1 -1
- package/dist/esm/buildIntlayerDictionary/writeDynamicDictionary.mjs +3 -3
- package/dist/esm/buildIntlayerDictionary/writeDynamicDictionary.mjs.map +1 -1
- package/dist/esm/buildIntlayerDictionary/writeMergedDictionary.mjs.map +1 -1
- package/dist/esm/init/frameworkSetup/nextAppRouter/transforms.mjs +1 -16
- package/dist/esm/init/frameworkSetup/nextAppRouter/transforms.mjs.map +1 -1
- package/dist/esm/init/frameworkSetup/tanstackStart/transforms.mjs +29 -16
- package/dist/esm/init/frameworkSetup/tanstackStart/transforms.mjs.map +1 -1
- package/dist/esm/init/index.mjs +16 -6
- package/dist/esm/init/index.mjs.map +1 -1
- package/dist/esm/init/utils/astImports.mjs +51 -0
- package/dist/esm/init/utils/astImports.mjs.map +1 -0
- package/dist/esm/init/utils/configManipulation.mjs +28 -14
- package/dist/esm/init/utils/configManipulation.mjs.map +1 -1
- package/dist/esm/init/utils/fileSystem.mjs +50 -1
- package/dist/esm/init/utils/fileSystem.mjs.map +1 -1
- package/dist/esm/init/utils/index.mjs +3 -2
- package/dist/esm/init/utils/packageManager.mjs +17 -8
- package/dist/esm/init/utils/packageManager.mjs.map +1 -1
- package/dist/types/buildIntlayerDictionary/writeDynamicDictionary.d.ts +3 -3
- package/dist/types/buildIntlayerDictionary/writeMergedDictionary.d.ts +1 -1
- package/dist/types/formatDictionary.d.ts +2 -4
- package/dist/types/formatDictionary.d.ts.map +1 -1
- package/dist/types/init/frameworkSetup/nextAppRouter/transforms.d.ts.map +1 -1
- package/dist/types/init/frameworkSetup/tanstackStart/transforms.d.ts.map +1 -1
- package/dist/types/init/index.d.ts +7 -0
- package/dist/types/init/index.d.ts.map +1 -1
- package/dist/types/init/utils/astImports.d.ts +29 -0
- package/dist/types/init/utils/astImports.d.ts.map +1 -0
- package/dist/types/init/utils/configManipulation.d.ts +3 -3
- package/dist/types/init/utils/configManipulation.d.ts.map +1 -1
- package/dist/types/init/utils/fileSystem.d.ts +29 -1
- package/dist/types/init/utils/fileSystem.d.ts.map +1 -1
- package/dist/types/init/utils/index.d.ts +4 -3
- package/dist/types/init/utils/packageManager.d.ts +30 -3
- package/dist/types/init/utils/packageManager.d.ts.map +1 -1
- package/package.json +8 -8
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buildIntlayerDictionary.cjs","names":["OUTPUT_FORMAT","IMPORT_MODE","readDictionariesFromDisk","writeUnmergedDictionaries","writeMergedDictionaries","writeDynamicDictionary","writeDynamicQualifiedDictionaries","writeFetchDictionary"],"sources":["../../../src/buildIntlayerDictionary/buildIntlayerDictionary.ts"],"sourcesContent":["import { IMPORT_MODE, OUTPUT_FORMAT } from '@intlayer/config/defaultValues';\nimport { colorizeKey, getAppLogger } from '@intlayer/config/logger';\nimport { isQualifiedDictionaryGroup } from '@intlayer/core/dictionaryManipulator';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { Dictionary } from '@intlayer/types/dictionary';\nimport { readDictionariesFromDisk } from '../utils/readDictionariesFromDisk';\nimport {\n type LocalizedDictionaryOutput,\n type QualifiedMergedDictionaryOutput,\n writeDynamicDictionary,\n writeDynamicQualifiedDictionaries,\n} from './writeDynamicDictionary';\nimport { writeFetchDictionary } from './writeFetchDictionary';\nimport {\n type PlainMergedDictionaryOutput,\n writeMergedDictionaries,\n} from './writeMergedDictionary';\nimport { writeUnmergedDictionaries } from './writeUnmergedDictionary';\n\nexport type BuildDictionariesOptions = Partial<{\n formats: typeof OUTPUT_FORMAT;\n importOtherDictionaries: boolean;\n env: 'prod' | 'dev';\n}>;\n\nconst defaultOptions = {\n formats: OUTPUT_FORMAT,\n importOtherDictionaries: true,\n env: 'dev',\n} as const satisfies BuildDictionariesOptions;\n\n/**\n * This function transpile the bundled code to to make dictionaries as JSON files\n */\nexport const buildDictionary = async (\n localDictionariesEntries: Dictionary[],\n configuration: IntlayerConfig,\n options?: BuildDictionariesOptions\n) => {\n const importMode =\n configuration?.build?.importMode ??\n configuration?.dictionary?.importMode ??\n IMPORT_MODE;\n\n const { importOtherDictionaries, env, formats } = {\n ...defaultOptions,\n ...options,\n };\n\n const unmergedDictionariesToUpdate: Dictionary[] = [\n ...localDictionariesEntries,\n ];\n\n if (importOtherDictionaries) {\n const prevUnmergedDictionaries: Record<string, Dictionary[]> =\n readDictionariesFromDisk(configuration.system.unmergedDictionariesDir);\n\n // Reinsert other dictionaries with the same key to avoid merging errors\n for (const dictionaryToWrite of localDictionariesEntries) {\n const allPrebuiltUnmergedDictionaries =\n prevUnmergedDictionaries[dictionaryToWrite.key]!;\n\n if (allPrebuiltUnmergedDictionaries?.length > 0) {\n // Do not add the same dictionary again by filtering out the one with the same localId\n const otherUnmergedDictionaries =\n allPrebuiltUnmergedDictionaries?.filter(\n (unmergedDictionary) =>\n unmergedDictionary.localId !== dictionaryToWrite.localId\n );\n\n unmergedDictionariesToUpdate.push(...(otherUnmergedDictionaries ?? []));\n }\n }\n }\n\n const unmergedDictionaries = await writeUnmergedDictionaries(\n unmergedDictionariesToUpdate,\n configuration,\n env\n );\n\n const mergedDictionaries = await writeMergedDictionaries(\n unmergedDictionaries,\n configuration\n );\n\n const dictionariesToBuildDynamic: PlainMergedDictionaryOutput = {};\n const qualifiedDictionariesToBuildDynamic: QualifiedMergedDictionaryOutput =\n {};\n const keysToBuildFetch = new Set<string>();\n\n for (const [key, mergedResult] of Object.entries(mergedDictionaries)) {\n const dictionary = mergedResult.dictionary;\n\n if (isQualifiedDictionaryGroup(dictionary)) {\n // Collections / variants
|
|
1
|
+
{"version":3,"file":"buildIntlayerDictionary.cjs","names":["OUTPUT_FORMAT","IMPORT_MODE","readDictionariesFromDisk","writeUnmergedDictionaries","writeMergedDictionaries","writeDynamicDictionary","writeDynamicQualifiedDictionaries","writeFetchDictionary"],"sources":["../../../src/buildIntlayerDictionary/buildIntlayerDictionary.ts"],"sourcesContent":["import { IMPORT_MODE, OUTPUT_FORMAT } from '@intlayer/config/defaultValues';\nimport { colorizeKey, getAppLogger } from '@intlayer/config/logger';\nimport { isQualifiedDictionaryGroup } from '@intlayer/core/dictionaryManipulator';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { Dictionary } from '@intlayer/types/dictionary';\nimport { readDictionariesFromDisk } from '../utils/readDictionariesFromDisk';\nimport {\n type LocalizedDictionaryOutput,\n type QualifiedMergedDictionaryOutput,\n writeDynamicDictionary,\n writeDynamicQualifiedDictionaries,\n} from './writeDynamicDictionary';\nimport { writeFetchDictionary } from './writeFetchDictionary';\nimport {\n type PlainMergedDictionaryOutput,\n writeMergedDictionaries,\n} from './writeMergedDictionary';\nimport { writeUnmergedDictionaries } from './writeUnmergedDictionary';\n\nexport type BuildDictionariesOptions = Partial<{\n formats: typeof OUTPUT_FORMAT;\n importOtherDictionaries: boolean;\n env: 'prod' | 'dev';\n}>;\n\nconst defaultOptions = {\n formats: OUTPUT_FORMAT,\n importOtherDictionaries: true,\n env: 'dev',\n} as const satisfies BuildDictionariesOptions;\n\n/**\n * This function transpile the bundled code to to make dictionaries as JSON files\n */\nexport const buildDictionary = async (\n localDictionariesEntries: Dictionary[],\n configuration: IntlayerConfig,\n options?: BuildDictionariesOptions\n) => {\n const importMode =\n configuration?.build?.importMode ??\n configuration?.dictionary?.importMode ??\n IMPORT_MODE;\n\n const { importOtherDictionaries, env, formats } = {\n ...defaultOptions,\n ...options,\n };\n\n const unmergedDictionariesToUpdate: Dictionary[] = [\n ...localDictionariesEntries,\n ];\n\n if (importOtherDictionaries) {\n const prevUnmergedDictionaries: Record<string, Dictionary[]> =\n readDictionariesFromDisk(configuration.system.unmergedDictionariesDir);\n\n // Reinsert other dictionaries with the same key to avoid merging errors\n for (const dictionaryToWrite of localDictionariesEntries) {\n const allPrebuiltUnmergedDictionaries =\n prevUnmergedDictionaries[dictionaryToWrite.key]!;\n\n if (allPrebuiltUnmergedDictionaries?.length > 0) {\n // Do not add the same dictionary again by filtering out the one with the same localId\n const otherUnmergedDictionaries =\n allPrebuiltUnmergedDictionaries?.filter(\n (unmergedDictionary) =>\n unmergedDictionary.localId !== dictionaryToWrite.localId\n );\n\n unmergedDictionariesToUpdate.push(...(otherUnmergedDictionaries ?? []));\n }\n }\n }\n\n const unmergedDictionaries = await writeUnmergedDictionaries(\n unmergedDictionariesToUpdate,\n configuration,\n env\n );\n\n const mergedDictionaries = await writeMergedDictionaries(\n unmergedDictionaries,\n configuration\n );\n\n const dictionariesToBuildDynamic: PlainMergedDictionaryOutput = {};\n const qualifiedDictionariesToBuildDynamic: QualifiedMergedDictionaryOutput =\n {};\n const keysToBuildFetch = new Set<string>();\n\n for (const [key, mergedResult] of Object.entries(mergedDictionaries)) {\n const dictionary = mergedResult.dictionary;\n\n if (isQualifiedDictionaryGroup(dictionary)) {\n // Collections / variants resolve their qualifier at\n // runtime from a selector. Static mode keeps every entry in one JSON;\n // dynamic mode splits them into one chunk per (locale, qualifierId).\n const entryMode = dictionary.importMode ?? importMode;\n\n if (entryMode === 'dynamic') {\n qualifiedDictionariesToBuildDynamic[key] = {\n dictionaryPath: mergedResult.dictionaryPath,\n dictionary,\n };\n } else if (entryMode === 'fetch') {\n const appLogger = getAppLogger(configuration);\n appLogger(\n `Dictionary ${colorizeKey(key)} uses 'fetch' import mode with (${dictionary.qualifierTypes.join(', ')}) entries — fetch mode is not qualifier-aware yet, falling back to static import.`,\n { level: 'warn' }\n );\n }\n\n continue;\n }\n\n const mode = dictionary.importMode ?? importMode;\n\n if (mode === 'dynamic' || mode === 'fetch') {\n dictionariesToBuildDynamic[key] = {\n dictionaryPath: mergedResult.dictionaryPath,\n dictionary,\n };\n }\n\n if (mode === 'fetch') {\n keysToBuildFetch.add(key);\n }\n }\n\n let dynamicDictionaries: LocalizedDictionaryOutput | null = null;\n\n if (Object.keys(dictionariesToBuildDynamic).length > 0) {\n dynamicDictionaries = await writeDynamicDictionary(\n dictionariesToBuildDynamic,\n configuration,\n formats\n );\n }\n\n if (Object.keys(qualifiedDictionariesToBuildDynamic).length > 0) {\n await writeDynamicQualifiedDictionaries(\n qualifiedDictionariesToBuildDynamic,\n configuration,\n formats\n );\n }\n\n let fetchDictionaries: LocalizedDictionaryOutput | null = null;\n\n if (dynamicDictionaries && keysToBuildFetch.size > 0) {\n const dictionariesToBuildFetch: LocalizedDictionaryOutput = {};\n\n for (const key of keysToBuildFetch) {\n if (dynamicDictionaries[key]) {\n dictionariesToBuildFetch[key] = dynamicDictionaries[key];\n }\n }\n\n if (Object.keys(dictionariesToBuildFetch).length > 0) {\n fetchDictionaries = await writeFetchDictionary(\n dictionariesToBuildFetch,\n configuration,\n formats\n );\n }\n }\n\n return {\n unmergedDictionaries,\n mergedDictionaries,\n dynamicDictionaries,\n fetchDictionaries,\n };\n};\n"],"mappings":";;;;;;;;;;;;AAyBA,MAAM,iBAAiB;CACrB,SAASA;CACT,yBAAyB;CACzB,KAAK;CACN;;;;AAKD,MAAa,kBAAkB,OAC7B,0BACA,eACA,YACG;CACH,MAAM,aACJ,eAAe,OAAO,cACtB,eAAe,YAAY,cAC3BC;CAEF,MAAM,EAAE,yBAAyB,KAAK,YAAY;EAChD,GAAG;EACH,GAAG;EACJ;CAED,MAAM,+BAA6C,CACjD,GAAG,yBACJ;AAED,KAAI,yBAAyB;EAC3B,MAAM,2BACJC,gEAAyB,cAAc,OAAO,wBAAwB;AAGxE,OAAK,MAAM,qBAAqB,0BAA0B;GACxD,MAAM,kCACJ,yBAAyB,kBAAkB;AAE7C,OAAI,iCAAiC,SAAS,GAAG;IAE/C,MAAM,4BACJ,iCAAiC,QAC9B,uBACC,mBAAmB,YAAY,kBAAkB,QACpD;AAEH,iCAA6B,KAAK,GAAI,6BAA6B,EAAE,CAAE;;;;CAK7E,MAAM,uBAAuB,MAAMC,kFACjC,8BACA,eACA,IACD;CAED,MAAM,qBAAqB,MAAMC,8EAC/B,sBACA,cACD;CAED,MAAM,6BAA0D,EAAE;CAClE,MAAM,sCACJ,EAAE;CACJ,MAAM,mCAAmB,IAAI,KAAa;AAE1C,MAAK,MAAM,CAAC,KAAK,iBAAiB,OAAO,QAAQ,mBAAmB,EAAE;EACpE,MAAM,aAAa,aAAa;AAEhC,2EAA+B,WAAW,EAAE;GAI1C,MAAM,YAAY,WAAW,cAAc;AAE3C,OAAI,cAAc,UAChB,qCAAoC,OAAO;IACzC,gBAAgB,aAAa;IAC7B;IACD;YACQ,cAAc,QAEvB,2CAD+B,cACtB,CACP,uDAA0B,IAAI,CAAC,kCAAkC,WAAW,eAAe,KAAK,KAAK,CAAC,oFACtG,EAAE,OAAO,QAAQ,CAClB;AAGH;;EAGF,MAAM,OAAO,WAAW,cAAc;AAEtC,MAAI,SAAS,aAAa,SAAS,QACjC,4BAA2B,OAAO;GAChC,gBAAgB,aAAa;GAC7B;GACD;AAGH,MAAI,SAAS,QACX,kBAAiB,IAAI,IAAI;;CAI7B,IAAI,sBAAwD;AAE5D,KAAI,OAAO,KAAK,2BAA2B,CAAC,SAAS,EACnD,uBAAsB,MAAMC,8EAC1B,4BACA,eACA,QACD;AAGH,KAAI,OAAO,KAAK,oCAAoC,CAAC,SAAS,EAC5D,OAAMC,yFACJ,qCACA,eACA,QACD;CAGH,IAAI,oBAAsD;AAE1D,KAAI,uBAAuB,iBAAiB,OAAO,GAAG;EACpD,MAAM,2BAAsD,EAAE;AAE9D,OAAK,MAAM,OAAO,iBAChB,KAAI,oBAAoB,KACtB,0BAAyB,OAAO,oBAAoB;AAIxD,MAAI,OAAO,KAAK,yBAAyB,CAAC,SAAS,EACjD,qBAAoB,MAAMC,0EACxB,0BACA,eACA,QACD;;AAIL,QAAO;EACL;EACA;EACA;EACA;EACD"}
|
|
@@ -49,8 +49,8 @@ const serializeLoaderTree = (tree, indentLevel) => {
|
|
|
49
49
|
}).join(",\n")}\n${pad}}`;
|
|
50
50
|
};
|
|
51
51
|
/**
|
|
52
|
-
* Generates the entry point of a qualified dictionary (collection / variant
|
|
53
|
-
*
|
|
52
|
+
* Generates the entry point of a qualified dictionary (collection / variant,
|
|
53
|
+
* possibly combined). Under each locale the loader map nests one
|
|
54
54
|
* level per declared dimension (canonical order) and carries a marker listing
|
|
55
55
|
* those dimensions so the runtime can walk the tree.
|
|
56
56
|
*
|
|
@@ -120,7 +120,7 @@ const writeDynamicDictionary = async (mergedDictionaries, configuration, formats
|
|
|
120
120
|
};
|
|
121
121
|
/**
|
|
122
122
|
* Writes the dynamic chunks and entry points of qualified dictionaries
|
|
123
|
-
* (collections, variants
|
|
123
|
+
* (collections, variants — possibly combined) in
|
|
124
124
|
* `importMode: 'dynamic'`.
|
|
125
125
|
*
|
|
126
126
|
* Each entry is reduced to one per-locale chunk written to a path nested by
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"writeDynamicDictionary.cjs","names":["QUALIFIER_DYNAMIC_TYPES_KEY","OUTPUT_FORMAT","parallelize","writeJsonIfChanged","writeFileIfChanged","COMPOSITE_ID_SEPARATOR"],"sources":["../../../src/buildIntlayerDictionary/writeDynamicDictionary.ts"],"sourcesContent":["import { mkdir } from 'node:fs/promises';\nimport { resolve } from 'node:path';\nimport { OUTPUT_FORMAT } from '@intlayer/config/defaultValues';\nimport { colorizePath } from '@intlayer/config/logger';\nimport { assertPathWithin } from '@intlayer/config/utils';\nimport {\n COMPOSITE_ID_SEPARATOR,\n QUALIFIER_DYNAMIC_TYPES_KEY,\n reconstructQualifiedEntry,\n} from '@intlayer/core/dictionaryManipulator';\nimport { getPerLocaleDictionary } from '@intlayer/core/plugins';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type {\n Dictionary,\n DictionaryQualifierType,\n QualifiedDictionaryGroup,\n} from '@intlayer/types/dictionary';\nimport { parallelize } from '../utils/parallelize';\nimport { writeFileIfChanged } from '../writeFileIfChanged';\nimport { writeJsonIfChanged } from '../writeJsonIfChanged';\nimport type { PlainMergedDictionaryOutput } from './writeMergedDictionary';\n\nexport type DictionaryResult = {\n dictionaryPath: string;\n dictionary: Dictionary;\n};\n\nexport type LocalizedDictionaryResult = Partial<\n Record<Locale, DictionaryResult>\n>;\n\nexport type LocalizedDictionaryOutput = Record<\n string,\n LocalizedDictionaryResult\n>;\n\nconst DICTIONARIES_SUBDIR = 'json'; // Necessary to add a static first dir for Turbopack\n\n/**\n * Generates the content of a dictionary entry point file.\n */\nexport const generateDictionaryEntryPoint = (\n key: string,\n locales: string[],\n format: 'cjs' | 'esm' = 'esm'\n): string => {\n const sortedLocales = [...locales].sort((a, b) =>\n String(a).localeCompare(String(b))\n );\n\n const localeEntries = sortedLocales\n .map((locale) =>\n format === 'esm'\n ? ` '${locale}': () => import('./${DICTIONARIES_SUBDIR}/${key}/${locale}.json').then(m => m.default)`\n : ` '${locale}': () => Promise.resolve(require('./${DICTIONARIES_SUBDIR}/${key}/${locale}.json'))`\n )\n .join(',\\n');\n\n if (format === 'esm') {\n return (\n `const content = {\\n${localeEntries}\\n};\\n\\n` +\n `export default content;\\n`\n );\n }\n return `module.exports = {\\n${localeEntries}\\n};\\n`;\n};\n\n/**\n * A nested loader tree: one level per declared dimension, leaves are loader\n * expression strings.\n */\ntype LoaderTree = { [segment: string]: LoaderTree | string };\n\nconst buildLoaderExpression = (\n key: string,\n segments: string[],\n locale: string,\n format: 'cjs' | 'esm'\n): string => {\n const path = `./${DICTIONARIES_SUBDIR}/${key}/${segments.join('/')}/${locale}.json`;\n\n return format === 'esm'\n ? `() => import('${path}').then(m => m.default)`\n : `() => Promise.resolve(require('${path}'))`;\n};\n\nconst buildLoaderTree = (\n key: string,\n entriesSegments: string[][],\n locale: string,\n format: 'cjs' | 'esm'\n): LoaderTree => {\n const root: LoaderTree = {};\n\n for (const segments of entriesSegments) {\n let node = root;\n\n segments.forEach((segment, index) => {\n if (index === segments.length - 1) {\n node[segment] = buildLoaderExpression(key, segments, locale, format);\n return;\n }\n\n node[segment] = (node[segment] as LoaderTree | undefined) ?? {};\n node = node[segment] as LoaderTree;\n });\n }\n\n return root;\n};\n\nconst serializeLoaderTree = (tree: LoaderTree, indentLevel: number): string => {\n const pad = ' '.repeat(indentLevel);\n const innerPad = ' '.repeat(indentLevel + 1);\n\n const lines = Object.keys(tree)\n .sort((a, b) => a.localeCompare(b))\n .map((segment) => {\n const value = tree[segment]!;\n const serialized =\n typeof value === 'string'\n ? value\n : serializeLoaderTree(value, indentLevel + 1);\n\n return `${innerPad}'${segment}': ${serialized}`;\n });\n\n return `{\\n${lines.join(',\\n')}\\n${pad}}`;\n};\n\n/**\n * Generates the entry point of a qualified dictionary (collection / variant /\n * meta record, possibly combined). Under each locale the loader map nests one\n * level per declared dimension (canonical order) and carries a marker listing\n * those dimensions so the runtime can walk the tree.\n *\n * One static `import()` is emitted per leaf `(locale, …segments)` chunk, which\n * keeps the output compatible with bundlers that reject template-literal\n * dynamic imports (Turbopack).\n */\nexport const generateQualifiedDictionaryEntryPoint = (\n key: string,\n qualifierTypes: DictionaryQualifierType[],\n entriesSegments: string[][],\n locales: string[],\n format: 'cjs' | 'esm' = 'esm'\n): string => {\n const sortedLocales = [...locales].sort((a, b) =>\n String(a).localeCompare(String(b))\n );\n\n const localeEntries = sortedLocales\n .map((locale) => {\n const tree = buildLoaderTree(key, entriesSegments, locale, format);\n return ` '${locale}': ${serializeLoaderTree(tree, 1)}`;\n })\n .join(',\\n');\n\n const marker = ` '${QUALIFIER_DYNAMIC_TYPES_KEY}': ${JSON.stringify(qualifierTypes)}`;\n\n if (format === 'esm') {\n return (\n `const content = {\\n${marker},\\n${localeEntries}\\n};\\n\\n` +\n `export default content;\\n`\n );\n }\n return `module.exports = {\\n${marker},\\n${localeEntries}\\n};\\n`;\n};\n\n/**\n * Write the localized dictionaries to the dictionariesDir\n * @param mergedDictionaries - The merged dictionaries\n * @param configuration - The configuration\n * @returns The final dictionaries\n *\n * @example\n * ```ts\n * const unmergedDictionaries = await writeUnmergedDictionaries(dictionaries);\n * const finalDictionaries = await writeFinalDictionaries(unmergedDictionaries);\n * console.log(finalDictionaries);\n *\n * // .intlayer/dynamic_dictionary/dictionaries/en_home.json\n * // .intlayer/dynamic_dictionary/dictionaries/fr_home.json\n * ```\n */\nexport const writeDynamicDictionary = async (\n mergedDictionaries: PlainMergedDictionaryOutput,\n configuration: IntlayerConfig,\n formats: ('cjs' | 'esm')[] = OUTPUT_FORMAT\n): Promise<LocalizedDictionaryOutput> => {\n const { locales, defaultLocale } = configuration.internationalization;\n const { dynamicDictionariesDir } = configuration.system;\n\n const dictDir = resolve(dynamicDictionariesDir, DICTIONARIES_SUBDIR);\n await mkdir(dictDir, { recursive: true });\n\n const resultDictionariesPaths: LocalizedDictionaryOutput = {};\n\n // Merge dictionaries with the same key and write to dictionariesDir\n await parallelize(\n Object.entries(mergedDictionaries).sort(([a], [b]) =>\n String(a).localeCompare(String(b))\n ),\n async ([key, dictionaryEntry]) => {\n if (key === 'undefined') return;\n\n const localizedDictionariesPathsRecord: LocalizedDictionaryResult = {};\n\n const keyDir = resolve(dictDir, key);\n assertPathWithin(keyDir, dictDir);\n await mkdir(keyDir, { recursive: true });\n\n await parallelize(locales, async (locale) => {\n const localizedDictionary = getPerLocaleDictionary(\n dictionaryEntry.dictionary,\n locale,\n defaultLocale\n );\n\n // Directory structure: json/key/locale.json\n const resultFilePath = resolve(keyDir, `${locale}.json`);\n\n await writeJsonIfChanged(resultFilePath, localizedDictionary).catch(\n (err) => {\n console.error(\n `Error creating localized ${key}/${locale}.json:`,\n err\n );\n }\n );\n\n localizedDictionariesPathsRecord[locale] = {\n dictionaryPath: resultFilePath,\n dictionary: localizedDictionary,\n };\n });\n\n resultDictionariesPaths[key] = localizedDictionariesPathsRecord;\n\n await parallelize(formats, async (format) => {\n const extension = format === 'cjs' ? 'cjs' : 'mjs';\n const content = generateDictionaryEntryPoint(key, locales, format);\n\n const dynEntryPath = resolve(\n dynamicDictionariesDir,\n `${key}.${extension}`\n );\n assertPathWithin(dynEntryPath, dynamicDictionariesDir);\n\n await writeFileIfChanged(dynEntryPath, content).catch((err) => {\n console.error(\n `Error creating dynamic ${colorizePath(dynEntryPath)}:`,\n err\n );\n });\n });\n }\n );\n\n return resultDictionariesPaths;\n};\n\nexport type QualifiedMergedDictionaryResult = {\n dictionaryPath: string;\n dictionary: QualifiedDictionaryGroup;\n};\n\nexport type QualifiedMergedDictionaryOutput = Record<\n string,\n QualifiedMergedDictionaryResult\n>;\n\n/**\n * Writes the dynamic chunks and entry points of qualified dictionaries\n * (collections, variants, meta records — possibly combined) in\n * `importMode: 'dynamic'`.\n *\n * Each entry is reduced to one per-locale chunk written to a path nested by\n * dimension — `json/{key}/{seg1}/{seg2}/{locale}.json` — and a single\n * `{key}.{ext}` entry point exposes the matching nested loader tree, so the\n * entry point is discovered and aggregated exactly like a plain dynamic one.\n */\nexport const writeDynamicQualifiedDictionaries = async (\n qualifiedDictionaries: QualifiedMergedDictionaryOutput,\n configuration: IntlayerConfig,\n formats: ('cjs' | 'esm')[] = OUTPUT_FORMAT\n): Promise<void> => {\n const { locales, defaultLocale } = configuration.internationalization;\n const { dynamicDictionariesDir } = configuration.system;\n\n const dictDir = resolve(dynamicDictionariesDir, DICTIONARIES_SUBDIR);\n await mkdir(dictDir, { recursive: true });\n\n await parallelize(\n Object.entries(qualifiedDictionaries).sort(([a], [b]) =>\n String(a).localeCompare(String(b))\n ),\n async ([key, { dictionary: group }]) => {\n if (key === 'undefined') return;\n\n const entryIds = Object.keys(group.content);\n\n const keyDir = resolve(dictDir, key);\n assertPathWithin(keyDir, dictDir);\n\n // Per-entry segment lists (one segment per declared dimension), reused for\n // both the chunk paths and the generated loader tree.\n const entriesSegments: string[][] = [];\n\n await parallelize(entryIds, async (entryId) => {\n // Rebuild a resolvable dictionary from the content node + composite id\n // so per-locale extraction sees the same `{ key, content, meta? }` shape.\n const entry = reconstructQualifiedEntry(group, entryId);\n\n const segments = entryId.split(COMPOSITE_ID_SEPARATOR);\n entriesSegments.push(segments);\n\n const entryDir = resolve(keyDir, ...segments);\n assertPathWithin(entryDir, keyDir);\n await mkdir(entryDir, { recursive: true });\n\n await parallelize(locales, async (locale) => {\n const localizedDictionary = getPerLocaleDictionary(\n entry,\n locale,\n defaultLocale\n );\n\n // Directory structure: json/key/<…segments>/locale.json\n const resultFilePath = resolve(entryDir, `${locale}.json`);\n\n await writeJsonIfChanged(resultFilePath, localizedDictionary).catch(\n (err) => {\n console.error(\n `Error creating localized ${key}/${segments.join('/')}/${locale}.json:`,\n err\n );\n }\n );\n });\n });\n\n await parallelize(formats, async (format) => {\n const extension = format === 'cjs' ? 'cjs' : 'mjs';\n const content = generateQualifiedDictionaryEntryPoint(\n key,\n group.qualifierTypes,\n entriesSegments,\n locales,\n format\n );\n\n const dynEntryPath = resolve(\n dynamicDictionariesDir,\n `${key}.${extension}`\n );\n assertPathWithin(dynEntryPath, dynamicDictionariesDir);\n\n await writeFileIfChanged(dynEntryPath, content).catch((err) => {\n console.error(\n `Error creating dynamic ${colorizePath(dynEntryPath)}:`,\n err\n );\n });\n });\n }\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"writeDynamicDictionary.cjs","names":["QUALIFIER_DYNAMIC_TYPES_KEY","OUTPUT_FORMAT","parallelize","writeJsonIfChanged","writeFileIfChanged","COMPOSITE_ID_SEPARATOR"],"sources":["../../../src/buildIntlayerDictionary/writeDynamicDictionary.ts"],"sourcesContent":["import { mkdir } from 'node:fs/promises';\nimport { resolve } from 'node:path';\nimport { OUTPUT_FORMAT } from '@intlayer/config/defaultValues';\nimport { colorizePath } from '@intlayer/config/logger';\nimport { assertPathWithin } from '@intlayer/config/utils';\nimport {\n COMPOSITE_ID_SEPARATOR,\n QUALIFIER_DYNAMIC_TYPES_KEY,\n reconstructQualifiedEntry,\n} from '@intlayer/core/dictionaryManipulator';\nimport { getPerLocaleDictionary } from '@intlayer/core/plugins';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type {\n Dictionary,\n DictionaryQualifierType,\n QualifiedDictionaryGroup,\n} from '@intlayer/types/dictionary';\nimport { parallelize } from '../utils/parallelize';\nimport { writeFileIfChanged } from '../writeFileIfChanged';\nimport { writeJsonIfChanged } from '../writeJsonIfChanged';\nimport type { PlainMergedDictionaryOutput } from './writeMergedDictionary';\n\nexport type DictionaryResult = {\n dictionaryPath: string;\n dictionary: Dictionary;\n};\n\nexport type LocalizedDictionaryResult = Partial<\n Record<Locale, DictionaryResult>\n>;\n\nexport type LocalizedDictionaryOutput = Record<\n string,\n LocalizedDictionaryResult\n>;\n\nconst DICTIONARIES_SUBDIR = 'json'; // Necessary to add a static first dir for Turbopack\n\n/**\n * Generates the content of a dictionary entry point file.\n */\nexport const generateDictionaryEntryPoint = (\n key: string,\n locales: string[],\n format: 'cjs' | 'esm' = 'esm'\n): string => {\n const sortedLocales = [...locales].sort((a, b) =>\n String(a).localeCompare(String(b))\n );\n\n const localeEntries = sortedLocales\n .map((locale) =>\n format === 'esm'\n ? ` '${locale}': () => import('./${DICTIONARIES_SUBDIR}/${key}/${locale}.json').then(m => m.default)`\n : ` '${locale}': () => Promise.resolve(require('./${DICTIONARIES_SUBDIR}/${key}/${locale}.json'))`\n )\n .join(',\\n');\n\n if (format === 'esm') {\n return (\n `const content = {\\n${localeEntries}\\n};\\n\\n` +\n `export default content;\\n`\n );\n }\n return `module.exports = {\\n${localeEntries}\\n};\\n`;\n};\n\n/**\n * A nested loader tree: one level per declared dimension, leaves are loader\n * expression strings.\n */\ntype LoaderTree = { [segment: string]: LoaderTree | string };\n\nconst buildLoaderExpression = (\n key: string,\n segments: string[],\n locale: string,\n format: 'cjs' | 'esm'\n): string => {\n const path = `./${DICTIONARIES_SUBDIR}/${key}/${segments.join('/')}/${locale}.json`;\n\n return format === 'esm'\n ? `() => import('${path}').then(m => m.default)`\n : `() => Promise.resolve(require('${path}'))`;\n};\n\nconst buildLoaderTree = (\n key: string,\n entriesSegments: string[][],\n locale: string,\n format: 'cjs' | 'esm'\n): LoaderTree => {\n const root: LoaderTree = {};\n\n for (const segments of entriesSegments) {\n let node = root;\n\n segments.forEach((segment, index) => {\n if (index === segments.length - 1) {\n node[segment] = buildLoaderExpression(key, segments, locale, format);\n return;\n }\n\n node[segment] = (node[segment] as LoaderTree | undefined) ?? {};\n node = node[segment] as LoaderTree;\n });\n }\n\n return root;\n};\n\nconst serializeLoaderTree = (tree: LoaderTree, indentLevel: number): string => {\n const pad = ' '.repeat(indentLevel);\n const innerPad = ' '.repeat(indentLevel + 1);\n\n const lines = Object.keys(tree)\n .sort((a, b) => a.localeCompare(b))\n .map((segment) => {\n const value = tree[segment]!;\n const serialized =\n typeof value === 'string'\n ? value\n : serializeLoaderTree(value, indentLevel + 1);\n\n return `${innerPad}'${segment}': ${serialized}`;\n });\n\n return `{\\n${lines.join(',\\n')}\\n${pad}}`;\n};\n\n/**\n * Generates the entry point of a qualified dictionary (collection / variant,\n * possibly combined). Under each locale the loader map nests one\n * level per declared dimension (canonical order) and carries a marker listing\n * those dimensions so the runtime can walk the tree.\n *\n * One static `import()` is emitted per leaf `(locale, …segments)` chunk, which\n * keeps the output compatible with bundlers that reject template-literal\n * dynamic imports (Turbopack).\n */\nexport const generateQualifiedDictionaryEntryPoint = (\n key: string,\n qualifierTypes: DictionaryQualifierType[],\n entriesSegments: string[][],\n locales: string[],\n format: 'cjs' | 'esm' = 'esm'\n): string => {\n const sortedLocales = [...locales].sort((a, b) =>\n String(a).localeCompare(String(b))\n );\n\n const localeEntries = sortedLocales\n .map((locale) => {\n const tree = buildLoaderTree(key, entriesSegments, locale, format);\n return ` '${locale}': ${serializeLoaderTree(tree, 1)}`;\n })\n .join(',\\n');\n\n const marker = ` '${QUALIFIER_DYNAMIC_TYPES_KEY}': ${JSON.stringify(qualifierTypes)}`;\n\n if (format === 'esm') {\n return (\n `const content = {\\n${marker},\\n${localeEntries}\\n};\\n\\n` +\n `export default content;\\n`\n );\n }\n return `module.exports = {\\n${marker},\\n${localeEntries}\\n};\\n`;\n};\n\n/**\n * Write the localized dictionaries to the dictionariesDir\n * @param mergedDictionaries - The merged dictionaries\n * @param configuration - The configuration\n * @returns The final dictionaries\n *\n * @example\n * ```ts\n * const unmergedDictionaries = await writeUnmergedDictionaries(dictionaries);\n * const finalDictionaries = await writeFinalDictionaries(unmergedDictionaries);\n * console.log(finalDictionaries);\n *\n * // .intlayer/dynamic_dictionary/dictionaries/en_home.json\n * // .intlayer/dynamic_dictionary/dictionaries/fr_home.json\n * ```\n */\nexport const writeDynamicDictionary = async (\n mergedDictionaries: PlainMergedDictionaryOutput,\n configuration: IntlayerConfig,\n formats: ('cjs' | 'esm')[] = OUTPUT_FORMAT\n): Promise<LocalizedDictionaryOutput> => {\n const { locales, defaultLocale } = configuration.internationalization;\n const { dynamicDictionariesDir } = configuration.system;\n\n const dictDir = resolve(dynamicDictionariesDir, DICTIONARIES_SUBDIR);\n await mkdir(dictDir, { recursive: true });\n\n const resultDictionariesPaths: LocalizedDictionaryOutput = {};\n\n // Merge dictionaries with the same key and write to dictionariesDir\n await parallelize(\n Object.entries(mergedDictionaries).sort(([a], [b]) =>\n String(a).localeCompare(String(b))\n ),\n async ([key, dictionaryEntry]) => {\n if (key === 'undefined') return;\n\n const localizedDictionariesPathsRecord: LocalizedDictionaryResult = {};\n\n const keyDir = resolve(dictDir, key);\n assertPathWithin(keyDir, dictDir);\n await mkdir(keyDir, { recursive: true });\n\n await parallelize(locales, async (locale) => {\n const localizedDictionary = getPerLocaleDictionary(\n dictionaryEntry.dictionary,\n locale,\n defaultLocale\n );\n\n // Directory structure: json/key/locale.json\n const resultFilePath = resolve(keyDir, `${locale}.json`);\n\n await writeJsonIfChanged(resultFilePath, localizedDictionary).catch(\n (err) => {\n console.error(\n `Error creating localized ${key}/${locale}.json:`,\n err\n );\n }\n );\n\n localizedDictionariesPathsRecord[locale] = {\n dictionaryPath: resultFilePath,\n dictionary: localizedDictionary,\n };\n });\n\n resultDictionariesPaths[key] = localizedDictionariesPathsRecord;\n\n await parallelize(formats, async (format) => {\n const extension = format === 'cjs' ? 'cjs' : 'mjs';\n const content = generateDictionaryEntryPoint(key, locales, format);\n\n const dynEntryPath = resolve(\n dynamicDictionariesDir,\n `${key}.${extension}`\n );\n assertPathWithin(dynEntryPath, dynamicDictionariesDir);\n\n await writeFileIfChanged(dynEntryPath, content).catch((err) => {\n console.error(\n `Error creating dynamic ${colorizePath(dynEntryPath)}:`,\n err\n );\n });\n });\n }\n );\n\n return resultDictionariesPaths;\n};\n\nexport type QualifiedMergedDictionaryResult = {\n dictionaryPath: string;\n dictionary: QualifiedDictionaryGroup;\n};\n\nexport type QualifiedMergedDictionaryOutput = Record<\n string,\n QualifiedMergedDictionaryResult\n>;\n\n/**\n * Writes the dynamic chunks and entry points of qualified dictionaries\n * (collections, variants — possibly combined) in\n * `importMode: 'dynamic'`.\n *\n * Each entry is reduced to one per-locale chunk written to a path nested by\n * dimension — `json/{key}/{seg1}/{seg2}/{locale}.json` — and a single\n * `{key}.{ext}` entry point exposes the matching nested loader tree, so the\n * entry point is discovered and aggregated exactly like a plain dynamic one.\n */\nexport const writeDynamicQualifiedDictionaries = async (\n qualifiedDictionaries: QualifiedMergedDictionaryOutput,\n configuration: IntlayerConfig,\n formats: ('cjs' | 'esm')[] = OUTPUT_FORMAT\n): Promise<void> => {\n const { locales, defaultLocale } = configuration.internationalization;\n const { dynamicDictionariesDir } = configuration.system;\n\n const dictDir = resolve(dynamicDictionariesDir, DICTIONARIES_SUBDIR);\n await mkdir(dictDir, { recursive: true });\n\n await parallelize(\n Object.entries(qualifiedDictionaries).sort(([a], [b]) =>\n String(a).localeCompare(String(b))\n ),\n async ([key, { dictionary: group }]) => {\n if (key === 'undefined') return;\n\n const entryIds = Object.keys(group.content);\n\n const keyDir = resolve(dictDir, key);\n assertPathWithin(keyDir, dictDir);\n\n // Per-entry segment lists (one segment per declared dimension), reused for\n // both the chunk paths and the generated loader tree.\n const entriesSegments: string[][] = [];\n\n await parallelize(entryIds, async (entryId) => {\n // Rebuild a resolvable dictionary from the content node + composite id\n // so per-locale extraction sees the same `{ key, content, meta? }` shape.\n const entry = reconstructQualifiedEntry(group, entryId);\n\n const segments = entryId.split(COMPOSITE_ID_SEPARATOR);\n entriesSegments.push(segments);\n\n const entryDir = resolve(keyDir, ...segments);\n assertPathWithin(entryDir, keyDir);\n await mkdir(entryDir, { recursive: true });\n\n await parallelize(locales, async (locale) => {\n const localizedDictionary = getPerLocaleDictionary(\n entry,\n locale,\n defaultLocale\n );\n\n // Directory structure: json/key/<…segments>/locale.json\n const resultFilePath = resolve(entryDir, `${locale}.json`);\n\n await writeJsonIfChanged(resultFilePath, localizedDictionary).catch(\n (err) => {\n console.error(\n `Error creating localized ${key}/${segments.join('/')}/${locale}.json:`,\n err\n );\n }\n );\n });\n });\n\n await parallelize(formats, async (format) => {\n const extension = format === 'cjs' ? 'cjs' : 'mjs';\n const content = generateQualifiedDictionaryEntryPoint(\n key,\n group.qualifierTypes,\n entriesSegments,\n locales,\n format\n );\n\n const dynEntryPath = resolve(\n dynamicDictionariesDir,\n `${key}.${extension}`\n );\n assertPathWithin(dynEntryPath, dynamicDictionariesDir);\n\n await writeFileIfChanged(dynEntryPath, content).catch((err) => {\n console.error(\n `Error creating dynamic ${colorizePath(dynEntryPath)}:`,\n err\n );\n });\n });\n }\n );\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"writeMergedDictionary.cjs","names":["parallelize","writeJsonIfChanged"],"sources":["../../../src/buildIntlayerDictionary/writeMergedDictionary.ts"],"sourcesContent":["import { mkdir } from 'node:fs/promises';\nimport { resolve } from 'node:path';\nimport { colorizePath } from '@intlayer/config/logger';\nimport { assertPathWithin } from '@intlayer/config/utils';\nimport {\n mergeQualifiedDictionaries,\n normalizeDictionaries,\n} from '@intlayer/core/dictionaryManipulator';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type {\n Dictionary,\n QualifiedDictionaryGroup,\n} from '@intlayer/types/dictionary';\nimport { parallelize } from '../utils/parallelize';\nimport { writeJsonIfChanged } from '../writeJsonIfChanged';\nimport type { UnmergedDictionaryOutput } from './writeUnmergedDictionary';\n\nexport type MergedDictionaryResult = {\n dictionaryPath: string;\n dictionary: Dictionary | QualifiedDictionaryGroup;\n};\n\nexport type MergedDictionaryOutput = Record<string, MergedDictionaryResult>;\n\n/**\n * Merged output restricted to plain (unqualified) dictionaries — qualified\n * groups (collections, variants
|
|
1
|
+
{"version":3,"file":"writeMergedDictionary.cjs","names":["parallelize","writeJsonIfChanged"],"sources":["../../../src/buildIntlayerDictionary/writeMergedDictionary.ts"],"sourcesContent":["import { mkdir } from 'node:fs/promises';\nimport { resolve } from 'node:path';\nimport { colorizePath } from '@intlayer/config/logger';\nimport { assertPathWithin } from '@intlayer/config/utils';\nimport {\n mergeQualifiedDictionaries,\n normalizeDictionaries,\n} from '@intlayer/core/dictionaryManipulator';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type {\n Dictionary,\n QualifiedDictionaryGroup,\n} from '@intlayer/types/dictionary';\nimport { parallelize } from '../utils/parallelize';\nimport { writeJsonIfChanged } from '../writeJsonIfChanged';\nimport type { UnmergedDictionaryOutput } from './writeUnmergedDictionary';\n\nexport type MergedDictionaryResult = {\n dictionaryPath: string;\n dictionary: Dictionary | QualifiedDictionaryGroup;\n};\n\nexport type MergedDictionaryOutput = Record<string, MergedDictionaryResult>;\n\n/**\n * Merged output restricted to plain (unqualified) dictionaries — qualified\n * groups (collections, variants) are static-only and filtered\n * out before the dynamic/fetch build steps.\n */\nexport type PlainMergedDictionaryResult = {\n dictionaryPath: string;\n dictionary: Dictionary;\n};\n\nexport type PlainMergedDictionaryOutput = Record<\n string,\n PlainMergedDictionaryResult\n>;\n\n/**\n * Write the merged dictionaries to the dictionariesDir\n * @param groupedDictionaries - The grouped dictionaries\n * @param configuration - The configuration\n * @returns The merged dictionaries\n *\n * @example\n * ```ts\n * const unmergedDictionaries = await writeUnmergedDictionaries(dictionaries);\n * const finalDictionaries = await writeFinalDictionaries(unmergedDictionaries);\n * console.log(finalDictionaries);\n *\n * // .intlayer/dictionary/home.json\n * // { key: 'home', content: { ... } },\n * ```\n */\nexport const writeMergedDictionaries = async (\n groupedDictionaries: UnmergedDictionaryOutput,\n configuration: IntlayerConfig\n): Promise<MergedDictionaryOutput> => {\n const { dictionariesDir } = configuration.system;\n\n // Create the dictionaries folder if it doesn't exist\n await mkdir(resolve(dictionariesDir), { recursive: true });\n\n const results = await parallelize(\n Object.entries(groupedDictionaries),\n async ([key, dictionariesEntry]) => {\n if (key === 'undefined') {\n return undefined as unknown as readonly [\n string,\n MergedDictionaryResult,\n ];\n }\n\n const normalizedDictionaries = normalizeDictionaries(\n dictionariesEntry.dictionaries,\n configuration\n );\n\n const mergedDictionary = mergeQualifiedDictionaries(\n normalizedDictionaries\n );\n\n const outputFileName = `${key}.json`;\n const resultFilePath = resolve(dictionariesDir, outputFileName);\n\n assertPathWithin(resultFilePath, dictionariesDir);\n\n // Write the merged dictionary\n await writeJsonIfChanged(resultFilePath, mergedDictionary).catch(\n (err) => {\n console.error(\n `Error creating merged ${colorizePath(resultFilePath)}:`,\n err\n );\n }\n );\n\n return [\n key,\n {\n dictionaryPath: resultFilePath,\n dictionary: mergedDictionary,\n } as MergedDictionaryResult,\n ] as const;\n }\n );\n\n return Object.fromEntries(\n results.filter(Boolean) as Array<readonly [string, MergedDictionaryResult]>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAuDA,MAAa,0BAA0B,OACrC,qBACA,kBACoC;CACpC,MAAM,EAAE,oBAAoB,cAAc;AAG1C,0DAAoB,gBAAgB,EAAE,EAAE,WAAW,MAAM,CAAC;CAE1D,MAAM,UAAU,MAAMA,sCACpB,OAAO,QAAQ,oBAAoB,EACnC,OAAO,CAAC,KAAK,uBAAuB;AAClC,MAAI,QAAQ,YACV;EAWF,MAAM,wJAJJ,kBAAkB,cAClB,cAIsB,CACvB;EAGD,MAAM,wCAAyB,iBAAiB,GADtB,IAAI,OACiC;AAE/D,+CAAiB,gBAAgB,gBAAgB;AAGjD,QAAMC,8CAAmB,gBAAgB,iBAAiB,CAAC,OACxD,QAAQ;AACP,WAAQ,MACN,mEAAsC,eAAe,CAAC,IACtD,IACD;IAEJ;AAED,SAAO,CACL,KACA;GACE,gBAAgB;GAChB,YAAY;GACb,CACF;GAEJ;AAED,QAAO,OAAO,YACZ,QAAQ,OAAO,QAAQ,CACxB"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
2
|
const require_runtime = require('../../../_virtual/_rolldown/runtime.cjs');
|
|
3
|
+
const require_init_utils_astImports = require('../../utils/astImports.cjs');
|
|
3
4
|
let recast = require("recast");
|
|
4
5
|
recast = require_runtime.__toESM(recast);
|
|
5
6
|
|
|
@@ -35,22 +36,6 @@ const findDefaultExportFunction = (ast) => {
|
|
|
35
36
|
}
|
|
36
37
|
return null;
|
|
37
38
|
};
|
|
38
|
-
/** Index just past the leading directives + import declarations, where new top-level code is safe to insert. */
|
|
39
|
-
const firstInsertIndex = (ast) => {
|
|
40
|
-
const body = ast.program.body;
|
|
41
|
-
let index = 0;
|
|
42
|
-
while (index < body.length && (body[index].type === "ExpressionStatement" && body[index].expression?.type === "StringLiteral" || body[index].type === "ImportDeclaration")) index++;
|
|
43
|
-
return index;
|
|
44
|
-
};
|
|
45
|
-
/** Ensures `import { importName } from source`, merging into an existing import from the same source. */
|
|
46
|
-
const ensureNamedImport = (ast, importName, source) => {
|
|
47
|
-
for (const stmt of ast.program.body) if (stmt.type === "ImportDeclaration" && stmt.source.value === source) {
|
|
48
|
-
if (!stmt.specifiers.some((spec) => spec.type === "ImportSpecifier" && spec.imported?.name === importName)) stmt.specifiers.push(b.importSpecifier(b.identifier(importName)));
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
const declaration = b.importDeclaration([b.importSpecifier(b.identifier(importName))], b.stringLiteral(source));
|
|
52
|
-
ast.program.body.splice(firstInsertIndex(ast), 0, declaration);
|
|
53
|
-
};
|
|
54
39
|
/** Ensures `export { exportedName } from source`, skipping when already declared/re-exported. */
|
|
55
40
|
const ensureExportFrom = (ast, exportedName, source) => {
|
|
56
41
|
if (ast.program.body.some((stmt) => {
|
|
@@ -63,7 +48,7 @@ const ensureExportFrom = (ast, exportedName, source) => {
|
|
|
63
48
|
return false;
|
|
64
49
|
})) return;
|
|
65
50
|
const exportNode = parseTsx(`export { ${exportedName} } from "${source}";`).program.body[0];
|
|
66
|
-
ast.program.body.splice(firstInsertIndex(ast), 0, exportNode);
|
|
51
|
+
ast.program.body.splice(require_init_utils_astImports.firstInsertIndex(ast), 0, exportNode);
|
|
67
52
|
};
|
|
68
53
|
/** Makes the function async and inserts `const locale = await getLocale();` once, at the top of its body. */
|
|
69
54
|
const ensureAwaitedLocale = (funcNode) => {
|
|
@@ -130,8 +115,8 @@ const wrapLayoutWithProvider = (code) => {
|
|
|
130
115
|
code,
|
|
131
116
|
status: "skipped"
|
|
132
117
|
};
|
|
133
|
-
ensureNamedImport(ast, "IntlayerClientProvider", "next-intlayer");
|
|
134
|
-
ensureNamedImport(ast, "getLocale", "next-intlayer/server");
|
|
118
|
+
require_init_utils_astImports.ensureNamedImport(ast, "IntlayerClientProvider", "next-intlayer");
|
|
119
|
+
require_init_utils_astImports.ensureNamedImport(ast, "getLocale", "next-intlayer/server");
|
|
135
120
|
ensureExportFrom(ast, "generateStaticParams", "next-intlayer");
|
|
136
121
|
ensureAwaitedLocale(funcNode);
|
|
137
122
|
setHtmlLang(ast);
|
|
@@ -196,8 +181,8 @@ const wrapPageWithProvider = (code) => {
|
|
|
196
181
|
};
|
|
197
182
|
if (jsxReturn.kind === "return") jsxReturn.node.argument = buildProviderElement("IntlayerServerProvider", jsxReturn.node.argument);
|
|
198
183
|
else jsxReturn.node.body = buildProviderElement("IntlayerServerProvider", jsxReturn.node.body);
|
|
199
|
-
ensureNamedImport(ast, "IntlayerServerProvider", "next-intlayer/server");
|
|
200
|
-
ensureNamedImport(ast, "getLocale", "next-intlayer/server");
|
|
184
|
+
require_init_utils_astImports.ensureNamedImport(ast, "IntlayerServerProvider", "next-intlayer/server");
|
|
185
|
+
require_init_utils_astImports.ensureNamedImport(ast, "getLocale", "next-intlayer/server");
|
|
201
186
|
ensureAwaitedLocale(funcNode);
|
|
202
187
|
return {
|
|
203
188
|
code: recast.print(ast).code,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transforms.cjs","names":[],"sources":["../../../../../src/init/frameworkSetup/nextAppRouter/transforms.ts"],"sourcesContent":["import * as recast from 'recast';\n\nconst { builders: b } = recast.types;\n\n/** babel-ts parser handles TypeScript *and* JSX (the `typescript` parser does not). */\nconst parseTsx = (code: string): any =>\n recast.parse(code, { parser: require('recast/parsers/babel-ts') });\n\n/** Result of a source transform. `code` is unchanged for any non-`wrapped` status. */\nexport type TransformResult = {\n code: string;\n status: 'wrapped' | 'already' | 'skipped-client' | 'skipped';\n};\n\n/** Detects a top-level `'use client'` directive (client components can't be async server providers). */\nconst isClientComponent = (ast: any): boolean => {\n const directives = ast.program.directives ?? [];\n if (\n directives.some((directive: any) => directive.value?.value === 'use client')\n ) {\n return true;\n }\n return ast.program.body.some(\n (stmt: any) =>\n stmt.type === 'ExpressionStatement' &&\n stmt.expression?.type === 'StringLiteral' &&\n stmt.expression.value === 'use client'\n );\n};\n\n/** Finds the function node behind `export default`, following an identifier reference if needed. */\nconst findDefaultExportFunction = (ast: any): any => {\n const body = ast.program.body;\n\n const asFunction = (node: any): any =>\n node &&\n (node.type === 'ArrowFunctionExpression' ||\n node.type === 'FunctionExpression' ||\n node.type === 'FunctionDeclaration')\n ? node\n : null;\n\n for (const stmt of body) {\n if (stmt.type !== 'ExportDefaultDeclaration') continue;\n\n const direct = asFunction(stmt.declaration);\n if (direct) return direct;\n\n if (stmt.declaration?.type === 'Identifier') {\n const name = stmt.declaration.name;\n for (const candidate of body) {\n if (candidate.type === 'VariableDeclaration') {\n for (const declarator of candidate.declarations) {\n if (\n declarator.id?.type === 'Identifier' &&\n declarator.id.name === name\n ) {\n const fn = asFunction(declarator.init);\n if (fn) return fn;\n }\n }\n }\n if (\n candidate.type === 'FunctionDeclaration' &&\n candidate.id?.name === name\n ) {\n return candidate;\n }\n }\n }\n }\n\n return null;\n};\n\n/** Index just past the leading directives + import declarations, where new top-level code is safe to insert. */\nconst firstInsertIndex = (ast: any): number => {\n const body = ast.program.body;\n let index = 0;\n while (\n index < body.length &&\n ((body[index].type === 'ExpressionStatement' &&\n body[index].expression?.type === 'StringLiteral') ||\n body[index].type === 'ImportDeclaration')\n ) {\n index++;\n }\n return index;\n};\n\n/** Ensures `import { importName } from source`, merging into an existing import from the same source. */\nconst ensureNamedImport = (\n ast: any,\n importName: string,\n source: string\n): void => {\n for (const stmt of ast.program.body) {\n if (stmt.type === 'ImportDeclaration' && stmt.source.value === source) {\n const hasSpecifier = stmt.specifiers.some(\n (spec: any) =>\n spec.type === 'ImportSpecifier' && spec.imported?.name === importName\n );\n if (!hasSpecifier) {\n stmt.specifiers.push(b.importSpecifier(b.identifier(importName)));\n }\n return;\n }\n }\n\n const declaration = b.importDeclaration(\n [b.importSpecifier(b.identifier(importName))],\n b.stringLiteral(source)\n );\n ast.program.body.splice(firstInsertIndex(ast), 0, declaration);\n};\n\n/** Ensures `export { exportedName } from source`, skipping when already declared/re-exported. */\nconst ensureExportFrom = (\n ast: any,\n exportedName: string,\n source: string\n): void => {\n const body = ast.program.body;\n\n const alreadyPresent = body.some((stmt: any) => {\n if (\n stmt.type === 'ExportNamedDeclaration' &&\n stmt.source?.value === source\n ) {\n return stmt.specifiers.some(\n (spec: any) => spec.exported?.name === exportedName\n );\n }\n // Locally declared `export const/function generateStaticParams`\n if (stmt.type === 'ExportNamedDeclaration' && stmt.declaration) {\n const decl = stmt.declaration;\n if (decl.type === 'FunctionDeclaration' && decl.id?.name === exportedName)\n return true;\n if (decl.type === 'VariableDeclaration') {\n return decl.declarations.some(\n (d: any) => d.id?.type === 'Identifier' && d.id.name === exportedName\n );\n }\n }\n return false;\n });\n\n if (alreadyPresent) return;\n\n const exportNode = parseTsx(`export { ${exportedName} } from \"${source}\";`)\n .program.body[0];\n ast.program.body.splice(firstInsertIndex(ast), 0, exportNode);\n};\n\n/** Makes the function async and inserts `const locale = await getLocale();` once, at the top of its body. */\nconst ensureAwaitedLocale = (funcNode: any): void => {\n funcNode.async = true;\n\n if (funcNode.body.type !== 'BlockStatement') {\n funcNode.body = b.blockStatement([b.returnStatement(funcNode.body)]);\n }\n\n const hasLocale = funcNode.body.body.some(\n (stmt: any) =>\n stmt.type === 'VariableDeclaration' &&\n stmt.declarations.some(\n (d: any) =>\n // const locale = ...\n (d.id?.type === 'Identifier' && d.id.name === 'locale') ||\n // const { locale } = ... or const { locale: locale } = ...\n (d.id?.type === 'ObjectPattern' &&\n d.id.properties?.some(\n (prop: any) =>\n prop.value?.type === 'Identifier' &&\n prop.value?.name === 'locale'\n ))\n )\n );\n\n if (!hasLocale) {\n const localeStatement = parseTsx('const locale = await getLocale();')\n .program.body[0];\n funcNode.body.body.unshift(localeStatement);\n }\n};\n\n/** Builds `<providerName locale={locale}>{child}</providerName>` around an existing JSX child node. */\nconst buildProviderElement = (providerName: string, childNode: any): any => {\n const template = parseTsx(\n `const __wrap = <${providerName} locale={locale}>{__child__}</${providerName}>;`\n );\n const providerElement = template.program.body[0].declarations[0].init;\n providerElement.children = [childNode];\n return providerElement;\n};\n\n/** Sets `lang={locale}` on the first `<html>` element, if present. */\nconst setHtmlLang = (ast: any): void => {\n recast.visit(ast, {\n visitJSXOpeningElement(path) {\n const node = path.node;\n if (node.name?.type === 'JSXIdentifier' && node.name.name === 'html') {\n const langAttr = node.attributes?.find(\n (attr: any) =>\n attr.type === 'JSXAttribute' && attr.name?.name === 'lang'\n ) as any;\n const localeExpression = b.jsxExpressionContainer(\n b.identifier('locale')\n );\n if (langAttr) {\n langAttr.value = localeExpression;\n } else {\n node.attributes.push(\n b.jsxAttribute(b.jsxIdentifier('lang'), localeExpression)\n );\n }\n return false;\n }\n this.traverse(path);\n },\n });\n};\n\n/**\n * Wraps the `{children}` of a Next.js App Router **layout** with\n * `IntlayerClientProvider`, deriving the locale via `getLocale()`. Safe and\n * idempotent: bails (returns the original code) for client components, when no\n * `{children}` placeholder is found, or when there is no default export.\n */\nexport const wrapLayoutWithProvider = (code: string): TransformResult => {\n const ast = parseTsx(code);\n\n if (isClientComponent(ast)) return { code, status: 'skipped-client' };\n if (code.includes('IntlayerClientProvider'))\n return { code, status: 'already' };\n\n const funcNode = findDefaultExportFunction(ast);\n if (!funcNode) return { code, status: 'skipped' };\n\n let wrapped = false;\n recast.visit(funcNode, {\n visitJSXExpressionContainer(path) {\n if (wrapped) return false;\n const expression = path.node.expression;\n if (expression?.type === 'Identifier' && expression.name === 'children') {\n path.replace(buildProviderElement('IntlayerClientProvider', path.node));\n wrapped = true;\n return false;\n }\n this.traverse(path);\n },\n });\n\n if (!wrapped) return { code, status: 'skipped' };\n\n ensureNamedImport(ast, 'IntlayerClientProvider', 'next-intlayer');\n ensureNamedImport(ast, 'getLocale', 'next-intlayer/server');\n ensureExportFrom(ast, 'generateStaticParams', 'next-intlayer');\n ensureAwaitedLocale(funcNode);\n setHtmlLang(ast);\n\n return { code: recast.print(ast).code, status: 'wrapped' };\n};\n\n/** Returns the single top-level JSX return of a function, or the JSX of an expression-bodied arrow. */\nconst findSingleJsxReturn = (\n funcNode: any\n): { kind: 'return'; node: any } | { kind: 'arrow'; node: any } | null => {\n if (\n funcNode.type === 'ArrowFunctionExpression' &&\n funcNode.body.type !== 'BlockStatement'\n ) {\n const body =\n funcNode.body.type === 'JSXElement' ||\n funcNode.body.type === 'JSXFragment'\n ? funcNode.body\n : null;\n return body ? { kind: 'arrow', node: funcNode } : null;\n }\n\n if (funcNode.body.type !== 'BlockStatement') return null;\n\n // Collect every JSX-returning `return` inside this function, but do not\n // descend into nested function scopes (e.g. an inner component defined in the\n // same file). More than one top-level JSX return is ambiguous — bail.\n const jsxReturns: any[] = [];\n let functionDepth = 0;\n recast.visit(funcNode, {\n visitFunction(path) {\n functionDepth++;\n if (functionDepth === 1) {\n this.traverse(path);\n }\n functionDepth--;\n return false;\n },\n visitReturnStatement(path) {\n const argument = path.node.argument;\n if (argument?.type === 'JSXElement' || argument?.type === 'JSXFragment') {\n jsxReturns.push(path.node);\n }\n this.traverse(path);\n },\n });\n\n if (jsxReturns.length !== 1) return null;\n return { kind: 'return', node: jsxReturns[0] };\n};\n\n/**\n * Wraps the returned JSX of a Next.js App Router **page** with\n * `IntlayerServerProvider`, deriving the locale via `getLocale()`. Safe and\n * idempotent: bails for client components, when the page has no single\n * top-level JSX return, or when there is no default export.\n */\nexport const wrapPageWithProvider = (code: string): TransformResult => {\n const ast = parseTsx(code);\n\n if (isClientComponent(ast)) return { code, status: 'skipped-client' };\n if (code.includes('IntlayerServerProvider'))\n return { code, status: 'already' };\n\n const funcNode = findDefaultExportFunction(ast);\n if (!funcNode) return { code, status: 'skipped' };\n\n const jsxReturn = findSingleJsxReturn(funcNode);\n if (!jsxReturn) return { code, status: 'skipped' };\n\n if (jsxReturn.kind === 'return') {\n jsxReturn.node.argument = buildProviderElement(\n 'IntlayerServerProvider',\n jsxReturn.node.argument\n );\n } else {\n jsxReturn.node.body = buildProviderElement(\n 'IntlayerServerProvider',\n jsxReturn.node.body\n );\n }\n\n ensureNamedImport(ast, 'IntlayerServerProvider', 'next-intlayer/server');\n ensureNamedImport(ast, 'getLocale', 'next-intlayer/server');\n ensureAwaitedLocale(funcNode);\n\n return { code: recast.print(ast).code, status: 'wrapped' };\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"transforms.cjs","names":["firstInsertIndex"],"sources":["../../../../../src/init/frameworkSetup/nextAppRouter/transforms.ts"],"sourcesContent":["import * as recast from 'recast';\nimport { ensureNamedImport, firstInsertIndex } from '../../utils/astImports';\n\nconst { builders: b } = recast.types;\n\n/** babel-ts parser handles TypeScript *and* JSX (the `typescript` parser does not). */\nconst parseTsx = (code: string): any =>\n recast.parse(code, { parser: require('recast/parsers/babel-ts') });\n\n/** Result of a source transform. `code` is unchanged for any non-`wrapped` status. */\nexport type TransformResult = {\n code: string;\n status: 'wrapped' | 'already' | 'skipped-client' | 'skipped';\n};\n\n/** Detects a top-level `'use client'` directive (client components can't be async server providers). */\nconst isClientComponent = (ast: any): boolean => {\n const directives = ast.program.directives ?? [];\n if (\n directives.some((directive: any) => directive.value?.value === 'use client')\n ) {\n return true;\n }\n return ast.program.body.some(\n (stmt: any) =>\n stmt.type === 'ExpressionStatement' &&\n stmt.expression?.type === 'StringLiteral' &&\n stmt.expression.value === 'use client'\n );\n};\n\n/** Finds the function node behind `export default`, following an identifier reference if needed. */\nconst findDefaultExportFunction = (ast: any): any => {\n const body = ast.program.body;\n\n const asFunction = (node: any): any =>\n node &&\n (node.type === 'ArrowFunctionExpression' ||\n node.type === 'FunctionExpression' ||\n node.type === 'FunctionDeclaration')\n ? node\n : null;\n\n for (const stmt of body) {\n if (stmt.type !== 'ExportDefaultDeclaration') continue;\n\n const direct = asFunction(stmt.declaration);\n if (direct) return direct;\n\n if (stmt.declaration?.type === 'Identifier') {\n const name = stmt.declaration.name;\n for (const candidate of body) {\n if (candidate.type === 'VariableDeclaration') {\n for (const declarator of candidate.declarations) {\n if (\n declarator.id?.type === 'Identifier' &&\n declarator.id.name === name\n ) {\n const fn = asFunction(declarator.init);\n if (fn) return fn;\n }\n }\n }\n if (\n candidate.type === 'FunctionDeclaration' &&\n candidate.id?.name === name\n ) {\n return candidate;\n }\n }\n }\n }\n\n return null;\n};\n\n/** Ensures `export { exportedName } from source`, skipping when already declared/re-exported. */\nconst ensureExportFrom = (\n ast: any,\n exportedName: string,\n source: string\n): void => {\n const body = ast.program.body;\n\n const alreadyPresent = body.some((stmt: any) => {\n if (\n stmt.type === 'ExportNamedDeclaration' &&\n stmt.source?.value === source\n ) {\n return stmt.specifiers.some(\n (spec: any) => spec.exported?.name === exportedName\n );\n }\n // Locally declared `export const/function generateStaticParams`\n if (stmt.type === 'ExportNamedDeclaration' && stmt.declaration) {\n const decl = stmt.declaration;\n if (decl.type === 'FunctionDeclaration' && decl.id?.name === exportedName)\n return true;\n if (decl.type === 'VariableDeclaration') {\n return decl.declarations.some(\n (d: any) => d.id?.type === 'Identifier' && d.id.name === exportedName\n );\n }\n }\n return false;\n });\n\n if (alreadyPresent) return;\n\n const exportNode = parseTsx(`export { ${exportedName} } from \"${source}\";`)\n .program.body[0];\n ast.program.body.splice(firstInsertIndex(ast), 0, exportNode);\n};\n\n/** Makes the function async and inserts `const locale = await getLocale();` once, at the top of its body. */\nconst ensureAwaitedLocale = (funcNode: any): void => {\n funcNode.async = true;\n\n if (funcNode.body.type !== 'BlockStatement') {\n funcNode.body = b.blockStatement([b.returnStatement(funcNode.body)]);\n }\n\n const hasLocale = funcNode.body.body.some(\n (stmt: any) =>\n stmt.type === 'VariableDeclaration' &&\n stmt.declarations.some(\n (d: any) =>\n // const locale = ...\n (d.id?.type === 'Identifier' && d.id.name === 'locale') ||\n // const { locale } = ... or const { locale: locale } = ...\n (d.id?.type === 'ObjectPattern' &&\n d.id.properties?.some(\n (prop: any) =>\n prop.value?.type === 'Identifier' &&\n prop.value?.name === 'locale'\n ))\n )\n );\n\n if (!hasLocale) {\n const localeStatement = parseTsx('const locale = await getLocale();')\n .program.body[0];\n funcNode.body.body.unshift(localeStatement);\n }\n};\n\n/** Builds `<providerName locale={locale}>{child}</providerName>` around an existing JSX child node. */\nconst buildProviderElement = (providerName: string, childNode: any): any => {\n const template = parseTsx(\n `const __wrap = <${providerName} locale={locale}>{__child__}</${providerName}>;`\n );\n const providerElement = template.program.body[0].declarations[0].init;\n providerElement.children = [childNode];\n return providerElement;\n};\n\n/** Sets `lang={locale}` on the first `<html>` element, if present. */\nconst setHtmlLang = (ast: any): void => {\n recast.visit(ast, {\n visitJSXOpeningElement(path) {\n const node = path.node;\n if (node.name?.type === 'JSXIdentifier' && node.name.name === 'html') {\n const langAttr = node.attributes?.find(\n (attr: any) =>\n attr.type === 'JSXAttribute' && attr.name?.name === 'lang'\n ) as any;\n const localeExpression = b.jsxExpressionContainer(\n b.identifier('locale')\n );\n if (langAttr) {\n langAttr.value = localeExpression;\n } else {\n node.attributes.push(\n b.jsxAttribute(b.jsxIdentifier('lang'), localeExpression)\n );\n }\n return false;\n }\n this.traverse(path);\n },\n });\n};\n\n/**\n * Wraps the `{children}` of a Next.js App Router **layout** with\n * `IntlayerClientProvider`, deriving the locale via `getLocale()`. Safe and\n * idempotent: bails (returns the original code) for client components, when no\n * `{children}` placeholder is found, or when there is no default export.\n */\nexport const wrapLayoutWithProvider = (code: string): TransformResult => {\n const ast = parseTsx(code);\n\n if (isClientComponent(ast)) return { code, status: 'skipped-client' };\n if (code.includes('IntlayerClientProvider'))\n return { code, status: 'already' };\n\n const funcNode = findDefaultExportFunction(ast);\n if (!funcNode) return { code, status: 'skipped' };\n\n let wrapped = false;\n recast.visit(funcNode, {\n visitJSXExpressionContainer(path) {\n if (wrapped) return false;\n const expression = path.node.expression;\n if (expression?.type === 'Identifier' && expression.name === 'children') {\n path.replace(buildProviderElement('IntlayerClientProvider', path.node));\n wrapped = true;\n return false;\n }\n this.traverse(path);\n },\n });\n\n if (!wrapped) return { code, status: 'skipped' };\n\n ensureNamedImport(ast, 'IntlayerClientProvider', 'next-intlayer');\n ensureNamedImport(ast, 'getLocale', 'next-intlayer/server');\n ensureExportFrom(ast, 'generateStaticParams', 'next-intlayer');\n ensureAwaitedLocale(funcNode);\n setHtmlLang(ast);\n\n return { code: recast.print(ast).code, status: 'wrapped' };\n};\n\n/** Returns the single top-level JSX return of a function, or the JSX of an expression-bodied arrow. */\nconst findSingleJsxReturn = (\n funcNode: any\n): { kind: 'return'; node: any } | { kind: 'arrow'; node: any } | null => {\n if (\n funcNode.type === 'ArrowFunctionExpression' &&\n funcNode.body.type !== 'BlockStatement'\n ) {\n const body =\n funcNode.body.type === 'JSXElement' ||\n funcNode.body.type === 'JSXFragment'\n ? funcNode.body\n : null;\n return body ? { kind: 'arrow', node: funcNode } : null;\n }\n\n if (funcNode.body.type !== 'BlockStatement') return null;\n\n // Collect every JSX-returning `return` inside this function, but do not\n // descend into nested function scopes (e.g. an inner component defined in the\n // same file). More than one top-level JSX return is ambiguous — bail.\n const jsxReturns: any[] = [];\n let functionDepth = 0;\n recast.visit(funcNode, {\n visitFunction(path) {\n functionDepth++;\n if (functionDepth === 1) {\n this.traverse(path);\n }\n functionDepth--;\n return false;\n },\n visitReturnStatement(path) {\n const argument = path.node.argument;\n if (argument?.type === 'JSXElement' || argument?.type === 'JSXFragment') {\n jsxReturns.push(path.node);\n }\n this.traverse(path);\n },\n });\n\n if (jsxReturns.length !== 1) return null;\n return { kind: 'return', node: jsxReturns[0] };\n};\n\n/**\n * Wraps the returned JSX of a Next.js App Router **page** with\n * `IntlayerServerProvider`, deriving the locale via `getLocale()`. Safe and\n * idempotent: bails for client components, when the page has no single\n * top-level JSX return, or when there is no default export.\n */\nexport const wrapPageWithProvider = (code: string): TransformResult => {\n const ast = parseTsx(code);\n\n if (isClientComponent(ast)) return { code, status: 'skipped-client' };\n if (code.includes('IntlayerServerProvider'))\n return { code, status: 'already' };\n\n const funcNode = findDefaultExportFunction(ast);\n if (!funcNode) return { code, status: 'skipped' };\n\n const jsxReturn = findSingleJsxReturn(funcNode);\n if (!jsxReturn) return { code, status: 'skipped' };\n\n if (jsxReturn.kind === 'return') {\n jsxReturn.node.argument = buildProviderElement(\n 'IntlayerServerProvider',\n jsxReturn.node.argument\n );\n } else {\n jsxReturn.node.body = buildProviderElement(\n 'IntlayerServerProvider',\n jsxReturn.node.body\n );\n }\n\n ensureNamedImport(ast, 'IntlayerServerProvider', 'next-intlayer/server');\n ensureNamedImport(ast, 'getLocale', 'next-intlayer/server');\n ensureAwaitedLocale(funcNode);\n\n return { code: recast.print(ast).code, status: 'wrapped' };\n};\n"],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
2
|
const require_runtime = require('../../../_virtual/_rolldown/runtime.cjs');
|
|
3
|
+
const require_init_utils_astImports = require('../../utils/astImports.cjs');
|
|
3
4
|
let recast = require("recast");
|
|
4
5
|
recast = require_runtime.__toESM(recast);
|
|
5
6
|
|
|
@@ -7,21 +8,6 @@ recast = require_runtime.__toESM(recast);
|
|
|
7
8
|
const { builders: b, namedTypes: n } = recast.types;
|
|
8
9
|
/** babel-ts parser handles TypeScript *and* JSX. */
|
|
9
10
|
const parseTsx = (code) => recast.parse(code, { parser: require("recast/parsers/babel-ts") });
|
|
10
|
-
/** Index just past the leading directives + import declarations. */
|
|
11
|
-
const firstInsertIndex = (ast) => {
|
|
12
|
-
const body = ast.program.body;
|
|
13
|
-
let index = 0;
|
|
14
|
-
while (index < body.length && (body[index].type === "ExpressionStatement" && body[index].expression?.type === "StringLiteral" || body[index].type === "ImportDeclaration")) index++;
|
|
15
|
-
return index;
|
|
16
|
-
};
|
|
17
|
-
/** Ensures `import { importName } from source`, merging into an existing import from the same source. */
|
|
18
|
-
const ensureNamedImport = (ast, importName, source) => {
|
|
19
|
-
for (const stmt of ast.program.body) if (stmt.type === "ImportDeclaration" && stmt.source.value === source) {
|
|
20
|
-
if (!stmt.specifiers.some((spec) => spec.type === "ImportSpecifier" && spec.imported?.name === importName)) stmt.specifiers.push(b.importSpecifier(b.identifier(importName)));
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
ast.program.body.splice(firstInsertIndex(ast), 0, b.importDeclaration([b.importSpecifier(b.identifier(importName))], b.stringLiteral(source)));
|
|
24
|
-
};
|
|
25
11
|
/**
|
|
26
12
|
* Finds the single function that returns the `<html>` document element. Returns
|
|
27
13
|
* `null` when there is no such function, or more than one (ambiguous).
|
|
@@ -46,15 +32,42 @@ const findHtmlDocumentFunction = (ast) => {
|
|
|
46
32
|
return found.size === 1 ? [...found][0] : null;
|
|
47
33
|
};
|
|
48
34
|
/**
|
|
35
|
+
* Whether the binding pattern `node` introduces a variable named `name`.
|
|
36
|
+
* Recurses through destructuring patterns so all of `const locale`,
|
|
37
|
+
* `const { locale } = …`, `const { locale = fallback } = …`,
|
|
38
|
+
* `const { foo: locale } = …`, `const [locale] = …` and `...locale` are
|
|
39
|
+
* detected — not just a bare identifier.
|
|
40
|
+
*/
|
|
41
|
+
const patternBindsName = (node, name) => {
|
|
42
|
+
if (!node) return false;
|
|
43
|
+
switch (node.type) {
|
|
44
|
+
case "Identifier": return node.name === name;
|
|
45
|
+
case "AssignmentPattern": return patternBindsName(node.left, name);
|
|
46
|
+
case "RestElement": return patternBindsName(node.argument, name);
|
|
47
|
+
case "ArrayPattern": return node.elements.some((element) => patternBindsName(element, name));
|
|
48
|
+
case "ObjectPattern": return node.properties.some((property) => property.type === "RestElement" ? patternBindsName(property.argument, name) : patternBindsName(property.value, name));
|
|
49
|
+
default: return false;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
49
53
|
* Inserts `const locale = …` once, at the top of the function body. A single
|
|
50
54
|
* self-contained statement is used (rather than an intermediate `params` const)
|
|
51
55
|
* so it can never collide with a `params` variable the document already
|
|
52
56
|
* declares. Expression-bodied arrow functions are first converted to a block so
|
|
53
57
|
* the declaration has somewhere to live.
|
|
58
|
+
*
|
|
59
|
+
* No-ops when `locale` is already bound in the function — whether as a simple
|
|
60
|
+
* identifier, through a destructuring pattern (e.g.
|
|
61
|
+
* `const { locale = defaultLocale } = useLoaderData()`), or as a function
|
|
62
|
+
* parameter — so the injected declaration never collides with one the document
|
|
63
|
+
* already provides (which would produce an
|
|
64
|
+
* `Identifier 'locale' has already been declared` parse error).
|
|
54
65
|
*/
|
|
55
66
|
const ensureLocaleFromParams = (funcNode) => {
|
|
56
67
|
if (funcNode.body?.type !== "BlockStatement") funcNode.body = b.blockStatement([b.returnStatement(funcNode.body)]);
|
|
57
|
-
|
|
68
|
+
const declaredInBody = funcNode.body.body.some((stmt) => stmt.type === "VariableDeclaration" && stmt.declarations.some((d) => patternBindsName(d.id, "locale")));
|
|
69
|
+
const declaredInParams = (funcNode.params ?? []).some((param) => patternBindsName(param, "locale"));
|
|
70
|
+
if (declaredInBody || declaredInParams) return;
|
|
58
71
|
const statement = parseTsx("const locale = useParams({ strict: false }).locale ?? defaultLocale;").program.body[0];
|
|
59
72
|
funcNode.body.body.unshift(statement);
|
|
60
73
|
};
|
|
@@ -111,10 +124,10 @@ const wrapRootWithProvider = (code) => {
|
|
|
111
124
|
code,
|
|
112
125
|
status: "skipped"
|
|
113
126
|
};
|
|
114
|
-
ensureNamedImport(ast, "IntlayerProvider", "react-intlayer");
|
|
115
|
-
ensureNamedImport(ast, "useParams", "@tanstack/react-router");
|
|
116
|
-
ensureNamedImport(ast, "defaultLocale", "intlayer");
|
|
117
|
-
ensureNamedImport(ast, "getHTMLTextDir", "intlayer");
|
|
127
|
+
require_init_utils_astImports.ensureNamedImport(ast, "IntlayerProvider", "react-intlayer");
|
|
128
|
+
require_init_utils_astImports.ensureNamedImport(ast, "useParams", "@tanstack/react-router");
|
|
129
|
+
require_init_utils_astImports.ensureNamedImport(ast, "defaultLocale", "intlayer");
|
|
130
|
+
require_init_utils_astImports.ensureNamedImport(ast, "getHTMLTextDir", "intlayer");
|
|
118
131
|
ensureLocaleFromParams(funcNode);
|
|
119
132
|
setHtmlLangAndDir(ast);
|
|
120
133
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transforms.cjs","names":[],"sources":["../../../../../src/init/frameworkSetup/tanstackStart/transforms.ts"],"sourcesContent":["import * as recast from 'recast';\n\nconst { builders: b, namedTypes: n } = recast.types;\n\n/** babel-ts parser handles TypeScript *and* JSX. */\nconst parseTsx = (code: string): any =>\n recast.parse(code, { parser: require('recast/parsers/babel-ts') });\n\n/** Result of a source transform. `code` is unchanged for any non-`wrapped` status. */\nexport type TransformResult = {\n code: string;\n status: 'wrapped' | 'already' | 'skipped';\n};\n\n
|
|
1
|
+
{"version":3,"file":"transforms.cjs","names":[],"sources":["../../../../../src/init/frameworkSetup/tanstackStart/transforms.ts"],"sourcesContent":["import * as recast from 'recast';\nimport { ensureNamedImport } from '../../utils/astImports';\n\nconst { builders: b, namedTypes: n } = recast.types;\n\n/** babel-ts parser handles TypeScript *and* JSX. */\nconst parseTsx = (code: string): any =>\n recast.parse(code, { parser: require('recast/parsers/babel-ts') });\n\n/** Result of a source transform. `code` is unchanged for any non-`wrapped` status. */\nexport type TransformResult = {\n code: string;\n status: 'wrapped' | 'already' | 'skipped';\n};\n\n/**\n * Finds the single function that returns the `<html>` document element. Returns\n * `null` when there is no such function, or more than one (ambiguous).\n */\nconst findHtmlDocumentFunction = (ast: any): any => {\n const found = new Set<any>();\n\n recast.visit(ast, {\n visitJSXOpeningElement(path) {\n const node = path.node;\n if (node.name?.type === 'JSXIdentifier' && node.name.name === 'html') {\n // Walk up to the nearest enclosing function node.\n let current = path.parentPath;\n while (current) {\n const value = current.value;\n if (\n value &&\n (n.FunctionDeclaration.check(value) ||\n n.FunctionExpression.check(value) ||\n n.ArrowFunctionExpression.check(value))\n ) {\n found.add(value);\n break;\n }\n current = current.parentPath;\n }\n }\n this.traverse(path);\n },\n });\n\n return found.size === 1 ? [...found][0] : null;\n};\n\n/**\n * Whether the binding pattern `node` introduces a variable named `name`.\n * Recurses through destructuring patterns so all of `const locale`,\n * `const { locale } = …`, `const { locale = fallback } = …`,\n * `const { foo: locale } = …`, `const [locale] = …` and `...locale` are\n * detected — not just a bare identifier.\n */\nconst patternBindsName = (node: any, name: string): boolean => {\n if (!node) return false;\n switch (node.type) {\n case 'Identifier':\n return node.name === name;\n case 'AssignmentPattern':\n // `locale = fallback`\n return patternBindsName(node.left, name);\n case 'RestElement':\n // `...locale`\n return patternBindsName(node.argument, name);\n case 'ArrayPattern':\n return node.elements.some((element: any) =>\n patternBindsName(element, name)\n );\n case 'ObjectPattern':\n return node.properties.some((property: any) =>\n property.type === 'RestElement'\n ? patternBindsName(property.argument, name)\n : // The *value* is the binding target (`{ foo: locale }` binds `locale`).\n patternBindsName(property.value, name)\n );\n default:\n return false;\n }\n};\n\n/**\n * Inserts `const locale = …` once, at the top of the function body. A single\n * self-contained statement is used (rather than an intermediate `params` const)\n * so it can never collide with a `params` variable the document already\n * declares. Expression-bodied arrow functions are first converted to a block so\n * the declaration has somewhere to live.\n *\n * No-ops when `locale` is already bound in the function — whether as a simple\n * identifier, through a destructuring pattern (e.g.\n * `const { locale = defaultLocale } = useLoaderData()`), or as a function\n * parameter — so the injected declaration never collides with one the document\n * already provides (which would produce an\n * `Identifier 'locale' has already been declared` parse error).\n */\nconst ensureLocaleFromParams = (funcNode: any): void => {\n if (funcNode.body?.type !== 'BlockStatement') {\n funcNode.body = b.blockStatement([b.returnStatement(funcNode.body)]);\n }\n\n const declaredInBody = funcNode.body.body.some(\n (stmt: any) =>\n stmt.type === 'VariableDeclaration' &&\n stmt.declarations.some((d: any) => patternBindsName(d.id, 'locale'))\n );\n const declaredInParams = (funcNode.params ?? []).some((param: any) =>\n patternBindsName(param, 'locale')\n );\n if (declaredInBody || declaredInParams) return;\n\n const statement = parseTsx(\n 'const locale = useParams({ strict: false }).locale ?? defaultLocale;'\n ).program.body[0];\n funcNode.body.body.unshift(statement);\n};\n\n/** Sets `lang={locale}` and `dir={getHTMLTextDir(locale)}` on the first `<html>` element. */\nconst setHtmlLangAndDir = (ast: any): void => {\n recast.visit(ast, {\n visitJSXOpeningElement(path) {\n const node = path.node;\n if (node.name?.type !== 'JSXIdentifier' || node.name.name !== 'html') {\n this.traverse(path);\n return;\n }\n\n const upsertAttribute = (name: string, value: any): void => {\n const existing = node.attributes?.find(\n (attr: any) =>\n attr.type === 'JSXAttribute' && attr.name?.name === name\n ) as any;\n if (existing) {\n existing.value = value;\n } else {\n node.attributes.push(b.jsxAttribute(b.jsxIdentifier(name), value));\n }\n };\n\n upsertAttribute('lang', b.jsxExpressionContainer(b.identifier('locale')));\n upsertAttribute(\n 'dir',\n b.jsxExpressionContainer(\n b.callExpression(b.identifier('getHTMLTextDir'), [\n b.identifier('locale'),\n ])\n )\n );\n return false;\n },\n });\n};\n\n/**\n * Wraps the `{children}` of a TanStack Start root document with\n * `IntlayerProvider`, deriving the locale from the `{-$locale}` route param.\n * Safe and idempotent: bails (returns the original code) when the provider is\n * already present, when no `<html>` document function is found, or when there is\n * no `{children}` placeholder to wrap.\n */\nexport const wrapRootWithProvider = (code: string): TransformResult => {\n const ast = parseTsx(code);\n\n if (code.includes('IntlayerProvider')) return { code, status: 'already' };\n\n const funcNode = findHtmlDocumentFunction(ast);\n if (!funcNode) return { code, status: 'skipped' };\n\n let wrapped = false;\n recast.visit(funcNode, {\n visitJSXExpressionContainer(path) {\n if (wrapped) return false;\n const expression = path.node.expression;\n if (expression?.type === 'Identifier' && expression.name === 'children') {\n const template = parseTsx(\n 'const __wrap = <IntlayerProvider locale={locale}>{__child__}</IntlayerProvider>;'\n );\n const providerElement = template.program.body[0].declarations[0].init;\n providerElement.children = [path.node];\n path.replace(providerElement);\n wrapped = true;\n return false;\n }\n this.traverse(path);\n },\n });\n\n if (!wrapped) return { code, status: 'skipped' };\n\n ensureNamedImport(ast, 'IntlayerProvider', 'react-intlayer');\n ensureNamedImport(ast, 'useParams', '@tanstack/react-router');\n ensureNamedImport(ast, 'defaultLocale', 'intlayer');\n ensureNamedImport(ast, 'getHTMLTextDir', 'intlayer');\n ensureLocaleFromParams(funcNode);\n setHtmlLangAndDir(ast);\n\n return { code: recast.print(ast).code, status: 'wrapped' };\n};\n"],"mappings":"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"}
|
package/dist/cjs/init/index.cjs
CHANGED
|
@@ -126,8 +126,15 @@ const initIntlayer = async (rootDir, options) => {
|
|
|
126
126
|
...packageJson.dependencies ?? {},
|
|
127
127
|
...packageJson.devDependencies ?? {}
|
|
128
128
|
};
|
|
129
|
+
const effectiveAllDeps = {
|
|
130
|
+
...allDeps,
|
|
131
|
+
...options?.hintDependencies ?? {}
|
|
132
|
+
};
|
|
129
133
|
const packageManager = require_init_utils_packageManager.detectPackageManager(rootDir);
|
|
130
|
-
const
|
|
134
|
+
const linguiPresent = Boolean(effectiveAllDeps["@lingui/core"] || effectiveAllDeps["@lingui/react"] || effectiveAllDeps["@intlayer/lingui"]);
|
|
135
|
+
const linguiCatalog = linguiPresent ? await require_init_utils_fileSystem.detectLinguiCatalogPattern(rootDir) : null;
|
|
136
|
+
const hintLinguiJson = Boolean(options?.hintDependencies?.["__lingui_json__"]);
|
|
137
|
+
const { packagesToInstall, devPackagesToInstall, compatSyncConfig, compatVitePluginConfig } = require_init_utils_packageManager.detectMissingIntlayerPackages(effectiveAllDeps, { linguiCatalogFormat: linguiCatalog?.format ?? (linguiPresent ? hintLinguiJson ? "json" : "po" : null) });
|
|
131
138
|
if (!options?.noInstallPackages) {
|
|
132
139
|
const withVersion = (packages) => options?.upgradeToVersion ? packages.map((pkg) => `${pkg}@${options.upgradeToVersion}`) : packages;
|
|
133
140
|
if (packagesToInstall.length > 0) {
|
|
@@ -249,11 +256,14 @@ const initIntlayer = async (rootDir, options) => {
|
|
|
249
256
|
const detectedPattern = await require_init_utils_fileSystem.detectJsonLocalePattern(rootDir);
|
|
250
257
|
await require_initConfig_index.initConfig(format, rootDir, detectedPattern?.locales);
|
|
251
258
|
if (compatSyncConfig) {
|
|
252
|
-
const
|
|
259
|
+
const nextIntlMessagesPattern = allDeps["next-intl"] || allDeps["@intlayer/next-intl"] || allDeps["use-intl"] || allDeps["@intlayer/use-intl"] ? await require_init_utils_fileSystem.detectNextIntlMessagesPattern(rootDir) : null;
|
|
260
|
+
const sourceTemplate = linguiCatalog?.template ?? nextIntlMessagesPattern?.template ?? detectedPattern?.template;
|
|
253
261
|
const resolvedSyncConfig = {
|
|
254
262
|
...compatSyncConfig,
|
|
255
263
|
...sourceTemplate ? { sourceTemplate } : {}
|
|
256
264
|
};
|
|
265
|
+
const syncPluginPackage = resolvedSyncConfig.plugin === "po" ? "@intlayer/sync-po-plugin" : "@intlayer/sync-json-plugin";
|
|
266
|
+
const syncPluginName = resolvedSyncConfig.plugin === "po" ? "syncPO" : "syncJSON";
|
|
257
267
|
if (resolvedSyncConfig.splitKeys && resolvedSyncConfig.sourceTemplate.includes("${key}")) resolvedSyncConfig.splitKeys = false;
|
|
258
268
|
for (const configFile of [
|
|
259
269
|
"intlayer.config.ts",
|
|
@@ -262,10 +272,10 @@ const initIntlayer = async (rootDir, options) => {
|
|
|
262
272
|
"intlayer.config.cjs"
|
|
263
273
|
]) if (await require_init_utils_fileSystem.exists(rootDir, configFile)) {
|
|
264
274
|
const configContent = await require_init_utils_fileSystem.readFileFromRoot(rootDir, configFile);
|
|
265
|
-
if (!configContent.includes(
|
|
275
|
+
if (!configContent.includes(syncPluginPackage)) {
|
|
266
276
|
await require_init_utils_fileSystem.writeFileToRoot(rootDir, configFile, require_init_utils_configManipulation.updateIntlayerConfigWithSyncPlugin(configContent, configFile.split(".").pop(), resolvedSyncConfig));
|
|
267
|
-
(0, _intlayer_config_logger.logger)(`${_intlayer_config_logger.v} Updated ${(0, _intlayer_config_logger.colorizePath)(configFile)} with
|
|
268
|
-
} else (0, _intlayer_config_logger.logger)(`${_intlayer_config_logger.v} ${(0, _intlayer_config_logger.colorizePath)(configFile)} already includes
|
|
277
|
+
(0, _intlayer_config_logger.logger)(`${_intlayer_config_logger.v} Updated ${(0, _intlayer_config_logger.colorizePath)(configFile)} with ${syncPluginName} compat plugin`);
|
|
278
|
+
} else (0, _intlayer_config_logger.logger)(`${_intlayer_config_logger.v} ${(0, _intlayer_config_logger.colorizePath)(configFile)} already includes ${syncPluginName} plugin`);
|
|
269
279
|
break;
|
|
270
280
|
}
|
|
271
281
|
}
|