Files
konstruct/.planning/phases/07-multilanguage/07-VERIFICATION.md

171 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
phase: 07-multilanguage
verified: 2026-03-25T23:30:00Z
status: human_needed
score: 11/11 automated must-haves verified
re_verification: false
human_verification:
- test: "Language switcher changes portal language end-to-end"
expected: "Clicking ES in the sidebar switches all nav labels, page titles, and content to Spanish with no untranslated strings visible"
why_human: "Cannot verify visual rendering, translation quality, or that all 40+ files produce correct output in a browser"
- test: "Language preference persists across sessions"
expected: "After selecting PT, logging out, and logging back in, the portal still displays in Portuguese"
why_human: "Requires real Auth.js JWT round-trip and DB persistence through login flow"
- test: "Login page browser locale detection"
expected: "On first visit with no cookie, a browser configured for Spanish automatically shows the login form in Spanish"
why_human: "Requires a real browser with locale set; cannot simulate navigator.language in static analysis"
- test: "AI Employee responds in the user's language"
expected: "Sending a Spanish message to an agent results in a Spanish reply; sending Portuguese yields Portuguese; English yields English"
why_human: "Requires live LLM inference — cannot verify LANGUAGE_INSTRUCTION produces correct multilingual behavior without a running agent"
- test: "Agent templates display translated content in template gallery"
expected: "When language is set to ES, the template gallery shows Spanish template names and descriptions (e.g., 'Representante de Soporte al Cliente' instead of 'Customer Support Rep')"
why_human: "Requires running portal with real DB data and translations JSONB populated by migration 009"
- test: "Invitation emails sent in correct language"
expected: "When an admin whose language preference is 'es' invites a user, the invitation email arrives with a Spanish subject line and body"
why_human: "Requires SMTP infrastructure and real email delivery to verify; cannot simulate email sending in static analysis"
---
# Phase 7: Multilanguage Verification Report
**Phase Goal:** The entire platform supports English, Spanish, and Portuguese — the portal UI is fully localized with a language switcher, and AI Employees respond in the user's language
**Verified:** 2026-03-25T23:30:00Z
**Status:** human_needed
**Re-verification:** No — initial verification
## Note on Git Structure
`packages/portal` is a **separate nested git repository**. Commits e33eac6, 6be47ae, 20f4c5b, and c499029 claimed in Plan 02 and 03 summaries all exist and are verified in `packages/portal`'s git history. The parent repository sees `packages/portal` as a submodule reference, not individual file commits. All 6 task commits across all 4 plans are real and traceable.
## Goal Achievement
### Observable Truths
| # | Truth | Status | Evidence |
|---|-------|--------|----------|
| 1 | AI Employees respond in the same language the user writes in | VERIFIED | `LANGUAGE_INSTRUCTION` constant present in `system_prompt_builder.py` (line 2023), appended before `AI_TRANSPARENCY_CLAUSE` in `build_system_prompt()` (line 74). TS mirror in `system-prompt-builder.ts` (line 1819, 46). |
| 2 | Agent templates have Spanish and Portuguese translations stored in DB | VERIFIED | Migration `009_multilanguage.py` adds `translations` JSONB column to `agent_templates` with `server_default='{}'` and backfills all 7 seed templates with native-quality es+pt translations (lines 37278). `AgentTemplate` ORM updated (line 256). |
| 3 | Invitation emails are sent in the inviting admin's language | VERIFIED | `email.py` has `_SUPPORTED_LANGUAGES`, `_SUBJECTS`, `_TEXT_BODIES`, `_HTML_BODIES` dicts for en/es/pt. `send_invite_email()` accepts `language` param with fallback to 'en'. Portal API passes `caller.language` when creating invitations. |
| 4 | `portal_users` table has a language column defaulting to 'en' | VERIFIED | Migration adds `language VARCHAR(10) NOT NULL DEFAULT 'en'` (lines 285294). `PortalUser` ORM maps `language: Mapped[str]` with `server_default='en'` (line 6468). |
| 5 | Templates API returns translated fields when locale param is provided | VERIFIED | `list_templates()` and `get_template()` accept `locale: str = Query("en")` (lines 115, 141). `TemplateResponse.from_orm(locale=)` merges translated fields from JSONB at serialization time (lines 6381). |
| 6 | next-intl installed and configured with cookie-based locale | VERIFIED | `next-intl@^4.8.3` in `package.json`. `i18n/request.ts` reads `konstruct_locale` cookie via `getRequestConfig`. `next.config.ts` wrapped with `createNextIntlPlugin`. |
| 7 | NextIntlClientProvider wraps the app in root layout.tsx | VERIFIED | `app/layout.tsx` imports `NextIntlClientProvider` from 'next-intl' (line 3), wraps body children (line 38). `getLocale()` and `getMessages()` called server-side for dynamic locale. |
| 8 | Language switcher is visible in the sidebar near the user avatar | VERIFIED | `components/language-switcher.tsx` is a substantive Client Component rendering EN/ES/PT buttons. `nav.tsx` imports and renders `<LanguageSwitcher />` (lines 25, 126). |
| 9 | Language selection persists via cookie (pre-auth) and DB (post-auth) | VERIFIED | `LanguageSwitcher` sets `konstruct_locale` cookie, PATCHes `/api/portal/users/me/language`, calls `update({ language })` on Auth.js session. `isPreAuth` prop skips DB/session update on login page. `session-sync.tsx` reconciles cookie from `session.user.language` post-login. |
| 10 | Every user-visible string in the portal uses useTranslations() | VERIFIED | All 22 component files and all 22 page files confirmed to contain `useTranslations` or `getTranslations` calls. 26 translation namespaces present in all three message files with zero missing keys (en/es/pt parity confirmed programmatically). |
| 11 | Adding a new language requires only a new JSON file in messages/ | VERIFIED | `SUPPORTED_LOCALES` in `i18n/locales.ts` is the single code change needed. All message files use identical key structures. No language code is hardcoded in components. |
**Score:** 11/11 truths verified (automated)
### Required Artifacts
| Artifact | Provided | Status | Details |
|----------|----------|--------|---------|
| `migrations/versions/009_multilanguage.py` | DB migration: language col + translations JSONB + es/pt seed | VERIFIED | Substantive: 331 lines, full upgrade/downgrade, 7 template translations |
| `packages/shared/shared/prompts/system_prompt_builder.py` | LANGUAGE_INSTRUCTION in system prompts | VERIFIED | `LANGUAGE_INSTRUCTION` constant defined and appended before `AI_TRANSPARENCY_CLAUSE` |
| `packages/portal/lib/system-prompt-builder.ts` | TS mirror with LANGUAGE_INSTRUCTION | VERIFIED | Constant defined at line 18, appended at line 46 |
| `packages/shared/shared/email.py` | Localized invitation emails | VERIFIED | Contains `language` param, three full en/es/pt templates |
| `packages/shared/shared/api/templates.py` | Locale-aware template list endpoint | VERIFIED | `locale` query param on both list and detail endpoints, overlay merge logic present |
| `packages/shared/shared/api/portal.py` | PATCH /users/me/language endpoint + language in AuthVerifyResponse | VERIFIED | Endpoint at line 864; `AuthVerifyResponse` includes `language` at line 50 |
| `packages/portal/i18n/request.ts` | next-intl server config reading locale from cookie | VERIFIED | `getRequestConfig` reads `konstruct_locale` cookie, falls back to 'en' |
| `packages/portal/i18n/locales.ts` | Shared locale constants (client-safe) | VERIFIED | `SUPPORTED_LOCALES`, `LOCALE_COOKIE`, `DEFAULT_LOCALE`, `isValidLocale` |
| `packages/portal/messages/en.json` | English source of truth | VERIFIED | 26 namespaces covering all pages and components |
| `packages/portal/messages/es.json` | Spanish translations | VERIFIED | 26 namespaces, zero missing keys vs. en.json |
| `packages/portal/messages/pt.json` | Portuguese translations | VERIFIED | 26 namespaces, zero missing keys vs. en.json |
| `packages/portal/components/language-switcher.tsx` | EN/ES/PT switcher component | VERIFIED | Substantive: cookie + DB PATCH + JWT update + router.refresh() |
| `packages/portal/lib/auth.ts` | language field in JWT callback | VERIFIED | JWT reads `user.language`, handles `trigger=update` for language, exposes on `session.user.language` |
| `packages/portal/lib/auth-types.ts` | language in session/token types | VERIFIED | `language` present in User, Session, and JWT type declarations |
| `packages/portal/components/session-sync.tsx` | Locale cookie sync from DB-authoritative session | VERIFIED | Reconciles `konstruct_locale` cookie with `session.user.language` post-login |
| `packages/portal/app/(auth)/login/page.tsx` | useTranslations + browser locale detection + LanguageSwitcher | VERIFIED | `useTranslations('login')`, `navigator.language` detection, `<LanguageSwitcher isPreAuth />` |
| `tests/integration/test_language_preference.py` | Integration tests for PATCH language endpoint | VERIFIED | 4 tests: valid patch, invalid locale, persistence, unauthenticated |
| `tests/integration/test_templates_i18n.py` | Integration tests for locale-aware templates | VERIFIED | 5 tests: default locale, Spanish, Portuguese, unsupported locale fallback, overlay check |
### Key Link Verification
| From | To | Via | Status | Details |
|------|----|-----|--------|---------|
| `system_prompt_builder.py` | AI Employee responses | `LANGUAGE_INSTRUCTION` appended in `build_system_prompt()` | WIRED | Line 74: `sections.append(LANGUAGE_INSTRUCTION)` before `AI_TRANSPARENCY_CLAUSE` |
| `templates.py` | `agent_templates.translations` | JSONB column merge on locale query param | WIRED | `TemplateResponse.from_orm(locale=locale)` merges on lines 7581 |
| `app/layout.tsx` | `i18n/request.ts` | `NextIntlClientProvider` reads locale + messages | WIRED | `getLocale()`/`getMessages()` called server-side, passed to provider at line 38 |
| `language-switcher.tsx` | `/api/portal/users/me/language` | PATCH request via `api.patch()` | WIRED | `await api.patch('/api/portal/users/me/language', { language: locale })` at line 49 |
| `nav.tsx` | `language-switcher.tsx` | `LanguageSwitcher` rendered in sidebar | WIRED | Imported at line 25, rendered at line 126 |
| `template-gallery.tsx` | `/api/portal/templates?locale=` | `useTemplates(locale)` passes locale query param | WIRED | `useLocale()` at line 203, `useTemplates(locale)` at line 204; `queries.ts` builds URL with `?locale=` |
| All portal components | `messages/{locale}.json` | `useTranslations()` hook via `NextIntlClientProvider` | WIRED | 48 total `useTranslations`/`getTranslations` usages across components and pages |
### Requirements Coverage
| Requirement | Source Plan(s) | Description | Status | Evidence |
|-------------|---------------|-------------|--------|----------|
| I18N-01 | 07-02, 07-03, 07-04 | Portal UI fully localized in English, Spanish, and Portuguese (all pages, labels, buttons, error messages) | VERIFIED (automated) | All 44+ TSX files use `useTranslations()`. 26 namespaces in en/es/pt with full key parity. |
| I18N-02 | 07-01, 07-02, 07-04 | Language switcher accessible from anywhere — selection persists across sessions | VERIFIED (automated) | `LanguageSwitcher` in nav + login. Cookie + DB PATCH + JWT update chain wired. `session-sync.tsx` reconciles on login. |
| I18N-03 | 07-01, 07-04 | AI Employees detect user language and respond accordingly | VERIFIED (automated), NEEDS HUMAN | `LANGUAGE_INSTRUCTION` present in all system prompts (Python + TS). Live LLM behavior requires human test. |
| I18N-04 | 07-01, 07-03, 07-04 | Agent templates, wizard steps, and onboarding fully translated | VERIFIED (automated) | Templates API serves JSONB translations by locale. All 6 wizard steps and 3 onboarding steps use `useTranslations()`. `template-gallery.tsx` passes `locale` to API. |
| I18N-05 | 07-01, 07-03, 07-04 | Error messages, validation text, and system notifications localized | VERIFIED (automated) | `validation` namespace in all 3 message files. Portal components use `t()` for validation strings. |
| I18N-06 | 07-01, 07-02, 07-04 | Adding a new language requires only translation files, not code changes | VERIFIED (automated) | `SUPPORTED_LOCALES` in `i18n/locales.ts` is the single code change. All message files are standalone JSON. Migration 009 seed data is locale-keyed JSONB. |
**All 6 I18N requirements are accounted for. Zero orphaned requirements.**
### Anti-Patterns Found
No anti-patterns detected in key Phase 07 files. No TODO/FIXME/PLACEHOLDER comments. No stub implementations. No empty handlers. No hardcoded English strings remaining in key UI files (spot-checked nav.tsx, chat-window.tsx, agents/page.tsx — all use `t()` calls).
### Human Verification Required
#### 1. Language Switcher Visual Correctness
**Test:** Start the portal dev environment. Switch to Spanish using the EN/ES/PT buttons in the sidebar. Navigate through Dashboard, Employees, Chat, Billing, and Usage.
**Expected:** All page titles, labels, buttons, table headers, and empty states display in Spanish with no untranslated (English) strings visible.
**Why human:** Automated checks confirm `useTranslations()` calls exist but cannot verify that every key is correctly mapped, that translation quality is natural (not machine-translated), or that no rendering path bypasses the translation layer.
#### 2. Language Persistence Across Sessions
**Test:** Select Portuguese (PT). Log out. Log back in.
**Expected:** The portal loads in Portuguese — the language preference survived the session boundary via DB + JWT.
**Why human:** Requires a live Auth.js token round-trip and database read. Static analysis confirms the wiring is correct but cannot simulate the full login/logout flow.
#### 3. Browser Locale Auto-Detection
**Test:** Clear all cookies. Open the login page in a browser configured for Spanish (`navigator.language = 'es-*'`).
**Expected:** The login form automatically displays in Spanish without requiring manual selection.
**Why human:** Requires a real browser with locale settings. The `useEffect` + `navigator.language` logic exists in the code (line 40 of `login/page.tsx`) but can only be tested in a browser.
#### 4. AI Employee Language Response Behavior
**Test:** Open Chat. Send a message in Spanish: "Hola, necesito ayuda con mi cuenta." Then send one in Portuguese: "Ola, preciso de ajuda com minha conta."
**Expected:** The agent responds in Spanish to the Spanish message and Portuguese to the Portuguese message.
**Why human:** Requires a live LLM inference call. The `LANGUAGE_INSTRUCTION` is wired into system prompts but its effectiveness depends on the LLM's actual behavior, which cannot be verified statically.
#### 5. Translated Template Gallery
**Test:** Switch to Spanish. Go to New Employee > Templates.
**Expected:** Template cards display Spanish names and descriptions (e.g., "Representante de Soporte al Cliente", "Asistente de Ventas") instead of English.
**Why human:** Requires the DB to have migration 009 applied (translating the JSONB data) and a live API call returning the translated fields. Confirms the full stack: DB migration → API overlay → React Query → template gallery render.
#### 6. Localized Invitation Email
**Test:** As an admin with language preference 'es', invite a new user.
**Expected:** The invitation email has a Spanish subject line: "Has sido invitado a unirte a {tenant_name} en Konstruct"
**Why human:** Requires SMTP infrastructure and actual email delivery. The code path (reading `caller.language`, passing to `send_invite_email(language=)`) is wired but cannot be validated without a mail server.
---
## Commit Verification
All commits confirmed present in their respective git repositories:
**Parent repo (`konstruct/`):**
- `7a3a4f0` — feat(07-01): DB migration 009, ORM updates, LANGUAGE_INSTRUCTION
- `9654982` — feat(07-01): localized emails, locale-aware templates API, language preference endpoint
**Portal nested repo (`packages/portal/`):**
- `e33eac6` — feat(07-02): install next-intl, configure i18n infrastructure, create message files
- `6be47ae` — feat(07-02): language switcher, Auth.js JWT language sync, login page locale detection
- `20f4c5b` — feat(07-03): extract i18n strings from portal components
- `c499029` — feat(07-03): extract i18n strings from portal pages
Note: `packages/portal` is a standalone git repository nested inside the monorepo. The parent repo's git log does not show individual portal commits, which is expected.
---
_Verified: 2026-03-25T23:30:00Z_
_Verifier: Claude (gsd-verifier)_