diff --git a/packages/next/client/index.tsx b/packages/next/client/index.tsx index 5cc0056da5c2..54f178e673b3 100644 --- a/packages/next/client/index.tsx +++ b/packages/next/client/index.tsx @@ -86,11 +86,11 @@ if (hasBasePath(asPath)) { if (process.env.__NEXT_I18N_SUPPORT) { const { normalizeLocalePath, - } = require('../next-server/lib/i18n/normalize-locale-path') + } = require('../next-server/lib/i18n/normalize-locale-path') as typeof import('../next-server/lib/i18n/normalize-locale-path') const { detectDomainLocale, - } = require('../next-server/lib/i18n/detect-domain-locale') + } = require('../next-server/lib/i18n/detect-domain-locale') as typeof import('../next-server/lib/i18n/detect-domain-locale') if (locales) { const localePathResult = normalizeLocalePath(asPath, locales) @@ -106,7 +106,7 @@ if (process.env.__NEXT_I18N_SUPPORT) { // attempt detecting default locale based on hostname const detectedDomain = detectDomainLocale( - process.env.__NEXT_I18N_DOMAINS, + process.env.__NEXT_I18N_DOMAINS as any, window.location.hostname ) diff --git a/packages/next/client/link.tsx b/packages/next/client/link.tsx index 41bdf3ef5428..08d3ff085d5d 100644 --- a/packages/next/client/link.tsx +++ b/packages/next/client/link.tsx @@ -26,7 +26,7 @@ export type LinkProps = { shallow?: boolean passHref?: boolean prefetch?: boolean - locale?: string + locale?: string | false } type LinkPropsRequired = RequiredKeys type LinkPropsOptional = OptionalKeys @@ -127,7 +127,7 @@ function linkClicked( replace?: boolean, shallow?: boolean, scroll?: boolean, - locale?: string + locale?: string | false ): void { const { nodeName } = e.currentTarget @@ -344,7 +344,7 @@ function Link(props: React.PropsWithChildren) { childProps.href = addBasePath( addLocale( as, - locale || (router && router.locale), + typeof locale !== 'undefined' ? locale : router && router.locale, router && router.defaultLocale ) ) diff --git a/packages/next/next-server/lib/router/router.ts b/packages/next/next-server/lib/router/router.ts index d38a746b25cb..5eeb52da7e5f 100644 --- a/packages/next/next-server/lib/router/router.ts +++ b/packages/next/next-server/lib/router/router.ts @@ -29,7 +29,7 @@ import escapePathDelimiters from './utils/escape-path-delimiters' interface TransitionOptions { shallow?: boolean - locale?: string + locale?: string | false } interface NextHistoryState { @@ -58,7 +58,7 @@ function addPathPrefix(path: string, prefix?: string) { export function addLocale( path: string, - locale?: string, + locale?: string | false, defaultLocale?: string ) { if (process.env.__NEXT_I18N_SUPPORT) { @@ -553,6 +553,7 @@ export default class Router implements BaseRouter { as, Object.assign({}, options, { shallow: options.shallow && this._shallow, + locale: options.locale || this.defaultLocale, }) ) } @@ -600,7 +601,25 @@ export default class Router implements BaseRouter { window.location.href = url return false } - this.locale = options.locale || this.locale + + if (process.env.__NEXT_I18N_SUPPORT) { + this.locale = options.locale || this.locale + + if (typeof options.locale === 'undefined') { + options.locale = this.locale + } + + const { + normalizeLocalePath, + } = require('../i18n/normalize-locale-path') as typeof import('../i18n/normalize-locale-path') + + const localePathResult = normalizeLocalePath(as, this.locales) + + if (localePathResult.detectedLocale) { + this.locale = localePathResult.detectedLocale + url = localePathResult.pathname + } + } if (!(options as any)._h) { this.isSsr = false @@ -614,7 +633,7 @@ export default class Router implements BaseRouter { this.abortComponentLoad(this._inFlightRoute) } - as = addLocale(as, this.locale, this.defaultLocale) + as = addLocale(as, options.locale, this.defaultLocale) const cleanedAs = delLocale( hasBasePath(as) ? delBasePath(as) : as, this.locale @@ -811,7 +830,7 @@ export default class Router implements BaseRouter { this.changeState( method, url, - addLocale(as, this.locale, this.defaultLocale), + addLocale(as, options.locale, this.defaultLocale), options ) diff --git a/test/integration/i18n-support/pages/locale-false.js b/test/integration/i18n-support/pages/locale-false.js new file mode 100644 index 000000000000..8c1f6186861b --- /dev/null +++ b/test/integration/i18n-support/pages/locale-false.js @@ -0,0 +1,54 @@ +import Link from 'next/link' +import { useRouter } from 'next/router' + +export default function Page(props) { + const router = useRouter() + const { nextLocale } = router.query + + return ( + <> + +

{JSON.stringify(props)}

+

{router.locale}

+

{JSON.stringify(router.locales)}

+

{JSON.stringify(router.query)}

+

{router.pathname}

+

{router.asPath}

+ + to /another + +
+ + to /gsp + +
+ + to /gsp/fallback/first + +
+ + to /gsp/fallback/hello + +
+ + to /gsp/no-fallback/first + +
+ + to /gssp + +
+ + to /gssp/first + +
+ + ) +} + +// make SSR page so we have query values immediately +export const getServerSideProps = () => { + return { + props: {}, + } +} diff --git a/test/integration/i18n-support/test/index.test.js b/test/integration/i18n-support/test/index.test.js index eb5cffe21010..a62672b55ef4 100644 --- a/test/integration/i18n-support/test/index.test.js +++ b/test/integration/i18n-support/test/index.test.js @@ -167,6 +167,7 @@ function runTests(isDev) { it('should navigate with locale prop correctly', async () => { const browser = await webdriver(appPort, '/links?nextLocale=fr') 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( @@ -208,7 +209,7 @@ function runTests(isDev) { expect(await browser.elementByCss('#router-as-path').text()).toBe( '/links?nextLocale=fr' ) - expect(await browser.elementByCss('#router-locale').text()).toBe('fr') + expect(await browser.elementByCss('#router-locale').text()).toBe('en-US') expect( JSON.parse(await browser.elementByCss('#router-locales').text()) ).toEqual(locales) @@ -217,7 +218,7 @@ function runTests(isDev) { ).toEqual({ nextLocale: 'fr' }) parsedUrl = url.parse(await browser.eval('window.location.href'), true) - expect(parsedUrl.pathname).toBe('/fr/links') + expect(parsedUrl.pathname).toBe('/links') expect(parsedUrl.query).toEqual({ nextLocale: 'fr' }) await browser.eval('window.history.forward()') @@ -240,6 +241,7 @@ function runTests(isDev) { parsedUrl = url.parse(await browser.eval('window.location.href'), true) expect(parsedUrl.pathname).toBe('/fr/another') expect(parsedUrl.query).toEqual({}) + expect(await browser.eval('window.beforeNav')).toBe(1) }) it('should navigate with locale prop correctly GSP', async () => { @@ -286,16 +288,182 @@ function runTests(isDev) { expect(await browser.elementByCss('#router-as-path').text()).toBe( '/links?nextLocale=nl' ) + 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({ nextLocale: 'nl' }) + + parsedUrl = url.parse(await browser.eval('window.location.href'), true) + expect(parsedUrl.pathname).toBe('/links') + expect(parsedUrl.query).toEqual({ nextLocale: 'nl' }) + + await browser.eval('window.history.forward()') + await browser.waitForElementByCss('#gsp') + + expect(await browser.elementByCss('#router-pathname').text()).toBe( + '/gsp/fallback/[slug]' + ) + expect(await browser.elementByCss('#router-as-path').text()).toBe( + '/gsp/fallback/first' + ) + expect(await browser.elementByCss('#router-locale').text()).toBe('nl') + expect( + JSON.parse(await browser.elementByCss('#router-locales').text()) + ).toEqual(locales) + expect( + JSON.parse(await browser.elementByCss('#router-query').text()) + ).toEqual({ slug: 'first' }) + + parsedUrl = url.parse(await browser.eval('window.location.href'), true) + expect(parsedUrl.pathname).toBe('/nl/gsp/fallback/first') + expect(parsedUrl.query).toEqual({}) + }) + + it('should navigate with locale false correctly', async () => { + const browser = await webdriver(appPort, '/locale-false?nextLocale=fr') + await addDefaultLocaleCookie(browser) + await browser.eval('window.beforeNav = 1') + + expect(await browser.elementByCss('#router-pathname').text()).toBe( + '/locale-false' + ) + expect(await browser.elementByCss('#router-as-path').text()).toBe( + '/locale-false?nextLocale=fr' + ) + 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({ nextLocale: 'fr' }) + + await browser.elementByCss('#to-another').click() + await browser.waitForElementByCss('#another') + + expect(await browser.elementByCss('#router-pathname').text()).toBe( + '/another' + ) + expect(await browser.elementByCss('#router-as-path').text()).toBe( + '/another' + ) + expect(await browser.elementByCss('#router-locale').text()).toBe('fr') + expect( + JSON.parse(await browser.elementByCss('#router-locales').text()) + ).toEqual(locales) + expect( + JSON.parse(await browser.elementByCss('#router-query').text()) + ).toEqual({}) + + let parsedUrl = url.parse(await browser.eval('window.location.href'), true) + expect(parsedUrl.pathname).toBe('/fr/another') + expect(parsedUrl.query).toEqual({}) + + await browser.eval('window.history.back()') + await browser.waitForElementByCss('#links') + + expect(await browser.elementByCss('#router-pathname').text()).toBe( + '/locale-false' + ) + expect(await browser.elementByCss('#router-as-path').text()).toBe( + '/locale-false?nextLocale=fr' + ) + 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({ nextLocale: 'fr' }) + + parsedUrl = url.parse(await browser.eval('window.location.href'), true) + expect(parsedUrl.pathname).toBe('/locale-false') + expect(parsedUrl.query).toEqual({ nextLocale: 'fr' }) + + await browser.eval('window.history.forward()') + await browser.waitForElementByCss('#another') + + expect(await browser.elementByCss('#router-pathname').text()).toBe( + '/another' + ) + expect(await browser.elementByCss('#router-as-path').text()).toBe( + '/another' + ) + expect(await browser.elementByCss('#router-locale').text()).toBe('fr') + expect( + JSON.parse(await browser.elementByCss('#router-locales').text()) + ).toEqual(locales) + expect( + JSON.parse(await browser.elementByCss('#router-query').text()) + ).toEqual({}) + + parsedUrl = url.parse(await browser.eval('window.location.href'), true) + expect(parsedUrl.pathname).toBe('/fr/another') + expect(parsedUrl.query).toEqual({}) + expect(await browser.eval('window.beforeNav')).toBe(1) + }) + + it('should navigate with locale false correctly GSP', async () => { + const browser = await webdriver(appPort, '/locale-false?nextLocale=nl') + await addDefaultLocaleCookie(browser) + + expect(await browser.elementByCss('#router-pathname').text()).toBe( + '/locale-false' + ) + expect(await browser.elementByCss('#router-as-path').text()).toBe( + '/locale-false?nextLocale=nl' + ) + 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({ nextLocale: 'nl' }) + + await browser.elementByCss('#to-fallback-first').click() + await browser.waitForElementByCss('#gsp') + + expect(await browser.elementByCss('#router-pathname').text()).toBe( + '/gsp/fallback/[slug]' + ) + expect(await browser.elementByCss('#router-as-path').text()).toBe( + '/gsp/fallback/first' + ) expect(await browser.elementByCss('#router-locale').text()).toBe('nl') expect( JSON.parse(await browser.elementByCss('#router-locales').text()) ).toEqual(locales) + expect( + JSON.parse(await browser.elementByCss('#router-query').text()) + ).toEqual({ slug: 'first' }) + + let parsedUrl = url.parse(await browser.eval('window.location.href'), true) + expect(parsedUrl.pathname).toBe('/nl/gsp/fallback/first') + expect(parsedUrl.query).toEqual({}) + + await browser.eval('window.history.back()') + await browser.waitForElementByCss('#links') + + expect(await browser.elementByCss('#router-pathname').text()).toBe( + '/locale-false' + ) + expect(await browser.elementByCss('#router-as-path').text()).toBe( + '/locale-false?nextLocale=nl' + ) + 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({ nextLocale: 'nl' }) parsedUrl = url.parse(await browser.eval('window.location.href'), true) - expect(parsedUrl.pathname).toBe('/nl/links') + expect(parsedUrl.pathname).toBe('/locale-false') expect(parsedUrl.query).toEqual({ nextLocale: 'nl' }) await browser.eval('window.history.forward()')