NextJS 国际化 - 原生实现

引言

国际化(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

相关推荐
Jonathan Star3 小时前
两个圆形 一个z里面一个z外面,z里面的大,颜色不同 html
前端·html
洛小豆4 小时前
前端开发必备:三种高效定位动态类名元素的 JavaScript 技巧
开发语言·前端·javascript·面试
啵一杯5 小时前
leetcode621. 任务调度器
服务器·前端·数据结构·算法·c#
2401_857297916 小时前
2025校招内推-招联金融
java·前端·算法·金融·求职招聘
琴~~6 小时前
前端全屏模式切换
前端·javascript
东方小月7 小时前
JavaScript中的随机函数Random的妙用
前端·javascript
有盐、在见7 小时前
前段辅助工具分享(像素大厨)
javascript
林九生7 小时前
【React】(推荐项目)使用 React、Socket.io、Nodejs、Redux-Toolkit、MongoDB 构建聊天应用程序 (2024)
前端·mongodb·react.js
每天吃饭的羊7 小时前
ConstructorParameters
前端
Jiaberrr7 小时前
微信小程序攻略:如何验证Token是否即将失效并自动刷新
前端·javascript·微信小程序·小程序·前端框架