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
133 lines
8.1 KiB
Markdown
133 lines
8.1 KiB
Markdown
# PRD -- MSP i18n: Invoicing Sub-batch
|
|
|
|
- Slug: `2026-04-09-msp-i18n-invoicing`
|
|
- Date: `2026-04-09`
|
|
- Status: Draft (ready to start — verified unchanged on 2026-04-17; see SCRATCHPAD "Status Recheck (2026-04-17)" for corrected file paths and upstream deltas)
|
|
- Parent plan: `/.ai/translation/MSP_i18n_plan.md`
|
|
|
|
## Summary
|
|
|
|
Extract all hardcoded English strings from 22 invoicing components in
|
|
`packages/billing/src/components/`, create the `msp/invoicing` namespace, wire
|
|
`useTranslation('msp/invoicing')`, and generate translations for 7 languages plus
|
|
pseudo-locales. PaperInvoice.tsx (44 LOC) has zero user-facing strings and is excluded.
|
|
|
|
## Problem
|
|
|
|
MSP billing pages are one of the highest-value surfaces in the application (invoicing,
|
|
templates, tax management, recurring billing). All 22 invoicing components are hardcoded
|
|
English -- zero currently use `useTranslation`. Non-English MSP users encounter a
|
|
fully translated sidebar, dashboard, and settings chrome, but switch to raw English when
|
|
they enter any invoicing workflow (drafts, finalized, automatic generation, manual
|
|
creation, templates, tax reconciliation, etc.).
|
|
|
|
## Goals
|
|
|
|
1. Create `server/public/locales/en/msp/invoicing.json` with all keys extracted from
|
|
22 invoicing components
|
|
2. Wire `useTranslation('msp/invoicing')` into every component with user-visible strings
|
|
3. Use `useFormatters` from `@alga-psa/ui/lib/i18n/client` for currency and date formatting
|
|
where hardcoded `Intl.NumberFormat` / `toLocaleString` is currently used
|
|
4. Generate translations for 6 non-English locales (fr, es, de, nl, it, pl) + 2
|
|
pseudo-locales (xx, yy)
|
|
5. Run `node scripts/generate-pseudo-locales.cjs && node scripts/validate-translations.cjs`
|
|
green (exit 0) before shipping
|
|
6. Measurable: invoicing coverage goes from 0% to 100% of production components wired
|
|
|
|
## Non-goals
|
|
|
|
- Translating template content (user-designed invoice layouts, template AST field names)
|
|
- Translating invoice data (invoice numbers, client names, descriptions) -- those are
|
|
tenant data, not UI chrome
|
|
- Moving existing non-invoicing billing keys from other namespaces
|
|
- Wiring EE-only invoicing components not exported through `@alga-psa/billing/components`
|
|
- Changing layout or visual design of any component
|
|
- Translating `console.error` / `console.log` / `console.warn` developer-only messages
|
|
|
|
## File Inventory
|
|
|
|
### Large (600+ LOC) -- 2-3 features each
|
|
|
|
| Component | LOC | Est. strings | Key content |
|
|
|-----------|-----|-------------|-------------|
|
|
| AutomaticInvoices.tsx | 1,983 | ~120 | Ready-to-invoice table, parent group labels, cadence badges, incompatibility reasons, materialization gap panel, recurring history table, reverse/delete dialogs, preview dialog, PO overage dialogs, error messages |
|
|
| ManualInvoices.tsx | 859 | ~50 | Form labels, automated items table, line item headers, prepayment checkbox, error messages, validation, submit button states |
|
|
|
|
### Medium (200-600 LOC) -- 1-2 features each
|
|
|
|
| Component | LOC | Est. strings | Key content |
|
|
|-----------|-----|-------------|-------------|
|
|
| DraftsTab.tsx | 550 | ~30 | Table column headers, search placeholder, bulk actions, empty state, reverse confirmation dialog |
|
|
| FinalizedTab.tsx | 506 | ~30 | Table column headers, search placeholder, bulk actions (download/email/unfinalize), empty state |
|
|
| RecurringServicePeriodsTab.tsx | 458 | ~40 | Page title, schedule selector, form labels, summary stats, table headers, repair panel, regeneration preview |
|
|
| BillingCycles.tsx | 397 | ~25 | Page title, tooltip, column headers, anchor labels, month names, search, loading text |
|
|
| InvoicePreviewPanel.tsx | 386 | ~20 | Preview title, template selector, action buttons, loading/error/empty states |
|
|
| InvoiceTemplateEditor.tsx | 345 | ~20 | Back nav, heading, template name label, visual/code tabs, save/cancel buttons, timestamps, validation errors |
|
|
| InvoiceTemplates.tsx | 344 | ~20 | Section heading, column headers, action menu items (edit/clone/set default/delete), create button, loading text |
|
|
| ExternalTaxBatchImportDashboard.tsx | 320 | ~25 | Card title/description, column headers, batch import button, progress bar labels, results summary, help text, empty state |
|
|
| ExternalTaxImportPanel.tsx | 297 | ~25 | Panel title, pending/imported alerts, import button, history section, reconciliation labels, help text |
|
|
| SendInvoiceEmailDialog.tsx | 278 | ~25 | Dialog title, summary counts, recipients heading, recipient source labels, custom message label, email preview, send/cancel buttons |
|
|
| TaxReconciliationView.tsx | 222 | ~20 | Card title, comparison labels (Internal/External), table headers, warning alert, help text |
|
|
| GenerateTab.tsx | 207 | ~15 | Invoice type selector labels, type descriptions, success dialog message |
|
|
|
|
### Small (< 200 LOC) -- 1 feature each
|
|
|
|
| Component | LOC | Est. strings | Key content |
|
|
|-----------|-----|-------------|-------------|
|
|
| PrepaymentInvoices.tsx | 165 | ~15 | Form heading, field labels, type options, placeholders, validation errors, submit button states |
|
|
| ContractInvoiceItems.tsx | 133 | ~10 | Table headers (Description/Quantity/Rate/Amount), subtotal labels, product badge |
|
|
| InvoicingHub.tsx | 94 | ~5 | Section heading, tab labels (Generate/Drafts/Finalized) |
|
|
| InvoiceTemplateManager.tsx | 92 | ~5 | Headings (Invoice Template Manager, Sample Invoices, Template Preview) |
|
|
| InvoiceTaxSourceBadge.tsx | 81 | ~10 | Badge labels (Tax: Internal/External/Pending), tooltip strings, adapter names |
|
|
| InvoiceAnnotations.tsx | 60 | ~5 | Heading, internal/external labels, placeholder, add button |
|
|
| PurchaseOrderSummaryBanner.tsx | 45 | ~5 | Field labels (PO Number, PO Authorized, PO Consumed, PO Remaining) |
|
|
|
|
### Excluded (zero user-facing strings)
|
|
|
|
| Component | LOC | Reason |
|
|
|-----------|-----|--------|
|
|
| PaperInvoice.tsx | 44 | Pure layout wrapper, no text content |
|
|
|
|
## Namespace Structure
|
|
|
|
```
|
|
msp/invoicing.json
|
|
automaticInvoices.* -- Ready-to-invoice, parent groups, history, dialogs
|
|
manualInvoices.* -- Manual invoice form, line items, prepayment
|
|
draftsTab.* -- Drafts list, bulk actions, reverse dialog
|
|
finalizedTab.* -- Finalized list, bulk actions
|
|
recurringServicePeriods.* -- Schedule management, repair, regeneration
|
|
billingCycles.* -- Billing cycle table, anchor labels
|
|
invoicePreview.* -- Preview panel, action buttons
|
|
templateEditor.* -- Template editor form, tabs
|
|
templates.* -- Template list, actions
|
|
externalTax.* -- Batch import, single import, reconciliation
|
|
sendEmail.* -- Email dialog, recipient info
|
|
generateTab.* -- Type selector, descriptions
|
|
prepayment.* -- Prepayment form
|
|
contractItems.* -- Contract invoice items table
|
|
hub.* -- Invoicing hub, tab labels
|
|
templateManager.* -- Template manager, sample invoices
|
|
taxBadge.* -- Tax source badge labels, tooltips
|
|
annotations.* -- Annotations section
|
|
purchaseOrder.* -- PO summary banner
|
|
common.* -- Shared strings (Search, Cancel, Actions, Loading, etc.)
|
|
```
|
|
|
|
## Acceptance Criteria
|
|
|
|
- [ ] All 22 components with user-visible strings import `useTranslation('msp/invoicing')`
|
|
and wrap every user-visible string with `t('key', { defaultValue: '...' })`
|
|
- [ ] `server/public/locales/en/msp/invoicing.json` contains all keys referenced by components
|
|
- [ ] Currency formatting uses `useFormatters` or locale-aware `Intl.NumberFormat` instead of
|
|
hardcoded `'en-US'` locale
|
|
- [ ] `ROUTE_NAMESPACES` for `/msp/billing` loads the `msp/invoicing` namespace
|
|
- [ ] `node scripts/generate-pseudo-locales.cjs && node scripts/validate-translations.cjs`
|
|
exits 0 (key parity across 9 locales, pseudo-locale fill patterns, Italian accent
|
|
preservation, `{{variable}}` interpolation preservation)
|
|
- [ ] All existing billing-related unit/integration tests pass
|
|
- [ ] Visual smoke test: `/msp/billing?tab=invoicing` (all sub-tabs), `/msp/billing?tab=billing-cycles`,
|
|
`/msp/billing?tab=service-periods`, `/msp/billing?tab=templates` render correctly in `en`
|
|
and at least one non-English locale; `xx` pseudo-locale shows pseudo-text for every
|
|
visible string
|