Skip to content
文档环境服务器操作、元数据与路由处理程序

服务器操作、元数据 & 路由处理器

在 Next.js 应用中,有几个地方可以在 React 组件外实现国际化:

  1. 元数据 API
  2. 服务器操作
  3. Open Graph 图片
  4. Manifest 清单
  5. 网站地图
  6. 路由处理器

next-intl/server 提供了一组可等待的异步函数,可用于这些场景。

元数据 API

要对页面标题等元数据进行国际化,可以在页面和布局中导出的 generateMetadata 函数中使用 next-intl 的功能。

layout.tsx
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 的异步函数显式传入 locale, 如果你使用基于语言环境的路由, 你可以使元数据处理器支持静态渲染

服务器操作

服务器操作 提供了一种机制,可以执行由客户端触发的服务器端代码。如果需要返回面向用户的消息,可以使用 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 的函数:

app/[locale]/opengraph-image.tsx
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 文件的请求:

proxy.ts
// ...
 
export const config = {
  matcher: [
    // 跳过所有不应国际化的路径
    '/((?!api|_next|_vercel|.*/opengraph-image|.*\\..*).*)'
 
    // ...
  ]
};

Manifest 清单

由于manifest 文件 需放置在 app 文件夹根目录(即 [locale] 动态段之外),你需要显式提供语言环境,因为 next-intl 无法从路径推断:

app/manifest.ts
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-intllocale 可通过查询参数、布局片段或解析请求头的 accept-language 获得。

app/api/hello/route.tsx
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')});
}