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
154 lines
8.7 KiB
Markdown
154 lines
8.7 KiB
Markdown
# 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.json` → `msp/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)
|