Skip to content

Commit

Permalink
Update redirect handling for locale domains (#17856)
Browse files Browse the repository at this point in the history
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
ijjk and kodiakhq[bot] committed Oct 14, 2020
1 parent 9300151 commit 9a5a152
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 143 deletions.
77 changes: 54 additions & 23 deletions packages/next/build/webpack/loaders/next-serverless-loader.ts
Expand Up @@ -222,64 +222,95 @@ const nextServerlessLoader: loader.Loader = function () {
const i18n = ${i18n}
const accept = require('@hapi/accept')
const { detectLocaleCookie } = require('next/dist/next-server/lib/i18n/detect-locale-cookie')
const { detectDomainLocales } = require('next/dist/next-server/lib/i18n/detect-domain-locales')
const { detectDomainLocale } = require('next/dist/next-server/lib/i18n/detect-domain-locale')
const { normalizeLocalePath } = require('next/dist/next-server/lib/i18n/normalize-locale-path')
let locales = i18n.locales
let defaultLocale = i18n.defaultLocale
let detectedLocale = detectLocaleCookie(req, i18n.locales)
const { defaultLocale, locales } = detectDomainLocales(
req,
const detectedDomain = detectDomainLocale(
i18n.domains,
i18n.locales,
i18n.defaultLocale,
req,
)
if (detectedDomain) {
defaultLocale = detectedDomain.defaultLocale
detectedLocale = defaultLocale
}
if (!detectedLocale) {
detectedLocale = accept.language(
req.headers['accept-language'],
locales
i18n.locales
)
}
let localeDomainRedirect
const localePathResult = normalizeLocalePath(parsedUrl.pathname, i18n.locales)
if (localePathResult.detectedLocale) {
detectedLocale = localePathResult.detectedLocale
req.url = formatUrl({
...parsedUrl,
pathname: localePathResult.pathname,
})
parsedUrl.pathname = localePathResult.pathname
// check if the locale prefix matches a domain's defaultLocale
// and we're on a locale specific domain if so redirect to that domain
if (detectedDomain) {
const matchedDomain = detectDomainLocale(
i18n.domains,
undefined,
detectedLocale
)
if (matchedDomain) {
localeDomainRedirect = \`http\${
matchedDomain.http ? '' : 's'
}://\${matchedDomain.domain}\`
}
}
}
const denormalizedPagePath = denormalizePagePath(parsedUrl.pathname || '/')
const detectedDefaultLocale = !detectedLocale || detectedLocale.toLowerCase() === defaultLocale.toLowerCase()
const detectedDefaultLocale =
!detectedLocale ||
detectedLocale.toLowerCase() === defaultLocale.toLowerCase()
const shouldStripDefaultLocale =
detectedDefaultLocale &&
denormalizedPagePath.toLowerCase() === \`/\${defaultLocale.toLowerCase()}\`
denormalizedPagePath.toLowerCase() === \`/\${i18n.defaultLocale.toLowerCase()}\`
const shouldAddLocalePrefix =
!detectedDefaultLocale && denormalizedPagePath === '/'
detectedLocale = detectedLocale || defaultLocale
detectedLocale = detectedLocale || i18n.defaultLocale
if (
!fromExport &&
!nextStartMode &&
i18n.localeDetection !== false &&
(shouldAddLocalePrefix || shouldStripDefaultLocale)
(
localeDomainRedirect ||
shouldAddLocalePrefix ||
shouldStripDefaultLocale
)
) {
res.setHeader(
'Location',
formatUrl({
// make sure to include any query values when redirecting
...parsedUrl,
pathname: shouldStripDefaultLocale ? '/' : \`/\${detectedLocale}\`,
pathname:
localeDomainRedirect
? localeDomainRedirect
: shouldStripDefaultLocale
? '/'
: \`/\${detectedLocale}\`,
})
)
res.statusCode = 307
res.end()
return
}
const localePathResult = normalizeLocalePath(parsedUrl.pathname, locales)
if (localePathResult.detectedLocale) {
detectedLocale = localePathResult.detectedLocale
req.url = formatUrl({
...parsedUrl,
pathname: localePathResult.pathname,
})
parsedUrl.pathname = localePathResult.pathname
}
detectedLocale = detectedLocale || defaultLocale
`
: `
Expand Down
41 changes: 41 additions & 0 deletions packages/next/next-server/lib/i18n/detect-domain-locale.ts
@@ -0,0 +1,41 @@
import { IncomingMessage } from 'http'

export function detectDomainLocale(
domainItems:
| Array<{
http?: boolean
domain: string
defaultLocale: string
}>
| undefined,
req?: IncomingMessage,
detectedLocale?: string
) {
let domainItem:
| {
http?: boolean
domain: string
defaultLocale: string
}
| undefined

if (domainItems) {
const { host } = req?.headers || {}
// remove port from host and remove port if present
const hostname = host?.split(':')[0].toLowerCase()

for (const item of domainItems) {
// remove port if present
const domainHostname = item.domain?.split(':')[0].toLowerCase()
if (
hostname === domainHostname ||
detectedLocale?.toLowerCase() === item.defaultLocale.toLowerCase()
) {
domainItem = item
break
}
}
}

return domainItem
}
37 changes: 0 additions & 37 deletions packages/next/next-server/lib/i18n/detect-domain-locales.ts

This file was deleted.

15 changes: 0 additions & 15 deletions packages/next/next-server/server/config.ts
Expand Up @@ -238,22 +238,7 @@ function assignDefaults(userConfig: { [key: string]: any }) {
if (!item || typeof item !== 'object') return true
if (!item.defaultLocale) return true
if (!item.domain || typeof item.domain !== 'string') return true
if (!item.locales || !Array.isArray(item.locales)) return true

const invalidLocales = item.locales.filter(
(locale: string) => !i18n.locales.includes(locale)
)

if (invalidLocales.length > 0) {
console.error(
`i18n.domains item "${
item.domain
}" has the following locales (${invalidLocales.join(
', '
)}) that aren't provided in the main i18n.locales. Add them to the i18n.locales list or remove them from the domains item locales to continue.\n`
)
return true
}
return false
})

Expand Down
97 changes: 53 additions & 44 deletions packages/next/next-server/server/next-server.ts
Expand Up @@ -79,7 +79,7 @@ import accept from '@hapi/accept'
import { normalizeLocalePath } from '../lib/i18n/normalize-locale-path'
import { detectLocaleCookie } from '../lib/i18n/detect-locale-cookie'
import * as Log from '../../build/output/log'
import { detectDomainLocales } from '../lib/i18n/detect-domain-locales'
import { detectDomainLocale } from '../lib/i18n/detect-domain-locale'

const getCustomRouteMatcher = pathMatch(true)

Expand Down Expand Up @@ -306,67 +306,85 @@ export default class Server {
if (i18n && !parsedUrl.pathname?.startsWith('/_next')) {
// get pathname from URL with basePath stripped for locale detection
const { pathname, ...parsed } = parseUrl(req.url || '/')
let defaultLocale = i18n.defaultLocale
let detectedLocale = detectLocaleCookie(req, i18n.locales)

const { defaultLocale, locales } = detectDomainLocales(
req,
i18n.domains,
i18n.locales,
i18n.defaultLocale
)
const detectedDomain = detectDomainLocale(i18n.domains, req)
if (detectedDomain) {
defaultLocale = detectedDomain.defaultLocale
detectedLocale = defaultLocale
}

if (!detectedLocale) {
detectedLocale = accept.language(
req.headers['accept-language'],
locales
i18n.locales
)
}

let localeDomainRedirect: string | undefined
const localePathResult = normalizeLocalePath(pathname!, i18n.locales)

if (localePathResult.detectedLocale) {
detectedLocale = localePathResult.detectedLocale
req.url = formatUrl({
...parsed,
pathname: localePathResult.pathname,
})
parsedUrl.pathname = localePathResult.pathname

// check if the locale prefix matches a domain's defaultLocale
// and we're on a locale specific domain if so redirect to that domain
if (detectedDomain) {
const matchedDomain = detectDomainLocale(
i18n.domains,
undefined,
detectedLocale
)

if (matchedDomain) {
localeDomainRedirect = `http${matchedDomain.http ? '' : 's'}://${
matchedDomain?.domain
}`
}
}
}

const denormalizedPagePath = denormalizePagePath(pathname || '/')
const detectedDefaultLocale =
!detectedLocale ||
detectedLocale.toLowerCase() === defaultLocale.toLowerCase()
const shouldStripDefaultLocale =
detectedDefaultLocale &&
denormalizedPagePath.toLowerCase() === `/${defaultLocale.toLowerCase()}`
denormalizedPagePath.toLowerCase() ===
`/${i18n.defaultLocale.toLowerCase()}`
const shouldAddLocalePrefix =
!detectedDefaultLocale && denormalizedPagePath === '/'

detectedLocale = detectedLocale || defaultLocale
detectedLocale = detectedLocale || i18n.defaultLocale

if (
i18n.localeDetection !== false &&
(shouldAddLocalePrefix || shouldStripDefaultLocale)
(localeDomainRedirect ||
shouldAddLocalePrefix ||
shouldStripDefaultLocale)
) {
res.setHeader(
'Location',
formatUrl({
// make sure to include any query values when redirecting
...parsed,
pathname: shouldStripDefaultLocale ? '/' : `/${detectedLocale}`,
pathname: localeDomainRedirect
? localeDomainRedirect
: shouldStripDefaultLocale
? '/'
: `/${detectedLocale}`,
})
)
res.statusCode = 307
res.end()
return
}

const localePathResult = normalizeLocalePath(pathname!, locales)

if (localePathResult.detectedLocale) {
detectedLocale = localePathResult.detectedLocale
req.url = formatUrl({
...parsed,
pathname: localePathResult.pathname,
})
parsedUrl.pathname = localePathResult.pathname
}

// TODO: render with domain specific locales and defaultLocale also?
// Currently locale specific domains will have all locales populated
// under router.locales instead of only the domain specific ones
parsedUrl.query.__nextLocales = i18n.locales
// parsedUrl.query.__nextDefaultLocale = defaultLocale
parsedUrl.query.__nextLocale = detectedLocale || defaultLocale
}

Expand Down Expand Up @@ -517,21 +535,15 @@ export default class Server {

if (i18n) {
const localePathResult = normalizeLocalePath(pathname, i18n.locales)
const { defaultLocale } = detectDomainLocales(
req,
i18n.domains,
i18n.locales,
i18n.defaultLocale
)
const { defaultLocale } =
detectDomainLocale(i18n.domains, req) || {}
let detectedLocale = defaultLocale

if (localePathResult.detectedLocale) {
pathname = localePathResult.pathname
detectedLocale = localePathResult.detectedLocale
}
_parsedUrl.query.__nextLocales = i18n.locales
_parsedUrl.query.__nextLocale = detectedLocale
// _parsedUrl.query.__nextDefaultLocale = defaultLocale
_parsedUrl.query.__nextLocale = detectedLocale!
}
pathname = getRouteFromAssetPath(pathname, '.json')

Expand Down Expand Up @@ -1078,8 +1090,6 @@ export default class Server {
amp: query.amp,
_nextDataReq: query._nextDataReq,
__nextLocale: query.__nextLocale,
__nextLocales: query.__nextLocales,
// __nextDefaultLocale: query.__nextDefaultLocale,
}
: query),
...(params || {}),
Expand Down Expand Up @@ -1151,11 +1161,10 @@ export default class Server {
delete query._nextDataReq

const locale = query.__nextLocale as string
const locales = query.__nextLocales as string[]
// const defaultLocale = query.__nextDefaultLocale as string
delete query.__nextLocale
delete query.__nextLocales
// delete query.__nextDefaultLocale

const { i18n } = this.nextConfig.experimental
const locales = i18n.locales as string[]

let previewData: string | false | object | undefined
let isPreviewMode = false
Expand Down

0 comments on commit 9a5a152

Please sign in to comment.