Translate remaining English strings and color statuses

- FlightList empty-state, Operated-by label, details/schedule error
  and not-found messages now route through i18n instead of hardcoded
  English. Added BOARD.FLIGHT-NOT-FOUND, BOARD.LOAD-FAILED,
  BOARD.OPERATED-BY, SHARED.RETRY to all nine locales.
- FlightStatus label now picks up the same colour as the plane icon
  (red for Cancelled, green for Arrived/Landed, blue for In Flight,
  orange for Delayed) — matches Angular's flight-status text treatment
  so 'Отменен' reads at a glance.
- Tests updated to expect the translation keys under the mocked `t`.
This commit is contained in:
2026-04-18 00:15:46 +03:00
parent 4d73e2fd3c
commit a444b71bcd
17 changed files with 92 additions and 41 deletions
@@ -272,7 +272,7 @@ export const OnlineBoardDetailsPage: FC<OnlineBoardDetailsPageProps> = ({
return (
<PageLayout {...commonLayoutProps}>
<div className="flight-details flight-details--error" data-testid="flight-details-error">
<p>Failed to load flight details. Please try again.</p>
<p>{t("BOARD.LOAD-FAILED")}</p>
</div>
</PageLayout>
);
@@ -282,7 +282,7 @@ export const OnlineBoardDetailsPage: FC<OnlineBoardDetailsPageProps> = ({
return (
<PageLayout {...commonLayoutProps}>
<div className="flight-details flight-details--not-found" data-testid="flight-details-not-found">
<p>Flight not found.</p>
<p>{t("BOARD.FLIGHT-NOT-FOUND")}</p>
</div>
</PageLayout>
);
@@ -357,7 +357,7 @@ export const OnlineBoardDetailsPage: FC<OnlineBoardDetailsPageProps> = ({
{/* Operating carrier */}
{displayFlight.operatingBy.carrier && (
<div className="flight-details__operating" data-testid="operating-carrier">
Operated by: {displayFlight.operatingBy.carrier}
{t("BOARD.OPERATED-BY")}: {displayFlight.operatingBy.carrier}
{displayFlight.operatingBy.flightNumber
? ` ${displayFlight.operatingBy.flightNumber}`
: ""}
@@ -89,7 +89,9 @@ describe("OnlineBoardSearchPage", () => {
it("renders empty flight list when no results", () => {
render(<OnlineBoardSearchPage params={departureParsedParams} />);
expect(screen.getByText("No flights found")).toBeTruthy();
// Empty-state text is now translated via SHARED.FLIGHTS-NOT-FOUND.
// Mocked `t` returns the key unchanged.
expect(screen.getByText("SHARED.FLIGHTS-NOT-FOUND")).toBeTruthy();
});
it("renders for flight search type", () => {
@@ -76,7 +76,7 @@ export const ScheduleDetailsPage: FC<ScheduleDetailsPageProps> = ({
if (error) {
return (
<div className="schedule-details schedule-details--error" data-testid="schedule-details-error">
<p>Failed to load schedule details. Please try again.</p>
<p>{t("BOARD.LOAD-FAILED")}</p>
</div>
);
}
@@ -84,7 +84,7 @@ export const ScheduleDetailsPage: FC<ScheduleDetailsPageProps> = ({
if (flights.length === 0) {
return (
<div className="schedule-details schedule-details--not-found" data-testid="schedule-details-not-found">
<p>Schedule details not found.</p>
<p>{t("BOARD.FLIGHT-NOT-FOUND")}</p>
</div>
);
}
@@ -11,6 +11,7 @@
import type { FC } from "react";
import { useCallback } from "react";
import { useNavigate, useParams } from "@modern-js/runtime/router";
import { useTranslation } from "@/i18n/provider.js";
import { FlightList } from "@/ui/flights/FlightList.js";
import "./ScheduleSearchPage.scss";
import { JsonLdRenderer } from "@/shared/seo/json-ld.js";
@@ -67,6 +68,7 @@ function extractSimpleFlights(flights: Array<{ routeType: string }>): ISimpleFli
export const ScheduleSearchPage: FC<ScheduleSearchPageProps> = ({ params }) => {
const navigate = useNavigate();
const { t } = useTranslation();
const routeParams = useParams<{ lang: string }>();
const lang = routeParams.lang ?? "ru";
@@ -143,8 +145,8 @@ export const ScheduleSearchPage: FC<ScheduleSearchPageProps> = ({ params }) => {
{/* Error state */}
{outboundError && (
<div className="schedule-search__error" data-testid="search-error">
<p>Failed to load schedule. Please try again.</p>
<button type="button" onClick={refresh}>Retry</button>
<p>{t("BOARD.LOAD-FAILED")}</p>
<button type="button" onClick={refresh}>{t("SHARED.RETRY")}</button>
</div>
)}
+6 -2
View File
@@ -41,7 +41,10 @@
"DETAILS-TITLE": "Flight details",
"FLIGHT-INFO": "Flight information",
"LOCAL-TIME-NOTE": "Times shown are LOCAL.",
"ESTIMATED-TIME-NOTE": "Arrival times and distances are estimated. Times may change depending on weather and airport load."
"ESTIMATED-TIME-NOTE": "Arrival times and distances are estimated. Times may change depending on weather and airport load.",
"FLIGHT-NOT-FOUND": "Flight not found.",
"LOAD-FAILED": "Failed to load data. Please try again.",
"OPERATED-BY": "Operated by"
},
"BOARDING-STATUSES": {
"Expected": "Erwartet",
@@ -368,7 +371,8 @@
"TRANSFER": "Transfer",
"TRAVEL-TIME": "Reisezeit",
"WEEK": "Woche",
"WEEK_FORMAT-WRONG": ""
"WEEK_FORMAT-WRONG": "",
"RETRY": "Retry"
},
"WARNING": {
"IFLY_HIGHLIGHT": "Bitte beachten Sie:",
+6 -2
View File
@@ -41,7 +41,10 @@
"DETAILS-TITLE": "Flight details",
"FLIGHT-INFO": "Flight information",
"LOCAL-TIME-NOTE": "Times shown are LOCAL.",
"ESTIMATED-TIME-NOTE": "Arrival times and distances are estimated. Times may change depending on weather and airport load."
"ESTIMATED-TIME-NOTE": "Arrival times and distances are estimated. Times may change depending on weather and airport load.",
"FLIGHT-NOT-FOUND": "Flight not found.",
"LOAD-FAILED": "Failed to load data. Please try again.",
"OPERATED-BY": "Operated by"
},
"BREADCRUMBS": {
"ONLINEBOARD": "Online Board"
@@ -395,7 +398,8 @@
"TRANSFER": "Connection",
"TRAVEL-TIME": "Travel time",
"WEEK": "Week",
"WEEK_FORMAT-WRONG": ""
"WEEK_FORMAT-WRONG": "",
"RETRY": "Retry"
},
"WARNING": {
"IFLY_HIGHLIGHT": "Please note:",
+6 -2
View File
@@ -41,7 +41,10 @@
"DETAILS-TITLE": "Flight details",
"FLIGHT-INFO": "Flight information",
"LOCAL-TIME-NOTE": "Times shown are LOCAL.",
"ESTIMATED-TIME-NOTE": "Arrival times and distances are estimated. Times may change depending on weather and airport load."
"ESTIMATED-TIME-NOTE": "Arrival times and distances are estimated. Times may change depending on weather and airport load.",
"FLIGHT-NOT-FOUND": "Flight not found.",
"LOAD-FAILED": "Failed to load data. Please try again.",
"OPERATED-BY": "Operated by"
},
"BOARDING-STATUSES": {
"Expected": "Prevista",
@@ -368,7 +371,8 @@
"TRANSFER": "Enlace",
"TRAVEL-TIME": "Duración del viaje",
"WEEK": "Semana",
"WEEK_FORMAT-WRONG": ""
"WEEK_FORMAT-WRONG": "",
"RETRY": "Retry"
},
"WARNING": {
"IFLY_HIGHLIGHT": "Nota:",
+6 -2
View File
@@ -41,7 +41,10 @@
"DETAILS-TITLE": "Flight details",
"FLIGHT-INFO": "Flight information",
"LOCAL-TIME-NOTE": "Times shown are LOCAL.",
"ESTIMATED-TIME-NOTE": "Arrival times and distances are estimated. Times may change depending on weather and airport load."
"ESTIMATED-TIME-NOTE": "Arrival times and distances are estimated. Times may change depending on weather and airport load.",
"FLIGHT-NOT-FOUND": "Flight not found.",
"LOAD-FAILED": "Failed to load data. Please try again.",
"OPERATED-BY": "Operated by"
},
"BOARDING-STATUSES": {
"Expected": "Prévu",
@@ -368,7 +371,8 @@
"TRANSFER": "Correspondance",
"TRAVEL-TIME": "Durée du trajet",
"WEEK": "Semaine",
"WEEK_FORMAT-WRONG": ""
"WEEK_FORMAT-WRONG": "",
"RETRY": "Retry"
},
"WARNING": {
"IFLY_HIGHLIGHT": "Remarque:",
+6 -2
View File
@@ -41,7 +41,10 @@
"DETAILS-TITLE": "Flight details",
"FLIGHT-INFO": "Flight information",
"LOCAL-TIME-NOTE": "Times shown are LOCAL.",
"ESTIMATED-TIME-NOTE": "Arrival times and distances are estimated. Times may change depending on weather and airport load."
"ESTIMATED-TIME-NOTE": "Arrival times and distances are estimated. Times may change depending on weather and airport load.",
"FLIGHT-NOT-FOUND": "Flight not found.",
"LOAD-FAILED": "Failed to load data. Please try again.",
"OPERATED-BY": "Operated by"
},
"BOARDING-STATUSES": {
"Expected": "Previsto",
@@ -368,7 +371,8 @@
"TRANSFER": "Scalo",
"TRAVEL-TIME": "Durata del viaggio",
"WEEK": "Settimana",
"WEEK_FORMAT-WRONG": ""
"WEEK_FORMAT-WRONG": "",
"RETRY": "Retry"
},
"WARNING": {
"IFLY_HIGHLIGHT": "Attenzione:",
+6 -2
View File
@@ -41,7 +41,10 @@
"DETAILS-TITLE": "Flight details",
"FLIGHT-INFO": "Flight information",
"LOCAL-TIME-NOTE": "Times shown are LOCAL.",
"ESTIMATED-TIME-NOTE": "Arrival times and distances are estimated. Times may change depending on weather and airport load."
"ESTIMATED-TIME-NOTE": "Arrival times and distances are estimated. Times may change depending on weather and airport load.",
"FLIGHT-NOT-FOUND": "Flight not found.",
"LOAD-FAILED": "Failed to load data. Please try again.",
"OPERATED-BY": "Operated by"
},
"BOARDING-STATUSES": {
"Expected": "予測",
@@ -368,7 +371,8 @@
"TRANSFER": "乗り換え",
"TRAVEL-TIME": "移動時間",
"WEEK": "週",
"WEEK_FORMAT-WRONG": ""
"WEEK_FORMAT-WRONG": "",
"RETRY": "Retry"
},
"WARNING": {
"IFLY_HIGHLIGHT": "ご注意:",
+6 -2
View File
@@ -41,7 +41,10 @@
"DETAILS-TITLE": "Flight details",
"FLIGHT-INFO": "Flight information",
"LOCAL-TIME-NOTE": "Times shown are LOCAL.",
"ESTIMATED-TIME-NOTE": "Arrival times and distances are estimated. Times may change depending on weather and airport load."
"ESTIMATED-TIME-NOTE": "Arrival times and distances are estimated. Times may change depending on weather and airport load.",
"FLIGHT-NOT-FOUND": "Flight not found.",
"LOAD-FAILED": "Failed to load data. Please try again.",
"OPERATED-BY": "Operated by"
},
"BOARDING-STATUSES": {
"Expected": "예상 ",
@@ -368,7 +371,8 @@
"TRANSFER": "연결편 항공",
"TRAVEL-TIME": "여행 시간",
"WEEK": "주",
"WEEK_FORMAT-WRONG": ""
"WEEK_FORMAT-WRONG": "",
"RETRY": "Retry"
},
"WARNING": {
"IFLY_HIGHLIGHT": "참고:",
+18 -14
View File
@@ -41,7 +41,10 @@
"DETAILS-TITLE": "Детали рейса",
"FLIGHT-INFO": "Информация о рейсе",
"LOCAL-TIME-NOTE": "Время в системе - МЕСТНОЕ.",
"ESTIMATED-TIME-NOTE": "Время прилета и расстояния являются расчетными и примерными. Время может изменяться в зависимости от погодных условий и загрузки аэропорта."
"ESTIMATED-TIME-NOTE": "Время прилета и расстояния являются расчетными и примерными. Время может изменяться в зависимости от погодных условий и загрузки аэропорта.",
"FLIGHT-NOT-FOUND": "Рейс не найден.",
"LOAD-FAILED": "Не удалось загрузить данные. Попробуйте снова.",
"OPERATED-BY": "Выполняет рейс"
},
"BREADCRUMBS": {
"ONLINEBOARD": "Онлайн-табло"
@@ -208,7 +211,7 @@
"TITLE": "Расписание рейсов",
"TITLE-TAB": "Расписание",
"SCHEDULE-BOTTOM-DESCRIPTION": "Расписание рейсов Аэрофлота",
"SCHEDULE-BOTTOM-DESCRIPTION-TEXT" : "<p>На странице расписания рейсов Аэрофлота представлена вся необходимая информация о времени отправления и прибытия наших рейсов. <br> Выбирайте дату и планируйте свое путешествие заранее: прямым рейсом или с пересадками.</p> <p>Мы предлагаем билеты на самолет по конкурентным ценам с удобным <a target='_blank' href='https://www.aeroflot.ru/ru/booking'>онлайн-сервисом</a> для оформления заказа.</p> <p>Путешествуйте с Аэрофлотом, где комфорт и надежность всегда на высоте!</p>"
"SCHEDULE-BOTTOM-DESCRIPTION-TEXT": "<p>На странице расписания рейсов Аэрофлота представлена вся необходимая информация о времени отправления и прибытия наших рейсов. <br> Выбирайте дату и планируйте свое путешествие заранее: прямым рейсом или с пересадками.</p> <p>Мы предлагаем билеты на самолет по конкурентным ценам с удобным <a target='_blank' href='https://www.aeroflot.ru/ru/booking'>онлайн-сервисом</a> для оформления заказа.</p> <p>Путешествуйте с Аэрофлотом, где комфорт и надежность всегда на высоте!</p>"
},
"SEO": {
"BOARD": {
@@ -251,7 +254,7 @@
"TITLE": "Расписание рейсов {{ departureCity }} - {{ arrivalCity }} | Аэрофлот"
}
},
"FLIGHTS-MAP":{
"FLIGHTS-MAP": {
"MAIN": {
"DESCRIPTION": "Карта полетов авиакомпании 'Аэрофлот'. Информация о направлениях рейсов.",
"TITLE": "Карта полетов авиакомпании Аэрофлот"
@@ -395,26 +398,27 @@
"TRANSFER": "Пересадка",
"TRAVEL-TIME": "В пути",
"WEEK": "Неделя",
"WEEK_FORMAT-WRONG": "Не соответствует формату ДД.ММ.ГГГГ - ДД.ММ.ГГГГ"
"WEEK_FORMAT-WRONG": "Не соответствует формату ДД.ММ.ГГГГ - ДД.ММ.ГГГГ",
"RETRY": "Повторить"
},
"SMOKE": {
"HEADING": "Страница проверки"
},
"WARNING":{
"IFLY_HIGHLIGHT" : "Обратите внимание!",
"IFLY_BODY" : "Рейсы Аэрофлота с нумерацией SU5800-SU5949 выполняются на самолётах и экипажем авиакомпании iFly.",
"IFLY_INFO" : "Информация о порядке обслуживания на борту <a target=\"_blank\" href=\"https://www.aeroflot.ru/ru-ru/aeroflot-ifly\">здесь</a>."
"WARNING": {
"IFLY_HIGHLIGHT": "Обратите внимание!",
"IFLY_BODY": "Рейсы Аэрофлота с нумерацией SU5800-SU5949 выполняются на самолётах и экипажем авиакомпании iFly.",
"IFLY_INFO": "Информация о порядке обслуживания на борту <a target=\"_blank\" href=\"https://www.aeroflot.ru/ru-ru/aeroflot-ifly\">здесь</a>."
},
"FLIGHTS-MAP":{
"FLIGHTS-MAP": {
"TITLE": "Карта полетов",
"ROUTE": "Найдите свой маршрут",
"FILTER_DEPARTURE_PLACEHOLDER": "Откуда",
"FILTER_ARRIVAL_PLACEHOLDER": "Куда",
"DOMESTIC_FLIGHTS": "Внутренние рейсы",
"INTERNATIONAL_FLIGHTS": "Международные регулярные рейсы",
"FILTER_INFO" : "Нажмите на «Купить билет», чтобы получить полный список рейсов по выбранному направлению",
"BUY_TICKET_BTN" : "Купить билет",
"CONNECTING_FLIGHTS" : "Показать только рейсы с пересадкой",
"NO_DIRECTIONS_INFO":"Маршрутов не найдено, измените параметры поиска"
"FILTER_INFO": "Нажмите на «Купить билет», чтобы получить полный список рейсов по выбранному направлению",
"BUY_TICKET_BTN": "Купить билет",
"CONNECTING_FLIGHTS": "Показать только рейсы с пересадкой",
"NO_DIRECTIONS_INFO": "Маршрутов не найдено, измените параметры поиска"
}
}
}
+6 -2
View File
@@ -41,7 +41,10 @@
"DETAILS-TITLE": "Flight details",
"FLIGHT-INFO": "Flight information",
"LOCAL-TIME-NOTE": "Times shown are LOCAL.",
"ESTIMATED-TIME-NOTE": "Arrival times and distances are estimated. Times may change depending on weather and airport load."
"ESTIMATED-TIME-NOTE": "Arrival times and distances are estimated. Times may change depending on weather and airport load.",
"FLIGHT-NOT-FOUND": "Flight not found.",
"LOAD-FAILED": "Failed to load data. Please try again.",
"OPERATED-BY": "Operated by"
},
"BOARDING-STATUSES": {
"Expected": "预计",
@@ -368,7 +371,8 @@
"TRANSFER": "联程",
"TRAVEL-TIME": "旅行时间",
"WEEK": "周",
"WEEK_FORMAT-WRONG": ""
"WEEK_FORMAT-WRONG": "",
"RETRY": "Retry"
},
"WARNING": {
"IFLY_HIGHLIGHT": "请注意:",
+3 -1
View File
@@ -1,4 +1,5 @@
import type { FC } from "react";
import { useTranslation } from "@/i18n/provider.js";
import type { ISimpleFlight } from "@/features/online-board/types.js";
import { FlightCard } from "./FlightCard.js";
import { FlightListSkeleton } from "./FlightListSkeleton.js";
@@ -27,6 +28,7 @@ export const FlightList: FC<FlightListProps> = ({
skeletonCount = 5,
onFlightClick,
}) => {
const { t } = useTranslation();
if (loading) {
return <FlightListSkeleton count={skeletonCount} />;
}
@@ -34,7 +36,7 @@ export const FlightList: FC<FlightListProps> = ({
if (flights.length === 0) {
return (
<div className="flight-list flight-list--empty">
<p className="flight-list__empty-message">No flights found</p>
<p className="flight-list__empty-message">{t("SHARED.FLIGHTS-NOT-FOUND")}</p>
</div>
);
}
+9
View File
@@ -21,6 +21,15 @@
white-space: nowrap;
}
// Mirror the icon colour on the label so "Отменен"/"Прибыл"/"В полете"
// read at a glance (Angular does the same).
&--cancelled &__label { color: #e55353; }
&--arrived &__label,
&--landed &__label { color: #6da244; }
&--in-flight &__label,
&--departed &__label { color: #2457ff; }
&--delayed &__label { color: #f29f3a; }
&__content {
display: flex;
align-items: center;
@@ -206,7 +206,7 @@ describe("Flight details page integration", () => {
/>,
);
expect(screen.getByTestId("operating-carrier")).toBeTruthy();
expect(screen.getByText(/Operated by: SU/)).toBeTruthy();
expect(screen.getByText(/BOARD\.OPERATED-BY: SU/)).toBeTruthy();
});
it("renders flying time", () => {
@@ -157,7 +157,7 @@ describe("Flight search page integration", () => {
it("renders empty state when no flights returned", () => {
setupMocksEmpty();
render(<OnlineBoardSearchPage params={DEPARTURE_PARAMS} />);
expect(screen.getByText("No flights found")).toBeTruthy();
expect(screen.getByText("SHARED.FLIGHTS-NOT-FOUND")).toBeTruthy();
});
it("renders calendar strip (DayTabs) with pagination arrows", () => {