Adding translation to rule editor and transaction table (#224)
* #199 Adding translation to rule editor and transaction table * Feature: Translation to discover schedule table Fix: Some translation improvements * fix: Fix minor after check * Feature: More translation to account Fix: Add *_old.json files to ignore * Update packages/desktop-client/src/components/accounts/Account.js Co-authored-by: Tom French <15848336+TomAFrench@users.noreply.github.com> * fix: Workaround for know caveats * lint: fix import order * fix: t is not a function when empty transactions list * Feature: Translate account filters * Feature: Translation on transactions table * Feature: Translate budget and the rest of bootstrap * Update packages/desktop-client/src/locales/es-ES.json Co-authored-by: Jed Fox <git@jedfox.com> * fix: Using the new key for unknow error * refactor: push useTranslation up above function definition, etc * refactor: push useTranslation up above function definition * refactor: set key for Trans component balanceType * refactor: pass i18keys to Trans components explicitly Co-authored-by: Tom French <15848336+TomAFrench@users.noreply.github.com> Co-authored-by: Jed Fox <git@jedfox.com>
This commit is contained in:
parent
cbf1e18299
commit
6fb497dec5
15 changed files with 411 additions and 157 deletions
3
packages/desktop-client/.gitignore
vendored
3
packages/desktop-client/.gitignore
vendored
|
@ -16,3 +16,6 @@ npm-debug.log
|
|||
|
||||
*kcab.*
|
||||
public/kcab
|
||||
|
||||
# Ignore auto generated dictionaries with check-i18n
|
||||
src/locales/*_old.json
|
||||
|
|
|
@ -151,6 +151,7 @@ function ReconcilingMessage({ balanceQuery, targetBalance, onDone }) {
|
|||
<View style={{ color: colors.n3 }}>
|
||||
<Text style={{ fontStyle: 'italic', textAlign: 'center' }}>
|
||||
<Trans
|
||||
i18nKey={'account.clearedBalance'}
|
||||
values={{
|
||||
cleared: format(cleared, 'financial'),
|
||||
diff:
|
||||
|
@ -158,9 +159,7 @@ function ReconcilingMessage({ balanceQuery, targetBalance, onDone }) {
|
|||
format(targetDiff, 'financial'),
|
||||
balance: format(targetBalance, 'financial')
|
||||
}}
|
||||
>
|
||||
{'account.clearedBalance'}
|
||||
</Trans>
|
||||
/>
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
|
@ -176,6 +175,7 @@ function ReconcilingMessage({ balanceQuery, targetBalance, onDone }) {
|
|||
|
||||
function ReconcileTooltip({ account, onReconcile, onClose }) {
|
||||
let balance = useSheetValue(queries.accountBalance(account));
|
||||
const { t } = useTranslation();
|
||||
|
||||
function onSubmit(e) {
|
||||
let input = e.target.elements[0];
|
||||
|
@ -187,10 +187,7 @@ function ReconcileTooltip({ account, onReconcile, onClose }) {
|
|||
return (
|
||||
<Tooltip position="bottom-right" width={275} onClose={onClose}>
|
||||
<View style={{ padding: '5px 8px' }}>
|
||||
<Text>
|
||||
Enter the current balance of your bank account that you want to
|
||||
reconcile with:
|
||||
</Text>
|
||||
<Text>{t('account.enterCurrentBalanceToReconcileAdvice')}</Text>
|
||||
<form onSubmit={onSubmit}>
|
||||
{balance != null && (
|
||||
<InitialFocus>
|
||||
|
@ -200,7 +197,7 @@ function ReconcileTooltip({ account, onReconcile, onClose }) {
|
|||
/>
|
||||
</InitialFocus>
|
||||
)}
|
||||
<Button primary>Reconcile</Button>
|
||||
<Button primary>{t('account.reconcile')}</Button>
|
||||
</form>
|
||||
</View>
|
||||
</Tooltip>
|
||||
|
@ -243,6 +240,7 @@ function AccountMenu({
|
|||
onMenuSelect
|
||||
}) {
|
||||
let [tooltip, setTooltip] = useState('default');
|
||||
const { t } = useTranslation();
|
||||
|
||||
return tooltip === 'reconcile' ? (
|
||||
<ReconcileTooltip
|
||||
|
@ -263,19 +261,21 @@ function AccountMenu({
|
|||
items={[
|
||||
canShowBalances && {
|
||||
name: 'toggle-balance',
|
||||
text: (showBalances ? 'Hide' : 'Show') + ' Running Balance'
|
||||
text: showBalances
|
||||
? t('account.hideRunningBalance')
|
||||
: t('account.showRunningBalance')
|
||||
},
|
||||
{ name: 'export', text: 'Export' },
|
||||
{ name: 'reconcile', text: 'Reconcile' },
|
||||
{ name: 'export', text: t('general.export') },
|
||||
{ name: 'reconcile', text: t('account.reconcile') },
|
||||
syncEnabled &&
|
||||
account &&
|
||||
!account.closed &&
|
||||
(canSync
|
||||
? { name: 'unlink', text: 'Unlink Account' }
|
||||
: { name: 'link', text: 'Link Account' }),
|
||||
? { name: 'unlink', text: t('account.unlinkAccount') }
|
||||
: { name: 'link', text: t('account.linkAccount') }),
|
||||
account.closed
|
||||
? { name: 'reopen', text: 'Reopen Account' }
|
||||
: { name: 'close', text: 'Close Account' }
|
||||
? { name: 'reopen', text: t('account.reopenAccount') }
|
||||
: { name: 'close', text: t('account.closeAccount') }
|
||||
].filter(x => x)}
|
||||
/>
|
||||
</MenuTooltip>
|
||||
|
@ -283,19 +283,30 @@ function AccountMenu({
|
|||
}
|
||||
|
||||
function CategoryMenu({ onClose, onMenuSelect }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<MenuTooltip onClose={onClose}>
|
||||
<Menu
|
||||
onMenuSelect={item => {
|
||||
onMenuSelect(item);
|
||||
}}
|
||||
items={[{ name: 'export', text: 'Export' }]}
|
||||
items={[{ name: 'export', text: t('general.export') }]}
|
||||
/>
|
||||
</MenuTooltip>
|
||||
);
|
||||
}
|
||||
|
||||
function DetailedBalance({ name, balance }) {
|
||||
const balanceType = {
|
||||
// t('account.selectedBalance')
|
||||
selected: 'account.selectedBalance',
|
||||
// t('account.clearedTotal')
|
||||
cleared: 'account.clearedTotal',
|
||||
// t('account.unclearedTotal')
|
||||
uncleared: 'account.unclearedTotal'
|
||||
};
|
||||
|
||||
return (
|
||||
<Text
|
||||
style={{
|
||||
|
@ -306,8 +317,12 @@ function DetailedBalance({ name, balance }) {
|
|||
color: colors.n5
|
||||
}}
|
||||
>
|
||||
{name}{' '}
|
||||
<Text style={{ fontWeight: 600 }}>{format(balance, 'financial')}</Text>
|
||||
<Trans
|
||||
i18nKey={balanceType[name] || name}
|
||||
values={{
|
||||
amount: format(balance, 'financial')
|
||||
}}
|
||||
/>
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
@ -342,7 +357,8 @@ function SelectedBalance({ selectedItems }) {
|
|||
if (balance == null) {
|
||||
return null;
|
||||
}
|
||||
return <DetailedBalance name="Selected balance:" balance={balance} />;
|
||||
|
||||
return <DetailedBalance name="selected" balance={balance} />;
|
||||
}
|
||||
|
||||
function MoreBalances({ balanceQuery }) {
|
||||
|
@ -357,8 +373,8 @@ function MoreBalances({ balanceQuery }) {
|
|||
|
||||
return (
|
||||
<View style={{ flexDirection: 'row' }}>
|
||||
<DetailedBalance name="Cleared total:" balance={cleared} />
|
||||
<DetailedBalance name="Uncleared total:" balance={uncleared} />
|
||||
<DetailedBalance name="cleared" balance={cleared} />
|
||||
<DetailedBalance name="uncleared" balance={uncleared} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
@ -458,6 +474,7 @@ function SelectedTransactionsButton({
|
|||
}) {
|
||||
let selectedItems = useSelectedItems();
|
||||
let history = useHistory();
|
||||
const { t } = useTranslation();
|
||||
|
||||
let types = useMemo(() => {
|
||||
let items = [...selectedItems];
|
||||
|
@ -498,37 +515,43 @@ function SelectedTransactionsButton({
|
|||
items={[
|
||||
...(!types.trans
|
||||
? [
|
||||
{ name: 'view-schedule', text: 'View schedule' },
|
||||
{ name: 'post-transaction', text: 'Post transaction' },
|
||||
{ name: 'skip', text: 'Skip scheduled date' }
|
||||
{ name: 'view-schedule', text: t('schedules.view_one') },
|
||||
{
|
||||
name: 'post-transaction',
|
||||
text: t('schedules.postTransaction')
|
||||
},
|
||||
{ name: 'skip', text: t('schedules.skipScheduledDate') }
|
||||
]
|
||||
: [
|
||||
{ name: 'show', text: 'Show', key: 'F' },
|
||||
{ name: 'delete', text: 'Delete', key: 'D' },
|
||||
{ name: 'show', text: t('general.show'), key: 'F' },
|
||||
{ name: 'delete', text: t('general.delete'), key: 'D' },
|
||||
...(linked
|
||||
? [
|
||||
{
|
||||
name: 'view-schedule',
|
||||
text: 'View schedule',
|
||||
text: t('schedules.view_one'),
|
||||
disabled: selectedItems.size > 1
|
||||
},
|
||||
{ name: 'unlink-schedule', text: 'Unlink schedule' }
|
||||
{
|
||||
name: 'unlink-schedule',
|
||||
text: t('schedules.unlinkSchedule')
|
||||
}
|
||||
]
|
||||
: [
|
||||
{
|
||||
name: 'link-schedule',
|
||||
text: 'Link schedule'
|
||||
text: t('schedules.linkSchedule')
|
||||
}
|
||||
]),
|
||||
Menu.line,
|
||||
{ type: Menu.label, name: 'Edit field' },
|
||||
{ name: 'date', text: 'Date' },
|
||||
{ name: 'account', text: 'Account', key: 'A' },
|
||||
{ name: 'payee', text: 'Payee', key: 'P' },
|
||||
{ name: 'notes', text: 'Notes', key: 'N' },
|
||||
{ name: 'category', text: 'Category', key: 'C' },
|
||||
{ name: 'amount', text: 'Amount' },
|
||||
{ name: 'cleared', text: 'Cleared', key: 'L' }
|
||||
{ type: Menu.label, name: t('general.editField') },
|
||||
{ name: 'date', text: t('general.date') },
|
||||
{ name: 'account', text: t('general.account_one'), key: 'A' },
|
||||
{ name: 'payee', text: t('general.payee_one'), key: 'P' },
|
||||
{ name: 'notes', text: t('general.note_other'), key: 'N' },
|
||||
{ name: 'category', text: t('general.category_one'), key: 'C' },
|
||||
{ name: 'amount', text: t('general.amount') },
|
||||
{ name: 'cleared', text: t('account.cleared'), key: 'L' }
|
||||
])
|
||||
]}
|
||||
onSelect={name => {
|
||||
|
@ -620,6 +643,7 @@ const AccountHeader = React.memo(
|
|||
let [menuOpen, setMenuOpen] = useState(false);
|
||||
let searchInput = useRef(null);
|
||||
let splitsExpanded = useSplitsExpanded();
|
||||
const { t } = useTranslation();
|
||||
|
||||
let canSync = syncEnabled && account && account.account_id;
|
||||
if (!account) {
|
||||
|
@ -732,7 +756,7 @@ const AccountHeader = React.memo(
|
|||
}
|
||||
style={{ color: 'currentColor', marginRight: 4 }}
|
||||
/>{' '}
|
||||
Sync
|
||||
{t('general.sync')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
|
@ -741,7 +765,7 @@ const AccountHeader = React.memo(
|
|||
height={13}
|
||||
style={{ color: 'currentColor', marginRight: 4 }}
|
||||
/>{' '}
|
||||
Import
|
||||
{t('general.import')}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
|
@ -753,7 +777,7 @@ const AccountHeader = React.memo(
|
|||
height={10}
|
||||
style={{ color: 'inherit', marginRight: 3 }}
|
||||
/>{' '}
|
||||
Add New
|
||||
{t('general.addNew')}
|
||||
</Button>
|
||||
)}
|
||||
<View>
|
||||
|
@ -774,7 +798,7 @@ const AccountHeader = React.memo(
|
|||
}
|
||||
inputRef={searchInput}
|
||||
value={search}
|
||||
placeholder="Search"
|
||||
placeholder={t('general.search')}
|
||||
getStyle={focused => [
|
||||
{
|
||||
backgroundColor: 'transparent',
|
||||
|
@ -812,8 +836,8 @@ const AccountHeader = React.memo(
|
|||
onClick={onToggleSplits}
|
||||
title={
|
||||
splitsExpanded.state.mode === 'collapse'
|
||||
? 'Collapse split transactions'
|
||||
: 'Expand split transactions'
|
||||
? t('account.collapseSplitTransaction_other')
|
||||
: t('account.expandSplitTransaction_other')
|
||||
}
|
||||
>
|
||||
{splitsExpanded.state.mode === 'collapse' ? (
|
||||
|
@ -1191,11 +1215,15 @@ class AccountInternal extends React.PureComponent {
|
|||
onImport = async () => {
|
||||
const accountId = this.props.accountId;
|
||||
const account = this.props.accounts.find(acct => acct.id === accountId);
|
||||
const t = this.props.t;
|
||||
|
||||
if (account) {
|
||||
const res = await window.Actual.openFileDialog({
|
||||
filters: [
|
||||
{ name: 'Financial Files', extensions: ['qif', 'ofx', 'qfx', 'csv'] }
|
||||
{
|
||||
name: t('general.financialFile_other'),
|
||||
extensions: ['qif', 'ofx', 'qfx', 'csv']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
|
@ -1220,11 +1248,12 @@ class AccountInternal extends React.PureComponent {
|
|||
let normalizedName =
|
||||
accountName && accountName.replace(/[()]/g, '').replace(/\s+/g, '-');
|
||||
let filename = `${normalizedName || 'transactions'}.csv`;
|
||||
const t = this.props.t;
|
||||
|
||||
window.Actual.saveFile(
|
||||
exportedTransactions,
|
||||
filename,
|
||||
'Export Transactions'
|
||||
t('general.exportTransaction_other')
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1338,21 +1367,24 @@ class AccountInternal extends React.PureComponent {
|
|||
if (filterName) {
|
||||
return filterName;
|
||||
}
|
||||
const t = this.props.t;
|
||||
|
||||
if (!account) {
|
||||
if (id === 'budgeted') {
|
||||
return 'Budgeted Accounts';
|
||||
return t('account.budgetedAccount_other');
|
||||
} else if (id === 'offbudget') {
|
||||
return 'Off Budget Accounts';
|
||||
return t('account.offBudgetAccount_other');
|
||||
} else if (id === 'uncategorized') {
|
||||
return 'Uncategorized';
|
||||
return t('account.uncategorized');
|
||||
} else if (!id) {
|
||||
return 'All Accounts';
|
||||
return t('account.allAccounts');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return (account.closed ? 'Closed: ' : '') + account.name;
|
||||
return account.closed
|
||||
? t('account.closedNamed', { name: account.name })
|
||||
: account.name;
|
||||
}
|
||||
|
||||
getBalanceQuery(account, id) {
|
||||
|
@ -1391,8 +1423,10 @@ class AccountInternal extends React.PureComponent {
|
|||
};
|
||||
|
||||
onShowTransactions = async ids => {
|
||||
const t = this.props.t;
|
||||
|
||||
this.onApplyFilter({
|
||||
customName: 'Selected transactions',
|
||||
customName: t('account.selectedTransaction_other'),
|
||||
filter: { id: { $oneof: ids } }
|
||||
});
|
||||
};
|
||||
|
@ -1574,7 +1608,8 @@ class AccountInternal extends React.PureComponent {
|
|||
replaceModal,
|
||||
showExtraBalances,
|
||||
expandSplits,
|
||||
accountId
|
||||
accountId,
|
||||
t
|
||||
} = this.props;
|
||||
let {
|
||||
transactions,
|
||||
|
@ -1709,7 +1744,7 @@ class AccountInternal extends React.PureComponent {
|
|||
fontStyle: 'italic'
|
||||
}}
|
||||
>
|
||||
No transactions
|
||||
{t('general.noTransaction_other')}
|
||||
</View>
|
||||
) : null
|
||||
}
|
||||
|
@ -1739,10 +1774,13 @@ class AccountInternal extends React.PureComponent {
|
|||
|
||||
function AccountHack(props) {
|
||||
let { dispatch: splitsExpandedDispatch } = useSplitsExpanded();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<AccountInternal
|
||||
{...props}
|
||||
splitsExpandedDispatch={splitsExpandedDispatch}
|
||||
t={t}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React, { useState, useRef, useEffect, useReducer } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import {
|
||||
|
@ -132,6 +133,8 @@ function ConfigureField({ field, op, value, dispatch, onApply }) {
|
|||
ops = ['is'];
|
||||
}
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
position="bottom-left"
|
||||
|
@ -146,15 +149,15 @@ function ConfigureField({ field, op, value, dispatch, onApply }) {
|
|||
options={
|
||||
field === 'amount'
|
||||
? [
|
||||
['amount', 'Amount'],
|
||||
['amount-inflow', 'Amount (inflow)'],
|
||||
['amount-outflow', 'Amount (outflow)']
|
||||
['amount', t('general.amount')],
|
||||
['amount-inflow', t('general.amountIinflow')],
|
||||
['amount-outflow', t('general.amountOutflow')]
|
||||
]
|
||||
: field === 'date'
|
||||
? [
|
||||
['date', 'Date'],
|
||||
['month', 'Month'],
|
||||
['year', 'Year']
|
||||
['date', t('general.date')],
|
||||
['month', t('general.month')],
|
||||
['year', t('general.year')]
|
||||
]
|
||||
: null
|
||||
}
|
||||
|
@ -235,7 +238,7 @@ function ConfigureField({ field, op, value, dispatch, onApply }) {
|
|||
});
|
||||
}}
|
||||
>
|
||||
Apply
|
||||
{t('general.apply')}
|
||||
</Button>
|
||||
</View>
|
||||
</form>
|
||||
|
@ -251,6 +254,8 @@ export function FilterButton({ onApply }) {
|
|||
};
|
||||
});
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
let [state, dispatch] = useReducer(
|
||||
(state, action) => {
|
||||
switch (action.type) {
|
||||
|
@ -306,7 +311,7 @@ export function FilterButton({ onApply }) {
|
|||
if (isDateValid(date)) {
|
||||
cond.value = formatDate(date, 'yyyy-MM');
|
||||
} else {
|
||||
alert('Invalid date format');
|
||||
alert(t('general.invalidDateFormat'));
|
||||
return;
|
||||
}
|
||||
} else if (cond.options.year) {
|
||||
|
@ -314,7 +319,7 @@ export function FilterButton({ onApply }) {
|
|||
if (isDateValid(date)) {
|
||||
cond.value = formatDate(date, 'yyyy');
|
||||
} else {
|
||||
alert('Invalid date format');
|
||||
alert(t('general.invalidDateFormat'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React, { useMemo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import {
|
||||
|
@ -181,6 +182,7 @@ export default function SimpleTransactionsTable({
|
|||
},
|
||||
[payees, categories, memoFields, selectedItems]
|
||||
);
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Table
|
||||
|
@ -200,43 +202,43 @@ export default function SimpleTransactionsTable({
|
|||
case 'date':
|
||||
return (
|
||||
<Field key={i} width={100}>
|
||||
Date
|
||||
{t('general.date')}
|
||||
</Field>
|
||||
);
|
||||
case 'imported_payee':
|
||||
return (
|
||||
<Field key={i} width="flex">
|
||||
Imported payee
|
||||
{t('general.importedPayee')}
|
||||
</Field>
|
||||
);
|
||||
case 'payee':
|
||||
return (
|
||||
<Field key={i} width="flex">
|
||||
Payee
|
||||
{t('general.payee_one')}
|
||||
</Field>
|
||||
);
|
||||
case 'category':
|
||||
return (
|
||||
<Field key={i} width="flex">
|
||||
Category
|
||||
{t('general.category_one')}
|
||||
</Field>
|
||||
);
|
||||
case 'account':
|
||||
return (
|
||||
<Field key={i} width="flex">
|
||||
Account
|
||||
{t('general.account_one')}
|
||||
</Field>
|
||||
);
|
||||
case 'notes':
|
||||
return (
|
||||
<Field key={i} width="flex">
|
||||
Notes
|
||||
{t('general.note_other')}
|
||||
</Field>
|
||||
);
|
||||
case 'amount':
|
||||
return (
|
||||
<Field key={i} width={75} style={{ textAlign: 'right' }}>
|
||||
Amount
|
||||
{t('general.amount')}
|
||||
</Field>
|
||||
);
|
||||
default:
|
||||
|
|
|
@ -8,6 +8,7 @@ import React, {
|
|||
useContext,
|
||||
useReducer
|
||||
} from 'react';
|
||||
import { useTranslation, Trans } from 'react-i18next';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
|
||||
import {
|
||||
|
@ -253,6 +254,7 @@ export function SplitsExpandedProvider({ children, initialMode = 'expand' }) {
|
|||
export const TransactionHeader = React.memo(
|
||||
({ hasSelected, showAccount, showCategory, showBalance }) => {
|
||||
let dispatchSelected = useSelectedDispatch();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Row
|
||||
|
@ -271,14 +273,18 @@ export const TransactionHeader = React.memo(
|
|||
width={20}
|
||||
onSelect={() => dispatchSelected({ type: 'select-all' })}
|
||||
/>
|
||||
<Cell value="Date" width={110} />
|
||||
{showAccount && <Cell value="Account" width="flex" />}
|
||||
<Cell value="Payee" width="flex" />
|
||||
<Cell value="Notes" width="flex" />
|
||||
{showCategory && <Cell value="Category" width="flex" />}
|
||||
<Cell value="Payment" width={80} textAlign="right" />
|
||||
<Cell value="Deposit" width={80} textAlign="right" />
|
||||
{showBalance && <Cell value="Balance" width={85} textAlign="right" />}
|
||||
<Cell value={t('general.date')} width={110} />
|
||||
{showAccount && <Cell value={t('general.account_one')} width="flex" />}
|
||||
<Cell value={t('general.payee_other')} width="flex" />
|
||||
<Cell value={t('general.note_other')} width="flex" />
|
||||
{showCategory && (
|
||||
<Cell value={t('general.category_other')} width="flex" />
|
||||
)}
|
||||
<Cell value={t('general.payment_one')} width={80} textAlign="right" />
|
||||
<Cell value={t('general.deposit_one')} width={80} textAlign="right" />
|
||||
{showBalance && (
|
||||
<Cell value={t('general.balance_one')} width={85} textAlign="right" />
|
||||
)}
|
||||
<Field width={21} truncate={false} />
|
||||
<Cell value="" width={15 + styles.scrollbarWidth} />
|
||||
</Row>
|
||||
|
@ -528,6 +534,7 @@ export const Transaction = React.memo(function Transaction(props) {
|
|||
onCreatePayee,
|
||||
onToggleSplit
|
||||
} = props;
|
||||
const { t } = useTranslation();
|
||||
|
||||
let dispatchSelected = useSelectedDispatch();
|
||||
|
||||
|
@ -896,7 +903,7 @@ export const Transaction = React.memo(function Transaction(props) {
|
|||
/>
|
||||
)}
|
||||
<Text style={{ fontStyle: 'italic', userSelect: 'none' }}>
|
||||
Split
|
||||
{t('general.split')}
|
||||
</Text>
|
||||
</View>
|
||||
</CellButton>
|
||||
|
@ -910,11 +917,11 @@ export const Transaction = React.memo(function Transaction(props) {
|
|||
onExpose={!isPreview && (name => onEdit(id, name))}
|
||||
value={
|
||||
isParent
|
||||
? 'Split'
|
||||
? t('general.split')
|
||||
: isOffBudget
|
||||
? 'Off Budget'
|
||||
? t('general.offBudget')
|
||||
: isBudgetTransfer
|
||||
? 'Transfer'
|
||||
? t('general.transfer')
|
||||
: ''
|
||||
}
|
||||
valueStyle={valueStyle}
|
||||
|
@ -936,7 +943,7 @@ export const Transaction = React.memo(function Transaction(props) {
|
|||
'name'
|
||||
)
|
||||
: transaction.id
|
||||
? 'Categorize'
|
||||
? t('general.categorize')
|
||||
: ''
|
||||
}
|
||||
exposed={focusedField === 'category'}
|
||||
|
@ -1049,6 +1056,8 @@ export const Transaction = React.memo(function Transaction(props) {
|
|||
});
|
||||
|
||||
export function TransactionError({ error, isDeposit, onAddSplit, style }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
switch (error.type) {
|
||||
case 'SplitTransactionError':
|
||||
if (error.version === 1) {
|
||||
|
@ -1064,21 +1073,21 @@ export function TransactionError({ error, isDeposit, onAddSplit, style }) {
|
|||
]}
|
||||
data-testid="transaction-error"
|
||||
>
|
||||
<Text>
|
||||
Amount left:{' '}
|
||||
<Text style={{ fontWeight: 500 }}>
|
||||
{integerToCurrency(
|
||||
<Trans
|
||||
i18nKey={'general.amountLeft'}
|
||||
values={{
|
||||
amount: integerToCurrency(
|
||||
isDeposit ? error.difference : -error.difference
|
||||
)}
|
||||
</Text>
|
||||
</Text>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<View style={{ flex: 1 }} />
|
||||
<Button
|
||||
style={{ marginLeft: 15, padding: '4px 10px' }}
|
||||
primary
|
||||
onClick={onAddSplit}
|
||||
>
|
||||
Add Split
|
||||
{t('general.addSplit')}
|
||||
</Button>
|
||||
</View>
|
||||
);
|
||||
|
@ -1135,6 +1144,7 @@ function NewTransaction({
|
|||
}) {
|
||||
const error = transactions[0].error;
|
||||
const isDeposit = transactions[0].amount > 0;
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<View
|
||||
|
@ -1192,7 +1202,7 @@ function NewTransaction({
|
|||
onClick={() => onClose()}
|
||||
data-testid="cancel-button"
|
||||
>
|
||||
Cancel
|
||||
{t('general.cancel')}
|
||||
</Button>
|
||||
{error ? (
|
||||
<TransactionError
|
||||
|
@ -1207,7 +1217,7 @@ function NewTransaction({
|
|||
onClick={onAdd}
|
||||
data-testid="add-button"
|
||||
>
|
||||
Add
|
||||
{t('general.add')}
|
||||
</Button>
|
||||
)}
|
||||
</View>
|
||||
|
@ -1532,12 +1542,14 @@ export let TransactionTable = React.forwardRef((props, ref) => {
|
|||
setPrevIsAdding(props.isAdding);
|
||||
}
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldAdd.current) {
|
||||
if (newTransactions[0].account == null) {
|
||||
props.addNotification({
|
||||
type: 'error',
|
||||
message: 'Account is a required field'
|
||||
message: t('transaction.accountIsRequired')
|
||||
});
|
||||
newNavigator.onEdit('temp', 'account');
|
||||
} else {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useBudgetMonthCount } from 'loot-design/src/components/budget/BudgetMonthCountContext';
|
||||
import { View } from 'loot-design/src/components/common';
|
||||
|
@ -16,6 +17,7 @@ function Calendar({ color, onClick }) {
|
|||
|
||||
export function MonthCountSelector({ maxMonths, onChange }) {
|
||||
let { displayMax } = useBudgetMonthCount();
|
||||
const { t } = useTranslation();
|
||||
|
||||
let style = { width: 15, height: 15, color: colors.n8 };
|
||||
let activeStyle = { color: colors.n5 };
|
||||
|
@ -50,7 +52,7 @@ export function MonthCountSelector({ maxMonths, onChange }) {
|
|||
transform: 'scale(1.2)'
|
||||
}
|
||||
}}
|
||||
title="Choose the number of months shown at a time"
|
||||
title={t('budget.chooseNumberMonths')}
|
||||
>
|
||||
{calendars}
|
||||
</View>
|
||||
|
|
|
@ -24,13 +24,13 @@ export default function Bootstrap() {
|
|||
function getErrorMessage(error) {
|
||||
switch (error) {
|
||||
case 'invalid-password':
|
||||
return 'Password cannot be empty';
|
||||
return t('bootstrap.passwordCannotBeEmpty');
|
||||
case 'password-match':
|
||||
return 'Passwords do not match';
|
||||
return t('bootstrap.passwordsDoNotMatch');
|
||||
case 'network-failure':
|
||||
return 'Unable to contact the server';
|
||||
return t('bootstrap.unableToContactTheServer');
|
||||
default:
|
||||
return "Whoops, an error occurred on our side! We'll try to get it fixed soon.";
|
||||
return t('bootstrap.unknownError');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
|
@ -14,17 +15,18 @@ export default function ChangePassword() {
|
|||
let history = useHistory();
|
||||
let [error, setError] = useState(null);
|
||||
let [msg, setMessage] = useState(null);
|
||||
const { t } = useTranslation();
|
||||
|
||||
function getErrorMessage(error) {
|
||||
switch (error) {
|
||||
case 'invalid-password':
|
||||
return 'Password cannot be empty';
|
||||
return t('bootstrap.passwordCannotBeEmpty');
|
||||
case 'password-match':
|
||||
return 'Passwords do not match';
|
||||
return t('bootstrap.passwordsDoNotMatch');
|
||||
case 'network-failure':
|
||||
return 'Unable to contact the server';
|
||||
return t('bootstrap.unableToContactTheServer');
|
||||
default:
|
||||
return 'Internal server error';
|
||||
return t('bootstrap.unknownError');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -35,7 +37,7 @@ export default function ChangePassword() {
|
|||
if (error) {
|
||||
setError(error);
|
||||
} else {
|
||||
setMessage('Password successfully changed');
|
||||
setMessage(t('bootstrap.passwordSuccessfullyChanged'));
|
||||
|
||||
setTimeout(() => {
|
||||
history.push('/');
|
||||
|
@ -46,7 +48,7 @@ export default function ChangePassword() {
|
|||
return (
|
||||
<>
|
||||
<View style={{ width: 500, marginTop: -30 }}>
|
||||
<Title text="Change server password" />
|
||||
<Title text={t('bootstrap.changeServerPassword')} />
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 16,
|
||||
|
@ -54,8 +56,7 @@ export default function ChangePassword() {
|
|||
lineHeight: 1.4
|
||||
}}
|
||||
>
|
||||
This will change the password for this server instance. All existing
|
||||
sessions will stay logged in.
|
||||
{t('bootstrap.thisWillChangeThePasswordAdvice')}
|
||||
</Text>
|
||||
|
||||
{error && (
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import {
|
||||
|
@ -280,7 +281,6 @@ function ActionEditor({ ops, action, editorStyle, onChange, onDelete, onAdd }) {
|
|||
return (
|
||||
<Editor style={editorStyle} error={error}>
|
||||
{/*<OpSelect ops={ops} value={op} onChange={onChange} />*/}
|
||||
|
||||
{op === 'set' ? (
|
||||
<>
|
||||
<View style={{ padding: '5px 10px', lineHeight: '1em' }}>
|
||||
|
@ -325,6 +325,7 @@ function ActionEditor({ ops, action, editorStyle, onChange, onDelete, onAdd }) {
|
|||
|
||||
function StageInfo() {
|
||||
let [open, setOpen] = useState();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<View style={{ position: 'relative', marginLeft: 5 }}>
|
||||
|
@ -346,9 +347,7 @@ function StageInfo() {
|
|||
lineHeight: 1.5
|
||||
}}
|
||||
>
|
||||
The stage of a rule allows you to force a specific order. Pre rules
|
||||
always run first, and post rules always run last. Within each stage
|
||||
rules are automatically ordered from least to most specific.
|
||||
{t('rules.stageOfRuleAdvice')}
|
||||
</Tooltip>
|
||||
)}
|
||||
</View>
|
||||
|
@ -554,6 +553,7 @@ export default function EditRule({
|
|||
let [transactions, setTransactions] = useState([]);
|
||||
let dispatch = useDispatch();
|
||||
let scrollableEl = useRef();
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(initiallyLoadPayees());
|
||||
|
@ -687,7 +687,7 @@ export default function EditRule({
|
|||
|
||||
return (
|
||||
<Modal
|
||||
title="Rule"
|
||||
title={t('general.rule_one')}
|
||||
padding={0}
|
||||
{...modalProps}
|
||||
style={[modalProps.style, { flex: 'inherit', maxWidth: '90%' }]}
|
||||
|
@ -713,7 +713,7 @@ export default function EditRule({
|
|||
}}
|
||||
>
|
||||
<Text style={{ color: colors.n4, marginRight: 15 }}>
|
||||
Stage of rule:
|
||||
{t('rules.stageOfRule')}
|
||||
</Text>
|
||||
|
||||
<Stack direction="row" align="center" spacing={1}>
|
||||
|
@ -721,19 +721,19 @@ export default function EditRule({
|
|||
selected={stage === 'pre'}
|
||||
onSelect={() => onChangeStage('pre')}
|
||||
>
|
||||
Pre
|
||||
{t('rules.stages.pre')}
|
||||
</StageButton>
|
||||
<StageButton
|
||||
selected={stage === null}
|
||||
onSelect={() => onChangeStage(null)}
|
||||
>
|
||||
Default
|
||||
{t('rules.stages.default')}
|
||||
</StageButton>
|
||||
<StageButton
|
||||
selected={stage === 'post'}
|
||||
onSelect={() => onChangeStage('post')}
|
||||
>
|
||||
Post
|
||||
{t('rules.stages.post')}
|
||||
</StageButton>
|
||||
|
||||
<StageInfo />
|
||||
|
@ -752,7 +752,7 @@ export default function EditRule({
|
|||
<View style={{ flexShrink: 0 }}>
|
||||
<View style={{ marginBottom: 30 }}>
|
||||
<Text style={{ color: colors.n4, marginBottom: 15 }}>
|
||||
If all these conditions match:
|
||||
{t('rules.ifAllTheseConditionsMatch')}
|
||||
</Text>
|
||||
|
||||
<ConditionsList
|
||||
|
@ -764,7 +764,7 @@ export default function EditRule({
|
|||
</View>
|
||||
|
||||
<Text style={{ color: colors.n4, marginBottom: 15 }}>
|
||||
Then apply these actions:
|
||||
{t('rules.thenApplyTheseActions')}
|
||||
</Text>
|
||||
<View style={{ flex: 1 }}>
|
||||
{actions.length === 0 ? (
|
||||
|
@ -772,7 +772,7 @@ export default function EditRule({
|
|||
style={{ alignSelf: 'flex-start' }}
|
||||
onClick={addInitialAction}
|
||||
>
|
||||
Add action
|
||||
{t('rules.addAction')}
|
||||
</Button>
|
||||
) : (
|
||||
<Stack spacing={2}>
|
||||
|
@ -807,7 +807,7 @@ export default function EditRule({
|
|||
}}
|
||||
>
|
||||
<Text style={{ color: colors.n4, marginBottom: 0 }}>
|
||||
This rule applies to these transactions:
|
||||
{t('rules.thisRuleAppliesToTheseTransactions')}
|
||||
</Text>
|
||||
|
||||
<View style={{ flex: 1 }} />
|
||||
|
@ -815,7 +815,7 @@ export default function EditRule({
|
|||
disabled={selectedInst.items.size === 0}
|
||||
onClick={onApply}
|
||||
>
|
||||
Apply actions ({selectedInst.items.size})
|
||||
{t('rules.applyAction', { size: selectedInst.items.size })}
|
||||
</Button>
|
||||
</View>
|
||||
|
||||
|
@ -830,9 +830,11 @@ export default function EditRule({
|
|||
justify="flex-end"
|
||||
style={{ marginTop: 20 }}
|
||||
>
|
||||
<Button onClick={() => modalProps.onClose()}>Cancel</Button>
|
||||
<Button onClick={() => modalProps.onClose()}>
|
||||
{t('general.cancel')}
|
||||
</Button>
|
||||
<Button primary onClick={() => onSave()}>
|
||||
Save
|
||||
{t('general.save')}
|
||||
</Button>
|
||||
</Stack>
|
||||
</View>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useLocation, useHistory } from 'react-router-dom';
|
||||
|
||||
import Platform from 'loot-core/src/client/platform';
|
||||
|
@ -35,6 +36,7 @@ let ROW_HEIGHT = 43;
|
|||
function DiscoverSchedulesTable({ schedules, loading }) {
|
||||
let selectedItems = useSelectedItems();
|
||||
let dispatchSelected = useSelectedDispatch();
|
||||
const { t } = useTranslation();
|
||||
|
||||
function renderItem({ item }) {
|
||||
let selected = selectedItems.has(item.id);
|
||||
|
@ -89,13 +91,13 @@ function DiscoverSchedulesTable({ schedules, loading }) {
|
|||
selected={selectedItems.size > 0}
|
||||
onSelect={() => dispatchSelected({ type: 'select-all' })}
|
||||
/>
|
||||
<Field width="flex">Payee</Field>
|
||||
<Field width="flex">Account</Field>
|
||||
<Field width="flex">{t('general.payee_one')}</Field>
|
||||
<Field width="flex">{t('general.account_one')}</Field>
|
||||
<Field width="auto" style={{ flex: 1.5 }}>
|
||||
When
|
||||
{t('general.when')}
|
||||
</Field>
|
||||
<Field width={100} style={{ textAlign: 'right' }}>
|
||||
Amount
|
||||
{t('general.amount')}
|
||||
</Field>
|
||||
</TableHeader>
|
||||
<Table
|
||||
|
@ -107,7 +109,7 @@ function DiscoverSchedulesTable({ schedules, loading }) {
|
|||
loading={loading}
|
||||
isSelected={id => selectedItems.has(id)}
|
||||
renderItem={renderItem}
|
||||
renderEmpty="No schedules found"
|
||||
renderEmpty={t('schedules.noSchedulesFound')}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
@ -161,24 +163,19 @@ export default function DiscoverSchedules() {
|
|||
setCreating(false);
|
||||
history.goBack();
|
||||
}
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Page title="Found schedules" modalSize={{ width: 850, height: 650 }}>
|
||||
<Page
|
||||
title={t('schedules.foundSchedules')}
|
||||
modalSize={{ width: 850, height: 650 }}
|
||||
>
|
||||
<P>{t('schedules.foundSomePossibleSchedulesAdvice')}</P>
|
||||
<P>{t('schedules.expectedSchedulesAdvice')}</P>
|
||||
<P>
|
||||
We found some possible schedules in your current transactions. Select
|
||||
the ones you want to create.
|
||||
</P>
|
||||
<P>
|
||||
If you expected a schedule here and don't see it, it might be because
|
||||
the payees of the transactions don't match. Make sure you rename payees
|
||||
on all transactions for a schedule to be the same payee.
|
||||
</P>
|
||||
<P>
|
||||
You can always do this later
|
||||
{Platform.isBrowser
|
||||
? ' from the "Find schedules" item in the sidebar menu'
|
||||
: ' from the "Tools > Find schedules" menu item'}
|
||||
.
|
||||
? t('schedules.doFromFindSchedules')
|
||||
: t('schedules.doFromToolsFindSchedules')}
|
||||
</P>
|
||||
|
||||
<SelectedProvider instance={selectedInst}>
|
||||
|
@ -194,14 +191,16 @@ export default function DiscoverSchedules() {
|
|||
justify="flex-end"
|
||||
style={{ paddingTop: 20 }}
|
||||
>
|
||||
<Button onClick={() => history.goBack()}>Do nothing</Button>
|
||||
<Button onClick={() => history.goBack()}>
|
||||
{t('general.doNothing')}
|
||||
</Button>
|
||||
<ButtonWithLoading
|
||||
primary
|
||||
loading={creating}
|
||||
disabled={selectedInst.items.size === 0}
|
||||
onClick={onCreate}
|
||||
>
|
||||
Create schedules
|
||||
{t('schedules.createSchedule', { count: selectedInst.items.size })}
|
||||
</ButtonWithLoading>
|
||||
</Stack>
|
||||
</Page>
|
||||
|
|
|
@ -437,7 +437,7 @@ export default function ScheduleDetails() {
|
|||
>
|
||||
<Stack direction="row" style={{ marginTop: 20 }}>
|
||||
<FormField style={{ flex: 1 }}>
|
||||
<FormLabel title={t('general.payee')} />
|
||||
<FormLabel title={t('general.payee_one')} />
|
||||
<PayeeAutocomplete
|
||||
value={state.fields.payee}
|
||||
inputProps={{ placeholder: t('schedules.none') }}
|
||||
|
|
|
@ -241,7 +241,7 @@ export function SchedulesTable({
|
|||
return (
|
||||
<>
|
||||
<TableHeader height={ROW_HEIGHT} inset={15} version="v2">
|
||||
<Field width="flex">{t('general.payee')}</Field>
|
||||
<Field width="flex">{t('general.payee_one')}</Field>
|
||||
<Field width="flex">{t('general.account_one')}</Field>
|
||||
<Field width={110}>{t('schedules.nextDate')}</Field>
|
||||
<Field width={120}>{t('general.status')}</Field>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {
|
||||
integerToCurrency,
|
||||
|
@ -52,6 +53,8 @@ export function BetweenAmountInput({ defaultValue, onChange }) {
|
|||
let [num1, setNum1] = useState(defaultValue.num1);
|
||||
let [num2, setNum2] = useState(defaultValue.num2);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||
<AmountInput
|
||||
|
@ -61,7 +64,7 @@ export function BetweenAmountInput({ defaultValue, onChange }) {
|
|||
onChange({ num1: value, num2 });
|
||||
}}
|
||||
/>
|
||||
<View style={{ margin: '0 5px' }}>and</View>
|
||||
<View style={{ margin: '0 5px' }}>{t('general.and')}</View>
|
||||
<AmountInput
|
||||
defaultValue={num2}
|
||||
onChange={value => {
|
||||
|
|
|
@ -2,58 +2,148 @@
|
|||
"account": {
|
||||
"addAccount": "Add account",
|
||||
"addAccountInFutureFromSidebar": "In the future, you can add accounts from the sidebar.",
|
||||
"allAccounts": "All Accounts",
|
||||
"allReconciled": "All reconciled!",
|
||||
"budgetedAccount_other": "Budgeted Accounts",
|
||||
"cleared": "Cleared",
|
||||
"clearedBalance": "Your cleared balance <strong>{{cleared}}</strong> needs <strong>{{diff}}</strong> to match your bank's balance of <strong>{{balance}}</strong>",
|
||||
"clearedTotal": "Cleared Total: <strong>{{amount}}</strong>",
|
||||
"closeAccount": "Close Account",
|
||||
"closedNamed": "Close: {{name}}",
|
||||
"collapseSplitTransaction_other": "Collapse split transactions",
|
||||
"doneReconciling": "Done Reconciling",
|
||||
"needAccountMessage": "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."
|
||||
"enterCurrentBalanceToReconcileAdvice": "Enter the current balance of your bank account that you want to reconcile with:",
|
||||
"expandSplitTransaction_other": "Expand split transactions",
|
||||
"hideRunningBalance": "Hide Running Balance",
|
||||
"linkAccount": "Link account",
|
||||
"needAccountMessage": "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.",
|
||||
"offBudgetAccount_other": "Off Budget Accounts",
|
||||
"reconcile": "Reconcile",
|
||||
"reopenAccount": "Reopen account",
|
||||
"selectedBalance": "Selected Balance: <strong>{{amount}}</strong>",
|
||||
"selectedTransaction_other": "Selected Transactions",
|
||||
"showRunningBalance": "Show Running Balance",
|
||||
"uncategorized": "Uncategorized",
|
||||
"unclearedTotal": "Uncleared Total: <strong>{{amount}}</strong>",
|
||||
"unlinkAccount": "Unlink Account"
|
||||
},
|
||||
"bootstrap": {
|
||||
"changeServerPassword": "Change server password",
|
||||
"passwordCannotBeEmpty": "Password cannot be empty",
|
||||
"passwordsDoNotMatch": "Passwords do not match",
|
||||
"passwordSuccessfullyChanged": "Password Successfully changed",
|
||||
"setPassword": "Set a password for this server instance",
|
||||
"thisWillChangeThePasswordAdvice": "This will change the password for this server instance. All existing sessions will stay logged in.",
|
||||
"title": "Bootstrap this Actual instance",
|
||||
"tryDemo": "Try Demo"
|
||||
"tryDemo": "Try Demo",
|
||||
"unableToContactTheServer": "Unable to contact the server",
|
||||
"unknownError": "Whoops, an error occurred on our side! We'll try to get it fixed soon."
|
||||
},
|
||||
"budget": {
|
||||
"chooseNumberMonths": "Choose the number of months shown at a time"
|
||||
},
|
||||
"general": {
|
||||
"account_one": "Account",
|
||||
"account_other": "Accounts",
|
||||
"add": "Add",
|
||||
"addNew": "Add new",
|
||||
"addSplit": "Add Split",
|
||||
"amount": "Amount",
|
||||
"amountIinflow": "Amount (inflow)",
|
||||
"amountLeft": "Amount left: <strong>{{amount}}</strong>",
|
||||
"amountOutflow": "Amount (outflow)",
|
||||
"and": "and",
|
||||
"apply": "Apply",
|
||||
"approximatelyWithAmount": "Approximately {{amount}}",
|
||||
"balance_one": "Balance",
|
||||
"cancel": "Cancel",
|
||||
"categorize": "Categorize",
|
||||
"category_one": "Category",
|
||||
"category_other": "Categories",
|
||||
"complete": "Complete",
|
||||
"date": "Date",
|
||||
"delete": "Delete",
|
||||
"payee": "Payee",
|
||||
"deposit_one": "Deposit",
|
||||
"doNothing": "Do nothing",
|
||||
"editField": "Edit field",
|
||||
"export": "Export",
|
||||
"exportTransaction_other": "Export Transactions",
|
||||
"financialFile_other": "Financial Files",
|
||||
"import": "Import",
|
||||
"importedPayee": "Imported payee",
|
||||
"invalidDateFormat": "Invalid date format",
|
||||
"month": "Month",
|
||||
"note_other": "Notes",
|
||||
"noTransaction_other": "No transactions",
|
||||
"offBudget": "Off Budget",
|
||||
"payee_one": "Payee",
|
||||
"payee_other": "Payees",
|
||||
"payment_one": "Payment",
|
||||
"recurring": "Recurring",
|
||||
"repeats": "Repeats",
|
||||
"restart": "Restart",
|
||||
"rule_one": "Rule",
|
||||
"save": "Save",
|
||||
"schedule": "Schedule",
|
||||
"schedule_other": "Schedules",
|
||||
"status": "Status"
|
||||
"search": "Search",
|
||||
"show": "Show",
|
||||
"split": "Split",
|
||||
"status": "Status",
|
||||
"sync": "Sync",
|
||||
"transfer": "Transfer",
|
||||
"when": "When",
|
||||
"year": "Year"
|
||||
},
|
||||
"rules": {
|
||||
"addAction": "Add action",
|
||||
"applyAction": "Apply action ({{size}})",
|
||||
"ifAllTheseConditionsMatch": "If all these conditions match:",
|
||||
"stageOfRule": "Stage of rule:",
|
||||
"stageOfRuleAdvice": "The stage of a rule allows you to force a specific order. Pre rules always run first, and post rules always run last. Within each stage rules are automatically ordered from least to most specific.",
|
||||
"stages": {
|
||||
"default": "Default",
|
||||
"post": "Post",
|
||||
"pre": "Pre"
|
||||
},
|
||||
"thenApplyTheseActions": "Then apply these actions:",
|
||||
"thisRuleAppliesToTheseTransactions": "This rule applies to these transactions:"
|
||||
},
|
||||
"schedules": {
|
||||
"addNewSchedule": "Add new schedule",
|
||||
"automaticallyAddTransaction": "Automatically add transaction",
|
||||
"automaticallyAddTransactionAdvice": "If checked, the schedule will automatically create transactions for you in the specified account",
|
||||
"createSchedule_one": "Create schedule",
|
||||
"createSchedule_other": "Create schedules",
|
||||
"doFromFindSchedules": "You can always do this later from the \"Find schedules\" item in the sidebar menu",
|
||||
"doFromToolsFindSchedules": "You can always do this later from the \"Tools > Find schedules\" menu item",
|
||||
"editAsRule": "Edit as rule",
|
||||
"expectedSchedulesAdvice": "If you expected a schedule here and don't see it, it might be because the payees of the transactions don't match. Make sure you rename payees on all transactions for a schedule to be the same payee.",
|
||||
"findMatchingTransactions": "Find matching transactions",
|
||||
"foundSchedules": "Found schedules",
|
||||
"foundSomePossibleSchedulesAdvice": "We found some possible schedules in your current transactions. Select the ones you want to create.",
|
||||
"isApproximately": "is approximately",
|
||||
"isBetween": "is between",
|
||||
"isExactly": "is exactly",
|
||||
"linkedTransactions": "Linked transactions",
|
||||
"linkSchedule": "Link Schedule",
|
||||
"linkToSchedule": "Link to schedule",
|
||||
"nextDate": "Next date",
|
||||
"none": "(none)",
|
||||
"noSchedules": "No schedules",
|
||||
"noSchedulesFound": "No schedules found",
|
||||
"postTransaction": "Post transaction",
|
||||
"scheduleNamed": "Schedule: {{name}}",
|
||||
"selectTransactionsToLinkOnSave": "Select transactions to link on save",
|
||||
"showCompletedSchedules": "Show completed schedules",
|
||||
"skipNextDate": "Skip next date",
|
||||
"skipScheduledDate": "Skip scheduled date",
|
||||
"theseTransactionsMatchThisSchedule": "These transactions match this schedule:",
|
||||
"thisScheduleHasCustomConditionsAndActions": "This schedule has custom conditions and actions",
|
||||
"unlinkFromSchedule": "Unlink from schedule",
|
||||
"upcomingDates": "Upcoming dates"
|
||||
"unlinkSchedule": "Unlink Schedule",
|
||||
"upcomingDates": "Upcoming dates",
|
||||
"view_one": "View schedule"
|
||||
},
|
||||
"status": {
|
||||
"completed": "completed",
|
||||
|
@ -66,5 +156,8 @@
|
|||
},
|
||||
"support": {
|
||||
"anErrorOccuredWhileSaving": "An error occurred while saving. Please contact {{email}} for support."
|
||||
},
|
||||
"transaction": {
|
||||
"accountIsRequired": "Account is a required field"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,58 +2,149 @@
|
|||
"account": {
|
||||
"addAccount": "Agregar cuenta",
|
||||
"addAccountInFutureFromSidebar": "En el futuro puedes agregar cuentas desde la barra lateral.",
|
||||
"allAccounts": "Todas las cuentas",
|
||||
"allReconciled": "¡Todo conciliado!",
|
||||
"budgetedAccount_other": "Cuentas presupuestadas",
|
||||
"cleared": "Aclarada",
|
||||
"clearedBalance": "Su saldo compensado <strong>{{cleared}}</strong> necesita <strong>{{diff}}</strong> para coincidir con el saldo del banco de <strong>{{balance}}</strong>",
|
||||
"clearedTotal": "Total aclarado: <strong>{{amount}}</strong>",
|
||||
"closeAccount": "Cerrar cuenta",
|
||||
"closedNamed": "Cerrada: {{name}}",
|
||||
"collapseSplitTransaction_other": "Colapsar transacciones divididas",
|
||||
"doneReconciling": "Conciliación finalizada",
|
||||
"needAccountMessage": "Para que Actual sea útil, debe <strong>agregar una cuenta</strong>. Puedes vincular la cuenta para descargar transacciones automáticamente o administrala localmente."
|
||||
"enterCurrentBalanceToReconcileAdvice": "Ingrese el saldo actual de su cuenta que desea conciliar:",
|
||||
"expandSplitTransaction_other": "Expandir transacciones divididas",
|
||||
"hideRunningBalance": "Ocultar saldo actualizado",
|
||||
"linkAccount": "Vincular cuenta",
|
||||
"needAccountMessage": "Para que Actual sea útil, debe <strong>agregar una cuenta</strong>. Puedes vincular la cuenta para descargar transacciones automáticamente o administrala localmente.",
|
||||
"offBudgetAccount_other": "Cuentas sin presupuestar",
|
||||
"reconcile": "Conciliar",
|
||||
"reopenAccount": "Re abrir cuenta",
|
||||
"selectedBalance": "Balance seleccionado: <strong>{{amount}}</strong>",
|
||||
"selectedTransaction_other": "Transacciones seleccionadas",
|
||||
"showRunningBalance": "Mostrar saldo actualizado",
|
||||
"uncategorized": "Sin categorizar",
|
||||
"unclearedTotal": "Total sin aclarar: <strong>{{amount}}</strong>",
|
||||
"unlinkAccount": "Desvincular cuenta"
|
||||
},
|
||||
"bootstrap": {
|
||||
"changeServerPassword": "Cambiar contraseña del servidor",
|
||||
"passwordCannotBeEmpty": "La contraseña no puede estar vacía",
|
||||
"passwordsDoNotMatch": "Las contraseñas no coinciden",
|
||||
"passwordSuccessfullyChanged": "La contraseña fue modificada exitosamente",
|
||||
"setPassword": "Establecer una contraseña para esta instancia de servidor",
|
||||
"thisWillChangeThePasswordAdvice": "Este proceso modificará la contraseña para el servidor. Todas las sesiones existentes permanecerán conectadas.",
|
||||
"title": "Bootstrap esta instancia de Actual",
|
||||
"tryDemo": "Probar Demo"
|
||||
"tryDemo": "Probar Demo",
|
||||
"unableToContactTheServer": "Incapaz de conectarse con el servidor",
|
||||
"unknownError": "¡Vaya, ocurrió un error de nuestro lado! Intentaremos solucionarlo pronto."
|
||||
},
|
||||
"budget": {
|
||||
"chooseNumberMonths": "Seleccióne el numero de meses para mostrar a la vez"
|
||||
},
|
||||
"general": {
|
||||
"account_one": "Cuenta",
|
||||
"account_other": "Cuentas",
|
||||
"add": "Agregar",
|
||||
"addNew": "Agregar nuevo",
|
||||
"addSplit": "Agregar división",
|
||||
"amount": "Monto",
|
||||
"amountIinflow": "Monto (Entrada)",
|
||||
"amountLeft": "Monto restante: <strong>{{amount}}</strong>",
|
||||
"amountOutflow": "Monto (Salida)",
|
||||
"and": "y",
|
||||
"apply": "Aplicar",
|
||||
"approximatelyWithAmount": "Aproximadamente {{amount}}",
|
||||
"balance_one": "Balance",
|
||||
"cancel": "Cancelar",
|
||||
"categorize": "Categorizar",
|
||||
"category_one": "Categoría",
|
||||
"category_other": "Categorías",
|
||||
"complete": "Completar",
|
||||
"date": "Fecha",
|
||||
"delete": "Borrar",
|
||||
"payee": "Beneficiario",
|
||||
"deposit_one": "Depósito",
|
||||
"doNothing": "No hacer nada",
|
||||
"editField": "Editar campo",
|
||||
"export": "Exportar",
|
||||
"exportTransaction_other": "Exportar transacciones",
|
||||
"financialFile_other": "Archivos financieros",
|
||||
"import": "Importar",
|
||||
"importedPayee": "Beneficiario importado",
|
||||
"invalidDateFormat": "Formato de fecha inválido",
|
||||
"month": "Mes",
|
||||
"note_other": "Notas",
|
||||
"noTransaction_other": "Sin transacciones",
|
||||
"offBudget": "Fuera de presupuesto",
|
||||
"payee_one": "Beneficiario",
|
||||
"payee_other": "Beneficiarios",
|
||||
"payment_one": "Pago",
|
||||
"recurring": "Periódico",
|
||||
"repeats": "Repetir",
|
||||
"restart": "Reiniciar",
|
||||
"rule_one": "Regla",
|
||||
"save": "Guardar",
|
||||
"schedule": "Agenda",
|
||||
"schedule_other": "Agendas",
|
||||
"status": "Estado"
|
||||
"search": "Buscar",
|
||||
"show": "Mostrar",
|
||||
"split": "Dividir",
|
||||
"status": "Estado",
|
||||
"sync": "Sincronizar",
|
||||
"transfer": "Transferencia",
|
||||
"when": "Cuando",
|
||||
"year": "Año"
|
||||
},
|
||||
"rules": {
|
||||
"addAction": "Agregar acción",
|
||||
"applyAction": "Aplicar acción ({{size}})",
|
||||
"ifAllTheseConditionsMatch": "Si todas estas condiciones coinciden:",
|
||||
"stageOfRule": "Etapa de la regla:",
|
||||
"stageOfRuleAdvice": "La etapa de una regla le permite forzar un orden específico. Reglas previas siempre se ejecuta primero y las reglas posteriores siempre se ejecutan al final. Dentro de cada etapa las reglas se ordenan automáticamente de menos a más específicas.",
|
||||
"stages": {
|
||||
"default": "Por defecto",
|
||||
"post": "Posterior",
|
||||
"pre": "Previa"
|
||||
},
|
||||
"thenApplyTheseActions": "Aplíque éstas acciones:",
|
||||
"thisRuleAppliesToTheseTransactions": "Ésta regla aplica a las siguientes transacciones:"
|
||||
},
|
||||
"schedules": {
|
||||
"addNewSchedule": "Agregar nuevo agenda",
|
||||
"addNewSchedule": "Agregar nueva agenda",
|
||||
"automaticallyAddTransaction": "Agregar transacción automáticamente",
|
||||
"automaticallyAddTransactionAdvice": "Si se selecciona, la agenda creará automáticamente una transacción para la cuenta especificada",
|
||||
"createSchedule_one": "Crear agenda",
|
||||
"createSchedule_many": "Crear agendas",
|
||||
"createSchedule_other": "Crear agendas",
|
||||
"doFromFindSchedules": "Puedes hacerlo después desde el ítem de menú \"Buscar agendas\" en la barra lateral",
|
||||
"doFromToolsFindSchedules": "Puedes hacerlo después desde el ítem de menú \"Herramientas > Buscar agendas\"",
|
||||
"editAsRule": "Editar como regla",
|
||||
"expectedSchedulesAdvice": "Si estabas esperando encontrar alguna agenda y no se visualiza, puede deberse a que los beneficiarios de las transacciones no coincidan. Asegurate de cambiar el nombre los beneficiarios en todas las transacciones de una agenda para que sean el mismo.",
|
||||
"findMatchingTransactions": "Encontrar transacciones que coincidan",
|
||||
"foundSchedules": "Agendas encontradas",
|
||||
"foundSomePossibleSchedulesAdvice": "Encontramos posibles agendas en la transacción actual. Selecciona las que desea crear.",
|
||||
"isApproximately": "es aproximadamente",
|
||||
"isBetween": "está entre",
|
||||
"isExactly": "es exactamente",
|
||||
"linkedTransactions": "Transacciones vinculadas",
|
||||
"linkSchedule": "Vincular agenda",
|
||||
"linkToSchedule": "Vincular a agenda",
|
||||
"nextDate": "Próxima fecha",
|
||||
"none": "(ninguno)",
|
||||
"noSchedules": "Sin agendas",
|
||||
"noSchedulesFound": "No se encontraron agendas",
|
||||
"postTransaction": "Publicar transacción",
|
||||
"scheduleNamed": "Agenda: {{name}}",
|
||||
"selectTransactionsToLinkOnSave": "Seleccionar transacciones para vincular al guardar",
|
||||
"showCompletedSchedules": "Mostrar agendas completadas",
|
||||
"skipNextDate": "Saltar próxima fecha",
|
||||
"skipScheduledDate": "Saltar próxima fecha agendada",
|
||||
"theseTransactionsMatchThisSchedule": "Éstas transacciones coinciden con la agenda",
|
||||
"thisScheduleHasCustomConditionsAndActions": "Ésta agenda tiene condiciones y acciones personalizadas",
|
||||
"unlinkFromSchedule": "Desvincular de la agenda",
|
||||
"upcomingDates": "Próximas fechas"
|
||||
"unlinkSchedule": "Desvincular agenda",
|
||||
"upcomingDates": "Próximas fechas",
|
||||
"view_one": "Ver agenda"
|
||||
},
|
||||
"status": {
|
||||
"completed": "completo",
|
||||
|
@ -66,5 +157,8 @@
|
|||
},
|
||||
"support": {
|
||||
"anErrorOccuredWhileSaving": "Ocurrió un error al guardar. Por favor, póngase en contacto con {{email}} para obtener asistencia."
|
||||
},
|
||||
"transaction": {
|
||||
"accountIsRequired": "Cuenta es un campo requerido"
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue