Skip to content

refactor(reports): remove TFunction from reportPdf/* so ADR-034 locale decoupling is compiler-enforced #2001

Description

@steilerDev

As a homeowner exporting a bank report in a language other than my UI language, I want every string in the generated PDF to come from the report language so that the document I hand to my bank is entirely in one language, with no possibility of a stray UI-locale string leaking in.

Parent Epic: none (Bank Report Wizard mini-epic)
Priority: Should Have

Context

#1993 / #2000 moved the PDF page-footer label out of a t() call inside reportPdf/* and into ReportContentLabels, populated once via reportT. #1938 did the same for the page header. Three call sites of the identical shape remain on beta:

Location Key
client/src/lib/reportPdf/coverLetterPdf.ts:46 sourceReports.coverLetter.reference
client/src/lib/reportPdf/coverLetterPdf.ts:54 sourceReports.coverLetter.subjectLabel
client/src/lib/reportPdf/overviewPdf.ts:844 sourceReports.table.${reason} (skip-footnote reason — dynamic key)

merge.ts still threads its t parameter into buildCoverLetterContent (line 114) and buildOverviewContent (line 118), so TFunction remains a parameter of all three reportPdf/* modules.

These are not live user-visible bugs. The sole production caller (ReportWizardPage.tsx:318) injects reportT (= i18n.getFixedT(reportLanguage, 'budget')), so all three currently resolve in the report language. They are latent hazards: ADR-034's "reportPdf/* modules must never reach the ambient locale" is enforced only by the convention that every caller remembers to pass reportT rather than t. ReportWizardPage.tsx has both variables in scope in the same component, one character apart.

Fixing these one call site at a time has now happened twice (#1938, #1993) and leaves the next one to be found by review. This story closes the class.

Acceptance Criteria

  • 1. ReportContentLabels gains fields for the three strings above; each is populated in buildReportContent.ts via reportT, in the same budget namespace reportT is bound to.
  • 2. The skip-footnote reason at overviewPdf.ts:844 uses a dynamic key — its resolution is moved to where reportT is available, and every reason value the code can produce resolves to a real string in both en and de (no key echoed into the PDF).
  • 3. buildCoverLetterContent and buildOverviewContent no longer accept a TFunction parameter.
  • 4. generateReportPdf no longer accepts a TFunction parameter, and ReportWizardPage.tsx:313 is updated accordingly.
  • 5. grep -rn "TFunction" client/src/lib/reportPdf/ --include=*.ts | grep -v test returns no matches in production files — the contract is compiler-enforced, not convention-enforced.
  • 6. Every key added in AC 1–2 exists in both client/src/i18n/en/budget.json and client/src/i18n/de/budget.json.
  • 7. A regression test fails if any of the three strings is resolved through a TFunction instead of reportContent.labels. The test must be verified to discriminate — confirm it fails when the production line is reverted, not merely that it passes as written.
  • 8. npm run typecheck passes. Note the trap from fix(reports): pageFooter uses injected report-language label instead of ambient locale (#1993) #2000: adding required fields to ReportContentLabels breaks every explicitly-typed construction site, and ts-jest emits no type diagnostics here — a fully green Jest run does not mean the field is wired everywhere. Sweep all construction sites (ReportContentEditor.test.tsx was the one missed in fix(reports): pageFooter uses injected report-language label instead of ambient locale (#1993) #2000).
  • 9. Quality Gates green.

Notes

Metadata

Metadata

Assignees

No one assigned

    Projects

    Status
    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions