Skip to content

Commit 7bcaf09

Browse files
committed
feat(i18n): adopt next/root-params
Next.js 16.3 exposes the `[locale]` root segment to Server Components via `next/root-params`, which next-intl reads directly as of 4.13. That removes the `setRequestLocale` bookkeeping the app needed for static rendering, along with the locale threading through every page's `params`. - `i18n.tsx` resolves the locale from the root param, so `requestLocale` is no longer needed. An explicit locale passed by a call site still wins, which stays useful for Server Actions and Route Handlers, where `next/root-params` is not supported yet - the root layout reads the locale via `getLocale()`, which returns the already validated value from `i18n.tsx`, and no longer receives `params` - `generateMetadata` and `getLocaleAndPath` read `next/root-params` directly rather than `getLocale()`, because they need the raw segment: `getLocale()` returns the already defaulted value, which would change the metadata emitted for unknown and disabled locales and make the notFound/redirect validation in `getLocaleAndPath` unreachable
1 parent 27ec25e commit 7bcaf09

7 files changed

Lines changed: 28 additions & 29 deletions

File tree

apps/site/app/[locale]/[...path]/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,10 @@ export const generateStaticParams = async () => {
6161
// finally it returns (if the locale and route are valid) the React Component with the relevant context
6262
// and attached context providers for rendering the current page
6363
const getPage: FC<PageParams> = async props => {
64-
const { path, locale: routeLocale } = await props.params;
64+
const { path } = await props.params;
6565

6666
// Gets the current full pathname for a given path
67-
const [locale, pathname] = basePage.getLocaleAndPath(path, routeLocale);
67+
const [locale, pathname] = await basePage.getLocaleAndPath(path);
6868

6969
// Gets the Markdown content and context
7070
const [content, context] = await basePage.getMarkdownContext({

apps/site/app/[locale]/blog/[...path]/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,10 @@ export const generateStaticParams = async () => {
4141
// finally it returns (if the locale and route are valid) the React Component with the relevant context
4242
// and attached context providers for rendering the current page
4343
const getPage: FC<PageParams> = async props => {
44-
const { path, locale: routeLocale } = await props.params;
44+
const { path } = await props.params;
4545

4646
// Gets the current full pathname for a given path
47-
const [locale, pathname] = basePage.getLocaleAndPath(path, routeLocale);
47+
const [locale, pathname] = await basePage.getLocaleAndPath(path);
4848

4949
// Verifies if the current route is a dynamic route
5050
const isDynamicRoute = BLOG_DYNAMIC_ROUTES.some(r => r.includes(pathname));

apps/site/app/[locale]/download/archive/[version]/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,10 @@ export const generateStaticParams = async () => {
4343
// finally it returns (if the locale and route are valid) the React Component with the relevant context
4444
// and attached context providers for rendering the current page
4545
const getPage: FC<PageParams> = async props => {
46-
const { version, locale: routeLocale } = await props.params;
46+
const { version } = await props.params;
4747

4848
// Gets the current full pathname for a given path
49-
const [locale, pathname] = basePage.getLocaleAndPath(version, routeLocale);
49+
const [locale, pathname] = await basePage.getLocaleAndPath(version);
5050

5151
if (version === 'current') {
5252
const releaseData = await provideReleaseData();

apps/site/app/[locale]/layout.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import PlatformAnalytics from '#platform/analytics';
22
import { availableLocales, defaultLocale } from '@node-core/website-i18n';
33
import classNames from 'classnames';
44
import { NextIntlClientProvider } from 'next-intl';
5+
import { getLocale } from 'next-intl/server';
56

67
import BaseLayout from '#site/layouts/Base';
78
import { IBM_PLEX_MONO, OPEN_SANS } from '#site/next.fonts';
@@ -13,12 +14,8 @@ import '#site/styles/index.css';
1314

1415
const fontClasses = classNames(IBM_PLEX_MONO.variable, OPEN_SANS.variable);
1516

16-
type RootLayoutProps = PropsWithChildren<{
17-
params: Promise<{ locale: string }>;
18-
}>;
19-
20-
const RootLayout: FC<RootLayoutProps> = async ({ children, params }) => {
21-
const { locale } = await params;
17+
const RootLayout: FC<PropsWithChildren> = async ({ children }) => {
18+
const locale = await getLocale();
2219

2320
const { langDir, hrefLang } =
2421
availableLocales.find(l => l.code === locale) || defaultLocale;

apps/site/app/[locale]/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,10 @@ export const generateStaticParams = async () => {
5050
// finally it returns (if the locale and route are valid) the React Component with the relevant context
5151
// and attached context providers for rendering the current page
5252
const getPage: FC<PageParams> = async props => {
53-
const { path, locale: routeLocale } = await props.params;
53+
const { path } = await props.params;
5454

5555
// Gets the current full pathname for a given path
56-
const [locale, pathname] = basePage.getLocaleAndPath(path, routeLocale);
56+
const [locale, pathname] = await basePage.getLocaleAndPath(path);
5757

5858
// Gets the Markdown content and context
5959
const [content, context] = await basePage.getMarkdownContext({

apps/site/i18n.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { availableLocaleCodes, defaultLocale } from '@node-core/website-i18n';
22
import defaultMessages from '@node-core/website-i18n/locales/en.json';
3+
import { locale as getRootLocale } from 'next/root-params';
34
import { getRequestConfig } from 'next-intl/server';
45

56
import { deepMerge } from './util/objects';
@@ -25,9 +26,10 @@ const loadLocaleDictionary = async (locale: string) => {
2526
};
2627

2728
// Provides `next-intl` configuration for RSC/SSR
28-
export default getRequestConfig(async ({ requestLocale }) => {
29-
// This typically corresponds to the `[locale]` segment
30-
let locale = await requestLocale;
29+
export default getRequestConfig(async params => {
30+
// An explicit locale passed to an awaitable API like `getTranslations({ locale })`
31+
// wins, otherwise we read the `[locale]` segment of the root layout
32+
let locale = params.locale ?? (await getRootLocale());
3133

3234
// Ensure that the incoming locale is valid
3335
if (!locale || !availableLocaleCodes.includes(locale)) {

apps/site/next.dynamic.page.mjs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
availableLocaleCodes,
77
} from '@node-core/website-i18n';
88
import { notFound, redirect } from 'next/navigation';
9-
import { setRequestLocale } from 'next-intl/server';
9+
import { locale as getRootLocale } from 'next/root-params';
1010

1111
import { setClientContext } from '#site/client-context';
1212
import WithLayout from '#site/components/withLayout';
@@ -28,11 +28,13 @@ export const generateViewport = () => ({ ...PAGE_VIEWPORT });
2828
*
2929
* @see https://nextjs.org/docs/app/api-reference/functions/generate-metadata
3030
*
31-
* @param {{ params: Promise<{ path: Array<string>; locale: string }>, prefix?: string }} props
31+
* @param {{ params: Promise<{ path: Array<string> }>, prefix?: string }} props
3232
* @returns {Promise<import('next').Metadata>} the metadata for the page
3333
*/
3434
export const generateMetadata = async ({ params, prefix }) => {
35-
const { path = [], locale = defaultLocale.code } = await params;
35+
const { path = [] } = await params;
36+
37+
const locale = (await getRootLocale()) ?? defaultLocale.code;
3638

3739
const pathname = dynamicRouter.getPathname(path);
3840

@@ -46,15 +48,16 @@ export const generateMetadata = async ({ params, prefix }) => {
4648
/**
4749
* This method is used for retrieving the current locale and pathname from the request
4850
*
51+
* The locale comes from the `[locale]` root param, so pages don't have to read it
52+
* from their own `params` and hand it over.
53+
*
4954
* @param {string|Array<string>} path
50-
* @param {string} locale
51-
* @returns {[string, string]} the locale and pathname for the request
55+
* @returns {Promise<[string, string]>} the locale and pathname for the request
5256
*/
53-
export const getLocaleAndPath = (path = [], locale = defaultLocale.code) => {
54-
if (!availableLocaleCodes.includes(locale)) {
55-
// Forces the current locale to be the Default Locale
56-
setRequestLocale(defaultLocale.code);
57+
export const getLocaleAndPath = async (path = []) => {
58+
const locale = (await getRootLocale()) ?? defaultLocale.code;
5759

60+
if (!availableLocaleCodes.includes(locale)) {
5861
if (!allLocaleCodes.includes(locale)) {
5962
// when the locale is not listed in the locales, return NotFound
6063
return notFound();
@@ -66,9 +69,6 @@ export const getLocaleAndPath = (path = [], locale = defaultLocale.code) => {
6669
return redirect(`/${defaultLocale.code}/${pathname}`);
6770
}
6871

69-
// Configures the current Locale to be the given Locale of the Request
70-
setRequestLocale(locale);
71-
7272
// Gets the current full pathname for a given path
7373
return [locale, dynamicRouter.getPathname(path)];
7474
};

0 commit comments

Comments
 (0)