11import { NextConfig } from "config/index.js" ;
2- import type { i18nConfig } from "types/next-types" ;
3- import type { InternalEvent } from "types/open-next" ;
2+ import type { DomainLocale , i18nConfig } from "types/next-types" ;
3+ import type { InternalEvent , InternalResult } from "types/open-next" ;
44
5+ import { emptyReadableStream } from "utils/stream.js" ;
56import { debug } from "../../../adapters/logger.js" ;
7+ import { constructNextUrl } from "../util.js" ;
68import { acceptLanguage } from "./accept-header" ;
79
810function isLocalizedPath ( path : string ) : boolean {
@@ -20,6 +22,34 @@ function getLocaleFromCookie(cookies: Record<string, string>) {
2022 : undefined ;
2123}
2224
25+ // Inspired by https://github.com/vercel/next.js/blob/6d93d652e0e7ba72d9a3b66e78746dce2069db03/packages/next/src/shared/lib/i18n/detect-domain-locale.ts#L3-L25
26+ export function detectDomainLocale ( {
27+ hostname,
28+ detectedLocale,
29+ } : {
30+ hostname ?: string ;
31+ detectedLocale ?: string ;
32+ } ) : DomainLocale | undefined {
33+ const i18n = NextConfig . i18n ;
34+ if ( ! i18n || i18n . localeDetection === false || ! i18n . domains ) {
35+ return ;
36+ }
37+ const lowercasedLocale = detectedLocale ?. toLowerCase ( ) ;
38+ for ( const domain of i18n . domains ) {
39+ // We remove the port if present
40+ const domainHostname = domain . domain . split ( ":" , 1 ) [ 0 ] . toLowerCase ( ) ;
41+ if (
42+ hostname === domainHostname ||
43+ lowercasedLocale === domain . defaultLocale . toLowerCase ( ) ||
44+ domain . locales ?. some (
45+ ( locale ) => lowercasedLocale === locale . toLowerCase ( ) ,
46+ )
47+ ) {
48+ return domain ;
49+ }
50+ }
51+ }
52+
2353export function detectLocale (
2454 internalEvent : InternalEvent ,
2555 i18n : i18nConfig ,
@@ -39,9 +69,16 @@ export function detectLocale(
3969 defaultLocale : i18n . defaultLocale ,
4070 } ) ;
4171
42- return cookiesLocale ?? preferredLocale ?? i18n . defaultLocale ;
72+ const domainLocale = detectDomainLocale ( {
73+ hostname : internalEvent . headers . host ,
74+ } ) ;
4375
44- // TODO: handle domain based locale detection
76+ return (
77+ domainLocale ?. defaultLocale ??
78+ cookiesLocale ??
79+ preferredLocale ??
80+ i18n . defaultLocale
81+ ) ;
4582}
4683
4784export function localizePath ( internalEvent : InternalEvent ) : string {
@@ -52,6 +89,73 @@ export function localizePath(internalEvent: InternalEvent): string {
5289 if ( isLocalizedPath ( internalEvent . rawPath ) ) {
5390 return internalEvent . rawPath ;
5491 }
92+
5593 const detectedLocale = detectLocale ( internalEvent , i18n ) ;
94+
5695 return `/${ detectedLocale } ${ internalEvent . rawPath } ` ;
5796}
97+
98+ /**
99+ *
100+ * @param internalEvent
101+ * In this function, for domain locale redirect we need to rely on the host to be present and correct
102+ * @returns `false` if no redirect is needed, `InternalResult` if a redirect is needed
103+ */
104+ export function handleLocaleRedirect (
105+ internalEvent : InternalEvent ,
106+ ) : false | InternalResult {
107+ const i18n = NextConfig . i18n ;
108+ if (
109+ ! i18n ||
110+ i18n . localeDetection === false ||
111+ internalEvent . rawPath !== "/"
112+ ) {
113+ return false ;
114+ }
115+ const preferredLocale = acceptLanguage (
116+ internalEvent . headers [ "accept-language" ] ,
117+ i18n ?. locales ,
118+ ) ;
119+
120+ const detectedLocale = detectLocale ( internalEvent , i18n ) ;
121+
122+ const domainLocale = detectDomainLocale ( {
123+ hostname : internalEvent . headers . host ,
124+ } ) ;
125+ const preferredDomain = detectDomainLocale ( {
126+ detectedLocale : preferredLocale ,
127+ } ) ;
128+
129+ if ( domainLocale && preferredDomain ) {
130+ const isPDomain = preferredDomain . domain === domainLocale . domain ;
131+ const isPLocale = preferredDomain . defaultLocale === preferredLocale ;
132+ if ( ! isPDomain || ! isPLocale ) {
133+ const scheme = `http${ preferredDomain . http ? "" : "s" } ` ;
134+ const rlocale = isPLocale ? "" : preferredLocale ;
135+ return {
136+ type : "core" ,
137+ statusCode : 307 ,
138+ headers : {
139+ Location : `${ scheme } ://${ preferredDomain . domain } /${ rlocale } ` ,
140+ } ,
141+ body : emptyReadableStream ( ) ,
142+ isBase64Encoded : false ,
143+ } ;
144+ }
145+ }
146+
147+ const defaultLocale = domainLocale ?. defaultLocale ?? i18n . defaultLocale ;
148+
149+ if ( detectedLocale . toLowerCase ( ) !== defaultLocale . toLowerCase ( ) ) {
150+ return {
151+ type : "core" ,
152+ statusCode : 307 ,
153+ headers : {
154+ Location : constructNextUrl ( internalEvent . url , `/${ detectedLocale } ` ) ,
155+ } ,
156+ body : emptyReadableStream ( ) ,
157+ isBase64Encoded : false ,
158+ } ;
159+ }
160+ return false ;
161+ }
0 commit comments