From dab7dae8f2ccf1c0d46b67323a27369aa9f8f719 Mon Sep 17 00:00:00 2001 From: Jan Amann Date: Tue, 17 Sep 2024 10:54:00 +0200 Subject: [PATCH 1/2] fix: Handle overlapping locale prefixes correctly --- .../next-intl/src/middleware/utils.test.tsx | 20 +++++++++++++++++++ packages/next-intl/src/middleware/utils.tsx | 3 +++ 2 files changed, 23 insertions(+) diff --git a/packages/next-intl/src/middleware/utils.test.tsx b/packages/next-intl/src/middleware/utils.test.tsx index e2609114b..17b3afb33 100644 --- a/packages/next-intl/src/middleware/utils.test.tsx +++ b/packages/next-intl/src/middleware/utils.test.tsx @@ -3,6 +3,7 @@ import { formatPathnameTemplate, getInternalTemplate, getNormalizedPathname, + getPathnameMatch, getRouteParams } from './utils'; @@ -168,3 +169,22 @@ describe('getInternalTemplate', () => { ]); }); }); + +describe('getPathnameMatch', () => { + it('prioritizes more specific prefixes for overlapping locales', () => { + expect( + getPathnameMatch('/de/at/test', ['de', 'de-at'], { + mode: 'always', + prefixes: { + 'de-at': '/de/at', + de: '/de' + } + }) + ).toEqual({ + locale: 'de-at', + prefix: '/de/at', + exact: true, + matchedPrefix: '/de/at' + }); + }); +}); diff --git a/packages/next-intl/src/middleware/utils.tsx b/packages/next-intl/src/middleware/utils.tsx index 26fda1b75..d70468166 100644 --- a/packages/next-intl/src/middleware/utils.tsx +++ b/packages/next-intl/src/middleware/utils.tsx @@ -151,6 +151,9 @@ export function getPathnameMatch( | undefined { const localePrefixes = getLocalePrefixes(locales, localePrefix); + // More specific ones first + localePrefixes.sort((a, b) => b[1].length - a[1].length); + for (const [locale, prefix] of localePrefixes) { let exact, matches; if (pathname === prefix || pathname.startsWith(prefix + '/')) { From 7fe99075355c1502c5ebbe3c6ff2dc46874751a2 Mon Sep 17 00:00:00 2001 From: Jan Amann Date: Tue, 17 Sep 2024 11:10:28 +0200 Subject: [PATCH 2/2] More tests --- .../next-intl/src/middleware/utils.test.tsx | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/packages/next-intl/src/middleware/utils.test.tsx b/packages/next-intl/src/middleware/utils.test.tsx index 17b3afb33..e322b5538 100644 --- a/packages/next-intl/src/middleware/utils.test.tsx +++ b/packages/next-intl/src/middleware/utils.test.tsx @@ -171,20 +171,34 @@ describe('getInternalTemplate', () => { }); describe('getPathnameMatch', () => { - it('prioritizes more specific prefixes for overlapping locales', () => { - expect( - getPathnameMatch('/de/at/test', ['de', 'de-at'], { - mode: 'always', - prefixes: { - 'de-at': '/de/at', - de: '/de' - } - }) - ).toEqual({ + it('prioritizes more specific custom prefixes for overlapping ones', () => { + const locales = ['de', 'de-at', 'de-at-x-test'] as const; + const localePrefix = { + mode: 'always', + prefixes: { + de: '/de', + 'de-at': '/de/at', + // Longer locale, shorter prefix + 'de-at-x-test': '/de/a' + } + } as const; + + expect(getPathnameMatch('/de/at/test', locales, localePrefix)).toEqual({ locale: 'de-at', prefix: '/de/at', exact: true, matchedPrefix: '/de/at' }); }); + + it('does not confuse unrelated parts of the pathname with a locale', () => { + expect( + getPathnameMatch('/de/ats', ['de', 'de-at'], {mode: 'always'}) + ).toEqual({ + locale: 'de', + prefix: '/de', + exact: true, + matchedPrefix: '/de' + }); + }); });