Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
61 KiB
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 haveuseTranslation=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.quoteLayoutsstill missing fromserver/public/locales/en/msp/core.json(verified via grep). TheF022/T027backfill 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:
- Preferred: publish a
useQuoteStatusLabel()/useQuoteStatusOptions()hook from@alga-psa/typesor@alga-psa/billing/hooks/, with keys underfeatures/billing.json#enums.quoteStatus.*. ThenQuoteStatusBadge.tsxbecomes a thin consumer of the hook. Zero changes needed inmsp/quotes. - 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_METADATAin@alga-psa/types. Translating those labels requires a types-level change (making the metadata accept at()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 usesIntl.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/quotesnamespace 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 usingnew Intl.NumberFormat('en-US', ...). These should be replaced withuseFormatters().formatCurrency()or a shared locale-aware helper. ThequoteLineItemDraft.tsutility also hasformatDraftQuoteMoney()withen-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.tsxuses.toLocaleString()for timestamps. These should useuseFormatters()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 Layoutsin English under aQUOTESsection header, while every other entry pseudo-translated cleanly. Root cause:server/src/config/menuConfig.ts:308-317added a Quotes section that references four translation keys that were never backfilled intomsp/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 undermsp/core.json(contractTemplates, clientContracts, invoicing, accountingExports, usageTracking, etc.). Only the Quotes block is missing — so the sidebar falls back to the hardcoded Englishnamefield inmenuConfig.ts.Decision: keep these keys in
msp/core(where the rest ofnav.billing.*lives) rather than introducing cross-namespace navigation. The fix is a pure locale-file backfill — no code change inmenuConfig.ts, and no dependency on themsp/quotesnamespace load order. Tracked asF022/T027below. -
(2026-04-10) Product markup on quote line items landed after the planning commit (commits
6a40d09fa"Show product markup on quote line items" and3afa5763f"Add recipient picker and discount-aware markup").QuoteLineItemsEditor.tsxnow 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_currencydiffers 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 asF020).
- A live markup badge in the Unit Price cell:
-
(2026-04-10) New component
QuoteSendRecipientsField.tsx(403 LOC) was added in commits3afa5763fandead79deecand is consumed byQuoteDetail.tsxandQuoteForm.tsxin 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/T026below. New key groupquoteRecipients.*.
- Trigger label (three-way ternary):
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.*. formatCurrencyis a local closure usingform.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_COLUMNSdefined 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.
QuoteSubTabContentis 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
- Column definitions at module scope:
BASE_QUOTE_COLUMNSin QuotesTab.tsx and column arrays in QuoteApprovalDashboard.tsx / QuoteTemplatesList.tsx are defined as constants outside the component. They needt()for titles butt()is only available inside the component. Options: (a) move inside component with useMemo, (b) use factory function that takestas parameter. Option (a) is simplest and consistent with other translated components. - 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. - Template string in headings: QuoteForm heading uses complex conditional logic. Keep the branching but wrap each branch in t().
- Interpolation-heavy strings: Sub-tab labels like
Active (${count}), section counts like${count} item(s), and notice messages likeCreated draft contract ${name}need careful interpolation variable placement. Use{{count}},{{name}}patterns. - 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. - 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-labelvalues that are already descriptive English. - QuoteStatusBadge is a pass-through: It renders
metadata.labelfrom 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. - Shared billing-frequency hook was missing
weekly:QuoteLineItemsEditor.tsxstill exposes Weekly / Monthly / Quarterly / Annually, butuseBillingFrequencyOptions()only had monthly/quarterly/annually when this batch started. Added follow-onF024/T029so the quote editor can adopt the shared enum hook without losing the existing weekly option. - Plan missed remaining formatter hotspots: After completing
F012/F013for the originally-listed files,QuoteForm.tsxstill had accepted/rejected banner dates plus a localen-USmoney helper, andquoteLineItemDraft.tsstill exported a draft-money formatter pinned toen-USthat surfaced throughQuoteFormandQuoteLineItemsEditor. AddedF025/T030so 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 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 with a node-side contract test that parsesen/msp/quotes.jsonand 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 touseTranslation('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 with thequoteForm.*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 therunWorkflowActionhelper signature; all rendered copy now flows throught(...).cd packages/billing && npm run typecheck-> blocked by a pre-existing upstream issue outside this batch:packages/ui/src/lib/dateFnsLocale.tsis missing theptlocale 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 with aQuoteFormcontract test that asserts theuseTranslation('msp/quotes')hook is present and that representative form/workflow/dialog keys resolve fromen/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 covering the major raw-English literals that used to be rendered directly byQuoteForm.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 touseTranslation('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 sharedquoteForm.*,quoteConversion.*,quoteLineItems.*, andcommon.*keys where the UI copy matches, and expanded server/public/locales/en/msp/quotes.json with the remainingquoteDetail.*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 thequoteDetail.*additions.
-
(2026-04-19) Completed
T005: extended packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts with aQuoteDetailcontract test that asserts the translation hook is present and that representative detail, dialog, badge, and conversion-preview keys resolve fromen/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 covering the major raw-English literals that used to be rendered directly byQuoteDetail.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 and its innerQuoteSubTabContenttouseTranslation('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-awareuseMemo, and expanded server/public/locales/en/msp/quotes.json with the remainingquotesTab.*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 thequotesTab.*additions.
-
(2026-04-19) Completed
T007: extended packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts with aQuotesTabcontract test that asserts the translation hook is present and that representative tab, table, action-menu, and dialog keys resolve fromen/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 covering the major raw-English literals that used to be rendered directly byQuotesTab.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 touseTranslation('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 with thetemplateEditor.*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 thetemplateEditor.*additions.- Source sweep via
rgconfirmed the remaining plain string literals inQuoteDocumentTemplateEditor.tsxare 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 with aQuoteDocumentTemplateEditorcontract test that asserts the translation hook is present and that representative editor, preview-pipeline, and footer keys resolve fromen/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 covering the major raw-English literals that used to be rendered directly byQuoteDocumentTemplateEditor.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 withweeklyin packages/billing/src/constants/billing.ts and all 9 server/public/locales/*/features/billing.json variants. This was required beforeQuoteLineItemsEditor.tsxcould adoptuseBillingFrequencyOptions()without regressing the existing weekly recurrence option noted in the PRD addendum. Validation:node - <<'NODE' ... NODEaudit confirmed every locale now exposesenums.billingFrequency.weeklyalongsidemonthly,quarterly, andannually.
-
(2026-04-19) Completed
T029: extended packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts with a source-level contract test for the shared billing-frequency enum additions. It assertsbilling.tsnow exportsweeklyin bothBILLING_FREQUENCY_VALUESandBILLING_FREQUENCY_LABEL_DEFAULTS, and that all 9features/billing.jsonlocale files defineenums.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 touseTranslation('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 touseBillingFrequencyOptions()/useFormatBillingFrequency()and moved the product-markup badge/tooltip text behindt(...)in preparation forF020. Expanded server/public/locales/en/msp/quotes.json with the newquoteLineItems.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 thequoteLineItems.*additions.node - <<'NODE' ... NODEaudit confirmed the component now references the plannedquoteLineItems.*keys for table chrome, controls, and inline hints.
-
(2026-04-19) Completed
T011: extended packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts with aQuoteLineItemsEditorcontract test that asserts themsp/quoteshook is present, the shared billing-frequency hooks are used, and representativequoteLineItems.*keys resolve fromen/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 covering the old raw-English table headers, discount controls, inline price hints, and markup text that used to be rendered directly byQuoteLineItemsEditor.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 touseTranslation('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 existingcommon.actions.*,common.columns.*, andcommon.badges.standardkeys where the wording already matched, and expanded server/public/locales/en/msp/quotes.json with the remainingtemplatesPage.actions/dialogs/errors/labels.*leaves. Validation:node - <<'NODE' ... NODEaudit confirmed the component now references the plannedtemplatesPage.*and sharedcommon.*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 thetemplatesPage.*additions.
-
(2026-04-19) Completed
T013: extended packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts with aQuoteDocumentTemplatesPagecontract test that asserts the page usesuseTranslation('msp/quotes')and that representative page, menu, error, and shared table/action keys resolve fromen/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 touseTranslation('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 localen-UScurrency formatter withuseFormatters().formatCurrency()here, which partially advancesF012for this dialog. Expanded server/public/locales/en/msp/quotes.json with the remainingquoteConversion.description,quoteConversion.errors.*,quoteConversion.sections.contractItems,quoteConversion.partial.*, andquoteConversion.actions.convertQuoteleaves. Validation:node - <<'NODE' ... NODEaudit confirmed the component now references the plannedquoteConversion.*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 thequoteConversion.*additions.
-
(2026-04-19) Completed
T014: extended packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts with aQuoteConversionDialogcontract test that asserts the standalone dialog usesuseTranslation('msp/quotes')and that representative dialog, mode, partial-alert, summary, and action keys resolve fromen/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 touseTranslation('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 localen-USamount/date helpers withuseFormatters().formatCurrency()/useFormatters().formatDate(), which partially advancesF012andF013for the approval dashboard. Expanded server/public/locales/en/msp/quotes.json with the remainingquoteApproval.actions/errors/loading/empty.*leaves and aligned the existing settings strings with the current rendered copy. Validation:node - <<'NODE' ... NODEaudit confirmed the component now references the plannedquoteApproval.*and sharedcommon.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 thequoteApproval.*additions.
-
(2026-04-19) Completed
T015: extended packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts with aQuoteApprovalDashboardcontract test that asserts the dashboard usesuseTranslation('msp/quotes'), adopts the locale-aware formatters hook, and resolves representative page/filter/empty-state/column keys fromen/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 touseTranslation('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 localen-UScurrency/date helpers withuseFormatters().formatCurrency()/useFormatters().formatDate(), which partially advancesF012andF013for the templates list. Expanded server/public/locales/en/msp/quotes.json with the remainingquoteTemplates.title/loading/errors/dialogs/actions.templateActions/empty.inlineleaves while reusing sharedcommon.columns.*andcommon.actions.*keys for the table chrome. Validation:node - <<'NODE' ... NODEaudit confirmed the component now references the plannedquoteTemplates.*, sharedcommon.*, 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 thequoteTemplates.*additions.
-
(2026-04-19) Completed
T016: extended packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts with aQuoteTemplatesListcontract test that asserts the list usesuseTranslation('msp/quotes'), adopts the locale-aware formatters hook, and resolves representative list/action/dialog keys fromen/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 touseTranslation('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 with the missingquotePreview.errors.*leaves and reusedcommon.actions.downloadPdf/common.badges.standardfor the shared button/suffix copy. Validation:node - <<'NODE' ... NODEaudit confirmed the component now references the plannedquotePreview.*and sharedcommon.*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 thequotePreview.*additions.
-
(2026-04-19) Completed
T017: extended packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts with aQuotePreviewPanelcontract test that asserts the panel usesuseTranslation('msp/quotes')and resolves representative panel, action, empty/loading/error, and shared suffix/button keys fromen/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 hardcodedIntl.NumberFormat('en-US', ...)quote currency helpers in QuotesTab.tsx, QuoteDetail.tsx, QuoteConversionDialog.tsx, QuoteApprovalDashboard.tsx, and QuoteTemplatesList.tsx withuseFormatters().formatCurrency(). Amount renderers now pass major-unit values into the shared formatter instead of manually instantiating locale-pinnedIntl.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' ... NODEaudit confirmed all F012 target files now import/useuseFormatters.
-
(2026-04-19) Completed
F013: replaced the remaining quote date/timestamp helpers withuseFormatters().formatDate()across QuotesTab.tsx, QuoteDetail.tsx, QuoteApprovalDashboard.tsx, and 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 oftoLocaleString(). Validation:rg -n "toLocaleDateString\\(|toLocaleString\\(" packages/billing/src/components/billing-dashboard/quotes/{QuotesTab,QuoteDetail,QuoteApprovalDashboard,QuoteDocumentTemplateEditor}.tsx-> 0 matches.node - <<'NODE' ... NODEaudit confirmed all F013 target files now import/useuseFormatters.
-
(2026-04-19) Completed
F025: cleaned up the remaining user-visible formatter gaps outside the original F012/F013 file list. QuoteForm.tsx now usesuseFormatters()for quote totals and accepted/rejected banner dates, QuoteLineItemsEditor.tsx usesuseFormatters().formatCurrency()for inline unit/total money displays, and quoteLineItemDraft.ts no longer hardcodesen-USinformatDraftQuoteMoney(). Validation:rg -n "Intl\\.NumberFormat\\('en-US'|toLocaleDateString\\(" packages/billing/src/components/billing-dashboard/quotes/{QuoteForm,QuoteLineItemsEditor,quoteLineItemDraft}.ts*-> 0 matches.node - <<'NODE' ... NODEaudit 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 with a source-level formatter regression check covering the five original F012 currency targets. It asserts each file importsuseFormattersand no longer containsIntl.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 with a source-level formatter regression check covering the four original F013 date targets. It asserts each file importsuseFormattersand 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 with a follow-on source-level formatter regression check forQuoteForm.tsx,QuoteLineItemsEditor.tsx, andquoteLineItemDraft.ts. It guards the newly-addedF025cleanup by asserting the components useuseFormatters()and the draft-money utility no longer hardcodesen-US. Validation command:cd packages/billing && npx vitest run tests/billing-dashboard/QuotesSubbatch.i18n.test.ts. -
(2026-04-19) Completed
F014: generated the six productionmsp/quoteslocale files at server/public/locales/de/msp/quotes.json, server/public/locales/es/msp/quotes.json, server/public/locales/fr/msp/quotes.json, server/public/locales/it/msp/quotes.json, server/public/locales/nl/msp/quotes.json, and 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 wherequote/markupdrifted intocitation/Zitat/cytat/formatting language during QA. Validation:node - <<'NODE' ... NODEparity audit confirmed all six locale files now matchen/msp/quotes.jsonat420leaf keys each withmissing: 0,extra: 0, andbadVars: 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 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 vianode scripts/generate-pseudo-locales.cjs, which rebuilt68pseudo-locale files from34English sources. For this batch the only newly-created artifacts were server/public/locales/xx/msp/quotes.json and server/public/locales/yy/msp/quotes.json. Spot-check validation:quotesTab.title->11111inxx,55555inyyquoteRecipients.removeAriaLabelpreserved{{email}}as11111 {{email}} 11111/55555 {{email}} 55555quoteLineItems.markup.badgepreserved both{{sign}}and{{value}}in the expected pseudo-locale fill pattern.
-
(2026-04-19) Completed
F017: rannode scripts/validate-translations.cjsand gotErrors: 0,Warnings: 0,PASSEDacrossde,es,fr,it,nl,pl,pt,xx, andyy. The only prerequisite fix was repo-level fallback plumbing for Portuguese: bootstrapped server/public/locales/pt/msp/quotes.json from the English namespace and added the missingenums.billingFrequency.weeklykey to server/public/locales/pt/features/billing.json, because the validator checks every real locale directory underserver/public/locales/, not just the six translated quote locales plus English. -
(2026-04-19) Completed
F018: updated packages/core/src/lib/i18n/config.ts so/msp/billingnow loadsmsp/quotes, and added explicit route entries for/msp/quote-approvalsand/msp/quote-document-templates. Keptfeatures/billingon 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 referencemsp/quotesin the source.
-
(2026-04-19) Completed
F019:npm run buildnow succeeds end-to-end. Fixes required to clear the build gate:- Restored the missing
useCallback(...)wrapper aroundhandleCloneTemplatein 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 and fixed the
templates.map((t) => ...)translation-function shadowing bug in packages/billing/src/components/billing-dashboard/quotes/QuotePreviewPanel.tsx. - Added the missing
ptdate-fns locale mapping in packages/ui/src/lib/dateFnsLocale.ts and server/src/lib/utils/dateFnsLocale.ts, which had become a real type error onceptwas treated as a supported locale throughout the repo. - Validation:
npm run build-> exit code0after 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.
- Restored the missing
-
(2026-04-19) Completed
F020: verified the post-planning product-markup UI in packages/billing/src/components/billing-dashboard/quotes/QuoteLineItemsEditor.tsx already routes the badge, unavailable state, and currency-mismatch tooltip throught('quoteLineItems.markup.*')with the required{{sign}},{{value}},{{costCurrency}}, and{{quoteCurrency}}interpolations. Locale audit confirmedquoteLineItems.markupexists inen,de,es,fr,it,nl,pl, theptfallback tree, and both pseudo-locales. -
(2026-04-19) Completed
F021: wired packages/billing/src/components/billing-dashboard/quotes/QuoteSendRecipientsField.tsx touseTranslation('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. ThequoteRecipients.*keys were already present inen/msp/quotes.jsonand had already propagated to the translated / pseudo locale files duringF014andF016, so this step was limited to component wiring. Validation:node - <<'NODE' ... NODEsource audit confirmed the component now referencesquoteRecipients.trigger.*,quoteRecipients.searchPlaceholder,quoteRecipients.empty.*,quoteRecipients.kind.*, andquoteRecipients.removeAriaLabel.node - <<'NODE' ... NODElocale audit confirmedquoteRecipientsis present in every currentmsp/quotes.jsonvariant (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, server/public/locales/de/msp/core.json, server/public/locales/es/msp/core.json, server/public/locales/fr/msp/core.json, server/public/locales/it/msp/core.json, server/public/locales/nl/msp/core.json, server/public/locales/pl/msp/core.json, and theptfallback tree at server/public/locales/pt/msp/core.json. Addednav.billing.sections.quotes,nav.billing.quotes,nav.billing.quoteBusinessTemplates, andnav.billing.quoteLayouts, then regenerated pseudo-locales so server/public/locales/xx/msp/core.json / server/public/locales/yy/msp/core.json picked up the new keys automatically. Validation:node - <<'NODE' ... NODEspot check confirmeddenow rendersAngebote / Angebotsvorlagen / Angebotslayoutsandxxrenders11111for 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,PASSEDafter themsp/coreadditions.
-
(2026-04-19) Completed
T002: rerannode scripts/validate-translations.cjsafter the final locale work (F017+F022) and the translation validator still reportsLocales checked: 9,Errors: 0,Warnings: 0,PASSED. This coversmsp/quoteskey parity / empty-value checks acrossde,es,fr,it,nl,pl,pt,xx, andyy. -
(2026-04-19) Completed
T020: reran the Italian accent audit on 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 and confirmed every string leaf contains the expected11111pseudo-locale fill pattern (missing: 0). This covers the visual-QA expectation that the quote namespace no longer falls back to readable English inxx. -
(2026-04-19) Completed
T022: ran an explicit interpolation-parity audit between 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 all420leaf 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 so the route-namespace contract checks the currentROUTE_NAMESPACESsource instead of brittle one-line import snapshots. The test now asserts/msp/billing,/msp/quote-approvals, and/msp/quote-document-templatesall includemsp/quotesin 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 newT023route assertion and the refreshed earlier wiring checks.
-
(2026-04-19) Completed
T024: rerannpm run buildafter the final locale backfills and contract-test refresh. The build completed successfully (exit code0), including thenext build --webpackphase and the full route manifest generation, with only the existing non-blocking webpack warnings that were already present duringF019. -
(2026-04-19) Completed
T025: extended packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts with an explicit markup contract for QuoteLineItemsEditor.tsx. The test now asserts the component routes the badge / unavailable label / unavailable tooltip throughquoteLineItems.markup.*, guards against the old raw template-literal render paths, and verifiesquoteLineItems.markup.badge,.unavailable, and.unavailableTooltipexist inen,de,es,fr,it,nl,pl,xx, andyy. Validation:cd packages/billing && npx vitest run tests/billing-dashboard/QuotesSubbatch.i18n.test.ts->24 passed, includingT025.
-
(2026-04-19) Completed
T026: extended packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts with a dedicated recipients-field contract for QuoteSendRecipientsField.tsx. The test asserts the component importsuseTranslation('msp/quotes'), references everyquoteRecipients.*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 inen,de,es,fr,it,nl,pl,xx, andyy. Validation:cd packages/billing && npx vitest run tests/billing-dashboard/QuotesSubbatch.i18n.test.ts->24 passed, includingT026.
-
(2026-04-19) Completed
T027: extended packages/billing/tests/billing-dashboard/QuotesSubbatch.i18n.test.ts with a sidebar-locale contract for the Quotes nav block. The test verifiesnav.billing.sections.quotes,nav.billing.quotes,nav.billing.quoteBusinessTemplates, andnav.billing.quoteLayoutsexist inen,de,es,fr,it,nl,pl,xx, andyy, and it hard-checks the smoke-test values called out in the PRD (de->Angebote / Angebotsvorlagen / Angebotslayouts,xx->11111fill for the section + all items). Validation:cd packages/billing && npx vitest run tests/billing-dashboard/QuotesSubbatch.i18n.test.ts->24 passed, includingT027.
Commands / Runbook
git status --shortrg -n '>[[:space:]]*[A-Z][^<{]*<' packages/billing/src/components/billing-dashboard/quotes/*.tsxrg -n "'[^']*[A-Za-z][^']*'|\"[^\"]*[A-Za-z][^\"]*\"" packages/billing/src/components/billing-dashboard/quotes/*.tsx