dev #3

Merged
jingrow merged 96 commits from dev into main 2026-01-13 22:47:33 +08:00
Showing only changes of commit bbacb2deb6 - Show all commits

View File

@ -26,17 +26,51 @@ const CONSTANTS = {
* @param {string} closeChar - 闭括号字符
* @returns {number} 闭括号的位置未找到返回 -1
*/
/**
* 查找平衡括号的结束位置正确处理字符串字面量中的括号
* @param {string} code - 代码字符串
* @param {number} startPos - 起始位置开括号之后
* @param {string} openChar - 开括号字符
* @param {string} closeChar - 闭括号字符
* @returns {number} 闭括号的位置未找到返回 -1
*/
function findBalancedBracket(code, startPos, openChar = '(', closeChar = ')') {
let depth = 1;
let pos = startPos;
let inString = false;
let stringChar = null;
while (pos < code.length && depth > 0) {
const char = code[pos];
if (char === openChar) depth++;
else if (char === closeChar) {
depth--;
if (depth === 0) return pos;
// 处理字符串字面量
if (!inString && (char === '"' || char === "'")) {
inString = true;
stringChar = char;
} else if (inString && char === stringChar) {
// 检查是否是转义的引号:计算连续的反斜杠数量
let backslashCount = 0;
let checkPos = pos - 1;
while (checkPos >= 0 && code[checkPos] === '\\') {
backslashCount++;
checkPos--;
}
// 如果反斜杠数量是偶数,则引号未转义,字符串结束
if (backslashCount % 2 === 0) {
inString = false;
stringChar = null;
}
}
// 只在非字符串中计算括号深度
if (!inString) {
if (char === openChar) depth++;
else if (char === closeChar) {
depth--;
if (depth === 0) return pos;
}
}
pos++;
}
@ -189,12 +223,14 @@ function processVueSFC(code, translations) {
if (depth === 0 && templateContent) {
let replacedContent = templateContent;
replacedContent = replaceInterpolations(replacedContent, translations);
// 先处理属性绑定(包括带参数的),避免 replaceInterpolations 误处理
replacedContent = replaceAttributeBindings(replacedContent, translations);
// 处理属性绑定中的数组字面量,如 :items="[{ label: $t('key'), ... }]"
replacedContent = replaceAttributeArrayLiterals(replacedContent, translations);
// 处理属性绑定中的对象字面量,如 :options="{ title: $t('key'), ... }"
replacedContent = replaceAttributeObjectLiterals(replacedContent, translations);
// 最后处理插值表达式
replacedContent = replaceInterpolations(replacedContent, translations);
code = code.substring(0, startIndex) + openTag + replacedContent + CONSTANTS.VUE_TEMPLATE_CLOSE_TAG +
code.substring(startIndex + openTag.length + templateContent.length + CONSTANTS.VUE_TEMPLATE_CLOSE_TAG_LEN);
@ -239,9 +275,18 @@ function replaceInterpolations(code, translations) {
// 处理插值表达式内部的 $t() 调用(不在单独的 {{ $t(...) }} 中)
// 例如:{{ condition ? $t('key1') : $t('key2') }}
// 注意:跳过属性绑定中的内容(它们会在 replaceAttributeBindings 中处理)
code = code.replace(
/\{\{[\s\S]*?\}\}/g,
(interpolation) => {
(interpolation, offset, string) => {
// 检查这个插值是否在属性绑定中(前后有 = 和引号)
const beforeMatch = string.substring(Math.max(0, offset - 20), offset);
const afterMatch = string.substring(offset + interpolation.length, Math.min(string.length, offset + interpolation.length + 20));
if (/[=:]["']\s*$/.test(beforeMatch) || /^["']\s*[>\/\s]/.test(afterMatch)) {
// 在属性绑定中,跳过处理
return interpolation;
}
// 如果这个插值已经是一个完整的 {{ $t(...) }},跳过
if (/^\{\{\s*[\$]t\s*\(/.test(interpolation.trim())) {
return interpolation;
@ -422,6 +467,7 @@ function replaceAttributeBindings(code, translations) {
}
// 再处理不带参数的调用::label="$t('key')"
// 统一处理逻辑:保留 $t() 调用结构,只替换翻译键(与带参数的处理保持一致)
return code.replace(
/([:@]|v-bind:|v-on:)([a-zA-Z0-9_-]+)=(["'])[\$]?t\((['"])((?:\\.|(?!\4).)*)\4\)\3/g,
(match, prefix, attrName, outerQuote, innerQuote, key) => {
@ -429,14 +475,10 @@ function replaceAttributeBindings(code, translations) {
const translation = translations[unescapedKey];
if (!translation || !translation.trim()) return match;
let escaped = escapeString(translation, outerQuote);
if (outerQuote === '"') {
escaped = escaped.replace(/'/g, "\\'");
return `${prefix}${attrName}="'${escaped}'"`;
} else {
escaped = escaped.replace(/"/g, '\\"');
return `${prefix}${attrName}='"${escaped}"'`;
}
// 转义翻译文本以适配内层引号(与带参数的处理保持一致)
const escaped = escapeString(translation, innerQuote);
// 保留 $t() 调用结构,只替换翻译键
return `${prefix}${attrName}=${outerQuote}$t(${innerQuote}${escaped}${innerQuote})${outerQuote}`;
}
);
}