From 0e45bcdf14df1bad02ed31d523ee445e986e2314 Mon Sep 17 00:00:00 2001 From: German Khokhlov Date: Thu, 29 Oct 2026 14:52:45 +0400 Subject: [PATCH] i18n(de): address code review findings Localize a legacy Nepal base shot only when the applied pack marker binds that title to this exact shot id, so a user shot renamed to a built-in title stays as typed in every locale. Co-Authored-By: Claude Opus 5.5 --- src/scenes/recipes.js | 27 ++++++------- src/scenes/sceneTitleDisplay.test.mjs | 17 ++++++++- src/scenes/shotTitleProvenance.test.mjs | 51 +++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 15 deletions(-) create mode 100644 src/scenes/shotTitleProvenance.test.mjs diff --git a/src/scenes/recipes.js b/src/scenes/recipes.js index 081fa1db4..a5938af40 100644 --- a/src/scenes/recipes.js +++ b/src/scenes/recipes.js @@ -1071,7 +1071,7 @@ export function displayShotTitle(title, shot = undefined, scene = undefined) { const fromRecipe = shot ? Boolean(shot.sourcePackId) || recipeDeclaresShotTitle(scene?.id, text) || - shotPackDeclaresBaseTitle(scene, text) + shotPackBindsShot(scene, text, shot) : true; if (fromRecipe && BUILTIN_SHOT_TITLES.has(text)) return t(text); const numbered = /^Shot ([1-9]\d*)$/.exec(text); @@ -1079,21 +1079,22 @@ export function displayShotTitle(title, shot = undefined, scene = undefined) { } /** - * Whether a shot pack that upgraded this scene declares the title for one of - * its legacy base shots. The upgrade rebuilds those shots without a - * sourcePackId (see SceneDirector._bootstrapLegacyShotPacks), so the pack's - * own shots in the scene vouch for them. - * @param {{shots?: Array<{sourcePackId?: string}>}} [scene] + * Whether a shot pack applied to this scene binds this title to this exact + * shot. The legacy upgrade rebuilds the base shots without a sourcePackId + * (see SceneDirector._bootstrapLegacyShotPacks), but the pack marker records + * their ids, so a user shot that only shares the title is not vouched for. + * @param {{appliedShotPacks?: Array<{shotBindings?: Object}>}} [scene] * @param {string} title + * @param {{id?: string}} shot * @returns {boolean} */ -function shotPackDeclaresBaseTitle(scene, title) { - const shots = Array.isArray(scene?.shots) ? scene.shots : []; - return SCENE_APPEND_RECIPES.some( - (pack) => - pack.legacySceneBootstrap?.cameraPath?.some( - (keyframe) => keyframe?.title === title, - ) && shots.some((item) => item?.sourcePackId === pack.id), +function shotPackBindsShot(scene, title, shot) { + const packs = Array.isArray(scene?.appliedShotPacks) + ? scene.appliedShotPacks + : []; + return ( + Boolean(shot?.id) && + packs.some((pack) => pack?.shotBindings?.[title] === shot.id) ); } diff --git a/src/scenes/sceneTitleDisplay.test.mjs b/src/scenes/sceneTitleDisplay.test.mjs index a5d157e64..f7e626ccc 100644 --- a/src/scenes/sceneTitleDisplay.test.mjs +++ b/src/scenes/sceneTitleDisplay.test.mjs @@ -151,12 +151,25 @@ test('the base shots of a scene the Nepal pack upgraded localize', () => { setLocale('de'); const title = pack.legacySceneBootstrap.cameraPath[0].title; const base = { id: 'shot-base', title }; + // A user shot renamed to the same built-in title is not the bound one. + const renamed = { id: 'shot-user', title }; const upgraded = { id: 'scene-upgraded', - shots: [base, { id: 'shot-pack', title: 'x', sourcePackId: pack.id }], + shots: [ + base, + { id: 'shot-pack', title: 'x', sourcePackId: pack.id }, + renamed, + ], + appliedShotPacks: [ + { id: pack.id, version: 1, shotBindings: { [title]: base.id } }, + ], }; - assert.equal(displayShotTitle(title, base, upgraded), displayShotTitle(title)); + assert.equal( + displayShotTitle(title, base, upgraded), + displayShotTitle(title), + ); assert.notEqual(displayShotTitle(title), title); + assert.equal(displayShotTitle(title, renamed, upgraded), title); // The same title in a scene no pack touched stays as typed. assert.equal( displayShotTitle(title, base, { id: 'scene-user', shots: [base] }), diff --git a/src/scenes/shotTitleProvenance.test.mjs b/src/scenes/shotTitleProvenance.test.mjs new file mode 100644 index 000000000..8edd877cd --- /dev/null +++ b/src/scenes/shotTitleProvenance.test.mjs @@ -0,0 +1,51 @@ +// Reproduction: a user-authored shot whose typed title equals a legacy base +// title of the Nepal pack must stay as typed in an upgraded scene. +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { SCENE_APPEND_RECIPES, displayShotTitle } from './recipes.js'; +import { setLocale } from '../i18n/index.js'; + +test.afterEach(() => setLocale('en')); + +test('a user shot named like a legacy base shot stays as typed in an upgraded scene', () => { + const pack = SCENE_APPEND_RECIPES.find( + (recipe) => recipe.legacySceneBootstrap?.cameraPath?.length, + ); + assert.ok(pack, 'Nepal append pack with legacy bootstrap'); + const baseTitles = pack.legacySceneBootstrap.cameraPath.map((k) => k.title); + // Shape written by SceneDirector._bootstrapLegacyShotPacks + appendShotPack: + // base shots without sourcePackId, pack shots with it, marker bindings. + const base = baseTitles.map((title, i) => ({ id: `base-${i}`, title })); + const packShot = { + id: 'pack-0', + title: 'Upper Valley Collapse', + sourcePackId: pack.id, + }; + // Captured by captureShot ("Shot 10"), then renamed by the user. + const userShot = { id: 'user-1', title: 'Bhote Koshi Flood Path' }; + const scene = { + id: 'scene-user-upgraded', + shots: [...base, packShot, userShot], + appliedShotPacks: [ + { + id: pack.id, + version: Number(pack.version) || 1, + shotBindings: Object.fromEntries(base.map((s) => [s.title, s.id])), + }, + ], + }; + for (const locale of ['de', 'ru', 'es']) { + setLocale(locale); + // The real base shot localizes (intended by the branch). + assert.notEqual( + displayShotTitle(base[5].title, base[5], scene), + base[5].title, + ); + // The user's shot must stay as typed. + assert.equal( + displayShotTitle(userShot.title, userShot, scene), + userShot.title, + `${locale}: user-typed title was translated`, + ); + } +});