--- phase: 14-rss-email-alert-ingestion-module-rollout plan: 05 type: execute wave: 4 depends_on: [14-02, 14-03, 14-04] files_modified: - apps/web/src/messages/de.json - apps/web/src/messages/en.json - apps/web/src/messages/tenderRadar-parity.spec.ts - apps/web/src/app/(portal)/modules/tender-radar/page.tsx - apps/web/src/app/(portal)/modules/tender-radar/components/ResultsList.tsx - apps/web/src/app/(portal)/modules/tender-radar/components/FilterPanel.tsx - apps/web/src/app/(portal)/modules/tender-radar/components/SavedSearchBar.tsx - apps/web/src/app/(portal)/modules/tender-radar/components/TenderDetail.tsx - apps/web/src/app/(portal)/modules/tender-radar/components/CoverageBanner.tsx - apps/web/src/app/(portal)/modules/tender-radar/settings/page.tsx - apps/web/src/app/(portal)/modules/tender-radar/settings/components/SourceConfigForm.tsx - apps/web/src/app/(portal)/modules/tender-radar/settings/components/RssFeedListForm.tsx - apps/web/src/app/(portal)/modules/tender-radar/settings/components/EmailAlertConfigForm.tsx autonomous: true requirements: [CONFIG-03] must_haves: truths: - "The entire tender-radar UI (results, filters, saved searches, detail, coverage, settings, RSS/email forms) renders via a tenderRadar i18n namespace, usable in DE and EN" - "de.json and en.json have identical key sets under tenderRadar (no missing translation on either side)" artifacts: - "tenderRadar namespace added to de.json + en.json with EN translations authored by Claude" - "tenderRadar-parity.spec.ts enforcing de/en key-set equality" key_links: - "Every tender-radar component/page reads strings via useTranslations('tenderRadar') — no hardcoded German literals remain" --- Complete the bilingual module rollout (CONFIG-03/UI-06 i18n, D-10/D-11): introduce a new `tenderRadar` namespace in de.json + en.json and convert every tender-radar component and page from hardcoded German strings to `useTranslations('tenderRadar')` keys. No visible language switcher (D-11) — locale continues to follow the existing next-intl cookie mechanism. EN translations are authored here with consistent Vergabe-domain terminology. Purpose: The module was intentionally built with German MVP-stub strings; this is the point that convention is retired for tender-radar specifically. Output: A complete `tenderRadar` namespace (DE + EN), all components converted, and a de/en key-parity guard test. ## Phase Goal (MVP user story) **As a** user, **I want to** use the entire Ausschreibungs-Radar UI in German or English, **so that** English-speaking colleagues can work with it without a German-only barrier. Slice: Task 1 lands the namespace + parity guard; Task 2 converts the main results/filter components; Task 3 converts the settings + config forms. After Task 3 the whole module honors the selected locale. ## Artifacts this phase produces - New top-level `tenderRadar` namespace in `apps/web/src/messages/de.json` and `en.json` (parallel key sets), covering: page headings, ResultsList, FilterPanel, SavedSearchBar, TenderDetail, CoverageBanner (incl. denylist "manuell beobachten" block from 14-04), settings page + SourceConfigForm + RssFeedListForm (14-02) + EmailAlertConfigForm (14-03). - `apps/web/src/messages/tenderRadar-parity.spec.ts` — structural de/en key-set equality test for the namespace. - All listed components/pages converted to `useTranslations('tenderRadar')`. @$HOME/.claude/gsd-core/workflows/execute-plan.md @$HOME/.claude/gsd-core/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/14-rss-email-alert-ingestion-module-rollout/14-CONTEXT.md @.planning/phases/14-rss-email-alert-ingestion-module-rollout/14-RESEARCH.md @apps/web/src/i18n/request.ts Task 1: tenderRadar namespace (DE + EN) + de/en key-parity spec apps/web/src/messages/de.json, apps/web/src/messages/en.json, apps/web/src/messages/tenderRadar-parity.spec.ts - apps/web/src/messages/de.json + en.json (top-level namespace shape; dkvFleet namespace as the sibling template for a module namespace) - apps/web/src/i18n/request.ts (locale from NEXT_LOCALE cookie, default 'de' — no code change needed for D-11) - apps/web/src/app/(portal)/marketplace/components/MarketplaceCard.tsx (useTranslations('marketplace') consumption example) - RESEARCH.md i18n section (D-10 namespace shape, key-parity test recommendation) + D-11 (no switcher) Inventory every hardcoded German string across the tender-radar components and pages (page.tsx, ResultsList, FilterPanel, SavedSearchBar, TenderDetail, CoverageBanner, settings/page.tsx, SourceConfigForm, RssFeedListForm, EmailAlertConfigForm). Add a `tenderRadar` object to de.json capturing all of them as keys (group by component, e.g. `results.*`, `filter.*`, `savedSearch.*`, `detail.*`, `coverage.*`, `settings.*`, `rssFeeds.*`, `emailAlerts.*`), preserving the exact current German copy as values. Add the identical key set to en.json with faithful English translations using consistent Vergabe-domain terminology (Ausschreibung → tender, Vergabestelle → contracting authority, Frist → deadline, Auftragswert → estimated value). Create tenderRadar-parity.spec.ts (apps/web, jsdom) that imports both JSON files and asserts the recursively-flattened key sets under `tenderRadar` are identical (fails listing any key present in one locale but not the other). Do NOT add a language switcher (D-11). cd apps/web && npx vitest run src/messages/tenderRadar-parity.spec.ts && npx tsc --noEmit - de.json and en.json both contain a `tenderRadar` namespace with identical (recursively-flattened) key sets. - tenderRadar-parity.spec.ts passes and would fail on any de/en key mismatch. - No language-switcher UI element is introduced. The tenderRadar namespace exists in both locales with enforced key parity and authored EN translations. Task 2: Convert results-side components to useTranslations apps/web/src/app/(portal)/modules/tender-radar/page.tsx, apps/web/src/app/(portal)/modules/tender-radar/components/ResultsList.tsx, apps/web/src/app/(portal)/modules/tender-radar/components/FilterPanel.tsx, apps/web/src/app/(portal)/modules/tender-radar/components/SavedSearchBar.tsx, apps/web/src/app/(portal)/modules/tender-radar/components/TenderDetail.tsx, apps/web/src/app/(portal)/modules/tender-radar/components/CoverageBanner.tsx - apps/web/src/app/(portal)/modules/tender-radar/page.tsx (headings) - apps/web/src/app/(portal)/modules/tender-radar/components/{ResultsList,FilterPanel,SavedSearchBar,TenderDetail,CoverageBanner}.tsx (strings to replace) - apps/web/src/app/(portal)/modules/tender-radar/components/ResultsList.test.tsx + SavedSearchBar.test.tsx + TenderDetail.test.tsx + CoverageBanner.test.tsx (existing tests — update next-intl mock so t(key) resolves; mirror marketplace tenant-selector.test.tsx mock) - tenderRadar keys added in Task 1 Convert page.tsx, ResultsList.tsx, FilterPanel.tsx, SavedSearchBar.tsx, TenderDetail.tsx, and CoverageBanner.tsx to read every user-facing string via `const t = useTranslations('tenderRadar')` and `t('...')` (or `t.rich`/interpolation where the current copy contains values). Replace all German literals with their tenderRadar keys from Task 1 — no hardcoded user-facing German text may remain in these files. Update the affected component tests to provide a next-intl `useTranslations` mock (returning the key or a lookup) so they still pass, mirroring the marketplace test mock convention. Preserve behavior and markup — this is a string-source swap only. cd apps/web && npx vitest run "src/app/(portal)/modules/tender-radar/components" && npx tsc --noEmit - `grep -nP "[A-Za-zÄÖÜäöüß]{4,}" ` audit: no user-facing German string literals remain in the six converted files (strings come from t()). - Each converted component imports useTranslations('tenderRadar'). - Updated component tests pass; web tsc clean. The results list, filters, saved searches, detail overlay, coverage banner, and page headings all render via the tenderRadar namespace. Task 3: Convert settings + config forms to useTranslations apps/web/src/app/(portal)/modules/tender-radar/settings/page.tsx, apps/web/src/app/(portal)/modules/tender-radar/settings/components/SourceConfigForm.tsx, apps/web/src/app/(portal)/modules/tender-radar/settings/components/RssFeedListForm.tsx, apps/web/src/app/(portal)/modules/tender-radar/settings/components/EmailAlertConfigForm.tsx - apps/web/src/app/(portal)/modules/tender-radar/settings/page.tsx (headings + digest section strings) - apps/web/src/app/(portal)/modules/tender-radar/settings/components/SourceConfigForm.tsx (labels, validation/save messages) - apps/web/src/app/(portal)/modules/tender-radar/settings/components/RssFeedListForm.tsx (from Plan 14-02) - apps/web/src/app/(portal)/modules/tender-radar/settings/components/EmailAlertConfigForm.tsx (from Plan 14-03) - apps/web/src/app/(portal)/modules/tender-radar/settings/components/SourceConfigForm.test.tsx (update next-intl mock) - tenderRadar keys added in Task 1 Convert settings/page.tsx, SourceConfigForm.tsx, RssFeedListForm.tsx, and EmailAlertConfigForm.tsx to `useTranslations('tenderRadar')`, replacing every label, placeholder, button, validation message, and success/error string with its tenderRadar key. For dynamic validation messages that embed numbers (e.g. interval bounds), use next-intl interpolation (`t('...', { min, max })`). Update SourceConfigForm.test.tsx (and any other affected settings test) with the next-intl mock so they pass. No hardcoded user-facing German text may remain in these four files. cd apps/web && npx vitest run "src/app/(portal)/modules/tender-radar/settings" src/messages/tenderRadar-parity.spec.ts && npx tsc --noEmit - settings/page.tsx + the three config forms read all user-facing strings via useTranslations('tenderRadar'). - No user-facing German literal remains in these four files. - Settings tests pass; parity spec still green (any key added here exists in both locales); web tsc clean. The entire tender-radar settings surface (source config, RSS feeds, email alerts, digest) is bilingual. ## Trust Boundaries | Boundary | Description | |----------|-------------| | (none new) | i18n string-source swap; no new data flow or input surface | ## STRIDE Threat Register | Threat ID | Category | Component | Severity | Disposition | Mitigation Plan | |-----------|----------|-----------|----------|-------------|-----------------| | T-14-05-01 | Tampering | de/en key drift | low | mitigate | tenderRadar-parity.spec.ts fails the build on any missing/extra key in either locale | | T-14-05-SC | Tampering | npm/pip/cargo installs | low | accept | No new packages — next-intl already installed | - `cd apps/web && npx vitest run "src/app/(portal)/modules/tender-radar" src/messages/tenderRadar-parity.spec.ts && npx tsc --noEmit` — all tender-radar web tests + parity green. - Manual: toggling the NEXT_LOCALE cookie de↔en switches the whole module UI. - Entire tender-radar UI usable in DE and EN via the tenderRadar namespace (CONFIG-03, D-10). - de/en key parity enforced by test; no language switcher added (D-11). Create `.planning/phases/14-rss-email-alert-ingestion-module-rollout/14-05-SUMMARY.md` when done.