feat(security): Ajoute réinitialisation mot de passe, robots.txt, et traductions.

Ajoute la fonctionnalité de réinitialisation de mot de passe, met à jour le
robots.txt, et ajoute des traductions en français et en anglais.
```
This commit is contained in:
Serreau Jovann
2025-11-17 14:13:50 +01:00
parent aac6bacbf0
commit 75f3533776
9 changed files with 221 additions and 91 deletions

View File

@@ -40,10 +40,9 @@ security:
# algorithm: bcrypt
role_hierarchy:
ROLE_ADMIN: [ROLE_ARTEMIS]
ROLE_ROOT: [ROLE_ADMIN] # ROLE_ROOT inclut ROLE_ADMIN, qui à son tour inclut ROLE_ARTEMIS
access_control:
- { path: ^/artemis, roles: [ROLE_ARTEMIS,ROLE_CUSTOMER] }
- { path: ^/admin, roles: [ROLE_ADMIN] }
- { path: ^/, roles: PUBLIC_ACCESS } # Toutes les autres pages nécessitent une authentification complète

View File

@@ -44,6 +44,11 @@ class SecurityController extends AbstractController
return $this->render('security/forgot_password.twig', [
'form' => $form->createView(),
]);
}
#[Route(path: '/logout', name: 'app_logout', options: ['sitemap' => false], methods: ['GET','POST'])]
public function logout(): Response
{
}
#[Route(path: '/mot-de-passe-oublie/sent', name: 'app_forgot_password_sent', options: ['sitemap' => false], methods: ['GET','POST'])]
public function forgotPasswordSent(Request $request,EventDispatcherInterface $eventDispatcher): Response
@@ -52,8 +57,49 @@ class SecurityController extends AbstractController
]);
}
#[Route(path: '/mot-de-passe-oublie/{id}/{token}', name: 'app_forgot_password_confirm', options: ['sitemap' => false], methods: ['GET','POST'])]
public function forgotPasswordConfirm(Request $request): Response
public function forgotPasswordConfirm(UserPasswordHasherInterface $userPasswordHasher,EventDispatcherInterface $eventDispatcher,Request $request,EntityManagerInterface $entityManager,string $id,string $token): Response
{
$errorMessage = "Requête non valide.";
if (!is_numeric($id)) {
$this->addFlash("error", $errorMessage);
return $this->redirectToRoute('app_forgot_password');
}
$account = $entityManager->getRepository(Account::class)->find((int)$id);
if (!$account instanceof Account) {
$this->addFlash("error", $errorMessage);
return $this->redirectToRoute('app_forgot_password');
}
$requestToken = $entityManager->getRepository(AccountResetPasswordRequest::class)->findOneBy([
'Account' => $account, // Assurez-vous que 'Account' est le nom correct de la propriété/colonne dans votre entité AccountResetPasswordRequest.
'token' => $token
]);
if (!$requestToken instanceof AccountResetPasswordRequest) {
$this->addFlash("error", $errorMessage);
return $this->redirectToRoute('app_forgot_password');
}
$now = new \DateTimeImmutable();
if ($requestToken->getExpiresAt() < $now) {
$this->addFlash("error", "Le lien de réinitialisation de mot de passe a expiré.");
return $this->redirectToRoute('app_forgot_password');
}
$event = new ResetPasswordConfirmEvent();
$form = $this->createForm(RequestPasswordConfirmType::class,$event);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$account->setPassword($userPasswordHasher->hashPassword($account,$event->getPassword()));
$entityManager->persist($account);
$entityManager->flush();
$this->addFlash("success", "Votre mot de passe a été mis à jour avec succès.");
return $this->redirectToRoute('app_login');
}
return $this->render('security/forgot-password-confirm.twig', [
'form' => $form->createView(),
'noIndex' => true,
'id' => $id,
'token' => $token,
'account' => $account,
]);
}
}

View File

@@ -45,6 +45,9 @@ class SeoController extends AbstractController
$robots->addDisallow($this->generateUrl('app_cookies'));
$robots->addDisallow($this->generateUrl('app_cgu'));
$robots->addDisallow($this->generateUrl('app_cgv'));
$robots->addDisallow($this->generateUrl('app_login'));
$robots->addDisallow($this->generateUrl('app_logout'));
$robots->addDisallow($this->generateUrl('app_forgot_password'));
$robots->addSpacer();
$robots->addComment("Sitemap");

View File

@@ -68,11 +68,10 @@ class ResetPasswordSubscriber
UrlGeneratorInterface::ABSOLUTE_URL
);
$title = "[E-Cosplay]";
$this->mailer->send(
$account->getEmail(),
$account->getUsername(),
' - Lien pour réinitialiser votre mot de passe',
'[E-Cosplay] - Lien pour réinitialiser votre mot de passe',
'mails/reset.twig',
[
'account' => $account,

View File

@@ -0,0 +1,28 @@
{% extends 'mails/base.twig' %}
{% block content %}
<mj-text>Bonjour, </mj-text>
{% if 'ROLE_CUSTOMER' in datas.account.roles %}
<mj-text>Nous avons reçu une demande de réinitialisation de mot de passe pour votre espace client.</mj-text>
{% else %}
<mj-text>Nous avons reçu une demande de réinitialisation de mot de passe pour votre compte E-Cosplay.</mj-text>
{% endif %}
<mj-text>Pour réinitialiser votre mot de passe, veuillez cliquer sur le bouton ci-dessous. Ce lien est valable pour une durée limitée.</mj-text>
<mj-button href="{{ datas.resetLink }}">
Réinitialiser mon mot de passe
</mj-button>
<mj-text padding-top="20px">
Ce lien expirera le {{ datas.request.expiresAt|date('d/m/Y à H:i') }}.
<br/>
Veuillez l'utiliser avant cette date et heure.
</mj-text>
<mj-text>Si vous n'avez pas demandé cette réinitialisation de mot de passe, veuillez ignorer cet e-mail. Votre mot de passe actuel restera inchangé.</mj-text>
<mj-text padding-top="20px">Cordialement,</mj-text>
<mj-text>L'équipe E-Cosplay</mj-text>
{% endblock %}

View File

@@ -0,0 +1,100 @@
{% extends 'base.twig' %}
{% block title %}{{ 'events.reset_password'|trans }}{% endblock %}
{% block meta_description %}{{ 'events.reset_password'|trans }}{% endblock %}
{% block canonical_url %}<link rel="canonical" href="{{ url('app_forgot_password_confirm', {id: id, token: token}) }}" />{% endblock %}
{% block breadcrumb_schema %}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "{{ 'breadcrumb.home'|trans }}",
"item": "{{ app.request.schemeAndHttpHost }}"
},
{
"@type": "ListItem",
"position": 2,
"name": "{{ 'breadcrumb.reset_password'|trans }}",
"item": "{{ app.request.schemeAndHttpHost }}{{ app.request.pathInfo }}"
}
]
}
</script>
{% endblock %}
{% block body %}
<div class="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
<div class="max-w-md w-full space-y-8 p-10 bg-white rounded-xl shadow-lg">
<h2 class="mt-6 text-center text-3xl font-extrabold text-gray-900">
{{ 'events.reset_password'|trans }}
</h2>
<p class="mt-2 text-center text-sm text-gray-600">
{{ 'text.enter_new_password'|trans }}
</p>
{# Affichage des messages flash (ex: token expiré ou invalide) #}
{% for flash_error in app.flashes('reset_password_error') %}
<div class="p-4 text-sm text-red-700 bg-red-100 rounded-lg" role="alert">
{{ flash_error }}
</div>
{% endfor %}
{# Le formulaire Symfony #}
{{ form_start(form, {'attr': {'class': 'mt-8 space-y-6'}}) }}
<div class="rounded-md shadow-sm -space-y-px">
{# Champ Nouveau Mot de Passe (first) #}
{# On suppose que form.plainPassword est un RepeatedType avec un champ 'first' et 'second' #}
<div>
{{ form_label(form.password.first, 'label.new_password'|trans, {'label_attr': {'class': 'sr-only'}}) }}
{{ form_widget(form.password.first, {
'attr': {
'class': 'appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm',
'placeholder': 'label.new_password'|trans,
'autocomplete': 'new-password',
'required': 'required'
}
}) }}
{{ form_errors(form.password.first) }}
</div>
{# Champ Confirmation Mot de Passe (second) #}
<div>
{{ form_label(form.password.second, 'label.confirm_password'|trans, {'label_attr': {'class': 'sr-only'}}) }}
{{ form_widget(form.password.second, {
'attr': {
'class': 'appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm',
'placeholder': 'label.confirm_password'|trans,
'autocomplete': 'new-password',
'required': 'required'
}
}) }}
{{ form_errors(form.password.second) }}
</div>
</div>
{# Affichage des erreurs globales du formulaire #}
{{ form_errors(form) }}
{# Bouton Soumettre #}
<div>
<button type="submit"
class="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
{{ 'button.reset_password'|trans }}
</button>
</div>
{{ form_end(form) }}
</div>
</div>
{% endblock %}

View File

@@ -1,7 +1,7 @@
{% extends 'base.twig' %}
{% block title %}{{ 'events.login'|trans }}{% endblock %}
{% block meta_description %}{{ 'events.login'|trans }}{% endblock %}
{% block title %}{{ 'page.login'|trans }}{% endblock %}
{% block meta_description %}{{ 'page.login'|trans }}{% endblock %}
{% block canonical_url %}<link rel="canonical" href="{{ url('app_login') }}" />{% endblock %}
{% block breadcrumb_schema %}
@@ -38,10 +38,14 @@
{# Display error messages if login fails #}
{% if error %}
<div class="p-4 mb-4 text-sm text-red-700 bg-red-100 rounded-lg" role="alert">
<span class="font-medium">{{ 'error.login_failed'|trans }}</span> {{ error.messageKey|trans(error.arguments, 'security') }}
{{ dump(error) }}
</div>
{% endif %}
{% for message in app.flashes('success') %}
<div class="p-4 text-sm text-green-700 bg-green-100 rounded-lg" role="alert">
{{ message }}
</div>
{% endfor %}
{# The actual login form #}
<form class="mt-8 space-y-6" action="{{ path('app_login') }}" method="post">
<input type="hidden" name="remember" value="true">

View File

@@ -536,34 +536,34 @@ events.list_main_title: Events
events.no_events_title: No Planned Events
events.no_events_message: It seems there are no planned events for the moment. Check back soon!
events.button_contact: Contact Us
events.login: Login
login_link: Log In
register_link: Register
# translations/messages.en.yaml
# Login Page (Connexion)
events.login: Login
events.forgot_password: Forgot Password
# Breadcrumbs
breadcrumb.login: Login
breadcrumb.forgot_password: Forgot Password
# Labels and Buttons
breadcrumb.login: Log In
label.email: Email address
label.password: Password
label.remember_me: Remember me
button.sign_in: Sign In
button.send_reset_link: Send Reset Link
# Links
link.forgot_password: Forgot your password?
link.back_to_login: Back to login
# Errors and Security Messages
link.forgot_password: Forgot password?
error.login_failed: Login failed.
security.login: Sign in to your account
# Descriptive text
security.login: Log in to your account
events.forgot_password: Forgot Password
breadcrumb.forgot_password: Forgot Password
text.enter_email_for_reset: Please enter your email address to receive a reset link.
button.send_reset_link: Send Reset Link
link.back_to_login: Back to Log In
events.reset_email_sent: Reset Email Sent
text.check_inbox_title: Check your inbox 📥
text.check_inbox_description: An email has been sent with a link to reset your password. It may arrive in a few minutes.
text.spam_folder_tip: If you don't see it, check your spam folder.
events.reset_password: Reset Password
breadcrumb.reset_password: Password Reset
label.new_password: New password
label.confirm_password: Confirm new password
text.enter_new_password: Please enter and confirm your new password.
button.reset_password: Reset Password
open_user_menu_sr: Open user menu
logged_in_as: Signed in as
logout_link: Log Out
page.login: Login

View File

@@ -309,12 +309,8 @@ legal_section8_p1_law: Tout litige en relation avec lutilisation du site %sit
rgpd_short_title: Politique RGPD
rgpd_page_title: Politique de Confidentialité (RGPD)
rgpd_page_title_long: Politique de Confidentialité et RGPD
# Section 1 : Engagement
rgpd_section1_title: 1. Engagement de l'Association E-Cosplay
rgpd_section1_p1: L'association E-Cosplay s'engage à respecter la vie privée de ses utilisateurs et à traiter leurs données personnelles en toute transparence et conformément au Règlement Général sur la Protection des Données (RGPD).
# Section 2 : Données Collectées
rgpd_section2_title: 2. Données Personnelles Collectées et Finalité
rgpd_section2_p1_commitment: L'association E-Cosplay adopte une politique de collecte de données strictement minimale.
rgpd_section2_p2_data_collected: "Nous ne collectons aucune information superflue ou de surplus. Les seules données personnelles recueillies sur ce site le sont dans le cadre précis du :"
@@ -324,8 +320,6 @@ rgpd_contest_form_title: Fiches d'inscription aux concours
rgpd_contest_form_details: Dans le cadre des concours cosplay, nous collectons les données nécessaires à l'inscription, incluant des fichiers média (audio, image, vidéo) soumis par les participants pour l'évaluation. Ces données sont soumises à la même rigueur de traitement que toutes les autres informations personnelles.
rgpd_no_other_collection_title: Absence d'autres collectes
rgpd_no_other_collection_details: Aucune autre donnée n'est collectée automatiquement ou indirectement à des fins de profilage ou de traçage.
# Section 3 : Confidentialité et Sécurité
rgpd_section3_title: 3. Confidentialité et Sécurité des Données
rgpd_section3_subtitle1: Non-Revente des Données
rgpd_section3_p1_no_resale: L'association E-Cosplay ne revend, ne loue et ne met à disposition sous aucune forme les données personnelles collectées à des tiers, à des fins commerciales ou autres. Vos données sont traitées en interne et restent confidentielles.
@@ -345,8 +339,6 @@ rgpd_breach_list2_strong: Déclaration aux Autorités
rgpd_breach_list2_details: Une déclaration sera effectuée auprès de la CNIL (Commission Nationale de l'Informatique et des Libertés) et de l'ANSSI (Agence Nationale de la Sécurité des Systèmes d'Information) dans un délai maximum de 24 heures après la découverte de la violation.
rgpd_breach_list3_strong: Transparence Complète
rgpd_breach_list3_details: Une communication transparente et complète sera mise en place concernant la nature de la violation, les données potentiellement affectées, et les mesures prises pour y remédier.
# Section 4 : Durée de Conservation
rgpd_section4_title: 4. Durée de Conservation et Suppression Automatique
rgpd_section4_subtitle1: Données d'Inscription aux Concours
rgpd_section4_p1_contest_data_intro: "Les données spécifiques collectées pour les concours (fiche d'inscription, fichiers audio/image/vidéo, ordre de passage, notations des juges) sont conservées pendant une durée limitée :"
@@ -365,8 +357,6 @@ rgpd_section4_p3_general_deletion: Pour assurer un nettoyage régulier, toutes l
rgpd_section4_subtitle4: Autres Données
rgpd_section4_p4_other_data: Les données du formulaire de contact sont conservées uniquement le temps de traiter la demande, puis supprimées automatiquement après un délai raisonnable de suivi (maximum 6 mois).
rgpd_section4_p5_rights_reminder: Vous conservez à tout moment votre droit de demander la suppression anticipée de vos données (voir Section 5).
# Section 5 : Vos Droits
rgpd_section5_title: 5. Vos Droits (Droit d'Accès, de Rectification et de Suppression)
rgpd_section5_p1_rights_intro: "Conformément au RGPD, vous disposez des droits suivants concernant vos données :"
rgpd_right_access: Droit d'accès (savoir quelles données sont conservées).
@@ -374,16 +364,12 @@ rgpd_right_rectification: Droit de rectification (modifier des données erronée
rgpd_right_erasure: Droit à l'effacement ou "droit à l'oubli" (demander la suppression de vos données).
rgpd_right_opposition: Droit d'opposition et de limitation du traitement.
rgpd_section5_p2_contact_dpo: "Pour exercer ces droits, vous pouvez contacter notre Délégué à la Protection des Données (DPO) :"
# --- Navigation & Menu ---
Accueil: "Accueil"
Qui sommes-nous: "Qui sommes-nous"
Nos membres: "Nos membres"
Nos événements: "Nos événements"
Contact: "Contact"
open_main_menu_sr: "Ouvrir le menu principal"
# --- Panier (Off-Canvas Cart) ---
open_cart_sr: "Ouvrir le panier"
your_cart: "Votre Panier"
close_cart_sr: "Fermer le panier"
@@ -391,8 +377,6 @@ cart_empty: "Votre panier est vide."
subtotal_label: "Sous-total"
checkout_button: "Passer à la caisse"
shipping_disclaimer: "Frais de port calculés à l'étape suivante."
# --- Footer ---
footer_contact_title: "Nous Contacter"
footer_follow_us_title: "Nous Suivre"
footer_mission_description: |
@@ -401,39 +385,25 @@ footer_mission_description: |
une plateforme pour les passionnés et de mettre en avant le talent créatif.
all_rights_reserved: "Tous droits réservés"
association_status: "Association loi 1901 à but non lucratif."
# --- Liens Légaux ---
legal_notice_link: "Mentions Légales"
cookie_policy_link: "Politique de Cookies"
hosting_link: "Hébergement"
rgpd_policy_link: "Politique RGPD"
cgu_link: "CGU"
cgv_link: "CGV"
# --- Formulaire de Contact (Keys) ---
# TITRES DE PAGE ET BREADCRUMBS
contact_page.title: "Contactez Nous"
contact_page.breadcrumb: "Contactez Nous"
breadcrumb.home: "Accueil"
# MESSAGES FLASH
flash.success.strong: "Message envoyé !"
flash.error.strong: "Message Erreur !"
# INFORMATIONS DE CONTACT (COLONNE DE GAUCHE)
contact_info.title: "Restons Connectés"
contact_info.subtitle: "Que ce soit pour une question technique, une opportunité de partenariat ou une simple salutation, nous sommes là pour vous."
contact_info.email: "E-mail"
contact_info.address: "Adresse Postale"
contact_info.join_title: "Rejoindre l'association"
contact_info.join_text: "Vous souhaitez nous rejoindre ou obtenir plus d'informations sur les modalités d'adhésion ? Veuillez nous écrire directement à :"
# FORMULAIRE (COLONNE DE DROITE)
form.title: "Envoyez-nous un Message"
form.submit_button: "Envoyer le message"
# CLÉS DU FORMULAIRE (celles de votre premier message)
contact_form.name.label: "Prénom"
contact_form.surname.label: "Nom de famille"
contact_form.subject.label: "Sujet"
@@ -446,7 +416,6 @@ contact_form.subject.placeholder: "Objet de votre message"
contact_form.email.placeholder: "votre.email@exemple.com"
contact_form.surname.placeholder: "Votre nom de famille"
contact_form.name.placeholder: "Votre prénom"
members_page.title: "Nos Membres & Bureau"
members_page.breadcrumb: "Membres"
members_page.board_title: "Membres du Bureau"
@@ -454,7 +423,6 @@ members_page.board_empty: "La liste des membres du bureau sera bientôt disponib
members_page.all_title: "Tous les Membres"
members_page.all_empty: "Aucun membre n'a été trouvé pour le moment."
members_title: 'Membres'
# CLÉS DANS LA FICHE DE MEMBRE (Badges et Labels)
member_card.role: "Rôle"
member_card.cosplay_label: "Cosplayer"
member_card.yes: "Oui"
@@ -474,22 +442,16 @@ orientation.pansexual: "Pansexuel(le)"
orientation.queer: "Queer"
orientation.questioning: "En questionnement"
orientation.other: "Autre"
# --- PAGE D'ACCUEIL (HOMEPAGE.TWIG) ---
home_page.title: "Accueil"
Boutiques: Boutique
# HERO SECTION
home_hero.title: "Le Point de Rencontre des Passionnés de Cosplay"
home_hero.subtitle: "Rejoignez une communauté inclusive où la créativité, la diversité et l'amitié sont à l'honneur."
home_hero.button_members: "Voir nos Membres"
home_hero.button_contact: "Nous Contacter"
# SECTION À PROPOS
home_about.pretitle: "Notre Histoire"
home_about.title: "Un Espace sûr pour tous les Fandoms"
home_about.text_1: "Notre association a été créée pour offrir un environnement bienveillant et structuré à tous les fans d'art vestimentaire, de culture geek et de jeux de rôle."
home_about.text_2: "Nous croyons que l'expression personnelle est essentielle. Que vous soyez débutant ou expérimenté, crosscosplayer, transgenre, ou simplement passionné par un univers, vous avez votre place ici."
# SECTION ACTIVITÉS
home_activities.title: "Ce que nous Faisons Ensemble"
home_activities.cosplay_title: "Création & Partage de Cosplay"
home_activities.cosplay_text: "Des ateliers pour améliorer vos techniques (couture, armure, maquillage) et des séances photo thématiques."
@@ -497,16 +459,12 @@ home_activities.community_title: "Événements et Communauté"
home_activities.community_text: "Organisation de rencontres régionales, de sorties en convention et de soirées de jeux de société ou de discussions autour d'anime/manga."
home_activities.diversity_title: "Diversité et Soutien"
home_activities.diversity_text: "Nous sommes un pilier de soutien pour tous, incluant le crossplay et l'accueil bienveillant des membres transgenres et de toutes les orientations."
# CTA ADHÉSION
home_cta.title: "Prêt à Partager votre Passion ?"
home_cta.subtitle: "Adhérez aujourd'hui et faites partie de l'aventure."
home_cta.button: "Adhérer Maintenant"
home_page.description: "Bienvenue dans la communauté e-cosplay ! Votre référence pour les concours, ateliers de craft, et l'entraide. Le cosplay est pour tous, rejoignez notre passion !"
members_description: 'Découvrez les membres actifs de notre association de cosplay ! Rencontrez les bénévoles, juges et organisateurs qui donnent vie à nos événements et activités.'
contact_page.description: 'Contactez-nous pour toute question sur le cosplay, les événements ou les partenariats ! Formulaire de contact direct et emails des fondatrices disponibles ici.'
# --- PAGE BOUTIQUE (SHOP.TWIG) ---
shop.title: "Boutique | En construction"
shop.description: "Découvrez bientôt notre boutique officielle pour les produits dérivés de l'association."
breadcrumb.shop: "Boutique"
@@ -515,8 +473,6 @@ shop.status_message: "Nous travaillons activement pour préparer notre boutique
shop.button_home: "Retour à l'Accueil"
shop.button_contact: "Nous Contacter"
shop.status_notification: "Suivez nos réseaux sociaux pour être le premier informé de l'ouverture !"
# --- PAGE ÉVÉNEMENTS (EVENTS.TWIG) ---
events.title: "Événements | Bientôt disponible"
events.description: "Consultez bientôt le calendrier de nos prochains événements, conventions et rencontres communautaires."
breadcrumb.events: "Événements"
@@ -526,7 +482,6 @@ events.no_events_message: "Il semble qu'il n'y ait aucun événement de prévu p
events.button_contact: "Nous Contacter"
login_link: Connexion
register_link: Inscription
breadcrumb.login: Connexion
label.email: Adresse e-mail
label.password: Mot de passe
@@ -535,26 +490,22 @@ button.sign_in: Se connecter
link.forgot_password: Mot de passe oublié ?
error.login_failed: Échec de la connexion.
security.login: Connexion à votre compte
events.forgot_password: Mot de passe oublié
# Breadcrumbs (Fil d'Ariane)
breadcrumb.forgot_password: Mot de passe oublié
# Texte descriptif
text.enter_email_for_reset: Veuillez entrer votre adresse e-mail pour recevoir un lien de réinitialisation.
# Bouton
button.send_reset_link: Envoyer le lien de réinitialisation
# Liens
link.back_to_login: Retour à la connexion
events.reset_email_sent: E-mail de réinitialisation envoyé
text.check_inbox_title: Vérifiez votre boîte de réception 📥
text.check_inbox_description: Un e-mail a été envoyé avec un lien pour réinitialiser votre mot de passe. Il se peut qu'il arrive dans quelques minutes.
text.spam_folder_tip: Si vous ne le voyez pas, vérifiez votre dossier de courriers indésirables (spam).
# ... (Assurez-vous que 'link.back_to_login' est déjà défini)
events.reset_password: Réinitialiser le mot de passe
breadcrumb.reset_password: Réinitialisation du mot de passe
label.new_password: Nouveau mot de passe
label.confirm_password: Confirmer le nouveau mot de passe
text.enter_new_password: Veuillez saisir et confirmer votre nouveau mot de passe.
button.reset_password: Réinitialiser le mot de passe
open_user_menu_sr: Ouvrir le menu utilisateur
logged_in_as: Connecté en tant que
logout_link: Déconnexion
page.login: Connexion