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

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`.