mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
fix(i18n): second screenshot review: remaining English, numbers and wraps
From the re-shot Russian screenshots:
- Translated: weather frame/imagery status errors, HUD summary provenance
("НЕДОСТУПНО: СУДА AIS ОНЛАЙН"), Bhote Koshi map labels, cockpit signal
distances (КМ). The HUD summary fields are separated with " · " in every
language, so the style name and view band no longer read as one phrase.
- Numbers follow the locale: cockpit altitude ("26 175"), precipitation.
- No breaks inside codes and dates: ADS-B and similar in layer rows,
Sentinel-2, cyclone issue dates (word joiner; the UI font has no U+2011),
no-break space after label colons in cyclone details.
- Layout: count lines under ALPR, short unavailable chip (context
"status chip"), roomier draw segments, tighter CCTV calibration chips,
two-line beat titles and media card titles, wrapping weather status.
- Wording: ru advisory is «сводка» (was «бюллетень»; "Advisory center" is
«Центр циклона по сводке»), Street View is «Просмотр улиц», OPACITY
«НЕПРОЗР.», synced «синхр.», Area «Зона»; the import dialog no longer says
"this scene" for several scenes. Glossary updated.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
2b6ccad732
commit
54dce5b3bd
32 files changed
+1204
-346
No files matched your search
@@ -17,10 +17,11 @@ import {
|
||||
import { t, tn, msg } from '../../i18n/index.js';
|
||||
import { formatQuantity } from '../../ui/unitLabels.js';
|
||||
|
||||
// "10-12 15:00 UTC" never breaks across lines.
|
||||
// "10-12 15:00 UTC" never breaks across lines: no-break spaces, and a word
|
||||
// joiner after the hyphen (JetBrains Mono has no U+2011 non-breaking hyphen).
|
||||
const utc = (value) =>
|
||||
value
|
||||
? `${value.slice(5, 16).replace('T', '\u00a0')}\u00a0UTC`
|
||||
? `${value.slice(5, 16).replace('-', '-\u2060').replace('T', '\u00a0')}\u00a0UTC`
|
||||
: t('Unavailable');
|
||||
const COVERAGE = msg(
|
||||
'Atlantic and eastern/central North Pacific; not worldwide cyclone coverage.',
|
||||
|
||||
@@ -565,7 +565,8 @@ test('advisory selection uses accessible row descriptors and shared camera hando
|
||||
1,
|
||||
);
|
||||
assert.equal(controls.summary.status, 'Track/cone awaiting advisory 10');
|
||||
assert.match(controls.info, /09-16\u00a003:00\u00a0UTC/);
|
||||
// No line break inside the date: a word joiner holds the hyphen.
|
||||
assert.match(controls.info, /issued 09-\u206016\u00a003:00\u00a0UTC/);
|
||||
assert.match(controls.info, /: 25\u00a0kt · .*: 1006\u00a0hPa/);
|
||||
assert.match(controls.infoTitle, /not storm size/);
|
||||
assert.match(controls.infoTitle, /follows the surface/);
|
||||
|
||||
@@ -27,7 +27,10 @@ export const displayMetres = (value) => formatQuantity(value, 'm');
|
||||
export const READOUT_WORDS = Object.freeze({
|
||||
date: (day) => displayDay(day, LONG_DAY),
|
||||
daysAgo: (delta) => formatRelativeTime(delta > 0 ? -delta : 0, 'day'),
|
||||
sensor: (sensor) => t(sensor),
|
||||
// "Sentinel-2" never breaks at its hyphen: a word joiner follows it
|
||||
// (JetBrains Mono has no U+2011 non-breaking hyphen).
|
||||
sensor: (sensor) =>
|
||||
t(sensor).replace(/(?<=[\p{L}\p{N}])-(?=[\p{L}\p{N}])/gu, '-\u2060'),
|
||||
metres: displayMetres,
|
||||
overview: () => t('overview'),
|
||||
cloud: (cloud) => {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { msg } from '../../i18n/index.js';
|
||||
|
||||
/** Resolve the visible surface that can own weather imagery without scene mutation. */
|
||||
export function resolveImageryHost({ viewer, tileset }) {
|
||||
if (viewer?.scene?.globe?.show === true)
|
||||
@@ -7,7 +9,9 @@ export function resolveImageryHost({ viewer, tileset }) {
|
||||
return { collection: null, kind: 'none' };
|
||||
}
|
||||
|
||||
export const NO_IMAGERY_HOST = 'Hidden by this map source · choose a globe map';
|
||||
export const NO_IMAGERY_HOST = msg(
|
||||
'Hidden by this map source · choose a globe map',
|
||||
);
|
||||
|
||||
/** Report imagery suspension without changing or detaching the resolved host. */
|
||||
export function imageryHostStatus(host) {
|
||||
|
||||
+17
-10
@@ -7,7 +7,7 @@ import {
|
||||
GLOBAL_INFRARED_MAX_GAP_MS,
|
||||
LIGHTNING_MAX_GAP_MS,
|
||||
} from './clock.js';
|
||||
import { t } from '../../i18n/index.js';
|
||||
import { t, msg } from '../../i18n/index.js';
|
||||
import { formatScaleLabel } from '../../ui/unitLabels.js';
|
||||
|
||||
const STOPS = [
|
||||
@@ -229,12 +229,14 @@ export function createWeatherLayer({
|
||||
rendering.setHidden?.(false);
|
||||
void warmNext(time);
|
||||
}
|
||||
error = ok ? null : 'Frame unavailable; previous observation retained';
|
||||
error = ok
|
||||
? null
|
||||
: msg('Frame unavailable; previous observation retained');
|
||||
if (!ok) stop();
|
||||
return ok;
|
||||
} catch {
|
||||
if (owner === generation && !controller.signal.aborted) {
|
||||
error = 'Weather imagery unavailable';
|
||||
error = msg('Weather imagery unavailable');
|
||||
stop();
|
||||
}
|
||||
return false;
|
||||
@@ -340,7 +342,9 @@ export function createWeatherLayer({
|
||||
if (!enabled || controller.signal.aborted || request !== controller)
|
||||
return false;
|
||||
if (snapshot.unavailable) {
|
||||
error = 'Weather source unavailable; previous observation retained';
|
||||
error = msg(
|
||||
'Weather source unavailable; previous observation retained',
|
||||
);
|
||||
stop();
|
||||
return true;
|
||||
}
|
||||
@@ -370,7 +374,7 @@ export function createWeatherLayer({
|
||||
return !controller.signal.aborted && request === controller && enabled;
|
||||
} catch (cause) {
|
||||
if (controller.signal.aborted || request !== controller) return false;
|
||||
error = cause?.message || 'Weather unavailable';
|
||||
error = cause?.message || msg('Weather unavailable');
|
||||
stop();
|
||||
return true;
|
||||
} finally {
|
||||
@@ -524,6 +528,10 @@ export function createWeatherLayer({
|
||||
})
|
||||
: t('{minutes}m ago', { minutes: age });
|
||||
const diagnostic = rendering?.getDiagnostics();
|
||||
// Errors are stored in English (stats, diagnostics); shown translated.
|
||||
const problem = error || diagnostic?.error;
|
||||
const shownProblem = problem ? t(problem) : null;
|
||||
const shownHost = hostStatus ? t(hostStatus) : null;
|
||||
const b = manifest?.bounds;
|
||||
const canvas = viewer?.scene?.canvas;
|
||||
const point =
|
||||
@@ -618,9 +626,8 @@ export function createWeatherLayer({
|
||||
detail,
|
||||
status:
|
||||
missing ||
|
||||
hostStatus ||
|
||||
error ||
|
||||
diagnostic?.error ||
|
||||
shownHost ||
|
||||
shownProblem ||
|
||||
(observationDelayed()
|
||||
? t('Source observations delayed')
|
||||
: manifest?.stale
|
||||
@@ -701,8 +708,8 @@ export function createWeatherLayer({
|
||||
}))
|
||||
: [],
|
||||
info: hostHidden
|
||||
? hostStatus
|
||||
: `${infoHeading}\n${infoTime}${missing ? `\n${missing}` : ''}${manifest?.stale ? `\n${t('STALE · cached source metadata')}` : ''}${error || diagnostic?.error ? '\n' + (error || diagnostic.error) : ''}\n${infoCoverage}${outside ? `\n${t('Map center is outside source coverage')}` : ''}${infoMotion ? `\n${infoMotion}` : ''}`,
|
||||
? shownHost
|
||||
: `${infoHeading}\n${infoTime}${missing ? `\n${missing}` : ''}${manifest?.stale ? `\n${t('STALE · cached source metadata')}` : ''}${shownProblem ? `\n${shownProblem}` : ''}\n${infoCoverage}${outside ? `\n${t('Map center is outside source coverage')}` : ''}${infoMotion ? `\n${infoMotion}` : ''}`,
|
||||
infoTitle: lightning
|
||||
? t(
|
||||
'NOAA/NWS 15-minute lightning density derived from Vaisala NLDN/GLD360. Coverage 110°E across the Pacific/Americas to 0°, 25°S–80°N. Not a live strike count, global coverage or a safety warning.',
|
||||
|
||||
@@ -3,6 +3,7 @@ import { orderWeatherImagery } from './imageryOrder.js';
|
||||
import { imageryHostStatus } from './imageryHost.js';
|
||||
import { createRasterTileProvider } from './rasterTiles.js';
|
||||
import { createWeatherShell } from './shellRendering.js';
|
||||
import { msg } from '../../i18n/index.js';
|
||||
import { readResponseBytesCapped } from '../../sources/httpBody.js';
|
||||
import {
|
||||
acquireInfraredMosaic,
|
||||
@@ -294,7 +295,9 @@ function createGlobeRendering({
|
||||
);
|
||||
}
|
||||
} else {
|
||||
lastError = 'Weather tiles unavailable · previous frame retained';
|
||||
lastError = msg(
|
||||
'Weather tiles unavailable · previous frame retained',
|
||||
);
|
||||
remove(frame);
|
||||
}
|
||||
frame.resolve(ok);
|
||||
@@ -413,7 +416,7 @@ function createGlobeRendering({
|
||||
return;
|
||||
}
|
||||
frame.failed = true;
|
||||
lastError = 'Some weather tiles unavailable';
|
||||
lastError = msg('Some weather tiles unavailable');
|
||||
onChange();
|
||||
});
|
||||
// A shown, transparent layer lets Cesium request staging tiles while the last
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { acquireWeatherImage } from './infraredImage.js';
|
||||
import { imageryHostStatus } from './imageryHost.js';
|
||||
import { WEATHER_DETAIL_SIZE, WEATHER_IMAGE_SIZES } from './source.js';
|
||||
import { msg } from '../../i18n/index.js';
|
||||
|
||||
/** Metres above the ellipsoid. Lower shells draw first, so lightning draws last. */
|
||||
export const WEATHER_SHELL_HEIGHTS = Object.freeze({
|
||||
@@ -630,7 +631,7 @@ export function createWeatherShell({
|
||||
if (incoming !== frame) return;
|
||||
incoming = null;
|
||||
close(frame);
|
||||
lastError = 'Weather tiles unavailable · previous frame retained';
|
||||
lastError = msg('Weather tiles unavailable · previous frame retained');
|
||||
frame.resolve(false);
|
||||
scene.requestRender();
|
||||
onChange();
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
createWindInspectionMarker,
|
||||
WIND_UNITS,
|
||||
} from './inspection.js';
|
||||
import { t } from '../../i18n/index.js';
|
||||
import { t, msg } from '../../i18n/index.js';
|
||||
import { unitLabel } from '../../ui/unitLabels.js';
|
||||
|
||||
/** Format a forecast timestamp explicitly in UTC. */
|
||||
@@ -174,7 +174,7 @@ export function createWindLayer({
|
||||
return true;
|
||||
} catch (cause) {
|
||||
if (controller.signal.aborted || request !== controller) return false;
|
||||
error = cause?.message || 'Wind source unavailable';
|
||||
error = cause?.message || msg('Wind source unavailable');
|
||||
return true;
|
||||
} finally {
|
||||
signal?.removeEventListener('abort', abort);
|
||||
@@ -260,8 +260,11 @@ export function createWindLayer({
|
||||
requestedScalar() !== 'none' &&
|
||||
manifest &&
|
||||
(!manifest.scalar || manifest.scalar.kind !== overlay);
|
||||
// Stored in English for diagnostics; shown translated.
|
||||
const imageryError =
|
||||
enabled && !loading ? diagnostic?.imageryError : null;
|
||||
enabled && !loading && diagnostic?.imageryError
|
||||
? t(diagnostic.imageryError)
|
||||
: null;
|
||||
const label = {
|
||||
none: t('Wind motion'),
|
||||
speed: t('Wind speed'),
|
||||
@@ -303,7 +306,7 @@ export function createWindLayer({
|
||||
? t('Loading forecast')
|
||||
: preparing
|
||||
? t('Preparing flow')
|
||||
: error ||
|
||||
: (error && t(error)) ||
|
||||
manifest?.reason ||
|
||||
imageryError ||
|
||||
(scalarMissing ? t('Selected field unavailable') : null) ||
|
||||
@@ -384,7 +387,7 @@ export function createWindLayer({
|
||||
model: model === 'ifs' ? 'ECMWF IFS' : 'GFS',
|
||||
field: label,
|
||||
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 })}` : ''}`,
|
||||
})}${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${t(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.',
|
||||
),
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
import { createWindRelief } from './relief.js';
|
||||
import { orderWeatherImagery } from '../weather/imageryOrder.js';
|
||||
import { NO_IMAGERY_HOST } from '../weather/imageryHost.js';
|
||||
import { msg } from '../../i18n/index.js';
|
||||
import { createWindGpuRendering } from './gpuRendering.js';
|
||||
import { advectParticle, sampleWind } from './model.js';
|
||||
import {
|
||||
@@ -344,7 +345,7 @@ export function createWindRendering({
|
||||
? !cesium.Primitive
|
||||
: !collection || !cesium.SingleTileImageryProvider
|
||||
) {
|
||||
imageryError = 'Globe imagery unavailable';
|
||||
imageryError = msg('Globe imagery unavailable');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -384,12 +385,12 @@ export function createWindRendering({
|
||||
imagery.alpha = overlay === 'temperature' ? 1 : 0.85;
|
||||
if (gpuActive) updateImageryFade(viewer.scene.camera, true);
|
||||
imageryErrorRemove = provider.errorEvent?.addEventListener(() => {
|
||||
imageryError = 'Globe field image unavailable';
|
||||
imageryError = msg('Globe field image unavailable');
|
||||
});
|
||||
viewer.scene?.requestRender?.();
|
||||
} catch {
|
||||
removeImagery();
|
||||
imageryError = 'Globe field image unavailable';
|
||||
imageryError = msg('Globe field image unavailable');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user