Skip to content

Commit a7b62c1

Browse files
committed
Update to only handle domain specific locales
1 parent 9c65c99 commit a7b62c1

File tree

7 files changed

+101
-16
lines changed

7 files changed

+101
-16
lines changed

docs/advanced-features/i18n-routing.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,17 @@ module.exports = {
3939
{
4040
domain: 'example.com',
4141
defaultLocale: 'en-US',
42+
locales: ['en-US'],
4243
},
4344
{
4445
domain: 'example.nl',
4546
defaultLocale: 'nl-NL',
47+
locales: ['nl-NL'],
4648
},
4749
{
4850
domain: 'example.fr',
4951
defaultLocale: 'fr',
52+
locales: ['fr'],
5053
},
5154
],
5255
},

packages/next/build/webpack/loaders/next-serverless-loader.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,18 @@ const nextServerlessLoader: loader.Loader = function () {
264264
})
265265
parsedUrl.pathname = localePathResult.pathname
266266
267+
// if we are on a locale domain and a locale path is detected
268+
// but isn't configured for that domain render the 404
269+
if (
270+
detectedDomain &&
271+
!detectedDomain.locales.includes(localePathResult.detectedLocale)
272+
) {
273+
// TODO: should this 404 for the default locale until we provide
274+
// redirecting to strip default locale from the path?
275+
parsedUrl.query.__nextLocale = detectedDomain.defaultLocale
276+
return this.render404(req, res, parsedUrl)
277+
}
278+
267279
// check if the locale prefix matches a domain's defaultLocale
268280
// and we're on a locale specific domain if so redirect to that domain
269281
// if (detectedDomain) {

packages/next/next-server/lib/i18n/detect-domain-locale.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export function detectDomainLocale(
33
| Array<{
44
http?: boolean
55
domain: string
6+
locales: string[]
67
defaultLocale: string
78
}>
89
| undefined,
@@ -13,6 +14,7 @@ export function detectDomainLocale(
1314
| {
1415
http?: boolean
1516
domain: string
17+
locales: string[]
1618
defaultLocale: string
1719
}
1820
| undefined

packages/next/next-server/server/config.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,32 @@ function assignDefaults(userConfig: { [key: string]: any }) {
295295
if (!item.defaultLocale) return true
296296
if (!item.domain || typeof item.domain !== 'string') return true
297297

298+
if (Array.isArray(item.locales)) {
299+
const invalidLocaleItems = item.locales.filter((locale: any) => {
300+
if (typeof locale !== 'string') return true
301+
302+
// automatically add the locale to the main locales config
303+
// so pre-rendering and such can use this as the source of all
304+
// configured locales
305+
if (!i18n.locales.includes(locale)) {
306+
i18n.locales.push(locale)
307+
}
308+
return false
309+
})
310+
311+
if (invalidLocaleItems.length > 0) {
312+
console.error(
313+
`Invalid domain locales for ${
314+
item.domain
315+
}, received (${invalidLocaleItems.map(String).join(', ')}). ` +
316+
`Items must be valid locale strings`
317+
)
318+
return true
319+
}
320+
} else {
321+
item.locales = []
322+
}
323+
298324
return false
299325
})
300326

packages/next/next-server/server/next-server.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,18 @@ export default class Server {
343343
;(req as any).__nextStrippedLocale = true
344344
parsedUrl.pathname = localePathResult.pathname
345345

346+
// if we are on a locale domain and a locale path is detected
347+
// but isn't configured for that domain render the 404
348+
if (
349+
detectedDomain &&
350+
!detectedDomain.locales.includes(localePathResult.detectedLocale)
351+
) {
352+
// TODO: should this 404 for the default locale until we provide
353+
// redirecting to strip default locale from the path?
354+
parsedUrl.query.__nextLocale = detectedDomain?.defaultLocale!
355+
return this.render404(req, res, parsedUrl)
356+
}
357+
346358
// check if the locale prefix matches a domain's defaultLocale
347359
// and we're on a locale specific domain if so redirect to that domain
348360
// if (detectedDomain) {
@@ -584,13 +596,25 @@ export default class Server {
584596
// remove port from host and remove port if present
585597
const hostname = host?.split(':')[0].toLowerCase()
586598
const localePathResult = normalizeLocalePath(pathname, i18n.locales)
587-
const { defaultLocale } =
588-
detectDomainLocale(i18n.domains, hostname) || {}
589-
let detectedLocale = defaultLocale
599+
const detectedDomain = detectDomainLocale(i18n.domains, hostname)
600+
let detectedLocale = detectedDomain?.defaultLocale
590601

591602
if (localePathResult.detectedLocale) {
592603
pathname = localePathResult.pathname
593604
detectedLocale = localePathResult.detectedLocale
605+
606+
if (
607+
detectedDomain &&
608+
!detectedDomain.locales.includes(
609+
localePathResult.detectedLocale
610+
)
611+
) {
612+
_parsedUrl.query.__nextLocale = detectedDomain.defaultLocale
613+
await this.render404(req, res, _parsedUrl)
614+
return {
615+
finished: true,
616+
}
617+
}
594618
}
595619
_parsedUrl.query.__nextLocale = detectedLocale!
596620
}

test/integration/i18n-support/next.config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@ module.exports = {
1111
http: true,
1212
domain: 'example.be',
1313
defaultLocale: 'nl-BE',
14+
locales: ['nl', 'nl-NL', 'nl-BE'],
1415
},
1516
{
1617
http: true,
1718
domain: 'example.fr',
1819
defaultLocale: 'fr',
20+
locales: ['fr', 'fr-BE'],
1921
},
2022
],
2123
},

test/integration/i18n-support/test/index.test.js

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,13 @@ function runTests(isDev) {
5252
http: true,
5353
domain: 'example.be',
5454
defaultLocale: 'nl-BE',
55+
locales: ['nl', 'nl-NL', 'nl-BE'],
5556
},
5657
{
5758
http: true,
5859
domain: 'example.fr',
5960
defaultLocale: 'fr',
61+
locales: ['fr', 'fr-BE'],
6062
},
6163
],
6264
})
@@ -661,12 +663,17 @@ function runTests(isDev) {
661663
})
662664

663665
it('should handle locales with domain', async () => {
664-
const checkDomainLocales = async (domainDefault = '', domain = '') => {
666+
const checkDomainLocales = async (
667+
domainDefault = '',
668+
domainLocales = [],
669+
domain = ''
670+
) => {
665671
for (const locale of locales) {
666-
// skip other domains' default locale since we redirect these
667-
if (['fr', 'nl-BE'].includes(locale) && locale !== domainDefault) {
668-
continue
669-
}
672+
// other domains' default locale is redirected
673+
const isRedirected =
674+
['fr', 'nl-BE'].includes(locale) && locale !== domainDefault
675+
676+
if (isRedirected) continue
670677

671678
const res = await fetchViaHTTP(
672679
appPort,
@@ -680,19 +687,28 @@ function runTests(isDev) {
680687
}
681688
)
682689

683-
expect(res.status).toBe(200)
690+
const isDomain404 = !domainLocales.includes(locale)
691+
692+
expect(res.status).toBe(isDomain404 ? 404 : 200)
684693

685-
const html = await res.text()
686-
const $ = cheerio.load(html)
694+
if (!isRedirected) {
695+
const html = await res.text()
696+
const $ = cheerio.load(html)
687697

688-
expect($('html').attr('lang')).toBe(locale)
689-
expect($('#router-locale').text()).toBe(locale)
690-
expect(JSON.parse($('#router-locales').text())).toEqual(locales)
698+
expect($('html').attr('lang')).toBe(
699+
isDomain404 ? domainDefault : locale
700+
)
701+
702+
if (!isDomain404) {
703+
expect($('#router-locale').text()).toBe(locale)
704+
expect(JSON.parse($('#router-locales').text())).toEqual(locales)
705+
}
706+
}
691707
}
692708
}
693709

694-
await checkDomainLocales('nl-BE', 'example.be')
695-
await checkDomainLocales('fr', 'example.fr')
710+
await checkDomainLocales('nl-BE', ['nl', 'nl-NL', 'nl-BE'], 'example.be')
711+
await checkDomainLocales('fr', ['fr', 'fr-BE'], 'example.fr')
696712
})
697713

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

0 commit comments

Comments
 (0)