mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
i18n: fix review findings and polish ru/es strings
Fixes the 51 P0–P2 findings from the full review of the i18n branch and the issues found while checking every new string on screen in ru and es. - Translate errors and statuses that still reached ru/es in English: feed and tile errors, provider statuses, the ALPR chip, the traffic mode chip, voice plan step labels, fire feed values, receiver bands, scene save reasons and the Cesium attribution link. - Show preset city names in the UI language (src/ui/placeNames.js); data and everything sent to the model stay English. - Share layer names between the layer panel, HUD and voice card (src/ui/layerNames.js). - Keep long ru/es strings whole: voice card error lines, analyst titles, the voice tier status, the Nepal evidence button, the ALPR retry chip. English layout is unchanged. - Ask before a language switch drops unsaved work. - Catalog fixes: glossary terms (пуск, пресет), gender agreement and consistent "peticiones" in es, fewer calques in both languages. - Fix English text that was itself wrong (e.g. "Exit cockpit to fly to a weather", raw tool ids in voice plan steps). Catalogs: es/ru 2535 messages each, complete. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
8029747b7e
commit
00704ddc89
212 files changed
+9871
-909
No files matched your search
@@ -0,0 +1,134 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { createCyclonesLayer } from './index.js';
|
||||
import { setLocale, t } from '../../i18n/index.js';
|
||||
|
||||
// The exact string the server sends (server/providers/cyclones.js:8).
|
||||
const SERVER_COVERAGE =
|
||||
'Atlantic and eastern/central North Pacific; not worldwide cyclone coverage.';
|
||||
const time = '2026-09-16T03:00:00.000Z';
|
||||
const storm = {
|
||||
id: 'ep152026',
|
||||
name: 'Fifteen-E',
|
||||
classification: 'PTC',
|
||||
basin: 'EP',
|
||||
position: { longitude: -125.8, latitude: 15.5 },
|
||||
positionAt: time,
|
||||
issuedAt: time,
|
||||
advisoryNumber: '10',
|
||||
windKt: 25,
|
||||
pressureHpa: 1006,
|
||||
geometryStatus: 'pending',
|
||||
geometryAdvisoryNumber: null,
|
||||
forecastPoints: [],
|
||||
track: null,
|
||||
cone: null,
|
||||
advisoryUrl: 'https://www.nhc.noaa.gov/text/MIATCMEP5.shtml',
|
||||
};
|
||||
const snapshot = (storms, extra = {}) => ({
|
||||
schemaVersion: 1,
|
||||
storms,
|
||||
unavailable: false,
|
||||
stale: false,
|
||||
fetchedAt: Date.parse(time),
|
||||
coverage: SERVER_COVERAGE,
|
||||
reason: null,
|
||||
...extra,
|
||||
});
|
||||
function harness(feed) {
|
||||
const rendering = {
|
||||
async setSnapshot() {
|
||||
return true;
|
||||
},
|
||||
setSelection() {},
|
||||
getFocusSphere: () => ({ radius: 500000 }),
|
||||
clear() {},
|
||||
destroy() {},
|
||||
getDiagnostics: () => ({ entities: 0, timerActive: false }),
|
||||
};
|
||||
const layer = createCyclonesLayer({
|
||||
feed,
|
||||
createRendering: () => rendering,
|
||||
matchMedia: () => ({ matches: false }),
|
||||
openLink: () => {},
|
||||
});
|
||||
layer.init({ camera: { flyToBoundingSphere() {} } });
|
||||
layer.attachShellServices({ runNavigation: (fn) => fn() });
|
||||
return layer;
|
||||
}
|
||||
|
||||
const translated = () => t(SERVER_COVERAGE);
|
||||
|
||||
test('K-35a: es, server snapshot without storms -> coverage line in Spanish', async () => {
|
||||
setLocale('es');
|
||||
try {
|
||||
assert.notEqual(translated(), SERVER_COVERAGE, 'es.json has a translation');
|
||||
const layer = harness({ getSnapshot: async () => snapshot([]) });
|
||||
layer.enable();
|
||||
await layer.update();
|
||||
const info = layer.getRowControls().info;
|
||||
assert.ok(
|
||||
info.endsWith(translated()),
|
||||
`expected Spanish coverage, got:\n${info}`,
|
||||
);
|
||||
layer.destroy();
|
||||
} finally {
|
||||
setLocale('en');
|
||||
}
|
||||
});
|
||||
|
||||
test('K-35b: es, server snapshot with a storm -> coverage line in Spanish', async () => {
|
||||
setLocale('es');
|
||||
try {
|
||||
const layer = harness({ getSnapshot: async () => snapshot([storm]) });
|
||||
layer.enable();
|
||||
await layer.update();
|
||||
const info = layer.getRowControls().info;
|
||||
assert.ok(
|
||||
info.endsWith(translated()),
|
||||
`expected Spanish coverage, got:\n${info}`,
|
||||
);
|
||||
layer.destroy();
|
||||
} finally {
|
||||
setLocale('en');
|
||||
}
|
||||
});
|
||||
|
||||
test('K-35c: es, unavailable without reason -> fallback status not the English literal', async () => {
|
||||
setLocale('es');
|
||||
try {
|
||||
const layer = harness({
|
||||
getSnapshot: async () =>
|
||||
snapshot([], { unavailable: true, reason: null }),
|
||||
});
|
||||
layer.enable();
|
||||
await layer.update();
|
||||
const status = layer.getRowControls().summary.status;
|
||||
assert.notEqual(
|
||||
status,
|
||||
'Cyclone advisories unavailable',
|
||||
`fallback shown in English: ${status}`,
|
||||
);
|
||||
layer.destroy();
|
||||
} finally {
|
||||
setLocale('en');
|
||||
}
|
||||
});
|
||||
|
||||
test('K-35: en coverage and fallback status read as before', async () => {
|
||||
const layer = harness({ getSnapshot: async () => snapshot([]) });
|
||||
layer.enable();
|
||||
await layer.update();
|
||||
assert.ok(layer.getRowControls().info.endsWith(SERVER_COVERAGE));
|
||||
layer.destroy();
|
||||
const down = harness({
|
||||
getSnapshot: async () => snapshot([], { unavailable: true, reason: null }),
|
||||
});
|
||||
down.enable();
|
||||
await down.update();
|
||||
assert.equal(
|
||||
down.getRowControls().summary.status,
|
||||
'Cyclone advisories unavailable',
|
||||
);
|
||||
down.destroy();
|
||||
});
|
||||
@@ -14,7 +14,13 @@ import {
|
||||
CYCLONE_OVERLAY_SOURCE_ID,
|
||||
cycloneStormIdFromEntryId,
|
||||
} from './labels.js';
|
||||
import { formatUtcDayMonth, t, tn, msg } from '../../i18n/index.js';
|
||||
import {
|
||||
formatUtcDayMonth,
|
||||
t,
|
||||
tn,
|
||||
msg,
|
||||
translateKnown,
|
||||
} from '../../i18n/index.js';
|
||||
import { formatQuantity } from '../../ui/unitLabels.js';
|
||||
|
||||
// "10-12 15:00 UTC" never breaks across lines: no-break spaces, and a word
|
||||
@@ -26,6 +32,15 @@ const utc = (value) =>
|
||||
const COVERAGE = msg(
|
||||
'Atlantic and eastern/central North Pacific; not worldwide cyclone coverage.',
|
||||
);
|
||||
// The server sends the same English coverage (server/providers/cyclones.js);
|
||||
// data stays English and is translated here, where it is shown.
|
||||
const coverageText = (coverage) =>
|
||||
!coverage || coverage === COVERAGE ? t(COVERAGE) : translateKnown(coverage);
|
||||
const ADVISORIES_UNAVAILABLE = msg('Cyclone advisories unavailable');
|
||||
// Reason the server gives with an unavailable snapshot, translated when shown.
|
||||
const SERVER_REASONS = new Set([msg('Cyclone data unavailable')]);
|
||||
const errorText = (error) =>
|
||||
error && (SERVER_REASONS.has(error) ? t(error) : translateKnown(error));
|
||||
const CLASSIFICATION_NAMES = Object.freeze({
|
||||
PTC: msg('Potential tropical cyclone'),
|
||||
HU: msg('Hurricane'),
|
||||
@@ -238,7 +253,7 @@ export function createCyclonesLayer({
|
||||
snapshot = next;
|
||||
selectedId = null;
|
||||
++navigationGeneration;
|
||||
error = next.reason || 'Cyclone advisories unavailable';
|
||||
error = next.reason || ADVISORIES_UNAVAILABLE;
|
||||
return true;
|
||||
}
|
||||
const applied = await rendering.setSnapshot(next, {
|
||||
@@ -263,7 +278,7 @@ export function createCyclonesLayer({
|
||||
return true;
|
||||
} catch (cause) {
|
||||
if (controller.signal.aborted || request !== controller) return false;
|
||||
error = cause?.message || 'Cyclone advisories unavailable';
|
||||
error = cause?.message || ADVISORIES_UNAVAILABLE;
|
||||
// Failed acquisition has no bounded last-good age guarantee at this layer.
|
||||
rendering?.clear();
|
||||
snapshot = null;
|
||||
@@ -332,7 +347,7 @@ export function createCyclonesLayer({
|
||||
})
|
||||
: t('Track/cone unavailable'));
|
||||
const status =
|
||||
error ||
|
||||
errorText(error) ||
|
||||
(snapshot?.stale
|
||||
? t('Cached advisory · stale source')
|
||||
: loading
|
||||
@@ -435,8 +450,8 @@ export function createCyclonesLayer({
|
||||
]
|
||||
: [],
|
||||
info: storm
|
||||
? `${detail}\n${position}\n${intensity}\n${geometry}${status && status !== geometry ? '\n' + status : ''}\n${snapshot.coverage}`
|
||||
: `${detail}${status ? '\n' + status : ''}\n${snapshot?.coverage || t(COVERAGE)}`,
|
||||
? `${detail}\n${position}\n${intensity}\n${geometry}${status && status !== geometry ? '\n' + status : ''}\n${coverageText(snapshot.coverage)}`
|
||||
: `${detail}${status ? '\n' + status : ''}\n${coverageText(snapshot?.coverage)}`,
|
||||
infoTitle: t(
|
||||
'Select a storm on the map, or choose a storm in the list to select it and move the camera. Click empty map space to clear the selection. NOAA NHC/CPHC advisory context. The cone describes forecast center-track uncertainty, not storm size or the full hazard area. Forecast point labels are source lead hours, not times computed from advisory issuance. Geometry follows the surface; height is not weather altitude. Consult the official advisory.',
|
||||
),
|
||||
|
||||
Reference in new issue
Block a user