Nuxt + @nuxtjs/i18n 使用注意事项

1,247 阅读6分钟

这两天在用@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这个页面,但是为什么在英语和中文页面访问不了呢?

经过我的一番查找,终于在文档的这个位置找到了解释:

image.png

意思是说:对于使用了definePageMeta({name: "..."})命名了路由的页面,由于i18n会覆盖原本nuxt引擎生成的name,将其生成对应的语言风格的name,所以这会破坏自定义的路由命名。

实际上带来的效果就是只有最后一个语言可以得到正确的页面,其他的语言会抛弃这个页面。

解决方案是,使用实验性特性scanPageMeta。

// nuxt.config.ts
export default defineNuxtConfig({
  experimental: {
    scanPageMeta: true,
  }
})

加上这条配置之后,页面终于可以正常跳转到user页面了。