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

  • Vue3 如何接入 i18n 实现国际化多语言已关闭评论
  • 63 次浏览
  • A+
所属分类:Web前端
摘要

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


1. 基本方法

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

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

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

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

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

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

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> 块:

<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

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',默认路径为中文。

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

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

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

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 同理。

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 中没有语言的情况。

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. 导航栏切换按钮

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

<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 进行设置:

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 互赞平台,提高您的开源项目知名度,收获更多用户。