feat(portal-shell): locale switcher in the footer #95
Reference in New Issue
Block a user
Delete Branch "feat/portal-shell/i18n-locale-switcher"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Add a locale switcher (FR / EN) to the footer's right cluster, next to the accessibility link. Closes the user-facing i18n loop — the FR bundle has existed since the sweep PR but had no in-app entry point until now.
The switcher reads the active locale from
<html lang>(set per locale by the build), shows the native name + a globe + chevron-down chip, and on selection rewrites the URL prefix (/en/...↔/fr/...) and hard-refreshes so the right bundle boots — per ADR-0019.Architecture
Same pattern as the theme switcher:
@angular/cdk/menufor the trigger + roving-focus menu + escape / click-outside dismissal.ViewEncapsulation.Nonebecause the menu opens in an overlay portal outside the component host — BEM-style class names (.locale-switcher__*) keep the global emissions contained.[attr.lang]="locale.code"so screen readers pronounce the native names correctly.Decisions worth flagging
/redirect — yet. The URL prefix is the source of truth in v1: the next visit lands on the same locale because the URL says so. The__Host-portal_localecookie + the BFF route at/api/preferences/locale+ the smart/redirect described in ADR-0019 wait for the auth flow to bring the BFF online.nx serve, the dev server has no locale prefix in the URL — clicking the trigger lands on a non-existent path. The switcher works against the production build (nx run portal-shell:serve-staticor any real deploy). This matches ADR-0019: dev = source locale, locale switching is a built-bundle concern.Translation choices
Two new i18n keys:
@@locale.trigger.ariaLanguage: <name> (change language)Langue : <name> (changer de langue)@@locale.menu.ariaLanguageLangueTest plan
pnpm exec nx run-many -t lint test build --projects=portal-shell— green. 40 / 40 specs (+5: four new forLocaleSwitcher, one for the footer embedding)./en/, click switcher → lands on/fr/; widget shows "Français"; reload stays in FR.<button lang="fr">Français</button>is announced with the FR voice)./en/accessibility?foo=bar→/fr/accessibility?foo=bar).What this PR explicitly does NOT do
/api/preferences/locale+__Host-portal_localecookie./redirect (cookie → Accept-Language → fr) — that's reverse-proxy / BFF work.Add a CDK-menu locale switcher (FR / EN) to the footer's right cluster, next to the accessibility link. Trigger reads the active locale from `<html lang>` (set per locale by the build) and shows the native name plus a globe + chevron. Selecting another locale rewrites the URL prefix (`/en/...` ↔ `/fr/...`) and triggers a hard refresh so the right bundle boots — per ADR-0019. Architecture mirrors the existing theme switcher: - `@angular/cdk/menu` for the trigger + roving-focus menu + escape / click-outside dismissal. - `ViewEncapsulation.None` because the menu opens in an overlay portal outside the component host; BEM-style class names keep the global emissions contained. - The trigger's `aria-label` is `$localize`-marked with a named placeholder so screen readers announce the current language. Locale display names ("Français", "English") are deliberately NOT i18n-marked. Universal switcher convention: each language is always shown in its own language, both in the trigger label and the menu items — translating them would defeat the purpose. The `<button>` for each menu entry carries `[attr.lang]="locale.code"` so screen readers pronounce the name correctly. v1 is SPA-only: - No backend cookie, no `/api/preferences/locale` BFF route, no smart `/` redirect — those land when the BFF is in place. Until then the locale prefix in the URL is the source of truth; the next visit naturally lands on the same locale's bundle. - The switcher works against the production build (`/en/`, `/fr/`). Under `nx serve` the dev server has no locale prefix, so clicking the trigger lands on a non-existent path. Expected — dev mode serves the source locale by design (ADR-0019). Touch target: the trigger keeps a 28px visible height to fit the 40px footer, but extends to 44px via vertical padding — meets the ADR-0016 AAA touch-target minimum without inflating the footer. Add Globe to the icon registry (single new entry).