diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
index cb4ed314f..0c3398a42 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -29,9 +29,8 @@ body:
label: Mandatory reproduction URL (CodeSandbox or GitHub repository)
description: |
**Templates:**
- - [CodeSandbox (`app` directory)](https://codesandbox.io/p/sandbox/next-intl-bug-template-forked-yow8ep)
- - [CodeSandbox (`app` directory, RSC RC)](https://codesandbox.io/p/sandbox/next-intl-bug-template-app-forked-zcymvq)
- - [CodeSandbox (`pages` directory)](https://codesandbox.io/p/sandbox/next-intl-bug-template-pages-krm37f)
+ - [CodeSandbox (App Router)](https://codesandbox.io/p/sandbox/next-intl-bug-template-app-forked-zcymvq)
+ - [CodeSandbox (Pages Router)](https://codesandbox.io/p/sandbox/next-intl-bug-template-pages-krm37f)
validations:
required: true
- type: textarea
diff --git a/.github/workflows/issue-commenter.yml b/.github/workflows/issue-commenter.yml
index 2f7bb8c95..8a413dac7 100644
--- a/.github/workflows/issue-commenter.yml
+++ b/.github/workflows/issue-commenter.yml
@@ -19,9 +19,8 @@ jobs:
Unfortunately, the reproduction is missing or incomplete, and as such we cannot investigate this issue. Please add a reproduction to the issue, otherwise it will be closed automatically.
**Templates:**
- - [CodeSandbox (`app` directory)](https://codesandbox.io/p/sandbox/next-intl-bug-template-forked-yow8ep)
- - [CodeSandbox (`app` directory, RSC RC)](https://codesandbox.io/p/sandbox/next-intl-bug-template-app-forked-zcymvq)
- - [CodeSandbox (`pages` directory)](https://codesandbox.io/p/sandbox/next-intl-bug-template-pages-krm37f)
+ - [CodeSandbox (App Router)](https://codesandbox.io/p/sandbox/next-intl-bug-template-app-forked-zcymvq)
+ - [CodeSandbox (Pages Router)](https://codesandbox.io/p/sandbox/next-intl-bug-template-pages-krm37f)
**Creating a good bug report takes time.**
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 279ab22e3..8a007427f 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -20,6 +20,12 @@ jobs:
cache: 'pnpm'
- run: pnpm install
+ # Next.js caching
+ - uses: actions/cache@v3
+ with:
+ path: ${{ github.workspace }}/examples/*/.next/cache
+ key: ${{ runner.os }}-nextjs-${{ hashFiles('pnpm-lock.yaml') }}
+
# Playwright
- run: |
PLAYWRIGHT_VERSION=$(cat pnpm-lock.yaml | grep /@playwright/test@ | sed 's/.*@\([^:]*\):.*/\1/')
@@ -32,7 +38,7 @@ jobs:
key: playwright-browsers-${{ env.PLAYWRIGHT_VERSION }}
- if: steps.cache-playwright-browsers.outputs.cache-hit != 'true'
# Setting up playwright in one example is sufficient
- run: pnpm --filter example-next-13 exec playwright install --with-deps
+ run: pnpm --filter example-next-13-advanced exec playwright install --with-deps
- run: pnpm run build
- run: pnpm run lint
diff --git a/README.md b/README.md
index fe5dda024..c9086ca9b 100644
--- a/README.md
+++ b/README.md
@@ -51,7 +51,7 @@ export default function UserProfile({user}) {
}
```
-```json
+```js
// en.json
{
"UserProfile": {
diff --git a/docs/components/CodeSnippets.tsx b/docs/components/CodeSnippets.tsx
index 6193863de..5bb02eb52 100644
--- a/docs/components/CodeSnippets.tsx
+++ b/docs/components/CodeSnippets.tsx
@@ -19,7 +19,7 @@ function icu() {
:
- "{'{'}username{'}'}'s profile"
+ "{'{'}firstName{'}'}'s profile"
,
diff --git a/docs/components/Hero.tsx b/docs/components/Hero.tsx
index c99fceb37..81773972f 100644
--- a/docs/components/Hero.tsx
+++ b/docs/components/Hero.tsx
@@ -47,7 +47,7 @@ export default function Hero({
📣{' '}
{rscAnnouncement}
diff --git a/docs/components/HeroCode.tsx b/docs/components/HeroCode.tsx
index 313749516..34291eea9 100644
--- a/docs/components/HeroCode.tsx
+++ b/docs/components/HeroCode.tsx
@@ -195,7 +195,7 @@ const files = [
:
- "{'{'}username{'}'}'s profile"
+ "{'{'}firstname{'}'}'s profile"
,
diff --git a/docs/components/VersionTabs.tsx b/docs/components/VersionTabs.tsx
deleted file mode 100644
index 8f1f4377b..000000000
--- a/docs/components/VersionTabs.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import {Tabs} from 'nextra-theme-docs';
-import {ReactNode} from 'react';
-import Chip from './Chip';
-
-type Props = {
- children: ReactNode;
- defaultLabel?: ReactNode;
- rscLabel?: ReactNode;
-};
-
-export default function VersionTabs({
- children,
- defaultLabel = 'Default',
- rscLabel = 'Server Components'
-}: Props) {
- return (
-
- {rscLabel}
-
- Beta
-
-
- ]}
- >
- {children}
-
- );
-}
diff --git a/docs/next.config.js b/docs/next.config.js
index e48e5a88f..4e21cad8b 100644
--- a/docs/next.config.js
+++ b/docs/next.config.js
@@ -28,14 +28,24 @@ module.exports = withNextra({
destination: '/docs/getting-started',
permanent: true
},
+ {
+ source: '/docs/configuration',
+ destination: '/docs/usage/configuration',
+ permanent: true
+ },
{
source: '/docs/getting-started/production-checklist',
- destination: '/docs/production-checklist',
+ destination: '/docs/environments/runtime-requirements',
permanent: true
},
{
source: '/docs/usage/production-checklist',
- destination: '/docs/production-checklist',
+ destination: '/docs/environments/runtime-requirements',
+ permanent: true
+ },
+ {
+ source: '/docs/production-checklist',
+ destination: '/docs/environments/runtime-requirements',
permanent: true
},
{
@@ -45,12 +55,22 @@ module.exports = withNextra({
},
{
source: '/docs/next-13/client-components',
- destination: '/docs/getting-started/app-router-client-components',
+ destination: '/docs/getting-started/app-router',
permanent: true
},
{
source: '/docs/next-13/server-components',
- destination: '/docs/getting-started/app-router-server-components',
+ destination: '/docs/getting-started/app-router',
+ permanent: true
+ },
+ {
+ source: '/docs/getting-started/app-router-server-components',
+ destination: '/docs/getting-started/app-router',
+ permanent: true
+ },
+ {
+ source: '/docs/getting-started/app-router-client-components',
+ destination: '/docs/getting-started/app-router',
permanent: true
},
{
@@ -75,22 +95,17 @@ module.exports = withNextra({
},
{
source: '/docs/usage/production-checklist',
- destination: '/docs/production-checklist',
+ destination: '/docs/environments/runtime-requirements',
permanent: true
},
{
source: '/docs/usage/runtime-requirements-polyfills',
- destination: '/docs/production-checklist#runtime-requirements',
- permanent: true
- },
- {
- source: '/docs/usage/configuration',
- destination: '/docs/configuration',
+ destination: '/docs/environments/runtime-requirements',
permanent: true
},
{
source: '/docs/usage/error-handling',
- destination: '/docs/configuration#error-handling',
+ destination: '/docs/usage/configuration#error-handling',
permanent: true
},
{
diff --git a/docs/package.json b/docs/package.json
index bca594e51..0d4ae83f3 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -24,14 +24,14 @@
"tailwindcss": "^3.3.2"
},
"devDependencies": {
- "@types/node": "20.1.2",
- "@types/react": "^18.2.5",
+ "@types/node": "^20.1.2",
+ "@types/react": "^18.2.29",
"autoprefixer": "^10.4.0",
"eslint": "^8.46.0",
"eslint-config-molindo": "^7.0.0",
"eslint-config-next": "^13.4.0",
"next-sitemap": "^4.0.7",
- "typescript": "^5.0.0"
+ "typescript": "^5.2.2"
},
"funding": "https://github.com/amannn/next-intl?sponsor=1"
}
diff --git a/docs/pages/blog/next-intl-3-0.mdx b/docs/pages/blog/next-intl-3-0.mdx
index 97848c7b8..e7a36ce7b 100644
--- a/docs/pages/blog/next-intl-3-0.mdx
+++ b/docs/pages/blog/next-intl-3-0.mdx
@@ -1,36 +1,38 @@
---
-title: next-intl 3.0 release candidate
+title: next-intl 3.0
---
-# next-intl 3.0 release candidate
+import PartnerContentLink from 'components/PartnerContentLink';
-Sep 28, 2023 · by Jan Amann
+# next-intl 3.0
-Almost one year ago, on Oct 25, 2022, [Next.js 13 was announced](https://nextjs.org/blog/next-13) with beta support for the App Router and Server Components. Ever since then, `next-intl` began an exploration on what it means to provide an ideal experience for implementing i18n with the newly added capabilities.
+Nov 14, 2023 · by Jan Amann
-Today, [after more than 300 commits and the involvement of over 50 community members](https://github.com/amannn/next-intl/pull/149), I'm absolutely thrilled to share **the first release candidate of `next-intl` 3.0**, which is now App Router-first.
+More than a year ago, on Oct 25, 2022, [Next.js 13 was announced](https://nextjs.org/blog/next-13) with beta support for the App Router and Server Components. Ever since then, `next-intl` began an exploration on what it means to provide an ideal experience for implementing i18n with the newly added capabilities.
-If you're still happy with the Pages Router, rest assured that `next-intl` is dedicated to support this paradigm for as long as Next.js does. For those who have already migrated to the App Router, this means that `next-intl` now takes full advantage of the new capabilities.
+Today, [after more than 370 commits and the involvement of over 60 community members](https://github.com/amannn/next-intl/pull/149), I'm absolutely thrilled to announce **next-intl 3.0**, which is now App Router-first.
+
+If you're still happy with the Pages Router, rest assured that `next-intl` is dedicated to support this paradigm for as long as Next.js does. For those who have already migrated to the App Router, this means that you can take full advantage of the new capabilities with `next-intl`.
## New features
-1. **Support for React Server Components**: The APIs `useTranslations`, `useFormatter`, `useLocale`, `useNow` and `useTimeZone` can now be used in Server Components ([proposed docs](https://next-intl-docs-git-feat-next-13-rsc-next-intl.vercel.app/docs/environments/server-client-components)).
-2. **New async APIs to handle i18n outside of components**: To handle i18n in the Metadata API and Route Handlers, the APIs `getTranslations`, `getFormatter`, `getNow`, and `getTimeZone` have been added ([proposed docs](https://next-intl-docs-git-feat-next-13-rsc-next-intl.vercel.app/docs/environments/metadata-route-handlers)).
-3. **Middleware for internationalized routing**: While Next.js has built-in support for this with the Pages Router, the App Router doesn't include a built-in solution anymore. `next-intl` now provides a drop-in solution that has you covered ([proposed docs](https://next-intl-docs-git-feat-next-13-rsc-next-intl.vercel.app/docs/routing/middleware)).
-4. **Internationalized navigation APIs**: Similar to the middleware, this provides a drop-in solution that adds internationalization support for Next.js' navigation APIs: `Link`, `useRouter`, `usePathname` and `redirect`. These APIs allow you to handle locale prefixes behind the scenes and also provide support for localizing pathnames (e.g. `/en/about` vs. `/de/ueber-uns`, see the [proposed docs](https://next-intl-docs-git-feat-next-13-rsc-next-intl.vercel.app/docs/routing/navigation)).
+1. **Support for React Server Components**: The APIs `useTranslations`, `useFormatter`, `useLocale`, `useNow` and `useTimeZone` can now be used in Server Components ([docs](/docs/environments/server-client-components)).
+2. **New async APIs**: To handle i18n in async components, the Metadata API and Route Handlers, the APIs `getTranslations`, `getFormatter`, `getNow`, and `getTimeZone` have been added ([docs](/docs/environments/metadata-route-handlers)).
+3. **Middleware for internationalized routing**: While Next.js has built-in support for this with the Pages Router, the App Router doesn't include a built-in solution anymore. `next-intl` now provides a drop-in solution that has you covered ([docs](/docs/routing/middleware)).
+4. **Internationalized navigation APIs**: Similar to the middleware, this provides a drop-in solution that adds internationalization support for Next.js' navigation APIs: `Link`, `useRouter`, `usePathname` and `redirect`. These APIs allow you to handle locale prefixes behind the scenes and also provide support for localizing pathnames (e.g. `/en/about` vs. `/de/ueber-uns`, see the [docs](/docs/routing/navigation)).
The latter two have already been added in minor versions, but 3.0 cleans up the API and includes many improvements and bug fixes.
## Breaking changes
-If you've been part of the Server Components beta and have already tried out previous releases, first of all, thank you so much! Second: Some APIs saw iterations over the beta period, please carefully review the breaking changes below, even if you're already using some of the new APIs.
+If you've already tried out pre-release versions, first of all, thank you so much! Second: Some APIs saw iterations over the beta period, please carefully review the breaking changes below, even if you're already using some of the new APIs.
### Updated setup
`next-intl` now requires two additional setup steps when you're using the App Router:
-1. [The `i18n.ts` module](https://next-intl-docs-git-feat-next-13-rsc-next-intl.vercel.app/docs/getting-started/app-router-server-components#i18nts) provides configuration for Server Components
-2. [`next-intl/plugin`](https://next-intl-docs-git-feat-next-13-rsc-next-intl.vercel.app/docs/getting-started/app-router-server-components#nextconfigjs) needs to be added to link your `i18n.ts` module to `next-intl`
+1. [The `i18n.ts` module](/docs/getting-started/app-router#i18nts) provides configuration for Server Components
+2. [`next-intl/plugin`](/docs/getting-started/app-router#nextconfigjs) needs to be added to link your `i18n.ts` module to `next-intl`
### New navigation APIs for the App Router
@@ -49,9 +51,9 @@ With 3.0, we're cleaning up these APIs by moving them to a shared namespace as w
+ const {Link, useRouter, usePathname, redirect} = createSharedPathnamesNavigation({locales});
```
-Typically, you'll want to call this factory function in a central place in your app where you can easily import from (see [the proposed navigation docs](https://next-intl-docs-git-feat-next-13-rsc-next-intl.vercel.app/docs/routing/navigation#shared-pathnames)).
+Typically, you'll want to call this factory function in a central place in your app where you can easily import from (see [the navigation docs](/docs/routing/navigation#shared-pathnames)).
-These changes bring the existing APIs in line with the new [`createLocalizedPathnamesNavigation` API](https://next-intl-docs-git-feat-next-13-rsc-next-intl.vercel.app/docs/routing/navigation#localized-pathnames) that allows you to localize pathnames:
+These changes bring the existing APIs in line with the new [`createLocalizedPathnamesNavigation` API](/docs/routing/navigation#localized-pathnames) that allows you to localize pathnames:
```tsx filename="navigation.ts"
import {createLocalizedPathnamesNavigation, Pathnames} from 'next-intl/navigation';
@@ -82,44 +84,61 @@ By using a similar API, you can upgrade from shared pathnames to localized pathn
### Switching the middleware default of `localePrefix` to `always`
-Previously, the [`localePrefix` of the middleware](https://next-intl-docs-git-feat-next-13-rsc-next-intl.vercel.app/docs/routing/middleware#locale-prefix) defaulted to `as-needed`, meaning that a locale prefix was only added for non-default locales.
+Previously, the [`localePrefix` of the middleware](/docs/routing/middleware#locale-prefix) defaulted to `as-needed`, meaning that a locale prefix was only added for non-default locales.
This default has now been changed to `always` since this has two advantages:
-1. We can recommend [a safer default `matcher`](https://next-intl-docs-git-feat-next-13-rsc-next-intl.vercel.app/docs/routing/middleware#matcher-config) that needs no extra treatment for pathnames with dots (e.g. `/users/jane.doe`)
-2. It avoids an [edge case of `Link`](https://next-intl-docs-git-feat-next-13-rsc-next-intl.vercel.app/docs/routing/middleware#locale-prefix-as-needed) where we include a prefix for the default locale on the server side but patch this on the client side by removing the prefix (certain SEO tools might report a hint that a link points to a redirect in this case).
+1. We can recommend [a safer default `matcher`](/docs/routing/middleware#matcher-config) that needs no extra treatment for pathnames with dots (e.g. `/users/jane.doe`)
+2. It avoids an [edge case of `Link`](/docs/routing/middleware#locale-prefix-as-needed) where we include a prefix for the default locale on the server side but patch this on the client side by removing the prefix (certain SEO tools might report a hint that a link points to a redirect in this case).
-If you want to stay on the `as-needed` strategy, you can [configure this option](https://next-intl-docs-git-feat-next-13-rsc-next-intl.vercel.app/docs/routing/middleware#locale-prefix-as-needed) in the middleware.
+If you want to stay on the `as-needed` strategy, you can [configure this option](/docs/routing/middleware#locale-prefix-as-needed) in the middleware.
### Static rendering of Server Components
With the newly introduced Server Components support comes a temporary workaround for static rendering.
-If you call APIs like `useTranslations` in a Server Component, by default, the hook will opt the page into dynamic rendering. This is a limitation that we aim to remove in the future, but as a stopgap solution, we've added the [`unstable_setRequestLocale`](https://next-intl-docs-git-feat-next-13-rsc-next-intl.vercel.app/docs/getting-started/app-router-server-components#static-rendering) API so that you can keep your pages fully static.
+If you call APIs from `next-intl` in Server Components, the page will by default opt into [dynamic rendering](https://nextjs.org/docs/app/building-your-application/rendering/server-components#dynamic-rendering). This is a limitation that we aim to remove in the future, but as a stopgap solution, we've added the [`unstable_setRequestLocale`](/docs/getting-started/app-router-server-components#static-rendering) API so that you can keep your pages fully static.
Note that if you're using `next-intl` exclusively in Client Components, this doesn't apply to your app and static rendering will continue to work as it did before.
### Other notable changes
1. `next-intl` now uses [`exports` in `package.json`](https://nodejs.org/api/packages.html#subpath-exports) to clearly define which modules are exported. This should not affect you, unless you've previously imported undocumented internals.
-2. `NextIntlProvider` has been removed in favor of [`NextIntlClientProvider`](https://next-intl-docs-git-feat-next-13-rsc-next-intl.vercel.app/docs/configuration#client-server-components)
+2. `NextIntlProvider` has been removed in favor of [`NextIntlClientProvider`](/docs/usage/configuration#client-server-components)
3. `NextIntlClientProvider` now needs to be imported from `next-intl` instead of `next-intl/client`.
-4. [The middleware](https://next-intl-docs-git-feat-next-13-rsc-next-intl.vercel.app/docs/routing/middleware) now needs to be imported from `next-intl/middleware` instead of `next-intl/server` (deprecated since v2.14).
+4. [The middleware](/docs/routing/middleware) now needs to be imported from `next-intl/middleware` instead of `next-intl/server` (deprecated since v2.14).
5. `next@^13.4` is now required for the RSC APIs. Next.js 12 is still supported for the Pages Router integration.
6. If you're using `NextIntlClientProvider` outside of the App Router (e.g. with the Pages Router), you need to define the `locale` prop explicitly.
7. `useMessages` now has a non-nullable return type for easier consumption and will throw if no messages are configured.
-8. `createTranslator(…).rich` now returns a `ReactNode`. Previously, this was somewhat confusing, since `t.rich` accepted and returned either React elements or strings depending on if you retrieve the fuction via `useTranslations` or `createTranslator`. Now, an explicit [`t.markup`](https://next-intl-docs-git-feat-next-13-rsc-next-intl.vercel.app/docs/usage/messages#html-markup) function has been added to generate markup strings like `'Hello'` outside of React components.
-9. `useIntl` has been replaced with [`useFormatter`](https://next-intl-docs-git-feat-next-13-rsc-next-intl.vercel.app/docs/usage/dates-times) (deprecated since v2.11).
-10. `createIntl` has been replaced with [`createFormatter`](https://next-intl-docs-git-feat-next-13-rsc-next-intl.vercel.app/docs/environments/core-library) (deprecated since v2.11).
+8. `createTranslator(…).rich` now returns a `ReactNode`. Previously, this was somewhat confusing, since `t.rich` accepted and returned either React elements or strings depending on if you retrieve the fuction via `useTranslations` or `createTranslator`. Now, an explicit [`t.markup`](/docs/usage/messages#html-markup) function has been added to generate markup strings like `'Hello'` outside of React components.
+9. `useIntl` has been replaced with [`useFormatter`](/docs/usage/dates-times) (deprecated since v2.11).
+10. `createIntl` has been replaced with [`createFormatter`](/docs/environments/core-library) (deprecated since v2.11).
## Upgrade now
-Along with the release candidate also comes a [preview of the updated docs](https://next-intl-docs-git-feat-next-13-rsc-next-intl.vercel.app/).
-
-We're still looking for more feedback, please try out the release candidate and [reach out](https://github.com/amannn/next-intl/pull/149)!
-
```
-npm install next-intl@3.0.0-rc.10
+npm install next-intl@3.0.0
```
-This release was truly a team effort and couldn't be nearly where it is today without the involvement of the community. Thank you so much for being part of this!
+## Thank you
+
+This release was truly a team effort and couldn't be nearly where it is today without the involvement of the community.
+
+Thank you so much for:
+
+- All the encouraging words along the way
+- Testing out prereleases
+- Providing feedback
+- Contributing code
+- Questioning ideas
+- Helping each other
+
+I had the pleasure to get in touch with so many of you along the way and I'm incredibly grateful for the willingness to help and support each other in our community.
+
+A special thank you goes to Crowdin, being the primary sponsor for `next-intl` and enabling me to regularly dedicate time for this project.
+
+—Jan
+
+---
+
+(this post has been updated from an initial announcement for the 3.0 release candidate)
diff --git a/docs/pages/blog/translations-outside-of-react-components.mdx b/docs/pages/blog/translations-outside-of-react-components.mdx
index fb4077ec1..3280699c8 100644
--- a/docs/pages/blog/translations-outside-of-react-components.mdx
+++ b/docs/pages/blog/translations-outside-of-react-components.mdx
@@ -113,26 +113,21 @@ Additionally, this approach is more robust to possibly unexpected states, like t
If you’re working with Next.js, you might want to translate i18n messages in [API routes](https://nextjs.org/docs/pages/building-your-application/routing/api-routes), [Route Handlers](https://nextjs.org/docs/app/building-your-application/routing/router-handlers) or the [Metadata API](https://nextjs.org/docs/app/api-reference/file-conventions/metadata).
-`next-intl` provides a [core library](/docs/environments/core-library) that is agnostic from React and can be used for these cases.
+`next-intl/server` provides a set of awaitable versions of the functions that you usually call as hooks from within components. These are agnostic from React and can be used for these cases.
```tsx
-import {createTranslator} from 'next-intl';
+import {getTranslations} from 'next-intl/server';
-const messages = {
- hello: 'Hello {name}!'
-};
+// The `locale` is received from Next.js via `params`
+const locale = params.locale;
// This creates the same function that is returned by `useTranslations`.
-// Since there's no provider, you can pass all the properties you'd
-// usually pass to the provider directly here.
-const t = createTranslator({locale: 'en', messages});
+const t = await getTranslations({locale});
// Result: "Hello world!"
t('hello', {name: 'world'});
```
-There's currently a proposal to further simplify this use case, by offering a set of [new APIs that integrate with Server Components](/docs/environments/metadata-route-handlers) (currently in beta).
-
## This seems familiar
If you’ve been working with React for a longer time, you might have experienced the change [from `component{DidMount,DidUpdate,WillUnmount}` to `useEffect`](https://legacy.reactjs.org/docs/hooks-effect.html#explanation-why-effects-run-on-each-update). The reason why `useEffect` is superior is because it nudges the developer into a direction where the app is always in sync and by doing this, a whole array of potential issues just magically disappear.
diff --git a/docs/pages/docs/_meta.json b/docs/pages/docs/_meta.json
index bb4c20f62..097f7bf7f 100644
--- a/docs/pages/docs/_meta.json
+++ b/docs/pages/docs/_meta.json
@@ -2,9 +2,7 @@
"getting-started": "Getting started",
"usage": "Usage guide",
"environments": "Environments",
- "configuration": "Global configuration",
"routing": "Routing",
"workflows": "Workflows & integrations",
- "production-checklist": "Production checklist",
"faq": "FAQ"
}
\ No newline at end of file
diff --git a/docs/pages/docs/environments/_meta.json b/docs/pages/docs/environments/_meta.json
index 62dc74241..d5ee47831 100644
--- a/docs/pages/docs/environments/_meta.json
+++ b/docs/pages/docs/environments/_meta.json
@@ -3,5 +3,6 @@
"server-client-components": "Server & Client Components",
"metadata-route-handlers": "Metadata & Route Handlers",
"error-files": "Error files (e.g. not-found)",
- "core-library": "Core library"
+ "core-library": "Core library",
+ "runtime-requirements": "Runtime requirements"
}
diff --git a/docs/pages/docs/environments/core-library.mdx b/docs/pages/docs/environments/core-library.mdx
index c9fc2bf9f..b8c6fc2e3 100644
--- a/docs/pages/docs/environments/core-library.mdx
+++ b/docs/pages/docs/environments/core-library.mdx
@@ -18,15 +18,15 @@ const t = createTranslator({locale: 'en', messages});
// Result: "Hello world!"
t('basic', {name: 'world'});
-// Rich text uses functions that accept and return a string.
-// Result: "Hello world!"
-t.rich('rich', {
+// To generate HTML markup, you can consider using the `markup`
+// function which in contrast to `t.rich` returns a markup string.
+t.markup('rich', {
name: 'world',
b: (chunks) => `${chunks}`
});
```
-For date, time and number formatting, the `intl` object can be created outside of React as well:
+For date, time and number formatting, the `format` object can be created outside of React as well:
```js
import {createFormatter} from 'next-intl';
diff --git a/docs/pages/docs/environments/error-files.mdx b/docs/pages/docs/environments/error-files.mdx
index fc095a991..52a0d2828 100644
--- a/docs/pages/docs/environments/error-files.mdx
+++ b/docs/pages/docs/environments/error-files.mdx
@@ -1,4 +1,3 @@
-import VersionTabs from 'components/VersionTabs';
import Callout from 'components/Callout';
# Internationalization in Next.js error files
@@ -42,7 +41,7 @@ export default function CatchAllPage() {
}
```
-After this change, all requests that are matched within the `[locale]` segment will render the `not-found` page when an unknown route is encountered.
+After this change, all requests that are matched within the `[locale]` segment will render the `not-found` page when an unknown route is encountered (e.g. `/en/unknown`).
### Catching non-localized requests
@@ -53,24 +52,20 @@ You can add a root `not-found` page to handle these cases too.
```tsx filename="app/not-found.tsx"
'use client';
-import Error from 'next/error';
+import {redirect, usePathname} from 'next/navigation';
-// Render the default Next.js 404 page when a route
-// is requested that doesn't match the middleware and
-// therefore doesn't have a locale associated with it.
+// Can be imported from a shared config
+const defaultLocale = 'en';
export default function NotFound() {
- return (
-
-
-
-
-
- );
+ const pathname = usePathname();
+
+ // Add a locale prefix to show a localized not found page
+ redirect(`/${defaultLocale}${pathname}`);
}
```
-Note that the presence of `app/not-found.tsx` requires that a root layout is available as well, even if it's just passing `children` through.
+Note that the presence of `app/not-found.tsx` requires that a root layout is available, even if it's just passing `children` through.
```tsx filename="app/layout.tsx"
// Since we have a root `not-found.tsx` page, a layout file
@@ -89,12 +84,8 @@ import {notFound} from 'next/navigation';
const locales = ['en', 'de'];
export default function LocaleLayout({children, params}) {
- const locale = useLocale();
-
- // Validate that the incoming `locale` parameter is a valid locale
- if (params.locale !== locale) {
- notFound();
- }
+ // Validate that the incoming `locale` parameter is valid
+ if (!locales.includes(locale as any)) notFound();
return (
@@ -125,11 +116,7 @@ When an `error` file is defined, Next.js creates [an error boundary within your
-Since the `error` file must be defined as a Client Component, you have to use [`NextIntlClientProvider`](/docs/configuration#client-server-components) to provide messages in case the `error` file renders.
-
-If you've [set up `next-intl` to be used in Client Components](/docs/getting-started/app-router-client-components), this is already the case and there's no additional setup needed. If you're using [the Server Components beta](/docs/getting-started/app-router-server-components) though, you have to provide the relevant messages in the wrapping layout.
-
-
-
Once `NextIntlClientProvider` is in place, you can use functionality from `next-intl` in the `error` file:
```tsx filename="app/[locale]/error.tsx"
@@ -178,3 +158,14 @@ export default function Error({error, reset}) {
);
}
```
+
+Note that `error.tsx` is loaded as soon as the app starts. If your app is performance-senstive and you want to avoid loading translation functionality from `next-intl` as part of the initial bundle, you can export a lazy reference from your `error` file:
+
+```tsx filename="app/[locale]/error.tsx"
+'use client';
+
+import {lazy} from 'react';
+
+// Move error content to a separate chunk and load it only when needed
+export default lazy(() => import('./Error'));
+```
diff --git a/docs/pages/docs/environments/index.mdx b/docs/pages/docs/environments/index.mdx
index 4ca310a98..ee525c26e 100644
--- a/docs/pages/docs/environments/index.mdx
+++ b/docs/pages/docs/environments/index.mdx
@@ -36,3 +36,5 @@ The `next-intl` APIs are available in the following environments:
href="/docs/environments/core-library"
/>
+
+While modern browsers and server runtimes typically support all necessary JavaScript APIs that are required for `next-intl`, you can double check [the runtime requirements](/docs/environments/runtime-requirements).
diff --git a/docs/pages/docs/environments/metadata-route-handlers.mdx b/docs/pages/docs/environments/metadata-route-handlers.mdx
index 857080547..cc18a4c52 100644
--- a/docs/pages/docs/environments/metadata-route-handlers.mdx
+++ b/docs/pages/docs/environments/metadata-route-handlers.mdx
@@ -1,8 +1,7 @@
import Callout from 'components/Callout';
-import VersionTabs from 'components/VersionTabs';
import {Tab} from 'nextra-theme-docs';
-# Internationalization of Metadata & Route Handlers in Next.js 13
+# Internationalization of Metadata & Route Handlers with the Next.js App Router
There are a few places in Next.js apps where you might need to apply internationalization outside of React components:
@@ -10,87 +9,17 @@ There are a few places in Next.js apps where you might need to apply internation
2. [Metadata files](https://nextjs.org/docs/app/api-reference/file-conventions/metadata)
3. [Route Handlers](https://nextjs.org/docs/app/building-your-application/routing/router-handlers)
-For these cases, you can either use:
-
-1. The [core library](/docs/environments/core-library) from `next-intl`, if you're using [the latest stable version](/docs/getting-started/app-router-client-components)
-2. A set of new APIs that automatically pick up your request configuration, if you're using [the Server Components beta version](/docs/getting-started/app-router-server-components)
-
-
-
-
-```tsx
-import {createTranslator, createFormatter} from 'next-intl';
-
-// The `locale` is received from Next.js via `params`
-const locale = params.locale;
-
-// You can use the core (non-React) APIs when you
-// have to use next-intl outside of components.
-const t = createTranslator({locale, messages});
-const format = createFormatter({locale: 'en'});
-```
-
-
-
-`next-intl/server` provides a set of awaitable versions of the functions that you usually call as hooks from within components. Unlike the hooks, these functions require a `locale` that you receive from Next.js.
-
-```tsx
-import {
- getTranslator,
- getFormatter,
- getNow,
- getTimeZone,
- getMessages
-} from 'next-intl/server';
-
-// The `locale` is received from Next.js via `params`
-const locale = params.locale;
-
-const t = await getTranslator(locale, 'Metadata');
-const format = await getFormatter(locale);
-const now = await getNow(locale);
-const timeZone = await getTimeZone(locale);
-const messages = await getMessages(locale);
-```
-
-
- The [global request configuration]((#global-request-configuration)) that
- you've set up in `i18n.ts` is automatically inherited by these functions. The
- `locale` is the only exception that needs to be provided in comparison to the
- hooks.
-
-
-
-
+`next-intl/server` provides a set of [awaitable functions](/docs/environments/server-client-components#async-components) that can be used in these cases.
### Metadata API
To internationalize metadata like the page title, you can use functionality from `next-intl` in the [`generateMetadata`](https://nextjs.org/docs/app/api-reference/functions/generate-metadata#generatemetadata-function) function that can be exported from pages and layouts.
-
-
-
-```tsx filename="app/[locale]/layout.tsx"
-import {createTranslator} from 'next-intl';
-
-export async function generateMetadata({params: {locale}}) {
- const messages = (await import(`../../../messages/${locale}.json`)).default;
- const t = createTranslator({locale, messages});
-
- return {
- title: t('LocaleLayout.title')
- };
-}
-```
-
-
-
-
```tsx filename="app/[locale]/layout.tsx"
-import {getTranslator} from 'next-intl/server';
+import {getTranslations} from 'next-intl/server';
export async function generateMetadata({params: {locale}}) {
- const t = await getTranslator(locale, 'Metadata');
+ const t = await getTranslations({locale, namespace: 'Metadata'});
return {
title: t('title')
@@ -98,74 +27,40 @@ export async function generateMetadata({params: {locale}}) {
}
```
-
-
+
+ By passing an explicit `locale` to the awaitable functions from `next-intl`,
+ you can make the metadata handler eligable for [static
+ rendering](/docs/getting-started/app-router#static-rendering).
+
### Metadata files
If you need to internationalize content within [metadata files](https://nextjs.org/docs/app/api-reference/file-conventions/metadata), such as an Open Graph image, you can call APIs from `next-intl` in the exported function.
-
-
-
-```tsx filename="app/[locale]/opengraph-image.tsx"
-import {ImageResponse} from 'next/og';
-import {createTranslator} from 'next-intl';
-
-export default async function Image({params: {locale}}) {
- const messages = (await import(`../../../messages/${locale}.json`)).default;
- const t = createTranslator({locale, messages, namespace: 'OpenGraph'});
-
- return new ImageResponse(
{t('title')}
);
-}
-```
-
-
-
-
```tsx filename="app/[locale]/opengraph-image.tsx"
import {ImageResponse} from 'next/og';
-import {getTranslator} from 'next-intl/server';
+import {getTranslations} from 'next-intl/server';
-export default async function Image({params: {locale}}) {
- const t = await getTranslator(locale, 'OpenGraph');
+export default async function OpenGraphImage({params: {locale}}) {
+ const t = await getTranslations({locale, namespace: 'OpenGraphImage'});
return new ImageResponse(
{t('title')}
);
}
```
-
-
-
### Route Handlers
-If you put [Route Handlers](https://nextjs.org/docs/app/building-your-application/routing/router-handlers) in your `[locale]` folder, you can use functionality from `next-intl` to localize the response based on the `locale`. Note however that you should make sure that the route is matched by your [middleware `config`](/docs/routing/middleware).
+You can use `next-intl` in [Route Handlers](https://nextjs.org/docs/app/building-your-application/routing/router-handlers) too. The `locale` can either be received from a search param, a layout segment or by parsing the `accept-language` header of the request.
-
-
-
-```tsx filename="app/[locale]/hello/route.tsx"
+```tsx filename="app/api/hello/route.tsx"
import {NextResponse} from 'next/server';
-import {createTranslator} from 'next-intl';
+import {getTranslations} from 'next-intl/server';
-export async function GET(request, {params: {locale}}) {
- const messages = (await import(`../../../messages/${locale}.json`)).default;
- const t = createTranslator({locale, messages, namespace: 'Hello'});
- return NextResponse.json({title: t('title')});
-}
-```
-
-
-
+export async function GET(request) {
+ // Example: Receive the `locale` via a search param
+ const {searchParams} = new URL(request.url);
+ const locale = searchParams.get('locale');
-```tsx filename="app/[locale]/hello/route.tsx"
-import {NextResponse} from 'next/server';
-import {getTranslator} from 'next-intl/server';
-
-export async function GET(request, {params: {locale}}) {
- const t = await getTranslator(locale, 'Hello');
+ const t = await getTranslations({locale, namespace: 'Hello'});
return NextResponse.json({title: t('title')});
}
```
-
-
-
diff --git a/docs/pages/docs/production-checklist.mdx b/docs/pages/docs/environments/runtime-requirements.mdx
similarity index 60%
rename from docs/pages/docs/production-checklist.mdx
rename to docs/pages/docs/environments/runtime-requirements.mdx
index b01c483c9..5930e873d 100644
--- a/docs/pages/docs/production-checklist.mdx
+++ b/docs/pages/docs/environments/runtime-requirements.mdx
@@ -1,20 +1,12 @@
import Callout from 'components/Callout';
-# Production checklist
+# Runtime requirements
-While the [installation instructions](/docs/getting-started) are sufficient to use `next-intl` in your Next.js app, this checklist helps you ensure you're all set for production:
+## Browser
-1. If you're using TypeScript, you can take advantage of autocompletion and type safety for message keys by [setting up a type for your messages](/docs/workflows/typescript).
-2. If you're formatting dates and times, a [time zone should be configured](/docs/configuration#time-zone). By default, dates are formatted according to the time zone of the environment, which can lead to markup mismatches if the server and the user are located in different time zones. By supplying the `timeZone` explicitly, you can ensure that dates and times are rendered the same way on the server as well as the client.
-3. If you're formatting relative dates and times, a [global value for `now`](/docs/configuration#global-now-value) can be useful. This ensures that the server and client will render the same markup. Especially if you use caching for the responses of the server, the likelyhood of mismatches increases.
-4. To achieve consistent date, time and number formatting, it might be useful to set up [global formats](/docs/configuration#formats) which ensure consistent formatting across the app.
-5. Please check the [runtime requirements](#runtime-requirements) and optionally provide polyfills.
+The source code of `next-intl` is compiled for [the same browsers that Next.js supports](https://nextjs.org/docs/architecture/supported-browsers).
-## Runtime requirements
-
-### Browser
-
-Based on the features you're using, you have to make sure your browser supports the following APIs:
+Based on the features you're using, you have to make sure your target browsers support the following APIs:
- Basic usage: `Intl.Locale` ([compatibility](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/Locale#browser_compatibility))
- Date & time formatting: `Intl.DateTimeFormat` ([compatibility](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#browser_compatibility))
@@ -31,7 +23,6 @@ If you target a browser that doesn't support all the required APIs, consider usi
import {useLocale} from 'next-intl';
import Script from 'next/script';
-// Use this component in your Next.js custom `_app`
function IntlPolyfills() {
const locale = useLocale();
@@ -68,6 +59,6 @@ function IntlPolyfills() {
(e.g. search for `Intl.DateTimeFormat.~locale.de-AT`).
-### Node
+## Node
-The minimum required version is **Node.js 13**. Starting from this version, all required APIs are available.
+The minimum version to support all relevant `Intl` APIs is **Node.js 13**. Starting from this version, all required APIs are available.
diff --git a/docs/pages/docs/environments/server-client-components.mdx b/docs/pages/docs/environments/server-client-components.mdx
index 4ab06f28a..c94e1caa5 100644
--- a/docs/pages/docs/environments/server-client-components.mdx
+++ b/docs/pages/docs/environments/server-client-components.mdx
@@ -1,14 +1,8 @@
import Callout from 'components/Callout';
-# Internationalization of Server & Client Components in Next.js 13
+# Internationalization of Server & Client Components
-
-
-This page contains background information about the advantages of moving internationalization to Server Components. Note that this is currently only available in [the Server Components beta version](/docs/getting-started/app-router-server-components).
-
-
-
-With the introduction of the App Router in Next.js 13, [React Server Components](https://nextjs.org/docs/getting-started/react-essentials) became publicly available. This new paradigm allows components that don’t require React’s interactive features, such as `useState` and `useEffect`, to remain server-side only.
+[React Server Components](https://nextjs.org/docs/app/building-your-application/rendering/server-components) allow you to implement components that remain server-side only if they don’t require React’s interactive features, such as `useState` and `useEffect`.
This applies to handling internationalization too.
@@ -16,7 +10,7 @@ This applies to handling internationalization too.
import {useTranslations} from 'next-intl';
// Since this component doesn't use any interactive features
-// from React, it can be implemented as a Server Component.
+// from React, it can be run as a Server Component.
export default function Index() {
const t = useTranslations('Index');
@@ -24,28 +18,106 @@ export default function Index() {
}
```
-## Benefits of handling i18n in Server Components [#server-components-benefits]
-
Moving internationalization to the server side unlocks new levels of performance, leaving the client side for interactive features.
-
-
-
- Your messages never leave the server and don't need to be serialized for
- the client side
-
-
- Library code for internationalization doesn't need to be loaded on the
- client side
-
-
No need to split your messages, e.g. based on routes or components
-
No runtime cost on the client side
-
- No need to handle environment differences like different time zones on the
- server and client
-
-
-
+**Benefits of server-side internationalization:**
+
+1. Your messages never leave the server and don't need to be serialized for 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
+5. No need to handle environment differences like different time zones on the server and client
+
+## Using internationalization in Server Components
+
+Server Components can be declared in two ways:
+
+1. Async components
+2. Non-async, regular components
+
+In a typical app, you'll likely find both types of components. `next-intl` provides corresponding APIs that work for the given component type.
+
+### Async components
+
+These are primarly concerned with fetching data and [can not use hooks](https://github.com/reactjs/rfcs/blob/main/text/0188-server-components.md#capabilities--constraints-of-server-and-client-components). Due to this, `next-intl` provides a set of awaitable versions of the functions that you usually call as hooks from within components.
+
+```tsx filename="[locale]/profile/page.tsx"
+import {getTranslations} from 'next-intl/server';
+
+export default async function ProfilePage() {
+ const user = await fetchUser();
+ const t = await getTranslations('ProfilePage');
+
+ return (
+
+
+
+ );
+}
+```
+
+These functions are available:
+
+```tsx
+import {
+ getTranslations,
+ getFormatter,
+ getNow,
+ getTimeZone,
+ getMessages,
+ getLocale
+} from 'next-intl/server';
+
+const t = await getTranslations('ProfilePage');
+const format = await getFormatter();
+const now = await getNow();
+const timeZone = await getTimeZone();
+const messages = await getMessages();
+const locale = await getLocale();
+```
+
+### Non-async components [#shared-components]
+
+Components that aren't declared with the `async` keyword and don't use interactive features like `useState`, are referred to as [shared components](https://github.com/reactjs/rfcs/blob/main/text/0188-server-components.md#sharing-code-between-server-and-client). These can render either as a Server or Client Component, depending on where they are imported from.
+
+In Next.js, Server Components are the default, and therefore shared components will typically execute as Server Components.
+
+```tsx filename="UserDetails.tsx"
+import {useTranslations} from 'next-intl';
+
+export default function UserDetails({user}) {
+ const t = useTranslations('UserProfile');
+
+ return (
+
+
{t('title')}
+
{t('followers', {count: user.numFollowers})}
+
+ );
+}
+```
+
+If you import `useTranslations`, `useFormatter`, `useLocale`, `useNow` and `useTimeZone` from a shared component, `next-intl` will automatically provide an implementation that works best for the environment this component executes in (server or client).
+
+
+How does the Server Components integration work?
+
+`next-intl` uses [`react-server` conditional exports](https://github.com/reactjs/rfcs/blob/main/text/0227-server-module-conventions.md#react-server-conditional-exports) to load code that is optimized for the usage in Server or Client Components. While configuration for hooks like `useTranslations` is read via `useContext` on the client side, on the server side it is loaded via [`i18n.ts`](/docs/usage/configuration#i18nts).
+
+Hooks are currently primarly known for being used in Client Components since they are typically stateful or don't apply to a server environment. However, hooks like [`useId`](https://react.dev/reference/react/useId) can be used in Server Components too. Similarly, `next-intl` provides a hooks-based API that looks identical, regardless of if it's used in a Server or Client Component.
+
+The one restriction that currently comes with this pattern is that hooks can not be called from `async` components. `next-intl` therefore provides a separate set of [awaitable APIs](#async-components) for this use case.
+
+
+
+
+Should I use async or non-async functions for my components?
+
+If you implement components that qualify as shared components, it can be beneficial to implement them as non-async functions. This allows to use these components either in a server or client environment, making them really flexible. Even if you don't intend to to ever run a particular component on the client side, this compatibility can still be helpful, e.g. for simplified testing.
+
+However, there's no need to dogmatically use non-async functions exclusively for handling internationalization—use what fits your app best.
+
+
## Using internationalization in Client Components
@@ -60,7 +132,10 @@ import {useTranslations} from 'next-intl';
import Expandable from './Expandable';
export default function FAQEntry() {
+ // Call `useTranslations` in a Server Component ...
const t = useTranslations('FAQEntry');
+
+ // ... and pass translated content to a Client Component
return (
@@ -92,7 +167,7 @@ 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.
-Learn more in the Next.js docs: [Nesting Server Components inside Client Components](https://nextjs.org/docs/getting-started/react-essentials#nesting-server-components-inside-client-components)
+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)
### Option 2: Moving state to the server side
@@ -127,18 +202,15 @@ If you need to incorporate dynamic state that can not be moved to the server sid
```tsx filename="Counter.tsx"
import pick from 'lodash/pick';
-import {useLocale, NextIntlClientProvider} from 'next-intl';
+import {NextIntlClientProvider} from 'next-intl';
import ClientCounter from './ClientCounter';
export default function Counter() {
- const locale = useLocale();
-
// Receive messages provided in `i18n.ts`
const messages = useMessages();
return (
- `NextIntlClientProvider` doesn't automatically inherit configuration from
- `i18n.ts`, therefore make sure to provide all relevant props on the component.
- If you're configuring non-serializable values like functions, you have to mark
- the component that renders `NextIntlClientProvider` with `'use client'`
- ([example](https://codesandbox.io/p/sandbox/next-intl-non-serializable-props-on-nextintlclientprovider-r0h2hi?file=%2Fsrc%2Fapp%2F%5Blocale%5D%2FNextIntlProvider.tsx)).
+ `NextIntlClientProvider` inherits the props `locale`, `now` and `timeZone`
+ when the component is rendered from a Server Component. Other configuration
+ properties like `messages` and `formats` can be provided as necessary.
### Option 4: Providing all messages
@@ -169,6 +239,8 @@ import {NextIntlClientProvider} from 'next-intl';
import {notFound} from 'next/navigation';
export default async function LocaleLayout({children, params: {locale}}) {
+ // ...
+
// Receive messages provided in `i18n.ts`
const messages = useMessages();
@@ -185,13 +257,13 @@ export default async function LocaleLayout({children, params: {locale}}) {
```
- Note that this is a tradeoff in regard to performance (see [the bullet points
- above](#server-components-benefits)).
+ Note that this is a tradeoff in regard to performance (see the bullet points
+ at the top of this page).
## Troubleshooting
-### "Failed to call `useTranslations`, because the context from `NextIntlClientProvider` was not found." [#missing-context]
+### "Failed to call `useTranslations` because the context from `NextIntlClientProvider` was not found." [#missing-context]
You might encounter this error or a similar one referencing `useFormatter` while working on your app.
@@ -199,3 +271,48 @@ This can happen because:
1. The component that calls the hook accidentally ended up in a client-side module graph, but you expected it to render as a Server Component. If this is the case, try to [pass this component via `children`](#option-1-passing-translations-to-client-components) to the Client Component instead.
2. You're intentionally calling the hook from a Client Component, but `NextIntlClientProvider` is not present as an ancestor in the component tree. If this is the case, you can [wrap your component in `NextIntlClientProvider`](#option-3-providing-individual-messages) to resolve this error.
+
+### "Functions cannot be passed directly to Client Components because they're not serializable." [#non-serializable-props]
+
+You might encounter this error when you try to pass a non-serializable prop to `NextIntlClientProvider`.
+
+The component accepts the following props that are not serializable:
+
+1. [`onError`](/docs/usage/configuration#error-handling)
+2. [`getMessageFallback`](/docs/usage/configuration#error-handling)
+3. Rich text elements for [`defaultTranslationValues`](/docs/usage/configuration#default-translation-values)
+
+To configure these, you can wrap `NextIntlClientProvider` with another component that is marked with `'use client'` and defines the relevant props:
+
+```tsx filename="MyCustomNextIntlClientProvider.tsx"
+'use client';
+
+import {NextIntlClientProvider} from 'next-intl';
+
+export default function MyCustomNextIntlClientProvider({
+ locale,
+ timeZone,
+ now,
+ ...rest
+}) {
+ return (
+ {text}
+ }}
+ // Make sure to forward these props to avoid markup mismatches
+ locale={locale}
+ timeZone={timeZone}
+ now={now}
+ {...props}
+ />
+ );
+}
+```
+
+([working example](https://codesandbox.io/p/sandbox/next-intl-non-serializable-props-on-nextintlclientprovider-r0h2hi?file=%2Fsrc%2Fapp%2F%5Blocale%5D%2FNextIntlProvider.tsx))
+
+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.
diff --git a/docs/pages/docs/faq.mdx b/docs/pages/docs/faq.mdx
index f5b2e51c6..7017b6e4e 100644
--- a/docs/pages/docs/faq.mdx
+++ b/docs/pages/docs/faq.mdx
@@ -2,7 +2,7 @@
## What trade-offs does this library make?
-If you bundle `next-intl` for the client side ([which you don't have to](/docs/environments/server-client-components)), the bundle size comes in at [~14kB gzipped](https://bundlephobia.com/result?p=next-intl) which is the tradeoff that's necessary for supporting all the mentioned internationalization features. There are smaller libraries for internationalization, but they typically cover less features.
+If you bundle `next-intl` for the client side ([which you don't have to](/docs/environments/server-client-components)), the bundle size comes in at [~14kB gzipped](https://bundlephobia.com/result?p=next-intl) which is the tradeoff that's necessary for supporting all the included i18n features. There are smaller libraries for internationalization, but they typically cover less features.
If you don't need all features of `next-intl`, you can reduce the bundle size with tree shaking. Furthermore if [native `Intl.MessageFormat`](https://github.com/tc39/proposal-intl-messageformat) lands in JavaScript in the future, we might be able to reduce the bundle size significantly.
@@ -10,7 +10,7 @@ Generally, it's recommended to [handle internationalization in Server Components
## How is this library different from using react-intl?
-1. This library offers tight integration with Next.js, e.g. through [the routing integration](/docs/routing).
+1. This library offers tight integration with Next.js, e.g. through [the routing integration](/docs/routing) and Server Components support.
2. This library offers a hooks-only API for message consumption. The reason for this is that the same API can be used for attributes as well as `children`.
3. This library is built around the concept of namespaces and that components consume a single namespace—however this is not required.
4. This library currently doesn't support AST-based extraction like `react-intl`. Note that building ASTs for messages upfront can help with runtime performance, but in exchange your bundle size might grow.
@@ -33,4 +33,4 @@ Yes, see [the Remix example](https://github.com/amannn/next-intl/tree/main/examp
## Can `next-intl` be used with [React Native](https://reactnative.dev/)?
-Yes, see [the React Native example](https://github.com/amannn/next-intl/tree/main/examples/example-react-native).
+Yes, see [the React Native example](https://github.com/amannn/next-intl/tree/main/examples/example-react-native). Please double check that [the runtime requirements](/docs/environments/runtime-requirements) are fulfilled.
diff --git a/docs/pages/docs/getting-started/_meta.json b/docs/pages/docs/getting-started/_meta.json
index fe6fbc321..dda40d626 100644
--- a/docs/pages/docs/getting-started/_meta.json
+++ b/docs/pages/docs/getting-started/_meta.json
@@ -1,6 +1,5 @@
{
"index": "Welcome!",
- "app-router-client-components": "App Router (Client Components)",
- "app-router-server-components": "App Router (Server Components)",
+ "app-router": "App Router",
"pages-router": "Pages Router"
}
diff --git a/docs/pages/docs/getting-started/app-router-client-components.mdx b/docs/pages/docs/getting-started/app-router-client-components.mdx
deleted file mode 100644
index db3f44866..000000000
--- a/docs/pages/docs/getting-started/app-router-client-components.mdx
+++ /dev/null
@@ -1,137 +0,0 @@
-import Callout from 'components/Callout';
-import Steps from 'components/Steps';
-
-# Next.js 13: Internationalization (i18n) in Client Components
-
-Next.js 13 introduces support for [React Server Components](https://nextjs.org/docs/getting-started/react-essentials) with the App Router. While [support for Server Components in `next-intl`](/docs/getting-started/app-router-server-components) is on the horizon, you can use `next-intl` in the `app` directory by deferring the usage of internationalization to Client Components.
-
-## Getting started
-
-If you haven't done so already, [create a Next.js 13 app that uses the `app` directory](https://nextjs.org/docs/getting-started/installation). The goal is to prefix all routes with the `locale`, so that we can retrieve it as a [dynamic segment](https://nextjs.org/docs/app/building-your-application/routing/defining-routes#creating-routes) and use it to configure `next-intl`.
-
-**Start by running `npm install next-intl` and create the following file structure:**
-
-```
-├── messages (1)
-│ ├── en.json
-│ └── ...
-├── middleware.ts (2)
-└── app
- └── [locale]
- ├── layout.tsx (3)
- └── page.tsx (4)
-```
-
-**Now, set up these files as follows:**
-
-
-
-### `messages/en.json`
-
-Messages can be provided locally or loaded from a remote data source (e.g. a translation management system). Use whatever suits your workflow best.
-
-The simplest option is to create JSON files locally based on locales, e.g. `en.json`.
-
-```json filename="messages/en.json"
-{
- "Index": {
- "title": "Hello world!"
- }
-}
-```
-
-### `middleware.ts`
-
-[The middleware](/docs/routing/middleware) matches a locale for the request and handles redirects and rewrites accordingly.
-
-```tsx filename="middleware.ts"
-import createMiddleware from 'next-intl/middleware';
-
-export default createMiddleware({
- // A list of all locales that are supported
- locales: ['en', 'de'],
-
- // If this locale is matched, pathnames work without a prefix (e.g. `/about`)
- defaultLocale: 'en'
-});
-
-export const config = {
- // Skip all paths that should not be internationalized. This example skips
- // certain folders and all pathnames with a dot (e.g. favicon.ico)
- matcher: ['/((?!api|_next|_vercel|.*\\..*).*)']
-};
-```
-
-**Note:** If you have pages that contain the character `.` in the pathname (e.g. `/users/jane.doe`), you might want to consider them in your [matcher config](/docs/routing/middleware#matcher-config).
-
-### `app/[locale]/layout.tsx` [#next-intl-client-provider]
-
-Provide the document layout and set up `NextIntlClientProvider`.
-
-```tsx filename="app/[locale]/layout.tsx" /NextIntlClientProvider/
-import {NextIntlClientProvider} from 'next-intl';
-import {notFound} from 'next/navigation';
-
-export function generateStaticParams() {
- return [{locale: 'en'}, {locale: 'de'}];
-}
-
-export default async function LocaleLayout({children, params: {locale}}) {
- let messages;
- try {
- messages = (await import(`../../messages/${locale}.json`)).default;
- } catch (error) {
- notFound();
- }
-
- return (
-
-
-
- {children}
-
-
-
- );
-}
-```
-
-### `app/[locale]/page.tsx` [#usage]
-
-Turn your page component into a Client Component to be able to use translations.
-
-```tsx filename="app/[locale]/page.tsx"
-'use client';
-
-import {useTranslations} from 'next-intl';
-
-export default function Index() {
- const t = useTranslations('Index');
- return
{t('title')}
;
-}
-```
-
-
-
-That's all you need to do to start using translations in the `app` directory!
-
-Note that you have to mark all components that use features from `next-intl` as Client Components if you use this approach. Support for `next-intl` APIs in Server Components is [available in a beta version](/docs/getting-started/app-router-server-components).
-
-
-
-**Next steps:**
-
-
-
- Ran into an issue? Have a look at [the App Router
- example](https://next-intl-example-next-13.vercel.app)
- ([source](https://github.com/amannn/next-intl/tree/main/examples/example-next-13)).
-
-
Exploring `next-intl`? Check out the [usage guide](/docs/usage).
-
- Decided you're sticking with `next-intl`? Consider the steps of the
- [checklist for production](/docs/production-checklist).
-
-
-
-
diff --git a/docs/pages/docs/getting-started/app-router-server-components.mdx b/docs/pages/docs/getting-started/app-router-server-components.mdx
deleted file mode 100644
index 36b704aba..000000000
--- a/docs/pages/docs/getting-started/app-router-server-components.mdx
+++ /dev/null
@@ -1,260 +0,0 @@
-import Callout from 'components/Callout';
-import Steps from 'components/Steps';
-
-# Next.js 13: Internationalization (i18n) in Server Components
-
-Next.js 13 introduces support for [React Server Components](https://nextjs.org/docs/getting-started/react-essentials) with the App Router and unlocks [many benefits](/docs/environments/server-client-components) when handling internationalization entirely on the server side. `next-intl` is adopting the new capabilities and is currently offering a preview version to early adopters, who are already building apps with Server Components.
-
-
- This page covers a getting started guide for the React Server Components beta that was running over the last couple of months. It's recommended to try out the [next-intl 3.0 release candidate](/blog/next-intl-3-0) if you'd like to use `next-intl` in Server Components.
-
-
-## Current version
-
-```
-npm install next-intl@3.0.0-beta.19
-```
-
-This version was tested with `next@13.5.1`.
-
-## Roadmap
-
-| Feature | Status |
-| :------------------------------------------------- | :----: |
-| Usage of all `next-intl` APIs in Server Components | ✅ |
-| Dynamic rendering | ✅ |
-| Static rendering (i.e. `generateStaticParams`) | 🏗️ |
-
-
- Support for static rendering is currently available via a stopgap solution
- (see [static rendering](#static-rendering)).
-
-
-## Getting started
-
-If you haven't done so already, [create a Next.js 13 app that uses the App Router](https://nextjs.org/docs/getting-started/installation). All pages should be moved within a `[locale]` folder so that we can use this segment to provide content in different languages (e.g. `/en`, `/en/about`, etc.).
-
-**Start by running `npm install next-intl` and create the following file structure:**
-
-```
-├── messages (1)
-│ ├── en.json
-│ └── ...
-├── i18n.ts (2)
-├── next.config.js (3)
-├── middleware.ts (4)
-└── app
- └── [locale]
- ├── layout.tsx (5)
- └── page.tsx (6)
-```
-
-**Now, set up the files as follows:**
-
-
-
-### `messages/en.json`
-
-Messages can be provided locally or loaded from a remote data source (e.g. a translation management system). Use whatever suits your workflow best.
-
-The simplest option is to create JSON files locally based on locales, e.g. `en.json`.
-
-```json filename="messages/en.json"
-{
- "Index": {
- "title": "Hello world!"
- }
-}
-```
-
-### `i18n.ts`
-
-`next-intl` creates a configuration once per request and makes it available to all Server Components. Here you can provide messages and other options depending the locale of the user.
-
-```tsx filename="i18n.ts"
-import {getRequestConfig} from 'next-intl/server';
-
-export default getRequestConfig(async ({locale}) => ({
- messages: (await import(`./messages/${locale}.json`)).default
-}));
-```
-
-### `next.config.js`
-
-Now, set up the plugin and provide the path to your `i18n.ts` file.
-
-```js filename="next.config.js"
-const withNextIntl = require('next-intl/plugin')(
- // This is the default (also the `src` folder is supported out of the box)
- './i18n.ts'
-);
-
-module.exports = withNextIntl({
- // Other Next.js configuration ...
-});
-```
-
-### `middleware.ts`
-
-[The middleware](/docs/routing/middleware) matches a locale for the request and handles redirects and rewrites accordingly.
-
-```tsx filename="middleware.ts"
-import createMiddleware from 'next-intl/middleware';
-
-export default createMiddleware({
- // A list of all locales that are supported
- locales: ['en', 'de'],
-
- // If this locale is matched, pathnames work without a prefix (e.g. `/about`)
- defaultLocale: 'en'
-});
-
-export const config = {
- // Skip all paths that should not be internationalized. This example skips
- // certain folders and all pathnames with a dot (e.g. favicon.ico)
- matcher: ['/((?!api|_next|_vercel|.*\\..*).*)']
-};
-```
-
-**Note:** If you have pages that contain the character `.` in the pathname (e.g. `/users/jane.doe`), you might want to consider them in your [matcher config](/docs/routing/middleware#matcher-config).
-
-### `app/[locale]/layout.tsx`
-
-The `locale` that was matched by the middleware is available via the `locale` param and can be used to configure the document language.
-
-```tsx filename="app/[locale]/layout.tsx"
-import {useLocale} from 'next-intl';
-import {notFound} from 'next/navigation';
-
-const locales = ['en', 'de'];
-
-export default function LocaleLayout({children, params: {locale}}) {
- // Validate that the incoming `locale` parameter is valid
- const isValidLocale = locales.some((cur) => cur === locale);
- if (!isValidLocale) notFound();
-
- return (
-
- {children}
-
- );
-}
-```
-
-### `app/[locale]/page.tsx`
-
-Use translations in your page components or anywhere else!
-
-```tsx filename="app/[locale]/page.tsx"
-import {useTranslations} from 'next-intl';
-
-export default function Index() {
- const t = useTranslations('Index');
- return
{t('title')}
;
-}
-```
-
-
-
-That's all it takes! Now you can internationalize your apps on the server side.
-
-
-
-**Next steps:**
-
-
-
- Ran into an issue? Have a look at [the Server Components
- example](https://next-intl-example-next-13-git-feat-next-13-rsc-amannn.vercel.app/)
- ([source](https://github.com/amannn/next-intl/tree/feat/next-13-rsc/examples/example-next-13)).
-
-
Exploring `next-intl`? Check out the [usage guide](/docs/usage).
-
- Decided you're sticking with `next-intl`? Consider the steps of the
- [checklist for production](/docs/production-checklist).
-
-
- Interested to learn more about the advantages of using `next-intl` in Server
- Components? Check out the [Server & Client Components
- guide](/docs/environments/server-client-components).
-
-
- Are you transitioning from the `pages` directory to `app`? Check out the
- [migration
- example](https://github.com/amannn/next-intl/tree/feat/next-13-rsc/examples/example-next-13-with-pages).
-
-
-
-
-
-## Static rendering
-
-By using APIs like `useTranslations` from `next-intl` in Server Components, your pages will currently opt into dynamic rendering. This is a limitation that we aim to remove in the future, but as a stopgap solution, `next-intl` provides a temporary API that can be used to enable static rendering:
-
-
-
-### Add `generateStaticParams` to `app/[locale]/layout.tsx`
-
-Since we use a dynamic route segment for the `[locale]` param, we need to provide all possible values via [`generateStaticParams`](https://nextjs.org/docs/app/api-reference/functions/generate-static-params) to Next.js, so the routes can be rendered at build time.
-
-```tsx filename="app/[locale]/layout.tsx"
-const locales = ['en', 'de'];
-
-export function generateStaticParams() {
- return locales.map((locale) => ({locale}));
-}
-```
-
-### Add `unstable_setRequestLocale` to all layouts and pages
-
-`next-intl` provides a temporary API that can be used to distribute the locale that is received via `params` in a layout or page for usage in all Server Components that are rendered as part of the request.
-
-```tsx filename="app/[locale]/layout.tsx"
-import {unstable_setRequestLocale} from 'next-intl/server';
-
-const locales = ['en', 'de'];
-
-export default async function LocaleLayout({
- children,
- params: {locale}
-}) {
- // Validate that the incoming `locale` parameter is valid
- const isValidLocale = locales.some((cur) => cur === locale);
- if (!isValidLocale) notFound();
-
- unstable_setRequestLocale(locale);
-
- return (
- // ...
- );
-}
-```
-
-```tsx filename="app/[locale]/page.tsx"
-import {unstable_setRequestLocale} from 'next-intl/server';
-import {locales} from '..';
-
-export default async function IndexPage({
- params: {locale}
-}) {
- unstable_setRequestLocale(locale);
-
- return (
- // ...
- );
-}
-```
-
-**What does "unstable" mean?**
-
-`unstable_setRequestLocale` is meant to be used as a stopgap solution and we aim to remove it in the future [in case Next.js adds an API to access parts of the URL](https://github.com/facebook/react/pull/27424#issuecomment-1739464985). If that's the case, you'll get a deprecation notice in a minor version and the API will be removed as part of a major version.
-
-Note that Next.js can render layouts and pages indepently. This means that e.g. when you navigate from `/settings/profile` to `/settings/privacy`, the `/settings` segment might not re-render as part of the request. Due to this, it's important that `unstable_setRequestLocale` is called not only in the parent `settings/layout.tsx`, but also in the individual pages `profile/page.tsx` and `privacy/page.tsx`.
-
-That being said, the API is expected to work reliably if you're cautious to apply it in all relevant places.
-
-
-
-## Providing feedback
-
-If you have feedback about using `next-intl` in the `app` directory, feel free to leave feedback in [the PR that implements the React Server Components support](https://github.com/amannn/next-intl/pull/149).
diff --git a/docs/pages/docs/getting-started/app-router.mdx b/docs/pages/docs/getting-started/app-router.mdx
new file mode 100644
index 000000000..00d531226
--- /dev/null
+++ b/docs/pages/docs/getting-started/app-router.mdx
@@ -0,0 +1,295 @@
+import Callout from 'components/Callout';
+import Steps from 'components/Steps';
+
+# Next.js App Router Internationalization (i18n)
+
+The Next.js App Router introduces support for [React Server Components](https://nextjs.org/docs/app/building-your-application/rendering/server-components) and unlocks [many benefits](/docs/environments/server-client-components) when handling internationalization on the server side.
+
+## Getting started
+
+If you haven't done so already, [create a Next.js app that uses the App Router](https://nextjs.org/docs/getting-started/installation).
+
+`next-intl` integrates with the App Router, by using a `[locale]` [dynamic segment](https://nextjs.org/docs/pages/building-your-application/routing/dynamic-routes) so that we can use this segment to provide content in different languages (e.g. `/en`, `/en/about`, etc.).
+
+Let's get started!
+
+**Run `npm install next-intl` and create the following file structure:**
+
+```
+├── messages (1)
+│ ├── en.json
+│ └── ...
+├── next.config.js (2)
+└── src
+ ├── i18n.ts (3)
+ ├── middleware.ts (4)
+ └── app
+ └── [locale]
+ ├── layout.tsx (5)
+ └── page.tsx (6)
+```
+
+**Now, set up the files as follows:**
+
+
+
+### `messages/en.json`
+
+Messages can be provided locally or loaded from a remote data source (e.g. a translation management system). Use whatever suits your workflow best.
+
+The simplest option is to add JSON files in your project based on locales, e.g. `en.json`.
+
+```json filename="messages/en.json"
+{
+ "Index": {
+ "title": "Hello world!"
+ }
+}
+```
+
+### `next.config.js`
+
+Now, set up the plugin which creates an alias to import your i18n configuration (specified in the next step) into Server Components.
+
+```js filename="next.config.js"
+const withNextIntl = require('next-intl/plugin')();
+
+module.exports = withNextIntl({
+ // Other Next.js configuration ...
+});
+```
+
+### `src/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.
+
+```tsx filename="src/i18n.ts"
+import {getRequestConfig} from 'next-intl/server';
+
+export default getRequestConfig(async ({locale}) => ({
+ messages: (await import(`../messages/${locale}.json`)).default
+}));
+```
+
+
+ Can I move this file somewhere else?
+
+This file is supported out-of-the-box both in the `src` folder as well as in the project root with the extensions `.ts`, `.tsx`, `.js` and `.jsx`.
+
+If you prefer to move this file somewhere else, you can provide an optional path to the plugin:
+
+```js filename="next.config.js"
+const withNextIntl = require('next-intl/plugin')(
+ // Specify a custom path here
+ './somewhere/else/i18n.ts'
+);
+
+module.exports = withNextIntl({
+ // Other Next.js configuration ...
+});
+```
+
+
+
+### `middleware.ts`
+
+The middleware matches a locale for the request and handles redirects and rewrites accordingly.
+
+```tsx filename="middleware.ts"
+import createMiddleware from 'next-intl/middleware';
+
+export default createMiddleware({
+ // A list of all locales that are supported
+ locales: ['en', 'de'],
+
+ // Used when no locale matches
+ defaultLocale: 'en'
+});
+
+export const config = {
+ // Match only internationalized pathnames
+ matcher: ['/', '/(de|en)/:path*']
+};
+```
+
+### `app/[locale]/layout.tsx`
+
+The `locale` that was matched by the middleware is available via the `locale` param and can be used to configure the document language.
+
+```tsx filename="app/[locale]/layout.tsx"
+import {notFound} from 'next/navigation';
+
+// Can be imported from a shared config
+const locales = ['en', 'de'];
+
+export default function LocaleLayout({children, params: {locale}}) {
+ // Validate that the incoming `locale` parameter is valid
+ if (!locales.includes(locale as any)) notFound();
+
+ return (
+
+ {children}
+
+ );
+}
+```
+
+### `app/[locale]/page.tsx`
+
+Use translations in your page components or anywhere else!
+
+```tsx filename="app/[locale]/page.tsx"
+import {useTranslations} from 'next-intl';
+
+export default function Index() {
+ const t = useTranslations('Index');
+ return
{t('title')}
;
+}
+```
+
+
+
+That's all it takes!
+
+
+
+**Next steps:**
+
+
+
Exploring `next-intl`? Check out the [usage guide](/docs/usage).
+
+ Ran into an issue? Have a look at [the App Router
+ example](https://next-intl-example-next-13.vercel.app/).
+
+
+ Want to learn more about about using translations across the server and
+ client? Check out [the Server & Client Components
+ guide](/docs/environments/server-client-components).
+
+
+ Wondering how to link between internationalized pages? Have a look at [the
+ navigation docs](/docs/routing/navigation).
+
+
+ Are you transitioning from the Pages to the App Router? Check out [the
+ migration example](/examples/app-router-migration).
+
+
+
+
+
+## Static rendering
+
+By using APIs like `useTranslations` from `next-intl` in Server Components, your pages will currently opt into [dynamic rendering](https://nextjs.org/docs/app/building-your-application/rendering/server-components#dynamic-rendering). This is a limitation that we aim to remove in the future, but as a stopgap solution, `next-intl` provides a temporary API that can be used to enable static rendering:
+
+
+
+### Add `generateStaticParams` to `app/[locale]/layout.tsx`
+
+Since we use a dynamic route segment for the `[locale]` param, we need to provide all possible values via [`generateStaticParams`](https://nextjs.org/docs/app/api-reference/functions/generate-static-params) to Next.js, so the routes can be rendered at build time.
+
+```tsx filename="app/[locale]/layout.tsx"
+const locales = ['en', 'de'];
+
+export function generateStaticParams() {
+ return locales.map((locale) => ({locale}));
+}
+```
+
+### Add `unstable_setRequestLocale` to all layouts and pages
+
+`next-intl` provides a temporary API that can be used to distribute the locale that is received via `params` in a layout or page for usage in all Server Components that are rendered as part of the request.
+
+```tsx filename="app/[locale]/layout.tsx"
+import {unstable_setRequestLocale} from 'next-intl/server';
+
+const locales = ['en', 'de'];
+
+export default async function LocaleLayout({
+ children,
+ params: {locale}
+}) {
+ // Validate that the incoming `locale` parameter is valid
+ if (!locales.includes(locale as any)) notFound();
+
+ unstable_setRequestLocale(locale);
+
+ return (
+ // ...
+ );
+}
+```
+
+```tsx filename="app/[locale]/page.tsx"
+import {unstable_setRequestLocale} from 'next-intl/server';
+import {locales} from '..';
+
+export default function IndexPage({
+ params: {locale}
+}) {
+ unstable_setRequestLocale(locale);
+
+ // Once the request locale is set, you
+ // can call hooks from `next-intl`
+ const t = useTranslations('IndexPage');
+
+ return (
+ // ...
+ );
+}
+```
+
+**Important:** `unstable_setRequestLocale` needs to be called after the `locale` is validated, but before you call any hooks from `next-intl`. Otherwise, you'll get an error when trying to prerender the page.
+
+Note that Next.js can render layouts and pages indepently. This means that e.g. when you navigate from `/settings/profile` to `/settings/privacy`, the `/settings` segment might not re-render as part of the request. Due to this, it's important that `unstable_setRequestLocale` is called not only in the parent `settings/layout.tsx`, but also in the individual pages `profile/page.tsx` and `privacy/page.tsx`.
+
+
+What does "unstable" mean?
+
+`unstable_setRequestLocale` is meant to be used as a stopgap solution and we aim to remove it in the future [in case Next.js adds an API to access parts of the URL](https://github.com/facebook/react/pull/27424#issuecomment-1739464985). If that's the case, you'll get a deprecation notice in a minor version and the API will be removed as part of a major version.
+
+That being said, the API is expected to work reliably if you're cautious to apply it in all relevant places.
+
+
+
+
+How does unstable_setRequestLocale work?
+
+`next-intl` uses [`cache()`](https://react.dev/reference/react/cache) to create a mutable store that holds the current locale. By calling `unstable_setRequestLocale`, the current locale will be written to the store, making it available to all APIs that require the locale.
+
+Note that the store is scoped to a request and therefore doesn't affect other requests that might be handled in parallel while a given request resolves asynchronously.
+
+
+
+
+Why is this API necessary?
+
+Next.js currently doesn't provide an API to read route params like `locale` at arbitrary places in Server Components. The `locale` is fundamental to all APIs provided by `next-intl`, therefore passing this as a prop throughout the tree doesn't stand out as particularly ergnomic.
+
+Due to this, `next-intl` uses its middleware to attach an `x-next-intl-locale` header to the incoming request, holding the negotiated locale as a value. This technique allows the locale to be read at arbitrary places via `headers().get('x-next-intl-locale')`.
+
+However, the usage of `headers` opts the route into dynamic rendering.
+
+By using `unstable_setRequestLocale`, you can provide the locale that is received in layouts and pages via `params` to `next-intl`. By doing this, all APIs from `next-intl` can now read from this value instead of the header, enabling static rendering.
+
+
+
+### Use the `locale` param in metadata
+
+In addition to the rendering of your pages, also page metadata needs to qualify for static rendering.
+
+To achieve this, you can forward the `locale` that you receive from Next.js via `params` to [the awaitable functions from `next-intl`](/docs/environments/server-client-components#async-components).
+
+```tsx filename="page.tsx"
+import {getTranslations} from 'next-intl/server';
+
+export async function generateMetadata({params: {locale}}) {
+ const t = await getTranslations({locale, namespace: 'Metadata'});
+
+ return {
+ title: t('title')
+ };
+}
+```
+
+
diff --git a/docs/pages/docs/getting-started/index.mdx b/docs/pages/docs/getting-started/index.mdx
index 261a3a63e..4d593c55e 100644
--- a/docs/pages/docs/getting-started/index.mdx
+++ b/docs/pages/docs/getting-started/index.mdx
@@ -13,35 +13,13 @@ Welcome to the `next-intl` docs!
In this guide you will learn how to set up internationalization (i18n) in your Next.js app.
-With **Next.js 13**, the [App Router](https://nextjs.org/docs/app) was introduced and announced as stable with version 13.4. Following the lead of Next.js, `next-intl` now also recommends this paradigm going forward.
-
-
- Support for using all `next-intl` APIs in Server Components is currently in
- beta. If you're fine with using internationalization in Client Components, you
- can use the latest stable release of `next-intl`, but if you're an early
- adopter, you may be interested in the Server Components beta.
-
+With **Next.js 13**, the [App Router](https://nextjs.org/docs/app) along with support for React Server Components was introduced and announced as stable with version 13.4. Following the lead of Next.js, `next-intl` also recommends this paradigm since it increases both simplicity as well as flexibility when it comes to i18n.
}
- title={
-
- Usage with the App Router and Client Components
- Stable
-
- }
- href="/docs/getting-started/app-router-client-components"
- />
- }
- title={
-
- Usage with the App Router and Server Components
- Beta
-
- }
- href="/docs/getting-started/app-router-server-components"
+ title="Usage with the App Router"
+ href="/docs/getting-started/app-router"
/>
}
diff --git a/docs/pages/docs/getting-started/pages-router.mdx b/docs/pages/docs/getting-started/pages-router.mdx
index ba9647505..bc1346933 100644
--- a/docs/pages/docs/getting-started/pages-router.mdx
+++ b/docs/pages/docs/getting-started/pages-router.mdx
@@ -1,9 +1,9 @@
import {Tab, Tabs} from 'nextra-theme-docs';
import Callout from 'components/Callout';
-# Next.js 13 internationalization (i18n) with the Pages Router
+# Next.js internationalization (i18n) with the Pages Router
-While it's recommended to [use `next-intl` with the App Router](/docs/getting-started), the Pages Router is still fully supported.
+While it's recommended to [use `next-intl` with the App Router](/docs/getting-started/app-router), the Pages Router is still fully supported.
@@ -13,10 +13,16 @@ While it's recommended to [use `next-intl` with the App Router](/docs/getting-st
```jsx filename="_app.tsx" /NextIntlClientProvider/
import {NextIntlClientProvider} from 'next-intl';
+import {useRouter} from 'next/router';
export default function App({Component, pageProps}) {
+ const router = useRouter();
+
return (
-
+
);
@@ -87,14 +93,15 @@ export async function getStaticProps() {
Exploring `next-intl`? Check out the [usage guide](/docs/usage).
-
- Decided you're sticking with `next-intl`? Consider the steps of the
- [checklist for production](/docs/production-checklist).
-
Ran into an issue? Have a look at [the Pages Router
example](/examples/pages-router) to explore a working app.
+
+ Are you migrating to the App Router? See [the migration
+ example](https://github.com/amannn/next-intl/tree/main/examples/example-next-13-with-pages)
+ that uses both the App Router as well as the Pages router.
+
diff --git a/docs/pages/docs/routing/index.mdx b/docs/pages/docs/routing/index.mdx
index 042d8ee18..e14a39abf 100644
--- a/docs/pages/docs/routing/index.mdx
+++ b/docs/pages/docs/routing/index.mdx
@@ -3,7 +3,7 @@ import {Card} from 'nextra-theme-docs';
# Internationalized routing
-When you provide content in multiple languages, you want to make the content available under distinct URLs (e.g. `/en/about`). `next-intl` provides the building blocks to set up internationalized routing as well as the navigation APIs to enable you to link between pages.
+When you provide content in multiple languages, you want to make your pages available under distinct pathnames (e.g. `/en/about`). `next-intl` provides the building blocks to set up internationalized routing as well as the navigation APIs to enable you to link between pages.
Switch to English` to allow the user to change the locale to `en`.
-4. When the user clicks on the link, a request to `/en` is initiated.
-5. The middleware will update the cookie value to `en` and subsequently redirects the user to `/`.
+1. A user requests `/` and based on the `accept-language` header, the `en` locale is matched.
+2. The `en` locale is saved in a cookie and the user is redirected to `/en`.
+3. The app renders `Switch to German` to allow the user to change the locale to `de`.
+4. When the user clicks on the link, a request to `/de` is initiated.
+5. The middleware will update the cookie value to `de`.
+
+
+ You can optionally remove the locale prefix in pathnames by changing the
+ [`localePrefix`](#locale-prefix) setting.
+
### Strategy 2: Domain-based routing [#domain-based-routing]
If you want to serve your localized content based on different domains, you can provide a list of mappings between domains and locales to the middleware.
-**Example:**
+**Examples:**
-- `us.example.com` (default: `en`)
-- `ca.example.com` (default: `en`)
-- `ca.example.com/fr` (`fr`)
+- `us.example.com/en`
+- `ca.example.com/en`
+- `ca.example.com/fr`
```tsx filename="middleware.ts"
import createMiddleware from 'next-intl/middleware';
@@ -101,7 +99,10 @@ export default createMiddleware({
});
```
-The middleware rewrites the requests internally, so that requests for the `defaultLocale` on a given domain work without a locale prefix (e.g. `us.example.com/about` → `/en/about`). If you want to include a prefix for the default locale as well, you can add [`localePrefix: 'always'`](#always-use-a-locale-prefix).
+
+ You can optionally remove the locale prefix in pathnames by changing the
+ [`localePrefix`](#locale-prefix) setting.
+
#### Locale detection
@@ -113,12 +114,6 @@ The locale is detected based on these priorities:
2. If the host of the request is configured in `domains`, the `defaultLocale` of the domain is used
3. As a fallback, the [locale detection of prefix-based routing](#locale-detection) applies
-
-
-Since unknown domains will be handled with [prefix-based routing](#prefix-based-routing), this strategy can be used for local development where the host is `localhost`.
-
-
-
Since the middleware is aware of all your domains, the domain will automatically be switched when the user requests to change the locale.
**Example workflow:**
@@ -128,11 +123,26 @@ Since the middleware is aware of all your domains, the domain will automatically
3. When the link is clicked, a request to `us.example.com/fr` is initiated.
4. The middleware recognizes that the user wants to switch to another domain and responds with a redirect to `ca.example.com/fr`.
+
+How is the best matching domain for a given locale detected?
+
+The bestmatching domain is detected based on these priorities:
+
+1. Stay on the current domain if the locale is supported here
+2. Use an alternative domain where the locale is configured as the `defaultLocale`
+3. Use an alternative domain where the available `locales` are restricted and the locale is supported
+4. Stay on the current domain if it supports all locales
+5. Use an alternative domain that supports all locales
+
+
+
## Further configuration
-### Always use a locale prefix
+### Locale prefix
-If you want to include a prefix for the default locale as well, you can configure the middleware accordingly.
+#### Always use a locale prefix [#locale-prefix-always]
+
+By default, pathnames always start with the locale (e.g. `/en/about`).
```tsx filename="middleware.ts" {6}
import createMiddleware from 'next-intl/middleware';
@@ -140,15 +150,37 @@ import createMiddleware from 'next-intl/middleware';
export default createMiddleware({
// ... other config
- localePrefix: 'always'
+ localePrefix: 'always' // This is the default
});
```
-In this case, requests without a prefix will be redirected accordingly (e.g. `/about` to `/en/about`).
+#### Don't use a locale prefix for the default locale [#locale-prefix-as-needed]
-Note that this will affect both prefix-based as well as domain-based routing.
+If you don't want to include a locale prefix for the default locale, but only for non-default locales, you can configure the middleware accordingly.
-### Never use a locale prefix
+```tsx filename="middleware.ts" {6}
+import createMiddleware from 'next-intl/middleware';
+
+export default createMiddleware({
+ // ... other config
+
+ localePrefix: 'as-needed'
+});
+```
+
+In this case, requests where the locale prefix matches the default locale will be redirected (e.g. `/en/about` to `/about`). This will affect both prefix-based as well as domain-based routing.
+
+**Important:** If you use this strategy, you should make sure that [your matcher detects unprefixed pathnames](#matcher-no-prefix).
+
+
+ If you use [the `Link` component](/docs/routing/navigation#link), the initial
+ render will point to the prefixed version but will be patched immediately on
+ the client once the component detects that the default locale has rendered.
+ The prefixed version is still valid, but SEO tools might report a hint that
+ the link points to a redirect.
+
+
+#### Never use a locale prefix [#locale-prefix-never]
For applications behind an authentication layer, where there is no need for SEO, it is possible to have the locale never show up in the URL.
@@ -162,9 +194,9 @@ export default createMiddleware({
});
```
-In this case all requests for all locales will be rewritten to have the locale
-prefixed internally. You still need to place all your pages inside a
-`[locale]` folder for the routes to be able to receive the `locale` param.
+In this case, requests for all locales will be rewritten to have the locale only prefixed internally. You still need to place all your pages inside a `[locale]` folder for the routes to be able to receive the `locale` param.
+
+**Important:** If you use this strategy, you should make sure that [your matcher detects unprefixed pathnames](#matcher-no-prefix).
Note that [alternate links](#disable-alternate-links) are disabled in this
@@ -205,11 +237,6 @@ export default createMiddleware({
### Localizing pathnames
-
- This API is only available in [the Server Components
- beta](/docs/getting-started/app-router-server-components).
-
-
Many apps choose to localize pathnames, especially when search engine optimization is relevant, e.g.:
- `/en/about`
@@ -269,20 +296,30 @@ Because of this, the following config is generally recommended:
```tsx filename="middleware.ts"
export const config = {
- // Skip all paths that should not be internationalized. This example skips
- // certain folders and all pathnames with a dot (e.g. favicon.ico)
- matcher: ['/((?!api|_next|_vercel|.*\\..*).*)']
+ // Match only internationalized pathnames
+ matcher: ['/', '/(de|en)/:path*']
};
```
-However, this can lead to false negatives if you have pages that contain the character `.` (e.g. `/users/jane.doe`). To make sure these are processed by the middleware, you can add corresponding entries to the matcher config:
+This enables:
+
+1. A redirect at `/` to a suitable locale
+2. Internationalization of all pathnames starting with a locale (e.g. `/en/about`)
+
+#### Pathnames without a locale prefix [#matcher-no-prefix]
+
+If you pick a config for [`localePrefix`](#locale-prefix) other than [`always`](#locale-prefix-always), you need a more flexible matcher since you have to match pathnames without a locale prefix as well (e.g. `/about`).
+
+A popular strategy is to match all routes that don't start with certain segments (e.g. `/_next`) and also none that include a dot (`.`) since these typically are static files. However, if you have some routes where a dot is expected (e.g. `/users/jane.doe`), you should explicitly provide a matcher for these.
```tsx filename="middleware.ts"
export const config = {
// Matcher entries are linked with a logical "or", therefore
// if one of them matches, the middleware will be invoked.
matcher: [
- // Match all pathnames without `.`
+ // Match all pathnames except for
+ // - … if they start with `/api`, `/_next` or `/_vercel`
+ // - … the ones containing a dot (e.g. `favicon.ico`)
'/((?!api|_next|_vercel|.*\\..*).*)',
// Match all pathnames within `/users`, optionally with a locale prefix
'/(.+)?/users/(.+)'
@@ -292,7 +329,7 @@ export const config = {
{/* Keep this in sync with `packages/next-intl/test/middleware/middleware.test.tsx` */}
-Additionally, some third-party providers like [Vercel Analytics](https://vercel.com/analytics) and [umami](https://umami.is/docs/running-on-vercel) typically use internal endpoints that are then rewritten to an external URL (e.g. `/_vercel/insights/view`). Make sure to exclude such requests from your middleware matcher so they aren't accidentally rewritten.
+Note that some third-party providers like [Vercel Analytics](https://vercel.com/analytics) and [umami](https://umami.is/docs/running-on-vercel) typically use internal endpoints that are then rewritten to an external URL (e.g. `/_vercel/insights/view`). Make sure to exclude such requests from your middleware matcher so they aren't accidentally rewritten.
## Composing other middlewares
@@ -309,30 +346,66 @@ import createIntlMiddleware from 'next-intl/middleware';
import {NextRequest} from 'next/server';
export default async function middleware(request: NextRequest) {
- // Step 1: Use the incoming request
+ // Step 1: Use the incoming request (example)
const defaultLocale = request.headers.get('x-default-locale') || 'en';
- // Step 2: Create and call the next-intl middleware
+ // Step 2: Create and call the next-intl middleware (example)
const handleI18nRouting = createIntlMiddleware({
locales: ['en', 'de'],
defaultLocale
});
const response = handleI18nRouting(request);
- // Step 3: Alter the response
+ // Step 3: Alter the response (example)
response.headers.set('x-default-locale', defaultLocale);
return response;
}
export const config = {
- matcher: ['/((?!_next|.*\\..*).*)']
+ // Match only internationalized pathnames
+ matcher: ['/', '/(de|en)/:path*']
+};
+```
+
+### Example: Additional rewrites
+
+If you need to handle rewrites apart from the ones provided by `next-intl`, you can adjust the `pathname` of the `request` before invoking the `next-intl` middleware (based on ["A/B Testing with Cookies" by Vercel](https://vercel.com/templates/next.js/cookies)).
+
+This example rewrites requests for `/[locale]/profile` to `/[locale]/profile/new` if a special cookie is set.
+
+```tsx filename="middleware.ts"
+import createIntlMiddleware from 'next-intl/middleware';
+import {NextRequest} from 'next/server';
+
+export default async function middleware(request: NextRequest) {
+ const [, locale, pathname] = request.nextUrl.pathname.split('/');
+
+ if (pathname === 'profile') {
+ const usesNewProfile =
+ (request.cookies.get('NEW_PROFILE')?.value || 'false') === 'true';
+
+ if (usesNewProfile) {
+ request.nextUrl.pathname = `/${locale}/profile/new`;
+ }
+ }
+
+ const handleI18nRouting = createIntlMiddleware({
+ locales: ['en', 'de'],
+ defaultLocale: 'en'
+ });
+ const response = handleI18nRouting(request);
+ return response;
+}
+
+export const config = {
+ matcher: ['/', '/(de|en)/:path*']
};
```
### Example: Integrating with Clerk
-[`@clerk/nextjs`](https://clerk.com/docs/references/nextjs/overview) provides a middleware that can be integrated with `next-intl` by using [the `beforeAuth` hook](https://clerk.com/docs/references/nextjs/auth-middleware#using-before-auth-to-execute-middleware-before-authentication). By doing this, the middleware from `next-intl` will run first, potentially redirect or rewrite incoming requests, followed by the middleware from `@clerk/next` acting on the response.
+[`@clerk/nextjs`](https://clerk.com/docs/references/nextjs/overview) provides a middleware that can be integrated with `next-intl` by using the [`beforeAuth` hook](https://clerk.com/docs/references/nextjs/auth-middleware#using-before-auth-to-execute-middleware-before-authentication). By doing this, the middleware from `next-intl` will run first, potentially redirect or rewrite incoming requests, followed by the middleware from `@clerk/next` acting on the response.
```tsx filename="middleware.ts"
import {authMiddleware} from '@clerk/nextjs';
@@ -349,11 +422,12 @@ export default authMiddleware({
},
// Ensure that locale-specific sign in pages are public
- publicRoutes: ['/', '/:locale/sign-in']
+ publicRoutes: ['/:locale', '/:locale/sign-in']
});
export const config = {
- matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)']
+ // Match only internationalized pathnames
+ matcher: ['/', '/(de|en)/:path*']
};
```
@@ -373,6 +447,7 @@ const publicPages = ['/', '/login'];
const intlMiddleware = createIntlMiddleware({
locales,
+ localePrefix: 'as-necssary',
defaultLocale: 'en'
});
@@ -418,33 +493,28 @@ There's a working [example that combines `next-intl` with Auth.js](https://githu
-Many thanks to [narakhan](https://github.com/narakhan) for [sharing his middleware implementation](https://github.com/amannn/next-intl/pull/149#issuecomment-1509990635)!
-
## Usage without middleware (static export)
If you're using the [static export feature from Next.js](https://nextjs.org/docs/app/building-your-application/deploying/static-exports) (`output: 'export'`), the middleware will not run. You can use [prefix-based routing](#prefix-based-routing) nontheless to internationalize your app, but a few tradeoffs apply.
**Static export limitations:**
-1. There's no default locale that can be used without a prefix (same as [`localePrefix: 'always'`](#always-use-a-locale-prefix))
+1. There's no default locale that can be used without a prefix (same as [`localePrefix: 'always'`](#locale-prefix-always))
2. The locale can't be negotiated at runtime (same as [`localeDetection: false`](#disable-automatic-locale-detection))
3. You can't use [pathname localization](#localizing-pathnames)
-4. You need to add a redirect for the root of the app
-5. Currently this is limited to the usage of `next-intl` in [Client
- Components](/docs/getting-started/app-router-client-components) ([Server
- Components](/docs/getting-started/app-router-server-components) are not
- supported yet).
+4. This requires [static rendering](/docs/getting-started/app-router#static-rendering)
+5. You need to add a redirect for the root of the app
```tsx filename="app/page.tsx"
import {redirect} from 'next/navigation';
-// Redirect the user to the default locale when the app root is requested
+// Redirect the user to the default locale when `/` is requested
export default function RootPage() {
redirect('/en');
}
```
-You can explore a working demo by building [the Next.js 13 example](/examples/app-router) after enabling the static export:
+You can explore a working demo by building [the Next.js App Router example](/examples/app-router) after enabling the static export:
```tsx filename="next.config.js"
module.exports = {
@@ -458,18 +528,15 @@ module.exports = {
This can happen either because:
-1. The middleware was not set up
-2. The middleware was set up in the wrong file (e.g. you're using the `src` folder, but `middleware.ts` was added in the root folder)
-3. The middleware matcher didn't match a request, but you're using APIs from `next-intl` in a component
-
-To address the latter case, you should validate that only valid locales are handled by the `[locale]` layout segment since this effectively acts as a catch-all.
-
-**Example:**
-
-1. You use [the recommended matcher](#matcher-config) that excludes all pathnames with a dot (`.`)
-2. The user requests a pathname that includes a dot (e.g. `favicon.ico`)
-3. If there's no static asset available that matches the request, your layout renders with an invalid `locale` param (i.e. `params.locale === 'favicon.ico'`)
+1. The middleware is not set up.
+2. The middleware is set up in the wrong file (e.g. you're using the `src` folder, but `middleware.ts` was added in the root folder).
+3. The middleware matcher didn't match a request, but you're using APIs from `next-intl` in a component.
+4. You're attempting to implement static rendering via [`force-static`](https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic).
-In this case, you want to [interrupt the render and return a 404 status code](/docs/getting-started/app-router-server-components#applocalelayouttsx).
+To recover from this error, please make sure that:
-Note that if you expect certain pathnames to include a dot (e.g. `/users/jane.doe`), you should ensure that they are [matched by the middleware](#matcher-config).
+1. You've [set up the middleware](/docs/getting-started/app-router#middlewarets).
+2. You're using APIs from `next-intl` (including [the navigation APIs](/docs/routing/navigation)) exclusively within the `locale` segment.
+3. Your [middleware matcher](#matcher-config) matches all routes of your application, including dynamic segments with potentially unexpected characters like dots (e.g. `/users/jane.doe`).
+4. If you're using [`localePrefix: 'as-needed'`](#locale-prefix-as-needed), the `locale` segment effectively acts like a catch-all for all unknown routes. You should make sure that `params.locale` is [validated](/docs/getting-started/app-router#applocalelayouttsx) before it's used by any APIs from `next-intl`.
+5. To implement static rendering properly, make sure to [provide a static locale](/docs/getting-started/app-router#static-rendering) to `next-intl`.
diff --git a/docs/pages/docs/routing/navigation.mdx b/docs/pages/docs/routing/navigation.mdx
index 73b7f0f58..ff24b31c2 100644
--- a/docs/pages/docs/routing/navigation.mdx
+++ b/docs/pages/docs/routing/navigation.mdx
@@ -1,8 +1,7 @@
import Callout from 'components/Callout';
-import VersionTabs from 'components/VersionTabs';
import {Tab, Tabs} from 'nextra-theme-docs';
-# Next.js 13 internationalized navigation
+# Next.js App Router internationalized navigation
`next-intl` provides drop-in replacements for common Next.js navigation APIs that automatically handle the user locale behind the scenes.
@@ -17,47 +16,53 @@ For example:
- `/en/about`
- `/de/about`
-**Localized pathnames:** Many apps choose to localize pathnames however, especially when search engine optimization is relevant. In this case, you'll provide distinct pathnames based on the user locale.
+**Localized pathnames:** Many apps choose to localize pathnames, especially when search engine optimization is relevant. In this case, you'll provide distinct pathnames based on the user locale.
For example:
- `/en/about`
- `/de/ueber-uns`
+**Note:** The terms "shared" and "localized" pathnames are used to refer to pathnames that are created via the file-system based routing in Next.js. If you're using an external system like a CMS to localize pathnames, you'll typically implement this with a catch-all route like `[locale]/[[...slug]]`.
+
---
-Each strategy will provide you with corresponding [navigation APIs](#apis).
+Each strategy will provide you with corresponding [navigation APIs](#apis) that you'll typically provide in a central module to easily access them in components (e.g. `src/navigation.ts`).
### Strategy 1: Shared pathnames [#shared-pathnames]
With this strategy, the pathnames of your app are identical for all locales. This is the simplest case, because the routes you define in Next.js will map directly to the pathnames that a user can request.
-You can import all relevant [navigation APIs](#apis) directly from `next-intl`:
+To create [navigation APIs](#apis) for this strategy, use the `createSharedPathnamesNavigation` function:
-```tsx
-import Link from 'next-intl/link';
-import {usePathname, useRouter} from 'next-intl/client';
-import {redirect} from 'next-intl/server';
+```tsx filename="navigation.ts"
+import {createSharedPathnamesNavigation} from 'next-intl/navigation';
+
+export const locales = ['en', 'de'] as const;
+
+export const {Link, redirect, usePathname, useRouter} =
+ createSharedPathnamesNavigation({locales});
```
-
+The `locales` argument is identical to the configuration that you pass to the middleware. To reuse it there, you can import the `locales` into the middleware.
-There's [an open RFC](https://github.com/amannn/next-intl/issues/402) to move all navigation APIs to a single `next-intl/navigation` namespace, providing a factory function that provides strict typing for the supported locales. Leave a comment there if you have feedback!
+```tsx filename="middleware.ts"
+import createMiddleware from 'next-intl/middleware';
+import {locales} from './navigation';
-
+export default createMiddleware({
+ defaultLocale: 'en',
+ locales
+});
+```
### Strategy 2: Localized pathnames [#localized-pathnames]
-
- This API is only available in [the Server Components
- beta](/docs/getting-started/app-router-server-components).
-
-
When using this strategy, you have to provide distinct pathnames for every locale that your app supports. However, the localized variants will be handled by a single route internally, therefore a mapping needs to be provided that is also [consumed by the middleware](/docs/routing/middleware#localizing-pathnames).
-You can use the `createLocalizedPathnamesNavigation` function to create corresponding [navigation APIs](#apis) in a central module to easily access the returned functions in your components:
+You can use the `createLocalizedPathnamesNavigation` function to create corresponding [navigation APIs](#apis):
-```tsx filename="./navigation.ts"
+```tsx filename="navigation.ts"
import {
createLocalizedPathnamesNavigation,
Pathnames
@@ -111,10 +116,8 @@ export default createMiddleware({
```
- Have a look at the [App Router example with RSC
- support](https://github.com/amannn/next-intl/tree/feat/next-13-rsc/examples/example-next-13)
- to explore a working implementation of localized pathnames
- ([demo](https://next-intl-example-next-13-git-feat-next-13-rsc-next-intl.vercel.app/)).
+ Have a look at the [App Router example](/examples/app-router) to explore a
+ working implementation of localized pathnames.
## APIs
@@ -123,11 +126,11 @@ export default createMiddleware({
This component wraps [`next/link`](https://nextjs.org/docs/app/api-reference/components/link) and automatically prefixes the `href` with the current locale as necessary. If the default locale is matched, the `href` remains unchanged and no prefix is added.
-
+
```tsx
-import Link from 'next-intl/link';
+import {Link} from '../navigation';
// When the user is on `/en`, the link will point to `/en/about`
About
@@ -149,7 +152,7 @@ The [`useSelectedLayoutSegment` hook](https://nextjs.org/docs/app/api-reference/
import {useSelectedLayoutSegment} from 'next/navigation';
import {ComponentProps} from 'react';
-import Link from 'next-intl/link';
+import {Link} from '../navigation';
export default function NavigationLink({
href,
@@ -290,7 +293,7 @@ The [navigation APIs](#apis) are strictly typed and only allow routes specified
… or globally configure `createLocalizedPathnamesNavigation` to accept arbitrary strings too:
-```tsx filename="./navigation.ts"
+```tsx filename="navigation.ts"
// ...
export const {Link, redirect, usePathname, useRouter} =
@@ -303,7 +306,7 @@ export const {Link, redirect, usePathname, useRouter} =
-
+
How does prefetching of localized links work?
@@ -315,12 +318,12 @@ Just like `next/link`, by default all links are prefetched. The one exception to
If you need to navigate programmatically, e.g. in an event handler, `next-intl` provides a convience API that wraps [`useRouter` from Next.js](https://nextjs.org/docs/app/api-reference/functions/use-router) and automatically applies the locale of the user.
-
+
```tsx
'use client';
-import {useRouter} from 'next-intl/client';
+import {useRouter} from '../navigation';
const router = useRouter();
@@ -343,7 +346,7 @@ By combining [`usePathname`](#usepathname) with [`useRouter`](#userouter), you c
```tsx
'use client';
-import {usePathname, useRouter} from 'next-intl/client';
+import {usePathname, useRouter} from '../navigation';
const pathname = usePathname();
const router = useRouter();
@@ -414,19 +417,19 @@ router.replace(
-
+
### `usePathname`
To retrieve the pathname without a potential locale prefix, you can call `usePathname`.
-
+
```tsx
'use client';
-import {usePathname} from 'next-intl/client';
+import {usePathname} from '../navigation';
// When the user is on `/en`, this will be `/`
const pathname = usePathname();
@@ -449,22 +452,17 @@ const pathname = usePathname();
Note that internal pathnames are returned without params being resolved (e.g. `/users/[userId]`).
-
+
### `redirect`
-
- This API is only available in [the Server Components
- beta](/docs/getting-started/app-router-server-components).
-
-
If you want to interrupt the render and redirect to another page, you can invoke the `redirect` function. This wraps [the `redirect` function from Next.js](https://nextjs.org/docs/app/api-reference/functions/redirect) and automatically applies the current locale.
-
+
```tsx
-import {redirect} from 'next-intl/server';
+import {redirect} from '../navigation';
// When the user is on `/en`, this will be `/en/login`
redirect('/login');
@@ -498,19 +496,16 @@ redirect({
```
-
+
### `getPathname`
If you need to construct a particular pathname based on a locale, you can call the `getPathname` function. This can for example be useful to retrieve a [canonical link](https://nextjs.org/docs/app/api-reference/functions/generate-metadata#alternates) for a page that accepts search params.
-
+
-
- This API is only available for localized pathnames, since it is not necessary
- for shared pathnames.
-
+(This API is only available for localized pathnames, since it is not necessary for shared pathnames.)
@@ -534,4 +529,4 @@ export async function generateMetadata({params}) {
```
-
+
diff --git a/docs/pages/docs/usage/_meta.json b/docs/pages/docs/usage/_meta.json
index 26eb17591..0c46d39a1 100644
--- a/docs/pages/docs/usage/_meta.json
+++ b/docs/pages/docs/usage/_meta.json
@@ -3,5 +3,6 @@
"messages": "Messages",
"numbers": "Numbers",
"dates-times": "Dates and times",
- "lists": "Lists"
+ "lists": "Lists",
+ "configuration": "Global configuration"
}
diff --git a/docs/pages/docs/configuration.mdx b/docs/pages/docs/usage/configuration.mdx
similarity index 70%
rename from docs/pages/docs/configuration.mdx
rename to docs/pages/docs/usage/configuration.mdx
index 992a323a7..b942064da 100644
--- a/docs/pages/docs/configuration.mdx
+++ b/docs/pages/docs/usage/configuration.mdx
@@ -1,7 +1,6 @@
import PartnerContentLink from 'components/PartnerContentLink';
import Callout from 'components/Callout';
-import VersionTabs from 'components/VersionTabs';
-import {Tab} from 'nextra-theme-docs';
+import {Tab, Tabs} from 'nextra-theme-docs';
# Global configuration
@@ -9,10 +8,23 @@ Configuration properties that you use across your Next.js app can be set globall
## Client- and Server Components [#client-server-components]
-Depending on if you handle [internationalization in Client- or Server Components](/docs/environments/server-client-components), the configuration from `NextIntlClientProvider` or `i18n.ts` will be applied respectively.
+Depending on if you handle [internationalization in Client- or Server Components](/docs/environments/server-client-components), the configuration from `i18n.ts` or `NextIntlClientProvider` will be applied respectively.
-
-
+### `i18n.ts`
+
+`i18n.ts` can be used to provide configuration for **Server Components**.
+
+```tsx filename="i18n.ts"
+import {getRequestConfig} from 'next-intl/server';
+
+export default getRequestConfig(async ({locale}) => ({
+ messages: (await import(`../messages/${locale}.json`)).default
+}));
+```
+
+The configuration object is created once for each request by internally using React's [`cache`](https://nextjs.org/docs/app/building-your-application/data-fetching/caching#react-cache). The first component to use internationalization will call the function defined with `getRequestConfig`.
+
+### `NextIntlClientProvider`
`NextIntlClientProvider` can be used to provide configuration for **Client Components**.
@@ -31,7 +43,7 @@ export default async function LocaleLayout({children, params: {locale}}) {
return (
-
+
{children}
@@ -40,27 +52,9 @@ export default async function LocaleLayout({children, params: {locale}}) {
}
```
-
-
-
-
- This only applies if you're using [the Server Components beta](/docs/getting-started/app-router-server-components).
-
-
-`i18n.ts` can be used to provide configuration for **Server Components**.
-
-```tsx filename="i18n.ts"
-import {getRequestConfig} from 'next-intl/server';
-
-export default getRequestConfig(async ({locale}) => ({
- messages: (await import(`./messages/${locale}.json`)).default
-}));
-```
-
-The configuration object is created once for each request by internally using React's [`cache`](https://nextjs.org/docs/app/building-your-application/data-fetching/caching#react-cache). The first component to use internationalization will call the function defined with `getRequestConfig`.
-
-
-
+
+ `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.
+
## Messages
@@ -74,52 +68,38 @@ The most crucial aspect of internationalization is providing labels based on the
...
```
-
-
-
-```tsx filename="app/[locale]/layout.tsx" /NextIntlClientProvider/
-import {NextIntlClientProvider} from 'next-intl';
-import {notFound} from 'next/navigation';
-
-export default async function LocaleLayout({children, params: {locale}}) {
- let messages;
- try {
- messages = (await import(`../../messages/${locale}.json`)).default;
- } catch (error) {
- notFound();
- }
-
- return (
-
-
-
- {children}
-
-
-
- );
-}
-```
-
-
+
-
- This only applies if you're using [the Server Components beta](/docs/getting-started/app-router-server-components).
-
-
```tsx filename="i18n.ts"
import {getRequestConfig} from 'next-intl/server';
export default getRequestConfig(async ({locale}) => ({
- messages: (await import(`./messages/${locale}.json`)).default
+ messages: (await import(`../messages/${locale}.json`)).default
}));
```
-
+
+
+```tsx
+import {NextIntlClientProvider} from 'next-intl';
+
+// ...
+
+const messages = useMessages();
+
+return (
+
+ {children}
+
+);
+```
+
+
+
-Colocating your messages with app code is beneficial because it allows developers to make changes to messages quickly. Additionally, you can [use the shape of your local messages for type checking](/docs/workflows/typescript).
+Colocating your messages with app code is beneficial because it allows developers to make changes quickly. Additionally, you can [use the shape of your local messages for type checking](/docs/workflows/typescript).
Translators can collaborate on messages by using CI tools, such as Crowdin's GitHub integration, which allows changes to be synchronized directly into your code repository.
@@ -172,29 +152,11 @@ const messages = deepmerge(defaultMessages, userMessages);
## Time zone
-If possible, you should configure an explicit time zone, as this affects the rendering of dates and times. By default, the available time zone of the runtime will be used: In Node.js this is the time zone that is configured for the server and in the browser, this is the local time zone of the user. As the time zone of the server and the one from the user can differ, this can lead to markup mismatches when your app is both rendered on the server as well as on the client side.
-
-To ensure consistency, you can globally define a time zone:
-
-
-
-
-```tsx
-// The time zone can either be statically defined, read from the
-// user profile if you store such a setting, or based on dynamic
-// request information like the locale or headers.
-const timeZone = 'Europe/Vienna';
-
-...
-```
+Specifying a time zone affects the rendering of dates and times. By default, the time zone of the server runtime will be used, but can be customized as necessary.
-
+
-
- This only applies if you're using [the Server Components beta](/docs/getting-started/app-router-server-components).
-
-
```tsx filename="i18n.ts"
import {getRequestConfig} from 'next-intl/server';
@@ -208,34 +170,35 @@ export default getRequestConfig(async ({locale}) => ({
```
-
+
+
+```tsx
+// The time zone can either be statically defined, read from the
+// user profile if you store such a setting, or based on dynamic
+// request information like the locale or headers.
+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).
+
+The time zone in Client Components is automatically inherited from the server side if you wrap the relevant components in a `NextIntlClientProvider` that is rendered by a Server Component. For all other cases, you can specify the value explicitly.
+
+
## Now value [#now]
-To avoid mismatches between the server and client environment, it is recommended to configure a global value for `now`:
+When formatting [relative dates and times](/docs/usage/dates-times#formatting-relative-time), `next-intl` will format times in relation to a reference point in time that is referred to as "now". By default, this is the time a component renders.
-
-
-```tsx
-
-
-
-```
+If you prefer to override the default, you can provide an explicit value for `now`:
-
+
-
- This only applies if you're using [the Server Components beta](/docs/getting-started/app-router-server-components).
-
-
```tsx filename="i18n.ts"
import {getRequestConfig} from 'next-intl/server';
@@ -248,9 +211,16 @@ export default getRequestConfig(async ({locale}) => ({
```
-
+
+
+```tsx
+const now = new Date('2020-11-20T10:36:01.516Z');
-This value will be used as the default for [the `relativeTime` function](/docs/usage/dates-times#formatting-relative-time) as well as returned during the initial render of [`useNow`](/docs/usage/dates-times#usenow).
+...
+```
+
+
+
**Tip:** For consistent results in end-to-end tests, it can be helpful to mock this value to a constant value, e.g. based on an environment parameter.
@@ -262,16 +232,22 @@ This value will be used as the default for [the `relativeTime` function](/docs/u
[`useNow`](/docs/usage/dates-times#usenow) on the client side.
+
+Similarly to the `timeZone`, the `now` value in Client Components is automatically inherited from the server side if you wrap the relevant components in a `NextIntlClientProvider` that is rendered by a Server Component.
+
+
## Formats
To achieve consistent date, time, number and list formatting, you can define a set of global formats.
-
+
-```js
- ({
+ formats: {
dateTime: {
short: {
day: 'numeric',
@@ -290,23 +266,17 @@ To achieve consistent date, time, number and list formatting, you can define a s
type: 'conjunction'
}
}
- }}
->
-
-
+ },
+ // ...
+}));
```
+
-
-
- This only applies if you're using [the Server Components beta](/docs/getting-started/app-router-server-components).
-
-
-```tsx filename="i18n.ts"
-import {getRequestConfig} from 'next-intl/server';
-export default getRequestConfig(async ({locale}) => ({
- formats: {
+```tsx
+ ({
type: 'conjunction'
}
}
- },
- // ...
-}));
+ }}
+>
+ ...
+
```
+
-
+
+
+Usage in components:
-```js
+```tsx
import {useFormatter} from 'next-intl';
function Component() {
@@ -353,7 +327,7 @@ Global formats for numbers, dates and times can be referenced in messages too.
}
```
-```js
+```tsx
import {useTranslations} from 'next-intl';
function Component() {
@@ -368,26 +342,9 @@ function Component() {
To achieve consistent usage of translation values and reduce redundancy, you can define a set of global default values. This configuration can also be used to apply consistent styling of commonly used rich text elements.
-
-
-```js
-{chunks},
- value: 123
- }}
->
-
-
-```
-
+
-
- This only applies if you're using [the Server Components beta](/docs/getting-started/app-router-server-components).
-
-
```tsx filename="i18n.ts"
import {getRequestConfig} from 'next-intl/server';
@@ -399,10 +356,25 @@ export default getRequestConfig(async ({locale}) => ({
// ...
}));
```
+
-
+
-The defaults will be overridden by locally provided values.
+```tsx
+{chunks},
+ value: 123
+ }}
+>
+ ...
+
+```
+
+
+
+
+The defaults will be overridden if local formats are provided at a specific call site.
## Error handling
@@ -410,7 +382,37 @@ By default, when a message fails to resolve or when the formatting failed, an er
This behavior can be customized with the `onError` and `getMessageFallback` configuration option.
-
+
+
+
+```tsx filename="i18n.ts"
+import {getRequestConfig} from 'next-intl/server';
+import {IntlErrorCode} from 'next-intl';
+
+export default getRequestConfig(async ({locale}) => ({
+ onError(error) {
+ if (error.code === IntlErrorCode.MISSING_MESSAGE) {
+ // Missing translations are expected and should only log an error
+ console.error(error);
+ } else {
+ // Other errors indicate a bug in the app and should be reported
+ reportToErrorTracking(error);
+ }
+ },
+ getMessageFallback({namespace, key, error}) {
+ const path = [namespace, key].filter((part) => part != null).join('.');
+
+ if (error.code === IntlErrorCode.MISSING_MESSAGE) {
+ return path + ' is not yet translated';
+ } else {
+ return 'Dear developer, please fix this message: ' + path;
+ }
+ },
+ // ...
+}));
+```
+
+
```tsx
@@ -430,62 +432,54 @@ function getMessageFallback({namespace, key, error}) {
const path = [namespace, key].filter((part) => part != null).join('.');
if (error.code === IntlErrorCode.MISSING_MESSAGE) {
- return `${path} is not yet translated`;
+ return path + ' is not yet translated';
} else {
- return `Dear developer, please fix this message: ${path}`;
+ return 'Dear developer, please fix this message: ' + path;
}
}
-
-
+
+ ...
```
+
-
-
-
- This only applies if you're using [the Server Components beta](/docs/getting-started/app-router-server-components).
-
-
-```tsx filename="i18n.ts"
-import {getRequestConfig} from 'next-intl/server';
-import {IntlErrorCode} from 'next-intl';
+## Locale
-export default getRequestConfig(async ({locale}) => ({
- onError(error) {
- if (error.code === IntlErrorCode.MISSING_MESSAGE) {
- // Missing translations are expected and should only log an error
- console.error(error);
- } else {
- // Other errors indicate a bug in the app and should be reported
- reportToErrorTracking(error);
- }
- },
- getMessageFallback({namespace, key, error}) {
- const path = [namespace, key].filter((part) => part != null).join('.');
+`NextIntlClientProvider` requires a `locale` that it provides to all hooks from `next-intl`.
- if (error.code === IntlErrorCode.MISSING_MESSAGE) {
- return `${path} is not yet translated`;
- } else {
- return `Dear developer, please fix this message: ${path}`;
- }
+If you render `NextIntlClientProvider` from a Server Component, the `locale` will automatically be received.
+
+In all other cases, e.g. when being rendered from a Client Component, a unit test or within the Pages Router, you need to pass this prop explicitly. Be sure to also set a [`timeZone`](#time-zone) and a value for [`now`](#now) in this case to avoid potential markup mismatches.
+
+
+I'm using the Pages Router, how can I provide the locale?
+
+If you use [internationalized routing with the Pages Router](https://nextjs.org/docs/pages/building-your-application/routing/internationalization), you can receive the locale from the router in order to pass it to `NextIntlClientProvider`:
+
+```tsx filename="_app.tsx"
+import {useRouter} from 'next/router';
-},
// ...
-}));
+const router = useRouter();
+
+return (
+
+ ...
+ ;
+);
```
-
-
+
## Retrieve global configuration
As a convenience, there are a couple of hooks that allow you to read global configuration.
-```js
+```tsx
import {useLocale, useTimeZone, useMessages} from 'next-intl';
function Component() {
diff --git a/docs/pages/docs/usage/dates-times.mdx b/docs/pages/docs/usage/dates-times.mdx
index bae73797a..89f0e4e98 100644
--- a/docs/pages/docs/usage/dates-times.mdx
+++ b/docs/pages/docs/usage/dates-times.mdx
@@ -5,12 +5,6 @@ import PartnerContentLink from 'components/PartnerContentLink';
The formatting of dates and times varies greatly between locales (e.g. "Apr 24, 2023" in `en-US` vs. "24 квіт. 2023 р." in `uk-UA`). By using the formatting capabilities of `next-intl`, you can handle all i18n differences in your Next.js app automatically.
-
-
-If you're formatting dates and times, you should [set up a global time zone](/docs/configuration#time-zone).
-
-
-
## Formatting dates and times
You can format plain dates that are not part of a message with the `useFormatter` hook:
@@ -39,10 +33,10 @@ See [the MDN docs about `DateTimeFormat`](https://developer.mozilla.org/en-US/do
How can I parse dates or manipulate them?
-To parse dates, you can pass them to [the `Date` constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date).
-
Since `next-intl` is only concerned with formatting dates, you can use a library like [date-fns](https://date-fns.org/) to manipulate them.
+To parse dates, you can pass them to [the `Date` constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date).
+
```tsx
import {subDays} from 'date-fns';
@@ -89,7 +83,7 @@ function Component() {
}
```
-Supplying `now` is necessary for the function to return consistent results. If you have [configured a global value for `now` on the provider](/docs/configuration#global-now-value), you can omit the second argument:
+Supplying `now` is necessary for the function to return consistent results. If you have [configured a global value for `now`](/docs/usage/configuration#now), you can omit the second argument:
```js
format.relativeTime(dateTime);
@@ -192,5 +186,5 @@ t(
To reuse date and time formats for multiple components, you can configure
- [global formats](/docs/configuration#formats).
+ [global formats](/docs/usage/configuration#formats).
diff --git a/docs/pages/docs/usage/index.mdx b/docs/pages/docs/usage/index.mdx
index d4e852698..7a0c1d3b2 100644
--- a/docs/pages/docs/usage/index.mdx
+++ b/docs/pages/docs/usage/index.mdx
@@ -23,4 +23,3 @@ If you prefer a more hands-on approach to learning, you can alternatively explor
- [App Router example](/examples/app-router)
- [Pages Router example](/examples/pages-router)
-- [Pages Router (advanced) example](/examples/pages-router-advanced)
diff --git a/docs/pages/docs/usage/lists.mdx b/docs/pages/docs/usage/lists.mdx
index 010cb98ee..48e0fe30b 100644
--- a/docs/pages/docs/usage/lists.mdx
+++ b/docs/pages/docs/usage/lists.mdx
@@ -32,7 +32,7 @@ Note that lists can can currently only be formatted via `useFormatter`, there's
To reuse list formats for multiple components, you can configure [global
- formats](/docs/configuration#formats).
+ formats](/docs/usage/configuration#formats).
diff --git a/docs/pages/docs/usage/messages.mdx b/docs/pages/docs/usage/messages.mdx
index 46554fc6a..836fdb72d 100644
--- a/docs/pages/docs/usage/messages.mdx
+++ b/docs/pages/docs/usage/messages.mdx
@@ -7,12 +7,12 @@ The main part of handling internationalization (typically referred to as _i18n_)
## Terminology
-- **Locale**: We use this term to describe an identifier that contains the language and formatting preferences of users. Apart from the language, this includes optional regional information (e.g. `en-US`, `de`).
-- **Messages**: These are collections of namespace-label pairs that provide grouping by locale (e.g. `en-US.json`, `de.json`).
+- **Locale**: We use this term to describe an identifier that contains the language and formatting preferences of users. Apart from the language, a locale can include optional regional information (e.g. `en-US`). Locales are specified as [IETF BCP 47 language tags](https://en.wikipedia.org/wiki/IETF_language_tag).
+- **Messages**: These are collections of namespace-label pairs that are grouped by locale (e.g. `en-US.json`).
## Structuring messages
-To group your messages within a locale, it's recommended to use component names as namespaces and embrace them as the primary unit of code organization in your app.
+To group your messages within a locale, it's recommended to use component names as namespaces and embrace them as the primary unit of code organization in your app. You can of course also use a different structure, depending on what suits your app best.
```json filename="en.json"
{
@@ -22,9 +22,9 @@ To group your messages within a locale, it's recommended to use component names
}
```
-Now, you can render messages from within a React component via the `useTranslations` hook:
+You can render messages from within a React component via the `useTranslations` hook:
-```js filename="About.tsx"
+```tsx filename="About.tsx"
import {useTranslations} from 'next-intl';
function About() {
@@ -60,7 +60,7 @@ Optionally, you can structure your messages as nested objects.
}
```
-```js filename="SignUp.tsx"
+```tsx filename="SignUp.tsx"
import {useTranslations} from 'next-intl';
function SignUp() {
@@ -117,9 +117,7 @@ export default function useLocaleLabel() {
How can I use translations outside of components?
-`next-intl` only offers an API to use translations from within React components.
-
-This may seem like an unnecessary limitation, but this is intentional and aims to encourage the use of proven patterns that avoid potential issues—especially if they are easy to overlook.
+`next-intl` is heavily based on the `useTranslations` API which is intended to consume translations from within React components. This may seem like a limitation, but this is intentional and aims to encourage the use of proven patterns that avoid potential issues—especially if they are easy to overlook.
If you're interested to dive deeper into this topic, there's a blog post that discusses the background of this design decision: [How (not) to use translations outside of React components](/blog/translations-outside-of-react-components).
@@ -165,7 +163,7 @@ t('message', {name: 'Jane'}); // "Hello Jane!"
To express the pluralization of a given number of items, the `plural` argument can be used:
-```js filename="en.json"
+```json filename="en.json"
"message": "You have {count, plural, =0 {no followers yet} =1 {one follower} other {# followers}}."
```
@@ -203,7 +201,7 @@ To match a specific number, `next-intl` additionally supports the special `=valu
To apply pluralization based on an order of items, the `selectordinal` argument can be used:
-```js filename="en.json"
+```tsx filename="en.json"
"message": "It's your {year, selectordinal, one {#st} two {#nd} few {#rd} other {#th}} birthday!"
```
@@ -235,7 +233,7 @@ To match a specific number, `next-intl` additionally supports the special `=valu
To map identifiers to human readable labels, you can use the `select` argument:
-```js filename="en.json"
+```tsx filename="en.json"
"message": "{gender, select, female {She} male {He} other {They}} is online."
```
@@ -268,6 +266,7 @@ You can format rich text with custom tags and map them to React components:
```
```js
+// Returns `<>Please refer to the guidelines.>`
t.rich('message', {
guidelines: (chunks) => {chunks}
});
@@ -295,7 +294,7 @@ Messages can use tags without any chunks as children, but syntax-wise, a closing
```js
t.rich('message', {
- br: (chunks) =>
+ br: () =>
});
```
@@ -337,57 +336,85 @@ t.rich('message', {
-## Arrays of messages
+## HTML markup
-If you need to render a list of messages, the recommended approach is to map an array of keys to the corresponding messages:
+To render rich text, you typically want to use [rich text formatting](#rich-text). However, if you have a use case where you need to emit raw HTML markup, you can use the `t.markup` function:
```json filename="en.json"
{
- "Benefits": {
- "zero-config": "Works with zero config",
- "customizable": "Easy to customize",
- "fast": "Blazingly fast"
- }
+ "markup": "This is important"
}
```
-```js filename="Benefits.tsx"
-import {useTranslations} from 'next-intl';
+```js
+// Returns 'This is important'
+t.markup('markup', {
+ important: (chunks) => `${chunks}`
+});
+```
-function Benefits() {
- const t = useTranslations('Benefits');
- const keys = ['zero-config', 'customizable', 'fast'] as const;
+Note that unlike `t.rich`, the provided markup functions accept `chunks` as a `string` and also return a `string` where the `chunks` are wrapped accordingly.
- return (
-
- {keys.map((key) => (
-
{t(key)}
- ))}
-
- );
+## Raw messages
+
+Messages are always parsed and therefore e.g. for rich text formatting you need to supply the necessary tags. If you want to avoid the parsing, e.g. because you have raw HTML stored in a message, there's a separate API for this use case:
+
+```json filename="en.json"
+{
+ "content": "
Headline
This is raw HTML
"
}
```
-If the number of items varies between locales, you can solve this by using [rich text](#rich-text).
+```js
+
+```
+
+
+ **Important**: You should always sanitize the content that you pass to
+ [`dangerouslySetInnerHTML`](https://react.dev/reference/react-dom/components/common#dangerously-setting-the-inner-html)
+ to avoid cross-site scripting attacks.
+
+
+The value of a raw message can be any valid JSON value: strings, booleans, objects and arrays.
+
+## Arrays of messages
+
+If you need to render a list of messages, the recommended approach is to map an array of keys to the corresponding messages:
```json filename="en.json"
{
- "Benefits": {
- "items": "Works with zero configEasy to customizeBlazingly fast"
+ "CompanyStats": {
+ "yearsOfService": {
+ "title": "Years of service",
+ "value": "34"
+ },
+ "happyClients": {
+ "title": "Happy clients",
+ "value": "1.000+"
+ },
+ "partners": {
+ "title": "Products",
+ "value": "5.000+"
+ }
}
}
```
-```js filename="Benefits.tsx"
+```tsx filename="CompanyStats.tsx"
import {useTranslations} from 'next-intl';
-function Benefits() {
- const t = useTranslations('Benefits');
+function CompanyStats() {
+ const t = useTranslations('CompanyStats');
+ const keys = ['yearsOfService', 'happyClients', 'partners'] as const;
+
return (
- {t.rich('items', {
- item: (chunks) =>
{chunks}
- })}
+ {keys.map((key) => (
+
+
{t(`${key}.title`)}
+
{t(`${key}.value`)}
+
+ ))}
);
}
@@ -408,7 +435,7 @@ The advantage of this approach over supporting arrays in messages is that this w
}
```
-```js filename="Benefits.tsx"
+```tsx filename="Benefits.tsx"
import {useTranslations} from 'next-intl';
function Benefits() {
@@ -430,28 +457,6 @@ function Benefits() {
-## Raw messages
-
-Messages are always parsed and therefore e.g. for rich text you need to supply the necessary tags. If you want to avoid the parsing, e.g. because you have raw HTML stored in a message, there's a separate API for this use case:
-
-```json filename="en.json"
-{
- "content": "
Headline
This is raw HTML
"
-}
-```
-
-```js
-
-```
-
-
- **Important**: You should always sanitize the content that you pass to
- [`dangerouslySetInnerHTML`](https://react.dev/reference/react-dom/components/common#dangerously-setting-the-inner-html)
- to avoid cross-site scripting attacks.
-
-
-The value of a raw message can be any valid JSON value: strings, booleans, objects and arrays.
-
## Right-to-left languages
Languages such as Arabic, Hebrew and Persian use [right-to-left script](https://en.wikipedia.org/wiki/Right-to-left_script) (often abbreviated as RTL). For these languages, writing begins on the right side of the page and continues to the left.
diff --git a/docs/pages/docs/usage/numbers.mdx b/docs/pages/docs/usage/numbers.mdx
index 4353dc9de..a4561713e 100644
--- a/docs/pages/docs/usage/numbers.mdx
+++ b/docs/pages/docs/usage/numbers.mdx
@@ -77,6 +77,6 @@ t(
-To reuse number formats for multiple components, you can configure [global formats](/docs/configuration#formats).
+To reuse number formats for multiple components, you can configure [global formats](/docs/usage/configuration#formats).
diff --git a/docs/pages/docs/workflows/localization-management.mdx b/docs/pages/docs/workflows/localization-management.mdx
index 2c25233f9..ea8855b0c 100644
--- a/docs/pages/docs/workflows/localization-management.mdx
+++ b/docs/pages/docs/workflows/localization-management.mdx
@@ -83,7 +83,7 @@ You can further simplify the process for translators by setting up
-
-
-
Your interface is called `IntlMessages`.
-
You're using TypeScript version 4 or later.
-
The path of your `import` is correct.
-
Your type declaration file is included in `tsconfig.json`.
-
- Your editor has loaded the most recent type declarations. When in doubt, you
- can restart.
-
-
-
-
+**If you're encountering problems, please double check that:**
+
+1. Your interface is called `IntlMessages`.
+2. You're using TypeScript version 4 or later.
+3. The path of your `import` is correct.
+4. Your type declaration file is included in `tsconfig.json`.
+5. Your editor has loaded the most recent type declarations. When in doubt, you can restart.
diff --git a/docs/pages/examples/_meta.json b/docs/pages/examples/_meta.json
index 405167b90..4a83ca6f4 100644
--- a/docs/pages/examples/_meta.json
+++ b/docs/pages/examples/_meta.json
@@ -7,8 +7,8 @@
"title": "Pages Router",
"theme": {"layout": "full"}
},
- "pages-router-advanced": {
- "title": "Pages Router (advanced)",
+ "app-router-migration": {
+ "title": "App Router migration",
"theme": {"layout": "full"}
}
}
diff --git a/docs/pages/examples/pages-router-advanced.mdx b/docs/pages/examples/app-router-migration.mdx
similarity index 54%
rename from docs/pages/examples/pages-router-advanced.mdx
rename to docs/pages/examples/app-router-migration.mdx
index 8fc9e6fea..ede2e4996 100644
--- a/docs/pages/examples/pages-router-advanced.mdx
+++ b/docs/pages/examples/app-router-migration.mdx
@@ -1,11 +1,11 @@
---
-title: Pages Router (advanced)
+title: App Router Migration
full: true
---
import CodeSandbox from 'components/CodeSandbox';
diff --git a/docs/pages/index.mdx b/docs/pages/index.mdx
index a57afbc53..ddaa08c90 100644
--- a/docs/pages/index.mdx
+++ b/docs/pages/index.mdx
@@ -21,7 +21,7 @@ import UserTestimonial from 'components/UserTestimonial';
description="Support multiple languages, with your app code becoming simpler instead of more complex."
getStarted="Get started"
viewExample="View an example"
- rscAnnouncement="Support for Next.js 13 and the App Router has arrived →"
+ rscAnnouncement="Support for Server Components has arrived →"
/>
@@ -75,7 +75,7 @@ import UserTestimonial from 'components/UserTestimonial';
diff --git a/docs/theme.config.js b/docs/theme.config.js
index e2c49c655..ac0a11a02 100644
--- a/docs/theme.config.js
+++ b/docs/theme.config.js
@@ -74,23 +74,23 @@ export default {
project: {
link: 'https://github.com/amannn/next-intl'
},
+ docsRepositoryBase: 'https://github.com/amannn/next-intl/blob/main/docs',
+ useNextSeoProps() {
+ return {
+ titleTemplate: '%s – Internationalization (i18n) for Next.js'
+ };
+ },
banner: {
text: (
<>
- Try out the{' '}
+ next-intl 3.0 is out! (
- 3.0 release candidate
+ announcement
- !
+ )
>
)
},
- docsRepositoryBase: 'https://github.com/amannn/next-intl/blob/main/docs',
- useNextSeoProps() {
- return {
- titleTemplate: '%s – Internationalization (i18n) for Next.js'
- };
- },
primaryHue: {light: 210, dark: 195},
footer: {
component: Footer
@@ -154,25 +154,32 @@ export default {
+
+
+
-
+
+
+
+
-
-
>
)
};
diff --git a/examples/example-advanced/package.json b/examples/example-advanced/package.json
index 95f6db923..caf4cf28e 100644
--- a/examples/example-advanced/package.json
+++ b/examples/example-advanced/package.json
@@ -23,12 +23,12 @@
"@types/jest": "^29.5.1",
"@types/lodash": "^4.14.176",
"@types/node": "^17.0.23",
- "@types/react": "^18.2.5",
+ "@types/react": "^18.2.29",
"eslint": "^8.46.0",
"eslint-config-molindo": "^7.0.0",
"eslint-config-next": "^13.4.0",
- "jest": "^27.4.5",
- "jest-environment-jsdom": "^27.0.0",
- "typescript": "^5.0.0"
+ "jest": "^29.0.0",
+ "jest-environment-jsdom": "^29.0.0",
+ "typescript": "^5.2.2"
}
}
diff --git a/examples/example-advanced/src/pages/_app.tsx b/examples/example-advanced/src/pages/_app.tsx
index f76456f51..14f75d12d 100644
--- a/examples/example-advanced/src/pages/_app.tsx
+++ b/examples/example-advanced/src/pages/_app.tsx
@@ -1,4 +1,5 @@
import {AppProps} from 'next/app';
+import {useRouter} from 'next/router';
import {NextIntlClientProvider} from 'next-intl';
type PageProps = {
@@ -11,6 +12,8 @@ type Props = Omit, 'pageProps'> & {
};
export default function App({Component, pageProps}: Props) {
+ const router = useRouter();
+
return (
+
);
diff --git a/examples/example-next-13-advanced/.eslintrc.js b/examples/example-next-13-advanced/.eslintrc.js
new file mode 100644
index 000000000..24eb90084
--- /dev/null
+++ b/examples/example-next-13-advanced/.eslintrc.js
@@ -0,0 +1,13 @@
+module.exports = {
+ env: {
+ node: true
+ },
+ extends: [
+ 'molindo/typescript',
+ 'molindo/react',
+ 'plugin:@next/next/recommended'
+ ],
+ rules: {
+ 'react/react-in-jsx-scope': 'off'
+ }
+};
diff --git a/examples/example-next-13-advanced/.gitignore b/examples/example-next-13-advanced/.gitignore
new file mode 100644
index 000000000..04239e7d0
--- /dev/null
+++ b/examples/example-next-13-advanced/.gitignore
@@ -0,0 +1,4 @@
+/node_modules
+/.next/
+.DS_Store
+tsconfig.tsbuildinfo
diff --git a/examples/example-next-13-advanced/README.md b/examples/example-next-13-advanced/README.md
new file mode 100644
index 000000000..73bdf62b8
--- /dev/null
+++ b/examples/example-next-13-advanced/README.md
@@ -0,0 +1,3 @@
+# example-next-13-advanced
+
+An example that showcases usage of `next-intl` in the `app` folder of Next.js 13.
diff --git a/examples/example-next-13-advanced/global.d.ts b/examples/example-next-13-advanced/global.d.ts
new file mode 100644
index 000000000..0e9a1a3de
--- /dev/null
+++ b/examples/example-next-13-advanced/global.d.ts
@@ -0,0 +1,3 @@
+// Declaring this interface provides type safety for message keys
+type Messages = typeof import('./messages/en.json');
+declare interface IntlMessages extends Messages {}
diff --git a/examples/example-next-13-advanced/messages/de.json b/examples/example-next-13-advanced/messages/de.json
new file mode 100644
index 000000000..f2c8a2254
--- /dev/null
+++ b/examples/example-next-13-advanced/messages/de.json
@@ -0,0 +1,57 @@
+{
+ "Index": {
+ "title": "Start",
+ "description": "Das ist die Startseite.",
+ "rich": "Das ist formatierter Test.",
+ "globalDefaults": "{globalString}"
+ },
+ "AsyncComponent": {
+ "basic": "AsyncComponent",
+ "rich": "This is a rich text.",
+ "markup": "Markup with {globalString}"
+ },
+ "LocaleLayout": {
+ "title": "next-intl Beispiel",
+ "description": "Das ist ein Beispiel, wie next-intl im `app`-Verzeichnis verwendet werden kann."
+ },
+ "Client": {
+ "title": "Client",
+ "description": "Dise Seite wird auf der Client-Seite initialisiert."
+ },
+ "Nested": {
+ "title": "Verschachtelt",
+ "description": "Das ist eine verschachtelte Seite."
+ },
+ "Navigation": {
+ "home": "Start",
+ "client": "Client-Seite",
+ "nested": "Verschachtelte Seite",
+ "newsArticle": "News-Artikel #{articleId}"
+ },
+ "NewsArticle": {
+ "title": "News-Artikel #{articleId}"
+ },
+ "NotFound": {
+ "title": "Diese Seite wurde nicht gefunden (404)"
+ },
+ "LocaleSwitcher": {
+ "switchLocale": "Zu {locale, select, de {Deutsch} en {Englisch} other {Unbekannt}} wechseln"
+ },
+ "Counter": {
+ "count": "Aktuell:",
+ "increment": "Erhöhen"
+ },
+ "ClientCounter": {
+ "count": "Aktuell: {count}",
+ "increment": "Erhöhen"
+ },
+ "ApiRoute": {
+ "hello": "Hallo {name}!"
+ },
+ "OpenGraph": {
+ "title": "next-intl Beispiel"
+ },
+ "ServerActions": {
+ "item": "Element #{id}"
+ }
+}
diff --git a/examples/example-next-13-advanced/messages/en.json b/examples/example-next-13-advanced/messages/en.json
new file mode 100644
index 000000000..cdbbdb93e
--- /dev/null
+++ b/examples/example-next-13-advanced/messages/en.json
@@ -0,0 +1,57 @@
+{
+ "Index": {
+ "title": "Home",
+ "description": "This is the home page.",
+ "rich": "This is a rich text.",
+ "globalDefaults": "{globalString}"
+ },
+ "AsyncComponent": {
+ "basic": "AsyncComponent",
+ "rich": "This is a rich text.",
+ "markup": "Markup with {globalString}"
+ },
+ "LocaleLayout": {
+ "title": "next-intl example",
+ "description": "This is an example of using next-intl in the `app` directory."
+ },
+ "Client": {
+ "title": "Client",
+ "description": "This page hydrates on the client side."
+ },
+ "Nested": {
+ "title": "Nested",
+ "description": "This is a nested page."
+ },
+ "Navigation": {
+ "home": "Home",
+ "client": "Client page",
+ "nested": "Nested page",
+ "newsArticle": "News article #{articleId}"
+ },
+ "NewsArticle": {
+ "title": "News article #{articleId}"
+ },
+ "NotFound": {
+ "title": "This page was not found (404)"
+ },
+ "LocaleSwitcher": {
+ "switchLocale": "Switch to {locale, select, de {German} en {English} other {Unknown}}"
+ },
+ "Counter": {
+ "count": "Current count:",
+ "increment": "Increment"
+ },
+ "ClientCounter": {
+ "count": "Current count: {count}",
+ "increment": "Increment"
+ },
+ "ApiRoute": {
+ "hello": "Hello {name}!"
+ },
+ "OpenGraph": {
+ "title": "next-intl example"
+ },
+ "ServerActions": {
+ "item": "Item #{id}"
+ }
+}
diff --git a/examples/example-next-13-advanced/messages/es.json b/examples/example-next-13-advanced/messages/es.json
new file mode 100644
index 000000000..dc4a979d9
--- /dev/null
+++ b/examples/example-next-13-advanced/messages/es.json
@@ -0,0 +1,57 @@
+{
+ "Index": {
+ "title": "Inicio",
+ "description": "Esta es la página de inicio.",
+ "rich": "Este es un texto importante.",
+ "globalDefaults": "{globalString}"
+ },
+ "AsyncComponent": {
+ "basic": "AsyncComponent",
+ "rich": "This is a rich text.",
+ "markup": "Markup with {globalString}"
+ },
+ "LocaleLayout": {
+ "title": "Ejemplo next-intl",
+ "description": "Este es un ejemplo de cómo usar next-intl en el directorio app."
+ },
+ "Client": {
+ "title": "Cliente",
+ "description": "Esta página se hidrata en el lado del cliente."
+ },
+ "Nested": {
+ "title": "Anidada",
+ "description": "Esta es una página anidada."
+ },
+ "Navigation": {
+ "home": "Inicio",
+ "client": "Página del cliente",
+ "nested": "Página anidada",
+ "newsArticle": "Noticias #{articleId}"
+ },
+ "NewsArticle": {
+ "title": "Noticias #{articleId}"
+ },
+ "NotFound": {
+ "title": "Esta página no se encontró (404)"
+ },
+ "LocaleSwitcher": {
+ "switchLocale": "Cambiar a {locale, select, de {Alemán} en {Inglés} other {Desconocido}}"
+ },
+ "Counter": {
+ "count": "Conteo actual:",
+ "increment": "Incrementar"
+ },
+ "ClientCounter": {
+ "count": "Conteo actual: {count}",
+ "increment": "Incrementar"
+ },
+ "ApiRoute": {
+ "hello": "¡Hola {name}!"
+ },
+ "OpenGraph": {
+ "title": "Ejemplo next-intl"
+ },
+ "ServerActions": {
+ "item": "Artículo #{id}"
+ }
+}
diff --git a/examples/example-next-13-advanced/next-env.d.ts b/examples/example-next-13-advanced/next-env.d.ts
new file mode 100644
index 000000000..4f11a03dc
--- /dev/null
+++ b/examples/example-next-13-advanced/next-env.d.ts
@@ -0,0 +1,5 @@
+///
+///
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/basic-features/typescript for more information.
diff --git a/examples/example-next-13-advanced/next.config.mjs b/examples/example-next-13-advanced/next.config.mjs
new file mode 100644
index 000000000..125c56e37
--- /dev/null
+++ b/examples/example-next-13-advanced/next.config.mjs
@@ -0,0 +1,6 @@
+// @ts-check
+
+import NextIntlPlugin from 'next-intl/plugin';
+
+const withNextIntl = NextIntlPlugin('./src/i18n.tsx');
+export default withNextIntl();
diff --git a/examples/example-next-13-advanced/package.json b/examples/example-next-13-advanced/package.json
new file mode 100644
index 000000000..2df98ac55
--- /dev/null
+++ b/examples/example-next-13-advanced/package.json
@@ -0,0 +1,33 @@
+{
+ "name": "example-next-13-advanced",
+ "version": "2.9.1",
+ "private": true,
+ "scripts": {
+ "dev": "next dev",
+ "lint": "eslint src && tsc",
+ "test": "playwright test",
+ "test:watch": "chokidar 'tests/main.spec.ts' -c 'npm run test'",
+ "build": "next build",
+ "start": "next start"
+ },
+ "dependencies": {
+ "lodash": "^4.17.21",
+ "ms": "2.1.3",
+ "next": "14.0.1",
+ "next-intl": "^2.13.2",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0"
+ },
+ "devDependencies": {
+ "@playwright/test": "^1.33.0",
+ "@types/lodash": "^4.14.176",
+ "@types/node": "^17.0.23",
+ "@types/react": "^18.2.29",
+ "chokidar-cli": "3.0.0",
+ "eslint": "^8.46.0",
+ "eslint-config-molindo": "7.0.0-alpha.7",
+ "eslint-config-next": "^13.4.0",
+ "sharp": "^0.32.6",
+ "typescript": "^5.2.2"
+ }
+}
diff --git a/examples/example-next-13-advanced/playwright.config.ts b/examples/example-next-13-advanced/playwright.config.ts
new file mode 100644
index 000000000..2c2f76509
--- /dev/null
+++ b/examples/example-next-13-advanced/playwright.config.ts
@@ -0,0 +1,24 @@
+/* eslint-disable import/no-extraneous-dependencies */
+import type {PlaywrightTestConfig} from '@playwright/test';
+import {devices} from '@playwright/test';
+
+// Use a distinct port on CI to avoid conflicts during concurrent tests
+const PORT = process.env.CI ? 3003 : 3000;
+
+const config: PlaywrightTestConfig = {
+ retries: process.env.CI ? 1 : 0,
+ testDir: './tests',
+ projects: [
+ {
+ name: 'chromium',
+ use: devices['Desktop Chrome']
+ }
+ ],
+ webServer: {
+ command: `PORT=${PORT} pnpm start`,
+ port: PORT,
+ reuseExistingServer: true
+ }
+};
+
+export default config;
diff --git a/examples/example-next-13-advanced/public/assets/image.jpg b/examples/example-next-13-advanced/public/assets/image.jpg
new file mode 100644
index 000000000..f9852a89d
Binary files /dev/null and b/examples/example-next-13-advanced/public/assets/image.jpg differ
diff --git a/examples/example-next-13-advanced/public/favicon.ico b/examples/example-next-13-advanced/public/favicon.ico
new file mode 100644
index 000000000..4ddd8fff7
Binary files /dev/null and b/examples/example-next-13-advanced/public/favicon.ico differ
diff --git a/examples/example-next-13-advanced/src/app/[locale]/[...rest]/page.tsx b/examples/example-next-13-advanced/src/app/[locale]/[...rest]/page.tsx
new file mode 100644
index 000000000..4c9f4064a
--- /dev/null
+++ b/examples/example-next-13-advanced/src/app/[locale]/[...rest]/page.tsx
@@ -0,0 +1,9 @@
+import {notFound} from 'next/navigation';
+
+export default function CatchAll() {
+ // `not-found` currently only renders when triggered by the `notFound` function
+ // https://beta.nextjs.org/docs/api-reference/file-conventions/not-found
+ notFound();
+
+ return null;
+}
diff --git a/examples/example-next-13-advanced/src/app/[locale]/actions/List.tsx b/examples/example-next-13-advanced/src/app/[locale]/actions/List.tsx
new file mode 100644
index 000000000..497317785
--- /dev/null
+++ b/examples/example-next-13-advanced/src/app/[locale]/actions/List.tsx
@@ -0,0 +1,38 @@
+'use client';
+
+import {ReactNode, useEffect, useState} from 'react';
+
+type Props = {
+ getNextItem(curLength: number): Promise;
+ title: string;
+};
+
+export default function List({getNextItem, title}: Props) {
+ const [isMounted, setIsMounted] = useState(false);
+ const [items, setItems] = useState>([]);
+
+ function onAddItem() {
+ getNextItem(items.length).then((item) => {
+ setItems([...items, item]);
+ });
+ }
+
+ useEffect(() => {
+ setIsMounted(true);
+ }, []);
+
+ return (
+
+
{title}
+
{isMounted && 'Mounted'}
+
+ {items.map((item, index) => (
+
{item}
+ ))}
+
+
+
+ );
+}
diff --git a/examples/example-next-13-advanced/src/app/[locale]/actions/ListItem.tsx b/examples/example-next-13-advanced/src/app/[locale]/actions/ListItem.tsx
new file mode 100644
index 000000000..df1a9e5b7
--- /dev/null
+++ b/examples/example-next-13-advanced/src/app/[locale]/actions/ListItem.tsx
@@ -0,0 +1,6 @@
+import {useTranslations} from 'next-intl';
+
+export default function ListItem({id}: {id: number}) {
+ const t = useTranslations('ServerActions');
+ return t('item', {id});
+}
diff --git a/examples/example-next-13-advanced/src/app/[locale]/actions/ListItemAsync.tsx b/examples/example-next-13-advanced/src/app/[locale]/actions/ListItemAsync.tsx
new file mode 100644
index 000000000..801919ba3
--- /dev/null
+++ b/examples/example-next-13-advanced/src/app/[locale]/actions/ListItemAsync.tsx
@@ -0,0 +1,6 @@
+import {getTranslations} from 'next-intl/server';
+
+export default async function ListItemAsync({id}: {id: number}) {
+ const t = await getTranslations('ServerActions');
+ return t('item', {id});
+}
diff --git a/examples/example-next-13-advanced/src/app/[locale]/actions/ListItemClient.tsx b/examples/example-next-13-advanced/src/app/[locale]/actions/ListItemClient.tsx
new file mode 100644
index 000000000..b94cf8749
--- /dev/null
+++ b/examples/example-next-13-advanced/src/app/[locale]/actions/ListItemClient.tsx
@@ -0,0 +1,8 @@
+'use client';
+
+import {useTranslations} from 'next-intl';
+
+export default function ListItemClient({id}: {id: number}) {
+ const t = useTranslations('ServerActions');
+ return t('item', {id});
+}
diff --git a/examples/example-next-13-advanced/src/app/[locale]/actions/page.tsx b/examples/example-next-13-advanced/src/app/[locale]/actions/page.tsx
new file mode 100644
index 000000000..8c875e923
--- /dev/null
+++ b/examples/example-next-13-advanced/src/app/[locale]/actions/page.tsx
@@ -0,0 +1,39 @@
+import {pick} from 'lodash';
+import {NextIntlClientProvider, useMessages} from 'next-intl';
+import List from './List';
+import ListItem from './ListItem';
+import ListItemAsync from './ListItemAsync';
+import ListItemClient from './ListItemClient';
+
+export default function ServerActions() {
+ return (
+ <>
+ {
+ 'use server';
+ const id = curLength + 1;
+ return ;
+ }}
+ title="Shared Server Components"
+ />
+ {
+ 'use server';
+ const id = curLength + 1;
+ return ;
+ }}
+ title="Async Server Components"
+ />
+
+ {
+ 'use server';
+ const id = curLength + 1;
+ return ;
+ }}
+ title="Client Components"
+ />
+
+ >
+ );
+}
diff --git a/examples/example-next-13-advanced/src/app/[locale]/api/route.ts b/examples/example-next-13-advanced/src/app/[locale]/api/route.ts
new file mode 100644
index 000000000..921971072
--- /dev/null
+++ b/examples/example-next-13-advanced/src/app/[locale]/api/route.ts
@@ -0,0 +1,18 @@
+import {NextRequest, NextResponse} from 'next/server';
+import {getTranslations} from 'next-intl/server';
+
+type Props = {
+ params: {
+ locale: string;
+ };
+};
+
+export async function GET(request: NextRequest, {params: {locale}}: Props) {
+ const name = request.nextUrl.searchParams.get('name');
+ if (!name) {
+ return new Response('Search param `name` was not provided.', {status: 400});
+ }
+
+ const t = await getTranslations({locale, namespace: 'ApiRoute'});
+ return NextResponse.json({message: t('hello', {name})});
+}
diff --git a/examples/example-next-13-advanced/src/app/[locale]/client/ClientContent.tsx b/examples/example-next-13-advanced/src/app/[locale]/client/ClientContent.tsx
new file mode 100644
index 000000000..eeebd96a8
--- /dev/null
+++ b/examples/example-next-13-advanced/src/app/[locale]/client/ClientContent.tsx
@@ -0,0 +1,20 @@
+'use client';
+
+import {useNow, useTimeZone, useLocale} from 'next-intl';
+import {Link, usePathname} from '../../../navigation';
+
+export default function ClientContent() {
+ const now = useNow();
+ const timeZone = useTimeZone();
+ const locale = useLocale();
+
+ return (
+ <>
+
{now.toISOString()}
+ Go to home
+
{usePathname()}
+
{timeZone}
+
{locale}
+ >
+ );
+}
diff --git a/examples/example-next-13-advanced/src/app/[locale]/client/DelayedServerContent.tsx b/examples/example-next-13-advanced/src/app/[locale]/client/DelayedServerContent.tsx
new file mode 100644
index 000000000..5f8d0989e
--- /dev/null
+++ b/examples/example-next-13-advanced/src/app/[locale]/client/DelayedServerContent.tsx
@@ -0,0 +1,13 @@
+import {useNow} from 'next-intl';
+import {use} from 'react';
+
+export default function DelayedServerContent() {
+ use(new Promise((resolve) => setTimeout(resolve, 50)));
+ const now = useNow();
+
+ return (
+ <>
+
{now.toISOString()}
+ >
+ );
+}
diff --git a/examples/example-next-13-advanced/src/app/[locale]/client/page.tsx b/examples/example-next-13-advanced/src/app/[locale]/client/page.tsx
new file mode 100644
index 000000000..67693fba0
--- /dev/null
+++ b/examples/example-next-13-advanced/src/app/[locale]/client/page.tsx
@@ -0,0 +1,20 @@
+import {useTranslations, useNow, NextIntlClientProvider} from 'next-intl';
+import PageLayout from '../../../components/PageLayout';
+import ClientContent from './ClientContent';
+import DelayedServerContent from './DelayedServerContent';
+
+export default function Client() {
+ const t = useTranslations('Client');
+ const now = useNow();
+
+ return (
+
+
{t('description')}
+
{now.toISOString()}
+
+
+
+
+
+ );
+}
diff --git a/examples/example-next-13-advanced/src/app/[locale]/client/redirect/page.tsx b/examples/example-next-13-advanced/src/app/[locale]/client/redirect/page.tsx
new file mode 100644
index 000000000..2c361746b
--- /dev/null
+++ b/examples/example-next-13-advanced/src/app/[locale]/client/redirect/page.tsx
@@ -0,0 +1,7 @@
+'use client';
+
+import {redirect} from '../../../../navigation';
+
+export default function ClientRedirectPage() {
+ redirect('/client');
+}
diff --git a/examples/example-next-13-advanced/src/app/[locale]/layout.tsx b/examples/example-next-13-advanced/src/app/[locale]/layout.tsx
new file mode 100644
index 000000000..032388470
--- /dev/null
+++ b/examples/example-next-13-advanced/src/app/[locale]/layout.tsx
@@ -0,0 +1,57 @@
+import {Metadata} from 'next';
+import {notFound} from 'next/navigation';
+import {
+ getFormatter,
+ getNow,
+ getTimeZone,
+ getTranslations
+} from 'next-intl/server';
+import {ReactNode} from 'react';
+import Navigation from '../../components/Navigation';
+import {locales} from '../../navigation';
+
+type Props = {
+ children: ReactNode;
+ params: {locale: string};
+};
+
+export async function generateMetadata({
+ params: {locale}
+}: Omit): Promise {
+ const t = await getTranslations({locale, namespace: 'LocaleLayout'});
+ const formatter = await getFormatter({locale});
+ const now = await getNow({locale});
+ const timeZone = await getTimeZone({locale});
+
+ return {
+ metadataBase: new URL('http://localhost:3000'),
+ title: t('title'),
+ description: t('description'),
+ other: {
+ currentYear: formatter.dateTime(now, {year: 'numeric'}),
+ timeZone: timeZone || 'N/A'
+ }
+ };
+}
+
+export default function LocaleLayout({children, params: {locale}}: Props) {
+ // Validate that the incoming `locale` parameter is valid
+ if (!locales.includes(locale as any)) notFound();
+
+ return (
+
+
+
+
+ {children}
+
+
+
+ );
+}
diff --git a/examples/example-next-13-advanced/src/app/[locale]/nested/UnlocalizedPathname.tsx b/examples/example-next-13-advanced/src/app/[locale]/nested/UnlocalizedPathname.tsx
new file mode 100644
index 000000000..141a7fab8
--- /dev/null
+++ b/examples/example-next-13-advanced/src/app/[locale]/nested/UnlocalizedPathname.tsx
@@ -0,0 +1,7 @@
+'use client';
+
+import {usePathname} from '../../../navigation';
+
+export default function UnlocalizedPathname() {
+ return
{usePathname()}
;
+}
diff --git a/examples/example-next-13-advanced/src/app/[locale]/nested/page.tsx b/examples/example-next-13-advanced/src/app/[locale]/nested/page.tsx
new file mode 100644
index 000000000..b7ce8b5a2
--- /dev/null
+++ b/examples/example-next-13-advanced/src/app/[locale]/nested/page.tsx
@@ -0,0 +1,14 @@
+import {useTranslations} from 'next-intl';
+import PageLayout from '../../../components/PageLayout';
+import UnlocalizedPathname from './UnlocalizedPathname';
+
+export default function Nested() {
+ const t = useTranslations('Nested');
+
+ return (
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/examples/example-next-13-advanced/src/app/[locale]/redirect/page.tsx b/examples/example-next-13-advanced/src/app/[locale]/redirect/page.tsx
new file mode 100644
index 000000000..520e04565
--- /dev/null
+++ b/examples/example-next-13-advanced/src/app/[locale]/redirect/page.tsx
@@ -0,0 +1,5 @@
+import {redirect} from '../../../navigation';
+
+export default function Redirect() {
+ redirect('/client');
+}
diff --git a/examples/example-next-13-advanced/src/app/layout.tsx b/examples/example-next-13-advanced/src/app/layout.tsx
new file mode 100644
index 000000000..b11c9e38b
--- /dev/null
+++ b/examples/example-next-13-advanced/src/app/layout.tsx
@@ -0,0 +1,12 @@
+import {ReactNode} from 'react';
+
+type Props = {
+ children: ReactNode;
+};
+
+// Even though this component is just passing its children through, the presence
+// of this file fixes an issue in Next.js 13.4 where link clicks that switch
+// the locale would otherwise cause a full reload.
+export default function RootLayout({children}: Props) {
+ return children;
+}
diff --git a/examples/example-next-13-advanced/src/components/AsyncComponent.tsx b/examples/example-next-13-advanced/src/components/AsyncComponent.tsx
new file mode 100644
index 000000000..5d3626df7
--- /dev/null
+++ b/examples/example-next-13-advanced/src/components/AsyncComponent.tsx
@@ -0,0 +1,23 @@
+import {getTranslations} from 'next-intl/server';
+
+export default async function AsyncComponent() {
+ const t = await getTranslations('AsyncComponent');
+
+ return (
+