1
1
import { 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" ;
4
4
5
+ import { emptyReadableStream } from "utils/stream.js" ;
5
6
import { debug } from "../../../adapters/logger.js" ;
7
+ import { constructNextUrl } from "../util.js" ;
6
8
import { acceptLanguage } from "./accept-header" ;
7
9
8
10
function isLocalizedPath ( path : string ) : boolean {
@@ -20,6 +22,34 @@ function getLocaleFromCookie(cookies: Record<string, string>) {
20
22
: undefined ;
21
23
}
22
24
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
+
23
53
export function detectLocale (
24
54
internalEvent : InternalEvent ,
25
55
i18n : i18nConfig ,
@@ -39,9 +69,16 @@ export function detectLocale(
39
69
defaultLocale : i18n . defaultLocale ,
40
70
} ) ;
41
71
42
- return cookiesLocale ?? preferredLocale ?? i18n . defaultLocale ;
72
+ const domainLocale = detectDomainLocale ( {
73
+ hostname : internalEvent . headers . host ,
74
+ } ) ;
43
75
44
- // TODO: handle domain based locale detection
76
+ return (
77
+ domainLocale ?. defaultLocale ??
78
+ cookiesLocale ??
79
+ preferredLocale ??
80
+ i18n . defaultLocale
81
+ ) ;
45
82
}
46
83
47
84
export function localizePath ( internalEvent : InternalEvent ) : string {
@@ -52,6 +89,73 @@ export function localizePath(internalEvent: InternalEvent): string {
52
89
if ( isLocalizedPath ( internalEvent . rawPath ) ) {
53
90
return internalEvent . rawPath ;
54
91
}
92
+
55
93
const detectedLocale = detectLocale ( internalEvent , i18n ) ;
94
+
56
95
return `/${ detectedLocale } ${ internalEvent . rawPath } ` ;
57
96
}
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