Skip to content

Commit 1b9e048

Browse files
committed
fix i18n data pathname resolving
1 parent 2865e18 commit 1b9e048

File tree

7 files changed

+134
-3
lines changed

7 files changed

+134
-3
lines changed

packages/next/src/shared/lib/i18n/normalize-locale-path.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,17 @@ export function normalizeLocalePath(
1717
locales?: string[]
1818
): PathLocale {
1919
let detectedLocale: string | undefined
20-
// first item will be empty string from splitting at first char
21-
const pathnameParts = pathname.split('/')
20+
const isDataRoute = pathname.startsWith('/_next/data')
21+
22+
// Create a pathname for locale detection, removing /_next/data/[buildId] if present
23+
// This is because locale detection relies on path splitting and so the first part
24+
// should be the locale.
25+
const pathNameNoDataPrefix = isDataRoute
26+
? pathname.replace(/^\/_next\/data\/[^/]+/, '')
27+
: pathname
28+
29+
// Split the path for locale detection
30+
const pathnameParts = pathNameNoDataPrefix.split('/')
2231

2332
;(locales || []).some((locale) => {
2433
if (
@@ -27,12 +36,17 @@ export function normalizeLocalePath(
2736
) {
2837
detectedLocale = locale
2938
pathnameParts.splice(1, 1)
30-
pathname = pathnameParts.join('/') || '/'
3139
return true
3240
}
3341
return false
3442
})
3543

44+
// For non-data routes, we return the path with the locale stripped out.
45+
// For data routes, we keep the path as is, since we only want to extract the locale.
46+
if (detectedLocale && !isDataRoute) {
47+
pathname = pathnameParts.join('/') || '/'
48+
}
49+
3650
return {
3751
pathname,
3852
detectedLocale,
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { nextTestSetup } from 'e2e-utils'
2+
3+
describe('i18n-navigations-middleware', () => {
4+
const { next } = nextTestSetup({
5+
files: __dirname,
6+
})
7+
8+
it('should respect selected locale when navigating to a dynamic route', async () => {
9+
const browser = await next.browser('/')
10+
// change to "de" locale
11+
await browser.elementByCss("[href='/de']").click()
12+
const dynamicLink = await browser.waitForElementByCss(
13+
"[href='/de/dynamic/1']"
14+
)
15+
expect(await browser.elementById('current-locale').text()).toBe(
16+
'Current locale: de'
17+
)
18+
19+
// navigate to dynamic route
20+
await dynamicLink.click()
21+
22+
// the locale should still be "de"
23+
expect(await browser.elementById('dynamic-locale').text()).toBe(
24+
'Locale: de'
25+
)
26+
})
27+
28+
it('should respect selected locale when navigating to a static route', async () => {
29+
const browser = await next.browser('/')
30+
// change to "de" locale
31+
await browser.elementByCss("[href='/de']").click()
32+
const staticLink = await browser.waitForElementByCss("[href='/de/static']")
33+
expect(await browser.elementById('current-locale').text()).toBe(
34+
'Current locale: de'
35+
)
36+
37+
// navigate to static route
38+
await staticLink.click()
39+
40+
// the locale should still be "de"
41+
expect(await browser.elementById('static-locale').text()).toBe('Locale: de')
42+
})
43+
})
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { NextResponse } from 'next/server'
2+
3+
export const config = { matcher: ['/foo'] }
4+
export async function middleware(req) {
5+
return NextResponse.next()
6+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* @type {import('next').NextConfig}
3+
*/
4+
module.exports = {
5+
i18n: {
6+
defaultLocale: 'default',
7+
locales: ['default', 'en', 'de'],
8+
},
9+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export const getServerSideProps = async ({ locale }) => {
2+
return {
3+
props: {
4+
locale,
5+
},
6+
}
7+
}
8+
9+
export default function Dynamic({ locale }) {
10+
return <div id="dynamic-locale">Locale: {locale}</div>
11+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import Link from 'next/link'
2+
3+
export const getServerSideProps = async ({ locale }) => {
4+
return {
5+
props: {
6+
locale,
7+
},
8+
}
9+
}
10+
11+
export default function Home({ locale }) {
12+
return (
13+
<main
14+
style={{
15+
display: 'flex',
16+
flexDirection: 'column',
17+
gap: '20px',
18+
}}
19+
>
20+
<p id="current-locale">Current locale: {locale}</p>
21+
Locale switch:
22+
<Link href="/" locale="default">
23+
Default
24+
</Link>
25+
<Link href="/" locale="en">
26+
English
27+
</Link>
28+
<Link href="/" locale="de">
29+
German
30+
</Link>
31+
<br />
32+
Test links:
33+
<Link href="/dynamic/1">Dynamic 1</Link>
34+
<Link href="/static">Static</Link>
35+
</main>
36+
)
37+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export const getServerSideProps = async ({ locale }) => {
2+
return {
3+
props: {
4+
locale,
5+
},
6+
}
7+
}
8+
9+
export default function Static({ locale }) {
10+
return <div id="static-locale">Locale: {locale}</div>
11+
}

0 commit comments

Comments
 (0)