Generated by Spark: Make expenses view use translations

This commit is contained in:
2026-01-27 14:31:53 +00:00
committed by GitHub
parent ad48a9e38c
commit e62e39f791
4 changed files with 297 additions and 111 deletions
+78 -75
View File
@@ -26,6 +26,7 @@ import { toast } from 'sonner'
import { ExpenseDetailDialog } from '@/components/ExpenseDetailDialog'
import { AdvancedSearch, type FilterField } from '@/components/AdvancedSearch'
import { usePermissions } from '@/hooks/use-permissions'
import { useTranslation } from '@/hooks/use-translation'
import type { Expense, ExpenseStatus } from '@/lib/types'
interface ExpensesViewProps {
@@ -53,6 +54,7 @@ export function ExpensesView({
onApprove,
onReject
}: ExpensesViewProps) {
const { t } = useTranslation()
const { hasPermission } = usePermissions()
const [statusFilter, setStatusFilter] = useState<'all' | ExpenseStatus>('all')
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false)
@@ -106,33 +108,33 @@ export function ExpensesView({
})
const expenseFields: FilterField[] = [
{ name: 'workerName', label: 'Worker Name', type: 'text' },
{ name: 'clientName', label: 'Client Name', type: 'text' },
{ name: 'status', label: 'Status', type: 'select', options: [
{ value: 'pending', label: 'Pending' },
{ value: 'approved', label: 'Approved' },
{ value: 'rejected', label: 'Rejected' },
{ value: 'paid', label: 'Paid' }
{ name: 'workerName', label: t('expenses.workerName'), type: 'text' },
{ name: 'clientName', label: t('expenses.clientName'), type: 'text' },
{ name: 'status', label: t('common.status'), type: 'select', options: [
{ value: 'pending', label: t('expenses.status.pending') },
{ value: 'approved', label: t('expenses.status.approved') },
{ value: 'rejected', label: t('expenses.status.rejected') },
{ value: 'paid', label: t('expenses.status.paid') }
]},
{ name: 'category', label: 'Category', type: 'select', options: [
{ value: 'Travel', label: 'Travel' },
{ value: 'Accommodation', label: 'Accommodation' },
{ value: 'Meals', label: 'Meals' },
{ value: 'Equipment', label: 'Equipment' },
{ value: 'Training', label: 'Training' },
{ value: 'Other', label: 'Other' }
{ name: 'category', label: t('expenses.category'), type: 'select', options: [
{ value: 'Travel', label: t('expenses.categories.travel') },
{ value: 'Accommodation', label: t('expenses.categories.accommodation') },
{ value: 'Meals', label: t('expenses.categories.meals') },
{ value: 'Equipment', label: t('expenses.categories.equipment') },
{ value: 'Training', label: t('expenses.categories.training') },
{ value: 'Other', label: t('expenses.categories.other') }
]},
{ name: 'amount', label: 'Amount', type: 'number' },
{ name: 'date', label: 'Date', type: 'date' },
{ name: 'billable', label: 'Billable', type: 'select', options: [
{ value: 'true', label: 'Yes' },
{ value: 'false', label: 'No' }
{ name: 'amount', label: t('expenses.amount'), type: 'number' },
{ name: 'date', label: t('expenses.date'), type: 'date' },
{ name: 'billable', label: t('expenses.billable'), type: 'select', options: [
{ value: 'true', label: t('common.yes') },
{ value: 'false', label: t('common.no') }
]}
]
const handleSubmitCreate = () => {
if (!formData.workerName || !formData.clientName || !formData.date || !formData.category || !formData.amount) {
toast.error('Please fill in all required fields')
toast.error(t('expenses.createDialog.fillAllFields'))
return
}
@@ -161,45 +163,45 @@ export function ExpensesView({
return (
<Stack spacing={6}>
<PageHeader
title="Expense Management"
description="Manage worker expenses and reimbursements"
title={t('expenses.title')}
description={t('expenses.subtitle')}
actions={
hasPermission('expenses.create') && (
<Dialog open={isCreateDialogOpen} onOpenChange={setIsCreateDialogOpen}>
<DialogTrigger asChild>
<Button>
<Plus size={18} className="mr-2" />
Create Expense
{t('expenses.createExpense')}
</Button>
</DialogTrigger>
<DialogContent className="max-w-2xl">
<DialogHeader>
<DialogTitle>Create New Expense</DialogTitle>
<DialogTitle>{t('expenses.createDialog.title')}</DialogTitle>
<DialogDescription>
Enter expense details for worker reimbursement or client billing
{t('expenses.createDialog.description')}
</DialogDescription>
</DialogHeader>
<Grid cols={2} gap={4} className="py-4">
<div className="space-y-2">
<Label htmlFor="exp-worker">Worker Name</Label>
<Label htmlFor="exp-worker">{t('expenses.createDialog.workerNameLabel')}</Label>
<Input
id="exp-worker"
placeholder="Enter worker name"
placeholder={t('expenses.createDialog.workerNamePlaceholder')}
value={formData.workerName}
onChange={(e) => setFormData({ ...formData, workerName: e.target.value })}
/>
</div>
<div className="space-y-2">
<Label htmlFor="exp-client">Client Name</Label>
<Label htmlFor="exp-client">{t('expenses.createDialog.clientNameLabel')}</Label>
<Input
id="exp-client"
placeholder="Enter client name"
placeholder={t('expenses.createDialog.clientNamePlaceholder')}
value={formData.clientName}
onChange={(e) => setFormData({ ...formData, clientName: e.target.value })}
/>
</div>
<div className="space-y-2">
<Label htmlFor="exp-date">Expense Date</Label>
<Label htmlFor="exp-date">{t('expenses.createDialog.expenseDateLabel')}</Label>
<Input
id="exp-date"
type="date"
@@ -208,41 +210,41 @@ export function ExpensesView({
/>
</div>
<div className="space-y-2">
<Label htmlFor="exp-category">Category</Label>
<Label htmlFor="exp-category">{t('expenses.createDialog.categoryLabel')}</Label>
<Select
value={formData.category}
onValueChange={(value) => setFormData({ ...formData, category: value })}
>
<SelectTrigger id="exp-category">
<SelectValue placeholder="Select category" />
<SelectValue placeholder={t('expenses.createDialog.categoryPlaceholder')} />
</SelectTrigger>
<SelectContent>
<SelectItem value="Travel">Travel</SelectItem>
<SelectItem value="Accommodation">Accommodation</SelectItem>
<SelectItem value="Meals">Meals</SelectItem>
<SelectItem value="Equipment">Equipment</SelectItem>
<SelectItem value="Training">Training</SelectItem>
<SelectItem value="Other">Other</SelectItem>
<SelectItem value="Travel">{t('expenses.categories.travel')}</SelectItem>
<SelectItem value="Accommodation">{t('expenses.categories.accommodation')}</SelectItem>
<SelectItem value="Meals">{t('expenses.categories.meals')}</SelectItem>
<SelectItem value="Equipment">{t('expenses.categories.equipment')}</SelectItem>
<SelectItem value="Training">{t('expenses.categories.training')}</SelectItem>
<SelectItem value="Other">{t('expenses.categories.other')}</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2 col-span-2">
<Label htmlFor="exp-description">Description</Label>
<Label htmlFor="exp-description">{t('expenses.createDialog.descriptionLabel')}</Label>
<Textarea
id="exp-description"
placeholder="Describe the expense"
placeholder={t('expenses.createDialog.descriptionPlaceholder')}
value={formData.description}
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
rows={3}
/>
</div>
<div className="space-y-2">
<Label htmlFor="exp-amount">Amount (£)</Label>
<Label htmlFor="exp-amount">{t('expenses.createDialog.amountLabel')}</Label>
<Input
id="exp-amount"
type="number"
step="0.01"
placeholder="0.00"
placeholder={t('expenses.createDialog.amountPlaceholder')}
value={formData.amount}
onChange={(e) => setFormData({ ...formData, amount: e.target.value })}
/>
@@ -255,13 +257,13 @@ export function ExpensesView({
onChange={(e) => setFormData({ ...formData, billable: e.target.checked })}
className="w-4 h-4"
/>
<span className="text-sm">Billable to client</span>
<span className="text-sm">{t('expenses.createDialog.billableLabel')}</span>
</label>
</div>
</Grid>
<Stack direction="horizontal" spacing={2} justify="end">
<Button variant="outline" onClick={() => setIsCreateDialogOpen(false)}>Cancel</Button>
<Button onClick={handleSubmitCreate}>Create Expense</Button>
<Button variant="outline" onClick={() => setIsCreateDialogOpen(false)}>{t('expenses.createDialog.cancel')}</Button>
<Button onClick={handleSubmitCreate}>{t('expenses.createDialog.create')}</Button>
</Stack>
</DialogContent>
</Dialog>
@@ -273,29 +275,29 @@ export function ExpensesView({
items={expensesToFilter}
fields={expenseFields}
onResultsChange={handleResultsChange}
placeholder="Search expenses or use query language (e.g., category = Travel billable = true)"
placeholder={t('expenses.searchPlaceholder')}
/>
<Grid cols={4} gap={4}>
<MetricCard
label="Pending Approval"
label={t('expenses.pendingApproval')}
value={pendingExpenses.length}
description={`£${totalPendingAmount.toLocaleString()} total`}
description={t('expenses.totalPending', { amount: totalPendingAmount.toLocaleString() })}
icon={<ClockCounterClockwise size={24} />}
/>
<MetricCard
label="Approved"
label={t('expenses.approved')}
value={approvedExpenses.length}
description={`£${totalApprovedAmount.toLocaleString()} total`}
description={t('expenses.totalApproved', { amount: totalApprovedAmount.toLocaleString() })}
icon={<CheckCircle size={24} className="text-success" />}
/>
<MetricCard
label="Rejected"
label={t('expenses.rejected')}
value={expenses.filter(e => e.status === 'rejected').length}
icon={<XCircle size={24} className="text-destructive" />}
/>
<MetricCard
label="Paid"
label={t('expenses.paid')}
value={expenses.filter(e => e.status === 'paid').length}
icon={<CurrencyDollar size={24} />}
/>
@@ -310,32 +312,32 @@ export function ExpensesView({
</div>
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Status</SelectItem>
<SelectItem value="pending">Pending</SelectItem>
<SelectItem value="approved">Approved</SelectItem>
<SelectItem value="rejected">Rejected</SelectItem>
<SelectItem value="paid">Paid</SelectItem>
<SelectItem value="all">{t('expenses.status.all')}</SelectItem>
<SelectItem value="pending">{t('expenses.status.pending')}</SelectItem>
<SelectItem value="approved">{t('expenses.status.approved')}</SelectItem>
<SelectItem value="rejected">{t('expenses.status.rejected')}</SelectItem>
<SelectItem value="paid">{t('expenses.status.paid')}</SelectItem>
</SelectContent>
</Select>
<Button variant="outline">
<Download size={18} className="mr-2" />
Export
{t('expenses.export')}
</Button>
</Stack>
<Tabs defaultValue="pending" className="space-y-4">
<TabsList>
<TabsTrigger value="pending">
Pending ({expenses.filter(e => e.status === 'pending').length})
{t('expenses.tabs.pending')} ({expenses.filter(e => e.status === 'pending').length})
</TabsTrigger>
<TabsTrigger value="approved">
Approved ({expenses.filter(e => e.status === 'approved').length})
{t('expenses.tabs.approved')} ({expenses.filter(e => e.status === 'approved').length})
</TabsTrigger>
<TabsTrigger value="rejected">
Rejected ({expenses.filter(e => e.status === 'rejected').length})
{t('expenses.tabs.rejected')} ({expenses.filter(e => e.status === 'rejected').length})
</TabsTrigger>
<TabsTrigger value="paid">
Paid ({expenses.filter(e => e.status === 'paid').length})
{t('expenses.tabs.paid')} ({expenses.filter(e => e.status === 'paid').length})
</TabsTrigger>
</TabsList>
@@ -354,8 +356,8 @@ export function ExpensesView({
{filteredExpenses.filter(e => e.status === 'pending').length === 0 && (
<Card className="p-12 text-center">
<CheckCircle size={48} className="mx-auto text-muted-foreground mb-4" />
<h3 className="text-lg font-semibold mb-2">All caught up!</h3>
<p className="text-muted-foreground">No pending expenses to review</p>
<h3 className="text-lg font-semibold mb-2">{t('expenses.allCaughtUp')}</h3>
<p className="text-muted-foreground">{t('expenses.noPendingExpenses')}</p>
</Card>
)}
</TabsContent>
@@ -406,6 +408,7 @@ interface ExpenseCardProps {
}
function ExpenseCard({ expense, onApprove, onReject, onViewDetails }: ExpenseCardProps) {
const { t } = useTranslation()
const { hasPermission } = usePermissions()
const statusConfig = {
pending: { icon: ClockCounterClockwise, color: 'text-warning' },
@@ -431,31 +434,31 @@ function ExpenseCard({ expense, onApprove, onReject, onViewDetails }: ExpenseCar
<Stack direction="horizontal" spacing={3} align="center" className="mb-2">
<h3 className="font-semibold text-lg">{expense.workerName}</h3>
<Badge variant={expense.status === 'approved' || expense.status === 'paid' ? 'success' : expense.status === 'rejected' ? 'destructive' : 'warning'}>
{expense.status}
{t(`expenses.status.${expense.status}`)}
</Badge>
{expense.billable && (
<Badge variant="outline">Billable</Badge>
<Badge variant="outline">{t('expenses.billable')}</Badge>
)}
</Stack>
<Grid cols={5} gap={4} className="text-sm">
<div>
<p className="text-muted-foreground">Client</p>
<p className="text-muted-foreground">{t('expenses.card.client')}</p>
<p className="font-medium">{expense.clientName}</p>
</div>
<div>
<p className="text-muted-foreground">Category</p>
<p className="text-muted-foreground">{t('expenses.card.category')}</p>
<p className="font-medium">{expense.category}</p>
</div>
<div>
<p className="text-muted-foreground">Date</p>
<p className="text-muted-foreground">{t('expenses.card.date')}</p>
<p className="font-medium">{new Date(expense.date).toLocaleDateString()}</p>
</div>
<div>
<p className="text-muted-foreground">Amount</p>
<p className="text-muted-foreground">{t('expenses.card.amount')}</p>
<p className="font-semibold font-mono text-lg">£{expense.amount.toFixed(2)}</p>
</div>
<div>
<p className="text-muted-foreground">Currency</p>
<p className="text-muted-foreground">{t('expenses.card.currency')}</p>
<p className="font-medium font-mono">{expense.currency}</p>
</div>
</Grid>
@@ -465,7 +468,7 @@ function ExpenseCard({ expense, onApprove, onReject, onViewDetails }: ExpenseCar
</div>
)}
<div className="mt-2 text-sm text-muted-foreground">
Submitted {new Date(expense.submittedDate).toLocaleDateString()}
{t('expenses.card.submitted', { date: new Date(expense.submittedDate).toLocaleDateString() })}
</div>
</div>
</Stack>
@@ -480,7 +483,7 @@ function ExpenseCard({ expense, onApprove, onReject, onViewDetails }: ExpenseCar
style={{ backgroundColor: 'var(--success)', color: 'var(--success-foreground)' }}
>
<CheckCircle size={16} className="mr-2" />
Approve
{t('expenses.approve')}
</Button>
<Button
size="sm"
@@ -488,14 +491,14 @@ function ExpenseCard({ expense, onApprove, onReject, onViewDetails }: ExpenseCar
onClick={() => onReject(expense.id)}
>
<XCircle size={16} className="mr-2" />
Reject
{t('expenses.reject')}
</Button>
</>
)}
{expense.receiptUrl && (
<Button size="sm" variant="outline">
<Camera size={16} className="mr-2" />
View Receipt
{t('expenses.viewReceipt')}
</Button>
)}
</Stack>
+73 -12
View File
@@ -408,27 +408,88 @@
}
},
"expenses": {
"title": "Expenses",
"subtitle": "Manage expense claims",
"title": "Expense Management",
"subtitle": "Manage worker expenses and reimbursements",
"createExpense": "Create Expense",
"addExpense": "Add Expense",
"expenseType": "Expense Type",
"editExpense": "Edit Expense",
"approveExpense": "Approve Expense",
"rejectExpense": "Reject Expense",
"worker": "Worker",
"workerName": "Worker Name",
"client": "Client",
"clientName": "Client Name",
"category": "Category",
"description": "Description",
"amount": "Amount",
"expenseDate": "Expense Date",
"date": "Date",
"submittedDate": "Submitted",
"currency": "Currency",
"billable": "Billable",
"billableToClient": "Billable to client",
"receipt": "Receipt",
"viewReceipt": "View Receipt",
"status": {
"draft": "Draft",
"submitted": "Submitted",
"pending": "Pending",
"approved": "Approved",
"rejected": "Rejected",
"paid": "Paid",
"all": "All Status"
},
"categories": {
"travel": "Travel",
"accommodation": "Accommodation",
"meals": "Meals",
"equipment": "Equipment",
"training": "Training",
"other": "Other"
},
"pendingApproval": "Pending Approval",
"approved": "Approved",
"rejected": "Rejected",
"paid": "Paid",
"totalPending": "£{{amount}} total",
"totalApproved": "£{{amount}} total",
"allCaughtUp": "All caught up!",
"noPendingExpenses": "No pending expenses to review",
"export": "Export",
"approve": "Approve",
"reject": "Reject",
"createDialog": {
"title": "Create New Expense",
"description": "Enter expense details for worker reimbursement or client billing",
"workerNameLabel": "Worker Name",
"workerNamePlaceholder": "Enter worker name",
"clientNameLabel": "Client Name",
"clientNamePlaceholder": "Enter client name",
"expenseDateLabel": "Expense Date",
"categoryLabel": "Category",
"categoryPlaceholder": "Select category",
"descriptionLabel": "Description",
"descriptionPlaceholder": "Describe the expense",
"amountLabel": "Amount (£)",
"amountPlaceholder": "0.00",
"billableLabel": "Billable to client",
"cancel": "Cancel",
"create": "Create Expense",
"fillAllFields": "Please fill in all required fields"
},
"searchPlaceholder": "Search expenses or use query language (e.g., category = Travel billable = true)",
"tabs": {
"pending": "Pending",
"approved": "Approved",
"rejected": "Rejected",
"paid": "Paid"
},
"approveExpense": "Approve Expense",
"rejectExpense": "Reject Expense",
"mileage": "Mileage",
"accommodation": "Accommodation",
"meals": "Meals",
"travel": "Travel",
"other": "Other"
"card": {
"client": "Client",
"category": "Category",
"date": "Date",
"amount": "Amount",
"currency": "Currency",
"submitted": "Submitted {{date}}"
}
},
"reports": {
"title": "Reports",
+73 -12
View File
@@ -408,27 +408,88 @@
}
},
"expenses": {
"title": "Gastos",
"subtitle": "Gestionar reclamos de gastos",
"title": "Gestión de Gastos",
"subtitle": "Gestionar gastos y reembolsos de trabajadores",
"createExpense": "Crear Gasto",
"addExpense": "Agregar Gasto",
"expenseType": "Tipo de Gasto",
"editExpense": "Editar Gasto",
"approveExpense": "Aprobar Gasto",
"rejectExpense": "Rechazar Gasto",
"worker": "Trabajador",
"workerName": "Nombre del Trabajador",
"client": "Cliente",
"clientName": "Nombre del Cliente",
"category": "Categoría",
"description": "Descripción",
"amount": "Monto",
"expenseDate": "Fecha del Gasto",
"date": "Fecha",
"submittedDate": "Enviado",
"currency": "Moneda",
"billable": "Facturable",
"billableToClient": "Facturable al cliente",
"receipt": "Recibo",
"viewReceipt": "Ver Recibo",
"status": {
"draft": "Borrador",
"submitted": "Enviado",
"pending": "Pendiente",
"approved": "Aprobado",
"rejected": "Rechazado",
"paid": "Pagado",
"all": "Todos los Estados"
},
"categories": {
"travel": "Viaje",
"accommodation": "Alojamiento",
"meals": "Comidas",
"equipment": "Equipo",
"training": "Capacitación",
"other": "Otro"
},
"pendingApproval": "Pendiente de Aprobación",
"approved": "Aprobado",
"rejected": "Rechazado",
"paid": "Pagado",
"totalPending": "£{{amount}} en total",
"totalApproved": "£{{amount}} en total",
"allCaughtUp": "¡Todo al día!",
"noPendingExpenses": "No hay gastos pendientes de revisión",
"export": "Exportar",
"approve": "Aprobar",
"reject": "Rechazar",
"createDialog": {
"title": "Crear Nuevo Gasto",
"description": "Ingresar detalles del gasto para reembolso del trabajador o facturación del cliente",
"workerNameLabel": "Nombre del Trabajador",
"workerNamePlaceholder": "Ingresar nombre del trabajador",
"clientNameLabel": "Nombre del Cliente",
"clientNamePlaceholder": "Ingresar nombre del cliente",
"expenseDateLabel": "Fecha del Gasto",
"categoryLabel": "Categoría",
"categoryPlaceholder": "Seleccionar categoría",
"descriptionLabel": "Descripción",
"descriptionPlaceholder": "Describir el gasto",
"amountLabel": "Monto (£)",
"amountPlaceholder": "0.00",
"billableLabel": "Facturable al cliente",
"cancel": "Cancelar",
"create": "Crear Gasto",
"fillAllFields": "Por favor complete todos los campos obligatorios"
},
"searchPlaceholder": "Buscar gastos o usar lenguaje de consulta (ej., category = Travel billable = true)",
"tabs": {
"pending": "Pendiente",
"approved": "Aprobado",
"rejected": "Rechazado",
"paid": "Pagado"
},
"approveExpense": "Aprobar Gasto",
"rejectExpense": "Rechazar Gasto",
"mileage": "Kilometraje",
"accommodation": "Alojamiento",
"meals": "Comidas",
"travel": "Viaje",
"other": "Otro"
"card": {
"client": "Cliente",
"category": "Categoría",
"date": "Fecha",
"amount": "Monto",
"currency": "Moneda",
"submitted": "Enviado el {{date}}"
}
},
"reports": {
"title": "Informes",
+73 -12
View File
@@ -408,27 +408,88 @@
}
},
"expenses": {
"title": "Dépenses",
"subtitle": "Gérer les réclamations de dépenses",
"title": "Gestion des Dépenses",
"subtitle": "Gérer les dépenses et remboursements des travailleurs",
"createExpense": "Créer une Dépense",
"addExpense": "Ajouter une Dépense",
"expenseType": "Type de Dépense",
"editExpense": "Modifier une Dépense",
"approveExpense": "Approuver une Dépense",
"rejectExpense": "Rejeter une Dépense",
"worker": "Travailleur",
"workerName": "Nom du Travailleur",
"client": "Client",
"clientName": "Nom du Client",
"category": "Catégorie",
"description": "Description",
"amount": "Montant",
"expenseDate": "Date de la Dépense",
"date": "Date",
"submittedDate": "Soumis",
"currency": "Devise",
"billable": "Facturable",
"billableToClient": "Facturable au client",
"receipt": "Reçu",
"viewReceipt": "Voir le Reçu",
"status": {
"draft": "Brouillon",
"submitted": "Soumis",
"pending": "En Attente",
"approved": "Approuvé",
"rejected": "Rejeté",
"paid": "Payé",
"all": "Tous les Statuts"
},
"categories": {
"travel": "Voyage",
"accommodation": "Hébergement",
"meals": "Repas",
"equipment": "Équipement",
"training": "Formation",
"other": "Autre"
},
"pendingApproval": "En Attente d'Approbation",
"approved": "Approuvé",
"rejected": "Rejeté",
"paid": "Payé",
"totalPending": "£{{amount}} au total",
"totalApproved": "£{{amount}} au total",
"allCaughtUp": "Tout est à jour !",
"noPendingExpenses": "Aucune dépense en attente d'examen",
"export": "Exporter",
"approve": "Approuver",
"reject": "Rejeter",
"createDialog": {
"title": "Créer une Nouvelle Dépense",
"description": "Saisir les détails de la dépense pour le remboursement du travailleur ou la facturation du client",
"workerNameLabel": "Nom du Travailleur",
"workerNamePlaceholder": "Entrer le nom du travailleur",
"clientNameLabel": "Nom du Client",
"clientNamePlaceholder": "Entrer le nom du client",
"expenseDateLabel": "Date de la Dépense",
"categoryLabel": "Catégorie",
"categoryPlaceholder": "Sélectionner une catégorie",
"descriptionLabel": "Description",
"descriptionPlaceholder": "Décrire la dépense",
"amountLabel": "Montant (£)",
"amountPlaceholder": "0.00",
"billableLabel": "Facturable au client",
"cancel": "Annuler",
"create": "Créer une Dépense",
"fillAllFields": "Veuillez remplir tous les champs obligatoires"
},
"searchPlaceholder": "Rechercher des dépenses ou utiliser le langage de requête (par ex., category = Travel billable = true)",
"tabs": {
"pending": "En Attente",
"approved": "Approuvé",
"rejected": "Rejeté",
"paid": "Payé"
},
"approveExpense": "Approuver une Dépense",
"rejectExpense": "Rejeter une Dépense",
"mileage": "Kilométrage",
"accommodation": "Hébergement",
"meals": "Repas",
"travel": "Voyage",
"other": "Autre"
"card": {
"client": "Client",
"category": "Catégorie",
"date": "Date",
"amount": "Montant",
"currency": "Devise",
"submitted": "Soumis le {{date}}"
}
},
"reports": {
"title": "Rapports",