'use client' import { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react' import { SHARED_LANGUAGE_COOKIE, SHARED_LANGUAGE_KEY, SHARED_THEME_KEY, readCurrentUserScopedPreference, readScopedPreference, writeScopedPreference } from '@/lib/preferences' import { apiFetch } from '@/lib/api' export type DashboardLanguage = 'en' | 'fr' | 'ar' export type DashboardTheme = 'light' | 'dark' type FleetDict = { statusLabels: Record<'AVAILABLE' | 'RENTED' | 'MAINTENANCE' | 'OUT_OF_SERVICE', string> logMaintenance: string maintenanceSubtitle: (name: string) => string maintenanceTypeRequired: string failedToSave: string typeLabel: string typePlaceholder: string description: string descriptionPlaceholder: string date: string nextDueDate: string cost: string odometer: string skip: string saveLog: string saving: string addNewVehicle: string makeMissing: string modelMissing: string plateMissing: string rateMissing: string failedToAdd: string make: string selectMake: string other: string typeMake: string model: string selectModel: string typeModel: string year: string category: string dailyRate: string licensePlate: string color: string select: string typeColor: string seats: string transmission: string manual: string automatic: string fuelType: string photosLabel: string clickToAddPhotos: string addMorePhotos: string cancel: string adding: string addVehicle: string heading: string vehicleCount: (n: number) => string searchPlaceholder: string categoryLabels: Record<'ECONOMY' | 'COMPACT' | 'MIDSIZE' | 'FULLSIZE' | 'SUV' | 'LUXURY' | 'VAN' | 'TRUCK', string> fuelTypeLabels: Record<'GASOLINE' | 'DIESEL' | 'ELECTRIC' | 'HYBRID', string> allStatuses: string allCategories: string allVisibility: string published: string unpublished: string colVehicle: string colCategory: string colStatus: string colDailyRate: string colPublished: string colActions: string noPhoto: string perDay: string noVehicles: string noMatch: string } type ReservationsDict = { heading: string subtitle: string colCustomer: string colVehicle: string colDates: string colSource: string colStatus: string colTotal: string noReservations: string loadingReservation: string failedToLoad: string confirmReservation: string checkInVehicle: string checkOutVehicle: string working: string failedConfirm: string failedCheckIn: string failedCheckOut: string sectionCustomer: string noPhone: string licenseLabel: string notCaptured: string flaggedBadge: string sectionVehicle: string sectionCharges: string chargeDiscount: string chargeInsurance: string chargeAdditionalDrivers: string chargePricingAdjustments: string chargeGrandTotal: string appliedInsurance: string pricingRulesApplied: string sectionAdditionalDrivers: string driverLicenseLabel: string driverChargeLabel: string approveBtn: string approvedBadge: string noApprovalNeeded: string noAdditionalDrivers: string failedApproveDriver: string sectionInspectionSummary: string checkInInspectionLabel: string checkOutInspectionLabel: string savedBadge: string pendingBadge: string inspectionCardTitle: (type: 'CHECKIN' | 'CHECKOUT') => string inspectionSubtitle: string saveInspection: string savingInspection: string failedSaveInspection: string diagramHelp: string mileageLabel: string fuelLevelLabel: string fuelLevels: Record<'FULL' | 'SEVEN_EIGHTHS' | 'THREE_QUARTERS' | 'FIVE_EIGHTHS' | 'HALF' | 'THREE_EIGHTHS' | 'QUARTER' | 'ONE_EIGHTH' | 'EMPTY', string> generalConditionLabel: string employeeNotesLabel: string customerAcknowledged: string damageMarkersHeading: string removeMarker: string noMarkers: string damageTypeLabels: Record<'SCRATCH' | 'DENT' | 'CRACK' | 'CHIP' | 'MISSING' | 'STAIN' | 'OTHER', string> severityLabels: Record<'MINOR' | 'MODERATE' | 'MAJOR', string> } type DashboardPageDict = { failedToLoad: string forbidden: string kpiTotalBookings: string kpiActiveVehicles: string kpiMonthlyRevenue: string kpiTotalCustomers: string vsLastMonth: string bookingSources: string barBookings: string barRevenue: string noBookingData: string quickStats: string noData: string recentReservations: string viewAll: string colBookingNum: string colCustomer: string colVehicle: string colDates: string colStatus: string colTotal: string noReservations: string trialEnds: (date: string) => string trialActive: string pastDueMsg: string suspendedMsg: string manageBilling: string statusLabels: Record<'CONFIRMED' | 'PENDING' | 'ACTIVE' | 'COMPLETED' | 'CANCELLED', string> } type CalendarDict = { heading: string blockDates: string clickSecondDay: (selected: string) => string cancelSelection: string legendReserved: string legendMaintenance: string legendBlocked: string legendAvailable: string failedLoad: string moreDays: (n: number) => string reservationsThisMonth: string blocksAndMaintenance: string removeBlockConfirm: string failedDeleteBlock: string modalTitle: string blockTypeLabel: string manualBlock: string maintenanceBlock: string startDate: string endDate: string reasonLabel: string optional: string reasonPlaceholderManual: string reasonPlaceholderMaintenance: string datesRequired: string endAfterStart: string failedCreateBlock: string cancel: string saveBlock: string saving: string removeBlock: string dayHeaders: [string, string, string, string, string, string, string] } type VehicleDetailDict = { tabDetails: string tabMaintenance: string tabCalendar: string editBtn: string cancelBtn: string saveBtn: string savingBtn: string uploadingPhotosBtn: string loadingVehicle: string photosHeading: string noPhotosYet: string clickToAddPhotos: string addMorePhotos: (current: number) => string maxPhotosReached: string newPhotoBadge: string vehicleDetailsHeading: string labelCategory: string labelDailyRate: string labelStatus: string labelSeats: string labelTransmission: string labelFuelType: string labelColor: string labelOdometer: string labelNotes: string makeLabel: string selectMake: string other: string typeMake: string modelLabel: string selectModel: string typeModel: string yearLabel: string dailyRateLabel: string licensePlateLabel: string statusLabel: string colorLabel: string selectColor: string typeColor: string seatsLabel: string transmissionLabel: string fuelTypeLabel: string odometerLabel: string notesLabel: string optionalNotes: string optional: string makeRequired: string modelRequired: string plateRequired: string rateInvalid: string failedSave: string maintenanceIntro: string logBtn: string statusNone: string statusOverdue: string statusDueSoon: string statusOk: string noRecord: string lastPrefix: string atKm: (km: string) => string duePrefix: string orSep: string kmLeft: (km: string) => string overdueByKm: string serviceDateLabel: string odometerAtService: string nextDueDateLabel: string nextDueKmLabel: string costLabel: string maintenanceNotesLabel: string saveLabel: string savingLabel: string dateRequired: string routineTypes: Record } type DashboardDictionary = { nav: Record titles: Record notifications: string noNewNotifications: string unreadNotifications: (count: number) => string signOut: string workspaceUser: string localAuth: string language: string theme: string light: string dark: string fleet: FleetDict vehicleDetail: VehicleDetailDict calendar: CalendarDict dashboardPage: DashboardPageDict reservations: ReservationsDict } const dictionaries: Record = { en: { nav: { dashboard: 'Dashboard', fleet: 'Fleet', reservations: 'Booking', onlineReservations: 'Online Reservations', customers: 'Customers', offers: 'Offers', team: 'Team', reports: 'Reports', subscription: 'Subscription', billing: 'Customer Billing', contracts: 'Contracts', notifications: 'Notifications', settings: 'Settings', }, titles: { '/dashboard': 'Dashboard', '/dashboard/fleet': 'Fleet Management', '/dashboard/reservations': 'Booking', '/dashboard/reservations/new': 'Book Car', '/dashboard/online-reservations': 'Online Reservations', '/dashboard/customers': 'Customers', '/dashboard/offers': 'Offers', '/dashboard/team': 'Team', '/dashboard/reports': 'Reports', '/dashboard/subscription': 'Subscription', '/dashboard/billing': 'Customer Billing', '/dashboard/contracts': 'Contracts', '/dashboard/notifications': 'Notifications', '/dashboard/settings': 'Settings', }, notifications: 'Notifications', noNewNotifications: 'No new notifications', unreadNotifications: (count) => `${count} unread notification${count > 1 ? 's' : ''}`, signOut: 'Sign out', workspaceUser: 'Workspace user', localAuth: 'Local authentication', language: 'Language', theme: 'Theme', light: 'Light', dark: 'Dark', fleet: { statusLabels: { AVAILABLE: 'Available', RENTED: 'Rented', MAINTENANCE: 'Maintenance', OUT_OF_SERVICE: 'Out of Service' }, logMaintenance: 'Log Maintenance', maintenanceSubtitle: (name) => `${name} has been set to maintenance. Add details below.`, maintenanceTypeRequired: 'Please enter a maintenance type.', failedToSave: 'Failed to save maintenance log.', typeLabel: 'Type *', typePlaceholder: 'e.g. Oil change, Tire rotation, Brake service…', description: 'Description', descriptionPlaceholder: 'Optional details…', date: 'Date *', nextDueDate: 'Next Due Date', cost: 'Cost (MAD)', odometer: 'Odometer (km)', skip: 'Skip', saveLog: 'Save Log', saving: 'Saving…', addNewVehicle: 'Add New Vehicle', makeMissing: 'Please select or enter a make.', modelMissing: 'Please select or enter a model.', plateMissing: 'Please enter a license plate.', rateMissing: 'Please enter a valid daily rate.', failedToAdd: 'Failed to add vehicle. Please check all fields and try again.', make: 'Make *', selectMake: 'Select make…', other: 'Other…', typeMake: 'Type make…', model: 'Model *', selectModel: 'Select model…', typeModel: 'Type model…', year: 'Year *', category: 'Category', dailyRate: 'Daily Rate (MAD) *', licensePlate: 'License Plate *', color: 'Color', select: 'Select…', typeColor: 'Type color…', seats: 'Seats', transmission: 'Transmission', manual: 'Manual', automatic: 'Automatic', fuelType: 'Fuel Type', photosLabel: 'Photos (up to 10)', clickToAddPhotos: 'Click to add photos', addMorePhotos: 'Add more photos', cancel: 'Cancel', adding: 'Adding…', addVehicle: 'Add Vehicle', heading: 'Fleet', vehicleCount: (n) => `${n} vehicle${n !== 1 ? 's' : ''} total`, searchPlaceholder: 'Search make, model, plate…', categoryLabels: { ECONOMY: 'Economy', COMPACT: 'Compact', MIDSIZE: 'Midsize', FULLSIZE: 'Full-size', SUV: 'SUV', LUXURY: 'Luxury', VAN: 'Van', TRUCK: 'Truck', }, fuelTypeLabels: { GASOLINE: 'Gasoline', DIESEL: 'Diesel', ELECTRIC: 'Electric', HYBRID: 'Hybrid', }, allStatuses: 'All statuses', allCategories: 'All categories', allVisibility: 'All visibility', published: 'Published', unpublished: 'Unpublished', colVehicle: 'Vehicle', colCategory: 'Category', colStatus: 'Status', colDailyRate: 'Daily Rate', colPublished: 'Published', colActions: 'Actions', noPhoto: 'No photo', perDay: '/day', noVehicles: 'No vehicles yet. Add your first vehicle to get started.', noMatch: 'No vehicles match your filters.', }, vehicleDetail: { tabDetails: 'Details', tabMaintenance: 'Maintenance', tabCalendar: 'Calendar', editBtn: 'Edit', cancelBtn: 'Cancel', saveBtn: 'Save', savingBtn: 'Saving…', uploadingPhotosBtn: 'Uploading photos…', loadingVehicle: 'Loading vehicle…', photosHeading: 'Photos', noPhotosYet: 'No photos uploaded yet.', clickToAddPhotos: 'Click to add photos', addMorePhotos: (n) => `Add more photos (${n}/10)`, maxPhotosReached: 'Maximum 10 photos reached.', newPhotoBadge: 'New', vehicleDetailsHeading: 'Vehicle details', labelCategory: 'Category', labelDailyRate: 'Daily rate', labelStatus: 'Status', labelSeats: 'Seats', labelTransmission: 'Transmission', labelFuelType: 'Fuel type', labelColor: 'Color', labelOdometer: 'Odometer', labelNotes: 'Notes', makeLabel: 'Make *', selectMake: 'Select make…', other: 'Other…', typeMake: 'Type make…', modelLabel: 'Model *', selectModel: 'Select model…', typeModel: 'Type model…', yearLabel: 'Year *', dailyRateLabel: 'Daily Rate (MAD) *', licensePlateLabel: 'License Plate *', statusLabel: 'Status', colorLabel: 'Color', selectColor: 'Select color…', typeColor: 'Type color…', seatsLabel: 'Seats', transmissionLabel: 'Transmission', fuelTypeLabel: 'Fuel Type', odometerLabel: 'Odometer (km)', notesLabel: 'Notes', optionalNotes: 'Optional notes…', optional: 'Optional…', makeRequired: 'Make is required.', modelRequired: 'Model is required.', plateRequired: 'License plate is required.', rateInvalid: 'Please enter a valid daily rate.', failedSave: 'Failed to save changes.', maintenanceIntro: 'Track routine maintenance for this vehicle. Click Log on any item to record a service.', logBtn: 'Log', statusNone: 'Not logged', statusOverdue: 'Overdue', statusDueSoon: 'Due soon', statusOk: 'Up to date', noRecord: 'No record yet', lastPrefix: 'Last:', atKm: (km) => `at ${km} km`, duePrefix: '· Due:', orSep: 'or', kmLeft: (km) => `${km} km left`, overdueByKm: 'overdue by km', serviceDateLabel: 'Service Date *', odometerAtService: 'Odometer at service (km)', nextDueDateLabel: 'Next due date', nextDueKmLabel: 'Next due at (km)', costLabel: 'Cost (MAD)', maintenanceNotesLabel: 'Notes', saveLabel: 'Save', savingLabel: 'Saving…', dateRequired: 'Service date is required.', routineTypes: { 'Oil Change': 'Oil Change', 'Technical Inspection': 'Technical Inspection', 'Vehicle Registration': 'Vehicle Registration', 'Car Tax': 'Car Tax', 'Tire Rotation': 'Tire Rotation', 'Brake Inspection': 'Brake Inspection', 'Air Filter': 'Air Filter', 'Cabin Filter': 'Cabin Filter', 'Battery Check': 'Battery Check', 'Belt / Chain Service': 'Belt / Chain Service', 'Coolant Flush': 'Coolant Flush', 'Transmission Service': 'Transmission Service', }, }, calendar: { heading: 'Availability Calendar', blockDates: 'Block dates', clickSecondDay: (selected) => `Click a second day to set the block end date (selected: ${selected})`, cancelSelection: 'cancel', legendReserved: 'Reserved', legendMaintenance: 'Maintenance', legendBlocked: 'Blocked', legendAvailable: 'Available', failedLoad: 'Failed to load calendar', moreDays: (n) => `+${n} more`, reservationsThisMonth: 'Reservations this month', blocksAndMaintenance: 'Blocks & maintenance', removeBlockConfirm: 'Remove this block?', failedDeleteBlock: 'Failed to delete block', modalTitle: 'Block vehicle dates', blockTypeLabel: 'Block type', manualBlock: 'Manual block', maintenanceBlock: 'Maintenance', startDate: 'Start date', endDate: 'End date (exclusive)', reasonLabel: 'Reason', optional: '(optional)', reasonPlaceholderManual: 'e.g. Reserved for VIP, company event…', reasonPlaceholderMaintenance: 'e.g. Oil change, tire rotation…', datesRequired: 'Start and end dates are required.', endAfterStart: 'End date must be after start date.', failedCreateBlock: 'Failed to create block', cancel: 'Cancel', saveBlock: 'Save block', saving: 'Saving…', removeBlock: 'Remove block', dayHeaders: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], }, dashboardPage: { failedToLoad: 'Failed to load dashboard', forbidden: 'You do not have permission to access this page. A Manager role or higher is required.', kpiTotalBookings: 'Total Bookings', kpiActiveVehicles: 'Active Vehicles', kpiMonthlyRevenue: 'Monthly Revenue', kpiTotalCustomers: 'Total Customers', vsLastMonth: 'vs last month', bookingSources: 'Booking Sources', barBookings: 'Bookings', barRevenue: 'Revenue (MAD)', noBookingData: 'No booking data available yet', quickStats: 'Quick Stats', noData: 'No data yet', recentReservations: 'Recent Reservations', viewAll: 'View all →', colBookingNum: 'Booking #', colCustomer: 'Customer', colVehicle: 'Vehicle', colDates: 'Dates', colStatus: 'Status', colTotal: 'Total', noReservations: "No reservations yet. Once customers book vehicles, they'll appear here.", trialEnds: (date) => `You're on a free trial. Trial ends ${date}.`, trialActive: "You're on a free trial.", pastDueMsg: 'Your payment is past due. Please update your billing information.', suspendedMsg: 'Your account has been suspended. Please contact support or renew your subscription.', manageBilling: 'Manage subscription →', statusLabels: { CONFIRMED: 'Confirmed', PENDING: 'Pending', ACTIVE: 'Active', COMPLETED: 'Completed', CANCELLED: 'Cancelled' }, }, reservations: { heading: 'Booking', subtitle: 'All booking sources, including dashboard, public site, and marketplace.', colCustomer: 'Customer', colVehicle: 'Vehicle', colDates: 'Dates', colSource: 'Source', colStatus: 'Status', colTotal: 'Total', noReservations: 'No reservations yet.', loadingReservation: 'Loading reservation…', failedToLoad: 'Failed to load reservation', confirmReservation: 'Confirm reservation', checkInVehicle: 'Check in vehicle', checkOutVehicle: 'Check out vehicle', working: 'Working…', failedConfirm: 'Failed to confirm reservation', failedCheckIn: 'Failed to check in reservation', failedCheckOut: 'Failed to check out reservation', sectionCustomer: 'Customer', noPhone: 'No phone provided', licenseLabel: 'License:', notCaptured: 'Not captured', flaggedBadge: 'Flagged', sectionVehicle: 'Vehicle', sectionCharges: 'Charges', chargeDiscount: 'Discount', chargeInsurance: 'Insurance', chargeAdditionalDrivers: 'Additional drivers', chargePricingAdjustments: 'Pricing adjustments', chargeGrandTotal: 'Grand total', appliedInsurance: 'Applied insurance', pricingRulesApplied: 'Pricing rules applied', sectionAdditionalDrivers: 'Additional drivers', driverLicenseLabel: 'License:', driverChargeLabel: 'Charge:', approveBtn: 'Approve', approvedBadge: 'Approved', noApprovalNeeded: 'No approval needed', noAdditionalDrivers: 'No additional drivers were added to this reservation.', failedApproveDriver: 'Failed to approve additional driver', sectionInspectionSummary: 'Inspection summary', checkInInspectionLabel: 'Check-in inspection', checkOutInspectionLabel: 'Check-out inspection', savedBadge: 'Saved', pendingBadge: 'Pending', inspectionCardTitle: (type) => type === 'CHECKIN' ? 'Check-in inspection' : 'Check-out inspection', inspectionSubtitle: 'Mark existing and newly discovered damage directly on the vehicle diagram.', saveInspection: 'Save inspection', savingInspection: 'Saving…', failedSaveInspection: 'Failed to save inspection', diagramHelp: 'Click anywhere on the diagram to add a damage marker with the selected type and severity.', mileageLabel: 'Mileage', fuelLevelLabel: 'Fuel level', fuelLevels: { FULL: 'Full', SEVEN_EIGHTHS: '7/8', THREE_QUARTERS: '3/4', FIVE_EIGHTHS: '5/8', HALF: '1/2', THREE_EIGHTHS: '3/8', QUARTER: '1/4', ONE_EIGHTH: '1/8', EMPTY: 'Empty' }, generalConditionLabel: 'General condition', employeeNotesLabel: 'Employee notes', customerAcknowledged: 'Customer acknowledged this inspection', damageMarkersHeading: 'Damage markers', removeMarker: 'Remove', noMarkers: 'No damage markers saved yet.', damageTypeLabels: { SCRATCH: 'Scratch', DENT: 'Dent', CRACK: 'Crack', CHIP: 'Chip', MISSING: 'Missing', STAIN: 'Stain', OTHER: 'Other' }, severityLabels: { MINOR: 'Minor', MODERATE: 'Moderate', MAJOR: 'Major' }, }, }, fr: { nav: { dashboard: 'Tableau de bord', fleet: 'Flotte', reservations: 'Réservations', onlineReservations: 'Réservations en ligne', customers: 'Clients', offers: 'Offres', team: 'Équipe', reports: 'Rapports', subscription: 'Abonnement', billing: 'Facturation clients', contracts: 'Contrats', notifications: 'Notifications', settings: 'Paramètres', }, titles: { '/dashboard': 'Tableau de bord', '/dashboard/fleet': 'Gestion de flotte', '/dashboard/reservations': 'Réservations', '/dashboard/reservations/new': 'Réserver une voiture', '/dashboard/online-reservations': 'Réservations en ligne', '/dashboard/customers': 'Clients', '/dashboard/offers': 'Offres', '/dashboard/team': 'Équipe', '/dashboard/reports': 'Rapports', '/dashboard/subscription': 'Abonnement', '/dashboard/billing': 'Facturation clients', '/dashboard/contracts': 'Contrats', '/dashboard/notifications': 'Notifications', '/dashboard/settings': 'Paramètres', }, notifications: 'Notifications', noNewNotifications: 'Aucune nouvelle notification', unreadNotifications: (count) => `${count} notification${count > 1 ? 's' : ''} non lue${count > 1 ? 's' : ''}`, signOut: 'Déconnexion', workspaceUser: 'Utilisateur espace', localAuth: 'Authentification locale', language: 'Langue', theme: 'Mode', light: 'Clair', dark: 'Sombre', fleet: { statusLabels: { AVAILABLE: 'Disponible', RENTED: 'Loué', MAINTENANCE: 'Maintenance', OUT_OF_SERVICE: 'Hors service' }, logMaintenance: 'Journal de maintenance', maintenanceSubtitle: (name) => `${name} a été mis en maintenance. Ajoutez les détails ci-dessous.`, maintenanceTypeRequired: 'Veuillez saisir un type de maintenance.', failedToSave: 'Échec de la sauvegarde du journal de maintenance.', typeLabel: 'Type *', typePlaceholder: 'ex. Vidange, Rotation des pneus, Freins…', description: 'Description', descriptionPlaceholder: 'Détails optionnels…', date: 'Date *', nextDueDate: 'Prochaine échéance', cost: 'Coût (MAD)', odometer: 'Kilométrage (km)', skip: 'Ignorer', saveLog: 'Enregistrer', saving: 'Enregistrement…', addNewVehicle: 'Ajouter un véhicule', makeMissing: 'Veuillez sélectionner ou saisir une marque.', modelMissing: 'Veuillez sélectionner ou saisir un modèle.', plateMissing: 'Veuillez saisir un numéro de plaque.', rateMissing: 'Veuillez saisir un tarif journalier valide.', failedToAdd: "Échec de l'ajout du véhicule. Vérifiez tous les champs.", make: 'Marque *', selectMake: 'Sélectionner la marque…', other: 'Autre…', typeMake: 'Saisir la marque…', model: 'Modèle *', selectModel: 'Sélectionner le modèle…', typeModel: 'Saisir le modèle…', year: 'Année *', category: 'Catégorie', dailyRate: 'Tarif journalier (MAD) *', licensePlate: "Plaque d'immatriculation *", color: 'Couleur', select: 'Sélectionner…', typeColor: 'Saisir la couleur…', seats: 'Places', transmission: 'Transmission', manual: 'Manuelle', automatic: 'Automatique', fuelType: 'Carburant', photosLabel: "Photos (jusqu'à 10)", clickToAddPhotos: 'Cliquer pour ajouter des photos', addMorePhotos: "Ajouter d'autres photos", cancel: 'Annuler', adding: 'Ajout…', addVehicle: 'Ajouter le véhicule', heading: 'Flotte', vehicleCount: (n) => `${n} véhicule${n !== 1 ? 's' : ''} au total`, searchPlaceholder: 'Rechercher marque, modèle, plaque…', categoryLabels: { ECONOMY: 'Économique', COMPACT: 'Compacte', MIDSIZE: 'Intermédiaire', FULLSIZE: 'Grande berline', SUV: 'SUV', LUXURY: 'Luxe', VAN: 'Van', TRUCK: 'Camionnette', }, fuelTypeLabels: { GASOLINE: 'Essence', DIESEL: 'Diesel', ELECTRIC: 'Électrique', HYBRID: 'Hybride', }, allStatuses: 'Tous les statuts', allCategories: 'Toutes les catégories', allVisibility: 'Toute visibilité', published: 'Publié', unpublished: 'Non publié', colVehicle: 'Véhicule', colCategory: 'Catégorie', colStatus: 'Statut', colDailyRate: 'Tarif journalier', colPublished: 'Publié', colActions: 'Actions', noPhoto: 'Pas de photo', perDay: '/jour', noVehicles: 'Aucun véhicule. Ajoutez votre premier véhicule pour commencer.', noMatch: 'Aucun véhicule ne correspond aux filtres.', }, vehicleDetail: { tabDetails: 'Détails', tabMaintenance: 'Entretien', tabCalendar: 'Calendrier', editBtn: 'Modifier', cancelBtn: 'Annuler', saveBtn: 'Enregistrer', savingBtn: 'Enregistrement…', uploadingPhotosBtn: 'Envoi des photos…', loadingVehicle: 'Chargement du véhicule…', photosHeading: 'Photos', noPhotosYet: 'Aucune photo ajoutée.', clickToAddPhotos: 'Cliquer pour ajouter des photos', addMorePhotos: (n) => `Ajouter des photos (${n}/10)`, maxPhotosReached: '10 photos maximum atteintes.', newPhotoBadge: 'Nouveau', vehicleDetailsHeading: 'Détails du véhicule', labelCategory: 'Catégorie', labelDailyRate: 'Tarif journalier', labelStatus: 'Statut', labelSeats: 'Places', labelTransmission: 'Transmission', labelFuelType: 'Carburant', labelColor: 'Couleur', labelOdometer: 'Kilométrage', labelNotes: 'Notes', makeLabel: 'Marque *', selectMake: 'Sélectionner la marque…', other: 'Autre…', typeMake: 'Saisir la marque…', modelLabel: 'Modèle *', selectModel: 'Sélectionner le modèle…', typeModel: 'Saisir le modèle…', yearLabel: 'Année *', dailyRateLabel: 'Tarif journalier (MAD) *', licensePlateLabel: "Plaque d'immatriculation *", statusLabel: 'Statut', colorLabel: 'Couleur', selectColor: 'Sélectionner la couleur…', typeColor: 'Saisir la couleur…', seatsLabel: 'Places', transmissionLabel: 'Transmission', fuelTypeLabel: 'Carburant', odometerLabel: 'Kilométrage (km)', notesLabel: 'Notes', optionalNotes: 'Notes optionnelles…', optional: 'Optionnel…', makeRequired: 'La marque est requise.', modelRequired: 'Le modèle est requis.', plateRequired: "La plaque d'immatriculation est requise.", rateInvalid: 'Veuillez saisir un tarif journalier valide.', failedSave: "Échec de l'enregistrement.", maintenanceIntro: "Suivez l'entretien de ce véhicule. Cliquez sur Saisir pour enregistrer une intervention.", logBtn: 'Saisir', statusNone: 'Non enregistré', statusOverdue: 'En retard', statusDueSoon: 'Bientôt dû', statusOk: 'À jour', noRecord: 'Aucune intervention', lastPrefix: 'Dernier :', atKm: (km) => `à ${km} km`, duePrefix: '· Échéance :', orSep: 'ou', kmLeft: (km) => `${km} km restants`, overdueByKm: 'dépassé (km)', serviceDateLabel: 'Date du service *', odometerAtService: 'Kilométrage lors du service (km)', nextDueDateLabel: 'Prochaine échéance (date)', nextDueKmLabel: 'Prochaine échéance (km)', costLabel: 'Coût (MAD)', maintenanceNotesLabel: 'Notes', saveLabel: 'Enregistrer', savingLabel: 'Enregistrement…', dateRequired: 'La date du service est requise.', routineTypes: { 'Oil Change': "Vidange d'huile", 'Technical Inspection': 'Contrôle technique', 'Vehicle Registration': 'Carte grise', 'Car Tax': 'Vignette automobile', 'Tire Rotation': 'Rotation des pneus', 'Brake Inspection': 'Contrôle des freins', 'Air Filter': 'Filtre à air', 'Cabin Filter': 'Filtre habitacle', 'Battery Check': 'Vérification batterie', 'Belt / Chain Service': 'Courroie / Chaîne', 'Coolant Flush': 'Liquide de refroidissement', 'Transmission Service': 'Entretien transmission', }, }, calendar: { heading: 'Calendrier de disponibilité', blockDates: 'Bloquer des dates', clickSecondDay: (selected) => `Cliquez sur un second jour pour définir la fin du blocage (sélectionné : ${selected})`, cancelSelection: 'annuler', legendReserved: 'Réservé', legendMaintenance: 'Maintenance', legendBlocked: 'Bloqué', legendAvailable: 'Disponible', failedLoad: 'Échec du chargement du calendrier', moreDays: (n) => `+${n} de plus`, reservationsThisMonth: 'Réservations ce mois-ci', blocksAndMaintenance: 'Blocages & maintenance', removeBlockConfirm: 'Supprimer ce blocage ?', failedDeleteBlock: 'Échec de la suppression du blocage', modalTitle: 'Bloquer les dates du véhicule', blockTypeLabel: 'Type de blocage', manualBlock: 'Blocage manuel', maintenanceBlock: 'Maintenance', startDate: 'Date de début', endDate: 'Date de fin (exclusive)', reasonLabel: 'Raison', optional: '(optionnel)', reasonPlaceholderManual: 'ex. Réservé pour VIP, événement…', reasonPlaceholderMaintenance: 'ex. Vidange, rotation des pneus…', datesRequired: 'Les dates de début et de fin sont requises.', endAfterStart: 'La date de fin doit être après la date de début.', failedCreateBlock: 'Échec de la création du blocage', cancel: 'Annuler', saveBlock: 'Enregistrer', saving: 'Enregistrement…', removeBlock: 'Supprimer le blocage', dayHeaders: ['Dim', 'Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam'], }, dashboardPage: { failedToLoad: 'Échec du chargement du tableau de bord', forbidden: "Vous n'avez pas la permission d'accéder à cette page. Un rôle Gestionnaire ou supérieur est requis.", kpiTotalBookings: 'Total des réservations', kpiActiveVehicles: 'Véhicules actifs', kpiMonthlyRevenue: 'Revenus du mois', kpiTotalCustomers: 'Total des clients', vsLastMonth: 'vs mois dernier', bookingSources: 'Sources de réservation', barBookings: 'Réservations', barRevenue: 'Revenus (MAD)', noBookingData: 'Aucune donnée de réservation disponible', quickStats: 'Statistiques rapides', noData: 'Aucune donnée', recentReservations: 'Réservations récentes', viewAll: 'Voir tout →', colBookingNum: 'Réservation #', colCustomer: 'Client', colVehicle: 'Véhicule', colDates: 'Dates', colStatus: 'Statut', colTotal: 'Total', noReservations: "Aucune réservation pour l'instant. Les réservations clients apparaîtront ici.", trialEnds: (date) => `Vous êtes en période d'essai. Fin de l'essai le ${date}.`, trialActive: "Vous êtes en période d'essai.", pastDueMsg: 'Votre paiement est en retard. Veuillez mettre à jour vos informations de facturation.', suspendedMsg: 'Votre compte a été suspendu. Veuillez contacter le support ou renouveler votre abonnement.', manageBilling: 'Gérer l’abonnement →', statusLabels: { CONFIRMED: 'Confirmé', PENDING: 'En attente', ACTIVE: 'Actif', COMPLETED: 'Terminé', CANCELLED: 'Annulé' }, }, reservations: { heading: 'Réservations', subtitle: 'Toutes les sources de réservation : tableau de bord, site public et marketplace.', colCustomer: 'Client', colVehicle: 'Véhicule', colDates: 'Dates', colSource: 'Source', colStatus: 'Statut', colTotal: 'Total', noReservations: 'Aucune réservation pour le moment.', loadingReservation: 'Chargement de la réservation…', failedToLoad: 'Échec du chargement de la réservation', confirmReservation: 'Confirmer la réservation', checkInVehicle: 'Enregistrer le départ', checkOutVehicle: 'Enregistrer le retour', working: 'En cours…', failedConfirm: 'Échec de la confirmation de la réservation', failedCheckIn: 'Échec de l\'enregistrement du départ', failedCheckOut: 'Échec de l\'enregistrement du retour', sectionCustomer: 'Client', noPhone: 'Aucun téléphone renseigné', licenseLabel: 'Permis :', notCaptured: 'Non saisi', flaggedBadge: 'Signalé', sectionVehicle: 'Véhicule', sectionCharges: 'Facturation', chargeDiscount: 'Remise', chargeInsurance: 'Assurance', chargeAdditionalDrivers: 'Conducteurs supplémentaires', chargePricingAdjustments: 'Ajustements tarifaires', chargeGrandTotal: 'Total général', appliedInsurance: 'Assurance appliquée', pricingRulesApplied: 'Règles tarifaires appliquées', sectionAdditionalDrivers: 'Conducteurs supplémentaires', driverLicenseLabel: 'Permis :', driverChargeLabel: 'Frais :', approveBtn: 'Approuver', approvedBadge: 'Approuvé', noApprovalNeeded: 'Pas d\'approbation requise', noAdditionalDrivers: 'Aucun conducteur supplémentaire ajouté à cette réservation.', failedApproveDriver: 'Échec de l\'approbation du conducteur supplémentaire', sectionInspectionSummary: 'Résumé des inspections', checkInInspectionLabel: 'Inspection de départ', checkOutInspectionLabel: 'Inspection de retour', savedBadge: 'Enregistré', pendingBadge: 'En attente', inspectionCardTitle: (type) => type === 'CHECKIN' ? 'Inspection de départ' : 'Inspection de retour', inspectionSubtitle: 'Signalez les dommages existants et nouvellement découverts sur le schéma du véhicule.', saveInspection: 'Enregistrer l\'inspection', savingInspection: 'Enregistrement…', failedSaveInspection: 'Échec de l\'enregistrement de l\'inspection', diagramHelp: 'Cliquez n\'importe où sur le schéma pour ajouter un marqueur de dommage avec le type et la gravité sélectionnés.', mileageLabel: 'Kilométrage', fuelLevelLabel: 'Niveau de carburant', fuelLevels: { FULL: 'Plein', SEVEN_EIGHTHS: '7/8', THREE_QUARTERS: '3/4', FIVE_EIGHTHS: '5/8', HALF: '1/2', THREE_EIGHTHS: '3/8', QUARTER: '1/4', ONE_EIGHTH: '1/8', EMPTY: 'Vide' }, generalConditionLabel: 'État général', employeeNotesLabel: 'Notes de l\'employé', customerAcknowledged: 'Le client a reconnu cette inspection', damageMarkersHeading: 'Marqueurs de dommages', removeMarker: 'Supprimer', noMarkers: 'Aucun marqueur de dommage enregistré.', damageTypeLabels: { SCRATCH: 'Rayure', DENT: 'Bosse', CRACK: 'Fissure', CHIP: 'Éclat', MISSING: 'Manquant', STAIN: 'Tache', OTHER: 'Autre' }, severityLabels: { MINOR: 'Léger', MODERATE: 'Modéré', MAJOR: 'Majeur' }, }, }, ar: { nav: { dashboard: 'لوحة التحكم', fleet: 'الأسطول', reservations: 'الحجوزات', onlineReservations: 'الحجوزات الإلكترونية', customers: 'العملاء', offers: 'العروض', team: 'الفريق', reports: 'التقارير', subscription: 'الاشتراك', billing: 'فوترة العملاء', contracts: 'العقود', notifications: 'الإشعارات', settings: 'الإعدادات', }, titles: { '/dashboard': 'لوحة التحكم', '/dashboard/fleet': 'إدارة الأسطول', '/dashboard/reservations': 'الحجوزات', '/dashboard/reservations/new': 'حجز سيارة', '/dashboard/online-reservations': 'الحجوزات الإلكترونية', '/dashboard/customers': 'العملاء', '/dashboard/offers': 'العروض', '/dashboard/team': 'الفريق', '/dashboard/reports': 'التقارير', '/dashboard/subscription': 'الاشتراك', '/dashboard/billing': 'فوترة العملاء', '/dashboard/contracts': 'العقود', '/dashboard/notifications': 'الإشعارات', '/dashboard/settings': 'الإعدادات', }, notifications: 'الإشعارات', noNewNotifications: 'لا توجد إشعارات جديدة', unreadNotifications: (count) => `${count} إشعار غير مقروء`, signOut: 'تسجيل الخروج', workspaceUser: 'مستخدم مساحة العمل', localAuth: 'مصادقة محلية', language: 'اللغة', theme: 'الوضع', light: 'فاتح', dark: 'داكن', fleet: { statusLabels: { AVAILABLE: 'متاح', RENTED: 'مؤجر', MAINTENANCE: 'صيانة', OUT_OF_SERVICE: 'خارج الخدمة' }, logMaintenance: 'تسجيل الصيانة', maintenanceSubtitle: (name) => `تم تعيين ${name} في وضع الصيانة. أضف التفاصيل أدناه.`, maintenanceTypeRequired: 'الرجاء إدخال نوع الصيانة.', failedToSave: 'فشل حفظ سجل الصيانة.', typeLabel: 'النوع *', typePlaceholder: 'مثال: تغيير الزيت، تدوير الإطارات، خدمة الفرامل…', description: 'الوصف', descriptionPlaceholder: 'تفاصيل اختيارية…', date: 'التاريخ *', nextDueDate: 'تاريخ الاستحقاق التالي', cost: 'التكلفة (درهم)', odometer: 'عداد المسافة (كم)', skip: 'تخطي', saveLog: 'حفظ السجل', saving: 'جارٍ الحفظ…', addNewVehicle: 'إضافة مركبة جديدة', makeMissing: 'الرجاء اختيار أو إدخال الماركة.', modelMissing: 'الرجاء اختيار أو إدخال الطراز.', plateMissing: 'الرجاء إدخال رقم اللوحة.', rateMissing: 'الرجاء إدخال سعر يومي صحيح.', failedToAdd: 'فشل إضافة المركبة. يرجى التحقق من جميع الحقول.', make: 'الماركة *', selectMake: 'اختر الماركة…', other: 'أخرى…', typeMake: 'اكتب الماركة…', model: 'الطراز *', selectModel: 'اختر الطراز…', typeModel: 'اكتب الطراز…', year: 'السنة *', category: 'الفئة', dailyRate: 'السعر اليومي (درهم) *', licensePlate: 'رقم اللوحة *', color: 'اللون', select: 'اختر…', typeColor: 'اكتب اللون…', seats: 'المقاعد', transmission: 'ناقل الحركة', manual: 'يدوي', automatic: 'أوتوماتيكي', fuelType: 'نوع الوقود', photosLabel: 'الصور (حتى 10)', clickToAddPhotos: 'انقر لإضافة صور', addMorePhotos: 'إضافة المزيد من الصور', cancel: 'إلغاء', adding: 'جارٍ الإضافة…', addVehicle: 'إضافة المركبة', heading: 'الأسطول', vehicleCount: (n) => `${n} مركبة إجمالاً`, searchPlaceholder: 'بحث عن الماركة، الطراز، اللوحة…', categoryLabels: { ECONOMY: 'اقتصادية', COMPACT: 'مدمجة', MIDSIZE: 'متوسطة', FULLSIZE: 'كبيرة', SUV: 'دفع رباعي', LUXURY: 'فاخرة', VAN: 'فان', TRUCK: 'شاحنة', }, fuelTypeLabels: { GASOLINE: 'بنزين', DIESEL: 'ديزل', ELECTRIC: 'كهربائي', HYBRID: 'هجين', }, allStatuses: 'جميع الحالات', allCategories: 'جميع الفئات', allVisibility: 'جميع الرؤية', published: 'منشور', unpublished: 'غير منشور', colVehicle: 'المركبة', colCategory: 'الفئة', colStatus: 'الحالة', colDailyRate: 'السعر اليومي', colPublished: 'النشر', colActions: 'الإجراءات', noPhoto: 'لا توجد صورة', perDay: '/يوم', noVehicles: 'لا توجد مركبات. أضف مركبتك الأولى للبدء.', noMatch: 'لا توجد مركبات تطابق المرشحات.', }, vehicleDetail: { tabDetails: 'التفاصيل', tabMaintenance: 'الصيانة', tabCalendar: 'التقويم', editBtn: 'تعديل', cancelBtn: 'إلغاء', saveBtn: 'حفظ', savingBtn: 'جارٍ الحفظ…', uploadingPhotosBtn: 'جارٍ رفع الصور…', loadingVehicle: 'جارٍ تحميل المركبة…', photosHeading: 'الصور', noPhotosYet: 'لم يتم إضافة أي صور.', clickToAddPhotos: 'انقر لإضافة صور', addMorePhotos: (n) => `إضافة المزيد من الصور (${n}/10)`, maxPhotosReached: 'تم الوصول إلى الحد الأقصى (10 صور).', newPhotoBadge: 'جديد', vehicleDetailsHeading: 'تفاصيل المركبة', labelCategory: 'الفئة', labelDailyRate: 'السعر اليومي', labelStatus: 'الحالة', labelSeats: 'المقاعد', labelTransmission: 'ناقل الحركة', labelFuelType: 'نوع الوقود', labelColor: 'اللون', labelOdometer: 'عداد المسافة', labelNotes: 'الملاحظات', makeLabel: 'الماركة *', selectMake: 'اختر الماركة…', other: 'أخرى…', typeMake: 'اكتب الماركة…', modelLabel: 'الطراز *', selectModel: 'اختر الطراز…', typeModel: 'اكتب الطراز…', yearLabel: 'السنة *', dailyRateLabel: 'السعر اليومي (درهم) *', licensePlateLabel: 'رقم اللوحة *', statusLabel: 'الحالة', colorLabel: 'اللون', selectColor: 'اختر اللون…', typeColor: 'اكتب اللون…', seatsLabel: 'المقاعد', transmissionLabel: 'ناقل الحركة', fuelTypeLabel: 'نوع الوقود', odometerLabel: 'عداد المسافة (كم)', notesLabel: 'الملاحظات', optionalNotes: 'ملاحظات اختيارية…', optional: 'اختياري…', makeRequired: 'الماركة مطلوبة.', modelRequired: 'الطراز مطلوب.', plateRequired: 'رقم اللوحة مطلوب.', rateInvalid: 'يرجى إدخال سعر يومي صحيح.', failedSave: 'فشل الحفظ.', maintenanceIntro: 'تتبع صيانة هذه المركبة. انقر على سجّل لأي عنصر لتسجيل الخدمة.', logBtn: 'سجّل', statusNone: 'غير مسجل', statusOverdue: 'متأخر', statusDueSoon: 'قريباً', statusOk: 'محدّث', noRecord: 'لا يوجد سجل', lastPrefix: 'آخر:', atKm: (km) => `عند ${km} كم`, duePrefix: '· الموعد:', orSep: 'أو', kmLeft: (km) => `${km} كم متبقية`, overdueByKm: 'تجاوز المسافة', serviceDateLabel: 'تاريخ الخدمة *', odometerAtService: 'قراءة العداد عند الخدمة (كم)', nextDueDateLabel: 'تاريخ الخدمة القادمة', nextDueKmLabel: 'المسافة عند الخدمة القادمة (كم)', costLabel: 'التكلفة (درهم)', maintenanceNotesLabel: 'الملاحظات', saveLabel: 'حفظ', savingLabel: 'جارٍ الحفظ…', dateRequired: 'تاريخ الخدمة مطلوب.', routineTypes: { 'Oil Change': 'تغيير الزيت', 'Technical Inspection': 'الفحص التقني', 'Vehicle Registration': 'تسجيل المركبة', 'Car Tax': 'الضريبة السنوية', 'Tire Rotation': 'تدوير الإطارات', 'Brake Inspection': 'فحص الفرامل', 'Air Filter': 'فلتر الهواء', 'Cabin Filter': 'فلتر المقصورة', 'Battery Check': 'فحص البطارية', 'Belt / Chain Service': 'خدمة السير / السلسلة', 'Coolant Flush': 'تغيير سائل التبريد', 'Transmission Service': 'خدمة ناقل الحركة', }, }, calendar: { heading: 'تقويم التوفر', blockDates: 'حجب التواريخ', clickSecondDay: (selected) => `انقر على يوم ثانٍ لتحديد نهاية الحجب (المختار: ${selected})`, cancelSelection: 'إلغاء', legendReserved: 'محجوز', legendMaintenance: 'صيانة', legendBlocked: 'محجوب', legendAvailable: 'متاح', failedLoad: 'فشل تحميل التقويم', moreDays: (n) => `+${n} أكثر`, reservationsThisMonth: 'الحجوزات هذا الشهر', blocksAndMaintenance: 'الحجب والصيانة', removeBlockConfirm: 'حذف هذا الحجب؟', failedDeleteBlock: 'فشل حذف الحجب', modalTitle: 'حجب تواريخ المركبة', blockTypeLabel: 'نوع الحجب', manualBlock: 'حجب يدوي', maintenanceBlock: 'صيانة', startDate: 'تاريخ البداية', endDate: 'تاريخ النهاية (غير شامل)', reasonLabel: 'السبب', optional: '(اختياري)', reasonPlaceholderManual: 'مثال: محجوز لشخص مميز، فعالية…', reasonPlaceholderMaintenance: 'مثال: تغيير الزيت، تدوير الإطارات…', datesRequired: 'تواريخ البداية والنهاية مطلوبة.', endAfterStart: 'يجب أن يكون تاريخ النهاية بعد تاريخ البداية.', failedCreateBlock: 'فشل إنشاء الحجب', cancel: 'إلغاء', saveBlock: 'حفظ', saving: 'جارٍ الحفظ…', removeBlock: 'حذف الحجب', dayHeaders: ['أحد', 'اثن', 'ثلا', 'أرب', 'خمي', 'جمع', 'سبت'], }, dashboardPage: { failedToLoad: 'فشل تحميل لوحة التحكم', forbidden: 'ليس لديك صلاحية الوصول إلى هذه الصفحة. يلزم دور المدير أو أعلى.', kpiTotalBookings: 'إجمالي الحجوزات', kpiActiveVehicles: 'المركبات النشطة', kpiMonthlyRevenue: 'الإيرادات الشهرية', kpiTotalCustomers: 'إجمالي العملاء', vsLastMonth: 'مقارنة بالشهر الماضي', bookingSources: 'مصادر الحجز', barBookings: 'الحجوزات', barRevenue: 'الإيرادات (درهم)', noBookingData: 'لا توجد بيانات حجز متاحة بعد', quickStats: 'إحصائيات سريعة', noData: 'لا توجد بيانات', recentReservations: 'الحجوزات الأخيرة', viewAll: 'عرض الكل ←', colBookingNum: 'الحجز #', colCustomer: 'العميل', colVehicle: 'المركبة', colDates: 'التواريخ', colStatus: 'الحالة', colTotal: 'الإجمالي', noReservations: 'لا توجد حجوزات بعد. ستظهر الحجوزات هنا عند قيام العملاء بالحجز.', trialEnds: (date) => `أنت في فترة تجريبية. تنتهي في ${date}.`, trialActive: 'أنت في فترة تجريبية مجانية.', pastDueMsg: 'دفعتك متأخرة. يرجى تحديث معلومات الفوترة.', suspendedMsg: 'تم تعليق حسابك. يرجى التواصل مع الدعم أو تجديد اشتراكك.', manageBilling: 'إدارة الاشتراك ←', statusLabels: { CONFIRMED: 'مؤكد', PENDING: 'قيد الانتظار', ACTIVE: 'نشط', COMPLETED: 'مكتمل', CANCELLED: 'ملغى' }, }, reservations: { heading: 'الحجوزات', subtitle: 'جميع مصادر الحجز: لوحة التحكم، الموقع العام، والسوق الإلكتروني.', colCustomer: 'العميل', colVehicle: 'المركبة', colDates: 'التواريخ', colSource: 'المصدر', colStatus: 'الحالة', colTotal: 'الإجمالي', noReservations: 'لا توجد حجوزات بعد.', loadingReservation: 'جارٍ تحميل الحجز…', failedToLoad: 'فشل تحميل الحجز', confirmReservation: 'تأكيد الحجز', checkInVehicle: 'تسجيل الاستلام', checkOutVehicle: 'تسجيل الإرجاع', working: 'جارٍ التنفيذ…', failedConfirm: 'فشل تأكيد الحجز', failedCheckIn: 'فشل تسجيل الاستلام', failedCheckOut: 'فشل تسجيل الإرجاع', sectionCustomer: 'العميل', noPhone: 'لم يُدخل رقم هاتف', licenseLabel: 'الرخصة:', notCaptured: 'غير مُسجّل', flaggedBadge: 'مُبلَّغ عنه', sectionVehicle: 'المركبة', sectionCharges: 'الرسوم', chargeDiscount: 'الخصم', chargeInsurance: 'التأمين', chargeAdditionalDrivers: 'السائقون الإضافيون', chargePricingAdjustments: 'تعديلات التسعير', chargeGrandTotal: 'الإجمالي الكلي', appliedInsurance: 'التأمين المطبق', pricingRulesApplied: 'قواعد التسعير المطبقة', sectionAdditionalDrivers: 'السائقون الإضافيون', driverLicenseLabel: 'الرخصة:', driverChargeLabel: 'الرسوم:', approveBtn: 'موافقة', approvedBadge: 'مُوافَق عليه', noApprovalNeeded: 'لا يحتاج موافقة', noAdditionalDrivers: 'لم يتم إضافة سائقين إضافيين لهذا الحجز.', failedApproveDriver: 'فشلت الموافقة على السائق الإضافي', sectionInspectionSummary: 'ملخص الفحص', checkInInspectionLabel: 'فحص الاستلام', checkOutInspectionLabel: 'فحص الإرجاع', savedBadge: 'محفوظ', pendingBadge: 'معلّق', inspectionCardTitle: (type) => type === 'CHECKIN' ? 'فحص الاستلام' : 'فحص الإرجاع', inspectionSubtitle: 'حدد الأضرار الموجودة والمكتشفة حديثاً مباشرة على مخطط المركبة.', saveInspection: 'حفظ الفحص', savingInspection: 'جارٍ الحفظ…', failedSaveInspection: 'فشل حفظ الفحص', diagramHelp: 'انقر في أي مكان على المخطط لإضافة علامة ضرر بالنوع والخطورة المحددين.', mileageLabel: 'عداد المسافة', fuelLevelLabel: 'مستوى الوقود', fuelLevels: { FULL: 'ممتلئ', SEVEN_EIGHTHS: '7/8', THREE_QUARTERS: '3/4', FIVE_EIGHTHS: '5/8', HALF: '1/2', THREE_EIGHTHS: '3/8', QUARTER: '1/4', ONE_EIGHTH: '1/8', EMPTY: 'فارغ' }, generalConditionLabel: 'الحالة العامة', employeeNotesLabel: 'ملاحظات الموظف', customerAcknowledged: 'وافق العميل على هذا الفحص', damageMarkersHeading: 'علامات الضرر', removeMarker: 'حذف', noMarkers: 'لا توجد علامات ضرر محفوظة.', damageTypeLabels: { SCRATCH: 'خدش', DENT: 'انبعاج', CRACK: 'تشقق', CHIP: 'تقشر', MISSING: 'مفقود', STAIN: 'بقعة', OTHER: 'أخرى' }, severityLabels: { MINOR: 'طفيف', MODERATE: 'متوسط', MAJOR: 'شديد' }, }, }, } type I18nContextValue = { language: DashboardLanguage setLanguage: (value: DashboardLanguage) => void theme: DashboardTheme setTheme: (value: DashboardTheme) => void dict: DashboardDictionary } const I18nContext = createContext(null) const DASHBOARD_LANGUAGE_KEY = 'dashboard-language' function normalizeLanguage(value: string | null | undefined): DashboardLanguage { return value === 'fr' || value === 'ar' || value === 'en' ? value : 'en' } function detectBrowserLanguage(): DashboardLanguage | null { if (typeof navigator === 'undefined') return null const langs = Array.from(navigator.languages ?? [navigator.language]) for (const lang of langs) { const code = lang.split('-')[0].toLowerCase() if (code === 'fr' || code === 'ar' || code === 'en') return code as DashboardLanguage } return null } export function DashboardI18nProvider({ children, initialLanguage = 'en', }: { children: React.ReactNode initialLanguage?: DashboardLanguage }) { const [language, setLanguageState] = useState(initialLanguage) const [theme, setThemeState] = useState('light') const themeInitialized = useRef(false) // Skip the very first write so we don't overwrite a stored preference with the // server-resolved initialLanguage before the read effect has a chance to apply it. const skipFirstLangWrite = useRef(true) function applyLanguage(nextLanguage: DashboardLanguage) { if (nextLanguage === language) return if (typeof document !== 'undefined') { document.documentElement.lang = nextLanguage document.documentElement.dir = nextLanguage === 'ar' ? 'rtl' : 'ltr' writeScopedPreference(SHARED_LANGUAGE_KEY, nextLanguage, [DASHBOARD_LANGUAGE_KEY]) document.cookie = `${SHARED_LANGUAGE_COOKIE}=${nextLanguage}; path=/; max-age=31536000; samesite=lax` document.cookie = `${DASHBOARD_LANGUAGE_KEY}=${nextLanguage}; path=/; max-age=31536000; samesite=lax` } setLanguageState(nextLanguage) } function applyTheme(nextTheme: DashboardTheme) { if (nextTheme === theme) return if (typeof document !== 'undefined') { document.documentElement.classList.remove('light', 'dark') document.documentElement.classList.add(nextTheme === 'light' ? 'light' : 'dark') document.documentElement.style.colorScheme = nextTheme === 'light' ? 'light' : 'dark' document.body.dataset.theme = nextTheme writeScopedPreference(SHARED_THEME_KEY, nextTheme, ['dashboard-theme']) } setThemeState(nextTheme) } useEffect(() => { const stored = readScopedPreference(SHARED_LANGUAGE_KEY, [DASHBOARD_LANGUAGE_KEY]) if (stored !== null) { const next = normalizeLanguage(stored) if (next !== language) setLanguageState(next) } else { const detected = detectBrowserLanguage() if (detected && detected !== language) setLanguageState(detected) } }, []) useEffect(() => { const storedTheme = readScopedPreference(SHARED_THEME_KEY, ['dashboard-theme']) if (storedTheme === 'light' || storedTheme === 'dark') { if (storedTheme !== theme) setThemeState(storedTheme) return } if (window.matchMedia('(prefers-color-scheme: dark)').matches && theme !== 'dark') { setThemeState('dark') } }, []) useEffect(() => { document.documentElement.lang = language document.documentElement.dir = language === 'ar' ? 'rtl' : 'ltr' if (skipFirstLangWrite.current) { skipFirstLangWrite.current = false return } writeScopedPreference(SHARED_LANGUAGE_KEY, language, [DASHBOARD_LANGUAGE_KEY]) document.cookie = `${SHARED_LANGUAGE_COOKIE}=${language}; path=/; max-age=31536000; samesite=lax` document.cookie = `${DASHBOARD_LANGUAGE_KEY}=${language}; path=/; max-age=31536000; samesite=lax` }, [language]) useEffect(() => { if (!themeInitialized.current) { // Skip first run: inline script already applied the correct class; writing 'light' here would flash and corrupt storage. themeInitialized.current = true return } document.documentElement.classList.remove('light', 'dark') document.documentElement.classList.add(theme === 'light' ? 'light' : 'dark') document.documentElement.style.colorScheme = theme === 'light' ? 'light' : 'dark' document.body.dataset.theme = theme writeScopedPreference(SHARED_THEME_KEY, theme, ['dashboard-theme']) }, [theme]) useEffect(() => { function handleAuthChanged() { const scopedLanguage = readCurrentUserScopedPreference(SHARED_LANGUAGE_KEY) const scopedTheme = readCurrentUserScopedPreference(SHARED_THEME_KEY) if (scopedLanguage) { // Employee has a stored preference — apply it. const next = normalizeLanguage(scopedLanguage) if (next !== language) setLanguageState(next) } else { // No scoped pref yet (new login). Promote any base preference that was set // before login (e.g., language selected on the sign-up page) to the scoped key. const baseLang = readScopedPreference(SHARED_LANGUAGE_KEY, [DASHBOARD_LANGUAGE_KEY]) const langToWrite = baseLang ? normalizeLanguage(baseLang) : language if (langToWrite !== language) setLanguageState(langToWrite) writeScopedPreference(SHARED_LANGUAGE_KEY, langToWrite, [DASHBOARD_LANGUAGE_KEY]) } if (scopedTheme === 'light' || scopedTheme === 'dark') { if (scopedTheme !== theme) setThemeState(scopedTheme) } else { writeScopedPreference(SHARED_THEME_KEY, theme, ['dashboard-theme']) } } window.addEventListener('rentaldrivego:auth-changed', handleAuthChanged as EventListener) return () => window.removeEventListener('rentaldrivego:auth-changed', handleAuthChanged as EventListener) }, [language, theme]) const value = useMemo( () => ({ language, setLanguage: applyLanguage, theme, setTheme: applyTheme, dict: dictionaries[language] }), [language, theme], ) return {children} } export function useDashboardI18n() { const context = useContext(I18nContext) if (!context) throw new Error('useDashboardI18n must be used within DashboardI18nProvider') return context } export function DashboardLanguageSwitcher() { const { language, setLanguage, dict } = useDashboardI18n() const [embedded, setEmbedded] = useState(false) useEffect(() => { setEmbedded(window.self !== window.top) }, []) if (embedded) return null function handleLanguageChange(value: DashboardLanguage) { setLanguage(value) apiFetch('/auth/employee/me/language', { method: 'PATCH', body: JSON.stringify({ language: value }), }).catch(() => {}) } return (
{dict.language} {(['en', 'fr', 'ar'] as DashboardLanguage[]).map((value) => { const active = value === language return ( ) })}
) } export function DashboardThemeSwitcher() { const { theme, setTheme, dict } = useDashboardI18n() const [embedded, setEmbedded] = useState(false) useEffect(() => { setEmbedded(window.self !== window.top) }, []) if (embedded) return null return (
{dict.theme} {(['light', 'dark'] as DashboardTheme[]).map((value) => { const active = value === theme return ( ) })}
) }