/** * Translates raw backend error messages (Symfony, Doctrine, API Platform) * into user-friendly French messages for display in toasts/alerts. */ const EXACT_MATCHES: Record = { // UniqueConstraintSubscriber (HTTP 409) 'nom duplique': 'Un élément avec ce nom existe déjà.', // English backend messages → French 'Machine not found.': 'Machine introuvable.', 'Composant not found.': 'Composant introuvable.', 'Piece not found.': 'Pièce introuvable.', 'Product not found.': 'Produit introuvable.', 'Site not found.': 'Site introuvable.', 'Custom field not found.': 'Champ personnalisé introuvable.', 'Custom field value not found.': 'Valeur du champ personnalisé introuvable.', 'Document not found.': 'Document introuvable.', 'File not found on disk.': 'Le fichier n\'a pas été trouvé sur le serveur.', 'Invalid document data.': 'Les données du document sont invalides.', 'Invalid JSON payload.': 'Les données envoyées sont invalides.', 'Unsupported entity type.': 'Type d\'entité non supporté.', 'Entity target is missing.': 'La cible de l\'entité est manquante.', 'customFieldId or customFieldName is required.': 'L\'identifiant du champ personnalisé est requis.', // Symfony validator messages 'This value should not be blank.': 'Ce champ ne peut pas être vide.', 'This value is not a valid email address.': 'L\'adresse email n\'est pas valide.', 'This value is already used.': 'Cette valeur est déjà utilisée.', 'This field is missing.': 'Un champ obligatoire est manquant.', // HTTP status texts (used in "Erreur XXX: StatusText" fallback) 'Internal Server Error': 'Erreur interne du serveur. Veuillez réessayer.', 'Bad Request': 'Requête invalide.', 'Not Found': 'Ressource introuvable.', 'Conflict': 'Un élément similaire existe déjà.', 'Unprocessable Entity': 'Données invalides.', 'Unprocessable Content': 'Données invalides.', 'Service Unavailable': 'Service temporairement indisponible. Veuillez réessayer.', 'Gateway Timeout': 'Le serveur met trop de temps à répondre. Veuillez réessayer.', } const TECHNICAL_PATTERNS: Array<[RegExp, string]> = [ // Database / Doctrine errors [/SQLSTATE\[/i, 'Une erreur est survenue. Veuillez réessayer.'], [/An exception occurred/i, 'Une erreur est survenue. Veuillez réessayer.'], [/Duplicate entry/i, 'Un élément avec ces données existe déjà.'], [/unique.*constraint.*violation/i, 'Un élément avec ces données existe déjà.'], [/foreign key constraint/i, 'Impossible de supprimer cet élément car il est utilisé ailleurs.'], [/violates not-null constraint/i, 'Un champ obligatoire n\'a pas été renseigné.'], [/violates check constraint/i, 'Une valeur saisie est invalide.'], // Symfony / API Platform internal messages [/Expected argument of type/i, 'Les données envoyées sont invalides.'], [/Could not denormalize/i, 'Les données envoyées sont invalides.'], [/The JSON value could not be decoded/i, 'Les données envoyées sont invalides.'], [/Syntax error.*JSON/i, 'Les données envoyées sont invalides.'], [/No route found/i, 'Ressource introuvable.'], [/Access Denied/i, 'Permissions insuffisantes pour cette action.'], ] /** * Detects if a message contains technical jargon that should not be shown to users. */ function containsTechnicalJargon(message: string): boolean { const patterns = [ /stack trace/i, /exception/i, /\bat\s+[\w\\]+::/, /vendor\//, /\.php/, /doctrine/i, /symfony/i, /SQLSTATE/i, /PDOException/i, /DBALException/i, /RuntimeException/i, /TypeError/i, /LogicException/i, /InvalidArgumentException/i, /UnexpectedValueException/i, /constraint.*violation/i, /entity.*manager/i, /Hydra error/i, ] return patterns.some((p) => p.test(message)) } /** * Translates a raw backend error message into a user-friendly French message. * * Usage: * import { humanizeError } from '~/shared/utils/errorMessages' * showError(humanizeError(rawMessage)) */ export function humanizeError(rawMessage: string | undefined | null): string { if (!rawMessage) return 'Une erreur est survenue.' const trimmed = rawMessage.trim() if (!trimmed) return 'Une erreur est survenue.' // 1. Exact match if (EXACT_MATCHES[trimmed]) return EXACT_MATCHES[trimmed] // 2. "Erreur XXX: StatusText" pattern — translate the status text const httpMatch = trimmed.match(/^Erreur (\d{3})\s*:\s*(.+)$/) if (httpMatch) { const statusText = httpMatch[2]!.trim() if (EXACT_MATCHES[statusText]) return EXACT_MATCHES[statusText] return `Erreur serveur (${httpMatch[1]}). Veuillez réessayer.` } // 3. Regex patterns for technical errors for (const [pattern, replacement] of TECHNICAL_PATTERNS) { if (pattern.test(trimmed)) return replacement } // 4. If it contains technical jargon, replace with generic message if (containsTechnicalJargon(trimmed)) { return 'Une erreur est survenue. Veuillez réessayer.' } // 5. Already user-friendly — return as-is return trimmed } /** * Extracts the best error message from various backend response formats. * Handles: { message }, { error }, { detail }, { "hydra:description" } */ export function extractApiErrorMessage(errorData: Record): string | null { if (!errorData || typeof errorData !== 'object') return null // Symfony validator violations — priorité max (message propre sans préfixe champ) if (Array.isArray(errorData.violations) && errorData.violations.length > 0) { const first = errorData.violations[0] as Record if (typeof first?.message === 'string') return first.message } // UniqueConstraintSubscriber format ({ success: false, error: "nom duplique" }) if (typeof errorData.error === 'string') return errorData.error // Custom controllers format if (typeof errorData.message === 'string') return errorData.message if (Array.isArray(errorData.message) && typeof errorData.message[0] === 'string') return errorData.message[0] // API Platform hydra format (fallback — peut contenir "propertyPath: message") if (typeof errorData['hydra:description'] === 'string') return errorData['hydra:description'] if (typeof errorData.detail === 'string') return errorData.detail return null }