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:
@@ -5,29 +5,38 @@
|
||||
* No Angular dependencies, no side effects.
|
||||
*/
|
||||
|
||||
/** Match `ru`, `ru-RU`, `ru-ru`, `RU` — anything starting with `ru` */
|
||||
function isRussianLocale(locale: string): boolean {
|
||||
return locale.toLowerCase().startsWith("ru");
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a duration given in total minutes into a human-readable string.
|
||||
* Russian units mirror Angular's DurationPipe (SHORT-DAY='д.', SHORT-HOUR='ч.',
|
||||
* SHORT-MIN='мин.') so values read as '1ч. 30мин.' not '1ч 30м'.
|
||||
*
|
||||
* Accepts either a short language code (`"ru"`) or a full BCP-47 locale
|
||||
* (`"ru-ru"`).
|
||||
*
|
||||
* @example formatDuration(150) => "2h 30m"
|
||||
* @example formatDuration(150, "ru") => "2ч. 30мин."
|
||||
* @example formatDuration(150, "ru-ru") => "2ч. 30мин."
|
||||
* @example formatDuration(0) => "0h 0m"
|
||||
*/
|
||||
export function formatDuration(
|
||||
minutes: number,
|
||||
locale: string = "en",
|
||||
): string {
|
||||
if (minutes < 0) return locale === "ru" ? "Неизвестно" : "Unknown";
|
||||
const ru = isRussianLocale(locale);
|
||||
if (minutes < 0) return ru ? "Неизвестно" : "Unknown";
|
||||
|
||||
const days = Math.floor(minutes / (60 * 24));
|
||||
const hours = Math.floor((minutes % (60 * 24)) / 60);
|
||||
const mins = Math.floor(minutes % 60);
|
||||
|
||||
const units =
|
||||
locale === "ru"
|
||||
? { d: "д.", h: "ч.", m: "мин." }
|
||||
: { d: "d", h: "h", m: "m" };
|
||||
const units = ru
|
||||
? { d: "д.", h: "ч.", m: "мин." }
|
||||
: { d: "d", h: "h", m: "m" };
|
||||
|
||||
const daysPart = days > 0 ? `${days}${units.d} ` : "";
|
||||
return `${daysPart}${hours}${units.h} ${mins}${units.m}`;
|
||||
@@ -61,7 +70,7 @@ export function formatDate(
|
||||
const d = typeof date === "string" ? new Date(date) : date;
|
||||
if (Number.isNaN(d.getTime())) return "";
|
||||
|
||||
return d.toLocaleDateString(locale === "ru" ? "ru-RU" : "en-US", {
|
||||
return d.toLocaleDateString(isRussianLocale(locale) ? "ru-RU" : "en-US", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
|
||||
Reference in New Issue
Block a user