@intlayer/engine 9.4.0-canary.0 → 9.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +3 -0
  2. package/dist/cjs/build.cjs +1 -0
  3. package/dist/cjs/createType/createModuleAugmentation.cjs +22 -1
  4. package/dist/cjs/createType/createModuleAugmentation.cjs.map +1 -1
  5. package/dist/cjs/createType/index.cjs +1 -0
  6. package/dist/cjs/docReview/pipeline.cjs +21 -3
  7. package/dist/cjs/docReview/pipeline.cjs.map +1 -1
  8. package/dist/cjs/docReview/reviewReport.cjs.map +1 -1
  9. package/dist/cjs/init/frameworkSetup/nextAppRouter/index.cjs +6 -6
  10. package/dist/cjs/init/frameworkSetup/nextAppRouter/index.cjs.map +1 -1
  11. package/dist/cjs/init/frameworkSetup/nextAppRouter/templates.cjs +11 -9
  12. package/dist/cjs/init/frameworkSetup/nextAppRouter/templates.cjs.map +1 -1
  13. package/dist/cjs/init/frameworkSetup/nextAppRouter/transforms.cjs +10 -70
  14. package/dist/cjs/init/frameworkSetup/nextAppRouter/transforms.cjs.map +1 -1
  15. package/dist/cjs/init/index.cjs +5 -3
  16. package/dist/cjs/init/index.cjs.map +1 -1
  17. package/dist/cjs/installSkills/index.cjs +37 -6
  18. package/dist/cjs/installSkills/index.cjs.map +1 -1
  19. package/dist/cjs/writeConfiguration/generateConfigurationContent.cjs +21 -1
  20. package/dist/cjs/writeConfiguration/generateConfigurationContent.cjs.map +1 -1
  21. package/dist/esm/build.mjs +2 -2
  22. package/dist/esm/createType/createModuleAugmentation.mjs +23 -3
  23. package/dist/esm/createType/createModuleAugmentation.mjs.map +1 -1
  24. package/dist/esm/createType/index.mjs +2 -2
  25. package/dist/esm/docReview/pipeline.mjs +21 -3
  26. package/dist/esm/docReview/pipeline.mjs.map +1 -1
  27. package/dist/esm/docReview/reviewReport.mjs.map +1 -1
  28. package/dist/esm/init/frameworkSetup/nextAppRouter/index.mjs +7 -7
  29. package/dist/esm/init/frameworkSetup/nextAppRouter/index.mjs.map +1 -1
  30. package/dist/esm/init/frameworkSetup/nextAppRouter/templates.mjs +11 -9
  31. package/dist/esm/init/frameworkSetup/nextAppRouter/templates.mjs.map +1 -1
  32. package/dist/esm/init/frameworkSetup/nextAppRouter/transforms.mjs +11 -70
  33. package/dist/esm/init/frameworkSetup/nextAppRouter/transforms.mjs.map +1 -1
  34. package/dist/esm/init/index.mjs +5 -3
  35. package/dist/esm/init/index.mjs.map +1 -1
  36. package/dist/esm/installSkills/index.mjs +37 -6
  37. package/dist/esm/installSkills/index.mjs.map +1 -1
  38. package/dist/esm/writeConfiguration/generateConfigurationContent.mjs +21 -1
  39. package/dist/esm/writeConfiguration/generateConfigurationContent.mjs.map +1 -1
  40. package/dist/types/build.d.ts +2 -2
  41. package/dist/types/createType/createModuleAugmentation.d.ts +16 -1
  42. package/dist/types/createType/createModuleAugmentation.d.ts.map +1 -1
  43. package/dist/types/createType/index.d.ts +2 -2
  44. package/dist/types/docReview/pipeline.d.ts +14 -2
  45. package/dist/types/docReview/pipeline.d.ts.map +1 -1
  46. package/dist/types/docReview/reviewReport.d.ts +6 -3
  47. package/dist/types/docReview/reviewReport.d.ts.map +1 -1
  48. package/dist/types/init/frameworkSetup/nextAppRouter/index.d.ts +4 -2
  49. package/dist/types/init/frameworkSetup/nextAppRouter/index.d.ts.map +1 -1
  50. package/dist/types/init/frameworkSetup/nextAppRouter/templates.d.ts +8 -3
  51. package/dist/types/init/frameworkSetup/nextAppRouter/templates.d.ts.map +1 -1
  52. package/dist/types/init/frameworkSetup/nextAppRouter/transforms.d.ts +8 -10
  53. package/dist/types/init/frameworkSetup/nextAppRouter/transforms.d.ts.map +1 -1
  54. package/dist/types/installSkills/index.d.ts.map +1 -1
  55. package/dist/types/writeConfiguration/generateConfigurationContent.d.ts +15 -0
  56. package/dist/types/writeConfiguration/generateConfigurationContent.d.ts.map +1 -1
  57. package/package.json +10 -10
package/README.md CHANGED
@@ -214,6 +214,7 @@ Explore our comprehensive documentation to get started with Intlayer and learn h
214
214
  <li><a href="https://intlayer.org/doc/environment/vite-and-vue" rel=''>Vite + Vue</a></li>
215
215
  <li><a href="https://intlayer.org/doc/environment/nuxt-and-vue" rel=''>Vite + Nuxt</a></li>
216
216
  <li><a href="https://intlayer.org/doc/environment/vite-and-solid" rel=''>Vite + Solid</a></li>
217
+ <li><a href="https://intlayer.org/doc/environment/solid-start" rel=''>Solid Start</a></li>
217
218
  <li><a href="https://intlayer.org/doc/environment/angular" rel=''>Angular</a></li>
218
219
  <li>
219
220
  <a href="https://intlayer.org/doc/environment/express" rel=''>Backend</a>
@@ -223,7 +224,9 @@ Explore our comprehensive documentation to get started with Intlayer and learn h
223
224
  <li><a href="https://intlayer.org/doc/environment/fastify" rel=''>Fastify</a></li>
224
225
  <li><a href="https://intlayer.org/doc/environment/adonisjs" rel=''>AdonisJS</a></li>
225
226
  <li><a href="https://intlayer.org/doc/environment/hono" rel=''>Hono</a></li>
227
+ <li><a href="https://intlayer.org/doc/environment/elysia" rel=''>Elysia</a></li>
226
228
  </ul>
229
+
227
230
  </li>
228
231
  </ul>
229
232
  </details>
@@ -36,6 +36,7 @@ exports.formatDictionariesOutput = require_formatDictionary.formatDictionariesOu
36
36
  exports.formatDictionary = require_formatDictionary.formatDictionary;
37
37
  exports.formatDictionaryOutput = require_formatDictionary.formatDictionaryOutput;
38
38
  exports.formatDistantDictionaries = require_loadDictionaries_loadRemoteDictionaries.formatDistantDictionaries;
39
+ exports.formatDomains = require_createType_createModuleAugmentation.formatDomains;
39
40
  exports.formatLocalDictionaries = require_loadDictionaries_loadContentDeclaration.formatLocalDictionaries;
40
41
  exports.formatRewriteRules = require_createType_createModuleAugmentation.formatRewriteRules;
41
42
  exports.generateDictionaryListContent = require_createDictionaryEntryPoint_generateDictionaryListContent.generateDictionaryListContent;
@@ -29,6 +29,25 @@ const formatRewriteRules = (rewrite) => {
29
29
  return `'${canonical}': { ${Object.entries(localized).filter(([, pattern]) => typeof pattern === "string").map(([locale, pattern]) => `'${locale}': '${pattern}'`).join("; ")} }`;
30
30
  }).join("; ")} }`;
31
31
  };
32
+ /**
33
+ * Serializes the `routing.domains` map as a type literal, so `getLocalizedUrl`
34
+ * and `getLocalizedPath` can resolve the origin of a locale — and the
35
+ * suppression of its path prefix — at the type level. Returns an empty string
36
+ * when the project maps no locale to a domain.
37
+ *
38
+ * Each entry carries the two facts the consuming types need, both computed with
39
+ * the very helpers the runtime uses:
40
+ * - `origin`: the normalized origin ({@link getDomainOrigin}), prepended to the
41
+ * localized path when the target locale lives on another domain.
42
+ * - `exclusive`: whether the locale is the only one on that hostname
43
+ * ({@link isLocaleExclusiveOnDomain}), in which case the domain identifies the
44
+ * locale and no path prefix is emitted.
45
+ */
46
+ const formatDomains = (domains) => {
47
+ const domainEntries = Object.entries(domains ?? {}).filter(([, domain]) => typeof domain === "string" && domain.length > 0);
48
+ if (!domainEntries.length) return "";
49
+ return `; domains: { ${domainEntries.map(([locale, domain]) => `'${locale}': { origin: '${(0, _intlayer_core_localization.getDomainOrigin)(domain)}'; exclusive: ${(0, _intlayer_core_localization.isLocaleExclusiveOnDomain)(locale, domains)} }`).join("; ")} }`;
50
+ };
32
51
  const zodToTsString = (schema) => {
33
52
  if (!schema) return "any";
34
53
  const def = schema._def ?? schema.def ?? schema;
@@ -116,7 +135,8 @@ const generateTypeIndexContent = (typeFiles, configuration, zodToTsFns) => {
116
135
  fileContent += ` interface __EditorRegistry { enabled : ${enabled} }\n\n`;
117
136
  const routingMode = routing?.mode ?? "prefix-no-default";
118
137
  const formattedRewriteRules = formatRewriteRules(routing?.rewrite);
119
- fileContent += ` interface __RoutingRegistry { mode: '${routingMode}'; defaultLocale: '${defaultLocale}'${formattedRewriteRules} }\n`;
138
+ const formattedDomains = formatDomains(routing?.domains);
139
+ fileContent += ` interface __RoutingRegistry { mode: '${routingMode}'; defaultLocale: '${defaultLocale}'${formattedRewriteRules}${formattedDomains} }\n`;
120
140
  fileContent += `}\n`;
121
141
  return fileContent;
122
142
  };
@@ -141,6 +161,7 @@ const createModuleAugmentation = async (configuration) => {
141
161
 
142
162
  //#endregion
143
163
  exports.createModuleAugmentation = createModuleAugmentation;
164
+ exports.formatDomains = formatDomains;
144
165
  exports.formatRewriteRules = formatRewriteRules;
145
166
  exports.getTypeName = getTypeName;
146
167
  //# sourceMappingURL=createModuleAugmentation.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"createModuleAugmentation.cjs","names":["kebabCaseToCamelCase","getRewriteRules","normalizePath","relative","basename","extname","getPathHash","mkdir","fg","join","writeFileIfChanged"],"sources":["../../../src/createType/createModuleAugmentation.ts"],"sourcesContent":["import { mkdir } from 'node:fs/promises';\nimport { basename, extname, join, relative } from 'node:path';\nimport { kebabCaseToCamelCase, normalizePath } from '@intlayer/config/utils';\nimport { getRewriteRules } from '@intlayer/core/localization';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { IntlayerConfig, RoutingConfig } from '@intlayer/types/config';\nimport fg from 'fast-glob';\nimport { getPathHash } from '../utils';\nimport { writeFileIfChanged } from '../writeFileIfChanged';\n\nexport const getTypeName = (key: string): string =>\n `${kebabCaseToCamelCase(key)}Content`;\n\n/** Returns lines like: [Locales.FRENCH]: 1; */\nconst formatLocales = (locales: Locale[]) =>\n locales.map((locale) => ` \"${locale}\": 1;`).join('\\n');\n\n/**\n * Serializes the `url` rewrite rules as a type literal, so `getLocalizedPath`\n * and `getLocalizedUrl` can resolve a canonical path to its localized literal at\n * the type level. Returns an empty string when the project declares no rewrite —\n * the consuming types then localize every path to itself.\n *\n * Rules are emitted in their normalized form (`:param`, no locale prefix), which\n * is exactly what the runtime matches against.\n */\nexport const formatRewriteRules = (\n rewrite: RoutingConfig['rewrite']\n): string => {\n const rewriteRules = getRewriteRules(rewrite, 'url');\n\n if (!rewriteRules?.rules?.length) return '';\n\n const formattedRules = rewriteRules.rules\n .map(({ canonical, localized }) => {\n const formattedLocalized = Object.entries(localized)\n .filter(([, pattern]) => typeof pattern === 'string')\n .map(([locale, pattern]) => `'${locale}': '${pattern}'`)\n .join('; ');\n\n return `'${canonical}': { ${formattedLocalized} }`;\n })\n .join('; ');\n\n return `; rewrite: { ${formattedRules} }`;\n};\n\nconst zodToTsString = (schema: any): string => {\n if (!schema) return 'any';\n\n // Support both real Zod objects (_def) and serialized versions (def or nested)\n const def = schema._def ?? schema.def ?? schema;\n\n // Handle serialized type names (sometimes 'type' instead of 'typeName')\n const typeName = def.typeName ?? def.type;\n\n switch (typeName) {\n case 'ZodString':\n case 'string':\n return 'string';\n case 'ZodNumber':\n case 'number':\n return 'number';\n case 'ZodBoolean':\n case 'boolean':\n return 'boolean';\n case 'ZodNull':\n case 'null':\n return 'null';\n case 'ZodUndefined':\n case 'undefined':\n return 'undefined';\n case 'ZodArray':\n case 'array':\n return `${zodToTsString(def.type ?? def.element)}[]`;\n case 'ZodObject':\n case 'object': {\n const shape = typeof def.shape === 'function' ? def.shape() : def.shape;\n if (!shape) return 'Record<string, any>';\n\n const entries = Object.entries(shape)\n .map(([k, v]) => ` \"${k}\": ${zodToTsString(v)};`)\n .join('\\n');\n return `{\\n${entries}\\n }`;\n }\n case 'ZodOptional':\n case 'optional':\n return `${zodToTsString(def.innerType ?? def.wrapped)} | undefined`;\n case 'ZodNullable':\n case 'nullable':\n return `${zodToTsString(def.innerType ?? def.wrapped)} | null`;\n case 'ZodUnion':\n case 'union': {\n const options = def.options ?? [];\n return options.map(zodToTsString).join(' | ');\n }\n case 'ZodIntersection':\n case 'intersection':\n return `${zodToTsString(def.left)} & ${zodToTsString(def.right)}`;\n case 'ZodEnum':\n case 'enum': {\n const values = def.values ?? [];\n return values.map((v: string) => `\"${v}\"`).join(' | ');\n }\n case 'ZodLiteral':\n case 'literal': {\n const value = def.value;\n return typeof value === 'string' ? `\"${value}\"` : String(value);\n }\n default:\n return 'any';\n }\n};\n\ntype ZodToTsFns = {\n zodToTs: (schema: any, opts?: any) => { node: any };\n printNode: (node: any) => string;\n createAuxiliaryTypeStore: () => any;\n};\n\n/** Generate the content of the module augmentation file */\nconst generateTypeIndexContent = (\n typeFiles: string[],\n configuration: IntlayerConfig,\n zodToTsFns: ZodToTsFns | null\n): string => {\n const { internationalization, system, editor, routing } = configuration;\n const { moduleAugmentationDir } = system;\n const { enabled } = editor;\n const { locales, requiredLocales, strictMode, defaultLocale } =\n internationalization;\n\n let fileContent = 'import \"intlayer\";\\n';\n\n // Build dictionary refs\n const dictionariesRef = typeFiles.map((dictionaryPath) => ({\n relativePath: `./${normalizePath(relative(moduleAugmentationDir, dictionaryPath))}`,\n id: basename(dictionaryPath, extname(dictionaryPath)),\n hash: `_${getPathHash(dictionaryPath)}`,\n }));\n\n // Import all dictionaries\n for (const dictionary of dictionariesRef) {\n fileContent += `import ${dictionary.hash} from '${dictionary.relativePath}';\\n`;\n }\n fileContent += '\\n';\n\n // Dictionary map entries (id: typeof <hash>)\n const formattedDictionaryMap: string = dictionariesRef\n .map((dictionary) => ` \"${dictionary.id}\": typeof ${dictionary.hash};`)\n .join('\\n');\n\n // Ensure required ⊆ declared; if empty, default required = declared\n const declared = locales;\n const requiredSanitized = requiredLocales?.length\n ? requiredLocales.filter((requiredLocales) =>\n declared.includes(requiredLocales)\n )\n : declared;\n\n const formattedDeclaredLocales = formatLocales(declared);\n const formattedRequiredLocales = formatLocales(requiredSanitized);\n\n // Build schema registry\n const schemas = configuration.schemas ?? {};\n const formattedSchemas = Object.entries(schemas)\n .map(([key, schema]) => {\n let typeStr = 'any';\n\n if (schema) {\n try {\n if (zodToTsFns) {\n const { node } = zodToTsFns.zodToTs(schema, {\n auxiliaryTypeStore: zodToTsFns.createAuxiliaryTypeStore(),\n });\n // 133 is the kind for AnyKeyword in TypeScript\n if ((node as any).kind !== 133) {\n typeStr = zodToTsFns.printNode(node);\n } else {\n typeStr = zodToTsString(schema);\n }\n } else {\n typeStr = zodToTsString(schema);\n }\n } catch (_e) {\n // Fallback to custom string generator\n typeStr = zodToTsString(schema);\n }\n }\n return ` \"${key}\": ${typeStr};`;\n })\n .join('\\n');\n\n // Choose strict mode registry key\n const strictKey =\n strictMode === 'strict'\n ? 'strict'\n : strictMode === 'inclusive'\n ? 'inclusive'\n : 'loose';\n\n /**\n * Module augmentation that ONLY adds keys to registries.\n * No types/aliases redefined here—avoids merge conflicts.\n */\n fileContent += `declare module 'intlayer' {\\n`;\n // Dictionaries registry\n fileContent += ` interface __DictionaryRegistry {\\n${formattedDictionaryMap}\\n }\\n\\n`;\n // Locales registries\n fileContent += ` interface __DeclaredLocalesRegistry {\\n${formattedDeclaredLocales}\\n }\\n\\n`;\n fileContent += ` interface __RequiredLocalesRegistry {\\n${formattedRequiredLocales}\\n }\\n\\n`;\n // Schema registry\n fileContent += ` interface __SchemaRegistry {\\n${formattedSchemas}\\n }\\n\\n`;\n // Resolved strict mode (narrow the literal at build time)\n fileContent += ` interface __StrictModeRegistry { mode: '${strictKey}' }\\n\\n`;\n // Editor registry\n fileContent += ` interface __EditorRegistry { enabled : ${enabled} }\\n\\n`;\n // Routing registry (mode + defaultLocale narrowed to literals)\n const routingMode = routing?.mode ?? 'prefix-no-default';\n const formattedRewriteRules = formatRewriteRules(routing?.rewrite);\n fileContent += ` interface __RoutingRegistry { mode: '${routingMode}'; defaultLocale: '${defaultLocale}'${formattedRewriteRules} }\\n`;\n fileContent += `}\\n`;\n\n return fileContent;\n};\n\n/** Generate the index file merging all the types */\nexport const createModuleAugmentation = async (\n configuration: IntlayerConfig\n) => {\n const { moduleAugmentationDir, typesDir } = configuration.system;\n\n await mkdir(moduleAugmentationDir, { recursive: true });\n\n const dictionariesTypesDefinitions: string[] = await fg(\n normalizePath(`${typesDir}/*.ts`),\n { ignore: ['**/*.d.ts'] }\n );\n\n let zodToTsFns: ZodToTsFns | null = null;\n\n const hasSchemas = Object.keys(configuration.schemas ?? {}).length > 0;\n\n // zod-to-ts drags in the whole `typescript` package (~300ms import), so\n // only load it when there are schemas to convert.\n if (hasSchemas) {\n try {\n const mod = await import('zod-to-ts');\n zodToTsFns = {\n zodToTs: mod.zodToTs,\n printNode: mod.printNode,\n createAuxiliaryTypeStore: mod.createAuxiliaryTypeStore,\n };\n } catch {\n // typescript peer dep not installed (plain JS project), use fallback\n }\n }\n\n const tsContent = generateTypeIndexContent(\n dictionariesTypesDefinitions,\n configuration,\n zodToTsFns\n );\n\n const tsFilePath = join(moduleAugmentationDir, 'intlayer.d.ts');\n await writeFileIfChanged(tsFilePath, tsContent);\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"createModuleAugmentation.cjs","names":["kebabCaseToCamelCase","getRewriteRules","getDomainOrigin","isLocaleExclusiveOnDomain","normalizePath","relative","basename","extname","getPathHash","mkdir","fg","join","writeFileIfChanged"],"sources":["../../../src/createType/createModuleAugmentation.ts"],"sourcesContent":["import { mkdir } from 'node:fs/promises';\nimport { basename, extname, join, relative } from 'node:path';\nimport { kebabCaseToCamelCase, normalizePath } from '@intlayer/config/utils';\nimport {\n getDomainOrigin,\n getRewriteRules,\n isLocaleExclusiveOnDomain,\n} from '@intlayer/core/localization';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { IntlayerConfig, RoutingConfig } from '@intlayer/types/config';\nimport fg from 'fast-glob';\nimport { getPathHash } from '../utils';\nimport { writeFileIfChanged } from '../writeFileIfChanged';\n\nexport const getTypeName = (key: string): string =>\n `${kebabCaseToCamelCase(key)}Content`;\n\n/** Returns lines like: [Locales.FRENCH]: 1; */\nconst formatLocales = (locales: Locale[]) =>\n locales.map((locale) => ` \"${locale}\": 1;`).join('\\n');\n\n/**\n * Serializes the `url` rewrite rules as a type literal, so `getLocalizedPath`\n * and `getLocalizedUrl` can resolve a canonical path to its localized literal at\n * the type level. Returns an empty string when the project declares no rewrite —\n * the consuming types then localize every path to itself.\n *\n * Rules are emitted in their normalized form (`:param`, no locale prefix), which\n * is exactly what the runtime matches against.\n */\nexport const formatRewriteRules = (\n rewrite: RoutingConfig['rewrite']\n): string => {\n const rewriteRules = getRewriteRules(rewrite, 'url');\n\n if (!rewriteRules?.rules?.length) return '';\n\n const formattedRules = rewriteRules.rules\n .map(({ canonical, localized }) => {\n const formattedLocalized = Object.entries(localized)\n .filter(([, pattern]) => typeof pattern === 'string')\n .map(([locale, pattern]) => `'${locale}': '${pattern}'`)\n .join('; ');\n\n return `'${canonical}': { ${formattedLocalized} }`;\n })\n .join('; ');\n\n return `; rewrite: { ${formattedRules} }`;\n};\n\n/**\n * Serializes the `routing.domains` map as a type literal, so `getLocalizedUrl`\n * and `getLocalizedPath` can resolve the origin of a locale — and the\n * suppression of its path prefix — at the type level. Returns an empty string\n * when the project maps no locale to a domain.\n *\n * Each entry carries the two facts the consuming types need, both computed with\n * the very helpers the runtime uses:\n * - `origin`: the normalized origin ({@link getDomainOrigin}), prepended to the\n * localized path when the target locale lives on another domain.\n * - `exclusive`: whether the locale is the only one on that hostname\n * ({@link isLocaleExclusiveOnDomain}), in which case the domain identifies the\n * locale and no path prefix is emitted.\n */\nexport const formatDomains = (domains: RoutingConfig['domains']): string => {\n const domainEntries = Object.entries(domains ?? {}).filter(\n ([, domain]) => typeof domain === 'string' && domain.length > 0\n ) as [string, string][];\n\n if (!domainEntries.length) return '';\n\n const formattedDomains = domainEntries\n .map(\n ([locale, domain]) =>\n `'${locale}': { origin: '${getDomainOrigin(domain)}'; exclusive: ${isLocaleExclusiveOnDomain(locale, domains)} }`\n )\n .join('; ');\n\n return `; domains: { ${formattedDomains} }`;\n};\n\nconst zodToTsString = (schema: any): string => {\n if (!schema) return 'any';\n\n // Support both real Zod objects (_def) and serialized versions (def or nested)\n const def = schema._def ?? schema.def ?? schema;\n\n // Handle serialized type names (sometimes 'type' instead of 'typeName')\n const typeName = def.typeName ?? def.type;\n\n switch (typeName) {\n case 'ZodString':\n case 'string':\n return 'string';\n case 'ZodNumber':\n case 'number':\n return 'number';\n case 'ZodBoolean':\n case 'boolean':\n return 'boolean';\n case 'ZodNull':\n case 'null':\n return 'null';\n case 'ZodUndefined':\n case 'undefined':\n return 'undefined';\n case 'ZodArray':\n case 'array':\n return `${zodToTsString(def.type ?? def.element)}[]`;\n case 'ZodObject':\n case 'object': {\n const shape = typeof def.shape === 'function' ? def.shape() : def.shape;\n if (!shape) return 'Record<string, any>';\n\n const entries = Object.entries(shape)\n .map(([k, v]) => ` \"${k}\": ${zodToTsString(v)};`)\n .join('\\n');\n return `{\\n${entries}\\n }`;\n }\n case 'ZodOptional':\n case 'optional':\n return `${zodToTsString(def.innerType ?? def.wrapped)} | undefined`;\n case 'ZodNullable':\n case 'nullable':\n return `${zodToTsString(def.innerType ?? def.wrapped)} | null`;\n case 'ZodUnion':\n case 'union': {\n const options = def.options ?? [];\n return options.map(zodToTsString).join(' | ');\n }\n case 'ZodIntersection':\n case 'intersection':\n return `${zodToTsString(def.left)} & ${zodToTsString(def.right)}`;\n case 'ZodEnum':\n case 'enum': {\n const values = def.values ?? [];\n return values.map((v: string) => `\"${v}\"`).join(' | ');\n }\n case 'ZodLiteral':\n case 'literal': {\n const value = def.value;\n return typeof value === 'string' ? `\"${value}\"` : String(value);\n }\n default:\n return 'any';\n }\n};\n\ntype ZodToTsFns = {\n zodToTs: (schema: any, opts?: any) => { node: any };\n printNode: (node: any) => string;\n createAuxiliaryTypeStore: () => any;\n};\n\n/** Generate the content of the module augmentation file */\nconst generateTypeIndexContent = (\n typeFiles: string[],\n configuration: IntlayerConfig,\n zodToTsFns: ZodToTsFns | null\n): string => {\n const { internationalization, system, editor, routing } = configuration;\n const { moduleAugmentationDir } = system;\n const { enabled } = editor;\n const { locales, requiredLocales, strictMode, defaultLocale } =\n internationalization;\n\n let fileContent = 'import \"intlayer\";\\n';\n\n // Build dictionary refs\n const dictionariesRef = typeFiles.map((dictionaryPath) => ({\n relativePath: `./${normalizePath(relative(moduleAugmentationDir, dictionaryPath))}`,\n id: basename(dictionaryPath, extname(dictionaryPath)),\n hash: `_${getPathHash(dictionaryPath)}`,\n }));\n\n // Import all dictionaries\n for (const dictionary of dictionariesRef) {\n fileContent += `import ${dictionary.hash} from '${dictionary.relativePath}';\\n`;\n }\n fileContent += '\\n';\n\n // Dictionary map entries (id: typeof <hash>)\n const formattedDictionaryMap: string = dictionariesRef\n .map((dictionary) => ` \"${dictionary.id}\": typeof ${dictionary.hash};`)\n .join('\\n');\n\n // Ensure required ⊆ declared; if empty, default required = declared\n const declared = locales;\n const requiredSanitized = requiredLocales?.length\n ? requiredLocales.filter((requiredLocales) =>\n declared.includes(requiredLocales)\n )\n : declared;\n\n const formattedDeclaredLocales = formatLocales(declared);\n const formattedRequiredLocales = formatLocales(requiredSanitized);\n\n // Build schema registry\n const schemas = configuration.schemas ?? {};\n const formattedSchemas = Object.entries(schemas)\n .map(([key, schema]) => {\n let typeStr = 'any';\n\n if (schema) {\n try {\n if (zodToTsFns) {\n const { node } = zodToTsFns.zodToTs(schema, {\n auxiliaryTypeStore: zodToTsFns.createAuxiliaryTypeStore(),\n });\n // 133 is the kind for AnyKeyword in TypeScript\n if ((node as any).kind !== 133) {\n typeStr = zodToTsFns.printNode(node);\n } else {\n typeStr = zodToTsString(schema);\n }\n } else {\n typeStr = zodToTsString(schema);\n }\n } catch (_e) {\n // Fallback to custom string generator\n typeStr = zodToTsString(schema);\n }\n }\n return ` \"${key}\": ${typeStr};`;\n })\n .join('\\n');\n\n // Choose strict mode registry key\n const strictKey =\n strictMode === 'strict'\n ? 'strict'\n : strictMode === 'inclusive'\n ? 'inclusive'\n : 'loose';\n\n /**\n * Module augmentation that ONLY adds keys to registries.\n * No types/aliases redefined here—avoids merge conflicts.\n */\n fileContent += `declare module 'intlayer' {\\n`;\n // Dictionaries registry\n fileContent += ` interface __DictionaryRegistry {\\n${formattedDictionaryMap}\\n }\\n\\n`;\n // Locales registries\n fileContent += ` interface __DeclaredLocalesRegistry {\\n${formattedDeclaredLocales}\\n }\\n\\n`;\n fileContent += ` interface __RequiredLocalesRegistry {\\n${formattedRequiredLocales}\\n }\\n\\n`;\n // Schema registry\n fileContent += ` interface __SchemaRegistry {\\n${formattedSchemas}\\n }\\n\\n`;\n // Resolved strict mode (narrow the literal at build time)\n fileContent += ` interface __StrictModeRegistry { mode: '${strictKey}' }\\n\\n`;\n // Editor registry\n fileContent += ` interface __EditorRegistry { enabled : ${enabled} }\\n\\n`;\n // Routing registry (mode + defaultLocale narrowed to literals, rewrite rules\n // and domain map serialized as type literals)\n const routingMode = routing?.mode ?? 'prefix-no-default';\n const formattedRewriteRules = formatRewriteRules(routing?.rewrite);\n const formattedDomains = formatDomains(routing?.domains);\n fileContent += ` interface __RoutingRegistry { mode: '${routingMode}'; defaultLocale: '${defaultLocale}'${formattedRewriteRules}${formattedDomains} }\\n`;\n fileContent += `}\\n`;\n\n return fileContent;\n};\n\n/** Generate the index file merging all the types */\nexport const createModuleAugmentation = async (\n configuration: IntlayerConfig\n) => {\n const { moduleAugmentationDir, typesDir } = configuration.system;\n\n await mkdir(moduleAugmentationDir, { recursive: true });\n\n const dictionariesTypesDefinitions: string[] = await fg(\n normalizePath(`${typesDir}/*.ts`),\n { ignore: ['**/*.d.ts'] }\n );\n\n let zodToTsFns: ZodToTsFns | null = null;\n\n const hasSchemas = Object.keys(configuration.schemas ?? {}).length > 0;\n\n // zod-to-ts drags in the whole `typescript` package (~300ms import), so\n // only load it when there are schemas to convert.\n if (hasSchemas) {\n try {\n const mod = await import('zod-to-ts');\n zodToTsFns = {\n zodToTs: mod.zodToTs,\n printNode: mod.printNode,\n createAuxiliaryTypeStore: mod.createAuxiliaryTypeStore,\n };\n } catch {\n // typescript peer dep not installed (plain JS project), use fallback\n }\n }\n\n const tsContent = generateTypeIndexContent(\n dictionariesTypesDefinitions,\n configuration,\n zodToTsFns\n );\n\n const tsFilePath = join(moduleAugmentationDir, 'intlayer.d.ts');\n await writeFileIfChanged(tsFilePath, tsContent);\n};\n"],"mappings":"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"}
@@ -4,6 +4,7 @@ const require_createType_createType = require('./createType.cjs');
4
4
 
5
5
  exports.createModuleAugmentation = require_createType_createModuleAugmentation.createModuleAugmentation;
6
6
  exports.createTypes = require_createType_createType.createTypes;
7
+ exports.formatDomains = require_createType_createModuleAugmentation.formatDomains;
7
8
  exports.formatRewriteRules = require_createType_createModuleAugmentation.formatRewriteRules;
8
9
  exports.generateTypeScriptType = require_createType_createType.generateTypeScriptType;
9
10
  exports.getTypeName = require_createType_createModuleAugmentation.getTypeName;
@@ -39,6 +39,12 @@ const fingerprintSectionFineBlocks = (section) => {
39
39
  * whole section. Within a changed section a target block with no base
40
40
  * counterpart is **kept as-is** (reused) rather than deleted, so a translation
41
41
  * that has extra paragraphs never loses content.
42
+ * When `changedLines` is `undefined` the changed lines are simply unknown, so
43
+ * **every** section is inspected instead of none: aligned blocks are still
44
+ * reused (there is no way to tell which translation went stale), but blocks
45
+ * living on one side only are reported as `insert_new` / `delete`. This is
46
+ * what makes a plain "compare this document with its translation" run — one
47
+ * with no git history behind it — report anything at all.
42
48
  *
43
49
  * Section-level insertions and deletions stay whole: a brand-new section is
44
50
  * translated as one unit, and a target section with no base counterpart is
@@ -49,11 +55,20 @@ const fingerprintSectionFineBlocks = (section) => {
49
55
  * @returns The (flattened) blocks, the plan, and the segments that need translation.
50
56
  */
51
57
  const buildAlignmentPlan = ({ baseText, targetText, changedLines }) => {
52
- const changedLineNumbers = Array.isArray(changedLines) ? changedLines : [];
58
+ const hasChangedLineInformation = Array.isArray(changedLines);
59
+ const changedLineNumbers = changedLines ?? [];
53
60
  const baseSections = fingerprintBlockList(require_docReview_segmentDocument.segmentSections(baseText));
54
61
  const targetSections = fingerprintBlockList(require_docReview_segmentDocument.segmentSections(targetText));
55
62
  const sectionAlignment = require_docReview_alignBlocks.alignBaseAndTargetBlocks(baseSections, targetSections);
56
63
  const changedSectionIndexes = require_docReview_mapChangedLinesToBlocks.mapChangedLinesToBlocks(baseSections, changedLineNumbers);
64
+ /**
65
+ * Whether a section must be opened and inspected block by block.
66
+ *
67
+ * Without changed-line information every section is inspected, so comparing a
68
+ * document with its translation from scratch still reports the blocks that
69
+ * exist on one side only. With changed lines, only the touched sections are.
70
+ */
71
+ const isSectionToInspect = (sectionIndex) => !hasChangedLineInformation || changedSectionIndexes.has(sectionIndex);
57
72
  const baseBlocks = [];
58
73
  const targetBlocks = [];
59
74
  const actions = [];
@@ -79,7 +94,7 @@ const buildAlignmentPlan = ({ baseText, targetText, changedLines }) => {
79
94
  if (pair.baseIndex < 0 || pair.targetIndex === null) continue;
80
95
  const baseSection = baseSections[pair.baseIndex];
81
96
  const targetSection = targetSections[pair.targetIndex];
82
- if (!changedSectionIndexes.has(pair.baseIndex)) {
97
+ if (!isSectionToInspect(pair.baseIndex)) {
83
98
  const baseIndex = pushBaseBlock(baseSection);
84
99
  const targetIndex = pushTargetBlock(targetSection);
85
100
  actions.push({
@@ -96,10 +111,13 @@ const buildAlignmentPlan = ({ baseText, targetText, changedLines }) => {
96
111
  for (const finePair of fineAlignment) {
97
112
  if (finePair.baseIndex === -1 && finePair.targetIndex !== null) {
98
113
  const targetIndex = pushTargetBlock(targetFineBlocks[finePair.targetIndex]);
99
- actions.push({
114
+ actions.push(hasChangedLineInformation ? {
100
115
  kind: "reuse",
101
116
  baseIndex: -1,
102
117
  targetIndex
118
+ } : {
119
+ kind: "delete",
120
+ targetIndex
103
121
  });
104
122
  continue;
105
123
  }
@@ -1 +1 @@
1
- {"version":3,"file":"pipeline.cjs","names":["normalizeBlock","fingerprintBlock","segmentDocument","segmentSections","alignBaseAndTargetBlocks","mapChangedLinesToBlocks","identifySegmentsToReview"],"sources":["../../../src/docReview/pipeline.ts"],"sourcesContent":["import { alignBaseAndTargetBlocks } from './alignBlocks';\nimport { fingerprintBlock } from './fingerprintBlock';\nimport { mapChangedLinesToBlocks } from './mapChangedLinesToBlocks';\nimport { normalizeBlock } from './normalizeBlock';\nimport {\n identifySegmentsToReview,\n mergeReviewedSegments,\n type SegmentToReview,\n} from './rebuildDocument';\nimport { segmentDocument, segmentSections } from './segmentDocument';\nimport type { Block, FingerprintedBlock, PlannedAction } from './types';\n\nexport type BuildAlignmentPlanInput = {\n /** The base (source) document, used as the translation reference. */\n baseText: string;\n /** The existing target (translated) document, possibly empty. */\n targetText: string;\n /** 1-based line numbers that changed in the base document, when known. */\n changedLines: number[] | undefined;\n};\n\nexport type BuildAlignmentPlanOutput = {\n baseBlocks: FingerprintedBlock[];\n targetBlocks: FingerprintedBlock[];\n plan: AlignmentPlan;\n segmentsToReview: SegmentToReview[];\n};\n\n/** Fingerprint a list of already-segmented blocks (context = neighbours). */\nconst fingerprintBlockList = (blocks: Block[]): FingerprintedBlock[] => {\n const normalized = blocks.map(normalizeBlock);\n\n return normalized.map((block, index, array) =>\n fingerprintBlock(block, array[index - 1] ?? null, array[index + 1] ?? null)\n );\n};\n\n/**\n * Re-segment a section's own text into fine blocks, shifting their relative line\n * numbers back to absolute document positions so changed-line mapping and the\n * review report keep reporting real file coordinates.\n */\nconst fingerprintSectionFineBlocks = (section: Block): FingerprintedBlock[] => {\n const lineOffset = section.lineStart - 1;\n\n const fineBlocks = segmentDocument(section.content).map(\n (block): Block => ({\n ...block,\n lineStart: block.lineStart + lineOffset,\n lineEnd: block.lineEnd + lineOffset,\n })\n );\n\n return fingerprintBlockList(fineBlocks);\n};\n\n/**\n * Build the block-aware alignment plan between a base document and its\n * translation, in two levels.\n *\n * 1. **Sections** (heading-anchored) are aligned first. Because a document and\n * its translation share the same heading structure, this alignment is robust\n * and never drops a section just because the prose was split into a different\n * number of paragraphs.\n * 2. Only the sections **touched by a changed line** are then re-segmented into\n * fine blocks (paragraphs, code fences) and aligned within the section, so a\n * small edit re-translates only the affected paragraph(s) instead of the\n * whole section. Within a changed section a target block with no base\n * counterpart is **kept as-is** (reused) rather than deleted, so a translation\n * that has extra paragraphs never loses content.\n *\n * Section-level insertions and deletions stay whole: a brand-new section is\n * translated as one unit, and a target section with no base counterpart is\n * reported as `delete` for visibility but never dropped by the merge (see\n * {@link mergeReviewedSegments}), so a review can never lose translated content.\n *\n * @param input - The base/target texts and optional changed lines.\n * @returns The (flattened) blocks, the plan, and the segments that need translation.\n */\nexport const buildAlignmentPlan = ({\n baseText,\n targetText,\n changedLines,\n}: BuildAlignmentPlanInput): BuildAlignmentPlanOutput => {\n const changedLineNumbers = Array.isArray(changedLines) ? changedLines : [];\n\n const baseSections = fingerprintBlockList(segmentSections(baseText));\n const targetSections = fingerprintBlockList(segmentSections(targetText));\n\n const sectionAlignment = alignBaseAndTargetBlocks(\n baseSections,\n targetSections\n );\n const changedSectionIndexes = mapChangedLinesToBlocks(\n baseSections,\n changedLineNumbers\n );\n\n // Flattened blocks the plan refers to by index. Reused/deleted/inserted\n // sections contribute their whole-section block; changed sections contribute\n // their fine sub-blocks.\n const baseBlocks: FingerprintedBlock[] = [];\n const targetBlocks: FingerprintedBlock[] = [];\n const actions: PlannedAction[] = [];\n\n const pushBaseBlock = (block: FingerprintedBlock): number =>\n baseBlocks.push(block) - 1;\n const pushTargetBlock = (block: FingerprintedBlock): number =>\n targetBlocks.push(block) - 1;\n\n for (const pair of sectionAlignment) {\n // Section present only in the target → reported as `delete` for visibility,\n // but kept verbatim by the merge (the aligner may simply have failed to\n // follow a reordered section), never silently dropped.\n if (pair.baseIndex === -1 && pair.targetIndex !== null) {\n const targetIndex = pushTargetBlock(targetSections[pair.targetIndex]!);\n actions.push({ kind: 'delete', targetIndex });\n continue;\n }\n\n // Section present only in the base → brand new, translate as one unit.\n if (pair.baseIndex >= 0 && pair.targetIndex === null) {\n const baseIndex = pushBaseBlock(baseSections[pair.baseIndex]!);\n actions.push({ kind: 'insert_new', baseIndex });\n continue;\n }\n\n if (pair.baseIndex < 0 || pair.targetIndex === null) continue;\n\n const baseSection = baseSections[pair.baseIndex]!;\n const targetSection = targetSections[pair.targetIndex]!;\n\n // Unchanged section → reuse the existing translation verbatim.\n if (!changedSectionIndexes.has(pair.baseIndex)) {\n const baseIndex = pushBaseBlock(baseSection);\n const targetIndex = pushTargetBlock(targetSection);\n actions.push({ kind: 'reuse', baseIndex, targetIndex });\n continue;\n }\n\n // Changed section → align its fine blocks and review only what changed.\n const baseFineBlocks = fingerprintSectionFineBlocks(baseSection);\n const targetFineBlocks = fingerprintSectionFineBlocks(targetSection);\n const fineAlignment = alignBaseAndTargetBlocks(\n baseFineBlocks,\n targetFineBlocks\n );\n const changedFineIndexes = mapChangedLinesToBlocks(\n baseFineBlocks,\n changedLineNumbers\n );\n\n for (const finePair of fineAlignment) {\n // Target-only fine block: keep it (no data loss), do not delete.\n if (finePair.baseIndex === -1 && finePair.targetIndex !== null) {\n const targetIndex = pushTargetBlock(\n targetFineBlocks[finePair.targetIndex]!\n );\n actions.push({ kind: 'reuse', baseIndex: -1, targetIndex });\n continue;\n }\n\n // Base-only fine block: a new paragraph inside the section, translate it.\n if (finePair.baseIndex >= 0 && finePair.targetIndex === null) {\n const baseIndex = pushBaseBlock(baseFineBlocks[finePair.baseIndex]!);\n actions.push({ kind: 'insert_new', baseIndex });\n continue;\n }\n\n if (finePair.baseIndex < 0 || finePair.targetIndex === null) continue;\n\n const baseIndex = pushBaseBlock(baseFineBlocks[finePair.baseIndex]!);\n const targetIndex = pushTargetBlock(\n targetFineBlocks[finePair.targetIndex]!\n );\n\n actions.push(\n changedFineIndexes.has(finePair.baseIndex)\n ? { kind: 'review', baseIndex, targetIndex }\n : { kind: 'reuse', baseIndex, targetIndex }\n );\n }\n }\n\n const plan = { actions };\n\n const { segmentsToReview } = identifySegmentsToReview({\n baseBlocks,\n targetBlocks,\n plan,\n });\n\n return { baseBlocks, targetBlocks, plan, segmentsToReview };\n};\n\nexport type { SegmentToReview };\nexport { mergeReviewedSegments };\n"],"mappings":";;;;;;;;;;AA6BA,MAAM,wBAAwB,WAA0C;CAGtE,OAFmB,OAAO,IAAIA,+CAEd,CAAC,CAAC,KAAK,OAAO,OAAO,UACnCC,oDAAiB,OAAO,MAAM,QAAQ,MAAM,MAAM,MAAM,QAAQ,MAAM,IAAI,CAC5E;AACF;;;;;;AAOA,MAAM,gCAAgC,YAAyC;CAC7E,MAAM,aAAa,QAAQ,YAAY;CAEvC,MAAM,aAAaC,kDAAgB,QAAQ,OAAO,CAAC,CAAC,KACjD,WAAkB;EACjB,GAAG;EACH,WAAW,MAAM,YAAY;EAC7B,SAAS,MAAM,UAAU;CAC3B,EACF;CAEA,OAAO,qBAAqB,UAAU;AACxC;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,MAAa,sBAAsB,EACjC,UACA,YACA,mBACuD;CACvD,MAAM,qBAAqB,MAAM,QAAQ,YAAY,IAAI,eAAe,CAAC;CAEzE,MAAM,eAAe,qBAAqBC,kDAAgB,QAAQ,CAAC;CACnE,MAAM,iBAAiB,qBAAqBA,kDAAgB,UAAU,CAAC;CAEvE,MAAM,mBAAmBC,uDACvB,cACA,cACF;CACA,MAAM,wBAAwBC,kEAC5B,cACA,kBACF;CAKA,MAAM,aAAmC,CAAC;CAC1C,MAAM,eAAqC,CAAC;CAC5C,MAAM,UAA2B,CAAC;CAElC,MAAM,iBAAiB,UACrB,WAAW,KAAK,KAAK,IAAI;CAC3B,MAAM,mBAAmB,UACvB,aAAa,KAAK,KAAK,IAAI;CAE7B,KAAK,MAAM,QAAQ,kBAAkB;EAInC,IAAI,KAAK,cAAc,MAAM,KAAK,gBAAgB,MAAM;GACtD,MAAM,cAAc,gBAAgB,eAAe,KAAK,YAAa;GACrE,QAAQ,KAAK;IAAE,MAAM;IAAU;GAAY,CAAC;GAC5C;EACF;EAGA,IAAI,KAAK,aAAa,KAAK,KAAK,gBAAgB,MAAM;GACpD,MAAM,YAAY,cAAc,aAAa,KAAK,UAAW;GAC7D,QAAQ,KAAK;IAAE,MAAM;IAAc;GAAU,CAAC;GAC9C;EACF;EAEA,IAAI,KAAK,YAAY,KAAK,KAAK,gBAAgB,MAAM;EAErD,MAAM,cAAc,aAAa,KAAK;EACtC,MAAM,gBAAgB,eAAe,KAAK;EAG1C,IAAI,CAAC,sBAAsB,IAAI,KAAK,SAAS,GAAG;GAC9C,MAAM,YAAY,cAAc,WAAW;GAC3C,MAAM,cAAc,gBAAgB,aAAa;GACjD,QAAQ,KAAK;IAAE,MAAM;IAAS;IAAW;GAAY,CAAC;GACtD;EACF;EAGA,MAAM,iBAAiB,6BAA6B,WAAW;EAC/D,MAAM,mBAAmB,6BAA6B,aAAa;EACnE,MAAM,gBAAgBD,uDACpB,gBACA,gBACF;EACA,MAAM,qBAAqBC,kEACzB,gBACA,kBACF;EAEA,KAAK,MAAM,YAAY,eAAe;GAEpC,IAAI,SAAS,cAAc,MAAM,SAAS,gBAAgB,MAAM;IAC9D,MAAM,cAAc,gBAClB,iBAAiB,SAAS,YAC5B;IACA,QAAQ,KAAK;KAAE,MAAM;KAAS,WAAW;KAAI;IAAY,CAAC;IAC1D;GACF;GAGA,IAAI,SAAS,aAAa,KAAK,SAAS,gBAAgB,MAAM;IAC5D,MAAM,YAAY,cAAc,eAAe,SAAS,UAAW;IACnE,QAAQ,KAAK;KAAE,MAAM;KAAc;IAAU,CAAC;IAC9C;GACF;GAEA,IAAI,SAAS,YAAY,KAAK,SAAS,gBAAgB,MAAM;GAE7D,MAAM,YAAY,cAAc,eAAe,SAAS,UAAW;GACnE,MAAM,cAAc,gBAClB,iBAAiB,SAAS,YAC5B;GAEA,QAAQ,KACN,mBAAmB,IAAI,SAAS,SAAS,IACrC;IAAE,MAAM;IAAU;IAAW;GAAY,IACzC;IAAE,MAAM;IAAS;IAAW;GAAY,CAC9C;EACF;CACF;CAEA,MAAM,OAAO,EAAE,QAAQ;CAEvB,MAAM,EAAE,qBAAqBC,2DAAyB;EACpD;EACA;EACA;CACF,CAAC;CAED,OAAO;EAAE;EAAY;EAAc;EAAM;CAAiB;AAC5D"}
1
+ {"version":3,"file":"pipeline.cjs","names":["normalizeBlock","fingerprintBlock","segmentDocument","segmentSections","alignBaseAndTargetBlocks","mapChangedLinesToBlocks","identifySegmentsToReview"],"sources":["../../../src/docReview/pipeline.ts"],"sourcesContent":["import { alignBaseAndTargetBlocks } from './alignBlocks';\nimport { fingerprintBlock } from './fingerprintBlock';\nimport { mapChangedLinesToBlocks } from './mapChangedLinesToBlocks';\nimport { normalizeBlock } from './normalizeBlock';\nimport {\n identifySegmentsToReview,\n mergeReviewedSegments,\n type SegmentToReview,\n} from './rebuildDocument';\nimport { segmentDocument, segmentSections } from './segmentDocument';\nimport type {\n AlignmentPlan,\n Block,\n FingerprintedBlock,\n PlannedAction,\n} from './types';\n\nexport type BuildAlignmentPlanInput = {\n /** The base (source) document, used as the translation reference. */\n baseText: string;\n /** The existing target (translated) document, possibly empty. */\n targetText: string;\n /**\n * 1-based line numbers that changed in the base document.\n *\n * `undefined` means the changed lines are unknown (no git history available):\n * every section is then compared block by block. An empty array means nothing\n * changed, and every aligned block is reused.\n */\n changedLines: number[] | undefined;\n};\n\nexport type BuildAlignmentPlanOutput = {\n baseBlocks: FingerprintedBlock[];\n targetBlocks: FingerprintedBlock[];\n plan: AlignmentPlan;\n segmentsToReview: SegmentToReview[];\n};\n\n/** Fingerprint a list of already-segmented blocks (context = neighbours). */\nconst fingerprintBlockList = (blocks: Block[]): FingerprintedBlock[] => {\n const normalized = blocks.map(normalizeBlock);\n\n return normalized.map((block, index, array) =>\n fingerprintBlock(block, array[index - 1] ?? null, array[index + 1] ?? null)\n );\n};\n\n/**\n * Re-segment a section's own text into fine blocks, shifting their relative line\n * numbers back to absolute document positions so changed-line mapping and the\n * review report keep reporting real file coordinates.\n */\nconst fingerprintSectionFineBlocks = (section: Block): FingerprintedBlock[] => {\n const lineOffset = section.lineStart - 1;\n\n const fineBlocks = segmentDocument(section.content).map(\n (block): Block => ({\n ...block,\n lineStart: block.lineStart + lineOffset,\n lineEnd: block.lineEnd + lineOffset,\n })\n );\n\n return fingerprintBlockList(fineBlocks);\n};\n\n/**\n * Build the block-aware alignment plan between a base document and its\n * translation, in two levels.\n *\n * 1. **Sections** (heading-anchored) are aligned first. Because a document and\n * its translation share the same heading structure, this alignment is robust\n * and never drops a section just because the prose was split into a different\n * number of paragraphs.\n * 2. Only the sections **touched by a changed line** are then re-segmented into\n * fine blocks (paragraphs, code fences) and aligned within the section, so a\n * small edit re-translates only the affected paragraph(s) instead of the\n * whole section. Within a changed section a target block with no base\n * counterpart is **kept as-is** (reused) rather than deleted, so a translation\n * that has extra paragraphs never loses content.\n * When `changedLines` is `undefined` the changed lines are simply unknown, so\n * **every** section is inspected instead of none: aligned blocks are still\n * reused (there is no way to tell which translation went stale), but blocks\n * living on one side only are reported as `insert_new` / `delete`. This is\n * what makes a plain \"compare this document with its translation\" run — one\n * with no git history behind it — report anything at all.\n *\n * Section-level insertions and deletions stay whole: a brand-new section is\n * translated as one unit, and a target section with no base counterpart is\n * reported as `delete` for visibility but never dropped by the merge (see\n * {@link mergeReviewedSegments}), so a review can never lose translated content.\n *\n * @param input - The base/target texts and optional changed lines.\n * @returns The (flattened) blocks, the plan, and the segments that need translation.\n */\nexport const buildAlignmentPlan = ({\n baseText,\n targetText,\n changedLines,\n}: BuildAlignmentPlanInput): BuildAlignmentPlanOutput => {\n // `undefined` means \"which lines changed is unknown\" (no git history to read),\n // which is not the same as \"no line changed\": the former must still compare the\n // two documents, the latter must reuse everything.\n const hasChangedLineInformation = Array.isArray(changedLines);\n const changedLineNumbers = changedLines ?? [];\n\n const baseSections = fingerprintBlockList(segmentSections(baseText));\n const targetSections = fingerprintBlockList(segmentSections(targetText));\n\n const sectionAlignment = alignBaseAndTargetBlocks(\n baseSections,\n targetSections\n );\n const changedSectionIndexes = mapChangedLinesToBlocks(\n baseSections,\n changedLineNumbers\n );\n\n /**\n * Whether a section must be opened and inspected block by block.\n *\n * Without changed-line information every section is inspected, so comparing a\n * document with its translation from scratch still reports the blocks that\n * exist on one side only. With changed lines, only the touched sections are.\n */\n const isSectionToInspect = (sectionIndex: number): boolean =>\n !hasChangedLineInformation || changedSectionIndexes.has(sectionIndex);\n\n // Flattened blocks the plan refers to by index. Reused/deleted/inserted\n // sections contribute their whole-section block; changed sections contribute\n // their fine sub-blocks.\n const baseBlocks: FingerprintedBlock[] = [];\n const targetBlocks: FingerprintedBlock[] = [];\n const actions: PlannedAction[] = [];\n\n const pushBaseBlock = (block: FingerprintedBlock): number =>\n baseBlocks.push(block) - 1;\n const pushTargetBlock = (block: FingerprintedBlock): number =>\n targetBlocks.push(block) - 1;\n\n for (const pair of sectionAlignment) {\n // Section present only in the target → reported as `delete` for visibility,\n // but kept verbatim by the merge (the aligner may simply have failed to\n // follow a reordered section), never silently dropped.\n if (pair.baseIndex === -1 && pair.targetIndex !== null) {\n const targetIndex = pushTargetBlock(targetSections[pair.targetIndex]!);\n actions.push({ kind: 'delete', targetIndex });\n continue;\n }\n\n // Section present only in the base → brand new, translate as one unit.\n if (pair.baseIndex >= 0 && pair.targetIndex === null) {\n const baseIndex = pushBaseBlock(baseSections[pair.baseIndex]!);\n actions.push({ kind: 'insert_new', baseIndex });\n continue;\n }\n\n if (pair.baseIndex < 0 || pair.targetIndex === null) continue;\n\n const baseSection = baseSections[pair.baseIndex]!;\n const targetSection = targetSections[pair.targetIndex]!;\n\n // Unchanged section → reuse the existing translation verbatim.\n if (!isSectionToInspect(pair.baseIndex)) {\n const baseIndex = pushBaseBlock(baseSection);\n const targetIndex = pushTargetBlock(targetSection);\n actions.push({ kind: 'reuse', baseIndex, targetIndex });\n continue;\n }\n\n // Inspected section → align its fine blocks and review only what changed.\n const baseFineBlocks = fingerprintSectionFineBlocks(baseSection);\n const targetFineBlocks = fingerprintSectionFineBlocks(targetSection);\n const fineAlignment = alignBaseAndTargetBlocks(\n baseFineBlocks,\n targetFineBlocks\n );\n const changedFineIndexes = mapChangedLinesToBlocks(\n baseFineBlocks,\n changedLineNumbers\n );\n\n for (const finePair of fineAlignment) {\n // Target-only fine block. Its content is kept either way — `delete` is\n // reported for visibility only and the merge keeps it verbatim (see\n // {@link mergeReviewedSegments}).\n // With changed lines the author's edit is known precisely, so an unmatched\n // target block is most likely the translator's own prose split and stays\n // silent. Without them the whole document is being compared, and such a\n // block is exactly the divergence the report exists to surface.\n if (finePair.baseIndex === -1 && finePair.targetIndex !== null) {\n const targetIndex = pushTargetBlock(\n targetFineBlocks[finePair.targetIndex]!\n );\n actions.push(\n hasChangedLineInformation\n ? { kind: 'reuse', baseIndex: -1, targetIndex }\n : { kind: 'delete', targetIndex }\n );\n continue;\n }\n\n // Base-only fine block: a new paragraph inside the section, translate it.\n if (finePair.baseIndex >= 0 && finePair.targetIndex === null) {\n const baseIndex = pushBaseBlock(baseFineBlocks[finePair.baseIndex]!);\n actions.push({ kind: 'insert_new', baseIndex });\n continue;\n }\n\n if (finePair.baseIndex < 0 || finePair.targetIndex === null) continue;\n\n const baseIndex = pushBaseBlock(baseFineBlocks[finePair.baseIndex]!);\n const targetIndex = pushTargetBlock(\n targetFineBlocks[finePair.targetIndex]!\n );\n\n actions.push(\n changedFineIndexes.has(finePair.baseIndex)\n ? { kind: 'review', baseIndex, targetIndex }\n : { kind: 'reuse', baseIndex, targetIndex }\n );\n }\n }\n\n const plan = { actions };\n\n const { segmentsToReview } = identifySegmentsToReview({\n baseBlocks,\n targetBlocks,\n plan,\n });\n\n return { baseBlocks, targetBlocks, plan, segmentsToReview };\n};\n\nexport type { SegmentToReview };\nexport { mergeReviewedSegments };\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"reviewReport.cjs","names":["buildAlignmentPlan","colorize","ANSIColors","colorizeNumber"],"sources":["../../../src/docReview/reviewReport.ts"],"sourcesContent":["import * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, colorizeNumber } from '@intlayer/config/logger';\nimport { buildAlignmentPlan } from './pipeline';\n\n/**\n * The kind of change detected for a block when reviewing a translation against\n * its base document.\n *\n * - `review`: the base block changed and its translation must be updated.\n * - `insert_new`: the base block has no translation yet and must be added.\n * - `delete`: the target block no longer exists in the base and should be removed.\n */\nexport type ReviewBlockAction = 'review' | 'insert_new' | 'delete';\n\n/** A 1-based, inclusive line range within a document. */\nexport type LineRange = {\n start: number;\n end: number;\n};\n\n/**\n * A single block that diverges between the base document and its translation.\n *\n * This is the unit an external translator (AI client, human, or agent) needs to\n * act on: it carries the base content to translate, the existing translation to\n * update, and where each lives so the change can be located in the file.\n */\nexport type ReviewReportBlock = {\n /** What should happen to this block. */\n action: ReviewBlockAction;\n /** Line range of the block in the base document (omitted for pure deletions). */\n baseLineRange?: LineRange;\n /** Line range of the block in the target document (omitted for new insertions). */\n targetLineRange?: LineRange;\n /** Raw markdown of the base block (omitted for pure deletions). */\n baseContent?: string;\n /** Existing translation of the block (omitted for new insertions). */\n targetContent?: string;\n};\n\nexport type ReviewReportSummary = {\n /** Number of blocks reused as-is (unchanged translation). */\n reuse: number;\n /** Number of blocks whose translation must be updated. */\n review: number;\n /** Number of blocks missing a translation. */\n insertNew: number;\n /** Number of stale target blocks to delete. */\n delete: number;\n};\n\nexport type ReviewReport = {\n /** Only the blocks that diverge (review, insert_new, delete). */\n blocks: ReviewReportBlock[];\n /** Counts per action over the whole document, including reused blocks. */\n summary: ReviewReportSummary;\n};\n\nexport type BuildReviewReportInput = {\n /** The base (source) document, used as the translation reference. */\n baseText: string;\n /** The existing target (translated) document, possibly empty. */\n targetText: string;\n /**\n * 1-based line numbers that changed in the base document. When omitted, only\n * inserted and deleted blocks are reported (no aligned block is flagged for\n * review since there is no way to know which ones changed).\n */\n changedLines?: number[];\n};\n\n/**\n * Compare a base markdown document with its translation and report only the\n * blocks that need attention, with their line ranges and content.\n *\n * Reusable across the CLI (`doc review --log`), the backend (comparing two\n * translation contents stored in database), and agents that generate the\n * missing translations.\n *\n * @param input - The base/target texts and optional changed lines.\n * @returns The divergent blocks and a per-action summary.\n */\nexport const buildReviewReport = ({\n baseText,\n targetText,\n changedLines,\n}: BuildReviewReportInput): ReviewReport => {\n const { baseBlocks, targetBlocks, plan } = buildAlignmentPlan({\n baseText,\n targetText,\n changedLines,\n });\n\n const summary: ReviewReportSummary = {\n reuse: 0,\n review: 0,\n insertNew: 0,\n delete: 0,\n };\n\n const blocks: ReviewReportBlock[] = [];\n\n for (const [actionIndex, action] of plan.actions.entries()) {\n if (action.kind === 'reuse') {\n summary.reuse += 1;\n continue;\n }\n\n if (action.kind === 'review') {\n summary.review += 1;\n const baseBlock = baseBlocks[action.baseIndex];\n\n if (!baseBlock) continue;\n\n const targetBlock =\n action.targetIndex !== null ? targetBlocks[action.targetIndex] : null;\n\n blocks.push({\n action: 'review',\n baseLineRange: { start: baseBlock.lineStart, end: baseBlock.lineEnd },\n targetLineRange: targetBlock\n ? { start: targetBlock.lineStart, end: targetBlock.lineEnd }\n : undefined,\n baseContent: baseBlock.content,\n targetContent: targetBlock?.content,\n });\n continue;\n }\n\n if (action.kind === 'insert_new') {\n summary.insertNew += 1;\n const baseBlock = baseBlocks[action.baseIndex];\n\n if (!baseBlock) continue;\n\n // Find the target line after which the new block should be inserted:\n // scan backwards for the last action that produced a target block.\n let insertAfterLine: number | undefined;\n for (let prevIndex = actionIndex - 1; prevIndex >= 0; prevIndex--) {\n const prevAction = plan.actions[prevIndex];\n if (!prevAction) continue;\n\n let targetIdx: number | null = null;\n if (prevAction.kind === 'reuse') targetIdx = prevAction.targetIndex;\n else if (\n prevAction.kind === 'review' &&\n prevAction.targetIndex !== null\n )\n targetIdx = prevAction.targetIndex;\n else if (prevAction.kind === 'delete')\n targetIdx = prevAction.targetIndex;\n\n if (targetIdx !== null) {\n const prevTargetBlock = targetBlocks[targetIdx];\n if (prevTargetBlock) {\n insertAfterLine = prevTargetBlock.lineEnd + 1;\n break;\n }\n }\n }\n\n blocks.push({\n action: 'insert_new',\n baseLineRange: { start: baseBlock.lineStart, end: baseBlock.lineEnd },\n targetLineRange:\n insertAfterLine !== undefined\n ? { start: insertAfterLine, end: insertAfterLine }\n : undefined,\n baseContent: baseBlock.content,\n });\n continue;\n }\n\n if (action.kind === 'delete') {\n summary.delete += 1;\n const targetBlock = targetBlocks[action.targetIndex];\n\n if (!targetBlock) continue;\n\n blocks.push({\n action: 'delete',\n targetLineRange: {\n start: targetBlock.lineStart,\n end: targetBlock.lineEnd,\n },\n targetContent: targetBlock.content,\n });\n }\n }\n\n return { blocks, summary };\n};\n\n/** Render a line range as a blue colored string, or an orange dash when absent. */\nconst colorizeLineRange = (range?: LineRange): string => {\n if (!range) return colorize('—', ANSIColors.ORANGE);\n const rangeStr =\n range.start === range.end\n ? `L${range.start}`\n : `L${range.start}-${range.end}`;\n return colorize(rangeStr, ANSIColors.BLUE);\n};\n\n/**\n * Colorize a count with green when zero (nothing to do) and a caller-supplied\n * non-zero color (orange for warnings, red for destructive actions).\n */\nconst colorizeCount = (\n count: number,\n nonZeroColor: (typeof ANSIColors)[keyof typeof ANSIColors]\n): string =>\n colorizeNumber(count, {\n zero: ANSIColors.GREEN,\n one: nonZeroColor,\n two: nonZeroColor,\n few: nonZeroColor,\n many: nonZeroColor,\n other: nonZeroColor,\n });\n\n/**\n * Render a {@link ReviewReport} as a human and agent readable log.\n *\n * Each divergent block is printed with its action, the base/target line ranges,\n * the base content to translate, and the existing translation to update.\n *\n * @param report - The report to format.\n * @param options - Optional labels for the base and target locales.\n * @returns A multi-line string describing every block that needs attention.\n */\nexport const formatReviewReport = (\n report: ReviewReport,\n options?: { baseLabel?: string; targetLabel?: string }\n): string => {\n const baseLabel = options?.baseLabel ?? 'base';\n const targetLabel = options?.targetLabel ?? 'target';\n\n const { summary, blocks } = report;\n\n // Build the summary header piece-by-piece so each count uses its own color.\n const header = [\n colorize('Review report: ', ANSIColors.ORANGE),\n colorizeCount(blocks.length, ANSIColors.ORANGE),\n colorize(' block(s) need attention (review=', ANSIColors.ORANGE),\n colorizeCount(summary.review, ANSIColors.ORANGE),\n colorize(', new=', ANSIColors.ORANGE),\n colorizeCount(summary.insertNew, ANSIColors.ORANGE),\n colorize(', delete=', ANSIColors.ORANGE),\n colorizeCount(summary.delete, ANSIColors.RED),\n colorize(', reuse=', ANSIColors.ORANGE),\n colorizeNumber(summary.reuse),\n colorize(').', ANSIColors.ORANGE),\n ].join('');\n\n if (blocks.length === 0) {\n return `${header}\\n${colorize('No changes needed.', ANSIColors.GREEN)}`;\n }\n\n const sections = blocks.map((block, index) => {\n const lines: string[] = [];\n\n // Block header: each token colored individually.\n const blockHeader = [\n colorize('--- Block ', ANSIColors.ORANGE),\n colorizeNumber(index + 1),\n colorize('/', ANSIColors.ORANGE),\n colorizeNumber(blocks.length),\n colorize(` [${block.action}] `, ANSIColors.ORANGE),\n baseLabel,\n colorize(' ', ANSIColors.ORANGE),\n colorizeLineRange(block.baseLineRange),\n colorize(' → ', ANSIColors.ORANGE),\n targetLabel,\n colorize(' ', ANSIColors.ORANGE),\n colorizeLineRange(block.targetLineRange),\n colorize(' ---', ANSIColors.ORANGE),\n ].join('');\n\n lines.push(blockHeader);\n\n if (block.baseContent !== undefined) {\n lines.push(colorize(`[${baseLabel}]`, ANSIColors.BEIGE));\n lines.push(colorize(block.baseContent.trimEnd(), ANSIColors.GREY));\n }\n\n if (block.targetContent !== undefined) {\n lines.push(colorize(`[${targetLabel}]`, ANSIColors.BEIGE));\n lines.push(colorize(block.targetContent.trimEnd(), ANSIColors.GREY));\n } else if (block.action === 'insert_new') {\n lines.push(colorize(`[${targetLabel}]`, ANSIColors.BEIGE));\n lines.push(colorize('(missing — to be translated)', ANSIColors.ORANGE));\n }\n\n return lines.join('\\n');\n });\n\n const editingNote = colorize(\n 'Tip: start editing from the last block — working bottom-up keeps earlier line numbers accurate.',\n ANSIColors.GREY\n );\n\n return [header, '', ...sections, '', editingNote].join('\\n');\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"reviewReport.cjs","names":["buildAlignmentPlan","colorize","ANSIColors","colorizeNumber"],"sources":["../../../src/docReview/reviewReport.ts"],"sourcesContent":["import * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, colorizeNumber } from '@intlayer/config/logger';\nimport { buildAlignmentPlan } from './pipeline';\n\n/**\n * The kind of change detected for a block when reviewing a translation against\n * its base document.\n *\n * - `review`: the base block changed and its translation must be updated.\n * - `insert_new`: the base block has no translation yet and must be added.\n * - `delete`: the target block no longer exists in the base and should be removed.\n */\nexport type ReviewBlockAction = 'review' | 'insert_new' | 'delete';\n\n/** A 1-based, inclusive line range within a document. */\nexport type LineRange = {\n start: number;\n end: number;\n};\n\n/**\n * A single block that diverges between the base document and its translation.\n *\n * This is the unit an external translator (AI client, human, or agent) needs to\n * act on: it carries the base content to translate, the existing translation to\n * update, and where each lives so the change can be located in the file.\n */\nexport type ReviewReportBlock = {\n /** What should happen to this block. */\n action: ReviewBlockAction;\n /** Line range of the block in the base document (omitted for pure deletions). */\n baseLineRange?: LineRange;\n /** Line range of the block in the target document (omitted for new insertions). */\n targetLineRange?: LineRange;\n /** Raw markdown of the base block (omitted for pure deletions). */\n baseContent?: string;\n /** Existing translation of the block (omitted for new insertions). */\n targetContent?: string;\n};\n\nexport type ReviewReportSummary = {\n /** Number of blocks reused as-is (unchanged translation). */\n reuse: number;\n /** Number of blocks whose translation must be updated. */\n review: number;\n /** Number of blocks missing a translation. */\n insertNew: number;\n /** Number of stale target blocks to delete. */\n delete: number;\n};\n\nexport type ReviewReport = {\n /** Only the blocks that diverge (review, insert_new, delete). */\n blocks: ReviewReportBlock[];\n /** Counts per action over the whole document, including reused blocks. */\n summary: ReviewReportSummary;\n};\n\nexport type BuildReviewReportInput = {\n /** The base (source) document, used as the translation reference. */\n baseText: string;\n /** The existing target (translated) document, possibly empty. */\n targetText: string;\n /**\n * 1-based line numbers that changed in the base document.\n *\n * When omitted the whole document is compared, and only inserted and deleted\n * blocks are reported (no aligned block is flagged for review since there is\n * no way to know which ones changed). An empty array means nothing changed,\n * and no block at all is reported.\n */\n changedLines?: number[];\n};\n\n/**\n * Compare a base markdown document with its translation and report only the\n * blocks that need attention, with their line ranges and content.\n *\n * Reusable across the CLI (`doc review --log`), the backend (comparing two\n * translation contents stored in database), and agents that generate the\n * missing translations.\n *\n * @param input - The base/target texts and optional changed lines.\n * @returns The divergent blocks and a per-action summary.\n */\nexport const buildReviewReport = ({\n baseText,\n targetText,\n changedLines,\n}: BuildReviewReportInput): ReviewReport => {\n const { baseBlocks, targetBlocks, plan } = buildAlignmentPlan({\n baseText,\n targetText,\n changedLines,\n });\n\n const summary: ReviewReportSummary = {\n reuse: 0,\n review: 0,\n insertNew: 0,\n delete: 0,\n };\n\n const blocks: ReviewReportBlock[] = [];\n\n for (const [actionIndex, action] of plan.actions.entries()) {\n if (action.kind === 'reuse') {\n summary.reuse += 1;\n continue;\n }\n\n if (action.kind === 'review') {\n summary.review += 1;\n const baseBlock = baseBlocks[action.baseIndex];\n\n if (!baseBlock) continue;\n\n const targetBlock =\n action.targetIndex !== null ? targetBlocks[action.targetIndex] : null;\n\n blocks.push({\n action: 'review',\n baseLineRange: { start: baseBlock.lineStart, end: baseBlock.lineEnd },\n targetLineRange: targetBlock\n ? { start: targetBlock.lineStart, end: targetBlock.lineEnd }\n : undefined,\n baseContent: baseBlock.content,\n targetContent: targetBlock?.content,\n });\n continue;\n }\n\n if (action.kind === 'insert_new') {\n summary.insertNew += 1;\n const baseBlock = baseBlocks[action.baseIndex];\n\n if (!baseBlock) continue;\n\n // Find the target line after which the new block should be inserted:\n // scan backwards for the last action that produced a target block.\n let insertAfterLine: number | undefined;\n for (let prevIndex = actionIndex - 1; prevIndex >= 0; prevIndex--) {\n const prevAction = plan.actions[prevIndex];\n if (!prevAction) continue;\n\n let targetIdx: number | null = null;\n if (prevAction.kind === 'reuse') targetIdx = prevAction.targetIndex;\n else if (\n prevAction.kind === 'review' &&\n prevAction.targetIndex !== null\n )\n targetIdx = prevAction.targetIndex;\n else if (prevAction.kind === 'delete')\n targetIdx = prevAction.targetIndex;\n\n if (targetIdx !== null) {\n const prevTargetBlock = targetBlocks[targetIdx];\n if (prevTargetBlock) {\n insertAfterLine = prevTargetBlock.lineEnd + 1;\n break;\n }\n }\n }\n\n blocks.push({\n action: 'insert_new',\n baseLineRange: { start: baseBlock.lineStart, end: baseBlock.lineEnd },\n targetLineRange:\n insertAfterLine !== undefined\n ? { start: insertAfterLine, end: insertAfterLine }\n : undefined,\n baseContent: baseBlock.content,\n });\n continue;\n }\n\n if (action.kind === 'delete') {\n summary.delete += 1;\n const targetBlock = targetBlocks[action.targetIndex];\n\n if (!targetBlock) continue;\n\n blocks.push({\n action: 'delete',\n targetLineRange: {\n start: targetBlock.lineStart,\n end: targetBlock.lineEnd,\n },\n targetContent: targetBlock.content,\n });\n }\n }\n\n return { blocks, summary };\n};\n\n/** Render a line range as a blue colored string, or an orange dash when absent. */\nconst colorizeLineRange = (range?: LineRange): string => {\n if (!range) return colorize('—', ANSIColors.ORANGE);\n const rangeStr =\n range.start === range.end\n ? `L${range.start}`\n : `L${range.start}-${range.end}`;\n return colorize(rangeStr, ANSIColors.BLUE);\n};\n\n/**\n * Colorize a count with green when zero (nothing to do) and a caller-supplied\n * non-zero color (orange for warnings, red for destructive actions).\n */\nconst colorizeCount = (\n count: number,\n nonZeroColor: (typeof ANSIColors)[keyof typeof ANSIColors]\n): string =>\n colorizeNumber(count, {\n zero: ANSIColors.GREEN,\n one: nonZeroColor,\n two: nonZeroColor,\n few: nonZeroColor,\n many: nonZeroColor,\n other: nonZeroColor,\n });\n\n/**\n * Render a {@link ReviewReport} as a human and agent readable log.\n *\n * Each divergent block is printed with its action, the base/target line ranges,\n * the base content to translate, and the existing translation to update.\n *\n * @param report - The report to format.\n * @param options - Optional labels for the base and target locales.\n * @returns A multi-line string describing every block that needs attention.\n */\nexport const formatReviewReport = (\n report: ReviewReport,\n options?: { baseLabel?: string; targetLabel?: string }\n): string => {\n const baseLabel = options?.baseLabel ?? 'base';\n const targetLabel = options?.targetLabel ?? 'target';\n\n const { summary, blocks } = report;\n\n // Build the summary header piece-by-piece so each count uses its own color.\n const header = [\n colorize('Review report: ', ANSIColors.ORANGE),\n colorizeCount(blocks.length, ANSIColors.ORANGE),\n colorize(' block(s) need attention (review=', ANSIColors.ORANGE),\n colorizeCount(summary.review, ANSIColors.ORANGE),\n colorize(', new=', ANSIColors.ORANGE),\n colorizeCount(summary.insertNew, ANSIColors.ORANGE),\n colorize(', delete=', ANSIColors.ORANGE),\n colorizeCount(summary.delete, ANSIColors.RED),\n colorize(', reuse=', ANSIColors.ORANGE),\n colorizeNumber(summary.reuse),\n colorize(').', ANSIColors.ORANGE),\n ].join('');\n\n if (blocks.length === 0) {\n return `${header}\\n${colorize('No changes needed.', ANSIColors.GREEN)}`;\n }\n\n const sections = blocks.map((block, index) => {\n const lines: string[] = [];\n\n // Block header: each token colored individually.\n const blockHeader = [\n colorize('--- Block ', ANSIColors.ORANGE),\n colorizeNumber(index + 1),\n colorize('/', ANSIColors.ORANGE),\n colorizeNumber(blocks.length),\n colorize(` [${block.action}] `, ANSIColors.ORANGE),\n baseLabel,\n colorize(' ', ANSIColors.ORANGE),\n colorizeLineRange(block.baseLineRange),\n colorize(' → ', ANSIColors.ORANGE),\n targetLabel,\n colorize(' ', ANSIColors.ORANGE),\n colorizeLineRange(block.targetLineRange),\n colorize(' ---', ANSIColors.ORANGE),\n ].join('');\n\n lines.push(blockHeader);\n\n if (block.baseContent !== undefined) {\n lines.push(colorize(`[${baseLabel}]`, ANSIColors.BEIGE));\n lines.push(colorize(block.baseContent.trimEnd(), ANSIColors.GREY));\n }\n\n if (block.targetContent !== undefined) {\n lines.push(colorize(`[${targetLabel}]`, ANSIColors.BEIGE));\n lines.push(colorize(block.targetContent.trimEnd(), ANSIColors.GREY));\n } else if (block.action === 'insert_new') {\n lines.push(colorize(`[${targetLabel}]`, ANSIColors.BEIGE));\n lines.push(colorize('(missing — to be translated)', ANSIColors.ORANGE));\n }\n\n return lines.join('\\n');\n });\n\n const editingNote = colorize(\n 'Tip: start editing from the last block — working bottom-up keeps earlier line numbers accurate.',\n ANSIColors.GREY\n );\n\n return [header, '', ...sections, '', editingNote].join('\\n');\n};\n"],"mappings":"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"}
@@ -35,7 +35,7 @@ const ensureProxyOrMiddleware = async (rootDir, srcDir, isNext16) => {
35
35
  await require_init_utils_fileSystem.writeFileToRoot(rootDir, targetPath, content);
36
36
  (0, _intlayer_config_logger.logger)(`${_intlayer_config_logger.v} Created ${(0, _intlayer_config_logger.colorizePath)(targetPath)} for locale detection`);
37
37
  };
38
- /** Logs the outcome of a layout/page provider-wrap transform. */
38
+ /** Logs the outcome of a layout provider-wrap transform. */
39
39
  const logTransformOutcome = (filePath, result) => {
40
40
  switch (result.status) {
41
41
  case "wrapped":
@@ -65,8 +65,10 @@ const createIfMissing = async (rootDir, filePath, content, description) => {
65
65
  };
66
66
  /**
67
67
  * Next.js App Router adapter. Scaffolds the locale proxy/middleware, restructures
68
- * the app into a `[locale]` segment, and wraps the layout/page with the Intlayer
69
- * providers — all idempotently and without overwriting recognizable user code.
68
+ * the app into a `[locale]` segment, and wraps the layout with the unified
69
+ * Intlayer provider — all idempotently and without overwriting recognizable
70
+ * user code. Pages are never wrapped: `IntlayerProvider` in the locale layout
71
+ * covers them.
70
72
  */
71
73
  const nextAppRouterAdapter = {
72
74
  name: "Next.js App Router",
@@ -95,9 +97,7 @@ const nextAppRouterAdapter = {
95
97
  const existingLayout = await require_init_frameworkSetup_nextAppRouter_detect.findAppFile(rootDir, localeDir, "layout");
96
98
  if (existingLayout) await transformExistingFile(rootDir, existingLayout, require_init_frameworkSetup_nextAppRouter_transforms.wrapLayoutWithProvider);
97
99
  else await createIfMissing(rootDir, (0, node_path.join)(localeDir, `layout.${scriptExtension}`), useTypeScript ? require_init_frameworkSetup_nextAppRouter_templates.LOCALE_LAYOUT_TEMPLATE_TS : require_init_frameworkSetup_nextAppRouter_templates.LOCALE_LAYOUT_TEMPLATE_JS, "locale layout");
98
- const existingPage = await require_init_frameworkSetup_nextAppRouter_detect.findAppFile(rootDir, localeDir, "page");
99
- if (existingPage) await transformExistingFile(rootDir, existingPage, require_init_frameworkSetup_nextAppRouter_transforms.wrapPageWithProvider);
100
- else await createIfMissing(rootDir, (0, node_path.join)(localeDir, `page.${scriptExtension}`), useTypeScript ? require_init_frameworkSetup_nextAppRouter_templates.LOCALE_PAGE_TEMPLATE_TS : require_init_frameworkSetup_nextAppRouter_templates.LOCALE_PAGE_TEMPLATE_JS, "locale page");
100
+ if (!await require_init_frameworkSetup_nextAppRouter_detect.findAppFile(rootDir, localeDir, "page")) await createIfMissing(rootDir, (0, node_path.join)(localeDir, `page.${scriptExtension}`), useTypeScript ? require_init_frameworkSetup_nextAppRouter_templates.LOCALE_PAGE_TEMPLATE_TS : require_init_frameworkSetup_nextAppRouter_templates.LOCALE_PAGE_TEMPLATE_JS, "locale page");
101
101
  }
102
102
  };
103
103
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["exists","join","v","colorizePath","PROXY_TEMPLATE","MIDDLEWARE_TEMPLATE","ensureDirectory","writeFileToRoot","x","readFileFromRoot","detectNextAppDir","isVersionAtLeast","colorize","ANSIColors","restructureAppIntoLocale","ROOT_LAYOUT_TEMPLATE_TS","ROOT_LAYOUT_TEMPLATE_JS","findAppFile","wrapLayoutWithProvider","LOCALE_LAYOUT_TEMPLATE_TS","LOCALE_LAYOUT_TEMPLATE_JS","wrapPageWithProvider","LOCALE_PAGE_TEMPLATE_TS","LOCALE_PAGE_TEMPLATE_JS"],"sources":["../../../../../src/init/frameworkSetup/nextAppRouter/index.ts"],"sourcesContent":["import { join } from 'node:path';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, colorizePath, logger, v, x } from '@intlayer/config/logger';\nimport {\n ensureDirectory,\n exists,\n readFileFromRoot,\n writeFileToRoot,\n} from '../../utils/fileSystem';\nimport type { FrameworkAdapter, FrameworkSetupContext } from '../types';\nimport { detectNextAppDir, findAppFile, isVersionAtLeast } from './detect';\nimport { restructureAppIntoLocale } from './restructure';\nimport {\n LOCALE_LAYOUT_TEMPLATE_JS,\n LOCALE_LAYOUT_TEMPLATE_TS,\n LOCALE_PAGE_TEMPLATE_JS,\n LOCALE_PAGE_TEMPLATE_TS,\n MIDDLEWARE_TEMPLATE,\n PROXY_TEMPLATE,\n ROOT_LAYOUT_TEMPLATE_JS,\n ROOT_LAYOUT_TEMPLATE_TS,\n} from './templates';\nimport {\n type TransformResult,\n wrapLayoutWithProvider,\n wrapPageWithProvider,\n} from './transforms';\n\n/**\n * Creates the locale-detection proxy (Next >= 16) or middleware (Next < 16),\n * but only when neither already exists, so a user's custom file is never\n * touched. Placed next to the app directory's parent (`src/` when present).\n */\nconst ensureProxyOrMiddleware = async (\n rootDir: string,\n srcDir: string,\n isNext16: boolean\n): Promise<void> => {\n const baseDir = srcDir || '.';\n const proxyCandidates = [\n 'proxy.ts',\n 'proxy.js',\n 'middleware.ts',\n 'middleware.js',\n ];\n\n for (const candidate of proxyCandidates) {\n if (await exists(rootDir, join(baseDir, candidate))) {\n logger(\n `${v} ${colorizePath(join(baseDir, candidate))} already exists, leaving it untouched`\n );\n return;\n }\n }\n\n const fileName = isNext16 ? 'proxy.ts' : 'middleware.ts';\n const content = isNext16 ? PROXY_TEMPLATE : MIDDLEWARE_TEMPLATE;\n const targetPath = join(baseDir, fileName);\n\n if (srcDir) await ensureDirectory(rootDir, srcDir);\n await writeFileToRoot(rootDir, targetPath, content);\n logger(`${v} Created ${colorizePath(targetPath)} for locale detection`);\n};\n\n/** Logs the outcome of a layout/page provider-wrap transform. */\nconst logTransformOutcome = (\n filePath: string,\n result: TransformResult\n): void => {\n switch (result.status) {\n case 'wrapped':\n logger(`${v} Added Intlayer provider to ${colorizePath(filePath)}`);\n break;\n case 'already':\n logger(\n `${v} ${colorizePath(filePath)} already wraps the Intlayer provider`\n );\n break;\n case 'skipped-client':\n logger(\n `${x} ${colorizePath(filePath)} is a client component — add the Intlayer provider manually.`,\n { level: 'warn' }\n );\n break;\n case 'skipped':\n logger(\n `${x} Could not safely add the Intlayer provider to ${colorizePath(filePath)} — please wire it manually.`,\n { level: 'warn' }\n );\n break;\n }\n};\n\n/** Transforms an existing file in place, writing it back only when changed. */\nconst transformExistingFile = async (\n rootDir: string,\n filePath: string,\n transform: (code: string) => TransformResult\n): Promise<void> => {\n const code = await readFileFromRoot(rootDir, filePath);\n const result = transform(code);\n if (result.status === 'wrapped') {\n await writeFileToRoot(rootDir, filePath, result.code);\n }\n logTransformOutcome(filePath, result);\n};\n\n/** Creates a file from a template only when it does not already exist. */\nconst createIfMissing = async (\n rootDir: string,\n filePath: string,\n content: string,\n description: string\n): Promise<boolean> => {\n if (await exists(rootDir, filePath)) return false;\n await writeFileToRoot(rootDir, filePath, content);\n logger(`${v} Created ${description} ${colorizePath(filePath)}`);\n return true;\n};\n\n/**\n * Next.js App Router adapter. Scaffolds the locale proxy/middleware, restructures\n * the app into a `[locale]` segment, and wraps the layout/page with the Intlayer\n * providers — all idempotently and without overwriting recognizable user code.\n */\nexport const nextAppRouterAdapter: FrameworkAdapter = {\n name: 'Next.js App Router',\n\n detect: async ({ rootDir, allDeps }) => {\n if (!allDeps.next) return false;\n return (await detectNextAppDir(rootDir)) !== null;\n },\n\n setup: async ({ rootDir, allDeps, useTypeScript }: FrameworkSetupContext) => {\n const appDirInfo = await detectNextAppDir(rootDir);\n if (!appDirInfo) return;\n\n const { appDir, srcDir } = appDirInfo;\n const isNext16 = isVersionAtLeast(allDeps.next, 16);\n const scriptExtension = useTypeScript ? 'tsx' : 'jsx';\n\n logger(\n colorize('Setting up Next.js App Router integration...', ANSIColors.CYAN)\n );\n\n // 1. Locale detection proxy / middleware (create only when absent).\n await ensureProxyOrMiddleware(rootDir, srcDir, isNext16);\n\n // 2. Move routable files under a `[locale]` segment (idempotent).\n const restructureResult = await restructureAppIntoLocale(rootDir, appDir);\n if (restructureResult.status === 'moved') {\n logger(\n `${v} Restructured app routes under ${colorizePath(join(appDir, '[locale]'))}: ${restructureResult.movedEntries\n .map((entry) => colorize(entry, ANSIColors.MAGENTA))\n .join(', ')}`\n );\n } else if (restructureResult.status === 'already-structured') {\n logger(\n `${v} ${colorizePath(join(appDir, restructureResult.localeSegment))} already exists, skipping restructure`\n );\n }\n\n // When the app already uses a different locale-prefix scheme (e.g. an\n // `[...locale]` / `[[...locale]]` catch-all), respect the user's routing:\n // don't scaffold a second, conflicting `[locale]` segment.\n if (\n restructureResult.status === 'already-structured' &&\n restructureResult.localeSegment !== '[locale]'\n ) {\n logger(\n `${v} Detected existing locale routing (${colorize(restructureResult.localeSegment, ANSIColors.MAGENTA)}), skipping locale layout/page scaffold`\n );\n return;\n }\n\n const localeDir = join(appDir, '[locale]');\n await ensureDirectory(rootDir, localeDir);\n\n // 3. Minimal pass-through root layout (the `[locale]` layout owns `<html>`).\n await createIfMissing(\n rootDir,\n join(appDir, `layout.${scriptExtension}`),\n useTypeScript ? ROOT_LAYOUT_TEMPLATE_TS : ROOT_LAYOUT_TEMPLATE_JS,\n 'root layout'\n );\n\n // 4. Locale layout — transform an existing one, else scaffold from template.\n const existingLayout = await findAppFile(rootDir, localeDir, 'layout');\n if (existingLayout) {\n await transformExistingFile(\n rootDir,\n existingLayout,\n wrapLayoutWithProvider\n );\n } else {\n await createIfMissing(\n rootDir,\n join(localeDir, `layout.${scriptExtension}`),\n useTypeScript ? LOCALE_LAYOUT_TEMPLATE_TS : LOCALE_LAYOUT_TEMPLATE_JS,\n 'locale layout'\n );\n }\n\n // 5. Locale page — transform an existing one, else scaffold from template.\n const existingPage = await findAppFile(rootDir, localeDir, 'page');\n if (existingPage) {\n await transformExistingFile(rootDir, existingPage, wrapPageWithProvider);\n } else {\n await createIfMissing(\n rootDir,\n join(localeDir, `page.${scriptExtension}`),\n useTypeScript ? LOCALE_PAGE_TEMPLATE_TS : LOCALE_PAGE_TEMPLATE_JS,\n 'locale page'\n );\n }\n },\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["exists","join","v","colorizePath","PROXY_TEMPLATE","MIDDLEWARE_TEMPLATE","ensureDirectory","writeFileToRoot","x","readFileFromRoot","detectNextAppDir","isVersionAtLeast","colorize","ANSIColors","restructureAppIntoLocale","ROOT_LAYOUT_TEMPLATE_TS","ROOT_LAYOUT_TEMPLATE_JS","findAppFile","wrapLayoutWithProvider","LOCALE_LAYOUT_TEMPLATE_TS","LOCALE_LAYOUT_TEMPLATE_JS","LOCALE_PAGE_TEMPLATE_TS","LOCALE_PAGE_TEMPLATE_JS"],"sources":["../../../../../src/init/frameworkSetup/nextAppRouter/index.ts"],"sourcesContent":["import { join } from 'node:path';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, colorizePath, logger, v, x } from '@intlayer/config/logger';\nimport {\n ensureDirectory,\n exists,\n readFileFromRoot,\n writeFileToRoot,\n} from '../../utils/fileSystem';\nimport type { FrameworkAdapter, FrameworkSetupContext } from '../types';\nimport { detectNextAppDir, findAppFile, isVersionAtLeast } from './detect';\nimport { restructureAppIntoLocale } from './restructure';\nimport {\n LOCALE_LAYOUT_TEMPLATE_JS,\n LOCALE_LAYOUT_TEMPLATE_TS,\n LOCALE_PAGE_TEMPLATE_JS,\n LOCALE_PAGE_TEMPLATE_TS,\n MIDDLEWARE_TEMPLATE,\n PROXY_TEMPLATE,\n ROOT_LAYOUT_TEMPLATE_JS,\n ROOT_LAYOUT_TEMPLATE_TS,\n} from './templates';\nimport { type TransformResult, wrapLayoutWithProvider } from './transforms';\n\n/**\n * Creates the locale-detection proxy (Next >= 16) or middleware (Next < 16),\n * but only when neither already exists, so a user's custom file is never\n * touched. Placed next to the app directory's parent (`src/` when present).\n */\nconst ensureProxyOrMiddleware = async (\n rootDir: string,\n srcDir: string,\n isNext16: boolean\n): Promise<void> => {\n const baseDir = srcDir || '.';\n const proxyCandidates = [\n 'proxy.ts',\n 'proxy.js',\n 'middleware.ts',\n 'middleware.js',\n ];\n\n for (const candidate of proxyCandidates) {\n if (await exists(rootDir, join(baseDir, candidate))) {\n logger(\n `${v} ${colorizePath(join(baseDir, candidate))} already exists, leaving it untouched`\n );\n return;\n }\n }\n\n const fileName = isNext16 ? 'proxy.ts' : 'middleware.ts';\n const content = isNext16 ? PROXY_TEMPLATE : MIDDLEWARE_TEMPLATE;\n const targetPath = join(baseDir, fileName);\n\n if (srcDir) await ensureDirectory(rootDir, srcDir);\n await writeFileToRoot(rootDir, targetPath, content);\n logger(`${v} Created ${colorizePath(targetPath)} for locale detection`);\n};\n\n/** Logs the outcome of a layout provider-wrap transform. */\nconst logTransformOutcome = (\n filePath: string,\n result: TransformResult\n): void => {\n switch (result.status) {\n case 'wrapped':\n logger(`${v} Added Intlayer provider to ${colorizePath(filePath)}`);\n break;\n case 'already':\n logger(\n `${v} ${colorizePath(filePath)} already wraps the Intlayer provider`\n );\n break;\n case 'skipped-client':\n logger(\n `${x} ${colorizePath(filePath)} is a client component — add the Intlayer provider manually.`,\n { level: 'warn' }\n );\n break;\n case 'skipped':\n logger(\n `${x} Could not safely add the Intlayer provider to ${colorizePath(filePath)} — please wire it manually.`,\n { level: 'warn' }\n );\n break;\n }\n};\n\n/** Transforms an existing file in place, writing it back only when changed. */\nconst transformExistingFile = async (\n rootDir: string,\n filePath: string,\n transform: (code: string) => TransformResult\n): Promise<void> => {\n const code = await readFileFromRoot(rootDir, filePath);\n const result = transform(code);\n if (result.status === 'wrapped') {\n await writeFileToRoot(rootDir, filePath, result.code);\n }\n logTransformOutcome(filePath, result);\n};\n\n/** Creates a file from a template only when it does not already exist. */\nconst createIfMissing = async (\n rootDir: string,\n filePath: string,\n content: string,\n description: string\n): Promise<boolean> => {\n if (await exists(rootDir, filePath)) return false;\n await writeFileToRoot(rootDir, filePath, content);\n logger(`${v} Created ${description} ${colorizePath(filePath)}`);\n return true;\n};\n\n/**\n * Next.js App Router adapter. Scaffolds the locale proxy/middleware, restructures\n * the app into a `[locale]` segment, and wraps the layout with the unified\n * Intlayer provider — all idempotently and without overwriting recognizable\n * user code. Pages are never wrapped: `IntlayerProvider` in the locale layout\n * covers them.\n */\nexport const nextAppRouterAdapter: FrameworkAdapter = {\n name: 'Next.js App Router',\n\n detect: async ({ rootDir, allDeps }) => {\n if (!allDeps.next) return false;\n return (await detectNextAppDir(rootDir)) !== null;\n },\n\n setup: async ({ rootDir, allDeps, useTypeScript }: FrameworkSetupContext) => {\n const appDirInfo = await detectNextAppDir(rootDir);\n if (!appDirInfo) return;\n\n const { appDir, srcDir } = appDirInfo;\n const isNext16 = isVersionAtLeast(allDeps.next, 16);\n const scriptExtension = useTypeScript ? 'tsx' : 'jsx';\n\n logger(\n colorize('Setting up Next.js App Router integration...', ANSIColors.CYAN)\n );\n\n // 1. Locale detection proxy / middleware (create only when absent).\n await ensureProxyOrMiddleware(rootDir, srcDir, isNext16);\n\n // 2. Move routable files under a `[locale]` segment (idempotent).\n const restructureResult = await restructureAppIntoLocale(rootDir, appDir);\n if (restructureResult.status === 'moved') {\n logger(\n `${v} Restructured app routes under ${colorizePath(join(appDir, '[locale]'))}: ${restructureResult.movedEntries\n .map((entry) => colorize(entry, ANSIColors.MAGENTA))\n .join(', ')}`\n );\n } else if (restructureResult.status === 'already-structured') {\n logger(\n `${v} ${colorizePath(join(appDir, restructureResult.localeSegment))} already exists, skipping restructure`\n );\n }\n\n // When the app already uses a different locale-prefix scheme (e.g. an\n // `[...locale]` / `[[...locale]]` catch-all), respect the user's routing:\n // don't scaffold a second, conflicting `[locale]` segment.\n if (\n restructureResult.status === 'already-structured' &&\n restructureResult.localeSegment !== '[locale]'\n ) {\n logger(\n `${v} Detected existing locale routing (${colorize(restructureResult.localeSegment, ANSIColors.MAGENTA)}), skipping locale layout/page scaffold`\n );\n return;\n }\n\n const localeDir = join(appDir, '[locale]');\n await ensureDirectory(rootDir, localeDir);\n\n // 3. Minimal pass-through root layout (the `[locale]` layout owns `<html>`).\n await createIfMissing(\n rootDir,\n join(appDir, `layout.${scriptExtension}`),\n useTypeScript ? ROOT_LAYOUT_TEMPLATE_TS : ROOT_LAYOUT_TEMPLATE_JS,\n 'root layout'\n );\n\n // 4. Locale layout — transform an existing one, else scaffold from template.\n const existingLayout = await findAppFile(rootDir, localeDir, 'layout');\n if (existingLayout) {\n await transformExistingFile(\n rootDir,\n existingLayout,\n wrapLayoutWithProvider\n );\n } else {\n await createIfMissing(\n rootDir,\n join(localeDir, `layout.${scriptExtension}`),\n useTypeScript ? LOCALE_LAYOUT_TEMPLATE_TS : LOCALE_LAYOUT_TEMPLATE_JS,\n 'locale layout'\n );\n }\n\n // 5. Locale page — only scaffolded when absent. An existing page needs no\n // transform: the locale layout's `IntlayerProvider` already covers it.\n const existingPage = await findAppFile(rootDir, localeDir, 'page');\n if (!existingPage) {\n await createIfMissing(\n rootDir,\n join(localeDir, `page.${scriptExtension}`),\n useTypeScript ? LOCALE_PAGE_TEMPLATE_TS : LOCALE_PAGE_TEMPLATE_JS,\n 'locale page'\n );\n }\n },\n};\n"],"mappings":"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"}
@@ -32,8 +32,14 @@ const ROOT_LAYOUT_TEMPLATE_JS = `const RootLayout = ({ children }) => <>{childre
32
32
 
33
33
  export default RootLayout;
34
34
  `;
35
- /** Locale layout (`app/[locale]/layout.tsx`) providing `<html>` + client provider. */
36
- const LOCALE_LAYOUT_TEMPLATE_TS = `import { type NextLayoutIntlayer, IntlayerClientProvider } from "next-intlayer";
35
+ /**
36
+ * Locale layout (`app/[locale]/layout.tsx`) providing `<html>` + the unified
37
+ * provider. `IntlayerProvider` seeds both the server context (read by server
38
+ * hooks and by any page below, without per-page wrapping) and the client
39
+ * provider in one mount.
40
+ */
41
+ const LOCALE_LAYOUT_TEMPLATE_TS = `import { type NextLayoutIntlayer } from "next-intlayer";
42
+ import { IntlayerProvider } from "next-intlayer/server";
37
43
  import { getHTMLTextDir } from "intlayer";
38
44
 
39
45
  export { generateStaticParams } from "next-intlayer";
@@ -44,9 +50,7 @@ const LocaleLayout: NextLayoutIntlayer = async ({ children, params }) => {
44
50
  return (
45
51
  <html lang={locale} dir={getHTMLTextDir(locale)}>
46
52
  <body>
47
- <IntlayerClientProvider locale={locale}>
48
- {children}
49
- </IntlayerClientProvider>
53
+ <IntlayerProvider locale={locale}>{children}</IntlayerProvider>
50
54
  </body>
51
55
  </html>
52
56
  );
@@ -54,7 +58,7 @@ const LocaleLayout: NextLayoutIntlayer = async ({ children, params }) => {
54
58
 
55
59
  export default LocaleLayout;
56
60
  `;
57
- const LOCALE_LAYOUT_TEMPLATE_JS = `import { IntlayerClientProvider } from "next-intlayer";
61
+ const LOCALE_LAYOUT_TEMPLATE_JS = `import { IntlayerProvider } from "next-intlayer/server";
58
62
  import { getHTMLTextDir } from "intlayer";
59
63
 
60
64
  export { generateStaticParams } from "next-intlayer";
@@ -65,9 +69,7 @@ const LocaleLayout = async ({ children, params }) => {
65
69
  return (
66
70
  <html lang={locale} dir={getHTMLTextDir(locale)}>
67
71
  <body>
68
- <IntlayerClientProvider locale={locale}>
69
- {children}
70
- </IntlayerClientProvider>
72
+ <IntlayerProvider locale={locale}>{children}</IntlayerProvider>
71
73
  </body>
72
74
  </html>
73
75
  );
@@ -1 +1 @@
1
- {"version":3,"file":"templates.cjs","names":[],"sources":["../../../../../src/init/frameworkSetup/nextAppRouter/templates.ts"],"sourcesContent":["/**\n * File templates for a fresh Next.js App Router integration. These are only\n * written when the target file does not exist (or, for the locale layout/page,\n * when the app is effectively empty) — they never overwrite user code.\n */\n\n/** Next.js >= 16 locale-detection proxy (`src/proxy.ts`). */\nexport const PROXY_TEMPLATE = `export { intlayerProxy as proxy } from \"next-intlayer/proxy\";\n\nexport const config = {\n matcher:\n \"/((?!api|static|assets|robots|sitemap|sw|service-worker|manifest|.*\\\\\\\\..*|_next).*)\",\n};\n`;\n\n/** Next.js < 16 locale-detection middleware (`src/middleware.ts`). */\nexport const MIDDLEWARE_TEMPLATE = `export { intlayerMiddleware as middleware } from \"next-intlayer/middleware\";\n\nexport const config = {\n matcher:\n \"/((?!api|static|assets|robots|sitemap|sw|service-worker|manifest|.*\\\\\\\\..*|_next).*)\",\n};\n`;\n\n/** Minimal pass-through root layout (`app/layout.tsx`), lets `[locale]/layout` own `<html>`. */\nexport const ROOT_LAYOUT_TEMPLATE_TS = `import type { PropsWithChildren, FC } from \"react\";\n\nconst RootLayout: FC<PropsWithChildren> = ({ children }) => <>{children}</>;\n\nexport default RootLayout;\n`;\n\nexport const ROOT_LAYOUT_TEMPLATE_JS = `const RootLayout = ({ children }) => <>{children}</>;\n\nexport default RootLayout;\n`;\n\n/** Locale layout (`app/[locale]/layout.tsx`) providing `<html>` + client provider. */\nexport const LOCALE_LAYOUT_TEMPLATE_TS = `import { type NextLayoutIntlayer, IntlayerClientProvider } from \"next-intlayer\";\nimport { getHTMLTextDir } from \"intlayer\";\n\nexport { generateStaticParams } from \"next-intlayer\";\n\nconst LocaleLayout: NextLayoutIntlayer = async ({ children, params }) => {\n const { locale } = await params;\n\n return (\n <html lang={locale} dir={getHTMLTextDir(locale)}>\n <body>\n <IntlayerClientProvider locale={locale}>\n {children}\n </IntlayerClientProvider>\n </body>\n </html>\n );\n};\n\nexport default LocaleLayout;\n`;\n\nexport const LOCALE_LAYOUT_TEMPLATE_JS = `import { IntlayerClientProvider } from \"next-intlayer\";\nimport { getHTMLTextDir } from \"intlayer\";\n\nexport { generateStaticParams } from \"next-intlayer\";\n\nconst LocaleLayout = async ({ children, params }) => {\n const { locale } = await params;\n\n return (\n <html lang={locale} dir={getHTMLTextDir(locale)}>\n <body>\n <IntlayerClientProvider locale={locale}>\n {children}\n </IntlayerClientProvider>\n </body>\n </html>\n );\n};\n\nexport default LocaleLayout;\n`;\n\n/**\n * Locale home page (`app/[locale]/page.tsx`). Self-contained starter that does\n * not depend on an example content declaration — add Intlayer content (a\n * `*.content.ts` file consumed via `useIntlayer`) as the next step.\n */\nexport const LOCALE_PAGE_TEMPLATE_TS = `import { type NextPageIntlayer } from \"next-intlayer\";\n\nconst Page: NextPageIntlayer = async ({ params }) => {\n const { locale } = await params;\n\n return (\n <main>\n <h1>{locale}</h1>\n <p>Get started by editing this page.</p>\n </main>\n );\n};\n\nexport default Page;\n`;\n\nexport const LOCALE_PAGE_TEMPLATE_JS = `const Page = async ({ params }) => {\n const { locale } = await params;\n\n return (\n <main>\n <h1>{locale}</h1>\n <p>Get started by editing this page.</p>\n </main>\n );\n};\n\nexport default Page;\n`;\n"],"mappings":";;;;;;;;AAOA,MAAa,iBAAiB;;;;;;;;AAS9B,MAAa,sBAAsB;;;;;;;;AASnC,MAAa,0BAA0B;;;;;;AAOvC,MAAa,0BAA0B;;;;;AAMvC,MAAa,4BAA4B;;;;;;;;;;;;;;;;;;;;;AAsBzC,MAAa,4BAA4B;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BzC,MAAa,0BAA0B;;;;;;;;;;;;;;;AAgBvC,MAAa,0BAA0B"}
1
+ {"version":3,"file":"templates.cjs","names":[],"sources":["../../../../../src/init/frameworkSetup/nextAppRouter/templates.ts"],"sourcesContent":["/**\n * File templates for a fresh Next.js App Router integration. These are only\n * written when the target file does not exist (or, for the locale layout/page,\n * when the app is effectively empty) — they never overwrite user code.\n */\n\n/** Next.js >= 16 locale-detection proxy (`src/proxy.ts`). */\nexport const PROXY_TEMPLATE = `export { intlayerProxy as proxy } from \"next-intlayer/proxy\";\n\nexport const config = {\n matcher:\n \"/((?!api|static|assets|robots|sitemap|sw|service-worker|manifest|.*\\\\\\\\..*|_next).*)\",\n};\n`;\n\n/** Next.js < 16 locale-detection middleware (`src/middleware.ts`). */\nexport const MIDDLEWARE_TEMPLATE = `export { intlayerMiddleware as middleware } from \"next-intlayer/middleware\";\n\nexport const config = {\n matcher:\n \"/((?!api|static|assets|robots|sitemap|sw|service-worker|manifest|.*\\\\\\\\..*|_next).*)\",\n};\n`;\n\n/** Minimal pass-through root layout (`app/layout.tsx`), lets `[locale]/layout` own `<html>`. */\nexport const ROOT_LAYOUT_TEMPLATE_TS = `import type { PropsWithChildren, FC } from \"react\";\n\nconst RootLayout: FC<PropsWithChildren> = ({ children }) => <>{children}</>;\n\nexport default RootLayout;\n`;\n\nexport const ROOT_LAYOUT_TEMPLATE_JS = `const RootLayout = ({ children }) => <>{children}</>;\n\nexport default RootLayout;\n`;\n\n/**\n * Locale layout (`app/[locale]/layout.tsx`) providing `<html>` + the unified\n * provider. `IntlayerProvider` seeds both the server context (read by server\n * hooks and by any page below, without per-page wrapping) and the client\n * provider in one mount.\n */\nexport const LOCALE_LAYOUT_TEMPLATE_TS = `import { type NextLayoutIntlayer } from \"next-intlayer\";\nimport { IntlayerProvider } from \"next-intlayer/server\";\nimport { getHTMLTextDir } from \"intlayer\";\n\nexport { generateStaticParams } from \"next-intlayer\";\n\nconst LocaleLayout: NextLayoutIntlayer = async ({ children, params }) => {\n const { locale } = await params;\n\n return (\n <html lang={locale} dir={getHTMLTextDir(locale)}>\n <body>\n <IntlayerProvider locale={locale}>{children}</IntlayerProvider>\n </body>\n </html>\n );\n};\n\nexport default LocaleLayout;\n`;\n\nexport const LOCALE_LAYOUT_TEMPLATE_JS = `import { IntlayerProvider } from \"next-intlayer/server\";\nimport { getHTMLTextDir } from \"intlayer\";\n\nexport { generateStaticParams } from \"next-intlayer\";\n\nconst LocaleLayout = async ({ children, params }) => {\n const { locale } = await params;\n\n return (\n <html lang={locale} dir={getHTMLTextDir(locale)}>\n <body>\n <IntlayerProvider locale={locale}>{children}</IntlayerProvider>\n </body>\n </html>\n );\n};\n\nexport default LocaleLayout;\n`;\n\n/**\n * Locale home page (`app/[locale]/page.tsx`). Self-contained starter that does\n * not depend on an example content declaration — add Intlayer content (a\n * `*.content.ts` file consumed via `useIntlayer`) as the next step.\n */\nexport const LOCALE_PAGE_TEMPLATE_TS = `import { type NextPageIntlayer } from \"next-intlayer\";\n\nconst Page: NextPageIntlayer = async ({ params }) => {\n const { locale } = await params;\n\n return (\n <main>\n <h1>{locale}</h1>\n <p>Get started by editing this page.</p>\n </main>\n );\n};\n\nexport default Page;\n`;\n\nexport const LOCALE_PAGE_TEMPLATE_JS = `const Page = async ({ params }) => {\n const { locale } = await params;\n\n return (\n <main>\n <h1>{locale}</h1>\n <p>Get started by editing this page.</p>\n </main>\n );\n};\n\nexport default Page;\n`;\n"],"mappings":";;;;;;;;AAOA,MAAa,iBAAiB;;;;;;;;AAS9B,MAAa,sBAAsB;;;;;;;;AASnC,MAAa,0BAA0B;;;;;;AAOvC,MAAa,0BAA0B;;;;;;;;;;AAWvC,MAAa,4BAA4B;;;;;;;;;;;;;;;;;;;;AAqBzC,MAAa,4BAA4B;;;;;;;;;;;;;;;;;;;;;;;;AAyBzC,MAAa,0BAA0B;;;;;;;;;;;;;;;AAgBvC,MAAa,0BAA0B"}