Backlog — Propagation de la locale utilisateur
Status : ticket différé, à planifier comme chantier dédié. Origine : audit anti-slop post-AddTeams (24 avril 2026) — 9
TODOrecensés, tous pré-existants (aucun introduit par la branche AddTeams).Objectif : remplacer les locales hardcodées
Some("fr".to_string())par la locale réelle de l'utilisateur (préférence DB → cookie →Accept-Language→ défaut).
1. Contexte
Plusieurs services envoient des emails transactionnels en utilisant des templates i18n
(EmailValidationTemplate, PasswordResetEmailTemplate, WelcomeEmailTemplate,
PasswordChangeConfirmationEmailTemplate). Aujourd'hui, la locale passée au template
est hardcodée à "fr" avec un TODO. Conséquence : un utilisateur qui a choisi
en dans ses préférences reçoit malgré tout un mail en français.
Par ailleurs, l'extracteur Axum Locale n'implémente qu'une partie de la priorité
prévue (cookie + Accept-Language) — la lecture de la préférence DB reste à faire.
2. Inventaire des TODO (9)
| # | Fichier | Ligne | Contenu | Impact |
|---|---|---|---|---|
| 1 | crates/rustwarden-core/src/i18n/extractor.rs | 57 | T009 - Implement full locale extraction logic | L'extracteur Locale ne lit jamais user_preferences.preferred_locale. Priorité 1 (source de vérité). |
| 2 | crates/rustwarden-core/src/services/email_validation_service.rs | 100 | Get from user preferences or Accept-Language header | Email de validation d'adresse toujours en fr. |
| 3 | crates/rustwarden-core/src/services/email_validation_service.rs | 220 | Get from user preferences | Email de bienvenue toujours en fr. |
| 4 | crates/rustwarden-core/src/services/password_reset_service.rs | 115 | Get from user preferences or Accept-Language header | Email de reset mot de passe toujours en fr. |
| 5 | crates/rustwarden-core/src/tasks/password_change_email_retry.rs | 197 | Get from user preferences | Email de confirmation de changement mot de passe (retry) toujours en fr. |
| 6 | crates/rustwarden-core/src/services/refresh_token_service.rs | 548 | Store user_agent in refresh_token table | Champ device_info toujours None dans la liste des sessions actives. |
| 7 | crates/rustwarden-core/src/services/refresh_token_service.rs | 549 | Store IP in refresh_token table | Champ ip_address toujours None. |
| 8 | crates/rustwarden-core/src/services/refresh_token_service.rs | 590 | Blacklist associated JWT if applicable | La révocation d'une session ne blackliste pas le JWT. |
| 9 | crates/rustwarden-ui/src/handlers/email_handler.rs | 102 | Get total count for pagination (requires separate query) | La pagination de la file d'emails retourne emails.len() au lieu d'un COUNT(*). |
Les items #6, #7, #8 et #9 ne sont pas strictement i18n mais font partie du même backlog "dette technique pré-existante" ; on les garde groupés ici pour un chantier unique "propagation métadonnées requête → services".
3. Ébauche de solution
3.1 Couche extracteur (item #1)
Enrichir Locale::from_request_parts
pour lire la préférence DB quand un Claims est disponible :
priorité : 1. user_preferences.preferred_locale (si Claims présent + DB accessible via Extension<DatabaseConnection>) 2. cookie `locale` 3. Accept-Language header 4. "fr"
Contrainte : from_request_parts ne peut pas faire de requête DB sans extracteur
d'état. Deux options :
- (A) garder
Localesans DB et ajouter un helperLocale::resolve(claims, db, headers)appelable dans les services. - (B) créer un
ExtractorsupplémentaireUserLocalequi dépend deAuthUser+Extension<DatabaseConnection>.
Préférence à discuter lors du chantier : option (A) pour garder Locale purement
HTTP, et ajouter un petit service LocaleResolver injecté dans les services emails.
3.2 Services emails (items #2, #3, #4, #5)
Remplacer Some("fr".to_string()) par un paramètre locale: Option<String> passé
par l'appelant. Les callers sont :
EmailValidationService::send_validation_email— appelé aprèsPOST /register: on connaît l'utilisateur, on peut lireuser_preferences.PasswordResetService::request_reset— appelé depuis/forgot-password(anonyme) : fallback surAccept-Languagedepuis le handler.- Task
PasswordChangeEmailRetryProcessor— appelé périodiquement : doit lireuser_preferencesdu user_id stocké en queue.
Signature cible (exemple) :
pub async fn send_validation_email( &self, db: &DatabaseConnection, user: &user::Model, locale: Option<String>, // déjà résolu par l'appelant ) -> Result<(), AppError>
3.3 Stockage métadonnées session (items #6, #7, #8)
- Migration : ajouter
user_agent VARCHAR(512)etip_address VARCHAR(45)àrefresh_token. - Handler
/login: extraireUser-Agent+ IP (x-forwarded-forouConnectInfo) et passer àRefreshTokenService::create. revoke_session: sirefresh_token.jwt_idexiste, appelerJwtBlacklistService::blacklist.
3.4 Pagination emails (item #9)
Ajouter une requête COUNT(*) dans email_handler::list et retourner le vrai total.
4. Critères d'acceptation
-
Tous les
TODOlistés supprimés (ou déplacés vers un backlog plus précis) -
Un utilisateur avec
preferred_locale = "en"reçoit tous les emails en anglais -
ActiveSession.device_infoetActiveSession.ip_addressrenseignés pour les nouvelles sessions -
Révoquer une session blackliste le JWT associé (testé via
cargo test) -
Pagination
/admin/emailsretourne un total correct même avec offset > len
5. Estimation
- Refacto locale + tests : ~1 j
- Migration
refresh_token+ wiring login + blacklist : ~0.5 j - Pagination emails : ~1 h
- Relecture PO + QA navigateur : ~0.5 j
Total ≈ 2-3 jours de dev.
6. Hors-scope
- Ajout de locales (IT/PT déjà ajoutés dans la branche AddTeams)
- UI de sélection de langue (déjà existante dans
settings/preferences) - Conversion des logs / messages d'erreur internes (reste en anglais par convention tech)
GitRust