deotalandAi/apps/FrontendDesigner/src/composables/useI18n.js

281 lines
7.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 国际化 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
// 只保存到 localStoragestore 由 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
// 只保存到 localStoragestore 由 App.vue 统一管理
if (saveToStorage) {
localStorage.setItem('app-locale', localeCode)
}
// 更新 HTML lang 属性
document.documentElement.lang = localeCode
}
return {
currentLocale,
locales: SUPPORTED_LOCALES,
t,
setLocale
}
}