@discord/intl 0.2.0 → 0.2.1-canary.1ed7c43

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/format.js CHANGED
@@ -34,6 +34,7 @@ exports.bindFormatValuesWithBuilder = bindFormatValuesWithBuilder;
34
34
  exports.bindFormatValues = bindFormatValues;
35
35
  var keyless_json_1 = require("./keyless-json");
36
36
  var intl_messageformat_1 = require("intl-messageformat");
37
+ var icu_skeleton_parser_1 = require("@formatjs/icu-skeleton-parser");
37
38
  /**
38
39
  * Returns true if the tag name should be considered a rich text tag that
39
40
  * applies formatting to a message, rather than being a user-supplied value.
@@ -96,10 +97,11 @@ function bindFormatValuesWithBuilder(builder, nodes, locales, formatters, format
96
97
  case keyless_json_1.FormatJsNodeType.Date: {
97
98
  // Distinct from FormatJS: We don't currently parse the skeleton ahead of time in the AST,
98
99
  // so this manages parsing the skeleton as well before passing it onto the date formatter.
99
- if (typeof node.style !== 'string') {
100
- throw '@discord/intl does not currently support non-string format skeletons';
101
- }
102
- var style = node.style in formats.date ? formats.date[node.style] : undefined; // TODO: parseSkeleton();
100
+ var style = node.style in formats.date
101
+ ? formats.date[node.style]
102
+ : node.style != null
103
+ ? (0, icu_skeleton_parser_1.parseDateTimeSkeleton)(node.style)
104
+ : undefined;
103
105
  // @ts-expect-error Cast string values to dates properly.
104
106
  builder.pushLiteralText(formatters.getDateTimeFormat(locales, style).format(value));
105
107
  break;
@@ -107,10 +109,11 @@ function bindFormatValuesWithBuilder(builder, nodes, locales, formatters, format
107
109
  case keyless_json_1.FormatJsNodeType.Time: {
108
110
  // Distinct from FormatJS: We don't currently parse the skeleton ahead of time in the AST,
109
111
  // so this manages parsing the skeleton as well before passing it onto the date formatter.
110
- if (typeof node.style !== 'string') {
111
- throw '@discord/intl does not currently support non-string format skeletons';
112
- }
113
- var style = node.style in formats.time ? formats.time[node.style] : undefined; // TODO: parseSkeleton();
112
+ var style = node.style in formats.time
113
+ ? formats.time[node.style]
114
+ : node.style != null
115
+ ? (0, icu_skeleton_parser_1.parseDateTimeSkeleton)(node.style)
116
+ : undefined; // TODO: parseSkeleton();
114
117
  builder.pushLiteralText(
115
118
  // @ts-expect-error Cast string values to dates properly.
116
119
  formatters.getDateTimeFormat(locales, style).format(value));
@@ -119,13 +122,13 @@ function bindFormatValuesWithBuilder(builder, nodes, locales, formatters, format
119
122
  case keyless_json_1.FormatJsNodeType.Number: {
120
123
  // Distinct from FormatJS: We don't currently parse the skeleton ahead of time in the AST,
121
124
  // so this manages parsing the skeleton as well before passing it onto the date formatter.
122
- if (typeof node.style !== 'string') {
123
- throw '@discord/intl does not currently support non-string format skeletons';
124
- }
125
- var style = node.style in formats.number ? formats.number[node.style] : undefined; // TODO: parseSkeleton();
125
+ var style = node.style in formats.number
126
+ ? formats.number[node.style]
127
+ : node.style != null
128
+ ? (0, icu_skeleton_parser_1.parseNumberSkeleton)((0, icu_skeleton_parser_1.parseNumberSkeletonFromString)(node.style))
129
+ : undefined;
126
130
  // @ts-expect-error Support `scale` style property.
127
- var scaledValue = value;
128
- // const scaledValue = (value as number) * (style?.scale ?? 1);
131
+ var scaledValue = (style === null || style === void 0 ? void 0 : style.scale) != 1 ? value * style.scale : value;
129
132
  builder.pushLiteralText(formatters.getNumberFormat(locales, style).format(scaledValue));
130
133
  break;
131
134
  }
@@ -4,11 +4,12 @@ import { IntlManager } from '../intl-manager';
4
4
  * Types for formatting functions when calling `formatToParts`, ensuring the
5
5
  * functions yield value AST nodes.
6
6
  */
7
- export type AstFunctionTypes = FunctionTypes<RichTextNode>;
7
+ export type AstFunctionTypes = FunctionTypes<RichTextNode, object>;
8
8
  export declare enum RichTextNodeType {
9
9
  Text = "text",
10
10
  Strong = "strong",
11
11
  Emphasis = "emphasis",
12
+ Strikethrough = "strikethrough",
12
13
  Code = "code",
13
14
  Link = "link",
14
15
  Paragraph = "paragraph",
@@ -36,6 +37,9 @@ interface RichTextStrongNode extends RichTextNodeBase<RichTextNode[]> {
36
37
  interface RichTextEmphasisNode extends RichTextNodeBase<RichTextNode[]> {
37
38
  type: RichTextNodeType.Emphasis;
38
39
  }
40
+ interface RichTextStrikethroughNode extends RichTextNodeBase<RichTextNode[]> {
41
+ type: RichTextNodeType.Strikethrough;
42
+ }
39
43
  interface RichTextCodeNode extends RichTextNodeBase<RichTextNode[]> {
40
44
  type: RichTextNodeType.Code;
41
45
  }
@@ -49,7 +53,7 @@ interface RichTextLinkNode extends RichTextNodeBase<RichTextNode[]> {
49
53
  interface RichTextObjectNode extends RichTextNodeBase<object> {
50
54
  type: RichTextNodeType.Object;
51
55
  }
52
- export type RichTextNode = RichTextTextNode | RichTextStrongNode | RichTextEmphasisNode | RichTextCodeNode | RichTextParagraphNode | RichTextLinkNode | RichTextObjectNode;
56
+ export type RichTextNode = RichTextTextNode | RichTextStrongNode | RichTextEmphasisNode | RichTextStrikethroughNode | RichTextCodeNode | RichTextParagraphNode | RichTextLinkNode | RichTextObjectNode;
53
57
  /**
54
58
  * Format the given message in the current locale with the provided values.
55
59
  * The returned values is _always_ an Array of parts, even if the message is
@@ -51,6 +51,7 @@ var RichTextNodeType;
51
51
  RichTextNodeType["Text"] = "text";
52
52
  RichTextNodeType["Strong"] = "strong";
53
53
  RichTextNodeType["Emphasis"] = "emphasis";
54
+ RichTextNodeType["Strikethrough"] = "strikethrough";
54
55
  RichTextNodeType["Code"] = "code";
55
56
  RichTextNodeType["Link"] = "link";
56
57
  RichTextNodeType["Paragraph"] = "paragraph";
@@ -65,19 +66,12 @@ var RichTextNodeType;
65
66
  */
66
67
  RichTextNodeType["Object"] = "object";
67
68
  })(RichTextNodeType || (exports.RichTextNodeType = RichTextNodeType = {}));
68
- var RICH_TEXT_TAG_TYPES = {
69
- $_: RichTextNodeType.Text,
70
- $b: RichTextNodeType.Strong,
71
- $i: RichTextNodeType.Emphasis,
72
- $code: RichTextNodeType.Code,
73
- $link: RichTextNodeType.Link,
74
- $p: RichTextNodeType.Paragraph,
75
- };
76
69
  var AST_RICH_TEXT_ELEMENTS = {
77
70
  $_: function () { return ({ type: RichTextNodeType.Text, content: '' }); },
78
71
  $b: function (content) { return ({ type: RichTextNodeType.Strong, content: content }); },
79
- $code: function (content) { return ({ type: RichTextNodeType.Code, content: content }); },
80
72
  $i: function (content) { return ({ type: RichTextNodeType.Emphasis, content: content }); },
73
+ $del: function (content) { return ({ type: RichTextNodeType.Strikethrough, content: content }); },
74
+ $code: function (content) { return ({ type: RichTextNodeType.Code, content: content }); },
81
75
  $link: function (_a) {
82
76
  var _b = __read(_a), target = _b[0], content = _b.slice(1);
83
77
  return ({
@@ -100,7 +94,7 @@ var AstBuilder = /** @class */ (function (_super) {
100
94
  if (!(tag in AST_RICH_TEXT_ELEMENTS)) {
101
95
  throw "".concat(tag, " is not a known rich text formatting tag");
102
96
  }
103
- var result = AST_RICH_TEXT_ELEMENTS[tag](children);
97
+ var result = AST_RICH_TEXT_ELEMENTS[tag](children, '');
104
98
  if (Array.isArray(result)) {
105
99
  (_a = this.result).push.apply(_a, __spreadArray([], __read(result), false));
106
100
  }
@@ -46,8 +46,9 @@ var string_1 = require("./string");
46
46
  var MARKDOWN_RICH_TEXT_ELEMENTS = {
47
47
  $_: function () { return ''; },
48
48
  $b: function (content) { return '**' + content.join('') + '**'; },
49
- $code: function (content) { return '`' + content.join('') + '`'; },
50
49
  $i: function (content) { return '*' + content.join('') + '*'; },
50
+ $del: function (content) { return '~~' + content.join('') + '~~'; },
51
+ $code: function (content) { return '`' + content.join('') + '`'; },
51
52
  $link: function (_a) {
52
53
  var _b = __read(_a), target = _b[0], content = _b.slice(1);
53
54
  return '[' + content.join('') + '](' + target + ')';
@@ -62,7 +63,7 @@ var MarkdownBuilder = /** @class */ (function (_super) {
62
63
  return _this;
63
64
  }
64
65
  MarkdownBuilder.prototype.pushRichTextTag = function (tag, children) {
65
- this.result += MARKDOWN_RICH_TEXT_ELEMENTS[tag](children);
66
+ this.result += MARKDOWN_RICH_TEXT_ELEMENTS[tag](children, '');
66
67
  };
67
68
  return MarkdownBuilder;
68
69
  }(string_1.StringBuilder));
@@ -18,11 +18,14 @@ export type ReactFunctionTypes = FunctionTypes<React.ReactNode, ReactClickHandle
18
18
  onContextMenu: ReactClickHandler;
19
19
  }>;
20
20
  export declare const DEFAULT_REACT_RICH_TEXT_ELEMENTS: RichTextFormattingMap<ReactFunctionTypes['hook']>;
21
- export declare function formatReact(this: IntlManager, message: AnyIntlMessage, values: object, Builder: FormatBuilderConstructor<React.ReactElement>): React.ReactElement;
21
+ export type ReactIntlMessage = React.ReactElement<{}, typeof React.Fragment & {
22
+ __brand: 'discord-intl';
23
+ }>;
24
+ export declare function formatReact(this: IntlManager, message: AnyIntlMessage, values: object, Builder: FormatBuilderConstructor<React.ReactElement>): ReactIntlMessage;
22
25
  /**
23
26
  * Create a new React formatter with the given rich text elements replacing the defaults. Use this
24
27
  * function to inject custom components for things like links and paragraphs, which may be best
25
28
  * suited to use components from a Design System or other library rather than native DOM elements.
26
29
  */
27
- export declare function makeReactFormatter(richTextElements: RichTextFormattingMap<ReactFunctionTypes['hook']>): FormatterImplementation<ReactFunctionTypes, React.ReactElement, React.ReactNode>;
28
- export declare const reactFormatter: FormatterImplementation<ReactFunctionTypes, React.ReactElement<any, string | React.JSXElementConstructor<any>>, React.ReactNode>;
30
+ export declare function makeReactFormatter(richTextElements: RichTextFormattingMap<ReactFunctionTypes['hook']>): FormatterImplementation<ReactFunctionTypes, ReactIntlMessage, React.ReactNode>;
31
+ export declare const reactFormatter: FormatterImplementation<ReactFunctionTypes, ReactIntlMessage, React.ReactNode>;
@@ -48,15 +48,16 @@ var format_1 = require("../format");
48
48
  var h = React.createElement;
49
49
  exports.DEFAULT_REACT_RICH_TEXT_ELEMENTS = {
50
50
  $_: function () { return ''; },
51
- $b: function (content) { return h('strong', undefined, content); },
52
- $code: function (content) { return h('code', undefined, content); },
53
- $i: function (content) { return h('em', undefined, content); },
51
+ $b: function (content, key) { return h('strong', { key: key }, content); },
52
+ $i: function (content, key) { return h('em', { key: key }, content); },
53
+ $del: function (content, key) { return h('del', { key: key }, content); },
54
+ $code: function (content, key) { return h('code', { key: key }, content); },
54
55
  // $link will always be [href, <empty>, ...content]
55
- $link: function (_a) {
56
+ $link: function (_a, key) {
56
57
  var _b = __read(_a), href = _b[0], content = _b.slice(1);
57
- return h('a', { href: href }, content);
58
+ return h('a', { href: href, key: key }, content);
58
59
  },
59
- $p: function (content) { return h('p', undefined, content); },
60
+ $p: function (content, key) { return h('p', { key: key }, content); },
60
61
  };
61
62
  /**
62
63
  * Creates a new `FormatBuilder` class that constructs a React element tree
@@ -70,11 +71,12 @@ function createReactBuilder(richTextElements) {
70
71
  __extends(class_1, _super);
71
72
  function class_1() {
72
73
  var _this = _super.apply(this, __spreadArray([], __read(arguments), false)) || this;
74
+ _this._nodeKey = 0;
73
75
  _this.result = [];
74
76
  return _this;
75
77
  }
76
78
  class_1.prototype.pushRichTextTag = function (tag, children) {
77
- this.result.push(richTextElements[tag](children));
79
+ this.result.push(richTextElements[tag](children, "".concat(this._nodeKey++)));
78
80
  };
79
81
  class_1.prototype.pushLiteralText = function (text) {
80
82
  this.result.push(text);
package/dist/index.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import * as React from 'react';
2
2
  import { RichTextNode } from './formatters';
3
- export { IntlManager, createLoader, DEFAULT_LOCALE, type FormatFunction } from './intl-manager';
4
3
  export * from './formatters';
4
+ export { IntlManager, DEFAULT_LOCALE, type FormatFunction } from './intl-manager';
5
+ export { createLoader, loadAllMessagesInLocale, waitForAllDefaultIntlMessagesLoaded, MessageLoader, } from './message-loader';
5
6
  export type * from './types.d.ts';
6
7
  /**
7
8
  * A type representing any message that has been formatted using one of the
package/dist/index.js CHANGED
@@ -14,9 +14,13 @@ 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.DEFAULT_LOCALE = exports.createLoader = exports.IntlManager = void 0;
17
+ exports.MessageLoader = exports.waitForAllDefaultIntlMessagesLoaded = exports.loadAllMessagesInLocale = exports.createLoader = exports.DEFAULT_LOCALE = exports.IntlManager = void 0;
18
+ __exportStar(require("./formatters"), exports);
18
19
  var intl_manager_1 = require("./intl-manager");
19
20
  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
- __exportStar(require("./formatters"), exports);
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, "loadAllMessagesInLocale", { enumerable: true, get: function () { return message_loader_1.loadAllMessagesInLocale; } });
25
+ Object.defineProperty(exports, "waitForAllDefaultIntlMessagesLoaded", { enumerable: true, get: function () { return message_loader_1.waitForAllDefaultIntlMessagesLoaded; } });
26
+ Object.defineProperty(exports, "MessageLoader", { enumerable: true, get: function () { return message_loader_1.MessageLoader; } });
@@ -1,5 +1,4 @@
1
1
  import { IntlShape } from '@formatjs/intl';
2
- import { LocaleId, LocaleImportMap, MessageLoader } from './message-loader';
3
2
  import type { FormatterImplementation, IntlMessageGetter, RequiredFormatValues, TypedIntlMessageGetter } from './types';
4
3
  import { InternalIntlMessage } from './message';
5
4
  import { FormatBuilderConstructor } from './format';
@@ -8,7 +7,7 @@ import { FormatBuilderConstructor } from './format';
8
7
  * requested locale is not possible.
9
8
  */
10
9
  export declare const DEFAULT_LOCALE: string;
11
- type FormatterReturnType<F extends FormatterImplementation<any, any>> = F extends FormatterImplementation<any, infer Return> ? Return : never;
10
+ type FormatterReturnType<F extends FormatterImplementation<any, any>> = ReturnType<F['format']>;
12
11
  type FormatterFunctionTypes<F extends FormatterImplementation<any, any>> = F extends FormatterImplementation<infer Functions, any> ? Functions : never;
13
12
  export type FormatFunction<F extends FormatterImplementation<any, any>> = <T extends IntlMessageGetter>(this: IntlManager, message: T, values: RequiredFormatValues<T, FormatterFunctionTypes<F>>) => FormatterReturnType<F>;
14
13
  type ThisWithFormatters<This, T extends Record<string, FormatterImplementation<any, any>>> = This & {
@@ -46,12 +45,16 @@ export declare class IntlManager {
46
45
  * immediately return the plain string value of the message in the current locale.
47
46
  */
48
47
  string<T extends TypedIntlMessageGetter<undefined>>(message: T): string;
48
+ /**
49
+ * Return a raw string representing the syntax of the original message, as
50
+ * authored, with no values replaced. The result of this function could be
51
+ * written back to the definition file for the message and re-parsed to
52
+ * create an identical message to the original.
53
+ *
54
+ * This should rarely be necessary outside of sending raw messages to other
55
+ * applications that do their own message parsing.
56
+ */
57
+ reserialize<T extends IntlMessageGetter>(message: T): string;
49
58
  bindFormatValues<T>(Builder: FormatBuilderConstructor<T>, message: InternalIntlMessage, values: Record<string, any>): T[];
50
59
  }
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;
57
60
  export {};
@@ -28,10 +28,8 @@ 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;
32
31
  var intl_1 = require("@formatjs/intl");
33
32
  var intl_messageformat_1 = require("intl-messageformat");
34
- var message_loader_1 = require("./message-loader");
35
33
  var format_1 = require("./format");
36
34
  /**
37
35
  * Fallback locale used for all internationalization when an operation in the
@@ -122,8 +120,25 @@ var IntlManager = /** @class */ (function () {
122
120
  * immediately return the plain string value of the message in the current locale.
123
121
  */
124
122
  IntlManager.prototype.string = function (message) {
125
- // TODO: Figure out how to make this typing exact.
126
- return message(this.currentLocale).message;
123
+ var resolved = message(this.currentLocale);
124
+ if (resolved == null || resolved.ast.length === 0)
125
+ return '';
126
+ // TODO: Figure out how to make this typing exact. This currently relies on
127
+ // the generic typing being sound enough to know that the message can only
128
+ // contain a single static text node and no placeholders or rich text.
129
+ return resolved.ast[0].value;
130
+ };
131
+ /**
132
+ * Return a raw string representing the syntax of the original message, as
133
+ * authored, with no values replaced. The result of this function could be
134
+ * written back to the definition file for the message and re-parsed to
135
+ * create an identical message to the original.
136
+ *
137
+ * This should rarely be necessary outside of sending raw messages to other
138
+ * applications that do their own message parsing.
139
+ */
140
+ IntlManager.prototype.reserialize = function (message) {
141
+ return message(this.currentLocale)[0].reserialize();
127
142
  };
128
143
  IntlManager.prototype.bindFormatValues = function (Builder, message, values) {
129
144
  return (0, format_1.bindFormatValues)(Builder, message.ast, [this.currentLocale, this.defaultLocale], this.intl.formatters, this.intl.formats, values);
@@ -131,11 +146,3 @@ var IntlManager = /** @class */ (function () {
131
146
  return IntlManager;
132
147
  }());
133
148
  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,9 +28,17 @@ 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, Promise<{
32
- default: MessagesData;
33
- }> | undefined>;
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
+ }>;
34
42
  /**
35
43
  * Map of pre-parsed messages, keyed by the message key and locale
36
44
  */
@@ -99,5 +107,36 @@ export declare class MessageLoader {
99
107
  * Subscribe to events about when a locale has loaded.
100
108
  */
101
109
  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>;
102
118
  }
119
+ /**
120
+ * Kick off a load request for the given locale across all currently-registered
121
+ * message loaders, returning a Promise that resolves when all loaders have
122
+ * finished loading that locale.
123
+ */
124
+ export declare function loadAllMessagesInLocale(locale: LocaleId): Promise<void>;
125
+ /**
126
+ * Returns a new Promise that resolves after all currently-registered message
127
+ * loaders have finished successfully loading their default locale content.
128
+ * Once this Promise has resolved, all messages that currently exist in the
129
+ * application (i.e., within all modules that have been imported or required in
130
+ * the current session) can be guaranteed to have _a_ render-able value.
131
+ *
132
+ * This function can be called at any point in an applications life cycle and
133
+ * will always ensure that _all_ loaders that currently exist are initialized.
134
+ */
135
+ export declare function waitForAllDefaultIntlMessagesLoaded(): Promise<void>;
136
+ /**
137
+ * Create a new MessageLoader, which handles lazily loading messages for
138
+ * different locales and sanity checks as needed to provide accessors for each
139
+ * message defined in `messageKeys`.
140
+ */
141
+ export declare function createLoader(messageKeys: string[], localeImportMap: LocaleImportMap, defaultLocale: LocaleId): MessageLoader;
103
142
  export {};
@@ -64,6 +64,9 @@ 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.loadAllMessagesInLocale = loadAllMessagesInLocale;
68
+ exports.waitForAllDefaultIntlMessagesLoaded = waitForAllDefaultIntlMessagesLoaded;
69
+ exports.createLoader = createLoader;
67
70
  var message_1 = require("./message");
68
71
  var MessageLoader = /** @class */ (function () {
69
72
  function MessageLoader(messageKeys, localeImportMap, defaultLocale) {
@@ -78,17 +81,24 @@ var MessageLoader = /** @class */ (function () {
78
81
  this._parseCache = new Map();
79
82
  this._subscribers = new Set();
80
83
  this._loadLocale(this.defaultLocale);
81
- this.fallbackMessage = new message_1.InternalIntlMessage('THIS MESSAGE FAILED TO LOAD', 'en-US');
84
+ this.fallbackMessage = new message_1.InternalIntlMessage([], this.defaultLocale);
82
85
  // In cases where hot module replacement is available, set up cache clearing whenever the
83
86
  // targets change so that values are always replaced.
84
87
  // @ts-expect-error `hot` not defined in types.
85
88
  if (module.hot) {
86
89
  var _loop_1 = function (locale, file) {
87
90
  // @ts-expect-error `hot` not defined in types.
88
- module.hot.accept(file, function () {
89
- _this._parseCache.clear();
90
- _this._loadLocale(locale);
91
- });
91
+ module.hot.accept(file, function () { return __awaiter(_this, void 0, void 0, function () {
92
+ return __generator(this, function (_a) {
93
+ switch (_a.label) {
94
+ case 0: return [4 /*yield*/, this._loadLocale(locale)];
95
+ case 1:
96
+ _a.sent();
97
+ this._parseCache.clear();
98
+ return [2 /*return*/];
99
+ }
100
+ });
101
+ }); });
92
102
  };
93
103
  try {
94
104
  for (var _b = __values(Object.entries(localeImportMap)), _c = _b.next(); !_c.done; _c = _b.next()) {
@@ -119,9 +129,8 @@ var MessageLoader = /** @class */ (function () {
119
129
  MessageLoader.prototype.get = function (key, locale) {
120
130
  var _a;
121
131
  var value = (_a = this.getMessageValue(key, locale)) !== null && _a !== void 0 ? _a : this.getMessageValue(key, this.defaultLocale);
122
- if (value != null) {
132
+ if (value != null)
123
133
  return value;
124
- }
125
134
  // If the message couldn't be found in either the requested nor the default locale, then
126
135
  // nothing can be done.
127
136
  var errorKey = this._debugKeyMap != null ? "\"".concat(this._debugKeyMap[key], "\" (").concat(key, ")") : undefined;
@@ -146,13 +155,11 @@ var MessageLoader = /** @class */ (function () {
146
155
  var cachedValue = this._parseCache.get(cacheKey);
147
156
  if (cachedValue != null)
148
157
  return cachedValue;
149
- // Return early if this locale is still in the process of being loaded.
150
- if (this._localeLoadingPromises[locale] != null) {
151
- return undefined;
152
- }
153
- // If not, check whether it's been loaded and trigger a load if not.
158
+ // Check whether the locale exists in memory. If not, start a request to
159
+ // load the locale if one has not already started. It's impossible for
160
+ // the request to finish within the same tick, so return undefined in any
161
+ // case then.
154
162
  if (this.messages[locale] == null) {
155
- // Ensure the locale is loaded, if it is supported
156
163
  if (this.supportedLocales.includes(locale)) {
157
164
  this._loadLocale(locale);
158
165
  }
@@ -186,12 +193,13 @@ var MessageLoader = /** @class */ (function () {
186
193
  };
187
194
  MessageLoader.prototype._loadLocale = function (locale) {
188
195
  return __awaiter(this, void 0, void 0, function () {
189
- var loadingPromise, _a, _b;
190
- return __generator(this, function (_c) {
191
- switch (_c.label) {
196
+ var current, _a, _b;
197
+ var _c;
198
+ return __generator(this, function (_d) {
199
+ switch (_d.label) {
192
200
  case 0:
193
201
  // Don't re-load a locale that's already in progress.
194
- if (this._localeLoadingPromises[locale] != null) {
202
+ if (((_c = this._localeLoadingPromises[locale]) === null || _c === void 0 ? void 0 : _c.current) != null) {
195
203
  return [2 /*return*/];
196
204
  }
197
205
  // Safety check in case the locale map doesn't include a require for the
@@ -203,14 +211,14 @@ var MessageLoader = /** @class */ (function () {
203
211
  // If the locale is already set in `messages`, then it doesn't need to be loaded again.
204
212
  if (this.messages[locale] != null)
205
213
  return [2 /*return*/];
206
- loadingPromise = this.localeImportMap[locale]();
207
- this._localeLoadingPromises[locale] = loadingPromise;
214
+ current = this.localeImportMap[locale]();
215
+ this._localeLoadingPromises[locale] = { initialized: false, current: current };
208
216
  _a = this.messages;
209
217
  _b = locale;
210
- return [4 /*yield*/, loadingPromise];
218
+ return [4 /*yield*/, current];
211
219
  case 1:
212
- _a[_b] = (_c.sent()).default;
213
- delete this._localeLoadingPromises[locale];
220
+ _a[_b] = (_d.sent()).default;
221
+ this._localeLoadingPromises[locale] = { initialized: true, current: undefined };
214
222
  this.emitChange();
215
223
  return [2 /*return*/];
216
224
  }
@@ -245,6 +253,111 @@ var MessageLoader = /** @class */ (function () {
245
253
  this._subscribers.add(callback);
246
254
  return function () { return _this._subscribers.delete(callback); };
247
255
  };
256
+ MessageLoader.prototype.isLocaleLoaded = function (locale, requireCurrent) {
257
+ if (requireCurrent === void 0) { requireCurrent = false; }
258
+ var ref = this._localeLoadingPromises[locale];
259
+ // Ensure the locale at least exists and is initialized.
260
+ if (ref == null || ref.initialized == false)
261
+ return false;
262
+ // `current` will be deleted once the locale has loaded, so if it is null,
263
+ // then the locale data is current.
264
+ if (requireCurrent)
265
+ return ref.current == null;
266
+ // Otherwise, initialization is enough.
267
+ return true;
268
+ };
269
+ MessageLoader.prototype.waitForLocaleLoaded = function (locale_1) {
270
+ return __awaiter(this, arguments, void 0, function (locale, requireCurrent) {
271
+ var ref;
272
+ if (requireCurrent === void 0) { requireCurrent = false; }
273
+ return __generator(this, function (_a) {
274
+ switch (_a.label) {
275
+ case 0:
276
+ ref = this._localeLoadingPromises[locale];
277
+ // If the locale hasn't been loaded at all, kick off the load and return
278
+ // that Promise directly.
279
+ if (ref == null)
280
+ return [2 /*return*/, this._loadLocale(locale)];
281
+ // If initialization is enough for this request, just resolve immediately.
282
+ if (ref.initialized && !requireCurrent)
283
+ return [2 /*return*/];
284
+ // Otherwise, check the `current` loading state on the object, and if that
285
+ // doesn't exist, then the locale data is already finished loading, and the
286
+ // containing Promise will just resolve with `undefined`.
287
+ return [4 /*yield*/, ref.current];
288
+ case 1:
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
+ _a.sent();
293
+ return [2 /*return*/];
294
+ }
295
+ });
296
+ });
297
+ };
298
+ /**
299
+ * Returns true if this loader's default locale has finished loading. When
300
+ * true, all messages managed by this loader are guaranteed to have _a_
301
+ * render-able value, even if one does not exist in the current locale.
302
+ */
303
+ MessageLoader.prototype.waitForDefaultLocale = function () {
304
+ return __awaiter(this, arguments, void 0, function (requireCurrent) {
305
+ if (requireCurrent === void 0) { requireCurrent = false; }
306
+ return __generator(this, function (_a) {
307
+ return [2 /*return*/, this.waitForLocaleLoaded(this.defaultLocale, requireCurrent)];
308
+ });
309
+ });
310
+ };
248
311
  return MessageLoader;
249
312
  }());
250
313
  exports.MessageLoader = MessageLoader;
314
+ var LOADER_REGISTRY = [];
315
+ /**
316
+ * Kick off a load request for the given locale across all currently-registered
317
+ * message loaders, returning a Promise that resolves when all loaders have
318
+ * finished loading that locale.
319
+ */
320
+ function loadAllMessagesInLocale(locale) {
321
+ return __awaiter(this, void 0, void 0, function () {
322
+ return __generator(this, function (_a) {
323
+ switch (_a.label) {
324
+ case 0: return [4 /*yield*/, Promise.all(LOADER_REGISTRY.map(function (loader) { return loader._loadLocale(locale); }))];
325
+ case 1:
326
+ _a.sent();
327
+ return [2 /*return*/];
328
+ }
329
+ });
330
+ });
331
+ }
332
+ /**
333
+ * Returns a new Promise that resolves after all currently-registered message
334
+ * loaders have finished successfully loading their default locale content.
335
+ * Once this Promise has resolved, all messages that currently exist in the
336
+ * application (i.e., within all modules that have been imported or required in
337
+ * the current session) can be guaranteed to have _a_ render-able value.
338
+ *
339
+ * This function can be called at any point in an applications life cycle and
340
+ * will always ensure that _all_ loaders that currently exist are initialized.
341
+ */
342
+ function waitForAllDefaultIntlMessagesLoaded() {
343
+ return __awaiter(this, void 0, void 0, function () {
344
+ return __generator(this, function (_a) {
345
+ switch (_a.label) {
346
+ case 0: return [4 /*yield*/, Promise.all(LOADER_REGISTRY.map(function (loader) { return loader.waitForDefaultLocale(); }))];
347
+ case 1:
348
+ _a.sent();
349
+ return [2 /*return*/];
350
+ }
351
+ });
352
+ });
353
+ }
354
+ /**
355
+ * Create a new MessageLoader, which handles lazily loading messages for
356
+ * different locales and sanity checks as needed to provide accessors for each
357
+ * message defined in `messageKeys`.
358
+ */
359
+ function createLoader(messageKeys, localeImportMap, defaultLocale) {
360
+ var loader = new MessageLoader(messageKeys, localeImportMap, defaultLocale);
361
+ LOADER_REGISTRY.push(loader);
362
+ return loader;
363
+ }
package/dist/message.d.ts CHANGED
@@ -1,20 +1,11 @@
1
1
  import { FormatJsNode } from './keyless-json';
2
2
  export declare class InternalIntlMessage {
3
3
  locale: string;
4
- message?: string;
5
4
  ast: FormatJsNode[];
5
+ constructor(messageOrAst: FormatJsNode[], locale: string);
6
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`).
7
+ * Return a stringified serialization of this message's AST, with no
8
+ * formatting or values applied.
10
9
  */
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;
10
+ reserialize(): string;
20
11
  }