diff --git a/dashboard/src/utils/i18n.js b/dashboard/src/utils/i18n.js index a698b5c..ca6c16b 100644 --- a/dashboard/src/utils/i18n.js +++ b/dashboard/src/utils/i18n.js @@ -1,50 +1,12 @@ import { ref } from 'vue'; -import { jingrowRequest } from 'jingrow-ui'; // 从构建时配置获取语言,默认为英文 const buildLocale = import.meta.env.DASHBOARD_LOCALE || 'en'; // 当前语言(从构建配置获取,不可切换) const currentLocale = ref(buildLocale); +// 翻译数据:构建时 Vite 插件已直接替换静态文本,这里只处理动态翻译 const translations = ref({}); -const isLoading = ref(false); -const initPromise = ref(null); - -/** - * 从后端加载翻译 - * @param {string} locale - 语言代码,默认 'en' - */ -export async function loadTranslations(locale = 'en') { - // 如果正在加载或已加载相同语言,直接返回 - if (isLoading.value) { - return initPromise.value; - } - - if (currentLocale.value === locale && Object.keys(translations.value).length > 0) { - return Promise.resolve(); - } - - isLoading.value = true; - - const promise = jingrowRequest({ - url: '/api/action/jingrow.translate.get_app_translations', - method: 'GET' - }) - .then((response) => { - translations.value = response || {}; - currentLocale.value = locale; - }) - .catch((error) => { - console.error('Failed to load translations:', error); - translations.value = {}; - }) - .finally(() => { - isLoading.value = false; - }); - - initPromise.value = promise; - return promise; -} /** * 翻译函数 @@ -83,10 +45,9 @@ export function getLocale() { */ export async function initI18n() { const locale = buildLocale.split('-')[0] || 'en'; // 处理 'en-US' -> 'en' - await loadTranslations(locale); document.documentElement.lang = locale; } // 导出响应式状态(供 composable 使用) -export { currentLocale, isLoading }; +export { currentLocale }; diff --git a/dashboard/vite-plugin-translate.mjs b/dashboard/vite-plugin-translate.mjs new file mode 100644 index 0000000..064c3a7 --- /dev/null +++ b/dashboard/vite-plugin-translate.mjs @@ -0,0 +1,188 @@ +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import { dirname } from 'path'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +/** + * Vite 插件:构建时直接读取 CSV,替换代码中的 t('xxx') 为翻译文本 + * 无需生成任何中间文件,直接从 CSV 读取并替换 + * @param {object} options - 插件选项 + * @param {string} options.locale - 目标语言代码,从 vite.config.ts 传递 + */ +function vitePluginTranslate(options = {}) { + let translations = {}; + let locale = options.locale || 'en'; + + return { + name: 'vite-plugin-translate', + enforce: 'pre', // 在 Vue 插件之前执行,处理原始模板 + configResolved(config) { + // 直接读取 CSV 翻译文件(与后端共享同一数据源) + if (locale && locale !== 'en') { + const csvPath = path.resolve(__dirname, `../jcloud/translations/${locale}.csv`); + if (fs.existsSync(csvPath)) { + translations = parseCSV(csvPath); + console.log(`[translate] Loaded ${Object.keys(translations).length} translations from ${csvPath}`); + } else { + console.warn(`[translate] CSV file not found: ${csvPath}, translations will not be replaced`); + } + } + }, + transform(code, id) { + // 只处理 .vue, .js, .ts 文件 + if (!/\.(vue|js|ts|jsx|tsx)$/.test(id)) { + return null; + } + + // 如果是英文,不替换 + if (locale === 'en') { + return null; + } + + // 如果翻译为空,警告但不阻止(可能 CSV 文件不存在,使用原文) + if (Object.keys(translations).length === 0) { + console.warn(`[translate] No translations loaded for locale: ${locale}, skipping replacement`); + return null; + } + + let replaced = code; + const isVueFile = id.endsWith('.vue'); + + if (isVueFile) { + // 分离