Skip to content

[Tracking] Frontend i18n rollout #61

@an9xyz

Description

@an9xyz

Tracking issue for the frontend i18n rollout in octo-admin, aligned with the backend i18n contract (octo-server/.context/i18n 接入方案.draft.md). UI strings live locally in the frontend; the backend only owns server-produced messages (error envelope, emails, notifications).

Approach

  • Library: i18next + react-i18next + i18next-browser-languagedetector
  • Resources: src/i18n/locales/{en-US,zh-CN}/<namespace>.json
  • Detection order (aligned with backend negotiation priority): ?langi18n_lang cookie → navigator.languageen-US fallback
  • antd ConfigProvider.locale follows i18n.resolvedLanguage
  • axios injects Accept-Language per request (contract §5.1)

Progress

✅ Phase 1 — Foundation + first batch (PR #56, merged)

  • i18n infra (detector chain, antd locale wiring, Accept-Language header)
  • MainLayout + Login migrated (namespaces: nav / layout / login)
  • Glossary alignment (Space → 空间 / Spaces)
  • P2 hardening: resolvedLanguage + cookie sameSite/secure

Phase 2 — Page migrations (mostly done)

All migrated pages route aria-labels through i18n; glossary terms consistent. Only Changelog remains.

⬜ Phase 3 — Lint guard + tooling (PR4)

  • ESLint rule to forbid hardcoded CJK in JSX (backstop against regressions)
  • Land before the next page batch so each migration PR can show "0 hardcoded CJK in touched files"
  • vitest coverage for language switching + (later) error interceptor branches
  • Key type generation (typed t() keys)

⬜ Phase 4 — User language preference API (PR2) — blocked on server

  • GET /v1/user → read language to init i18n
  • PUT /v1/user/language (value or null to clear); use effective_language from response to changeLanguage + write i18n_lang cookie
  • Language switcher in settings
  • Blocked: server endpoint not ready yet.

⬜ Phase 5 — Error envelope v2 adaptation (PR5) — intentionally last

  • axios response interceptor reads error.{code,message,http_status,details}, falls back to msg/status
  • ApiError gains code / httpStatus / details
  • 401 detection via error.code === 'err.shared.auth.required' || error.http_status === 401 (not outer HTTP status — fixed 400 during compat window)
  • err.shared.internal → generic toast, do not expose error.message
  • Local errors.json fallback keyed by err.shared.* / err.server.*
  • Optional X-Octo-Error-Envelope: v2 header (sunset stats only)
  • Deferred to last so it can be verified against the real backend rollout.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions