From 8c6d5fff009ac3c54b2847c23b8148759d0b6ffa Mon Sep 17 00:00:00 2001 From: Jan Amann Date: Thu, 24 Oct 2024 11:33:57 +0200 Subject: [PATCH] fix: Resolve locale for navigation APIs consistently from `i18n/request.ts` in `react-server` like all other APIs do (#1459) --- docs/src/pages/docs/routing/middleware.mdx | 21 +++++++++++++------ docs/src/pages/docs/workflows/typescript.mdx | 2 +- packages/next-intl/.size-limit.ts | 2 +- .../src/navigation/createNavigation.test.tsx | 13 ++++++------ .../react-server/createNavigation.tsx | 4 ++-- .../react-server/getServerLocale.tsx | 10 +++++++++ 6 files changed, 36 insertions(+), 16 deletions(-) create mode 100644 packages/next-intl/src/navigation/react-server/getServerLocale.tsx diff --git a/docs/src/pages/docs/routing/middleware.mdx b/docs/src/pages/docs/routing/middleware.mdx index 5ce9a6fae..248d3f371 100644 --- a/docs/src/pages/docs/routing/middleware.mdx +++ b/docs/src/pages/docs/routing/middleware.mdx @@ -432,14 +432,23 @@ export default function RootPage() { ## Troubleshooting -### "Unable to find `next-intl` locale because the middleware didn't run on this request and no `locale` was returned in `getRequestConfig`." [#unable-to-find-locale] - -If the middleware is not expected to run on this request (e.g. because you're using a setup [without i18n routing](/docs/getting-started/app-router/without-i18n-routing)), you should explicitly return a `locale` from [`getRequestConfig`](/docs/usage/configuration#i18n-request) to recover from this error. +### "The middleware doesn't run for a particular page." [#middleware-not-running] -If the error occurs for pathnames where the middleware is expected to run, please make sure that: +To resolve this, make sure that: -1. The middleware is set up in the correct file (e.g. `src/middleware.ts`). +1. The [middleware](/docs/getting-started/app-router/with-i18n-routing#middleware) is set up in the correct file (e.g. `src/middleware.ts`). 2. Your middleware [matcher](#matcher-config) correctly matches all routes of your application, including dynamic segments with potentially unexpected characters like dots (e.g. `/users/jane.doe`). -3. In case you require static rendering, make sure to follow the [static rendering guide](/docs/getting-started/app-router/with-i18n-routing#static-rendering) instead of relying on hacks like [`force-static`](https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic). +3. In case you're [composing other middlewares](#composing-other-middlewares), ensure that the middleware is called correctly. +4. In case you require static rendering, make sure to follow the [static rendering guide](/docs/getting-started/app-router/with-i18n-routing#static-rendering) instead of relying on hacks like [`force-static`](https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic). + +### "My page content isn't localized despite the pathname containing a locale prefix." [#content-not-localized] + +This is very likely the result of your [middleware not running](#middleware-not-running) on the request. As a result, a potential fallback from [`i18n/request.ts`](/docs/usage/configuration#i18n-request) might be applied. + +### "Unable to find `next-intl` locale because the middleware didn't run on this request and no `locale` was returned in `getRequestConfig`." [#unable-to-find-locale] + +If the middleware _is not_ expected to run on this request (e.g. because you're using a setup [without i18n routing](/docs/getting-started/app-router/without-i18n-routing)), you should explicitly return a `locale` from [`getRequestConfig`](/docs/usage/configuration#i18n-request) to recover from this error. + +If the middleware _is_ expected to run, verify that your [middleware is set up correctly](#middleware-not-running). Note that `next-intl` will invoke the `notFound()` function to abort the render if no locale is available after `getRequestConfig` has run. You should consider adding a [`not-found` page](/docs/environments/error-files#not-foundjs) due to this. diff --git a/docs/src/pages/docs/workflows/typescript.mdx b/docs/src/pages/docs/workflows/typescript.mdx index ca2124cf7..c7014a09f 100644 --- a/docs/src/pages/docs/workflows/typescript.mdx +++ b/docs/src/pages/docs/workflows/typescript.mdx @@ -112,7 +112,7 @@ declare global { ## Troubleshooting -If you're encountering problems, please double check that: +If you're encountering problems, double check that: 1. Your interface uses the correct name. 2. You're using TypeScript version 4 or later. diff --git a/packages/next-intl/.size-limit.ts b/packages/next-intl/.size-limit.ts index aeeb383a9..191d2f8c4 100644 --- a/packages/next-intl/.size-limit.ts +++ b/packages/next-intl/.size-limit.ts @@ -33,7 +33,7 @@ const config: SizeLimitConfig = [ name: "import {createSharedPathnamesNavigation} from 'next-intl/navigation' (react-server)", path: 'dist/production/navigation.react-server.js', import: '{createSharedPathnamesNavigation}', - limit: '16.765 KB' + limit: '16.77 KB' }, { name: "import {createLocalizedPathnamesNavigation} from 'next-intl/navigation' (react-server)", diff --git a/packages/next-intl/src/navigation/createNavigation.test.tsx b/packages/next-intl/src/navigation/createNavigation.test.tsx index f8cf84035..a04ddaee4 100644 --- a/packages/next-intl/src/navigation/createNavigation.test.tsx +++ b/packages/next-intl/src/navigation/createNavigation.test.tsx @@ -9,9 +9,9 @@ import React from 'react'; import {renderToString} from 'react-dom/server'; import {beforeEach, describe, expect, it, vi} from 'vitest'; import {DomainsConfig, Pathnames, defineRouting} from '../routing'; -import {getRequestLocale} from '../server/react-server/RequestLocale'; import createNavigationClient from './react-client/createNavigation'; import createNavigationServer from './react-server/createNavigation'; +import getServerLocale from './react-server/getServerLocale'; vi.mock('react'); vi.mock('next/navigation', async () => { @@ -23,15 +23,16 @@ vi.mock('next/navigation', async () => { permanentRedirect: vi.fn() }; }); -vi.mock('../../src/server/react-server/RequestLocale'); +vi.mock('./react-server/getServerLocale'); function mockCurrentLocale(locale: string) { // Enable synchronous rendering without having to suspend - const localePromise = Promise.resolve(locale); - (localePromise as any).status = 'fulfilled'; - (localePromise as any).value = locale; + const value = locale; + const promise = Promise.resolve(value); + (promise as any).status = 'fulfilled'; + (promise as any).value = value; - vi.mocked(getRequestLocale).mockImplementation(() => localePromise); + vi.mocked(getServerLocale).mockImplementation(() => promise); vi.mocked(nextUseParams<{locale: string}>).mockImplementation(() => ({ locale diff --git a/packages/next-intl/src/navigation/react-server/createNavigation.tsx b/packages/next-intl/src/navigation/react-server/createNavigation.tsx index 13055861a..ba09f314c 100644 --- a/packages/next-intl/src/navigation/react-server/createNavigation.tsx +++ b/packages/next-intl/src/navigation/react-server/createNavigation.tsx @@ -8,8 +8,8 @@ import { Locales, Pathnames } from '../../routing/types'; -import {getRequestLocale} from '../../server/react-server/RequestLocale'; import createSharedNavigationFns from '../shared/createSharedNavigationFns'; +import getServerLocale from './getServerLocale'; export default function createNavigation< const AppLocales extends Locales, @@ -35,7 +35,7 @@ export default function createNavigation< type Locale = AppLocales extends never ? string : AppLocales[number]; function getLocale() { - return getRequestLocale() as Promise; + return getServerLocale() as Promise; } // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/packages/next-intl/src/navigation/react-server/getServerLocale.tsx b/packages/next-intl/src/navigation/react-server/getServerLocale.tsx new file mode 100644 index 000000000..b153bdf4c --- /dev/null +++ b/packages/next-intl/src/navigation/react-server/getServerLocale.tsx @@ -0,0 +1,10 @@ +import getConfig from '../../server/react-server/getConfig'; + +/** + * This is only moved to a separate module for easier mocking in + * `../createNavigatoin.test.tsx` in order to avoid suspending. + */ +export default async function getServerLocale() { + const config = await getConfig(); + return config.locale; +}