多语言
This commit is contained in:
parent
2441594f1c
commit
935d764bb5
|
|
@ -7,7 +7,7 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import {useI18n} from 'vue-i18n'
|
import {useI18n} from 'vue-i18n'
|
||||||
import {computed, ref} from 'vue'
|
import {computed, ref} from 'vue'
|
||||||
import tools from "@/utils/tools.js";
|
import tools from "@/utils/tools"
|
||||||
import colorTool from '@/utils/color'
|
import colorTool from '@/utils/color'
|
||||||
|
|
||||||
const {locale, messages} = useI18n()
|
const {locale, messages} = useI18n()
|
||||||
|
|
|
||||||
|
|
@ -231,5 +231,8 @@ export default {
|
||||||
del: async function (data = {}) {
|
del: async function (data = {}) {
|
||||||
return await http.delete("translation/del", data);
|
return await http.delete("translation/del", data);
|
||||||
},
|
},
|
||||||
|
load: async function (data = {}) {
|
||||||
|
return await http.get("translations", data);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,27 @@
|
||||||
<template>
|
<template>
|
||||||
<el-form ref="form" label-width="120px" label-position="left" style="padding:0 20px;">
|
<el-form ref="form" label-width="120px" label-position="left" style="padding:0 20px;">
|
||||||
<el-divider>{{t('user.thememode')}}</el-divider>
|
<el-divider>{{ t('user.thememode') }}</el-divider>
|
||||||
<el-row class="pi-theme" :gutter="20">
|
<el-row class="pi-theme" :gutter="20">
|
||||||
<el-col :span="8"><img src="/images/light.png" class="pi-pic" :class="{'active': dark == 'light'}" @click="themeClick('light')"/><el-text type="info" class="pi-text">浅色</el-text></el-col>
|
<el-col :span="8"><img src="/images/light.png" class="pi-pic" :class="{'active': dark == 'light'}"
|
||||||
<el-col :span="8"><img src="/images/dark.png" class="pi-pic" :class="{'active': dark == 'dark'}" @click="themeClick('dark')"/><el-text type="info" class="pi-text">深色</el-text></el-col>
|
@click="themeClick('light')"/>
|
||||||
<el-col :span="8"><img src="/images/follow.png" class="pi-pic" :class="{'active': dark == 'follow'}" @click="themeClick('follow')"/><el-text type="info" class="pi-text">跟随系统</el-text></el-col>
|
<el-text type="info" class="pi-text">浅色</el-text>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8"><img src="/images/dark.png" class="pi-pic" :class="{'active': dark == 'dark'}"
|
||||||
|
@click="themeClick('dark')"/>
|
||||||
|
<el-text type="info" class="pi-text">深色</el-text>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8"><img src="/images/follow.png" class="pi-pic" :class="{'active': dark == 'follow'}"
|
||||||
|
@click="themeClick('follow')"/>
|
||||||
|
<el-text type="info" class="pi-text">跟随系统</el-text>
|
||||||
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-divider></el-divider>
|
<el-divider></el-divider>
|
||||||
<el-form-item :label="t('user.language')">
|
<el-form-item :label="t('user.language')">
|
||||||
<el-select v-model="lang">
|
<el-select v-model="lang">
|
||||||
<el-option label="简体中文" value="zh-cn"></el-option>
|
<el-option label="简体中文" value="zh-cn"></el-option>
|
||||||
<el-option label="English" value="en"></el-option>
|
<el-option label="English" value="en"></el-option>
|
||||||
|
<el-option label="日本語" value="ja"></el-option>
|
||||||
|
<el-option label="Tiếng Việt" value="vi"></el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-divider></el-divider>
|
<el-divider></el-divider>
|
||||||
|
|
@ -46,9 +57,10 @@ import tools from "@/utils/tools";
|
||||||
import config from "@/config";
|
import config from "@/config";
|
||||||
import globalStore from "@/store/global.js";
|
import globalStore from "@/store/global.js";
|
||||||
import {useI18n} from "vue-i18n";
|
import {useI18n} from "vue-i18n";
|
||||||
|
import {setupI18n} from "@/locales/setup.js";
|
||||||
|
|
||||||
const global = globalStore()
|
const global = globalStore()
|
||||||
const {t, locale} = useI18n()
|
const {t} = useI18n()
|
||||||
|
|
||||||
let layout = ref(tools.data.get('APP_LAYOUT') || global.layout);
|
let layout = ref(tools.data.get('APP_LAYOUT') || global.layout);
|
||||||
let menuIsCollapse = ref(global.menuIsCollapse);
|
let menuIsCollapse = ref(global.menuIsCollapse);
|
||||||
|
|
@ -83,8 +95,8 @@ watch(weakMode, (val) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(lang, (val) => {
|
watch(lang, (val) => {
|
||||||
locale.value = val
|
setupI18n(val)
|
||||||
tools.data.set("APP_LANG", val);
|
tools.data.set("APP_LANG", val)
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(colorPrimary, (val) => {
|
watch(colorPrimary, (val) => {
|
||||||
|
|
@ -106,14 +118,14 @@ function themeClick(val) {
|
||||||
if (val == 'dark') {
|
if (val == 'dark') {
|
||||||
document.documentElement.classList.add("dark")
|
document.documentElement.classList.add("dark")
|
||||||
localStorage.setItem("APP_DARK", val)
|
localStorage.setItem("APP_DARK", val)
|
||||||
} else if(val == 'light') {
|
} else if (val == 'light') {
|
||||||
document.documentElement.classList.remove("dark")
|
document.documentElement.classList.remove("dark")
|
||||||
localStorage.setItem("APP_DARK", val)
|
localStorage.setItem("APP_DARK", val)
|
||||||
} else {
|
} else {
|
||||||
const systemTheme = window.matchMedia('(prefers-color-scheme: dark)')
|
const systemTheme = window.matchMedia('(prefers-color-scheme: dark)')
|
||||||
if (systemTheme.matches) {
|
if (systemTheme.matches) {
|
||||||
document.documentElement.classList.add("dark")
|
document.documentElement.classList.add("dark")
|
||||||
}else {
|
} else {
|
||||||
document.documentElement.classList.remove("dark")
|
document.documentElement.classList.remove("dark")
|
||||||
}
|
}
|
||||||
localStorage.setItem("APP_DARK", val)
|
localStorage.setItem("APP_DARK", val)
|
||||||
|
|
@ -123,8 +135,25 @@ function themeClick(val) {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.pi-theme {text-align: center;}
|
.pi-theme {
|
||||||
.pi-theme .pi-pic {cursor: pointer;margin-bottom: 6px;border-radius: 8px; box-shadow: 0 2px 8px #0003;width: 100px;}
|
text-align: center;
|
||||||
.pi-theme .pi-pic.active {border: 2px solid var(--el-color-primary); cursor: pointer;}
|
}
|
||||||
.pi-theme .pi-text {margin-top: 6px; cursor: pointer;}
|
|
||||||
|
.pi-theme .pi-pic {
|
||||||
|
cursor: pointer;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 8px #0003;
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pi-theme .pi-pic.active {
|
||||||
|
border: 2px solid var(--el-color-primary);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pi-theme .pi-text {
|
||||||
|
margin-top: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,12 @@
|
||||||
import { createI18n } from 'vue-i18n'
|
import {createI18n} from 'vue-i18n'
|
||||||
import el_zh_cn from 'element-plus/dist/locale/zh-cn'
|
|
||||||
import el_en from 'element-plus/dist/locale/en'
|
|
||||||
|
|
||||||
import config from "@/config"
|
import config from "@/config"
|
||||||
import tools from '@/utils/tools'
|
|
||||||
import zh_cn from '@/locales/lang/zh-cn'
|
|
||||||
import en from '@/locales/lang/en'
|
|
||||||
|
|
||||||
const messages = {
|
|
||||||
'zh-cn': {
|
|
||||||
...el_zh_cn,
|
|
||||||
...zh_cn
|
|
||||||
},
|
|
||||||
'en': {
|
|
||||||
...el_en,
|
|
||||||
...en
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const i18n = createI18n({
|
const i18n = createI18n({
|
||||||
legacy: false,
|
legacy: false,
|
||||||
fallbackLocale: 'zh-cn',
|
fallbackLocale: config.LANG,
|
||||||
locale: tools.data.get("APP_LANG") || config.LANG,
|
locale: config.LANG,
|
||||||
globalInjection: true,
|
globalInjection: true,
|
||||||
messages,
|
messages: {}
|
||||||
})
|
})
|
||||||
|
|
||||||
export default i18n;
|
export default i18n;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export default {}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export default {}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export default {}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
import el_zh_cn from "element-plus/dist/locale/zh-cn";
|
||||||
|
import el_en from "element-plus/dist/locale/en";
|
||||||
|
import el_vi from "element-plus/dist/locale/vi";
|
||||||
|
import el_ms from "element-plus/dist/locale/ms";
|
||||||
|
import el_ja from "element-plus/dist/locale/ja";
|
||||||
|
|
||||||
|
import lang_zh_cn from "@/locales/lang/zh-cn";
|
||||||
|
import lang_en from "@/locales/lang/en";
|
||||||
|
import lang_ja from "@/locales/lang/ja";
|
||||||
|
import lang_vi from "@/locales/lang/vi";
|
||||||
|
import lang_ms from "@/locales/lang/ms";
|
||||||
|
|
||||||
|
export const LANGUAGE_MAP: Record<
|
||||||
|
string,
|
||||||
|
{ el: any; local: any }
|
||||||
|
> = {
|
||||||
|
"zh-cn": {el: el_zh_cn, local: lang_zh_cn},
|
||||||
|
en: {el: el_en, local: lang_en},
|
||||||
|
ja: {el: el_ja, local: lang_ja},
|
||||||
|
vi: {el: el_vi, local: lang_vi},
|
||||||
|
ms: {el: el_ms, local: lang_ms},
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
import i18n from "@/locales/index";
|
||||||
|
import config from "@/config";
|
||||||
|
import tools from "@/utils/tools";
|
||||||
|
import api from "@/api";
|
||||||
|
import {LANGUAGE_MAP} from "@/locales/languages";
|
||||||
|
|
||||||
|
export async function setupI18n(locale: string = null) {
|
||||||
|
locale = locale || tools.data.get("APP_LANG") || config.LANG;
|
||||||
|
const langConfig = LANGUAGE_MAP[locale];
|
||||||
|
// 先从缓存中取
|
||||||
|
var messages = tools.data.get("LOCALE:" + locale)
|
||||||
|
if (!messages) {
|
||||||
|
const res = await api.system.translation.load({locale});
|
||||||
|
messages = tools.deepMerge(langConfig.el, langConfig.local, res['data'] || {})
|
||||||
|
tools.data.set("LOCALE:" + locale, messages, 86400)
|
||||||
|
}
|
||||||
|
// 设置语言包内容
|
||||||
|
i18n.global.setLocaleMessage(locale, messages);
|
||||||
|
// 切换语言
|
||||||
|
i18n.global.locale.value = locale;
|
||||||
|
return i18n;
|
||||||
|
}
|
||||||
28
src/main.ts
28
src/main.ts
|
|
@ -1,21 +1,29 @@
|
||||||
import { createApp } from 'vue'
|
import {createApp} from 'vue'
|
||||||
import ElementPlus from 'element-plus'
|
import ElementPlus from 'element-plus'
|
||||||
import 'element-plus/dist/index.css'
|
import 'element-plus/dist/index.css'
|
||||||
import 'element-plus/theme-chalk/display.css'
|
import 'element-plus/theme-chalk/display.css'
|
||||||
import i18n from './locales'
|
import i18n from './locales'
|
||||||
|
import {setupI18n} from "@/locales/setup"
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
|
|
||||||
import pinia from './store'
|
import pinia from './store'
|
||||||
import router from './router'
|
import router from './router'
|
||||||
import pi from './pi'
|
import pi from './pi'
|
||||||
|
|
||||||
const app = createApp(App);
|
async function bootstrap() {
|
||||||
|
await setupI18n();
|
||||||
|
const app = createApp(App);
|
||||||
|
|
||||||
|
app.use(pinia);
|
||||||
|
app.use(router);
|
||||||
|
app.use(ElementPlus);
|
||||||
|
app.use(i18n);
|
||||||
|
app.use(pi);
|
||||||
|
|
||||||
|
//挂载app
|
||||||
|
app.mount('#app');
|
||||||
|
}
|
||||||
|
|
||||||
|
bootstrap()
|
||||||
|
|
||||||
|
|
||||||
app.use(pinia);
|
|
||||||
app.use(router);
|
|
||||||
app.use(ElementPlus);
|
|
||||||
app.use(i18n);
|
|
||||||
app.use(pi);
|
|
||||||
|
|
||||||
//挂载app
|
|
||||||
app.mount('#app');
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,9 @@ axios.interceptors.request.use((config) => {
|
||||||
config.params = config.params || {};
|
config.params = config.params || {};
|
||||||
config.params['_'] = new Date().getTime();
|
config.params['_'] = new Date().getTime();
|
||||||
}
|
}
|
||||||
|
// 多语言
|
||||||
|
const lang = tools.data.get("APP_LANG") || 'zh-cn'
|
||||||
|
config.headers['Accept-Language'] = lang
|
||||||
return config;
|
return config;
|
||||||
})
|
})
|
||||||
//响应拦截
|
//响应拦截
|
||||||
|
|
|
||||||
|
|
@ -214,6 +214,28 @@ const tools = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
// 深度合并数据
|
||||||
|
deepMerge: (...objects: any[]) => {
|
||||||
|
const result: any = {}
|
||||||
|
for (const obj of objects) {
|
||||||
|
tools.mergeObject(result, obj)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
},
|
||||||
|
mergeObject: (target: any, source: any) => {
|
||||||
|
if (!source || typeof source !== 'object') return
|
||||||
|
for (const key in source) {
|
||||||
|
const value = source[key]
|
||||||
|
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
||||||
|
if (!target[key] || typeof target[key] !== 'object') {
|
||||||
|
target[key] = {}
|
||||||
|
}
|
||||||
|
tools.mergeObject(target[key], value)
|
||||||
|
} else {
|
||||||
|
target[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export default tools
|
export default tools
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,7 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import {getCurrentInstance, ref, onMounted, watch} from 'vue'
|
import {getCurrentInstance, ref, onMounted, watch} from 'vue'
|
||||||
import {useI18n} from 'vue-i18n'
|
import {useI18n} from 'vue-i18n'
|
||||||
|
import {setupI18n} from "@/locales/setup"
|
||||||
import {useRouter, useRoute} from 'vue-router'
|
import {useRouter, useRoute} from 'vue-router'
|
||||||
import tools from "@/utils/tools";
|
import tools from "@/utils/tools";
|
||||||
import sysConfig from "@/config/index"
|
import sysConfig from "@/config/index"
|
||||||
|
|
@ -81,7 +82,7 @@ defineOptions({
|
||||||
})
|
})
|
||||||
|
|
||||||
const {proxy} = getCurrentInstance()
|
const {proxy} = getCurrentInstance()
|
||||||
const {t, locale} = useI18n()
|
const {t} = useI18n()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
import bg from '@/assets/images/bg.jpg'
|
import bg from '@/assets/images/bg.jpg'
|
||||||
|
|
@ -98,6 +99,14 @@ const langs = ref([
|
||||||
{
|
{
|
||||||
name: 'English',
|
name: 'English',
|
||||||
value: 'en',
|
value: 'en',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '日本語',
|
||||||
|
value: 'ja',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Tiếng Việt',
|
||||||
|
value: 'vi',
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
@ -140,7 +149,7 @@ onMounted(() => {
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(lang, (val) => {
|
watch(lang, (val) => {
|
||||||
locale.value = val
|
setupI18n(val)
|
||||||
tools.data.set("APP_LANG", val)
|
tools.data.set("APP_LANG", val)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue