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
+
+ {{ $t('page.home') }}
+ {{ $t('nav.about') }}
+
+```
+
+## 路由策略
+
+- **默认语言**: 英文 (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 @@
@@ -234,13 +109,13 @@ const goTab = (path) => {
-
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
+ }
+})