Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 43 additions & 13 deletions src/renderer/src/selector/localize.tsx
Original file line number Diff line number Diff line change
@@ -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<string, ICacheEntry>();

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;
Loading