144 lines
4.5 KiB
JavaScript
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
|
|
};
|