Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update to only handle domain specific locales #18240

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/advanced-features/i18n-routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,17 @@ module.exports = {
{
domain: 'example.com',
defaultLocale: 'en-US',
locales: ['en-US'],
},
{
domain: 'example.nl',
defaultLocale: 'nl-NL',
locales: ['nl-NL'],
},
{
domain: 'example.fr',
defaultLocale: 'fr',
locales: ['fr'],
},
],
},
Expand Down
12 changes: 12 additions & 0 deletions packages/next/build/webpack/loaders/next-serverless-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,18 @@ const nextServerlessLoader: loader.Loader = function () {
})
parsedUrl.pathname = localePathResult.pathname

// if we are on a locale domain and a locale path is detected
// but isn't configured for that domain render the 404
if (
detectedDomain &&
!detectedDomain.locales.includes(localePathResult.detectedLocale)
) {
// TODO: should this 404 for the default locale until we provide
// redirecting to strip default locale from the path?
parsedUrl.query.__nextLocale = detectedDomain.defaultLocale
return this.render404(req, res, parsedUrl)
}

// 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) {
Expand Down
2 changes: 2 additions & 0 deletions packages/next/next-server/lib/i18n/detect-domain-locale.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export function detectDomainLocale(
| Array<{
http?: boolean
domain: string
locales: string[]
defaultLocale: string
}>
| undefined,
Expand All @@ -13,6 +14,7 @@ export function detectDomainLocale(
| {
http?: boolean
domain: string
locales: string[]
defaultLocale: string
}
| undefined
Expand Down
26 changes: 26 additions & 0 deletions packages/next/next-server/server/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,32 @@ function assignDefaults(userConfig: { [key: string]: any }) {
if (!item.defaultLocale) return true
if (!item.domain || typeof item.domain !== 'string') return true

if (Array.isArray(item.locales)) {
const invalidLocaleItems = item.locales.filter((locale: any) => {
if (typeof locale !== 'string') return true

// automatically add the locale to the main locales config
// so pre-rendering and such can use this as the source of all
// configured locales
if (!i18n.locales.includes(locale)) {
i18n.locales.push(locale)
}
return false
})

if (invalidLocaleItems.length > 0) {
console.error(
`Invalid domain locales for ${
item.domain
}, received (${invalidLocaleItems.map(String).join(', ')}). ` +
`Items must be valid locale strings`
)
return true
}
} else {
item.locales = []
}

return false
})

Expand Down
30 changes: 27 additions & 3 deletions packages/next/next-server/server/next-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,18 @@ export default class Server {
;(req as any).__nextStrippedLocale = true
parsedUrl.pathname = localePathResult.pathname

// if we are on a locale domain and a locale path is detected
// but isn't configured for that domain render the 404
if (
detectedDomain &&
!detectedDomain.locales.includes(localePathResult.detectedLocale)
) {
// TODO: should this 404 for the default locale until we provide
// redirecting to strip default locale from the path?
parsedUrl.query.__nextLocale = detectedDomain?.defaultLocale!
return this.render404(req, res, parsedUrl)
}

// 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) {
Expand Down Expand Up @@ -584,13 +596,25 @@ export default class Server {
// remove port from host and remove port if present
const hostname = host?.split(':')[0].toLowerCase()
const localePathResult = normalizeLocalePath(pathname, i18n.locales)
const { defaultLocale } =
detectDomainLocale(i18n.domains, hostname) || {}
let detectedLocale = defaultLocale
const detectedDomain = detectDomainLocale(i18n.domains, hostname)
let detectedLocale = detectedDomain?.defaultLocale

if (localePathResult.detectedLocale) {
pathname = localePathResult.pathname
detectedLocale = localePathResult.detectedLocale

if (
detectedDomain &&
!detectedDomain.locales.includes(
localePathResult.detectedLocale
)
) {
_parsedUrl.query.__nextLocale = detectedDomain.defaultLocale
await this.render404(req, res, _parsedUrl)
return {
finished: true,
}
}
}
_parsedUrl.query.__nextLocale = detectedLocale!
}
Expand Down
2 changes: 2 additions & 0 deletions test/integration/i18n-support/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ module.exports = {
http: true,
domain: 'example.be',
defaultLocale: 'nl-BE',
locales: ['nl', 'nl-NL', 'nl-BE'],
},
{
http: true,
domain: 'example.fr',
defaultLocale: 'fr',
locales: ['fr', 'fr-BE'],
},
],
},
Expand Down
42 changes: 29 additions & 13 deletions test/integration/i18n-support/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,13 @@ function runTests(isDev) {
http: true,
domain: 'example.be',
defaultLocale: 'nl-BE',
locales: ['nl', 'nl-NL', 'nl-BE'],
},
{
http: true,
domain: 'example.fr',
defaultLocale: 'fr',
locales: ['fr', 'fr-BE'],
},
],
})
Expand Down Expand Up @@ -661,12 +663,17 @@ function runTests(isDev) {
})

it('should handle locales with domain', async () => {
const checkDomainLocales = async (domainDefault = '', domain = '') => {
const checkDomainLocales = async (
domainDefault = '',
domainLocales = [],
domain = ''
) => {
for (const locale of locales) {
// skip other domains' default locale since we redirect these
if (['fr', 'nl-BE'].includes(locale) && locale !== domainDefault) {
continue
}
// other domains' default locale is redirected
const isRedirected =
['fr', 'nl-BE'].includes(locale) && locale !== domainDefault

if (isRedirected) continue

const res = await fetchViaHTTP(
appPort,
Expand All @@ -680,19 +687,28 @@ function runTests(isDev) {
}
)

expect(res.status).toBe(200)
const isDomain404 = !domainLocales.includes(locale)

expect(res.status).toBe(isDomain404 ? 404 : 200)

const html = await res.text()
const $ = cheerio.load(html)
if (!isRedirected) {
const html = await res.text()
const $ = cheerio.load(html)

expect($('html').attr('lang')).toBe(locale)
expect($('#router-locale').text()).toBe(locale)
expect(JSON.parse($('#router-locales').text())).toEqual(locales)
expect($('html').attr('lang')).toBe(
isDomain404 ? domainDefault : locale
)

if (!isDomain404) {
expect($('#router-locale').text()).toBe(locale)
expect(JSON.parse($('#router-locales').text())).toEqual(locales)
}
}
}
}

await checkDomainLocales('nl-BE', 'example.be')
await checkDomainLocales('fr', 'example.fr')
await checkDomainLocales('nl-BE', ['nl', 'nl-NL', 'nl-BE'], 'example.be')
await checkDomainLocales('fr', ['fr', 'fr-BE'], 'example.fr')
})

it('should generate AMP pages with all locales', async () => {
Expand Down