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:
tongque 2024-04-27 17:24:53 +08:00
parent 9d808b4ccd
commit a50e356f42
14 changed files with 197 additions and 22 deletions

1
ui/env.d.ts vendored
View File

@ -11,3 +11,4 @@ declare module 'katex'
interface ImportMeta {
readonly env: ImportMetaEnv
}
declare type Recordable<T = any> = Record<string, T>;

View File

@ -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': {
iconReader: () => {
return h('i', [

View File

@ -11,30 +11,28 @@
<TopMenu></TopMenu>
</div>
<div class="flex-center avatar">
<el-tooltip effect="dark" content="项目地址" placement="top">
<AppIcon
iconName="app-github"
class="cursor color-secondary mr-8 ml-8"
style="font-size: 20px"
@click="toUrl('https://github.com/1Panel-dev/MaxKB')"
></AppIcon>
<el-tooltip effect="dark" :content="$t('layout.topbar.github')" placement="top">
<AppIcon iconName="app-github" 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 effect="dark" content="用户手册" placement="top">
<AppIcon
iconName="app-reading"
class="cursor color-secondary mr-8 ml-8"
style="font-size: 20px"
@click="toUrl('https://github.com/1Panel-dev/MaxKB/wiki')"
></AppIcon>
<el-tooltip effect="dark" :content="$t('layout.topbar.handbook')" placement="top">
<AppIcon iconName="app-reading" 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 effect="dark" content="论坛求助" placement="top">
<AppIcon
iconName="app-help"
class="cursor color-secondary mr-16 ml-8"
style="font-size: 20px"
@click="toUrl('https://bbs.fit2cloud.com/c/mk/11')"
></AppIcon>
<el-tooltip effect="dark" :content="$t('layout.topbar.forum')" placement="top">
<AppIcon iconName="app-help" class="cursor color-secondary mr-8 ml-8" style="font-size: 20px"
@click="toUrl('https://bbs.fit2cloud.com/c/mk/11')"></AppIcon>
</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>
</div>
</div>
@ -43,9 +41,15 @@
import TopMenu from './top-menu/index.vue'
import Avatar from './avatar/index.vue'
import { useRouter } from 'vue-router'
import { langList } from '@/locales/index';
import { useLocale } from '@/locales/useLocale';
const router = useRouter()
const defaultTitle = import.meta.env.VITE_APP_TITLE
const { changeLocale } = useLocale();
const changeLang = (lang: string) => {
changeLocale(lang);
};
function toUrl(url: string) {
window.open(url, '_blank')
}
@ -58,6 +62,7 @@ function toUrl(url: string) {
.app-title-container {
margin-right: 45px;
.app-title-icon {
background-image: url('@/assets/logo.png');
background-size: 100% 100%;
@ -69,6 +74,7 @@ function toUrl(url: string) {
font-size: 24px;
}
}
.line {
height: 2em;
}

66
ui/src/locales/index.ts Normal file
View 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;

View File

@ -0,0 +1,4 @@
export default {
};

View 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,
};

View File

@ -0,0 +1,7 @@
export default {
topbar: {
github:"Github",
handbook:"Handbook",
forum:"Forum"
},
};

View File

@ -0,0 +1,4 @@
export default {
};

View File

@ -0,0 +1,4 @@
export default {
};

View 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,
};

View File

@ -0,0 +1,7 @@
export default {
topbar: {
github:"项目地址",
handbook:"用户手册",
forum:"论坛求助"
},
};

View File

@ -0,0 +1,4 @@
export default {
};

View 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,
};
}

View File

@ -9,7 +9,7 @@ import directives from '@/directives'
import App from './App.vue'
import router from '@/router'
import Components from '@/components'
import i18n from './locales';
const app = createApp(App)
app.use(store)
app.use(directives)
@ -24,5 +24,6 @@ app.use(ElementPlus, {
app.use(theme)
app.use(router)
app.use(i18n);
app.use(Components)
app.mount('#app')