From 250ad254a67d20585fdbf1cf29ba0758ebe29612 Mon Sep 17 00:00:00 2001 From: sganggs Date: Tue, 6 Oct 2026 18:35:33 +0800 Subject: [PATCH] =?UTF-8?q?feedback5=20W5S:=20a=20language=20switch=20in?= =?UTF-8?q?=20the=20middle=20of=20a=20screen=20re-reads=20the=20game=20tex?= =?UTF-8?q?ts=20=E2=80=94=20useGameData(),=20the=20=E5=B9=B2=E5=91=98?= =?UTF-8?q?=E8=B0=83=E9=85=8D=20/=20=E5=B9=B2=E5=91=98=E6=8C=81=E6=9C=89?= =?UTF-8?q?=20rosters,=20the=20strategy=20list=20and=20the=20open=20detail?= =?UTF-8?q?=20card=20list=20data.locale()=20among=20their=20memo=20depende?= =?UTF-8?q?ncies=20(they=20kept=20the=20records=20of=20the=20language=20th?= =?UTF-8?q?ey=20were=20built=20in);=20docs/I18N.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/I18N.md | 3 ++- public/js/screens/bandDraft.js | 2 +- public/js/screens/game.js | 2 +- public/js/screens/loadout.js | 2 +- public/js/screens/ownership.js | 2 +- public/js/ui/gameComponents.js | 3 ++- 6 files changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/I18N.md b/docs/I18N.md index 7e5bdf95..7b3dcfbf 100644 --- a/docs/I18N.md +++ b/docs/I18N.md @@ -40,7 +40,8 @@ html`${tParts('第 {r} 回合 · 最终攻势', { r: html`$ current language (§2). Do not wrap them in `t()`. - **Re-rendering**: `main.js` App subscribes with `useLang()` (`public/js/ui/lang.js`), so the whole tree re-renders on a switch. A component that caches text outside rendering (a `useMemo` returning text, a module-level string) must - depend on the language (`useLang()`) or move the `t()` call into rendering. + depend on the language (`useLang()`) or move the `t()` call into rendering; a memo over data records (the roster, the + strategy list, `useGameData()`) lists `data.locale()` among its dependencies, so the game texts follow the switch. - Font samples, data keys and other Chinese that is not UI text can carry `// i18n-ignore` on the line. ### Translating diff --git a/public/js/screens/bandDraft.js b/public/js/screens/bandDraft.js index 0f0b3a03..46d9e791 100644 --- a/public/js/screens/bandDraft.js +++ b/public/js/screens/bandDraft.js @@ -179,7 +179,7 @@ export function BandDraftScreen() { const mode = gd.config?.modes?.[pub?.modeId]; const offBonds = modeOffBonds(mode); // the bonds this mode never activates (标准: 10 of 23) const solo = roomSolo || mode?.type === 'SINGLE' || String(pub?.modeId || '').includes('single'); - const bands = useMemo(() => allowedBands(gd.list('bands'), mode?.type || (solo ? 'SINGLE' : 'MULTI')), [gd.ready, mode?.type, solo]); + const bands = useMemo(() => allowedBands(gd.list('bands'), mode?.type || (solo ? 'SINGLE' : 'MULTI')), [gd.ready, mode?.type, solo, data.locale()]); const players = sortedPlayers(pub); const draft = normalizeDraft(pub?.draft, players); const myPick = draft.picks.get(myId) || priv?.bandId || null; diff --git a/public/js/screens/game.js b/public/js/screens/game.js index ab58efca..43563c64 100644 --- a/public/js/screens/game.js +++ b/public/js/screens/game.js @@ -1044,7 +1044,7 @@ function MatchScreen() { const u = (Array.isArray(field?.units) ? field.units : []).find((x) => x && x.id === detail.unitId); return u ? { ...detail, unit: u } : detail; }, [detail, field]); - const resolved = useMemo(() => resolveDetail(detailTarget, placeCtx.pieces, { priv, backups: gd.backups }), [detailTarget, placeCtx, gd.ready]); + const resolved = useMemo(() => resolveDetail(detailTarget, placeCtx.pieces, { priv, backups: gd.backups }), [detailTarget, placeCtx, gd.ready, data.locale()]); useEffect(() => { if (detail && !resolved && detail.kind === 'piece') setDetail(null); }, [resolved]); const snapHp = (() => { const id = resolved?.unitId; diff --git a/public/js/screens/loadout.js b/public/js/screens/loadout.js index c716f919..07728cab 100644 --- a/public/js/screens/loadout.js +++ b/public/js/screens/loadout.js @@ -403,7 +403,7 @@ function LoadoutScreen({ st }) { const m = data.get('assets'); const getChess = (id) => data.lookup('chess', id); const getBond = (id) => data.lookup('bonds', id); - const roster = useMemo(() => rosterOf(data.list('chess')), [ready]); + const roster = useMemo(() => rosterOf(data.list('chess')), [ready, data.locale()]); // (names follow a language switch) const bonds = useMemo(() => { const used = new Set(roster.flatMap((c) => c.bonds || [])); return (data.list('bonds') || []).filter((b) => b && used.has(b.bondId)) diff --git a/public/js/screens/ownership.js b/public/js/screens/ownership.js index 579b4b2d..d1ecc0e8 100644 --- a/public/js/screens/ownership.js +++ b/public/js/screens/ownership.js @@ -23,7 +23,7 @@ const ROMAN = ['', 'I', 'II', 'III', 'IV', 'V', 'VI']; /** The roster of the tab (data.list('chess'), memoised on the data being ready). */ export function useOwnershipRoster(ready) { - return useMemo(() => ownershipRoster(data.list('chess')), [ready]); + return useMemo(() => ownershipRoster(data.list('chess')), [ready, data.locale()]); } /** One chess: the operator → its stand-in, and the 持有 switch (the whole card is the switch). */ diff --git a/public/js/ui/gameComponents.js b/public/js/ui/gameComponents.js index 3ba221fd..b531e986 100644 --- a/public/js/ui/gameComponents.js +++ b/public/js/ui/gameComponents.js @@ -24,7 +24,8 @@ export const GAME_FILES = ['config', 'assets', 'chess', 'bonds', 'items', 'bands */ export function useGameData() { const ready = useData(...GAME_FILES); - return useMemo(() => makeLookups(ready), [ready]); + // (config / factions / choices are read once per memo: a language switch reads them again in the new locale) + return useMemo(() => makeLookups(ready), [ready, data.locale()]); } /** A 自选 operator's summon record (data/backups.json `tokens`, 0.2.0), or null. */