# PRD — MSP i18n Batches 2b-1 + 2b-2: Core Namespace + Dashboard - Slug: `2026-03-19-msp-i18n-batch-2b1-core` - Date: `2026-03-19` - Status: Complete ## Summary Extract all hardcoded English strings from two areas: 1. **MSP portal shell (msp/core)** — navigation sidebar, header bar, quick-create menu, job indicator, trial/payment banners, AI chat sidebar, and confirmation dialogs (~80 new keys, added to existing 72 keys) 2. **MSP dashboard (msp/dashboard)** — welcome banners, feature cards, onboarding checklist, step definitions, progress indicators (~110 keys, new namespace) Wire up all components with `useTranslation()`, generate translations for all 7 languages + 2 pseudo-locales, and validate with visual QA. **Total scope: ~190 new keys across 17 files.** ## Problem The MSP portal shell (sidebar, header, breadcrumbs, banners, quick-create) and dashboard display ~190 hardcoded English strings that are not yet translatable. The shell is visible on **every** MSP page; the dashboard is the first page users see after login. Together they are the highest-impact untranslated surfaces. Users with non-English locale preferences see a mix of translated page content and English chrome. ## Goals 1. Extract all user-visible hardcoded strings from MSP shell components into `msp/core.json` 2. Create `msp/dashboard.json` namespace with all dashboard strings 3. Wire all components to consume translations via `useTranslation()` 4. Make `menuConfig.ts` translation-aware so sidebar nav items and section titles use translated labels 5. Generate accurate translations for all 7 production languages (en, fr, es, de, nl, it, pl) 6. Update pseudo-locale files (xx, yy) to cover new keys 7. Register `/msp` and `/msp/dashboard` routes in `ROUTE_NAMESPACES` to load `msp/dashboard` 8. Zero regressions — everything works identically with `msp-i18n-enabled` flag OFF (forced English) ## Non-goals - Translating strings inside child components rendered by QuickCreateDialog (QuickAddTicket, QuickAddClient, etc.) — those belong to their respective feature namespaces - Translating the Enterprise Edition RightSidebar chat UI — that's in `@enterprise/components` - Adding new languages beyond the existing 7 - Changing the namespace structure or route mappings (beyond adding dashboard) - Translating the GitHub star button (external widget, not our text) - Refactoring menuConfig.ts to a different data structure ## Users and Primary Flows **Primary user**: MSP portal operators who have set a non-English locale preference. **Flow**: User logs in → sees sidebar, header, breadcrumbs, banners, quick-create menu all in their preferred language instead of hardcoded English. ## UX / UI Notes - No visual changes — all strings appear in the same locations, just translated - German/Dutch translations are typically 30-50% longer than English — verify no layout overflow in sidebar (collapsed tooltip), header breadcrumbs, banner badges, dropdown menus, and dashboard feature cards - The sidebar collapsed state shows tooltips — these must also be translated - Tab labels in settings sidebar sections are already covered by existing `settings.sections.*` and `settings.tabs.*` keys — verify they're consumed, not hardcoded - Dashboard onboarding cards have progress rings with text like "3 of 5 Steps" — verify interpolation works in all languages - Dashboard feature card descriptions are long sentences — verify no truncation in German/Dutch ## Requirements ### Functional Requirements #### FR1: menuConfig.ts — Translation-aware menu data The menu config currently exports hardcoded English `name` and `title` strings. Two approaches (choose one): **Option A (Recommended): Add translation keys to MenuItem, translate in Sidebar** - Add a `translationKey` field to `MenuItem` and `NavigationSection` - Keep `name` as the English fallback / stable identifier - In Sidebar.tsx, use `t(item.translationKey) || item.name` for display - Avoids making menuConfig.ts depend on React hooks **Option B: Translate at config level** - Create a `useTranslatedMenuConfig()` hook that returns translated copies - More encapsulated but requires all consumers to be React components #### FR2: Sidebar.tsx — Translated labels | String | Key | |--------|-----| | "Go to dashboard" (aria-label) | `sidebar.goToDashboard` | | "AlgaPSA" (logo text) | Keep hardcoded (brand name) | | "AlgaPSA Logo" (alt text) | `sidebar.logoAlt` | | "Back to Main" (button + tooltip, 2 occurrences) | Already exists: `sidebar.backToMain` | | "Expand sidebar" | `sidebar.expandSidebar` | | "Collapse sidebar" | `sidebar.collapseSidebar` | Sidebar already renders `item.name` from menuConfig — once FR1 is done, these will be translated. #### FR3: Header.tsx — Quick Create menu | String | Key | |--------|-----| | "Open quick create" (aria-label) | `header.quickCreate.ariaLabel` | | "Quick Create" (button text) | `header.quickCreate.title` | | "Create" (dropdown header) | `header.quickCreate.heading` | | 7x option labels (Ticket, Client, Contact, Project, Asset, Service, Product) | `header.quickCreate.options..label` | | 7x option descriptions | `header.quickCreate.options..description` | #### FR4: Header.tsx — Job Activity Indicator | String | Key | |--------|-----| | "View background job activity" (aria-label) | `header.jobs.ariaLabel` | | "Background Jobs" | `header.jobs.title` | | "Track imports, automation runs, and scheduled work." | `header.jobs.description` | | "Active jobs" | `header.jobs.active` | | "Queued jobs" | `header.jobs.queued` | | "Failed last 24h" | `header.jobs.failedLast24h` | | "Open Job Center" | `header.jobs.openJobCenter` | #### FR5: Header.tsx — Breadcrumb and User Menu | String | Key | |--------|-----| | "Home" (breadcrumb root) | `header.breadcrumb.home` | | "Dashboard" (breadcrumb fallback) | `header.breadcrumb.dashboard` | | `Active tenant ${tenant}` (aria-label) | `header.tenantBadge.ariaLabel` | | "Open user menu" (aria-label) | Already covered by existing header keys | | "User" (fallback name) | Already exists: `header.userFallback` | | "Quick access to profile & account." | Already exists: `header.quickAccess` | | "Profile" | Already exists: `header.profile` | | "Account" | Already exists: `header.account` | | "Sign out" | Already exists: `header.signOut` | #### FR6: DefaultLayout.tsx — AI Chat Confirmation Dialogs | String | Key | |--------|-----| | "Leave page and cancel AI response?" | `dialogs.aiInterrupt.navigate.title` | | "An AI response or tool action is still in progress. Leaving this page now will cancel it." | `dialogs.aiInterrupt.navigate.message` | | "Leave page" | `dialogs.aiInterrupt.navigate.confirm` | | "Stay on page" | `dialogs.aiInterrupt.navigate.cancel` | | "Close chat and cancel AI response?" | `dialogs.aiInterrupt.closeChat.title` | | "An AI response or tool action is still in progress. Closing the chat now will cancel it." | `dialogs.aiInterrupt.closeChat.message` | | "Close chat" | `dialogs.aiInterrupt.closeChat.confirm` | | "Keep chat open" | `dialogs.aiInterrupt.closeChat.cancel` | #### FR7: TrialBanner.tsx — Trial status messages | String | Key | |--------|-----| | "Premium confirmed — starts next billing cycle" | `banners.trial.premiumConfirmed` | | "1 day left" | `banners.trial.dayLeft` | | "{{count}} days left" | `banners.trial.daysLeft` | | "Premium Trial: {{daysLabel}} — confirm to keep" | `banners.trial.premiumTrial` | | "{{tier}} Trial: {{daysLabel}}" | `banners.trial.stripeTrial` | #### FR8: PaymentFailedBanner.tsx | String | Key | |--------|-----| | "Payment failed — Update payment method" | `banners.paymentFailed.message` | | "Failed to open billing portal" | `banners.paymentFailed.portalError` | #### FR9: QuickCreateDialog.tsx — Success messages and dialog titles | String | Key | |--------|-----| | "Asset created successfully" | `quickCreate.success.asset` | | `Ticket #{{number}} created successfully` | `quickCreate.success.ticket` | | `Client "{{name}}" created successfully` | `quickCreate.success.client` | | `{{name}} added successfully` | `quickCreate.success.contact` | | `Project "{{name}}" created successfully` | `quickCreate.success.project` | | "Service created successfully" | `quickCreate.success.service` | | "Product created successfully" | `quickCreate.success.product` | | "Failed to load clients" | `quickCreate.errors.loadClients` | | "Failed to load service types" | `quickCreate.errors.loadServiceTypes` | | "Add New Contact" (dialog title) | `quickCreate.dialogTitles.contact` | | "Add New Project" (dialog title) | `quickCreate.dialogTitles.project` | | "Add New Service" (dialog title) | `quickCreate.dialogTitles.service` | #### FR10: RightSidebar.tsx — CE fallback text | String | Key | |--------|-----| | "Chat" | `rightSidebar.title` | | "The chat feature is only available in the Enterprise Edition." | `rightSidebar.enterpriseOnly` | #### FR11: PlatformNotificationBanner.tsx | String | Key | |--------|-----| | "Learn More" | `banners.platformNotification.learnMore` | | "Dismiss notification" (aria-label) | `banners.platformNotification.dismiss` | #### FR12: Billing sidebar navigation The billing navigation sections in menuConfig.ts have section titles and item names that need translation keys: | String | Key | |--------|-----| | Section: "Contracts" | `nav.billing.sections.contracts` | | Section: "Invoicing" | `nav.billing.sections.invoicing` | | Section: "Pricing" | `nav.billing.sections.pricing` | | Section: "Tracking & Reports" | `nav.billing.sections.trackingReports` | | Items: "Contract Templates", "Client Contracts", "Contract Line Presets", "Invoicing", "Invoice Templates", "Billing Cycles", "Service Catalog", "Products", "Tax Rates", "Usage Tracking", "Reports", "Accounting Exports" | `nav.billing.` | #### FR13: Settings/Extensions sidebar items | String | Key | |--------|-----| | "Language" (settings nav, only shown when i18n flag is on) | `settings.tabs.language` | | "SLA" (settings nav) | `settings.tabs.sla` | | "Settings" (extensions sidebar label) | Already exists: `sidebar.settings` | #### FR14: DashboardContainer.tsx — Welcome banners and feature cards **Welcome banners (namespace: `msp/dashboard`):** | String | Key | |--------|-----| | "Welcome to Your MSP Command Center" | `welcome.title` | | "Track onboarding progress, configure critical services..." | `welcome.description` | | "Welcome back" | `welcome.titleCommunity` | | "Jump into tickets, scheduling, projects, and reporting..." | `welcome.descriptionCommunity` | **Feature cards:** | String | Key | |--------|-----| | "Platform Features" | `features.heading` | | "Ticket Management" | `features.tickets.title` | | "Streamline support with routing, SLA tracking..." | `features.tickets.description` | | "System Monitoring" | `features.monitoring.title` | | "Watch critical signals across clients..." | `features.monitoring.description` | | "Security Management" | `features.security.title` | | "Manage policies, approvals, and audit responses..." | `features.security.description` | | "Project Management" | `features.projects.title` | | "Organize delivery plans, tasks, and milestones..." | `features.projects.description` | | "Reporting & Analytics" | `features.reports.title` | | "Build rollups on utilization, SLA attainment..." | `features.reports.description` | | "Schedule Management" | `features.schedule.title` | | "Coordinate onsite visits and remote sessions..." | `features.schedule.description` | | "Coming soon!" | `features.comingSoon` | **Knowledge base section:** | String | Key | |--------|-----| | "Need a deeper dive?" | `knowledgeBase.title` | | "Explore deployment runbooks and best practices..." | `knowledgeBase.description` | | "Visit resources" | `knowledgeBase.cta` | #### FR15: DashboardOnboardingSection.tsx — Onboarding progress UI | String | Key | |--------|-----| | "Onboarding complete" | `onboarding.completeTitle` | | "Complete your setup" | `onboarding.incompleteTitle` | | "Complete" (badge) | `onboarding.badges.complete` | | "You're ready to use the full MSP dashboard experience." | `onboarding.completeDescription` | | "Work through each step to unlock the full MSP dashboard experience." | `onboarding.incompleteDescription` | | "PROGRESS" | `onboarding.progress.label` | | "{{completed}} of {{total}} Steps" | `onboarding.progress.steps` | | "Just getting started!" | `onboarding.progress.messageStart` | | "All set - great job!" | `onboarding.progress.messageComplete` | | "Keep going - you've got this!" | `onboarding.progress.messageInProgress` | | "STEP {{index}}" | `onboarding.stepLabel` | | "NOT STARTED" (badge) | `onboarding.badges.notStarted` | | "IN PROGRESS" (badge) | `onboarding.badges.inProgress` | | "BLOCKED" (badge) | `onboarding.badges.blocked` | | "Complete your first import OR create 5 contacts" | `onboarding.substeps.dataImport` | | "Completed" (CTA when done) | `onboarding.cta.completed` | | "Hiding..." | `onboarding.cta.hiding` | | "Hide" | `onboarding.cta.hide` | | "Dismiss {{title}}" (aria-label) | `onboarding.cta.dismiss` | | "Hidden setup cards ({{count}})" | `onboarding.hidden.title` | | "Restore any card if you need it later." | `onboarding.hidden.subtitle` | | "Restoring..." | `onboarding.cta.restoring` | #### FR16: OnboardingChecklist.tsx — Alternative checklist view | String | Key | |--------|-----| | "Onboarding checklist" | `onboarding.checklist.title` | | "{{completed}} of {{total}} tasks complete" | `onboarding.checklist.progress` | | "Configuration complete" | `onboarding.checklist.completeTitle` | | "Invite clients to experience your branded portal." | `onboarding.checklist.completeDescription` | | "Invite clients" | `onboarding.checklist.inviteCta` | | "View onboarding checklist" | `onboarding.checklist.viewButton` | | "Complete" (status) | Reuse: `onboarding.badges.complete` | | "In Progress" (status) | Reuse: `onboarding.badges.inProgress` | | "Not Started" (status) | Reuse: `onboarding.badges.notStarted` | | "Blocked" (status) | Reuse: `onboarding.badges.blocked` | | "Create your first 5 contacts" | `onboarding.substeps.createContacts` | #### FR17: stepDefinitions.ts — Onboarding step content | String | Key | |--------|-----| | "Secure Identity & SSO" | `onboarding.steps.identity.title` | | "Connect Google Workspace or Microsoft 365 so admins sign in with managed identities." | `onboarding.steps.identity.description` | | "Connect SSO" | `onboarding.steps.identity.cta` | | "Set Up Customer Portal" | `onboarding.steps.portal.title` | | "Configure your portal so customers can sign in on your domain with your branding." | `onboarding.steps.portal.description` | | "Open Portal Settings" | `onboarding.steps.portal.cta` | | "Import Core Data" | `onboarding.steps.dataImport.title` | | "Add contacts so you can start working for clients and keep workflows moving." | `onboarding.steps.dataImport.description` | | "Create Contacts" | `onboarding.steps.dataImport.cta` | | "Calendar Sync" | `onboarding.steps.calendar.title` | | "Connect Google or Outlook calendars to keep dispatch and client appointments aligned." | `onboarding.steps.calendar.description` | | "Configure Calendar" | `onboarding.steps.calendar.cta` | | "Configure Email" | `onboarding.steps.email.title` | | "Set up inbound ticket email and verify an outbound sending domain for reliable delivery." | `onboarding.steps.email.description` | | "Configure Email" | `onboarding.steps.email.cta` | #### FR18: ROUTE_NAMESPACES update Add `msp/dashboard` to the route namespace configuration so it loads on dashboard routes: ```typescript '/msp': ['common', 'msp/core', 'msp/dashboard'], '/msp/dashboard': ['common', 'msp/core', 'msp/dashboard'], ``` ### Non-functional Requirements - All new keys follow the established naming convention (see translation-guide.md) - No performance regression — `msp/core` already loads on every MSP route; `msp/dashboard` loads only on dashboard route - Feature flag `msp-i18n-enabled` OFF = forced English (no change in behavior) ## Data / API / Integrations - No database changes - No API changes - Translation files: `server/public/locales/{lang}/msp/core.json` and `server/public/locales/{lang}/msp/dashboard.json` for all 9 locales ## Security / Permissions No changes — translations are static JSON files served from `/public/locales/`. ## Rollout / Migration - Behind existing `msp-i18n-enabled` feature flag - Flag OFF: I18nWrapper forces English locale → `t()` returns English values → identical to current hardcoded strings - Flag ON: User sees their preferred language - No migration needed — purely additive change to translation files + component wiring ## Open Questions 1. **menuConfig.ts approach**: Option A (translationKey field) vs Option B (hook)? Recommendation: Option A for simplicity — menuConfig stays a plain data file. 2. **Breadcrumb translation**: `getMenuItemNameByPath()` currently returns `item.name` (English). Should it return the translation key instead, letting the breadcrumb component translate? Or should breadcrumbs show the translated nav label? 3. **GitHubStarButton**: The "Star" text and aria-label are part of the GitHub buttons widget — not our text. Skip translation? (Recommendation: yes, skip.) 4. **Onboarding components location**: Dashboard onboarding components live in `packages/onboarding/`, not `server/src/`. They'll need to import `useTranslation` from `@alga-psa/ui/lib/i18n/client` — verify this works from the package context. 5. **stepDefinitions.ts**: This file exports plain data (not a React component). Should it export translation keys (like menuConfig Option A) and let the rendering component call `t()`? (Recommendation: yes, same pattern as menuConfig.) ## Acceptance Criteria (Definition of Done) ### msp/core (shell) - [ ] All ~80 new keys added to `en/msp/core.json` - [ ] All 9 shell component files + menuConfig.ts wired to use `useTranslation('msp/core')` - [ ] With `msp-i18n-enabled` ON + locale set to `xx`: sidebar, header, breadcrumbs, banners, quick-create all show `11111` ### msp/dashboard - [ ] New `msp/dashboard.json` namespace created with ~110 keys - [ ] All 4 dashboard component files + stepDefinitions.ts wired to use `useTranslation('msp/dashboard')` - [ ] `ROUTE_NAMESPACES` updated for `/msp` and `/msp/dashboard` to include `msp/dashboard` - [ ] With `msp-i18n-enabled` ON + locale set to `xx`: welcome banner, feature cards, onboarding checklist all show `11111` ### Cross-cutting - [ ] All 7 production locale files updated with translations for both namespaces (fr, es, de, nl, it, pl) - [ ] Both pseudo-locale files updated for both namespaces (xx, yy) - [ ] With `msp-i18n-enabled` OFF: all text displays in English (no regressions) - [ ] With `msp-i18n-enabled` ON + locale set to `pl`: shell and dashboard show Polish text - [ ] German translations don't overflow sidebar tooltips, header dropdowns, banner badges, or dashboard cards - [ ] Italian translations pass accent audit (no missing `à`, `è`, `ù`, `ò`) - [ ] Cross-check: nav item names in `msp/core.json` match across all languages - [ ] No TypeScript errors, build passes