From db249e7ee9af0098a39c79fa5bd53af0374ebace Mon Sep 17 00:00:00 2001 From: Yevhenii Voloshchak Date: Thu, 6 May 2021 20:35:23 +0300 Subject: [PATCH 1/3] Apply localization --- src/CONST.js | 16 +- src/ONYXKEYS.js | 3 + src/components/AttachmentModal.js | 6 +- .../AttachmentPicker/index.native.js | 42 +++- src/components/AttachmentView.js | 14 +- src/components/ConfirmModal.js | 17 +- src/components/HeaderWithCloseButton.js | 7 +- src/components/IOUConfirmationList.js | 34 ++- src/components/OptionsSelector.js | 10 +- src/components/RenderHTML.js | 7 +- src/components/TextInputFocusable/index.js | 13 +- src/components/UnreadActionIndicator.js | 9 +- .../UpdateAppModal/BaseUpdateAppModal.js | 12 +- src/components/VideoChatButtonAndMenu.js | 20 +- src/components/WelcomeText.js | 11 +- src/components/withLocalize.js | 21 +- src/languages/en.js | 227 ++++++++++++++++++ src/libs/OptionsListUtils.js | 11 +- src/pages/DetailsPage.js | 30 ++- src/pages/NewChatPage.js | 34 ++- src/pages/NewGroupPage.js | 38 +-- src/pages/NotFound.js | 11 +- src/pages/ReportParticipantsPage.js | 32 ++- src/pages/SearchPage.js | 34 ++- src/pages/SetPasswordPage.js | 23 +- .../home/report/EmojiPickerMenu/index.js | 11 +- src/pages/home/report/ReportActionCompose.js | 15 +- .../home/report/ReportActionContextMenu.js | 17 +- src/pages/home/report/ReportActionsView.js | 7 +- .../home/report/ReportTypingIndicator.js | 16 +- src/pages/home/sidebar/SidebarScreen.js | 17 +- src/pages/iou/IOUModal.js | 50 ++-- src/pages/iou/steps/IOUAmountPage.js | 16 +- .../IOUParticipantsRequest.js | 25 +- .../IOUParticipantsSplit.js | 31 ++- src/pages/settings/AddSecondaryLoginPage.js | 37 ++- src/pages/settings/InitialPage.js | 54 +++-- src/pages/settings/PasswordPage.js | 39 +-- src/pages/settings/PaymentsPage.js | 27 ++- src/pages/settings/PreferencesPage.js | 130 +++++----- src/pages/settings/Profile/LoginField.js | 20 +- src/pages/settings/Profile/ProfilePage.js | 85 ++++--- src/pages/signin/ChangeExpensifyLoginLink.js | 18 +- src/pages/signin/LoginForm.js | 12 +- src/pages/signin/PasswordForm.js | 25 +- src/pages/signin/ResendValidationForm.js | 23 +- .../SignInPageLayoutNarrow.js | 18 +- .../SignInPageLayout/SignInPageLayoutWide.js | 18 +- .../signin/TermsAndLicenses/TermsOnly.js | 15 +- .../TermsAndLicenses/TermsWithLicenses.js | 19 +- 50 files changed, 991 insertions(+), 436 deletions(-) mode change 100644 => 100755 src/CONST.js mode change 100644 => 100755 src/ONYXKEYS.js mode change 100644 => 100755 src/components/AttachmentModal.js mode change 100644 => 100755 src/components/AttachmentPicker/index.native.js mode change 100644 => 100755 src/components/AttachmentView.js mode change 100644 => 100755 src/components/ConfirmModal.js mode change 100644 => 100755 src/components/HeaderWithCloseButton.js mode change 100644 => 100755 src/components/IOUConfirmationList.js mode change 100644 => 100755 src/components/OptionsSelector.js mode change 100644 => 100755 src/components/RenderHTML.js mode change 100644 => 100755 src/components/TextInputFocusable/index.js mode change 100644 => 100755 src/components/UnreadActionIndicator.js mode change 100644 => 100755 src/components/UpdateAppModal/BaseUpdateAppModal.js mode change 100644 => 100755 src/components/VideoChatButtonAndMenu.js mode change 100644 => 100755 src/components/WelcomeText.js mode change 100644 => 100755 src/components/withLocalize.js mode change 100644 => 100755 src/languages/en.js mode change 100644 => 100755 src/pages/DetailsPage.js mode change 100644 => 100755 src/pages/NewChatPage.js mode change 100644 => 100755 src/pages/NewGroupPage.js mode change 100644 => 100755 src/pages/NotFound.js mode change 100644 => 100755 src/pages/ReportParticipantsPage.js mode change 100644 => 100755 src/pages/SearchPage.js mode change 100644 => 100755 src/pages/SetPasswordPage.js mode change 100644 => 100755 src/pages/home/report/EmojiPickerMenu/index.js mode change 100644 => 100755 src/pages/home/report/ReportActionCompose.js mode change 100644 => 100755 src/pages/home/report/ReportActionContextMenu.js mode change 100644 => 100755 src/pages/home/report/ReportActionsView.js mode change 100644 => 100755 src/pages/home/report/ReportTypingIndicator.js mode change 100644 => 100755 src/pages/home/sidebar/SidebarScreen.js mode change 100644 => 100755 src/pages/iou/IOUModal.js mode change 100644 => 100755 src/pages/iou/steps/IOUAmountPage.js mode change 100644 => 100755 src/pages/iou/steps/IOUParticipantsPage/IOUParticipantsRequest.js mode change 100644 => 100755 src/pages/iou/steps/IOUParticipantsPage/IOUParticipantsSplit.js mode change 100644 => 100755 src/pages/settings/AddSecondaryLoginPage.js mode change 100644 => 100755 src/pages/settings/InitialPage.js mode change 100644 => 100755 src/pages/settings/PasswordPage.js mode change 100644 => 100755 src/pages/settings/PaymentsPage.js mode change 100644 => 100755 src/pages/settings/PreferencesPage.js mode change 100644 => 100755 src/pages/settings/Profile/LoginField.js mode change 100644 => 100755 src/pages/settings/Profile/ProfilePage.js mode change 100644 => 100755 src/pages/signin/ChangeExpensifyLoginLink.js mode change 100644 => 100755 src/pages/signin/LoginForm.js mode change 100644 => 100755 src/pages/signin/PasswordForm.js mode change 100644 => 100755 src/pages/signin/ResendValidationForm.js mode change 100644 => 100755 src/pages/signin/SignInPageLayout/SignInPageLayoutNarrow.js mode change 100644 => 100755 src/pages/signin/SignInPageLayout/SignInPageLayoutWide.js mode change 100644 => 100755 src/pages/signin/TermsAndLicenses/TermsOnly.js mode change 100644 => 100755 src/pages/signin/TermsAndLicenses/TermsWithLicenses.js diff --git a/src/CONST.js b/src/CONST.js old mode 100644 new mode 100755 index daefbe0ec6be..7839d77831f2 --- a/src/CONST.js +++ b/src/CONST.js @@ -67,8 +67,8 @@ const CONST = { }, MESSAGES: { // eslint-disable-next-line max-len - NO_PHONE_NUMBER: 'Please enter a phone number including the country code e.g +447814266907', - MAXIMUM_PARTICIPANTS_REACHED: 'You\'ve reached the maximum number of participants for a group chat.', + NO_PHONE_NUMBER: 'noPhoneNumberMessage', + MAXIMUM_PARTICIPANTS_REACHED: 'maxParticipantsReachedMessage', }, PRIORITY_MODE: { GSD: 'gsd', @@ -85,12 +85,12 @@ const CONST = { DEFAULT_TIME_ZONE: {automatic: true, selected: 'America/Los_Angeles'}, DEFAULT_ACCOUNT_DATA: {error: '', success: '', loading: false}, PRONOUNS: { - HE_HIM_HIS: 'He/him', - SHE_HER_HERS: 'She/her', - THEY_THEM_THEIRS: 'They/them', - ZE_HIR_HIRS: 'Ze/hir', - SELF_SELECT: 'Self-select', - CALL_ME_BY_MY_NAME: 'Call me by my name', + HE_HIM_HIS: 'pronouns.heHimHis', + SHE_HER_HERS: 'pronouns.sheHerHers', + THEY_THEM_THEIRS: 'pronouns.theyThemTheirs', + ZE_HIR_HIRS: 'pronouns.zeHirHirs', + SELF_SELECT: 'pronouns.selfSelect', + CALL_ME_BY_MY_NAME: 'pronouns.callMeByMyName', }, APP_STATE: { ACTIVE: 'active', diff --git a/src/ONYXKEYS.js b/src/ONYXKEYS.js old mode 100644 new mode 100755 index bbd2263fca1e..7794a5b8852c --- a/src/ONYXKEYS.js +++ b/src/ONYXKEYS.js @@ -71,4 +71,7 @@ export default { REPORT_USER_IS_TYPING: 'reportUserIsTyping_', REPORT_IOUS: 'reportIOUs_', }, + + // Indicates which locale should be used + PREFERRED_LOCALE: 'preferredLocale', }; diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js old mode 100644 new mode 100755 index 4f8a00915671..f7a83a1146f9 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -15,6 +15,7 @@ import compose from '../libs/compose'; import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; import HeaderWithCloseButton from './HeaderWithCloseButton'; import fileDownload from '../libs/fileDownload'; +import withLocalize, {withLocalizePropTypes} from './withLocalize'; /** * Modal render prop component that exposes modal launching triggers that can be used @@ -46,6 +47,8 @@ const propTypes = { authToken: PropTypes.string.isRequired, }).isRequired, + ...withLocalizePropTypes, + ...windowDimensionsPropTypes, }; @@ -133,7 +136,7 @@ class AttachmentModal extends PureComponent { styles.buttonConfirmText, ]} > - Upload + {this.props.translate('common.upload')} )} @@ -165,4 +168,5 @@ export default compose( key: ONYXKEYS.SESSION, }, }), + withLocalize, )(AttachmentModal); diff --git a/src/components/AttachmentPicker/index.native.js b/src/components/AttachmentPicker/index.native.js old mode 100644 new mode 100755 index 085a4061ce29..37467cc5f7fe --- a/src/components/AttachmentPicker/index.native.js +++ b/src/components/AttachmentPicker/index.native.js @@ -5,16 +5,28 @@ import React, {Component} from 'react'; import {Alert, Linking, View} from 'react-native'; import RNImagePicker from 'react-native-image-picker'; import RNDocumentPicker from 'react-native-document-picker'; +import Onyx from 'react-native-onyx'; import basePropTypes from './AttachmentPickerPropTypes'; import styles from '../../styles/styles'; import Popover from '../Popover'; import MenuItem from '../MenuItem'; import {Camera, Gallery, Paperclip} from '../Icon/Expensicons'; import withWindowDimensions, {windowDimensionsPropTypes} from '../withWindowDimensions'; +import withLocalize, {withLocalizePropTypes} from '../withLocalize'; +import compose from '../../libs/compose'; +import {translate} from '../../libs/translate'; + +let preferredLocale; + +Onyx.connect({ + key: preferredLocale, + callback: val => preferredLocale = val || 'en', +}); const propTypes = { ...basePropTypes, ...windowDimensionsPropTypes, + ...withLocalizePropTypes, }; /** @@ -39,15 +51,15 @@ const documentPickerOptions = { */ function showPermissionsAlert() { Alert.alert( - 'Camera Permission Required', - 'Expensify.cash does not have access to your camera, please enable the permission and try again.', + translate(preferredLocale, 'attachmentPicker.cameraPermissionRequired'), + translate(preferredLocale, 'attachmentPicker.expensifyDoesntHaveAccessToCamera'), [ { - text: 'Cancel', + text: translate(preferredLocale, 'common.cancel'), style: 'cancel', }, { - text: 'Settings', + text: translate(preferredLocale, 'common.settings'), onPress: () => Linking.openSettings(), }, ], @@ -61,8 +73,8 @@ function showPermissionsAlert() { */ function showGeneralAlert() { Alert.alert( - 'Attachment Error', - 'An error occurred while selecting an attachment, please try again', + translate(preferredLocale, 'attachmentPicker.attachmentError'), + translate(preferredLocale, 'attachmentPicker.errorWhileSelectingAttachment'), ); } @@ -99,8 +111,11 @@ function showImagePicker(imagePickerFunc) { showGeneralAlert(response.error); break; } - - reject(new Error(`Error during attachment selection: ${response.error}`)); + const errorDescription = translate( + preferredLocale, + 'attachmentPicker.errorDuringAttachmentSelection', + ); + reject(new Error(`${errorDescription}: ${response.error}`)); } resolve(response); @@ -142,17 +157,17 @@ class AttachmentPicker extends Component { this.menuItemData = [ { icon: Camera, - text: 'Take Photo', + text: this.props.translate('attachmentPicker.takePhoto'), pickAttachment: () => showImagePicker(RNImagePicker.launchCamera), }, { icon: Gallery, - text: 'Choose from Gallery', + text: this.props.translate('attachmentPicker.chooseFromGallery'), pickAttachment: () => showImagePicker(RNImagePicker.launchImageLibrary), }, { icon: Paperclip, - text: 'Choose Document', + text: this.props.translate('attachmentPicker.chooseDocument'), pickAttachment: showDocumentPicker, }, ]; @@ -247,4 +262,7 @@ class AttachmentPicker extends Component { AttachmentPicker.propTypes = propTypes; AttachmentPicker.displayName = 'AttachmentPicker'; -export default withWindowDimensions(AttachmentPicker); +export default compose( + withWindowDimensions, + withLocalize, +)(AttachmentPicker); diff --git a/src/components/AttachmentView.js b/src/components/AttachmentView.js old mode 100644 new mode 100755 index d1b37391e973..1c316d4199c1 --- a/src/components/AttachmentView.js +++ b/src/components/AttachmentView.js @@ -7,6 +7,8 @@ import PDFView from './PDFView'; import ImageView from './ImageView'; import Icon from './Icon'; import {Paperclip} from './Icon/Expensicons'; +import withLocalize, {withLocalizePropTypes} from './withLocalize'; +import compose from '../libs/compose'; const propTypes = { // URL to full-sized attachment @@ -15,18 +17,21 @@ const propTypes = { file: PropTypes.shape({ name: PropTypes.string, }), + + ...withLocalizePropTypes, }; const defaultProps = { file: { - name: 'Unknown Filename', + name: '', }, }; const AttachmentView = (props) => { // Check both sourceURL and file.name since PDFs dragged into the the text field // will appear with a sourceURL that is a blob - if (Str.isPDF(props.sourceURL) || (props.file && Str.isPDF(props.file.name))) { + if (Str.isPDF(props.sourceURL) + || (props.file && Str.isPDF(props.file.name || props.translate('attachmentView.unknownFilename')))) { return ( ( styles.buttonSuccessText, ]} > - {props.confirmText} + {props.confirmText || props.translate('common.yes')} @@ -77,7 +81,7 @@ const ConfirmModal = props => ( onPress={props.onCancel} > - {props.cancelText} + {props.cancelText || props.translate('common.no')} @@ -87,4 +91,7 @@ const ConfirmModal = props => ( ConfirmModal.propTypes = propTypes; ConfirmModal.defaultProps = defaultProps; ConfirmModal.displayName = 'ConfirmModal'; -export default withWindowDimensions(ConfirmModal); +export default compose( + withWindowDimensions, + withLocalize, +)(ConfirmModal); diff --git a/src/components/HeaderWithCloseButton.js b/src/components/HeaderWithCloseButton.js old mode 100644 new mode 100755 index 6a281565a1b0..5848ebf814d1 --- a/src/components/HeaderWithCloseButton.js +++ b/src/components/HeaderWithCloseButton.js @@ -7,6 +7,7 @@ import styles from '../styles/styles'; import Header from './Header'; import Icon from './Icon'; import {Close, Download, BackArrow} from './Icon/Expensicons'; +import withLocalize, {withLocalizePropTypes} from './withLocalize'; const propTypes = { /** Title of the Header */ @@ -29,6 +30,8 @@ const propTypes = { /** Whether we should show a border on the bottom of the Header */ shouldShowBorderBottom: PropTypes.bool, + + ...withLocalizePropTypes, }; const defaultProps = { @@ -63,7 +66,7 @@ const HeaderWithCloseButton = props => (
{ - props.title === 'Attachment' && ( + props.title === props.translate('common.attachment') && ( - WHAT'S IT FOR? + {this.props.translate('iOUConfirmationList.whatsItFor')} @@ -249,7 +252,7 @@ class IOUConfirmationList extends Component { style={[styles.textInput]} value={this.props.comment} onChangeText={this.props.onUpdateComment} - placeholder="Optional" + placeholder={this.props.translate('common.optional')} placeholderTextColor={themeColors.placeholderText} /> @@ -257,7 +260,9 @@ class IOUConfirmationList extends Component { this.props.onConfirm(this.getSplits())} /> @@ -270,9 +275,14 @@ IOUConfirmationList.displayName = 'IOUConfirmPage'; IOUConfirmationList.propTypes = propTypes; IOUConfirmationList.defaultProps = defaultProps; -export default compose(withOnyx({ - iou: {key: ONYXKEYS.IOU}, - myPersonalDetails: { - key: ONYXKEYS.MY_PERSONAL_DETAILS, - }, -}), withSafeAreaInsets, withWindowDimensions)(IOUConfirmationList); +export default compose( + withLocalize, + withSafeAreaInsets, + withWindowDimensions, + withOnyx({ + iou: {key: ONYXKEYS.IOU}, + myPersonalDetails: { + key: ONYXKEYS.MY_PERSONAL_DETAILS, + }, + }), +)(IOUConfirmationList); diff --git a/src/components/OptionsSelector.js b/src/components/OptionsSelector.js old mode 100644 new mode 100755 index 422b7d72855d..597af214dfc3 --- a/src/components/OptionsSelector.js +++ b/src/components/OptionsSelector.js @@ -7,6 +7,7 @@ import OptionsList from './OptionsList'; import styles from '../styles/styles'; import themeColors from '../styles/themes/default'; import optionPropTypes from './optionPropTypes'; +import withLocalize, {withLocalizePropTypes} from './withLocalize'; const propTypes = { // Callback to fire when a row is tapped @@ -62,11 +63,13 @@ const propTypes = { // Whether to focus the textinput after an option is selected shouldFocusOnSelectRow: PropTypes.bool, + + ...withLocalizePropTypes, }; const defaultProps = { onSelectRow: () => {}, - placeholderText: 'Name, email, or phone number', + placeholderText: '', selectedOptions: [], headerMessage: '', canSelectMultipleOptions: false, @@ -195,7 +198,8 @@ class OptionsSelector extends Component { value={this.props.value} onChangeText={this.props.onChangeText} onKeyPress={this.handleKeyPress} - placeholder={this.props.placeholderText} + placeholder={this.props.placeholderText + || this.props.translate('optionsSelector.nameEmailOrPhoneNumber')} placeholderTextColor={themeColors.placeholderText} /> @@ -221,4 +225,4 @@ class OptionsSelector extends Component { OptionsSelector.defaultProps = defaultProps; OptionsSelector.propTypes = propTypes; -export default OptionsSelector; +export default withLocalize(OptionsSelector); diff --git a/src/components/RenderHTML.js b/src/components/RenderHTML.js old mode 100644 new mode 100755 index d44fbebaf1f3..83f654b42596 --- a/src/components/RenderHTML.js +++ b/src/components/RenderHTML.js @@ -14,6 +14,7 @@ import AnchorForCommentsOnly from './AnchorForCommentsOnly'; import InlineCodeBlock from './InlineCodeBlock'; import AttachmentModal from './AttachmentModal'; import ThumbnailImage from './ThumbnailImage'; +import withLocalize from './withLocalize'; const MAX_IMG_DIMENSIONS = 512; @@ -76,7 +77,7 @@ function CodeRenderer({ ); } -function ImgRenderer({tnode}) { +function ImgRenderer({tnode, translate}) { const htmlAttribs = tnode.attributes; // There are two kinds of images that need to be displayed: @@ -114,7 +115,7 @@ function ImgRenderer({tnode}) { return ( @@ -143,7 +144,7 @@ ImgRenderer.model = defaultHTMLElementModels.img; const renderers = { a: AnchorRenderer, code: CodeRenderer, - img: ImgRenderer, + img: withLocalize(ImgRenderer), }; const propTypes = { diff --git a/src/components/TextInputFocusable/index.js b/src/components/TextInputFocusable/index.js old mode 100644 new mode 100755 index 89b5928742f9..a66d8ef8ff0f --- a/src/components/TextInputFocusable/index.js +++ b/src/components/TextInputFocusable/index.js @@ -2,6 +2,7 @@ import React from 'react'; import {TextInput, StyleSheet} from 'react-native'; import PropTypes from 'prop-types'; import _ from 'underscore'; +import withLocalize, {withLocalizePropTypes} from '../withLocalize'; const propTypes = { // Maximum number of lines in the text input @@ -44,6 +45,8 @@ const propTypes = { /* Set focus to this component the first time it renders. Override this in case you need to set focus on one * field out of many, or when you want to disable autoFocus */ autoFocus: PropTypes.bool, + + ...withLocalizePropTypes, }; const defaultProps = { @@ -178,15 +181,15 @@ class TextInputFocusable extends React.Component { .then((x) => { const extension = IMAGE_EXTENSIONS[x.type]; if (!extension) { - throw new Error('No extension found for mime type'); + throw new Error(this.props.translate('textInputFocusable.noExtentionFoundForMimeType')); } return new File([x], `pasted_image.${extension}`, {}); }) .then(this.props.onPasteFile) .catch((error) => { - console.debug(error); - alert(`There was a problem getting the image you pasted. \n${error.message}`); + const errorDesc = this.props.translate('textInputFocusable.problemGettingImageYouPasted'); + alert(`${errorDesc}. \n${error.message}`); }); } } @@ -235,7 +238,7 @@ class TextInputFocusable extends React.Component { TextInputFocusable.propTypes = propTypes; TextInputFocusable.defaultProps = defaultProps; -export default React.forwardRef((props, ref) => ( +export default withLocalize(React.forwardRef((props, ref) => ( /* eslint-disable-next-line react/jsx-props-no-spreading */ -)); +))); diff --git a/src/components/UnreadActionIndicator.js b/src/components/UnreadActionIndicator.js old mode 100644 new mode 100755 index 7c6789c0a66a..389413e4f38e --- a/src/components/UnreadActionIndicator.js +++ b/src/components/UnreadActionIndicator.js @@ -2,15 +2,18 @@ import React from 'react'; import {View} from 'react-native'; import styles from '../styles/styles'; import Text from './Text'; +import withLocalize, {withLocalizePropTypes} from './withLocalize'; -const UnreadActionIndicator = () => ( +const UnreadActionIndicator = props => ( - NEW + {props.translate('common.new')} ); +UnreadActionIndicator.propTypes = {...withLocalizePropTypes}; + UnreadActionIndicator.displayName = 'UnreadActionIndicator'; -export default UnreadActionIndicator; +export default withLocalize(UnreadActionIndicator); diff --git a/src/components/UpdateAppModal/BaseUpdateAppModal.js b/src/components/UpdateAppModal/BaseUpdateAppModal.js old mode 100644 new mode 100755 index e547c460e95f..f3c6280a16b4 --- a/src/components/UpdateAppModal/BaseUpdateAppModal.js +++ b/src/components/UpdateAppModal/BaseUpdateAppModal.js @@ -1,6 +1,7 @@ import React, {PureComponent} from 'react'; import {propTypes, defaultProps} from './UpdateAppModalPropTypes'; import ConfirmModal from '../ConfirmModal'; +import withLocalize from '../withLocalize'; class BaseUpdateAppModal extends PureComponent { constructor(props) { @@ -25,14 +26,13 @@ class BaseUpdateAppModal extends PureComponent { return ( <> this.setState({isModalOpen: false})} - prompt="A new version of Expensify.cash is available. - Update now or restart the app at a later time to download the latest changes." - confirmText="Update App" - cancelText="Cancel" + prompt={this.props.translate('baseUpdateAppModal.updatePrompt')} + confirmText={this.props.translate('baseUpdateAppModal.updateApp')} + cancelText={this.props.translate('common.cancel')} /> ); @@ -41,4 +41,4 @@ class BaseUpdateAppModal extends PureComponent { BaseUpdateAppModal.propTypes = propTypes; BaseUpdateAppModal.defaultProps = defaultProps; -export default BaseUpdateAppModal; +export default withLocalize(BaseUpdateAppModal); diff --git a/src/components/VideoChatButtonAndMenu.js b/src/components/VideoChatButtonAndMenu.js old mode 100644 new mode 100755 index e5419ce8acae..092a30e0e5ae --- a/src/components/VideoChatButtonAndMenu.js +++ b/src/components/VideoChatButtonAndMenu.js @@ -10,7 +10,9 @@ import GoogleMeetIcon from '../../assets/images/google-meet.svg'; import CONST from '../CONST'; import styles from '../styles/styles'; import themeColors from '../styles/themes/default'; -import withWindowDimensions from './withWindowDimensions'; +import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; +import withLocalize, {withLocalizePropTypes} from './withLocalize'; +import compose from '../libs/compose'; class VideoChatButtonAndMenu extends Component { constructor(props) { @@ -22,12 +24,12 @@ class VideoChatButtonAndMenu extends Component { this.menuItemData = [ { icon: ZoomIcon, - text: 'Zoom', + text: props.translate('videoChatButtonAndMenu.zoom'), onPress: () => openURLInNewTab(CONST.NEW_ZOOM_MEETING_URL), }, { icon: GoogleMeetIcon, - text: 'Google Meet', + text: props.translate('videoChatButtonAndMenu.googleMeet'), onPress: () => openURLInNewTab(CONST.NEW_GOOGLE_MEET_MEETING_URL), }, ].map(item => ({ @@ -110,5 +112,15 @@ class VideoChatButtonAndMenu extends Component { } } +const propTypes = { + ...withLocalizePropTypes, + ...windowDimensionsPropTypes, +}; + +VideoChatButtonAndMenu.propTypes = propTypes; + VideoChatButtonAndMenu.displayName = 'VideoChatButtonAndMenu'; -export default withWindowDimensions(VideoChatButtonAndMenu); +export default compose( + withWindowDimensions, + withLocalize, +)(VideoChatButtonAndMenu); diff --git a/src/components/WelcomeText.js b/src/components/WelcomeText.js old mode 100644 new mode 100755 index 2ccee7b079b5..8a192d13cb65 --- a/src/components/WelcomeText.js +++ b/src/components/WelcomeText.js @@ -2,11 +2,14 @@ import React from 'react'; import {Text} from 'react-native'; import PropTypes from 'prop-types'; import styles from '../styles/styles'; +import withLocalize, {withLocalizePropTypes} from './withLocalize'; const propTypes = { // Fontsize textSize: PropTypes.oneOf(['default', 'large']), + + ...withLocalizePropTypes, }; const defaultProps = { @@ -16,12 +19,12 @@ const defaultProps = { const WelcomeText = props => ( <> - With Expensify.cash, chat and payments are the same thing. + {props.translate('welcomeText.phrase1')} - Money talks. And now that chat and payments are in one place, it's also easy. + {props.translate('welcomeText.phrase2')} {' '} - Your payments get to you as fast as you can get your point across. + {props.translate('welcomeText.phrase3')} ); @@ -30,4 +33,4 @@ WelcomeText.displayName = 'WelcomeText'; WelcomeText.propTypes = propTypes; WelcomeText.defaultProps = defaultProps; -export default WelcomeText; +export default withLocalize(WelcomeText); diff --git a/src/components/withLocalize.js b/src/components/withLocalize.js old mode 100644 new mode 100755 index b18c99b37a39..9fe283af1e7a --- a/src/components/withLocalize.js +++ b/src/components/withLocalize.js @@ -11,14 +11,12 @@ import numberFormat from '../libs/numberFormat'; const withLocalizePropTypes = { // Translations functions using current User's preferred locale - translations: PropTypes.shape({ - translate: PropTypes.func.isRequired, - numberFormat: PropTypes.func.isRequired, - timestampToRelative: PropTypes.func.isRequired, - timestampToDateTime: PropTypes.func.isRequired, - toLocalPhone: PropTypes.func.isRequired, - fromLocalPhone: PropTypes.func.isRequired, - }), + translate: PropTypes.func.isRequired, + numberFormat: PropTypes.func.isRequired, + timestampToRelative: PropTypes.func.isRequired, + timestampToDateTime: PropTypes.func.isRequired, + toLocalPhone: PropTypes.func.isRequired, + fromLocalPhone: PropTypes.func.isRequired, }; function withLocalizeHOC(WrappedComponent) { @@ -39,7 +37,12 @@ function withLocalizeHOC(WrappedComponent) { ); }; diff --git a/src/languages/en.js b/src/languages/en.js old mode 100644 new mode 100755 index ff1ccd6837b6..2cea557474a2 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -1,4 +1,231 @@ +/* eslint-disable max-len */ export default { + common: { + cancel: 'Cancel', + upload: 'Upload', + yes: 'Yes', + no: 'No', + attachment: 'Attachment', + to: 'To', + optional: 'Optional', + split: 'Split', + request: 'Request', + new: 'NEW', + search: 'Search', + next: 'Next', + add: 'Add', + resend: 'Resend', + save: 'Save', + password: 'Password', + profile: 'Profile', + payments: 'Payments', + preferences: 'Preferences', + view: 'View', + not: 'Not', + signIn: 'Sign In', + continue: 'Continue', + phoneNumber: 'Phone Number', + email: 'Email', + }, + attachmentPicker: { + cameraPermissionRequired: 'Camera Permission Required', + expensifyDoesntHaveAccessToCamera: 'Expensify.cash does not have access to your camera, please enable the permission and try again.', + attachmentError: 'Attachment Error', + errorWhileSelectingAttachment: 'An error occurred while selecting an attachment, please try again', + errorDuringAttachmentSelection: 'Error during attachment selection', + takePhoto: 'Take Photo', + chooseFromGallery: 'Choose from Gallery', + chooseDocument: 'Choose Document', + }, + textInputFocusable: { + noExtentionFoundForMimeType: 'No extension found for mime type', + problemGettingImageYouPasted: 'There was a problem getting the image you pasted', + }, + baseUpdateAppModal: { + updateApp: 'Update App', + updatePrompt: 'A new version of Expensify.cash is available.\nUpdate now or restart the app at a later time to download the latest changes.', + }, + iOUConfirmationList: { + whoPaid: 'WHO PAID?', + whoWasThere: 'WHO WAS THERE?', + whatsItFor: 'WHAT\'S IT FOR?', + }, + optionsSelector: { + nameEmailOrPhoneNumber: 'Name, email, or phone number', + }, + videoChatButtonAndMenu: { + zoom: 'Zoom', + googleMeet: 'Google Meet', + }, hello: 'Hello', phoneCountryCode: '1', + welcomeText: { + phrase1: 'With Expensify.cash, chat and payments are the same thing.', + phrase2: 'Money talks. And now that chat and payments are in one place, it\'s also easy.', + phrase3: 'Your payments get to you as fast as you can get your point across.', + }, + reportActionCompose: { + uploadAttachment: 'Upload Attachment', + addAttachment: 'Add Attachment', + writeSomething: 'Write something...', + youAppearToBeOffline: 'You appear to be offline.', + }, + reportActionContextMenu: { + copyToClipboard: 'Copy to Clipboard', + copied: 'Copied!', + copyLink: 'Copy Link', + markAsUnread: 'Mark as Unread', + editComment: 'Edit Comment', + deleteComment: 'Delete Comment', + }, + reportActionsView: { + beFirstPersonToComment: 'Be the first person to comment', + }, + reportTypingIndicator: { + isTyping: ' is typing...', + and: ' and ', + areTyping: ' are typing...', + multipleUsers: 'Multiple users', + }, + sidebarScreen: { + newChat: 'New Chat', + newGroup: 'New Group', + }, + iou: { + contacts: 'CONTACTS', + recents: 'RECENTS', + amount: 'Amount', + participants: 'Participants', + confirm: 'Confirm', + splitBill: 'Split Bill', + requestMoney: 'Request Money', + }, + loginField: { + addYourPhoneToSettleViaVenmo: 'Add your phone number to settle up via Venmo.', + numberHasNotBeenValidated: 'The number has not yet been validated. Click the button to resend the validation link via text.', + useYourPhoneToSettleViaVenmo: 'Use your phone number to settle up via Venmo.', + emailHasNotBeenValidated: 'The email has not yet been validated. Click the button to resend the validation link via text.', + }, + profilePage: { + uploadPhoto: 'Upload Photo', + removePhoto: 'Remove Photo', + profile: 'Profile', + editPhoto: 'Edit Photo', + tellUsAboutYourself: 'Tell us about yourself, we would love to get to know you!', + firstName: 'First Name', + john: 'John', + lastName: 'Last Name', + doe: 'Doe', + preferredPronouns: 'Preferred Pronouns', + selectYourPronouns: 'Select your pronouns', + selfSelectYourPronoun: 'Self-select your pronoun', + emailAddress: 'Email Address', + setMyTimezoneAutomatically: 'Set my timezone automatically', + timezone: 'Timezone', + }, + addSecondaryLoginPage: { + addPhoneNumber: 'Add Phone Number', + addEmailAddress: 'Add Email Address', + enterPreferredPhoneNumberToSendValidationLink: 'Enter your preferred phone number and password to send a validation link.', + enterPreferredEmailToSendValidationLink: 'Enter your preferred email address and password to send a validation link.', + sendValidation: 'Send Validation', + + }, + initialSettingsPage: { + settings: 'Settings', + signOut: 'Sign Out', + versionLetter: 'v', + changePassword: 'Change Password', + readTheTermsAndPrivacyPolicy: { + phrase1: 'Read the', + phrase2: 'terms of service', + phrase3: 'and', + phrase4: 'privacy policy', + }, + }, + passwordPage: { + changePassword: 'Change Password', + changingYourPasswordPrompt: 'Changing your password will update your password for both your Expensify.com\nand Expensify.cash accounts.', + currentPassword: 'Current Password', + newPassword: 'New Password', + newPasswordPrompt: 'New password must be different than your old password, have at least 8 characters,\n1 capital letter, 1 lowercase letter, 1 number.', + confirmNewPassword: 'Confirm New Password', + }, + paymentsPage: { + enterYourUsernameToGetPaidViaPayPal: 'Enter your username to get paid back via PayPal.', + payPalMe: 'PayPal.me/', + yourPayPalUsername: 'Your PayPal username', + addPayPalAccount: 'Add PayPal Account', + }, + preferencesPage: { + mostRecent: 'Most Recent', + mostRecentModeDescription: 'This will display all chats by default, sorted by most recent, with pinned items at the top', + focus: '#focus', + focusModeDescription: '#focus – This will only display unread and pinned chats, all sorted alphabetically.', + notifications: 'Notifications', + receiveRelevantFeatureUpdatesAndExpensifyNews: 'Receive relevant feature updates and Expensify news', + priorityMode: 'Priority Mode', + }, + signInPage: { + expensifyDotCash: 'Expensify.cash', + expensifyIsOpenSource: 'Expensify.cash is open source', + theCode: 'the code', + openJobs: 'open jobs', + }, + termsOfUse: { + phrase1: 'By logging in, you agree to the', + phrase2: 'terms of service', + phrase3: 'and', + phrase4: 'privacy policy', + phrase5: '. Money transmission is provided by Expensify Payments LLC (NMLS ID:2017010) pursuant to its', + phrase6: 'licenses', + }, + passwordForm: { + pleaseFillOutAllFields: 'Please fill out all fields', + forgot: 'Forgot?', + twoFactorCode: 'Two Factor Code', + requiredWhen2FAEnabled: 'Required when 2FA is enabled', + }, + loginForm: { + pleaseEnterEmailOrPhoneNumber: 'Please enter an email or phone number', + phoneOrEmail: 'Phone or Email', + enterYourPhoneOrEmail: 'Enter your phone or email:', + }, + resendValidationForm: { + linkHasBeenResent: 'Link has been re-sent', + accountUnvalidated: 'Account is unvalidated', + accountForgotPassword: 'Account forgot password: Sending reset password link.', + weSentYouMagicSignInLink: 'We\'ve sent you a magic sign in link – just click on it to log in!', + resendLink: 'Resend Link', + }, + detailsPage: { + details: 'Details', + localTime: 'Local Time', + }, + newGroupPage: { + createGroup: 'Create Group', + }, + notFound: { + chatYouLookingForCannotBeFound: 'The chat you are looking for cannot be found.', + getMeOutOfHere: 'Get me out of here', + }, + setPasswordPage: { + passwordCannotBeBlank: 'Password cannot be blank', + enterPassword: 'Enter a password', + setPassword: 'Set Password', + }, + attachmentView: { + unknownFilename: 'Unknown Filename', + }, + pronouns: { + heHimHis: 'He/him', + sheHerHers: 'She/her', + theyThemTheirs: 'They/them', + zeHirHirs: 'Ze/hir', + selfSelect: 'Self-select', + callMeByMyName: 'Call me by my name', + }, + noPhoneNumberMessage: 'Please enter a phone number including the country code e.g +447814266907', + maxParticipantsReachedMessage: 'You\'ve reached the maximum number of participants for a group chat.', + cameraPermissionsNotGranted: 'Camera permissions not granted', }; diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index a10144982bfa..af61f8d386e4 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -8,6 +8,7 @@ import {getDefaultAvatar} from './actions/PersonalDetails'; import ONYXKEYS from '../ONYXKEYS'; import CONST from '../CONST'; import {getReportParticipantsTitle} from './reportUtils'; +import {translate} from './translate'; import Permissions from './Permissions'; /** @@ -18,6 +19,7 @@ import Permissions from './Permissions'; let currentUserLogin; let countryCodeByIP; +let preferredLocale; // We are initializing a default avatar here so that we use the same default color for each user we are inviting. This // will update when the OptionsListUtils re-loads. But will stay the same color for the life of the JS session. @@ -130,6 +132,11 @@ Onyx.connect({ callback: val => countryCodeByIP = val || 1, }); +Onyx.connect({ + key: ONYXKEYS.PREFERRED_LOCALE, + callback: val => preferredLocale = val || 'en', +}); + /** * Searches for a match when provided with a value * @@ -451,12 +458,12 @@ function getSidebarOptions(reports, personalDetails, draftComments, activeReport */ function getHeaderMessage(hasSelectableOptions, hasUserToInvite, searchValue, maxParticipantsReached = false) { if (maxParticipantsReached) { - return CONST.MESSAGES.MAXIMUM_PARTICIPANTS_REACHED; + return translate(preferredLocale, CONST.MESSAGES.MAXIMUM_PARTICIPANTS_REACHED); } if (!hasSelectableOptions && !hasUserToInvite) { if (/^\d+$/.test(searchValue)) { - return CONST.MESSAGES.NO_PHONE_NUMBER; + return translate(preferredLocale, CONST.MESSAGES.NO_PHONE_NUMBER); } return searchValue; diff --git a/src/pages/DetailsPage.js b/src/pages/DetailsPage.js old mode 100644 new mode 100755 index 7a99bbdd61cf..3fa3d0ba3bc6 --- a/src/pages/DetailsPage.js +++ b/src/pages/DetailsPage.js @@ -14,6 +14,8 @@ import HeaderWithCloseButton from '../components/HeaderWithCloseButton'; import Navigation from '../libs/Navigation/Navigation'; import ScreenWrapper from '../components/ScreenWrapper'; import personalDetailsPropType from './personalDetailsPropType'; +import withLocalize, {withLocalizePropTypes} from '../components/withLocalize'; +import compose from '../libs/compose'; const matchType = PropTypes.shape({ params: PropTypes.shape({ @@ -32,19 +34,20 @@ const propTypes = { // Route params route: matchType.isRequired, + + ...withLocalizePropTypes, }; -const DetailsPage = ({personalDetails, route}) => { +const DetailsPage = ({personalDetails, route, translate}) => { const details = personalDetails[route.params.login]; // If we have a reportID param this means that we // arrived here via the ParticipantsPage and should be allowed to navigate back to it const shouldShowBackButton = Boolean(route.params.reportID); - return ( Navigation.dismissModal()} @@ -74,7 +77,9 @@ const DetailsPage = ({personalDetails, route}) => { {details.login ? ( - {Str.isSMSLogin(details.login) ? 'Phone Number' : 'Email'} + {translate(Str.isSMSLogin(details.login) + ? 'common.phoneNumber' + : 'common.email')} {Str.isSMSLogin(details.login) @@ -86,7 +91,7 @@ const DetailsPage = ({personalDetails, route}) => { {details.pronouns ? ( - Preferred Pronouns + {translate('profilePage.preferredPronouns')} {details.pronouns} @@ -96,7 +101,7 @@ const DetailsPage = ({personalDetails, route}) => { {details.timezone ? ( - Local Time + {translate('detailsPage.localTime')} {moment().tz(details.timezone.selected).format('LT')} @@ -116,8 +121,11 @@ const DetailsPage = ({personalDetails, route}) => { DetailsPage.propTypes = propTypes; DetailsPage.displayName = 'DetailsPage'; -export default withOnyx({ - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS, - }, -})(DetailsPage); +export default compose( + withLocalize, + withOnyx({ + personalDetails: { + key: ONYXKEYS.PERSONAL_DETAILS, + }, + }), +)(DetailsPage); diff --git a/src/pages/NewChatPage.js b/src/pages/NewChatPage.js old mode 100644 new mode 100755 index f0aa0160c889..2eae67d9f2e6 --- a/src/pages/NewChatPage.js +++ b/src/pages/NewChatPage.js @@ -12,6 +12,8 @@ import withWindowDimensions, {windowDimensionsPropTypes} from '../components/wit import HeaderWithCloseButton from '../components/HeaderWithCloseButton'; import Navigation from '../libs/Navigation/Navigation'; import ScreenWrapper from '../components/ScreenWrapper'; +import withLocalize, {withLocalizePropTypes} from '../components/withLocalize'; +import compose from '../libs/compose'; const personalDetailsPropTypes = PropTypes.shape({ // The login of the person (either email or phone number) @@ -23,6 +25,8 @@ const personalDetailsPropTypes = PropTypes.shape({ // This is either the user's full name, or their login if full name is an empty string displayName: PropTypes.string.isRequired, + + ...withLocalizePropTypes, }); const propTypes = { @@ -74,7 +78,7 @@ class NewChatPage extends Component { const sections = []; sections.push({ - title: 'CONTACTS', + title: this.props.translate('iou.contacts'), data: this.state.personalDetails, shouldShow: this.state.personalDetails.length > 0, indexOffset: sections.reduce((prev, {data}) => prev + data.length, 0), @@ -114,7 +118,7 @@ class NewChatPage extends Component { return ( Navigation.dismissModal(true)} /> @@ -152,14 +156,18 @@ class NewChatPage extends Component { NewChatPage.propTypes = propTypes; -export default withWindowDimensions(withOnyx({ - reports: { - key: ONYXKEYS.COLLECTION.REPORT, - }, - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS, - }, - session: { - key: ONYXKEYS.SESSION, - }, -})(NewChatPage)); +export default compose( + withLocalize, + withWindowDimensions, + withOnyx({ + reports: { + key: ONYXKEYS.COLLECTION.REPORT, + }, + personalDetails: { + key: ONYXKEYS.PERSONAL_DETAILS, + }, + session: { + key: ONYXKEYS.SESSION, + }, + }), +)(NewChatPage); diff --git a/src/pages/NewGroupPage.js b/src/pages/NewGroupPage.js old mode 100644 new mode 100755 index 3b3d6cad19ae..f1e83de73ae5 --- a/src/pages/NewGroupPage.js +++ b/src/pages/NewGroupPage.js @@ -14,6 +14,8 @@ import withWindowDimensions, {windowDimensionsPropTypes} from '../components/wit import HeaderWithCloseButton from '../components/HeaderWithCloseButton'; import ScreenWrapper from '../components/ScreenWrapper'; import Navigation from '../libs/Navigation/Navigation'; +import withLocalize, {withLocalizePropTypes} from '../components/withLocalize'; +import compose from '../libs/compose'; const personalDetailsPropTypes = PropTypes.shape({ // The login of the person (either email or phone number) @@ -43,6 +45,8 @@ const propTypes = { }).isRequired, ...windowDimensionsPropTypes, + + ...withLocalizePropTypes, }; class NewGroupPage extends Component { @@ -92,14 +96,14 @@ class NewGroupPage extends Component { } sections.push({ - title: 'RECENTS', + title: this.props.translate('iou.recents'), data: this.state.recentReports, shouldShow: this.state.recentReports.length > 0, indexOffset: sections.reduce((prev, {data}) => prev + data.length, 0), }); sections.push({ - title: 'CONTACTS', + title: this.props.translate('iou.contacts'), data: this.state.personalDetails, shouldShow: this.state.personalDetails.length > 0, indexOffset: sections.reduce((prev, {data}) => prev + data.length, 0), @@ -182,7 +186,7 @@ class NewGroupPage extends Component { return ( Navigation.dismissModal(true)} /> @@ -228,7 +232,7 @@ class NewGroupPage extends Component { ]} > - Create Group + {this.props.translate('newGroupPage.createGroup')} @@ -242,14 +246,18 @@ class NewGroupPage extends Component { NewGroupPage.propTypes = propTypes; -export default withWindowDimensions(withOnyx({ - reports: { - key: ONYXKEYS.COLLECTION.REPORT, - }, - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS, - }, - session: { - key: ONYXKEYS.SESSION, - }, -})(NewGroupPage)); +export default compose( + withLocalize, + withWindowDimensions, + withOnyx({ + reports: { + key: ONYXKEYS.COLLECTION.REPORT, + }, + personalDetails: { + key: ONYXKEYS.PERSONAL_DETAILS, + }, + session: { + key: ONYXKEYS.SESSION, + }, + }), +)(NewGroupPage); diff --git a/src/pages/NotFound.js b/src/pages/NotFound.js old mode 100644 new mode 100755 index fc73cd44a302..9372506327c6 --- a/src/pages/NotFound.js +++ b/src/pages/NotFound.js @@ -10,8 +10,9 @@ import styles from '../styles/styles'; import logo from '../../assets/images/expensify-logo_reversed.png'; import Navigation from '../libs/Navigation/Navigation'; import ROUTES from '../ROUTES'; +import withLocalize, {withLocalizePropTypes} from '../components/withLocalize'; -const NotFound = () => ( +const NotFound = ({translations: {translate}}) => ( <> ( /> 404 - The chat you are looking for cannot be found. + {translate('notFound.chatYouLookingForCannotBeFound')} Navigation.navigate(ROUTES.HOME)} > - Get me out of here + {translate('notFound.getMeOutOfHere')} ); -export default NotFound; +NotFound.propTypes = {...withLocalizePropTypes}; + +export default withLocalize(NotFound); diff --git a/src/pages/ReportParticipantsPage.js b/src/pages/ReportParticipantsPage.js old mode 100644 new mode 100755 index 697914b452ae..b1975196f760 --- a/src/pages/ReportParticipantsPage.js +++ b/src/pages/ReportParticipantsPage.js @@ -14,6 +14,8 @@ import ScreenWrapper from '../components/ScreenWrapper'; import OptionsList from '../components/OptionsList'; import ROUTES from '../ROUTES'; import personalDetailsPropType from './personalDetailsPropType'; +import withLocalize, {withLocalizePropTypes} from '../components/withLocalize'; +import compose from '../libs/compose'; const propTypes = { /* Onyx Props */ @@ -39,6 +41,8 @@ const propTypes = { reportID: PropTypes.string, }), }).isRequired, + + ...withLocalizePropTypes, }; /** @@ -68,13 +72,18 @@ const getAllParticipants = (report, personalDetails) => { }); }; -const ReportParticipantsPage = ({personalDetails, report, route}) => { +const ReportParticipantsPage = ({ + personalDetails, + report, + route, + translate, +}) => { const participants = getAllParticipants(report, personalDetails); return ( { ReportParticipantsPage.propTypes = propTypes; ReportParticipantsPage.displayName = 'ParticipantsPage'; -export default withOnyx({ - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS, - }, - report: { - key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.reportID}`, - }, -})(ReportParticipantsPage); +export default compose( + withLocalize, + withOnyx({ + personalDetails: { + key: ONYXKEYS.PERSONAL_DETAILS, + }, + report: { + key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.reportID}`, + }, + }), +)(ReportParticipantsPage); diff --git a/src/pages/SearchPage.js b/src/pages/SearchPage.js old mode 100644 new mode 100755 index 339b4cfe28bf..2ba3ebc84e4a --- a/src/pages/SearchPage.js +++ b/src/pages/SearchPage.js @@ -15,6 +15,8 @@ import HeaderWithCloseButton from '../components/HeaderWithCloseButton'; import ScreenWrapper from '../components/ScreenWrapper'; import Timing from '../libs/actions/Timing'; import CONST from '../CONST'; +import withLocalize, {withLocalizePropTypes} from '../components/withLocalize'; +import compose from '../libs/compose'; const personalDetailsPropTypes = PropTypes.shape({ // The login of the person (either email or phone number) @@ -47,6 +49,8 @@ const propTypes = { /* Window Dimensions Props */ ...windowDimensionsPropTypes, + + ...withLocalizePropTypes, }; class SearchPage extends Component { @@ -86,7 +90,7 @@ class SearchPage extends Component { */ getSections() { const sections = [{ - title: 'RECENT', + title: this.props.translate('iou.recents'), data: this.state.recentReports.concat(this.state.personalDetails), shouldShow: true, indexOffset: 0, @@ -138,7 +142,7 @@ class SearchPage extends Component { return ( Navigation.dismissModal(true)} /> @@ -178,14 +182,18 @@ class SearchPage extends Component { SearchPage.propTypes = propTypes; SearchPage.displayName = 'SearchPage'; -export default withWindowDimensions(withOnyx({ - reports: { - key: ONYXKEYS.COLLECTION.REPORT, - }, - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS, - }, - session: { - key: ONYXKEYS.SESSION, - }, -})(SearchPage)); +export default compose( + withLocalize, + withWindowDimensions, + withOnyx({ + reports: { + key: ONYXKEYS.COLLECTION.REPORT, + }, + personalDetails: { + key: ONYXKEYS.PERSONAL_DETAILS, + }, + session: { + key: ONYXKEYS.SESSION, + }, + }), +)(SearchPage); diff --git a/src/pages/SetPasswordPage.js b/src/pages/SetPasswordPage.js old mode 100644 new mode 100755 index 8309ad5bd728..6e45d9ea353a --- a/src/pages/SetPasswordPage.js +++ b/src/pages/SetPasswordPage.js @@ -17,6 +17,8 @@ import ButtonWithLoader from '../components/ButtonWithLoader'; import themeColors from '../styles/themes/default'; import SignInPageLayout from './signin/SignInPageLayout'; import canFocusInputOnScreenFocus from '../libs/canFocusInputOnScreenFocus'; +import withLocalize, {withLocalizePropTypes} from '../components/withLocalize'; +import compose from '../libs/compose'; const propTypes = { /* Onyx Props */ @@ -41,6 +43,8 @@ const propTypes = { // The accountID and validateCode are passed via the URL route: validateLinkPropTypes, + + ...withLocalizePropTypes, }; const defaultProps = { @@ -69,7 +73,7 @@ class SetPasswordPage extends Component { validateAndSubmitForm() { if (!this.state.password.trim()) { this.setState({ - formError: 'Password cannot be blank', + formError: this.props.translate('setPasswordPage.passwordCannotBeBlank'), }); return; } @@ -89,7 +93,9 @@ class SetPasswordPage extends Component { - Enter a password: + + {this.props.translate('setPasswordPage.enterPassword')} + @@ -131,7 +137,10 @@ class SetPasswordPage extends Component { SetPasswordPage.propTypes = propTypes; SetPasswordPage.defaultProps = defaultProps; -export default withOnyx({ - credentials: {key: ONYXKEYS.CREDENTIALS}, - account: {key: ONYXKEYS.ACCOUNT}, -})(SetPasswordPage); +export default compose( + withLocalize, + withOnyx({ + credentials: {key: ONYXKEYS.CREDENTIALS}, + account: {key: ONYXKEYS.ACCOUNT}, + }), +)(SetPasswordPage); diff --git a/src/pages/home/report/EmojiPickerMenu/index.js b/src/pages/home/report/EmojiPickerMenu/index.js old mode 100644 new mode 100755 index c6dbedb14c6f..06d453009695 --- a/src/pages/home/report/EmojiPickerMenu/index.js +++ b/src/pages/home/report/EmojiPickerMenu/index.js @@ -9,6 +9,8 @@ import emojis from '../../../../../assets/emojis'; import EmojiPickerMenuItem from '../EmojiPickerMenuItem'; import TextInputFocusable from '../../../../components/TextInputFocusable'; import withWindowDimensions, {windowDimensionsPropTypes} from '../../../../components/withWindowDimensions'; +import withLocalize, {withLocalizePropTypes} from '../../../../components/withLocalize'; +import compose from '../../../../libs/compose'; const propTypes = { // Function to add the selected emoji to the main compose text input @@ -18,6 +20,8 @@ const propTypes = { forwardedRef: PropTypes.func, ...windowDimensionsPropTypes, + + ...withLocalizePropTypes, }; const defaultProps = { @@ -305,7 +309,7 @@ class EmojiPickerMenu extends Component { ( +export default compose( + withWindowDimensions, + withLocalize, +)(React.forwardRef((props, ref) => ( // eslint-disable-next-line react/jsx-props-no-spreading ))); diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js old mode 100644 new mode 100755 index dd28bcb37b4f..b738c5436dba --- a/src/pages/home/report/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose.js @@ -39,6 +39,7 @@ import getButtonState from '../../../libs/getButtonState'; import CONST from '../../../CONST'; import canFocusInputOnScreenFocus from '../../../libs/canFocusInputOnScreenFocus'; import variables from '../../../styles/variables'; +import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; import Permissions from '../../../libs/Permissions'; import Navigation from '../../../libs/Navigation/Navigation'; import ROUTES from '../../../ROUTES'; @@ -81,6 +82,7 @@ const propTypes = { isOffline: PropTypes.bool, }), + ...withLocalizePropTypes, }; const defaultProps = { @@ -304,7 +306,7 @@ class ReportActionCompose extends React.Component { ]} > { addAction(this.props.reportID, '', file); this.setTextInputShouldClear(false); @@ -337,7 +339,7 @@ class ReportActionCompose extends React.Component { hasMultipleParticipants ? { icon: Receipt, - text: 'Split Bill', + text: this.props.translate('iou.splitBill'), onSelected: () => { Navigation.navigate( ROUTES.getIouSplitRoute(this.props.reportID), @@ -346,7 +348,7 @@ class ReportActionCompose extends React.Component { } : { icon: MoneyCircle, - text: 'Request Money', + text: this.props.translate('iou.requestMoney'), onSelected: () => { Navigation.navigate( ROUTES.getIouRequestRoute(this.props.reportID), @@ -356,7 +358,7 @@ class ReportActionCompose extends React.Component { ] : []), { icon: Paperclip, - text: 'Add Attachment', + text: this.props.translate('reportActionCompose.addAttachment'), onSelected: () => { openPicker({ onPicked: (file) => { @@ -375,7 +377,7 @@ class ReportActionCompose extends React.Component { multiline ref={el => this.textInput = el} textAlignVertical="top" - placeholder="Write something..." + placeholder={this.props.translate('reportActionCompose.writeSomething')} placeholderTextColor={themeColors.placeholderText} onChangeText={this.updateComment} onKeyPress={this.triggerSubmitShortcut} @@ -466,7 +468,7 @@ class ReportActionCompose extends React.Component { height={variables.iconSizeExtraSmall} /> - You appear to be offline. + {this.props.translate('reportActionCompose.youAppearToBeOffline')} @@ -483,6 +485,7 @@ export default compose( withWindowDimensions, withDrawerState, withNavigationFocus, + withLocalize, withOnyx({ comment: { key: ({reportID}) => `${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`, diff --git a/src/pages/home/report/ReportActionContextMenu.js b/src/pages/home/report/ReportActionContextMenu.js old mode 100644 new mode 100755 index 5edbd19eff68..ee5a75f8b12d --- a/src/pages/home/report/ReportActionContextMenu.js +++ b/src/pages/home/report/ReportActionContextMenu.js @@ -12,6 +12,7 @@ import ReportActionContextMenuItem from './ReportActionContextMenuItem'; import ReportActionPropTypes from './ReportActionPropTypes'; import Clipboard from '../../../libs/Clipboard'; import {isReportMessageAttachment} from '../../../libs/reportUtils'; +import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; const propTypes = { // The ID of the report this report action is attached to. @@ -27,6 +28,8 @@ const propTypes = { // Controls the visibility of this component. isVisible: PropTypes.bool, + + ...withLocalizePropTypes, }; const defaultProps = { @@ -42,9 +45,9 @@ class ReportActionContextMenu extends React.Component { this.CONTEXT_ACTIONS = [ // Copy to clipboard { - text: 'Copy to Clipboard', + text: this.props.translate('reportActionContextMenu.copyToClipboard'), icon: ClipboardIcon, - successText: 'Copied!', + successText: this.props.translate('reportActionContextMenu.copied'), successIcon: Checkmark, shouldShow: true, @@ -67,14 +70,14 @@ class ReportActionContextMenu extends React.Component { }, { - text: 'Copy Link', + text: this.props.translate('reportActionContextMenu.copyLink'), icon: LinkCopy, shouldShow: false, onPress: () => {}, }, { - text: 'Mark as Unread', + text: this.props.translate('reportActionContextMenu.markAsUnread'), icon: Mail, successIcon: Checkmark, shouldShow: true, @@ -85,14 +88,14 @@ class ReportActionContextMenu extends React.Component { }, { - text: 'Edit Comment', + text: this.props.translate('reportActionContextMenu.editComment'), icon: Pencil, shouldShow: false, onPress: () => {}, }, { - text: 'Delete Comment', + text: this.props.translate('reportActionContextMenu.deleteComment'), icon: Trashcan, shouldShow: false, onPress: () => {}, @@ -124,4 +127,4 @@ class ReportActionContextMenu extends React.Component { ReportActionContextMenu.propTypes = propTypes; ReportActionContextMenu.defaultProps = defaultProps; -export default ReportActionContextMenu; +export default withLocalize(ReportActionContextMenu); diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js old mode 100644 new mode 100755 index f267ef26880c..d4af96718f73 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -30,6 +30,7 @@ import themeColors from '../../../styles/themes/default'; import compose from '../../../libs/compose'; import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions'; import withDrawerState, {withDrawerPropTypes} from '../../../components/withDrawerState'; +import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; const propTypes = { // The ID of the report actions will be created for @@ -66,6 +67,7 @@ const propTypes = { ...windowDimensionsPropTypes, ...withDrawerPropTypes, + ...withLocalizePropTypes, }; const defaultProps = { @@ -364,7 +366,9 @@ class ReportActionsView extends React.Component { if (_.size(this.props.reportActions) === 1) { return ( - Be the first person to comment! + + {this.props.translate('reportActionsView.beFirstPersonToComment')} + ); } @@ -394,6 +398,7 @@ ReportActionsView.defaultProps = defaultProps; export default compose( withWindowDimensions, withDrawerState, + withLocalize, withOnyx({ report: { key: ({reportID}) => `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, diff --git a/src/pages/home/report/ReportTypingIndicator.js b/src/pages/home/report/ReportTypingIndicator.js old mode 100644 new mode 100755 index 7936d7dfc7b8..f7bfaa9ab3db --- a/src/pages/home/report/ReportTypingIndicator.js +++ b/src/pages/home/report/ReportTypingIndicator.js @@ -7,10 +7,13 @@ import compose from '../../../libs/compose'; import ONYXKEYS from '../../../ONYXKEYS'; import styles from '../../../styles/styles'; import {getDisplayName} from '../../../libs/actions/PersonalDetails'; +import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; const propTypes = { // Key-value pairs of user logins and whether or not they are typing. Keys are logins. userTypingStatuses: PropTypes.objectOf(PropTypes.bool), + + ...withLocalizePropTypes, }; const defaultProps = { @@ -56,7 +59,7 @@ class ReportTypingIndicator extends React.Component { ]} > {getDisplayName(this.state.usersTyping[0])} - {' is typing...'} + {this.props.translate('reportTypingIndicator.isTyping')} ); @@ -69,9 +72,9 @@ class ReportTypingIndicator extends React.Component { ]} > {getDisplayName(this.state.usersTyping[0])} - {' and '} + {this.props.translate('reportTypingIndicator.and')} {getDisplayName(this.state.usersTyping[1])} - {' are typing...'} + {this.props.translate('reportTypingIndicator.areTyping')} ); @@ -83,8 +86,10 @@ class ReportTypingIndicator extends React.Component { styles.chatItemComposeSecondaryRowOffset, ]} > - Multiple users - {' are typing...'} + + {this.props.translate('reportTypingIndicator.multipleUsers')} + + {this.props.translate('reportTypingIndicator.areTyping')} ); @@ -97,6 +102,7 @@ ReportTypingIndicator.defaultProps = defaultProps; ReportTypingIndicator.displayName = 'ReportTypingIndicator'; export default compose( + withLocalize, withOnyx({ userTypingStatuses: { key: ({reportID}) => `${ONYXKEYS.COLLECTION.REPORT_USER_IS_TYPING}${reportID}`, diff --git a/src/pages/home/sidebar/SidebarScreen.js b/src/pages/home/sidebar/SidebarScreen.js old mode 100644 new mode 100755 index ff4ef2ee630c..1e90b7aa98a4 --- a/src/pages/home/sidebar/SidebarScreen.js +++ b/src/pages/home/sidebar/SidebarScreen.js @@ -10,6 +10,8 @@ import ROUTES from '../../../ROUTES'; import Timing from '../../../libs/actions/Timing'; import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions'; import CONST from '../../../CONST'; +import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; +import compose from '../../../libs/compose'; import { ChatBubble, Users, @@ -21,6 +23,8 @@ import Permissions from '../../../libs/Permissions'; const propTypes = { // propTypes for withWindowDimensions ...windowDimensionsPropTypes, + + ...withLocalizePropTypes, }; class SidebarScreen extends Component { @@ -101,25 +105,25 @@ class SidebarScreen extends Component { menuItems={[ { icon: ChatBubble, - text: 'New Chat', + text: this.props.translate('sidebarScreen.newChat'), onSelected: () => Navigation.navigate(ROUTES.NEW_CHAT), }, ...(Permissions.canUseIOU() ? [ { icon: MoneyCircle, - text: 'Request Money', + text: this.props.translate('iou.requestMoney'), onSelected: () => Navigation.navigate(ROUTES.IOU_REQUEST), }, ] : []), { icon: Users, - text: 'New Group', + text: this.props.translate('sidebarScreen.newGroup'), onSelected: () => Navigation.navigate(ROUTES.NEW_GROUP), }, ...(Permissions.canUseIOU() ? [ { icon: Receipt, - text: 'Split Bill', + text: this.props.translate('iou.splitBill'), onSelected: () => Navigation.navigate(ROUTES.IOU_BILL), }, ] : []), @@ -133,4 +137,7 @@ class SidebarScreen extends Component { } SidebarScreen.propTypes = propTypes; -export default withWindowDimensions(SidebarScreen); +export default compose( + withLocalize, + withWindowDimensions, +)(SidebarScreen); diff --git a/src/pages/iou/IOUModal.js b/src/pages/iou/IOUModal.js old mode 100644 new mode 100755 index f3fe5f198afc..b18633b2ba0b --- a/src/pages/iou/IOUModal.js +++ b/src/pages/iou/IOUModal.js @@ -13,6 +13,8 @@ import {createIOUSplit, createIOUTransaction, getPreferredCurrency} from '../../ import {Close, BackArrow} from '../../components/Icon/Expensicons'; import Navigation from '../../libs/Navigation/Navigation'; import ONYXKEYS from '../../ONYXKEYS'; +import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; +import compose from '../../libs/compose'; import {getPersonalDetailsForLogins} from '../../libs/OptionsListUtils'; /** @@ -48,6 +50,8 @@ const propTypes = { // Avatar url of participant avatar: PropTypes.string, }).isRequired, + + ...withLocalizePropTypes, }; const defaultProps = { @@ -59,9 +63,9 @@ const defaultProps = { // Determines type of step to display within Modal, value provides the title for that page. const Steps = { - IOUAmount: 'Amount', - IOUParticipants: 'Participants', - IOUConfirm: 'Confirm', + IOUAmount: 'iou.amount', + IOUParticipants: 'iou.participants', + IOUConfirm: 'iou.confirm', }; class IOUModal extends Component { @@ -122,12 +126,15 @@ class IOUModal extends Component { getTitleForStep() { const currentStepIndex = this.state.currentStepIndex; if (currentStepIndex === 1 || currentStepIndex === 2) { - return `${this.props.hasMultipleParticipants ? 'Split' : 'Request'} $${this.state.amount}`; + return `${this.props.hasMultipleParticipants + ? this.props.translate('common.split') + : this.props.translate('common.request')} $${this.state.amount}`; } if (currentStepIndex === 0) { - return this.props.hasMultipleParticipants ? 'Split Bill' : 'Request Money'; + return this.props.translate(this.props.hasMultipleParticipants ? 'iou.splitBill' : 'iou.requestMoney'); } - return this.steps[currentStepIndex] || ''; + + return this.props.translate(this.steps[currentStepIndex]) || ''; } addParticipants(participants) { @@ -278,17 +285,20 @@ IOUModal.propTypes = propTypes; IOUModal.defaultProps = defaultProps; IOUModal.displayName = 'IOUModal'; -export default withOnyx({ - report: { - key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.reportID}`, - }, - iousReport: { - key: ONYXKEYS.COLLECTION.REPORT_IOUS, - }, - iou: { - key: ONYXKEYS.IOU, - }, - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS, - }, -})(IOUModal); +export default compose( + withLocalize, + withOnyx({ + report: { + key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.reportID}`, + }, + iousReport: { + key: ONYXKEYS.COLLECTION.REPORT_IOUS, + }, + iou: { + key: ONYXKEYS.IOU, + }, + personalDetails: { + key: ONYXKEYS.PERSONAL_DETAILS, + }, + }), +)(IOUModal); diff --git a/src/pages/iou/steps/IOUAmountPage.js b/src/pages/iou/steps/IOUAmountPage.js old mode 100644 new mode 100755 index e266b6359451..507a9f9624f6 --- a/src/pages/iou/steps/IOUAmountPage.js +++ b/src/pages/iou/steps/IOUAmountPage.js @@ -13,6 +13,8 @@ import themeColors from '../../../styles/themes/default'; import BigNumberPad from '../../../components/BigNumberPad'; import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions'; import TextInputAutoWidth from '../../../components/TextInputAutoWidth'; +import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; +import compose from '../../../libs/compose'; const propTypes = { // Callback to inform parent modal of success @@ -36,6 +38,8 @@ const propTypes = { // Whether or not the IOU step is loading (retrieving users preferred currency) loading: PropTypes.bool, }), + + ...withLocalizePropTypes, }; const defaultProps = { @@ -129,7 +133,7 @@ class IOUAmountPage extends React.Component { disabled={this.state.amount.length === 0} > - Next + {this.props.translate('common.next')} @@ -141,6 +145,10 @@ IOUAmountPage.displayName = 'IOUAmountPage'; IOUAmountPage.propTypes = propTypes; IOUAmountPage.defaultProps = defaultProps; -export default withWindowDimensions(withOnyx({ - iou: {key: ONYXKEYS.IOU}, -})(IOUAmountPage)); +export default compose( + withWindowDimensions, + withLocalize, + withOnyx({ + iou: {key: ONYXKEYS.IOU}, + }), +)(IOUAmountPage); diff --git a/src/pages/iou/steps/IOUParticipantsPage/IOUParticipantsRequest.js b/src/pages/iou/steps/IOUParticipantsPage/IOUParticipantsRequest.js old mode 100644 new mode 100755 index 2ade0725a778..bfb83d80fcf2 --- a/src/pages/iou/steps/IOUParticipantsPage/IOUParticipantsRequest.js +++ b/src/pages/iou/steps/IOUParticipantsPage/IOUParticipantsRequest.js @@ -4,6 +4,8 @@ import {withOnyx} from 'react-native-onyx'; import {getNewChatOptions} from '../../../../libs/OptionsListUtils'; import OptionsSelector from '../../../../components/OptionsSelector'; import ONYXKEYS from '../../../../ONYXKEYS'; +import withLocalize, {withLocalizePropTypes} from '../../../../components/withLocalize'; +import compose from '../../../../libs/compose'; const personalDetailsPropTypes = PropTypes.shape({ // The login of the person (either email or phone number) @@ -32,6 +34,8 @@ const propTypes = { reportID: PropTypes.number, reportName: PropTypes.string, }).isRequired, + + ...withLocalizePropTypes, }; class IOUParticipantsRequest extends Component { @@ -61,7 +65,7 @@ class IOUParticipantsRequest extends Component { getSections() { const sections = []; sections.push({ - title: 'CONTACTS', + title: this.props.translate('iou.contacts'), data: this.state.personalDetails, shouldShow: this.state.personalDetails.length > 0, indexOffset: 0, @@ -120,11 +124,14 @@ class IOUParticipantsRequest extends Component { IOUParticipantsRequest.displayName = 'IOUParticipantsRequest'; IOUParticipantsRequest.propTypes = propTypes; -export default withOnyx({ - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS, - }, - reports: { - key: ONYXKEYS.COLLECTION.REPORT, - }, -})(IOUParticipantsRequest); +export default compose( + withLocalize, + withOnyx({ + personalDetails: { + key: ONYXKEYS.PERSONAL_DETAILS, + }, + reports: { + key: ONYXKEYS.COLLECTION.REPORT, + }, + }), +)(IOUParticipantsRequest); diff --git a/src/pages/iou/steps/IOUParticipantsPage/IOUParticipantsSplit.js b/src/pages/iou/steps/IOUParticipantsPage/IOUParticipantsSplit.js old mode 100644 new mode 100755 index 0aa0cafe7ed5..564539b4177d --- a/src/pages/iou/steps/IOUParticipantsPage/IOUParticipantsSplit.js +++ b/src/pages/iou/steps/IOUParticipantsPage/IOUParticipantsSplit.js @@ -12,6 +12,8 @@ import styles from '../../../../styles/styles'; import OptionsSelector from '../../../../components/OptionsSelector'; import {getNewGroupOptions} from '../../../../libs/OptionsListUtils'; import CONST from '../../../../CONST'; +import withLocalize, {withLocalizePropTypes} from '../../../../components/withLocalize'; +import compose from '../../../../libs/compose'; const personalDetailsPropTypes = PropTypes.shape({ // The login of the person (either email or phone number) @@ -54,6 +56,8 @@ const propTypes = { reportID: PropTypes.number, reportName: PropTypes.string, }).isRequired, + + ...withLocalizePropTypes, }; const defaultProps = { @@ -106,7 +110,7 @@ class IOUParticipantsSplit extends Component { } sections.push({ - title: 'RECENTS', + title: this.props.translate('iou.recents'), data: this.state.recentReports, shouldShow: this.state.recentReports.length > 0, @@ -116,7 +120,7 @@ class IOUParticipantsSplit extends Component { }); sections.push({ - title: 'CONTACTS', + title: this.props.translate('iou.contacts'), data: this.state.personalDetails, shouldShow: this.state.personalDetails.length > 0, @@ -191,7 +195,7 @@ class IOUParticipantsSplit extends Component { return ( - To + {this.props.translate('common.to')} - Next + {this.props.translate('common.next')} @@ -247,11 +251,14 @@ IOUParticipantsSplit.displayName = 'IOUParticipantsSplit'; IOUParticipantsSplit.propTypes = propTypes; IOUParticipantsSplit.defaultProps = defaultProps; -export default withOnyx({ - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS, - }, - reports: { - key: ONYXKEYS.COLLECTION.REPORT, - }, -})(IOUParticipantsSplit); +export default compose( + withLocalize, + withOnyx({ + personalDetails: { + key: ONYXKEYS.PERSONAL_DETAILS, + }, + reports: { + key: ONYXKEYS.COLLECTION.REPORT, + }, + }), +)(IOUParticipantsSplit); diff --git a/src/pages/settings/AddSecondaryLoginPage.js b/src/pages/settings/AddSecondaryLoginPage.js old mode 100644 new mode 100755 index 4527654d97f3..56ee6ef0e9f8 --- a/src/pages/settings/AddSecondaryLoginPage.js +++ b/src/pages/settings/AddSecondaryLoginPage.js @@ -15,6 +15,8 @@ import ButtonWithLoader from '../../components/ButtonWithLoader'; import ROUTES from '../../ROUTES'; import CONST from '../../CONST'; import KeyboardAvoidingView from '../../libs/KeyboardAvoidingView'; +import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; +import compose from '../../libs/compose'; const propTypes = { /* Onyx Props */ @@ -48,6 +50,8 @@ const propTypes = { type: PropTypes.string, }), }), + + ...withLocalizePropTypes, }; const defaultProps = { @@ -99,7 +103,9 @@ class AddSecondaryLoginPage extends Component { Navigation.navigate(ROUTES.SETTINGS_PROFILE)} onCloseButtonPress={() => Navigation.dismissModal()} @@ -107,13 +113,15 @@ class AddSecondaryLoginPage extends Component { - {this.formType === CONST.LOGIN_TYPE.PHONE - ? 'Enter your preferred phone number and password to send a validation link.' - : 'Enter your preferred email address and password to send a validation link.'} + {this.props.translate(this.formType === CONST.LOGIN_TYPE.PHONE + ? 'addSecondaryLoginPage.enterPreferredPhoneNumberToSendValidationLink' + : 'addSecondaryLoginPage.enterPreferredEmailToSendValidationLink')} - {this.formType === CONST.LOGIN_TYPE.PHONE ? 'Phone Number' : 'Email Address'} + {this.props.translate(this.formType === CONST.LOGIN_TYPE.PHONE + ? 'common.phoneNumber' + : 'profilePage.emailAddress')} - Password + + {this.props.translate('addSecondaryLoginPage.password')} + @@ -162,8 +172,11 @@ AddSecondaryLoginPage.propTypes = propTypes; AddSecondaryLoginPage.defaultProps = defaultProps; AddSecondaryLoginPage.displayName = 'AddSecondaryLoginPage'; -export default withOnyx({ - user: { - key: ONYXKEYS.USER, - }, -})(AddSecondaryLoginPage); +export default compose( + withLocalize, + withOnyx({ + user: { + key: ONYXKEYS.USER, + }, + }), +)(AddSecondaryLoginPage); diff --git a/src/pages/settings/InitialPage.js b/src/pages/settings/InitialPage.js old mode 100644 new mode 100755 index 9ae4ce9ab941..f8976accd8df --- a/src/pages/settings/InitialPage.js +++ b/src/pages/settings/InitialPage.js @@ -20,6 +20,8 @@ import MenuItem from '../../components/MenuItem'; import ROUTES from '../../ROUTES'; import openURLInNewTab from '../../libs/openURLInNewTab'; import CONST from '../../CONST'; +import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; +import compose from '../../libs/compose'; const propTypes = { /* Onyx Props */ @@ -43,6 +45,8 @@ const propTypes = { // Email of the logged in person email: PropTypes.string, }), + + ...withLocalizePropTypes, }; const defaultProps = { @@ -53,28 +57,28 @@ const defaultProps = { const menuItems = [ { - title: 'Profile', + translationKey: 'common.profile', icon: Profile, action: () => { Navigation.navigate(ROUTES.SETTINGS_PROFILE); }, }, { - title: 'Preferences', + translationKey: 'common.preferences', icon: Gear, action: () => { Navigation.navigate(ROUTES.SETTINGS_PREFERENCES); }, }, { - title: 'Change Password', + translationKey: 'initialSettingsPage.changePassword', icon: Lock, action: () => { Navigation.navigate(ROUTES.SETTINGS_PASSWORD); }, }, { - title: 'Payments', + translationKey: 'common.payments', icon: Wallet, action: () => { Navigation.navigate(ROUTES.SETTINGS_PAYMENTS); }, }, { - title: 'Sign Out', + translationKey: 'initialSettingsPage.signOut', icon: SignOut, action: signOut, }, @@ -84,6 +88,7 @@ const InitialSettingsPage = ({ myPersonalDetails, network, session, + translate, }) => { // On the very first sign in or after clearing storage these // details will not be present on the first render so we'll just @@ -94,7 +99,7 @@ const InitialSettingsPage = ({ return ( Navigation.dismissModal(true)} /> ( item.action()} shouldShowRightArrow @@ -136,26 +141,26 @@ const InitialSettingsPage = ({ - v + {translate('initialSettingsPage.versionLetter')} {version} - Read the + {translate('initialSettingsPage.readTheTermsAndPrivacyPolicy.phrase1')} {' '} openURLInNewTab(CONST.TERMS_URL)} > - terms of service + {translate('initialSettingsPage.readTheTermsAndPrivacyPolicy.phrase2')} {' '} - and + {translate('initialSettingsPage.readTheTermsAndPrivacyPolicy.phrase3')} {' '} openURLInNewTab(CONST.PRIVACY_URL)} > - privacy policy + {translate('initialSettingsPage.readTheTermsAndPrivacyPolicy.phrase4')} . @@ -169,14 +174,17 @@ InitialSettingsPage.propTypes = propTypes; InitialSettingsPage.defaultProps = defaultProps; InitialSettingsPage.displayName = 'InitialSettingsPage'; -export default withOnyx({ - myPersonalDetails: { - key: ONYXKEYS.MY_PERSONAL_DETAILS, - }, - network: { - key: ONYXKEYS.NETWORK, - }, - session: { - key: ONYXKEYS.SESSION, - }, -})(InitialSettingsPage); +export default compose( + withLocalize, + withOnyx({ + myPersonalDetails: { + key: ONYXKEYS.MY_PERSONAL_DETAILS, + }, + network: { + key: ONYXKEYS.NETWORK, + }, + session: { + key: ONYXKEYS.SESSION, + }, + }), +)(InitialSettingsPage); diff --git a/src/pages/settings/PasswordPage.js b/src/pages/settings/PasswordPage.js old mode 100644 new mode 100755 index e01689d4d69d..d43d50e4911d --- a/src/pages/settings/PasswordPage.js +++ b/src/pages/settings/PasswordPage.js @@ -14,6 +14,8 @@ import ONYXKEYS from '../../ONYXKEYS'; import CONST from '../../CONST'; import ButtonWithLoader from '../../components/ButtonWithLoader'; import {changePassword} from '../../libs/actions/User'; +import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; +import compose from '../../libs/compose'; const propTypes = { /* Onyx Props */ @@ -28,6 +30,8 @@ const propTypes = { // Whether or not a sign on form is loading (being submitted) loading: PropTypes.bool, }), + + ...withLocalizePropTypes, }; const defaultProps = { @@ -64,7 +68,7 @@ class PasswordPage extends Component { return ( Navigation.navigate(ROUTES.SETTINGS)} onCloseButtonPress={() => Navigation.dismissModal(true)} @@ -72,11 +76,12 @@ class PasswordPage extends Component { - Changing your password will update your password for both your Expensify.com - and Expensify.cash accounts. + {this.props.translate('passwordPage.changingYourPasswordPrompt')} - Current Password* + + {`${this.props.translate('passwordPage.currentPassword')}*`} + - New Password* + + {`${this.props.translate('passwordPage.newPassword')}*`} + {this.state.isPasswordRequirementsVisible && ( - New password must be different than your old password, have at least 8 characters, - 1 capital letter, 1 lowercase letter, 1 number. + {this.props.translate('passwordPage.newPasswordPrompt')} )} - Confirm New Password* + + {`${this.props.translate('passwordPage.confirmNewPassword')}*`} + @@ -145,8 +153,11 @@ PasswordPage.displayName = 'PasswordPage'; PasswordPage.propTypes = propTypes; PasswordPage.defaultProps = defaultProps; -export default withOnyx({ - account: { - key: ONYXKEYS.ACCOUNT, - }, -})(PasswordPage); +export default compose( + withLocalize, + withOnyx({ + account: { + key: ONYXKEYS.ACCOUNT, + }, + }), +)(PasswordPage); diff --git a/src/pages/settings/PaymentsPage.js b/src/pages/settings/PaymentsPage.js old mode 100644 new mode 100755 index 37db4ba2d203..e62f6cc31282 --- a/src/pages/settings/PaymentsPage.js +++ b/src/pages/settings/PaymentsPage.js @@ -12,9 +12,13 @@ import NameValuePair from '../../libs/actions/NameValuePair'; import {getUserDetails} from '../../libs/actions/User'; import Navigation from '../../libs/Navigation/Navigation'; import styles from '../../styles/styles'; +import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; +import compose from '../../libs/compose'; const propTypes = { payPalMeUsername: PropTypes.string, + + ...withLocalizePropTypes, }; const defaultProps = { @@ -54,7 +58,7 @@ class PaymentsPage extends React.Component { return ( Navigation.navigate(ROUTES.SETTINGS)} onCloseButtonPress={() => Navigation.dismissModal(true)} @@ -62,15 +66,15 @@ class PaymentsPage extends React.Component { - Enter your username to get paid back via PayPal. + {this.props.translate('paymentsPage.enterYourUsernameToGetPaidViaPayPal')} - PayPal.me/ + {this.props.translate('paymentsPage.payPalMe')} this.setState({payPalMeUsername: text})} /> @@ -84,7 +88,7 @@ class PaymentsPage extends React.Component { ]} > - Add PayPal Account + {this.props.translate('paymentsPage.addPayPalAccount')} @@ -97,8 +101,11 @@ PaymentsPage.propTypes = propTypes; PaymentsPage.defaultProps = defaultProps; PaymentsPage.displayName = 'PaymentsPage'; -export default withOnyx({ - payPalMeUsername: { - key: ONYXKEYS.NVP_PAYPAL_ME_ADDRESS, - }, -})(PaymentsPage); +export default compose( + withLocalize, + withOnyx({ + payPalMeUsername: { + key: ONYXKEYS.NVP_PAYPAL_ME_ADDRESS, + }, + }), +)(PaymentsPage); diff --git a/src/pages/settings/PreferencesPage.js b/src/pages/settings/PreferencesPage.js old mode 100644 new mode 100755 index aaa9b480d7ce..6881c8a40a52 --- a/src/pages/settings/PreferencesPage.js +++ b/src/pages/settings/PreferencesPage.js @@ -17,6 +17,8 @@ import {setExpensifyNewsStatus} from '../../libs/actions/User'; import ScreenWrapper from '../../components/ScreenWrapper'; import Switch from '../../components/Switch'; import Picker from '../../components/Picker'; +import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; +import compose from '../../libs/compose'; const propTypes = { // The chat priority mode @@ -27,6 +29,8 @@ const propTypes = { // Whether or not the user is subscribed to news updates expensifyNewsStatus: PropTypes.bool, }), + + ...withLocalizePropTypes, }; const defaultProps = { @@ -34,74 +38,80 @@ const defaultProps = { user: {}, }; -const priorityModes = { - default: { - value: CONST.PRIORITY_MODE.DEFAULT, - label: 'Most Recent', - description: 'This will display all chats by default, sorted by most recent, with pinned items at the top', - }, - gsd: { - value: CONST.PRIORITY_MODE.GSD, - label: '#focus', - description: '#focus – This will only display unread and pinned chats, all sorted alphabetically.', - }, -}; - +const PreferencesPage = ({priorityMode, user, translate}) => { + const priorityModes = { + default: { + value: CONST.PRIORITY_MODE.DEFAULT, + label: translate('preferencesPage.mostRecent'), + description: translate('preferencesPage.mostRecentModeDescription'), + }, + gsd: { + value: CONST.PRIORITY_MODE.GSD, + label: translate('preferencesPage.focus'), + description: translate('preferencesPage.focusModeDescription'), + }, + }; -const PreferencesPage = ({priorityMode, user}) => ( - - Navigation.navigate(ROUTES.SETTINGS)} - onCloseButtonPress={() => Navigation.dismissModal(true)} - /> - - - Notifications - - - - Receive relevant feature updates and Expensify news - + return ( + + Navigation.navigate(ROUTES.SETTINGS)} + onCloseButtonPress={() => Navigation.dismissModal(true)} + /> + + + + {translate('preferencesPage.notifications')} + + + + + {translate('preferencesPage.receiveRelevantFeatureUpdatesAndExpensifyNews')} + + + + + - - + {translate('preferencesPage.priorityMode')} + + + NameValuePair.set(CONST.NVP.PRIORITY_MODE, mode, ONYXKEYS.NVP_PRIORITY_MODE) + } + items={Object.values(priorityModes)} + value={priorityMode} + icon={() => } /> + + {priorityModes[priorityMode].description} + - - Priority Mode - - - NameValuePair.set(CONST.NVP.PRIORITY_MODE, mode, ONYXKEYS.NVP_PRIORITY_MODE) - } - items={Object.values(priorityModes)} - value={priorityMode} - icon={() => } - /> - - - {priorityModes[priorityMode].description} - - - -); + + ); +}; PreferencesPage.propTypes = propTypes; PreferencesPage.defaultProps = defaultProps; PreferencesPage.displayName = 'PreferencesPage'; -export default withOnyx({ - priorityMode: { - key: ONYXKEYS.NVP_PRIORITY_MODE, - }, - user: { - key: ONYXKEYS.USER, - }, -})(PreferencesPage); +export default compose( + withLocalize, + withOnyx({ + priorityMode: { + key: ONYXKEYS.NVP_PRIORITY_MODE, + }, + user: { + key: ONYXKEYS.USER, + }, + }), +)(PreferencesPage); diff --git a/src/pages/settings/Profile/LoginField.js b/src/pages/settings/Profile/LoginField.js old mode 100644 new mode 100755 index 9342f4e7b059..43d1829471e5 --- a/src/pages/settings/Profile/LoginField.js +++ b/src/pages/settings/Profile/LoginField.js @@ -10,6 +10,7 @@ import ROUTES from '../../../ROUTES'; import CONST from '../../../CONST'; import Navigation from '../../../libs/Navigation/Navigation'; import {resendValidateCode} from '../../../libs/actions/User'; +import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; const propTypes = { // Label to display on login form @@ -26,9 +27,11 @@ const propTypes = { // Date of when login was validated validatedDate: PropTypes.string, }).isRequired, + + ...withLocalizePropTypes, }; -export default class LoginField extends Component { +class LoginField extends Component { constructor(props) { super(props); this.state = { @@ -61,21 +64,20 @@ export default class LoginField extends Component { if (this.props.type === CONST.LOGIN_TYPE.PHONE) { // No phone number if (!this.props.login.partnerUserID) { - note = 'Add your phone number to settle up via Venmo.'; + note = this.props.translate('loginField.addYourPhoneToSettleViaVenmo'); // Has unvalidated phone number } else if (!this.props.login.validatedDate) { - // eslint-disable-next-line max-len - note = 'The number has not yet been validated. Click the button to resend the validation link via text.'; + note = this.props.translate('loginField.numberHasNotBeenValidated'); // Has verified phone number } else { - note = 'Use your phone number to settle up via Venmo.'; + note = this.props.translate('loginField.useYourPhoneToSettleViaVenmo'); } // Has unvalidated email } else if (this.props.login.partnerUserID && !this.props.login.validatedDate) { - note = 'The email has not yet been validated. Click the button to resend the validation link via text.'; + note = this.props.translate('loginField.emailHasNotBeenValidated'); } return ( @@ -92,7 +94,7 @@ export default class LoginField extends Component { - {`Add ${this.props.label}`} + {`${this.props.translate('common.add')} ${this.props.label}`} @@ -111,7 +113,7 @@ export default class LoginField extends Component { ) : ( - Resend + {this.props.translate('common.resend')} )} @@ -130,3 +132,5 @@ export default class LoginField extends Component { LoginField.propTypes = propTypes; LoginField.displayName = 'LoginField'; + +export default withLocalize(LoginField); diff --git a/src/pages/settings/Profile/ProfilePage.js b/src/pages/settings/Profile/ProfilePage.js old mode 100644 new mode 100755 index 9844cfb4396d..04b26374a747 --- a/src/pages/settings/Profile/ProfilePage.js +++ b/src/pages/settings/Profile/ProfilePage.js @@ -28,6 +28,8 @@ import {DownArrow, Upload, Trashcan} from '../../../components/Icon/Expensicons' import AttachmentPicker from '../../../components/AttachmentPicker'; import CreateMenu from '../../../components/CreateMenu'; import Picker from '../../../components/Picker'; +import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; +import compose from '../../../libs/compose'; const propTypes = { /* Onyx Props */ @@ -74,6 +76,8 @@ const propTypes = { validatedDate: PropTypes.string, })), }), + + ...withLocalizePropTypes, }; const defaultProps = { @@ -98,14 +102,16 @@ class ProfilePage extends Component { pronouns, timezone = {}, } = props.myPersonalDetails; - const pronounsList = Object.values(CONST.PRONOUNS); + const pronounsKeyList = Object.values(CONST.PRONOUNS); + const pronounsList = []; + pronounsKeyList.map(pronounKey => pronounsList.push(this.props.translate(pronounKey))); let currentUserPronouns = pronouns; let initialSelfSelectedPronouns = ''; // This handles populating the self-selected pronouns in the form if (pronouns && !pronounsList.includes(pronouns)) { - currentUserPronouns = CONST.PRONOUNS.SELF_SELECT; + currentUserPronouns = this.props.translate(CONST.PRONOUNS.SELF_SELECT); initialSelfSelectedPronouns = pronouns; } @@ -195,7 +201,9 @@ class ProfilePage extends Component { setPersonalDetails({ firstName, lastName, - pronouns: pronouns === CONST.PRONOUNS.SELF_SELECT ? selfSelectedPronouns : pronouns, + pronouns: pronouns === this.props.translate(CONST.PRONOUNS.SELF_SELECT) + ? selfSelectedPronouns + : pronouns, timezone: { automatic: isAutomaticTimezone, selected: selectedTimezone, @@ -213,7 +221,7 @@ class ProfilePage extends Component { const menuItems = [ { icon: Upload, - text: 'Upload Photo', + text: this.props.translate('profilePage.uploadPhoto'), onSelected: () => { openPicker({ onPicked: setAvatar, @@ -226,7 +234,7 @@ class ProfilePage extends Component { if (!this.props.myPersonalDetails.avatar.includes('/images/avatars/avatar')) { menuItems.push({ icon: Trashcan, - text: 'Remove Photo', + text: this.props.translate('profilePage.removePhoto'), onSelected: () => { deleteAvatar(this.props.myPersonalDetails.login); }, @@ -251,7 +259,7 @@ class ProfilePage extends Component { return ( Navigation.navigate(ROUTES.SETTINGS)} onCloseButtonPress={() => Navigation.dismissModal(true)} @@ -272,7 +280,7 @@ class ProfilePage extends Component { - Edit Photo + {this.props.translate('profilePage.editPhoto')} @@ -290,58 +298,74 @@ class ProfilePage extends Component { )} - Tell us about yourself, we would love to get to know you! + {this.props.translate('profilePage.tellUsAboutYourself')} - First Name + + {this.props.translate('profilePage.firstName')} + this.setState({firstName})} - placeholder="John" + placeholder={this.props.translate('profilePage.john')} placeholderTextColor={themeColors.placeholderText} /> - Last Name + + {this.props.translate('profilePage.lastName')} + this.setState({lastName})} - placeholder="Doe" + placeholder={this.props.translate('profilePage.doe')} placeholderTextColor={themeColors.placeholderText} /> - Preferred Pronouns + + {this.props.translate('profilePage.preferredPronouns')} + this.setState({pronouns, selfSelectedPronouns: ''})} items={this.pronounDropdownValues} placeholder={{ value: '', - label: 'Select your pronouns', + label: this.props.translate('profilePage.selectYourPronouns'), }} value={this.state.pronouns} icon={() => } /> - {this.state.pronouns === CONST.PRONOUNS.SELF_SELECT && ( + {this.state.pronouns === this.props.translate(CONST.PRONOUNS.SELF_SELECT) && ( this.setState({selfSelectedPronouns})} - placeholder="Self-select your pronoun" + placeholder={this.props.translate('profilePage.selfSelectYourPronoun')} placeholderTextColor={themeColors.placeholderText} /> )} - - + + - Timezone + + {this.props.translate('profilePage.timezone')} + this.setState({selectedTimezone})} items={timezones} @@ -352,7 +376,7 @@ class ProfilePage extends Component { /> @@ -370,7 +394,7 @@ class ProfilePage extends Component { ]} > - Save + {this.props.translate('common.save')} @@ -383,11 +407,14 @@ ProfilePage.propTypes = propTypes; ProfilePage.defaultProps = defaultProps; ProfilePage.displayName = 'ProfilePage'; -export default withOnyx({ - myPersonalDetails: { - key: ONYXKEYS.MY_PERSONAL_DETAILS, - }, - user: { - key: ONYXKEYS.USER, - }, -})(ProfilePage); +export default compose( + withLocalize, + withOnyx({ + myPersonalDetails: { + key: ONYXKEYS.MY_PERSONAL_DETAILS, + }, + user: { + key: ONYXKEYS.USER, + }, + }), +)(ProfilePage); diff --git a/src/pages/signin/ChangeExpensifyLoginLink.js b/src/pages/signin/ChangeExpensifyLoginLink.js old mode 100644 new mode 100755 index a973d81da0f5..598dd35e10e4 --- a/src/pages/signin/ChangeExpensifyLoginLink.js +++ b/src/pages/signin/ChangeExpensifyLoginLink.js @@ -7,6 +7,8 @@ import styles from '../../styles/styles'; import {restartSignin} from '../../libs/actions/Session'; import themeColors from '../../styles/themes/default'; import ONYXKEYS from '../../ONYXKEYS'; +import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; +import compose from '../../libs/compose'; const propTypes = { // The credentials of the logged in person @@ -14,9 +16,11 @@ const propTypes = { // The email the user logged in with login: PropTypes.string, }).isRequired, + + ...withLocalizePropTypes, }; -const ChangeExpensifyLoginLink = ({credentials}) => ( +const ChangeExpensifyLoginLink = ({credentials, translate}) => ( ( underlayColor={themeColors.componentBG} > - Not  + {translate('common.not')} +   {Str.removeSMSDomain(credentials.login)} ? @@ -35,6 +40,9 @@ const ChangeExpensifyLoginLink = ({credentials}) => ( ChangeExpensifyLoginLink.propTypes = propTypes; ChangeExpensifyLoginLink.displayName = 'ChangeExpensifyLoginLink'; -export default withOnyx({ - credentials: {key: ONYXKEYS.CREDENTIALS}, -})(ChangeExpensifyLoginLink); +export default compose( + withLocalize, + withOnyx({ + credentials: {key: ONYXKEYS.CREDENTIALS}, + }), +)(ChangeExpensifyLoginLink); diff --git a/src/pages/signin/LoginForm.js b/src/pages/signin/LoginForm.js old mode 100644 new mode 100755 index d37e22431193..95511fad652b --- a/src/pages/signin/LoginForm.js +++ b/src/pages/signin/LoginForm.js @@ -15,6 +15,7 @@ import ONYXKEYS from '../../ONYXKEYS'; import withWindowDimensions, {windowDimensionsPropTypes} from '../../components/withWindowDimensions'; import compose from '../../libs/compose'; import canFocusInputOnScreenFocus from '../../libs/canFocusInputOnScreenFocus'; +import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; const propTypes = { /* Onyx Props */ @@ -32,6 +33,8 @@ const propTypes = { }), ...windowDimensionsPropTypes, + + ...withLocalizePropTypes, }; const defaultProps = { @@ -55,7 +58,7 @@ class LoginForm extends React.Component { */ validateAndSubmitForm() { if (!this.state.login.trim()) { - this.setState({formError: 'Please enter an email or phone number'}); + this.setState({formError: this.props.translate('loginForm.pleaseEnterEmailOrPhoneNumber')}); return; } @@ -71,7 +74,7 @@ class LoginForm extends React.Component { return ( <> - Enter your phone or email: + {this.props.translate('loginForm.enterYourPhoneOrEmail')} this.setState({login: text})} onSubmitEditing={this.validateAndSubmitForm} autoCapitalize="none" - placeholder="Phone or Email" + placeholder={this.props.translate('loginForm.phoneOrEmail')} placeholderTextColor={themeColors.placeholderText} autoFocus={canFocusInputOnScreenFocus()} /> @@ -122,4 +125,5 @@ export default compose( account: {key: ONYXKEYS.ACCOUNT}, }), withWindowDimensions, + withLocalize, )(LoginForm); diff --git a/src/pages/signin/PasswordForm.js b/src/pages/signin/PasswordForm.js old mode 100644 new mode 100755 index 173d294586ff..9faddcf9a492 --- a/src/pages/signin/PasswordForm.js +++ b/src/pages/signin/PasswordForm.js @@ -11,6 +11,8 @@ import {signIn, resetPassword} from '../../libs/actions/Session'; import ONYXKEYS from '../../ONYXKEYS'; import CONST from '../../CONST'; import ChangeExpensifyLoginLink from './ChangeExpensifyLoginLink'; +import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; +import compose from '../../libs/compose'; const propTypes = { /* Onyx Props */ @@ -26,6 +28,8 @@ const propTypes = { // Whether or not a sign on form is loading (being submitted) loading: PropTypes.bool, }), + + ...withLocalizePropTypes, }; const defaultProps = { @@ -52,7 +56,7 @@ class PasswordForm extends React.Component { if (!this.state.password.trim() || (this.props.account.requiresTwoFactorAuth && !this.state.twoFactorAuthCode.trim()) ) { - this.setState({formError: 'Please fill out all fields'}); + this.setState({formError: this.props.translate('passwordForm.pleaseFillOutAllFields')}); return; } @@ -67,7 +71,7 @@ class PasswordForm extends React.Component { return ( <> - Password + {this.props.translate('common.password')} - Forgot? + {this.props.translate('passwordForm.forgot')} {this.props.account.requiresTwoFactorAuth && ( - Two Factor Code + {this.props.translate('passwordForm.twoFactorCode')} this.setState({twoFactorAuthCode: text})} onSubmitEditing={this.validateAndSubmitForm} @@ -104,7 +108,7 @@ class PasswordForm extends React.Component { )} @@ -123,6 +127,9 @@ class PasswordForm extends React.Component { PasswordForm.propTypes = propTypes; PasswordForm.defaultProps = defaultProps; -export default withOnyx({ - account: {key: ONYXKEYS.ACCOUNT}, -})(PasswordForm); +export default compose( + withLocalize, + withOnyx({ + account: {key: ONYXKEYS.ACCOUNT}, + }), +)(PasswordForm); diff --git a/src/pages/signin/ResendValidationForm.js b/src/pages/signin/ResendValidationForm.js old mode 100644 new mode 100755 index 504b5601d817..0f5fffcd47ef --- a/src/pages/signin/ResendValidationForm.js +++ b/src/pages/signin/ResendValidationForm.js @@ -8,6 +8,8 @@ import ButtonWithLoader from '../../components/ButtonWithLoader'; import {resendValidationLink, resetPassword} from '../../libs/actions/Session'; import ONYXKEYS from '../../ONYXKEYS'; import ChangeExpensifyLoginLink from './ChangeExpensifyLoginLink'; +import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; +import compose from '../../libs/compose'; const propTypes = { /* Onyx Props */ @@ -20,6 +22,8 @@ const propTypes = { // Weather or not the account is validated validated: PropTypes.bool, }), + + ...withLocalizePropTypes, }; const defaultProps = { @@ -48,15 +52,15 @@ class ResendValidationForm extends React.Component { */ validateAndSubmitForm() { this.setState({ - formSuccess: 'Link has been re-sent', + formSuccess: this.props.translate('resendValidationForm.linkHasBeenResent'), }); if (!this.props.account.validated) { resendValidationLink(); - console.debug('Account is unvalidated: Sending validation link.'); + console.debug(this.props.translate('resendValidationForm.accountUnvalidated')); } else { resetPassword(); - console.debug('Account forgot password: Sending reset password link.'); + console.debug(this.props.translate('resendValidationForm.accountForgotPassword')); } this.successMessageTimer = setTimeout(() => { @@ -69,12 +73,12 @@ class ResendValidationForm extends React.Component { <> - We've sent you a magic sign in link – just click on it to log in! + {this.props.translate('resendValidationForm.weSentYouMagicSignInLink')} @@ -94,6 +98,9 @@ class ResendValidationForm extends React.Component { ResendValidationForm.propTypes = propTypes; ResendValidationForm.defaultProps = defaultProps; -export default withOnyx({ - account: {key: ONYXKEYS.ACCOUNT}, -})(ResendValidationForm); +export default compose( + withLocalize, + withOnyx({ + account: {key: ONYXKEYS.ACCOUNT}, + }), +)(ResendValidationForm); diff --git a/src/pages/signin/SignInPageLayout/SignInPageLayoutNarrow.js b/src/pages/signin/SignInPageLayout/SignInPageLayoutNarrow.js old mode 100644 new mode 100755 index 8e3f533a20b0..88062b5ef15f --- a/src/pages/signin/SignInPageLayout/SignInPageLayoutNarrow.js +++ b/src/pages/signin/SignInPageLayout/SignInPageLayoutNarrow.js @@ -6,12 +6,13 @@ import { import PropTypes from 'prop-types'; import styles from '../../../styles/styles'; import variables from '../../../styles/variables'; -import ExpensifyCashLogo from '../../../components/ExpensifyCashLogo'; +import ExpensifyCashLogo from '../../../../assets/images/expensify-cash.svg'; import welcomeScreenshot from '../../../../assets/images/welcome-screenshot.png'; import TermsAndLicenses from '../TermsAndLicenses'; import WelcomeText from '../../../components/WelcomeText'; import openURLInNewTab from '../../../libs/openURLInNewTab/index.native'; import CONST from '../../../CONST'; +import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; const propTypes = { @@ -21,6 +22,8 @@ const propTypes = { // Whether we should show the welcome elements shouldShowWelcomeText: PropTypes.bool, shouldShowWelcomeScreenshot: PropTypes.bool, + + ...withLocalizePropTypes, }; const defaultProps = { @@ -38,7 +41,7 @@ const SignInPageLayoutNarrow = props => ( - Expensify.cash + {props.translate('signInPage.expensifyDotCash')} @@ -59,21 +62,22 @@ const SignInPageLayoutNarrow = props => ( {props.shouldShowWelcomeText && } - Expensify.cash is open source. View + {`${props.translate('signInPage.expensifyIsOpenSource')}. ${ + props.translate('common.view')}`} {' '} openURLInNewTab(CONST.GITHUB_URL)} > - the code + {props.translate('signInPage.theCode')} - . View + {`. ${props.translate('common.view')}`} {' '} openURLInNewTab(CONST.UPWORK_URL)} > - open jobs + {props.translate('signInPage.openJobs')} . @@ -90,4 +94,4 @@ SignInPageLayoutNarrow.defaultProps = defaultProps; SignInPageLayoutNarrow.displayName = 'SignInPageLayoutNarrow'; -export default SignInPageLayoutNarrow; +export default withLocalize(SignInPageLayoutNarrow); diff --git a/src/pages/signin/SignInPageLayout/SignInPageLayoutWide.js b/src/pages/signin/SignInPageLayout/SignInPageLayoutWide.js old mode 100644 new mode 100755 index 99289a7f7e3a..37fcc000aee2 --- a/src/pages/signin/SignInPageLayout/SignInPageLayoutWide.js +++ b/src/pages/signin/SignInPageLayout/SignInPageLayoutWide.js @@ -4,13 +4,14 @@ import { } from 'react-native'; import PropTypes from 'prop-types'; import styles from '../../../styles/styles'; -import ExpensifyCashLogo from '../../../components/ExpensifyCashLogo'; +import ExpensifyCashLogo from '../../../../assets/images/expensify-cash.svg'; import welcomeScreenshot from '../../../../assets/images/welcome-screenshot-wide.png'; import variables from '../../../styles/variables'; import TermsAndLicenses from '../TermsAndLicenses'; import WelcomeText from '../../../components/WelcomeText'; import openURLInNewTab from '../../../libs/openURLInNewTab'; import CONST from '../../../CONST'; +import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; const propTypes = { // The children to show inside the layout @@ -19,6 +20,8 @@ const propTypes = { // Whether we should show the welcome text // (the welcome screenshot always displays on wide views) shouldShowWelcomeText: PropTypes.bool, + + ...withLocalizePropTypes, }; const defaultProps = { @@ -44,7 +47,7 @@ const SignInPageLayoutWide = props => ( - Expensify.cash + {props.translate('signInPage.expensifyDotCash')} @@ -58,21 +61,22 @@ const SignInPageLayoutWide = props => ( )} - Expensify.cash is open source. View + {`${props.translate('signInPage.expensifyIsOpenSource')}. ${ + props.translate('common.view')}`} {' '} openURLInNewTab(CONST.GITHUB_URL)} > - the code + {props.translate('signInPage.theCode')} - . View + {`. ${props.translate('common.view')}`} {' '} openURLInNewTab(CONST.UPWORK_URL)} > - open jobs + {props.translate('signInPage.openJobs')} . @@ -88,4 +92,4 @@ SignInPageLayoutWide.defaultProps = defaultProps; SignInPageLayoutWide.displayName = 'SignInPageLayoutWide'; -export default SignInPageLayoutWide; +export default withLocalize(SignInPageLayoutWide); diff --git a/src/pages/signin/TermsAndLicenses/TermsOnly.js b/src/pages/signin/TermsAndLicenses/TermsOnly.js old mode 100644 new mode 100755 index c1d4ca2ce59e..a208104f2d37 --- a/src/pages/signin/TermsAndLicenses/TermsOnly.js +++ b/src/pages/signin/TermsAndLicenses/TermsOnly.js @@ -3,30 +3,33 @@ import {Text, View} from 'react-native'; import styles from '../../../styles/styles'; import CONST from '../../../CONST'; import openURLInNewTab from '../../../libs/openURLInNewTab'; +import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; -const TermsOnly = () => ( +const TermsOnly = ({translate}) => ( - By logging in, you agree to the + {translate('termsOfUse.phrase1')} {' '} openURLInNewTab(CONST.TERMS_URL)} > - terms of service + {translate('termsOfUse.phrase2')} {' '} - and + {translate('termsOfUse.phrase3')} {' '} openURLInNewTab(CONST.PRIVACY_URL)} > - privacy policy + {translate('termsOfUse.phrase4')} . ); -export default TermsOnly; +TermsOnly.propTypes = {...withLocalizePropTypes}; + +export default withLocalize(TermsOnly); diff --git a/src/pages/signin/TermsAndLicenses/TermsWithLicenses.js b/src/pages/signin/TermsAndLicenses/TermsWithLicenses.js old mode 100644 new mode 100755 index 94af1d8c4307..1b70e1827a25 --- a/src/pages/signin/TermsAndLicenses/TermsWithLicenses.js +++ b/src/pages/signin/TermsAndLicenses/TermsWithLicenses.js @@ -3,38 +3,41 @@ import {Text, View} from 'react-native'; import styles from '../../../styles/styles'; import CONST from '../../../CONST'; import openURLInNewTab from '../../../libs/openURLInNewTab'; +import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize'; -const TermsWithLicenses = () => ( +const TermsWithLicenses = ({translate}) => ( - By logging in, you agree to the + {translate('termsOfUse.phrase1')} {' '} openURLInNewTab(CONST.TERMS_URL)} > - terms of service + {translate('termsOfUse.phrase2')} {' '} - and + {translate('termsOfUse.phrase3')} {' '} openURLInNewTab(CONST.PRIVACY_URL)} > - privacy policy + {translate('termsOfUse.phrase4')} - . Money transmission is provided by Expensify Payments LLC (NMLS ID:2017010) pursuant to its + {translate('termsOfUse.phrase5')} {' '} openURLInNewTab(CONST.LICENSES_URL)} > - licenses + {translate('termsOfUse.phrase6')} . ); -export default TermsWithLicenses; +TermsWithLicenses.propTypes = {...withLocalizePropTypes}; + +export default withLocalize(TermsWithLicenses); From f5c7b369a9cca25eb1038e8824441e0057c1e979 Mon Sep 17 00:00:00 2001 From: Yevhenii Voloshchak Date: Mon, 10 May 2021 16:29:43 +0300 Subject: [PATCH 2/3] Address the issues with localization --- src/CONST.js | 13 -- .../AttachmentPicker/index.native.js | 166 ++++++++---------- src/components/IOUConfirmationList.js | 2 +- src/components/withLocalize.js | 26 ++- src/languages/en.js | 14 +- src/libs/OptionsListUtils.js | 4 +- .../home/report/ReportTypingIndicator.js | 8 +- src/pages/iou/IOUModal.js | 2 +- src/pages/settings/Profile/ProfilePage.js | 10 +- 9 files changed, 118 insertions(+), 127 deletions(-) diff --git a/src/CONST.js b/src/CONST.js index 7839d77831f2..46325236a537 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -65,11 +65,6 @@ const CONST = { SWITCH_REPORT: 'switch_report', COLD: 'cold', }, - MESSAGES: { - // eslint-disable-next-line max-len - NO_PHONE_NUMBER: 'noPhoneNumberMessage', - MAXIMUM_PARTICIPANTS_REACHED: 'maxParticipantsReachedMessage', - }, PRIORITY_MODE: { GSD: 'gsd', DEFAULT: 'default', @@ -84,14 +79,6 @@ const CONST = { }, DEFAULT_TIME_ZONE: {automatic: true, selected: 'America/Los_Angeles'}, DEFAULT_ACCOUNT_DATA: {error: '', success: '', loading: false}, - PRONOUNS: { - HE_HIM_HIS: 'pronouns.heHimHis', - SHE_HER_HERS: 'pronouns.sheHerHers', - THEY_THEM_THEIRS: 'pronouns.theyThemTheirs', - ZE_HIR_HIRS: 'pronouns.zeHirHirs', - SELF_SELECT: 'pronouns.selfSelect', - CALL_ME_BY_MY_NAME: 'pronouns.callMeByMyName', - }, APP_STATE: { ACTIVE: 'active', BACKGROUND: 'background', diff --git a/src/components/AttachmentPicker/index.native.js b/src/components/AttachmentPicker/index.native.js index 37467cc5f7fe..e7a2da327afd 100755 --- a/src/components/AttachmentPicker/index.native.js +++ b/src/components/AttachmentPicker/index.native.js @@ -5,7 +5,6 @@ import React, {Component} from 'react'; import {Alert, Linking, View} from 'react-native'; import RNImagePicker from 'react-native-image-picker'; import RNDocumentPicker from 'react-native-document-picker'; -import Onyx from 'react-native-onyx'; import basePropTypes from './AttachmentPickerPropTypes'; import styles from '../../styles/styles'; import Popover from '../Popover'; @@ -14,14 +13,6 @@ import {Camera, Gallery, Paperclip} from '../Icon/Expensicons'; import withWindowDimensions, {windowDimensionsPropTypes} from '../withWindowDimensions'; import withLocalize, {withLocalizePropTypes} from '../withLocalize'; import compose from '../../libs/compose'; -import {translate} from '../../libs/translate'; - -let preferredLocale; - -Onyx.connect({ - key: preferredLocale, - callback: val => preferredLocale = val || 'en', -}); const propTypes = { ...basePropTypes, @@ -46,83 +37,6 @@ const documentPickerOptions = { type: [RNDocumentPicker.types.allFiles], }; -/** - * Inform the users when they need to grant camera access and guide them to settings - */ -function showPermissionsAlert() { - Alert.alert( - translate(preferredLocale, 'attachmentPicker.cameraPermissionRequired'), - translate(preferredLocale, 'attachmentPicker.expensifyDoesntHaveAccessToCamera'), - [ - { - text: translate(preferredLocale, 'common.cancel'), - style: 'cancel', - }, - { - text: translate(preferredLocale, 'common.settings'), - onPress: () => Linking.openSettings(), - }, - ], - {cancelable: false}, - ); -} - -/** - * A generic handling when we don't know the exact reason for an error - * - */ -function showGeneralAlert() { - Alert.alert( - translate(preferredLocale, 'attachmentPicker.attachmentError'), - translate(preferredLocale, 'attachmentPicker.errorWhileSelectingAttachment'), - ); -} - -/** - * Launch the DocumentPicker. Results are in the same format as ImagePicker - * - * @returns {Promise} - */ -function showDocumentPicker() { - return RNDocumentPicker.pick(documentPickerOptions).catch((error) => { - if (!RNDocumentPicker.isCancel(error)) { - showGeneralAlert(error.message); - throw error; - } - }); -} - -/** - * Common image picker handling - * - * @param {function} imagePickerFunc - RNImagePicker.launchCamera or RNImagePicker.launchImageLibrary - * @returns {Promise} - */ -function showImagePicker(imagePickerFunc) { - return new Promise((resolve, reject) => { - imagePickerFunc(imagePickerOptions, (response) => { - if (response.error) { - switch (response.error) { - case 'Camera permissions not granted': - case 'Permissions weren\'t granted': - showPermissionsAlert(); - break; - default: - showGeneralAlert(response.error); - break; - } - const errorDescription = translate( - preferredLocale, - 'attachmentPicker.errorDuringAttachmentSelection', - ); - reject(new Error(`${errorDescription}: ${response.error}`)); - } - - resolve(response); - }); - }); -} - /** * The data returned from `show` is different on web and mobile, so use this function to ensure the data we * send to the xhr will be handled properly. @@ -158,17 +72,17 @@ class AttachmentPicker extends Component { { icon: Camera, text: this.props.translate('attachmentPicker.takePhoto'), - pickAttachment: () => showImagePicker(RNImagePicker.launchCamera), + pickAttachment: () => this.showImagePicker(RNImagePicker.launchCamera), }, { icon: Gallery, text: this.props.translate('attachmentPicker.chooseFromGallery'), - pickAttachment: () => showImagePicker(RNImagePicker.launchImageLibrary), + pickAttachment: () => this.showImagePicker(RNImagePicker.launchImageLibrary), }, { icon: Paperclip, text: this.props.translate('attachmentPicker.chooseDocument'), - pickAttachment: showDocumentPicker, + pickAttachment: this.showDocumentPicker, }, ]; @@ -189,6 +103,80 @@ class AttachmentPicker extends Component { } } + /** + * Inform the users when they need to grant camera access and guide them to settings + */ + showPermissionsAlert() { + Alert.alert( + this.props.translate('attachmentPicker.cameraPermissionRequired'), + this.props.translate('attachmentPicker.expensifyDoesntHaveAccessToCamera'), + [ + { + text: this.props.translate('common.cancel'), + style: 'cancel', + }, + { + text: this.props.translate('common.settings'), + onPress: () => Linking.openSettings(), + }, + ], + {cancelable: false}, + ); + } + + /** + * Common image picker handling + * + * @param {function} imagePickerFunc - RNImagePicker.launchCamera or RNImagePicker.launchImageLibrary + * @returns {Promise} + */ + showImagePicker(imagePickerFunc) { + return new Promise((resolve, reject) => { + imagePickerFunc(imagePickerOptions, (response) => { + if (response.error) { + switch (response.error) { + case 'Camera permissions not granted': + case 'Permissions weren\'t granted': + this.showPermissionsAlert(); + break; + default: + this.showGeneralAlert(response.error); + break; + } + const errorDescription = this.props.translate('attachmentPicker.errorDuringAttachmentSelection'); + reject(new Error(`${errorDescription}: ${response.error}`)); + } + + resolve(response); + }); + }); + } + + /** + * A generic handling when we don't know the exact reason for an error + * + */ + showGeneralAlert() { + Alert.alert( + this.props.translate('attachmentPicker.attachmentError'), + this.props.translate('attachmentPicker.errorWhileSelectingAttachment'), + ); + } + + /** + * Launch the DocumentPicker. Results are in the same format as ImagePicker + * + * @returns {Promise} + */ + showDocumentPicker() { + return RNDocumentPicker.pick(documentPickerOptions).catch((error) => { + if (!RNDocumentPicker.isCancel(error)) { + this.showGeneralAlert(error.message); + throw error; + } + }); + } + /** * Triggers the `onPicked` callback with the selected attachment */ diff --git a/src/components/IOUConfirmationList.js b/src/components/IOUConfirmationList.js index feac515e3f33..eb5ee4f76631 100755 --- a/src/components/IOUConfirmationList.js +++ b/src/components/IOUConfirmationList.js @@ -262,7 +262,7 @@ class IOUConfirmationList extends Component { isLoading={this.props.iou.loading} text={this.props.hasMultipleParticipants ? this.props.translate('common.split') - : `${this.props.translate('common.request')} $${this.props.iouAmount}`} + : this.props.translate('common.request', {amount: this.props.iouAmount})} onClick={() => this.props.onConfirm(this.getSplits())} /> diff --git a/src/components/withLocalize.js b/src/components/withLocalize.js index 9fe283af1e7a..9b10a7bc4d51 100755 --- a/src/components/withLocalize.js +++ b/src/components/withLocalize.js @@ -10,17 +10,27 @@ import {toLocalPhone, fromLocalPhone} from '../libs/LocalePhoneNumber'; import numberFormat from '../libs/numberFormat'; const withLocalizePropTypes = { - // Translations functions using current User's preferred locale + // Returns translated string for given locale and phrase translate: PropTypes.func.isRequired, + + // Formats number formatted according to locale and options numberFormat: PropTypes.func.isRequired, + + // Converts a timestamp into a localized string representation that's relative to current moment in time timestampToRelative: PropTypes.func.isRequired, + + // Formats a timestamp to local date and time string timestampToDateTime: PropTypes.func.isRequired, + + // Returns a locally converted phone number without the country code toLocalPhone: PropTypes.func.isRequired, + + // Returns an internationally converted phone number with the country code fromLocalPhone: PropTypes.func.isRequired, }; function withLocalizeHOC(WrappedComponent) { - const withLocalize = (props) => { + const WithLocalize = (props) => { const translations = { translate: (phrase, variables) => translate(props.preferredLocale, phrase, variables), numberFormat: (number, options) => numberFormat(props.preferredLocale, number, options), @@ -46,11 +56,17 @@ function withLocalizeHOC(WrappedComponent) { /> ); }; - withLocalize.displayName = `withLocalize(${getComponentDisplayName(WrappedComponent)})`; - withLocalize.defaultProps = { + WithLocalize.displayName = `WithLocalize(${getComponentDisplayName(WrappedComponent)})`; + WithLocalize.propTypes = { + preferredLocale: PropTypes.string, + }; + WithLocalize.defaultProps = { preferredLocale: 'en', }; - return withLocalize; + return React.forwardRef((props, ref) => ( + // eslint-disable-next-line react/jsx-props-no-spreading + + )); } export default compose( withOnyx({ diff --git a/src/languages/en.js b/src/languages/en.js index 2cea557474a2..ea7cc11c58a9 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -9,7 +9,7 @@ export default { to: 'To', optional: 'Optional', split: 'Split', - request: 'Request', + request: ({amount}) => `Request ${amount}`, new: 'NEW', search: 'Search', next: 'Next', @@ -26,6 +26,7 @@ export default { continue: 'Continue', phoneNumber: 'Phone Number', email: 'Email', + and: 'and', }, attachmentPicker: { cameraPermissionRequired: 'Camera Permission Required', @@ -82,9 +83,8 @@ export default { beFirstPersonToComment: 'Be the first person to comment', }, reportTypingIndicator: { - isTyping: ' is typing...', - and: ' and ', - areTyping: ' are typing...', + isTyping: 'is typing...', + areTyping: 'are typing...', multipleUsers: 'Multiple users', }, sidebarScreen: { @@ -225,7 +225,9 @@ export default { selfSelect: 'Self-select', callMeByMyName: 'Call me by my name', }, - noPhoneNumberMessage: 'Please enter a phone number including the country code e.g +447814266907', - maxParticipantsReachedMessage: 'You\'ve reached the maximum number of participants for a group chat.', cameraPermissionsNotGranted: 'Camera permissions not granted', + messages: { + noPhoneNumber: 'Please enter a phone number including the country code e.g +447814266907', + maxParticipantsReached: 'You\'ve reached the maximum number of participants for a group chat.', + }, }; diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index af61f8d386e4..07ee43178d95 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -458,12 +458,12 @@ function getSidebarOptions(reports, personalDetails, draftComments, activeReport */ function getHeaderMessage(hasSelectableOptions, hasUserToInvite, searchValue, maxParticipantsReached = false) { if (maxParticipantsReached) { - return translate(preferredLocale, CONST.MESSAGES.MAXIMUM_PARTICIPANTS_REACHED); + return translate(preferredLocale, 'messages.maxParticipantsReached'); } if (!hasSelectableOptions && !hasUserToInvite) { if (/^\d+$/.test(searchValue)) { - return translate(preferredLocale, CONST.MESSAGES.NO_PHONE_NUMBER); + return translate(preferredLocale, 'messages.noPhoneNumber'); } return searchValue; diff --git a/src/pages/home/report/ReportTypingIndicator.js b/src/pages/home/report/ReportTypingIndicator.js index f7bfaa9ab3db..c0a70b824064 100755 --- a/src/pages/home/report/ReportTypingIndicator.js +++ b/src/pages/home/report/ReportTypingIndicator.js @@ -59,7 +59,7 @@ class ReportTypingIndicator extends React.Component { ]} > {getDisplayName(this.state.usersTyping[0])} - {this.props.translate('reportTypingIndicator.isTyping')} + {` ${this.props.translate('reportTypingIndicator.isTyping')}`} ); @@ -72,9 +72,9 @@ class ReportTypingIndicator extends React.Component { ]} > {getDisplayName(this.state.usersTyping[0])} - {this.props.translate('reportTypingIndicator.and')} + {` ${this.props.translate('common.and')} `} {getDisplayName(this.state.usersTyping[1])} - {this.props.translate('reportTypingIndicator.areTyping')} + {` ${this.props.translate('reportTypingIndicator.areTyping')}`} ); @@ -89,7 +89,7 @@ class ReportTypingIndicator extends React.Component { {this.props.translate('reportTypingIndicator.multipleUsers')} - {this.props.translate('reportTypingIndicator.areTyping')} + {` ${this.props.translate('reportTypingIndicator.areTyping')}`} ); diff --git a/src/pages/iou/IOUModal.js b/src/pages/iou/IOUModal.js index b18633b2ba0b..da6baf1aa760 100755 --- a/src/pages/iou/IOUModal.js +++ b/src/pages/iou/IOUModal.js @@ -128,7 +128,7 @@ class IOUModal extends Component { if (currentStepIndex === 1 || currentStepIndex === 2) { return `${this.props.hasMultipleParticipants ? this.props.translate('common.split') - : this.props.translate('common.request')} $${this.state.amount}`; + : this.props.translate('common.request', {amount: this.state.amount})}`; } if (currentStepIndex === 0) { return this.props.translate(this.props.hasMultipleParticipants ? 'iou.splitBill' : 'iou.requestMoney'); diff --git a/src/pages/settings/Profile/ProfilePage.js b/src/pages/settings/Profile/ProfilePage.js index 04b26374a747..294ca6c081cd 100755 --- a/src/pages/settings/Profile/ProfilePage.js +++ b/src/pages/settings/Profile/ProfilePage.js @@ -102,16 +102,14 @@ class ProfilePage extends Component { pronouns, timezone = {}, } = props.myPersonalDetails; - const pronounsKeyList = Object.values(CONST.PRONOUNS); - const pronounsList = []; - pronounsKeyList.map(pronounKey => pronounsList.push(this.props.translate(pronounKey))); + const pronounsList = Object.values(this.props.translate('pronouns')); let currentUserPronouns = pronouns; let initialSelfSelectedPronouns = ''; // This handles populating the self-selected pronouns in the form if (pronouns && !pronounsList.includes(pronouns)) { - currentUserPronouns = this.props.translate(CONST.PRONOUNS.SELF_SELECT); + currentUserPronouns = this.props.translate('pronouns.selfSelect'); initialSelfSelectedPronouns = pronouns; } @@ -201,7 +199,7 @@ class ProfilePage extends Component { setPersonalDetails({ firstName, lastName, - pronouns: pronouns === this.props.translate(CONST.PRONOUNS.SELF_SELECT) + pronouns: pronouns === this.props.translate('pronouns.selfSelect') ? selfSelectedPronouns : pronouns, timezone: { @@ -342,7 +340,7 @@ class ProfilePage extends Component { icon={() => } /> - {this.state.pronouns === this.props.translate(CONST.PRONOUNS.SELF_SELECT) && ( + {this.state.pronouns === this.props.translate('pronouns.selfSelect') && ( Date: Mon, 10 May 2021 21:48:57 +0300 Subject: [PATCH 3/3] Move request translation key from common to iou --- src/components/IOUConfirmationList.js | 2 +- src/languages/en.js | 2 +- src/pages/iou/IOUModal.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/IOUConfirmationList.js b/src/components/IOUConfirmationList.js index eb5ee4f76631..48ce0b74be40 100755 --- a/src/components/IOUConfirmationList.js +++ b/src/components/IOUConfirmationList.js @@ -262,7 +262,7 @@ class IOUConfirmationList extends Component { isLoading={this.props.iou.loading} text={this.props.hasMultipleParticipants ? this.props.translate('common.split') - : this.props.translate('common.request', {amount: this.props.iouAmount})} + : this.props.translate('iou.request', {amount: this.props.iouAmount})} onClick={() => this.props.onConfirm(this.getSplits())} /> diff --git a/src/languages/en.js b/src/languages/en.js index ea7cc11c58a9..b6ea0f7f1801 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -9,7 +9,6 @@ export default { to: 'To', optional: 'Optional', split: 'Split', - request: ({amount}) => `Request ${amount}`, new: 'NEW', search: 'Search', next: 'Next', @@ -99,6 +98,7 @@ export default { confirm: 'Confirm', splitBill: 'Split Bill', requestMoney: 'Request Money', + request: ({amount}) => `Request ${amount}`, }, loginField: { addYourPhoneToSettleViaVenmo: 'Add your phone number to settle up via Venmo.', diff --git a/src/pages/iou/IOUModal.js b/src/pages/iou/IOUModal.js index da6baf1aa760..05fd51b55c11 100755 --- a/src/pages/iou/IOUModal.js +++ b/src/pages/iou/IOUModal.js @@ -128,7 +128,7 @@ class IOUModal extends Component { if (currentStepIndex === 1 || currentStepIndex === 2) { return `${this.props.hasMultipleParticipants ? this.props.translate('common.split') - : this.props.translate('common.request', {amount: this.state.amount})}`; + : this.props.translate('iou.request', {amount: this.state.amount})}`; } if (currentStepIndex === 0) { return this.props.translate(this.props.hasMultipleParticipants ? 'iou.splitBill' : 'iou.requestMoney');