Files

144 lines
4.5 KiB
JavaScript

import { removeTrailingForwardSlash } from "@astrojs/internal-helpers/path";
import { normalizeTheLocale, pathHasLocale } from "./index.js";
class I18nRouter {
#strategy;
#defaultLocale;
#locales;
#base;
#domains;
constructor(options) {
this.#strategy = options.strategy;
this.#defaultLocale = options.defaultLocale;
this.#locales = options.locales;
this.#base = options.base === "/" ? "/" : removeTrailingForwardSlash(options.base || "");
this.#domains = options.domains;
}
/**
* Evaluate routing strategy for a pathname.
* Returns decision object (not HTTP Response).
*/
match(pathname, context) {
if (this.shouldSkipProcessing(pathname, context)) {
return { type: "continue" };
}
switch (this.#strategy) {
case "manual":
return { type: "continue" };
case "pathname-prefix-always":
return this.matchPrefixAlways(pathname, context);
case "domains-prefix-always":
if (this.localeHasntDomain(context.currentLocale, context.currentDomain)) {
return { type: "continue" };
}
return this.matchPrefixAlways(pathname, context);
case "pathname-prefix-other-locales":
return this.matchPrefixOtherLocales(pathname, context);
case "domains-prefix-other-locales":
if (this.localeHasntDomain(context.currentLocale, context.currentDomain)) {
return { type: "continue" };
}
return this.matchPrefixOtherLocales(pathname, context);
case "pathname-prefix-always-no-redirect":
return this.matchPrefixAlwaysNoRedirect(pathname, context);
case "domains-prefix-always-no-redirect":
if (this.localeHasntDomain(context.currentLocale, context.currentDomain)) {
return { type: "continue" };
}
return this.matchPrefixAlwaysNoRedirect(pathname, context);
default:
return { type: "continue" };
}
}
/**
* Check if i18n processing should be skipped for this request
*/
shouldSkipProcessing(pathname, context) {
if (pathname.includes("/404") || pathname.includes("/500")) {
return true;
}
if (pathname.includes("/_server-islands/")) {
return true;
}
if (context.isReroute) {
return true;
}
if (context.routeType && context.routeType !== "page" && context.routeType !== "fallback") {
return true;
}
return false;
}
/**
* Strategy: pathname-prefix-always
* All locales must have a prefix, including the default locale.
*/
matchPrefixAlways(pathname, _context) {
const isRoot = pathname === this.#base + "/" || pathname === this.#base;
if (isRoot) {
const basePrefix = this.#base === "/" ? "" : this.#base;
return {
type: "redirect",
location: `${basePrefix}/${this.#defaultLocale}`
};
}
if (!pathHasLocale(pathname, this.#locales)) {
return { type: "notFound" };
}
return { type: "continue" };
}
/**
* Strategy: pathname-prefix-other-locales
* Default locale has no prefix, other locales must have a prefix.
*/
matchPrefixOtherLocales(pathname, _context) {
let pathnameContainsDefaultLocale = false;
for (const segment of pathname.split("/")) {
if (normalizeTheLocale(segment) === normalizeTheLocale(this.#defaultLocale)) {
pathnameContainsDefaultLocale = true;
break;
}
}
if (pathnameContainsDefaultLocale) {
const newLocation = pathname.replace(`/${this.#defaultLocale}`, "");
return {
type: "notFound",
location: newLocation
};
}
return { type: "continue" };
}
/**
* Strategy: pathname-prefix-always-no-redirect
* Like prefix-always but allows root to serve instead of redirecting
*/
matchPrefixAlwaysNoRedirect(pathname, _context) {
const isRoot = pathname === this.#base + "/" || pathname === this.#base;
if (isRoot) {
return { type: "continue" };
}
if (!pathHasLocale(pathname, this.#locales)) {
return { type: "notFound" };
}
return { type: "continue" };
}
/**
* Check if the current locale doesn't belong to the configured domain.
* Used for domain-based routing strategies.
*/
localeHasntDomain(currentLocale, currentDomain) {
if (!this.#domains || !currentDomain) {
return false;
}
if (!currentLocale) {
return false;
}
const localesForDomain = this.#domains[currentDomain];
if (!localesForDomain) {
return true;
}
return !localesForDomain.includes(currentLocale);
}
}
export {
I18nRouter
};