Appearance
vue 项目使用 i18n 插件
navigator.language 这个 BOM API 可以获取当前浏览器的语言
1.配置 i18n 插件
需要先安装 vue-i18n 插件,创建 locale 文件夹,创建 index 文件,引入后配置 i18n 插件,同时需要准备各个国家的翻译文案。导出 i18n 对象后,可以直接使用 app.use 注册插件,就可以在模板中使用该插件提供的 api 实现多语言文本替换了。
ts
import { createI18n } from "vue-i18n";
// 提前准备好的各个国家的翻译文案
import CN from "./CN";
import US from "./US";
import BR from "./BR";
import ID from "./ID";
import JP from "./JP";
import RU from "./RU";
import TH from "./TH";
import VN from "./VN";
import NG from "./NG";
const i18n = createI18n({
// 当前激活的国家文本 (value必须是messages配置项中的某个key)
locale: "CN",
// 失败激活的国家文本(value必须是messages配置项中的某个key)
fallbackLocale: "US",
allowComposition: true,
// 配置各个国家的文本
messages: {
CN,
US,
BR,
ID,
JP,
RU,
TH,
VN,
NG,
},
});
export default i18n;
import { createI18n } from "vue-i18n";
// 提前准备好的各个国家的翻译文案
import CN from "./CN";
import US from "./US";
import BR from "./BR";
import ID from "./ID";
import JP from "./JP";
import RU from "./RU";
import TH from "./TH";
import VN from "./VN";
import NG from "./NG";
const i18n = createI18n({
// 当前激活的国家文本 (value必须是messages配置项中的某个key)
locale: "CN",
// 失败激活的国家文本(value必须是messages配置项中的某个key)
fallbackLocale: "US",
allowComposition: true,
// 配置各个国家的文本
messages: {
CN,
US,
BR,
ID,
JP,
RU,
TH,
VN,
NG,
},
});
export default i18n;
2.配置某个国家的文案
形如这种对象形式的来配置各个文本
// CN.ts
ts
import layout from "./CN/layout";
export default {
layout,
message: {
hello: "你好",
},
};
import layout from "./CN/layout";
export default {
layout,
message: {
hello: "你好",
},
};
// 模块化举例 layout.ts
ts
export default {
header: {
signIn: "登录",
signUp: "注册",
home: "首页",
game: "游戏",
},
};
export default {
header: {
signIn: "登录",
signUp: "注册",
home: "首页",
game: "游戏",
},
};
3.在模板中使用
通过 app.use 安装好了该插件就可以直接在模板中使用其提供的$t 方法,来通过 2 中配置的对象路径来读取对应的文案。
vue
<template>
<div class="sign-container">
<div class="mr-10 sign-in-btn btn">{{ $t("layout.header.signIn") }}</div>
<div class="sign-up-btn btn">{{ $t("layout.header.signUp") }}</div>
</div>
</template>
<template>
<div class="sign-container">
<div class="mr-10 sign-in-btn btn">{{ $t("layout.header.signIn") }}</div>
<div class="sign-up-btn btn">{{ $t("layout.header.signUp") }}</div>
</div>
</template>
4.在 ts 中使用
引入创建的 i18n 实例即可使用提供的 api,i18n.global.t(),读取对象路径获取对应的值。
ts
<script lang='ts' setup>
import NavBtn from '@/components/btn/NavBtn.vue';
import i18n from '@/locale' // 引入i8n实例
const list = [
{ icon: 'icon-home', title: i18n.global.t('layout.header.home'), path: '/' },
{ icon: 'icon-live-broadcast', title: i18n.global.t('layout.header.game'), path: '/game' },
]
</script>
<script lang='ts' setup>
import NavBtn from '@/components/btn/NavBtn.vue';
import i18n from '@/locale' // 引入i8n实例
const list = [
{ icon: 'icon-home', title: i18n.global.t('layout.header.home'), path: '/' },
{ icon: 'icon-live-broadcast', title: i18n.global.t('layout.header.game'), path: '/game' },
]
</script>
5.切换当前语言设置
该钩子可以获取到 i18n 实例,i18n.locale.value 即可设置语言,但是必须是实例化 i18n 时 messages 配置项中有的 key 才能生效。
ts
import { useI18n } from "vue-i18n";
// i18n插件
const i18n = useI18n();
function setLanguage(value: LOCALE_VALUE_TYPE) {
i18n.locale.value = value;
}
import { useI18n } from "vue-i18n";
// i18n插件
const i18n = useI18n();
function setLanguage(value: LOCALE_VALUE_TYPE) {
i18n.locale.value = value;
}
所有模板内容调用了$t 函数都会重新解析模板生成对应语言的 dom 文本
但是通过 ts 调用 t 函数是不会因为语言更新而重新调用 这个需要特别注意(也就是 4 的例子)。,这种方式生成的内容都是死内容,需要自己手动监听语言的变化,重新调用 t 函数生成对应语言文本。
6.(JS 中如何处理多语言切换)多语言路由表渲染导航菜单处理
最开始我是直接在路由上直接调用 t 函数根据语言模式生成对应文本,这样生成的路由表是文本内容是死的,有两种解决方案
1.getter 函数(代码量多一些)
所以可以配置一个 getter 函数,getter 函数调用后返回当前语言的路由表。
routes.ts
ts
import type { RouteRecordRaw } from "vue-router";
import i18n from "@/locale";
export const initRoutes: RouteRecordRaw[] = [
{
path: "/",
name: "home",
component: () => import("@/views/home/index.vue"),
meta: {
title: i18n.global.t("menu.home"),
icon: "icon-folder",
level: 1,
},
},
{
path: "/sports",
name: "sports",
component: () => import("@/views/sports/index.vue"),
meta: {
title: i18n.global.t("menu.sports"),
icon: "icon-at",
level: 1,
},
},
{
path: "/game",
name: "game",
component: () => import("@/views/game/index.vue"),
meta: {
title: i18n.global.t("menu.game"),
icon: "icon-code",
level: 1,
},
},
{
path: "/test",
name: "test",
component: () => import("@/views/test/index.vue"),
meta: {
title: "test",
icon: "icon-folder",
level: 1,
},
children: [
{
path: "/test/test-son-01",
name: "test-son-01",
component: () => import("@/views/test/children/test-son-01/index.vue"),
meta: {
title: "test-son-01",
icon: "icon-folder",
level: 2,
},
children: [
{
path: "/test/test-son-01/test-son-01-son-01",
name: "test-son-01-son-01",
component: () =>
import(
"@/views/test/children/test-son-01/children/test-son-01-son-01/index.vue"
),
meta: {
title: "test-son-01-son-01",
icon: "icon-folder",
level: 3,
},
},
{
path: "/test/test-son-01/test-son-01-son-02",
name: "test-son-01-son-02",
component: () =>
import(
"@/views/test/children/test-son-01/children/test-son-01-son-02/index.vue"
),
meta: {
title: "test-son-01-son-02",
icon: "icon-folder",
level: 3,
},
},
],
},
{
path: "/test/test-son-02",
name: "test-son-02",
component: () => import("@/views/test/children/test-son-02/index.vue"),
meta: {
title: "test-son-02",
icon: "icon-folder",
level: 2,
},
children: [
{
path: "/test/test-son-02/test-son-02-son-01",
name: "test-son-02-son-01",
component: () =>
import(
"@/views/test/children/test-son-02/children/test-son-02-son-01/index.vue"
),
meta: {
title: "test-son-02-son-01",
icon: "icon-folder",
level: 3,
},
},
{
path: "/test/test-son-02/test-son-02-son-02",
name: "test-son-02-son-02",
component: () =>
import(
"@/views/test/children/test-son-02/children/test-son-02-son-02/index.vue"
),
meta: {
title: "test-son-02-son-02",
icon: "icon-folder",
level: 3,
},
},
],
},
],
},
{
path: "/blog",
name: "blog",
component: () => import("@/views/blog/index.vue"),
meta: {
title: i18n.global.t("menu.blog"),
icon: "icon-launch",
level: 1,
},
},
];
/**
* 获取路由表
* @returns
*/
export const getterInitRoutes = () => {
return [
{
path: "/",
name: "home",
component: () => import("@/views/home/index.vue"),
meta: {
title: i18n.global.t("menu.home"),
icon: "icon-folder",
level: 1,
},
},
{
path: "/sports",
name: "sports",
component: () => import("@/views/sports/index.vue"),
meta: {
title: i18n.global.t("menu.sports"),
icon: "icon-at",
level: 1,
},
},
{
path: "/game",
name: "game",
component: () => import("@/views/game/index.vue"),
meta: {
title: i18n.global.t("menu.game"),
icon: "icon-code",
level: 1,
},
},
{
path: "/test",
name: "test",
component: () => import("@/views/test/index.vue"),
meta: {
title: "test",
icon: "icon-folder",
level: 1,
},
children: [
{
path: "/test/test-son-01",
name: "test-son-01",
component: () =>
import("@/views/test/children/test-son-01/index.vue"),
meta: {
title: "test-son-01",
icon: "icon-folder",
level: 2,
},
children: [
{
path: "/test/test-son-01/test-son-01-son-01",
name: "test-son-01-son-01",
component: () =>
import(
"@/views/test/children/test-son-01/children/test-son-01-son-01/index.vue"
),
meta: {
title: "test-son-01-son-01",
icon: "icon-folder",
level: 3,
},
},
{
path: "/test/test-son-01/test-son-01-son-02",
name: "test-son-01-son-02",
component: () =>
import(
"@/views/test/children/test-son-01/children/test-son-01-son-02/index.vue"
),
meta: {
title: "test-son-01-son-02",
icon: "icon-folder",
level: 3,
},
},
],
},
{
path: "/test/test-son-02",
name: "test-son-02",
component: () =>
import("@/views/test/children/test-son-02/index.vue"),
meta: {
title: "test-son-02",
icon: "icon-folder",
level: 2,
},
children: [
{
path: "/test/test-son-02/test-son-02-son-01",
name: "test-son-02-son-01",
component: () =>
import(
"@/views/test/children/test-son-02/children/test-son-02-son-01/index.vue"
),
meta: {
title: "test-son-02-son-01",
icon: "icon-folder",
level: 3,
},
},
{
path: "/test/test-son-02/test-son-02-son-02",
name: "test-son-02-son-02",
component: () =>
import(
"@/views/test/children/test-son-02/children/test-son-02-son-02/index.vue"
),
meta: {
title: "test-son-02-son-02",
icon: "icon-folder",
level: 3,
},
},
],
},
],
},
{
path: "/blog",
name: "blog",
component: () => import("@/views/blog/index.vue"),
meta: {
title: i18n.global.t("menu.blog"),
icon: "icon-launch",
level: 1,
},
},
];
};
import type { RouteRecordRaw } from "vue-router";
import i18n from "@/locale";
export const initRoutes: RouteRecordRaw[] = [
{
path: "/",
name: "home",
component: () => import("@/views/home/index.vue"),
meta: {
title: i18n.global.t("menu.home"),
icon: "icon-folder",
level: 1,
},
},
{
path: "/sports",
name: "sports",
component: () => import("@/views/sports/index.vue"),
meta: {
title: i18n.global.t("menu.sports"),
icon: "icon-at",
level: 1,
},
},
{
path: "/game",
name: "game",
component: () => import("@/views/game/index.vue"),
meta: {
title: i18n.global.t("menu.game"),
icon: "icon-code",
level: 1,
},
},
{
path: "/test",
name: "test",
component: () => import("@/views/test/index.vue"),
meta: {
title: "test",
icon: "icon-folder",
level: 1,
},
children: [
{
path: "/test/test-son-01",
name: "test-son-01",
component: () => import("@/views/test/children/test-son-01/index.vue"),
meta: {
title: "test-son-01",
icon: "icon-folder",
level: 2,
},
children: [
{
path: "/test/test-son-01/test-son-01-son-01",
name: "test-son-01-son-01",
component: () =>
import(
"@/views/test/children/test-son-01/children/test-son-01-son-01/index.vue"
),
meta: {
title: "test-son-01-son-01",
icon: "icon-folder",
level: 3,
},
},
{
path: "/test/test-son-01/test-son-01-son-02",
name: "test-son-01-son-02",
component: () =>
import(
"@/views/test/children/test-son-01/children/test-son-01-son-02/index.vue"
),
meta: {
title: "test-son-01-son-02",
icon: "icon-folder",
level: 3,
},
},
],
},
{
path: "/test/test-son-02",
name: "test-son-02",
component: () => import("@/views/test/children/test-son-02/index.vue"),
meta: {
title: "test-son-02",
icon: "icon-folder",
level: 2,
},
children: [
{
path: "/test/test-son-02/test-son-02-son-01",
name: "test-son-02-son-01",
component: () =>
import(
"@/views/test/children/test-son-02/children/test-son-02-son-01/index.vue"
),
meta: {
title: "test-son-02-son-01",
icon: "icon-folder",
level: 3,
},
},
{
path: "/test/test-son-02/test-son-02-son-02",
name: "test-son-02-son-02",
component: () =>
import(
"@/views/test/children/test-son-02/children/test-son-02-son-02/index.vue"
),
meta: {
title: "test-son-02-son-02",
icon: "icon-folder",
level: 3,
},
},
],
},
],
},
{
path: "/blog",
name: "blog",
component: () => import("@/views/blog/index.vue"),
meta: {
title: i18n.global.t("menu.blog"),
icon: "icon-launch",
level: 1,
},
},
];
/**
* 获取路由表
* @returns
*/
export const getterInitRoutes = () => {
return [
{
path: "/",
name: "home",
component: () => import("@/views/home/index.vue"),
meta: {
title: i18n.global.t("menu.home"),
icon: "icon-folder",
level: 1,
},
},
{
path: "/sports",
name: "sports",
component: () => import("@/views/sports/index.vue"),
meta: {
title: i18n.global.t("menu.sports"),
icon: "icon-at",
level: 1,
},
},
{
path: "/game",
name: "game",
component: () => import("@/views/game/index.vue"),
meta: {
title: i18n.global.t("menu.game"),
icon: "icon-code",
level: 1,
},
},
{
path: "/test",
name: "test",
component: () => import("@/views/test/index.vue"),
meta: {
title: "test",
icon: "icon-folder",
level: 1,
},
children: [
{
path: "/test/test-son-01",
name: "test-son-01",
component: () =>
import("@/views/test/children/test-son-01/index.vue"),
meta: {
title: "test-son-01",
icon: "icon-folder",
level: 2,
},
children: [
{
path: "/test/test-son-01/test-son-01-son-01",
name: "test-son-01-son-01",
component: () =>
import(
"@/views/test/children/test-son-01/children/test-son-01-son-01/index.vue"
),
meta: {
title: "test-son-01-son-01",
icon: "icon-folder",
level: 3,
},
},
{
path: "/test/test-son-01/test-son-01-son-02",
name: "test-son-01-son-02",
component: () =>
import(
"@/views/test/children/test-son-01/children/test-son-01-son-02/index.vue"
),
meta: {
title: "test-son-01-son-02",
icon: "icon-folder",
level: 3,
},
},
],
},
{
path: "/test/test-son-02",
name: "test-son-02",
component: () =>
import("@/views/test/children/test-son-02/index.vue"),
meta: {
title: "test-son-02",
icon: "icon-folder",
level: 2,
},
children: [
{
path: "/test/test-son-02/test-son-02-son-01",
name: "test-son-02-son-01",
component: () =>
import(
"@/views/test/children/test-son-02/children/test-son-02-son-01/index.vue"
),
meta: {
title: "test-son-02-son-01",
icon: "icon-folder",
level: 3,
},
},
{
path: "/test/test-son-02/test-son-02-son-02",
name: "test-son-02-son-02",
component: () =>
import(
"@/views/test/children/test-son-02/children/test-son-02-son-02/index.vue"
),
meta: {
title: "test-son-02-son-02",
icon: "icon-folder",
level: 3,
},
},
],
},
],
},
{
path: "/blog",
name: "blog",
component: () => import("@/views/blog/index.vue"),
meta: {
title: i18n.global.t("menu.blog"),
icon: "icon-launch",
level: 1,
},
},
];
};
使用
vue
<template>
<div class="navigations-container">
<a-menu :selected-keys="[$route.path]">
<NavigateItems :routes="routesList" />
</a-menu>
</div>
</template>
<script lang="ts" setup>
// 路由表
import { getterInitRoutes } from "@/router/routes";
// 组件
import NavigateItems from "./NavigateItems.vue";
// hooks
import { useRoute } from "vue-router";
import useSettingStore from "@/store/setting";
import { watch, reactive } from "vue";
import { storeToRefs } from "pinia";
// types
import type { RouteRecordRaw } from "vue-router";
// 设置仓库
const settingStore = useSettingStore();
// 当前语言
const { language } = storeToRefs(settingStore);
// 路由
const $route = useRoute();
// 路由表
const routesList: RouteRecordRaw[] = reactive([]);
// 语言变化时 需要重新根据语言来获取最新的路由表
watch(
language,
() => {
routesList.length = 0;
getterInitRoutes().forEach((ele) => {
routesList.push(ele);
});
},
{ immediate: true }
);
</script>
<style scoped lang="scss">
.navigations-container {
flex-grow: 1;
}
</style>
<template>
<div class="navigations-container">
<a-menu :selected-keys="[$route.path]">
<NavigateItems :routes="routesList" />
</a-menu>
</div>
</template>
<script lang="ts" setup>
// 路由表
import { getterInitRoutes } from "@/router/routes";
// 组件
import NavigateItems from "./NavigateItems.vue";
// hooks
import { useRoute } from "vue-router";
import useSettingStore from "@/store/setting";
import { watch, reactive } from "vue";
import { storeToRefs } from "pinia";
// types
import type { RouteRecordRaw } from "vue-router";
// 设置仓库
const settingStore = useSettingStore();
// 当前语言
const { language } = storeToRefs(settingStore);
// 路由
const $route = useRoute();
// 路由表
const routesList: RouteRecordRaw[] = reactive([]);
// 语言变化时 需要重新根据语言来获取最新的路由表
watch(
language,
() => {
routesList.length = 0;
getterInitRoutes().forEach((ele) => {
routesList.push(ele);
});
},
{ immediate: true }
);
</script>
<style scoped lang="scss">
.navigations-container {
flex-grow: 1;
}
</style>
2.使用 computed 函数
利用 i18n.global.t 函数在语言切换时会重新调用一次的特性,可以使用 computed 函数,传入 getter 函数,getter 函数直接返回路由表,这样就能做到语言切换时重新调用 getter 函数,相当于底层就是基于 1 的实现,但是简化了监听的流程,让 vue 监听到 t 函数执行直接调用 getter 函数获取最新的路由表。
定义
ts
/**
* 获取路由表
* @returns
*/
export const getterInitRoutes = computed(() => {
return [
{
path: "/",
name: "home",
component: () => import("@/views/home/index.vue"),
meta: {
title: i18n.global.t("menu.home"),
icon: "icon-folder",
level: 1,
},
},
{
path: "/sports",
name: "sports",
component: () => import("@/views/sports/index.vue"),
meta: {
title: i18n.global.t("menu.sports"),
icon: "icon-at",
level: 1,
},
},
{
path: "/game",
name: "game",
component: () => import("@/views/game/index.vue"),
meta: {
title: i18n.global.t("menu.game"),
icon: "icon-code",
level: 1,
},
},
{
path: "/test",
name: "test",
component: () => import("@/views/test/index.vue"),
meta: {
title: "test",
icon: "icon-folder",
level: 1,
},
children: [
{
path: "/test/test-son-01",
name: "test-son-01",
component: () =>
import("@/views/test/children/test-son-01/index.vue"),
meta: {
title: "test-son-01",
icon: "icon-folder",
level: 2,
},
children: [
{
path: "/test/test-son-01/test-son-01-son-01",
name: "test-son-01-son-01",
component: () =>
import(
"@/views/test/children/test-son-01/children/test-son-01-son-01/index.vue"
),
meta: {
title: "test-son-01-son-01",
icon: "icon-folder",
level: 3,
},
},
{
path: "/test/test-son-01/test-son-01-son-02",
name: "test-son-01-son-02",
component: () =>
import(
"@/views/test/children/test-son-01/children/test-son-01-son-02/index.vue"
),
meta: {
title: "test-son-01-son-02",
icon: "icon-folder",
level: 3,
},
},
],
},
{
path: "/test/test-son-02",
name: "test-son-02",
component: () =>
import("@/views/test/children/test-son-02/index.vue"),
meta: {
title: "test-son-02",
icon: "icon-folder",
level: 2,
},
children: [
{
path: "/test/test-son-02/test-son-02-son-01",
name: "test-son-02-son-01",
component: () =>
import(
"@/views/test/children/test-son-02/children/test-son-02-son-01/index.vue"
),
meta: {
title: "test-son-02-son-01",
icon: "icon-folder",
level: 3,
},
},
{
path: "/test/test-son-02/test-son-02-son-02",
name: "test-son-02-son-02",
component: () =>
import(
"@/views/test/children/test-son-02/children/test-son-02-son-02/index.vue"
),
meta: {
title: "test-son-02-son-02",
icon: "icon-folder",
level: 3,
},
},
],
},
],
},
{
path: "/blog",
name: "blog",
component: () => import("@/views/blog/index.vue"),
meta: {
title: i18n.global.t("menu.blog"),
icon: "icon-launch",
level: 1,
},
},
];
});
/**
* 获取路由表
* @returns
*/
export const getterInitRoutes = computed(() => {
return [
{
path: "/",
name: "home",
component: () => import("@/views/home/index.vue"),
meta: {
title: i18n.global.t("menu.home"),
icon: "icon-folder",
level: 1,
},
},
{
path: "/sports",
name: "sports",
component: () => import("@/views/sports/index.vue"),
meta: {
title: i18n.global.t("menu.sports"),
icon: "icon-at",
level: 1,
},
},
{
path: "/game",
name: "game",
component: () => import("@/views/game/index.vue"),
meta: {
title: i18n.global.t("menu.game"),
icon: "icon-code",
level: 1,
},
},
{
path: "/test",
name: "test",
component: () => import("@/views/test/index.vue"),
meta: {
title: "test",
icon: "icon-folder",
level: 1,
},
children: [
{
path: "/test/test-son-01",
name: "test-son-01",
component: () =>
import("@/views/test/children/test-son-01/index.vue"),
meta: {
title: "test-son-01",
icon: "icon-folder",
level: 2,
},
children: [
{
path: "/test/test-son-01/test-son-01-son-01",
name: "test-son-01-son-01",
component: () =>
import(
"@/views/test/children/test-son-01/children/test-son-01-son-01/index.vue"
),
meta: {
title: "test-son-01-son-01",
icon: "icon-folder",
level: 3,
},
},
{
path: "/test/test-son-01/test-son-01-son-02",
name: "test-son-01-son-02",
component: () =>
import(
"@/views/test/children/test-son-01/children/test-son-01-son-02/index.vue"
),
meta: {
title: "test-son-01-son-02",
icon: "icon-folder",
level: 3,
},
},
],
},
{
path: "/test/test-son-02",
name: "test-son-02",
component: () =>
import("@/views/test/children/test-son-02/index.vue"),
meta: {
title: "test-son-02",
icon: "icon-folder",
level: 2,
},
children: [
{
path: "/test/test-son-02/test-son-02-son-01",
name: "test-son-02-son-01",
component: () =>
import(
"@/views/test/children/test-son-02/children/test-son-02-son-01/index.vue"
),
meta: {
title: "test-son-02-son-01",
icon: "icon-folder",
level: 3,
},
},
{
path: "/test/test-son-02/test-son-02-son-02",
name: "test-son-02-son-02",
component: () =>
import(
"@/views/test/children/test-son-02/children/test-son-02-son-02/index.vue"
),
meta: {
title: "test-son-02-son-02",
icon: "icon-folder",
level: 3,
},
},
],
},
],
},
{
path: "/blog",
name: "blog",
component: () => import("@/views/blog/index.vue"),
meta: {
title: i18n.global.t("menu.blog"),
icon: "icon-launch",
level: 1,
},
},
];
});
使用
ts
<template>
<div class="navigations-container">
<a-menu :selected-keys="[ $route.path ]">
<NavigateItems :routes="getterInitRoutes" />
</a-menu>
</div>
</template>
<script lang='ts' setup>
// 路由表
import { getterInitRoutes } from '@/router/routes';
// 组件
import NavigateItems from './NavigateItems.vue';
// hooks
import { useRoute } from 'vue-router';
// 路由
const $route = useRoute()
</script>
<style scoped lang='scss'>
.navigations-container {
flex-grow: 1;
}
</style>
<template>
<div class="navigations-container">
<a-menu :selected-keys="[ $route.path ]">
<NavigateItems :routes="getterInitRoutes" />
</a-menu>
</div>
</template>
<script lang='ts' setup>
// 路由表
import { getterInitRoutes } from '@/router/routes';
// 组件
import NavigateItems from './NavigateItems.vue';
// hooks
import { useRoute } from 'vue-router';
// 路由
const $route = useRoute()
</script>
<style scoped lang='scss'>
.navigations-container {
flex-grow: 1;
}
</style>
7.渲染动态内容
例如要渲染 确定删除 xx 吗?的国际化
定义:
ts
en:{
deleteTip:'Are you sure to delete {username}'
},
cn:{
deleteTip:'你确定要删除 {username} 吗'
}
en:{
deleteTip:'Are you sure to delete {username}'
},
cn:{
deleteTip:'你确定要删除 {username} 吗'
}
使用
ts
$t("deleteTip", { username: "张三" });
$t("deleteTip", { username: "张三" });
8.多语言面包屑
ts
<template>
<div class="breadcrumb-container">
<a-breadcrumb>
<a-breadcrumb-item @click="() => onHandleClick(item.path)" v-for="item in list" :key="item.path">
{{ item.title }}
</a-breadcrumb-item>
</a-breadcrumb>
</div>
</template>
<script lang='ts' setup>
import { useRoute, useRouter } from 'vue-router';
import { computed } from 'vue'
import type { RouteRecordRaw } from 'vue-router';
import { getterInitRoutes } from '@/router/routes';
const router = useRouter()
const route = useRoute()
const list = computed(() => {
// 当前激活的路由
const nowRoutes = route.matched
// 遍历当前激活的路由表 获取对应的title
return nowRoutes.map(ele => {
return {
path: ele.path,
title:getRouteTitle(ele.path,getterInitRoutes.value)
}
})
})
// 根据当前路由的path获取对应的title
function getRouteTitle (path: string, routes: RouteRecordRaw[]):string {
let title = ''
// 遍历当前层级的路由 获取对应的title
if(routes.some(ele => {
if (ele.path === path) {
if (ele.meta) {
title = ele.meta.title
} else {
title = ele.path
}
return true
}
})) {
// 若找到了则直接返回对应title
return title
} else {
// 若没找到就遍历当前层的每一级路由,递归调用对应子路由
for (let i = 0; i < routes.length; i++){
// 若有子孩子就递归调用 没有就跳过调用递归函数
if (routes[ i ].children) {
const res = getRouteTitle(path, routes[ i ].children as RouteRecordRaw[])
// 若找到了 并且不为not found(因为第一次调用可能未找到则返回值为not found,但后续子路由还没开始找呢) 没找到则直接跳过,遍历下一个路由
if (res&&res!=='not found') {
return res
}
}
}
// 兜底的 若遍历一直没找到就是not found
return 'not found'
}
}
const onHandleClick = (path: string) => {
router.push(path)
}
defineOptions({
name: 'Breadcrumbs'
})
</script>
<template>
<div class="breadcrumb-container">
<a-breadcrumb>
<a-breadcrumb-item @click="() => onHandleClick(item.path)" v-for="item in list" :key="item.path">
{{ item.title }}
</a-breadcrumb-item>
</a-breadcrumb>
</div>
</template>
<script lang='ts' setup>
import { useRoute, useRouter } from 'vue-router';
import { computed } from 'vue'
import type { RouteRecordRaw } from 'vue-router';
import { getterInitRoutes } from '@/router/routes';
const router = useRouter()
const route = useRoute()
const list = computed(() => {
// 当前激活的路由
const nowRoutes = route.matched
// 遍历当前激活的路由表 获取对应的title
return nowRoutes.map(ele => {
return {
path: ele.path,
title:getRouteTitle(ele.path,getterInitRoutes.value)
}
})
})
// 根据当前路由的path获取对应的title
function getRouteTitle (path: string, routes: RouteRecordRaw[]):string {
let title = ''
// 遍历当前层级的路由 获取对应的title
if(routes.some(ele => {
if (ele.path === path) {
if (ele.meta) {
title = ele.meta.title
} else {
title = ele.path
}
return true
}
})) {
// 若找到了则直接返回对应title
return title
} else {
// 若没找到就遍历当前层的每一级路由,递归调用对应子路由
for (let i = 0; i < routes.length; i++){
// 若有子孩子就递归调用 没有就跳过调用递归函数
if (routes[ i ].children) {
const res = getRouteTitle(path, routes[ i ].children as RouteRecordRaw[])
// 若找到了 并且不为not found(因为第一次调用可能未找到则返回值为not found,但后续子路由还没开始找呢) 没找到则直接跳过,遍历下一个路由
if (res&&res!=='not found') {
return res
}
}
}
// 兜底的 若遍历一直没找到就是not found
return 'not found'
}
}
const onHandleClick = (path: string) => {
router.push(path)
}
defineOptions({
name: 'Breadcrumbs'
})
</script>
9.多语言配置网页标题(Vue 路由)
网页标题也得适配国际化,在主动切换语言时也需要根据当前路由来设置对应的网页标题。
方案一
在路由元数据route.meta
中,添加一个属性title
,其值为配置i18n
中对应定义好的 key,在需要设置网页标题时只需要获取当前激活的路由元数据route.meta
中的title
,即可获得需要转换的 key,通过i18n
即可获取当前语言下的文本值。
0.配置路由元数据
js
const routes = [
{
path:"/home",
component:...., // 省略
meta:{
title:'home' // 需要在i18n配置好这个 home 的国际化值
}
}
]
const routes = [
{
path:"/home",
component:...., // 省略
meta:{
title:'home' // 需要在i18n配置好这个 home 的国际化值
}
}
]
1.页面切换后的标题国际化afterEach
ts
export const afterGuards: NavigationHookAfter = (to) => {
nprogress.end();
// to为路由元信息
to.meta?.title
? (document.title = `${i18n.global.t(to.meta.title)} | Photo Share`)
: (document.title = "Photo Share");
};
export const afterGuards: NavigationHookAfter = (to) => {
nprogress.end();
// to为路由元信息
to.meta?.title
? (document.title = `${i18n.global.t(to.meta.title)} | Photo Share`)
: (document.title = "Photo Share");
};
2.主动设置语言的标题国际化
ts
/**
* 切换语言
* @param value
*/
const toggleLocale = (value: LOCALE_VALUE) => {
locale.value = value;
i18n.global.locale = value;
// 根据当前激活的路由来设置标题国际化
route.meta?.title
? (document.title = `${i18n.global.t(route.meta.title)} | Photo Share`)
: (document.title = "Photo Share");
};
/**
* 切换语言
* @param value
*/
const toggleLocale = (value: LOCALE_VALUE) => {
locale.value = value;
i18n.global.locale = value;
// 根据当前激活的路由来设置标题国际化
route.meta?.title
? (document.title = `${i18n.global.t(route.meta.title)} | Photo Share`)
: (document.title = "Photo Share");
};