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 .ftl files from a known directory at startup
  • Bundles built per locale (en/es minimum per ADR-008)
  • Negotiation respects Accept-Language with 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.