Internationalization (i18n)

This project ships a shared, full-stack localization setup for three locales:

  • en
  • zh-Hans
  • zh-Hant

The product follows the browser language by default, but users can override the locale manually in Settings. That page also exposes the current browser-facing workspace posture (API base, theme, timezone) so operators can verify what context their local session is actually using. The frontend and backend stay aligned so API error messages, active-run surfaces, and formatted dates match the effective UI language.

Design Principles

  • Keep one source of truth for supported locales in locales/manifest.json.
  • Store copy as stable translation keys, not inline UI strings.
  • Prefer whole-sentence translations over string concatenation.
  • Keep user-controlled locale selection above browser negotiation.
  • Use locale-aware formatting (Intl, translated backend errors, Content-Language headers).
  • Treat technical identifiers (provider.type, model IDs, dataset paths) as data, not translatable copy.

Repository Layout

locales/
├── manifest.json
├── en/
│   ├── frontend.json
│   └── backend.json
├── zh-Hans/
│   ├── frontend.json
│   └── backend.json
└── zh-Hant/
    ├── frontend.json
    └── backend.json
  • manifest.json: shared locale registry and native names.
  • frontend.json: all React UI copy rendered through react-i18next.
  • backend.json: API-side messages translated by FastAPI middleware/helpers.

Runtime Behavior

Frontend

  • frontend/src/i18n/index.ts initializes i18next.
  • frontend/src/app/locale-provider.tsx resolves the effective locale and keeps <html lang> in sync.
  • frontend/src/i18n/locale.ts contains locale detection, browser matching, persistence, and manual override helpers.
  • frontend/src/api/client.ts sends X-Eval-Locale on API requests.
  • SSE requests use ?locale= because EventSource cannot send custom headers.

Backend

  • backend/src/eval_752/i18n/__init__.py loads locales/manifest.json, resolves locales, and translates backend messages.
  • LocaleMiddleware applies this precedence:
    1. X-Eval-Locale
    2. locale query parameter
    3. Accept-Language
    4. default locale from manifest.json
  • Responses emit Content-Language so clients can verify which locale was applied.

Adding Or Updating Copy

Frontend copy

  1. Add a stable key to locales/en/frontend.json.
  2. Add the same key to every other frontend.json.
  3. Use useTranslation() in React components or i18n.t(...) in non-React utilities/hooks.
  4. Prefer interpolation:
t("runs.newRunForm.created", { id, status });
  1. Prefer locale-aware formatting for dates/numbers. Reuse existing helpers where possible.

Backend copy

  1. Add a stable key to locales/en/backend.json.
  2. Mirror the key in every other backend.json.
  3. Use translate("path.to.key", value=name) in routes, validators, and backend response builders.
  4. Do not return new user-facing str(exc) messages unless the text is intentionally raw upstream output.

Adding a New Language

  1. Add the locale code and native name to locales/manifest.json.
  2. Create locales/<locale>/frontend.json.
  3. Create locales/<locale>/backend.json.
  4. Copy the English key structure exactly before translating values.
  5. Register the frontend resource import in frontend/src/i18n/resources.ts.
  6. Verify locale detection logic in frontend/src/i18n/locale.ts if the new language needs custom browser mapping rules.
  7. Verify backend locale matching in backend/src/eval_752/i18n/__init__.py if the new language needs custom negotiation rules.
  8. Confirm the language appears in Settings and that browser matching behaves as expected.

Contributor Checklist

When a PR changes user-facing copy:

  • Update all locale catalogs, not just English.
  • Keep translation keys stable; rename keys only when semantics change.
  • Avoid building sentences from fragments across multiple keys.
  • Keep placeholders ({{name}}, {{count}}) consistent across languages.
  • Verify browser-follow mode and manual override mode.
  • Verify at least one backend-translated error or validation path.

Tests And Verification

Run the relevant checks after changing i18n:

cd backend
uv run pytest --cov

cd ../frontend
pnpm typecheck
pnpm test

Automated coverage already includes:

  • locale negotiation tests in backend/tests/app/test_i18n.py
  • locale catalog parity checks in backend/tests/test_locale_catalogs.py
  • frontend locale helper tests in frontend/src/i18n/locale.test.ts
  • API client locale-header coverage in frontend/src/api/client.test.ts

Common Pitfalls

  • Do not add user-facing fallback English directly inside components.
  • Do not localize persisted identifiers or API enum values unless they are presentation-only.
  • Do not concatenate translated fragments around punctuation when one sentence key is possible.
  • Do not add a locale to the manifest without also adding both catalogs and frontend resource registration.

See also: