# SCRATCHPAD — MSP i18n Infrastructure Sprint (Phase 0) ## Key Discoveries ### Current State (Pre-Phase 0) - `I18N_CONFIG.ns` is `['common', 'client-portal', 'msp']` — ALL namespaces loaded eagerly on every page - `msp.json` exists for all 7 languages (en, fr, es, de, nl, it, pl) — ~73 lines, ~60 keys - `msp/` directory does NOT yet exist under any locale folder - **Zero** MSP components currently call `useTranslation('msp')` — the namespace exists but is unused in component code - Phase 1 test file at `server/src/test/unit/i18n/mspI18nPhase1.test.ts` references `'msp'` namespace (lines 280, 284, 285) — needs update to `'msp/core'` - `I18nWrapper` currently does NOT use `usePathname()` — it only resolves locale, not route-specific namespaces - `I18nProvider` does NOT accept a `namespaces` prop — no lazy loading mechanism exists yet - `packages/core/src/lib/i18n/config.ts` is a near-exact copy of `packages/ui/src/lib/i18n/config.ts` (exists to break circular dependency: ui → analytics → tenancy → ui) - http-backend loadPath `/locales/{{lng}}/{{ns}}.json` naturally resolves nested paths (e.g., `msp/core` → `/locales/en/msp/core.json`) — no config change needed for nested namespaces ### Key Infrastructure Already in Place | Component | Path | Status | |-----------|------|--------| | I18nProvider | `packages/ui/src/lib/i18n/client.tsx` | Exists, needs `namespaces` prop | | I18nWrapper | `packages/tenancy/src/components/i18n/I18nWrapper.tsx` | Exists, needs `usePathname()` + namespace passing | | i18n config (ui) | `packages/ui/src/lib/i18n/config.ts` | Exists, needs `ROUTE_NAMESPACES` + `getNamespacesForRoute()` | | i18n config (core) | `packages/core/src/lib/i18n/config.ts` | Exists, needs sync with ui config | | useFormatters() | `packages/ui/src/lib/i18n/client.tsx:214-257` | Exists, locale-aware (uses `useI18n()` context) | | Feature flag | `msp-i18n-enabled` | Exists, gates MSP i18n | | Phase 1 tests | `server/src/test/unit/i18n/mspI18nPhase1.test.ts` | Exists, needs 'msp' → 'msp/core' update | | MSP layout (standard) | `server/src/app/msp/layout.tsx` + `MspLayoutClient.tsx` | Exists, conditionally wraps I18nWrapper | | MSP layout (EE) | `ee/server/src/app/msp/layout.tsx` + `MspLayoutClient.tsx` | Exists, conditionally wraps I18nWrapper | | Translation files | `server/public/locales/{lang}/msp.json` | 7 files, to be renamed to `msp/core.json` | ### useTranslation('msp') References Grep found **zero** references in actual source code — only in documentation/plan files: - `docs/plans/2026-02-19-msp-i18n-infrastructure-sprint/features.json` - `docs/plans/2026-02-19-msp-i18n-infrastructure-sprint/PRD.md` - `docs/plans/2026-02-18-msp-i18n-full-translation-plan.md` - `docs/plans/2026-02-12-msp-i18n-phase1/SCRATCHPAD.md` This means F017 ("Update all useTranslation('msp') references") only affects the Phase 1 test file (lines 280, 284, 285) and the config `ns` array. No component code references `'msp'` namespace directly. ## Decisions | Decision | Rationale | |----------|-----------| | `ns: ['common']` — only common on init | Future-proof; as 12+ MSP namespaces are added, eager loading would be 20+ HTTP requests per page | | Route-based lazy loading via `ROUTE_NAMESPACES` | Deterministic — developer adds route entry, namespaces auto-load. No guessing. | | `getNamespacesForRoute()` uses exact → longest prefix → fallback | Handles both exact routes (`/msp/tickets`) and sub-routes (`/msp/tickets/123`) | | Pseudo-locales `xx`/`yy` available in all environments | Visual QA for extraction completeness; useful in staging/production for verification | | Pseudo-locale files NOT committed to git | They're generated artifacts; add `server/public/locales/xx/` and `yy/` to .gitignore | | `msp.json` → `msp/core.json` rename now | Establishes the `msp/` directory convention before batch 1 adds `msp/settings.json` | | `I18nWrapper` uses `usePathname()` from `next/navigation` | Standard Next.js hook; re-renders on route change, triggering namespace re-resolution | ## Implementation Order Recommended order to minimize breakage: 1. **Rename msp.json → msp/core.json** (F016) — file system change, update test references (F017, F023) 2. **Add ROUTE_NAMESPACES + getNamespacesForRoute()** (F002, F003, F018) — config additions, no behavioral change yet 3. **Change I18N_CONFIG.ns to ['common']** (F001) — behavioral change, client portal will need route-based loading from this point 4. **Update I18nProvider** (F004, F005) — accept `namespaces` prop, add `loadNamespaces()` effect 5. **Update I18nWrapper** (F006, F007, F008) — `usePathname()` + pass namespaces to I18nProvider 6. **Sync core config** (F019) — copy changes to packages/core 7. **Add pseudo-locale support** (F009-F015) — script + config 8. **Verify** (F020-F024) — client portal, MSP flag off, MSP flag on, tests, build ## Key File Paths | Purpose | Path | |---------|------| | i18n config (ui) | `packages/ui/src/lib/i18n/config.ts` | | i18n config (core) | `packages/core/src/lib/i18n/config.ts` | | I18nProvider | `packages/ui/src/lib/i18n/client.tsx` | | I18nWrapper | `packages/tenancy/src/components/i18n/I18nWrapper.tsx` | | Phase 1 tests | `server/src/test/unit/i18n/mspI18nPhase1.test.ts` | | MSP layout (standard) | `server/src/app/msp/layout.tsx` | | MSP layout client (standard) | `server/src/app/msp/MspLayoutClient.tsx` | | MSP layout (EE) | `ee/server/src/app/msp/layout.tsx` | | MSP layout client (EE) | `ee/server/src/app/msp/MspLayoutClient.tsx` | | Translation files | `server/public/locales/{lang}/msp.json` (current) → `server/public/locales/{lang}/msp/core.json` (target) | | Pseudo-locale script | `scripts/generate-pseudo-locale.ts` (new) | | Feature flags | `server/src/lib/feature-flags/featureFlags.ts` | ## Gotchas - **Config duplication**: `packages/ui/src/lib/i18n/config.ts` and `packages/core/src/lib/i18n/config.ts` MUST stay in sync. The core copy exists to break a circular dependency (ui → analytics → tenancy → ui). - **http-backend loadPath**: `/locales/{{lng}}/{{ns}}.json` already resolves nested paths — `msp/core` becomes `/locales/en/msp/core.json`. No backend config change needed. - **Client portal must keep working**: After changing `ns: ['common']`, the client portal relies on `I18nWrapper` → `getNamespacesForRoute()` to load `client-portal` namespace. Test this carefully. - **Build memory**: Phase 1 build needed `NODE_OPTIONS=--max-old-space-size=8192`. Expect the same here. - **Pseudo-locale .gitignore**: Remember to add `server/public/locales/xx/` and `server/public/locales/yy/` to `.gitignore`. - **i18next.loadNamespaces()**: This is an async operation. The `I18nProvider` effect needs to handle the promise and potentially show a loading state while namespaces are being fetched. - **usePathname() requires 'use client'**: `I18nWrapper` is already a client component, so this is fine. - **ROUTE_NAMESPACES entries for future routes**: Include entries for routes that don't have feature namespace files yet (e.g., `/msp/settings` with just `['common', 'msp/core']`). Missing namespace files simply won't be fetched — i18next http-backend handles 404s gracefully. ## Commands ```bash # Run Phase 1 tests (to verify msp/core rename doesn't break them) npx jest server/src/test/unit/i18n/mspI18nPhase1.test.ts # Build with enough memory NODE_OPTIONS=--max-old-space-size=8192 npm run build # Generate pseudo-locale (after script is created) npx ts-node scripts/generate-pseudo-locale.ts --locale xx --fill 1111 npx ts-node scripts/generate-pseudo-locale.ts --locale yy --fill 5555 # Check for remaining 'msp' namespace references (should be zero after rename) grep -r "useTranslation('msp')" --include="*.ts" --include="*.tsx" server/ packages/ ee/ grep -r "'msp'" server/src/test/unit/i18n/ # Verify msp/core.json exists for all languages ls server/public/locales/*/msp/core.json # Verify old msp.json removed ls server/public/locales/*/msp.json # should fail (files removed) ``` ## Updates - 2026-02-20: Updated `I18N_CONFIG.ns` in `packages/ui/src/lib/i18n/config.ts` to `['common']` for lazy namespace loading (F001). - 2026-02-20: Added `ROUTE_NAMESPACES` mapping in `packages/ui/src/lib/i18n/config.ts` for client portal and MSP routes (F002). - 2026-02-20: Added `getNamespacesForRoute()` helper with exact + longest prefix matching in `packages/ui/src/lib/i18n/config.ts` (F003). - 2026-02-20: Added optional `namespaces` prop to `I18nProvider` in `packages/ui/src/lib/i18n/client.tsx` (F004). - 2026-02-20: `I18nProvider` now loads missing namespaces on route changes via `i18next.loadNamespaces()` (F005). - 2026-02-20: `I18nWrapper` now reads the current route via `usePathname()` (F006). - 2026-02-20: `I18nWrapper` now resolves namespaces with `getNamespacesForRoute()` and passes them to `I18nProvider` (F007). - 2026-02-20: `I18nWrapper` memoizes namespace resolution to update on pathname changes (F008). - 2026-02-20: Added `scripts/generate-pseudo-locale.ts` with CLI args parsing for pseudo-locale generation (F009). - 2026-02-20: Pseudo-locale script now walks all English JSON namespaces recursively (including nested `features/` and `msp/`) (F010). - 2026-02-20: Pseudo-locale output preserves directory structure under `server/public/locales//` (F011). - 2026-02-20: Pseudo-locale generator replaces all leaf string values with the fill token (F012). - 2026-02-20: Pseudo-locale generator preserves `{{variables}}` in transformed strings (F013). - 2026-02-20: Pseudo-locale generator preserves JSON key structure (nested objects/arrays) (F014). - 2026-02-20: Added pseudo-locales `xx` and `yy` to `LOCALE_CONFIG.supportedLocales` in UI config (F015). - 2026-02-20: Renamed `server/public/locales/{lang}/msp.json` to `server/public/locales/{lang}/msp/core.json` for all locales (F016). - 2026-02-20: Confirmed no `useTranslation('msp')` references in code; remaining namespace updates handled in Phase 1 tests (F017). - 2026-02-20: ROUTE_NAMESPACES entries use `msp/core` for MSP routes (F018). - 2026-02-20: Synced `packages/core/src/lib/i18n/config.ts` with UI config (namespaces, route mapping, pseudo-locales) (F019). - 2026-02-20: Updated Phase 1 i18n tests to use `msp/core` namespace and file paths (F023). - 2026-02-20: Added tracking items F025/T053 to ensure pseudo-locale outputs are gitignored per PRD rollout guidance. - 2026-02-20: Ignored pseudo-locale outputs in `.gitignore` (`server/public/locales/xx/`, `server/public/locales/yy/`) (F025). - 2026-02-20: Added Phase 0 i18n test suite and made pseudo-locale generator ESM-safe; validated UI `I18N_CONFIG.ns` via tests (T001). - 2026-02-20: Verified core `I18N_CONFIG.ns` via Phase 0 test coverage (T002). - 2026-02-20: Confirmed `ROUTE_NAMESPACES` export via Phase 0 tests (T003). - 2026-02-20: Verified `/client-portal` route namespace mapping in Phase 0 tests (T004). - 2026-02-20: Completed T005 checklist entry — ROUTE_NAMESPACES maps '/client-portal/tickets' to ['common', 'client-portal', 'features/tickets'] - 2026-02-20: Completed T006 checklist entry — ROUTE_NAMESPACES maps '/msp' to ['common', 'msp/core'] (at minimum) - 2026-02-20: Completed T007 checklist entry — ROUTE_NAMESPACES maps '/msp/tickets' to ['common', 'msp/core', 'features/tickets'] - 2026-02-20: Completed T008 checklist entry — ROUTE_NAMESPACES maps '/msp/settings' to ['common', 'msp/core'] - 2026-02-20: Completed T009 checklist entry — All ROUTE_NAMESPACES MSP entries use 'msp/core' (not 'msp') - 2026-02-20: Completed T010 checklist entry — getNamespacesForRoute is exported from packages/ui/src/lib/i18n/config.ts - 2026-02-20: Completed T011 checklist entry — getNamespacesForRoute('/msp/tickets') returns namespaces including 'msp/core' and 'features/tickets' - 2026-02-20: Completed T012 checklist entry — getNamespacesForRoute('/client-portal/billing') returns namespaces including 'client-portal' and 'features/billing' - 2026-02-20: Completed T013 checklist entry — getNamespacesForRoute exact match takes priority over prefix match (e.g., '/msp/tickets' exact entry wins over '/msp' prefix) - 2026-02-20: Completed T014 checklist entry — getNamespacesForRoute uses longest prefix match when no exact match (e.g., '/msp/tickets/123' matches '/msp/tickets' entry) - 2026-02-20: Completed T015 checklist entry — getNamespacesForRoute falls back to ['common'] for unknown routes - 2026-02-20: Completed T016 checklist entry — I18nProvider accepts optional namespaces prop (string[]) - 2026-02-20: Completed T017 checklist entry — I18nProvider calls i18next.loadNamespaces() for namespaces not yet loaded when namespaces prop is provided - 2026-02-20: Completed T018 checklist entry — I18nProvider loads new namespaces when namespaces prop changes (simulating route navigation) - 2026-02-20: Completed T019 checklist entry — I18nProvider does not re-load namespaces that are already loaded - 2026-02-20: Completed T020 checklist entry — I18nWrapper uses usePathname() to read the current route - 2026-02-20: Completed T021 checklist entry — I18nWrapper calls getNamespacesForRoute(pathname) and passes result as namespaces prop to I18nProvider - 2026-02-20: Completed T022 checklist entry — I18nWrapper re-resolves namespaces when pathname changes (route navigation triggers new namespace resolution) - 2026-02-20: Completed T023 checklist entry — scripts/generate-pseudo-locale.ts exists and is runnable - 2026-02-20: Completed T024 checklist entry — Pseudo-locale script accepts --locale and --fill CLI arguments - 2026-02-20: Completed T025 checklist entry — Pseudo-locale script with --locale xx --fill 1111 generates files under server/public/locales/xx/ - 2026-02-20: Completed T026 checklist entry — Pseudo-locale script reads all English namespace JSON files including nested features/ and msp/ directories - 2026-02-20: Completed T027 checklist entry — Pseudo-locale script output preserves directory structure (e.g., features/tickets.json -> xx/features/tickets.json) - 2026-02-20: Completed T028 checklist entry — Pseudo-locale script replaces all leaf string values with the fill string - 2026-02-20: Completed T029 checklist entry — Pseudo-locale script preserves {{variables}} within fill strings (e.g., '1111 {{name}} 1111' for a value containing {{name}}) - 2026-02-20: Completed T030 checklist entry — Pseudo-locale script preserves multiple {{variables}} in a single value (e.g., 'Page {{current}} of {{total}}') - 2026-02-20: Completed T031 checklist entry — Pseudo-locale script preserves JSON key structure exactly (nested objects maintained, no flattening) - 2026-02-20: Completed T032 checklist entry — Pseudo-locale output for common.json has same key structure as en/common.json but all leaf values replaced - 2026-02-20: Completed T033 checklist entry — Pseudo-locales xx and yy are added to LOCALE_CONFIG.supportedLocales - 2026-02-20: Completed T034 checklist entry — Pseudo-locales xx and yy are present in LOCALE_CONFIG.supportedLocales in all environments - 2026-02-20: Completed T035 checklist entry — Pseudo-locales xx and yy are added to I18N_CONFIG.supportedLngs - 2026-02-20: Completed T036 checklist entry — server/public/locales/en/msp/core.json exists with same content as old msp.json - 2026-02-20: Completed T037 checklist entry — server/public/locales/{lang}/msp/core.json exists for all 7 languages (en, fr, es, de, nl, it, pl) - 2026-02-20: Completed T038 checklist entry — Old server/public/locales/{lang}/msp.json files are removed for all 7 languages - 2026-02-20: Completed T039 checklist entry — No useTranslation('msp') references remain in source code (only 'msp/core') - 2026-02-20: Completed T040 checklist entry — Phase 1 test file references updated from 'msp' to 'msp/core' namespace - 2026-02-20: Completed T041 checklist entry — i18next.t('nav.home', { ns: 'msp/core' }) returns correct English translation after namespace rename - 2026-02-20: Completed T042 checklist entry — packages/core/src/lib/i18n/config.ts I18N_CONFIG.ns is synced to ['common'] - 2026-02-20: Completed T043 checklist entry — packages/core/src/lib/i18n/config.ts LOCALE_CONFIG matches packages/ui/src/lib/i18n/config.ts (including any pseudo-locale changes) - 2026-02-20: Completed T044 checklist entry — Client portal at /client-portal loads common and client-portal namespaces correctly - 2026-02-20: Completed T045 checklist entry — Client portal at /client-portal/tickets loads features/tickets namespace in addition to common and client-portal - 2026-02-20: Completed T046 checklist entry — Client portal translations continue displaying correctly after config changes (no regressions) - 2026-02-20: Completed T047 checklist entry — MSP portal with flag OFF: no I18nWrapper rendered, no namespace HTTP requests for msp/core - 2026-02-20: Completed T048 checklist entry — MSP portal with flag OFF: layout renders identically to before changes (zero behavior change) - 2026-02-20: Completed T049 checklist entry — MSP portal with flag ON: msp/core namespace loads correctly via lazy loading - 2026-02-20: Completed T050 checklist entry — MSP portal with flag ON: navigating to /msp/tickets loads features/tickets namespace in addition to msp/core - 2026-02-20: Aligned client-portal and appointments locale keys across languages and updated Phase 1 i18n expectations; Phase 1 vitest now passes (T051). - 2026-02-20: Added pseudo-locale fallback mappings to date-fns locale utilities and confirmed `npm run build` succeeds (T052). - 2026-02-20: Verified pseudo-locale output directories are gitignored (T053). - 2026-02-20: Validated client portal namespace routing via Phase 0 tests (F020). - 2026-02-20: Confirmed MSP flag-off behavior remains unchanged via Phase 1 layout checks (F021). - 2026-02-20: Verified MSP flag-on namespace routing loads `msp/core` and feature namespaces (F022). - 2026-02-20: Build completed successfully with pseudo-locale support included (F024).