请求配置
您可以针对每个请求设置在整个 Next.js 应用中使用的配置属性。
服务器与客户端组件
根据您是在服务器组件还是客户端组件中处理国际化,将分别应用 i18n/request.ts 或 NextIntlClientProvider 的配置。
i18n/request.ts 和 getRequestConfig
i18n/request.ts 可用于为仅服务器端代码提供配置,即服务器组件、服务器操作及类似功能。配置通过 getRequestConfig 函数提供,并且如果您使用基于 locale 的路由,将提供一个 requestLocale 参数。
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 可用于为客户端组件提供配置。
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,这些属性会被继承:
localemessagesnowtimeZoneformats
如果您不想继承其中某些属性,例如因为您在服务器和客户端组件中选择性使用国际化,您可以选择放弃继承:
<NextIntlClientProvider
// 不向客户端传递 messages
messages={null}
// ...
>
...
</NextIntlClientProvider>此外,嵌套的 NextIntlClientProvider 实例会继承各自上级的配置。但请注意,单个属性是作为原子处理的,因此例如 messages 需要手动合并(如果需要)。
以下这些属性不会被继承:
onErrorgetMessageFallback
如何向 NextIntlClientProvider 提供不可序列化的属性(如 onError)?
React 限制传递给客户端组件的属性类型为可序列化类型。由于 onError 和 getMessageFallback 可以接收函数,这些配置选项无法由客户端自动继承。
为了在客户端定义这些属性,您可以添加一个定义这些属性的 provider:
'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 组件后,您可以在服务器组件中使用它:
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 继承了外层的配置,只有 onError 和 getMessageFallback 函数被添加。
Locale
locale 是一个标识符,包含用户的语言和格式偏好,可选地包括区域信息(例如 en-US)。Locale 使用 IETF BCP 47 语言标签 指定。
根据您是否使用基于 locale 的路由,您可以从 requestLocale 参数读取 locale,或者自行提供一个值:
使用基于 locale 的路由:
export default getRequestConfig(async ({requestLocale}) => {
// 通常对应于 `[locale]` 路由段
const requested = await requestLocale;
const locale = hasLocale(routing.locales, requested)
? requested
: routing.defaultLocale;
return {
locale
// ...
};
});不使用基于 locale 的路由:
export default getRequestConfig(async () => {
// 提供静态 locale,读取用户设置,
// 读取 `cookies()`、`headers()` 等
const locale = 'en';
return {
locale
// ...
};
});requestLocale 参数可以包含哪些值?
虽然 requestLocale 参数通常对应于由中间件匹配到的 [locale] 路由段,但有三个特殊情况需要考虑:
- 覆盖:若显式向可等待函数(如
getTranslations({locale: 'en'}))传递了locale,则使用此值替代路由段。 undefined:当页面渲染不在[locale]路由段下(例如语言选择页面app/page.tsx)时,此值可能为undefined。- 非法值:由于
[locale]路由段在某种程度上是对未知路由的 catch-all(例如/unknown.txt),非法值应替换为有效 locale。此外,您可能希望在根布局调用notFound()以中止渲染。
如何更改 locale?
根据您是否使用基于 locale 的路由,您可以按以下方式更改 locale:
- 使用基于 locale 的路由:locale 由路由管理,可通过使用
next-intl的导航 API(例如Link或useRouter)进行切换。 - 不使用基于 locale 的路由:可以通过更新读取 locale 的地方的值(例如 cookie、用户设置等)来更改 locale。
了解更多:
useLocale 和 getLocale
应用的当前 locale 会自动融入类似 useTranslations 和 useFormatter 的 hook 中,影响渲染结果。
如果您需要在其它地方使用此值,例如实现语言切换器或传递给 API 调用,可以通过 useLocale 或 getLocale 读取:
// 普通组件
import {useLocale} from 'next-intl';
const locale = useLocale();
// 异步服务器组件
import {getLocale} from 'next-intl/server';
const locale = await getLocale();useLocale 返回的值是什么?
根据组件的渲染方式,返回的 locale 对应:
- 服务器组件:locale 为
i18n/request.ts中返回的值。 - 客户端组件:locale 来自
NextIntlClientProvider。
请注意,如果 NextIntlClientProvider 是由服务器组件渲染,它会自动继承 locale,因此您很少需要自己向 NextIntlClientProvider 传递 locale。
我在使用 Pages Router,如何访问 locale?
如果您使用带国际化的 Pages 路由,可以从路由器读取 locale 并传递给 NextIntlClientProvider:
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 并不限制如何存储消息,允许您自由定义异步函数来在渲染时获取消息:
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-intl 的 VSCode 集成 能够帮助您在单个大型文件中管理消息。如果您仅是出于组织考虑拆分文件,建议使用 VSCode 插件而不是拆分。
useMessages 和 getMessages
如果组件中需要访问消息,可以通过 useMessages() 或 getMessages() 从配置获取:
// 普通组件
import {useMessages} from 'next-intl';
const messages = useMessages();
// 异步服务器组件
import {getMessages} from 'next-intl/server';
const messages = await getMessages();时区
指定时区会影响日期和时间的渲染。默认使用服务器运行时的时区,但可以根据需要自定义。
import {getRequestConfig} from 'next-intl/server';
export default getRequestConfig(async () => {
return {
// 时区可以静态定义,读取用户配置(如果存储了此设置),
// 或基于请求动态信息,如 locale 或 cookie。
timeZone: 'Europe/Vienna'
// ...
};
});可用时区名称可在tz 数据库中查询。
如果您使用由服务器组件渲染的 NextIntlClientProvider 将相关客户端组件包裹起来,客户端组件会自动继承服务器端的时区。其他情况,您可以在包裹的 NextIntlClientProvider 上显式指定该值。
了解更多:
useTimeZone 和 getTimeZone
可以在组件中通过 useTimeZone 或 getTimeZone 读取配置的时区:
// 普通组件
import {useTimeZone} from 'next-intl';
const timeZone = useTimeZone();
// 异步服务器组件
import {getTimeZone} from 'next-intl/server';
const timeZone = await getTimeZone();当前时间值
在格式化相对日期和时间时,next-intl 会以一个参考时间点「现在」为基准进行格式化。虽然提供这个值有助于缓存,但您也可以提供全局的 now 值,例如确保测试时的一致性。
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 包裹客户端组件,则客户端组件也会自动继承。
useNow 和 getNow
配置的 now 值可以通过 useNow 或 getNow 在组件中获取:
// 普通组件
import {useNow} from 'next-intl';
const now = useNow();
// 异步服务器组件
import {getNow} from 'next-intl/server';
const now = await getNow();注意,返回值默认是当前时间,因此即使您没有配置全局 now,使用此 hook 在为 format.relativeTime提供 now 时也很有用。
格式
喜欢看视频吗?
为了实现日期、时间、数字和列表格式的一致性,您可以定义一套全局格式。
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
类型来使用严格类型。
数字、日期和时间的全局格式也可以在消息中引用:
{
"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 包裹相关组件,则格式会自动从服务器继承。
错误处理 (onError 和 getMessageFallback)
默认情况下,当消息解析失败或格式化失败时,会在控制台打印错误,并渲染 ${namespace}.${key} 以保证应用继续运行。
您可以通过配置 onError 和 getMessageFallback 自定义此行为。
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;
}
}
// ...
};
});注意,onError 和 getMessageFallback 不会被客户端组件自动继承。如果您想让客户端组件也可用该功能,可以创建一个客户端 provider 并定义这些属性。