Skip to content

feat!: next-intl@4 #1412

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

Merged
merged 116 commits into from
Mar 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
116 commits
Select commit Hold shift + click to select a range
a8fb0e3
fix: Don't commit canary releases
amannn Jun 20, 2024
cf3f8b0
Improve docs [skip ci]
amannn Jun 20, 2024
4fd9b20
fix: Try out updated credits
amannn Jun 20, 2024
47239f9
v3.15.3-canary.0
amannn Jun 20, 2024
0d60a72
Revert "v3.15.3-canary.0"
amannn Jun 20, 2024
c459669
Merge remote-tracking branch 'origin/main' into canary
amannn Jun 20, 2024
8d3ab38
fix: Prefer more specific routes in `usePathname` when detecting the …
amannn Jun 26, 2024
605c9a6
Merge remote-tracking branch 'origin/main' into canary
amannn Jun 26, 2024
cb15046
Merge branch 'fix/sort-pathnames-usepathname' into canary
amannn Jun 26, 2024
11262b8
Merge remote-tracking branch 'origin/main' into canary
amannn Jul 11, 2024
7976376
feat: Support `trailingSlash: true` in Next.js config (#1188)
amannn Jul 11, 2024
b352c10
fix: Release please?
amannn Jul 11, 2024
cba448a
Merge remote-tracking branch 'origin/main' into canary
amannn Jul 11, 2024
0c670b2
Merge remote-tracking branch 'origin/main' into canary
amannn Jul 12, 2024
5dc7f53
Merge remote-tracking branch 'origin/main' into canary
amannn Jul 12, 2024
11dcbcc
Merge remote-tracking branch 'origin/main' into canary
amannn Jul 12, 2024
1b074da
Merge remote-tracking branch 'origin/main' into canary
amannn Jul 12, 2024
e98682b
Merge remote-tracking branch 'origin/main' into canary
amannn Jul 15, 2024
7c6f533
Merge branch 'main' into canary
amannn Aug 23, 2024
8b4c7c4
feat!: Automatically inherit `formats` when `NextIntlClientProvider` …
amannn Aug 26, 2024
d80d691
feat!: Inherit context between providers (#1413)
amannn Oct 9, 2024
8158de4
Merge remote-tracking branch 'origin/canary' into HEAD
amannn Oct 9, 2024
7dd54c1
Merge remote-tracking branch 'origin/canary' into v4
amannn Oct 24, 2024
ccaef97
fix lint
amannn Oct 24, 2024
25a0dc8
bump size
amannn Oct 24, 2024
9f4754c
feat!: Make package ESM-only, target modern browsers, use modern JSX …
amannn Oct 29, 2024
5b5f0e3
feat!: Remove deprecated APIs (#1479)
amannn Oct 29, 2024
eb5bf09
feat!: Bump minimum required `typescript` version to 5 for projects u…
amannn Oct 29, 2024
a6238f4
feat!: Remove deprecated APIs pt. 2 (#1482)
amannn Oct 29, 2024
829998e
feat!: Infer default `locale` for `NextIntlClientProvider` from `useP…
amannn Oct 29, 2024
a9c35db
add comment
amannn Oct 29, 2024
5402851
feat!: Require `locale` to be returned from `getRequestConfig` (#1486)
amannn Oct 29, 2024
b4e4d1d
feat!: Decrease cookie expiration to 5 hours, only set cookie when ne…
amannn Oct 30, 2024
d83abc2
feat: Return type-safe `IntlMessages` from `useMessages` & `getMessag…
amannn Oct 30, 2024
a7aaf56
feat!: Revamp augmented types and add support for typed `Locale` (#1495)
amannn Nov 1, 2024
2388c9b
remove unused file
amannn Nov 1, 2024
01268f6
feat: Type-safe ICU arguments (#1499)
amannn Nov 7, 2024
0bb75c5
decrease size by making plain message error handling dev-only
amannn Nov 7, 2024
dd28ac1
fix: Allow to merge inline formats with global formats in `t` (#1523)
amannn Nov 7, 2024
4675f7e
Merge remote-tracking branch 'origin/main' into v4
amannn Nov 8, 2024
c154409
move/rename file
amannn Nov 8, 2024
887034d
simplify signature
amannn Nov 8, 2024
ab22a92
temporarily disable exports that cause OOM
amannn Nov 8, 2024
b9c515e
simplify types for `createTranslator`
amannn Nov 8, 2024
8ac7206
fix: Fix recursion bug in typed ICU args (#1527)
amannn Nov 8, 2024
21f2dac
remove outdated constant
amannn Nov 12, 2024
2a92f17
feat!: Remove default of `now={new Date()}` from `NextIntlClientProvi…
amannn Nov 14, 2024
dc36097
feat!: Don't read a default for `useLocale` from `useParams.locale` o…
amannn Nov 14, 2024
3ff1923
decrease size, relax next.js peer dependency
amannn Nov 14, 2024
f44ae15
feat: Adopt shared ICU type parser (#1549)
amannn Nov 14, 2024
7d89d54
chore: Strict type imports with ESLint (#1524)
amannn Nov 14, 2024
c2a8984
Merge remote-tracking branch 'origin/main' into v4
amannn Nov 15, 2024
00a79d4
feat: publish v4 prerelease
amannn Nov 15, 2024
ba18078
rename canary workflow
amannn Nov 15, 2024
6f1a39d
unique workflow names [skip ci]
amannn Nov 15, 2024
486a7bd
docs: minor fixes
amannn Nov 15, 2024
dfa5ea0
docs: blog post draft
amannn Nov 18, 2024
9ea117c
fix: Disallow string dates, improve autocomplete (#1557)
amannn Nov 18, 2024
c8eb4ad
fix: Use `fs.watch` instead of `chokidar`
amannn Nov 19, 2024
5a7f7be
fix: Move locale validation to `defineRouting` (#1560)
amannn Nov 19, 2024
bfcd251
docs: Proofread blog post
amannn Nov 19, 2024
7b755e9
fix: update warning for now fallback in `createFormatter`
amannn Nov 19, 2024
18156c4
fix: use v4-beta tag instead
amannn Nov 19, 2024
0b2c951
docs: reference new tag
amannn Nov 19, 2024
6275030
feat!: Disallow passing `null`, `undefined` or `boolean` as an ICU ar…
amannn Nov 20, 2024
c323050
fix: release for "feat!: " as well
amannn Nov 20, 2024
8cce53e
docs: performance notes of messages augmentation
amannn Nov 20, 2024
6d94a9e
docs: fix toc, note in gdpr release notes [skip ci]
amannn Nov 28, 2024
db95243
docs: Update legacy example to use `next-intl` instead of `use-intl` …
amannn Nov 28, 2024
21b882a
docs: improve relative time formatting docs (also fix release conditi…
amannn Nov 28, 2024
cb6b998
Merge remote-tracking branch 'origin/main' into v4
amannn Nov 28, 2024
8f37883
fix: Change error messages to not mention provider (since there might…
amannn Dec 2, 2024
8b9a2fc
Merge remote-tracking branch 'origin/main' into v4
amannn Dec 18, 2024
7e7011d
fix: Fix hard reload when using `<Link />` in Next.js 15 (#1620)
amannn Dec 18, 2024
9f3a4b0
docs: Improve blog post wording
amannn Dec 19, 2024
dac018a
docs: blog post wording
amannn Dec 19, 2024
c224d1c
docs: Remove blog post
amannn Dec 20, 2024
0825f08
feat: Re-introduce `locale` argument for `getRequestConfig` to be use…
amannn Dec 20, 2024
56f59a7
docs: v4 banner
amannn Dec 20, 2024
e65f4f9
Merge remote-tracking branch 'origin/main' into v4
amannn Dec 20, 2024
ddd5ae5
fix: Make locale cookie a session cookie (#1634)
amannn Dec 23, 2024
a1a70cb
docs: Improve wording about GDPR compliance
amannn Dec 23, 2024
32dfcc2
docs: Note on `await connection()` in relative time docs
amannn Dec 23, 2024
f10dbba
feat: Allow overriding typed `Messages` when using `createTranslator`…
amannn Jan 10, 2025
32fc4e3
fix: Retain index files in dist output to match declarations (#1665)
amannn Jan 17, 2025
bb49880
Revert "fix: Retain index files in dist output to match declarations …
amannn Jan 17, 2025
f511797
fix: Remove scripts from published packages
amannn Jan 17, 2025
5ec7f45
fix: Remove scripts from ignored pkg fields
amannn Jan 17, 2025
5b218d4
fix: Change imports to use `.js` to make them compatible with `eslint…
DuckThom Jan 24, 2025
67507cc
fix: Accept a deep partial of `Messages` for `messages` prop of provi…
amannn Jan 24, 2025
c40c5c9
feat: Inherit messages by default in `NextIntlClientProvider` (#1682)
amannn Jan 27, 2025
40d535a
fix: Start watcher during a hot reload of `next.config.ts` (#1690)
amannn Jan 28, 2025
77949ef
feat: Allow to override options of global formats (#1693)
amannn Feb 3, 2025
bae1131
feat: Use overloads for `relativeTime`
amannn Feb 3, 2025
9e73cbe
feat: Validate locale returned from `i18n/request.ts` (#1695)
amannn Feb 4, 2025
c4c5986
fix: Handle watch events on macOS caused by git (#1696)
amannn Feb 4, 2025
ca3fcd5
feat: change createMessagesDeclaration to also accept array for monor…
felix-quotez Feb 5, 2025
8868626
Merge remote-tracking branch 'origin/main' into v4
amannn Feb 17, 2025
d17baf9
Fix lint in test
amannn Feb 17, 2025
15c826b
fix!: Return `x-default` alternate link also for sub pages when using…
amannn Feb 17, 2025
ca68f02
fix: Merge remote-tracking branch 'origin/main' into v4
amannn Feb 18, 2025
22cf1cd
fix: Formatting
amannn Feb 18, 2025
1a5e0ec
fix: Don't attach alternate links for redirects and consider port of …
amannn Feb 20, 2025
4106641
fix: Merge remote-tracking branch 'origin/main' into v4
amannn Feb 21, 2025
021e874
feat!: Stricter config for `domains` to improve handling of `localePr…
amannn Feb 21, 2025
dea867b
feat: Support partial `pathnames` (#1743)
amannn Feb 24, 2025
f4bb734
Merge remote-tracking branch 'origin/main' into v4
amannn Feb 28, 2025
6c12292
docs: Fix mdx in playground
amannn Feb 28, 2025
22325ce
Merge remote-tracking branch 'origin/main' into v4
amannn Feb 28, 2025
9b168c4
Merge branch 'main' into v4
amannn Feb 28, 2025
7decc4c
docs: Delete redundant config in app router example
amannn Feb 28, 2025
1a9dd14
Merge remote-tracking branch 'origin/main' into v4
amannn Mar 11, 2025
bbb5b08
docs: Update blog post
amannn Mar 11, 2025
ca89b76
Merge remote-tracking branch 'origin/main' into v4
amannn Mar 11, 2025
6e3952f
docs: Version selector
amannn Mar 12, 2025
7bfd643
chore: Remove v4 prerelease workflow
amannn Mar 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
jobs:
build:
name: Build, lint, and test
runs-on: ubuntu-latest
runs-on: macos-15
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: prerelease
name: prerelease (canary)

on:
push:
Expand Down Expand Up @@ -26,7 +26,7 @@ jobs:
- run: |
sed -i 's/"use-intl": "workspace:\^"/"use-intl": "workspace:"/' ./packages/next-intl/package.json && git commit -am "use fixed version"
- run: pnpm lerna publish 0.0.0-canary-${GITHUB_SHA::7} --no-git-reset --dist-tag canary --no-push --yes
if: "${{startsWith(github.event.head_commit.message, 'fix: ') || startsWith(github.event.head_commit.message, 'feat: ')}}"
if: "${{startsWith(github.event.head_commit.message, 'fix: ') || startsWith(github.event.head_commit.message, 'feat: ') || startsWith(github.event.head_commit.message, 'feat!: ')}}"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
git config --global user.name "${{ github.actor }}"
git config --global user.email "${{ github.actor }}@users.noreply.github.com"
- run: pnpm run publish
if: "${{startsWith(github.event.head_commit.message, 'fix: ') || startsWith(github.event.head_commit.message, 'feat: ')}}"
if: "${{startsWith(github.event.head_commit.message, 'fix: ') || startsWith(github.event.head_commit.message, 'feat: ') || startsWith(github.event.head_commit.message, 'feat!: ')}}"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ Note that the exclamation mark syntax (`!`) for indicating breaking changes is c

Other prefixes that are allowed and will _not_ create a release are the following:

1. `docs`: Documentation-only changes
1. `docs`: Documentation-only changes and updated examples
2. `test`: Missing tests were added or existing ones corrected
3. `ci`: Changes to CI configuration files and scripts
4. `build`: Changes that affect the build system or external dependencies
Expand Down
3 changes: 3 additions & 0 deletions docs/src/components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {useRouter} from 'next/router';
import config from '@/config';
import FooterLink from './FooterLink';
import FooterSeparator from './FooterSeparator';
import FooterVersionSelector from './FooterVersionSelector';

export default function Footer() {
const router = useRouter();
Expand All @@ -19,6 +20,8 @@ export default function Footer() {
<FooterLink href="/examples">Examples</FooterLink>
<FooterSeparator />
<FooterLink href="/blog">Blog</FooterLink>
<FooterSeparator />
<FooterVersionSelector />
</div>
<div>
<FooterLink href={config.blueskyUrl} target="_blank">
Expand Down
19 changes: 19 additions & 0 deletions docs/src/components/FooterVersionSelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {ChangeEvent} from 'react';

export default function FooterVersionSelector() {
function onChange(event: ChangeEvent<HTMLSelectElement>) {
const version = event.target.value;
window.location.href = `https://${version}.next-intl.dev`;
}

return (
<select
className="inline-flex appearance-none items-center bg-transparent py-3 text-xs text-slate-500 transition-colors hover:text-slate-900 dark:text-slate-400 dark:hover:text-white"
defaultValue="v4"
onChange={onChange}
>
<option value="v3">v3</option>
<option value="v4">v4</option>
</select>
);
}
2 changes: 1 addition & 1 deletion docs/src/pages/blog/_meta.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export default {
title: 'Overview'
},
'next-intl-4-0': {
title: 'next-intl 4.0 beta',
title: 'next-intl 4.0',
display: 'hidden'
},
'next-intl-3-22': {
Expand Down
4 changes: 2 additions & 2 deletions docs/src/pages/blog/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import StayUpdated from '@/components/StayUpdated.mdx';
<div className="flex flex-col gap-4 py-8">
<BlogPostLink
href="/blog/next-intl-4-0"
title="next-intl 4.0 beta"
date="Dec 23, 2024"
title="next-intl 4.0"
date="Mar 12, 2025"
author="By Jan Amann"
/>
<BlogPostLink
Expand Down
28 changes: 14 additions & 14 deletions docs/src/pages/blog/next-intl-4-0.mdx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
---
title: next-intl 4.0 beta
title: next-intl 4.0
---

import PartnerContentLink from '@/components/PartnerContentLink';
import StayUpdated from '@/components/StayUpdated.mdx';

# next-intl 4.0 beta
# next-intl 4.0

<small>Dec 23, 2024 · by Jan Amann</small>
<small>Mar 12, 2025 · by Jan Amann</small>

After a year of feature development, this release focuses on streamlining the API surface while maintaining the core architecture of `next-intl`. With many improvements already released in [previous minor versions](/blog/next-intl-3-22), this update introduces several enhancements that will improve your development experience and make working with internationalization even more seamless.

Expand Down Expand Up @@ -44,7 +44,7 @@ declare module 'next-intl' {
}
```

See the updated [TypeScript augmentation](https://v4.next-intl.dev/docs/workflows/typescript) guide.
See the updated [TypeScript augmentation](/docs/workflows/typescript) guide.

## Strictly-typed locale

Expand All @@ -65,7 +65,7 @@ declare module 'next-intl' {

By doing so, APIs like `useLocale()` or `<Link />` that either return or receive a `locale` will now pick up your app-specific `Locale` type, improving type safety across your app.

To simplify narrowing of `string`-based locales, a `hasLocale` function has been added. This can for example be used in [`i18n/request.ts`](https://v4.next-intl.dev/docs/getting-started/app-router/with-i18n-routing#i18n-request) to return a valid locale:
To simplify narrowing of `string`-based locales, a `hasLocale` function has been added. This can for example be used in [`i18n/request.ts`](/docs/getting-started/app-router/with-i18n-routing#i18n-request) to return a valid locale:

```tsx
import {getRequestConfig} from 'next-intl/server';
Expand Down Expand Up @@ -148,13 +148,13 @@ t('followers', {count: 30000});
"{count, number} followers"
```

Due to a current limitation in TypeScript, this feature is opt-in for now. Please refer to the [strict arguments](https://v4.next-intl.dev/docs/workflows/typescript#messages-arguments) docs to learn how to enable it.
Due to a current limitation in TypeScript, this feature is opt-in for now. Please refer to the [strict arguments](/docs/workflows/typescript#messages-arguments) docs to learn how to enable it.

## GDPR compliance [#gdpr-compliance]

In order to comply with the current GDPR regulations, the following changes have been made and are relevant to you if you're using the `next-intl` middleware for i18n routing:

1. The locale cookie now defaults to a session cookie that expires when a browser is closed.
1. The locale cookie now defaults to a session cookie that expires when the browser is closed.
2. The locale cookie is now only set when a user switches to a locale that doesn't match the `accept-language` header.

If you want to increase the cookie expiration, e.g. because you're informing users about the usage of cookies or if GDPR doesn't apply to your app, you can use the `maxAge` attribute to do so:
Expand All @@ -174,11 +174,11 @@ export const routing = defineRouting({
});
```

Since the cookie is now only available after a locale switch, make sure to not rely on it always being present. E.g. if you need access to the user's locale in a [Route Handler](https://v4.next-intl.dev/docs/environments/actions-metadata-route-handlers#route-handlers), a reliable option is to provide the locale as a search param (e.g. `/api/posts/12?locale=en`).
Since the cookie is now only available after a locale switch, make sure to not rely on it always being present. E.g. if you need access to the user's locale in a [Route Handler](/docs/environments/actions-metadata-route-handlers#route-handlers), a reliable option is to provide the locale as a search param (e.g. `/api/posts/12?locale=en`).

As part of this change, disabling a cookie now requires you to set [`localeCookie: false`](https://v4.next-intl.dev/docs/routing#locale-cookie) in your routing configuration. Previously, `localeDetection: false` ambiguously also disabled the cookie from being set, but since a separate `localeCookie` option was introduced recently, this should now be used instead.
As part of this change, disabling a cookie now requires you to set [`localeCookie: false`](/docs/routing#locale-cookie) in your routing configuration. Previously, `localeDetection: false` ambiguously also disabled the cookie from being set, but since a separate `localeCookie` option was introduced recently, this should now be used instead.

Learn more in the [locale cookie](https://v4.next-intl.dev/docs/routing#locale-cookie) docs.
Learn more in the [locale cookie](/docs/routing#locale-cookie) docs.

## Modernized build output

Expand Down Expand Up @@ -266,7 +266,7 @@ This will create the following structure:
- `example.no`: `no-NO`
- `example.no/en`: `en-NO`

Learn more in the updated docs for [`domains`](https://v4.next-intl.dev/docs/routing#domains).
Learn more in the updated docs for [`domains`](/docs/routing#domains).

## Preparation for upcoming Next.js features [#nextjs-future]

Expand Down Expand Up @@ -300,11 +300,9 @@ For a smooth upgrade, please initially upgrade to the latest v3.x version and ch
Afterwards, you can upgrade by running:

```
npm install next-intl@v4-beta
npm install next-intl@4
```

The beta docs are available here: [v4.next-intl.dev](https://v4.next-intl.dev)

I'd love to hear about your experiences with `[email protected]`! Join the conversation in the [discussions](https://github.com/amannn/next-intl/discussions/1631).

## Thank you!
Expand All @@ -315,6 +313,8 @@ A special thank you goes to <PartnerContentLink href="https://crowdin.com/">Crow

—Jan

(this post has been updated from an initial announcement for the 3.0 release candidate)

PS: Have you heard that [learn.next-intl.dev](https://learn.next-intl.dev) is coming?

<StayUpdated />
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,9 @@ Note that by default, `next-intl` returns [the `link` response header](/docs/rou

Next.js supports providing alternate URLs per language via the [`alternates` entry](https://nextjs.org/docs/app/api-reference/file-conventions/metadata/sitemap#generate-a-localized-sitemap). You can construct a list of entries for each pathname and locale as follows:

```tsx filename="app/sitemap.ts" {4-5,8-9}
```tsx filename="app/sitemap.ts" {5-6,9-10}
import {MetadataRoute} from 'next';
import {Locale} from 'next-intl';
import {routing, getPathname} from '@/i18n/routing';

// Adapt this as necessary
Expand All @@ -198,7 +199,7 @@ function getEntries(href: Href) {
}));
}

function getUrl(href: Href, locale: (typeof routing.locales)[number]) {
function getUrl(href: Href, locale: Locale) {
const pathname = getPathname({locale, href});
return host + pathname;
}
Expand Down Expand Up @@ -230,12 +231,17 @@ You can use `next-intl` in [Route Handlers](https://nextjs.org/docs/app/building

```tsx filename="app/api/hello/route.tsx"
import {NextResponse} from 'next/server';
import {hasLocale} from 'next-intl';
import {getTranslations} from 'next-intl/server';
import {routing} from '@/i18n/routing';

export async function GET(request) {
// Example: Receive the `locale` via a search param
const {searchParams} = new URL(request.url);
const locale = searchParams.get('locale');
if (!hasLocale(routing.locales, locale)) {
return NextResponse.json({error: 'Invalid locale'}, {status: 400});
}

const t = await getTranslations({locale, namespace: 'Hello'});
return NextResponse.json({title: t('title')});
Expand Down
10 changes: 5 additions & 5 deletions docs/src/pages/docs/environments/error-files.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -77,16 +77,16 @@ export default function RootLayout({children}) {
}
```

For the 404 page to render, we need to call the `notFound` function in the root layout 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 the root layout when we detect an incoming `locale` param that isn't valid.

```tsx filename="app/[locale]/layout.tsx"
import {hasLocale} from 'next-intl';
import {notFound} from 'next/navigation';
import {routing} from '@/i18n/routing';

export default async function LocaleLayout({children, params}) {
export default function LocaleLayout({children, params}) {
const {locale} = await params;

// Ensure that the incoming `locale` is valid
if (!routing.locales.includes(locale as any)) {
if (!hasLocale(routing.locales, locale)) {
notFound();
}

Expand Down
24 changes: 10 additions & 14 deletions docs/src/pages/docs/environments/server-client-components.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,17 @@ These functions are available:

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.

In Next.js, Server Components are the default, and therefore shared components will typically execute as Server Components.
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');

// This component will execute as a Server Component by default.
// However, if it is imported from a Client Component, it will
// execute as a Client Component.
return (
<section>
<h2>{t('title')}</h2>
Expand Down Expand Up @@ -112,11 +115,13 @@ Regarding performance, async functions and hooks can be used interchangeably. Th

## Using internationalization in Client Components

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.
Depending on your situation, you may need to handle internationalization in Client Components. Providing all messages to the client side is the easiest way to get started, therefore `next-intl` automatically does this when you render [`NextIntlClientProvider`](/docs/usage/configuration#nextintlclientprovider). This is a reasonable approach for many apps.

However, 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.

There are several options for using translations from `next-intl` in Client Components, listed here in order of enabling the best performance:

### Option 1: Passing translations to Client Components
### Option 1: Passing translated labels to Client Components

The preferred approach is to pass the processed labels as props or `children` from a Server Component.

Expand Down Expand Up @@ -275,8 +280,6 @@ In particular, page and search params are often a great option because they offe

### Option 3: Providing individual messages

To reduce bundle size, `next-intl` doesn't automatically provide [messages](/docs/usage/configuration#messages) or [formats](/docs/usage/configuration#formats) to Client Components.

If you need to incorporate dynamic state into components that can not be moved to the server side, you can wrap these components with `NextIntlClientProvider` and provide the relevant messages.

```tsx filename="Counter.tsx"
Expand Down Expand Up @@ -312,22 +315,16 @@ An automatic, compiler-driven approach is being evaluated in [`next-intl#1`](htt

### Option 4: Providing all messages

If you're building a highly dynamic app where most components use React's interactive features, you may prefer to make all messages available to Client Components.
If you're building a highly dynamic app where most components use React's interactive features, you may prefer to make all messages available to Client Components—this is the default behavior of `next-intl`.

```tsx filename="layout.tsx" /NextIntlClientProvider/
import {NextIntlClientProvider} from 'next-intl';
import {getMessages} from 'next-intl/server';

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

return (
<html lang={locale}>
<body>
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
<NextIntlClientProvider>{children}</NextIntlClientProvider>
</body>
</html>
);
Expand Down Expand Up @@ -366,7 +363,6 @@ The component accepts the following props that are not serializable:

1. [`onError`](/docs/usage/configuration#error-handling)
2. [`getMessageFallback`](/docs/usage/configuration#error-handling)
3. Rich text elements for [`defaultTranslationValues`](/docs/usage/configuration#default-translation-values)

To configure these, you can wrap `NextIntlClientProvider` with another component that is marked with `'use client'` and defines the relevant props.

Expand Down
Loading