Skip to content

Commit

Permalink
feat: Mark setRequestLocale as stable (#1437)
Browse files Browse the repository at this point in the history
The previously `unstable_setRequestLocale` is now marked as stable as
it's likely required for the foreseeable future.
  • Loading branch information
amannn authored Oct 21, 2024
1 parent e6d9d60 commit 7b3a9a3
Show file tree
Hide file tree
Showing 19 changed files with 68 additions and 47 deletions.
File renamed without changes.
20 changes: 17 additions & 3 deletions docs/pages/blog/next-intl-3-22.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ While this release is fully backwards-compatible, it includes some modern altern
2. [**`i18n/request.ts`**](#i18n-request): Streamlined file organization (introduced in `v3.19`)
3. [**`await requestLocale`**](#await-request-locale): Preparation for Next.js 15 (introduced in `v3.22`)
4. [**`createNavigation`**](#create-navigation): Streamlined navigation APIs (introduced in `v3.22`)
5. [**`defaultTranslationValues`**](#default-translation-values): Deprecated in favor of a user-land pattern (introduced in `v3.22`)
5. [**`setRequestLocale`**](#set-request-locale): Static rendering (marked as stable in `v3.22`)
6. [**`defaultTranslationValues`**](#default-translation-values): Deprecated in favor of a user-land pattern (introduced in `v3.22`)

Let's take a look at these changes in more detail.

Expand Down Expand Up @@ -63,7 +64,7 @@ export default createMiddleware(routing);

If you've used `defineRouting` previously to v3.22 and have provided middleware options as a second argument to `createMiddleware`, you can now pass these to `defineRouting` instead.

The docs have been consistently updated to reflect these changes and also suggest the creation of navigation APIs directly in `i18n/routing.ts`. If you prefer to keep your navigation APIs separately, that's of course fine as well.
The docs have been consistently updated to reflect these changes and also suggest the creation of navigation APIs directly in `i18n/routing.ts` for simplicity. If you prefer to keep your navigation APIs separately, that's of course fine as well.

## `i18n/request.ts` [#i18n-request]

Expand Down Expand Up @@ -187,7 +188,20 @@ const locale = await getLocale();
+ getPathname(/* ... */);
```

5. If you're using a combination of `localePrefix: 'as-needed'` and `domains` and you're using `getPathname`, you now need to provide a `domain` argument (see [Special case: Using `domains` with `localePrefix: 'as-needed'`](/docs/routing#domains-localeprefix-asneeded))
5. If you're using a combination of `localePrefix: 'as-needed'` & `domains` and you're using `getPathname`, you now need to provide a `domain` argument (see [Special case: Using `domains` with `localePrefix: 'as-needed'`](/docs/routing#domains-localeprefix-asneeded))

## `setRequestLocale` marked as stable [#set-request-locale]

In case you rely on [static rendering](/docs/getting-started/app-router/with-i18n-routing#static-rendering), you might have used the `unstable_setRequestLocale` API before. This function has now been marked as stable since it will likely remain required for the foreseeable future.

```diff
- import {unstable_setRequestLocale} from 'next-intl/server';
+ import {setRequestLocale} from 'next-intl/server';
```

Close to a year ago, I opened [discussion #58862](https://github.com/vercel/next.js/discussions/58862) in the Next.js repository. This was an attempt at starting a conversation about how Next.js could provide a way to access a user locale in Server Components without a tradeoff in ergonomics or rendering implications. While the issue has gained in popularity and currently [ranks as #2](https://github.com/vercel/next.js/discussions?discussions_q=is%3Aopen+sort%3Atop+created%3A%3E%3D2023-10-22) of the top upvoted discussions of the past year, I've unfortunately not been able to get a response from the Next.js team on this topic so far. Based on my understanding, it's certainly not an easy problem to solve, but I'd be more than happy to collaborate on this if I can.

While I'm still optimistic that we can make the `setRequestLocale` API obsolete at some point in the future, the "unstable" prefix doesn't seem to be justified anymore—especially since the API has been known to work reliably since its introduction.

## `defaultTranslationValues` (deprecated) [#default-translation-values]

Expand Down
43 changes: 20 additions & 23 deletions docs/pages/docs/getting-started/app-router/with-i18n-routing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ In case you ran into an issue, have a look at [the App Router example](/examples

## Static rendering

When using the setup with i18n routing, `next-intl`will currently opt into dynamic rendering when APIs like `useTranslations` are used in Server Components. 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.
When using the setup with i18n routing, `next-intl` will currently opt into dynamic rendering when APIs like `useTranslations` are used in Server Components. 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.

<Steps>

Expand All @@ -291,15 +291,23 @@ export function generateStaticParams() {
}
```

### Add `unstable_setRequestLocale` to all layouts and pages
### Add `setRequestLocale` to all relevant layouts and pages

`next-intl` provides a temporary API that can be used to distribute the locale that is received via `params` in layouts and pages for usage in all Server Components that are rendered as part of the request.
`next-intl` provides an API that can be used to distribute the locale that is received via `params` in layouts and pages 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';
import {setRequestLocale} from 'next-intl/server';
import {notFound} from 'next/navigation';
import {routing} from '@/i18n/routing';

export default async function LocaleLayout({children, params: {locale}}) {
unstable_setRequestLocale(locale);
// Ensure that the incoming `locale` is valid
if (!routing.locales.includes(locale as any)) {
notFound();
}

// Enable static rendering
setRequestLocale(locale);

return (
// ...
Expand All @@ -308,10 +316,11 @@ export default async function LocaleLayout({children, params: {locale}}) {
```

```tsx filename="app/[locale]/page.tsx"
import {unstable_setRequestLocale} from 'next-intl/server';
import {setRequestLocale} from 'next-intl/server';

export default function IndexPage({params: {locale}}) {
unstable_setRequestLocale(locale);
// Enable static rendering
setRequestLocale(locale);

// Once the request locale is set, you
// can call hooks from `next-intl`
Expand All @@ -325,25 +334,13 @@ export default function IndexPage({params: {locale}}) {

**Keep in mind that:**

1. The locale that you pass to `unstable_setRequestLocale` should be validated (e.g. in [`i18n/request.ts`](/docs/usage/configuration#i18n-request)).

1. The locale that you pass to `setRequestLocale` should be validated (e.g. in your [root layout](#layout)).
2. You need to call this function in every page and every layout that you intend to enable static rendering for since Next.js can render layouts and pages independently.

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`.

<Details id="setrequestlocale-unstable">
<summary>What does "unstable" mean?</summary>

`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/vercel/next.js/discussions/58862). 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.

</Details>

<Details id="setrequestlocale-implementation">
<summary>How does unstable_setRequestLocale work?</summary>
<summary>How does setRequestLocale work?</summary>

`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.
`next-intl` uses [`cache()`](https://react.dev/reference/react/cache) to create a mutable store that holds the current locale. By calling `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.

Expand All @@ -358,7 +355,7 @@ Due to this, `next-intl` uses its middleware to attach an `x-next-intl-locale` h

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`. All APIs from `next-intl` can now read from this value instead of the header, enabling static rendering.
By using `setRequestLocale`, you can provide the locale that is received in layouts and pages via `params` to `next-intl`. All APIs from `next-intl` can now read from this value instead of the header, enabling static rendering.

</Details>

Expand Down
2 changes: 1 addition & 1 deletion docs/pages/docs/usage/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -699,7 +699,7 @@ Depending on if you're using [i18n routing](/docs/getting-started/app-router), t
The returned value is resolved based on these priorities:
1. **Server Components**: If you're using [i18n routing](/docs/getting-started/app-router), the returned locale is the one that you've either provided via [`unstable_setRequestLocale`](/docs/getting-started/app-router/with-i18n-routing#static-rendering) or alternatively the one in the `[locale]` segment that was matched by the middleware. If you're not using i18n routing, the returned locale is the one that you've provided via `getRequestConfig`.
1. **Server Components**: If you're using [i18n routing](/docs/getting-started/app-router), the returned locale is the one that you've either provided via [`setRequestLocale`](/docs/getting-started/app-router/with-i18n-routing#static-rendering) or alternatively the one in the `[locale]` segment that was matched by the middleware. If you're not using i18n routing, the returned locale is the one that you've provided via `getRequestConfig`.
2. **Client Components**: In this case, the locale is received from `NextIntlClientProvider` or alternatively `useParams().locale`. Note that `NextIntlClientProvider` automatically inherits the locale if the component is rendered by a Server Component. For all other cases, you can specify the value
explicitly.
Expand Down
2 changes: 1 addition & 1 deletion docs/theme.config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export default {
title: isDefault ? config.description : pageConfig.title,
subtitle: pageConfig.frontMatter.subtitle
};
const ogImageUrl = new URL('/api/og', config.baseUrl);
const ogImageUrl = new URL('/api/og-image', config.baseUrl);
ogImageUrl.search = new URLSearchParams({
params: JSON.stringify(ogPayload)
}).toString();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {useTranslations} from 'next-intl';
import {unstable_setRequestLocale} from 'next-intl/server';
import {setRequestLocale} from 'next-intl/server';
import PageTitle from '@/components/PageTitle';

type Props = {
Expand All @@ -8,7 +8,7 @@ type Props = {

export default function About({params: {locale}}: Props) {
// Enable static rendering
unstable_setRequestLocale(locale);
setRequestLocale(locale);

const t = useTranslations('About');
return <PageTitle>{t('title')}</PageTitle>;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {Metadata} from 'next';
import {notFound} from 'next/navigation';
import {NextIntlClientProvider} from 'next-intl';
import {getMessages, unstable_setRequestLocale} from 'next-intl/server';
import {getMessages, setRequestLocale} from 'next-intl/server';
import {ReactNode} from 'react';
import Document from '@/components/Document';
import {locales} from '@/config';
Expand All @@ -26,7 +26,7 @@ export default async function LocaleLayout({
params: {locale}
}: Props) {
// Enable static rendering
unstable_setRequestLocale(locale);
setRequestLocale(locale);

// Ensure that the incoming locale is valid
if (!locales.includes(locale as any)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {useTranslations} from 'next-intl';
import {unstable_setRequestLocale} from 'next-intl/server';
import {setRequestLocale} from 'next-intl/server';
import PageTitle from '@/components/PageTitle';

type Props = {
Expand All @@ -8,7 +8,7 @@ type Props = {

export default function Index({params: {locale}}: Props) {
// Enable static rendering
unstable_setRequestLocale(locale);
setRequestLocale(locale);

const t = useTranslations('Index');
return <PageTitle>{t('title')}</PageTitle>;
Expand Down
4 changes: 2 additions & 2 deletions examples/example-app-router/src/app/[locale]/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {notFound} from 'next/navigation';
import {getTranslations, unstable_setRequestLocale} from 'next-intl/server';
import {getTranslations, setRequestLocale} from 'next-intl/server';
import {ReactNode} from 'react';
import BaseLayout from '@/components/BaseLayout';
import {routing} from '@/i18n/routing';
Expand Down Expand Up @@ -33,7 +33,7 @@ export default async function LocaleLayout({
}

// Enable static rendering
unstable_setRequestLocale(locale);
setRequestLocale(locale);

return <BaseLayout locale={locale}>{children}</BaseLayout>;
}
4 changes: 2 additions & 2 deletions examples/example-app-router/src/app/[locale]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {useTranslations} from 'next-intl';
import {unstable_setRequestLocale} from 'next-intl/server';
import {setRequestLocale} from 'next-intl/server';
import PageLayout from '@/components/PageLayout';

type Props = {
Expand All @@ -8,7 +8,7 @@ type Props = {

export default function IndexPage({params: {locale}}: Props) {
// Enable static rendering
unstable_setRequestLocale(locale);
setRequestLocale(locale);

const t = useTranslations('IndexPage');

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {useTranslations} from 'next-intl';
import {unstable_setRequestLocale} from 'next-intl/server';
import {setRequestLocale} from 'next-intl/server';
import PageLayout from '@/components/PageLayout';

type Props = {
Expand All @@ -8,7 +8,7 @@ type Props = {

export default function PathnamesPage({params: {locale}}: Props) {
// Enable static rendering
unstable_setRequestLocale(locale);
setRequestLocale(locale);

const t = useTranslations('PathnamesPage');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import useBasePathname from './useBasePathname';
import useBaseRouter from './useBaseRouter';

/**
* @deprecated Consider switching to `createNavigation` (see https://github.com/amannn/next-intl/pull/1316)
* @deprecated Consider switching to `createNavigation` (see https://next-intl-docs.vercel.app/blog/next-intl-3-22#create-navigation)
**/
export default function createLocalizedPathnamesNavigation<
AppLocales extends Locales,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import useBasePathname from './useBasePathname';
import useBaseRouter from './useBaseRouter';

/**
* @deprecated Consider switching to `createNavigation` (see https://github.com/amannn/next-intl/pull/1316)
* @deprecated Consider switching to `createNavigation` (see https://next-intl-docs.vercel.app/blog/next-intl-3-22#create-navigation)
**/
export default function createSharedPathnamesNavigation<
AppLocales extends Locales,
Expand Down
5 changes: 5 additions & 0 deletions packages/next-intl/src/server/react-client/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type {
getNow as getNow_type,
getRequestConfig as getRequestConfig_type,
getTimeZone as getTimeZone_type,
setRequestLocale as setRequestLocale_type,
unstable_setRequestLocale as unstable_setRequestLocale_type
} from '../react-server';

Expand Down Expand Up @@ -48,3 +49,7 @@ export const getTranslations = notSupported('getTranslations');
export const unstable_setRequestLocale = notSupported(
'unstable_setRequestLocale'
) as typeof unstable_setRequestLocale_type;

export const setRequestLocale = notSupported(
'setRequestLocale'
) as typeof setRequestLocale_type;
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ async function getLocaleFromHeaderImpl(): Promise<string | undefined> {
(error as any).digest === 'DYNAMIC_SERVER_USAGE'
) {
const wrappedError = new Error(
'Usage of next-intl APIs in Server Components currently opts into dynamic rendering. This limitation will eventually be lifted, but as a stopgap solution, you can use the `unstable_setRequestLocale` API to enable static rendering, see https://next-intl-docs.vercel.app/docs/getting-started/app-router/with-i18n-routing#static-rendering',
'Usage of next-intl APIs in Server Components currently opts into dynamic rendering. This limitation will eventually be lifted, but as a stopgap solution, you can use the `setRequestLocale` API to enable static rendering, see https://next-intl-docs.vercel.app/docs/getting-started/app-router/with-i18n-routing#static-rendering',
{cause: error}
);
(wrappedError as any).digest = (error as any).digest;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ function getLocaleFromHeaderImpl() {
(error as any).digest === 'DYNAMIC_SERVER_USAGE'
) {
throw new Error(
'Usage of next-intl APIs in Server Components currently opts into dynamic rendering. This limitation will eventually be lifted, but as a stopgap solution, you can use the `unstable_setRequestLocale` API to enable static rendering, see https://next-intl-docs.vercel.app/docs/getting-started/app-router/with-i18n-routing#static-rendering',
'Usage of next-intl APIs in Server Components currently opts into dynamic rendering. This limitation will eventually be lifted, but as a stopgap solution, you can use the `setRequestLocale` API to enable static rendering, see https://next-intl-docs.vercel.app/docs/getting-started/app-router/with-i18n-routing#static-rendering',
{cause: error}
);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export type RequestConfig = Omit<IntlConfig, 'locale'> & {

export type GetRequestConfigParams = {
/**
* Deprecated in favor of `requestLocale` (see https://github.com/amannn/next-intl/pull/1383).
* Deprecated in favor of `requestLocale` (see https://next-intl-docs.vercel.app/blog/next-intl-3-22#await-request-locale).
*
* The locale that was matched by the `[locale]` path segment. Note however
* that this can be overridden in async APIs when the `locale` is explicitly
Expand Down
7 changes: 6 additions & 1 deletion packages/next-intl/src/server/react-server/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,9 @@ export {default as getTranslations} from './getTranslations';
export {default as getMessages} from './getMessages';
export {default as getLocale} from './getLocale';

export {setCachedRequestLocale as unstable_setRequestLocale} from './RequestLocaleCache';
export {setCachedRequestLocale as setRequestLocale} from './RequestLocaleCache';

export {
/** @deprecated Deprecated in favor of `setRequestLocale`. */
setCachedRequestLocale as unstable_setRequestLocale
} from './RequestLocaleCache';
2 changes: 1 addition & 1 deletion packages/use-intl/src/core/IntlConfig.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ type IntlConfig<Messages = AbstractIntlMessages> = {
* Defaults will be overidden by locally provided values.
*
* @deprecated See https://next-intl-docs.vercel.app/docs/usage/messages#rich-text-reuse-tags
* */
**/
defaultTranslationValues?: RichTranslationValues;
};

Expand Down

0 comments on commit 7b3a9a3

Please sign in to comment.