/** * 国际化 Hook * 提供语言切换和文本翻译功能 */ import { ref, watch } from 'vue' import { useI18n as useVueI18n } from 'vue-i18n' import { useAppStore } from '../stores' // 支持的语言列表 export const SUPPORTED_LOCALES = [ { code: 'zh-CN', name: '中文', nativeName: '中文', flag: '🇨🇳' }, { code: 'en-US', name: 'English', nativeName: 'English', flag: '🇺🇸' } ] // 检测浏览器语言 function detectBrowserLocale() { const browserLang = navigator.language || navigator.languages?.[0] if (browserLang?.startsWith('zh')) { return 'zh-CN' } else if (browserLang?.startsWith('en')) { return 'en-US' } return 'en-US' // 默认语言 } // 格式化日期 export function formatDate(date, options = {}) { const defaultOptions = { year: 'numeric', month: 'long', day: 'numeric' } const { locale } = useI18n() return new Intl.DateTimeFormat(locale.value, { ...defaultOptions, ...options }).format(new Date(date)) } // 格式化数字 export function formatNumber(number, options = {}) { const defaultOptions = { minimumFractionDigits: 0, maximumFractionDigits: 2 } const { locale } = useI18n() return new Intl.NumberFormat(locale.value, { ...defaultOptions, ...options }).format(number) } // 格式化货币 export function formatCurrency(amount, currency = 'USD') { const { locale } = useI18n() return new Intl.NumberFormat(locale.value, { style: 'currency', currency }).format(amount) } // 格式化相对时间 export function formatRelativeTime(date) { const now = new Date() const targetDate = new Date(date) const diffInSeconds = Math.floor((now - targetDate) / 1000) const rtf = new Intl.RelativeTimeFormat(useI18n().locale.value, { numeric: 'auto' }) const units = [ { unit: 'year', seconds: 31536000 }, { unit: 'month', seconds: 2592000 }, { unit: 'week', seconds: 604800 }, { unit: 'day', seconds: 86400 }, { unit: 'hour', seconds: 3600 }, { unit: 'minute', seconds: 60 }, { unit: 'second', seconds: 1 } ] for (const { unit, seconds } of units) { const interval = Math.floor(diffInSeconds / seconds) if (interval >= 1) { return rtf.format(-interval, unit) } } return rtf.format(0, 'second') } export function useI18nExt() { const { locale: i18nLocale, messages, t, d } = useVueI18n() const appStore = useAppStore() // 当前语言 const currentLocale = ref(i18nLocale.value) // 设置语言 const setLocale = (localeCode, saveToStorage = true) => { // 验证语言有效性 const isValidLocale = SUPPORTED_LOCALES.some(lang => lang.code === localeCode) if (!isValidLocale) { console.warn(`Invalid locale: ${localeCode}, falling back to 'en-US'`) localeCode = 'en-US' } // 更新 i18n 语言 i18nLocale.value = localeCode // 更新当前语言状态 currentLocale.value = localeCode // 只保存到 localStorage,store 由 App.vue 统一管理 if (saveToStorage) { localStorage.setItem('app-locale', localeCode) } // 更新 HTML lang 属性 document.documentElement.lang = localeCode // 不直接调用store,避免循环 // 更新日期格式 document.documentElement.dir = getTextDirection(localeCode) } // 获取文本方向 const getTextDirection = (localeCode) => { // 大多数语言从左到右,少数语言从右到左 const rtlLocales = ['ar', 'he', 'fa', 'ur'] return rtlLocales.some(lang => localeCode.startsWith(lang)) ? 'rtl' : 'ltr' } // 获取语言显示名称 const getLocaleName = (localeCode) => { const locale = SUPPORTED_LOCALES.find(lang => lang.code === localeCode) return locale ? `${locale.flag} ${locale.nativeName}` : localeCode } // 获取所有支持的语言 const getSupportedLocales = () => { return SUPPORTED_LOCALES } // 检查是否为 RTL 语言 const isRTL = () => { return getTextDirection(currentLocale.value) === 'rtl' } // 切换到下一个语言 const toggleLocale = () => { const currentIndex = SUPPORTED_LOCALES.findIndex(lang => lang.code === currentLocale.value) const nextIndex = (currentIndex + 1) % SUPPORTED_LOCALES.length const nextLocale = SUPPORTED_LOCALES[nextIndex] setLocale(nextLocale.code) } // 动态加载语言包 const loadLocaleMessages = async (localeCode) => { try { // 这里可以动态加载语言包文件 // const messages = await import(`../locales/lang/${localeCode}.js`) // i18n.global.setLocaleMessage(localeCode, messages.default) console.log(`Loading messages for locale: ${localeCode}`) } catch (error) { console.error(`Failed to load messages for locale: ${localeCode}`, error) } } // 翻译函数扩展 const translate = (key, params = {}) => { let translation = t(key) // 处理参数替换 if (typeof params === 'object' && Object.keys(params).length > 0) { Object.keys(params).forEach(param => { translation = translation.replace(`{${param}}`, params[param]) }) } return translation } // 复数化处理 const pluralize = (key, count) => { if (count === 0) { return t(`${key}.zero`) } else if (count === 1) { return t(`${key}.one`) } else if (count > 1 && count <= 10) { return t(`${key}.few`) } else { return t(`${key}.other`) } } // 初始化语言设置 const initLocale = () => { // 从 localStorage 读取保存的语言设置 const savedLocale = localStorage.getItem('app-locale') // 从 store 获取语言设置 const storeLocale = appStore.locale // 优先级:store > localStorage > 浏览器检测 const initialLocale = storeLocale || savedLocale || detectBrowserLocale() // 设置初始语言 setLocale(initialLocale, false) // 移除所有监听器,避免循环调用 // store 和 i18n 独立工作,避免相互干扰 } return { locale: currentLocale, setLocale, getLocaleName, getSupportedLocales, isRTL, toggleLocale, loadLocaleMessages, translate, pluralize, formatDate, formatNumber, formatCurrency, formatRelativeTime, initLocale } } // 简化的导出供页面组件使用 export function useI18n() { const { locale: i18nLocale, t } = useVueI18n() const appStore = useAppStore() // 当前语言 const currentLocale = ref(i18nLocale.value) // 设置语言 const setLocale = (localeCode, saveToStorage = true) => { // 验证语言有效性 const isValidLocale = SUPPORTED_LOCALES.some(lang => lang.code === localeCode) if (!isValidLocale) { console.warn(`Invalid locale: ${localeCode}, falling back to 'en-US'`) localeCode = 'en-US' } // 更新 i18n 语言 i18nLocale.value = localeCode // 更新当前语言状态 currentLocale.value = localeCode // 只保存到 localStorage,store 由 App.vue 统一管理 if (saveToStorage) { localStorage.setItem('app-locale', localeCode) } // 更新 HTML lang 属性 document.documentElement.lang = localeCode } return { currentLocale, locales: SUPPORTED_LOCALES, t, setLocale } }