Skip to content

Commit 633e07d

Browse files
authored
refactor(RSC): Refactor async APIs to make the locale optional (#600)
1 parent 49fce7c commit 633e07d

File tree

30 files changed

+558
-186
lines changed

30 files changed

+558
-186
lines changed

docs/pages/blog/next-intl-3-0.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ If you're still happy with the Pages Router, rest assured that `next-intl` is de
1515
## New features
1616

1717
1. **Support for React Server Components**: The APIs `useTranslations`, `useFormatter`, `useLocale`, `useNow` and `useTimeZone` can now be used in Server Components ([proposed docs](https://next-intl-docs-git-feat-next-13-rsc-next-intl.vercel.app/docs/environments/server-client-components)).
18-
2. **New async APIs to handle i18n outside of components**: To handle i18n in the Metadata API and Route Handlers, the APIs `getTranslator`, `getFormatter`, `getNow`, and `getTimeZone` have been added ([proposed docs](https://next-intl-docs-git-feat-next-13-rsc-next-intl.vercel.app/docs/environments/metadata-route-handlers)).
18+
2. **New async APIs to handle i18n outside of components**: To handle i18n in the Metadata API and Route Handlers, the APIs `getTranslations`, `getFormatter`, `getNow`, and `getTimeZone` have been added ([proposed docs](https://next-intl-docs-git-feat-next-13-rsc-next-intl.vercel.app/docs/environments/metadata-route-handlers)).
1919
3. **Middleware for internationalized routing**: While Next.js has built-in support for this with the Pages Router, the App Router doesn't include a built-in solution anymore. `next-intl` now provides a drop-in solution that has you covered ([proposed docs](https://next-intl-docs-git-feat-next-13-rsc-next-intl.vercel.app/docs/routing/middleware)).
2020
4. **Internationalized navigation APIs**: Similar to the middleware, this provides a drop-in solution that adds internationalization support for Next.js' navigation APIs: `Link`, `useRouter`, `usePathname` and `redirect`. These APIs allow you to handle locale prefixes behind the scenes and also provide support for localizing pathnames (e.g. `/en/about` vs. `/de/ueber-uns`, see the [proposed docs](https://next-intl-docs-git-feat-next-13-rsc-next-intl.vercel.app/docs/routing/navigation)).
2121

docs/pages/blog/translations-outside-of-react-components.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,13 +116,13 @@ If you’re working with Next.js, you might want to translate i18n messages in [
116116
`next-intl/server` provides a set of awaitable versions of the functions that you usually call as hooks from within components. These are agnostic from React and can be used for these cases.
117117

118118
```tsx
119-
import {getTranslator} from 'next-intl/server';
119+
import {getTranslations} from 'next-intl/server';
120120

121121
// The `locale` is received from Next.js via `params`
122122
const locale = params.locale;
123123

124124
// This creates the same function that is returned by `useTranslations`.
125-
const t = await getTranslator(locale);
125+
const t = await getTranslations(locale);
126126

127127
// Result: "Hello world!"
128128
t('hello', {name: 'world'});

docs/pages/docs/environments/metadata-route-handlers.mdx

Lines changed: 10 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -9,42 +9,17 @@ There are a few places in Next.js apps where you might need to apply internation
99
2. [Metadata files](https://nextjs.org/docs/app/api-reference/file-conventions/metadata)
1010
3. [Route Handlers](https://nextjs.org/docs/app/building-your-application/routing/router-handlers)
1111

12-
`next-intl/server` provides a set of awaitable versions of the functions that you usually call as hooks from within components. Unlike the hooks, these functions require a `locale` that you [receive from Next.js](https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes).
13-
14-
```tsx
15-
import {
16-
getTranslator,
17-
getFormatter,
18-
getNow,
19-
getTimeZone,
20-
getMessages
21-
} from 'next-intl/server';
22-
23-
// The `locale` is received from Next.js via `params`
24-
const locale = params.locale;
25-
26-
const t = await getTranslator(locale, 'Metadata');
27-
const format = await getFormatter(locale);
28-
const now = await getNow(locale);
29-
const timeZone = await getTimeZone(locale);
30-
const messages = await getMessages(locale);
31-
```
32-
33-
<Callout>
34-
The request configuration that you've set up in `i18n.ts` is automatically
35-
inherited by these functions. The `locale` is the only exception that needs to
36-
be provided in comparison to the hooks.
37-
</Callout>
12+
`next-intl/server` provides a set of [awaitable functions](/docs/environments/server-client-components#async-components) that can be used in these cases.
3813

3914
### Metadata API
4015

4116
To internationalize metadata like the page title, you can use functionality from `next-intl` in the [`generateMetadata`](https://nextjs.org/docs/app/api-reference/functions/generate-metadata#generatemetadata-function) function that can be exported from pages and layouts.
4217

4318
```tsx filename="app/[locale]/layout.tsx"
44-
import {getTranslator} from 'next-intl/server';
19+
import {getTranslations} from 'next-intl/server';
4520

4621
export async function generateMetadata({params: {locale}}) {
47-
const t = await getTranslator(locale, 'Metadata');
22+
const t = await getTranslations({locale, namespace: 'Metadata'});
4823

4924
return {
5025
title: t('title')
@@ -58,27 +33,28 @@ If you need to internationalize content within [metadata files](https://nextjs.o
5833

5934
```tsx filename="app/[locale]/opengraph-image.tsx"
6035
import {ImageResponse} from 'next/og';
61-
import {getTranslator} from 'next-intl/server';
36+
import {getTranslations} from 'next-intl/server';
6237

63-
export default async function Image({params: {locale}}) {
64-
const t = await getTranslator(locale, 'OpenGraph');
38+
export default async function OpenGraphImage({params: {locale}}) {
39+
const t = await getTranslations({locale, namespace: 'OpenGraphImage'});
6540
return new ImageResponse(<div style={{fontSize: 128}}>{t('title')}</div>);
6641
}
6742
```
6843

6944
### Route Handlers
7045

71-
You can use `next-intl` in [Route Handlers](https://nextjs.org/docs/app/building-your-application/routing/router-handlers) too. The required `locale` can either be received from a search param, a layout segment or by parsing the `accept-language` header of the request.
46+
You can use `next-intl` in [Route Handlers](https://nextjs.org/docs/app/building-your-application/routing/router-handlers) too. The `locale` can either be received from a search param, a layout segment or by parsing the `accept-language` header of the request.
7247

7348
```tsx filename="app/api/hello/route.tsx"
7449
import {NextResponse} from 'next/server';
75-
import {getTranslator} from 'next-intl/server';
50+
import {getTranslations} from 'next-intl/server';
7651

7752
export async function GET(request) {
53+
// Example: Receive the `locale` via a search param
7854
const {searchParams} = new URL(request.url);
7955
const locale = searchParams.get('locale');
8056

81-
const t = await getTranslator(locale, 'Hello');
57+
const t = await getTranslations({locale, namespace: 'Hello'});
8258
return NextResponse.json({title: t('title')});
8359
}
8460
```

docs/pages/docs/environments/server-client-components.mdx

Lines changed: 85 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,120 @@
11
import Callout from 'components/Callout';
22

3-
# Internationalization of Server & Client Components in Next.js 13
3+
# Internationalization of Server & Client Components
44

5-
With the introduction of the App Router in Next.js 13, [React Server Components](https://nextjs.org/docs/getting-started/react-essentials) became publicly available. This new paradigm allows components that don’t require React’s interactive features, such as `useState` and `useEffect`, to remain server-side only.
5+
[React Server Components](https://nextjs.org/docs/app/building-your-application/rendering/server-components) allow you to implement components that remain server-side only if they don’t require React’s interactive features, such as `useState` and `useEffect`.
66

77
This applies to handling internationalization too.
88

99
```tsx filename="app/[locale]/page.tsx"
1010
import {useTranslations} from 'next-intl';
1111

1212
// Since this component doesn't use any interactive features
13-
// from React, it can be implemented as a Server Component.
13+
// from React, it can be run as a Server Component.
1414

1515
export default function Index() {
1616
const t = useTranslations('Index');
1717
return <h1>{t('title')}</h1>;
1818
}
1919
```
2020

21-
Depending on if you import `useTranslations`, `useFormatter`, `useLocale`, `useNow` and `useTimeZone` from a Server or Client Component, `next-intl` will automatically provide an implementation that works best for the given environment.
21+
Moving internationalization to the server side unlocks new levels of performance, leaving the client side for interactive features.
2222

23-
<details>
24-
<summary>Deep dive: How does the Server Components integration work?</summary>
23+
**Benefits of server-side internationalization:**
2524

26-
`next-intl` uses [`react-server` conditional exports](https://github.com/reactjs/rfcs/blob/main/text/0227-server-module-conventions.md#react-server-conditional-exports) to load code that is optimized for the usage in Server or Client Components. While configuration for hooks like `useTranslations` is read via `useContext` on the client side, on the server side it is loaded via [`i18n.ts`](/docs/usage/configuration#i18nts).
25+
1. Your messages never leave the server and don't need to be serialized for the client side
26+
2. Library code for internationalization doesn't need to be loaded on the client side
27+
3. No need to split your messages, e.g. based on routes or components
28+
4. No runtime cost on the client side
29+
5. No need to handle environment differences like different time zones on the server and client
30+
31+
## Using internationalization in Server Components
32+
33+
Server Components can be declared in two ways:
34+
35+
1. Async components
36+
2. Non-async, regular components
37+
38+
In a typical app, you'll likely find both types of components. `next-intl` provides corresponding APIs that work for the given component type.
39+
40+
### Async components
2741

28-
Hooks are currently primarly known for being used in Client Components since they are typically stateful or don't apply to a server environment. However, hooks like [`useId`](https://react.dev/reference/react/useId) can be used in Server Components too. Similarly, `next-intl` uses a hooks-based API that looks identical, regardless of if it's used in a Server or Client Component. This allows to use hooks like `useTranslations` in [shared components](https://github.com/reactjs/rfcs/blob/bf51f8755ddb38d92e23ad415fc4e3c02b95b331/text/0000-server-components.md#sharing-code-between-server-and-client), which can run both as a Server or a Client Component, depending on where they are imported from.
42+
These are primarly concerned with fetching data and [can not use hooks](https://github.com/reactjs/rfcs/blob/main/text/0188-server-components.md#capabilities--constraints-of-server-and-client-components). Due to this, `next-intl` provides a set of awaitable versions of the functions that you usually call as hooks from within components.
2943

30-
The one restriction that currently comes with this pattern is that hooks can not be called from `async` components. To resolve this,
31-
you can split your component into two, leaving the async code in the first one and
32-
moving the usage of the hook to the second one.
44+
```tsx filename="[locale]/profile/page.tsx"
45+
import {getTranslations} from 'next-intl/server';
3346

34-
**Example:**
47+
export default async function ProfilePage() {
48+
const user = await fetchUser();
49+
const t = await getTranslations('ProfilePage');
3550

36-
```tsx filename="app/[locale]/profile/page.tsx"
37-
export default async function Profile() {
38-
// Use this component for all async code ...
39-
const user = await getUser();
40-
return <ProfileContent user={user} />;
51+
return (
52+
<PageLayout title={t('title', {username: user.name})}>
53+
<UserDetails user={user} />
54+
</PageLayout>
55+
);
4156
}
57+
```
58+
59+
These functions are available:
60+
61+
```tsx
62+
import {
63+
getTranslations,
64+
getFormatter,
65+
getNow,
66+
getTimeZone,
67+
getMessages,
68+
getLocale
69+
} from 'next-intl/server';
70+
71+
const t = await getTranslations('ProfilePage');
72+
const format = await getFormatter();
73+
const now = await getNow();
74+
const timeZone = await getTimeZone();
75+
const messages = await getMessages();
76+
```
77+
78+
### Non-async components [#shared-components]
79+
80+
Components that aren't declared with the `async` keyword and don't use interactive features like `useState`, are referred to as [shared components](https://github.com/reactjs/rfcs/blob/main/text/0188-server-components.md#sharing-code-between-server-and-client). These can render either as a Server or Client Component, depending on where they are imported from.
4281

43-
function ProfileContent({user}) {
44-
// ... and use this one for rendering the fetched data
45-
const t = useTranslations('ProfileContent');
46-
return <p><{t('title', {userName: user.name})}/p>
82+
In Next.js, Server Components are the default, and therefore shared components will typically execute as Server Components.
83+
84+
```tsx filename="UserDetails.tsx"
85+
import {useTranslations} from 'next-intl';
86+
87+
export default function UserDetails({user}) {
88+
const t = useTranslations('UserProfile');
89+
90+
return (
91+
<section>
92+
<h2>{t('title')}</h2>
93+
<p>{t('followers', {count: user.numFollowers})}</p>
94+
</section>
95+
);
4796
}
4897
```
4998

50-
As a benefit, the extracted component now works both in Server as well as Client Components, depending on where it is rendered.
99+
If you import `useTranslations`, `useFormatter`, `useLocale`, `useNow` and `useTimeZone` from a shared component, `next-intl` will automatically provide an implementation that works best for the environment this component executes in (server or client).
51100

52-
For edge cases in server-only components, you can use [awaitable APIs from `next-intl`](/docs/environments/metadata-route-handlers).
101+
<details>
102+
<summary>How does the Server Components integration work?</summary>
103+
104+
`next-intl` uses [`react-server` conditional exports](https://github.com/reactjs/rfcs/blob/main/text/0227-server-module-conventions.md#react-server-conditional-exports) to load code that is optimized for the usage in Server or Client Components. While configuration for hooks like `useTranslations` is read via `useContext` on the client side, on the server side it is loaded via [`i18n.ts`](/docs/usage/configuration#i18nts).
105+
106+
Hooks are currently primarly known for being used in Client Components since they are typically stateful or don't apply to a server environment. However, hooks like [`useId`](https://react.dev/reference/react/useId) can be used in Server Components too. Similarly, `next-intl` provides a hooks-based API that looks identical, regardless of if it's used in a Server or Client Component.
107+
108+
The one restriction that currently comes with this pattern is that hooks can not be called from `async` components. `next-intl` therefore provides a separate set of [awaitable APIs](#async-components) for this use case.
53109

54110
</details>
55111

56-
## Benefits of handling i18n in Server Components [#server-components-benefits]
112+
<details>
113+
<summary>Should I use async or non-async functions for my components?</summary>
57114

58-
Moving internationalization to the server side unlocks new levels of performance, leaving the client side for interactive features.
115+
If you implement components that qualify as shared components, it can be beneficial to implement them as non-async functions. This allows to use these components either in a server or client environment, making them really flexible. Even if you don't intend to to ever run a particular component on the client side, this compatibility can still be helpful, e.g. for simplified testing. However, there's no need to dogmatically use non-async functions exclusively for handling internationalization—use what fits your app best.
59116

60-
<Callout emoji="" title="Benefits of server-side internationalization:">
61-
<ol className="ml-4 list-decimal">
62-
<li>
63-
Your messages never leave the server and don't need to be serialized for
64-
the client side
65-
</li>
66-
<li>
67-
Library code for internationalization doesn't need to be loaded on the
68-
client side
69-
</li>
70-
<li>No need to split your messages, e.g. based on routes or components</li>
71-
<li>No runtime cost on the client side</li>
72-
<li>
73-
No need to handle environment differences like different time zones on the
74-
server and client
75-
</li>
76-
</ol>
77-
</Callout>
117+
</details>
78118

79119
## Using internationalization in Client Components
80120

@@ -211,8 +251,8 @@ export default async function LocaleLayout({children, params: {locale}}) {
211251
```
212252

213253
<Callout type="warning">
214-
Note that this is a tradeoff in regard to performance (see [the bullet points
215-
above](#server-components-benefits)).
254+
Note that this is a tradeoff in regard to performance (see the bullet points
255+
at the top of this page).
216256
</Callout>
217257

218258
## Troubleshooting

docs/pages/docs/getting-started/app-router.mdx

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import Callout from 'components/Callout';
22
import Steps from 'components/Steps';
33

4-
# Next.js 13: Internationalization (i18n)
4+
# Next.js App Router Internationalization (i18n)
55

6-
Next.js 13 introduces support for [React Server Components](https://nextjs.org/docs/getting-started/react-essentials) with the App Router and unlocks [many benefits](/docs/environments/server-client-components) when handling internationalization on the server side.
6+
Next.js 13 introduces support for [React Server Components](https://nextjs.org/docs/app/building-your-application/rendering/server-components) with the App Router and unlocks [many benefits](/docs/environments/server-client-components) when handling internationalization on the server side.
77

88
## Getting started
99

@@ -155,11 +155,17 @@ That's all it takes!
155155
<li>Exploring `next-intl`? Check out the [usage guide](/docs/usage).</li>
156156
<li>
157157
Ran into an issue? Have a look at [the App Router
158-
example](https://next-intl-example-next-13.vercel.app/)
159-
([source](https://github.com/amannn/next-intl/tree/main/examples/example-next-13)).
158+
example](https://next-intl-example-next-13.vercel.app/).
159+
</li>
160+
<li>
161+
Want to learn more about about using translations across the server and
162+
client? Check out [the Server & Client Components
163+
guide](/docs/environments/server-client-components).
164+
</li>
165+
<li>
166+
Wondering how to link between internationalized pages? Have a look at [the
167+
navigation docs](/docs/routing/navigation).
160168
</li>
161-
<li>Considering using `next-intl` in Client Components? Check out [the Client Components guide](/docs/environments/server-client-components).</li>
162-
<li>Wondering how to link between internationalized pages? Have a look at [the navigation docs](/docs/routing/navigation).</li>
163169
</ul>
164170

165171
</Callout>
@@ -215,7 +221,7 @@ export default function IndexPage({
215221
}) {
216222
unstable_setRequestLocale(locale);
217223

218-
// Once the request locale is set, you
224+
// Once the request locale is set, you
219225
// can call hooks from `next-intl`
220226
const t = useTranslations('IndexPage');
221227

@@ -227,8 +233,6 @@ export default function IndexPage({
227233

228234
**Important:** `unstable_setRequestLocale` needs to be called after the `locale` is validated, but before you call any hooks from `next-intl`. Otherwise, you'll get an error when trying to prerender the page.
229235

230-
</Steps>
231-
232236
<details>
233237
<summary>What does "unstable" mean?</summary>
234238

@@ -237,8 +241,8 @@ export default function IndexPage({
237241
Note that Next.js can render layouts and pages indepently. This means that e.g. when you navigate from `/settings/profile` to `/settings/privacy`, the `/settings` segment might not re-render as part of the request. Due to this, it's important that `unstable_setRequestLocale` is called not only in the parent `settings/layout.tsx`, but also in the individual pages `profile/page.tsx` and `privacy/page.tsx`.
238242

239243
That being said, the API is expected to work reliably if you're cautious to apply it in all relevant places.
240-
</details>
241244

245+
</details>
242246

243247
<details>
244248
<summary>How does unstable_setRequestLocale work?</summary>
@@ -248,3 +252,23 @@ That being said, the API is expected to work reliably if you're cautious to appl
248252
Note that the store is scoped to a request and therefore doesn't affect other requests that might be handled in parallel while a given request resolves asynchronously.
249253

250254
</details>
255+
256+
### Use the `locale` param in metadata
257+
258+
In addition to the rendering of your pages, also page metadata needs to qualify for static rendering.
259+
260+
To achieve this, you can forward the `locale` that you receive from Next.js via `params` to [the awaitable functions from `next-intl`](/docs/environments/server-client-components#async-components).
261+
262+
```tsx filename="page.tsx"
263+
import {getTranslations} from 'next-intl/server';
264+
265+
export async function generateMetadata({params: {locale}}) {
266+
const t = await getTranslations({locale, namespace: 'Metadata'});
267+
268+
return {
269+
title: t('title')
270+
};
271+
}
272+
```
273+
274+
</Steps>

0 commit comments

Comments
 (0)