next-intl-app-router

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

next-intl (App Router)

next-intl(App Router)

Setup and usage of
next-intl
with prefix-based locale routing (e.g.
/en/about
,
/ja/about
). Use this skill in any Next.js App Router project.
Example code: Copy-paste examples live in this skill's examples/ folder. See examples/README.md for where each file goes in your project.
在Next.js App Router中设置并使用
next-intl
实现基于前缀的区域路由(例如
/en/about
/ja/about
)。可在任意Next.js App Router项目中使用本方案。
示例代码: 可直接复制粘贴的示例存放在本技能的examples/文件夹中。查看examples/README.md了解各文件在项目中的存放位置。

File layout

文件布局

Keep this structure:
├── messages/
│   ├── en.json
│   ├── ja.json
│   └── ...
├── next.config.ts
└── src/
    ├── i18n/
    │   ├── request.ts
    │   ├── routing.ts
    │   └── navigation.ts
    ├── proxy.ts          # Next.js 16+ (was middleware.ts)
    └── app/
        ├── layout.tsx    # Root layout, no NextIntlClientProvider here
        └── [locale]/
            ├── layout.tsx
            ├── page.tsx
            └── ...
Root layout does not wrap with
NextIntlClientProvider
; only
app/[locale]/layout.tsx
does.

请保持如下文件结构:
├── messages/
│   ├── en.json
│   ├── ja.json
│   └── ...
├── next.config.ts
└── src/
    ├── i18n/
    │   ├── request.ts
    │   ├── routing.ts
    │   └── navigation.ts
    ├── proxy.ts          # Next.js 16+(原middleware.ts)
    └── app/
        ├── layout.tsx    # 根布局,此处无需NextIntlClientProvider
        └── [locale]/
            ├── layout.tsx
            ├── page.tsx
            └── ...
根布局不需要
NextIntlClientProvider
包裹;仅
app/[locale]/layout.tsx
需要。

1. Next config

1. Next.js配置

Wire the plugin (default path
./i18n/request.ts
):
ts
// next.config.ts
import type { NextConfig } from "next";
import createNextIntlPlugin from "next-intl/plugin";

const nextConfig: NextConfig = {
  /* ... */
};
const withNextIntl = createNextIntlPlugin();
export default withNextIntl(nextConfig);
Custom path:
createNextIntlPlugin('./src/i18n/request.ts')
.

引入插件(默认路径为
./i18n/request.ts
):
ts
// next.config.ts
import type { NextConfig } from "next";
import createNextIntlPlugin from "next-intl/plugin";

const nextConfig: NextConfig = {
  /* ... */
};
const withNextIntl = createNextIntlPlugin();
export default withNextIntl(nextConfig);
自定义路径:
createNextIntlPlugin('./src/i18n/request.ts')

2. Routing config

2. 路由配置

Central config in
src/i18n/routing.ts
:
ts
import { defineRouting } from "next-intl/routing";

export const routing = defineRouting({
  locales: ["en", "ja", "zh-CN", "zh-TW"],
  defaultLocale: "en",
});

src/i18n/routing.ts
中配置核心路由规则:
ts
import { defineRouting } from "next-intl/routing";

export const routing = defineRouting({
  locales: ["en", "ja", "zh-CN", "zh-TW"],
  defaultLocale: "en",
});

3. Request config

3. 请求配置

src/i18n/request.ts
: resolve locale from the
[locale]
segment and load messages.
ts
import { getRequestConfig } from "next-intl/server";
import { hasLocale } from "next-intl";
import { routing } from "./routing";

export default getRequestConfig(async ({ requestLocale }) => {
  const requested = await requestLocale;
  const locale = hasLocale(routing.locales, requested)
    ? requested
    : routing.defaultLocale;

  return {
    locale,
    messages: (await import(`../../messages/${locale}.json`)).default,
  };
});

src/i18n/request.ts
:从
[locale]
路由段解析区域设置并加载对应消息文件。
ts
import { getRequestConfig } from "next-intl/server";
import { hasLocale } from "next-intl";
import { routing } from "./routing";

export default getRequestConfig(async ({ requestLocale }) => {
  const requested = await requestLocale;
  const locale = hasLocale(routing.locales, requested)
    ? requested
    : routing.defaultLocale;

  return {
    locale,
    messages: (await import(`../../messages/${locale}.json`)).default,
  };
});

4. Proxy / middleware (Next.js 16)

4. 代理/中间件(Next.js 16)

Next.js 16 uses
proxy.ts
instead of
middleware.ts
. Same API:
ts
// src/proxy.ts
import createMiddleware from "next-intl/middleware";
import { routing } from "./i18n/routing";

export const proxy = createMiddleware(routing);

export const config = {
  matcher: "/((?!api|trpc|_next|_vercel|.*\\..*).*)",
};
Matcher: all pathnames except
/api
,
/trpc
,
/_next
,
/_vercel
, and paths containing a dot (e.g.
favicon.ico
).

Next.js 16使用
proxy.ts
替代
middleware.ts
,API保持一致:
ts
// src/proxy.ts
import createMiddleware from "next-intl/middleware";
import { routing } from "./i18n/routing";

export const proxy = createMiddleware(routing);

export const config = {
  matcher: "/((?!api|trpc|_next|_vercel|.*\\..*).*)",
};
匹配规则:所有路径名,除了
/api
/trpc
/_next
/_vercel
以及包含点的路径(例如
favicon.ico
)。

5. Navigation helpers

5. 导航工具

Use project navigation wrappers so links keep the current locale:
ts
// src/i18n/navigation.ts
import { createNavigation } from "next-intl/navigation";
import { routing } from "./routing";

export const { Link, redirect, usePathname, useRouter, getPathname } =
  createNavigation(routing);
In components: import
Link
(and others) from
@/i18n/navigation
, not from
next/navigation
or
next/link
, for locale-aware URLs. Example: examples/Nav-client.tsx, examples/BackToHomeButton.tsx.

使用项目封装的导航工具,确保链接保留当前区域设置:
ts
// src/i18n/navigation.ts
import { createNavigation } from "next-intl/navigation";
import { routing } from "./routing";

export const { Link, redirect, usePathname, useRouter, getPathname } =
  createNavigation(routing);
在组件中:从
@/i18n/navigation
导入
Link
(及其他工具),不要
next/navigation
next/link
导入,以支持区域感知的URL。示例:examples/Nav-client.tsxexamples/BackToHomeButton.tsx

6. Locale layout and static rendering

6. 区域布局与静态渲染

app/[locale]/layout.tsx
must (full file: examples/app-locale-layout.tsx):
  1. Validate
    locale
    with
    hasLocale
    notFound()
    if invalid.
  2. Call
    setRequestLocale(locale)
    for static rendering.
  3. Wrap children with
    NextIntlClientProvider
    and
    getMessages()
    .
tsx
// app/[locale]/layout.tsx
import { NextIntlClientProvider, hasLocale } from "next-intl";
import { setRequestLocale } from "next-intl/server";
import { notFound } from "next/navigation";
import { routing } from "@/i18n/routing";
import { getMessages } from "next-intl/server";

type Props = {
  children: React.ReactNode;
  params: Promise<{ locale: string }>;
};

export function generateStaticParams() {
  return routing.locales.map((locale) => ({ locale }));
}

export default async function LocaleLayout({ children, params }: Props) {
  const { locale } = await params;
  if (!hasLocale(routing.locales, locale)) notFound();

  setRequestLocale(locale);
  const messages = await getMessages();

  return (
    <NextIntlClientProvider messages={messages}>
      {children}
    </NextIntlClientProvider>
  );
}

app/[locale]/layout.tsx
必须满足以下要求(完整文件:examples/app-locale-layout.tsx):
  1. 使用
    hasLocale
    验证
    locale
    → 若无效则调用
    notFound()
  2. 调用
    setRequestLocale(locale)
    以支持静态渲染。
  3. NextIntlClientProvider
    包裹子组件并传入
    getMessages()
    获取的消息。
tsx
// app/[locale]/layout.tsx
import { NextIntlClientProvider, hasLocale } from "next-intl";
import { setRequestLocale } from "next-intl/server";
import { notFound } from "next/navigation";
import { routing } from "@/i18n/routing";
import { getMessages } from "next-intl/server";

type Props = {
  children: React.ReactNode;
  params: Promise<{ locale: string }>;
};

export function generateStaticParams() {
  return routing.locales.map((locale) => ({ locale }));
}

export default async function LocaleLayout({ children, params }: Props) {
  const { locale } = await params;
  if (!hasLocale(routing.locales, locale)) notFound();

  setRequestLocale(locale);
  const messages = await getMessages();

  return (
    <NextIntlClientProvider messages={messages}>
      {children}
    </NextIntlClientProvider>
  );
}

7. Pages under
[locale]

7.
[locale]
下的页面

For static rendering, every page under
[locale]
that uses next-intl must call
setRequestLocale(locale)
(and use
use(params)
if needed). Examples: app-locale-page.tsx, app-locale-about-page.tsx. (and use
use(params)
if needed). Layout already sets it; pages that render server components using locale should set it too.
tsx
// app/[locale]/page.tsx
import { use } from "react";
import { setRequestLocale } from "next-intl/server";

export default function IndexPage({
  params,
}: {
  params: Promise<{ locale: string }>;
}) {
  const { locale } = use(params);
  setRequestLocale(locale);
  return <TokyoPage />;
}
tsx
// app/[locale]/about/page.tsx
import { use } from "react";
import { setRequestLocale } from "next-intl/server";
import AboutContainer from "./components/AboutContainer";

export default function AboutPage({
  params,
}: {
  params: Promise<{ locale: string }>;
}) {
  const { locale } = use(params);
  setRequestLocale(locale);
  return <AboutContainer />;
}
Call
setRequestLocale
before any
next-intl
APIs in that layout/page.

对于静态渲染,
[locale]
下所有使用next-intl的页面必须调用
setRequestLocale(locale)
(必要时使用
use(params)
)。示例:app-locale-page.tsxapp-locale-about-page.tsx。(必要时使用
use(params)
)。布局已设置该方法;使用区域设置渲染服务端组件的页面也需调用。
tsx
// app/[locale]/page.tsx
import { use } from "react";
import { setRequestLocale } from "next-intl/server";

export default function IndexPage({
  params,
}: {
  params: Promise<{ locale: string }>;
}) {
  const { locale } = use(params);
  setRequestLocale(locale);
  return <TokyoPage />;
}
tsx
// app/[locale]/about/page.tsx
import { use } from "react";
import { setRequestLocale } from "next-intl/server";
import AboutContainer from "./components/AboutContainer";

export default function AboutPage({
  params,
}: {
  params: Promise<{ locale: string }>;
}) {
  const { locale } = use(params);
  setRequestLocale(locale);
  return <AboutContainer />;
}
在该布局/页面中调用任何
next-intl
API之前,需先调用
setRequestLocale

8. Using translations

8. 使用翻译功能

Client components:
useTranslations(namespace)
:
tsx
"use client";
import { useTranslations } from "next-intl";
import { Link } from "@/i18n/navigation";

export default function BackToHomeButton() {
  const t = useTranslations("BackToHomeButton");
  return (
    <Link href="/">
      <span>{t("buttonText")}</span>
    </Link>
  );
}
tsx
"use client";
import { useTranslations } from "next-intl";
import { Link } from "@/i18n/navigation";

export default function Nav() {
  const t = useTranslations("Navigation");
  return <Link href="/about">{t("links.about")}</Link>;
}
Server components: use
getTranslations
from
next-intl/server
(await with locale/namespace as needed).

客户端组件: 使用
useTranslations(namespace)
tsx
"use client";
import { useTranslations } from "next-intl";
import { Link } from "@/i18n/navigation";

export default function BackToHomeButton() {
  const t = useTranslations("BackToHomeButton");
  return (
    <Link href="/">
      <span>{t("buttonText")}</span>
    </Link>
  );
}
tsx
"use client";
import { useTranslations } from "next-intl";
import { Link } from "@/i18n/navigation";

export default function Nav() {
  const t = useTranslations("Navigation");
  return <Link href="/about">{t("links.about")}</Link>;
}
服务端组件: 使用
next-intl/server
中的
getTranslations
(根据需要传入区域设置/命名空间并await)。

9. Messages format

9. 消息文件格式

One JSON file per locale under
messages/
. Nested keys map to namespaces and keys:
json
{
  "HomePage": {
    "title": "Hello world!"
  },
  "LandingPage": {
    "title": "Tokyo Sounds",
    "navbar": {
      "home": "Home",
      "about": "About"
    }
  },
  "BackToHomeButton": {
    "buttonText": "Back to Home",
    "tooltip": "Return to the main page"
  }
}
  • useTranslations("LandingPage")
    t("title")
    ,
    t("navbar.about")
    .
  • Interpolation:
    "selectColor": "Select {color} color"
    t("selectColor", { color: "Blue" })
    .

每个区域对应一个JSON文件,存放在
messages/
目录下。嵌套键对应命名空间和翻译键:
json
{
  "HomePage": {
    "title": "Hello world!"
  },
  "LandingPage": {
    "title": "Tokyo Sounds",
    "navbar": {
      "home": "Home",
      "about": "About"
    }
  },
  "BackToHomeButton": {
    "buttonText": "Back to Home",
    "tooltip": "Return to the main page"
  }
}
  • useTranslations("LandingPage")
    t("title")
    t("navbar.about")
  • 插值语法:
    "selectColor": "Select {color} color"
    t("selectColor", { color: "Blue" })

Checklist

检查清单

  • next.config.ts
    :
    createNextIntlPlugin()
    wraps config.
  • src/i18n/routing.ts
    :
    defineRouting
    with
    locales
    and
    defaultLocale
    .
  • src/i18n/request.ts
    :
    getRequestConfig
    +
    hasLocale
    + dynamic
    messages/${locale}.json
    .
  • src/proxy.ts
    (or
    middleware.ts
    ):
    createMiddleware(routing)
    and matcher.
  • src/i18n/navigation.ts
    :
    createNavigation(routing)
    and re-export
    Link
    , etc.
  • app/[locale]/layout.tsx
    :
    hasLocale
    notFound
    ,
    setRequestLocale
    ,
    generateStaticParams
    ,
    NextIntlClientProvider
    +
    getMessages()
    .
  • Each
    app/[locale]/**/page.tsx
    :
    setRequestLocale(locale)
    when using static rendering.
  • Client components:
    useTranslations("Namespace")
    ; links use
    Link
    from
    @/i18n/navigation
    .

  • next.config.ts
    :使用
    createNextIntlPlugin()
    包裹配置。
  • src/i18n/routing.ts
    :使用
    defineRouting
    配置
    locales
    defaultLocale
  • src/i18n/request.ts
    :配置
    getRequestConfig
    +
    hasLocale
    + 动态引入
    messages/${locale}.json
  • src/proxy.ts
    (或
    middleware.ts
    ):配置
    createMiddleware(routing)
    和匹配规则。
  • src/i18n/navigation.ts
    :使用
    createNavigation(routing)
    并重新导出
    Link
    等工具。
  • app/[locale]/layout.tsx
    :通过
    hasLocale
    验证→无效则
    notFound
    ,调用
    setRequestLocale
    ,配置
    generateStaticParams
    ,使用
    NextIntlClientProvider
    +
    getMessages()
  • 每个
    app/[locale]/**/page.tsx
    :使用静态渲染时需调用
    setRequestLocale(locale)
  • 客户端组件:使用
    useTranslations("Namespace")
    ;链接使用
    @/i18n/navigation
    中的
    Link

Reference

参考资料

  • Copy-paste examples: examples/ — standalone files for use in any project.
  • Extended config (localePrefix, pathnames, etc.): reference.md
  • Official: next-intl App Router, Routing setup
  • 可复制示例: examples/ — 独立文件,可用于任意项目。
  • 扩展配置(localePrefix、pathnames等):reference.md
  • 官方文档:next-intl App RouterRouting setup