Skip to content
文档使用指南请求配置

请求配置

您可以针对每个请求设置在整个 Next.js 应用中使用的配置属性。

服务器与客户端组件

根据您是在服务器组件还是客户端组件中处理国际化,将分别应用 i18n/request.tsNextIntlClientProvider 的配置。

i18n/request.tsgetRequestConfig

i18n/request.ts 可用于为仅服务器端代码提供配置,即服务器组件、服务器操作及类似功能。配置通过 getRequestConfig 函数提供,并且如果您使用基于 locale 的路由,将提供一个 requestLocale 参数。

i18n/request.ts
import {getRequestConfig} from 'next-intl/server';
import {routing} from '@/i18n/routing';
 
export default getRequestConfig(async ({requestLocale}) => {
  // ...
 
  return {
    locale,
    messages
    // ...
  };
});

配置对象会针对每个请求创建一次,内部使用 React 的 cache。第一个使用国际化的组件会调用 getRequestConfig 定义的函数。

由于该函数在服务器组件的渲染过程中执行,您可以调用像 cookies()headers() 这样的函数来返回特定请求的配置。

NextIntlClientProvider

NextIntlClientProvider 可用于为客户端组件提供配置。

layout.tsx
import {NextIntlClientProvider} from 'next-intl';
import {getMessages} from 'next-intl/server';
 
export default async function RootLayout(/* ... */) {
  // ...
 
  return (
    <html lang={locale}>
      <body>
        <NextIntlClientProvider>...</NextIntlClientProvider>
      </body>
    </html>
  );
}

如果您从服务器组件渲染 NextIntlClientProvider,这些属性会被继承:

  1. locale
  2. messages
  3. now
  4. timeZone
  5. formats

如果您不想继承其中某些属性,例如因为您在服务器和客户端组件中选择性使用国际化,您可以选择放弃继承:

layout.tsx
<NextIntlClientProvider
  // 不向客户端传递 messages
  messages={null}
  // ...
>
  ...
</NextIntlClientProvider>

此外,嵌套的 NextIntlClientProvider 实例会继承各自上级的配置。但请注意,单个属性是作为原子处理的,因此例如 messages 需要手动合并(如果需要)。

以下这些属性不会被继承:

  1. onError
  2. getMessageFallback
如何向 NextIntlClientProvider 提供不可序列化的属性(如 onError)?

React 限制传递给客户端组件的属性类型为可序列化类型。由于 onErrorgetMessageFallback 可以接收函数,这些配置选项无法由客户端自动继承。

为了在客户端定义这些属性,您可以添加一个定义这些属性的 provider:

IntlErrorHandlingProvider.tsx
'use client';
 
import {NextIntlClientProvider} from 'next-intl';
 
export default function IntlErrorHandlingProvider({children}) {
  return (
    <NextIntlClientProvider
      onError={(error) => console.error(error)}
      getMessageFallback={({namespace, key}) => `${namespace}.${key}`}
    >
      {children}
    </NextIntlClientProvider>
  );
}

定义好客户端的 provider 组件后,您可以在服务器组件中使用它:

layout.tsx
import {NextIntlClientProvider} from 'next-intl';
import {getLocale} from 'next-intl/server';
import IntlErrorHandlingProvider from './IntlErrorHandlingProvider';
 
export default async function RootLayout({children}) {
  const locale = await getLocale();
 
  return (
    <html lang={locale}>
      <body>
        <NextIntlClientProvider>
          <IntlErrorHandlingProvider>{children}</IntlErrorHandlingProvider>
        </NextIntlClientProvider>
      </body>
    </html>
  );
}

这样,您的 provider 组件将成为客户端包的一部分,因此可以定义并传递函数作为属性。

请注意,内层的 NextIntlClientProvider 继承了外层的配置,只有 onErrorgetMessageFallback 函数被添加。

Locale

locale 是一个标识符,包含用户的语言和格式偏好,可选地包括区域信息(例如 en-US)。Locale 使用 IETF BCP 47 语言标签 指定。

根据您是否使用基于 locale 的路由,您可以从 requestLocale 参数读取 locale,或者自行提供一个值:

使用基于 locale 的路由:

i18n/request.ts
export default getRequestConfig(async ({requestLocale}) => {
  // 通常对应于 `[locale]` 路由段
  const requested = await requestLocale;
  const locale = hasLocale(routing.locales, requested)
    ? requested
    : routing.defaultLocale;
 
  return {
    locale
    // ...
  };
});

不使用基于 locale 的路由:

i18n/request.ts
export default getRequestConfig(async () => {
  // 提供静态 locale,读取用户设置,
  // 读取 `cookies()`、`headers()` 等
  const locale = 'en';
 
  return {
    locale
    // ...
  };
});
requestLocale 参数可以包含哪些值?

虽然 requestLocale 参数通常对应于由中间件匹配到的 [locale] 路由段,但有三个特殊情况需要考虑:

  1. 覆盖:若显式向可等待函数(如 getTranslations({locale: 'en'}))传递了 locale,则使用此值替代路由段。
  2. undefined:当页面渲染不在 [locale] 路由段下(例如语言选择页面 app/page.tsx)时,此值可能为 undefined
  3. 非法值:由于 [locale] 路由段在某种程度上是对未知路由的 catch-all(例如 /unknown.txt),非法值应替换为有效 locale。此外,您可能希望在根布局调用 notFound() 以中止渲染。
如何更改 locale?

根据您是否使用基于 locale 的路由,您可以按以下方式更改 locale:

  1. 使用基于 locale 的路由:locale 由路由管理,可通过使用 next-intl 的导航 API(例如 LinkuseRouter)进行切换。
  2. 不使用基于 locale 的路由:可以通过更新读取 locale 的地方的值(例如 cookie、用户设置等)来更改 locale。

了解更多:

useLocalegetLocale

应用的当前 locale 会自动融入类似 useTranslationsuseFormatter 的 hook 中,影响渲染结果。

如果您需要在其它地方使用此值,例如实现语言切换器或传递给 API 调用,可以通过 useLocalegetLocale 读取:

// 普通组件
import {useLocale} from 'next-intl';
const locale = useLocale();
 
// 异步服务器组件
import {getLocale} from 'next-intl/server';
const locale = await getLocale();
useLocale 返回的值是什么?

根据组件的渲染方式,返回的 locale 对应:

  1. 服务器组件:locale 为 i18n/request.ts 中返回的值。
  2. 客户端组件:locale 来自 NextIntlClientProvider

请注意,如果 NextIntlClientProvider 是由服务器组件渲染,它会自动继承 locale,因此您很少需要自己向 NextIntlClientProvider 传递 locale。

我在使用 Pages Router,如何访问 locale?

如果您使用带国际化的 Pages 路由,可以从路由器读取 locale 并传递给 NextIntlClientProvider

_app.tsx
import {useRouter} from 'next/router';
 
// ...
 
const router = useRouter();
 
return (
  <NextIntlClientProvider locale={router.locale}>
    ...
  </NextIntlClientProvider>;
);

Locale 类型

传递 locale 给其它函数时,可以使用 Locale 类型作为参数:

import {Locale} from 'next-intl';
 
async function getPosts(locale: Locale) {
  // ...
}

这在集成后端服务或 CMS 时可能有用:

💡

默认情况下,Locale 类型为 string。但您可以通过扩展 Locale 类型, 使用基于支持的 locales 的严格联合类型。

消息

国际化最关键的方面是基于用户语言提供标签。推荐的工作流是将消息与代码一同存储在版本库中。

├── messages
│   ├── en.json
│   ├── de-AT.json
│   └── ...
...

将消息与应用代码放在一起的优点是方便开发者快速修改,同时您可以利用本地消息的形状进行类型检查

如果团队频繁发布,也可以考虑自动化翻译流程:

不过,next-intl 并不限制如何存储消息,允许您自由定义异步函数来在渲染时获取消息:

i18n/request.ts
import {getRequestConfig} from 'next-intl/server';
 
export default getRequestConfig(async () => {
  return {
    messages: (await import(`../../messages/${locale}.json`)).default
    // ...
  };
});

消息配置后,可以通过 useTranslations 使用。

getRequestConfig 可以执行任意代码,您还可以:

  • 多 locale 共享消息
  • 将其他 locale 的消息合并为备用
  • 从远程源加载消息

了解更多:

如何将消息拆分为多个文件?

由于消息可以自由定义和加载,您可以将其拆分为多个文件,并在运行时合并:

const messages = {
  ...(await import(`../../messages/${locale}/login.json`)).default,
  ...(await import(`../../messages/${locale}/dashboard.json`)).default
};

注意,next-intlVSCode 集成 能够帮助您在单个大型文件中管理消息。如果您仅是出于组织考虑拆分文件,建议使用 VSCode 插件而不是拆分。

useMessagesgetMessages

如果组件中需要访问消息,可以通过 useMessages()getMessages() 从配置获取:

// 普通组件
import {useMessages} from 'next-intl';
const messages = useMessages();
 
// 异步服务器组件
import {getMessages} from 'next-intl/server';
const messages = await getMessages();

时区

指定时区会影响日期和时间的渲染。默认使用服务器运行时的时区,但可以根据需要自定义。

i18n/request.ts
import {getRequestConfig} from 'next-intl/server';
 
export default getRequestConfig(async () => {
  return {
    // 时区可以静态定义,读取用户配置(如果存储了此设置),
    // 或基于请求动态信息,如 locale 或 cookie。
    timeZone: 'Europe/Vienna'
 
    // ...
  };
});

可用时区名称可在tz 数据库中查询。

如果您使用由服务器组件渲染的 NextIntlClientProvider 将相关客户端组件包裹起来,客户端组件会自动继承服务器端的时区。其他情况,您可以在包裹的 NextIntlClientProvider 上显式指定该值。

了解更多:

useTimeZonegetTimeZone

可以在组件中通过 useTimeZonegetTimeZone 读取配置的时区:

// 普通组件
import {useTimeZone} from 'next-intl';
const timeZone = useTimeZone();
 
// 异步服务器组件
import {getTimeZone} from 'next-intl/server';
const timeZone = await getTimeZone();

当前时间值

在格式化相对日期和时间时,next-intl 会以一个参考时间点「现在」为基准进行格式化。虽然提供这个值有助于缓存,但您也可以提供全局的 now 值,例如确保测试时的一致性。

i18n/request.ts
import {getRequestConfig} from 'next-intl/server';
 
export default getRequestConfig(async () => {
  return {
    now: new Date('2024-11-14T10:36:01.516Z')
 
    // ...
  };
});

如果在 i18n/request.ts 中提供了 now,且您用服务器组件渲染了 NextIntlClientProvider 包裹客户端组件,则客户端组件也会自动继承。

useNowgetNow

配置的 now 值可以通过 useNowgetNow 在组件中获取:

// 普通组件
import {useNow} from 'next-intl';
const now = useNow();
 
// 异步服务器组件
import {getNow} from 'next-intl/server';
const now = await getNow();

注意,返回值默认是当前时间,因此即使您没有配置全局 now,使用此 hook 在为 format.relativeTime提供 now 时也很有用。

格式

喜欢看视频吗?

为了实现日期、时间、数字和列表格式的一致性,您可以定义一套全局格式。

i18n/request.ts
import {getRequestConfig} from 'next-intl/server';
 
export default getRequestConfig(async () => {
  return {
    formats: {
      dateTime: {
        short: {
          day: 'numeric',
          month: 'short',
          year: 'numeric'
        }
      },
      number: {
        precise: {
          maximumFractionDigits: 5
        }
      },
      list: {
        enumeration: {
          style: 'long',
          type: 'conjunction'
        }
      }
    }
 
    // ...
  };
});

配置好 formats 后,可以通过 useFormatter 在组件中使用:

import {useFormatter} from 'next-intl';
 
function Component() {
  const format = useFormatter();
 
  format.dateTime(new Date('2020-11-20T10:36:01.516Z'), 'short');
  format.number(47.414329182, 'precise');
  format.list(['HTML', 'CSS', 'JavaScript'], 'enumeration');
}
💡

默认情况下,格式名称的类型为松散的 string。但您可以通过扩展 Formats 类型来使用严格类型。

数字、日期和时间的全局格式也可以在消息中引用:

en.json
{
  "ordered": "You've ordered this product on {orderDate, date, short}",
  "latitude": "Latitude: {latitude, number, precise}"
}
import {useTranslations} from 'next-intl';
 
function Component() {
  const t = useTranslations();
 
  t('ordered', {orderDate: new Date('2020-11-20T10:36:01.516Z')});
  t('latitude', {latitude: 47.414329182});
}

如果您用服务器组件渲染的 NextIntlClientProvider 包裹相关组件,则格式会自动从服务器继承。

错误处理 (onErrorgetMessageFallback)

默认情况下,当消息解析失败或格式化失败时,会在控制台打印错误,并渲染 ${namespace}.${key} 以保证应用继续运行。

您可以通过配置 onErrorgetMessageFallback 自定义此行为。

i18n/request.ts
import {getRequestConfig} from 'next-intl/server';
import {IntlErrorCode} from 'next-intl';
 
export default getRequestConfig(async () => {
  return {
    onError(error) {
      if (error.code === IntlErrorCode.MISSING_MESSAGE) {
        // 缺少翻译属于预期,只打印错误日志
        console.error(error);
      } else {
        // 其它错误表示应用有缺陷,应报告
        reportToErrorTracking(error);
      }
    },
 
    getMessageFallback({namespace, key, error}) {
      const path = [namespace, key].filter((part) => part != null).join('.');
 
      if (error.code === IntlErrorCode.MISSING_MESSAGE) {
        return path + ' 尚未翻译';
      } else {
        return '尊敬的开发者,请修复此消息:' + path;
      }
    }
 
    // ...
  };
});

注意,onErrorgetMessageFallback 不会被客户端组件自动继承。如果您想让客户端组件也可用该功能,可以创建一个客户端 provider 并定义这些属性。

如何使用其他 locale 的消息作为回退?

getMessageFallback 用于定制缺失消息的错误处理逻辑。

如果某些 locale 预期会缺失消息,可以考虑在提供消息时合并默认 locale 的消息作为回退。

了解更多: