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 f54f30591..7cdd3634f 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,11 +1,31 @@ +import {Metadata} from 'next'; import {useTranslations} from 'next-intl'; +import {defaultLocale, getPathname} from '@/navigation'; +import {Locale} from '@/types'; type Props = { params: { + locale: Locale; articleId: string; }; }; +export async function generateMetadata({params}: Props): Promise { + let canonical = getPathname({ + href: { + pathname: '/news/[articleId]', + params: {articleId: params.articleId} + }, + locale: params.locale + }); + + if (params.locale !== defaultLocale) { + canonical = '/' + params.locale + canonical; + } + + return {alternates: {canonical}}; +} + export default function NewsArticle({params}: Props) { const t = useTranslations('NewsArticle'); return

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

; diff --git a/examples/example-app-router-playground/src/middleware.ts b/examples/example-app-router-playground/src/middleware.ts index c8954579c..5ab0397ee 100644 --- a/examples/example-app-router-playground/src/middleware.ts +++ b/examples/example-app-router-playground/src/middleware.ts @@ -1,8 +1,8 @@ import createMiddleware from 'next-intl/middleware'; -import {locales, pathnames, localePrefix} from './navigation'; +import {locales, pathnames, localePrefix, defaultLocale} from './navigation'; export default createMiddleware({ - defaultLocale: 'en', + defaultLocale, localePrefix, pathnames, locales diff --git a/examples/example-app-router-playground/src/navigation.ts b/examples/example-app-router-playground/src/navigation.ts index 9b8cf20f3..d14e4385c 100644 --- a/examples/example-app-router-playground/src/navigation.ts +++ b/examples/example-app-router-playground/src/navigation.ts @@ -3,6 +3,8 @@ import {LocalePrefix, Pathnames} from 'next-intl/routing'; export const locales = ['en', 'de', 'es', 'ja'] as const; +export const defaultLocale = 'en' as const; + export const localePrefix = ( process.env.NEXT_PUBLIC_LOCALE_PREFIX === 'never' ? 'never' @@ -40,7 +42,7 @@ export const pathnames = { } } satisfies Pathnames; -export const {Link, redirect, usePathname, useRouter} = +export const {Link, getPathname, redirect, usePathname, useRouter} = createLocalizedPathnamesNavigation({ locales, localePrefix, diff --git a/examples/example-app-router-playground/src/types.ts b/examples/example-app-router-playground/src/types.ts new file mode 100644 index 000000000..d036874f6 --- /dev/null +++ b/examples/example-app-router-playground/src/types.ts @@ -0,0 +1,3 @@ +import {locales} from './navigation'; + +export type Locale = (typeof locales)[number]; diff --git a/examples/example-app-router-playground/tests/main.spec.ts b/examples/example-app-router-playground/tests/main.spec.ts index 566d5f92e..e32eb46c4 100644 --- a/examples/example-app-router-playground/tests/main.spec.ts +++ b/examples/example-app-router-playground/tests/main.spec.ts @@ -682,6 +682,21 @@ it('supports custom prefixes', async ({page}) => { page.getByRole('heading', {name: 'Anidada'}); }); +it('can use `getPahname` to define a canonical link', async ({page}) => { + async function getCanonicalPathname() { + const href = await page + .locator('link[rel="canonical"]') + .getAttribute('href'); + return new URL(href!).pathname; + } + + await page.goto('/news/3'); + await expect(getCanonicalPathname()).resolves.toBe('/news/3'); + + await page.goto('/de/neuigkeiten/3'); + await expect(getCanonicalPathname()).resolves.toBe('/de/neuigkeiten/3'); +}); + describe('server actions', () => { it('can use `getTranslations` in server actions', async ({page}) => { await page.goto('/actions');