feat(canopy-portal): wire Fluent i18n loader (replace stub LocaleManager)
Context
services/canopy-portal/src/i18n.rs:16-19 defines LocaleManager as a self-labelled "Stub locale bundle manager" with a TODO: to load .ftl files, build bundles, negotiate from Accept-Language, and provide an Axum extractor. The bundles HashMap is never populated; negotiate() always returns the default.
ADR-008 says canopy-portal is post-UAT (Dioxus fullstack), so this work is gated on the portal-implementation timeline. Filing the issue so the stub doesn't drift.
Acceptance criteria
- Loader reads
.ftlfiles from a known directory at startup - Bundles built per locale (en/es minimum per ADR-008)
- Negotiation respects
Accept-Languagewith fallback chain - Axum extractor exposes the negotiated bundle to handlers
- WCAG 2.1 AA: language attribute on
<html lang="">matches negotiation - Unit tests cover negotiation + missing-locale fallback
Dependencies
- ADR-008 applicant portal (Dioxus) — post-UAT.
Surfaced by
Multi-agent plan-vs-code audit, 2026-05-05.