From de3bf0e778bb68d9f36d2bd071e48e732aea9861 Mon Sep 17 00:00:00 2001 From: Jan Amann Date: Wed, 18 Dec 2024 14:12:32 +0100 Subject: [PATCH 01/12] feat: Adopt `rootParams` --- examples/example-app-router/next-env.d.ts | 2 +- examples/example-app-router/package.json | 2 +- .../src/app/[locale]/layout.tsx | 27 +-- .../src/app/[locale]/page.tsx | 12 +- .../src/app/[locale]/pathnames/page.tsx | 12 +- .../src/app/{ => [locale]}/styles.css | 0 .../example-app-router/src/app/layout.tsx | 12 -- .../example-app-router/src/app/not-found.tsx | 15 -- examples/example-app-router/src/app/page.tsx | 6 - .../src/components/BaseLayout.tsx | 2 + .../example-app-router/src/i18n/request.ts | 10 +- .../example-app-router/tests/main.spec.ts | 4 +- packages/use-intl/src/core/hasLocale.tsx | 2 +- pnpm-lock.yaml | 194 +++++++++++++++--- 14 files changed, 185 insertions(+), 115 deletions(-) rename examples/example-app-router/src/app/{ => [locale]}/styles.css (100%) delete mode 100644 examples/example-app-router/src/app/layout.tsx delete mode 100644 examples/example-app-router/src/app/not-found.tsx delete mode 100644 examples/example-app-router/src/app/page.tsx diff --git a/examples/example-app-router/next-env.d.ts b/examples/example-app-router/next-env.d.ts index 40c3d6809..1b3be0840 100644 --- a/examples/example-app-router/next-env.d.ts +++ b/examples/example-app-router/next-env.d.ts @@ -2,4 +2,4 @@ /// // NOTE: This file should not be edited -// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/examples/example-app-router/package.json b/examples/example-app-router/package.json index c6eeb30e1..6bf531001 100644 --- a/examples/example-app-router/package.json +++ b/examples/example-app-router/package.json @@ -12,7 +12,7 @@ }, "dependencies": { "clsx": "^2.1.1", - "next": "^14.2.4", + "next": "15.1.1-canary.12", "next-intl": "^3.0.0", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/examples/example-app-router/src/app/[locale]/layout.tsx b/examples/example-app-router/src/app/[locale]/layout.tsx index 7dc3091ae..de03e0a24 100644 --- a/examples/example-app-router/src/app/[locale]/layout.tsx +++ b/examples/example-app-router/src/app/[locale]/layout.tsx @@ -1,39 +1,28 @@ -import {notFound} from 'next/navigation'; -import {Locale, hasLocale} from 'next-intl'; -import {getTranslations, setRequestLocale} from 'next-intl/server'; +import {getLocale, getTranslations} from 'next-intl/server'; import {ReactNode} from 'react'; import BaseLayout from '@/components/BaseLayout'; import {routing} from '@/i18n/routing'; +import './styles.css'; type Props = { children: ReactNode; - params: {locale: Locale}; }; export function generateStaticParams() { return routing.locales.map((locale) => ({locale})); } -export async function generateMetadata({ - params: {locale} -}: Omit) { - const t = await getTranslations({locale, namespace: 'LocaleLayout'}); +export const dynamicParams = false; + +export async function generateMetadata() { + const t = await getTranslations('LocaleLayout'); return { title: t('title') }; } -export default async function LocaleLayout({ - children, - params: {locale} -}: Props) { - if (!hasLocale(routing.locales, locale)) { - notFound(); - } - - // Enable static rendering - setRequestLocale(locale); - +export default async function LocaleLayout({children}: Props) { + const locale = await getLocale(); return {children}; } diff --git a/examples/example-app-router/src/app/[locale]/page.tsx b/examples/example-app-router/src/app/[locale]/page.tsx index ea963c340..c3a9bd142 100644 --- a/examples/example-app-router/src/app/[locale]/page.tsx +++ b/examples/example-app-router/src/app/[locale]/page.tsx @@ -1,15 +1,7 @@ -import {Locale, useTranslations} from 'next-intl'; -import {setRequestLocale} from 'next-intl/server'; +import {useTranslations} from 'next-intl'; import PageLayout from '@/components/PageLayout'; -type Props = { - params: {locale: Locale}; -}; - -export default function IndexPage({params: {locale}}: Props) { - // Enable static rendering - setRequestLocale(locale); - +export default function IndexPage() { const t = useTranslations('IndexPage'); return ( diff --git a/examples/example-app-router/src/app/[locale]/pathnames/page.tsx b/examples/example-app-router/src/app/[locale]/pathnames/page.tsx index b53ab8535..ecadf9581 100644 --- a/examples/example-app-router/src/app/[locale]/pathnames/page.tsx +++ b/examples/example-app-router/src/app/[locale]/pathnames/page.tsx @@ -1,15 +1,7 @@ -import {Locale, useTranslations} from 'next-intl'; -import {setRequestLocale} from 'next-intl/server'; +import {useTranslations} from 'next-intl'; import PageLayout from '@/components/PageLayout'; -type Props = { - params: {locale: Locale}; -}; - -export default function PathnamesPage({params: {locale}}: Props) { - // Enable static rendering - setRequestLocale(locale); - +export default function PathnamesPage() { const t = useTranslations('PathnamesPage'); return ( diff --git a/examples/example-app-router/src/app/styles.css b/examples/example-app-router/src/app/[locale]/styles.css similarity index 100% rename from examples/example-app-router/src/app/styles.css rename to examples/example-app-router/src/app/[locale]/styles.css diff --git a/examples/example-app-router/src/app/layout.tsx b/examples/example-app-router/src/app/layout.tsx deleted file mode 100644 index aae3d9f0c..000000000 --- a/examples/example-app-router/src/app/layout.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import {ReactNode} from 'react'; -import './styles.css'; - -type Props = { - children: ReactNode; -}; - -// Since we have a `not-found.tsx` page on the root, a layout file -// is required, even if it's just passing children through. -export default function RootLayout({children}: Props) { - return children; -} diff --git a/examples/example-app-router/src/app/not-found.tsx b/examples/example-app-router/src/app/not-found.tsx deleted file mode 100644 index 69e748520..000000000 --- a/examples/example-app-router/src/app/not-found.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import BaseLayout from '@/components/BaseLayout'; -import NotFoundPage from '@/components/NotFoundPage'; -import {routing} from '@/i18n/routing'; - -// This page renders when a route like `/unknown.txt` is requested. -// In this case, the layout at `app/[locale]/layout.tsx` receives -// an invalid value as the `[locale]` param and calls `notFound()`. - -export default function GlobalNotFound() { - return ( - - - - ); -} diff --git a/examples/example-app-router/src/app/page.tsx b/examples/example-app-router/src/app/page.tsx deleted file mode 100644 index d5d37ccca..000000000 --- a/examples/example-app-router/src/app/page.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import {redirect} from 'next/navigation'; - -// This page only renders when the app is built statically (output: 'export') -export default function RootPage() { - redirect('/en'); -} diff --git a/examples/example-app-router/src/components/BaseLayout.tsx b/examples/example-app-router/src/components/BaseLayout.tsx index 8907d4e9b..6aa7bb2ba 100644 --- a/examples/example-app-router/src/components/BaseLayout.tsx +++ b/examples/example-app-router/src/components/BaseLayout.tsx @@ -12,6 +12,8 @@ type Props = { locale: string; }; +// Inline? + export default async function BaseLayout({children, locale}: Props) { // Providing all messages to the client // side is the easiest way to get started diff --git a/examples/example-app-router/src/i18n/request.ts b/examples/example-app-router/src/i18n/request.ts index 370fc6d0c..81ba3e509 100644 --- a/examples/example-app-router/src/i18n/request.ts +++ b/examples/example-app-router/src/i18n/request.ts @@ -1,12 +1,12 @@ +import {unstable_rootParams as rootParams} from 'next/server'; import {hasLocale} from 'next-intl'; import {getRequestConfig} from 'next-intl/server'; import {routing} from './routing'; -export default getRequestConfig(async ({requestLocale}) => { - // Typically corresponds to the `[locale]` segment - const requested = await requestLocale; - const locale = hasLocale(routing.locales, requested) - ? requested +export default getRequestConfig(async () => { + const params = await rootParams(); + const locale = hasLocale(routing.locales, params.locale) + ? params.locale : routing.defaultLocale; return { diff --git a/examples/example-app-router/tests/main.spec.ts b/examples/example-app-router/tests/main.spec.ts index 8fcb44cf8..94e31e79a 100644 --- a/examples/example-app-router/tests/main.spec.ts +++ b/examples/example-app-router/tests/main.spec.ts @@ -36,8 +36,8 @@ it("handles not found pages for routes that don't match the middleware", async ( it('sets caching headers', async ({request}) => { for (const pathname of ['/en', '/en/pathnames', '/de', '/de/pfadnamen']) { - expect((await request.get(pathname)).headers()['cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate' + expect((await request.get(pathname)).headers()['cache-control']).toContain( + 's-maxage=31536000' ); } }); diff --git a/packages/use-intl/src/core/hasLocale.tsx b/packages/use-intl/src/core/hasLocale.tsx index 5bb68714c..576ee3289 100644 --- a/packages/use-intl/src/core/hasLocale.tsx +++ b/packages/use-intl/src/core/hasLocale.tsx @@ -7,7 +7,7 @@ import type {Locale} from './AppConfig.tsx'; */ export default function hasLocale( locales: ReadonlyArray, - candidate?: string | null + candidate: unknown ): candidate is LocaleType { return locales.includes(candidate as LocaleType); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ec944187c..c76948fb0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -40,10 +40,10 @@ importers: version: 2.1.5(react@18.3.1) '@vercel/analytics': specifier: 1.3.1 - version: 1.3.1(next@14.2.16(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) + version: 1.3.1(next@14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) '@vercel/speed-insights': specifier: ^1.0.12 - version: 1.0.13(next@14.2.16(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) + version: 1.0.13(next@14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) clsx: specifier: ^2.1.1 version: 2.1.1 @@ -55,10 +55,10 @@ importers: version: 14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) nextra: specifier: ^3.1.0 - version: 3.1.0(@types/react@18.3.12)(acorn@8.13.0)(next@14.2.16(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + version: 3.1.0(@types/react@18.3.12)(acorn@8.13.0)(next@14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) nextra-theme-docs: specifier: ^3.1.0 - version: 3.1.0(next@14.2.16(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@3.1.0(@types/react@18.3.12)(acorn@8.13.0)(next@14.2.16(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 3.1.0(next@14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@3.1.0(@types/react@18.3.12)(acorn@8.13.0)(next@14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: specifier: ^18.3.1 version: 18.3.1 @@ -83,13 +83,13 @@ importers: version: 18.3.12 autoprefixer: specifier: ^10.4.19 - version: 10.4.20(postcss@8.4.39) + version: 10.4.20(postcss@8.4.47) eslint: specifier: ^9.11.1 version: 9.13.0(jiti@2.3.3) eslint-config-molindo: specifier: ^8.0.0 - version: 8.0.0(@typescript-eslint/eslint-plugin@8.11.0(@typescript-eslint/parser@8.9.0(eslint@9.13.0(jiti@2.3.3))(typescript@5.6.3))(eslint@9.13.0(jiti@2.3.3))(typescript@5.6.3))(@typescript-eslint/parser@8.9.0(eslint@9.13.0(jiti@2.3.3))(typescript@5.6.3))(eslint@9.13.0(jiti@2.3.3))(jest@29.7.0(@types/node@20.17.0))(tailwindcss@3.4.14)(typescript@5.6.3)(vitest@2.1.3(@edge-runtime/vm@4.0.3)(@types/node@20.17.0)(jsdom@25.0.1)(terser@5.36.0)) + version: 8.0.0(@typescript-eslint/eslint-plugin@8.11.0(@typescript-eslint/parser@8.11.0(eslint@9.13.0(jiti@2.3.3))(typescript@5.6.3))(eslint@9.13.0(jiti@2.3.3))(typescript@5.6.3))(@typescript-eslint/parser@8.11.0(eslint@9.13.0(jiti@2.3.3))(typescript@5.6.3))(eslint@9.13.0(jiti@2.3.3))(jest@29.7.0(@types/node@20.17.0))(tailwindcss@3.4.14)(typescript@5.6.3)(vitest@2.1.3(@edge-runtime/vm@4.0.3)(@types/node@20.17.0)(jsdom@25.0.1)(terser@5.36.0)) fast-glob: specifier: ^3.3.2 version: 3.3.2 @@ -98,7 +98,7 @@ importers: version: 15.11.0 next-sitemap: specifier: ^4.2.3 - version: 4.2.3(next@14.2.16(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) + version: 4.2.3(next@14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) next-validate-link: specifier: ^1.3.0 version: 1.3.0 @@ -115,8 +115,8 @@ importers: specifier: ^2.1.1 version: 2.1.1 next: - specifier: ^14.2.4 - version: 14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: 15.1.1-canary.12 + version: 15.1.1-canary.12(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next-intl: specifier: ^3.0.0 version: link:../../packages/next-intl @@ -159,7 +159,7 @@ importers: version: 9.13.0(jiti@2.3.3) eslint-config-molindo: specifier: ^8.0.0 - version: 8.0.0(@typescript-eslint/eslint-plugin@8.11.0(@typescript-eslint/parser@8.11.0(eslint@9.13.0(jiti@2.3.3))(typescript@5.6.3))(eslint@9.13.0(jiti@2.3.3))(typescript@5.6.3))(@typescript-eslint/parser@8.11.0(eslint@9.13.0(jiti@2.3.3))(typescript@5.6.3))(eslint@9.13.0(jiti@2.3.3))(jest@29.7.0(@types/node@20.17.0))(tailwindcss@3.4.14)(typescript@5.6.3)(vitest@2.1.3(@edge-runtime/vm@4.0.3)(@types/node@20.17.0)(jsdom@25.0.1)(terser@5.36.0)) + version: 8.0.0(@typescript-eslint/eslint-plugin@8.11.0(@typescript-eslint/parser@8.9.0(eslint@9.13.0(jiti@2.3.3))(typescript@5.6.3))(eslint@9.13.0(jiti@2.3.3))(typescript@5.6.3))(@typescript-eslint/parser@8.9.0(eslint@9.13.0(jiti@2.3.3))(typescript@5.6.3))(eslint@9.13.0(jiti@2.3.3))(jest@29.7.0(@types/node@20.17.0))(tailwindcss@3.4.14)(typescript@5.6.3)(vitest@2.1.3(@edge-runtime/vm@4.0.3)(@types/node@20.17.0)(jsdom@25.0.1)(terser@5.36.0)) jest: specifier: ^29.7.0 version: 29.7.0(@types/node@20.17.0) @@ -266,7 +266,7 @@ importers: version: 14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next-auth: specifier: ^4.24.7 - version: 4.24.8(next@14.2.16(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 4.24.8(next@14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next-intl: specifier: ^3.0.0 version: link:../../packages/next-intl @@ -617,7 +617,7 @@ importers: dependencies: next: specifier: ^12.0.0 - version: 12.3.4(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + version: 12.3.4(@babel/core@7.25.9)(react-dom@17.0.2(react@17.0.2))(react@17.0.2) next-intl: specifier: ^3.0.0 version: link:../../packages/next-intl @@ -2746,7 +2746,7 @@ packages: '@expo/bunyan@4.0.1': resolution: {integrity: sha512-+Lla7nYSiHZirgK+U/uYzsLv/X+HaJienbD5AKX1UQZHYfWaP+9uuQluRB4GrEVWF0GZ7vEVp/jzaOT9k/SQlg==} - engines: {'0': node >=0.10.0} + engines: {node: '>=0.10.0'} '@expo/cli@0.4.11': resolution: {integrity: sha512-L9Ci9RBh0aPFEDF1AjDYPk54OgeUJIKzxF3lRgITm+lQpI3IEKjAc9LaYeQeO1mlZMUQmPkHArF8iyz1eOeVoQ==} @@ -3385,6 +3385,9 @@ packages: '@next/env@14.2.16': resolution: {integrity: sha512-fLrX5TfJzHCbnZ9YUSnGW63tMV3L4nSfhgOQ0iCcX21Pt+VSTDuaLsSuL8J/2XAiVA5AnzvXDpf6pMs60QxOag==} + '@next/env@15.1.1-canary.12': + resolution: {integrity: sha512-8jh0yUEk+tGbgaZWBCU+kYD7y0sukwGTFdBBEbbwvXFSHuPcd65R6ss5HfxjNXqxx8gs4+uFVlUFfthLMc3Tlg==} + '@next/mdx@14.2.16': resolution: {integrity: sha512-IVd/Z3vYpIZ/nzqhmSHmTKfSrQ6eZrorkzs0uzMCXt3hL6lw4qf/jcEExZmXoenqnZ5vX0xkou+y4uWAKZhvfw==} peerDependencies: @@ -3420,6 +3423,12 @@ packages: cpu: [arm64] os: [darwin] + '@next/swc-darwin-arm64@15.1.1-canary.12': + resolution: {integrity: sha512-sOsUB3xS5dMPvw6+xM5h8n8F6c37UvmQS8VBr5l0R8XrcmcfHn13NdAWsxGptvePCdqK16m7m7rgHD60OuCvbg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + '@next/swc-darwin-x64@12.3.4': resolution: {integrity: sha512-PPF7tbWD4k0dJ2EcUSnOsaOJ5rhT3rlEt/3LhZUGiYNL8KvoqczFrETlUx0cUYaXe11dRA3F80Hpt727QIwByQ==} engines: {node: '>= 10'} @@ -3432,6 +3441,12 @@ packages: cpu: [x64] os: [darwin] + '@next/swc-darwin-x64@15.1.1-canary.12': + resolution: {integrity: sha512-EqXWaJfJD0H28SFSs6sh5iNHCWNHlv5ZWNwEXOYqfK7NG0KEx1NmhFzTiefWcvgE1Mwtbt1W4iZriiiLuVadTQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + '@next/swc-freebsd-x64@12.3.4': resolution: {integrity: sha512-KM9JXRXi/U2PUM928z7l4tnfQ9u8bTco/jb939pdFUHqc28V43Ohd31MmZD1QzEK4aFlMRaIBQOWQZh4D/E5lQ==} engines: {node: '>= 10'} @@ -3456,6 +3471,12 @@ packages: cpu: [arm64] os: [linux] + '@next/swc-linux-arm64-gnu@15.1.1-canary.12': + resolution: {integrity: sha512-2G2o06m/5NGQ+H9AiykuvfjEoC422NfzgcSSoP9Jo1RpWGUMcxH8/e46/Ljq6zrtRDXLlA8NqxE7KM8/xWHYIg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + '@next/swc-linux-arm64-musl@12.3.4': resolution: {integrity: sha512-EETZPa1juczrKLWk5okoW2hv7D7WvonU+Cf2CgsSoxgsYbUCZ1voOpL4JZTOb6IbKMDo6ja+SbY0vzXZBUMvkQ==} engines: {node: '>= 10'} @@ -3468,6 +3489,12 @@ packages: cpu: [arm64] os: [linux] + '@next/swc-linux-arm64-musl@15.1.1-canary.12': + resolution: {integrity: sha512-iiDrM7WvT2gGI0ujEkDqOymQs+q2+EmdCgxnXyEX6pzErcXvsNH2KMHcNNlCbE2mXy0iXRjCJbJPOTc1y+EUsw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + '@next/swc-linux-x64-gnu@12.3.4': resolution: {integrity: sha512-4csPbRbfZbuWOk3ATyWcvVFdD9/Rsdq5YHKvRuEni68OCLkfy4f+4I9OBpyK1SKJ00Cih16NJbHE+k+ljPPpag==} engines: {node: '>= 10'} @@ -3480,6 +3507,12 @@ packages: cpu: [x64] os: [linux] + '@next/swc-linux-x64-gnu@15.1.1-canary.12': + resolution: {integrity: sha512-xIj8FnwQReI7OZLqYHf0NpcseiLhJD69S8Ln5PjrRKQhrUV52YVZekULVNLXk/ZPJt3TtsRqJgEAkRlFhnpSxQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + '@next/swc-linux-x64-musl@12.3.4': resolution: {integrity: sha512-YeBmI+63Ro75SUiL/QXEVXQ19T++58aI/IINOyhpsRL1LKdyfK/35iilraZEFz9bLQrwy1LYAR5lK200A9Gjbg==} engines: {node: '>= 10'} @@ -3492,6 +3525,12 @@ packages: cpu: [x64] os: [linux] + '@next/swc-linux-x64-musl@15.1.1-canary.12': + resolution: {integrity: sha512-0OtOIleTRWZu4Xwia4t8vj4Blv8CIQJdOru/M4H7NOCIGeD362ecQEjE8RIric3jlWN/9FPXa4Evs2OJVpvLBw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + '@next/swc-win32-arm64-msvc@12.3.4': resolution: {integrity: sha512-Sd0qFUJv8Tj0PukAYbCCDbmXcMkbIuhnTeHm9m4ZGjCf6kt7E/RMs55Pd3R5ePjOkN7dJEuxYBehawTR/aPDSQ==} engines: {node: '>= 10'} @@ -3504,6 +3543,12 @@ packages: cpu: [arm64] os: [win32] + '@next/swc-win32-arm64-msvc@15.1.1-canary.12': + resolution: {integrity: sha512-8fkPT1nliopZkbicjnocR33+cXzb8CGypIFMisnd6YROoF5484ot4LtLXMNzKRyrTJAYVXNq0uyPO4f87wVXwA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + '@next/swc-win32-ia32-msvc@12.3.4': resolution: {integrity: sha512-rt/vv/vg/ZGGkrkKcuJ0LyliRdbskQU+91bje+PgoYmxTZf/tYs6IfbmgudBJk6gH3QnjHWbkphDdRQrseRefQ==} engines: {node: '>= 10'} @@ -3528,6 +3573,12 @@ packages: cpu: [x64] os: [win32] + '@next/swc-win32-x64-msvc@15.1.1-canary.12': + resolution: {integrity: sha512-FdXcNAn0C1GB1CZ6vKu/4h8bkcaQjoBiXxR7z0H2rSnd0TbMjnvA25+Ofxe+NuiYcjN05vEihPiFUp5RyYaglw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -4525,6 +4576,9 @@ packages: '@swc/helpers@0.4.11': resolution: {integrity: sha512-rEUrBSGIoSFuYxwBYtlUFMlE2CwGhmW+w9355/5oduSw8e5h2+Tj4UrAGNNgP9915++wj5vkQo0UuOBqOAq4nw==} + '@swc/helpers@0.5.15': + resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} + '@swc/helpers@0.5.5': resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==} @@ -5179,6 +5233,7 @@ packages: '@xmldom/xmldom@0.7.13': resolution: {integrity: sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g==} engines: {node: '>=10.0.0'} + deprecated: this version is no longer supported, please update to at least 0.8.* '@xmldom/xmldom@0.8.10': resolution: {integrity: sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==} @@ -10551,6 +10606,27 @@ packages: sass: optional: true + next@15.1.1-canary.12: + resolution: {integrity: sha512-CSJXkPtNUOt3FptORfeOTCuCCJ0hdG9Lk6ZglTU21KMwlXWtVYkZyp/r0tmZUrXiJ8YIkccq4p8kpQIGbsQRhA==} + engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.41.2 + babel-plugin-react-compiler: '*' + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + babel-plugin-react-compiler: + optional: true + sass: + optional: true + nextra-theme-docs@3.1.0: resolution: {integrity: sha512-2zAC+xnqLzl/kLYCaoVfdupyA6pD5OgF+4iR3zQiPOzfnwJikPQePnr3SCT+tPPgYVuoqSDA5GNc9DvvAHtefQ==} peerDependencies: @@ -11758,6 +11834,7 @@ packages: engines: {node: '>=0.6.0', teleport: '>=0.2.0'} deprecated: |- You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other. + (For a CapTP with native promises, see @endo/eventual-send and @endo/captp) qrcode-terminal@0.11.0: @@ -13062,12 +13139,15 @@ packages: sudo-prompt@8.2.5: resolution: {integrity: sha512-rlBo3HU/1zAJUrkY6jNxDOC9eVYliG6nS4JA8u8KAshITd07tafMc/Br7xQwCSseXwJ2iCcHCE8SNWX3q8Z+kw==} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. sudo-prompt@9.1.1: resolution: {integrity: sha512-es33J1g2HjMpyAhz8lOR+ICmXXAqTuKbuXuUWLhOLew20oN9oUCgCJx615U/v7aioZg7IX5lIh9x34vwneu4pA==} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. sudo-prompt@9.2.1: resolution: {integrity: sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. supports-color@4.5.0: resolution: {integrity: sha512-ycQR/UbvI9xIlEdQT1TQqwoXtEldExbCEAJgRo5YXlmSKjv6ThHnP9/vwGa1gr19Gfw+LkFd7KqYMhzrRC5JYw==} @@ -17426,6 +17506,8 @@ snapshots: '@next/env@14.2.16': {} + '@next/env@15.1.1-canary.12': {} + '@next/mdx@14.2.16(@mdx-js/loader@3.1.0(webpack@5.95.0(esbuild@0.23.1)))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))': dependencies: source-map: 0.7.4 @@ -17445,12 +17527,18 @@ snapshots: '@next/swc-darwin-arm64@14.2.16': optional: true + '@next/swc-darwin-arm64@15.1.1-canary.12': + optional: true + '@next/swc-darwin-x64@12.3.4': optional: true '@next/swc-darwin-x64@14.2.16': optional: true + '@next/swc-darwin-x64@15.1.1-canary.12': + optional: true + '@next/swc-freebsd-x64@12.3.4': optional: true @@ -17463,30 +17551,45 @@ snapshots: '@next/swc-linux-arm64-gnu@14.2.16': optional: true + '@next/swc-linux-arm64-gnu@15.1.1-canary.12': + optional: true + '@next/swc-linux-arm64-musl@12.3.4': optional: true '@next/swc-linux-arm64-musl@14.2.16': optional: true + '@next/swc-linux-arm64-musl@15.1.1-canary.12': + optional: true + '@next/swc-linux-x64-gnu@12.3.4': optional: true '@next/swc-linux-x64-gnu@14.2.16': optional: true + '@next/swc-linux-x64-gnu@15.1.1-canary.12': + optional: true + '@next/swc-linux-x64-musl@12.3.4': optional: true '@next/swc-linux-x64-musl@14.2.16': optional: true + '@next/swc-linux-x64-musl@15.1.1-canary.12': + optional: true + '@next/swc-win32-arm64-msvc@12.3.4': optional: true '@next/swc-win32-arm64-msvc@14.2.16': optional: true + '@next/swc-win32-arm64-msvc@15.1.1-canary.12': + optional: true + '@next/swc-win32-ia32-msvc@12.3.4': optional: true @@ -17499,6 +17602,9 @@ snapshots: '@next/swc-win32-x64-msvc@14.2.16': optional: true + '@next/swc-win32-x64-msvc@15.1.1-canary.12': + optional: true + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -18891,6 +18997,10 @@ snapshots: dependencies: tslib: 2.8.0 + '@swc/helpers@0.5.15': + dependencies: + tslib: 2.8.0 + '@swc/helpers@0.5.5': dependencies: '@swc/counter': 0.1.3 @@ -19523,14 +19633,14 @@ snapshots: '@vanilla-extract/private@1.0.3': {} - '@vercel/analytics@1.3.1(next@14.2.16(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)': + '@vercel/analytics@1.3.1(next@14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)': dependencies: server-only: 0.0.1 optionalDependencies: next: 14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 - '@vercel/speed-insights@1.0.13(next@14.2.16(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)': + '@vercel/speed-insights@1.0.13(next@14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)': optionalDependencies: next: 14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 @@ -20206,16 +20316,6 @@ snapshots: atob@2.1.2: {} - autoprefixer@10.4.20(postcss@8.4.39): - dependencies: - browserslist: 4.24.0 - caniuse-lite: 1.0.30001669 - fraction.js: 4.3.7 - normalize-range: 0.1.2 - picocolors: 1.0.1 - postcss: 8.4.39 - postcss-value-parser: 4.2.0 - autoprefixer@10.4.20(postcss@8.4.47): dependencies: browserslist: 4.24.0 @@ -27347,7 +27447,7 @@ snapshots: dependencies: type-fest: 2.19.0 - next-auth@4.24.8(next@14.2.16(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next-auth@4.24.8(next@14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.24.7 '@panva/hkdf': 1.2.0 @@ -27362,7 +27462,7 @@ snapshots: react-dom: 18.3.1(react@18.3.1) uuid: 8.3.2 - next-sitemap@4.2.3(next@14.2.16(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)): + next-sitemap@4.2.3(next@14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)): dependencies: '@corex/deepmerge': 4.0.43 '@next/env': 13.5.6 @@ -27386,7 +27486,7 @@ snapshots: transitivePeerDependencies: - supports-color - next@12.3.4(react-dom@17.0.2(react@17.0.2))(react@17.0.2): + next@12.3.4(@babel/core@7.25.9)(react-dom@17.0.2(react@17.0.2))(react@17.0.2): dependencies: '@next/env': 12.3.4 '@swc/helpers': 0.4.11 @@ -27394,7 +27494,7 @@ snapshots: postcss: 8.4.14 react: 17.0.2 react-dom: 17.0.2(react@17.0.2) - styled-jsx: 5.0.7(react@17.0.2) + styled-jsx: 5.0.7(@babel/core@7.25.9)(react@17.0.2) use-sync-external-store: 1.2.0(react@17.0.2) optionalDependencies: '@next/swc-android-arm-eabi': 12.3.4 @@ -27440,7 +27540,33 @@ snapshots: - '@babel/core' - babel-plugin-macros - nextra-theme-docs@3.1.0(next@14.2.16(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@3.1.0(@types/react@18.3.12)(acorn@8.13.0)(next@14.2.16(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next@15.1.1-canary.12(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@next/env': 15.1.1-canary.12 + '@swc/counter': 0.1.3 + '@swc/helpers': 0.5.15 + busboy: 1.6.0 + caniuse-lite: 1.0.30001669 + postcss: 8.4.31 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + styled-jsx: 5.1.6(@babel/core@7.25.9)(react@18.3.1) + optionalDependencies: + '@next/swc-darwin-arm64': 15.1.1-canary.12 + '@next/swc-darwin-x64': 15.1.1-canary.12 + '@next/swc-linux-arm64-gnu': 15.1.1-canary.12 + '@next/swc-linux-arm64-musl': 15.1.1-canary.12 + '@next/swc-linux-x64-gnu': 15.1.1-canary.12 + '@next/swc-linux-x64-musl': 15.1.1-canary.12 + '@next/swc-win32-arm64-msvc': 15.1.1-canary.12 + '@next/swc-win32-x64-msvc': 15.1.1-canary.12 + '@playwright/test': 1.48.1 + sharp: 0.33.5 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + + nextra-theme-docs@3.1.0(next@14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@3.1.0(@types/react@18.3.12)(acorn@8.13.0)(next@14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@headlessui/react': 2.1.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1) clsx: 2.1.1 @@ -27448,13 +27574,13 @@ snapshots: flexsearch: 0.7.43 next: 14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next-themes: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - nextra: 3.1.0(@types/react@18.3.12)(acorn@8.13.0)(next@14.2.16(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + nextra: 3.1.0(@types/react@18.3.12)(acorn@8.13.0)(next@14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) scroll-into-view-if-needed: 3.1.0 zod: 3.23.8 - nextra@3.1.0(@types/react@18.3.12)(acorn@8.13.0)(next@14.2.16(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3): + nextra@3.1.0(@types/react@18.3.12)(acorn@8.13.0)(next@14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3): dependencies: '@formatjs/intl-localematcher': 0.5.5 '@headlessui/react': 2.1.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -30512,9 +30638,11 @@ snapshots: dependencies: inline-style-parser: 0.2.4 - styled-jsx@5.0.7(react@17.0.2): + styled-jsx@5.0.7(@babel/core@7.25.9)(react@17.0.2): dependencies: react: 17.0.2 + optionalDependencies: + '@babel/core': 7.25.9 styled-jsx@5.1.1(@babel/core@7.25.9)(react@18.3.1): dependencies: From b1882217fa1f5e390907e9ce646f021c1bba5bcb Mon Sep 17 00:00:00 2001 From: Jan Amann Date: Wed, 18 Dec 2024 14:48:50 +0100 Subject: [PATCH 02/12] wip --- examples/example-app-router/src/app/[locale]/layout.tsx | 5 ++--- examples/example-app-router/src/components/BaseLayout.tsx | 7 ++++--- .../example-app-router/src/components/Navigation.spec.tsx | 4 +++- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/examples/example-app-router/src/app/[locale]/layout.tsx b/examples/example-app-router/src/app/[locale]/layout.tsx index de03e0a24..980e8defb 100644 --- a/examples/example-app-router/src/app/[locale]/layout.tsx +++ b/examples/example-app-router/src/app/[locale]/layout.tsx @@ -1,4 +1,4 @@ -import {getLocale, getTranslations} from 'next-intl/server'; +import {getTranslations} from 'next-intl/server'; import {ReactNode} from 'react'; import BaseLayout from '@/components/BaseLayout'; import {routing} from '@/i18n/routing'; @@ -23,6 +23,5 @@ export async function generateMetadata() { } export default async function LocaleLayout({children}: Props) { - const locale = await getLocale(); - return {children}; + return {children}; } diff --git a/examples/example-app-router/src/components/BaseLayout.tsx b/examples/example-app-router/src/components/BaseLayout.tsx index 6aa7bb2ba..302d670d2 100644 --- a/examples/example-app-router/src/components/BaseLayout.tsx +++ b/examples/example-app-router/src/components/BaseLayout.tsx @@ -1,7 +1,7 @@ import {clsx} from 'clsx'; import {Inter} from 'next/font/google'; import {NextIntlClientProvider} from 'next-intl'; -import {getMessages} from 'next-intl/server'; +import {getLocale, getMessages} from 'next-intl/server'; import {ReactNode} from 'react'; import Navigation from '@/components/Navigation'; @@ -9,12 +9,13 @@ const inter = Inter({subsets: ['latin']}); type Props = { children: ReactNode; - locale: string; }; // Inline? -export default async function BaseLayout({children, locale}: Props) { +export default async function BaseLayout({children}: Props) { + const locale = await getLocale(); + // Providing all messages to the client // side is the easiest way to get started const messages = await getMessages(); diff --git a/examples/example-app-router/src/components/Navigation.spec.tsx b/examples/example-app-router/src/components/Navigation.spec.tsx index ba25fad02..3f6904b03 100644 --- a/examples/example-app-router/src/components/Navigation.spec.tsx +++ b/examples/example-app-router/src/components/Navigation.spec.tsx @@ -19,7 +19,9 @@ jest.mock('next/navigation', () => ({ useSelectedLayoutSegment: () => ({locale: 'en'}) })); -it('renders', () => { +// Disabled until canary version is reverted +// eslint-disable-next-line jest/no-disabled-tests +it.skip('renders', () => { render( Date: Wed, 18 Dec 2024 14:57:49 +0100 Subject: [PATCH 03/12] root redirect --- .../src/app/(unlocalized)/layout.tsx | 10 ++++++++++ .../example-app-router/src/app/(unlocalized)/page.tsx | 6 ++++++ 2 files changed, 16 insertions(+) create mode 100644 examples/example-app-router/src/app/(unlocalized)/layout.tsx create mode 100644 examples/example-app-router/src/app/(unlocalized)/page.tsx diff --git a/examples/example-app-router/src/app/(unlocalized)/layout.tsx b/examples/example-app-router/src/app/(unlocalized)/layout.tsx new file mode 100644 index 000000000..b2b918bd7 --- /dev/null +++ b/examples/example-app-router/src/app/(unlocalized)/layout.tsx @@ -0,0 +1,10 @@ +import {ReactNode} from 'react'; + +type Props = { + children: ReactNode; +}; + +export default function RootLayout({children}: Props) { + // No need for a layout, as this only renders a redirect + return children; +} diff --git a/examples/example-app-router/src/app/(unlocalized)/page.tsx b/examples/example-app-router/src/app/(unlocalized)/page.tsx new file mode 100644 index 000000000..66962d12d --- /dev/null +++ b/examples/example-app-router/src/app/(unlocalized)/page.tsx @@ -0,0 +1,6 @@ +import {redirect} from 'next/navigation'; + +// This page only renders when the app is built statically (output: 'export') +export default function RootRedirect() { + return redirect('/en'); +} From a7930c728c62df30d46cf021bb9677e38762b245 Mon Sep 17 00:00:00 2001 From: Jan Amann Date: Wed, 18 Dec 2024 17:57:32 +0100 Subject: [PATCH 04/12] wip --- examples/example-app-router/src/app/[locale]/layout.tsx | 1 - examples/example-app-router/src/components/BaseLayout.tsx | 1 + examples/example-app-router/src/{app/[locale] => }/styles.css | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename examples/example-app-router/src/{app/[locale] => }/styles.css (100%) diff --git a/examples/example-app-router/src/app/[locale]/layout.tsx b/examples/example-app-router/src/app/[locale]/layout.tsx index 980e8defb..472481eeb 100644 --- a/examples/example-app-router/src/app/[locale]/layout.tsx +++ b/examples/example-app-router/src/app/[locale]/layout.tsx @@ -2,7 +2,6 @@ import {getTranslations} from 'next-intl/server'; import {ReactNode} from 'react'; import BaseLayout from '@/components/BaseLayout'; import {routing} from '@/i18n/routing'; -import './styles.css'; type Props = { children: ReactNode; diff --git a/examples/example-app-router/src/components/BaseLayout.tsx b/examples/example-app-router/src/components/BaseLayout.tsx index 302d670d2..d4bec12fb 100644 --- a/examples/example-app-router/src/components/BaseLayout.tsx +++ b/examples/example-app-router/src/components/BaseLayout.tsx @@ -4,6 +4,7 @@ import {NextIntlClientProvider} from 'next-intl'; import {getLocale, getMessages} from 'next-intl/server'; import {ReactNode} from 'react'; import Navigation from '@/components/Navigation'; +import '../styles.css'; const inter = Inter({subsets: ['latin']}); diff --git a/examples/example-app-router/src/app/[locale]/styles.css b/examples/example-app-router/src/styles.css similarity index 100% rename from examples/example-app-router/src/app/[locale]/styles.css rename to examples/example-app-router/src/styles.css From c2af326f15eeb73c20b8fafbd3f10a9ba7082fcd Mon Sep 17 00:00:00 2001 From: Jan Amann Date: Thu, 19 Dec 2024 11:41:03 +0100 Subject: [PATCH 05/12] Re-introduce `({locale})` --- packages/next-intl/src/server/react-server/getConfig.tsx | 2 ++ .../src/server/react-server/getRequestConfig.tsx | 9 ++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/next-intl/src/server/react-server/getConfig.tsx b/packages/next-intl/src/server/react-server/getConfig.tsx index d3daa45d8..4b07ff97b 100644 --- a/packages/next-intl/src/server/react-server/getConfig.tsx +++ b/packages/next-intl/src/server/react-server/getConfig.tsx @@ -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. diff --git a/packages/next-intl/src/server/react-server/getRequestConfig.tsx b/packages/next-intl/src/server/react-server/getRequestConfig.tsx index d52612e1c..d96097c18 100644 --- a/packages/next-intl/src/server/react-server/getRequestConfig.tsx +++ b/packages/next-intl/src/server/react-server/getRequestConfig.tsx @@ -1,4 +1,4 @@ -import type {IntlConfig} from 'use-intl/core'; +import type {IntlConfig, Locale} from 'use-intl/core'; export type RequestConfig = Omit & { /** @@ -8,6 +8,13 @@ export type RequestConfig = Omit & { }; 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. * From e4eaf314feb9ed150cd66beaa30f98c2613fd967 Mon Sep 17 00:00:00 2001 From: Jan Amann Date: Thu, 19 Dec 2024 11:47:59 +0100 Subject: [PATCH 06/12] update playground to nextjs 15 canary and rootparams --- .../next-env.d.ts | 2 +- .../package.json | 8 +- .../src/app/[locale]/about/page.tsx | 13 +- .../src/app/[locale]/api/route.ts | 11 +- .../src/app/[locale]/layout.tsx | 34 ++--- .../app/[locale]/news/[articleId]/page.tsx | 19 ++- .../src/app/[locale]/opengraph-image.tsx | 11 +- .../src/app/[locale]/page.tsx | 12 +- .../src/app/layout.tsx | 11 -- .../src/app/not-found.tsx | 17 --- .../AsyncComponentWithNamespaceAndLocale.tsx | 10 +- ...syncComponentWithoutNamespaceAndLocale.tsx | 8 +- .../src/i18n/request.tsx | 19 +-- pnpm-lock.yaml | 124 ++++++++++-------- 14 files changed, 135 insertions(+), 164 deletions(-) delete mode 100644 examples/example-app-router-playground/src/app/layout.tsx delete mode 100644 examples/example-app-router-playground/src/app/not-found.tsx diff --git a/examples/example-app-router-playground/next-env.d.ts b/examples/example-app-router-playground/next-env.d.ts index 40c3d6809..1b3be0840 100644 --- a/examples/example-app-router-playground/next-env.d.ts +++ b/examples/example-app-router-playground/next-env.d.ts @@ -2,4 +2,4 @@ /// // NOTE: This file should not be edited -// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/examples/example-app-router-playground/package.json b/examples/example-app-router-playground/package.json index 941e94bc8..8e231b60c 100644 --- a/examples/example-app-router-playground/package.json +++ b/examples/example-app-router-playground/package.json @@ -12,11 +12,11 @@ "storybook": "storybook dev -p 6006" }, "dependencies": { - "@mdx-js/react": "^3.0.1", + "@mdx-js/react": "^3.1.0", "@radix-ui/react-dropdown-menu": "^2.1.1", "lodash": "^4.17.21", "ms": "2.1.3", - "next": "^14.2.4", + "next": "15.1.1-canary.12", "next-intl": "^3.0.0", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -24,8 +24,8 @@ }, "devDependencies": { "@jest/globals": "^29.7.0", - "@mdx-js/loader": "^3.0.1", - "@next/mdx": "^14.2.5", + "@mdx-js/loader": "^3.1.0", + "@next/mdx": "15.1.1-canary.12", "@playwright/test": "^1.48.1", "@storybook/nextjs": "^8.2.9", "@storybook/react": "^8.2.9", diff --git a/examples/example-app-router-playground/src/app/[locale]/about/page.tsx b/examples/example-app-router-playground/src/app/[locale]/about/page.tsx index 5f9e2a5cb..90bd77faa 100644 --- a/examples/example-app-router-playground/src/app/[locale]/about/page.tsx +++ b/examples/example-app-router-playground/src/app/[locale]/about/page.tsx @@ -1,12 +1,7 @@ -import {Locale} from 'next-intl'; +import {getLocale} from 'next-intl/server'; -type Props = { - params: { - locale: Locale; - }; -}; - -export default async function AboutPage({params}: Props) { - const Content = (await import(`./${params.locale}.mdx`)).default; +export default async function AboutPage() { + const locale = await getLocale(); + const Content = (await import(`./${locale}.mdx`)).default; return ; } diff --git a/examples/example-app-router-playground/src/app/[locale]/api/route.ts b/examples/example-app-router-playground/src/app/[locale]/api/route.ts index 890078add..c35b451df 100644 --- a/examples/example-app-router-playground/src/app/[locale]/api/route.ts +++ b/examples/example-app-router-playground/src/app/[locale]/api/route.ts @@ -1,19 +1,12 @@ import {NextRequest, NextResponse} from 'next/server'; -import {Locale} from 'next-intl'; import {getTranslations} from 'next-intl/server'; -type Props = { - params: { - locale: Locale; - }; -}; - -export async function GET(request: NextRequest, {params: {locale}}: Props) { +export async function GET(request: NextRequest) { 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'}); + const t = await getTranslations('ApiRoute'); return NextResponse.json({message: t('hello', {name})}); } diff --git a/examples/example-app-router-playground/src/app/[locale]/layout.tsx b/examples/example-app-router-playground/src/app/[locale]/layout.tsx index 2a9b487b4..b4a8c193e 100644 --- a/examples/example-app-router-playground/src/app/[locale]/layout.tsx +++ b/examples/example-app-router-playground/src/app/[locale]/layout.tsx @@ -1,6 +1,5 @@ import {Metadata} from 'next'; -import {notFound} from 'next/navigation'; -import {Locale, NextIntlClientProvider, hasLocale} from 'next-intl'; +import {NextIntlClientProvider, useLocale} from 'next-intl'; import { getFormatter, getNow, @@ -11,18 +10,17 @@ import {ReactNode} from 'react'; import {routing} from '@/i18n/routing'; import Navigation from '../../components/Navigation'; -type Props = { - children: ReactNode; - params: {locale: Locale}; -}; +export async function generateStaticParams() { + return routing.locales.map((locale) => ({locale})); +} + +export const dynamicParams = false; -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}); +export async function generateMetadata(): Promise { + const t = await getTranslations('LocaleLayout'); + const formatter = await getFormatter(); + const now = await getNow(); + const timeZone = await getTimeZone(); return { metadataBase: new URL('http://localhost:3000'), @@ -35,10 +33,12 @@ export async function generateMetadata({ }; } -export default function LocaleLayout({children, params: {locale}}: Props) { - if (!hasLocale(routing.locales, locale)) { - notFound(); - } +type Props = { + children: ReactNode; +}; + +export default function LocaleLayout({children}: Props) { + const locale = useLocale(); return ( diff --git a/examples/example-app-router-playground/src/app/[locale]/news/[articleId]/page.tsx b/examples/example-app-router-playground/src/app/[locale]/news/[articleId]/page.tsx index 010fd66e5..3088cbac5 100644 --- a/examples/example-app-router-playground/src/app/[locale]/news/[articleId]/page.tsx +++ b/examples/example-app-router-playground/src/app/[locale]/news/[articleId]/page.tsx @@ -1,29 +1,34 @@ import {Metadata} from 'next'; -import {Locale, useTranslations} from 'next-intl'; +import {useTranslations} from 'next-intl'; +import {getLocale} from 'next-intl/server'; +import {use} from 'react'; import {getPathname} from '@/i18n/routing'; type Props = { - params: { - locale: Locale; + params: Promise<{ articleId: string; - }; + }>; }; export async function generateMetadata({params}: Props): Promise { + const {articleId} = await params; + const locale = await getLocale(); + return { alternates: { canonical: getPathname({ href: { pathname: '/news/[articleId]', - params: {articleId: params.articleId} + params: {articleId} }, - locale: params.locale + locale }) } }; } export default function NewsArticle({params}: Props) { + const {articleId} = use(params); const t = useTranslations('NewsArticle'); - return

{t('title', {articleId: params.articleId})}

; + return

{t('title', {articleId})}

; } diff --git a/examples/example-app-router-playground/src/app/[locale]/opengraph-image.tsx b/examples/example-app-router-playground/src/app/[locale]/opengraph-image.tsx index ffa13ed2e..0f24de5e6 100644 --- a/examples/example-app-router-playground/src/app/[locale]/opengraph-image.tsx +++ b/examples/example-app-router-playground/src/app/[locale]/opengraph-image.tsx @@ -1,14 +1,7 @@ import {ImageResponse} from 'next/og'; -import {Locale} from 'next-intl'; import {getTranslations} from 'next-intl/server'; -type Props = { - params: { - locale: Locale; - }; -}; - -export default async function Image({params: {locale}}: Props) { - const t = await getTranslations({locale, namespace: 'OpenGraph'}); +export default async function Image() { + const t = await getTranslations('OpenGraph'); return new ImageResponse(
{t('title')}
); } diff --git a/examples/example-app-router-playground/src/app/[locale]/page.tsx b/examples/example-app-router-playground/src/app/[locale]/page.tsx index b06facdf5..f6302c186 100644 --- a/examples/example-app-router-playground/src/app/[locale]/page.tsx +++ b/examples/example-app-router-playground/src/app/[locale]/page.tsx @@ -1,9 +1,12 @@ import Image from 'next/image'; import {useFormatter, useNow, useTimeZone, useTranslations} from 'next-intl'; +import {use} from 'react'; import DropdownMenu from '@/components/DropdownMenu'; import RichText from '@/components/RichText'; import {Link} from '@/i18n/routing'; -import AsyncComponent from '../../components/AsyncComponent'; +import AsyncComponent, { + AsyncComponentGerman +} from '../../components/AsyncComponent'; import AsyncComponentWithNamespaceAndLocale from '../../components/AsyncComponentWithNamespaceAndLocale'; import AsyncComponentWithoutNamespace from '../../components/AsyncComponentWithoutNamespace'; import AsyncComponentWithoutNamespaceAndLocale from '../../components/AsyncComponentWithoutNamespaceAndLocale'; @@ -16,7 +19,7 @@ import MessagesAsPropsCounter from '../../components/client/01-MessagesAsPropsCo import MessagesOnClientCounter from '../../components/client/02-MessagesOnClientCounter'; type Props = { - searchParams: Record; + searchParams: Promise>; }; export default function Index({searchParams}: Props) { @@ -55,11 +58,14 @@ export default function Index({searchParams}: Props) { Link on client without provider -

{JSON.stringify(searchParams, null, 2)}

+

+ {JSON.stringify(use(searchParams), null, 2)} +

{JSON.stringify(t.has('title'))}

+ diff --git a/examples/example-app-router-playground/src/app/layout.tsx b/examples/example-app-router-playground/src/app/layout.tsx deleted file mode 100644 index e05792cc7..000000000 --- a/examples/example-app-router-playground/src/app/layout.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import {ReactNode} from 'react'; - -type Props = { - children: ReactNode; -}; - -// Since we have a `not-found.tsx` page on the root, a layout file -// is required, even if it's just passing children through. -export default function RootLayout({children}: Props) { - return children; -} diff --git a/examples/example-app-router-playground/src/app/not-found.tsx b/examples/example-app-router-playground/src/app/not-found.tsx deleted file mode 100644 index ed4705cb1..000000000 --- a/examples/example-app-router-playground/src/app/not-found.tsx +++ /dev/null @@ -1,17 +0,0 @@ -'use client'; - -import Error from 'next/error'; - -// 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. - -export default function NotFound() { - return ( - - - - - - ); -} diff --git a/examples/example-app-router-playground/src/components/AsyncComponentWithNamespaceAndLocale.tsx b/examples/example-app-router-playground/src/components/AsyncComponentWithNamespaceAndLocale.tsx index 71efe8662..2780b3e3f 100644 --- a/examples/example-app-router-playground/src/components/AsyncComponentWithNamespaceAndLocale.tsx +++ b/examples/example-app-router-playground/src/components/AsyncComponentWithNamespaceAndLocale.tsx @@ -1,8 +1,7 @@ -import {getLocale, getTranslations} from 'next-intl/server'; +import {getTranslations} from 'next-intl/server'; export default async function AsyncComponentWithNamespaceAndLocale() { - const locale = await getLocale(); - const t = await getTranslations({locale, namespace: 'AsyncComponent'}); + const t = await getTranslations('AsyncComponent'); return (
@@ -12,11 +11,10 @@ export default async function AsyncComponentWithNamespaceAndLocale() { } export async function TypeTest() { - const locale = await getLocale(); - const t = await getTranslations({locale}); + const t = await getTranslations(); // @ts-expect-error - await getTranslations({locale, namespace: 'Unknown'}); + await getTranslations('Unknown'); // @ts-expect-error t('AsyncComponent.unknown'); diff --git a/examples/example-app-router-playground/src/components/AsyncComponentWithoutNamespaceAndLocale.tsx b/examples/example-app-router-playground/src/components/AsyncComponentWithoutNamespaceAndLocale.tsx index 3986dacd9..1fd8b1d09 100644 --- a/examples/example-app-router-playground/src/components/AsyncComponentWithoutNamespaceAndLocale.tsx +++ b/examples/example-app-router-playground/src/components/AsyncComponentWithoutNamespaceAndLocale.tsx @@ -1,8 +1,7 @@ -import {getLocale, getTranslations} from 'next-intl/server'; +import {getTranslations} from 'next-intl/server'; export default async function AsyncComponentWithoutNamespaceAndLocale() { - const locale = await getLocale(); - const t = await getTranslations({locale}); + const t = await getTranslations(); return (
@@ -12,8 +11,7 @@ export default async function AsyncComponentWithoutNamespaceAndLocale() { } export async function TypeTest() { - const locale = await getLocale(); - const t = await getTranslations({locale}); + const t = await getTranslations(); // @ts-expect-error t('AsyncComponent.unknown'); diff --git a/examples/example-app-router-playground/src/i18n/request.tsx b/examples/example-app-router-playground/src/i18n/request.tsx index 2f329e1a9..0f341ebae 100644 --- a/examples/example-app-router-playground/src/i18n/request.tsx +++ b/examples/example-app-router-playground/src/i18n/request.tsx @@ -1,4 +1,5 @@ import {headers} from 'next/headers'; +import {unstable_rootParams as rootParams} from 'next/server'; import {Formats, hasLocale} from 'next-intl'; import {getRequestConfig} from 'next-intl/server'; import defaultMessages from '../../messages/en.json'; @@ -30,15 +31,17 @@ export const formats = { } } satisfies Formats; -export default getRequestConfig(async ({requestLocale}) => { - // Typically corresponds to the `[locale]` segment - const requested = await requestLocale; - const locale = hasLocale(routing.locales, requested) - ? requested - : routing.defaultLocale; +export default getRequestConfig(async ({locale}) => { + if (!locale) { + const params = await rootParams(); + locale = hasLocale(routing.locales, params.locale) + ? params.locale + : routing.defaultLocale; + } - const now = headers().get('x-now'); - const timeZone = headers().get('x-time-zone') ?? 'Europe/Vienna'; + const headersList = await headers(); + const now = headersList.get('x-now'); + const timeZone = headersList.get('x-time-zone') ?? 'Europe/Vienna'; const localeMessages = (await import(`../../messages/${locale}.json`)) .default; const messages = {...defaultMessages, ...localeMessages}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c76948fb0..b8c19732e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -40,10 +40,10 @@ importers: version: 2.1.5(react@18.3.1) '@vercel/analytics': specifier: 1.3.1 - version: 1.3.1(next@14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) + version: 1.3.1(next@14.2.16(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) '@vercel/speed-insights': specifier: ^1.0.12 - version: 1.0.13(next@14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) + version: 1.0.13(next@14.2.16(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) clsx: specifier: ^2.1.1 version: 2.1.1 @@ -55,10 +55,10 @@ importers: version: 14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) nextra: specifier: ^3.1.0 - version: 3.1.0(@types/react@18.3.12)(acorn@8.13.0)(next@14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + version: 3.1.0(@types/react@18.3.12)(acorn@8.13.0)(next@14.2.16(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) nextra-theme-docs: specifier: ^3.1.0 - version: 3.1.0(next@14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@3.1.0(@types/react@18.3.12)(acorn@8.13.0)(next@14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 3.1.0(next@14.2.16(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@3.1.0(@types/react@18.3.12)(acorn@8.13.0)(next@14.2.16(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: specifier: ^18.3.1 version: 18.3.1 @@ -83,13 +83,13 @@ importers: version: 18.3.12 autoprefixer: specifier: ^10.4.19 - version: 10.4.20(postcss@8.4.47) + version: 10.4.20(postcss@8.4.39) eslint: specifier: ^9.11.1 version: 9.13.0(jiti@2.3.3) eslint-config-molindo: specifier: ^8.0.0 - version: 8.0.0(@typescript-eslint/eslint-plugin@8.11.0(@typescript-eslint/parser@8.11.0(eslint@9.13.0(jiti@2.3.3))(typescript@5.6.3))(eslint@9.13.0(jiti@2.3.3))(typescript@5.6.3))(@typescript-eslint/parser@8.11.0(eslint@9.13.0(jiti@2.3.3))(typescript@5.6.3))(eslint@9.13.0(jiti@2.3.3))(jest@29.7.0(@types/node@20.17.0))(tailwindcss@3.4.14)(typescript@5.6.3)(vitest@2.1.3(@edge-runtime/vm@4.0.3)(@types/node@20.17.0)(jsdom@25.0.1)(terser@5.36.0)) + version: 8.0.0(@typescript-eslint/eslint-plugin@8.11.0(@typescript-eslint/parser@8.9.0(eslint@9.13.0(jiti@2.3.3))(typescript@5.6.3))(eslint@9.13.0(jiti@2.3.3))(typescript@5.6.3))(@typescript-eslint/parser@8.9.0(eslint@9.13.0(jiti@2.3.3))(typescript@5.6.3))(eslint@9.13.0(jiti@2.3.3))(jest@29.7.0(@types/node@20.17.0))(tailwindcss@3.4.14)(typescript@5.6.3)(vitest@2.1.3(@edge-runtime/vm@4.0.3)(@types/node@20.17.0)(jsdom@25.0.1)(terser@5.36.0)) fast-glob: specifier: ^3.3.2 version: 3.3.2 @@ -98,7 +98,7 @@ importers: version: 15.11.0 next-sitemap: specifier: ^4.2.3 - version: 4.2.3(next@14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) + version: 4.2.3(next@14.2.16(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) next-validate-link: specifier: ^1.3.0 version: 1.3.0 @@ -159,7 +159,7 @@ importers: version: 9.13.0(jiti@2.3.3) eslint-config-molindo: specifier: ^8.0.0 - version: 8.0.0(@typescript-eslint/eslint-plugin@8.11.0(@typescript-eslint/parser@8.9.0(eslint@9.13.0(jiti@2.3.3))(typescript@5.6.3))(eslint@9.13.0(jiti@2.3.3))(typescript@5.6.3))(@typescript-eslint/parser@8.9.0(eslint@9.13.0(jiti@2.3.3))(typescript@5.6.3))(eslint@9.13.0(jiti@2.3.3))(jest@29.7.0(@types/node@20.17.0))(tailwindcss@3.4.14)(typescript@5.6.3)(vitest@2.1.3(@edge-runtime/vm@4.0.3)(@types/node@20.17.0)(jsdom@25.0.1)(terser@5.36.0)) + version: 8.0.0(@typescript-eslint/eslint-plugin@8.11.0(@typescript-eslint/parser@8.11.0(eslint@9.13.0(jiti@2.3.3))(typescript@5.6.3))(eslint@9.13.0(jiti@2.3.3))(typescript@5.6.3))(@typescript-eslint/parser@8.11.0(eslint@9.13.0(jiti@2.3.3))(typescript@5.6.3))(eslint@9.13.0(jiti@2.3.3))(jest@29.7.0(@types/node@20.17.0))(tailwindcss@3.4.14)(typescript@5.6.3)(vitest@2.1.3(@edge-runtime/vm@4.0.3)(@types/node@20.17.0)(jsdom@25.0.1)(terser@5.36.0)) jest: specifier: ^29.7.0 version: 29.7.0(@types/node@20.17.0) @@ -266,7 +266,7 @@ importers: version: 14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next-auth: specifier: ^4.24.7 - version: 4.24.8(next@14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 4.24.8(next@14.2.16(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next-intl: specifier: ^3.0.0 version: link:../../packages/next-intl @@ -305,7 +305,7 @@ importers: examples/example-app-router-playground: dependencies: '@mdx-js/react': - specifier: ^3.0.1 + specifier: ^3.1.0 version: 3.1.0(@types/react@18.3.12)(react@18.3.1) '@radix-ui/react-dropdown-menu': specifier: ^2.1.1 @@ -317,8 +317,8 @@ importers: specifier: 2.1.3 version: 2.1.3 next: - specifier: ^14.2.4 - version: 14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: 15.1.1-canary.12 + version: 15.1.1-canary.12(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next-intl: specifier: ^3.0.0 version: link:../../packages/next-intl @@ -336,17 +336,17 @@ importers: specifier: ^29.7.0 version: 29.7.0 '@mdx-js/loader': - specifier: ^3.0.1 - version: 3.1.0(webpack@5.95.0(esbuild@0.23.1)) + specifier: ^3.1.0 + version: 3.1.0(acorn@7.4.1)(webpack@5.95.0(esbuild@0.23.1)) '@next/mdx': - specifier: ^14.2.5 - version: 14.2.16(@mdx-js/loader@3.1.0(webpack@5.95.0(esbuild@0.23.1)))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1)) + specifier: 15.1.1-canary.12 + version: 15.1.1-canary.12(@mdx-js/loader@3.1.0(acorn@7.4.1)(webpack@5.95.0(esbuild@0.23.1)))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1)) '@playwright/test': specifier: ^1.48.1 version: 1.48.1 '@storybook/nextjs': specifier: ^8.2.9 - version: 8.3.6(@types/webpack@5.28.5(esbuild@0.23.1))(esbuild@0.23.1)(next@14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sockjs-client@1.6.1)(storybook@8.3.6)(type-fest@4.26.1)(typescript@5.6.3)(webpack-dev-server@5.1.0(webpack@5.95.0(esbuild@0.23.1)))(webpack-hot-middleware@2.26.1)(webpack@5.95.0(esbuild@0.23.1)) + version: 8.3.6(@types/webpack@5.28.5(esbuild@0.23.1))(esbuild@0.23.1)(next@15.1.1-canary.12(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sockjs-client@1.6.1)(storybook@8.3.6)(type-fest@4.26.1)(typescript@5.6.3)(webpack-dev-server@5.1.0(webpack@5.95.0(esbuild@0.23.1)))(webpack-hot-middleware@2.26.1)(webpack@5.95.0(esbuild@0.23.1)) '@storybook/react': specifier: ^8.2.9 version: 8.3.6(@storybook/test@8.3.6(storybook@8.3.6))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.6)(typescript@5.6.3) @@ -617,7 +617,7 @@ importers: dependencies: next: specifier: ^12.0.0 - version: 12.3.4(@babel/core@7.25.9)(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + version: 12.3.4(react-dom@17.0.2(react@17.0.2))(react@17.0.2) next-intl: specifier: ^3.0.0 version: link:../../packages/next-intl @@ -2746,7 +2746,7 @@ packages: '@expo/bunyan@4.0.1': resolution: {integrity: sha512-+Lla7nYSiHZirgK+U/uYzsLv/X+HaJienbD5AKX1UQZHYfWaP+9uuQluRB4GrEVWF0GZ7vEVp/jzaOT9k/SQlg==} - engines: {node: '>=0.10.0'} + engines: {'0': node >=0.10.0} '@expo/cli@0.4.11': resolution: {integrity: sha512-L9Ci9RBh0aPFEDF1AjDYPk54OgeUJIKzxF3lRgITm+lQpI3IEKjAc9LaYeQeO1mlZMUQmPkHArF8iyz1eOeVoQ==} @@ -3273,9 +3273,6 @@ packages: '@mdx-js/mdx@2.3.0': resolution: {integrity: sha512-jLuwRlz8DQfQNiUCJR50Y09CGPq3fLtmtUQfVrj79E0JWu3dvsVcxVIcfhR5h0iXu+/z++zDrYeiJqifRynJkA==} - '@mdx-js/mdx@3.0.1': - resolution: {integrity: sha512-eIQ4QTrOWyL3LWEe/bu6Taqzq2HQvHcyTMaOrI95P2/LmJE7AsfPfgJGuFLPVqBUE1BC1rik3VIhU+s9u72arA==} - '@mdx-js/mdx@3.1.0': resolution: {integrity: sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw==} @@ -3388,8 +3385,8 @@ packages: '@next/env@15.1.1-canary.12': resolution: {integrity: sha512-8jh0yUEk+tGbgaZWBCU+kYD7y0sukwGTFdBBEbbwvXFSHuPcd65R6ss5HfxjNXqxx8gs4+uFVlUFfthLMc3Tlg==} - '@next/mdx@14.2.16': - resolution: {integrity: sha512-IVd/Z3vYpIZ/nzqhmSHmTKfSrQ6eZrorkzs0uzMCXt3hL6lw4qf/jcEExZmXoenqnZ5vX0xkou+y4uWAKZhvfw==} + '@next/mdx@15.1.1-canary.12': + resolution: {integrity: sha512-WFR/g1erCYl51GLUbW/Q9oowdQGAyesbpmaYTgP21T3y+ihNurAFygKMGwaYQ688G0/Yfi1SXpbSSX14xlJ3QA==} peerDependencies: '@mdx-js/loader': '>=0.15.0' '@mdx-js/react': '>=0.15.0' @@ -12221,9 +12218,6 @@ packages: remark-mdx@2.3.0: resolution: {integrity: sha512-g53hMkpM0I98MU266IzDFMrTD980gNF3BJnkyFcmN+dD873mQeD5rdMO3Y2X+x8umQfbSE0PcoEDl7ledSA+2g==} - remark-mdx@3.0.1: - resolution: {integrity: sha512-3Pz3yPQ5Rht2pM5R+0J2MrGoBSrzf+tJG94N+t/ilfdh8YLyyKYtidAYwTveB20BoHAcwIopOUqhcmh2F7hGYA==} - remark-mdx@3.1.0: resolution: {integrity: sha512-Ngl/H3YXyBV9RcRNdlYsZujAmhsxwzxpDzpDEhFBVAGthS4GDgnctpDjgFl/ULx5UEDzqtW1cyBSNKqYYrqLBA==} @@ -17342,13 +17336,14 @@ snapshots: - supports-color - typescript - '@mdx-js/loader@3.1.0(webpack@5.95.0(esbuild@0.23.1))': + '@mdx-js/loader@3.1.0(acorn@7.4.1)(webpack@5.95.0(esbuild@0.23.1))': dependencies: - '@mdx-js/mdx': 3.0.1 + '@mdx-js/mdx': 3.1.0(acorn@7.4.1) source-map: 0.7.4 optionalDependencies: webpack: 5.95.0(esbuild@0.23.1) transitivePeerDependencies: + - acorn - supports-color '@mdx-js/mdx@2.3.0': @@ -17373,7 +17368,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@mdx-js/mdx@3.0.1': + '@mdx-js/mdx@3.1.0(acorn@7.4.1)': dependencies: '@types/estree': 1.0.6 '@types/estree-jsx': 1.0.5 @@ -17381,15 +17376,16 @@ snapshots: '@types/mdx': 2.0.13 collapse-white-space: 2.1.0 devlop: 1.1.0 - estree-util-build-jsx: 3.0.1 estree-util-is-identifier-name: 3.0.0 - estree-util-to-js: 2.0.0 + estree-util-scope: 1.0.0 estree-walker: 3.0.3 - hast-util-to-estree: 3.1.0 hast-util-to-jsx-runtime: 2.3.2 markdown-extensions: 2.0.0 - periscopic: 3.1.0 - remark-mdx: 3.0.1 + recma-build-jsx: 1.0.0 + recma-jsx: 1.0.0(acorn@7.4.1) + recma-stringify: 1.0.0 + rehype-recma: 1.0.0 + remark-mdx: 3.1.0 remark-parse: 11.0.0 remark-rehype: 11.1.1 source-map: 0.7.4 @@ -17399,6 +17395,7 @@ snapshots: unist-util-visit: 5.0.0 vfile: 6.0.3 transitivePeerDependencies: + - acorn - supports-color '@mdx-js/mdx@3.1.0(acorn@8.13.0)': @@ -17508,11 +17505,11 @@ snapshots: '@next/env@15.1.1-canary.12': {} - '@next/mdx@14.2.16(@mdx-js/loader@3.1.0(webpack@5.95.0(esbuild@0.23.1)))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))': + '@next/mdx@15.1.1-canary.12(@mdx-js/loader@3.1.0(acorn@7.4.1)(webpack@5.95.0(esbuild@0.23.1)))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))': dependencies: source-map: 0.7.4 optionalDependencies: - '@mdx-js/loader': 3.1.0(webpack@5.95.0(esbuild@0.23.1)) + '@mdx-js/loader': 3.1.0(acorn@7.4.1)(webpack@5.95.0(esbuild@0.23.1)) '@mdx-js/react': 3.1.0(@types/react@18.3.12)(react@18.3.1) '@next/swc-android-arm-eabi@12.3.4': @@ -18825,7 +18822,7 @@ snapshots: dependencies: storybook: 8.3.6 - '@storybook/nextjs@8.3.6(@types/webpack@5.28.5(esbuild@0.23.1))(esbuild@0.23.1)(next@14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sockjs-client@1.6.1)(storybook@8.3.6)(type-fest@4.26.1)(typescript@5.6.3)(webpack-dev-server@5.1.0(webpack@5.95.0(esbuild@0.23.1)))(webpack-hot-middleware@2.26.1)(webpack@5.95.0(esbuild@0.23.1))': + '@storybook/nextjs@8.3.6(@types/webpack@5.28.5(esbuild@0.23.1))(esbuild@0.23.1)(next@15.1.1-canary.12(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sockjs-client@1.6.1)(storybook@8.3.6)(type-fest@4.26.1)(typescript@5.6.3)(webpack-dev-server@5.1.0(webpack@5.95.0(esbuild@0.23.1)))(webpack-hot-middleware@2.26.1)(webpack@5.95.0(esbuild@0.23.1))': dependencies: '@babel/core': 7.25.9 '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.25.9) @@ -18853,7 +18850,7 @@ snapshots: fs-extra: 11.2.0 image-size: 1.0.2 loader-utils: 3.2.1 - next: 14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next: 15.1.1-canary.12(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) node-polyfill-webpack-plugin: 2.0.1(webpack@5.95.0(esbuild@0.23.1)) pnp-webpack-plugin: 1.7.0(typescript@5.6.3) postcss: 8.4.47 @@ -19633,14 +19630,14 @@ snapshots: '@vanilla-extract/private@1.0.3': {} - '@vercel/analytics@1.3.1(next@14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)': + '@vercel/analytics@1.3.1(next@14.2.16(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)': dependencies: server-only: 0.0.1 optionalDependencies: next: 14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 - '@vercel/speed-insights@1.0.13(next@14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)': + '@vercel/speed-insights@1.0.13(next@14.2.16(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)': optionalDependencies: next: 14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 @@ -20316,6 +20313,16 @@ snapshots: atob@2.1.2: {} + autoprefixer@10.4.20(postcss@8.4.39): + dependencies: + browserslist: 4.24.0 + caniuse-lite: 1.0.30001669 + fraction.js: 4.3.7 + normalize-range: 0.1.2 + picocolors: 1.0.1 + postcss: 8.4.39 + postcss-value-parser: 4.2.0 + autoprefixer@10.4.20(postcss@8.4.47): dependencies: browserslist: 4.24.0 @@ -27447,7 +27454,7 @@ snapshots: dependencies: type-fest: 2.19.0 - next-auth@4.24.8(next@14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next-auth@4.24.8(next@14.2.16(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.24.7 '@panva/hkdf': 1.2.0 @@ -27462,7 +27469,7 @@ snapshots: react-dom: 18.3.1(react@18.3.1) uuid: 8.3.2 - next-sitemap@4.2.3(next@14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)): + next-sitemap@4.2.3(next@14.2.16(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)): dependencies: '@corex/deepmerge': 4.0.43 '@next/env': 13.5.6 @@ -27486,7 +27493,7 @@ snapshots: transitivePeerDependencies: - supports-color - next@12.3.4(@babel/core@7.25.9)(react-dom@17.0.2(react@17.0.2))(react@17.0.2): + next@12.3.4(react-dom@17.0.2(react@17.0.2))(react@17.0.2): dependencies: '@next/env': 12.3.4 '@swc/helpers': 0.4.11 @@ -27494,7 +27501,7 @@ snapshots: postcss: 8.4.14 react: 17.0.2 react-dom: 17.0.2(react@17.0.2) - styled-jsx: 5.0.7(@babel/core@7.25.9)(react@17.0.2) + styled-jsx: 5.0.7(react@17.0.2) use-sync-external-store: 1.2.0(react@17.0.2) optionalDependencies: '@next/swc-android-arm-eabi': 12.3.4 @@ -27566,7 +27573,7 @@ snapshots: - '@babel/core' - babel-plugin-macros - nextra-theme-docs@3.1.0(next@14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@3.1.0(@types/react@18.3.12)(acorn@8.13.0)(next@14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + nextra-theme-docs@3.1.0(next@14.2.16(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@3.1.0(@types/react@18.3.12)(acorn@8.13.0)(next@14.2.16(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@headlessui/react': 2.1.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1) clsx: 2.1.1 @@ -27574,13 +27581,13 @@ snapshots: flexsearch: 0.7.43 next: 14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next-themes: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - nextra: 3.1.0(@types/react@18.3.12)(acorn@8.13.0)(next@14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + nextra: 3.1.0(@types/react@18.3.12)(acorn@8.13.0)(next@14.2.16(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) scroll-into-view-if-needed: 3.1.0 zod: 3.23.8 - nextra@3.1.0(@types/react@18.3.12)(acorn@8.13.0)(next@14.2.16(@babel/core@7.25.9)(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3): + nextra@3.1.0(@types/react@18.3.12)(acorn@8.13.0)(next@14.2.16(@playwright/test@1.48.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3): dependencies: '@formatjs/intl-localematcher': 0.5.5 '@headlessui/react': 2.1.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -29384,6 +29391,16 @@ snapshots: estree-util-build-jsx: 3.0.1 vfile: 6.0.3 + recma-jsx@1.0.0(acorn@7.4.1): + dependencies: + acorn-jsx: 5.3.2(acorn@7.4.1) + estree-util-to-js: 2.0.0 + recma-parse: 1.0.0 + recma-stringify: 1.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - acorn + recma-jsx@1.0.0(acorn@8.13.0): dependencies: acorn-jsx: 5.3.2(acorn@8.13.0) @@ -29579,13 +29596,6 @@ snapshots: transitivePeerDependencies: - supports-color - remark-mdx@3.0.1: - dependencies: - mdast-util-mdx: 3.0.0 - micromark-extension-mdxjs: 3.0.0 - transitivePeerDependencies: - - supports-color - remark-mdx@3.1.0: dependencies: mdast-util-mdx: 3.0.0 @@ -30638,11 +30648,9 @@ snapshots: dependencies: inline-style-parser: 0.2.4 - styled-jsx@5.0.7(@babel/core@7.25.9)(react@17.0.2): + styled-jsx@5.0.7(react@17.0.2): dependencies: react: 17.0.2 - optionalDependencies: - '@babel/core': 7.25.9 styled-jsx@5.1.1(@babel/core@7.25.9)(react@18.3.1): dependencies: From 1c6e61ada3118ab79ae757b9a0dc86dd7ac27afc Mon Sep 17 00:00:00 2001 From: Jan Amann Date: Thu, 19 Dec 2024 11:48:13 +0100 Subject: [PATCH 07/12] add test for explicit locale --- examples/example-app-router-playground/messages/de.json | 2 +- .../src/components/AsyncComponent.tsx | 9 +++++++++ .../example-app-router-playground/tests/main.spec.ts | 7 +++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/examples/example-app-router-playground/messages/de.json b/examples/example-app-router-playground/messages/de.json index 42dfccb6c..b03d43c4e 100644 --- a/examples/example-app-router-playground/messages/de.json +++ b/examples/example-app-router-playground/messages/de.json @@ -3,7 +3,7 @@ "hello": "Hallo {name}!" }, "AsyncComponent": { - "basic": "AsyncComponent", + "basic": "AsyncComponent (de)", "markup": "Markup with bold content", "rich": "This is a rich text." }, diff --git a/examples/example-app-router-playground/src/components/AsyncComponent.tsx b/examples/example-app-router-playground/src/components/AsyncComponent.tsx index aa4b17029..75ba88a8c 100644 --- a/examples/example-app-router-playground/src/components/AsyncComponent.tsx +++ b/examples/example-app-router-playground/src/components/AsyncComponent.tsx @@ -17,6 +17,15 @@ export default async function AsyncComponent() { ); } +export async function AsyncComponentGerman() { + const t = await getTranslations({locale: 'de', namespace: 'AsyncComponent'}); + return ( +

+ {t('basic')} +

+ ); +} + export async function TypeTest() { const t = await getTranslations('AsyncComponent'); diff --git a/examples/example-app-router-playground/tests/main.spec.ts b/examples/example-app-router-playground/tests/main.spec.ts index 7882adc7b..a32a2ca7d 100644 --- a/examples/example-app-router-playground/tests/main.spec.ts +++ b/examples/example-app-router-playground/tests/main.spec.ts @@ -659,6 +659,13 @@ it('can use async APIs in async components', async ({page}) => { .getByText('AsyncComponent'); }); +it('can use an explicit locale in an async component', async ({page}) => { + await page.goto('/de'); + await expect(page.getByTestId('AsyncComponentGerman')).toHaveText( + 'AsyncComponent (de)' + ); +}); + it('supports custom prefixes', async ({page}) => { await page.goto('/spain'); await expect(page).toHaveURL('/spain'); From aafa8b7182f5fde237ae650769c3d255f7b440eb Mon Sep 17 00:00:00 2001 From: Jan Amann Date: Thu, 19 Dec 2024 12:10:22 +0100 Subject: [PATCH 08/12] feat: Support usage of `rootParams` in `i18n/request.ts` --- .../next-intl/src/server/react-server/getConfig.tsx | 2 ++ .../src/server/react-server/getRequestConfig.tsx | 9 ++++++++- packages/use-intl/src/core/hasLocale.test.tsx | 10 +++++++++- packages/use-intl/src/core/hasLocale.tsx | 2 +- 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/packages/next-intl/src/server/react-server/getConfig.tsx b/packages/next-intl/src/server/react-server/getConfig.tsx index d3daa45d8..4b07ff97b 100644 --- a/packages/next-intl/src/server/react-server/getConfig.tsx +++ b/packages/next-intl/src/server/react-server/getConfig.tsx @@ -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. diff --git a/packages/next-intl/src/server/react-server/getRequestConfig.tsx b/packages/next-intl/src/server/react-server/getRequestConfig.tsx index d52612e1c..d96097c18 100644 --- a/packages/next-intl/src/server/react-server/getRequestConfig.tsx +++ b/packages/next-intl/src/server/react-server/getRequestConfig.tsx @@ -1,4 +1,4 @@ -import type {IntlConfig} from 'use-intl/core'; +import type {IntlConfig, Locale} from 'use-intl/core'; export type RequestConfig = Omit & { /** @@ -8,6 +8,13 @@ export type RequestConfig = Omit & { }; 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. * diff --git a/packages/use-intl/src/core/hasLocale.test.tsx b/packages/use-intl/src/core/hasLocale.test.tsx index 713260179..7181d8e56 100644 --- a/packages/use-intl/src/core/hasLocale.test.tsx +++ b/packages/use-intl/src/core/hasLocale.test.tsx @@ -1,4 +1,4 @@ -import {it} from 'vitest'; +import {expect, it} from 'vitest'; import hasLocale from './hasLocale.tsx'; it('narrows down the type', () => { @@ -24,3 +24,11 @@ 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); + + // Relevant since `ParamValue` in Next.js includes `string[]` + expect(hasLocale(locales, ['de'])).toBe(false); +}); diff --git a/packages/use-intl/src/core/hasLocale.tsx b/packages/use-intl/src/core/hasLocale.tsx index 5bb68714c..aa9e88f85 100644 --- a/packages/use-intl/src/core/hasLocale.tsx +++ b/packages/use-intl/src/core/hasLocale.tsx @@ -7,7 +7,7 @@ import type {Locale} from './AppConfig.tsx'; */ export default function hasLocale( locales: ReadonlyArray, - candidate?: string | null + candidate?: unknown ): candidate is LocaleType { return locales.includes(candidate as LocaleType); } From 7975883b92004b8cd02e4fd0c914e7e3630fe38c Mon Sep 17 00:00:00 2001 From: Jan Amann Date: Thu, 19 Dec 2024 12:13:17 +0100 Subject: [PATCH 09/12] mandatory candidate (even if undefined) --- packages/use-intl/src/core/hasLocale.test.tsx | 1 + packages/use-intl/src/core/hasLocale.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/use-intl/src/core/hasLocale.test.tsx b/packages/use-intl/src/core/hasLocale.test.tsx index 7181d8e56..36c611f3b 100644 --- a/packages/use-intl/src/core/hasLocale.test.tsx +++ b/packages/use-intl/src/core/hasLocale.test.tsx @@ -28,6 +28,7 @@ it('can be called with a non-matching narrow candidate', () => { 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); diff --git a/packages/use-intl/src/core/hasLocale.tsx b/packages/use-intl/src/core/hasLocale.tsx index aa9e88f85..576ee3289 100644 --- a/packages/use-intl/src/core/hasLocale.tsx +++ b/packages/use-intl/src/core/hasLocale.tsx @@ -7,7 +7,7 @@ import type {Locale} from './AppConfig.tsx'; */ export default function hasLocale( locales: ReadonlyArray, - candidate?: unknown + candidate: unknown ): candidate is LocaleType { return locales.includes(candidate as LocaleType); } From d658b5cd31a6b0b3e195354cb215c41efeb27a53 Mon Sep 17 00:00:00 2001 From: Jan Amann Date: Fri, 28 Feb 2025 11:56:47 +0100 Subject: [PATCH 10/12] fix conflict --- .../src/app/[locale]/layout.tsx | 39 ++----------------- 1 file changed, 3 insertions(+), 36 deletions(-) diff --git a/examples/example-app-router-playground/src/app/[locale]/layout.tsx b/examples/example-app-router-playground/src/app/[locale]/layout.tsx index e20f32df1..d2413ddf6 100644 --- a/examples/example-app-router-playground/src/app/[locale]/layout.tsx +++ b/examples/example-app-router-playground/src/app/[locale]/layout.tsx @@ -1,11 +1,5 @@ import {Metadata} from 'next'; -<<<<<<< HEAD import {NextIntlClientProvider, useLocale} from 'next-intl'; -======= -import {Inter} from 'next/font/google'; -import {notFound} from 'next/navigation'; -import {Locale, NextIntlClientProvider, hasLocale} from 'next-intl'; ->>>>>>> origin/v4 import { getFormatter, getNow, @@ -13,10 +7,12 @@ import { getTranslations } from 'next-intl/server'; import {ReactNode} from 'react'; +import {Inter} from 'next/font/google'; import {routing} from '@/i18n/routing'; import Navigation from '../../components/Navigation'; -<<<<<<< HEAD +const inter = Inter({subsets: ['latin']}); + export async function generateStaticParams() { return routing.locales.map((locale) => ({locale})); } @@ -28,25 +24,6 @@ export async function generateMetadata(): Promise { const formatter = await getFormatter(); const now = await getNow(); const timeZone = await getTimeZone(); -======= -type Props = { - children: ReactNode; - params: Promise<{locale: Locale}>; -}; - -const inter = Inter({subsets: ['latin']}); - -export async function generateMetadata( - props: Omit -): Promise { - const params = await props.params; - const {locale} = params; - - const t = await getTranslations({locale, namespace: 'LocaleLayout'}); - const formatter = await getFormatter({locale}); - const now = await getNow({locale}); - const timeZone = await getTimeZone({locale}); ->>>>>>> origin/v4 return { metadataBase: new URL('http://localhost:3000'), @@ -59,22 +36,12 @@ export async function generateMetadata( }; } -<<<<<<< HEAD type Props = { children: ReactNode; }; export default function LocaleLayout({children}: Props) { const locale = useLocale(); -======= -export default async function LocaleLayout({params, children}: Props) { - const {locale} = await params; - - // Ensure that the incoming `locale` is valid - if (!hasLocale(routing.locales, locale)) { - notFound(); - } ->>>>>>> origin/v4 return ( From 22cb5ed7327f36c601549f32fdfdcc754fe7777c Mon Sep 17 00:00:00 2001 From: Jan Amann Date: Fri, 28 Feb 2025 12:00:08 +0100 Subject: [PATCH 11/12] revert skipped --- .../example-app-router/src/components/Navigation.spec.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/example-app-router/src/components/Navigation.spec.tsx b/examples/example-app-router/src/components/Navigation.spec.tsx index 3f6904b03..ba25fad02 100644 --- a/examples/example-app-router/src/components/Navigation.spec.tsx +++ b/examples/example-app-router/src/components/Navigation.spec.tsx @@ -19,9 +19,7 @@ jest.mock('next/navigation', () => ({ useSelectedLayoutSegment: () => ({locale: 'en'}) })); -// Disabled until canary version is reverted -// eslint-disable-next-line jest/no-disabled-tests -it.skip('renders', () => { +it('renders', () => { render( Date: Fri, 28 Feb 2025 12:03:33 +0100 Subject: [PATCH 12/12] example fixes --- examples/example-app-router/src/app/(unlocalized)/page.tsx | 2 +- examples/example-app-router/src/app/[locale]/layout.tsx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/example-app-router/src/app/(unlocalized)/page.tsx b/examples/example-app-router/src/app/(unlocalized)/page.tsx index 66962d12d..2e49dc5ba 100644 --- a/examples/example-app-router/src/app/(unlocalized)/page.tsx +++ b/examples/example-app-router/src/app/(unlocalized)/page.tsx @@ -2,5 +2,5 @@ import {redirect} from 'next/navigation'; // This page only renders when the app is built statically (output: 'export') export default function RootRedirect() { - return redirect('/en'); + redirect('/en'); } diff --git a/examples/example-app-router/src/app/[locale]/layout.tsx b/examples/example-app-router/src/app/[locale]/layout.tsx index 155f5d0d4..0303f2d9d 100644 --- a/examples/example-app-router/src/app/[locale]/layout.tsx +++ b/examples/example-app-router/src/app/[locale]/layout.tsx @@ -5,6 +5,7 @@ import {clsx} from 'clsx'; import {Inter} from 'next/font/google'; import {routing} from '@/i18n/routing'; import Navigation from '@/components/Navigation'; +import '@/styles.css'; type Props = { children: ReactNode;