diff --git a/docs/pages/blog/next-intl-3-0.mdx b/docs/pages/blog/next-intl-3-0.mdx
index 048c1e0d0..f79ffe703 100644
--- a/docs/pages/blog/next-intl-3-0.mdx
+++ b/docs/pages/blog/next-intl-3-0.mdx
@@ -15,7 +15,7 @@ If you're still happy with the Pages Router, rest assured that `next-intl` is de
## New features
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)).
-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)).
+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)).
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)).
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)).
diff --git a/docs/pages/blog/translations-outside-of-react-components.mdx b/docs/pages/blog/translations-outside-of-react-components.mdx
index a84abc5d9..608337aee 100644
--- a/docs/pages/blog/translations-outside-of-react-components.mdx
+++ b/docs/pages/blog/translations-outside-of-react-components.mdx
@@ -116,13 +116,13 @@ If you’re working with Next.js, you might want to translate i18n messages in [
`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.
```tsx
-import {getTranslator} from 'next-intl/server';
+import {getTranslations} from 'next-intl/server';
// The `locale` is received from Next.js via `params`
const locale = params.locale;
// This creates the same function that is returned by `useTranslations`.
-const t = await getTranslator(locale);
+const t = await getTranslations(locale);
// Result: "Hello world!"
t('hello', {name: 'world'});
diff --git a/docs/pages/docs/environments/metadata-route-handlers.mdx b/docs/pages/docs/environments/metadata-route-handlers.mdx
index 96241b91d..1924078d4 100644
--- a/docs/pages/docs/environments/metadata-route-handlers.mdx
+++ b/docs/pages/docs/environments/metadata-route-handlers.mdx
@@ -9,42 +9,17 @@ There are a few places in Next.js apps where you might need to apply internation
2. [Metadata files](https://nextjs.org/docs/app/api-reference/file-conventions/metadata)
3. [Route Handlers](https://nextjs.org/docs/app/building-your-application/routing/router-handlers)
-`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).
-
-```tsx
-import {
- getTranslator,
- getFormatter,
- getNow,
- getTimeZone,
- getMessages
-} from 'next-intl/server';
-
-// The `locale` is received from Next.js via `params`
-const locale = params.locale;
-
-const t = await getTranslator(locale, 'Metadata');
-const format = await getFormatter(locale);
-const now = await getNow(locale);
-const timeZone = await getTimeZone(locale);
-const messages = await getMessages(locale);
-```
-
-
- The request configuration that you've set up in `i18n.ts` is automatically
- inherited by these functions. The `locale` is the only exception that needs to
- be provided in comparison to the hooks.
-
+`next-intl/server` provides a set of [awaitable functions](/docs/environments/server-client-components#async-components) that can be used in these cases.
### Metadata API
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.
```tsx filename="app/[locale]/layout.tsx"
-import {getTranslator} from 'next-intl/server';
+import {getTranslations} from 'next-intl/server';
export async function generateMetadata({params: {locale}}) {
- const t = await getTranslator(locale, 'Metadata');
+ const t = await getTranslations({locale, namespace: 'Metadata'});
return {
title: t('title')
@@ -58,27 +33,28 @@ If you need to internationalize content within [metadata files](https://nextjs.o
```tsx filename="app/[locale]/opengraph-image.tsx"
import {ImageResponse} from 'next/og';
-import {getTranslator} from 'next-intl/server';
+import {getTranslations} from 'next-intl/server';
-export default async function Image({params: {locale}}) {
- const t = await getTranslator(locale, 'OpenGraph');
+export default async function OpenGraphImage({params: {locale}}) {
+ const t = await getTranslations({locale, namespace: 'OpenGraphImage'});
return new ImageResponse(
{t('title')}
);
}
```
### Route Handlers
-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.
+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.
```tsx filename="app/api/hello/route.tsx"
import {NextResponse} from 'next/server';
-import {getTranslator} from 'next-intl/server';
+import {getTranslations} from 'next-intl/server';
export async function GET(request) {
+ // Example: Receive the `locale` via a search param
const {searchParams} = new URL(request.url);
const locale = searchParams.get('locale');
- const t = await getTranslator(locale, 'Hello');
+ const t = await getTranslations({locale, namespace: 'Hello'});
return NextResponse.json({title: t('title')});
}
```
diff --git a/docs/pages/docs/environments/server-client-components.mdx b/docs/pages/docs/environments/server-client-components.mdx
index 65ab1841d..e59b04842 100644
--- a/docs/pages/docs/environments/server-client-components.mdx
+++ b/docs/pages/docs/environments/server-client-components.mdx
@@ -1,8 +1,8 @@
import Callout from 'components/Callout';
-# Internationalization of Server & Client Components in Next.js 13
+# Internationalization of Server & Client Components
-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.
+[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`.
This applies to handling internationalization too.
@@ -10,7 +10,7 @@ This applies to handling internationalization too.
import {useTranslations} from 'next-intl';
// Since this component doesn't use any interactive features
-// from React, it can be implemented as a Server Component.
+// from React, it can be run as a Server Component.
export default function Index() {
const t = useTranslations('Index');
@@ -18,63 +18,103 @@ export default function Index() {
}
```
-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.
+Moving internationalization to the server side unlocks new levels of performance, leaving the client side for interactive features.
-
-Deep dive: How does the Server Components integration work?
+**Benefits of server-side internationalization:**
-`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).
+1. Your messages never leave the server and don't need to be serialized for the client side
+2. Library code for internationalization doesn't need to be loaded on the client side
+3. No need to split your messages, e.g. based on routes or components
+4. No runtime cost on the client side
+5. No need to handle environment differences like different time zones on the server and client
+
+## Using internationalization in Server Components
+
+Server Components can be declared in two ways:
+
+1. Async components
+2. Non-async, regular components
+
+In a typical app, you'll likely find both types of components. `next-intl` provides corresponding APIs that work for the given component type.
+
+### Async components
-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.
+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.
-The one restriction that currently comes with this pattern is that hooks can not be called from `async` components. To resolve this,
-you can split your component into two, leaving the async code in the first one and
-moving the usage of the hook to the second one.
+```tsx filename="[locale]/profile/page.tsx"
+import {getTranslations} from 'next-intl/server';
-**Example:**
+export default async function ProfilePage() {
+ const user = await fetchUser();
+ const t = await getTranslations('ProfilePage');
-```tsx filename="app/[locale]/profile/page.tsx"
-export default async function Profile() {
- // Use this component for all async code ...
- const user = await getUser();
- return ;
+ return (
+
+
+
+ );
}
+```
+
+These functions are available:
+
+```tsx
+import {
+ getTranslations,
+ getFormatter,
+ getNow,
+ getTimeZone,
+ getMessages,
+ getLocale
+} from 'next-intl/server';
+
+const t = await getTranslations('ProfilePage');
+const format = await getFormatter();
+const now = await getNow();
+const timeZone = await getTimeZone();
+const messages = await getMessages();
+```
+
+### Non-async components [#shared-components]
+
+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.
-function ProfileContent({user}) {
- // ... and use this one for rendering the fetched data
- const t = useTranslations('ProfileContent');
- return
<{t('title', {userName: user.name})}/p>
+In Next.js, Server Components are the default, and therefore shared components will typically execute as Server Components.
+
+```tsx filename="UserDetails.tsx"
+import {useTranslations} from 'next-intl';
+
+export default function UserDetails({user}) {
+ const t = useTranslations('UserProfile');
+
+ return (
+
+
{t('title')}
+
{t('followers', {count: user.numFollowers})}
+
+ );
}
```
-As a benefit, the extracted component now works both in Server as well as Client Components, depending on where it is rendered.
+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).
-For edge cases in server-only components, you can use [awaitable APIs from `next-intl`](/docs/environments/metadata-route-handlers).
+
+How does the Server Components integration work?
+
+`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).
+
+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.
+
+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.
-## Benefits of handling i18n in Server Components [#server-components-benefits]
+
+Should I use async or non-async functions for my components?
-Moving internationalization to the server side unlocks new levels of performance, leaving the client side for interactive features.
+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.
-
-
-
- Your messages never leave the server and don't need to be serialized for
- the client side
-
-
- Library code for internationalization doesn't need to be loaded on the
- client side
-
-
No need to split your messages, e.g. based on routes or components
-
No runtime cost on the client side
-
- No need to handle environment differences like different time zones on the
- server and client
-
-
-
+
## Using internationalization in Client Components
@@ -211,8 +251,8 @@ export default async function LocaleLayout({children, params: {locale}}) {
```
- Note that this is a tradeoff in regard to performance (see [the bullet points
- above](#server-components-benefits)).
+ Note that this is a tradeoff in regard to performance (see the bullet points
+ at the top of this page).
## Troubleshooting
diff --git a/docs/pages/docs/getting-started/app-router.mdx b/docs/pages/docs/getting-started/app-router.mdx
index 2a26cf865..c4b570a31 100644
--- a/docs/pages/docs/getting-started/app-router.mdx
+++ b/docs/pages/docs/getting-started/app-router.mdx
@@ -1,9 +1,9 @@
import Callout from 'components/Callout';
import Steps from 'components/Steps';
-# Next.js 13: Internationalization (i18n)
+# Next.js App Router Internationalization (i18n)
-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.
+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.
## Getting started
@@ -155,11 +155,17 @@ That's all it takes!
Exploring `next-intl`? Check out the [usage guide](/docs/usage).
Ran into an issue? Have a look at [the App Router
- example](https://next-intl-example-next-13.vercel.app/)
- ([source](https://github.com/amannn/next-intl/tree/main/examples/example-next-13)).
+ example](https://next-intl-example-next-13.vercel.app/).
+
+
+ Want to learn more about about using translations across the server and
+ client? Check out [the Server & Client Components
+ guide](/docs/environments/server-client-components).
+
+
+ Wondering how to link between internationalized pages? Have a look at [the
+ navigation docs](/docs/routing/navigation).
-
Considering using `next-intl` in Client Components? Check out [the Client Components guide](/docs/environments/server-client-components).
-
Wondering how to link between internationalized pages? Have a look at [the navigation docs](/docs/routing/navigation).
@@ -215,7 +221,7 @@ export default function IndexPage({
}) {
unstable_setRequestLocale(locale);
- // Once the request locale is set, you
+ // Once the request locale is set, you
// can call hooks from `next-intl`
const t = useTranslations('IndexPage');
@@ -227,8 +233,6 @@ export default function IndexPage({
**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.
-
-
What does "unstable" mean?
@@ -237,8 +241,8 @@ export default function IndexPage({
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`.
That being said, the API is expected to work reliably if you're cautious to apply it in all relevant places.
-
+How does unstable_setRequestLocale work?
@@ -248,3 +252,23 @@ That being said, the API is expected to work reliably if you're cautious to appl
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.
+
+### Use the `locale` param in metadata
+
+In addition to the rendering of your pages, also page metadata needs to qualify for static rendering.
+
+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).
+
+```tsx filename="page.tsx"
+import {getTranslations} from 'next-intl/server';
+
+export async function generateMetadata({params: {locale}}) {
+ const t = await getTranslations({locale, namespace: 'Metadata'});
+
+ return {
+ title: t('title')
+ };
+}
+```
+
+
diff --git a/examples/example-next-13-advanced/package.json b/examples/example-next-13-advanced/package.json
index c75c520fd..db58c4528 100644
--- a/examples/example-next-13-advanced/package.json
+++ b/examples/example-next-13-advanced/package.json
@@ -27,6 +27,7 @@
"eslint": "^8.46.0",
"eslint-config-molindo": "7.0.0-alpha.7",
"eslint-config-next": "^13.4.0",
+ "sharp": "^0.32.6",
"typescript": "^5.0.0"
}
}
diff --git a/examples/example-next-13-advanced/src/app/[locale]/api/route.ts b/examples/example-next-13-advanced/src/app/[locale]/api/route.ts
index 045117cff..921971072 100644
--- a/examples/example-next-13-advanced/src/app/[locale]/api/route.ts
+++ b/examples/example-next-13-advanced/src/app/[locale]/api/route.ts
@@ -1,5 +1,5 @@
import {NextRequest, NextResponse} from 'next/server';
-import {getTranslator} from 'next-intl/server';
+import {getTranslations} from 'next-intl/server';
type Props = {
params: {
@@ -13,6 +13,6 @@ export async function GET(request: NextRequest, {params: {locale}}: Props) {
return new Response('Search param `name` was not provided.', {status: 400});
}
- const t = await getTranslator(locale, 'ApiRoute');
+ const t = await getTranslations({locale, namespace: 'ApiRoute'});
return NextResponse.json({message: t('hello', {name})});
}
diff --git a/examples/example-next-13-advanced/src/app/[locale]/layout.tsx b/examples/example-next-13-advanced/src/app/[locale]/layout.tsx
index f9ad554bf..559db61ae 100644
--- a/examples/example-next-13-advanced/src/app/[locale]/layout.tsx
+++ b/examples/example-next-13-advanced/src/app/[locale]/layout.tsx
@@ -5,7 +5,7 @@ import {
getFormatter,
getNow,
getTimeZone,
- getTranslator
+ getTranslations
} from 'next-intl/server';
import {ReactNode} from 'react';
import Navigation from '../../components/Navigation';
@@ -18,10 +18,10 @@ type Props = {
export async function generateMetadata({
params: {locale}
}: Omit): Promise {
- const t = await getTranslator(locale, 'LocaleLayout');
- const formatter = await getFormatter(locale);
- const now = await getNow(locale);
- const timeZone = await getTimeZone(locale);
+ const t = await getTranslations({locale, namespace: 'LocaleLayout'});
+ const formatter = await getFormatter({locale});
+ const now = await getNow({locale});
+ const timeZone = await getTimeZone({locale});
return {
title: t('title'),
diff --git a/examples/example-next-13-advanced/src/app/[locale]/opengraph-image.tsx b/examples/example-next-13-advanced/src/app/[locale]/opengraph-image.tsx
index 05e520749..345cbb46f 100644
--- a/examples/example-next-13-advanced/src/app/[locale]/opengraph-image.tsx
+++ b/examples/example-next-13-advanced/src/app/[locale]/opengraph-image.tsx
@@ -1,5 +1,5 @@
import {ImageResponse} from 'next/server';
-import {getTranslator} from 'next-intl/server';
+import {getTranslations} from 'next-intl/server';
type Props = {
params: {
@@ -8,6 +8,6 @@ type Props = {
};
export default async function Image({params: {locale}}: Props) {
- const t = await getTranslator(locale, 'OpenGraph');
+ const t = await getTranslations({locale, namespace: 'OpenGraph'});
return new ImageResponse(
{t('title')}
);
}
diff --git a/examples/example-next-13-advanced/src/app/[locale]/page.tsx b/examples/example-next-13-advanced/src/app/[locale]/page.tsx
index 29c34716e..e8197b92d 100644
--- a/examples/example-next-13-advanced/src/app/[locale]/page.tsx
+++ b/examples/example-next-13-advanced/src/app/[locale]/page.tsx
@@ -12,10 +12,9 @@ import {Link} from '../../navigation';
type Props = {
searchParams: Record;
- params: {locale: string};
};
-export default function Index({params, searchParams}: Props) {
+export default function Index({searchParams}: Props) {
const t = useTranslations('Index');
const format = useFormatter();
const now = useNow();
@@ -55,7 +54,7 @@ export default function Index({params, searchParams}: Props) {
{JSON.stringify(searchParams, null, 2)}
{/* @ts-ignore -- Waiting for TS support */}
-
+
);
}
diff --git a/examples/example-next-13-advanced/src/components/AsyncComponent.tsx b/examples/example-next-13-advanced/src/components/AsyncComponent.tsx
index 4cf51960e..bf85ed724 100644
--- a/examples/example-next-13-advanced/src/components/AsyncComponent.tsx
+++ b/examples/example-next-13-advanced/src/components/AsyncComponent.tsx
@@ -1,11 +1,7 @@
-import {getTranslator} from 'next-intl/server';
+import {getTranslations} from 'next-intl/server';
-type Props = {
- locale: string;
-};
-
-export default async function AsyncComponent({locale}: Props) {
- const t = await getTranslator(locale, 'AsyncComponent');
+export default async function AsyncComponent() {
+ const t = await getTranslations('AsyncComponent');
return (