修复用户信息下拉菜单无法翻译的问题

This commit is contained in:
jingrow 2025-12-30 03:55:02 +08:00
parent c6067d0a9f
commit 114f6211c7
3 changed files with 89 additions and 17 deletions

View File

@ -10,7 +10,7 @@
<JLogo class="h-8 w-8 rounded" />
<div class="ml-2 flex flex-col">
<div class="text-base font-medium leading-none text-gray-900">
今果 Jingrow
Jingrow
</div>
<div
v-if="$account.user"
@ -37,7 +37,7 @@
<span class="mr-1.5">
<FeatherIcon name="search" class="h-5 w-5 text-gray-700" />
</span>
<span class="text-sm">Search</span>
<span class="text-sm">{{ $t('Search') }}</span>
<span class="ml-auto text-sm text-gray-500">
<template v-if="$platform === 'mac'">K</template>
<template v-else>Ctrl+K</template>
@ -124,26 +124,29 @@ export default {
data() {
return {
showCommandPalette: false,
showTeamSwitcher: false,
dropdownItems: [
showTeamSwitcher: false
};
},
computed: {
dropdownItems() {
return [
{
label: 'Switch Team',
label: this.$t('Switch Team'),
icon: 'command',
onClick: () => (this.showTeamSwitcher = true)
},
{
label: 'Support & Docs',
label: this.$t('Support & Docs'),
icon: 'help-circle',
onClick: () => (window.location.href = '/support')
},
{
label: 'Logout',
label: this.$t('Logout'),
icon: 'log-out',
onClick: () => this.$auth.logout()
}
]
};
},
];
},
mounted() {
window.addEventListener('keydown', e => {
if (e.key === 'k' && (e.ctrlKey || e.metaKey)) {

View File

@ -13,6 +13,7 @@ const CONSTANTS = {
VUE_TEMPLATE_TAG_LEN: 9,
VUE_TEMPLATE_CLOSE_TAG_LEN: 11,
VUE_TEMPLATE_MODULE: '?vue&type=template',
VUE_SCRIPT_MODULE: '?vue&type=script',
VUE_STYLE_MODULE: '?vue&type=style',
FILE_EXTENSIONS: /\.(vue|js|ts|jsx|tsx)(\?.*)?$/,
NON_ASCII_REGEX: /[^\x00-\x7F]/,
@ -127,16 +128,29 @@ function processNestedTranslations(paramsCode, translations, pattern) {
* @param {string} options.defaultLocale - 默认语言代码
*/
function vitePluginTranslate(options = {}) {
let translations = {};
const locale = options.locale || 'en';
const defaultLocale = options.defaultLocale || 'en';
const translationsPath = options.translationsPath || '../jcloud/translations';
// 在插件初始化时立即加载翻译数据,而不是等到 configResolved
let translations = {};
if (locale && locale !== defaultLocale) {
const csvPath = path.resolve(__dirname, `${translationsPath}/${locale}.csv`);
if (fs.existsSync(csvPath)) {
try {
translations = parseCSV(csvPath);
} catch (error) {
console.warn(`[vite-plugin-translate] Failed to load translations from ${csvPath}:`, error.message);
}
}
}
return {
name: 'vite-plugin-translate',
enforce: 'pre',
configResolved(config) {
if (locale && locale !== defaultLocale) {
// 确保翻译数据已加载(双重检查)
if (locale && locale !== defaultLocale && Object.keys(translations).length === 0) {
const csvPath = path.resolve(__dirname, `${translationsPath}/${locale}.csv`);
if (fs.existsSync(csvPath)) {
try {
@ -158,12 +172,44 @@ function vitePluginTranslate(options = {}) {
if (isVueFile) {
const isTemplateModule = id.includes(CONSTANTS.VUE_TEMPLATE_MODULE);
const isScriptModule = id.includes(CONSTANTS.VUE_SCRIPT_MODULE);
if (isTemplateModule) {
// Vue 编译后的模板模块
code = replaceInterpolations(code, translations);
code = replaceCompiledFormat(code, translations);
code = replaceAttributeBindings(code, translations);
} else if (isScriptModule) {
// Vue 编译后的 script 模块 - 需要处理 this.$t()、$t() 以及编译后的函数调用(如 le()、t()
// 先处理 this.$t() 和 $t()
code = code.replace(
/(?:this\.)?\$t\((['"])((?:\\.|(?!\1).)*)\1\)/g,
(match, quote, key) => {
const unescapedKey = unescapeString(key);
const translation = translations[unescapedKey];
if (translation) {
return quote + escapeString(translation, quote) + quote;
}
return match;
}
);
// 再处理编译后的函数调用(如 le("key")、t("key")
// 匹配函数名后跟括号和字符串参数
code = code.replace(
/\b([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\((['"])((?:\\.|(?!\2).)*)\2\)/g,
(match, funcName, quote, key) => {
// 只处理可能是翻译函数的调用(通常是单字母或短名称,如 t, le, i 等)
// 但排除明显的非翻译函数(如 console.log, JSON.parse 等)
if (funcName.length <= 3 && !['log', 'warn', 'error', 'parse', 'stringify', 'keys', 'values', 'entries'].includes(funcName)) {
const unescapedKey = unescapeString(key);
const translation = translations[unescapedKey];
if (translation) {
return funcName + '(' + quote + escapeString(translation, quote) + quote + ')';
}
}
return match;
}
);
} else {
// 原始 Vue SFC 文件
code = processVueSFC(code, translations);
@ -242,17 +288,39 @@ function processVueSFC(code, translations) {
code = code.replace(
/(<script[^>]*>)([\s\S]*?)(<\/script>)/gi,
(match, openTag, scriptContent, closeTag) => {
const replacedContent = scriptContent.replace(
// 先处理 $t() 和 this.$t() 调用
let replacedContent = scriptContent.replace(
/(?:this\.)?\$t\((['"])((?:\\.|(?!\1).)*)\1\)/g,
(match, quote, key) => {
const unescapedKey = unescapeString(key);
const translation = translations[unescapedKey];
// 如果匹配包含 this.,保留它
const hasThis = match.startsWith('this.');
const prefix = hasThis ? 'this.' : '';
return translation ? `${prefix}$t(${quote}${escapeString(translation, quote)}${quote})` : match;
if (translation) {
return quote + escapeString(translation, quote) + quote;
}
return match;
}
);
// 再处理 t() 函数调用(从 i18n.js 导入的翻译函数)
// 检查是否有从 i18n 导入 t 函数
const hasTImport = /import\s*\{[^}]*\bt\b[^}]*\}\s*from\s*['"]\.\.?\/.*i18n['"]/i.test(scriptContent) ||
/import\s*\{[^}]*\bt\b[^}]*\}\s*from\s*['"]@\/.*i18n['"]/i.test(scriptContent);
if (hasTImport) {
// 匹配 t('key') 或 t("key"),但排除 t.something() 或 t['something']()
replacedContent = replacedContent.replace(
/\bt\((['"])((?:\\.|(?!\1).)*)\1\)/g,
(match, quote, key) => {
const unescapedKey = unescapeString(key);
const translation = translations[unescapedKey];
if (translation) {
return quote + escapeString(translation, quote) + quote;
}
return match;
}
);
}
return openTag + replacedContent + closeTag;
}
);

View File

@ -506,6 +506,7 @@ You are not logged in.,您尚未登录。,
to access dashboard.,以访问仪表板。,
Switch Team,切换团队,
Logout,退出登录,
Support & Docs,支持与文档,
Suspended,已暂停,
Broken,损坏,
China,中国大陆,

Can't render this file because it has a wrong number of fields in line 401.