Browse Source

```
feat(i18n): 引入 vue-i18n 实现国际化功能

新增 vue-i18n 依赖并配置相关模块,支持多语言切换。
在 App.vue 中集成 Element Plus 的 ConfigProvider 并使用语言切换组件,
同时更新页面标题为国际化字段。初始化 Pinia 并注册 i18n 实例到 Vue 应用。
```

zhangningning 46 minutes ago
parent
commit
d525865583
9 changed files with 207 additions and 2 deletions
  1. 71 0
      package-lock.json
  2. 1 0
      package.json
  3. 10 1
      src/App.vue
  4. 30 0
      src/components/LangSwitch.vue
  5. 29 0
      src/i18n/index.js
  6. 11 0
      src/locales/en.js
  7. 13 0
      src/locales/zh-CN.js
  8. 6 1
      src/main.js
  9. 36 0
      src/pinia/langStore.js

+ 71 - 0
package-lock.json

@@ -20,6 +20,7 @@
         "sass": "^1.94.2",
         "veaury": "^2.6.3",
         "vue": "^3.5.24",
+        "vue-i18n": "^9.14.5",
         "vue-react-wrapper": "^0.3.1",
         "vue-router": "^4.6.3"
       },
@@ -739,6 +740,50 @@
         "prosemirror-view": "^1.0.0"
       }
     },
+    "node_modules/@intlify/core-base": {
+      "version": "9.14.5",
+      "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.14.5.tgz",
+      "integrity": "sha512-5ah5FqZG4pOoHjkvs8mjtv+gPKYU0zCISaYNjBNNqYiaITxW8ZtVih3GS/oTOqN8d9/mDLyrjD46GBApNxmlsA==",
+      "license": "MIT",
+      "dependencies": {
+        "@intlify/message-compiler": "9.14.5",
+        "@intlify/shared": "9.14.5"
+      },
+      "engines": {
+        "node": ">= 16"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/kazupon"
+      }
+    },
+    "node_modules/@intlify/message-compiler": {
+      "version": "9.14.5",
+      "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.14.5.tgz",
+      "integrity": "sha512-IHzgEu61/YIpQV5Pc3aRWScDcnFKWvQA9kigcINcCBXN8mbW+vk9SK+lDxA6STzKQsVJxUPg9ACC52pKKo3SVQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@intlify/shared": "9.14.5",
+        "source-map-js": "^1.0.2"
+      },
+      "engines": {
+        "node": ">= 16"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/kazupon"
+      }
+    },
+    "node_modules/@intlify/shared": {
+      "version": "9.14.5",
+      "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.14.5.tgz",
+      "integrity": "sha512-9gB+E53BYuAEMhbCAxVgG38EZrk59sxBtv3jSizNL2hEWlgjBjAw1AwpLHtNaeda12pe6W20OGEa0TwuMSRbyQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 16"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/kazupon"
+      }
+    },
     "node_modules/@jridgewell/sourcemap-codec": {
       "version": "1.5.5",
       "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
@@ -5413,6 +5458,32 @@
         }
       }
     },
+    "node_modules/vue-i18n": {
+      "version": "9.14.5",
+      "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.14.5.tgz",
+      "integrity": "sha512-0jQ9Em3ymWngyiIkj0+c/k7WgaPO+TNzjKSNq9BvBQaKJECqn9cd9fL4tkDhB5G1QBskGl9YxxbDAhgbFtpe2g==",
+      "license": "MIT",
+      "dependencies": {
+        "@intlify/core-base": "9.14.5",
+        "@intlify/shared": "9.14.5",
+        "@vue/devtools-api": "^6.5.0"
+      },
+      "engines": {
+        "node": ">= 16"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/kazupon"
+      },
+      "peerDependencies": {
+        "vue": "^3.0.0"
+      }
+    },
+    "node_modules/vue-i18n/node_modules/@vue/devtools-api": {
+      "version": "6.6.4",
+      "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
+      "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
+      "license": "MIT"
+    },
     "node_modules/vue-react-wrapper": {
       "version": "0.3.1",
       "resolved": "https://registry.npmjs.org/vue-react-wrapper/-/vue-react-wrapper-0.3.1.tgz",

+ 1 - 0
package.json

@@ -21,6 +21,7 @@
     "sass": "^1.94.2",
     "veaury": "^2.6.3",
     "vue": "^3.5.24",
+    "vue-i18n": "^9.14.5",
     "vue-react-wrapper": "^0.3.1",
     "vue-router": "^4.6.3"
   },

+ 10 - 1
src/App.vue

@@ -1,9 +1,10 @@
 <template>
   <div id="app">
+    <ElConfigProvider :locale="langStore.elLocale">
     <el-container style="min-height: 100vh;">
       <el-header>
         <div class="header-content">
-          <div class="logo" @click="$router.push('/')">视频学习平台</div>
+          <div class="logo" @click="$router.push('/')">{{ $t('common.title') }}</div>
           <el-menu :default-active="activeIndex" mode="horizontal" :ellipsis="false">
             <el-menu-item index="1" @click="$router.push('/')">AI工作流</el-menu-item>
             <el-menu-item index="2" @click="$router.push('/my-learning')">工作流交易</el-menu-item>
@@ -15,6 +16,7 @@
             <el-button type="text">登录</el-button>
             <el-button type="primary">注册</el-button>
           </div>
+          <LangSwitch />
         </div>
       </el-header>
       
@@ -28,12 +30,19 @@
         </div>
       </el-footer>
     </el-container>
+    </ElConfigProvider>
   </div>
 </template>
 
 <script setup>
 import { computed,ref } from 'vue'
+import LangSwitch from './components/LangSwitch.vue'
+import { ElConfigProvider } from 'element-plus'
 import { useRoute } from 'vue-router'
+// 在Pinia安装后再设置初始语言
+import { useLangStore } from '@/pinia/langStore'
+const langStore = useLangStore()
+$i18n.global.locale.value = langStore.currentLang
 
 const route = useRoute()
 

+ 30 - 0
src/components/LangSwitch.vue

@@ -0,0 +1,30 @@
+<template>
+  <div class="lang-switch">
+    <el-button @click="switchToZh" :type="langStore.currentLang === 'zh-CN' ? 'primary' : 'text'">中文</el-button>
+    <el-button @click="switchToEn" :type="langStore.currentLang === 'en' ? 'primary' : 'text'">English</el-button>
+  </div>
+</template>
+
+<script setup>
+import { useLangStore } from '@/pinia/langStore'
+
+const langStore = useLangStore()
+
+// 切换到中文
+const switchToZh = () => {
+  langStore.changeLang('zh-CN')
+}
+
+// 切换到英文
+const switchToEn = () => {
+  langStore.changeLang('en')
+}
+</script>
+
+<style scoped>
+.lang-switch button {
+  margin: 0 8px;
+  padding: 4px 12px;
+  cursor: pointer;
+}
+</style>

+ 29 - 0
src/i18n/index.js

@@ -0,0 +1,29 @@
+import { createI18n } from 'vue-i18n'
+import { useLangStore } from '@/pinia/langStore'
+// 导入语言包
+import zhCN from '@/locales/zh-CN'
+import en from '@/locales/en'
+
+// 语言包映射
+const messages = {
+  'zh-CN': zhCN,
+  'en': en
+}
+
+// 创建i18n实例
+const i18n = createI18n({
+  legacy: false, // 必须设置为false,适配Vue3的组合式API
+  globalInjection: true, // 全局注入$t等方法,方便模板使用
+  locale: 'zh-CN', // 初始语言从Pinia读取
+  fallbackLocale: 'zh-CN', // 回退语言(当指定语言不存在时)
+  messages,
+  sync: true,
+  silentTranslationWarn: true,
+  missingWarn: false,
+  silentFallbackWarn: true // 语言包
+})
+
+// 将i18n挂载到window,方便Pinia中直接访问
+window.$i18n = i18n
+
+export default i18n

+ 11 - 0
src/locales/en.js

@@ -0,0 +1,11 @@
+export default {
+  common: {
+    title: 'Vue3 Multi-Language Demo',
+    welcome: 'Welcome to use vue-i18n + Pinia + localStorage',
+    switchLang: 'Switch Language'
+  },
+  home: {
+    desc: 'Current Language: {lang}',
+    button: 'Click to Test'
+  }
+}

+ 13 - 0
src/locales/zh-CN.js

@@ -0,0 +1,13 @@
+export default {
+  // 通用模块
+  common: {
+    title: 'Vue3多语言示例',
+    welcome: '欢迎使用vue-i18n + Pinia + localStorage',
+    switchLang: '切换语言'
+  },
+  // 页面模块(可按业务拆分)
+  home: {
+    desc: '当前语言:{lang}',
+    button: '点击测试'
+  }
+}

+ 6 - 1
src/main.js

@@ -5,18 +5,23 @@ import 'element-plus/dist/index.css'
 import router from './router'
 // import './styles/index.scss'
 import App from './App.vue'
+import i18n from './i18n' // 导入i18n配置
 
 // 如果您正在使用CDN引入,请删除下面一行。
 import * as ElementPlusIconsVue from '@element-plus/icons-vue'
 import Breadcrumb from '@/components/Breadcrumb.vue'
 
 const app = createApp(App)
+app.use(createPinia())
+
+// 注册i18n
+app.use(i18n)
 
 for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
   app.component(key, component)
 }
 
-app.use(createPinia())
+
 app.use(router)
 app.use(ElementPlus)
 // 全局注册 Breadcrumb 组件

+ 36 - 0
src/pinia/langStore.js

@@ -0,0 +1,36 @@
+import { defineStore } from 'pinia'
+import zhCn from 'element-plus/es/locale/lang/zh-cn'
+import en from 'element-plus/es/locale/lang/en'
+
+// 定义localStorage的key
+const LANG_KEY = 'APP_CURRENT_LANG'
+const elLocaleMap = {
+  'zh-CN': zhCn,
+  en: en
+}
+
+// 默认语言(优先从localStorage读取,没有则用浏览器默认)
+const defaultLang = localStorage.getItem(LANG_KEY) || (navigator.language || 'zh-CN').toLowerCase()
+
+export const useLangStore = defineStore('lang', {
+  state: () => ({
+    // 当前语言
+    currentLang: defaultLang === 'en' ? 'en' : 'zh-CN', // 兼容处理,只保留en/zh-CN
+    elLocale: elLocaleMap[defaultLang === 'en' ? 'en' : 'zh-CN']
+  }),
+  actions: {
+    // 切换语言
+    changeLang(lang) {
+      this.currentLang = lang
+      // 持久化到localStorage
+      localStorage.setItem(LANG_KEY, lang)
+      
+      // 触发i18n实例的语言切换(后续在i18n配置中关联)
+      if (window.$i18n && window.$i18n.global) {
+        window.$i18n.global.locale.value = lang
+      }
+      window.$i18n.locale = lang
+      console.log('切换到语言:', lang, $i18n)
+    }
+  }
+})