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
+24
-23
@@ -23,6 +23,8 @@ import * as Cesium from 'cesium';
|
||||
import {
|
||||
SCENE_APPEND_RECIPES,
|
||||
SCENE_RECIPES,
|
||||
displaySceneTitle,
|
||||
displayShotTitle,
|
||||
getSceneAppendRecipeById,
|
||||
} from './recipes.js';
|
||||
import { sceneLayerPlan, sceneRequiresContextModeExit } from './scenePolicy.js';
|
||||
@@ -66,19 +68,7 @@ import {
|
||||
// project titles, and voice-matching titles all stay English (keep-English
|
||||
// boundary + persisted data); only the presentation of an UNRENAMED built-in
|
||||
// localizes, so an operator's renamed scene is never masked by the catalog.
|
||||
// Recipe titles are marked with msg() in recipes.js.
|
||||
|
||||
/**
|
||||
* Display title for a scene: the localized recipe name while the stored title
|
||||
* still matches the built-in verbatim, the stored title otherwise.
|
||||
* @param {{id?: string, title?: string}} scene
|
||||
* @returns {string}
|
||||
*/
|
||||
function displaySceneTitle(scene) {
|
||||
const builtin = SCENE_RECIPES.find((recipe) => recipe.id === scene?.id);
|
||||
if (!builtin?.title) return scene?.title || '';
|
||||
return scene.title === builtin.title ? t(builtin.title) : scene.title;
|
||||
}
|
||||
// Recipe and built-in shot titles are marked with msg() in recipes.js.
|
||||
|
||||
/** @constant {string} localStorage key for the serialized project */
|
||||
const STORAGE_KEY = 'godsEyeView.sceneProject.v2';
|
||||
@@ -390,8 +380,8 @@ export class SceneDirector {
|
||||
_shotOutcome(type, scene, shot, index = scene.shots.indexOf(shot)) {
|
||||
if (type === 'shot-loaded')
|
||||
this._presentation.status = t('Loaded: {scene} / {shot}', {
|
||||
scene: scene.title,
|
||||
shot: shot.title,
|
||||
scene: displaySceneTitle(scene),
|
||||
shot: displayShotTitle(shot.title),
|
||||
});
|
||||
this._publish({
|
||||
type,
|
||||
@@ -412,10 +402,15 @@ export class SceneDirector {
|
||||
subscribe: (listener) => this.subscribe(listener),
|
||||
read: () => ({
|
||||
// Scene titles surface in the panel through SceneControls; built-in
|
||||
// recipe titles display their localized name until renamed by the user.
|
||||
// recipe and shot titles display their localized name until renamed
|
||||
// by the user. Shots keep the stored title for editing and matching.
|
||||
scenes: this._project.scenes.map((scene) => ({
|
||||
...scene,
|
||||
title: displaySceneTitle(scene),
|
||||
shots: scene.shots.map((shot) => ({
|
||||
...shot,
|
||||
displayTitle: displayShotTitle(shot.title),
|
||||
})),
|
||||
})),
|
||||
selectedSceneId: this._selectedSceneId,
|
||||
selectedShotId: this._selectedShotId,
|
||||
@@ -990,8 +985,8 @@ export class SceneDirector {
|
||||
this._shotOutcome('shot-captured', scene, shot);
|
||||
this._updateStatus(
|
||||
t('Captured: {scene} / {shot}', {
|
||||
scene: scene.title,
|
||||
shot: shot.title,
|
||||
scene: displaySceneTitle(scene),
|
||||
shot: displayShotTitle(shot.title),
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -1023,8 +1018,8 @@ export class SceneDirector {
|
||||
this._shotOutcome('shot-updated', scene, shot);
|
||||
this._updateStatus(
|
||||
t('Updated: {scene} / {shot}', {
|
||||
scene: scene.title,
|
||||
shot: shot.title,
|
||||
scene: displaySceneTitle(scene),
|
||||
shot: displayShotTitle(shot.title),
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -1134,7 +1129,7 @@ export class SceneDirector {
|
||||
if (!token.cancelled)
|
||||
this._updateStatus(
|
||||
t('Could not leave scene: {scene}', {
|
||||
scene: previousScene.title,
|
||||
scene: displaySceneTitle(previousScene),
|
||||
}),
|
||||
);
|
||||
return;
|
||||
@@ -1187,7 +1182,10 @@ export class SceneDirector {
|
||||
});
|
||||
if (this._loadAbort === controller) this._loadAbort = null;
|
||||
this._updateStatus(
|
||||
t('Seeked: {scene} / {shot}', { scene: scene.title, shot: shot.title }),
|
||||
t('Seeked: {scene} / {shot}', {
|
||||
scene: displaySceneTitle(scene),
|
||||
shot: displayShotTitle(shot.title),
|
||||
}),
|
||||
);
|
||||
this._activateInteractions(scene, shot);
|
||||
return { started: true, shotId };
|
||||
@@ -1551,7 +1549,10 @@ export class SceneDirector {
|
||||
seeking: true,
|
||||
});
|
||||
this._updateStatus(
|
||||
t('Seeked: {scene} / {shot}', { scene: scene.title, shot: shot.title }),
|
||||
t('Seeked: {scene} / {shot}', {
|
||||
scene: displaySceneTitle(scene),
|
||||
shot: displayShotTitle(shot.title),
|
||||
}),
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/** Connect the portable Director runner to the existing scene presentation. */
|
||||
import { t } from '../i18n/index.js';
|
||||
import { displaySceneTitle, displayShotTitle } from './recipes.js';
|
||||
|
||||
export function createScenePlaybackAdapter(director, defaultShotDurationSec) {
|
||||
let timing;
|
||||
@@ -19,8 +20,8 @@ export function createScenePlaybackAdapter(director, defaultShotDurationSec) {
|
||||
t('Running {index}/{total}: {scene} / {shot}', {
|
||||
index: index + 1,
|
||||
total,
|
||||
scene: scene.title,
|
||||
shot: shot.title,
|
||||
scene: displaySceneTitle(scene),
|
||||
shot: displayShotTitle(shot.title),
|
||||
}),
|
||||
);
|
||||
director._logEvent('shot_start', {
|
||||
|
||||
+51
-1
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { expandNepalEvidencePack } from './nepalEvidencePack.js';
|
||||
import { msg } from '../i18n/index.js';
|
||||
import { msg, t } from '../i18n/index.js';
|
||||
|
||||
const BHOTE_KOSHI_NEPAL_BASE_RECIPE = Object.freeze({
|
||||
id: 'bhote-koshi-nepal-evidence-pack',
|
||||
@@ -1011,3 +1011,53 @@ export function getSceneRecipeById(id) {
|
||||
export function getSceneAppendRecipeById(id) {
|
||||
return SCENE_APPEND_RECIPES.find((recipe) => recipe.id === id) || null;
|
||||
}
|
||||
|
||||
// Built-in shot titles stay English in recipes and saved projects (upgrades
|
||||
// and bindings match them verbatim). These are their display names; a title
|
||||
// the user typed shows as typed. Place names inside them stay Latin.
|
||||
const BUILTIN_SHOT_TITLES = new Set([
|
||||
msg('Global Incident Context'),
|
||||
msg('Nepal-Focused Globe Rotation'),
|
||||
msg('Bhote Koshi Regional Approach'),
|
||||
msg('Bhote Koshi Nearby Cities'),
|
||||
msg('Bhote Koshi Incident Corridor'),
|
||||
msg('Bhote Koshi Flood Path'),
|
||||
msg('Bhote Koshi Corridor Overview'),
|
||||
msg('Bhote Koshi Upper Valley'),
|
||||
msg('Upper Valley Collapse'),
|
||||
msg('Debris-Dammed Lake'),
|
||||
msg('Nepal-China Border Gate'),
|
||||
msg('Timure Evidence Cluster'),
|
||||
msg('Syabru Besi Passage'),
|
||||
msg('Bidur / Trishuli Consequence'),
|
||||
msg('Second landslide'),
|
||||
msg('Mailung / Upper Trishuli bridge'),
|
||||
msg('Dandagaun viewpoint restaurant'),
|
||||
msg('Devighat / Taadi Khola Bridge'),
|
||||
msg('Final view'),
|
||||
]);
|
||||
|
||||
/**
|
||||
* Display title for a scene: the localized recipe name while the stored title
|
||||
* still matches the built-in verbatim, the stored title otherwise.
|
||||
* @param {{id?: string, title?: string}} scene
|
||||
* @returns {string}
|
||||
*/
|
||||
export function displaySceneTitle(scene) {
|
||||
const builtin = SCENE_RECIPES.find((recipe) => recipe.id === scene?.id);
|
||||
if (!builtin?.title) return scene?.title || '';
|
||||
return scene.title === builtin.title ? t(builtin.title) : scene.title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display title for a shot: built-in titles and the numbered "Shot N"
|
||||
* fallback localize; any other title shows as stored.
|
||||
* @param {string} title
|
||||
* @returns {string}
|
||||
*/
|
||||
export function displayShotTitle(title) {
|
||||
const text = typeof title === 'string' ? title : '';
|
||||
if (BUILTIN_SHOT_TITLES.has(text)) return t(text);
|
||||
const numbered = /^Shot (\d+)$/.exec(text);
|
||||
return numbered ? t('Shot {n}', { n: Number(numbered[1]) }) : text;
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import { displaySceneTitle, displayShotTitle } from './recipes.js';
|
||||
|
||||
const DEFAULT_SHOT_DURATION_SEC = 4;
|
||||
const clamp01 = (value) => Math.max(0, Math.min(1, value));
|
||||
const deepClone = (value) => JSON.parse(JSON.stringify(value));
|
||||
@@ -63,8 +65,10 @@ export function layerStatesForShot(
|
||||
state.params.sceneContext = {
|
||||
sceneId: scene.id,
|
||||
sceneTitle: scene.title,
|
||||
sceneDisplayTitle: displaySceneTitle(scene),
|
||||
shotId: shot.id,
|
||||
shotTitle: shot.title,
|
||||
shotDisplayTitle: displayShotTitle(shot.title),
|
||||
shotIndex,
|
||||
shotCount: scene.shots.length,
|
||||
durationSec: shot.durationSec || DEFAULT_SHOT_DURATION_SEC,
|
||||
|
||||
Reference in new issue
Block a user