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:
German KhokhlovandClaude Opus 5.5 committed 2026-10-15 23:49:52 +04:00
1 parent 8029747b7e
commit 00704ddc89
212 files changed
+9871 -909

No files matched your search

+16 -9
View File
@@ -60,20 +60,22 @@ export function createQueries({
if (status === 'auth-failed') {
// Actionable, not a countdown: retrying cannot fix a rejected credential,
// so the chip asks the operator to do the one thing that can.
return 'API key rejected — check AISSTREAM_API_KEY';
return t('API key rejected — check AISSTREAM_API_KEY');
}
if (status === 'stale') {
const silentSec = Math.round(Number(payload?.silentForMs) / 1000);
return Number.isFinite(silentSec) && silentSec > 0
? `feed silent ${silentSec}s — no AIS data`
: 'feed silent — no AIS data';
? t('feed silent {seconds}s — no AIS data', { seconds: silentSec })
: t('feed silent — no AIS data');
}
const attempt = Number(payload?.reconnectAttempt);
const suffix =
Number.isFinite(attempt) && attempt >= 1 ? ` (attempt ${attempt})` : '';
if (Number.isFinite(attempt) && attempt >= 1)
return status === 'down'
? t('feed down — retrying slowly (attempt {attempt})', { attempt })
: t('reconnecting to feed… (attempt {attempt})', { attempt });
return status === 'down'
? `feed down — retrying slowly${suffix}`
: `reconnecting to feed…${suffix}`;
? t('feed down — retrying slowly')
: t('reconnecting to feed…');
}
/**
@@ -106,7 +108,8 @@ export function createQueries({
typeof payload.error === 'string' && payload.error.trim()
? payload.error.trim()
: '';
const reason = AIS_STATUS_REASON[status] || 'feed unavailable';
// The reasons are English in policy.js (msg) and shown in the UI language.
const reason = t(AIS_STATUS_REASON[status] || msg('feed unavailable'));
return detail && !AIS_STATUS_REASON[status]
? `${reason} (${detail})`
: reason;
@@ -468,7 +471,7 @@ export function createQueries({
* @param {Object} [options={}] - Options from the detection system.
* @param {number} [options.maxCount] - Maximum objects to return (defaults to all).
* @param {number} [options.seed] - Seed offset for stride sampling.
* @returns {Array<{position: Cesium.Cartesian3, id: string, type: string, skipLabel: boolean}>}
* @returns {Array<{position: Cesium.Cartesian3, id: string, type: string, skipLabel: boolean, hideLabel: boolean}>}
*/
getDetectableObjects(options = {}) {
if (
@@ -489,6 +492,7 @@ export function createQueries({
const start = seed % stride;
const selected = state.selectedRecord;
const carded = state.cardedRecords;
const result = [];
for (let idx = 0; idx < records.length; idx += 1) {
if ((idx - start) % stride !== 0) continue;
@@ -503,6 +507,9 @@ export function createQueries({
id: record.name || record.mmsi || 'VESSEL',
type: 'SEA',
skipLabel: record === selected,
// An open ambient card already names the vessel with its speed and
// course; a detection label under it would only cover them.
hideLabel: carded?.has(record) === true,
klass: record.type
? String(record.type).toUpperCase().slice(0, 14)
: undefined,