使用以下命令从头开始安装一个新的 Next.js 应用程序:
npx create-next-app@latest
安装过程将引导您选择应用程序的名称和其他选项,如下所示。
如果您想按照本教程进行操作,请确保选择以下选项。
✔ 是否要使用 src/
目录?... No
✔ 是否要使用 App Router?(推荐)... Yes
现在,您可以导航到应用程序的根文件夹(例如,在我的情况下是 test-i18n),并使用 npm run dev
运行应用程序。
如果您熟悉 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。
简而言之,每个文件夹表示一个路由段,它映射到一个 URL 段。一个特殊的 page
文件创建了路由的 UI。
在深入研究国际化之前,您需要了解的最后一个概念是动态路由。
根据文档,"当您事先不知道确切的段名并且想要根据动态数据创建路由时,可以使用在请求时填充或在构建时预渲染的动态段。"
可以通过将文件夹的名称放在方括号中来创建动态段:[folderName]
。例如,[language]
或 [id]
。
这很快就会被使用。
让我们创建一个支持国际化的文件夹结构。
在 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>;
}
如果您导航到以下位置,将会发生以下情况:
-
http://localhost:3000/ - 您将看到 Next.js 的欢迎页面。
-
http://localhost:3000/en - 您将看到 Home 组件。您可以将
en
替换为任何其他内容,甚至是不是语言的区域设置。 -
http://localhost:3000/en/about - 您将看到 About 组件。
-
http://localhost:3000/about - 您将看到 Home 组件,因为 "about" 被解释为可能是语言或区域设置的动态段。
现在,我们在 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。
本地化意味着根据用户的首选语言或区域设置更改 "显示的内容"。
根据文档的指导,我们应该在应用程序的 [lang]
文件夹内创建一个名为 dictionaries
的文件夹。
然后,我们需要为每种语言创建一个类似 en.json
的文件,其中 en
将根据语言而变化。根据提供的示例,我得到了以下结果:
然后,我们在 [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
中,并像使用对象一样访问所需的内容。
因此,如果您导航到以下位置:
-
http://localhost:3000/nl - 您将看到 Home in nl: Thuis
-
http://localhost:3000/nl/about - 您将看到 Over
-
http://localhost:3000/es/about - 您将得到一个丑陋的 Error: dictionaries[locale] is not a function. 问题不在于函数本身,而是我们不支持
es
(对不起,es
的使用者,这只是一个示例)。我们应该更优雅地处理这个问题。
为了解决这个错误,我们可以添加一个条件来检查语言,并在使用不受支持的语言时返回默认语言。
这个条件应该在每个组件中使用,所以我们可能会将其提取出来,并在某个 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 来设置语言。
对于这个示例,我对一个简单的解决方案感到满意,它的工作原理如下:
-
如果没有选择语言,则默认为英语,例如 http://localhost:3000/en
-
如果选择了语言,则使用该语言
-
如果 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 来获取和保存用户的本地化偏好的可能性。