@opennextjs/aws 3.5.0 → 3.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,30 @@
1
- import type { i18nConfig } from "../../../types/next-types";
2
- import type { InternalEvent } from "../../../types/open-next";
1
+ import type { DomainLocale, i18nConfig } from "../../../types/next-types";
2
+ import type { InternalEvent, InternalResult } from "../../../types/open-next";
3
+ /**
4
+ * @param arg an object containing the hostname and detectedLocale
5
+ * @returns The `DomainLocale` object if a domain is detected, `undefined` otherwise
6
+ */
7
+ export declare function detectDomainLocale({ hostname, detectedLocale, }: {
8
+ hostname?: string;
9
+ detectedLocale?: string;
10
+ }): DomainLocale | undefined;
11
+ /**
12
+ *
13
+ * @param internalEvent
14
+ * @param i18n
15
+ * @returns The detected locale, if `localeDetection` is set to `false` it will return the default locale **or** the domain default locale if a domain is detected.
16
+ */
3
17
  export declare function detectLocale(internalEvent: InternalEvent, i18n: i18nConfig): string;
18
+ /**
19
+ * This function is used for OpenNext internal routing to localize the path for next config rewrite/redirects/headers and the middleware
20
+ * @param internalEvent
21
+ * @returns The localized path
22
+ */
4
23
  export declare function localizePath(internalEvent: InternalEvent): string;
24
+ /**
25
+ *
26
+ * @param internalEvent
27
+ * In this function, for domain locale redirect we need to rely on the host to be present and correct
28
+ * @returns `false` if no redirect is needed, `InternalResult` if a redirect is needed
29
+ */
30
+ export declare function handleLocaleRedirect(internalEvent: InternalEvent): false | InternalResult;
@@ -1,5 +1,7 @@
1
1
  import { NextConfig } from "../../../adapters/config/index.js";
2
+ import { emptyReadableStream } from "../../../utils/stream.js";
2
3
  import { debug } from "../../../adapters/logger.js";
4
+ import { constructNextUrl } from "../util.js";
3
5
  import { acceptLanguage } from "./accept-header";
4
6
  function isLocalizedPath(path) {
5
7
  return (NextConfig.i18n?.locales.includes(path.split("/")[1].toLowerCase()) ?? false);
@@ -12,9 +14,40 @@ function getLocaleFromCookie(cookies) {
12
14
  ? i18n?.locales.find((locale) => nextLocale === locale.toLowerCase())
13
15
  : undefined;
14
16
  }
17
+ // Inspired by https://github.com/vercel/next.js/blob/6d93d652e0e7ba72d9a3b66e78746dce2069db03/packages/next/src/shared/lib/i18n/detect-domain-locale.ts#L3-L25
18
+ /**
19
+ * @param arg an object containing the hostname and detectedLocale
20
+ * @returns The `DomainLocale` object if a domain is detected, `undefined` otherwise
21
+ */
22
+ export function detectDomainLocale({ hostname, detectedLocale, }) {
23
+ const i18n = NextConfig.i18n;
24
+ const domains = i18n?.domains;
25
+ if (!domains) {
26
+ return;
27
+ }
28
+ const lowercasedLocale = detectedLocale?.toLowerCase();
29
+ for (const domain of domains) {
30
+ // We remove the port if present
31
+ const domainHostname = domain.domain.split(":", 1)[0].toLowerCase();
32
+ if (hostname === domainHostname ||
33
+ lowercasedLocale === domain.defaultLocale.toLowerCase() ||
34
+ domain.locales?.some((locale) => lowercasedLocale === locale.toLowerCase())) {
35
+ return domain;
36
+ }
37
+ }
38
+ }
39
+ /**
40
+ *
41
+ * @param internalEvent
42
+ * @param i18n
43
+ * @returns The detected locale, if `localeDetection` is set to `false` it will return the default locale **or** the domain default locale if a domain is detected.
44
+ */
15
45
  export function detectLocale(internalEvent, i18n) {
46
+ const domainLocale = detectDomainLocale({
47
+ hostname: internalEvent.headers.host,
48
+ });
16
49
  if (i18n.localeDetection === false) {
17
- return i18n.defaultLocale;
50
+ return domainLocale?.defaultLocale ?? i18n.defaultLocale;
18
51
  }
19
52
  const cookiesLocale = getLocaleFromCookie(internalEvent.cookies);
20
53
  const preferredLocale = acceptLanguage(internalEvent.headers["accept-language"], i18n?.locales);
@@ -22,18 +55,79 @@ export function detectLocale(internalEvent, i18n) {
22
55
  cookiesLocale,
23
56
  preferredLocale,
24
57
  defaultLocale: i18n.defaultLocale,
58
+ domainLocale,
25
59
  });
26
- return cookiesLocale ?? preferredLocale ?? i18n.defaultLocale;
27
- // TODO: handle domain based locale detection
60
+ return (domainLocale?.defaultLocale ??
61
+ cookiesLocale ??
62
+ preferredLocale ??
63
+ i18n.defaultLocale);
28
64
  }
65
+ /**
66
+ * This function is used for OpenNext internal routing to localize the path for next config rewrite/redirects/headers and the middleware
67
+ * @param internalEvent
68
+ * @returns The localized path
69
+ */
29
70
  export function localizePath(internalEvent) {
30
71
  const i18n = NextConfig.i18n;
31
72
  if (!i18n) {
32
73
  return internalEvent.rawPath;
33
74
  }
75
+ // When the path is already localized we don't need to do anything
34
76
  if (isLocalizedPath(internalEvent.rawPath)) {
35
77
  return internalEvent.rawPath;
36
78
  }
37
79
  const detectedLocale = detectLocale(internalEvent, i18n);
38
80
  return `/${detectedLocale}${internalEvent.rawPath}`;
39
81
  }
82
+ /**
83
+ *
84
+ * @param internalEvent
85
+ * In this function, for domain locale redirect we need to rely on the host to be present and correct
86
+ * @returns `false` if no redirect is needed, `InternalResult` if a redirect is needed
87
+ */
88
+ export function handleLocaleRedirect(internalEvent) {
89
+ const i18n = NextConfig.i18n;
90
+ if (!i18n ||
91
+ i18n.localeDetection === false ||
92
+ internalEvent.rawPath !== "/") {
93
+ return false;
94
+ }
95
+ const preferredLocale = acceptLanguage(internalEvent.headers["accept-language"], i18n?.locales);
96
+ const detectedLocale = detectLocale(internalEvent, i18n);
97
+ const domainLocale = detectDomainLocale({
98
+ hostname: internalEvent.headers.host,
99
+ });
100
+ const preferredDomain = detectDomainLocale({
101
+ detectedLocale: preferredLocale,
102
+ });
103
+ if (domainLocale && preferredDomain) {
104
+ const isPDomain = preferredDomain.domain === domainLocale.domain;
105
+ const isPLocale = preferredDomain.defaultLocale === preferredLocale;
106
+ if (!isPDomain || !isPLocale) {
107
+ const scheme = `http${preferredDomain.http ? "" : "s"}`;
108
+ const rlocale = isPLocale ? "" : preferredLocale;
109
+ return {
110
+ type: "core",
111
+ statusCode: 307,
112
+ headers: {
113
+ Location: `${scheme}://${preferredDomain.domain}/${rlocale}`,
114
+ },
115
+ body: emptyReadableStream(),
116
+ isBase64Encoded: false,
117
+ };
118
+ }
119
+ }
120
+ const defaultLocale = domainLocale?.defaultLocale ?? i18n.defaultLocale;
121
+ if (detectedLocale.toLowerCase() !== defaultLocale.toLowerCase()) {
122
+ return {
123
+ type: "core",
124
+ statusCode: 307,
125
+ headers: {
126
+ Location: constructNextUrl(internalEvent.url, `/${detectedLocale}`),
127
+ },
128
+ body: emptyReadableStream(),
129
+ isBase64Encoded: false,
130
+ };
131
+ }
132
+ return false;
133
+ }
@@ -2,7 +2,7 @@ import { NextConfig } from "../../adapters/config/index";
2
2
  import { compile, match } from "path-to-regexp";
3
3
  import { emptyReadableStream, toReadableStream } from "../../utils/stream";
4
4
  import { debug } from "../../adapters/logger";
5
- import { localizePath } from "./i18n";
5
+ import { handleLocaleRedirect, localizePath } from "./i18n";
6
6
  import { constructNextUrl, convertFromQueryString, convertToQueryString, escapeRegex, getUrlParts, isExternal, unescapeRegex, } from "./util";
7
7
  const routeHasMatcher = (headers, cookies, query) => (redirect) => {
8
8
  switch (redirect.type) {
@@ -215,6 +215,9 @@ export function handleRedirects(event, redirects) {
215
215
  const trailingSlashRedirect = handleTrailingSlashRedirect(event);
216
216
  if (trailingSlashRedirect)
217
217
  return trailingSlashRedirect;
218
+ const localeRedirect = handleLocaleRedirect(event);
219
+ if (localeRedirect)
220
+ return localeRedirect;
218
221
  const { internalEvent, __rewrite } = handleRewrites(event, redirects.filter((r) => !r.internal));
219
222
  if (__rewrite && !__rewrite.internal) {
220
223
  return {
@@ -67,7 +67,8 @@ export function getUrlParts(url, isExternal) {
67
67
  * @__PURE__
68
68
  */
69
69
  export function constructNextUrl(baseUrl, path) {
70
- const nextBasePath = NextConfig.basePath;
70
+ // basePath is generated as "" if not provided on Next.js 15 (not sure about older versions)
71
+ const nextBasePath = NextConfig.basePath ?? "";
71
72
  const url = new URL(`${nextBasePath}${path}`, baseUrl);
72
73
  return url.href;
73
74
  }
@@ -52,9 +52,16 @@ export type Header = {
52
52
  has?: RouteHas[];
53
53
  missing?: RouteHas[];
54
54
  };
55
+ export interface DomainLocale {
56
+ defaultLocale: string;
57
+ domain: string;
58
+ http?: true;
59
+ locales: readonly string[];
60
+ }
55
61
  export interface i18nConfig {
56
62
  locales: string[];
57
63
  defaultLocale: string;
64
+ domains?: DomainLocale[];
58
65
  localeDetection?: false;
59
66
  }
60
67
  export interface NextConfig {
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "access": "public"
4
4
  },
5
5
  "name": "@opennextjs/aws",
6
- "version": "3.5.0",
6
+ "version": "3.5.1",
7
7
  "bin": {
8
8
  "open-next": "./dist/index.js"
9
9
  },