Internationalize the Harness Studio browser UI
Traceability
- Spec ID: studio-i18n
- Status: Implemented
Intent
Harness Studio (packages/harness-studio) is the React browser front end of Better Harness. Today all of its UI copy — headings, buttons, empty states, aria-labels, placeholders, status copy, and date/number formatting — is hard-coded English spread across 29 src/app/**/*.tsx components (roughly 300+ strings) plus a few shell-model helpers. There is no i18n library, no resource layer, and no language switch.
Add first-class internationalization with react-i18next: English remains the default (fallback) language, Simplified Chinese becomes the first translated language (the project already ships README.zh-CN.md), and the language is auto-detected from navigator.language, switchable from the Studio chrome, and persisted in localStorage — matching the existing theme toggle pattern (harness-studio-theme).
The translation payload is bundled by the existing esbuild-wasm toolchain (there is no CDN or runtime resource loading in this repo), so resources are TypeScript modules under src/app/i18n/ organized into i18next namespaces, one per Studio domain, so parallel workstreams can edit disjoint files.
Acceptance Scenarios
- AC-1: With no stored preference and a Chinese
navigator.language(zh*), Studio boots in Simplified Chinese; any other language boots in English. The storedharness-studio-languagepreference wins over auto-detection. - AC-2: A visible language control in the Studio context bar (next to the theme toggle) switches the whole UI between English and Simplified Chinese in place without a reload; the choice is persisted and survives reloads.
- AC-3: Every user-visible string previously hard-coded in
src/app/**/*.tsxand in thestudio-shell-modelcopy is resolved through i18next. Machine data (session titles, commit messages, report payloads, file paths, protocol names such as AG-UI, and server error messages) renders verbatim and is never translated. - AC-4: Dates, times, and numbers rendered with
Intlfollow the active Studio language (e.g. the Date calendar, commit timestamps, token counts), not the browser locale. - AC-5: Plural-sensitive copy uses an unsuffixed singular/default key plus i18next's
_otherkey. English distinguishes the two values;zh-CNkeeps the same wording where the language does not require a plural distinction. - AC-6:
enandzh-CNresources stay structurally identical: a vitest check fails on missing keys, extra keys, or missing interpolation variables (<code v-pre>{{var}}</code>). - AC-7: Existing behavior tests keep passing: unit tests that assert current English copy pass English resources through the model helpers, and the Playwright suite (default English, headless Chromium) is unchanged except for language curriculum produced by the new language switch test.
- AC-8: Keyboard focus, overflow, and console/page errors are re-checked at narrow (390px) and wide widths; the language control follows the existing Studio control styling (shared tokens,
aria-label, focus treatment).
Non-goals
- No translation of server-side CLI output,
scripts/harness-inspector/ui/(the non-React static Inspector report rendered byinspector render), or documentation. Those ship their own surfaces. - No new Studio areas, no lazy-loaded translation chunks, no per-locale number/date frameworks beyond
Intlwith the active language. - No translation key catalog extraction tooling; strings are converted in place with the established keys below.
- No third language, plural-form locales beyond
zh-CN, or RTL support.
Key conventions
- Namespaces mirror Studio domains:
common,overview,workspace,sessions,run,experiment,artifacts,artifactViewers,git,inputs,inspector,customize,compare. Default namespace iscommon; components calluseTranslation("<ns>")and the default namespace is used by App-shell copy. - Resources live in
src/app/i18n/en/<ns>.tsandsrc/app/i18n/zh-CN/<ns>.ts;resources.tstypes the combined map andindex.tsinitializes the i18next instance (detection, persistence,fallbackLng: "en",interpolation.escapeValue: false,react.useSuspense: false). - Only UI copy is translated. Do not translate: data values (session prompts, commit messages, report/story titles, artifact labels), file paths, identifiers, protocol names (AG-UI, ACP), the "Better Harness" / "Studio" / "Harness Inspector" product names, or server-provided error strings (
error instanceof Error ? error.message : ...keeps an English fallback that is translated). - Plural copy uses <code v-pre>{{count}}</code> with an unsuffixed singular/default key and an
_otherkey. Both locales keep the same key tree for parity;zh-CNmay use identical values for both keys. Intlconstruction in components and module helpers uses the active i18n language (i18n.resolvedLanguage ?? i18n.language) instead of a hard-coded"en"or the browser default.- Model helpers (
studioOverview,studioDestinationsinstudio-shell-model.ts) accept atparameter and return translated copy; unit tests initialize a real i18next instance with theenresources and pass itstso existing English assertions keep meaning.
Plan and Tasks
- Dependencies: add
i18nextandreact-i18nexttopackages/harness-studio/package.json. src/app/i18n/:en/<ns>.ts+zh-CN/<ns>.tsper namespace,resources.ts(typed combined resources),index.ts(instance, detection, persistence,changeLanguagehelper).commonnamespace bootstraps App-shell copy so App.tsx conversion is the reference pattern for later workstreams.
src/app/main.tsx: wrapAppin<I18nextProvider>(instance fromi18n/index.ts).src/app/App.tsx: convert shell copy (loading/failure/gate/navigation/ context bar/overview/sessions workspace/compare workspace/empty states), dropAREA_COPY, add the language toggle control next to the theme toggle, and register its style insrc/app/styles/workbench.cssusing existing tokens.- Parallel workstreams (disjoint namespaces and files):
- artifacts:
ArtifactsWorkspace,artifacts/*(incl. pdf/docx/pptx/ xlsx viewers, AgentReact preview, Artifact collaboration, registry, preview host, diagnostics, zoom controls). - git:
GitHistoryView; inputs:InputTraceView; customize:CustomizationView; compare:CompareView,SimpleCompareView; sessions:InspectorWorkbench,run/RunView+experiment/*; code:code/*; run/experiment split per file count.
- artifacts:
test/i18n-resources.test.ts: structural parity betweenenandzh-CN(key sets equal, <code v-pre>{{var}}</code> sets equal per key).- Update
test/studio-shell-model.test.tsto pass an Englishtbuilt from the resources; addtest/browser/language.spec.mjscovering auto-detect, manual switch, and persistence. - Verification:
npm run typecheck,npm test(studio unit),npm run build(studio),npm run test:browserplus visual review of wide/compact/narrow layouts with no console errors.
Test / review evidence
- Unit: i18n resource parity test; translated Artifact collaboration render; shell-model helpers under English
t. - Browser: language switch spec (zh-CN renders, persists, reloads), Customization language switching with server diagnostics left verbatim, and AgentReact language switching; existing English behavior remains covered.
- Build: Studio typecheck/build and 64 test files pass. Focused Playwright coverage passes for language, Customization wide/compact/narrow layouts, and AgentReact behavior plus wide/compact/narrow overflow checks.