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
121 lines
9.1 KiB
Markdown
121 lines
9.1 KiB
Markdown
# PRD — MSP i18n: Service Catalog & Tax Rate Management
|
|
|
|
- Slug: `2026-04-09-msp-i18n-service-catalog`
|
|
- Date: `2026-04-09`
|
|
- Status: Draft (ready to start — verified unchanged on 2026-04-17; see SCRATCHPAD "Status Recheck (2026-04-17)" for billing-settings overlap check and mandatory `features/billing` add to `/msp/settings`)
|
|
|
|
## Summary
|
|
|
|
Extract hardcoded English strings from 14 service-catalog and tax-rate-management components in `packages/billing/src/components/billing-dashboard/`, create the `msp/service-catalog` namespace, wire `useTranslation()`, and generate translations for 7 languages plus 2 pseudo-locales.
|
|
|
|
## Problem
|
|
|
|
MSP operators configuring services, rate tiers, tax rates, and billing types see an entirely English UI regardless of their locale preference. These settings screens are used during initial tenant setup and ongoing catalog maintenance, so they must reflect the user's chosen language.
|
|
|
|
## Goals
|
|
|
|
1. Create `en/msp/service-catalog.json` namespace with all extracted keys.
|
|
2. Wire all 14 component files with `useTranslation('msp/service-catalog')`.
|
|
3. Generate translations for 7 production locales (de, es, fr, it, nl, pl, pt) plus 2 pseudo-locales (xx, yy).
|
|
4. Register `msp/service-catalog` in `ROUTE_NAMESPACES` for `/msp/settings`.
|
|
5. Zero regressions with `msp-i18n-enabled` flag OFF.
|
|
|
|
## Non-goals
|
|
|
|
- Translating server-side actions, API responses, or error messages from the backend.
|
|
- Refactoring component architecture or consolidating the two `ServiceConfigurationPanel` files.
|
|
- Translating sub-components imported from other packages (e.g., `TaxComponentEditor`, `TaxThresholdEditor`, `TaxHolidayManager`).
|
|
- Translating client-portal-facing billing views (those use `features/billing.json`).
|
|
|
|
## In scope: shared enum label migration (carried over from contract-lines batch)
|
|
|
|
The contract-lines batch (shipped 2026-04-14) introduced a localized option-hook pattern for shared billing enums. See [`.ai/translation/enum-labels-pattern.md`](../../../../.ai/translation/enum-labels-pattern.md) for the full recipe. This batch owns **one** carried-over call site:
|
|
|
|
| File | Line | Current import | Target hook |
|
|
|---|---|---|---|
|
|
| `service-configurations/BucketServiceConfigPanel.tsx` | 116 | `BILLING_FREQUENCY_OPTIONS` from `@alga-psa/billing/constants/billing` | `useBillingFrequencyOptions` from `@alga-psa/billing/hooks/useBillingEnumOptions` |
|
|
|
|
Migration is mechanical: replace the import, call `const billingFrequencyOptions = useBillingFrequencyOptions();` at the top of the component body, and pass `billingFrequencyOptions` into the `<CustomSelect>`. The `features/billing` namespace is already loaded on `/msp/billing` and `/client-portal/billing`; if `BucketServiceConfigPanel` renders under `/msp/settings`, add `features/billing` to that route entry in `packages/core/src/lib/i18n/config.ts` alongside the `msp/service-catalog` registration.
|
|
|
|
### Local enum constants (not the shared pattern — translate in-place)
|
|
|
|
`ConfigurationTypeSelector`, `HourlyServiceConfigPanel`, `ServiceForm`, and `FixedServiceConfigPanel` also contain module-level constant arrays with hardcoded English labels (`CONFIGURATION_TYPE_OPTIONS`, `userTypeOptions`, billing-method options, `alignmentOptions`). These are **component-local** and should NOT be extracted into the shared `useBillingEnumOptions` hook. Instead:
|
|
|
|
1. Move the array inside the component body so it has access to `t()`.
|
|
2. Keep value fields untranslated (data identifiers); wrap each `label` with `t('service-catalog.<section>.options.<value>', { defaultValue: '...' })`.
|
|
3. If the same array is used by multiple sibling components in this batch, extract a small hook colocated with them (e.g., `useUserTypeRateOptions()` in `service-configurations/` or nearby).
|
|
|
|
The dividing line: shared across feature areas → published hook in `packages/billing/src/hooks/`; local to one feature area → inline `t()` or a hook living in the same directory.
|
|
|
|
## File Inventory
|
|
|
|
| # | Component | Path (relative to `packages/billing/src/components/billing-dashboard/`) | LOC | Est. Strings | Key content |
|
|
|---|-----------|-------------------------------------------------------------------------|-----|-------------|-------------|
|
|
| 1 | TaxRates.tsx | `TaxRates.tsx` | 510 | ~45-65 | Tax rate CRUD, table columns, dialog, validation |
|
|
| 2 | UsageServiceConfigPanel.tsx | `service-configurations/UsageServiceConfigPanel.tsx` | 386 | ~30-45 | Usage-based config, tiered pricing, validation |
|
|
| 3 | ServiceRateTiers.tsx | `service-config/ServiceRateTiers.tsx` | 354 | ~25-40 | Rate tier table, CRUD, validation errors |
|
|
| 4 | ServiceSelectionDialog.tsx | `service-config/ServiceSelectionDialog.tsx` | 322 | ~25-35 | Service picker dialog, search, quick-add |
|
|
| 5 | ServiceConfigurationPanel.tsx (configurations) | `service-configurations/ServiceConfigurationPanel.tsx` | 279 | ~10-15 | Orchestrator panel, save/cancel, bucket overlay |
|
|
| 6 | HourlyServiceConfigPanel.tsx | `service-configurations/HourlyServiceConfigPanel.tsx` | 252 | ~25-35 | Hourly config, user type rates, validation |
|
|
| 7 | ServiceForm.tsx | `ServiceForm.tsx` | 215 | ~20-30 | Create service form, billing method, tax rate |
|
|
| 8 | ConfigurationTypeSelector.tsx | `service-configurations/ConfigurationTypeSelector.tsx` | 184 | ~20-30 | Type picker cards/dropdown, descriptions, warning dialog |
|
|
| 9 | TaxRateDetailPanel.tsx | `TaxRateDetailPanel.tsx` | 176 | ~25-35 | Tax rate detail view, tabs, precedence info |
|
|
| 10 | BaseServiceConfigPanel.tsx | `service-configurations/BaseServiceConfigPanel.tsx` | 176 | ~15-25 | Base config: rate, quantity, type selector |
|
|
| 11 | BucketServiceConfigPanel.tsx | `service-configurations/BucketServiceConfigPanel.tsx` | 171 | ~15-25 | Bucket hours config, rollover, overage rate |
|
|
| 12 | ServiceTaxSettings.tsx | `service-config/ServiceTaxSettings.tsx` | 142 | ~10-15 | Per-service tax rate assignment |
|
|
| 13 | ServiceConfigurationPanel.tsx (service-config) | `service-config/ServiceConfigurationPanel.tsx` | 135 | ~10-15 | Service detail page shell, unit of measure |
|
|
| 14 | FixedServiceConfigPanel.tsx | `service-configurations/FixedServiceConfigPanel.tsx` | 92 | ~10-15 | Fixed price config, proration, alignment |
|
|
| | **Total** | | **3,393** | **~285-430** | |
|
|
|
|
## Namespace Structure
|
|
|
|
**Namespace file:** `server/public/locales/en/msp/service-catalog.json`
|
|
|
|
Proposed top-level key groups:
|
|
|
|
```
|
|
taxRates.* — TaxRates.tsx, TaxRateDetailPanel.tsx
|
|
serviceForm.* — ServiceForm.tsx
|
|
serviceConfig.* — BaseServiceConfigPanel.tsx, ServiceConfigurationPanel (configurations)
|
|
fixedConfig.* — FixedServiceConfigPanel.tsx
|
|
hourlyConfig.* — HourlyServiceConfigPanel.tsx
|
|
usageConfig.* — UsageServiceConfigPanel.tsx
|
|
bucketConfig.* — BucketServiceConfigPanel.tsx
|
|
rateTiers.* — ServiceRateTiers.tsx
|
|
serviceSelection.* — ServiceSelectionDialog.tsx
|
|
configType.* — ConfigurationTypeSelector.tsx
|
|
serviceTaxSettings.* — ServiceTaxSettings.tsx
|
|
serviceDetail.* — ServiceConfigurationPanel (service-config)
|
|
```
|
|
|
|
## ROUTE_NAMESPACES Change
|
|
|
|
Add `msp/service-catalog` to the existing `/msp/settings` route entry:
|
|
|
|
```typescript
|
|
// Before
|
|
'/msp/settings': ['common', 'msp/core', 'msp/settings', 'msp/admin', 'msp/email-providers', 'features/projects', 'features/tickets'],
|
|
|
|
// After
|
|
'/msp/settings': ['common', 'msp/core', 'msp/settings', 'msp/admin', 'msp/email-providers', 'features/projects', 'features/tickets', 'msp/service-catalog'],
|
|
```
|
|
|
|
## Acceptance Criteria
|
|
|
|
- [ ] `en/msp/service-catalog.json` created with all keys (~285-430 strings).
|
|
- [ ] All 14 component files wired with `useTranslation('msp/service-catalog')`.
|
|
- [ ] All `t()` calls use `{ defaultValue: 'English fallback' }` pattern.
|
|
- [ ] 7 production locale files created (de, es, fr, it, nl, pl, pt).
|
|
- [ ] Pseudo-locale files created (xx, yy) via `generate-pseudo-locales.cjs`.
|
|
- [ ] `validate-translations.cjs` passes (0 errors, 0 warnings).
|
|
- [ ] Italian accent audit passes.
|
|
- [ ] `ROUTE_NAMESPACES` updated for `/msp/settings`.
|
|
- [ ] `msp-i18n-enabled` OFF: all 14 components show English text, no regressions.
|
|
- [ ] `msp-i18n-enabled` ON + locale `xx`: all service catalog screens show `11111`.
|
|
- [ ] German translations don't overflow in form labels, dialog fields, or table headers.
|
|
- [ ] `npm run build` succeeds with no TypeScript errors.
|
|
- [ ] Currency values use `useFormatters()` where applicable (rate display in ServiceRateTiers, ServiceSelectionDialog).
|
|
- [ ] `BucketServiceConfigPanel.tsx` imports `useBillingFrequencyOptions` from `@alga-psa/billing/hooks/useBillingEnumOptions` (not `BILLING_FREQUENCY_OPTIONS` from the deprecated constants file). Verify with `rg -n 'BILLING_FREQUENCY_OPTIONS|BILLING_FREQUENCY_DISPLAY' packages/billing/src/components/billing-dashboard/service-configurations/`.
|
|
- [ ] If `BucketServiceConfigPanel` renders under a route that does not already load `features/billing`, that route entry in `packages/core/src/lib/i18n/config.ts` has been updated.
|
|
- [ ] Local enum constants (`CONFIGURATION_TYPE_OPTIONS`, `userTypeOptions`, billing-method options, `alignmentOptions`) have been moved inside their component bodies with `label` fields wrapped in `t()` calls (values remain untranslated).
|