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.*
|
*kcab.*
|
||||||
public/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 }}>
|
<View style={{ color: colors.n3 }}>
|
||||||
<Text style={{ fontStyle: 'italic', textAlign: 'center' }}>
|
<Text style={{ fontStyle: 'italic', textAlign: 'center' }}>
|
||||||
<Trans
|
<Trans
|
||||||
|
i18nKey={'account.clearedBalance'}
|
||||||
values={{
|
values={{
|
||||||
cleared: format(cleared, 'financial'),
|
cleared: format(cleared, 'financial'),
|
||||||
diff:
|
diff:
|
||||||
|
@ -158,9 +159,7 @@ function ReconcilingMessage({ balanceQuery, targetBalance, onDone }) {
|
||||||
format(targetDiff, 'financial'),
|
format(targetDiff, 'financial'),
|
||||||
balance: format(targetBalance, 'financial')
|
balance: format(targetBalance, 'financial')
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
{'account.clearedBalance'}
|
|
||||||
</Trans>
|
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
@ -176,6 +175,7 @@ function ReconcilingMessage({ balanceQuery, targetBalance, onDone }) {
|
||||||
|
|
||||||
function ReconcileTooltip({ account, onReconcile, onClose }) {
|
function ReconcileTooltip({ account, onReconcile, onClose }) {
|
||||||
let balance = useSheetValue(queries.accountBalance(account));
|
let balance = useSheetValue(queries.accountBalance(account));
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
function onSubmit(e) {
|
function onSubmit(e) {
|
||||||
let input = e.target.elements[0];
|
let input = e.target.elements[0];
|
||||||
|
@ -187,10 +187,7 @@ function ReconcileTooltip({ account, onReconcile, onClose }) {
|
||||||
return (
|
return (
|
||||||
<Tooltip position="bottom-right" width={275} onClose={onClose}>
|
<Tooltip position="bottom-right" width={275} onClose={onClose}>
|
||||||
<View style={{ padding: '5px 8px' }}>
|
<View style={{ padding: '5px 8px' }}>
|
||||||
<Text>
|
<Text>{t('account.enterCurrentBalanceToReconcileAdvice')}</Text>
|
||||||
Enter the current balance of your bank account that you want to
|
|
||||||
reconcile with:
|
|
||||||
</Text>
|
|
||||||
<form onSubmit={onSubmit}>
|
<form onSubmit={onSubmit}>
|
||||||
{balance != null && (
|
{balance != null && (
|
||||||
<InitialFocus>
|
<InitialFocus>
|
||||||
|
@ -200,7 +197,7 @@ function ReconcileTooltip({ account, onReconcile, onClose }) {
|
||||||
/>
|
/>
|
||||||
</InitialFocus>
|
</InitialFocus>
|
||||||
)}
|
)}
|
||||||
<Button primary>Reconcile</Button>
|
<Button primary>{t('account.reconcile')}</Button>
|
||||||
</form>
|
</form>
|
||||||
</View>
|
</View>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
@ -243,6 +240,7 @@ function AccountMenu({
|
||||||
onMenuSelect
|
onMenuSelect
|
||||||
}) {
|
}) {
|
||||||
let [tooltip, setTooltip] = useState('default');
|
let [tooltip, setTooltip] = useState('default');
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return tooltip === 'reconcile' ? (
|
return tooltip === 'reconcile' ? (
|
||||||
<ReconcileTooltip
|
<ReconcileTooltip
|
||||||
|
@ -263,19 +261,21 @@ function AccountMenu({
|
||||||
items={[
|
items={[
|
||||||
canShowBalances && {
|
canShowBalances && {
|
||||||
name: 'toggle-balance',
|
name: 'toggle-balance',
|
||||||
text: (showBalances ? 'Hide' : 'Show') + ' Running Balance'
|
text: showBalances
|
||||||
|
? t('account.hideRunningBalance')
|
||||||
|
: t('account.showRunningBalance')
|
||||||
},
|
},
|
||||||
{ name: 'export', text: 'Export' },
|
{ name: 'export', text: t('general.export') },
|
||||||
{ name: 'reconcile', text: 'Reconcile' },
|
{ name: 'reconcile', text: t('account.reconcile') },
|
||||||
syncEnabled &&
|
syncEnabled &&
|
||||||
account &&
|
account &&
|
||||||
!account.closed &&
|
!account.closed &&
|
||||||
(canSync
|
(canSync
|
||||||
? { name: 'unlink', text: 'Unlink Account' }
|
? { name: 'unlink', text: t('account.unlinkAccount') }
|
||||||
: { name: 'link', text: 'Link Account' }),
|
: { name: 'link', text: t('account.linkAccount') }),
|
||||||
account.closed
|
account.closed
|
||||||
? { name: 'reopen', text: 'Reopen Account' }
|
? { name: 'reopen', text: t('account.reopenAccount') }
|
||||||
: { name: 'close', text: 'Close Account' }
|
: { name: 'close', text: t('account.closeAccount') }
|
||||||
].filter(x => x)}
|
].filter(x => x)}
|
||||||
/>
|
/>
|
||||||
</MenuTooltip>
|
</MenuTooltip>
|
||||||
|
@ -283,19 +283,30 @@ function AccountMenu({
|
||||||
}
|
}
|
||||||
|
|
||||||
function CategoryMenu({ onClose, onMenuSelect }) {
|
function CategoryMenu({ onClose, onMenuSelect }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuTooltip onClose={onClose}>
|
<MenuTooltip onClose={onClose}>
|
||||||
<Menu
|
<Menu
|
||||||
onMenuSelect={item => {
|
onMenuSelect={item => {
|
||||||
onMenuSelect(item);
|
onMenuSelect(item);
|
||||||
}}
|
}}
|
||||||
items={[{ name: 'export', text: 'Export' }]}
|
items={[{ name: 'export', text: t('general.export') }]}
|
||||||
/>
|
/>
|
||||||
</MenuTooltip>
|
</MenuTooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DetailedBalance({ name, balance }) {
|
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 (
|
return (
|
||||||
<Text
|
<Text
|
||||||
style={{
|
style={{
|
||||||
|
@ -306,8 +317,12 @@ function DetailedBalance({ name, balance }) {
|
||||||
color: colors.n5
|
color: colors.n5
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{name}{' '}
|
<Trans
|
||||||
<Text style={{ fontWeight: 600 }}>{format(balance, 'financial')}</Text>
|
i18nKey={balanceType[name] || name}
|
||||||
|
values={{
|
||||||
|
amount: format(balance, 'financial')
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -342,7 +357,8 @@ function SelectedBalance({ selectedItems }) {
|
||||||
if (balance == null) {
|
if (balance == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return <DetailedBalance name="Selected balance:" balance={balance} />;
|
|
||||||
|
return <DetailedBalance name="selected" balance={balance} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function MoreBalances({ balanceQuery }) {
|
function MoreBalances({ balanceQuery }) {
|
||||||
|
@ -357,8 +373,8 @@ function MoreBalances({ balanceQuery }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ flexDirection: 'row' }}>
|
<View style={{ flexDirection: 'row' }}>
|
||||||
<DetailedBalance name="Cleared total:" balance={cleared} />
|
<DetailedBalance name="cleared" balance={cleared} />
|
||||||
<DetailedBalance name="Uncleared total:" balance={uncleared} />
|
<DetailedBalance name="uncleared" balance={uncleared} />
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -458,6 +474,7 @@ function SelectedTransactionsButton({
|
||||||
}) {
|
}) {
|
||||||
let selectedItems = useSelectedItems();
|
let selectedItems = useSelectedItems();
|
||||||
let history = useHistory();
|
let history = useHistory();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
let types = useMemo(() => {
|
let types = useMemo(() => {
|
||||||
let items = [...selectedItems];
|
let items = [...selectedItems];
|
||||||
|
@ -498,37 +515,43 @@ function SelectedTransactionsButton({
|
||||||
items={[
|
items={[
|
||||||
...(!types.trans
|
...(!types.trans
|
||||||
? [
|
? [
|
||||||
{ name: 'view-schedule', text: 'View schedule' },
|
{ name: 'view-schedule', text: t('schedules.view_one') },
|
||||||
{ name: 'post-transaction', text: 'Post transaction' },
|
{
|
||||||
{ name: 'skip', text: 'Skip scheduled date' }
|
name: 'post-transaction',
|
||||||
|
text: t('schedules.postTransaction')
|
||||||
|
},
|
||||||
|
{ name: 'skip', text: t('schedules.skipScheduledDate') }
|
||||||
]
|
]
|
||||||
: [
|
: [
|
||||||
{ name: 'show', text: 'Show', key: 'F' },
|
{ name: 'show', text: t('general.show'), key: 'F' },
|
||||||
{ name: 'delete', text: 'Delete', key: 'D' },
|
{ name: 'delete', text: t('general.delete'), key: 'D' },
|
||||||
...(linked
|
...(linked
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
name: 'view-schedule',
|
name: 'view-schedule',
|
||||||
text: 'View schedule',
|
text: t('schedules.view_one'),
|
||||||
disabled: selectedItems.size > 1
|
disabled: selectedItems.size > 1
|
||||||
},
|
},
|
||||||
{ name: 'unlink-schedule', text: 'Unlink schedule' }
|
{
|
||||||
|
name: 'unlink-schedule',
|
||||||
|
text: t('schedules.unlinkSchedule')
|
||||||
|
}
|
||||||
]
|
]
|
||||||
: [
|
: [
|
||||||
{
|
{
|
||||||
name: 'link-schedule',
|
name: 'link-schedule',
|
||||||
text: 'Link schedule'
|
text: t('schedules.linkSchedule')
|
||||||
}
|
}
|
||||||
]),
|
]),
|
||||||
Menu.line,
|
Menu.line,
|
||||||
{ type: Menu.label, name: 'Edit field' },
|
{ type: Menu.label, name: t('general.editField') },
|
||||||
{ name: 'date', text: 'Date' },
|
{ name: 'date', text: t('general.date') },
|
||||||
{ name: 'account', text: 'Account', key: 'A' },
|
{ name: 'account', text: t('general.account_one'), key: 'A' },
|
||||||
{ name: 'payee', text: 'Payee', key: 'P' },
|
{ name: 'payee', text: t('general.payee_one'), key: 'P' },
|
||||||
{ name: 'notes', text: 'Notes', key: 'N' },
|
{ name: 'notes', text: t('general.note_other'), key: 'N' },
|
||||||
{ name: 'category', text: 'Category', key: 'C' },
|
{ name: 'category', text: t('general.category_one'), key: 'C' },
|
||||||
{ name: 'amount', text: 'Amount' },
|
{ name: 'amount', text: t('general.amount') },
|
||||||
{ name: 'cleared', text: 'Cleared', key: 'L' }
|
{ name: 'cleared', text: t('account.cleared'), key: 'L' }
|
||||||
])
|
])
|
||||||
]}
|
]}
|
||||||
onSelect={name => {
|
onSelect={name => {
|
||||||
|
@ -620,6 +643,7 @@ const AccountHeader = React.memo(
|
||||||
let [menuOpen, setMenuOpen] = useState(false);
|
let [menuOpen, setMenuOpen] = useState(false);
|
||||||
let searchInput = useRef(null);
|
let searchInput = useRef(null);
|
||||||
let splitsExpanded = useSplitsExpanded();
|
let splitsExpanded = useSplitsExpanded();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
let canSync = syncEnabled && account && account.account_id;
|
let canSync = syncEnabled && account && account.account_id;
|
||||||
if (!account) {
|
if (!account) {
|
||||||
|
@ -732,7 +756,7 @@ const AccountHeader = React.memo(
|
||||||
}
|
}
|
||||||
style={{ color: 'currentColor', marginRight: 4 }}
|
style={{ color: 'currentColor', marginRight: 4 }}
|
||||||
/>{' '}
|
/>{' '}
|
||||||
Sync
|
{t('general.sync')}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
@ -741,7 +765,7 @@ const AccountHeader = React.memo(
|
||||||
height={13}
|
height={13}
|
||||||
style={{ color: 'currentColor', marginRight: 4 }}
|
style={{ color: 'currentColor', marginRight: 4 }}
|
||||||
/>{' '}
|
/>{' '}
|
||||||
Import
|
{t('general.import')}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -753,7 +777,7 @@ const AccountHeader = React.memo(
|
||||||
height={10}
|
height={10}
|
||||||
style={{ color: 'inherit', marginRight: 3 }}
|
style={{ color: 'inherit', marginRight: 3 }}
|
||||||
/>{' '}
|
/>{' '}
|
||||||
Add New
|
{t('general.addNew')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<View>
|
<View>
|
||||||
|
@ -774,7 +798,7 @@ const AccountHeader = React.memo(
|
||||||
}
|
}
|
||||||
inputRef={searchInput}
|
inputRef={searchInput}
|
||||||
value={search}
|
value={search}
|
||||||
placeholder="Search"
|
placeholder={t('general.search')}
|
||||||
getStyle={focused => [
|
getStyle={focused => [
|
||||||
{
|
{
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
|
@ -812,8 +836,8 @@ const AccountHeader = React.memo(
|
||||||
onClick={onToggleSplits}
|
onClick={onToggleSplits}
|
||||||
title={
|
title={
|
||||||
splitsExpanded.state.mode === 'collapse'
|
splitsExpanded.state.mode === 'collapse'
|
||||||
? 'Collapse split transactions'
|
? t('account.collapseSplitTransaction_other')
|
||||||
: 'Expand split transactions'
|
: t('account.expandSplitTransaction_other')
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{splitsExpanded.state.mode === 'collapse' ? (
|
{splitsExpanded.state.mode === 'collapse' ? (
|
||||||
|
@ -1191,11 +1215,15 @@ class AccountInternal extends React.PureComponent {
|
||||||
onImport = async () => {
|
onImport = async () => {
|
||||||
const accountId = this.props.accountId;
|
const accountId = this.props.accountId;
|
||||||
const account = this.props.accounts.find(acct => acct.id === accountId);
|
const account = this.props.accounts.find(acct => acct.id === accountId);
|
||||||
|
const t = this.props.t;
|
||||||
|
|
||||||
if (account) {
|
if (account) {
|
||||||
const res = await window.Actual.openFileDialog({
|
const res = await window.Actual.openFileDialog({
|
||||||
filters: [
|
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 =
|
let normalizedName =
|
||||||
accountName && accountName.replace(/[()]/g, '').replace(/\s+/g, '-');
|
accountName && accountName.replace(/[()]/g, '').replace(/\s+/g, '-');
|
||||||
let filename = `${normalizedName || 'transactions'}.csv`;
|
let filename = `${normalizedName || 'transactions'}.csv`;
|
||||||
|
const t = this.props.t;
|
||||||
|
|
||||||
window.Actual.saveFile(
|
window.Actual.saveFile(
|
||||||
exportedTransactions,
|
exportedTransactions,
|
||||||
filename,
|
filename,
|
||||||
'Export Transactions'
|
t('general.exportTransaction_other')
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1338,21 +1367,24 @@ class AccountInternal extends React.PureComponent {
|
||||||
if (filterName) {
|
if (filterName) {
|
||||||
return filterName;
|
return filterName;
|
||||||
}
|
}
|
||||||
|
const t = this.props.t;
|
||||||
|
|
||||||
if (!account) {
|
if (!account) {
|
||||||
if (id === 'budgeted') {
|
if (id === 'budgeted') {
|
||||||
return 'Budgeted Accounts';
|
return t('account.budgetedAccount_other');
|
||||||
} else if (id === 'offbudget') {
|
} else if (id === 'offbudget') {
|
||||||
return 'Off Budget Accounts';
|
return t('account.offBudgetAccount_other');
|
||||||
} else if (id === 'uncategorized') {
|
} else if (id === 'uncategorized') {
|
||||||
return 'Uncategorized';
|
return t('account.uncategorized');
|
||||||
} else if (!id) {
|
} else if (!id) {
|
||||||
return 'All Accounts';
|
return t('account.allAccounts');
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (account.closed ? 'Closed: ' : '') + account.name;
|
return account.closed
|
||||||
|
? t('account.closedNamed', { name: account.name })
|
||||||
|
: account.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
getBalanceQuery(account, id) {
|
getBalanceQuery(account, id) {
|
||||||
|
@ -1391,8 +1423,10 @@ class AccountInternal extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
onShowTransactions = async ids => {
|
onShowTransactions = async ids => {
|
||||||
|
const t = this.props.t;
|
||||||
|
|
||||||
this.onApplyFilter({
|
this.onApplyFilter({
|
||||||
customName: 'Selected transactions',
|
customName: t('account.selectedTransaction_other'),
|
||||||
filter: { id: { $oneof: ids } }
|
filter: { id: { $oneof: ids } }
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -1574,7 +1608,8 @@ class AccountInternal extends React.PureComponent {
|
||||||
replaceModal,
|
replaceModal,
|
||||||
showExtraBalances,
|
showExtraBalances,
|
||||||
expandSplits,
|
expandSplits,
|
||||||
accountId
|
accountId,
|
||||||
|
t
|
||||||
} = this.props;
|
} = this.props;
|
||||||
let {
|
let {
|
||||||
transactions,
|
transactions,
|
||||||
|
@ -1709,7 +1744,7 @@ class AccountInternal extends React.PureComponent {
|
||||||
fontStyle: 'italic'
|
fontStyle: 'italic'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
No transactions
|
{t('general.noTransaction_other')}
|
||||||
</View>
|
</View>
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
|
@ -1739,10 +1774,13 @@ class AccountInternal extends React.PureComponent {
|
||||||
|
|
||||||
function AccountHack(props) {
|
function AccountHack(props) {
|
||||||
let { dispatch: splitsExpandedDispatch } = useSplitsExpanded();
|
let { dispatch: splitsExpandedDispatch } = useSplitsExpanded();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AccountInternal
|
<AccountInternal
|
||||||
{...props}
|
{...props}
|
||||||
splitsExpandedDispatch={splitsExpandedDispatch}
|
splitsExpandedDispatch={splitsExpandedDispatch}
|
||||||
|
t={t}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import React, { useState, useRef, useEffect, useReducer } from 'react';
|
import React, { useState, useRef, useEffect, useReducer } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -132,6 +133,8 @@ function ConfigureField({ field, op, value, dispatch, onApply }) {
|
||||||
ops = ['is'];
|
ops = ['is'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
position="bottom-left"
|
position="bottom-left"
|
||||||
|
@ -146,15 +149,15 @@ function ConfigureField({ field, op, value, dispatch, onApply }) {
|
||||||
options={
|
options={
|
||||||
field === 'amount'
|
field === 'amount'
|
||||||
? [
|
? [
|
||||||
['amount', 'Amount'],
|
['amount', t('general.amount')],
|
||||||
['amount-inflow', 'Amount (inflow)'],
|
['amount-inflow', t('general.amountIinflow')],
|
||||||
['amount-outflow', 'Amount (outflow)']
|
['amount-outflow', t('general.amountOutflow')]
|
||||||
]
|
]
|
||||||
: field === 'date'
|
: field === 'date'
|
||||||
? [
|
? [
|
||||||
['date', 'Date'],
|
['date', t('general.date')],
|
||||||
['month', 'Month'],
|
['month', t('general.month')],
|
||||||
['year', 'Year']
|
['year', t('general.year')]
|
||||||
]
|
]
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
|
@ -235,7 +238,7 @@ function ConfigureField({ field, op, value, dispatch, onApply }) {
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Apply
|
{t('general.apply')}
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
</form>
|
</form>
|
||||||
|
@ -251,6 +254,8 @@ export function FilterButton({ onApply }) {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
let [state, dispatch] = useReducer(
|
let [state, dispatch] = useReducer(
|
||||||
(state, action) => {
|
(state, action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
@ -306,7 +311,7 @@ export function FilterButton({ onApply }) {
|
||||||
if (isDateValid(date)) {
|
if (isDateValid(date)) {
|
||||||
cond.value = formatDate(date, 'yyyy-MM');
|
cond.value = formatDate(date, 'yyyy-MM');
|
||||||
} else {
|
} else {
|
||||||
alert('Invalid date format');
|
alert(t('general.invalidDateFormat'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else if (cond.options.year) {
|
} else if (cond.options.year) {
|
||||||
|
@ -314,7 +319,7 @@ export function FilterButton({ onApply }) {
|
||||||
if (isDateValid(date)) {
|
if (isDateValid(date)) {
|
||||||
cond.value = formatDate(date, 'yyyy');
|
cond.value = formatDate(date, 'yyyy');
|
||||||
} else {
|
} else {
|
||||||
alert('Invalid date format');
|
alert(t('general.invalidDateFormat'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import React, { useMemo, useCallback } from 'react';
|
import React, { useMemo, useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -181,6 +182,7 @@ export default function SimpleTransactionsTable({
|
||||||
},
|
},
|
||||||
[payees, categories, memoFields, selectedItems]
|
[payees, categories, memoFields, selectedItems]
|
||||||
);
|
);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table
|
<Table
|
||||||
|
@ -200,43 +202,43 @@ export default function SimpleTransactionsTable({
|
||||||
case 'date':
|
case 'date':
|
||||||
return (
|
return (
|
||||||
<Field key={i} width={100}>
|
<Field key={i} width={100}>
|
||||||
Date
|
{t('general.date')}
|
||||||
</Field>
|
</Field>
|
||||||
);
|
);
|
||||||
case 'imported_payee':
|
case 'imported_payee':
|
||||||
return (
|
return (
|
||||||
<Field key={i} width="flex">
|
<Field key={i} width="flex">
|
||||||
Imported payee
|
{t('general.importedPayee')}
|
||||||
</Field>
|
</Field>
|
||||||
);
|
);
|
||||||
case 'payee':
|
case 'payee':
|
||||||
return (
|
return (
|
||||||
<Field key={i} width="flex">
|
<Field key={i} width="flex">
|
||||||
Payee
|
{t('general.payee_one')}
|
||||||
</Field>
|
</Field>
|
||||||
);
|
);
|
||||||
case 'category':
|
case 'category':
|
||||||
return (
|
return (
|
||||||
<Field key={i} width="flex">
|
<Field key={i} width="flex">
|
||||||
Category
|
{t('general.category_one')}
|
||||||
</Field>
|
</Field>
|
||||||
);
|
);
|
||||||
case 'account':
|
case 'account':
|
||||||
return (
|
return (
|
||||||
<Field key={i} width="flex">
|
<Field key={i} width="flex">
|
||||||
Account
|
{t('general.account_one')}
|
||||||
</Field>
|
</Field>
|
||||||
);
|
);
|
||||||
case 'notes':
|
case 'notes':
|
||||||
return (
|
return (
|
||||||
<Field key={i} width="flex">
|
<Field key={i} width="flex">
|
||||||
Notes
|
{t('general.note_other')}
|
||||||
</Field>
|
</Field>
|
||||||
);
|
);
|
||||||
case 'amount':
|
case 'amount':
|
||||||
return (
|
return (
|
||||||
<Field key={i} width={75} style={{ textAlign: 'right' }}>
|
<Field key={i} width={75} style={{ textAlign: 'right' }}>
|
||||||
Amount
|
{t('general.amount')}
|
||||||
</Field>
|
</Field>
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -8,6 +8,7 @@ import React, {
|
||||||
useContext,
|
useContext,
|
||||||
useReducer
|
useReducer
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
import { useTranslation, Trans } from 'react-i18next';
|
||||||
import { useSelector, useDispatch } from 'react-redux';
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -253,6 +254,7 @@ export function SplitsExpandedProvider({ children, initialMode = 'expand' }) {
|
||||||
export const TransactionHeader = React.memo(
|
export const TransactionHeader = React.memo(
|
||||||
({ hasSelected, showAccount, showCategory, showBalance }) => {
|
({ hasSelected, showAccount, showCategory, showBalance }) => {
|
||||||
let dispatchSelected = useSelectedDispatch();
|
let dispatchSelected = useSelectedDispatch();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row
|
<Row
|
||||||
|
@ -271,14 +273,18 @@ export const TransactionHeader = React.memo(
|
||||||
width={20}
|
width={20}
|
||||||
onSelect={() => dispatchSelected({ type: 'select-all' })}
|
onSelect={() => dispatchSelected({ type: 'select-all' })}
|
||||||
/>
|
/>
|
||||||
<Cell value="Date" width={110} />
|
<Cell value={t('general.date')} width={110} />
|
||||||
{showAccount && <Cell value="Account" width="flex" />}
|
{showAccount && <Cell value={t('general.account_one')} width="flex" />}
|
||||||
<Cell value="Payee" width="flex" />
|
<Cell value={t('general.payee_other')} width="flex" />
|
||||||
<Cell value="Notes" width="flex" />
|
<Cell value={t('general.note_other')} width="flex" />
|
||||||
{showCategory && <Cell value="Category" width="flex" />}
|
{showCategory && (
|
||||||
<Cell value="Payment" width={80} textAlign="right" />
|
<Cell value={t('general.category_other')} width="flex" />
|
||||||
<Cell value="Deposit" width={80} textAlign="right" />
|
)}
|
||||||
{showBalance && <Cell value="Balance" width={85} textAlign="right" />}
|
<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} />
|
<Field width={21} truncate={false} />
|
||||||
<Cell value="" width={15 + styles.scrollbarWidth} />
|
<Cell value="" width={15 + styles.scrollbarWidth} />
|
||||||
</Row>
|
</Row>
|
||||||
|
@ -528,6 +534,7 @@ export const Transaction = React.memo(function Transaction(props) {
|
||||||
onCreatePayee,
|
onCreatePayee,
|
||||||
onToggleSplit
|
onToggleSplit
|
||||||
} = props;
|
} = props;
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
let dispatchSelected = useSelectedDispatch();
|
let dispatchSelected = useSelectedDispatch();
|
||||||
|
|
||||||
|
@ -896,7 +903,7 @@ export const Transaction = React.memo(function Transaction(props) {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Text style={{ fontStyle: 'italic', userSelect: 'none' }}>
|
<Text style={{ fontStyle: 'italic', userSelect: 'none' }}>
|
||||||
Split
|
{t('general.split')}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</CellButton>
|
</CellButton>
|
||||||
|
@ -910,11 +917,11 @@ export const Transaction = React.memo(function Transaction(props) {
|
||||||
onExpose={!isPreview && (name => onEdit(id, name))}
|
onExpose={!isPreview && (name => onEdit(id, name))}
|
||||||
value={
|
value={
|
||||||
isParent
|
isParent
|
||||||
? 'Split'
|
? t('general.split')
|
||||||
: isOffBudget
|
: isOffBudget
|
||||||
? 'Off Budget'
|
? t('general.offBudget')
|
||||||
: isBudgetTransfer
|
: isBudgetTransfer
|
||||||
? 'Transfer'
|
? t('general.transfer')
|
||||||
: ''
|
: ''
|
||||||
}
|
}
|
||||||
valueStyle={valueStyle}
|
valueStyle={valueStyle}
|
||||||
|
@ -936,7 +943,7 @@ export const Transaction = React.memo(function Transaction(props) {
|
||||||
'name'
|
'name'
|
||||||
)
|
)
|
||||||
: transaction.id
|
: transaction.id
|
||||||
? 'Categorize'
|
? t('general.categorize')
|
||||||
: ''
|
: ''
|
||||||
}
|
}
|
||||||
exposed={focusedField === 'category'}
|
exposed={focusedField === 'category'}
|
||||||
|
@ -1049,6 +1056,8 @@ export const Transaction = React.memo(function Transaction(props) {
|
||||||
});
|
});
|
||||||
|
|
||||||
export function TransactionError({ error, isDeposit, onAddSplit, style }) {
|
export function TransactionError({ error, isDeposit, onAddSplit, style }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
switch (error.type) {
|
switch (error.type) {
|
||||||
case 'SplitTransactionError':
|
case 'SplitTransactionError':
|
||||||
if (error.version === 1) {
|
if (error.version === 1) {
|
||||||
|
@ -1064,21 +1073,21 @@ export function TransactionError({ error, isDeposit, onAddSplit, style }) {
|
||||||
]}
|
]}
|
||||||
data-testid="transaction-error"
|
data-testid="transaction-error"
|
||||||
>
|
>
|
||||||
<Text>
|
<Trans
|
||||||
Amount left:{' '}
|
i18nKey={'general.amountLeft'}
|
||||||
<Text style={{ fontWeight: 500 }}>
|
values={{
|
||||||
{integerToCurrency(
|
amount: integerToCurrency(
|
||||||
isDeposit ? error.difference : -error.difference
|
isDeposit ? error.difference : -error.difference
|
||||||
)}
|
)
|
||||||
</Text>
|
}}
|
||||||
</Text>
|
/>
|
||||||
<View style={{ flex: 1 }} />
|
<View style={{ flex: 1 }} />
|
||||||
<Button
|
<Button
|
||||||
style={{ marginLeft: 15, padding: '4px 10px' }}
|
style={{ marginLeft: 15, padding: '4px 10px' }}
|
||||||
primary
|
primary
|
||||||
onClick={onAddSplit}
|
onClick={onAddSplit}
|
||||||
>
|
>
|
||||||
Add Split
|
{t('general.addSplit')}
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
@ -1135,6 +1144,7 @@ function NewTransaction({
|
||||||
}) {
|
}) {
|
||||||
const error = transactions[0].error;
|
const error = transactions[0].error;
|
||||||
const isDeposit = transactions[0].amount > 0;
|
const isDeposit = transactions[0].amount > 0;
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
|
@ -1192,7 +1202,7 @@ function NewTransaction({
|
||||||
onClick={() => onClose()}
|
onClick={() => onClose()}
|
||||||
data-testid="cancel-button"
|
data-testid="cancel-button"
|
||||||
>
|
>
|
||||||
Cancel
|
{t('general.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
{error ? (
|
{error ? (
|
||||||
<TransactionError
|
<TransactionError
|
||||||
|
@ -1207,7 +1217,7 @@ function NewTransaction({
|
||||||
onClick={onAdd}
|
onClick={onAdd}
|
||||||
data-testid="add-button"
|
data-testid="add-button"
|
||||||
>
|
>
|
||||||
Add
|
{t('general.add')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
@ -1532,12 +1542,14 @@ export let TransactionTable = React.forwardRef((props, ref) => {
|
||||||
setPrevIsAdding(props.isAdding);
|
setPrevIsAdding(props.isAdding);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (shouldAdd.current) {
|
if (shouldAdd.current) {
|
||||||
if (newTransactions[0].account == null) {
|
if (newTransactions[0].account == null) {
|
||||||
props.addNotification({
|
props.addNotification({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
message: 'Account is a required field'
|
message: t('transaction.accountIsRequired')
|
||||||
});
|
});
|
||||||
newNavigator.onEdit('temp', 'account');
|
newNavigator.onEdit('temp', 'account');
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { useBudgetMonthCount } from 'loot-design/src/components/budget/BudgetMonthCountContext';
|
import { useBudgetMonthCount } from 'loot-design/src/components/budget/BudgetMonthCountContext';
|
||||||
import { View } from 'loot-design/src/components/common';
|
import { View } from 'loot-design/src/components/common';
|
||||||
|
@ -16,6 +17,7 @@ function Calendar({ color, onClick }) {
|
||||||
|
|
||||||
export function MonthCountSelector({ maxMonths, onChange }) {
|
export function MonthCountSelector({ maxMonths, onChange }) {
|
||||||
let { displayMax } = useBudgetMonthCount();
|
let { displayMax } = useBudgetMonthCount();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
let style = { width: 15, height: 15, color: colors.n8 };
|
let style = { width: 15, height: 15, color: colors.n8 };
|
||||||
let activeStyle = { color: colors.n5 };
|
let activeStyle = { color: colors.n5 };
|
||||||
|
@ -50,7 +52,7 @@ export function MonthCountSelector({ maxMonths, onChange }) {
|
||||||
transform: 'scale(1.2)'
|
transform: 'scale(1.2)'
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
title="Choose the number of months shown at a time"
|
title={t('budget.chooseNumberMonths')}
|
||||||
>
|
>
|
||||||
{calendars}
|
{calendars}
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -24,13 +24,13 @@ export default function Bootstrap() {
|
||||||
function getErrorMessage(error) {
|
function getErrorMessage(error) {
|
||||||
switch (error) {
|
switch (error) {
|
||||||
case 'invalid-password':
|
case 'invalid-password':
|
||||||
return 'Password cannot be empty';
|
return t('bootstrap.passwordCannotBeEmpty');
|
||||||
case 'password-match':
|
case 'password-match':
|
||||||
return 'Passwords do not match';
|
return t('bootstrap.passwordsDoNotMatch');
|
||||||
case 'network-failure':
|
case 'network-failure':
|
||||||
return 'Unable to contact the server';
|
return t('bootstrap.unableToContactTheServer');
|
||||||
default:
|
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 React, { useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
|
@ -14,17 +15,18 @@ export default function ChangePassword() {
|
||||||
let history = useHistory();
|
let history = useHistory();
|
||||||
let [error, setError] = useState(null);
|
let [error, setError] = useState(null);
|
||||||
let [msg, setMessage] = useState(null);
|
let [msg, setMessage] = useState(null);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
function getErrorMessage(error) {
|
function getErrorMessage(error) {
|
||||||
switch (error) {
|
switch (error) {
|
||||||
case 'invalid-password':
|
case 'invalid-password':
|
||||||
return 'Password cannot be empty';
|
return t('bootstrap.passwordCannotBeEmpty');
|
||||||
case 'password-match':
|
case 'password-match':
|
||||||
return 'Passwords do not match';
|
return t('bootstrap.passwordsDoNotMatch');
|
||||||
case 'network-failure':
|
case 'network-failure':
|
||||||
return 'Unable to contact the server';
|
return t('bootstrap.unableToContactTheServer');
|
||||||
default:
|
default:
|
||||||
return 'Internal server error';
|
return t('bootstrap.unknownError');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +37,7 @@ export default function ChangePassword() {
|
||||||
if (error) {
|
if (error) {
|
||||||
setError(error);
|
setError(error);
|
||||||
} else {
|
} else {
|
||||||
setMessage('Password successfully changed');
|
setMessage(t('bootstrap.passwordSuccessfullyChanged'));
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
history.push('/');
|
history.push('/');
|
||||||
|
@ -46,7 +48,7 @@ export default function ChangePassword() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<View style={{ width: 500, marginTop: -30 }}>
|
<View style={{ width: 500, marginTop: -30 }}>
|
||||||
<Title text="Change server password" />
|
<Title text={t('bootstrap.changeServerPassword')} />
|
||||||
<Text
|
<Text
|
||||||
style={{
|
style={{
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
|
@ -54,8 +56,7 @@ export default function ChangePassword() {
|
||||||
lineHeight: 1.4
|
lineHeight: 1.4
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
This will change the password for this server instance. All existing
|
{t('bootstrap.thisWillChangeThePasswordAdvice')}
|
||||||
sessions will stay logged in.
|
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -280,7 +281,6 @@ function ActionEditor({ ops, action, editorStyle, onChange, onDelete, onAdd }) {
|
||||||
return (
|
return (
|
||||||
<Editor style={editorStyle} error={error}>
|
<Editor style={editorStyle} error={error}>
|
||||||
{/*<OpSelect ops={ops} value={op} onChange={onChange} />*/}
|
{/*<OpSelect ops={ops} value={op} onChange={onChange} />*/}
|
||||||
|
|
||||||
{op === 'set' ? (
|
{op === 'set' ? (
|
||||||
<>
|
<>
|
||||||
<View style={{ padding: '5px 10px', lineHeight: '1em' }}>
|
<View style={{ padding: '5px 10px', lineHeight: '1em' }}>
|
||||||
|
@ -325,6 +325,7 @@ function ActionEditor({ ops, action, editorStyle, onChange, onDelete, onAdd }) {
|
||||||
|
|
||||||
function StageInfo() {
|
function StageInfo() {
|
||||||
let [open, setOpen] = useState();
|
let [open, setOpen] = useState();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ position: 'relative', marginLeft: 5 }}>
|
<View style={{ position: 'relative', marginLeft: 5 }}>
|
||||||
|
@ -346,9 +347,7 @@ function StageInfo() {
|
||||||
lineHeight: 1.5
|
lineHeight: 1.5
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
The stage of a rule allows you to force a specific order. Pre rules
|
{t('rules.stageOfRuleAdvice')}
|
||||||
always run first, and post rules always run last. Within each stage
|
|
||||||
rules are automatically ordered from least to most specific.
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
@ -554,6 +553,7 @@ export default function EditRule({
|
||||||
let [transactions, setTransactions] = useState([]);
|
let [transactions, setTransactions] = useState([]);
|
||||||
let dispatch = useDispatch();
|
let dispatch = useDispatch();
|
||||||
let scrollableEl = useRef();
|
let scrollableEl = useRef();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(initiallyLoadPayees());
|
dispatch(initiallyLoadPayees());
|
||||||
|
@ -687,7 +687,7 @@ export default function EditRule({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title="Rule"
|
title={t('general.rule_one')}
|
||||||
padding={0}
|
padding={0}
|
||||||
{...modalProps}
|
{...modalProps}
|
||||||
style={[modalProps.style, { flex: 'inherit', maxWidth: '90%' }]}
|
style={[modalProps.style, { flex: 'inherit', maxWidth: '90%' }]}
|
||||||
|
@ -713,7 +713,7 @@ export default function EditRule({
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text style={{ color: colors.n4, marginRight: 15 }}>
|
<Text style={{ color: colors.n4, marginRight: 15 }}>
|
||||||
Stage of rule:
|
{t('rules.stageOfRule')}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Stack direction="row" align="center" spacing={1}>
|
<Stack direction="row" align="center" spacing={1}>
|
||||||
|
@ -721,19 +721,19 @@ export default function EditRule({
|
||||||
selected={stage === 'pre'}
|
selected={stage === 'pre'}
|
||||||
onSelect={() => onChangeStage('pre')}
|
onSelect={() => onChangeStage('pre')}
|
||||||
>
|
>
|
||||||
Pre
|
{t('rules.stages.pre')}
|
||||||
</StageButton>
|
</StageButton>
|
||||||
<StageButton
|
<StageButton
|
||||||
selected={stage === null}
|
selected={stage === null}
|
||||||
onSelect={() => onChangeStage(null)}
|
onSelect={() => onChangeStage(null)}
|
||||||
>
|
>
|
||||||
Default
|
{t('rules.stages.default')}
|
||||||
</StageButton>
|
</StageButton>
|
||||||
<StageButton
|
<StageButton
|
||||||
selected={stage === 'post'}
|
selected={stage === 'post'}
|
||||||
onSelect={() => onChangeStage('post')}
|
onSelect={() => onChangeStage('post')}
|
||||||
>
|
>
|
||||||
Post
|
{t('rules.stages.post')}
|
||||||
</StageButton>
|
</StageButton>
|
||||||
|
|
||||||
<StageInfo />
|
<StageInfo />
|
||||||
|
@ -752,7 +752,7 @@ export default function EditRule({
|
||||||
<View style={{ flexShrink: 0 }}>
|
<View style={{ flexShrink: 0 }}>
|
||||||
<View style={{ marginBottom: 30 }}>
|
<View style={{ marginBottom: 30 }}>
|
||||||
<Text style={{ color: colors.n4, marginBottom: 15 }}>
|
<Text style={{ color: colors.n4, marginBottom: 15 }}>
|
||||||
If all these conditions match:
|
{t('rules.ifAllTheseConditionsMatch')}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<ConditionsList
|
<ConditionsList
|
||||||
|
@ -764,7 +764,7 @@ export default function EditRule({
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<Text style={{ color: colors.n4, marginBottom: 15 }}>
|
<Text style={{ color: colors.n4, marginBottom: 15 }}>
|
||||||
Then apply these actions:
|
{t('rules.thenApplyTheseActions')}
|
||||||
</Text>
|
</Text>
|
||||||
<View style={{ flex: 1 }}>
|
<View style={{ flex: 1 }}>
|
||||||
{actions.length === 0 ? (
|
{actions.length === 0 ? (
|
||||||
|
@ -772,7 +772,7 @@ export default function EditRule({
|
||||||
style={{ alignSelf: 'flex-start' }}
|
style={{ alignSelf: 'flex-start' }}
|
||||||
onClick={addInitialAction}
|
onClick={addInitialAction}
|
||||||
>
|
>
|
||||||
Add action
|
{t('rules.addAction')}
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<Stack spacing={2}>
|
<Stack spacing={2}>
|
||||||
|
@ -807,7 +807,7 @@ export default function EditRule({
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text style={{ color: colors.n4, marginBottom: 0 }}>
|
<Text style={{ color: colors.n4, marginBottom: 0 }}>
|
||||||
This rule applies to these transactions:
|
{t('rules.thisRuleAppliesToTheseTransactions')}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<View style={{ flex: 1 }} />
|
<View style={{ flex: 1 }} />
|
||||||
|
@ -815,7 +815,7 @@ export default function EditRule({
|
||||||
disabled={selectedInst.items.size === 0}
|
disabled={selectedInst.items.size === 0}
|
||||||
onClick={onApply}
|
onClick={onApply}
|
||||||
>
|
>
|
||||||
Apply actions ({selectedInst.items.size})
|
{t('rules.applyAction', { size: selectedInst.items.size })}
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
@ -830,9 +830,11 @@ export default function EditRule({
|
||||||
justify="flex-end"
|
justify="flex-end"
|
||||||
style={{ marginTop: 20 }}
|
style={{ marginTop: 20 }}
|
||||||
>
|
>
|
||||||
<Button onClick={() => modalProps.onClose()}>Cancel</Button>
|
<Button onClick={() => modalProps.onClose()}>
|
||||||
|
{t('general.cancel')}
|
||||||
|
</Button>
|
||||||
<Button primary onClick={() => onSave()}>
|
<Button primary onClick={() => onSave()}>
|
||||||
Save
|
{t('general.save')}
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useLocation, useHistory } from 'react-router-dom';
|
import { useLocation, useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
import Platform from 'loot-core/src/client/platform';
|
import Platform from 'loot-core/src/client/platform';
|
||||||
|
@ -35,6 +36,7 @@ let ROW_HEIGHT = 43;
|
||||||
function DiscoverSchedulesTable({ schedules, loading }) {
|
function DiscoverSchedulesTable({ schedules, loading }) {
|
||||||
let selectedItems = useSelectedItems();
|
let selectedItems = useSelectedItems();
|
||||||
let dispatchSelected = useSelectedDispatch();
|
let dispatchSelected = useSelectedDispatch();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
function renderItem({ item }) {
|
function renderItem({ item }) {
|
||||||
let selected = selectedItems.has(item.id);
|
let selected = selectedItems.has(item.id);
|
||||||
|
@ -89,13 +91,13 @@ function DiscoverSchedulesTable({ schedules, loading }) {
|
||||||
selected={selectedItems.size > 0}
|
selected={selectedItems.size > 0}
|
||||||
onSelect={() => dispatchSelected({ type: 'select-all' })}
|
onSelect={() => dispatchSelected({ type: 'select-all' })}
|
||||||
/>
|
/>
|
||||||
<Field width="flex">Payee</Field>
|
<Field width="flex">{t('general.payee_one')}</Field>
|
||||||
<Field width="flex">Account</Field>
|
<Field width="flex">{t('general.account_one')}</Field>
|
||||||
<Field width="auto" style={{ flex: 1.5 }}>
|
<Field width="auto" style={{ flex: 1.5 }}>
|
||||||
When
|
{t('general.when')}
|
||||||
</Field>
|
</Field>
|
||||||
<Field width={100} style={{ textAlign: 'right' }}>
|
<Field width={100} style={{ textAlign: 'right' }}>
|
||||||
Amount
|
{t('general.amount')}
|
||||||
</Field>
|
</Field>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<Table
|
<Table
|
||||||
|
@ -107,7 +109,7 @@ function DiscoverSchedulesTable({ schedules, loading }) {
|
||||||
loading={loading}
|
loading={loading}
|
||||||
isSelected={id => selectedItems.has(id)}
|
isSelected={id => selectedItems.has(id)}
|
||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
renderEmpty="No schedules found"
|
renderEmpty={t('schedules.noSchedulesFound')}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
@ -161,24 +163,19 @@ export default function DiscoverSchedules() {
|
||||||
setCreating(false);
|
setCreating(false);
|
||||||
history.goBack();
|
history.goBack();
|
||||||
}
|
}
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
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>
|
<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
|
{Platform.isBrowser
|
||||||
? ' from the "Find schedules" item in the sidebar menu'
|
? t('schedules.doFromFindSchedules')
|
||||||
: ' from the "Tools > Find schedules" menu item'}
|
: t('schedules.doFromToolsFindSchedules')}
|
||||||
.
|
|
||||||
</P>
|
</P>
|
||||||
|
|
||||||
<SelectedProvider instance={selectedInst}>
|
<SelectedProvider instance={selectedInst}>
|
||||||
|
@ -194,14 +191,16 @@ export default function DiscoverSchedules() {
|
||||||
justify="flex-end"
|
justify="flex-end"
|
||||||
style={{ paddingTop: 20 }}
|
style={{ paddingTop: 20 }}
|
||||||
>
|
>
|
||||||
<Button onClick={() => history.goBack()}>Do nothing</Button>
|
<Button onClick={() => history.goBack()}>
|
||||||
|
{t('general.doNothing')}
|
||||||
|
</Button>
|
||||||
<ButtonWithLoading
|
<ButtonWithLoading
|
||||||
primary
|
primary
|
||||||
loading={creating}
|
loading={creating}
|
||||||
disabled={selectedInst.items.size === 0}
|
disabled={selectedInst.items.size === 0}
|
||||||
onClick={onCreate}
|
onClick={onCreate}
|
||||||
>
|
>
|
||||||
Create schedules
|
{t('schedules.createSchedule', { count: selectedInst.items.size })}
|
||||||
</ButtonWithLoading>
|
</ButtonWithLoading>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Page>
|
</Page>
|
||||||
|
|
|
@ -437,7 +437,7 @@ export default function ScheduleDetails() {
|
||||||
>
|
>
|
||||||
<Stack direction="row" style={{ marginTop: 20 }}>
|
<Stack direction="row" style={{ marginTop: 20 }}>
|
||||||
<FormField style={{ flex: 1 }}>
|
<FormField style={{ flex: 1 }}>
|
||||||
<FormLabel title={t('general.payee')} />
|
<FormLabel title={t('general.payee_one')} />
|
||||||
<PayeeAutocomplete
|
<PayeeAutocomplete
|
||||||
value={state.fields.payee}
|
value={state.fields.payee}
|
||||||
inputProps={{ placeholder: t('schedules.none') }}
|
inputProps={{ placeholder: t('schedules.none') }}
|
||||||
|
|
|
@ -241,7 +241,7 @@ export function SchedulesTable({
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TableHeader height={ROW_HEIGHT} inset={15} version="v2">
|
<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="flex">{t('general.account_one')}</Field>
|
||||||
<Field width={110}>{t('schedules.nextDate')}</Field>
|
<Field width={110}>{t('schedules.nextDate')}</Field>
|
||||||
<Field width={120}>{t('general.status')}</Field>
|
<Field width={120}>{t('general.status')}</Field>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
integerToCurrency,
|
integerToCurrency,
|
||||||
|
@ -52,6 +53,8 @@ export function BetweenAmountInput({ defaultValue, onChange }) {
|
||||||
let [num1, setNum1] = useState(defaultValue.num1);
|
let [num1, setNum1] = useState(defaultValue.num1);
|
||||||
let [num2, setNum2] = useState(defaultValue.num2);
|
let [num2, setNum2] = useState(defaultValue.num2);
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||||
<AmountInput
|
<AmountInput
|
||||||
|
@ -61,7 +64,7 @@ export function BetweenAmountInput({ defaultValue, onChange }) {
|
||||||
onChange({ num1: value, num2 });
|
onChange({ num1: value, num2 });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<View style={{ margin: '0 5px' }}>and</View>
|
<View style={{ margin: '0 5px' }}>{t('general.and')}</View>
|
||||||
<AmountInput
|
<AmountInput
|
||||||
defaultValue={num2}
|
defaultValue={num2}
|
||||||
onChange={value => {
|
onChange={value => {
|
||||||
|
|
|
@ -2,58 +2,148 @@
|
||||||
"account": {
|
"account": {
|
||||||
"addAccount": "Add account",
|
"addAccount": "Add account",
|
||||||
"addAccountInFutureFromSidebar": "In the future, you can add accounts from the sidebar.",
|
"addAccountInFutureFromSidebar": "In the future, you can add accounts from the sidebar.",
|
||||||
|
"allAccounts": "All Accounts",
|
||||||
"allReconciled": "All reconciled!",
|
"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>",
|
"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",
|
"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": {
|
"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",
|
"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",
|
"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": {
|
"general": {
|
||||||
"account_one": "Account",
|
"account_one": "Account",
|
||||||
"account_other": "Accounts",
|
"account_other": "Accounts",
|
||||||
"add": "Add",
|
"add": "Add",
|
||||||
|
"addNew": "Add new",
|
||||||
|
"addSplit": "Add Split",
|
||||||
"amount": "Amount",
|
"amount": "Amount",
|
||||||
|
"amountIinflow": "Amount (inflow)",
|
||||||
|
"amountLeft": "Amount left: <strong>{{amount}}</strong>",
|
||||||
|
"amountOutflow": "Amount (outflow)",
|
||||||
|
"and": "and",
|
||||||
|
"apply": "Apply",
|
||||||
"approximatelyWithAmount": "Approximately {{amount}}",
|
"approximatelyWithAmount": "Approximately {{amount}}",
|
||||||
|
"balance_one": "Balance",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
|
"categorize": "Categorize",
|
||||||
|
"category_one": "Category",
|
||||||
|
"category_other": "Categories",
|
||||||
"complete": "Complete",
|
"complete": "Complete",
|
||||||
"date": "Date",
|
"date": "Date",
|
||||||
"delete": "Delete",
|
"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",
|
"recurring": "Recurring",
|
||||||
"repeats": "Repeats",
|
"repeats": "Repeats",
|
||||||
"restart": "Restart",
|
"restart": "Restart",
|
||||||
|
"rule_one": "Rule",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
"schedule": "Schedule",
|
"schedule": "Schedule",
|
||||||
"schedule_other": "Schedules",
|
"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": {
|
"schedules": {
|
||||||
"addNewSchedule": "Add new schedule",
|
"addNewSchedule": "Add new schedule",
|
||||||
"automaticallyAddTransaction": "Automatically add transaction",
|
"automaticallyAddTransaction": "Automatically add transaction",
|
||||||
"automaticallyAddTransactionAdvice": "If checked, the schedule will automatically create transactions for you in the specified account",
|
"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",
|
"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",
|
"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",
|
"isApproximately": "is approximately",
|
||||||
"isBetween": "is between",
|
"isBetween": "is between",
|
||||||
"isExactly": "is exactly",
|
"isExactly": "is exactly",
|
||||||
"linkedTransactions": "Linked transactions",
|
"linkedTransactions": "Linked transactions",
|
||||||
|
"linkSchedule": "Link Schedule",
|
||||||
"linkToSchedule": "Link to schedule",
|
"linkToSchedule": "Link to schedule",
|
||||||
"nextDate": "Next date",
|
"nextDate": "Next date",
|
||||||
"none": "(none)",
|
"none": "(none)",
|
||||||
"noSchedules": "No schedules",
|
"noSchedules": "No schedules",
|
||||||
|
"noSchedulesFound": "No schedules found",
|
||||||
"postTransaction": "Post transaction",
|
"postTransaction": "Post transaction",
|
||||||
"scheduleNamed": "Schedule: {{name}}",
|
"scheduleNamed": "Schedule: {{name}}",
|
||||||
"selectTransactionsToLinkOnSave": "Select transactions to link on save",
|
"selectTransactionsToLinkOnSave": "Select transactions to link on save",
|
||||||
"showCompletedSchedules": "Show completed schedules",
|
"showCompletedSchedules": "Show completed schedules",
|
||||||
"skipNextDate": "Skip next date",
|
"skipNextDate": "Skip next date",
|
||||||
|
"skipScheduledDate": "Skip scheduled date",
|
||||||
"theseTransactionsMatchThisSchedule": "These transactions match this schedule:",
|
"theseTransactionsMatchThisSchedule": "These transactions match this schedule:",
|
||||||
"thisScheduleHasCustomConditionsAndActions": "This schedule has custom conditions and actions",
|
"thisScheduleHasCustomConditionsAndActions": "This schedule has custom conditions and actions",
|
||||||
"unlinkFromSchedule": "Unlink from schedule",
|
"unlinkFromSchedule": "Unlink from schedule",
|
||||||
"upcomingDates": "Upcoming dates"
|
"unlinkSchedule": "Unlink Schedule",
|
||||||
|
"upcomingDates": "Upcoming dates",
|
||||||
|
"view_one": "View schedule"
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"completed": "completed",
|
"completed": "completed",
|
||||||
|
@ -66,5 +156,8 @@
|
||||||
},
|
},
|
||||||
"support": {
|
"support": {
|
||||||
"anErrorOccuredWhileSaving": "An error occurred while saving. Please contact {{email}} for support."
|
"anErrorOccuredWhileSaving": "An error occurred while saving. Please contact {{email}} for support."
|
||||||
|
},
|
||||||
|
"transaction": {
|
||||||
|
"accountIsRequired": "Account is a required field"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,58 +2,149 @@
|
||||||
"account": {
|
"account": {
|
||||||
"addAccount": "Agregar cuenta",
|
"addAccount": "Agregar cuenta",
|
||||||
"addAccountInFutureFromSidebar": "En el futuro puedes agregar cuentas desde la barra lateral.",
|
"addAccountInFutureFromSidebar": "En el futuro puedes agregar cuentas desde la barra lateral.",
|
||||||
|
"allAccounts": "Todas las cuentas",
|
||||||
"allReconciled": "¡Todo conciliado!",
|
"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>",
|
"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",
|
"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": {
|
"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",
|
"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",
|
"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": {
|
"general": {
|
||||||
"account_one": "Cuenta",
|
"account_one": "Cuenta",
|
||||||
"account_other": "Cuentas",
|
"account_other": "Cuentas",
|
||||||
"add": "Agregar",
|
"add": "Agregar",
|
||||||
|
"addNew": "Agregar nuevo",
|
||||||
|
"addSplit": "Agregar división",
|
||||||
"amount": "Monto",
|
"amount": "Monto",
|
||||||
|
"amountIinflow": "Monto (Entrada)",
|
||||||
|
"amountLeft": "Monto restante: <strong>{{amount}}</strong>",
|
||||||
|
"amountOutflow": "Monto (Salida)",
|
||||||
|
"and": "y",
|
||||||
|
"apply": "Aplicar",
|
||||||
"approximatelyWithAmount": "Aproximadamente {{amount}}",
|
"approximatelyWithAmount": "Aproximadamente {{amount}}",
|
||||||
|
"balance_one": "Balance",
|
||||||
"cancel": "Cancelar",
|
"cancel": "Cancelar",
|
||||||
|
"categorize": "Categorizar",
|
||||||
|
"category_one": "Categoría",
|
||||||
|
"category_other": "Categorías",
|
||||||
"complete": "Completar",
|
"complete": "Completar",
|
||||||
"date": "Fecha",
|
"date": "Fecha",
|
||||||
"delete": "Borrar",
|
"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",
|
"recurring": "Periódico",
|
||||||
"repeats": "Repetir",
|
"repeats": "Repetir",
|
||||||
"restart": "Reiniciar",
|
"restart": "Reiniciar",
|
||||||
|
"rule_one": "Regla",
|
||||||
"save": "Guardar",
|
"save": "Guardar",
|
||||||
"schedule": "Agenda",
|
"schedule": "Agenda",
|
||||||
"schedule_other": "Agendas",
|
"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": {
|
"schedules": {
|
||||||
"addNewSchedule": "Agregar nuevo agenda",
|
"addNewSchedule": "Agregar nueva agenda",
|
||||||
"automaticallyAddTransaction": "Agregar transacción automáticamente",
|
"automaticallyAddTransaction": "Agregar transacción automáticamente",
|
||||||
"automaticallyAddTransactionAdvice": "Si se selecciona, la agenda creará automáticamente una transacción para la cuenta especificada",
|
"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",
|
"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",
|
"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",
|
"isApproximately": "es aproximadamente",
|
||||||
"isBetween": "está entre",
|
"isBetween": "está entre",
|
||||||
"isExactly": "es exactamente",
|
"isExactly": "es exactamente",
|
||||||
"linkedTransactions": "Transacciones vinculadas",
|
"linkedTransactions": "Transacciones vinculadas",
|
||||||
|
"linkSchedule": "Vincular agenda",
|
||||||
"linkToSchedule": "Vincular a agenda",
|
"linkToSchedule": "Vincular a agenda",
|
||||||
"nextDate": "Próxima fecha",
|
"nextDate": "Próxima fecha",
|
||||||
"none": "(ninguno)",
|
"none": "(ninguno)",
|
||||||
"noSchedules": "Sin agendas",
|
"noSchedules": "Sin agendas",
|
||||||
|
"noSchedulesFound": "No se encontraron agendas",
|
||||||
"postTransaction": "Publicar transacción",
|
"postTransaction": "Publicar transacción",
|
||||||
"scheduleNamed": "Agenda: {{name}}",
|
"scheduleNamed": "Agenda: {{name}}",
|
||||||
"selectTransactionsToLinkOnSave": "Seleccionar transacciones para vincular al guardar",
|
"selectTransactionsToLinkOnSave": "Seleccionar transacciones para vincular al guardar",
|
||||||
"showCompletedSchedules": "Mostrar agendas completadas",
|
"showCompletedSchedules": "Mostrar agendas completadas",
|
||||||
"skipNextDate": "Saltar próxima fecha",
|
"skipNextDate": "Saltar próxima fecha",
|
||||||
|
"skipScheduledDate": "Saltar próxima fecha agendada",
|
||||||
"theseTransactionsMatchThisSchedule": "Éstas transacciones coinciden con la agenda",
|
"theseTransactionsMatchThisSchedule": "Éstas transacciones coinciden con la agenda",
|
||||||
"thisScheduleHasCustomConditionsAndActions": "Ésta agenda tiene condiciones y acciones personalizadas",
|
"thisScheduleHasCustomConditionsAndActions": "Ésta agenda tiene condiciones y acciones personalizadas",
|
||||||
"unlinkFromSchedule": "Desvincular de la agenda",
|
"unlinkFromSchedule": "Desvincular de la agenda",
|
||||||
"upcomingDates": "Próximas fechas"
|
"unlinkSchedule": "Desvincular agenda",
|
||||||
|
"upcomingDates": "Próximas fechas",
|
||||||
|
"view_one": "Ver agenda"
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"completed": "completo",
|
"completed": "completo",
|
||||||
|
@ -66,5 +157,8 @@
|
||||||
},
|
},
|
||||||
"support": {
|
"support": {
|
||||||
"anErrorOccuredWhileSaving": "Ocurrió un error al guardar. Por favor, póngase en contacto con {{email}} para obtener asistencia."
|
"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