Skip to content

Commit 370200e

Browse files
committed
handle locale redirect
1 parent 8348e2a commit 370200e

File tree

2 files changed

+76
-17
lines changed

2 files changed

+76
-17
lines changed

packages/open-next/src/core/routing/i18n/index.ts

Lines changed: 71 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { NextConfig } from "config/index.js";
22
import type { DomainLocale, i18nConfig } from "types/next-types";
3-
import type { InternalEvent } from "types/open-next";
3+
import type { InternalEvent, InternalResult } from "types/open-next";
44

55
import { debug } from "../../../adapters/logger.js";
66
import { acceptLanguage } from "./accept-header";
7+
import { emptyReadableStream } from "utils/stream.js";
78

89
function isLocalizedPath(path: string): boolean {
910
return (
@@ -21,15 +22,15 @@ function getLocaleFromCookie(cookies: Record<string, string>) {
2122
}
2223

2324
// Inspired by https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/i18n/detect-domain-locale.ts
24-
export function detectDomainLocale(
25-
hostname: string,
26-
i18n: i18nConfig,
27-
detectedLocale?: string,
28-
): DomainLocale | undefined {
29-
if (i18n.localeDetection === false) {
30-
return;
31-
}
32-
if (!i18n.domains) {
25+
export function detectDomainLocale({
26+
hostname,
27+
detectedLocale,
28+
}: {
29+
hostname?: string;
30+
detectedLocale?: string;
31+
}): DomainLocale | undefined {
32+
const i18n = NextConfig.i18n;
33+
if (!i18n || i18n.localeDetection === false || !i18n.domains) {
3334
return;
3435
}
3536
for (const item of i18n.domains) {
@@ -64,7 +65,9 @@ export function detectLocale(
6465
defaultLocale: i18n.defaultLocale,
6566
});
6667

67-
const domainLocale = detectDomainLocale(internalEvent.headers.host, i18n);
68+
const domainLocale = detectDomainLocale({
69+
hostname: internalEvent.headers.host,
70+
});
6871

6972
return (
7073
domainLocale?.defaultLocale ??
@@ -82,17 +85,70 @@ export function localizePath(internalEvent: InternalEvent): string {
8285
if (isLocalizedPath(internalEvent.rawPath)) {
8386
return internalEvent.rawPath;
8487
}
88+
89+
const detectedLocale = detectLocale(internalEvent, i18n);
90+
91+
return `/${detectedLocale}${internalEvent.rawPath}`;
92+
}
93+
94+
export function handleLocaleRedirect(
95+
internalEvent: InternalEvent,
96+
): false | InternalResult {
97+
const i18n = NextConfig.i18n;
98+
if (
99+
!i18n ||
100+
i18n.localeDetection === false ||
101+
internalEvent.rawPath !== "/"
102+
) {
103+
return false;
104+
}
85105
const preferredLocale = acceptLanguage(
86106
internalEvent.headers["accept-language"],
87107
i18n?.locales,
88108
);
89109

90110
const detectedLocale = detectLocale(internalEvent, i18n);
91111

92-
// not entirely sure if we should do that or not here
93-
if (preferredLocale && preferredLocale !== detectedLocale) {
94-
return `/${preferredLocale}${internalEvent.rawPath}`;
112+
const domainLocale = detectDomainLocale({
113+
hostname: internalEvent.headers.host,
114+
});
115+
const preferredDomain = detectDomainLocale({
116+
detectedLocale: preferredLocale?.toLowerCase(),
117+
});
118+
119+
if (domainLocale && preferredDomain) {
120+
const isPDomain = preferredDomain.domain === domainLocale.domain;
121+
const isPLocale = preferredDomain.defaultLocale === preferredLocale;
122+
if (!isPDomain || !isPLocale) {
123+
const scheme = `http${preferredDomain.http ? "" : "s"}`;
124+
const rlocale = isPLocale ? "" : preferredLocale;
125+
return {
126+
type: "core",
127+
statusCode: 307,
128+
headers: {
129+
Location: `${scheme}://${preferredDomain.domain}/${rlocale}`,
130+
},
131+
body: emptyReadableStream(),
132+
isBase64Encoded: false,
133+
};
134+
}
95135
}
96136

97-
return `/${detectedLocale}${internalEvent.rawPath}`;
137+
const defaultLocale = domainLocale?.defaultLocale ?? i18n.defaultLocale;
138+
139+
if (detectedLocale.toLowerCase() !== defaultLocale.toLowerCase()) {
140+
return {
141+
type: "core",
142+
statusCode: 307,
143+
headers: {
144+
Location: new URL(
145+
`${NextConfig.basePath || ""}/${detectedLocale}`,
146+
internalEvent.url,
147+
).href,
148+
},
149+
body: emptyReadableStream(),
150+
isBase64Encoded: false,
151+
};
152+
}
153+
return false;
98154
}

packages/open-next/src/core/routing/matcher.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import type { InternalEvent, InternalResult } from "types/open-next";
1212
import { emptyReadableStream, toReadableStream } from "utils/stream";
1313

1414
import { debug } from "../../adapters/logger";
15-
import { localizePath } from "./i18n";
15+
import { handleLocaleRedirect, localizePath } from "./i18n";
1616
import {
1717
constructNextUrl,
1818
convertFromQueryString,
@@ -316,8 +316,11 @@ export function handleRedirects(
316316
redirects: RedirectDefinition[],
317317
): InternalResult | undefined {
318318
const trailingSlashRedirect = handleTrailingSlashRedirect(event);
319-
// TODO: handle locale redirects directly from here, at the moment it's next that will handle it
320319
if (trailingSlashRedirect) return trailingSlashRedirect;
320+
321+
const localeRedirect = handleLocaleRedirect(event);
322+
if (localeRedirect) return localeRedirect;
323+
321324
const { internalEvent, __rewrite } = handleRewrites(
322325
event,
323326
redirects.filter((r) => !r.internal),

0 commit comments

Comments
 (0)