修复用户信息下拉菜单无法翻译的问题
This commit is contained in:
parent
c6067d0a9f
commit
114f6211c7
@ -10,7 +10,7 @@
|
|||||||
<JLogo class="h-8 w-8 rounded" />
|
<JLogo class="h-8 w-8 rounded" />
|
||||||
<div class="ml-2 flex flex-col">
|
<div class="ml-2 flex flex-col">
|
||||||
<div class="text-base font-medium leading-none text-gray-900">
|
<div class="text-base font-medium leading-none text-gray-900">
|
||||||
今果 Jingrow
|
Jingrow
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="$account.user"
|
v-if="$account.user"
|
||||||
@ -37,7 +37,7 @@
|
|||||||
<span class="mr-1.5">
|
<span class="mr-1.5">
|
||||||
<FeatherIcon name="search" class="h-5 w-5 text-gray-700" />
|
<FeatherIcon name="search" class="h-5 w-5 text-gray-700" />
|
||||||
</span>
|
</span>
|
||||||
<span class="text-sm">Search</span>
|
<span class="text-sm">{{ $t('Search') }}</span>
|
||||||
<span class="ml-auto text-sm text-gray-500">
|
<span class="ml-auto text-sm text-gray-500">
|
||||||
<template v-if="$platform === 'mac'">⌘K</template>
|
<template v-if="$platform === 'mac'">⌘K</template>
|
||||||
<template v-else>Ctrl+K</template>
|
<template v-else>Ctrl+K</template>
|
||||||
@ -124,26 +124,29 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
showCommandPalette: false,
|
showCommandPalette: false,
|
||||||
showTeamSwitcher: false,
|
showTeamSwitcher: false
|
||||||
dropdownItems: [
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
dropdownItems() {
|
||||||
|
return [
|
||||||
{
|
{
|
||||||
label: 'Switch Team',
|
label: this.$t('Switch Team'),
|
||||||
icon: 'command',
|
icon: 'command',
|
||||||
onClick: () => (this.showTeamSwitcher = true)
|
onClick: () => (this.showTeamSwitcher = true)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Support & Docs',
|
label: this.$t('Support & Docs'),
|
||||||
icon: 'help-circle',
|
icon: 'help-circle',
|
||||||
onClick: () => (window.location.href = '/support')
|
onClick: () => (window.location.href = '/support')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Logout',
|
label: this.$t('Logout'),
|
||||||
icon: 'log-out',
|
icon: 'log-out',
|
||||||
onClick: () => this.$auth.logout()
|
onClick: () => this.$auth.logout()
|
||||||
}
|
}
|
||||||
]
|
];
|
||||||
};
|
},
|
||||||
},
|
|
||||||
mounted() {
|
mounted() {
|
||||||
window.addEventListener('keydown', e => {
|
window.addEventListener('keydown', e => {
|
||||||
if (e.key === 'k' && (e.ctrlKey || e.metaKey)) {
|
if (e.key === 'k' && (e.ctrlKey || e.metaKey)) {
|
||||||
|
|||||||
@ -13,6 +13,7 @@ const CONSTANTS = {
|
|||||||
VUE_TEMPLATE_TAG_LEN: 9,
|
VUE_TEMPLATE_TAG_LEN: 9,
|
||||||
VUE_TEMPLATE_CLOSE_TAG_LEN: 11,
|
VUE_TEMPLATE_CLOSE_TAG_LEN: 11,
|
||||||
VUE_TEMPLATE_MODULE: '?vue&type=template',
|
VUE_TEMPLATE_MODULE: '?vue&type=template',
|
||||||
|
VUE_SCRIPT_MODULE: '?vue&type=script',
|
||||||
VUE_STYLE_MODULE: '?vue&type=style',
|
VUE_STYLE_MODULE: '?vue&type=style',
|
||||||
FILE_EXTENSIONS: /\.(vue|js|ts|jsx|tsx)(\?.*)?$/,
|
FILE_EXTENSIONS: /\.(vue|js|ts|jsx|tsx)(\?.*)?$/,
|
||||||
NON_ASCII_REGEX: /[^\x00-\x7F]/,
|
NON_ASCII_REGEX: /[^\x00-\x7F]/,
|
||||||
@ -127,16 +128,29 @@ function processNestedTranslations(paramsCode, translations, pattern) {
|
|||||||
* @param {string} options.defaultLocale - 默认语言代码
|
* @param {string} options.defaultLocale - 默认语言代码
|
||||||
*/
|
*/
|
||||||
function vitePluginTranslate(options = {}) {
|
function vitePluginTranslate(options = {}) {
|
||||||
let translations = {};
|
|
||||||
const locale = options.locale || 'en';
|
const locale = options.locale || 'en';
|
||||||
const defaultLocale = options.defaultLocale || 'en';
|
const defaultLocale = options.defaultLocale || 'en';
|
||||||
const translationsPath = options.translationsPath || '../jcloud/translations';
|
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 {
|
return {
|
||||||
name: 'vite-plugin-translate',
|
name: 'vite-plugin-translate',
|
||||||
enforce: 'pre',
|
enforce: 'pre',
|
||||||
configResolved(config) {
|
configResolved(config) {
|
||||||
if (locale && locale !== defaultLocale) {
|
// 确保翻译数据已加载(双重检查)
|
||||||
|
if (locale && locale !== defaultLocale && Object.keys(translations).length === 0) {
|
||||||
const csvPath = path.resolve(__dirname, `${translationsPath}/${locale}.csv`);
|
const csvPath = path.resolve(__dirname, `${translationsPath}/${locale}.csv`);
|
||||||
if (fs.existsSync(csvPath)) {
|
if (fs.existsSync(csvPath)) {
|
||||||
try {
|
try {
|
||||||
@ -158,12 +172,44 @@ function vitePluginTranslate(options = {}) {
|
|||||||
|
|
||||||
if (isVueFile) {
|
if (isVueFile) {
|
||||||
const isTemplateModule = id.includes(CONSTANTS.VUE_TEMPLATE_MODULE);
|
const isTemplateModule = id.includes(CONSTANTS.VUE_TEMPLATE_MODULE);
|
||||||
|
const isScriptModule = id.includes(CONSTANTS.VUE_SCRIPT_MODULE);
|
||||||
|
|
||||||
if (isTemplateModule) {
|
if (isTemplateModule) {
|
||||||
// Vue 编译后的模板模块
|
// Vue 编译后的模板模块
|
||||||
code = replaceInterpolations(code, translations);
|
code = replaceInterpolations(code, translations);
|
||||||
code = replaceCompiledFormat(code, translations);
|
code = replaceCompiledFormat(code, translations);
|
||||||
code = replaceAttributeBindings(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 {
|
} else {
|
||||||
// 原始 Vue SFC 文件
|
// 原始 Vue SFC 文件
|
||||||
code = processVueSFC(code, translations);
|
code = processVueSFC(code, translations);
|
||||||
@ -242,17 +288,39 @@ function processVueSFC(code, translations) {
|
|||||||
code = code.replace(
|
code = code.replace(
|
||||||
/(<script[^>]*>)([\s\S]*?)(<\/script>)/gi,
|
/(<script[^>]*>)([\s\S]*?)(<\/script>)/gi,
|
||||||
(match, openTag, scriptContent, closeTag) => {
|
(match, openTag, scriptContent, closeTag) => {
|
||||||
const replacedContent = scriptContent.replace(
|
// 先处理 $t() 和 this.$t() 调用
|
||||||
|
let replacedContent = scriptContent.replace(
|
||||||
/(?:this\.)?\$t\((['"])((?:\\.|(?!\1).)*)\1\)/g,
|
/(?:this\.)?\$t\((['"])((?:\\.|(?!\1).)*)\1\)/g,
|
||||||
(match, quote, key) => {
|
(match, quote, key) => {
|
||||||
const unescapedKey = unescapeString(key);
|
const unescapedKey = unescapeString(key);
|
||||||
const translation = translations[unescapedKey];
|
const translation = translations[unescapedKey];
|
||||||
// 如果匹配包含 this.,保留它
|
if (translation) {
|
||||||
const hasThis = match.startsWith('this.');
|
return quote + escapeString(translation, quote) + quote;
|
||||||
const prefix = hasThis ? 'this.' : '';
|
}
|
||||||
return translation ? `${prefix}$t(${quote}${escapeString(translation, quote)}${quote})` : match;
|
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;
|
return openTag + replacedContent + closeTag;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@ -506,6 +506,7 @@ You are not logged in.,您尚未登录。,
|
|||||||
to access dashboard.,以访问仪表板。,
|
to access dashboard.,以访问仪表板。,
|
||||||
Switch Team,切换团队,
|
Switch Team,切换团队,
|
||||||
Logout,退出登录,
|
Logout,退出登录,
|
||||||
|
Support & Docs,支持与文档,
|
||||||
Suspended,已暂停,
|
Suspended,已暂停,
|
||||||
Broken,损坏,
|
Broken,损坏,
|
||||||
China,中国大陆,
|
China,中国大陆,
|
||||||
|
|||||||
|
Can't render this file because it has a wrong number of fields in line 401.
|
Loading…
x
Reference in New Issue
Block a user