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
238 lines
12 KiB
Markdown
238 lines
12 KiB
Markdown
# PRD — MSP i18n Batches: Dispatch, Reports, Admin, Time Entry
|
|
|
|
- Slug: `2026-03-20-msp-i18n-dispatch-reports-admin-time`
|
|
- Date: `2026-03-20`
|
|
- Status: Draft
|
|
|
|
## Summary
|
|
|
|
Translate four MSP feature areas in priority order:
|
|
|
|
| Batch | Namespace | Strings | Files | Location |
|
|
|-------|-----------|---------|-------|----------|
|
|
| 2b-5 | `msp/dispatch` | ~120 | 13 | `packages/scheduling/src/components/technician-dispatch/` |
|
|
| 2b-7 | `msp/reports` | ~99 | 4 | `packages/billing/src/components/billing-dashboard/reports/` + `packages/ui/src/pages/Reports.tsx` |
|
|
| 2b-8 | `msp/admin` | ~123 | 4 | `packages/ui/src/components/settings/admin/` + `packages/integrations/src/components/email/admin/` |
|
|
| 2b-3 | `msp/time-entry` | ~161 | 33 | `packages/scheduling/src/components/time-management/` |
|
|
|
|
**Total: ~503 strings across 54 files, creating 4 new namespaces.**
|
|
|
|
Each batch: extract hardcoded strings → create English namespace JSON → wire `useTranslation()` → generate 6 non-English translations + 2 pseudo-locales → update `ROUTE_NAMESPACES` → visual QA.
|
|
|
|
## Problem
|
|
|
|
These four MSP feature areas display hundreds of hardcoded English strings. Users with non-English locale preferences see translated shell/dashboard/settings chrome but English content on these pages. Dispatch, reports, admin, and time entry are all high-traffic pages for MSP operators.
|
|
|
|
## Goals
|
|
|
|
1. Create 4 new namespace files: `msp/dispatch.json`, `msp/reports.json`, `msp/admin.json`, `msp/time-entry.json`
|
|
2. Wire all ~54 component files to consume translations via `useTranslation()`
|
|
3. Generate translations for all 7 production languages + 2 pseudo-locales (9 locale files per namespace = 36 new files)
|
|
4. Register all 4 namespaces in `ROUTE_NAMESPACES`
|
|
5. Zero regressions with `msp-i18n-enabled` flag OFF
|
|
|
|
## Non-goals
|
|
|
|
- Translating strings in components outside the 4 listed areas
|
|
- Translating console.log/console.error messages (developer-facing, not user-facing)
|
|
- Changing component behavior or refactoring
|
|
- Adding new languages
|
|
|
|
## Users and Primary Flows
|
|
|
|
**Primary user**: MSP portal operators using non-English locales.
|
|
|
|
**Flows**:
|
|
- Technician dispatch: scheduling technicians, viewing/filtering work items, drag-and-drop scheduling
|
|
- Reports: viewing contract revenue, expiration, bucket hours, profitability reports
|
|
- Admin: configuring telemetry settings, email providers, inbound ticket defaults, M365 diagnostics
|
|
- Time entry: logging time, managing timesheets, submitting for approval, approval workflow
|
|
|
|
## UX / UI Notes
|
|
|
|
- No visual changes — translated text appears in same locations
|
|
- German/Dutch text 30-50% longer — verify no overflow in: dispatch filter dropdowns, report table columns, admin form labels, timesheet column headers
|
|
- Status badges (Submitted, Approved, Changes Requested, etc.) appear in multiple components — use shared keys within each namespace to avoid inconsistency
|
|
- Time units ("h", "m", "hrs") need locale-appropriate formatting
|
|
- Pagination strings ("Page X of Y", "Showing X of Y items") need interpolation
|
|
|
|
## Requirements
|
|
|
|
### Batch 2b-5: msp/dispatch (~120 strings, 13 files)
|
|
|
|
#### D-FR1: WorkItemListPanel.tsx (11 strings)
|
|
| String | Key |
|
|
|--------|-----|
|
|
| "Work Items" | `workItems.title` |
|
|
| "Search work items..." | `workItems.searchPlaceholder` |
|
|
| "Filter by status..." | `workItems.filterPlaceholder` |
|
|
| "Unscheduled" | `workItems.status.unscheduled` |
|
|
| "Scheduled" | `workItems.status.scheduled` |
|
|
| "Previous" / "Next" | `workItems.pagination.previous` / `.next` |
|
|
| "Page {{current}} of {{total}}" | `workItems.pagination.pageInfo` |
|
|
| "Showing {{count}} of {{total}} items" | `workItems.pagination.showing` |
|
|
|
|
#### D-FR2: ScheduleViewPanel.tsx (11 strings)
|
|
| String | Key |
|
|
|--------|-----|
|
|
| "Technician Dispatch" | `page.title` |
|
|
| "Show Inactive Users" | `schedule.showInactive` |
|
|
| "< Prev" / "Next >" | `schedule.prev` / `.next` |
|
|
| "Today" | `schedule.today` |
|
|
| "Day" / "Week" | `schedule.viewDay` / `.viewWeek` |
|
|
| "Previous {{mode}}" / "Next {{mode}}" (aria) | `schedule.prevAria` / `.nextAria` |
|
|
|
|
#### D-FR3: WorkItemDetailsDrawer.tsx (~31 strings)
|
|
All field labels (Service, Status, Client, Contact, etc.), section headings (Appointment Request Details, Approval Information), error toasts, and fallback text ("N/A", "Error loading content").
|
|
|
|
#### D-FR4: TechnicianDispatchDashboard.tsx (~28 strings)
|
|
Error/success toasts, permission denied messages, filter options ("All Open", "All Closed"), private event title ("Busy"), access denied message.
|
|
|
|
#### D-FR5: ScheduleEvent.tsx + WeeklyScheduleEvent.tsx (~15 strings)
|
|
"View Details", "Delete", "Delete schedule entry", "Busy", "Unknown", "Unassigned", "Untitled" fallbacks.
|
|
|
|
#### D-FR6: WeeklyTechnicianScheduleGrid.tsx (~8 strings)
|
|
"Compare All", "Clear All", "Compare", "Stop Comparing", "View Week" tooltips and aria-labels.
|
|
|
|
#### D-FR7: WorkItemCard.tsx (2 strings)
|
|
"Needs dispatch for: {{agents}}", "Needs Dispatch" badge.
|
|
|
|
#### D-FR8: TimeHeader.tsx (2 strings)
|
|
" AM" / " PM" time suffixes.
|
|
|
|
#### D-FR9: ROUTE_NAMESPACES update
|
|
```
|
|
'/msp/technician-dispatch': ['common', 'msp/core', 'msp/dispatch']
|
|
```
|
|
|
|
---
|
|
|
|
### Batch 2b-7: msp/reports (~99 strings, 4 files)
|
|
|
|
#### R-FR1: ContractReports.tsx (~54 strings)
|
|
Tab labels ("Contract Revenue", "Expiration", "Bucket Hours", "Profitability"), tab descriptions, summary cards ("Total MRR", "YTD Revenue", "Active Contracts"), all column headers for 4 report tables, empty state messages, unit labels ("hrs", "days", "%").
|
|
|
|
#### R-FR2: ContractPerformance.tsx (~19 strings)
|
|
Metric labels ("Total Clients", "Active Clients", "Total Plans", "Avg. Plans Per Client", "Total Revenue"), table headers, empty states, "Select contract..." placeholder.
|
|
|
|
#### R-FR3: ContractUsageReport.tsx (~17 strings)
|
|
"Contract Usage Report" heading, column headers, "Summary" section, metric labels, status labels, empty states.
|
|
|
|
#### R-FR4: Reports.tsx (~9 strings)
|
|
"Reports" heading, 4 card titles ("Time Utilization", "Project Progress", "Revenue by Client", "Team Performance"), placeholder texts.
|
|
|
|
#### R-FR5: ROUTE_NAMESPACES update
|
|
```
|
|
'/msp/billing': ['common', 'msp/core', 'features/billing', 'msp/reports']
|
|
```
|
|
(Reports load on the billing route since they're in the billing dashboard)
|
|
|
|
---
|
|
|
|
### Batch 2b-8: msp/admin (~123 strings, 4 files)
|
|
|
|
#### A-FR1: TenantTelemetrySettings.tsx (~25 strings)
|
|
Section headings, toggle labels ("Enable Telemetry", "Allow User Opt-Out"), descriptions, anonymization options ("No Anonymization", "Partial", "Full"), compliance notes, "What We Collect" / "What We DON'T Collect" lists, buttons.
|
|
|
|
#### A-FR2: EmailSettings.tsx (~48 strings)
|
|
Tab labels ("Inbound Email", "Outbound Email"), provider options ("SMTP", "Resend"), form labels (Host, Port, Username, Password, From Address), placeholders, domain verification statuses ("Verified", "Failed", "Pending"), general settings labels, error messages.
|
|
|
|
#### A-FR3: Microsoft365DiagnosticsDialog.tsx (~18 strings)
|
|
Dialog title/description, diagnostic status badges ("Pass", "Warn", "Fail", "Skip"), "Running diagnostics...", "Copy Support Bundle", "Recommendations", notes/warnings.
|
|
|
|
#### A-FR4: InboundTicketDefaultsManager.tsx (~32 strings)
|
|
Section heading, field labels ("Board:", "Status:", "Priority:", "Entered By:"), badges ("Active", "Inactive"), empty states, "How It Works" section with bullet points, dialog titles, action buttons.
|
|
|
|
#### A-FR5: ROUTE_NAMESPACES update
|
|
Admin components load on settings routes:
|
|
```
|
|
'/msp/settings': ['common', 'msp/core', 'msp/settings', 'msp/admin', 'features/projects']
|
|
```
|
|
|
|
---
|
|
|
|
### Batch 2b-3: msp/time-entry (~161 strings, 33 files)
|
|
|
|
#### T-FR1: TimePeriodList.tsx (~18 strings)
|
|
"Select a Time Period", "Manage Time Periods", column headers (Period, Status, Hours Entered, Days Logged, Last Entry, Actions), status badges (In Progress, Submitted, Approved, Changes Requested, Current).
|
|
|
|
#### T-FR2: TimeEntryEditForm.tsx (~21 strings)
|
|
Form labels (Service, Date, Start Time, End Time, Duration, Billable, Notes), placeholders, validation messages ("Start time must be earlier than end time", "Duration must be at least 1 minute", "Service is required"), "Unsaved changes", "Delete Time Entry".
|
|
|
|
#### T-FR3: WorkItemPicker.tsx (~25 strings)
|
|
"Create Ad-hoc Entry", search placeholder, filter labels ("Assigned to", "Assigned to me", "All Types"), type options (Tickets, Project Tasks, Interactions, Ad-hoc Entries), date range labels, validation, empty/loading states, bundled ticket info.
|
|
|
|
#### T-FR4: WorkItemList.tsx (~20 strings)
|
|
Assignment text ("Unassigned", "1 user assigned", "{{count}} users assigned"), contact/due date labels, type badges (Ticket, Billable, Project Task, Ad-hoc Entry, Interaction), pagination, empty state.
|
|
|
|
#### T-FR5: TimeSheetHeader.tsx (~15 strings)
|
|
Status badges (reuse from T-FR1), heading template ("Time Sheet for {{name}}"), navigation aria-labels, "Show intervals", "Submit Time Sheet", "Reopen for edits", pagination.
|
|
|
|
#### T-FR6: TimeSheetApproval.tsx (~14 strings)
|
|
"Time Entry Details", field labels (Work Item, Duration, Billable, Notes), "Entry Change Suggestion", placeholder, "Approve" / "Request Changes" buttons.
|
|
|
|
#### T-FR7: ApprovalActions.tsx (~10 strings)
|
|
"Approve", "Reject", "Request Changes" buttons, dialog titles, "Rejection Reason" label, confirm buttons.
|
|
|
|
#### T-FR8: TimeSheetComments.tsx (~7 strings)
|
|
"Approver" / "Employee" labels, comment placeholders, "Respond to Changes" / "Add Comment" buttons.
|
|
|
|
#### T-FR9: Remaining files (~31 strings across ~20 files)
|
|
- `ManagerApprovalDashboard.tsx` (3): team lead access message
|
|
- `TimeEntryChangeRequestFeedback.tsx` (4): feedback states
|
|
- `WorkItemDrawer.tsx` (3): error states
|
|
- `TimeSheetClient.tsx` (2): delegation disabled, reopen toast
|
|
- `TimeSheetListView.tsx` (2): loading/empty states
|
|
- `SelectedWorkItem.tsx` (3): ad-hoc entry text, buttons
|
|
- `ContractInfoBanner.tsx` (4): contract line info messages
|
|
- `BillableLegend.tsx` (3): legend heading/description
|
|
- `IntervalItem.tsx` (3): "Now", "Auto-closed", "Active"
|
|
- `AddWorkItemDialog.tsx` (2): dialog title/description
|
|
- `TimeEntryReadOnly.tsx` (3): fallback texts
|
|
- Other files (minimal): TimeEntryProvider, IntervalSection, IntervalManagement
|
|
|
|
#### T-FR10: ROUTE_NAMESPACES update
|
|
```
|
|
'/msp/time-entry': ['common', 'msp/core', 'msp/time-entry']
|
|
'/msp/time-sheet-approvals': ['common', 'msp/core', 'msp/time-entry']
|
|
'/msp/time-management': ['common', 'msp/core', 'msp/time-entry']
|
|
```
|
|
|
|
### Non-functional Requirements
|
|
|
|
- Follow established key naming convention (page/sections/fields/actions/table/dialogs/errors/validation/messages)
|
|
- All `t()` calls use `{ defaultValue: '...' }` for English fallback
|
|
- Console.log/console.error messages stay in English (developer-facing)
|
|
- Feature flag `msp-i18n-enabled` OFF = forced English
|
|
|
|
## Rollout / Migration
|
|
|
|
- Behind existing `msp-i18n-enabled` feature flag
|
|
- No database changes
|
|
- Purely additive: new JSON files + component wiring
|
|
|
|
## Open Questions
|
|
|
|
1. **Time units**: Should "h"/"m"/"hrs" be translation keys, or handled by `useFormatters()` duration formatting? (Recommendation: translation keys for now — duration formatters can be added later.)
|
|
2. **Shared status badges**: Time entry uses status badges (Submitted, Approved, etc.) in multiple components. Should these be in `common.json` or `msp/time-entry.json`? (Recommendation: `msp/time-entry.json` with a shared `statuses.*` section within the namespace.)
|
|
3. **Reports namespace loading**: Reports are within the billing page. Should `msp/reports` load on `/msp/billing` alongside other billing namespaces? (Recommendation: yes, add to the billing route.)
|
|
4. **Admin namespace loading**: Admin components render inside settings tabs. Should `msp/admin` load on `/msp/settings`? (Recommendation: yes.)
|
|
|
|
## Acceptance Criteria (Definition of Done)
|
|
|
|
### Per batch
|
|
- [ ] English namespace JSON created with all keys
|
|
- [ ] All component files wired with `useTranslation('<namespace>')`
|
|
- [ ] `ROUTE_NAMESPACES` updated
|
|
- [ ] All 7 production locale files created with translations
|
|
- [ ] Pseudo-locale files created (xx, yy)
|
|
- [ ] Italian accent audit passes
|
|
- [ ] `msp-i18n-enabled` OFF: English text, no regressions
|
|
- [ ] `msp-i18n-enabled` ON + locale `xx`: all strings show `11111`
|
|
|
|
### Cross-cutting
|
|
- [ ] All 36 new locale files are valid JSON
|
|
- [ ] All `{{variables}}` preserved across all languages
|
|
- [ ] German translations don't overflow in tables, dropdowns, form labels
|
|
- [ ] Build passes with no TypeScript errors
|