diff --git a/packages/next/client/link.tsx b/packages/next/client/link.tsx index 4447abb16a78a906a1adf67fda1a18e75f92a9ea..0ea989d872b7bbdefe46c3066b1608d27825d8f9 100644 --- a/packages/next/client/link.tsx +++ b/packages/next/client/link.tsx @@ -3,6 +3,7 @@ import { UrlObject } from 'url' import { addBasePath, addLocale, + getDomainLocale, isLocalURL, NextRouter, PrefetchOptions, @@ -297,13 +298,19 @@ 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, - typeof locale !== 'undefined' ? locale : router && router.locale, - router && router.defaultLocale - ) + const curLocale = + typeof locale !== 'undefined' ? locale : router && router.locale + + const localeDomain = getDomainLocale( + as, + curLocale, + router && router.locales, + router && router.domainLocales ) + + childProps.href = + localeDomain || + addBasePath(addLocale(as, curLocale, router && router.defaultLocale)) } return React.cloneElement(child, childProps) diff --git a/packages/next/next-server/lib/router/router.ts b/packages/next/next-server/lib/router/router.ts index 59bad4242b2348c0f3f2074688d8869bbe565862..9b141348f8b968b5da3eb5cb11fcea94b0b41074 100644 --- a/packages/next/next-server/lib/router/router.ts +++ b/packages/next/next-server/lib/router/router.ts @@ -74,6 +74,28 @@ function addPathPrefix(path: string, prefix?: string) { : path } +export function getDomainLocale( + path: string, + locale?: string | false, + locales?: string[], + domainLocales?: DomainLocales +) { + if (process.env.__NEXT_I18N_SUPPORT) { + locale = locale || normalizeLocalePath(path, locales).detectedLocale + + const detectedDomain = detectDomainLocale(domainLocales, undefined, locale) + + if (detectedDomain) { + return `http${detectedDomain.http ? '' : 's'}://${detectedDomain.domain}${ + basePath || '' + }${locale === detectedDomain.defaultLocale ? '' : `/${locale}`}${path}` + } + return false + } + + return false +} + export function addLocale( path: string, locale?: string | false, @@ -290,6 +312,7 @@ export type BaseRouter = { locale?: string locales?: string[] defaultLocale?: string + domainLocales?: DomainLocales } export type NextRouter = BaseRouter & diff --git a/packages/next/next-server/server/render.tsx b/packages/next/next-server/server/render.tsx index 74e57abde7097568b68773643e0a98cfb70938f0..25ebe5c47a5250c5e9be5b5b7de25185bf9aa2b2 100644 --- a/packages/next/next-server/server/render.tsx +++ b/packages/next/next-server/server/render.tsx @@ -74,6 +74,7 @@ class ServerRouter implements NextRouter { locale?: string locales?: string[] defaultLocale?: string + domainLocales?: DomainLocales // 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() @@ -85,7 +86,8 @@ class ServerRouter implements NextRouter { basePath: string, locale?: string, locales?: string[], - defaultLocale?: string + defaultLocale?: string, + domainLocales?: DomainLocales ) { this.route = pathname.replace(/\/$/, '') || '/' this.pathname = pathname @@ -96,6 +98,7 @@ class ServerRouter implements NextRouter { this.locale = locale this.locales = locales this.defaultLocale = defaultLocale + this.domainLocales = domainLocales } push(): any { noRouter() @@ -533,7 +536,8 @@ export async function renderToHTML( basePath, renderOpts.locale, renderOpts.locales, - renderOpts.defaultLocale + renderOpts.defaultLocale, + renderOpts.domainLocales ) const ctx = { err, diff --git a/test/integration/i18n-support/test/shared.js b/test/integration/i18n-support/test/shared.js index b0a92b1b5fa0b9b9f2a35108c7ead478f8f22c9e..0b4f9fe6a663c24d137256718625798c1137ddb6 100644 --- a/test/integration/i18n-support/test/shared.js +++ b/test/integration/i18n-support/test/shared.js @@ -74,6 +74,44 @@ export function runTests(ctx) { ) }) + it('should render the correct href for locale domain', async () => { + let browser = await webdriver( + ctx.appPort, + `${ctx.basePath || ''}/links?nextLocale=go` + ) + + for (const [element, pathname] of [ + ['#to-another', '/another'], + ['#to-gsp', '/gsp'], + ['#to-fallback-first', '/gsp/fallback/first'], + ['#to-fallback-hello', '/gsp/fallback/hello'], + ['#to-gssp', '/gssp'], + ['#to-gssp-slug', '/gssp/first'], + ]) { + const href = await browser.elementByCss(element).getAttribute('href') + expect(href).toBe(`https://example.com${ctx.basePath || ''}${pathname}`) + } + + browser = await webdriver( + ctx.appPort, + `${ctx.basePath || ''}/links?nextLocale=go-BE` + ) + + for (const [element, pathname] of [ + ['#to-another', '/another'], + ['#to-gsp', '/gsp'], + ['#to-fallback-first', '/gsp/fallback/first'], + ['#to-fallback-hello', '/gsp/fallback/hello'], + ['#to-gssp', '/gssp'], + ['#to-gssp-slug', '/gssp/first'], + ]) { + const href = await browser.elementByCss(element).getAttribute('href') + expect(href).toBe( + `https://example.com${ctx.basePath || ''}/go-BE${pathname}` + ) + } + }) + it('should navigate through history with query correctly', async () => { const browser = await webdriver(ctx.appPort, `${ctx.basePath || '/'}`)