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
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
200 lines
18 KiB
Markdown
200 lines
18 KiB
Markdown
# 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/<locale>/` (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).
|