Skip to content

Commit

Permalink
feat: Inherit messages by default in NextIntlClientProvider (#1682)
Browse files Browse the repository at this point in the history
This makes it easier to get started. If you don't want this behavior,
you can still opt-out via `messages={null}`.

**TODO**
- [ ] Merge #1684 along with
this
  • Loading branch information
amannn authored Jan 27, 2025
1 parent 67507cc commit c40c5c9
Show file tree
Hide file tree
Showing 20 changed files with 94 additions and 112 deletions.
18 changes: 6 additions & 12 deletions docs/src/pages/docs/environments/server-client-components.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -115,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 @@ -278,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) 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 @@ -315,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
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,6 @@ The `locale` that was matched by the middleware is available via the `locale` pa

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

Expand All @@ -201,24 +200,16 @@ export default async function LocaleLayout({
notFound();
}

// Providing all messages to the client
// side is the easiest way to get started
const messages = await getMessages();

return (
<html lang={locale}>
<body>
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
<NextIntlClientProvider>{children}</NextIntlClientProvider>
</body>
</html>
);
}
```

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

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

And that's it!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ The `locale` that was provided in `i18n/request.ts` is available via `getLocale`

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

export default async function RootLayout({
children
Expand All @@ -138,24 +138,16 @@ export default async function RootLayout({
}) {
const locale = await getLocale();

// Providing all messages to the client
// side is the easiest way to get started
const messages = await getMessages();

return (
<html lang={locale}>
<body>
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
<NextIntlClientProvider>{children}</NextIntlClientProvider>
</body>
</html>
);
}
```

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

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

Use translations in your page components or anywhere else!
Expand Down
23 changes: 11 additions & 12 deletions docs/src/pages/docs/usage/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,7 @@ export default async function RootLayout(/* ... */) {
return (
<html lang={locale}>
<body>
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
<NextIntlClientProvider>{children}</NextIntlClientProvider>
</body>
</html>
);
Expand All @@ -76,14 +74,15 @@ export default async function RootLayout(/* ... */) {
These props are inherited if you're rendering `NextIntlClientProvider` from a Server Component:

1. `locale`
2. `now`
3. `timeZone`
4. `formats`
2. `messages`
3. `now`
4. `timeZone`
5. `formats`

In contrast, these props can be provided as necessary:

1. `messages` (see [Internationalization in Client Components](/docs/environments/server-client-components#using-internationalization-in-client-components))
2. `onError` and `getMessageFallback`
1. `onError`
2. `getMessageFallback`

Additionally, nested instances of `NextIntlClientProvider` will inherit configuration from their respective ancestors. Note however that individual props are treated as atomic, therefore e.g. `messages` need to be merged manually—if necessary.

Expand Down Expand Up @@ -115,17 +114,16 @@ Once you have defined your client-side provider component, you can use it in a S

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

export default async function RootLayout({children}) {
const locale = await getLocale();
const messages = await getMessages();

return (
<html lang={locale}>
<body>
<NextIntlClientProvider messages={messages}>
<NextIntlClientProvider>
<IntlErrorHandlingProvider>{children}</IntlErrorHandlingProvider>
</NextIntlClientProvider>
</body>
Expand Down Expand Up @@ -380,13 +378,14 @@ const messages = await getMessages();
```tsx
import {NextIntlClientProvider} from 'next-intl';
import {getMessages} from 'next-intl/server';
import pick from 'lodash/pick';

async function Component({children}) {
// Read messages configured via `i18n/request.ts`
const messages = await getMessages();

return (
<NextIntlClientProvider messages={messages}>
<NextIntlClientProvider messages={pick(messages, ['Navigation'])}>
{children}
</NextIntlClientProvider>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {notFound} from 'next/navigation';
import {NextIntlClientProvider, hasLocale} from 'next-intl';
import {getMessages} from 'next-intl/server';
import {ReactNode} from 'react';
import {routing} from '@/i18n/routing';

Expand All @@ -14,19 +13,13 @@ export default async function LocaleLayout({children, params}: Props) {
notFound();
}

// Providing all messages to the client
// side is the easiest way to get started
const messages = await getMessages();

return (
<html lang={params.locale}>
<head>
<title>next-intl</title>
</head>
<body>
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
<NextIntlClientProvider>{children}</NextIntlClientProvider>
</body>
</html>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {Metadata} from 'next';
import {notFound} from 'next/navigation';
import {Locale, NextIntlClientProvider, hasLocale} from 'next-intl';
import {getMessages, setRequestLocale} from 'next-intl/server';
import {setRequestLocale} from 'next-intl/server';
import {ReactNode} from 'react';
import Document from '@/components/Document';
import {locales} from '@/config';
Expand Down Expand Up @@ -33,13 +33,9 @@ export default async function LocaleLayout({
// Enable static rendering
setRequestLocale(locale);

// Providing all messages to the client
// side is the easiest way to get started
const messages = await getMessages();

return (
<Document locale={locale}>
<NextIntlClientProvider messages={messages}>
<NextIntlClientProvider>
<div className="m-auto max-w-[60rem] p-4">
<PublicNavigation />
<div className="-mx-4 min-h-[200px] bg-slate-100 p-4">{children}</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {Metadata} from 'next';
import {NextIntlClientProvider} from 'next-intl';
import {getLocale, getMessages} from 'next-intl/server';
import {getLocale} from 'next-intl/server';
import {ReactNode} from 'react';
import Document from '@/components/Document';
import AppNavigation from './AppNavigation';
Expand All @@ -18,13 +18,9 @@ export const metadata: Metadata = {
export default async function LocaleLayout({children}: Props) {
const locale = await getLocale();

// Providing all messages to the client
// side is the easiest way to get started
const messages = await getMessages();

return (
<Document locale={locale}>
<NextIntlClientProvider messages={messages}>
<NextIntlClientProvider>
<div className="flex">
<div className="flex min-h-[100vh] w-[270px] shrink-0 flex-col justify-between bg-slate-100 p-8">
<AppNavigation />
Expand Down
10 changes: 2 additions & 8 deletions examples/example-app-router-single-locale/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {NextIntlClientProvider} from 'next-intl';
import {getLocale, getMessages} from 'next-intl/server';
import {getLocale} from 'next-intl/server';
import {ReactNode} from 'react';

type Props = {
Expand All @@ -9,19 +9,13 @@ type Props = {
export default async function LocaleLayout({children}: Props) {
const locale = await getLocale();

// Providing all messages to the client
// side is the easiest way to get started
const messages = await getMessages();

return (
<html lang={locale}>
<head>
<title>next-intl</title>
</head>
<body>
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
<NextIntlClientProvider>{children}</NextIntlClientProvider>
</body>
</html>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import clsx from 'clsx';
import {Inter} from 'next/font/google';
import {NextIntlClientProvider} from 'next-intl';
import {getLocale, getMessages} from 'next-intl/server';
import {getLocale} from 'next-intl/server';
import {ReactNode} from 'react';
import './globals.css';

Expand All @@ -14,10 +14,6 @@ type Props = {
export default async function LocaleLayout({children}: Props) {
const locale = await getLocale();

// Providing all messages to the client
// side is the easiest way to get started
const messages = await getMessages();

return (
<html lang={locale}>
<head>
Expand All @@ -29,9 +25,7 @@ export default async function LocaleLayout({children}: Props) {
inter.className
)}
>
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
<NextIntlClientProvider>{children}</NextIntlClientProvider>
</body>
</html>
);
Expand Down
7 changes: 1 addition & 6 deletions examples/example-app-router/src/components/BaseLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {clsx} from 'clsx';
import {Inter} from 'next/font/google';
import {NextIntlClientProvider} from 'next-intl';
import {getMessages} from 'next-intl/server';
import {ReactNode} from 'react';
import Navigation from '@/components/Navigation';

Expand All @@ -13,14 +12,10 @@ type Props = {
};

export default async function BaseLayout({children, locale}: Props) {
// Providing all messages to the client
// side is the easiest way to get started
const messages = await getMessages();

return (
<html className="h-full" lang={locale}>
<body className={clsx(inter.className, 'flex h-full flex-col')}>
<NextIntlClientProvider messages={messages}>
<NextIntlClientProvider>
<Navigation />
{children}
</NextIntlClientProvider>
Expand Down
2 changes: 1 addition & 1 deletion packages/next-intl/.size-limit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const config: SizeLimitConfig = [
name: "import {NextIntlClientProvider} from 'next-intl' (react-client)",
import: '{NextIntlClientProvider}',
path: 'dist/esm/production/index.react-client.js',
limit: '1 KB'
limit: '1.005 KB'
},
{
name: "import * from 'next-intl' (react-server)",
Expand Down
Loading

0 comments on commit c40c5c9

Please sign in to comment.