feat(i18n): initialize vue-i18n for internationalization support
- Added vue-i18n as a dependency. - Configured vue-i18n in the main application file. - Created initial locale files with translations.
This commit is contained in:
parent
9d808b4ccd
commit
a50e356f42
1
ui/env.d.ts
vendored
1
ui/env.d.ts
vendored
@ -11,3 +11,4 @@ declare module 'katex'
|
|||||||
interface ImportMeta {
|
interface ImportMeta {
|
||||||
readonly env: ImportMetaEnv
|
readonly env: ImportMetaEnv
|
||||||
}
|
}
|
||||||
|
declare type Recordable<T = any> = Record<string, T>;
|
||||||
|
|||||||
@ -641,6 +641,25 @@ export const iconMap: any = {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
'app-translate': {
|
||||||
|
iconReader: () => {
|
||||||
|
return h('svg', {
|
||||||
|
xmlns: "http://www.w3.org/2000/svg",
|
||||||
|
viewBox: "0 0 20 20",
|
||||||
|
fill: "currentColor",
|
||||||
|
class: "w-5 h-5"
|
||||||
|
}, [
|
||||||
|
h('path', {
|
||||||
|
d: "M7.75 2.75a.75.75 0 0 0-1.5 0v1.258a32.987 32.987 0 0 0-3.599.278.75.75 0 1 0 .198 1.487A31.545 31.545 0 0 1 8.7 5.545 19.381 19.381 0 0 1 7 9.56a19.418 19.418 0 0 1-1.002-2.05.75.75 0 0 0-1.384.577 20.935 20.935 0 0 0 1.492 2.91 19.613 19.613 0 0 1-3.828 4.154.75.75 0 1 0 .945 1.164A21.116 21.116 0 0 0 7 12.331c.095.132.192.262.29.391a.75.75 0 0 0 1.194-.91c-.204-.266-.4-.538-.59-.815a20.888 20.888 0 0 0 2.333-5.332c.31.031.618.068.924.108a.75.75 0 0 0 .198-1.487 32.832 32.832 0 0 0-3.599-.278V2.75Z"
|
||||||
|
}),
|
||||||
|
h('path', {
|
||||||
|
"fill-rule": "evenodd",
|
||||||
|
d: "M13 8a.75.75 0 0 1 .671.415l4.25 8.5a.75.75 0 1 1-1.342.67L15.787 16h-5.573l-.793 1.585a.75.75 0 1 1-1.342-.67l4.25-8.5A.75.75 0 0 1 13 8Zm2.037 6.5L13 10.427 10.964 14.5h4.073Z",
|
||||||
|
"clip-rule": "evenodd"
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
},
|
||||||
'app-user': {
|
'app-user': {
|
||||||
iconReader: () => {
|
iconReader: () => {
|
||||||
return h('i', [
|
return h('i', [
|
||||||
|
|||||||
@ -11,30 +11,28 @@
|
|||||||
<TopMenu></TopMenu>
|
<TopMenu></TopMenu>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-center avatar">
|
<div class="flex-center avatar">
|
||||||
<el-tooltip effect="dark" content="项目地址" placement="top">
|
<el-tooltip effect="dark" :content="$t('layout.topbar.github')" placement="top">
|
||||||
<AppIcon
|
<AppIcon iconName="app-github" class="cursor color-secondary mr-8 ml-8" style="font-size: 20px"
|
||||||
iconName="app-github"
|
@click="toUrl('https://github.com/1Panel-dev/MaxKB')"></AppIcon>
|
||||||
class="cursor color-secondary mr-8 ml-8"
|
|
||||||
style="font-size: 20px"
|
|
||||||
@click="toUrl('https://github.com/1Panel-dev/MaxKB')"
|
|
||||||
></AppIcon>
|
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-tooltip effect="dark" content="用户手册" placement="top">
|
<el-tooltip effect="dark" :content="$t('layout.topbar.handbook')" placement="top">
|
||||||
<AppIcon
|
<AppIcon iconName="app-reading" class="cursor color-secondary mr-8 ml-8" style="font-size: 20px"
|
||||||
iconName="app-reading"
|
@click="toUrl('https://github.com/1Panel-dev/MaxKB/wiki')"></AppIcon>
|
||||||
class="cursor color-secondary mr-8 ml-8"
|
|
||||||
style="font-size: 20px"
|
|
||||||
@click="toUrl('https://github.com/1Panel-dev/MaxKB/wiki')"
|
|
||||||
></AppIcon>
|
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-tooltip effect="dark" content="论坛求助" placement="top">
|
<el-tooltip effect="dark" :content="$t('layout.topbar.forum')" placement="top">
|
||||||
<AppIcon
|
<AppIcon iconName="app-help" class="cursor color-secondary mr-8 ml-8" style="font-size: 20px"
|
||||||
iconName="app-help"
|
@click="toUrl('https://bbs.fit2cloud.com/c/mk/11')"></AppIcon>
|
||||||
class="cursor color-secondary mr-16 ml-8"
|
|
||||||
style="font-size: 20px"
|
|
||||||
@click="toUrl('https://bbs.fit2cloud.com/c/mk/11')"
|
|
||||||
></AppIcon>
|
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
<el-dropdown trigger="click" type="primary">
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu>
|
||||||
|
<el-dropdown-item v-for="(lang, index) in langList" :key="index" :value="lang.value"
|
||||||
|
@click="changeLang(lang.value)">{{ lang.label }}</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
<AppIcon iconName="app-translate" class="cursor color-secondary mr-16 ml-8" style="font-size: 20px" @click="">
|
||||||
|
</AppIcon>
|
||||||
|
</el-dropdown>
|
||||||
<Avatar></Avatar>
|
<Avatar></Avatar>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -43,9 +41,15 @@
|
|||||||
import TopMenu from './top-menu/index.vue'
|
import TopMenu from './top-menu/index.vue'
|
||||||
import Avatar from './avatar/index.vue'
|
import Avatar from './avatar/index.vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
import { langList } from '@/locales/index';
|
||||||
|
import { useLocale } from '@/locales/useLocale';
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const defaultTitle = import.meta.env.VITE_APP_TITLE
|
const defaultTitle = import.meta.env.VITE_APP_TITLE
|
||||||
|
|
||||||
|
const { changeLocale } = useLocale();
|
||||||
|
const changeLang = (lang: string) => {
|
||||||
|
changeLocale(lang);
|
||||||
|
};
|
||||||
function toUrl(url: string) {
|
function toUrl(url: string) {
|
||||||
window.open(url, '_blank')
|
window.open(url, '_blank')
|
||||||
}
|
}
|
||||||
@ -58,6 +62,7 @@ function toUrl(url: string) {
|
|||||||
|
|
||||||
.app-title-container {
|
.app-title-container {
|
||||||
margin-right: 45px;
|
margin-right: 45px;
|
||||||
|
|
||||||
.app-title-icon {
|
.app-title-icon {
|
||||||
background-image: url('@/assets/logo.png');
|
background-image: url('@/assets/logo.png');
|
||||||
background-size: 100% 100%;
|
background-size: 100% 100%;
|
||||||
@ -69,6 +74,7 @@ function toUrl(url: string) {
|
|||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.line {
|
.line {
|
||||||
height: 2em;
|
height: 2em;
|
||||||
}
|
}
|
||||||
|
|||||||
66
ui/src/locales/index.ts
Normal file
66
ui/src/locales/index.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import { useLocalStorage, usePreferredLanguages } from '@vueuse/core';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { createI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
// 导入语言文件
|
||||||
|
const langModules = import.meta.glob('./lang/*/index.ts', { eager: true }) as Record<string, () => Promise<{ default: Object }>>;
|
||||||
|
|
||||||
|
const langModuleMap = new Map<string, Object>();
|
||||||
|
|
||||||
|
export const langCode: Array<string> = [];
|
||||||
|
|
||||||
|
export const localeConfigKey = 'MaxKB-locale';
|
||||||
|
|
||||||
|
// 获取浏览器默认语言环境
|
||||||
|
const languages = usePreferredLanguages();
|
||||||
|
|
||||||
|
// 生成语言模块列表
|
||||||
|
const generateLangModuleMap = () => {
|
||||||
|
const fullPaths = Object.keys(langModules);
|
||||||
|
fullPaths.forEach((fullPath) => {
|
||||||
|
const k = fullPath.replace('./lang', '');
|
||||||
|
const startIndex = 1;
|
||||||
|
const lastIndex = k.lastIndexOf('/');
|
||||||
|
const code = k.substring(startIndex, lastIndex);
|
||||||
|
langCode.push(code);
|
||||||
|
langModuleMap.set(code, langModules[fullPath]);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 导出 Message
|
||||||
|
const importMessages = computed(() => {
|
||||||
|
generateLangModuleMap();
|
||||||
|
|
||||||
|
const message: Recordable = {};
|
||||||
|
langModuleMap.forEach((value: any, key) => {
|
||||||
|
message[key] = value.default;
|
||||||
|
});
|
||||||
|
return message;
|
||||||
|
});
|
||||||
|
|
||||||
|
export const i18n = createI18n({
|
||||||
|
legacy: false,
|
||||||
|
locale: useLocalStorage(localeConfigKey, 'zh_CN').value || languages.value[0] || 'zh_CN',
|
||||||
|
fallbackLocale: 'zh_CN',
|
||||||
|
messages: importMessages.value,
|
||||||
|
globalInjection: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const langList = computed(() => {
|
||||||
|
if (langModuleMap.size === 0) generateLangModuleMap();
|
||||||
|
|
||||||
|
const list:any=[]
|
||||||
|
langModuleMap.forEach((value: any, key) => {
|
||||||
|
list.push({
|
||||||
|
label: value.default.lang,
|
||||||
|
value: key,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return list;
|
||||||
|
});
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
export const { t } = i18n.global;
|
||||||
|
|
||||||
|
export default i18n;
|
||||||
4
ui/src/locales/lang/en_US/components/index.ts
Normal file
4
ui/src/locales/lang/en_US/components/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
export default {
|
||||||
|
|
||||||
|
};
|
||||||
12
ui/src/locales/lang/en_US/index.ts
Normal file
12
ui/src/locales/lang/en_US/index.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import en from 'element-plus/es/locale/lang/en';
|
||||||
|
import components from './components';
|
||||||
|
import layout from './layout';
|
||||||
|
import pages from './pages';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
lang: 'English',
|
||||||
|
layout,
|
||||||
|
pages,
|
||||||
|
components,
|
||||||
|
en,
|
||||||
|
};
|
||||||
7
ui/src/locales/lang/en_US/layout.ts
Normal file
7
ui/src/locales/lang/en_US/layout.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export default {
|
||||||
|
topbar: {
|
||||||
|
github:"Github",
|
||||||
|
handbook:"Handbook",
|
||||||
|
forum:"Forum"
|
||||||
|
},
|
||||||
|
};
|
||||||
4
ui/src/locales/lang/en_US/pages/index.ts
Normal file
4
ui/src/locales/lang/en_US/pages/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
export default {
|
||||||
|
|
||||||
|
};
|
||||||
4
ui/src/locales/lang/zh_CN/components/index.ts
Normal file
4
ui/src/locales/lang/zh_CN/components/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
export default {
|
||||||
|
|
||||||
|
};
|
||||||
12
ui/src/locales/lang/zh_CN/index.ts
Normal file
12
ui/src/locales/lang/zh_CN/index.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import zhCn from 'element-plus/es/locale/lang/zh-cn';
|
||||||
|
import components from './components';
|
||||||
|
import layout from './layout';
|
||||||
|
import pages from './pages';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
lang: '简体中文',
|
||||||
|
layout,
|
||||||
|
pages,
|
||||||
|
components,
|
||||||
|
zhCn,
|
||||||
|
};
|
||||||
7
ui/src/locales/lang/zh_CN/layout.ts
Normal file
7
ui/src/locales/lang/zh_CN/layout.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export default {
|
||||||
|
topbar: {
|
||||||
|
github:"项目地址",
|
||||||
|
handbook:"用户手册",
|
||||||
|
forum:"论坛求助"
|
||||||
|
},
|
||||||
|
};
|
||||||
4
ui/src/locales/lang/zh_CN/pages/index.ts
Normal file
4
ui/src/locales/lang/zh_CN/pages/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
export default {
|
||||||
|
|
||||||
|
};
|
||||||
28
ui/src/locales/useLocale.ts
Normal file
28
ui/src/locales/useLocale.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { useLocalStorage } from '@vueuse/core';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
import { i18n, langCode, localeConfigKey } from '@/locales/index';
|
||||||
|
|
||||||
|
export function useLocale() {
|
||||||
|
const { locale } = useI18n({ useScope: 'global' });
|
||||||
|
function changeLocale(lang: string) {
|
||||||
|
// 如果切换的语言不在对应语言文件里则默认为简体中文
|
||||||
|
if (!langCode.includes(lang)) {
|
||||||
|
lang = 'zh_CN';
|
||||||
|
}
|
||||||
|
|
||||||
|
locale.value = lang;
|
||||||
|
useLocalStorage(localeConfigKey, 'zh_CN').value = lang;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getComponentsLocale = computed(() => {
|
||||||
|
return i18n.global.getLocaleMessage(locale.value).componentsLocale;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
changeLocale,
|
||||||
|
getComponentsLocale,
|
||||||
|
locale,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -9,7 +9,7 @@ import directives from '@/directives'
|
|||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import router from '@/router'
|
import router from '@/router'
|
||||||
import Components from '@/components'
|
import Components from '@/components'
|
||||||
|
import i18n from './locales';
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
app.use(store)
|
app.use(store)
|
||||||
app.use(directives)
|
app.use(directives)
|
||||||
@ -24,5 +24,6 @@ app.use(ElementPlus, {
|
|||||||
app.use(theme)
|
app.use(theme)
|
||||||
|
|
||||||
app.use(router)
|
app.use(router)
|
||||||
|
app.use(i18n);
|
||||||
app.use(Components)
|
app.use(Components)
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user