Skip to content

Commit ae4c2db

Browse files
committed
docs: Add examples for leaving i18n on the server side
1 parent 18157bc commit ae4c2db

File tree

2 files changed

+85
-3
lines changed

2 files changed

+85
-3
lines changed

docs/pages/docs/environments/server-client-components.mdx

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ Depending on your situation, you may need to handle internationalization in Clie
127127

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

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

@@ -165,10 +165,91 @@ function Expandable({title, children}) {
165165
}
166166
```
167167

168-
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.
168+
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.
169169

170170
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)
171171

172+
<details>
173+
<summary>Example: How can I implement a form?</summary>
174+
175+
Forms need client-side state for showing loading indicators and validation errors.
176+
177+
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';`.
178+
179+
**Example:**
180+
181+
```tsx filename="app/register/page.tsx"
182+
import {useTranslations} from 'next-intl';
183+
184+
// A Client Component, so that it can use `useFormState` to
185+
// potentially display errors received after submission.
186+
import RegisterForm from './RegisterForm';
187+
188+
// A Client Component, so that it can use `useFormStatus`
189+
// to disable the input field during submission.
190+
import FormField from './FormField';
191+
192+
// A Client Component, so that it can use `useFormStatus`
193+
// to disable the submit button during submission.
194+
import FormSubmitButton from './FormSubmitButton';
195+
196+
export default function RegisterPage() {
197+
const t = useTranslations('RegisterPage');
198+
199+
function registerUser() {
200+
'use server';
201+
// ...
202+
}
203+
204+
return (
205+
<RegisterForm action={registerUser}>
206+
<FormField label={t('firstName')} name="firstName" />
207+
<FormField label={t('lastName')} name="lastName" />
208+
<FormField label={t('email')} name="email" />
209+
<FormField label={t('password')} name="password" />
210+
<FormSubmitButton label={t('submit')} />
211+
</RegisterForm>
212+
);
213+
}
214+
```
215+
216+
</details>
217+
218+
<details>
219+
<summary>Example: How can I implement a locale switcher?</summary>
220+
221+
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.
222+
223+
```tsx filename="LocaleSwitcher.tsx"
224+
import {useLocale, useTranslations} from 'next-intl';
225+
import {locales} from 'config';
226+
227+
// A Client Component that registers an event listener for
228+
// the `change` event of the select, uses `useRouter`
229+
// to change the locale and uses `useTransition` to display
230+
// a loading state during the transition.
231+
import LocaleSwitcherSelect from './LocaleSwitcherSelect';
232+
233+
export default function LocaleSwitcher() {
234+
const t = useTranslations('LocaleSwitcher');
235+
const locale = useLocale();
236+
237+
return (
238+
<LocaleSwitcherSelect defaultValue={locale} label={t('label')}>
239+
{locales.map((cur) => (
240+
<option key={cur} value={cur}>
241+
{t('locale', {locale: cur})}
242+
</option>
243+
))}
244+
</LocaleSwitcherSelect>
245+
);
246+
}
247+
```
248+
249+
[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))
250+
251+
</details>
252+
172253
### Option 2: Moving state to the server side
173254

174255
You might run into cases where you have dynamic state, such as pagination, that should be reflected in translated messages.

examples/example-app-router/src/components/LocaleSwitcher.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {useLocale, useTranslations} from 'next-intl';
2+
import {locales} from 'config';
23
import LocaleSwitcherSelect from './LocaleSwitcherSelect';
34

45
export default function LocaleSwitcher() {
@@ -7,7 +8,7 @@ export default function LocaleSwitcher() {
78

89
return (
910
<LocaleSwitcherSelect defaultValue={locale} label={t('label')}>
10-
{['en', 'de'].map((cur) => (
11+
{locales.map((cur) => (
1112
<option key={cur} value={cur}>
1213
{t('locale', {locale: cur})}
1314
</option>

0 commit comments

Comments
 (0)