feat(portal-shell): wire @angular/localize plumbing per ADR-0019 #91

Merged
julien merged 1 commits from feat/portal-shell/i18n-plumbing into main 2026-05-11 16:46:08 +02:00
Owner

Summary

First implementation step of ADR-0019. Wire the @angular/localize plumbing into portal-shell so the next sweep PR can start marking UI strings without any infrastructure work.

What changes

  • Promote @angular/localize to a direct dependency (it was already a transitive via the Angular metapackage; promoting it makes the init polyfill explicitly resolvable from the project).
  • Configure the i18n block in apps/portal-shell/project.json:
    • sourceLocale: { code: "en", baseHref: "/en/" } — matches the project English-only rule.
    • locales.fr: { translation: "...messages.fr.xlf", baseHref: "/fr/" } — single target locale for now.
  • Add the init polyfill to the build target ("polyfills": ["@angular/localize/init"]).
  • Add an extract-i18n Nx target that wraps Angular's @angular/build:extract-i18n executor and drops the source XLF next to the translation files.
  • Enable --localize on the production buildnx build portal-shell --configuration=production now emits two folders side by side: dist/apps/portal-shell/browser/en/ and .../fr/. Each carries its own <html lang> and <base href> per the ADR.
  • Seed an empty messages.fr.xlf (XLIFF 1.2 skeleton with sourceLanguage="en" / targetLanguage="fr" and an inline editor convention note). The sweep PR drops <trans-unit> entries directly into the body block.

Verification

dist/apps/portal-shell/browser/
├── en/
│   └── index.html   ← <html lang="en">, <base href="/en/">
└── fr/
    └── index.html   ← <html lang="fr">, <base href="/fr/">

Until the sweep PR marks strings, both bundles ship the same English source text — that's expected and matches what the ADR calls out ("the FR bundle falls back to source text for every untranslated key").

What this PR explicitly does NOT do

  • Mark UI strings. Every i18n attribute / $localize call lands in the next PR. Pure infra commit here.
  • Locale switcher in the footer + __Host-portal_locale cookie + smart / redirect. Lands once switching shows a meaningful difference.
  • Collapse /accessibility + /accessibilite into a single localised route. Depends on marked text + localized route paths — sweep PR territory.
  • CI gate that fails the build on missing translations. Lands when there are translations to be missing.

Test plan

  • pnpm exec nx run-many -t lint test build --projects=portal-shell — green (36 / 36 specs unchanged).
  • pnpm exec nx build portal-shell --configuration=production — produces both locale folders with correct <html lang> and <base href>.
  • pnpm exec nx run portal-shell:extract-i18n — runs cleanly, reports (Messages: 0) as expected.
  • Production initial bundle: 123 kB gzip per locale (vs 121 kB on main; +1.5 kB for the @angular/localize runtime polyfill). Both stay well under the 300 KB budget.
  • Manual: pnpm exec nx serve portal-shell runs unchanged (source locale en, no --localize in dev for now).
## Summary First implementation step of ADR-0019. Wire the `@angular/localize` plumbing into `portal-shell` so the next sweep PR can start marking UI strings without any infrastructure work. ## What changes - **Promote `@angular/localize` to a direct dependency** (it was already a transitive via the Angular metapackage; promoting it makes the `init` polyfill explicitly resolvable from the project). - **Configure the `i18n` block** in [`apps/portal-shell/project.json`](apps/portal-shell/project.json): - `sourceLocale: { code: "en", baseHref: "/en/" }` — matches the project English-only rule. - `locales.fr: { translation: "...messages.fr.xlf", baseHref: "/fr/" }` — single target locale for now. - **Add the `init` polyfill** to the build target (`"polyfills": ["@angular/localize/init"]`). - **Add an `extract-i18n` Nx target** that wraps Angular's `@angular/build:extract-i18n` executor and drops the source XLF next to the translation files. - **Enable `--localize` on the production build** — `nx build portal-shell --configuration=production` now emits two folders side by side: `dist/apps/portal-shell/browser/en/` and `.../fr/`. Each carries its own `<html lang>` and `<base href>` per the ADR. - **Seed an empty `messages.fr.xlf`** (XLIFF 1.2 skeleton with sourceLanguage="en" / targetLanguage="fr" and an inline editor convention note). The sweep PR drops `<trans-unit>` entries directly into the body block. ## Verification ``` dist/apps/portal-shell/browser/ ├── en/ │ └── index.html ← <html lang="en">, <base href="/en/"> └── fr/ └── index.html ← <html lang="fr">, <base href="/fr/"> ``` Until the sweep PR marks strings, both bundles ship the same English source text — that's expected and matches what the ADR calls out ("the FR bundle falls back to source text for every untranslated key"). ## What this PR explicitly does NOT do - **Mark UI strings.** Every `i18n` attribute / `$localize` call lands in the next PR. Pure infra commit here. - **Locale switcher in the footer** + `__Host-portal_locale` cookie + smart `/` redirect. Lands once switching shows a meaningful difference. - **Collapse `/accessibility` + `/accessibilite` into a single localised route.** Depends on marked text + localized route paths — sweep PR territory. - **CI gate** that fails the build on missing translations. Lands when there are translations to be missing. ## Test plan - [x] `pnpm exec nx run-many -t lint test build --projects=portal-shell` — green (36 / 36 specs unchanged). - [x] `pnpm exec nx build portal-shell --configuration=production` — produces both locale folders with correct `<html lang>` and `<base href>`. - [x] `pnpm exec nx run portal-shell:extract-i18n` — runs cleanly, reports `(Messages: 0)` as expected. - [x] Production initial bundle: **123 kB gzip per locale** (vs 121 kB on `main`; +1.5 kB for the `@angular/localize` runtime polyfill). Both stay well under the 300 KB budget. - [ ] Manual: `pnpm exec nx serve portal-shell` runs unchanged (source locale `en`, no `--localize` in dev for now).
julien added 1 commit 2026-05-11 16:45:44 +02:00
feat(portal-shell): wire @angular/localize plumbing per ADR-0019
CI / commits (pull_request) Successful in 1m55s
CI / scan (pull_request) Successful in 2m14s
CI / check (pull_request) Successful in 2m26s
CI / a11y (pull_request) Successful in 2m9s
CI / perf (pull_request) Failing after 3m41s
d762238a30
Lay down the i18n infrastructure ahead of the string sweep that
marks every UI label. No template or code changes yet — that comes
next.

What this commit does:

- Add `@angular/localize` as a direct dependency (it was a transitive
  via the Angular metapackage; promoting it makes the `init` polyfill
  resolvable).
- Configure the i18n block in `apps/portal-shell/project.json`:
  source locale `en` (matches the project English-only rule, with
  `baseHref: /en/`), one target locale `fr` (with
  `baseHref: /fr/` and `translation` pointing at the seed XLF).
- Add `@angular/localize/init` to the `polyfills` array so the
  runtime resolver is available before the app bootstraps.
- Add an `extract-i18n` Nx target that wraps Angular's
  `@angular/build:extract-i18n` executor, dropping the source XLF
  next to the translation files (`apps/portal-shell/src/locale/`).
- Enable `--localize` on the production build configuration. The
  build now emits two folders — `dist/apps/portal-shell/browser/en/`
  and `.../fr/` — each with the right `<html lang>` and `<base href>`.
- Seed an empty `messages.fr.xlf` (XLIFF 1.2 skeleton) so the build
  is happy and the sweep PR can drop trans-units into it directly.

What is intentionally out of scope:

- String marking (the sweep PR — every existing UI label, then
  populate `messages.fr.xlf` with translations).
- Locale switcher in the footer + `__Host-portal_locale` cookie + the
  smart `/` redirect (lands once strings are translated and the user
  can meaningfully switch).
- Collapsing `/accessibility` + `/accessibilite` into a single
  localised route (lands with the sweep, since it requires marked
  text + localized route paths).
- CI gate that fails the build on missing translations (lands when
  there are translations to be missing).
julien merged commit 29d16c7527 into main 2026-05-11 16:46:08 +02:00
julien deleted branch feat/portal-shell/i18n-plumbing 2026-05-11 16:46:11 +02:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: julien/apf_portal#91