next-intl 改造 i18n 步骤
next.config.ts 中添加 next-intl 插件
export default withNextIntl(nextConfig);
i18n/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,
};
});
requestLocale首选语言环境 path > cookie > accept-language > defaultLocale
可以从本地或远程加载翻译资源
i18n/routing.ts 定义支持的语言环境
import { defineRouting } from "next-intl/routing";
export const routing = defineRouting({
locales: ["en", "zh"],
defaultLocale: "en",
localePrefix: "as-needed", // 默认语言不显示前缀 /en
});
i18n/navigation.ts 导出国际化版本的导航 api
import { createNavigation } from "next-intl/navigation";
import { routing } from "./routing";
export const { Link, redirect, usePathname, useRouter, getPathname } =
createNavigation(routing);
使用这里的导航以便自动处理多语言路径
proxy.ts 中添加 i18n 中间件
import { NextRequest, NextResponse } from "next/server";
import createMiddleware from "next-intl/middleware";
import { routing } from "./i18n/routing";
export const config = {
matcher: "/((?!api|trpc|_next|_vercel|.*\\..*).*)", // 仅匹配页面路由
};
const handleI18nRouting = createMiddleware(routing);
export default async function (request: NextRequest) {
const response = handleI18nRouting(request);
// ...其他中间件
return response;
}
messages/en.json 翻译文件
{
"Layout": {
"title": "Scott Tang",
"blog": "Blog",
"about": "About",
"present": "Copyright ©2016 present <link>Scott Tang</link>"
},
"HomePage": {
"description": "This is my portfolio, blog, and personal website."
}
}
将所有页面移到 app/[locale]/ 下,支持动态语言路由
app/[locale]/layout.tsx - 集成 NextIntlClientProvider + 生成多语言静态参数([locale])
import { Locale, NextIntlClientProvider } from "next-intl";
import { routing } from "@/i18n/routing";
// /[locale] 动态路由参数
export function generateStaticParams() {
return routing.locales.map((locale) => ({ locale }));
}
// provider
<NextIntlClientProvider></NextIntlClientProvider>;
根 app/layout.tsx - 简化为传递子组件
export default function RootLayout({ children }: Props) {
return children;
}
根 app/page.tsx - 简化为空组件,默认 / -> /en
// 不需要首页
export default function RootPage() {}
因为 localePrefix: "as-needed", 首页
/渲染的默认语言/en
components/LocaleSwitch.tsx - 语言切换组件
import { useRouter, usePathname } from "@/i18n/navigation";
const pathname = usePathname();
const router = useRouter();
// 切换语言只需 router.replace
// next-intl 内部会设置 cookie 以便下次访问时默认语言
router.replace(pathname, { locale: newLocale });
如果不用 [locale] 动态路由,只用 cookie 记录语言,切换语言需调用服务端 Action 进行 set-cookie 触发重新渲染
next.js 会将 set-cookie 的 action 标记为"需要触发重新渲染"
由于 next-intl 中间件默认会使用 header() 获取首选语言,因此会导致页面渲染变为
动态渲染,如果不希望如此, 则需要在 layout 和 page 使用 Trans 之前手动调用setRequestLocale(locale);在 next.js 支持 rootParams 之后会解决此问题
使用场景
import { Link } from "@/i18n/navigation";
setRequestLocale(locale); // 静态渲染
const t = useTranslations("HomePage"); // 客户端组件 内使用
const t = await getTranslations("Layout"); // 服务端组件/函数 内使用