Vue 3 全局主题和国际化实现指南

303 阅读2分钟

Vue 3 全局主题和国际化实现指南

目录结构

首先,让我们规划一个清晰的目录结构:

src/
├── styles/
│   ├── theme/
│   │   ├── dark.scss
│   │   ├── light.scss
│   │   └── variables.scss
│   └── index.scss
├── locales/
│   ├── en.ts
│   ├── zh.ts
│   └── index.ts
└── hooks/
    ├── useTheme.ts
    └── useI18n.ts

一、主题系统实现

1. 主题变量定义

// styles/theme/variables.scss

:root {
  // 颜色系统
  --primary-color: #1890ff;
  --success-color: #52c41a;
  --warning-color: #faad14;
  --error-color: #f5222d;
  
  // 文字颜色
  --text-primary: rgba(0, 0, 0, 0.85);
  --text-secondary: rgba(0, 0, 0, 0.65);
  --text-disabled: rgba(0, 0, 0, 0.25);
  
  // 背景颜色
  --bg-primary: #ffffff;
  --bg-secondary: #f5f5f5;
  
  // 边框颜色
  --border-color: #d9d9d9;
  
  // 字体大小
  --font-size-small: 12px;
  --font-size-base: 14px;
  --font-size-large: 16px;
}

2. 主题配置文件

// styles/theme/light.scss
:root[data-theme='light'] {
  --primary-color: #1890ff;
  --text-primary: rgba(0, 0, 0, 0.85);
  --bg-primary: #ffffff;
}

// styles/theme/dark.scss
:root[data-theme='dark'] {
  --primary-color: #177ddc;
  --text-primary: rgba(255, 255, 255, 0.85);
  --bg-primary: #141414;
}

3. 主题管理 Hook

// hooks/useTheme.ts
import { ref, watchEffect } from 'vue'

type Theme = 'light' | 'dark'

export function useTheme() {
  const theme = ref<Theme>('light')
  
  // 初始化主题
  const initTheme = () => {
    const savedTheme = localStorage.getItem('theme') as Theme
    if (savedTheme) {
      theme.value = savedTheme
    } else {
      // 根据系统主题设置默认值
      const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
      theme.value = prefersDark ? 'dark' : 'light'
    }
  }
  
  // 切换主题
  const changeTheme = (newTheme: Theme) => {
    theme.value = newTheme
    localStorage.setItem('theme', newTheme)
  }
  
  // 监听主题变化
  watchEffect(() => {
    document.documentElement.setAttribute('data-theme', theme.value)
    // 加载对应的主题样式
    import(`../styles/theme/${theme.value}.scss`)
  })
  
  return {
    theme,
    changeTheme,
    initTheme
  }
}

二、国际化系统实现

1. 语言包定义

// locales/zh.ts
export default {
  common: {
    confirm: '确认',
    cancel: '取消',
    loading: '加载中...'
  },
  user: {
    login: '登录',
    register: '注册',
    username: '用户名',
    password: '密码'
  }
}

// locales/en.ts
export default {
  common: {
    confirm: 'Confirm',
    cancel: 'Cancel',
    loading: 'Loading...'
  },
  user: {
    login: 'Login',
    register: 'Register',
    username: 'Username',
    password: 'Password'
  }
}

2. 语言管理 Hook

// hooks/useI18n.ts
import { ref, computed } from 'vue'
import type { Ref } from 'vue'

type Language = 'zh' | 'en'
type MessageSchema = typeof import('../locales/zh').default

export function useI18n() {
  const locale = ref<Language>('zh')
  const messages: Record<Language, MessageSchema> = {
    zh: require('../locales/zh').default,
    en: require('../locales/en').default
  }
  
  // 初始化语言
  const initLocale = () => {
    const savedLocale = localStorage.getItem('locale') as Language
    if (savedLocale) {
      locale.value = savedLocale
    } else {
      // 根据浏览器语言设置默认值
      const browserLang = navigator.language.toLowerCase()
      locale.value = browserLang.includes('zh') ? 'zh' : 'en'
    }
  }
  
  // 切换语言
  const changeLocale = (newLocale: Language) => {
    locale.value = newLocale
    localStorage.setItem('locale', newLocale)
  }
  
  // 翻译函数
  const t = (key: string) => {
    const keys = key.split('.')
    let result = messages[locale.value]
    
    for (const k of keys) {
      if (!result) return key
      result = result[k]
    }
    
    return result || key
  }
  
  return {
    locale,
    t,
    changeLocale,
    initLocale
  }
}

三、全局配置和使用

1. 创建全局配置 Provider

// components/Provider.vue
<template>
  <div :class="['app-provider', theme]">
    <slot />
  </div>
</template>

<script setup lang="ts">
import { provide, onMounted } from 'vue'
import { useTheme } from '../hooks/useTheme'
import { useI18n } from '../hooks/useI18n'

const { theme, changeTheme, initTheme } = useTheme()
const { locale, t, changeLocale, initLocale } = useI18n()

// 提供全局配置
provide('theme', {
  theme,
  changeTheme
})

provide('i18n', {
  locale,
  t,
  changeLocale
})

onMounted(() => {
  initTheme()
  initLocale()
})
</script>

2. 在入口文件中使用

// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import Provider from './components/Provider.vue'
import './styles/index.scss'

const app = createApp(App)

app.component('Provider', Provider)
app.mount('#app')

3. 在组件中使用

// components/Example.vue
<template>
  <div class="example">
    <!-- 使用翻译 -->
    <h1>{{ t('common.title') }}</h1>
    
    <!-- 主题切换按钮 -->
    <button @click="toggleTheme">
      {{ t('common.switchTheme') }}
    </button>
    
    <!-- 语言切换按钮 -->
    <button @click="toggleLocale">
      {{ t('common.switchLanguage') }}
    </button>
  </div>
</template>

<script setup lang="ts">
import { inject } from 'vue'

const { theme, changeTheme } = inject('theme')
const { locale, t, changeLocale } = inject('i18n')

const toggleTheme = () => {
  changeTheme(theme.value === 'light' ? 'dark' : 'light')
}

const toggleLocale = () => {
  changeLocale(locale.value === 'zh' ? 'en' : 'zh')
}
</script>

<style lang="scss" scoped>
.example {
  color: var(--text-primary);
  background-color: var(--bg-primary);
  
  h1 {
    font-size: var(--font-size-large);
  }
  
  button {
    background-color: var(--primary-color);
    color: white;
  }
}
</style>

四、TypeScript 支持

1. 声明文件

// types/theme.d.ts
declare interface ThemeContext {
  theme: Ref<'light' | 'dark'>
  changeTheme: (theme: 'light' | 'dark') => void
}

// types/i18n.d.ts
declare interface I18nContext {
  locale: Ref<'zh' | 'en'>
  t: (key: string) => string
  changeLocale: (locale: 'zh' | 'en') => void
}

2. 模块增强

// types/vue.d.ts
import { ThemeContext, I18nContext } from './theme'

declare module '@vue/runtime-core' {
  interface ComponentCustomProperties {
    $theme: ThemeContext
    $i18n: I18nContext
  }
}

五、最佳实践建议

  1. 主题切换优化

    • 使用 CSS 变量实现无刷新切换
    • 考虑添加过渡动画
    • 实现主题预览功能
  2. 国际化优化

    • 支持语言包按需加载
    • 实现数字、日期、货币等格式化
    • 支持复数形式
  3. 性能优化

    • 缓存翻译结果
    • 避免不必要的重渲染
    • 优化样式文件加载
  4. 可维护性建议

    • 保持语言包结构统一
    • 使用类型检查确保翻译键的完整性
    • 定期更新和维护主题变量

总结

通过以上配置,我们实现了一个完整的全局主题和国际化解决方案。这个方案具有以下特点:

  1. 支持主题无缝切换
  2. 完整的国际化支持
  3. 类型安全
  4. 可扩展性强
  5. 使用简单直观

建议根据实际项目需求进行适当调整和优化。

参考资源