From 72800fc2b5ef004d246084ee80e5a3c2c9a8c5b4 Mon Sep 17 00:00:00 2001 From: Peter Velkov Date: Fri, 30 Jul 2021 22:10:29 +0300 Subject: [PATCH] feat: create LocaleContext and LocaleContextProvider --- src/App.js | 11 +- src/components/withLocalize.js | 224 ++++++++++++++++----------------- 2 files changed, 119 insertions(+), 116 deletions(-) diff --git a/src/App.js b/src/App.js index 0ac8e0ac8e60..3fd37312645e 100644 --- a/src/App.js +++ b/src/App.js @@ -4,6 +4,7 @@ import {SafeAreaProvider} from 'react-native-safe-area-context'; import CustomStatusBar from './components/CustomStatusBar'; import ErrorBoundary from './components/ErrorBoundary'; import Expensify from './Expensify'; +import {LocaleContextProvider} from './components/withLocalize'; LogBox.ignoreLogs([ // Basically it means that if the app goes in the background and back to foreground on Android, @@ -17,10 +18,12 @@ LogBox.ignoreLogs([ const App = () => ( - - - - + + + + + + ); diff --git a/src/components/withLocalize.js b/src/components/withLocalize.js index 435a2df71629..4eb5ca18d6d8 100755 --- a/src/components/withLocalize.js +++ b/src/components/withLocalize.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {createContext, forwardRef} from 'react'; import PropTypes from 'prop-types'; import {withOnyx} from 'react-native-onyx'; import getComponentDisplayName from '../libs/getComponentDisplayName'; @@ -9,6 +9,8 @@ import {toLocalPhone, fromLocalPhone} from '../libs/LocalePhoneNumber'; import numberFormat from '../libs/numberFormat'; import CONST from '../CONST'; +const LocaleContext = createContext(null); + const withLocalizePropTypes = { /** Returns translated string for given locale and phrase */ translate: PropTypes.func.isRequired, @@ -29,124 +31,122 @@ const withLocalizePropTypes = { fromLocalPhone: PropTypes.func.isRequired, }; -export default (WrappedComponent) => { - const propTypes = { - /** The user's preferred locale e.g. 'en', 'es-ES' */ - preferredLocale: PropTypes.string, - - /** Passed ref from whatever component is wrapped in the HOC */ - forwardedRef: PropTypes.oneOfType([ - PropTypes.func, - PropTypes.shape({current: PropTypes.instanceOf(React.Component)}), - ]), - }; - - const defaultProps = { - preferredLocale: CONST.DEFAULT_LOCALE, - forwardedRef: undefined, - }; - - class WithLocalize extends React.Component { - constructor(props) { - super(props); - - this.translate = this.translate.bind(this); - this.numberFormat = this.numberFormat.bind(this); - this.timestampToRelative = this.timestampToRelative.bind(this); - this.timestampToDateTime = this.timestampToDateTime.bind(this); - this.fromLocalPhone = this.fromLocalPhone.bind(this); - this.toLocalPhone = this.toLocalPhone.bind(this); - } - - /** - * @param {String} phrase - * @param {Object} [variables] - * @returns {String} - */ - translate(phrase, variables) { - return translate(this.props.preferredLocale, phrase, variables); - } - - /** - * @param {Number} number - * @param {Intl.NumberFormatOptions} options - * @returns {String} - */ - numberFormat(number, options) { - return numberFormat(this.props.preferredLocale, number, options); - } - - /** - * @param {Number} timestamp - * @returns {String} - */ - timestampToRelative(timestamp) { - return DateUtils.timestampToRelative(this.props.preferredLocale, timestamp); - } - - /** - * @param {Number} timestamp - * @param {Boolean} [includeTimezone] - * @returns {String} - */ - timestampToDateTime(timestamp, includeTimezone) { - return DateUtils.timestampToDateTime( - this.props.preferredLocale, - timestamp, - includeTimezone, - ); - } - - /** - * @param {Number} number - * @returns {String} - */ - toLocalPhone(number) { - return toLocalPhone(this.props.preferredLocale, number); - } - - /** - * @param {Number} number - * @returns {String} - */ - fromLocalPhone(number) { - return fromLocalPhone(this.props.preferredLocale, number); - } - - render() { - return ( - - ); - } +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, +}; + +const localeProviderDefaultProps = { + preferredLocale: CONST.DEFAULT_LOCALE, +}; + +class LocaleContextProvider extends React.Component { + constructor(props) { + super(props); + + /* The context this component exposes to consumers */ + this.translateUtils = { + translate: this.translate.bind(this), + numberFormat: this.numberFormat.bind(this), + timestampToRelative: this.timestampToRelative.bind(this), + timestampToDateTime: this.timestampToDateTime.bind(this), + fromLocalPhone: this.fromLocalPhone.bind(this), + toLocalPhone: this.toLocalPhone.bind(this), + }; } - WithLocalize.propTypes = propTypes; - WithLocalize.defaultProps = defaultProps; + /** + * @param {String} phrase + * @param {Object} [variables] + * @returns {String} + */ + translate(phrase, variables) { + return translate(this.props.preferredLocale, phrase, variables); + } + + /** + * @param {Number} number + * @param {Intl.NumberFormatOptions} options + * @returns {String} + */ + numberFormat(number, options) { + return numberFormat(this.props.preferredLocale, number, options); + } - const withForwardedRef = React.forwardRef((props, ref) => ( - // eslint-disable-next-line react/jsx-props-no-spreading - + /** + * @param {Number} timestamp + * @returns {String} + */ + timestampToRelative(timestamp) { + return DateUtils.timestampToRelative(this.props.preferredLocale, timestamp); + } + + /** + * @param {Number} timestamp + * @param {Boolean} [includeTimezone] + * @returns {String} + */ + timestampToDateTime(timestamp, includeTimezone) { + return DateUtils.timestampToDateTime( + this.props.preferredLocale, + timestamp, + includeTimezone, + ); + } + + /** + * @param {Number} number + * @returns {String} + */ + toLocalPhone(number) { + return toLocalPhone(this.props.preferredLocale, number); + } + + /** + * @param {Number} number + * @returns {String} + */ + fromLocalPhone(number) { + return fromLocalPhone(this.props.preferredLocale, number); + } + + render() { + return ( + + {this.props.children} + + ); + } +} + +LocaleContextProvider.propTypes = localeProviderPropTypes; +LocaleContextProvider.defaultProps = localeProviderDefaultProps; + +const Provider = withOnyx({ + preferredLocale: { + key: ONYXKEYS.NVP_PREFERRED_LOCALE, + }, +})(LocaleContextProvider); + +Provider.displayName = 'withOnyx(LocaleContextProvider)'; + +export default function withLocalize(WrappedComponent) { + const WithLocalize = forwardRef((props, ref) => ( + + {/* eslint-disable-next-line react/jsx-props-no-spreading */} + { translateUtils => } + )); - withForwardedRef.displayName = `withLocalize(${getComponentDisplayName(WrappedComponent)})`; + WithLocalize.displayName = `withLocalize(${getComponentDisplayName(WrappedComponent)})`; - return withOnyx({ - preferredLocale: { - key: ONYXKEYS.NVP_PREFERRED_LOCALE, - }, - })(withForwardedRef); -}; + return WithLocalize; +} export { withLocalizePropTypes, + Provider as LocaleContextProvider, };