import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import { dirname } from 'path'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); /** * Vite 插件:构建时直接读取 CSV,替换代码中的 $t('xxx') 为翻译文本 * @param {object} options - 插件选项 * @param {string} options.locale - 目标语言代码 */ function vitePluginTranslate(options = {}) { let translations = {}; let locale = options.locale || 'en'; return { name: 'vite-plugin-translate', enforce: 'pre', configResolved(config) { if (locale && locale !== 'en') { const csvPath = path.resolve(__dirname, `../jcloud/translations/${locale}.csv`); if (fs.existsSync(csvPath)) { translations = parseCSV(csvPath); } } }, transform(code, id) { const fileMatches = /\.(vue|js|ts|jsx|tsx)(\?.*)?$/.test(id); if (!fileMatches || locale === 'en' || Object.keys(translations).length === 0) { return null; } let replaced = code; const isVueFile = id.includes('.vue') && !id.includes('?vue&type=style'); if (isVueFile) { const isTemplateModule = id.includes('?vue&type=template'); if (isTemplateModule) { // Vue 编译后的模板模块 replaced = replaceInterpolations(replaced, translations); replaced = replaceCompiledFormat(replaced, translations); replaced = replaceAttributeBindings(replaced, translations); } else { // 原始 Vue SFC 文件 replaced = processVueSFC(replaced, translations); } } else { // 非 Vue 文件 replaced = replaced.replace( /\bt\((['"])([^'"]+)\1\)/g, (match, quote, key) => { const translation = translations[key]; return translation ? quote + escapeString(translation, quote) + quote : match; } ); } return replaced !== code ? { code: replaced, map: null } : null; } }; } /** * 处理 Vue SFC 文件 */ function processVueSFC(code, translations) { // 处理 ,处理嵌套 let depth = 1; let pos = templateRegex.lastIndex; let templateContent = ''; while (depth > 0 && pos < code.length) { const nextOpen = code.indexOf('', pos); if (nextClose === -1) break; if (nextOpen !== -1 && nextOpen < nextClose) { depth++; pos = nextOpen + 9; } else { depth--; if (depth === 0) { templateContent = code.substring(templateRegex.lastIndex, nextClose); break; } pos = nextClose + 11; } } if (depth === 0 && templateContent) { let replacedContent = templateContent; replacedContent = replaceInterpolations(replacedContent, translations); replacedContent = replaceAttributeBindings(replacedContent, translations); code = code.substring(0, startIndex) + openTag + replacedContent + '' + code.substring(startIndex + openTag.length + templateContent.length + 11); break; } } // 处理