--- phase: 07-multilanguage plan: "03" subsystem: portal-i18n tags: - i18n - next-intl - portal - components - pages dependency_graph: requires: - 07-01 - 07-02 provides: - fully-translated-portal-ui affects: - packages/portal/components - packages/portal/app - packages/portal/messages tech_stack: added: [] patterns: - next-intl useTranslations() in client components - next-intl getTranslations() in server components - ICU message format with named params - useTemplates(locale) locale-aware API call key_files: created: [] modified: - packages/portal/components/nav.tsx - packages/portal/components/agent-designer.tsx - packages/portal/components/billing-status.tsx - packages/portal/components/budget-alert-badge.tsx - packages/portal/components/chat-message.tsx - packages/portal/components/chat-sidebar.tsx - packages/portal/components/chat-window.tsx - packages/portal/components/employee-wizard.tsx - packages/portal/components/impersonation-banner.tsx - packages/portal/components/message-volume-chart.tsx - packages/portal/components/onboarding-stepper.tsx - packages/portal/components/provider-cost-chart.tsx - packages/portal/components/subscription-card.tsx - packages/portal/components/template-gallery.tsx - packages/portal/components/tenant-form.tsx - packages/portal/components/tenant-switcher.tsx - packages/portal/components/wizard-steps/step-role.tsx - packages/portal/components/wizard-steps/step-persona.tsx - packages/portal/components/wizard-steps/step-tools.tsx - packages/portal/components/wizard-steps/step-channels.tsx - packages/portal/components/wizard-steps/step-escalation.tsx - packages/portal/components/wizard-steps/step-review.tsx - packages/portal/app/(dashboard)/dashboard/page.tsx - packages/portal/app/(dashboard)/agents/page.tsx - packages/portal/app/(dashboard)/agents/[id]/page.tsx - packages/portal/app/(dashboard)/agents/new/page.tsx - packages/portal/app/(dashboard)/agents/new/templates/page.tsx - packages/portal/app/(dashboard)/agents/new/wizard/page.tsx - packages/portal/app/(dashboard)/agents/new/advanced/page.tsx - packages/portal/app/(dashboard)/billing/page.tsx - packages/portal/app/(dashboard)/chat/page.tsx - packages/portal/app/(dashboard)/usage/page.tsx - packages/portal/app/(dashboard)/usage/[tenantId]/page.tsx - packages/portal/app/(dashboard)/settings/api-keys/page.tsx - packages/portal/app/(dashboard)/users/page.tsx - packages/portal/app/(dashboard)/admin/users/page.tsx - packages/portal/app/(dashboard)/tenants/page.tsx - packages/portal/app/(dashboard)/tenants/new/page.tsx - packages/portal/app/(dashboard)/tenants/[id]/page.tsx - packages/portal/app/(dashboard)/onboarding/page.tsx - packages/portal/app/(dashboard)/onboarding/steps/connect-channel.tsx - packages/portal/app/(dashboard)/onboarding/steps/configure-agent.tsx - packages/portal/app/(dashboard)/onboarding/steps/test-message.tsx - packages/portal/app/invite/[token]/page.tsx - packages/portal/lib/queries.ts - packages/portal/messages/en.json - packages/portal/messages/es.json - packages/portal/messages/pt.json decisions: - "onboarding/page.tsx uses getTranslations() not useTranslations() — Server Component requires next-intl/server import" - "TIME_RANGE_OPTIONS moved inside component body — module-level constants cannot access t() hook" - "billing-status.tsx trialEnds key simplified to only {date} param — removed boolean hasDays ICU param that caused TypeScript error" - "WhatsApp credential step instructions stored as plain-text translation keys — avoids dangerouslySetInnerHTML for HTML-marked-up steps" - "useTemplates(locale?) accepts optional locale and passes as ?locale= query param — enables locale-aware template API calls" metrics: duration: ~45min completed: 2026-03-25 tasks_completed: 2 files_modified: 48 --- # Phase 7 Plan 3: Portal i18n String Extraction Summary All 44 portal TSX files now use next-intl `useTranslations()` for every user-visible string — zero hardcoded English in components or pages, with full EN/ES/PT translations for all keys including new namespaces `billingStatus`, `budgetAlert`, `subscriptionCard`, and `invite`. ## Tasks Completed ### Task 1: Extract strings from 22 component files All component files migrated to `useTranslations()`: - **Navigation**: `nav.tsx` — nav labels, sign out - **Tenant management**: `tenant-form.tsx`, `tenant-switcher.tsx` — form labels, switcher UI - **Billing**: `billing-status.tsx`, `budget-alert-badge.tsx`, `subscription-card.tsx` — all subscription states, budget thresholds, plan details - **Templates**: `template-gallery.tsx` — category labels, deploy buttons, preview modal - **Employee wizard**: `employee-wizard.tsx` + all 6 step components (role, persona, tools, channels, escalation, review) - **Onboarding**: `onboarding-stepper.tsx`, `impersonation-banner.tsx` - **Chat**: `chat-sidebar.tsx`, `chat-window.tsx`, `chat-message.tsx`, `message-volume-chart.tsx`, `provider-cost-chart.tsx` - **Agent designer**: `agent-designer.tsx` ### Task 2: Extract strings from 22 page files All page files migrated to `useTranslations()`: - **Core pages**: dashboard, chat, billing, usage (list + detail) - **Agent pages**: agents list, agent detail, new agent picker, templates, wizard, advanced designer - **Settings**: api-keys - **User management**: users, admin/users - **Tenant management**: tenants list, tenant detail, new tenant - **Onboarding**: onboarding page (Server Component with `getTranslations`), plus all 3 step components (connect-channel, configure-agent, test-message) - **Public**: invite accept page ## Deviations from Plan ### Auto-fixed Issues **1. [Rule 1 - Bug] Fixed TypeScript error in billing-status.tsx** - **Found during:** Task 1 - **Issue:** `hasDays: days !== null` passed a boolean to an ICU message parameter typed as `string | number | Date` — TypeScript strict mode rejects this - **Fix:** Removed `hasDays` parameter entirely; simplified `trialEnds` key to `"Trial ends {date}"` using only `{date}` - **Files modified:** `components/billing-status.tsx`, `messages/en.json`, `messages/es.json`, `messages/pt.json` - **Commit:** 20f4c5b **2. [Rule 2 - Missing functionality] Added onboarding step translations not in plan scope** - **Found during:** Task 2 - **Issue:** `onboarding/steps/connect-channel.tsx`, `configure-agent.tsx`, `test-message.tsx` contained hardcoded English; plan listed them in `files_modified` but original task breakdown only mentioned 22 pages without explicitly calling out the step components as separate - **Fix:** Added ~60 new keys to the `onboarding` namespace in all three message files; rewrote all three step components with `useTranslations("onboarding")` - **Files modified:** all 3 step files + 3 message files **3. [Rule 1 - Bug] TIME_RANGE_OPTIONS moved inside component** - **Found during:** Task 2 - **Issue:** `app/(dashboard)/usage/[tenantId]/page.tsx` had `TIME_RANGE_OPTIONS` defined at module level with hardcoded English strings, which cannot access the `t()` hook - **Fix:** Moved array construction inside the component function body - **Files modified:** `app/(dashboard)/usage/[tenantId]/page.tsx` **4. [Rule 2 - Missing functionality] WhatsApp instructions as plain text** - **Found during:** Task 1 (connect-channel.tsx) - **Issue:** Original file used `` HTML inside `
  • ` elements for emphasis in credential instructions; direct translation keys can't hold HTML safely - **Fix:** Stored instructions as plain-text translation keys (no HTML); bold emphasis replaced with readable text - **Files modified:** `components/wizard-steps/step-channels.tsx` was already in scope; `onboarding/steps/connect-channel.tsx` instructions simplified ## Verification TypeScript type check (`npx tsc --noEmit`) passes with zero errors after all changes. ## Self-Check: PASSED Files created/modified confirmed present. Commits verified: - `20f4c5b` — feat(07-03): extract i18n strings from portal components - `c499029` — feat(07-03): extract i18n strings from portal pages