Skip to content

Commit 16bba01

Browse files
authored
Move L10nState from Redux to React component state (#5526)
Most importantly, this means we'll no longer store a ReactLocalization object in the Redux store. This makes it easier to use our Redux state and selectors from a library that runs outside of a browser context, for example directly in node. We weren't really using the Redux facilities for anything other than value storage. The localization, requestedLocales and direction properties were only needed locally within the AppLocalizationProvider component. The primaryLocale and requestL10n properties are also referenced from the LanguageSwitcher component; we're now using React context and a useL10n hook to keep that working. And for the togglePseudoStrategy console command we use a hacky exported function that accesses a global. Overall I think this is simpler; it certainly removes a whole bunch of code.
2 parents bda8d5b + b4913f8 commit 16bba01

File tree

15 files changed

+237
-367
lines changed

15 files changed

+237
-367
lines changed

src/actions/index.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
// @flow
66
import * as app from './app';
77
import * as icons from './icons';
8-
import * as l10n from './l10n';
98
import * as profileView from './profile-view';
109
import * as publish from './publish';
1110
import * as receiveProfile from './receive-profile';
@@ -16,7 +15,6 @@ export default Object.assign(
1615
{},
1716
app,
1817
icons,
19-
l10n,
2018
profileView,
2119
publish,
2220
receiveProfile,

src/actions/l10n.js

Lines changed: 0 additions & 44 deletions
This file was deleted.

src/components/app/AppLocalizationProvider.js

Lines changed: 73 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,10 @@
33
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
44
// @flow
55

6-
import explicitConnect from 'firefox-profiler/utils/connect';
76
import { LocalizationProvider, ReactLocalization } from '@fluent/react';
87
import { negotiateLanguages } from '@fluent/langneg';
98

109
import * as React from 'react';
11-
import {
12-
getLocalization,
13-
getPrimaryLocale,
14-
getDirection,
15-
getRequestedLocales,
16-
getPseudoStrategy,
17-
} from 'firefox-profiler/selectors/l10n';
18-
import { requestL10n, receiveL10n } from 'firefox-profiler/actions/l10n';
1910
import {
2011
AVAILABLE_LOCALES,
2112
DEFAULT_LOCALE,
@@ -25,13 +16,18 @@ import {
2516
} from 'firefox-profiler/app-logic/l10n';
2617

2718
import { ensureExists } from 'firefox-profiler/utils/flow';
28-
import type { Localization } from 'firefox-profiler/types';
29-
import type { ConnectedProps } from 'firefox-profiler/utils/connect';
19+
import type { Localization, PseudoStrategy } from 'firefox-profiler/types';
20+
import { L10nContext } from 'firefox-profiler/contexts/L10nContext';
21+
import type { L10nContextType } from 'firefox-profiler/contexts/L10nContext';
3022

3123
type FetchProps = {|
3224
+requestedLocales: null | string[],
33-
+pseudoStrategy: null | 'accented' | 'bidi',
34-
+receiveL10n: typeof receiveL10n,
25+
+pseudoStrategy: PseudoStrategy,
26+
+receiveL10n: (
27+
localization: Localization,
28+
primaryLocale: string,
29+
direction: 'ltr' | 'rtl'
30+
) => void,
3531
|};
3632

3733
/**
@@ -98,7 +94,7 @@ class AppLocalizationFetcher extends React.PureComponent<FetchProps> {
9894
}
9995

10096
type InitProps = {|
101-
+requestL10n: typeof requestL10n,
97+
+requestL10n: (locales: string[]) => void,
10298
+requestedLocales: null | string[],
10399
|};
104100

@@ -176,44 +172,56 @@ class AppLocalizationInit extends React.PureComponent<InitProps> {
176172
}
177173
}
178174

179-
type ProviderStateProps = {|
175+
type L10nState = {|
180176
+requestedLocales: null | string[],
181-
+pseudoStrategy: null | 'accented' | 'bidi',
177+
+pseudoStrategy: PseudoStrategy,
182178
+localization: Localization,
183179
+primaryLocale: string | null,
184180
+direction: 'ltr' | 'rtl',
185181
|};
186-
type ProviderOwnProps = {|
182+
183+
type ProviderProps = {|
187184
children: React.Node,
188185
|};
189-
type ProviderDispatchProps = {|
190-
+requestL10n: typeof requestL10n,
191-
+receiveL10n: typeof receiveL10n,
192-
|};
193186

194-
type ProviderProps = ConnectedProps<
195-
ProviderOwnProps,
196-
ProviderStateProps,
197-
ProviderDispatchProps,
198-
>;
187+
// Global reference to the AppLocalizationProvider instance for console access
188+
let globalL10nProvider: AppLocalizationProvider | null = null;
199189

200190
/**
201191
* This component is responsible for providing the fluent localization data to
202192
* the components. It also updates the locale attributes on the document.
203193
* Moreover it delegates to AppLocalizationInit and AppLocalizationFetcher the
204194
* handling of initialization, persisting and fetching the locales information.
205195
*/
206-
class AppLocalizationProviderImpl extends React.PureComponent<ProviderProps> {
196+
export class AppLocalizationProvider extends React.PureComponent<
197+
ProviderProps,
198+
L10nState,
199+
> {
200+
state: L10nState = {
201+
requestedLocales: null,
202+
pseudoStrategy: null,
203+
localization: new ReactLocalization([]),
204+
primaryLocale: null,
205+
direction: 'ltr',
206+
};
207+
207208
componentDidMount() {
208209
this._updateLocalizationDocumentAttribute();
210+
globalL10nProvider = this;
211+
}
212+
213+
componentWillUnmount() {
214+
if (globalL10nProvider === this) {
215+
globalL10nProvider = null;
216+
}
209217
}
210218

211219
componentDidUpdate() {
212220
this._updateLocalizationDocumentAttribute();
213221
}
214222

215223
_updateLocalizationDocumentAttribute() {
216-
const { primaryLocale, direction } = this.props;
224+
const { primaryLocale, direction } = this.state;
217225
if (!primaryLocale) {
218226
// The localization isn't ready.
219227
return;
@@ -223,52 +231,59 @@ class AppLocalizationProviderImpl extends React.PureComponent<ProviderProps> {
223231
ensureExists(document.documentElement).setAttribute('lang', primaryLocale);
224232
}
225233

234+
_requestL10n = (locales: string[]) => {
235+
this.setState({ requestedLocales: locales });
236+
};
237+
238+
_receiveL10n = (
239+
localization: Localization,
240+
primaryLocale: string,
241+
direction: 'ltr' | 'rtl'
242+
) => {
243+
this.setState({ localization, primaryLocale, direction });
244+
};
245+
246+
// Used by the global togglePseudoStrategy function for console access
247+
// eslint-disable-next-line react/no-unused-class-component-methods
248+
togglePseudoStrategy = (pseudoStrategy: PseudoStrategy) => {
249+
this.setState({ pseudoStrategy });
250+
};
251+
226252
render() {
227-
const {
253+
const { primaryLocale, localization, requestedLocales, pseudoStrategy } =
254+
this.state;
255+
const { children } = this.props;
256+
257+
const contextValue: L10nContextType = {
228258
primaryLocale,
229-
children,
230-
localization,
231-
requestedLocales,
232-
pseudoStrategy,
233-
receiveL10n,
234-
requestL10n,
235-
} = this.props;
259+
requestL10n: this._requestL10n,
260+
};
261+
236262
return (
237-
<>
263+
<L10nContext.Provider value={contextValue}>
238264
<AppLocalizationInit
239-
requestL10n={requestL10n}
265+
requestL10n={this._requestL10n}
240266
requestedLocales={requestedLocales}
241267
/>
242268
<AppLocalizationFetcher
243269
requestedLocales={requestedLocales}
244270
pseudoStrategy={pseudoStrategy}
245-
receiveL10n={receiveL10n}
271+
receiveL10n={this._receiveL10n}
246272
/>
247273
{/* if primaryLocale is null, the localization isn't ready */}
248274
{primaryLocale ? (
249275
<LocalizationProvider l10n={localization}>
250276
{children}
251277
</LocalizationProvider>
252278
) : null}
253-
</>
279+
</L10nContext.Provider>
254280
);
255281
}
256282
}
257-
export const AppLocalizationProvider = explicitConnect<
258-
ProviderOwnProps,
259-
ProviderStateProps,
260-
ProviderDispatchProps,
261-
>({
262-
mapStateToProps: (state) => ({
263-
localization: getLocalization(state),
264-
primaryLocale: getPrimaryLocale(state),
265-
direction: getDirection(state),
266-
requestedLocales: getRequestedLocales(state),
267-
pseudoStrategy: getPseudoStrategy(state),
268-
}),
269-
mapDispatchToProps: {
270-
requestL10n,
271-
receiveL10n,
272-
},
273-
component: AppLocalizationProviderImpl,
274-
});
283+
284+
// Hack: Expose a way to call globalL10nProvider.togglePseudoStrategy from window-console.js.
285+
export function togglePseudoStrategy(pseudoStrategy: PseudoStrategy) {
286+
if (globalL10nProvider) {
287+
globalL10nProvider.togglePseudoStrategy(pseudoStrategy);
288+
}
289+
}

src/components/app/LanguageSwitcher.js

Lines changed: 35 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -8,67 +8,45 @@ import * as React from 'react';
88

99
import { Localized } from '@fluent/react';
1010

11-
import { requestL10n } from 'firefox-profiler/actions/l10n';
12-
import { getPrimaryLocale } from 'firefox-profiler/selectors/l10n';
1311
import {
1412
AVAILABLE_LOCALES_TO_LOCALIZED_NAMES,
1513
AVAILABLE_LOCALES,
1614
} from 'firefox-profiler/app-logic/l10n';
17-
import explicitConnect from 'firefox-profiler/utils/connect';
18-
19-
import type { ConnectedProps } from 'firefox-profiler/utils/connect';
20-
21-
type OwnProps = {||};
22-
type StateProps = {|
23-
+primaryLocale: string | null,
24-
|};
25-
type DispatchProps = {|
26-
requestL10n: typeof requestL10n,
27-
|};
28-
29-
type Props = ConnectedProps<OwnProps, StateProps, DispatchProps>;
30-
class LanguageSwitcherImpl extends React.PureComponent<Props> {
31-
_onLocaleChange = (event: SyntheticEvent<HTMLSelectElement>) => {
32-
this.props.requestL10n([event.currentTarget.value]);
33-
};
15+
import { useL10n } from 'firefox-profiler/hooks/useL10n';
16+
17+
export function LanguageSwitcher(): React.Node {
18+
const { primaryLocale, requestL10n } = useL10n();
19+
20+
const onLocaleChange = React.useCallback(
21+
(event: SyntheticEvent<HTMLSelectElement>) => {
22+
requestL10n([event.currentTarget.value]);
23+
},
24+
[requestL10n]
25+
);
26+
27+
if (!primaryLocale) {
28+
// We're actually guaranteed primaryLocale is not null, because
29+
// AppLocalizationProvider doesn't render when it is. This check is mostly
30+
// so that Flow doesn't warn later.
31+
return null;
32+
}
3433

35-
render() {
36-
const { primaryLocale } = this.props;
37-
if (!primaryLocale) {
38-
// We're actually guaranteed primaryLocale is not null, because
39-
// AppLocalizationProvider doesn't render when it is. This check is mostly
40-
// so that Flow doesn't warn later.
41-
return null;
42-
}
43-
return (
44-
<Localized
45-
id="FooterLinks--languageSwitcher--select"
46-
attrs={{ title: true }}
34+
return (
35+
<Localized
36+
id="FooterLinks--languageSwitcher--select"
37+
attrs={{ title: true }}
38+
>
39+
<select
40+
className="appFooterLinksLanguageSwitcher"
41+
onChange={onLocaleChange}
42+
value={primaryLocale}
4743
>
48-
<select
49-
className="appFooterLinksLanguageSwitcher"
50-
onChange={this._onLocaleChange}
51-
value={primaryLocale}
52-
>
53-
{AVAILABLE_LOCALES.map((locale) => (
54-
<option value={locale} key={locale}>
55-
{AVAILABLE_LOCALES_TO_LOCALIZED_NAMES[locale] ?? locale}
56-
</option>
57-
))}
58-
</select>
59-
</Localized>
60-
);
61-
}
44+
{AVAILABLE_LOCALES.map((locale) => (
45+
<option value={locale} key={locale}>
46+
{AVAILABLE_LOCALES_TO_LOCALIZED_NAMES[locale] ?? locale}
47+
</option>
48+
))}
49+
</select>
50+
</Localized>
51+
);
6252
}
63-
64-
export const LanguageSwitcher = explicitConnect<
65-
OwnProps,
66-
StateProps,
67-
DispatchProps,
68-
>({
69-
component: LanguageSwitcherImpl,
70-
mapStateToProps: (state) => ({
71-
primaryLocale: getPrimaryLocale(state),
72-
}),
73-
mapDispatchToProps: { requestL10n },
74-
});

src/contexts/L10nContext.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
// @flow
5+
6+
import * as React from 'react';
7+
8+
// Create a context for L10n functionality
9+
export type L10nContextType = {
10+
primaryLocale: string | null,
11+
requestL10n: (locales: string[]) => void,
12+
};
13+
14+
export const L10nContext: React.Context<L10nContextType | null> =
15+
React.createContext(null);

0 commit comments

Comments
 (0)