未验证 提交 7b6a61a7 编写于 作者: J JJ Kasper 提交者: GitHub

Ensure correct locale is used for non-prefixed path (#18708)

When visiting a non-locale prefixed path (`/hello` instead of `/fr/hello`) we don't trigger locale redirects currently so if another locale is matched we need to ensure this is reset to the `defaultLocale` for rendering to prevent a mis-match on the client and the server.

This also fixes hydration errors from occurring with `asPath` for `getServerSideProps` pages due to `normalizeLocalePath` expecting only a pathname and `asPath` containing `hash` and `query values also. 

Fixes: https://github.com/vercel/next.js/issues/18337
Fixes: https://github.com/vercel/next.js/issues/18510
上级 c8d26edf
......@@ -370,7 +370,10 @@ const nextServerlessLoader: loader.Loader = function () {
return
}
detectedLocale = detectedLocale || defaultLocale
detectedLocale =
localePathResult.detectedLocale ||
(detectedDomain && detectedDomain.defaultLocale) ||
defaultLocale
`
: `
const i18n = {}
......
......@@ -92,11 +92,21 @@ if (process.env.__NEXT_I18N_SUPPORT) {
detectDomainLocale,
} = require('../next-server/lib/i18n/detect-domain-locale') as typeof import('../next-server/lib/i18n/detect-domain-locale')
const {
parseRelativeUrl,
} = require('../next-server/lib/router/utils/parse-relative-url') as typeof import('../next-server/lib/router/utils/parse-relative-url')
const {
formatUrl,
} = require('../next-server/lib/router/utils/format-url') as typeof import('../next-server/lib/router/utils/format-url')
if (locales) {
const localePathResult = normalizeLocalePath(asPath, locales)
const parsedAs = parseRelativeUrl(asPath)
const localePathResult = normalizeLocalePath(parsedAs.pathname, locales)
if (localePathResult.detectedLocale) {
asPath = asPath.substr(localePathResult.detectedLocale.length + 1) || '/'
parsedAs.pathname = localePathResult.pathname
asPath = formatUrl(parsedAs)
} else {
// derive the default locale if it wasn't detected in the asPath
// since we don't prerender static pages with all possible default
......
......@@ -429,7 +429,11 @@ export default class Server {
res.end()
return
}
parsedUrl.query.__nextLocale = detectedLocale || defaultLocale
parsedUrl.query.__nextLocale =
localePathResult.detectedLocale ||
detectedDomain?.defaultLocale ||
defaultLocale
}
res.statusCode = 200
......
if (typeof window !== 'undefined') {
window.caughtWarns = []
const origWarn = window.console.warn
const origError = window.console.error
window.console.warn = function (...args) {
window.caughtWarns.push(args.join(' '))
origWarn(...args)
}
window.console.error = function (...args) {
window.caughtWarns.push(args.join(' '))
origError(...args)
}
}
export default function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
......@@ -9,6 +9,7 @@ export default function Page(props) {
<p id="another">another page</p>
<p id="props">{JSON.stringify(props)}</p>
<p id="router-locale">{router.locale}</p>
<p id="router-default-locale">{router.defaultLocale}</p>
<p id="router-locales">{JSON.stringify(router.locales)}</p>
<p id="router-query">{JSON.stringify(router.query)}</p>
<p id="router-pathname">{router.pathname}</p>
......
......@@ -11,6 +11,7 @@ export default function Page(props) {
<p id="gsp">gsp page</p>
<p id="props">{JSON.stringify(props)}</p>
<p id="router-locale">{router.locale}</p>
<p id="router-default-locale">{router.defaultLocale}</p>
<p id="router-locales">{JSON.stringify(router.locales)}</p>
<p id="router-query">{JSON.stringify(router.query)}</p>
<p id="router-pathname">{router.pathname}</p>
......
......@@ -11,6 +11,7 @@ export default function Page(props) {
<p id="gsp">gsp page</p>
<p id="props">{JSON.stringify(props)}</p>
<p id="router-locale">{router.locale}</p>
<p id="router-default-locale">{router.defaultLocale}</p>
<p id="router-locales">{JSON.stringify(router.locales)}</p>
<p id="router-query">{JSON.stringify(router.query)}</p>
<p id="router-pathname">{router.pathname}</p>
......
......@@ -9,6 +9,7 @@ export default function Page(props) {
<p id="index">index page</p>
<p id="props">{JSON.stringify(props)}</p>
<p id="router-locale">{router.locale}</p>
<p id="router-default-locale">{router.defaultLocale}</p>
<p id="router-locales">{JSON.stringify(router.locales)}</p>
<p id="router-query">{JSON.stringify(router.query)}</p>
<p id="router-pathname">{router.pathname}</p>
......
......@@ -10,6 +10,7 @@ export default function Page(props) {
<p id="links">links page</p>
<p id="props">{JSON.stringify(props)}</p>
<p id="router-locale">{router.locale}</p>
<p id="router-default-locale">{router.defaultLocale}</p>
<p id="router-locales">{JSON.stringify(router.locales)}</p>
<p id="router-query">{JSON.stringify(router.query)}</p>
<p id="router-pathname">{router.pathname}</p>
......
......@@ -41,6 +41,49 @@ async function addDefaultLocaleCookie(browser) {
}
function runTests(isDev) {
it('should have correct values for non-prefixed path', async () => {
for (const paths of [
['/links', '/links'],
['/another', '/another'],
['/gsp/fallback/first', '/gsp/fallback/[slug]'],
['/gsp/no-fallback/first', '/gsp/no-fallback/[slug]'],
]) {
const [asPath, pathname] = paths
const res = await fetchViaHTTP(appPort, asPath, undefined, {
redirect: 'manual',
headers: {
'accept-language': 'fr',
},
})
expect(res.status).toBe(200)
const $ = cheerio.load(await res.text())
expect($('html').attr('lang')).toBe('en-US')
expect($('#router-locale').text()).toBe('en-US')
expect($('#router-default-locale').text()).toBe('en-US')
expect(JSON.parse($('#router-locales').text())).toEqual(locales)
expect($('#router-pathname').text()).toBe(pathname)
expect($('#router-as-path').text()).toBe(asPath)
}
})
it('should not have hydration mis-match from hash', async () => {
const browser = await webdriver(appPort, '/en#')
expect(await browser.elementByCss('html').getAttribute('lang')).toBe('en')
expect(await browser.elementByCss('#router-locale').text()).toBe('en')
expect(await browser.elementByCss('#router-default-locale').text()).toBe(
'en-US'
)
expect(
JSON.parse(await browser.elementByCss('#router-locales').text())
).toEqual(locales)
expect(await browser.elementByCss('#router-pathname').text()).toBe('/')
expect(await browser.elementByCss('#router-as-path').text()).toBe('/')
expect(await browser.eval('window.caughtWarns')).toEqual([])
})
if (!isDev) {
it('should add i18n config to routes-manifest', async () => {
const routesManifest = await fs.readJSON(
......@@ -365,11 +408,13 @@ function runTests(isDev) {
expect(parsedUrl.pathname).toBe('/fr/another')
expect(parsedUrl.query).toEqual({})
expect(await browser.eval('window.beforeNav')).toBe(1)
expect(await browser.eval('window.caughtWarns')).toEqual([])
})
it('should navigate with locale prop correctly GSP', async () => {
const browser = await webdriver(appPort, '/links?nextLocale=nl')
await addDefaultLocaleCookie(browser)
await browser.eval('window.beforeNav = 1')
expect(await browser.elementByCss('#router-pathname').text()).toBe('/links')
expect(await browser.elementByCss('#router-as-path').text()).toBe(
......@@ -451,6 +496,8 @@ function runTests(isDev) {
parsedUrl = url.parse(await browser.eval('window.location.href'), true)
expect(parsedUrl.pathname).toBe('/nl/gsp/fallback/first')
expect(parsedUrl.query).toEqual({})
expect(await browser.eval('window.beforeNav')).toBe(1)
expect(await browser.eval('window.caughtWarns')).toEqual([])
})
it('should navigate with locale false correctly', async () => {
......@@ -571,11 +618,13 @@ function runTests(isDev) {
expect(parsedUrl.pathname).toBe('/fr/another')
expect(parsedUrl.query).toEqual({})
expect(await browser.eval('window.beforeNav')).toBe(1)
expect(await browser.eval('window.caughtWarns')).toEqual([])
})
it('should navigate with locale false correctly GSP', async () => {
const browser = await webdriver(appPort, '/locale-false?nextLocale=nl')
await addDefaultLocaleCookie(browser)
await browser.eval('window.beforeNav = 1')
expect(await browser.elementByCss('#router-pathname').text()).toBe(
'/locale-false'
......@@ -661,6 +710,8 @@ function runTests(isDev) {
parsedUrl = url.parse(await browser.eval('window.location.href'), true)
expect(parsedUrl.pathname).toBe('/nl/gsp/fallback/first')
expect(parsedUrl.query).toEqual({})
expect(await browser.eval('window.beforeNav')).toBe(1)
expect(await browser.eval('window.caughtWarns')).toEqual([])
})
it('should update asPath on the client correctly', async () => {
......@@ -1581,7 +1632,7 @@ function runTests(isDev) {
it('should load getStaticProps non-fallback correctly another locale via cookie', async () => {
const html = await renderViaHTTP(
appPort,
'/gsp/no-fallback/second',
'/nl-NL/gsp/no-fallback/second',
{},
{
headers: {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册