From 03207ca0fc903a2236c1d370a23be7f116859d4d Mon Sep 17 00:00:00 2001 From: German Khokhlov Date: Tue, 13 Oct 2026 23:11:37 +0400 Subject: [PATCH] fix(i18n): Google controls, vessel units and counts follow the UI language Screens that only open with provider keys showed a few gaps: - Google Street View's own controls (Map Data, Terms, Report a problem) and its addresses were always English: the Maps script now loads with the UI language. English adds no parameter, as before. - The selected-vessel HUD line kept KT and DEG after translated labels. - Layer counts used a decimal point (12.0K); other languages use their own separator (12,0K). - A collapsed rail title was cut with an ellipsis in Russian; tighter tracking fits it whole. English is unchanged. Co-Authored-By: Claude Opus 5.5 --- src/i18n/locales/es.json | 4 +++- src/i18n/locales/ru.json | 4 +++- src/layers/streetLevel/providers/google/index.js | 3 ++- .../streetLevel/providers/google/mapsLoader.js | 12 +++++++++--- .../providers/google/mapsLoader.test.mjs | 16 ++++++++++++++++ src/layers/vessels/cards.js | 8 ++++++-- src/ui/layerPanel.js | 9 +++++++-- src/ui/styles/controls.css | 2 +- 8 files changed, 47 insertions(+), 11 deletions(-) diff --git a/src/i18n/locales/es.json b/src/i18n/locales/es.json index 9501423bc..ed910dfff 100644 --- a/src/i18n/locales/es.json +++ b/src/i18n/locales/es.json @@ -2073,5 +2073,7 @@ "Soul": "Soul", "Techno": "Techno", "Trance": "Trance", - "World": "Músicas del mundo" + "World": "Músicas del mundo", + "{speed}KT": "{speed} KN", + "{heading}DEG": "{heading}°" } diff --git a/src/i18n/locales/ru.json b/src/i18n/locales/ru.json index d9bb99622..52325ffe3 100644 --- a/src/i18n/locales/ru.json +++ b/src/i18n/locales/ru.json @@ -2105,5 +2105,7 @@ "Soul": "Соул", "Techno": "Техно", "Trance": "Транс", - "World": "Музыка мира" + "World": "Музыка мира", + "{speed}KT": "{speed} УЗ", + "{heading}DEG": "{heading}°" } diff --git a/src/layers/streetLevel/providers/google/index.js b/src/layers/streetLevel/providers/google/index.js index 54fcc02f5..5789a9321 100644 --- a/src/layers/streetLevel/providers/google/index.js +++ b/src/layers/streetLevel/providers/google/index.js @@ -1,5 +1,6 @@ import { FOLLOW_MAP_STACK_ID } from '../../policy.js'; import { createMapsLoader } from './mapsLoader.js'; +import { getLocale } from '../../../../i18n/index.js'; import { createGoogleViewer } from './viewer.js'; import { COLORS, @@ -48,7 +49,7 @@ function settleWithin(promise, { ms, signal, loader }) { */ export function createGoogleProvider({ getApiKey, - loader = createMapsLoader({ getApiKey }), + loader = createMapsLoader({ getApiKey, getLanguage: getLocale }), }) { if (typeof getApiKey !== 'function') throw new TypeError('Google Street View needs getApiKey'); diff --git a/src/layers/streetLevel/providers/google/mapsLoader.js b/src/layers/streetLevel/providers/google/mapsLoader.js index dd66e1c92..841120347 100644 --- a/src/layers/streetLevel/providers/google/mapsLoader.js +++ b/src/layers/streetLevel/providers/google/mapsLoader.js @@ -9,10 +9,13 @@ const READY_CALLBACK = '__gevGoogleMapsReady'; const PAGE_LOAD = Symbol.for('gev.googleMapsLoad'); /** - * @param {{getApiKey: () => string|null|undefined, document?: Document, global?: object}} options + * @param {{getApiKey: () => string|null|undefined, getLanguage?: () => string|null|undefined, document?: Document, global?: object}} options + * `getLanguage` names the UI language; Google's own controls (Terms, + * Report a problem) follow it. English adds nothing, as before. */ export function createMapsLoader({ getApiKey, + getLanguage, document = globalThis.document, global = globalThis, }) { @@ -53,12 +56,15 @@ export function createMapsLoader({ }; const script = document.createElement('script'); script.async = true; - script.src = `${SCRIPT_ORIGIN}/maps/api/js?${new URLSearchParams({ + const params = new URLSearchParams({ key, v: 'weekly', loading: 'async', callback: READY_CALLBACK, - })}`; + }); + const language = getLanguage?.(); + if (language && language !== 'en') params.set('language', language); + script.src = `${SCRIPT_ORIGIN}/maps/api/js?${params}`; script.onerror = () => { if (global[PAGE_LOAD] === loading) delete global[PAGE_LOAD]; script.remove(); diff --git a/src/layers/streetLevel/providers/google/mapsLoader.test.mjs b/src/layers/streetLevel/providers/google/mapsLoader.test.mjs index 789353143..1da6cf9bd 100644 --- a/src/layers/streetLevel/providers/google/mapsLoader.test.mjs +++ b/src/layers/streetLevel/providers/google/mapsLoader.test.mjs @@ -142,3 +142,19 @@ test('a retried load still reports a refusal once, and the page handler once', a assert.deepEqual(heard, ['a']); assert.equal(before, 1); }); + +test('Google controls follow the UI language; English adds no parameter', async () => { + const language = (locale) => { + const page = fakePage(); + const loader = createMapsLoader({ + getApiKey: () => 'k-1', + getLanguage: () => locale, + ...page, + }); + void loader.importLibrary('streetView').catch(() => {}); + return new URL(page.scripts[0].src).searchParams.get('language'); + }; + assert.equal(language('es'), 'es'); + assert.equal(language('ru'), 'ru'); + assert.equal(language('en'), null); +}); diff --git a/src/layers/vessels/cards.js b/src/layers/vessels/cards.js index 406e843b4..c1d3b452d 100644 --- a/src/layers/vessels/cards.js +++ b/src/layers/vessels/cards.js @@ -160,11 +160,15 @@ export function createCards({ } function formatSpeed(speed) { - return speed === null ? '--KT' : `${speed.toFixed(1)}KT`; + return t('{speed}KT', { + speed: speed === null ? '--' : speed.toFixed(1), + }); } function formatHeading(heading) { - return Number.isFinite(heading) ? `${Math.round(heading)}DEG` : '--DEG'; + return t('{heading}DEG', { + heading: Number.isFinite(heading) ? Math.round(heading) : '--', + }); } function formatPositionTime(record) { diff --git a/src/ui/layerPanel.js b/src/ui/layerPanel.js index b7fad6db9..6f9a95fb7 100644 --- a/src/ui/layerPanel.js +++ b/src/ui/layerPanel.js @@ -5,7 +5,7 @@ export { layerFeedState } from '../data/feedState.js'; import { GUIDANCE_STATUSES } from '../loadingFeedback.js'; import { keyGuidanceText, keyRequirementText } from './keyGuidance.js'; import { createWeatherPanel } from './weatherPanel.js'; -import { msg, t, translateKnown } from '../i18n/index.js'; +import { formatNumber, 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 @@ -891,7 +891,12 @@ export class LayerPanel { } _formatCount(n) { - if (n >= 1000) return `${(n / 1000).toFixed(1)}K`; + if (n >= 1000) + return `${formatNumber(n / 1000, { + minimumFractionDigits: 1, + maximumFractionDigits: 1, + useGrouping: false, + })}K`; return String(n); } diff --git a/src/ui/styles/controls.css b/src/ui/styles/controls.css index 5fdef1cf0..545cd78a0 100644 --- a/src/ui/styles/controls.css +++ b/src/ui/styles/controls.css @@ -230,7 +230,7 @@ html:not([lang='en']) min-width: 0; overflow: hidden; text-overflow: ellipsis; - letter-spacing: 2px; + letter-spacing: 1.5px; } .panel-divider {