Align Online-Board page titles with TZ Table 6 (сегодня/завтра/ДД.ММ.ГГГГ date display)
- Add formatDateForTitle helper: returns today/tomorrow labels or dd.MM.yyyy - Switch all search page title builders to use formatDateForTitle; descriptions keep dd.MM.yyyy - FLIGHT-DETAILS title now uses routeCities (no date) per TZ rows 6-8; adds TITLE-NO-ROUTE fallback for SSR when cities not yet loaded - buildFlightDetailsSeoFromId accepts optional cityNames param - Update ru/en i18n TITLE strings to TZ Table 6 format; add TITLE-NO-ROUTE to all 9 locales - Tests: 32 cases covering today/tomorrow/arbitrary-date branches and routeCities logic
This commit is contained in:
@@ -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, unknown>): 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, unknown>): 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", () => {
|
||||
|
||||
@@ -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 <head> 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
|
||||
|
||||
@@ -201,7 +201,8 @@
|
||||
},
|
||||
"FLIGHT-DETAILS": {
|
||||
"DESCRIPTION": "",
|
||||
"TITLE": ""
|
||||
"TITLE": "",
|
||||
"TITLE-NO-ROUTE": ""
|
||||
},
|
||||
"FLIGHT-SEARCH": {
|
||||
"DESCRIPTION": "",
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -201,7 +201,8 @@
|
||||
},
|
||||
"FLIGHT-DETAILS": {
|
||||
"DESCRIPTION": "",
|
||||
"TITLE": ""
|
||||
"TITLE": "",
|
||||
"TITLE-NO-ROUTE": ""
|
||||
},
|
||||
"FLIGHT-SEARCH": {
|
||||
"DESCRIPTION": "",
|
||||
|
||||
@@ -201,7 +201,8 @@
|
||||
},
|
||||
"FLIGHT-DETAILS": {
|
||||
"DESCRIPTION": "",
|
||||
"TITLE": ""
|
||||
"TITLE": "",
|
||||
"TITLE-NO-ROUTE": ""
|
||||
},
|
||||
"FLIGHT-SEARCH": {
|
||||
"DESCRIPTION": "",
|
||||
|
||||
@@ -201,7 +201,8 @@
|
||||
},
|
||||
"FLIGHT-DETAILS": {
|
||||
"DESCRIPTION": "",
|
||||
"TITLE": ""
|
||||
"TITLE": "",
|
||||
"TITLE-NO-ROUTE": ""
|
||||
},
|
||||
"FLIGHT-SEARCH": {
|
||||
"DESCRIPTION": "",
|
||||
|
||||
@@ -201,7 +201,8 @@
|
||||
},
|
||||
"FLIGHT-DETAILS": {
|
||||
"DESCRIPTION": "",
|
||||
"TITLE": ""
|
||||
"TITLE": "",
|
||||
"TITLE-NO-ROUTE": ""
|
||||
},
|
||||
"FLIGHT-SEARCH": {
|
||||
"DESCRIPTION": "",
|
||||
|
||||
@@ -201,7 +201,8 @@
|
||||
},
|
||||
"FLIGHT-DETAILS": {
|
||||
"DESCRIPTION": "",
|
||||
"TITLE": ""
|
||||
"TITLE": "",
|
||||
"TITLE-NO-ROUTE": ""
|
||||
},
|
||||
"FLIGHT-SEARCH": {
|
||||
"DESCRIPTION": "",
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -201,7 +201,8 @@
|
||||
},
|
||||
"FLIGHT-DETAILS": {
|
||||
"DESCRIPTION": "",
|
||||
"TITLE": ""
|
||||
"TITLE": "",
|
||||
"TITLE-NO-ROUTE": ""
|
||||
},
|
||||
"FLIGHT-SEARCH": {
|
||||
"DESCRIPTION": "",
|
||||
|
||||
Reference in New Issue
Block a user