mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
fix(i18n): keep values the AI tools read in English
- Satellite class in analyst records and voice payloads stays English
("STATION", "COMMS"), so voice filters on the class work in every
language; cards and the legend still show the translated label.
- Layer source names (OSM routing, ALPR sources) stay English in layer
state; the layer row translates them.
- New translateKnown(): catalog text when there is an entry, the given text
otherwise, with no warning. The layer row uses it for loading labels and
errors, Recent Imagery for its notices.
- Mark the remaining Recent Imagery and wind messages.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
0c647a791b
commit
2c74057b5e
13 files changed
+60
-20
No files matched your search
@@ -167,9 +167,14 @@ export function satelliteClassColor(group) {
|
||||
* @param {{ isIss?: boolean }} [options] Whether this is the ISS.
|
||||
* @returns {string} Display label.
|
||||
*/
|
||||
export function satelliteClassLabel(group, { isIss = false } = {}) {
|
||||
export function satelliteClassLabel(
|
||||
group,
|
||||
{ isIss = false, localize = true } = {},
|
||||
) {
|
||||
const { klass, subtype } = satelliteClassOf(group, { isIss });
|
||||
const base = t(CLASS_DISPLAY_KEYS[klass].label);
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -109,6 +109,18 @@ export function tn(count, one, other, values) {
|
||||
return interpolate(pattern, { ...values, count });
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate text that may be ours or may come from a provider: a catalog
|
||||
* entry is used when there is one, anything else shows as given, without a
|
||||
* missing-translation warning.
|
||||
*/
|
||||
export function translateKnown(text) {
|
||||
const catalog = CATALOGS[currentLocale];
|
||||
if (!catalog || typeof text !== 'string') return text;
|
||||
const entry = catalog[text];
|
||||
return typeof entry === 'string' ? entry : text;
|
||||
}
|
||||
|
||||
/** Mark English text that is stored in data and translated later by t(). */
|
||||
export function msg(text) {
|
||||
return text;
|
||||
|
||||
@@ -2030,5 +2030,7 @@
|
||||
"subject": "sujeto",
|
||||
"{value}m": "{value} m",
|
||||
"Google 3D tiles unavailable — check the key's API restrictions, quota, or network": "Teselas de Google 3D no disponibles: revisa las restricciones de API de la clave, la cuota o la red",
|
||||
"Needs {envVars} — add it in Provider Settings — or a Cesium ion token for the ion-hosted route": "Necesita {envVars} — añádela en Ajustes de proveedores — o un token de Cesium ion para la ruta alojada en ion"
|
||||
"Needs {envVars} — add it in Provider Settings — or a Cesium ion token for the ion-hosted route": "Necesita {envVars} — añádela en Ajustes de proveedores — o un token de Cesium ion para la ruta alojada en ion",
|
||||
"Comparison in use by another scene": "La comparación está en uso por otra escena",
|
||||
"A location reading needs a loaded forecast and the Earth at the center of the view.": "Una lectura en un punto necesita un pronóstico cargado y la Tierra en el centro de la vista."
|
||||
}
|
||||
@@ -2062,5 +2062,7 @@
|
||||
"subject": "объект",
|
||||
"{value}m": "{value} м",
|
||||
"Google 3D tiles unavailable — check the key's API restrictions, quota, or network": "Тайлы Google 3D недоступны — проверьте ограничения API ключа, квоту или сеть",
|
||||
"Needs {envVars} — add it in Provider Settings — or a Cesium ion token for the ion-hosted route": "Нужен {envVars} — добавьте его в «Настройках провайдеров» — или токен Cesium ion для маршрута через ion"
|
||||
"Needs {envVars} — add it in Provider Settings — or a Cesium ion token for the ion-hosted route": "Нужен {envVars} — добавьте его в «Настройках провайдеров» — или токен Cesium ion для маршрута через ion",
|
||||
"Comparison in use by another scene": "Сравнение занято другой сценой",
|
||||
"A location reading needs a loaded forecast and the Earth at the center of the view.": "Для показания в точке нужен загруженный прогноз и Земля в центре вида."
|
||||
}
|
||||
@@ -294,7 +294,8 @@ export function createAlprCamerasLayer({ source, services } = {}) {
|
||||
id: LAYER_ID,
|
||||
name: 'ALPR Cameras',
|
||||
icon: '📷',
|
||||
source: t(source.label || msg('Mapped camera locations')),
|
||||
// English in layer state; the layer row translates it.
|
||||
source: source.label || msg('Mapped camera locations'),
|
||||
updateInterval: 0,
|
||||
statsRefreshInterval: 1000,
|
||||
init(viewer) {
|
||||
|
||||
@@ -557,7 +557,8 @@ export function directionsStats(state) {
|
||||
armed,
|
||||
pointerBlocked,
|
||||
} = state;
|
||||
const source = t('OSM routing');
|
||||
// English in layer state; the layer row translates it.
|
||||
const source = 'OSM routing'; // i18n-ignore: source name, translated by layerPanel
|
||||
if (pointerBlocked) {
|
||||
return {
|
||||
count: route?.steps.length || 0,
|
||||
|
||||
@@ -47,7 +47,7 @@ export const SPLIT_PUBLISH_MS = 400;
|
||||
/** The three modes, in share-link order (`m` = index). */
|
||||
export const MODES = Object.freeze(['image', 'basemap', 'ab']);
|
||||
|
||||
const COMPARISON_IN_USE = 'Comparison in use by another scene';
|
||||
const COMPARISON_IN_USE = msg('Comparison in use by another scene');
|
||||
// Kept English in the snapshot (AI tools read it); the card translates them.
|
||||
const COMPARISON_UNAVAILABLE = msg('Esri map unavailable · no swipe');
|
||||
const COMPARISON_NEEDS_GLOBE = msg('Swipe needs a globe map');
|
||||
|
||||
@@ -37,7 +37,9 @@ export function mapAnalystRecord(raw) {
|
||||
// Empty records stay class-less; the class table's fallback is VISUAL and
|
||||
// must not leak onto a row that has no satellite identity.
|
||||
satelliteClass:
|
||||
noradId || group ? satelliteClassLabel(group, { isIss }) : null,
|
||||
noradId || group
|
||||
? satelliteClassLabel(group, { isIss, localize: false })
|
||||
: null,
|
||||
group,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -239,7 +239,10 @@ export function createTracking({ state: layerState, services, parts, source }) {
|
||||
name,
|
||||
operator: '',
|
||||
noradId: String(noradId),
|
||||
class: satelliteClassLabel(sat.group, { isIss: noradId === ISS_NORAD }),
|
||||
class: satelliteClassLabel(sat.group, {
|
||||
isIss: noradId === ISS_NORAD,
|
||||
localize: false,
|
||||
}),
|
||||
altitude:
|
||||
altitudeKm === null ? '' : `${altitudeKm.toLocaleString('en-US')} km`,
|
||||
},
|
||||
|
||||
@@ -65,8 +65,9 @@ export function inspectWindAtCenter(
|
||||
model,
|
||||
validTime,
|
||||
status,
|
||||
explanation:
|
||||
explanation: msg(
|
||||
'A location reading needs a loaded forecast and the Earth at the center of the view.',
|
||||
),
|
||||
};
|
||||
const location = cesium.Cartographic.fromCartesian(point, ellipsoid);
|
||||
const lon = cesium.Math.toDegrees(location.longitude);
|
||||
|
||||
@@ -6,7 +6,13 @@
|
||||
*/
|
||||
|
||||
/** Guidance shown when the active map stack cannot host draped imagery. */
|
||||
export const NO_IMAGERY_HOST = 'Hidden by this map source · choose a globe map';
|
||||
// Marks text for i18n extraction without importing i18n here; the UI
|
||||
// translates it where it is shown.
|
||||
const msg = (text) => text;
|
||||
|
||||
export const NO_IMAGERY_HOST = msg(
|
||||
'Hidden by this map source · choose a globe map',
|
||||
);
|
||||
|
||||
/**
|
||||
* Resolve the imagery layer collection for the active map stack.
|
||||
|
||||
+12
-7
@@ -5,7 +5,7 @@ export { layerFeedState } from '../data/feedState.js';
|
||||
import { GUIDANCE_STATUSES } from '../loadingFeedback.js';
|
||||
import { keySetupRequirementEnvVars } from '../keySetupCore.mjs';
|
||||
import { createWeatherPanel } from './weatherPanel.js';
|
||||
import { t, msg } from '../i18n/index.js';
|
||||
import { msg, t, translateKnown } from '../i18n/index.js';
|
||||
|
||||
// Feed-state enum VALUES are machine identifiers (layerFeedState contract, QA
|
||||
// hooks, CSS feed-* classes); only the final label value translates, at this
|
||||
@@ -176,6 +176,8 @@ const SOURCE_LABEL_KEYS = new Map(
|
||||
msg('NOAA nowCOAST · OBSERVED'),
|
||||
msg('NASA FIRMS · LIVE'),
|
||||
msg('OSM routing'),
|
||||
msg('Mapped camera locations'),
|
||||
msg('OpenStreetMap · community mapped'),
|
||||
].map((key) => [key, key]),
|
||||
);
|
||||
|
||||
@@ -721,8 +723,9 @@ export class LayerPanel {
|
||||
{ source },
|
||||
);
|
||||
}
|
||||
const presentedError =
|
||||
stats.error || stats.lastError || stats.managerRefreshError;
|
||||
const presentedError = translateKnown(
|
||||
stats.error || stats.lastError || stats.managerRefreshError,
|
||||
);
|
||||
if (presentedError) {
|
||||
if (typeof stats.retryInSec === 'number' && stats.retryInSec > 0) {
|
||||
return t('{state} · {source} · {detail} · retry {seconds}s', {
|
||||
@@ -751,14 +754,14 @@ export class LayerPanel {
|
||||
if (stats.loading) {
|
||||
const loadingLabel =
|
||||
typeof stats.loadingLabel === 'string' && stats.loadingLabel.trim()
|
||||
? stats.loadingLabel.trim()
|
||||
? translateKnown(stats.loadingLabel.trim())
|
||||
: t('loading...');
|
||||
return t('{source} · {loadingLabel}', { source, loadingLabel });
|
||||
}
|
||||
if (feedState === 'fallback') {
|
||||
const detail =
|
||||
typeof stats.loadingLabel === 'string' && stats.loadingLabel.trim()
|
||||
? stats.loadingLabel.trim()
|
||||
? translateKnown(stats.loadingLabel.trim())
|
||||
: stats.coverage || ago;
|
||||
return t('{state} · {source} · {detail}', {
|
||||
state: stateLabel,
|
||||
@@ -803,7 +806,7 @@ export class LayerPanel {
|
||||
if (typeof stats.loadingLabel === 'string' && stats.loadingLabel.trim()) {
|
||||
return t('{source} · {loadingLabel}', {
|
||||
source,
|
||||
loadingLabel: stats.loadingLabel.trim(),
|
||||
loadingLabel: translateKnown(stats.loadingLabel.trim()),
|
||||
});
|
||||
}
|
||||
return t('{source} · {ago}', { source, ago });
|
||||
@@ -861,7 +864,9 @@ export class LayerPanel {
|
||||
? t('UNAVAILABLE', { context: 'status chip' })
|
||||
: stateText;
|
||||
const keyGuidance = unavailable
|
||||
? String(layer.stats.error || t('Data source unavailable'))
|
||||
? String(
|
||||
translateKnown(layer.stats.error) || t('Data source unavailable'),
|
||||
)
|
||||
: layerKeyRequirementTooltip(layer);
|
||||
// Name the missing key on the control itself: a row reading KEY REQUIRED
|
||||
// without saying WHICH key leaves a dead control and no next step. Empty
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* strip and selection bar are this adapter's own keyed DOM. Source toggles
|
||||
* live on the data-panel row.
|
||||
*/
|
||||
import { t, tn, msg, formatNumber } from '../i18n/index.js';
|
||||
import { formatNumber, msg, t, tn, translateKnown } from '../i18n/index.js';
|
||||
import { createRailCardBlocks } from './railCardBlocks.js';
|
||||
import { createRailCards } from './railCards.js';
|
||||
import { createImagerySplit } from './imagerySplit.js';
|
||||
@@ -39,7 +39,7 @@ function localizeNotice(text) {
|
||||
if (!text) return '';
|
||||
if (LAYER_NOTICES.has(text)) return t(text);
|
||||
const tooLarge = TOO_LARGE.exec(text);
|
||||
if (!tooLarge) return text;
|
||||
if (!tooLarge) return translateKnown(text);
|
||||
const values = {
|
||||
size: formatQuantity(Number(tooLarge[2].replace(/,/g, '')), 'km', {
|
||||
useGrouping: true,
|
||||
|
||||
Reference in new issue
Block a user