Generated by Spark: Make timesheets use translations

This commit is contained in:
2026-01-27 14:13:25 +00:00
committed by GitHub
parent e4dee19be7
commit ad5af12ae6
4 changed files with 236 additions and 75 deletions

View File

@@ -32,6 +32,7 @@ import { TimesheetTabs } from '@/components/timesheets/TimesheetTabs'
import { useTimeTracking } from '@/hooks/use-time-tracking'
import { useTimesheetsCrud } from '@/hooks/use-timesheets-crud'
import { usePermissions } from '@/hooks/use-permissions'
import { useTranslation } from '@/hooks/use-translation'
import { toast } from 'sonner'
import type { Timesheet, TimesheetStatus, ShiftEntry } from '@/lib/types'
@@ -46,6 +47,7 @@ export function TimesheetsView({
setSearchQuery,
onCreateInvoice
}: TimesheetsViewProps) {
const { t } = useTranslation()
const [statusFilter, setStatusFilter] = useState<'all' | TimesheetStatus>('all')
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false)
const [isBulkImportOpen, setIsBulkImportOpen] = useState(false)
@@ -90,13 +92,13 @@ export function TimesheetsView({
submittedDate: new Date().toISOString(),
shifts: []
})
toast.success('Timesheet created successfully')
toast.success(t('timesheets.createSuccess'))
setIsCreateDialogOpen(false)
} catch (error) {
toast.error('Failed to create timesheet')
toast.error(t('timesheets.createError'))
console.error('Error creating timesheet:', error)
}
}, [createTimesheet])
}, [createTimesheet, t])
const handleCreateDetailedTimesheet = useCallback(async (data: {
workerName: string
@@ -120,13 +122,13 @@ export function TimesheetsView({
submittedDate: new Date().toISOString(),
shifts: data.shifts
})
toast.success('Detailed timesheet created successfully')
toast.success(t('timesheets.createDetailedSuccess'))
setIsCreateDialogOpen(false)
} catch (error) {
toast.error('Failed to create detailed timesheet')
toast.error(t('timesheets.createDetailedError'))
console.error('Error creating detailed timesheet:', error)
}
}, [createTimesheet])
}, [createTimesheet, t])
const handleBulkImport = useCallback(async (csvData: string) => {
try {
@@ -156,17 +158,17 @@ export function TimesheetsView({
})
await bulkCreateTimesheets(timesheetsData)
toast.success(`${timesheetsData.length} timesheets imported successfully`)
toast.success(t('timesheets.importSuccess', { count: timesheetsData.length }))
setIsBulkImportOpen(false)
} catch (error) {
toast.error('Failed to import timesheets')
toast.error(t('timesheets.importError'))
console.error('Error importing timesheets:', error)
}
}, [bulkCreateTimesheets])
}, [bulkCreateTimesheets, t])
const handleApprove = useCallback(async (id: string) => {
if (!hasPermission('timesheets.approve')) {
toast.error('You do not have permission to approve timesheets')
toast.error(t('timesheets.noPermissionApprove'))
return
}
@@ -175,16 +177,16 @@ export function TimesheetsView({
status: 'approved',
approvedDate: new Date().toISOString()
})
toast.success('Timesheet approved')
toast.success(t('timesheets.approveSuccess'))
} catch (error) {
toast.error('Failed to approve timesheet')
toast.error(t('timesheets.approveError'))
console.error('Error approving timesheet:', error)
}
}, [updateTimesheet, hasPermission])
}, [updateTimesheet, hasPermission, t])
const handleReject = useCallback(async (id: string) => {
if (!hasPermission('timesheets.approve')) {
toast.error('You do not have permission to reject timesheets')
toast.error(t('timesheets.noPermissionReject'))
return
}
@@ -192,28 +194,28 @@ export function TimesheetsView({
await updateTimesheet(id, {
status: 'rejected'
})
toast.error('Timesheet rejected')
toast.error(t('timesheets.rejectSuccess'))
} catch (error) {
toast.error('Failed to reject timesheet')
toast.error(t('timesheets.rejectError'))
console.error('Error rejecting timesheet:', error)
}
}, [updateTimesheet, hasPermission])
}, [updateTimesheet, hasPermission, t])
const handleAdjust = useCallback(async (timesheetId: string, adjustment: any) => {
if (!hasPermission('timesheets.edit')) {
toast.error('You do not have permission to adjust timesheets')
toast.error(t('timesheets.noPermissionEdit'))
return
}
try {
await updateTimesheet(timesheetId, adjustment)
toast.success('Timesheet adjusted')
toast.success(t('timesheets.adjustSuccess'))
setSelectedTimesheet(null)
} catch (error) {
toast.error('Failed to adjust timesheet')
toast.error(t('timesheets.adjustError'))
console.error('Error adjusting timesheet:', error)
}
}, [updateTimesheet, hasPermission])
}, [updateTimesheet, hasPermission, t])
const handleTimeAndRateAdjustment = useCallback(async (adjustment: {
timesheetId: string
@@ -230,7 +232,7 @@ export function TimesheetsView({
notes?: string
}) => {
if (!hasPermission('timesheets.edit')) {
toast.error('You do not have permission to adjust timesheets')
toast.error(t('timesheets.noPermissionEdit'))
return
}
@@ -276,30 +278,30 @@ export function TimesheetsView({
await updateTimesheet(adjustment.timesheetId, updates)
if (adjustment.approvalRequired) {
toast.success('Adjustment submitted for approval')
toast.success(t('timesheets.adjustmentSubmitted'))
} else {
toast.success('Adjustment applied successfully')
toast.success(t('timesheets.adjustmentApplied'))
}
} catch (error) {
toast.error('Failed to apply adjustment')
toast.error(t('timesheets.adjustmentError'))
console.error('Error applying adjustment:', error)
}
}, [updateTimesheet, hasPermission, timesheets])
}, [updateTimesheet, hasPermission, timesheets, t])
const handleDelete = useCallback(async (id: string) => {
if (!hasPermission('timesheets.delete')) {
toast.error('You do not have permission to delete timesheets')
toast.error(t('timesheets.noPermissionDelete'))
return
}
try {
await deleteTimesheet(id)
toast.success('Timesheet deleted')
toast.success(t('timesheets.deleteSuccess'))
} catch (error) {
toast.error('Failed to delete timesheet')
toast.error(t('timesheets.deleteError'))
console.error('Error deleting timesheet:', error)
}
}, [deleteTimesheet, hasPermission])
}, [deleteTimesheet, hasPermission, t])
const timesheetsToFilter = useMemo(() => {
return timesheets.filter(t => {
@@ -348,16 +350,16 @@ export function TimesheetsView({
const [csvData, setCsvData] = useState('')
const timesheetFields: 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' }
{ name: 'workerName', label: t('timesheets.workerName'), type: 'text' },
{ name: 'clientName', label: t('timesheets.clientName'), type: 'text' },
{ name: 'status', label: t('timesheets.status.all'), type: 'select', options: [
{ value: 'pending', label: t('timesheets.status.pending') },
{ value: 'approved', label: t('timesheets.status.approved') },
{ value: 'rejected', label: t('timesheets.status.rejected') }
]},
{ name: 'hours', label: 'Hours', type: 'number' },
{ name: 'amount', label: 'Amount', type: 'number' },
{ name: 'weekEnding', label: 'Week Ending', type: 'date' }
{ name: 'hours', label: t('timesheets.hours'), type: 'number' },
{ name: 'amount', label: t('timesheets.amount'), type: 'number' },
{ name: 'weekEnding', label: t('timesheets.weekEnding'), type: 'date' }
]
const pendingCount = filteredTimesheets.filter(ts => ts.status === 'pending').length
@@ -371,8 +373,8 @@ export function TimesheetsView({
return (
<Stack spacing={6}>
<PageHeader
title="Timesheets"
description="Manage and approve worker timesheets"
title={t('timesheets.title')}
description={t('timesheets.subtitle')}
actions={
<Stack direction="horizontal" spacing={2}>
<Button
@@ -380,7 +382,7 @@ export function TimesheetsView({
onClick={() => setShowAnalytics(!showAnalytics)}
>
<ChartBar size={18} className="mr-2" />
{showAnalytics ? 'Hide' : 'Show'} Analytics
{showAnalytics ? t('timesheets.hideAnalytics') : t('timesheets.showAnalytics')}
</Button>
<TimesheetCreateDialogs
isCreateDialogOpen={isCreateDialogOpen}
@@ -403,28 +405,28 @@ export function TimesheetsView({
<>
<Grid cols={4} gap={4} responsive>
<MetricCard
label="Total Timesheets"
label={t('timesheets.totalTimesheets')}
value={filteredTimesheets.length}
icon={<FileText size={24} />}
description={`${pendingCount} pending review`}
description={t('timesheets.pendingReview', { count: pendingCount })}
/>
<MetricCard
label="Total Hours"
label={t('timesheets.totalHours')}
value={`${totalHours.toFixed(1)}h`}
icon={<Clock size={24} />}
description="This period"
description={t('timesheets.thisPeriod')}
/>
<MetricCard
label="Validation Issues"
label={t('timesheets.validationIssues')}
value={validationStats.invalid}
icon={<Warning size={24} />}
description={validationStats.invalid > 0 ? 'Errors found' : 'All valid'}
description={validationStats.invalid > 0 ? t('timesheets.errorsFound') : t('timesheets.allValid')}
/>
<MetricCard
label="Total Value"
label={t('timesheets.totalValue')}
value={`£${totalValue.toLocaleString()}`}
icon={<CurrencyDollar size={24} />}
description="Pending invoicing"
description={t('timesheets.pendingInvoicing')}
/>
</Grid>
@@ -432,12 +434,12 @@ export function TimesheetsView({
<Card className="border-l-4 border-l-warning">
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<CardTitle className="text-base font-medium">Pending</CardTitle>
<CardTitle className="text-base font-medium">{t('timesheets.pending')}</CardTitle>
<Badge variant="outline" className="text-warning border-warning/30 bg-warning/10">
{pendingCount}
</Badge>
</div>
<CardDescription className="text-xs">Awaiting approval</CardDescription>
<CardDescription className="text-xs">{t('timesheets.awaitingApproval')}</CardDescription>
</CardHeader>
<CardContent>
<div className="text-3xl font-bold text-warning">
@@ -449,13 +451,13 @@ export function TimesheetsView({
<Card className="border-l-4 border-l-success">
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<CardTitle className="text-base font-medium">Approved</CardTitle>
<CardTitle className="text-base font-medium">{t('timesheets.approved')}</CardTitle>
<Badge variant="outline" className="text-success border-success/30 bg-success/10">
<CheckCircle size={12} weight="bold" className="mr-1" />
{approvedCount}
</Badge>
</div>
<CardDescription className="text-xs">Ready for billing</CardDescription>
<CardDescription className="text-xs">{t('timesheets.readyForBilling')}</CardDescription>
</CardHeader>
<CardContent>
<div className="text-3xl font-bold text-success">
@@ -467,13 +469,13 @@ export function TimesheetsView({
<Card className="border-l-4 border-l-accent">
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<CardTitle className="text-base font-medium">Approval Rate</CardTitle>
<CardTitle className="text-base font-medium">{t('timesheets.approvalRate')}</CardTitle>
<Badge variant="outline" className="text-accent border-accent/30 bg-accent/10">
<TrendUp size={12} weight="bold" className="mr-1" />
{approvalRate.toFixed(0)}%
</Badge>
</div>
<CardDescription className="text-xs">This period</CardDescription>
<CardDescription className="text-xs">{t('timesheets.thisPeriod')}</CardDescription>
</CardHeader>
<CardContent>
<div className="text-3xl font-bold text-accent">
@@ -492,13 +494,13 @@ export function TimesheetsView({
<CardHeader className="pb-4">
<div className="flex items-center justify-between">
<div>
<CardTitle className="text-lg">Search & Filter</CardTitle>
<CardTitle className="text-lg">{t('timesheets.searchAndFilter')}</CardTitle>
<CardDescription className="text-xs mt-1">
Find timesheets using advanced search
{t('timesheets.findTimesheetsAdvanced')}
</CardDescription>
</div>
<Badge variant="secondary" className="font-mono">
{filteredTimesheets.length} results
{t('timesheets.results', { count: filteredTimesheets.length })}
</Badge>
</div>
</CardHeader>
@@ -507,7 +509,7 @@ export function TimesheetsView({
items={timesheetsToFilter}
fields={timesheetFields}
onResultsChange={handleResultsChange}
placeholder="Search by worker, client, or status..."
placeholder={t('timesheets.searchPlaceholder')}
/>
</CardContent>
</Card>
@@ -522,24 +524,27 @@ export function TimesheetsView({
</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="all">{t('timesheets.status.all')}</SelectItem>
<SelectItem value="pending">{t('timesheets.status.pending')}</SelectItem>
<SelectItem value="approved">{t('timesheets.status.approved')}</SelectItem>
<SelectItem value="rejected">{t('timesheets.status.rejected')}</SelectItem>
</SelectContent>
</Select>
{validationStats.invalid > 0 && (
<Badge variant="destructive" className="px-3 py-1.5">
<Warning size={14} weight="bold" className="mr-1" />
{validationStats.invalid} validation {validationStats.invalid === 1 ? 'error' : 'errors'}
{t('timesheets.validationErrors', {
count: validationStats.invalid,
errors: validationStats.invalid === 1 ? t('timesheets.error') : t('timesheets.errors')
})}
</Badge>
)}
</Stack>
<Button variant="outline">
<Download size={18} className="mr-2" />
Export CSV
{t('timesheets.exportCsv')}
</Button>
</Stack>

View File

@@ -115,7 +115,7 @@
},
"timesheets": {
"title": "Timesheets",
"subtitle": "Manage and approve timesheet entries",
"subtitle": "Manage and approve worker timesheets",
"addTimesheet": "Add Timesheet",
"editTimesheet": "Edit Timesheet",
"approveTimesheet": "Approve Timesheet",
@@ -133,7 +133,9 @@
"submitted": "Submitted",
"approved": "Approved",
"rejected": "Rejected",
"processed": "Processed"
"processed": "Processed",
"pending": "Pending",
"all": "All Status"
},
"hoursWorked": "Hours Worked",
"overtimeHours": "Overtime Hours",
@@ -142,7 +144,57 @@
"nightShift": "Night Shift",
"weekendShift": "Weekend Shift",
"adjustments": "Adjustments",
"comments": "Comments"
"comments": "Comments",
"analytics": "Analytics",
"showAnalytics": "Show Analytics",
"hideAnalytics": "Hide Analytics",
"totalTimesheets": "Total Timesheets",
"pendingReview": "{{count}} pending review",
"thisPeriod": "This period",
"validationIssues": "Validation Issues",
"errorsFound": "Errors found",
"allValid": "All valid",
"totalValue": "Total Value",
"pendingInvoicing": "Pending invoicing",
"pending": "Pending",
"awaitingApproval": "Awaiting approval",
"approved": "Approved",
"readyForBilling": "Ready for billing",
"approvalRate": "Approval Rate",
"searchAndFilter": "Search & Filter",
"findTimesheetsAdvanced": "Find timesheets using advanced search",
"results": "{{count}} results",
"searchPlaceholder": "Search by worker, client, or status...",
"allStatus": "All Status",
"validationErrors": "{{count}} validation {{errors}}",
"error": "error",
"errors": "errors",
"exportCsv": "Export CSV",
"workerName": "Worker Name",
"clientName": "Client Name",
"bulkImport": "Bulk Import",
"detailedEntry": "Detailed Entry",
"createSuccess": "Timesheet created successfully",
"createError": "Failed to create timesheet",
"createDetailedSuccess": "Detailed timesheet created successfully",
"createDetailedError": "Failed to create detailed timesheet",
"importSuccess": "{{count}} timesheets imported successfully",
"importError": "Failed to import timesheets",
"approveSuccess": "Timesheet approved",
"approveError": "Failed to approve timesheet",
"rejectSuccess": "Timesheet rejected",
"rejectError": "Failed to reject timesheet",
"adjustSuccess": "Timesheet adjusted",
"adjustError": "Failed to adjust timesheet",
"deleteSuccess": "Timesheet deleted",
"deleteError": "Failed to delete timesheet",
"noPermissionApprove": "You do not have permission to approve timesheets",
"noPermissionReject": "You do not have permission to reject timesheets",
"noPermissionEdit": "You do not have permission to adjust timesheets",
"noPermissionDelete": "You do not have permission to delete timesheets",
"adjustmentSubmitted": "Adjustment submitted for approval",
"adjustmentApplied": "Adjustment applied successfully",
"adjustmentError": "Failed to apply adjustment"
},
"billing": {
"title": "Billing",

View File

@@ -115,7 +115,7 @@
},
"timesheets": {
"title": "Hojas de Tiempo",
"subtitle": "Gestionar y aprobar entradas de hojas de tiempo",
"subtitle": "Gestionar y aprobar hojas de tiempo de trabajadores",
"addTimesheet": "Agregar Hoja de Tiempo",
"editTimesheet": "Editar Hoja de Tiempo",
"approveTimesheet": "Aprobar Hoja de Tiempo",
@@ -133,7 +133,9 @@
"submitted": "Enviado",
"approved": "Aprobado",
"rejected": "Rechazado",
"processed": "Procesado"
"processed": "Procesado",
"pending": "Pendiente",
"all": "Todos los Estados"
},
"hoursWorked": "Horas Trabajadas",
"overtimeHours": "Horas Extras",
@@ -142,7 +144,57 @@
"nightShift": "Turno Nocturno",
"weekendShift": "Turno de Fin de Semana",
"adjustments": "Ajustes",
"comments": "Comentarios"
"comments": "Comentarios",
"analytics": "Análisis",
"showAnalytics": "Mostrar Análisis",
"hideAnalytics": "Ocultar Análisis",
"totalTimesheets": "Hojas de Tiempo Totales",
"pendingReview": "{{count}} pendientes de revisión",
"thisPeriod": "Este período",
"validationIssues": "Problemas de Validación",
"errorsFound": "Errores encontrados",
"allValid": "Todo válido",
"totalValue": "Valor Total",
"pendingInvoicing": "Facturación pendiente",
"pending": "Pendiente",
"awaitingApproval": "Esperando aprobación",
"approved": "Aprobado",
"readyForBilling": "Listo para facturar",
"approvalRate": "Tasa de Aprobación",
"searchAndFilter": "Buscar y Filtrar",
"findTimesheetsAdvanced": "Encontrar hojas de tiempo usando búsqueda avanzada",
"results": "{{count}} resultados",
"searchPlaceholder": "Buscar por trabajador, cliente o estado...",
"allStatus": "Todos los Estados",
"validationErrors": "{{count}} {{errors}} de validación",
"error": "error",
"errors": "errores",
"exportCsv": "Exportar CSV",
"workerName": "Nombre del Trabajador",
"clientName": "Nombre del Cliente",
"bulkImport": "Importación Masiva",
"detailedEntry": "Entrada Detallada",
"createSuccess": "Hoja de tiempo creada exitosamente",
"createError": "Fallo al crear hoja de tiempo",
"createDetailedSuccess": "Hoja de tiempo detallada creada exitosamente",
"createDetailedError": "Fallo al crear hoja de tiempo detallada",
"importSuccess": "{{count}} hojas de tiempo importadas exitosamente",
"importError": "Fallo al importar hojas de tiempo",
"approveSuccess": "Hoja de tiempo aprobada",
"approveError": "Fallo al aprobar hoja de tiempo",
"rejectSuccess": "Hoja de tiempo rechazada",
"rejectError": "Fallo al rechazar hoja de tiempo",
"adjustSuccess": "Hoja de tiempo ajustada",
"adjustError": "Fallo al ajustar hoja de tiempo",
"deleteSuccess": "Hoja de tiempo eliminada",
"deleteError": "Fallo al eliminar hoja de tiempo",
"noPermissionApprove": "No tienes permiso para aprobar hojas de tiempo",
"noPermissionReject": "No tienes permiso para rechazar hojas de tiempo",
"noPermissionEdit": "No tienes permiso para ajustar hojas de tiempo",
"noPermissionDelete": "No tienes permiso para eliminar hojas de tiempo",
"adjustmentSubmitted": "Ajuste enviado para aprobación",
"adjustmentApplied": "Ajuste aplicado exitosamente",
"adjustmentError": "Fallo al aplicar ajuste"
},
"billing": {
"title": "Facturación",

View File

@@ -115,7 +115,7 @@
},
"timesheets": {
"title": "Feuilles de Temps",
"subtitle": "Gérer et approuver les entrées de feuilles de temps",
"subtitle": "Gérer et approuver les feuilles de temps des travailleurs",
"addTimesheet": "Ajouter une Feuille de Temps",
"editTimesheet": "Modifier une Feuille de Temps",
"approveTimesheet": "Approuver une Feuille de Temps",
@@ -133,7 +133,9 @@
"submitted": "Soumis",
"approved": "Approuvé",
"rejected": "Rejeté",
"processed": "Traité"
"processed": "Traité",
"pending": "En Attente",
"all": "Tous les Statuts"
},
"hoursWorked": "Heures Travaillées",
"overtimeHours": "Heures Supplémentaires",
@@ -142,7 +144,57 @@
"nightShift": "Quart de Nuit",
"weekendShift": "Quart de Fin de Semaine",
"adjustments": "Ajustements",
"comments": "Commentaires"
"comments": "Commentaires",
"analytics": "Analytique",
"showAnalytics": "Afficher l'Analytique",
"hideAnalytics": "Masquer l'Analytique",
"totalTimesheets": "Feuilles de Temps Totales",
"pendingReview": "{{count}} en attente de révision",
"thisPeriod": "Cette période",
"validationIssues": "Problèmes de Validation",
"errorsFound": "Erreurs trouvées",
"allValid": "Tout est valide",
"totalValue": "Valeur Totale",
"pendingInvoicing": "Facturation en attente",
"pending": "En Attente",
"awaitingApproval": "En attente d'approbation",
"approved": "Approuvé",
"readyForBilling": "Prêt pour la facturation",
"approvalRate": "Taux d'Approbation",
"searchAndFilter": "Recherche et Filtrage",
"findTimesheetsAdvanced": "Rechercher des feuilles de temps avec recherche avancée",
"results": "{{count}} résultats",
"searchPlaceholder": "Rechercher par travailleur, client ou statut...",
"allStatus": "Tous les Statuts",
"validationErrors": "{{count}} {{errors}} de validation",
"error": "erreur",
"errors": "erreurs",
"exportCsv": "Exporter CSV",
"workerName": "Nom du Travailleur",
"clientName": "Nom du Client",
"bulkImport": "Importation en Vrac",
"detailedEntry": "Entrée Détaillée",
"createSuccess": "Feuille de temps créée avec succès",
"createError": "Échec de la création de la feuille de temps",
"createDetailedSuccess": "Feuille de temps détaillée créée avec succès",
"createDetailedError": "Échec de la création de la feuille de temps détaillée",
"importSuccess": "{{count}} feuilles de temps importées avec succès",
"importError": "Échec de l'importation des feuilles de temps",
"approveSuccess": "Feuille de temps approuvée",
"approveError": "Échec de l'approbation de la feuille de temps",
"rejectSuccess": "Feuille de temps rejetée",
"rejectError": "Échec du rejet de la feuille de temps",
"adjustSuccess": "Feuille de temps ajustée",
"adjustError": "Échec de l'ajustement de la feuille de temps",
"deleteSuccess": "Feuille de temps supprimée",
"deleteError": "Échec de la suppression de la feuille de temps",
"noPermissionApprove": "Vous n'avez pas la permission d'approuver les feuilles de temps",
"noPermissionReject": "Vous n'avez pas la permission de rejeter les feuilles de temps",
"noPermissionEdit": "Vous n'avez pas la permission d'ajuster les feuilles de temps",
"noPermissionDelete": "Vous n'avez pas la permission de supprimer les feuilles de temps",
"adjustmentSubmitted": "Ajustement soumis pour approbation",
"adjustmentApplied": "Ajustement appliqué avec succès",
"adjustmentError": "Échec de l'application de l'ajustement"
},
"billing": {
"title": "Facturation",