服务器操作、元数据 & 路由处理器
在 Next.js 应用中,有几个地方可以在 React 组件外实现国际化:
next-intl/server 提供了一组可等待的异步函数,可用于这些场景。
元数据 API
要对页面标题等元数据进行国际化,可以在页面和布局中导出的 generateMetadata 函数中使用 next-intl 的功能。
import {getTranslations} from 'next-intl/server';
export async function generateMetadata({params}) {
const {locale} = await params;
const t = await getTranslations({locale, namespace: 'Metadata'});
return {
title: t('title')
};
}服务器操作
服务器操作 提供了一种机制,可以执行由客户端触发的服务器端代码。如果需要返回面向用户的消息,可以使用 next-intl 根据用户的语言环境为这些消息进行本地化。
import {getTranslations} from 'next-intl/server';
async function loginAction(data: FormData) {
'use server';
const t = await getTranslations('LoginForm');
const areCredentialsValid = /* ... */;
if (!areCredentialsValid) {
return {error: t('invalidCredentials')};
}
}注意,当你向用户展示服务器操作生成的消息时,应考虑用户在消息显示期间切换语言环境的情况,以确保界面的一致本地化。如果你使用了作为路由策略一部分的 [locale] 片段,则会自动处理该情况。如果没有,可能需要手动清除消息,例如通过给相关组件使用 key={locale} 来重置状态。
使用 Zod 进行验证时,如何本地化错误消息?
Zod 支持提供上下文错误映射,用于自定义每次解析的错误消息。由于语言环境针对具体请求,该机制非常适合将来自 zod 的结构化错误转换为本地化消息:
import {getTranslations} from 'next-intl/server';
import {loginUser} from '@/services/session';
import {z} from 'zod';
const loginFormSchema = z.object({
email: z.string().email(),
password: z.string().min(1)
});
// ...
async function loginAction(data: FormData) {
'use server';
const t = await getTranslations('LoginForm');
const values = Object.fromEntries(data);
const result = loginFormSchema.safeParse(values, {
error(issue) {
if (issue.path) {
const path = issue.path.join('.');
return {
email: t('invalidEmail'),
password: t('invalidPassword')
}[path];
}
}
});
// ...
}Open Graph 图片
如果你正在以编程方式生成Open Graph 图片,可以在导出的组件中调用 next-intl 的函数:
import {ImageResponse} from 'next/og';
import {getTranslations} from 'next-intl/server';
export default async function OpenGraphImage({params}) {
const {locale} = await params;
const t = await getTranslations({locale, namespace: 'OpenGraphImage'});
return new ImageResponse(<div style={{fontSize: 128}}>{t('title')}</div>);
}Next.js 会基于 opengraph-image.tsx 所在路径段创建一个公开路由,例如:
http://localhost:3000/en/opengraph-image?f87b2d56cee109c7然而,如果你使用了基于语言环境的路由,且自定义了 localePrefix 设置,这个路由可能无法访问,因为 Next.js 不知道中间件可能的重写规则。
如果你的应用存在这种情况,可以调整你的匹配器,忽略对 opengraph-image.tsx 文件的请求:
// ...
export const config = {
matcher: [
// 跳过所有不应国际化的路径
'/((?!api|_next|_vercel|.*/opengraph-image|.*\\..*).*)'
// ...
]
};Manifest 清单
由于manifest 文件 需放置在 app 文件夹根目录(即 [locale] 动态段之外),你需要显式提供语言环境,因为 next-intl 无法从路径推断:
import {MetadataRoute} from 'next';
import {getTranslations} from 'next-intl/server';
export default async function manifest(): Promise<MetadataRoute.Manifest> {
// 选择一个代表应用的语言环境
const locale = 'en';
const t = await getTranslations({
namespace: 'Manifest',
locale
});
return {
name: t('name'),
start_url: '/',
theme_color: '#101E33'
};
}网站地图
如果你使用网站地图告知搜索引擎你站点的所有页面,可以为每个 URL 附加特定语言的备用条目,表示该页面支持多语言或多地区。
注意,默认情况下,next-intl 会返回 link 响应头,通知搜索引擎页面支持多语言。虽然这对搜索引擎而言足够,但如果你有更具体的要求(例如使用基于 CMS 的 URL,如 /news/[articleSlug],且 articleSlug 随语言环境变化),可以选择在网站地图中提供此信息。
Next.js 支持通过 alternates 条目为每种语言提供备选 URL,结合 getPathname 构建每种语言的路径:
import {MetadataRoute} from 'next';
import {getPathname} from '@/i18n/navigation';
const host = 'https://acme.com';
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
return [
{
url: host,
lastModified: new Date(),
alternates: {
languages: {
es: host + (await getPathname({locale: 'es', href: '/'})),
de: host + (await getPathname({locale: 'de', href: '/'}))
}
}
}
];
}了解更多:
路由处理器
你也可以在路由处理器中使用 next-intl。locale 可通过查询参数、布局片段或解析请求头的 accept-language 获得。
import {NextResponse} from 'next/server';
import {hasLocale} from 'next-intl';
import {getTranslations} from 'next-intl/server';
import {routing} from '@/i18n/routing';
export async function GET(request) {
// 示例:通过查询参数接收 `locale`
const {searchParams} = new URL(request.url);
const locale = searchParams.get('locale');
if (!hasLocale(routing.locales, locale)) {
return NextResponse.json({error: 'Invalid locale'}, {status: 400});
}
const t = await getTranslations({locale, namespace: 'Hello'});
return NextResponse.json({title: t('title')});
}