i18n: BCP-47 URL locales + complete EN translations

- URL surface now matches Angular: `/ru-ru/`, `/en-us/`, `/zh-cn/`, …
  (BCP-47). Bare short codes still work — the [lang]/layout auto-
  promotes them with a replace navigation. Internally everything that
  needs the short language (i18n file lookup, API path segment,
  Accept-Language header, dictionary `title[lang]` key, Intl
  formatters) reads it through the new `useLocale()` hook, which
  returns both `locale` (BCP-47) and `language` (short).
- ApiClient.locale is now mutable and is updated from the [lang]
  layout whenever the URL locale changes — was hard-coded to "ru" in
  the root layout before, so backend responses for /en/... still came
  back in Russian. Cities / airports / flight statuses now arrive in
  the active language.
- All 21 empty EN translation keys filled in (AIRPLANE.*, BOARD.
  PREVIOUS-FLIGHT, SCHEDULE.FILE-NAME, SEO.SCHEDULE.*, SEO.FLIGHTS-
  MAP.*, SHARED.FLIGHT-TRANSFER-PLURAL-*, SHARED.WEEK_FORMAT-WRONG)
  so /en-us renders without falling back to raw keys.
- Added BOARD.LOAD-FAILED-TITLE / -MESSAGE keys (RU + EN) and removed
  the three hardcoded Russian error strings from the search-page
  error card.
- FlightStatus now reads `FLIGHT-STATUSES.{Status}` from i18n instead
  of hardcoding the Russian labels.
- FlightCard's OperatorLogo now picks the en/ru carrier-logo variant
  from `useLocale().language` instead of always passing "ru" — the
  Aeroflot/Rossiya logos display in the active language where
  variants exist.
- registerPrimeLocales(): all 9 supported languages get a PrimeReact
  `addLocale` entry at module load (RU + EN hand-curated, others built
  from Intl). Calendar/AutoComplete widgets switch with the URL.
- ErrorBoundary catches outside the i18n provider, so it now ships
  its own minimal localised string table keyed off the URL locale —
  no more "Something went wrong" leaking on the Russian site.
- Hreflang URLs now emit BCP-47 (`/en-us/...`) while `hreflang="en"`
  stays the short Google-friendly form.
- Datetime helpers accept either short or BCP-47 locale (`isRussianLocale`)
  so callers can pass through whatever the route hands them.
This commit is contained in:
2026-04-19 17:36:24 +03:00
parent b8e595dc25
commit ce2ca4a689
51 changed files with 585 additions and 236 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ import { renderHook } from "@testing-library/react";
import { useCityName } from "./useDictionaries.js";
vi.mock("@modern-js/runtime/router", () => ({
useParams: () => ({ lang: "ru" }),
useParams: () => ({ lang: "ru-ru" }),
}));
const mockDictionariesState = vi.fn();
+3 -3
View File
@@ -9,7 +9,7 @@
* a Map<code, CityModel | AirportModel>.
*/
import { useParams } from "@modern-js/runtime/router";
import { useLocale } from "@/i18n/useLocale.js";
import { useDictionaries as useDictionariesState } from "@/shared/dictionaries/useDictionaries.js";
/**
@@ -19,8 +19,8 @@ import { useDictionaries as useDictionariesState } from "@/shared/dictionaries/u
* see DictionariesService.getCityOrAirport).
*/
export function useCityName(code: string): string {
const { lang } = useParams<{ lang: string }>();
const { dictionaries } = useDictionariesState(lang ?? "ru");
const { language } = useLocale();
const { dictionaries } = useDictionariesState(language);
if (!code || !dictionaries) return code;
const upper = code.toUpperCase();
const city = dictionaries.cityByCode.get(upper);