@discord/intl 0.2.0-canary.cad566b → 0.2.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.
package/dist/index.d.ts CHANGED
@@ -1,8 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import { RichTextNode } from './formatters';
3
+ export { IntlManager, createLoader, DEFAULT_LOCALE, type FormatFunction } from './intl-manager';
3
4
  export * from './formatters';
4
- export { IntlManager, DEFAULT_LOCALE, type FormatFunction } from './intl-manager';
5
- export { createLoader, waitForAllDefaultIntlMessagesLoaded } from './message-loader';
6
5
  export type * from './types.d.ts';
7
6
  /**
8
7
  * A type representing any message that has been formatted using one of the
package/dist/index.js CHANGED
@@ -14,11 +14,9 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.waitForAllDefaultIntlMessagesLoaded = exports.createLoader = exports.DEFAULT_LOCALE = exports.IntlManager = void 0;
18
- __exportStar(require("./formatters"), exports);
17
+ exports.DEFAULT_LOCALE = exports.createLoader = exports.IntlManager = void 0;
19
18
  var intl_manager_1 = require("./intl-manager");
20
19
  Object.defineProperty(exports, "IntlManager", { enumerable: true, get: function () { return intl_manager_1.IntlManager; } });
20
+ Object.defineProperty(exports, "createLoader", { enumerable: true, get: function () { return intl_manager_1.createLoader; } });
21
21
  Object.defineProperty(exports, "DEFAULT_LOCALE", { enumerable: true, get: function () { return intl_manager_1.DEFAULT_LOCALE; } });
22
- var message_loader_1 = require("./message-loader");
23
- Object.defineProperty(exports, "createLoader", { enumerable: true, get: function () { return message_loader_1.createLoader; } });
24
- Object.defineProperty(exports, "waitForAllDefaultIntlMessagesLoaded", { enumerable: true, get: function () { return message_loader_1.waitForAllDefaultIntlMessagesLoaded; } });
22
+ __exportStar(require("./formatters"), exports);
@@ -1,4 +1,5 @@
1
1
  import { IntlShape } from '@formatjs/intl';
2
+ import { LocaleId, LocaleImportMap, MessageLoader } from './message-loader';
2
3
  import type { FormatterImplementation, IntlMessageGetter, RequiredFormatValues, TypedIntlMessageGetter } from './types';
3
4
  import { InternalIntlMessage } from './message';
4
5
  import { FormatBuilderConstructor } from './format';
@@ -47,4 +48,10 @@ export declare class IntlManager {
47
48
  string<T extends TypedIntlMessageGetter<undefined>>(message: T): string;
48
49
  bindFormatValues<T>(Builder: FormatBuilderConstructor<T>, message: InternalIntlMessage, values: Record<string, any>): T[];
49
50
  }
51
+ /**
52
+ * Create a new MessageLoader, which handles lazily loading messages for
53
+ * different locales and sanity checks as needed to provide accessors for each
54
+ * message defined in `messageKeys`.
55
+ */
56
+ export declare function createLoader(messageKeys: string[], localeImportMap: LocaleImportMap, defaultLocale: LocaleId): MessageLoader;
50
57
  export {};
@@ -28,8 +28,10 @@ var __read = (this && this.__read) || function (o, n) {
28
28
  };
29
29
  Object.defineProperty(exports, "__esModule", { value: true });
30
30
  exports.IntlManager = exports.DEFAULT_LOCALE = void 0;
31
+ exports.createLoader = createLoader;
31
32
  var intl_1 = require("@formatjs/intl");
32
33
  var intl_messageformat_1 = require("intl-messageformat");
34
+ var message_loader_1 = require("./message-loader");
33
35
  var format_1 = require("./format");
34
36
  /**
35
37
  * Fallback locale used for all internationalization when an operation in the
@@ -120,11 +122,8 @@ var IntlManager = /** @class */ (function () {
120
122
  * immediately return the plain string value of the message in the current locale.
121
123
  */
122
124
  IntlManager.prototype.string = function (message) {
123
- // @ts-expect-error Figure out how to make this typing exact. This
124
- // currently relies on the generic typing being sound enough to know that
125
- // the message can only contain a single static text node and no
126
- // placeholders or rich text.
127
- return message(this.currentLocale).ast[0].value;
125
+ // TODO: Figure out how to make this typing exact.
126
+ return message(this.currentLocale).message;
128
127
  };
129
128
  IntlManager.prototype.bindFormatValues = function (Builder, message, values) {
130
129
  return (0, format_1.bindFormatValues)(Builder, message.ast, [this.currentLocale, this.defaultLocale], this.intl.formatters, this.intl.formats, values);
@@ -132,3 +131,11 @@ var IntlManager = /** @class */ (function () {
132
131
  return IntlManager;
133
132
  }());
134
133
  exports.IntlManager = IntlManager;
134
+ /**
135
+ * Create a new MessageLoader, which handles lazily loading messages for
136
+ * different locales and sanity checks as needed to provide accessors for each
137
+ * message defined in `messageKeys`.
138
+ */
139
+ function createLoader(messageKeys, localeImportMap, defaultLocale) {
140
+ return new message_loader_1.MessageLoader(messageKeys, localeImportMap, defaultLocale);
141
+ }
@@ -28,17 +28,9 @@ export declare class MessageLoader {
28
28
  * then a request has already been made to load the locale and it is waiting
29
29
  * to resolve.
30
30
  */
31
- _localeLoadingPromises: Record<LocaleId, {
32
- /**`true` if this locale has been loaded at least once. */
33
- initialized: boolean;
34
- /**
35
- * If the locale is currently being loaded, this Promise will exist and
36
- * represent that loading status. Otherwise, this value is undefined.
37
- */
38
- current?: Promise<{
39
- default: MessagesData;
40
- }> | undefined;
41
- }>;
31
+ _localeLoadingPromises: Record<LocaleId, Promise<{
32
+ default: MessagesData;
33
+ }> | undefined>;
42
34
  /**
43
35
  * Map of pre-parsed messages, keyed by the message key and locale
44
36
  */
@@ -107,27 +99,5 @@ export declare class MessageLoader {
107
99
  * Subscribe to events about when a locale has loaded.
108
100
  */
109
101
  onChange(callback: () => void): () => void;
110
- isLocaleLoaded(locale: LocaleId, requireCurrent?: boolean): boolean;
111
- waitForLocaleLoaded(locale: LocaleId, requireCurrent?: boolean): Promise<void>;
112
- /**
113
- * Returns true if this loader's default locale has finished loading. When
114
- * true, all messages managed by this loader are guaranteed to have _a_
115
- * render-able value, even if one does not exist in the current locale.
116
- */
117
- waitForDefaultLocale(requireCurrent?: boolean): Promise<void>;
118
102
  }
119
- /**
120
- * Returns a new Promise that resolves after all currently-registered message
121
- * loaders have finished successfully loading their default locale content.
122
- * Once this Promise has resolved, all messages that currently exist in the
123
- * application (i.e., within all modules that have been imported or required in
124
- * the current session) can be guaranteed to have _a_ render-able value.
125
- */
126
- export declare function waitForAllDefaultIntlMessagesLoaded(): Promise<void>;
127
- /**
128
- * Create a new MessageLoader, which handles lazily loading messages for
129
- * different locales and sanity checks as needed to provide accessors for each
130
- * message defined in `messageKeys`.
131
- */
132
- export declare function createLoader(messageKeys: string[], localeImportMap: LocaleImportMap, defaultLocale: LocaleId): MessageLoader;
133
103
  export {};
@@ -64,8 +64,6 @@ var __read = (this && this.__read) || function (o, n) {
64
64
  };
65
65
  Object.defineProperty(exports, "__esModule", { value: true });
66
66
  exports.MessageLoader = void 0;
67
- exports.waitForAllDefaultIntlMessagesLoaded = waitForAllDefaultIntlMessagesLoaded;
68
- exports.createLoader = createLoader;
69
67
  var message_1 = require("./message");
70
68
  var MessageLoader = /** @class */ (function () {
71
69
  function MessageLoader(messageKeys, localeImportMap, defaultLocale) {
@@ -80,24 +78,17 @@ var MessageLoader = /** @class */ (function () {
80
78
  this._parseCache = new Map();
81
79
  this._subscribers = new Set();
82
80
  this._loadLocale(this.defaultLocale);
83
- this.fallbackMessage = new message_1.InternalIntlMessage([], this.defaultLocale);
81
+ this.fallbackMessage = new message_1.InternalIntlMessage('THIS MESSAGE FAILED TO LOAD', 'en-US');
84
82
  // In cases where hot module replacement is available, set up cache clearing whenever the
85
83
  // targets change so that values are always replaced.
86
84
  // @ts-expect-error `hot` not defined in types.
87
85
  if (module.hot) {
88
86
  var _loop_1 = function (locale, file) {
89
87
  // @ts-expect-error `hot` not defined in types.
90
- module.hot.accept(file, function () { return __awaiter(_this, void 0, void 0, function () {
91
- return __generator(this, function (_a) {
92
- switch (_a.label) {
93
- case 0: return [4 /*yield*/, this._loadLocale(locale)];
94
- case 1:
95
- _a.sent();
96
- this._parseCache.clear();
97
- return [2 /*return*/];
98
- }
99
- });
100
- }); });
88
+ module.hot.accept(file, function () {
89
+ _this._parseCache.clear();
90
+ _this._loadLocale(locale);
91
+ });
101
92
  };
102
93
  try {
103
94
  for (var _b = __values(Object.entries(localeImportMap)), _c = _b.next(); !_c.done; _c = _b.next()) {
@@ -128,8 +119,9 @@ var MessageLoader = /** @class */ (function () {
128
119
  MessageLoader.prototype.get = function (key, locale) {
129
120
  var _a;
130
121
  var value = (_a = this.getMessageValue(key, locale)) !== null && _a !== void 0 ? _a : this.getMessageValue(key, this.defaultLocale);
131
- if (value != null)
122
+ if (value != null) {
132
123
  return value;
124
+ }
133
125
  // If the message couldn't be found in either the requested nor the default locale, then
134
126
  // nothing can be done.
135
127
  var errorKey = this._debugKeyMap != null ? "\"".concat(this._debugKeyMap[key], "\" (").concat(key, ")") : undefined;
@@ -150,25 +142,20 @@ var MessageLoader = /** @class */ (function () {
150
142
  * returning.
151
143
  */
152
144
  MessageLoader.prototype.getMessageValue = function (key, locale) {
153
- var _a, _b, _c;
154
145
  var cacheKey = key + '@' + locale;
155
146
  var cachedValue = this._parseCache.get(cacheKey);
156
147
  if (cachedValue != null)
157
148
  return cachedValue;
158
- // Return early if this locale has not yet been initialized and is still in
159
- // the process of being loaded.
160
- if (!((_b = (_a = this._localeLoadingPromises[locale]) === null || _a === void 0 ? void 0 : _a.initialized) !== null && _b !== void 0 ? _b : false) ||
161
- ((_c = this._localeLoadingPromises[locale]) === null || _c === void 0 ? void 0 : _c.current) != null) {
149
+ // Return early if this locale is still in the process of being loaded.
150
+ if (this._localeLoadingPromises[locale] != null) {
162
151
  return undefined;
163
152
  }
164
153
  // If not, check whether it's been loaded and trigger a load if not.
165
154
  if (this.messages[locale] == null) {
166
- // Ensure the locale starts loading, if it is supported
155
+ // Ensure the locale is loaded, if it is supported
167
156
  if (this.supportedLocales.includes(locale)) {
168
157
  this._loadLocale(locale);
169
158
  }
170
- // ...But that means it definitely won't be available on this tick,
171
- // so return undefined.
172
159
  return undefined;
173
160
  }
174
161
  // Then try to return the loaded message.
@@ -199,7 +186,7 @@ var MessageLoader = /** @class */ (function () {
199
186
  };
200
187
  MessageLoader.prototype._loadLocale = function (locale) {
201
188
  return __awaiter(this, void 0, void 0, function () {
202
- var current, _a, _b;
189
+ var loadingPromise, _a, _b;
203
190
  return __generator(this, function (_c) {
204
191
  switch (_c.label) {
205
192
  case 0:
@@ -216,14 +203,14 @@ var MessageLoader = /** @class */ (function () {
216
203
  // If the locale is already set in `messages`, then it doesn't need to be loaded again.
217
204
  if (this.messages[locale] != null)
218
205
  return [2 /*return*/];
219
- current = this.localeImportMap[locale]();
220
- this._localeLoadingPromises[locale] = { initialized: false, current: current };
206
+ loadingPromise = this.localeImportMap[locale]();
207
+ this._localeLoadingPromises[locale] = loadingPromise;
221
208
  _a = this.messages;
222
209
  _b = locale;
223
- return [4 /*yield*/, current];
210
+ return [4 /*yield*/, loadingPromise];
224
211
  case 1:
225
212
  _a[_b] = (_c.sent()).default;
226
- this._localeLoadingPromises[locale] = { initialized: true, current: undefined };
213
+ delete this._localeLoadingPromises[locale];
227
214
  this.emitChange();
228
215
  return [2 /*return*/];
229
216
  }
@@ -258,91 +245,6 @@ var MessageLoader = /** @class */ (function () {
258
245
  this._subscribers.add(callback);
259
246
  return function () { return _this._subscribers.delete(callback); };
260
247
  };
261
- MessageLoader.prototype.isLocaleLoaded = function (locale, requireCurrent) {
262
- if (requireCurrent === void 0) { requireCurrent = false; }
263
- var ref = this._localeLoadingPromises[locale];
264
- // Ensure the locale at least exists and is initialized.
265
- if (ref == null || ref.initialized == false)
266
- return false;
267
- // `current` will be deleted once the locale has loaded, so if it is null,
268
- // then the locale data is current.
269
- if (requireCurrent)
270
- return ref.current == null;
271
- // Otherwise, initialization is enough.
272
- return true;
273
- };
274
- MessageLoader.prototype.waitForLocaleLoaded = function (locale_1) {
275
- return __awaiter(this, arguments, void 0, function (locale, requireCurrent) {
276
- var ref;
277
- if (requireCurrent === void 0) { requireCurrent = false; }
278
- return __generator(this, function (_a) {
279
- switch (_a.label) {
280
- case 0:
281
- ref = this._localeLoadingPromises[locale];
282
- // If the locale hasn't been loaded at all, kick off the load and return
283
- // that Promise directly.
284
- if (ref == null)
285
- return [2 /*return*/, this._loadLocale(locale)];
286
- // If initialization is enough for this request, just resolve immediately.
287
- if (ref.initialized && !requireCurrent)
288
- return [2 /*return*/];
289
- // Otherwise, check the `current` loading state on the object, and if that
290
- // doesn't exist, then the locale data is already finished loading, and the
291
- // containing Promise will just resolve with `undefined`.
292
- return [4 /*yield*/, ref.current];
293
- case 1:
294
- // Otherwise, check the `current` loading state on the object, and if that
295
- // doesn't exist, then the locale data is already finished loading, and the
296
- // containing Promise will just resolve with `undefined`.
297
- _a.sent();
298
- return [2 /*return*/];
299
- }
300
- });
301
- });
302
- };
303
- /**
304
- * Returns true if this loader's default locale has finished loading. When
305
- * true, all messages managed by this loader are guaranteed to have _a_
306
- * render-able value, even if one does not exist in the current locale.
307
- */
308
- MessageLoader.prototype.waitForDefaultLocale = function () {
309
- return __awaiter(this, arguments, void 0, function (requireCurrent) {
310
- if (requireCurrent === void 0) { requireCurrent = false; }
311
- return __generator(this, function (_a) {
312
- return [2 /*return*/, this.waitForLocaleLoaded(this.defaultLocale, requireCurrent)];
313
- });
314
- });
315
- };
316
248
  return MessageLoader;
317
249
  }());
318
250
  exports.MessageLoader = MessageLoader;
319
- var LOADER_REGISTRY = [];
320
- /**
321
- * Returns a new Promise that resolves after all currently-registered message
322
- * loaders have finished successfully loading their default locale content.
323
- * Once this Promise has resolved, all messages that currently exist in the
324
- * application (i.e., within all modules that have been imported or required in
325
- * the current session) can be guaranteed to have _a_ render-able value.
326
- */
327
- function waitForAllDefaultIntlMessagesLoaded() {
328
- return __awaiter(this, void 0, void 0, function () {
329
- return __generator(this, function (_a) {
330
- switch (_a.label) {
331
- case 0: return [4 /*yield*/, Promise.all(LOADER_REGISTRY.map(function (loader) { return loader.waitForDefaultLocale(); }))];
332
- case 1:
333
- _a.sent();
334
- return [2 /*return*/];
335
- }
336
- });
337
- });
338
- }
339
- /**
340
- * Create a new MessageLoader, which handles lazily loading messages for
341
- * different locales and sanity checks as needed to provide accessors for each
342
- * message defined in `messageKeys`.
343
- */
344
- function createLoader(messageKeys, localeImportMap, defaultLocale) {
345
- var loader = new MessageLoader(messageKeys, localeImportMap, defaultLocale);
346
- LOADER_REGISTRY.push(loader);
347
- return loader;
348
- }
package/dist/message.d.ts CHANGED
@@ -1,6 +1,20 @@
1
1
  import { FormatJsNode } from './keyless-json';
2
2
  export declare class InternalIntlMessage {
3
3
  locale: string;
4
+ message?: string;
4
5
  ast: FormatJsNode[];
5
- constructor(messageOrAst: FormatJsNode[], locale: string);
6
+ /**
7
+ * A stripped-down representation of the message with no rich formatting
8
+ * elements preserved. This will be lazily created the first time it is
9
+ * requested (e.g., by calling `formatToPlainString`).
10
+ */
11
+ get plainAst(): FormatJsNode[];
12
+ _plainAst: FormatJsNode[];
13
+ constructor(messageOrAst: string | FormatJsNode[], locale: string);
14
+ /**
15
+ * Returns the same element with any formatting tags removed. If the element
16
+ * itself is a tag, the children will be hoisted and returned, otherwise the
17
+ * element is returned as an array of itself.
18
+ */
19
+ private _removeRichTags;
6
20
  }
package/dist/message.js CHANGED
@@ -1,11 +1,93 @@
1
1
  "use strict";
2
+ var __values = (this && this.__values) || function(o) {
3
+ var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
4
+ if (m) return m.call(o);
5
+ if (o && typeof o.length === "number") return {
6
+ next: function () {
7
+ if (o && i >= o.length) o = void 0;
8
+ return { value: o && o[i++], done: !o };
9
+ }
10
+ };
11
+ throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
12
+ };
13
+ var __read = (this && this.__read) || function (o, n) {
14
+ var m = typeof Symbol === "function" && o[Symbol.iterator];
15
+ if (!m) return o;
16
+ var i = m.call(o), r, ar = [], e;
17
+ try {
18
+ while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
19
+ }
20
+ catch (error) { e = { error: error }; }
21
+ finally {
22
+ try {
23
+ if (r && !r.done && (m = i["return"])) m.call(i);
24
+ }
25
+ finally { if (e) throw e.error; }
26
+ }
27
+ return ar;
28
+ };
29
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
30
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
31
+ if (ar || !(i in from)) {
32
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
33
+ ar[i] = from[i];
34
+ }
35
+ }
36
+ return to.concat(ar || Array.prototype.slice.call(from));
37
+ };
2
38
  Object.defineProperty(exports, "__esModule", { value: true });
3
39
  exports.InternalIntlMessage = void 0;
40
+ var keyless_json_1 = require("./keyless-json");
4
41
  var InternalIntlMessage = /** @class */ (function () {
5
42
  function InternalIntlMessage(messageOrAst, locale) {
6
43
  this.locale = locale;
7
- this.ast = messageOrAst;
44
+ if (typeof messageOrAst === 'string') {
45
+ this.message = messageOrAst;
46
+ }
47
+ else {
48
+ this.ast = messageOrAst;
49
+ }
8
50
  }
51
+ Object.defineProperty(InternalIntlMessage.prototype, "plainAst", {
52
+ /**
53
+ * A stripped-down representation of the message with no rich formatting
54
+ * elements preserved. This will be lazily created the first time it is
55
+ * requested (e.g., by calling `formatToPlainString`).
56
+ */
57
+ get: function () {
58
+ return (this._plainAst = this._removeRichTags(this.ast));
59
+ },
60
+ enumerable: false,
61
+ configurable: true
62
+ });
63
+ /**
64
+ * Returns the same element with any formatting tags removed. If the element
65
+ * itself is a tag, the children will be hoisted and returned, otherwise the
66
+ * element is returned as an array of itself.
67
+ */
68
+ InternalIntlMessage.prototype._removeRichTags = function (nodes) {
69
+ var e_1, _a;
70
+ var result = new Array(nodes.length);
71
+ try {
72
+ for (var nodes_1 = __values(nodes), nodes_1_1 = nodes_1.next(); !nodes_1_1.done; nodes_1_1 = nodes_1.next()) {
73
+ var node = nodes_1_1.value;
74
+ if (node.type === keyless_json_1.FormatJsNodeType.Tag) {
75
+ result.push.apply(result, __spreadArray([], __read(this._removeRichTags(node.children)), false));
76
+ }
77
+ else {
78
+ result.push(node);
79
+ }
80
+ }
81
+ }
82
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
83
+ finally {
84
+ try {
85
+ if (nodes_1_1 && !nodes_1_1.done && (_a = nodes_1.return)) _a.call(nodes_1);
86
+ }
87
+ finally { if (e_1) throw e_1.error; }
88
+ }
89
+ return result;
90
+ };
9
91
  return InternalIntlMessage;
10
92
  }());
11
93
  exports.InternalIntlMessage = InternalIntlMessage;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@discord/intl",
3
- "version": "0.2.0-canary.cad566b",
3
+ "version": "0.2.0",
4
4
  "license": "MIT",
5
5
  "description": "Client runtime for managing messages and translations in a React project.",
6
6
  "main": "./dist/index.js",
package/src/index.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import * as React from 'react';
2
2
  import { RichTextNode } from './formatters';
3
3
 
4
+ export { IntlManager, createLoader, DEFAULT_LOCALE, type FormatFunction } from './intl-manager';
4
5
  export * from './formatters';
5
- export { IntlManager, DEFAULT_LOCALE, type FormatFunction } from './intl-manager';
6
- export { createLoader, waitForAllDefaultIntlMessagesLoaded } from './message-loader';
6
+
7
7
  export type * from './types.d.ts';
8
8
 
9
9
  /**
@@ -116,11 +116,8 @@ export class IntlManager {
116
116
  * immediately return the plain string value of the message in the current locale.
117
117
  */
118
118
  string<T extends TypedIntlMessageGetter<undefined>>(message: T): string {
119
- // @ts-expect-error Figure out how to make this typing exact. This
120
- // currently relies on the generic typing being sound enough to know that
121
- // the message can only contain a single static text node and no
122
- // placeholders or rich text.
123
- return message(this.currentLocale).ast[0].value;
119
+ // TODO: Figure out how to make this typing exact.
120
+ return message(this.currentLocale).message;
124
121
  }
125
122
 
126
123
  bindFormatValues<T>(
@@ -138,3 +135,16 @@ export class IntlManager {
138
135
  );
139
136
  }
140
137
  }
138
+
139
+ /**
140
+ * Create a new MessageLoader, which handles lazily loading messages for
141
+ * different locales and sanity checks as needed to provide accessors for each
142
+ * message defined in `messageKeys`.
143
+ */
144
+ export function createLoader(
145
+ messageKeys: string[],
146
+ localeImportMap: LocaleImportMap,
147
+ defaultLocale: LocaleId,
148
+ ) {
149
+ return new MessageLoader(messageKeys, localeImportMap, defaultLocale);
150
+ }
@@ -32,18 +32,7 @@ export class MessageLoader {
32
32
  * then a request has already been made to load the locale and it is waiting
33
33
  * to resolve.
34
34
  */
35
- _localeLoadingPromises: Record<
36
- LocaleId,
37
- {
38
- /**`true` if this locale has been loaded at least once. */
39
- initialized: boolean;
40
- /**
41
- * If the locale is currently being loaded, this Promise will exist and
42
- * represent that loading status. Otherwise, this value is undefined.
43
- */
44
- current?: Promise<{ default: MessagesData }> | undefined;
45
- }
46
- >;
35
+ _localeLoadingPromises: Record<LocaleId, Promise<{ default: MessagesData }> | undefined>;
47
36
 
48
37
  /**
49
38
  * Map of pre-parsed messages, keyed by the message key and locale
@@ -96,7 +85,7 @@ export class MessageLoader {
96
85
  this._subscribers = new Set();
97
86
 
98
87
  this._loadLocale(this.defaultLocale);
99
- this.fallbackMessage = new InternalIntlMessage([], this.defaultLocale);
88
+ this.fallbackMessage = new InternalIntlMessage('THIS MESSAGE FAILED TO LOAD', 'en-US');
100
89
 
101
90
  // In cases where hot module replacement is available, set up cache clearing whenever the
102
91
  // targets change so that values are always replaced.
@@ -104,9 +93,9 @@ export class MessageLoader {
104
93
  if (module.hot) {
105
94
  for (const [locale, file] of Object.entries(localeImportMap)) {
106
95
  // @ts-expect-error `hot` not defined in types.
107
- module.hot.accept(file, async () => {
108
- await this._loadLocale(locale);
96
+ module.hot.accept(file, () => {
109
97
  this._parseCache.clear();
98
+ this._loadLocale(locale);
110
99
  });
111
100
  }
112
101
  }
@@ -127,7 +116,9 @@ export class MessageLoader {
127
116
  get(key: string, locale: LocaleId): InternalIntlMessage {
128
117
  const value =
129
118
  this.getMessageValue(key, locale) ?? this.getMessageValue(key, this.defaultLocale);
130
- if (value != null) return value;
119
+ if (value != null) {
120
+ return value;
121
+ }
131
122
 
132
123
  // If the message couldn't be found in either the requested nor the default locale, then
133
124
  // nothing can be done.
@@ -158,23 +149,18 @@ export class MessageLoader {
158
149
  const cachedValue = this._parseCache.get(cacheKey);
159
150
  if (cachedValue != null) return cachedValue;
160
151
 
161
- // Return early if this locale has not yet been initialized and is still in
162
- // the process of being loaded.
163
- if (
164
- !(this._localeLoadingPromises[locale]?.initialized ?? false) ||
165
- this._localeLoadingPromises[locale]?.current != null
166
- ) {
152
+ // Return early if this locale is still in the process of being loaded.
153
+ if (this._localeLoadingPromises[locale] != null) {
167
154
  return undefined;
168
155
  }
169
156
 
170
157
  // If not, check whether it's been loaded and trigger a load if not.
171
158
  if (this.messages[locale] == null) {
172
- // Ensure the locale starts loading, if it is supported
159
+ // Ensure the locale is loaded, if it is supported
173
160
  if (this.supportedLocales.includes(locale)) {
174
161
  this._loadLocale(locale);
175
162
  }
176
- // ...But that means it definitely won't be available on this tick,
177
- // so return undefined.
163
+
178
164
  return undefined;
179
165
  }
180
166
 
@@ -227,10 +213,10 @@ export class MessageLoader {
227
213
  // If the locale is already set in `messages`, then it doesn't need to be loaded again.
228
214
  if (this.messages[locale] != null) return;
229
215
 
230
- const current = this.localeImportMap[locale]();
231
- this._localeLoadingPromises[locale] = { initialized: false, current };
232
- this.messages[locale] = (await current).default;
233
- this._localeLoadingPromises[locale] = { initialized: true, current: undefined };
216
+ const loadingPromise = this.localeImportMap[locale]();
217
+ this._localeLoadingPromises[locale] = loadingPromise;
218
+ this.messages[locale] = (await loadingPromise).default;
219
+ delete this._localeLoadingPromises[locale];
234
220
  this.emitChange();
235
221
  }
236
222
 
@@ -252,65 +238,4 @@ export class MessageLoader {
252
238
 
253
239
  return () => this._subscribers.delete(callback);
254
240
  }
255
-
256
- isLocaleLoaded(locale: LocaleId, requireCurrent: boolean = false): boolean {
257
- const ref = this._localeLoadingPromises[locale];
258
- // Ensure the locale at least exists and is initialized.
259
- if (ref == null || ref.initialized == false) return false;
260
- // `current` will be deleted once the locale has loaded, so if it is null,
261
- // then the locale data is current.
262
- if (requireCurrent) return ref.current == null;
263
- // Otherwise, initialization is enough.
264
- return true;
265
- }
266
-
267
- async waitForLocaleLoaded(locale: LocaleId, requireCurrent = false): Promise<void> {
268
- const ref = this._localeLoadingPromises[locale];
269
- // If the locale hasn't been loaded at all, kick off the load and return
270
- // that Promise directly.
271
- if (ref == null) return this._loadLocale(locale);
272
- // If initialization is enough for this request, just resolve immediately.
273
- if (ref.initialized && !requireCurrent) return;
274
- // Otherwise, check the `current` loading state on the object, and if that
275
- // doesn't exist, then the locale data is already finished loading, and the
276
- // containing Promise will just resolve with `undefined`.
277
- await ref.current;
278
- }
279
-
280
- /**
281
- * Returns true if this loader's default locale has finished loading. When
282
- * true, all messages managed by this loader are guaranteed to have _a_
283
- * render-able value, even if one does not exist in the current locale.
284
- */
285
- async waitForDefaultLocale(requireCurrent = false): Promise<void> {
286
- return this.waitForLocaleLoaded(this.defaultLocale, requireCurrent);
287
- }
288
- }
289
-
290
- const LOADER_REGISTRY: MessageLoader[] = [];
291
-
292
- /**
293
- * Returns a new Promise that resolves after all currently-registered message
294
- * loaders have finished successfully loading their default locale content.
295
- * Once this Promise has resolved, all messages that currently exist in the
296
- * application (i.e., within all modules that have been imported or required in
297
- * the current session) can be guaranteed to have _a_ render-able value.
298
- */
299
- export async function waitForAllDefaultIntlMessagesLoaded(): Promise<void> {
300
- await Promise.all(LOADER_REGISTRY.map((loader) => loader.waitForDefaultLocale()));
301
- }
302
-
303
- /**
304
- * Create a new MessageLoader, which handles lazily loading messages for
305
- * different locales and sanity checks as needed to provide accessors for each
306
- * message defined in `messageKeys`.
307
- */
308
- export function createLoader(
309
- messageKeys: string[],
310
- localeImportMap: LocaleImportMap,
311
- defaultLocale: LocaleId,
312
- ) {
313
- const loader = new MessageLoader(messageKeys, localeImportMap, defaultLocale);
314
- LOADER_REGISTRY.push(loader);
315
- return loader;
316
241
  }
package/src/message.ts CHANGED
@@ -1,11 +1,44 @@
1
- import { FormatJsNode } from './keyless-json';
1
+ import { FormatJsNode, FormatJsNodeType } from './keyless-json';
2
2
 
3
3
  export class InternalIntlMessage {
4
4
  locale: string;
5
+ message?: string;
5
6
  ast: FormatJsNode[];
6
7
 
7
- constructor(messageOrAst: FormatJsNode[], locale: string) {
8
+ /**
9
+ * A stripped-down representation of the message with no rich formatting
10
+ * elements preserved. This will be lazily created the first time it is
11
+ * requested (e.g., by calling `formatToPlainString`).
12
+ */
13
+ get plainAst(): FormatJsNode[] {
14
+ return (this._plainAst = this._removeRichTags(this.ast));
15
+ }
16
+ _plainAst: FormatJsNode[];
17
+
18
+ constructor(messageOrAst: string | FormatJsNode[], locale: string) {
8
19
  this.locale = locale;
9
- this.ast = messageOrAst;
20
+
21
+ if (typeof messageOrAst === 'string') {
22
+ this.message = messageOrAst;
23
+ } else {
24
+ this.ast = messageOrAst;
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Returns the same element with any formatting tags removed. If the element
30
+ * itself is a tag, the children will be hoisted and returned, otherwise the
31
+ * element is returned as an array of itself.
32
+ */
33
+ private _removeRichTags(nodes: FormatJsNode[]): FormatJsNode[] {
34
+ const result = new Array(nodes.length);
35
+ for (const node of nodes) {
36
+ if (node.type === FormatJsNodeType.Tag) {
37
+ result.push(...this._removeRichTags(node.children));
38
+ } else {
39
+ result.push(node);
40
+ }
41
+ }
42
+ return result;
10
43
  }
11
44
  }