Merge origin/main: GNSS integrity, geo file import and cockpit emergencies meet i18n

Conflicts resolved by keeping both sides: the Events group gains
gnss-integrity with a translated label, the cockpit aircraft line shows
main's transponder emergency through a localized formatter (cockpitMath.js
stays i18n-free, like the Context scope line), and the boundary lists keep
both the i18n modules and main's new data modules.

Main's new UI is translated into es and ru: the GNSS Integrity row, legend
and notes; geo file import (button, hints, list actions, every import
error, with numbers passed as placeholders); transponder status names.
Also formats kartaview/viewer.js so format:check passes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
German KhokhlovandClaude Opus 5.5 committed 2026-10-15 16:55:45 +04:00
commit 8029747b7e
88 files changed
+11991 -137

No files matched your search

+99
View File
@@ -0,0 +1,99 @@
/**
* Transponder emergency status for one aircraft, from the Mode A squawk and
* the ADS-B emergency/priority field.
*
* Emergency squawks are ICAO-standard worldwide: 7500 unlawful interference,
* 7600 radio failure, 7700 general emergency. readsb (adsb.lol) also reports
* `emergency`, "a superset of the 7x00 squawks": none, general, lifeguard,
* minfuel, nordo, unlawful, downed, reserved. OpenSky state vectors carry the
* squawk only.
*
* The status is what the transponder broadcast in THIS poll, never a finding:
* crews pass through 7x00 codes while dialling. Highlighting follows the latest
* broadcast, which does not confirm an incident or its resolution.
*/
/** @constant {string} Tint for aircraft broadcasting an emergency (not a priority). */
export const EMERGENCY_TINT_CSS = '#FF4444';
// Marks catalog text without importing i18n: tools and the server load this module.
const msg = (text) => text;
const SQUAWK_KINDS = { 7500: 'unlawful', 7600: 'nordo', 7700: 'general' };
const KINDS = {
general: { label: msg('General emergency'), severity: 'emergency' },
unlawful: { label: msg('Unlawful interference'), severity: 'emergency' },
nordo: { label: msg('Radio failure'), severity: 'emergency' },
downed: { label: msg('Downed aircraft'), severity: 'emergency' },
minfuel: { label: msg('Minimum fuel'), severity: 'priority' },
lifeguard: {
label: msg('Lifeguard (medical priority)'),
severity: 'priority',
},
};
/**
* A Mode A code as its four octal digits, else null.
* @param {unknown} value
* @returns {string|null}
*/
export function normalizeSquawk(value) {
const code = String(value ?? '').trim();
return /^[0-7]{4}$/.test(code) ? code : null;
}
/**
* Emergency status from one observation. Higher severity wins; an ADS-B
* emergency wins a tie. A
* 7x00 squawk counts even when the ADS-B field says `none`.
* @param {{squawk?: unknown, emergency?: unknown}} [observation]
* @returns {{kind: string, label: string, severity: 'emergency'|'priority',
* squawk: string|null, source: 'ads-b'|'squawk'}|null} `source` is
* 'squawk' when the Mode A code itself names the status (so it can be
* quoted), else 'ads-b'.
*/
export function aircraftEmergency({ squawk, emergency } = {}) {
const code = normalizeSquawk(squawk);
const reported = String(emergency ?? '')
.trim()
.toLowerCase();
const fromAdsb = Object.hasOwn(KINDS, reported);
const squawkKind = SQUAWK_KINDS[code] ?? null;
const kind =
fromAdsb && (KINDS[reported].severity === 'emergency' || !squawkKind)
? reported
: squawkKind;
if (!kind) return null;
return {
kind,
...KINDS[kind],
squawk: code,
source: SQUAWK_KINDS[code] === kind ? 'squawk' : 'ads-b',
};
}
/**
* How a summary may phrase the status without flattening it into an incident:
* quote the code when the Mode A squawk itself names the status, otherwise
* attribute it to the ADS-B emergency field.
* @param {{kind?: string, squawk?: string|null, source?: string}|null|undefined} status
* @returns {string|null} e.g. 'broadcasting squawk 7700 (general emergency)'
* or 'ADS-B reports general emergency'; null when there is no status.
*/
export function emergencyBroadcastPhrase(status) {
if (!status || !Object.hasOwn(KINDS, status.kind)) return null;
const label = KINDS[status.kind].label.toLowerCase();
return status.source === 'squawk' && status.squawk
? `broadcasting squawk ${status.squawk} (${label})`
: `ADS-B reports ${label}`;
}
/**
* True when the aircraft is broadcasting an emergency (not a priority status).
* @param {{severity?: string}|null|undefined} status
* @returns {boolean}
*/
export function isEmergency(status) {
return status?.severity === 'emergency';
}