// vibe_erp SPA locale context. // // Provides a `useT()` hook that returns a translation function // `t('key')` bound to the active locale. The active locale is // stored in localStorage and defaults to the browser's language. // // **Why client-side, not server-driven.** The backend already has // ICU4J i18n with per-plug-in locale chains. The SPA's UI chrome // (sidebar, buttons, status labels) is a separate concern — it // runs entirely in the browser and should not require an API call // to render a button label. The two i18n systems share the same // locale code (en-US, zh-CN) so they stay in sync when the user // picks a language. import { createContext, useCallback, useContext, useState, type ReactNode, } from 'react' import { en, locales, type LocaleCode, type MessageKey } from './messages' const STORAGE_KEY = 'vibeerp.locale' function detectLocale(): LocaleCode { const stored = localStorage.getItem(STORAGE_KEY) if (stored && stored in locales) return stored as LocaleCode const browserLang = navigator.language if (browserLang.startsWith('zh')) return 'zh-CN' return 'en-US' } interface LocaleState { locale: LocaleCode setLocale: (code: LocaleCode) => void t: (key: MessageKey) => string } const LocaleContext = createContext(null) export function LocaleProvider({ children }: { children: ReactNode }) { const [locale, setLocaleState] = useState(detectLocale) const setLocale = useCallback((code: LocaleCode) => { localStorage.setItem(STORAGE_KEY, code) setLocaleState(code) }, []) const messages = locales[locale] const t = useCallback( (key: MessageKey): string => messages[key] ?? en[key] ?? key, [messages], ) return ( {children} ) } export function useT() { const ctx = useContext(LocaleContext) if (!ctx) throw new Error('useT must be used inside ') return ctx.t } export function useLocale() { const ctx = useContext(LocaleContext) if (!ctx) throw new Error('useLocale must be used inside ') return ctx } export const AVAILABLE_LOCALES: { code: LocaleCode; label: string }[] = [ { code: 'en-US', label: 'English' }, { code: 'zh-CN', label: '中文' }, ]