Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support ./i18n/request.ts in addition to ./i18n.ts #1308

Merged
merged 2 commits into from
Aug 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/pages/blog/next-intl-3-0.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ The latter two have already been added in minor versions, but 3.0 cleans up the

`next-intl` now requires two additional setup steps when you're using the App Router:

1. [The `i18n.ts` module](/docs/getting-started/app-router#i18nts) provides configuration for Server Components
1. [The `i18n.ts` module](/docs/getting-started/app-router#i18n-request) provides configuration for Server Components
2. [`next-intl/plugin`](/docs/getting-started/app-router#nextconfigjs) needs to be added to link your `i18n.ts` module to `next-intl`

### New navigation APIs for the App Router
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ If you're using the [`pathnames`](/docs/routing#pathnames) setting, you can gene
```tsx
import {MetadataRoute} from 'next';
import {locales, defaultLocale} from '@/config';
import {getPathname} from '@/routing';
import {getPathname} from '@/i18n/routing';

// Adapt this as necessary
const host = 'https://acme.com';
Expand Down
2 changes: 1 addition & 1 deletion docs/pages/docs/environments/core-library.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ While `next-intl` is primarily intended to be used in Next.js apps, the core is

1. [Routing APIs](/docs/routing)
2. [Awaitable APIs](/docs/environments/actions-metadata-route-handlers) for Server Actions, Metadata and Route Handlers
3. [Server Components integration](/docs/environments/server-client-components) along with `i18n.ts`
3. [Server Components integration](/docs/environments/server-client-components) along with `i18n/request.ts`

In case Server Components establish themselves in React apps outside of Next.js, the support for Server Components might be moved to the core library in the future.

Expand Down
10 changes: 4 additions & 6 deletions docs/pages/docs/environments/error-files.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -77,18 +77,16 @@ export default function RootLayout({children}) {
}
```

For the 404 page to render, we need to call the `notFound` function in [`i18n.ts`](/docs/usage/configuration#i18nts) when we detect an incoming `locale` param that isn't a valid locale.
For the 404 page to render, we need to call the `notFound` function in [`i18n/request.ts`](/docs/usage/configuration#i18n-request) when we detect an incoming `locale` param that isn't a valid locale.

```tsx filename="i18n.ts"
```tsx filename="i18n/request.ts"
import {notFound} from 'next/navigation';
import {getRequestConfig} from 'next-intl/server';

// Can be imported from a shared config
const locales = ['en', 'de'];
import {routing} from '@/i18n/routing';

export default getRequestConfig(async ({locale}) => {
// Validate that the incoming `locale` parameter is valid
if (!locales.includes(locale as any)) notFound();
if (!routing.locales.includes(locale as any)) notFound();

return {
// ...
Expand Down
10 changes: 5 additions & 5 deletions docs/pages/docs/environments/server-client-components.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ If you import `useTranslations`, `useFormatter`, `useLocale`, `useNow` and `useT
<Details id="rsc-background">
<summary>How does the Server Components integration work?</summary>

`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).
`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/request.ts`](/docs/usage/configuration#i18n-request).

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.

Expand All @@ -106,13 +106,13 @@ If you implement components that qualify as shared components, it can be benefic

However, there's no need to dogmatically use non-async functions exclusively for handling internationalization—use what fits your app best.

In regard to performance, async functions and hooks can be used very much interchangeably. The configuration from [`i18n.ts`](/docs/usage/configuration#i18nts) is only loaded once upon first usage and both implementations use request-based caching internally where relevant. The only minor difference is that async functions have the benefit that rendering can be resumed right after an async function has been invoked. In contrast, in case a hook call triggers the initialization in `i18n.ts`, the component will suspend until the config is resolved and will re-render subsequently, possibly re-executing component logic prior to the hook call. However, once config has been resolved as part of a request, hooks will execute synchronously without suspending, resulting in less overhead in comparison to async functions since rendering can be resumed without having to wait for the microtask queue to flush (see [resuming a suspended component by replaying its execution](https://github.com/acdlite/rfcs/blob/first-class-promises/text/0000-first-class-support-for-promises.md#resuming-a-suspended-component-by-replaying-its-execution) in the corresponding React RFC).
Regarding performance, async functions and hooks can be used interchangeably. The configuration from [`i18n/request.ts`](/docs/usage/configuration#i18n-request) is only loaded once upon first usage and both implementations use request-based caching internally where relevant. The only minor difference is that async functions have the benefit that rendering can be resumed right after an async function has been invoked. In contrast, in case a hook call triggers the initialization in `i18n/request.ts`, the component will suspend until the config is resolved and will re-render subsequently, possibly re-executing component logic prior to the hook call. However, once config has been resolved as part of a request, hooks will execute synchronously without suspending, resulting in less overhead in comparison to async functions since rendering can be resumed without having to wait for the microtask queue to flush (see [resuming a suspended component by replaying its execution](https://github.com/acdlite/rfcs/blob/first-class-promises/text/0000-first-class-support-for-promises.md#resuming-a-suspended-component-by-replaying-its-execution) in the corresponding React RFC).

</Details>

## Using internationalization in Client Components

Depending on your situation, you may need to handle internationalization in Client Components as well. While providing all messages to the client side is typically the easiest way to [get started](/docs/getting-started/app-router#layout) and a reasonable approach for many apps, you can be more selective about which messages are passed to the client side if you're interested in optimizing the performance of your app.
Depending on your situation, you may need to handle internationalization in Client Components. While providing all messages to the client side is typically the easiest way to [get started](/docs/getting-started/app-router#layout) and a reasonable approach for many apps, you can be more selective about which messages are passed to the client side if you're interested in optimizing the performance of your app.

<Details id="client-messages-performance">
<summary>How does loading messages on the client side relate to performance?</summary>
Expand Down Expand Up @@ -298,7 +298,7 @@ import {NextIntlClientProvider, useMessages} from 'next-intl';
import ClientCounter from './ClientCounter';

export default function Counter() {
// Receive messages provided in `i18n.ts` …
// Receive messages provided in `i18n/request.ts` …
const messages = useMessages();

return (
Expand Down Expand Up @@ -332,7 +332,7 @@ import {NextIntlClientProvider} from 'next-intl';
import {getMessages} from 'next-intl/server';

export default async function RootLayout(/* ... */) {
// Receive messages provided in `i18n.ts`
// Receive messages provided in `i18n/request.ts`
const messages = await getMessages();

return (
Expand Down
36 changes: 18 additions & 18 deletions docs/pages/docs/getting-started/app-router/with-i18n-routing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ Now, we're going to create the following file structure:
│ └── ...
├── next.config.mjs (2)
└── src
├── routing.ts (3)
├── i18n
│ ├── routing.ts (3)
│ └── request.ts (5)
├── middleware.ts (4)
├── i18n.ts (5)
└── app
└── [locale]
├── layout.tsx (6)
Expand Down Expand Up @@ -93,7 +94,7 @@ module.exports = withNextIntl(nextConfig);
</Tab>
</Tabs>

### `src/routing.ts` [#i18n-routing]
### `src/i18n/routing.ts` [#i18n-routing]

We'll integrate with Next.js' routing in two places:

Expand All @@ -104,7 +105,7 @@ This enables you to work with pathnames like `/about`, while i18n aspects like l

To share the configuration between these two places, we'll set up `routing.ts`:

```ts filename="src/routing.ts"
```ts filename="src/i18n/routing.ts"
import {defineRouting} from 'next-intl/routing';
import {createSharedPathnamesNavigation} from 'next-intl/navigation';

Expand All @@ -130,7 +131,7 @@ Once we have our routing configuration in place, we can use it to set up the mid

```tsx filename="src/middleware.ts"
import createMiddleware from 'next-intl/middleware';
import {routing} from './routing';
import {routing} from './i18n/routing';

export default createMiddleware(routing);

Expand All @@ -140,11 +141,11 @@ export const config = {
};
```

### `src/i18n.ts` [#i18n-request]
### `src/i18n/request.ts` [#i18n-request]

`next-intl` creates a request-scoped configuration object, which you can use to provide messages and other options based on the user's locale to Server Components.

```tsx filename="src/i18n.ts"
```tsx filename="src/i18n/request.ts"
import {notFound} from 'next/navigation';
import {getRequestConfig} from 'next-intl/server';
import {routing} from './routing';
Expand All @@ -154,30 +155,30 @@ export default getRequestConfig(async ({locale}) => {
if (!routing.locales.includes(locale as any)) notFound();

return {
messages: (await import(`../messages/${locale}.json`)).default
messages: (await import(`../../messages/${locale}.json`)).default
};
});
```

<Details id="move-i18n-request">
<Details id="i18n-request-path">
<summary>Can I move this file somewhere else?</summary>

This file is supported out-of-the-box as `./i18n.ts` both in the `src` folder as well as in the project root with the extensions `.ts`, `.tsx`, `.js` and `.jsx`.
This file is supported out-of-the-box as `./i18n/request.ts` both in the `src` folder as well as in the project root with the extensions `.ts`, `.tsx`, `.js` and `.jsx`.

If you prefer to move this file somewhere else, you can optionally provide a path to the plugin:

```js filename="next.config.mjs"
const withNextIntl = createNextIntlPlugin(
// Specify a custom path here
'./somewhere/else/i18n.ts'
'./somewhere/else/request.ts'
);
```

</Details>

### `src/app/[locale]/layout.tsx` [#layout]

The `locale` that was matched by the middleware is available via the `locale` param and can be used to configure the document language. Additionally, we can use this place to pass configuration from `i18n.ts` to Client Components via `NextIntlClientProvider`.
The `locale` that was matched by the middleware is available via the `locale` param and can be used to configure the document language. Additionally, we can use this place to pass configuration from `i18n/request.ts` to Client Components via `NextIntlClientProvider`.

```tsx filename="app/[locale]/layout.tsx"
import {NextIntlClientProvider} from 'next-intl';
Expand Down Expand Up @@ -206,7 +207,7 @@ export default async function LocaleLayout({
}
```

Note that `NextIntlClientProvider` automatically inherits configuration from `i18n.ts` here, but `messages` need to be passed explicitly.
Note that `NextIntlClientProvider` automatically inherits configuration from `i18n/request.ts` here, but `messages` need to be passed explicitly.

### `src/app/[locale]/page.tsx` [#page]

Expand All @@ -216,7 +217,7 @@ Now you can use translations and other functionality from `next-intl` in your co

```tsx filename="app/[locale]/page.tsx"
import {useTranslations} from 'next-intl';
import {Link} from '@/routing';
import {Link} from '@/i18n/routing';

export default function HomePage() {
const t = useTranslations('HomePage');
Expand Down Expand Up @@ -264,11 +265,10 @@ When using the setup with i18n routing, `next-intl`will currently opt into dynam
Since we are using a dynamic route segment for the `[locale]` param, we need to pass all possible values to Next.js via [`generateStaticParams`](https://nextjs.org/docs/app/api-reference/functions/generate-static-params) so that the routes can be rendered at build time.

```tsx filename="app/[locale]/layout.tsx"
// Can be imported from a shared config
const locales = ['en', 'de'];
import {routing} from '@/i18n/routing';

export function generateStaticParams() {
return locales.map((locale) => ({locale}));
return routing.locales.map((locale) => ({locale}));
}
```

Expand Down Expand Up @@ -306,7 +306,7 @@ export default function IndexPage({params: {locale}}) {

**Keep in mind that:**

1. The locale that you pass to `unstable_setRequestLocale` should be validated (e.g. in [`i18n.ts`](/docs/usage/configuration#i18nts)).
1. The locale that you pass to `unstable_setRequestLocale` should be validated (e.g. in [`i18n/request.ts`](/docs/usage/configuration#i18n-request)).

2. You need to call this function in every page and every layout that you intend to enable static rendering for since Next.js can render layouts and pages independently.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ Now, we're going to create the following file structure:
│ └── ...
├── next.config.mjs (2)
└── src
├── i18n.ts (3)
├── i18n
│ └── request.tsx (3)
└── app
├── layout.tsx (4)
└── page.tsx (5)
Expand Down Expand Up @@ -87,11 +88,11 @@ module.exports = withNextIntl(nextConfig);
</Tab>
</Tabs>

### `i18n.ts` [#i18n-request]
### `i18n/request.ts` [#i18n-request]

`next-intl` creates a request-scoped configuration object, which you can use to provide messages and other options based on the user's locale to Server Components.

```tsx filename="src/i18n.ts"
```tsx filename="src/i18n/request.ts"
import {getRequestConfig} from 'next-intl/server';

export default getRequestConfig(async () => {
Expand All @@ -101,15 +102,15 @@ export default getRequestConfig(async () => {

return {
locale,
messages: (await import(`../messages/${locale}.json`)).default
messages: (await import(`../../messages/${locale}.json`)).default
};
});
```

<Details id="move-i18n-request">
<Details id="i18n-request-path">
<summary>Can I move this file somewhere else?</summary>

This file is supported out-of-the-box as `./i18n.ts` both in the `src` folder as well as in the project root with the extensions `.ts`, `.tsx`, `.js` and `.jsx`.
This file is supported out-of-the-box as `./i18n/request.ts` both in the `src` folder as well as in the project root with the extensions `.ts`, `.tsx`, `.js` and `.jsx`.

If you prefer to move this file somewhere else, you can optionally provide a path to the plugin:

Expand All @@ -124,7 +125,7 @@ const withNextIntl = createNextIntlPlugin(

### `app/layout.tsx` [#layout]

The `locale` that was provided in `i18n.ts` is available via `getLocale` and can be used to configure the document language. Additionally, we can use this place to pass configuration from `i18n.ts` to Client Components via `NextIntlClientProvider`.
The `locale` that was provided in `i18n/request.ts` is available via `getLocale` and can be used to configure the document language. Additionally, we can use this place to pass configuration from `i18n/request.ts` to Client Components via `NextIntlClientProvider`.

```tsx filename="app/layout.tsx"
import {NextIntlClientProvider} from 'next-intl';
Expand Down Expand Up @@ -153,7 +154,7 @@ export default async function RootLayout({
}
```

Note that `NextIntlClientProvider` automatically inherits configuration from `i18n.ts` here, but `messages` need to be passed explicitly.
Note that `NextIntlClientProvider` automatically inherits configuration from `i18n/request.ts` here, but `messages` need to be passed explicitly.

### `app/page.tsx` [#page]

Expand Down
2 changes: 1 addition & 1 deletion docs/pages/docs/routing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ This enables you to express your app in terms of APIs like `<Link href="/about">

The routing configuration that is shared between the [middleware](/docs/routing/middleware) and [the navigation APIs](/docs/routing/navigation) can be defined with the `defineRouting` function.

```tsx filename="src/routing.ts"
```tsx filename="src/i18n/routing.ts"
import {defineRouting} from 'next-intl/routing';

export const routing = defineRouting({
Expand Down
Loading
Loading