vite-intlayer 7.3.8 → 7.3.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/IntlayerCompilerPlugin.cjs +68 -11
- package/dist/cjs/IntlayerCompilerPlugin.cjs.map +1 -1
- package/dist/cjs/intlayerPrunePlugin.cjs +1 -1
- package/dist/esm/IntlayerCompilerPlugin.mjs +69 -11
- package/dist/esm/IntlayerCompilerPlugin.mjs.map +1 -1
- package/dist/esm/intlayerPrunePlugin.mjs +1 -1
- package/package.json +10 -10
|
@@ -6,8 +6,6 @@ let node_path = require("node:path");
|
|
|
6
6
|
let __intlayer_babel = require("@intlayer/babel");
|
|
7
7
|
let __intlayer_chokidar = require("@intlayer/chokidar");
|
|
8
8
|
let __intlayer_config = require("@intlayer/config");
|
|
9
|
-
let fast_glob = require("fast-glob");
|
|
10
|
-
fast_glob = require_rolldown_runtime.__toESM(fast_glob);
|
|
11
9
|
|
|
12
10
|
//#region src/IntlayerCompilerPlugin.ts
|
|
13
11
|
/**
|
|
@@ -37,9 +35,45 @@ const intlayerCompiler = (options) => {
|
|
|
37
35
|
let filesList = [];
|
|
38
36
|
let babel = null;
|
|
39
37
|
let pendingDictionaryWrite = null;
|
|
38
|
+
const recentlyProcessedFiles = /* @__PURE__ */ new Map();
|
|
39
|
+
const recentDictionaryContent = /* @__PURE__ */ new Map();
|
|
40
|
+
const DEBOUNCE_MS = 500;
|
|
40
41
|
const configOptions = options?.configOptions;
|
|
41
42
|
const customCompilerConfig = options?.compilerConfig;
|
|
42
43
|
/**
|
|
44
|
+
* Check if a file was recently processed (within debounce window)
|
|
45
|
+
* and should be skipped to prevent infinite loops
|
|
46
|
+
*/
|
|
47
|
+
const wasRecentlyProcessed = (filePath) => {
|
|
48
|
+
const lastProcessed = recentlyProcessedFiles.get(filePath);
|
|
49
|
+
if (!lastProcessed) return false;
|
|
50
|
+
return Date.now() - lastProcessed < DEBOUNCE_MS;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Mark a file as recently processed
|
|
54
|
+
*/
|
|
55
|
+
const markAsProcessed = (filePath) => {
|
|
56
|
+
recentlyProcessedFiles.set(filePath, Date.now());
|
|
57
|
+
const now = Date.now();
|
|
58
|
+
for (const [path, timestamp] of recentlyProcessedFiles.entries()) if (now - timestamp > DEBOUNCE_MS * 2) recentlyProcessedFiles.delete(path);
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Create a simple hash of content for comparison
|
|
62
|
+
* Used to detect if dictionary content has actually changed
|
|
63
|
+
*/
|
|
64
|
+
const hashContent = (content) => {
|
|
65
|
+
return JSON.stringify(Object.keys(content).sort().map((k) => [k, content[k]]));
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Check if dictionary content has changed since last write
|
|
69
|
+
*/
|
|
70
|
+
const hasDictionaryContentChanged = (dictionaryKey, content) => {
|
|
71
|
+
const newHash = hashContent(content);
|
|
72
|
+
if (recentDictionaryContent.get(dictionaryKey) === newHash) return false;
|
|
73
|
+
recentDictionaryContent.set(dictionaryKey, newHash);
|
|
74
|
+
return true;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
43
77
|
* Get compiler config from intlayer config or custom options
|
|
44
78
|
*/
|
|
45
79
|
const getCompilerConfig = () => {
|
|
@@ -47,7 +81,11 @@ const intlayerCompiler = (options) => {
|
|
|
47
81
|
return {
|
|
48
82
|
enabled: customCompilerConfig?.enabled ?? rawConfig.compiler?.enabled ?? true,
|
|
49
83
|
transformPattern: customCompilerConfig?.transformPattern ?? rawConfig.compiler?.transformPattern ?? config.build.traversePattern,
|
|
50
|
-
excludePattern:
|
|
84
|
+
excludePattern: [
|
|
85
|
+
...customCompilerConfig?.excludePattern ?? [],
|
|
86
|
+
"**/node_modules/**",
|
|
87
|
+
...config.content.fileExtensions.map((pattern) => `*${pattern}`)
|
|
88
|
+
],
|
|
51
89
|
outputDir: customCompilerConfig?.outputDir ?? rawConfig.compiler?.outputDir ?? "compiler"
|
|
52
90
|
};
|
|
53
91
|
};
|
|
@@ -160,15 +198,19 @@ const intlayerCompiler = (options) => {
|
|
|
160
198
|
/**
|
|
161
199
|
* Build the list of files to transform based on configuration patterns
|
|
162
200
|
*/
|
|
163
|
-
const
|
|
164
|
-
const { baseDir } = config.content;
|
|
201
|
+
const buildFilesListFn = async () => {
|
|
202
|
+
const { baseDir, fileExtensions } = config.content;
|
|
165
203
|
const compilerConfig = getCompilerConfig();
|
|
166
|
-
const patterns = Array.isArray(compilerConfig.transformPattern) ? compilerConfig.transformPattern : [compilerConfig.transformPattern];
|
|
167
204
|
const excludePatterns = Array.isArray(compilerConfig.excludePattern) ? compilerConfig.excludePattern : [compilerConfig.excludePattern];
|
|
168
|
-
filesList =
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
205
|
+
filesList = (0, __intlayer_chokidar.buildFilesList)({
|
|
206
|
+
transformPattern: compilerConfig.transformPattern,
|
|
207
|
+
excludePattern: [
|
|
208
|
+
...excludePatterns,
|
|
209
|
+
"**/node_modules/**",
|
|
210
|
+
...fileExtensions.map((pattern) => `**/*${pattern}`)
|
|
211
|
+
],
|
|
212
|
+
baseDir
|
|
213
|
+
});
|
|
172
214
|
};
|
|
173
215
|
/**
|
|
174
216
|
* Initialize the compiler with the given mode
|
|
@@ -181,7 +223,7 @@ const intlayerCompiler = (options) => {
|
|
|
181
223
|
} catch {
|
|
182
224
|
logger("Failed to load @babel/core. Transformation will be disabled.", { level: "warn" });
|
|
183
225
|
}
|
|
184
|
-
await
|
|
226
|
+
await buildFilesListFn();
|
|
185
227
|
};
|
|
186
228
|
/**
|
|
187
229
|
* Vite hook: config
|
|
@@ -230,6 +272,14 @@ const intlayerCompiler = (options) => {
|
|
|
230
272
|
const handleHotUpdate = async (ctx) => {
|
|
231
273
|
const { file, server, modules } = ctx;
|
|
232
274
|
if (filesList.some((f) => f === file)) {
|
|
275
|
+
if (wasRecentlyProcessed(file)) {
|
|
276
|
+
logger(`${(0, __intlayer_config.colorize)("Compiler:", __intlayer_config.ANSIColors.GREY_DARK)} Skipping re-transform of ${(0, __intlayer_config.colorizePath)((0, node_path.relative)(projectRoot, file))} (recently processed)`, {
|
|
277
|
+
level: "info",
|
|
278
|
+
isVerbose: true
|
|
279
|
+
});
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
markAsProcessed(file);
|
|
233
283
|
for (const mod of modules) server.moduleGraph.invalidateModule(mod);
|
|
234
284
|
try {
|
|
235
285
|
await transformHandler(await (0, node_fs_promises.readFile)(file, "utf-8"), file);
|
|
@@ -255,6 +305,13 @@ const intlayerCompiler = (options) => {
|
|
|
255
305
|
*/
|
|
256
306
|
const writeAndBuildDictionary = async (result) => {
|
|
257
307
|
const { dictionaryKey, content } = result;
|
|
308
|
+
if (!hasDictionaryContentChanged(dictionaryKey, content)) {
|
|
309
|
+
logger(`${(0, __intlayer_config.colorize)("Compiler:", __intlayer_config.ANSIColors.GREY_DARK)} Skipping dictionary ${(0, __intlayer_config.colorizeKey)(dictionaryKey)} (content unchanged)`, {
|
|
310
|
+
level: "info",
|
|
311
|
+
isVerbose: true
|
|
312
|
+
});
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
258
315
|
const outputDir = getOutputDir();
|
|
259
316
|
const { defaultLocale } = config.internationalization;
|
|
260
317
|
const isPerLocaleFile = Boolean(config?.dictionary?.locale);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IntlayerCompilerPlugin.cjs","names":["config: IntlayerConfig","logger: ReturnType<typeof getAppLogger>","filesList: string[]","babel: any","pendingDictionaryWrite: Promise<void> | null","mergedContent: DictionaryContentMap","ANSIColors","mergedContent: Record<string, string>","fg","compilerMode: CompilerMode","mergedDictionary: Dictionary","dictionaryToBuild: Dictionary","intlayerExtractBabelPlugin","result:\n | { code: string; map?: unknown; extracted?: boolean }\n | null\n | undefined"],"sources":["../../src/IntlayerCompilerPlugin.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { mkdir, readFile } from 'node:fs/promises';\nimport { createRequire } from 'node:module';\nimport { join, relative } from 'node:path';\nimport {\n type ExtractResult,\n intlayerExtractBabelPlugin,\n} from '@intlayer/babel';\nimport {\n buildDictionary,\n prepareIntlayer,\n writeContentDeclaration,\n} from '@intlayer/chokidar';\nimport {\n ANSIColors,\n colorize,\n colorizeKey,\n colorizePath,\n type GetConfigurationOptions,\n getAppLogger,\n getConfiguration,\n} from '@intlayer/config';\nimport type {\n CompilerConfig,\n Dictionary,\n IntlayerConfig,\n} from '@intlayer/types';\nimport fg from 'fast-glob';\n\n/**\n * Translation node structure used in dictionaries\n */\ntype TranslationNode = {\n nodeType: 'translation';\n translation: Record<string, string>;\n};\n\n/**\n * Dictionary content structure - map of keys to translation nodes\n */\ntype DictionaryContentMap = Record<string, TranslationNode>;\n\n/**\n * Mode of the compiler\n * - 'dev': Development mode with HMR support\n * - 'build': Production build mode\n */\nexport type CompilerMode = 'dev' | 'build';\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 */\nexport const intlayerCompiler = (options?: IntlayerCompilerOptions): any => {\n // Private state\n let config: IntlayerConfig;\n let logger: ReturnType<typeof getAppLogger>;\n let projectRoot = '';\n let filesList: string[] = [];\n let babel: any = null;\n\n // Promise to track dictionary writing (for synchronization)\n let pendingDictionaryWrite: Promise<void> | null = null;\n\n const configOptions = options?.configOptions;\n const customCompilerConfig = options?.compilerConfig;\n\n /**\n * Get compiler config from intlayer config or custom options\n */\n const getCompilerConfig = () => {\n // Access compiler config from the raw config (may not be in the type)\n const rawConfig = config as IntlayerConfig & {\n compiler?: Partial<CompilerConfig>;\n };\n\n return {\n enabled:\n customCompilerConfig?.enabled ?? rawConfig.compiler?.enabled ?? true,\n transformPattern:\n customCompilerConfig?.transformPattern ??\n rawConfig.compiler?.transformPattern ??\n config.build.traversePattern,\n excludePattern: customCompilerConfig?.excludePattern ??\n rawConfig.compiler?.excludePattern ?? ['**/node_modules/**'],\n outputDir:\n customCompilerConfig?.outputDir ??\n rawConfig.compiler?.outputDir ??\n 'compiler',\n };\n };\n\n /**\n * Get the output directory path for compiler dictionaries\n */\n const getOutputDir = (): string => {\n const { baseDir } = config.content;\n const compilerConfig = getCompilerConfig();\n return join(baseDir, compilerConfig.outputDir);\n };\n\n /**\n * Get the file path for a dictionary\n */\n const getDictionaryFilePath = (dictionaryKey: string): string => {\n const outputDir = getOutputDir();\n return join(outputDir, `${dictionaryKey}.content.json`);\n };\n\n /**\n * Read an existing dictionary file if it exists\n */\n const readExistingDictionary = async (\n dictionaryKey: string\n ): Promise<Dictionary | null> => {\n const filePath = getDictionaryFilePath(dictionaryKey);\n\n if (!existsSync(filePath)) {\n return null;\n }\n\n try {\n const content = await readFile(filePath, 'utf-8');\n return JSON.parse(content) as Dictionary;\n } catch {\n return null;\n }\n };\n\n /**\n * Merge extracted content with existing dictionary for multilingual format.\n * - Keys in extracted but not in existing: added with default locale only\n * - Keys in both: preserve existing translations, update default locale value\n * - Keys in existing but not in extracted: removed (no longer in source)\n */\n const mergeWithExistingMultilingualDictionary = (\n extractedContent: Record<string, string>,\n existingDictionary: Dictionary | null,\n defaultLocale: string\n ): DictionaryContentMap => {\n const mergedContent: DictionaryContentMap = {};\n const existingContent = existingDictionary?.content as\n | DictionaryContentMap\n | undefined;\n\n for (const [key, value] of Object.entries(extractedContent)) {\n const existingEntry = existingContent?.[key];\n\n if (\n existingEntry &&\n existingEntry.nodeType === 'translation' &&\n existingEntry.translation\n ) {\n const oldValue = existingEntry.translation[defaultLocale];\n const isUpdated = oldValue !== value;\n\n // Key exists in both - preserve existing translations, update default locale\n mergedContent[key] = {\n nodeType: 'translation',\n translation: {\n ...existingEntry.translation,\n [defaultLocale]: value,\n },\n };\n\n if (isUpdated) {\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Updated \"${key}\" [${defaultLocale}]: \"${oldValue?.slice(0, 30)}...\" → \"${value.slice(0, 30)}...\"`,\n { level: 'info', isVerbose: true }\n );\n }\n } else {\n // New key - add with default locale only\n mergedContent[key] = {\n nodeType: 'translation',\n translation: {\n [defaultLocale]: value,\n },\n };\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Added new key \"${key}\"`,\n {\n level: 'info',\n isVerbose: true,\n }\n );\n }\n }\n\n // Log removed keys\n if (existingContent) {\n const removedKeys = Object.keys(existingContent).filter(\n (key) => !(key in extractedContent)\n );\n for (const key of removedKeys) {\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Removed key \"${key}\" (no longer in source)`,\n {\n level: 'info',\n isVerbose: true,\n }\n );\n }\n }\n\n return mergedContent;\n };\n\n /**\n * Merge extracted content with existing dictionary for per-locale format.\n * - Keys in extracted but not in existing: added\n * - Keys in both: update value\n * - Keys in existing but not in extracted: removed (no longer in source)\n */\n const mergeWithExistingPerLocaleDictionary = (\n extractedContent: Record<string, string>,\n existingDictionary: Dictionary | null,\n defaultLocale: string\n ): Record<string, string> => {\n const mergedContent: Record<string, string> = {};\n const existingContent = existingDictionary?.content as\n | Record<string, string>\n | undefined;\n\n for (const [key, value] of Object.entries(extractedContent)) {\n const existingValue = existingContent?.[key];\n\n if (existingValue && typeof existingValue === 'string') {\n const isUpdated = existingValue !== value;\n\n mergedContent[key] = value;\n\n if (isUpdated) {\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Updated \"${key}\" [${defaultLocale}]: \"${existingValue?.slice(0, 30)}...\" → \"${value.slice(0, 30)}...\"`,\n { level: 'info', isVerbose: true }\n );\n }\n } else {\n // New key\n mergedContent[key] = value;\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Added new key \"${key}\"`,\n {\n level: 'info',\n isVerbose: true,\n }\n );\n }\n }\n\n // Log removed keys\n if (existingContent) {\n const removedKeys = Object.keys(existingContent).filter(\n (key) => !(key in extractedContent)\n );\n for (const key of removedKeys) {\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Removed key \"${key}\" (no longer in source)`,\n {\n level: 'info',\n isVerbose: true,\n }\n );\n }\n }\n\n return mergedContent;\n };\n\n /**\n * Build the list of files to transform based on configuration patterns\n */\n const buildFilesList = async (): Promise<void> => {\n const { baseDir } = config.content;\n const compilerConfig = getCompilerConfig();\n\n const patterns = Array.isArray(compilerConfig.transformPattern)\n ? compilerConfig.transformPattern\n : [compilerConfig.transformPattern];\n const excludePatterns = Array.isArray(compilerConfig.excludePattern)\n ? compilerConfig.excludePattern\n : [compilerConfig.excludePattern];\n\n filesList = fg\n .sync(patterns, {\n cwd: baseDir,\n ignore: excludePatterns,\n })\n .map((file) => join(baseDir, file));\n };\n\n /**\n * Initialize the compiler with the given mode\n */\n const init = async (_compilerMode: CompilerMode): Promise<void> => {\n config = getConfiguration(configOptions);\n logger = getAppLogger(config);\n\n // Load Babel dynamically\n try {\n const localRequire = createRequire(import.meta.url);\n babel = localRequire('@babel/core');\n } catch {\n logger('Failed to load @babel/core. Transformation will be disabled.', {\n level: 'warn',\n });\n }\n\n // Build files list for transformation\n await buildFilesList();\n };\n\n /**\n * Vite hook: config\n * Called before Vite config is resolved - perfect time to prepare dictionaries\n */\n const configHook = async (\n _config: unknown,\n env: { command: string; mode: string }\n ): Promise<void> => {\n // Initialize config early\n config = getConfiguration(configOptions);\n logger = getAppLogger(config);\n\n const isDevCommand = env.command === 'serve' && env.mode === 'development';\n const isBuildCommand = env.command === 'build';\n\n // Prepare all existing dictionaries (builds them to .intlayer/dictionary/)\n // This ensures built dictionaries exist before the prune plugin runs\n if (isDevCommand || isBuildCommand) {\n await prepareIntlayer(config, {\n clean: isBuildCommand,\n cacheTimeoutMs: isBuildCommand\n ? 1000 * 30 // 30 seconds for build\n : 1000 * 60 * 60, // 1 hour for dev\n });\n }\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 }): Promise<void> => {\n const compilerMode: CompilerMode = viteConfig.env?.DEV ? 'dev' : 'build';\n projectRoot = viteConfig.root;\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 // Autonomous compiler - no need to prepare dictionaries\n // Content is extracted inline during transformation\n logger('Intlayer compiler initialized', {\n level: 'info',\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 * Configure the dev server\n */\n const configureServer = async (): Promise<void> => {\n // In autonomous mode, we don't need file watching for dictionaries\n // Content is extracted inline during transformation\n };\n\n /**\n * Vite hook: handleHotUpdate\n * Handles HMR for content files - invalidates cache and triggers re-transform\n */\n const handleHotUpdate = async (ctx: any): Promise<unknown[] | undefined> => {\n const { file, server, modules } = ctx;\n\n // Check if this is a file we should transform\n const isTransformableFile = filesList.some((f) => f === file);\n\n if (isTransformableFile) {\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 return [];\n }\n\n return undefined;\n };\n\n /**\n * Write and build a single dictionary immediately\n * This is called during transform to ensure dictionaries are always up-to-date.\n *\n * The merge strategy:\n * - New keys are added with the default locale only\n * - Existing keys preserve their translations, with default locale updated\n * - Keys no longer in source are removed\n *\n * Dictionary format:\n * - Per-locale: When config.dictionary.locale is set, content is simple strings with locale property\n * - Multilingual: When not set, content is wrapped in translation nodes without locale property\n */\n const writeAndBuildDictionary = async (\n result: ExtractResult\n ): Promise<void> => {\n const { dictionaryKey, content } = result;\n\n const outputDir = getOutputDir();\n const { defaultLocale } = config.internationalization;\n\n // Check if per-locale format is configured\n // When config.dictionary.locale is set, use per-locale format (simple strings with locale property)\n // Otherwise, use multilingual format (content wrapped in TranslationNode objects)\n const isPerLocaleFile = Boolean(config?.dictionary?.locale);\n\n // Ensure output directory exists\n await mkdir(outputDir, { recursive: true });\n\n // Read existing dictionary to preserve translations and metadata\n const existingDictionary = await readExistingDictionary(dictionaryKey);\n\n const relativeFilePath = join(\n relative(config.content.baseDir, outputDir),\n `${dictionaryKey}.content.json`\n );\n\n // Build dictionary based on format - matching transformFiles.ts behavior\n let mergedDictionary: Dictionary;\n\n if (isPerLocaleFile) {\n // Per-locale format: simple string content with locale property\n const mergedContent = mergeWithExistingPerLocaleDictionary(\n content,\n existingDictionary,\n defaultLocale\n );\n\n mergedDictionary = {\n // Preserve existing metadata (title, description, tags, etc.)\n ...(existingDictionary && {\n $schema: existingDictionary.$schema,\n id: existingDictionary.id,\n title: existingDictionary.title,\n description: existingDictionary.description,\n tags: existingDictionary.tags,\n fill: existingDictionary.fill,\n filled: existingDictionary.filled,\n priority: existingDictionary.priority,\n version: existingDictionary.version,\n }),\n // Required fields\n key: dictionaryKey,\n content: mergedContent,\n locale: defaultLocale,\n filePath: relativeFilePath,\n };\n } else {\n // Multilingual format: content wrapped in translation nodes, no locale property\n const mergedContent = mergeWithExistingMultilingualDictionary(\n content,\n existingDictionary,\n defaultLocale\n );\n\n mergedDictionary = {\n // Preserve existing metadata (title, description, tags, etc.)\n ...(existingDictionary && {\n $schema: existingDictionary.$schema,\n id: existingDictionary.id,\n title: existingDictionary.title,\n description: existingDictionary.description,\n tags: existingDictionary.tags,\n fill: existingDictionary.fill,\n filled: existingDictionary.filled,\n priority: existingDictionary.priority,\n version: existingDictionary.version,\n }),\n // Required fields\n key: dictionaryKey,\n content: mergedContent,\n filePath: relativeFilePath,\n };\n }\n\n try {\n const writeResult = await writeContentDeclaration(\n mergedDictionary,\n config,\n {\n newDictionariesPath: relative(config.content.baseDir, outputDir),\n }\n );\n\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} ${writeResult.status === 'created' ? 'Created' : writeResult.status === 'updated' ? 'Updated' : 'Processed'} content declaration: ${colorizePath(relative(projectRoot, writeResult.path))}`,\n {\n level: 'info',\n }\n );\n\n // Build the dictionary immediately so it's available for the prune plugin\n const dictionaryToBuild: Dictionary = {\n ...mergedDictionary,\n filePath: relative(config.content.baseDir, writeResult.path),\n };\n\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Building dictionary ${colorizeKey(dictionaryKey)}`,\n {\n level: 'info',\n }\n );\n\n await buildDictionary([dictionaryToBuild], config);\n\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Dictionary ${colorizeKey(dictionaryKey)} built successfully`,\n {\n level: 'info',\n }\n );\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 = (result: ExtractResult): void => {\n const contentKeys = Object.keys(result.content);\n\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Extracted ${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\n /**\n * Detect the package name to import useIntlayer from based on file extension\n */\n const detectPackageName = (filename: string): string => {\n if (filename.endsWith('.vue')) {\n return 'vue-intlayer';\n }\n if (filename.endsWith('.svelte')) {\n return 'svelte-intlayer';\n }\n if (filename.endsWith('.tsx') || filename.endsWith('.jsx')) {\n return 'react-intlayer';\n }\n // Default to react-intlayer for JSX/TSX files\n return 'intlayer';\n };\n\n /**\n * Transform a Vue file using the Vue extraction plugin\n */\n const transformVue = async (\n code: string,\n filename: string,\n defaultLocale: string\n ) => {\n const { intlayerVueExtract } = await import('@intlayer/vue-compiler');\n return intlayerVueExtract(code, filename, {\n defaultLocale,\n filesList,\n packageName: 'vue-intlayer',\n onExtract: handleExtractedContent,\n });\n };\n\n /**\n * Transform a Svelte file using the Svelte extraction plugin\n */\n const transformSvelte = async (\n code: string,\n filename: string,\n defaultLocale: string\n ) => {\n const { intlayerSvelteExtract } = await import('@intlayer/svelte-compiler');\n const result = await intlayerSvelteExtract(code, filename, {\n defaultLocale,\n filesList,\n packageName: 'svelte-intlayer',\n onExtract: handleExtractedContent,\n });\n\n return result;\n };\n\n /**\n * Transform a JSX/TSX file using the Babel extraction plugin\n */\n const transformJsx = (\n code: string,\n filename: string,\n defaultLocale: string\n ) => {\n if (!babel) {\n return undefined;\n }\n\n const packageName = detectPackageName(filename);\n\n const result = babel.transformSync(code, {\n filename,\n plugins: [\n [\n intlayerExtractBabelPlugin,\n {\n defaultLocale,\n filesList,\n packageName,\n onExtract: handleExtractedContent,\n },\n ],\n ],\n parserOpts: {\n sourceType: 'module',\n allowImportExportEverywhere: true,\n plugins: [\n 'typescript',\n 'jsx',\n 'decorators-legacy',\n 'classProperties',\n 'objectRestSpread',\n 'asyncGenerators',\n 'functionBind',\n 'exportDefaultFrom',\n 'exportNamespaceFrom',\n 'dynamicImport',\n 'nullishCoalescingOperator',\n 'optionalChaining',\n ],\n },\n });\n\n if (result?.code) {\n return {\n code: result.code,\n map: result.map,\n extracted: true,\n };\n }\n\n return undefined;\n };\n\n /**\n * Transform a file using the appropriate extraction plugin based on file type\n */\n const transformHandler = async (\n code: string,\n id: string,\n _options?: { ssr?: boolean }\n ) => {\n const compilerConfig = getCompilerConfig();\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 { defaultLocale } = config.internationalization;\n\n const filename = id;\n\n if (!filesList.includes(filename)) {\n return undefined;\n }\n\n // Only process Vue and Svelte source files with extraction\n // JSX/TSX files are handled by Babel which has its own detection\n const isVue = filename.endsWith('.vue');\n const isSvelte = filename.endsWith('.svelte');\n\n if (!isVue && !isSvelte) {\n // For non-Vue/Svelte files, use JSX transformation via Babel\n try {\n const result = transformJsx(code, filename, defaultLocale);\n\n if (pendingDictionaryWrite) {\n await pendingDictionaryWrite;\n }\n\n if (result?.code) {\n return {\n code: result.code,\n map: result.map,\n };\n }\n } catch (error) {\n logger(\n `Failed to transform ${colorizePath(relative(projectRoot, filename))}: ${error}`,\n {\n level: 'error',\n }\n );\n }\n return undefined;\n }\n\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Transforming ${colorizePath(relative(projectRoot, filename))}`,\n {\n level: 'info',\n }\n );\n\n try {\n let result:\n | { code: string; map?: unknown; extracted?: boolean }\n | null\n | undefined;\n\n // Route to appropriate transformer based on file extension\n if (isVue) {\n result = await transformVue(code, filename, defaultLocale);\n } else if (isSvelte) {\n result = await transformSvelte(code, filename, defaultLocale);\n }\n\n // Wait for the dictionary to be written before returning\n // This ensures the dictionary exists before the prune plugin runs\n if (pendingDictionaryWrite) {\n await pendingDictionaryWrite;\n }\n\n if (result?.code) {\n return {\n code: result.code,\n map: result.map,\n };\n }\n } catch (error) {\n logger(\n `Failed to transform ${relative(projectRoot, filename)}: ${error}`,\n {\n level: 'error',\n }\n );\n }\n\n return undefined;\n };\n\n /**\n * Apply hook for determining when plugin should be active\n */\n const apply = (_config: unknown, _env: { command: string }): boolean => {\n const compilerConfig = getCompilerConfig();\n // Apply if compiler is enabled\n return compilerConfig.enabled;\n };\n\n return {\n name: 'vite-intlayer-compiler',\n enforce: 'pre',\n config: configHook,\n configResolved,\n buildStart,\n buildEnd,\n configureServer,\n handleHotUpdate,\n transform: transformHandler,\n apply: (_viteConfig: unknown, env: { command: string }) => {\n // Initialize config if not already done\n if (!config) {\n config = getConfiguration(configOptions);\n }\n return apply(_viteConfig, env);\n },\n };\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"IntlayerCompilerPlugin.cjs","names":["config: IntlayerConfig","logger: ReturnType<typeof getAppLogger>","filesList: string[]","babel: any","pendingDictionaryWrite: Promise<void> | null","mergedContent: DictionaryContentMap","ANSIColors","mergedContent: Record<string, string>","compilerMode: CompilerMode","mergedDictionary: Dictionary","dictionaryToBuild: Dictionary","intlayerExtractBabelPlugin","result:\n | { code: string; map?: unknown; extracted?: boolean }\n | null\n | undefined"],"sources":["../../src/IntlayerCompilerPlugin.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { mkdir, readFile } from 'node:fs/promises';\nimport { createRequire } from 'node:module';\nimport { join, relative } from 'node:path';\nimport {\n type ExtractResult,\n intlayerExtractBabelPlugin,\n} from '@intlayer/babel';\nimport {\n buildDictionary,\n buildFilesList,\n prepareIntlayer,\n writeContentDeclaration,\n} from '@intlayer/chokidar';\nimport {\n ANSIColors,\n colorize,\n colorizeKey,\n colorizePath,\n type GetConfigurationOptions,\n getAppLogger,\n getConfiguration,\n} from '@intlayer/config';\nimport type {\n CompilerConfig,\n Dictionary,\n IntlayerConfig,\n} from '@intlayer/types';\n\n/**\n * Translation node structure used in dictionaries\n */\ntype TranslationNode = {\n nodeType: 'translation';\n translation: Record<string, string>;\n};\n\n/**\n * Dictionary content structure - map of keys to translation nodes\n */\ntype DictionaryContentMap = Record<string, TranslationNode>;\n\n/**\n * Mode of the compiler\n * - 'dev': Development mode with HMR support\n * - 'build': Production build mode\n */\nexport type CompilerMode = 'dev' | 'build';\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 */\nexport const intlayerCompiler = (options?: IntlayerCompilerOptions): any => {\n // Private state\n let config: IntlayerConfig;\n let logger: ReturnType<typeof getAppLogger>;\n let projectRoot = '';\n let filesList: string[] = [];\n let babel: any = null;\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 const configOptions = options?.configOptions;\n const customCompilerConfig = options?.compilerConfig;\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 return JSON.stringify(\n Object.keys(content)\n .sort()\n .map((k) => [k, content[k]])\n );\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 * Get compiler config from intlayer config or custom options\n */\n const getCompilerConfig = () => {\n // Access compiler config from the raw config (may not be in the type)\n const rawConfig = config as IntlayerConfig & {\n compiler?: Partial<CompilerConfig>;\n };\n\n return {\n enabled:\n customCompilerConfig?.enabled ?? rawConfig.compiler?.enabled ?? true,\n transformPattern:\n customCompilerConfig?.transformPattern ??\n rawConfig.compiler?.transformPattern ??\n config.build.traversePattern,\n excludePattern: [\n ...(customCompilerConfig?.excludePattern ?? []),\n '**/node_modules/**',\n ...config.content.fileExtensions.map((pattern) => `*${pattern}`),\n ],\n outputDir:\n customCompilerConfig?.outputDir ??\n rawConfig.compiler?.outputDir ??\n 'compiler',\n };\n };\n\n /**\n * Get the output directory path for compiler dictionaries\n */\n const getOutputDir = (): string => {\n const { baseDir } = config.content;\n const compilerConfig = getCompilerConfig();\n return join(baseDir, compilerConfig.outputDir);\n };\n\n /**\n * Get the file path for a dictionary\n */\n const getDictionaryFilePath = (dictionaryKey: string): string => {\n const outputDir = getOutputDir();\n return join(outputDir, `${dictionaryKey}.content.json`);\n };\n\n /**\n * Read an existing dictionary file if it exists\n */\n const readExistingDictionary = async (\n dictionaryKey: string\n ): Promise<Dictionary | null> => {\n const filePath = getDictionaryFilePath(dictionaryKey);\n\n if (!existsSync(filePath)) {\n return null;\n }\n\n try {\n const content = await readFile(filePath, 'utf-8');\n return JSON.parse(content) as Dictionary;\n } catch {\n return null;\n }\n };\n\n /**\n * Merge extracted content with existing dictionary for multilingual format.\n * - Keys in extracted but not in existing: added with default locale only\n * - Keys in both: preserve existing translations, update default locale value\n * - Keys in existing but not in extracted: removed (no longer in source)\n */\n const mergeWithExistingMultilingualDictionary = (\n extractedContent: Record<string, string>,\n existingDictionary: Dictionary | null,\n defaultLocale: string\n ): DictionaryContentMap => {\n const mergedContent: DictionaryContentMap = {};\n const existingContent = existingDictionary?.content as\n | DictionaryContentMap\n | undefined;\n\n for (const [key, value] of Object.entries(extractedContent)) {\n const existingEntry = existingContent?.[key];\n\n if (\n existingEntry &&\n existingEntry.nodeType === 'translation' &&\n existingEntry.translation\n ) {\n const oldValue = existingEntry.translation[defaultLocale];\n const isUpdated = oldValue !== value;\n\n // Key exists in both - preserve existing translations, update default locale\n mergedContent[key] = {\n nodeType: 'translation',\n translation: {\n ...existingEntry.translation,\n [defaultLocale]: value,\n },\n };\n\n if (isUpdated) {\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Updated \"${key}\" [${defaultLocale}]: \"${oldValue?.slice(0, 30)}...\" → \"${value.slice(0, 30)}...\"`,\n { level: 'info', isVerbose: true }\n );\n }\n } else {\n // New key - add with default locale only\n mergedContent[key] = {\n nodeType: 'translation',\n translation: {\n [defaultLocale]: value,\n },\n };\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Added new key \"${key}\"`,\n {\n level: 'info',\n isVerbose: true,\n }\n );\n }\n }\n\n // Log removed keys\n if (existingContent) {\n const removedKeys = Object.keys(existingContent).filter(\n (key) => !(key in extractedContent)\n );\n for (const key of removedKeys) {\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Removed key \"${key}\" (no longer in source)`,\n {\n level: 'info',\n isVerbose: true,\n }\n );\n }\n }\n\n return mergedContent;\n };\n\n /**\n * Merge extracted content with existing dictionary for per-locale format.\n * - Keys in extracted but not in existing: added\n * - Keys in both: update value\n * - Keys in existing but not in extracted: removed (no longer in source)\n */\n const mergeWithExistingPerLocaleDictionary = (\n extractedContent: Record<string, string>,\n existingDictionary: Dictionary | null,\n defaultLocale: string\n ): Record<string, string> => {\n const mergedContent: Record<string, string> = {};\n const existingContent = existingDictionary?.content as\n | Record<string, string>\n | undefined;\n\n for (const [key, value] of Object.entries(extractedContent)) {\n const existingValue = existingContent?.[key];\n\n if (existingValue && typeof existingValue === 'string') {\n const isUpdated = existingValue !== value;\n\n mergedContent[key] = value;\n\n if (isUpdated) {\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Updated \"${key}\" [${defaultLocale}]: \"${existingValue?.slice(0, 30)}...\" → \"${value.slice(0, 30)}...\"`,\n { level: 'info', isVerbose: true }\n );\n }\n } else {\n // New key\n mergedContent[key] = value;\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Added new key \"${key}\"`,\n {\n level: 'info',\n isVerbose: true,\n }\n );\n }\n }\n\n // Log removed keys\n if (existingContent) {\n const removedKeys = Object.keys(existingContent).filter(\n (key) => !(key in extractedContent)\n );\n for (const key of removedKeys) {\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Removed key \"${key}\" (no longer in source)`,\n {\n level: 'info',\n isVerbose: true,\n }\n );\n }\n }\n\n return mergedContent;\n };\n\n /**\n * Build the list of files to transform based on configuration patterns\n */\n const buildFilesListFn = async (): Promise<void> => {\n const { baseDir, fileExtensions } = config.content;\n const compilerConfig = getCompilerConfig();\n\n const excludePatterns = Array.isArray(compilerConfig.excludePattern)\n ? compilerConfig.excludePattern\n : [compilerConfig.excludePattern];\n\n filesList = buildFilesList({\n transformPattern: compilerConfig.transformPattern,\n excludePattern: [\n ...excludePatterns,\n '**/node_modules/**',\n ...fileExtensions.map((pattern) => `**/*${pattern}`),\n ],\n baseDir,\n });\n };\n\n /**\n * Initialize the compiler with the given mode\n */\n const init = async (_compilerMode: CompilerMode): Promise<void> => {\n config = getConfiguration(configOptions);\n logger = getAppLogger(config);\n\n // Load Babel dynamically\n try {\n const localRequire = createRequire(import.meta.url);\n babel = localRequire('@babel/core');\n } catch {\n logger('Failed to load @babel/core. Transformation will be disabled.', {\n level: 'warn',\n });\n }\n\n // Build files list for transformation\n await buildFilesListFn();\n };\n\n /**\n * Vite hook: config\n * Called before Vite config is resolved - perfect time to prepare dictionaries\n */\n const configHook = async (\n _config: unknown,\n env: { command: string; mode: string }\n ): Promise<void> => {\n // Initialize config early\n config = getConfiguration(configOptions);\n logger = getAppLogger(config);\n\n const isDevCommand = env.command === 'serve' && env.mode === 'development';\n const isBuildCommand = env.command === 'build';\n\n // Prepare all existing dictionaries (builds them to .intlayer/dictionary/)\n // This ensures built dictionaries exist before the prune plugin runs\n if (isDevCommand || isBuildCommand) {\n await prepareIntlayer(config, {\n clean: isBuildCommand,\n cacheTimeoutMs: isBuildCommand\n ? 1000 * 30 // 30 seconds for build\n : 1000 * 60 * 60, // 1 hour for dev\n });\n }\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 }): Promise<void> => {\n const compilerMode: CompilerMode = viteConfig.env?.DEV ? 'dev' : 'build';\n projectRoot = viteConfig.root;\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 // Autonomous compiler - no need to prepare dictionaries\n // Content is extracted inline during transformation\n logger('Intlayer compiler initialized', {\n level: 'info',\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 * Configure the dev server\n */\n const configureServer = async (): Promise<void> => {\n // In autonomous mode, we don't need file watching for dictionaries\n // Content is extracted inline during transformation\n };\n\n /**\n * Vite hook: handleHotUpdate\n * Handles HMR for content files - invalidates cache and triggers re-transform\n */\n const handleHotUpdate = async (ctx: any): Promise<unknown[] | undefined> => {\n const { file, server, modules } = ctx;\n\n // Check if this is a file we should transform\n const isTransformableFile = filesList.some((f) => f === file);\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 return [];\n }\n\n return undefined;\n };\n\n /**\n * Write and build a single dictionary immediately\n * This is called during transform to ensure dictionaries are always up-to-date.\n *\n * The merge strategy:\n * - New keys are added with the default locale only\n * - Existing keys preserve their translations, with default locale updated\n * - Keys no longer in source are removed\n *\n * Dictionary format:\n * - Per-locale: When config.dictionary.locale is set, content is simple strings with locale property\n * - Multilingual: When not set, content is wrapped in translation nodes without locale property\n */\n const writeAndBuildDictionary = async (\n result: ExtractResult\n ): Promise<void> => {\n const { dictionaryKey, content } = 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 const outputDir = getOutputDir();\n const { defaultLocale } = config.internationalization;\n\n // Check if per-locale format is configured\n // When config.dictionary.locale is set, use per-locale format (simple strings with locale property)\n // Otherwise, use multilingual format (content wrapped in TranslationNode objects)\n const isPerLocaleFile = Boolean(config?.dictionary?.locale);\n\n // Ensure output directory exists\n await mkdir(outputDir, { recursive: true });\n\n // Read existing dictionary to preserve translations and metadata\n const existingDictionary = await readExistingDictionary(dictionaryKey);\n\n const relativeFilePath = join(\n relative(config.content.baseDir, outputDir),\n `${dictionaryKey}.content.json`\n );\n\n // Build dictionary based on format - matching transformFiles.ts behavior\n let mergedDictionary: Dictionary;\n\n if (isPerLocaleFile) {\n // Per-locale format: simple string content with locale property\n const mergedContent = mergeWithExistingPerLocaleDictionary(\n content,\n existingDictionary,\n defaultLocale\n );\n\n mergedDictionary = {\n // Preserve existing metadata (title, description, tags, etc.)\n ...(existingDictionary && {\n $schema: existingDictionary.$schema,\n id: existingDictionary.id,\n title: existingDictionary.title,\n description: existingDictionary.description,\n tags: existingDictionary.tags,\n fill: existingDictionary.fill,\n filled: existingDictionary.filled,\n priority: existingDictionary.priority,\n version: existingDictionary.version,\n }),\n // Required fields\n key: dictionaryKey,\n content: mergedContent,\n locale: defaultLocale,\n filePath: relativeFilePath,\n };\n } else {\n // Multilingual format: content wrapped in translation nodes, no locale property\n const mergedContent = mergeWithExistingMultilingualDictionary(\n content,\n existingDictionary,\n defaultLocale\n );\n\n mergedDictionary = {\n // Preserve existing metadata (title, description, tags, etc.)\n ...(existingDictionary && {\n $schema: existingDictionary.$schema,\n id: existingDictionary.id,\n title: existingDictionary.title,\n description: existingDictionary.description,\n tags: existingDictionary.tags,\n fill: existingDictionary.fill,\n filled: existingDictionary.filled,\n priority: existingDictionary.priority,\n version: existingDictionary.version,\n }),\n // Required fields\n key: dictionaryKey,\n content: mergedContent,\n filePath: relativeFilePath,\n };\n }\n\n try {\n const writeResult = await writeContentDeclaration(\n mergedDictionary,\n config,\n {\n newDictionariesPath: relative(config.content.baseDir, outputDir),\n }\n );\n\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} ${writeResult.status === 'created' ? 'Created' : writeResult.status === 'updated' ? 'Updated' : 'Processed'} content declaration: ${colorizePath(relative(projectRoot, writeResult.path))}`,\n {\n level: 'info',\n }\n );\n\n // Build the dictionary immediately so it's available for the prune plugin\n const dictionaryToBuild: Dictionary = {\n ...mergedDictionary,\n filePath: relative(config.content.baseDir, writeResult.path),\n };\n\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Building dictionary ${colorizeKey(dictionaryKey)}`,\n {\n level: 'info',\n }\n );\n\n await buildDictionary([dictionaryToBuild], config);\n\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Dictionary ${colorizeKey(dictionaryKey)} built successfully`,\n {\n level: 'info',\n }\n );\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 = (result: ExtractResult): void => {\n const contentKeys = Object.keys(result.content);\n\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Extracted ${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\n /**\n * Detect the package name to import useIntlayer from based on file extension\n */\n const detectPackageName = (filename: string): string => {\n if (filename.endsWith('.vue')) {\n return 'vue-intlayer';\n }\n if (filename.endsWith('.svelte')) {\n return 'svelte-intlayer';\n }\n if (filename.endsWith('.tsx') || filename.endsWith('.jsx')) {\n return 'react-intlayer';\n }\n // Default to react-intlayer for JSX/TSX files\n return 'intlayer';\n };\n\n /**\n * Transform a Vue file using the Vue extraction plugin\n */\n const transformVue = async (\n code: string,\n filename: string,\n defaultLocale: string\n ) => {\n const { intlayerVueExtract } = await import('@intlayer/vue-compiler');\n return intlayerVueExtract(code, filename, {\n defaultLocale,\n filesList,\n packageName: 'vue-intlayer',\n onExtract: handleExtractedContent,\n });\n };\n\n /**\n * Transform a Svelte file using the Svelte extraction plugin\n */\n const transformSvelte = async (\n code: string,\n filename: string,\n defaultLocale: string\n ) => {\n const { intlayerSvelteExtract } = await import('@intlayer/svelte-compiler');\n const result = await intlayerSvelteExtract(code, filename, {\n defaultLocale,\n filesList,\n packageName: 'svelte-intlayer',\n onExtract: handleExtractedContent,\n });\n\n return result;\n };\n\n /**\n * Transform a JSX/TSX file using the Babel extraction plugin\n */\n const transformJsx = (\n code: string,\n filename: string,\n defaultLocale: string\n ) => {\n if (!babel) {\n return undefined;\n }\n\n const packageName = detectPackageName(filename);\n\n const result = babel.transformSync(code, {\n filename,\n plugins: [\n [\n intlayerExtractBabelPlugin,\n {\n defaultLocale,\n filesList,\n packageName,\n onExtract: handleExtractedContent,\n },\n ],\n ],\n parserOpts: {\n sourceType: 'module',\n allowImportExportEverywhere: true,\n plugins: [\n 'typescript',\n 'jsx',\n 'decorators-legacy',\n 'classProperties',\n 'objectRestSpread',\n 'asyncGenerators',\n 'functionBind',\n 'exportDefaultFrom',\n 'exportNamespaceFrom',\n 'dynamicImport',\n 'nullishCoalescingOperator',\n 'optionalChaining',\n ],\n },\n });\n\n if (result?.code) {\n return {\n code: result.code,\n map: result.map,\n extracted: true,\n };\n }\n\n return undefined;\n };\n\n /**\n * Transform a file using the appropriate extraction plugin based on file type\n */\n const transformHandler = async (\n code: string,\n id: string,\n _options?: { ssr?: boolean }\n ) => {\n const compilerConfig = getCompilerConfig();\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 { defaultLocale } = config.internationalization;\n\n const filename = id;\n\n if (!filesList.includes(filename)) {\n return undefined;\n }\n\n // Only process Vue and Svelte source files with extraction\n // JSX/TSX files are handled by Babel which has its own detection\n const isVue = filename.endsWith('.vue');\n const isSvelte = filename.endsWith('.svelte');\n\n if (!isVue && !isSvelte) {\n // For non-Vue/Svelte files, use JSX transformation via Babel\n try {\n const result = transformJsx(code, filename, defaultLocale);\n\n if (pendingDictionaryWrite) {\n await pendingDictionaryWrite;\n }\n\n if (result?.code) {\n return {\n code: result.code,\n map: result.map,\n };\n }\n } catch (error) {\n logger(\n `Failed to transform ${colorizePath(relative(projectRoot, filename))}: ${error}`,\n {\n level: 'error',\n }\n );\n }\n return undefined;\n }\n\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Transforming ${colorizePath(relative(projectRoot, filename))}`,\n {\n level: 'info',\n }\n );\n\n try {\n let result:\n | { code: string; map?: unknown; extracted?: boolean }\n | null\n | undefined;\n\n // Route to appropriate transformer based on file extension\n if (isVue) {\n result = await transformVue(code, filename, defaultLocale);\n } else if (isSvelte) {\n result = await transformSvelte(code, filename, defaultLocale);\n }\n\n // Wait for the dictionary to be written before returning\n // This ensures the dictionary exists before the prune plugin runs\n if (pendingDictionaryWrite) {\n await pendingDictionaryWrite;\n }\n\n if (result?.code) {\n return {\n code: result.code,\n map: result.map,\n };\n }\n } catch (error) {\n logger(\n `Failed to transform ${relative(projectRoot, filename)}: ${error}`,\n {\n level: 'error',\n }\n );\n }\n\n return undefined;\n };\n\n /**\n * Apply hook for determining when plugin should be active\n */\n const apply = (_config: unknown, _env: { command: string }): boolean => {\n const compilerConfig = getCompilerConfig();\n // Apply if compiler is enabled\n return compilerConfig.enabled;\n };\n\n return {\n name: 'vite-intlayer-compiler',\n enforce: 'pre',\n config: configHook,\n configResolved,\n buildStart,\n buildEnd,\n configureServer,\n handleHotUpdate,\n transform: transformHandler,\n apply: (_viteConfig: unknown, env: { command: string }) => {\n // Initialize config if not already done\n if (!config) {\n config = getConfiguration(configOptions);\n }\n return apply(_viteConfig, env);\n },\n };\n};\n"],"mappings":"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"}
|
|
@@ -4,9 +4,9 @@ let node_path = require("node:path");
|
|
|
4
4
|
let __intlayer_babel = require("@intlayer/babel");
|
|
5
5
|
let __intlayer_chokidar = require("@intlayer/chokidar");
|
|
6
6
|
let __intlayer_config = require("@intlayer/config");
|
|
7
|
+
let __intlayer_dictionaries_entry = require("@intlayer/dictionaries-entry");
|
|
7
8
|
let fast_glob = require("fast-glob");
|
|
8
9
|
fast_glob = require_rolldown_runtime.__toESM(fast_glob);
|
|
9
|
-
let __intlayer_dictionaries_entry = require("@intlayer/dictionaries-entry");
|
|
10
10
|
|
|
11
11
|
//#region src/intlayerPrunePlugin.ts
|
|
12
12
|
const intlayerPrune = (intlayerConfig) => {
|
|
@@ -3,9 +3,8 @@ import { mkdir, readFile } from "node:fs/promises";
|
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { join, relative } from "node:path";
|
|
5
5
|
import { intlayerExtractBabelPlugin } from "@intlayer/babel";
|
|
6
|
-
import { buildDictionary, prepareIntlayer, writeContentDeclaration } from "@intlayer/chokidar";
|
|
6
|
+
import { buildDictionary, buildFilesList, prepareIntlayer, writeContentDeclaration } from "@intlayer/chokidar";
|
|
7
7
|
import { ANSIColors, colorize, colorizeKey, colorizePath, getAppLogger, getConfiguration } from "@intlayer/config";
|
|
8
|
-
import fg from "fast-glob";
|
|
9
8
|
|
|
10
9
|
//#region src/IntlayerCompilerPlugin.ts
|
|
11
10
|
/**
|
|
@@ -35,9 +34,45 @@ const intlayerCompiler = (options) => {
|
|
|
35
34
|
let filesList = [];
|
|
36
35
|
let babel = null;
|
|
37
36
|
let pendingDictionaryWrite = null;
|
|
37
|
+
const recentlyProcessedFiles = /* @__PURE__ */ new Map();
|
|
38
|
+
const recentDictionaryContent = /* @__PURE__ */ new Map();
|
|
39
|
+
const DEBOUNCE_MS = 500;
|
|
38
40
|
const configOptions = options?.configOptions;
|
|
39
41
|
const customCompilerConfig = options?.compilerConfig;
|
|
40
42
|
/**
|
|
43
|
+
* Check if a file was recently processed (within debounce window)
|
|
44
|
+
* and should be skipped to prevent infinite loops
|
|
45
|
+
*/
|
|
46
|
+
const wasRecentlyProcessed = (filePath) => {
|
|
47
|
+
const lastProcessed = recentlyProcessedFiles.get(filePath);
|
|
48
|
+
if (!lastProcessed) return false;
|
|
49
|
+
return Date.now() - lastProcessed < DEBOUNCE_MS;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Mark a file as recently processed
|
|
53
|
+
*/
|
|
54
|
+
const markAsProcessed = (filePath) => {
|
|
55
|
+
recentlyProcessedFiles.set(filePath, Date.now());
|
|
56
|
+
const now = Date.now();
|
|
57
|
+
for (const [path, timestamp] of recentlyProcessedFiles.entries()) if (now - timestamp > DEBOUNCE_MS * 2) recentlyProcessedFiles.delete(path);
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Create a simple hash of content for comparison
|
|
61
|
+
* Used to detect if dictionary content has actually changed
|
|
62
|
+
*/
|
|
63
|
+
const hashContent = (content) => {
|
|
64
|
+
return JSON.stringify(Object.keys(content).sort().map((k) => [k, content[k]]));
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Check if dictionary content has changed since last write
|
|
68
|
+
*/
|
|
69
|
+
const hasDictionaryContentChanged = (dictionaryKey, content) => {
|
|
70
|
+
const newHash = hashContent(content);
|
|
71
|
+
if (recentDictionaryContent.get(dictionaryKey) === newHash) return false;
|
|
72
|
+
recentDictionaryContent.set(dictionaryKey, newHash);
|
|
73
|
+
return true;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
41
76
|
* Get compiler config from intlayer config or custom options
|
|
42
77
|
*/
|
|
43
78
|
const getCompilerConfig = () => {
|
|
@@ -45,7 +80,11 @@ const intlayerCompiler = (options) => {
|
|
|
45
80
|
return {
|
|
46
81
|
enabled: customCompilerConfig?.enabled ?? rawConfig.compiler?.enabled ?? true,
|
|
47
82
|
transformPattern: customCompilerConfig?.transformPattern ?? rawConfig.compiler?.transformPattern ?? config.build.traversePattern,
|
|
48
|
-
excludePattern:
|
|
83
|
+
excludePattern: [
|
|
84
|
+
...customCompilerConfig?.excludePattern ?? [],
|
|
85
|
+
"**/node_modules/**",
|
|
86
|
+
...config.content.fileExtensions.map((pattern) => `*${pattern}`)
|
|
87
|
+
],
|
|
49
88
|
outputDir: customCompilerConfig?.outputDir ?? rawConfig.compiler?.outputDir ?? "compiler"
|
|
50
89
|
};
|
|
51
90
|
};
|
|
@@ -158,15 +197,19 @@ const intlayerCompiler = (options) => {
|
|
|
158
197
|
/**
|
|
159
198
|
* Build the list of files to transform based on configuration patterns
|
|
160
199
|
*/
|
|
161
|
-
const
|
|
162
|
-
const { baseDir } = config.content;
|
|
200
|
+
const buildFilesListFn = async () => {
|
|
201
|
+
const { baseDir, fileExtensions } = config.content;
|
|
163
202
|
const compilerConfig = getCompilerConfig();
|
|
164
|
-
const patterns = Array.isArray(compilerConfig.transformPattern) ? compilerConfig.transformPattern : [compilerConfig.transformPattern];
|
|
165
203
|
const excludePatterns = Array.isArray(compilerConfig.excludePattern) ? compilerConfig.excludePattern : [compilerConfig.excludePattern];
|
|
166
|
-
filesList =
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
204
|
+
filesList = buildFilesList({
|
|
205
|
+
transformPattern: compilerConfig.transformPattern,
|
|
206
|
+
excludePattern: [
|
|
207
|
+
...excludePatterns,
|
|
208
|
+
"**/node_modules/**",
|
|
209
|
+
...fileExtensions.map((pattern) => `**/*${pattern}`)
|
|
210
|
+
],
|
|
211
|
+
baseDir
|
|
212
|
+
});
|
|
170
213
|
};
|
|
171
214
|
/**
|
|
172
215
|
* Initialize the compiler with the given mode
|
|
@@ -179,7 +222,7 @@ const intlayerCompiler = (options) => {
|
|
|
179
222
|
} catch {
|
|
180
223
|
logger("Failed to load @babel/core. Transformation will be disabled.", { level: "warn" });
|
|
181
224
|
}
|
|
182
|
-
await
|
|
225
|
+
await buildFilesListFn();
|
|
183
226
|
};
|
|
184
227
|
/**
|
|
185
228
|
* Vite hook: config
|
|
@@ -228,6 +271,14 @@ const intlayerCompiler = (options) => {
|
|
|
228
271
|
const handleHotUpdate = async (ctx) => {
|
|
229
272
|
const { file, server, modules } = ctx;
|
|
230
273
|
if (filesList.some((f) => f === file)) {
|
|
274
|
+
if (wasRecentlyProcessed(file)) {
|
|
275
|
+
logger(`${colorize("Compiler:", ANSIColors.GREY_DARK)} Skipping re-transform of ${colorizePath(relative(projectRoot, file))} (recently processed)`, {
|
|
276
|
+
level: "info",
|
|
277
|
+
isVerbose: true
|
|
278
|
+
});
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
markAsProcessed(file);
|
|
231
282
|
for (const mod of modules) server.moduleGraph.invalidateModule(mod);
|
|
232
283
|
try {
|
|
233
284
|
await transformHandler(await readFile(file, "utf-8"), file);
|
|
@@ -253,6 +304,13 @@ const intlayerCompiler = (options) => {
|
|
|
253
304
|
*/
|
|
254
305
|
const writeAndBuildDictionary = async (result) => {
|
|
255
306
|
const { dictionaryKey, content } = result;
|
|
307
|
+
if (!hasDictionaryContentChanged(dictionaryKey, content)) {
|
|
308
|
+
logger(`${colorize("Compiler:", ANSIColors.GREY_DARK)} Skipping dictionary ${colorizeKey(dictionaryKey)} (content unchanged)`, {
|
|
309
|
+
level: "info",
|
|
310
|
+
isVerbose: true
|
|
311
|
+
});
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
256
314
|
const outputDir = getOutputDir();
|
|
257
315
|
const { defaultLocale } = config.internationalization;
|
|
258
316
|
const isPerLocaleFile = Boolean(config?.dictionary?.locale);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IntlayerCompilerPlugin.mjs","names":["config: IntlayerConfig","logger: ReturnType<typeof getAppLogger>","filesList: string[]","babel: any","pendingDictionaryWrite: Promise<void> | null","mergedContent: DictionaryContentMap","mergedContent: Record<string, string>","compilerMode: CompilerMode","mergedDictionary: Dictionary","dictionaryToBuild: Dictionary","result:\n | { code: string; map?: unknown; extracted?: boolean }\n | null\n | undefined"],"sources":["../../src/IntlayerCompilerPlugin.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { mkdir, readFile } from 'node:fs/promises';\nimport { createRequire } from 'node:module';\nimport { join, relative } from 'node:path';\nimport {\n type ExtractResult,\n intlayerExtractBabelPlugin,\n} from '@intlayer/babel';\nimport {\n buildDictionary,\n prepareIntlayer,\n writeContentDeclaration,\n} from '@intlayer/chokidar';\nimport {\n ANSIColors,\n colorize,\n colorizeKey,\n colorizePath,\n type GetConfigurationOptions,\n getAppLogger,\n getConfiguration,\n} from '@intlayer/config';\nimport type {\n CompilerConfig,\n Dictionary,\n IntlayerConfig,\n} from '@intlayer/types';\nimport fg from 'fast-glob';\n\n/**\n * Translation node structure used in dictionaries\n */\ntype TranslationNode = {\n nodeType: 'translation';\n translation: Record<string, string>;\n};\n\n/**\n * Dictionary content structure - map of keys to translation nodes\n */\ntype DictionaryContentMap = Record<string, TranslationNode>;\n\n/**\n * Mode of the compiler\n * - 'dev': Development mode with HMR support\n * - 'build': Production build mode\n */\nexport type CompilerMode = 'dev' | 'build';\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 */\nexport const intlayerCompiler = (options?: IntlayerCompilerOptions): any => {\n // Private state\n let config: IntlayerConfig;\n let logger: ReturnType<typeof getAppLogger>;\n let projectRoot = '';\n let filesList: string[] = [];\n let babel: any = null;\n\n // Promise to track dictionary writing (for synchronization)\n let pendingDictionaryWrite: Promise<void> | null = null;\n\n const configOptions = options?.configOptions;\n const customCompilerConfig = options?.compilerConfig;\n\n /**\n * Get compiler config from intlayer config or custom options\n */\n const getCompilerConfig = () => {\n // Access compiler config from the raw config (may not be in the type)\n const rawConfig = config as IntlayerConfig & {\n compiler?: Partial<CompilerConfig>;\n };\n\n return {\n enabled:\n customCompilerConfig?.enabled ?? rawConfig.compiler?.enabled ?? true,\n transformPattern:\n customCompilerConfig?.transformPattern ??\n rawConfig.compiler?.transformPattern ??\n config.build.traversePattern,\n excludePattern: customCompilerConfig?.excludePattern ??\n rawConfig.compiler?.excludePattern ?? ['**/node_modules/**'],\n outputDir:\n customCompilerConfig?.outputDir ??\n rawConfig.compiler?.outputDir ??\n 'compiler',\n };\n };\n\n /**\n * Get the output directory path for compiler dictionaries\n */\n const getOutputDir = (): string => {\n const { baseDir } = config.content;\n const compilerConfig = getCompilerConfig();\n return join(baseDir, compilerConfig.outputDir);\n };\n\n /**\n * Get the file path for a dictionary\n */\n const getDictionaryFilePath = (dictionaryKey: string): string => {\n const outputDir = getOutputDir();\n return join(outputDir, `${dictionaryKey}.content.json`);\n };\n\n /**\n * Read an existing dictionary file if it exists\n */\n const readExistingDictionary = async (\n dictionaryKey: string\n ): Promise<Dictionary | null> => {\n const filePath = getDictionaryFilePath(dictionaryKey);\n\n if (!existsSync(filePath)) {\n return null;\n }\n\n try {\n const content = await readFile(filePath, 'utf-8');\n return JSON.parse(content) as Dictionary;\n } catch {\n return null;\n }\n };\n\n /**\n * Merge extracted content with existing dictionary for multilingual format.\n * - Keys in extracted but not in existing: added with default locale only\n * - Keys in both: preserve existing translations, update default locale value\n * - Keys in existing but not in extracted: removed (no longer in source)\n */\n const mergeWithExistingMultilingualDictionary = (\n extractedContent: Record<string, string>,\n existingDictionary: Dictionary | null,\n defaultLocale: string\n ): DictionaryContentMap => {\n const mergedContent: DictionaryContentMap = {};\n const existingContent = existingDictionary?.content as\n | DictionaryContentMap\n | undefined;\n\n for (const [key, value] of Object.entries(extractedContent)) {\n const existingEntry = existingContent?.[key];\n\n if (\n existingEntry &&\n existingEntry.nodeType === 'translation' &&\n existingEntry.translation\n ) {\n const oldValue = existingEntry.translation[defaultLocale];\n const isUpdated = oldValue !== value;\n\n // Key exists in both - preserve existing translations, update default locale\n mergedContent[key] = {\n nodeType: 'translation',\n translation: {\n ...existingEntry.translation,\n [defaultLocale]: value,\n },\n };\n\n if (isUpdated) {\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Updated \"${key}\" [${defaultLocale}]: \"${oldValue?.slice(0, 30)}...\" → \"${value.slice(0, 30)}...\"`,\n { level: 'info', isVerbose: true }\n );\n }\n } else {\n // New key - add with default locale only\n mergedContent[key] = {\n nodeType: 'translation',\n translation: {\n [defaultLocale]: value,\n },\n };\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Added new key \"${key}\"`,\n {\n level: 'info',\n isVerbose: true,\n }\n );\n }\n }\n\n // Log removed keys\n if (existingContent) {\n const removedKeys = Object.keys(existingContent).filter(\n (key) => !(key in extractedContent)\n );\n for (const key of removedKeys) {\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Removed key \"${key}\" (no longer in source)`,\n {\n level: 'info',\n isVerbose: true,\n }\n );\n }\n }\n\n return mergedContent;\n };\n\n /**\n * Merge extracted content with existing dictionary for per-locale format.\n * - Keys in extracted but not in existing: added\n * - Keys in both: update value\n * - Keys in existing but not in extracted: removed (no longer in source)\n */\n const mergeWithExistingPerLocaleDictionary = (\n extractedContent: Record<string, string>,\n existingDictionary: Dictionary | null,\n defaultLocale: string\n ): Record<string, string> => {\n const mergedContent: Record<string, string> = {};\n const existingContent = existingDictionary?.content as\n | Record<string, string>\n | undefined;\n\n for (const [key, value] of Object.entries(extractedContent)) {\n const existingValue = existingContent?.[key];\n\n if (existingValue && typeof existingValue === 'string') {\n const isUpdated = existingValue !== value;\n\n mergedContent[key] = value;\n\n if (isUpdated) {\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Updated \"${key}\" [${defaultLocale}]: \"${existingValue?.slice(0, 30)}...\" → \"${value.slice(0, 30)}...\"`,\n { level: 'info', isVerbose: true }\n );\n }\n } else {\n // New key\n mergedContent[key] = value;\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Added new key \"${key}\"`,\n {\n level: 'info',\n isVerbose: true,\n }\n );\n }\n }\n\n // Log removed keys\n if (existingContent) {\n const removedKeys = Object.keys(existingContent).filter(\n (key) => !(key in extractedContent)\n );\n for (const key of removedKeys) {\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Removed key \"${key}\" (no longer in source)`,\n {\n level: 'info',\n isVerbose: true,\n }\n );\n }\n }\n\n return mergedContent;\n };\n\n /**\n * Build the list of files to transform based on configuration patterns\n */\n const buildFilesList = async (): Promise<void> => {\n const { baseDir } = config.content;\n const compilerConfig = getCompilerConfig();\n\n const patterns = Array.isArray(compilerConfig.transformPattern)\n ? compilerConfig.transformPattern\n : [compilerConfig.transformPattern];\n const excludePatterns = Array.isArray(compilerConfig.excludePattern)\n ? compilerConfig.excludePattern\n : [compilerConfig.excludePattern];\n\n filesList = fg\n .sync(patterns, {\n cwd: baseDir,\n ignore: excludePatterns,\n })\n .map((file) => join(baseDir, file));\n };\n\n /**\n * Initialize the compiler with the given mode\n */\n const init = async (_compilerMode: CompilerMode): Promise<void> => {\n config = getConfiguration(configOptions);\n logger = getAppLogger(config);\n\n // Load Babel dynamically\n try {\n const localRequire = createRequire(import.meta.url);\n babel = localRequire('@babel/core');\n } catch {\n logger('Failed to load @babel/core. Transformation will be disabled.', {\n level: 'warn',\n });\n }\n\n // Build files list for transformation\n await buildFilesList();\n };\n\n /**\n * Vite hook: config\n * Called before Vite config is resolved - perfect time to prepare dictionaries\n */\n const configHook = async (\n _config: unknown,\n env: { command: string; mode: string }\n ): Promise<void> => {\n // Initialize config early\n config = getConfiguration(configOptions);\n logger = getAppLogger(config);\n\n const isDevCommand = env.command === 'serve' && env.mode === 'development';\n const isBuildCommand = env.command === 'build';\n\n // Prepare all existing dictionaries (builds them to .intlayer/dictionary/)\n // This ensures built dictionaries exist before the prune plugin runs\n if (isDevCommand || isBuildCommand) {\n await prepareIntlayer(config, {\n clean: isBuildCommand,\n cacheTimeoutMs: isBuildCommand\n ? 1000 * 30 // 30 seconds for build\n : 1000 * 60 * 60, // 1 hour for dev\n });\n }\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 }): Promise<void> => {\n const compilerMode: CompilerMode = viteConfig.env?.DEV ? 'dev' : 'build';\n projectRoot = viteConfig.root;\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 // Autonomous compiler - no need to prepare dictionaries\n // Content is extracted inline during transformation\n logger('Intlayer compiler initialized', {\n level: 'info',\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 * Configure the dev server\n */\n const configureServer = async (): Promise<void> => {\n // In autonomous mode, we don't need file watching for dictionaries\n // Content is extracted inline during transformation\n };\n\n /**\n * Vite hook: handleHotUpdate\n * Handles HMR for content files - invalidates cache and triggers re-transform\n */\n const handleHotUpdate = async (ctx: any): Promise<unknown[] | undefined> => {\n const { file, server, modules } = ctx;\n\n // Check if this is a file we should transform\n const isTransformableFile = filesList.some((f) => f === file);\n\n if (isTransformableFile) {\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 return [];\n }\n\n return undefined;\n };\n\n /**\n * Write and build a single dictionary immediately\n * This is called during transform to ensure dictionaries are always up-to-date.\n *\n * The merge strategy:\n * - New keys are added with the default locale only\n * - Existing keys preserve their translations, with default locale updated\n * - Keys no longer in source are removed\n *\n * Dictionary format:\n * - Per-locale: When config.dictionary.locale is set, content is simple strings with locale property\n * - Multilingual: When not set, content is wrapped in translation nodes without locale property\n */\n const writeAndBuildDictionary = async (\n result: ExtractResult\n ): Promise<void> => {\n const { dictionaryKey, content } = result;\n\n const outputDir = getOutputDir();\n const { defaultLocale } = config.internationalization;\n\n // Check if per-locale format is configured\n // When config.dictionary.locale is set, use per-locale format (simple strings with locale property)\n // Otherwise, use multilingual format (content wrapped in TranslationNode objects)\n const isPerLocaleFile = Boolean(config?.dictionary?.locale);\n\n // Ensure output directory exists\n await mkdir(outputDir, { recursive: true });\n\n // Read existing dictionary to preserve translations and metadata\n const existingDictionary = await readExistingDictionary(dictionaryKey);\n\n const relativeFilePath = join(\n relative(config.content.baseDir, outputDir),\n `${dictionaryKey}.content.json`\n );\n\n // Build dictionary based on format - matching transformFiles.ts behavior\n let mergedDictionary: Dictionary;\n\n if (isPerLocaleFile) {\n // Per-locale format: simple string content with locale property\n const mergedContent = mergeWithExistingPerLocaleDictionary(\n content,\n existingDictionary,\n defaultLocale\n );\n\n mergedDictionary = {\n // Preserve existing metadata (title, description, tags, etc.)\n ...(existingDictionary && {\n $schema: existingDictionary.$schema,\n id: existingDictionary.id,\n title: existingDictionary.title,\n description: existingDictionary.description,\n tags: existingDictionary.tags,\n fill: existingDictionary.fill,\n filled: existingDictionary.filled,\n priority: existingDictionary.priority,\n version: existingDictionary.version,\n }),\n // Required fields\n key: dictionaryKey,\n content: mergedContent,\n locale: defaultLocale,\n filePath: relativeFilePath,\n };\n } else {\n // Multilingual format: content wrapped in translation nodes, no locale property\n const mergedContent = mergeWithExistingMultilingualDictionary(\n content,\n existingDictionary,\n defaultLocale\n );\n\n mergedDictionary = {\n // Preserve existing metadata (title, description, tags, etc.)\n ...(existingDictionary && {\n $schema: existingDictionary.$schema,\n id: existingDictionary.id,\n title: existingDictionary.title,\n description: existingDictionary.description,\n tags: existingDictionary.tags,\n fill: existingDictionary.fill,\n filled: existingDictionary.filled,\n priority: existingDictionary.priority,\n version: existingDictionary.version,\n }),\n // Required fields\n key: dictionaryKey,\n content: mergedContent,\n filePath: relativeFilePath,\n };\n }\n\n try {\n const writeResult = await writeContentDeclaration(\n mergedDictionary,\n config,\n {\n newDictionariesPath: relative(config.content.baseDir, outputDir),\n }\n );\n\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} ${writeResult.status === 'created' ? 'Created' : writeResult.status === 'updated' ? 'Updated' : 'Processed'} content declaration: ${colorizePath(relative(projectRoot, writeResult.path))}`,\n {\n level: 'info',\n }\n );\n\n // Build the dictionary immediately so it's available for the prune plugin\n const dictionaryToBuild: Dictionary = {\n ...mergedDictionary,\n filePath: relative(config.content.baseDir, writeResult.path),\n };\n\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Building dictionary ${colorizeKey(dictionaryKey)}`,\n {\n level: 'info',\n }\n );\n\n await buildDictionary([dictionaryToBuild], config);\n\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Dictionary ${colorizeKey(dictionaryKey)} built successfully`,\n {\n level: 'info',\n }\n );\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 = (result: ExtractResult): void => {\n const contentKeys = Object.keys(result.content);\n\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Extracted ${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\n /**\n * Detect the package name to import useIntlayer from based on file extension\n */\n const detectPackageName = (filename: string): string => {\n if (filename.endsWith('.vue')) {\n return 'vue-intlayer';\n }\n if (filename.endsWith('.svelte')) {\n return 'svelte-intlayer';\n }\n if (filename.endsWith('.tsx') || filename.endsWith('.jsx')) {\n return 'react-intlayer';\n }\n // Default to react-intlayer for JSX/TSX files\n return 'intlayer';\n };\n\n /**\n * Transform a Vue file using the Vue extraction plugin\n */\n const transformVue = async (\n code: string,\n filename: string,\n defaultLocale: string\n ) => {\n const { intlayerVueExtract } = await import('@intlayer/vue-compiler');\n return intlayerVueExtract(code, filename, {\n defaultLocale,\n filesList,\n packageName: 'vue-intlayer',\n onExtract: handleExtractedContent,\n });\n };\n\n /**\n * Transform a Svelte file using the Svelte extraction plugin\n */\n const transformSvelte = async (\n code: string,\n filename: string,\n defaultLocale: string\n ) => {\n const { intlayerSvelteExtract } = await import('@intlayer/svelte-compiler');\n const result = await intlayerSvelteExtract(code, filename, {\n defaultLocale,\n filesList,\n packageName: 'svelte-intlayer',\n onExtract: handleExtractedContent,\n });\n\n return result;\n };\n\n /**\n * Transform a JSX/TSX file using the Babel extraction plugin\n */\n const transformJsx = (\n code: string,\n filename: string,\n defaultLocale: string\n ) => {\n if (!babel) {\n return undefined;\n }\n\n const packageName = detectPackageName(filename);\n\n const result = babel.transformSync(code, {\n filename,\n plugins: [\n [\n intlayerExtractBabelPlugin,\n {\n defaultLocale,\n filesList,\n packageName,\n onExtract: handleExtractedContent,\n },\n ],\n ],\n parserOpts: {\n sourceType: 'module',\n allowImportExportEverywhere: true,\n plugins: [\n 'typescript',\n 'jsx',\n 'decorators-legacy',\n 'classProperties',\n 'objectRestSpread',\n 'asyncGenerators',\n 'functionBind',\n 'exportDefaultFrom',\n 'exportNamespaceFrom',\n 'dynamicImport',\n 'nullishCoalescingOperator',\n 'optionalChaining',\n ],\n },\n });\n\n if (result?.code) {\n return {\n code: result.code,\n map: result.map,\n extracted: true,\n };\n }\n\n return undefined;\n };\n\n /**\n * Transform a file using the appropriate extraction plugin based on file type\n */\n const transformHandler = async (\n code: string,\n id: string,\n _options?: { ssr?: boolean }\n ) => {\n const compilerConfig = getCompilerConfig();\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 { defaultLocale } = config.internationalization;\n\n const filename = id;\n\n if (!filesList.includes(filename)) {\n return undefined;\n }\n\n // Only process Vue and Svelte source files with extraction\n // JSX/TSX files are handled by Babel which has its own detection\n const isVue = filename.endsWith('.vue');\n const isSvelte = filename.endsWith('.svelte');\n\n if (!isVue && !isSvelte) {\n // For non-Vue/Svelte files, use JSX transformation via Babel\n try {\n const result = transformJsx(code, filename, defaultLocale);\n\n if (pendingDictionaryWrite) {\n await pendingDictionaryWrite;\n }\n\n if (result?.code) {\n return {\n code: result.code,\n map: result.map,\n };\n }\n } catch (error) {\n logger(\n `Failed to transform ${colorizePath(relative(projectRoot, filename))}: ${error}`,\n {\n level: 'error',\n }\n );\n }\n return undefined;\n }\n\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Transforming ${colorizePath(relative(projectRoot, filename))}`,\n {\n level: 'info',\n }\n );\n\n try {\n let result:\n | { code: string; map?: unknown; extracted?: boolean }\n | null\n | undefined;\n\n // Route to appropriate transformer based on file extension\n if (isVue) {\n result = await transformVue(code, filename, defaultLocale);\n } else if (isSvelte) {\n result = await transformSvelte(code, filename, defaultLocale);\n }\n\n // Wait for the dictionary to be written before returning\n // This ensures the dictionary exists before the prune plugin runs\n if (pendingDictionaryWrite) {\n await pendingDictionaryWrite;\n }\n\n if (result?.code) {\n return {\n code: result.code,\n map: result.map,\n };\n }\n } catch (error) {\n logger(\n `Failed to transform ${relative(projectRoot, filename)}: ${error}`,\n {\n level: 'error',\n }\n );\n }\n\n return undefined;\n };\n\n /**\n * Apply hook for determining when plugin should be active\n */\n const apply = (_config: unknown, _env: { command: string }): boolean => {\n const compilerConfig = getCompilerConfig();\n // Apply if compiler is enabled\n return compilerConfig.enabled;\n };\n\n return {\n name: 'vite-intlayer-compiler',\n enforce: 'pre',\n config: configHook,\n configResolved,\n buildStart,\n buildEnd,\n configureServer,\n handleHotUpdate,\n transform: transformHandler,\n apply: (_viteConfig: unknown, env: { command: string }) => {\n // Initialize config if not already done\n if (!config) {\n config = getConfiguration(configOptions);\n }\n return apply(_viteConfig, env);\n },\n };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoFA,MAAa,oBAAoB,YAA2C;CAE1E,IAAIA;CACJ,IAAIC;CACJ,IAAI,cAAc;CAClB,IAAIC,YAAsB,EAAE;CAC5B,IAAIC,QAAa;CAGjB,IAAIC,yBAA+C;CAEnD,MAAM,gBAAgB,SAAS;CAC/B,MAAM,uBAAuB,SAAS;;;;CAKtC,MAAM,0BAA0B;EAE9B,MAAM,YAAY;AAIlB,SAAO;GACL,SACE,sBAAsB,WAAW,UAAU,UAAU,WAAW;GAClE,kBACE,sBAAsB,oBACtB,UAAU,UAAU,oBACpB,OAAO,MAAM;GACf,gBAAgB,sBAAsB,kBACpC,UAAU,UAAU,kBAAkB,CAAC,qBAAqB;GAC9D,WACE,sBAAsB,aACtB,UAAU,UAAU,aACpB;GACH;;;;;CAMH,MAAM,qBAA6B;EACjC,MAAM,EAAE,YAAY,OAAO;AAE3B,SAAO,KAAK,SADW,mBAAmB,CACN,UAAU;;;;;CAMhD,MAAM,yBAAyB,kBAAkC;AAE/D,SAAO,KADW,cAAc,EACT,GAAG,cAAc,eAAe;;;;;CAMzD,MAAM,yBAAyB,OAC7B,kBAC+B;EAC/B,MAAM,WAAW,sBAAsB,cAAc;AAErD,MAAI,CAAC,WAAW,SAAS,CACvB,QAAO;AAGT,MAAI;GACF,MAAM,UAAU,MAAM,SAAS,UAAU,QAAQ;AACjD,UAAO,KAAK,MAAM,QAAQ;UACpB;AACN,UAAO;;;;;;;;;CAUX,MAAM,2CACJ,kBACA,oBACA,kBACyB;EACzB,MAAMC,gBAAsC,EAAE;EAC9C,MAAM,kBAAkB,oBAAoB;AAI5C,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,iBAAiB,EAAE;GAC3D,MAAM,gBAAgB,kBAAkB;AAExC,OACE,iBACA,cAAc,aAAa,iBAC3B,cAAc,aACd;IACA,MAAM,WAAW,cAAc,YAAY;IAC3C,MAAM,YAAY,aAAa;AAG/B,kBAAc,OAAO;KACnB,UAAU;KACV,aAAa;MACX,GAAG,cAAc;OAChB,gBAAgB;MAClB;KACF;AAED,QAAI,UACF,QACE,GAAG,SAAS,aAAa,WAAW,UAAU,CAAC,YAAY,IAAI,KAAK,cAAc,MAAM,UAAU,MAAM,GAAG,GAAG,CAAC,UAAU,MAAM,MAAM,GAAG,GAAG,CAAC,OAC5I;KAAE,OAAO;KAAQ,WAAW;KAAM,CACnC;UAEE;AAEL,kBAAc,OAAO;KACnB,UAAU;KACV,aAAa,GACV,gBAAgB,OAClB;KACF;AACD,WACE,GAAG,SAAS,aAAa,WAAW,UAAU,CAAC,kBAAkB,IAAI,IACrE;KACE,OAAO;KACP,WAAW;KACZ,CACF;;;AAKL,MAAI,iBAAiB;GACnB,MAAM,cAAc,OAAO,KAAK,gBAAgB,CAAC,QAC9C,QAAQ,EAAE,OAAO,kBACnB;AACD,QAAK,MAAM,OAAO,YAChB,QACE,GAAG,SAAS,aAAa,WAAW,UAAU,CAAC,gBAAgB,IAAI,0BACnE;IACE,OAAO;IACP,WAAW;IACZ,CACF;;AAIL,SAAO;;;;;;;;CAST,MAAM,wCACJ,kBACA,oBACA,kBAC2B;EAC3B,MAAMC,gBAAwC,EAAE;EAChD,MAAM,kBAAkB,oBAAoB;AAI5C,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,iBAAiB,EAAE;GAC3D,MAAM,gBAAgB,kBAAkB;AAExC,OAAI,iBAAiB,OAAO,kBAAkB,UAAU;IACtD,MAAM,YAAY,kBAAkB;AAEpC,kBAAc,OAAO;AAErB,QAAI,UACF,QACE,GAAG,SAAS,aAAa,WAAW,UAAU,CAAC,YAAY,IAAI,KAAK,cAAc,MAAM,eAAe,MAAM,GAAG,GAAG,CAAC,UAAU,MAAM,MAAM,GAAG,GAAG,CAAC,OACjJ;KAAE,OAAO;KAAQ,WAAW;KAAM,CACnC;UAEE;AAEL,kBAAc,OAAO;AACrB,WACE,GAAG,SAAS,aAAa,WAAW,UAAU,CAAC,kBAAkB,IAAI,IACrE;KACE,OAAO;KACP,WAAW;KACZ,CACF;;;AAKL,MAAI,iBAAiB;GACnB,MAAM,cAAc,OAAO,KAAK,gBAAgB,CAAC,QAC9C,QAAQ,EAAE,OAAO,kBACnB;AACD,QAAK,MAAM,OAAO,YAChB,QACE,GAAG,SAAS,aAAa,WAAW,UAAU,CAAC,gBAAgB,IAAI,0BACnE;IACE,OAAO;IACP,WAAW;IACZ,CACF;;AAIL,SAAO;;;;;CAMT,MAAM,iBAAiB,YAA2B;EAChD,MAAM,EAAE,YAAY,OAAO;EAC3B,MAAM,iBAAiB,mBAAmB;EAE1C,MAAM,WAAW,MAAM,QAAQ,eAAe,iBAAiB,GAC3D,eAAe,mBACf,CAAC,eAAe,iBAAiB;EACrC,MAAM,kBAAkB,MAAM,QAAQ,eAAe,eAAe,GAChE,eAAe,iBACf,CAAC,eAAe,eAAe;AAEnC,cAAY,GACT,KAAK,UAAU;GACd,KAAK;GACL,QAAQ;GACT,CAAC,CACD,KAAK,SAAS,KAAK,SAAS,KAAK,CAAC;;;;;CAMvC,MAAM,OAAO,OAAO,kBAA+C;AACjE,WAAS,iBAAiB,cAAc;AACxC,WAAS,aAAa,OAAO;AAG7B,MAAI;AAEF,WADqB,cAAc,OAAO,KAAK,IAAI,CAC9B,cAAc;UAC7B;AACN,UAAO,gEAAgE,EACrE,OAAO,QACR,CAAC;;AAIJ,QAAM,gBAAgB;;;;;;CAOxB,MAAM,aAAa,OACjB,SACA,QACkB;AAElB,WAAS,iBAAiB,cAAc;AACxC,WAAS,aAAa,OAAO;EAE7B,MAAM,eAAe,IAAI,YAAY,WAAW,IAAI,SAAS;EAC7D,MAAM,iBAAiB,IAAI,YAAY;AAIvC,MAAI,gBAAgB,eAClB,OAAM,gBAAgB,QAAQ;GAC5B,OAAO;GACP,gBAAgB,iBACZ,MAAO,KACP,MAAO,KAAK;GACjB,CAAC;;;;;;CAQN,MAAM,iBAAiB,OAAO,eAGT;EACnB,MAAMC,eAA6B,WAAW,KAAK,MAAM,QAAQ;AACjE,gBAAc,WAAW;AACzB,QAAM,KAAK,aAAa;;;;;;CAO1B,MAAM,aAAa,YAA2B;AAG5C,SAAO,iCAAiC,EACtC,OAAO,QACR,CAAC;;;;;CAMJ,MAAM,WAAW,YAA2B;AAE1C,MAAI,uBACF,OAAM;;;;;CAOV,MAAM,kBAAkB,YAA2B;;;;;CASnD,MAAM,kBAAkB,OAAO,QAA6C;EAC1E,MAAM,EAAE,MAAM,QAAQ,YAAY;AAKlC,MAF4B,UAAU,MAAM,MAAM,MAAM,KAAK,EAEpC;AAEvB,QAAK,MAAM,OAAO,QAChB,QAAO,YAAY,iBAAiB,IAAI;AAK1C,OAAI;AAIF,UAAM,iBAHO,MAAM,SAAS,MAAM,QAAQ,EAGb,KAAK;YAC3B,OAAO;AACd,WACE,GAAG,SAAS,aAAa,WAAW,UAAU,CAAC,0BAA0B,KAAK,IAAI,SAClF,EACE,OAAO,SACR,CACF;;AAIH,UAAO,GAAG,KAAK,EAAE,MAAM,eAAe,CAAC;AACvC,UAAO,EAAE;;;;;;;;;;;;;;;;CAmBb,MAAM,0BAA0B,OAC9B,WACkB;EAClB,MAAM,EAAE,eAAe,YAAY;EAEnC,MAAM,YAAY,cAAc;EAChC,MAAM,EAAE,kBAAkB,OAAO;EAKjC,MAAM,kBAAkB,QAAQ,QAAQ,YAAY,OAAO;AAG3D,QAAM,MAAM,WAAW,EAAE,WAAW,MAAM,CAAC;EAG3C,MAAM,qBAAqB,MAAM,uBAAuB,cAAc;EAEtE,MAAM,mBAAmB,KACvB,SAAS,OAAO,QAAQ,SAAS,UAAU,EAC3C,GAAG,cAAc,eAClB;EAGD,IAAIC;AAEJ,MAAI,iBAAiB;GAEnB,MAAM,gBAAgB,qCACpB,SACA,oBACA,cACD;AAED,sBAAmB;IAEjB,GAAI,sBAAsB;KACxB,SAAS,mBAAmB;KAC5B,IAAI,mBAAmB;KACvB,OAAO,mBAAmB;KAC1B,aAAa,mBAAmB;KAChC,MAAM,mBAAmB;KACzB,MAAM,mBAAmB;KACzB,QAAQ,mBAAmB;KAC3B,UAAU,mBAAmB;KAC7B,SAAS,mBAAmB;KAC7B;IAED,KAAK;IACL,SAAS;IACT,QAAQ;IACR,UAAU;IACX;SACI;GAEL,MAAM,gBAAgB,wCACpB,SACA,oBACA,cACD;AAED,sBAAmB;IAEjB,GAAI,sBAAsB;KACxB,SAAS,mBAAmB;KAC5B,IAAI,mBAAmB;KACvB,OAAO,mBAAmB;KAC1B,aAAa,mBAAmB;KAChC,MAAM,mBAAmB;KACzB,MAAM,mBAAmB;KACzB,QAAQ,mBAAmB;KAC3B,UAAU,mBAAmB;KAC7B,SAAS,mBAAmB;KAC7B;IAED,KAAK;IACL,SAAS;IACT,UAAU;IACX;;AAGH,MAAI;GACF,MAAM,cAAc,MAAM,wBACxB,kBACA,QACA,EACE,qBAAqB,SAAS,OAAO,QAAQ,SAAS,UAAU,EACjE,CACF;AAED,UACE,GAAG,SAAS,aAAa,WAAW,UAAU,CAAC,GAAG,YAAY,WAAW,YAAY,YAAY,YAAY,WAAW,YAAY,YAAY,YAAY,wBAAwB,aAAa,SAAS,aAAa,YAAY,KAAK,CAAC,IACzO,EACE,OAAO,QACR,CACF;GAGD,MAAMC,oBAAgC;IACpC,GAAG;IACH,UAAU,SAAS,OAAO,QAAQ,SAAS,YAAY,KAAK;IAC7D;AAED,UACE,GAAG,SAAS,aAAa,WAAW,UAAU,CAAC,uBAAuB,YAAY,cAAc,IAChG,EACE,OAAO,QACR,CACF;AAED,SAAM,gBAAgB,CAAC,kBAAkB,EAAE,OAAO;AAElD,UACE,GAAG,SAAS,aAAa,WAAW,UAAU,CAAC,cAAc,YAAY,cAAc,CAAC,sBACxF,EACE,OAAO,QACR,CACF;WACM,OAAO;AACd,UACE,GAAG,SAAS,aAAa,WAAW,UAAU,CAAC,wCAAwC,YAAY,cAAc,CAAC,IAAI,SACtH,EACE,OAAO,SACR,CACF;;;;;;;CAQL,MAAM,0BAA0B,WAAgC;EAC9D,MAAM,cAAc,OAAO,KAAK,OAAO,QAAQ;AAE/C,SACE,GAAG,SAAS,aAAa,WAAW,UAAU,CAAC,aAAa,YAAY,OAAO,qBAAqB,aAAa,SAAS,aAAa,OAAO,SAAS,CAAC,IACxJ,EACE,OAAO,QACR,CACF;AAGD,4BAA0B,0BAA0B,QAAQ,SAAS,EAClE,WAAW,wBAAwB,OAAO,CAAC,CAC3C,OAAO,UAAU;AAChB,UACE,GAAG,SAAS,aAAa,WAAW,UAAU,CAAC,oCAAoC,SACnF,EACE,OAAO,SACR,CACF;IACD;;;;;CAMN,MAAM,qBAAqB,aAA6B;AACtD,MAAI,SAAS,SAAS,OAAO,CAC3B,QAAO;AAET,MAAI,SAAS,SAAS,UAAU,CAC9B,QAAO;AAET,MAAI,SAAS,SAAS,OAAO,IAAI,SAAS,SAAS,OAAO,CACxD,QAAO;AAGT,SAAO;;;;;CAMT,MAAM,eAAe,OACnB,MACA,UACA,kBACG;EACH,MAAM,EAAE,uBAAuB,MAAM,OAAO;AAC5C,SAAO,mBAAmB,MAAM,UAAU;GACxC;GACA;GACA,aAAa;GACb,WAAW;GACZ,CAAC;;;;;CAMJ,MAAM,kBAAkB,OACtB,MACA,UACA,kBACG;EACH,MAAM,EAAE,0BAA0B,MAAM,OAAO;AAQ/C,SAPe,MAAM,sBAAsB,MAAM,UAAU;GACzD;GACA;GACA,aAAa;GACb,WAAW;GACZ,CAAC;;;;;CAQJ,MAAM,gBACJ,MACA,UACA,kBACG;AACH,MAAI,CAAC,MACH;EAGF,MAAM,cAAc,kBAAkB,SAAS;EAE/C,MAAM,SAAS,MAAM,cAAc,MAAM;GACvC;GACA,SAAS,CACP,CACE,4BACA;IACE;IACA;IACA;IACA,WAAW;IACZ,CACF,CACF;GACD,YAAY;IACV,YAAY;IACZ,6BAA6B;IAC7B,SAAS;KACP;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACD;IACF;GACF,CAAC;AAEF,MAAI,QAAQ,KACV,QAAO;GACL,MAAM,OAAO;GACb,KAAK,OAAO;GACZ,WAAW;GACZ;;;;;CASL,MAAM,mBAAmB,OACvB,MACA,IACA,aACG;AAIH,MAAI,CAHmB,mBAAmB,CAGtB,QAClB;AAKF,MAAI,GAAG,SAAS,IAAI,CAClB;EAGF,MAAM,EAAE,kBAAkB,OAAO;EAEjC,MAAM,WAAW;AAEjB,MAAI,CAAC,UAAU,SAAS,SAAS,CAC/B;EAKF,MAAM,QAAQ,SAAS,SAAS,OAAO;EACvC,MAAM,WAAW,SAAS,SAAS,UAAU;AAE7C,MAAI,CAAC,SAAS,CAAC,UAAU;AAEvB,OAAI;IACF,MAAM,SAAS,aAAa,MAAM,UAAU,cAAc;AAE1D,QAAI,uBACF,OAAM;AAGR,QAAI,QAAQ,KACV,QAAO;KACL,MAAM,OAAO;KACb,KAAK,OAAO;KACb;YAEI,OAAO;AACd,WACE,uBAAuB,aAAa,SAAS,aAAa,SAAS,CAAC,CAAC,IAAI,SACzE,EACE,OAAO,SACR,CACF;;AAEH;;AAGF,SACE,GAAG,SAAS,aAAa,WAAW,UAAU,CAAC,gBAAgB,aAAa,SAAS,aAAa,SAAS,CAAC,IAC5G,EACE,OAAO,QACR,CACF;AAED,MAAI;GACF,IAAIC;AAMJ,OAAI,MACF,UAAS,MAAM,aAAa,MAAM,UAAU,cAAc;YACjD,SACT,UAAS,MAAM,gBAAgB,MAAM,UAAU,cAAc;AAK/D,OAAI,uBACF,OAAM;AAGR,OAAI,QAAQ,KACV,QAAO;IACL,MAAM,OAAO;IACb,KAAK,OAAO;IACb;WAEI,OAAO;AACd,UACE,uBAAuB,SAAS,aAAa,SAAS,CAAC,IAAI,SAC3D,EACE,OAAO,SACR,CACF;;;;;;CASL,MAAM,SAAS,SAAkB,SAAuC;AAGtE,SAFuB,mBAAmB,CAEpB;;AAGxB,QAAO;EACL,MAAM;EACN,SAAS;EACT,QAAQ;EACR;EACA;EACA;EACA;EACA;EACA,WAAW;EACX,QAAQ,aAAsB,QAA6B;AAEzD,OAAI,CAAC,OACH,UAAS,iBAAiB,cAAc;AAE1C,UAAO,MAAM,aAAa,IAAI;;EAEjC"}
|
|
1
|
+
{"version":3,"file":"IntlayerCompilerPlugin.mjs","names":["config: IntlayerConfig","logger: ReturnType<typeof getAppLogger>","filesList: string[]","babel: any","pendingDictionaryWrite: Promise<void> | null","mergedContent: DictionaryContentMap","mergedContent: Record<string, string>","compilerMode: CompilerMode","mergedDictionary: Dictionary","dictionaryToBuild: Dictionary","result:\n | { code: string; map?: unknown; extracted?: boolean }\n | null\n | undefined"],"sources":["../../src/IntlayerCompilerPlugin.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { mkdir, readFile } from 'node:fs/promises';\nimport { createRequire } from 'node:module';\nimport { join, relative } from 'node:path';\nimport {\n type ExtractResult,\n intlayerExtractBabelPlugin,\n} from '@intlayer/babel';\nimport {\n buildDictionary,\n buildFilesList,\n prepareIntlayer,\n writeContentDeclaration,\n} from '@intlayer/chokidar';\nimport {\n ANSIColors,\n colorize,\n colorizeKey,\n colorizePath,\n type GetConfigurationOptions,\n getAppLogger,\n getConfiguration,\n} from '@intlayer/config';\nimport type {\n CompilerConfig,\n Dictionary,\n IntlayerConfig,\n} from '@intlayer/types';\n\n/**\n * Translation node structure used in dictionaries\n */\ntype TranslationNode = {\n nodeType: 'translation';\n translation: Record<string, string>;\n};\n\n/**\n * Dictionary content structure - map of keys to translation nodes\n */\ntype DictionaryContentMap = Record<string, TranslationNode>;\n\n/**\n * Mode of the compiler\n * - 'dev': Development mode with HMR support\n * - 'build': Production build mode\n */\nexport type CompilerMode = 'dev' | 'build';\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 */\nexport const intlayerCompiler = (options?: IntlayerCompilerOptions): any => {\n // Private state\n let config: IntlayerConfig;\n let logger: ReturnType<typeof getAppLogger>;\n let projectRoot = '';\n let filesList: string[] = [];\n let babel: any = null;\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 const configOptions = options?.configOptions;\n const customCompilerConfig = options?.compilerConfig;\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 return JSON.stringify(\n Object.keys(content)\n .sort()\n .map((k) => [k, content[k]])\n );\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 * Get compiler config from intlayer config or custom options\n */\n const getCompilerConfig = () => {\n // Access compiler config from the raw config (may not be in the type)\n const rawConfig = config as IntlayerConfig & {\n compiler?: Partial<CompilerConfig>;\n };\n\n return {\n enabled:\n customCompilerConfig?.enabled ?? rawConfig.compiler?.enabled ?? true,\n transformPattern:\n customCompilerConfig?.transformPattern ??\n rawConfig.compiler?.transformPattern ??\n config.build.traversePattern,\n excludePattern: [\n ...(customCompilerConfig?.excludePattern ?? []),\n '**/node_modules/**',\n ...config.content.fileExtensions.map((pattern) => `*${pattern}`),\n ],\n outputDir:\n customCompilerConfig?.outputDir ??\n rawConfig.compiler?.outputDir ??\n 'compiler',\n };\n };\n\n /**\n * Get the output directory path for compiler dictionaries\n */\n const getOutputDir = (): string => {\n const { baseDir } = config.content;\n const compilerConfig = getCompilerConfig();\n return join(baseDir, compilerConfig.outputDir);\n };\n\n /**\n * Get the file path for a dictionary\n */\n const getDictionaryFilePath = (dictionaryKey: string): string => {\n const outputDir = getOutputDir();\n return join(outputDir, `${dictionaryKey}.content.json`);\n };\n\n /**\n * Read an existing dictionary file if it exists\n */\n const readExistingDictionary = async (\n dictionaryKey: string\n ): Promise<Dictionary | null> => {\n const filePath = getDictionaryFilePath(dictionaryKey);\n\n if (!existsSync(filePath)) {\n return null;\n }\n\n try {\n const content = await readFile(filePath, 'utf-8');\n return JSON.parse(content) as Dictionary;\n } catch {\n return null;\n }\n };\n\n /**\n * Merge extracted content with existing dictionary for multilingual format.\n * - Keys in extracted but not in existing: added with default locale only\n * - Keys in both: preserve existing translations, update default locale value\n * - Keys in existing but not in extracted: removed (no longer in source)\n */\n const mergeWithExistingMultilingualDictionary = (\n extractedContent: Record<string, string>,\n existingDictionary: Dictionary | null,\n defaultLocale: string\n ): DictionaryContentMap => {\n const mergedContent: DictionaryContentMap = {};\n const existingContent = existingDictionary?.content as\n | DictionaryContentMap\n | undefined;\n\n for (const [key, value] of Object.entries(extractedContent)) {\n const existingEntry = existingContent?.[key];\n\n if (\n existingEntry &&\n existingEntry.nodeType === 'translation' &&\n existingEntry.translation\n ) {\n const oldValue = existingEntry.translation[defaultLocale];\n const isUpdated = oldValue !== value;\n\n // Key exists in both - preserve existing translations, update default locale\n mergedContent[key] = {\n nodeType: 'translation',\n translation: {\n ...existingEntry.translation,\n [defaultLocale]: value,\n },\n };\n\n if (isUpdated) {\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Updated \"${key}\" [${defaultLocale}]: \"${oldValue?.slice(0, 30)}...\" → \"${value.slice(0, 30)}...\"`,\n { level: 'info', isVerbose: true }\n );\n }\n } else {\n // New key - add with default locale only\n mergedContent[key] = {\n nodeType: 'translation',\n translation: {\n [defaultLocale]: value,\n },\n };\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Added new key \"${key}\"`,\n {\n level: 'info',\n isVerbose: true,\n }\n );\n }\n }\n\n // Log removed keys\n if (existingContent) {\n const removedKeys = Object.keys(existingContent).filter(\n (key) => !(key in extractedContent)\n );\n for (const key of removedKeys) {\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Removed key \"${key}\" (no longer in source)`,\n {\n level: 'info',\n isVerbose: true,\n }\n );\n }\n }\n\n return mergedContent;\n };\n\n /**\n * Merge extracted content with existing dictionary for per-locale format.\n * - Keys in extracted but not in existing: added\n * - Keys in both: update value\n * - Keys in existing but not in extracted: removed (no longer in source)\n */\n const mergeWithExistingPerLocaleDictionary = (\n extractedContent: Record<string, string>,\n existingDictionary: Dictionary | null,\n defaultLocale: string\n ): Record<string, string> => {\n const mergedContent: Record<string, string> = {};\n const existingContent = existingDictionary?.content as\n | Record<string, string>\n | undefined;\n\n for (const [key, value] of Object.entries(extractedContent)) {\n const existingValue = existingContent?.[key];\n\n if (existingValue && typeof existingValue === 'string') {\n const isUpdated = existingValue !== value;\n\n mergedContent[key] = value;\n\n if (isUpdated) {\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Updated \"${key}\" [${defaultLocale}]: \"${existingValue?.slice(0, 30)}...\" → \"${value.slice(0, 30)}...\"`,\n { level: 'info', isVerbose: true }\n );\n }\n } else {\n // New key\n mergedContent[key] = value;\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Added new key \"${key}\"`,\n {\n level: 'info',\n isVerbose: true,\n }\n );\n }\n }\n\n // Log removed keys\n if (existingContent) {\n const removedKeys = Object.keys(existingContent).filter(\n (key) => !(key in extractedContent)\n );\n for (const key of removedKeys) {\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Removed key \"${key}\" (no longer in source)`,\n {\n level: 'info',\n isVerbose: true,\n }\n );\n }\n }\n\n return mergedContent;\n };\n\n /**\n * Build the list of files to transform based on configuration patterns\n */\n const buildFilesListFn = async (): Promise<void> => {\n const { baseDir, fileExtensions } = config.content;\n const compilerConfig = getCompilerConfig();\n\n const excludePatterns = Array.isArray(compilerConfig.excludePattern)\n ? compilerConfig.excludePattern\n : [compilerConfig.excludePattern];\n\n filesList = buildFilesList({\n transformPattern: compilerConfig.transformPattern,\n excludePattern: [\n ...excludePatterns,\n '**/node_modules/**',\n ...fileExtensions.map((pattern) => `**/*${pattern}`),\n ],\n baseDir,\n });\n };\n\n /**\n * Initialize the compiler with the given mode\n */\n const init = async (_compilerMode: CompilerMode): Promise<void> => {\n config = getConfiguration(configOptions);\n logger = getAppLogger(config);\n\n // Load Babel dynamically\n try {\n const localRequire = createRequire(import.meta.url);\n babel = localRequire('@babel/core');\n } catch {\n logger('Failed to load @babel/core. Transformation will be disabled.', {\n level: 'warn',\n });\n }\n\n // Build files list for transformation\n await buildFilesListFn();\n };\n\n /**\n * Vite hook: config\n * Called before Vite config is resolved - perfect time to prepare dictionaries\n */\n const configHook = async (\n _config: unknown,\n env: { command: string; mode: string }\n ): Promise<void> => {\n // Initialize config early\n config = getConfiguration(configOptions);\n logger = getAppLogger(config);\n\n const isDevCommand = env.command === 'serve' && env.mode === 'development';\n const isBuildCommand = env.command === 'build';\n\n // Prepare all existing dictionaries (builds them to .intlayer/dictionary/)\n // This ensures built dictionaries exist before the prune plugin runs\n if (isDevCommand || isBuildCommand) {\n await prepareIntlayer(config, {\n clean: isBuildCommand,\n cacheTimeoutMs: isBuildCommand\n ? 1000 * 30 // 30 seconds for build\n : 1000 * 60 * 60, // 1 hour for dev\n });\n }\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 }): Promise<void> => {\n const compilerMode: CompilerMode = viteConfig.env?.DEV ? 'dev' : 'build';\n projectRoot = viteConfig.root;\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 // Autonomous compiler - no need to prepare dictionaries\n // Content is extracted inline during transformation\n logger('Intlayer compiler initialized', {\n level: 'info',\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 * Configure the dev server\n */\n const configureServer = async (): Promise<void> => {\n // In autonomous mode, we don't need file watching for dictionaries\n // Content is extracted inline during transformation\n };\n\n /**\n * Vite hook: handleHotUpdate\n * Handles HMR for content files - invalidates cache and triggers re-transform\n */\n const handleHotUpdate = async (ctx: any): Promise<unknown[] | undefined> => {\n const { file, server, modules } = ctx;\n\n // Check if this is a file we should transform\n const isTransformableFile = filesList.some((f) => f === file);\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 return [];\n }\n\n return undefined;\n };\n\n /**\n * Write and build a single dictionary immediately\n * This is called during transform to ensure dictionaries are always up-to-date.\n *\n * The merge strategy:\n * - New keys are added with the default locale only\n * - Existing keys preserve their translations, with default locale updated\n * - Keys no longer in source are removed\n *\n * Dictionary format:\n * - Per-locale: When config.dictionary.locale is set, content is simple strings with locale property\n * - Multilingual: When not set, content is wrapped in translation nodes without locale property\n */\n const writeAndBuildDictionary = async (\n result: ExtractResult\n ): Promise<void> => {\n const { dictionaryKey, content } = 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 const outputDir = getOutputDir();\n const { defaultLocale } = config.internationalization;\n\n // Check if per-locale format is configured\n // When config.dictionary.locale is set, use per-locale format (simple strings with locale property)\n // Otherwise, use multilingual format (content wrapped in TranslationNode objects)\n const isPerLocaleFile = Boolean(config?.dictionary?.locale);\n\n // Ensure output directory exists\n await mkdir(outputDir, { recursive: true });\n\n // Read existing dictionary to preserve translations and metadata\n const existingDictionary = await readExistingDictionary(dictionaryKey);\n\n const relativeFilePath = join(\n relative(config.content.baseDir, outputDir),\n `${dictionaryKey}.content.json`\n );\n\n // Build dictionary based on format - matching transformFiles.ts behavior\n let mergedDictionary: Dictionary;\n\n if (isPerLocaleFile) {\n // Per-locale format: simple string content with locale property\n const mergedContent = mergeWithExistingPerLocaleDictionary(\n content,\n existingDictionary,\n defaultLocale\n );\n\n mergedDictionary = {\n // Preserve existing metadata (title, description, tags, etc.)\n ...(existingDictionary && {\n $schema: existingDictionary.$schema,\n id: existingDictionary.id,\n title: existingDictionary.title,\n description: existingDictionary.description,\n tags: existingDictionary.tags,\n fill: existingDictionary.fill,\n filled: existingDictionary.filled,\n priority: existingDictionary.priority,\n version: existingDictionary.version,\n }),\n // Required fields\n key: dictionaryKey,\n content: mergedContent,\n locale: defaultLocale,\n filePath: relativeFilePath,\n };\n } else {\n // Multilingual format: content wrapped in translation nodes, no locale property\n const mergedContent = mergeWithExistingMultilingualDictionary(\n content,\n existingDictionary,\n defaultLocale\n );\n\n mergedDictionary = {\n // Preserve existing metadata (title, description, tags, etc.)\n ...(existingDictionary && {\n $schema: existingDictionary.$schema,\n id: existingDictionary.id,\n title: existingDictionary.title,\n description: existingDictionary.description,\n tags: existingDictionary.tags,\n fill: existingDictionary.fill,\n filled: existingDictionary.filled,\n priority: existingDictionary.priority,\n version: existingDictionary.version,\n }),\n // Required fields\n key: dictionaryKey,\n content: mergedContent,\n filePath: relativeFilePath,\n };\n }\n\n try {\n const writeResult = await writeContentDeclaration(\n mergedDictionary,\n config,\n {\n newDictionariesPath: relative(config.content.baseDir, outputDir),\n }\n );\n\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} ${writeResult.status === 'created' ? 'Created' : writeResult.status === 'updated' ? 'Updated' : 'Processed'} content declaration: ${colorizePath(relative(projectRoot, writeResult.path))}`,\n {\n level: 'info',\n }\n );\n\n // Build the dictionary immediately so it's available for the prune plugin\n const dictionaryToBuild: Dictionary = {\n ...mergedDictionary,\n filePath: relative(config.content.baseDir, writeResult.path),\n };\n\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Building dictionary ${colorizeKey(dictionaryKey)}`,\n {\n level: 'info',\n }\n );\n\n await buildDictionary([dictionaryToBuild], config);\n\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Dictionary ${colorizeKey(dictionaryKey)} built successfully`,\n {\n level: 'info',\n }\n );\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 = (result: ExtractResult): void => {\n const contentKeys = Object.keys(result.content);\n\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Extracted ${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\n /**\n * Detect the package name to import useIntlayer from based on file extension\n */\n const detectPackageName = (filename: string): string => {\n if (filename.endsWith('.vue')) {\n return 'vue-intlayer';\n }\n if (filename.endsWith('.svelte')) {\n return 'svelte-intlayer';\n }\n if (filename.endsWith('.tsx') || filename.endsWith('.jsx')) {\n return 'react-intlayer';\n }\n // Default to react-intlayer for JSX/TSX files\n return 'intlayer';\n };\n\n /**\n * Transform a Vue file using the Vue extraction plugin\n */\n const transformVue = async (\n code: string,\n filename: string,\n defaultLocale: string\n ) => {\n const { intlayerVueExtract } = await import('@intlayer/vue-compiler');\n return intlayerVueExtract(code, filename, {\n defaultLocale,\n filesList,\n packageName: 'vue-intlayer',\n onExtract: handleExtractedContent,\n });\n };\n\n /**\n * Transform a Svelte file using the Svelte extraction plugin\n */\n const transformSvelte = async (\n code: string,\n filename: string,\n defaultLocale: string\n ) => {\n const { intlayerSvelteExtract } = await import('@intlayer/svelte-compiler');\n const result = await intlayerSvelteExtract(code, filename, {\n defaultLocale,\n filesList,\n packageName: 'svelte-intlayer',\n onExtract: handleExtractedContent,\n });\n\n return result;\n };\n\n /**\n * Transform a JSX/TSX file using the Babel extraction plugin\n */\n const transformJsx = (\n code: string,\n filename: string,\n defaultLocale: string\n ) => {\n if (!babel) {\n return undefined;\n }\n\n const packageName = detectPackageName(filename);\n\n const result = babel.transformSync(code, {\n filename,\n plugins: [\n [\n intlayerExtractBabelPlugin,\n {\n defaultLocale,\n filesList,\n packageName,\n onExtract: handleExtractedContent,\n },\n ],\n ],\n parserOpts: {\n sourceType: 'module',\n allowImportExportEverywhere: true,\n plugins: [\n 'typescript',\n 'jsx',\n 'decorators-legacy',\n 'classProperties',\n 'objectRestSpread',\n 'asyncGenerators',\n 'functionBind',\n 'exportDefaultFrom',\n 'exportNamespaceFrom',\n 'dynamicImport',\n 'nullishCoalescingOperator',\n 'optionalChaining',\n ],\n },\n });\n\n if (result?.code) {\n return {\n code: result.code,\n map: result.map,\n extracted: true,\n };\n }\n\n return undefined;\n };\n\n /**\n * Transform a file using the appropriate extraction plugin based on file type\n */\n const transformHandler = async (\n code: string,\n id: string,\n _options?: { ssr?: boolean }\n ) => {\n const compilerConfig = getCompilerConfig();\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 { defaultLocale } = config.internationalization;\n\n const filename = id;\n\n if (!filesList.includes(filename)) {\n return undefined;\n }\n\n // Only process Vue and Svelte source files with extraction\n // JSX/TSX files are handled by Babel which has its own detection\n const isVue = filename.endsWith('.vue');\n const isSvelte = filename.endsWith('.svelte');\n\n if (!isVue && !isSvelte) {\n // For non-Vue/Svelte files, use JSX transformation via Babel\n try {\n const result = transformJsx(code, filename, defaultLocale);\n\n if (pendingDictionaryWrite) {\n await pendingDictionaryWrite;\n }\n\n if (result?.code) {\n return {\n code: result.code,\n map: result.map,\n };\n }\n } catch (error) {\n logger(\n `Failed to transform ${colorizePath(relative(projectRoot, filename))}: ${error}`,\n {\n level: 'error',\n }\n );\n }\n return undefined;\n }\n\n logger(\n `${colorize('Compiler:', ANSIColors.GREY_DARK)} Transforming ${colorizePath(relative(projectRoot, filename))}`,\n {\n level: 'info',\n }\n );\n\n try {\n let result:\n | { code: string; map?: unknown; extracted?: boolean }\n | null\n | undefined;\n\n // Route to appropriate transformer based on file extension\n if (isVue) {\n result = await transformVue(code, filename, defaultLocale);\n } else if (isSvelte) {\n result = await transformSvelte(code, filename, defaultLocale);\n }\n\n // Wait for the dictionary to be written before returning\n // This ensures the dictionary exists before the prune plugin runs\n if (pendingDictionaryWrite) {\n await pendingDictionaryWrite;\n }\n\n if (result?.code) {\n return {\n code: result.code,\n map: result.map,\n };\n }\n } catch (error) {\n logger(\n `Failed to transform ${relative(projectRoot, filename)}: ${error}`,\n {\n level: 'error',\n }\n );\n }\n\n return undefined;\n };\n\n /**\n * Apply hook for determining when plugin should be active\n */\n const apply = (_config: unknown, _env: { command: string }): boolean => {\n const compilerConfig = getCompilerConfig();\n // Apply if compiler is enabled\n return compilerConfig.enabled;\n };\n\n return {\n name: 'vite-intlayer-compiler',\n enforce: 'pre',\n config: configHook,\n configResolved,\n buildStart,\n buildEnd,\n configureServer,\n handleHotUpdate,\n transform: transformHandler,\n apply: (_viteConfig: unknown, env: { command: string }) => {\n // Initialize config if not already done\n if (!config) {\n config = getConfiguration(configOptions);\n }\n return apply(_viteConfig, env);\n },\n };\n};\n"],"mappings":"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"}
|
|
@@ -3,8 +3,8 @@ import { isAbsolute, join } from "node:path";
|
|
|
3
3
|
import { intlayerOptimizeBabelPlugin } from "@intlayer/babel";
|
|
4
4
|
import { runOnce } from "@intlayer/chokidar";
|
|
5
5
|
import { getAppLogger } from "@intlayer/config";
|
|
6
|
-
import fg from "fast-glob";
|
|
7
6
|
import { getDictionaries } from "@intlayer/dictionaries-entry";
|
|
7
|
+
import fg from "fast-glob";
|
|
8
8
|
|
|
9
9
|
//#region src/intlayerPrunePlugin.ts
|
|
10
10
|
const intlayerPrune = (intlayerConfig) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vite-intlayer",
|
|
3
|
-
"version": "7.3.
|
|
3
|
+
"version": "7.3.10",
|
|
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": [
|
|
@@ -77,14 +77,14 @@
|
|
|
77
77
|
},
|
|
78
78
|
"dependencies": {
|
|
79
79
|
"@babel/core": "7.28.4",
|
|
80
|
-
"@intlayer/babel": "7.3.
|
|
81
|
-
"@intlayer/chokidar": "7.3.
|
|
82
|
-
"@intlayer/config": "7.3.
|
|
83
|
-
"@intlayer/core": "7.3.
|
|
84
|
-
"@intlayer/dictionaries-entry": "7.3.
|
|
85
|
-
"@intlayer/types": "7.3.
|
|
80
|
+
"@intlayer/babel": "7.3.10",
|
|
81
|
+
"@intlayer/chokidar": "7.3.10",
|
|
82
|
+
"@intlayer/config": "7.3.10",
|
|
83
|
+
"@intlayer/core": "7.3.10",
|
|
84
|
+
"@intlayer/dictionaries-entry": "7.3.10",
|
|
85
|
+
"@intlayer/types": "7.3.10",
|
|
86
86
|
"fast-glob": "3.3.3",
|
|
87
|
-
"intlayer": "7.3.
|
|
87
|
+
"intlayer": "7.3.10"
|
|
88
88
|
},
|
|
89
89
|
"devDependencies": {
|
|
90
90
|
"@types/node": "24.10.1",
|
|
@@ -98,8 +98,8 @@
|
|
|
98
98
|
},
|
|
99
99
|
"peerDependencies": {
|
|
100
100
|
"@babel/core": ">=6.0.0",
|
|
101
|
-
"@intlayer/svelte-compiler": "7.3.
|
|
102
|
-
"@intlayer/vue-compiler": "7.3.
|
|
101
|
+
"@intlayer/svelte-compiler": "7.3.10",
|
|
102
|
+
"@intlayer/vue-compiler": "7.3.10",
|
|
103
103
|
"vite": ">=4.0.0"
|
|
104
104
|
},
|
|
105
105
|
"peerDependenciesMeta": {
|