Skip to content

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");
};