Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
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
commonon init, with additional namespaces loaded on-demand per route - G2: Add
ROUTE_NAMESPACESmapping andgetNamespacesForRoute()helper to config - G3: Update
I18nWrapperto useusePathname()and load route-appropriate namespaces - G4: Create
scripts/generate-pseudo-locale.tsthat generatesxxandyypseudo-locale files - G5: Add pseudo-locales (
xx,yy) to config - G6: Rename
msp.jsontomsp/core.jsonfor all 7 languages - G7: Update all references from
useTranslation('msp')touseTranslation('msp/core') - G8: Keep
packages/core/src/lib/i18n/config.tsin sync withpackages/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.jsonor 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
- Developer creates
server/public/locales/en/msp/<feature>.json - Developer adds route entry to
ROUTE_NAMESPACESin config - Namespaces auto-load when users navigate to that route
- No changes to I18nWrapper, I18nProvider, or layout files needed
Flow 2: Visual QA with pseudo-locales
- Developer runs
npx ts-node scripts/generate-pseudo-locale.ts --locale xx --fill 1111 - Pseudo-locale files generated for all existing namespaces
- Developer enables
msp-i18n-enabledflag locally - Developer switches browser to
xxlocale - 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/settingsdoesn't load on/msp/tickets)
Persona: End user (Client portal)
Flow 5: Client portal — no change
- Client portal continues loading
common+client-portal+ relevantfeatures/*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.nschanged from['common', 'client-portal', 'msp']to['common'] - FR2:
ROUTE_NAMESPACESconstant exported frompackages/ui/src/lib/i18n/config.tsmapping 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:
I18nWrapperreadsusePathname()and passes resolved namespaces toI18nProvider - FR5:
I18nProvidercallsi18next.loadNamespaces()for any namespaces not yet loaded when route changes - FR6:
I18nProvideraccepts optionalnamespacesprop for route-specific namespace list
Pseudo-Locale Generation
- FR7:
scripts/generate-pseudo-locale.tsscript exists and is runnable withnpx ts-node - FR8: Script accepts
--locale <code>and--fill <string>CLI arguments - FR9: Script reads all English namespace JSON files (including nested
features/andmsp/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
xxandyyadded toLOCALE_CONFIG.supportedLocales
Namespace Rename (msp.json → msp/core.json)
- FR15:
server/public/locales/{lang}/msp.jsonrenamed toserver/public/locales/{lang}/msp/core.jsonfor all 7 languages - FR16: All
useTranslation('msp')references updated touseTranslation('msp/core') - FR17:
I18N_CONFIG.nsno longer lists'msp'(it now only lists'common') - FR18:
ROUTE_NAMESPACESuses'msp/core'(not'msp')
Config Sync
- FR19:
packages/core/src/lib/i18n/config.tsupdated to match any locale config changes inpackages/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/corenamespace 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
- Rename
msp.json→msp/core.json(7 files moved, old files removed) - Update config and wrapper in one PR
- Update Phase 1 tests to reference
msp/coreinstead ofmsp - All behind existing
msp-i18n-enabledflag — zero user impact - Pseudo-locale files not committed to git (generated on demand, .gitignore the
xx/andyy/directories)
Open Questions
- Should pseudo-locale directories be gitignored or committed? (Recommendation: gitignore — they're generated artifacts)
- Should
ROUTE_NAMESPACESinclude entries for routes that don't have feature namespaces yet (e.g.,/msp/settingsbeforemsp/settings.jsonexists)? (Recommendation: yes, with just['common', 'msp/core']— the missing namespace will simply not be fetched until the file exists)
Acceptance Criteria (Definition of Done)
I18N_CONFIG.nsis['common']— only common loaded on init- Navigating to
/msp/ticketsloadscommon,msp/core,features/tickets(and NOTclient-portal,msp/settings, etc.) - Navigating to
/client-portal/billingloadscommon,client-portal,features/billing scripts/generate-pseudo-locale.tsgenerates correct pseudo files for all namespaces- Pseudo files preserve
{{variables}}and JSON structure msp/core.jsonexists for all 7 languages with same content as oldmsp.json- Old
msp.jsonfiles are removed useTranslation('msp/core')returns correct translations- Client portal has zero regressions
- MSP portal with flag OFF has zero behavior change
npm run buildsucceeds- Phase 1 tests pass (updated to
msp/corenamespace)