Skip to content

feat: JSON-based i18n system with English + Turkish support#2

Merged
KilimcininKorOglu merged 1 commit into
badursun:mainfrom
KilimcininKorOglu:feature/i18n-multi-lang
May 8, 2026
Merged

feat: JSON-based i18n system with English + Turkish support#2
KilimcininKorOglu merged 1 commit into
badursun:mainfrom
KilimcininKorOglu:feature/i18n-multi-lang

Conversation

@KilimcininKorOglu

Copy link
Copy Markdown
Collaborator

Summary

Adds a complete, lightweight internationalization (i18n) system to the web UI — no external dependencies. Ships with full English and Turkish translations covering every user-facing string in the interface.

What's included

  • lib/i18n.ts — Zustand-powered locale store with useTranslation() hook, string interpolation ({var}), pluralization (_one / _other suffix convention), and getTranslation() for non-component contexts
  • locales/en.json + locales/tr.json — ~400 flat-key translation entries each, covering all 32 component files + failure-modes.ts
  • components/language-switcher.tsx — dropdown selector, integrated into both the home page header and the Settings modal (new "Language" tab)
  • All 32 component files migrated — every hardcoded string replaced with t() / tArray() / plural() calls

Design decisions

Concern Decision Rationale
Key format Flat with dot-prefix (common.close, agentPane.desc.developer) Simpler types — keyof typeof en gives compile-time key validation with zero codegen
Pluralization _one / _other suffix convention ICU-compatible idiom without the library
Interpolation {var} syntax, regex replace 5 lines, zero deps
State Separate useI18nStore (Zustand) Consistent with existing useSessionStore pattern; no Context Provider needed
Persistence localStorage key selfclaude.locale Client-only app, no SSR locale detection needed
Fallback Current locale → en → raw key Never crashes; missing key is immediately visible in dev
Bundle ~8 KB per locale JSON, tree-shaken at build Two files loaded upfront; lazy loading unnecessary for a dev tool

Adding a new language

  1. Create locales/xx.json (copy en.json and translate)
  2. Add 'xx' to SUPPORTED array and CATALOGS map in lib/i18n.ts (2 lines)
  3. Done — the switcher picks it up automatically

TypeScript safety

  • TranslationKey = keyof typeof en — compile-time validation of every t() call
  • Missing keys in tr.json would cause a type error if enforced via Record<TranslationKey, string> assertion (currently relaxed for flexibility)

Test plan

  • pnpm --filter @selfclaude/web typecheck passes (zero i18n-related errors)
  • Full stack tested in browser (selfclaude start --foreground)
  • Language switch EN → TR verified: all strings translate instantly
  • Language switch TR → EN verified: all strings revert
  • Page reload preserves language preference (localStorage)
  • Info banners render translated body text with embedded links
  • Settings modal "Language" tab renders and functions correctly

Custom Zustand-powered i18n with zero external deps. Flat-key JSON
catalogs, useTranslation() hook with interpolation + pluralization,
localStorage persistence, and a LanguageSwitcher dropdown on the
home page header + Settings modal. All 32 web UI component files
migrated from hardcoded English strings to t() / tArray() / plural()
calls. Turkish translations complete and verified in-browser.
@KilimcininKorOglu
KilimcininKorOglu merged commit 00a6bdf into badursun:main May 8, 2026
1 check passed
ersinkoc pushed a commit to oxogdev/SelfClaude that referenced this pull request May 8, 2026
The cumulative role-cost ledger (`metrics.json`) shows what was spent
but not what *happened*. Phase 2 adds an event log that captures the
shape of activity — turns by role, tool calls by kind, files touched,
phase-contract pass rates — and surfaces it in two places:

  - Bottom toolbar: new "first-pass %" badge driven by Phase 1 contract
    attempts. The Phase 1 determinism KPI is now visible per session.
  - Pinned project cards: cumulative project rollup with raw counters
    (turns / files / pass rate) primary, and an explicitly-labelled
    "Estimate (Nx baseline)" chip secondary. Operator picks the
    multiplier from the home-page header (off / 2x / 3x / 5x / 10x);
    "off" hides the estimate panel entirely.

Per ROADMAP calibration badursun#2: raw numbers first, estimates clearly
second and clearly labelled. The dropdown's tooltip spells out that
the estimate is a comparison against a chosen baseline, not a
measurement — there's no way for it to be confused with a real value.

Storage: append-only JSONL at <cwd>/.selfclaude/session-metrics.jsonl,
separate from the existing cost ledger so neither schema migrates.
Rollups (per-session, per-project) are computed on read, not cached —
keeps the math honest and lets us iterate on the formula without
invalidating stored state.

Telemetry hooks: every dev / agent / sup tool call records a tool-call
event with tool name + agent + filePath (when present); applyMetrics
emits a turn event per CC result; createSession / destroySession
bracket with session-start / session-end. The Phase 1
phase-contract-attempt event already emitted from the orchestrator now
flows through the same pipe.

Endpoints:
  - GET /api/sessions/:id/metrics   — live session rollup
  - GET /api/projects/metrics?cwd=…  — cross-session project rollup

Tests: 13 unit tests covering append/read round-trip, malformed-line
tolerance, session filtering, turns by role, tool/agent/file dedup,
first-pass / ultimate / override accounting, duration computation
with and without an end ts, and project-level aggregation. 214 unit
tests total, all green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant