vite-intlayer 9.3.2 → 9.4.0-canary.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +1 -1
  2. package/dist/esm/index.mjs +3 -1
  3. package/dist/esm/intlayerChunkPlugin.mjs +181 -0
  4. package/dist/esm/intlayerChunkPlugin.mjs.map +1 -0
  5. package/dist/esm/intlayerMinifyPlugin.mjs +16 -15
  6. package/dist/esm/intlayerMinifyPlugin.mjs.map +1 -1
  7. package/dist/esm/intlayerNitroHandler.mjs +17 -7
  8. package/dist/esm/intlayerNitroHandler.mjs.map +1 -1
  9. package/dist/esm/intlayerOptimizePlugin.mjs +6 -5
  10. package/dist/esm/intlayerOptimizePlugin.mjs.map +1 -1
  11. package/dist/esm/intlayerPlugin.mjs +4 -0
  12. package/dist/esm/intlayerPlugin.mjs.map +1 -1
  13. package/dist/esm/intlayerPreloadPlugin.mjs +88 -0
  14. package/dist/esm/intlayerPreloadPlugin.mjs.map +1 -0
  15. package/dist/esm/intlayerProxyHandler.mjs +449 -0
  16. package/dist/esm/intlayerProxyHandler.mjs.map +1 -0
  17. package/dist/esm/intlayerProxyPlugin.mjs +10 -406
  18. package/dist/esm/intlayerProxyPlugin.mjs.map +1 -1
  19. package/dist/esm/intlayerPrunePlugin.mjs +5 -13
  20. package/dist/esm/intlayerPrunePlugin.mjs.map +1 -1
  21. package/dist/types/index.d.ts +3 -1
  22. package/dist/types/intlayerChunkPlugin.d.ts +88 -0
  23. package/dist/types/intlayerChunkPlugin.d.ts.map +1 -0
  24. package/dist/types/intlayerMinifyPlugin.d.ts +5 -1
  25. package/dist/types/intlayerMinifyPlugin.d.ts.map +1 -1
  26. package/dist/types/intlayerNitroHandler.d.ts +1 -1
  27. package/dist/types/intlayerNitroHandler.d.ts.map +1 -1
  28. package/dist/types/intlayerPlugin.d.ts.map +1 -1
  29. package/dist/types/intlayerPreloadPlugin.d.ts +34 -0
  30. package/dist/types/intlayerPreloadPlugin.d.ts.map +1 -0
  31. package/dist/types/intlayerProxyHandler.d.ts +50 -0
  32. package/dist/types/intlayerProxyHandler.d.ts.map +1 -0
  33. package/dist/types/intlayerProxyPlugin.d.ts +4 -11
  34. package/dist/types/intlayerProxyPlugin.d.ts.map +1 -1
  35. package/dist/types/intlayerPrunePlugin.d.ts +5 -0
  36. package/dist/types/intlayerPrunePlugin.d.ts.map +1 -1
  37. package/package.json +10 -10
@@ -1 +1 @@
1
- {"version":3,"file":"intlayerPrunePlugin.mjs","names":[],"sources":["../../src/intlayerPrunePlugin.ts"],"sourcesContent":["import { join } from 'node:path';\nimport type { PruneContext } from '@intlayer/babel';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport {\n colorize,\n colorizeKey,\n colorizeNumber,\n getAppLogger,\n} from '@intlayer/config/logger';\nimport { formatPath, runOnce } from '@intlayer/engine/utils';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { PluginOption } from 'vite';\n\n// Dictionary JSON types\n\n/**\n * A compiled intlayer translation node – used in static dictionaries where\n * all locales are bundled in a single file.\n *\n * Structure:\n * { nodeType: \"translation\", translation: { en: { field1, field2 }, fr: {…} } }\n */\ntype TranslationNode = {\n nodeType: 'translation';\n translation: Record<string, unknown>;\n};\n\n/**\n * Compiled intlayer dictionary as stored in a `.json` file.\n *\n * Two content shapes are supported (see `pruneStaticDictionaryContent` and\n * `pruneDynamicDictionaryContent`).\n */\ntype CompiledDictionaryJson = {\n key: string;\n content: TranslationNode | Record<string, unknown>;\n locale?: string; // present in per-locale dynamic dictionary files\n localIds?: string[];\n [extraKey: string]: unknown;\n};\n\n// Type guards\n\nconst isTranslationNode = (value: unknown): value is TranslationNode =>\n typeof value === 'object' &&\n value !== null &&\n (value as Record<string, unknown>).nodeType === 'translation' &&\n typeof (value as Record<string, unknown>).translation === 'object';\n\nconst isPlainRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\n// Pruning logic\n\n/**\n * Result of a prune attempt.\n *\n * `wasRecognised` is `false` when the content structure did not match any\n * known shape – the caller should log a warning and skip the file entirely.\n */\ntype PruneResult = {\n prunedDictionary: CompiledDictionaryJson;\n wasRecognised: boolean;\n};\n\n/**\n * Prune a **static** dictionary JSON (all locales in one file).\n *\n * Shape A – the whole `content` is a single translation node:\n * { nodeType: \"translation\", translation: { en: { f1, f2 }, fr: { f1, f2 } } }\n * → prune the field objects inside each locale.\n *\n * Shape B – `content` is a plain record of fields, each being a translated node:\n * { field1: { nodeType: \"translation\", … }, field2: { … } }\n * → prune the top-level keys of `content` directly.\n *\n * Returns `{ wasRecognised: false }` when neither shape matches.\n */\nconst pruneStaticDictionaryContent = (\n dictionary: CompiledDictionaryJson,\n usedFieldNames: Set<string>\n): PruneResult => {\n const { content } = dictionary;\n\n // Shape A\n if (isTranslationNode(content)) {\n const firstLocaleValue = Object.values(content.translation)[0];\n const localeValuesAreRecords = isPlainRecord(firstLocaleValue);\n\n if (localeValuesAreRecords) {\n const prunedTranslationByLocale: Record<string, unknown> = {};\n\n for (const [localeName, localeContent] of Object.entries(\n content.translation\n )) {\n if (!isPlainRecord(localeContent)) {\n // Locale value is not a record (e.g. a primitive) – keep as-is\n prunedTranslationByLocale[localeName] = localeContent;\n continue;\n }\n\n const prunedLocaleFields: Record<string, unknown> = {};\n for (const [fieldName, fieldValue] of Object.entries(localeContent)) {\n if (usedFieldNames.has(fieldName)) {\n prunedLocaleFields[fieldName] = fieldValue;\n }\n }\n prunedTranslationByLocale[localeName] = prunedLocaleFields;\n }\n\n return {\n prunedDictionary: {\n ...dictionary,\n content: { ...content, translation: prunedTranslationByLocale },\n },\n wasRecognised: true,\n };\n }\n }\n\n // Shape B\n if (isPlainRecord(content) && !isTranslationNode(content)) {\n const prunedContentFields: Record<string, unknown> = {};\n\n for (const [fieldName, fieldValue] of Object.entries(content)) {\n if (usedFieldNames.has(fieldName)) {\n prunedContentFields[fieldName] = fieldValue;\n }\n }\n\n return {\n prunedDictionary: {\n ...dictionary,\n content: prunedContentFields as CompiledDictionaryJson['content'],\n },\n wasRecognised: true,\n };\n }\n\n return { prunedDictionary: dictionary, wasRecognised: false };\n};\n\n/**\n * Prune a **dynamic / per-locale** dictionary JSON (one file per locale).\n *\n * Structure:\n * { key, content: { field1: value, field2: value }, locale: \"en\" }\n *\n * The `content` here is already the flat, locale-specific record, so we\n * prune its top-level keys directly.\n */\nconst pruneDynamicDictionaryContent = (\n dictionary: CompiledDictionaryJson,\n usedFieldNames: Set<string>\n): PruneResult => {\n const { content } = dictionary;\n\n if (!isPlainRecord(content)) {\n return { prunedDictionary: dictionary, wasRecognised: false };\n }\n\n const prunedContentFields: Record<string, unknown> = {};\n for (const [fieldName, fieldValue] of Object.entries(content)) {\n if (usedFieldNames.has(fieldName)) {\n prunedContentFields[fieldName] = fieldValue;\n }\n }\n\n return {\n prunedDictionary: {\n ...dictionary,\n content: prunedContentFields as CompiledDictionaryJson['content'],\n },\n wasRecognised: true,\n };\n};\n\n/**\n * Returns the Vite plugin that removes unused content fields from compiled\n * dictionary JSON files during a production build.\n *\n * Targets:\n * - `<dictionariesDir>/**\\/*.json` – static all-locale dictionaries\n * - `<dynamicDictionariesDir>/**\\/*.json` – per-locale dynamic dictionaries\n * - `<fetchDictionariesDir>/**\\/*.json` – per-locale fetch dictionaries\n *\n * Decision table for each dictionary JSON:\n *\n * | condition | action |\n * |------------------------------------------------|-----------------|\n * | key in `dictionariesWithEdgeCases` | skip (warn once)|\n * | JSON parse error / missing key field | skip + warn |\n * | unrecognised content structure | skip + warn |\n * | analysis incomplete + key not in usage map | skip + warn |\n * | usage = 'all' (spread / untracked variable) | skip prune |\n * | usage = Set<string> | prune fields |\n *\n * Pruned dictionaries are returned as compact JSON (minification is handled\n * separately by `intlayerMinifyPlugin`).\n *\n * @param intlayerConfig - Resolved intlayer configuration.\n * @param pruneContext - Shared state produced by the usage analyser that\n * runs inside `intlayerOptimizePlugin`.\n */\nexport const intlayerPrune = (\n intlayerConfig: IntlayerConfig,\n pruneContext: PruneContext\n): PluginOption[] => {\n const logger = getAppLogger(intlayerConfig);\n\n const { optimize, purge } = intlayerConfig.build;\n const editorEnabled = intlayerConfig.editor.enabled;\n\n const {\n dictionariesDir,\n dynamicDictionariesDir,\n fetchDictionariesDir,\n baseDir,\n } = intlayerConfig.system;\n\n /**\n * Tracks dictionary keys whose \"pruned fields\" log has already been emitted\n * during this build session. Using an in-memory Set (instead of `runOnce`\n * file locks) avoids race conditions when client and SSR environments run\n * transforms concurrently — JavaScript's single-threaded event loop ensures\n * the `.has` / `.add` pair is always atomic.\n */\n const loggedPrunedDictionaryKeys = new Set<string>();\n\n /**\n * Accumulated statistics for the build summary.\n */\n const prunedFieldsCountPerDictionary = new Map<string, number>();\n\n const isDictionaryJsonFile = (absoluteFilePath: string): boolean =>\n absoluteFilePath.endsWith('.json') &&\n (absoluteFilePath.startsWith(dictionariesDir) ||\n absoluteFilePath.startsWith(dynamicDictionariesDir) ||\n absoluteFilePath.startsWith(fetchDictionariesDir));\n\n const isDynamicOrFetchDictionaryFile = (absoluteFilePath: string): boolean =>\n absoluteFilePath.startsWith(dynamicDictionariesDir) ||\n absoluteFilePath.startsWith(fetchDictionariesDir);\n\n const isPruneEnabled = (\n _config: unknown,\n env: { command: string }\n ): boolean => {\n const isBuildCommand = env.command === 'build';\n const isOptimizeActive =\n (optimize === undefined && isBuildCommand) || optimize === true;\n\n if (!isBuildCommand) return false;\n if (!isOptimizeActive) return false;\n if (!purge) return false;\n\n if (editorEnabled) {\n runOnce(\n join(\n baseDir,\n '.intlayer',\n 'cache',\n 'intlayer-purge-editor-warning.lock'\n ),\n () =>\n logger([\n 'Dictionary purge is',\n colorize('disabled', ANSIColors.GREY_DARK),\n 'because',\n colorize('editor.enabled', ANSIColors.BLUE),\n 'is',\n colorize('true', ANSIColors.GREY_DARK),\n colorize(\n '— the editor requires full dictionary content',\n ANSIColors.GREY\n ),\n ]),\n { cacheTimeoutMs: 1000 * 10 }\n );\n return false;\n }\n\n return true;\n };\n\n const prunePlugin: PluginOption = {\n name: 'vite-intlayer-dictionary-prune',\n // 'pre' so we receive raw JSON before Vite's built-in JSON → ESM conversion\n enforce: 'pre',\n apply: isPruneEnabled,\n\n transform: (rawJsonCode, moduleId) => {\n const absoluteFilePath = moduleId.split('?', 1)[0];\n\n if (!absoluteFilePath || !isDictionaryJsonFile(absoluteFilePath))\n return null;\n\n // Parse JSON\n let parsedDictionary: CompiledDictionaryJson;\n try {\n parsedDictionary = JSON.parse(rawJsonCode) as CompiledDictionaryJson;\n } catch {\n // Malformed JSON – leave it for Vite to report the error\n return null;\n }\n\n const { key: dictionaryKey } = parsedDictionary;\n\n if (!dictionaryKey) {\n logger(\n [\n `Dictionary file`,\n formatPath(absoluteFilePath),\n `is missing a \"key\" field. Skipping prune for this file.`,\n ],\n { level: 'warn' }\n );\n return null;\n }\n\n // Skip keys already marked as edge cases─\n if (pruneContext.dictionariesWithEdgeCases.has(dictionaryKey)) {\n return null;\n }\n\n const fieldUsage =\n pruneContext.dictionaryKeyToFieldUsageMap.get(dictionaryKey);\n\n // No usage entry in the map─\n if (!fieldUsage) {\n if (pruneContext.hasUnparsableSourceFiles) {\n // At least one source file failed to parse; the unparsable file might\n // reference this key, so we cannot safely prune it.\n pruneContext.dictionariesWithEdgeCases.add(dictionaryKey);\n logger(\n [\n `Skipping prune for dictionary`,\n colorizeKey(dictionaryKey),\n `: analysis is incomplete due to earlier source-file parse failures.`,\n ],\n { level: 'warn' }\n );\n return null;\n }\n\n // Analysis was complete but this key was never referenced in any source\n // file – the dictionary is either unused or loaded dynamically by key.\n // Leave the content unchanged (the minify plugin will compact it).\n return null;\n }\n\n // Usage is 'all': at least one call-site consumes all fields─\n if (fieldUsage === 'all') {\n return null; // nothing to prune\n }\n\n // Prune\n const isDynamicOrFetch = isDynamicOrFetchDictionaryFile(absoluteFilePath);\n\n const { prunedDictionary, wasRecognised } = isDynamicOrFetch\n ? pruneDynamicDictionaryContent(parsedDictionary, fieldUsage)\n : pruneStaticDictionaryContent(parsedDictionary, fieldUsage);\n\n if (!wasRecognised) {\n pruneContext.dictionariesWithEdgeCases.add(dictionaryKey);\n logger(\n [\n `Unrecognised content structure in dictionary`,\n colorizeKey(dictionaryKey),\n `(file:`,\n `${formatPath(absoluteFilePath)}).`,\n `Skipping prune for this dictionary.`,\n ],\n { level: 'warn' }\n );\n return null;\n }\n\n // Log pruned fields\n const originalContent = parsedDictionary.content;\n let originalFieldNames: string[];\n\n if (isTranslationNode(originalContent)) {\n // Shape A – fields live inside each locale object\n const firstLocaleValue = Object.values(originalContent.translation)[0];\n originalFieldNames = isPlainRecord(firstLocaleValue)\n ? Object.keys(firstLocaleValue)\n : [];\n } else if (isPlainRecord(originalContent)) {\n // Shape B / dynamic – flat content record\n originalFieldNames = Object.keys(originalContent);\n } else {\n originalFieldNames = [];\n }\n\n const removedFieldNames = originalFieldNames.filter(\n (fieldName) => !fieldUsage.has(fieldName)\n );\n\n if (removedFieldNames.length > 0) {\n prunedFieldsCountPerDictionary.set(\n dictionaryKey,\n removedFieldNames.length\n );\n\n if (!loggedPrunedDictionaryKeys.has(dictionaryKey)) {\n loggedPrunedDictionaryKeys.add(dictionaryKey);\n logger(\n [\n `Pruned`,\n colorizeNumber(removedFieldNames.length),\n `unused field${removedFieldNames.length === 1 ? '' : 's'} from`,\n `${colorizeKey(dictionaryKey)}:`,\n removedFieldNames\n .map((fieldName) => colorize(fieldName, ANSIColors.GREY_LIGHT))\n .join(', '),\n ],\n { isVerbose: true }\n );\n }\n }\n\n return { code: JSON.stringify(prunedDictionary), map: null };\n },\n\n /**\n * Log a summary of all fields removed during this build.\n */\n buildEnd: () => {\n runOnce(\n join(baseDir, '.intlayer', 'cache', 'intlayer-prune-summary.lock'),\n () => {\n const totalPrunedFieldsCount = [\n ...prunedFieldsCountPerDictionary.values(),\n ].reduce((a, b) => a + b, 0);\n const totalPrunedDictionariesCount =\n prunedFieldsCountPerDictionary.size;\n\n if (totalPrunedFieldsCount > 0) {\n logger([\n `Pruned`,\n colorizeNumber(totalPrunedFieldsCount),\n `unused field${totalPrunedFieldsCount === 1 ? '' : 's'} across`,\n colorizeNumber(totalPrunedDictionariesCount),\n `dictionar${totalPrunedDictionariesCount === 1 ? 'y' : 'ies'}.`,\n ]);\n }\n },\n { cacheTimeoutMs: 1000 * 5 }\n );\n },\n };\n\n return [prunePlugin];\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"intlayerPrunePlugin.mjs","names":[],"sources":["../../src/intlayerPrunePlugin.ts"],"sourcesContent":["import { join } from 'node:path';\nimport type { PruneContext } from '@intlayer/babel';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport {\n colorize,\n colorizeKey,\n colorizeNumber,\n getAppLogger,\n} from '@intlayer/config/logger';\nimport { formatPath, runOnce } from '@intlayer/engine/utils';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { PluginOption } from 'vite';\n\n// Dictionary JSON types\n\n/**\n * A compiled intlayer translation node – used in static dictionaries where\n * all locales are bundled in a single file.\n *\n * Structure:\n * { nodeType: \"translation\", translation: { en: { field1, field2 }, fr: {…} } }\n */\ntype TranslationNode = {\n nodeType: 'translation';\n translation: Record<string, unknown>;\n};\n\n/**\n * Compiled intlayer dictionary as stored in a `.json` file.\n *\n * Two content shapes are supported (see `pruneStaticDictionaryContent` and\n * `pruneDynamicDictionaryContent`).\n */\ntype CompiledDictionaryJson = {\n key: string;\n content: TranslationNode | Record<string, unknown>;\n locale?: string; // present in per-locale dynamic dictionary files\n localIds?: string[];\n [extraKey: string]: unknown;\n};\n\n// Type guards\n\nconst isTranslationNode = (value: unknown): value is TranslationNode =>\n typeof value === 'object' &&\n value !== null &&\n (value as Record<string, unknown>).nodeType === 'translation' &&\n typeof (value as Record<string, unknown>).translation === 'object';\n\nconst isPlainRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\n// Pruning logic\n\n/**\n * Result of a prune attempt.\n *\n * `wasRecognised` is `false` when the content structure did not match any\n * known shape – the caller should log a warning and skip the file entirely.\n */\ntype PruneResult = {\n prunedDictionary: CompiledDictionaryJson;\n wasRecognised: boolean;\n};\n\n/**\n * Prune a **static** dictionary JSON (all locales in one file).\n *\n * Shape A – the whole `content` is a single translation node:\n * { nodeType: \"translation\", translation: { en: { f1, f2 }, fr: { f1, f2 } } }\n * → prune the field objects inside each locale.\n *\n * Shape B – `content` is a plain record of fields, each being a translated node:\n * { field1: { nodeType: \"translation\", … }, field2: { … } }\n * → prune the top-level keys of `content` directly.\n *\n * Returns `{ wasRecognised: false }` when neither shape matches.\n */\nconst pruneStaticDictionaryContent = (\n dictionary: CompiledDictionaryJson,\n usedFieldNames: Set<string>\n): PruneResult => {\n const { content } = dictionary;\n\n // Shape A\n if (isTranslationNode(content)) {\n const firstLocaleValue = Object.values(content.translation)[0];\n const localeValuesAreRecords = isPlainRecord(firstLocaleValue);\n\n if (localeValuesAreRecords) {\n const prunedTranslationByLocale: Record<string, unknown> = {};\n\n for (const [localeName, localeContent] of Object.entries(\n content.translation\n )) {\n if (!isPlainRecord(localeContent)) {\n // Locale value is not a record (e.g. a primitive) – keep as-is\n prunedTranslationByLocale[localeName] = localeContent;\n continue;\n }\n\n const prunedLocaleFields: Record<string, unknown> = {};\n for (const [fieldName, fieldValue] of Object.entries(localeContent)) {\n if (usedFieldNames.has(fieldName)) {\n prunedLocaleFields[fieldName] = fieldValue;\n }\n }\n prunedTranslationByLocale[localeName] = prunedLocaleFields;\n }\n\n return {\n prunedDictionary: {\n ...dictionary,\n content: { ...content, translation: prunedTranslationByLocale },\n },\n wasRecognised: true,\n };\n }\n }\n\n // Shape B\n if (isPlainRecord(content) && !isTranslationNode(content)) {\n const prunedContentFields: Record<string, unknown> = {};\n\n for (const [fieldName, fieldValue] of Object.entries(content)) {\n if (usedFieldNames.has(fieldName)) {\n prunedContentFields[fieldName] = fieldValue;\n }\n }\n\n return {\n prunedDictionary: {\n ...dictionary,\n content: prunedContentFields as CompiledDictionaryJson['content'],\n },\n wasRecognised: true,\n };\n }\n\n return { prunedDictionary: dictionary, wasRecognised: false };\n};\n\n/**\n * Prune a **dynamic / per-locale** dictionary JSON (one file per locale).\n *\n * Structure:\n * { key, content: { field1: value, field2: value }, locale: \"en\" }\n *\n * The `content` here is already the flat, locale-specific record, so we\n * prune its top-level keys directly.\n */\nconst pruneDynamicDictionaryContent = (\n dictionary: CompiledDictionaryJson,\n usedFieldNames: Set<string>\n): PruneResult => {\n const { content } = dictionary;\n\n if (!isPlainRecord(content)) {\n return { prunedDictionary: dictionary, wasRecognised: false };\n }\n\n const prunedContentFields: Record<string, unknown> = {};\n for (const [fieldName, fieldValue] of Object.entries(content)) {\n if (usedFieldNames.has(fieldName)) {\n prunedContentFields[fieldName] = fieldValue;\n }\n }\n\n return {\n prunedDictionary: {\n ...dictionary,\n content: prunedContentFields as CompiledDictionaryJson['content'],\n },\n wasRecognised: true,\n };\n};\n\n/**\n * Returns the Vite plugin that removes unused content fields from compiled\n * dictionary JSON files during a production build.\n *\n * Targets:\n * - `<dictionariesDir>/**\\/*.json` – static all-locale dictionaries\n * - `<dynamicDictionariesDir>/**\\/*.json` – per-locale dynamic dictionaries\n * - `<fetchDictionariesDir>/**\\/*.json` – per-locale fetch dictionaries\n *\n * Decision table for each dictionary JSON:\n *\n * | condition | action |\n * |------------------------------------------------|-----------------|\n * | key in `dictionariesWithEdgeCases` | skip (warn once)|\n * | JSON parse error / missing key field | skip + warn |\n * | unrecognised content structure | skip + warn |\n * | analysis incomplete + key not in usage map | skip + warn |\n * | usage = 'all' (spread / untracked variable) | skip prune |\n * | usage = Set<string> | prune fields |\n *\n * Pruned dictionaries are returned as compact JSON (minification is handled\n * separately by `intlayerMinifyPlugin`).\n *\n * Pruning stays compatible with the visual editor: the editor resolves every\n * edit through `dictionaryKey` + `keyPath` against the unmerged dictionaries\n * entry, which this plugin never touches, and a pruned field is one no source\n * file reads — so it is never rendered and never selectable in the page.\n *\n * @param intlayerConfig - Resolved intlayer configuration.\n * @param pruneContext - Shared state produced by the usage analyser that\n * runs inside `intlayerOptimizePlugin`.\n */\nexport const intlayerPrune = (\n intlayerConfig: IntlayerConfig,\n pruneContext: PruneContext\n): PluginOption[] => {\n const logger = getAppLogger(intlayerConfig);\n\n const { optimize, purge } = intlayerConfig.build;\n\n const {\n dictionariesDir,\n dynamicDictionariesDir,\n fetchDictionariesDir,\n baseDir,\n } = intlayerConfig.system;\n\n /**\n * Tracks dictionary keys whose \"pruned fields\" log has already been emitted\n * during this build session. Using an in-memory Set (instead of `runOnce`\n * file locks) avoids race conditions when client and SSR environments run\n * transforms concurrently — JavaScript's single-threaded event loop ensures\n * the `.has` / `.add` pair is always atomic.\n */\n const loggedPrunedDictionaryKeys = new Set<string>();\n\n /**\n * Accumulated statistics for the build summary.\n */\n const prunedFieldsCountPerDictionary = new Map<string, number>();\n\n const isDictionaryJsonFile = (absoluteFilePath: string): boolean =>\n absoluteFilePath.endsWith('.json') &&\n (absoluteFilePath.startsWith(dictionariesDir) ||\n absoluteFilePath.startsWith(dynamicDictionariesDir) ||\n absoluteFilePath.startsWith(fetchDictionariesDir));\n\n const isDynamicOrFetchDictionaryFile = (absoluteFilePath: string): boolean =>\n absoluteFilePath.startsWith(dynamicDictionariesDir) ||\n absoluteFilePath.startsWith(fetchDictionariesDir);\n\n const isPruneEnabled = (\n _config: unknown,\n env: { command: string }\n ): boolean => {\n const isBuildCommand = env.command === 'build';\n const isOptimizeActive =\n (optimize === undefined && isBuildCommand) || optimize === true;\n\n if (!isBuildCommand) return false;\n if (!isOptimizeActive) return false;\n if (!purge) return false;\n\n return true;\n };\n\n const prunePlugin: PluginOption = {\n name: 'vite-intlayer-dictionary-prune',\n // 'pre' so we receive raw JSON before Vite's built-in JSON → ESM conversion\n enforce: 'pre',\n apply: isPruneEnabled,\n\n transform: (rawJsonCode, moduleId) => {\n const absoluteFilePath = moduleId.split('?', 1)[0];\n\n if (!absoluteFilePath || !isDictionaryJsonFile(absoluteFilePath))\n return null;\n\n // Parse JSON\n let parsedDictionary: CompiledDictionaryJson;\n try {\n parsedDictionary = JSON.parse(rawJsonCode) as CompiledDictionaryJson;\n } catch {\n // Malformed JSON – leave it for Vite to report the error\n return null;\n }\n\n const { key: dictionaryKey } = parsedDictionary;\n\n if (!dictionaryKey) {\n logger(\n [\n `Dictionary file`,\n formatPath(absoluteFilePath),\n `is missing a \"key\" field. Skipping prune for this file.`,\n ],\n { level: 'warn' }\n );\n return null;\n }\n\n // Skip keys already marked as edge cases─\n if (pruneContext.dictionariesWithEdgeCases.has(dictionaryKey)) {\n return null;\n }\n\n const fieldUsage =\n pruneContext.dictionaryKeyToFieldUsageMap.get(dictionaryKey);\n\n // No usage entry in the map─\n if (!fieldUsage) {\n if (pruneContext.hasUnparsableSourceFiles) {\n // At least one source file failed to parse; the unparsable file might\n // reference this key, so we cannot safely prune it.\n pruneContext.dictionariesWithEdgeCases.add(dictionaryKey);\n logger(\n [\n `Skipping prune for dictionary`,\n colorizeKey(dictionaryKey),\n `: analysis is incomplete due to earlier source-file parse failures.`,\n ],\n { level: 'warn' }\n );\n return null;\n }\n\n // Analysis was complete but this key was never referenced in any source\n // file – the dictionary is either unused or loaded dynamically by key.\n // Leave the content unchanged (the minify plugin will compact it).\n return null;\n }\n\n // Usage is 'all': at least one call-site consumes all fields─\n if (fieldUsage === 'all') {\n return null; // nothing to prune\n }\n\n // Prune\n const isDynamicOrFetch = isDynamicOrFetchDictionaryFile(absoluteFilePath);\n\n const { prunedDictionary, wasRecognised } = isDynamicOrFetch\n ? pruneDynamicDictionaryContent(parsedDictionary, fieldUsage)\n : pruneStaticDictionaryContent(parsedDictionary, fieldUsage);\n\n if (!wasRecognised) {\n pruneContext.dictionariesWithEdgeCases.add(dictionaryKey);\n logger(\n [\n `Unrecognised content structure in dictionary`,\n colorizeKey(dictionaryKey),\n `(file:`,\n `${formatPath(absoluteFilePath)}).`,\n `Skipping prune for this dictionary.`,\n ],\n { level: 'warn' }\n );\n return null;\n }\n\n // Log pruned fields\n const originalContent = parsedDictionary.content;\n let originalFieldNames: string[];\n\n if (isTranslationNode(originalContent)) {\n // Shape A – fields live inside each locale object\n const firstLocaleValue = Object.values(originalContent.translation)[0];\n originalFieldNames = isPlainRecord(firstLocaleValue)\n ? Object.keys(firstLocaleValue)\n : [];\n } else if (isPlainRecord(originalContent)) {\n // Shape B / dynamic – flat content record\n originalFieldNames = Object.keys(originalContent);\n } else {\n originalFieldNames = [];\n }\n\n const removedFieldNames = originalFieldNames.filter(\n (fieldName) => !fieldUsage.has(fieldName)\n );\n\n if (removedFieldNames.length > 0) {\n prunedFieldsCountPerDictionary.set(\n dictionaryKey,\n removedFieldNames.length\n );\n\n if (!loggedPrunedDictionaryKeys.has(dictionaryKey)) {\n loggedPrunedDictionaryKeys.add(dictionaryKey);\n logger(\n [\n `Pruned`,\n colorizeNumber(removedFieldNames.length),\n `unused field${removedFieldNames.length === 1 ? '' : 's'} from`,\n `${colorizeKey(dictionaryKey)}:`,\n removedFieldNames\n .map((fieldName) => colorize(fieldName, ANSIColors.GREY_LIGHT))\n .join(', '),\n ],\n { isVerbose: true }\n );\n }\n }\n\n return { code: JSON.stringify(prunedDictionary), map: null };\n },\n\n /**\n * Log a summary of all fields removed during this build.\n */\n buildEnd: () => {\n runOnce(\n join(baseDir, '.intlayer', 'cache', 'intlayer-prune-summary.lock'),\n () => {\n const totalPrunedFieldsCount = [\n ...prunedFieldsCountPerDictionary.values(),\n ].reduce((a, b) => a + b, 0);\n const totalPrunedDictionariesCount =\n prunedFieldsCountPerDictionary.size;\n\n if (totalPrunedFieldsCount > 0) {\n logger([\n `Pruned`,\n colorizeNumber(totalPrunedFieldsCount),\n `unused field${totalPrunedFieldsCount === 1 ? '' : 's'} across`,\n colorizeNumber(totalPrunedDictionariesCount),\n `dictionar${totalPrunedDictionariesCount === 1 ? 'y' : 'ies'}.`,\n ]);\n }\n },\n { cacheTimeoutMs: 1000 * 5 }\n );\n },\n };\n\n return [prunePlugin];\n};\n"],"mappings":"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"}
@@ -1,7 +1,9 @@
1
1
  import { IntlayerCompilerOptions, intlayerCompiler } from "./IntlayerCompilerPlugin.js";
2
+ import { ChunkingContext, DictionaryModule, ModuleGraphNode, createDictionaryModuleParser, intlayerChunk, resolveBoundaries, toBoundaryName } from "./intlayerChunkPlugin.js";
2
3
  import { intlayerMinify } from "./intlayerMinifyPlugin.js";
3
4
  import { intlayerOptimize } from "./intlayerOptimizePlugin.js";
4
5
  import { IntlayerProxyPluginOptions, createIntlayerProxyHandler, intLayerMiddlewarePlugin, intlayerMiddleware, intlayerProxy } from "./intlayerProxyPlugin.js";
5
6
  import { IntlayerPluginOptions, intLayerPlugin, intlayer, intlayerPlugin } from "./intlayerPlugin.js";
7
+ import { intlayerPreload } from "./intlayerPreloadPlugin.js";
6
8
  import { intlayerPrune } from "./intlayerPrunePlugin.js";
7
- export { IntlayerCompilerOptions, IntlayerPluginOptions, IntlayerProxyPluginOptions, createIntlayerProxyHandler, intLayerMiddlewarePlugin, intLayerPlugin, intlayer, intlayerCompiler, intlayerMiddleware, intlayerMinify, intlayerOptimize, intlayerPlugin, intlayerProxy, intlayerPrune };
9
+ export { ChunkingContext, DictionaryModule, IntlayerCompilerOptions, IntlayerPluginOptions, IntlayerProxyPluginOptions, ModuleGraphNode, createDictionaryModuleParser, createIntlayerProxyHandler, intLayerMiddlewarePlugin, intLayerPlugin, intlayer, intlayerChunk, intlayerCompiler, intlayerMiddleware, intlayerMinify, intlayerOptimize, intlayerPlugin, intlayerPreload, intlayerProxy, intlayerPrune, resolveBoundaries, toBoundaryName };
@@ -0,0 +1,88 @@
1
+ import { IntlayerConfig } from "@intlayer/types/config";
2
+ import { Plugin } from "vite";
3
+ //#region src/intlayerChunkPlugin.d.ts
4
+ /**
5
+ * Module-graph accessor Rolldown passes to a chunk-name function.
6
+ *
7
+ * Declared structurally rather than imported from `rolldown` so this plugin
8
+ * keeps working against the Rolldown bundled inside Vite, whose types are not
9
+ * re-exported.
10
+ */
11
+ type ChunkingContext = {
12
+ getModuleInfo: (moduleId: string) => ModuleGraphNode | null;
13
+ };
14
+ type ModuleGraphNode = {
15
+ id: string;
16
+ isEntry: boolean;
17
+ /** Ids of the modules that statically `import` this one. */
18
+ importers: string[];
19
+ /** Ids of the modules that reach this one through `import()`. */
20
+ dynamicImporters: string[];
21
+ };
22
+ /** A per-locale dictionary chunk, identified from its module id. */
23
+ type DictionaryModule = {
24
+ key: string;
25
+ locale: string;
26
+ };
27
+ /**
28
+ * Builds the parser that recognises a per-locale dictionary chunk emitted in
29
+ * `dynamic` import mode.
30
+ *
31
+ * The layout is read relative to `dynamicDictionariesDir` rather than matched
32
+ * anywhere in the path, so an application file that happens to sit under its own
33
+ * `json/<something>/<something>.json` cannot be mistaken for a dictionary.
34
+ * Everything between the key and the locale is a qualifier dimension
35
+ * (collections, variants), which is why the middle segments are skipped instead
36
+ * of being required to be absent.
37
+ *
38
+ * @param dynamicDictionariesDir - Absolute, POSIX-normalized dictionaries root.
39
+ * @returns Parser returning the key and locale, or `null` for any other module.
40
+ */
41
+ declare const createDictionaryModuleParser: (dynamicDictionariesDir: string) => ((moduleId: string) => DictionaryModule | null);
42
+ /** Turns a boundary module id into a readable chunk-name fragment. */
43
+ declare const toBoundaryName: (moduleId: string) => string;
44
+ /**
45
+ * Walks up the import graph from a dictionary JSON to the code-split boundaries
46
+ * that reach it.
47
+ *
48
+ * A module pulled in through `import()` — a `React.lazy` section, a split
49
+ * route — begins its own chunk, so it is the unit whose dictionaries should
50
+ * travel together: one request per Suspense boundary rather than one per
51
+ * dictionary. The walk deliberately starts from the JSON's importers rather
52
+ * than the JSON itself, because *every* dictionary is reached through
53
+ * `import()` and would otherwise be read as its own boundary.
54
+ *
55
+ * @returns Boundary ids reaching this dictionary, empty when the graph is
56
+ * unavailable (the module was pruned, or resolution failed).
57
+ */
58
+ declare const resolveBoundaries: (jsonModuleId: string, context: ChunkingContext) => string[];
59
+ /**
60
+ * Groups dynamically imported dictionaries by the code-split boundary that
61
+ * consumes them, so each lazily loaded page or section fetches its content in
62
+ * a single request instead of one per dictionary.
63
+ *
64
+ * Without this, `importMode: 'dynamic'` emits a chunk per dictionary per
65
+ * locale. A page assembled from many components then issues dozens of ~3 KB
66
+ * requests, and because each is discovered only once its consumer has
67
+ * executed, they land in the last wave of the request waterfall — page text
68
+ * arrives after everything else.
69
+ *
70
+ * Switching to `importMode: 'static'` would collapse those requests but inline
71
+ * every locale into the bundle, which does not scale past a handful of
72
+ * languages. Grouping keeps the per-locale split and removes the fragmentation.
73
+ *
74
+ * Dictionaries reached from more than one boundary are placed in a shared
75
+ * chunk, so no page ships another page's content and the bytes are not
76
+ * duplicated across chunks.
77
+ *
78
+ * Because a boundary's dictionaries share one chunk, warming that chunk warms
79
+ * the whole boundary in a single request — which is why the top-level-await
80
+ * preload (`intlayerPreload`) costs one request per boundary rather than one
81
+ * per dictionary, without any page loading content belonging to another.
82
+ *
83
+ * @param intlayerConfig - Resolved Intlayer configuration.
84
+ */
85
+ declare const intlayerChunk: (intlayerConfig: IntlayerConfig) => Plugin;
86
+ //#endregion
87
+ export { ChunkingContext, DictionaryModule, ModuleGraphNode, createDictionaryModuleParser, intlayerChunk, resolveBoundaries, toBoundaryName };
88
+ //# sourceMappingURL=intlayerChunkPlugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"intlayerChunkPlugin.d.ts","names":[],"sources":["../../src/intlayerChunkPlugin.ts"],"mappings":";;;;;;;;;;KAaY;EACV,gBAAgB,qBAAqB;;KAG3B;EACV;EACA;;EAEA;;EAEA;;;KAIU;EACV;EACA;;;;;;;;;;;;;;;;cAiBW,+BAA4B,qCAEpC,qBAAqB;;cAuCb,iBAAc;;;;;;;;;;;;;;;cAuBd,oBAAiB,sBACR,SACX;;;;;;;;;;;;;;;;;;;;;;;;;;;cAyDE,gBAAa,gBAAoB,mBAAiB"}
@@ -23,7 +23,11 @@ import { PruneContext } from "@intlayer/babel";
23
23
  * and should be left completely untouched to avoid shipping broken data.
24
24
  *
25
25
  * Field renaming (property mangling) is applied only for dictionaries that
26
- * have a known, finite field usage set in `pruneContext.dictionaryKeyToFieldRenameMap`.
26
+ * have a known, finite field usage set in `pruneContext.dictionaryKeyToFieldRenameMap`,
27
+ * and only while the visual editor is disabled: renaming rewrites the content
28
+ * keys the interpreter walks, so the `keyPath` reported to the editor would no
29
+ * longer resolve against the unmerged dictionaries. The whitespace compaction
30
+ * and the top-level metadata strip are keyPath-neutral and always run.
27
31
  * The corresponding rename is also applied to source-file property accesses by
28
32
  * the babel rename pass inside `intlayerOptimize`. Internal intlayer fields
29
33
  * such as `nodeType` are never renamed.
@@ -1 +1 @@
1
- {"version":3,"file":"intlayerMinifyPlugin.d.ts","names":[],"sources":["../../src/intlayerMinifyPlugin.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAoKa,iBAAc,gBACT,gBAAc,cAChB,wBACb"}
1
+ {"version":3,"file":"intlayerMinifyPlugin.d.ts","names":[],"sources":["../../src/intlayerMinifyPlugin.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAwKa,iBAAc,gBACT,gBAAc,cAChB,wBACb"}
@@ -40,7 +40,7 @@ type H3EventLike = {
40
40
  * and Deno — where `event.node` is `undefined` and `fromNodeMiddleware` crashes with
41
41
  * "undefined is not an object (evaluating 'event.node.req')".
42
42
  *
43
- * It bridges h3 v2 events to the Node.js-style `createIntlayerProxyHandler` middleware
43
+ * It bridges h3 v2 events to the Node.js-style proxy middleware
44
44
  * via lightweight IncomingMessage / ServerResponse shims:
45
45
  *
46
46
  * - **Redirect** (301 / 5xx): builds a Web API `Response` and returns it — Nitro sends
@@ -1 +1 @@
1
- {"version":3,"file":"intlayerNitroHandler.d.ts","names":[],"sources":["../../src/intlayerNitroHandler.ts"],"mappings":";;;;;;;;;KAcK;;;;;WAKM;;;;;;EAMT,KAAK;;;;;WAKI,SAAS;;;;;WAKT;aACE,SAAS;;;;;;;;;;;;;;;;;;;;;;iBAmCA,SAAA,OAAO,cAAc,QAAQ"}
1
+ {"version":3,"file":"intlayerNitroHandler.d.ts","names":[],"sources":["../../src/intlayerNitroHandler.ts"],"mappings":";;;;;;;;;KAyBK;;;;;WAKM;;;;;;EAMT,KAAK;;;;;WAKI,SAAS;;;;;WAKT;aACE,SAAS;;;;;;;;;;;;;;;;;;;;;;iBAmCA,SAAA,OAAO,cAAc,QAAQ"}
@@ -1 +1 @@
1
- {"version":3,"file":"intlayerPlugin.d.ts","names":[],"sources":["../../src/intlayerPlugin.ts"],"mappings":";;;;;;;;;;;;;;KAyCY,wBAAwB;;;;;;;;EAQlC,gBAAgB;;;;;;;;;;;;;;;EAgBhB,QAAQ,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;cA0BF,iBAAc,gBACT,0BACf;;;;;;;;;;;cAuNU,iBAAQ;;;;;;;;;;;;;cAaR,uBAAc"}
1
+ {"version":3,"file":"intlayerPlugin.d.ts","names":[],"sources":["../../src/intlayerPlugin.ts"],"mappings":";;;;;;;;;;;;;;KA2CY,wBAAwB;;;;;;;;EAQlC,gBAAgB;;;;;;;;;;;;;;;EAgBhB,QAAQ,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;cA0BF,iBAAc,gBACT,0BACf;;;;;;;;;;;cAgOU,iBAAQ;;;;;;;;;;;;;cAaR,uBAAc"}
@@ -0,0 +1,34 @@
1
+ import { IntlayerConfig } from "@intlayer/types/config";
2
+ import { Plugin } from "vite";
3
+ //#region src/intlayerPreloadPlugin.d.ts
4
+ /**
5
+ * Makes dictionaries load with the chunk that needs them, instead of being
6
+ * requested once that chunk renders.
7
+ *
8
+ * In `importMode: 'dynamic'` a component reads its dictionary through a loader
9
+ * map and suspends while the matching chunk is fetched. The fetch is only
10
+ * discovered once the component executes, so it lands at the very end of the
11
+ * request waterfall and every navigation flashes the pending fallback before
12
+ * any text appears — the cost that used to push applications towards
13
+ * `importMode: 'static'`, which inlines every locale into the bundle.
14
+ *
15
+ * Starting that fetch from the generated entry point itself moves it forward to
16
+ * module-evaluation time: it leaves with the chunk that needs it — on a
17
+ * router's `import()` of a route, including a preload on hover — instead of
18
+ * waiting for the consuming component to execute. By the time the read happens
19
+ * the content is usually already there and the render is synchronous.
20
+ *
21
+ * The fetch is deliberately not awaited at the top level. Doing so would make
22
+ * the entry point an async module and hold up every chunk that imports it,
23
+ * which permanently strands `React.lazy` boundaries resolving during hydration
24
+ * on their fallback — see `addDynamicEntryPreload`.
25
+ *
26
+ * Complements the grouping in `intlayerChunk`: grouping decides how many
27
+ * requests a boundary makes, this decides when they start.
28
+ *
29
+ * @param intlayerConfig - Resolved Intlayer configuration.
30
+ */
31
+ declare const intlayerPreload: (intlayerConfig: IntlayerConfig) => Plugin;
32
+ //#endregion
33
+ export { intlayerPreload };
34
+ //# sourceMappingURL=intlayerPreloadPlugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"intlayerPreloadPlugin.d.ts","names":[],"sources":["../../src/intlayerPreloadPlugin.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAmDa,kBAAe,gBAAoB,mBAAiB"}
@@ -0,0 +1,50 @@
1
+ import { IntlayerConfig } from "@intlayer/types/config";
2
+ import { IncomingMessage, ServerResponse } from "node:http";
3
+ //#region src/intlayerProxyHandler.d.ts
4
+ /**
5
+ * A Node.js-compatible Connect middleware function.
6
+ * Compatible with Vite dev/preview server, Node.js http, Express, and h3's
7
+ * `fromNodeMiddleware` wrapper for Nitro/TanStack Start production use.
8
+ */
9
+ type NodeMiddleware = (req: IncomingMessage, res: ServerResponse<IncomingMessage>, next: () => void) => void;
10
+ type IntlayerProxyHandlerOptions = {
11
+ /**
12
+ * The resolved Intlayer configuration driving locale detection and routing.
13
+ */
14
+ configuration: IntlayerConfig;
15
+ /**
16
+ * A function that allows you to ignore specific requests from the intlayer proxy.
17
+ *
18
+ * @param req - The incoming request.
19
+ * @returns A boolean value indicating whether to ignore the request.
20
+ */
21
+ ignore?: (req: IncomingMessage) => boolean | undefined;
22
+ /**
23
+ * Whether a development or preview server is serving the app.
24
+ *
25
+ * It only matters in the proxy's auto mode, where a dev server keeps locale
26
+ * routing URL-driven by ignoring the stored locale as a redirect source.
27
+ *
28
+ * @default false
29
+ */
30
+ isDevServer?: boolean;
31
+ };
32
+ /**
33
+ * Creates the locale-routing middleware from an already-resolved configuration.
34
+ *
35
+ * This is the runtime core shared by every environment - dev server, preview
36
+ * server and the production Nitro server. It deliberately receives the
37
+ * configuration instead of loading it: loading it would pull
38
+ * `@intlayer/config/node` - and therefore `esbuild` - into the server bundle,
39
+ * which then fails to boot wherever `node_modules` is not shipped next to the
40
+ * build output.
41
+ *
42
+ * @param options - The resolved configuration plus optional handler behaviour.
43
+ * @returns A Connect-compatible `(req, res, next) => void` middleware.
44
+ *
45
+ * @see `createIntlayerProxyHandler` for the public, configuration-loading entry point.
46
+ */
47
+ declare const createProxyHandler: (options: IntlayerProxyHandlerOptions) => NodeMiddleware;
48
+ //#endregion
49
+ export { IntlayerProxyHandlerOptions, NodeMiddleware, createProxyHandler };
50
+ //# sourceMappingURL=intlayerProxyHandler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"intlayerProxyHandler.d.ts","names":[],"sources":["../../src/intlayerProxyHandler.ts"],"mappings":";;;;;;;;KAyEY,kBACV,KAAK,iBACL,KAAK,eAAe,kBACpB;KAGU;;;;EAIV,eAAe;;;;;;;EAOf,UAAU,KAAK;;;;;;;;;EASf;;;;;;;;;;;;;;;;;cAkBW,qBAAkB,SACpB,gCACR"}
@@ -1,6 +1,7 @@
1
+ import { NodeMiddleware } from "./intlayerProxyHandler.js";
1
2
  import { GetConfigurationOptions } from "@intlayer/config/node";
2
3
  import { Plugin } from "vite";
3
- import { IncomingMessage, ServerResponse } from "node:http";
4
+ import { IncomingMessage } from "node:http";
4
5
  //#region src/intlayerProxyPlugin.d.ts
5
6
  type IntlayerProxyPluginOptions = {
6
7
  /**
@@ -43,18 +44,11 @@ type IntlayerProxyPluginOptions = {
43
44
  */
44
45
  isDevServer?: boolean;
45
46
  };
46
- /**
47
- * A Node.js-compatible Connect middleware function.
48
- * Compatible with Vite dev/preview server, Node.js http, Express, and h3's
49
- * `fromNodeMiddleware` wrapper for Nitro/TanStack Start production use.
50
- */
51
- type NodeMiddleware = (req: IncomingMessage, res: ServerResponse<IncomingMessage>, next: () => void) => void;
52
47
  /**
53
48
  * Creates a standalone, framework-agnostic locale-routing middleware.
54
49
  *
55
- * This function contains all the locale detection, redirect, and rewrite logic.
56
- * It is intentionally separated from the Vite plugin so the same handler can be
57
- * used in every environment:
50
+ * It loads the Intlayer configuration and hands it to the runtime handler in
51
+ * `intlayerProxyHandler`, so it can be used in every environment:
58
52
  *
59
53
  * - **Dev**: wired up automatically by `intlayerProxy` via `configureServer`
60
54
  * - **Preview**: wired up automatically by `intlayerProxy` via `configurePreviewServer`
@@ -71,7 +65,6 @@ type NodeMiddleware = (req: IncomingMessage, res: ServerResponse<IncomingMessage
71
65
  *
72
66
  * @param options - Plugin-specific options, such as path ignoring and Intlayer configuration overrides.
73
67
  * @returns A Connect-compatible `(req, res, next) => void` middleware.
74
- *
75
68
  */
76
69
  declare const createIntlayerProxyHandler: (options?: IntlayerProxyPluginOptions) => NodeMiddleware;
77
70
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"intlayerProxyPlugin.d.ts","names":[],"sources":["../../src/intlayerProxyPlugin.ts"],"mappings":";;;;KA0EY;;;;;;;;;;;;;;EAcV,UAAU,KAAK;;;;;;;;;;;EAWf,gBAAgB;;;;;;;;;;;;;;EAchB;;;;;;;KAQG,kBACH,KAAK,iBACL,KAAK,eAAe,kBACpB;;;;;;;;;;;;;;;;;;;;;;;;;cA2BW,6BAA0B,UAC3B,+BACT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA27BU,gBAAa,UAAc,+BAA6B;;;;;;;;;;;;;cAuIxD,2BAAkB;;;;;;;;;;;;;cAclB,iCAAwB"}
1
+ {"version":3,"file":"intlayerProxyPlugin.d.ts","names":[],"sources":["../../src/intlayerProxyPlugin.ts"],"mappings":";;;;;KA6BY;;;;;;;;;;;;;;EAcV,UAAU,KAAK;;;;;;;;;;;EAWf,gBAAgB;;;;;;;;;;;;;;EAchB;;;;;;;;;;;;;;;;;;;;;;;;cAyBW,6BAA0B,UAC3B,+BACT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAgDU,gBAAa,UAAc,+BAA6B;;;;;;;;;;;;;cAuIxD,2BAAkB;;;;;;;;;;;;;cAclB,iCAAwB"}
@@ -25,6 +25,11 @@ import { PruneContext } from "@intlayer/babel";
25
25
  * Pruned dictionaries are returned as compact JSON (minification is handled
26
26
  * separately by `intlayerMinifyPlugin`).
27
27
  *
28
+ * Pruning stays compatible with the visual editor: the editor resolves every
29
+ * edit through `dictionaryKey` + `keyPath` against the unmerged dictionaries
30
+ * entry, which this plugin never touches, and a pruned field is one no source
31
+ * file reads — so it is never rendered and never selectable in the page.
32
+ *
28
33
  * @param intlayerConfig - Resolved intlayer configuration.
29
34
  * @param pruneContext - Shared state produced by the usage analyser that
30
35
  * runs inside `intlayerOptimizePlugin`.
@@ -1 +1 @@
1
- {"version":3,"file":"intlayerPrunePlugin.d.ts","names":[],"sources":["../../src/intlayerPrunePlugin.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA4Ma,gBAAa,gBACR,gBAAc,cAChB,iBACb"}
1
+ {"version":3,"file":"intlayerPrunePlugin.d.ts","names":[],"sources":["../../src/intlayerPrunePlugin.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAiNa,gBAAa,gBACR,gBAAc,cAChB,iBACb"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-intlayer",
3
- "version": "9.3.2",
3
+ "version": "9.4.0-canary.0",
4
4
  "private": false,
5
5
  "description": "A Vite plugin for seamless internationalization (i18n), providing locale detection, redirection, and environment-based configuration",
6
6
  "keywords": [
@@ -81,12 +81,12 @@
81
81
  "typecheck": "tsc --noEmit --project tsconfig.types.json"
82
82
  },
83
83
  "dependencies": {
84
- "@intlayer/babel": "9.3.2",
85
- "@intlayer/config": "9.3.2",
86
- "@intlayer/core": "9.3.2",
87
- "@intlayer/dictionaries-entry": "9.3.2",
88
- "@intlayer/engine": "9.3.2",
89
- "@intlayer/types": "9.3.2"
84
+ "@intlayer/babel": "9.4.0-canary.0",
85
+ "@intlayer/config": "9.4.0-canary.0",
86
+ "@intlayer/core": "9.4.0-canary.0",
87
+ "@intlayer/dictionaries-entry": "9.4.0-canary.0",
88
+ "@intlayer/engine": "9.4.0-canary.0",
89
+ "@intlayer/types": "9.4.0-canary.0"
90
90
  },
91
91
  "devDependencies": {
92
92
  "@types/node": "26.2.0",
@@ -96,12 +96,12 @@
96
96
  "rimraf": "6.1.3",
97
97
  "tsdown": "0.22.14",
98
98
  "typescript": "7.0.2",
99
- "vitest": "4.1.10"
99
+ "vitest": "4.1.11"
100
100
  },
101
101
  "peerDependencies": {
102
102
  "@babel/core": ">=6.0.0",
103
- "@intlayer/svelte-compiler": "9.3.2",
104
- "@intlayer/vue-compiler": "9.3.2",
103
+ "@intlayer/svelte-compiler": "9.4.0-canary.0",
104
+ "@intlayer/vue-compiler": "9.4.0-canary.0",
105
105
  "h3": ">=1.0.0",
106
106
  "vite": ">=4.0.0"
107
107
  },