Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
8.0 KiB
PRD -- MSP i18n: Quotes Sub-batch
- Slug:
2026-04-09-msp-i18n-quotes - Date:
2026-04-09 - Status: Draft (ready to start — verified unchanged on 2026-04-17; see SCRATCHPAD "Status Recheck (2026-04-17)" for new enum-hook guidance and recommended
QuoteStatusBadgefollow-up)
Summary
Extract all hardcoded English strings from the 12 quoting components, create the msp/quotes namespace, wire useTranslation(), and generate translations for 7 languages plus 2 pseudo-locales.
Problem
The entire quoting UI -- create/edit forms, detail views, quote lists, approval workflows, conversion dialogs, line-item editors, document template editors, and preview panels -- displays English-only text regardless of the user's locale preference. This is inconsistent with the rest of the MSP portal, which has been progressively translated.
Goals
- Create
server/public/locales/en/msp/quotes.jsonwith all extracted keys. - Wire all 12 component files with
useTranslation('msp/quotes'). - Generate translations for 7 languages (de, es, fr, it, nl, pl, en) plus 2 pseudo-locales (xx, yy) -- 9 locale files total.
- Register
msp/quotesinROUTE_NAMESPACESfor/msp/quote-approvalsand/msp/quote-document-templates. - Pass
validate-translations.cjswith 0 errors across all 9 locales. - Zero regressions with the
msp-i18n-enabledfeature flag OFF.
Non-goals
- Translating server-side quote actions or API responses.
- Translating the client-portal quote acceptance flow (that belongs to
client-portalorfeatures/billingnamespaces). - Refactoring component architecture.
- Translating
quoteLineItemDraft.tsutility functions -- these are pure logic with no rendered strings (onlyIntl.NumberFormatcalls that already respect locale). - Translating the
QuoteStatusBadge.tsxcomponent -- it reads labels fromQUOTE_STATUS_METADATAin@alga-psa/types, which is a shared type constant. Translating those labels requires a separate types-level effort.
File Inventory
| # | File | LOC | Est. Strings | Category |
|---|---|---|---|---|
| 1 | QuoteForm.tsx |
1,072 | ~180-250 | Main create/edit form + workflow actions + send/approval/conversion dialogs |
| 2 | QuoteDetail.tsx |
1,106 | ~200-280 | Full detail view + all action dialogs + conversion preview + activity log |
| 3 | QuotesTab.tsx |
656 | ~80-120 | Quote list tab with sub-tabs, filters, row actions, send/delete dialogs |
| 4 | QuoteDocumentTemplateEditor.tsx |
596 | ~60-90 | Visual/code template editor with preview pipeline |
| 5 | QuoteLineItemsEditor.tsx |
676 | ~80-115 | Line items table, discount panel, phase sections, inline editing, product markup badge (+tooltip for cost/quote currency mismatch) |
| 6 | QuoteDocumentTemplatesPage.tsx |
299 | ~30-45 | Template list page with actions menu |
| 7 | QuoteConversionDialog.tsx |
293 | ~40-55 | Standalone conversion dialog with mode selection and item preview |
| 8 | QuoteApprovalDashboard.tsx |
245 | ~35-50 | Approval queue with settings toggle |
| 9 | QuoteTemplatesList.tsx |
215 | ~25-35 | Business template list with actions |
| 10 | QuotePreviewPanel.tsx |
215 | ~15-25 | Preview panel with template selector |
| 11 | QuoteStatusBadge.tsx |
37 | 0 | Uses QUOTE_STATUS_METADATA -- no local strings |
| 12 | quoteLineItemDraft.ts |
247 | 0 | Pure logic utilities -- no rendered strings |
| 13 | QuoteSendRecipientsField.tsx |
403 | ~10-15 | Searchable combobox for quote send recipients: trigger labels (Select a client first / No users or contacts available / Add internal user or client contact…), search placeholder, empty states (No recipients available / No matches), kind badges (Internal/Contact), remove aria-label with interpolated email |
| Total | ~6,060 | ~555-1,080 |
String estimates use ~0.15-0.2 strings/LOC. Previous batches showed this overestimates by 1.5-2x. The lower bound (~535) is the more realistic target.
All files are in packages/billing/src/components/billing-dashboard/quotes/.
Namespace Structure
msp/quotes.json
quotesTab.* -- QuotesTab.tsx list chrome, sub-tab labels, row actions, dialogs
quoteForm.* -- QuoteForm.tsx form labels, workflow buttons, dialogs
quoteDetail.* -- QuoteDetail.tsx detail sections, actions, dialogs, activity log
quoteLineItems.* -- QuoteLineItemsEditor.tsx table headers, discount panel, sections
quoteLineItems.markup.* -- Product markup badge ("{{value}}% markup"), "Markup unavailable" label, and currency-mismatch tooltip ({{costCurrency}}, {{quoteCurrency}})
quoteRecipients.* -- QuoteSendRecipientsField.tsx trigger labels, search placeholder, empty states, kind badges, remove aria-label
quoteConversion.* -- QuoteConversionDialog.tsx mode labels, item mapping, summary
quoteApproval.* -- QuoteApprovalDashboard.tsx settings, filters, empty states
quoteTemplates.* -- QuoteTemplatesList.tsx list chrome, actions
quotePreview.* -- QuotePreviewPanel.tsx panel chrome, template selector
templateEditor.* -- QuoteDocumentTemplateEditor.tsx editor chrome, tabs, preview
templatesPage.* -- QuoteDocumentTemplatesPage.tsx page chrome, table columns, actions
common.* -- Shared labels reused across multiple quote components (e.g. Cancel, Back, Delete, Save)
MSP sidebar navigation (msp/core backfill)
In addition to the msp/quotes namespace work, server/src/config/menuConfig.ts:308-317
references four nav.billing.* keys that are not yet present in msp/core.json:
nav.billing.sections.quotes— "Quotes" section headernav.billing.quotes— "Quotes" itemnav.billing.quoteBusinessTemplates— "Quote Templates" itemnav.billing.quoteLayouts— "Quote Layouts" item
All other nav.billing.* entries (contracts, invoicing, accounting exports, usage
tracking, etc.) are already populated, so the sidebar falls back to the English name
field only for the Quotes block. These four keys must be backfilled across all 9 locale
files under server/public/locales/{locale}/msp/core.json; no component changes are
required since menuConfig.ts already wires translationKey.
ROUTE_NAMESPACES Changes
The /msp/billing route already loads features/billing which will eventually include quote strings. However, two standalone quote routes need explicit namespace loading:
'/msp/quote-approvals': ['common', 'msp/core', 'msp/quotes'],
'/msp/quote-document-templates': ['common', 'msp/core', 'msp/quotes'],
Additionally, the existing /msp/billing entry should add msp/quotes:
'/msp/billing': ['common', 'msp/core', 'features/billing', 'msp/reports', 'msp/quotes'],
Acceptance Criteria
server/public/locales/en/msp/quotes.jsonexists and contains all extracted keys.- All 11 UI component files (excluding
QuoteStatusBadge.tsxandquoteLineItemDraft.ts) importuseTranslationfrom@alga-psa/ui/lib/i18n/clientand uset('key', { defaultValue: '...' })for all user-visible strings. This includesQuoteSendRecipientsField.tsx(added post-planning, 2026-04-10) and the markup badge added toQuoteLineItemsEditor.tsx. - Currency and date formatting uses
useFormatters()where applicable, replacing hardcodednew Intl.NumberFormat('en-US', ...)calls. - All 9 locale files exist:
{de,en,es,fr,it,nl,pl,xx,yy}/msp/quotes.json. validate-translations.cjspasses with 0 errors and 0 warnings formsp/quotesacross all 9 locales.- Italian translations use correct accents (verified by accent audit).
- Pseudo-locale
xxshows11111patterns for visual QA. ROUTE_NAMESPACESinpackages/core/src/lib/i18n/config.tsincludes/msp/quote-approvalsand/msp/quote-document-templatesentries, and/msp/billingincludesmsp/quotes.npm run buildsucceeds with no TypeScript errors.- No hardcoded English strings remain in the 11 wired component files (verified by grep for bare string literals in JSX), including the markup badge/tooltip in
QuoteLineItemsEditor.tsxand all trigger/search/empty-state strings inQuoteSendRecipientsField.tsx.