---
phase: 07-multilanguage
plan: 02
type: execute
wave: 1
depends_on: []
files_modified:
  - packages/portal/package.json
  - packages/portal/next.config.ts
  - packages/portal/i18n/request.ts
  - packages/portal/messages/en.json
  - packages/portal/messages/es.json
  - packages/portal/messages/pt.json
  - packages/portal/app/layout.tsx
  - packages/portal/components/language-switcher.tsx
  - packages/portal/components/nav.tsx
  - packages/portal/lib/auth.ts
  - packages/portal/lib/auth-types.ts
  - packages/portal/components/session-sync.tsx
  - packages/portal/app/(auth)/login/page.tsx
autonomous: true
requirements:
  - I18N-01
  - I18N-02
  - I18N-06

must_haves:
  truths:
    - "next-intl is installed and configured with cookie-based locale (no URL routing)"
    - "NextIntlClientProvider wraps the app in root layout.tsx"
    - "Language switcher is visible in the sidebar near the user avatar"
    - "Language selection persists via cookie (pre-auth) and DB (post-auth)"
    - "Login page detects browser locale and shows a language switcher"
    - "Adding a new language requires only a new JSON file in messages/"
  artifacts:
    - path: "packages/portal/i18n/request.ts"
      provides: "next-intl server config reading locale from cookie"
      contains: "getRequestConfig"
    - path: "packages/portal/messages/en.json"
      provides: "English translation source of truth"
      contains: "nav"
    - path: "packages/portal/messages/es.json"
      provides: "Spanish translations"
      contains: "nav"
    - path: "packages/portal/messages/pt.json"
      provides: "Portuguese translations"
      contains: "nav"
    - path: "packages/portal/components/language-switcher.tsx"
      provides: "Language picker component with EN/ES/PT options"
      contains: "LanguageSwitcher"
  key_links:
    - from: "packages/portal/app/layout.tsx"
      to: "packages/portal/i18n/request.ts"
      via: "NextIntlClientProvider reads locale + messages from server config"
      pattern: "NextIntlClientProvider"
    - from: "packages/portal/components/language-switcher.tsx"
      to: "/api/portal/users/me/language"
      via: "PATCH request to persist language preference"
      pattern: "fetch.*language"
    - from: "packages/portal/components/nav.tsx"
      to: "packages/portal/components/language-switcher.tsx"
      via: "LanguageSwitcher rendered in user section of sidebar"
      pattern: "LanguageSwitcher"
---

<objective>
Frontend i18n infrastructure: install next-intl, create message files with complete translation keys for all portal pages, configure root layout provider, build language switcher, and integrate with Auth.js JWT for language persistence.

Purpose: Establishes the i18n framework so all portal components can use `useTranslations()`. Creates the complete en/es/pt message files with all translation keys for every page and component. This plan does the infrastructure setup AND the translation file authoring, while Plan 03 does the actual string extraction (replacing hardcoded strings with `t()` calls).
Output: Working next-intl setup, complete message files in 3 languages, language switcher in sidebar and login page.
</objective>

<execution_context>
@/home/adelorenzo/.claude/get-shit-done/workflows/execute-plan.md
@/home/adelorenzo/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/07-multilanguage/07-CONTEXT.md
@.planning/phases/07-multilanguage/07-RESEARCH.md

<interfaces>
<!-- Current root layout (Server Component — must remain Server Component) -->
From packages/portal/app/layout.tsx:
```typescript
// Currently: static html lang="en", no i18n provider
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" className="...">
      <body className="min-h-full flex flex-col">{children}</body>
    </html>
  );
}
```

<!-- Nav sidebar (Client Component) — language switcher goes in user section -->
From packages/portal/components/nav.tsx:
```typescript
'use client';
// navItems array has hardcoded labels: "Dashboard", "Employees", "Chat", etc.
// User section at bottom with sign out button
export function Nav() { ... }
```

<!-- Auth.js config — JWT already carries role, active_tenant_id -->
From packages/portal/lib/auth.ts:
```typescript
// JWT callback with trigger="update" pattern used for active_tenant_id
// Same pattern needed for language field
```

<!-- next.config.ts — needs withNextIntl wrapper -->
From packages/portal/next.config.ts:
```typescript
const nextConfig: NextConfig = { output: "standalone" };
export default nextConfig;
// MUST become: export default withNextIntl(nextConfig);
```

<!-- Login page — needs pre-auth language switcher -->
From packages/portal/app/(auth)/login/page.tsx — Client Component with email/password form

<!-- Session sync — syncs RBAC headers, could sync language -->
From packages/portal/components/session-sync.tsx
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Install next-intl, configure i18n infrastructure, create complete message files</name>
  <files>
    packages/portal/package.json
    packages/portal/next.config.ts
    packages/portal/i18n/request.ts
    packages/portal/messages/en.json
    packages/portal/messages/es.json
    packages/portal/messages/pt.json
    packages/portal/app/layout.tsx
  </files>
  <action>
    1. Install dependencies in packages/portal/:
       ```bash
       cd packages/portal && npm install next-intl @formatjs/intl-localematcher negotiator && npm install --save-dev @types/negotiator
       ```

    2. Create i18n/request.ts (next-intl server config WITHOUT URL routing):
       - Define SUPPORTED_LOCALES = ['en', 'es', 'pt'] as const
       - Export type Locale, isValidLocale helper, LOCALE_COOKIE constant ('konstruct_locale')
       - getRequestConfig reads locale from cookie (LOCALE_COOKIE), falls back to 'en'
       - Dynamic import of messages/{locale}.json
       - Follow Pattern 1 from RESEARCH.md exactly

    3. Update next.config.ts:
       - Import createNextIntlPlugin from 'next-intl/plugin'
       - Wrap config: const withNextIntl = createNextIntlPlugin('./i18n/request.ts')
       - export default withNextIntl({ output: 'standalone' })

    4. Create complete messages/en.json with ALL translation keys for every page and component:
       - nav: dashboard, employees, chat, usage, billing, apiKeys, users, platform, signOut
       - login: title, subtitle, emailLabel, passwordLabel, submitButton, invalidCredentials, signingIn
       - dashboard: title, welcome, agentCount, tenantCount, recentActivity, noActivity
       - agents: pageTitle, newEmployee, noAgents, noAgentsDescription, createFirst, active, inactive, editButton, deleteButton, confirmDelete, agent fields (name, role, persona, systemPrompt, modelPreference, tools, escalation)
       - agentDesigner: title, fields (jobDescription, statementOfWork, persona, systemPrompt, toolAssignments, escalationRules), saveButton, addTool, addRule, condition, action
       - agentNew: title, subtitle, options (template, wizard, advanced), descriptions for each
       - templates: title, subtitle, deploy, deploying, deployed, recommended, noTemplates, category labels
       - wizard: steps (role, persona, tools, channels, escalation, review), labels for each field, next, back, deploy, deployingAgent
       - onboarding: title, steps (connectChannel, configureAgent, testMessage), descriptions, buttons, completion message
       - chat: title, newConversation, noConversations, noMessages, typeMessage, send, selectAgent, conversations
       - billing: title, currentPlan, subscribe, manage, cancelSubscription, upgrade, invoiceHistory
       - usage: title, selectTenant, tokenUsage, costBreakdown, timeRange, day, week, month, noBudget
       - apiKeys: title, addKey, provider, keyHint, deleteKey, confirmDelete, noKeys
       - users: title, inviteUser, name, email, role, status, actions, pending, accepted, revokeInvite
       - adminUsers: title, allUsers, platformUsers
       - tenants: title, newTenant, name, slug, plan, actions, editTenant, deleteTenant, confirmDelete, noTenants
       - tenantForm: nameLabel, slugLabel, planLabel, createButton, updateButton, creating, updating
       - common: loading, error, save, cancel, delete, confirm, search, noResults, retry, back, close
       - impersonation: banner text, stopButton
       - tenantSwitcher: selectTenant, allTenants, currentTenant
       - validation: required, invalidEmail, minLength, maxLength, invalidFormat
       - language: switcherLabel, en, es, pt

    5. Create messages/es.json — complete Spanish translation of ALL keys from en.json. Use proper Latin American Spanish business terminology. Not literal translations — natural phrasing. Example: "Employees" -> "Empleados", "Dashboard" -> "Panel", "Sign out" -> "Cerrar sesion", "AI Workforce" -> "Fuerza laboral IA".

    6. Create messages/pt.json — complete Brazilian Portuguese translation of ALL keys from en.json. Use proper Brazilian Portuguese business terminology. Example: "Employees" -> "Funcionarios", "Dashboard" -> "Painel", "Sign out" -> "Sair", "AI Workforce" -> "Forca de trabalho IA".

    7. Update app/layout.tsx:
       - Keep as Server Component (NO 'use client')
       - Import NextIntlClientProvider from 'next-intl'
       - Import getLocale, getMessages from 'next-intl/server'
       - Make function async
       - Call const locale = await getLocale(); const messages = await getMessages();
       - Set html lang={locale} (dynamic, not hardcoded "en")
       - Wrap body children with <NextIntlClientProvider messages={messages}>
       - Keep all existing font and class logic unchanged
  </action>
  <verify>
    <automated>cd /home/adelorenzo/repos/konstruct/packages/portal && npx next build 2>&1 | tail -20</automated>
  </verify>
  <done>
    - next-intl installed and configured
    - i18n/request.ts reads locale from cookie
    - next.config.ts wrapped with withNextIntl
    - Complete en.json, es.json, pt.json message files with keys for every page/component
    - Root layout wraps app with NextIntlClientProvider
    - Portal builds successfully
  </done>
</task>

<task type="auto">
  <name>Task 2: Language switcher component + Auth.js JWT language sync + login page locale detection</name>
  <files>
    packages/portal/components/language-switcher.tsx
    packages/portal/components/nav.tsx
    packages/portal/lib/auth.ts
    packages/portal/lib/auth-types.ts
    packages/portal/components/session-sync.tsx
    packages/portal/app/(auth)/login/page.tsx
  </files>
  <action>
    1. Create components/language-switcher.tsx:
       - 'use client' component
       - Three clickable buttons: EN / ES / PT (compact, inline)
       - Current locale highlighted (derived from cookie or session)
       - On click: (a) set document.cookie with konstruct_locale={locale}, path=/, max-age=31536000; (b) if authenticated, PATCH /api/portal/users/me/language with the locale; (c) call update({ language: locale }) on Auth.js session; (d) router.refresh() to re-render with new locale
       - Style: compact row of 3 buttons, fits in sidebar user section. Use sidebar color tokens. Active locale has subtle highlight.
       - Accept optional `isPreAuth` prop — when true, skip the DB PATCH and session update (for login page)

    2. Add LanguageSwitcher to nav.tsx:
       - Import and render <LanguageSwitcher /> between the user info section and the sign-out button
       - Keep existing nav structure and styling intact

    3. Update Auth.js config in lib/auth.ts:
       - In the JWT callback: read user.language from the verify endpoint response and add to token
       - Handle trigger="update" case for language: if (trigger === "update" && session?.language) token.language = session.language
       - In the session callback: expose token.language on session.user.language
       - Follow the exact same pattern already used for active_tenant_id

    4. Update auth-types.ts if needed to include language in the session/token types.

    5. Update session-sync.tsx:
       - After login, sync the locale cookie from session.user.language (if the cookie differs from the session value, update the cookie so i18n/request.ts reads the DB-authoritative value)

    6. Update login page (app/(auth)/login/page.tsx):
       - On mount (useEffect), detect browser locale via navigator.language.slice(0, 2), check if supported ['en', 'es', 'pt'], set konstruct_locale cookie if no cookie exists yet
       - Add <LanguageSwitcher isPreAuth /> near the form (below the sign-in button or in the page header)
       - Use useTranslations('login') for all login form strings (title, labels, button, error message)
  </action>
  <verify>
    <automated>cd /home/adelorenzo/repos/konstruct/packages/portal && npx next build 2>&1 | tail -20</automated>
  </verify>
  <done>
    - LanguageSwitcher component renders EN/ES/PT buttons with active highlight
    - Sidebar shows language switcher near user avatar
    - Changing language updates cookie + DB + JWT + triggers re-render
    - Login page detects browser locale and shows language switcher
    - Login form strings use useTranslations('login')
    - Portal builds successfully
  </done>
</task>

</tasks>

<verification>
- Portal builds: `cd packages/portal && npx next build`
- next-intl configured: i18n/request.ts exists, next.config.ts uses withNextIntl
- Message files exist: en.json, es.json, pt.json all have matching key structures
- LanguageSwitcher component exists and is rendered in Nav
- Login page uses useTranslations
</verification>

<success_criteria>
- next-intl v4 installed and configured without URL-based routing
- Complete en/es/pt message files covering all pages and components
- Language switcher in sidebar (post-auth) and login page (pre-auth)
- Language preference persists via cookie + DB + JWT
- Browser locale auto-detected on first visit
- Portal builds without errors
</success_criteria>

<output>
After completion, create `.planning/phases/07-multilanguage/07-02-SUMMARY.md`
</output>
