mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
fix(i18n): fix Russian layouts and the gaps the screenshot review found
A review of the Russian UI screenshots found ~90 places where longer words
broke the layout or text was still English. Fixed so they hold for any
language, then shortened where a control is short by design.
- Layout: label columns that size to their text (weather and wind settings,
Recent Imagery slots and opacity, style sliders, display panel), layer
list names and icons, buttons that wrap instead of overflowing (CCTV,
scenes, Bhote Koshi), voice header that always shows the cost, styled
EN | ES | RU chips, voice error tray above the dock.
- t() context: t('CLEAR', { context: 'action' }) is «СБРОСИТЬ» while the sky
stays «ЯСНО»; also used for radio categories, voice states and directions.
- Units and dates follow the locale through src/ui/unitLabels.js
(formatQuantity: localized number, translated unit, non-breaking space):
wind, pressure, cyclones, directions, CCTV, SDR gain, scene timing, flight
tooltip, Recent Imagery dates and resolution.
- Newly translated: directions turn-by-turn steps (built from the maneuver;
English matches the server word for word), HUD mode and summary, voice
card states, radio categories on the globe, CCTV status, layer source
lines, scene dialogs (Cancel, file pickers), Bhote Koshi statuses, titles,
comparison dates and caveat, Recent Imagery notices. Stored data and text
for AI tools stay English.
- es and ru: 1,885 messages each.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
3129e531e6
commit
2b6ccad732
71 files changed
+2435
-370
No files matched your search
@@ -12,6 +12,7 @@ import {
|
||||
WIND_UNITS,
|
||||
} from './inspection.js';
|
||||
import { t } from '../../i18n/index.js';
|
||||
import { unitLabel } from '../../ui/unitLabels.js';
|
||||
|
||||
/** Format a forecast timestamp explicitly in UTC. */
|
||||
export function formatWindValidTime(value) {
|
||||
@@ -358,7 +359,7 @@ export function createWindLayer({
|
||||
: []
|
||||
).map((value) => ({
|
||||
id: `units-${value}`,
|
||||
label: value,
|
||||
label: unitLabel(value),
|
||||
active: units === value,
|
||||
params: { units: value },
|
||||
title: t('Wind speed units'),
|
||||
@@ -382,7 +383,7 @@ export function createWindLayer({
|
||||
info: `${t('{model} forecast · {field} ({unit})', {
|
||||
model: model === 'ifs' ? 'ECMWF IFS' : 'GFS',
|
||||
field: label,
|
||||
unit: legendUnit,
|
||||
unit: unitLabel(legendUnit),
|
||||
})}${historyStatus ? `\n${historyStatus}` : ''}\n${t('Valid: {time}', { time: valid || t('Unavailable') })}${loading ? ` · ${t('loading')}` : preparing ? ` · ${t('preparing')}` : ''}\n${t('Issued: {time}', { time: run || t('Unavailable') })}${manifest?.stale ? ' · STALE' : ''}${error ? '\n' + error : ''}${scalarMissing ? `\n${t('Selected field unavailable · wind remains visible')}` : ''}${imageryError && !scalarMissing ? `\n${t('{error} · wind remains visible', { error: imageryError })}` : ''}`,
|
||||
infoTitle: t(
|
||||
'Surface wind at 10 m. Approximately 1° global grid. Curves follow the 10 m wind field, lifted 12 km for visibility; display height is not weather altitude. View lighting is for readability. Animation shows flow through one fixed forecast; it does not advance time. Color fields drape the globe basemap or the active photorealistic 3D Tiles.',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { formatWindSpeed, WIND_UNITS } from './inspection.js';
|
||||
import { t } from '../../i18n/index.js';
|
||||
import { formatQuantity, unitLabel } from '../../ui/unitLabels.js';
|
||||
|
||||
/** Reformat a captured sample; changing units never samples the map again. */
|
||||
export function formatWindReading(reading, units) {
|
||||
@@ -20,20 +21,42 @@ export function formatWindReading(reading, units) {
|
||||
export function windUnitChips(units) {
|
||||
return Object.keys(WIND_UNITS).map((value) => ({
|
||||
id: `units-${value}`,
|
||||
label: value,
|
||||
label: unitLabel(value),
|
||||
active: units === value,
|
||||
params: { units: value },
|
||||
title: t('Wind speed units'),
|
||||
}));
|
||||
}
|
||||
|
||||
/** formatWindSpeed's value in the active language: "22,0 км/ч". */
|
||||
function displaySpeed(speed, units) {
|
||||
const unit = Object.hasOwn(WIND_UNITS, units) ? units : 'km/h';
|
||||
return formatQuantity(speed * WIND_UNITS[unit], unit, {
|
||||
minimumFractionDigits: 1,
|
||||
maximumFractionDigits: 1,
|
||||
});
|
||||
}
|
||||
|
||||
/** "1012.4 hPa" / "-3.0 °C" from the English reading, in the active language. */
|
||||
function displayScalar(reading) {
|
||||
if (reading.scalarKind === 'speed' && Number.isFinite(reading.speed))
|
||||
return displaySpeed(reading.speed, reading.units);
|
||||
const match = /^(-?\d+(?:\.\d+)?) (°C|hPa)$/.exec(reading.scalarValue);
|
||||
if (!match) return reading.scalarValue;
|
||||
const decimals = match[1].split('.')[1]?.length || 0;
|
||||
return formatQuantity(Number(match[1]), match[2], {
|
||||
minimumFractionDigits: decimals,
|
||||
maximumFractionDigits: decimals,
|
||||
});
|
||||
}
|
||||
|
||||
/** The wind line in the active language; the reading itself stays English. */
|
||||
function windText(reading) {
|
||||
if (!Number.isFinite(reading.speed))
|
||||
return reading.wind === 'Aim the map center at Earth'
|
||||
? t('Aim the map center at Earth')
|
||||
: reading.wind;
|
||||
const speed = formatWindSpeed(reading.speed, reading.units);
|
||||
const speed = displaySpeed(reading.speed, reading.units);
|
||||
return reading.from === 'Calm'
|
||||
? `${speed}${t(' · calm')}`
|
||||
: `${speed}${t(' from {direction}', { direction: t(reading.from) })}`;
|
||||
@@ -64,7 +87,7 @@ export function windReadingResult(reading) {
|
||||
? [
|
||||
{
|
||||
id: 'scalar',
|
||||
text: `${t(reading.scalarLabel)} · ${reading.scalarValue}`,
|
||||
text: `${t(reading.scalarLabel)} · ${displayScalar(reading)}`,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
|
||||
@@ -2,7 +2,16 @@ import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { formatWindReading, windReadingResult } from './presentation.js';
|
||||
|
||||
const sample = { speed: 5, from: 'SW', coordinates: '41.9°N · 87.6°W', model: 'NOAA GFS', validTime: '2026-09-21 12:00 UTC', scalarLabel: 'Air temperature · 2 m', scalarValue: '12 °C', explanation: 'Interpolated model forecast.' };
|
||||
const sample = {
|
||||
speed: 5,
|
||||
from: 'SW',
|
||||
coordinates: '41.9°N · 87.6°W',
|
||||
model: 'NOAA GFS',
|
||||
validTime: '2026-09-21 12:00 UTC',
|
||||
scalarLabel: 'Air temperature · 2 m',
|
||||
scalarValue: '12 °C',
|
||||
explanation: 'Interpolated model forecast.',
|
||||
};
|
||||
test('captured reading reformats without changing its sample and produces a portable result block', () => {
|
||||
const reading = formatWindReading(sample, 'mph');
|
||||
assert.equal(reading.wind, '11.2 mph from SW');
|
||||
@@ -11,12 +20,30 @@ test('captured reading reformats without changing its sample and produces a port
|
||||
assert.equal(sample.units, undefined);
|
||||
const section = windReadingResult(reading);
|
||||
assert.equal(section.id, 'reading');
|
||||
assert.equal(section.lines.find(({ id }) => id === 'scalar').text, 'Air temperature · 2 m · 12 °C');
|
||||
assert.equal(section.lines.find(({ id }) => id === 'wind').text, '11.2 mph from SW');
|
||||
assert.equal(section.lines.find(({ id }) => id === 'meta').text, 'NOAA GFS · valid 09-21 12:00 UTC');
|
||||
assert.equal(
|
||||
section.lines.find(({ id }) => id === 'scalar').text,
|
||||
'Air temperature · 2 m · 12\u00a0°C',
|
||||
);
|
||||
assert.equal(
|
||||
section.lines.find(({ id }) => id === 'wind').text,
|
||||
'11.2\u00a0mph from SW',
|
||||
);
|
||||
assert.equal(
|
||||
section.lines.find(({ id }) => id === 'meta').text,
|
||||
'NOAA GFS · valid 09-21 12:00 UTC',
|
||||
);
|
||||
assert.equal(section.label, 'WIND AT 41.9°N 87.6°W');
|
||||
assert.deepEqual(section.clear.params, { inspect: false });
|
||||
assert.equal(formatWindReading({ ...sample, from: 'Calm' }, 'm/s').wind, '5.0 m/s · calm');
|
||||
assert.equal(
|
||||
formatWindReading({ ...sample, from: 'Calm' }, 'm/s').wind,
|
||||
'5.0 m/s · calm',
|
||||
);
|
||||
assert.equal(formatWindReading(null, 'km/h'), null);
|
||||
assert.equal(windReadingResult({ coordinates: 'No surface reading', wind: 'Unavailable' }).lines.some(({ id }) => id === 'scalar'), false);
|
||||
assert.equal(
|
||||
windReadingResult({
|
||||
coordinates: 'No surface reading',
|
||||
wind: 'Unavailable',
|
||||
}).lines.some(({ id }) => id === 'scalar'),
|
||||
false,
|
||||
);
|
||||
});
|
||||
Reference in new issue
Block a user