未验证 提交 2790ea41 编写于 作者: J JJ Kasper 提交者: GitHub

Ensure i18n + trailingSlash works for different page types (#19331)

This adds additional test coverage and more handling for i18n + `trailingSlash: true`

Closes: https://github.com/vercel/next.js/issues/19069
上级 0c389225
......@@ -357,12 +357,7 @@ export default class Server {
pathname: localePathResult.pathname,
})
;(req as any).__nextStrippedLocale = true
parsedUrl.pathname = `${basePath || ''}${localePathResult.pathname}${
(req as any).__nextHadTrailingSlash &&
localePathResult.pathname !== '/'
? '/'
: ''
}`
parsedUrl.pathname = `${basePath || ''}${localePathResult.pathname}`
}
// If a detected locale is a domain specific locale and we aren't already
......@@ -1466,8 +1461,11 @@ export default class Server {
isRedirect = renderResult.renderOpts.isRedirect
} else {
const origQuery = parseUrl(req.url || '', true).query
const hadTrailingSlash =
urlPathname !== '/' && this.nextConfig.trailingSlash
const resolvedUrl = formatUrl({
pathname: resolvedUrlPathname,
pathname: `${resolvedUrlPathname}${hadTrailingSlash ? '/' : ''}`,
// make sure to only add query values from original URL
query: origQuery,
})
......@@ -1487,7 +1485,7 @@ export default class Server {
? formatUrl({
// we use the original URL pathname less the _next/data prefix if
// present
pathname: urlPathname,
pathname: `${urlPathname}${hadTrailingSlash ? '/' : ''}`,
query: origQuery,
})
: resolvedUrl,
......
......@@ -495,8 +495,11 @@ export async function renderToHTML(
}
: {}),
}
renderOpts.resolvedAsPath = `${pathname}${
// ensure trailing slash is present for non-dynamic auto-export pages
req.url!.endsWith('/') && pathname !== '/' && !pageIsDynamic ? '/' : ''
}`
req.url = pathname
renderOpts.resolvedAsPath = pathname
renderOpts.nextExport = true
}
......
export default (req, res) => {
res.json({ hello: true, query: req.query })
}
export default (req, res) => {
res.json({ post: true, query: req.query })
}
......@@ -9,6 +9,7 @@ export default function Page(props) {
<p id="auto-export">auto-export 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>
......
......@@ -62,11 +62,18 @@ export const getStaticPaths = ({ locales, defaultLocale }) => {
throw new Error('missing defaultLocale in getStaticPaths')
}
return {
const paths = [
// the default locale will be used since one isn't defined here
paths: ['first', 'second'].map((slug) => ({
params: { slug },
})),
{ params: { slug: 'first' } },
{ params: { slug: 'second' } },
]
for (const locale of locales) {
paths.push({ params: { slug: 'always' }, locale })
}
return {
paths,
fallback: true,
}
}
......@@ -9,6 +9,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="gssp">gssp 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="auto-export">auto-export 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>
......
......@@ -62,11 +62,18 @@ export const getStaticPaths = ({ locales, defaultLocale }) => {
throw new Error('missing defaultLocale in getStaticPaths')
}
return {
const paths = [
// the default locale will be used since one isn't defined here
paths: ['first', 'second'].map((slug) => ({
params: { slug },
})),
{ params: { slug: 'first' } },
{ params: { slug: 'second' } },
]
for (const locale of locales) {
paths.push({ params: { slug: 'always' }, locale })
}
return {
paths,
fallback: true,
}
}
......@@ -9,6 +9,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="gssp">gssp 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>
......
......@@ -319,23 +319,33 @@ describe('i18n Support', () => {
it('should serve pages correctly with locale prefix', async () => {
for (const locale of locales) {
const res = await fetchViaHTTP(
curCtx.appPort,
`/${locale}/`,
undefined,
{
redirect: 'manual',
}
)
expect(res.status).toBe(200)
for (const [pathname, asPath] of [
['/', '/'],
['/links', '/links/'],
['/auto-export', '/auto-export/'],
['/gsp', '/gsp/'],
['/gsp/fallback/[slug]', '/gsp/fallback/always/'],
['/gssp', '/gssp/'],
['/gssp/[slug]', '/gssp/first/'],
]) {
const res = await fetchViaHTTP(
curCtx.appPort,
`${locale === 'en-US' ? '' : `/${locale}`}${asPath}`,
undefined,
{
redirect: 'manual',
}
)
expect(res.status).toBe(200)
const $ = cheerio.load(await res.text())
const $ = cheerio.load(await res.text())
expect($('#router-pathname').text()).toBe('/')
expect($('#router-as-path').text()).toBe('/')
expect($('#router-locale').text()).toBe(locale)
expect(JSON.parse($('#router-locales').text())).toEqual(locales)
expect($('#router-default-locale').text()).toBe('en-US')
expect($('#router-pathname').text()).toBe(pathname)
expect($('#router-as-path').text()).toBe(asPath)
expect($('#router-locale').text()).toBe(locale)
expect(JSON.parse($('#router-locales').text())).toEqual(locales)
expect($('#router-default-locale').text()).toBe('en-US')
}
}
})
......
......@@ -213,6 +213,11 @@ export function runTests(ctx) {
initialRevalidateSeconds: false,
srcRoute: null,
},
'/en-US/gsp/fallback/always': {
dataRoute: `/_next/data/${ctx.buildId}/en-US/gsp/fallback/always.json`,
initialRevalidateSeconds: false,
srcRoute: '/gsp/fallback/[slug]',
},
'/en-US/gsp/fallback/first': {
dataRoute: `/_next/data/${ctx.buildId}/en-US/gsp/fallback/first.json`,
initialRevalidateSeconds: false,
......@@ -253,6 +258,21 @@ export function runTests(ctx) {
initialRevalidateSeconds: false,
srcRoute: '/not-found/fallback/[slug]',
},
'/en/gsp/fallback/always': {
dataRoute: `/_next/data/${ctx.buildId}/en/gsp/fallback/always.json`,
initialRevalidateSeconds: false,
srcRoute: '/gsp/fallback/[slug]',
},
'/fr-BE/gsp/fallback/always': {
dataRoute: `/_next/data/${ctx.buildId}/fr-BE/gsp/fallback/always.json`,
initialRevalidateSeconds: false,
srcRoute: '/gsp/fallback/[slug]',
},
'/fr/gsp/fallback/always': {
dataRoute: `/_next/data/${ctx.buildId}/fr/gsp/fallback/always.json`,
initialRevalidateSeconds: false,
srcRoute: '/gsp/fallback/[slug]',
},
'/frank': {
dataRoute: `/_next/data/${ctx.buildId}/frank.json`,
initialRevalidateSeconds: false,
......@@ -263,11 +283,26 @@ export function runTests(ctx) {
srcRoute: null,
initialRevalidateSeconds: false,
},
'/nl-BE/gsp/fallback/always': {
dataRoute: `/_next/data/${ctx.buildId}/nl-BE/gsp/fallback/always.json`,
initialRevalidateSeconds: false,
srcRoute: '/gsp/fallback/[slug]',
},
'/nl-NL/gsp/fallback/always': {
dataRoute: `/_next/data/${ctx.buildId}/nl-NL/gsp/fallback/always.json`,
initialRevalidateSeconds: false,
srcRoute: '/gsp/fallback/[slug]',
},
'/nl-NL/gsp/no-fallback/second': {
dataRoute: `/_next/data/${ctx.buildId}/nl-NL/gsp/no-fallback/second.json`,
initialRevalidateSeconds: false,
srcRoute: '/gsp/no-fallback/[slug]',
},
'/nl/gsp/fallback/always': {
dataRoute: `/_next/data/${ctx.buildId}/nl/gsp/fallback/always.json`,
initialRevalidateSeconds: false,
srcRoute: '/gsp/fallback/[slug]',
},
'/not-found': {
dataRoute: `/_next/data/${ctx.buildId}/not-found.json`,
srcRoute: null,
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册