|
1 | 1 | import { atom, browser, createI18n, defineLocalization, localeFrom } from '@clerk/i18n'; |
2 | 2 | import { useStore } from '@clerk/i18n/react'; |
3 | 3 | import { createContextAndHook } from '@clerk/shared/react'; |
4 | | -import React, { useMemo } from 'react'; |
| 4 | +import React, { useMemo, useRef } from 'react'; |
5 | 5 |
|
6 | 6 | import type { FlatOverrides, I18n, NestedOverrides, Registry, ResolvedOverrides, WritableStore } from '@clerk/i18n'; |
7 | 7 |
|
8 | 8 | interface LocalizationValue { |
9 | 9 | i18n: I18n; |
10 | 10 | $locale: WritableStore<string | null>; |
| 11 | + $overrides: WritableStore<ResolvedOverrides>; |
11 | 12 | } |
12 | 13 |
|
13 | 14 | const [LocalizationCtx, useLocalization] = createContextAndHook<LocalizationValue>('LocalizationContext'); |
@@ -38,16 +39,34 @@ export function LocalizationProvider<R extends Registry = Registry>({ |
38 | 39 | // Seed $explicit so the first render is correct; no useEffect needed. |
39 | 40 | const value = useMemo(() => { |
40 | 41 | const $explicit = atom<string | null>(locale ?? null); |
| 42 | + const $overrides = atom<ResolvedOverrides>( |
| 43 | + overrides ? defineLocalization(overrides as NestedOverrides<Registry>) : {}, |
| 44 | + ); |
41 | 45 | const $locale = localeFrom($explicit, browser({ available: locales, fallback: defaultLocale })); |
42 | 46 | const i18n = createI18n($locale, { |
43 | 47 | get: () => Promise.resolve({}), |
44 | | - overrides: atom<ResolvedOverrides>(overrides ? defineLocalization(overrides as NestedOverrides<Registry>) : {}), |
| 48 | + overrides: $overrides, |
45 | 49 | cache: initialMessages, |
46 | 50 | }); |
47 | | - return { i18n, $locale: $explicit }; |
| 51 | + return { i18n, $locale: $explicit, $overrides }; |
48 | 52 | // eslint-disable-next-line react-hooks/exhaustive-deps |
49 | 53 | }, []); |
50 | 54 |
|
| 55 | + // Sync controlled props → atoms during render (derived-state pattern, no effect needed). |
| 56 | + // locale: primitive comparison against atom's current value is sufficient. |
| 57 | + const nextLocale = locale ?? null; |
| 58 | + if (value.$locale.get() !== nextLocale) { |
| 59 | + value.$locale.set(nextLocale); |
| 60 | + } |
| 61 | + |
| 62 | + // overrides: defineLocalization() returns a new object every call, so compare |
| 63 | + // the prop reference — not the resolved value — to avoid spurious updates. |
| 64 | + const prevOverridesRef = useRef(overrides); |
| 65 | + if (prevOverridesRef.current !== overrides) { |
| 66 | + prevOverridesRef.current = overrides; |
| 67 | + value.$overrides.set(overrides ? defineLocalization(overrides as NestedOverrides<Registry>) : {}); |
| 68 | + } |
| 69 | + |
51 | 70 | return <LocalizationCtx.Provider value={{ value }}>{children}</LocalizationCtx.Provider>; |
52 | 71 | } |
53 | 72 |
|
|
0 commit comments