Files
Adolfo Delorenzo 1018269f82 docs(07-02): complete frontend i18n infrastructure plan
- Create 07-02-SUMMARY.md with full execution details
- Update STATE.md with position, decisions, metrics
- Update ROADMAP.md progress (Phase 7: 2/4 plans complete)
- Mark requirements I18N-01, I18N-02 complete in REQUIREMENTS.md
2026-03-25 16:30:37 -06:00

160 lines
8.2 KiB
Markdown

---
phase: 07-multilanguage
plan: "02"
subsystem: ui
tags: [next-intl, i18n, react, cookie, auth-jwt]
# Dependency graph
requires:
- phase: 07-multilanguage
provides: Phase 07-01 language system prompt builder for backend
provides:
- next-intl v4 cookie-based i18n infrastructure with no URL routing
- complete en/es/pt message files covering all portal pages and components
- LanguageSwitcher component rendered in sidebar and login page
- Auth.js JWT language field — persists language across sessions
- Browser locale auto-detection on first visit (login page)
- locale cookie synced from DB-authoritative session in SessionSync
affects: [07-03-multilanguage, portal-components]
# Tech tracking
tech-stack:
added: [next-intl@4.8.3, @formatjs/intl-localematcher, negotiator, @types/negotiator]
patterns:
- cookie-based locale detection via getRequestConfig reading konstruct_locale cookie
- i18n/locales.ts separates shared constants from server-only request.ts
- NextIntlClientProvider wraps app in async Server Component root layout
- LanguageSwitcher uses isPreAuth prop to skip DB/JWT update on login page
key-files:
created:
- packages/portal/i18n/request.ts
- packages/portal/i18n/locales.ts
- packages/portal/messages/en.json
- packages/portal/messages/es.json
- packages/portal/messages/pt.json
- packages/portal/components/language-switcher.tsx
modified:
- packages/portal/next.config.ts
- packages/portal/app/layout.tsx
- packages/portal/components/nav.tsx
- packages/portal/components/session-sync.tsx
- packages/portal/lib/auth.ts
- packages/portal/lib/auth-types.ts
- packages/portal/lib/api.ts
- packages/portal/app/(auth)/login/page.tsx
key-decisions:
- "i18n/locales.ts created to hold shared constants (SUPPORTED_LOCALES, LOCALE_COOKIE, isValidLocale) — client components cannot import i18n/request.ts because it imports next/headers (server-only)"
- "LOCALE_COOKIE = 'konstruct_locale' — cookie-based locale with no URL routing avoids App Router [locale] segment pattern entirely"
- "LanguageSwitcher isPreAuth prop — skips DB PATCH and session.update() on login page, sets cookie only"
- "api.patch() added to api client — language switcher uses existing RBAC-header-aware fetch wrapper"
- "SessionSync reconciles locale cookie from session.user.language — ensures DB-authoritative value wins after login"
patterns-established:
- "Server-only i18n: i18n/request.ts imports next/headers; shared constants in i18n/locales.ts for client use"
- "Auth.js JWT language pattern: trigger=update with session.language updates token.language (same as active_tenant_id)"
- "Cookie-first locale: setLocaleCookie + router.refresh() gives instant locale switch without full page reload"
requirements-completed: [I18N-01, I18N-02, I18N-06]
# Metrics
duration: 9min
completed: 2026-03-25
---
# Phase 7 Plan 02: Frontend i18n Infrastructure Summary
**next-intl v4 cookie-based i18n with EN/ES/PT message files, sidebar LanguageSwitcher, and Auth.js JWT language persistence**
## Performance
- **Duration:** 9 min
- **Started:** 2026-03-25T22:00:07Z
- **Completed:** 2026-03-25T22:08:54Z
- **Tasks:** 2
- **Files modified:** 14
## Accomplishments
- next-intl v4.8.3 installed and configured with cookie-based locale detection (no URL routing) — reads `konstruct_locale` cookie in `i18n/request.ts`
- Complete message files created for all portal pages and components in 3 languages: `en.json`, `es.json` (Latin American Spanish), `pt.json` (Brazilian Portuguese)
- LanguageSwitcher component renders compact EN/ES/PT buttons — sets cookie, PATCHes DB, updates Auth.js JWT, calls router.refresh()
- Login page uses `useTranslations('login')` for all form strings and includes pre-auth LanguageSwitcher with browser locale auto-detection on first visit
- Auth.js JWT extended with `language` field following the exact trigger="update" pattern already used for `active_tenant_id`
- SessionSync reconciles locale cookie from session.user.language to ensure DB-authoritative value is applied after login
## Task Commits
Each task was committed atomically:
1. **Task 1: Install next-intl, configure i18n infrastructure, create complete message files** - `e33eac6` (feat)
2. **Task 2: Language switcher component + Auth.js JWT language sync + login page locale detection** - `6be47ae` (feat)
**Plan metadata:** (to be committed with SUMMARY.md)
## Files Created/Modified
- `packages/portal/i18n/locales.ts` - Shared locale constants safe for Client Components (SUPPORTED_LOCALES, LOCALE_COOKIE, isValidLocale)
- `packages/portal/i18n/request.ts` - next-intl server config reading locale from cookie; re-exports from locales.ts
- `packages/portal/messages/en.json` - Complete English translation source (nav, login, dashboard, agents, templates, wizard, onboarding, chat, billing, usage, apiKeys, users, tenants, common, impersonation, tenantSwitcher, validation, language)
- `packages/portal/messages/es.json` - Complete Latin American Spanish translations
- `packages/portal/messages/pt.json` - Complete Brazilian Portuguese translations
- `packages/portal/components/language-switcher.tsx` - EN/ES/PT switcher with isPreAuth prop
- `packages/portal/next.config.ts` - Wrapped with createNextIntlPlugin
- `packages/portal/app/layout.tsx` - Async Server Component with NextIntlClientProvider
- `packages/portal/components/nav.tsx` - Added LanguageSwitcher in user section
- `packages/portal/components/session-sync.tsx` - Added locale cookie sync from session
- `packages/portal/lib/auth.ts` - language field in JWT callback and session callback
- `packages/portal/lib/auth-types.ts` - language added to User, Session, JWT types
- `packages/portal/lib/api.ts` - Added api.patch() method
- `packages/portal/app/(auth)/login/page.tsx` - useTranslations, browser locale detection, LanguageSwitcher
## Decisions Made
- **i18n/locales.ts split from i18n/request.ts**: Client Components importing from `i18n/request.ts` caused a build error because that file imports `next/headers` (server-only). Creating `i18n/locales.ts` for shared constants (SUPPORTED_LOCALES, LOCALE_COOKIE, isValidLocale) resolved this — client components import from `locales.ts`, server config imports from `request.ts`.
- **api.patch() added to api client**: The existing api object had get/post/put/delete but not patch. Added it to keep consistent RBAC-header-aware fetch pattern.
- **Cookie name `konstruct_locale`**: Short, namespaced to avoid conflicts with other cookies on the same domain.
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 3 - Blocking] Separated server-only imports from client-safe constants**
- **Found during:** Task 2 (LanguageSwitcher importing from i18n/request.ts)
- **Issue:** Build error: `i18n/request.ts` imports `next/headers` which is server-only; Client Component `language-switcher.tsx` was importing from it
- **Fix:** Created `i18n/locales.ts` with shared constants only; updated all client imports to use `@/i18n/locales`; `i18n/request.ts` re-exports from locales.ts for server callers
- **Files modified:** i18n/locales.ts (created), i18n/request.ts, components/language-switcher.tsx, components/session-sync.tsx, app/(auth)/login/page.tsx
- **Verification:** Portal builds cleanly with no errors
- **Committed in:** 6be47ae (Task 2 commit)
---
**Total deviations:** 1 auto-fixed (1 blocking build error)
**Impact on plan:** Required split of server-only config from shared constants. No scope creep — this is a standard next-intl + App Router pattern.
## Issues Encountered
None beyond the deviation documented above.
## User Setup Required
None - no external service configuration required.
## Next Phase Readiness
- next-intl infrastructure complete — Plan 03 can begin replacing hardcoded strings with `t()` calls across all portal components
- All three languages have complete message files — no translation gaps to block Plan 03
- Adding a new language requires only a new JSON file in `messages/` and adding the locale to `SUPPORTED_LOCALES` in `i18n/locales.ts`
- LanguageSwitcher is live in sidebar and login page — language preference flows: cookie → DB → JWT → session
## Self-Check: PASSED
All files confirmed present. All commits confirmed in git history.
---
*Phase: 07-multilanguage*
*Completed: 2026-03-25*