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
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
117 lines
8.0 KiB
Markdown
117 lines
8.0 KiB
Markdown
# 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 `QuoteStatusBadge` follow-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
|
|
|
|
1. Create `server/public/locales/en/msp/quotes.json` with all extracted keys.
|
|
2. Wire all 12 component files with `useTranslation('msp/quotes')`.
|
|
3. Generate translations for 7 languages (de, es, fr, it, nl, pl, en) plus 2 pseudo-locales (xx, yy) -- 9 locale files total.
|
|
4. Register `msp/quotes` in `ROUTE_NAMESPACES` for `/msp/quote-approvals` and `/msp/quote-document-templates`.
|
|
5. Pass `validate-translations.cjs` with 0 errors across all 9 locales.
|
|
6. Zero regressions with the `msp-i18n-enabled` feature flag OFF.
|
|
|
|
## Non-goals
|
|
|
|
- Translating server-side quote actions or API responses.
|
|
- Translating the client-portal quote acceptance flow (that belongs to `client-portal` or `features/billing` namespaces).
|
|
- Refactoring component architecture.
|
|
- Translating `quoteLineItemDraft.ts` utility functions -- these are pure logic with no rendered strings (only `Intl.NumberFormat` calls that already respect locale).
|
|
- Translating the `QuoteStatusBadge.tsx` component -- it reads labels from `QUOTE_STATUS_METADATA` in `@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 header
|
|
- `nav.billing.quotes` — "Quotes" item
|
|
- `nav.billing.quoteBusinessTemplates` — "Quote Templates" item
|
|
- `nav.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:
|
|
|
|
```typescript
|
|
'/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`:
|
|
|
|
```typescript
|
|
'/msp/billing': ['common', 'msp/core', 'features/billing', 'msp/reports', 'msp/quotes'],
|
|
```
|
|
|
|
## Acceptance Criteria
|
|
|
|
1. `server/public/locales/en/msp/quotes.json` exists and contains all extracted keys.
|
|
2. All 11 UI component files (excluding `QuoteStatusBadge.tsx` and `quoteLineItemDraft.ts`) import `useTranslation` from `@alga-psa/ui/lib/i18n/client` and use `t('key', { defaultValue: '...' })` for all user-visible strings. This includes `QuoteSendRecipientsField.tsx` (added post-planning, 2026-04-10) and the markup badge added to `QuoteLineItemsEditor.tsx`.
|
|
3. Currency and date formatting uses `useFormatters()` where applicable, replacing hardcoded `new Intl.NumberFormat('en-US', ...)` calls.
|
|
4. All 9 locale files exist: `{de,en,es,fr,it,nl,pl,xx,yy}/msp/quotes.json`.
|
|
5. `validate-translations.cjs` passes with 0 errors and 0 warnings for `msp/quotes` across all 9 locales.
|
|
6. Italian translations use correct accents (verified by accent audit).
|
|
7. Pseudo-locale `xx` shows `11111` patterns for visual QA.
|
|
8. `ROUTE_NAMESPACES` in `packages/core/src/lib/i18n/config.ts` includes `/msp/quote-approvals` and `/msp/quote-document-templates` entries, and `/msp/billing` includes `msp/quotes`.
|
|
9. `npm run build` succeeds with no TypeScript errors.
|
|
10. 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.tsx` and all trigger/search/empty-state strings in `QuoteSendRecipientsField.tsx`.
|