Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

273 lines
61 KiB
Markdown

# Scratchpad -- MSP i18n: Quotes Sub-batch
- Plan slug: `2026-04-09-msp-i18n-quotes`
- Created: `2026-04-09`
- Last synced to codebase: `2026-04-17`
## Status Recheck (2026-04-17)
**Still 0% implemented.** Verified against the current codebase:
- `server/public/locales/en/msp/quotes.json`**does not exist**.
- All 12 files under `packages/billing/src/components/billing-dashboard/quotes/` still have `useTranslation=0` (confirmed by grep).
- `features.json` / `tests.json`: 0/22 features, 0/27 tests marked implemented.
- `nav.billing.sections.quotes`, `nav.billing.quotes`, `nav.billing.quoteBusinessTemplates`, `nav.billing.quoteLayouts` **still missing** from `server/public/locales/en/msp/core.json` (verified via grep). The `F022`/`T027` backfill remains outstanding.
### Upstream changes since the 2026-04-10 addendum (affect this plan)
| Commit | What changed | Impact on this plan |
|---|---|---|
| `7da29f66c add footer for most of them` (touches `QuoteForm.tsx`, `QuoteDetail.tsx`, `QuoteLineItemsEditor.tsx`) | Footer UI added to quote components. | Include footer labels in the existing `quoteForm.*` / `quoteDetail.*` / `quoteLineItems.*` groups. No new namespace needed. |
| `ead79deec Fix quote recipients field build errors` | Build fix on `QuoteSendRecipientsField.tsx`. | `QuoteSendRecipientsField.tsx` is now 396 LOC (PRD estimated 403). Strings enumerated in 2026-04-10 addendum remain the same; `F021`/`quoteRecipients.*` group still correct. |
| `8528a0816 enums translated` (merged via `i18n/more_enum_hooks` / PR #2344) — finishes the shared **enum-labels pattern** for billing enums. `useBillingFrequencyOptions` / `useFormatBillingFrequency` / `useContractLineTypeOptions` / `useFormatContractLineType` published from `@alga-psa/billing/hooks/useBillingEnumOptions`; keys in `features/billing.json#enums.*`. | `QuoteLineItemsEditor.tsx` renders a frequency select (Weekly / Monthly / Quarterly / Annually — see "Decisions" below). `QuoteStatusBadge.tsx` still blocked on status-metadata translation. | **New guidance:** replace the local `frequency` option list in `QuoteLineItemsEditor.tsx` with `useBillingFrequencyOptions()`. Do NOT duplicate those labels into `msp/quotes`. Verify `/msp/billing`, `/msp/quote-approvals`, and `/msp/quote-document-templates` entries in `ROUTE_NAMESPACES` load `features/billing` (the PRD already adds `msp/quotes` to `/msp/billing`, which already has `features/billing`). |
| `QuoteLineItemsEditor.tsx` now 666 LOC (PRD estimate: 676). No behavioral drift beyond markup badge already captured in the 2026-04-10 addendum. | — | No change to `F006` / `F020`. |
### PRD correction — `QuoteStatusBadge` follow-up is now unblocked
The 2026-04-09 decision excluded `QuoteStatusBadge.tsx` because `QUOTE_STATUS_METADATA` labels were baked in at the types layer. The enum-labels pattern shipped 2026-04-14 is the pattern that solves this (see `.ai/translation/enum-labels-pattern.md`). Options:
1. **Preferred:** publish a `useQuoteStatusLabel()` / `useQuoteStatusOptions()` hook from `@alga-psa/types` or `@alga-psa/billing/hooks/`, with keys under `features/billing.json#enums.quoteStatus.*`. Then `QuoteStatusBadge.tsx` becomes a thin consumer of the hook. Zero changes needed in `msp/quotes`.
2. Leave out-of-scope per original plan, and track a dedicated "quote status labels via enum-hook" plan.
Recommend (1) — it removes the only remaining untranslated quote surface and matches the pattern now adopted project-wide. Add as a **new feature F023** (wire `QuoteStatusBadge` via shared hook) and a corresponding **T028** (pseudo-locale visibility test on status badges).
### No structural changes otherwise
Existing F001-F022 / T001-T027 remain valid. Proceed with the corrected frequency-options source and, if accepted, add F023/T028 for `QuoteStatusBadge`.
---
## Decisions
- (2026-04-09) **QuoteStatusBadge.tsx excluded from wiring**: This component reads labels from `QUOTE_STATUS_METADATA` in `@alga-psa/types`. Translating those labels requires a types-level change (making the metadata accept a `t()` function or switching to key-based lookup). Out of scope for this batch; track as follow-up.
- (2026-04-09) **quoteLineItemDraft.ts excluded from wiring**: Pure logic/data utility with no rendered JSX. The only user-visible output is `formatDraftQuoteMoney()` which uses `Intl.NumberFormat` -- already locale-capable via the formatter replacement (F012).
- (2026-04-09) **Single namespace for all 12 files**: All quote components live in the same directory and share significant vocabulary (Cancel, Back, Save, Send, Delete, etc.). A single `msp/quotes` namespace avoids cross-namespace imports and keeps the bundle small.
- (2026-04-09) **Execution order**: F001 (scaffold namespace) -> F002-F011 (wire components, largest first) -> F012-F013 (formatters) -> F014-F017 (translations + validation) -> F018 (routes) -> F019 (build check).
- (2026-04-09) **Currency formatting replacement**: Five components define local `formatCurrency()` helpers using `new Intl.NumberFormat('en-US', ...)`. These should be replaced with `useFormatters().formatCurrency()` or a shared locale-aware helper. The `quoteLineItemDraft.ts` utility also has `formatDraftQuoteMoney()` with `en-US` -- this should be made locale-aware as part of F012.
- (2026-04-09) **Date formatting replacement**: Four components define local `formatDate()` helpers using `.toLocaleDateString()` with no locale argument (defaults to browser locale, which is correct). However, `QuoteDocumentTemplateEditor.tsx` uses `.toLocaleString()` for timestamps. These should use `useFormatters()` for consistency.
## Post-planning additions (2026-04-10)
- **(2026-04-10)** **MSP sidebar Quotes section renders raw English even in xx/de.** While doing the billing-dashboard xx smoke test, the left-hand MSP navigation consistently showed `Quotes / Quote Templates / Quote Layouts` in English under a `QUOTES` section header, while every other entry pseudo-translated cleanly. Root cause: `server/src/config/menuConfig.ts:308-317` added a Quotes section that references four translation keys that were never backfilled into `msp/core.json`:
- `nav.billing.sections.quotes` (section header)
- `nav.billing.quotes` (Quotes item)
- `nav.billing.quoteBusinessTemplates` (Quote Templates item; note: href=`/msp/billing?tab=quote-business-templates`)
- `nav.billing.quoteLayouts` (Quote Layouts item; note: href=`/msp/billing?tab=quote-templates`)
Every other `nav.billing.*` key is present in all 9 locale files under `msp/core.json` (contractTemplates, clientContracts, invoicing, accountingExports, usageTracking, etc.). Only the Quotes block is missing — so the sidebar falls back to the hardcoded English `name` field in `menuConfig.ts`.
**Decision:** keep these keys in `msp/core` (where the rest of `nav.billing.*` lives) rather than introducing cross-namespace navigation. The fix is a pure locale-file backfill — no code change in `menuConfig.ts`, and no dependency on the `msp/quotes` namespace load order. Tracked as `F022` / `T027` below.
- **(2026-04-10)** **Product markup on quote line items** landed after the planning commit (commits `6a40d09fa` "Show product markup on quote line items" and `3afa5763f` "Add recipient picker and discount-aware markup"). `QuoteLineItemsEditor.tsx` now renders, for product-kind items only:
- A live markup badge in the Unit Price cell: `` `${sign}${markup.toFixed(1)}% markup` `` -- needs a translation key with `{{value}}` interpolation (and probably a separate key for the sign, or format the whole string via t with `{{signedValue}}`).
- A "Markup unavailable" label + tooltip when `cost_currency` differs from the quote currency. Tooltip text: `` `Markup can't be calculated because cost is tracked in ${item.cost_currency} and this quote is in ${currencyCode}.` `` -- needs `{{costCurrency}}` and `{{quoteCurrency}}` interpolation.
- These all live under a new `quoteLineItems.markup.*` key group. F006 already covers QuoteLineItemsEditor wiring but predates the markup UI, so the wiring pass must explicitly hit these new strings (tracked as `F020`).
- **(2026-04-10)** **New component `QuoteSendRecipientsField.tsx`** (403 LOC) was added in commits `3afa5763f` and `ead79deec` and is consumed by `QuoteDetail.tsx` and `QuoteForm.tsx` in their send dialogs. It is a searchable combobox with its own user-visible strings, none of which are covered by F002/F003:
- Trigger label (three-way ternary): `'Select a client first'`, `'No users or contacts available'`, `'Add internal user or client contact…'`.
- Search input `placeholder="Search by name or email…"`.
- Empty states: `'No recipients available'` and `'No matches'`.
- Kind badge values: `'Internal'` / `'Contact'`.
- Remove button `aria-label={`Remove ${r.email}`}` -- needs `{{email}}` interpolation.
- Tracked as `F021` / `T025` / `T026` below. New key group `quoteRecipients.*`.
## Discoveries / Constraints
### QuoteForm.tsx (1,072 LOC)
- Largest component by string density. Contains: form field labels (Title, Description/Scope, Client, Contact, Currency, Quote Date, Valid Until, PO Number, Notes to Client, Terms & Conditions, Quote Layout), heading variants (New Quote, Edit Quote, New Quote Template, Edit Quote Template, Quote QQQ vN), workflow action buttons for every status, three embedded dialogs (send, approval, conversion preview), totals section (Subtotal, Discounts, Tax, Total), status banners (Accepted, Rejected, Converted), read-only notice.
- The heading logic is complex with conditional template/edit/read-only/quote-number branching -- use a computed key or switch statement with t().
- Conversion dialog preview is duplicated between QuoteForm and QuoteDetail. Could share keys under `quoteConversion.*`.
- `formatCurrency` is a local closure using `form.currency_code` -- will need to use the formatter's currency parameter.
### QuoteDetail.tsx (1,106 LOC)
- Similar to QuoteForm but read-only. Additional sections: Version History, Scope of Work, Activity Log, Quote Layout (with template selector), Client Notes, Internal Notes, Terms & Conditions.
- Has `formatQuoteNumber()` helper -- "Template quote" fallback string needs translation.
- Accepted/rejected status sections have inline status text with bold labels ("Accepted by:", "Accepted on:", "Rejected on:", "Reason:").
- Line item table differs from QuoteLineItemsEditor (read-only, simpler columns). Has client selection badges for optional items ("Client selected this optional item" / "Client declined this optional item").
- Activity log renders `activity.activity_type.replace(/_/g, ' ')` -- these are server-side enum values, likely should stay untranslated or get a separate mapping. Leave as-is for now.
### QuotesTab.tsx (656 LOC)
- Contains `BASE_QUOTE_COLUMNS` defined at module level as a constant array. Column titles ('Quote #', 'Client', 'Title', 'Total', 'Status', 'Date') need t() but the array is outside the component. Will need to move column definitions inside the component or use a factory function.
- Sub-tab labels use template literals with counts: `` `Active (${count})` ``. Use t() with `{{count}}` interpolation.
- Two dialogs: delete confirmation and send quote. Send dialog has additional recipients and message fields.
- `QuoteSubTabContent` is an inner component -- needs to share the same t() or get its own hook call.
### QuoteDocumentTemplateEditor.tsx (596 LOC)
- Has design/code/preview tabs with pipeline status labels (Shape, Render).
- Template name/version form fields.
- "Code view is generated from the Visual workspace and is read-only." info alert.
- Preview section has "Sample Scenario" label, scenario descriptions (from imported data -- may not be translatable here), and pipeline error messages.
- Created/Updated timestamps.
- "Template name is required." validation message.
### QuoteLineItemsEditor.tsx (585 LOC)
- Table headers at module level inside `renderItemRows`: Move, Item, Billing, Flags, Qty, Unit Price, Total, Actions.
- Checkbox labels: "Optional", "Recurring".
- Frequency select options: Weekly, Monthly, Quarterly, Annually.
- Discount panel: "Percentage discount", "Fixed discount", "Whole quote", "Specific item", "Specific service", "Applies to the full quote subtotal".
- Phase/section label: "Phase / Section", placeholder "e.g. Discovery, Rollout, Ongoing".
- Dynamic text: "Custom item", section item count "{n} item(s)", "Set price", "No price in {currency}".
- Collapse/Expand toggle labels.
- "Ungrouped Items" as a phase fallback label.
### QuoteDocumentTemplatesPage.tsx (299 LOC)
- Page heading "Quote Layouts", description, "New Layout" button.
- Table columns: Name, Source, Default, Actions.
- Action menu: Edit, Edit as Copy, Clone, Set as Default, Delete.
- Uses `confirm()` for delete -- should be replaced with a proper translated confirmation, but that is a UX change beyond i18n scope. At minimum translate the confirm message string.
- "(Standard)" suffix on template names.
### QuoteConversionDialog.tsx (293 LOC)
- Standalone dialog used from multiple places (though QuoteForm and QuoteDetail have their own inline conversion dialogs too).
- Mode labels: "Contract Only", "Invoice Only", "Contract + Invoice".
- Mode descriptions are full sentences explaining what each mode does.
- Item mapping preview with category headings and counts.
- Summary section: "Quote Total", "Status After Conversion" -> "Converted".
- Partial conversion alert with dynamic text.
### QuoteApprovalDashboard.tsx (245 LOC)
- Heading "Quote Approvals", description paragraph.
- Approval required toggle with two conditional description strings.
- Status filter: "Pending Approval", "Approved".
- Table columns: Quote #, Client, Title, Amount, Status, Quote Date, Valid Until.
- Empty state with dynamic status text.
- "Back to Quotes" button.
### QuoteTemplatesList.tsx (215 LOC)
- Description paragraph about what templates do.
- Table columns: Title, Items, Currency, Created, Actions.
- Action menu: Edit Template, Create Quote from Template, Delete.
- Empty state with "Save as Template" reference.
- Delete confirmation dialog.
- "New Template" button.
### QuotePreviewPanel.tsx (215 LOC)
- Panel heading "Quote Preview".
- Template selector with "(Standard)" suffix.
- "Open Quote" and "Download PDF" buttons.
- Empty state: "Select a quote to preview" with icon.
- Loading state: "Loading Preview..."
- Error fallback: "Could not display preview. Data might be missing."
## Key Gotchas
1. **Column definitions at module scope**: `BASE_QUOTE_COLUMNS` in QuotesTab.tsx and column arrays in QuoteApprovalDashboard.tsx / QuoteTemplatesList.tsx are defined as constants outside the component. They need `t()` for titles but `t()` is only available inside the component. Options: (a) move inside component with useMemo, (b) use factory function that takes `t` as parameter. Option (a) is simplest and consistent with other translated components.
2. **Shared conversion dialog content**: QuoteForm.tsx, QuoteDetail.tsx, and QuoteConversionDialog.tsx all have conversion preview UI. Share keys under `quoteConversion.*` to avoid triple-maintaining the same strings.
3. **Template string in headings**: QuoteForm heading uses complex conditional logic. Keep the branching but wrap each branch in t().
4. **Interpolation-heavy strings**: Sub-tab labels like `Active (${count})`, section counts like `${count} item(s)`, and notice messages like `Created draft contract ${name}` need careful interpolation variable placement. Use `{{count}}`, `{{name}}` patterns.
5. **formatCurrency/formatDate helper replacement**: These are defined as standalone functions, not as component methods. Replacing them with `useFormatters()` requires either moving the formatting call inside the component or passing the formatter down. Since these functions are used in column definitions (which will move inside the component per gotcha #1), this should align naturally.
6. **Keep stable values untranslated**: Tab IDs (`active`, `sent`, `closed`, `approval`), query param values (`quotes`, `edit`, `detail`), status enum values (`draft`, `sent`, `accepted`, etc.), CSS class names, element IDs, route paths, `aria-label` values that are already descriptive English.
7. **QuoteStatusBadge is a pass-through**: It renders `metadata.label` from a types constant. The label translation belongs in the types layer or a shared status-label mapping, not in this namespace. Leave it untranslated for now.
8. **Shared billing-frequency hook was missing `weekly`**: `QuoteLineItemsEditor.tsx` still exposes Weekly / Monthly / Quarterly / Annually, but `useBillingFrequencyOptions()` only had monthly/quarterly/annually when this batch started. Added follow-on `F024`/`T029` so the quote editor can adopt the shared enum hook without losing the existing weekly option.
9. **Plan missed remaining formatter hotspots**: After completing `F012`/`F013` for the originally-listed files, `QuoteForm.tsx` still had accepted/rejected banner dates plus a local `en-US` money helper, and `quoteLineItemDraft.ts` still exported a draft-money formatter pinned to `en-US` that surfaced through `QuoteForm` and `QuoteLineItemsEditor`. Added `F025`/`T030` so the checklist matches the PRD acceptance criteria instead of silently leaving those locale-pinned displays behind.
## Progress Log
- (2026-04-19) Completed `F001`: created [server/public/locales/en/msp/quotes.json](/Users/natalliabukhtsik/Desktop/projects/bigmac/server/public/locales/en/msp/quotes.json) with the planned component-scoped roots (`common`, `quotesTab`, `quoteForm`, `quoteDetail`, `quoteLineItems`, `quoteRecipients`, `quoteConversion`, `quoteApproval`, `quoteTemplates`, `quotePreview`, `templateEditor`, `templatesPage`) and seeded the first shared/base English keys from the PRD + component inventory. Expect follow-on wiring passes (`F002`-`F021`) to expand individual leaf keys as each component is converted.
- (2026-04-19) Completed `T001`: added [packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts) with a node-side contract test that parses `en/msp/quotes.json` and asserts the namespace exposes the planned top-level groups in order. Validation command: `cd packages/billing && npx vitest run tests/billing-dashboard/QuotesSubbatch.i18n.test.ts`.
- (2026-04-19) Completed `F002`: wired [packages/billing/src/components/billing-dashboard/quotes/QuoteForm.tsx](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/src/components/billing-dashboard/quotes/QuoteForm.tsx) to `useTranslation('msp/quotes')` for the form chrome, workflow actions, notices/errors, send/approval/conversion dialogs, status banners, and totals footer. Expanded [server/public/locales/en/msp/quotes.json](/Users/natalliabukhtsik/Desktop/projects/bigmac/server/public/locales/en/msp/quotes.json) with the `quoteForm.*` leaf keys now consumed by the component. Validation:
- `rg -n "text=\\\"|title=\\\"|placeholder=\\\"|>[^<{]*[A-Za-z][^<{]*<" packages/billing/src/components/billing-dashboard/quotes/QuoteForm.tsx` -> only non-UI match left was the `runWorkflowAction` helper signature; all rendered copy now flows through `t(...)`.
- `cd packages/billing && npm run typecheck` -> blocked by a pre-existing upstream issue outside this batch: `packages/ui/src/lib/dateFnsLocale.ts` is missing the `pt` locale mapping required by the now-expanded locale union. Track this for the later build/typecheck gate (`F019`/`T024`) unless another batch lands the fix first.
- (2026-04-19) Completed `T003`: extended [packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts) with a `QuoteForm` contract test that asserts the `useTranslation('msp/quotes')` hook is present and that representative form/workflow/dialog keys resolve from `en/msp/quotes.json`. Validation command: `cd packages/billing && npx vitest run tests/billing-dashboard/QuotesSubbatch.i18n.test.ts`.
- (2026-04-19) Completed `T004`: added a negative-source regression check to [packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts) covering the major raw-English literals that used to be rendered directly by `QuoteForm.tsx` (buttons, labels, placeholders, dialog titles, conversion preview headings, and loading text). Validation command: `cd packages/billing && npx vitest run tests/billing-dashboard/QuotesSubbatch.i18n.test.ts`.
- (2026-04-19) Completed `F003`: wired [packages/billing/src/components/billing-dashboard/quotes/QuoteDetail.tsx](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/src/components/billing-dashboard/quotes/QuoteDetail.tsx) to `useTranslation('msp/quotes')` across the page shell, field summaries, totals, version history, status banners, line-item table, converted-record links, and the conversion/preview/send/approval dialogs. Reused the shared `quoteForm.*`, `quoteConversion.*`, `quoteLineItems.*`, and `common.*` keys where the UI copy matches, and expanded [server/public/locales/en/msp/quotes.json](/Users/natalliabukhtsik/Desktop/projects/bigmac/server/public/locales/en/msp/quotes.json) with the remaining `quoteDetail.*` leaves (actions, alerts, notices, errors, preview/loading, and table labels). Validation:
- `rg -n "text=\\\"|title=\\\"|placeholder=\\\"|>[^<{]*[A-Za-z][^<{]*<" packages/billing/src/components/billing-dashboard/quotes/QuoteDetail.tsx` -> no matches; the detail component no longer renders raw English JSX literals directly.
- `node -e "const fs=require('fs'); JSON.parse(fs.readFileSync('server/public/locales/en/msp/quotes.json','utf8')); console.log('quotes.json ok');"` -> parsed successfully after the `quoteDetail.*` additions.
- (2026-04-19) Completed `T005`: extended [packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts) with a `QuoteDetail` contract test that asserts the translation hook is present and that representative detail, dialog, badge, and conversion-preview keys resolve from `en/msp/quotes.json`. Validation command: `cd packages/billing && npx vitest run tests/billing-dashboard/QuotesSubbatch.i18n.test.ts`.
- (2026-04-19) Completed `T006`: added a negative-source regression check to [packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts) covering the major raw-English literals that used to be rendered directly by `QuoteDetail.tsx` (page actions, section headings, converted-record links, dialog titles, and table headers). Validation command: `cd packages/billing && npx vitest run tests/billing-dashboard/QuotesSubbatch.i18n.test.ts`.
- (2026-04-19) Completed `F004`: wired [packages/billing/src/components/billing-dashboard/quotes/QuotesTab.tsx](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/src/components/billing-dashboard/quotes/QuotesTab.tsx) and its inner `QuoteSubTabContent` to `useTranslation('msp/quotes')`, including the sub-tab labels, table column titles, row-action menu labels/aria copy, client filter, empty state, loading state, and the delete/send dialogs. Moved the previously module-scoped quote columns into a translation-aware `useMemo`, and expanded [server/public/locales/en/msp/quotes.json](/Users/natalliabukhtsik/Desktop/projects/bigmac/server/public/locales/en/msp/quotes.json) with the remaining `quotesTab.*` leaves (`actions`, `filters.allClients`, `empty.byCategory`, `errors`, `loading`, and the revised send/delete dialog copy). Validation:
- `rg -n "text=\\\"|title=\\\"|placeholder=\\\"|>[^<{]*[A-Za-z][^<{]*<" packages/billing/src/components/billing-dashboard/quotes/QuotesTab.tsx` -> only remaining matches are non-UI function-type signatures (`onDownload`, `onDuplicate`, `onDownloadPdf`) inside the props interface.
- `node -e "const fs=require('fs'); JSON.parse(fs.readFileSync('server/public/locales/en/msp/quotes.json','utf8')); console.log('quotes.json ok');"` -> parsed successfully after the `quotesTab.*` additions.
- (2026-04-19) Completed `T007`: extended [packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts) with a `QuotesTab` contract test that asserts the translation hook is present and that representative tab, table, action-menu, and dialog keys resolve from `en/msp/quotes.json`. Validation command: `cd packages/billing && npx vitest run tests/billing-dashboard/QuotesSubbatch.i18n.test.ts`.
- (2026-04-19) Completed `T008`: added a negative-source regression check to [packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts) covering the major raw-English literals that used to be rendered directly by `QuotesTab.tsx` (page heading, row-action labels, filter labels, send-dialog copy, and loading text). Validation command: `cd packages/billing && npx vitest run tests/billing-dashboard/QuotesSubbatch.i18n.test.ts`.
- (2026-04-19) Completed `F005`: wired [packages/billing/src/components/billing-dashboard/quotes/QuoteDocumentTemplateEditor.tsx](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/src/components/billing-dashboard/quotes/QuoteDocumentTemplateEditor.tsx) to `useTranslation('msp/quotes')` for the page chrome, layout-detail form labels, tab labels, preview-sample controls, preview-pipeline status/error copy, code-view read-only alert, and footer timestamps. Expanded [server/public/locales/en/msp/quotes.json](/Users/natalliabukhtsik/Desktop/projects/bigmac/server/public/locales/en/msp/quotes.json) with the `templateEditor.*` action/default/error/footer/heading/placeholder/preview/pipeline keys now consumed by the component. Validation:
- `node -e "const fs=require('fs'); JSON.parse(fs.readFileSync('server/public/locales/en/msp/quotes.json','utf8')); console.log('quotes.json ok');"` -> parsed successfully after the `templateEditor.*` additions.
- Source sweep via `rg` confirmed the remaining plain string literals in `QuoteDocumentTemplateEditor.tsx` are technical/internal values (`'visual'`, `'preview'`, `quote-designer-preview-*`, AST kind names, etc.) rather than rendered UI copy.
- (2026-04-19) Completed `T009`: extended [packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts) with a `QuoteDocumentTemplateEditor` contract test that asserts the translation hook is present and that representative editor, preview-pipeline, and footer keys resolve from `en/msp/quotes.json`. Validation command: `cd packages/billing && npx vitest run tests/billing-dashboard/QuotesSubbatch.i18n.test.ts`.
- (2026-04-19) Completed `T010`: added a negative-source regression check to [packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts) covering the major raw-English literals that used to be rendered directly by `QuoteDocumentTemplateEditor.tsx` (editor headings, detail labels, tab labels, preview status copy, and footer labels). Validation command: `cd packages/billing && npx vitest run tests/billing-dashboard/QuotesSubbatch.i18n.test.ts`.
- (2026-04-19) Completed `F024`: extended the shared billing-frequency enum contract with `weekly` in [packages/billing/src/constants/billing.ts](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/src/constants/billing.ts) and all 9 [server/public/locales/*/features/billing.json](/Users/natalliabukhtsik/Desktop/projects/bigmac/server/public/locales/en/features/billing.json) variants. This was required before `QuoteLineItemsEditor.tsx` could adopt `useBillingFrequencyOptions()` without regressing the existing weekly recurrence option noted in the PRD addendum. Validation:
- `node - <<'NODE' ... NODE` audit confirmed every locale now exposes `enums.billingFrequency.weekly` alongside `monthly`, `quarterly`, and `annually`.
- (2026-04-19) Completed `T029`: extended [packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts) with a source-level contract test for the shared billing-frequency enum additions. It asserts `billing.ts` now exports `weekly` in both `BILLING_FREQUENCY_VALUES` and `BILLING_FREQUENCY_LABEL_DEFAULTS`, and that all 9 `features/billing.json` locale files define `enums.billingFrequency.weekly`. Validation command: `cd packages/billing && npx vitest run tests/billing-dashboard/QuotesSubbatch.i18n.test.ts`.
- (2026-04-19) Completed `F006`: wired [packages/billing/src/components/billing-dashboard/quotes/QuoteLineItemsEditor.tsx](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/src/components/billing-dashboard/quotes/QuoteLineItemsEditor.tsx) to `useTranslation('msp/quotes')` for the section heading, table headers, section fallback/count labels, optional/recurring controls, discount badge + target descriptions, discount-panel selectors/actions, empty state, remove button, and inline pricing hints. This pass also switched recurring-frequency options over to `useBillingFrequencyOptions()` / `useFormatBillingFrequency()` and moved the product-markup badge/tooltip text behind `t(...)` in preparation for `F020`. Expanded [server/public/locales/en/msp/quotes.json](/Users/natalliabukhtsik/Desktop/projects/bigmac/server/public/locales/en/msp/quotes.json) with the new `quoteLineItems.actions`, `billingMethods`, `discounts.description/targets/types`, `labels.itemCount*`, and picker-placeholder keys. Validation:
- `node -e "const fs=require('fs'); JSON.parse(fs.readFileSync('server/public/locales/en/msp/quotes.json','utf8')); console.log('quotes.json ok');"` -> parsed successfully after the `quoteLineItems.*` additions.
- `node - <<'NODE' ... NODE` audit confirmed the component now references the planned `quoteLineItems.*` keys for table chrome, controls, and inline hints.
- (2026-04-19) Completed `T011`: extended [packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts) with a `QuoteLineItemsEditor` contract test that asserts the `msp/quotes` hook is present, the shared billing-frequency hooks are used, and representative `quoteLineItems.*` keys resolve from `en/msp/quotes.json`. Validation command: `cd packages/billing && npx vitest run tests/billing-dashboard/QuotesSubbatch.i18n.test.ts`.
- (2026-04-19) Completed `T012`: added a negative-source regression check to [packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts) covering the old raw-English table headers, discount controls, inline price hints, and markup text that used to be rendered directly by `QuoteLineItemsEditor.tsx`. Validation command: `cd packages/billing && npx vitest run tests/billing-dashboard/QuotesSubbatch.i18n.test.ts`.
- (2026-04-19) Completed `F007`: wired [packages/billing/src/components/billing-dashboard/quotes/QuoteDocumentTemplatesPage.tsx](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/src/components/billing-dashboard/quotes/QuoteDocumentTemplatesPage.tsx) to `useTranslation('msp/quotes')` for the page heading/description, error alert title, table column titles, action-menu labels, standard/custom source labels, "Open menu" screen-reader text, and the delete-confirm prompt. Reused existing `common.actions.*`, `common.columns.*`, and `common.badges.standard` keys where the wording already matched, and expanded [server/public/locales/en/msp/quotes.json](/Users/natalliabukhtsik/Desktop/projects/bigmac/server/public/locales/en/msp/quotes.json) with the remaining `templatesPage.actions/dialogs/errors/labels.*` leaves. Validation:
- `node - <<'NODE' ... NODE` audit confirmed the component now references the planned `templatesPage.*` and shared `common.*` keys for all page chrome and menu actions.
- `node -e "const fs=require('fs'); JSON.parse(fs.readFileSync('server/public/locales/en/msp/quotes.json','utf8')); console.log('quotes.json ok');"` -> parsed successfully after the `templatesPage.*` additions.
- (2026-04-19) Completed `T013`: extended [packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts) with a `QuoteDocumentTemplatesPage` contract test that asserts the page uses `useTranslation('msp/quotes')` and that representative page, menu, error, and shared table/action keys resolve from `en/msp/quotes.json`. Validation command: `cd packages/billing && npx vitest run tests/billing-dashboard/QuotesSubbatch.i18n.test.ts`.
- (2026-04-19) Completed `F008`: wired [packages/billing/src/components/billing-dashboard/quotes/QuoteConversionDialog.tsx](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/src/components/billing-dashboard/quotes/QuoteConversionDialog.tsx) to `useTranslation('msp/quotes')` for the dialog title/description, loading and error states, partial-conversion alert copy, mode labels/descriptions, item-mapping headings, summary labels, and cancel/convert button states. Also replaced the local `en-US` currency formatter with `useFormatters().formatCurrency()` here, which partially advances `F012` for this dialog. Expanded [server/public/locales/en/msp/quotes.json](/Users/natalliabukhtsik/Desktop/projects/bigmac/server/public/locales/en/msp/quotes.json) with the remaining `quoteConversion.description`, `quoteConversion.errors.*`, `quoteConversion.sections.contractItems`, `quoteConversion.partial.*`, and `quoteConversion.actions.convertQuote` leaves. Validation:
- `node - <<'NODE' ... NODE` audit confirmed the component now references the planned `quoteConversion.*` keys for dialog copy, sections, and actions.
- `node -e "const fs=require('fs'); JSON.parse(fs.readFileSync('server/public/locales/en/msp/quotes.json','utf8')); console.log('quotes.json ok');"` -> parsed successfully after the `quoteConversion.*` additions.
- (2026-04-19) Completed `T014`: extended [packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts) with a `QuoteConversionDialog` contract test that asserts the standalone dialog uses `useTranslation('msp/quotes')` and that representative dialog, mode, partial-alert, summary, and action keys resolve from `en/msp/quotes.json`. Validation command: `cd packages/billing && npx vitest run tests/billing-dashboard/QuotesSubbatch.i18n.test.ts`.
- (2026-04-19) Completed `F009`: wired [packages/billing/src/components/billing-dashboard/quotes/QuoteApprovalDashboard.tsx](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/src/components/billing-dashboard/quotes/QuoteApprovalDashboard.tsx) to `useTranslation('msp/quotes')` for the page title/description, approval-settings label and toggle copy, status filter label/options, back button, loading state, error alert title, empty-state messages, and all table columns. This pass also replaced the local `en-US` amount/date helpers with `useFormatters().formatCurrency()` / `useFormatters().formatDate()`, which partially advances `F012` and `F013` for the approval dashboard. Expanded [server/public/locales/en/msp/quotes.json](/Users/natalliabukhtsik/Desktop/projects/bigmac/server/public/locales/en/msp/quotes.json) with the remaining `quoteApproval.actions/errors/loading/empty.*` leaves and aligned the existing settings strings with the current rendered copy. Validation:
- `node - <<'NODE' ... NODE` audit confirmed the component now references the planned `quoteApproval.*` and shared `common.columns.*` keys, plus the locale-aware formatters hook.
- `node -e "const fs=require('fs'); JSON.parse(fs.readFileSync('server/public/locales/en/msp/quotes.json','utf8')); console.log('quotes.json ok');"` -> parsed successfully after the `quoteApproval.*` additions.
- (2026-04-19) Completed `T015`: extended [packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts) with a `QuoteApprovalDashboard` contract test that asserts the dashboard uses `useTranslation('msp/quotes')`, adopts the locale-aware formatters hook, and resolves representative page/filter/empty-state/column keys from `en/msp/quotes.json`. Validation command: `cd packages/billing && npx vitest run tests/billing-dashboard/QuotesSubbatch.i18n.test.ts`.
- (2026-04-19) Completed `F010`: wired [packages/billing/src/components/billing-dashboard/quotes/QuoteTemplatesList.tsx](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/src/components/billing-dashboard/quotes/QuoteTemplatesList.tsx) to `useTranslation('msp/quotes')` for the description text, new-template button, loading state, alert title, empty-state sentence, row-action menu labels/aria copy, and delete confirmation dialog. This pass also replaced the local `en-US` currency/date helpers with `useFormatters().formatCurrency()` / `useFormatters().formatDate()`, which partially advances `F012` and `F013` for the templates list. Expanded [server/public/locales/en/msp/quotes.json](/Users/natalliabukhtsik/Desktop/projects/bigmac/server/public/locales/en/msp/quotes.json) with the remaining `quoteTemplates.title/loading/errors/dialogs/actions.templateActions/empty.inline` leaves while reusing shared `common.columns.*` and `common.actions.*` keys for the table chrome. Validation:
- `node - <<'NODE' ... NODE` audit confirmed the component now references the planned `quoteTemplates.*`, shared `common.*`, and locale-aware formatter hooks.
- `node -e "const fs=require('fs'); JSON.parse(fs.readFileSync('server/public/locales/en/msp/quotes.json','utf8')); console.log('quotes.json ok');"` -> parsed successfully after the `quoteTemplates.*` additions.
- (2026-04-19) Completed `T016`: extended [packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts) with a `QuoteTemplatesList` contract test that asserts the list uses `useTranslation('msp/quotes')`, adopts the locale-aware formatters hook, and resolves representative list/action/dialog keys from `en/msp/quotes.json`. Validation command: `cd packages/billing && npx vitest run tests/billing-dashboard/QuotesSubbatch.i18n.test.ts`.
- (2026-04-19) Completed `F011`: wired [packages/billing/src/components/billing-dashboard/quotes/QuotePreviewPanel.tsx](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/src/components/billing-dashboard/quotes/QuotePreviewPanel.tsx) to `useTranslation('msp/quotes')` for the panel heading, no-selection empty state, template-selector placeholder and standard-template suffix, loading state, open/download button labels, preview-unavailable fallback, and action/load error messaging. Expanded [server/public/locales/en/msp/quotes.json](/Users/natalliabukhtsik/Desktop/projects/bigmac/server/public/locales/en/msp/quotes.json) with the missing `quotePreview.errors.*` leaves and reused `common.actions.downloadPdf` / `common.badges.standard` for the shared button/suffix copy. Validation:
- `node - <<'NODE' ... NODE` audit confirmed the component now references the planned `quotePreview.*` and shared `common.*` keys for panel chrome and actions.
- `node -e "const fs=require('fs'); JSON.parse(fs.readFileSync('server/public/locales/en/msp/quotes.json','utf8')); console.log('quotes.json ok');"` -> parsed successfully after the `quotePreview.*` additions.
- (2026-04-19) Completed `T017`: extended [packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts) with a `QuotePreviewPanel` contract test that asserts the panel uses `useTranslation('msp/quotes')` and resolves representative panel, action, empty/loading/error, and shared suffix/button keys from `en/msp/quotes.json`. Validation command: `cd packages/billing && npx vitest run tests/billing-dashboard/QuotesSubbatch.i18n.test.ts`.
- (2026-04-19) Completed `F012`: replaced the remaining hardcoded `Intl.NumberFormat('en-US', ...)` quote currency helpers in [QuotesTab.tsx](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/src/components/billing-dashboard/quotes/QuotesTab.tsx), [QuoteDetail.tsx](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/src/components/billing-dashboard/quotes/QuoteDetail.tsx), [QuoteConversionDialog.tsx](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/src/components/billing-dashboard/quotes/QuoteConversionDialog.tsx), [QuoteApprovalDashboard.tsx](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/src/components/billing-dashboard/quotes/QuoteApprovalDashboard.tsx), and [QuoteTemplatesList.tsx](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/src/components/billing-dashboard/quotes/QuoteTemplatesList.tsx) with `useFormatters().formatCurrency()`. Amount renderers now pass major-unit values into the shared formatter instead of manually instantiating locale-pinned `Intl.NumberFormat`. Validation:
- `rg -n "Intl\\.NumberFormat\\('en-US'" packages/billing/src/components/billing-dashboard/quotes/{QuotesTab,QuoteDetail,QuoteConversionDialog,QuoteApprovalDashboard,QuoteTemplatesList}.tsx` -> 0 matches.
- `node - <<'NODE' ... NODE` audit confirmed all F012 target files now import/use `useFormatters`.
- (2026-04-19) Completed `F013`: replaced the remaining quote date/timestamp helpers with `useFormatters().formatDate()` across [QuotesTab.tsx](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/src/components/billing-dashboard/quotes/QuotesTab.tsx), [QuoteDetail.tsx](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/src/components/billing-dashboard/quotes/QuoteDetail.tsx), [QuoteApprovalDashboard.tsx](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/src/components/billing-dashboard/quotes/QuoteApprovalDashboard.tsx), and [QuoteDocumentTemplateEditor.tsx](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/src/components/billing-dashboard/quotes/QuoteDocumentTemplateEditor.tsx). The detail accepted/rejected banners now flow through the shared formatter, and the template editor footer timestamps use locale-aware date+time options instead of `toLocaleString()`. Validation:
- `rg -n "toLocaleDateString\\(|toLocaleString\\(" packages/billing/src/components/billing-dashboard/quotes/{QuotesTab,QuoteDetail,QuoteApprovalDashboard,QuoteDocumentTemplateEditor}.tsx` -> 0 matches.
- `node - <<'NODE' ... NODE` audit confirmed all F013 target files now import/use `useFormatters`.
- (2026-04-19) Completed `F025`: cleaned up the remaining user-visible formatter gaps outside the original F012/F013 file list. [QuoteForm.tsx](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/src/components/billing-dashboard/quotes/QuoteForm.tsx) now uses `useFormatters()` for quote totals and accepted/rejected banner dates, [QuoteLineItemsEditor.tsx](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/src/components/billing-dashboard/quotes/QuoteLineItemsEditor.tsx) uses `useFormatters().formatCurrency()` for inline unit/total money displays, and [quoteLineItemDraft.ts](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/src/components/billing-dashboard/quotes/quoteLineItemDraft.ts) no longer hardcodes `en-US` in `formatDraftQuoteMoney()`. Validation:
- `rg -n "Intl\\.NumberFormat\\('en-US'|toLocaleDateString\\(" packages/billing/src/components/billing-dashboard/quotes/{QuoteForm,QuoteLineItemsEditor,quoteLineItemDraft}.ts*` -> 0 matches.
- `node - <<'NODE' ... NODE` audit confirmed the remaining user-facing formatter consumers now use locale-aware paths.
- (2026-04-19) Completed `T018`: extended [packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts) with a source-level formatter regression check covering the five original F012 currency targets. It asserts each file imports `useFormatters` and no longer contains `Intl.NumberFormat('en-US', ...)`. Validation command: `cd packages/billing && npx vitest run tests/billing-dashboard/QuotesSubbatch.i18n.test.ts`.
- (2026-04-19) Completed `T019`: extended [packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts) with a source-level formatter regression check covering the four original F013 date targets. It asserts each file imports `useFormatters` and no longer contains `.toLocaleDateString()` / `.toLocaleString()`. Validation command: `cd packages/billing && npx vitest run tests/billing-dashboard/QuotesSubbatch.i18n.test.ts`.
- (2026-04-19) Completed `T030`: extended [packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts](/Users/natalliabukhtsik/Desktop/projects/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts) with a follow-on source-level formatter regression check for `QuoteForm.tsx`, `QuoteLineItemsEditor.tsx`, and `quoteLineItemDraft.ts`. It guards the newly-added `F025` cleanup by asserting the components use `useFormatters()` and the draft-money utility no longer hardcodes `en-US`. Validation command: `cd packages/billing && npx vitest run tests/billing-dashboard/QuotesSubbatch.i18n.test.ts`.
- (2026-04-19) Completed `F014`: generated the six production `msp/quotes` locale files at [server/public/locales/de/msp/quotes.json](/Users/natalliabukhtsik/Desktop/projects/bigmac/server/public/locales/de/msp/quotes.json), [server/public/locales/es/msp/quotes.json](/Users/natalliabukhtsik/Desktop/projects/bigmac/server/public/locales/es/msp/quotes.json), [server/public/locales/fr/msp/quotes.json](/Users/natalliabukhtsik/Desktop/projects/bigmac/server/public/locales/fr/msp/quotes.json), [server/public/locales/it/msp/quotes.json](/Users/natalliabukhtsik/Desktop/projects/bigmac/server/public/locales/it/msp/quotes.json), [server/public/locales/nl/msp/quotes.json](/Users/natalliabukhtsik/Desktop/projects/bigmac/server/public/locales/nl/msp/quotes.json), and [server/public/locales/pl/msp/quotes.json](/Users/natalliabukhtsik/Desktop/projects/bigmac/server/public/locales/pl/msp/quotes.json). Seeded them from the English namespace with placeholder-protected machine translation, then manually corrected the business-domain strings where `quote`/`markup` drifted into `citation`/`Zitat`/`cytat`/formatting language during QA. Validation:
- `node - <<'NODE' ... NODE` parity audit confirmed all six locale files now match `en/msp/quotes.json` at `420` leaf keys each with `missing: 0`, `extra: 0`, and `badVars: 0`.
- `rg -n "citation|cotation|citaat|cytat|Zitat|citazione|Marcado|__ALGA" server/public/locales/{de,es,fr,it,nl,pl}/msp/quotes.json` -> 0 matches after the domain-term cleanup and interpolation-token sweep.
- (2026-04-19) Completed `F015`: audited [server/public/locales/it/msp/quotes.json](/Users/natalliabukhtsik/Desktop/projects/bigmac/server/public/locales/it/msp/quotes.json) for the common missing-accent regressions (`perche`, `piu`, `puo`, `cosi`, `gia`, weekday forms, etc.) and found no exact-word matches. Accent presence spot-counts confirmed the file already contains the expected accented vowels (`à: 15`, `è: 12`, `é: 1`, `ò: 2`), so the audit passed without further accent corrections.
- (2026-04-19) Completed `F016`: regenerated pseudo-locales via `node scripts/generate-pseudo-locales.cjs`, which rebuilt `68` pseudo-locale files from `34` English sources. For this batch the only newly-created artifacts were [server/public/locales/xx/msp/quotes.json](/Users/natalliabukhtsik/Desktop/projects/bigmac/server/public/locales/xx/msp/quotes.json) and [server/public/locales/yy/msp/quotes.json](/Users/natalliabukhtsik/Desktop/projects/bigmac/server/public/locales/yy/msp/quotes.json). Spot-check validation:
- `quotesTab.title` -> `11111` in `xx`, `55555` in `yy`
- `quoteRecipients.removeAriaLabel` preserved `{{email}}` as `11111 {{email}} 11111` / `55555 {{email}} 55555`
- `quoteLineItems.markup.badge` preserved both `{{sign}}` and `{{value}}` in the expected pseudo-locale fill pattern.
- (2026-04-19) Completed `F017`: ran `node scripts/validate-translations.cjs` and got `Errors: 0`, `Warnings: 0`, `PASSED` across `de`, `es`, `fr`, `it`, `nl`, `pl`, `pt`, `xx`, and `yy`. The only prerequisite fix was repo-level fallback plumbing for Portuguese: bootstrapped [server/public/locales/pt/msp/quotes.json](/Users/natalliabukhtsik/Desktop/projects/bigmac/server/public/locales/pt/msp/quotes.json) from the English namespace and added the missing `enums.billingFrequency.weekly` key to [server/public/locales/pt/features/billing.json](/Users/natalliabukhtsik/Desktop/projects/bigmac/server/public/locales/pt/features/billing.json), because the validator checks every real locale directory under `server/public/locales/`, not just the six translated quote locales plus English.
- (2026-04-19) Completed `F018`: updated [packages/core/src/lib/i18n/config.ts](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/core/src/lib/i18n/config.ts) so `/msp/billing` now loads `msp/quotes`, and added explicit route entries for `/msp/quote-approvals` and `/msp/quote-document-templates`. Kept `features/billing` on the standalone quote routes as well so the billing enum/status resources remain available there. Validation:
- `rg -n \"'/msp/billing'|'/msp/quote-approvals'|'/msp/quote-document-templates'|msp/quotes\" packages/core/src/lib/i18n/config.ts` -> verified all three route entries now reference `msp/quotes` in the source.
- (2026-04-19) Completed `F019`: `npm run build` now succeeds end-to-end. Fixes required to clear the build gate:
- Restored the missing `useCallback(...)` wrapper around `handleCloneTemplate` in [packages/billing/src/components/billing-dashboard/quotes/QuoteDocumentTemplatesPage.tsx](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/src/components/billing-dashboard/quotes/QuoteDocumentTemplatesPage.tsx), which had been left with a dangling dependency array and broke parsing.
- Guarded optional date formatter inputs in [packages/billing/src/components/billing-dashboard/quotes/QuoteDetail.tsx](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/src/components/billing-dashboard/quotes/QuoteDetail.tsx) and fixed the `templates.map((t) => ...)` translation-function shadowing bug in [packages/billing/src/components/billing-dashboard/quotes/QuotePreviewPanel.tsx](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/src/components/billing-dashboard/quotes/QuotePreviewPanel.tsx).
- Added the missing `pt` date-fns locale mapping in [packages/ui/src/lib/dateFnsLocale.ts](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/ui/src/lib/dateFnsLocale.ts) and [server/src/lib/utils/dateFnsLocale.ts](/Users/natalliabukhtsik/Desktop/projects/bigmac/server/src/lib/utils/dateFnsLocale.ts), which had become a real type error once `pt` was treated as a supported locale throughout the repo.
- Validation: `npm run build` -> exit code `0` after the quote fixes above; Next.js still emits the pre-existing webpack warnings during the server build, but the build now completes successfully and the TypeScript phase finishes cleanly.
- (2026-04-19) Completed `F020`: verified the post-planning product-markup UI in [packages/billing/src/components/billing-dashboard/quotes/QuoteLineItemsEditor.tsx](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/src/components/billing-dashboard/quotes/QuoteLineItemsEditor.tsx) already routes the badge, unavailable state, and currency-mismatch tooltip through `t('quoteLineItems.markup.*')` with the required `{{sign}}`, `{{value}}`, `{{costCurrency}}`, and `{{quoteCurrency}}` interpolations. Locale audit confirmed `quoteLineItems.markup` exists in `en`, `de`, `es`, `fr`, `it`, `nl`, `pl`, the `pt` fallback tree, and both pseudo-locales.
- (2026-04-19) Completed `F021`: wired [packages/billing/src/components/billing-dashboard/quotes/QuoteSendRecipientsField.tsx](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/src/components/billing-dashboard/quotes/QuoteSendRecipientsField.tsx) to `useTranslation('msp/quotes')` for all remaining user-visible combobox strings: the three trigger states, the search placeholder, the no-results/no-options empty states, the internal/contact kind badges, and the interpolated remove-recipient aria-label. The `quoteRecipients.*` keys were already present in `en/msp/quotes.json` and had already propagated to the translated / pseudo locale files during `F014` and `F016`, so this step was limited to component wiring. Validation:
- `node - <<'NODE' ... NODE` source audit confirmed the component now references `quoteRecipients.trigger.*`, `quoteRecipients.searchPlaceholder`, `quoteRecipients.empty.*`, `quoteRecipients.kind.*`, and `quoteRecipients.removeAriaLabel`.
- `node - <<'NODE' ... NODE` locale audit confirmed `quoteRecipients` is present in every current `msp/quotes.json` variant (`en`, `de`, `es`, `fr`, `it`, `nl`, `pl`, `pt`, `xx`, `yy`).
- (2026-04-19) Completed `F022`: backfilled the missing MSP sidebar quote-navigation keys in [server/public/locales/en/msp/core.json](/Users/natalliabukhtsik/Desktop/projects/bigmac/server/public/locales/en/msp/core.json), [server/public/locales/de/msp/core.json](/Users/natalliabukhtsik/Desktop/projects/bigmac/server/public/locales/de/msp/core.json), [server/public/locales/es/msp/core.json](/Users/natalliabukhtsik/Desktop/projects/bigmac/server/public/locales/es/msp/core.json), [server/public/locales/fr/msp/core.json](/Users/natalliabukhtsik/Desktop/projects/bigmac/server/public/locales/fr/msp/core.json), [server/public/locales/it/msp/core.json](/Users/natalliabukhtsik/Desktop/projects/bigmac/server/public/locales/it/msp/core.json), [server/public/locales/nl/msp/core.json](/Users/natalliabukhtsik/Desktop/projects/bigmac/server/public/locales/nl/msp/core.json), [server/public/locales/pl/msp/core.json](/Users/natalliabukhtsik/Desktop/projects/bigmac/server/public/locales/pl/msp/core.json), and the `pt` fallback tree at [server/public/locales/pt/msp/core.json](/Users/natalliabukhtsik/Desktop/projects/bigmac/server/public/locales/pt/msp/core.json). Added `nav.billing.sections.quotes`, `nav.billing.quotes`, `nav.billing.quoteBusinessTemplates`, and `nav.billing.quoteLayouts`, then regenerated pseudo-locales so [server/public/locales/xx/msp/core.json](/Users/natalliabukhtsik/Desktop/projects/bigmac/server/public/locales/xx/msp/core.json) / [server/public/locales/yy/msp/core.json](/Users/natalliabukhtsik/Desktop/projects/bigmac/server/public/locales/yy/msp/core.json) picked up the new keys automatically. Validation:
- `node - <<'NODE' ... NODE` spot check confirmed `de` now renders `Angebote / Angebotsvorlagen / Angebotslayouts` and `xx` renders `11111` for the Quotes section header and all three items.
- `node scripts/generate-pseudo-locales.cjs` -> regenerated pseudo locale artifacts after the English core backfill.
- `node scripts/validate-translations.cjs` -> `Errors: 0`, `Warnings: 0`, `PASSED` after the `msp/core` additions.
- (2026-04-19) Completed `T002`: reran `node scripts/validate-translations.cjs` after the final locale work (`F017` + `F022`) and the translation validator still reports `Locales checked: 9`, `Errors: 0`, `Warnings: 0`, `PASSED`. This covers `msp/quotes` key parity / empty-value checks across `de`, `es`, `fr`, `it`, `nl`, `pl`, `pt`, `xx`, and `yy`.
- (2026-04-19) Completed `T020`: reran the Italian accent audit on [server/public/locales/it/msp/quotes.json](/Users/natalliabukhtsik/Desktop/projects/bigmac/server/public/locales/it/msp/quotes.json). A source scan found no unaccented regressions for the common problem words (`perche`, `piu`, `puo`, `cosi`, `gia`), and the file still contains the expected accented-vowel mix (`à: 15`, `è: 12`, `é: 1`, `ò: 2`).
- (2026-04-19) Completed `T021`: recursively walked [server/public/locales/xx/msp/quotes.json](/Users/natalliabukhtsik/Desktop/projects/bigmac/server/public/locales/xx/msp/quotes.json) and confirmed every string leaf contains the expected `11111` pseudo-locale fill pattern (`missing: 0`). This covers the visual-QA expectation that the quote namespace no longer falls back to readable English in `xx`.
- (2026-04-19) Completed `T022`: ran an explicit interpolation-parity audit between [server/public/locales/en/msp/quotes.json](/Users/natalliabukhtsik/Desktop/projects/bigmac/server/public/locales/en/msp/quotes.json) and every translated / pseudo locale variant (`de`, `es`, `fr`, `it`, `nl`, `pl`, `pt`, `xx`, `yy`). The audit flattened all `420` leaf keys and confirmed the placeholder sets match exactly for every locale (`badVars: 0`), covering strings like `{{count}}`, `{{name}}`, `{{email}}`, `{{costCurrency}}`, `{{quoteCurrency}}`, `{{sign}}`, and `{{value}}`.
- (2026-04-19) Completed `T023`: refreshed [packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts) so the route-namespace contract checks the current `ROUTE_NAMESPACES` source instead of brittle one-line import snapshots. The test now asserts `/msp/billing`, `/msp/quote-approvals`, and `/msp/quote-document-templates` all include `msp/quotes` in [packages/core/src/lib/i18n/config.ts](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/core/src/lib/i18n/config.ts). Validation:
- `cd packages/billing && npx vitest run tests/billing-dashboard/QuotesSubbatch.i18n.test.ts` -> `24 passed`, including the new `T023` route assertion and the refreshed earlier wiring checks.
- (2026-04-19) Completed `T024`: reran `npm run build` after the final locale backfills and contract-test refresh. The build completed successfully (exit code `0`), including the `next build --webpack` phase and the full route manifest generation, with only the existing non-blocking webpack warnings that were already present during `F019`.
- (2026-04-19) Completed `T025`: extended [packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts) with an explicit markup contract for [QuoteLineItemsEditor.tsx](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/src/components/billing-dashboard/quotes/QuoteLineItemsEditor.tsx). The test now asserts the component routes the badge / unavailable label / unavailable tooltip through `quoteLineItems.markup.*`, guards against the old raw template-literal render paths, and verifies `quoteLineItems.markup.badge`, `.unavailable`, and `.unavailableTooltip` exist in `en`, `de`, `es`, `fr`, `it`, `nl`, `pl`, `xx`, and `yy`. Validation:
- `cd packages/billing && npx vitest run tests/billing-dashboard/QuotesSubbatch.i18n.test.ts` -> `24 passed`, including `T025`.
- (2026-04-19) Completed `T026`: extended [packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts) with a dedicated recipients-field contract for [QuoteSendRecipientsField.tsx](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/src/components/billing-dashboard/quotes/QuoteSendRecipientsField.tsx). The test asserts the component imports `useTranslation('msp/quotes')`, references every `quoteRecipients.*` leaf used by the combobox UI, rejects the old raw placeholder / ternary / `aria-label={`Remove ${r.email}`}` render paths, and verifies the locale keys exist in `en`, `de`, `es`, `fr`, `it`, `nl`, `pl`, `xx`, and `yy`. Validation:
- `cd packages/billing && npx vitest run tests/billing-dashboard/QuotesSubbatch.i18n.test.ts` -> `24 passed`, including `T026`.
- (2026-04-19) Completed `T027`: extended [packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts](/Users/natalliabukhtsik/Desktop/projects/bigmac/packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts) with a sidebar-locale contract for the Quotes nav block. The test verifies `nav.billing.sections.quotes`, `nav.billing.quotes`, `nav.billing.quoteBusinessTemplates`, and `nav.billing.quoteLayouts` exist in `en`, `de`, `es`, `fr`, `it`, `nl`, `pl`, `xx`, and `yy`, and it hard-checks the smoke-test values called out in the PRD (`de` -> `Angebote / Angebotsvorlagen / Angebotslayouts`, `xx` -> `11111` fill for the section + all items). Validation:
- `cd packages/billing && npx vitest run tests/billing-dashboard/QuotesSubbatch.i18n.test.ts` -> `24 passed`, including `T027`.
## Commands / Runbook
- `git status --short`
- `rg -n '>[[:space:]]*[A-Z][^<{]*<' packages/billing/src/components/billing-dashboard/quotes/*.tsx`
- `rg -n "'[^']*[A-Za-z][^']*'|\"[^\"]*[A-Za-z][^\"]*\"" packages/billing/src/components/billing-dashboard/quotes/*.tsx`