diff --git a/README.md b/README.md index c8faff111bae..4a691045e7c2 100644 --- a/README.md +++ b/README.md @@ -619,7 +619,30 @@ Some pointers: key to the translation file and use the arrow function version, like so: `nameOfTheKey: ({amount, dateTime}) => "User has sent " + amount + " to you on " + dateTime,`. This is because the order of the phrases might vary from one language to another. - +- When working with translations that involve plural forms, it's important to handle different cases correctly. + + For example: + - zero: Used when there are no items **(optional)**. + - one: Used when there's exactly one item. + - two: Used when there's two items. **(optional)** + - few: Used for a small number of items **(optional)**. + - many: Used for larger quantities **(optional)**. + - other: A catch-all case for other counts or variations. + + Here’s an example of how to implement plural translations: + + messages: () => ({ + zero: 'No messages', + one: 'One message', + two: 'Two messages', + few: (count) => `${count} messages`, + many: (count) => `You have ${count} messages`, + other: (count) => `You have ${count} unread messages`, + }) + + In your code, you can use the translation like this: + + `translate('common.messages', {count: 1});` ---- # Deploying diff --git a/src/components/AccountSwitcher.tsx b/src/components/AccountSwitcher.tsx index 71970b88eac9..8d3e311c7c61 100644 --- a/src/components/AccountSwitcher.tsx +++ b/src/components/AccountSwitcher.tsx @@ -108,7 +108,7 @@ function AccountSwitcher() { const error = ErrorUtils.getLatestErrorField({errorFields}, 'connect'); const personalDetails = PersonalDetailsUtils.getPersonalDetailByEmail(email); return createBaseMenuItem(personalDetails, error, { - badgeText: translate('delegate.role', role), + badgeText: translate('delegate.role', {role}), onPress: () => { if (isOffline) { Modal.close(() => setShouldShowOfflineModal(true)); diff --git a/src/components/AccountingConnectionConfirmationModal.tsx b/src/components/AccountingConnectionConfirmationModal.tsx index c472f215b6df..bfacd8c0bf76 100644 --- a/src/components/AccountingConnectionConfirmationModal.tsx +++ b/src/components/AccountingConnectionConfirmationModal.tsx @@ -14,11 +14,11 @@ function AccountingConnectionConfirmationModal({integrationToConnect, onCancel, return ( diff --git a/src/components/LocaleContextProvider.tsx b/src/components/LocaleContextProvider.tsx index 383784a468d7..b677cf1e66e2 100644 --- a/src/components/LocaleContextProvider.tsx +++ b/src/components/LocaleContextProvider.tsx @@ -8,7 +8,7 @@ import * as LocalePhoneNumber from '@libs/LocalePhoneNumber'; import * as Localize from '@libs/Localize'; import * as NumberFormatUtils from '@libs/NumberFormatUtils'; import CONST from '@src/CONST'; -import type {TranslationPaths} from '@src/languages/types'; +import type {TranslationParameters, TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import type {WithCurrentUserPersonalDetailsProps} from './withCurrentUserPersonalDetails'; import withCurrentUserPersonalDetails from './withCurrentUserPersonalDetails'; @@ -28,7 +28,7 @@ type LocaleContextProviderProps = LocaleContextProviderOnyxProps & type LocaleContextProps = { /** Returns translated string for given locale and phrase */ - translate: (phraseKey: TKey, ...phraseParameters: Localize.PhraseParameters>) => string; + translate: (path: TPath, ...parameters: TranslationParameters) => string; /** Formats number formatted according to locale and options */ numberFormat: (number: number, options?: Intl.NumberFormatOptions) => string; @@ -79,8 +79,8 @@ function LocaleContextProvider({preferredLocale, currentUserPersonalDetails, chi const translate = useMemo( () => - (phraseKey, ...phraseParameters) => - Localize.translate(locale, phraseKey, ...phraseParameters), + (path, ...parameters) => + Localize.translate(locale, path, ...parameters), [locale], ); diff --git a/src/components/ParentNavigationSubtitle.tsx b/src/components/ParentNavigationSubtitle.tsx index f60b877a5d23..997106f3e649 100644 --- a/src/components/ParentNavigationSubtitle.tsx +++ b/src/components/ParentNavigationSubtitle.tsx @@ -6,7 +6,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import CONST from '@src/CONST'; -import type {ParentNavigationSummaryParams} from '@src/languages/types'; +import type {ParentNavigationSummaryParams} from '@src/languages/params'; import ROUTES from '@src/ROUTES'; import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback'; import Text from './Text'; diff --git a/src/components/ReceiptAudit.tsx b/src/components/ReceiptAudit.tsx index bb704def1836..29439911e221 100644 --- a/src/components/ReceiptAudit.tsx +++ b/src/components/ReceiptAudit.tsx @@ -22,7 +22,7 @@ function ReceiptAudit({notes, shouldShowAuditResult}: ReceiptAuditProps) { let auditText = ''; if (notes.length > 0 && shouldShowAuditResult) { - auditText = translate('iou.receiptIssuesFound', notes.length); + auditText = translate('iou.receiptIssuesFound', {count: notes.length}); } else if (!notes.length && shouldShowAuditResult) { auditText = translate('common.verified'); } diff --git a/src/components/ReportActionItem/ExportWithDropdownMenu.tsx b/src/components/ReportActionItem/ExportWithDropdownMenu.tsx index aff868a74bc5..2f01bb0f9f46 100644 --- a/src/components/ReportActionItem/ExportWithDropdownMenu.tsx +++ b/src/components/ReportActionItem/ExportWithDropdownMenu.tsx @@ -59,7 +59,7 @@ function ExportWithDropdownMenu({ const options = [ { value: CONST.REPORT.EXPORT_OPTIONS.EXPORT_TO_INTEGRATION, - text: translate('workspace.common.exportIntegrationSelected', connectionName), + text: translate('workspace.common.exportIntegrationSelected', {connectionName}), ...optionTemplate, }, { @@ -126,7 +126,7 @@ function ExportWithDropdownMenu({ title={translate('workspace.exportAgainModal.title')} onConfirm={confirmExport} onCancel={() => setModalStatus(null)} - prompt={translate('workspace.exportAgainModal.description', report?.reportName ?? '', connectionName)} + prompt={translate('workspace.exportAgainModal.description', {connectionName, reportName: report?.reportName ?? ''})} confirmText={translate('workspace.exportAgainModal.confirmText')} cancelText={translate('workspace.exportAgainModal.cancelText')} isVisible={!!modalStatus} diff --git a/src/languages/en.ts b/src/languages/en.ts index 2c38477d9447..699caa960518 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2,45 +2,90 @@ import {CONST as COMMON_CONST, Str} from 'expensify-common'; import startCase from 'lodash/startCase'; import CONST from '@src/CONST'; import type {Country} from '@src/CONST'; -import type {ConnectionName, PolicyConnectionSyncStage, SageIntacctMappingName} from '@src/types/onyx/Policy'; import type { AccountOwnerParams, + ActionsAreCurrentlyRestricted, + AddEmployeeParams, AddressLineParams, AdminCanceledRequestParams, + AgeParams, AlreadySignedInParams, ApprovalWorkflowErrorParams, ApprovedAmountParams, AssignCardParams, + AssignedYouCardParams, + AssigneeParams, + AuthenticationErrorParams, + AutoPayApprovedReportsLimitErrorParams, + BadgeFreeTrialParams, BeginningOfChatHistoryAdminRoomPartOneParams, BeginningOfChatHistoryAnnounceRoomPartOneParams, BeginningOfChatHistoryAnnounceRoomPartTwo, BeginningOfChatHistoryDomainRoomPartOneParams, + BillingBannerCardAuthenticationRequiredParams, + BillingBannerCardExpiredParams, + BillingBannerCardOnDisputeParams, + BillingBannerDisputePendingParams, + BillingBannerInsufficientFundsParams, + BillingBannerSubtitleWithDateParams, CanceledRequestParams, + CardEndingParams, + CardInfoParams, + CardNextPaymentParams, + CategoryNameParams, ChangeFieldParams, + ChangeOwnerDuplicateSubscriptionParams, + ChangeOwnerHasFailedSettlementsParams, + ChangeOwnerSubscriptionParams, ChangePolicyParams, ChangeTypeParams, + CharacterLengthLimitParams, CharacterLimitParams, CompanyCardFeedNameParams, ConfirmHoldExpenseParams, ConfirmThatParams, + ConnectionNameParams, + ConnectionParams, + CustomersOrJobsLabelParams, + DateParams, DateShouldBeAfterParams, DateShouldBeBeforeParams, + DefaultAmountParams, + DefaultVendorDescriptionParams, + DelegateRoleParams, DelegateSubmitParams, DeleteActionParams, DeleteConfirmationParams, DeleteExpenseTranslationParams, DidSplitAmountMessageParams, + DimensionsCountParams, DistanceRateOperationsParams, EditActionParams, ElectronicFundsParams, EnterMagicCodeParams, + ExportAgainModalDescriptionParams, ExportedToIntegrationParams, + ExportIntegrationSelectedParams, + FeatureNameParams, + FiltersAmountBetweenParams, FormattedMaxLengthParams, ForwardedAmountParams, GoBackMessageParams, GoToRoomParams, + ImportedTagsMessageParams, + ImportFieldParams, + ImportMembersSuccessfullDescriptionParams, + ImportTagsSuccessfullDescriptionParams, + IncorrectZipFormatParams, InstantSummaryParams, + IntacctMappingTitleParams, + IntegrationExportParams, + IntegrationSyncFailedParams, + InvalidPropertyParams, + InvalidValueParams, IssueVirtualCardParams, + LastSyncAccountingParams, + LastSyncDateParams, LocalTimeParams, LoggedInAsParams, LogSizeParams, @@ -48,12 +93,15 @@ import type { ManagerApprovedParams, MarkedReimbursedParams, MarkReimbursedFromIntegrationParams, + MissingPropertyParams, NoLongerHaveAccessParams, NotAllowedExtensionParams, NotYouParams, OOOEventSummaryFullDayParams, OOOEventSummaryPartialDayParams, + OptionalParam, OurEmailProviderParams, + OwnerOwesAmountParams, PaidElsewhereWithAmountParams, PaidWithExpensifyWithAmountParams, ParentNavigationSummaryParams, @@ -63,21 +111,28 @@ import type { PayerPaidParams, PayerSettledParams, PaySomeoneParams, + ReconciliationWorksParams, ReimbursementRateParams, + RemovedFromApprovalWorkflowParams, RemovedTheRequestParams, + RemoveMemberPromptParams, RemoveMembersWarningPrompt, RenamedRoomActionParams, ReportArchiveReasonsClosedParams, ReportArchiveReasonsInvoiceReceiverPolicyDeletedParams, ReportArchiveReasonsMergedParams, - ReportArchiveReasonsPolicyDeletedParams, ReportArchiveReasonsRemovedFromPolicyParams, + ReportPolicyNameParams, RequestAmountParams, RequestCountParams, RequestedAmountMessageParams, + RequiredFieldParams, ResolutionConstraintsParams, + RoleNamesParams, RoomNameReservedErrorParams, RoomRenamedToParams, + SecondaryLoginParams, + SelectedNumberParams, SetTheDistanceMerchantParams, SetTheRequestParams, SettledAfterAddedBankAccountParams, @@ -86,20 +141,32 @@ import type { SignUpNewFaceCodeParams, SizeExceededParams, SplitAmountParams, + SpreadCategoriesParams, + SpreadFieldNameParams, + SpreadSheetColumnParams, + StatementTitleParams, StepCounterParams, StripePaidParams, + SubscriptionCommitmentParams, + SubscriptionSettingsRenewsOnParams, + SubscriptionSettingsSaveUpToParams, + SubscriptionSizeParams, + SyncStageNameConnectionsParams, TaskCreatedActionParams, + TaxAmountParams, TermsParams, ThreadRequestReportNameParams, ThreadSentMoneyReportNameParams, ToValidateLoginParams, TransferParams, - TranslationBase, + TrialStartedTitleParams, UnapprovedParams, + UnapproveWithIntegrationWarningParams, UnshareParams, UntilTimeParams, UpdatedTheDistanceMerchantParams, UpdatedTheRequestParams, + UpdateRoleParams, UsePlusButtonParams, UserIsAlreadyMemberParams, UserSplitParams, @@ -123,8 +190,11 @@ import type { WelcomeNoteParams, WelcomeToRoomParams, WeSentYouMagicSignInLinkParams, + WorkspaceOwnerWillNeedToAddOrUpdatePaymentCardParams, + YourPlanPriceParams, ZipCodeExampleFormatParams, -} from './types'; +} from './params'; +import type {TranslationDeepObject} from './types'; type StateValue = { stateISO: string; @@ -136,7 +206,7 @@ type States = Record; type AllCountries = Record; /* eslint-disable max-len */ -export default { +const translations = { common: { cancel: 'Cancel', dismiss: 'Dismiss', @@ -264,7 +334,7 @@ export default { fieldRequired: 'This field is required.', requestModified: 'This request is being modified by another member.', characterLimit: ({limit}: CharacterLimitParams) => `Exceeds the maximum length of ${limit} characters`, - characterLimitExceedCounter: ({length, limit}) => `Character limit exceeded (${length}/${limit})`, + characterLimitExceedCounter: ({length, limit}: CharacterLengthLimitParams) => `Character limit exceeded (${length}/${limit})`, dateInvalid: 'Please select a valid date.', invalidDateShouldBeFuture: 'Please choose today or a future date.', invalidTimeShouldBeFuture: 'Please choose a time at least one minute ahead.', @@ -643,7 +713,7 @@ export default { shouldUseYou ? `This chat is no longer active because you are no longer a member of the ${policyName} workspace.` : `This chat is no longer active because ${displayName} is no longer a member of the ${policyName} workspace.`, - [CONST.REPORT.ARCHIVE_REASON.POLICY_DELETED]: ({policyName}: ReportArchiveReasonsPolicyDeletedParams) => + [CONST.REPORT.ARCHIVE_REASON.POLICY_DELETED]: ({policyName}: ReportArchiveReasonsInvoiceReceiverPolicyDeletedParams) => `This chat is no longer active because ${policyName} is no longer an active workspace.`, [CONST.REPORT.ARCHIVE_REASON.INVOICE_RECEIVER_POLICY_DELETED]: ({policyName}: ReportArchiveReasonsInvoiceReceiverPolicyDeletedParams) => `This chat is no longer active because ${policyName} is no longer an active workspace.`, @@ -684,13 +754,13 @@ export default { dragAndDrop: 'Drag and drop your spreadsheet here, or choose a file below. Supported formats: .csv, .txt, .xls, and .xlsx.', chooseSpreadsheet: 'Select a spreadsheet file to import. Supported formats: .csv, .txt, .xls, and .xlsx.', fileContainsHeader: 'File contains column headers', - column: (name: string) => `Column ${name}`, - fieldNotMapped: (fieldName: string) => `Oops! A required field ("${fieldName}") hasn't been mapped. Please review and try again.`, - singleFieldMultipleColumns: (fieldName: string) => `Oops! You've mapped a single field ("${fieldName}") to multiple columns. Please review and try again.`, + column: ({name}: SpreadSheetColumnParams) => `Column ${name}`, + fieldNotMapped: ({fieldName}: SpreadFieldNameParams) => `Oops! A required field ("${fieldName}") hasn't been mapped. Please review and try again.`, + singleFieldMultipleColumns: ({fieldName}: SpreadFieldNameParams) => `Oops! You've mapped a single field ("${fieldName}") to multiple columns. Please review and try again.`, importSuccessfullTitle: 'Import successful', - importCategoriesSuccessfullDescription: (categories: number) => (categories > 1 ? `${categories} categories have been added.` : '1 category has been added.'), - importMembersSuccessfullDescription: (members: number) => (members > 1 ? `${members} members have been added.` : '1 member has been added.'), - importTagsSuccessfullDescription: (tags: number) => (tags > 1 ? `${tags} tags have been added.` : '1 tag has been added.'), + importCategoriesSuccessfullDescription: ({categories}: SpreadCategoriesParams) => (categories > 1 ? `${categories} categories have been added.` : '1 category has been added.'), + importMembersSuccessfullDescription: ({members}: ImportMembersSuccessfullDescriptionParams) => (members > 1 ? `${members} members have been added.` : '1 member has been added.'), + importTagsSuccessfullDescription: ({tags}: ImportTagsSuccessfullDescriptionParams) => (tags > 1 ? `${tags} tags have been added.` : '1 tag has been added.'), importFailedTitle: 'Import failed', importFailedDescription: 'Please ensure all fields are filled out correctly and try again. If the problem persists, please reach out to Concierge.', importDescription: 'Choose which fields to map from your spreadsheet by clicking the dropdown next to each imported column below.', @@ -729,7 +799,7 @@ export default { splitBill: 'Split expense', splitScan: 'Split receipt', splitDistance: 'Split distance', - paySomeone: (name: string) => `Pay ${name ?? 'someone'}`, + paySomeone: ({name}: PaySomeoneParams = {}) => `Pay ${name ?? 'someone'}`, assignTask: 'Assign task', header: 'Quick action', trackManual: 'Track expense', @@ -753,7 +823,7 @@ export default { original: 'Original', split: 'Split', splitExpense: 'Split expense', - paySomeone: ({name}: PaySomeoneParams) => `Pay ${name ?? 'someone'}`, + paySomeone: ({name}: PaySomeoneParams = {}) => `Pay ${name ?? 'someone'}`, expense: 'Expense', categorize: 'Categorize', share: 'Share', @@ -775,7 +845,7 @@ export default { receiptScanning: 'Receipt scanning...', receiptScanInProgress: 'Receipt scan in progress', receiptScanInProgressDescription: 'Receipt scan in progress. Check back later or enter the details now.', - receiptIssuesFound: (count: number) => `${count === 1 ? 'Issue' : 'Issues'} found`, + receiptIssuesFound: ({count}: DistanceRateOperationsParams) => `${count === 1 ? 'Issue' : 'Issues'} found`, fieldPending: 'Pending...', defaultRate: 'Default rate', receiptMissingDetails: 'Receipt missing details', @@ -825,9 +895,9 @@ export default { yourSplit: ({amount}: UserSplitParams) => `Your split ${amount}`, payerOwesAmount: ({payer, amount, comment}: PayerOwesAmountParams) => `${payer} owes ${amount}${comment ? ` for ${comment}` : ''}`, payerOwes: ({payer}: PayerOwesParams) => `${payer} owes: `, - payerPaidAmount: ({payer, amount}: PayerPaidAmountParams): string => `${payer ? `${payer} ` : ''}paid ${amount}`, + payerPaidAmount: ({payer, amount}: PayerPaidAmountParams) => `${payer ? `${payer} ` : ''}paid ${amount}`, payerPaid: ({payer}: PayerPaidParams) => `${payer} paid: `, - payerSpentAmount: ({payer, amount}: PayerPaidAmountParams): string => `${payer} spent ${amount}`, + payerSpentAmount: ({payer, amount}: PayerPaidAmountParams) => `${payer} spent ${amount}`, payerSpent: ({payer}: PayerPaidParams) => `${payer} spent: `, managerApproved: ({manager}: ManagerApprovedParams) => `${manager} approved:`, managerApprovedAmount: ({manager, amount}: ManagerApprovedAmountParams) => `${manager} approved ${amount}`, @@ -926,7 +996,7 @@ export default { unapprove: 'Unapprove', unapproveReport: 'Unapprove report', headsUp: 'Heads up!', - unapproveWithIntegrationWarning: (accountingIntegration: string) => + unapproveWithIntegrationWarning: ({accountingIntegration}: UnapproveWithIntegrationWarningParams) => `This report has already been exported to ${accountingIntegration}. Changes to this report in Expensify may lead to data discrepancies and Expensify Card reconciliation issues. Are you sure you want to unapprove this report?`, reimbursable: 'reimbursable', nonReimbursable: 'non-reimbursable', @@ -1310,15 +1380,16 @@ export default { availableSpend: 'Remaining limit', smartLimit: { name: 'Smart limit', - title: (formattedLimit: string) => `You can spend up to ${formattedLimit} on this card, and the limit will reset as your submitted expenses are approved.`, + title: ({formattedLimit}: ViolationsOverLimitParams) => `You can spend up to ${formattedLimit} on this card, and the limit will reset as your submitted expenses are approved.`, }, fixedLimit: { name: 'Fixed limit', - title: (formattedLimit: string) => `You can spend up to ${formattedLimit} on this card, and then it will deactivate.`, + title: ({formattedLimit}: ViolationsOverLimitParams) => `You can spend up to ${formattedLimit} on this card, and then it will deactivate.`, }, monthlyLimit: { name: 'Monthly limit', - title: (formattedLimit: string) => `You can spend up to ${formattedLimit} on this card per month. The limit will reset on the 1st day of each calendar month.`, + title: ({formattedLimit}: ViolationsOverLimitParams) => + `You can spend up to ${formattedLimit} on this card per month. The limit will reset on the 1st day of each calendar month.`, }, virtualCardNumber: 'Virtual card number', physicalCardNumber: 'Physical card number', @@ -1519,7 +1590,7 @@ export default { }, }, reportDetailsPage: { - inWorkspace: ({policyName}) => `in ${policyName}`, + inWorkspace: ({policyName}: ReportPolicyNameParams) => `in ${policyName}`, }, reportDescriptionPage: { roomDescription: 'Room description', @@ -1532,7 +1603,7 @@ export default { groupChat: { lastMemberTitle: 'Heads up!', lastMemberWarning: "Since you're the last person here, leaving will make this chat inaccessible to all users. Are you sure you want to leave?", - defaultReportName: ({displayName}: {displayName: string}) => `${displayName}'s group chat`, + defaultReportName: ({displayName}: ReportArchiveReasonsClosedParams) => `${displayName}'s group chat`, }, languagePage: { language: 'Language', @@ -1664,7 +1735,7 @@ export default { dateShouldBeBefore: ({dateString}: DateShouldBeBeforeParams) => `Date should be before ${dateString}.`, dateShouldBeAfter: ({dateString}: DateShouldBeAfterParams) => `Date should be after ${dateString}.`, hasInvalidCharacter: 'Name can only include Latin characters.', - incorrectZipFormat: (zipFormat?: string) => `Incorrect zip code format.${zipFormat ? ` Acceptable format: ${zipFormat}` : ''}`, + incorrectZipFormat: ({zipFormat}: IncorrectZipFormatParams = {}) => `Incorrect zip code format.${zipFormat ? ` Acceptable format: ${zipFormat}` : ''}`, }, }, resendValidationForm: { @@ -1681,8 +1752,8 @@ export default { succesfullyUnlinkedLogin: 'Secondary login successfully unlinked!', }, emailDeliveryFailurePage: { - ourEmailProvider: (user: OurEmailProviderParams) => - `Our email provider has temporarily suspended emails to ${user.login} due to delivery issues. To unblock your login, please follow these steps:`, + ourEmailProvider: ({login}: OurEmailProviderParams) => + `Our email provider has temporarily suspended emails to ${login} due to delivery issues. To unblock your login, please follow these steps:`, confirmThat: ({login}: ConfirmThatParams) => `Confirm that ${login} is spelled correctly and is a real, deliverable email address. `, emailAliases: 'Email aliases such as "expenses@domain.com" must have access to their own email inbox for it to be a valid Expensify login.', ensureYourEmailClient: 'Ensure your email client allows expensify.com emails. ', @@ -2199,7 +2270,7 @@ export default { testTransactions: 'Test transactions', issueAndManageCards: 'Issue and manage cards', reconcileCards: 'Reconcile cards', - selected: ({selectedNumber}) => `${selectedNumber} selected`, + selected: ({selectedNumber}: SelectedNumberParams) => `${selectedNumber} selected`, settlementFrequency: 'Settlement frequency', deleteConfirmation: 'Are you sure you want to delete this workspace?', unavailable: 'Unavailable workspace', @@ -2218,7 +2289,7 @@ export default { `You have been invited to ${workspaceName || 'a workspace'}! Download the Expensify mobile app at use.expensify.com/download to start tracking your expenses.`, subscription: 'Subscription', markAsExported: 'Mark as manually entered', - exportIntegrationSelected: (connectionName: ConnectionName) => `Export to ${CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[connectionName]}`, + exportIntegrationSelected: ({connectionName}: ExportIntegrationSelectedParams) => `Export to ${CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[connectionName]}`, letsDoubleCheck: "Let's double check that everything looks right.", lineItemLevel: 'Line-item level', reportLevel: 'Report level', @@ -2235,13 +2306,13 @@ export default { createNewConnection: 'Create new connection', reuseExistingConnection: 'Reuse existing connection', existingConnections: 'Existing connections', - lastSyncDate: (connectionName: string, formattedDate: string) => `${connectionName} - Last synced ${formattedDate}`, - authenticationError: (connectionName: string) => `Can’t connect to ${connectionName} due to an authentication error.`, + lastSyncDate: ({connectionName, formattedDate}: LastSyncDateParams) => `${connectionName} - Last synced ${formattedDate}`, + authenticationError: ({connectionName}: AuthenticationErrorParams) => `Can’t connect to ${connectionName} due to an authentication error.`, learnMore: 'Learn more.', memberAlternateText: 'Members can submit and approve reports.', adminAlternateText: 'Admins have full edit access to all reports and workspace settings.', auditorAlternateText: 'Auditors can view and comment on reports.', - roleName: (role?: string): string => { + roleName: ({role}: OptionalParam = {}) => { switch (role) { case CONST.POLICY.ROLE.ADMIN: return 'Admin'; @@ -2366,8 +2437,8 @@ export default { accountsSwitchDescription: 'Enabled categories will be available for members to select when creating their expenses.', trackingCategories: 'Tracking categories', trackingCategoriesDescription: 'Choose how to handle Xero tracking categories in Expensify.', - mapTrackingCategoryTo: ({categoryName}) => `Map Xero ${categoryName} to`, - mapTrackingCategoryToDescription: ({categoryName}) => `Choose where to map ${categoryName} when exporting to Xero.`, + mapTrackingCategoryTo: ({categoryName}: CategoryNameParams) => `Map Xero ${categoryName} to`, + mapTrackingCategoryToDescription: ({categoryName}: CategoryNameParams) => `Choose where to map ${categoryName} when exporting to Xero.`, customers: 'Re-bill customers', customersDescription: 'Choose whether to re-bill customers in Expensify. Your Xero customer contacts can be tagged to expenses, and will export to Xero as a sales invoice.', taxesDescription: 'Choose how to handle Xero taxes in Expensify.', @@ -2464,7 +2535,7 @@ export default { }, creditCardAccount: 'Credit card account', defaultVendor: 'Default vendor', - defaultVendorDescription: (isReimbursable: boolean): string => + defaultVendorDescription: ({isReimbursable}: DefaultVendorDescriptionParams) => `Set a default vendor that will apply to ${isReimbursable ? '' : 'non-'}reimbursable expenses that don't have a matching vendor in Sage Intacct.`, exportDescription: 'Configure how Expensify data exports to Sage Intacct.', exportPreferredExporterNote: @@ -2678,12 +2749,12 @@ export default { importJobs: 'Import projects', customers: 'customers', jobs: 'projects', - label: (importFields: string[], importType: string) => `${importFields.join(' and ')}, ${importType}`, + label: ({importFields, importType}: CustomersOrJobsLabelParams) => `${importFields.join(' and ')}, ${importType}`, }, importTaxDescription: 'Import tax groups from NetSuite.', importCustomFields: { chooseOptionBelow: 'Choose an option below:', - requiredFieldError: (fieldName: string) => `Please enter the ${fieldName}`, + requiredFieldError: ({fieldName}: RequiredFieldParams) => `Please enter the ${fieldName}`, customSegments: { title: 'Custom segments/records', addText: 'Add custom segment/record', @@ -2724,7 +2795,7 @@ export default { customRecordMappingTitle: 'How should this custom record be displayed in Expensify?', }, errors: { - uniqueFieldError: (fieldName: string) => `A custom segment/record with this ${fieldName?.toLowerCase()} already exists.`, + uniqueFieldError: ({fieldName}: RequiredFieldParams) => `A custom segment/record with this ${fieldName?.toLowerCase()} already exists.`, }, }, customLists: { @@ -2758,18 +2829,18 @@ export default { [CONST.INTEGRATION_ENTITY_MAP_TYPES.NETSUITE_DEFAULT]: { label: 'NetSuite employee default', description: 'Not imported into Expensify, applied on export', - footerContent: (importField: string) => + footerContent: ({importField}: ImportFieldParams) => `If you use ${importField} in NetSuite, we'll apply the default set on the employee record upon export to Expense Report or Journal Entry.`, }, [CONST.INTEGRATION_ENTITY_MAP_TYPES.TAG]: { label: 'Tags', description: 'Line-item level', - footerContent: (importField: string) => `${startCase(importField)} will be selectable for each individual expense on an employee's report.`, + footerContent: ({importField}: ImportFieldParams) => `${startCase(importField)} will be selectable for each individual expense on an employee's report.`, }, [CONST.INTEGRATION_ENTITY_MAP_TYPES.REPORT_FIELD]: { label: 'Report fields', description: 'Report level', - footerContent: (importField: string) => `${startCase(importField)} selection will apply to all expense on an employee's report.`, + footerContent: ({importField}: ImportFieldParams) => `${startCase(importField)} selection will apply to all expense on an employee's report.`, }, }, }, @@ -2800,8 +2871,8 @@ export default { addAUserDefinedDimension: 'Add a user-defined dimension', detailedInstructionsLink: 'View detailed instructions', detailedInstructionsRestOfSentence: ' on adding user-defined dimensions.', - userDimensionsAdded: (dimensionsCount: number) => `${dimensionsCount} ${Str.pluralize('UDD', `UDDs`, dimensionsCount)} added`, - mappingTitle: (mappingName: SageIntacctMappingName): string => { + userDimensionsAdded: ({dimensionsCount}: DimensionsCountParams) => `${dimensionsCount} ${Str.pluralize('UDD', `UDDs`, dimensionsCount)} added`, + mappingTitle: ({mappingName}: IntacctMappingTitleParams) => { switch (mappingName) { case CONST.SAGE_INTACCT_CONFIG.MAPPINGS.DEPARTMENTS: return 'departments'; @@ -2835,7 +2906,7 @@ export default { }, yourCardProvider: `Who's your card provider?`, enableFeed: { - title: (provider: string) => `Enable your ${provider} feed`, + title: ({provider}: GoBackMessageParams) => `Enable your ${provider} feed`, heading: 'We have a direct integration with your card issuer and can import your transaction data into Expensify quickly and accurately.\n\nTo get started, simply:', visa: `1. Visit [this help article](${CONST.COMPANY_CARDS_HELP}) for detailed instructionson how to set up your Visa Commercial Cards.\n\n2. [Contact your bank](${CONST.COMPANY_CARDS_HELP}) to verify they support a custom feed for your program, and ask them toenable it.\n\n3. *Once the feed is enabled and you have its details, continue to the next screen.*`, amex: `1. Visit [this help article](${CONST.COMPANY_CARDS_HELP}) to find out if American Express can enable a custom feed for your program.\n\n2. Once the feed is enabled, Amex will send you a production letter.\n\n3. *Once you have the feed information, continue to the next screen.*`, @@ -2882,7 +2953,7 @@ export default { card: 'Card', startTransactionDate: 'Start transaction date', cardName: 'Card name', - assignedYouCard: (assigner: string) => `${assigner} assigned you a company card! Imported transactions will appear in this chat.`, + assignedYouCard: ({assigner}: AssignedYouCardParams) => `${assigner} assigned you a company card! Imported transactions will appear in this chat.`, chooseCardFeed: 'Choose card feed', }, expensifyCard: { @@ -2928,20 +2999,21 @@ export default { deactivate: 'Deactivate card', changeCardLimit: 'Change card limit', changeLimit: 'Change limit', - smartLimitWarning: (limit: string) => `If you change this card’s limit to ${limit}, new transactions will be declined until you approve more expenses on the card.`, - monthlyLimitWarning: (limit: string) => `If you change this card’s limit to ${limit}, new transactions will be declined until next month.`, - fixedLimitWarning: (limit: string) => `If you change this card’s limit to ${limit}, new transactions will be declined.`, + smartLimitWarning: ({limit}: CharacterLimitParams) => + `If you change this card’s limit to ${limit}, new transactions will be declined until you approve more expenses on the card.`, + monthlyLimitWarning: ({limit}: CharacterLimitParams) => `If you change this card’s limit to ${limit}, new transactions will be declined until next month.`, + fixedLimitWarning: ({limit}: CharacterLimitParams) => `If you change this card’s limit to ${limit}, new transactions will be declined.`, changeCardLimitType: 'Change card limit type', changeLimitType: 'Change limit type', - changeCardSmartLimitTypeWarning: (limit: string) => + changeCardSmartLimitTypeWarning: ({limit}: CharacterLimitParams) => `If you change this card's limit type to Smart Limit, new transactions will be declined because the ${limit} unapproved limit has already been reached.`, - changeCardMonthlyLimitTypeWarning: (limit: string) => + changeCardMonthlyLimitTypeWarning: ({limit}: CharacterLimitParams) => `If you change this card's limit type to Monthly, new transactions will be declined because the ${limit} monthly limit has already been reached.`, addShippingDetails: 'Add shipping details', - issuedCard: (assignee: string) => `issued ${assignee} an Expensify Card! The card will arrive in 2-3 business days.`, - issuedCardNoShippingDetails: (assignee: string) => `issued ${assignee} an Expensify Card! The card will be shipped once shipping details are added.`, + issuedCard: ({assignee}: AssigneeParams) => `issued ${assignee} an Expensify Card! The card will arrive in 2-3 business days.`, + issuedCardNoShippingDetails: ({assignee}: AssigneeParams) => `issued ${assignee} an Expensify Card! The card will be shipped once shipping details are added.`, issuedCardVirtual: ({assignee, link}: IssueVirtualCardParams) => `issued ${assignee} a virtual ${link}! The card can be used right away.`, - addedShippingDetails: (assignee: string) => `${assignee} added shipping details. Expensify Card will arrive in 2-3 business days.`, + addedShippingDetails: ({assignee}: AssigneeParams) => `${assignee} added shipping details. Expensify Card will arrive in 2-3 business days.`, }, categories: { deleteCategories: 'Delete categories', @@ -3040,8 +3112,8 @@ export default { cardNumber: 'Card number', cardholder: 'Cardholder', cardName: 'Card name', - integrationExport: (integration: string, type: string) => `${integration} ${type} export`, - integrationExportTitleFirstPart: (integration: string) => `Choose the ${integration} account where transactions should be exported. Select a different`, + integrationExport: ({integration, type}: IntegrationExportParams) => `${integration} ${type} export`, + integrationExportTitleFirstPart: ({integration}: IntegrationExportParams) => `Choose the ${integration} account where transactions should be exported. Select a different`, integrationExportTitleLinkPart: 'export option', integrationExportTitleSecondPart: 'to change the available accounts.', lastUpdated: 'Last updated', @@ -3074,7 +3146,7 @@ export default { giveItNameInstruction: 'Give the card a name that sets it apart from the others.', updating: 'Updating...', noAccountsFound: 'No accounts found', - noAccountsFoundDescription: (connection: string) => `Please add the account in ${connection} and sync the connection again.`, + noAccountsFoundDescription: ({connection}: ConnectionParams) => `Please add the account in ${connection} and sync the connection again.`, }, workflows: { title: 'Workflows', @@ -3195,7 +3267,7 @@ export default { tagRules: 'Tag rules', approverDescription: 'Approver', importTags: 'Import tags', - importedTagsMessage: (columnCounts: number) => + importedTagsMessage: ({columnCounts}: ImportedTagsMessageParams) => `We found *${columnCounts} columns* in your spreadsheet. Select *Name* next to the column that contains tags names. You can also select *Enabled* next to the column that sets tags status.`, }, taxes: { @@ -3218,7 +3290,7 @@ export default { updateTaxClaimableFailureMessage: 'The reclaimable portion must be less than the distance rate amount.', }, deleteTaxConfirmation: 'Are you sure you want to delete this tax?', - deleteMultipleTaxConfirmation: ({taxAmount}) => `Are you sure you want to delete ${taxAmount} taxes?`, + deleteMultipleTaxConfirmation: ({taxAmount}: TaxAmountParams) => `Are you sure you want to delete ${taxAmount} taxes?`, actions: { delete: 'Delete rate', deleteMultiple: 'Delete rates', @@ -3261,7 +3333,7 @@ export default { removeWorkspaceMemberButtonTitle: 'Remove from workspace', removeGroupMemberButtonTitle: 'Remove from group', removeRoomMemberButtonTitle: 'Remove from chat', - removeMemberPrompt: ({memberName}: {memberName: string}) => `Are you sure you want to remove ${memberName}?`, + removeMemberPrompt: ({memberName}: RemoveMemberPromptParams) => `Are you sure you want to remove ${memberName}?`, removeMemberTitle: 'Remove member', transferOwner: 'Transfer owner', makeMember: 'Make member', @@ -3274,7 +3346,7 @@ export default { genericRemove: 'There was a problem removing that workspace member.', }, addedWithPrimary: 'Some members were added with their primary logins.', - invitedBySecondaryLogin: ({secondaryLogin}) => `Added by secondary login ${secondaryLogin}.`, + invitedBySecondaryLogin: ({secondaryLogin}: SecondaryLoginParams) => `Added by secondary login ${secondaryLogin}.`, membersListTitle: 'Directory of all workspace members.', importMembers: 'Import members', }, @@ -3322,8 +3394,8 @@ export default { xero: 'Xero', netsuite: 'NetSuite', intacct: 'Sage Intacct', - connectionName: (integration: ConnectionName) => { - switch (integration) { + connectionName: ({connectionName}: ConnectionNameParams) => { + switch (connectionName) { case CONST.POLICY.CONNECTIONS.NAME.QBO: return 'Quickbooks Online'; case CONST.POLICY.CONNECTIONS.NAME.XERO: @@ -3340,21 +3412,22 @@ export default { errorODIntegration: "There's an error with a connection that's been set up in Expensify Classic. ", goToODToFix: 'Go to Expensify Classic to fix this issue.', setup: 'Connect', - lastSync: (relativeDate: string) => `Last synced ${relativeDate}`, + lastSync: ({relativeDate}: LastSyncAccountingParams) => `Last synced ${relativeDate}`, import: 'Import', export: 'Export', advanced: 'Advanced', other: 'Other integrations', syncNow: 'Sync now', disconnect: 'Disconnect', - disconnectTitle: (integration?: ConnectionName): string => { - const integrationName = integration && CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[integration] ? CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[integration] : 'integration'; + disconnectTitle: ({connectionName}: OptionalParam = {}) => { + const integrationName = + connectionName && CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[connectionName] ? CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[connectionName] : 'integration'; return `Disconnect ${integrationName}`; }, - connectTitle: (integrationToConnect: ConnectionName): string => `Connect ${CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[integrationToConnect] ?? 'accounting integration'}`, + connectTitle: ({connectionName}: ConnectionNameParams) => `Connect ${CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[connectionName] ?? 'accounting integration'}`, - syncError: (integration?: ConnectionName): string => { - switch (integration) { + syncError: ({connectionName}: ConnectionNameParams) => { + switch (connectionName) { case CONST.POLICY.CONNECTIONS.NAME.QBO: return "Can't connect to QuickBooks Online."; case CONST.POLICY.CONNECTIONS.NAME.XERO: @@ -3380,20 +3453,18 @@ export default { [CONST.INTEGRATION_ENTITY_MAP_TYPES.REPORT_FIELD]: 'Imported as report fields', [CONST.INTEGRATION_ENTITY_MAP_TYPES.NETSUITE_DEFAULT]: 'NetSuite employee default', }, - disconnectPrompt: (currentIntegration?: ConnectionName): string => { + disconnectPrompt: ({connectionName}: OptionalParam = {}) => { const integrationName = - currentIntegration && CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[currentIntegration] - ? CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[currentIntegration] - : 'this integration'; + connectionName && CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[connectionName] ? CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[connectionName] : 'this integration'; return `Are you sure you want to disconnect ${integrationName}?`; }, - connectPrompt: (integrationToConnect: ConnectionName): string => + connectPrompt: ({connectionName}: ConnectionNameParams) => `Are you sure you want to connect ${ - CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[integrationToConnect] ?? 'this accounting integration' + CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[connectionName] ?? 'this accounting integration' }? This will remove any existing acounting connections.`, enterCredentials: 'Enter your credentials', connections: { - syncStageName: (stage: PolicyConnectionSyncStage) => { + syncStageName: ({stage}: SyncStageNameConnectionsParams) => { switch (stage) { case 'quickbooksOnlineImportCustomers': return 'Importing customers'; @@ -3530,7 +3601,7 @@ export default { chooseBankAccount: 'Choose the bank account that your Expensify Card payments will be reconciled against.', accountMatches: 'Make sure this account matches your ', settlementAccount: 'Expensify Card settlement account ', - reconciliationWorks: (lastFourPAN: string) => `(ending in ${lastFourPAN}) so Continuous Reconciliation works properly.`, + reconciliationWorks: ({lastFourPAN}: ReconciliationWorksParams) => `(ending in ${lastFourPAN}) so Continuous Reconciliation works properly.`, }, }, export: { @@ -3590,7 +3661,10 @@ export default { rate: 'Rate', addRate: 'Add rate', trackTax: 'Track tax', - deleteRates: ({count}: DistanceRateOperationsParams) => `Delete ${Str.pluralize('rate', 'rates', count)}`, + deleteRates: () => ({ + one: 'Delete rate', + other: 'Delete rates', + }), enableRates: ({count}: DistanceRateOperationsParams) => `Enable ${Str.pluralize('rate', 'rates', count)}`, disableRates: ({count}: DistanceRateOperationsParams) => `Disable ${Str.pluralize('rate', 'rates', count)}`, enableRate: 'Enable rate', @@ -3659,19 +3733,19 @@ export default { amountOwedText: 'This account has an outstanding balance from a previous month.\n\nDo you want to clear the balance and take over billing of this workspace?', ownerOwesAmountTitle: 'Outstanding balance', ownerOwesAmountButtonText: 'Transfer balance', - ownerOwesAmountText: ({email, amount}) => + ownerOwesAmountText: ({email, amount}: OwnerOwesAmountParams) => `The account owning this workspace (${email}) has an outstanding balance from a previous month.\n\nDo you want to transfer this amount (${amount}) in order to take over billing for this workspace? Your payment card will be charged immediately.`, subscriptionTitle: 'Take over annual subscription', subscriptionButtonText: 'Transfer subscription', - subscriptionText: ({usersCount, finalCount}) => + subscriptionText: ({usersCount, finalCount}: ChangeOwnerSubscriptionParams) => `Taking over this workspace will merge its annual subscription with your current subscription. This will increase your subscription size by ${usersCount} members making your new subscription size ${finalCount}. Would you like to continue?`, duplicateSubscriptionTitle: 'Duplicate subscription alert', duplicateSubscriptionButtonText: 'Continue', - duplicateSubscriptionText: ({email, workspaceName}) => + duplicateSubscriptionText: ({email, workspaceName}: ChangeOwnerDuplicateSubscriptionParams) => `It looks like you may be trying to take over billing for ${email}'s workspaces, but to do that, you need to be an admin on all their workspaces first.\n\nClick "Continue" if you only want to take over billing for the workspace ${workspaceName}.\n\nIf you want to take over billing for their entire subscription, please have them add you as an admin to all their workspaces first before taking over billing.`, hasFailedSettlementsTitle: 'Cannot transfer ownership', hasFailedSettlementsButtonText: 'Got it', - hasFailedSettlementsText: ({email}) => + hasFailedSettlementsText: ({email}: ChangeOwnerHasFailedSettlementsParams) => `You can't take over billing because ${email} has an overdue expensify Expensify Card settlement. Please ask them to reach out to concierge@expensify.com to resolve the issue. Then, you can take over billing for this workspace.`, failedToClearBalanceTitle: 'Failed to clear balance', failedToClearBalanceButtonText: 'OK', @@ -3685,7 +3759,7 @@ export default { }, exportAgainModal: { title: 'Careful!', - description: (reportName: string, connectionName: ConnectionName) => + description: ({reportName, connectionName}: ExportAgainModalDescriptionParams) => `The following reports have already been exported to ${CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[connectionName]}:\n\n${reportName}\n\nAre you sure you want to export them again?`, confirmText: 'Yes, export again', cancelText: 'Cancel', @@ -3748,7 +3822,7 @@ export default { upgradeToUnlock: 'Unlock this feature', completed: { headline: `You've upgraded your workspace!`, - successMessage: (policyName: string) => `You've successfully upgraded your ${policyName} workspace to the Control plan!`, + successMessage: ({policyName}: ReportPolicyNameParams) => `You've successfully upgraded your ${policyName} workspace to the Control plan!`, viewSubscription: 'View your subscription', moreDetails: 'for more details.', gotIt: 'Got it, thanks', @@ -3756,8 +3830,8 @@ export default { }, restrictedAction: { restricted: 'Restricted', - actionsAreCurrentlyRestricted: ({workspaceName}) => `Actions on the ${workspaceName} workspace are currently restricted`, - workspaceOwnerWillNeedToAddOrUpdatePaymentCard: ({workspaceOwnerName}) => + actionsAreCurrentlyRestricted: ({workspaceName}: ActionsAreCurrentlyRestricted) => `Actions on the ${workspaceName} workspace are currently restricted`, + workspaceOwnerWillNeedToAddOrUpdatePaymentCard: ({workspaceOwnerName}: WorkspaceOwnerWillNeedToAddOrUpdatePaymentCardParams) => `Workspace owner, ${workspaceOwnerName} will need to add or update the payment card on file to unlock new workspace activity.`, youWillNeedToAddOrUpdatePaymentCard: "You'll need to add or update the payment card on file to unlock new workspace activity.", addPaymentCardToUnlock: 'Add a payment card to unlock!', @@ -3778,7 +3852,7 @@ export default { maxAge: 'Max age', maxExpenseAge: 'Max expense age', maxExpenseAgeDescription: 'Flag spend older than a specific number of days.', - maxExpenseAgeDays: (age: number) => `${age} ${Str.pluralize('day', 'days', age)}`, + maxExpenseAgeDays: ({age}: AgeParams) => `${age} ${Str.pluralize('day', 'days', age)}`, billableDefault: 'Billable default', billableDefaultDescription: 'Choose whether cash and credit card expenses should be billable by default. Billable expenses are enabled or disabled in', billable: 'Billable', @@ -3815,26 +3889,26 @@ export default { randomReportAuditDescription: 'Require that some reports be manually approved, even if eligible for auto-approval.', autoPayApprovedReportsTitle: 'Auto-pay approved reports', autoPayApprovedReportsSubtitle: 'Configure which expense reports are eligible for auto-pay.', - autoPayApprovedReportsLimitError: (currency?: string) => `Please enter an amount less than ${currency ?? ''}20,000`, + autoPayApprovedReportsLimitError: ({currency}: AutoPayApprovedReportsLimitErrorParams = {}) => `Please enter an amount less than ${currency ?? ''}20,000`, autoPayApprovedReportsLockedSubtitle: 'Go to more features and enable workflows, then add payments to unlock this feature.', autoPayReportsUnderTitle: 'Auto-pay reports under', autoPayReportsUnderDescription: 'Fully compliant expense reports under this amount will be automatically paid. ', unlockFeatureGoToSubtitle: 'Go to', - unlockFeatureEnableWorkflowsSubtitle: (featureName: string) => `and enable workflows, then add ${featureName} to unlock this feature.`, - enableFeatureSubtitle: (featureName: string) => `and enable ${featureName} to unlock this feature.`, + unlockFeatureEnableWorkflowsSubtitle: ({featureName}: FeatureNameParams) => `and enable workflows, then add ${featureName} to unlock this feature.`, + enableFeatureSubtitle: ({featureName}: FeatureNameParams) => `and enable ${featureName} to unlock this feature.`, }, categoryRules: { title: 'Category rules', approver: 'Approver', requireDescription: 'Require description', descriptionHint: 'Description hint', - descriptionHintDescription: (categoryName: string) => + descriptionHintDescription: ({categoryName}: CategoryNameParams) => `Remind employees to provide additional information for “${categoryName}” spend. This hint appears in the description field on expenses.`, descriptionHintLabel: 'Hint', descriptionHintSubtitle: 'Pro-tip: The shorter the better!', maxAmount: 'Max amount', flagAmountsOver: 'Flag amounts over', - flagAmountsOverDescription: (categoryName) => `Applies to the category “${categoryName}”.`, + flagAmountsOverDescription: ({categoryName}: CategoryNameParams) => `Applies to the category “${categoryName}”.`, flagAmountsOverSubtitle: 'This overrides the max amount for all expenses.', expenseLimitTypes: { expense: 'Individual expense', @@ -3844,7 +3918,7 @@ export default { }, requireReceiptsOver: 'Require receipts over', requireReceiptsOverList: { - default: (defaultAmount: string) => `${defaultAmount} ${CONST.DOT_SEPARATOR} Default`, + default: ({defaultAmount}: DefaultAmountParams) => `${defaultAmount} ${CONST.DOT_SEPARATOR} Default`, never: 'Never require receipts', always: 'Always require receipts', }, @@ -3907,8 +3981,8 @@ export default { }, }, workspaceActions: { - renamedWorkspaceNameAction: ({oldName, newName}) => `updated the name of this workspace from ${oldName} to ${newName}`, - removedFromApprovalWorkflow: ({submittersNames}: {submittersNames: string[]}) => { + renamedWorkspaceNameAction: ({oldName, newName}: RenamedRoomActionParams) => `updated the name of this workspace from ${oldName} to ${newName}`, + removedFromApprovalWorkflow: ({submittersNames}: RemovedFromApprovalWorkflowParams) => { let joinedNames = ''; if (submittersNames.length === 1) { joinedNames = submittersNames[0]; @@ -3961,7 +4035,7 @@ export default { deleteConfirmation: 'Are you sure you want to delete this task?', }, statementPage: { - title: (year, monthName) => `${monthName} ${year} statement`, + title: ({year, monthName}: StatementTitleParams) => `${monthName} ${year} statement`, generatingPDF: "We're generating your PDF right now. Please check back soon!", }, keyboardShortcutsPage: { @@ -4011,8 +4085,8 @@ export default { filtersHeader: 'Filters', filters: { date: { - before: (date?: string) => `Before ${date ?? ''}`, - after: (date?: string) => `After ${date ?? ''}`, + before: ({date}: OptionalParam = {}) => `Before ${date ?? ''}`, + after: ({date}: OptionalParam = {}) => `After ${date ?? ''}`, }, status: 'Status', keyword: 'Keyword', @@ -4022,9 +4096,9 @@ export default { pinned: 'Pinned', unread: 'Unread', amount: { - lessThan: (amount?: string) => `Less than ${amount ?? ''}`, - greaterThan: (amount?: string) => `Greater than ${amount ?? ''}`, - between: (greaterThan: string, lessThan: string) => `Between ${greaterThan} and ${lessThan}`, + lessThan: ({amount}: OptionalParam = {}) => `Less than ${amount ?? ''}`, + greaterThan: ({amount}: OptionalParam = {}) => `Greater than ${amount ?? ''}`, + between: ({greaterThan, lessThan}: FiltersAmountBetweenParams) => `Between ${greaterThan} and ${lessThan}`, }, current: 'Current', past: 'Past', @@ -4144,7 +4218,7 @@ export default { nonReimbursableLink: 'View company card expenses.', pending: ({label}: ExportedToIntegrationParams) => `started exporting this report to ${label}...`, }, - integrationsMessage: (errorMessage: string, label: string) => `failed to export this report to ${label} ("${errorMessage}").`, + integrationsMessage: ({errorMessage, label}: IntegrationSyncFailedParams) => `failed to export this report to ${label} ("${errorMessage}").`, managerAttachReceipt: `added a receipt`, managerDetachReceipt: `removed a receipt`, markedReimbursed: ({amount, currency}: MarkedReimbursedParams) => `paid ${currency}${amount} elsewhere`, @@ -4161,10 +4235,10 @@ export default { stripePaid: ({amount, currency}: StripePaidParams) => `paid ${currency}${amount}`, takeControl: `took control`, unapproved: ({amount, currency}: UnapprovedParams) => `unapproved ${currency}${amount}`, - integrationSyncFailed: (label: string, errorMessage: string) => `failed to sync with ${label} ("${errorMessage}")`, - addEmployee: (email: string, role: string) => `added ${email} as ${role === 'user' ? 'member' : 'admin'}`, - updateRole: (email: string, currentRole: string, newRole: string) => `updated the role of ${email} from ${currentRole} to ${newRole}`, - removeMember: (email: string, role: string) => `removed ${role} ${email}`, + integrationSyncFailed: ({label, errorMessage}: IntegrationSyncFailedParams) => `failed to sync with ${label} ("${errorMessage}")`, + addEmployee: ({email, role}: AddEmployeeParams) => `added ${email} as ${role === 'user' ? 'member' : 'admin'}`, + updateRole: ({email, currentRole, newRole}: UpdateRoleParams) => `updated the role of ${email} from ${currentRole} to ${newRole}`, + removeMember: ({email, role}: AddEmployeeParams) => `removed ${role} ${email}`, }, }, }, @@ -4385,7 +4459,7 @@ export default { allTagLevelsRequired: 'All tags required', autoReportedRejectedExpense: ({rejectReason, rejectedBy}: ViolationsAutoReportedRejectedExpenseParams) => `${rejectedBy} rejected this expense with the comment "${rejectReason}"`, billableExpense: 'Billable no longer valid', - cashExpenseWithNoReceipt: ({formattedLimit}: ViolationsCashExpenseWithNoReceiptParams) => `Receipt required${formattedLimit ? ` over ${formattedLimit}` : ''}`, + cashExpenseWithNoReceipt: ({formattedLimit}: ViolationsCashExpenseWithNoReceiptParams = {}) => `Receipt required${formattedLimit ? ` over ${formattedLimit}` : ''}`, categoryOutOfPolicy: 'Category no longer valid', conversionSurcharge: ({surcharge}: ViolationsConversionSurchargeParams) => `Applied ${surcharge}% conversion surcharge`, customUnitOutOfPolicy: 'Rate not valid for this workspace', @@ -4396,8 +4470,8 @@ export default { maxAge: ({maxAge}: ViolationsMaxAgeParams) => `Date older than ${maxAge} days`, missingCategory: 'Missing category', missingComment: 'Description required for selected category', - missingTag: ({tagName}: ViolationsMissingTagParams) => `Missing ${tagName ?? 'tag'}`, - modifiedAmount: ({type, displayPercentVariance}: ViolationsModifiedAmountParams): string => { + missingTag: ({tagName}: ViolationsMissingTagParams = {}) => `Missing ${tagName ?? 'tag'}`, + modifiedAmount: ({type, displayPercentVariance}: ViolationsModifiedAmountParams) => { switch (type) { case 'distance': return 'Amount differs from calculated distance'; @@ -4445,10 +4519,10 @@ export default { return ''; }, smartscanFailed: 'Receipt scanning failed. Enter details manually.', - someTagLevelsRequired: ({tagName}: ViolationsTagOutOfPolicyParams) => `Missing ${tagName ?? 'Tag'}`, - tagOutOfPolicy: ({tagName}: ViolationsTagOutOfPolicyParams) => `${tagName ?? 'Tag'} no longer valid`, + someTagLevelsRequired: ({tagName}: ViolationsTagOutOfPolicyParams = {}) => `Missing ${tagName ?? 'Tag'}`, + tagOutOfPolicy: ({tagName}: ViolationsTagOutOfPolicyParams = {}) => `${tagName ?? 'Tag'} no longer valid`, taxAmountChanged: 'Tax amount was modified', - taxOutOfPolicy: ({taxName}: ViolationsTaxOutOfPolicyParams) => `${taxName ?? 'Tax'} no longer valid`, + taxOutOfPolicy: ({taxName}: ViolationsTaxOutOfPolicyParams = {}) => `${taxName ?? 'Tax'} no longer valid`, taxRateChanged: 'Tax rate was modified', taxRequired: 'Missing tax rate', none: 'None', @@ -4465,7 +4539,7 @@ export default { hold: 'Hold', }, reportViolations: { - [CONST.REPORT_VIOLATIONS.FIELD_REQUIRED]: (fieldName: string) => `${fieldName} is required`, + [CONST.REPORT_VIOLATIONS.FIELD_REQUIRED]: ({fieldName}: RequiredFieldParams) => `${fieldName} is required`, }, violationDismissal: { rter: { @@ -4520,12 +4594,12 @@ export default { authenticatePaymentCard: 'Authenticate payment card', mobileReducedFunctionalityMessage: 'You can’t make changes to your subscription in the mobile app.', badge: { - freeTrial: ({numOfDays}) => `Free trial: ${numOfDays} ${numOfDays === 1 ? 'day' : 'days'} left`, + freeTrial: ({numOfDays}: BadgeFreeTrialParams) => `Free trial: ${numOfDays} ${numOfDays === 1 ? 'day' : 'days'} left`, }, billingBanner: { policyOwnerAmountOwed: { title: 'Your payment info is outdated', - subtitle: ({date}) => `Update your payment card by ${date} to continue using all of your favorite features.`, + subtitle: ({date}: BillingBannerSubtitleWithDateParams) => `Update your payment card by ${date} to continue using all of your favorite features.`, }, policyOwnerAmountOwedOverdue: { title: 'Your payment info is outdated', @@ -4533,7 +4607,7 @@ export default { }, policyOwnerUnderInvoicing: { title: 'Your payment info is outdated', - subtitle: ({date}) => `Your payment is past due. Please pay your invoice by ${date} to avoid service interruption.`, + subtitle: ({date}: BillingBannerSubtitleWithDateParams) => `Your payment is past due. Please pay your invoice by ${date} to avoid service interruption.`, }, policyOwnerUnderInvoicingOverdue: { title: 'Your payment info is outdated', @@ -4541,22 +4615,22 @@ export default { }, billingDisputePending: { title: 'Your card couldn’t be charged', - subtitle: ({amountOwed, cardEnding}) => + subtitle: ({amountOwed, cardEnding}: BillingBannerDisputePendingParams) => `You disputed the ${amountOwed} charge on the card ending in ${cardEnding}. Your account will be locked until the dispute is resolved with your bank.`, }, cardAuthenticationRequired: { title: 'Your card couldn’t be charged', - subtitle: ({cardEnding}) => + subtitle: ({cardEnding}: BillingBannerCardAuthenticationRequiredParams) => `Your payment card hasn’t been fully authenticated. Please complete the authentication process to activate your payment card ending in ${cardEnding}.`, }, insufficientFunds: { title: 'Your card couldn’t be charged', - subtitle: ({amountOwed}) => + subtitle: ({amountOwed}: BillingBannerInsufficientFundsParams) => `Your payment card was declined due to insufficient funds. Please retry or add a new payment card to clear your ${amountOwed} outstanding balance.`, }, cardExpired: { title: 'Your card couldn’t be charged', - subtitle: ({amountOwed}) => `Your payment card expired. Please add a new payment card to clear your ${amountOwed} outstanding balance.`, + subtitle: ({amountOwed}: BillingBannerCardExpiredParams) => `Your payment card expired. Please add a new payment card to clear your ${amountOwed} outstanding balance.`, }, cardExpireSoon: { title: 'Your card is expiring soon', @@ -4570,7 +4644,7 @@ export default { title: 'Your card couldn’t be charged', subtitle: 'Before retrying, please call your bank directly to authorize Expensify charges and remove any holds. Otherwise, try adding a different payment card.', }, - cardOnDispute: ({amountOwed, cardEnding}) => + cardOnDispute: ({amountOwed, cardEnding}: BillingBannerCardOnDisputeParams) => `You disputed the ${amountOwed} charge on the card ending in ${cardEnding}. Your account will be locked until the dispute is resolved with your bank.`, preTrial: { title: 'Start a free trial', @@ -4579,7 +4653,7 @@ export default { subtitleEnd: 'so your team can start expensing.', }, trialStarted: { - title: ({numOfDays}) => `Free trial: ${numOfDays} ${numOfDays === 1 ? 'day' : 'days'} left!`, + title: ({numOfDays}: TrialStartedTitleParams) => `Free trial: ${numOfDays} ${numOfDays === 1 ? 'day' : 'days'} left!`, subtitle: 'Add a payment card to continue using all of your favorite features.', }, trialEnded: { @@ -4591,9 +4665,9 @@ export default { title: 'Payment', subtitle: 'Add a card to pay for your Expensify subscription.', addCardButton: 'Add payment card', - cardNextPayment: ({nextPaymentDate}) => `Your next payment date is ${nextPaymentDate}.`, - cardEnding: ({cardNumber}) => `Card ending in ${cardNumber}`, - cardInfo: ({name, expiration, currency}) => `Name: ${name}, Expiration: ${expiration}, Currency: ${currency}`, + cardNextPayment: ({nextPaymentDate}: CardNextPaymentParams) => `Your next payment date is ${nextPaymentDate}.`, + cardEnding: ({cardNumber}: CardEndingParams) => `Card ending in ${cardNumber}`, + cardInfo: ({name, expiration, currency}: CardInfoParams) => `Name: ${name}, Expiration: ${expiration}, Currency: ${currency}`, changeCard: 'Change payment card', changeCurrency: 'Change payment currency', cardNotFound: 'No payment card added', @@ -4612,8 +4686,8 @@ export default { title: 'Your plan', collect: { title: 'Collect', - priceAnnual: ({lower, upper}) => `From ${lower}/active member with the Expensify Card, ${upper}/active member without the Expensify Card.`, - pricePayPerUse: ({lower, upper}) => `From ${lower}/active member with the Expensify Card, ${upper}/active member without the Expensify Card.`, + priceAnnual: ({lower, upper}: YourPlanPriceParams) => `From ${lower}/active member with the Expensify Card, ${upper}/active member without the Expensify Card.`, + pricePayPerUse: ({lower, upper}: YourPlanPriceParams) => `From ${lower}/active member with the Expensify Card, ${upper}/active member without the Expensify Card.`, benefit1: 'Unlimited SmartScans and distance tracking', benefit2: 'Expensify Cards with Smart Limits', benefit3: 'Bill pay and invoicing', @@ -4624,8 +4698,8 @@ export default { }, control: { title: 'Control', - priceAnnual: ({lower, upper}) => `From ${lower}/active member with the Expensify Card, ${upper}/active member without the Expensify Card.`, - pricePayPerUse: ({lower, upper}) => `From ${lower}/active member with the Expensify Card, ${upper}/active member without the Expensify Card.`, + priceAnnual: ({lower, upper}: YourPlanPriceParams) => `From ${lower}/active member with the Expensify Card, ${upper}/active member without the Expensify Card.`, + pricePayPerUse: ({lower, upper}: YourPlanPriceParams) => `From ${lower}/active member with the Expensify Card, ${upper}/active member without the Expensify Card.`, benefit1: 'Everything in Collect, plus:', benefit2: 'NetSuite and Sage Intacct integrations', benefit3: 'Certinia and Workday sync', @@ -4656,10 +4730,10 @@ export default { note: 'Note: An active member is anyone who has created, edited, submitted, approved, reimbursed, or exported expense data tied to your company workspace.', confirmDetails: 'Confirm your new annual subscription details:', subscriptionSize: 'Subscription size', - activeMembers: ({size}) => `${size} active members/month`, + activeMembers: ({size}: SubscriptionSizeParams) => `${size} active members/month`, subscriptionRenews: 'Subscription renews', youCantDowngrade: 'You can’t downgrade during your annual subscription.', - youAlreadyCommitted: ({size, date}) => + youAlreadyCommitted: ({size, date}: SubscriptionCommitmentParams) => `You already committed to an annual subscription size of ${size} active members per month until ${date}. You can switch to a pay-per-use subscription on ${date} by disabling auto-renew.`, error: { size: 'Please enter a valid subscription size.', @@ -4676,13 +4750,13 @@ export default { title: 'Subscription settings', autoRenew: 'Auto-renew', autoIncrease: 'Auto-increase annual seats', - saveUpTo: ({amountWithCurrency}) => `Save up to ${amountWithCurrency}/month per active member`, + saveUpTo: ({amountWithCurrency}: SubscriptionSettingsSaveUpToParams) => `Save up to ${amountWithCurrency}/month per active member`, automaticallyIncrease: 'Automatically increase your annual seats to accommodate for active members that exceed your subscription size. Note: This will extend your annual subscription end date.', disableAutoRenew: 'Disable auto-renew', helpUsImprove: 'Help us improve Expensify', whatsMainReason: "What's the main reason you're disabling auto-renew?", - renewsOn: ({date}) => `Renews on ${date}.`, + renewsOn: ({date}: SubscriptionSettingsRenewsOnParams) => `Renews on ${date}.`, }, requestEarlyCancellation: { title: 'Request early cancellation', @@ -4731,7 +4805,7 @@ export default { addCopilot: 'Add copilot', membersCanAccessYourAccount: 'These members can access your account:', youCanAccessTheseAccounts: 'You can access these accounts via the account switcher:', - role: (role?: string): string => { + role: ({role}: OptionalParam = {}) => { switch (role) { case CONST.DELEGATE_ROLE.ALL: return 'Full'; @@ -4745,7 +4819,7 @@ export default { accessLevel: 'Access level', confirmCopilot: 'Confirm your copilot below.', accessLevelDescription: 'Choose an access level below. Both Full and Limited access allow copilots to view all conversations and expenses.', - roleDescription: (role?: string): string => { + roleDescription: ({role}: OptionalParam = {}) => { switch (role) { case CONST.DELEGATE_ROLE.ALL: return 'Allow another member to take all actions in your account, on your behalf. Includes chat, submissions, approvals, payments, settings updates, and more.'; @@ -4771,9 +4845,9 @@ export default { nothingToPreview: 'Nothing to preview', editJson: 'Edit JSON:', preview: 'Preview:', - missingProperty: ({propertyName}) => `Missing ${propertyName}`, - invalidProperty: ({propertyName, expectedType}) => `Invalid property: ${propertyName} - Expected: ${expectedType}`, - invalidValue: ({expectedValues}) => `Invalid value - Expected: ${expectedValues}`, + missingProperty: ({propertyName}: MissingPropertyParams) => `Missing ${propertyName}`, + invalidProperty: ({propertyName, expectedType}: InvalidPropertyParams) => `Invalid property: ${propertyName} - Expected: ${expectedType}`, + invalidValue: ({expectedValues}: InvalidValueParams) => `Invalid value - Expected: ${expectedValues}`, missingValue: 'Missing value', createReportAction: 'Create Report Action', reportAction: 'Report Action', @@ -4788,4 +4862,6 @@ export default { time: 'Time', none: 'None', }, -} satisfies TranslationBase; +}; + +export default translations satisfies TranslationDeepObject; diff --git a/src/languages/es.ts b/src/languages/es.ts index 4e63f99357e7..78454752893f 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1,45 +1,90 @@ import {Str} from 'expensify-common'; import CONST from '@src/CONST'; -import type {ConnectionName, PolicyConnectionSyncStage, SageIntacctMappingName} from '@src/types/onyx/Policy'; +import type en from './en'; import type { AccountOwnerParams, + ActionsAreCurrentlyRestricted, + AddEmployeeParams, AddressLineParams, AdminCanceledRequestParams, + AgeParams, AlreadySignedInParams, ApprovalWorkflowErrorParams, ApprovedAmountParams, AssignCardParams, + AssignedYouCardParams, + AssigneeParams, + AuthenticationErrorParams, + AutoPayApprovedReportsLimitErrorParams, + BadgeFreeTrialParams, BeginningOfChatHistoryAdminRoomPartOneParams, BeginningOfChatHistoryAnnounceRoomPartOneParams, BeginningOfChatHistoryAnnounceRoomPartTwo, BeginningOfChatHistoryDomainRoomPartOneParams, + BillingBannerCardAuthenticationRequiredParams, + BillingBannerCardExpiredParams, + BillingBannerCardOnDisputeParams, + BillingBannerDisputePendingParams, + BillingBannerInsufficientFundsParams, + BillingBannerSubtitleWithDateParams, CanceledRequestParams, + CardEndingParams, + CardInfoParams, + CardNextPaymentParams, + CategoryNameParams, ChangeFieldParams, + ChangeOwnerDuplicateSubscriptionParams, + ChangeOwnerHasFailedSettlementsParams, + ChangeOwnerSubscriptionParams, ChangePolicyParams, ChangeTypeParams, + CharacterLengthLimitParams, CharacterLimitParams, CompanyCardFeedNameParams, ConfirmHoldExpenseParams, ConfirmThatParams, + ConnectionNameParams, + ConnectionParams, + CustomersOrJobsLabelParams, + DateParams, DateShouldBeAfterParams, DateShouldBeBeforeParams, + DefaultAmountParams, + DefaultVendorDescriptionParams, + DelegateRoleParams, DelegateSubmitParams, DeleteActionParams, DeleteConfirmationParams, DeleteExpenseTranslationParams, DidSplitAmountMessageParams, + DimensionsCountParams, DistanceRateOperationsParams, EditActionParams, ElectronicFundsParams, - EnglishTranslation, EnterMagicCodeParams, + ExportAgainModalDescriptionParams, ExportedToIntegrationParams, + ExportIntegrationSelectedParams, + FeatureNameParams, + FiltersAmountBetweenParams, FormattedMaxLengthParams, ForwardedAmountParams, GoBackMessageParams, GoToRoomParams, + ImportedTagsMessageParams, + ImportFieldParams, + ImportMembersSuccessfullDescriptionParams, + ImportTagsSuccessfullDescriptionParams, + IncorrectZipFormatParams, InstantSummaryParams, + IntacctMappingTitleParams, + IntegrationExportParams, + IntegrationSyncFailedParams, + InvalidPropertyParams, + InvalidValueParams, IssueVirtualCardParams, + LastSyncAccountingParams, + LastSyncDateParams, LocalTimeParams, LoggedInAsParams, LogSizeParams, @@ -47,12 +92,15 @@ import type { ManagerApprovedParams, MarkedReimbursedParams, MarkReimbursedFromIntegrationParams, + MissingPropertyParams, NoLongerHaveAccessParams, NotAllowedExtensionParams, NotYouParams, OOOEventSummaryFullDayParams, OOOEventSummaryPartialDayParams, + OptionalParam, OurEmailProviderParams, + OwnerOwesAmountParams, PaidElsewhereWithAmountParams, PaidWithExpensifyWithAmountParams, ParentNavigationSummaryParams, @@ -62,21 +110,28 @@ import type { PayerPaidParams, PayerSettledParams, PaySomeoneParams, + ReconciliationWorksParams, ReimbursementRateParams, + RemovedFromApprovalWorkflowParams, RemovedTheRequestParams, + RemoveMemberPromptParams, RemoveMembersWarningPrompt, RenamedRoomActionParams, ReportArchiveReasonsClosedParams, ReportArchiveReasonsInvoiceReceiverPolicyDeletedParams, ReportArchiveReasonsMergedParams, - ReportArchiveReasonsPolicyDeletedParams, ReportArchiveReasonsRemovedFromPolicyParams, + ReportPolicyNameParams, RequestAmountParams, RequestCountParams, RequestedAmountMessageParams, + RequiredFieldParams, ResolutionConstraintsParams, + RoleNamesParams, RoomNameReservedErrorParams, RoomRenamedToParams, + SecondaryLoginParams, + SelectedNumberParams, SetTheDistanceMerchantParams, SetTheRequestParams, SettledAfterAddedBankAccountParams, @@ -85,19 +140,32 @@ import type { SignUpNewFaceCodeParams, SizeExceededParams, SplitAmountParams, + SpreadCategoriesParams, + SpreadFieldNameParams, + SpreadSheetColumnParams, + StatementTitleParams, StepCounterParams, StripePaidParams, + SubscriptionCommitmentParams, + SubscriptionSettingsRenewsOnParams, + SubscriptionSettingsSaveUpToParams, + SubscriptionSizeParams, + SyncStageNameConnectionsParams, TaskCreatedActionParams, + TaxAmountParams, TermsParams, ThreadRequestReportNameParams, ThreadSentMoneyReportNameParams, ToValidateLoginParams, TransferParams, + TrialStartedTitleParams, UnapprovedParams, + UnapproveWithIntegrationWarningParams, UnshareParams, UntilTimeParams, UpdatedTheDistanceMerchantParams, UpdatedTheRequestParams, + UpdateRoleParams, UsePlusButtonParams, UserIsAlreadyMemberParams, UserSplitParams, @@ -122,11 +190,14 @@ import type { WelcomeNoteParams, WelcomeToRoomParams, WeSentYouMagicSignInLinkParams, + WorkspaceOwnerWillNeedToAddOrUpdatePaymentCardParams, + YourPlanPriceParams, ZipCodeExampleFormatParams, -} from './types'; +} from './params'; +import type {TranslationDeepObject} from './types'; /* eslint-disable max-len */ -export default { +const translations = { common: { cancel: 'Cancelar', dismiss: 'Descartar', @@ -254,7 +325,7 @@ export default { fieldRequired: 'Este campo es obligatorio.', requestModified: 'Esta solicitud está siendo modificada por otro miembro.', characterLimit: ({limit}: CharacterLimitParams) => `Supera el límite de ${limit} caracteres`, - characterLimitExceedCounter: ({length, limit}) => `Se superó el límite de caracteres (${length}/${limit})`, + characterLimitExceedCounter: ({length, limit}: CharacterLengthLimitParams) => `Se superó el límite de caracteres (${length}/${limit})`, dateInvalid: 'Por favor, selecciona una fecha válida.', invalidDateShouldBeFuture: 'Por favor, elige una fecha igual o posterior a hoy.', invalidTimeShouldBeFuture: 'Por favor, elige una hora al menos un minuto en el futuro.', @@ -636,7 +707,7 @@ export default { shouldUseYou ? `Este chat ya no está activo porque tu ya no eres miembro del espacio de trabajo ${policyName}.` : `Este chat está desactivado porque ${displayName} ha dejado de ser miembro del espacio de trabajo ${policyName}.`, - [CONST.REPORT.ARCHIVE_REASON.POLICY_DELETED]: ({policyName}: ReportArchiveReasonsPolicyDeletedParams) => + [CONST.REPORT.ARCHIVE_REASON.POLICY_DELETED]: ({policyName}: ReportArchiveReasonsInvoiceReceiverPolicyDeletedParams) => `Este chat está desactivado porque el espacio de trabajo ${policyName} se ha eliminado.`, [CONST.REPORT.ARCHIVE_REASON.INVOICE_RECEIVER_POLICY_DELETED]: ({policyName}: ReportArchiveReasonsInvoiceReceiverPolicyDeletedParams) => `Este chat está desactivado porque el espacio de trabajo ${policyName} se ha eliminado.`, @@ -677,14 +748,14 @@ export default { dragAndDrop: 'Arrastra y suelta un archivo de hoja de cálculo aquí', chooseSpreadsheet: 'Subir', fileContainsHeader: 'El archivo contiene encabezados', - column: (name: string) => `Columna ${name}`, - fieldNotMapped: (fieldName: string) => `¡Vaya! Un campo obligatorio ("${fieldName}") no ha sido mapeado. Por favor, revisa e inténtalo de nuevo.`, - singleFieldMultipleColumns: (fieldName: string) => `¡Vaya! Has mapeado un solo campo ("${fieldName}") a varias columnas. Por favor, revisa e inténtalo de nuevo.`, + column: ({name}: SpreadSheetColumnParams) => `Columna ${name}`, + fieldNotMapped: ({fieldName}: SpreadFieldNameParams) => `¡Vaya! Un campo obligatorio ("${fieldName}") no ha sido mapeado. Por favor, revisa e inténtalo de nuevo.`, + singleFieldMultipleColumns: ({fieldName}: SpreadFieldNameParams) => `¡Vaya! Has mapeado un solo campo ("${fieldName}") a varias columnas. Por favor, revisa e inténtalo de nuevo.`, importFailedTitle: 'Fallo en la importación', importFailedDescription: 'Por favor, asegúrate de que todos los campos estén llenos correctamente e inténtalo de nuevo. Si el problema persiste, por favor contacta a Concierge.', - importCategoriesSuccessfullDescription: (categories: number) => (categories > 1 ? `Se han agregado ${categories} categorías.` : 'Se ha agregado 1 categoría.'), - importMembersSuccessfullDescription: (members: number) => (members > 1 ? `Se han agregado ${members} miembros.` : 'Se ha agregado 1 miembro.'), - importTagsSuccessfullDescription: (tags: number) => (tags > 1 ? `Se han agregado ${tags} etiquetas.` : 'Se ha agregado 1 etiqueta.'), + importCategoriesSuccessfullDescription: ({categories}: SpreadCategoriesParams) => (categories > 1 ? `Se han agregado ${categories} categorías.` : 'Se ha agregado 1 categoría.'), + importMembersSuccessfullDescription: ({members}: ImportMembersSuccessfullDescriptionParams) => (members > 1 ? `Se han agregado ${members} miembros.` : 'Se ha agregado 1 miembro.'), + importTagsSuccessfullDescription: ({tags}: ImportTagsSuccessfullDescriptionParams) => (tags > 1 ? `Se han agregado ${tags} etiquetas.` : 'Se ha agregado 1 etiqueta.'), importSuccessfullTitle: 'Importar categorías', importDescription: 'Elige qué campos mapear desde tu hoja de cálculo haciendo clic en el menú desplegable junto a cada columna importada a continuación.', sizeNotMet: 'El archivo adjunto debe ser más grande que 0 bytes.', @@ -722,7 +793,7 @@ export default { splitBill: 'Dividir gasto', splitScan: 'Dividir recibo', splitDistance: 'Dividir distancia', - paySomeone: (name: string) => `Pagar a ${name ?? 'alguien'}`, + paySomeone: ({name}: PaySomeoneParams = {}) => `Pagar a ${name ?? 'alguien'}`, assignTask: 'Assignar tarea', header: 'Acción rápida', trackManual: 'Crear gasto', @@ -751,7 +822,7 @@ export default { share: 'Compartir', participants: 'Participantes', submitExpense: 'Presentar gasto', - paySomeone: ({name}: PaySomeoneParams) => `Pagar a ${name ?? 'alguien'}`, + paySomeone: ({name}: PaySomeoneParams = {}) => `Pagar a ${name ?? 'alguien'}`, trackExpense: 'Seguimiento de gastos', pay: 'Pagar', cancelPayment: 'Cancelar el pago', @@ -765,7 +836,7 @@ export default { pendingMatchWithCreditCardDescription: 'Recibo pendiente de adjuntar con la transacción de la tarjeta. Márcalo como efectivo para cancelar.', markAsCash: 'Marcar como efectivo', routePending: 'Ruta pendiente...', - receiptIssuesFound: (count: number) => `${count === 1 ? 'Problema encontrado' : 'Problemas encontrados'}`, + receiptIssuesFound: ({count}: DistanceRateOperationsParams) => `${count === 1 ? 'Problema encontrado' : 'Problemas encontrados'}`, fieldPending: 'Pendiente...', receiptScanning: 'Escaneando recibo...', receiptScanInProgress: 'Escaneado de recibo en proceso', @@ -820,7 +891,7 @@ export default { payerOwes: ({payer}: PayerOwesParams) => `${payer} debe: `, payerPaidAmount: ({payer, amount}: PayerPaidAmountParams) => `${payer ? `${payer} ` : ''}pagó ${amount}`, payerPaid: ({payer}: PayerPaidParams) => `${payer} pagó: `, - payerSpentAmount: ({payer, amount}: PayerPaidAmountParams): string => `${payer} gastó ${amount}`, + payerSpentAmount: ({payer, amount}: PayerPaidAmountParams) => `${payer} gastó ${amount}`, payerSpent: ({payer}: PayerPaidParams) => `${payer} gastó: `, managerApproved: ({manager}: ManagerApprovedParams) => `${manager} aprobó:`, managerApprovedAmount: ({manager, amount}: ManagerApprovedAmountParams) => `${manager} aprobó ${amount}`, @@ -928,7 +999,7 @@ export default { unapprove: 'Desaprobar', unapproveReport: 'Anular la aprobación del informe', headsUp: 'Atención!', - unapproveWithIntegrationWarning: (accountingIntegration: string) => + unapproveWithIntegrationWarning: ({accountingIntegration}: UnapproveWithIntegrationWarningParams) => `Este informe ya se ha exportado a ${accountingIntegration}. Los cambios realizados en este informe en Expensify pueden provocar discrepancias en los datos y problemas de conciliación de la tarjeta Expensify. ¿Está seguro de que desea anular la aprobación de este informe?`, reimbursable: 'reembolsable', nonReimbursable: 'no reembolsable', @@ -1315,15 +1386,15 @@ export default { availableSpend: 'Límite restante', smartLimit: { name: 'Límite inteligente', - title: (formattedLimit: string) => `Puedes gastar hasta ${formattedLimit} en esta tarjeta al mes. El límite se restablecerá el primer día del mes.`, + title: ({formattedLimit}: ViolationsOverLimitParams) => `Puedes gastar hasta ${formattedLimit} en esta tarjeta al mes. El límite se restablecerá el primer día del mes.`, }, fixedLimit: { name: 'Límite fijo', - title: (formattedLimit: string) => `Puedes gastar hasta ${formattedLimit} en esta tarjeta, luego se desactivará.`, + title: ({formattedLimit}: ViolationsOverLimitParams) => `Puedes gastar hasta ${formattedLimit} en esta tarjeta, luego se desactivará.`, }, monthlyLimit: { name: 'Límite mensual', - title: (formattedLimit: string) => `Puedes gastar hasta ${formattedLimit} en esta tarjeta y el límite se restablecerá a medida que se aprueben tus gastos.`, + title: ({formattedLimit}: ViolationsOverLimitParams) => `Puedes gastar hasta ${formattedLimit} en esta tarjeta y el límite se restablecerá a medida que se aprueben tus gastos.`, }, virtualCardNumber: 'Número de la tarjeta virtual', physicalCardNumber: 'Número de la tarjeta física', @@ -1527,7 +1598,7 @@ export default { }, }, reportDetailsPage: { - inWorkspace: ({policyName}) => `en ${policyName}`, + inWorkspace: ({policyName}: ReportPolicyNameParams) => `en ${policyName}`, }, reportDescriptionPage: { roomDescription: 'Descripción de la sala de chat', @@ -1540,7 +1611,7 @@ export default { groupChat: { lastMemberTitle: '¡Atención!', lastMemberWarning: 'Ya que eres la última persona aquí, si te vas, este chat quedará inaccesible para todos los miembros. ¿Estás seguro de que quieres salir del chat?', - defaultReportName: ({displayName}: {displayName: string}) => `Chat de grupo de ${displayName}`, + defaultReportName: ({displayName}: ReportArchiveReasonsClosedParams) => `Chat de grupo de ${displayName}`, }, languagePage: { language: 'Idioma', @@ -1671,7 +1742,7 @@ export default { error: { dateShouldBeBefore: ({dateString}: DateShouldBeBeforeParams) => `La fecha debe ser anterior a ${dateString}.`, dateShouldBeAfter: ({dateString}: DateShouldBeAfterParams) => `La fecha debe ser posterior a ${dateString}.`, - incorrectZipFormat: (zipFormat?: string) => `Formato de código postal incorrecto.${zipFormat ? ` Formato aceptable: ${zipFormat}` : ''}`, + incorrectZipFormat: ({zipFormat}: IncorrectZipFormatParams = {}) => `Formato de código postal incorrecto.${zipFormat ? ` Formato aceptable: ${zipFormat}` : ''}`, hasInvalidCharacter: 'El nombre sólo puede incluir caracteres latinos.', }, }, @@ -2228,7 +2299,7 @@ export default { testTransactions: 'Transacciones de prueba', issueAndManageCards: 'Emitir y gestionar tarjetas', reconcileCards: 'Reconciliar tarjetas', - selected: ({selectedNumber}) => `${selectedNumber} seleccionados`, + selected: ({selectedNumber}: SelectedNumberParams) => `${selectedNumber} seleccionados`, settlementFrequency: 'Frecuencia de liquidación', deleteConfirmation: '¿Estás seguro de que quieres eliminar este espacio de trabajo?', unavailable: 'Espacio de trabajo no disponible', @@ -2247,7 +2318,7 @@ export default { `¡Has sido invitado a ${workspaceName}! Descargue la aplicación móvil Expensify en use.expensify.com/download para comenzar a rastrear sus gastos.`, subscription: 'Suscripción', markAsExported: 'Marcar como introducido manualmente', - exportIntegrationSelected: (connectionName: ConnectionName) => `Exportar a ${CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[connectionName]}`, + exportIntegrationSelected: ({connectionName}: ExportIntegrationSelectedParams) => `Exportar a ${CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[connectionName]}`, letsDoubleCheck: 'Verifiquemos que todo esté correcto', reportField: 'Campo del informe', lineItemLevel: 'Nivel de partida', @@ -2264,14 +2335,14 @@ export default { createNewConnection: 'Crear una nueva conexión', reuseExistingConnection: 'Reutilizar la conexión existente', existingConnections: 'Conexiones existentes', - lastSyncDate: (connectionName: string, formattedDate: string) => `${connectionName} - Última sincronización ${formattedDate}`, + lastSyncDate: ({connectionName, formattedDate}: LastSyncDateParams) => `${connectionName} - Última sincronización ${formattedDate}`, topLevel: 'Nivel superior', - authenticationError: (connectionName: string) => `No se puede conectar a ${connectionName} debido a un error de autenticación.`, + authenticationError: ({connectionName}: AuthenticationErrorParams) => `No se puede conectar a ${connectionName} debido a un error de autenticación.`, learnMore: 'Más información.', memberAlternateText: 'Los miembros pueden presentar y aprobar informes.', adminAlternateText: 'Los administradores tienen acceso total para editar todos los informes y la configuración del área de trabajo.', auditorAlternateText: 'Los auditores pueden ver y comentar los informes.', - roleName: (role?: string): string => { + roleName: ({role}: OptionalParam = {}) => { switch (role) { case CONST.POLICY.ROLE.ADMIN: return 'Administrador'; @@ -2402,8 +2473,8 @@ export default { accountsSwitchDescription: 'Las categorías activas estarán disponibles para ser escogidas cuando se crea un gasto.', trackingCategories: 'Categorías de seguimiento', trackingCategoriesDescription: 'Elige cómo gestionar categorías de seguimiento de Xero en Expensify.', - mapTrackingCategoryTo: ({categoryName}) => `Asignar ${categoryName} de Xero a`, - mapTrackingCategoryToDescription: ({categoryName}) => `Elige dónde mapear ${categoryName} al exportar a Xero.`, + mapTrackingCategoryTo: ({categoryName}: CategoryNameParams) => `Asignar ${categoryName} de Xero a`, + mapTrackingCategoryToDescription: ({categoryName}: CategoryNameParams) => `Elige dónde mapear ${categoryName} al exportar a Xero.`, customers: 'Volver a facturar a los clientes', customersDescription: 'Elige si quieres volver a facturar a los clientes en Expensify. Tus contactos de clientes de Xero se pueden etiquetar como gastos, y se exportarán a Xero como una factura de venta.', @@ -2504,7 +2575,7 @@ export default { }, creditCardAccount: 'Cuenta de tarjeta de crédito', defaultVendor: 'Proveedor por defecto', - defaultVendorDescription: (isReimbursable: boolean): string => + defaultVendorDescription: ({isReimbursable}: DefaultVendorDescriptionParams) => `Establezca un proveedor predeterminado que se aplicará a los gastos ${isReimbursable ? '' : 'no '}reembolsables que no tienen un proveedor coincidente en Sage Intacct.`, exportDescription: 'Configure cómo se exportan los datos de Expensify a Sage Intacct.', exportPreferredExporterNote: @@ -2722,12 +2793,12 @@ export default { importJobs: 'Importar proyectos', customers: 'clientes', jobs: 'proyectos', - label: (importFields: string[], importType: string) => `${importFields.join(' y ')}, ${importType}`, + label: ({importFields, importType}: CustomersOrJobsLabelParams) => `${importFields.join(' y ')}, ${importType}`, }, importTaxDescription: 'Importar grupos de impuestos desde NetSuite.', importCustomFields: { chooseOptionBelow: 'Elija una de las opciones siguientes:', - requiredFieldError: (fieldName: string) => `Por favor, introduzca el ${fieldName}`, + requiredFieldError: ({fieldName}: RequiredFieldParams) => `Por favor, introduzca el ${fieldName}`, customSegments: { title: 'Segmentos/registros personalizados', addText: 'Añadir segmento/registro personalizado', @@ -2768,7 +2839,7 @@ export default { customRecordMappingTitle: '¿Cómo debería mostrarse este registro de segmento personalizado en Expensify?', }, errors: { - uniqueFieldError: (fieldName: string) => `Ya existe un segmento/registro personalizado con este ${fieldName?.toLowerCase()}.`, + uniqueFieldError: ({fieldName}: RequiredFieldParams) => `Ya existe un segmento/registro personalizado con este ${fieldName?.toLowerCase()}.`, }, }, customLists: { @@ -2802,18 +2873,18 @@ export default { [CONST.INTEGRATION_ENTITY_MAP_TYPES.NETSUITE_DEFAULT]: { label: 'Predeterminado del empleado NetSuite', description: 'No importado a Expensify, aplicado en exportación', - footerContent: (importField: string) => + footerContent: ({importField}: ImportFieldParams) => `Si usa ${importField} en NetSuite, aplicaremos el conjunto predeterminado en el registro del empleado al exportarlo a Informe de gastos o Entrada de diario.`, }, [CONST.INTEGRATION_ENTITY_MAP_TYPES.TAG]: { label: 'Etiquetas', description: 'Nivel de línea de pedido', - footerContent: (importField: string) => `Se podrán seleccionar ${importField} para cada gasto individual en el informe de un empleado.`, + footerContent: ({importField}: ImportFieldParams) => `Se podrán seleccionar ${importField} para cada gasto individual en el informe de un empleado.`, }, [CONST.INTEGRATION_ENTITY_MAP_TYPES.REPORT_FIELD]: { label: 'Campos de informe', description: 'Nivel de informe', - footerContent: (importField: string) => `La selección de ${importField} se aplicará a todos los gastos en el informe de un empleado.`, + footerContent: ({importField}: ImportFieldParams) => `La selección de ${importField} se aplicará a todos los gastos en el informe de un empleado.`, }, }, }, @@ -2844,8 +2915,8 @@ export default { addAUserDefinedDimension: 'Añadir una dimensión definida por el usuario', detailedInstructionsLink: 'Ver instrucciones detalladas', detailedInstructionsRestOfSentence: ' para añadir dimensiones definidas por el usuario.', - userDimensionsAdded: (dimensionsCount: number) => `${dimensionsCount} ${Str.pluralize('UDD', `UDDs`, dimensionsCount)} añadido`, - mappingTitle: (mappingName: SageIntacctMappingName): string => { + userDimensionsAdded: ({dimensionsCount}: DimensionsCountParams) => `${dimensionsCount} ${Str.pluralize('UDD', `UDDs`, dimensionsCount)} añadido`, + mappingTitle: ({mappingName}: IntacctMappingTitleParams) => { switch (mappingName) { case CONST.SAGE_INTACCT_CONFIG.MAPPINGS.DEPARTMENTS: return 'departamentos'; @@ -2879,7 +2950,7 @@ export default { }, yourCardProvider: `¿Quién es su proveedor de tarjetas?`, enableFeed: { - title: (provider: string) => `Habilita tu feed ${provider}`, + title: ({provider}: GoBackMessageParams) => `Habilita tu feed ${provider}`, heading: 'Tenemos una integración directa con el emisor de su tarjeta y podemos importar los datos de sus transacciones a Expensify de forma rápida y precisa.\n\nPara empezar, simplemente:', visa: `1. Visite [este artículo de ayuda](${CONST.COMPANY_CARDS_HELP}) para obtener instrucciones detalladas sobre cómo configurar sus tarjetas comerciales Visa.\n\n2. [Póngase en contacto con su banco](${CONST.COMPANY_CARDS_HELP}) para comprobar que admiten un feed personalizado para su programa, y pídales que lo activen.\n\n3. *Una vez que el feed esté habilitado y tengas sus datos, pasa a la siguiente pantalla.*`, @@ -2927,7 +2998,7 @@ export default { card: 'Tarjeta', startTransactionDate: 'Fecha de inicio de transacciones', cardName: 'Nombre de la tarjeta', - assignedYouCard: (assigner: string) => `¡${assigner} te ha asignado una tarjeta de empresa! Las transacciones importadas aparecerán en este chat.`, + assignedYouCard: ({assigner}: AssignedYouCardParams) => `¡${assigner} te ha asignado una tarjeta de empresa! Las transacciones importadas aparecerán en este chat.`, chooseCardFeed: 'Elige feed de tarjetas', }, expensifyCard: { @@ -2975,21 +3046,21 @@ export default { deactivate: 'Desactivar tarjeta', changeCardLimit: 'Modificar el límite de la tarjeta', changeLimit: 'Modificar límite', - smartLimitWarning: (limit: string) => + smartLimitWarning: ({limit}: CharacterLimitParams) => `Si cambias el límite de esta tarjeta a ${limit}, las nuevas transacciones serán rechazadas hasta que apruebes antiguos gastos de la tarjeta.`, - monthlyLimitWarning: (limit: string) => `Si cambias el límite de esta tarjeta a ${limit}, las nuevas transacciones serán rechazadas hasta el próximo mes.`, - fixedLimitWarning: (limit: string) => `Si cambias el límite de esta tarjeta a ${limit}, se rechazarán las nuevas transacciones.`, + monthlyLimitWarning: ({limit}: CharacterLimitParams) => `Si cambias el límite de esta tarjeta a ${limit}, las nuevas transacciones serán rechazadas hasta el próximo mes.`, + fixedLimitWarning: ({limit}: CharacterLimitParams) => `Si cambias el límite de esta tarjeta a ${limit}, se rechazarán las nuevas transacciones.`, changeCardLimitType: 'Modificar el tipo de límite de la tarjeta', changeLimitType: 'Modificar el tipo de límite', - changeCardSmartLimitTypeWarning: (limit: string) => + changeCardSmartLimitTypeWarning: ({limit}: CharacterLimitParams) => `Si cambias el tipo de límite de esta tarjeta a Límite inteligente, las nuevas transacciones serán rechazadas porque ya se ha alcanzado el límite de ${limit} no aprobado.`, - changeCardMonthlyLimitTypeWarning: (limit: string) => + changeCardMonthlyLimitTypeWarning: ({limit}: CharacterLimitParams) => `Si cambias el tipo de límite de esta tarjeta a Mensual, las nuevas transacciones serán rechazadas porque ya se ha alcanzado el límite de ${limit} mensual.`, addShippingDetails: 'Añadir detalles de envío', - issuedCard: (assignee: string) => `¡emitió a ${assignee} una Tarjeta Expensify! La tarjeta llegará en 2-3 días laborables.`, - issuedCardNoShippingDetails: (assignee: string) => `¡emitió a ${assignee} una Tarjeta Expensify! La tarjeta se enviará una vez que se agreguen los detalles de envío.`, + issuedCard: ({assignee}: AssigneeParams) => `¡emitió a ${assignee} una Tarjeta Expensify! La tarjeta llegará en 2-3 días laborables.`, + issuedCardNoShippingDetails: ({assignee}: AssigneeParams) => `¡emitió a ${assignee} una Tarjeta Expensify! La tarjeta se enviará una vez que se agreguen los detalles de envío.`, issuedCardVirtual: ({assignee, link}: IssueVirtualCardParams) => `¡emitió a ${assignee} una ${link} virtual! La tarjeta puede utilizarse inmediatamente.`, - addedShippingDetails: (assignee: string) => `${assignee} agregó los detalles de envío. La Tarjeta Expensify llegará en 2-3 días hábiles.`, + addedShippingDetails: ({assignee}: AssigneeParams) => `${assignee} agregó los detalles de envío. La Tarjeta Expensify llegará en 2-3 días hábiles.`, }, categories: { deleteCategories: 'Eliminar categorías', @@ -3088,8 +3159,9 @@ export default { cardNumber: 'Número de la tarjeta', cardholder: 'Titular de la tarjeta', cardName: 'Nombre de la tarjeta', - integrationExport: (integration: string, type: string) => `Exportación a ${integration} ${type}`, - integrationExportTitleFirstPart: (integration: string) => `Seleccione la cuenta ${integration} donde se deben exportar las transacciones. Seleccione una cuenta diferente`, + integrationExport: ({integration, type}: IntegrationExportParams) => `Exportación a ${integration} ${type}`, + integrationExportTitleFirstPart: ({integration}: IntegrationExportParams) => + `Seleccione la cuenta ${integration} donde se deben exportar las transacciones. Seleccione una cuenta diferente`, integrationExportTitleLinkPart: 'opción de exportación', integrationExportTitleSecondPart: 'para cambiar las cuentas disponibles.', lastUpdated: 'Última actualización', @@ -3123,7 +3195,7 @@ export default { giveItNameInstruction: 'Nombra la tarjeta para distingirla de las demás.', updating: 'Actualizando...', noAccountsFound: 'No se han encontrado cuentas', - noAccountsFoundDescription: (connection: string) => `Añade la cuenta en ${connection} y sincroniza la conexión de nuevo.`, + noAccountsFoundDescription: ({connection}: ConnectionParams) => `Añade la cuenta en ${connection} y sincroniza la conexión de nuevo.`, }, workflows: { title: 'Flujos de trabajo', @@ -3244,7 +3316,7 @@ export default { tagRules: 'Reglas de etiquetas', approverDescription: 'Aprobador', importTags: 'Importar categorías', - importedTagsMessage: (columnCounts: number) => + importedTagsMessage: ({columnCounts}: ImportedTagsMessageParams) => `Hemos encontrado *${columnCounts} columnas* en su hoja de cálculo. Seleccione *Nombre* junto a la columna que contiene los nombres de las etiquetas. También puede seleccionar *Habilitado* junto a la columna que establece el estado de la etiqueta.`, }, taxes: { @@ -3267,7 +3339,7 @@ export default { updateTaxClaimableFailureMessage: 'La porción recuperable debe ser menor al monto del importe por distancia.', }, deleteTaxConfirmation: '¿Estás seguro de que quieres eliminar este impuesto?', - deleteMultipleTaxConfirmation: ({taxAmount}) => `¿Estás seguro de que quieres eliminar ${taxAmount} impuestos?`, + deleteMultipleTaxConfirmation: ({taxAmount}: TaxAmountParams) => `¿Estás seguro de que quieres eliminar ${taxAmount} impuestos?`, actions: { delete: 'Eliminar tasa', deleteMultiple: 'Eliminar tasas', @@ -3310,7 +3382,7 @@ export default { removeWorkspaceMemberButtonTitle: 'Eliminar del espacio de trabajo', removeGroupMemberButtonTitle: 'Eliminar del grupo', removeRoomMemberButtonTitle: 'Eliminar del chat', - removeMemberPrompt: ({memberName}: {memberName: string}) => `¿Estás seguro de que deseas eliminar a ${memberName}?`, + removeMemberPrompt: ({memberName}: RemoveMemberPromptParams) => `¿Estás seguro de que deseas eliminar a ${memberName}?`, removeMemberTitle: 'Eliminar miembro', transferOwner: 'Transferir la propiedad', makeMember: 'Hacer miembro', @@ -3323,7 +3395,7 @@ export default { genericRemove: 'Ha ocurrido un problema al eliminar al miembro del espacio de trabajo.', }, addedWithPrimary: 'Se agregaron algunos miembros con sus nombres de usuario principales.', - invitedBySecondaryLogin: ({secondaryLogin}) => `Agregado por nombre de usuario secundario ${secondaryLogin}.`, + invitedBySecondaryLogin: ({secondaryLogin}: SecondaryLoginParams) => `Agregado por nombre de usuario secundario ${secondaryLogin}.`, membersListTitle: 'Directorio de todos los miembros del espacio de trabajo.', importMembers: 'Importar miembros', }, @@ -3335,8 +3407,8 @@ export default { xero: 'Xero', netsuite: 'NetSuite', intacct: 'Sage Intacct', - connectionName: (integration: ConnectionName) => { - switch (integration) { + connectionName: ({connectionName}: ConnectionNameParams) => { + switch (connectionName) { case CONST.POLICY.CONNECTIONS.NAME.QBO: return 'Quickbooks Online'; case CONST.POLICY.CONNECTIONS.NAME.XERO: @@ -3353,20 +3425,21 @@ export default { errorODIntegration: 'Hay un error con una conexión que se ha configurado en Expensify Classic. ', goToODToFix: 'Ve a Expensify Classic para solucionar este problema.', setup: 'Configurar', - lastSync: (relativeDate: string) => `Recién sincronizado ${relativeDate}`, + lastSync: ({relativeDate}: LastSyncAccountingParams) => `Recién sincronizado ${relativeDate}`, import: 'Importar', export: 'Exportar', advanced: 'Avanzado', other: 'Otras integraciones', syncNow: 'Sincronizar ahora', disconnect: 'Desconectar', - disconnectTitle: (integration?: ConnectionName): string => { - const integrationName = integration && CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[integration] ? CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[integration] : 'integración'; + disconnectTitle: ({connectionName}: OptionalParam = {}) => { + const integrationName = + connectionName && CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[connectionName] ? CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[connectionName] : 'integración'; return `Desconectar ${integrationName}`; }, - connectTitle: (integrationToConnect: ConnectionName): string => `Conectar ${CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[integrationToConnect] ?? 'accounting integration'}`, - syncError: (integration?: ConnectionName): string => { - switch (integration) { + connectTitle: ({connectionName}: ConnectionNameParams) => `Conectar ${CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[connectionName] ?? 'accounting integration'}`, + syncError: ({connectionName}: OptionalParam = {}) => { + switch (connectionName) { case CONST.POLICY.CONNECTIONS.NAME.QBO: return 'No se puede conectar a QuickBooks Online.'; case CONST.POLICY.CONNECTIONS.NAME.XERO: @@ -3392,18 +3465,18 @@ export default { [CONST.INTEGRATION_ENTITY_MAP_TYPES.REPORT_FIELD]: 'Importado como campos de informe', [CONST.INTEGRATION_ENTITY_MAP_TYPES.NETSUITE_DEFAULT]: 'Predeterminado del empleado NetSuite', }, - disconnectPrompt: (currentIntegration?: ConnectionName): string => { + disconnectPrompt: ({connectionName}: OptionalParam = {}) => { const integrationName = - currentIntegration && CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[currentIntegration] ? CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[currentIntegration] : 'integración'; + connectionName && CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[connectionName] ? CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[connectionName] : 'integración'; return `¿Estás seguro de que quieres desconectar ${integrationName}?`; }, - connectPrompt: (integrationToConnect: ConnectionName): string => + connectPrompt: ({connectionName}: ConnectionNameParams) => `¿Estás seguro de que quieres conectar a ${ - CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[integrationToConnect] ?? 'esta integración contable' + CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[connectionName] ?? 'esta integración contable' }? Esto eliminará cualquier conexión contable existente.`, enterCredentials: 'Ingresa tus credenciales', connections: { - syncStageName: (stage: PolicyConnectionSyncStage) => { + syncStageName: ({stage}: SyncStageNameConnectionsParams) => { switch (stage) { case 'quickbooksOnlineImportCustomers': return 'Importando clientes'; @@ -3540,7 +3613,7 @@ export default { chooseBankAccount: 'Elige la cuenta bancaria con la que se conciliarán los pagos de tu Tarjeta Expensify.', accountMatches: 'Asegúrate de que esta cuenta coincide con ', settlementAccount: 'la cuenta de liquidación de tu Tarjeta Expensify ', - reconciliationWorks: (lastFourPAN: string) => `(que termina en ${lastFourPAN}) para que la conciliación continua funcione correctamente.`, + reconciliationWorks: ({lastFourPAN}: ReconciliationWorksParams) => `(que termina en ${lastFourPAN}) para que la conciliación continua funcione correctamente.`, }, }, card: { @@ -3638,7 +3711,10 @@ export default { rate: 'Tasa', addRate: 'Agregar tasa', trackTax: 'Impuesto de seguimiento', - deleteRates: ({count}: DistanceRateOperationsParams) => `Eliminar ${Str.pluralize('tasa', 'tasas', count)}`, + deleteRates: () => ({ + one: 'Eliminar tasa', + other: 'Eliminar tasas', + }), enableRates: ({count}: DistanceRateOperationsParams) => `Activar ${Str.pluralize('tasa', 'tasas', count)}`, disableRates: ({count}: DistanceRateOperationsParams) => `Desactivar ${Str.pluralize('tasa', 'tasas', count)}`, enableRate: 'Activar tasa', @@ -3708,19 +3784,19 @@ export default { amountOwedText: 'Esta cuenta tiene un saldo pendiente de un mes anterior.\n\n¿Quiere liquidar el saldo y hacerse cargo de la facturación de este espacio de trabajo?', ownerOwesAmountTitle: 'Saldo pendiente', ownerOwesAmountButtonText: 'Transferir saldo', - ownerOwesAmountText: ({email, amount}) => + ownerOwesAmountText: ({email, amount}: OwnerOwesAmountParams) => `La cuenta propietaria de este espacio de trabajo (${email}) tiene un saldo pendiente de un mes anterior.\n\n¿Desea transferir este monto (${amount}) para hacerse cargo de la facturación de este espacio de trabajo? tu tarjeta de pago se cargará inmediatamente.`, subscriptionTitle: 'Asumir la suscripción anual', subscriptionButtonText: 'Transferir suscripción', - subscriptionText: ({usersCount, finalCount}) => + subscriptionText: ({usersCount, finalCount}: ChangeOwnerSubscriptionParams) => `Al hacerse cargo de este espacio de trabajo se fusionará tu suscripción anual asociada con tu suscripción actual. Esto aumentará el tamaño de tu suscripción en ${usersCount} miembros, lo que hará que tu nuevo tamaño de suscripción sea ${finalCount}. ¿Te gustaria continuar?`, duplicateSubscriptionTitle: 'Alerta de suscripción duplicada', duplicateSubscriptionButtonText: 'Continuar', - duplicateSubscriptionText: ({email, workspaceName}) => + duplicateSubscriptionText: ({email, workspaceName}: ChangeOwnerDuplicateSubscriptionParams) => `Parece que estás intentando hacerte cargo de la facturación de los espacios de trabajo de ${email}, pero para hacerlo, primero debes ser administrador de todos sus espacios de trabajo.\n\nHaz clic en "Continuar" si solo quieres tomar sobrefacturación para el espacio de trabajo ${workspaceName}.\n\nSi desea hacerse cargo de la facturación de toda tu suscripción, pídales que lo agreguen como administrador a todos sus espacios de trabajo antes de hacerse cargo de la facturación.`, hasFailedSettlementsTitle: 'No se puede transferir la propiedad', hasFailedSettlementsButtonText: 'Entiendo', - hasFailedSettlementsText: ({email}) => + hasFailedSettlementsText: ({email}: ChangeOwnerHasFailedSettlementsParams) => `No puede hacerse cargo de la facturación porque ${email} tiene una liquidación vencida de la tarjeta Expensify. Avíseles que se comuniquen con concierge@expensify.com para resolver el problema. Luego, podrá hacerse cargo de la facturación de este espacio de trabajo.`, failedToClearBalanceTitle: 'Fallo al liquidar el saldo', failedToClearBalanceButtonText: 'OK', @@ -3735,7 +3811,7 @@ export default { exportAgainModal: { title: '¡Cuidado!', - description: (reportName: string, connectionName: ConnectionName) => + description: ({reportName, connectionName}: ExportAgainModalDescriptionParams) => `Los siguientes informes ya se han exportado a ${CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[connectionName]}:\n\n${reportName}\n\n¿Estás seguro de que deseas exportarlos de nuevo?`, confirmText: 'Sí, exportar de nuevo', cancelText: 'Cancelar', @@ -3798,7 +3874,7 @@ export default { upgradeToUnlock: 'Desbloquear esta función', completed: { headline: 'Has mejorado tu espacio de trabajo.', - successMessage: (policyName: string) => `Ha mejorado correctamente su espacio de trabajo ${policyName} al plan Control.`, + successMessage: ({policyName}: ReportPolicyNameParams) => `Ha mejorado correctamente su espacio de trabajo ${policyName} al plan Control.`, viewSubscription: 'Ver su suscripción', moreDetails: 'para obtener más información.', gotIt: 'Entendido, gracias.', @@ -3806,8 +3882,8 @@ export default { }, restrictedAction: { restricted: 'Restringido', - actionsAreCurrentlyRestricted: ({workspaceName}) => `Las acciones en el espacio de trabajo ${workspaceName} están actualmente restringidas`, - workspaceOwnerWillNeedToAddOrUpdatePaymentCard: ({workspaceOwnerName}) => + actionsAreCurrentlyRestricted: ({workspaceName}: ActionsAreCurrentlyRestricted) => `Las acciones en el espacio de trabajo ${workspaceName} están actualmente restringidas`, + workspaceOwnerWillNeedToAddOrUpdatePaymentCard: ({workspaceOwnerName}: WorkspaceOwnerWillNeedToAddOrUpdatePaymentCardParams) => `El propietario del espacio de trabajo, ${workspaceOwnerName} tendrá que añadir o actualizar la tarjeta de pago registrada para desbloquear nueva actividad en el espacio de trabajo.`, youWillNeedToAddOrUpdatePaymentCard: 'Debes añadir o actualizar la tarjeta de pago registrada para desbloquear nueva actividad en el espacio de trabajo.', addPaymentCardToUnlock: 'Añade una tarjeta para desbloquearlo!', @@ -3828,7 +3904,7 @@ export default { maxAge: 'Antigüedad máxima', maxExpenseAge: 'Antigüedad máxima de los gastos', maxExpenseAgeDescription: 'Marca los gastos de más de un número determinado de días.', - maxExpenseAgeDays: (age: number) => `${age} ${Str.pluralize('día', 'días', age)}`, + maxExpenseAgeDays: ({age}: AgeParams) => `${age} ${Str.pluralize('día', 'días', age)}`, billableDefault: 'Valor predeterminado facturable', billableDefaultDescription: 'Elige si los gastos en efectivo y con tarjeta de crédito deben ser facturables por defecto. Los gastos facturables se activan o desactivan en', billable: 'Facturable', @@ -3865,26 +3941,26 @@ export default { randomReportAuditDescription: 'Requiere que algunos informes sean aprobados manualmente, incluso si son elegibles para la aprobación automática.', autoPayApprovedReportsTitle: 'Pago automático de informes aprobados', autoPayApprovedReportsSubtitle: 'Configura qué informes de gastos pueden pagarse de forma automática.', - autoPayApprovedReportsLimitError: (currency?: string) => `Por favor, introduce un monto menor a ${currency ?? ''}20,000`, + autoPayApprovedReportsLimitError: ({currency}: AutoPayApprovedReportsLimitErrorParams = {}) => `Por favor, introduce un monto menor a ${currency ?? ''}20,000`, autoPayApprovedReportsLockedSubtitle: 'Ve a más funciones y habilita flujos de trabajo, luego agrega pagos para desbloquear esta función.', autoPayReportsUnderTitle: 'Pagar automáticamente informes por debajo de', autoPayReportsUnderDescription: 'Los informes de gastos totalmente conformes por debajo de esta cantidad se pagarán automáticamente.', unlockFeatureGoToSubtitle: 'Ir a', - unlockFeatureEnableWorkflowsSubtitle: (featureName: string) => `y habilita flujos de trabajo, luego agrega ${featureName} para desbloquear esta función.`, - enableFeatureSubtitle: (featureName: string) => `y habilita ${featureName} para desbloquear esta función.`, + unlockFeatureEnableWorkflowsSubtitle: ({featureName}: FeatureNameParams) => `y habilita flujos de trabajo, luego agrega ${featureName} para desbloquear esta función.`, + enableFeatureSubtitle: ({featureName}: FeatureNameParams) => `y habilita ${featureName} para desbloquear esta función.`, }, categoryRules: { title: 'Reglas de categoría', approver: 'Aprobador', requireDescription: 'Requerir descripción', descriptionHint: 'Sugerencia de descripción', - descriptionHintDescription: (categoryName: string) => + descriptionHintDescription: ({categoryName}: CategoryNameParams) => `Recuerda a los empleados que deben proporcionar información adicional para los gastos de “${categoryName}”. Esta sugerencia aparece en el campo de descripción en los gastos.`, descriptionHintLabel: 'Sugerencia', descriptionHintSubtitle: 'Consejo: ¡Cuanto más corta, mejor!', maxAmount: 'Importe máximo', flagAmountsOver: 'Señala importes superiores a', - flagAmountsOverDescription: (categoryName: string) => `Aplica a la categoría “${categoryName}”.`, + flagAmountsOverDescription: ({categoryName}: CategoryNameParams) => `Aplica a la categoría “${categoryName}”.`, flagAmountsOverSubtitle: 'Esto anula el importe máximo para todos los gastos.', expenseLimitTypes: { expense: 'Gasto individual', @@ -3894,7 +3970,7 @@ export default { }, requireReceiptsOver: 'Requerir recibos para importes superiores a', requireReceiptsOverList: { - default: (defaultAmount: string) => `${defaultAmount} ${CONST.DOT_SEPARATOR} Predeterminado`, + default: ({defaultAmount}: DefaultAmountParams) => `${defaultAmount} ${CONST.DOT_SEPARATOR} Predeterminado`, never: 'Nunca requerir recibos', always: 'Requerir recibos siempre', }, @@ -3958,8 +4034,8 @@ export default { }, }, workspaceActions: { - renamedWorkspaceNameAction: ({oldName, newName}) => `actualizó el nombre de este espacio de trabajo de ${oldName} a ${newName}`, - removedFromApprovalWorkflow: ({submittersNames}: {submittersNames: string[]}) => { + renamedWorkspaceNameAction: ({oldName, newName}: RenamedRoomActionParams) => `actualizó el nombre de este espacio de trabajo de ${oldName} a ${newName}`, + removedFromApprovalWorkflow: ({submittersNames}: RemovedFromApprovalWorkflowParams) => { let joinedNames = ''; if (submittersNames.length === 1) { joinedNames = submittersNames[0]; @@ -4012,7 +4088,7 @@ export default { deleteConfirmation: '¿Estás seguro de que quieres eliminar esta tarea?', }, statementPage: { - title: (year, monthName) => `Estado de cuenta de ${monthName} ${year}`, + title: ({year, monthName}: StatementTitleParams) => `Estado de cuenta de ${monthName} ${year}`, generatingPDF: 'Estamos generando tu PDF ahora mismo. ¡Por favor, vuelve más tarde!', }, keyboardShortcutsPage: { @@ -4062,8 +4138,8 @@ export default { filtersHeader: 'Filtros', filters: { date: { - before: (date?: string) => `Antes de ${date ?? ''}`, - after: (date?: string) => `Después de ${date ?? ''}`, + before: ({date}: OptionalParam = {}) => `Antes de ${date ?? ''}`, + after: ({date}: OptionalParam = {}) => `Después de ${date ?? ''}`, }, status: 'Estado', keyword: 'Palabra clave', @@ -4073,9 +4149,9 @@ export default { pinned: 'Fijado', unread: 'No leído', amount: { - lessThan: (amount?: string) => `Menos de ${amount ?? ''}`, - greaterThan: (amount?: string) => `Más que ${amount ?? ''}`, - between: (greaterThan: string, lessThan: string) => `Entre ${greaterThan} y ${lessThan}`, + lessThan: ({amount}: OptionalParam = {}) => `Menos de ${amount ?? ''}`, + greaterThan: ({amount}: OptionalParam = {}) => `Más que ${amount ?? ''}`, + between: ({greaterThan, lessThan}: FiltersAmountBetweenParams) => `Entre ${greaterThan} y ${lessThan}`, }, current: 'Actual', past: 'Anterior', @@ -4196,7 +4272,7 @@ export default { nonReimbursableLink: 'Ver los gastos de la tarjeta de empresa.', pending: ({label}: ExportedToIntegrationParams) => `comenzó a exportar este informe a ${label}...`, }, - integrationsMessage: (errorMessage: string, label: string) => `no se pudo exportar este informe a ${label} ("${errorMessage}").`, + integrationsMessage: ({label, errorMessage}: IntegrationSyncFailedParams) => `no se pudo exportar este informe a ${label} ("${errorMessage}").`, managerAttachReceipt: `agregó un recibo`, managerDetachReceipt: `quitó un recibo`, markedReimbursed: ({amount, currency}: MarkedReimbursedParams) => `pagó ${currency}${amount} en otro lugar`, @@ -4213,11 +4289,11 @@ export default { stripePaid: ({amount, currency}: StripePaidParams) => `pagado ${currency}${amount}`, takeControl: `tomó el control`, unapproved: ({amount, currency}: UnapprovedParams) => `no aprobado ${currency}${amount}`, - integrationSyncFailed: (label: string, errorMessage: string) => `no se pudo sincronizar con ${label} ("${errorMessage}")`, - addEmployee: (email: string, role: string) => `agregó a ${email} como ${role === 'user' ? 'miembro' : 'administrador'}`, - updateRole: (email: string, currentRole: string, newRole: string) => + integrationSyncFailed: ({label, errorMessage}: IntegrationSyncFailedParams) => `no se pudo sincronizar con ${label} ("${errorMessage}")`, + addEmployee: ({email, role}: AddEmployeeParams) => `agregó a ${email} como ${role === 'user' ? 'miembro' : 'administrador'}`, + updateRole: ({email, currentRole, newRole}: UpdateRoleParams) => `actualicé el rol ${email} de ${currentRole === 'user' ? 'miembro' : 'administrador'} a ${newRole === 'user' ? 'miembro' : 'administrador'}`, - removeMember: (email: string, role: string) => `eliminado ${role === 'user' ? 'miembro' : 'administrador'} ${email}`, + removeMember: ({email, role}: AddEmployeeParams) => `eliminado ${role === 'user' ? 'miembro' : 'administrador'} ${email}`, }, }, }, @@ -4898,9 +4974,9 @@ export default { allTagLevelsRequired: 'Todas las etiquetas son obligatorias', autoReportedRejectedExpense: ({rejectedBy, rejectReason}: ViolationsAutoReportedRejectedExpenseParams) => `${rejectedBy} rechazó la solicitud y comentó "${rejectReason}"`, billableExpense: 'La opción facturable ya no es válida', - cashExpenseWithNoReceipt: ({formattedLimit}: ViolationsCashExpenseWithNoReceiptParams) => `Recibo obligatorio para cantidades mayores de ${formattedLimit}`, + cashExpenseWithNoReceipt: ({formattedLimit}: ViolationsCashExpenseWithNoReceiptParams = {}) => `Recibo obligatorio para cantidades mayores de ${formattedLimit}`, categoryOutOfPolicy: 'La categoría ya no es válida', - conversionSurcharge: ({surcharge}: ViolationsConversionSurchargeParams = {}) => `${surcharge}% de recargo aplicado`, + conversionSurcharge: ({surcharge}: ViolationsConversionSurchargeParams) => `${surcharge}% de recargo aplicado`, customUnitOutOfPolicy: 'Tasa inválida para este espacio de trabajo', duplicatedTransaction: 'Duplicado', fieldRequired: 'Los campos del informe son obligatorios', @@ -4909,7 +4985,7 @@ export default { maxAge: ({maxAge}: ViolationsMaxAgeParams) => `Fecha de más de ${maxAge} días`, missingCategory: 'Falta categoría', missingComment: 'Descripción obligatoria para la categoría seleccionada', - missingTag: ({tagName}: ViolationsMissingTagParams) => `Falta ${tagName ?? 'etiqueta'}`, + missingTag: ({tagName}: ViolationsMissingTagParams = {}) => `Falta ${tagName ?? 'etiqueta'}`, modifiedAmount: ({type, displayPercentVariance}: ViolationsModifiedAmountParams) => { switch (type) { case 'distance': @@ -4960,10 +5036,10 @@ export default { return ''; }, smartscanFailed: 'No se pudo escanear el recibo. Introduce los datos manualmente', - someTagLevelsRequired: ({tagName}: ViolationsTagOutOfPolicyParams) => `Falta ${tagName ?? 'Tag'}`, - tagOutOfPolicy: ({tagName}: ViolationsTagOutOfPolicyParams) => `La etiqueta ${tagName ? `${tagName} ` : ''}ya no es válida`, + someTagLevelsRequired: ({tagName}: ViolationsTagOutOfPolicyParams = {}) => `Falta ${tagName ?? 'Tag'}`, + tagOutOfPolicy: ({tagName}: ViolationsTagOutOfPolicyParams = {}) => `La etiqueta ${tagName ? `${tagName} ` : ''}ya no es válida`, taxAmountChanged: 'El importe del impuesto fue modificado', - taxOutOfPolicy: ({taxName}: ViolationsTaxOutOfPolicyParams) => `${taxName ?? 'El impuesto'} ya no es válido`, + taxOutOfPolicy: ({taxName}: ViolationsTaxOutOfPolicyParams = {}) => `${taxName ?? 'El impuesto'} ya no es válido`, taxRateChanged: 'La tasa de impuesto fue modificada', taxRequired: 'Falta la tasa de impuesto', none: 'Ninguno', @@ -4980,7 +5056,7 @@ export default { hold: 'Bloqueado', }, reportViolations: { - [CONST.REPORT_VIOLATIONS.FIELD_REQUIRED]: (fieldName: string) => `${fieldName} es obligatorio`, + [CONST.REPORT_VIOLATIONS.FIELD_REQUIRED]: ({fieldName}: RequiredFieldParams) => `${fieldName} es obligatorio`, }, violationDismissal: { rter: { @@ -5035,12 +5111,12 @@ export default { authenticatePaymentCard: 'Autenticar tarjeta de pago', mobileReducedFunctionalityMessage: 'No puedes hacer cambios en tu suscripción en la aplicación móvil.', badge: { - freeTrial: ({numOfDays}) => `Prueba gratuita: ${numOfDays === 1 ? `queda 1 día` : `quedan ${numOfDays} días`}`, + freeTrial: ({numOfDays}: BadgeFreeTrialParams) => `Prueba gratuita: ${numOfDays === 1 ? `queda 1 día` : `quedan ${numOfDays} días`}`, }, billingBanner: { policyOwnerAmountOwed: { title: 'Tu información de pago está desactualizada', - subtitle: ({date}) => `Actualiza tu tarjeta de pago antes del ${date} para continuar utilizando todas tus herramientas favoritas`, + subtitle: ({date}: BillingBannerSubtitleWithDateParams) => `Actualiza tu tarjeta de pago antes del ${date} para continuar utilizando todas tus herramientas favoritas`, }, policyOwnerAmountOwedOverdue: { title: 'Tu información de pago está desactualizada', @@ -5048,7 +5124,7 @@ export default { }, policyOwnerUnderInvoicing: { title: 'Tu información de pago está desactualizada', - subtitle: ({date}) => `Tu pago está vencido. Por favor, paga tu factura antes del ${date} para evitar la interrupción del servicio.`, + subtitle: ({date}: BillingBannerSubtitleWithDateParams) => `Tu pago está vencido. Por favor, paga tu factura antes del ${date} para evitar la interrupción del servicio.`, }, policyOwnerUnderInvoicingOverdue: { title: 'Tu información de pago está desactualizada', @@ -5056,22 +5132,23 @@ export default { }, billingDisputePending: { title: 'No se ha podido realizar el cobro a tu tarjeta', - subtitle: ({amountOwed, cardEnding}) => + subtitle: ({amountOwed, cardEnding}: BillingBannerDisputePendingParams) => `Has impugnado el cargo ${amountOwed} en la tarjeta terminada en ${cardEnding}. Tu cuenta estará bloqueada hasta que se resuelva la disputa con tu banco.`, }, cardAuthenticationRequired: { title: 'No se ha podido realizar el cobro a tu tarjeta', - subtitle: ({cardEnding}) => + subtitle: ({cardEnding}: BillingBannerCardAuthenticationRequiredParams) => `Tu tarjeta de pago no ha sido autenticada completamente. Por favor, completa el proceso de autenticación para activar tu tarjeta de pago que termina en ${cardEnding}.`, }, insufficientFunds: { title: 'No se ha podido realizar el cobro a tu tarjeta', - subtitle: ({amountOwed}) => + subtitle: ({amountOwed}: BillingBannerInsufficientFundsParams) => `Tu tarjeta de pago fue rechazada por falta de fondos. Vuelve a intentarlo o añade una nueva tarjeta de pago para liquidar tu saldo pendiente de ${amountOwed}.`, }, cardExpired: { title: 'No se ha podido realizar el cobro a tu tarjeta', - subtitle: ({amountOwed}) => `Tu tarjeta de pago ha expirado. Por favor, añade una nueva tarjeta de pago para liquidar tu saldo pendiente de ${amountOwed}.`, + subtitle: ({amountOwed}: BillingBannerCardExpiredParams) => + `Tu tarjeta de pago ha expirado. Por favor, añade una nueva tarjeta de pago para liquidar tu saldo pendiente de ${amountOwed}.`, }, cardExpireSoon: { title: 'Tu tarjeta caducará pronto', @@ -5087,7 +5164,7 @@ export default { subtitle: 'Antes de volver a intentarlo, llama directamente a tu banco para que autorice los cargos de Expensify y elimine las retenciones. De lo contrario, añade una tarjeta de pago diferente.', }, - cardOnDispute: ({amountOwed, cardEnding}) => + cardOnDispute: ({amountOwed, cardEnding}: BillingBannerCardOnDisputeParams) => `Has impugnado el cargo ${amountOwed} en la tarjeta terminada en ${cardEnding}. Tu cuenta estará bloqueada hasta que se resuelva la disputa con tu banco.`, preTrial: { title: 'Iniciar una prueba gratuita', @@ -5096,7 +5173,7 @@ export default { subtitleEnd: 'para que tu equipo pueda empezar a enviar gastos.', }, trialStarted: { - title: ({numOfDays}) => `Prueba gratuita: ¡${numOfDays === 1 ? `queda 1 día` : `quedan ${numOfDays} días`}!`, + title: ({numOfDays}: TrialStartedTitleParams) => `Prueba gratuita: ¡${numOfDays === 1 ? `queda 1 día` : `quedan ${numOfDays} días`}!`, subtitle: 'Añade una tarjeta de pago para seguir utilizando tus funciones favoritas.', }, trialEnded: { @@ -5108,9 +5185,9 @@ export default { title: 'Pago', subtitle: 'Añade una tarjeta para pagar tu suscripción a Expensify.', addCardButton: 'Añade tarjeta de pago', - cardNextPayment: ({nextPaymentDate}) => `Tu próxima fecha de pago es ${nextPaymentDate}.`, - cardEnding: ({cardNumber}) => `Tarjeta terminada en ${cardNumber}`, - cardInfo: ({name, expiration, currency}) => `Nombre: ${name}, Expiración: ${expiration}, Moneda: ${currency}`, + cardNextPayment: ({nextPaymentDate}: CardNextPaymentParams) => `Tu próxima fecha de pago es ${nextPaymentDate}.`, + cardEnding: ({cardNumber}: CardEndingParams) => `Tarjeta terminada en ${cardNumber}`, + cardInfo: ({name, expiration, currency}: CardInfoParams) => `Nombre: ${name}, Expiración: ${expiration}, Moneda: ${currency}`, changeCard: 'Cambiar tarjeta de pago', changeCurrency: 'Cambiar moneda de pago', cardNotFound: 'No se ha añadido ninguna tarjeta de pago', @@ -5129,8 +5206,8 @@ export default { title: 'Tu plan', collect: { title: 'Recolectar', - priceAnnual: ({lower, upper}) => `Desde ${lower}/miembro activo con la Tarjeta Expensify, ${upper}/miembro activo sin la Tarjeta Expensify.`, - pricePayPerUse: ({lower, upper}) => `Desde ${lower}/miembro activo con la Tarjeta Expensify, ${upper}/miembro activo sin la Tarjeta Expensify.`, + priceAnnual: ({lower, upper}: YourPlanPriceParams) => `Desde ${lower}/miembro activo con la Tarjeta Expensify, ${upper}/miembro activo sin la Tarjeta Expensify.`, + pricePayPerUse: ({lower, upper}: YourPlanPriceParams) => `Desde ${lower}/miembro activo con la Tarjeta Expensify, ${upper}/miembro activo sin la Tarjeta Expensify.`, benefit1: 'SmartScans ilimitados y seguimiento de la distancia', benefit2: 'Tarjetas Expensify con Límites Inteligentes', benefit3: 'Pago de facturas y facturación', @@ -5141,8 +5218,8 @@ export default { }, control: { title: 'Control', - priceAnnual: ({lower, upper}) => `Desde ${lower}/miembro activo con la Tarjeta Expensify, ${upper}/miembro activo sin la Tarjeta Expensify.`, - pricePayPerUse: ({lower, upper}) => `Desde ${lower}/miembro activo con la Tarjeta Expensify, ${upper}/miembro activo sin la Tarjeta Expensify.`, + priceAnnual: ({lower, upper}: YourPlanPriceParams) => `Desde ${lower}/miembro activo con la Tarjeta Expensify, ${upper}/miembro activo sin la Tarjeta Expensify.`, + pricePayPerUse: ({lower, upper}: YourPlanPriceParams) => `Desde ${lower}/miembro activo con la Tarjeta Expensify, ${upper}/miembro activo sin la Tarjeta Expensify.`, benefit1: 'Todo en Recolectar, más:', benefit2: 'Integraciones con NetSuite y Sage Intacct', benefit3: 'Sincronización de Certinia y Workday', @@ -5173,10 +5250,10 @@ export default { note: 'Nota: Un miembro activo es cualquiera que haya creado, editado, enviado, aprobado, reembolsado, o exportado datos de gastos vinculados al espacio de trabajo de tu empresa.', confirmDetails: 'Confirma los datos de tu nueva suscripción anual:', subscriptionSize: 'Tamaño de suscripción', - activeMembers: ({size}) => `${size} miembros activos/mes`, + activeMembers: ({size}: SubscriptionSizeParams) => `${size} miembros activos/mes`, subscriptionRenews: 'Renovación de la suscripción', youCantDowngrade: 'No puedes bajar de categoría durante tu suscripción anual.', - youAlreadyCommitted: ({size, date}) => + youAlreadyCommitted: ({size, date}: SubscriptionCommitmentParams) => `Ya se ha comprometido a un tamaño de suscripción anual de ${size} miembros activos al mes hasta el ${date}. Puede cambiar a una suscripción de pago por uso en ${date} desactivando la auto-renovación.`, error: { size: 'Por favor ingrese un tamaño de suscripción valido.', @@ -5193,13 +5270,13 @@ export default { title: 'Configuración de suscripción', autoRenew: 'Auto-renovación', autoIncrease: 'Auto-incremento', - saveUpTo: ({amountWithCurrency}) => `Ahorre hasta ${amountWithCurrency} al mes por miembro activo`, + saveUpTo: ({amountWithCurrency}: SubscriptionSettingsSaveUpToParams) => `Ahorre hasta ${amountWithCurrency} al mes por miembro activo`, automaticallyIncrease: 'Aumenta automáticamente tus plazas anuales para dar lugar a los miembros activos que superen el tamaño de tu suscripción. Nota: Esto ampliará la fecha de finalización de tu suscripción anual.', disableAutoRenew: 'Desactivar auto-renovación', helpUsImprove: 'Ayúdanos a mejorar Expensify', whatsMainReason: '¿Cuál es la razón principal por la que deseas desactivar la auto-renovación?', - renewsOn: ({date}) => `Se renovará el ${date}.`, + renewsOn: ({date}: SubscriptionSettingsRenewsOnParams) => `Se renovará el ${date}.`, }, requestEarlyCancellation: { title: 'Solicitar cancelación anticipada', @@ -5248,7 +5325,7 @@ export default { addCopilot: 'Agregar copiloto', membersCanAccessYourAccount: 'Estos miembros pueden acceder a tu cuenta:', youCanAccessTheseAccounts: 'Puedes acceder a estas cuentas a través del conmutador de cuentas:', - role: (role?: string): string => { + role: ({role}: OptionalParam = {}) => { switch (role) { case CONST.DELEGATE_ROLE.ALL: return 'Completo'; @@ -5262,7 +5339,7 @@ export default { accessLevel: 'Nivel de acceso', confirmCopilot: 'Confirma tu copiloto a continuación.', accessLevelDescription: 'Elige un nivel de acceso a continuación. Tanto el acceso Completo como el Limitado permiten a los copilotos ver todas las conversaciones y gastos.', - roleDescription: (role?: string): string => { + roleDescription: ({role}: OptionalParam = {}) => { switch (role) { case CONST.DELEGATE_ROLE.ALL: return 'Permite a otro miembro realizar todas las acciones en tu cuenta, en tu nombre. Incluye chat, presentaciones, aprobaciones, pagos, actualizaciones de configuración y más.'; @@ -5288,9 +5365,9 @@ export default { nothingToPreview: 'Nada que previsualizar', editJson: 'Editar JSON:', preview: 'Previa:', - missingProperty: ({propertyName}) => `Falta ${propertyName}`, - invalidProperty: ({propertyName, expectedType}) => `Propiedad inválida: ${propertyName} - Esperado: ${expectedType}`, - invalidValue: ({expectedValues}) => `Valor inválido - Esperado: ${expectedValues}`, + missingProperty: ({propertyName}: MissingPropertyParams) => `Falta ${propertyName}`, + invalidProperty: ({propertyName, expectedType}: InvalidPropertyParams) => `Propiedad inválida: ${propertyName} - Esperado: ${expectedType}`, + invalidValue: ({expectedValues}: InvalidValueParams) => `Valor inválido - Esperado: ${expectedValues}`, missingValue: 'Valor en falta', createReportAction: 'Crear Report Action', reportAction: 'Report Action', @@ -5305,4 +5382,6 @@ export default { time: 'Hora', none: 'Ninguno', }, -} satisfies EnglishTranslation; +}; + +export default translations satisfies TranslationDeepObject; diff --git a/src/languages/params.ts b/src/languages/params.ts new file mode 100644 index 000000000000..20e7ac34185c --- /dev/null +++ b/src/languages/params.ts @@ -0,0 +1,751 @@ +import type {OnyxInputOrEntry, ReportAction} from '@src/types/onyx'; +import type {DelegateRole} from '@src/types/onyx/Account'; +import type {ConnectionName, PolicyConnectionSyncStage, SageIntacctMappingName, Unit} from '@src/types/onyx/Policy'; +import type {ViolationDataType} from '@src/types/onyx/TransactionViolation'; + +type AddressLineParams = { + lineNumber: number; +}; + +type CharacterLimitParams = { + limit: number | string; +}; + +type AssigneeParams = { + assignee: string; +}; + +type CharacterLengthLimitParams = { + limit: number; + length: number; +}; + +type ZipCodeExampleFormatParams = { + zipSampleFormat: string; +}; + +type LoggedInAsParams = { + email: string; +}; + +type SignUpNewFaceCodeParams = { + login: string; +}; + +type WelcomeEnterMagicCodeParams = { + login: string; +}; + +type AlreadySignedInParams = { + email: string; +}; + +type GoBackMessageParams = { + provider: string; +}; + +type LocalTimeParams = { + user: string; + time: string; +}; + +type EditActionParams = { + action: OnyxInputOrEntry; +}; + +type DeleteActionParams = { + action: OnyxInputOrEntry; +}; + +type DeleteConfirmationParams = { + action: OnyxInputOrEntry; +}; + +type BeginningOfChatHistoryDomainRoomPartOneParams = { + domainRoom: string; +}; + +type BeginningOfChatHistoryAdminRoomPartOneParams = { + workspaceName: string; +}; + +type BeginningOfChatHistoryAnnounceRoomPartOneParams = { + workspaceName: string; +}; + +type BeginningOfChatHistoryAnnounceRoomPartTwo = { + workspaceName: string; +}; + +type WelcomeToRoomParams = { + roomName: string; +}; + +type UsePlusButtonParams = { + additionalText: string; +}; + +type ReportArchiveReasonsClosedParams = { + displayName: string; +}; + +type ReportArchiveReasonsMergedParams = { + displayName: string; + oldDisplayName: string; +}; + +type ReportArchiveReasonsRemovedFromPolicyParams = { + displayName: string; + policyName: string; + shouldUseYou?: boolean; +}; + +type ReportPolicyNameParams = { + policyName: string; +}; + +type ReportArchiveReasonsInvoiceReceiverPolicyDeletedParams = { + policyName: string; +}; + +type RequestCountParams = { + count: number; + scanningReceipts: number; + pendingReceipts: number; +}; + +type SettleExpensifyCardParams = { + formattedAmount: string; +}; + +type RequestAmountParams = {amount: string}; + +type RequestedAmountMessageParams = {formattedAmount: string; comment?: string}; + +type SplitAmountParams = {amount: string}; + +type DidSplitAmountMessageParams = {formattedAmount: string; comment: string}; + +type UserSplitParams = {amount: string}; + +type PayerOwesAmountParams = {payer: string; amount: number | string; comment?: string}; + +type PayerOwesParams = {payer: string}; + +type CompanyCardFeedNameParams = {feedName: string}; + +type PayerPaidAmountParams = {payer?: string; amount: number | string}; + +type ApprovedAmountParams = {amount: number | string}; + +type ForwardedAmountParams = {amount: number | string}; + +type ManagerApprovedParams = {manager: string}; + +type ManagerApprovedAmountParams = {manager: string; amount: number | string}; + +type PayerPaidParams = {payer: string}; + +type PayerSettledParams = {amount: number | string}; + +type WaitingOnBankAccountParams = {submitterDisplayName: string}; + +type CanceledRequestParams = {amount: string; submitterDisplayName: string}; + +type AdminCanceledRequestParams = {manager: string; amount: string}; + +type SettledAfterAddedBankAccountParams = {submitterDisplayName: string; amount: string}; + +type PaidElsewhereWithAmountParams = {payer?: string; amount: string}; + +type PaidWithExpensifyWithAmountParams = {payer?: string; amount: string}; + +type ThreadRequestReportNameParams = {formattedAmount: string; comment: string}; + +type ThreadSentMoneyReportNameParams = {formattedAmount: string; comment: string}; + +type SizeExceededParams = {maxUploadSizeInMB: number}; + +type ResolutionConstraintsParams = {minHeightInPx: number; minWidthInPx: number; maxHeightInPx: number; maxWidthInPx: number}; + +type NotAllowedExtensionParams = {allowedExtensions: string[]}; + +type EnterMagicCodeParams = {contactMethod: string}; + +type TransferParams = {amount: string}; + +type InstantSummaryParams = {rate: string; minAmount: string}; + +type NotYouParams = {user: string}; + +type DateShouldBeBeforeParams = {dateString: string}; + +type DateShouldBeAfterParams = {dateString: string}; + +type WeSentYouMagicSignInLinkParams = {login: string; loginType: string}; + +type ToValidateLoginParams = {primaryLogin: string; secondaryLogin: string}; + +type NoLongerHaveAccessParams = {primaryLogin: string}; + +type OurEmailProviderParams = {login: string}; + +type ConfirmThatParams = {login: string}; + +type UntilTimeParams = {time: string}; + +type StepCounterParams = {step: number; total?: number; text?: string}; + +type UserIsAlreadyMemberParams = {login: string; name: string}; + +type GoToRoomParams = {roomName: string}; + +type WelcomeNoteParams = {workspaceName: string}; + +type RoomNameReservedErrorParams = {reservedName: string}; + +type RenamedRoomActionParams = {oldName: string; newName: string}; + +type RoomRenamedToParams = {newName: string}; + +type OOOEventSummaryFullDayParams = {summary: string; dayCount: number; date: string}; + +type OOOEventSummaryPartialDayParams = {summary: string; timePeriod: string; date: string}; + +type ParentNavigationSummaryParams = {reportName?: string; workspaceName?: string}; + +type SetTheRequestParams = {valueName: string; newValueToDisplay: string}; + +type SetTheDistanceMerchantParams = {translatedChangedField: string; newMerchant: string; newAmountToDisplay: string}; + +type RemovedTheRequestParams = {valueName: string; oldValueToDisplay: string}; + +type UpdatedTheRequestParams = {valueName: string; newValueToDisplay: string; oldValueToDisplay: string}; + +type UpdatedTheDistanceMerchantParams = {translatedChangedField: string; newMerchant: string; oldMerchant: string; newAmountToDisplay: string; oldAmountToDisplay: string}; + +type FormattedMaxLengthParams = {formattedMaxLength: string}; + +type WalletProgramParams = {walletProgram: string}; + +type ViolationsAutoReportedRejectedExpenseParams = {rejectedBy: string; rejectReason: string}; + +type ViolationsCashExpenseWithNoReceiptParams = {formattedLimit?: string} | undefined; + +type ViolationsConversionSurchargeParams = {surcharge: number}; + +type ViolationsInvoiceMarkupParams = {invoiceMarkup: number}; + +type ViolationsMaxAgeParams = {maxAge: number}; + +type ViolationsMissingTagParams = {tagName?: string} | undefined; + +type ViolationsModifiedAmountParams = {type?: ViolationDataType; displayPercentVariance?: number}; + +type ViolationsOverAutoApprovalLimitParams = {formattedLimit: string}; + +type ViolationsOverCategoryLimitParams = {formattedLimit: string}; + +type ViolationsOverLimitParams = {formattedLimit: string}; + +type ViolationsPerDayLimitParams = {formattedLimit: string}; + +type ViolationsReceiptRequiredParams = {formattedLimit?: string; category?: string}; + +type ViolationsRterParams = { + brokenBankConnection: boolean; + isAdmin: boolean; + email?: string; + isTransactionOlderThan7Days: boolean; + member?: string; +}; + +type ViolationsTagOutOfPolicyParams = {tagName?: string} | undefined; + +type ViolationsTaxOutOfPolicyParams = {taxName?: string} | undefined; + +type PaySomeoneParams = {name?: string} | undefined; + +type TaskCreatedActionParams = {title: string}; + +type OptionalParam = Partial; + +type TermsParams = {amount: string}; + +type ElectronicFundsParams = {percentage: string; amount: string}; + +type LogSizeParams = {size: number}; + +type LogSizeAndDateParams = {size: number; date: string}; + +type HeldRequestParams = {comment: string}; + +type DistanceRateOperationsParams = {count: number}; + +type ReimbursementRateParams = {unit: Unit}; + +type ConfirmHoldExpenseParams = {transactionCount: number}; + +type ChangeFieldParams = {oldValue?: string; newValue: string; fieldName: string}; + +type ChangePolicyParams = {fromPolicy: string; toPolicy: string}; + +type ChangeTypeParams = {oldType: string; newType: string}; + +type DelegateSubmitParams = {delegateUser: string; originalManager: string}; + +type AccountOwnerParams = {accountOwnerEmail: string}; + +type ExportedToIntegrationParams = {label: string; markedManually?: boolean; inProgress?: boolean; lastModified?: string}; + +type IntegrationsMessageParams = { + label: string; + result: { + code?: number; + messages?: string[]; + title?: string; + link?: { + url: string; + text: string; + }; + }; +}; + +type MarkedReimbursedParams = {amount: string; currency: string}; + +type MarkReimbursedFromIntegrationParams = {amount: string; currency: string}; + +type ShareParams = {to: string}; + +type UnshareParams = {to: string}; + +type StripePaidParams = {amount: string; currency: string}; + +type UnapprovedParams = {amount: string; currency: string}; + +type RemoveMembersWarningPrompt = { + memberName: string; + ownerName: string; +}; + +type RemoveMemberPromptParams = { + memberName: string; +}; + +type DeleteExpenseTranslationParams = { + count: number; +}; + +type IssueVirtualCardParams = { + assignee: string; + link: string; +}; + +type ApprovalWorkflowErrorParams = { + name1: string; + name2: string; +}; + +type ConnectionNameParams = { + connectionName: ConnectionName; +}; + +type LastSyncDateParams = { + connectionName: string; + formattedDate: string; +}; + +type CustomersOrJobsLabelParams = { + importFields: string[]; + importType: string; +}; + +type ExportAgainModalDescriptionParams = { + reportName: string; + connectionName: ConnectionName; +}; + +type IntegrationSyncFailedParams = {label: string; errorMessage: string}; + +type AddEmployeeParams = {email: string; role: string}; + +type UpdateRoleParams = {email: string; currentRole: string; newRole: string}; + +type RemoveMemberParams = {email: string; role: string}; + +type DateParams = {date: string}; + +type FiltersAmountBetweenParams = {greaterThan: string; lessThan: string}; + +type StatementPageTitleParams = {year: string | number; monthName: string}; + +type DisconnectPromptParams = {currentIntegration?: ConnectionName} | undefined; + +type DisconnectTitleParams = {integration?: ConnectionName} | undefined; + +type AmountWithCurrencyParams = {amountWithCurrency: string}; + +type SelectedNumberParams = {selectedNumber: number}; + +type LowerUpperParams = {lower: string; upper: string}; + +type CategoryNameParams = {categoryName: string}; + +type TaxAmountParams = {taxAmount: number}; + +type SecondaryLoginParams = {secondaryLogin: string}; + +type OwnerOwesAmountParams = {amount: string; email: string}; + +type ChangeOwnerSubscriptionParams = {usersCount: number; finalCount: number}; + +type ChangeOwnerDuplicateSubscriptionParams = {email: string; workspaceName: string}; + +type ChangeOwnerHasFailedSettlementsParams = {email: string}; + +type ActionsAreCurrentlyRestricted = {workspaceName: string}; + +type WorkspaceOwnerWillNeedToAddOrUpdatePaymentCardParams = {workspaceOwnerName: string}; + +type RenamedWorkspaceNameActionParams = {oldName: string; newName: string}; + +type StatementTitleParams = {year: number | string; monthName: string}; + +type BadgeFreeTrialParams = {numOfDays: number}; + +type BillingBannerSubtitleWithDateParams = {date: string}; + +type BillingBannerDisputePendingParams = {amountOwed: number; cardEnding: string}; + +type BillingBannerCardAuthenticationRequiredParams = {cardEnding: string}; + +type BillingBannerInsufficientFundsParams = {amountOwed: number}; + +type BillingBannerCardExpiredParams = {amountOwed: number}; + +type BillingBannerCardOnDisputeParams = {amountOwed: string; cardEnding: string}; + +type TrialStartedTitleParams = {numOfDays: number}; + +type CardNextPaymentParams = {nextPaymentDate: string}; + +type CardEndingParams = {cardNumber: string}; + +type CardInfoParams = {name: string; expiration: string; currency: string}; + +type YourPlanPriceParams = {lower: string; upper: string}; + +type SubscriptionSizeParams = {size: number}; + +type SubscriptionCommitmentParams = {size: number; date: string}; + +type SubscriptionSettingsSaveUpToParams = {amountWithCurrency: string}; + +type SubscriptionSettingsRenewsOnParams = {date: string}; + +type UnapproveWithIntegrationWarningParams = {accountingIntegration: string}; + +type IncorrectZipFormatParams = {zipFormat?: string} | undefined; + +type ExportIntegrationSelectedParams = {connectionName: ConnectionName}; + +type DefaultVendorDescriptionParams = {isReimbursable: boolean}; + +type RequiredFieldParams = {fieldName: string}; + +type ImportFieldParams = {importField: string}; + +type DimensionsCountParams = {dimensionsCount: number}; + +type IntacctMappingTitleParams = {mappingName: SageIntacctMappingName}; + +type AgeParams = {age: number}; + +type LastSyncAccountingParams = {relativeDate: string}; + +type SyncStageNameConnectionsParams = {stage: PolicyConnectionSyncStage}; + +type ReconciliationWorksParams = {lastFourPAN: string}; + +type DelegateRoleParams = {role: DelegateRole}; + +type RoleNamesParams = {role: string}; + +type AssignCardParams = { + assignee: string; + feed: string; +}; + +type SpreadSheetColumnParams = { + name: string; +}; + +type SpreadFieldNameParams = { + fieldName: string; +}; + +type SpreadCategoriesParams = { + categories: number; +}; + +type AssignedYouCardParams = { + assigner: string; +}; + +type FeatureNameParams = { + featureName: string; +}; + +type AutoPayApprovedReportsLimitErrorParams = { + currency?: string; +}; + +type DefaultAmountParams = { + defaultAmount: string; +}; + +type RemovedFromApprovalWorkflowParams = { + submittersNames: string[]; +}; + +type IntegrationExportParams = { + integration: string; + type?: string; +}; + +type ConnectionParams = { + connection: string; +}; + +type MissingPropertyParams = { + propertyName: string; +}; + +type InvalidPropertyParams = { + propertyName: string; + expectedType: string; +}; + +type InvalidValueParams = { + expectedValues: string; +}; + +type ImportTagsSuccessfullDescriptionParams = { + tags: number; +}; + +type ImportedTagsMessageParams = { + columnCounts: number; +}; + +type ImportMembersSuccessfullDescriptionParams = { + members: number; +}; + +type AuthenticationErrorParams = { + connectionName: string; +}; + +export type { + AuthenticationErrorParams, + ImportMembersSuccessfullDescriptionParams, + ImportedTagsMessageParams, + ImportTagsSuccessfullDescriptionParams, + MissingPropertyParams, + InvalidPropertyParams, + InvalidValueParams, + ConnectionParams, + IntegrationExportParams, + RemovedFromApprovalWorkflowParams, + DefaultAmountParams, + AutoPayApprovedReportsLimitErrorParams, + FeatureNameParams, + SpreadSheetColumnParams, + SpreadFieldNameParams, + AssignedYouCardParams, + SpreadCategoriesParams, + DelegateRoleParams, + ReconciliationWorksParams, + LastSyncAccountingParams, + SyncStageNameConnectionsParams, + AgeParams, + RequiredFieldParams, + DimensionsCountParams, + IntacctMappingTitleParams, + ImportFieldParams, + AssigneeParams, + DefaultVendorDescriptionParams, + ExportIntegrationSelectedParams, + UnapproveWithIntegrationWarningParams, + IncorrectZipFormatParams, + CardNextPaymentParams, + CardEndingParams, + CardInfoParams, + YourPlanPriceParams, + SubscriptionSizeParams, + SubscriptionCommitmentParams, + SubscriptionSettingsSaveUpToParams, + SubscriptionSettingsRenewsOnParams, + BadgeFreeTrialParams, + BillingBannerSubtitleWithDateParams, + BillingBannerDisputePendingParams, + BillingBannerCardAuthenticationRequiredParams, + BillingBannerInsufficientFundsParams, + BillingBannerCardExpiredParams, + BillingBannerCardOnDisputeParams, + TrialStartedTitleParams, + RemoveMemberPromptParams, + StatementTitleParams, + RenamedWorkspaceNameActionParams, + WorkspaceOwnerWillNeedToAddOrUpdatePaymentCardParams, + ActionsAreCurrentlyRestricted, + ChangeOwnerHasFailedSettlementsParams, + OwnerOwesAmountParams, + ChangeOwnerDuplicateSubscriptionParams, + ChangeOwnerSubscriptionParams, + SecondaryLoginParams, + TaxAmountParams, + CategoryNameParams, + SelectedNumberParams, + AmountWithCurrencyParams, + LowerUpperParams, + LogSizeAndDateParams, + AddressLineParams, + AdminCanceledRequestParams, + AlreadySignedInParams, + ApprovedAmountParams, + BeginningOfChatHistoryAdminRoomPartOneParams, + BeginningOfChatHistoryAnnounceRoomPartOneParams, + BeginningOfChatHistoryAnnounceRoomPartTwo, + BeginningOfChatHistoryDomainRoomPartOneParams, + CanceledRequestParams, + CharacterLimitParams, + ConfirmHoldExpenseParams, + ConfirmThatParams, + CompanyCardFeedNameParams, + DateShouldBeAfterParams, + DateShouldBeBeforeParams, + DeleteActionParams, + DeleteConfirmationParams, + DidSplitAmountMessageParams, + DistanceRateOperationsParams, + EditActionParams, + ElectronicFundsParams, + EnterMagicCodeParams, + FormattedMaxLengthParams, + ForwardedAmountParams, + GoBackMessageParams, + GoToRoomParams, + HeldRequestParams, + InstantSummaryParams, + IssueVirtualCardParams, + LocalTimeParams, + LogSizeParams, + LoggedInAsParams, + ManagerApprovedAmountParams, + ManagerApprovedParams, + SignUpNewFaceCodeParams, + NoLongerHaveAccessParams, + NotAllowedExtensionParams, + NotYouParams, + OOOEventSummaryFullDayParams, + OOOEventSummaryPartialDayParams, + OurEmailProviderParams, + PaidElsewhereWithAmountParams, + PaidWithExpensifyWithAmountParams, + ParentNavigationSummaryParams, + PaySomeoneParams, + PayerOwesAmountParams, + PayerOwesParams, + RoleNamesParams, + PayerPaidAmountParams, + PayerPaidParams, + PayerSettledParams, + ReimbursementRateParams, + RemovedTheRequestParams, + RenamedRoomActionParams, + ReportArchiveReasonsClosedParams, + ReportArchiveReasonsMergedParams, + ReportPolicyNameParams, + ReportArchiveReasonsInvoiceReceiverPolicyDeletedParams, + ReportArchiveReasonsRemovedFromPolicyParams, + RequestAmountParams, + RequestCountParams, + RequestedAmountMessageParams, + ResolutionConstraintsParams, + RoomNameReservedErrorParams, + RoomRenamedToParams, + SetTheDistanceMerchantParams, + SetTheRequestParams, + SettleExpensifyCardParams, + SettledAfterAddedBankAccountParams, + SizeExceededParams, + SplitAmountParams, + StepCounterParams, + TaskCreatedActionParams, + TermsParams, + ThreadRequestReportNameParams, + ThreadSentMoneyReportNameParams, + ToValidateLoginParams, + TransferParams, + UntilTimeParams, + UpdatedTheDistanceMerchantParams, + UpdatedTheRequestParams, + UsePlusButtonParams, + UserIsAlreadyMemberParams, + UserSplitParams, + ViolationsAutoReportedRejectedExpenseParams, + ViolationsCashExpenseWithNoReceiptParams, + ViolationsConversionSurchargeParams, + ViolationsInvoiceMarkupParams, + ViolationsMaxAgeParams, + ViolationsMissingTagParams, + ViolationsModifiedAmountParams, + ViolationsOverAutoApprovalLimitParams, + ViolationsOverCategoryLimitParams, + ViolationsOverLimitParams, + ViolationsPerDayLimitParams, + ViolationsReceiptRequiredParams, + ViolationsRterParams, + ViolationsTagOutOfPolicyParams, + ViolationsTaxOutOfPolicyParams, + WaitingOnBankAccountParams, + WalletProgramParams, + WeSentYouMagicSignInLinkParams, + WelcomeEnterMagicCodeParams, + WelcomeNoteParams, + WelcomeToRoomParams, + ZipCodeExampleFormatParams, + ChangeFieldParams, + ChangePolicyParams, + ChangeTypeParams, + ExportedToIntegrationParams, + DelegateSubmitParams, + AccountOwnerParams, + IntegrationsMessageParams, + MarkedReimbursedParams, + MarkReimbursedFromIntegrationParams, + ShareParams, + UnshareParams, + StripePaidParams, + UnapprovedParams, + RemoveMembersWarningPrompt, + DeleteExpenseTranslationParams, + ApprovalWorkflowErrorParams, + ConnectionNameParams, + LastSyncDateParams, + CustomersOrJobsLabelParams, + ExportAgainModalDescriptionParams, + IntegrationSyncFailedParams, + AddEmployeeParams, + UpdateRoleParams, + RemoveMemberParams, + DateParams, + FiltersAmountBetweenParams, + StatementPageTitleParams, + DisconnectPromptParams, + DisconnectTitleParams, + CharacterLengthLimitParams, + OptionalParam, + AssignCardParams, +}; diff --git a/src/languages/translations.ts b/src/languages/translations.ts index 4d89f1f529de..ec99d999f94e 100644 --- a/src/languages/translations.ts +++ b/src/languages/translations.ts @@ -1,7 +1,7 @@ import en from './en'; import es from './es'; import esES from './es-ES'; -import type {TranslationBase, TranslationFlatObject} from './types'; +import type {FlatTranslationsObject, TranslationDeepObject} from './types'; /** * Converts an object to it's flattened version. @@ -12,10 +12,10 @@ import type {TranslationBase, TranslationFlatObject} from './types'; */ // Necessary to export so that it is accessible to the unit tests // eslint-disable-next-line rulesdir/no-inline-named-export -export function flattenObject(obj: TranslationBase): TranslationFlatObject { +export function flattenObject(obj: TranslationDeepObject): FlatTranslationsObject { const result: Record = {}; - const recursive = (data: TranslationBase, key: string): void => { + const recursive = (data: TranslationDeepObject, key: string): void => { // If the data is a function or not a object (eg. a string or array), // it's the final value for the key being built and there is no need // for more recursion @@ -27,7 +27,7 @@ export function flattenObject(obj: TranslationBase): TranslationFlatObject { // Recursive call to the keys and connect to the respective data Object.keys(data).forEach((k) => { isEmpty = false; - recursive(data[k] as TranslationBase, key ? `${key}.${k}` : k); + recursive(data[k] as TranslationDeepObject, key ? `${key}.${k}` : k); }); // Check for when the object is empty but a key exists, so that @@ -39,7 +39,7 @@ export function flattenObject(obj: TranslationBase): TranslationFlatObject { }; recursive(obj, ''); - return result as TranslationFlatObject; + return result as FlatTranslationsObject; } export default { diff --git a/src/languages/types.ts b/src/languages/types.ts index a7a11fafb27b..0bdf740d982e 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -1,278 +1,53 @@ -import type {OnyxInputOrEntry, ReportAction} from '@src/types/onyx'; -import type {Unit} from '@src/types/onyx/Policy'; -import type {ViolationDataType} from '@src/types/onyx/TransactionViolation'; +/* eslint-disable @typescript-eslint/no-explicit-any */ import type en from './en'; -type AddressLineParams = { - lineNumber: number; -}; - -type CharacterLimitParams = { - limit: number; -}; - -type ZipCodeExampleFormatParams = { - zipSampleFormat: string; -}; - -type LoggedInAsParams = { - email: string; -}; - -type SignUpNewFaceCodeParams = { - login: string; -}; - -type WelcomeEnterMagicCodeParams = { - login: string; -}; - -type AlreadySignedInParams = { - email: string; -}; - -type GoBackMessageParams = { - provider: string; -}; - -type LocalTimeParams = { - user: string; - time: string; -}; - -type EditActionParams = { - action: OnyxInputOrEntry; -}; - -type DeleteActionParams = { - action: OnyxInputOrEntry; -}; - -type DeleteConfirmationParams = { - action: OnyxInputOrEntry; -}; - -type BeginningOfChatHistoryDomainRoomPartOneParams = { - domainRoom: string; -}; - -type BeginningOfChatHistoryAdminRoomPartOneParams = { - workspaceName: string; -}; - -type BeginningOfChatHistoryAnnounceRoomPartOneParams = { - workspaceName: string; -}; - -type BeginningOfChatHistoryAnnounceRoomPartTwo = { - workspaceName: string; -}; - -type WelcomeToRoomParams = { - roomName: string; -}; - -type UsePlusButtonParams = { - additionalText: string; -}; - -type ReportArchiveReasonsClosedParams = { - displayName: string; -}; - -type ReportArchiveReasonsMergedParams = { - displayName: string; - oldDisplayName: string; -}; - -type ReportArchiveReasonsRemovedFromPolicyParams = { - displayName: string; - policyName: string; - shouldUseYou?: boolean; -}; - -type ReportArchiveReasonsPolicyDeletedParams = { - policyName: string; -}; - -type ReportArchiveReasonsInvoiceReceiverPolicyDeletedParams = { - policyName: string; -}; - -type RequestCountParams = { - count: number; - scanningReceipts: number; - pendingReceipts: number; -}; - -type SettleExpensifyCardParams = { - formattedAmount: string; -}; - -type RequestAmountParams = {amount: string}; - -type RequestedAmountMessageParams = {formattedAmount: string; comment?: string}; - -type SplitAmountParams = {amount: string}; - -type DidSplitAmountMessageParams = {formattedAmount: string; comment: string}; - -type UserSplitParams = {amount: string}; - -type PayerOwesAmountParams = {payer: string; amount: number | string; comment?: string}; - -type PayerOwesParams = {payer: string}; - -type CompanyCardFeedNameParams = {feedName: string}; - -type PayerPaidAmountParams = {payer?: string; amount: number | string}; - -type ApprovedAmountParams = {amount: number | string}; - -type ForwardedAmountParams = {amount: number | string}; - -type ManagerApprovedParams = {manager: string}; - -type ManagerApprovedAmountParams = {manager: string; amount: number | string}; - -type PayerPaidParams = {payer: string}; - -type PayerSettledParams = {amount: number | string}; - -type WaitingOnBankAccountParams = {submitterDisplayName: string}; - -type CanceledRequestParams = {amount: string; submitterDisplayName: string}; - -type AdminCanceledRequestParams = {manager: string; amount: string}; - -type SettledAfterAddedBankAccountParams = {submitterDisplayName: string; amount: string}; - -type PaidElsewhereWithAmountParams = {payer?: string; amount: string}; - -type PaidWithExpensifyWithAmountParams = {payer?: string; amount: string}; - -type ThreadRequestReportNameParams = {formattedAmount: string; comment: string}; - -type ThreadSentMoneyReportNameParams = {formattedAmount: string; comment: string}; - -type SizeExceededParams = {maxUploadSizeInMB: number}; - -type ResolutionConstraintsParams = {minHeightInPx: number; minWidthInPx: number; maxHeightInPx: number; maxWidthInPx: number}; - -type NotAllowedExtensionParams = {allowedExtensions: string[]}; - -type EnterMagicCodeParams = {contactMethod: string}; - -type TransferParams = {amount: string}; - -type InstantSummaryParams = {rate: string; minAmount: string}; - -type NotYouParams = {user: string}; - -type DateShouldBeBeforeParams = {dateString: string}; - -type DateShouldBeAfterParams = {dateString: string}; - -type WeSentYouMagicSignInLinkParams = {login: string; loginType: string}; - -type ToValidateLoginParams = {primaryLogin: string; secondaryLogin: string}; - -type NoLongerHaveAccessParams = {primaryLogin: string}; - -type OurEmailProviderParams = {login: string}; - -type ConfirmThatParams = {login: string}; - -type UntilTimeParams = {time: string}; - -type StepCounterParams = {step: number; total?: number; text?: string}; - -type UserIsAlreadyMemberParams = {login: string; name: string}; - -type GoToRoomParams = {roomName: string}; - -type WelcomeNoteParams = {workspaceName: string}; - -type RoomNameReservedErrorParams = {reservedName: string}; - -type RenamedRoomActionParams = {oldName: string; newName: string}; - -type RoomRenamedToParams = {newName: string}; - -type OOOEventSummaryFullDayParams = {summary: string; dayCount: number; date: string}; - -type OOOEventSummaryPartialDayParams = {summary: string; timePeriod: string; date: string}; - -type ParentNavigationSummaryParams = {reportName?: string; workspaceName?: string}; - -type SetTheRequestParams = {valueName: string; newValueToDisplay: string}; - -type SetTheDistanceMerchantParams = {translatedChangedField: string; newMerchant: string; newAmountToDisplay: string}; - -type RemovedTheRequestParams = {valueName: string; oldValueToDisplay: string}; - -type UpdatedTheRequestParams = {valueName: string; newValueToDisplay: string; oldValueToDisplay: string}; - -type UpdatedTheDistanceMerchantParams = {translatedChangedField: string; newMerchant: string; oldMerchant: string; newAmountToDisplay: string; oldAmountToDisplay: string}; - -type FormattedMaxLengthParams = {formattedMaxLength: string}; - -type WalletProgramParams = {walletProgram: string}; - -type ViolationsAutoReportedRejectedExpenseParams = {rejectedBy: string; rejectReason: string}; - -type ViolationsCashExpenseWithNoReceiptParams = {formattedLimit?: string}; - -type ViolationsConversionSurchargeParams = {surcharge?: number}; - -type ViolationsInvoiceMarkupParams = {invoiceMarkup?: number}; - -type ViolationsMaxAgeParams = {maxAge: number}; - -type ViolationsMissingTagParams = {tagName?: string}; - -type ViolationsModifiedAmountParams = {type?: ViolationDataType; displayPercentVariance?: number}; - -type ViolationsOverAutoApprovalLimitParams = {formattedLimit?: string}; - -type ViolationsOverCategoryLimitParams = {formattedLimit?: string}; - -type ViolationsOverLimitParams = {formattedLimit?: string}; - -type ViolationsPerDayLimitParams = {formattedLimit?: string}; - -type ViolationsReceiptRequiredParams = {formattedLimit?: string; category?: string}; - -type ViolationsRterParams = { - brokenBankConnection: boolean; - isAdmin: boolean; - email?: string; - isTransactionOlderThan7Days: boolean; - member?: string; -}; - -type ViolationsTagOutOfPolicyParams = {tagName?: string}; - -type ViolationsTaxOutOfPolicyParams = {taxName?: string}; - -type PaySomeoneParams = {name?: string}; - -type TaskCreatedActionParams = {title: string}; - -/* Translation Object types */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -type TranslationBaseValue = string | string[] | ((...args: any[]) => string); - -type TranslationBase = {[key: string]: TranslationBaseValue | TranslationBase}; - -/* Flat Translation Object types */ -// Flattens an object and returns concatenations of all the keys of nested objects -type FlattenObject = { +type PluralParams = {count: number}; +type PluralHandler = ((count: number) => string) | string; +type PluralForm = { + zero?: string; + one: string; + two?: string; + few?: PluralHandler; + many?: PluralHandler; + other: PluralHandler; +}; + +/** + * Retrieves the first argument of a function + */ +type FirstArgument = TFunction extends (arg: infer A, ...args: any[]) => any ? A : never; + +/** + * Translation value can be a string or a function that returns a string + */ +type TranslationLeafValue = TStringOrFunction extends string + ? string + : ( + arg: FirstArgument extends Record | undefined ? FirstArgument : Record, + ...noOtherArguments: unknown[] + ) => string | PluralForm; + +/** + * Translation object is a recursive object that can contain other objects or string/function values + */ +type TranslationDeepObject = { // eslint-disable-next-line @typescript-eslint/no-explicit-any - [TKey in keyof TObject]: TObject[TKey] extends (...args: any[]) => any - ? `${TPrefix}${TKey & string}` - : // eslint-disable-next-line @typescript-eslint/no-explicit-any - TObject[TKey] extends any[] + [Path in keyof TTranslations]: TTranslations[Path] extends string | ((...args: any[]) => any) + ? TranslationLeafValue + : TTranslations[Path] extends number | boolean | null | undefined | unknown[] + ? string + : TranslationDeepObject; +}; + +/** + * Flattens an object and returns concatenations of all the keys of nested objects + * + * Ex: + * Input: { common: { yes: "Yes", no: "No" }} + * Output: "common.yes" | "common.no" + */ +type FlattenObject = { + [TKey in keyof TObject]: TObject[TKey] extends (arg: any) => any ? `${TPrefix}${TKey & string}` : // eslint-disable-next-line @typescript-eslint/ban-types TObject[TKey] extends object @@ -280,222 +55,43 @@ type FlattenObject = { : `${TPrefix}${TKey & string}`; }[keyof TObject]; -// Retrieves a type for a given key path (calculated from the type above) -type TranslateType = TPath extends keyof TObject - ? TObject[TPath] - : TPath extends `${infer TKey}.${infer TRest}` - ? TKey extends keyof TObject - ? TranslateType +/** + * Retrieves a type for a given key path (calculated from the type above) + */ +type TranslationValue = TKey extends keyof TObject + ? TObject[TKey] + : TKey extends `${infer TPathKey}.${infer TRest}` + ? TPathKey extends keyof TObject + ? TranslationValue : never : never; -type EnglishTranslation = typeof en; - -type TranslationPaths = FlattenObject; - -type TranslationFlatObject = { - [TKey in TranslationPaths]: TranslateType; -}; - -type TermsParams = {amount: string}; - -type ElectronicFundsParams = {percentage: string; amount: string}; - -type LogSizeParams = {size: number}; - -type HeldRequestParams = {comment: string}; - -type DistanceRateOperationsParams = {count: number}; - -type ReimbursementRateParams = {unit: Unit}; - -type ConfirmHoldExpenseParams = {transactionCount: number}; - -type ChangeFieldParams = {oldValue?: string; newValue: string; fieldName: string}; - -type ChangePolicyParams = {fromPolicy: string; toPolicy: string}; - -type ChangeTypeParams = {oldType: string; newType: string}; - -type DelegateSubmitParams = {delegateUser: string; originalManager: string}; - -type AccountOwnerParams = {accountOwnerEmail: string}; - -type ExportedToIntegrationParams = {label: string; markedManually?: boolean; inProgress?: boolean; lastModified?: string}; - -type IntegrationsMessageParams = { - label: string; - result: { - code?: number; - messages?: string[]; - title?: string; - link?: { - url: string; - text: string; - }; - }; -}; - -type MarkedReimbursedParams = {amount: string; currency: string}; - -type MarkReimbursedFromIntegrationParams = {amount: string; currency: string}; - -type ShareParams = {to: string}; - -type UnshareParams = {to: string}; - -type StripePaidParams = {amount: string; currency: string}; - -type UnapprovedParams = {amount: string; currency: string}; -type RemoveMembersWarningPrompt = { - memberName: string; - ownerName: string; -}; - -type DeleteExpenseTranslationParams = { - count: number; -}; - -type IssueVirtualCardParams = { - assignee: string; - link: string; -}; - -type ApprovalWorkflowErrorParams = { - name1: string; - name2: string; -}; - -type AssignCardParams = { - assignee: string; - feed: string; -}; - -export type { - AddressLineParams, - AdminCanceledRequestParams, - AlreadySignedInParams, - ApprovedAmountParams, - BeginningOfChatHistoryAdminRoomPartOneParams, - BeginningOfChatHistoryAnnounceRoomPartOneParams, - BeginningOfChatHistoryAnnounceRoomPartTwo, - BeginningOfChatHistoryDomainRoomPartOneParams, - CanceledRequestParams, - CharacterLimitParams, - ConfirmHoldExpenseParams, - ConfirmThatParams, - CompanyCardFeedNameParams, - DateShouldBeAfterParams, - DateShouldBeBeforeParams, - DeleteActionParams, - DeleteConfirmationParams, - DidSplitAmountMessageParams, - DistanceRateOperationsParams, - EditActionParams, - ElectronicFundsParams, - EnglishTranslation, - EnterMagicCodeParams, - FormattedMaxLengthParams, - ForwardedAmountParams, - GoBackMessageParams, - GoToRoomParams, - HeldRequestParams, - InstantSummaryParams, - IssueVirtualCardParams, - LocalTimeParams, - LogSizeParams, - LoggedInAsParams, - ManagerApprovedAmountParams, - ManagerApprovedParams, - SignUpNewFaceCodeParams, - NoLongerHaveAccessParams, - NotAllowedExtensionParams, - NotYouParams, - OOOEventSummaryFullDayParams, - OOOEventSummaryPartialDayParams, - OurEmailProviderParams, - PaidElsewhereWithAmountParams, - PaidWithExpensifyWithAmountParams, - ParentNavigationSummaryParams, - PaySomeoneParams, - PayerOwesAmountParams, - PayerOwesParams, - PayerPaidAmountParams, - PayerPaidParams, - PayerSettledParams, - ReimbursementRateParams, - RemovedTheRequestParams, - RenamedRoomActionParams, - ReportArchiveReasonsClosedParams, - ReportArchiveReasonsMergedParams, - ReportArchiveReasonsPolicyDeletedParams, - ReportArchiveReasonsInvoiceReceiverPolicyDeletedParams, - ReportArchiveReasonsRemovedFromPolicyParams, - RequestAmountParams, - RequestCountParams, - RequestedAmountMessageParams, - ResolutionConstraintsParams, - RoomNameReservedErrorParams, - RoomRenamedToParams, - SetTheDistanceMerchantParams, - SetTheRequestParams, - SettleExpensifyCardParams, - SettledAfterAddedBankAccountParams, - SizeExceededParams, - SplitAmountParams, - StepCounterParams, - TaskCreatedActionParams, - TermsParams, - ThreadRequestReportNameParams, - ThreadSentMoneyReportNameParams, - ToValidateLoginParams, - TransferParams, - TranslationBase, - TranslationFlatObject, - TranslationPaths, - UntilTimeParams, - UpdatedTheDistanceMerchantParams, - UpdatedTheRequestParams, - UsePlusButtonParams, - UserIsAlreadyMemberParams, - UserSplitParams, - ViolationsAutoReportedRejectedExpenseParams, - ViolationsCashExpenseWithNoReceiptParams, - ViolationsConversionSurchargeParams, - ViolationsInvoiceMarkupParams, - ViolationsMaxAgeParams, - ViolationsMissingTagParams, - ViolationsModifiedAmountParams, - ViolationsOverAutoApprovalLimitParams, - ViolationsOverCategoryLimitParams, - ViolationsOverLimitParams, - ViolationsPerDayLimitParams, - ViolationsReceiptRequiredParams, - ViolationsRterParams, - ViolationsTagOutOfPolicyParams, - ViolationsTaxOutOfPolicyParams, - WaitingOnBankAccountParams, - WalletProgramParams, - WeSentYouMagicSignInLinkParams, - WelcomeEnterMagicCodeParams, - WelcomeNoteParams, - WelcomeToRoomParams, - ZipCodeExampleFormatParams, - ChangeFieldParams, - ChangePolicyParams, - ChangeTypeParams, - ExportedToIntegrationParams, - DelegateSubmitParams, - AccountOwnerParams, - IntegrationsMessageParams, - MarkedReimbursedParams, - MarkReimbursedFromIntegrationParams, - ShareParams, - UnshareParams, - StripePaidParams, - UnapprovedParams, - RemoveMembersWarningPrompt, - DeleteExpenseTranslationParams, - ApprovalWorkflowErrorParams, - AssignCardParams, -}; +/** + * English is the default translation, other languages will be type-safe based on this + */ +type DefaultTranslation = typeof en; + +/** + * Flattened default translation object + */ +type TranslationPaths = FlattenObject; + +/** + * Flattened default translation object with its values + */ +type FlatTranslationsObject = { + [Path in TranslationPaths]: TranslationValue; +}; + +/** + * Determines the expected parameters for a specific translation function based on the provided translation path + */ +type TranslationParameters = FlatTranslationsObject[TKey] extends (...args: infer Args) => infer Return + ? Return extends PluralForm + ? Args[0] extends undefined + ? [PluralParams] + : [Args[0] & PluralParams] + : Args + : never[]; + +export type {DefaultTranslation, TranslationDeepObject, TranslationPaths, PluralForm, TranslationValue, FlatTranslationsObject, TranslationParameters}; diff --git a/src/libs/CategoryUtils.ts b/src/libs/CategoryUtils.ts index 7b2f71dbd101..f27b32360a84 100644 --- a/src/libs/CategoryUtils.ts +++ b/src/libs/CategoryUtils.ts @@ -38,10 +38,9 @@ function formatRequireReceiptsOverText(translate: LocaleContextProps['translate' const maxExpenseAmountToDisplay = policy?.maxExpenseAmount === CONST.DISABLED_MAX_EXPENSE_VALUE ? 0 : policy?.maxExpenseAmount; - return translate( - `workspace.rules.categoryRules.requireReceiptsOverList.default`, - CurrencyUtils.convertToShortDisplayString(maxExpenseAmountToDisplay, policy?.outputCurrency ?? CONST.CURRENCY.USD), - ); + return translate(`workspace.rules.categoryRules.requireReceiptsOverList.default`, { + defaultAmount: CurrencyUtils.convertToShortDisplayString(maxExpenseAmountToDisplay, policy?.outputCurrency ?? CONST.CURRENCY.USD), + }); } function getCategoryApproverRule(approvalRules: ApprovalRule[], categoryName: string) { diff --git a/src/libs/ErrorUtils.ts b/src/libs/ErrorUtils.ts index cf852e533a20..78821cde5e13 100644 --- a/src/libs/ErrorUtils.ts +++ b/src/libs/ErrorUtils.ts @@ -1,14 +1,14 @@ import mapValues from 'lodash/mapValues'; import type {OnyxEntry} from 'react-native-onyx'; import CONST from '@src/CONST'; -import type {TranslationFlatObject, TranslationPaths} from '@src/languages/types'; +import type {TranslationPaths} from '@src/languages/types'; import type {ErrorFields, Errors} from '@src/types/onyx/OnyxCommon'; import type Response from '@src/types/onyx/Response'; import type {ReceiptError} from '@src/types/onyx/Transaction'; import DateUtils from './DateUtils'; import * as Localize from './Localize'; -function getAuthenticateErrorMessage(response: Response): keyof TranslationFlatObject { +function getAuthenticateErrorMessage(response: Response): TranslationPaths { switch (response.jsonCode) { case CONST.JSON_CODE.UNABLE_TO_RETRY: return 'session.offlineMessageRetry'; diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index c9eef3170245..bd8a34406846 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -6,7 +6,7 @@ import type {MessageElementBase, MessageTextElement} from '@libs/MessageElement' import Config from '@src/CONFIG'; import CONST from '@src/CONST'; import translations from '@src/languages/translations'; -import type {TranslationFlatObject, TranslationPaths} from '@src/languages/types'; +import type {PluralForm, TranslationParameters, TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Locale} from '@src/types/onyx'; import LocaleListener from './LocaleListener'; @@ -45,9 +45,6 @@ function init() { }, {}); } -type PhraseParameters = T extends (...args: infer A) => string ? A : never[]; -type Phrase = TranslationFlatObject[TKey] extends (...args: infer A) => unknown ? (...args: A) => string : string; - /** * Map to store translated values for each locale. * This is used to avoid translating the same phrase multiple times. @@ -82,17 +79,12 @@ const translationCache = new Map, Map( language: 'en' | 'es' | 'es-ES', phraseKey: TKey, - fallbackLanguage: 'en' | 'es' | null = null, - ...phraseParameters: PhraseParameters> + fallbackLanguage: 'en' | 'es' | null, + ...parameters: TranslationParameters ): string | null { // Get the cache for the above locale const cacheForLocale = translationCache.get(language); @@ -106,11 +98,44 @@ function getTranslatedPhrase( return valueFromCache; } - const translatedPhrase = translations?.[language]?.[phraseKey] as Phrase; + const translatedPhrase = translations?.[language]?.[phraseKey]; if (translatedPhrase) { if (typeof translatedPhrase === 'function') { - return translatedPhrase(...phraseParameters); + /** + * If the result of `translatedPhrase` is an object, check if it contains the 'count' parameter + * to handle pluralization logic. + * Alternatively, before evaluating the translated result, we can check if the 'count' parameter + * exists in the passed parameters. + */ + const translateFunction = translatedPhrase as unknown as (...parameters: TranslationParameters) => string | PluralForm; + const translateResult = translateFunction(...parameters); + + if (typeof translateResult === 'string') { + return translateResult; + } + + const phraseObject = parameters[0] as {count?: number}; + if (typeof phraseObject?.count !== 'number') { + throw new Error(`Invalid plural form for '${phraseKey}'`); + } + + const pluralRule = new Intl.PluralRules(language).select(phraseObject.count); + + const pluralResult = translateResult[pluralRule]; + if (pluralResult) { + if (typeof pluralResult === 'string') { + return pluralResult; + } + + return pluralResult(phraseObject.count); + } + + if (typeof translateResult.other === 'string') { + return translateResult.other; + } + + return translateResult.other(phraseObject.count); } // We set the translated value in the cache only for the phrases without parameters. @@ -123,10 +148,10 @@ function getTranslatedPhrase( } // Phrase is not found in full locale, search it in fallback language e.g. es - const fallbacktranslatedPhrase = getTranslatedPhrase(fallbackLanguage, phraseKey, null, ...phraseParameters); + const fallbackTranslatedPhrase = getTranslatedPhrase(fallbackLanguage, phraseKey, null, ...parameters); - if (fallbacktranslatedPhrase) { - return fallbacktranslatedPhrase; + if (fallbackTranslatedPhrase) { + return fallbackTranslatedPhrase; } if (fallbackLanguage !== CONST.LOCALES.DEFAULT) { @@ -134,22 +159,22 @@ function getTranslatedPhrase( } // Phrase is not translated, search it in default language (en) - return getTranslatedPhrase(CONST.LOCALES.DEFAULT, phraseKey, null, ...phraseParameters); + return getTranslatedPhrase(CONST.LOCALES.DEFAULT, phraseKey, null, ...parameters); } /** * Return translated string for given locale and phrase * * @param [desiredLanguage] eg 'en', 'es-ES' - * @param [phraseParameters] Parameters to supply if the phrase is a template literal. + * @param [parameters] Parameters to supply if the phrase is a template literal. */ -function translate(desiredLanguage: 'en' | 'es' | 'es-ES' | 'es_ES', phraseKey: TKey, ...phraseParameters: PhraseParameters>): string { +function translate(desiredLanguage: 'en' | 'es' | 'es-ES' | 'es_ES', path: TPath, ...parameters: TranslationParameters): string { // Search phrase in full locale e.g. es-ES const language = desiredLanguage === CONST.LOCALES.ES_ES_ONFIDO ? CONST.LOCALES.ES_ES : desiredLanguage; // Phrase is not found in full locale, search it in fallback language e.g. es const languageAbbreviation = desiredLanguage.substring(0, 2) as 'en' | 'es'; - const translatedPhrase = getTranslatedPhrase(language, phraseKey, languageAbbreviation, ...phraseParameters); + const translatedPhrase = getTranslatedPhrase(language, path, languageAbbreviation, ...parameters); if (translatedPhrase !== null && translatedPhrase !== undefined) { return translatedPhrase; } @@ -157,21 +182,21 @@ function translate(desiredLanguage: 'en' | 'es' | // Phrase is not found in default language, on production and staging log an alert to server // on development throw an error if (Config.IS_IN_PRODUCTION || Config.IS_IN_STAGING) { - const phraseString: string = Array.isArray(phraseKey) ? phraseKey.join('.') : phraseKey; + const phraseString = Array.isArray(path) ? path.join('.') : path; Log.alert(`${phraseString} was not found in the en locale`); if (userEmail.includes(CONST.EMAIL.EXPENSIFY_EMAIL_DOMAIN)) { return CONST.MISSING_TRANSLATION; } return phraseString; } - throw new Error(`${phraseKey} was not found in the default language`); + throw new Error(`${path} was not found in the default language`); } /** * Uses the locale in this file updated by the Onyx subscriber. */ -function translateLocal(phrase: TKey, ...variables: PhraseParameters>) { - return translate(BaseLocaleListener.getPreferredLocale(), phrase, ...variables); +function translateLocal(phrase: TPath, ...parameters: TranslationParameters) { + return translate(BaseLocaleListener.getPreferredLocale(), phrase, ...parameters); } function getPreferredListFormat(): Intl.ListFormat { @@ -226,4 +251,3 @@ function getDevicePreferredLocale(): Locale { } export {translate, translateLocal, formatList, formatMessageElementList, getDevicePreferredLocale}; -export type {PhraseParameters, Phrase}; diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 4d13051d107c..c95f6b5a371a 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -821,7 +821,10 @@ function getCustomersOrJobsLabelNetSuite(policy: Policy | undefined, translate: importFields.push(translate('workspace.netsuite.import.customersOrJobs.jobs')); } - const importedValueLabel = translate(`workspace.netsuite.import.customersOrJobs.label`, importFields, translate(`workspace.accounting.importTypes.${importedValue}`).toLowerCase()); + const importedValueLabel = translate(`workspace.netsuite.import.customersOrJobs.label`, { + importFields, + importType: translate(`workspace.accounting.importTypes.${importedValue}`).toLowerCase(), + }); return importedValueLabel.charAt(0).toUpperCase() + importedValueLabel.slice(1); } diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index f584f694edd0..12787bc01b2d 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -1266,7 +1266,7 @@ function getMessageOfOldDotReportAction(oldDotAction: PartialReportAction | OldD case CONST.REPORT.ACTIONS.TYPE.INTEGRATIONS_MESSAGE: { const {result, label} = originalMessage; const errorMessage = result?.messages?.join(', ') ?? ''; - return Localize.translateLocal('report.actions.type.integrationsMessage', errorMessage, label); + return Localize.translateLocal('report.actions.type.integrationsMessage', {errorMessage, label}); } case CONST.REPORT.ACTIONS.TYPE.MANAGER_ATTACH_RECEIPT: return Localize.translateLocal('report.actions.type.managerAttachReceipt'); @@ -1643,7 +1643,7 @@ function getPolicyChangeLogAddEmployeeMessage(reportAction: OnyxInputOrEntry): reportAction is ReportAction { @@ -1658,7 +1658,7 @@ function getPolicyChangeLogChangeRoleMessage(reportAction: OnyxInputOrEntry>) { @@ -1719,11 +1719,11 @@ function getCardIssuedMessage(reportAction: OnyxEntry, shouldRende const shouldShowAddMissingDetailsButton = reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.CARD_MISSING_ADDRESS && missingDetails && isAssigneeCurrentUser; switch (reportAction?.actionName) { case CONST.REPORT.ACTIONS.TYPE.CARD_ISSUED: - return Localize.translateLocal('workspace.expensifyCard.issuedCard', assignee); + return Localize.translateLocal('workspace.expensifyCard.issuedCard', {assignee}); case CONST.REPORT.ACTIONS.TYPE.CARD_ISSUED_VIRTUAL: return Localize.translateLocal('workspace.expensifyCard.issuedCardVirtual', {assignee, link}); case CONST.REPORT.ACTIONS.TYPE.CARD_MISSING_ADDRESS: - return Localize.translateLocal(`workspace.expensifyCard.${shouldShowAddMissingDetailsButton ? 'issuedCardNoShippingDetails' : 'addedShippingDetails'}`, assignee); + return Localize.translateLocal(`workspace.expensifyCard.${shouldShowAddMissingDetailsButton ? 'issuedCardNoShippingDetails' : 'addedShippingDetails'}`, {assignee}); default: return ''; } diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 1c52c316ea10..0c16d2fe2184 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -19,7 +19,8 @@ import * as defaultWorkspaceAvatars from '@components/Icon/WorkspaceDefaultAvata import type {MoneyRequestAmountInputProps} from '@components/MoneyRequestAmountInput'; import type {IOUAction, IOUType} from '@src/CONST'; import CONST from '@src/CONST'; -import type {ParentNavigationSummaryParams, TranslationPaths} from '@src/languages/types'; +import type {ParentNavigationSummaryParams} from '@src/languages/params'; +import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Route} from '@src/ROUTES'; import ROUTES from '@src/ROUTES'; @@ -7701,7 +7702,7 @@ function getFieldViolationTranslation(reportField: PolicyReportField, violation? switch (violation) { case 'fieldRequired': - return Localize.translateLocal('reportViolations.fieldRequired', reportField.name); + return Localize.translateLocal('reportViolations.fieldRequired', {fieldName: reportField.name}); default: return ''; } diff --git a/src/libs/Violations/ViolationsUtils.ts b/src/libs/Violations/ViolationsUtils.ts index adbc05460220..c7ee0a0b0867 100644 --- a/src/libs/Violations/ViolationsUtils.ts +++ b/src/libs/Violations/ViolationsUtils.ts @@ -1,11 +1,10 @@ import reject from 'lodash/reject'; import Onyx from 'react-native-onyx'; import type {OnyxUpdate} from 'react-native-onyx'; -import type {Phrase, PhraseParameters} from '@libs/Localize'; +import type {LocaleContextProps} from '@components/LocaleContextProvider'; import {getCustomUnitRate, getSortedTagKeys} from '@libs/PolicyUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import CONST from '@src/CONST'; -import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Policy, PolicyCategories, PolicyTagLists, Transaction, TransactionViolation, ViolationName} from '@src/types/onyx'; @@ -237,10 +236,7 @@ const ViolationsUtils = { * possible values could be either translation keys that resolve to strings or translation keys that resolve to * functions. */ - getViolationTranslation( - violation: TransactionViolation, - translate: (phraseKey: TKey, ...phraseParameters: PhraseParameters>) => string, - ): string { + getViolationTranslation(violation: TransactionViolation, translate: LocaleContextProps['translate']): string { const { brokenBankConnection = false, isAdmin = false, @@ -250,7 +246,7 @@ const ViolationsUtils = { category, rejectedBy = '', rejectReason = '', - formattedLimit, + formattedLimit = '', surcharge = 0, invoiceMarkup = 0, maxAge = 0, diff --git a/src/libs/WorkspacesSettingsUtils.ts b/src/libs/WorkspacesSettingsUtils.ts index ed46b0b5f5ec..d8cd2ff00828 100644 --- a/src/libs/WorkspacesSettingsUtils.ts +++ b/src/libs/WorkspacesSettingsUtils.ts @@ -1,6 +1,7 @@ import Onyx from 'react-native-onyx'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; +import type {LocaleContextProps} from '@components/LocaleContextProvider'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -8,7 +9,6 @@ import type {Policy, ReimbursementAccount, Report, ReportAction, ReportActions, import type {PolicyConnectionSyncProgress, Unit} from '@src/types/onyx/Policy'; import {isConnectionInProgress} from './actions/connections'; import * as CurrencyUtils from './CurrencyUtils'; -import type {Phrase, PhraseParameters} from './Localize'; import * as OptionsListUtils from './OptionsListUtils'; import {hasCustomUnitsError, hasEmployeeListError, hasPolicyError, hasSyncError, hasTaxRateError} from './PolicyUtils'; import * as ReportActionsUtils from './ReportActionsUtils'; @@ -234,7 +234,7 @@ function getUnitTranslationKey(unit: Unit): TranslationPaths { */ function getOwnershipChecksDisplayText( error: ValueOf, - translate: (phraseKey: TKey, ...phraseParameters: PhraseParameters>) => string, + translate: LocaleContextProps['translate'], policy: OnyxEntry, accountLogin: string | undefined, ) { @@ -271,14 +271,14 @@ function getOwnershipChecksDisplayText( case CONST.POLICY.OWNERSHIP_ERRORS.DUPLICATE_SUBSCRIPTION: title = translate('workspace.changeOwner.duplicateSubscriptionTitle'); text = translate('workspace.changeOwner.duplicateSubscriptionText', { - email: changeOwner?.duplicateSubscription, - workspaceName: policy?.name, + email: changeOwner?.duplicateSubscription ?? '', + workspaceName: policy?.name ?? '', }); buttonText = translate('workspace.changeOwner.duplicateSubscriptionButtonText'); break; case CONST.POLICY.OWNERSHIP_ERRORS.HAS_FAILED_SETTLEMENTS: title = translate('workspace.changeOwner.hasFailedSettlementsTitle'); - text = translate('workspace.changeOwner.hasFailedSettlementsText', {email: accountLogin}); + text = translate('workspace.changeOwner.hasFailedSettlementsText', {email: accountLogin ?? ''}); buttonText = translate('workspace.changeOwner.hasFailedSettlementsButtonText'); break; case CONST.POLICY.OWNERSHIP_ERRORS.FAILED_TO_CLEAR_BALANCE: diff --git a/src/libs/actions/Policy/Category.ts b/src/libs/actions/Policy/Category.ts index c342fe6eedb6..a9f6e376b80a 100644 --- a/src/libs/actions/Policy/Category.ts +++ b/src/libs/actions/Policy/Category.ts @@ -153,7 +153,7 @@ function updateImportSpreadsheetData(categoriesLength: number) { shouldFinalModalBeOpened: true, importFinalModal: { title: translateLocal('spreadsheet.importSuccessfullTitle'), - prompt: translateLocal('spreadsheet.importCategoriesSuccessfullDescription', categoriesLength), + prompt: translateLocal('spreadsheet.importCategoriesSuccessfullDescription', {categories: categoriesLength}), }, }, }, diff --git a/src/libs/actions/Policy/Member.ts b/src/libs/actions/Policy/Member.ts index 44ce9ea6f91c..8c2a66a8ccf6 100644 --- a/src/libs/actions/Policy/Member.ts +++ b/src/libs/actions/Policy/Member.ts @@ -183,7 +183,10 @@ function updateImportSpreadsheetData(membersLength: number): OnyxData { key: ONYXKEYS.IMPORTED_SPREADSHEET, value: { shouldFinalModalBeOpened: true, - importFinalModal: {title: translateLocal('spreadsheet.importSuccessfullTitle'), prompt: translateLocal('spreadsheet.importMembersSuccessfullDescription', membersLength)}, + importFinalModal: { + title: translateLocal('spreadsheet.importSuccessfullTitle'), + prompt: translateLocal('spreadsheet.importMembersSuccessfullDescription', {members: membersLength}), + }, }, }, ], diff --git a/src/libs/actions/Policy/Tag.ts b/src/libs/actions/Policy/Tag.ts index f2cd818fd6c1..9628b6ceda77 100644 --- a/src/libs/actions/Policy/Tag.ts +++ b/src/libs/actions/Policy/Tag.ts @@ -136,7 +136,10 @@ function updateImportSpreadsheetData(tagsLength: number): OnyxData { key: ONYXKEYS.IMPORTED_SPREADSHEET, value: { shouldFinalModalBeOpened: true, - importFinalModal: {title: translateLocal('spreadsheet.importSuccessfullTitle'), prompt: translateLocal('spreadsheet.importTagsSuccessfullDescription', tagsLength)}, + importFinalModal: { + title: translateLocal('spreadsheet.importSuccessfullTitle'), + prompt: translateLocal('spreadsheet.importTagsSuccessfullDescription', {tags: tagsLength}), + }, }, }, ], diff --git a/src/pages/MissingPersonalDetails/index.tsx b/src/pages/MissingPersonalDetails/index.tsx index 9a2266f6384b..5220f25be981 100644 --- a/src/pages/MissingPersonalDetails/index.tsx +++ b/src/pages/MissingPersonalDetails/index.tsx @@ -135,7 +135,7 @@ function MissingPersonalDetails() { if (countrySpecificZipRegex) { if (!countrySpecificZipRegex.test(values[INPUT_IDS.ZIP_POST_CODE]?.trim().toUpperCase())) { if (ValidationUtils.isRequiredFulfilled(values[INPUT_IDS.ZIP_POST_CODE]?.trim())) { - errors[INPUT_IDS.ZIP_POST_CODE] = translate('privatePersonalDetails.error.incorrectZipFormat', countryZipFormat); + errors[INPUT_IDS.ZIP_POST_CODE] = translate('privatePersonalDetails.error.incorrectZipFormat', {zipFormat: countryZipFormat}); } else { errors[INPUT_IDS.ZIP_POST_CODE] = translate('common.error.fieldRequired'); } diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index 66c61b134aef..60b32fc14930 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -491,10 +491,11 @@ function ReportDetailsPage({policies, report, route}: ReportDetailsPageProps) { /> ) : null; - const connectedIntegrationName = connectedIntegration ? translate('workspace.accounting.connectionName', connectedIntegration) : ''; + const connectedIntegrationName = connectedIntegration ? translate('workspace.accounting.connectionName', {connectionName: connectedIntegration}) : ''; const unapproveWarningText = ( - {translate('iou.headsUp')} {translate('iou.unapproveWithIntegrationWarning', connectedIntegrationName)} + {translate('iou.headsUp')}{' '} + {translate('iou.unapproveWithIntegrationWarning', {accountingIntegration: connectedIntegrationName})} ); diff --git a/src/pages/RestrictedAction/Workspace/WorkspaceAdminRestrictedAction.tsx b/src/pages/RestrictedAction/Workspace/WorkspaceAdminRestrictedAction.tsx index 89b5dcdd8a2b..342cd4ce5e6e 100644 --- a/src/pages/RestrictedAction/Workspace/WorkspaceAdminRestrictedAction.tsx +++ b/src/pages/RestrictedAction/Workspace/WorkspaceAdminRestrictedAction.tsx @@ -50,10 +50,10 @@ function WorkspaceAdminRestrictedAction({policyID}: WorkspaceAdminRestrictedActi height={variables.restrictedActionIllustrationHeight} /> - {translate('workspace.restrictedAction.actionsAreCurrentlyRestricted', {workspaceName: policy?.name})} + {translate('workspace.restrictedAction.actionsAreCurrentlyRestricted', {workspaceName: policy?.name ?? ''})} - {translate('workspace.restrictedAction.workspaceOwnerWillNeedToAddOrUpdatePaymentCard', {workspaceOwnerName: policy?.owner})} + {translate('workspace.restrictedAction.workspaceOwnerWillNeedToAddOrUpdatePaymentCard', {workspaceOwnerName: policy?.owner ?? ''})}