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 <[email protected]>
This commit is contained in:
German KhokhlovandClaude Opus 5.5 committed 2026-10-13 23:11:37 +04:00
1 parent 7db0abc396
commit 03207ca0fc
8 files changed
+47 -11

No files matched your search

+3 -1
View File
@@ -2073,5 +2073,7 @@
"Soul": "Soul", "Soul": "Soul",
"Techno": "Techno", "Techno": "Techno",
"Trance": "Trance", "Trance": "Trance",
"World": "Músicas del mundo" "World": "Músicas del mundo",
"{speed}KT": "{speed} KN",
"{heading}DEG": "{heading}°"
} }
+3 -1
View File
@@ -2105,5 +2105,7 @@
"Soul": "Соул", "Soul": "Соул",
"Techno": "Техно", "Techno": "Техно",
"Trance": "Транс", "Trance": "Транс",
"World": "Музыка мира" "World": "Музыка мира",
"{speed}KT": "{speed} УЗ",
"{heading}DEG": "{heading}°"
} }
@@ -1,5 +1,6 @@
import { FOLLOW_MAP_STACK_ID } from '../../policy.js'; import { FOLLOW_MAP_STACK_ID } from '../../policy.js';
import { createMapsLoader } from './mapsLoader.js'; import { createMapsLoader } from './mapsLoader.js';
import { getLocale } from '../../../../i18n/index.js';
import { createGoogleViewer } from './viewer.js'; import { createGoogleViewer } from './viewer.js';
import { import {
COLORS, COLORS,
@@ -48,7 +49,7 @@ function settleWithin(promise, { ms, signal, loader }) {
*/ */
export function createGoogleProvider({ export function createGoogleProvider({
getApiKey, getApiKey,
loader = createMapsLoader({ getApiKey }), loader = createMapsLoader({ getApiKey, getLanguage: getLocale }),
}) { }) {
if (typeof getApiKey !== 'function') if (typeof getApiKey !== 'function')
throw new TypeError('Google Street View needs getApiKey'); throw new TypeError('Google Street View needs getApiKey');
@@ -9,10 +9,13 @@ const READY_CALLBACK = '__gevGoogleMapsReady';
const PAGE_LOAD = Symbol.for('gev.googleMapsLoad'); 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({ export function createMapsLoader({
getApiKey, getApiKey,
getLanguage,
document = globalThis.document, document = globalThis.document,
global = globalThis, global = globalThis,
}) { }) {
@@ -53,12 +56,15 @@ export function createMapsLoader({
}; };
const script = document.createElement('script'); const script = document.createElement('script');
script.async = true; script.async = true;
script.src = `${SCRIPT_ORIGIN}/maps/api/js?${new URLSearchParams({ const params = new URLSearchParams({
key, key,
v: 'weekly', v: 'weekly',
loading: 'async', loading: 'async',
callback: READY_CALLBACK, callback: READY_CALLBACK,
})}`; });
const language = getLanguage?.();
if (language && language !== 'en') params.set('language', language);
script.src = `${SCRIPT_ORIGIN}/maps/api/js?${params}`;
script.onerror = () => { script.onerror = () => {
if (global[PAGE_LOAD] === loading) delete global[PAGE_LOAD]; if (global[PAGE_LOAD] === loading) delete global[PAGE_LOAD];
script.remove(); script.remove();
@@ -142,3 +142,19 @@ test('a retried load still reports a refusal once, and the page handler once', a
assert.deepEqual(heard, ['a']); assert.deepEqual(heard, ['a']);
assert.equal(before, 1); 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);
});
+6 -2
View File
@@ -160,11 +160,15 @@ export function createCards({
} }
function formatSpeed(speed) { function formatSpeed(speed) {
return speed === null ? '--KT' : `${speed.toFixed(1)}KT`; return t('{speed}KT', {
speed: speed === null ? '--' : speed.toFixed(1),
});
} }
function formatHeading(heading) { 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) { function formatPositionTime(record) {
+7 -2
View File
@@ -5,7 +5,7 @@ export { layerFeedState } from '../data/feedState.js';
import { GUIDANCE_STATUSES } from '../loadingFeedback.js'; import { GUIDANCE_STATUSES } from '../loadingFeedback.js';
import { keyGuidanceText, keyRequirementText } from './keyGuidance.js'; import { keyGuidanceText, keyRequirementText } from './keyGuidance.js';
import { createWeatherPanel } from './weatherPanel.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 // Feed-state enum VALUES are machine identifiers (layerFeedState contract, QA
// hooks, CSS feed-* classes); only the final label value translates, at this // hooks, CSS feed-* classes); only the final label value translates, at this
@@ -891,7 +891,12 @@ export class LayerPanel {
} }
_formatCount(n) { _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); return String(n);
} }
+1 -1
View File
@@ -230,7 +230,7 @@ html:not([lang='en'])
min-width: 0; min-width: 0;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
letter-spacing: 2px; letter-spacing: 1.5px;
} }
.panel-divider { .panel-divider {