Hermes 284313f908
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
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

12 KiB

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