diff --git a/src/features/online-board/seo.test.ts b/src/features/online-board/seo.test.ts index 32fe3ad7..0e65b11b 100644 --- a/src/features/online-board/seo.test.ts +++ b/src/features/online-board/seo.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it, vi, afterEach } from "vitest"; import { buildOnlineBoardStartSeo, buildFlightSearchSeo, @@ -6,6 +6,7 @@ import { buildArrivalSearchSeo, buildRouteSearchSeo, buildFlightDetailsSeo, + buildFlightDetailsSeoFromId, } from "./seo.js"; import type { ISimpleFlight } from "./types.js"; @@ -20,8 +21,92 @@ function stubT(key: string, opts?: Record): string { return key; } +/** + * Stub t() that resolves SHARED.TODAY / SHARED.TOMORROW to their Russian + * equivalents so we can assert the rendered value directly. + */ +function stubTRu(key: string, opts?: Record): string { + if (key === "SHARED.TODAY") return "сегодня"; + if (key === "SHARED.TOMORROW") return "завтра"; + return stubT(key, opts); +} + const CANONICAL = "https://www.aeroflot.ru"; +// --------------------------------------------------------------------------- +// formatDateForTitle — via buildFlightSearchSeo with frozen clock +// --------------------------------------------------------------------------- + +describe("formatDateForTitle date branches", () => { + afterEach(() => { + vi.useRealTimers(); + }); + + it("returns 'сегодня' when date matches today (local)", () => { + // Freeze clock to 2026-05-15T12:00:00 local + vi.useFakeTimers({ now: new Date(2026, 4, 15, 12, 0, 0) }); + + const params = { + type: "flight" as const, + carrier: "SU", + flightNumber: "0100", + date: "20260515", + }; + const result = buildFlightSearchSeo(stubTRu, params, "ru", CANONICAL); + + expect(result.title).toContain("date=сегодня"); + // description must use dd.MM.yyyy, not "сегодня" + expect(result.description).toContain("date=15.05.2026"); + }); + + it("returns 'завтра' when date is today + 1", () => { + vi.useFakeTimers({ now: new Date(2026, 4, 15, 12, 0, 0) }); + + const params = { + type: "flight" as const, + carrier: "SU", + flightNumber: "0100", + date: "20260516", + }; + const result = buildFlightSearchSeo(stubTRu, params, "ru", CANONICAL); + + expect(result.title).toContain("date=завтра"); + expect(result.description).toContain("date=16.05.2026"); + }); + + it("returns dd.MM.yyyy for an arbitrary past date", () => { + vi.useFakeTimers({ now: new Date(2026, 4, 15, 12, 0, 0) }); + + const params = { + type: "flight" as const, + carrier: "SU", + flightNumber: "0100", + date: "20250115", + }; + const result = buildFlightSearchSeo(stubTRu, params, "ru", CANONICAL); + + expect(result.title).toContain("date=15.01.2025"); + }); + + it("accepts yyyy-MM-dd API shape for today", () => { + vi.useFakeTimers({ now: new Date(2026, 4, 15, 12, 0, 0) }); + + const params = { + type: "departure" as const, + station: "SVO", + date: "2026-05-15", + }; + const result = buildDepartureSearchSeo(stubTRu, params, "ru", CANONICAL); + + expect(result.title).toContain("date=сегодня"); + expect(result.description).toContain("date=15.05.2026"); + }); +}); + +// --------------------------------------------------------------------------- +// buildOnlineBoardStartSeo +// --------------------------------------------------------------------------- + describe("buildOnlineBoardStartSeo", () => { it("uses MAIN translation keys for title and description", () => { const result = buildOnlineBoardStartSeo(stubT, "ru", CANONICAL); @@ -62,6 +147,10 @@ describe("buildOnlineBoardStartSeo", () => { }); }); +// --------------------------------------------------------------------------- +// buildFlightSearchSeo +// --------------------------------------------------------------------------- + describe("buildFlightSearchSeo", () => { const params = { type: "flight" as const, @@ -78,6 +167,14 @@ describe("buildFlightSearchSeo", () => { expect(result.description).toContain("SEO.BOARD.FLIGHT-SEARCH.DESCRIPTION"); }); + it("title date uses formatDateForTitle; description uses dd.MM.yyyy", () => { + // 20250115 is not today/tomorrow, so title date = "15.01.2025" + const result = buildFlightSearchSeo(stubT, params, "ru", CANONICAL); + + expect(result.title).toContain("date=15.01.2025"); + expect(result.description).toContain("date=15.01.2025"); + }); + it("sets canonical to the flight search URL", () => { const result = buildFlightSearchSeo(stubT, params, "ru", CANONICAL); @@ -93,6 +190,10 @@ describe("buildFlightSearchSeo", () => { }); }); +// --------------------------------------------------------------------------- +// buildDepartureSearchSeo +// --------------------------------------------------------------------------- + describe("buildDepartureSearchSeo", () => { const params = { type: "departure" as const, @@ -119,6 +220,13 @@ describe("buildDepartureSearchSeo", () => { expect(result.title).toContain("departureCity=SVO"); }); + it("title date uses formatDateForTitle; description keeps dd.MM.yyyy", () => { + const result = buildDepartureSearchSeo(stubT, params, "ru", CANONICAL); + + expect(result.title).toContain("date=15.01.2025"); + expect(result.description).toContain("date=15.01.2025"); + }); + it("sets canonical to the departure search URL", () => { const result = buildDepartureSearchSeo(stubT, params, "en", CANONICAL); @@ -128,6 +236,10 @@ describe("buildDepartureSearchSeo", () => { }); }); +// --------------------------------------------------------------------------- +// buildArrivalSearchSeo +// --------------------------------------------------------------------------- + describe("buildArrivalSearchSeo", () => { const params = { type: "arrival" as const, @@ -154,6 +266,13 @@ describe("buildArrivalSearchSeo", () => { expect(result.title).toContain("arrivalCity=JFK"); }); + it("title date uses formatDateForTitle; description keeps dd.MM.yyyy", () => { + const result = buildArrivalSearchSeo(stubT, params, "ru", CANONICAL); + + expect(result.title).toContain("date=15.01.2025"); + expect(result.description).toContain("date=15.01.2025"); + }); + it("sets canonical to the arrival search URL", () => { const result = buildArrivalSearchSeo(stubT, params, "ru", CANONICAL); @@ -163,6 +282,10 @@ describe("buildArrivalSearchSeo", () => { }); }); +// --------------------------------------------------------------------------- +// buildRouteSearchSeo +// --------------------------------------------------------------------------- + describe("buildRouteSearchSeo", () => { const params = { type: "route" as const, @@ -192,6 +315,13 @@ describe("buildRouteSearchSeo", () => { expect(result.title).toContain("arrivalCity=JFK"); }); + it("title date uses formatDateForTitle; description keeps dd.MM.yyyy", () => { + const result = buildRouteSearchSeo(stubT, params, "ru", CANONICAL); + + expect(result.title).toContain("date=15.01.2025"); + expect(result.description).toContain("date=15.01.2025"); + }); + it("sets canonical to the route search URL", () => { const result = buildRouteSearchSeo(stubT, params, "en", CANONICAL); @@ -201,6 +331,10 @@ describe("buildRouteSearchSeo", () => { }); }); +// --------------------------------------------------------------------------- +// buildFlightDetailsSeo +// --------------------------------------------------------------------------- + describe("buildFlightDetailsSeo", () => { const flight: ISimpleFlight = { id: "SU100-20250115", @@ -263,11 +397,48 @@ describe("buildFlightDetailsSeo", () => { }, }; - it("uses FLIGHT-DETAILS translation keys", () => { + it("uses FLIGHT-DETAILS.TITLE with flightNumber and routeCities (no date)", () => { const result = buildFlightDetailsSeo(stubT, flight, "ru", CANONICAL); expect(result.title).toContain("SEO.BOARD.FLIGHT-DETAILS.TITLE"); expect(result.title).toContain("flightNumber=SU 0100"); + expect(result.title).toContain("routeCities=Moscow-New York"); + // No date interpolation in title + expect(result.title).not.toContain("date="); + }); + + it("description keeps dd.MM.yyyy date", () => { + const result = buildFlightDetailsSeo(stubT, flight, "ru", CANONICAL); + + expect(result.description).toContain("SEO.BOARD.FLIGHT-DETAILS.DESCRIPTION"); + expect(result.description).toContain("flightNumber=SU 0100"); + expect(result.description).toContain("date=15.01.2025"); + }); + + it("uses TITLE-NO-ROUTE when cityNames not available (from id only)", () => { + const result = buildFlightDetailsSeoFromId( + stubT, + { carrier: "SU", flightNumber: "0100", date: "20250115" }, + "ru", + CANONICAL, + // no cityNames provided + ); + + expect(result.title).toContain("SEO.BOARD.FLIGHT-DETAILS.TITLE-NO-ROUTE"); + expect(result.title).toContain("flightNumber=SU 0100"); + expect(result.title).not.toContain("routeCities="); + }); + + it("uses explicit cityNames over leg cities when both present", () => { + const result = buildFlightDetailsSeo( + stubT, + flight, + "ru", + CANONICAL, + { departure: "Москва", arrival: "Нью-Йорк" }, + ); + + expect(result.title).toContain("routeCities=Москва-Нью-Йорк"); }); it("sets canonical to the details URL", () => { diff --git a/src/features/online-board/seo.ts b/src/features/online-board/seo.ts index 2bcc7348..e8a62bcc 100644 --- a/src/features/online-board/seo.ts +++ b/src/features/online-board/seo.ts @@ -47,6 +47,7 @@ const SITE_NAME = "Aeroflot"; /** * Format a date string to dd.MM.yyyy for display in SEO strings. * Accepts both 'yyyyMMdd' (URL param shape) and 'yyyy-MM-dd' (API response). + * Used for descriptions only — titles use formatDateForTitle. */ function formatDateForSeo(input: string): string { const digits = input.replace(/-/g, ""); @@ -57,6 +58,33 @@ function formatDateForSeo(input: string): string { return `${day}.${month}.${year}`; } +/** + * Format a date for title display per TZ §4.1.3 Table 6: + * - t("SHARED.TODAY") if date is today (local) + * - t("SHARED.TOMORROW") if date is today + 1 day (local) + * - "dd.MM.yyyy" otherwise + * + * Input: "yyyyMMdd" (URL shape) or "yyyy-MM-dd" (API shape). + */ +function formatDateForTitle(input: string, t: TFunction): string { + const digits = input.replace(/-/g, ""); + if (digits.length !== 8) return input; + + const y = Number(digits.slice(0, 4)); + const m = Number(digits.slice(4, 6)) - 1; + const d = Number(digits.slice(6, 8)); + const inputDate = new Date(y, m, d); + inputDate.setHours(0, 0, 0, 0); + + const today = new Date(); + today.setHours(0, 0, 0, 0); + + const deltaDays = Math.round((inputDate.getTime() - today.getTime()) / 86_400_000); + if (deltaDays === 0) return t("SHARED.TODAY"); + if (deltaDays === 1) return t("SHARED.TOMORROW"); + return `${digits.slice(6, 8)}.${digits.slice(4, 6)}.${digits.slice(0, 4)}`; +} + /** * Build the canonical URL for a given set of params + locale. */ @@ -153,15 +181,16 @@ export function buildFlightSearchSeo( canonicalOrigin: string, ): SeoHeadProps { const flightDisplay = `${params.carrier} ${params.flightNumber}${params.suffix ?? ""}`; - const dateDisplay = formatDateForSeo(params.date); + const dateForTitle = formatDateForTitle(params.date, t); + const dateForDesc = formatDateForSeo(params.date); const title = t("SEO.BOARD.FLIGHT-SEARCH.TITLE", { flightNumber: flightDisplay, - date: dateDisplay, + date: dateForTitle, }); const description = t("SEO.BOARD.FLIGHT-SEARCH.DESCRIPTION", { flightNumber: flightDisplay, - date: dateDisplay, + date: dateForDesc, }); const canonical = buildCanonical(canonicalOrigin, locale, params); const hreflangPath = buildPathWithoutLocale(params); @@ -193,15 +222,16 @@ export function buildDepartureSearchSeo( cityNames?: CityNames, ): SeoHeadProps { const departureCity = cityNames?.departure ?? params.station; - const dateDisplay = formatDateForSeo(params.date); + const dateForTitle = formatDateForTitle(params.date, t); + const dateForDesc = formatDateForSeo(params.date); const title = t("SEO.BOARD.DEPARTURE-SEARCH.TITLE", { departureCity, - date: dateDisplay, + date: dateForTitle, }); const description = t("SEO.BOARD.DEPARTURE-SEARCH.DESCRIPTION", { departureCity, - date: dateDisplay, + date: dateForDesc, }); const canonical = buildCanonical(canonicalOrigin, locale, params); const hreflangPath = buildPathWithoutLocale(params); @@ -233,15 +263,16 @@ export function buildArrivalSearchSeo( cityNames?: CityNames, ): SeoHeadProps { const arrivalCity = cityNames?.arrival ?? params.station; - const dateDisplay = formatDateForSeo(params.date); + const dateForTitle = formatDateForTitle(params.date, t); + const dateForDesc = formatDateForSeo(params.date); const title = t("SEO.BOARD.ARRIVAL-SEARCH.TITLE", { arrivalCity, - date: dateDisplay, + date: dateForTitle, }); const description = t("SEO.BOARD.ARRIVAL-SEARCH.DESCRIPTION", { arrivalCity, - date: dateDisplay, + date: dateForDesc, }); const canonical = buildCanonical(canonicalOrigin, locale, params); const hreflangPath = buildPathWithoutLocale(params); @@ -274,17 +305,18 @@ export function buildRouteSearchSeo( ): SeoHeadProps { const departureCity = cityNames?.departure ?? params.departure; const arrivalCity = cityNames?.arrival ?? params.arrival; - const dateDisplay = formatDateForSeo(params.date); + const dateForTitle = formatDateForTitle(params.date, t); + const dateForDesc = formatDateForSeo(params.date); const title = t("SEO.BOARD.ROUTE-SEARCH.TITLE", { departureCity, arrivalCity, - date: dateDisplay, + date: dateForTitle, }); const description = t("SEO.BOARD.ROUTE-SEARCH.DESCRIPTION", { departureCity, arrivalCity, - date: dateDisplay, + date: dateForDesc, }); const canonical = buildCanonical(canonicalOrigin, locale, params); const hreflangPath = buildPathWithoutLocale(params); @@ -310,37 +342,64 @@ export function buildRouteSearchSeo( * Accepts a plain object so the route page can render SeoHead * synchronously without waiting for the flight data to load — SSR * needs the title + meta in the of the first response. + * + * @param cityNames - Optional departure/arrival city names. When available + * (e.g. after flight data has loaded client-side), supply these so the + * title includes the route cities per TZ Table 6 rows 6-8. When not + * available at SSR time the title will omit the route segment. */ export function buildFlightDetailsSeoFromId( t: TFunction, flightId: { carrier: string; flightNumber: string; suffix?: string; date: string }, locale: string, canonicalOrigin: string, + cityNames?: CityNames, ): SeoHeadProps { const flight: ISimpleFlight = { flightId } as unknown as ISimpleFlight; - return buildFlightDetailsSeo(t, flight, locale, canonicalOrigin); + return buildFlightDetailsSeo(t, flight, locale, canonicalOrigin, cityNames); } /** * SEO props for flight details page. + * Title per TZ Table 6 rows 6-8: "Информация о рейсе: {flightNumber}, {fromCity}-{toCity}" + * No date in the title. Description keeps the full dd.MM.yyyy date. */ export function buildFlightDetailsSeo( t: TFunction, flight: ISimpleFlight, locale: string, canonicalOrigin: string, + cityNames?: CityNames, ): SeoHeadProps { const { carrier, flightNumber, suffix, date } = flight.flightId; const flightDisplay = `${carrier} ${flightNumber}${suffix ?? ""}`; - const dateDisplay = formatDateForSeo(date); + const dateForDesc = formatDateForSeo(date); + + // Derive route cities: prefer explicit cityNames, then flight leg data. + let departureCity = cityNames?.departure; + let arrivalCity = cityNames?.arrival; + if (!departureCity || !arrivalCity) { + if (flight.routeType === "Direct") { + departureCity ??= flight.leg.departure.scheduled.city; + arrivalCity ??= flight.leg.arrival.scheduled.city; + } else if (flight.routeType === "MultiLeg" && flight.legs.length > 0) { + const firstLeg = flight.legs[0]; + const lastLeg = flight.legs[flight.legs.length - 1]; + if (firstLeg) departureCity ??= firstLeg.departure.scheduled.city; + if (lastLeg) arrivalCity ??= lastLeg.arrival.scheduled.city; + } + } + + const routeCities = + departureCity && arrivalCity ? `${departureCity}-${arrivalCity}` : undefined; + + const title = routeCities + ? t("SEO.BOARD.FLIGHT-DETAILS.TITLE", { flightNumber: flightDisplay, routeCities }) + : t("SEO.BOARD.FLIGHT-DETAILS.TITLE-NO-ROUTE", { flightNumber: flightDisplay }); - const title = t("SEO.BOARD.FLIGHT-DETAILS.TITLE", { - flightNumber: flightDisplay, - date: dateDisplay, - }); const description = t("SEO.BOARD.FLIGHT-DETAILS.DESCRIPTION", { flightNumber: flightDisplay, - date: dateDisplay, + date: dateForDesc, }); const detailsParams: OnlineBoardParams = suffix diff --git a/src/i18n/locales/de/common.json b/src/i18n/locales/de/common.json index ed74b8bf..634b9860 100644 --- a/src/i18n/locales/de/common.json +++ b/src/i18n/locales/de/common.json @@ -201,7 +201,8 @@ }, "FLIGHT-DETAILS": { "DESCRIPTION": "", - "TITLE": "" + "TITLE": "", + "TITLE-NO-ROUTE": "" }, "FLIGHT-SEARCH": { "DESCRIPTION": "", diff --git a/src/i18n/locales/en/common.json b/src/i18n/locales/en/common.json index 59bb58db..025e7375 100644 --- a/src/i18n/locales/en/common.json +++ b/src/i18n/locales/en/common.json @@ -226,27 +226,28 @@ "BOARD": { "ARRIVAL-SEARCH": { "DESCRIPTION": "Up-to-date list of Aeroflot flights arriving on {date}. Online arrivals board for {arrivalCity}.", - "TITLE": "Online arrivals board for {arrivalCity} | Aeroflot flights arriving {date}" + "TITLE": "Arrivals: {arrivalCity}, {date}" }, "DEPARTURE-SEARCH": { "DESCRIPTION": "Up-to-date list of Aeroflot flights departing on {date}. Online departures board for {departureCity}.", - "TITLE": "Online departures board for {departureCity} | Aeroflot flights departing {date}" + "TITLE": "Departures: {departureCity}, {date}" }, "FLIGHT-DETAILS": { "DESCRIPTION": "Real-time departure and arrival information for flight {flightNumber}. Departure time, arrival time, and current flight status on the official Aeroflot website.", - "TITLE": "Flight status {flightNumber} {date} | Aeroflot" + "TITLE": "Flight info: {flightNumber}, {routeCities}", + "TITLE-NO-ROUTE": "Flight info: {flightNumber}" }, "FLIGHT-SEARCH": { "DESCRIPTION": "Departure and arrival information for flight {flightNumber} on {date}.", - "TITLE": "Flight {flightNumber} – Online arrivals and departures board {date} | Aeroflot" + "TITLE": "Flight: {flightNumber}, {date}" }, "MAIN": { "DESCRIPTION": "Arrivals and departures board for Aeroflot airline. Real-time flight arrival and departure information.", - "TITLE": "Online departures and arrivals board for Aeroflot flights | Aeroflot" + "TITLE": "Online Board" }, "ROUTE-SEARCH": { "DESCRIPTION": "Arrivals and departures board for Aeroflot flights on the {departureCity} - {arrivalCity} route. Real-time flight information for {date}.", - "TITLE": "Arrivals and departures {departureCity} - {arrivalCity} {date} | Aeroflot" + "TITLE": "Route: {departureCity}-{arrivalCity}, {date}" } }, "SCHEDULE": { diff --git a/src/i18n/locales/es/common.json b/src/i18n/locales/es/common.json index cefd228f..0583ca34 100644 --- a/src/i18n/locales/es/common.json +++ b/src/i18n/locales/es/common.json @@ -201,7 +201,8 @@ }, "FLIGHT-DETAILS": { "DESCRIPTION": "", - "TITLE": "" + "TITLE": "", + "TITLE-NO-ROUTE": "" }, "FLIGHT-SEARCH": { "DESCRIPTION": "", diff --git a/src/i18n/locales/fr/common.json b/src/i18n/locales/fr/common.json index 6826aaf6..0501462e 100644 --- a/src/i18n/locales/fr/common.json +++ b/src/i18n/locales/fr/common.json @@ -201,7 +201,8 @@ }, "FLIGHT-DETAILS": { "DESCRIPTION": "", - "TITLE": "" + "TITLE": "", + "TITLE-NO-ROUTE": "" }, "FLIGHT-SEARCH": { "DESCRIPTION": "", diff --git a/src/i18n/locales/it/common.json b/src/i18n/locales/it/common.json index 2f9ed013..ae3b89f2 100644 --- a/src/i18n/locales/it/common.json +++ b/src/i18n/locales/it/common.json @@ -201,7 +201,8 @@ }, "FLIGHT-DETAILS": { "DESCRIPTION": "", - "TITLE": "" + "TITLE": "", + "TITLE-NO-ROUTE": "" }, "FLIGHT-SEARCH": { "DESCRIPTION": "", diff --git a/src/i18n/locales/ja/common.json b/src/i18n/locales/ja/common.json index 2c0600a8..ac876b74 100644 --- a/src/i18n/locales/ja/common.json +++ b/src/i18n/locales/ja/common.json @@ -201,7 +201,8 @@ }, "FLIGHT-DETAILS": { "DESCRIPTION": "", - "TITLE": "" + "TITLE": "", + "TITLE-NO-ROUTE": "" }, "FLIGHT-SEARCH": { "DESCRIPTION": "", diff --git a/src/i18n/locales/ko/common.json b/src/i18n/locales/ko/common.json index 8a1c9680..eb8bb4d9 100644 --- a/src/i18n/locales/ko/common.json +++ b/src/i18n/locales/ko/common.json @@ -201,7 +201,8 @@ }, "FLIGHT-DETAILS": { "DESCRIPTION": "", - "TITLE": "" + "TITLE": "", + "TITLE-NO-ROUTE": "" }, "FLIGHT-SEARCH": { "DESCRIPTION": "", diff --git a/src/i18n/locales/ru/common.json b/src/i18n/locales/ru/common.json index 096e1313..53095496 100644 --- a/src/i18n/locales/ru/common.json +++ b/src/i18n/locales/ru/common.json @@ -226,27 +226,28 @@ "BOARD": { "ARRIVAL-SEARCH": { "DESCRIPTION": "Актуальный список рейсов авиакомпании Аэрофлот, прибывающих {date}. Онлайн-табло прилетов в {arrivalCity}.", - "TITLE": "Онлайн-табло прилетов в {arrivalCity} | Прибытие рейсов от Аэрофлот {date}" + "TITLE": "Прилет: {arrivalCity}, {date}" }, "DEPARTURE-SEARCH": { "DESCRIPTION": "Актуальный список рейсов авиакомпании Аэрофлот, отправляющихся {date}. Онлайн-табло вылетов из {departureCity}.", - "TITLE": "Онлайн-табло вылетов из {departureCity} | Отправление рейсов Аэрофлот {date}" + "TITLE": "Вылет: {departureCity}, {date}" }, "FLIGHT-DETAILS": { "DESCRIPTION": "Информация об отправлении и прибытии рейса {flightNumber} в режиме онлайн! Время вылета, время прилета, актуальный статус рейса на официальном сайте авиакомпании Аэрофлот.", - "TITLE": "Статус рейса {flightNumber} {date} | Аэрофлот" + "TITLE": "Информация о рейсе: {flightNumber}, {routeCities}", + "TITLE-NO-ROUTE": "Информация о рейсе: {flightNumber}" }, "FLIGHT-SEARCH": { "DESCRIPTION": "Информация об отправлении и прибытии рейса {flightNumber} {date}.", - "TITLE": "Рейс {flightNumber} – Онлайн-табло прилета и вылета {date} | Аэрофлот" + "TITLE": "Рейс: {flightNumber}, {date}" }, "MAIN": { "DESCRIPTION": "Табло прибытия и отправления рейсов авиакомпании 'Аэрофлот'. Информация о прилетах и вылетах в режиме онлайн.", - "TITLE": "Онлайн-табло вылетов и прилетов рейсов авиакомпании Аэрофлот | Аэрофлот" + "TITLE": "Онлайн-Табло" }, "ROUTE-SEARCH": { "DESCRIPTION": "Табло прибытия и отправления рейсов авиакомпании Аэрофлот по направлению {departureCity} - {arrivalCity}. Информация о прилетах и вылетах в режиме онлайн на {date}.", - "TITLE": "Прибытие и отправление рейсов {departureCity} - {arrivalCity} {date} | Аэрофлот" + "TITLE": "Маршрут: {departureCity}-{arrivalCity}, {date}" } }, "SCHEDULE": { diff --git a/src/i18n/locales/zh/common.json b/src/i18n/locales/zh/common.json index b39e6831..f064cb6e 100644 --- a/src/i18n/locales/zh/common.json +++ b/src/i18n/locales/zh/common.json @@ -201,7 +201,8 @@ }, "FLIGHT-DETAILS": { "DESCRIPTION": "", - "TITLE": "" + "TITLE": "", + "TITLE-NO-ROUTE": "" }, "FLIGHT-SEARCH": { "DESCRIPTION": "",