From 241f5e71ac4533e1e497c8e715eb5c838711cc9c Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 20 Oct 2023 17:35:17 +0700 Subject: [PATCH 01/37] fix: missing translation for server errors --- src/components/Form.js | 5 +---- src/components/Form/FormWrapper.js | 5 +---- src/components/OptionsSelector/BaseOptionsSelector.js | 2 +- src/components/PDFView/PDFPasswordForm.js | 6 +++--- src/libs/ErrorUtils.ts | 6 ++++-- src/pages/ReimbursementAccount/AddressForm.js | 8 ++++---- src/pages/ReimbursementAccount/IdentityForm.js | 8 ++++---- .../Contacts/ValidateCodeForm/BaseValidateCodeForm.js | 2 +- .../TwoFactorAuthForm/BaseTwoFactorAuthForm.js | 2 +- src/pages/settings/Wallet/ActivatePhysicalCardPage.js | 2 +- src/pages/signin/LoginForm/BaseLoginForm.js | 3 +-- src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js | 6 +++--- 12 files changed, 25 insertions(+), 30 deletions(-) diff --git a/src/components/Form.js b/src/components/Form.js index b4e639dcf964..e4babf275af9 100644 --- a/src/components/Form.js +++ b/src/components/Form.js @@ -207,10 +207,7 @@ function Form(props) { // eslint-disable-next-line react-hooks/exhaustive-deps -- we just want to revalidate the form on update if the preferred locale changed on another device so that errors get translated }, [props.preferredLocale]); - const errorMessage = useMemo(() => { - const latestErrorMessage = ErrorUtils.getLatestErrorMessage(props.formState); - return typeof latestErrorMessage === 'string' ? latestErrorMessage : ''; - }, [props.formState]); + const errorMessage = useMemo(() => ErrorUtils.getLatestErrorMessage(props.formState), [props.formState]); /** * @param {String} inputID - The inputID of the input being touched diff --git a/src/components/Form/FormWrapper.js b/src/components/Form/FormWrapper.js index 3d9fd37d6f22..b2754cd9c0cf 100644 --- a/src/components/Form/FormWrapper.js +++ b/src/components/Form/FormWrapper.js @@ -81,10 +81,7 @@ function FormWrapper(props) { const {onSubmit, children, formState, errors, inputRefs, submitButtonText, footerContent, isSubmitButtonVisible, style, enabledWhenOffline, isSubmitActionDangerous, formID} = props; const formRef = useRef(null); const formContentRef = useRef(null); - const errorMessage = useMemo(() => { - const latestErrorMessage = ErrorUtils.getLatestErrorMessage(formState); - return typeof latestErrorMessage === 'string' ? latestErrorMessage : ''; - }, [formState]); + const errorMessage = useMemo(() => ErrorUtils.getLatestErrorMessage(formState), [formState]); const scrollViewContent = useCallback( (safeAreaPaddingBottomStyle) => ( diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index 4ffddd700359..7bf16fdef4f5 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -172,7 +172,7 @@ class BaseOptionsSelector extends Component { updateSearchValue(value) { this.setState({ - errorMessage: value.length > this.props.maxLength ? this.props.translate('common.error.characterLimitExceedCounter', {length: value.length, limit: this.props.maxLength}) : '', + errorMessage: value.length > this.props.maxLength ? ['common.error.characterLimitExceedCounter', {length: value.length, limit: this.props.maxLength}] : '', }); this.props.onChangeText(value); diff --git a/src/components/PDFView/PDFPasswordForm.js b/src/components/PDFView/PDFPasswordForm.js index 58a4e64a28a5..e91eacbec71f 100644 --- a/src/components/PDFView/PDFPasswordForm.js +++ b/src/components/PDFView/PDFPasswordForm.js @@ -54,13 +54,13 @@ function PDFPasswordForm({isFocused, isPasswordInvalid, shouldShowLoadingIndicat const errorText = useMemo(() => { if (isPasswordInvalid) { - return translate('attachmentView.passwordIncorrect'); + return 'attachmentView.passwordIncorrect'; } if (!_.isEmpty(validationErrorText)) { - return translate(validationErrorText); + return validationErrorText; } return ''; - }, [isPasswordInvalid, translate, validationErrorText]); + }, [isPasswordInvalid, validationErrorText]); useEffect(() => { if (!isFocused) { diff --git a/src/libs/ErrorUtils.ts b/src/libs/ErrorUtils.ts index bf4fc0d810a4..ce14d2eda58d 100644 --- a/src/libs/ErrorUtils.ts +++ b/src/libs/ErrorUtils.ts @@ -46,7 +46,9 @@ type OnyxDataWithErrors = { errors?: Errors; }; -function getLatestErrorMessage(onyxData: TOnyxData): string { +type TranslationData = [string, Record]; + +function getLatestErrorMessage(onyxData: TOnyxData): TranslationData | string { const errors = onyxData.errors ?? {}; if (Object.keys(errors).length === 0) { @@ -55,7 +57,7 @@ function getLatestErrorMessage(onyxData: T const key = Object.keys(errors).sort().reverse()[0]; - return errors[key]; + return [errors[key], {isTranslated: true}]; } type OnyxDataWithErrorFields = { diff --git a/src/pages/ReimbursementAccount/AddressForm.js b/src/pages/ReimbursementAccount/AddressForm.js index 5ddea09c6f4e..5089fc8167ce 100644 --- a/src/pages/ReimbursementAccount/AddressForm.js +++ b/src/pages/ReimbursementAccount/AddressForm.js @@ -103,7 +103,7 @@ function AddressForm(props) { value={props.values.street} defaultValue={props.defaultValues.street} onInputChange={props.onFieldChange} - errorText={props.errors.street ? props.translate('bankAccount.error.addressStreet') : ''} + errorText={props.errors.street ? 'bankAccount.error.addressStreet' : ''} hint={props.translate('common.noPO')} renamedInputKeys={props.inputKeys} maxInputLength={CONST.FORM_CHARACTER_LIMIT} @@ -118,7 +118,7 @@ function AddressForm(props) { value={props.values.city} defaultValue={props.defaultValues.city} onChangeText={(value) => props.onFieldChange({city: value})} - errorText={props.errors.city ? props.translate('bankAccount.error.addressCity') : ''} + errorText={props.errors.city ? 'bankAccount.error.addressCity' : ''} containerStyles={[styles.mt4]} /> @@ -129,7 +129,7 @@ function AddressForm(props) { value={props.values.state} defaultValue={props.defaultValues.state || ''} onInputChange={(value) => props.onFieldChange({state: value})} - errorText={props.errors.state ? props.translate('bankAccount.error.addressState') : ''} + errorText={props.errors.state ? 'bankAccount.error.addressState' : ''} /> props.onFieldChange({zipCode: value})} - errorText={props.errors.zipCode ? props.translate('bankAccount.error.zipCode') : ''} + errorText={props.errors.zipCode ? 'bankAccount.error.zipCode' : ''} maxLength={CONST.BANK_ACCOUNT.MAX_LENGTH.ZIP_CODE} hint={props.translate('common.zipCodeExampleFormat', {zipSampleFormat: CONST.COUNTRY_ZIP_REGEX_DATA.US.samples})} containerStyles={[styles.mt2]} diff --git a/src/pages/ReimbursementAccount/IdentityForm.js b/src/pages/ReimbursementAccount/IdentityForm.js index 20c6e10ec64d..b86779b109f9 100644 --- a/src/pages/ReimbursementAccount/IdentityForm.js +++ b/src/pages/ReimbursementAccount/IdentityForm.js @@ -131,7 +131,7 @@ const defaultProps = { function IdentityForm(props) { // dob field has multiple validations/errors, we are handling it temporarily like this. - const dobErrorText = (props.errors.dob ? props.translate('bankAccount.error.dob') : '') || (props.errors.dobAge ? props.translate('bankAccount.error.age') : ''); + const dobErrorText = (props.errors.dob ? 'bankAccount.error.dob' : '') || (props.errors.dobAge ? 'bankAccount.error.age' : ''); const identityFormInputKeys = ['firstName', 'lastName', 'dob', 'ssnLast4']; const minDate = subYears(new Date(), CONST.DATE_BIRTH.MAX_AGE); @@ -150,7 +150,7 @@ function IdentityForm(props) { value={props.values.firstName} defaultValue={props.defaultValues.firstName} onChangeText={(value) => props.onFieldChange({firstName: value})} - errorText={props.errors.firstName ? props.translate('bankAccount.error.firstName') : ''} + errorText={props.errors.firstName ? 'bankAccount.error.firstName' : ''} /> @@ -163,7 +163,7 @@ function IdentityForm(props) { value={props.values.lastName} defaultValue={props.defaultValues.lastName} onChangeText={(value) => props.onFieldChange({lastName: value})} - errorText={props.errors.lastName ? props.translate('bankAccount.error.lastName') : ''} + errorText={props.errors.lastName ? 'bankAccount.error.lastName' : ''} /> @@ -189,7 +189,7 @@ function IdentityForm(props) { keyboardType={CONST.KEYBOARD_TYPE.NUMBER_PAD} defaultValue={props.defaultValues.ssnLast4} onChangeText={(value) => props.onFieldChange({ssnLast4: value})} - errorText={props.errors.ssnLast4 ? props.translate('bankAccount.error.ssnLast4') : ''} + errorText={props.errors.ssnLast4 ? 'bankAccount.error.ssnLast4' : ''} maxLength={CONST.BANK_ACCOUNT.MAX_LENGTH.SSN} /> diff --git a/src/pages/settings/Wallet/ActivatePhysicalCardPage.js b/src/pages/settings/Wallet/ActivatePhysicalCardPage.js index 0175f2ceac1f..dc139c03000f 100644 --- a/src/pages/settings/Wallet/ActivatePhysicalCardPage.js +++ b/src/pages/settings/Wallet/ActivatePhysicalCardPage.js @@ -117,7 +117,7 @@ function ActivatePhysicalCardPage({ activateCardCodeInputRef.current.blur(); if (lastFourDigits.replace(CONST.MAGIC_CODE_EMPTY_CHAR, '').length !== LAST_FOUR_DIGITS_LENGTH) { - setFormError(translate('activateCardPage.error.thatDidntMatch')); + setFormError('activateCardPage.error.thatDidntMatch'); return; } diff --git a/src/pages/signin/LoginForm/BaseLoginForm.js b/src/pages/signin/LoginForm/BaseLoginForm.js index 3576f92be31f..38e428451f2c 100644 --- a/src/pages/signin/LoginForm/BaseLoginForm.js +++ b/src/pages/signin/LoginForm/BaseLoginForm.js @@ -197,7 +197,6 @@ function LoginForm(props) { }, })); - const formErrorText = useMemo(() => (formError ? translate(formError) : ''), [formError, translate]); const serverErrorText = useMemo(() => ErrorUtils.getLatestErrorMessage(props.account), [props.account]); const hasError = !_.isEmpty(serverErrorText); @@ -222,7 +221,7 @@ function LoginForm(props) { autoCapitalize="none" autoCorrect={false} keyboardType={CONST.KEYBOARD_TYPE.EMAIL_ADDRESS} - errorText={formErrorText} + errorText={formError} hasError={hasError} maxLength={CONST.LOGIN_CHARACTER_LIMIT} /> diff --git a/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js b/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js index dc100fffe4f1..43b54454ba0f 100755 --- a/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js +++ b/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js @@ -312,7 +312,7 @@ function BaseValidateCodeForm(props) { onChangeText={(text) => onTextInput(text, 'recoveryCode')} maxLength={CONST.RECOVERY_CODE_LENGTH} label={props.translate('recoveryCodeForm.recoveryCode')} - errorText={formError.recoveryCode ? props.translate(formError.recoveryCode) : ''} + errorText={formError.recoveryCode ? formError.recoveryCode : ''} hasError={hasError} onSubmitEditing={validateAndSubmitForm} autoFocus @@ -328,7 +328,7 @@ function BaseValidateCodeForm(props) { onChangeText={(text) => onTextInput(text, 'twoFactorAuthCode')} onFulfill={validateAndSubmitForm} maxLength={CONST.TFA_CODE_LENGTH} - errorText={formError.twoFactorAuthCode ? props.translate(formError.twoFactorAuthCode) : ''} + errorText={formError.twoFactorAuthCode ? formError.twoFactorAuthCode : ''} hasError={hasError} autoFocus /> @@ -357,7 +357,7 @@ function BaseValidateCodeForm(props) { value={validateCode} onChangeText={(text) => onTextInput(text, 'validateCode')} onFulfill={validateAndSubmitForm} - errorText={formError.validateCode ? props.translate(formError.validateCode) : ''} + errorText={formError.validateCode ? formError.validateCode : ''} hasError={hasError} autoFocus /> From 096ed12e2b91ae5bdc14f0db171d6c7aeefbd9a4 Mon Sep 17 00:00:00 2001 From: tienifr Date: Sat, 21 Oct 2023 22:24:13 +0700 Subject: [PATCH 02/37] remove redundant dependency --- src/pages/settings/Wallet/ActivatePhysicalCardPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Wallet/ActivatePhysicalCardPage.js b/src/pages/settings/Wallet/ActivatePhysicalCardPage.js index dc139c03000f..71b147e3c28c 100644 --- a/src/pages/settings/Wallet/ActivatePhysicalCardPage.js +++ b/src/pages/settings/Wallet/ActivatePhysicalCardPage.js @@ -122,7 +122,7 @@ function ActivatePhysicalCardPage({ } CardSettings.activatePhysicalExpensifyCard(Number(lastFourDigits), cardID); - }, [lastFourDigits, cardID, translate]); + }, [lastFourDigits, cardID]); if (_.isEmpty(physicalCard)) { return ; From 0a9a467ac459e36a3e1ad9f059379ee70c4eb778 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 25 Oct 2023 15:37:34 +0700 Subject: [PATCH 03/37] do not translate already translated text in DotIndicatorMessage --- src/components/AvatarWithImagePicker.js | 2 +- src/components/DistanceRequest/index.js | 4 +-- src/components/OfflineWithFeedback.js | 3 +- src/libs/ErrorUtils.ts | 36 +++++++++++++++---- src/pages/SearchPage.js | 4 ++- .../settings/Wallet/ExpensifyCardPage.js | 2 +- src/pages/signin/LoginForm/BaseLoginForm.js | 3 +- src/pages/signin/UnlinkLoginForm.js | 6 ++-- 8 files changed, 42 insertions(+), 18 deletions(-) diff --git a/src/components/AvatarWithImagePicker.js b/src/components/AvatarWithImagePicker.js index 3dd23d9051eb..40ee7aa04208 100644 --- a/src/components/AvatarWithImagePicker.js +++ b/src/components/AvatarWithImagePicker.js @@ -365,7 +365,7 @@ class AvatarWithImagePicker extends React.Component { {this.state.validationError && ( )} diff --git a/src/components/DistanceRequest/index.js b/src/components/DistanceRequest/index.js index bd35678273ec..3d9cdb31195e 100644 --- a/src/components/DistanceRequest/index.js +++ b/src/components/DistanceRequest/index.js @@ -152,11 +152,11 @@ function DistanceRequest({transactionID, report, transaction, route, isEditingRe // Initially, both waypoints will be null, and if we give fallback value as empty string that will result in true condition, that's why different default values. if (_.keys(waypoints).length === 2 && lodashGet(waypoints, 'waypoint0.address', 'address1') === lodashGet(waypoints, 'waypoint1.address', 'address2')) { - return {0: translate('iou.error.duplicateWaypointsErrorMessage')}; + return {0: 'iou.error.duplicateWaypointsErrorMessage'}; } if (_.size(validatedWaypoints) < 2) { - return {0: translate('iou.error.emptyWaypointsErrorMessage')}; + return {0: 'iou.error.emptyWaypointsErrorMessage'}; } }; diff --git a/src/components/OfflineWithFeedback.js b/src/components/OfflineWithFeedback.js index 643e7b2f4a2f..a73a41f21810 100644 --- a/src/components/OfflineWithFeedback.js +++ b/src/components/OfflineWithFeedback.js @@ -7,6 +7,7 @@ import stylePropTypes from '../styles/stylePropTypes'; import styles from '../styles/styles'; import Tooltip from './Tooltip'; import Icon from './Icon'; +import * as ErrorUtils from '../libs/ErrorUtils'; import * as Expensicons from './Icon/Expensicons'; import * as StyleUtils from '../styles/StyleUtils'; import DotIndicatorMessage from './DotIndicatorMessage'; @@ -103,7 +104,7 @@ function OfflineWithFeedback(props) { const hasErrors = !_.isEmpty(props.errors); // Some errors have a null message. This is used to apply opacity only and to avoid showing redundant messages. - const errorMessages = _.omit(props.errors, (e) => e === null); + const errorMessages = ErrorUtils.getErrorMessagesWithTranslationData(_.omit(props.errors, (e) => e === null)); const hasErrorMessages = !_.isEmpty(errorMessages); const isOfflinePendingAction = isOffline && props.pendingAction; const isUpdateOrDeleteError = hasErrors && (props.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || props.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE); diff --git a/src/libs/ErrorUtils.ts b/src/libs/ErrorUtils.ts index ce14d2eda58d..891616669eb3 100644 --- a/src/libs/ErrorUtils.ts +++ b/src/libs/ErrorUtils.ts @@ -1,3 +1,5 @@ +import mapKeys from 'lodash/mapKeys'; +import isEmpty from 'lodash/isEmpty'; import CONST from '../CONST'; import DateUtils from './DateUtils'; import * as Localize from './Localize'; @@ -46,9 +48,9 @@ type OnyxDataWithErrors = { errors?: Errors; }; -type TranslationData = [string, Record]; +type TranslationData = [string, Record] | string; -function getLatestErrorMessage(onyxData: TOnyxData): TranslationData | string { +function getLatestErrorMessage(onyxData: TOnyxData): TranslationData { const errors = onyxData.errors ?? {}; if (Object.keys(errors).length === 0) { @@ -64,7 +66,7 @@ type OnyxDataWithErrorFields = { errorFields?: ErrorFields; }; -function getLatestErrorField(onyxData: TOnyxData, fieldName: string): Record { +function getLatestErrorField(onyxData: TOnyxData, fieldName: string): Record { const errorsForField = onyxData.errorFields?.[fieldName] ?? {}; if (Object.keys(errorsForField).length === 0) { @@ -73,10 +75,10 @@ function getLatestErrorField(onyxData const key = Object.keys(errorsForField).sort().reverse()[0]; - return {[key]: errorsForField[key]}; + return {[key]: [errorsForField[key], {isTranslated: true}]}; } -function getEarliestErrorField(onyxData: TOnyxData, fieldName: string): Record { +function getEarliestErrorField(onyxData: TOnyxData, fieldName: string): Record { const errorsForField = onyxData.errorFields?.[fieldName] ?? {}; if (Object.keys(errorsForField).length === 0) { @@ -85,10 +87,30 @@ function getEarliestErrorField(onyxDa const key = Object.keys(errorsForField).sort()[0]; - return {[key]: errorsForField[key]}; + return {[key]: [errorsForField[key], {isTranslated: true}]}; } type ErrorsList = Record; +type ErrorsListWithTranslationData = Record; + +/** + * Method used to attach already translated message with isTranslated: true property + * @param errors - An object containing current errors in the form + * @returns Errors in the form of {timestamp: [message, {isTranslated: true}]} + */ +function getErrorMessagesWithTranslationData(errors: TranslationData | ErrorsList): ErrorsListWithTranslationData { + if (isEmpty(errors)) { + return {}; + } + + if (typeof errors === 'string' || Array.isArray(errors)) { + const [message, variables] = Array.isArray(errors) ? errors : [errors]; + // eslint-disable-next-line @typescript-eslint/naming-convention + return {0: [message, {...variables, isTranslated: true}]}; + } + + return mapKeys(errors, (message) => [message, {isTranslated: true}]); +} /** * Method used to generate error message for given inputID @@ -113,4 +135,4 @@ function addErrorMessage(errors: ErrorsList, inputID?: string, message?: string) } } -export {getAuthenticateErrorMessage, getMicroSecondOnyxError, getLatestErrorMessage, getLatestErrorField, getEarliestErrorField, addErrorMessage}; +export {getAuthenticateErrorMessage, getMicroSecondOnyxError, getLatestErrorMessage, getLatestErrorField, getEarliestErrorField, getErrorMessagesWithTranslationData, addErrorMessage}; diff --git a/src/pages/SearchPage.js b/src/pages/SearchPage.js index c671e7b1a096..f0a4eb58916c 100755 --- a/src/pages/SearchPage.js +++ b/src/pages/SearchPage.js @@ -202,7 +202,9 @@ class SearchPage extends Component { shouldShowOptions={didScreenTransitionEnd && isOptionsDataReady} textInputLabel={this.props.translate('optionsSelector.nameEmailOrPhoneNumber')} textInputAlert={ - this.props.network.isOffline ? `${this.props.translate('common.youAppearToBeOffline')} ${this.props.translate('search.resultsAreLimited')}` : '' + this.props.network.isOffline + ? [`${this.props.translate('common.youAppearToBeOffline')} ${this.props.translate('search.resultsAreLimited')}`, {isTranslated: true}] + : '' } onLayout={this.searchRendered} safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle} diff --git a/src/pages/settings/Wallet/ExpensifyCardPage.js b/src/pages/settings/Wallet/ExpensifyCardPage.js index e198d449d57d..d6096a3e3aac 100644 --- a/src/pages/settings/Wallet/ExpensifyCardPage.js +++ b/src/pages/settings/Wallet/ExpensifyCardPage.js @@ -90,7 +90,7 @@ function ExpensifyCardPage({ ) : null} diff --git a/src/pages/signin/LoginForm/BaseLoginForm.js b/src/pages/signin/LoginForm/BaseLoginForm.js index 2c595a39c201..0196d5f91c02 100644 --- a/src/pages/signin/LoginForm/BaseLoginForm.js +++ b/src/pages/signin/LoginForm/BaseLoginForm.js @@ -239,11 +239,10 @@ function LoginForm(props) { {!_.isEmpty(props.account.success) && {props.account.success}} {!_.isEmpty(props.closeAccount.success || props.account.message) && ( - // DotIndicatorMessage mostly expects onyxData errors, so we need to mock an object so that the messages looks similar to prop.account.errors )} { diff --git a/src/pages/signin/UnlinkLoginForm.js b/src/pages/signin/UnlinkLoginForm.js index 6807ba74c6f9..5b26d254bee5 100644 --- a/src/pages/signin/UnlinkLoginForm.js +++ b/src/pages/signin/UnlinkLoginForm.js @@ -7,6 +7,7 @@ import Str from 'expensify-common/lib/str'; import styles from '../../styles/styles'; import Button from '../../components/Button'; import Text from '../../components/Text'; +import * as ErrorUtils from '../../libs/ErrorUtils'; import * as Session from '../../libs/actions/Session'; import ONYXKEYS from '../../ONYXKEYS'; import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; @@ -63,18 +64,17 @@ function UnlinkLoginForm(props) { {props.translate('unlinkLoginForm.noLongerHaveAccess', {primaryLogin})} {!_.isEmpty(props.account.message) && ( - // DotIndicatorMessage mostly expects onyxData errors so we need to mock an object so that the messages looks similar to prop.account.errors )} {!_.isEmpty(props.account.errors) && ( )} From b04abe52ac9f1f58ce85ea4398f51647c69b8699 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 25 Oct 2023 16:00:13 +0700 Subject: [PATCH 04/37] fix missing translation for FormAlertWrapper --- src/components/FormAlertWithSubmitButton.js | 2 +- src/components/FormAlertWrapper.js | 2 +- src/pages/EnablePayments/OnfidoPrivacy.js | 6 ++++-- src/pages/settings/Wallet/ReportCardLostPage.js | 4 ++-- src/pages/settings/Wallet/TransferBalancePage.js | 3 ++- src/pages/tasks/NewTaskPage.js | 6 +++--- src/pages/workspace/WorkspaceInvitePage.js | 3 ++- 7 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/components/FormAlertWithSubmitButton.js b/src/components/FormAlertWithSubmitButton.js index 33d188719d11..f078b99ec47c 100644 --- a/src/components/FormAlertWithSubmitButton.js +++ b/src/components/FormAlertWithSubmitButton.js @@ -27,7 +27,7 @@ const propTypes = { isMessageHtml: PropTypes.bool, /** Error message to display above button */ - message: PropTypes.string, + message: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.object]))]), /** Callback fired when the "fix the errors" link is pressed */ onFixTheErrorsLinkPressed: PropTypes.func, diff --git a/src/components/FormAlertWrapper.js b/src/components/FormAlertWrapper.js index 67e031ce6ab6..757bc1cca2fb 100644 --- a/src/components/FormAlertWrapper.js +++ b/src/components/FormAlertWrapper.js @@ -27,7 +27,7 @@ const propTypes = { isMessageHtml: PropTypes.bool, /** Error message to display above button */ - message: PropTypes.string, + message: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.object]))]), /** Props to detect online status */ network: networkPropTypes.isRequired, diff --git a/src/pages/EnablePayments/OnfidoPrivacy.js b/src/pages/EnablePayments/OnfidoPrivacy.js index 85ceb03b01d5..5575525890f2 100644 --- a/src/pages/EnablePayments/OnfidoPrivacy.js +++ b/src/pages/EnablePayments/OnfidoPrivacy.js @@ -44,9 +44,11 @@ function OnfidoPrivacy({walletOnfidoData, translate, form}) { BankAccounts.openOnfidoFlow(); }; - let onfidoError = ErrorUtils.getLatestErrorMessage(walletOnfidoData) || ''; + const onfidoError = ErrorUtils.getLatestErrorMessage(walletOnfidoData) || ''; const onfidoFixableErrors = lodashGet(walletOnfidoData, 'fixableErrors', []); - onfidoError += !_.isEmpty(onfidoFixableErrors) ? `\n${onfidoFixableErrors.join('\n')}` : ''; + if (_.isArray(onfidoError)) { + onfidoError[0] += !_.isEmpty(onfidoFixableErrors) ? `\n${onfidoFixableErrors.join('\n')}` : ''; + } return ( diff --git a/src/pages/settings/Wallet/ReportCardLostPage.js b/src/pages/settings/Wallet/ReportCardLostPage.js index 29a588916326..696a162ac6e5 100644 --- a/src/pages/settings/Wallet/ReportCardLostPage.js +++ b/src/pages/settings/Wallet/ReportCardLostPage.js @@ -182,7 +182,7 @@ function ReportCardLostPage({ @@ -200,7 +200,7 @@ function ReportCardLostPage({ diff --git a/src/pages/settings/Wallet/TransferBalancePage.js b/src/pages/settings/Wallet/TransferBalancePage.js index ae54dab569f7..34c97f8e5277 100644 --- a/src/pages/settings/Wallet/TransferBalancePage.js +++ b/src/pages/settings/Wallet/TransferBalancePage.js @@ -3,6 +3,7 @@ import React, {useEffect} from 'react'; import {View, ScrollView} from 'react-native'; import PropTypes from 'prop-types'; import {withOnyx} from 'react-native-onyx'; +import * as ErrorUtils from '../../../libs/ErrorUtils'; import ONYXKEYS from '../../../ONYXKEYS'; import HeaderWithBackButton from '../../../components/HeaderWithBackButton'; import ScreenWrapper from '../../../components/ScreenWrapper'; @@ -165,7 +166,7 @@ function TransferBalancePage(props) { const transferAmount = props.userWallet.currentBalance - calculatedFee; const isTransferable = transferAmount > 0; const isButtonDisabled = !isTransferable || !selectedAccount; - const errorMessage = !_.isEmpty(props.walletTransfer.errors) ? _.chain(props.walletTransfer.errors).values().first().value() : ''; + const errorMessage = !_.isEmpty(props.walletTransfer.errors) ? ErrorUtils.getErrorMessagesWithTranslationData(_.chain(props.walletTransfer.errors).values().first().value()) : ''; const shouldShowTransferView = PaymentUtils.hasExpensifyPaymentMethod(paymentCardList, props.bankAccountList) && diff --git a/src/pages/tasks/NewTaskPage.js b/src/pages/tasks/NewTaskPage.js index f0d2d506c9d8..2e1f42d90b6e 100644 --- a/src/pages/tasks/NewTaskPage.js +++ b/src/pages/tasks/NewTaskPage.js @@ -114,17 +114,17 @@ function NewTaskPage(props) { // the response function onSubmit() { if (!props.task.title && !props.task.shareDestination) { - setErrorMessage(props.translate('newTaskPage.confirmError')); + setErrorMessage('newTaskPage.confirmError'); return; } if (!props.task.title) { - setErrorMessage(props.translate('newTaskPage.pleaseEnterTaskName')); + setErrorMessage('newTaskPage.pleaseEnterTaskName'); return; } if (!props.task.shareDestination) { - setErrorMessage(props.translate('newTaskPage.pleaseEnterTaskDestination')); + setErrorMessage('newTaskPage.pleaseEnterTaskDestination'); return; } diff --git a/src/pages/workspace/WorkspaceInvitePage.js b/src/pages/workspace/WorkspaceInvitePage.js index a21173dd7d98..33fd3786c490 100644 --- a/src/pages/workspace/WorkspaceInvitePage.js +++ b/src/pages/workspace/WorkspaceInvitePage.js @@ -4,6 +4,7 @@ import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import lodashGet from 'lodash/get'; +import * as ErrorUtils from '../../libs/ErrorUtils'; import ScreenWrapper from '../../components/ScreenWrapper'; import HeaderWithBackButton from '../../components/HeaderWithBackButton'; import Navigation from '../../libs/Navigation/Navigation'; @@ -281,7 +282,7 @@ function WorkspaceInvitePage(props) { isAlertVisible={shouldShowAlertPrompt} buttonText={translate('common.next')} onSubmit={inviteUser} - message={props.policy.alertMessage} + message={ErrorUtils.getErrorMessagesWithTranslationData(props.policy.alertMessage)} containerStyles={[styles.flexReset, styles.flexGrow0, styles.flexShrink0, styles.flexBasisAuto, styles.mb5]} enabledWhenOffline disablePressOnEnter From 95188d6cb14118265db6a76cb6cbe52eadab039e Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 25 Oct 2023 16:44:38 +0700 Subject: [PATCH 05/37] fix missing translation for FormHelpMessage --- src/components/MoneyRequestConfirmationList.js | 2 +- src/pages/NewChatPage.js | 2 +- src/pages/ReimbursementAccount/AddressForm.js | 4 ++-- src/pages/ReimbursementAccount/CompanyStep.js | 2 +- src/pages/iou/steps/MoneyRequestAmountForm.js | 2 +- src/pages/settings/Profile/PersonalDetails/AddressPage.js | 2 +- src/pages/settings/Security/TwoFactorAuth/Steps/CodesStep.js | 2 +- src/pages/settings/Wallet/AddDebitCardPage.js | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index 0b266351a60c..c5f04d52f5f3 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -538,7 +538,7 @@ function MoneyRequestConfirmationList(props) { )} {button} diff --git a/src/pages/NewChatPage.js b/src/pages/NewChatPage.js index 381564b82600..e45635f82f1d 100755 --- a/src/pages/NewChatPage.js +++ b/src/pages/NewChatPage.js @@ -251,7 +251,7 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, i shouldShowOptions={isOptionsDataReady} shouldShowConfirmButton confirmButtonText={selectedOptions.length > 1 ? translate('newChatPage.createGroup') : translate('newChatPage.createChat')} - textInputAlert={isOffline ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : ''} + textInputAlert={isOffline ? [`${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}`, {isTranslated: true}] : ''} onConfirmSelection={createGroup} textInputLabel={translate('optionsSelector.nameEmailOrPhoneNumber')} safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle} diff --git a/src/pages/ReimbursementAccount/AddressForm.js b/src/pages/ReimbursementAccount/AddressForm.js index 5089fc8167ce..4eb5009256b1 100644 --- a/src/pages/ReimbursementAccount/AddressForm.js +++ b/src/pages/ReimbursementAccount/AddressForm.js @@ -104,7 +104,7 @@ function AddressForm(props) { defaultValue={props.defaultValues.street} onInputChange={props.onFieldChange} errorText={props.errors.street ? 'bankAccount.error.addressStreet' : ''} - hint={props.translate('common.noPO')} + hint="common.noPO" renamedInputKeys={props.inputKeys} maxInputLength={CONST.FORM_CHARACTER_LIMIT} /> @@ -144,7 +144,7 @@ function AddressForm(props) { onChangeText={(value) => props.onFieldChange({zipCode: value})} errorText={props.errors.zipCode ? 'bankAccount.error.zipCode' : ''} maxLength={CONST.BANK_ACCOUNT.MAX_LENGTH.ZIP_CODE} - hint={props.translate('common.zipCodeExampleFormat', {zipSampleFormat: CONST.COUNTRY_ZIP_REGEX_DATA.US.samples})} + hint={['common.zipCodeExampleFormat', {zipSampleFormat: CONST.COUNTRY_ZIP_REGEX_DATA.US.samples}]} containerStyles={[styles.mt2]} /> diff --git a/src/pages/ReimbursementAccount/CompanyStep.js b/src/pages/ReimbursementAccount/CompanyStep.js index 0ca9b1b7ea92..926eb3f651ac 100644 --- a/src/pages/ReimbursementAccount/CompanyStep.js +++ b/src/pages/ReimbursementAccount/CompanyStep.js @@ -207,7 +207,7 @@ function CompanyStep({reimbursementAccount, reimbursementAccountDraft, getDefaul containerStyles={[styles.mt4]} defaultValue={getDefaultStateForField('website', defaultWebsite)} shouldSaveDraft - hint={translate('common.websiteExample')} + hint="common.websiteExample" keyboardType={CONST.KEYBOARD_TYPE.URL} /> )} )} diff --git a/src/pages/settings/Wallet/AddDebitCardPage.js b/src/pages/settings/Wallet/AddDebitCardPage.js index e75c3b2c517e..1f62e6f68ac9 100644 --- a/src/pages/settings/Wallet/AddDebitCardPage.js +++ b/src/pages/settings/Wallet/AddDebitCardPage.js @@ -178,7 +178,7 @@ function DebitCardPage(props) { accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} keyboardType={CONST.KEYBOARD_TYPE.NUMBER_PAD} maxLength={CONST.BANK_ACCOUNT.MAX_LENGTH.ZIP_CODE} - hint={translate('common.zipCodeExampleFormat', {zipSampleFormat: CONST.COUNTRY_ZIP_REGEX_DATA.US.samples})} + hint={['common.zipCodeExampleFormat', {zipSampleFormat: CONST.COUNTRY_ZIP_REGEX_DATA.US.samples}]} containerStyles={[styles.mt4]} /> From e2d2551625ca6798da2de0ea59b8a33f911c90d3 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 25 Oct 2023 17:03:38 +0700 Subject: [PATCH 06/37] fix lint --- src/components/MoneyRequestConfirmationList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index c5f04d52f5f3..91cba3f2d9bb 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -544,7 +544,7 @@ function MoneyRequestConfirmationList(props) { {button} ); - }, [confirm, props.bankAccountRoute, props.iouCurrencyCode, props.iouType, props.isReadOnly, props.policyID, selectedParticipants, splitOrRequestOptions, translate, formError]); + }, [confirm, props.bankAccountRoute, props.iouCurrencyCode, props.iouType, props.isReadOnly, props.policyID, selectedParticipants, splitOrRequestOptions, formError]); const {image: receiptImage, thumbnail: receiptThumbnail} = props.receiptPath && props.receiptFilename ? ReceiptUtils.getThumbnailAndImageURIs(transaction, props.receiptPath, props.receiptFilename) : {}; From 6803203c19db5f5abb2368c954dcfbc936763f18 Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 21 Nov 2023 20:59:36 +0700 Subject: [PATCH 07/37] fix translation for AddressForm --- src/components/AddressForm.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/AddressForm.js b/src/components/AddressForm.js index 19ab35f036c1..4684e11dc0bb 100644 --- a/src/components/AddressForm.js +++ b/src/components/AddressForm.js @@ -65,7 +65,7 @@ const defaultProps = { function AddressForm({city, country, formID, onAddressChanged, onSubmit, shouldSaveDraft, state, street1, street2, submitButtonText, zip}) { const {translate} = useLocalize(); const zipSampleFormat = lodashGet(CONST.COUNTRY_ZIP_REGEX_DATA, [country, 'samples'], ''); - const zipFormat = translate('common.zipCodeExampleFormat', {zipSampleFormat}); + const zipFormat = ['common.zipCodeExampleFormat', {zipSampleFormat}]; const isUSAForm = country === CONST.COUNTRY.US; /** From b8dad84ebd7d172300b1b8460a1c0d0a71ef0360 Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 1 Dec 2023 19:43:35 +0700 Subject: [PATCH 08/37] create prop type for translatable text --- src/components/AddressSearch/index.js | 3 ++- src/components/FormAlertWithSubmitButton.js | 3 ++- src/components/FormAlertWrapper.js | 3 ++- src/components/FormHelpMessage.js | 3 ++- src/components/RoomNameInput/roomNameInputPropTypes.js | 3 ++- .../TextInput/BaseTextInput/baseTextInputPropTypes.js | 3 ++- src/components/translatableTextPropTypes.js | 8 ++++++++ 7 files changed, 20 insertions(+), 6 deletions(-) create mode 100644 src/components/translatableTextPropTypes.js diff --git a/src/components/AddressSearch/index.js b/src/components/AddressSearch/index.js index 9f16766a22ae..4143b6e8f699 100644 --- a/src/components/AddressSearch/index.js +++ b/src/components/AddressSearch/index.js @@ -9,6 +9,7 @@ import LocationErrorMessage from '@components/LocationErrorMessage'; import networkPropTypes from '@components/networkPropTypes'; import {withNetwork} from '@components/OnyxProvider'; import TextInput from '@components/TextInput'; +import translatableTextPropTypes from '@components/translatableTextPropTypes'; import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import * as ApiUtils from '@libs/ApiUtils'; import compose from '@libs/compose'; @@ -38,7 +39,7 @@ const propTypes = { onBlur: PropTypes.func, /** Error text to display */ - errorText: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.object]))]), + errorText: translatableTextPropTypes, /** Hint text to display */ hint: PropTypes.string, diff --git a/src/components/FormAlertWithSubmitButton.js b/src/components/FormAlertWithSubmitButton.js index cab71c6a935c..6a7d770d8779 100644 --- a/src/components/FormAlertWithSubmitButton.js +++ b/src/components/FormAlertWithSubmitButton.js @@ -5,6 +5,7 @@ import _ from 'underscore'; import useThemeStyles from '@styles/useThemeStyles'; import Button from './Button'; import FormAlertWrapper from './FormAlertWrapper'; +import translatableTextPropTypes from './translatableTextPropTypes'; const propTypes = { /** Text for the button */ @@ -27,7 +28,7 @@ const propTypes = { isMessageHtml: PropTypes.bool, /** Error message to display above button */ - message: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.object]))]), + message: translatableTextPropTypes, /** Callback fired when the "fix the errors" link is pressed */ onFixTheErrorsLinkPressed: PropTypes.func, diff --git a/src/components/FormAlertWrapper.js b/src/components/FormAlertWrapper.js index d3f49728bfec..f26161efa847 100644 --- a/src/components/FormAlertWrapper.js +++ b/src/components/FormAlertWrapper.js @@ -10,6 +10,7 @@ import {withNetwork} from './OnyxProvider'; import RenderHTML from './RenderHTML'; import Text from './Text'; import TextLink from './TextLink'; +import translatableTextPropTypes from './translatableTextPropTypes'; import withLocalize, {withLocalizePropTypes} from './withLocalize'; const propTypes = { @@ -27,7 +28,7 @@ const propTypes = { isMessageHtml: PropTypes.bool, /** Error message to display above button */ - message: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.object]))]), + message: translatableTextPropTypes, /** Props to detect online status */ network: networkPropTypes.isRequired, diff --git a/src/components/FormHelpMessage.js b/src/components/FormHelpMessage.js index bec02c3d51f0..6644bbc0ccee 100644 --- a/src/components/FormHelpMessage.js +++ b/src/components/FormHelpMessage.js @@ -9,10 +9,11 @@ import useThemeStyles from '@styles/useThemeStyles'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; import Text from './Text'; +import translatableTextPropTypes from './translatableTextPropTypes'; const propTypes = { /** Error or hint text. Ignored when children is not empty */ - message: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.object]))]), + message: translatableTextPropTypes, /** Children to render next to dot indicator */ children: PropTypes.node, diff --git a/src/components/RoomNameInput/roomNameInputPropTypes.js b/src/components/RoomNameInput/roomNameInputPropTypes.js index f457e4e2a494..339c15d0c1e1 100644 --- a/src/components/RoomNameInput/roomNameInputPropTypes.js +++ b/src/components/RoomNameInput/roomNameInputPropTypes.js @@ -1,5 +1,6 @@ import PropTypes from 'prop-types'; import refPropTypes from '@components/refPropTypes'; +import translatableTextPropTypes from '@components/translatableTextPropTypes'; const propTypes = { /** Callback to execute when the text input is modified correctly */ @@ -12,7 +13,7 @@ const propTypes = { disabled: PropTypes.bool, /** Error text to show */ - errorText: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.object]))]), + errorText: translatableTextPropTypes, /** A ref forwarded to the TextInput */ forwardedRef: refPropTypes, diff --git a/src/components/TextInput/BaseTextInput/baseTextInputPropTypes.js b/src/components/TextInput/BaseTextInput/baseTextInputPropTypes.js index 5387d1ff81d1..48e5556738bd 100644 --- a/src/components/TextInput/BaseTextInput/baseTextInputPropTypes.js +++ b/src/components/TextInput/BaseTextInput/baseTextInputPropTypes.js @@ -1,4 +1,5 @@ import PropTypes from 'prop-types'; +import translatableTextPropTypes from '@components/translatableTextPropTypes'; const propTypes = { /** Input label */ @@ -17,7 +18,7 @@ const propTypes = { placeholder: PropTypes.string, /** Error text to display */ - errorText: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.object]))]), + errorText: translatableTextPropTypes, /** Icon to display in right side of text input */ icon: PropTypes.func, diff --git a/src/components/translatableTextPropTypes.js b/src/components/translatableTextPropTypes.js new file mode 100644 index 000000000000..8da65b0ba202 --- /dev/null +++ b/src/components/translatableTextPropTypes.js @@ -0,0 +1,8 @@ +import PropTypes from 'prop-types'; + +/** + * Traslatable text with phrase key and/or variables + * + * E.g. ['common.error.characterLimitExceedCounter', {length: 5, limit: 20}] + */ +export default PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.object]))]); From bcd1c1950341f43171243e51c7a88d0177a9ac84 Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 1 Dec 2023 19:44:59 +0700 Subject: [PATCH 09/37] use translatable text type for hint --- .../TextInput/BaseTextInput/baseTextInputPropTypes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/TextInput/BaseTextInput/baseTextInputPropTypes.js b/src/components/TextInput/BaseTextInput/baseTextInputPropTypes.js index 48e5556738bd..14f1db5ea045 100644 --- a/src/components/TextInput/BaseTextInput/baseTextInputPropTypes.js +++ b/src/components/TextInput/BaseTextInput/baseTextInputPropTypes.js @@ -65,7 +65,7 @@ const propTypes = { maxLength: PropTypes.number, /** Hint text to display below the TextInput */ - hint: PropTypes.string, + hint: translatableTextPropTypes, /** Prefix character */ prefixCharacter: PropTypes.string, From 5276da1d4b530f4a584a86cca6c7fda4faab1b0e Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 27 Dec 2023 17:23:25 +0700 Subject: [PATCH 10/37] use MaybePhraseKey type --- src/components/CheckboxWithLabel.tsx | 3 ++- src/components/CountrySelector.js | 3 ++- src/components/FormAlertWithSubmitButton.tsx | 3 ++- src/components/FormAlertWrapper.tsx | 3 ++- src/components/MagicCodeInput.js | 3 ++- src/components/MenuItem.tsx | 3 ++- ...TemporaryForRefactorRequestConfirmationList.js | 4 ++-- src/components/Picker/types.ts | 3 ++- src/components/RadioButtonWithLabel.tsx | 3 ++- src/components/StatePicker/index.js | 3 ++- src/components/TimePicker/TimePicker.js | 2 +- src/components/ValuePicker/index.js | 3 ++- src/components/translatableTextPropTypes.js | 1 + src/libs/ErrorUtils.ts | 15 ++++++--------- 14 files changed, 30 insertions(+), 22 deletions(-) diff --git a/src/components/CheckboxWithLabel.tsx b/src/components/CheckboxWithLabel.tsx index 9660c9e1a2e5..e3a6b5fc44c7 100644 --- a/src/components/CheckboxWithLabel.tsx +++ b/src/components/CheckboxWithLabel.tsx @@ -1,6 +1,7 @@ import React, {ComponentType, ForwardedRef, useState} from 'react'; import {StyleProp, View, ViewStyle} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; +import type {MaybePhraseKey} from '@libs/Localize'; import variables from '@styles/variables'; import Checkbox from './Checkbox'; import FormHelpMessage from './FormHelpMessage'; @@ -38,7 +39,7 @@ type CheckboxWithLabelProps = RequiredLabelProps & { style?: StyleProp; /** Error text to display */ - errorText?: string; + errorText?: MaybePhraseKey; /** Value for checkbox. This prop is intended to be set by Form.js only */ value?: boolean; diff --git a/src/components/CountrySelector.js b/src/components/CountrySelector.js index 68a6486bce48..01d297d35467 100644 --- a/src/components/CountrySelector.js +++ b/src/components/CountrySelector.js @@ -8,10 +8,11 @@ import ROUTES from '@src/ROUTES'; import FormHelpMessage from './FormHelpMessage'; import MenuItemWithTopDescription from './MenuItemWithTopDescription'; import refPropTypes from './refPropTypes'; +import translatableTextPropTypes from './translatableTextPropTypes'; const propTypes = { /** Form error text. e.g when no country is selected */ - errorText: PropTypes.string, + errorText: translatableTextPropTypes, /** Callback called when the country changes. */ onInputChange: PropTypes.func.isRequired, diff --git a/src/components/FormAlertWithSubmitButton.tsx b/src/components/FormAlertWithSubmitButton.tsx index d8e30b27371d..d9412bf79857 100644 --- a/src/components/FormAlertWithSubmitButton.tsx +++ b/src/components/FormAlertWithSubmitButton.tsx @@ -1,12 +1,13 @@ import React from 'react'; import {StyleProp, View, ViewStyle} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; +import type {MaybePhraseKey} from '@libs/Localize'; import Button from './Button'; import FormAlertWrapper from './FormAlertWrapper'; type FormAlertWithSubmitButtonProps = { /** Error message to display above button */ - message?: string; + message?: MaybePhraseKey; /** Whether the button is disabled */ isDisabled?: boolean; diff --git a/src/components/FormAlertWrapper.tsx b/src/components/FormAlertWrapper.tsx index a144bf069502..9d366fd72cb0 100644 --- a/src/components/FormAlertWrapper.tsx +++ b/src/components/FormAlertWrapper.tsx @@ -2,6 +2,7 @@ import React, {ReactNode} from 'react'; import {StyleProp, View, ViewStyle} from 'react-native'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; +import type {MaybePhraseKey} from '@libs/Localize'; import Network from '@src/types/onyx/Network'; import FormHelpMessage from './FormHelpMessage'; import {withNetwork} from './OnyxProvider'; @@ -26,7 +27,7 @@ type FormAlertWrapperProps = { isMessageHtml?: boolean; /** Error message to display above button */ - message?: string; + message?: MaybePhraseKey; /** Props to detect online status */ network: Network; diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index 55a65237a691..b075edc9aeca 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -14,6 +14,7 @@ import networkPropTypes from './networkPropTypes'; import {withNetwork} from './OnyxProvider'; import Text from './Text'; import TextInput from './TextInput'; +import translatableTextPropTypes from './translatableTextPropTypes'; const TEXT_INPUT_EMPTY_STATE = ''; @@ -34,7 +35,7 @@ const propTypes = { shouldDelayFocus: PropTypes.bool, /** Error text to display */ - errorText: PropTypes.string, + errorText: translatableTextPropTypes, /** Specifies autocomplete hints for the system, so it can provide autofill */ autoComplete: PropTypes.oneOf(['sms-otp', 'one-time-code', 'off']).isRequired, diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx index db150d55f0d2..a713e11e5871 100644 --- a/src/components/MenuItem.tsx +++ b/src/components/MenuItem.tsx @@ -12,6 +12,7 @@ import ControlSelection from '@libs/ControlSelection'; import convertToLTR from '@libs/convertToLTR'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import getButtonState from '@libs/getButtonState'; +import type {MaybePhraseKey} from '@libs/Localize'; import {AvatarSource} from '@libs/UserUtils'; import variables from '@styles/variables'; import * as Session from '@userActions/Session'; @@ -142,7 +143,7 @@ type MenuItemProps = (ResponsiveProps | UnresponsiveProps) & error?: string; /** Error to display at the bottom of the component */ - errorText?: string; + errorText?: MaybePhraseKey; /** A boolean flag that gives the icon a green fill if true */ success?: boolean; diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 20012bc90ef0..32b9100c0803 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -545,13 +545,13 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ )} {button} ); - }, [confirm, bankAccountRoute, iouCurrencyCode, iouType, isReadOnly, policyID, selectedParticipants, splitOrRequestOptions, translate, formError, styles.ph1, styles.mb2]); + }, [confirm, bankAccountRoute, iouCurrencyCode, iouType, isReadOnly, policyID, selectedParticipants, splitOrRequestOptions, formError, styles.ph1, styles.mb2]); const {image: receiptImage, thumbnail: receiptThumbnail} = receiptPath && receiptFilename ? ReceiptUtils.getThumbnailAndImageURIs(transaction, receiptPath, receiptFilename) : {}; return ( diff --git a/src/components/Picker/types.ts b/src/components/Picker/types.ts index 58eed0371893..3fada48005f5 100644 --- a/src/components/Picker/types.ts +++ b/src/components/Picker/types.ts @@ -1,5 +1,6 @@ import {ChangeEvent, Component, ReactElement} from 'react'; import {MeasureLayoutOnSuccessCallback, NativeMethods, StyleProp, ViewStyle} from 'react-native'; +import type {MaybePhraseKey} from '@libs/Localize'; type MeasureLayoutOnFailCallback = () => void; @@ -58,7 +59,7 @@ type BasePickerProps = { placeholder?: PickerPlaceholder; /** Error text to display */ - errorText?: string; + errorText?: MaybePhraseKey; /** Customize the BasePicker container */ containerStyles?: StyleProp; diff --git a/src/components/RadioButtonWithLabel.tsx b/src/components/RadioButtonWithLabel.tsx index 4c223262ac50..5327b9dbb2d4 100644 --- a/src/components/RadioButtonWithLabel.tsx +++ b/src/components/RadioButtonWithLabel.tsx @@ -1,6 +1,7 @@ import React, {ComponentType} from 'react'; import {StyleProp, View, ViewStyle} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; +import type {MaybePhraseKey} from '@libs/Localize'; import FormHelpMessage from './FormHelpMessage'; import * as Pressables from './Pressable'; import RadioButton from './RadioButton'; @@ -26,7 +27,7 @@ type RadioButtonWithLabelProps = { hasError?: boolean; /** Error text to display */ - errorText?: string; + errorText?: MaybePhraseKey; }; const PressableWithFeedback = Pressables.PressableWithFeedback; diff --git a/src/components/StatePicker/index.js b/src/components/StatePicker/index.js index 6fa60fbba947..e937fb2f76fd 100644 --- a/src/components/StatePicker/index.js +++ b/src/components/StatePicker/index.js @@ -6,13 +6,14 @@ import _ from 'underscore'; import FormHelpMessage from '@components/FormHelpMessage'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import refPropTypes from '@components/refPropTypes'; +import translatableTextPropTypes from '@components/translatableTextPropTypes'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import StateSelectorModal from './StateSelectorModal'; const propTypes = { /** Error text to display */ - errorText: PropTypes.string, + errorText: translatableTextPropTypes, /** State to display */ value: PropTypes.string, diff --git a/src/components/TimePicker/TimePicker.js b/src/components/TimePicker/TimePicker.js index 5b49739150cc..f0633415c78b 100644 --- a/src/components/TimePicker/TimePicker.js +++ b/src/components/TimePicker/TimePicker.js @@ -446,7 +446,7 @@ function TimePicker({forwardedRef, defaultValue, onSubmit, onInputChange}) { {isError ? ( ) : ( diff --git a/src/components/ValuePicker/index.js b/src/components/ValuePicker/index.js index b5ddaa7dcb73..38e1689f3b10 100644 --- a/src/components/ValuePicker/index.js +++ b/src/components/ValuePicker/index.js @@ -5,6 +5,7 @@ import {View} from 'react-native'; import FormHelpMessage from '@components/FormHelpMessage'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import refPropTypes from '@components/refPropTypes'; +import translatableTextPropTypes from '@components/translatableTextPropTypes'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import variables from '@styles/variables'; @@ -12,7 +13,7 @@ import ValueSelectorModal from './ValueSelectorModal'; const propTypes = { /** Form Error description */ - errorText: PropTypes.string, + errorText: translatableTextPropTypes, /** Item to display */ value: PropTypes.string, diff --git a/src/components/translatableTextPropTypes.js b/src/components/translatableTextPropTypes.js index 8da65b0ba202..10130ab2da3e 100644 --- a/src/components/translatableTextPropTypes.js +++ b/src/components/translatableTextPropTypes.js @@ -2,6 +2,7 @@ import PropTypes from 'prop-types'; /** * Traslatable text with phrase key and/or variables + * Use Localize.MaybePhraseKey instead for Typescript * * E.g. ['common.error.characterLimitExceedCounter', {length: 5, limit: 20}] */ diff --git a/src/libs/ErrorUtils.ts b/src/libs/ErrorUtils.ts index 4c369d6a8b4f..e3dd952c7ad0 100644 --- a/src/libs/ErrorUtils.ts +++ b/src/libs/ErrorUtils.ts @@ -56,9 +56,7 @@ type OnyxDataWithErrors = { errors?: Errors; }; -type TranslationData = [string, Record] | string; - -function getLatestErrorMessage(onyxData: TOnyxData): TranslationData { +function getLatestErrorMessage(onyxData: TOnyxData): Localize.MaybePhraseKey { const errors = onyxData.errors ?? {}; if (Object.keys(errors).length === 0) { @@ -74,7 +72,7 @@ type OnyxDataWithErrorFields = { errorFields?: ErrorFields; }; -function getLatestErrorField(onyxData: TOnyxData, fieldName: string): Record { +function getLatestErrorField(onyxData: TOnyxData, fieldName: string): Record { const errorsForField = onyxData.errorFields?.[fieldName] ?? {}; if (Object.keys(errorsForField).length === 0) { @@ -86,7 +84,7 @@ function getLatestErrorField(onyxData return {[key]: [errorsForField[key], {isTranslated: true}]}; } -function getEarliestErrorField(onyxData: TOnyxData, fieldName: string): Record { +function getEarliestErrorField(onyxData: TOnyxData, fieldName: string): Record { const errorsForField = onyxData.errorFields?.[fieldName] ?? {}; if (Object.keys(errorsForField).length === 0) { @@ -98,15 +96,14 @@ function getEarliestErrorField(onyxDa return {[key]: [errorsForField[key], {isTranslated: true}]}; } -type ErrorsList = Record; -type ErrorsListWithTranslationData = Record; +type ErrorsList = Record; /** * Method used to attach already translated message with isTranslated: true property * @param errors - An object containing current errors in the form * @returns Errors in the form of {timestamp: [message, {isTranslated: true}]} */ -function getErrorMessagesWithTranslationData(errors: TranslationData | ErrorsList): ErrorsListWithTranslationData { +function getErrorMessagesWithTranslationData(errors: Localize.MaybePhraseKey | ErrorsList): ErrorsList { if (isEmpty(errors)) { return {}; } @@ -114,7 +111,7 @@ function getErrorMessagesWithTranslationData(errors: TranslationData | ErrorsLis if (typeof errors === 'string' || Array.isArray(errors)) { const [message, variables] = Array.isArray(errors) ? errors : [errors]; // eslint-disable-next-line @typescript-eslint/naming-convention - return {0: [message, {...variables, isTranslated: true}]}; + return {0: [message as string, {...variables, isTranslated: true}]}; } return mapKeys(errors, (message) => [message, {isTranslated: true}]); From 93de9802ff67a2c8ebe1b493efac97988b5a4190 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 27 Dec 2023 17:30:58 +0700 Subject: [PATCH 11/37] fix type --- src/libs/ErrorUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/ErrorUtils.ts b/src/libs/ErrorUtils.ts index e3dd952c7ad0..0f97fa8f39cc 100644 --- a/src/libs/ErrorUtils.ts +++ b/src/libs/ErrorUtils.ts @@ -96,7 +96,7 @@ function getEarliestErrorField(onyxDa return {[key]: [errorsForField[key], {isTranslated: true}]}; } -type ErrorsList = Record; +type ErrorsList = Record; /** * Method used to attach already translated message with isTranslated: true property @@ -111,7 +111,7 @@ function getErrorMessagesWithTranslationData(errors: Localize.MaybePhraseKey | E if (typeof errors === 'string' || Array.isArray(errors)) { const [message, variables] = Array.isArray(errors) ? errors : [errors]; // eslint-disable-next-line @typescript-eslint/naming-convention - return {0: [message as string, {...variables, isTranslated: true}]}; + return {'0': [message as string, {...variables, isTranslated: true}]}; } return mapKeys(errors, (message) => [message, {isTranslated: true}]); From 916e3decb5fb25aa5757645226aa98e1fc8bf640 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 27 Dec 2023 17:42:35 +0700 Subject: [PATCH 12/37] fix type --- src/components/FormAlertWrapper.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/FormAlertWrapper.tsx b/src/components/FormAlertWrapper.tsx index 9d366fd72cb0..ef7e57758e3e 100644 --- a/src/components/FormAlertWrapper.tsx +++ b/src/components/FormAlertWrapper.tsx @@ -67,7 +67,7 @@ function FormAlertWrapper({ {` ${translate('common.inTheFormBeforeContinuing')}.`} ); - } else if (isMessageHtml) { + } else if (isMessageHtml && typeof message === 'string') { content = ${message}`} />; } From a6a834015904b14dbe2e50bad458584e6f0c499a Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 27 Dec 2023 17:48:44 +0700 Subject: [PATCH 13/37] remove redundant logic --- .../Contacts/ValidateCodeForm/BaseValidateCodeForm.js | 2 +- .../TwoFactorAuthForm/BaseTwoFactorAuthForm.js | 2 +- src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pages/settings/Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.js b/src/pages/settings/Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.js index f7008715f406..8b19c7bdd233 100644 --- a/src/pages/settings/Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.js +++ b/src/pages/settings/Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.js @@ -188,7 +188,7 @@ function BaseValidateCodeForm(props) { name="validateCode" value={validateCode} onChangeText={onTextInput} - errorText={formError.validateCode ? formError.validateCode : ErrorUtils.getLatestErrorMessage(props.account)} + errorText={formError.validateCode || ErrorUtils.getLatestErrorMessage(props.account)} hasError={!_.isEmpty(validateLoginError)} onFulfill={validateAndSubmitForm} autoFocus={false} diff --git a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/BaseTwoFactorAuthForm.js b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/BaseTwoFactorAuthForm.js index 77898916c353..f65f7368de76 100644 --- a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/BaseTwoFactorAuthForm.js +++ b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/BaseTwoFactorAuthForm.js @@ -93,7 +93,7 @@ function BaseTwoFactorAuthForm(props) { value={twoFactorAuthCode} onChangeText={onTextInput} onFulfill={validateAndSubmitForm} - errorText={formError.twoFactorAuthCode ? formError.twoFactorAuthCode : ErrorUtils.getLatestErrorMessage(props.account)} + errorText={formError.twoFactorAuthCode || ErrorUtils.getLatestErrorMessage(props.account)} ref={inputRef} autoFocus={false} /> diff --git a/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js b/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js index eaff916004be..98dc6bc68f99 100755 --- a/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js +++ b/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.js @@ -310,7 +310,7 @@ function BaseValidateCodeForm(props) { onChangeText={(text) => onTextInput(text, 'recoveryCode')} maxLength={CONST.RECOVERY_CODE_LENGTH} label={props.translate('recoveryCodeForm.recoveryCode')} - errorText={formError.recoveryCode ? formError.recoveryCode : ''} + errorText={formError.recoveryCode || ''} hasError={hasError} onSubmitEditing={validateAndSubmitForm} autoFocus @@ -326,7 +326,7 @@ function BaseValidateCodeForm(props) { onChangeText={(text) => onTextInput(text, 'twoFactorAuthCode')} onFulfill={validateAndSubmitForm} maxLength={CONST.TFA_CODE_LENGTH} - errorText={formError.twoFactorAuthCode ? formError.twoFactorAuthCode : ''} + errorText={formError.twoFactorAuthCode || ''} hasError={hasError} autoFocus key="twoFactorAuthCode" @@ -356,7 +356,7 @@ function BaseValidateCodeForm(props) { value={validateCode} onChangeText={(text) => onTextInput(text, 'validateCode')} onFulfill={validateAndSubmitForm} - errorText={formError.validateCode ? formError.validateCode : ''} + errorText={formError.validateCode || ''} hasError={hasError} autoFocus key="validateCode" From 809a5f3655ca897b01f169c011c32d45745974e1 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 27 Dec 2023 18:05:30 +0700 Subject: [PATCH 14/37] fix missing translation in OfflineWithFeedback --- src/components/OfflineWithFeedback.tsx | 3 ++- src/pages/workspace/WorkspaceMembersPage.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/OfflineWithFeedback.tsx b/src/components/OfflineWithFeedback.tsx index 5fcf1fe7442b..4f86218eab20 100644 --- a/src/components/OfflineWithFeedback.tsx +++ b/src/components/OfflineWithFeedback.tsx @@ -3,6 +3,7 @@ import {ImageStyle, StyleProp, TextStyle, View, ViewStyle} from 'react-native'; import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; +import * as ErrorUtils from '@libs/ErrorUtils'; import shouldRenderOffscreen from '@libs/shouldRenderOffscreen'; import CONST from '@src/CONST'; import * as OnyxCommon from '@src/types/onyx/OnyxCommon'; @@ -82,7 +83,7 @@ function OfflineWithFeedback({ const hasErrors = isNotEmptyObject(errors ?? {}); // Some errors have a null message. This is used to apply opacity only and to avoid showing redundant messages. - const errorMessages = omitBy(errors, (e) => e === null); + const errorMessages = ErrorUtils.getErrorMessagesWithTranslationData(omitBy(errors, (e) => e === null)); const hasErrorMessages = isNotEmptyObject(errorMessages); const isOfflinePendingAction = !!isOffline && !!pendingAction; const isUpdateOrDeleteError = hasErrors && (pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE); diff --git a/src/pages/workspace/WorkspaceMembersPage.js b/src/pages/workspace/WorkspaceMembersPage.js index 9834d4e9e1c0..3e2c1a5ca93f 100644 --- a/src/pages/workspace/WorkspaceMembersPage.js +++ b/src/pages/workspace/WorkspaceMembersPage.js @@ -407,7 +407,7 @@ function WorkspaceMembersPage(props) { return ( Policy.dismissAddedWithPrimaryLoginMessages(policyID)} /> From 7647500a94655937c3c7c135ee1aead66c143a13 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 27 Dec 2023 18:07:46 +0700 Subject: [PATCH 15/37] fix lint --- src/libs/ErrorUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ErrorUtils.ts b/src/libs/ErrorUtils.ts index 0f97fa8f39cc..e1a585139c5f 100644 --- a/src/libs/ErrorUtils.ts +++ b/src/libs/ErrorUtils.ts @@ -111,7 +111,7 @@ function getErrorMessagesWithTranslationData(errors: Localize.MaybePhraseKey | E if (typeof errors === 'string' || Array.isArray(errors)) { const [message, variables] = Array.isArray(errors) ? errors : [errors]; // eslint-disable-next-line @typescript-eslint/naming-convention - return {'0': [message as string, {...variables, isTranslated: true}]}; + return {'0': [message ?? '', {...variables, isTranslated: true}]}; } return mapKeys(errors, (message) => [message, {isTranslated: true}]); From 5167629906c1d3f411105a9287fca8b70f7798f3 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 27 Dec 2023 18:13:58 +0700 Subject: [PATCH 16/37] fix type --- src/libs/ErrorUtils.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/libs/ErrorUtils.ts b/src/libs/ErrorUtils.ts index e1a585139c5f..b694234ce69a 100644 --- a/src/libs/ErrorUtils.ts +++ b/src/libs/ErrorUtils.ts @@ -1,4 +1,3 @@ -import isEmpty from 'lodash/isEmpty'; import mapKeys from 'lodash/mapKeys'; import CONST from '@src/CONST'; import {TranslationFlatObject, TranslationPaths} from '@src/languages/types'; @@ -104,14 +103,14 @@ type ErrorsList = Record; * @returns Errors in the form of {timestamp: [message, {isTranslated: true}]} */ function getErrorMessagesWithTranslationData(errors: Localize.MaybePhraseKey | ErrorsList): ErrorsList { - if (isEmpty(errors)) { + if (!errors || (Array.isArray(errors) && errors.length === 0)) { return {}; } if (typeof errors === 'string' || Array.isArray(errors)) { const [message, variables] = Array.isArray(errors) ? errors : [errors]; // eslint-disable-next-line @typescript-eslint/naming-convention - return {'0': [message ?? '', {...variables, isTranslated: true}]}; + return {'0': [message, {...(variables ?? []), isTranslated: true}]}; } return mapKeys(errors, (message) => [message, {isTranslated: true}]); From 72e81c06a65ed11aac7e2afa08fad478300873b9 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 27 Dec 2023 18:23:56 +0700 Subject: [PATCH 17/37] fix missing translations for DotIndicatorMessage --- src/libs/actions/Card.js | 5 ++--- src/pages/iou/request/step/IOURequestStepDistance.js | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/Card.js b/src/libs/actions/Card.js index 68642bd8fdf1..d0b589f00fea 100644 --- a/src/libs/actions/Card.js +++ b/src/libs/actions/Card.js @@ -1,6 +1,5 @@ import Onyx from 'react-native-onyx'; import * as API from '@libs/API'; -import * as Localize from '@libs/Localize'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -164,12 +163,12 @@ function revealVirtualCardDetails(cardID) { API.makeRequestWithSideEffects('RevealExpensifyCardDetails', {cardID}) .then((response) => { if (response.jsonCode !== CONST.JSON_CODE.SUCCESS) { - reject(Localize.translateLocal('cardPage.cardDetailsLoadingFailure')); + reject('cardPage.cardDetailsLoadingFailure'); return; } resolve(response); }) - .catch(() => reject(Localize.translateLocal('cardPage.cardDetailsLoadingFailure'))); + .catch(() => reject('cardPage.cardDetailsLoadingFailure')); }); } diff --git a/src/pages/iou/request/step/IOURequestStepDistance.js b/src/pages/iou/request/step/IOURequestStepDistance.js index 66cbd7f135a9..5d7acb66374e 100644 --- a/src/pages/iou/request/step/IOURequestStepDistance.js +++ b/src/pages/iou/request/step/IOURequestStepDistance.js @@ -132,7 +132,7 @@ function IOURequestStepDistance({ } if (_.size(validatedWaypoints) < 2) { - return {0: translate('iou.error.atLeastTwoDifferentWaypoints')}; + return {0: 'iou.error.atLeastTwoDifferentWaypoints'}; } }; From 94fc3e294f0b8b3281ee6ccb24d2b2262ced3517 Mon Sep 17 00:00:00 2001 From: tienifr Date: Thu, 28 Dec 2023 14:27:55 +0700 Subject: [PATCH 18/37] fix lint --- src/libs/actions/Card.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libs/actions/Card.js b/src/libs/actions/Card.js index d0b589f00fea..1fb0166ccf17 100644 --- a/src/libs/actions/Card.js +++ b/src/libs/actions/Card.js @@ -163,11 +163,13 @@ function revealVirtualCardDetails(cardID) { API.makeRequestWithSideEffects('RevealExpensifyCardDetails', {cardID}) .then((response) => { if (response.jsonCode !== CONST.JSON_CODE.SUCCESS) { + // eslint-disable-next-line prefer-promise-reject-errors reject('cardPage.cardDetailsLoadingFailure'); return; } resolve(response); }) + // eslint-disable-next-line prefer-promise-reject-errors .catch(() => reject('cardPage.cardDetailsLoadingFailure')); }); } From dea7484a711629fd7d27c0b474c661e1d79ace15 Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 2 Jan 2024 17:13:13 +0700 Subject: [PATCH 19/37] handle client error --- src/components/OfflineWithFeedback.tsx | 2 +- src/languages/en.ts | 3 ++ src/languages/es.ts | 3 ++ src/libs/ErrorUtils.ts | 34 +++++++++---------- src/libs/Localize/index.ts | 2 +- src/libs/actions/Report.ts | 2 +- src/libs/actions/Session/index.ts | 2 +- .../settings/Wallet/TransferBalancePage.js | 2 +- src/pages/signin/LoginForm/BaseLoginForm.js | 2 +- src/pages/signin/UnlinkLoginForm.js | 4 +-- src/pages/workspace/WorkspaceInvitePage.js | 2 +- src/types/onyx/OnyxCommon.ts | 3 +- 12 files changed, 34 insertions(+), 27 deletions(-) diff --git a/src/components/OfflineWithFeedback.tsx b/src/components/OfflineWithFeedback.tsx index 0d54431da458..902e20063687 100644 --- a/src/components/OfflineWithFeedback.tsx +++ b/src/components/OfflineWithFeedback.tsx @@ -83,7 +83,7 @@ function OfflineWithFeedback({ const hasErrors = isNotEmptyObject(errors ?? {}); // Some errors have a null message. This is used to apply opacity only and to avoid showing redundant messages. - const errorMessages = ErrorUtils.getErrorMessagesWithTranslationData(omitBy(errors, (e) => e === null)); + const errorMessages = ErrorUtils.getErrorsWithTranslationData(omitBy(errors, (e) => e === null)); const hasErrorMessages = isNotEmptyObject(errorMessages); const isOfflinePendingAction = !!isOffline && !!pendingAction; const isUpdateOrDeleteError = hasErrors && (pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE); diff --git a/src/languages/en.ts b/src/languages/en.ts index c1decfdf1c70..d86cc1b4d421 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -842,6 +842,9 @@ export default { sharedNoteMessage: 'Keep notes about this chat here. Expensify employees and other users on the team.expensify.com domain can view these notes.', composerLabel: 'Notes', myNote: 'My note', + error: { + genericFailureMessage: "Private notes couldn't be saved", + }, }, addDebitCardPage: { addADebitCard: 'Add a debit card', diff --git a/src/languages/es.ts b/src/languages/es.ts index 42461e766b29..b86a1093bd3a 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -838,6 +838,9 @@ export default { sharedNoteMessage: 'Guarda notas sobre este chat aquí. Los empleados de Expensify y otros usuarios del dominio team.expensify.com pueden ver estas notas.', composerLabel: 'Notas', myNote: 'Mi nota', + error: { + genericFailureMessage: 'Notas privadas no han podido ser guardados', + }, }, addDebitCardPage: { addADebitCard: 'Añadir una tarjeta de débito', diff --git a/src/libs/ErrorUtils.ts b/src/libs/ErrorUtils.ts index b694234ce69a..2828c492b123 100644 --- a/src/libs/ErrorUtils.ts +++ b/src/libs/ErrorUtils.ts @@ -1,4 +1,4 @@ -import mapKeys from 'lodash/mapKeys'; +import mapValues from 'lodash/mapValues'; import CONST from '@src/CONST'; import {TranslationFlatObject, TranslationPaths} from '@src/languages/types'; import {ErrorFields, Errors} from '@src/types/onyx/OnyxCommon'; @@ -39,8 +39,8 @@ function getAuthenticateErrorMessage(response: Response): keyof TranslationFlatO * Method used to get an error object with microsecond as the key. * @param error - error key or message to be saved */ -function getMicroSecondOnyxError(error: string): Record { - return {[DateUtils.getMicroseconds()]: error}; +function getMicroSecondOnyxError(error: string, isTranslated = false): Record { + return {[DateUtils.getMicroseconds()]: error && [error, {isTranslated}]}; } /** @@ -51,6 +51,11 @@ function getMicroSecondOnyxErrorObject(error: Record): Record(onyxData: T } const key = Object.keys(errors).sort().reverse()[0]; - - return [errors[key], {isTranslated: true}]; + return getErrorWithTranslationData(errors[key]); } type OnyxDataWithErrorFields = { @@ -79,8 +83,7 @@ function getLatestErrorField(onyxData } const key = Object.keys(errorsForField).sort().reverse()[0]; - - return {[key]: [errorsForField[key], {isTranslated: true}]}; + return {[key]: getErrorWithTranslationData(errorsForField[key])}; } function getEarliestErrorField(onyxData: TOnyxData, fieldName: string): Record { @@ -91,29 +94,26 @@ function getEarliestErrorField(onyxDa } const key = Object.keys(errorsForField).sort()[0]; - - return {[key]: [errorsForField[key], {isTranslated: true}]}; + return {[key]: getErrorWithTranslationData(errorsForField[key])}; } type ErrorsList = Record; /** - * Method used to attach already translated message with isTranslated: true property + * Method used to attach already translated message with isTranslated property * @param errors - An object containing current errors in the form - * @returns Errors in the form of {timestamp: [message, {isTranslated: true}]} + * @returns Errors in the form of {timestamp: [message, {isTranslated}]} */ -function getErrorMessagesWithTranslationData(errors: Localize.MaybePhraseKey | ErrorsList): ErrorsList { +function getErrorsWithTranslationData(errors: Localize.MaybePhraseKey | ErrorsList): ErrorsList { if (!errors || (Array.isArray(errors) && errors.length === 0)) { return {}; } if (typeof errors === 'string' || Array.isArray(errors)) { - const [message, variables] = Array.isArray(errors) ? errors : [errors]; - // eslint-disable-next-line @typescript-eslint/naming-convention - return {'0': [message, {...(variables ?? []), isTranslated: true}]}; + return {'0': getErrorWithTranslationData(errors)}; } - return mapKeys(errors, (message) => [message, {isTranslated: true}]); + return mapValues(errors, getErrorWithTranslationData); } /** @@ -146,6 +146,6 @@ export { getLatestErrorMessage, getLatestErrorField, getEarliestErrorField, - getErrorMessagesWithTranslationData, + getErrorsWithTranslationData, addErrorMessage, }; diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index 77c34ebdc576..82ba8dc418d3 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -97,7 +97,7 @@ function translateLocal(phrase: TKey, ...variable return translate(BaseLocaleListener.getPreferredLocale(), phrase, ...variables); } -type MaybePhraseKey = string | [string, Record & {isTranslated?: true}] | []; +type MaybePhraseKey = string | [string, Record & {isTranslated?: boolean}] | []; /** * Return translated string for given error. diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 06c0316a40b5..ec917a5eac99 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -2379,7 +2379,7 @@ const updatePrivateNotes = (reportID: string, accountID: number, note: string) = value: { privateNotes: { [accountID]: { - errors: ErrorUtils.getMicroSecondOnyxError("Private notes couldn't be saved"), + errors: ErrorUtils.getMicroSecondOnyxError('privateNotes.error.genericFailureMessage'), }, }, }, diff --git a/src/libs/actions/Session/index.ts b/src/libs/actions/Session/index.ts index ca38e0dd5902..32adbcf59cfa 100644 --- a/src/libs/actions/Session/index.ts +++ b/src/libs/actions/Session/index.ts @@ -647,7 +647,7 @@ function clearAccountMessages() { } function setAccountError(error: string) { - Onyx.merge(ONYXKEYS.ACCOUNT, {errors: ErrorUtils.getMicroSecondOnyxError(error)}); + Onyx.merge(ONYXKEYS.ACCOUNT, {errors: ErrorUtils.getMicroSecondOnyxError(error, true)}); } // It's necessary to throttle requests to reauthenticate since calling this multiple times will cause Pusher to diff --git a/src/pages/settings/Wallet/TransferBalancePage.js b/src/pages/settings/Wallet/TransferBalancePage.js index 499f50616218..86bf5f9c7a8d 100644 --- a/src/pages/settings/Wallet/TransferBalancePage.js +++ b/src/pages/settings/Wallet/TransferBalancePage.js @@ -167,7 +167,7 @@ function TransferBalancePage(props) { const transferAmount = props.userWallet.currentBalance - calculatedFee; const isTransferable = transferAmount > 0; const isButtonDisabled = !isTransferable || !selectedAccount; - const errorMessage = !_.isEmpty(props.walletTransfer.errors) ? ErrorUtils.getErrorMessagesWithTranslationData(_.chain(props.walletTransfer.errors).values().first().value()) : ''; + const errorMessage = !_.isEmpty(props.walletTransfer.errors) ? ErrorUtils.getErrorsWithTranslationData(_.chain(props.walletTransfer.errors).values().first().value()) : ''; const shouldShowTransferView = PaymentUtils.hasExpensifyPaymentMethod(paymentCardList, props.bankAccountList) && diff --git a/src/pages/signin/LoginForm/BaseLoginForm.js b/src/pages/signin/LoginForm/BaseLoginForm.js index 8c2acbb17a68..037b52957574 100644 --- a/src/pages/signin/LoginForm/BaseLoginForm.js +++ b/src/pages/signin/LoginForm/BaseLoginForm.js @@ -288,7 +288,7 @@ function LoginForm(props) { )} { diff --git a/src/pages/signin/UnlinkLoginForm.js b/src/pages/signin/UnlinkLoginForm.js index 962e17786ce7..1d278760f13c 100644 --- a/src/pages/signin/UnlinkLoginForm.js +++ b/src/pages/signin/UnlinkLoginForm.js @@ -68,14 +68,14 @@ function UnlinkLoginForm(props) { )} {!_.isEmpty(props.account.errors) && ( )} diff --git a/src/pages/workspace/WorkspaceInvitePage.js b/src/pages/workspace/WorkspaceInvitePage.js index cb2b9ff52670..87b1802511af 100644 --- a/src/pages/workspace/WorkspaceInvitePage.js +++ b/src/pages/workspace/WorkspaceInvitePage.js @@ -278,7 +278,7 @@ function WorkspaceInvitePage(props) { isAlertVisible={shouldShowAlertPrompt} buttonText={translate('common.next')} onSubmit={inviteUser} - message={ErrorUtils.getErrorMessagesWithTranslationData(props.policy.alertMessage)} + message={ErrorUtils.getErrorsWithTranslationData(props.policy.alertMessage)} containerStyles={[styles.flexReset, styles.flexGrow0, styles.flexShrink0, styles.flexBasisAuto, styles.mb5]} enabledWhenOffline disablePressOnEnter diff --git a/src/types/onyx/OnyxCommon.ts b/src/types/onyx/OnyxCommon.ts index 956e9ff36b24..93f5e9df2350 100644 --- a/src/types/onyx/OnyxCommon.ts +++ b/src/types/onyx/OnyxCommon.ts @@ -1,4 +1,5 @@ import {ValueOf} from 'type-fest'; +import * as Localize from '@libs/Localize'; import {AvatarSource} from '@libs/UserUtils'; import CONST from '@src/CONST'; @@ -8,7 +9,7 @@ type PendingFields = Record = Record; -type Errors = Record; +type Errors = Record; type AvatarType = typeof CONST.ICON_TYPE_AVATAR | typeof CONST.ICON_TYPE_WORKSPACE; From a92b6901b0de2c4f45e05f50593d7757480ddc01 Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 2 Jan 2024 17:22:05 +0700 Subject: [PATCH 20/37] fix type --- src/libs/ErrorUtils.ts | 1 + src/libs/ReportUtils.ts | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libs/ErrorUtils.ts b/src/libs/ErrorUtils.ts index 2828c492b123..bba042d02ef3 100644 --- a/src/libs/ErrorUtils.ts +++ b/src/libs/ErrorUtils.ts @@ -110,6 +110,7 @@ function getErrorsWithTranslationData(errors: Localize.MaybePhraseKey | ErrorsLi } if (typeof errors === 'string' || Array.isArray(errors)) { + // eslint-disable-next-line @typescript-eslint/naming-convention return {'0': getErrorWithTranslationData(errors)}; } diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 6a4914f44121..7e1e5c1c0f9c 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -164,7 +164,7 @@ type ReportRouteParams = { type ReportOfflinePendingActionAndErrors = { addWorkspaceRoomOrChatPendingAction: PendingAction | undefined; - addWorkspaceRoomOrChatErrors: Record | null | undefined; + addWorkspaceRoomOrChatErrors: Errors | null | undefined; }; type OptimisticApprovedReportAction = Pick< @@ -3864,7 +3864,7 @@ function isValidReportIDFromPath(reportIDFromPath: string): boolean { /** * Return the errors we have when creating a chat or a workspace room */ -function getAddWorkspaceRoomOrChatReportErrors(report: OnyxEntry): Record | null | undefined { +function getAddWorkspaceRoomOrChatReportErrors(report: OnyxEntry): Errors | null | undefined { // We are either adding a workspace room, or we're creating a chat, it isn't possible for both of these to have errors for the same report at the same time, so // simply looking up the first truthy value will get the relevant property if it's set. return report?.errorFields?.addWorkspaceRoom ?? report?.errorFields?.createChat; From a8061efe5d6065b68aa9c1c661a4ba50800271e4 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 3 Jan 2024 00:51:10 +0700 Subject: [PATCH 21/37] fix test --- tests/actions/IOUTest.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/actions/IOUTest.js b/tests/actions/IOUTest.js index 4d9ce42a08ce..f08bfdd73ce9 100644 --- a/tests/actions/IOUTest.js +++ b/tests/actions/IOUTest.js @@ -683,7 +683,7 @@ describe('actions/IOU', () => { Onyx.disconnect(connectionID); expect(transaction.pendingAction).toBeFalsy(); expect(transaction.errors).toBeTruthy(); - expect(_.values(transaction.errors)[0]).toBe('iou.error.genericCreateFailureMessage'); + expect(_.values(transaction.errors)[0]).toBe(["iou.error.genericCreateFailureMessage", {isTranslated: false}]); resolve(); }, }); @@ -1631,7 +1631,7 @@ describe('actions/IOU', () => { Onyx.disconnect(connectionID); const updatedAction = _.find(allActions, (reportAction) => !_.isEmpty(reportAction)); expect(updatedAction.actionName).toEqual('MODIFIEDEXPENSE'); - expect(_.values(updatedAction.errors)).toEqual(expect.arrayContaining(['iou.error.genericEditFailureMessage'])); + expect(_.values(updatedAction.errors)).toEqual(expect.arrayContaining([["iou.error.genericEditFailureMessage", {isTranslated: false}]])); resolve(); }, }); @@ -1846,7 +1846,7 @@ describe('actions/IOU', () => { callback: (allActions) => { Onyx.disconnect(connectionID); const erroredAction = _.find(_.values(allActions), (action) => !_.isEmpty(action.errors)); - expect(_.values(erroredAction.errors)).toEqual(expect.arrayContaining(['iou.error.other'])); + expect(_.values(erroredAction.errors)).toEqual(expect.arrayContaining([["iou.error.other", {isTranslated: false}]])); resolve(); }, }); From bb7c9cab65081a15e4215da4d92e617c323eb4c1 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 3 Jan 2024 00:58:37 +0700 Subject: [PATCH 22/37] fix lint --- tests/actions/IOUTest.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/actions/IOUTest.js b/tests/actions/IOUTest.js index f08bfdd73ce9..7c7bf520675a 100644 --- a/tests/actions/IOUTest.js +++ b/tests/actions/IOUTest.js @@ -683,7 +683,7 @@ describe('actions/IOU', () => { Onyx.disconnect(connectionID); expect(transaction.pendingAction).toBeFalsy(); expect(transaction.errors).toBeTruthy(); - expect(_.values(transaction.errors)[0]).toBe(["iou.error.genericCreateFailureMessage", {isTranslated: false}]); + expect(_.values(transaction.errors)[0]).toBe(['iou.error.genericCreateFailureMessage', {isTranslated: false}]); resolve(); }, }); @@ -1631,7 +1631,7 @@ describe('actions/IOU', () => { Onyx.disconnect(connectionID); const updatedAction = _.find(allActions, (reportAction) => !_.isEmpty(reportAction)); expect(updatedAction.actionName).toEqual('MODIFIEDEXPENSE'); - expect(_.values(updatedAction.errors)).toEqual(expect.arrayContaining([["iou.error.genericEditFailureMessage", {isTranslated: false}]])); + expect(_.values(updatedAction.errors)).toEqual(expect.arrayContaining([['iou.error.genericEditFailureMessage', {isTranslated: false}]])); resolve(); }, }); @@ -1846,7 +1846,7 @@ describe('actions/IOU', () => { callback: (allActions) => { Onyx.disconnect(connectionID); const erroredAction = _.find(_.values(allActions), (action) => !_.isEmpty(action.errors)); - expect(_.values(erroredAction.errors)).toEqual(expect.arrayContaining([["iou.error.other", {isTranslated: false}]])); + expect(_.values(erroredAction.errors)).toEqual(expect.arrayContaining([['iou.error.other', {isTranslated: false}]])); resolve(); }, }); From 49a45b2cf26dc05203a066342c0ae6ccde5fa742 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 3 Jan 2024 01:06:08 +0700 Subject: [PATCH 23/37] fix test --- tests/actions/IOUTest.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/actions/IOUTest.js b/tests/actions/IOUTest.js index 7c7bf520675a..1fecb79c6908 100644 --- a/tests/actions/IOUTest.js +++ b/tests/actions/IOUTest.js @@ -683,7 +683,7 @@ describe('actions/IOU', () => { Onyx.disconnect(connectionID); expect(transaction.pendingAction).toBeFalsy(); expect(transaction.errors).toBeTruthy(); - expect(_.values(transaction.errors)[0]).toBe(['iou.error.genericCreateFailureMessage', {isTranslated: false}]); + expect(_.values(transaction.errors)[0]).toStrictEqual(['iou.error.genericCreateFailureMessage', {isTranslated: false}]); resolve(); }, }); From 68bc74de6ce6964e01ccb4512f064c60adcb9f92 Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 5 Jan 2024 00:21:27 +0700 Subject: [PATCH 24/37] update spanish translation message --- src/languages/es.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index d9785f2d4a55..83d904d4a98e 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -843,7 +843,7 @@ export default { composerLabel: 'Notas', myNote: 'Mi nota', error: { - genericFailureMessage: 'Notas privadas no han podido ser guardados', + genericFailureMessage: 'Las notas privadas no han podido ser guardadas', }, }, addDebitCardPage: { From f81c7a1fc924d16ea6043944b3240d2fe8da9ec1 Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 9 Jan 2024 23:37:19 +0700 Subject: [PATCH 25/37] fix lint --- src/components/MenuItem.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx index 14b721fa3d4f..afb5f0cfa173 100644 --- a/src/components/MenuItem.tsx +++ b/src/components/MenuItem.tsx @@ -135,8 +135,8 @@ type MenuItemProps = (IconProps | AvatarProps | NoIcon) & { /** Error to display below the title */ error?: string; - /** Error to display at the bottom of the component */ - errorText?: MaybePhraseKey; + /** Error to display at the bottom of the component */ + errorText?: MaybePhraseKey; /** A boolean flag that gives the icon a green fill if true */ success?: boolean; From df4e6f0708957a04d26ce6f06f65a92a4fd34fd6 Mon Sep 17 00:00:00 2001 From: tienifr Date: Thu, 18 Jan 2024 17:38:27 +0700 Subject: [PATCH 26/37] fix lint --- src/components/AddressSearch/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/AddressSearch/types.ts b/src/components/AddressSearch/types.ts index f32c83104c94..9b4254a9bc45 100644 --- a/src/components/AddressSearch/types.ts +++ b/src/components/AddressSearch/types.ts @@ -1,7 +1,7 @@ import type {RefObject} from 'react'; import type {NativeSyntheticEvent, StyleProp, TextInputFocusEventData, View, ViewStyle} from 'react-native'; import type {Place} from 'react-native-google-places-autocomplete'; -import type { MaybePhraseKey } from '@libs/Localize'; +import type {MaybePhraseKey} from '@libs/Localize'; import type Locale from '@src/types/onyx/Locale'; type CurrentLocationButtonProps = { From 592553b93b7eb1daefcc3960519b8a029be9d6f4 Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 19 Jan 2024 01:56:34 +0700 Subject: [PATCH 27/37] move translatableTextPropTypes to TS file --- src/components/CountrySelector.js | 2 +- src/components/MagicCodeInput.js | 2 +- .../RoomNameInput/roomNameInputPropTypes.js | 2 +- src/components/StatePicker/index.js | 2 +- .../TextInput/BaseTextInput/baseTextInputPropTypes.js | 2 +- src/components/ValuePicker/index.js | 2 +- src/libs/Localize/index.ts | 11 ++++++++++- 7 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/components/CountrySelector.js b/src/components/CountrySelector.js index 01d297d35467..36fa55c91f47 100644 --- a/src/components/CountrySelector.js +++ b/src/components/CountrySelector.js @@ -3,12 +3,12 @@ import React, {useEffect} from 'react'; import {View} from 'react-native'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; +import {translatableTextPropTypes} from '@libs/Localize'; import Navigation from '@libs/Navigation/Navigation'; import ROUTES from '@src/ROUTES'; import FormHelpMessage from './FormHelpMessage'; import MenuItemWithTopDescription from './MenuItemWithTopDescription'; import refPropTypes from './refPropTypes'; -import translatableTextPropTypes from './translatableTextPropTypes'; const propTypes = { /** Form error text. e.g when no country is selected */ diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index b075edc9aeca..c1d3d726e375 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -7,6 +7,7 @@ import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import * as Browser from '@libs/Browser'; +import {translatableTextPropTypes} from '@libs/Localize'; import * as ValidationUtils from '@libs/ValidationUtils'; import CONST from '@src/CONST'; import FormHelpMessage from './FormHelpMessage'; @@ -14,7 +15,6 @@ import networkPropTypes from './networkPropTypes'; import {withNetwork} from './OnyxProvider'; import Text from './Text'; import TextInput from './TextInput'; -import translatableTextPropTypes from './translatableTextPropTypes'; const TEXT_INPUT_EMPTY_STATE = ''; diff --git a/src/components/RoomNameInput/roomNameInputPropTypes.js b/src/components/RoomNameInput/roomNameInputPropTypes.js index 7f69de3a53f2..f634c6e0b3d6 100644 --- a/src/components/RoomNameInput/roomNameInputPropTypes.js +++ b/src/components/RoomNameInput/roomNameInputPropTypes.js @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; import refPropTypes from '@components/refPropTypes'; -import translatableTextPropTypes from '@components/translatableTextPropTypes'; +import {translatableTextPropTypes} from '@libs/Localize'; const propTypes = { /** Callback to execute when the text input is modified correctly */ diff --git a/src/components/StatePicker/index.js b/src/components/StatePicker/index.js index dce3c7dc801b..918280f9f953 100644 --- a/src/components/StatePicker/index.js +++ b/src/components/StatePicker/index.js @@ -6,9 +6,9 @@ import _ from 'underscore'; import FormHelpMessage from '@components/FormHelpMessage'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import refPropTypes from '@components/refPropTypes'; -import translatableTextPropTypes from '@components/translatableTextPropTypes'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; +import {translatableTextPropTypes} from '@libs/Localize'; import StateSelectorModal from './StateSelectorModal'; const propTypes = { diff --git a/src/components/TextInput/BaseTextInput/baseTextInputPropTypes.js b/src/components/TextInput/BaseTextInput/baseTextInputPropTypes.js index ddc3a5e2f9c2..e6077bde71b3 100644 --- a/src/components/TextInput/BaseTextInput/baseTextInputPropTypes.js +++ b/src/components/TextInput/BaseTextInput/baseTextInputPropTypes.js @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; import sourcePropTypes from '@components/Image/sourcePropTypes'; -import translatableTextPropTypes from '@components/translatableTextPropTypes'; +import {translatableTextPropTypes} from '@libs/Localize'; const propTypes = { /** Input label */ diff --git a/src/components/ValuePicker/index.js b/src/components/ValuePicker/index.js index 7f6b310c6374..28fa1ab26af2 100644 --- a/src/components/ValuePicker/index.js +++ b/src/components/ValuePicker/index.js @@ -5,9 +5,9 @@ import {View} from 'react-native'; import FormHelpMessage from '@components/FormHelpMessage'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import refPropTypes from '@components/refPropTypes'; -import translatableTextPropTypes from '@components/translatableTextPropTypes'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; +import {translatableTextPropTypes} from '@libs/Localize'; import variables from '@styles/variables'; import ValueSelectorModal from './ValueSelectorModal'; diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index 4e28bdb30549..54a00c3db6f2 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -1,3 +1,4 @@ +import PropTypes from 'prop-types'; import * as RNLocalize from 'react-native-localize'; import Onyx from 'react-native-onyx'; import Log from '@libs/Log'; @@ -97,6 +98,14 @@ function translateLocal(phrase: TKey, ...variable return translate(BaseLocaleListener.getPreferredLocale(), phrase, ...variables); } +/** + * Traslatable text with phrase key and/or variables + * Use MaybePhraseKey for Typescript + * + * E.g. ['common.error.characterLimitExceedCounter', {length: 5, limit: 20}] + */ +const translatableTextPropTypes = PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.object]))]); + type MaybePhraseKey = string | [string, Record & {isTranslated?: boolean}] | []; /** @@ -174,4 +183,4 @@ function getDevicePreferredLocale(): string { } export {translate, translateLocal, translateIfPhraseKey, formatList, formatMessageElementList, getDevicePreferredLocale}; -export type {PhraseParameters, Phrase, MaybePhraseKey}; +export type {PhraseParameters, Phrase, MaybePhraseKey, translatableTextPropTypes}; From ef01f3763274c676dc520dc1a3bd446c27e00ee9 Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 19 Jan 2024 01:59:55 +0700 Subject: [PATCH 28/37] remove redundant file --- src/components/translatableTextPropTypes.js | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 src/components/translatableTextPropTypes.js diff --git a/src/components/translatableTextPropTypes.js b/src/components/translatableTextPropTypes.js deleted file mode 100644 index 10130ab2da3e..000000000000 --- a/src/components/translatableTextPropTypes.js +++ /dev/null @@ -1,9 +0,0 @@ -import PropTypes from 'prop-types'; - -/** - * Traslatable text with phrase key and/or variables - * Use Localize.MaybePhraseKey instead for Typescript - * - * E.g. ['common.error.characterLimitExceedCounter', {length: 5, limit: 20}] - */ -export default PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.object]))]); From e520a8e8628ab7d7b91cd071212b6b8cd80839de Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 19 Jan 2024 02:08:12 +0700 Subject: [PATCH 29/37] fix lint --- src/libs/Localize/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index 54a00c3db6f2..c8363043567a 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -182,5 +182,5 @@ function getDevicePreferredLocale(): string { return RNLocalize.findBestAvailableLanguage([CONST.LOCALES.EN, CONST.LOCALES.ES])?.languageTag ?? CONST.LOCALES.DEFAULT; } -export {translate, translateLocal, translateIfPhraseKey, formatList, formatMessageElementList, getDevicePreferredLocale}; -export type {PhraseParameters, Phrase, MaybePhraseKey, translatableTextPropTypes}; +export {translatableTextPropTypes, translate, translateLocal, translateIfPhraseKey, formatList, formatMessageElementList, getDevicePreferredLocale}; +export type {PhraseParameters, Phrase, MaybePhraseKey}; From e834c5fe137c16e44f5bf1d23d4e90bbea614591 Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 23 Jan 2024 11:22:06 +0700 Subject: [PATCH 30/37] reapply changes --- src/pages/SearchPage/index.js | 2 +- .../MoneyTemporaryForRefactorRequestParticipantsSelector.js | 2 +- .../MoneyRequestParticipantsSelector.js | 2 +- src/pages/tasks/TaskShareDestinationSelectorModal.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pages/SearchPage/index.js b/src/pages/SearchPage/index.js index 211f3622e06c..8e6cacc2073a 100644 --- a/src/pages/SearchPage/index.js +++ b/src/pages/SearchPage/index.js @@ -50,7 +50,7 @@ function SearchPage({betas, reports}) { const themeStyles = useThemeStyles(); const personalDetails = usePersonalDetails(); - const offlineMessage = isOffline ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : ''; + const offlineMessage = isOffline ? [`${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}`, {isTranslated: true}] : ''; const [searchValue, debouncedSearchValue, setSearchValue] = useDebouncedState(''); diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js index 0949081435c4..171f3c421171 100644 --- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js +++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js @@ -83,7 +83,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({ const {isOffline} = useNetwork(); const personalDetails = usePersonalDetails(); - const offlineMessage = isOffline ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : ''; + const offlineMessage = isOffline ? [`${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}`, {isTranslated: true}] : ''; const maxParticipantsReached = participants.length === CONST.REPORT.MAXIMUM_PARTICIPANTS; diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js index 9567b17ecdf5..6257c2065592 100755 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js @@ -88,7 +88,7 @@ function MoneyRequestParticipantsSelector({ const {isOffline} = useNetwork(); const personalDetails = usePersonalDetails(); - const offlineMessage = isOffline ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : ''; + const offlineMessage = isOffline ? [`${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}`, {isTranslated: true}] : ''; const newChatOptions = useMemo(() => { const chatOptions = OptionsListUtils.getFilteredOptions( diff --git a/src/pages/tasks/TaskShareDestinationSelectorModal.js b/src/pages/tasks/TaskShareDestinationSelectorModal.js index 64fd5f50b61f..fc0f4880efd9 100644 --- a/src/pages/tasks/TaskShareDestinationSelectorModal.js +++ b/src/pages/tasks/TaskShareDestinationSelectorModal.js @@ -145,7 +145,7 @@ function TaskShareDestinationSelectorModal(props) { showTitleTooltip shouldShowOptions={didScreenTransitionEnd} textInputLabel={props.translate('optionsSelector.nameEmailOrPhoneNumber')} - textInputAlert={isOffline ? `${props.translate('common.youAppearToBeOffline')} ${props.translate('search.resultsAreLimited')}` : ''} + textInputAlert={isOffline ? [`${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}`, {isTranslated: true}] : ''} safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle} autoFocus={false} ref={inputCallbackRef} From ed0cce8af3ebdca68b3d1d2ce2a7df1c373f17d0 Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 23 Jan 2024 11:30:22 +0700 Subject: [PATCH 31/37] fix lint --- src/pages/tasks/TaskShareDestinationSelectorModal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/tasks/TaskShareDestinationSelectorModal.js b/src/pages/tasks/TaskShareDestinationSelectorModal.js index fc0f4880efd9..b8d9229e6158 100644 --- a/src/pages/tasks/TaskShareDestinationSelectorModal.js +++ b/src/pages/tasks/TaskShareDestinationSelectorModal.js @@ -145,7 +145,7 @@ function TaskShareDestinationSelectorModal(props) { showTitleTooltip shouldShowOptions={didScreenTransitionEnd} textInputLabel={props.translate('optionsSelector.nameEmailOrPhoneNumber')} - textInputAlert={isOffline ? [`${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}`, {isTranslated: true}] : ''} + textInputAlert={isOffline ? [`${props.translate('common.youAppearToBeOffline')} ${props.translate('search.resultsAreLimited')}`, {isTranslated: true}] : ''} safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle} autoFocus={false} ref={inputCallbackRef} From 03a41e373463a19c5981a52b74305200e08bdfd2 Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 23 Jan 2024 18:41:30 +0700 Subject: [PATCH 32/37] fix remaining cases --- src/components/DotIndicatorMessage.tsx | 8 ++++---- src/components/TextInput/BaseTextInput/types.ts | 2 +- src/libs/ErrorUtils.ts | 12 ++++++------ src/pages/iou/steps/MoneyRequestAmountForm.js | 6 +++--- .../Profile/CustomStatus/StatusClearAfterPage.js | 12 ++++-------- src/pages/settings/Wallet/TransferBalancePage.js | 2 +- src/pages/signin/LoginForm/BaseLoginForm.js | 2 +- src/pages/signin/UnlinkLoginForm.js | 2 +- src/pages/workspace/WorkspaceInvitePage.js | 3 +-- src/stories/Form.stories.js | 2 +- 10 files changed, 23 insertions(+), 28 deletions(-) diff --git a/src/components/DotIndicatorMessage.tsx b/src/components/DotIndicatorMessage.tsx index d18704fdfb05..113a262a6f97 100644 --- a/src/components/DotIndicatorMessage.tsx +++ b/src/components/DotIndicatorMessage.tsx @@ -23,7 +23,7 @@ type DotIndicatorMessageProps = { * timestamp: 'message', * } */ - messages: Record; + messages: Record; /** The type of message, 'error' shows a red dot, 'success' shows a green dot */ type: 'error' | 'success'; @@ -36,8 +36,8 @@ type DotIndicatorMessageProps = { }; /** Check if the error includes a receipt. */ -function isReceiptError(message: string | ReceiptError): message is ReceiptError { - if (typeof message === 'string') { +function isReceiptError(message: Localize.MaybePhraseKey | ReceiptError): message is ReceiptError { + if (typeof message === 'string' || Array.isArray(message)) { return false; } return (message?.error ?? '') === CONST.IOU.RECEIPT_ERROR; @@ -58,7 +58,7 @@ function DotIndicatorMessage({messages = {}, style, type, textStyles}: DotIndica .map((key) => messages[key]); // Removing duplicates using Set and transforming the result into an array - const uniqueMessages = [...new Set(sortedMessages)].map((message) => Localize.translateIfPhraseKey(message)); + const uniqueMessages = [...new Set(sortedMessages)].map((message) => (isReceiptError(message) ? message : Localize.translateIfPhraseKey(message))); const isErrorMessage = type === 'error'; diff --git a/src/components/TextInput/BaseTextInput/types.ts b/src/components/TextInput/BaseTextInput/types.ts index 21875d4dcc64..9931f8ec90d4 100644 --- a/src/components/TextInput/BaseTextInput/types.ts +++ b/src/components/TextInput/BaseTextInput/types.ts @@ -65,7 +65,7 @@ type CustomBaseTextInputProps = { hideFocusedState?: boolean; /** Hint text to display below the TextInput */ - hint?: string; + hint?: MaybePhraseKey; /** Prefix character */ prefixCharacter?: string; diff --git a/src/libs/ErrorUtils.ts b/src/libs/ErrorUtils.ts index aa74d84e7b65..208b824ef872 100644 --- a/src/libs/ErrorUtils.ts +++ b/src/libs/ErrorUtils.ts @@ -52,7 +52,7 @@ function getMicroSecondOnyxErrorObject(error: Record): Record(onyxData: T } const key = Object.keys(errors).sort().reverse()[0]; - return getErrorWithTranslationData(errors[key]); + return getErrorMessageWithTranslationData(errors[key]); } type OnyxDataWithErrorFields = { @@ -83,7 +83,7 @@ function getLatestErrorField(onyxData } const key = Object.keys(errorsForField).sort().reverse()[0]; - return {[key]: getErrorWithTranslationData(errorsForField[key])}; + return {[key]: getErrorMessageWithTranslationData(errorsForField[key])}; } function getEarliestErrorField(onyxData: TOnyxData, fieldName: string): Record { @@ -94,7 +94,7 @@ function getEarliestErrorField(onyxDa } const key = Object.keys(errorsForField).sort()[0]; - return {[key]: getErrorWithTranslationData(errorsForField[key])}; + return {[key]: getErrorMessageWithTranslationData(errorsForField[key])}; } type ErrorsList = Record; @@ -111,10 +111,10 @@ function getErrorsWithTranslationData(errors: Localize.MaybePhraseKey | ErrorsLi if (typeof errors === 'string' || Array.isArray(errors)) { // eslint-disable-next-line @typescript-eslint/naming-convention - return {'0': getErrorWithTranslationData(errors)}; + return {'0': getErrorMessageWithTranslationData(errors)}; } - return mapValues(errors, getErrorWithTranslationData); + return mapValues(errors, getErrorMessageWithTranslationData); } /** diff --git a/src/pages/iou/steps/MoneyRequestAmountForm.js b/src/pages/iou/steps/MoneyRequestAmountForm.js index 536944f4a2d8..de9b9708eb3e 100644 --- a/src/pages/iou/steps/MoneyRequestAmountForm.js +++ b/src/pages/iou/steps/MoneyRequestAmountForm.js @@ -228,12 +228,12 @@ function MoneyRequestAmountForm({amount, taxAmount, currency, isEditing, forward */ const submitAndNavigateToNextPage = useCallback(() => { if (isAmountInvalid(currentAmount)) { - setFormError(translate('iou.error.invalidAmount')); + setFormError('iou.error.invalidAmount'); return; } if (isTaxAmountInvalid(currentAmount, taxAmount, isTaxAmountForm)) { - setFormError(translate('iou.error.invalidTaxAmount', {amount: formattedTaxAmount})); + setFormError(['iou.error.invalidTaxAmount', {amount: formattedTaxAmount}]); return; } @@ -243,7 +243,7 @@ function MoneyRequestAmountForm({amount, taxAmount, currency, isEditing, forward initializeAmount(backendAmount); onSubmitButtonPress({amount: currentAmount, currency}); - }, [onSubmitButtonPress, currentAmount, taxAmount, currency, isTaxAmountForm, formattedTaxAmount, translate, initializeAmount]); + }, [onSubmitButtonPress, currentAmount, taxAmount, currency, isTaxAmountForm, formattedTaxAmount, initializeAmount]); /** * Input handler to check for a forward-delete key (or keyboard shortcut) press. diff --git a/src/pages/settings/Profile/CustomStatus/StatusClearAfterPage.js b/src/pages/settings/Profile/CustomStatus/StatusClearAfterPage.js index 84ca74c2842f..61208447495d 100644 --- a/src/pages/settings/Profile/CustomStatus/StatusClearAfterPage.js +++ b/src/pages/settings/Profile/CustomStatus/StatusClearAfterPage.js @@ -54,21 +54,17 @@ function getSelectedStatusType(data) { } const useValidateCustomDate = (data) => { - const {translate} = useLocalize(); const [customDateError, setCustomDateError] = useState(''); const [customTimeError, setCustomTimeError] = useState(''); const validate = () => { const {dateValidationErrorKey, timeValidationErrorKey} = ValidationUtils.validateDateTimeIsAtLeastOneMinuteInFuture(data); - const dateError = dateValidationErrorKey ? translate(dateValidationErrorKey) : ''; - setCustomDateError(dateError); - - const timeError = timeValidationErrorKey ? translate(timeValidationErrorKey) : ''; - setCustomTimeError(timeError); + setCustomDateError(dateValidationErrorKey); + setCustomTimeError(timeValidationErrorKey); return { - dateError, - timeError, + dateValidationErrorKey, + timeValidationErrorKey, }; }; diff --git a/src/pages/settings/Wallet/TransferBalancePage.js b/src/pages/settings/Wallet/TransferBalancePage.js index 44c9bde8cd3c..3dfb1f059933 100644 --- a/src/pages/settings/Wallet/TransferBalancePage.js +++ b/src/pages/settings/Wallet/TransferBalancePage.js @@ -167,7 +167,7 @@ function TransferBalancePage(props) { const transferAmount = props.userWallet.currentBalance - calculatedFee; const isTransferable = transferAmount > 0; const isButtonDisabled = !isTransferable || !selectedAccount; - const errorMessage = !_.isEmpty(props.walletTransfer.errors) ? ErrorUtils.getErrorsWithTranslationData(_.chain(props.walletTransfer.errors).values().first().value()) : ''; + const errorMessage = ErrorUtils.getLatestErrorMessage(props.walletTransfer); const shouldShowTransferView = PaymentUtils.hasExpensifyPaymentMethod(paymentCardList, props.bankAccountList) && diff --git a/src/pages/signin/LoginForm/BaseLoginForm.js b/src/pages/signin/LoginForm/BaseLoginForm.js index 40b8b7d97bd1..e9ec74e506e2 100644 --- a/src/pages/signin/LoginForm/BaseLoginForm.js +++ b/src/pages/signin/LoginForm/BaseLoginForm.js @@ -297,7 +297,7 @@ function LoginForm(props) { )} { diff --git a/src/pages/signin/UnlinkLoginForm.js b/src/pages/signin/UnlinkLoginForm.js index 1d278760f13c..52eb710e2ea5 100644 --- a/src/pages/signin/UnlinkLoginForm.js +++ b/src/pages/signin/UnlinkLoginForm.js @@ -68,7 +68,7 @@ function UnlinkLoginForm(props) { )} {!_.isEmpty(props.account.errors) && ( diff --git a/src/pages/workspace/WorkspaceInvitePage.js b/src/pages/workspace/WorkspaceInvitePage.js index 6b8a77f739af..e453ea632863 100644 --- a/src/pages/workspace/WorkspaceInvitePage.js +++ b/src/pages/workspace/WorkspaceInvitePage.js @@ -15,7 +15,6 @@ import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; import compose from '@libs/compose'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; -import * as ErrorUtils from '@libs/ErrorUtils'; import * as LoginUtils from '@libs/LoginUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; @@ -306,7 +305,7 @@ function WorkspaceInvitePage(props) { isAlertVisible={shouldShowAlertPrompt} buttonText={translate('common.next')} onSubmit={inviteUser} - message={ErrorUtils.getErrorsWithTranslationData(props.policy.alertMessage)} + message={[props.policy.alertMessage, {isTranslated: true}]} containerStyles={[styles.flexReset, styles.flexGrow0, styles.flexShrink0, styles.flexBasisAuto, styles.mb5]} enabledWhenOffline disablePressOnEnter diff --git a/src/stories/Form.stories.js b/src/stories/Form.stories.js index 6a4274c87eda..8a152d040a1f 100644 --- a/src/stories/Form.stories.js +++ b/src/stories/Form.stories.js @@ -68,7 +68,7 @@ function Template(args) { label="Street" inputID="street" containerStyles={[defaultStyles.mt4]} - hint="No PO box" + hint="common.noPO" /> Date: Sun, 28 Jan 2024 23:40:34 +0700 Subject: [PATCH 33/37] revert unnecessary change --- .../MoneyTemporaryForRefactorRequestConfirmationList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index 154be33cb7d4..485bf9a3adc0 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -612,7 +612,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ {button} ); - }, [confirm, bankAccountRoute, iouCurrencyCode, iouType, isReadOnly, policyID, selectedParticipants, splitOrRequestOptions, formError, styles.ph1, styles.mb2]); + }, [isReadOnly, iouType, selectedParticipants.length, confirm, bankAccountRoute, iouCurrencyCode, policyID, splitOrRequestOptions, formError, styles.ph1, styles.mb2]); const {image: receiptImage, thumbnail: receiptThumbnail} = receiptPath && receiptFilename ? ReceiptUtils.getThumbnailAndImageURIs(transaction, receiptPath, receiptFilename) : {}; return ( From dfb569fb10d66521057a0475860760d260e438d5 Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 29 Jan 2024 13:59:48 +0700 Subject: [PATCH 34/37] fix console error --- src/pages/settings/Profile/ProfilePage.js | 3 ++- tests/actions/IOUTest.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pages/settings/Profile/ProfilePage.js b/src/pages/settings/Profile/ProfilePage.js index 99cc5cf7e35a..22a945310c4f 100755 --- a/src/pages/settings/Profile/ProfilePage.js +++ b/src/pages/settings/Profile/ProfilePage.js @@ -15,6 +15,7 @@ import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withWindowDimensions'; import useThemeStyles from '@hooks/useThemeStyles'; import compose from '@libs/compose'; +import {translatableTextPropTypes} from '@libs/Localize'; import Navigation from '@libs/Navigation/Navigation'; import * as UserUtils from '@libs/UserUtils'; import userPropTypes from '@pages/settings/userPropTypes'; @@ -34,7 +35,7 @@ const propTypes = { validatedDate: PropTypes.string, /** Field-specific server side errors keyed by microtime */ - errorFields: PropTypes.objectOf(PropTypes.objectOf(PropTypes.string)), + errorFields: translatableTextPropTypes, }), ), diff --git a/tests/actions/IOUTest.js b/tests/actions/IOUTest.js index f389fb37b1f3..d184bbe0f922 100644 --- a/tests/actions/IOUTest.js +++ b/tests/actions/IOUTest.js @@ -683,7 +683,7 @@ describe('actions/IOU', () => { Onyx.disconnect(connectionID); expect(transaction.pendingAction).toBeFalsy(); expect(transaction.errors).toBeTruthy(); - expect(_.values(transaction.errors)[0]).toStrictEqual(['iou.error.genericCreateFailureMessage', {isTranslated: false}]); + expect(_.values(transaction.errors)[0]).toEqual(expect.arrayContaining(['iou.error.genericCreateFailureMessage', {isTranslated: false}])); resolve(); }, }); From f2f26703e2178efa372746495b9cd60c0b3801f4 Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 29 Jan 2024 14:18:16 +0700 Subject: [PATCH 35/37] fix crash for attachment --- src/libs/OptionsListUtils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index ef2888eebfb2..93a739dc273b 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -523,7 +523,8 @@ function getLastMessageTextForReport(report: OnyxEntry): string { } else if (ReportActionUtils.isDeletedParentAction(lastReportAction) && ReportUtils.isChatReport(report)) { lastMessageTextFromReport = ReportUtils.getDeletedParentActionMessageForChatReport(lastReportAction); } else if (ReportUtils.isReportMessageAttachment({text: report?.lastMessageText ?? '', html: report?.lastMessageHtml, translationKey: report?.lastMessageTranslationKey, type: ''})) { - lastMessageTextFromReport = `[${Localize.translateLocal((report?.lastMessageTranslationKey ?? 'common.attachment') as TranslationPaths)}]`; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- It's possible for lastMessageTranslationKey to be empty '', so we must fallback to common.attachment. + lastMessageTextFromReport = `[${Localize.translateLocal((report?.lastMessageTranslationKey || 'common.attachment') as TranslationPaths)}]`; } else if (ReportActionUtils.isModifiedExpenseAction(lastReportAction)) { const properSchemaForModifiedExpenseMessage = ModifiedExpenseMessage.getForReportAction(lastReportAction); lastMessageTextFromReport = ReportUtils.formatReportLastMessageText(properSchemaForModifiedExpenseMessage, true); From f8bb87b586aa3246dc0693950afd9533dfa8ede0 Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 29 Jan 2024 14:34:47 +0700 Subject: [PATCH 36/37] fix type console error --- src/components/transactionPropTypes.js | 3 ++- src/pages/settings/InitialSettingsPage.js | 3 ++- .../settings/Profile/Contacts/ContactMethodDetailsPage.js | 3 ++- src/pages/settings/Profile/Contacts/ContactMethodsPage.js | 3 ++- src/pages/settings/Profile/Contacts/NewContactMethodPage.js | 3 ++- .../Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.js | 3 ++- src/pages/settings/Profile/ProfilePage.js | 2 +- src/pages/settings/Wallet/Card/BaseGetPhysicalCard.js | 3 ++- src/pages/settings/Wallet/ExpensifyCardPage.js | 3 ++- src/pages/workspace/WorkspaceNewRoomPage.js | 3 ++- src/pages/workspace/withPolicy.tsx | 3 ++- 11 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/components/transactionPropTypes.js b/src/components/transactionPropTypes.js index c70a2e524583..bdcf60bec5da 100644 --- a/src/components/transactionPropTypes.js +++ b/src/components/transactionPropTypes.js @@ -1,5 +1,6 @@ import PropTypes from 'prop-types'; import _ from 'underscore'; +import {translatableTextPropTypes} from '@libs/Localize'; import CONST from '@src/CONST'; import sourcePropTypes from './Image/sourcePropTypes'; @@ -80,5 +81,5 @@ export default PropTypes.shape({ }), /** Server side errors keyed by microtime */ - errorFields: PropTypes.objectOf(PropTypes.objectOf(PropTypes.string)), + errorFields: PropTypes.objectOf(PropTypes.objectOf(translatableTextPropTypes)), }); diff --git a/src/pages/settings/InitialSettingsPage.js b/src/pages/settings/InitialSettingsPage.js index 6e310b9a62bd..4917aea8524b 100755 --- a/src/pages/settings/InitialSettingsPage.js +++ b/src/pages/settings/InitialSettingsPage.js @@ -27,6 +27,7 @@ import useWaitForNavigation from '@hooks/useWaitForNavigation'; import * as CardUtils from '@libs/CardUtils'; import compose from '@libs/compose'; import * as CurrencyUtils from '@libs/CurrencyUtils'; +import {translatableTextPropTypes} from '@libs/Localize'; import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; @@ -99,7 +100,7 @@ const propTypes = { validatedDate: PropTypes.string, /** Field-specific server side errors keyed by microtime */ - errorFields: PropTypes.objectOf(PropTypes.objectOf(PropTypes.string)), + errorFields: PropTypes.objectOf(PropTypes.objectOf(translatableTextPropTypes)), }), ), diff --git a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js index 7cafbe21ff6b..a9acf37ae556 100644 --- a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js +++ b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js @@ -21,6 +21,7 @@ import withThemeStyles, {withThemeStylesPropTypes} from '@components/withThemeSt import compose from '@libs/compose'; import {canUseTouchScreen} from '@libs/DeviceCapabilities'; import * as ErrorUtils from '@libs/ErrorUtils'; +import {translatableTextPropTypes} from '@libs/Localize'; import Navigation from '@libs/Navigation/Navigation'; import * as Session from '@userActions/Session'; import * as User from '@userActions/User'; @@ -44,7 +45,7 @@ const propTypes = { validatedDate: PropTypes.string, /** Field-specific server side errors keyed by microtime */ - errorFields: PropTypes.objectOf(PropTypes.objectOf(PropTypes.string)), + errorFields: PropTypes.objectOf(PropTypes.objectOf(translatableTextPropTypes)), /** Field-specific pending states for offline UI status */ pendingFields: PropTypes.objectOf(PropTypes.objectOf(PropTypes.string)), diff --git a/src/pages/settings/Profile/Contacts/ContactMethodsPage.js b/src/pages/settings/Profile/Contacts/ContactMethodsPage.js index 34399daf55e3..e958373bf9fd 100644 --- a/src/pages/settings/Profile/Contacts/ContactMethodsPage.js +++ b/src/pages/settings/Profile/Contacts/ContactMethodsPage.js @@ -16,6 +16,7 @@ import Text from '@components/Text'; import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import compose from '@libs/compose'; +import {translatableTextPropTypes} from '@libs/Localize'; import Navigation from '@libs/Navigation/Navigation'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -36,7 +37,7 @@ const propTypes = { validatedDate: PropTypes.string, /** Field-specific server side errors keyed by microtime */ - errorFields: PropTypes.objectOf(PropTypes.objectOf(PropTypes.string)), + errorFields: PropTypes.objectOf(PropTypes.objectOf(translatableTextPropTypes)), /** Field-specific pending states for offline UI status */ pendingFields: PropTypes.objectOf(PropTypes.objectOf(PropTypes.string)), diff --git a/src/pages/settings/Profile/Contacts/NewContactMethodPage.js b/src/pages/settings/Profile/Contacts/NewContactMethodPage.js index 8f6982e24b98..69fe8490f6aa 100644 --- a/src/pages/settings/Profile/Contacts/NewContactMethodPage.js +++ b/src/pages/settings/Profile/Contacts/NewContactMethodPage.js @@ -15,6 +15,7 @@ import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import compose from '@libs/compose'; import * as ErrorUtils from '@libs/ErrorUtils'; +import {translatableTextPropTypes} from '@libs/Localize'; import * as LoginUtils from '@libs/LoginUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as User from '@userActions/User'; @@ -37,7 +38,7 @@ const propTypes = { validatedDate: PropTypes.string, /** Field-specific server side errors keyed by microtime */ - errorFields: PropTypes.objectOf(PropTypes.objectOf(PropTypes.string)), + errorFields: PropTypes.objectOf(PropTypes.objectOf(translatableTextPropTypes)), /** Field-specific pending states for offline UI status */ pendingFields: PropTypes.objectOf(PropTypes.objectOf(PropTypes.string)), diff --git a/src/pages/settings/Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.js b/src/pages/settings/Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.js index 8b19c7bdd233..5c1fa30a88f1 100644 --- a/src/pages/settings/Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.js +++ b/src/pages/settings/Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.js @@ -18,6 +18,7 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import compose from '@libs/compose'; import * as ErrorUtils from '@libs/ErrorUtils'; +import {translatableTextPropTypes} from '@libs/Localize'; import * as ValidationUtils from '@libs/ValidationUtils'; import * as Session from '@userActions/Session'; import * as User from '@userActions/User'; @@ -45,7 +46,7 @@ const propTypes = { validatedDate: PropTypes.string, /** Field-specific server side errors keyed by microtime */ - errorFields: PropTypes.objectOf(PropTypes.objectOf(PropTypes.string)), + errorFields: PropTypes.objectOf(PropTypes.objectOf(translatableTextPropTypes)), /** Field-specific pending states for offline UI status */ pendingFields: PropTypes.objectOf(PropTypes.objectOf(PropTypes.string)), diff --git a/src/pages/settings/Profile/ProfilePage.js b/src/pages/settings/Profile/ProfilePage.js index 22a945310c4f..b959e3b66b97 100755 --- a/src/pages/settings/Profile/ProfilePage.js +++ b/src/pages/settings/Profile/ProfilePage.js @@ -35,7 +35,7 @@ const propTypes = { validatedDate: PropTypes.string, /** Field-specific server side errors keyed by microtime */ - errorFields: translatableTextPropTypes, + errorFields: PropTypes.objectOf(PropTypes.objectOf(translatableTextPropTypes)), }), ), diff --git a/src/pages/settings/Wallet/Card/BaseGetPhysicalCard.js b/src/pages/settings/Wallet/Card/BaseGetPhysicalCard.js index da77d1fa6a15..f23f48fc2011 100644 --- a/src/pages/settings/Wallet/Card/BaseGetPhysicalCard.js +++ b/src/pages/settings/Wallet/Card/BaseGetPhysicalCard.js @@ -12,6 +12,7 @@ import * as Wallet from '@libs/actions/Wallet'; import * as CardUtils from '@libs/CardUtils'; import FormUtils from '@libs/FormUtils'; import * as GetPhysicalCardUtils from '@libs/GetPhysicalCardUtils'; +import {translatableTextPropTypes} from '@libs/Localize'; import Navigation from '@libs/Navigation/Navigation'; import assignedCardPropTypes from '@pages/settings/Wallet/assignedCardPropTypes'; import CONST from '@src/CONST'; @@ -69,7 +70,7 @@ const propTypes = { validatedDate: PropTypes.string, /** Field-specific server side errors keyed by microtime */ - errorFields: PropTypes.objectOf(PropTypes.objectOf(PropTypes.string)), + errorFields: PropTypes.objectOf(PropTypes.objectOf(translatableTextPropTypes)), /** Field-specific pending states for offline UI status */ pendingFields: PropTypes.objectOf(PropTypes.objectOf(PropTypes.string)), diff --git a/src/pages/settings/Wallet/ExpensifyCardPage.js b/src/pages/settings/Wallet/ExpensifyCardPage.js index d7d63f0271ca..755790dfec81 100644 --- a/src/pages/settings/Wallet/ExpensifyCardPage.js +++ b/src/pages/settings/Wallet/ExpensifyCardPage.js @@ -18,6 +18,7 @@ import * as CardUtils from '@libs/CardUtils'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import FormUtils from '@libs/FormUtils'; import * as GetPhysicalCardUtils from '@libs/GetPhysicalCardUtils'; +import {translatableTextPropTypes} from '@libs/Localize'; import Navigation from '@libs/Navigation/Navigation'; import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; import * as Card from '@userActions/Card'; @@ -56,7 +57,7 @@ const propTypes = { validatedDate: PropTypes.string, /** Field-specific server side errors keyed by microtime */ - errorFields: PropTypes.objectOf(PropTypes.objectOf(PropTypes.string)), + errorFields: PropTypes.objectOf(PropTypes.objectOf(translatableTextPropTypes)), /** Field-specific pending states for offline UI status */ pendingFields: PropTypes.objectOf(PropTypes.objectOf(PropTypes.string)), diff --git a/src/pages/workspace/WorkspaceNewRoomPage.js b/src/pages/workspace/WorkspaceNewRoomPage.js index b616b519ff32..7da9f425c5a8 100644 --- a/src/pages/workspace/WorkspaceNewRoomPage.js +++ b/src/pages/workspace/WorkspaceNewRoomPage.js @@ -23,6 +23,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import compose from '@libs/compose'; import * as ErrorUtils from '@libs/ErrorUtils'; +import {translatableTextPropTypes} from '@libs/Localize'; import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; @@ -69,7 +70,7 @@ const propTypes = { isLoading: PropTypes.bool, /** Field errors in the form */ - errorFields: PropTypes.objectOf(PropTypes.objectOf(PropTypes.string)), + errorFields: PropTypes.objectOf(PropTypes.objectOf(translatableTextPropTypes)), }), /** Session details for the user */ diff --git a/src/pages/workspace/withPolicy.tsx b/src/pages/workspace/withPolicy.tsx index ec38b61fb0dc..e8f83a3a895e 100644 --- a/src/pages/workspace/withPolicy.tsx +++ b/src/pages/workspace/withPolicy.tsx @@ -5,6 +5,7 @@ import type {ComponentType, ForwardedRef, RefAttributes} from 'react'; import React, {forwardRef} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; +import {translatableTextPropTypes} from '@libs/Localize'; import policyMemberPropType from '@pages/policyMemberPropType'; import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; @@ -53,7 +54,7 @@ const policyPropTypes = { * } * } */ - errorFields: PropTypes.objectOf(PropTypes.objectOf(PropTypes.string)), + errorFields: PropTypes.objectOf(PropTypes.objectOf(translatableTextPropTypes)), /** Whether or not the policy requires tags */ requiresTag: PropTypes.bool, From 078c2b701015901f2e1ad0f824ae6b94f5fb646a Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 2 Feb 2024 20:35:02 +0700 Subject: [PATCH 37/37] fix lint --- .../MoneyRequestParticipantsSelector.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js index 35ac9fe9ac9a..a37e4436d0e2 100755 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js @@ -92,9 +92,6 @@ function MoneyRequestParticipantsSelector({ const maxParticipantsReached = participants.length === CONST.REPORT.MAXIMUM_PARTICIPANTS; const setSearchTermAndSearchInServer = useSearchTermAndSearch(setSearchTerm, maxParticipantsReached); - const maxParticipantsReached = participants.length === CONST.REPORT.MAXIMUM_PARTICIPANTS; - const setSearchTermAndSearchInServer = useSearchTermAndSearch(setSearchTerm, maxParticipantsReached); - const offlineMessage = isOffline ? [`${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}`, {isTranslated: true}] : ''; const newChatOptions = useMemo(() => {