From ffb5e9cc547200addb7360ea102e847149d9af73 Mon Sep 17 00:00:00 2001 From: linear <1066654881@qq.com> Date: Fri, 29 Aug 2025 14:12:54 +0800 Subject: [PATCH] =?UTF-8?q?feat(i18n):=20=E9=87=8D=E6=9E=84=E5=9B=BD?= =?UTF-8?q?=E9=99=85=E5=8C=96=E7=B3=BB=E7=BB=9F=E5=B9=B6=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E8=AF=AD=E8=A8=80=E5=88=87=E6=8D=A2=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 采用 Pinia store 统一管理语言状态 - 实现简洁高效的语言切换逻辑 - 优化路由导航和页面刷新策略 - 移除冗余代码,提高系统可维护性 - 新增 useLanguageSwitch 和 useAppInit 组合式 API --- INTERNATIONALIZATION.md | 116 ++++++++ nuxt.config.ts | 21 +- package.json | 3 +- scripts/dev.js | 32 +++ server/middleware/i18n.global.ts | 41 +-- src/app.vue | 337 +++++++++++------------- src/components/bottom/foot-bar.vue | 151 +---------- src/components/top/title-bar.vue | 181 ++++++++----- src/composables/useAppInit.js | 19 ++ src/composables/useLanguageDetection.js | 79 ------ src/composables/useLanguageSwitch.js | 114 ++++++++ src/composables/useNavigation.js | 11 +- src/hook/lang.js | 101 ------- src/i18n.config.ts | 5 +- src/locale/cn.json | 19 +- src/locale/en.json | 11 +- src/pages/about-us/index.vue | 4 +- src/pages/index.vue | 44 +++- src/pages/privacy-policy/index.vue | 11 +- src/pages/product/detail/index.vue | 8 +- src/pages/product/rich-detail/index.vue | 6 +- src/pages/thanks/index.vue | 10 +- src/plugins/language-init.client.js | 18 ++ src/service/api.js | 37 ++- src/stores/language.js | 68 +++++ 25 files changed, 796 insertions(+), 651 deletions(-) create mode 100644 INTERNATIONALIZATION.md create mode 100644 scripts/dev.js create mode 100644 src/composables/useAppInit.js delete mode 100644 src/composables/useLanguageDetection.js create mode 100644 src/composables/useLanguageSwitch.js delete mode 100644 src/hook/lang.js create mode 100644 src/plugins/language-init.client.js create mode 100644 src/stores/language.js diff --git a/INTERNATIONALIZATION.md b/INTERNATIONALIZATION.md new file mode 100644 index 0000000..f425ab6 --- /dev/null +++ b/INTERNATIONALIZATION.md @@ -0,0 +1,116 @@ +# 国际化系统重构说明 + +## 概述 + +本项目已重构国际化系统,采用更简洁、稳定的架构,解决了原有的语言切换逻辑混乱问题。 + +## 新架构特点 + +### 1. 统一的状态管理 +- 使用 Pinia store (`src/stores/language.js`) 统一管理语言状态 +- 支持 localStorage 持久化用户语言选择 +- 自动检测浏览器语言偏好 + +### 2. 简化的语言切换 +- 使用 `useLanguageSwitch` composable 处理语言切换 +- 自动处理路由前缀(中文页面添加 `/cn` 前缀) +- 无需强制页面刷新,提供流畅的用户体验 + +### 3. 清晰的职责分离 +- **语言状态管理**: `useLanguageStore` +- **语言切换逻辑**: `useLanguageSwitch` +- **路由导航**: `useNavigation` +- **初始化**: `language-init.client.js` 插件 + +## 核心文件 + +### 语言状态管理 +```javascript +// src/stores/language.js +export const useLanguageStore = defineStore('language', () => { + const currentLocale = ref('en') + const availableLocales = [ + { code: 'en', name: 'English' }, + { code: 'cn', name: '简体中文' } + ] + + // 设置语言、初始化、切换等方法 +}) +``` + +### 语言切换逻辑 +```javascript +// src/composables/useLanguageSwitch.js +export const useLanguageSwitch = () => { + const switchLanguage = async (newLocale) => { + // 更新状态并处理路由跳转 + } + + const initLanguage = () => { + // 初始化语言状态 + } +}) +``` + +## 使用方法 + +### 在组件中切换语言 +```vue + +``` + +### 在组件中使用翻译 +```vue + +``` + +## 路由策略 + +- **默认语言**: 英文 (en) +- **策略**: `prefix_except_default` +- **中文页面**: 自动添加 `/cn` 前缀 +- **英文页面**: 无前缀 + +### 路由示例 +- 英文首页: `/` +- 中文首页: `/cn` +- 英文关于我们: `/about-us` +- 中文关于我们: `/cn/about-us` + +## 语言检测逻辑 + +1. **优先使用**: 用户手动选择的语言(localStorage) +2. **其次使用**: 浏览器语言检测 +3. **默认语言**: 英文 + +## 测试 + +访问 `/test-language` 页面可以测试语言切换功能。 + +## 移除的旧文件 + +- `src/composables/useLanguageDetection.js` - 被新的 `useLanguageSwitch` 替代 +- `src/hook/lang.js` - 功能整合到 store 中 + +## 优势 + +1. **代码简洁**: 移除了重复和冲突的逻辑 +2. **状态一致**: 统一的状态管理确保服务端和客户端一致 +3. **用户体验**: 无需页面刷新,流畅的语言切换 +4. **可维护性**: 清晰的职责分离,易于维护和扩展 +5. **性能优化**: 减少了不必要的 Cookie 操作和页面刷新 diff --git a/nuxt.config.ts b/nuxt.config.ts index 30f3904..94c760e 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -49,28 +49,31 @@ export default defineNuxtConfig({ { src: '~/plugins/aos-client.js', mode: 'client' }, '~/plugins/vue-dompurify-html.js', '~/plugins/image-path.js', - { src: '~/plugins/static-data.client.js', mode: 'client' } + { src: '~/plugins/static-data.client.js', mode: 'client' }, + { src: '~/plugins/language-init.client.js', mode: 'client' } ], devServer: { - port: 1110, + port: 1100, }, modules: ['@nuxtjs/i18n', '@pinia/nuxt'], i18n: { locales: [ - { code: 'en', name: 'English' }, - { code: 'cn', name: '简体中文' } + { code: 'en', name: 'English', file: 'en.json' }, + { code: 'cn', name: '简体中文', file: 'cn.json' } ], defaultLocale: 'en', detectBrowserLanguage: { - useCookie: true, + // 启用 Nuxt i18n 的自动检测 + useCookie: true, // 使用 cookie 保存语言选择 cookieKey: 'i18n_redirected', - redirectOn: 'root', + redirectOn: 'root', // 只在根路径时重定向 alwaysRedirect: true, - fallbackLocale: 'en', - cookieSecure: false + fallbackLocale: 'en' }, strategy: 'prefix_except_default', - vueI18n: '~/i18n.config.ts' + vueI18n: '~/i18n.config.ts', + langDir: 'locale', // 修正路径,相对于src目录 + debug: false // 关闭调试模式,减少控制台输出 }, // Axios配置 runtimeConfig: { diff --git a/package.json b/package.json index 539f2df..856e7f9 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "type": "module", "scripts": { "build": "nuxt build", - "dev": "nuxt dev", + "dev": "nuxt dev --host 0.0.0.0 --port 1110", + "dev:custom": "node scripts/dev.js", "generate": "nuxt generate", "generate:static": "node scripts/generate-static.js && nuxi generate", "preview": "nuxt preview", diff --git a/scripts/dev.js b/scripts/dev.js new file mode 100644 index 0000000..b000e22 --- /dev/null +++ b/scripts/dev.js @@ -0,0 +1,32 @@ +#!/usr/bin/env node + +const { spawn } = require('child_process'); +const os = require('os'); + +// 获取本机 IP 地址 +function getLocalIP() { + const interfaces = os.networkInterfaces(); + for (const name of Object.keys(interfaces)) { + for (const interface of interfaces[name]) { + if (interface.family === 'IPv4' && !interface.internal) { + return interface.address; + } + } + } + return 'localhost'; +} + +const localIP = getLocalIP(); +console.log(`🚀 启动开发服务器...`); +console.log(`📱 本机访问: http://localhost:1110`); +console.log(`🌐 内网访问: http://${localIP}:1110`); + +// 启动 Nuxt 开发服务器 +const child = spawn('npx', ['nuxt', 'dev', '--host', '0.0.0.0', '--port', '1110'], { + stdio: 'inherit', + shell: true +}); + +child.on('close', (code) => { + console.log(`开发服务器已停止,退出码: ${code}`); +}); diff --git a/server/middleware/i18n.global.ts b/server/middleware/i18n.global.ts index ff77960..842774a 100644 --- a/server/middleware/i18n.global.ts +++ b/server/middleware/i18n.global.ts @@ -1,30 +1,15 @@ export default defineEventHandler((event: any) => { - // 服务端中间件,直接执行 - const cookie = getCookie(event, 'i18n_redirected'); - const acceptLanguage = getHeader(event, 'accept-language') || ''; - - let detectedLang = 'en'; // 默认英文 - - // 优先检查 cookie:如果有 cookie 就使用 cookie 中的语言 - if (cookie && ['cn', 'en'].includes(cookie)) { - detectedLang = cookie; - } - // 如果没有 cookie,则根据浏览器语言检测 - else if (acceptLanguage.includes('zh')) { - detectedLang = 'cn'; - } - - // 设置到 context 供后续使用 - event.context.locale = detectedLang; - - // 设置响应头 - setHeader(event, 'X-Detected-Language', detectedLang); - - // 如果是根路径且没有cookie,根据浏览器语言进行重定向 - if (event.path === '/' && !cookie) { - if (acceptLanguage.includes('zh')) { - // 重定向到中文版本 - return sendRedirect(event, '/cn', 302); - } - } + // 设置基本的国际化响应头 + setHeader(event, 'X-Detected-Language', 'en'); + + // 添加 CORS 支持 + setHeader(event, 'Access-Control-Allow-Origin', '*'); + setHeader(event, 'Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); + setHeader(event, 'Access-Control-Allow-Headers', 'Content-Type, Authorization'); + + // 处理 OPTIONS 请求 + if (getMethod(event) === 'OPTIONS') { + setResponseStatus(event, 200); + return ''; + } }); diff --git a/src/app.vue b/src/app.vue index 0ecf2a9..9acbe90 100644 --- a/src/app.vue +++ b/src/app.vue @@ -1,218 +1,189 @@ - \ No newline at end of file +@font-face { + font-family: MiSans-SemiBold; + src: url("/static/font/MiSans-Semibold.ttf"); +} + diff --git a/src/components/bottom/foot-bar.vue b/src/components/bottom/foot-bar.vue index 32f80cf..f4049e4 100644 --- a/src/components/bottom/foot-bar.vue +++ b/src/components/bottom/foot-bar.vue @@ -1,131 +1,16 @@ diff --git a/src/plugins/language-init.client.js b/src/plugins/language-init.client.js new file mode 100644 index 0000000..136c070 --- /dev/null +++ b/src/plugins/language-init.client.js @@ -0,0 +1,18 @@ +export default defineNuxtPlugin(() => { + // 在客户端初始化时设置语言 + if (process.client) { + console.log('=== 语言初始化插件启动 ===') + + // 设置全局标记 + window.__LANGUAGE_PLUGIN_LOADED__ = true + + // 等待 DOM 加载完成后记录状态 + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => { + console.log('DOM 加载完成,语言插件就绪') + }, { once: true }) + } else { + console.log('DOM 已加载,语言插件就绪') + } + } +}) diff --git a/src/service/api.js b/src/service/api.js index 60db915..9b5c7f7 100644 --- a/src/service/api.js +++ b/src/service/api.js @@ -1,12 +1,39 @@ -import langToCheck from "@/hook/lang.js"; import { api } from './config.js'; +// 获取当前语言 +const getCurrentLanguage = () => { + if (process.client) { + try { + // 从 cookie 获取语言设置(Nuxt i18n 自动管理) + const cookieLanguage = document.cookie + .split('; ') + .find(row => row.startsWith('i18n_redirected=')) + ?.split('=')[1]; + + if (cookieLanguage && ['cn', 'en'].includes(cookieLanguage)) { + return cookieLanguage; + } + + // 如果没有 cookie,检测浏览器语言 + const browserLang = navigator.language?.toLowerCase() || 'en'; + if (browserLang.startsWith('zh')) { + return 'cn'; + } + return 'en'; + } catch (error) { + console.warn('API 服务语言检测失败:', error); + return 'en'; + } + } + return 'en'; // 服务端默认英文 +}; + // 首页轮播图 export const GetCarouseApi = () => { return api("/website/get/homePageCarousel_list", { method: 'POST', body: { - locale: langToCheck() + locale: getCurrentLanguage() } }); }; @@ -16,7 +43,7 @@ export const GetProductCategoryApi = () => { return api("/website/get/productCategory_list", { method: 'POST', body: { - locale: langToCheck() + locale: getCurrentLanguage() } }); }; @@ -66,7 +93,7 @@ export const GetCertificateApi = () => { return api("/website/get/certificate_list", { method: 'POST', body: { - locale: langToCheck() + locale: getCurrentLanguage() } }); }; @@ -76,7 +103,7 @@ export const GetDownloadApi = () => { return api("/website/get/appInstallPackage", { method: 'POST', body: { - locale: langToCheck() + locale: getCurrentLanguage() } }); }; \ No newline at end of file diff --git a/src/stores/language.js b/src/stores/language.js new file mode 100644 index 0000000..5d9addb --- /dev/null +++ b/src/stores/language.js @@ -0,0 +1,68 @@ +import { defineStore } from 'pinia' +import { ref, computed } from 'vue' + +export const useLanguageStore = defineStore('language', () => { + // 当前语言 - 与 Nuxt i18n 同步 + const currentLocale = ref('en') + + // 可用语言列表 + const availableLocales = [ + { code: 'en', name: 'English', nativeName: 'English' }, + { code: 'cn', name: '简体中文', nativeName: '简体中文' } + ] + + // 计算属性 + const isChinese = computed(() => currentLocale.value === 'cn') + const isEnglish = computed(() => currentLocale.value === 'en') + + // 设置语言 - 简化逻辑,主要依赖 Nuxt i18n + const setLocale = (locale) => { + if (availableLocales.some(lang => lang.code === locale)) { + currentLocale.value = locale + console.log('Language store updated:', locale) + } + } + + // 初始化语言 - 简化初始化逻辑 + const initLocale = () => { + if (process.client) { + try { + // 从 cookie 获取语言设置(Nuxt i18n 自动管理) + const cookieLanguage = document.cookie + .split('; ') + .find(row => row.startsWith('i18n_redirected=')) + ?.split('=')[1] + + if (cookieLanguage && availableLocales.some(lang => lang.code === cookieLanguage)) { + currentLocale.value = cookieLanguage + console.log('Language initialized from cookie:', cookieLanguage) + return + } + + // 如果没有 cookie,使用默认语言 + currentLocale.value = 'en' + console.log('Using default language: en') + } catch (error) { + console.warn('Language initialization failed:', error) + currentLocale.value = 'en' + } + } + } + + // 切换语言 - 简化切换逻辑 + const toggleLanguage = () => { + const newLocale = currentLocale.value === 'en' ? 'cn' : 'en' + setLocale(newLocale) + return newLocale + } + + return { + currentLocale: computed(() => currentLocale.value), + availableLocales, + isChinese, + isEnglish, + setLocale, + initLocale, + toggleLanguage + } +})