diff --git a/src/renderer/src/selector/localize.tsx b/src/renderer/src/selector/localize.tsx index a3d1b71f..e1c67839 100644 --- a/src/renderer/src/selector/localize.tsx +++ b/src/renderer/src/selector/localize.tsx @@ -1,23 +1,53 @@ -import { createSelector } from 'reselect'; import { IState } from '../model/state'; interface IStringsSelectorProps { layout: string; } -const langSelector = (state: IState) => state.strings.lang || 'en'; -const layoutSelector = (state: IState, props: IStringsSelectorProps) => - state.strings[props.layout]; +/* + * The localized strings live in redux as react-localization `LocalizedStrings` + * instances. Changing the UI language only flips `state.strings.lang`; the per + * layout instances keep the same reference and are updated by *mutating* them + * in place via `setLanguage`. That means returning the instance directly leaves + * its reference unchanged across a language switch, so `useSelector` (which + * compares by reference / shallowEqual) sees "no change" and skips the + * re-render, leaving stale strings on screen. + * + * To fix that we return a fresh, prototype-preserving snapshot whenever the + * (source, language) pair changes, so the reference changes exactly when the + * displayed strings change. We cache one snapshot per layout so the reference + * stays stable between renders for the same language -- important because some + * consumers call useSelector without shallowEqual and would otherwise re-render + * on every dispatch. + */ +interface ICacheEntry { + source: any; + lang: string; + value: any; +} +const snapshotCache = new Map(); + +export const localStrings = (state: IState, props: IStringsSelectorProps) => { + const source = state.strings[props.layout]; + const lang = state.strings.lang || 'en'; -export const localStrings = createSelector( - layoutSelector, - langSelector, - (layout, lang) => { - if (lang) { - layout.setLanguage(lang); - } - return layout; + const cached = snapshotCache.get(props.layout); + if (cached && cached.source === source && cached.lang === lang) { + return cached.value; } -); + + // Keep the shared instance in sync with the current language for any code + // that reads it directly, then snapshot it into a new object that keeps the + // LocalizedStrings prototype (so getString/formatString still work) but has a + // new identity so consumers re-render when the language changes. + source.setLanguage(lang); + const value = Object.create( + Object.getPrototypeOf(source), + Object.getOwnPropertyDescriptors(source) + ); + + snapshotCache.set(props.layout, { source, lang, value }); + return value; +}; export default localStrings;