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
202 lines
11 KiB
Markdown
202 lines
11 KiB
Markdown
# PRD — MSP i18n Batch 2b-21a: Tickets Migration
|
|
|
|
- Slug: `2026-04-05-msp-i18n-tickets-migration`
|
|
- Date: `2026-04-05`
|
|
- Status: Draft
|
|
- Parent plan: `/.ai/translation/MSP_i18n_plan.md` (Batch 2b-21a)
|
|
|
|
## Summary
|
|
|
|
Wire the existing `features/tickets` namespace into 23 unwired MSP ticket components in
|
|
`packages/tickets/src/components/`. Infrastructure is already in place: the JSON file
|
|
(`features/tickets.json`, 147 keys across 7 languages) exists and `ROUTE_NAMESPACES` already
|
|
loads it for `/msp/tickets`. The client-portal equivalents are 100% wired; this batch closes
|
|
the MSP-side gap.
|
|
|
|
## Problem
|
|
|
|
MSP users currently see translated navigation, sidebar, dashboard, and settings chrome, but
|
|
the ticket module — one of the highest-traffic MSP surfaces — is still hardcoded English.
|
|
5 of 28 production ticket components (18%) are wired; 23 remain. Because `ROUTE_NAMESPACES`
|
|
for `/msp/tickets` already loads `features/tickets`, the translation assets are downloaded
|
|
but unused. Non-English MSP users switch from a translated sidebar into an English wall of
|
|
tickets every day.
|
|
|
|
This is not a new-translation project — it is a mechanical wiring pass plus gap-filling for
|
|
MSP-specific strings (bulk operations, dispatch hooks, display settings, dashboard filters)
|
|
that the client portal didn't need.
|
|
|
|
## Goals
|
|
|
|
1. Wire `useTranslation('features/tickets')` into all 23 unwired production MSP ticket components
|
|
2. Identify and add MSP-specific keys missing from `features/tickets.json` (bulk actions,
|
|
export options, watch list, materials, display/category settings, dashboard filters,
|
|
agent schedule drawer)
|
|
3. Regenerate translations for 6 non-English locales + 2 pseudo-locales for any new keys
|
|
4. Preserve 100% test pass rate and zero user-facing regressions
|
|
5. Measurable: MSP tickets coverage goes from 18% → 100% of production components wired
|
|
|
|
## Non-goals
|
|
|
|
- Retranslating existing 147 keys — they already have 7-language coverage and are actively used
|
|
- Moving `features/tickets.json` keys into a separate `msp/tickets.json` — the shared namespace
|
|
is the correct design per the parent plan
|
|
- Translating ticket content itself (titles, descriptions, comments) — those are tenant data,
|
|
not UI chrome
|
|
- Wiring test-only components (23 `.test.tsx` files in `packages/tickets/src/components/`)
|
|
- Extending `ROUTE_NAMESPACES` — `/msp/tickets` is already wired; only verify route coverage
|
|
- Translating EE-only ticket components (ITIL extras) beyond what's exported through
|
|
`@alga-psa/tickets/components`
|
|
|
|
## Users and Primary Flows
|
|
|
|
**Primary user:** MSP technicians, dispatchers, and managers using non-English UI language
|
|
(any of fr, es, de, nl, it, pl).
|
|
|
|
**Primary flows affected:**
|
|
1. `/msp/tickets` list — dashboard, filters, bulk actions, quick-add
|
|
2. `/msp/tickets/[id]` detail — ticket info, properties, materials, watch list, email notifications
|
|
3. `/msp/settings/*` for ticket configuration — categories, display settings, response states
|
|
4. `/msp/service-requests/*` — CategoryPicker reused from tickets package
|
|
5. Quick-create dialog (global nav) — QuickAddTicket reused in `layout/QuickCreateDialog`
|
|
|
|
## UX / UI Notes
|
|
|
|
- No visual changes. Text replaced inline via `t('key', 'English fallback')`.
|
|
- Toast messages (`toast.success` / `toast.error`) also translated.
|
|
- `throw new Error('...')` strings that surface to users (handled by error boundaries) translated;
|
|
those that only appear in logs stay in English.
|
|
- Skeletons, badges, and style-only files (`TicketOriginBadge`, `ResponseSourceBadge`,
|
|
`TicketListSkeleton`, `TicketDetailsSkeleton`, `AgentScheduleDrawerStyles`) have no or 0-1
|
|
strings — confirm and skip if truly zero.
|
|
|
|
## Requirements
|
|
|
|
### Functional Requirements
|
|
|
|
**Sub-batch A: Large dashboard + quick-add components (4 files, ~150-200 strings)**
|
|
|
|
| Component | LOC | Est. strings | Key content |
|
|
|-----------|-----|--------------|-------------|
|
|
| TicketingDashboard.tsx | 2,024 | ~65 | Page title, add button, filter chrome (status/priority/assignee/response state/due date), bulk actions (move/delete/bundle), empty states, toast messages |
|
|
| QuickAddTicket.tsx | 1,596 | ~50 | Dialog title, field labels, placeholders, validation errors, toast messages, tag-creation errors |
|
|
| ticket/TicketInfo.tsx | 1,587 | ~45 | Ticket detail header, field labels, action buttons, confirmations |
|
|
| ticket/TicketProperties.tsx | 1,234 | ~45 | Side panel labels, inline edit controls, status/priority/board selects, assignment UI |
|
|
|
|
**Sub-batch B: Settings + mid-size (4 files, ~90 strings)**
|
|
|
|
| Component | LOC | Est. strings | Key content |
|
|
|-----------|-----|--------------|-------------|
|
|
| settings/CategoriesSettings.tsx | 865 | ~30 | Board/channel scoping, category tree, add/edit/delete dialogs |
|
|
| TicketExportDialog.tsx | 243 | ~25 | Export format options, column picker, date range, toast messages |
|
|
| ticket/TicketMaterialsCard.tsx | 444 | ~20 | Materials list, add material dialog, cost display, confirmations |
|
|
| settings/DisplaySettings.tsx | 250 | ~20 | Column visibility, sort options, per-user preferences |
|
|
|
|
**Sub-batch C: Small + utility components (15 files, ~50-70 strings)**
|
|
|
|
| Component | LOC | Est. strings | Notes |
|
|
|-----------|-----|--------------|-------|
|
|
| ticket/TicketWatchListCard.tsx | 512 | ~10 | Watcher list, add/remove |
|
|
| ticket/TicketEmailNotifications.tsx | 172 | ~8 | Notification toggle labels |
|
|
| ResponseStateSelect.tsx | 110 | ~6 | Response state options |
|
|
| QuickAddCategory.tsx | 274 | ~6 | Inline category creation |
|
|
| ticket/TicketDetailsContainer.tsx | 252 | ~5 | Loading/error states |
|
|
| CategoryPicker.tsx | 274 | ~5 | Picker placeholder + empty state |
|
|
| ticket/CommentMetadataDebugModal.tsx | 97 | ~4 | Debug modal (dev only, low priority) |
|
|
| ticket/TicketNavigation.tsx | 143 | ~3 | Prev/next buttons |
|
|
| TicketingDashboardContainer.tsx | 607 | ~2 | Mostly logic, few strings |
|
|
| ticket/TicketDetailsSkeleton.tsx | 155 | 0 | Skeleton — confirm zero |
|
|
| TicketOriginBadge.tsx | 143 | 0-2 | Origin labels — may need small key set |
|
|
| ticket/AgentScheduleDrawerStyles.tsx | 93 | 0 | Styles only |
|
|
| ResponseSourceBadge.tsx | 56 | 0-2 | Source labels — may need small key set |
|
|
| TicketListSkeleton.tsx | 32 | 0 | Skeleton |
|
|
| ticket/AgentScheduleDrawer.tsx | 16 | 0 | Re-export shim |
|
|
|
|
**Namespace key gaps to fill (preliminary):**
|
|
|
|
The current `features/tickets.json` covers client-portal needs. Likely MSP gaps:
|
|
|
|
- `bulk.*` — bulk move/delete/bundle actions and their toast messages
|
|
- `dashboard.*` — page title, bulk action bar, "N tickets selected"
|
|
- `export.*` — export dialog labels, format options
|
|
- `settings.categories.*` — board-scoped category management
|
|
- `settings.display.*` — column visibility, sort preferences
|
|
- `watchList.*` — watcher add/remove, empty state
|
|
- `materials.*` — materials add/edit, cost labels
|
|
- `quickAdd.*` — extended quickAdd labels beyond existing `create.*`
|
|
- `properties.*` — inline-edit side panel labels (many overlap with `fields.*` — reuse where possible)
|
|
- `errors.*` — toast error strings (session required, permission denied, partial failure)
|
|
- `validation.*` — form validation messages beyond existing `create.errors.*`
|
|
|
|
Final gap list determined during implementation — run pseudo-locale tests to surface missing keys.
|
|
|
|
### Non-functional Requirements
|
|
|
|
1. **No regressions:** all existing ticket tests pass after migration
|
|
2. **Lang-pack validation:** after every namespace edit, run
|
|
`node scripts/generate-pseudo-locales.cjs && node scripts/validate-translations.cjs`
|
|
and commit only when green. The validator covers key parity, missing/extra keys,
|
|
`{{variable}}` preservation, pseudo-locale fill patterns, and Italian accent preservation
|
|
in a single run. Pseudo-locale files (`xx/`, `yy/`) are **always** regenerated from
|
|
English — never hand-edited.
|
|
3. **Naming convention:** follow existing `features/tickets.json` patterns (camelCase, nested under semantic groups)
|
|
4. **Fallback-safe:** all `t()` calls use `t('key', 'English fallback')` signature so missing keys don't break UI
|
|
5. **Shared with client portal:** before adding a key, check if existing `features/tickets` key covers it
|
|
|
|
## Data / API / Integrations
|
|
|
|
- No database changes
|
|
- No API changes
|
|
- No new npm dependencies
|
|
- Reuses existing `useTranslation` hook from `@alga-psa/ui/lib/i18n/client`
|
|
- Reuses existing i18next infrastructure loaded via `I18nWrapper` (already in MSP layout)
|
|
|
|
## Security / Permissions
|
|
|
|
No change. Translation is a pure presentation-layer concern.
|
|
|
|
## Observability
|
|
|
|
N/A — no new operational concerns.
|
|
|
|
## Rollout / Migration
|
|
|
|
- **Flag:** work proceeds without feature-flag gating because `I18nWrapper` in MSP layout
|
|
already forces English fallback when `msp-i18n-enabled` is off (see parent plan Phase 0.7)
|
|
- **Per-PR rollout:** ship sub-batches A/B/C as independent PRs to keep review scope manageable
|
|
- **Deploy path:** translations are static JSON served from `server/public/locales/`; no cache
|
|
invalidation beyond standard Next.js static-asset rebuild
|
|
- **Back-out:** each PR is independently revertable; components continue rendering English
|
|
via `defaultValue` fallbacks even if JSON keys are reverted
|
|
|
|
## Open Questions
|
|
|
|
1. Should `bulk.*`, `dashboard.*`, `export.*`, `settings.*` keys live in `features/tickets.json`
|
|
or be extracted to a new `msp/ticketing.json` namespace? **Tentative answer:** keep in
|
|
`features/tickets.json` since client portal may eventually surface similar controls
|
|
(bulk operations on client-side ticket list). Revisit after sub-batch A if the file
|
|
grows past ~250 keys.
|
|
2. For toast messages using template literals (e.g., `` `${N} tickets moved` ``), use
|
|
i18next interpolation `t('bulk.moved', { count: N })` or keep template string with `t()`
|
|
for the static part? **Tentative answer:** interpolation — matches existing patterns in
|
|
`features/tickets.json` `messages.*`.
|
|
3. For `throw new Error('User must be logged in')` — translate or leave English?
|
|
**Tentative answer:** translate only if the error surfaces to user via toast/UI;
|
|
leave English if caught and rethrown by error boundaries or logged only.
|
|
|
|
## Acceptance Criteria (Definition of Done)
|
|
|
|
- [ ] All 23 unwired production MSP ticket components either (a) import `useTranslation`
|
|
and wrap all user-visible strings, or (b) are confirmed to have zero user-visible
|
|
strings (skeletons, styles, re-exports)
|
|
- [ ] `features/tickets.json` contains all keys referenced by MSP ticket components
|
|
- [ ] `node scripts/generate-pseudo-locales.cjs && node scripts/validate-translations.cjs`
|
|
exits 0 (covers key parity across 9 locales, pseudo-locale fill patterns, Italian
|
|
accent preservation, and `{{variable}}` interpolation preservation)
|
|
- [ ] All existing ticket-related unit/integration tests pass
|
|
- [ ] Visual smoke test: `/msp/tickets`, `/msp/tickets/[id]`, `/msp/settings/ticketing`,
|
|
`/msp/service-requests/[id]` render correctly in `en` and at least one non-English
|
|
locale (de or fr recommended); `xx` pseudo-locale shows pseudo-text for every visible
|
|
string (no bare English leakage)
|
|
- [ ] Parent plan `.ai/translation/MSP_i18n_plan.md` updated: sub-batch 2b-21a marked ✅
|