diff --git a/packages/next/next-server/lib/router/router.ts b/packages/next/next-server/lib/router/router.ts index c915f38c1d8d82f..0e348a4c0e7f3f2 100644 --- a/packages/next/next-server/lib/router/router.ts +++ b/packages/next/next-server/lib/router/router.ts @@ -933,7 +933,11 @@ export default class Router implements BaseRouter { // WARNING: `_h` is an internal option for handing Next.js client-side // hydration. Your app should _never_ use this property. It may change at // any time without notice. - if (!(options as any)._h && this.onlyAHashChange(cleanedAs)) { + if ( + !(options as any)._h && + this.onlyAHashChange(cleanedAs) && + !localeChange + ) { this.asPath = cleanedAs Router.events.emit('hashChangeStart', as, routeProps) // TODO: do we need the resolved href when only a hash change? diff --git a/test/integration/i18n-support-same-page-hash-change/next.config.js b/test/integration/i18n-support-same-page-hash-change/next.config.js new file mode 100644 index 000000000000000..d65eed7f75ccbf9 --- /dev/null +++ b/test/integration/i18n-support-same-page-hash-change/next.config.js @@ -0,0 +1,6 @@ +module.exports = { + i18n: { + locales: ['en', 'fr'], + defaultLocale: 'en', + }, +} diff --git a/test/integration/i18n-support-same-page-hash-change/pages/about.js b/test/integration/i18n-support-same-page-hash-change/pages/about.js new file mode 100644 index 000000000000000..20de96ca7b5065a --- /dev/null +++ b/test/integration/i18n-support-same-page-hash-change/pages/about.js @@ -0,0 +1,27 @@ +import Link from 'next/link' +import { useRouter } from 'next/router' + +export default function Page(props) { + const router = useRouter() + + return ( + <> +

{props.locale}

+

{router.locale}

+ + Change Locale + + + ) +} + +export const getStaticProps = async ({ locale }) => { + return { + props: { + locale, + }, + } +} diff --git a/test/integration/i18n-support-same-page-hash-change/pages/posts/[...slug].js b/test/integration/i18n-support-same-page-hash-change/pages/posts/[...slug].js new file mode 100644 index 000000000000000..385d003540fedbb --- /dev/null +++ b/test/integration/i18n-support-same-page-hash-change/pages/posts/[...slug].js @@ -0,0 +1,43 @@ +import Link from 'next/link' +import { useRouter } from 'next/router' + +export default function Page(props) { + const router = useRouter() + + return ( + <> +

{props.locale}

+

{router.locale}

+ + Change Locale + + + ) +} + +export const getStaticProps = async ({ locale }) => { + return { + props: { + locale, + }, + } +} + +export const getStaticPaths = () => { + return { + paths: [ + { + params: { slug: ['a'] }, + locale: 'en', + }, + { + params: { slug: ['a'] }, + locale: 'fr', + }, + ], + fallback: false, + } +} diff --git a/test/integration/i18n-support-same-page-hash-change/test/index.test.js b/test/integration/i18n-support-same-page-hash-change/test/index.test.js new file mode 100644 index 000000000000000..a9693372fba582c --- /dev/null +++ b/test/integration/i18n-support-same-page-hash-change/test/index.test.js @@ -0,0 +1,81 @@ +/* eslint-env jest */ + +import { join } from 'path' +import webdriver from 'next-webdriver' +import { + launchApp, + killApp, + findPort, + nextBuild, + nextStart, + check, +} from 'next-test-utils' + +jest.setTimeout(1000 * 60 * 2) + +const appDir = join(__dirname, '..') +let appPort +let app + +const runTests = () => { + it('should update props on locale change with same hash', async () => { + const browser = await webdriver(appPort, `/about#hash`) + + await browser.elementByCss('#change-locale').click() + + await check(() => browser.eval('window.location.pathname'), '/fr/about') + await check(() => browser.eval('window.location.hash'), '#hash') + + expect(await browser.elementByCss('#router-locale').text()).toBe('fr') + expect(await browser.elementByCss('#props-locale').text()).toBe('fr') + + await browser.elementByCss('#change-locale').click() + + await check(() => browser.eval('window.location.pathname'), '/about') + await check(() => browser.eval('window.location.hash'), '#hash') + + expect(await browser.elementByCss('#router-locale').text()).toBe('en') + expect(await browser.elementByCss('#props-locale').text()).toBe('en') + }) + + it('should update props on locale change with same hash (dynamic page)', async () => { + const browser = await webdriver(appPort, `/posts/a#hash`) + + await browser.elementByCss('#change-locale').click() + + await check(() => browser.eval('window.location.pathname'), '/fr/posts/a') + await check(() => browser.eval('window.location.hash'), '#hash') + + expect(await browser.elementByCss('#router-locale').text()).toBe('fr') + expect(await browser.elementByCss('#props-locale').text()).toBe('fr') + + await browser.elementByCss('#change-locale').click() + + await check(() => browser.eval('window.location.pathname'), '/posts/a') + await check(() => browser.eval('window.location.hash'), '#hash') + + expect(await browser.elementByCss('#router-locale').text()).toBe('en') + expect(await browser.elementByCss('#props-locale').text()).toBe('en') + }) +} + +describe('Hash changes i18n', () => { + describe('dev mode', () => { + beforeAll(async () => { + appPort = await findPort() + app = await launchApp(appDir, appPort) + }) + afterAll(() => killApp(app)) + runTests(true) + }) + + describe('production mode', () => { + beforeAll(async () => { + await nextBuild(appDir) + appPort = await findPort() + app = await nextStart(appDir, appPort) + }) + afterAll(() => killApp(app)) + runTests() + }) +})