import fs from 'fs'; import path from 'path'; // Regex to find placeholders like {variableName} const placeholderRegex = /\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g; interface Messages { [key: string]: string; } interface AllMessages { [lang: string]: Messages; } interface GenerateConfig { defaultLanguage: string; enabledLanguages: string[]; } function getFileHeader(): string { return `/* Auto-generated by vite-plugin-messages - DO NOT EDIT */\n/* Generated: ${new Date().toISOString()} */\n`; } export function generateMessages(langDir: string, outDir: string, config: GenerateConfig) { const { defaultLanguage, enabledLanguages } = config; if (!fs.existsSync(outDir)) { fs.mkdirSync(outDir, { recursive: true }); } const allMessages: AllMessages = {}; for (const lang of enabledLanguages) { const filePath = path.join(langDir, `${lang}.json`); try { const content = fs.readFileSync(filePath, 'utf-8'); allMessages[lang] = JSON.parse(content); } catch (e) { console.warn(`⚠️ Failed to load ${lang}.json:`, e instanceof Error ? e.message : String(e)); allMessages[lang] = {}; } } if (Object.keys(allMessages).length === 0) { console.warn('⚠️ No language files found'); return; } const defaultMessages = allMessages[defaultLanguage] || {}; const allKeys = Object.keys(defaultMessages); for (const lang of enabledLanguages) { const messages = allMessages[lang] || {}; const functions: string[] = []; const reExports: string[] = []; for (const key of allKeys) { const hasTranslation = key in messages; if (hasTranslation) { functions.push(createMessageFunction(key, messages[key])); } else if (lang !== defaultLanguage) { reExports.push(key); } } let content = getFileHeader(); if (reExports.length > 0) { content += `\nexport { ${reExports.join(', ')} } from './${defaultLanguage}.ts';\n`; } if (functions.length > 0) { content += `\n${functions.join('\n\n')}\n`; } const filePath = path.join(outDir, `${lang}.ts`); fs.writeFileSync(filePath, content, 'utf-8'); console.log(`✅ Generated messages/${lang}.ts (${functions.length} translated, ${reExports.length} re-exported)`); } generateIndexFile(outDir, allKeys, defaultMessages, config); } function extractPlaceholders(value: string): string[] { const matches = value.matchAll(placeholderRegex); const placeholders = new Set(); for (const match of matches) { placeholders.add(match[1]); } return Array.from(placeholders); } function escapeTemplateString(value: string): string { // Escape backticks and ${} that are not our placeholders return value .replace(/\\/g, '\\\\') .replace(/`/g, '\\`'); } function createMessageFunction(key: string, value: string): string { const placeholders = extractPlaceholders(value); const templateString = value.replace(placeholderRegex, '${params.$1}'); const escapedTemplate = escapeTemplateString(templateString); if (placeholders.length > 0) { const paramsType = placeholders.map(p => `${p}: string | number`).join(', '); return `export const ${key} = (params: { ${paramsType} }): string => \`${escapedTemplate}\`;`; } return `export const ${key} = (): string => \`${escapedTemplate}\`;`; } function generateIndexFile(outDir: string, allKeys: string[], defaultMessages: Messages, config: GenerateConfig) { const { enabledLanguages } = config; const langImports = enabledLanguages .map(lang => `import * as ${lang} from './${lang}.ts';`) .join('\n'); const langObject = enabledLanguages.join(', '); const languageType = enabledLanguages.map(l => `'${l}'`).join(' | '); const messageExports: string[] = []; for (const key of allKeys) { const value = defaultMessages[key] || ''; const placeholders = extractPlaceholders(value); const paramsType = placeholders.length > 0 ? `params: { ${placeholders.map(p => `${p}: string | number`).join(', ')} }` : ''; const paramsArg = paramsType ? 'params' : ''; messageExports.push( `export const ${key} = (${paramsType}): string => allMessages[getLocale()].${key}(${paramsArg});` ); } const content = `${getFileHeader()} import config from '@/config'; ${langImports} const allMessages = { ${langObject} } as const; export type Language = ${languageType}; export const defaultLanguage: Language = '${config.defaultLanguage}'; export const enabledLanguages: readonly Language[] = ${JSON.stringify(config.enabledLanguages)} as const; let _locale: Language = defaultLanguage; export const getLocale = (): Language => _locale; export const isValidLocale = (locale: unknown): locale is Language => enabledLanguages.includes(locale as Language); export const setLocale = (locale: unknown): void => { if (isValidLocale(locale)) { _locale = locale as Language; } }; // Message functions ${messageExports.join('\n\n')} `; const filePath = path.join(outDir, 'index.ts'); fs.writeFileSync(filePath, content, 'utf-8'); console.log('✅ Generated messages/index.ts'); }