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
}
}
五、最佳实践建议
-
主题切换优化
- 使用 CSS 变量实现无刷新切换
- 考虑添加过渡动画
- 实现主题预览功能
-
国际化优化
- 支持语言包按需加载
- 实现数字、日期、货币等格式化
- 支持复数形式
-
性能优化
- 缓存翻译结果
- 避免不必要的重渲染
- 优化样式文件加载
-
可维护性建议
- 保持语言包结构统一
- 使用类型检查确保翻译键的完整性
- 定期更新和维护主题变量
总结
通过以上配置,我们实现了一个完整的全局主题和国际化解决方案。这个方案具有以下特点:
- 支持主题无缝切换
- 完整的国际化支持
- 类型安全
- 可扩展性强
- 使用简单直观
建议根据实际项目需求进行适当调整和优化。