Next.js 13+ App Router 国际化方案实践
为什么要写这篇文章?
在开发 Next.js 应用时,我们经常需要支持多语言特性。特别是 Next.js 13+ 版本采用了全新的 App Router,原有的国际化方案已经不再适用。今天我们就来聊聊如何在 Next.js 13+ 中优雅地实现国际化。
方案特点
- ✨ 完整支持服务端渲染(SSR)
- 🔍 智能的语言自动检测
- 🛡️ TypeScript 类型安全
- 🚀 按需加载翻译资源
- 💾 用户语言偏好持久化
开始动手
第一步:安装依赖
首先让我们准备好必要的工具库:
css
Bash
npm install accept-language i18next i18next-resources-to-backend
这些库各自承担着重要角色:
accept-language
➜ 处理浏览器语言检测i18next
➜ 提供核心翻译能力i18next-resources-to-backend
➜ 实现翻译文件按需加载
第二步:组织项目结构
我们需要一个清晰的目录结构来管理国际化相关文件:
ini
src/
├── app/
│ ├── [lng]/ # 语言路由目录
│ │ ├── layout.tsx
│ │ └── page.tsx
│ └── lib/
│ └── i18n/ # 国际化核心逻辑
│ ├── setting.ts
│ └── index.ts
├── middleware.ts # 语言检测中间件
└── locales/ # 翻译资源目录
├── en/
│ └── translation.json
└── zh/
└── translation.json
第三步:配置核心功能
1. 基础配置 (setting.ts)
ini
TypeScript
export const fallbackLng = "en";
export const languages = [fallbackLng, "zh"];
export const defaultNS = "translation";
export function getOptions(lng = fallbackLng, ns = defaultNS) {
return {
supportedLngs: languages,
fallbackLng,
lng,
fallbackNS: defaultNS,
defaultNS,
ns
};
}
2. 语言检测中间件 (middleware.ts)
javascript
TypeScript
import { NextResponse } from "next/server";
import acceptLanguage from "accept-language";
import { fallbackLng, languages } from "@/app/lib/i18n/setting";
acceptLanguage.languages(languages);
export const config = {
matcher: ["/((?!api|next|images|svg|assets|favicon.ico).*)"]
};
export async function middleware(req: any) {
const cookieName = "lang";
// ❗️ 跳过内部路由
if (req.nextUrl.pathname.startsWith("/_next")) {
return NextResponse.next();
}
// 智能语言检测
let lng = req.cookies.has(cookieName)
? acceptLanguage.get(req.cookies.get(cookieName).value)
: acceptLanguage.get(req.headers.get("Accept-Language"));
lng = lng || fallbackLng;
// 路径重定向处理
if (!languages.some(loc => req.nextUrl.pathname.startsWith(`/${loc}`))) {
return NextResponse.redirect(
new URL(`/${lng}${req.nextUrl.pathname}`, req.url)
);
}
// 保存语言偏好
const response = NextResponse.next();
const pathLng = languages.find(l => req.nextUrl.pathname.startsWith(`/${l}`));
if (pathLng) response.cookies.set(cookieName, pathLng);
return response;
}
3. 翻译工具函数 (index.ts)
typescript
TypeScript
import { createInstance } from "i18next";
import resourcesToBackend from "i18next-resources-to-backend";
import { getOptions } from "./setting";
const initI18next = async (lng: string, ns: string) => {
const i18nInstance = createInstance();
await i18nInstance
.use(
resourcesToBackend((language: string, namespace: string) =>
import(`@/locales/${language}/${namespace}.json`)
)
)
.init(getOptions(lng, ns));
return i18nInstance;
};
export async function useTranslation(lng: string, ns: string = "translation") {
const i18nextInstance = await initI18next(lng, ns);
return {
t: i18nextInstance.getFixedT(lng, ns),
i18n: i18nextInstance,
};
}
第四步:实现语言切换
ini
TypeScript
'use client';
import { useRouter } from "next/navigation";
export default function LanguageSwitcher({ currentLang }: { currentLang: string }) {
const router = useRouter();
const switchLanguage = (newLang: string) => {
const path = window.location.pathname;
const newPath = path.replace(`/${currentLang}`, `/${newLang}`);
router.push(newPath);
};
return (
<select
onChange={(e) => switchLanguage(e.target.value)}
value={currentLang}
className="p-2 border rounded"
>
<option value="en">English</option>
<option value="zh">中文</option>
</select>
);
}
实战应用
让我们看看如何在页面中使用这套国际化方案:
javascript
TypeScript
// app/[lng]/page.tsx
export default async function Page({ params }) {
const { lng } = await params;
const { t } = await useTranslation(lng);
return (
<div>
<h1>{t("welcome")}</h1>
<LanguageSwitcher currentLang={lng} />
</div>
);
}
翻译文件示例:
javascript
JSON
// locales/en/translation.json
{
"welcome": "Welcome to Next.js i18n"
}
// locales/zh/translation.json
{
"welcome": "欢迎使用 Next.js 国际化"
}
踩坑指南
在实施过程中,我们需要注意以下几点:
- 路由限制 :所有页面必须放在
[lng]
动态路由下 - 配置冲突 :不要在
next.config.ts
中使用旧版 i18n 配置 - 性能优化:建议按功能模块拆分翻译文件,避免一次性加载过多资源
- 类型安全:推荐使用 TypeScript 定义翻译键,提供更好的开发体验
参考
App Router 国际化:2024年Nextjs国际化配置方案
结语
希望这篇文章能帮助你在项目中更好地实现国际化功能。
如果觉得文章对你有帮助,别忘了点个赞 👍