Skip to content

Commit

Permalink
docs: Add examples for leaving i18n on the server side
Browse files Browse the repository at this point in the history
  • Loading branch information
amannn committed Nov 28, 2023
1 parent 18157bc commit ae4c2db
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 3 deletions.
85 changes: 83 additions & 2 deletions docs/pages/docs/environments/server-client-components.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ Depending on your situation, you may need to handle internationalization in Clie

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

```tsx filename="[locale]/faq/page.tsx"
```tsx filename="[locale]/faq/page.tsx" {10-12}
import {useTranslations} from 'next-intl';
import Expandable from './Expandable';

Expand Down Expand Up @@ -165,10 +165,91 @@ function Expandable({title, children}) {
}
```

As you see, we can use interactive features from React like `useState` on translated content, even though the translation only runs on the server side.
By doing this, we can use interactive features from React like `useState` on translated content, even though the translation only runs on the server side.

Learn more in the Next.js docs: [Passing Server Components to Client Components as Props](https://nextjs.org/docs/app/building-your-application/rendering/composition-patterns#supported-pattern-passing-server-components-to-client-components-as-props)

<details>
<summary>Example: How can I implement a form?</summary>

Forms need client-side state for showing loading indicators and validation errors.

To keep internationalization on the server side, it can be helpful to structure your components in a way where the interactive parts are moved out to leaf components instead of marking the whole form with `'use client';`.

**Example:**

```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.
import RegisterForm from './RegisterForm';

// A Client Component, so that it can use `useFormStatus`
// to disable the input field during submission.
import FormField from './FormField';

// A Client Component, so that it can use `useFormStatus`
// to disable the submit button during submission.
import FormSubmitButton from './FormSubmitButton';

export default function RegisterPage() {
const t = useTranslations('RegisterPage');

function registerUser() {
'use server';
// ...
}

return (
<RegisterForm action={registerUser}>
<FormField label={t('firstName')} name="firstName" />
<FormField label={t('lastName')} name="lastName" />
<FormField label={t('email')} name="email" />
<FormField label={t('password')} name="password" />
<FormSubmitButton label={t('submit')} />
</RegisterForm>
);
}
```

</details>

<details>
<summary>Example: How can I implement a locale switcher?</summary>

If you implement a locale switcher as an interactive select, you can keep internationalization on the server side by rendering the labels from a Server Component and only marking the select element as a Client Component.

```tsx filename="LocaleSwitcher.tsx"
import {useLocale, useTranslations} from 'next-intl';
import {locales} from 'config';

// A Client Component that registers an event listener for
// the `change` event of the select, uses `useRouter`
// to change the locale and uses `useTransition` to display
// a loading state during the transition.
import LocaleSwitcherSelect from './LocaleSwitcherSelect';

export default function LocaleSwitcher() {
const t = useTranslations('LocaleSwitcher');
const locale = useLocale();

return (
<LocaleSwitcherSelect defaultValue={locale} label={t('label')}>
{locales.map((cur) => (
<option key={cur} value={cur}>
{t('locale', {locale: cur})}
</option>
))}
</LocaleSwitcherSelect>
);
}
```

[Example implementation](https://github.com/amannn/next-intl/blob/main/examples/example-app-router/src/components/LocaleSwitcher.tsx) ([demo](https://next-intl-example-app-router.vercel.app/en))

</details>

### Option 2: Moving state to the server side

You might run into cases where you have dynamic state, such as pagination, that should be reflected in translated messages.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {useLocale, useTranslations} from 'next-intl';
import {locales} from 'config';
import LocaleSwitcherSelect from './LocaleSwitcherSelect';

export default function LocaleSwitcher() {
Expand All @@ -7,7 +8,7 @@ export default function LocaleSwitcher() {

return (
<LocaleSwitcherSelect defaultValue={locale} label={t('label')}>
{['en', 'de'].map((cur) => (
{locales.map((cur) => (
<option key={cur} value={cur}>
{t('locale', {locale: cur})}
</option>
Expand Down

2 comments on commit ae4c2db

@vercel
Copy link

@vercel vercel bot commented on ae4c2db Nov 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on ae4c2db Nov 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

next-intl-docs – ./docs

next-intl-docs.vercel.app
next-intl-docs-git-main-next-intl.vercel.app
next-intl-docs-next-intl.vercel.app

Please sign in to comment.