-
-
Notifications
You must be signed in to change notification settings - Fork 253
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
docs: Getting started with all messages on the client side #1046
Changes from 4 commits
eb90d31
42f5d76
80332fc
51ce76c
30ab630
d34cacb
9af4a63
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,7 +23,7 @@ Moving internationalization to the server side unlocks new levels of performance | |
|
||
**Benefits of server-side internationalization:** | ||
|
||
1. Your messages never leave the server and don't need to be serialized for the client side | ||
1. Your messages never leave the server and don't need to be passed to 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 | ||
|
@@ -123,7 +123,9 @@ In regard to performance, async functions and hooks can be used very much interc | |
|
||
## Using internationalization in Client Components | ||
|
||
Depending on your situation, you may need to handle internationalization in Client Components as well. There are several options for using translations or other functionality from `next-intl` in Client Components, listed here in order of recommendation. | ||
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 emerging apps, you can be more selective about which messages are passed to the client side to reduce the bundle size of your app. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "emerging" was an attempt at a better phrasing for "smaller apps" :). Might be more accurate anyway, also big apps start out as small ones. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know that the aim of this docs rewrite is to not scare or overwhelm people with performance implications, but is there a way to somewhere define what is "emerging" and what is considered a bigger app in terms of number of messages? Perhaps in an expandable section here or in this expandable section in Getting Started. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've tried to rephrase this again in 30ab630 and included some more details. Coming back to this, I think even "emerging" is the wrong term here. For some apps, splitting messages will never be a necessity. I think a particular number of messages doesn't really answer this question here, in the latest commit I tried to focus on pointing you towards metrics and tools that can help you to ensure your app meets your performance goals. Do you find this helpful? I've now also removed the expandable section from the getting started docs. I think the comment "Providing all messages to the client side is the easiest way to get started" should be sufficient in terms of setting up an app. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just had a look at the latest commit. I think that this is much better. In the previous version with "emerging", I might have read that and been worried that I would need to optimise things from the get-go if my app was on a grander scale. I really like the newer explanation, particularly where you remind users to "always measure before you optimize". |
||
|
||
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 | ||
|
||
|
@@ -221,15 +223,15 @@ To keep internationalization on the server side, it can be helpful to structure | |
```tsx filename="app/register/page.tsx" | ||
import {useTranslations} from 'next-intl'; | ||
|
||
// A Client Component, so that it can use `useFormState` to | ||
// potentially display errors received after submission. | ||
// A Client Component, so that `useFormState` can be used | ||
// to potentially display errors received after submission. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reads slightly better IMO. |
||
import RegisterForm from './RegisterForm'; | ||
|
||
// A Client Component, so that it can use `useFormStatus` | ||
// A Client Component, so that `useFormStatus` can be used | ||
// to disable the input field during submission. | ||
import FormField from './FormField'; | ||
|
||
// A Client Component, so that it can use `useFormStatus` | ||
// A Client Component, so that `useFormStatus` can be used | ||
// to disable the submit button during submission. | ||
import FormSubmitButton from './FormSubmitButton'; | ||
|
||
|
@@ -310,14 +312,12 @@ export default function Counter() { | |
} | ||
``` | ||
|
||
In case you prefer to make all messages available to the client side, you can [configure `NextIntlClientProvider` in the root layout](#option-4-providing-all-messages) instead. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is covered in the very next headline. |
||
|
||
<Details id="messages-client-namespaces"> | ||
<summary>How can I know the messages I need to provide to the client side?</summary> | ||
|
||
Currently, the messages you select for being passed to the client side need to be picked based on knowledge about the implementation of the wrapped components. | ||
|
||
An automatic, compiler-driven approach is being evaluated in [`next-intl#2`](https://github.com/amannn/next-intl/issues/1). | ||
An automatic, compiler-driven approach is being evaluated in [`next-intl#1`](https://github.com/amannn/next-intl/issues/1). | ||
|
||
</Details> | ||
|
||
|
@@ -347,11 +347,6 @@ export default function LocaleLayout({children, params: {locale}}) { | |
} | ||
``` | ||
|
||
<Callout type="warning"> | ||
Note that this is a tradeoff in regard to performance (see the bullet points | ||
at the top of this page). | ||
</Callout> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Too dramatic IMO, we already told the user everything relevant at the top of the page :). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Definitely agree, the wording scared me off a bit when I read this for the first time 😁 |
||
|
||
## Troubleshooting | ||
|
||
### "Failed to call `useTranslations` because the context from `NextIntlClientProvider` was not found." [#missing-context] | ||
|
@@ -404,4 +399,4 @@ export default function MyCustomNextIntlClientProvider({ | |
|
||
By doing this, your custom provider will already be part of the client-side bundle and can therefore define and pass functions as props. | ||
|
||
**Important:** Be sure to pass explicit `locale`, `timeZone` and `now` props to `NextIntlClientProvider` in this case, since the props aren't automatically inherited from a Server Component when you import `NextIntlClientProvider` from a Client Component. | ||
**Important:** Be sure to pass explicit `locale`, `formats`, `timeZone` and `now` props to `NextIntlClientProvider` in this case, since the props aren't automatically inherited from a Server Component when you import `NextIntlClientProvider` from a Client Component. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -56,8 +56,6 @@ Now, set up the plugin which creates an alias to provide your i18n configuration | |
<Tabs items={['next.config.mjs', 'next.config.js']}> | ||
<Tab> | ||
|
||
If you're using ECMAScript modules for your Next.js config, you can use the plugin as follows: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not really necessary IMO. |
||
|
||
```js filename="next.config.mjs" | ||
import createNextIntlPlugin from 'next-intl/plugin'; | ||
|
||
|
@@ -72,8 +70,6 @@ export default withNextIntl(nextConfig); | |
</Tab> | ||
<Tab> | ||
|
||
If you're using CommonJS for your Next.js config, you can use the plugin as follows: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above. |
||
|
||
```js filename="next.config.js" | ||
const createNextIntlPlugin = require('next-intl/plugin'); | ||
|
||
|
@@ -90,7 +86,7 @@ module.exports = withNextIntl(nextConfig); | |
|
||
### `i18n.ts` [#i18nts] | ||
|
||
`next-intl` creates a configuration once per request. Here you can provide messages and other options depending on the locale of the user. | ||
`next-intl` creates a request-scoped configuration object that can be used to provide messages and other options depending on the locale of the user for usage in Server Components. | ||
|
||
```tsx filename="src/i18n.ts" | ||
import {notFound} from 'next/navigation'; | ||
|
@@ -148,24 +144,45 @@ export const config = { | |
|
||
### `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. | ||
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`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Always somewhat of a balance to include necessary information in the getting started guide, but be rather to the point here. We can save many details for later IMO. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In which part of the docs is there more information on this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The global configuration docs have all the details about this. I'd prefer to be quite selective about which links to include in the getting started docs and therefore not link to this page at this point. Links tend to create branches of user flows where a developer is just trying to get something up and running. In the latest iteration I've now removed all links that would create "branches" and instead only used them if prerequisite knowledge is required (e.g. what a dynamic segment like Once the user has finished the setup, we can help the developer continue with the links in the "Next steps" section. Does that sound reasonable to you? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I completely agree. I think that the getting started steps should be as simple as possible to allow users to get things working and then provide links later to allow for more exploration. Otherwise, as you said, users can get a bit distracted reading into things and perhaps confused. |
||
|
||
```tsx filename="app/[locale]/layout.tsx" | ||
export default function LocaleLayout({ | ||
import {getMessages} from 'next-intl/server'; | ||
|
||
export default async function LocaleLayout({ | ||
children, | ||
params: {locale} | ||
}: { | ||
children: React.ReactNode; | ||
params: {locale: string}; | ||
}) { | ||
// Providing all messages to the client | ||
// side is the easiest way to get started | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Inline comments are often copied along to user apps, so this could be a reminder when revisiting this code at a later stage of the project. |
||
const messages = await getMessages(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This component could also use While this is a subtle difference from the users' perspective, it enables automatically inheriting configuration from |
||
|
||
return ( | ||
<html lang={locale}> | ||
<body>{children}</body> | ||
<body> | ||
<NextIntlClientProvider messages={messages}> | ||
{children} | ||
</NextIntlClientProvider> | ||
</body> | ||
</html> | ||
); | ||
} | ||
``` | ||
|
||
Note that `NextIntlClientProvider` automatically inherits most configuration from `i18n.ts` here. | ||
|
||
<Details id="nextintlclientprovider-messages"> | ||
<summary>Do I need to provide all messages to the client side?</summary> | ||
|
||
Not at all! While this is the easiest way to get started and a reasonable approach for emerging apps, you can optionally be more selective about which messages are provided to the client side—or provide none at all. | ||
|
||
If you consider yourself a performance aficionado, you might want to explore the [Server & Client Components guide](/docs/environments/server-client-components) after finishing the setup. | ||
|
||
</Details> | ||
|
||
### `app/[locale]/page.tsx` [#page] | ||
|
||
Use translations in your page components or anywhere else! | ||
|
@@ -191,10 +208,6 @@ In case you ran into an issue, have a look at [the App Router example](https://n | |
|
||
<ul className="ml-4 list-disc"> | ||
<li>[Usage guide](/docs/usage): Format messages, dates and times</li> | ||
<li> | ||
[Environments](/docs/environments): Explore usage in Server & Client | ||
Components and the Metadata API | ||
</li> | ||
<li> | ||
[Routing](/docs/routing): Integrate i18n routing with `<Link />` & friends | ||
</li> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -39,10 +39,11 @@ The configuration object is created once for each request by internally using Re | |
`NextIntlClientProvider` can be used to provide configuration for **Client Components**. | ||
|
||
```tsx filename="app/[locale]/layout.tsx" /NextIntlClientProvider/ | ||
import {NextIntlClientProvider, useMessages} from 'next-intl'; | ||
import {NextIntlClientProvider} from 'next-intl'; | ||
import {getMessages} from 'next-intl/server'; | ||
|
||
export default function LocaleLayout({children, params: {locale}}) { | ||
const messages = useMessages(); | ||
export default async function LocaleLayout({children, params: {locale}}) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar reasoning as above. |
||
const messages = await getMessages(); | ||
|
||
return ( | ||
<html lang={locale}> | ||
|
@@ -56,13 +57,7 @@ export default function LocaleLayout({children, params: {locale}}) { | |
} | ||
``` | ||
|
||
`NextIntlClientProvider` inherits the props `locale`, `now` and `timeZone` when the component is rendered from a Server Component. Other configuration like `messages` and `formats` can be provided as necessary. | ||
|
||
<Callout> | ||
Before passing all messages to the client side, learn more about the options | ||
you have to [use internationalization in Client | ||
Components](/docs/environments/server-client-components). | ||
</Callout> | ||
`NextIntlClientProvider` inherits the props `locale`, `now` and `timeZone` when the component is rendered from a Server Component. In contrast, `messages` can be provided [as necessary](/docs/environments/server-client-components#using-internationalization-in-client-components). | ||
|
||
## Messages | ||
|
||
|
@@ -95,29 +90,37 @@ export default getRequestConfig(async ({locale}) => { | |
}); | ||
``` | ||
|
||
To read configured messages in a component, you can use the `useMessages` hook: | ||
After messages are configured, they can be used via `useTranslations`. | ||
|
||
In case you require access to messages in a component, you can use a convenience API to read them from your configuration: | ||
|
||
```tsx | ||
// Regular components | ||
import {useMessages} from 'next-intl'; | ||
|
||
const messages = useMessages(); | ||
|
||
// Async Server Components | ||
import {getMessages} from 'next-intl/server'; | ||
const messages = await getMessages(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Due to this, it might be a good idea to briefly mention this API on this page too. To be consistent, I've included them for the other config options below as well. |
||
``` | ||
|
||
</Tab> | ||
<Tab> | ||
|
||
```tsx | ||
import {NextIntlClientProvider, useMessages} from 'next-intl'; | ||
import {NextIntlClientProvider} from 'next-intl'; | ||
import {getMessages} from 'next-intl/server'; | ||
|
||
// Read messages configured via `i18n.ts`. Alternatively, | ||
// messages can be fetched from any other source too. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reading from other sources shouldn't be relevant here (see also "How can I load messages from remote sources?" above). |
||
const messages = useMessages(); | ||
async function Component({children}) { | ||
// Read messages configured via `i18n.ts` | ||
const messages = await getMessages(); | ||
|
||
return ( | ||
<NextIntlClientProvider messages={messages}> | ||
{children} | ||
</NextIntlClientProvider> | ||
); | ||
return ( | ||
<NextIntlClientProvider messages={messages}> | ||
{children} | ||
</NextIntlClientProvider> | ||
); | ||
} | ||
``` | ||
|
||
</Tab> | ||
|
@@ -211,12 +214,16 @@ const timeZone = 'Europe/Vienna'; | |
|
||
The available time zone names can be looked up in [the tz database](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones). | ||
|
||
To read the time zone in a component, you can use the `useTimeZone` hook: | ||
The configured time zone can be read from components: | ||
|
||
```tsx | ||
// Regular components | ||
import {useTimeZone} from 'next-intl'; | ||
const messages = useTimeZone(); | ||
|
||
const timeZone = useTimeZone(); | ||
// Async Server Components | ||
import {getTimeZone} from 'next-intl/server'; | ||
const timeZone = await getTimeZone(); | ||
``` | ||
|
||
The time zone in Client Components is automatically inherited from the server | ||
|
@@ -261,12 +268,16 @@ const now = new Date('2020-11-20T10:36:01.516Z'); | |
</Tab> | ||
</Tabs> | ||
|
||
To read the now value in a component, you can use the `useNow` hook: | ||
The configured `now` value can be read from components: | ||
|
||
```tsx | ||
// Regular components | ||
import {useNow} from 'next-intl'; | ||
|
||
const now = useNow(); | ||
|
||
// Async Server Components | ||
import {getNow} from 'next-intl/server'; | ||
const now = await getNow(); | ||
``` | ||
|
||
Similarly to the `timeZone`, the `now` value in Client Components is | ||
|
@@ -527,12 +538,16 @@ function getMessageFallback({namespace, key, error}) { | |
|
||
The current locale of your app is automatically incorporated into hooks like `useTranslations` & `useFormatter` and will affect the rendered output. | ||
|
||
In case you need to use this value in other places of your app, you can read it via the `useLocale` hook: | ||
In case you need to use this value in other places of your app, you can read it in components: | ||
|
||
```tsx | ||
// Regular components | ||
import {useLocale} from 'next-intl'; | ||
|
||
const locale = useLocale(); | ||
|
||
// Async Server Components | ||
import {getLocale} from 'next-intl/server'; | ||
const locale = await getLocale(); | ||
``` | ||
|
||
<Details id="locale-change"> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe less scary.