UNPKG

vite-intlayer

Version:

A Vite plugin for seamless internationalization (i18n), providing locale detection, redirection, and environment-based configuration

1 lines 38.3 kB
{"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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"}