@discord/intl 0.0.1

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.
@@ -0,0 +1,148 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.InternalIntlMessage = void 0;
40
+ var icu_messageformat_parser_1 = require("@formatjs/icu-messageformat-parser");
41
+ var intl_messageformat_1 = require("intl-messageformat");
42
+ var InternalIntlMessage = /** @class */ (function () {
43
+ function InternalIntlMessage(messageOrAst, locale) {
44
+ this.locale = locale;
45
+ if (typeof messageOrAst === 'string') {
46
+ // TODO: This part should pre-parse our Markdown shorthand, too, so that
47
+ // the AST that comes out of this is a standardized format, and we don't
48
+ // have to do any special handling after calling `format`. That also
49
+ // removes the need for handling "unsafe" values, since they'll be
50
+ // parsed before values get interpolated. We can actually do that in the
51
+ // loader, too, and avoid any runtime cost for it.
52
+ this.message = messageOrAst;
53
+ this.ast = (0, icu_messageformat_parser_1.parse)(messageOrAst);
54
+ }
55
+ else {
56
+ this.ast = messageOrAst;
57
+ }
58
+ this.plainAst = undefined;
59
+ }
60
+ /**
61
+ * Format this message with the given locale into an AST of chunks that can
62
+ * then be used or rendered into strings, React elements, or anything else as
63
+ * needed.
64
+ */
65
+ InternalIntlMessage.prototype.formatToParts = function (formatters, formats, values) {
66
+ return (0, intl_messageformat_1.formatToParts)(this.ast, this.locale, formatters, formats, values);
67
+ };
68
+ /**
69
+ * Formats this message just like `formatToParts`, but strips all stylistic
70
+ * elements and tags and returns a plain string with only the literal text of
71
+ * the message remaining.
72
+ *
73
+ * This method works by quickly scanning the string and removing any tag
74
+ * elements, formatting, then concatenating the pieces into a single result.
75
+ *
76
+ * Note that this function does _not_ handle serializing values other than
77
+ * plain/primitive values into strings. All values are simply converted using
78
+ * the `String()` constructor. So, for example, passing a function as a value
79
+ * will directly serialize the function definition into a string, rather than
80
+ * calling the function or evaluating the result.
81
+ */
82
+ InternalIntlMessage.prototype.formatToPlainString = function (formatters, formats, values) {
83
+ var e_1, _a, _b, e_2, _c;
84
+ // Lazily create the reduced AST if it hasn't already been done.
85
+ if (this.plainAst == null) {
86
+ this.plainAst = [];
87
+ try {
88
+ for (var _d = __values(this.ast), _e = _d.next(); !_e.done; _e = _d.next()) {
89
+ var part = _e.value;
90
+ (_b = this.plainAst).push.apply(_b, __spreadArray([], __read(this._removeRichTags(part)), false));
91
+ }
92
+ }
93
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
94
+ finally {
95
+ try {
96
+ if (_e && !_e.done && (_a = _d.return)) _a.call(_d);
97
+ }
98
+ finally { if (e_1) throw e_1.error; }
99
+ }
100
+ }
101
+ var parts = (0, intl_messageformat_1.formatToParts)(this.plainAst, this.locale, formatters, formats, values);
102
+ var result = '';
103
+ try {
104
+ for (var parts_1 = __values(parts), parts_1_1 = parts_1.next(); !parts_1_1.done; parts_1_1 = parts_1.next()) {
105
+ var part = parts_1_1.value;
106
+ result += String(part.value);
107
+ }
108
+ }
109
+ catch (e_2_1) { e_2 = { error: e_2_1 }; }
110
+ finally {
111
+ try {
112
+ if (parts_1_1 && !parts_1_1.done && (_c = parts_1.return)) _c.call(parts_1);
113
+ }
114
+ finally { if (e_2) throw e_2.error; }
115
+ }
116
+ return result;
117
+ };
118
+ /**
119
+ * Returns the same element with any formatting tags removed. If the element
120
+ * itself is a tag, the children will be hoisted and returned, otherwise the
121
+ * element is returned as an array of itself.
122
+ */
123
+ InternalIntlMessage.prototype._removeRichTags = function (element) {
124
+ var e_3, _a;
125
+ if (element.type === icu_messageformat_parser_1.TYPE.tag) {
126
+ var childContent = [];
127
+ try {
128
+ for (var _b = __values(element.children), _c = _b.next(); !_c.done; _c = _b.next()) {
129
+ var child = _c.value;
130
+ childContent.push.apply(childContent, __spreadArray([], __read(this._removeRichTags(child)), false));
131
+ }
132
+ }
133
+ catch (e_3_1) { e_3 = { error: e_3_1 }; }
134
+ finally {
135
+ try {
136
+ if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
137
+ }
138
+ finally { if (e_3) throw e_3.error; }
139
+ }
140
+ return childContent;
141
+ }
142
+ else {
143
+ return [element];
144
+ }
145
+ };
146
+ return InternalIntlMessage;
147
+ }());
148
+ exports.InternalIntlMessage = InternalIntlMessage;
@@ -0,0 +1,24 @@
1
+ import * as React from 'react';
2
+ import { IntlManager } from './intl-manager';
3
+ import type { FormatValuesFor, RichTextElementMap, TypedIntlMessageGetter } from './types';
4
+ export declare class IntlManagerReact<DefaultElements extends RichTextElementMap, DefaultValues extends keyof DefaultElements> extends IntlManager<DefaultElements, DefaultValues> {
5
+ /**
6
+ * A reactive component form of `intl.format` that automatically updates when
7
+ * the application's locale changes and when new data is loaded for the
8
+ * subject message.
9
+ */
10
+ IntlMessage: <T extends TypedIntlMessageGetter<object | undefined>>(props: {
11
+ message: T;
12
+ values?: Omit<FormatValuesFor<T>, DefaultValues>;
13
+ }) => React.FunctionComponentElement<{
14
+ children?: React.ReactNode | undefined;
15
+ }>;
16
+ /**
17
+ * Format the given message as a React component, allowing it to listen for
18
+ * and respond to updates about the current locale and other relevant
19
+ * information.
20
+ */
21
+ format<T extends TypedIntlMessageGetter<object | undefined>>(message: T): JSX.Element;
22
+ format<T extends TypedIntlMessageGetter<object | undefined>>(message: T, values: Omit<FormatValuesFor<T>, DefaultValues>): JSX.Element;
23
+ format(message: any): JSX.Element;
24
+ }
package/dist/react.js ADDED
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ var __extends = (this && this.__extends) || (function () {
3
+ var extendStatics = function (d, b) {
4
+ extendStatics = Object.setPrototypeOf ||
5
+ ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
6
+ function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
7
+ return extendStatics(d, b);
8
+ };
9
+ return function (d, b) {
10
+ if (typeof b !== "function" && b !== null)
11
+ throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
12
+ extendStatics(d, b);
13
+ function __() { this.constructor = d; }
14
+ d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
15
+ };
16
+ })();
17
+ var __read = (this && this.__read) || function (o, n) {
18
+ var m = typeof Symbol === "function" && o[Symbol.iterator];
19
+ if (!m) return o;
20
+ var i = m.call(o), r, ar = [], e;
21
+ try {
22
+ while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
23
+ }
24
+ catch (error) { e = { error: error }; }
25
+ finally {
26
+ try {
27
+ if (r && !r.done && (m = i["return"])) m.call(i);
28
+ }
29
+ finally { if (e) throw e.error; }
30
+ }
31
+ return ar;
32
+ };
33
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
34
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
35
+ if (ar || !(i in from)) {
36
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
37
+ ar[i] = from[i];
38
+ }
39
+ }
40
+ return to.concat(ar || Array.prototype.slice.call(from));
41
+ };
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ exports.IntlManagerReact = void 0;
44
+ var React = require("react");
45
+ var intl_manager_1 = require("./intl-manager");
46
+ var IntlManagerReact = /** @class */ (function (_super) {
47
+ __extends(IntlManagerReact, _super);
48
+ function IntlManagerReact() {
49
+ var _this = _super.apply(this, __spreadArray([], __read(arguments), false)) || this;
50
+ /**
51
+ * A reactive component form of `intl.format` that automatically updates when
52
+ * the application's locale changes and when new data is loaded for the
53
+ * subject message.
54
+ */
55
+ _this.IntlMessage = function (props) {
56
+ var message = props.message, values = props.values;
57
+ // Use the locale from this point in the application, which may be
58
+ // different from the global locale.
59
+ var locale = React.useSyncExternalStore(_this.onLocaleChange, function () { return _this.currentLocale; });
60
+ // Source the actual message to render for that locale from its loader.
61
+ // TODO(faulty): This can and should be replaced by a
62
+ // `use(messagesLoadedPromise)` once `use` is shipped to stable.
63
+ React.useSyncExternalStore(message.onChange, function () { return message(locale); });
64
+ // The return the formatted version of that string.
65
+ if (typeof message === 'string')
66
+ return message;
67
+ return React.createElement(React.Fragment, undefined, _this.formatToParts(message, values));
68
+ };
69
+ return _this;
70
+ }
71
+ IntlManagerReact.prototype.format = function (message, values) {
72
+ // A string literal means there's no locale dependency, so it can just be
73
+ // formatted directly without any subscription.
74
+ if (typeof message === 'string')
75
+ return this.formatToParts(message);
76
+ return React.createElement(this.IntlMessage, { message: message, values: values });
77
+ };
78
+ return IntlManagerReact;
79
+ }(intl_manager_1.IntlManager));
80
+ exports.IntlManagerReact = IntlManagerReact;
@@ -0,0 +1,158 @@
1
+ import type { InternalIntlMessage } from './message';
2
+ import type { IntlMessageGetterAdditions } from './message-loader';
3
+ /**
4
+ * Use this function to define messages as part of the `@discord/intl`
5
+ * system, with type checking support.
6
+ *
7
+ * This function does not actually exist, and gets compiled out as part
8
+ * of the bundling process both on web and mobile. See the
9
+ * `@discord/intl/README.md` for more information.
10
+ */
11
+ export declare function defineMessages(messages: object): object;
12
+
13
+ /**
14
+ * A map of tag names to render functions to apply when the tags are
15
+ * encountered while formatting a message. Each `IntlManager` can be
16
+ * constructed with a different map of these elements, for example to allow
17
+ * one manager to render React output while another renders accessibility text.
18
+ */
19
+ export type RichTextElementMap = Record<string, (chunks: any[]) => any>;
20
+
21
+ /**
22
+ * Messages with no rich formatting and no value interpolations are just plain
23
+ * strings, and are able to skip multiple formatting steps to work more
24
+ * efficiently. This type represents those messages distinctly.
25
+ */
26
+ type PlainIntlMessage = string;
27
+ /**
28
+ * A richly-formatted message that injects dynamic values while formatting,
29
+ * with the types of those values defined by the `FormatValues` object. All
30
+ * messages of this type must have at least one value in the object, otherwise
31
+ * the message should be typed as a `PlainIntlMessage`.
32
+ */
33
+ type TypedIntlMessage<FormatValues extends object> = InternalIntlMessage & {
34
+ values: FormatValues;
35
+ };
36
+
37
+ type AnyIntlMessage<FormatValues extends object | undefined = undefined> =
38
+ FormatValues extends object ? TypedIntlMessage<FormatValues> : PlainIntlMessage;
39
+
40
+ /**
41
+ * Getter function that retrieves a message that best matches the requested
42
+ * locale. `FormatValues` represents either an object containing all of the
43
+ * value types that are required to format the message, or `undefined` to
44
+ * represent that there are no values required and the message is a plain
45
+ * string.
46
+ */
47
+ export type TypedIntlMessageGetter<FormatValues extends object | undefined> =
48
+ IntlMessageGetterAdditions & {
49
+ (locale: string): AnyIntlMessage<FormatValues>;
50
+ };
51
+
52
+ /**
53
+ * Common utility type for converting a union type to an intersection of all
54
+ * the types in that union.
55
+ */
56
+ type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void
57
+ ? I
58
+ : never;
59
+
60
+ /**
61
+ * Given a message getter of type `T` (which may be a union of multiple message
62
+ * types), return a combined type representing the intersection of all values
63
+ * needed to satisfy the mix of values.
64
+ *
65
+ * For example, given a variable representing one of two message getters, where
66
+ * one requires a single value to be formatted, but the other requires an
67
+ * additional value on top of that one:
68
+ *
69
+ * ```typescript
70
+ * // message1: "Hello {username}"
71
+ * // message2: "Hello {username}, it's {currentDate, date}"
72
+ * const message = someCondition ? message1 || message2;
73
+ * ```
74
+ *
75
+ * Attempting to format this message would require that both `username` _and_
76
+ * `currentDate` are supplied, so that all possible cases are covered, which is
77
+ * what this type resolves:
78
+ *
79
+ * ```typescript
80
+ * type ResolvedValuesTypes = FormatValuesFor<typeof message>;
81
+ * // => {username: string, currentDate: Date}
82
+ * ```
83
+ *
84
+ * This values type can then be used to enforce that all values are passed to
85
+ * functions that format the message, like `i18n.format`:
86
+ *
87
+ * ```typescript
88
+ * declare function format<T>(message: T, values: FormatValuesFor<T>): string;
89
+ * ```
90
+ *
91
+ * ## Mixed message types
92
+ *
93
+ * If T is a union of message types including both rich messages requiring
94
+ * values _and_ plain string messages with no values, this type will still
95
+ * properly resolve all of the required values, assuming that the getters for
96
+ * the plain messages use the convention of passing `undefined` as the generic
97
+ * argument, representing "no values should be passed for this message".
98
+ *
99
+ *
100
+ * ## Why this type?
101
+ *
102
+ * All "messages" are really defined as functions that return a specific
103
+ * value of the message for a requested locale, `(locale: string) => Message`.
104
+ * This is named `TypedIntlMessageGetter<T>`, where `T` is the definition of
105
+ * the values required to format the message.
106
+ *
107
+ * This type requires being passed the entire getter function rather than the
108
+ * returned message types, as narrowing to the returned type prevents
109
+ * TypeScript from distributing the union properly. This surfaces as error
110
+ * messages showing up on the _message_ type rather than the _value_ type when
111
+ * they don't match, which doesn't give the user any helpful information about
112
+ * what's wrong. This method ensures the value type is always the one being
113
+ * checked against the provided message types, and error messages will be
114
+ * accurate as a result.
115
+ */
116
+ type FormatValuesFor<T> =
117
+ // `[]` syntax forces TypeScript to not distribute the possible union of
118
+ // types, meaning this represents "_all_ values of T are an extension of
119
+ // the parent type", which in this case represents messages with no
120
+ // format values. If that condition is matched, then `FormatValues` _cannot_
121
+ // be supplied, represented by the `never`. This prevents accidentally
122
+ // passing an object, even an empty one, for those strings, and allows the
123
+ // formatter to optimize a little more on each call.
124
+ [T] extends [TypedIntlMessageGetter<undefined>]
125
+ ? never
126
+ : // The `never` conditino needs to be repeated here, but this time _without_
127
+ // the `[]` syntax, so that only the union elements with actual values are
128
+ // included in the union. Without this, the `infer U` on the latter side
129
+ // fails to resolve and the type just becomes `never`.
130
+ UnionToIntersection<
131
+ T extends TypedIntlMessageGetter<undefined>
132
+ ? never
133
+ : T extends TypedIntlMessageGetter<infer U>
134
+ ? U
135
+ : never
136
+ >;
137
+
138
+ /**
139
+ * A generic definition of a `format*` function that accepts any message
140
+ * argument as the first parameter, and the required values object for that
141
+ * message as the second parameter. The second parameter
142
+ *
143
+ * An optional third parameter for additional options can be specified as the
144
+ * second generic type parameter.
145
+ */
146
+ export interface IntlFormatFn<
147
+ T extends object | undefined,
148
+ DefaultValues extends string | number | symbol,
149
+ OptionsType,
150
+ ResultType,
151
+ > {
152
+ (message: TypedIntlMessageGetter<T>, options?: OptionsType): ResultType;
153
+ (
154
+ message: TypedIntlMessageGetter<T>,
155
+ values: Omit<FormatValuesFor<T>, DefaultValues>,
156
+ options?: OptionsType,
157
+ ): ResultType;
158
+ }
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@discord/intl",
3
+ "version": "0.0.1",
4
+ "license": "MIT",
5
+ "description": "Client runtime for managing messages and translations in a React project.",
6
+ "main": "./src/index.ts",
7
+ "exports": {
8
+ ".": {
9
+ "default": "./dist/index.js",
10
+ "types": "./dist/index.d.ts"
11
+ }
12
+ },
13
+ "files": [
14
+ "dist",
15
+ "src"
16
+ ],
17
+ "dependencies": {
18
+ "@formatjs/icu-messageformat-parser": "^2.7.6",
19
+ "@formatjs/intl": "^2.10.1",
20
+ "intl-messageformat": "^10.5.11"
21
+ },
22
+ "devDependencies": {
23
+ "@swc/cli": "^0.3.12",
24
+ "@swc/core": "^1.4.11",
25
+ "typescript": "*"
26
+ },
27
+ "peerDependencies": {
28
+ "@rspack/core": "0.x || 1.x",
29
+ "webpack": "^5.0.0",
30
+ "react": "> 16"
31
+ },
32
+ "peerDependenciesMeta": {
33
+ "@rspack/core": {
34
+ "optional": true
35
+ },
36
+ "webpack": {
37
+ "optional": true
38
+ }
39
+ },
40
+ "scripts": {
41
+ "build": "tsc && cp -r ./src/*.d.ts dist/"
42
+ }
43
+ }
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export { IntlManager, createLoader, DEFAULT_LOCALE } from './intl-manager';
2
+ export { IntlManagerReact } from './react';
3
+
4
+ export type * from './types.d.ts';
@@ -0,0 +1,166 @@
1
+ import { IntlShape, createIntl } from '@formatjs/intl';
2
+ import {
3
+ PART_TYPE as FormatPartType,
4
+ Formats,
5
+ Formatters,
6
+ IntlMessageFormat,
7
+ } from 'intl-messageformat';
8
+
9
+ import { LocaleImportMap, MessageLoader } from './message-loader';
10
+
11
+ import type { TypedIntlMessageGetter, FormatValuesFor, RichTextElementMap } from './types';
12
+
13
+ /**
14
+ * Fallback locale used for all internationalization when an operation in the
15
+ * requested locale is not possible.
16
+ */
17
+ export const DEFAULT_LOCALE: string = 'en-US';
18
+
19
+ export class IntlManager<
20
+ DefaultElements extends RichTextElementMap,
21
+ DefaultValues extends keyof DefaultElements,
22
+ > {
23
+ defaultLocale: string;
24
+ currentLocale: string;
25
+ intl: IntlShape;
26
+
27
+ /**
28
+ * When formatting tag elements like `<b>value</b>`, these functions will
29
+ * automatically be used by all messages, unless they are explicitly removed
30
+ * for a specific call, either by overriding the value or by calling a
31
+ * dedicated method like `formatToPlainString` which will remove all
32
+ * rich elements from the string and return only the plain text result.
33
+ */
34
+ defaultRichTextElements: DefaultElements;
35
+
36
+ _localeSubscriptions: Set<(locale: string) => void>;
37
+
38
+ constructor(defaultLocale: string = DEFAULT_LOCALE, defaultRichTextElements: DefaultElements) {
39
+ this.defaultLocale = defaultLocale;
40
+ this.currentLocale = defaultLocale;
41
+ this.intl = createIntl({
42
+ formats: IntlMessageFormat.formats,
43
+ defaultLocale,
44
+ locale: defaultLocale,
45
+ });
46
+
47
+ this.defaultRichTextElements = defaultRichTextElements;
48
+ this._localeSubscriptions = new Set();
49
+ }
50
+
51
+ /**
52
+ * Set the locale for this package to use. This is a global change, and all
53
+ * methods on this object will use this locale for formatting.
54
+ */
55
+ setLocale(locale: string) {
56
+ this.currentLocale = locale;
57
+ this.intl = createIntl({ defaultLocale: this.defaultLocale, locale });
58
+ this.emitLocaleChange(locale);
59
+ }
60
+
61
+ emitLocaleChange(locale: string) {
62
+ for (const callback of this._localeSubscriptions) {
63
+ callback(locale);
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Subscribe to changes in the current locale for this manager.
69
+ */
70
+ onLocaleChange = (callback: (locale: string) => void) => {
71
+ this._localeSubscriptions.add(callback);
72
+ return () => this._localeSubscriptions.delete(callback);
73
+ };
74
+
75
+ /**
76
+ * Format the given message in the current locale with the provided values.
77
+ * The returned values is _always_ an Array of parts, even if the message is
78
+ * a simple string value.
79
+ *
80
+ * This function is the basis of how messages are normally formatted, and can
81
+ * be used anywhere. However, it is not reactive and only functions on the
82
+ * data that is currently loaded and known. For a reactive function that
83
+ * automatically updates when the locale changes or when new data is loaded,
84
+ * use `format`, which will wrap the formatting in a React component that
85
+ * subscribes to the current locale and state of loaded messages.
86
+ */
87
+ formatToParts<T extends TypedIntlMessageGetter<object | undefined>>(
88
+ message: T,
89
+ ): Array<string | any>;
90
+ formatToParts<T extends TypedIntlMessageGetter<object | undefined>>(
91
+ message: T,
92
+ values: Omit<FormatValuesFor<T>, DefaultValues>,
93
+ ): Array<string | any>;
94
+ formatToParts<T extends TypedIntlMessageGetter<object | undefined>>(
95
+ message: T,
96
+ values?: Omit<FormatValuesFor<T>, DefaultValues>,
97
+ ) {
98
+ if (typeof message === 'string') return [message];
99
+ const resolvedMessage = typeof message === 'function' ? message(this.currentLocale) : message;
100
+ if (typeof resolvedMessage === 'string') return [resolvedMessage];
101
+
102
+ const resolvedValues =
103
+ values != null
104
+ ? { ...this.defaultRichTextElements, ...values }
105
+ : this.defaultRichTextElements;
106
+ const parts = resolvedMessage.formatToParts(
107
+ this.intl.formatters as Formatters,
108
+ this.intl.formats as Formats,
109
+ resolvedValues,
110
+ );
111
+
112
+ const result = [];
113
+ let inLiteral = false;
114
+ for (const part of parts) {
115
+ // This condition merges consecutive literal elements (static strings)
116
+ // into single parts to reduce the number of nodes in the result. This
117
+ // condition will never be true on the first loop, ensuring that the
118
+ // array must at least have one entry before it attempts to concatenate
119
+ // onto that value again.
120
+ if (inLiteral && (inLiteral = part.type === FormatPartType.literal)) {
121
+ result[result.length - 1] += part.value;
122
+ continue;
123
+ }
124
+
125
+ inLiteral = part.type === FormatPartType.literal;
126
+ result.push(part.value);
127
+ }
128
+
129
+ return result;
130
+ }
131
+
132
+ /**
133
+ * Format the given message with the provided values, removing any styling
134
+ * and non-textual content from the message, returning a plain string.
135
+ */
136
+ formatToPlainString<T extends TypedIntlMessageGetter<object | undefined>>(message: T): string;
137
+ formatToPlainString<T extends TypedIntlMessageGetter<object | undefined>>(
138
+ message: T,
139
+ values: Omit<FormatValuesFor<T>, DefaultValues>,
140
+ ): string;
141
+ formatToPlainString<T extends TypedIntlMessageGetter<object | undefined>>(
142
+ message: T,
143
+ values?: Omit<FormatValuesFor<T>, DefaultValues>,
144
+ ) {
145
+ if (typeof message === 'string') return message;
146
+ const resolvedMessage = message(this.currentLocale);
147
+ if (typeof resolvedMessage === 'string') return resolvedMessage;
148
+
149
+ // No need to pass in `defaultRichTextElements`, since the stylistic tags
150
+ // will be removed from the string anyway.
151
+ return resolvedMessage.formatToPlainString(
152
+ this.intl.formatters as Formatters,
153
+ this.intl.formats as Formats,
154
+ values,
155
+ );
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Create a new MessageLoader, which handles lazily loading messages for
161
+ * different locales and sanity checks as needed to provide accessors for each
162
+ * message defined in `messageKeys`.
163
+ */
164
+ export function createLoader(messageKeys: string[], localeImportMap: LocaleImportMap) {
165
+ return new MessageLoader(messageKeys, localeImportMap);
166
+ }