Fix three parity issues from final audit
1. Route heading uses airport name when a code maps only to an airport (SVO → 'Шереметьево') but prefers the city when the code is a city too (LED → 'Санкт-Петербург', not 'Пулково'). Angular does the same. Apply the new lookup order in both the onlineboard and schedule search pages. 2. Append ', Сегодня' (or 'DD.MM.YYYY' for other dates) to the board search heading, matching Angular. 3. Render the '+1' day-change marker on FlightCard even when only scheduled times are known. Previously the fallback pulled the value from `actualBlockOff/On.dayChange`, which is undefined for scheduled-only flights — so overnight flights like SU 6805 (23:30 → 00:55 +1) showed no indicator. Read `scheduledDeparture/Arrival.dayChange.value` when the actual block time is missing. 4. Localize the PrimeReact Calendar widget: register a Russian locale in [lang]/layout.tsx and set the active one on every locale change, so 'Choose Date' reads 'Выбрать дату' and month/day names localize.
This commit is contained in:
@@ -185,27 +185,40 @@ export const OnlineBoardSearchPage: FC<OnlineBoardSearchPageProps> = ({
|
||||
const lang = routeParams.lang ?? "ru";
|
||||
const { dictionaries } = useDictionaries(lang);
|
||||
|
||||
// Human-readable title/breadcrumb. Angular derives these from the
|
||||
// station dictionary — e.g. "Маршрут: Шереметьево - Санкт-Петербург".
|
||||
// Human-readable title/breadcrumb. Angular prefers the city name when a
|
||||
// code resolves to a city (LED → 'Санкт-Петербург'); falls back to the
|
||||
// airport name only for codes that aren't city codes (SVO → 'Шереметьево').
|
||||
const describeStation = (code?: string): string => {
|
||||
if (!code || !dictionaries) return code ?? "";
|
||||
const upper = code.toUpperCase();
|
||||
return (
|
||||
dictionaries.airportByCode.get(upper)?.name ??
|
||||
dictionaries.cityByCode.get(upper)?.name ??
|
||||
code
|
||||
);
|
||||
const city = dictionaries.cityByCode.get(upper);
|
||||
if (city) return city.name;
|
||||
const airport = dictionaries.airportByCode.get(upper);
|
||||
if (airport) return airport.name;
|
||||
return code;
|
||||
};
|
||||
// Today's date gets rendered as 'Сегодня', matching Angular's heading.
|
||||
const dateLabel = ((): string => {
|
||||
if (!params.date || params.date.length !== 8) return "";
|
||||
const now = new Date();
|
||||
const todayYyyymmdd = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, "0")}${String(now.getDate()).padStart(2, "0")}`;
|
||||
if (params.date === todayYyyymmdd) return t("SHARED.TODAY");
|
||||
// Otherwise format as 'DD.MM.YYYY'.
|
||||
return `${params.date.slice(6, 8)}.${params.date.slice(4, 6)}.${params.date.slice(0, 4)}`;
|
||||
})();
|
||||
let searchHeading: string;
|
||||
switch (params.type) {
|
||||
case "route":
|
||||
searchHeading = `${t("BOARD.ROUTE-TEXT")}${describeStation(params.departure)} - ${describeStation(params.arrival)}`;
|
||||
if (dateLabel) searchHeading += `, ${dateLabel}`;
|
||||
break;
|
||||
case "departure":
|
||||
searchHeading = `${t("BOARD.DEPARTURE")}: ${describeStation(params.station)}`;
|
||||
if (dateLabel) searchHeading += `, ${dateLabel}`;
|
||||
break;
|
||||
case "arrival":
|
||||
searchHeading = `${t("BOARD.ARRIVAL")}: ${describeStation(params.station)}`;
|
||||
if (dateLabel) searchHeading += `, ${dateLabel}`;
|
||||
break;
|
||||
case "flight":
|
||||
searchHeading = `${t("BOARD.FLIGHT_NUMBER")}: ${params.carrier}${params.flightNumber}`;
|
||||
|
||||
@@ -84,14 +84,16 @@ export const ScheduleSearchPage: FC<ScheduleSearchPageProps> = ({ params }) => {
|
||||
|
||||
// Resolve IATA codes to human city/airport names so the heading reads
|
||||
// 'Маршрут: Шереметьево - Санкт-Петербург' instead of 'SVO - LED'.
|
||||
// City wins over airport when the code resolves to both (Angular
|
||||
// parity — LED is both codes; arrivals render the city name).
|
||||
const describeStation = (code?: string): string => {
|
||||
if (!code || !dictionaries) return code ?? "";
|
||||
const upper = code.toUpperCase();
|
||||
return (
|
||||
dictionaries.airportByCode.get(upper)?.name ??
|
||||
dictionaries.cityByCode.get(upper)?.name ??
|
||||
code
|
||||
);
|
||||
const city = dictionaries.cityByCode.get(upper);
|
||||
if (city) return city.name;
|
||||
const airport = dictionaries.airportByCode.get(upper);
|
||||
if (airport) return airport.name;
|
||||
return code;
|
||||
};
|
||||
const depName = describeStation(outbound.departure);
|
||||
const arrName = describeStation(outbound.arrival);
|
||||
|
||||
@@ -1,11 +1,39 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useParams } from "@modern-js/runtime/router";
|
||||
import { Outlet } from "@modern-js/runtime/router";
|
||||
import { addLocale, locale as setPrimeLocale } from "primereact/api";
|
||||
import { isLanguage, type Language } from "@/i18n/resolver";
|
||||
import { createI18nInstance } from "@/i18n/config";
|
||||
import { I18nProvider } from "@/i18n/provider";
|
||||
import type i18next from "i18next";
|
||||
|
||||
// Register PrimeReact locales once at module load so the Calendar /
|
||||
// AutoComplete widgets render with localized labels (e.g. 'Выбрать дату'
|
||||
// instead of 'Choose Date'). Only the keys PrimeReact actually reads
|
||||
// are listed here; the rest fall back to defaults.
|
||||
addLocale("ru", {
|
||||
dayNames: ["воскресенье", "понедельник", "вторник", "среда", "четверг", "пятница", "суббота"],
|
||||
dayNamesShort: ["вс", "пн", "вт", "ср", "чт", "пт", "сб"],
|
||||
dayNamesMin: ["вс", "пн", "вт", "ср", "чт", "пт", "сб"],
|
||||
monthNames: ["январь", "февраль", "март", "апрель", "май", "июнь", "июль", "август", "сентябрь", "октябрь", "ноябрь", "декабрь"],
|
||||
monthNamesShort: ["янв", "фев", "мар", "апр", "май", "июн", "июл", "авг", "сен", "окт", "ноя", "дек"],
|
||||
today: "Сегодня",
|
||||
clear: "Очистить",
|
||||
chooseDate: "Выбрать дату",
|
||||
prevDecade: "Предыдущее десятилетие",
|
||||
nextDecade: "Следующее десятилетие",
|
||||
prevYear: "Предыдущий год",
|
||||
nextYear: "Следующий год",
|
||||
prevMonth: "Предыдущий месяц",
|
||||
nextMonth: "Следующий месяц",
|
||||
chooseYear: "Выбрать год",
|
||||
chooseMonth: "Выбрать месяц",
|
||||
weekHeader: "Нед",
|
||||
firstDayOfWeek: 1,
|
||||
emptyMessage: "Совпадений не найдено",
|
||||
emptyFilterMessage: "Совпадений не найдено",
|
||||
});
|
||||
|
||||
/**
|
||||
* Locale-scoped layout. Validates the `lang` URL segment,
|
||||
* creates the i18n instance, and wraps children via <Outlet />.
|
||||
@@ -22,6 +50,9 @@ export default function LangLayout(): JSX.Element {
|
||||
useEffect(() => {
|
||||
if (!locale) return;
|
||||
let cancelled = false;
|
||||
// PrimeReact reads the active locale via its module-level state; set it
|
||||
// whenever our URL locale changes so widgets pick up the new labels.
|
||||
setPrimeLocale(locale === "ru" ? "ru" : "en");
|
||||
void createI18nInstance({ locale }).then((instance) => {
|
||||
if (!cancelled) {
|
||||
setI18n(instance);
|
||||
|
||||
@@ -77,7 +77,13 @@ export const FlightCard: FC<FlightCardProps> = ({ flight, onClick }) => {
|
||||
<TimeGroup
|
||||
scheduled={depTimes.scheduledDeparture.local}
|
||||
actual={depTimes.actualBlockOff?.local}
|
||||
dayChange={depTimes.actualBlockOff?.dayChange.value}
|
||||
// Prefer the actual day-offset but fall back to the scheduled one
|
||||
// so scheduled-only flights still show the '+1' marker when they
|
||||
// cross midnight (SU 6805 departs at 23:30 and arrives at 00:55+1).
|
||||
dayChange={
|
||||
depTimes.actualBlockOff?.dayChange.value ??
|
||||
depTimes.scheduledDeparture.dayChange?.value
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -99,7 +105,10 @@ export const FlightCard: FC<FlightCardProps> = ({ flight, onClick }) => {
|
||||
<TimeGroup
|
||||
scheduled={arrTimes.scheduledArrival.local}
|
||||
actual={arrTimes.actualBlockOn?.local}
|
||||
dayChange={arrTimes.actualBlockOn?.dayChange.value}
|
||||
dayChange={
|
||||
arrTimes.actualBlockOn?.dayChange.value ??
|
||||
arrTimes.scheduledArrival.dayChange?.value
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user