plan/react-rewrite #1
@@ -0,0 +1,71 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildHreflangSet } from "./hreflang.js";
|
||||
|
||||
describe("buildHreflangSet", () => {
|
||||
const LANGUAGES = ["ru", "en", "es", "fr", "it", "ja", "ko", "zh", "de"] as const;
|
||||
|
||||
it("returns entries for all 9 languages plus x-default", () => {
|
||||
const result = buildHreflangSet({
|
||||
canonicalOrigin: "https://www.aeroflot.ru",
|
||||
pathWithoutLocale: "/onlineboard/flight/SU100-2025-01-15",
|
||||
});
|
||||
|
||||
expect(result).toHaveLength(10); // 9 languages + x-default
|
||||
});
|
||||
|
||||
it("includes all 9 languages", () => {
|
||||
const result = buildHreflangSet({
|
||||
canonicalOrigin: "https://www.aeroflot.ru",
|
||||
pathWithoutLocale: "/smoke",
|
||||
});
|
||||
|
||||
const langs = result.map((entry) => entry.lang);
|
||||
for (const lang of LANGUAGES) {
|
||||
expect(langs).toContain(lang);
|
||||
}
|
||||
});
|
||||
|
||||
it("x-default points to the ru variant", () => {
|
||||
const result = buildHreflangSet({
|
||||
canonicalOrigin: "https://www.aeroflot.ru",
|
||||
pathWithoutLocale: "/smoke",
|
||||
});
|
||||
|
||||
const xDefault = result.find((entry) => entry.lang === "x-default");
|
||||
expect(xDefault).toBeDefined();
|
||||
expect(xDefault?.href).toBe("https://www.aeroflot.ru/ru/smoke");
|
||||
});
|
||||
|
||||
it("builds correct href for each language", () => {
|
||||
const result = buildHreflangSet({
|
||||
canonicalOrigin: "https://www.aeroflot.ru",
|
||||
pathWithoutLocale: "/onlineboard",
|
||||
});
|
||||
|
||||
const en = result.find((entry) => entry.lang === "en");
|
||||
expect(en?.href).toBe("https://www.aeroflot.ru/en/onlineboard");
|
||||
|
||||
const ja = result.find((entry) => entry.lang === "ja");
|
||||
expect(ja?.href).toBe("https://www.aeroflot.ru/ja/onlineboard");
|
||||
});
|
||||
|
||||
it("preserves paths with nested segments", () => {
|
||||
const result = buildHreflangSet({
|
||||
canonicalOrigin: "https://www.aeroflot.ru",
|
||||
pathWithoutLocale: "/onlineboard/flight/SU100-2025-01-15",
|
||||
});
|
||||
|
||||
const fr = result.find((entry) => entry.lang === "fr");
|
||||
expect(fr?.href).toBe("https://www.aeroflot.ru/fr/onlineboard/flight/SU100-2025-01-15");
|
||||
});
|
||||
|
||||
it("handles root path", () => {
|
||||
const result = buildHreflangSet({
|
||||
canonicalOrigin: "https://www.aeroflot.ru",
|
||||
pathWithoutLocale: "",
|
||||
});
|
||||
|
||||
const ru = result.find((entry) => entry.lang === "ru");
|
||||
expect(ru?.href).toBe("https://www.aeroflot.ru/ru");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
import type { Language } from "@/i18n/resolver";
|
||||
|
||||
const LANGUAGES: readonly Language[] = ["ru", "en", "es", "fr", "it", "ja", "ko", "zh", "de"];
|
||||
const X_DEFAULT_LANGUAGE: Language = "ru";
|
||||
|
||||
export interface HreflangEntry {
|
||||
lang: Language | "x-default";
|
||||
href: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the full set of reciprocal hreflang links for a given path.
|
||||
* Returns 9 language entries + 1 x-default entry (pointing to ru).
|
||||
*/
|
||||
export function buildHreflangSet(args: {
|
||||
canonicalOrigin: string;
|
||||
pathWithoutLocale: string;
|
||||
}): HreflangEntry[] {
|
||||
const { canonicalOrigin, pathWithoutLocale } = args;
|
||||
|
||||
const entries: HreflangEntry[] = LANGUAGES.map((lang) => ({
|
||||
lang,
|
||||
href: `${canonicalOrigin}/${lang}${pathWithoutLocale}`,
|
||||
}));
|
||||
|
||||
entries.push({
|
||||
lang: "x-default",
|
||||
href: `${canonicalOrigin}/${X_DEFAULT_LANGUAGE}${pathWithoutLocale}`,
|
||||
});
|
||||
|
||||
return entries;
|
||||
}
|
||||
Reference in New Issue
Block a user