feat(i18n): presentation extraction — t() call sites, data-i18n templates, locale selector

Route every application-owned presentation string through the i18n
core: t() call sites across the app/layers/ui/data/voice surfaces,
data-i18n* attributes in the src/ui/templates/*.html markup (applied at
boot by applyDocumentTranslations from src/standalone/application.js),
and the dock locale selector (runtime-rendered from the offered pair —
a single EN button while only English ships).

Rebased onto current main, where the extraction follows the module
splits: the shell wiring now lands in the owner modules
(panelChrome, visualSettings, locationNavigation, shareRestoration,
shellFeedback, applicationShell) and the voice status corpus in the
realtime* modules; scene status lives in director.js +
playbackAdapter.js.

Modules shared with the node runtime (loadingFeedback,
installationFeedback, regionalModel, keySetupCore, vessels ingestion)
stay environment-neutral English and localize at their browser render
sites (chip maps in shellFeedback/installations controls, the weather
table in cockpitBriefing, keySetupRequirementEnvVars compositions), so
the package-boundary graph stays clean; the browser boundary groups
now own the i18n modules they reach (scripts/package-boundaries.json).
This commit is contained in:
Meir Michanie committed 2026-10-01 12:41:20 +02:00
1 parent bde1e430ef
commit 284c4d4601
98 files changed
+3474 -1625

No files matched your search

+42 -21
View File
@@ -1,4 +1,5 @@
/** Instrument values, routes and vision controls for the Cockpit controller. */
import { t } from '../i18n/index.js';
import {
COCKPIT_VISION_MODES,
normalizeCockpitVisionMode,
@@ -31,7 +32,10 @@ export function updateHud(
const heading = normalizeHeading(this.heading ?? info.track ?? 0);
if (this.callsign) {
this.callsign.textContent =
info.callsign || info.registration || info.icao24 || 'AIRCRAFT';
info.callsign ||
info.registration ||
info.icao24 ||
t('cockpit.hud.fallbackCallsign');
}
const speedKt = Number.isFinite(info.velocityMps)
? info.velocityMps * 1.94384
@@ -126,14 +130,22 @@ export function updateHud(
this.position.textContent = `${lat} · ${lon}`;
}
if (this.aircraftMeta) {
// State-sibling keys at the writer: each feed state translates
// independently so no state can leak English under other locales.
const feedState = this.surfaceAcquiring
? 'ACQUIRING SURFACE'
? t('cockpit.hud.metaFeedAcquiringSurface')
: this.surfaceFallback
? 'SURFACE FALLBACK'
? t('cockpit.hud.metaFeedSurfaceFallback')
: info.stale
? 'STALE FEED'
: 'LIVE TRACK';
this.aircraftMeta.textContent = `${info.layerId === 'military' ? 'MILITARY' : 'COMMERCIAL'} · ${feedState} · COURSE ALIGNED`;
? t('cockpit.hud.metaFeedStale')
: t('cockpit.hud.metaFeedLive');
this.aircraftMeta.textContent = t('cockpit.hud.aircraftMetaTemplate', {
aircraftClass:
info.layerId === 'military'
? t('cockpit.hud.metaClassMilitary')
: t('cockpit.hud.metaClassCommercial'),
feedState,
});
}
this.updateRoute(info);
if (
@@ -158,13 +170,14 @@ export function updateRoute(info) {
const validDestination =
Number.isFinite(destination?.lat) && Number.isFinite(destination?.lon);
const routeLabel = (airport) =>
[airport?.code, airport?.name].filter(Boolean).join(' · ') || 'UNKNOWN';
[airport?.code, airport?.name].filter(Boolean).join(' · ') ||
t('cockpit.route.unknownEndpoint');
if (this.routeFrom) this.routeFrom.textContent = routeLabel(origin);
if (this.routeTo) this.routeTo.textContent = routeLabel(destination);
if (this.routeStatus) {
this.routeStatus.textContent = validDestination
? 'ARROW · ESTIMATED DIRECTION'
: 'ROUTE DATA UNAVAILABLE';
? t('cockpit.route.statusArrowEstimated')
: t('cockpit.route.statusUnavailable');
}
if (this.route) this.route.hidden = !origin && !destination;
if (
@@ -198,7 +211,9 @@ export function updateRoute(info) {
);
}
if (this.routeDirectionLabel) {
this.routeDirectionLabel.textContent = `DEST ${String(Math.round(destinationBearing)).padStart(3, '0')}°`;
this.routeDirectionLabel.textContent = t('cockpit.route.directionLabel', {
bearing: `${String(Math.round(destinationBearing)).padStart(3, '0')}°`,
});
}
}
@@ -206,12 +221,16 @@ export function syncWeatherToggle(enabled) {
if (!this.weatherToggle) return;
const active = !!enabled;
this.weatherToggle.setAttribute('aria-pressed', String(active));
this.weatherToggle.setAttribute(
'aria-label',
`${active ? 'Disable' : 'Enable'} cockpit weather effects`,
);
this.weatherToggle.title = `${active ? 'Disable' : 'Enable'} cockpit weather effects`;
if (this.weatherState) this.weatherState.textContent = active ? 'ON' : 'OFF';
const weatherLabel = active
? t('cockpit.context.weatherDisableAriaLabel')
: t('cockpit.context.weatherEnableAriaLabel');
this.weatherToggle.setAttribute('aria-label', weatherLabel);
this.weatherToggle.title = weatherLabel;
if (this.weatherState) {
this.weatherState.textContent = active
? t('cockpit.context.weatherStateOn')
: t('cockpit.context.weatherStateOff');
}
}
export function setVisionMode(mode, { revealParameters = false } = {}) {
@@ -230,17 +249,19 @@ export function setVisionMode(mode, { revealParameters = false } = {}) {
const names = {
optical: inherited,
crt: 'CRT',
nvg: 'Night vision',
thermal: 'Thermal',
noir: 'Noir',
nvg: t('cockpit.vision.styleNameNightVision'),
thermal: t('cockpit.vision.styleNameThermal'),
noir: t('cockpit.vision.styleNameNoir'),
};
if (this.visionCurrent) {
this.visionCurrent.dataset.cockpitVision = next;
this.visionCurrent.setAttribute(
'aria-label',
`Current cockpit vision style: ${names[next]}. Activate for next style.`,
t('cockpit.vision.currentAriaTemplate', { style: names[next] }),
);
this.visionCurrent.title = `Current style: ${names[next]} — click for next`;
this.visionCurrent.title = t('cockpit.vision.currentTitleTemplate', {
style: names[next],
});
}
if (this.visionCurrentLabel)
this.visionCurrentLabel.textContent = labels[next];