diff --git a/packages/desktop-client/src/components/accounts/Account.jsx b/packages/desktop-client/src/components/accounts/Account.jsx index b946bcbabae6730a591cd44cb03fe530cb260453..ece252ea1b19ce0cfbc644fa3aca9103dc5afb1e 100644 --- a/packages/desktop-client/src/components/accounts/Account.jsx +++ b/packages/desktop-client/src/components/accounts/Account.jsx @@ -1,8 +1,10 @@ import React, { PureComponent, createRef, useMemo } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; import { useSelector } from 'react-redux'; import { Navigate, useParams, useLocation } from 'react-router-dom'; import { debounce } from 'debounce'; +import { t } from 'i18next'; import { v4 as uuidv4 } from 'uuid'; import { validForTransfer } from 'loot-core/client/transfer'; @@ -70,9 +72,11 @@ function EmptyMessage({ onAdd }) { }} > <Text style={{ textAlign: 'center', lineHeight: '1.4em' }}> - For Actual to be useful, you need to <strong>add an account</strong>. - You can link an account to automatically download transactions, or - manage it locally yourself. + <Trans> + For Actual to be useful, you need to <strong>add an account</strong> + . You can link an account to automatically download transactions, or + manage it locally yourself. + </Trans> </Text> <Button @@ -81,13 +85,13 @@ function EmptyMessage({ onAdd }) { autoFocus onPress={onAdd} > - Add account + <Trans>Add account</Trans> </Button> <View style={{ marginTop: 20, fontSize: 13, color: theme.tableTextLight }} > - In the future, you can add accounts from the sidebar. + <Trans>In the future, you can add accounts from the sidebar.</Trans> </View> </View> </View> @@ -456,6 +460,8 @@ class AccountInternal extends PureComponent { }; onImport = async () => { + const { t } = useTranslation(); + const accountId = this.props.accountId; const account = this.props.accounts.find(acct => acct.id === accountId); const categories = await this.props.getCategories(); @@ -464,7 +470,7 @@ class AccountInternal extends PureComponent { const res = await window.Actual?.openFileDialog({ filters: [ { - name: 'Financial Files', + name: t('Financial Files'), extensions: ['qif', 'ofx', 'qfx', 'csv', 'tsv', 'xml'], }, ], @@ -486,6 +492,8 @@ class AccountInternal extends PureComponent { }; onExport = async accountName => { + const { t } = useTranslation(); + const exportedTransactions = await send('transactions-export-query', { query: this.currentQuery.serialize(), }); @@ -496,7 +504,7 @@ class AccountInternal extends PureComponent { window.Actual?.saveFile( exportedTransactions, filename, - 'Export Transactions', + t('Export Transactions'), ); }; @@ -676,13 +684,13 @@ class AccountInternal extends PureComponent { if (!account) { if (id === 'budgeted') { - return 'Budgeted Accounts'; + return t('Budgeted Accounts'); } else if (id === 'offbudget') { - return 'Off Budget Accounts'; + return t('Off Budget Accounts'); } else if (id === 'uncategorized') { - return 'Uncategorized'; + return t('Uncategorized'); } else if (!id) { - return 'All Accounts'; + return t('All Accounts'); } return null; } @@ -793,6 +801,8 @@ class AccountInternal extends PureComponent { }; onCreateReconciliationTransaction = async diff => { + const { t } = useTranslation(); + // Create a new reconciliation transaction const reconciliationTransactions = realizeTempTransactions([ { @@ -802,7 +812,7 @@ class AccountInternal extends PureComponent { reconciled: false, amount: diff, date: currentDay(), - notes: 'Reconciliation balance adjustment', + notes: t('Reconciliation balance adjustment'), }, ]); @@ -819,8 +829,10 @@ class AccountInternal extends PureComponent { }; onShowTransactions = async ids => { + const { t } = useTranslation(); + this.onApplyFilter({ - customName: 'Selected transactions', + customName: t('Selected transactions'), queryFilter: { id: { $oneof: ids } }, }); }; diff --git a/packages/desktop-client/src/components/accounts/AccountSyncCheck.jsx b/packages/desktop-client/src/components/accounts/AccountSyncCheck.jsx index e8812ecadac81d9b1aa5b06526e2128e526cb827..c08b49792e280df95afe1be1f54b0e504b15ded9 100644 --- a/packages/desktop-client/src/components/accounts/AccountSyncCheck.jsx +++ b/packages/desktop-client/src/components/accounts/AccountSyncCheck.jsx @@ -1,7 +1,10 @@ import React, { useRef, useState } from 'react'; +import { Trans } from 'react-i18next'; import { useSelector } from 'react-redux'; import { useParams } from 'react-router-dom'; +import { t } from 'i18next'; + import { authorizeBank } from '../../gocardless'; import { useAccounts } from '../../hooks/useAccounts'; import { useActions } from '../../hooks/useActions'; @@ -17,9 +20,13 @@ function getErrorMessage(type, code) { case 'ITEM_ERROR': switch (code.toUpperCase()) { case 'NO_ACCOUNTS': - return 'No open accounts could be found. Did you close the account? If so, unlink the account.'; + return t( + 'No open accounts could be found. Did you close the account? If so, unlink the account.', + ); case 'ITEM_LOGIN_REQUIRED': - return 'Your password or something else has changed with your bank and you need to login again.'; + return t( + 'Your password or something else has changed with your bank and you need to login again.', + ); default: } break; @@ -27,39 +34,41 @@ function getErrorMessage(type, code) { case 'INVALID_INPUT': switch (code.toUpperCase()) { case 'INVALID_ACCESS_TOKEN': - return 'Item is no longer authorized. You need to login again.'; + return t('Item is no longer authorized. You need to login again.'); default: } break; case 'RATE_LIMIT_EXCEEDED': - return 'Rate limit exceeded for this item. Please try again later.'; + return t('Rate limit exceeded for this item. Please try again later.'); case 'INVALID_ACCESS_TOKEN': - return 'Your SimpleFIN Access Token is no longer valid. Please reset and generate a new token.'; + return t( + 'Your SimpleFIN Access Token is no longer valid. Please reset and generate a new token.', + ); case 'ACCOUNT_NEEDS_ATTENTION': return ( - <> + <Trans> The account needs your attention at{' '} <Link variant="external" to="https://bridge.simplefin.org/auth/login"> SimpleFIN </Link> . - </> + </Trans> ); default: } return ( - <> + <Trans> An internal error occurred. Try to login again, or get{' '} <Link variant="external" to="https://actualbudget.org/contact/"> in touch </Link>{' '} for support. - </> + </Trans> ); } @@ -116,7 +125,9 @@ export function AccountSyncCheck() { <SvgExclamationOutline style={{ width: 14, height: 14, marginRight: 5 }} />{' '} - This account is experiencing connection problems. Let’s fix it. + <Trans> + This account is experiencing connection problems. Let’s fix it. + </Trans> </Button> <Popover @@ -127,7 +138,7 @@ export function AccountSyncCheck() { style={{ fontSize: 14, padding: 15, maxWidth: 400 }} > <div style={{ marginBottom: '1.15em' }}> - The server returned the following error: + <Trans>The server returned the following error:</Trans> </div> <div style={{ marginBottom: '1.25em', color: theme.errorText }}> @@ -137,18 +148,22 @@ export function AccountSyncCheck() { <View style={{ justifyContent: 'flex-end', flexDirection: 'row' }}> {showAuth ? ( <> - <Button onPress={unlink}>Unlink</Button> + <Button onPress={unlink}> + <Trans>Unlink</Trans> + </Button> <Button variant="primary" autoFocus onPress={reauth} style={{ marginLeft: 5 }} > - Reauthorize + <Trans>Reauthorize</Trans> </Button> </> ) : ( - <Button onPress={unlink}>Unlink account</Button> + <Button onPress={unlink}> + <Trans>Unlink account</Trans> + </Button> )} </View> </Popover> diff --git a/packages/desktop-client/src/components/accounts/Balance.jsx b/packages/desktop-client/src/components/accounts/Balance.jsx index 91a500866269aa4f5495bde4536fb6968d23caf8..3422add9161372e1c1d79fdc15014f1d917729f1 100644 --- a/packages/desktop-client/src/components/accounts/Balance.jsx +++ b/packages/desktop-client/src/components/accounts/Balance.jsx @@ -1,4 +1,5 @@ import React, { useRef } from 'react'; +import { useTranslation } from 'react-i18next'; import { useHover } from 'usehooks-ts'; @@ -42,6 +43,8 @@ function DetailedBalance({ name, balance, isExactBalance = true }) { } function SelectedBalance({ selectedItems, account }) { + const { t } = useTranslation(); + const name = `selected-balance-${[...selectedItems].join('-')}`; const rows = useSheetValue({ @@ -99,7 +102,7 @@ function SelectedBalance({ selectedItems, account }) { return ( <DetailedBalance - name="Selected balance:" + name={t('Selected balance:')} balance={balance} isExactBalance={isExactBalance} /> @@ -107,9 +110,11 @@ function SelectedBalance({ selectedItems, account }) { } function FilteredBalance({ filteredAmount }) { + const { t } = useTranslation(); + return ( <DetailedBalance - name="Filtered balance:" + name={t('Filtered balance:')} balance={filteredAmount || 0} isExactBalance={true} /> @@ -117,6 +122,8 @@ function FilteredBalance({ filteredAmount }) { } function MoreBalances({ balanceQuery }) { + const { t } = useTranslation(); + const cleared = useSheetValue({ name: balanceQuery.name + '-cleared', query: balanceQuery.query.filter({ cleared: true }), @@ -128,8 +135,8 @@ function MoreBalances({ balanceQuery }) { return ( <View style={{ flexDirection: 'row' }}> - <DetailedBalance name="Cleared total:" balance={cleared} /> - <DetailedBalance name="Uncleared total:" balance={uncleared} /> + <DetailedBalance name={t('Cleared total:')} balance={cleared} /> + <DetailedBalance name={t('Uncleared total:')} balance={uncleared} /> </View> ); } diff --git a/packages/desktop-client/src/components/accounts/Header.jsx b/packages/desktop-client/src/components/accounts/Header.jsx index 91204e6b3bddc29d33fa3db9f4582166bf0a5c9c..f8aa6b492d87272b74def9482723b2908404d3ba 100644 --- a/packages/desktop-client/src/components/accounts/Header.jsx +++ b/packages/desktop-client/src/components/accounts/Header.jsx @@ -1,5 +1,6 @@ import React, { useState, useRef } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; +import { Trans, useTranslation } from 'react-i18next'; import { useLocalPref } from '../../hooks/useLocalPref'; import { useSplitsExpanded } from '../../hooks/useSplitsExpanded'; @@ -88,6 +89,8 @@ export function AccountHeader({ onMakeAsSplitTransaction, onMakeAsNonSplitTransactions, }) { + const { t } = useTranslation(); + const [menuOpen, setMenuOpen] = useState(false); const searchInput = useRef(null); const triggerRef = useRef(null); @@ -231,7 +234,7 @@ export function AccountHeader({ data-testid="account-name" > {account && account.closed - ? 'Closed: ' + accountName + ? t('Closed: {{ accountName }}', { accountName }) : accountName} </View> @@ -243,7 +246,7 @@ export function AccountHeader({ )} <Button variant="bare" - aria-label="Edit account name" + aria-label={t('Edit account name')} className="hover-visible" onPress={() => onExposeName(true)} > @@ -262,7 +265,7 @@ export function AccountHeader({ data-testid="account-name" > {account && account.closed - ? 'Closed: ' + accountName + ? t('Closed: {{ accountName }}', { accountName }) : accountName} </View> )} @@ -302,7 +305,7 @@ export function AccountHeader({ } style={{ marginRight: 4 }} />{' '} - {isServerOffline ? 'Bank Sync Offline' : 'Bank Sync'} + {isServerOffline ? t('Bank Sync Offline') : t('Bank Sync')} </> ) : ( <> @@ -311,15 +314,15 @@ export function AccountHeader({ height={13} style={{ marginRight: 4 }} />{' '} - Import + <Trans>Import</Trans> </> )} </Button> )} {!showEmptyMessage && ( <Button variant="bare" onPress={onAddTransaction}> - <SvgAdd width={10} height={10} style={{ marginRight: 3 }} /> Add - New + <SvgAdd width={10} height={10} style={{ marginRight: 3 }} /> + <Trans>Add New</Trans> </Button> )} <View style={{ flexShrink: 0 }}> @@ -327,7 +330,7 @@ export function AccountHeader({ </View> <View style={{ flex: 1 }} /> <Search - placeholder="Search" + placeholder={t('Search')} value={search} onChange={onSearch} inputRef={searchInput} @@ -359,8 +362,8 @@ export function AccountHeader({ variant="bare" aria-label={ splitsExpanded.state.mode === 'collapse' - ? 'Collapse split transactions' - : 'Expand split transactions' + ? t('Collapse split transactions') + : t('Expand split transactions') } isDisabled={search !== '' || filterConditions.length > 0} style={{ padding: 6, marginLeft: 10 }} @@ -369,8 +372,8 @@ export function AccountHeader({ <View title={ splitsExpanded.state.mode === 'collapse' - ? 'Collapse split transactions' - : 'Expand split transactions' + ? t('Collapse split transactions') + : t('Expand split transactions') } > {splitsExpanded.state.mode === 'collapse' ? ( @@ -432,9 +435,9 @@ export function AccountHeader({ items={[ isSorted && { name: 'remove-sorting', - text: 'Remove all sorting', + text: t('Remove all sorting'), }, - { name: 'export', text: 'Export' }, + { name: 'export', text: t('Export') }, ]} /> </Popover> @@ -480,6 +483,8 @@ function AccountMenu({ onReconcile, onMenuSelect, }) { + const { t } = useTranslation(); + const [tooltip, setTooltip] = useState('default'); const syncServerStatus = useSyncServerStatus(); @@ -501,36 +506,42 @@ function AccountMenu({ items={[ isSorted && { name: 'remove-sorting', - text: 'Remove all sorting', + text: t('Remove all sorting'), }, canShowBalances && { name: 'toggle-balance', - text: (showBalances ? 'Hide' : 'Show') + ' running balance', + text: showBalances + ? t('Hide running balance') + : t('Show running balance'), }, { name: 'toggle-cleared', - text: (showCleared ? 'Hide' : 'Show') + ' “cleared†checkboxes', + text: showCleared + ? t('Hide “cleared†checkboxes') + : t('Show “cleared†checkboxes'), }, { name: 'toggle-reconciled', - text: (showReconciled ? 'Hide' : 'Show') + ' reconciled transactions', + text: showReconciled + ? t('Hide reconciled transactions') + : t('Show reconciled transactions'), }, - { name: 'export', text: 'Export' }, - { name: 'reconcile', text: 'Reconcile' }, + { name: 'export', text: t('Export') }, + { name: 'reconcile', text: t('Reconcile') }, account && !account.closed && (canSync ? { name: 'unlink', - text: 'Unlink account', + text: t('Unlink account'), } : syncServerStatus === 'online' && { name: 'link', - text: 'Link account', + text: t('Link account'), }), account.closed - ? { name: 'reopen', text: 'Reopen account' } - : { name: 'close', text: 'Close account' }, + ? { name: 'reopen', text: t('Reopen account') } + : { name: 'close', text: t('Close account') }, ].filter(x => x)} /> ); diff --git a/packages/desktop-client/src/components/accounts/Reconcile.jsx b/packages/desktop-client/src/components/accounts/Reconcile.jsx index ab7127858ef6008f9e23480586f63c53fd5c9231..0a4ac13b16c39dec61c27eec3fcc510c0b60982d 100644 --- a/packages/desktop-client/src/components/accounts/Reconcile.jsx +++ b/packages/desktop-client/src/components/accounts/Reconcile.jsx @@ -1,4 +1,5 @@ import React, { useState } from 'react'; +import { Trans } from 'react-i18next'; import * as queries from 'loot-core/src/client/queries'; import { currencyToInteger } from 'loot-core/src/shared/util'; @@ -59,33 +60,42 @@ export function ReconcilingMessage({ marginRight: 3, }} /> - All reconciled! + <Trans>All reconciled!</Trans> </View> ) : ( <View style={{ color: theme.tableText }}> <Text style={{ fontStyle: 'italic', textAlign: 'center' }}> - Your cleared balance{' '} - <strong>{format(cleared, 'financial')}</strong> needs{' '} - <strong> - {(targetDiff > 0 ? '+' : '') + format(targetDiff, 'financial')} - </strong>{' '} - to match - <br /> your bank’s balance of{' '} - <Text style={{ fontWeight: 700 }}> - {format(targetBalance, 'financial')} - </Text> + <Trans> + Your cleared balance{' '} + <strong> + {{ clearedBalance: format(cleared, 'financial') }} + </strong>{' '} + needs{' '} + <strong> + {{ + difference: + (targetDiff > 0 ? '+' : '') + + format(targetDiff, 'financial'), + }} + </strong>{' '} + to match + <br /> your bank's balance of{' '} + <Text style={{ fontWeight: 700 }}> + {{ bankBalance: format(targetBalance, 'financial') }} + </Text> + </Trans> </Text> </View> )} <View style={{ marginLeft: 15 }}> <Button variant="primary" onPress={onDone}> - Done Reconciling + <Trans>Done Reconciling</Trans> </Button> </View> {targetDiff !== 0 && ( <View style={{ marginLeft: 15 }}> <Button onPress={() => onCreateTransaction(targetDiff)}> - Create Reconciliation Transaction + <Trans>Create Reconciliation Transaction</Trans> </Button> </View> )} @@ -121,8 +131,10 @@ export function ReconcileMenu({ account, onReconcile, onClose }) { return ( <View style={{ padding: '5px 8px' }}> <Text> - Enter the current balance of your bank account that you want to - reconcile with: + <Trans> + Enter the current balance of your bank account that you want to + reconcile with: + </Trans> </Text> {clearedBalance != null && ( <InitialFocus> @@ -136,7 +148,7 @@ export function ReconcileMenu({ account, onReconcile, onClose }) { </InitialFocus> )} <Button variant="primary" onPress={onSubmit}> - Reconcile + <Trans>Reconcile</Trans> </Button> </View> ); diff --git a/upcoming-release-notes/3277.md b/upcoming-release-notes/3277.md new file mode 100644 index 0000000000000000000000000000000000000000..201f78a71f76dc62c1b7f05fb0501885c9984182 --- /dev/null +++ b/upcoming-release-notes/3277.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [matt-fidd] +--- + +Support translations in desktop-client/components/accounts.