i18n: fix the LGTM review findings and tidy the branch texts

- i18n scanner: lastError sink, no false sinks on defaults and ternaries
- shared src/i18n/marker.js for modules that only mark strings
- wind field messages move to wind/fields.js; CITY_POIS imports from cityPois.js
- drop review IDs, change history and stale references from comments and docs

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
German KhokhlovandClaude Opus 5.5 committed 2026-10-16 21:00:28 +04:00
1 parent 0ed05568f7
commit 20f4cf3763
178 files changed
+918 -948

No files matched your search

+2 -4
View File
@@ -7,6 +7,8 @@
* integer category (states index 17) and ADS-B emitter strings ("A1".."B1").
*/
import { msg } from '../i18n/marker.js';
const HELI = new Set([
'EC20',
'EC25',
@@ -438,10 +440,6 @@ export function classifyAircraft({ typeCode, category } = {}) {
return 'airliner';
}
// Portable flight records import this module, so it has no i18n: msg() only
// marks the labels, and the card translates them where it shows them.
const msg = (text) => text;
/** Readable name of each class, for cards and readouts (English data). */
export const AIRCRAFT_CLASS_LABELS = Object.freeze({
light: msg('Light aircraft'),
+2 -3
View File
@@ -13,12 +13,11 @@
* broadcast, which does not confirm an incident or its resolution.
*/
import { msg } from '../i18n/marker.js';
/** @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 = {
+4 -4
View File
@@ -249,7 +249,7 @@ export function createAisWatchdog(options = {}) {
if (!env.hasKey) {
const actions = terminateOwned('key-absent');
status = 'missing-key';
// i18n-ignore: server detail; the chip shows a translated reason (vessels/queries.js)
// i18n-ignore: server detail; layers/vessels/queries.js shows a translated reason
error = 'AISSTREAM_API_KEY is not set';
reconnectAttempt = 0;
keyFingerprint = null;
@@ -258,7 +258,7 @@ export function createAisWatchdog(options = {}) {
if (env.hasTransport === false) {
const actions = terminateOwned('no-transport');
status = 'unsupported';
// i18n-ignore: server detail; the chip shows a translated reason (vessels/queries.js)
// i18n-ignore: server detail; layers/vessels/queries.js shows a translated reason
error = 'Node WebSocket transport is unavailable';
return actions;
}
@@ -331,7 +331,7 @@ export function createAisWatchdog(options = {}) {
if (!silenceWatchArmed) return [];
const silentFor = monoNow - silenceSinceMono;
if (silentFor >= recycleAfterMs) {
// i18n-ignore: server detail; the chip shows a translated reason (vessels/queries.js)
// i18n-ignore: server detail; layers/vessels/queries.js shows a translated reason
error = `AISStream delivered no data for ${Math.round(silentFor / 1000)}s`;
const actions = terminateOwned('silent');
scheduleRetry('transport');
@@ -404,7 +404,7 @@ export function createAisWatchdog(options = {}) {
function onClose(eventGeneration) {
if (!ownsGeneration(eventGeneration)) return [];
release();
// i18n-ignore: server detail; the chip shows a translated reason (vessels/queries.js)
// i18n-ignore: server detail; layers/vessels/queries.js shows a translated reason
if (!error) error = 'AISStream websocket closed';
scheduleRetry('transport');
return [];
+2 -4
View File
@@ -40,10 +40,8 @@ const MEDIA_PRECONNECT_ORIGINS = Object.freeze({
facebook: Object.freeze(['https://www.facebook.com']),
});
// Authored English in public/events/bhote-koshi-2026/event.json. The pack is
// data and stays English; these known strings translate at display. Anything
// else (a newer pack, a credit, a source's own title) shows as authored. A
// string that is not known whole translates by its " · " parts.
// Known English from public/events/bhote-koshi-2026/event.json, translated at
// display (whole or by " · " parts). Anything else shows as authored.
const EVENT_PACK_TEXT = new Set([
// Field reports: our labels (credits after " · " stay) and their statuses.
msg('Rasuwagadhi witness video'),
+3 -5
View File
@@ -826,11 +826,9 @@ export function updateEvidenceFrame(
const EVIDENCE_SOURCE_MIN_FONT_SCALE = 0.8;
/**
* Set the font for the «{publisher} / OPEN» line and return the line so it
* fits `maxWidth`: a translation («ИСТОЧНИК YOUTUBE / ОТКРЫТЬ») is longer than
* the English and the portrait card cut its action word. A line that fits is
* drawn exactly as before; one that does not first shrinks its font (to 80%),
* then shortens the publisher — the «/ OPEN» action always stays whole.
* Set the font for the «{publisher} / OPEN» line and return a line that fits
* `maxWidth`. Translations run longer: first shrink the font (to 80%), then
* shorten the publisher; «/ OPEN» always stays whole.
* @param {CanvasRenderingContext2D} ctx
* @param {string} publisher Display publisher, already upper-cased.
* @param {string} font CSS font with a px size.
+3 -4
View File
@@ -1,7 +1,6 @@
// Proposal 5 / E4: the evidence frame's «{publisher} / OPEN» line is drawn on
// a canvas, so CSS cannot widen it; on the portrait card the Russian
// «ИСТОЧНИК YOUTUBE / ОТКРЫТЬ» lost its last letter. The line is measured and
// shrunk to fit, keeping the action word whole; English draws as before.
// The «{publisher} / OPEN» line is drawn on a canvas, so CSS cannot wrap it:
// a long translation shrinks to fit the portrait card, action word whole.
// English is drawn at full size.
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import test from 'node:test';
@@ -2,8 +2,8 @@ import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import test from 'node:test';
// K-30: the publisher on the evidence frame ("YouTube source") is pack text
// and is shown in the UI language; English stays "YOUTUBE SOURCE / OPEN".
// The publisher on the evidence frame ("YouTube source") is pack text shown
// in the UI language; English stays "YOUTUBE SOURCE / OPEN".
import { setLocale } from '../i18n/index.js';
import { createEvidenceFrame } from './bhoteKoshiEvent.js';
+2 -3
View File
@@ -52,9 +52,8 @@ const TRIGGER_ORBIT_DURATION_SEC = 2.8;
const TRIGGER_SOURCE_URL =
'https://earthquake.usgs.gov/earthquakes/eventpage/us7000tbwb/executive';
// Overview pin titles that are more than a place name translate at display,
// through the same entries the event panel uses. The data stays English and
// place names stay Latin; everything else shows as authored.
// Descriptive pin titles translate at display (same entries as the event
// panel); place names stay Latin, anything else shows as authored.
const PLACE_TITLE_TEXT = new Map(
[
msg('Collapse viewpoint'),
+1 -3
View File
@@ -2,9 +2,7 @@
import * as Cesium from 'cesium';
import { createImportImageBudget } from './geoImportImages.js';
import { createImportedPins } from './geoImportPins.js';
// Marks catalog text without importing i18n; the UI translates the message.
const msg = (text) => text;
import { msg } from '../i18n/marker.js';
/** Largest file accepted, in bytes. */
export const MAX_IMPORT_BYTES = 50 * 1024 * 1024;
+2 -4
View File
@@ -1,8 +1,6 @@
// Portable node/browser module (the HUD summary in loadingFeedback.js reads
// it too), so it marks its copy with a local msg() instead of importing i18n;
// installations/controls.js translates describeInstallationFeedback().
const msg = (text) => text;
import { msg } from '../i18n/marker.js';
// layers/installations/controls.js translates describeInstallationFeedback().
const REASONS = Object.freeze({
rate_limited: msg('Overpass rate-limited'),
timeout: msg('Overpass timed out'),
+5 -9
View File
@@ -1,14 +1,10 @@
import { msg } from '../i18n/marker.js';
/**
* The English names the data gives a military installation that has no
* mapped name. They stay English in data — the server provider, the AI tools
* and the voice context read the records — and the display translates them
* (installationTitle in src/layers/installations/classLabel.js and
* formatAwarenessLabel in src/data/militaryAwarenessEngine.js).
*
* The server, tools and source modules load this file, so it marks the names
* with a local msg() instead of importing i18n.
* English names for installations without a mapped name. Data keeps them
* English (server, AI tools, voice context); classLabel.js and
* militaryAwarenessEngine.js translate them.
*/
const msg = (text) => text;
/** Name of an unnamed feature, by installation class. */
export const UNNAMED_INSTALLATION_LABEL_BY_CLASS = Object.freeze({
+2 -2
View File
@@ -1,5 +1,5 @@
// Marks restore labels for i18n extraction; shareRestoration translates them.
// Kept local so this module stays free of the i18n import.
// Restore labels are marked with msg(); ui/shareRestoration.js translates them.
// Local, not i18n/marker.js: the token-ledger tests run this file in isolation.
const msg = (text) => text;
import { normalizeIdSwitches, patchRequestedIdSwitches } from './idSwitches.js';
import reservationRows from './layerStateTokenReservations.json' with { type: 'json' };
-7
View File
@@ -32,13 +32,6 @@ const CLASS_BY_MILITARY_TAG = {
const LABEL_BY_CLASS = UNNAMED_INSTALLATION_LABEL_BY_CLASS;
const UNNAMED_FALLBACK = UNNAMED_INSTALLATION_FALLBACK;
/** Every name humanizeInstallationClass gives a record of a class
* CLASS_BY_MILITARY_TAG can produce, for the display to translate. */
export const UNNAMED_INSTALLATION_NAMES = Object.freeze([
...Object.values(LABEL_BY_CLASS),
UNNAMED_FALLBACK,
]);
const MAX_FOOTPRINT_POINTS = 400;
/**
+5 -11
View File
@@ -1,15 +1,9 @@
/**
* The tool name to show for a pointer owner.
*
* Pointer-owner ids (src/data/inputOwnership.js) are stable internal
* identifiers — 'draw', 'recent-imagery-box' — and never belong on screen.
* This maps each to the English name of the tool, which the caller translates
* with translateKnown() where it is shown. Pure and stateless, so any package
* can carry it without a second copy of the pointer claim.
*/
import { msg } from '../i18n/marker.js';
// English marker for the i18n extractor; this module imports no i18n.
const msg = (text) => text;
/**
* Tool name for a pointer owner. Owner ids (inputOwnership.js) never go on
* screen; callers translate the English name with translateKnown().
*/
const POINTER_OWNER_NAMES = Object.freeze({
draw: msg('Draw'),
+6 -18
View File
@@ -20,53 +20,43 @@
import { t, msg } from '../i18n/index.js';
/**
* Class registry. `label` is the card/legend token, `color` the point color,
* `blurb` the plain-language gloss for the legend tooltip.
* @type {Readonly<Record<string, { label: string, color: string, blurb: string }>>}
* Class registry: the point color of each class. The card and legend text is
* in CLASS_DISPLAY_KEYS.
* @type {Readonly<Record<string, { color: string }>>}
*/
export const SATELLITE_CLASSES = Object.freeze({
station: Object.freeze({
label: 'STATION', // i18n-ignore: English token; shown through CLASS_DISPLAY_KEYS
// Warm white — "crewed", and the brightest class in the sky. Deliberately
// NOT amber: 40-48deg amber is the app's known-military convention
// (flights.js MIL_TINT / militaryFlights.js MIL_ICON_COLOR) and must not
// leak into another domain. Slightly warm so it also separates from the
// pure white the civilian air fleet draws in.
color: '#fff6e5',
blurb: 'Crewed stations and their visiting vehicles', // i18n-ignore: shown through CLASS_DISPLAY_KEYS
}),
nav: Object.freeze({
label: 'NAV', // i18n-ignore: English token; shown through CLASS_DISPLAY_KEYS
// Cyan — the strongest accent goes to the class users actually name
// ("how many GPS satellites are up?"). Takes over the slot the catch-all
// VISUAL group used to occupy, so this adds no new hue to the app.
color: '#4fd8ff',
blurb: 'GNSS navigation — GPS, GLONASS, Galileo', // i18n-ignore: shown through CLASS_DISPLAY_KEYS
}),
geo: Object.freeze({
label: 'GEO', // i18n-ignore: English token; shown through CLASS_DISPLAY_KEYS
// Violet, unchanged. Already the app's "space" semantic (the detection
// overlay paints the SAT tier #bda4ff), and the geostationary belt draws
// as one clean equatorial ring at globe scale.
color: '#c89bff',
blurb: 'Geostationary belt — comms and weather, fixed over the equator', // i18n-ignore: shown through CLASS_DISPLAY_KEYS
}),
visual: Object.freeze({
label: 'VISUAL', // i18n-ignore: English token; shown through CLASS_DISPLAY_KEYS
// Muted blue-gray. This is the catch-all bucket, so it gives up the bright
// cyan it used to hold and recedes behind the three classes that mean
// something. Still well clear of the dense shell by luminance.
color: '#9fb3c4',
blurb: 'Brightest naked-eye objects — CelesTrak visual group', // i18n-ignore: shown through CLASS_DISPLAY_KEYS
}),
comms: Object.freeze({
label: 'COMMS', // i18n-ignore: English token; shown through CLASS_DISPLAY_KEYS
// Dim slate — thousands of these appear in DENSE mode. They must read as
// texture behind the core catalog, never compete with it. Rec.601 luma
// ~0.40 against VISUAL's ~0.69 keeps the two separable even when the
// NVG/FLIR shaders collapse the scene to a single channel.
color: '#54697f',
blurb: 'Broadband constellation shell — shown only in DENSE mode', // i18n-ignore: shown through CLASS_DISPLAY_KEYS
}),
});
@@ -79,9 +69,7 @@ export const SATELLITE_CLASS_ORDER = Object.freeze([
'comms',
]);
// Display keys for the class table above: `label`/`blurb` stay the English
// source-of-truth tokens (tests read them), while the card and legend accessors
// translate through these keys at the presentation boundary.
// What the card and legend show for each class.
const CLASS_DISPLAY_KEYS = Object.freeze({
station: Object.freeze({
label: msg('STATION'),
@@ -164,7 +152,8 @@ export function satelliteClassColor(group) {
* The ISS is always a STATION regardless of which group ingested it — see
* satelliteClassOf, which owns that rule for every consumer.
* @param {string|undefined|null} group CelesTrak group tag.
* @param {{ isIss?: boolean }} [options] Whether this is the ISS.
* @param {{ isIss?: boolean, localize?: boolean }} [options] isIss: the ISS;
* localize: false keeps the English label (data for the AI tools).
* @returns {string} Display label.
*/
export function satelliteClassLabel(
@@ -172,7 +161,6 @@ export function satelliteClassLabel(
{ isIss = false, localize = true } = {},
) {
const { klass, subtype } = satelliteClassOf(group, { isIss });
// localize: false keeps the English label for data the AI tools query.
const label = CLASS_DISPLAY_KEYS[klass].label;
const base = localize ? t(label) : label;
return subtype ? `${base} · ${subtype}` : base;
+2 -4
View File
@@ -48,9 +48,8 @@ function run(locale) {
return details;
}
test('K-32 es: tracked ISS card', () => {
test('es: tracked ISS card is localized', () => {
const details = run('es');
console.log('es details:', JSON.stringify(details));
assert.equal(details[0], 'ESTACIÓN · ISS');
assert.doesNotMatch(
details[2],
@@ -59,9 +58,8 @@ test('K-32 es: tracked ISS card', () => {
);
});
test('K-32 ru: tracked ISS card', () => {
test('ru: tracked ISS card is localized', () => {
const details = run('ru');
console.log('ru details:', JSON.stringify(details));
assert.equal(details[0], 'СТАНЦИЯ · ISS');
assert.match(details[1], /км/, 'ru glossary: km → км');
assert.doesNotMatch(
+2 -3
View File
@@ -68,9 +68,8 @@ const NUMERIC_TYPE_FAMILIES = {
9: 'OTHER',
};
// Display keys for the normalized English type tokens above. The tokens stay
// machine values — TYPE_STYLES matches on them and tests pin them — so card
// text translates only through this map at the presentation boundary.
// Card text for the type tokens above. The tokens stay English: TYPE_STYLES
// and tests match on them.
const TYPE_DISPLAY_KEYS = Object.freeze({
FISHING: msg('FISHING'),
TOWING: msg('TOWING'),
+1 -1
View File
@@ -1,4 +1,4 @@
// K-12: a translated vessel type is never cut mid-word on the card.
// A translated vessel type is never cut mid-word on the card.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import * as Cesium from 'cesium';