mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
feat(i18n): presentation extraction — t() call sites, data-i18n templates, locale selector
Route every application-owned presentation string through the i18n core: t() call sites across the app/layers/ui/data/voice surfaces, data-i18n* attributes in the src/ui/templates/*.html markup (applied at boot by applyDocumentTranslations from src/standalone/application.js), and the dock locale selector (runtime-rendered from the offered pair — a single EN button while only English ships). Rebased onto current main, where the extraction follows the module splits: the shell wiring now lands in the owner modules (panelChrome, visualSettings, locationNavigation, shareRestoration, shellFeedback, applicationShell) and the voice status corpus in the realtime* modules; scene status lives in director.js + playbackAdapter.js. Modules shared with the node runtime (loadingFeedback, installationFeedback, regionalModel, keySetupCore, vessels ingestion) stay environment-neutral English and localize at their browser render sites (chip maps in shellFeedback/installations controls, the weather table in cockpitBriefing, keySetupRequirementEnvVars compositions), so the package-boundary graph stays clean; the browser boundary groups now own the i18n modules they reach (scripts/package-boundaries.json).
This commit is contained in:
1 parent
bde1e430ef
commit
284c4d4601
98 files changed
+3474
-1625
No files matched your search
+63
-15
@@ -26,6 +26,7 @@ import {
|
||||
getSceneAppendRecipeById,
|
||||
} from './recipes.js';
|
||||
import { sceneLayerPlan, sceneRequiresContextModeExit } from './scenePolicy.js';
|
||||
import { t } from '../i18n/index.js';
|
||||
import { createSceneDataPacks } from './dataPacks/controller.js';
|
||||
import { createSceneInteractions } from './interactions.js';
|
||||
import { createSceneSharing } from './sharing.js';
|
||||
@@ -61,6 +62,31 @@ import {
|
||||
SceneDocumentError,
|
||||
} from '../director/document.js';
|
||||
|
||||
// Built-in recipe DISPLAY names. Recipe ids, the URL 'scene' param, stored
|
||||
// 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.
|
||||
const RECIPE_TITLE_KEYS = Object.freeze({
|
||||
'flights-radar': 'setup.scenes.recipe.flightsRadar',
|
||||
'orbital-watch': 'setup.scenes.recipe.orbitalWatch',
|
||||
'thermal-threats': 'setup.scenes.recipe.thermalThreats',
|
||||
'city-overload': 'setup.scenes.recipe.cityOverload',
|
||||
'omniscience-pullback': 'setup.scenes.recipe.omnisciencePullback',
|
||||
});
|
||||
|
||||
/**
|
||||
* 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 key = RECIPE_TITLE_KEYS[scene?.id];
|
||||
if (!key) return scene?.title || '';
|
||||
const builtin = SCENE_RECIPES.find((recipe) => recipe.id === scene.id);
|
||||
return scene.title === builtin?.title ? t(key) : scene.title;
|
||||
}
|
||||
|
||||
/** @constant {string} localStorage key for the serialized project */
|
||||
const STORAGE_KEY = 'godsEyeView.sceneProject.v2';
|
||||
const STORAGE_CHECKPOINT_KEY = 'godsEyeView.sceneProject.checkpoint.v1';
|
||||
@@ -172,7 +198,7 @@ export class SceneDirector {
|
||||
this._presentation = {
|
||||
status: this._storageReadError
|
||||
? 'Saved project could not be read; storage preserved. Import a valid file to resume saving.'
|
||||
: 'Ready',
|
||||
: t('setup.scenes.statusReady'),
|
||||
progress: 0,
|
||||
runtime: '',
|
||||
playbackActive: false,
|
||||
@@ -370,7 +396,10 @@ export class SceneDirector {
|
||||
|
||||
_shotOutcome(type, scene, shot, index = scene.shots.indexOf(shot)) {
|
||||
if (type === 'shot-loaded')
|
||||
this._presentation.status = `Loaded: ${scene.title} / ${shot.title}`;
|
||||
this._presentation.status = t('setup.scenes.status.loaded', {
|
||||
scene: scene.title,
|
||||
shot: shot.title,
|
||||
});
|
||||
this._publish({
|
||||
type,
|
||||
sceneId: scene.id,
|
||||
@@ -389,7 +418,12 @@ export class SceneDirector {
|
||||
this._controls = new SceneControls({
|
||||
subscribe: (listener) => this.subscribe(listener),
|
||||
read: () => ({
|
||||
scenes: this._project.scenes,
|
||||
// Scene titles surface in the panel through SceneControls; built-in
|
||||
// recipe titles display their localized name until renamed by the user.
|
||||
scenes: this._project.scenes.map((scene) => ({
|
||||
...scene,
|
||||
title: displaySceneTitle(scene),
|
||||
})),
|
||||
selectedSceneId: this._selectedSceneId,
|
||||
selectedShotId: this._selectedShotId,
|
||||
running: this._running,
|
||||
@@ -920,14 +954,16 @@ export class SceneDirector {
|
||||
|
||||
const camera = this.styleManager.getCameraState();
|
||||
if (!camera) {
|
||||
this._updateStatus('Cannot capture shot: camera not ready');
|
||||
this._updateStatus(t('setup.scenes.status.captureCameraNotReady'));
|
||||
return;
|
||||
}
|
||||
|
||||
const shot = normalizeShot(
|
||||
{
|
||||
id: uid('shot'),
|
||||
title: `Shot ${scene.shots.length + 1}`,
|
||||
title: t('setup.scenes.status.shotTitleDefault', {
|
||||
n: scene.shots.length + 1,
|
||||
}),
|
||||
durationSec: DEFAULT_SHOT_DURATION_SEC,
|
||||
holdSec: DEFAULT_HOLD_SEC,
|
||||
camera,
|
||||
@@ -941,7 +977,12 @@ export class SceneDirector {
|
||||
this._selectedShotId = shot.id;
|
||||
this._saveProject();
|
||||
this._shotOutcome('shot-captured', scene, shot);
|
||||
this._updateStatus(`Captured: ${scene.title} / ${shot.title}`);
|
||||
this._updateStatus(
|
||||
t('setup.scenes.status.captured', {
|
||||
scene: scene.title,
|
||||
shot: shot.title,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -954,7 +995,7 @@ export class SceneDirector {
|
||||
|
||||
const shot = scene.shots.find((item) => item.id === this._selectedShotId);
|
||||
if (!shot) {
|
||||
this._updateStatus('Select a shot first');
|
||||
this._updateStatus(t('setup.scenes.status.selectShotFirst'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -969,7 +1010,12 @@ export class SceneDirector {
|
||||
|
||||
this._saveProject();
|
||||
this._shotOutcome('shot-updated', scene, shot);
|
||||
this._updateStatus(`Updated: ${scene.title} / ${shot.title}`);
|
||||
this._updateStatus(
|
||||
t('setup.scenes.status.updated', {
|
||||
scene: scene.title,
|
||||
shot: shot.title,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1564,7 +1610,7 @@ export class SceneDirector {
|
||||
this._claimingCamera = false;
|
||||
}
|
||||
if (claimed === false) {
|
||||
this._updateStatus('Camera unavailable — exit cockpit first');
|
||||
this._updateStatus(t('setup.scenes.status.cameraUnavailable'));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -1699,7 +1745,7 @@ export class SceneDirector {
|
||||
if (!queue.length) return { started: false, reason: 'scene-complete' };
|
||||
}
|
||||
if (!queue.length) {
|
||||
this._updateStatus('No shots to run');
|
||||
this._updateStatus(t('setup.scenes.status.noShotsToRun'));
|
||||
return { started: false, reason: 'no-shots' };
|
||||
}
|
||||
|
||||
@@ -1782,7 +1828,11 @@ export class SceneDirector {
|
||||
releaseOnFinish: preview,
|
||||
});
|
||||
} catch (error) {
|
||||
this._updateStatus(`Error: ${error.message || 'run failed'}`);
|
||||
this._updateStatus(
|
||||
t('setup.scenes.status.runError', {
|
||||
message: error.message || 'run failed',
|
||||
}),
|
||||
);
|
||||
this._logEvent('scene_run_error', {
|
||||
message: error.message || 'unknown error',
|
||||
});
|
||||
@@ -1956,7 +2006,7 @@ export class SceneDirector {
|
||||
link.click();
|
||||
link.remove();
|
||||
URL.revokeObjectURL(url);
|
||||
this._presentation.status = 'Project exported';
|
||||
this._presentation.status = t('setup.scenes.status.projectExported');
|
||||
this._publish({ type: 'project-exported', project: this._project });
|
||||
}
|
||||
|
||||
@@ -2218,9 +2268,7 @@ export class SceneDirector {
|
||||
`[Scenes] Could not exit ${mode}:`,
|
||||
result.error || 'unknown reason',
|
||||
);
|
||||
this._updateStatus(
|
||||
`Could not exit ${mode} — scene layers may be refused`,
|
||||
);
|
||||
this._updateStatus(t('setup.scenes.status.contextExitFailed', { mode }));
|
||||
this._logEvent('context_mode_exit_failed', {
|
||||
mode,
|
||||
error: result.error || null,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
/** Connect the portable Director runner to the existing scene presentation. */
|
||||
import { t } from '../i18n/index.js';
|
||||
|
||||
export function createScenePlaybackAdapter(director, defaultShotDurationSec) {
|
||||
let timing;
|
||||
let travel;
|
||||
@@ -14,7 +16,12 @@ export function createScenePlaybackAdapter(director, defaultShotDurationSec) {
|
||||
director._renderSceneSelect();
|
||||
director._renderShotList();
|
||||
director._updateStatus(
|
||||
`Running ${index + 1}/${total}: ${scene.title} / ${shot.title}`,
|
||||
t('setup.scenes.status.runningShot', {
|
||||
index: index + 1,
|
||||
total,
|
||||
scene: scene.title,
|
||||
shot: shot.title,
|
||||
}),
|
||||
);
|
||||
director._logEvent('shot_start', {
|
||||
sceneId: scene.id,
|
||||
@@ -71,7 +78,7 @@ export function createScenePlaybackAdapter(director, defaultShotDurationSec) {
|
||||
},
|
||||
complete() {
|
||||
director._setProgress(1);
|
||||
director._updateStatus('Scene run complete');
|
||||
director._updateStatus(t('setup.scenes.status.runComplete'));
|
||||
director._logEvent('scene_run_complete', {});
|
||||
},
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user