feat(i18n): 重构国际化系统并优化语言切换功能
- 采用 Pinia store 统一管理语言状态 - 实现简洁高效的语言切换逻辑 - 优化路由导航和页面刷新策略 - 移除冗余代码,提高系统可维护性 - 新增 useLanguageSwitch 和 useAppInit 组合式 API
This commit is contained in:
parent
15cdaee012
commit
ffb5e9cc54
|
|
@ -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
|
||||
<script setup>
|
||||
import { useLanguageSwitch } from '@/composables/useLanguageSwitch.js'
|
||||
|
||||
const { switchLanguage, currentLocale } = useLanguageSwitch()
|
||||
|
||||
const changeToChinese = () => {
|
||||
switchLanguage('cn')
|
||||
}
|
||||
|
||||
const changeToEnglish = () => {
|
||||
switchLanguage('en')
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### 在组件中使用翻译
|
||||
```vue
|
||||
<template>
|
||||
<h1>{{ $t('page.home') }}</h1>
|
||||
<p>{{ $t('nav.about') }}</p>
|
||||
</template>
|
||||
```
|
||||
|
||||
## 路由策略
|
||||
|
||||
- **默认语言**: 英文 (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 操作和页面刷新
|
||||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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}`);
|
||||
});
|
||||
|
|
@ -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 '';
|
||||
}
|
||||
});
|
||||
|
|
|
|||
337
src/app.vue
337
src/app.vue
|
|
@ -1,218 +1,189 @@
|
|||
<template>
|
||||
<div>
|
||||
<keep-alive v-if="isKeepAlive">
|
||||
<NuxtPage />
|
||||
</keep-alive>
|
||||
<NuxtPage v-else />
|
||||
<back-top v-if="currentPath !== '/404'" />
|
||||
</div>
|
||||
<div>
|
||||
<keep-alive v-if="isKeepAlive">
|
||||
<NuxtPage />
|
||||
</keep-alive>
|
||||
<NuxtPage v-else />
|
||||
<back-top v-if="currentPath !== '/404'" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
computed
|
||||
} from 'vue';
|
||||
import {
|
||||
useRoute
|
||||
} from '#imports';
|
||||
import BackTop from "@/components/back-top/back-top.vue";
|
||||
|
||||
// 获取当前路由对象
|
||||
const route = useRoute();
|
||||
// 计算当前路由的完整路径
|
||||
const currentPath = computed(() => route.fullPath);
|
||||
// 根据 meta 信息判断是否需要缓存页面
|
||||
const isKeepAlive = computed(() => {
|
||||
return route.meta.keepAlive;
|
||||
});
|
||||
|
||||
useHead({
|
||||
title: '明阳良光',
|
||||
meta: [{
|
||||
name: 'viewport',
|
||||
content: 'width=device-width, initial-scale=1'
|
||||
},
|
||||
{
|
||||
hid: 'description',
|
||||
name: 'description',
|
||||
content: '明阳良光'
|
||||
},
|
||||
{
|
||||
name: 'theme-color',
|
||||
content: '#4f8cef'
|
||||
}
|
||||
],
|
||||
link: [
|
||||
{
|
||||
hid: 'favicon',
|
||||
rel: 'icon',
|
||||
href: '/favicon.ico'
|
||||
},
|
||||
{
|
||||
hid: 'iconfont',
|
||||
rel: 'stylesheet',
|
||||
href: '/static/font/iconfont.css'
|
||||
}
|
||||
],
|
||||
script: [{
|
||||
innerHTML: `
|
||||
import { computed, onMounted } from "vue";
|
||||
import { useRoute } from "#imports";
|
||||
import BackTop from "@/components/back-top/back-top.vue";
|
||||
import { useAppInit } from "@/composables/useAppInit.js";
|
||||
|
||||
// 获取当前路由对象
|
||||
const route = useRoute();
|
||||
// 计算当前路由的完整路径
|
||||
const currentPath = computed(() => route.fullPath);
|
||||
// 根据 meta 信息判断是否需要缓存页面
|
||||
const isKeepAlive = computed(() => {
|
||||
return route.meta.keepAlive;
|
||||
});
|
||||
|
||||
// 应用初始化
|
||||
const { initApp } = useAppInit();
|
||||
|
||||
onMounted(() => {
|
||||
// 在应用挂载时初始化
|
||||
initApp();
|
||||
});
|
||||
|
||||
useHead({
|
||||
title: "明阳良光",
|
||||
meta: [
|
||||
{
|
||||
name: "viewport",
|
||||
content: "width=device-width, initial-scale=1",
|
||||
},
|
||||
{
|
||||
hid: "description",
|
||||
name: "description",
|
||||
content: "明阳良光",
|
||||
},
|
||||
{
|
||||
name: "theme-color",
|
||||
content: "#4f8cef",
|
||||
},
|
||||
],
|
||||
link: [
|
||||
{
|
||||
hid: "favicon",
|
||||
rel: "icon",
|
||||
href: "/favicon.ico",
|
||||
},
|
||||
{
|
||||
hid: "iconfont",
|
||||
rel: "stylesheet",
|
||||
href: "/static/font/iconfont.css",
|
||||
},
|
||||
],
|
||||
script: [
|
||||
{
|
||||
innerHTML: `
|
||||
(function() {
|
||||
// 立即执行函数,确保在任何Vue组件初始化前设置语言
|
||||
// 简化的客户端语言状态设置
|
||||
if (typeof window !== 'undefined') {
|
||||
try {
|
||||
// 优先使用cookie中的语言设置(与Nuxt i18n保持一致)
|
||||
let savedLang = 'en';
|
||||
// 根据当前路径确定语言
|
||||
const currentPath = window.location.pathname;
|
||||
const currentLang = currentPath.startsWith('/cn') ? 'cn' : 'en';
|
||||
|
||||
// 1. 检查cookie
|
||||
const cookies = document.cookie.split(';');
|
||||
const localeCookie = cookies.find(cookie =>
|
||||
cookie.trim().startsWith('i18n_redirected=')
|
||||
);
|
||||
if (localeCookie) {
|
||||
const langValue = localeCookie.split('=')[1];
|
||||
if (['cn', 'en'].includes(langValue)) {
|
||||
savedLang = langValue;
|
||||
}
|
||||
}
|
||||
// 设置语言属性
|
||||
document.documentElement.setAttribute('lang', currentLang === 'cn' ? 'zh-CN' : 'en');
|
||||
document.documentElement.setAttribute('data-locale', currentLang);
|
||||
|
||||
// 2. 如果没有cookie,检查localStorage
|
||||
if (savedLang === 'en') {
|
||||
const storedLang = localStorage.getItem('locale_lang');
|
||||
if (storedLang && ['cn', 'en'].includes(storedLang)) {
|
||||
savedLang = storedLang;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 如果都没有,根据浏览器语言设置
|
||||
if (savedLang === 'en') {
|
||||
const browserLang = navigator.language.toLowerCase();
|
||||
if (browserLang.startsWith("en")) {
|
||||
savedLang = "en";
|
||||
} else if (browserLang.includes("zh") || browserLang.includes("cn")) {
|
||||
savedLang = "cn";
|
||||
}
|
||||
}
|
||||
|
||||
// 设置多个位置确保语言被正确读取
|
||||
document.documentElement.setAttribute('data-initial-lang', savedLang);
|
||||
document.documentElement.setAttribute('lang', savedLang === 'cn' ? 'zh-CN' : 'en');
|
||||
document.documentElement.setAttribute('data-locale', savedLang);
|
||||
window.__INITIAL_LANG__ = savedLang;
|
||||
|
||||
// 为i18n设置初始语言
|
||||
window.__I18N_INITIAL_LOCALE__ = savedLang;
|
||||
|
||||
// 确保cookie和localStorage同步
|
||||
if (savedLang !== 'en') {
|
||||
document.cookie = 'i18n_redirected=' + savedLang + '; path=/; max-age=31536000';
|
||||
localStorage.setItem('locale_lang', savedLang);
|
||||
// 设置页面标题的语言属性
|
||||
if (document.title) {
|
||||
document.title = document.title + (currentLang === 'cn' ? ' - 明阳良光' : ' - Mingyang Liangguang');
|
||||
}
|
||||
} catch (e) {
|
||||
const defaultLang = 'en';
|
||||
document.documentElement.setAttribute('data-initial-lang', defaultLang);
|
||||
console.warn('Language setup failed:', e);
|
||||
// 出错时默认英文
|
||||
document.documentElement.setAttribute('lang', 'en');
|
||||
document.documentElement.setAttribute('data-locale', defaultLang);
|
||||
window.__INITIAL_LANG__ = defaultLang;
|
||||
window.__I18N_INITIAL_LOCALE__ = defaultLang;
|
||||
document.documentElement.setAttribute('data-locale', 'en');
|
||||
}
|
||||
}
|
||||
})();
|
||||
`,
|
||||
type: 'text/javascript'
|
||||
}]
|
||||
})
|
||||
type: "text/javascript",
|
||||
},
|
||||
],
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="scss">
|
||||
@import './common/css/common.scss';
|
||||
@import "./common/css/common.scss";
|
||||
|
||||
// 防止语言切换时的闪烁 - 优化版本
|
||||
html:not([data-locale]) {
|
||||
// 在语言未确定前隐藏内容
|
||||
body {
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
}
|
||||
// 防止语言切换时的闪烁 - 优化版本
|
||||
html:not([data-locale]) {
|
||||
// 在语言未确定前隐藏内容
|
||||
body {
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
}
|
||||
|
||||
// 语言确定后显示内容
|
||||
html[data-locale] {
|
||||
body {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
// 语言确定后显示内容
|
||||
html[data-locale] {
|
||||
body {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 确保语言切换平滑
|
||||
html {
|
||||
transition: all 0.1s ease;
|
||||
}
|
||||
// 确保语言切换平滑
|
||||
html {
|
||||
transition: all 0.1s ease;
|
||||
}
|
||||
|
||||
// 语言特定的样式优化
|
||||
html[data-locale="en"] {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
}
|
||||
// 语言特定的样式优化
|
||||
html[data-locale="en"] {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
}
|
||||
|
||||
html[data-locale="cn"] {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Microsoft YaHei', sans-serif;
|
||||
}
|
||||
html[data-locale="cn"] {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC",
|
||||
"Microsoft YaHei", sans-serif;
|
||||
}
|
||||
|
||||
// 防止水合不匹配时的闪烁
|
||||
.navbar {
|
||||
opacity: 1;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
// 防止水合不匹配时的闪烁
|
||||
.navbar {
|
||||
opacity: 1;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
// 确保内容在语言切换时不会跳动
|
||||
* {
|
||||
transition: none;
|
||||
}
|
||||
// 确保内容在语言切换时不会跳动
|
||||
* {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
// 只有特定元素需要过渡效果
|
||||
.navbar,
|
||||
.menu-item,
|
||||
.language-selector,
|
||||
.mobile-menu {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
// 只有特定元素需要过渡效果
|
||||
.navbar,
|
||||
.menu-item,
|
||||
.language-selector,
|
||||
.mobile-menu {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
// 防止语言切换时的布局抖动
|
||||
.language-selector {
|
||||
min-width: 80px; // 确保语言选择器有固定宽度
|
||||
}
|
||||
// 防止语言切换时的布局抖动
|
||||
.language-selector {
|
||||
min-width: 80px; // 确保语言选择器有固定宽度
|
||||
}
|
||||
|
||||
.lang-text {
|
||||
min-width: 40px; // 确保语言文本有固定宽度
|
||||
text-align: center;
|
||||
}
|
||||
.lang-text {
|
||||
min-width: 40px; // 确保语言文本有固定宽度
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: MiSans-Light;
|
||||
src: url("/static/font/MiSans-Light.ttf");
|
||||
}
|
||||
@font-face {
|
||||
font-family: MiSans-Light;
|
||||
src: url("/static/font/MiSans-Light.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: MiSans-Bold;
|
||||
src: url("/static/font/MiSans-Bold.ttf");
|
||||
}
|
||||
@font-face {
|
||||
font-family: MiSans-Bold;
|
||||
src: url("/static/font/MiSans-Bold.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: MiSans-Medium;
|
||||
src: url("/static/font/MiSans-Medium.ttf");
|
||||
}
|
||||
@font-face {
|
||||
font-family: MiSans-Medium;
|
||||
src: url("/static/font/MiSans-Medium.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: MiSans-Regular;
|
||||
src: url("/static/font/MiSans-Regular.ttf");
|
||||
}
|
||||
@font-face {
|
||||
font-family: MiSans-Regular;
|
||||
src: url("/static/font/MiSans-Regular.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: MiSans-Normal;
|
||||
src: url("/static/font/MiSans-Normal.ttf");
|
||||
}
|
||||
@font-face {
|
||||
font-family: MiSans-Normal;
|
||||
src: url("/static/font/MiSans-Normal.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: MiSans-SemiBold;
|
||||
src: url("/static/font/MiSans-Semibold.ttf");
|
||||
}
|
||||
</style>
|
||||
@font-face {
|
||||
font-family: MiSans-SemiBold;
|
||||
src: url("/static/font/MiSans-Semibold.ttf");
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,131 +1,16 @@
|
|||
<script setup name="footer">
|
||||
import { ref, reactive, onUnmounted, onMounted } from "vue";
|
||||
import { ref } from "vue";
|
||||
import { useI18n } from "#imports";
|
||||
import { GetMessageApi } from "@/service/api.js";
|
||||
import { useRoute } from "#imports";
|
||||
import { useNavigation } from "@/composables/useNavigation.js";
|
||||
const { t } = useI18n();
|
||||
import { GetProductCategoryApi } from "~/service/api.js";
|
||||
//商品与新闻状态
|
||||
import useSheller from "~/stores/seller.js";
|
||||
// 语言判断
|
||||
import langToCheck from "~/hook/lang.js";
|
||||
const langIs = ref(langToCheck());
|
||||
const route = useRoute();
|
||||
const { navigateTo } = useNavigation();
|
||||
//页面跳转事件
|
||||
const Store = useSheller();
|
||||
const props = defineProps({
|
||||
//首页加边距
|
||||
marginSpacing: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
import { useLanguageStore } from "@/stores/language.js";
|
||||
|
||||
const { t } = useI18n();
|
||||
const languageStore = useLanguageStore();
|
||||
const langIs = ref(languageStore.currentLocale);
|
||||
const { navigateTo } = useNavigation();
|
||||
|
||||
|
||||
|
||||
const subRouteEvent = (subParmas, item) => {
|
||||
let secondaryRoute = subParmas.enName;
|
||||
switch (secondaryRoute) {
|
||||
case "Power Station": {
|
||||
Store.clickTab = 0;
|
||||
navigateTo("product", { query: { CategoryId: 0 } });
|
||||
break;
|
||||
}
|
||||
case "Solar Panel": {
|
||||
Store.clickTab = 1;
|
||||
navigateTo("product", { query: { CategoryId: 1 } });
|
||||
break;
|
||||
}
|
||||
case "Accessory": {
|
||||
Store.clickTab = 2;
|
||||
navigateTo("product", { query: { CategoryId: 2 } });
|
||||
break;
|
||||
}
|
||||
case "About Us": {
|
||||
navigateTo("about-us");
|
||||
break;
|
||||
}
|
||||
case "Contact": {
|
||||
navigateTo("contact-us");
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// 产品分类跳转到首页对应分类
|
||||
navigateTo("home");
|
||||
break;
|
||||
}
|
||||
}
|
||||
footTabs.forEach((it) => {
|
||||
(it.checked = false), (it.expand = false);
|
||||
});
|
||||
};
|
||||
// 改成小写,并 " - "代替空格
|
||||
const FlowReplace = (str) => {
|
||||
return str.toLowerCase().replace(/\s+/g, "-");
|
||||
};
|
||||
//expandwei
|
||||
const footTabs = reactive([]);
|
||||
const GetProductCategorylist = () => {
|
||||
GetProductCategoryApi().then((res) => {
|
||||
let arrofObjects = res.rows;
|
||||
Store.categoryList = arrofObjects.map((obj, k) => {
|
||||
return {
|
||||
...obj,
|
||||
categorId: k,
|
||||
path: "product/product",
|
||||
Params: k,
|
||||
};
|
||||
});
|
||||
footTabs[0].dropList = Store.categoryList;
|
||||
});
|
||||
};
|
||||
onMounted(() => {
|
||||
footTabs.push(
|
||||
{
|
||||
enName: "Product",
|
||||
name: "产品系列",
|
||||
expand: false,
|
||||
dropList: [
|
||||
{
|
||||
enName: "监控摄像头",
|
||||
name: "监控摄像头",
|
||||
},
|
||||
{
|
||||
enName: "LED灯泡",
|
||||
name: "LED灯泡",
|
||||
},
|
||||
{
|
||||
enName: "车灯",
|
||||
name: "车灯",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
enName: "About",
|
||||
name: "关于我们",
|
||||
expand: false,
|
||||
dropList: [
|
||||
{
|
||||
enName: "About Us",
|
||||
name: "关于我们",
|
||||
path: "/about-us"
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
enName: "Contact",
|
||||
name: "联系我们",
|
||||
expand: false,
|
||||
dropList: [
|
||||
{
|
||||
enName: "Contact",
|
||||
name: "联系我们",
|
||||
path: "/contact-us/contact-us"
|
||||
},
|
||||
],
|
||||
}
|
||||
);
|
||||
});
|
||||
// 服务协议、隐私政策、域名、网安信息
|
||||
const GoAgreement = (value) => {
|
||||
switch (value) {
|
||||
|
|
@ -163,17 +48,7 @@ const GoAgreement = (value) => {
|
|||
}
|
||||
}
|
||||
};
|
||||
//折叠面板组件
|
||||
const TransitTap = (symbolName) => {
|
||||
footTabs.forEach((it) => {
|
||||
symbolName == it.enName ? (it.expand = !it.expand) : (it.expand = false);
|
||||
});
|
||||
};
|
||||
|
||||
// 接口
|
||||
const goTab = (path) => {
|
||||
navigateTo(path);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -234,13 +109,13 @@ const goTab = (path) => {
|
|||
</div>
|
||||
</div>
|
||||
<!-- 末尾 -->
|
||||
<div class="foot-bottom" v-if="langIs === 'cn'">
|
||||
<div class="foot-bottom">
|
||||
<div class="foot-bottom__box">
|
||||
<div class="foot-bottom__box__content">
|
||||
<p class="foot-bottom__box__content__copyright">
|
||||
版权所有 © 2025 明阳良光 |
|
||||
<a @click="GoAgreement(5)" class="foot-bottom__box__content__link">粤ICP备2023070569号</a> |
|
||||
<a @click="GoAgreement(7)" class="foot-bottom__box__content__link">粤公网安备 粤ICP备2023070569号</a>
|
||||
{{ t('footer.copyright') }} |
|
||||
<a @click="GoAgreement(5)" class="foot-bottom__box__content__link">{{ t('footer.icp') }}</a> |
|
||||
<a @click="GoAgreement(7)" class="foot-bottom__box__content__link">{{ t('footer.public-security') }}</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
<script setup>
|
||||
import { ref, reactive, onMounted, computed, nextTick, watch } from "vue";
|
||||
import { useI18n } from "#imports";
|
||||
import { useRoute } from "#imports";
|
||||
import { useRoute, useRouter } from "#imports";
|
||||
import { useNavigation } from "@/composables/useNavigation.js";
|
||||
import { useLanguageSwitch } from "@/composables/useLanguageSwitch.js";
|
||||
|
||||
const { locale, t } = useI18n();
|
||||
const route = useRoute();
|
||||
const { navigateTo, switchLanguage, localePath } = useNavigation();
|
||||
const router = useRouter();
|
||||
const { navigateTo, localePath } = useNavigation();
|
||||
const { switchLanguage, initLanguage } = useLanguageSwitch();
|
||||
|
||||
// 语言显示
|
||||
const langToShow = computed(() => {
|
||||
|
|
@ -23,11 +26,11 @@ const langToShow = computed(() => {
|
|||
// 语言列表
|
||||
const langList = ref([
|
||||
{ name: "简体中文", code: "cn", checked: false },
|
||||
{ name: "English", code: "en", checked: true },
|
||||
{ name: "English", code: "en", checked: false },
|
||||
]);
|
||||
|
||||
// 菜单数据
|
||||
const menuItems = ref([
|
||||
// 菜单数据 - 使用计算属性自动响应语言变化
|
||||
const menuItems = computed(() => [
|
||||
{
|
||||
name: t("nav.home"),
|
||||
enName: "home",
|
||||
|
|
@ -54,41 +57,33 @@ const menuItems = ref([
|
|||
},
|
||||
]);
|
||||
|
||||
// 监听语言变化,更新菜单名称
|
||||
watch(() => locale.value, () => {
|
||||
menuItems.value.forEach(item => {
|
||||
switch (item.enName) {
|
||||
case "home":
|
||||
item.name = t("nav.home");
|
||||
break;
|
||||
case "about":
|
||||
item.name = t("nav.about");
|
||||
break;
|
||||
case "products":
|
||||
item.name = t("nav.products");
|
||||
break;
|
||||
case "contact":
|
||||
item.name = t("nav.contact");
|
||||
break;
|
||||
}
|
||||
});
|
||||
}, { immediate: true });
|
||||
|
||||
// 移动端菜单控制
|
||||
const isMobileMenuOpen = ref(false);
|
||||
const langPopup = ref(false);
|
||||
|
||||
// 语言切换下拉显示控制
|
||||
const showLangDropdown = ref(false);
|
||||
|
||||
// 语言切换防抖
|
||||
const isSwitching = ref(false);
|
||||
|
||||
// 语言切换
|
||||
const chooseLanguage = (lang) => {
|
||||
if (lang.code === locale.value) {
|
||||
langPopup.value = false;
|
||||
const chooseLanguage = async (lang) => {
|
||||
if (lang.code === locale.value || isSwitching.value) {
|
||||
showLangDropdown.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// 使用统一的语言切换方法
|
||||
switchLanguage(lang.code);
|
||||
|
||||
langPopup.value = false;
|
||||
try {
|
||||
isSwitching.value = true;
|
||||
// 使用新的语言切换方法
|
||||
await switchLanguage(lang.code, locale, router);
|
||||
} catch (error) {
|
||||
console.warn('Language switch failed:', error);
|
||||
} finally {
|
||||
isSwitching.value = false;
|
||||
showLangDropdown.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 菜单点击
|
||||
|
|
@ -102,28 +97,29 @@ const handleMenuClick = (item) => {
|
|||
const setActiveMenu = () => {
|
||||
const currentPath = route.path;
|
||||
// 重置所有状态
|
||||
menuItems.value.forEach((item) => {
|
||||
const items = menuItems.value;
|
||||
items.forEach((item) => {
|
||||
item.active = false;
|
||||
});
|
||||
|
||||
// 根据路径设置激活状态
|
||||
if (currentPath === "/" || currentPath === "/cn") {
|
||||
const homeItem = menuItems.value.find((item) => item.enName === "home");
|
||||
const homeItem = items.find((item) => item.enName === "home");
|
||||
if (homeItem) {
|
||||
homeItem.active = true;
|
||||
}
|
||||
} else if (currentPath.includes("/about-us")) {
|
||||
const aboutItem = menuItems.value.find((item) => item.enName === "about");
|
||||
const aboutItem = items.find((item) => item.enName === "about");
|
||||
if (aboutItem) {
|
||||
aboutItem.active = true;
|
||||
}
|
||||
} else if (currentPath.includes("/product")) {
|
||||
const productItem = menuItems.value.find((item) => item.enName === "products");
|
||||
const productItem = items.find((item) => item.enName === "products");
|
||||
if (productItem) {
|
||||
productItem.active = true;
|
||||
}
|
||||
} else if (currentPath.includes("/contact-us")) {
|
||||
const contactItem = menuItems.value.find((item) => item.enName === "contact");
|
||||
const contactItem = items.find((item) => item.enName === "contact");
|
||||
if (contactItem) {
|
||||
contactItem.active = true;
|
||||
}
|
||||
|
|
@ -139,13 +135,13 @@ const toggleMobileMenu = () => {
|
|||
const handleLogoClick = () => {
|
||||
// 使用统一的导航方法跳转到首页
|
||||
navigateTo("/");
|
||||
menuItems.value.forEach((menu) => {
|
||||
menu.active = menu.enName === "home";
|
||||
});
|
||||
isMobileMenuOpen.value = false;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
// 初始化语言
|
||||
initLanguage(locale, router);
|
||||
|
||||
// 更新语言列表选中状态
|
||||
langList.value.forEach((item) => {
|
||||
item.checked = item.code === locale.value;
|
||||
|
|
@ -157,6 +153,13 @@ onMounted(() => {
|
|||
});
|
||||
});
|
||||
|
||||
// 监听语言变化,更新语言列表选中状态
|
||||
watch(() => locale.value, (newLocale) => {
|
||||
langList.value.forEach((item) => {
|
||||
item.checked = item.code === newLocale;
|
||||
});
|
||||
}, { immediate: true });
|
||||
|
||||
// 监听路由变化,更新激活状态
|
||||
watch(
|
||||
() => route.path,
|
||||
|
|
@ -198,39 +201,51 @@ watch(
|
|||
<!-- 语言切换和移动端按钮 -->
|
||||
<div class="navbar-actions">
|
||||
<!-- 语言切换 -->
|
||||
<div class="language-selector" @click="langPopup = !langPopup">
|
||||
<img
|
||||
class="lang-earth-icon"
|
||||
src="/static/home/earch.png"
|
||||
:alt="langToShow"
|
||||
:title="langToShow"
|
||||
/>
|
||||
<span class="lang-text">{{ langToShow }}</span>
|
||||
<svg
|
||||
class="lang-icon"
|
||||
:class="{ rotate: langPopup }"
|
||||
width="12"
|
||||
height="12"
|
||||
viewBox="0 0 12 12"
|
||||
>
|
||||
<path
|
||||
d="M2 4l4 4 4-4"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
fill="none"
|
||||
<div
|
||||
class="language-container"
|
||||
@mouseenter="showLangDropdown = true"
|
||||
@mouseleave="showLangDropdown = false"
|
||||
@click="showLangDropdown = !showLangDropdown"
|
||||
>
|
||||
<div class="language-selector">
|
||||
<img
|
||||
class="lang-earth-icon"
|
||||
src="/static/home/earch.png"
|
||||
:alt="langToShow"
|
||||
:title="langToShow"
|
||||
/>
|
||||
</svg>
|
||||
<span class="lang-text">{{ langToShow }}</span>
|
||||
<svg
|
||||
class="lang-icon"
|
||||
:class="{ rotate: showLangDropdown }"
|
||||
width="12"
|
||||
height="12"
|
||||
viewBox="0 0 12 12"
|
||||
>
|
||||
<path
|
||||
d="M2 4l4 4 4-4"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
fill="none"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- 语言下拉菜单 -->
|
||||
<div class="lang-dropdown" v-show="langPopup">
|
||||
<div
|
||||
class="lang-dropdown"
|
||||
v-show="showLangDropdown"
|
||||
@click.stop
|
||||
>
|
||||
<div
|
||||
v-for="lang in langList"
|
||||
:key="lang.code"
|
||||
class="lang-option"
|
||||
:class="{ active: lang.checked }"
|
||||
@click.stop="chooseLanguage(lang)"
|
||||
:class="{ active: lang.checked, disabled: isSwitching }"
|
||||
@click="chooseLanguage(lang)"
|
||||
>
|
||||
{{ lang.name }}
|
||||
<span v-if="isSwitching && lang.checked" class="loading-dot">...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -291,6 +306,7 @@ watch(
|
|||
z-index: 1000;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
pointer-events: auto;
|
||||
|
||||
&-container {
|
||||
max-width: 1200px;
|
||||
|
|
@ -301,6 +317,7 @@ watch(
|
|||
padding: 0 20px;
|
||||
height: 50px;
|
||||
gap: 60px;
|
||||
pointer-events: auto;
|
||||
|
||||
/* 4K 和超大屏幕 */
|
||||
@media (min-width: 2560px) {
|
||||
|
|
@ -366,6 +383,7 @@ watch(
|
|||
display: flex;
|
||||
align-items: center;
|
||||
gap: 40px;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
&-menu {
|
||||
|
|
@ -380,6 +398,7 @@ watch(
|
|||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
pointer-events: auto;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
gap: 16px;
|
||||
|
|
@ -452,6 +471,12 @@ watch(
|
|||
}
|
||||
}
|
||||
|
||||
// 语言容器
|
||||
.language-container {
|
||||
position: relative;
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
// 语言选择器
|
||||
.language-selector {
|
||||
position: relative;
|
||||
|
|
@ -464,6 +489,8 @@ watch(
|
|||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
z-index: 1001;
|
||||
pointer-events: auto;
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.02);
|
||||
|
|
@ -516,7 +543,7 @@ watch(
|
|||
position: absolute;
|
||||
top: 100%;
|
||||
right: 0;
|
||||
margin-top: 8px;
|
||||
margin-top: 0;
|
||||
background: white;
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 8px;
|
||||
|
|
@ -524,14 +551,21 @@ watch(
|
|||
overflow: hidden;
|
||||
min-width: 140px;
|
||||
z-index: 1002;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.lang-option {
|
||||
padding: 10px 14px;
|
||||
font-size: 12px;
|
||||
color: #000000;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
pointer-events: auto;
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
|
|
@ -543,6 +577,23 @@ watch(
|
|||
color: #000000;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.loading-dot {
|
||||
font-size: 10px;
|
||||
color: #4f8cef;
|
||||
animation: loading 1.5s infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes loading {
|
||||
0%, 20% { opacity: 0; }
|
||||
50% { opacity: 1; }
|
||||
80%, 100% { opacity: 0; }
|
||||
}
|
||||
|
||||
// 移动端菜单按钮
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
import { useLanguageStore } from '@/stores/language.js'
|
||||
|
||||
export const useAppInit = () => {
|
||||
const languageStore = useLanguageStore()
|
||||
|
||||
// 应用初始化
|
||||
const initApp = () => {
|
||||
console.log('App initialization started')
|
||||
|
||||
// 初始化语言
|
||||
languageStore.initLocale()
|
||||
|
||||
console.log('App initialization completed')
|
||||
}
|
||||
|
||||
return {
|
||||
initApp
|
||||
}
|
||||
}
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
// 统一的语言检测逻辑,确保服务器端和客户端一致
|
||||
export const useLanguageDetection = () => {
|
||||
// 检测初始语言的统一逻辑
|
||||
const detectInitialLanguage = () => {
|
||||
// 服务端环境
|
||||
if (typeof window === 'undefined') {
|
||||
return 'en'; // 服务端默认英文,实际语言由中间件设置
|
||||
}
|
||||
|
||||
// 客户端环境
|
||||
try {
|
||||
// 1. 优先使用cookie(与服务器中间件保持一致)
|
||||
const cookies = document.cookie.split(';');
|
||||
const localeCookie = cookies.find(cookie =>
|
||||
cookie.trim().startsWith('i18n_redirected=')
|
||||
);
|
||||
if (localeCookie) {
|
||||
const langValue = localeCookie.split('=')[1];
|
||||
if (['cn', 'en'].includes(langValue)) {
|
||||
return langValue;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 检查内联脚本设置的语言
|
||||
const scriptLang = window.__INITIAL_LANG__ ||
|
||||
document.documentElement.getAttribute('data-initial-lang');
|
||||
if (scriptLang && ['cn', 'en'].includes(scriptLang)) {
|
||||
return scriptLang;
|
||||
}
|
||||
|
||||
// 3. 检查localStorage
|
||||
const storedLang = localStorage.getItem('locale_lang');
|
||||
if (storedLang && ['cn', 'en'].includes(storedLang)) {
|
||||
return storedLang;
|
||||
}
|
||||
|
||||
// 4. 检查浏览器语言(与服务器中间件保持一致)
|
||||
const browserLang = navigator.language.toLowerCase();
|
||||
if (browserLang.startsWith("en")) {
|
||||
return "en";
|
||||
} else if (browserLang.includes("zh") || browserLang.includes("cn")) {
|
||||
return "cn";
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Language detection failed:', e);
|
||||
}
|
||||
|
||||
return 'en'; // 默认英文
|
||||
};
|
||||
|
||||
// 保存语言设置
|
||||
const saveLanguage = (lang) => {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
try {
|
||||
// 保存到Cookie(使用Nuxt i18n的标准键名)
|
||||
document.cookie = 'i18n_redirected=' + lang + '; path=/; max-age=31536000';
|
||||
|
||||
// 保存到localStorage
|
||||
localStorage.setItem('locale_lang', lang);
|
||||
|
||||
// 更新全局变量
|
||||
window.__INITIAL_LANG__ = lang;
|
||||
window.__I18N_INITIAL_LOCALE__ = lang;
|
||||
|
||||
// 更新文档属性
|
||||
document.documentElement.setAttribute('data-initial-lang', lang);
|
||||
document.documentElement.setAttribute('data-locale', lang);
|
||||
document.documentElement.setAttribute('lang', lang === 'cn' ? 'zh-CN' : 'en');
|
||||
} catch (error) {
|
||||
console.warn('Language save failed:', error);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
detectInitialLanguage,
|
||||
saveLanguage
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
import { useLanguageStore } from '@/stores/language.js'
|
||||
|
||||
export const useLanguageSwitch = () => {
|
||||
const languageStore = useLanguageStore()
|
||||
|
||||
// 切换语言 - 简化逻辑,主要依赖 Nuxt i18n 的路由处理
|
||||
const switchLanguage = async (newLocale, locale, router) => {
|
||||
if (!newLocale || newLocale === locale.value) {
|
||||
console.log('Language switch skipped:', { newLocale, current: locale.value })
|
||||
return
|
||||
}
|
||||
|
||||
console.log('Language switch started:', { from: locale.value, to: newLocale })
|
||||
|
||||
try {
|
||||
// 更新 store 中的语言状态
|
||||
languageStore.setLocale(newLocale)
|
||||
console.log('Store updated:', newLocale)
|
||||
|
||||
// 更新 i18n locale
|
||||
locale.value = newLocale
|
||||
console.log('I18n locale updated:', newLocale)
|
||||
|
||||
// 使用 Nuxt i18n 的 switchLocalePath 获取新语言的路径
|
||||
const currentRoute = router.currentRoute.value
|
||||
const currentPath = currentRoute.path
|
||||
console.log('Current path:', currentPath)
|
||||
|
||||
// 构建新语言的路径
|
||||
let newPath = currentPath
|
||||
|
||||
if (newLocale === 'cn') {
|
||||
// 切换到中文,需要添加 /cn 前缀
|
||||
if (!currentPath.startsWith('/cn')) {
|
||||
newPath = `/cn${currentPath === '/' ? '' : currentPath}`
|
||||
}
|
||||
} else {
|
||||
// 切换到英文,需要移除 /cn 前缀
|
||||
if (currentPath.startsWith('/cn')) {
|
||||
newPath = currentPath.replace('/cn', '') || '/'
|
||||
}
|
||||
}
|
||||
|
||||
console.log('New path:', newPath)
|
||||
|
||||
// 跳转到新语言的对应页面
|
||||
if (newPath !== currentPath) {
|
||||
try {
|
||||
await router.push(newPath)
|
||||
console.log('Navigation completed')
|
||||
} catch (error) {
|
||||
console.warn('Language switch navigation failed:', error)
|
||||
// 如果路由跳转失败,至少确保语言状态已更新
|
||||
}
|
||||
} else {
|
||||
console.log('No navigation needed')
|
||||
}
|
||||
|
||||
console.log('Language switch completed successfully')
|
||||
} catch (error) {
|
||||
console.error('Language switch failed:', error)
|
||||
// 出错时回退到默认语言
|
||||
languageStore.setLocale('en')
|
||||
locale.value = 'en'
|
||||
}
|
||||
}
|
||||
|
||||
// 获取当前语言显示名称
|
||||
const getCurrentLanguageName = (locale) => {
|
||||
try {
|
||||
const currentLang = languageStore.availableLocales.find(
|
||||
lang => lang.code === locale.value
|
||||
)
|
||||
return currentLang ? currentLang.name : 'English'
|
||||
} catch (error) {
|
||||
console.warn('Get language name failed:', error)
|
||||
return 'English'
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化语言 - 简化初始化逻辑
|
||||
const initLanguage = (locale, router) => {
|
||||
console.log('Language initialization started')
|
||||
try {
|
||||
// 确保语言 store 已初始化
|
||||
languageStore.initLocale()
|
||||
console.log('Store initialized')
|
||||
|
||||
// 同步 store 和 i18n 的语言状态
|
||||
const storeLocale = languageStore.currentLocale.value
|
||||
console.log('Store locale:', storeLocale, 'I18n locale:', locale.value)
|
||||
|
||||
if (storeLocale && storeLocale !== locale.value) {
|
||||
locale.value = storeLocale
|
||||
console.log('I18n locale synced:', storeLocale)
|
||||
}
|
||||
|
||||
console.log('Language initialization completed')
|
||||
} catch (error) {
|
||||
console.error('Language initialization failed:', error)
|
||||
// 出错时使用默认语言
|
||||
languageStore.setLocale('en')
|
||||
locale.value = 'en'
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
switchLanguage,
|
||||
getCurrentLanguageName,
|
||||
initLanguage,
|
||||
currentLocale: languageStore.currentLocale,
|
||||
availableLocales: languageStore.availableLocales
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import { useRouter } from '#imports'
|
||||
import { useLocalePath, useSwitchLocalePath } from '#i18n'
|
||||
import { useLanguageSwitch } from './useLanguageSwitch.js'
|
||||
|
||||
/**
|
||||
* 统一的导航跳转 composable
|
||||
|
|
@ -9,6 +10,7 @@ export const useNavigation = () => {
|
|||
const router = useRouter()
|
||||
const localePath = useLocalePath()
|
||||
const switchLocalePath = useSwitchLocalePath()
|
||||
const { switchLanguage } = useLanguageSwitch()
|
||||
|
||||
/**
|
||||
* 统一的路由跳转方法
|
||||
|
|
@ -115,10 +117,11 @@ export const useNavigation = () => {
|
|||
/**
|
||||
* 语言切换跳转
|
||||
* @param {string} locale - 目标语言代码
|
||||
* @param {object} i18nLocale - i18n locale 对象
|
||||
*/
|
||||
const switchLanguage = (locale) => {
|
||||
const targetPath = switchLocalePath(locale)
|
||||
router.replace(targetPath)
|
||||
const switchLanguageRoute = (locale, i18nLocale) => {
|
||||
// 使用新的语言切换逻辑
|
||||
switchLanguage(locale, i18nLocale, router)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -139,7 +142,7 @@ export const useNavigation = () => {
|
|||
goContact,
|
||||
goPrivacyPolicy,
|
||||
goProductDetail,
|
||||
switchLanguage,
|
||||
switchLanguage: switchLanguageRoute,
|
||||
getLocalizedPath,
|
||||
localePath,
|
||||
switchLocalePath
|
||||
|
|
|
|||
101
src/hook/lang.js
101
src/hook/lang.js
|
|
@ -1,101 +0,0 @@
|
|||
// export 语言检测 - 改进版,支持SSR,与Nuxt i18n保持一致
|
||||
export default function getLanguage() {
|
||||
// 服务端渲染时的默认语言
|
||||
if (typeof window === 'undefined') {
|
||||
return "en";
|
||||
}
|
||||
|
||||
// 客户端逻辑
|
||||
try {
|
||||
// 1. 优先检查cookie(与Nuxt i18n保持一致)
|
||||
const cookies = document.cookie.split(';');
|
||||
const localeCookie = cookies.find(cookie =>
|
||||
cookie.trim().startsWith('i18n_redirected=')
|
||||
);
|
||||
if (localeCookie) {
|
||||
const langValue = localeCookie.split('=')[1];
|
||||
if (['cn', 'en'].includes(langValue)) {
|
||||
return langValue;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 检查localStorage(兼容旧版本)
|
||||
const langStorage = localStorage.getItem('locale_lang');
|
||||
if (langStorage && ['cn', 'en'].includes(langStorage)) {
|
||||
return langStorage;
|
||||
}
|
||||
|
||||
// 3. 根据浏览器语言判断
|
||||
const browserLang = navigator.language.toLowerCase();
|
||||
if (browserLang.startsWith("en")) {
|
||||
return "en";
|
||||
} else if (browserLang.includes("zh") || browserLang.includes("cn")) {
|
||||
return "cn";
|
||||
}
|
||||
|
||||
return "en"; // 默认英文
|
||||
} catch (error) {
|
||||
console.warn('localStorage access failed:', error);
|
||||
return "en"; // 错误时返回默认语言
|
||||
}
|
||||
}
|
||||
|
||||
// 新增:用于组件中的响应式语言检测
|
||||
export async function useLanguage() {
|
||||
// 检查是否在Nuxt环境中
|
||||
if (typeof window !== 'undefined') {
|
||||
// 客户端环境,使用useCookie
|
||||
try {
|
||||
const { useCookie } = await import('#imports');
|
||||
return useCookie('i18n_redirected', {
|
||||
default: () => 'en',
|
||||
secure: false,
|
||||
sameSite: 'lax'
|
||||
});
|
||||
} catch (e) {
|
||||
// 如果useCookie不可用,回退到普通函数
|
||||
return { value: getLanguage() };
|
||||
}
|
||||
} else {
|
||||
// 服务端环境,回退到普通函数
|
||||
return { value: getLanguage() };
|
||||
}
|
||||
}
|
||||
|
||||
// export 导出防抖函数
|
||||
export const debounce = (func, delay) => {
|
||||
let timerId;
|
||||
return () => {
|
||||
if (timerId) {
|
||||
clearTimeout(timerId);
|
||||
}
|
||||
timerId = setTimeout(() => {
|
||||
func();
|
||||
timerId = null;
|
||||
}, delay);
|
||||
};
|
||||
};
|
||||
|
||||
// 时间戳转换年月日
|
||||
export const formatTimestamp = (timestamp) => {
|
||||
const date = new Date(timestamp);
|
||||
const year = date.getFullYear();
|
||||
const month = date.getMonth() + 1; // getMonth()返回的是0到11,所以要加1
|
||||
const day = date.getDate();
|
||||
// 将月份和日期补零,确保是两位数
|
||||
const formattedMonth = month < 10 ? "0" + month : month;
|
||||
const formattedDay = day < 10 ? "0" + day : day;
|
||||
return `${year}-${formattedMonth}-${formattedDay}`;
|
||||
};
|
||||
|
||||
// 时间戳转换年月日
|
||||
export const formatTimestamp2 = (timestamp) => {
|
||||
const date = new Date(timestamp);
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, "0"); // 使用padStart()方法补零
|
||||
const day = String(date.getDate()).padStart(2, "0");
|
||||
const hours = String(date.getHours()).padStart(2, "0");
|
||||
const minutes = String(date.getMinutes()).padStart(2, "0");
|
||||
|
||||
return `${year}-${month}-${day}-${hours}:${minutes}`;
|
||||
};
|
||||
|
|
@ -8,5 +8,8 @@ export default defineI18nConfig(() => ({
|
|||
messages: {
|
||||
en,
|
||||
cn
|
||||
}
|
||||
},
|
||||
// 移除重复的locale配置,让Nuxt i18n自动管理
|
||||
fallbackWarn: false,
|
||||
missingWarn: false
|
||||
}))
|
||||
|
|
|
|||
|
|
@ -340,14 +340,17 @@
|
|||
"nav.products": "产品系列",
|
||||
"nav.contact": "联系我们",
|
||||
|
||||
"footer.company.name": "中山市明阳良光照明有限公司",
|
||||
"footer.company.description": "太阳能智能云台摄像头制造商",
|
||||
"footer.company.design": "明阳良光原创设计",
|
||||
"footer.wechat": "微信",
|
||||
"footer.whatsapp": "WhatsApp",
|
||||
"footer.address": "地址",
|
||||
"footer.qr.wechat": "微信二维码",
|
||||
"footer.qr.whatsapp": "WhatsApp二维码",
|
||||
"footer.company.name": "中山市明阳良光照明有限公司",
|
||||
"footer.company.description": "太阳能智能云台摄像头制造商",
|
||||
"footer.company.design": "明阳良光原创设计",
|
||||
"footer.wechat": "微信",
|
||||
"footer.whatsapp": "WhatsApp",
|
||||
"footer.address": "地址",
|
||||
"footer.qr.wechat": "微信二维码",
|
||||
"footer.qr.whatsapp": "WhatsApp二维码",
|
||||
"footer.copyright": "版权所有 © 2025 明阳良光",
|
||||
"footer.icp": "粤ICP备2023070569号",
|
||||
"footer.public-security": "粤公网安备 粤ICP备2023070569号",
|
||||
|
||||
"about.exhibition.title": "展会荣誉",
|
||||
"about.exhibition.subtitle": "国际展会参展经历与企业资质认证展示",
|
||||
|
|
|
|||
|
|
@ -347,6 +347,9 @@
|
|||
"footer.address": "Address",
|
||||
"footer.qr.wechat": "WeChat QR Code",
|
||||
"footer.qr.whatsapp": "WhatsApp QR Code",
|
||||
"footer.copyright": "Copyright © 2025 Mingyang Liangguang",
|
||||
"footer.icp": "ICP No. 2023070569",
|
||||
"footer.public-security": "Public Security No. 2023070569",
|
||||
|
||||
"about.exhibition.title": "Exhibition & Honors",
|
||||
"about.exhibition.subtitle": "International exhibition experience and corporate qualification certification display",
|
||||
|
|
@ -381,13 +384,13 @@
|
|||
"about.production.title": "Manufacturing Process",
|
||||
"about.production.subtitle": "Professional production team and strict process flow to ensure product quality meets international standards",
|
||||
"about.production.assembly": "Precision Assembly",
|
||||
"about.production.assembly.desc": "Professional technicians precision assembly",
|
||||
"about.production.assembly.desc": "Professional precision assembly",
|
||||
"about.production.testing": "Quality Testing",
|
||||
"about.production.testing.desc": "Strict testing of every product",
|
||||
"about.production.testing.desc": "Strict product testing",
|
||||
"about.production.debugging": "Product Debugging",
|
||||
"about.production.debugging.desc": "Ensure optimal product performance",
|
||||
"about.production.debugging.desc": "Ensure optimal performance",
|
||||
"about.production.packaging": "Exquisite Packaging",
|
||||
"about.production.packaging.desc": "Protect products for safe transportation",
|
||||
"about.production.packaging.desc": "Safe product packaging",
|
||||
"about.production.quality-control": "Quality Control",
|
||||
"about.production.capacity": "Production Capacity",
|
||||
"about.production.monthly-capacity": "Monthly Capacity (Units)",
|
||||
|
|
|
|||
|
|
@ -14,9 +14,7 @@ const { navigateTo } = useNavigation();
|
|||
// 设置页面标题
|
||||
usePageTitle("page.about-us");
|
||||
|
||||
// 动画控制
|
||||
const animatedElements = ref([]);
|
||||
const isVisible = ref(false);
|
||||
|
||||
|
||||
const goTab = (path) => {
|
||||
navigateTo(path);
|
||||
|
|
|
|||
|
|
@ -13,9 +13,51 @@ import "swiper/css/navigation";
|
|||
import "swiper/css/pagination";
|
||||
|
||||
const router = useRouter();
|
||||
const { t } = useI18n();
|
||||
const { t, locale } = useI18n();
|
||||
const { cdnImageUrl } = useImagePath();
|
||||
|
||||
// 调试信息
|
||||
onMounted(() => {
|
||||
console.log('=== 首页语言状态 ===');
|
||||
console.log('当前语言:', locale.value);
|
||||
console.log('浏览器语言:', navigator.language);
|
||||
console.log('浏览器语言列表:', navigator.languages);
|
||||
|
||||
// 检查Cookie
|
||||
const cookieLanguage = document.cookie
|
||||
.split('; ')
|
||||
.find(row => row.startsWith('i18n_redirected='))
|
||||
?.split('=')[1] || '无';
|
||||
console.log('Cookie 语言:', cookieLanguage);
|
||||
|
||||
// 检查所有Cookie
|
||||
console.log('所有Cookie:', document.cookie);
|
||||
|
||||
// 检查localStorage
|
||||
console.log('localStorage语言:', localStorage.getItem('i18n_redirected'));
|
||||
|
||||
// 分析语言检测逻辑
|
||||
console.log('=== 语言检测分析 ===');
|
||||
console.log('默认语言:', 'en');
|
||||
console.log('可用语言:', ['en', 'cn']);
|
||||
console.log('浏览器语言匹配:', navigator.language.includes('zh') ? '应该匹配中文' : '应该匹配英文');
|
||||
console.log('Cookie应该设置为什么:', navigator.language.includes('zh') ? 'cn' : 'en');
|
||||
|
||||
// 检查当前URL
|
||||
console.log('当前URL:', window.location.href);
|
||||
console.log('当前路径:', window.location.pathname);
|
||||
|
||||
// 添加清除Cookie的方法到window对象,方便测试
|
||||
window.clearLanguageCookie = () => {
|
||||
document.cookie = 'i18n_redirected=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
|
||||
console.log('已清除语言Cookie,请刷新页面重新测试');
|
||||
alert('已清除语言Cookie,请刷新页面重新测试');
|
||||
};
|
||||
|
||||
console.log('=== 测试方法 ===');
|
||||
console.log('在控制台输入: window.clearLanguageCookie() 来清除Cookie');
|
||||
});
|
||||
|
||||
// 设置页面标题
|
||||
usePageTitle("page.home");
|
||||
|
||||
|
|
|
|||
|
|
@ -8,23 +8,20 @@
|
|||
} from "vue-i18n";
|
||||
import zhContent from '~/pages/protocol/components/privacy-policy/zh-page.vue';
|
||||
import enContent from '~/pages/protocol/components/privacy-policy/en-page.vue';
|
||||
import langToCheck from '@/hook/lang.js';
|
||||
import { useLanguageStore } from '@/stores/language.js';
|
||||
const {
|
||||
t
|
||||
} = useI18n();
|
||||
const langIs = ref('')
|
||||
const languageStore = useLanguageStore();
|
||||
const langIs = ref(languageStore.currentLocale)
|
||||
useHead({
|
||||
title: t('foot.privacy-policy'),
|
||||
})
|
||||
onBeforeMount(()=>{
|
||||
langIs.value = langToCheck()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<zh-content v-if="langIs == 'zh-Hans' "></zh-content>
|
||||
<ja-content v-else-if="langIs == 'ja' "></ja-content>
|
||||
<zh-content v-if="langIs == 'cn' "></zh-content>
|
||||
<en-content v-else></en-content>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
GetProductDetailApi
|
||||
} from "@/service/api.js";
|
||||
// 语言判断
|
||||
import langToCheck from "@/hook/lang.js";
|
||||
import { useLanguageStore } from "@/stores/language.js";
|
||||
import {
|
||||
useI18n
|
||||
} from "vue-i18n";
|
||||
|
|
@ -28,10 +28,8 @@
|
|||
t
|
||||
} = useI18n();
|
||||
const route = useRoute();
|
||||
const langIs = ref('');
|
||||
onBeforeMount(()=>{
|
||||
langIs.value = langToCheck()
|
||||
})
|
||||
const languageStore = useLanguageStore();
|
||||
const langIs = ref(languageStore.currentLocale);
|
||||
//监听返回顶部
|
||||
let proId = ref("");
|
||||
let proName = ref("");
|
||||
|
|
|
|||
|
|
@ -13,14 +13,15 @@
|
|||
import {
|
||||
GetProductDetailApi
|
||||
} from "@/service/api.js";
|
||||
import langToCheck from "@/hook/lang.js";
|
||||
import { useLanguageStore } from "@/stores/language.js";
|
||||
import {
|
||||
useI18n
|
||||
} from "vue-i18n";
|
||||
const {
|
||||
t
|
||||
} = useI18n();
|
||||
const langIs = ref('');
|
||||
const languageStore = useLanguageStore();
|
||||
const langIs = ref(languageStore.currentLocale);
|
||||
import {
|
||||
useRoute
|
||||
} from '#imports';
|
||||
|
|
@ -36,7 +37,6 @@
|
|||
|
||||
let tabCollection = reactive([]);
|
||||
onBeforeMount(() => {
|
||||
langIs.value = langToCheck()
|
||||
tabCollection.push({
|
||||
name: t("detail.summary"),
|
||||
value: 0,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
import {
|
||||
useI18n
|
||||
} from "vue-i18n";
|
||||
import langToCheck from "@/hook/lang.js";
|
||||
import { useLanguageStore } from "@/stores/language.js";
|
||||
import { useNavigation } from "@/composables/useNavigation.js";
|
||||
|
||||
const {
|
||||
|
|
@ -20,10 +20,8 @@
|
|||
title: t('contact.thank-message'),
|
||||
})
|
||||
|
||||
const langIs = ref('')
|
||||
onBeforeMount(()=>{
|
||||
langIs.value = langToCheck()
|
||||
})
|
||||
const languageStore = useLanguageStore();
|
||||
const langIs = ref(languageStore.currentLocale)
|
||||
|
||||
const route = useRoute();
|
||||
const { goHome } = useNavigation();
|
||||
|
|
@ -38,7 +36,7 @@
|
|||
<img src="/static/contact-us/complete.webp" alt="Icon">
|
||||
</div>
|
||||
<div class="message">{{t('contact.thank-message')}}</div>
|
||||
<button type="button" @click="BackToMenu">{{$t('contact.back')}}</button>
|
||||
<button type="button" @click="BackToMenu">{{t('contact.back')}}</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -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 已加载,语言插件就绪')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -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()
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
@ -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
|
||||
}
|
||||
})
|
||||
Loading…
Reference in New Issue