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

8.7 KiB

PRD — MSP i18n Infrastructure Sprint (Phase 0)

  • Slug: msp-i18n-infrastructure-sprint
  • Date: 2026-02-19
  • Status: Draft
  • Parent plan: docs/plans/2026-02-18-msp-i18n-full-translation-plan.md
  • Depends on: docs/plans/2026-02-12-msp-i18n-phase1/ (completed)

Summary

Prepare the i18n infrastructure for batch translation of the entire MSP portal. This sprint makes five changes: (a) lazy namespace loading so only route-relevant namespaces are fetched, (b) pseudo-locale generation for visual QA, (c) rename msp.json to msp/core.json, (d) update I18nWrapper to load namespaces by route, and (e) sync the core config package. When complete, any subsequent translation batch can be added as a new namespace file without architectural changes.

Problem

Phase 1 established the MSP i18n foundation (feature flag, I18nWrapper, namespace restructuring, language settings). But the current config eagerly loads ALL namespaces on every page (ns: ['common', 'client-portal', 'msp']). As 12+ MSP feature namespaces are added, this becomes 20+ HTTP requests per page load. Additionally, there's no way to visually verify that all hardcoded strings have been extracted. The msp.json filename doesn't match the planned msp/core.json convention.

Goals

  • G1: Change i18next to only load common on init, with additional namespaces loaded on-demand per route
  • G2: Add ROUTE_NAMESPACES mapping and getNamespacesForRoute() helper to config
  • G3: Update I18nWrapper to use usePathname() and load route-appropriate namespaces
  • G4: Create scripts/generate-pseudo-locale.ts that generates xx and yy pseudo-locale files
  • G5: Add pseudo-locales (xx, yy) to config
  • G6: Rename msp.json to msp/core.json for all 7 languages
  • G7: Update all references from useTranslation('msp') to useTranslation('msp/core')
  • G8: Keep packages/core/src/lib/i18n/config.ts in sync with packages/ui/src/lib/i18n/config.ts
  • G9: Client portal continues working with zero regressions
  • G10: MSP portal with flag OFF continues working with zero regressions

Non-goals

  • Translating any new MSP page content (that's Batch 1+)
  • Creating the actual msp/settings.json or other feature namespace files
  • Server-side namespace preloading or caching
  • CI/CD translation validation pipeline
  • Changing the feature flag behavior

Users and Primary Flows

Persona: Developer adding a translation batch

Flow 1: Adding a new MSP feature namespace

  1. Developer creates server/public/locales/en/msp/<feature>.json
  2. Developer adds route entry to ROUTE_NAMESPACES in config
  3. Namespaces auto-load when users navigate to that route
  4. No changes to I18nWrapper, I18nProvider, or layout files needed

Flow 2: Visual QA with pseudo-locales

  1. Developer runs npx ts-node scripts/generate-pseudo-locale.ts --locale xx --fill 1111
  2. Pseudo-locale files generated for all existing namespaces
  3. Developer enables msp-i18n-enabled flag locally
  4. Developer switches browser to xx locale
  5. All translated strings show 1111; untranslated strings remain in English (= missed extraction)

Persona: End user (MSP portal)

Flow 3: Flag OFF (default) — no change

  • MSP portal stays English-only, identical to today
  • No additional HTTP requests for namespace files

Flow 4: Flag ON — navigating MSP portal

  • Only common + msp/core + route-specific namespaces are loaded
  • No loading of irrelevant namespaces (e.g., msp/settings doesn't load on /msp/tickets)

Persona: End user (Client portal)

Flow 5: Client portal — no change

  • Client portal continues loading common + client-portal + relevant features/* namespaces
  • Zero regressions from namespace renaming or config changes

UX / UI Notes

No user-facing UI changes in this sprint. All changes are developer-facing infrastructure.

Requirements

Functional Requirements

Lazy Namespace Loading

  • FR1: I18N_CONFIG.ns changed from ['common', 'client-portal', 'msp'] to ['common']
  • FR2: ROUTE_NAMESPACES constant exported from packages/ui/src/lib/i18n/config.ts mapping route prefixes to namespace arrays
  • FR3: getNamespacesForRoute(pathname: string): string[] function exported from config — exact match first, then longest prefix match, fallback to ['common']
  • FR4: I18nWrapper reads usePathname() and passes resolved namespaces to I18nProvider
  • FR5: I18nProvider calls i18next.loadNamespaces() for any namespaces not yet loaded when route changes
  • FR6: I18nProvider accepts optional namespaces prop for route-specific namespace list

Pseudo-Locale Generation

  • FR7: scripts/generate-pseudo-locale.ts script exists and is runnable with npx ts-node
  • FR8: Script accepts --locale <code> and --fill <string> CLI arguments
  • FR9: Script reads all English namespace JSON files (including nested features/ and msp/ directories)
  • FR10: Script outputs corresponding files under server/public/locales/<locale>/ preserving directory structure
  • FR11: Script replaces all leaf string values with the fill string
  • FR12: Script preserves {{variables}} within the fill string (e.g., "1111 {{name}} 1111")
  • FR13: Script preserves JSON key structure exactly
  • FR14: Pseudo-locales xx and yy added to LOCALE_CONFIG.supportedLocales

Namespace Rename (msp.json → msp/core.json)

  • FR15: server/public/locales/{lang}/msp.json renamed to server/public/locales/{lang}/msp/core.json for all 7 languages
  • FR16: All useTranslation('msp') references updated to useTranslation('msp/core')
  • FR17: I18N_CONFIG.ns no longer lists 'msp' (it now only lists 'common')
  • FR18: ROUTE_NAMESPACES uses 'msp/core' (not 'msp')

Config Sync

  • FR19: packages/core/src/lib/i18n/config.ts updated to match any locale config changes in packages/ui/src/lib/i18n/config.ts

Backward Compatibility

  • FR20: Client portal loads correct namespaces per route (no regressions)
  • FR21: MSP portal with flag OFF has zero behavior change
  • FR22: MSP portal with flag ON loads msp/core namespace correctly
  • FR23: Existing Phase 1 tests continue passing (with updated namespace name msp/core)

Data / API / Integrations

No database changes. No new API endpoints. Translation files served as static JSON from server/public/locales/.

Key file changes

File Change
packages/ui/src/lib/i18n/config.ts Add ROUTE_NAMESPACES, getNamespacesForRoute(), change ns to ['common'], add pseudo-locales
packages/core/src/lib/i18n/config.ts Sync locale config changes
packages/ui/src/lib/i18n/client.tsx I18nProvider accepts namespaces prop, calls loadNamespaces() on route change
packages/tenancy/src/components/i18n/I18nWrapper.tsx Use usePathname() + getNamespacesForRoute() to pass namespaces to I18nProvider
server/public/locales/{lang}/msp.json Renamed to server/public/locales/{lang}/msp/core.json (7 languages)
scripts/generate-pseudo-locale.ts New file — pseudo-locale generator

Rollout / Migration

  1. Rename msp.jsonmsp/core.json (7 files moved, old files removed)
  2. Update config and wrapper in one PR
  3. Update Phase 1 tests to reference msp/core instead of msp
  4. All behind existing msp-i18n-enabled flag — zero user impact
  5. Pseudo-locale files not committed to git (generated on demand, .gitignore the xx/ and yy/ directories)

Open Questions

  • Should pseudo-locale directories be gitignored or committed? (Recommendation: gitignore — they're generated artifacts)
  • Should ROUTE_NAMESPACES include entries for routes that don't have feature namespaces yet (e.g., /msp/settings before msp/settings.json exists)? (Recommendation: yes, with just ['common', 'msp/core'] — the missing namespace will simply not be fetched until the file exists)

Acceptance Criteria (Definition of Done)

  1. I18N_CONFIG.ns is ['common'] — only common loaded on init
  2. Navigating to /msp/tickets loads common, msp/core, features/tickets (and NOT client-portal, msp/settings, etc.)
  3. Navigating to /client-portal/billing loads common, client-portal, features/billing
  4. scripts/generate-pseudo-locale.ts generates correct pseudo files for all namespaces
  5. Pseudo files preserve {{variables}} and JSON structure
  6. msp/core.json exists for all 7 languages with same content as old msp.json
  7. Old msp.json files are removed
  8. useTranslation('msp/core') returns correct translations
  9. Client portal has zero regressions
  10. MSP portal with flag OFF has zero behavior change
  11. npm run build succeeds
  12. Phase 1 tests pass (updated to msp/core namespace)