使用next-intl实现国际化能力

April 17, 2024

NextBlogger项目中之前的各种文案是hardcode的,无法实现多语言的适配。最近了解到了 SaaS-Boilerplate介绍 中的 next-intl 的能力,所以将其引入项目,实现国际化能力。

背景介绍

Next.js实现国际化有多种方案,可以分位next.js原生和第三方lib两类。

next原生方案

next.js原生自v10.0.0开始就加入了国际化(i18n)的支持,只需要按照其文档一步步接入即可。

大致接入流程:

  1. 配置 next.config.js ,指定locale列表、域名默认locale
  2. 中间件配置

    国际化需要在中间件中对path等进行处理,常见的包括:

    • 对path中的locale参数校验、解析等处理,比如根据用户域名、浏览器设置重定向到默认locale
    • 跳过非locale类path的校验。比如公共资源、api无需locale
  3. page中通过router取出locale数据进行处理
  4. 其他
    1. next/link 或 next/router 跳转时需要指定locale参数
    2. cookie:默认会在cookie中种植 NEXT_LOCALE 参数记录locale
  5. 静态生成页面的特殊处理

具体使用方式详见官方文档

虽然整个接入过程不算复杂,但是细节较多,而且也不提供对翻译文件之类的处理,需要适用方自行考虑解决方案。所以才有了其他的第三方方案。

第三方lib

本文仅介绍 next-intl 的接入方式。

next-intl介绍

next-intl是一个用于Next.js的国际化插件,它提供了基于React Intl的国际化解决方案。这个库支持多语言文本和格式化,为Next.js应用提供了强大的国际化功能。

在使用next-intl时,仅需要进行一些简单的配置,比如在next.config.js中设置i18n对象,指定支持的语言和默认语言。然后创建用于保存翻译文件的文件夹,并在其中创建相应语言的JSON文件,以保存每种语言的翻译内容。设置完毕后就可以在代码中快速获取对应语言版本的文案了。

除了基础的配置和使用外,next-intl还提供了丰富的API和功能,使得开发者能够更灵活地处理国际化相关的需求。例如,它支持动态切换语言,可以根据用户的偏好或浏览器的语言设置来自动选择语言。此外,next-intl还提供了格式化日期、数字和货币等功能,使得国际化内容更加符合目标语言的文化习惯。

基础接入流程

NextBlogger项目使用App路由,此处介绍其基础接入流程。

翻译文件准备

翻译文件可以借助crowdin等平台来实现自动翻译。

next.config配置

配置 next.config.mjs 以及定义 i18n.ts ,目的是为了根据请求locale动态加载不同的翻译文件,供后续在服务端、客户端组件中使用。

中间件配置

中间件需要引入locales配置,指定 locales 相关参数定义,用于路由判断。主要包括以下几个参数:

  • locales:支持的语言列表
  • localePrefix:路由的处理方式,可以控制在URL path中是否出现locale,详见文档。项目中使用的是 as-needed ,即默认locale时不需要出现在path中。
  • defaultLocale:默认locale

全局layout处理

在全局layout中通常做以下几件事:

  • 判断locale合法性,异常请求拦截
  • 在输出的html中增加lang property,主要用于浏览器的拼写检查、SEO优化等。
Loading...

服务端组件中的应用

提供了同步和异步接口,具体使用方法详见文档。项目中的典型用法:

Loading...

客户端组件的应用

官方文档提供了4种不同的用法。项目中使用了 NextIntlClientProvider 来包裹。详见代码

locale切换

页面中需要提供语言切换的入口。

  • dashboard/layout 中引入了LocaleSwitcher组件,提供语言切换的组件。
  • LocaleSwitch封装 next-intl/navigation 中更新locale的能力,当用户点击切换button时实现参数切换、路由跳转等。

静态化遇到的问题

NextBlogger项目使用了next.js静态生成的能力,在引入next-intl的过程中遇到了一些小问题,此处记录供后续参考。

next-intl 的API会将组件改为动态渲染,所以如果直接编译静态化页面会报错。官方承诺会在后续的版本中解决,但是目前只能采取对应的临时解决方案

locale参数的生成

静态化编译必须要在编译期间列出可能出现的语言列表,并完成各静态页面的多个语言版本的编译。

重点:必须在 [locale]/layout.tsxgenerateStaticParams 中指定所有语言列表。

nextjs会结合layout和子page文件中的 generateStaticParams ,生成完整的排列组合列表跟到页面组件进行编译时静态渲染。比如:

Loading...

编译时会生成各语言的编译结果页。

Loading...

编译时长以及内存占用没有明显变化(比如根据语言数量翻倍),猜测是locale无关的组件编译结果被复用了。

 

next-intl 提供了一个临时的 API unstable_setRequestLocale,该 API 可以接收并分发通过页面和布局参数传递的 locale 设置,以便在整个请求过程中渲染的服务器端组件能够使用这个设置来提供多语言支持。

注意:必须在所有支持locale并且启用静态生成(存在 generateStaticParams )的相关layout、page中设置(公共组件不需要)。否则可能会遇到以下错误:

⚠️
Error: Usage of next-intl APIs in Server Components currently opts into dynamic rendering. This limitation will eventually be lifted, but as a stopgap solution, you can use the unstable_setRequestLocale API to enable static rendering, see https://next-intl-docs.vercel.app/docs/getting-started/app-router-server-components#static-rendering

 

奇怪的not-found

为了实现locale相关自定义404页面,我在[locale]目录下增加了not-found组件。为了实现静态渲染,所以增加了对应的generateStaticParams,目标是为了在编译阶段就生成好各语言的错误页。

Loading...

奇怪的是在编译阶段报了一些奇怪的错误

Loading...

看提示跟not-found组件无关,而是在编译article子页面的时候报错。这个问题定位了很久,知道看到官网介绍的方法,去掉NotFoundPage组件中的参数后才恢复。正确的做法:

Loading...

其实在next官网上也注明了:

not-found.js components do not accept any props. 【app route】

 

not-found还有两个特性:

  • 全局兜底的 app/not-found.js 必须存在,用于捕获遗漏的404.
  • 自定义 not-found.js何时被启用: 在page中使用notFound() 抛出异常时,next会查找最近的 not-found.js 并启用。

猜测在编译article目录下的page时,会遇到被自定义not-found实现间接影响的原因,是因为在页面中使用了notFound()抛出异常。next.js为了静态渲染响应速度,应该也将not-found组件编译了。

PS:nextjs的编译错误提示还真是晦涩,不知道是否有更有的方法能给出ts代码行等。

 

next-intl 的接入整体来说还是比较便捷的,只是在静态生成上面遇到了一些小问题。从看文档到接入不超过两天(空闲时间)。不过unstable_setRequestLocale 这种API的引入总让人担心后其后续的稳定性。

See all postsSee all posts