From 206612e23b512d1231f51b2140c6a13894349cbc Mon Sep 17 00:00:00 2001 From: jingrow Date: Mon, 29 Dec 2025 22:21:35 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96vite=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E6=8F=92=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dashboard/vite-plugin-translate.mjs | 116 +++++++++++++++++++++------- 1 file changed, 86 insertions(+), 30 deletions(-) diff --git a/dashboard/vite-plugin-translate.mjs b/dashboard/vite-plugin-translate.mjs index 541aaed..00d8854 100644 --- a/dashboard/vite-plugin-translate.mjs +++ b/dashboard/vite-plugin-translate.mjs @@ -390,6 +390,23 @@ function replaceAttributeBindings(code, translations) { ); } +/** + * 在代码片段中替换所有 $t() 调用为翻译文本 + * @param {string} content - 要处理的代码片段 + * @param {object} translations - 翻译字典 + * @param {RegExp} pattern - 可选的匹配模式,默认匹配所有 $t() 调用 + * @returns {string} 替换后的代码 + */ +function replaceTranslationCalls(content, translations, pattern = /\$t\((['"])((?:\\.|(?!\1).)*)\1\)/g) { + return content.replace(pattern, (match, quote, key) => { + const unescapedKey = unescapeString(key); + const translation = translations[unescapedKey]; + if (!translation || !translation.trim()) return match; + const escaped = escapeString(translation, quote); + return `${quote}${escaped}${quote}`; + }); +} + /** * 替换属性绑定中数组字面量内的 $t('xxx') * 例如::items="[{ label: $t('key'), ... }]" @@ -400,18 +417,7 @@ function replaceAttributeArrayLiterals(code, translations) { return code.replace( /([:@]|v-bind:|v-on:)([a-zA-Z0-9_-]+)=(["'])\[([\s\S]*?)\]\3/g, (match, prefix, attrName, outerQuote, arrayContent) => { - // 在数组内容中查找并替换所有 $t() 调用,直接替换为翻译文本 - const replacedContent = arrayContent.replace( - /\$t\((['"])((?:\\.|(?!\1).)*)\1\)/g, - (m, quote, key) => { - const unescapedKey = unescapeString(key); - const translation = translations[unescapedKey]; - if (!translation || !translation.trim()) return m; - // 直接替换为翻译文本字符串,转义引号 - const escaped = escapeString(translation, quote); - return `${quote}${escaped}${quote}`; - } - ); + const replacedContent = replaceTranslationCalls(arrayContent, translations); return `${prefix}${attrName}=${outerQuote}[${replacedContent}]${outerQuote}`; } ); @@ -422,26 +428,76 @@ function replaceAttributeArrayLiterals(code, translations) { * 例如::options="{ title: $t('key'), actions: [...] }" */ function replaceAttributeObjectLiterals(code, translations) { - // 匹配属性绑定中的对象字面量,如 :options="{ ... }" - // 需要处理嵌套的大括号 - return code.replace( - /([:@]|v-bind:|v-on:)([a-zA-Z0-9_-]+)=(["'])\{([\s\S]*?)\}\3/g, - (match, prefix, attrName, outerQuote, objectContent) => { - // 在对象内容中查找并替换所有 $t() 调用,直接替换为翻译文本 - const replacedContent = objectContent.replace( - /(\w+:\s*)\$t\((['"])((?:\\.|(?!\2).)*)\2\)/g, - (m, keyPrefix, quote, key) => { - const unescapedKey = unescapeString(key); - const translation = translations[unescapedKey]; - if (!translation || !translation.trim()) return m; - // 直接替换为翻译文本字符串,转义引号 - const escaped = escapeString(translation, quote); - return `${keyPrefix}${quote}${escaped}${quote}`; + // 匹配属性绑定中的对象字面量,使用平衡括号匹配以正确处理嵌套 + const pattern = /([:@]|v-bind:|v-on:)([a-zA-Z0-9_-]+)=(["'])\{/g; + const matches = []; + let match; + + while ((match = pattern.exec(code)) !== null) { + const startIndex = match.index; + const prefix = match[1]; + const attrName = match[2]; + const outerQuote = match[3]; + const openBraceIndex = startIndex + match[0].length - 1; // 开括号的位置 + const afterBraceIndex = openBraceIndex + 1; // 开括号之后的位置 + + // 找到匹配的闭合大括号 + let depth = 1; + let pos = afterBraceIndex; + let braceEnd = -1; + + while (pos < code.length && depth > 0) { + const char = code[pos]; + // 跳过字符串字面量中的大括号 + if (char === '"' || char === "'") { + pos++; + // 跳过转义的引号 + while (pos < code.length && (code[pos] !== char || code[pos - 1] === '\\')) { + pos++; } - ); - return `${prefix}${attrName}=${outerQuote}{${replacedContent}}${outerQuote}`; + pos++; + continue; + } + + if (char === '{') depth++; + else if (char === '}') { + depth--; + if (depth === 0) { + braceEnd = pos; + break; + } + } + pos++; } - ); + + if (braceEnd === -1) continue; + + // 检查是否以相同的引号结束 + if (braceEnd + 1 < code.length && code[braceEnd + 1] === outerQuote) { + const objectContent = code.substring(afterBraceIndex, braceEnd); + matches.push({ startIndex, prefix, attrName, outerQuote, afterBraceIndex, braceEnd, objectContent }); + } + } + + // 从后往前替换,避免索引错乱 + for (let i = matches.length - 1; i >= 0; i--) { + const m = matches[i]; + // 匹配对象属性值中的 $t(),支持键名(标识符或字符串) + const replacedContent = m.objectContent.replace( + /(['"]?\w+['"]?\s*:\s*)\$t\((['"])((?:\\.|(?!\2).)*)\2\)/g, + (match, keyPrefix, quote, key) => { + const unescapedKey = unescapeString(key); + const translation = translations[unescapedKey]; + if (!translation || !translation.trim()) return match; + const escaped = escapeString(translation, quote); + return `${keyPrefix}${quote}${escaped}${quote}`; + } + ); + const replacement = `${m.prefix}${m.attrName}=${m.outerQuote}{${replacedContent}}${m.outerQuote}`; + code = code.substring(0, m.startIndex) + replacement + code.substring(m.braceEnd + 2); + } + + return code; } /**