LocaleContext.tsx
2.28 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
// 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<LocaleState | null>(null)
export function LocaleProvider({ children }: { children: ReactNode }) {
const [locale, setLocaleState] = useState<LocaleCode>(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 (
<LocaleContext.Provider value={{ locale, setLocale, t }}>
{children}
</LocaleContext.Provider>
)
}
export function useT() {
const ctx = useContext(LocaleContext)
if (!ctx) throw new Error('useT must be used inside <LocaleProvider>')
return ctx.t
}
export function useLocale() {
const ctx = useContext(LocaleContext)
if (!ctx) throw new Error('useLocale must be used inside <LocaleProvider>')
return ctx
}
export const AVAILABLE_LOCALES: { code: LocaleCode; label: string }[] = [
{ code: 'en-US', label: 'English' },
{ code: 'zh-CN', label: '中文' },
]