@pistonite/pure 0.24.2 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/pref/locale.ts +58 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pistonite/pure",
3
- "version": "0.24.2",
3
+ "version": "0.25.0",
4
4
  "type": "module",
5
5
  "description": "Pure TypeScript libraries for my projects",
6
6
  "homepage": "https://github.com/Pistonite/pure",
@@ -1,7 +1,15 @@
1
1
  import { persist } from "../memory/persist.ts";
2
+ import type { Result } from "../result/index.ts";
3
+ import { serial } from "../sync/serial.ts";
2
4
 
3
5
  let supportedLocales: readonly string[] = [];
4
6
  let defaultLocale: string = "";
7
+ let settingLocale: string = ""; // if locale is being set (setLocale called)
8
+ let onBeforeChangeHook: (
9
+ newLocale: string,
10
+ ) => Promise<Result<void, "cancel">> = () => {
11
+ return Promise.resolve({} as Result<void, "cancel">);
12
+ };
5
13
  const locale = persist<string>({
6
14
  initial: "",
7
15
  key: "Pure.Locale",
@@ -36,11 +44,13 @@ export type LocaleOptions<TLocale extends string> = {
36
44
  * These can be full locale strings like "en-US" or just languages like "en"
37
45
  */
38
46
  supported: readonly TLocale[];
47
+
39
48
  /**
40
49
  * The default locale if the user's preferred locale is not supported.
41
50
  * This must be one of the items in `supported`.
42
51
  */
43
52
  default: TLocale;
53
+
44
54
  /**
45
55
  * Initial value for locale
46
56
  *
@@ -57,6 +67,24 @@ export type LocaleOptions<TLocale extends string> = {
57
67
  * Persist the locale preference to localStorage
58
68
  */
59
69
  persist?: boolean;
70
+
71
+ /**
72
+ * Hook to be called by `setLocale`, but before setting the locale and thus notifying
73
+ * the subscribers.
74
+ *
75
+ * Internally, this is synchronized by the `serial` function, which means
76
+ * if another `setLocale` is called before the hook finishes, the set operation of the current
77
+ * call will not happen and the locale will only be set after the hook finishes in the new call.
78
+ *
79
+ * If there are race conditions in the hook, `checkCancel` should be used after any async operations,
80
+ * which will throw an error if another call happened.
81
+ *
82
+ * Note that this hook will not be called during initialization.
83
+ */
84
+ onBeforeChange?: (
85
+ newLocale: string,
86
+ checkCancel: () => void,
87
+ ) => void | Promise<void>;
60
88
  };
61
89
 
62
90
  /**
@@ -93,6 +121,15 @@ export type LocaleOptions<TLocale extends string> = {
93
121
  export const initLocale = <TLocale extends string>(
94
122
  options: LocaleOptions<TLocale>,
95
123
  ): void => {
124
+ if (options.onBeforeChange) {
125
+ const onBeforeChange = options.onBeforeChange;
126
+ onBeforeChangeHook = serial({
127
+ fn: (checkCancel) => async (newLocale: string) => {
128
+ await onBeforeChange(newLocale, checkCancel);
129
+ },
130
+ });
131
+ }
132
+
96
133
  let _locale = "";
97
134
  supportedLocales = options.supported;
98
135
  if (options.initial) {
@@ -136,14 +173,29 @@ export const getDefaultLocale = (): string => {
136
173
  /**
137
174
  * Set the selected locale
138
175
  *
139
- * Returns `false` if the locale is not supported
176
+ * Returns `false` if the locale is not supported.
177
+ *
178
+ * onBeforeChange hook is called regardless of if the new locale
179
+ * is the same as the current locale. If the hook is asynchronous and
180
+ * another `setLocale` is called before it finishes, the locale will not be set
181
+ * with the current call and will be set with the new call instead.
140
182
  */
141
183
  export const setLocale = (newLocale: string): boolean => {
142
184
  const supported = convertToSupportedLocale(newLocale);
143
185
  if (!supported) {
144
186
  return false;
145
187
  }
146
- locale.set(supported);
188
+ if (supported === settingLocale) {
189
+ return true;
190
+ }
191
+ settingLocale = supported;
192
+ onBeforeChangeHook(supported).then((result) => {
193
+ if (result.err) {
194
+ return;
195
+ }
196
+ settingLocale = "";
197
+ locale.set(supported);
198
+ });
147
199
  return true;
148
200
  };
149
201
 
@@ -202,7 +254,10 @@ export const convertToSupportedLocaleOrDefault = (
202
254
  * Add a subscriber to be notified when the locale changes.
203
255
  * Returns a function to remove the subscriber
204
256
  *
205
- * If `notifyImmediately` is `true`, the subscriber will be called immediately with the current locale
257
+ * If `notifyImmediately` is `true`, the subscriber will be called immediately with the current locale.
258
+ * Note that it's not guaranteed that the new locale is ready when the subscriber is notified.
259
+ * Any async operations such as loading the language files should be done in the
260
+ * `onBeforeChange` hook if the subscribers need to wait for it.
206
261
  */
207
262
  export const addLocaleSubscriber = (
208
263
  fn: (locale: string) => void,