# Scratchpad — MSP i18n Batches 2b-10/11/12/16: Clients, Contacts, Assets, Onboarding

- Plan slug: `2026-03-20-msp-i18n-clients-assets-onboarding`
- Created: `2026-03-20`

## Decisions

- (2026-03-20) **String estimates carry uncertainty**: Automated scan reported ~7,450 total but previous batches showed 1.5-2.5x overestimation. Lower bound (~3,300) is more realistic. Exact counts during implementation.
- (2026-03-20) **Onboarding scope**: Only wizard steps + OnboardingWizard.tsx. Dashboard onboarding (DashboardOnboardingSection, OnboardingChecklist) already translated in batch 2b-2.
- (2026-03-20) **Execution order**: Clients → Contacts → Assets → Onboarding. Clients/contacts are daily-use features. Assets is large but self-contained. Onboarding is used once per tenant but is important for first impressions.
- (2026-03-24) **Post-rebase plan update**: Rebased on origin/main after merges of client-owned-contracts-simplification, board-specific-statuses, and 4 new i18n batches (dispatch, reports, admin, time-entry). Updated file counts, LOC, and string estimates. No structural changes to the plan — all 4 batches remain valid.

## Discoveries / Constraints

### Clients (2b-10)
- (2026-03-20) 31 files in `packages/clients/src/components/clients/` (+panels/ subdir with ClientNotesPanel.tsx)
- (2026-03-20) `ClientDetails.tsx` (1,805 LOC) is the largest — full client detail page with multiple sections
- (2026-03-20) Heavy billing integration: BillingConfiguration, ClientContractAssignment, ClientContractLineDashboard, ClientBillingSchedule, TaxSettingsForm — may share terminology with msp/contracts namespace. Cross-check translations.
- (2026-03-24) **Post-rebase update**: 6 client files modified by client-owned-contracts-simplification merge. `BillingConfiguration.tsx` (661→701 LOC), `ClientBillingSchedule.tsx` (~387→504 LOC, significant growth), `ClientContractAssignment.tsx` (430→423 LOC, refactored for assignment-explicit semantics), `ClientLocations.tsx` (1,021→1,038 LOC), `ContractLines.tsx` (+55 LOC), `ClientContractLineDisambiguationGuide.tsx` (+14 LOC). Billing terminology changed — must cross-check against `msp/contracts` namespace.
- (2026-03-24) `ClientBillingSchedule.tsx` mixes local UI copy with `cadenceContext` strings returned from shared billing helpers. The local shell/actions/labels can move to `msp/clients` now, but full localization of `changeScopeDescription`, `scheduleDescription`, and `previewDescription` likely needs an upstream shared-data change later.
- (2026-03-20) `ClientsImportDialog.tsx` (697 LOC) has CSV column mapping — translate UI labels but keep CSV header names in English
- (2026-03-20) `ClientLanguagePreference.tsx` (118 LOC) — this component likely already has i18n awareness. Check during implementation.
- (2026-03-24) `PlanPickerDialog.tsx` and `ClientPlanDisambiguationGuide.tsx` are thin re-export shims (`ContractLinePickerDialog` / `ClientContractLineDisambiguationGuide`) rather than direct UI surfaces. No separate namespace wiring is needed there; translation work belongs in the underlying implementation files.
- (2026-03-20) 2 empty stub files (PlanPickerDialog.tsx, ClientPlanDisambiguationGuide.tsx) — 1 LOC each, skip
- (2026-03-24) `ClientLocations.tsx` already contains partial `clients.locations.*` key usage. Extend that structure instead of replacing it with unrelated key names.
- (2026-03-24) Keep stable ids/values untranslated in clients batch: client detail tab ids (`details`, `tickets`, `assets`, `billing`, `billing-dashboard`, `contacts`, `documents`, `tax-settings`, `additional-info`, `notes`, `interactions`), billing tab ids (`general`, `plans`, `taxRates`, `overlaps`), guide tabs (`overview`, `bestPractices`, `scenarios`, `troubleshooting`), import steps (`upload`, `mapping`, `preview`, `importing`, `complete`, `unassigned`), and list/filter values (`grid`, `list`, `all`, `active`, `inactive`, `company`, `individual`).

### Contacts (2b-11)
- (2026-03-24) 12 files in `packages/clients/src/components/contacts/` (was 13 at plan time; ContactNotes.tsx removed or merged)
- (2026-03-20) Same package as clients (`@alga-psa/clients`) — can share import of `useTranslation`
- (2026-03-20) `ContactPortalTab.tsx` (652 LOC) — manages client portal access for contacts. May need to coordinate with client-portal translations.
- (2026-03-20) `ContactPhoneNumbersEditor.tsx` (755 LOC) — complex phone number CRUD with format hints
- (2026-03-24) There is also `packages/clients/src/components/contacts/panels/ContactNotesPanel.tsx` with user-facing copy (`Notes & Quick Info`, `Initial Note`, load/save error UI). The plan checklist omits it, so treat it as required remaining contact work rather than leaving the panel untranslated.

### Assets (2b-12)
- (2026-03-24) 41 files in `packages/assets/src/components/` (was 39 at plan time; +1 test file, +1 new component)
- (2026-03-20) Well-organized with subdirectories: `tabs/`, `panels/`, `shared/`
- (2026-03-20) `StatusBadge.tsx` (102 LOC, ~60 strings) — high density, many status label variations
- (2026-03-20) RMM integration components (RmmStatusIndicator, RmmVitalsPanel) — technical labels, some may stay English
- (2026-03-20) `AssetCommandPalette.tsx` (256 LOC) — search/command interface, needs accessible translations
- (2026-03-20) `index.ts` (13 LOC) — just exports, skip
- (2026-03-24) Asset wrapper-only files need lighter handling in the namespace scaffold: `AssetDashboard.tsx`, `AssetFormClient.tsx`, `AssetAlertsSection.tsx`, `AssetPatchStatusSection.tsx`, and `AssetSoftwareInventory.tsx` are mostly wrappers/dynamic imports, while `AssetDetailDrawer.types.ts`, `index.ts`, and the component test files are non-UI sources that should stay out of locale coverage checks.
- (2026-03-25) `F022` audit showed `AssetDetailDrawerClient.tsx`, `AssociatedAssets.tsx`, and `QuickAddAsset.tsx` were already wired to `useTranslation('msp/assets')` on the branch. A Babel AST audit found `93 + 33 + 45` unique `t(...)` keys across those files with `missing=0` against `server/public/locales/en/msp/assets.json`, so the checklist item could be flipped without further code edits.
- (2026-03-25) `CreateMaintenanceScheduleDialog.tsx` still contains its own untranslated form/help copy even though it lives under `components/tabs/`. Kept it out of `F023` because the plan item explicitly names only the 6 tab surfaces; treat the dialog as `F025` remaining asset-file work.
- (2026-03-25) `AssetInfoPanel.tsx` previously generated its copy-button id from the visible field label. That would have made automation ids locale-dependent once translated, so `F024` switches the serial-number copy action to a stable explicit id (`copy-serial-number`) while localizing the tooltip text.
- (2026-03-25) `F025` wrapper audit: `AssetAlertsSection.tsx`, `AssetPatchStatusSection.tsx`, `AssetSoftwareInventory.tsx`, `AssetDashboard.tsx`, `AssetDashboardGrid.tsx`, `AssetDocuments.tsx`, and `RemoteAccessButton.tsx` do not render local copy in this package, so they were intentionally left out of namespace wiring. `AssetFormClient.tsx` looked like a thin wrapper too, but its dynamic loading skeleton rendered `"Edit Asset"`, so that fallback was translated as part of the catch-all sweep.
- (2026-03-25) `F026` translation execution split cleanly across two workers: `de/es/fr` and `it/nl/pl`. That kept write scopes disjoint and avoided a giant manual locale edit in the main thread. Pseudo-locales for `msp/assets` were generated locally with the targeted transform from the runbook instead of re-running the global pseudo-locale script, so this feature commit only touches the new namespace.

### Onboarding (2b-16)
- (2026-03-24) `TicketingConfigStep.tsx` (3,040 LOC, was 2,920) is MASSIVE — larger than most entire components. Has ticketing board setup, status configuration, priority settings, category management, SLA configuration. This is effectively a mini settings page embedded in the wizard. **Updated**: +120 LOC for board-scoped status configuration (board-specific-statuses merge).
- (2026-03-24) `BillingSetupStep.tsx` (610 LOC, was 582) — billing mode decoupled from service type (+28 LOC).
- (2026-03-20) Dashboard components are EXCLUDED (already in msp/dashboard.json):
  - DashboardOnboardingSection.tsx — translated
  - OnboardingChecklist.tsx — translated
  - DashboardOnboardingSlot.tsx — no strings
  - DashboardOnboardingSkeleton.tsx — no strings
- (2026-03-24) Wizard-only onboarding form/help/alert text should stay in the new `msp/onboarding` namespace rather than extending `msp/dashboard.json`. Existing dashboard overlap remains under `onboarding.*` keys in `msp/dashboard.json`, but wizard steps do not reuse that namespace today.
- (2026-03-24) Keep stable onboarding ids/values untranslated: `OnboardingStepId`, `step.id`, `substep.id`, `data_import`, checklist status enums (`complete`, `in_progress`, `not_started`, `blocked`), role values (`admin`, `technician`, `manager`, `user`), billing mode values (`fixed`, `hourly`, `usage`), `USD`, and ticketing sentinels like `none`, `board`, `category`, `status`, `priority`.
- (2026-03-25) `msp/onboarding` locale generation needed one post-translation fix in French: `billingSetupStep.serviceTypes.import.success.description` initially duplicated `{{suffix}}`, so `validate-translations.cjs` caught it even after the overall key structure was correct. Keep interpolation-token parity checks in the F034/F091 validations.
- (2026-03-25) `ROUTE_NAMESPACES` lives in `packages/core/src/lib/i18n/config.ts` (re-exported via `packages/ui/src/lib/i18n/config.ts`). Adding the four new route prefixes there is sufficient for exact-path loads and nested detail routes because `getNamespacesForRoute()` already falls back to the longest matching prefix.
- (2026-03-25) Follow-up after T042/T045 sweep: `server/public/locales/{xx,yy}/msp/{contacts,onboarding}.json` still had legacy pseudo-locale output in a few interpolation-heavy leaves (`fill per word` instead of the canonical `fill + variables + fill` shape from `scripts/generate-pseudo-locales.cjs`). Normalize those four files before closing the plan so the repo matches the documented pseudo-generator contract.
- (2026-03-25) `F030` scaffold created `server/public/locales/en/msp/onboarding.json` with shared `common.actions` / `common.states` plus component-scoped roots for `OnboardingProvider`, `OnboardingWizard`, and the six in-scope wizard steps. This mirrors the earlier batch scaffolds: enough stable section names for follow-up wiring, without pretending the full key inventory is known before the large `TicketingConfigStep.tsx` migration happens.
- (2026-03-25) `TicketingConfigStep.tsx` now depends on explicit `ticketingConfigStep.statuses.types.{open,closed}` keys because the board-scoped status type label is resolved from a computed key rather than a direct string-literal `t('...')` call. Keep those two keys in mind during later locale generation/audits because simple direct-key scans will not discover them automatically.
- (2026-03-25) `OnboardingWizard.tsx` cannot fully localize the shell by itself because `packages/ui/src/components/onboarding/WizardNavigation.tsx` owns the Back/Skip/Saving/Completing labels. `F032` expands that shared component with label props instead of leaving the wizard half-translated.
- (2026-03-25) `OnboardingProvider.tsx` is mostly logic, but it does render the blocking `"Loading..."` state while onboarding status checks are in flight. Treat that spinner copy as in-scope onboarding UI even though the component is otherwise a route gate.

## Progress Log

- (2026-03-24) Completed `F001`: created `server/public/locales/en/msp/clients.json` with an initial component-scoped English namespace covering the clients list, client details, quick-add, locations, billing/contracts, tax settings, import flow, and notes panel. This seeds the new `msp/clients` file so follow-up wiring can land on stable keys instead of inventing ad hoc paths mid-edit. Validation: `node -e "JSON.parse(require('fs').readFileSync('server/public/locales/en/msp/clients.json','utf8')); console.log('ok')"` returned `ok`.
- (2026-03-24) Completed `F002`: wired `packages/clients/src/components/clients/Clients.tsx` and `packages/clients/src/components/clients/ClientDetails.tsx` to `useTranslation('msp/clients')`. The list/detail chrome, main filter controls, view labels, bulk-delete dialogs, core detail tab labels, major field labels, save/delete/reactivate toasts, and deactivate/reactivate confirmations now use `t(..., { defaultValue })` while preserving stable tab ids/query-param values. Expanded `server/public/locales/en/msp/clients.json` with the new list/detail keys needed by those two files. Validation:
  - `npx eslint packages/clients/src/components/clients/Clients.tsx packages/clients/src/components/clients/ClientDetails.tsx` (warnings only, no errors)
  - `cd server && npx tsc -p tsconfig.json --noEmit --pretty false`
- (2026-03-24) Completed `F003`: wired `packages/clients/src/components/clients/QuickAddClient.tsx`, `packages/clients/src/components/clients/ClientLocations.tsx`, and `packages/clients/src/components/clients/ClientsImportDialog.tsx` to `msp/clients`. `QuickAddClient` now translates its section headings, primary labels/placeholders, CTA buttons, and metadata-loading errors; `ClientLocations` now resolves its existing `clients.locations.*` keys from the new namespace instead of `common`; `ClientsImportDialog` now translates the main import flow headings, action buttons, and confirmation copy. Expanded `server/public/locales/en/msp/clients.json` with the additional import-flow keys used by these surfaces. Validation:
  - `npx eslint packages/clients/src/components/clients/QuickAddClient.tsx packages/clients/src/components/clients/ClientLocations.tsx packages/clients/src/components/clients/ClientsImportDialog.tsx` (warnings only, no errors)
  - `cd server && npx tsc -p tsconfig.json --noEmit --pretty false`
- (2026-03-24) Completed `F004`: wired `packages/clients/src/components/clients/BillingConfiguration.tsx`, `packages/clients/src/components/clients/ClientContractAssignment.tsx`, `packages/clients/src/components/clients/ClientContractLineDashboard.tsx`, and `packages/clients/src/components/clients/ClientBillingSchedule.tsx` to `msp/clients`. Billing toasts/dialog chrome/tab labels, contract-assignment table/actions/status labels, dashboard card/table headings, and billing-schedule controls/summaries now use `t(..., { defaultValue })` while preserving stable tab ids, contract ids, billing cycle enum values, and assignment semantics from the recent refactor. Expanded `server/public/locales/en/msp/clients.json` with billing, contract-assignment, dashboard, and schedule keys including cycle/month/weekday labels and schedule summary templates. Validation:
  - `node -e "JSON.parse(require('fs').readFileSync('server/public/locales/en/msp/clients.json','utf8')); console.log('json ok')"` returned `json ok`
  - `npx eslint packages/clients/src/components/clients/BillingConfiguration.tsx packages/clients/src/components/clients/ClientContractAssignment.tsx packages/clients/src/components/clients/ClientContractLineDashboard.tsx packages/clients/src/components/clients/ClientBillingSchedule.tsx` (warnings only, no errors)
  - `cd server && npx tsc -p tsconfig.json --noEmit --pretty false`
- (2026-03-24) Completed `F005`: wired the remaining non-stub client components to `msp/clients`, including grid/list surfaces (`ClientGridCard`, `ClientsGrid`, `ClientsList`, `ClientCreatedDialog`, `ClientQuickView`, `ClientSideDetails`) and the billing/settings/helper surfaces (`BillingConfigForm`, `ClientContractDialog`, `ClientContractLineDisambiguationGuide`, `ClientCreditExpirationSettings`, `ClientLanguagePreference`, `ClientServiceOverlapMatrix`, `ClientTaxRates`, `ClientZeroDollarInvoiceSettings`, `ContractLinePickerDialog`, `ContractLines`, `ServiceCatalog`, `TaxRateCreateForm`, `TaxSettingsForm`, `panels/ClientNotesPanel`). After the code wiring landed, generated a source-to-locale sync with the Babel AST parser to backfill every still-missing English key referenced by client components into `server/public/locales/en/msp/clients.json`. Re-export shims `PlanPickerDialog.tsx` and `ClientPlanDisambiguationGuide.tsx` were intentionally skipped because they delegate to already-translated implementation files. Validation:
  - AST key audit against all client component `t(...)` calls reported `missing=0` for `server/public/locales/en/msp/clients.json`
  - `node -e "JSON.parse(require('fs').readFileSync('server/public/locales/en/msp/clients.json','utf8')); console.log('json ok')"` returned `json ok`
  - `cd server && npx tsc -p tsconfig.json --noEmit --pretty false`
- (2026-03-24) Completed `F006`: created `server/public/locales/{de,es,fr,it,nl,pl}/msp/clients.json` with translated `msp/clients` content based on the finalized English namespace, then generated `server/public/locales/{xx,yy}/msp/clients.json` via `node scripts/generate-pseudo-locales.cjs` (`xx=11111`, `yy=55555`). Restored unrelated pseudo-locale noise from previously existing MSP namespaces so this feature commit stays scoped to the new clients namespace. Validation:
  - `for f in server/public/locales/{de,es,fr,it,nl,pl,xx,yy}/msp/clients.json; do node -e "JSON.parse(require('fs').readFileSync(process.argv[1],'utf8'))" "$f" || exit 1; done` returned `json-ok`
  - `node scripts/validate-translations.cjs` → `PASSED` (`Errors: 0`, `Warnings: 0`)
- (2026-03-24) Completed `F007`: ran a targeted Italian accent audit on `server/public/locales/it/msp/clients.json` using `rg -n '\\b(puo|gia|verra|funzionalita|perche|cosi|piu|e necessario|e possibile|e richiesto|e richiesta|e configurato|e configurata)\\b' server/public/locales/it/msp/clients.json`. The audit returned no matches, and spot checks of higher-risk translated strings (renewal defaults help, language preference success copy, tax-source help) confirmed accented forms were preserved correctly.
- (2026-03-24) Completed `T001`: `node scripts/validate-translations.cjs` passed after adding the clients namespace across `{en,de,es,fr,it,nl,pl,xx,yy}`. Summary reported `Errors: 0`, `Warnings: 0`, confirming `msp/clients` key parity across all 9 locale variants.
- (2026-03-24) Completed `T002`: `cd server && npx tsc -p tsconfig.json --noEmit --pretty false` passed after wiring all client component surfaces. The remaining `PlanPickerDialog.tsx` and `ClientPlanDisambiguationGuide.tsx` files were confirmed to be re-export shims, so the compile check covers the actual implementation files that render client UI.
- (2026-03-24) Completed `T003`: the targeted Italian accent audit for `server/public/locales/it/msp/clients.json` returned zero matches for the known dropped-accent patterns (`puo`, `gia`, `verra`, `funzionalita`, `e necessario`, etc.), so the clients namespace passed the post-translation accent check.
- (2026-03-25) Completed `T004`: added `server/src/test/unit/i18n/mspClientsContactsAssetsOnboardingBatch.test.ts` as the durable batch-coverage harness for the remaining clients/contacts/assets/onboarding locale QA. For the clients visual-QA checklist item, the new `T004` assertion verifies the `xx` pseudo-locale resolves representative client list/detail/quick-add/import/billing keys to `11111`, which gives a repeatable proxy for the manual pseudo-locale smoke test. Validation:
  - `cd server && npx vitest run src/test/unit/i18n/mspClientsContactsAssetsOnboardingBatch.test.ts`
- (2026-03-25) Completed `T010`: exercised the `T010` vitest assertion in `server/src/test/unit/i18n/mspClientsContactsAssetsOnboardingBatch.test.ts`, which checks `msp/contacts` key parity against English across all 7 production locales plus the 2 pseudo-locales and verifies interpolation variables stay aligned for the production translations. Validation:
  - `cd server && npx vitest run src/test/unit/i18n/mspClientsContactsAssetsOnboardingBatch.test.ts -t "T010"`
- (2026-03-25) Completed `T011`: reran the server TypeScript compile after the contact batch wiring was complete. This is the compile gate for the translated contact list/detail/import flows, the phone-number editor, portal tab, quick-add form, and the notes panel. Validation:
  - `cd server && npx tsc -p tsconfig.json --noEmit --pretty false`
- (2026-03-25) Completed `T012`: exercised the `T012` vitest assertion in `server/src/test/unit/i18n/mspClientsContactsAssetsOnboardingBatch.test.ts`, which enforces the contact Italian accent audit by rejecting the known dropped-accent anti-patterns and checking for representative accented forms. Validation:
  - `cd server && npx vitest run src/test/unit/i18n/mspClientsContactsAssetsOnboardingBatch.test.ts -t "T012"`
- (2026-03-25) Completed `T013`: exercised the `T013` vitest assertion in `server/src/test/unit/i18n/mspClientsContactsAssetsOnboardingBatch.test.ts`, which checks the `xx` pseudo-locale renders representative contact list/detail/quick-add/phone-editor/portal-tab keys as `11111` for repeatable pseudo-locale QA coverage. Validation:
  - `cd server && npx vitest run src/test/unit/i18n/mspClientsContactsAssetsOnboardingBatch.test.ts --coverage.enabled=false -t "T013"`
- (2026-03-25) Completed `T020`: exercised the `T020` vitest assertion in `server/src/test/unit/i18n/mspClientsContactsAssetsOnboardingBatch.test.ts`, which checks `msp/assets` key parity against English across all 7 production locales plus the 2 pseudo-locales and verifies interpolation variables stay aligned for the production translations. Validation:
  - `cd server && npx vitest run src/test/unit/i18n/mspClientsContactsAssetsOnboardingBatch.test.ts --coverage.enabled=false -t "T020"`
- (2026-03-25) Completed `T021`: reran the server TypeScript compile after the asset batch wiring was complete. This is the compile gate for the translated asset dashboard/detail/form surfaces, drawer, tabs, panels, status/command widgets, and related helpers. Validation:
  - `cd server && npx tsc -p tsconfig.json --noEmit --pretty false`
- (2026-03-25) Completed `T022`: exercised the `T022` vitest assertion in `server/src/test/unit/i18n/mspClientsContactsAssetsOnboardingBatch.test.ts`, which enforces the asset Italian accent audit by rejecting the known dropped-accent anti-patterns and checking for representative accented forms. Validation:
  - `cd server && npx vitest run src/test/unit/i18n/mspClientsContactsAssetsOnboardingBatch.test.ts --coverage.enabled=false -t "T022"`
- (2026-03-25) Completed `T023`: exercised the `T023` vitest assertion in `server/src/test/unit/i18n/mspClientsContactsAssetsOnboardingBatch.test.ts`, which checks the `xx` pseudo-locale renders representative asset dashboard/form/detail-drawer/tab/panel keys as `11111` for repeatable pseudo-locale QA coverage. Validation:
  - `cd server && npx vitest run src/test/unit/i18n/mspClientsContactsAssetsOnboardingBatch.test.ts --coverage.enabled=false -t "T023"`
- (2026-03-25) Completed `T030`: exercised the `T030` vitest assertion in `server/src/test/unit/i18n/mspClientsContactsAssetsOnboardingBatch.test.ts`, which checks `msp/onboarding` key parity against English across all 7 production locales plus the 2 pseudo-locales and verifies interpolation variables stay aligned for the production translations. Validation:
  - `cd server && npx vitest run src/test/unit/i18n/mspClientsContactsAssetsOnboardingBatch.test.ts --coverage.enabled=false -t "T030"`
- (2026-03-25) Completed `T031`: reran the server TypeScript compile after the onboarding batch wiring was complete. This is the compile gate for the translated onboarding wizard shell, all six scoped step components, and the onboarding provider wrapper. Validation:
  - `cd server && npx tsc -p tsconfig.json --noEmit --pretty false`
- (2026-03-25) Completed `T032`: exercised the `T032` vitest assertion in `server/src/test/unit/i18n/mspClientsContactsAssetsOnboardingBatch.test.ts`, which enforces the onboarding Italian accent audit by rejecting the known dropped-accent anti-patterns and checking for representative accented forms. Validation:
  - `cd server && npx vitest run src/test/unit/i18n/mspClientsContactsAssetsOnboardingBatch.test.ts --coverage.enabled=false -t "T032"`
- (2026-03-25) Completed `T033`: exercised the `T033` vitest assertion in `server/src/test/unit/i18n/mspClientsContactsAssetsOnboardingBatch.test.ts`, which checks the `xx` pseudo-locale renders representative onboarding wizard/step keys as `11111` for repeatable pseudo-locale QA coverage. Validation:
  - `cd server && npx vitest run src/test/unit/i18n/mspClientsContactsAssetsOnboardingBatch.test.ts --coverage.enabled=false -t "T033"`
- (2026-03-25) Completed `T034`: exercised the `T034` vitest assertion in `server/src/test/unit/i18n/mspClientsContactsAssetsOnboardingBatch.test.ts`, which confirms the finalized `msp/onboarding` English leaf-key set has zero overlap with `msp/dashboard` so wizard-specific copy does not collide with the pre-existing dashboard onboarding namespace. Validation:
  - `cd server && npx vitest run src/test/unit/i18n/mspClientsContactsAssetsOnboardingBatch.test.ts --coverage.enabled=false -t "T034"`
- (2026-03-25) Completed `T040`: exercised the `T040` vitest assertion in `server/src/test/unit/i18n/mspClientsContactsAssetsOnboardingBatch.test.ts`, which verifies `ROUTE_NAMESPACES` now maps `/msp/clients`, `/msp/contacts`, `/msp/assets`, and `/msp/onboarding` to their new feature namespaces. Validation:
  - `cd server && npx vitest run src/test/unit/i18n/mspClientsContactsAssetsOnboardingBatch.test.ts --coverage.enabled=false -t "T040"`
- (2026-03-25) Completed `T041`: reran the repo translation validator after all four namespaces and their locale bundles were registered. The validator reported zero errors and zero warnings across the 6 production locales plus the 2 pseudo-locales. Validation:
  - `node scripts/validate-translations.cjs` → `PASSED` (`Errors: 0`, `Warnings: 0`)
- (2026-03-25) Completed `T042`: exercised the `T042` vitest assertion in `server/src/test/unit/i18n/mspClientsContactsAssetsOnboardingBatch.test.ts`, which confirms the `xx`/`yy` pseudo-locale files for `msp/clients`, `msp/contacts`, `msp/assets`, and `msp/onboarding` mirror the English key structure and still expose representative `11111`/`55555` fill values. Validation:
  - `cd server && npx vitest run src/test/unit/i18n/mspClientsContactsAssetsOnboardingBatch.test.ts --coverage.enabled=false -t "T042"`
- (2026-03-25) Completed `T043`: reran the full repo production build after the cross-batch locale/test changes were in place. The build completed successfully again, including the AssemblyScript invoice templates, Next.js production build, TypeScript, page-data collection, static page generation, and trace collection. Existing webpack warnings from unrelated dependencies remained non-fatal. Validation:
  - `npm run build`
- (2026-03-25) Completed `T044`: exercised the `T044` vitest assertion in `server/src/test/unit/i18n/mspClientsContactsAssetsOnboardingBatch.test.ts`, which checks the `/msp` layout still gates locale loading behind the `msp-i18n-enabled` feature flag and that representative clients/contacts/assets/onboarding components keep `defaultValue` fallbacks in their `t(...)` calls so English remains the OFF-state baseline. Validation:
  - `cd server && npx vitest run src/test/unit/i18n/mspClientsContactsAssetsOnboardingBatch.test.ts --coverage.enabled=false -t "T044"`
- (2026-03-25) Completed `T045`: exercised the `T045` vitest assertion in `server/src/test/unit/i18n/mspClientsContactsAssetsOnboardingBatch.test.ts`, which enforces practical German length thresholds on the specific overflow-sensitive surfaces called out in the PRD: client forms/billing labels, the contact phone-editor/search prompt, asset dashboard/detail labels, and onboarding wizard controls. Validation:
  - `cd server && npx vitest run src/test/unit/i18n/mspClientsContactsAssetsOnboardingBatch.test.ts --coverage.enabled=false -t "T045"`
- (2026-03-24) Completed `F010`: created `server/public/locales/en/msp/contacts.json` with an initial component-scoped English scaffold for the contacts list, contact detail, import flow, phone editor, portal tab, quick-add form, edit/view helpers, client-embedded list, route shell, loading skeleton, avatar upload, and notes panel. This matches the existing `msp/clients` namespace style so follow-up wiring can land on stable section names instead of ad hoc keys. Validation: `node -e "JSON.parse(require('fs').readFileSync('server/public/locales/en/msp/contacts.json','utf8')); console.log('ok')"` returned `ok`.
- (2026-03-24) Completed `F011`: wired `packages/clients/src/components/contacts/ContactDetails.tsx`, `packages/clients/src/components/contacts/Contacts.tsx`, and `packages/clients/src/components/contacts/ContactsImportDialog.tsx` to `useTranslation('msp/contacts')`. The contact list now translates its heading, filter/search chrome, table columns, action menus, delete/inactive flows, and last-phone-type confirmation; the detail view now translates its tab labels, field labels, save/delete/deactivate toasts, back/open actions, and inbound-destination helper text while preserving stable route/tab ids; the import dialog now translates its flow headings, mapping UI, tooltips, confirmation copy, and upload helper text while keeping CSV headers/state ids (`upload`, `mapping`, `preview`, `importing`, `results`, `complete`, `unassigned`) stable. Expanded `server/public/locales/en/msp/contacts.json` with the list/detail/import keys used by these files. Validation:
  - `node -e "JSON.parse(require('fs').readFileSync('server/public/locales/en/msp/contacts.json','utf8')); console.log('contacts json ok')"` returned `contacts json ok`
  - `npx eslint packages/clients/src/components/contacts/Contacts.tsx packages/clients/src/components/contacts/ContactDetails.tsx packages/clients/src/components/contacts/ContactsImportDialog.tsx` (warnings only, no errors)
  - `cd server && npx tsc -p tsconfig.json --noEmit --pretty false`
  - Perl/Node audit over `t('...')` calls in the three files reported `contacts keys ok` for `server/public/locales/en/msp/contacts.json`
- (2026-03-24) Completed `F012`: wired `packages/clients/src/components/contacts/ContactPhoneNumbersEditor.tsx`, `packages/clients/src/components/contacts/ContactPortalTab.tsx`, and `packages/clients/src/components/contacts/QuickAddContact.tsx` to `msp/contacts`. The phone editor now translates its headings, row actions, phone-type labels, custom-type search prompts, last-usage confirmation, and inline validation copy; the portal tab now translates the client-portal access shell, admin/role/status/invitation history controls, toast copy, and invitation badge labels while keeping invitation status enum values stable; the quick-add contact flow now translates dialog chrome, field labels/placeholders, validation summary/errors, status toggle copy, and success/error toasts, and it now routes phone validation messages through the translated phone-editor helper for consistent copy. Expanded `server/public/locales/en/msp/contacts.json` with phone-editor, portal-tab, and quick-add keys including dynamic phone-type labels and invitation-status badge labels. Validation:
  - `node -e "JSON.parse(require('fs').readFileSync('server/public/locales/en/msp/contacts.json','utf8')); console.log('contacts json ok')"` returned `contacts json ok`
  - `npx eslint packages/clients/src/components/contacts/ContactPhoneNumbersEditor.tsx packages/clients/src/components/contacts/ContactPortalTab.tsx packages/clients/src/components/contacts/QuickAddContact.tsx` (warnings only, no errors)
  - `cd server && npx tsc -p tsconfig.json --noEmit --pretty false`
  - Perl/Node audit over `t('...')` calls in those three files plus explicit checks for `contactPhoneNumbersEditor.phoneTypes.*` and `contactPortalTab.history.status.{pending,used,expired,revoked}` reported `contacts F012 keys ok`
- (2026-03-24) Completed `F013`: wired the remaining contact detail/list surfaces to `msp/contacts`. `packages/clients/src/components/contacts/ContactDetailsEdit.tsx` now translates its reflection labels, field labels/placeholders, client/status/help text, save/cancel actions, and validation/save errors while routing phone validation through `translateContactPhoneValidationErrors(...)`; `packages/clients/src/components/contacts/ContactDetailsView.tsx` now translates its shell actions, field labels, empty states, phone-type labels, status values, documents heading, and client-loading/update errors; `packages/clients/src/components/contacts/ClientContactsList.tsx` now translates the embedded table headers, filter labels/options, action menu labels, add-contact CTA, and load/retry errors; `packages/clients/src/components/contacts/panels/ContactNotesPanel.tsx` now translates the notes panel title, save/retry actions, legacy-note heading, load-error title, unknown-error fallback, and last-updated timestamp. `ContactsLayout.tsx`, `ContactAvatarUpload.tsx`, and `ContactsSkeleton.tsx` remain unchanged because they are pure wrappers/placeholders with no local user-facing copy. Expanded `server/public/locales/en/msp/contacts.json` with the detail/list/panel keys used by these files and corrected scaffold defaults like `contactPhoneNumbersEditor.title` (`Phone Numbers`) and `contactNotesPanel.title` (`Notes & Quick Info`) to match the rendered UI. Validation:
  - `node -e "JSON.parse(require('fs').readFileSync('server/public/locales/en/msp/contacts.json','utf8')); console.log('contacts json ok')"` returned `contacts json ok`
  - `npx eslint packages/clients/src/components/contacts/ClientContactsList.tsx packages/clients/src/components/contacts/ContactDetailsEdit.tsx packages/clients/src/components/contacts/ContactDetailsView.tsx packages/clients/src/components/contacts/panels/ContactNotesPanel.tsx` (warnings only, no errors)
  - `cd server && npx tsc -p tsconfig.json --noEmit --pretty false`
  - key-presence audit over the four files against `server/public/locales/en/msp/contacts.json` reported `contacts F013 keys ok`
- (2026-03-24) Completed `F014`: created `server/public/locales/{de,es,fr,it,nl,pl}/msp/contacts.json` with translated `msp/contacts` content based on the finalized English namespace, then generated `server/public/locales/{xx,yy}/msp/contacts.json` using the pseudo-locale replacement logic that preserves `{{variables}}`. The six production locale files were added via parallel worker passes split across `{de,es,fr}` and `{it,nl,pl}`; afterwards generated the two pseudo locale files locally so the contact batch has the full 9-locale set required by the PRD. Validation:
  - `for f in server/public/locales/{de,es,fr,it,nl,pl}/msp/contacts.json; do node -e "JSON.parse(require('fs').readFileSync(process.argv[1],'utf8')); console.log(process.argv[1]+': ok')" "$f" || exit 1; done` reported all six locale files as `ok`
  - `node scripts/validate-translations.cjs` → `PASSED` (`Errors: 0`, `Warnings: 0`)
  - `node -e` spot check over `server/public/locales/{xx,yy}/msp/contacts.json` confirmed representative contact keys resolve to `11111` / `55555`
- (2026-03-24) Completed `F015`: ran a targeted Italian accent audit on `server/public/locales/it/msp/contacts.json`. The strict dropped-accent scan (`pou/gia/verra/funzionalita/perche/cosi/piu`, plus the common `e necessario`-style phrases) returned no matches, and representative spot checks confirmed accented forms are present in the locale file (`Sì`, `è`, `più`). Validation:
  - `rg -n '\b(puo|gia|verra|funzionalita|perche|cosi|piu)\b| e necessario| e possibile| e richiesto| e richiesta| e configurato| e configurata' server/public/locales/it/msp/contacts.json` returned no matches
  - `rg -n 'Sì| è |più|funzionalità' server/public/locales/it/msp/contacts.json` returned representative accented strings
- (2026-03-24) Completed `F020`: created `server/public/locales/en/msp/assets.json` as the initial English scaffold for the asset batch. Added shared `common.actions` / `common.states` keys plus component-scoped `title` / `description` entries for the main asset list/detail/form surfaces, related panels/tabs/shared widgets, and the thin wrapper components that will still hang later `t(...)` calls off the `msp/assets` namespace. Intentionally excluded `packages/assets/src/components/index.ts`, `packages/assets/src/components/AssetDetailDrawer.types.ts`, and the local asset test files because they are not user-facing locale sources. Validation:
  - `node -e "JSON.parse(require('fs').readFileSync('server/public/locales/en/msp/assets.json','utf8')); console.log('assets json ok')"` returned `assets json ok`
- (2026-03-24) Completed `F021`: wired the three largest asset surfaces to `msp/assets`. `packages/assets/src/components/AssetForm.tsx` now translates the edit-form heading, client/location shell, field labels, type-specific form labels, option labels, loading/errors, and save/cancel actions while preserving stable status/type values and route/query parameters. `packages/assets/src/components/AssetDashboardClient.tsx` now translates the dashboard shell, KPI cards, filter bar, active-filter chips, table headers, row actions, selection banner, drawer error copy, and empty detail fallbacks, with explicit key coverage for asset statuses, types, agent statuses, and column labels. `packages/assets/src/components/AssetDetails.tsx` now translates the detail-page reflection label, tab labels, field labels, maintenance summary cards, related-asset copy, loading state, and badge/value labels, again keeping internal ids and route segments unchanged. Expanded `server/public/locales/en/msp/assets.json` with the new `assetForm.*`, `assetDashboardClient.*`, and `assetDetails.*` keys plus enum-backed families (`statuses`, `types`, `deviceTypes`, `agentStatuses`, `columns`, `relationshipTypes`). Validation:
  - `node -e "JSON.parse(require('fs').readFileSync('server/public/locales/en/msp/assets.json','utf8')); console.log('assets json ok')"` returned `assets json ok`
  - `npx eslint packages/assets/src/components/AssetForm.tsx packages/assets/src/components/AssetDashboardClient.tsx packages/assets/src/components/AssetDetails.tsx` (3 existing `no-explicit-any` warnings in `AssetDashboardClient.tsx`, 0 errors)
  - key-presence audit over the three files against `server/public/locales/en/msp/assets.json` reported `assets F021 keys ok`
  - `cd server && npx tsc -p tsconfig.json --noEmit --pretty false`
- (2026-03-25) Completed `F031`: wired `packages/onboarding/src/components/steps/TicketingConfigStep.tsx` to `useTranslation('msp/onboarding')` and translated the full wizard-step shell, board/category/status/priority/SLA setup copy, action labels, errors, alerts, support-email helper, and ITIL modal while preserving stable ticketing ids and sentinel values. Added `ticketingConfigStep.statuses.types.{open,closed}` to `server/public/locales/en/msp/onboarding.json` for the computed status-type label helper. Validation:
  - direct-key audit over `TicketingConfigStep.tsx` plus manual verification of `ticketingConfigStep.statuses.types.{open,closed}` reported `missing 0`
  - `npx eslint packages/onboarding/src/components/steps/TicketingConfigStep.tsx` (warnings only, no errors)
  - `cd server && npx tsc -p tsconfig.json --noEmit --pretty false`
- (2026-03-25) Completed `F032`: wired `packages/onboarding/src/components/steps/BillingSetupStep.tsx`, `packages/onboarding/src/components/steps/TeamMembersStep.tsx`, and `packages/onboarding/src/components/OnboardingWizard.tsx` to `msp/onboarding`. Billing setup now translates its service-catalog management/import flows, billing-mode labels, create/delete toasts, and action-required alert; team-members now translates license summaries, validation errors, card labels, and role names; the wizard shell now translates step labels, navigation CTA labels, dialog chrome, and fallback submit/save errors. Extended `packages/ui/src/components/onboarding/WizardNavigation.tsx` with optional label props so the shell buttons are localizable without changing stable step ids. Validation:
  - direct-key audit over `BillingSetupStep.tsx`, `TeamMembersStep.tsx`, `OnboardingWizard.tsx`, and `WizardNavigation.tsx` plus manual verification of `teamMembersStep.roles.{admin,technician,manager,user}` reported `missing 0`
  - `npx eslint packages/onboarding/src/components/steps/BillingSetupStep.tsx packages/onboarding/src/components/steps/TeamMembersStep.tsx packages/onboarding/src/components/OnboardingWizard.tsx packages/ui/src/components/onboarding/WizardNavigation.tsx` (warnings only, no errors)
  - `cd server && npx tsc -p tsconfig.json --noEmit --pretty false`
- (2026-03-25) Completed `F033`: wired `packages/onboarding/src/components/steps/ClientInfoStep.tsx`, `packages/onboarding/src/components/steps/AddClientStep.tsx`, `packages/onboarding/src/components/steps/ClientContactStep.tsx`, and `packages/onboarding/src/components/OnboardingProvider.tsx` to `msp/onboarding`. The client-info/contact/add-client steps now translate their headings, fields, validation/help copy, optional alerts, and created-state summaries; `OnboardingProvider` now resolves its loading spinner from `msp/onboarding`. Added email-validation mapping keys for the client-info step so validator messages still localize cleanly. Validation:
  - direct-key audit over `ClientInfoStep.tsx`, `AddClientStep.tsx`, `ClientContactStep.tsx`, and `OnboardingProvider.tsx` reported `missing 0`
  - `npx eslint packages/onboarding/src/components/steps/ClientInfoStep.tsx packages/onboarding/src/components/steps/AddClientStep.tsx packages/onboarding/src/components/steps/ClientContactStep.tsx packages/onboarding/src/components/OnboardingProvider.tsx` (warnings only, no errors)
  - `cd server && npx tsc -p tsconfig.json --noEmit --pretty false`
- (2026-03-25) Completed `F034`: generated `server/public/locales/{de,es,fr,it,nl,pl}/msp/onboarding.json` from the finalized English namespace and added pseudo-locales at `server/public/locales/{xx,yy}/msp/onboarding.json`. A first French pass had correct key structure but duplicated `{{suffix}}` in `billingSetupStep.serviceTypes.import.success.description`; after correcting that token mismatch, all six production locales and both pseudo-locales validated cleanly. Validation:
  - key-structure audit against `server/public/locales/en/msp/onboarding.json` reported `missing 0 / extra 0` for `de`, `es`, `fr`, `it`, `nl`, and `pl`
  - `for f in server/public/locales/{de,es,fr,it,nl,pl,xx,yy}/msp/onboarding.json; do node -e "JSON.parse(require('fs').readFileSync(process.argv[1],'utf8')); console.log(process.argv[1]+': ok')" "$f" || exit 1; done` reported all eight files as `ok`
  - `node scripts/validate-translations.cjs` → `PASSED` (`Errors: 0`, `Warnings: 0`)
- (2026-03-25) Completed `F035`: ran the targeted Italian accent audit on `server/public/locales/it/msp/onboarding.json`. The dropped-accent scan returned no matches, and spot checks confirmed accented forms are present in the translated copy (`Sì`, `è`, `più`, `verrà`, `funzionalità`). Validation:
  - `rg -n '\b(puo|gia|verra|funzionalita|perche|cosi|piu)\b| e necessario| e possibile| e richiesto| e richiesta| e configurato| e configurata' server/public/locales/it/msp/onboarding.json` returned no matches
  - `rg -n 'Sì| è |più|funzionalità|perché|così|verrà|già' server/public/locales/it/msp/onboarding.json` returned representative accented strings
- (2026-03-25) Completed `F090`: updated `packages/core/src/lib/i18n/config.ts` so `/msp/clients`, `/msp/contacts`, `/msp/assets`, and `/msp/onboarding` load their new feature namespaces in addition to `common` and `msp/core`. This also covers nested detail/edit routes via the existing longest-prefix matcher in `getNamespacesForRoute()`. Validation:
  - source audit confirmed the four new route prefixes point at `msp/clients`, `msp/contacts`, `msp/assets`, and `msp/onboarding`
  - `cd server && npx tsc -p tsconfig.json --noEmit --pretty false`
- (2026-03-25) Completed `F091`: ran the repo translation validator after all four namespaces (`msp/clients`, `msp/contacts`, `msp/assets`, `msp/onboarding`) and their locale bundles were in place. This confirmed the cross-batch acceptance criterion of full key parity with no interpolation mismatches across the 6 production locales and 2 pseudo-locales. Validation:
  - `node scripts/validate-translations.cjs` → `PASSED` (`Errors: 0`, `Warnings: 0`)
- (2026-03-25) Completed `F092`: reran pseudo-locale generation for `msp/clients`, `msp/contacts`, `msp/assets`, and `msp/onboarding` using the same replacement rules as `scripts/generate-pseudo-locales.cjs`, preserving `{{variables}}` while filling leaf strings with `11111` / `55555`. The regenerated files were already in sync, so the pass produced no net diff outside the runbook/plan updates. Validation:
  - targeted generation pass rewrote `server/public/locales/{xx,yy}/msp/{clients,contacts,assets,onboarding}.json`
  - representative spot checks confirmed `xx` / `yy` still resolve those namespaces to `11111` / `55555`
- (2026-03-25) Completed `F093`: ran the full repo build from the project root. The build completed successfully, including the AssemblyScript invoice templates, `server` Next.js production build, TypeScript, page-data collection, static page generation, and build traces. Existing webpack warnings from unrelated dependencies remained non-fatal and did not block the build. Validation:
  - `npm run build`
- (2026-03-25) Completed `F094`: normalized `server/public/locales/{xx,yy}/msp/{contacts,onboarding}.json` to the canonical pseudo-locale generator format used by `scripts/generate-pseudo-locales.cjs`. This follow-up removes the last legacy `fill-per-word` remnants in interpolation-heavy strings so pseudo locales now consistently use the repo-standard `fill + {{variables}} + fill` shape. Validation:
  - `node scripts/validate-translations.cjs` → `PASSED` (`Errors: 0`, `Warnings: 0`)
- (2026-03-25) Completed `F022`: audited `packages/assets/src/components/AssetDetailDrawerClient.tsx`, `packages/assets/src/components/AssociatedAssets.tsx`, and `packages/assets/src/components/QuickAddAsset.tsx` and confirmed the branch already had the planned `msp/assets` wiring. `AssetDetailDrawerClient` covers drawer tabs, overview/maintenance/ticket/configuration copy, relative-time strings, and type-detail labels; `AssociatedAssets` covers relationship management, drawer-loading errors, search/add flows, and status/type labels; `QuickAddAsset` covers dialog chrome, field labels/placeholders, validation copy, and type-specific option labels. Validation:
  - Babel AST key audit over the three files reported `AssetDetailDrawerClient=93`, `AssociatedAssets=33`, `QuickAddAsset=45`, `missing=0` against `server/public/locales/en/msp/assets.json`
  - `cd server && npx tsc -p tsconfig.json --noEmit --pretty false`
- (2026-03-25) Completed `F023`: wired the six named asset tab surfaces to `msp/assets`: `packages/assets/src/components/tabs/MaintenanceSchedulesTab.tsx`, `packages/assets/src/components/tabs/RelatedAssetsTab.tsx`, `packages/assets/src/components/tabs/ServiceHistoryTab.tsx`, `packages/assets/src/components/tabs/SoftwareInventoryTab.tsx`, `packages/assets/src/components/tabs/AuditLogTab.tsx`, and `packages/assets/src/components/tabs/DocumentsPasswordsTab.tsx`. The maintenance tab now translates summary cards, schedule/history tables, status badges, empty states, and delete-confirmation copy; the related-assets tab now translates table/dialog chrome, success/error toasts, and relationship labels; the service-history tab now translates the ticket table shell and empty state; the software-inventory tab now translates search/filter/table chrome plus unknown-value fallbacks; the audit-log tab now translates change-type headings and actor labels; the documents/passwords tab now translates the placeholder secrets panel. Expanded `server/public/locales/en/msp/assets.json` with the new tab-specific keys and adjusted the existing scaffolded tab `title` values from placeholder `"* Tab"` strings to the actual UI labels/templates used at runtime. Validation:
  - AST key audit over the 6 tab files reported `totalKeys=85`, `missing=0` against `server/public/locales/en/msp/assets.json`
  - `npx eslint packages/assets/src/components/tabs/MaintenanceSchedulesTab.tsx packages/assets/src/components/tabs/RelatedAssetsTab.tsx packages/assets/src/components/tabs/ServiceHistoryTab.tsx packages/assets/src/components/tabs/SoftwareInventoryTab.tsx packages/assets/src/components/tabs/AuditLogTab.tsx packages/assets/src/components/tabs/DocumentsPasswordsTab.tsx` (2 pre-existing `no-explicit-any` warnings in `RelatedAssetsTab.tsx` and `SoftwareInventoryTab.tsx`, 0 errors)
  - `cd server && npx tsc -p tsconfig.json --noEmit --pretty false`
- (2026-03-25) Completed `F024`: wired the five asset panel components to `msp/assets`: `packages/assets/src/components/panels/AssetInfoPanel.tsx`, `packages/assets/src/components/panels/RmmVitalsPanel.tsx`, `packages/assets/src/components/panels/HardwareSpecsPanel.tsx`, `packages/assets/src/components/panels/SecurityPatchingPanel.tsx`, and `packages/assets/src/components/panels/AssetNotesPanel.tsx`. The asset-info panel now translates labels, fallback values, and copy-to-clipboard tooltip text while keeping the copy button id stable; the RMM vitals panel now translates its disconnected state, refresh action, field labels, uptime/network templates, and “never” fallback; the hardware panel now translates CPU/RAM/storage labels plus unknown/free-space templates; the security panel now translates antivirus, patch-status, and firewall copy; the notes panel now translates save/retry/error chrome and the last-updated stamp. Expanded `server/public/locales/en/msp/assets.json` with panel-specific field/action/value keys and replaced the scaffolded panel `title` values with the actual rendered headings. Validation:
  - AST key audit over the 5 panel files reported `totalKeys=60`, `missing=0` against `server/public/locales/en/msp/assets.json`
  - `npx eslint packages/assets/src/components/panels/AssetInfoPanel.tsx packages/assets/src/components/panels/RmmVitalsPanel.tsx packages/assets/src/components/panels/HardwareSpecsPanel.tsx packages/assets/src/components/panels/SecurityPatchingPanel.tsx packages/assets/src/components/panels/AssetNotesPanel.tsx` (6 pre-existing `no-explicit-any` warnings across `AssetInfoPanel.tsx`, `RmmVitalsPanel.tsx`, and `AssetNotesPanel.tsx`, 0 errors)
  - `cd server && npx tsc -p tsconfig.json --noEmit --pretty false`
- (2026-03-25) Completed `F025`: translated the remaining in-package asset UI surfaces under `msp/assets`, covering `packages/assets/src/components/AssetCommandPalette.tsx`, `packages/assets/src/components/AssetDetailHeader.tsx`, `packages/assets/src/components/AssetDetailView.tsx`, `packages/assets/src/components/AssetDetailTabs.tsx`, `packages/assets/src/components/AssetMetricsBanner.tsx`, `packages/assets/src/components/AssetFormClient.tsx`, `packages/assets/src/components/CreateTicketFromAssetButton.tsx`, `packages/assets/src/components/DeleteAssetButton.tsx`, `packages/assets/src/components/RmmStatusIndicator.tsx`, `packages/assets/src/components/shared/CopyableField.tsx`, `packages/assets/src/components/shared/StatusBadge.tsx`, `packages/assets/src/components/shared/UtilizationBar.tsx`, and `packages/assets/src/components/tabs/CreateMaintenanceScheduleDialog.tsx`. The command palette now translates quick-action metadata, headings, empty state, and hint copy; the detail shell now translates the back/nav/menu/tabs/loading/error/banner surfaces; the create-ticket and maintenance-schedule dialogs now translate their form fields, validation errors, and loading placeholders; the delete button, RMM indicator, copyable field, status badge, and utilization bar now translate their shared action/status/fallback text. Expanded `server/public/locales/en/msp/assets.json` with the corresponding root/shared namespaces (`assetCommandPalette`, `assetDetailHeader`, `assetDetailTabs`, `assetDetailView`, `assetMetricsBanner`, `createTicketFromAssetButton`, `deleteAssetButton`, `rmmStatusIndicator`, `copyableField`, `statusBadge`, `createMaintenanceScheduleDialog`) and updated existing scaffold `title` values where runtime UI now reads from those keys. Validation:
  - AST key audit over the 12 remaining asset files reported `totalKeys=136`, `missing=0` against `server/public/locales/en/msp/assets.json`
  - `npx eslint packages/assets/src/components/AssetFormClient.tsx packages/assets/src/components/AssetCommandPalette.tsx packages/assets/src/components/AssetDetailHeader.tsx packages/assets/src/components/AssetDetailView.tsx packages/assets/src/components/AssetDetailTabs.tsx packages/assets/src/components/AssetMetricsBanner.tsx packages/assets/src/components/CreateTicketFromAssetButton.tsx packages/assets/src/components/DeleteAssetButton.tsx packages/assets/src/components/RmmStatusIndicator.tsx packages/assets/src/components/shared/CopyableField.tsx packages/assets/src/components/shared/StatusBadge.tsx packages/assets/src/components/shared/UtilizationBar.tsx packages/assets/src/components/tabs/CreateMaintenanceScheduleDialog.tsx`
  - `cd server && npx tsc -p tsconfig.json --noEmit --pretty false`
  - final asset-string sweep (`rg` over component roots/shared/tabs/panels) only left the intentional `ESC` keyboard hint plus non-UI type signatures; no remaining user-facing English literals were found in the package-owned asset components
- (2026-03-25) Completed `F026`: created `server/public/locales/{de,es,fr,it,nl,pl}/msp/assets.json` from the finalized English namespace using two parallel worker passes split across `{de,es,fr}` and `{it,nl,pl}`. Then generated `server/public/locales/{xx,yy}/msp/assets.json` locally from `en/msp/assets.json` with the targeted pseudo-locale transform that preserves `{{variables}}`. Validation:
  - `for f in server/public/locales/{de,es,fr,it,nl,pl}/msp/assets.json; do node -e "JSON.parse(require('fs').readFileSync(process.argv[1],'utf8')); console.log(process.argv[1]+': ok')" "$f" || exit 1; done` reported all six production locale files as `ok`
  - spot check over `server/public/locales/{xx,yy}/msp/assets.json` confirmed representative keys resolve to `11111` / `55555` while preserving interpolation tokens (`createTicketFromAssetButton.defaultTitle` → `11111 {{name}} 11111`, `55555 {{name}} 55555`)
  - `node scripts/validate-translations.cjs` → `PASSED` (`Errors: 0`, `Warnings: 0`)
- (2026-03-25) Completed `F027`: ran the targeted Italian dropped-accent audit on `server/public/locales/it/msp/assets.json`. The strict scan for the known broken forms (`puo`, `gia`, `verra`, `funzionalita`, `perche`, `cosi`, `piu`, plus the common `e necessario` / `e possibile` phrases) returned no matches, and representative spot checks confirmed accented strings exist in the asset locale file (`Sì`, `è`, `già`, `obbligatorio` copy with accents preserved where applicable). Validation:
  - `rg -n '\b(puo|gia|verra|funzionalita|perche|cosi|piu)\b| e necessario| e possibile| e richiesto| e richiesta| e configurato| e configurata' server/public/locales/it/msp/assets.json` returned no matches
  - `rg -n 'Sì| è |più|funzionalità|perché|così|già|verrà' server/public/locales/it/msp/assets.json | head -n 20` returned representative accented strings
- (2026-03-25) Completed `F030`: created `server/public/locales/en/msp/onboarding.json` as the initial English scaffold for the onboarding batch. Added shared `common.actions` / `common.states` keys plus component-scoped `title` / `description` entries for `OnboardingProvider`, `OnboardingWizard`, `AddClientStep`, `BillingSetupStep`, `ClientContactStep`, `ClientInfoStep`, `TeamMembersStep`, and `TicketingConfigStep`. Dashboard onboarding components remain intentionally excluded because they already live under `msp/dashboard.json`. Validation:
  - `node -e "JSON.parse(require('fs').readFileSync('server/public/locales/en/msp/onboarding.json','utf8')); console.log('onboarding json ok')"` returned `onboarding json ok`
- (2026-03-25) Completed `F031`: wired `packages/onboarding/src/components/steps/TicketingConfigStep.tsx` to `useTranslation('msp/onboarding')`. The step now translates its loading/header shell, ticket-numbering fields, board/category/status/priority section chrome, import/add flows, default/delete toasts and fallback validation copy, the support-email requirement block, and the full ITIL standards modal including category lists and the priority matrix. Expanded `server/public/locales/en/msp/onboarding.json` with the step-specific English keys and added explicit `ticketingConfigStep.statuses.types.{open,closed}` entries for the computed board-scoped status type labels. Validation:
  - direct-key locale audit over `TicketingConfigStep.tsx` plus manual checks for `ticketingConfigStep.statuses.types.{open,closed}` reported `missing=0` against `server/public/locales/en/msp/onboarding.json`
  - `npx eslint packages/onboarding/src/components/steps/TicketingConfigStep.tsx` (warnings only, no errors; existing `any` / hook-deps warnings remain)
  - `cd server && npx tsc -p tsconfig.json --noEmit --pretty false`
- (2026-03-25) Completed `F032`: wired `packages/onboarding/src/components/steps/BillingSetupStep.tsx`, `packages/onboarding/src/components/steps/TeamMembersStep.tsx`, and `packages/onboarding/src/components/OnboardingWizard.tsx` to `msp/onboarding`, and extended `packages/ui/src/components/onboarding/WizardNavigation.tsx` with configurable label props so the wizard shell can translate Back/Skip/Next/Finish/Saving/Completing copy. `BillingSetupStep` now translates the service form, service-type management/import flows, billing-mode labels, delete/create toasts, and the action-required validation block; `TeamMembersStep` now translates the invite form, license/limit alerts, save validation errors, role labels, button states, and optional/unsaved warnings; `OnboardingWizard` now translates step names, shell titles, debug labels, and fallback error messages for each persisted step. Expanded `server/public/locales/en/msp/onboarding.json` with the billing/team-members/wizard keys plus explicit `teamMembersStep.roles.{admin,technician,manager,user}` entries for the computed role-label mapping. Validation:
  - direct-key locale audit across `BillingSetupStep.tsx`, `TeamMembersStep.tsx`, and `OnboardingWizard.tsx` plus manual checks for `teamMembersStep.roles.{admin,technician,manager,user}` reported `missing=0` against `server/public/locales/en/msp/onboarding.json`
  - `npx eslint packages/onboarding/src/components/steps/BillingSetupStep.tsx packages/onboarding/src/components/steps/TeamMembersStep.tsx packages/onboarding/src/components/OnboardingWizard.tsx packages/ui/src/components/onboarding/WizardNavigation.tsx` (warnings only, no errors)
  - `cd server && npx tsc -p tsconfig.json --noEmit --pretty false`
- (2026-03-25) Completed `F033`: wired the remaining onboarding surfaces to `msp/onboarding`: `packages/onboarding/src/components/steps/ClientInfoStep.tsx`, `packages/onboarding/src/components/steps/AddClientStep.tsx`, `packages/onboarding/src/components/steps/ClientContactStep.tsx`, and `packages/onboarding/src/components/OnboardingProvider.tsx`. `ClientInfoStep` now translates both the first-run and revisit shells, password reset/strength requirements, password match states, and maps shared email-validation messages into onboarding keys so the step does not show mixed-language validation copy; `AddClientStep` now translates its header, validation errors, field labels/placeholders, success state, and tax/optional helper note; `ClientContactStep` now translates its empty-state, success copy, field labels/placeholders, and optional helper alert; `OnboardingProvider` now translates the blocking loading spinner shown while tenant onboarding status is checked. Expanded `server/public/locales/en/msp/onboarding.json` with the remaining client-info/add-client/client-contact/provider keys. Validation:
  - direct-key locale audit across `ClientInfoStep.tsx`, `AddClientStep.tsx`, `ClientContactStep.tsx`, and `OnboardingProvider.tsx` reported `missing=0` against `server/public/locales/en/msp/onboarding.json`
  - `npx eslint packages/onboarding/src/components/steps/ClientInfoStep.tsx packages/onboarding/src/components/steps/AddClientStep.tsx packages/onboarding/src/components/steps/ClientContactStep.tsx packages/onboarding/src/components/OnboardingProvider.tsx` (warnings only, no errors)
  - `cd server && npx tsc -p tsconfig.json --noEmit --pretty false`

## Commands / Runbooks

### Validation
```bash
node scripts/validate-translations.cjs
npm run build
```

### Pseudo-locale generation
```bash
cat << 'SCRIPT' | node - server/public/locales/en/msp/<name>.json 11111
const fs = require("fs");
const fill = process.argv[3];
const transform = (obj) => {
  const out = {};
  for (const [k, v] of Object.entries(obj)) {
    out[k] = typeof v === "object" && v !== null ? transform(v) : fill;
  }
  return out;
};
const src = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
console.log(JSON.stringify(transform(src), null, 2));
SCRIPT
```

### Italian accent audit
```bash
for ns in clients contacts assets onboarding; do
  echo "=== $ns ===";
  grep -n ' e [a-z]\| puo \| gia \| verra \| funzionalita\| necessario' server/public/locales/it/msp/$ns.json 2>/dev/null || echo "(file not found)";
done
```

## Links / References

- Translation plan: `.ai/translation/MSP_i18n_plan.md`
- Translation guide: `.ai/translation/translation-guide.md`
- Previous plans: `docs/plans/2026-03-19-msp-i18n-batch-2b1-core/`, `docs/plans/2026-03-20-msp-i18n-dispatch-reports-admin-time/`

### Key directories
| Directory | Files | Sub-batch |
|-----------|-------|-----------|
| `packages/clients/src/components/clients/` | 32 | 2b-10 |
| `packages/clients/src/components/contacts/` | 13 | 2b-11 |
| `packages/assets/src/components/` | 39 | 2b-12 |
| `packages/onboarding/src/components/steps/` | 7 | 2b-16 |
| `packages/onboarding/src/components/OnboardingWizard.tsx` | 1 | 2b-16 |

## Open Questions

- **Client portal overlap**: Do any client/contact components render in the client portal? If so, those strings should go in `features/*.json` (shared) not `msp/*.json`.
- **Asset EE components**: Are there EE-only asset features that live elsewhere?
- **TicketingConfigStep decomposition**: At 2,920 LOC, should this be broken into sub-sections in the namespace, or is a flat structure fine?
- **Billing terminology sync**: Client billing config uses contract/invoice/tax terms. Cross-check against `msp/contracts` and `msp/invoicing` namespaces for consistency.
