diff --git a/src/routes/[lang]/onlineboard/arrival/[params]/page.test.tsx b/src/routes/[lang]/onlineboard/arrival/[params]/page.test.tsx
index 3b28ac0f..60686ffc 100644
--- a/src/routes/[lang]/onlineboard/arrival/[params]/page.test.tsx
+++ b/src/routes/[lang]/onlineboard/arrival/[params]/page.test.tsx
@@ -51,6 +51,10 @@ vi.mock("@/features/online-board/seo.js", () => ({
buildArrivalSearchSeo: () => ({}),
}));
+vi.mock("@/shared/hooks/useDictionaries.js", () => ({
+ useCityName: (code: string) => code,
+}));
+
const mockNavigate = vi.fn();
vi.mock("@modern-js/runtime/router", () => ({
useParams: vi.fn(),
diff --git a/src/routes/[lang]/onlineboard/arrival/[params]/page.tsx b/src/routes/[lang]/onlineboard/arrival/[params]/page.tsx
index 056582aa..3ff4fb1e 100644
--- a/src/routes/[lang]/onlineboard/arrival/[params]/page.tsx
+++ b/src/routes/[lang]/onlineboard/arrival/[params]/page.tsx
@@ -14,6 +14,7 @@ import { SeoHead } from "@/ui/seo/SeoHead.js";
import { FlightListSkeleton } from "@/ui/flights/FlightListSkeleton.js";
import { ErrorPage } from "@/ui/errors/ErrorPage.js";
import { getEnv } from "@/env/index.js";
+import { useCityName } from "@/shared/hooks/useDictionaries.js";
import { boardDateRedirect } from "../../_guards.js";
const OnlineBoardSearchPage = lazy(() =>
@@ -29,6 +30,9 @@ export default function ArrivalSearchPage(): JSX.Element {
const locale = routeParams.lang ?? "ru-ru";
const parsed = parseStationUrlParams(raw);
+ // Resolve IATA → city name for `
`. Hooks must run unconditionally.
+ const stationName = useCityName(parsed?.station ?? "");
+
if (!parsed) return ;
const redirect = boardDateRedirect(locale, parsed.date);
@@ -44,7 +48,9 @@ export default function ArrivalSearchPage(): JSX.Element {
: {}),
};
- const seoProps = buildArrivalSearchSeo(t, searchParams, locale, canonicalOrigin);
+ const seoProps = buildArrivalSearchSeo(t, searchParams, locale, canonicalOrigin, {
+ arrival: stationName,
+ });
return (
<>
diff --git a/src/routes/[lang]/onlineboard/departure/[params]/page.test.tsx b/src/routes/[lang]/onlineboard/departure/[params]/page.test.tsx
index a24d71ef..0ac6337d 100644
--- a/src/routes/[lang]/onlineboard/departure/[params]/page.test.tsx
+++ b/src/routes/[lang]/onlineboard/departure/[params]/page.test.tsx
@@ -51,6 +51,10 @@ vi.mock("@/features/online-board/seo.js", () => ({
buildDepartureSearchSeo: () => ({}),
}));
+vi.mock("@/shared/hooks/useDictionaries.js", () => ({
+ useCityName: (code: string) => code,
+}));
+
const mockNavigate = vi.fn();
vi.mock("@modern-js/runtime/router", () => ({
useParams: vi.fn(),
diff --git a/src/routes/[lang]/onlineboard/departure/[params]/page.tsx b/src/routes/[lang]/onlineboard/departure/[params]/page.tsx
index 34ef604c..44c3b62d 100644
--- a/src/routes/[lang]/onlineboard/departure/[params]/page.tsx
+++ b/src/routes/[lang]/onlineboard/departure/[params]/page.tsx
@@ -14,6 +14,7 @@ import { SeoHead } from "@/ui/seo/SeoHead.js";
import { FlightListSkeleton } from "@/ui/flights/FlightListSkeleton.js";
import { ErrorPage } from "@/ui/errors/ErrorPage.js";
import { getEnv } from "@/env/index.js";
+import { useCityName } from "@/shared/hooks/useDictionaries.js";
import { boardDateRedirect } from "../../_guards.js";
const OnlineBoardSearchPage = lazy(() =>
@@ -29,6 +30,9 @@ export default function DepartureSearchPage(): JSX.Element {
const locale = routeParams.lang ?? "ru-ru";
const parsed = parseStationUrlParams(raw);
+ // Resolve IATA → city name for ``. Hooks must run unconditionally.
+ const stationName = useCityName(parsed?.station ?? "");
+
if (!parsed) return ;
const redirect = boardDateRedirect(locale, parsed.date);
@@ -44,7 +48,9 @@ export default function DepartureSearchPage(): JSX.Element {
: {}),
};
- const seoProps = buildDepartureSearchSeo(t, searchParams, locale, canonicalOrigin);
+ const seoProps = buildDepartureSearchSeo(t, searchParams, locale, canonicalOrigin, {
+ departure: stationName,
+ });
return (
<>
diff --git a/src/routes/[lang]/onlineboard/route/[params]/page.test.tsx b/src/routes/[lang]/onlineboard/route/[params]/page.test.tsx
index 99cd58e6..27bfed6c 100644
--- a/src/routes/[lang]/onlineboard/route/[params]/page.test.tsx
+++ b/src/routes/[lang]/onlineboard/route/[params]/page.test.tsx
@@ -51,6 +51,10 @@ vi.mock("@/features/online-board/seo.js", () => ({
buildRouteSearchSeo: () => ({}),
}));
+vi.mock("@/shared/hooks/useDictionaries.js", () => ({
+ useCityName: (code: string) => code,
+}));
+
const mockNavigate = vi.fn();
vi.mock("@modern-js/runtime/router", () => ({
useParams: vi.fn(),
diff --git a/src/routes/[lang]/onlineboard/route/[params]/page.tsx b/src/routes/[lang]/onlineboard/route/[params]/page.tsx
index 995064c4..b28331f4 100644
--- a/src/routes/[lang]/onlineboard/route/[params]/page.tsx
+++ b/src/routes/[lang]/onlineboard/route/[params]/page.tsx
@@ -14,6 +14,7 @@ import { SeoHead } from "@/ui/seo/SeoHead.js";
import { FlightListSkeleton } from "@/ui/flights/FlightListSkeleton.js";
import { ErrorPage } from "@/ui/errors/ErrorPage.js";
import { getEnv } from "@/env/index.js";
+import { useCityName } from "@/shared/hooks/useDictionaries.js";
import { boardDateRedirect } from "../../_guards.js";
const OnlineBoardSearchPage = lazy(() =>
@@ -29,6 +30,10 @@ export default function RouteSearchPage(): JSX.Element {
const locale = routeParams.lang ?? "ru-ru";
const parsed = parseRouteUrlParams(raw);
+ // Resolve IATA → city names for ``. Hooks must run unconditionally.
+ const depName = useCityName(parsed?.departure ?? "");
+ const arrName = useCityName(parsed?.arrival ?? "");
+
if (!parsed) return ;
const redirect = boardDateRedirect(locale, parsed.date);
@@ -45,7 +50,10 @@ export default function RouteSearchPage(): JSX.Element {
: {}),
};
- const seoProps = buildRouteSearchSeo(t, searchParams, locale, canonicalOrigin);
+ const seoProps = buildRouteSearchSeo(t, searchParams, locale, canonicalOrigin, {
+ departure: depName,
+ arrival: arrName,
+ });
return (
<>
diff --git a/src/routes/[lang]/schedule/route/[params]/[returnParams]/page.test.tsx b/src/routes/[lang]/schedule/route/[params]/[returnParams]/page.test.tsx
index d5d73651..365fbaeb 100644
--- a/src/routes/[lang]/schedule/route/[params]/[returnParams]/page.test.tsx
+++ b/src/routes/[lang]/schedule/route/[params]/[returnParams]/page.test.tsx
@@ -52,6 +52,10 @@ vi.mock("@/features/schedule/seo.js", () => ({
buildScheduleSearchSeo: () => ({}),
}));
+vi.mock("@/shared/hooks/useDictionaries.js", () => ({
+ useCityName: (code: string) => code,
+}));
+
const mockNavigate = vi.fn();
vi.mock("@modern-js/runtime/router", () => ({
useParams: vi.fn(),
diff --git a/src/routes/[lang]/schedule/route/[params]/[returnParams]/page.tsx b/src/routes/[lang]/schedule/route/[params]/[returnParams]/page.tsx
index c33a3f10..f69ebb3b 100644
--- a/src/routes/[lang]/schedule/route/[params]/[returnParams]/page.tsx
+++ b/src/routes/[lang]/schedule/route/[params]/[returnParams]/page.tsx
@@ -14,6 +14,7 @@ import { SeoHead } from "@/ui/seo/SeoHead.js";
import { FlightListSkeleton } from "@/ui/flights/FlightListSkeleton.js";
import { ErrorPage } from "@/ui/errors/ErrorPage.js";
import { getEnv } from "@/env/index.js";
+import { useCityName } from "@/shared/hooks/useDictionaries.js";
import { scheduleDateRedirect } from "../../../_guards.js";
const ScheduleSearchPage = lazy(() =>
@@ -32,6 +33,12 @@ export default function ScheduleRoundTripSearchPage(): JSX.Element {
const outbound = parseScheduleRouteParams(outboundRaw);
const inbound = parseScheduleRouteParams(inboundRaw);
+ // Resolve IATA → city names for ``. Hooks must run unconditionally,
+ // so call with the outbound codes (which double as the SEO heading codes)
+ // even when parsing failed.
+ const depName = useCityName(outbound?.departure ?? "");
+ const arrName = useCityName(outbound?.arrival ?? "");
+
if (!outbound || !inbound) return ;
const redirect =
@@ -41,7 +48,10 @@ export default function ScheduleRoundTripSearchPage(): JSX.Element {
const canonicalOrigin = getEnv().PROD_ORIGIN;
const scheduleParams = { type: "roundtrip" as const, outbound, inbound };
- const seoProps = buildScheduleSearchSeo(t, scheduleParams, locale, canonicalOrigin);
+ const seoProps = buildScheduleSearchSeo(t, scheduleParams, locale, canonicalOrigin, {
+ departure: depName,
+ arrival: arrName,
+ });
return (
<>
diff --git a/src/routes/[lang]/schedule/route/[params]/page.test.tsx b/src/routes/[lang]/schedule/route/[params]/page.test.tsx
index c4e6b755..5b6cfdbd 100644
--- a/src/routes/[lang]/schedule/route/[params]/page.test.tsx
+++ b/src/routes/[lang]/schedule/route/[params]/page.test.tsx
@@ -51,6 +51,10 @@ vi.mock("@/features/schedule/seo.js", () => ({
buildScheduleSearchSeo: () => ({}),
}));
+vi.mock("@/shared/hooks/useDictionaries.js", () => ({
+ useCityName: (code: string) => code,
+}));
+
const mockNavigate = vi.fn();
vi.mock("@modern-js/runtime/router", () => ({
useParams: vi.fn(),
diff --git a/src/routes/[lang]/schedule/route/[params]/page.tsx b/src/routes/[lang]/schedule/route/[params]/page.tsx
index d505acd2..6a9ac6b5 100644
--- a/src/routes/[lang]/schedule/route/[params]/page.tsx
+++ b/src/routes/[lang]/schedule/route/[params]/page.tsx
@@ -14,6 +14,7 @@ import { SeoHead } from "@/ui/seo/SeoHead.js";
import { FlightListSkeleton } from "@/ui/flights/FlightListSkeleton.js";
import { ErrorPage } from "@/ui/errors/ErrorPage.js";
import { getEnv } from "@/env/index.js";
+import { useCityName } from "@/shared/hooks/useDictionaries.js";
import { scheduleDateRedirect } from "../../_guards.js";
const ScheduleSearchPage = lazy(() =>
@@ -29,6 +30,12 @@ export default function ScheduleRouteSearchPage(): JSX.Element {
const locale = routeParams.lang ?? "ru-ru";
const parsed = parseScheduleRouteParams(raw);
+ // Resolve IATA → city names so `` reads
+ // "Расписание по маршруту: Москва-Мурманск" instead of "MOW-MMK".
+ // Hooks must run unconditionally; pass empty codes if parsing failed.
+ const depName = useCityName(parsed?.departure ?? "");
+ const arrName = useCityName(parsed?.arrival ?? "");
+
if (!parsed) return ;
const redirect = scheduleDateRedirect(locale, parsed.dateFrom);
@@ -36,7 +43,10 @@ export default function ScheduleRouteSearchPage(): JSX.Element {
const canonicalOrigin = getEnv().PROD_ORIGIN;
const scheduleParams = { type: "route" as const, outbound: parsed };
- const seoProps = buildScheduleSearchSeo(t, scheduleParams, locale, canonicalOrigin);
+ const seoProps = buildScheduleSearchSeo(t, scheduleParams, locale, canonicalOrigin, {
+ departure: depName,
+ arrival: arrName,
+ });
return (
<>