mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
fix(i18n): map cards and wind readout in the UI language; final review wording
A final review of both PDFs found text the earlier checks missed:
- Map cards: fire confidence (high/nominal/low) and age (24h), vessel
types (TOWING, TANKER) and installation classes (BASE, AIRFIELD) showed
in English. They now go through the catalog; the data stays English.
- The wind readout kept "10-14 14:00 UTC" and "29.93°N 97.74°W"; it now
uses the locale's date, decimal separator and hemisphere letters.
- Catalog: one term per concept (label/подпись, basemap/подложка,
PROVIDER CONTROLS, HEARD, zoom in, REGIONAL NEWS), calques and broken
grammar in the scene editor, provider settings and Bhote Koshi notes,
and Spanish wording that read wrong ("restringidas por el proveedor",
"Incluye … no se incluyen", "justo ahora", "Sal").
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
eed05721f2
commit
8c844d7741
11 files changed
+251
-134
No files matched your search
@@ -332,11 +332,11 @@ export function createModel({
|
||||
function buildSelectedFireCard(fire, nowMs) {
|
||||
const meta = [
|
||||
t('{value} conf', {
|
||||
value: confidenceBucket(fire.confidence),
|
||||
value: confidenceLabel(confidenceBucket(fire.confidence)),
|
||||
}),
|
||||
];
|
||||
if (fire.acqMs > 0) {
|
||||
const age = formatAge(nowMs - fire.acqMs);
|
||||
const age = formatAgeLabel(nowMs - fire.acqMs);
|
||||
if (age) meta.push(t('{value} ago', { value: age }));
|
||||
}
|
||||
const sat = satelliteShortName(fire.satellite);
|
||||
@@ -374,9 +374,9 @@ export function createModel({
|
||||
|
||||
function buildFireCard(candidate, nowMs) {
|
||||
const fire = candidate.fire;
|
||||
const meta = [confidenceBucket(fire.confidence)];
|
||||
const meta = [confidenceLabel(confidenceBucket(fire.confidence))];
|
||||
if (fire.acqMs > 0) {
|
||||
const age = formatAge(nowMs - fire.acqMs);
|
||||
const age = formatAgeLabel(nowMs - fire.acqMs);
|
||||
if (age) meta.push(age);
|
||||
}
|
||||
const sat = satelliteShortName(fire.satellite) || fire.sensor;
|
||||
@@ -409,7 +409,7 @@ export function createModel({
|
||||
const noun = cell.count === 1 ? t('FIRE') : t('FIRES');
|
||||
const parts = [t('max {value} MW', { value: formatFrp(cell.maxFrp) })];
|
||||
if (cell.newestAcqMs > 0) {
|
||||
const age = formatAge(nowMs - cell.newestAcqMs);
|
||||
const age = formatAgeLabel(nowMs - cell.newestAcqMs);
|
||||
if (age) parts.push(t('new {value}', { value: age }));
|
||||
}
|
||||
return {
|
||||
@@ -503,6 +503,32 @@ export function createModel({
|
||||
return t('{count}h ago', { count: Math.round(minutes / 60) });
|
||||
}
|
||||
|
||||
/** A confidence bucket as a card shows it, in the UI language. */
|
||||
|
||||
function confidenceLabel(bucket) {
|
||||
switch (bucket) {
|
||||
case 'high':
|
||||
return t('high', { context: 'fire confidence' });
|
||||
case 'nominal':
|
||||
return t('nominal', { context: 'fire confidence' });
|
||||
case 'low':
|
||||
return t('low', { context: 'fire confidence' });
|
||||
default:
|
||||
return bucket;
|
||||
}
|
||||
}
|
||||
|
||||
/** formatAge for a card: "<1h", "24h", "3d" in the UI language ("24 ч"). */
|
||||
|
||||
function formatAgeLabel(deltaMs) {
|
||||
if (!Number.isFinite(deltaMs) || deltaMs < 0) return '';
|
||||
const hours = deltaMs / 3600000;
|
||||
if (hours < 1) return t('<1h', { context: 'age' });
|
||||
if (hours < 48)
|
||||
return t('{count}h', { context: 'age', count: Math.round(hours) });
|
||||
return t('{count}d', { context: 'age', count: Math.round(hours / 24) });
|
||||
}
|
||||
|
||||
/** Normalized 0..1 confidence → low/nominal/high display bucket. */
|
||||
|
||||
function confidenceBucket(confidence) {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { t } from '../../i18n/index.js';
|
||||
|
||||
/**
|
||||
* An installation class as a map card shows it: AIRFIELD, NAVAL BASE…, in
|
||||
* the UI language. The class itself stays an English identifier in data.
|
||||
* @param {string} klass
|
||||
* @returns {string}
|
||||
*/
|
||||
export function installationClassLabel(klass) {
|
||||
const key = String(klass || 'installation');
|
||||
switch (key) {
|
||||
case 'airfield':
|
||||
return t('AIRFIELD', { context: 'installation class' });
|
||||
case 'naval_base':
|
||||
return t('NAVAL BASE', { context: 'installation class' });
|
||||
case 'range':
|
||||
return t('RANGE', { context: 'installation class' });
|
||||
case 'military_land':
|
||||
return t('MILITARY LAND', { context: 'installation class' });
|
||||
case 'base':
|
||||
return t('BASE', { context: 'installation class' });
|
||||
case 'places_candidate':
|
||||
return t('PLACES CANDIDATE', { context: 'installation class' });
|
||||
case 'installation':
|
||||
return t('INSTALLATION', { context: 'installation class' });
|
||||
default:
|
||||
return key.replaceAll('_', ' ').toUpperCase();
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { installationClassLabel } from './classLabel.js';
|
||||
import * as Cesium from 'cesium';
|
||||
import {
|
||||
MILITARY_LABEL_CAP,
|
||||
@@ -177,9 +178,7 @@ export function createNamedMarkers({ state, services, parts, overlayHost }) {
|
||||
marker.entry.selected = record.id === state.selectedId;
|
||||
marker.entry.details = marker.entry.selected
|
||||
? [
|
||||
String(record.class || 'installation')
|
||||
.replaceAll('_', ' ')
|
||||
.toUpperCase(),
|
||||
installationClassLabel(record.class),
|
||||
...(record.memberNames || []).slice(0, 3),
|
||||
]
|
||||
: [];
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { installationClassLabel } from './classLabel.js';
|
||||
import * as Cesium from 'cesium';
|
||||
import { t } from '../../i18n/index.js';
|
||||
import { LAYER_ID, MAX_RENDERED, COLOR_BY_CLASS } from './policy.js';
|
||||
@@ -273,9 +274,7 @@ export function createRendering({
|
||||
entity.gevLabelModel = {
|
||||
title: record.name || t('MAPPED INSTALLATION'),
|
||||
details: [
|
||||
String(record.class || 'installation')
|
||||
.replaceAll('_', ' ')
|
||||
.toUpperCase(),
|
||||
installationClassLabel(record.class),
|
||||
...(record.memberNames || []).slice(0, 3),
|
||||
],
|
||||
accent: COLOR_BY_CLASS[record.class] || '#9ca6b0',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
accentForVesselType,
|
||||
normalizeVesselType,
|
||||
displayVesselType,
|
||||
} from '../../data/vesselLabels.js';
|
||||
import { formatNumber, t } from '../../i18n/index.js';
|
||||
|
||||
@@ -130,7 +130,7 @@ export function createCards({
|
||||
/** Uppercased, card-width-bounded AIS type (empty string when unknown). */
|
||||
|
||||
function vesselTypeShort(record) {
|
||||
return normalizeVesselType(record.type).toUpperCase().slice(0, 14);
|
||||
return displayVesselType(record.type).toUpperCase().slice(0, 14);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { formatWindSpeed, WIND_UNITS } from './inspection.js';
|
||||
import { t } from '../../i18n/index.js';
|
||||
import { formatNumber, formatUtcDayMonth, t } from '../../i18n/index.js';
|
||||
import { formatQuantity, unitLabel } from '../../ui/unitLabels.js';
|
||||
|
||||
/** Reformat a captured sample; changing units never samples the map again. */
|
||||
@@ -62,12 +62,46 @@ function windText(reading) {
|
||||
: `${speed}${t(' from {direction}', { direction: t(reading.from) })}`;
|
||||
}
|
||||
|
||||
/** "29.93°N · 97.74°W" from the English reading, in the active language. */
|
||||
function displayCoordinates(coordinates) {
|
||||
const match = /^(\d+(?:\.\d+)?)°([NS]) · (\d+(?:\.\d+)?)°([EW])$/.exec(
|
||||
coordinates,
|
||||
);
|
||||
if (!match) return coordinates.replace(' · ', ' ');
|
||||
const part = (value, hemisphere) => {
|
||||
const decimals = value.split('.')[1]?.length || 0;
|
||||
const amount = formatNumber(Number(value), {
|
||||
minimumFractionDigits: decimals,
|
||||
maximumFractionDigits: decimals,
|
||||
useGrouping: false,
|
||||
});
|
||||
switch (hemisphere) {
|
||||
case 'N':
|
||||
return t('{value}°N', { value: amount });
|
||||
case 'S':
|
||||
return t('{value}°S', { value: amount });
|
||||
case 'E':
|
||||
return t('{value}°E', { value: amount });
|
||||
default:
|
||||
return t('{value}°W', { value: amount });
|
||||
}
|
||||
};
|
||||
return `${part(match[1], match[2])} ${part(match[3], match[4])}`;
|
||||
}
|
||||
|
||||
/** "2026-10-14 14:00 UTC" from the reading, as "10-14 14:00 UTC" / "14.10 …". */
|
||||
function displayValidTime(validTime) {
|
||||
const match = /^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}) UTC$/.exec(validTime || '');
|
||||
if (!match) return validTime?.replace(/^\d{4}-/, '');
|
||||
return `${formatUtcDayMonth(match[1])} ${match[2]} UTC`;
|
||||
}
|
||||
|
||||
/** Portable result block consumed by the WEATHER card. */
|
||||
export function windReadingResult(reading) {
|
||||
const coordinates =
|
||||
reading.coordinates === 'No surface reading'
|
||||
? t('No surface reading')
|
||||
: reading.coordinates.replace(' · ', ' ');
|
||||
: displayCoordinates(reading.coordinates);
|
||||
return {
|
||||
id: 'reading',
|
||||
label: t('WIND AT {coordinates}', { coordinates }),
|
||||
@@ -80,7 +114,7 @@ export function windReadingResult(reading) {
|
||||
id: 'meta',
|
||||
text: t('{model} · valid {time}', {
|
||||
model: reading.model,
|
||||
time: reading.validTime?.replace(/^\d{4}-/, ''),
|
||||
time: displayValidTime(reading.validTime),
|
||||
}),
|
||||
},
|
||||
...(reading.scalarValue
|
||||
|
||||
Reference in new issue
Block a user