NextJS 国际化 - 原生实现

NextJS 国际化 - 原生实现

技术博客 admin 512 浏览

引言

国际化(Internationalization, 简称 i18n)是一个设计和开发软件的过程, 使我们的系统可以在不同的地区和语言环境中使用, 而不需要进行大规模的改动。对于前端来说这通常涉及到日期、时间、数字、货币和文本的格式化和翻译。

刚好前段时间做的新项目, 需要在 NextJS 项目中增加对国际化的支持, 但是实际项目中我们使用的是社区的一个第三方库 next-intl。所以对于 NextJS 中国际化实现原理其实一直是一知半解的。

本文将借助 NextJs 的自身那一套路由配合中间件来简单实现 i18n, 而这一切就变得很简单了。因为 NextJs 本身也是配套了一些国际化路由的支持, 也可以帮助我们轻松地实现 i18n。下面我们将演示在不依赖任何第三方库情况下, 完成 i18n 配置, 通过这一步我们可以了解到在 NextJsi18n 实现原理。

本文项目最终代码: next-play/tree/i18n-pure

一、定义路由

app 目录下, 创建一个动态路由 [lang] 而所有页面路由都创建在该路由, 这里我们创建几个页面对应路由和页面关系如下:

  • 首页, 路由为 /[lang]
  • demo 页面, 路由为: /[lang]/demo
  • detail 页面, 路由为: /[lang]/detail
  • list 页面, 路由为: /[lang]/list

创建出来的项目, 目录树结果如下:

sh
复制代码
└── app ├── Provider.tsx ├── [lang] │  ├── demo │  │  └── page.tsx │  ├── detail │  │  └── page.tsx │  ├── list │  │  └── page.tsx │  └── post │  └── page.tsx │  ├── page.tsx ├── ....

截图如下:

运行项目后, 浏览器访问 http://localhost:3001/zh-CN/detail 能够正常的展示画面

下面我们调整首页 /[lang]/page.tsx 内容: 使用 Link 来实现不同页面的跳转

js
复制代码
import Link from 'next/link'; const Home = () => { return ( <main className="space-y-10 [&_>*]:block"> <Link href="/en/demo">demo</Link> <Link href="/zh/detail">detail</Link> <Link href="/ja/list">list</Link> <Link href="/ko/post">post</Link> </main> ); }; export default Home;

效果如下: 点击链接、能顺利切换, 并且路由前面都是带有语言标识的

二、获取动态路由参数

在上文我们定义了动态路由 [lang] 下面我们介绍下如何在不同情况下, 获取动态路由 [lang] 中参数值

2.1 客户端组件

在客户端组件中, 我们可以通过 useParams hooks 获取到 URL 中的所有动态路由参数内容, 如下代码所示:

js
复制代码
// src/app/[lang]/post/page.tsx 'use client'; import { useParams } from 'next/navigation'; const Post = () => { const { lang } = useParams(); console.log('%c [ lang ]', 'background:pink; color:#bf2c9f;', lang); return <main>post</main>; }; export default Post;

最终在 浏览器 控制台将输出如下内容:

2.2 page.jsx

NextJS 默认会将动态路由所有参数作为 Page 组件的 props 进行传递, 也就是说在 Page 组件内我们可以通过 props 获取到 URL 中的所有动态路由参数内容, 这里不限制 Page 组件到底是 服务端组件 还是 客户端组件, 都是可以获取到我们需要的内容。

  1. 客户端组件:
js
复制代码
// src/app/[lang]/post/page.tsx 'use client'; const Post = (props) => { console.log('%c [ rest ]', 'background:pink; color:#bf2c9f;', props); return <main>post</main>; }; export default Post;

浏览器 控制台中打印内容如下:

  1. 服务端组件:
js
复制代码
// src/app/[lang]/post/page.tsx const Post = (props) => { console.log('%c [ rest ]', 'background:pink; color:#bf2c9f;', props); return <main>post</main>; }; export default Post;

命令行 终端中打印内容如下:

补充: searchParamsURL 参数, NextJS 也会帮我们解析好传给 Page 组件

2.3 layout.tsx

NextJS 默认会将动态路由所有参数作为 Layout 组件的 props 进行传递, 也就是说在 Layout 组件内我们可以通过 props 获取到 URL 中的所有动态路由参数内容, 这里不限制 Page 组件到底是 服务端组件 还是 客户端组件, 都是可以获取到我们需要的内容。

  1. 客户端组件:
js
复制代码
// src/app/[lang]/layout.tsx 'use client'; export default function RootLayout({ children, ...restProps }: Readonly<{ children: React.ReactNode; }>) { console.log('%c [ restProps ]', 'background:pink; color:#bf2c9f;', restProps); return <>{children}</>; }

浏览器 控制台中打印内容如下:

  1. 服务端组件:
js
复制代码
// src/app/[lang]/layout.tsx export default function RootLayout({ children, ...restProps }: Readonly<{ children: React.ReactNode; }>) { console.log('%c [ restProps ]', 'background:pink; color:#bf2c9f;', restProps); return <>{children}</>; }

命令行 终端中打印内容如下:

补充: 不同于 Page 组件这里是没有 searchParams 参数的

2.4 generateMetadata

在服务端 page.jsxlayout.tsx 组件中, 我们可以导出一个 generateMetadata 方法, 该方法返回一个 Metadata 对象, 通过这种方式我们可以为不同的页面动态的设置 Metadata 值。该方法的第一个参数其实就是 page.jsxlayout.tsxProps 值, 所以在该方法内, 我们其实也是可以拿到所有动态路由参数, 然后我们可以通过不同的动态路由值动态设置 Metadata

js
复制代码
// src/app/[lang]/post/page.tsx import { Metadata } from 'next'; /** 动态设置元数据 */ export const generateMetadata = async (props) => { console.log('%c [ props ]-5', 'background:pink; color:#bf2c9f;', props); return {} as Metadata; }; const Post = () => { return <main>post</main>; }; export default Post;

命令行 终端中打印内容如下:

注意 📢: Layout 页面中 generateMetadata 是没有 searchParams 字段的

js
复制代码
// src/app/[lang]/layout.tsx import { Metadata } from 'next'; /** 动态设置元数据 */ export const generateMetadata = async (props) => { console.log('%c [ props ]-5', 'background:pink; color:#bf2c9f;', props); return {} as Metadata; }; export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return <>{children}</>; }

三、本地化

上面介绍了 NextJS 动态路由 可以帮我们应对不同语言环境, 并将动态路由参数 lang 转发到每个 LayoutPage 页面

接下来我们需要考虑的就是如何根据用户所选的语言环境, 呈现对应的语言内容, 当然这并不是 NextJS 独有的功能。

这里我们则需要提供每个语言环境的 字典 包, 该 字典 提供从某个 到本地化 字符串 的映射对象。针对不同的语言环境加载不同的的字典, 并将页面内容映射为对应语言环境。

3.1 定义字典

如下代码所示, 我们定义了三种语言的字典包

json
复制代码
// src/dictionaries/en.json { "cart": "Add to Cart" }
json
复制代码
// src/dictionaries/zh.json { "cart": "加入购物车" }
json
复制代码
// src/dictionaries/ja.json { "cart": "カートに入れる" }

3.2 使用

开始前, 我们需要写一个方法, 来获取当前语言环境对于的语言包:

  • 这里使用了 import 方法, 目的是为了实现按需加载
  • 同时导出了 getDictionary 方法, 该方法接收一个参数(当前语言环境), 并返回对应语言环境的字典
js
复制代码
// src/app/dictionaries/index.ts const dictionaries = { en: () => import('./en.json').then((module) => module.default), ja: () => import('./ja.json').then((module) => module.default), zh: () => import('./zh.json').then((module) => module.default), } as Record<string, () => Promise<Record<string, string>>>; export const getDictionary = async (locale: string) => dictionaries[locale]();

最后再需要使用字典的地方, 调用 getDictionary 方法, 即可

js
复制代码
import { getDictionary } from '@/dictionaries'; interface PostProps { params: { lang: string; }; } const Post = async ({ params: { lang } }: PostProps) => { const dict = await getDictionary(lang); // en return <main>{dict.cart}</main>; }; export default Post;

3.3 测试

最后看下最终的效果吧

四、参考

参考资料: routing/internationalization

源文:NextJS 国际化 - 原生实现

如有侵权请联系站点删除!

技术合作服务热线,欢迎来电咨询!