From 1e125254ebcf4ddd713b84286935c38d23c5c8d9 Mon Sep 17 00:00:00 2001 From: owenchen Date: Thu, 27 Aug 2026 16:23:18 +0800 Subject: [PATCH] =?UTF-8?q?locale=E4=B8=AD=E8=8B=B1=E6=96=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WebVue/src/locales/index.ts | 37 +++++++++++++++++++++- WebVue/src/main.ts | 4 +++ WebVue/src/views/login/index.vue | 54 +++++++++++++++++++++++++++++--- 3 files changed, 89 insertions(+), 6 deletions(-) diff --git a/WebVue/src/locales/index.ts b/WebVue/src/locales/index.ts index c10b77e..6a6ff1c 100644 --- a/WebVue/src/locales/index.ts +++ b/WebVue/src/locales/index.ts @@ -1,6 +1,8 @@ import { createI18n } from 'vue-i18n' import type { I18nOptions } from 'vue-i18n'; import type { App } from 'vue'; +import { set } from 'lodash-es'; +import type { LocaleEntity } from '@/data/auto/localeEntity'; //这儿使用await,需要如下两步处理 //npm install vite-plugin-top-level-await -D @@ -36,5 +38,38 @@ export async function setupI18n(app: App) { app.use(i18n); } +/** + * 将后端 LocaleService.FindAll() 返回的 locale 表内容合并到对应语言的翻译中, + * 作为本地 JSON 翻译文件(lang/en、lang/zh-cn)的补充。 + * 数据映射规则: + * Lang -> 语言 key(如 zh-cn / en) + * GroupName -> 顶层分组(对应 json 文件名的 key,如 layout / login / request) + * Name -> 点分隔的翻译 key(如 header.logout) + * Value -> 翻译文本 + * 需在应用启动后调用;内部使用动态 import,避免与 utils/request 形成循环依赖。 + */ +export async function mergeDbLocaleMessages() { + try { + const { default: localeService } = await import('@/service/security/localeService'); + const list = await localeService.FindAll(); + if (!list || list.length === 0) return; -export default i18n; \ No newline at end of file + // 按语言分组,并将 GroupName.Name 铺平成嵌套对象 + const grouped: Record> = {}; + list.forEach((item: LocaleEntity) => { + if (!item.Lang || !item.Name || !item.Value) return; + const key = item.GroupName ? `${item.GroupName}.${item.Name}` : item.Name; + if (!grouped[item.Lang]) grouped[item.Lang] = {}; + set(grouped[item.Lang], key, item.Value); + }); + + // 合并进对应语言的翻译,覆盖同名 key + Object.entries(grouped).forEach(([lang, messages]) => { + i18n.global.mergeLocaleMessage(lang, messages); + }); + } catch (e) { + console.warn('加载数据库翻译失败,使用本地翻译文件', e); + } +} + +export default i18n; diff --git a/WebVue/src/main.ts b/WebVue/src/main.ts index 4c0f3ed..379c6bf 100644 --- a/WebVue/src/main.ts +++ b/WebVue/src/main.ts @@ -5,6 +5,7 @@ import App from "./App.vue"; import { setupStore } from "./store"; //使用了Top await import { setupRouter } from "./router"; +import { mergeDbLocaleMessages } from "./locales"; import { setupAntd, @@ -32,8 +33,11 @@ function setupPlugins() { async function setupApp() { // 挂载pinia状态管理 setupStore(app); + // 并行加载数据库 locale 表内容作为中英文翻译补充 + const dbLocale = mergeDbLocaleMessages(); // 挂载路由 await setupRouter(app); + await dbLocale; app.mount("#app"); } diff --git a/WebVue/src/views/login/index.vue b/WebVue/src/views/login/index.vue index 9497a19..06b2a3e 100644 --- a/WebVue/src/views/login/index.vue +++ b/WebVue/src/views/login/index.vue @@ -19,6 +19,25 @@ 登录 +
+ + {{ t('CulturePolicy.zh-cn') }} + + + + {{ t('CulturePolicy.en') }} + +
@@ -47,6 +66,8 @@ style="width: 80px; margin-top: 16px; margin-left: 24px"> 首页 + + @@ -73,6 +94,7 @@ const { t } = i18n.global; //const props = defineProps(['id']) const router = useRouter(); +const userLoginProfile = useLoginProfileStore(); const state = reactive({ loading: false, @@ -82,6 +104,16 @@ const state = reactive({ }, }); +// 当前语言(默认取本地存储,其次中文) +const currentLocale = ref(userLoginProfile.getLocale() ?? "zh-cn"); + +// 切换中英文并持久化 +const changeLocale = (locale: string) => { + currentLocale.value = locale; + userLoginProfile.setLocale(locale); + (i18n.global.locale as any).value = locale; +}; + const onRegister = () => { router.push({ path: "/register" }); }; @@ -101,7 +133,7 @@ const handleSubmit = async () => { UserMobile: state.LoginData.UserMobile, UserPassword: "", Guid: uuidv4(), - Culture: "zh-cn", + Culture: currentLocale.value, }; let key = await authService.GetRsaPublicKey(); @@ -114,8 +146,6 @@ const handleSubmit = async () => { let profile: LoginProfile = await authService.Login(data); - const userLoginProfile = useLoginProfileStore(); - userLoginProfile.updateLoginProfile(profile); router.push({ path: "/dashboard" }); @@ -248,8 +278,6 @@ const onBiometricLogin = async () => { // 4. Persist the JWT locally and navigate. installProfile is the // same call LoginView makes after a successful password login. - const userLoginProfile = useLoginProfileStore(); - userLoginProfile.updateLoginProfile(profile); router.push({ path: "/dashboard" }); @@ -354,5 +382,21 @@ const onBiometricLogin = async () => { padding-left: 6px; } } + + .culture-switch { + display: flex; + align-items: center; + justify-content: center; + margin-top: 16px; + + .ant-btn-text { + color: #08c; + } + + .culture-active { + font-weight: 600; + color: #1677ff; + } + } }