3.3 国际化(Internationalization, i18n)


3.3 国际化(Internationalization, i18n)

本节摘要:多语言不是"把文案翻译一下"这么简单——还要处理 URL 结构、语言切换、日期货币格式与 SEO。本节讲清 Next.js 的国际化方案:路由级语言前缀、字典管理、动态切换,以及多语言 SEO 的注意事项。

核心问题

阅读完本节,你应当能够:

  1. 用路由级国际化(/en、/zh)组织多语言
  2. 用字典文件管理翻译文案
  3. 实现语言切换
  4. 处理日期/货币的本地化
  5. 配置多语言 SEO

问题与直觉:国际化为什么"不只是翻译"

"你好" → "Hello" 只是第一步。真正的国际化要处理:

  • URL 结构/en/about/zh/about(或子域名 en.example.com);
  • 动态切换:用户切换语言,全站跟着变;
  • 本地化格式:日期 2026/08/15 vs Aug 15, 2026,货币 ¥100 vs $100
  • SEO:每种语言都是独立页面,要告诉搜索引擎语言关系。

直觉类比:国际化像"同一家餐厅的多语言菜单"——菜(功能)一样,但每本菜单(语言版)有自己的封面(URL)、排版(格式)、内容(翻译)。

💡 关键直觉:Next.js 推荐的国际化是"URL 前缀"方案——/zh/about/en/about 是两个独立路由,各自服务端渲染,SEO 友好、可被独立收录。语言信息在 URL 里,而不是只在 Cookie 里。

核心原理:路由级国际化

2.1 目录结构

app/ ├── [lang]/ # 语言段 │ ├── layout.tsx # 语言布局(设置 lang 属性) │ ├── page.tsx # /zh、/en │ ├── about/page.tsx # /zh/about、/en/about │ └── blog/[slug]/page.tsx └── layout.tsx # 根布局

2.2 字典管理

// dictionaries.ts const dictionaries = { zh: () => import("./dictionaries/zh.json").then((m) => m.default), en: () => import("./dictionaries/en.json").then((m) => m.default), }; export async function getDictionary(lang: "zh" | "en") { return dictionaries[lang](); }
// dictionaries/zh.json { "home": { "title": "首页", "welcome": "欢迎使用" }, "common": { "login": "登录", "logout": "登出" } }
// dictionaries/en.json { "home": { "title": "Home", "welcome": "Welcome" }, "common": { "login": "Login", "logout": "Logout" } }

2.3 在页面中使用

// app/[lang]/page.tsx import { getDictionary } from "@/dictionaries"; export default async function HomePage({ params, }: { params: Promise<{ lang: "zh" | "en" }>; }) { const { lang } = await params; const dict = await getDictionary(lang); return ( <main> <h1>{dict.home.title}</h1> <p>{dict.home.welcome}</p> </main> ); }

2.4 语言切换

// components/LangSwitcher.tsx(客户端) "use client"; import { usePathname } from "next/navigation"; import Link from "next/link"; export function LangSwitcher({ lang }: { lang: "zh" | "en" }) { const pathname = usePathname(); const target = lang === "zh" ? "en" : "zh"; // 替换 URL 中的语言段 const newPath = pathname.replace(/^\/(zh|en)/, `/${target}`); return <Link href={newPath}>切换至 {target === "zh" ? "中文" : "English"}</Link>; }

工程实践要点:本地化与 SEO

3.1 日期与货币

// utils/format.ts export function formatDate(date: Date, lang: "zh" | "en") { return new Intl.DateTimeFormat(lang === "zh" ? "zh-CN" : "en-US", { year: "numeric", month: "long", day: "numeric", }).format(date); } export function formatCurrency(amount: number, lang: "zh" | "en") { return new Intl.NumberFormat(lang === "zh" ? "zh-CN" : "en-US", { style: "currency", currency: lang === "zh" ? "CNY" : "USD", }).format(amount); }

3.2 多语言 SEO

// app/[lang]/layout.tsx import type { Metadata } from "next"; export async function generateMetadata({ params, }: { params: Promise<{ lang: "zh" | "en" }>; }): Promise<Metadata> { const { lang } = await params; return { title: lang === "zh" ? "我的网站" : "My Website", alternates: { languages: { "zh-CN": "/zh", "en-US": "/en", }, }, }; }

hreflang 的价值:告诉搜索引擎"同一内容的多语言版本关系",避免重复内容惩罚。

3.3 根布局设置 lang 属性

// app/[lang]/layout.tsx export default function LangLayout({ children, params, }: { children: React.ReactNode; params: Promise<{ lang: "zh" | "en" }>; }) { return <html lang={(await params).lang}>{children}</html>; }

lang 属性:屏幕阅读器、浏览器翻译、SEO 都依赖它,必须正确设置。

常见误区与排查

误区 现象 正解
只翻译文案不处理 URL 无独立语言页 用 [lang] 路由段
忘记 lang 属性 无障碍/SEO 受损 布局里设置 html lang
字典 key 不一致 翻译缺失 两语言字典保持同构
客户端写死文案 切换无效 字典从服务器组件传入
忽略 hreflang 多语言被当重复内容 generateMetadata 配 alternates

动手演练:双语站点骨架

app/ ├── [lang]/ │ ├── layout.tsx # 设置 lang + 语言切换器 │ ├── page.tsx # 首页(读字典) │ └── about/page.tsx # 关于页 ├── dictionaries/ │ ├── zh.json │ └── en.json └── layout.tsx # 根布局
// app/[lang]/layout.tsx import { LangSwitcher } from "@/components/LangSwitcher"; export default async function LangLayout({ children, params, }: { children: React.ReactNode; params: Promise<{ lang: "zh" | "en" }>; }) { const { lang } = await params; return ( <html lang={lang}> <body> <header> <LangSwitcher lang={lang} /> </header> <main>{children}</main> </body> </html> ); }

访问 /zh 看到中文、/en 看到英文,点切换器在两种语言间跳转(URL 变化、页面刷新)——URL 前缀方案的多语言站点跑通了。后续可扩展:默认语言重定向(//zh)、更多语言、服务端检测浏览器语言。

温故知新

  • URL 前缀方案:[lang] 路由段组织多语言,独立 SEO。
  • 字典管理:JSON 字典 + getDictionary,两语言保持同构。
  • 语言切换:客户端替换 URL 语言段。
  • 本地化:Intl API 处理日期与货币。
  • SEO:generateMetadata 配 alternates.languages(hreflang)。
  • lang 属性:根布局正确设置,无障碍与 SEO 都依赖。

深入理解:国际化的完整工作流

多语言请求处理流程

URL 前缀(/zh、/en)决定语言 → 加载对应字典 → 渲染 + 正确 lang 属性 + hreflang SEO。

国际化实施清单

  1. 语言段:app/[lang]/ 组织所有页面;
  2. 字典:dictionaries/zh.json、en.json 保持同构(key 一致);
  3. 默认语言:/ 根路径重定向到默认语言(中间件或 rewrite);
  4. 语言切换:客户端替换 URL 语言段,保留当前路径;
  5. 本地化:Intl API 处理日期、货币、数字格式;
  6. SEO:generateMetadata 配 alternates.languages(hreflang);
  7. RTL 语言(阿拉伯语等):需额外处理文本方向(dir 属性)。

常见陷阱

  • 字典 key 不一致:中英两文件 key 不同,某语言缺失翻译——用 TypeScript 类型约束字典结构(两语言实现同一 interface);
  • 忘记 html lang:无障碍与 SEO 受损;
  • 硬编码文案:客户端组件里写死字符串——文案必须来自字典(从服务器组件传入或客户端字典库)。

一句话:国际化的本质是"语言是路由的一部分"——每个语言版本都是独立页面、独立 SEO、独立可访问,字典只是内容的载体。

常见问题速答

问:国际化一定要用 URL 前缀吗?
Next.js 官方推荐 URL 前缀(/zh、/en),因为:SEO 友好(独立 URL)、可分享(链接带语言)、无状态(刷新不丢语言)。Cookie 方案省 URL 但 SEO 差。

问:默认语言怎么处理根路径?
中间件把 / 重定向到默认语言(如 /zh):读取浏览器语言(Accept-Language)或 Cookie 决定。未匹配语言时回退默认。

问:客户端组件的文案怎么国际化?
客户端组件不能直接用服务器字典(跨边界)。方案:字典作为 props 从服务器组件传入,或用客户端 i18n 库(如 i18next)。简单场景 props 传入最省事。

问:图片/SEO 也需要国际化吗?
需要:每个语言的 og 图片、title 描述都独立;hreflang 关联语言版本;alt 文本按语言翻译。

问:日期货币格式怎么处理?
用 Intl API(Intl.DateTimeFormat、Intl.NumberFormat),按语言传 locale——比手动拼接格式更规范、自动适配不同地区的习惯。

一句话总结

国际化的本质是"语言是路由的一部分"——app/[lang]/ 组织页面,字典保持同构,Intl API 处理格式,hreflang 关联 SEO。记住:URL 前缀方案让每个语言版本独立可收录、可分享、可访问。从第一天就考虑 i18n,比上线后重构便宜十倍。

动手练习建议

把练习项目改造成双语站:目录重构为 app/[lang]/,建 dictionaries/zh.json 与 en.json(保持同构),首页与关于页从字典读文案,语言切换器替换 URL 语言段,根布局设置 html lang。

进阶练习:加默认语言重定向(中间件把 / 指向浏览器语言的版本)、日期本地化(Intl API 按语言格式化)、generateMetadata 配 hreflang。改完用 /zh 与 /en 分别访问验证——每个语言版本都是独立可访问、可收录的页面,这就是 URL 前缀方案的价值。

章节自测

能解释为什么 URL 前缀方案比 Cookie 方案更适合 SEO 吗?能说出字典文件保持同构的原因吗?能说出 hreflang 的作用吗?答对即掌握国际化核心。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U