-
Adds client-side i18n infrastructure to the SPA (CLAUDE.md guardrail #6: global/i18n from day one). New files: - i18n/messages.ts: flat key-value message bundles for en-US and zh-CN. Keys use dot-notation (nav.*, action.*, status.*, label.*). ~80 keys per locale covering navigation, actions, status badges, and common labels. - i18n/LocaleContext.tsx: LocaleProvider + useT() hook + useLocale() hook. Active locale stored in localStorage, defaults to the browser's navigator.language. Auto-detects zh-* → zh-CN. Wired into the SPA: - main.tsx wraps the app in <LocaleProvider> - AppLayout sidebar uses t(key) for every heading and item - Top bar has a locale dropdown (English / 中文) that switches the entire sidebar + status labels instantly - StatusBadge uses t('status.DRAFT') etc. so statuses render as '草稿' / '已确认' / '已发货' in Chinese The i18n system is intentionally simple: plain strings, no ICU MessageFormat patterns (those live on the backend via ICU4J). A future chunk can adopt @formatjs/intl-messageformat if the SPA needs plural/gender/number formatting client-side. Not yet translated: page-level titles and form labels (they still use hard-coded English). The infrastructure is in place; translating individual pages is incremental.