Add locale resolver with Language type and URL prefix parsing
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
LANGUAGES,
|
||||
isLanguage,
|
||||
resolveLocaleFromPath,
|
||||
stripLocaleFromPath,
|
||||
} from "./resolver.js";
|
||||
|
||||
describe("LANGUAGES", () => {
|
||||
it("contains exactly 9 supported languages", () => {
|
||||
expect(LANGUAGES).toHaveLength(9);
|
||||
expect(LANGUAGES).toContain("ru");
|
||||
expect(LANGUAGES).toContain("en");
|
||||
expect(LANGUAGES).toContain("de");
|
||||
});
|
||||
});
|
||||
|
||||
describe("isLanguage", () => {
|
||||
it("returns true for valid languages", () => {
|
||||
expect(isLanguage("ru")).toBe(true);
|
||||
expect(isLanguage("en")).toBe(true);
|
||||
expect(isLanguage("zh")).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false for invalid strings", () => {
|
||||
expect(isLanguage("xx")).toBe(false);
|
||||
expect(isLanguage("")).toBe(false);
|
||||
expect(isLanguage("RU")).toBe(false);
|
||||
expect(isLanguage("russian")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveLocaleFromPath", () => {
|
||||
it("extracts locale from the first path segment", () => {
|
||||
expect(resolveLocaleFromPath("/ru/onlineboard")).toBe("ru");
|
||||
expect(resolveLocaleFromPath("/en/onlineboard/flight/SU100")).toBe("en");
|
||||
expect(resolveLocaleFromPath("/de/schedule")).toBe("de");
|
||||
});
|
||||
|
||||
it("returns null for paths without a valid locale prefix", () => {
|
||||
expect(resolveLocaleFromPath("/onlineboard")).toBeNull();
|
||||
expect(resolveLocaleFromPath("/xx/onlineboard")).toBeNull();
|
||||
expect(resolveLocaleFromPath("/")).toBeNull();
|
||||
expect(resolveLocaleFromPath("")).toBeNull();
|
||||
});
|
||||
|
||||
it("handles bare locale path (e.g., /ru)", () => {
|
||||
expect(resolveLocaleFromPath("/ru")).toBe("ru");
|
||||
expect(resolveLocaleFromPath("/ru/")).toBe("ru");
|
||||
});
|
||||
});
|
||||
|
||||
describe("stripLocaleFromPath", () => {
|
||||
it("strips locale and returns the rest", () => {
|
||||
expect(stripLocaleFromPath("/ru/onlineboard")).toEqual({
|
||||
locale: "ru",
|
||||
rest: "/onlineboard",
|
||||
});
|
||||
expect(stripLocaleFromPath("/en/onlineboard/flight/SU100")).toEqual({
|
||||
locale: "en",
|
||||
rest: "/onlineboard/flight/SU100",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns / as rest for bare locale path", () => {
|
||||
expect(stripLocaleFromPath("/ru")).toEqual({ locale: "ru", rest: "/" });
|
||||
expect(stripLocaleFromPath("/ru/")).toEqual({ locale: "ru", rest: "/" });
|
||||
});
|
||||
|
||||
it("returns null for paths without a valid locale prefix", () => {
|
||||
expect(stripLocaleFromPath("/onlineboard")).toBeNull();
|
||||
expect(stripLocaleFromPath("/xx/schedule")).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
export type Language = "ru" | "en" | "es" | "fr" | "it" | "ja" | "ko" | "zh" | "de";
|
||||
|
||||
export const LANGUAGES: readonly Language[] = [
|
||||
"ru", "en", "es", "fr", "it", "ja", "ko", "zh", "de",
|
||||
] as const;
|
||||
|
||||
const languageSet: ReadonlySet<string> = new Set(LANGUAGES);
|
||||
|
||||
export function isLanguage(x: string): x is Language {
|
||||
return languageSet.has(x);
|
||||
}
|
||||
|
||||
export function resolveLocaleFromPath(pathname: string): Language | null {
|
||||
const segments = pathname.split("/").filter(Boolean);
|
||||
const first = segments[0];
|
||||
if (first !== undefined && isLanguage(first)) {
|
||||
return first;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function stripLocaleFromPath(
|
||||
pathname: string,
|
||||
): { locale: Language; rest: string } | null {
|
||||
const locale = resolveLocaleFromPath(pathname);
|
||||
if (locale === null) return null;
|
||||
|
||||
const rest = pathname.slice(`/${locale}`.length);
|
||||
return {
|
||||
locale,
|
||||
rest: rest === "" || rest === "/" ? "/" : rest,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user