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

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 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:

'/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

  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.