优化vite翻译插件

This commit is contained in:
jingrow 2025-12-29 21:19:47 +08:00
parent 99de35ce6d
commit 4ad23c57ab
3 changed files with 57 additions and 21 deletions

View File

@ -304,7 +304,7 @@
>
{{
$route.name == 'Login'
? $t('Don\'t have an account? Create one.')
? $t("Don't have an account? Create one.")
: $t('Already have an account? Log in.')
}}
</router-link>

View File

@ -68,11 +68,11 @@ function processNestedTranslations(paramsCode, translations, pattern) {
// 格式1带前缀的 (ctxPrefix, quote, key)
ctxPrefix = captureGroups[0] || '';
quote = captureGroups[1];
key = captureGroups[2];
key = unescapeString(captureGroups[2]);
} else if (captureGroups.length === 2) {
// 格式2或3不带前缀的 (quote, key)
quote = captureGroups[0];
key = captureGroups[1];
key = unescapeString(captureGroups[1]);
} else {
// 无法解析,返回原匹配
return match;
@ -137,9 +137,10 @@ function vitePluginTranslate(options = {}) {
} else {
// 非 Vue 文件
code = code.replace(
/\bt\((['"])([^'"]+)\1\)/g,
/\bt\((['"])((?:\\.|(?!\1).)*)\1\)/g,
(match, quote, key) => {
const translation = translations[key];
const unescapedKey = unescapeString(key);
const translation = translations[unescapedKey];
return translation ? quote + escapeString(translation, quote) + quote : match;
}
);
@ -202,9 +203,10 @@ function processVueSFC(code, translations) {
/(<script[^>]*>)([\s\S]*?)(<\/script>)/gi,
(match, openTag, scriptContent, closeTag) => {
const replacedContent = scriptContent.replace(
/\$t\((['"])([^'"]+)\1\)/g,
/\$t\((['"])((?:\\.|(?!\1).)*)\1\)/g,
(match, quote, key) => {
const translation = translations[key];
const unescapedKey = unescapeString(key);
const translation = translations[unescapedKey];
return translation ? `$t(${quote}${escapeString(translation, quote)}${quote})` : match;
}
);
@ -221,19 +223,43 @@ function processVueSFC(code, translations) {
function replaceInterpolations(code, translations) {
// 先处理不带参数的调用:{{ $t('xxx') }}
code = code.replace(
/\{\{\s*[\$]t\s*\(\s*(['"])([^'"]+)\1\s*\)\s*\}\}/g,
(match, quote, key) => translations[key] || match
/\{\{\s*[\$]t\s*\(\s*(['"])((?:\\.|(?!\1).)*)\1\s*\)\s*\}\}/g,
(match, quote, key) => {
const unescapedKey = unescapeString(key);
return translations[unescapedKey] || match;
}
);
// 处理插值表达式内部的 $t() 调用(不在单独的 {{ $t(...) }} 中)
// 例如:{{ condition ? $t('key1') : $t('key2') }}
code = code.replace(
/\{\{[\s\S]*?\}\}/g,
(interpolation) => {
// 如果这个插值已经是一个完整的 {{ $t(...) }},跳过
if (/^\{\{\s*[\$]t\s*\(/.test(interpolation.trim())) {
return interpolation;
}
// 否则,处理其中的 $t() 调用
return interpolation.replace(
/\$t\((['"])((?:\\.|(?!\1).)*)\1\)/g,
(match, quote, key) => {
const unescapedKey = unescapeString(key);
const translation = translations[unescapedKey];
return translation ? `$t(${quote}${escapeString(translation, quote)}${quote})` : match;
}
);
}
);
// 处理带参数的调用:{{ $t('key', { params }) }}
const matches = [];
const pattern = /\{\{\s*[\$]t\s*\(\s*(['"])([^'"]+)\1\s*,\s*/g;
const pattern = /\{\{\s*[\$]t\s*\(\s*(['"])((?:\\.|(?!\1).)*)\1\s*,\s*/g;
let match;
while ((match = pattern.exec(code)) !== null) {
const startIndex = match.index;
const quote = match[1];
const key = match[2];
const key = unescapeString(match[2]);
const afterKeyIndex = match.index + match[0].length;
// 找到参数部分的结束位置
@ -261,13 +287,13 @@ function replaceInterpolations(code, translations) {
paramsPart = processNestedTranslations(
paramsPart,
translations,
/\{\{\s*[\$]t\s*\(\s*(['"])([^'"]+)\1\s*\)\s*\}\}/g
/\{\{\s*[\$]t\s*\(\s*(['"])((?:\\.|(?!\1).)*)\1\s*\)\s*\}\}/g
);
// 再处理 $t(...) 格式(参数对象中的直接调用)
paramsPart = processNestedTranslations(
paramsPart,
translations,
/\$t\((['"])([^'"]+)\1\)/g
/\$t\((['"])((?:\\.|(?!\1).)*)\1\)/g
);
const translation = translations[m.key];
@ -286,14 +312,14 @@ function replaceInterpolations(code, translations) {
function replaceCompiledFormat(code, translations) {
// 处理带参数的调用
const matches = [];
const pattern = /([a-zA-Z_$][a-zA-Z0-9_$]*\.)?[\$]t\((['"])([^'"]+)\2\s*,\s*/g;
const pattern = /([a-zA-Z_$][a-zA-Z0-9_$]*\.)?[\$]t\((['"])((?:\\.|(?!\2).)*)\2\s*,\s*/g;
let match;
while ((match = pattern.exec(code)) !== null) {
const startIndex = match.index;
const ctxPrefix = match[1] || '';
const quote = match[2];
const key = match[3];
const key = unescapeString(match[3]);
const afterKeyIndex = match.index + match[0].length;
const paramsEnd = findBalancedBracket(code, afterKeyIndex);
@ -311,7 +337,7 @@ function replaceCompiledFormat(code, translations) {
paramsPart = processNestedTranslations(
paramsPart,
translations,
/([a-zA-Z_$][a-zA-Z0-9_$]*\.)?[\$]t\((['"])([^'"]+)\2\)/g
/([a-zA-Z_$][a-zA-Z0-9_$]*\.)?[\$]t\((['"])((?:\\.|(?!\2).)*)\2\)/g
);
const translation = translations[m.key];
@ -323,9 +349,10 @@ function replaceCompiledFormat(code, translations) {
// 处理不带参数的调用
code = code.replace(
/([a-zA-Z_$][a-zA-Z0-9_$]*\.)?[\$]t\((['"])([^'"]+)\2\)/g,
/([a-zA-Z_$][a-zA-Z0-9_$]*\.)?[\$]t\((['"])((?:\\.|(?!\2).)*)\2\)/g,
(match, ctxPrefix, quote, key) => {
const translation = translations[key];
const unescapedKey = unescapeString(key);
const translation = translations[unescapedKey];
return translation ? `${ctxPrefix || ''}$t(${quote}${escapeString(translation, quote)}${quote})` : match;
}
);
@ -338,9 +365,10 @@ function replaceCompiledFormat(code, translations) {
*/
function replaceAttributeBindings(code, translations) {
return code.replace(
/([:@]|v-bind:|v-on:)([a-zA-Z0-9_-]+)=(["'])[\$]?t\((['"])([^'"]+)\4\)\3/g,
/([:@]|v-bind:|v-on:)([a-zA-Z0-9_-]+)=(["'])[\$]?t\((['"])((?:\\.|(?!\4).)*)\4\)\3/g,
(match, prefix, attrName, outerQuote, innerQuote, key) => {
const translation = translations[key];
const unescapedKey = unescapeString(key);
const translation = translations[unescapedKey];
if (!translation || !translation.trim()) return match;
let escaped = escapeString(translation, outerQuote);
@ -355,6 +383,14 @@ function replaceAttributeBindings(code, translations) {
);
}
/**
* 去除字符串中的转义字符 \' 转换为 '\" 转换为 "
*/
function unescapeString(text) {
return text
.replace(/\\(.)/g, '$1');
}
/**
* 转义字符串
*/

View File

@ -11,7 +11,7 @@ import { sentryVitePlugin } from '@sentry/vite-plugin';
import vitePluginTranslate from './vite-plugin-translate.mjs';
// 语言配置:设置目标语言,默认为 'en'(英文),可设置为 'zh'(中文)等
const locale = process.env.DASHBOARD_LOCALE || 'en';
const locale = process.env.DASHBOARD_LOCALE || 'zh';
export default defineConfig({
plugins: [