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