vite-intlayer 9.3.0 → 9.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -47,27 +47,27 @@ With **per-locale content files**, **TypeScript autocompletion**, **tree-shakabl
47
47
 
48
48
  ## Keys benefits of Intlayer:
49
49
 
50
- | Feature | Description |
51
- | --------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
52
- | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/frameworks.png?raw=true" alt="Feature" width="700"> | **Cross-Frameworks Support**<br><br>Intlayer is compatible with all major frameworks and libraries, including Next.js, React, Vite, Vue.js, Nuxt, Preact, Express, and more. |
53
- | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/javascript_content_management.jpg?raw=true" alt="Feature" width="700"> | **JavaScript-Powered Content Management**<br><br>Harness the flexibility of JavaScript to define and manage your content efficiently. <br><br> - [Content declaration](https://intlayer.org/doc/concept/content) |
54
- | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/per_locale_content_declaration_file.png?raw=true" alt="Feature" width="700"> | **Per-Locale Content Declaration File**<br><br>Speed up your development by declaring your content once, before auto generation.<br><br> - [Per-Locale Content Declaration File](https://intlayer.org/doc/concept/per-locale-file) |
55
- | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/compiler.jpg?raw=true" alt="Feature" width="700"> | **Compiler**<br><br>The Intlayer Compiler extract automatically the content from the components and generate the dictionary files.<br><br> - [Compiler](https://intlayer.org/doc/compiler) |
56
- | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/autocompletion.png?raw=true" alt="Feature" width="700"> | **Type-Safe Environment**<br><br>Leverage TypeScript to ensure your content definitions and code are error-free, while also benefiting from IDE autocompletion.<br><br> - [TypeScript configuration](https://intlayer.org/doc/environment/vite-and-react#configure-typescript) |
57
- | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/config_file.png?raw=true" alt="Feature" width="700"> | **Simplified Setup**<br><br>Get up and running quickly with minimal configuration. Adjust settings for internationalization, routing, AI, build, and content handling with ease. <br><br> - [Explore Next.js integration](https://intlayer.org/doc/environment/nextjs) |
58
- | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/content_retrieval.png?raw=true" alt="Feature" width="700"> | **Simplified Content Retrieval**<br><br>No need to call your `t` function for each piece of content. Retrieve all your content directly using a single hook.<br><br> - [React integration](https://intlayer.org/doc/environment/create-react-app) |
59
- | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/server_component.png?raw=true" alt="Feature" width="700"> | **Consistent Server Component Implementation**<br><br>Perfectly suited for Next.js server components, use the same implementation for both client and server components, no need to pass your `t` function across each server component. <br><br> - [Server Components](https://intlayer.org/doc/environment/nextjs#step-7-utilize-content-in-your-code) |
60
- | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/file_tree.png?raw=true" alt="Feature" width="700"> | **Organized Codebase**<br><br>Keep your codebase more organized: 1 component = 1 dictionary in the same folder. Translations close to their respective components, enhance maintainability and clarity. <br><br> - [How Intlayer works](https://intlayer.org/doc/concept/how-works-intlayer) |
61
- | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/url_routing.png?raw=true" alt="Feature" width="700"> | **Enhanced Routing**<br><br>Full support of app routing, adapting seamlessly to complex application structures, for Next.js, React, Vite, Vue.js, etc.<br><br> - [Explore Next.js integration](https://intlayer.org/doc/environment/nextjs) |
62
- | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/markdown.png?raw=true" alt="Feature" width="700"> | **Markdown Support**<br><br>Import and interpret, locale files and remote Markdown for multilingual content like privacy policies, documentation, etc. Interpret and make Markdown metadata accessible in your code.<br><br> - [Content files](https://intlayer.org/doc/concept/content/file) |
63
- | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/visual_editor.png?raw=true" alt="Feature" width="700"> | **Free Visual Editor & CMS**<br><br>A free visual editor and CMS are available for content writers, removing the need for a localization platform. Keep your content synchronized using Git, or externalize it totally or partially with the CMS.<br><br> - [Intlayer Editor](https://intlayer.org/doc/concept/editor) <br> - [Intlayer CMS](https://intlayer.org/doc/concept/cms) |
64
- | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/bundle.png?raw=true" alt="Feature" width="700"> | **Tree-shakable Content**<br><br>Tree-shakable content, reducing the size of the final bundle. Loads content per component, excluding any unused content from your bundle. Supports lazy loading to enhance app loading efficiency. <br><br> - [App build optimization](https://intlayer.org/doc/concept/how-works-intlayer#app-build-optimization) |
65
- | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/static_rendering.png?raw=true" alt="Feature" width="700"> | **Static Rendering**<br><br>Doesn't block Static Rendering. <br><br> - [Next.js integration](https://intlayer.org/doc/environment/nextjs) |
66
- | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/AI_translation.png?raw=true" alt="Feature" width="700"> | **AI-Powered Translation**<br><br>Transform your website into 231 languages with just one click using Intlayer's advanced AI-powered translation tools using your own AI provider / API key. <br><br> - [CI/CD integration](https://intlayer.org/doc/concept/ci-cd) <br> - [Intlayer CLI](https://intlayer.org/doc/concept/cli) <br> - [Auto fill](https://intlayer.org/doc/concept/auto-fill) |
67
- | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/mcp.png?raw=true" alt="Feature" width="700"> | **MCP Server Integration**<br><br>Provides an MCP (Model Context Protocol) server for IDE automation, enabling seamless content management and i18n workflows directly within your development environment. <br><br> - [MCP Server](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/mcp_server.md) |
68
- | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/vscode_extension.png?raw=true" alt="Feature" width="700"> | **VSCode Extension**<br><br>Intlayer provides a VSCode extension to help you manage your content and translations, building your dictionaries, translating your content, and more. <br><br> - [VSCode Extension](https://intlayer.org/doc/vs-code-extension) |
69
- | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/interoperability.png?raw=true" alt="Feature" width="700"> | **Interoperability**<br><br>Allow interoperability with react-i18next, next-i18next, next-intl, react-intl, vue-i18n. <br><br> - [Intlayer and react-intl](https://intlayer.org/blog/intlayer-with-react-intl) <br> - [Intlayer and next-intl](https://intlayer.org/blog/intlayer-with-next-intl) <br> - [Intlayer and next-i18next](https://intlayer.org/blog/intlayer-with-next-i18next) <br> - [Intlayer and vue-i18n](https://intlayer.org/blog/intlayer-with-vue-i18n) |
70
- | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/benchmark.png?raw=true" alt="Feature" width="700"> | **Performances & Benchmark**<br><br>Uses advanced tree-shaking and dynamic loading to boost performances and keep the solution as light as possible. <br><br> - [Performances & Benchmark](https://intlayer.org/doc/benchmark) |
50
+ | Feature | Description |
51
+ | --------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
52
+ | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/frameworks.png?raw=true" alt="Feature" width="700"> | **Cross-Frameworks Support**<br><br>Intlayer is compatible with all major frameworks and libraries, including Next.js, React, Vite, Vue.js, Nuxt, Preact, Express, and more. |
53
+ | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/javascript_content_management.jpg?raw=true" alt="Feature" width="700"> | **JavaScript-Powered Content Management**<br><br>Harness the flexibility of JavaScript to define and manage your content efficiently. <br><br> - [Content declaration](https://intlayer.org/doc/concept/content) |
54
+ | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/per_locale_content_declaration_file.png?raw=true" alt="Feature" width="700"> | **Per-Locale Content Declaration File**<br><br>Speed up your development by declaring your content once, before auto generation.<br><br> - [Per-Locale Content Declaration File](https://intlayer.org/doc/concept/per-locale-file) |
55
+ | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/compiler.jpg?raw=true" alt="Feature" width="700"> | **Compiler**<br><br>The Intlayer Compiler extract automatically the content from the components and generate the dictionary files.<br><br> - [Compiler](https://intlayer.org/doc/compiler) |
56
+ | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/autocompletion.png?raw=true" alt="Feature" width="700"> | **Type-Safe Environment**<br><br>Leverage TypeScript to ensure your content definitions and code are error-free, while also benefiting from IDE autocompletion.<br><br> - [TypeScript configuration](https://intlayer.org/doc/environment/vite-and-react#configure-typescript) |
57
+ | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/config_file.png?raw=true" alt="Feature" width="700"> | **Simplified Setup**<br><br>Get up and running quickly with minimal configuration. Adjust settings for internationalization, routing, AI, build, and content handling with ease. <br><br> - [Explore Next.js integration](https://intlayer.org/doc/environment/nextjs) |
58
+ | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/content_retrieval.png?raw=true" alt="Feature" width="700"> | **Simplified Content Retrieval**<br><br>No need to call your `t` function for each piece of content. Retrieve all your content directly using a single hook.<br><br> - [React integration](https://intlayer.org/doc/environment/create-react-app) |
59
+ | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/server_component.png?raw=true" alt="Feature" width="700"> | **Consistent Server Component Implementation**<br><br>Perfectly suited for Next.js server components, use the same implementation for both client and server components, no need to pass your `t` function across each server component. <br><br> - [Server Components](https://intlayer.org/doc/environment/nextjs#step-7-utilize-content-in-your-code) |
60
+ | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/file_tree.png?raw=true" alt="Feature" width="700"> | **Organized Codebase**<br><br>Keep your codebase more organized: 1 component = 1 dictionary in the same folder. Translations close to their respective components, enhance maintainability and clarity. <br><br> - [How Intlayer works](https://intlayer.org/doc/concept/how-works-intlayer) |
61
+ | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/url_routing.png?raw=true" alt="Feature" width="700"> | **Enhanced Routing**<br><br>Full support of app routing, adapting seamlessly to complex application structures, for Next.js, React, Vite, Vue.js, etc.<br><br> - [Explore Next.js integration](https://intlayer.org/doc/environment/nextjs) |
62
+ | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/markdown.png?raw=true" alt="Feature" width="700"> | **Markdown Support**<br><br>Import and interpret, locale files and remote Markdown for multilingual content like privacy policies, documentation, etc. Interpret and make Markdown metadata accessible in your code.<br><br> - [Content files](https://intlayer.org/doc/concept/content/file) |
63
+ | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/visual_editor.png?raw=true" alt="Feature" width="700"> | **Free Visual Editor & CMS**<br><br>A free visual editor and CMS are available for content writers, removing the need for a localization platform. Keep your content synchronized using Git, or externalize it totally or partially with the CMS.<br><br> - [Intlayer Editor](https://intlayer.org/doc/concept/editor) <br> - [Intlayer CMS](https://intlayer.org/doc/concept/cms) |
64
+ | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/bundle.png?raw=true" alt="Feature" width="700"> | **Tree-shakable Content**<br><br>Tree-shakable content, reducing the size of the final bundle. Loads content per component, excluding any unused content from your bundle. Supports lazy loading to enhance app loading efficiency. <br><br> - [App build optimization](https://intlayer.org/doc/concept/how-works-intlayer#app-build-optimization) |
65
+ | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/static_rendering.png?raw=true" alt="Feature" width="700"> | **Static Rendering**<br><br>Doesn't block Static Rendering. <br><br> - [Next.js integration](https://intlayer.org/doc/environment/nextjs) |
66
+ | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/AI_translation.png?raw=true" alt="Feature" width="700"> | **AI-Powered Translation**<br><br>Transform your website into 231 languages with just one click using Intlayer's advanced AI-powered translation tools using your own AI provider / API key. <br><br> - [CI/CD integration](https://intlayer.org/doc/concept/ci-cd) <br> - [Intlayer CLI](https://intlayer.org/doc/concept/cli) <br> - [Auto fill](https://intlayer.org/doc/concept/auto-fill) |
67
+ | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/mcp.png?raw=true" alt="Feature" width="700"> | **MCP Server Integration**<br><br>Provides an MCP (Model Context Protocol) server for IDE automation, enabling seamless content management and i18n workflows directly within your development environment. <br><br> - [MCP Server](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/mcp_server.md) |
68
+ | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/vscode_extension.png?raw=true" alt="Feature" width="700"> | **VSCode Extension**<br><br>Intlayer provides a VSCode extension to help you manage your content and translations, building your dictionaries, translating your content, and more. <br><br> - [VSCode Extension](https://intlayer.org/doc/vs-code-extension) |
69
+ | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/interoperability.png?raw=true" alt="Feature" width="700"> | **Interoperability**<br><br>Allow interoperability with react-i18next, next-i18next, next-intl, react-intl, vue-i18n. <br><br> - [Intlayer and react-intl](https://intlayer.org/blog/intlayer-with-react-intl) <br> - [Intlayer and next-intl](https://intlayer.org/blog/intlayer-with-next-intl) <br> - [Intlayer and next-i18next](https://intlayer.org/blog/intlayer-with-next-i18next) <br> - [Intlayer and vue-i18n](https://intlayer.org/blog/intlayer-with-vue-i18n) <br> - [Intlayer compat adapters](https://intlayer.org/doc/compatibility) |
70
+ | <img src="https://github.com/aymericzip/intlayer/blob/main/docs/assets/benchmark.png?raw=true" alt="Feature" width="700"> | **Performances & Benchmark**<br><br>Uses advanced tree-shaking and dynamic loading to boost performances and keep the solution as light as possible. <br><br> - [Performances & Benchmark](https://intlayer.org/doc/benchmark) |
71
71
 
72
72
  ---
73
73
 
@@ -162,7 +162,7 @@ Explore our comprehensive documentation to get started with Intlayer and learn h
162
162
  <li><a href="https://intlayer.org/doc/concept/cms" rel=''>Intlayer CMS</a></li>
163
163
  <li><a href="https://intlayer.org/doc/concept/content" rel=''>Dictionary</a>
164
164
  <ul>
165
- <li><a href="https://intlayer.org/doc/concept/content/per-locale-file" rel=''>Per-Locale Content Declaration File</a></li>
165
+ <li><a href="https://intlayer.org/doc/concept/per-locale-file" rel=''>Per-Locale Content Declaration File</a></li>
166
166
  <li><a href="https://intlayer.org/doc/concept/content/translation" rel=''>Translation</a></li>
167
167
  <li><a href="https://intlayer.org/doc/concept/content/enumeration" rel=''>Enumeration</a></li>
168
168
  <li><a href="https://intlayer.org/doc/concept/content/condition" rel=''>Condition</a></li>
@@ -183,7 +183,7 @@ Explore our comprehensive documentation to get started with Intlayer and learn h
183
183
  <ul>
184
184
  <li><a href="https://intlayer.org/doc/environment/nextjs/15" rel=''>Next.js 15</a></li>
185
185
  <li><a href="https://intlayer.org/doc/environment/nextjs/14" rel=''>Next.js 14 (App Router)</a></li>
186
- <li><a href="https://intlayer.org/doc/environment/nextjs/next-with-Page-Router" rel=''>Next.js Page Router</a></li>
186
+ <li><a href="https://intlayer.org/doc/environment/nextjs/next-with-page-router" rel=''>Next.js Page Router</a></li>
187
187
  <li><a href="https://intlayer.org/doc/environment/nextjs/compiler" rel=''>Next.js using Compiler</a></li>
188
188
  </ul>
189
189
  </li>
@@ -212,7 +212,7 @@ Explore our comprehensive documentation to get started with Intlayer and learn h
212
212
  <li><a href="https://intlayer.org/doc/environment/sveltekit" rel=''>SvelteKit</a></li>
213
213
  <li><a href="https://intlayer.org/doc/environment/vite-and-preact" rel=''>Vite + Preact</a></li>
214
214
  <li><a href="https://intlayer.org/doc/environment/vite-and-vue" rel=''>Vite + Vue</a></li>
215
- <li><a href="https://intlayer.org/doc/environment/vite-and-nuxt" rel=''>Vite + Nuxt</a></li>
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
217
  <li><a href="https://intlayer.org/doc/environment/angular" rel=''>Angular</a></li>
218
218
  <li>
@@ -190,7 +190,8 @@ const intlayerCompiler = (options) => {
190
190
  isVerbose: true
191
191
  });
192
192
  try {
193
- const result = await extractContent(filename, detectPackageName(dirname(filename)), {
193
+ const packageName = detectPackageName(dirname(filename));
194
+ const result = await extractContent(filename, packageName, {
194
195
  configuration: config,
195
196
  code,
196
197
  onExtract: async ({ key, content }) => {
@@ -1 +1 @@
1
- {"version":3,"file":"IntlayerCompilerPlugin.mjs","names":[],"sources":["../../src/IntlayerCompilerPlugin.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { dirname, relative } from 'node:path';\nimport {\n type CompilerMode,\n detectPackageName,\n type ExtractPluginOptions,\n type ExtractResult,\n extractContent,\n getExtractPluginOptions,\n writeContentHelper,\n} from '@intlayer/babel';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport {\n colorize,\n colorizeKey,\n colorizeNumber,\n colorizePath,\n getAppLogger,\n x,\n} from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport { normalizePath } from '@intlayer/config/utils';\nimport type { CompilerConfig, IntlayerConfig } from '@intlayer/types/config';\nimport type { HmrContext, Plugin, PluginOption } from 'vite';\nimport { createPrimaryInstanceGuard } from './dedupePlugin';\n\nconst COMPILER_PLUGIN_NAME = 'vite-intlayer-compiler';\n\n/**\n * Options for initializing the compiler\n */\nexport type IntlayerCompilerOptions = {\n /**\n * Configuration options for getting the intlayer configuration\n */\n configOptions?: GetConfigurationOptions;\n\n /**\n * Custom compiler configuration to override defaults\n */\n compilerConfig?: Partial<CompilerConfig>;\n};\n\n/**\n * Create an IntlayerCompiler - A Vite-compatible compiler plugin for Intlayer\n *\n * This autonomous compiler handles:\n * - Configuration loading and management\n * - Hot Module Replacement (HMR) for content changes\n * - File transformation with content extraction\n * - Dictionary persistence and building\n *\n * @example\n * ```ts\n * // vite.config.ts\n * import { defineConfig } from 'vite';\n * import { intlayerCompiler } from 'vite-intlayer';\n *\n * export default defineConfig({\n * plugins: [intlayerCompiler()],\n * });\n * ```\n *\n * @deprecated Since Intlayer v9, the compiler is bundled directly into the `intlayer()` plugin and activates automatically once `compiler.enabled` is set with a `compiler.output` path. Registering `intlayerCompiler()` separately as shown below is now optional. See the [v9 release notes](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/releases/v9.md).\n */\nexport const intlayerCompiler = (\n options?: IntlayerCompilerOptions\n): PluginOption => {\n let config: IntlayerConfig;\n let compilerConfig: ExtractPluginOptions;\n let logger: ReturnType<typeof getAppLogger>;\n let projectRoot = '';\n let filesList: string[] = [];\n\n // Ensures the compiler runs its side effects only once, even when it is\n // registered both via `intlayer()` (which now bundles it) and a manual\n // `intlayerCompiler()` call.\n const guard = createPrimaryInstanceGuard(COMPILER_PLUGIN_NAME);\n\n // Promise to track dictionary writing (for synchronization)\n let pendingDictionaryWrite: Promise<void> | null = null;\n\n // Track recently processed files to prevent infinite loops\n // Key: file path, Value: timestamp of last processing\n const recentlyProcessedFiles = new Map<string, number>();\n // Track recently written dictionaries to prevent duplicate writes\n // Key: dictionary key, Value: hash of content that was written\n const recentDictionaryContent = new Map<string, string>();\n // Debounce window in milliseconds - skip re-processing files within this window\n const DEBOUNCE_MS = 500;\n\n /**\n * Check if a file was recently processed (within debounce window)\n * and should be skipped to prevent infinite loops\n */\n const wasRecentlyProcessed = (filePath: string): boolean => {\n const lastProcessed = recentlyProcessedFiles.get(filePath);\n if (!lastProcessed) return false;\n\n const now = Date.now();\n return now - lastProcessed < DEBOUNCE_MS;\n };\n\n /**\n * Mark a file as recently processed\n */\n const markAsProcessed = (filePath: string): void => {\n recentlyProcessedFiles.set(filePath, Date.now());\n\n // Clean up old entries to prevent memory leaks\n const now = Date.now();\n for (const [path, timestamp] of recentlyProcessedFiles.entries()) {\n if (now - timestamp > DEBOUNCE_MS * 2) {\n recentlyProcessedFiles.delete(path);\n }\n }\n };\n\n /**\n * Create a simple hash of content for comparison\n * Used to detect if dictionary content has actually changed\n */\n const hashContent = (content: Record<string, string>): string =>\n JSON.stringify(\n Object.keys(content)\n .sort()\n .map((key) => [key, content[key]])\n );\n\n /**\n * Check if dictionary content has changed since last write\n */\n const hasDictionaryContentChanged = (\n dictionaryKey: string,\n content: Record<string, string>\n ): boolean => {\n const newHash = hashContent(content);\n const previousHash = recentDictionaryContent.get(dictionaryKey);\n\n if (previousHash === newHash) {\n return false;\n }\n\n // Update the stored hash\n recentDictionaryContent.set(dictionaryKey, newHash);\n return true;\n };\n\n /**\n * Build the list of files to transform based on configuration patterns\n */\n const buildFilesListFn = async (): Promise<void> => {\n // Normalize to POSIX so comparisons match on Windows.\n filesList = (compilerConfig.filesList ?? []).map(normalizePath);\n };\n\n /**\n * Initialize the compiler with the given mode\n */\n const init = async (compilerMode: CompilerMode): Promise<void> => {\n config = getConfiguration(options?.configOptions);\n\n compilerConfig = getExtractPluginOptions(config, compilerMode);\n\n logger = getAppLogger(config);\n\n // Build files list for transformation\n await buildFilesListFn();\n };\n\n /**\n * Vite hook: configResolved\n * Called when Vite config is resolved\n */\n const configResolved = async (viteConfig: {\n env?: { DEV?: boolean };\n root: string;\n plugins?: readonly { name: string }[];\n }): Promise<void> => {\n // Decide whether this is the primary instance before doing any work.\n guard.resolve({ plugins: viteConfig.plugins ?? [] });\n if (!guard.isPrimary) return;\n\n const compilerMode: CompilerMode = viteConfig.env?.DEV ? 'dev' : 'build';\n projectRoot = viteConfig.root;\n\n await init(compilerMode);\n };\n\n /**\n * Build start hook - no longer needs to prepare dictionaries\n * The compiler is now autonomous and extracts content inline\n */\n const buildStart = async (): Promise<void> => {\n if (!guard.isPrimary) return;\n\n // Bootstrap dictionaries and types before build starts\n // This ensures existing dictionaries are available for resolution\n try {\n logger('Intlayer compiler initialized', {\n level: 'info',\n });\n } catch (error) {\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Failed to prepare Intlayer: ${error}`,\n {\n level: 'error',\n }\n );\n }\n };\n\n /**\n * Build end hook - wait for any pending dictionary writes\n */\n const buildEnd = async (): Promise<void> => {\n // Wait for any pending dictionary writes to complete\n if (pendingDictionaryWrite) {\n await pendingDictionaryWrite;\n }\n };\n\n /**\n * Vite hook: handleHotUpdate\n * Handles HMR for content files - invalidates cache and triggers re-transform\n */\n const handleHotUpdate = async ({\n file,\n server,\n modules,\n }: HmrContext): Promise<void> => {\n if (!guard.isPrimary) return undefined;\n\n // Check if this is a file we should transform (compare as POSIX paths).\n const normalizedFile = normalizePath(file);\n const isTransformableFile = filesList.some(\n (fileEl) => fileEl === normalizedFile\n );\n\n if (isTransformableFile) {\n // Check if this file was recently processed to prevent infinite loops\n // When a component is transformed, it writes a dictionary, which triggers HMR,\n // which would re-transform the component - this debounce prevents that loop\n if (wasRecentlyProcessed(file)) {\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Skipping re-transform of ${colorizePath(relative(projectRoot, file))} (recently processed)`,\n {\n level: 'info',\n isVerbose: true,\n }\n );\n return undefined;\n }\n\n // Mark file as being processed before transformation\n markAsProcessed(file);\n\n // Invalidate all affected modules to ensure re-transform\n for (const mod of modules) {\n server.moduleGraph.invalidateModule(mod);\n }\n\n // Force re-transform by reading and processing the file\n // This ensures content extraction happens on every file change\n try {\n const code = await readFile(file, 'utf-8');\n\n // Trigger the transform manually to extract content\n await transformHandler(code, file);\n } catch (error) {\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Failed to re-transform ${file}: ${error}`,\n {\n level: 'error',\n }\n );\n }\n\n // Trigger full reload for content changes\n server.ws.send({ type: 'full-reload' });\n }\n };\n\n /**\n * Write and build one or more dictionaries based on extracted content.\n * Leverages shared logic from @intlayer/babel.\n */\n const writeAndBuildDictionary = async (\n result: ExtractResult\n ): Promise<void> => {\n const { dictionaryKey, content, filePath: sourceFilePath } = result;\n\n // Skip if content hasn't changed - prevents infinite loops during HMR\n if (!hasDictionaryContentChanged(dictionaryKey, content)) {\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Skipping dictionary ${colorizeKey(dictionaryKey)} (content unchanged)`,\n {\n level: 'info',\n isVerbose: true,\n }\n );\n return;\n }\n\n try {\n await writeContentHelper(content, dictionaryKey, sourceFilePath!, config);\n } catch (error) {\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Failed to write/build dictionary for ${colorizeKey(dictionaryKey)}: ${error}`,\n {\n level: 'error',\n }\n );\n }\n };\n\n /**\n * Callback for when content is extracted from a file\n * Immediately writes and builds the dictionary\n */\n const handleExtractedContent = async (\n result: ExtractResult\n ): Promise<void> => {\n const contentKeys = Object.keys(result.content);\n\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Extracted ${colorizeNumber(contentKeys.length)} content keys from ${colorizePath(relative(projectRoot, result.filePath))}`,\n {\n level: 'info',\n }\n );\n\n // Chain the write operation to ensure sequential writes\n pendingDictionaryWrite = (pendingDictionaryWrite ?? Promise.resolve())\n .then(() => writeAndBuildDictionary(result))\n .catch((error) => {\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Error in dictionary write chain: ${error}`,\n {\n level: 'error',\n }\n );\n });\n\n return pendingDictionaryWrite;\n };\n\n /**\n * Transform a file using the appropriate extraction plugin based on file type.\n * Delegates to `extractContent` from `@intlayer/babel` which handles\n * JS/TS/JSX/TSX/Vue/Svelte/Astro extraction and transformation.\n */\n const transformHandler = async (code: string, id: string) => {\n // Skip duplicate instances so dictionaries are not written twice.\n if (!guard.isPrimary) {\n return undefined;\n }\n\n // Only transform if compiler is enabled\n if (!compilerConfig.enabled) {\n return undefined;\n }\n\n // Skip virtual modules (query strings indicate compiled/virtual modules)\n // e.g., App.svelte?svelte&type=style, Component.vue?vue&type=script\n if (id.includes('?')) {\n return undefined;\n }\n\n const filename = id;\n\n // Compare as POSIX paths; filename stays raw for extraction below.\n if (!filesList.includes(normalizePath(filename))) {\n return undefined;\n }\n\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Transforming ${colorizePath(relative(projectRoot, filename))}`,\n {\n level: 'info',\n isVerbose: true,\n }\n );\n\n try {\n const packageName = detectPackageName(dirname(filename));\n\n const result = await extractContent(filename, packageName, {\n configuration: config,\n code,\n // Dictionary writing is handled by handleExtractedContent below.\n onExtract: async ({ key, content }) => {\n await handleExtractedContent({\n dictionaryKey: key,\n content,\n filePath: filename,\n locale: config.internationalization.defaultLocale,\n });\n },\n });\n\n // Wait for the dictionary to be written before returning\n // This ensures the dictionary exists before any subsequent processing\n if (pendingDictionaryWrite) {\n await pendingDictionaryWrite;\n }\n\n if (result?.transformedCode) {\n return {\n code: result.transformedCode,\n };\n }\n } catch (error) {\n logger(\n [\n `Failed to transform ${colorizePath(relative(projectRoot, filename))}:`,\n error,\n ],\n {\n level: 'error',\n }\n );\n }\n\n return undefined;\n };\n\n const plugin: Plugin = {\n name: COMPILER_PLUGIN_NAME,\n enforce: 'pre',\n configResolved,\n buildStart,\n buildEnd,\n handleHotUpdate,\n transform: transformHandler,\n apply: (_viteConfig, env) => {\n // Initialize config if not already done\n if (!config) {\n config = getConfiguration(options?.configOptions);\n }\n if (!logger) {\n logger = getAppLogger(config);\n }\n\n if (!config.compiler.output) {\n logger(\n `${x} No output configuration found. Add a ${colorize('compiler.output', ANSIColors.BLUE)} in your configuration.`,\n {\n level: 'error',\n }\n );\n\n return false;\n }\n\n if (!compilerConfig) {\n compilerConfig = getExtractPluginOptions(config, env.command);\n }\n\n return compilerConfig.enabled;\n },\n };\n\n // Register the plugin object so the dedupe guard can identify the primary\n // instance by reference during `configResolved`.\n guard.setPlugin(plugin);\n\n return plugin;\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"IntlayerCompilerPlugin.mjs","names":[],"sources":["../../src/IntlayerCompilerPlugin.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { dirname, relative } from 'node:path';\nimport {\n type CompilerMode,\n detectPackageName,\n type ExtractPluginOptions,\n type ExtractResult,\n extractContent,\n getExtractPluginOptions,\n writeContentHelper,\n} from '@intlayer/babel';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport {\n colorize,\n colorizeKey,\n colorizeNumber,\n colorizePath,\n getAppLogger,\n x,\n} from '@intlayer/config/logger';\nimport {\n type GetConfigurationOptions,\n getConfiguration,\n} from '@intlayer/config/node';\nimport { normalizePath } from '@intlayer/config/utils';\nimport type { CompilerConfig, IntlayerConfig } from '@intlayer/types/config';\nimport type { HmrContext, Plugin, PluginOption } from 'vite';\nimport { createPrimaryInstanceGuard } from './dedupePlugin';\n\nconst COMPILER_PLUGIN_NAME = 'vite-intlayer-compiler';\n\n/**\n * Options for initializing the compiler\n */\nexport type IntlayerCompilerOptions = {\n /**\n * Configuration options for getting the intlayer configuration\n */\n configOptions?: GetConfigurationOptions;\n\n /**\n * Custom compiler configuration to override defaults\n */\n compilerConfig?: Partial<CompilerConfig>;\n};\n\n/**\n * Create an IntlayerCompiler - A Vite-compatible compiler plugin for Intlayer\n *\n * This autonomous compiler handles:\n * - Configuration loading and management\n * - Hot Module Replacement (HMR) for content changes\n * - File transformation with content extraction\n * - Dictionary persistence and building\n *\n * @example\n * ```ts\n * // vite.config.ts\n * import { defineConfig } from 'vite';\n * import { intlayerCompiler } from 'vite-intlayer';\n *\n * export default defineConfig({\n * plugins: [intlayerCompiler()],\n * });\n * ```\n *\n * @deprecated Since Intlayer v9, the compiler is bundled directly into the `intlayer()` plugin and activates automatically once `compiler.enabled` is set with a `compiler.output` path. Registering `intlayerCompiler()` separately as shown below is now optional. See the [v9 release notes](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/releases/v9.md).\n */\nexport const intlayerCompiler = (\n options?: IntlayerCompilerOptions\n): PluginOption => {\n let config: IntlayerConfig;\n let compilerConfig: ExtractPluginOptions;\n let logger: ReturnType<typeof getAppLogger>;\n let projectRoot = '';\n let filesList: string[] = [];\n\n // Ensures the compiler runs its side effects only once, even when it is\n // registered both via `intlayer()` (which now bundles it) and a manual\n // `intlayerCompiler()` call.\n const guard = createPrimaryInstanceGuard(COMPILER_PLUGIN_NAME);\n\n // Promise to track dictionary writing (for synchronization)\n let pendingDictionaryWrite: Promise<void> | null = null;\n\n // Track recently processed files to prevent infinite loops\n // Key: file path, Value: timestamp of last processing\n const recentlyProcessedFiles = new Map<string, number>();\n // Track recently written dictionaries to prevent duplicate writes\n // Key: dictionary key, Value: hash of content that was written\n const recentDictionaryContent = new Map<string, string>();\n // Debounce window in milliseconds - skip re-processing files within this window\n const DEBOUNCE_MS = 500;\n\n /**\n * Check if a file was recently processed (within debounce window)\n * and should be skipped to prevent infinite loops\n */\n const wasRecentlyProcessed = (filePath: string): boolean => {\n const lastProcessed = recentlyProcessedFiles.get(filePath);\n if (!lastProcessed) return false;\n\n const now = Date.now();\n return now - lastProcessed < DEBOUNCE_MS;\n };\n\n /**\n * Mark a file as recently processed\n */\n const markAsProcessed = (filePath: string): void => {\n recentlyProcessedFiles.set(filePath, Date.now());\n\n // Clean up old entries to prevent memory leaks\n const now = Date.now();\n for (const [path, timestamp] of recentlyProcessedFiles.entries()) {\n if (now - timestamp > DEBOUNCE_MS * 2) {\n recentlyProcessedFiles.delete(path);\n }\n }\n };\n\n /**\n * Create a simple hash of content for comparison\n * Used to detect if dictionary content has actually changed\n */\n const hashContent = (content: Record<string, string>): string =>\n JSON.stringify(\n Object.keys(content)\n .sort()\n .map((key) => [key, content[key]])\n );\n\n /**\n * Check if dictionary content has changed since last write\n */\n const hasDictionaryContentChanged = (\n dictionaryKey: string,\n content: Record<string, string>\n ): boolean => {\n const newHash = hashContent(content);\n const previousHash = recentDictionaryContent.get(dictionaryKey);\n\n if (previousHash === newHash) {\n return false;\n }\n\n // Update the stored hash\n recentDictionaryContent.set(dictionaryKey, newHash);\n return true;\n };\n\n /**\n * Build the list of files to transform based on configuration patterns\n */\n const buildFilesListFn = async (): Promise<void> => {\n // Normalize to POSIX so comparisons match on Windows.\n filesList = (compilerConfig.filesList ?? []).map(normalizePath);\n };\n\n /**\n * Initialize the compiler with the given mode\n */\n const init = async (compilerMode: CompilerMode): Promise<void> => {\n config = getConfiguration(options?.configOptions);\n\n compilerConfig = getExtractPluginOptions(config, compilerMode);\n\n logger = getAppLogger(config);\n\n // Build files list for transformation\n await buildFilesListFn();\n };\n\n /**\n * Vite hook: configResolved\n * Called when Vite config is resolved\n */\n const configResolved = async (viteConfig: {\n env?: { DEV?: boolean };\n root: string;\n plugins?: readonly { name: string }[];\n }): Promise<void> => {\n // Decide whether this is the primary instance before doing any work.\n guard.resolve({ plugins: viteConfig.plugins ?? [] });\n if (!guard.isPrimary) return;\n\n const compilerMode: CompilerMode = viteConfig.env?.DEV ? 'dev' : 'build';\n projectRoot = viteConfig.root;\n\n await init(compilerMode);\n };\n\n /**\n * Build start hook - no longer needs to prepare dictionaries\n * The compiler is now autonomous and extracts content inline\n */\n const buildStart = async (): Promise<void> => {\n if (!guard.isPrimary) return;\n\n // Bootstrap dictionaries and types before build starts\n // This ensures existing dictionaries are available for resolution\n try {\n logger('Intlayer compiler initialized', {\n level: 'info',\n });\n } catch (error) {\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Failed to prepare Intlayer: ${error}`,\n {\n level: 'error',\n }\n );\n }\n };\n\n /**\n * Build end hook - wait for any pending dictionary writes\n */\n const buildEnd = async (): Promise<void> => {\n // Wait for any pending dictionary writes to complete\n if (pendingDictionaryWrite) {\n await pendingDictionaryWrite;\n }\n };\n\n /**\n * Vite hook: handleHotUpdate\n * Handles HMR for content files - invalidates cache and triggers re-transform\n */\n const handleHotUpdate = async ({\n file,\n server,\n modules,\n }: HmrContext): Promise<void> => {\n if (!guard.isPrimary) return undefined;\n\n // Check if this is a file we should transform (compare as POSIX paths).\n const normalizedFile = normalizePath(file);\n const isTransformableFile = filesList.some(\n (fileEl) => fileEl === normalizedFile\n );\n\n if (isTransformableFile) {\n // Check if this file was recently processed to prevent infinite loops\n // When a component is transformed, it writes a dictionary, which triggers HMR,\n // which would re-transform the component - this debounce prevents that loop\n if (wasRecentlyProcessed(file)) {\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Skipping re-transform of ${colorizePath(relative(projectRoot, file))} (recently processed)`,\n {\n level: 'info',\n isVerbose: true,\n }\n );\n return undefined;\n }\n\n // Mark file as being processed before transformation\n markAsProcessed(file);\n\n // Invalidate all affected modules to ensure re-transform\n for (const mod of modules) {\n server.moduleGraph.invalidateModule(mod);\n }\n\n // Force re-transform by reading and processing the file\n // This ensures content extraction happens on every file change\n try {\n const code = await readFile(file, 'utf-8');\n\n // Trigger the transform manually to extract content\n await transformHandler(code, file);\n } catch (error) {\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Failed to re-transform ${file}: ${error}`,\n {\n level: 'error',\n }\n );\n }\n\n // Trigger full reload for content changes\n server.ws.send({ type: 'full-reload' });\n }\n };\n\n /**\n * Write and build one or more dictionaries based on extracted content.\n * Leverages shared logic from @intlayer/babel.\n */\n const writeAndBuildDictionary = async (\n result: ExtractResult\n ): Promise<void> => {\n const { dictionaryKey, content, filePath: sourceFilePath } = result;\n\n // Skip if content hasn't changed - prevents infinite loops during HMR\n if (!hasDictionaryContentChanged(dictionaryKey, content)) {\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Skipping dictionary ${colorizeKey(dictionaryKey)} (content unchanged)`,\n {\n level: 'info',\n isVerbose: true,\n }\n );\n return;\n }\n\n try {\n await writeContentHelper(content, dictionaryKey, sourceFilePath!, config);\n } catch (error) {\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Failed to write/build dictionary for ${colorizeKey(dictionaryKey)}: ${error}`,\n {\n level: 'error',\n }\n );\n }\n };\n\n /**\n * Callback for when content is extracted from a file\n * Immediately writes and builds the dictionary\n */\n const handleExtractedContent = async (\n result: ExtractResult\n ): Promise<void> => {\n const contentKeys = Object.keys(result.content);\n\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Extracted ${colorizeNumber(contentKeys.length)} content keys from ${colorizePath(relative(projectRoot, result.filePath))}`,\n {\n level: 'info',\n }\n );\n\n // Chain the write operation to ensure sequential writes\n pendingDictionaryWrite = (pendingDictionaryWrite ?? Promise.resolve())\n .then(() => writeAndBuildDictionary(result))\n .catch((error) => {\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Error in dictionary write chain: ${error}`,\n {\n level: 'error',\n }\n );\n });\n\n return pendingDictionaryWrite;\n };\n\n /**\n * Transform a file using the appropriate extraction plugin based on file type.\n * Delegates to `extractContent` from `@intlayer/babel` which handles\n * JS/TS/JSX/TSX/Vue/Svelte/Astro extraction and transformation.\n */\n const transformHandler = async (code: string, id: string) => {\n // Skip duplicate instances so dictionaries are not written twice.\n if (!guard.isPrimary) {\n return undefined;\n }\n\n // Only transform if compiler is enabled\n if (!compilerConfig.enabled) {\n return undefined;\n }\n\n // Skip virtual modules (query strings indicate compiled/virtual modules)\n // e.g., App.svelte?svelte&type=style, Component.vue?vue&type=script\n if (id.includes('?')) {\n return undefined;\n }\n\n const filename = id;\n\n // Compare as POSIX paths; filename stays raw for extraction below.\n if (!filesList.includes(normalizePath(filename))) {\n return undefined;\n }\n\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Transforming ${colorizePath(relative(projectRoot, filename))}`,\n {\n level: 'info',\n isVerbose: true,\n }\n );\n\n try {\n const packageName = detectPackageName(dirname(filename));\n\n const result = await extractContent(filename, packageName, {\n configuration: config,\n code,\n // Dictionary writing is handled by handleExtractedContent below.\n onExtract: async ({ key, content }) => {\n await handleExtractedContent({\n dictionaryKey: key,\n content,\n filePath: filename,\n locale: config.internationalization.defaultLocale,\n });\n },\n });\n\n // Wait for the dictionary to be written before returning\n // This ensures the dictionary exists before any subsequent processing\n if (pendingDictionaryWrite) {\n await pendingDictionaryWrite;\n }\n\n if (result?.transformedCode) {\n return {\n code: result.transformedCode,\n };\n }\n } catch (error) {\n logger(\n [\n `Failed to transform ${colorizePath(relative(projectRoot, filename))}:`,\n error,\n ],\n {\n level: 'error',\n }\n );\n }\n\n return undefined;\n };\n\n const plugin: Plugin = {\n name: COMPILER_PLUGIN_NAME,\n enforce: 'pre',\n configResolved,\n buildStart,\n buildEnd,\n handleHotUpdate,\n transform: transformHandler,\n apply: (_viteConfig, env) => {\n // Initialize config if not already done\n if (!config) {\n config = getConfiguration(options?.configOptions);\n }\n if (!logger) {\n logger = getAppLogger(config);\n }\n\n if (!config.compiler.output) {\n logger(\n `${x} No output configuration found. Add a ${colorize('compiler.output', ANSIColors.BLUE)} in your configuration.`,\n {\n level: 'error',\n }\n );\n\n return false;\n }\n\n if (!compilerConfig) {\n compilerConfig = getExtractPluginOptions(config, env.command);\n }\n\n return compilerConfig.enabled;\n },\n };\n\n // Register the plugin object so the dedupe guard can identify the primary\n // instance by reference during `configResolved`.\n guard.setPlugin(plugin);\n\n return plugin;\n};\n"],"mappings":"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"}
@@ -118,7 +118,7 @@ const intlayerMinify = (intlayerConfig, pruneContext) => {
118
118
  "is",
119
119
  colorize("true", ANSIColors.GREY_DARK),
120
120
  colorize("— the editor requires full dictionary content", ANSIColors.GREY)
121
- ]), { cacheTimeoutMs: 1e3 * 10 });
121
+ ]), { cacheTimeoutMs: 1e4 });
122
122
  return false;
123
123
  }
124
124
  return true;
@@ -1 +1 @@
1
- {"version":3,"file":"intlayerMinifyPlugin.mjs","names":[],"sources":["../../src/intlayerMinifyPlugin.ts"],"sourcesContent":["import { join } from 'node:path';\nimport type { NestedRenameMap, PruneContext } from '@intlayer/babel';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, getAppLogger } 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// Field-rename helper\n\n/**\n * Recursively renames user-defined keys in a compiled intlayer content value\n * using the provided `NestedRenameMap`.\n *\n * Traversal rules (mirrors `buildNestedRenameMapFromContent`):\n * - Arrays → each element is recursed into with the same rename map.\n * This mirrors the array pass-through in the source-code rename walk,\n * where numeric indices (e.g. [0]) are transparent.\n * - Object with `nodeType: 'translation'` → intlayer translation node.\n * Keys at this level (`nodeType`, `translation`) are NOT renamed.\n * Recurse into each per-locale value with the same rename map.\n * - Object without `nodeType` → user-defined record.\n * Rename its keys using the current rename map level, then recurse into\n * each value with that entry's `children` map.\n * - Primitives → returned as-is.\n */\nconst renameContentRecursively = (\n value: unknown,\n renameMap: NestedRenameMap\n): unknown => {\n // Arrays: each element is renamed with the same map (indices are transparent).\n if (Array.isArray(value)) {\n return (value as unknown[]).map((element) =>\n renameContentRecursively(element, renameMap)\n );\n }\n\n if (!value || typeof value !== 'object') return value;\n\n const record = value as Record<string, unknown>;\n\n // Translation node: recurse into each locale with the same rename map\n if (\n typeof record.nodeType === 'string' &&\n record.translation &&\n typeof record.translation === 'object' &&\n !Array.isArray(record.translation)\n ) {\n const renamedTranslation: Record<string, unknown> = {};\n for (const [locale, localeValue] of Object.entries(\n record.translation as Record<string, unknown>\n )) {\n renamedTranslation[locale] = renameContentRecursively(\n localeValue,\n renameMap\n );\n }\n return { ...record, translation: renamedTranslation };\n }\n\n // User-defined record: rename keys and recurse into values\n const result: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(record)) {\n const renameEntry = renameMap.get(key);\n if (renameEntry) {\n result[renameEntry.shortName] = renameContentRecursively(\n val,\n renameEntry.children\n );\n } else {\n result[key] = val; // key not in map – keep as-is (e.g. already-pruned)\n }\n }\n return result;\n};\n\n/**\n * Applies the nested field rename map to a parsed dictionary object and\n * returns the renamed copy. The top-level dict keys (`key`, `locale`, etc.)\n * are never touched; only keys inside `content` are renamed.\n */\nconst applyFieldRenameToDict = (\n dict: Record<string, unknown>,\n renameMap: NestedRenameMap\n): Record<string, unknown> => {\n const content = dict.content;\n if (!content || typeof content !== 'object' || Array.isArray(content))\n return dict;\n\n return {\n ...dict,\n content: renameContentRecursively(content, renameMap),\n };\n};\n\n/**\n * Discriminates a compiled qualified dictionary group (collections, variants)\n * from a plain dictionary. Mirrors `isQualifiedDictionaryGroup` from\n * `@intlayer/core` but is inlined here to avoid a runtime dependency on core\n * inside the build plugin.\n */\nconst isQualifiedDictionaryGroup = (\n dict: Record<string, unknown>\n): dict is Record<string, unknown> & {\n qualifierTypes: unknown;\n content: Record<string, unknown>;\n} =>\n 'qualifierTypes' in dict &&\n Array.isArray(dict.qualifierTypes) &&\n 'content' in dict &&\n typeof dict.content === 'object' &&\n dict.content !== null;\n\n/**\n * Applies the field-rename map to a single qualified-group content node (the\n * value stored under a composite id). Coordinates are not stored on the node —\n * they live in the key — so only the node's content fields are renamed.\n */\nconst minifyGroupEntryNode = (\n node: unknown,\n fieldRenameMap: NestedRenameMap | undefined\n): unknown =>\n fieldRenameMap &&\n fieldRenameMap.size > 0 &&\n node &&\n typeof node === 'object' &&\n !Array.isArray(node)\n ? renameContentRecursively(node, fieldRenameMap)\n : node;\n\n// Plugin\n\n/**\n * Returns the Vite plugin that minifies compiled dictionary JSON files by\n * removing all unnecessary whitespace and optionally renaming content field\n * names to short alphabetic aliases (a, b, c, …).\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 * The plugin is deliberately independent of the prune plugin: it can run\n * on its own when only `build.minify` is enabled. When both `purge` and\n * `minify` are active, the prune plugin runs first (it uses `enforce: 'pre'`\n * and is registered before this one); this plugin then receives the already-\n * pruned JSON, renames its field keys, and compacts it.\n *\n * Files listed in `pruneContext.dictionariesWithEdgeCases` are skipped:\n * those dictionaries encountered a structural issue during the prune phase\n * and should be left completely untouched to avoid shipping broken data.\n *\n * Field renaming (property mangling) is applied only for dictionaries that\n * have a known, finite field usage set in `pruneContext.dictionaryKeyToFieldRenameMap`.\n * The corresponding rename is also applied to source-file property accesses by\n * the babel rename pass inside `intlayerOptimize`. Internal intlayer fields\n * such as `nodeType` are never renamed.\n *\n * @param intlayerConfig - Resolved intlayer configuration.\n * @param pruneContext - Optional shared state from the prune plugin. When\n * provided, dictionaries flagged as edge-cases are\n * skipped and field renames are applied. Pass `null`\n * if the prune plugin is not active.\n */\nexport const intlayerMinify = (\n intlayerConfig: IntlayerConfig,\n pruneContext: PruneContext | null\n): PluginOption[] => {\n const logger = getAppLogger(intlayerConfig);\n\n const { optimize, minify } = intlayerConfig.build;\n const editorEnabled = intlayerConfig.editor.enabled;\n\n const { dictionariesDir, dynamicDictionariesDir, baseDir } =\n intlayerConfig.system;\n\n // Fetch-mode dictionaries are served from a remote API at runtime using their\n // original field names. Minifying them (renaming fields) would create a\n // mismatch between the server response and the renamed client-side accesses.\n const isDictionaryJsonFile = (absoluteFilePath: string): boolean =>\n absoluteFilePath.endsWith('.json') &&\n (absoluteFilePath.startsWith(dictionariesDir) ||\n absoluteFilePath.startsWith(dynamicDictionariesDir));\n\n const isMinifyEnabled = (\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 (!isOptimizeActive) return false;\n if (!minify) return false;\n if (!isBuildCommand) return false;\n\n if (editorEnabled) {\n runOnce(\n join(\n baseDir,\n '.intlayer',\n 'cache',\n 'intlayer-minify-editor-warning.lock'\n ),\n () =>\n logger([\n 'Dictionary minification 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 minifyPlugin: PluginOption = {\n name: 'vite-intlayer-dictionary-minify',\n // 'pre' so we receive raw JSON before Vite's built-in JSON → ESM\n // conversion. Declaration order in the plugin array ensures this runs\n // after the prune plugin (which is also 'pre' but registered earlier).\n enforce: 'pre',\n apply: isMinifyEnabled,\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 parsedDict: Record<string, unknown>;\n try {\n parsedDict = JSON.parse(rawJsonCode) as Record<string, unknown>;\n } catch (parseError) {\n logger(\n [\n `Could not parse`,\n formatPath(absoluteFilePath),\n `as JSON. Skipping minification for this file.`,\n parseError instanceof Error\n ? `(${parseError.message})`\n : String(parseError),\n ],\n { level: 'warn' }\n );\n return null;\n }\n\n const dictionaryKey =\n typeof parsedDict.key === 'string' ? parsedDict.key : undefined;\n\n // Skip edge-case dictionaries\n if (\n pruneContext &&\n dictionaryKey &&\n pruneContext.dictionariesWithEdgeCases.has(dictionaryKey)\n ) {\n return null; // structural issue flagged during prune – leave untouched\n }\n\n const fieldRenameMap =\n pruneContext && dictionaryKey\n ? pruneContext.dictionaryKeyToFieldRenameMap.get(dictionaryKey)\n : undefined;\n\n // Qualified groups (collections / variants) carry their payload as a\n // `content` map keyed by composite id + `qualifierTypes`. Preserve\n // `qualifierTypes` and the per-entry nodes (the coordinates live in the\n // keys); only the field-rename pass is applied to each node.\n if (isQualifiedDictionaryGroup(parsedDict)) {\n const minifiedContent: Record<string, unknown> = {};\n for (const [entryId, node] of Object.entries(parsedDict.content)) {\n minifiedContent[entryId] = minifyGroupEntryNode(node, fieldRenameMap);\n }\n\n return {\n code: JSON.stringify({\n key: parsedDict.key,\n qualifierTypes: parsedDict.qualifierTypes,\n content: minifiedContent,\n }),\n map: null,\n };\n }\n\n // Apply field rename (property mangling)\n if (fieldRenameMap && fieldRenameMap.size > 0) {\n parsedDict = applyFieldRenameToDict(parsedDict, fieldRenameMap);\n }\n\n // Strip top-level metadata – ship only key + content. The variant\n // identity of a qualified chunk is encoded in its composite id (the chunk\n // path), so no extra per-chunk side-data is needed for selector matching.\n return {\n code: JSON.stringify({\n key: parsedDict.key,\n content: parsedDict.content,\n }),\n map: null,\n };\n },\n };\n\n return [minifyPlugin];\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"intlayerMinifyPlugin.mjs","names":[],"sources":["../../src/intlayerMinifyPlugin.ts"],"sourcesContent":["import { join } from 'node:path';\nimport type { NestedRenameMap, PruneContext } from '@intlayer/babel';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, getAppLogger } 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// Field-rename helper\n\n/**\n * Recursively renames user-defined keys in a compiled intlayer content value\n * using the provided `NestedRenameMap`.\n *\n * Traversal rules (mirrors `buildNestedRenameMapFromContent`):\n * - Arrays → each element is recursed into with the same rename map.\n * This mirrors the array pass-through in the source-code rename walk,\n * where numeric indices (e.g. [0]) are transparent.\n * - Object with `nodeType: 'translation'` → intlayer translation node.\n * Keys at this level (`nodeType`, `translation`) are NOT renamed.\n * Recurse into each per-locale value with the same rename map.\n * - Object without `nodeType` → user-defined record.\n * Rename its keys using the current rename map level, then recurse into\n * each value with that entry's `children` map.\n * - Primitives → returned as-is.\n */\nconst renameContentRecursively = (\n value: unknown,\n renameMap: NestedRenameMap\n): unknown => {\n // Arrays: each element is renamed with the same map (indices are transparent).\n if (Array.isArray(value)) {\n return (value as unknown[]).map((element) =>\n renameContentRecursively(element, renameMap)\n );\n }\n\n if (!value || typeof value !== 'object') return value;\n\n const record = value as Record<string, unknown>;\n\n // Translation node: recurse into each locale with the same rename map\n if (\n typeof record.nodeType === 'string' &&\n record.translation &&\n typeof record.translation === 'object' &&\n !Array.isArray(record.translation)\n ) {\n const renamedTranslation: Record<string, unknown> = {};\n for (const [locale, localeValue] of Object.entries(\n record.translation as Record<string, unknown>\n )) {\n renamedTranslation[locale] = renameContentRecursively(\n localeValue,\n renameMap\n );\n }\n return { ...record, translation: renamedTranslation };\n }\n\n // User-defined record: rename keys and recurse into values\n const result: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(record)) {\n const renameEntry = renameMap.get(key);\n if (renameEntry) {\n result[renameEntry.shortName] = renameContentRecursively(\n val,\n renameEntry.children\n );\n } else {\n result[key] = val; // key not in map – keep as-is (e.g. already-pruned)\n }\n }\n return result;\n};\n\n/**\n * Applies the nested field rename map to a parsed dictionary object and\n * returns the renamed copy. The top-level dict keys (`key`, `locale`, etc.)\n * are never touched; only keys inside `content` are renamed.\n */\nconst applyFieldRenameToDict = (\n dict: Record<string, unknown>,\n renameMap: NestedRenameMap\n): Record<string, unknown> => {\n const content = dict.content;\n if (!content || typeof content !== 'object' || Array.isArray(content))\n return dict;\n\n return {\n ...dict,\n content: renameContentRecursively(content, renameMap),\n };\n};\n\n/**\n * Discriminates a compiled qualified dictionary group (collections, variants)\n * from a plain dictionary. Mirrors `isQualifiedDictionaryGroup` from\n * `@intlayer/core` but is inlined here to avoid a runtime dependency on core\n * inside the build plugin.\n */\nconst isQualifiedDictionaryGroup = (\n dict: Record<string, unknown>\n): dict is Record<string, unknown> & {\n qualifierTypes: unknown;\n content: Record<string, unknown>;\n} =>\n 'qualifierTypes' in dict &&\n Array.isArray(dict.qualifierTypes) &&\n 'content' in dict &&\n typeof dict.content === 'object' &&\n dict.content !== null;\n\n/**\n * Applies the field-rename map to a single qualified-group content node (the\n * value stored under a composite id). Coordinates are not stored on the node —\n * they live in the key — so only the node's content fields are renamed.\n */\nconst minifyGroupEntryNode = (\n node: unknown,\n fieldRenameMap: NestedRenameMap | undefined\n): unknown =>\n fieldRenameMap &&\n fieldRenameMap.size > 0 &&\n node &&\n typeof node === 'object' &&\n !Array.isArray(node)\n ? renameContentRecursively(node, fieldRenameMap)\n : node;\n\n// Plugin\n\n/**\n * Returns the Vite plugin that minifies compiled dictionary JSON files by\n * removing all unnecessary whitespace and optionally renaming content field\n * names to short alphabetic aliases (a, b, c, …).\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 * The plugin is deliberately independent of the prune plugin: it can run\n * on its own when only `build.minify` is enabled. When both `purge` and\n * `minify` are active, the prune plugin runs first (it uses `enforce: 'pre'`\n * and is registered before this one); this plugin then receives the already-\n * pruned JSON, renames its field keys, and compacts it.\n *\n * Files listed in `pruneContext.dictionariesWithEdgeCases` are skipped:\n * those dictionaries encountered a structural issue during the prune phase\n * and should be left completely untouched to avoid shipping broken data.\n *\n * Field renaming (property mangling) is applied only for dictionaries that\n * have a known, finite field usage set in `pruneContext.dictionaryKeyToFieldRenameMap`.\n * The corresponding rename is also applied to source-file property accesses by\n * the babel rename pass inside `intlayerOptimize`. Internal intlayer fields\n * such as `nodeType` are never renamed.\n *\n * @param intlayerConfig - Resolved intlayer configuration.\n * @param pruneContext - Optional shared state from the prune plugin. When\n * provided, dictionaries flagged as edge-cases are\n * skipped and field renames are applied. Pass `null`\n * if the prune plugin is not active.\n */\nexport const intlayerMinify = (\n intlayerConfig: IntlayerConfig,\n pruneContext: PruneContext | null\n): PluginOption[] => {\n const logger = getAppLogger(intlayerConfig);\n\n const { optimize, minify } = intlayerConfig.build;\n const editorEnabled = intlayerConfig.editor.enabled;\n\n const { dictionariesDir, dynamicDictionariesDir, baseDir } =\n intlayerConfig.system;\n\n // Fetch-mode dictionaries are served from a remote API at runtime using their\n // original field names. Minifying them (renaming fields) would create a\n // mismatch between the server response and the renamed client-side accesses.\n const isDictionaryJsonFile = (absoluteFilePath: string): boolean =>\n absoluteFilePath.endsWith('.json') &&\n (absoluteFilePath.startsWith(dictionariesDir) ||\n absoluteFilePath.startsWith(dynamicDictionariesDir));\n\n const isMinifyEnabled = (\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 (!isOptimizeActive) return false;\n if (!minify) return false;\n if (!isBuildCommand) return false;\n\n if (editorEnabled) {\n runOnce(\n join(\n baseDir,\n '.intlayer',\n 'cache',\n 'intlayer-minify-editor-warning.lock'\n ),\n () =>\n logger([\n 'Dictionary minification 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 minifyPlugin: PluginOption = {\n name: 'vite-intlayer-dictionary-minify',\n // 'pre' so we receive raw JSON before Vite's built-in JSON → ESM\n // conversion. Declaration order in the plugin array ensures this runs\n // after the prune plugin (which is also 'pre' but registered earlier).\n enforce: 'pre',\n apply: isMinifyEnabled,\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 parsedDict: Record<string, unknown>;\n try {\n parsedDict = JSON.parse(rawJsonCode) as Record<string, unknown>;\n } catch (parseError) {\n logger(\n [\n `Could not parse`,\n formatPath(absoluteFilePath),\n `as JSON. Skipping minification for this file.`,\n parseError instanceof Error\n ? `(${parseError.message})`\n : String(parseError),\n ],\n { level: 'warn' }\n );\n return null;\n }\n\n const dictionaryKey =\n typeof parsedDict.key === 'string' ? parsedDict.key : undefined;\n\n // Skip edge-case dictionaries\n if (\n pruneContext &&\n dictionaryKey &&\n pruneContext.dictionariesWithEdgeCases.has(dictionaryKey)\n ) {\n return null; // structural issue flagged during prune – leave untouched\n }\n\n const fieldRenameMap =\n pruneContext && dictionaryKey\n ? pruneContext.dictionaryKeyToFieldRenameMap.get(dictionaryKey)\n : undefined;\n\n // Qualified groups (collections / variants) carry their payload as a\n // `content` map keyed by composite id + `qualifierTypes`. Preserve\n // `qualifierTypes` and the per-entry nodes (the coordinates live in the\n // keys); only the field-rename pass is applied to each node.\n if (isQualifiedDictionaryGroup(parsedDict)) {\n const minifiedContent: Record<string, unknown> = {};\n for (const [entryId, node] of Object.entries(parsedDict.content)) {\n minifiedContent[entryId] = minifyGroupEntryNode(node, fieldRenameMap);\n }\n\n return {\n code: JSON.stringify({\n key: parsedDict.key,\n qualifierTypes: parsedDict.qualifierTypes,\n content: minifiedContent,\n }),\n map: null,\n };\n }\n\n // Apply field rename (property mangling)\n if (fieldRenameMap && fieldRenameMap.size > 0) {\n parsedDict = applyFieldRenameToDict(parsedDict, fieldRenameMap);\n }\n\n // Strip top-level metadata – ship only key + content. The variant\n // identity of a qualified chunk is encoded in its composite id (the chunk\n // path), so no extra per-chunk side-data is needed for selector matching.\n return {\n code: JSON.stringify({\n key: parsedDict.key,\n content: parsedDict.content,\n }),\n map: null,\n };\n },\n };\n\n return [minifyPlugin];\n};\n"],"mappings":"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"}
@@ -1,10 +1,11 @@
1
1
  import { createIntlayerProxyHandler } from "./intlayerProxyPlugin.mjs";
2
- import * as ANSIColors from "@intlayer/config/colors";
3
- import { colorize, getAppLogger } from "@intlayer/config/logger";
2
+ import { getAppLogger } from "@intlayer/config/logger";
4
3
  import { getConfiguration } from "@intlayer/config/node";
4
+ import { formatProxyEnabledMessage } from "@intlayer/core/localization";
5
5
 
6
6
  //#region src/intlayerNitroHandler.ts
7
- getAppLogger(getConfiguration())(`Intlayer proxy ${colorize("enabled", ANSIColors.GREEN)}`, { level: "info" });
7
+ const intlayerConfig = getConfiguration();
8
+ getAppLogger(intlayerConfig)(formatProxyEnabledMessage(false), { level: "info" });
8
9
  const nodeMiddleware = createIntlayerProxyHandler();
9
10
  /**
10
11
  * Native h3 v2 event handler for Nitro production servers (TanStack Start, Nuxt, etc.).
@@ -36,6 +37,8 @@ var intlayerNitroHandler_default = async (event) => new Promise((resolve) => {
36
37
  * - headers.host : domain-based locale routing
37
38
  * - headers.accept-language : browser Accept-Language fallback
38
39
  * - headers.x-forwarded-* : forwarded host/proto for reverse-proxy setups
40
+ * - headers.x-nitro-prerender : marks a request issued by Nitro's
41
+ * prerenderer, which keeps the generated page URL-driven
39
42
  *
40
43
  * headers must be a mutable plain object because setLocaleInStorageServer
41
44
  * writes Set-Cookie back via req.headers[name] = value.
@@ -48,7 +51,8 @@ var intlayerNitroHandler_default = async (event) => new Promise((resolve) => {
48
51
  host: event.headers.get("host") ?? "",
49
52
  "accept-language": event.headers.get("accept-language") ?? "",
50
53
  "x-forwarded-host": event.headers.get("x-forwarded-host") ?? "",
51
- "x-forwarded-proto": event.headers.get("x-forwarded-proto") ?? ""
54
+ "x-forwarded-proto": event.headers.get("x-forwarded-proto") ?? "",
55
+ "x-nitro-prerender": event.headers.get("x-nitro-prerender") ?? ""
52
56
  }
53
57
  };
54
58
  let responseStatusCode = 200;
@@ -1 +1 @@
1
- {"version":3,"file":"intlayerNitroHandler.mjs","names":[],"sources":["../../src/intlayerNitroHandler.ts"],"sourcesContent":["import type { IncomingMessage, ServerResponse } from 'node:http';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, getAppLogger } from '@intlayer/config/logger';\nimport { getConfiguration } from '@intlayer/config/node';\nimport { createIntlayerProxyHandler } from './intlayerProxyPlugin';\n\n/**\n * Minimal duck-type for h3 v2's H3Event.\n *\n * We intentionally avoid importing from 'h3' to keep this file runtime-agnostic —\n * Nitro bundles h3 internally and provides the populated event at runtime. Using a\n * structural type here means the file compiles without h3 in devDependencies and\n * works with any h3 v2-compatible runtime (Bun, Deno, Node).\n */\ntype H3EventLike = {\n /**\n * pathname + search — a computed getter on H3Event:\n * `return this.url.pathname + this.url.search`\n */\n readonly path: string;\n /**\n * Full URL object — a **plain property** (not a getter) on H3Event, safe to\n * replace for internal URL rewrites. After assignment, `event.path` will\n * automatically reflect the new pathname + search via the getter.\n */\n url: URL;\n /**\n * Web Fetch API Headers — always populated in h3 v2 regardless of preset\n * (Node, Bun, Deno). Use `.get(name)` instead of bracket-access.\n */\n readonly headers: Headers;\n /**\n * Lazy response object — created on first access; its `headers` carry outgoing\n * response headers (e.g. Set-Cookie) that h3 merges into the HTTP response.\n */\n readonly res: {\n readonly headers: Headers;\n };\n};\n\nconst intlayerConfig = getConfiguration();\nconst logger = getAppLogger(intlayerConfig);\nlogger(`Intlayer proxy ${colorize('enabled', ANSIColors.GREEN)}`, {\n level: 'info',\n});\n\nconst nodeMiddleware = createIntlayerProxyHandler();\n\n/**\n * Native h3 v2 event handler for Nitro production servers (TanStack Start, Nuxt, etc.).\n *\n * Unlike `fromNodeMiddleware` (h3 v1 API), this handler uses the Web Fetch API event\n * model exclusively and is therefore compatible with ALL Nitro presets — including Bun\n * and Deno — where `event.node` is `undefined` and `fromNodeMiddleware` crashes with\n * \"undefined is not an object (evaluating 'event.node.req')\".\n *\n * It bridges h3 v2 events to the Node.js-style `createIntlayerProxyHandler` middleware\n * via lightweight IncomingMessage / ServerResponse shims:\n *\n * - **Redirect** (301 / 5xx): builds a Web API `Response` and returns it — Nitro sends\n * the correct HTTP response to the browser.\n * - **Rewrite** (`next()` + modified `req.url`): replaces `event.url` with the rewritten\n * URL so `event.path` (a getter) returns the new pathname for downstream handlers and\n * the Nitro router.\n * - **Pass-through** (`next()`, URL unchanged): returns `undefined` — Nitro proceeds to\n * the next handler / route.\n */\nexport default async (event: H3EventLike): Promise<Response | void> =>\n new Promise<Response | void>((resolve) => {\n const initialPath = event.path;\n\n /**\n * Minimal IncomingMessage shim.\n *\n * Only the fields actually read by createIntlayerProxyHandler are populated:\n * - url : the current pathname + search, modified for rewrites\n * - headers.cookie : locale cookie detection\n * - headers.host : domain-based locale routing\n * - headers.accept-language : browser Accept-Language fallback\n * - headers.x-forwarded-* : forwarded host/proto for reverse-proxy setups\n *\n * headers must be a mutable plain object because setLocaleInStorageServer\n * writes Set-Cookie back via req.headers[name] = value.\n */\n const fakeReq = {\n url: initialPath,\n method: 'GET',\n headers: {\n cookie: event.headers.get('cookie') ?? '',\n host: event.headers.get('host') ?? '',\n 'accept-language': event.headers.get('accept-language') ?? '',\n 'x-forwarded-host': event.headers.get('x-forwarded-host') ?? '',\n 'x-forwarded-proto': event.headers.get('x-forwarded-proto') ?? '',\n } as Record<string, string>,\n } as unknown as IncomingMessage;\n\n let responseStatusCode = 200;\n const accumulatedHeaders: Record<string, string> = {};\n\n /**\n * Minimal ServerResponse shim.\n *\n * Implements only the methods that createIntlayerProxyHandler invokes:\n * writeHead() — status + Location header for 301 redirects\n * setHeader() — Set-Cookie written by setLocaleInStorageServer\n * getHeader() — defensive read-back (not strictly required but safe)\n * end() — finalises the response; for redirects this returns a\n * Web API Response object that Nitro sends to the client\n */\n const fakeRes = {\n writeHead(\n statusCode: number,\n headersArg?: Record<string, string | string[] | number> | string\n ) {\n // Capture the status code and any headers supplied alongside writeHead.\n responseStatusCode = statusCode;\n if (headersArg && typeof headersArg === 'object') {\n for (const [key, value] of Object.entries(headersArg)) {\n accumulatedHeaders[key.toLowerCase()] = Array.isArray(value)\n ? (value[0] ?? '')\n : String(value);\n }\n }\n return fakeRes;\n },\n setHeader(name: string, value: string | number | string[]) {\n // Capture Set-Cookie and other outgoing headers.\n accumulatedHeaders[name.toLowerCase()] = Array.isArray(value)\n ? (value[0] ?? '')\n : String(value);\n return fakeRes;\n },\n getHeader(name: string) {\n return accumulatedHeaders[name.toLowerCase()];\n },\n getHeaders() {\n return { ...accumulatedHeaders };\n },\n end(body?: string | Buffer | null) {\n // Build a Web API Response from accumulated status + headers + body.\n // For 3xx redirects the body is intentionally null.\n const webHeaders = new Headers();\n for (const [key, value] of Object.entries(accumulatedHeaders)) {\n webHeaders.set(key, value);\n }\n const isRedirect =\n responseStatusCode >= 300 && responseStatusCode < 400;\n resolve(\n new Response(\n isRedirect ? null : typeof body === 'string' ? body : null,\n {\n status: responseStatusCode,\n headers: webHeaders,\n }\n )\n );\n return fakeRes;\n },\n headersSent: false,\n } as unknown as ServerResponse<IncomingMessage>;\n\n nodeMiddleware(fakeReq, fakeRes, () => {\n // Middleware called next() — either a URL rewrite or a true pass-through.\n const rewrittenPath = fakeReq.url as string;\n\n if (rewrittenPath !== initialPath) {\n // The middleware rewrote the URL (e.g. /about → /en/about for locale prefix).\n // Replace event.url so that event.path (the getter: url.pathname + url.search)\n // returns the new path and the Nitro router matches the correct route.\n //\n // event.url is a plain property on h3 v2's H3Event (not a getter), so direct\n // assignment is safe. We use event.url.origin as the base so relative paths\n // resolve correctly; for path-only requests origin defaults to http://localhost.\n try {\n event.url = new URL(rewrittenPath, event.url.origin);\n } catch {\n console.error(\n '[intlayer-proxy] URL rewrite failed — invalid path:',\n rewrittenPath\n );\n }\n }\n\n // Forward any Set-Cookie or custom headers set by setLocaleInStorageServer to\n // the h3 v2 response object so they are included in the outgoing HTTP response.\n // Accessing event.res lazily creates the H3EventResponse (no cost if empty).\n if (Object.keys(accumulatedHeaders).length > 0) {\n for (const [key, value] of Object.entries(accumulatedHeaders)) {\n event.res.headers.set(key, value);\n }\n }\n\n resolve(undefined);\n });\n });\n"],"mappings":";;;;;;AAyCe,aADQ,iBACkB,CACpC,CAAC,CAAC,kBAAkB,SAAS,WAAW,WAAW,KAAK,KAAK,EAChE,OAAO,OACT,CAAC;AAED,MAAM,iBAAiB,2BAA2B;;;;;;;;;;;;;;;;;;;;AAqBlD,mCAAe,OAAO,UACpB,IAAI,SAA0B,YAAY;CACxC,MAAM,cAAc,MAAM;;;;;;;;;;;;;;CAe1B,MAAM,UAAU;EACd,KAAK;EACL,QAAQ;EACR,SAAS;GACP,QAAQ,MAAM,QAAQ,IAAI,QAAQ,KAAK;GACvC,MAAM,MAAM,QAAQ,IAAI,MAAM,KAAK;GACnC,mBAAmB,MAAM,QAAQ,IAAI,iBAAiB,KAAK;GAC3D,oBAAoB,MAAM,QAAQ,IAAI,kBAAkB,KAAK;GAC7D,qBAAqB,MAAM,QAAQ,IAAI,mBAAmB,KAAK;EACjE;CACF;CAEA,IAAI,qBAAqB;CACzB,MAAM,qBAA6C,CAAC;;;;;;;;;;;CAYpD,MAAM,UAAU;EACd,UACE,YACA,YACA;GAEA,qBAAqB;GACrB,IAAI,cAAc,OAAO,eAAe,UACtC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,UAAU,GAClD,mBAAmB,IAAI,YAAY,KAAK,MAAM,QAAQ,KAAK,IACtD,MAAM,MAAM,KACb,OAAO,KAAK;GAGpB,OAAO;EACT;EACA,UAAU,MAAc,OAAmC;GAEzD,mBAAmB,KAAK,YAAY,KAAK,MAAM,QAAQ,KAAK,IACvD,MAAM,MAAM,KACb,OAAO,KAAK;GAChB,OAAO;EACT;EACA,UAAU,MAAc;GACtB,OAAO,mBAAmB,KAAK,YAAY;EAC7C;EACA,aAAa;GACX,OAAO,EAAE,GAAG,mBAAmB;EACjC;EACA,IAAI,MAA+B;GAGjC,MAAM,aAAa,IAAI,QAAQ;GAC/B,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,kBAAkB,GAC1D,WAAW,IAAI,KAAK,KAAK;GAI3B,QACE,IAAI,SAFJ,sBAAsB,OAAO,qBAAqB,MAGnC,OAAO,OAAO,SAAS,WAAW,OAAO,MACtD;IACE,QAAQ;IACR,SAAS;GACX,CACF,CACF;GACA,OAAO;EACT;EACA,aAAa;CACf;CAEA,eAAe,SAAS,eAAe;EAErC,MAAM,gBAAgB,QAAQ;EAE9B,IAAI,kBAAkB,aAQpB,IAAI;GACF,MAAM,MAAM,IAAI,IAAI,eAAe,MAAM,IAAI,MAAM;EACrD,QAAQ;GACN,QAAQ,MACN,uDACA,aACF;EACF;EAMF,IAAI,OAAO,KAAK,kBAAkB,CAAC,CAAC,SAAS,GAC3C,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,kBAAkB,GAC1D,MAAM,IAAI,QAAQ,IAAI,KAAK,KAAK;EAIpC,QAAQ,MAAS;CACnB,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"intlayerNitroHandler.mjs","names":[],"sources":["../../src/intlayerNitroHandler.ts"],"sourcesContent":["import type { IncomingMessage, ServerResponse } from 'node:http';\nimport { getAppLogger } from '@intlayer/config/logger';\nimport { getConfiguration } from '@intlayer/config/node';\nimport { formatProxyEnabledMessage } from '@intlayer/core/localization';\nimport { createIntlayerProxyHandler } from './intlayerProxyPlugin';\n\n/**\n * Minimal duck-type for h3 v2's H3Event.\n *\n * We intentionally avoid importing from 'h3' to keep this file runtime-agnostic —\n * Nitro bundles h3 internally and provides the populated event at runtime. Using a\n * structural type here means the file compiles without h3 in devDependencies and\n * works with any h3 v2-compatible runtime (Bun, Deno, Node).\n */\ntype H3EventLike = {\n /**\n * pathname + search — a computed getter on H3Event:\n * `return this.url.pathname + this.url.search`\n */\n readonly path: string;\n /**\n * Full URL object — a **plain property** (not a getter) on H3Event, safe to\n * replace for internal URL rewrites. After assignment, `event.path` will\n * automatically reflect the new pathname + search via the getter.\n */\n url: URL;\n /**\n * Web Fetch API Headers — always populated in h3 v2 regardless of preset\n * (Node, Bun, Deno). Use `.get(name)` instead of bracket-access.\n */\n readonly headers: Headers;\n /**\n * Lazy response object — created on first access; its `headers` carry outgoing\n * response headers (e.g. Set-Cookie) that h3 merges into the HTTP response.\n */\n readonly res: {\n readonly headers: Headers;\n };\n};\n\nconst intlayerConfig = getConfiguration();\nconst logger = getAppLogger(intlayerConfig);\n// A Nitro server is a production server, so the stored locale always drives\n// redirects here — hence the `false`. This runs once per server process; the\n// Vite preview server deliberately does not add a second proxy layer on top of\n// this one (see `configurePreviewServer` in `intlayerProxyPlugin`).\nlogger(formatProxyEnabledMessage(false), {\n level: 'info',\n});\n\nconst nodeMiddleware = createIntlayerProxyHandler();\n\n/**\n * Native h3 v2 event handler for Nitro production servers (TanStack Start, Nuxt, etc.).\n *\n * Unlike `fromNodeMiddleware` (h3 v1 API), this handler uses the Web Fetch API event\n * model exclusively and is therefore compatible with ALL Nitro presets — including Bun\n * and Deno — where `event.node` is `undefined` and `fromNodeMiddleware` crashes with\n * \"undefined is not an object (evaluating 'event.node.req')\".\n *\n * It bridges h3 v2 events to the Node.js-style `createIntlayerProxyHandler` middleware\n * via lightweight IncomingMessage / ServerResponse shims:\n *\n * - **Redirect** (301 / 5xx): builds a Web API `Response` and returns it — Nitro sends\n * the correct HTTP response to the browser.\n * - **Rewrite** (`next()` + modified `req.url`): replaces `event.url` with the rewritten\n * URL so `event.path` (a getter) returns the new pathname for downstream handlers and\n * the Nitro router.\n * - **Pass-through** (`next()`, URL unchanged): returns `undefined` — Nitro proceeds to\n * the next handler / route.\n */\nexport default async (event: H3EventLike): Promise<Response | void> =>\n new Promise<Response | void>((resolve) => {\n const initialPath = event.path;\n\n /**\n * Minimal IncomingMessage shim.\n *\n * Only the fields actually read by createIntlayerProxyHandler are populated:\n * - url : the current pathname + search, modified for rewrites\n * - headers.cookie : locale cookie detection\n * - headers.host : domain-based locale routing\n * - headers.accept-language : browser Accept-Language fallback\n * - headers.x-forwarded-* : forwarded host/proto for reverse-proxy setups\n * - headers.x-nitro-prerender : marks a request issued by Nitro's\n * prerenderer, which keeps the generated page URL-driven\n *\n * headers must be a mutable plain object because setLocaleInStorageServer\n * writes Set-Cookie back via req.headers[name] = value.\n */\n const fakeReq = {\n url: initialPath,\n method: 'GET',\n headers: {\n cookie: event.headers.get('cookie') ?? '',\n host: event.headers.get('host') ?? '',\n 'accept-language': event.headers.get('accept-language') ?? '',\n 'x-forwarded-host': event.headers.get('x-forwarded-host') ?? '',\n 'x-forwarded-proto': event.headers.get('x-forwarded-proto') ?? '',\n 'x-nitro-prerender': event.headers.get('x-nitro-prerender') ?? '',\n } as Record<string, string>,\n } as unknown as IncomingMessage;\n\n let responseStatusCode = 200;\n const accumulatedHeaders: Record<string, string> = {};\n\n /**\n * Minimal ServerResponse shim.\n *\n * Implements only the methods that createIntlayerProxyHandler invokes:\n * writeHead() — status + Location header for 301 redirects\n * setHeader() — Set-Cookie written by setLocaleInStorageServer\n * getHeader() — defensive read-back (not strictly required but safe)\n * end() — finalises the response; for redirects this returns a\n * Web API Response object that Nitro sends to the client\n */\n const fakeRes = {\n writeHead(\n statusCode: number,\n headersArg?: Record<string, string | string[] | number> | string\n ) {\n // Capture the status code and any headers supplied alongside writeHead.\n responseStatusCode = statusCode;\n if (headersArg && typeof headersArg === 'object') {\n for (const [key, value] of Object.entries(headersArg)) {\n accumulatedHeaders[key.toLowerCase()] = Array.isArray(value)\n ? (value[0] ?? '')\n : String(value);\n }\n }\n return fakeRes;\n },\n setHeader(name: string, value: string | number | string[]) {\n // Capture Set-Cookie and other outgoing headers.\n accumulatedHeaders[name.toLowerCase()] = Array.isArray(value)\n ? (value[0] ?? '')\n : String(value);\n return fakeRes;\n },\n getHeader(name: string) {\n return accumulatedHeaders[name.toLowerCase()];\n },\n getHeaders() {\n return { ...accumulatedHeaders };\n },\n end(body?: string | Buffer | null) {\n // Build a Web API Response from accumulated status + headers + body.\n // For 3xx redirects the body is intentionally null.\n const webHeaders = new Headers();\n for (const [key, value] of Object.entries(accumulatedHeaders)) {\n webHeaders.set(key, value);\n }\n const isRedirect =\n responseStatusCode >= 300 && responseStatusCode < 400;\n resolve(\n new Response(\n isRedirect ? null : typeof body === 'string' ? body : null,\n {\n status: responseStatusCode,\n headers: webHeaders,\n }\n )\n );\n return fakeRes;\n },\n headersSent: false,\n } as unknown as ServerResponse<IncomingMessage>;\n\n nodeMiddleware(fakeReq, fakeRes, () => {\n // Middleware called next() — either a URL rewrite or a true pass-through.\n const rewrittenPath = fakeReq.url as string;\n\n if (rewrittenPath !== initialPath) {\n // The middleware rewrote the URL (e.g. /about → /en/about for locale prefix).\n // Replace event.url so that event.path (the getter: url.pathname + url.search)\n // returns the new path and the Nitro router matches the correct route.\n //\n // event.url is a plain property on h3 v2's H3Event (not a getter), so direct\n // assignment is safe. We use event.url.origin as the base so relative paths\n // resolve correctly; for path-only requests origin defaults to http://localhost.\n try {\n event.url = new URL(rewrittenPath, event.url.origin);\n } catch {\n console.error(\n '[intlayer-proxy] URL rewrite failed — invalid path:',\n rewrittenPath\n );\n }\n }\n\n // Forward any Set-Cookie or custom headers set by setLocaleInStorageServer to\n // the h3 v2 response object so they are included in the outgoing HTTP response.\n // Accessing event.res lazily creates the H3EventResponse (no cost if empty).\n if (Object.keys(accumulatedHeaders).length > 0) {\n for (const [key, value] of Object.entries(accumulatedHeaders)) {\n event.res.headers.set(key, value);\n }\n }\n\n resolve(undefined);\n });\n });\n"],"mappings":";;;;;;AAwCA,MAAM,iBAAiB,iBAAiB;AACzB,aAAa,cAKvB,CAAC,CAAC,0BAA0B,KAAK,GAAG,EACvC,OAAO,OACT,CAAC;AAED,MAAM,iBAAiB,2BAA2B;;;;;;;;;;;;;;;;;;;;AAqBlD,mCAAe,OAAO,UACpB,IAAI,SAA0B,YAAY;CACxC,MAAM,cAAc,MAAM;;;;;;;;;;;;;;;;CAiB1B,MAAM,UAAU;EACd,KAAK;EACL,QAAQ;EACR,SAAS;GACP,QAAQ,MAAM,QAAQ,IAAI,QAAQ,KAAK;GACvC,MAAM,MAAM,QAAQ,IAAI,MAAM,KAAK;GACnC,mBAAmB,MAAM,QAAQ,IAAI,iBAAiB,KAAK;GAC3D,oBAAoB,MAAM,QAAQ,IAAI,kBAAkB,KAAK;GAC7D,qBAAqB,MAAM,QAAQ,IAAI,mBAAmB,KAAK;GAC/D,qBAAqB,MAAM,QAAQ,IAAI,mBAAmB,KAAK;EACjE;CACF;CAEA,IAAI,qBAAqB;CACzB,MAAM,qBAA6C,CAAC;;;;;;;;;;;CAYpD,MAAM,UAAU;EACd,UACE,YACA,YACA;GAEA,qBAAqB;GACrB,IAAI,cAAc,OAAO,eAAe,UACtC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,UAAU,GAClD,mBAAmB,IAAI,YAAY,KAAK,MAAM,QAAQ,KAAK,IACtD,MAAM,MAAM,KACb,OAAO,KAAK;GAGpB,OAAO;EACT;EACA,UAAU,MAAc,OAAmC;GAEzD,mBAAmB,KAAK,YAAY,KAAK,MAAM,QAAQ,KAAK,IACvD,MAAM,MAAM,KACb,OAAO,KAAK;GAChB,OAAO;EACT;EACA,UAAU,MAAc;GACtB,OAAO,mBAAmB,KAAK,YAAY;EAC7C;EACA,aAAa;GACX,OAAO,EAAE,GAAG,mBAAmB;EACjC;EACA,IAAI,MAA+B;GAGjC,MAAM,aAAa,IAAI,QAAQ;GAC/B,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,kBAAkB,GAC1D,WAAW,IAAI,KAAK,KAAK;GAI3B,QACE,IAAI,SAFJ,sBAAsB,OAAO,qBAAqB,MAGnC,OAAO,OAAO,SAAS,WAAW,OAAO,MACtD;IACE,QAAQ;IACR,SAAS;GACX,CACF,CACF;GACA,OAAO;EACT;EACA,aAAa;CACf;CAEA,eAAe,SAAS,eAAe;EAErC,MAAM,gBAAgB,QAAQ;EAE9B,IAAI,kBAAkB,aAQpB,IAAI;GACF,MAAM,MAAM,IAAI,IAAI,eAAe,MAAM,IAAI,MAAM;EACrD,QAAQ;GACN,QAAQ,MACN,uDACA,aACF;EACF;EAMF,IAAI,OAAO,KAAK,kBAAkB,CAAC,CAAC,SAAS,GAC3C,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,kBAAkB,GAC1D,MAAM,IAAI,QAAQ,IAAI,KAAK,KAAK;EAIpC,QAAQ,MAAS;CACnB,CAAC;AACH,CAAC"}
@@ -70,7 +70,8 @@ const intlayerOptimize = async (intlayerConfig, pruneContext, compatCallers) =>
70
70
  apply: isAnalysisEnabled,
71
71
  buildStart: async () => {
72
72
  if (!pruneContext) return;
73
- const usageCheckRegex = buildUsageCheckRegex((compatCallers ?? []).map((caller) => caller.callerName));
73
+ const extraCallerNames = (compatCallers ?? []).map((caller) => caller.callerName);
74
+ const usageCheckRegex = buildUsageCheckRegex(extraCallerNames);
74
75
  await Promise.all(componentFilesList.map(async (sourceFilePath) => {
75
76
  if (!SOURCE_FILE_REGEX.test(sourceFilePath)) return;
76
77
  let sourceCode;
@@ -241,7 +242,7 @@ const intlayerOptimize = async (intlayerConfig, pruneContext, compatCallers) =>
241
242
  `dictionar${partiallyMinifiedDictionariesCount === 1 ? "y" : "ies"}`,
242
243
  `(preserved nested keys for opaque fields).`
243
244
  ]);
244
- }, { cacheTimeoutMs: 1e3 * 5 });
245
+ }, { cacheTimeoutMs: 5e3 });
245
246
  }
246
247
  }
247
248
  },
@@ -260,7 +261,7 @@ const intlayerOptimize = async (intlayerConfig, pruneContext, compatCallers) =>
260
261
  ]);
261
262
  if (!editorEnabled && minify) logger(`Dictionary minification ${colorize("enabled", ANSIColors.GREEN)}`);
262
263
  if (!editorEnabled && purge) logger(`Dictionary purge unused keys ${colorize("enabled", ANSIColors.GREEN)}`);
263
- }, { cacheTimeoutMs: 1e3 * 10 });
264
+ }, { cacheTimeoutMs: 1e4 });
264
265
  return true;
265
266
  },
266
267
  transform: async (sourceCode, moduleId, options) => {