Skip to content

Commit

Permalink
Merge pull request #26079 from software-mansion-labs/maciejdobosz/exp…
Browse files Browse the repository at this point in the history
…-170-250-migrate-withlocalizejs-to-function-component

Rewrite to functional component
  • Loading branch information
roryabraham authored Sep 29, 2023
2 parents bcc8a63 + f5b12d2 commit 334523e
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 153 deletions.
2 changes: 1 addition & 1 deletion .storybook/preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import './fonts.css';
import ComposeProviders from '../src/components/ComposeProviders';
import HTMLEngineProvider from '../src/components/HTMLEngineProvider';
import OnyxProvider from '../src/components/OnyxProvider';
import {LocaleContextProvider} from '../src/components/withLocalize';
import {LocaleContextProvider} from '../src/components/LocaleContextProvider';
import {KeyboardStateProvider} from '../src/components/withKeyboardState';
import {EnvironmentProvider} from '../src/components/withEnvironment';
import {WindowDimensionsProvider} from '../src/components/withWindowDimensions';
Expand Down
2 changes: 1 addition & 1 deletion src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {PickerStateProvider} from 'react-native-picker-select';
import CustomStatusBar from './components/CustomStatusBar';
import ErrorBoundary from './components/ErrorBoundary';
import Expensify from './Expensify';
import {LocaleContextProvider} from './components/withLocalize';
import {LocaleContextProvider} from './components/LocaleContextProvider';
import OnyxProvider from './components/OnyxProvider';
import HTMLEngineProvider from './components/HTMLEngineProvider';
import PopoverContextProvider from './components/PopoverProvider';
Expand Down
135 changes: 135 additions & 0 deletions src/components/LocaleContextProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import React, {createContext, useMemo} from 'react';
import PropTypes from 'prop-types';
import {withOnyx} from 'react-native-onyx';
import lodashGet from 'lodash/get';

import ONYXKEYS from '../ONYXKEYS';
import * as Localize from '../libs/Localize';
import DateUtils from '../libs/DateUtils';
import * as NumberFormatUtils from '../libs/NumberFormatUtils';
import * as LocaleDigitUtils from '../libs/LocaleDigitUtils';
import CONST from '../CONST';
import compose from '../libs/compose';
import withCurrentUserPersonalDetails from './withCurrentUserPersonalDetails';
import * as LocalePhoneNumber from '../libs/LocalePhoneNumber';

const LocaleContext = createContext(null);

const localeProviderPropTypes = {
/** The user's preferred locale e.g. 'en', 'es-ES' */
preferredLocale: PropTypes.string,

/** Actual content wrapped by this component */
children: PropTypes.node.isRequired,

/** The current user's personalDetails */
currentUserPersonalDetails: PropTypes.shape({
/** Timezone of the current user */
timezone: PropTypes.shape({
/** Value of the selected timezone */
selected: PropTypes.string,
}),
}),
};

const localeProviderDefaultProps = {
preferredLocale: CONST.LOCALES.DEFAULT,
currentUserPersonalDetails: {},
};

function LocaleContextProvider({children, currentUserPersonalDetails, preferredLocale}) {
const selectedTimezone = useMemo(() => lodashGet(currentUserPersonalDetails, 'timezone.selected'), [currentUserPersonalDetails]);

/**
* @param {String} phrase
* @param {Object} [variables]
* @returns {String}
*/
const translate = useMemo(() => (phrase, variables) => Localize.translate(preferredLocale, phrase, variables), [preferredLocale]);

/**
* @param {Number} number
* @param {Intl.NumberFormatOptions} options
* @returns {String}
*/
const numberFormat = useMemo(() => (number, options) => NumberFormatUtils.format(preferredLocale, number, options), [preferredLocale]);

/**
* @param {String} datetime
* @returns {String}
*/
const datetimeToRelative = useMemo(() => (datetime) => DateUtils.datetimeToRelative(preferredLocale, datetime), [preferredLocale]);

/**
* @param {String} datetime - ISO-formatted datetime string
* @param {Boolean} [includeTimezone]
* @param {Boolean} isLowercase
* @returns {String}
*/
const datetimeToCalendarTime = useMemo(
() =>
(datetime, includeTimezone, isLowercase = false) =>
DateUtils.datetimeToCalendarTime(preferredLocale, datetime, includeTimezone, selectedTimezone, isLowercase),
[preferredLocale, selectedTimezone],
);

/**
* Updates date-fns internal locale to the user preferredLocale
*/
const updateLocale = useMemo(() => () => DateUtils.setLocale(preferredLocale), [preferredLocale]);

/**
* @param {String} phoneNumber
* @returns {String}
*/
const formatPhoneNumber = LocalePhoneNumber.formatPhoneNumber;

/**
* @param {String} digit
* @returns {String}
*/
const toLocaleDigit = useMemo(() => (digit) => LocaleDigitUtils.toLocaleDigit(preferredLocale, digit), [preferredLocale]);

/**
* @param {String} localeDigit
* @returns {String}
*/
const fromLocaleDigit = useMemo(() => (localeDigit) => LocaleDigitUtils.fromLocaleDigit(preferredLocale, localeDigit), [preferredLocale]);

/**
* The context this component exposes to child components
* @returns {object} translation util functions and locale
*/
const contextValue = useMemo(
() => ({
translate,
numberFormat,
datetimeToRelative,
datetimeToCalendarTime,
updateLocale,
formatPhoneNumber,
toLocaleDigit,
fromLocaleDigit,
preferredLocale,
}),
[translate, numberFormat, datetimeToRelative, datetimeToCalendarTime, updateLocale, formatPhoneNumber, toLocaleDigit, fromLocaleDigit, preferredLocale],
);

return <LocaleContext.Provider value={contextValue}>{children}</LocaleContext.Provider>;
}

LocaleContextProvider.propTypes = localeProviderPropTypes;
LocaleContextProvider.defaultProps = localeProviderDefaultProps;

const Provider = compose(
withCurrentUserPersonalDetails,
withOnyx({
preferredLocale: {
key: ONYXKEYS.NVP_PREFERRED_LOCALE,
},
}),
)(LocaleContextProvider);

Provider.displayName = 'withOnyx(LocaleContextProvider)';

export {Provider as LocaleContextProvider, LocaleContext};
152 changes: 3 additions & 149 deletions src/components/withLocalize.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,7 @@
import React, {createContext, forwardRef} from 'react';
import React, {forwardRef} from 'react';
import PropTypes from 'prop-types';
import {withOnyx} from 'react-native-onyx';
import lodashGet from 'lodash/get';
import {LocaleContext} from './LocaleContextProvider';
import getComponentDisplayName from '../libs/getComponentDisplayName';
import ONYXKEYS from '../ONYXKEYS';
import * as Localize from '../libs/Localize';
import DateUtils from '../libs/DateUtils';
import * as NumberFormatUtils from '../libs/NumberFormatUtils';
import * as LocaleDigitUtils from '../libs/LocaleDigitUtils';
import CONST from '../CONST';
import compose from '../libs/compose';
import withCurrentUserPersonalDetails from './withCurrentUserPersonalDetails';
import * as LocalePhoneNumber from '../libs/LocalePhoneNumber';

const LocaleContext = createContext(null);

const withLocalizePropTypes = {
/** Returns translated string for given locale and phrase */
Expand Down Expand Up @@ -42,140 +30,6 @@ const withLocalizePropTypes = {
toLocaleDigit: PropTypes.func.isRequired,
};

const localeProviderPropTypes = {
/** The user's preferred locale e.g. 'en', 'es-ES' */
preferredLocale: PropTypes.string,

/** Actual content wrapped by this component */
children: PropTypes.node.isRequired,

/** The current user's personalDetails */
currentUserPersonalDetails: PropTypes.shape({
/** Timezone of the current user */
timezone: PropTypes.shape({
/** Value of the selected timezone */
selected: PropTypes.string,
}),
}),
};

const localeProviderDefaultProps = {
preferredLocale: CONST.LOCALES.DEFAULT,
currentUserPersonalDetails: {},
};

class LocaleContextProvider extends React.Component {
shouldComponentUpdate(nextProps) {
return (
nextProps.preferredLocale !== this.props.preferredLocale ||
lodashGet(nextProps, 'currentUserPersonalDetails.timezone.selected') !== lodashGet(this.props, 'currentUserPersonalDetails.timezone.selected')
);
}

/**
* The context this component exposes to child components
* @returns {object} translation util functions and locale
*/
getContextValue() {
return {
translate: this.translate.bind(this),
numberFormat: this.numberFormat.bind(this),
datetimeToRelative: this.datetimeToRelative.bind(this),
datetimeToCalendarTime: this.datetimeToCalendarTime.bind(this),
updateLocale: this.updateLocale.bind(this),
formatPhoneNumber: this.formatPhoneNumber.bind(this),
fromLocaleDigit: this.fromLocaleDigit.bind(this),
toLocaleDigit: this.toLocaleDigit.bind(this),
preferredLocale: this.props.preferredLocale,
};
}

/**
* @param {String} phrase
* @param {Object} [variables]
* @returns {String}
*/
translate(phrase, variables) {
return Localize.translate(this.props.preferredLocale, phrase, variables);
}

/**
* @param {Number} number
* @param {Intl.NumberFormatOptions} options
* @returns {String}
*/
numberFormat(number, options) {
return NumberFormatUtils.format(this.props.preferredLocale, number, options);
}

/**
* @param {String} datetime
* @returns {String}
*/
datetimeToRelative(datetime) {
return DateUtils.datetimeToRelative(this.props.preferredLocale, datetime);
}

/**
* @param {String} datetime - ISO-formatted datetime string
* @param {Boolean} [includeTimezone]
* @param {Boolean} isLowercase
* @returns {String}
*/
datetimeToCalendarTime(datetime, includeTimezone, isLowercase = false) {
return DateUtils.datetimeToCalendarTime(this.props.preferredLocale, datetime, includeTimezone, lodashGet(this.props, 'currentUserPersonalDetails.timezone.selected'), isLowercase);
}

/**
* Updates date-fns internal locale to the user preferredLocale
*/
updateLocale() {
DateUtils.setLocale(this.props.preferredLocale);
}

/**
* @param {String} phoneNumber
* @returns {String}
*/
formatPhoneNumber(phoneNumber) {
return LocalePhoneNumber.formatPhoneNumber(phoneNumber);
}

/**
* @param {String} digit
* @returns {String}
*/
toLocaleDigit(digit) {
return LocaleDigitUtils.toLocaleDigit(this.props.preferredLocale, digit);
}

/**
* @param {String} localeDigit
* @returns {String}
*/
fromLocaleDigit(localeDigit) {
return LocaleDigitUtils.fromLocaleDigit(this.props.preferredLocale, localeDigit);
}

render() {
return <LocaleContext.Provider value={this.getContextValue()}>{this.props.children}</LocaleContext.Provider>;
}
}

LocaleContextProvider.propTypes = localeProviderPropTypes;
LocaleContextProvider.defaultProps = localeProviderDefaultProps;

const Provider = compose(
withCurrentUserPersonalDetails,
withOnyx({
preferredLocale: {
key: ONYXKEYS.NVP_PREFERRED_LOCALE,
},
}),
)(LocaleContextProvider);

Provider.displayName = 'withOnyx(LocaleContextProvider)';

export default function withLocalize(WrappedComponent) {
const WithLocalize = forwardRef((props, ref) => (
<LocaleContext.Consumer>
Expand All @@ -196,4 +50,4 @@ export default function withLocalize(WrappedComponent) {
return WithLocalize;
}

export {withLocalizePropTypes, Provider as LocaleContextProvider, LocaleContext};
export {withLocalizePropTypes};
2 changes: 1 addition & 1 deletion src/hooks/useLocalize.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {useContext} from 'react';
import {LocaleContext} from '../components/withLocalize';
import {LocaleContext} from '../components/LocaleContextProvider';

export default function useLocalize() {
return useContext(LocaleContext);
Expand Down
2 changes: 1 addition & 1 deletion tests/utils/LHNTestUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import {render} from '@testing-library/react-native';
import ComposeProviders from '../../src/components/ComposeProviders';
import OnyxProvider from '../../src/components/OnyxProvider';
import {LocaleContextProvider} from '../../src/components/withLocalize';
import {LocaleContextProvider} from '../../src/components/LocaleContextProvider';
import SidebarLinksData from '../../src/pages/home/sidebar/SidebarLinksData';
import {EnvironmentProvider} from '../../src/components/withEnvironment';
import CONST from '../../src/CONST';
Expand Down

0 comments on commit 334523e

Please sign in to comment.