|
1 | 1 | import {useState, useMemo, useEffect} from 'react';
|
2 | 2 |
|
| 3 | +import {type PickersLocaleText} from '@mui/x-date-pickers'; |
| 4 | + |
3 | 5 | import dateFormat from 'date-fns/format';
|
4 | 6 | import dateFormatDistance from 'date-fns/formatDistance';
|
5 | 7 | import dateFormatDistanceStrict from 'date-fns/formatDistanceStrict';
|
@@ -28,76 +30,169 @@ const localeLoaders: Readonly<Record<string, () => Promise<Locale>>> = {
|
28 | 30 | 'fr-BE': async () => import('date-fns/locale/fr/index.js') as Promise<Locale>,
|
29 | 31 | };
|
30 | 32 |
|
31 |
| -const loadLocale = async (key: string): Promise<Locale | undefined> => |
32 |
| - localeLoaders[key]?.(); |
| 33 | +type LocaleText = Partial<PickersLocaleText<any>>; |
33 | 34 |
|
34 |
| -export const dateMaskMap = { |
35 |
| - 'en-US': '__/__/____', |
36 |
| - 'nl-BE': '__.__.____', |
37 |
| - 'fr-BE': '__/__/____', |
| 35 | +type PickersLocalization = { |
| 36 | + components: { |
| 37 | + MuiLocalizationProvider: { |
| 38 | + defaultProps: { |
| 39 | + localeText: LocaleText; |
| 40 | + }; |
| 41 | + }; |
| 42 | + }; |
38 | 43 | };
|
39 | 44 |
|
40 |
| -export const dateTimeMaskMap = { |
41 |
| - 'en-US': `${dateMaskMap['en-US']} __:__ _M`, |
42 |
| - 'nl-BE': `${dateMaskMap['nl-BE']} __:__`, |
43 |
| - 'fr-BE': `${dateMaskMap['fr-BE']} __:__`, |
| 45 | +const pickersLocalizationLoaders: Readonly< |
| 46 | + Record<string, () => Promise<PickersLocalization>> |
| 47 | +> = { |
| 48 | + async 'nl-BE'() { |
| 49 | + const module = await import('@mui/x-date-pickers/locales/nlNL.js'); |
| 50 | + return module.nlNL; |
| 51 | + }, |
| 52 | + async 'fr-BE'() { |
| 53 | + const module = await import('@mui/x-date-pickers/locales/frFR.js'); |
| 54 | + return module.frFR; |
| 55 | + }, |
44 | 56 | };
|
45 | 57 |
|
| 58 | +const loadLocale = async (key: string): Promise<Locale | undefined> => |
| 59 | + localeLoaders[key]?.(); |
| 60 | + |
| 61 | +const loadPickersLocalization = async ( |
| 62 | + key: string, |
| 63 | +): Promise<PickersLocalization | undefined> => |
| 64 | + pickersLocalizationLoaders[key]?.(); |
| 65 | + |
| 66 | +type Cache<T> = Map<string, T>; |
| 67 | + |
46 | 68 | const localesCache = new Map<string, Locale | undefined>();
|
47 | 69 |
|
48 |
| -const getLocale = async (owner: string): Promise<Locale | undefined> => { |
49 |
| - const key = getSetting(owner, 'lang'); |
50 |
| - if (localesCache.has(key)) { |
51 |
| - return localesCache.get(key); |
| 70 | +const pickersLocalizationsCache = new Map< |
| 71 | + string, |
| 72 | + PickersLocalization | undefined |
| 73 | +>(); |
| 74 | + |
| 75 | +const _load = async <T>( |
| 76 | + kind: string, |
| 77 | + cache: Cache<T>, |
| 78 | + load: (key: string) => Promise<T>, |
| 79 | + key: string, |
| 80 | +): Promise<T | undefined> => { |
| 81 | + if (cache.has(key)) { |
| 82 | + return cache.get(key); |
52 | 83 | }
|
53 | 84 |
|
54 |
| - return loadLocale(key).then( |
55 |
| - (loadedLocale) => { |
56 |
| - localesCache.set(key, loadedLocale); |
57 |
| - return loadedLocale; |
| 85 | + return load(key).then( |
| 86 | + (value) => { |
| 87 | + cache.set(key, value); |
| 88 | + return value; |
58 | 89 | },
|
59 | 90 | (error) => {
|
60 | 91 | const message = error instanceof Error ? error.message : 'unknown error';
|
61 |
| - console.error(`failed to load locale ${key}: ${message}`); |
| 92 | + console.error(`failed to load ${kind} ${key}: ${message}`); |
62 | 93 | console.debug({error});
|
63 | 94 | return undefined;
|
64 | 95 | },
|
65 | 96 | );
|
66 | 97 | };
|
67 | 98 |
|
68 |
| -export const useLocale = () => { |
69 |
| - const key = useLocaleKey(); |
70 |
| - const [lastLoadedLocale, setLastLoadedLocale] = useState<Locale | undefined>( |
| 99 | +const _getLocale = async (key: string): Promise<Locale | undefined> => { |
| 100 | + return _load<Locale | undefined>('locale', localesCache, loadLocale, key); |
| 101 | +}; |
| 102 | + |
| 103 | +const getLocale = async (owner: string): Promise<Locale | undefined> => { |
| 104 | + const key = getSetting(owner, 'lang'); |
| 105 | + return _getLocale(key); |
| 106 | +}; |
| 107 | + |
| 108 | +const _getPickersLocalization = async ( |
| 109 | + key: string, |
| 110 | +): Promise<PickersLocalization | undefined> => { |
| 111 | + return _load<PickersLocalization | undefined>( |
| 112 | + 'pickers localization', |
| 113 | + pickersLocalizationsCache, |
| 114 | + loadPickersLocalization, |
| 115 | + key, |
| 116 | + ); |
| 117 | +}; |
| 118 | + |
| 119 | +const _pickersLocalizationToLocaleText = ( |
| 120 | + localization: PickersLocalization | undefined, |
| 121 | +) => { |
| 122 | + return localization?.components.MuiLocalizationProvider.defaultProps |
| 123 | + .localeText; |
| 124 | +}; |
| 125 | + |
| 126 | +export const getLocaleText = async ( |
| 127 | + owner: string, |
| 128 | +): Promise<LocaleText | undefined> => { |
| 129 | + const key = getSetting(owner, 'lang'); |
| 130 | + const localization = await _getPickersLocalization(key); |
| 131 | + return _pickersLocalizationToLocaleText(localization); |
| 132 | +}; |
| 133 | + |
| 134 | +const useLoadedValue = <T>( |
| 135 | + kind: string, |
| 136 | + cache: Cache<T>, |
| 137 | + load: (key: string) => Promise<T>, |
| 138 | + key: string, |
| 139 | +): T | undefined => { |
| 140 | + const [lastLoadedValue, setLastLoadedValue] = useState<T | undefined>( |
71 | 141 | undefined,
|
72 | 142 | );
|
73 | 143 |
|
74 | 144 | useEffect(() => {
|
75 |
| - if (localesCache.has(key)) { |
76 |
| - setLastLoadedLocale(localesCache.get(key)); |
| 145 | + if (cache.has(key)) { |
| 146 | + setLastLoadedValue(cache.get(key)); |
77 | 147 | return undefined;
|
78 | 148 | }
|
79 | 149 |
|
80 | 150 | let isCancelled = false;
|
81 |
| - loadLocale(key).then( |
82 |
| - (loadedLocale) => { |
83 |
| - localesCache.set(key, loadedLocale); |
| 151 | + load(key).then( |
| 152 | + (value) => { |
| 153 | + cache.set(key, value); |
84 | 154 | if (!isCancelled) {
|
85 |
| - setLastLoadedLocale(loadedLocale); |
| 155 | + setLastLoadedValue(value); |
86 | 156 | }
|
87 | 157 | },
|
88 | 158 | (error) => {
|
89 | 159 | const message =
|
90 | 160 | error instanceof Error ? error.message : 'unknown error';
|
91 |
| - console.error(`failed to load locale ${key}: ${message}`); |
| 161 | + console.error(`failed to load ${kind} ${key}: ${message}`); |
92 | 162 | console.debug({error});
|
93 | 163 | },
|
94 | 164 | );
|
95 | 165 | return () => {
|
96 | 166 | isCancelled = true;
|
97 | 167 | };
|
98 |
| - }, [key, setLastLoadedLocale]); |
| 168 | + }, [key, setLastLoadedValue]); |
| 169 | + |
| 170 | + return cache.has(key) ? cache.get(key) : lastLoadedValue; |
| 171 | +}; |
| 172 | + |
| 173 | +export const useLocale = () => { |
| 174 | + const key = useLocaleKey(); |
| 175 | + return useLoadedValue<Locale | undefined>( |
| 176 | + 'locale', |
| 177 | + localesCache, |
| 178 | + loadLocale, |
| 179 | + key, |
| 180 | + ); |
| 181 | +}; |
| 182 | + |
| 183 | +const usePickersLocalization = (key: string) => { |
| 184 | + return useLoadedValue<PickersLocalization | undefined>( |
| 185 | + 'pickers localization', |
| 186 | + pickersLocalizationsCache, |
| 187 | + loadPickersLocalization, |
| 188 | + key, |
| 189 | + ); |
| 190 | +}; |
99 | 191 |
|
100 |
| - return localesCache.has(key) ? localesCache.get(key) : lastLoadedLocale; |
| 192 | +export const useLocaleText = () => { |
| 193 | + const key = useLocaleKey(); |
| 194 | + const localization = usePickersLocalization(key); |
| 195 | + return _pickersLocalizationToLocaleText(localization); |
101 | 196 | };
|
102 | 197 |
|
103 | 198 | export type WeekStartsOn = WeekDay;
|
@@ -162,16 +257,6 @@ export const useDefaultDateFormatOptions = () => {
|
162 | 257 | );
|
163 | 258 | };
|
164 | 259 |
|
165 |
| -export const useDateMask = () => { |
166 |
| - const key = useLocaleKey(); |
167 |
| - return dateMaskMap[key]; |
168 |
| -}; |
169 |
| - |
170 |
| -export const useDateTimeMask = () => { |
171 |
| - const key = useLocaleKey(); |
172 |
| - return dateTimeMaskMap[key]; |
173 |
| -}; |
174 |
| - |
175 | 260 | const stringifyOptions = (options) => {
|
176 | 261 | if (options === undefined) return undefined;
|
177 | 262 |
|
|
0 commit comments