首页
开发技巧正文内容

使用 Next.js App Router 进行本地化

2024年04月22日
阅读时长 2 分钟
阅读量 7
使用 Next.js App Router 进行本地化

使用新的 App Router 为 Next.js 应用程序进行本地化和国际化

使用以下命令从头开始安装一个新的 Next.js 应用程序:

npx create-next-app@latest

安装过程将引导您选择应用程序的名称和其他选项,如下所示。

使用 App Router 创建新的 Next.js 应用程序

如果您想按照本教程进行操作,请确保选择以下选项。

✔ 是否要使用 src/ 目录?... No ✔ 是否要使用 App Router?(推荐)... Yes

现在,您可以导航到应用程序的根文件夹(例如,在我的情况下是 test-i18n),并使用 npm run dev 运行应用程序。

App Router

如果您熟悉 App Router,则可以跳过此部分。

文档中了解到以下信息:

  • App Router 在名为 app 的目录中工作。

  • 您可以与 Pages Router 一起使用 App Router,此时前者优先。

  • 默认情况下,app 目录中的组件是 React Server Components

  • Next.js 使用基于文件系统的路由器,因此 app 目录中的每个文件夹都表示一个路由。

  • 要在定义的路由上创建一些 UI,只需在路由文件夹中添加一个名为 page.js 的文件(扩展名可以是 .jsx 或 .tsx)。Page 是用于创建路由的 UI 并使路由公开访问的特殊文件。同一文件夹中的其他文件将无法公开访问。

在下面的图片中,您可以在 settings 文件夹中添加一个 page.tsx 文件,以创建一个在 acme.com/dashboard/settings 上可用的 UI。

Next.js 文件系统路由器

简而言之,每个文件夹表示一个路由段,它映射到一个 URL 段。一个特殊的 page 文件创建了路由的 UI。

动态路由

在深入研究国际化之前,您需要了解的最后一个概念是动态路由。

根据文档,"当您事先不知道确切的段名并且想要根据动态数据创建路由时,可以使用在请求时填充或在构建时预渲染的动态段。"

可以通过将文件夹的名称放在方括号中来创建动态段:[folderName]。例如,[language][id]

这很快就会被使用。

Next.js App Router 国际化 - i18n

让我们创建一个支持国际化的文件夹结构。

app 文件夹内,创建一个动态文件夹路由,并将其命名为 [lang]

[lang] 内部,我添加了几个更多的文件夹,它们将成为 URL 路由段。在每个文件夹内,我添加了一个 page.tsx 文件来创建 UI。

文件夹结构如下所示:

app/
  [lang]
    about/page.tsx
    cart/page.tsx
    products/page.tsx
    page.tsx

每个 page.tsx 文件都是一个简单的 React 函数组件,如下所示:

// app/[lang]/page.tsx

export default function Home() {
  return <div>Home</div>;
}

如果您导航到以下位置,将会发生以下情况:

Next.js 应用程序与 App Router - 用于国际化的文件夹结构

组件中的语言

现在,我们在 URL 中有了语言,但我们可能希望在组件内部使用它来本地化语言。

这是一个 JavaScript 组件的示例:


export default function Home({ params: { lang } }) {
  console.log(lang);

  return <div>Home - Language: {lang}</div>;
}

这是它的 TypeScript 对应组件:

type Props = {
  params: { [lang: string]: string };
};

export default function Home({ params: { lang } }: Props) {
  console.log(lang);

  return <div>Home - Language: {lang}</div>;
}

请注意,由于默认情况下,app 目录中的组件是 React Server Components,因此 console.log 将在终端中输出。

现在我们知道了语言,我们可以继续本地化 UI。

Next.js App Router 本地化 - l10n

本地化意味着根据用户的首选语言或区域设置更改 "显示的内容"。

根据文档的指导,我们应该在应用程序的 [lang] 文件夹内创建一个名为 dictionaries 的文件夹。

然后,我们需要为每种语言创建一个类似 en.json 的文件,其中 en 将根据语言而变化。根据提供的示例,我得到了以下结果:

Next.js App Router 本地化

然后,我们在 [lang] 文件夹中创建一个 dictionaries.js 文件。

// app/[lang]/dictionaries.js

import "server-only";

const dictionaries = {
  en: () => import("./dictionaries/en.json").then((module) => module.default),
  nl: () => import("./dictionaries/nl.json").then((module) => module.default),
};

export const getDictionary = async (locale) => dictionaries[locale]();

最后,我们可以使用 getDictionary 函数来访问正确的字典,从而本地化应用程序的内容,如下所示:

import { getDictionary } from "./dictionaries";

...

export default async function Home({ params: { lang } }: Props) {
  console.log(lang);
  const dict = await getDictionary(lang);

  return <div>Home in {lang}: {dict.home}</div>;
}

请注意,在组件中:

  • 导入 getDictionary 函数

  • 将函数声明为 async,以便我们可以使用 await 关键字

  • 一旦获取到正确的字典,我们将其保存在 dict 中,并像使用对象一样访问所需的内容。

因此,如果您导航到以下位置:

为了解决这个错误,我们可以添加一个条件来检查语言,并在使用不受支持的语言时返回默认语言。

这个条件应该在每个组件中使用,所以我们可能会将其提取出来,并在某个 utils 中声明它。但是然后我们需要导入它并在每个组件中调用它。

对于这种情况,我们可以利用中间件。

用于本地化的中间件

您可以将中间件视为位于服务器和应用程序之间的中间层,松散地位于 CDN 级别,例如在服务器和应用程序之间。

在这个层中,我们可以设置一个中间件来转换一些请求以适应我们的需求。

例如,我们希望中间件层将所有不受支持的语言转换为 en

要使用中间件,我们首先在根目录中创建一个 middleware.ts 文件,与 app 文件夹处于同一级别。

// middleware.ts

let locales = ["en", "nl"];

export function middleware(request) {
  // 检查路径名中是否有任何受支持的语言环境
  const { pathname } = request.nextUrl;
  const pathnameHasLocale = locales.some(
    (locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
  );

  if (pathnameHasLocale) return;

  // 如果没有语言环境,则重定向
  const locale = "en";
  request.nextUrl.pathname = `/${locale}${pathname}`;
  // 例如,如果传入的请求是 /products
  // 新的 URL 将是 /en/products
  return Response.redirect(request.nextUrl);
}

export const config = {
  matcher: [
    // 跳过所有内部路径(_next)
    "/((?!_next).*)",
    // 仅在根(/)URL 上运行
    "/",
  ],
};

该中间件是根据文档进行调整的,原因如下:

  • 文档依赖于两个不同的库来完成工作,而我不想安装更多的库。

  • 他们有一个通用的解决方案,可以轻松扩展以考虑更多的用例,并最终使用 cookie 来设置语言。

对于这个示例,我对一个简单的解决方案感到满意,它的工作原理如下:

  1. 如果没有选择语言,则默认为英语,例如 http://localhost:3000/en

  2. 如果选择了语言,则使用该语言

  3. 如果 URL 不包含语言,则添加 en 并保留其他段。例如,如果传入的请求(到中间件)是 http://localhost:3000/about,则默认为 http://localhost:3000/en/about请注意,如果在中间件中添加 console.log,每次路由更改时都会看到多个日志。这是因为中间件在项目中的每个路由上都会被调用。因此,如果您想定义中间件应该运行的特定路径,请阅读匹配路径

上面示例中的配置仅在 / 上运行中间件。

您可以在https://erichowey.dev/找到使用 Cookies 的更全面的解决方案。

通过设置中间件,我们可以创建一种改变语言的方式。

切换按钮以更改语言环境

一种常见的改变语言的方式是创建一个语言切换组件,其中包含一些按钮或其他元素来切换语言。

// app/components/LangSwitcher.tsx

import Link from "next/link";

export default function LangSwitcher() {
  return (
    <>
      <Link href="/en"> EN </Link>
      <Link href="/nl"> NL </Link>
    </>
  );
}

在这一点上,无论您在何处需要,都可以导入该组件,用户将能够点击首选语言。

这将相应地更改 URL 和内容。

在接下来的内容中,我将添加使用 cookies 来获取和保存用户的本地化偏好的可能性。

免责声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

相关文章

探索多种软件架构模式及其实用应用
2024年11月22日19:06
本文深入探讨了多种软件架构模式,包括有界上下文、边车模式、发布-订阅模式、应用网关、微服务、命令职责分离(CQRS)等,介绍了它们的优点、使用场景以及具体应用实例。文章强调根据具体项目需求和团队能力选择最合适的架构,以构建高效和可维护的解决方案,同时展示了各架构模式间的综合应用,提供了丰富的案例和技术细节。
15个高级Python快捷键助您更快编程
2024年11月21日07:02
本文分享了 15 个高级的 Python 编程快捷键,包括上下文管理器、行内字典合并、函数参数解包、链式比较、dataclasses、海象运算符、反转列表、备忘录缓存、splitlines、enumerate、字典推导、zip 用于并行迭代、itertools.chain 扁平化列表、functools.partial 部分函数和 os.path 文件路径管理等,帮助开发者提高编程效率和代码简洁性。
揭示网页开发的 11 个迷思:停止相信这些误区
2024年11月19日22:05
网页开发充满误解,这篇博文针对11个常见迷思进行揭秘。包括网站开发后不需更新、需掌握所有技术、AI会取代开发者等。强调持续学习、专业化、用户体验的重要性,澄清误区如多任务处理的必要性和最新技术的必需性。文章提醒开发者注重实用而非追求完美代码,以务实态度面对开发工作。
你知道 CSS 的四种 Focus 样式吗?
2024年11月18日21:41
本文介绍了四种 CSS focus 样式::focus、:focus-visible、:focus-within 以及自定义的 :focus-visible-within,帮助提升网站用户体验。:focus 样式应用于被选中元素;:focus-visible 仅在键盘导航时显示;:focus-within 用于父元素;自定义 :focus-visible-within 结合两者效果。合理运用这些样式能使网站更方便键盘用户导航。
利用 Python 实现自动化图像裁剪:简单高效的工作流程
2024年11月11日20:49
使用 Python 和 OpenCV 自动裁剪图像,轻松实现 16:9 的完美构图。这个指南介绍了如何通过代码进行灰度化、模糊处理和边缘检测,最终识别出最重要的部分进行裁剪。特别适合需要批量处理图像的情况,节省大量时间。
每位资深前端开发人员都应了解的 TypeScript 高级概念
2024年11月11日02:07
资深前端开发者应了解 TypeScript 的高级概念,如联合类型、交叉类型、类型保护、条件类型、映射类型、模板字面量类型和递归类型。这些特性可提升代码的可维护性和可扩展性,确保在开发复杂应用时实现更高的类型安全性和效率。