✦ 大道至简 · 时光是画在卷上的河流 · 行到水穷处,坐看云起时

next-intl2026.08.24 · 11 分钟阅读

国际化 —— next-intl 双语言静态化

中英双语言带 /zh /en URL 前缀,且每个语言各自静态生成。讲清 routing/request/navigation 三个配置文件、setRequestLocale 的坑,以及 Server/Client 组件取文案的方式。

L

Leo

2026.08.24 · 更新于 2026.08.24

1 次浏览
国际化 —— next-intl 双语言静态化

博客要中英双语言,且 URL 带前缀(/zh/en),每个语言还各自静态生成。这一篇讲 next-intl 的三个配置文件、[locale] 动态段,以及 Server / Client 组件里怎么取文案。

一、总体结构

i18n/
├── routing.ts      # 语言定义(哪些语言、默认语言)
├── request.ts      # 请求时解析 locale 并加载字典
└── navigation.ts   # 封装的 Link/redirect/usePathname(自动带前缀)
messages/
├── zh.json         # 中文文案
└── en.json         # 英文文案
app/[locale]/       # 动态段:/zh/* 和 /en/* 共用同一套页面代码

二、routing.ts —— 语言定义

import { defineRouting } from "next-intl/routing";

export const routing = defineRouting({
  locales: ["zh", "en"],
  defaultLocale: "zh",
});
export type Locale = (typeof routing.locales)[number];

就两件事:支持哪些语言、默认哪个。

三、request.ts —— 请求时解析语言

import { hasLocale } from "next-intl";
import { getRequestConfig } from "next-intl/server";
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,  // 按语言加载字典
  };
});
  • next.config.ts 里的插件 createNextIntlPlugin("./i18n/request.ts") 就指向这里,它会在每个请求时调用。
  • requestLocale 来自 URL 的 [locale] 段。
  • import(\../messages/$.json`)` 是动态 import,构建时只打包用到的语言文件,不会把中英文全塞进 bundle。

四、navigation.ts —— 免前缀编程

import { createNavigation } from "next-intl/navigation";
import { routing } from "./routing";

export const { Link, redirect, usePathname, useRouter, getPathname } =
  createNavigation(routing);

这是项目里所有内部跳转都走 @/i18n/navigation 的原因:

  • Link href="/posts" 会自动渲染成 /zh/posts/en/posts
  • redirect({ href: "/login", locale }) 跳转自动带当前语言前缀;
  • usePathname() 返回的路径不带 /zh 前缀,方便逻辑判断。

副作用:项目里统一 import { Link } from "@/i18n/navigation",而不是 next/link。搜索代码时能看到这个统一入口。

五、Server Component 里取文案

页面是 Server Component,用 getTranslations 拿对应字典,并 setRequestLocale 配合静态生成:

export default async function Home({ params }) {
  const { locale } = await params;
  setRequestLocale(locale);          // 标记此页按 locale 静态生成
  const t = await getTranslations("home");      // 取 home 命名空间的文案
  return <p>{t("hero.subtitle")}</p>;
}

为什么要 setRequestLocale:它告诉 Next「这个页面的渲染依赖 locale」,从而允许 /zh /en 各自静态生成(根布局 generateStaticParams 返回 ['zh','en'] 才能生效)。每个 [locale] 下的页面都必须调用它,否则双语言静态化不会工作。

params 在 Next 16 是 Promise,必须 await

export default async function Page({ params }: { params: Promise<{ locale: string }> }) {
  const { locale } = await params;
  ...
}

六、Client Component 里取文案

交互组件(评论、点赞、导航)是 client,用 hooks:

"use client";
import { useTranslations } from "next-intl";
export default function CommentForm() {
  const t = useTranslations("blog.commentForm");
  return <button>{t("submit")}</button>;
}

七、带参数 / 复数的文案

messages/zh.json 里支持插值和复数(next-intl 用 ICU 语法):

{
  "common": {
    "units": { "viewsCount": "{views} 次浏览" }
  },
  "posts": {
    "likesCount": "{count, plural, other{# 个赞}}"
  }
}

使用:

common("units.viewsCount", { views: post.views });
t("likesCount", { count: likes });

八、双语言 URL 的 SEO 配合

app/sitemap.ts 里给每个页面生成双语 alternates,告诉搜索引擎两个语言页面的对应关系(这就是 XML 里的 hreflang,避免被判重复内容):

alternates: {
  languages: { zh: `${BASE}/zh${p}`, en: `${BASE}/en${p}` },
}

详见 SEO 篇。

九、与中间件的配合

proxy.ts(Next 16 的 middleware)里,next-intl 的中间件负责语言前缀探测:未带 /zh /en 前缀的访问,按浏览器语言重定向到对应前缀。

const intlMiddleware = createMiddleware(routing);
// 组合方式见认证篇 —— withAuth 先判登录,通过后再跑 intlMiddleware

十、一个连带影响:详情页被迫 SSR

next-intl 的服务端 getTranslations 在静态生成 / ISR on-demand 重建路径下会读 headers(),触发 DYNAMIC_SERVER_USAGE 500。因此文章详情页和标签详情页都加了 export const dynamic = "force-dynamic" 改走 SSR(见渲染策略篇)。这是 i18n 与静态渲染交织时最容易踩的坑。

十一、本节小结

  • 三个文件routing.ts(语言)、request.ts(解析+加载字典)、navigation.ts(跳转封装)。
  • [locale] 动态段 + generateStaticParams/zh /en 各自静态生成。
  • Server 组件getTranslations + setRequestLocaleClient 组件useTranslations
  • 跳转一律走 @/i18n/navigation,自动带语言前缀。
  • 字典动态 import,只打包用到的语言;中间件负责前缀探测;setRequestLocale 别漏,详情页会被迫 SSR。
标签 / TAGSnext-intl
L

Leo

博主

独立开发者 / Blogger,原博客「大道至简」维护者。正在把 WordPress 上攒了几年的文章与拾语迁移到 Next.js。

读者留言

COMMENTS · 0

发表留言

评论经审核后展示 · 请友善发言0/100