@discord/intl 0.1.2 → 0.2.0-canary.641ab2f

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.
@@ -1,7 +1,6 @@
1
+ import { FormatJsNode } from './keyless-json';
1
2
  import { InternalIntlMessage } from './message';
2
- import type { MessageFormatElement } from '@formatjs/icu-messageformat-parser';
3
- type AnyRawMessage = string | MessageFormatElement[];
4
- type MessagesData = Record<string, AnyRawMessage>;
3
+ type MessagesData = Record<string, FormatJsNode[]>;
5
4
  export interface IntlMessageGetterAdditions {
6
5
  onChange(callback: () => void): () => void;
7
6
  }
@@ -29,9 +28,17 @@ export declare class MessageLoader {
29
28
  * then a request has already been made to load the locale and it is waiting
30
29
  * to resolve.
31
30
  */
32
- _localeLoadingPromises: Record<LocaleId, Promise<{
33
- default: MessagesData;
34
- }> | 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
+ }>;
35
42
  /**
36
43
  * Map of pre-parsed messages, keyed by the message key and locale
37
44
  */
@@ -100,5 +107,30 @@ export declare class MessageLoader {
100
107
  * Subscribe to events about when a locale has loaded.
101
108
  */
102
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>;
103
118
  }
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
+ * This function can be called at any point in an applications life cycle and
127
+ * will always ensure that _all_ loaders that currently exist are initialized.
128
+ */
129
+ export declare function waitForAllDefaultIntlMessagesLoaded(): Promise<void>;
130
+ /**
131
+ * Create a new MessageLoader, which handles lazily loading messages for
132
+ * different locales and sanity checks as needed to provide accessors for each
133
+ * message defined in `messageKeys`.
134
+ */
135
+ export declare function createLoader(messageKeys: string[], localeImportMap: LocaleImportMap, defaultLocale: LocaleId): MessageLoader;
104
136
  export {};
@@ -64,6 +64,8 @@ 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;
67
69
  var message_1 = require("./message");
68
70
  var MessageLoader = /** @class */ (function () {
69
71
  function MessageLoader(messageKeys, localeImportMap, defaultLocale) {
@@ -78,17 +80,24 @@ var MessageLoader = /** @class */ (function () {
78
80
  this._parseCache = new Map();
79
81
  this._subscribers = new Set();
80
82
  this._loadLocale(this.defaultLocale);
81
- this.fallbackMessage = new message_1.InternalIntlMessage('THIS MESSAGE FAILED TO LOAD', 'en-US');
83
+ this.fallbackMessage = new message_1.InternalIntlMessage([], this.defaultLocale);
82
84
  // In cases where hot module replacement is available, set up cache clearing whenever the
83
85
  // targets change so that values are always replaced.
84
86
  // @ts-expect-error `hot` not defined in types.
85
87
  if (module.hot) {
86
88
  var _loop_1 = function (locale, file) {
87
89
  // @ts-expect-error `hot` not defined in types.
88
- module.hot.accept(file, function () {
89
- _this._parseCache.clear();
90
- _this._loadLocale(locale);
91
- });
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
+ }); });
92
101
  };
93
102
  try {
94
103
  for (var _b = __values(Object.entries(localeImportMap)), _c = _b.next(); !_c.done; _c = _b.next()) {
@@ -119,9 +128,8 @@ var MessageLoader = /** @class */ (function () {
119
128
  MessageLoader.prototype.get = function (key, locale) {
120
129
  var _a;
121
130
  var value = (_a = this.getMessageValue(key, locale)) !== null && _a !== void 0 ? _a : this.getMessageValue(key, this.defaultLocale);
122
- if (value != null) {
131
+ if (value != null)
123
132
  return value;
124
- }
125
133
  // If the message couldn't be found in either the requested nor the default locale, then
126
134
  // nothing can be done.
127
135
  var errorKey = this._debugKeyMap != null ? "\"".concat(this._debugKeyMap[key], "\" (").concat(key, ")") : undefined;
@@ -146,13 +154,11 @@ var MessageLoader = /** @class */ (function () {
146
154
  var cachedValue = this._parseCache.get(cacheKey);
147
155
  if (cachedValue != null)
148
156
  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.
157
+ // Check whether the locale exists in memory. If not, start a request to
158
+ // load the locale if one has not already started. It's impossible for
159
+ // the request to finish within the same tick, so return undefined in any
160
+ // case then.
154
161
  if (this.messages[locale] == null) {
155
- // Ensure the locale is loaded, if it is supported
156
162
  if (this.supportedLocales.includes(locale)) {
157
163
  this._loadLocale(locale);
158
164
  }
@@ -186,12 +192,13 @@ var MessageLoader = /** @class */ (function () {
186
192
  };
187
193
  MessageLoader.prototype._loadLocale = function (locale) {
188
194
  return __awaiter(this, void 0, void 0, function () {
189
- var loadingPromise, _a, _b;
190
- return __generator(this, function (_c) {
191
- switch (_c.label) {
195
+ var current, _a, _b;
196
+ var _c;
197
+ return __generator(this, function (_d) {
198
+ switch (_d.label) {
192
199
  case 0:
193
200
  // Don't re-load a locale that's already in progress.
194
- if (this._localeLoadingPromises[locale] != null) {
201
+ if (((_c = this._localeLoadingPromises[locale]) === null || _c === void 0 ? void 0 : _c.current) != null) {
195
202
  return [2 /*return*/];
196
203
  }
197
204
  // Safety check in case the locale map doesn't include a require for the
@@ -203,14 +210,14 @@ var MessageLoader = /** @class */ (function () {
203
210
  // If the locale is already set in `messages`, then it doesn't need to be loaded again.
204
211
  if (this.messages[locale] != null)
205
212
  return [2 /*return*/];
206
- loadingPromise = this.localeImportMap[locale]();
207
- this._localeLoadingPromises[locale] = loadingPromise;
213
+ current = this.localeImportMap[locale]();
214
+ this._localeLoadingPromises[locale] = { initialized: false, current: current };
208
215
  _a = this.messages;
209
216
  _b = locale;
210
- return [4 /*yield*/, loadingPromise];
217
+ return [4 /*yield*/, current];
211
218
  case 1:
212
- _a[_b] = (_c.sent()).default;
213
- delete this._localeLoadingPromises[locale];
219
+ _a[_b] = (_d.sent()).default;
220
+ this._localeLoadingPromises[locale] = { initialized: true, current: undefined };
214
221
  this.emitChange();
215
222
  return [2 /*return*/];
216
223
  }
@@ -245,6 +252,94 @@ var MessageLoader = /** @class */ (function () {
245
252
  this._subscribers.add(callback);
246
253
  return function () { return _this._subscribers.delete(callback); };
247
254
  };
255
+ MessageLoader.prototype.isLocaleLoaded = function (locale, requireCurrent) {
256
+ if (requireCurrent === void 0) { requireCurrent = false; }
257
+ var ref = this._localeLoadingPromises[locale];
258
+ // Ensure the locale at least exists and is initialized.
259
+ if (ref == null || ref.initialized == false)
260
+ return false;
261
+ // `current` will be deleted once the locale has loaded, so if it is null,
262
+ // then the locale data is current.
263
+ if (requireCurrent)
264
+ return ref.current == null;
265
+ // Otherwise, initialization is enough.
266
+ return true;
267
+ };
268
+ MessageLoader.prototype.waitForLocaleLoaded = function (locale_1) {
269
+ return __awaiter(this, arguments, void 0, function (locale, requireCurrent) {
270
+ var ref;
271
+ if (requireCurrent === void 0) { requireCurrent = false; }
272
+ return __generator(this, function (_a) {
273
+ switch (_a.label) {
274
+ case 0:
275
+ ref = this._localeLoadingPromises[locale];
276
+ // If the locale hasn't been loaded at all, kick off the load and return
277
+ // that Promise directly.
278
+ if (ref == null)
279
+ return [2 /*return*/, this._loadLocale(locale)];
280
+ // If initialization is enough for this request, just resolve immediately.
281
+ if (ref.initialized && !requireCurrent)
282
+ return [2 /*return*/];
283
+ // Otherwise, check the `current` loading state on the object, and if that
284
+ // doesn't exist, then the locale data is already finished loading, and the
285
+ // containing Promise will just resolve with `undefined`.
286
+ return [4 /*yield*/, ref.current];
287
+ case 1:
288
+ // Otherwise, check the `current` loading state on the object, and if that
289
+ // doesn't exist, then the locale data is already finished loading, and the
290
+ // containing Promise will just resolve with `undefined`.
291
+ _a.sent();
292
+ return [2 /*return*/];
293
+ }
294
+ });
295
+ });
296
+ };
297
+ /**
298
+ * Returns true if this loader's default locale has finished loading. When
299
+ * true, all messages managed by this loader are guaranteed to have _a_
300
+ * render-able value, even if one does not exist in the current locale.
301
+ */
302
+ MessageLoader.prototype.waitForDefaultLocale = function () {
303
+ return __awaiter(this, arguments, void 0, function (requireCurrent) {
304
+ if (requireCurrent === void 0) { requireCurrent = false; }
305
+ return __generator(this, function (_a) {
306
+ return [2 /*return*/, this.waitForLocaleLoaded(this.defaultLocale, requireCurrent)];
307
+ });
308
+ });
309
+ };
248
310
  return MessageLoader;
249
311
  }());
250
312
  exports.MessageLoader = MessageLoader;
313
+ var LOADER_REGISTRY = [];
314
+ /**
315
+ * Returns a new Promise that resolves after all currently-registered message
316
+ * loaders have finished successfully loading their default locale content.
317
+ * Once this Promise has resolved, all messages that currently exist in the
318
+ * application (i.e., within all modules that have been imported or required in
319
+ * the current session) can be guaranteed to have _a_ render-able value.
320
+ *
321
+ * This function can be called at any point in an applications life cycle and
322
+ * will always ensure that _all_ loaders that currently exist are initialized.
323
+ */
324
+ function waitForAllDefaultIntlMessagesLoaded() {
325
+ return __awaiter(this, void 0, void 0, function () {
326
+ return __generator(this, function (_a) {
327
+ switch (_a.label) {
328
+ case 0: return [4 /*yield*/, Promise.all(LOADER_REGISTRY.map(function (loader) { return loader.waitForDefaultLocale(); }))];
329
+ case 1:
330
+ _a.sent();
331
+ return [2 /*return*/];
332
+ }
333
+ });
334
+ });
335
+ }
336
+ /**
337
+ * Create a new MessageLoader, which handles lazily loading messages for
338
+ * different locales and sanity checks as needed to provide accessors for each
339
+ * message defined in `messageKeys`.
340
+ */
341
+ function createLoader(messageKeys, localeImportMap, defaultLocale) {
342
+ var loader = new MessageLoader(messageKeys, localeImportMap, defaultLocale);
343
+ LOADER_REGISTRY.push(loader);
344
+ return loader;
345
+ }
package/dist/message.d.ts CHANGED
@@ -1,41 +1,6 @@
1
- import { Formats, Formatters, MessageFormatPart } from 'intl-messageformat';
2
- import type { MessageFormatElement } from '@formatjs/icu-messageformat-parser';
1
+ import { FormatJsNode } from './keyless-json';
3
2
  export declare class InternalIntlMessage {
4
3
  locale: string;
5
- message?: string;
6
- ast: MessageFormatElement[];
7
- /**
8
- * A stripped-down representation of the message with no rich formatting
9
- * elements preserved. This will be lazily created the first time it is
10
- * requested (e.g., by calling `formatToPlainString`).
11
- */
12
- plainAst?: MessageFormatElement[];
13
- constructor(messageOrAst: string | MessageFormatElement[], locale: string);
14
- /**
15
- * Format this message with the given locale into an AST of chunks that can
16
- * then be used or rendered into strings, React elements, or anything else as
17
- * needed.
18
- */
19
- formatToParts<T>(formatters: Formatters, formats: Formats, values?: Record<string, any>): Array<MessageFormatPart<T>>;
20
- /**
21
- * Formats this message just like `formatToParts`, but strips all stylistic
22
- * elements and tags and returns a plain string with only the literal text of
23
- * the message remaining.
24
- *
25
- * This method works by quickly scanning the string and removing any tag
26
- * elements, formatting, then concatenating the pieces into a single result.
27
- *
28
- * Note that this function does _not_ handle serializing values other than
29
- * plain/primitive values into strings. All values are simply converted using
30
- * the `String()` constructor. So, for example, passing a function as a value
31
- * will directly serialize the function definition into a string, rather than
32
- * calling the function or evaluating the result.
33
- */
34
- formatToPlainString(formatters: Formatters, formats: Formats, values?: Record<string, any>): string;
35
- /**
36
- * Returns the same element with any formatting tags removed. If the element
37
- * itself is a tag, the children will be hoisted and returned, otherwise the
38
- * element is returned as an array of itself.
39
- */
40
- _removeRichTags(element: MessageFormatElement): MessageFormatElement[];
4
+ ast: FormatJsNode[];
5
+ constructor(messageOrAst: FormatJsNode[], locale: string);
41
6
  }
package/dist/message.js CHANGED
@@ -1,142 +1,11 @@
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
- };
38
2
  Object.defineProperty(exports, "__esModule", { value: true });
39
3
  exports.InternalIntlMessage = void 0;
40
- var icu_messageformat_parser_1 = require("@formatjs/icu-messageformat-parser");
41
- var intl_messageformat_1 = require("intl-messageformat");
42
4
  var InternalIntlMessage = /** @class */ (function () {
43
5
  function InternalIntlMessage(messageOrAst, locale) {
44
6
  this.locale = locale;
45
- if (typeof messageOrAst === 'string') {
46
- this.message = messageOrAst;
47
- this.ast = (0, icu_messageformat_parser_1.parse)(messageOrAst);
48
- }
49
- else {
50
- this.ast = messageOrAst;
51
- }
52
- this.plainAst = undefined;
7
+ this.ast = messageOrAst;
53
8
  }
54
- /**
55
- * Format this message with the given locale into an AST of chunks that can
56
- * then be used or rendered into strings, React elements, or anything else as
57
- * needed.
58
- */
59
- InternalIntlMessage.prototype.formatToParts = function (formatters, formats, values) {
60
- return (0, intl_messageformat_1.formatToParts)(this.ast, this.locale, formatters, formats, values);
61
- };
62
- /**
63
- * Formats this message just like `formatToParts`, but strips all stylistic
64
- * elements and tags and returns a plain string with only the literal text of
65
- * the message remaining.
66
- *
67
- * This method works by quickly scanning the string and removing any tag
68
- * elements, formatting, then concatenating the pieces into a single result.
69
- *
70
- * Note that this function does _not_ handle serializing values other than
71
- * plain/primitive values into strings. All values are simply converted using
72
- * the `String()` constructor. So, for example, passing a function as a value
73
- * will directly serialize the function definition into a string, rather than
74
- * calling the function or evaluating the result.
75
- */
76
- InternalIntlMessage.prototype.formatToPlainString = function (formatters, formats, values) {
77
- var e_1, _a, _b, e_2, _c;
78
- // Lazily create the reduced AST if it hasn't already been done.
79
- if (this.plainAst == null) {
80
- this.plainAst = [];
81
- try {
82
- for (var _d = __values(this.ast), _e = _d.next(); !_e.done; _e = _d.next()) {
83
- var part = _e.value;
84
- (_b = this.plainAst).push.apply(_b, __spreadArray([], __read(this._removeRichTags(part)), false));
85
- }
86
- }
87
- catch (e_1_1) { e_1 = { error: e_1_1 }; }
88
- finally {
89
- try {
90
- if (_e && !_e.done && (_a = _d.return)) _a.call(_d);
91
- }
92
- finally { if (e_1) throw e_1.error; }
93
- }
94
- }
95
- var parts = (0, intl_messageformat_1.formatToParts)(this.plainAst, this.locale, formatters, formats, values);
96
- var result = '';
97
- try {
98
- for (var parts_1 = __values(parts), parts_1_1 = parts_1.next(); !parts_1_1.done; parts_1_1 = parts_1.next()) {
99
- var part = parts_1_1.value;
100
- result += String(part.value);
101
- }
102
- }
103
- catch (e_2_1) { e_2 = { error: e_2_1 }; }
104
- finally {
105
- try {
106
- if (parts_1_1 && !parts_1_1.done && (_c = parts_1.return)) _c.call(parts_1);
107
- }
108
- finally { if (e_2) throw e_2.error; }
109
- }
110
- return result;
111
- };
112
- /**
113
- * Returns the same element with any formatting tags removed. If the element
114
- * itself is a tag, the children will be hoisted and returned, otherwise the
115
- * element is returned as an array of itself.
116
- */
117
- InternalIntlMessage.prototype._removeRichTags = function (element) {
118
- var e_3, _a;
119
- if (element.type === icu_messageformat_parser_1.TYPE.tag) {
120
- var childContent = [];
121
- try {
122
- for (var _b = __values(element.children), _c = _b.next(); !_c.done; _c = _b.next()) {
123
- var child = _c.value;
124
- childContent.push.apply(childContent, __spreadArray([], __read(this._removeRichTags(child)), false));
125
- }
126
- }
127
- catch (e_3_1) { e_3 = { error: e_3_1 }; }
128
- finally {
129
- try {
130
- if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
131
- }
132
- finally { if (e_3) throw e_3.error; }
133
- }
134
- return childContent;
135
- }
136
- else {
137
- return [element];
138
- }
139
- };
140
9
  return InternalIntlMessage;
141
10
  }());
142
11
  exports.InternalIntlMessage = InternalIntlMessage;
package/dist/types.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { InternalIntlMessage } from './message';
2
2
  import type { IntlMessageGetterAdditions } from './message-loader';
3
+ import { FormatBuilderConstructor } from './format';
3
4
 
4
5
  /**
5
6
  * Use this function to define messages as part of the `@discord/intl`
@@ -11,14 +12,6 @@ import type { IntlMessageGetterAdditions } from './message-loader';
11
12
  */
12
13
  export declare function defineMessages(messages: object): object;
13
14
 
14
- /**
15
- * A map of tag names to render functions to apply when the tags are
16
- * encountered while formatting a message. Each `IntlManager` can be
17
- * constructed with a different map of these elements, for example to allow
18
- * one manager to render React output while another renders accessibility text.
19
- */
20
- export type RichTextElementMap = Record<string, (chunks: any[]) => any>;
21
-
22
15
  /**
23
16
  * Placeholder type representing that this property is a Hook to be rendered
24
17
  * while formatting.
@@ -46,19 +39,8 @@ export type IntlTime = string | number | Date | null | undefined;
46
39
  * strings, and are able to skip multiple formatting steps to work more
47
40
  * efficiently. This type represents those messages distinctly.
48
41
  */
49
- type PlainIntlMessage = string;
50
- /**
51
- * A richly-formatted message that injects dynamic values while formatting,
52
- * with the types of those values defined by the `FormatValues` object. All
53
- * messages of this type must have at least one value in the object, otherwise
54
- * the message should be typed as a `PlainIntlMessage`.
55
- */
56
- type TypedIntlMessage<FormatValues extends object> = InternalIntlMessage & {
57
- __values: FormatValues;
58
- };
59
-
60
- type AnyIntlMessage<FormatValues extends object | undefined = undefined> =
61
- FormatValues extends undefined ? PlainIntlMessage : TypedIntlMessage<FormatValues>;
42
+ export type PlainIntlMessage = string;
43
+ export type AnyIntlMessage = PlainIntlMessage | InternalIntlMessage;
62
44
 
63
45
  export interface IntlMessageGetter extends IntlMessageGetterAdditions {
64
46
  (locale: string): InternalIntlMessage | PlainIntlMessage;
@@ -85,8 +67,8 @@ export interface TypedIntlMessageGetter<FormatValues extends object | undefined>
85
67
  // from the declared type. The latter of these is definitely semantically
86
68
  // correct, but the former is more just an annoying part of the type system
87
69
  // because of this returned type being nested within this interface and the
88
- // strict subtying that TypeScript uses.
89
- (locale: string): InternalIntlMessage | PlainIntlMessage;
70
+ // strict subtyping that TypeScript uses.
71
+ (locale: string): InternalIntlMessage;
90
72
 
91
73
  /**
92
74
  * A phantom type (not part of the actual typing of the object) that ensures
@@ -183,7 +165,7 @@ type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
183
165
  * checked against the provided message types, and error messages will be
184
166
  * accurate as a result.
185
167
  */
186
- type FormatValuesFor<T> =
168
+ export type FormatValuesFor<T> =
187
169
  // `[]` syntax forces TypeScript to not distribute the possible union of
188
170
  // types, meaning this represents "_all_ values of T are an extension of
189
171
  // the parent type", which in this case represents messages with no
@@ -209,12 +191,18 @@ type FormatValuesFor<T> =
209
191
  * the caller to inject React elements within a string, while an AST formatter
210
192
  * might require a specific object shape, or allow any plain object.
211
193
  */
212
- interface FunctionTypeMap {
194
+ export interface FunctionTypeMap {
213
195
  link: any;
214
196
  hook: any;
215
197
  handler: any;
216
198
  }
217
199
 
200
+ export interface FunctionTypes<Result, HandlerType = (content: Result[]) => Result | Result[]> {
201
+ link: undefined | ((content: Result[]) => Result | Result[]);
202
+ hook: undefined | ((content: Result[]) => Result | Result[]);
203
+ handler: undefined | HandlerType;
204
+ }
205
+
218
206
  type ReplaceKeepingNullability<T, P> = P | Exclude<T, NonNullable<T>>;
219
207
 
220
208
  /**
@@ -226,6 +214,8 @@ type ReplaceKeepingNullability<T, P> = P | Exclude<T, NonNullable<T>>;
226
214
  type MapFunctionTypes<T, FunctionTypes extends FunctionTypeMap> = {
227
215
  [K in keyof T]: ReplaceKeepingNullability<
228
216
  T[K],
217
+ // TODO: replace these types with string literal types in the generated
218
+ // file, turning this whole chain into `FunctionTypes[T[K]] ?? T[K]`.
229
219
  NonNullable<T[K]> extends LinkFunction
230
220
  ? FunctionTypes['link']
231
221
  : NonNullable<T[K]> extends HookFunction
@@ -251,7 +241,7 @@ type CheckStrictAny<Check, Yes, No> = (Check extends never ? true : false) exten
251
241
  * // Result is `{name: string}`
252
242
  * ```
253
243
  */
254
- type IntlMessageGetterInnerType<T extends IntlMessageGetter> =
244
+ export type IntlMessageGetterInnerType<T extends IntlMessageGetter> =
255
245
  T extends TypedIntlMessageGetter<infer U> ? U : never;
256
246
 
257
247
  /**
@@ -260,13 +250,12 @@ type IntlMessageGetterInnerType<T extends IntlMessageGetter> =
260
250
  * types from `FunctionTypes`. The result is the set of required values
261
251
  * necessary to format the message of type `T` with the calling formatter.
262
252
  */
263
- type RequiredFormatValues<
253
+ export type RequiredFormatValues<
264
254
  Values extends IntlMessageGetter,
265
- DefaultElements,
266
255
  FunctionTypes extends FunctionTypeMap,
267
256
  > =
268
257
  IntlMessageGetterInnerType<Values> extends undefined
269
- ? never
258
+ ? {}
270
259
  : CheckStrictAny<
271
260
  IntlMessageGetterInnerType<Values>,
272
261
  // If `T` is explicitly `any`, then individual properties can't be typed and
@@ -277,23 +266,30 @@ type RequiredFormatValues<
277
266
  // value and turn every property into the mapped `LinkFunction` type, which
278
267
  // is almost always going to be wrong.
279
268
  any,
280
- MapFunctionTypes<
281
- Omit<FormatValuesFor<IntlMessageGetterInnerType<Values>>, keyof DefaultElements>,
282
- FunctionTypes
283
- >
269
+ MapFunctionTypes<FormatValuesFor<IntlMessageGetterInnerType<Values>>, FunctionTypes>
284
270
  >;
285
271
 
286
- export interface IntlOptionalRichTextKeys {
287
- $_: any;
288
- $b: any;
289
- $i: any;
290
- $p: any;
291
- $link: any;
292
- $code: any;
272
+ export interface RichTextFormattingMap<T = any> {
273
+ $_: T;
274
+ $b: T;
275
+ $i: T;
276
+ $p: T;
277
+ $link: T;
278
+ $code: T;
293
279
  }
294
280
 
295
- export type IntlFormatFunctionValuesArg<T extends IntlMessageGetter> = RequiredFormatValues<
296
- T,
297
- IntlOptionalRichTextKeys,
298
- FunctionTypeMap
299
- >;
281
+ export type RichTextTagNames = keyof RichTextFormattingMap;
282
+
283
+ export interface FormatterImplementation<
284
+ FunctionTypes extends FunctionTypeMap,
285
+ Result,
286
+ BuilderResult = Result,
287
+ > {
288
+ format: (
289
+ message: AnyIntlMessage,
290
+ values: object,
291
+ builder?: FormatBuilderConstructor<BuilderResult>,
292
+ ) => Result;
293
+ builder: FormatBuilderConstructor<BuilderResult extends (infer T)[] ? T : BuilderResult>;
294
+ __$functionTypes?: FunctionTypes;
295
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@discord/intl",
3
- "version": "0.1.2",
3
+ "version": "0.2.0-canary.641ab2f",
4
4
  "license": "MIT",
5
5
  "description": "Client runtime for managing messages and translations in a React project.",
6
6
  "main": "./dist/index.js",
@@ -15,7 +15,6 @@
15
15
  "src"
16
16
  ],
17
17
  "dependencies": {
18
- "@formatjs/icu-messageformat-parser": "^2.7.6",
19
18
  "@formatjs/intl": "^2.10.1",
20
19
  "intl-messageformat": "^10.5.11"
21
20
  },