这两天在用@nuxtjs/i18n改造公司的Nuxt项目,增加一些国际化支持,遇到一些坑,在此记录一下。
安装使用
按照nuxt官网的i18n介绍,直接下载并安装即可。
可以直接通过安装的文档:
npx nuxi@latest module add i18n
不过我是直接yarn安装的,其实也是可以:
yarn add @nuxtjs/i18n
然后在nuxt.config.js中的modules中添加模块使用声明,并进行语言配置:
{
modules: [
"@nuxtjs/i18n"
],
i18n: { // 模块相关配置
vueI18n: './i18n.config.ts', // 文本配置文件
lgacy: false, // 禁用vueI18n的Legacy语法
locales: [en, 'zh-CN', 'zh-HK'], // 设置语言列表
defaultLocale: 'en', // 设置默认语言
},
}
然后开始编辑i18n.config.ts。
import en_us from '@/languages/en-us'
import zh_cn from '@/languages/zh-cn'
import zh_hk from '@/languages/zh-hk'
export const i18nDefined = {
'en': en_us,
'zh-CN': zh_cn,
'zh-HK': zh_hk,
}
export default defineI18nConfig(() => ({
legacy: false,
locale: 'en',
messages: i18nDefined,
}))
这里我将三种语言提出为三个文件:分别处理英语、中文、粤语繁体。
但是后来我又改成了一种集中型的格式:
// language/index.ts
import {Language} from "~/types/Language";
const comparison = {
...
notFillError: {
[Language.English]: "Please fill out this field.",
[Language.Chinese]: "该字段为必填",
[Language.Traditional]: "該欄位為必填項",
},
continue: "Continue",
...
}
// 获取对应语言的文本对照表
export function messageFor(language: Language) {
function message(obj) {
// 用来兼容默认配置,对应上面的continue
if (typeof obj === 'string') {
return obj
}
// 错误处理
if (!obj) {
return ''
}
// 返回对应语言的文本对照表
if (obj[language] && typeof obj[language] === 'string') {
return obj[language]
}
// 以下是递归处理,最后产生正确的树形结构
const ret = {}
const keys = Object.keys(obj)
for (const key of keys) {
ret[key] = message(obj[key])
}
return ret
}
return message(comparison)
}
export default comparison
我将原本分散的文本对照表放在一起处理,这样可以更直观地知道有哪些语言没有填写对应的文本翻译,也方便进行修改。为了达到这样一个效果,我写了一个messageFor函数,用来获取对应语言的文本对照表。然后在三种语言对应的文件中引入对应的语言,我这里只展示en-us的配置,其他两种语言都是一样的:
// language/en-us.ts
import { Language } from "~/types/Language";
import { messageFor } from "./index";
export default {
...messageFor(Language.English)
}
@nuxtjs/i18n原理介绍
@nuxtjs/i18n默认是利用路由做语言切换的。使用nuxt开发的人应该知道,nuxt是不需要写路由配置的,项目根目录下的pages目录的文件结构就是路由表。
例如假设目录结构是这样的项目:
/pages/
/home.vue
/about.vue
/index.vue
/user.vue
/user/
/overview.vue
/personal.vue
/settings.vue
其实就会对应生成下面这样的路由表:
[{
name: 'home',
path: '/home',
component: () => import("@/pages/home.vue"),
}, {
name: 'about',
path: '/about',
component: () => import("@/pages/about.vue"),
}, {
name: 'index',
path: '/',
component: () => import("@/pages/index.vue"),
}, {
name: 'user',
path: '/user',
component: () => import("@/pages/user.vue"),
children: [{
name: 'user-overview',
path: '/user/overview',
component: () => import("@/pages/user/overview.vue"),
}, {
name: 'user-personal',
path: '/user/personal',
component: () => import("@/pages/user/personal.vue"),
}, {
name: 'user-settings',
path: '/user/settings',
component: () => import("@/pages/user/settings.vue"),
},]
}]
大致是这么一个结构,具体的可以查阅nuxt的官网了解。
但是使用了@nuxtjs/i18n(以下简称i18n)情况就稍微有一点不一样了,我们在配置中,有这么两条配置:
{
i18n: {
...
locales: [en, 'zh-CN', 'zh-HK'], // 设置语言列表
defaultLocale: 'en', // 设置默认语言
}
}
其中locales除了是用来表示该项目支持的语言列表之外,其实还是告诉nuxt引擎,我们有三种语言需要生成三种路由。
而defaultLocale则是声明默认的路由表是什么。
简单来说就是:同样一个home路由,原本只会有一个/home,但是引入了i18n之后,实际上会生成三个路由,分别是/home,/zh-CN/home, /zh-HK/home, 一个路由对应着一个语言的页面。
有人会奇怪为什么要这样处理,只用一个页面,然后切换语言的时候只更换文本不行吗?其实是可以的,i18n官网也有这样做的方案解释。
那为什么还要这样处理?因为是为了更好的SEO。我们使用nuxt搭建项目,一个很重要的原因就是为了提供更好的SEO性能,特别是面向C端的项目,SEO是特别重要的事情。而i18n采用修改路由的方式生成几份页面,这样一来搜索引擎就可以爬取到各种语言的数据,方便不同的用户都能搜索到我们的页面。
坑一,setLocale方法的使用
const { setLocale } = useI18n()
setLocale是i18n提供一个修改本地语言的一个方法,但是如果我们直接使用它会发现其实什么都不会改变。仔细翻阅文档会发现,setLocale是vue-i18n提供的方法,它在@nuxtjs/i18n中并不会起到什么效果。
坑二,切换语言
那么setLocale方法无效的话,应该怎么切换当前页的语言呢?
答案是使用localePath方法。
const localePath = useLocalePath()
localePath接收两个参数,一个是目标路由,一个是目标语言,通过下面这种方法,即可切换语言:
navigateTo(localPath(route.path, language), { replace: true })
这其实就是跳转到与当前路由相同的另一个语言的页面。在那个页面上就会是使用正确语言渲染的页面。
之后还需要使用<nuxt-link-locale>
组件替换所有的<nuxt-link>
或者<route-link>
组件。保证路由跳转的部分正确。
<nuxt-link to='path'>label</nuxt-link>
<route-link to='path'>label</route-link>
<!-- 将上面这种形式改为下面这种形式 -->
<nuxt-link-locale to='path'>label</nuxt-link-locale>
如果是使用代码跳转的,需要使用localePath来获取正确的路由。
navigateTo(target) // 原来
navigateTo(localPath(target)) // 修改后
坑三,pageName
其实上面两个都不算是坑,顶多是使用不熟练,不过是API使用的问题而已。
接下来这个才是大坑。
我在user.vue中使用nuxt的definePageMeta设置了页面的name。
// /pages/user.vue
definePageMeta({
name: "user"
})
好家伙,这个操作直接导致user这个路由丢失了,跳转到/user这个路由的时候,会直接跳转到404页面,也就是说无法跳转到user这个页面。
为了找到这个bug出现的原因,我花了一个上午的时间。
最后在打开nuxt的开发者工具之后发现Pages这一栏中没有/user这个路由,反而有/zh-HK/user这个路由,也就是说,在粤语页面可以访问到user这个页面,但是为什么在英语和中文页面访问不了呢?
经过我的一番查找,终于在文档的这个位置找到了解释:
意思是说:对于使用了definePageMeta({name: "..."})命名了路由的页面,由于i18n会覆盖原本nuxt引擎生成的name,将其生成对应的语言风格的name,所以这会破坏自定义的路由命名。
实际上带来的效果就是只有最后一个语言可以得到正确的页面,其他的语言会抛弃这个页面。
解决方案是,使用实验性特性scanPageMeta。
// nuxt.config.ts
export default defineNuxtConfig({
experimental: {
scanPageMeta: true,
}
})
加上这条配置之后,页面终于可以正常跳转到user页面了。