feat: centralize API error localization
Replace the err instanceof ApiError ? err.message : t(fallback) anti-
pattern -- which showed raw English backend text for the common case and
only used the localized fallback for the rare network-failure case -- at
all 13 call sites across 7 files.
New frontend/src/api/errorMessages.ts (describeApiError) resolves a
caught error to a localized {title, explanation, nextStep?, technical}
by checking the 32 known AppError codes first, then known HTTP statuses
(401/403/404/409/422/500), then a fully generic fallback. New
ApiErrorNotice (PageChrome.tsx) renders title/explanation/nextStep with
the raw text demoted to a "Technical details"/"Details techniques"
disclosure -- never shown as the primary message.
ApiError itself is split out of client.ts into a standalone
api/apiError.ts with no import.meta.env dependency, so errorMessages.ts
(and its tests) can be loaded outside a Vite/browser context.
This commit is contained in:
@@ -1,8 +1,180 @@
|
||||
{
|
||||
"generic": "Une erreur est survenue. Veuillez réessayer.",
|
||||
"workspaceLoadFailed": "Nous n'avons pas pu charger cet espace de travail.",
|
||||
"unauthorized": "Votre session a expiré. Veuillez vous reconnecter.",
|
||||
"forbidden": "Vous n'avez pas accès à cette section.",
|
||||
"notFound": "Cette fiche est introuvable.",
|
||||
"networkUnavailable": "La connexion au serveur est actuellement indisponible."
|
||||
"generic": {
|
||||
"title": "Une erreur s'est produite",
|
||||
"explanation": "Cette action n'a pas pu être terminée. Veuillez réessayer."
|
||||
},
|
||||
"codes": {
|
||||
"VEHICLE_NOT_FOUND": {
|
||||
"title": "Véhicule introuvable",
|
||||
"explanation": "Ce véhicule est introuvable. Il a peut-être été supprimé, ou la référence est incorrecte."
|
||||
},
|
||||
"BOOKING_NOT_FOUND": {
|
||||
"title": "Réservation introuvable",
|
||||
"explanation": "Cette réservation est introuvable. Elle a peut-être été supprimée, ou la référence est incorrecte."
|
||||
},
|
||||
"CUSTOMER_NOT_FOUND": {
|
||||
"title": "Client introuvable",
|
||||
"explanation": "Cette fiche client est introuvable."
|
||||
},
|
||||
"ENTITY_NOT_FOUND": {
|
||||
"title": "Fiche introuvable",
|
||||
"explanation": "La fiche sous-jacente à cette action est introuvable."
|
||||
},
|
||||
"EVENT_NOT_FOUND": {
|
||||
"title": "Tâche d'automatisation introuvable",
|
||||
"explanation": "Cet événement d'automatisation est introuvable."
|
||||
},
|
||||
"ISSUE_NOT_FOUND": {
|
||||
"title": "Problème de qualité des données introuvable",
|
||||
"explanation": "Ce problème de qualité des données est introuvable."
|
||||
},
|
||||
"ISSUE_NOT_OPEN": {
|
||||
"title": "Le problème n'est plus ouvert",
|
||||
"explanation": "Ce problème a déjà été résolu, reporté ou rejeté.",
|
||||
"nextStep": "Actualisez la page pour voir son état actuel."
|
||||
},
|
||||
"BOOKING_NOT_ACTIVE": {
|
||||
"title": "La réservation n'est pas active",
|
||||
"explanation": "Seule une réservation réservée ou active peut être utilisée pour cette action."
|
||||
},
|
||||
"INVALID_BOOKING_STATE": {
|
||||
"title": "La réservation est dans le mauvais état",
|
||||
"explanation": "L'état actuel de cette réservation ne permet pas cette action."
|
||||
},
|
||||
"NOT_RETRYABLE": {
|
||||
"title": "Cette tâche ne peut pas être relancée",
|
||||
"explanation": "Seule une livraison échouée peut être relancée."
|
||||
},
|
||||
"CONFLICT_STILL_PRESENT": {
|
||||
"title": "Le conflit n'a pas été résolu",
|
||||
"explanation": "L'application de ce changement n'a pas résolu le conflit sous-jacent.",
|
||||
"nextStep": "Consultez à nouveau la recommandation avant de réessayer."
|
||||
},
|
||||
"OVERLAP_STILL_PRESENT": {
|
||||
"title": "Le chevauchement n'a pas été résolu",
|
||||
"explanation": "Le blocage de cette réservation n'a pas supprimé le chevauchement ; un autre engagement subsiste."
|
||||
},
|
||||
"EMPTY_VALUE": {
|
||||
"title": "Un champ requis est vide",
|
||||
"explanation": "Ce champ ne peut pas rester vide.",
|
||||
"nextStep": "Saisissez une valeur et réessayez."
|
||||
},
|
||||
"NO_FIELDS_PROVIDED": {
|
||||
"title": "Aucune modification fournie",
|
||||
"explanation": "Au moins un champ doit être complété pour continuer."
|
||||
},
|
||||
"INVALID_FIELD": {
|
||||
"title": "Champ non autorisé ici",
|
||||
"explanation": "L'un des champs fournis n'est pas autorisé pour cette action."
|
||||
},
|
||||
"INVALID_FIELD_OVERRIDE": {
|
||||
"title": "Ce champ ne peut pas être fusionné",
|
||||
"explanation": "L'un des champs sélectionnés ne peut pas être utilisé dans cette fusion."
|
||||
},
|
||||
"INVALID_SURVIVOR": {
|
||||
"title": "Sélection non valide",
|
||||
"explanation": "La fiche que vous souhaitez conserver doit être l'une des deux fiches comparées."
|
||||
},
|
||||
"INVALID_BOOKING_REFERENCE": {
|
||||
"title": "Référence de réservation non valide ici",
|
||||
"explanation": "La réservation sélectionnée ne fait pas partie des réservations liées à ce problème."
|
||||
},
|
||||
"INVALID_EVENT_ID": {
|
||||
"title": "Référence d'événement non valide",
|
||||
"explanation": "Cette référence d'événement d'automatisation n'est pas valide."
|
||||
},
|
||||
"INVALID_IDEMPOTENCY_KEY": {
|
||||
"title": "La demande n'a pas pu être répétée en toute sécurité",
|
||||
"explanation": "La clé de suivi de cette demande n'est pas valide.",
|
||||
"nextStep": "Rechargez la page et réessayez."
|
||||
},
|
||||
"IDEMPOTENCY_KEY_REUSED": {
|
||||
"title": "Cette action a déjà été soumise",
|
||||
"explanation": "Une demande identique a déjà été traitée, avec un résultat différent.",
|
||||
"nextStep": "Rechargez la page pour voir l'état actuel avant de réessayer."
|
||||
},
|
||||
"CORRECTED_VALUE_REQUIRED": {
|
||||
"title": "Une valeur corrigée est requise",
|
||||
"explanation": "Le choix « corriger le relevé » nécessite de saisir la valeur corrigée."
|
||||
},
|
||||
"CORRECTION_BELOW_CANONICAL": {
|
||||
"title": "La correction est inférieure au relevé confirmé",
|
||||
"explanation": "Un kilométrage corrigé ne peut jamais être inférieur au dernier relevé confirmé."
|
||||
},
|
||||
"NOT_A_DUPLICATE_ISSUE": {
|
||||
"title": "Type de problème incorrect",
|
||||
"explanation": "Cette action s'applique uniquement aux problèmes de client potentiellement en double."
|
||||
},
|
||||
"NOT_A_MISSING_FIELD_ISSUE": {
|
||||
"title": "Type de problème incorrect",
|
||||
"explanation": "Cette action s'applique uniquement aux problèmes de champ obligatoire manquant."
|
||||
},
|
||||
"NOT_AN_ODOMETER_ISSUE": {
|
||||
"title": "Type de problème incorrect",
|
||||
"explanation": "Cette action s'applique uniquement aux problèmes d'anomalie de kilométrage."
|
||||
},
|
||||
"NOT_AN_OVERLAP_ISSUE": {
|
||||
"title": "Type de problème incorrect",
|
||||
"explanation": "Cette action s'applique uniquement aux problèmes de chevauchement de réservations."
|
||||
},
|
||||
"NOT_A_STATUS_CONFLICT_ISSUE": {
|
||||
"title": "Type de problème incorrect",
|
||||
"explanation": "Cette action s'applique uniquement aux problèmes de conflit de statut du véhicule."
|
||||
},
|
||||
"UNSUPPORTED_ENTITY": {
|
||||
"title": "Non pris en charge pour ce type de fiche",
|
||||
"explanation": "Cette action n'est pas disponible pour ce type de fiche."
|
||||
},
|
||||
"MANUAL_REVIEW_REQUIRED": {
|
||||
"title": "Évaluation manuelle requise",
|
||||
"explanation": "Les faits concernant ce véhicule se contredisent, ce qui rend tout changement automatique non sûr.",
|
||||
"nextStep": "Reportez ou rejetez ce problème, ou examinez-le manuellement."
|
||||
},
|
||||
"NO_CONFLICT_DETECTED": {
|
||||
"title": "Rien à appliquer",
|
||||
"explanation": "Le statut actuel correspond déjà aux faits, il n'y a donc plus rien à appliquer."
|
||||
},
|
||||
"RECOMMENDATION_STALE": {
|
||||
"title": "La situation a changé entre-temps",
|
||||
"explanation": "Les faits sous-jacents ont changé depuis l'affichage de cette recommandation.",
|
||||
"nextStep": "Consultez à nouveau la recommandation avant de l'appliquer."
|
||||
},
|
||||
"UNAUTHORIZED_SERVICE": {
|
||||
"title": "Échec de l'autorisation du service",
|
||||
"explanation": "Cette demande automatisée n'a pas pu être autorisée."
|
||||
}
|
||||
},
|
||||
"http": {
|
||||
"401": {
|
||||
"title": "Session expirée",
|
||||
"explanation": "Votre session a expiré.",
|
||||
"nextStep": "Veuillez vous reconnecter."
|
||||
},
|
||||
"403": {
|
||||
"title": "Accès refusé",
|
||||
"explanation": "Vous n'avez pas la permission d'effectuer cette action."
|
||||
},
|
||||
"404": {
|
||||
"title": "Introuvable",
|
||||
"explanation": "Cette fiche est introuvable."
|
||||
},
|
||||
"409": {
|
||||
"title": "Cette action n'est plus possible",
|
||||
"explanation": "L'état sous-jacent a changé depuis le chargement de cette page.",
|
||||
"nextStep": "Actualisez la page et réessayez."
|
||||
},
|
||||
"422": {
|
||||
"title": "Échec de la validation",
|
||||
"explanation": "Les informations fournies ne sont pas valides."
|
||||
},
|
||||
"500": {
|
||||
"title": "Erreur serveur",
|
||||
"explanation": "Un problème est survenu de notre côté."
|
||||
},
|
||||
"network": {
|
||||
"title": "Connexion indisponible",
|
||||
"explanation": "La connexion au serveur est actuellement indisponible.",
|
||||
"nextStep": "Vérifiez votre connexion et réessayez."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user