Vue3 如何接入 i18n 实现国际化多语言
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 站点的爬取机制如下:
- 类似 Vue 站点的 JS 动态页面是可以被爬取的,不影响权重 (参见 Google SEO)。
- 与用户首选语言匹配的页面将优先展示 (参见 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;
}
这里需要注意三点:
- 我们增加了一个新的空
locales
,这样请求才能到达router.beforeEach
。 - 我们去掉了
defaultLocale
。 - 使用刚才定义的 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 互赞平台,提高您的开源项目知名度,收获更多用户。