feat: 国际化i18n

This commit is contained in:
wang_fan_w 2024-04-27 19:18:49 +08:00
parent c9c51599d3
commit 6ba10b1a2f
23 changed files with 235 additions and 40 deletions

View File

@ -27,6 +27,7 @@
"pinia": "^2.1.7",
"pinia-plugin-persist": "^1.0.0",
"vue": "^3.4.21",
"vue-i18n": "10.0.0-alpha.2",
"vue-router": "^4.3.0"
},
"devDependencies": {

44
pnpm-lock.yaml generated
View File

@ -1,5 +1,9 @@
lockfileVersion: '6.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
dependencies:
dc-admin:
specifier: 'link:'
@ -16,6 +20,9 @@ dependencies:
vue:
specifier: ^3.4.21
version: 3.4.21(typescript@5.4.3)
vue-i18n:
specifier: 10.0.0-alpha.2
version: 10.0.0-alpha.2(vue@3.4.21)
vue-router:
specifier: ^4.3.0
version: 4.3.0(vue@3.4.21)
@ -1119,6 +1126,27 @@ packages:
resolution: {integrity: sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==}
dev: true
/@intlify/core-base@10.0.0-alpha.2:
resolution: {integrity: sha512-bT+RwQtJ2BOwpt/lLlDtJM8+lfNG9TdWWGRYphDsV8123SiGb4Qm6iEjBN3/RKC/9hX+EHl2ENslURfioov3dQ==}
engines: {node: '>= 16'}
dependencies:
'@intlify/message-compiler': 10.0.0-alpha.2
'@intlify/shared': 10.0.0-alpha.2
dev: false
/@intlify/message-compiler@10.0.0-alpha.2:
resolution: {integrity: sha512-OGwttsMwB2BUzhZLraoAfAqcza5UyLMEU013ort3LbmdE6ke/pFONFyxjNQdmFWzW2K868AIVgwx4zo8lbmhjg==}
engines: {node: '>= 16'}
dependencies:
'@intlify/shared': 10.0.0-alpha.2
source-map-js: 1.2.0
dev: false
/@intlify/shared@10.0.0-alpha.2:
resolution: {integrity: sha512-pWlpsC3IqkDuIH/5bNlyyiUbAXYymeNXkznORzPWT3qpAe8MazPOm14wMHGn/wESCdB5b9txQson4+CH0ym1hQ==}
engines: {node: '>= 16'}
dev: false
/@jridgewell/gen-mapping@0.3.5:
resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==}
engines: {node: '>=6.0.0'}
@ -5161,6 +5189,18 @@ packages:
- supports-color
dev: true
/vue-i18n@10.0.0-alpha.2(vue@3.4.21):
resolution: {integrity: sha512-wP3+3k0TbdqvYDUn5lS5v7dtAsX0lP9J+CRcoP7mTQXsmkGurfruyd0rvw+DBnW+mULsoP/oygtj75HZFPp6YQ==}
engines: {node: '>= 16'}
peerDependencies:
vue: ^3.0.0
dependencies:
'@intlify/core-base': 10.0.0-alpha.2
'@intlify/shared': 10.0.0-alpha.2
'@vue/devtools-api': 6.6.1
vue: 3.4.21(typescript@5.4.3)
dev: false
/vue-router@4.3.0(vue@3.4.21):
resolution: {integrity: sha512-dqUcs8tUeG+ssgWhcPbjHvazML16Oga5w34uCUmsk7i0BcnskoLGwjpa15fqMr2Fa5JgVBrdL2MEgqz6XZ/6IQ==}
peerDependencies:
@ -5289,7 +5329,3 @@ packages:
resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==}
engines: {node: '>=12.20'}
dev: true
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1714214996917" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="848" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M909 466.8a388 388 0 0 1 2.62 45.2c0 213-172.64 385.61-385.6 385.61a384 384 0 0 1-241.85-85.26c110.61-22.28 231.57-67.67 350.51-135.56l5.85-3.36 10-5.82c100.74-59.3 187.91-128 257.34-199.64zM951 220.64c-26.75-47.29-108.91-56.8-217.31-33.55A383.7 383.7 0 0 0 526 126.39C313 126.39 140.37 299 140.37 512q0 9.4 0.44 18.81C75.88 605.25 48.08 673.46 73 717.43c49.18 86.92 285.61 46.17 528.09-91S1000.22 307.56 951 220.64zM118.54 689.27c-6.61-11.69 2.46-42.83 32-85l0.7-1a383.22 383.22 0 0 0 41.64 103.13c-42.47 1.11-68.4-6.63-74.34-17.13z m533.19-319.54A69.75 69.75 0 1 1 721.48 300a69.74 69.74 0 0 1-69.75 69.73z m217.18-34.23a386.83 386.83 0 0 0-79.51-105.1l1.44-0.19c66.36-8.61 107 0.32 114.67 13.82 6.94 12.28-3.42 45.97-36.6 91.47z" fill="#8D92F8" p-id="849"></path><path d="M955.81 287a409.18 409.18 0 0 0-48.36-30.91c-1.12 16.69-13.13 44.62-38.54 79.43A386.83 386.83 0 0 0 789.4 230.4l1.44-0.19a377 377 0 0 1 48.63-3.39 400.28 400.28 0 0 0-123.85-19.54c-226.72 0-410.51 188.83-410.51 421.77a433 433 0 0 0 15.39 114.77c84.4-21.8 182-61.63 280.55-117.4C792.38 518.17 930.3 382.22 955.81 287z m-304.08-56.77A69.75 69.75 0 1 1 582 300a69.75 69.75 0 0 1 69.73-69.77zM907.82 468c-69.43 71.62-156.6 140.34-257.34 199.64l-10 5.82-5.85 3.36c-99.21 56.6-199.82 97.57-294.63 122.53a424.43 424.43 0 0 0 37.24 68.49A384.47 384.47 0 0 0 526 897.61c213 0 385.6-172.65 385.6-385.61a388 388 0 0 0-2.6-45.2z" fill="#8486F8" p-id="850"></path><path d="M911.58 512a388 388 0 0 0-2.58-45.2l-1.18 1.2c-69.43 71.62-156.6 140.34-257.34 199.64l-10 5.82-5.85 3.36a1305.32 1305.32 0 0 1-174.76 83.48 390.72 390.72 0 0 0 37.84 136.3q14 1 28.26 1c212.97 0.01 385.61-172.6 385.61-385.6zM871.58 331.78q-1.3 1.85-2.67 3.72c-0.68-1.32-1.38-2.62-2.08-3.93q-7.51-0.29-15.11-0.28c-207.71 0-377.82 161-392.41 365a1353 1353 0 0 0 141.74-69.83c158.26-89.54 280-198 331.76-286.76a394 394 0 0 0-61.23-7.92z" fill="#757BF2" p-id="851"></path><path d="M907.82 468c-66.59 68.69-149.5 134.7-245.05 192.3a338.52 338.52 0 0 0-9.33 215.7c150.33-52.59 258.14-195.7 258.14-364a388 388 0 0 0-2.58-45.2z" fill="#6D6DF2" p-id="852"></path><path d="M438.53 206.83c7.55 11.35-32.19 25.35-74.12 53.29S294.25 319.34 286.7 308s15.62-50.21 57.55-78.14 86.75-34.37 94.28-23.03z" fill="#FFFFFF" p-id="853"></path></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -1,6 +1,6 @@
<template>
<div>
<a-config-provider :locale="locale">
<a-config-provider global :locale="locale">
<slot></slot>
</a-config-provider>
</div>
@ -9,6 +9,10 @@
<script setup lang="ts">
import zhCN from "@arco-design/web-vue/es/locale/lang/zh-cn";
import enUS from "@arco-design/web-vue/es/locale/lang/en-us";
import { storeToRefs } from "pinia";
import { useThemeConfig } from "@/store/theme-config";
const themeStore = useThemeConfig();
const { language } = storeToRefs(themeStore);
interface Lang {
[key: string]: any;
}
@ -16,9 +20,9 @@ const locales = ref<Lang>({
"zh-CN": zhCN,
"en-US": enUS
});
const localeType = ref<string>("zh-CN");
const locale = computed(() => {
return locales.value[localeType.value] || zhCN;
return locales.value[language.value] || zhCN;
});
</script>

22
src/lang/index.ts Normal file
View File

@ -0,0 +1,22 @@
import { createI18n } from "vue-i18n";
import zhCN from "@/lang/modules/zhCN";
import enUS from "@/lang/modules/enUS";
import pinia from "@/store/index";
import { storeToRefs } from "pinia";
import { useThemeConfig } from "@/store/theme-config";
const themeStore = useThemeConfig(pinia);
const { language } = storeToRefs(themeStore);
/* 这里必须是messages名称 */
const messages = {
"zh-CN": zhCN,
"en-US": enUS
};
const i18n = createI18n({
legacy: false, // Composition API模式需要设为false
globalInjection: true, // 全局生效: $
locale: language.value, // 默认语言
messages // 数据源
});
export default i18n;

33
src/lang/modules/enUS.ts Normal file
View File

@ -0,0 +1,33 @@
export default {
language: {
["login"]: "login",
["home"]: "home",
["common-components"]: "common components",
["form-component"]: "form components",
["dynamic-form"]: "dynamic form",
["multilevel-menu"]: "multilevel menu",
["second-menu-1"]: "second-menu-1",
["second-menu-2"]: "second-menu-2",
["third-menu-1"]: "third-menu-1",
["third-menu-2"]: "third-menu-2",
["third-menu-3"]: "third-menu-3",
["third-menu-4"]: "third-menu-4",
["third-menu-5"]: "third-menu-5",
["third-menu-6"]: "third-menu-6",
["third-menu-7"]: "third-menu-7",
["third-menu-8"]: "third-menu-8",
["third-menu-9"]: "third-menu-9",
["about-project"]: "about project",
["not-power"]: "No permission",
["not-found"]: "Page not found",
["zh-CN"]: "Chinese",
["en-US"]: "English",
["refresh"]: "refresh",
["close-current"]: "close current",
["close-left-side"]: "close left side",
["close-right-side"]: "close right side",
["close-other"]: "close other",
["close-all"]: "close all",
["internationalization"]: "internationalization"
}
};

33
src/lang/modules/zhCN.ts Normal file
View File

@ -0,0 +1,33 @@
export default {
language: {
["login"]: "登录",
["home"]: "首页",
["common-components"]: "常用组件",
["form-component"]: "表单组件",
["dynamic-form"]: "动态表单",
["multilevel-menu"]: "多级菜单",
["second-menu-1"]: "二级菜单-1",
["second-menu-2"]: "二级菜单-2",
["third-menu-1"]: "三级菜单-1",
["third-menu-2"]: "三级菜单-2",
["third-menu-3"]: "三级菜单-3",
["third-menu-4"]: "三级菜单-4",
["third-menu-5"]: "三级菜单-5",
["third-menu-6"]: "三级菜单-6",
["third-menu-7"]: "三级菜单-7",
["third-menu-8"]: "三级菜单-8",
["third-menu-9"]: "三级菜单-9",
["about-project"]: "关于项目",
["not-power"]: "没有权限",
["not-found"]: "未找到页面",
["zh-CN"]: "中文",
["en-US"]: "English",
["refresh"]: "刷新",
["close-current"]: "关闭当前",
["close-left-side"]: "关闭左侧",
["close-right-side"]: "关闭右侧",
["close-other"]: "关闭其它",
["close-all"]: "关闭全部",
["internationalization"]: "国际化"
}
};

View File

@ -3,9 +3,9 @@
<a-space direction="vertical">
<a-breadcrumb>
<a-breadcrumb-item v-for="(item, index) in breadcrumb" :key="item.path">
<span v-if="index === breadcrumb.length - 1" class="main_button">{{ item?.meta?.title || "" }}</span>
<span v-if="index === breadcrumb.length - 1" class="main_button">{{ $t(`language.${item?.meta?.title || ""}`) }}</span>
<span v-else class="route_button" @click="onBreadcrumb(item as RouteLocationMatched)">{{
item?.meta?.title || ""
$t(`language.${item?.meta?.title || ""}`)
}}</span>
</a-breadcrumb-item>
</a-breadcrumb>

View File

@ -14,13 +14,17 @@
</div>
</div>
<div class="header_setting">
<a-tooltip content="语言">
<a-dropdown trigger="hover" @select="onLange">
<a-button size="mini" type="text" class="icon_btn">
<template #icon>
<icon-language :size="18" />
</template>
</a-button>
</a-tooltip>
<template #content>
<a-doption :disabled="language === 'zh-CN'">{{ $t(`language.zh-CN`) }}</a-doption>
<a-doption :disabled="language === 'en-US'">{{ $t(`language.en-US`) }}</a-doption>
</template>
</a-dropdown>
<a-tooltip content="切换黑夜模式">
<a-button size="mini" type="text" class="icon_btn">
<template #icon>
@ -102,19 +106,31 @@ import Notice from "@/layout/components/Header/components/notice/index.vue";
import Breadcrumb from "@/layout/components/Header/components/breadcrumb/index.vue";
import myImage from "@/assets/img/my-image.jpg";
import pinia from "@/store/index";
import { useI18n } from "vue-i18n";
import { Modal } from "@arco-design/web-vue";
import { useRouter } from "vue-router";
import { storeToRefs } from "pinia";
import { useUserInfoStore } from "@/store/user-info";
import { useThemeConfig } from "@/store/theme-config";
const i18n = useI18n();
const router = useRouter();
const themeStore = useThemeConfig();
const { collapsed } = storeToRefs(themeStore);
const themeStore = useThemeConfig(pinia);
const { collapsed, language } = storeToRefs(themeStore);
const onCollapsed = () => {
themeStore.setCollapsed(!collapsed.value);
};
//
const onLange = (e: string) => {
if (e === "Chinese" || e === "中文") {
themeStore.setLanguage("zh-CN");
} else {
themeStore.setLanguage("en-US");
}
i18n.locale.value = language.value;
};
const logOut = () => {
Modal.warning({
title: "提示",

View File

@ -4,11 +4,11 @@
<a-scrollbar style="height: 100%; overflow: auto" outer-class="scrollbar">
<div class="main">
<router-view v-slot="{ Component, route }">
<main-transition>
<MainTransition>
<keep-alive :include="cacheRoutes">
<component :is="Component" :key="route.name" v-if="refreshPage" />
</keep-alive>
</main-transition>
</MainTransition>
</router-view>
</div>
</a-scrollbar>

View File

@ -4,14 +4,14 @@
<template #icon v-if="item.meta.svgIcon || item.meta.icon">
<MenuItemIcon :svg-icon="item.meta.svgIcon" :icon="item.meta.icon" />
</template>
<template #title>{{ item.meta.title }}</template>
<template #title>{{ $t(`language.${item.meta.title}`) }}</template>
<MenuItem :route-tree="item.children" />
</a-sub-menu>
<a-menu-item v-else :key="item?.name">
<template #icon v-if="item.meta.svgIcon || item.meta.icon">
<MenuItemIcon :svg-icon="item.meta.svgIcon" :icon="item.meta.icon" />
</template>
<div>{{ item.meta.title }}</div>
<div>{{ $t(`language.${item.meta.title}`) }}</div>
</a-menu-item>
</template>
</template>

View File

@ -9,7 +9,12 @@
@tab-click="onTabs"
@delete="onDelete"
>
<a-tab-pane v-for="item of tabsList" :key="item.name" :title="item.meta.title" :closable="!item.meta.affix" />
<a-tab-pane
v-for="item of tabsList"
:key="item.name"
:title="$t(`language.${item.meta.title}`)"
:closable="!item.meta.affix"
/>
</a-tabs>
<div class="tabs_setting">
<a-dropdown trigger="hover" :popup-max-height="false">
@ -17,27 +22,27 @@
<template #content>
<a-doption @click="refresh">
<template #icon><icon-refresh /></template>
<template #default>刷新</template>
<template #default>{{ $t(`language.refresh`) }}</template>
</a-doption>
<a-doption @click="closeCurrent">
<template #icon><icon-close /></template>
<template #default>关闭当前</template>
<template #default>{{ $t(`language.close-current`) }}</template>
</a-doption>
<a-doption @click="closeSides('left')">
<template #icon><icon-left /></template>
<template #default>关闭左侧</template>
<template #default>{{ $t(`language.close-left-side`) }}</template>
</a-doption>
<a-doption @click="closeSides('right')">
<template #icon><icon-right /></template>
<template #default>关闭右侧</template>
<template #default>{{ $t(`language.close-right-side`) }}</template>
</a-doption>
<a-doption @click="closeOther('other')">
<template #icon><icon-close-circle /></template>
<template #default>关闭其它</template>
<template #default>{{ $t(`language.close-other`) }}</template>
</a-doption>
<a-doption @click="closeOther('all')">
<template #icon><icon-folder-delete /></template>
<template #default>全部关闭</template>
<template #default>{{ $t(`language.close-all`) }}</template>
</a-doption>
</template>
</a-dropdown>

View File

@ -1,8 +1,8 @@
<template>
<div>
<lang-provider>
<LangProvider>
<component :is="layouts['defaults']" />
</lang-provider>
</LangProvider>
</div>
</template>

View File

@ -9,13 +9,13 @@ import "virtual:svg-icons-register";
import "@arco-design/web-vue/dist/arco.css";
// 额外引入图标库
import ArcoVueIcon from "@arco-design/web-vue/es/icon";
import i18n from "@/lang/index";
pinia.use(piniaPluginPersist);
const app = createApp(App);
app.use(ArcoVue, {
// 用于改变使用组件时的前缀名称
componentPrefix: "arco"
});
app.use(i18n);
app.use(ArcoVueIcon);
app.use(pinia);
app.use(router);

View File

@ -192,6 +192,21 @@ export const dynamicRoutes: RouteRecordRaw[] = [
}
]
},
{
path: "/internationalization",
name: "internationalization",
component: () => import("@/views/internationalization/internationalization.vue"),
meta: {
title: "internationalization",
hide: false,
keepAlive: true,
affix: false,
link: "",
iframe: false,
roles: ["admin", "common"],
svgIcon: "earth"
}
},
{
path: "/about-project",
name: "about-project",
@ -223,7 +238,7 @@ export const staticRoutes = [
name: "login",
component: () => import("@/views/login/login.vue"),
meta: {
title: "登录"
title: "login"
}
}
/**
@ -239,19 +254,19 @@ export const staticRoutes = [
export const notFoundAndNoPower = [
{
path: "/401",
name: "noPower",
name: "no-power",
component: () => import("@/views/error/401.vue"),
meta: {
title: "notFound",
title: "not-power",
hide: true
}
},
{
path: "/:path(.*)*", // 匹配任意路由,兜底,未找到页面的时候跳转该页面
name: "notFound",
name: "not-found",
component: () => import("@/views/error/404.vue"),
meta: {
title: "notFound",
title: "not-found",
hide: true
}
}

View File

@ -7,7 +7,8 @@ import { defineStore } from "pinia";
export const useThemeConfig = defineStore("themeConfig", {
state: (): any => ({
collapsed: false, // 是否折叠菜单
refreshPage: true // 刷新页面
refreshPage: true, // 刷新页面
language: "zh-CN" // 系统语言
}),
actions: {
// 折叠菜单
@ -17,9 +18,14 @@ export const useThemeConfig = defineStore("themeConfig", {
// 刷新页面
setRefreshPage(data: Boolean) {
this.refreshPage = data;
},
// 设置语言
setLanguage(data: string) {
this.language = data;
}
},
persist: {
enabled: true
enabled: true, // 开启数据缓存-默认缓存全部数据
key: "themeConfig"
}
});

View File

@ -26,6 +26,7 @@ export const useUserInfoStore = defineStore("userInfo", {
}
},
persist: {
enabled: true // 开启数据缓存-默认缓存全部数据
enabled: true, // 开启数据缓存-默认缓存全部数据
key: "userInfo"
}
});

View File

@ -59,3 +59,15 @@ export function arrayFlattened(tree: any, term: string) {
}
return result.reverse();
}
/**
*
* @returns
*/
export function webDefaultLanguage() {
if (navigator.language === "zh-CN") {
return "zhCN";
} else {
return "enUS";
}
}

View File

@ -1,6 +1,6 @@
<template>
<div>
<div>表单数据<a-date-picker style="width: 200px" /></div>
<div>表单数据</div>
</div>
</template>

View File

@ -0,0 +1,10 @@
<template>
<div>
<a-date-picker style="width: 200px" />
<a-pagination :total="50" show-total show-jumper show-page-size />
</div>
</template>
<script setup lang="ts"></script>
<style lang="scss" scoped></style>

View File

@ -1,7 +1,7 @@
<template>
<div>
<div>二级-菜单 页面缓存</div>
<a-input :style="{ width: '320px' }" placeholder="请输入内容" allow-clear v-model="form" />
<a-input :style="{ width: '320px' }" placeholder="请输入内容测试页面缓存" allow-clear v-model="form" />
</div>
</template>

View File

@ -1,7 +1,7 @@
<template>
<div>
<div>三级-菜单-01 页面缓存</div>
<a-input :style="{ width: '320px' }" placeholder="请输入内容" allow-clear v-model="form" />
<a-input :style="{ width: '320px' }" placeholder="请输入内容测试页面缓存" allow-clear v-model="form" />
</div>
</template>

View File

@ -1,7 +1,7 @@
<template>
<div>
<div>三级-菜单-02 页面缓存</div>
<a-input :style="{ width: '320px' }" placeholder="请输入内容" allow-clear v-model="form" />
<a-input :style="{ width: '320px' }" placeholder="请输入内容测试页面缓存" allow-clear v-model="form" />
</div>
</template>