Skip to content

Commit f7a4e93

Browse files
committed
fix i18n data pathname resolving
1 parent 162342c commit f7a4e93

File tree

7 files changed

+134
-36
lines changed

7 files changed

+134
-36
lines changed

packages/next/src/server/lib/router-utils/resolve-routes.ts

Lines changed: 10 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { pathHasPrefix } from '../../../shared/lib/router/utils/path-has-prefix'
2626
import { detectDomainLocale } from '../../../shared/lib/i18n/detect-domain-locale'
2727
import { normalizeLocalePath } from '../../../shared/lib/i18n/normalize-locale-path'
2828
import { removePathPrefix } from '../../../shared/lib/router/utils/remove-path-prefix'
29+
import { getNextPathnameInfo } from '../../../shared/lib/router/utils/get-next-pathname-info'
2930
import { NextDataPathnameNormalizer } from '../../normalizers/request/next-data'
3031
import { BasePathPathnameNormalizer } from '../../normalizers/request/base-path'
3132
import { PostponedPathnameNormalizer } from '../../normalizers/request/postponed'
@@ -178,54 +179,27 @@ export function getResolveRoutes(
178179
| ReturnType<typeof normalizeLocalePath>
179180
| undefined = undefined
180181

181-
if (config.i18n) {
182-
const hadTrailingSlash = parsedUrl.pathname?.endsWith('/')
183-
const hadBasePath = pathHasPrefix(
184-
parsedUrl.pathname || '',
185-
config.basePath
186-
)
187-
initialLocaleResult = normalizeLocalePath(
188-
removePathPrefix(parsedUrl.pathname || '/', config.basePath),
189-
config.i18n.locales
190-
)
182+
// TODO: This empty pathname fallback mirrors existing handling in this file but it seems
183+
// incorrect that we would ever have an empty pathname here.
184+
const pathnameInfo = getNextPathnameInfo(parsedUrl.pathname || '', {
185+
nextConfig: config,
186+
})
191187

188+
if (config.i18n) {
192189
domainLocale = detectDomainLocale(
193190
config.i18n.domains,
194191
getHostname(parsedUrl, req.headers)
195192
)
196193
defaultLocale = domainLocale?.defaultLocale || config.i18n.defaultLocale
197-
198-
parsedUrl.query.__nextDefaultLocale = defaultLocale
199-
parsedUrl.query.__nextLocale =
200-
initialLocaleResult.detectedLocale || defaultLocale
201-
202-
// ensure locale is present for resolving routes
203-
if (
204-
!initialLocaleResult.detectedLocale &&
205-
!initialLocaleResult.pathname.startsWith('/_next/')
206-
) {
207-
parsedUrl.pathname = addPathPrefix(
208-
initialLocaleResult.pathname === '/'
209-
? `/${defaultLocale}`
210-
: addPathPrefix(
211-
initialLocaleResult.pathname || '',
212-
`/${defaultLocale}`
213-
),
214-
hadBasePath ? config.basePath : ''
215-
)
216-
217-
if (hadTrailingSlash) {
218-
parsedUrl.pathname = maybeAddTrailingSlash(parsedUrl.pathname)
219-
}
220-
}
194+
parsedUrl.query.__nextLocale = pathnameInfo.locale
221195
}
222196

223197
const checkLocaleApi = (pathname: string) => {
224198
if (
225199
config.i18n &&
226200
pathname === urlNoQuery &&
227-
initialLocaleResult?.detectedLocale &&
228-
pathHasPrefix(initialLocaleResult.pathname, '/api')
201+
pathnameInfo.locale &&
202+
pathHasPrefix(pathnameInfo.pathname, '/api')
229203
) {
230204
return true
231205
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { nextTestSetup } from 'e2e-utils'
2+
3+
describe('i18n-navigations-middleware', () => {
4+
const { next } = nextTestSetup({
5+
files: __dirname,
6+
})
7+
8+
it('should initially render the default locale', async () => {
9+
const browser = await next.browser('/')
10+
expect(await browser.elementById('current-locale').text()).toBe(
11+
'Current locale: en'
12+
)
13+
})
14+
15+
it('should respect selected locale when navigating to a dynamic route', async () => {
16+
const browser = await next.browser('/')
17+
// change to "de" locale
18+
await browser.elementByCss("[href='/de']").click()
19+
const dynamicLink = await browser.waitForElementByCss(
20+
"[href='/de/dynamic/1']"
21+
)
22+
expect(await browser.elementById('current-locale').text()).toBe(
23+
'Current locale: de'
24+
)
25+
26+
// navigate to dynamic route
27+
await dynamicLink.click()
28+
29+
// the locale should still be "de"
30+
expect(await browser.elementById('dynamic-locale').text()).toBe(
31+
'Locale: de'
32+
)
33+
})
34+
35+
it('should respect selected locale when navigating to a static route', async () => {
36+
const browser = await next.browser('/')
37+
// change to "de" locale
38+
await browser.elementByCss("[href='/de']").click()
39+
const staticLink = await browser.waitForElementByCss("[href='/de/static']")
40+
expect(await browser.elementById('current-locale').text()).toBe(
41+
'Current locale: de'
42+
)
43+
44+
// navigate to static route
45+
await staticLink.click()
46+
47+
// the locale should still be "de"
48+
expect(await browser.elementById('static-locale').text()).toBe('Locale: de')
49+
})
50+
})
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)