#199 Adding translation to schedules list (#219)

* #199 Adding translation to schedules list

* #199 Complete translation on EditSchedule Form

* #199 Translation for status badge

* #199 Minor changes, suggested by @j-f1

Co-authored-by: Tom French <15848336+TomAFrench@users.noreply.github.com>
This commit is contained in:
Manuel Eduardo Cánepa Cihuelo 2022-08-30 19:22:43 -03:00 committed by GitHub
parent 304a384b6c
commit e436c01430
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 195 additions and 49 deletions

View file

@ -27,6 +27,7 @@ import { usePageType } from '../Page';
import { Page } from '../Page';
import { OpSelect } from '../modals/EditRule';
import { AmountInput, BetweenAmountInput } from '../util/AmountInput';
import { useTranslation } from 'react-i18next';
function mergeFields(defaults, initial) {
let res = { ...defaults };
@ -92,6 +93,7 @@ export default function ScheduleDetails() {
});
let pageType = usePageType();
const { t } = useTranslation();
let [state, dispatch] = useReducer(
(state, action) => {
@ -365,8 +367,10 @@ export default function ScheduleDetails() {
if (res.error) {
dispatch({
type: 'form-error',
error:
'An error occurred while saving. Please contact help@actualbudget.com for support.'
// Note: email is outside of translation to be easily replace on future
error: t('support.anErrorOccuredWhileSaving', {
email: 'help@actualbudget.com'
})
});
} else {
if (adding) {
@ -423,15 +427,19 @@ export default function ScheduleDetails() {
return (
<Page
title={payee ? `Schedule: ${payee.name}` : 'Schedule'}
title={
payee
? t('schedules.scheduleNamed', { name: payee.name })
: t('general.schedule')
}
modalSize="medium"
>
<Stack direction="row" style={{ marginTop: 20 }}>
<FormField style={{ flex: 1 }}>
<FormLabel title="Payee" />
<FormLabel title={t('general.payee')} />
<PayeeAutocomplete
value={state.fields.payee}
inputProps={{ placeholder: '(none)' }}
inputProps={{ placeholder: t('schedules.none') }}
onSelect={id =>
dispatch({ type: 'set-field', field: 'payee', value: id })
}
@ -439,10 +447,10 @@ export default function ScheduleDetails() {
</FormField>
<FormField style={{ flex: 1 }}>
<FormLabel title="Account" />
<FormLabel title={t('general.account')} />
<AccountAutocomplete
value={state.fields.account}
inputProps={{ placeholder: '(none)' }}
inputProps={{ placeholder: t('schedules.none') }}
onSelect={id =>
dispatch({ type: 'set-field', field: 'account', value: id })
}
@ -451,18 +459,21 @@ export default function ScheduleDetails() {
<FormField style={{ flex: 1 }}>
<Stack direction="row" align="center" style={{ marginBottom: 3 }}>
<FormLabel title="Amount" style={{ margin: 0, flex: 1 }} />
<FormLabel
title={t('general.amount')}
style={{ margin: 0, flex: 1 }}
/>
<OpSelect
ops={['is', 'isapprox', 'isbetween']}
value={state.fields.amountOp}
formatOp={op => {
switch (op) {
case 'is':
return 'is exactly';
return t('schedules.isExactly');
case 'isapprox':
return 'is approximately';
return t('schedules.isApproximately');
case 'isbetween':
return 'is between';
return t('schedules.isBetween');
default:
throw new Error('Invalid op for select: ' + op);
}
@ -504,7 +515,7 @@ export default function ScheduleDetails() {
</Stack>
<View style={{ marginTop: 20 }}>
<FormLabel title="Date" />
<FormLabel title={t('general.date')} />
</View>
<Stack direction="row" align="flex-start">
@ -529,7 +540,7 @@ export default function ScheduleDetails() {
{state.upcomingDates && (
<View style={{ fontSize: 13, marginTop: 20 }}>
<Text style={{ color: colors.n4, fontWeight: 600 }}>
Upcoming dates
{t('schedules.upcomingDates')}
</Text>
<Stack
direction="column"
@ -561,7 +572,7 @@ export default function ScheduleDetails() {
}}
/>
<label for="form_repeats" style={{ userSelect: 'none' }}>
Repeats
{t('general.repeats')}
</label>
</View>
@ -592,7 +603,7 @@ export default function ScheduleDetails() {
}}
/>
<label for="form_posts_transaction" style={{ userSelect: 'none' }}>
Automatically add transaction
{t('schedules.automaticallyAddTransaction')}
</label>
</View>
@ -606,8 +617,7 @@ export default function ScheduleDetails() {
lineHeight: '1.4em'
}}
>
If checked, the schedule will automatically create transactions for
you in the specified account
{t('schedules.automaticallyAddTransactionAdvice')}
</Text>
{!adding && state.schedule.rule && (
@ -621,11 +631,11 @@ export default function ScheduleDetails() {
width: 350
}}
>
This schedule has custom conditions and actions
{t('schedules.thisScheduleHasCustomConditionsAndActions')}
</Text>
)}
<Button onClick={() => onEditRule()} disabled={adding}>
Edit as rule
{t('schedules.editAsRule')}
</Button>
</Stack>
)}
@ -637,11 +647,11 @@ export default function ScheduleDetails() {
{adding ? (
<View style={{ flexDirection: 'row', padding: '5px 0' }}>
<Text style={{ color: colors.n4 }}>
These transactions match this schedule:
{t('schedules.theseTransactionsMatchThisSchedule')}
</Text>
<View style={{ flex: 1 }} />
<Text style={{ color: colors.n6 }}>
Select transactions to link on save
{t('schedules.selectTransactionsToLinkOnSave')}
</Text>
</View>
) : (
@ -656,7 +666,7 @@ export default function ScheduleDetails() {
}}
onClick={() => onSwitchTransactions('linked')}
>
Linked transactions
{t('schedules.linkedTransactions')}
</Button>{' '}
<Button
bare
@ -669,15 +679,20 @@ export default function ScheduleDetails() {
}}
onClick={() => onSwitchTransactions('matched')}
>
Find matching transactions
{t('schedules.findMatchingTransactions')}
</Button>
<View style={{ flex: 1 }} />
<SelectedItemsButton
name="transactions"
items={
state.transactionsMode === 'linked'
? [{ name: 'unlink', text: 'Unlink from schedule' }]
: [{ name: 'link', text: 'Link to schedule' }]
? [
{
name: 'unlink',
text: t('schedules.unlinkFromSchedule')
}
]
: [{ name: 'link', text: t('schedules.linkToSchedule') }]
}
onSelect={(name, ids) => {
switch (name) {
@ -715,10 +730,10 @@ export default function ScheduleDetails() {
>
{state.error && <Text style={{ color: colors.r4 }}>{state.error}</Text>}
<Button style={{ marginRight: 10 }} onClick={() => history.goBack()}>
Cancel
{t('general.cancel')}
</Button>
<Button primary onClick={onSave}>
{adding ? 'Add' : 'Save'}
{adding ? t('general.add') : t('general.save')}
</Button>
</Stack>
</Page>

View file

@ -22,12 +22,15 @@ import DotsHorizontalTriple from 'loot-design/src/svg/v1/DotsHorizontalTriple';
import Check from 'loot-design/src/svg/v2/Check';
import DisplayId from '../util/DisplayId';
import { StatusBadge } from './StatusBadge';
import { useTranslation } from 'react-i18next';
export let ROW_HEIGHT = 43;
function OverflowMenu({ schedule, status, onAction }) {
let [open, setOpen] = useState(false);
const { t } = useTranslation();
return (
<View>
<Button
@ -58,15 +61,15 @@ function OverflowMenu({ schedule, status, onAction }) {
items={[
status === 'due' && {
name: 'post-transaction',
text: 'Post transaction'
text: t('schedules.postTransaction')
},
...(schedule.completed
? [{ name: 'restart', text: 'Restart' }]
? [{ name: 'restart', text: t('general.restart') }]
: [
{ name: 'skip', text: 'Skip next date' },
{ name: 'complete', text: 'Complete' }
{ name: 'skip', text: t('schedules.skipNextDate') },
{ name: 'complete', text: t('general.complete') }
]),
{ name: 'delete', text: 'Delete' }
{ name: 'delete', text: t('general.delete') }
]}
/>
</Tooltip>
@ -80,6 +83,8 @@ export function ScheduleAmountCell({ amount, op }) {
let str = integerToCurrency(Math.abs(num || 0));
let isApprox = op === 'isapprox' || op === 'isbetween';
const { t } = useTranslation();
return (
<Cell
width={100}
@ -99,7 +104,7 @@ export function ScheduleAmountCell({ amount, op }) {
lineHeight: '1em',
marginRight: 10
}}
title={(isApprox ? 'Approximately ' : '') + str}
title={t('general.approximatelyWithAmount', { amount: str })}
>
~
</View>
@ -112,7 +117,9 @@ export function ScheduleAmountCell({ amount, op }) {
overflow: 'hidden',
textOverflow: 'ellipsis'
}}
title={(isApprox ? 'Approximately ' : '') + str}
title={
isApprox ? t('general.approximatelyWithAmount', { amount: str }) : str
}
>
{num > 0 ? `+${str}` : `${str}`}
</Text>
@ -135,6 +142,8 @@ export function SchedulesTable({
let [showCompleted, setShowCompleted] = useState(false);
const { t } = useTranslation();
let items = useMemo(() => {
if (!allowCompleted) {
return schedules.filter(s => !s.completed);
@ -219,7 +228,7 @@ export function SchedulesTable({
color: colors.n6
}}
>
Show completed schedules
{t('schedules.showCompletedSchedules')}
</Field>
</Row>
);
@ -230,16 +239,16 @@ export function SchedulesTable({
return (
<>
<TableHeader height={ROW_HEIGHT} inset={15} version="v2">
<Field width="flex">Payee</Field>
<Field width="flex">Account</Field>
<Field width={110}>Next date</Field>
<Field width={120}>Status</Field>
<Field width="flex">{t('general.payee')}</Field>
<Field width="flex">{t('general.account')}</Field>
<Field width={110}>{t('schedules.nextDate')}</Field>
<Field width={120}>{t('general.status')}</Field>
<Field width={100} style={{ textAlign: 'right' }}>
Amount
{t('general.amount')}
</Field>
{!minimal && (
<Field width={80} style={{ textAlign: 'center' }}>
Recurring
{t('general.recurring')}
</Field>
)}
{!minimal && <Field width={40}></Field>}
@ -251,7 +260,7 @@ export function SchedulesTable({
style={[{ flex: 1, backgroundColor: 'transparent' }, style]}
items={items}
renderItem={renderItem}
renderEmpty="No schedules"
renderEmpty={t('schedules.noSchedules')}
allowPopupsEscape={items.length < 6}
/>
</>

View file

@ -9,54 +9,65 @@ import CalendarIcon from 'loot-design/src/svg/v2/Calendar';
import ValidationCheck from 'loot-design/src/svg/v2/ValidationCheck';
import FavoriteStar from 'loot-design/src/svg/v2/FavoriteStar';
import CheckCircle1 from 'loot-design/src/svg/v2/CheckCircle1';
import { useTranslation } from 'react-i18next';
export function getStatusProps(status) {
let color, backgroundColor, Icon;
let color, backgroundColor, Icon, title;
const { t } = useTranslation();
switch (status) {
case 'missed':
color = colors.r1;
backgroundColor = colors.r10;
Icon = EditSkull1;
title = t('status.missed');
break;
case 'due':
color = colors.y1;
backgroundColor = colors.y9;
Icon = AlertTriangle;
title = t('status.due');
break;
case 'upcoming':
color = colors.p1;
backgroundColor = colors.p10;
Icon = CalendarIcon;
title = t('status.upcoming');
break;
case 'paid':
color = colors.g2;
backgroundColor = colors.g10;
Icon = ValidationCheck;
title = t('status.paid');
break;
case 'completed':
color = colors.n4;
backgroundColor = colors.n11;
Icon = FavoriteStar;
title = t('status.completed');
break;
case 'pending':
color = colors.g4;
backgroundColor = colors.g11;
Icon = CalendarIcon;
title = t('status.pending');
break;
case 'scheduled':
color = colors.n1;
backgroundColor = colors.n11;
Icon = CalendarIcon;
title = t('status.scheduled');
break;
default:
color = colors.n1;
backgroundColor = colors.n11;
Icon = CheckCircle1;
title = status;
break;
}
return { color, backgroundColor, Icon };
return { title, color, backgroundColor, Icon };
}
export function StatusIcon({ status }) {
@ -66,7 +77,7 @@ export function StatusIcon({ status }) {
}
export function StatusBadge({ status, style }) {
let { color, backgroundColor, Icon } = getStatusProps(status);
let { title, color, backgroundColor, Icon } = getStatusProps(status);
return (
<View
style={[
@ -90,7 +101,7 @@ export function StatusBadge({ status, style }) {
marginRight: 7
}}
/>
<Text style={{ lineHeight: '1em' }}>{titleFirst(status)}</Text>
<Text style={{ lineHeight: '1em' }}>{titleFirst(title)}</Text>
</View>
);
}

View file

@ -5,12 +5,15 @@ import { send } from 'loot-core/src/platform/client/fetch';
import { useSchedules } from 'loot-core/src/client/data-hooks/schedules';
import { Page } from '../Page';
import { SchedulesTable, ROW_HEIGHT } from './SchedulesTable';
import { useTranslation } from 'react-i18next';
export default function Schedules() {
let history = useHistory();
let scheduleData = useSchedules();
const { t } = useTranslation();
if (scheduleData == null) {
return null;
}
@ -50,7 +53,7 @@ export default function Schedules() {
}
return (
<Page title="Schedules">
<Page title={t('general.schedules')}>
<View
style={{
marginTop: 20,
@ -70,7 +73,7 @@ export default function Schedules() {
<View style={{ alignItems: 'flex-end', margin: '20px 0', flexShrink: 0 }}>
<Button primary onClick={onAdd}>
Add new schedule
{t('schedules.addNewSchedule')}
</Button>
</View>
</Page>

View file

@ -8,8 +8,62 @@
"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."
},
"bootstrap": {
"title": "Bootstrap this Actual instance",
"setPassword": "Set a password for this server instance",
"title": "Bootstrap this Actual instance",
"tryDemo": "Try Demo"
},
"general": {
"account": "Account",
"add": "Add",
"amount": "Amount",
"approximatelyWithAmount": "Approximately {{amount}}",
"cancel": "Cancel",
"complete": "Complete",
"date": "Date",
"delete": "Delete",
"payee": "Payee",
"recurring": "Recurring",
"repeats": "Repeats",
"restart": "Restart",
"save": "Save",
"schedule": "Schedule",
"schedules": "Schedules",
"status": "Status"
},
"schedules": {
"addNewSchedule": "Add new schedule",
"automaticallyAddTransaction": "Automatically add transaction",
"automaticallyAddTransactionAdvice": "If checked, the schedule will automatically create transactions for you in the specified account",
"editAsRule": "Edit as rule",
"findMatchingTransactions": "Find matching transactions",
"isApproximately": "is approximately",
"isBetween": "is between",
"isExactly": "is exactly",
"linkedTransactions": "Linked transactions",
"linkToSchedule": "Link to schedule",
"nextDate": "Next date",
"none": "(none)",
"noSchedules": "No schedules",
"postTransaction": "Post transaction",
"scheduleNamed": "Schedule: {{name}}",
"selectTransactionsToLinkOnSave": "Select transactions to link on save",
"showCompletedSchedules": "Show completed schedules",
"skipNextDate": "Skip next date",
"theseTransactionsMatchThisSchedule": "These transactions match this schedule:",
"thisScheduleHasCustomConditionsAndActions": "This schedule has custom conditions and actions",
"unlinkFromSchedule": "Unlink from schedule",
"upcomingDates": "Upcoming dates"
},
"status": {
"completed": "completed",
"due": "due",
"missed": "missed",
"paid": "paid",
"pending": "pending",
"scheduled": "scheduled",
"upcoming": "upcoming"
},
"support": {
"anErrorOccuredWhileSaving": "An error occurred while saving. Please contact {{email}} for support."
}
}

View file

@ -11,5 +11,59 @@
"setPassword": "Establecer una contraseña para esta instancia de servidor",
"title": "Bootstrap esta instancia de Actual",
"tryDemo": "Probar Demo"
},
"general": {
"account": "Cuenta",
"add": "Agregar",
"amount": "Monto",
"approximatelyWithAmount": "Aproximadamente {{amount}}",
"cancel": "Cancelar",
"complete": "Completar",
"date": "Fecha",
"delete": "Borrar",
"payee": "Beneficiario",
"recurring": "Periódico",
"repeats": "Repetir",
"restart": "Reiniciar",
"save": "Guardar",
"schedule": "Agenda",
"schedules": "Agendas",
"status": "Estado"
},
"schedules": {
"addNewSchedule": "Agregar nuevo agenda",
"automaticallyAddTransaction": "Agregar transacción automáticamente",
"automaticallyAddTransactionAdvice": "Si se selecciona, la agenda creará automáticamente una transacción para la cuenta especificada",
"editAsRule": "Editar como regla",
"findMatchingTransactions": "Encontrar transacciones que coincidan",
"isApproximately": "es aproximadamente",
"isBetween": "está entre",
"isExactly": "es exactamente",
"linkedTransactions": "Transacciones vinculadas",
"linkToSchedule": "Vincular a agenda",
"nextDate": "Próxima fecha",
"none": "(ninguno)",
"noSchedules": "Sin agendas",
"postTransaction": "Publicar transacción",
"scheduleNamed": "Agenda: {{name}}",
"selectTransactionsToLinkOnSave": "Seleccionar transacciones para vincular al guardar",
"showCompletedSchedules": "Mostrar agendas completadas",
"skipNextDate": "Saltar próxima fecha",
"theseTransactionsMatchThisSchedule": "Éstas transacciones coinciden con la agenda",
"thisScheduleHasCustomConditionsAndActions": "Ésta agenda tiene condiciones y acciones personalizadas",
"unlinkFromSchedule": "Desvincular de la agenda",
"upcomingDates": "Próximas fechas"
},
"status": {
"completed": "completo",
"due": "adeudado",
"missed": "omitido",
"paid": "pago",
"pending": "pendiente",
"scheduled": "agendado",
"upcoming": "próximo"
},
"support": {
"anErrorOccuredWhileSaving": "Ocurrió un error al guardar. Por favor, póngase en contacto con {{email}} para obtener asistencia."
}
}