From a429a47fe0585c1b3d28f5a8c366238b36439642 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Tue, 10 Nov 2020 20:09:45 -0600 Subject: [PATCH] Ensure correct defaultLocale is provided for locale domain (#19040) This ensures we use the `defaultLocale` for a locale domain when rendering non-static pages. Static pages will initially contain the global `defaultLocale` and then be updated on the client since we don't currently prerender a version of the pages for each locale domain. Closes: https://github.com/vercel/next.js/issues/18970 --- .../webpack/loaders/next-serverless-loader.ts | 2 +- .../lib/router/utils/prepare-destination.ts | 1 + .../next-server/server/incremental-cache.ts | 7 +++- .../next/next-server/server/next-server.ts | 24 ++++++++++---- packages/next/next-server/server/render.tsx | 1 + test/integration/i18n-support/pages/frank.js | 1 + .../i18n-support/pages/gssp/index.js | 1 + .../i18n-support/test/index.test.js | 32 +++++++++++++++++++ 8 files changed, 61 insertions(+), 8 deletions(-) diff --git a/packages/next/build/webpack/loaders/next-serverless-loader.ts b/packages/next/build/webpack/loaders/next-serverless-loader.ts index 6394eb587f46c..1e858e4913991 100644 --- a/packages/next/build/webpack/loaders/next-serverless-loader.ts +++ b/packages/next/build/webpack/loaders/next-serverless-loader.ts @@ -810,7 +810,7 @@ const nextServerlessLoader: loader.Loader = function () { err: undefined, locale: detectedLocale, locales, - defaultLocale: i18n.defaultLocale, + defaultLocale, })) sendPayload(req, res, result, 'html', ${ diff --git a/packages/next/next-server/lib/router/utils/prepare-destination.ts b/packages/next/next-server/lib/router/utils/prepare-destination.ts index de5156f36920b..9063992e18319 100644 --- a/packages/next/next-server/lib/router/utils/prepare-destination.ts +++ b/packages/next/next-server/lib/router/utils/prepare-destination.ts @@ -59,6 +59,7 @@ export default function prepareDestination( // clone query so we don't modify the original query = Object.assign({}, query) delete query.__nextLocale + delete query.__nextDefaultLocale if (destination.startsWith('/')) { parsedDestination = parseRelativeUrl(destination) diff --git a/packages/next/next-server/server/incremental-cache.ts b/packages/next/next-server/server/incremental-cache.ts index 401445e02371f..36f515522562d 100644 --- a/packages/next/next-server/server/incremental-cache.ts +++ b/packages/next/next-server/server/incremental-cache.ts @@ -3,6 +3,7 @@ import LRUCache from 'next/dist/compiled/lru-cache' import path from 'path' import { PrerenderManifest } from '../../build' import { PRERENDER_MANIFEST } from '../lib/constants' +import { normalizeLocalePath } from '../lib/i18n/normalize-locale-path' import { normalizePagePath } from './normalize-page-path' function toRoute(pathname: string): string { @@ -30,6 +31,7 @@ export class IncrementalCache { prerenderManifest: PrerenderManifest cache: LRUCache + locales?: string[] constructor({ max, @@ -37,12 +39,14 @@ export class IncrementalCache { distDir, pagesDir, flushToDisk, + locales, }: { dev: boolean max?: number distDir: string pagesDir: string flushToDisk?: boolean + locales?: string[] }) { this.incrementalOptions = { dev, @@ -51,6 +55,7 @@ export class IncrementalCache { flushToDisk: !dev && (typeof flushToDisk !== 'undefined' ? flushToDisk : true), } + this.locales = locales if (dev) { this.prerenderManifest = { @@ -82,7 +87,7 @@ export class IncrementalCache { } private calculateRevalidate(pathname: string): number | false { - pathname = toRoute(pathname) + pathname = toRoute(normalizeLocalePath(pathname, this.locales).pathname) // in development we don't have a prerender-manifest // and default to always revalidating to allow easier debugging diff --git a/packages/next/next-server/server/next-server.ts b/packages/next/next-server/server/next-server.ts index f76147ef1ca81..b38ba2a01a513 100644 --- a/packages/next/next-server/server/next-server.ts +++ b/packages/next/next-server/server/next-server.ts @@ -205,7 +205,6 @@ export default class Server { ? requireFontManifest(this.distDir, this._isLikeServerless) : null, optimizeImages: this.nextConfig.experimental.optimizeImages, - defaultLocale: this.nextConfig.i18n?.defaultLocale, } // Only the `publicRuntimeConfig` key is exposed to the client side @@ -259,6 +258,7 @@ export default class Server { 'pages' ), flushToDisk: this.nextConfig.experimental.sprFlushToDisk, + locales: this.nextConfig.i18n?.locales, }) /** @@ -438,6 +438,9 @@ export default class Server { return } + parsedUrl.query.__nextDefaultLocale = + detectedDomain?.defaultLocale || i18n.defaultLocale + parsedUrl.query.__nextLocale = localePathResult.detectedLocale || detectedDomain?.defaultLocale || @@ -603,7 +606,10 @@ export default class Server { pathname = localePathResult.pathname detectedLocale = localePathResult.detectedLocale } + _parsedUrl.query.__nextLocale = detectedLocale! + _parsedUrl.query.__nextDefaultLocale = + defaultLocale || i18n.defaultLocale } pathname = getRouteFromAssetPath(pathname, '.json') @@ -1145,6 +1151,7 @@ export default class Server { amp: query.amp, _nextDataReq: query._nextDataReq, __nextLocale: query.__nextLocale, + __nextDefaultLocale: query.__nextDefaultLocale, } : query), ...(params || {}), @@ -1217,7 +1224,12 @@ export default class Server { } const locale = query.__nextLocale as string + const defaultLocale = isSSG + ? this.nextConfig.i18n?.defaultLocale + : (query.__nextDefaultLocale as string) + delete query.__nextLocale + delete query.__nextDefaultLocale const { i18n } = this.nextConfig const locales = i18n.locales as string[] @@ -1281,9 +1293,9 @@ export default class Server { let ssgCacheKey = isPreviewMode || !isSSG ? undefined // Preview mode bypasses the cache - : `${locale ? `/${locale}` : ''}${resolvedUrlPathname}${ - query.amp ? '.amp' : '' - }` + : `${locale ? `/${locale}` : ''}${ + pathname === '/' && locale ? '' : resolvedUrlPathname + }${query.amp ? '.amp' : ''}` if (is404Page && isSSG) { ssgCacheKey = `${locale ? `/${locale}` : ''}${pathname}${ @@ -1371,7 +1383,7 @@ export default class Server { fontManifest: this.renderOpts.fontManifest, locale, locales, - // defaultLocale, + defaultLocale, } ) @@ -1395,7 +1407,7 @@ export default class Server { resolvedUrl, locale, locales, - // defaultLocale, + defaultLocale, // For getServerSideProps we need to ensure we use the original URL // and not the resolved URL to prevent a hydration mismatch on // asPath diff --git a/packages/next/next-server/server/render.tsx b/packages/next/next-server/server/render.tsx index 3c2ecaf704c57..34a0bbc6a5456 100644 --- a/packages/next/next-server/server/render.tsx +++ b/packages/next/next-server/server/render.tsx @@ -406,6 +406,7 @@ export async function renderToHTML( const isFallback = !!query.__nextFallback delete query.__nextFallback delete query.__nextLocale + delete query.__nextDefaultLocale const isSSG = !!getStaticProps const isBuildTimeSSG = isSSG && renderOpts.nextExport diff --git a/test/integration/i18n-support/pages/frank.js b/test/integration/i18n-support/pages/frank.js index 56d0ebaea4ff1..6b51cabc71053 100644 --- a/test/integration/i18n-support/pages/frank.js +++ b/test/integration/i18n-support/pages/frank.js @@ -9,6 +9,7 @@ export default function Page(props) {

frank page

{JSON.stringify(props)}

{router.locale}

+

{router.defaultLocale}

{JSON.stringify(router.locales)}

{JSON.stringify(router.query)}

{router.pathname}

diff --git a/test/integration/i18n-support/pages/gssp/index.js b/test/integration/i18n-support/pages/gssp/index.js index eb21a085c24c3..dfae0973c5c55 100644 --- a/test/integration/i18n-support/pages/gssp/index.js +++ b/test/integration/i18n-support/pages/gssp/index.js @@ -9,6 +9,7 @@ export default function Page(props) {

gssp page

{JSON.stringify(props)}

{router.locale}

+

{router.defaultLocale}

{JSON.stringify(router.locales)}

{JSON.stringify(router.query)}

{router.pathname}

diff --git a/test/integration/i18n-support/test/index.test.js b/test/integration/i18n-support/test/index.test.js index f6de700a2b643..b2e65ee76b4a5 100644 --- a/test/integration/i18n-support/test/index.test.js +++ b/test/integration/i18n-support/test/index.test.js @@ -996,6 +996,9 @@ function runTests(isDev) { expect($('html').attr('lang')).toBe(locale) expect($('#router-locale').text()).toBe(locale) expect(JSON.parse($('#router-locales').text())).toEqual(locales) + // this will not be the domain's defaultLocale since we don't + // generate a prerendered version for each locale domain currently + expect($('#router-default-locale').text()).toBe('en-US') } } @@ -1006,6 +1009,32 @@ function runTests(isDev) { } }) + it('should provide correctly defaultLocale for locale domain', async () => { + for (const { host, locale } of [ + { host: 'example.fr', locale: 'fr' }, + { host: 'example.be', locale: 'nl-BE' }, + ]) { + const res = await fetchViaHTTP(appPort, '/gssp', undefined, { + redirect: 'manual', + headers: { + host, + }, + }) + + expect(res.status).toBe(200) + const html = await res.text() + const $ = cheerio.load(html) + expect($('#router-locale').text()).toBe(locale) + expect($('#router-default-locale').text()).toBe(locale) + expect(JSON.parse($('#props').text())).toEqual({ + defaultLocale: locale, + locale, + locales, + }) + expect(JSON.parse($('#router-locales').text())).toEqual(locales) + } + }) + it('should generate AMP pages with all locales', async () => { for (const locale of locales) { const localePath = locale !== 'en-US' ? `/${locale}` : '' @@ -1160,6 +1189,9 @@ function runTests(isDev) { await browser.waitForElementByCss('#frank') expect(await browser.elementByCss('#router-locale').text()).toBe('fr') + expect(await browser.elementByCss('#router-default-locale').text()).toBe( + 'en-US' + ) expect( JSON.parse(await browser.elementByCss('#router-locales').text()) ).toEqual(locales)