diff --git a/packages/next/build/webpack/loaders/next-serverless-loader.ts b/packages/next/build/webpack/loaders/next-serverless-loader.ts index d531cf46cd1124c..2621f4100528516 100644 --- a/packages/next/build/webpack/loaders/next-serverless-loader.ts +++ b/packages/next/build/webpack/loaders/next-serverless-loader.ts @@ -229,24 +229,34 @@ const nextServerlessLoader: loader.Loader = function () { detectedLocale = accept.language( req.headers['accept-language'], i18n.locales - ) || i18n.defaultLocale + ) } + const denormalizedPagePath = denormalizePagePath(parsedUrl.pathname || '/') + const detectedDefaultLocale = detectedLocale === i18n.defaultLocale + const shouldStripDefaultLocale = + detectedDefaultLocale && + denormalizedPagePath === \`/\${i18n.defaultLocale}\` + const shouldAddLocalePrefix = + !detectedDefaultLocale && denormalizedPagePath === '/' + detectedLocale = detectedLocale || i18n.defaultLocale + if ( !nextStartMode && i18n.localeDetection !== false && - denormalizePagePath(parsedUrl.pathname || '/') === '/' + (shouldAddLocalePrefix || shouldStripDefaultLocale) ) { res.setHeader( 'Location', formatUrl({ // make sure to include any query values when redirecting ...parsedUrl, - pathname: \`/\${detectedLocale}\`, + pathname: shouldStripDefaultLocale ? '/' : \`/\${detectedLocale}\`, }) ) res.statusCode = 307 res.end() + return } // TODO: domain based locales (domain to locale mapping needs to be provided in next.config.js) @@ -458,6 +468,7 @@ const nextServerlessLoader: loader.Loader = function () { isDataReq: _nextData, locale: detectedLocale, locales: i18n.locales, + defaultLocale: i18n.defaultLocale, }, options, ) diff --git a/packages/next/client/index.tsx b/packages/next/client/index.tsx index bd148864714498e..e511d8abb294940 100644 --- a/packages/next/client/index.tsx +++ b/packages/next/client/index.tsx @@ -65,6 +65,7 @@ const { isFallback, head: initialHeadData, locales, + defaultLocale, } = data let { locale } = data @@ -317,6 +318,7 @@ export default async (opts: { webpackHMR?: any } = {}) => { render({ App, Component, styleSheets, props, err }), locale, locales, + defaultLocale, }) // call init-client middleware diff --git a/packages/next/client/link.tsx b/packages/next/client/link.tsx index 76b59444dde7081..56da5f68c4c3c38 100644 --- a/packages/next/client/link.tsx +++ b/packages/next/client/link.tsx @@ -332,7 +332,9 @@ function Link(props: React.PropsWithChildren) { // If child is an tag and doesn't have a href attribute, or if the 'passHref' property is // defined, we specify the current 'href', so that repetition is not needed by the user if (props.passHref || (child.type === 'a' && !('href' in child.props))) { - childProps.href = addBasePath(addLocale(as, router && router.locale)) + childProps.href = addBasePath( + addLocale(as, router && router.locale, router && router.defaultLocale) + ) } return React.cloneElement(child, childProps) diff --git a/packages/next/client/page-loader.ts b/packages/next/client/page-loader.ts index 57e8c3374bfb72d..f53dc9a1a61713e 100644 --- a/packages/next/client/page-loader.ts +++ b/packages/next/client/page-loader.ts @@ -203,13 +203,23 @@ export default class PageLoader { * @param {string} href the route href (file-system path) * @param {string} asPath the URL as shown in browser (virtual path); used for dynamic routes */ - getDataHref(href: string, asPath: string, ssg: boolean, locale?: string) { + getDataHref( + href: string, + asPath: string, + ssg: boolean, + locale?: string, + defaultLocale?: string + ) { const { pathname: hrefPathname, query, search } = parseRelativeUrl(href) const { pathname: asPathname } = parseRelativeUrl(asPath) const route = normalizeRoute(hrefPathname) const getHrefForSlug = (path: string) => { - const dataRoute = addLocale(getAssetPathFromRoute(path, '.json'), locale) + const dataRoute = addLocale( + getAssetPathFromRoute(path, '.json'), + locale, + defaultLocale + ) return addBasePath( `/_next/data/${this.buildId}${dataRoute}${ssg ? '' : search}` ) @@ -229,7 +239,12 @@ export default class PageLoader { * @param {string} href the route href (file-system path) * @param {string} asPath the URL as shown in browser (virtual path); used for dynamic routes */ - prefetchData(href: string, asPath: string) { + prefetchData( + href: string, + asPath: string, + locale?: string, + defaultLocale?: string + ) { const { pathname: hrefPathname } = parseRelativeUrl(href) const route = normalizeRoute(hrefPathname) return this.promisedSsgManifest!.then( @@ -237,7 +252,13 @@ export default class PageLoader { // Check if the route requires a data file s.has(route) && // Try to generate data href, noop when falsy - (_dataHref = this.getDataHref(href, asPath, true)) && + (_dataHref = this.getDataHref( + href, + asPath, + true, + locale, + defaultLocale + )) && // noop when data has already been prefetched (dedupe) !document.querySelector( `link[rel="${relPrefetch}"][href^="${_dataHref}"]` diff --git a/packages/next/client/router.ts b/packages/next/client/router.ts index 54a3f65b378f7fa..cff79525bd50f37 100644 --- a/packages/next/client/router.ts +++ b/packages/next/client/router.ts @@ -39,6 +39,7 @@ const urlPropertyFields = [ 'basePath', 'locale', 'locales', + 'defaultLocale', ] const routerEvents = [ 'routeChangeStart', diff --git a/packages/next/export/index.ts b/packages/next/export/index.ts index 056f86c5fb8ee10..cc5f14a7a5ffc00 100644 --- a/packages/next/export/index.ts +++ b/packages/next/export/index.ts @@ -283,6 +283,8 @@ export default async function exportApp( } } + const { i18n } = nextConfig.experimental + // Start the rendering process const renderOpts = { dir, @@ -298,8 +300,9 @@ export default async function exportApp( ampValidatorPath: nextConfig.experimental.amp?.validator || undefined, ampSkipValidation: nextConfig.experimental.amp?.skipValidation || false, ampOptimizerConfig: nextConfig.experimental.amp?.optimizer || undefined, - locales: nextConfig.experimental.i18n?.locales, - locale: nextConfig.experimental.i18n?.defaultLocale, + locales: i18n?.locales, + locale: i18n.defaultLocale, + defaultLocale: i18n.defaultLocale, } const { serverRuntimeConfig, publicRuntimeConfig } = nextConfig diff --git a/packages/next/next-server/lib/i18n/normalize-locale-path.ts b/packages/next/next-server/lib/i18n/normalize-locale-path.ts index ca88a7277c04daa..a46bb4733407364 100644 --- a/packages/next/next-server/lib/i18n/normalize-locale-path.ts +++ b/packages/next/next-server/lib/i18n/normalize-locale-path.ts @@ -6,10 +6,14 @@ export function normalizeLocalePath( pathname: string } { let detectedLocale: string | undefined + // first item will be empty string from splitting at first char + const pathnameParts = pathname.split('/') + ;(locales || []).some((locale) => { - if (pathname.startsWith(`/${locale}`)) { + if (pathnameParts[1] === locale) { detectedLocale = locale - pathname = pathname.replace(new RegExp(`^/${locale}`), '') || '/' + pathnameParts.splice(1, 1) + pathname = pathnameParts.join('/') || '/' return true } return false diff --git a/packages/next/next-server/lib/router/router.ts b/packages/next/next-server/lib/router/router.ts index f2614c4545ce378..c6d0916aedd2f06 100644 --- a/packages/next/next-server/lib/router/router.ts +++ b/packages/next/next-server/lib/router/router.ts @@ -55,9 +55,13 @@ function addPathPrefix(path: string, prefix?: string) { : path } -export function addLocale(path: string, locale?: string) { +export function addLocale( + path: string, + locale?: string, + defaultLocale?: string +) { if (process.env.__NEXT_i18n_SUPPORT) { - return locale && !path.startsWith('/' + locale) + return locale && locale !== defaultLocale && !path.startsWith('/' + locale) ? addPathPrefix(path, '/' + locale) : path } @@ -246,6 +250,7 @@ export type BaseRouter = { basePath: string locale?: string locales?: string[] + defaultLocale?: string } export type NextRouter = BaseRouter & @@ -356,6 +361,7 @@ export default class Router implements BaseRouter { _shallow?: boolean locale?: string locales?: string[] + defaultLocale?: string static events: MittEmitter = mitt() @@ -375,6 +381,7 @@ export default class Router implements BaseRouter { isFallback, locale, locales, + defaultLocale, }: { subscription: Subscription initialProps: any @@ -387,6 +394,7 @@ export default class Router implements BaseRouter { isFallback: boolean locale?: string locales?: string[] + defaultLocale?: string } ) { // represents the current component key @@ -440,6 +448,7 @@ export default class Router implements BaseRouter { if (process.env.__NEXT_i18n_SUPPORT) { this.locale = locale this.locales = locales + this.defaultLocale = defaultLocale } if (typeof window !== 'undefined') { @@ -596,7 +605,7 @@ export default class Router implements BaseRouter { this.abortComponentLoad(this._inFlightRoute) } - as = addLocale(as, this.locale) + as = addLocale(as, this.locale, this.defaultLocale) const cleanedAs = delLocale( hasBasePath(as) ? delBasePath(as) : as, this.locale @@ -790,7 +799,12 @@ export default class Router implements BaseRouter { } Router.events.emit('beforeHistoryChange', as) - this.changeState(method, url, addLocale(as, this.locale), options) + this.changeState( + method, + url, + addLocale(as, this.locale, this.defaultLocale), + options + ) if (process.env.NODE_ENV !== 'production') { const appComp: any = this.components['/_app'].Component @@ -960,7 +974,8 @@ export default class Router implements BaseRouter { formatWithValidation({ pathname, query }), delBasePath(as), __N_SSG, - this.locale + this.locale, + this.defaultLocale ) } @@ -1117,7 +1132,12 @@ export default class Router implements BaseRouter { const route = removePathTrailingSlash(pathname) await Promise.all([ - this.pageLoader.prefetchData(url, asPath), + this.pageLoader.prefetchData( + url, + asPath, + this.locale, + this.defaultLocale + ), this.pageLoader[options.priority ? 'loadPage' : 'prefetch'](route), ]) } diff --git a/packages/next/next-server/lib/utils.ts b/packages/next/next-server/lib/utils.ts index a65c74eabd1f44c..057188def881c5d 100644 --- a/packages/next/next-server/lib/utils.ts +++ b/packages/next/next-server/lib/utils.ts @@ -103,6 +103,7 @@ export type NEXT_DATA = { head: HeadEntry[] locale?: string locales?: string[] + defaultLocale?: string } /** diff --git a/packages/next/next-server/server/config.ts b/packages/next/next-server/server/config.ts index 90d8659dbd54669..867433dfbd160e5 100644 --- a/packages/next/next-server/server/config.ts +++ b/packages/next/next-server/server/config.ts @@ -227,12 +227,35 @@ function assignDefaults(userConfig: { [key: string]: any }) { throw new Error(`Specified i18n.defaultLocale should be a string`) } + if (!Array.isArray(i18n.locales)) { + throw new Error( + `Specified i18n.locales must be an array of locale strings e.g. ["en-US", "nl-NL"] received ${typeof i18n.locales}` + ) + } + + const invalidLocales = i18n.locales.filter( + (locale: any) => typeof locale !== 'string' + ) + + if (invalidLocales.length > 0) { + throw new Error( + `Specified i18n.locales contains invalid values, locales must be valid locale tags provided as strings e.g. "en-US".\n` + + `See here for list of valid language sub-tags: http://www.iana.org/assignments/language-subtag-registry/language-subtag-registry` + ) + } + if (!i18n.locales.includes(i18n.defaultLocale)) { throw new Error( `Specified i18n.defaultLocale should be included in i18n.locales` ) } + // make sure default Locale is at the front + i18n.locales = [ + i18n.defaultLocale, + ...i18n.locales.filter((locale: string) => locale !== i18n.defaultLocale), + ] + const localeDetectionType = typeof i18n.locales.localeDetection if ( diff --git a/packages/next/next-server/server/next-server.ts b/packages/next/next-server/server/next-server.ts index b920d7fc45957b0..03e5c8f3bd76fd9 100644 --- a/packages/next/next-server/server/next-server.ts +++ b/packages/next/next-server/server/next-server.ts @@ -140,6 +140,7 @@ export default class Server { optimizeImages: boolean locale?: string locales?: string[] + defaultLocale?: string } private compression?: Middleware private onErrorMiddleware?: ({ err }: { err: Error }) => Promise @@ -193,6 +194,7 @@ export default class Server { : null, optimizeImages: this.nextConfig.experimental.optimizeImages, locales: this.nextConfig.experimental.i18n?.locales, + defaultLocale: this.nextConfig.experimental.i18n?.defaultLocale, } // Only the `publicRuntimeConfig` key is exposed to the client side @@ -302,31 +304,42 @@ export default class Server { req.url = req.url!.replace(basePath, '') || '/' } - if (i18n) { + if (i18n && !parsedUrl.pathname?.startsWith('/_next')) { // get pathname from URL with basePath stripped for locale detection const { pathname, ...parsed } = parseUrl(req.url || '/') let detectedLocale = detectLocaleCookie(req, i18n.locales) if (!detectedLocale) { - detectedLocale = - accept.language(req.headers['accept-language'], i18n.locales) || - i18n.defaultLocale + detectedLocale = accept.language( + req.headers['accept-language'], + i18n.locales + ) } + const denormalizedPagePath = denormalizePagePath(pathname || '/') + const detectedDefaultLocale = detectedLocale === i18n.defaultLocale + const shouldStripDefaultLocale = + detectedDefaultLocale && + denormalizedPagePath === `/${i18n.defaultLocale}` + const shouldAddLocalePrefix = + !detectedDefaultLocale && denormalizedPagePath === '/' + detectedLocale = detectedLocale || i18n.defaultLocale + if ( i18n.localeDetection !== false && - denormalizePagePath(pathname || '/') === '/' + (shouldAddLocalePrefix || shouldStripDefaultLocale) ) { res.setHeader( 'Location', formatUrl({ // make sure to include any query values when redirecting ...parsed, - pathname: `/${detectedLocale}`, + pathname: shouldStripDefaultLocale ? '/' : `/${detectedLocale}`, }) ) res.statusCode = 307 res.end() + return } // TODO: domain based locales (domain to locale mapping needs to be provided in next.config.js) @@ -487,21 +500,17 @@ export default class Server { // re-create page's pathname let pathname = `/${params.path.join('/')}` - if (this.nextConfig.experimental.i18n) { - const localePathResult = normalizeLocalePath( - pathname, - this.renderOpts.locales - ) - let detectedLocale = detectLocaleCookie( - req, - this.renderOpts.locales! - ) + const { i18n } = this.nextConfig.experimental + + if (i18n) { + const localePathResult = normalizeLocalePath(pathname, i18n.locales) + let detectedLocale = detectLocaleCookie(req, i18n.locales) if (localePathResult.detectedLocale) { pathname = localePathResult.pathname detectedLocale = localePathResult.detectedLocale } - ;(req as any)._nextLocale = detectedLocale + ;(req as any)._nextLocale = detectedLocale || i18n.defaultLocale } pathname = getRouteFromAssetPath(pathname, '.json') diff --git a/packages/next/next-server/server/render.tsx b/packages/next/next-server/server/render.tsx index 22d45c812b26e78..b8092979093187e 100644 --- a/packages/next/next-server/server/render.tsx +++ b/packages/next/next-server/server/render.tsx @@ -69,6 +69,7 @@ class ServerRouter implements NextRouter { isFallback: boolean locale?: string locales?: string[] + defaultLocale?: string // TODO: Remove in the next major version, as this would mean the user is adding event listeners in server-side `render` method static events: MittEmitter = mitt() @@ -79,7 +80,8 @@ class ServerRouter implements NextRouter { { isFallback }: { isFallback: boolean }, basePath: string, locale?: string, - locales?: string[] + locales?: string[], + defaultLocale?: string ) { this.route = pathname.replace(/\/$/, '') || '/' this.pathname = pathname @@ -89,6 +91,7 @@ class ServerRouter implements NextRouter { this.basePath = basePath this.locale = locale this.locales = locales + this.defaultLocale = defaultLocale } push(): any { noRouter() @@ -164,6 +167,7 @@ export type RenderOptsPartial = { resolvedAsPath?: string locale?: string locales?: string[] + defaultLocale?: string } export type RenderOpts = LoadComponentsReturnType & RenderOptsPartial @@ -203,6 +207,7 @@ function renderDocument( devOnlyCacheBusterQueryString, locale, locales, + defaultLocale, }: RenderOpts & { props: any docComponentsRendered: DocumentProps['docComponentsRendered'] @@ -251,6 +256,7 @@ function renderDocument( appGip, // whether the _app has getInitialProps locale, locales, + defaultLocale, head: React.Children.toArray(docProps.head || []) .map((elem) => { const { children } = elem?.props @@ -517,7 +523,8 @@ export async function renderToHTML( }, basePath, renderOpts.locale, - renderOpts.locales + renderOpts.locales, + renderOpts.defaultLocale ) const ctx = { err, diff --git a/test/integration/i18n-support/next.config.js b/test/integration/i18n-support/next.config.js index 1fb0f8120d470a0..d759a91263b9fb8 100644 --- a/test/integration/i18n-support/next.config.js +++ b/test/integration/i18n-support/next.config.js @@ -3,7 +3,7 @@ module.exports = { experimental: { i18n: { locales: ['nl-NL', 'nl-BE', 'nl', 'en-US', 'en'], - defaultLocale: 'en', + defaultLocale: 'en-US', }, }, } diff --git a/test/integration/i18n-support/test/index.test.js b/test/integration/i18n-support/test/index.test.js index a09d7aaea3f326a..dff7a80ad0ddf2b 100644 --- a/test/integration/i18n-support/test/index.test.js +++ b/test/integration/i18n-support/test/index.test.js @@ -24,9 +24,82 @@ let app let appPort // let buildId -const locales = ['nl-NL', 'nl-BE', 'nl', 'en-US', 'en'] +const locales = ['en-US', 'nl-NL', 'nl-BE', 'nl', 'en'] function runTests() { + it('should remove un-necessary locale prefix for default locale', async () => { + const res = await fetchViaHTTP(appPort, '/en-US', undefined, { + redirect: 'manual', + headers: { + 'Accept-Language': 'en-US;q=0.9', + }, + }) + + expect(res.status).toBe(307) + + const parsedUrl = url.parse(res.headers.get('location'), true) + + expect(parsedUrl.pathname).toBe('/') + expect(parsedUrl.query).toEqual({}) + }) + + it('should load getStaticProps page correctly SSR (default locale no prefix)', async () => { + const html = await renderViaHTTP(appPort, '/gsp') + const $ = cheerio.load(html) + + expect(JSON.parse($('#props').text())).toEqual({ + locale: 'en-US', + locales, + }) + expect($('#router-locale').text()).toBe('en-US') + expect(JSON.parse($('#router-locales').text())).toEqual(locales) + expect($('html').attr('lang')).toBe('en-US') + }) + + it('should load getStaticProps fallback prerender page correctly SSR (default locale no prefix)', async () => { + const html = await renderViaHTTP(appPort, '/gsp/fallback/first') + const $ = cheerio.load(html) + + expect(JSON.parse($('#props').text())).toEqual({ + locale: 'en-US', + locales, + params: { + slug: 'first', + }, + }) + expect(JSON.parse($('#router-query').text())).toEqual({ + slug: 'first', + }) + expect($('#router-locale').text()).toBe('en-US') + expect(JSON.parse($('#router-locales').text())).toEqual(locales) + expect($('html').attr('lang')).toBe('en-US') + }) + + it('should load getStaticProps fallback non-prerender page correctly (default locale no prefix', async () => { + const browser = await webdriver(appPort, '/gsp/fallback/another') + + await browser.waitForElementByCss('#props') + + expect(JSON.parse(await browser.elementByCss('#props').text())).toEqual({ + locale: 'en-US', + locales, + params: { + slug: 'another', + }, + }) + expect( + JSON.parse(await browser.elementByCss('#router-query').text()) + ).toEqual({ + slug: 'another', + }) + // TODO: this will be fixed after the fallback is generated for all locales + // instead of delaying populating the locale on the client + // expect(await browser.elementByCss('#router-locale').text()).toBe('en') + expect( + JSON.parse(await browser.elementByCss('#router-locales').text()) + ).toEqual(locales) + }) + it('should redirect to locale prefixed route for /', async () => { const res = await fetchViaHTTP(appPort, '/', undefined, { redirect: 'manual', @@ -47,14 +120,14 @@ function runTests() { { redirect: 'manual', headers: { - 'Accept-Language': 'en-US,en;q=0.9', + 'Accept-Language': 'en;q=0.9', }, } ) expect(res2.status).toBe(307) const parsedUrl2 = url.parse(res2.headers.get('location'), true) - expect(parsedUrl2.pathname).toBe('/en-US') + expect(parsedUrl2.pathname).toBe('/en') expect(parsedUrl2.query).toEqual({ hello: 'world' }) }) @@ -65,7 +138,7 @@ function runTests() { expect(res.status).toBe(307) const parsedUrl = url.parse(res.headers.get('location'), true) - expect(parsedUrl.pathname).toBe('/en') + expect(parsedUrl.pathname).toBe('/en-US') expect(parsedUrl.query).toEqual({}) const res2 = await fetchViaHTTP( @@ -79,7 +152,7 @@ function runTests() { expect(res2.status).toBe(307) const parsedUrl2 = url.parse(res2.headers.get('location'), true) - expect(parsedUrl2.pathname).toBe('/en') + expect(parsedUrl2.pathname).toBe('/en-US') expect(parsedUrl2.query).toEqual({ hello: 'world' }) }) @@ -97,11 +170,11 @@ function runTests() { }) it('should load getStaticProps fallback prerender page correctly SSR', async () => { - const html = await renderViaHTTP(appPort, '/en/gsp/fallback/first') + const html = await renderViaHTTP(appPort, '/en-US/gsp/fallback/first') const $ = cheerio.load(html) expect(JSON.parse($('#props').text())).toEqual({ - locale: 'en', + locale: 'en-US', locales, params: { slug: 'first', @@ -110,9 +183,9 @@ function runTests() { expect(JSON.parse($('#router-query').text())).toEqual({ slug: 'first', }) - expect($('#router-locale').text()).toBe('en') + expect($('#router-locale').text()).toBe('en-US') expect(JSON.parse($('#router-locales').text())).toEqual(locales) - expect($('html').attr('lang')).toBe('en') + expect($('html').attr('lang')).toBe('en-US') }) it('should load getStaticProps fallback non-prerender page correctly', async () => { @@ -137,12 +210,112 @@ function runTests() { JSON.parse(await browser.elementByCss('#router-locales').text()) ).toEqual(locales) - // TODO: handle updating locale for fallback pages? + // TODO: this will be fixed after fallback pages are generated + // for all locales // expect( // await browser.elementByCss('html').getAttribute('lang') // ).toBe('en-US') }) + it('should load getServerSideProps page correctly SSR (default locale no prefix)', async () => { + const html = await renderViaHTTP(appPort, '/gssp') + const $ = cheerio.load(html) + + expect(JSON.parse($('#props').text())).toEqual({ + locale: 'en-US', + locales, + }) + expect($('#router-locale').text()).toBe('en-US') + expect(JSON.parse($('#router-locales').text())).toEqual(locales) + expect(JSON.parse($('#router-query').text())).toEqual({}) + expect($('html').attr('lang')).toBe('en-US') + }) + + it('should navigate client side for default locale with no prefix', async () => { + const browser = await webdriver(appPort, '/') + // make sure default locale is used in case browser isn't set to + // favor en-US by default + await browser.manage().addCookie({ name: 'NEXT_LOCALE', value: 'en-US' }) + await browser.get(browser.initUrl) + + const checkIndexValues = async () => { + expect(JSON.parse(await browser.elementByCss('#props').text())).toEqual({ + locale: 'en-US', + locales, + }) + expect(await browser.elementByCss('#router-locale').text()).toBe('en-US') + expect( + JSON.parse(await browser.elementByCss('#router-locales').text()) + ).toEqual(locales) + expect( + JSON.parse(await browser.elementByCss('#router-query').text()) + ).toEqual({}) + expect(await browser.elementByCss('#router-pathname').text()).toBe('/') + expect(await browser.elementByCss('#router-as-path').text()).toBe('/') + expect( + url.parse(await browser.eval(() => window.location.href)).pathname + ).toBe('/') + } + + await checkIndexValues() + + await browser.elementByCss('#to-another').click() + await browser.waitForElementByCss('#another') + + expect(JSON.parse(await browser.elementByCss('#props').text())).toEqual({ + locale: 'en-US', + locales, + }) + expect(await browser.elementByCss('#router-locale').text()).toBe('en-US') + expect( + JSON.parse(await browser.elementByCss('#router-locales').text()) + ).toEqual(locales) + expect( + JSON.parse(await browser.elementByCss('#router-query').text()) + ).toEqual({}) + expect(await browser.elementByCss('#router-pathname').text()).toBe( + '/another' + ) + expect(await browser.elementByCss('#router-as-path').text()).toBe( + '/another' + ) + expect( + url.parse(await browser.eval(() => window.location.href)).pathname + ).toBe('/another') + + await browser.elementByCss('#to-index').click() + await browser.waitForElementByCss('#index') + + await checkIndexValues() + + await browser.elementByCss('#to-gsp').click() + await browser.waitForElementByCss('#gsp') + + expect(JSON.parse(await browser.elementByCss('#props').text())).toEqual({ + locale: 'en-US', + locales, + }) + expect(await browser.elementByCss('#router-locale').text()).toBe('en-US') + expect( + JSON.parse(await browser.elementByCss('#router-locales').text()) + ).toEqual(locales) + expect( + JSON.parse(await browser.elementByCss('#router-query').text()) + ).toEqual({}) + expect(await browser.elementByCss('#router-pathname').text()).toBe('/gsp') + expect(await browser.elementByCss('#router-as-path').text()).toBe('/gsp') + expect( + url.parse(await browser.eval(() => window.location.href)).pathname + ).toBe('/gsp') + + await browser.elementByCss('#to-index').click() + await browser.waitForElementByCss('#index') + + await checkIndexValues() + + await browser.manage().deleteCookie('NEXT_LOCALE') + }) + it('should load getStaticProps fallback non-prerender page another locale correctly', async () => { const browser = await webdriver(appPort, '/nl-NL/gsp/fallback/another') @@ -167,12 +340,12 @@ function runTests() { }) it('should load getStaticProps non-fallback correctly', async () => { - const browser = await webdriver(appPort, '/en/gsp/no-fallback/first') + const browser = await webdriver(appPort, '/en-US/gsp/no-fallback/first') await browser.waitForElementByCss('#props') expect(JSON.parse(await browser.elementByCss('#props').text())).toEqual({ - locale: 'en', + locale: 'en-US', locales, params: { slug: 'first', @@ -183,11 +356,13 @@ function runTests() { ).toEqual({ slug: 'first', }) - expect(await browser.elementByCss('#router-locale').text()).toBe('en') + expect(await browser.elementByCss('#router-locale').text()).toBe('en-US') expect( JSON.parse(await browser.elementByCss('#router-locales').text()) ).toEqual(locales) - expect(await browser.elementByCss('html').getAttribute('lang')).toBe('en') + expect(await browser.elementByCss('html').getAttribute('lang')).toBe( + 'en-US' + ) }) it('should load getStaticProps non-fallback correctly another locale', async () => { diff --git a/test/lib/next-webdriver.js b/test/lib/next-webdriver.js index e3b8e6ed6c2f2b0..9ad87b8b2237532 100644 --- a/test/lib/next-webdriver.js +++ b/test/lib/next-webdriver.js @@ -167,6 +167,7 @@ export default async (appPort, path, waitHydration = true) => { } const url = `http://${deviceIP}:${appPort}${path}` + browser.initUrl = url console.log(`\n> Loading browser with ${url}\n`) await browser.get(url)