docs(phase-7): complete Multilanguage phase execution
This commit is contained in:
@@ -141,7 +141,7 @@ Phases execute in numeric order: 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7
|
||||
| 4. RBAC | 3/3 | Complete | 2026-03-24 |
|
||||
| 5. Employee Design | 4/4 | Complete | 2026-03-25 |
|
||||
| 6. Web Chat | 3/3 | Complete | 2026-03-25 |
|
||||
| 7. Multilanguage | 4/4 | Complete | 2026-03-25 |
|
||||
| 7. Multilanguage | 4/4 | Complete | 2026-03-25 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ milestone: v1.0
|
||||
milestone_name: milestone
|
||||
status: completed
|
||||
stopped_at: Completed 07-multilanguage-04-PLAN.md
|
||||
last_updated: "2026-03-25T23:06:30.006Z"
|
||||
last_updated: "2026-03-25T23:12:21.218Z"
|
||||
last_activity: 2026-03-23 — Completed 03-02 onboarding wizard, Slack OAuth, BYO API keys
|
||||
progress:
|
||||
total_phases: 7
|
||||
|
||||
170
.planning/phases/07-multilanguage/07-VERIFICATION.md
Normal file
170
.planning/phases/07-multilanguage/07-VERIFICATION.md
Normal file
@@ -0,0 +1,170 @@
|
||||
---
|
||||
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 20–23), appended before `AI_TRANSPARENCY_CLAUSE` in `build_system_prompt()` (line 74). TS mirror in `system-prompt-builder.ts` (line 18–19, 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 37–278). `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 285–294). `PortalUser` ORM maps `language: Mapped[str]` with `server_default='en'` (line 64–68). |
|
||||
| 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 63–81). |
|
||||
| 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 75–81 |
|
||||
| `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)_
|
||||
Reference in New Issue
Block a user