2025-12-18 13:50:39 +08:00
|
|
|
|
import { createI18n } from 'vue-i18n'
|
2026-02-14 11:56:08 +08:00
|
|
|
|
|
|
|
|
|
|
type LocaleCode = 'en' | 'zh'
|
|
|
|
|
|
|
|
|
|
|
|
type LocaleMessages = Record<string, any>
|
2025-12-18 13:50:39 +08:00
|
|
|
|
|
|
|
|
|
|
const LOCALE_KEY = 'sub2api_locale'
|
2026-02-14 11:56:08 +08:00
|
|
|
|
const DEFAULT_LOCALE: LocaleCode = 'en'
|
|
|
|
|
|
|
|
|
|
|
|
const localeLoaders: Record<LocaleCode, () => Promise<{ default: LocaleMessages }>> = {
|
|
|
|
|
|
en: () => import('./locales/en'),
|
|
|
|
|
|
zh: () => import('./locales/zh')
|
|
|
|
|
|
}
|
2025-12-18 13:50:39 +08:00
|
|
|
|
|
2026-02-14 11:56:08 +08:00
|
|
|
|
function isLocaleCode(value: string): value is LocaleCode {
|
|
|
|
|
|
return value === 'en' || value === 'zh'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function getDefaultLocale(): LocaleCode {
|
2025-12-18 13:50:39 +08:00
|
|
|
|
const saved = localStorage.getItem(LOCALE_KEY)
|
2026-02-14 11:56:08 +08:00
|
|
|
|
if (saved && isLocaleCode(saved)) {
|
2025-12-18 13:50:39 +08:00
|
|
|
|
return saved
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const browserLang = navigator.language.toLowerCase()
|
|
|
|
|
|
if (browserLang.startsWith('zh')) {
|
|
|
|
|
|
return 'zh'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-14 11:56:08 +08:00
|
|
|
|
return DEFAULT_LOCALE
|
2025-12-18 13:50:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const i18n = createI18n({
|
|
|
|
|
|
legacy: false,
|
|
|
|
|
|
locale: getDefaultLocale(),
|
2026-02-14 11:56:08 +08:00
|
|
|
|
fallbackLocale: DEFAULT_LOCALE,
|
|
|
|
|
|
messages: {},
|
2025-12-29 15:21:05 +08:00
|
|
|
|
// 禁用 HTML 消息警告 - 引导步骤使用富文本内容(driver.js 支持 HTML)
|
|
|
|
|
|
// 这些内容是内部定义的,不存在 XSS 风险
|
|
|
|
|
|
warnHtmlMessage: false
|
2025-12-18 13:50:39 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
2026-02-14 11:56:08 +08:00
|
|
|
|
const loadedLocales = new Set<LocaleCode>()
|
|
|
|
|
|
|
|
|
|
|
|
export async function loadLocaleMessages(locale: LocaleCode): Promise<void> {
|
|
|
|
|
|
if (loadedLocales.has(locale)) {
|
|
|
|
|
|
return
|
2025-12-18 13:50:39 +08:00
|
|
|
|
}
|
2026-02-14 11:56:08 +08:00
|
|
|
|
|
|
|
|
|
|
const loader = localeLoaders[locale]
|
|
|
|
|
|
const module = await loader()
|
|
|
|
|
|
i18n.global.setLocaleMessage(locale, module.default)
|
|
|
|
|
|
loadedLocales.add(locale)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export async function initI18n(): Promise<void> {
|
|
|
|
|
|
const current = getLocale()
|
|
|
|
|
|
await loadLocaleMessages(current)
|
|
|
|
|
|
document.documentElement.setAttribute('lang', current)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export async function setLocale(locale: string): Promise<void> {
|
|
|
|
|
|
if (!isLocaleCode(locale)) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
await loadLocaleMessages(locale)
|
|
|
|
|
|
i18n.global.locale.value = locale
|
|
|
|
|
|
localStorage.setItem(LOCALE_KEY, locale)
|
|
|
|
|
|
document.documentElement.setAttribute('lang', locale)
|
2026-02-26 14:04:13 +08:00
|
|
|
|
|
|
|
|
|
|
// 同步更新浏览器页签标题,使其跟随语言切换
|
|
|
|
|
|
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)
|
2025-12-18 13:50:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-14 11:56:08 +08:00
|
|
|
|
export function getLocale(): LocaleCode {
|
|
|
|
|
|
const current = i18n.global.locale.value
|
|
|
|
|
|
return isLocaleCode(current) ? current : DEFAULT_LOCALE
|
2025-12-18 13:50:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const availableLocales = [
|
|
|
|
|
|
{ code: 'en', name: 'English', flag: '🇺🇸' },
|
2025-12-25 08:40:35 -08:00
|
|
|
|
{ code: 'zh', name: '中文', flag: '🇨🇳' }
|
2026-02-14 11:56:08 +08:00
|
|
|
|
] as const
|
2025-12-18 13:50:39 +08:00
|
|
|
|
|
|
|
|
|
|
export default i18n
|