| 14-rss-email-alert-ingestion-module-rollout |
05 |
ui |
| i18n |
| next-intl |
| react |
| tender-radar |
|
| phase |
provides |
| 14-rss-email-alert-ingestion-module-rollout |
RssFeedListForm (14-02), EmailAlertConfigForm (14-03), CoverageBanner denylist block (14-04) |
|
|
| tenderRadar i18n namespace (DE + EN) covering the entire tender-radar module UI |
| de/en key-parity guard test (tenderRadar-parity.spec.ts) |
| All tender-radar components/pages converted from hardcoded German to useTranslations('tenderRadar') |
|
| any future tender-radar UI work |
| platform-wide i18n rollout for other modules |
|
| added |
patterns |
|
|
| Module i18n namespace: top-level {moduleName} key in de.json/en.json, grouped by component (results.*, filter.*, savedSearch.*, detail.*, coverage.*, settings.*, sourceConfig.*, rssFeeds.*, emailAlerts.*) |
| de/en key-parity spec: recursively flatten both locale JSON objects under the namespace and assert identical sorted key sets — catches any future one-sided edit |
| Filter option label translation while preserving the backend-matched data value: FilterPanel's Bundesland/CPV constants keep a stable `code` (NUTS-1 prefix / CPV division code) used only as the i18n lookup key; the actual filter `value` sent to the API stays the untranslated canonical string |
| Test mock convention: vi.mock('next-intl', () => ({ useTranslations: () => (key, params) => lookupTable[key] ?? key })) — mirrors apps/web's existing marketplace test convention (tenant-selector.test.tsx) |
|
|
| created |
modified |
| apps/web/src/messages/tenderRadar-parity.spec.ts |
|
| apps/web/src/messages/de.json |
| apps/web/src/messages/en.json |
| 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/ResultsList.test.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/SavedSearchBar.test.tsx |
| apps/web/src/app/(portal)/modules/tender-radar/components/TenderDetail.tsx |
| apps/web/src/app/(portal)/modules/tender-radar/components/TenderDetail.test.tsx |
| apps/web/src/app/(portal)/modules/tender-radar/components/CoverageBanner.tsx |
| apps/web/src/app/(portal)/modules/tender-radar/components/CoverageBanner.test.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/SourceConfigForm.test.tsx |
| apps/web/src/app/(portal)/modules/tender-radar/settings/components/RssFeedListForm.tsx |
| apps/web/src/app/(portal)/modules/tender-radar/settings/components/RssFeedListForm.test.tsx |
| apps/web/src/app/(portal)/modules/tender-radar/settings/components/EmailAlertConfigForm.tsx |
| apps/web/src/app/(portal)/modules/tender-radar/settings/components/EmailAlertConfigForm.test.tsx |
|
|
| Proper-noun portal display labels (DÖE, DTVP, tender24, lhs-vpbw, vergabe.landbw) in TenderDetail.portalLabel() are left as hardcoded string constants, not routed through t() — they are brand/identifier abbreviations, not translatable prose, matching the same treatment protocol names (IMAP, STARTTLS, SSL-TLS) already receive |
| FilterPanel's Bundesland option *value* (the string sent to the backend filter query) stays the canonical German name unchanged — apps/api's nutsPrefixFor() matches against that exact string server-side and D-10 does not touch the API contract; only the rendered *label* is translated, keyed by the NUTS-1 prefix code (DE1..DEG) |
| CPV division labels (44 EU CPV vocabulary division descriptions) were moved into the tenderRadar namespace and translated with official EU CPV English division titles — these are genuine multi-word German prose that would otherwise fail the plan's grep-based German-literal audit even though they were originally embedded as 'constant data' rather than typical UI copy |
| Date formatting (toLocaleDateString('de-DE')) in ResultsList/TenderDetail is left unchanged regardless of locale — plan explicitly scoped this as a 'string-source swap only', not a full locale-aware formatting upgrade |
|
|
| id |
description |
requirement |
verification |
human_judgment |
| D1 |
tenderRadar i18n namespace added to de.json + en.json with EN translations authored by Claude, enforced by tenderRadar-parity.spec.ts |
CONFIG-03 |
| kind |
ref |
status |
| unit |
apps/web/src/messages/tenderRadar-parity.spec.ts#tenderRadar i18n namespace — de/en key parity |
pass |
|
|
false |
|
| id |
description |
requirement |
verification |
human_judgment |
| D2 |
Results-side components (page.tsx, ResultsList, FilterPanel, SavedSearchBar, TenderDetail, CoverageBanner) converted to useTranslations('tenderRadar'); no hardcoded user-facing German text remains |
CONFIG-03 |
| kind |
ref |
status |
| unit |
apps/web/src/app/(portal)/modules/tender-radar/components/ResultsList.test.tsx (8 tests) |
pass |
|
| kind |
ref |
status |
| unit |
apps/web/src/app/(portal)/modules/tender-radar/components/SavedSearchBar.test.tsx (9 tests) |
pass |
|
| kind |
ref |
status |
| unit |
apps/web/src/app/(portal)/modules/tender-radar/components/TenderDetail.test.tsx (5 tests) |
pass |
|
| kind |
ref |
status |
| unit |
apps/web/src/app/(portal)/modules/tender-radar/components/CoverageBanner.test.tsx (4 tests) |
pass |
|
|
false |
|
| id |
description |
requirement |
verification |
human_judgment |
| D3 |
Settings surface (settings/page.tsx, SourceConfigForm, RssFeedListForm, EmailAlertConfigForm) converted to useTranslations('tenderRadar') |
CONFIG-03 |
| kind |
ref |
status |
| unit |
apps/web/src/app/(portal)/modules/tender-radar/settings/components/SourceConfigForm.test.tsx (4 tests) |
pass |
|
| kind |
ref |
status |
| unit |
apps/web/src/app/(portal)/modules/tender-radar/settings/components/RssFeedListForm.test.tsx (6 tests) |
pass |
|
| kind |
ref |
status |
| unit |
apps/web/src/app/(portal)/modules/tender-radar/settings/components/EmailAlertConfigForm.test.tsx (5 tests) |
pass |
|
|
false |
|
| id |
description |
verification |
human_judgment |
rationale |
| D4 |
No language-switcher UI element introduced (D-11) — locale continues to follow the existing next-intl NEXT_LOCALE cookie mechanism, unchanged |
|
true |
Absence-of-a-feature is a structural/visual claim best confirmed by a human scan of the diff rather than an automated assertion; no new UI surface was added anywhere in this plan's file list. |
|
|
~50min |
2026-07-23 |
complete |