diff --git a/src/layers/alpr/render/navara.js b/src/layers/alpr/render/navara.js index fcb5e8fd2..534413527 100644 --- a/src/layers/alpr/render/navara.js +++ b/src/layers/alpr/render/navara.js @@ -1,4 +1,4 @@ -import { t } from '../../../i18n/index.js'; +import { msg } from '../../../i18n/index.js'; import * as geo from '../../../geo/index.js'; import { distanceScale } from '../../../globe/cpuPicks.js'; import { selectionDispatcherFor } from '../../../data/selectionDispatcher.js'; @@ -341,7 +341,7 @@ export function createAlprNavaraRenderer(engine, { state, services, source }) { layerName: 'ALPR Cameras', source: source.attribution?.description || source.label || 'Camera source', - label: t('ALPR camera'), + label: msg('ALPR camera'), latitude: record.latitude, longitude: record.longitude, properties: { diff --git a/src/layers/alpr/render/navara.test.mjs b/src/layers/alpr/render/navara.test.mjs index a0cdb8249..d4234829d 100644 --- a/src/layers/alpr/render/navara.test.mjs +++ b/src/layers/alpr/render/navara.test.mjs @@ -1,6 +1,8 @@ import test from 'node:test'; import assert from 'node:assert/strict'; import * as geo from '../../../geo/index.js'; +import { setLocale } from '../../../i18n/index.js'; +import { shownEntityLabel } from '../../../voice/speech.js'; import { createAlprNavaraRenderer } from './navara.js'; function event() { @@ -311,6 +313,16 @@ test('ALPR preserves selected metadata and releases an evicted or replaced selec assert.equal(h.contexts.has('alpr:1'), false); }); +test('the camera label stays English data in a translated interface, as on Cesium', (t) => { + const h = harness(t); + setLocale('ru'); + t.after(() => setLocale('en')); + h.load(records(1)); + const { label } = h.contexts.get('alpr:0'); + assert.equal(label, 'ALPR camera'); + assert.equal(shownEntityLabel(label), 'Камера ALPR'); +}); + test('ten ALPR toggles clear contexts, picks and credits without allocating new sets', (t) => { const h = harness(t); for (let i = 0; i < 10; i++) { diff --git a/src/layers/installations/render/navara.js b/src/layers/installations/render/navara.js index 9ccc6cebb..a6a82aa02 100644 --- a/src/layers/installations/render/navara.js +++ b/src/layers/installations/render/navara.js @@ -7,6 +7,7 @@ import { MILITARY_LABEL_CAP, MILITARY_POINT_CAP, } from '../../../data/militaryNames.js'; +import { installationClassLabel, installationTitle } from '../classLabel.js'; import { COLOR_BY_CLASS, LAYER_ID, MAX_RENDERED } from '../policy.js'; import { createInstallationDrapedMesh, @@ -278,11 +279,9 @@ export function createInstallationsNavaraRenderer( } context.gevContextId = record.id; context.gevLabelModel = { - title: record.name || t('MAPPED INSTALLATION'), + title: installationTitle(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', diff --git a/src/layers/installations/render/navara.test.mjs b/src/layers/installations/render/navara.test.mjs index cb962ceb6..9c92a1855 100644 --- a/src/layers/installations/render/navara.test.mjs +++ b/src/layers/installations/render/navara.test.mjs @@ -1,6 +1,8 @@ import test from 'node:test'; import assert from 'node:assert/strict'; import { Mesh } from 'three'; +import { setLocale } from '../../../i18n/index.js'; +import { installationClassLabel, installationTitle } from '../classLabel.js'; import { createInstallationsNavaraRenderer } from './navara.js'; test('selected installation fill is reused and removed with its scene resources', () => { @@ -157,3 +159,82 @@ test('selected installation fill is reused and removed with its scene resources' assert.equal(deletedEffects, 1, 'destroy deletes the public effect handle'); assert.equal(effectConfig.gevInstallationsDrape.owner.meshes.size, 0); }); + +test('the card label reads in the UI language, as on Cesium', () => { + const record = { + id: 'osm:node:4', + longitude: 10, + latitude: 50, + class: 'naval_base', + name: 'Firing range', + }; + const state = { + enabled: true, + records: [record], + recordById: new Map([[record.id, record]]), + selectedId: null, + }; + const contexts = new Map(); + const set = { setCount() {}, set() {}, commit() {}, destroy() {} }; + const renderer = createInstallationsNavaraRenderer( + { + movers: { createSet: () => set }, + camera: { height: () => 1_000 }, + render: { request() {} }, + }, + { + state, + services: { + context: { + getSelectedEntityContext: () => null, + registerEntityContext: (entity, value) => + contexts.set(value.id, entity), + selectEntityContext() {}, + removeEntityContextsForLayer() {}, + clearSelectedEntityContextForLayer() {}, + }, + picking: { registerPickOwner() {}, unregisterPickOwner() {} }, + }, + parts: { + model: { + colorFor: () => ({ + red: 0.6, + green: 0.6, + blue: 0.6, + alpha: 1, + toCssColorString: () => '#9ca6b0', + }), + installationSurfaceHeightM: () => 0, + installationSourceLabel: () => 'OpenStreetMap', + }, + namedMarkers: { sync() {}, enable() {}, hide() {}, destroy() {} }, + viewport: { clearUnavailableRetry() {} }, + ingestion: { setInstallationStatus() {} }, + }, + source: {}, + overlayHost: { setVisible() {}, setEntries() {}, clearSource() {} }, + view: { + canvas: { clientHeight: 800 }, + camera: { raw: { fov: 60 } }, + registerEffect() {}, + addEffect: () => ({ update() {}, delete() {} }), + }, + EffectDesc: class {}, + NavaraEffect: class {}, + DrapedMesh: Mesh, + setupMaterialForDrape() {}, + }, + ); + try { + setLocale('ru'); + renderer.lifecycle.methods.enable(); + const label = contexts.get(record.id).gevLabelModel; + assert.equal(label.title, installationTitle('Firing range')); + assert.notEqual(label.title, 'Firing range'); + assert.equal(label.details[0], installationClassLabel('naval_base')); + assert.notEqual(label.details[0], 'NAVAL BASE'); + } finally { + setLocale('en'); + renderer.lifecycle.methods.destroy(); + } +});