Skip to content

Commit

Permalink
feat: Re-introduce locale argument for getRequestConfig to be use…
Browse files Browse the repository at this point in the history
…d for overriding the locale (#1625)

**tldr;** — Do you use i18n routing and have you already switched to
[`await
requestLocale`](https://next-intl.dev/blog/next-intl-3-22#await-request-locale)
in `getRequestConfig`? If yes, you can skip this.

---

### Deprecation of `locale` in favor of `await requestLocale`

In [next-intl 3.22](https://next-intl.dev/blog/next-intl-3-22), the
`locale` argument that was passed to `getRequestConfig` was deprecated
in favor of [`await
requestLocale`](https://next-intl.dev/blog/next-intl-3-22#await-request-locale):

```diff
// i18n/request.ts

export default function getRequestConfig(async ({
-  locale
+  requestLocale
}) => {
+  const locale = await requestLocale;
  // ...
}));
```

This change was done in preparation for Next.js 15 where reading from
headers [became
async](https://nextjs.org/blog/next-15#async-request-apis-breaking-change).
If you're using i18n routing, please upgrade to `requestLocale` now.

### Preview: `rootParams` are coming to Next.js

Now, with [`rootParams`](vercel/next.js#72837)
being on the horizon, this API will allow you to read a locale without
receiving any param passed to `getRequestConfig`:

```tsx
// i18n/request.ts

import {unstable_rootParams as rootParams} from 'next/server';
import {getRequestConfig} from 'next-intl/server';
import {hasLocale} from 'next-intl';
import {routing} from './routing';
 
export default getRequestConfig(async () => {
  const params = await rootParams();
  const locale = hasLocale(routing.locales, params.locale)
    ? params.locale
    : routing.defaultLocale;
 
  // ...
});
```

Among other simplifications, this allows to remove manual overrides like
this that were merely done for enabling static rendering:

```diff
- type Props = {
-   params: Promise<{locale: string}>;
- };
 
export async function generateMetadata(
-  {params}: Props
) {
-  const {locale} = await params;
-  const t = await getTranslations({locale, namespace: 'HomePage'});
+  const t = await getTranslations('HomePage');
 
  // ...
}
```

However, in some rare cases, you might want to render messages from
multiple locales on the same page:

```tsx
// Use messages from 'en', regardless of what the current user locale is
const t = getTranslations({locale: 'en'});
```

If you're using this pattern, you'll be able to accept the overridden
locale in `getRequestConfig` as follows:

```tsx
// i18n/request.ts

import {unstable_rootParams as rootParams} from 'next/server';
import {getRequestConfig} from 'next-intl/server';
import {hasLocale} from 'next-intl';
import {routing} from './routing';
 
export default getRequestConfig(async ({locale}) => {
  // Use a locale based on these priorities:
  // 1. An override passed to the function
  // 2. A locale from the `[locale]` segment
  // 3. A default locale
  if (!locale) {
    const params = await rootParams();
    locale = hasLocale(routing.locales, params.locale)
      ? params.locale
      : routing.defaultLocale;
  }
 
  // ...
});
```

This is quite an edge case, but this use case will remain supported via
the re-introduced `locale` argument. Note that `await requestLocale`
considers a potential locale override, therefore the `locale` argument
will only be relevant once `rootParams` are a thing.

I hope to have more to share on this in the future!
  • Loading branch information
amannn authored Dec 20, 2024
1 parent c224d1c commit 0825f08
Show file tree
Hide file tree
Showing 4 changed files with 21 additions and 3 deletions.
2 changes: 2 additions & 0 deletions packages/next-intl/src/server/react-server/getConfig.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ See also: https://next-intl.dev/docs/usage/configuration#i18n-request
}

const params: GetRequestConfigParams = {
locale: localeOverride,

// In case the consumer doesn't read `params.locale` and instead provides the
// `locale` (either in a single-language workflow or because the locale is
// read from the user settings), don't attempt to read the request locale.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type {IntlConfig} from 'use-intl/core';
import type {IntlConfig, Locale} from 'use-intl/core';

export type RequestConfig = Omit<IntlConfig, 'locale'> & {
/**
Expand All @@ -8,6 +8,13 @@ export type RequestConfig = Omit<IntlConfig, 'locale'> & {
};

export type GetRequestConfigParams = {
/**
* If you provide an explicit locale to an async server-side function like
* `getTranslations({locale: 'en'})`, it will be passed via `locale` to
* `getRequestConfig` so you can use it instead of the segment value.
*/
locale?: Locale;

/**
* Typically corresponds to the `[locale]` segment that was matched by the middleware.
*
Expand Down
11 changes: 10 additions & 1 deletion packages/use-intl/src/core/hasLocale.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {it} from 'vitest';
import {expect, it} from 'vitest';
import hasLocale from './hasLocale.tsx';

it('narrows down the type', () => {
Expand All @@ -24,3 +24,12 @@ it('can be called with a non-matching narrow candidate', () => {
candidate satisfies never;
}
});

it('can be called with any candidate', () => {
const locales = ['en-US', 'en-GB'] as const;
expect(hasLocale(locales, 'unknown')).toBe(false);
expect(hasLocale(locales, undefined)).toBe(false);

// Relevant since `ParamValue` in Next.js includes `string[]`
expect(hasLocale(locales, ['de'])).toBe(false);
});
2 changes: 1 addition & 1 deletion packages/use-intl/src/core/hasLocale.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type {Locale} from './AppConfig.tsx';
*/
export default function hasLocale<LocaleType extends Locale>(
locales: ReadonlyArray<LocaleType>,
candidate?: string | null
candidate: unknown
): candidate is LocaleType {
return locales.includes(candidate as LocaleType);
}

0 comments on commit 0825f08

Please sign in to comment.