diff --git a/docs/pages/docs/environments/server-client-components.mdx b/docs/pages/docs/environments/server-client-components.mdx
index 922dd7fef..c07d28929 100644
--- a/docs/pages/docs/environments/server-client-components.mdx
+++ b/docs/pages/docs/environments/server-client-components.mdx
@@ -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';
@@ -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)
+
+Example: How can I implement a form?
+
+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 (
+
+
+
+
+
+
+
+ );
+}
+```
+
+
+
+
+Example: How can I implement a locale switcher?
+
+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 (
+
+ {locales.map((cur) => (
+
+ ))}
+
+ );
+}
+```
+
+[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))
+
+
+
### 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.
diff --git a/examples/example-app-router/src/components/LocaleSwitcher.tsx b/examples/example-app-router/src/components/LocaleSwitcher.tsx
index ac5f7821c..2b07d5a35 100644
--- a/examples/example-app-router/src/components/LocaleSwitcher.tsx
+++ b/examples/example-app-router/src/components/LocaleSwitcher.tsx
@@ -1,4 +1,5 @@
import {useLocale, useTranslations} from 'next-intl';
+import {locales} from 'config';
import LocaleSwitcherSelect from './LocaleSwitcherSelect';
export default function LocaleSwitcher() {
@@ -7,7 +8,7 @@ export default function LocaleSwitcher() {
return (
- {['en', 'de'].map((cur) => (
+ {locales.map((cur) => (