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.