From 82fbf452a8cdd45e6211d1b8b246a3f79c060256 Mon Sep 17 00:00:00 2001 From: wucm667 Date: Thu, 26 Feb 2026 14:04:13 +0800 Subject: [PATCH] =?UTF-8?q?feat(i18n):=20=E5=88=87=E6=8D=A2=E8=AF=AD?= =?UTF-8?q?=E8=A8=80=E6=97=B6=E5=90=8C=E6=AD=A5=E6=9B=B4=E6=96=B0=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E6=A0=87=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - resolveDocumentTitle() 新增 titleKey 参数,优先通过 i18n 翻译 - router beforeEach 中将路由 meta.titleKey 传入标题解析函数 - setLocale() 切换语言后同步刷新 document.title --- frontend/src/i18n/index.ts | 8 ++++++++ frontend/src/router/index.ts | 11 +++++++---- frontend/src/router/title.ts | 12 +++++++++++- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/frontend/src/i18n/index.ts b/frontend/src/i18n/index.ts index 00e34dc2..5dab65e8 100644 --- a/frontend/src/i18n/index.ts +++ b/frontend/src/i18n/index.ts @@ -68,6 +68,14 @@ export async function setLocale(locale: string): Promise { i18n.global.locale.value = locale localStorage.setItem(LOCALE_KEY, locale) document.documentElement.setAttribute('lang', locale) + + // 同步更新浏览器页签标题,使其跟随语言切换 + const { resolveDocumentTitle } = await import('@/router/title') + const { default: router } = await import('@/router') + const { useAppStore } = await import('@/stores/app') + const route = router.currentRoute.value + const appStore = useAppStore() + document.title = resolveDocumentTitle(route.meta.title, appStore.siteName, route.meta.titleKey as string) } export function getLocale(): LocaleCode { diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 1a67cac6..4b50a163 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -41,7 +41,8 @@ const routes: RouteRecordRaw[] = [ component: () => import('@/views/auth/LoginView.vue'), meta: { requiresAuth: false, - title: 'Login' + title: 'Login', + titleKey: 'common.login' } }, { @@ -50,7 +51,8 @@ const routes: RouteRecordRaw[] = [ component: () => import('@/views/auth/RegisterView.vue'), meta: { requiresAuth: false, - title: 'Register' + title: 'Register', + titleKey: 'auth.createAccount' } }, { @@ -86,7 +88,8 @@ const routes: RouteRecordRaw[] = [ component: () => import('@/views/auth/ForgotPasswordView.vue'), meta: { requiresAuth: false, - title: 'Forgot Password' + title: 'Forgot Password', + titleKey: 'auth.forgotPasswordTitle' } }, { @@ -390,7 +393,7 @@ router.beforeEach((to, _from, next) => { // Set page title const appStore = useAppStore() - document.title = resolveDocumentTitle(to.meta.title, appStore.siteName) + document.title = resolveDocumentTitle(to.meta.title, appStore.siteName, to.meta.titleKey as string) // Check if route requires authentication const requiresAuth = to.meta.requiresAuth !== false // Default to true diff --git a/frontend/src/router/title.ts b/frontend/src/router/title.ts index e0db24b0..89ec9276 100644 --- a/frontend/src/router/title.ts +++ b/frontend/src/router/title.ts @@ -1,9 +1,19 @@ +import { i18n } from '@/i18n' + /** * 统一生成页面标题,避免多处写入 document.title 产生覆盖冲突。 + * 优先使用 titleKey 通过 i18n 翻译,fallback 到静态 routeTitle。 */ -export function resolveDocumentTitle(routeTitle: unknown, siteName?: string): string { +export function resolveDocumentTitle(routeTitle: unknown, siteName?: string, titleKey?: string): string { const normalizedSiteName = typeof siteName === 'string' && siteName.trim() ? siteName.trim() : 'Sub2API' + if (typeof titleKey === 'string' && titleKey.trim()) { + const translated = i18n.global.t(titleKey) + if (translated && translated !== titleKey) { + return `${translated} - ${normalizedSiteName}` + } + } + if (typeof routeTitle === 'string' && routeTitle.trim()) { return `${routeTitle.trim()} - ${normalizedSiteName}` }