• 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.
    zichun authored
     
    Browse Code »