{ "filter.all": "TOUS", "legend.title": "LÉGENDE", "action.close": "Fermer", "action.remove": "Retirer", "action.add": "Ajouter", "app.loading": "Chargement…", "app.error.generic": "Une erreur est survenue", "error.generic": "Erreur inattendue", "error.not_found": "Ressource introuvable", "error.forbidden": "Accès refusé", "error.unauthorized": "Authentification requise", "error.validation_failed": "La validation a échoué (règle bloquante)", "assignment.duplicate": "Ce bénévole est déjà affecté à ce créneau", "assignment.wrong_edition": "Bénévole ou créneau hors de cette édition", "volunteer.duplicate_email": "Un bénévole avec cet email existe déjà", "shift.ends_not_after_starts": "La fin doit être après le début", "shift.position_wrong_edition": "Le poste n'appartient pas à cette édition", "auth.invalid_credentials": "Email ou mot de passe incorrect", "auth.invalid_token": "Ce lien de connexion est invalide ou expiré", "role.coordinator": "Coordination", "role.lead": "Référent", "role.volunteer": "Bénévole", "auth.logout": "Déconnexion", "auth.email": "Email", "auth.password": "Mot de passe", "auth.signIn": "Se connecter", "auth.signingIn": "Connexion…", "auth.admin.title": "Espace coordination", "auth.demoCredentials": "Démo : {email} · mot de passe « {password} »", "auth.notAdmin": "Bénévole ou référent ?", "auth.magicLinkLink": "Recevoir un lien de connexion", "auth.isAdmin": "Vous êtes de la coordination ?", "auth.adminLink": "Connexion par mot de passe", "auth.magic.title": "Connexion bénévole / référent", "auth.magic.lead": "Entrez votre email : vous recevrez un lien de connexion, sans mot de passe.", "auth.magic.send": "Envoyer le lien", "auth.magic.sending": "Envoi…", "auth.magic.sent": "Si un compte correspond à cet email, un lien de connexion vient d'être envoyé.", "auth.magic.devHint": "Démo (email stubé) — cliquez ce lien :", "auth.magic.another": "Utiliser un autre email", "auth.magic.verifyTitle": "Connexion en cours", "auth.magic.verifying": "Vérification du lien…", "auth.magic.retry": "Demander un nouveau lien", "header.title": "GESTION DES BÉNÉVOLES", "header.meta": "BÉNÉVOLAT · {count} INSCRITS", "header.mockMode": "DONNÉES DE DÉMO", "tab.grille": "Grille des besoins", "tab.volunteers": "Vue par bénévole", "tab.mobileVolunteer": "Bénévole · mobile", "tab.mobileLead": "Référent · mobile", "tab.config": "Configuration", "position.category.bar": "Bar", "position.category.ticketing": "Billetterie", "position.category.welcome": "Accueil", "position.category.parking": "Parking", "position.category.cleaning": "Propreté", "position.category.catering": "Catering", "position.category.setup": "Montage", "position.category.stage": "Régie", "position.category.other": "Autre", "validation.SHIFT_OVERLAP": "Se chevauche avec un autre créneau", "validation.SHIFT_OVERLAP.at": "Déjà affecté sur {position} au même moment", "validation.SHIFT_OVERLAP.rule": "Deux créneaux d'un même bénévole se chevauchent.", "validation.OUTSIDE_AVAILABILITY": "Hors des disponibilités déclarées ({gap} non couvertes).", "validation.OUTSIDE_AVAILABILITY.rule": "Le créneau n'est pas entièrement couvert par les disponibilités déclarées.", "validation.DAILY_HOURS_EXCEEDED": "Plus de 8 h sur la journée ({hours})", "validation.DAILY_HOURS_EXCEEDED.rule": "Plus de 8 h 00 d'affectations cumulées sur une même journée.", "validation.INSUFFICIENT_REST": "Moins de 8 h de repos entre deux créneaux ({rest})", "validation.INSUFFICIENT_REST.rule": "Moins de 8 h 00 entre la fin d'un créneau et le début du suivant.", "grille.filter.day": "JOUR", "grille.filter.position": "POSTE", "grille.legend.critical": "SOUS-EFFECTIF CRITIQUE", "grille.legend.incomplete": "INCOMPLET", "grille.legend.complete": "COMPLET", "grille.legend.overstaffed": "SUREFFECTIF", "grille.legend.undefined": "À DÉFINIR", "grille.positionsCount": "{count} POSTES", "grille.assignedRequired": "AFFECTÉS / IDÉAL", "grille.footnote": "Chaque cellule est un créneau. Nombre = bénévoles affectés, / = effectif idéal. Cliquer ouvre le panneau d'affectation à droite.", "grille.empty": "Aucun créneau pour le moment. Créez des postes dans Configuration → « Postes & créneaux » — un créneau est généré par jour et par créneau type.", "grille.panel.empty.title": "Cliquez une cellule de la grille pour affecter des bénévoles.", "grille.panel.empty.body": "Le panneau s'ouvre ici, sans quitter la grille : candidats triés par charge, alertes bloquantes et avertissements du moteur de validation.", "grille.panel.slotDuration": "CRÉNEAU DE {hours} H", "grille.panel.assigned": "AFFECTÉS", "grille.panel.min": "MINIMUM {count}", "grille.panel.ideal": "IDÉAL {count}", "grille.panel.assignmentsTitle": "AFFECTATIONS ({count})", "grille.panel.noneAssigned": "Personne n'est encore affecté sur ce créneau.", "grille.panel.hoursTotal": "{hours} H AU TOTAL", "grille.panel.candidates": "CANDIDATS", "grille.panel.candidatesHint": "LES MOINS CHARGÉS D'ABORD", "grille.panel.hoursAssigned": "{hours} H · {slots} CRÉNEAUX", "grille.panel.assign": "AFFECTER", "grille.panel.blocked": "BLOQUÉ", "grille.panel.blockingPrefix": "BLOQUANT — {reason}", "grille.panel.warningsTitle": "⚠ AVERTISSEMENTS — AFFECTATION POSSIBLE", "volunteers.filter.show": "AFFICHER", "volunteers.filter.all": "TOUS", "volunteers.filter.overloaded": "SURCHARGÉS", "volunteers.filter.unassigned": "SANS AFFECTATION", "volunteers.legend.assigned": "AFFECTATION", "volunteers.count": "{count} BÉNÉVOLES", "volunteers.totalHours": "TOTAL H", "volunteers.name": "NOM", "volunteers.cumulative": "CUMUL", "volunteers.badge.overloaded": "▲ SURCHARGE", "volunteers.badge.unassigned": "○ OUBLIÉ", "volunteers.empty": "Aucun bénévole ne correspond à ce filtre — bonne nouvelle. Repassez sur « TOUS » pour revoir l'ensemble de l'équipe.", "volunteers.noData": "Aucun bénévole pour le moment. Ajoutez-les dans Configuration → « Bénévoles & import ».", "volunteers.footnote": "Surcharge = plus de 8 h sur une même journée ou moins de 8 h entre deux créneaux. Oublié = aucune affectation.", "design.noteTitle": "CHOIX DE CONCEPTION", "mobileVol.statusTime": "9:41", "mobileVol.kicker": "MON PLANNING BÉNÉVOLE", "mobileVol.greeting": "SALUT {name}", "mobileVol.slots": "{count} CRÉNEAUX", "mobileVol.hours": "{count} HEURES", "mobileVol.location": "📍 {location}", "mobileVol.slotAssigned": "✓ TU ES ATTENDU SUR CE CRÉNEAU", "mobileVol.noSlots": "Aucun créneau pour l'instant. La coordination t'affectera bientôt.", "mobileVol.noAccount": "Ce compte n'est rattaché à aucun bénévole. Contactez la coordination.", "mobileVol.detailsUnavailable": "Le détail des créneaux (poste, horaires) n'est pas encore exposé aux bénévoles par l'API.", "mobileVol.designNote": "Lisibilité plein soleil : fonds opaques, texte noir sur jaune ou blanc, aucun gris clair. Cibles tactiles à 52–62 px de haut. Une seule action par créneau. Les données proviennent des affectations réelles de l'API.", "mobileLead.statusTime": "18:07", "mobileLead.kicker": "RÉFÉRENT DE POSTE", "mobileLead.inProgress": "CRÉNEAU · {hours}", "mobileLead.present": "{present}/{total} POINTÉS PRÉSENTS", "mobileLead.absenceAlert": "⚠ {count} ABSENCE(S) SUR LE CRÉNEAU", "mobileLead.requestBackup": "DEMANDER UN RENFORT À LA COORDINATION", "mobileLead.teamTitle": "POINTAGE DE L'ÉQUIPE", "mobileLead.badgePresent": "✓ PRÉSENT", "mobileLead.badgeAbsent": "✕ ABSENT", "mobileLead.markPresent": "PRÉSENT", "mobileLead.markAbsent": "ABSENT", "mobileLead.emptyTeam": "Aucun bénévole affecté à ce créneau.", "mobileLead.designNote": "Le référent ne voit que son poste et un créneau. Le pointage est un geste unique par personne. Il est local à cette maquette : le pointage sur site est hors périmètre (CLAUDE.md §4).", "config.admin": "ADMINISTRATION", "config.reserved": "RÉSERVÉ À LA COORDINATION", "config.tab.grid": "POSTES & CRÉNEAUX", "config.tab.volunteers": "BÉNÉVOLES & IMPORT", "config.tab.mail": "MESSAGES", "config.tab.rules": "RÈGLES & ÉDITION", "config.grid.positionsHeader": "POSTES · {count} CRÉNEAUX", "config.grid.slotsCount": "{count} créneaux", "config.grid.addPosition": "+ AJOUTER UN POSTE", "config.grid.newPositionName": "Nouveau poste", "config.grid.minTotal": "BESOIN MINIMUM · TOTAL", "config.grid.idealTotal": "BESOIN IDÉAL · TOTAL", "config.grid.min": "MIN", "config.grid.ideal": "IDÉAL", "config.grid.emptyDay": "Aucun créneau ce jour.", "config.grid.deletePosition": "SUPPRIMER LE POSTE", "config.grid.deletePositionConfirm": "Supprimer ce poste et tous ses créneaux ?", "config.grid.generateShifts": "GÉNÉRER LES CRÉNEAUX", "config.grid.generated": "{count} créneau(x) ajouté(s).", "config.grid.note": "Effectifs, créneaux, nom et catégorie du poste sont enregistrés immédiatement via l'API. Pour ajouter ou retirer des jours, modifiez les dates de l'édition dans l'onglet « Règles & édition ».", "config.vol.importTitle": "IMPORTER DES BÉNÉVOLES", "config.vol.importHint": "Trois façons d'ajouter des bénévoles : saisie manuelle, import d'un fichier CSV / Excel, ou (à venir) le formulaire d'inscription en ligne.", "config.vol.profiles": "PROFILS ENREGISTRÉS", "config.vol.colName": "NOM", "config.vol.colPhone": "TÉLÉPHONE", "config.vol.colEmail": "EMAIL", "config.vol.ficheKicker": "FICHE BÉNÉVOLE", "config.vol.firstName": "PRÉNOM", "config.vol.lastName": "NOM", "config.vol.phone": "TÉLÉPHONE", "config.vol.email": "EMAIL (LIEN MAGIQUE)", "config.vol.delete": "SUPPRIMER (RGPD — EFFACEMENT COMPLET)", "config.vol.deleteConfirm": "Supprimer définitivement ce bénévole et toutes ses données (disponibilités, affectations) ?", "config.vol.note": "Modifications enregistrées immédiatement via l'API. La suppression est un effacement complet en cascade (RGPD).", "config.mail.smtpBanner": "Envoie un récapitulatif de planning à chaque bénévole. L'email part si le serveur SMTP est configuré (EMAIL_ENABLED) ; sinon il est seulement journalisé.", "config.mail.templateTitle": "MODÈLE DE MESSAGE", "config.mail.senderNote": "Expéditeur défini côté serveur (SMTP_FROM).", "config.mail.subject": "OBJET", "config.mail.body": "CORPS DU MESSAGE", "config.mail.variables": "VARIABLES DISPONIBLES", "config.mail.variablesHint": "Cliquer une variable l'ajoute au corps. {lien_connexion} insère un lien de connexion personnel.", "config.mail.send": "ENVOYER LE RÉCAPITULATIF", "config.mail.sendWithLink": "ENVOYER AVEC LIEN DE CONNEXION", "config.mail.sending": "ENVOI EN COURS…", "config.mail.sent": "Envoi lancé : {queued} email(s), {skipped} bénévole(s) sans adresse.", "config.mail.loginLinkLine": "Accède à ton planning : {lien_connexion}", "config.mail.previewTitle": "APERÇU", "config.mail.previewSender": "DE : expéditeur configuré côté serveur", "config.rules.editionTitle": "ÉDITION", "config.rules.editionName": "Nom", "config.rules.editionStart": "Premier jour", "config.rules.editionEnd": "Dernier jour", "config.rules.editionDays": "{count} jour(s)", "config.rules.editionSave": "ENREGISTRER L'ÉDITION", "config.rules.editionSaved": "Édition enregistrée.", "config.rules.apiMode": "Mode API", "config.rules.volunteersCount": "Bénévoles", "config.rules.positionsCount": "Postes", "config.rules.shiftsCount": "Créneaux", "edition.dates_inconsistent": "La date de fin doit être postérieure ou égale à la date de début.", "day_part.code_required": "Le libellé du créneau type est requis.", "day_part.duplicate_code": "Un créneau type porte déjà ce libellé.", "config.rules.slotsTitle": "CRÉNEAUX TYPES", "config.rules.slotLabel": "Libellé", "config.rules.slotAdd": "AJOUTER", "config.rules.slotsNote": "Ces créneaux sont ajoutés à besoin 0/0 sur chaque jour à la création d'un poste. Après un ajout ici (ou une extension des dates), utilisez « Générer les créneaux » sur le poste.", "config.rules.thresholdsTitle": "MOTEUR DE VALIDATION", "config.rules.maxDaily": "Maximum d'heures par journée", "config.rules.minRest": "Repos minimum entre deux créneaux", "config.rules.thresholdsNote": "Seuils figés côté back-end (CLAUDE.md §3) : non paramétrables. Le double emploi au même créneau est toujours bloquant.", "config.rules.rulesTitle": "RÈGLES APPLIQUÉES", "config.rules.rolesTitle2": "RÔLES ET ACCÈS", "config.rules.roleCoordinator": "Coordination", "config.rules.roleCoordinatorDesc": "Accès complet, construit et publie les plannings", "config.rules.roleLead": "Référent de poste", "config.rules.roleLeadDesc": "Lecture sur l'édition, écriture sur ses postes", "config.rules.roleVolunteer": "Bénévole", "config.rules.roleVolunteerDesc": "Ses seules données, lien magique par email", "config.vol.addButton": "AJOUTER MANUELLEMENT", "config.vol.importButton2": "IMPORTER UN FICHIER", "config.vol.search": "Rechercher un nom, un email…", "config.vol.emptyList": "Aucun bénévole pour l'instant. Ajoutez-en un ou importez un fichier.", "config.vol.noMatch": "Aucun bénévole ne correspond à la recherche.", "config.vol.count": "{count} bénévole(s)", "config.vol.cancel": "ANNULER", "config.vol.field.firstName": "Prénom", "config.vol.field.lastName": "Nom", "config.vol.field.email": "Email (lien magique)", "config.vol.field.phone": "Téléphone", "config.vol.field.comment": "Commentaire", "config.vol.addTitle": "NOUVEAU BÉNÉVOLE", "config.vol.addHint": "Le bénévole reçoit un accès par lien magique uniquement si un email est renseigné.", "config.vol.create": "CRÉER LE BÉNÉVOLE", "config.vol.requiredNames": "Le prénom et le nom sont obligatoires.", "config.vol.wiz.title": "IMPORTER UN FICHIER", "config.vol.wiz.step1": "1 · FICHIER", "config.vol.wiz.step2": "2 · COLONNES", "config.vol.wiz.step3": "3 · APERÇU & IMPORT", "config.vol.wiz.pickFile": "CHOISIR UN FICHIER CSV OU EXCEL", "config.vol.wiz.fileHint": "Une ligne par bénévole. La première ligne contient les intitulés de colonnes. En mode démonstration, seuls les fichiers CSV sont pris en charge.", "config.vol.wiz.analyzing": "Analyse du fichier…", "config.vol.wiz.detected": "{rows} ligne(s) · {cols} colonne(s) détectée(s)", "config.vol.wiz.preview": "APERÇU DES PREMIÈRES LIGNES", "config.vol.wiz.mapTitle": "ASSOCIER LES COLONNES", "config.vol.wiz.mapHint": "« Prénom » et « Nom » sont obligatoires. Laissez « — » pour ignorer une colonne.", "config.vol.wiz.ignore": "—", "config.vol.wiz.mapMissing": "Associez au moins les colonnes « Prénom » et « Nom ».", "config.vol.wiz.simulate": "SIMULER", "config.vol.wiz.runImport": "IMPORTER POUR DE VRAI", "config.vol.wiz.back": "RETOUR", "config.vol.wiz.next": "SUIVANT", "config.vol.wiz.dryRunTitle": "SIMULATION — RIEN N'A ÉTÉ ENREGISTRÉ", "config.vol.wiz.doneTitle": "IMPORT TERMINÉ", "config.vol.wiz.created": "{count} créé(s)", "config.vol.wiz.updated": "{count} mis à jour", "config.vol.wiz.skipped": "{count} ignoré(s)", "config.vol.wiz.rowsWithIssues": "LIGNES AVEC REMARQUES", "config.vol.wiz.line": "Ligne {line}", "config.vol.wiz.noIssues": "Aucune remarque : tout est prêt à être importé.", "config.vol.wiz.close": "FERMER", "import.error.empty_file": "Le fichier est vide.", "import.error.encoding": "L'encodage du fichier n'est pas reconnu.", "import.error.unreadable_file": "Fichier illisible ou format non pris en charge.", "import.error.file_too_large": "Le fichier est trop volumineux (5 Mo maximum).", "import.error.xlsx_demo_only": "En mode démonstration, l'import Excel n'est pas disponible : exportez votre fichier en CSV.", "import.mapping.invalid": "Le paramétrage des colonnes est invalide.", "import.mapping.required_field_missing": "Le champ « {field} » doit être associé à une colonne.", "import.mapping.column_not_found": "La colonne « {column} » est absente du fichier.", "import.row.missing_required_field": "Champ obligatoire manquant : {field}.", "import.row.invalid_email": "Adresse email invalide : {value}.", "import.row.duplicate_email_in_file": "Email déjà présent à la ligne {first_line} du fichier.", "import.row.duplicate_name_in_file": "Nom et prénom déjà présents à la ligne {first_line} du fichier.", "import.row.updated_existing": "Bénévole existant mis à jour.", "import.row.possible_duplicate_name": "Un bénévole portant le même nom existe déjà dans l’édition.", "config.tab.artists": "PROGRAMMATION", "config.artists.title": "PROGRAMMATION", "config.artists.hint": "La liste des artistes de l'édition, jour par jour. Les bénévoles y choisissent ceux qu'ils veulent voir depuis leur fiche.", "config.artists.count": "ARTISTES", "config.artists.empty": "Aucun artiste ce jour.", "config.artists.outside": "hors dates", "config.artists.namePlaceholder": "Nom de l'artiste", "config.vol.field.artists": "Artistes à voir", "config.vol.field.positions": "Postes souhaités", "config.vol.noArtists": "Aucun artiste choisi", "config.vol.noPositions": "Aucun poste choisi", "config.vol.pickArtist": "Ajouter un artiste…", "config.vol.pickPosition": "Ajouter un poste…", "config.vol.colWishes": "VŒUX", "config.vol.wishesCount": "{count} vœu(x)", "config.vol.addAvailabilityHint": "Les disponibilités se renseignent sur la fiche du bénévole, une fois celui-ci créé.", "config.vol.avail.title": "Disponibilités", "config.vol.avail.hint": "Plages horaires où le bénévole peut venir. Une heure de fin ≤ début signifie que la plage se termine le lendemain.", "config.vol.avail.none": "Aucune disponibilité déclarée", "config.vol.avail.add": "Ajouter une plage", "config.vol.avail.save": "Enregistrer les disponibilités", "grille.panel.coverage.full": "Disponible", "grille.panel.coverage.partial": "Partiellement dispo", "grille.panel.coverage.none": "Dispo non déclarée", "availability.date_outside_edition": "Cette date est hors des dates de l'édition.", "availability.empty_window": "Une plage doit avoir un début et une fin différents.", "grille.solver.run": "SUGGESTIONS (SOLVEUR)", "grille.solver.running": "CALCUL EN COURS…", "grille.solver.result": "{count} suggestion(s)", "grille.panel.suggested": "Suggéré" }