Vue3 如何接入 i18n 实现国际化多语言

1. 基本方法

在 Vue.js 3 中实现网页的国际化多语言,最常用的包是 vue-i18n,通常我们会与 vue-i18n-routing 一起使用。

vue-i18n 负责根据当前页面的语言渲染文本占位符,例如:

html 复制代码
<span>{{ t('Login') }}</span>

当语言设置为中文时,会将 Login 渲染为"登录"。

vue-i18n-routing 负责将页面语言与 URL 绑定,例如:

html 复制代码
https://githubstar.pro/zh-CN/repo

表示访问中文版的 /repo 路径。

将不同语言的网页放在不同的 URL 下有助于 SEO,因为可以在 <head> 部分添加语言信息,增加不同语言被搜索引擎索引的概率。

Google 对于多语言 Vue 站点的爬取机制如下:

  1. 类似 Vue 站点的 JS 动态页面是可以被爬取的,不影响权重 (参见 Google SEO)。
  2. 与用户首选语言匹配的页面将优先展示 (参见 Google SEO)。

2. 基础实现

第一步,安装一个 Vite 下使用 <i18n> 标签的插件:unplugin-vue-i18n

然后调整 vite.config.js

javascript 复制代码
import { fileURLToPath, URL } from 'node:url';
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import VueDevTools from 'vite-plugin-vue-devtools';
import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite';

export default defineConfig({
  plugins: [
    vue(),
    VueDevTools(),
    VueI18nPlugin({}),
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url)),
    },
  },
});

添加插件后,我们可以在组件内使用 <i18n> 块:

html 复制代码
<script setup lang="ts">
import { useI18n } from 'vue-i18n';

const { t, locale } = useI18n({ inheritLocale: true, useScope: 'local' });
</script>

<template>
  <span>{{ t('Login') }}</span>
</template>

<i18n lang="yaml">
en:
  Login: 'Login to web'
zh-CN:
  Login: '登录'
</i18n>

这里我们定义了两种不同的语言。

3. 路径绑定

接下来,我们需要定义使用 URL 作为当前语言,编辑 router/index.ts

typescript 复制代码
import { createRouter as _createRouter, type RouteLocationNormalized } from 'vue-i18n-routing';
import { createWebHistory } from 'vue-router';
import HomeView from '@/views/HomeView.vue';

const locales = [
  {
    code: 'en',
    iso: 'en-US',
    name: 'English',
  },
  {
    code: 'zh-CN',
    iso: 'zh-CN',
    name: '中文',
  },
];

export function createRouter(i18n: any) {
  const router = _createRouter(i18n, {
    version: 4,
    locales: locales,
    defaultLocale: 'zh-CN',
    history: createWebHistory(import.meta.env.BASE_URL),
    routes: [
      {
        path: '/home',
        name: 'home',
        component: HomeView,
      },
    ],
  });
  return router;
}

我们定义了支持的语言种类,并将原来的 routes 包装起来,vue-i18n-routing 会自动生成所有支持语言的 routes

  • /home = 中文
  • /en/home = 英文

由于我们设置了 defaultLocale: 'zh-CN',默认路径为中文。

然后,我们需要将源代码中涉及跳转的部分,例如:

javascript 复制代码
router.push({ name: 'home' });

全部加上 localePath,表示是当前语言的 URL 路径下:

javascript 复制代码
import { useLocalePath } from 'vue-i18n-routing';

const localePath = useLocalePath();

router.push(localePath({ name: 'home' }));

这样就完成了路径绑定。

4. 自动切换

有时,我们希望没有默认语言,而是根据用户的浏览器语言自动选择:

  • /zh-CN/home = 中文
  • /en/home = 英文
  • /home -> 重定向 (浏览器偏好中文) -> /zh-CN/home = 中文
  • /home -> 重定向 (浏览器偏好英文) -> /en/home = 英文

这时我们需要定义一个 store,这里使用 Pinia store,Vuex 同理。

typescript 复制代码
import { usePreferredLanguages, useStorage } from '@vueuse/core';
import { defineStore } from 'pinia';

export const useLangStore = defineStore('lang', {
  state: () => {
    const savedLang = useStorage<string | null>('lang', null, undefined);
    const systemLang = usePreferredLanguages();
    return { savedLang, systemLang };
  },
  getters: {
    lang: (state) => {
      const lang = state.savedLang || state.systemLang[0];
      if (lang.startsWith('zh')) {
        return 'zh-CN';
      } else {
        return 'en';
      }
    },
  },
  actions: {
    setLang(l?: string) {
      if (!l) {
        this.savedLang = null;
      } else {
        this.savedLang = l;
      }
    },
  }
});

这段代码使用了 VueUse 中的 usePreferredLanguages 来获得用户偏好的浏览器语言,并用 useStorage 添加了一个 LocalStorage 中的存储项。

逻辑是:如果用户手动设定了语言(savedLang),则使用之;如果没有,则使用系统偏好的第一个语言。这样,我们只要取 lang 的值就可以得到最终的偏好语言是中文还是英文。

然后,我们需要定义一个路径守卫,以自动处理 URL 中没有语言的情况。

typescript 复制代码
import { createRouter as _createRouter, type RouteLocationNormalized } from 'vue-i18n-routing';
import { createWebHistory } from 'vue-router';
import HomeView from '@/views/HomeView.vue';

const locales = [
  {
    code: 'en',
    iso: 'en-US',
    name: 'English',
  },
  {
    code: 'zh-CN',
    iso: 'zh-CN',
    name: '中文',
  },
  {
    code: '',
    iso: '',
    name: '',
  }
];

export function createRouter(i18n: any) {
  const router = _createRouter(i18n, {
    version: 4,
    locales: locales,
    history: createWebHistory(import.meta.env.BASE_URL),
    routes: [
      {
        path: '/home',
        name: 'home',
        component: HomeView,
      },
    ],
  });
  router.beforeEach((to: RouteLocationNormalized, from: RouteLocationNormalized) => {
    const lang = useLangStore();
    const pathLocale = to.path.split('/')[1];
    if ((!pathLocale) || (!locales.some(locale => locale.code === pathLocale))) {
      return `/${lang.lang}${to.path}`;
    }
  });
  return router;
}

这里需要注意三点:

  1. 我们增加了一个新的空 locales,这样请求才能到达 router.beforeEach
  2. 我们去掉了 defaultLocale
  3. 使用刚才定义的 store:useLangStore() 这行代码必须放在 router.beforeEach 中,而不能放在模块顶端,因为加载模块时 Pinia 还没有启动。

这样,就实现了无语言路径自动跳转到当前偏好语言路径。

5. 导航栏切换按钮

然后,可以在导航栏增加一个按钮,来手动切换语言,例如:

html 复制代码
<script setup lang="ts">
import { useLocalePath, useSwitchLocalePath } from 'vue-i18n-routing';
import { useLangStore } from '@/stores/lang';

const lang = useLangStore();
const { t, locale } = useI18n({ inheritLocale: true, useScope: 'local' });
</script>

<template>
<div
  @click="
    lang.setLang('en');
    router.push(switchLocalePath('en'));
    menuShown = '';
  "
  class="py-2 px-2 gap-2 flex items-center cursor-pointer hover:bg-slate-400/10"
  :class="{ 'text-sky-300': locale == 'en' }"
  role="option"
  tabindex="-1"
  :aria-selected="locale == 'en'"
>
  <IconEnglish class="w-5 h-5 text-slate-400 dark:text-slate-200" />
  English
</div>
<div
  @click="
    lang.setLang('zh-CN');
    router.push(switchLocalePath('zh-CN'));
    menuShown = '';
  "
  class="py-2 px-2 gap-2 flex items-center cursor-pointer hover:bg-slate-400/10"
  :class="{ 'text-sky-300': locale == 'zh-CN' }"
  role="option"
  tabindex="-1"
  :aria-selected="locale == 'zh-CN'"
>
  <IconChinese class="w-5 h-5 text-slate-400 dark:text-slate-200" />
  中文
</div>
</template>

这里,我们在刚才定义的 store 中存储当前手动设定的语言,同时使用 switchLocalePath 来实现路径和语言的切换。

6. SEO 和 Head Meta

同一内容的不同语言版本应该在 head 中进行标注,并指向所有其他替代页面(参见 Google SEO)。这里我们可以在 App.vue 中用 useLocaleHead 和来自 @unhead/vue 包的 useHead 进行设置:

javascript 复制代码
import { useLocaleHead } from 'vue-i18n-routing';
import { useHead } from '@unhead/vue';

const i18nHead = useLocaleHead({ addSeoAttributes: true, defaultLocale: null, strategy: null });

onMounted(() => {
  useHead({
    htmlAttrs: computed(() => ({
      lang: i18nHead.value.htmlAttrs!.lang,
    })),
    link: computed(() => [...(i18nHead.value.link || [])]),
    meta: computed(() => [...(i18nHead.value.meta || [])]),
  });
});

这样就基本实现了一个多语言的国际化站点。可能在进行前端翻译的同时,后端也需要进行翻译,请期待下一期:Python Flask 后端如何接入 i18n 实现国际化多语言!

6. 案例分析

案例:GithubStar.Pro 的前端界面国际化多语言,是使用本文所述的方法实现的,各位可以看看效果。

也欢迎各位使用 GithubStar.Pro 互赞平台,提高您的开源项目知名度,收获更多用户。

相关推荐
小白学习日记2 分钟前
【复习】HTML常用标签<table>
前端·html
丁总学Java44 分钟前
微信小程序-npm支持-如何使用npm包
前端·微信小程序·npm·node.js
yanlele1 小时前
前瞻 - 盘点 ES2025 已经定稿的语法规范
前端·javascript·代码规范
懒羊羊大王呀1 小时前
CSS——属性值计算
前端·css
xgq1 小时前
使用File System Access API 直接读写本地文件
前端·javascript·面试
永远不打烊1 小时前
librtmp 原生API做直播推流
前端
无咎.lsy1 小时前
vue之vuex的使用及举例
前端·javascript·vue.js
fishmemory7sec1 小时前
Electron 主进程与渲染进程、预加载preload.js
前端·javascript·electron
fishmemory7sec2 小时前
Electron 使⽤ electron-builder 打包应用
前端·javascript·electron
长路 ㅤ   2 小时前
cherry-markdown开源markdown组件详细使用教程
vue·markdown组件