diff --git a/src/CONST.js b/src/CONST.js
index 1b0a49e36d18..ac5bda74851c 100755
--- a/src/CONST.js
+++ b/src/CONST.js
@@ -84,6 +84,7 @@ const CONST = {
PAY_WITH_EXPENSIFY: 'payWithExpensify',
FREE_PLAN: 'freePlan',
DEFAULT_ROOMS: 'defaultRooms',
+ INTERNATIONALIZATION: 'internationalization',
},
BUTTON_STATES: {
DEFAULT: 'default',
diff --git a/src/components/LocalePicker.js b/src/components/LocalePicker.js
new file mode 100644
index 000000000000..88a25971d7a3
--- /dev/null
+++ b/src/components/LocalePicker.js
@@ -0,0 +1,88 @@
+import React from 'react';
+import {withOnyx} from 'react-native-onyx';
+import PropTypes from 'prop-types';
+import styles from '../styles/styles';
+import Picker from './Picker';
+import Text from './Text';
+import compose from '../libs/compose';
+import {setLocale} from '../libs/actions/App';
+import withLocalize, {withLocalizePropTypes} from './withLocalize';
+import ONYXKEYS from '../ONYXKEYS';
+import CONST from '../CONST';
+import Permissions from '../libs/Permissions';
+import {translate} from '../libs/translate';
+
+const propTypes = {
+ /** Indicates which locale the user currently has selected */
+ preferredLocale: PropTypes.string,
+
+ /** Indicates size of a picker component and whether to render the label or not */
+ size: PropTypes.oneOf(['normal', 'small']),
+
+ /** Beta features list */
+ betas: PropTypes.arrayOf(PropTypes.string),
+
+ ...withLocalizePropTypes,
+};
+
+const defaultProps = {
+ preferredLocale: CONST.DEFAULT_LOCALE,
+ size: 'normal',
+ betas: [],
+};
+
+const localesToLanguages = {
+ default: {
+ value: 'en',
+ label: translate('en', 'preferencesPage.languages.english'),
+ },
+ es: {
+ value: 'es',
+ label: translate('es', 'preferencesPage.languages.spanish'),
+ },
+};
+
+const LocalePicker = ({
+ // eslint-disable-next-line no-shadow
+ preferredLocale, translate, betas, size,
+}) => {
+ if (!Permissions.canUseInternationalization(betas)) {
+ return null;
+ }
+
+ return (
+ <>
+ {size === 'normal' && (
+
+ {translate('preferencesPage.language')}
+
+ )}
+ {
+ if (locale !== preferredLocale) {
+ setLocale(locale);
+ }
+ }}
+ items={Object.values(localesToLanguages)}
+ size={size}
+ value={preferredLocale}
+ />
+ >
+ );
+};
+
+LocalePicker.defaultProps = defaultProps;
+LocalePicker.propTypes = propTypes;
+LocalePicker.displayName = 'LocalePicker';
+
+export default compose(
+ withLocalize,
+ withOnyx({
+ preferredLocale: {
+ key: ONYXKEYS.PREFERRED_LOCALE,
+ },
+ betas: {
+ key: ONYXKEYS.BETAS,
+ },
+ }),
+)(LocalePicker);
diff --git a/src/components/Picker/PickerPropTypes.js b/src/components/Picker/PickerPropTypes.js
index 633966a2bef4..3a6077b5886c 100644
--- a/src/components/Picker/PickerPropTypes.js
+++ b/src/components/Picker/PickerPropTypes.js
@@ -1,6 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import Icon from '../Icon';
+import styles from '../../styles/styles';
import {DownArrow} from '../Icon/Expensicons';
const propTypes = {
@@ -36,13 +37,32 @@ const propTypes = {
/** An icon to display with the picker */
icon: PropTypes.func,
+
+ /** Size of a picker component */
+ size: PropTypes.oneOf(['normal', 'small']),
};
const defaultProps = {
useDisabledStyles: false,
disabled: false,
placeholder: {},
value: null,
- icon: () => ,
+ icon: size => (
+ <>
+ {size === 'small'
+ ? (
+
+ )
+ : (
+
+ )}
+ >
+ ),
};
export {
diff --git a/src/components/Picker/index.js b/src/components/Picker/index.js
index 427d38184ef3..b963c81601b8 100644
--- a/src/components/Picker/index.js
+++ b/src/components/Picker/index.js
@@ -13,19 +13,29 @@ const Picker = ({
value,
icon,
disabled,
-}) => (
-
-);
+ size,
+}) => {
+ let pickerStyles;
+ if (size === 'small') {
+ pickerStyles = styles.pickerSmall;
+ } else {
+ pickerStyles = useDisabledStyles ? pickerDisabledStyles : styles.picker;
+ }
+
+ return (
+ icon(size)}
+ disabled={disabled}
+ fixAndroidTouchableBug
+ />
+ );
+};
Picker.propTypes = pickerPropTypes.propTypes;
Picker.defaultProps = pickerPropTypes.defaultProps;
diff --git a/src/libs/Permissions.js b/src/libs/Permissions.js
index 7063273b245c..1ca31ce88712 100644
--- a/src/libs/Permissions.js
+++ b/src/libs/Permissions.js
@@ -51,10 +51,19 @@ function canUseDefaultRooms(betas) {
return _.contains(betas, CONST.BETAS.DEFAULT_ROOMS) || canUseAllBetas(betas);
}
+/**
+ * @param {Array} betas
+ * @returns {Boolean}
+ */
+function canUseInternationalization(betas) {
+ return _.contains(betas, CONST.BETAS.INTERNATIONALIZATION) || canUseAllBetas(betas);
+}
+
export default {
canUseChronos,
canUseIOU,
canUsePayWithExpensify,
canUseFreePlan,
canUseDefaultRooms,
+ canUseInternationalization,
};
diff --git a/src/libs/actions/SignInRedirect.js b/src/libs/actions/SignInRedirect.js
index 583753ec8d3e..a206b5f249ac 100644
--- a/src/libs/actions/SignInRedirect.js
+++ b/src/libs/actions/SignInRedirect.js
@@ -19,6 +19,12 @@ Onyx.connect({
},
});
+let currentPreferredLocale;
+Onyx.connect({
+ key: ONYXKEYS.PREFERRED_LOCALE,
+ callback: val => currentPreferredLocale = val,
+});
+
/**
* Clears the Onyx store and redirects to the sign in page.
* Normally this method would live in Session.js, but that would cause a circular dependency with Network.js.
@@ -37,12 +43,16 @@ function redirectToSignIn(errorMessage) {
}
const activeClients = currentActiveClients;
+ const preferredLocale = currentPreferredLocale;
// We must set the authToken to null so we can navigate to "signin" it's not possible to navigate to the route as
// it only exists when the authToken is null.
Onyx.set(ONYXKEYS.SESSION, {authToken: null})
.then(() => {
Onyx.clear().then(() => {
+ if (preferredLocale) {
+ Onyx.set(ONYXKEYS.PREFERRED_LOCALE, preferredLocale);
+ }
if (errorMessage) {
Onyx.set(ONYXKEYS.SESSION, {error: errorMessage});
}
diff --git a/src/pages/settings/PreferencesPage.js b/src/pages/settings/PreferencesPage.js
index 009150001840..03e8c23c017c 100755
--- a/src/pages/settings/PreferencesPage.js
+++ b/src/pages/settings/PreferencesPage.js
@@ -4,6 +4,7 @@ import {withOnyx} from 'react-native-onyx';
import PropTypes from 'prop-types';
import HeaderWithCloseButton from '../../components/HeaderWithCloseButton';
+import LocalePicker from '../../components/LocalePicker';
import Navigation from '../../libs/Navigation/Navigation';
import ROUTES from '../../ROUTES';
import ONYXKEYS from '../../ONYXKEYS';
@@ -17,7 +18,6 @@ import Switch from '../../components/Switch';
import Picker from '../../components/Picker';
import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize';
import compose from '../../libs/compose';
-import {setLocale} from '../../libs/actions/App';
const propTypes = {
/** The chat priority mode */
@@ -29,20 +29,16 @@ const propTypes = {
expensifyNewsStatus: PropTypes.bool,
}),
- /** Indicates which locale the user currently has selected */
- preferredLocale: PropTypes.string,
-
...withLocalizePropTypes,
};
const defaultProps = {
priorityMode: CONST.PRIORITY_MODE.DEFAULT,
user: {},
- preferredLocale: CONST.DEFAULT_LOCALE,
};
const PreferencesPage = ({
- priorityMode, user, translate, preferredLocale,
+ priorityMode, user, translate,
}) => {
const priorityModes = {
default: {
@@ -57,17 +53,6 @@ const PreferencesPage = ({
},
};
- const localesToLanguages = {
- default: {
- value: 'en',
- label: translate('preferencesPage.languages.english'),
- },
- es: {
- value: 'es',
- label: translate('preferencesPage.languages.spanish'),
- },
- };
-
return (
{priorityModes[priorityMode].description}
-
- {translate('preferencesPage.language')}
-
- {
- if (locale !== preferredLocale) {
- setLocale(locale);
- }
- }}
- items={Object.values(localesToLanguages)}
- value={preferredLocale}
- />
+
@@ -142,8 +116,5 @@ export default compose(
user: {
key: ONYXKEYS.USER,
},
- preferredLocale: {
- key: ONYXKEYS.NVP_PREFERRED_LOCALE,
- },
}),
)(PreferencesPage);
diff --git a/src/pages/signin/TermsAndLicenses/TermsWithLicenses/index.android.js b/src/pages/signin/TermsAndLicenses/TermsWithLicenses/index.android.js
index fa0d4cbed3c6..4ef064130e71 100644
--- a/src/pages/signin/TermsAndLicenses/TermsWithLicenses/index.android.js
+++ b/src/pages/signin/TermsAndLicenses/TermsWithLicenses/index.android.js
@@ -7,12 +7,10 @@ import withLocalize, {
withLocalizePropTypes,
} from '../../../../components/withLocalize';
import LogoWordmark from '../../../../../assets/images/expensify-wordmark.svg';
+import LocalePicker from '../../../../components/LocalePicker';
const TermsWithLicenses = ({translate}) => (
-
-
-
(
styles.flexWrap,
styles.textAlignCenter,
styles.alignItemsCenter,
- styles.justifyContentCenter,
]}
>
@@ -61,6 +58,12 @@ const TermsWithLicenses = ({translate}) => (
.
+
+
+
+
);
diff --git a/src/pages/signin/TermsAndLicenses/TermsWithLicenses/index.ios.js b/src/pages/signin/TermsAndLicenses/TermsWithLicenses/index.ios.js
index 720c89434bf3..f884c94d7d8e 100644
--- a/src/pages/signin/TermsAndLicenses/TermsWithLicenses/index.ios.js
+++ b/src/pages/signin/TermsAndLicenses/TermsWithLicenses/index.ios.js
@@ -7,19 +7,16 @@ import withLocalize, {
withLocalizePropTypes,
} from '../../../../components/withLocalize';
import LogoWordmark from '../../../../../assets/images/expensify-wordmark.svg';
+import LocalePicker from '../../../../components/LocalePicker';
const TermsWithLicenses = ({translate}) => (
-
-
-
@@ -67,6 +64,12 @@ const TermsWithLicenses = ({translate}) => (
.
+
+
+
+
);
diff --git a/src/pages/signin/TermsAndLicenses/TermsWithLicenses/index.js b/src/pages/signin/TermsAndLicenses/TermsWithLicenses/index.js
index 175bda985afc..a022e771f31b 100755
--- a/src/pages/signin/TermsAndLicenses/TermsWithLicenses/index.js
+++ b/src/pages/signin/TermsAndLicenses/TermsWithLicenses/index.js
@@ -7,13 +7,11 @@ import withLocalize, {
withLocalizePropTypes,
} from '../../../../components/withLocalize';
import LogoWordmark from '../../../../../assets/images/expensify-wordmark.svg';
+import LocalePicker from '../../../../components/LocalePicker';
const TermsWithLicenses = ({translate}) => (
-
-
-
-
+
{translate('termsOfUse.phrase1')}
{' '}
(
.
+
+
+
+
);
diff --git a/src/styles/styles.js b/src/styles/styles.js
index a5c5f0d3e90f..7d4572d835cb 100644
--- a/src/styles/styles.js
+++ b/src/styles/styles.js
@@ -340,6 +340,64 @@ const styles = {
},
},
+ pickerSmall: {
+ inputIOS: {
+ fontFamily: fontFamily.GTA,
+ fontSize: variables.fontSizeSmall,
+ paddingLeft: 9,
+ paddingRight: 25,
+ paddingTop: 6,
+ paddingBottom: 6,
+ borderRadius: variables.componentBorderRadius,
+ borderWidth: 1,
+ borderColor: themeColors.border,
+ color: themeColors.text,
+ height: variables.componentSizeSmall,
+ opacity: 1,
+ backgroundColor: 'transparent',
+ },
+ inputWeb: {
+ fontFamily: fontFamily.GTA,
+ fontSize: variables.fontSizeSmall,
+ paddingLeft: 9,
+ paddingRight: 25,
+ paddingTop: 6,
+ paddingBottom: 6,
+ borderWidth: 1,
+ borderRadius: variables.componentBorderRadius,
+ borderColor: themeColors.border,
+ color: themeColors.text,
+ appearance: 'none',
+ height: variables.componentSizeSmall,
+ opacity: 1,
+ cursor: 'pointer',
+ backgroundColor: 'transparent',
+ },
+ inputAndroid: {
+ fontFamily: fontFamily.GTA,
+ fontSize: variables.fontSizeSmall,
+ paddingLeft: 9,
+ paddingRight: 25,
+ paddingTop: 6,
+ paddingBottom: 6,
+ borderWidth: 1,
+ borderRadius: variables.componentBorderRadius,
+ borderColor: themeColors.border,
+ color: themeColors.text,
+ height: variables.componentSizeSmall,
+ opacity: 1,
+ },
+ iconContainer: {
+ top: 7,
+ right: 9,
+ pointerEvents: 'none',
+ },
+ icon: {
+ width: variables.iconSizeExtraSmall,
+ height: variables.iconSizeExtraSmall,
+ },
+ },
+
badge: {
backgroundColor: themeColors.badgeDefaultBG,
borderRadius: 14,