@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,154 @@
1
+ export enum FormatJsNodeType {
2
+ Literal = 0,
3
+ Argument,
4
+ Number,
5
+ Date,
6
+ Time,
7
+ Select,
8
+ Plural,
9
+ Pound,
10
+ Tag,
11
+ }
12
+
13
+ export interface FormatJsLiteral {
14
+ type: FormatJsNodeType.Literal;
15
+ value: string;
16
+ }
17
+
18
+ export interface FormatJsArgument {
19
+ type: FormatJsNodeType.Argument;
20
+ value: string;
21
+ }
22
+
23
+ export interface FormatJsNumber {
24
+ type: FormatJsNodeType.Number;
25
+ value: string;
26
+ style?: string;
27
+ }
28
+
29
+ export interface FormatJsDate {
30
+ type: FormatJsNodeType.Date;
31
+ value: string;
32
+ style?: string;
33
+ }
34
+
35
+ export interface FormatJsTime {
36
+ type: FormatJsNodeType.Time;
37
+ value: string;
38
+ style?: string;
39
+ }
40
+
41
+ export type FormatJsPluralType = 'cardinal' | 'ordinal';
42
+
43
+ export interface FormatJsPlural {
44
+ type: FormatJsNodeType.Plural;
45
+ value: string;
46
+ options: Record<string, { value: FormatJsNode[] }>;
47
+ offset: number;
48
+ pluralType: FormatJsPluralType;
49
+ }
50
+
51
+ export interface FormatJsPound {
52
+ type: FormatJsNodeType.Pound;
53
+ }
54
+
55
+ export interface FormatJsTag {
56
+ type: FormatJsNodeType.Tag;
57
+ value: string;
58
+ children: FormatJsNode[];
59
+ }
60
+
61
+ export type FormatJsNode =
62
+ | FormatJsLiteral
63
+ | FormatJsArgument
64
+ | FormatJsNumber
65
+ | FormatJsDate
66
+ | FormatJsTime
67
+ | FormatJsPlural
68
+ | FormatJsPound
69
+ | FormatJsTag;
70
+
71
+ // Using a static object here ensures there's only one allocation for it, which
72
+ // will likely appear in many, many messages. Freezing ensures that the shared
73
+ // object can't be accidentally modified if a consumer tries to transform the
74
+ // AST.
75
+ export const FORMAT_JS_POUND: FormatJsPound = Object.freeze({ type: 7 });
76
+
77
+ function hydrateArray(elements: Array<Array<any>>) {
78
+ for (let i = 0; i < elements.length; i++) {
79
+ elements[i] = hydrateFormatJsAst(elements[i]);
80
+ }
81
+ }
82
+
83
+ function hydratePlural(keyless: Array<any>): FormatJsNode {
84
+ const [type, value, options, offset, pluralType] = keyless;
85
+ // This tries to be efficient about updating the value for each option
86
+ // with a parsed version, reusing the same receiving object, and even
87
+ // the inner key within it, just replacing the end value with the
88
+ // parsed version.
89
+ // This saves multiple allocations compared to building a new object
90
+ // from scratch, either for the whole options object or for each value.
91
+ for (const key in options) {
92
+ hydrateArray(options[key].value);
93
+ }
94
+ // `pluralType` is technically only valid on `Plural` nodes, even
95
+ // though the structure is identical to `Select`.
96
+ if (type === FormatJsNodeType.Plural) {
97
+ return { type, value, options, offset, pluralType };
98
+ } else {
99
+ return { type, value, options, offset };
100
+ }
101
+ }
102
+
103
+ function hydrateSingle(keyless: Array<any>): FormatJsNode {
104
+ const [type] = keyless;
105
+ switch (type) {
106
+ case FormatJsNodeType.Literal:
107
+ case FormatJsNodeType.Argument:
108
+ return { type, value: keyless[1] };
109
+ case FormatJsNodeType.Number:
110
+ case FormatJsNodeType.Date:
111
+ case FormatJsNodeType.Time:
112
+ return { type, value: keyless[1], style: keyless[2] };
113
+ case FormatJsNodeType.Select:
114
+ case FormatJsNodeType.Plural:
115
+ return hydratePlural(keyless);
116
+ case FormatJsNodeType.Pound:
117
+ return FORMAT_JS_POUND;
118
+ case FormatJsNodeType.Tag: {
119
+ const [type, value, children] = keyless;
120
+ hydrateArray(children);
121
+ return { type, value, children: children };
122
+ }
123
+ default:
124
+ throw new Error(`FormatJS keyless JSON encountered an unknown type: ${type}`);
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Hydrate the given keyless JSON into a FormatJS-compatible AST structure.
130
+ * The given object is reused as much as possible, meaning it cannot be safely
131
+ * used again after hydrating.
132
+ */
133
+ export function hydrateFormatJsAst(keyless: Array<Array<any>>): FormatJsNode[];
134
+ export function hydrateFormatJsAst(keyless: Array<any>): FormatJsNode | FormatJsNode[] {
135
+ // If the first element of the array is itself an array, then we have a list
136
+ // of elements to parse rather than a single object.
137
+ if (Array.isArray(keyless[0])) {
138
+ hydrateArray(keyless);
139
+ return keyless;
140
+ } else if (keyless.length === 0) {
141
+ // Some entries can be empty arrays, like an empty set of children, and those can
142
+ // be preserved.
143
+ return keyless;
144
+ }
145
+
146
+ return hydrateSingle(keyless);
147
+ }
148
+
149
+ export function hydrateMessages(messages: Record<string, any>) {
150
+ for (const key in messages) {
151
+ messages[key] = hydrateFormatJsAst(messages[key]);
152
+ }
153
+ return messages;
154
+ }
@@ -0,0 +1,156 @@
1
+ import { InternalIntlMessage } from './message';
2
+
3
+ import type { MessageFormatElement } from '@formatjs/icu-messageformat-parser';
4
+
5
+ type AnyRawMessage = string | MessageFormatElement[];
6
+ type MessagesData = Record<string, AnyRawMessage>;
7
+ export interface IntlMessageGetterAdditions {
8
+ isLocaleLoaded(locale: LocaleId): boolean;
9
+ onChange(callback: () => void): () => void;
10
+ }
11
+
12
+ export interface IntlMessageGetter extends IntlMessageGetterAdditions {
13
+ (locale: LocaleId): InternalIntlMessage;
14
+ }
15
+
16
+ type LocaleId = string;
17
+
18
+ export type LocaleImportMap = Record<LocaleId, () => Promise<{ default: MessagesData }>>;
19
+
20
+ export class MessageLoader {
21
+ messageKeys: string[];
22
+ /** Map of locale to string key to message content. */
23
+ messages: Record<LocaleId, MessagesData>;
24
+ localeImportMap: LocaleImportMap;
25
+ supportedLocales: LocaleId[];
26
+
27
+ /**
28
+ * Promise representing the current locale being loaded. If this is non-null,
29
+ * then a request has already been made to load the locale and it is waiting
30
+ * to resolve.
31
+ */
32
+ _localeLoadingPromises: Record<LocaleId, Promise<{ default: MessagesData }> | undefined>;
33
+
34
+ /**
35
+ * Map of pre-parsed messages, keyed by the message key and locale
36
+ */
37
+ _parseCache: Map<string, InternalIntlMessage>;
38
+
39
+ /**
40
+ * List of subscribers listening for changes to the current locale and
41
+ * when it has loaded.
42
+ */
43
+ _subscribers: Set<() => void>;
44
+
45
+ fallbackMessage: InternalIntlMessage;
46
+
47
+ constructor(messageKeys: string[], localeImportMap: LocaleImportMap) {
48
+ this.messageKeys = messageKeys;
49
+ this.messages = {};
50
+ this.localeImportMap = localeImportMap;
51
+ this.supportedLocales = Object.keys(localeImportMap);
52
+
53
+ this._localeLoadingPromises = {};
54
+ this._parseCache = new Map();
55
+ this._subscribers = new Set();
56
+
57
+ this.fallbackMessage = new InternalIntlMessage('THIS MESSAGE FAILED TO LOAD', 'en-US');
58
+ }
59
+
60
+ get(key: string, locale: LocaleId): InternalIntlMessage {
61
+ const cacheKey = key + '@' + locale;
62
+ const cachedValue = this._parseCache.get(cacheKey);
63
+ if (cachedValue != null) return cachedValue;
64
+
65
+ if (this.messages[locale] == null) {
66
+ // Ensure the locale is loaded, if it is supported
67
+ if (this.supportedLocales.includes(locale)) {
68
+ this._loadLocale(locale);
69
+ }
70
+
71
+ // Otherwise, fallback to just the key value in the meantime.
72
+ return this.fallbackMessage;
73
+ }
74
+
75
+ // Then try to return the loaded message
76
+ if (key in this.messages[locale]) {
77
+ const content = this.messages[locale][key];
78
+ const message = new InternalIntlMessage(content, locale);
79
+ this._parseCache.set(cacheKey, message);
80
+ return message;
81
+ }
82
+
83
+ // And finally throw if there's no match whatsoever
84
+ throw new Error(
85
+ `Requested message ${key} does not have a value in the requested locale nor the default locale`,
86
+ );
87
+ }
88
+
89
+ /**
90
+ * Returns a record mapping the keys this object manages to bound functions
91
+ * for `get` with the that key as the first argument, allowing consumers to
92
+ * just call the function with a locale to retrieve the translated message
93
+ * for that key.
94
+ */
95
+ getBinds(): Record<string, IntlMessageGetter> {
96
+ const onChange = this.onChange.bind(this);
97
+ return this.messageKeys.reduce(
98
+ (acc, key) => {
99
+ const bound = this.get.bind(this, key) as IntlMessageGetter;
100
+ bound.onChange = onChange;
101
+ acc[key] = bound;
102
+ return acc;
103
+ },
104
+ {} as Record<string, IntlMessageGetter>,
105
+ );
106
+ }
107
+
108
+ async _loadLocale(locale: LocaleId) {
109
+ // Don't re-load a locale that's already in progress.
110
+ if (this._localeLoadingPromises[locale] != null) {
111
+ return;
112
+ }
113
+
114
+ // Safety check in case the locale map doesn't include a require for the
115
+ // requested locale. Shouldn't happen, but throwing here is much more
116
+ // contextual than whatever error would result otherwise.
117
+ if (this.localeImportMap[locale] == null) {
118
+ throw new Error(
119
+ `Requested to load locale ${locale}, which should be supported, but no source for translation data was provided.`,
120
+ );
121
+ }
122
+
123
+ // If the locale is already set in `messages`, then it doesn't need to be loaded again.
124
+ if (this.messages[locale] != null) return;
125
+
126
+ const loadStart = performance.now();
127
+ const loadingPromise = this.localeImportMap[locale]();
128
+ this._localeLoadingPromises[locale] = loadingPromise;
129
+ this.messages[locale] = (await loadingPromise).default;
130
+ delete this._localeLoadingPromises[locale];
131
+ const loadEnd = performance.now();
132
+ console.warn(
133
+ `[INTL] Took ${loadEnd - loadStart}ms to load ${locale} with ${Object.keys(this.messages[locale]).length} keys`,
134
+ );
135
+ this.emitChange();
136
+ }
137
+
138
+ /**
139
+ * Inform subscribers that the loader state has changed and they should
140
+ * potentially update to get new values for messages.
141
+ */
142
+ emitChange() {
143
+ for (const callback of this._subscribers.values()) {
144
+ callback();
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Subscribe to events about when a locale has loaded.
150
+ */
151
+ onChange(callback: () => void): () => void {
152
+ this._subscribers.add(callback);
153
+
154
+ return () => this._subscribers.delete(callback);
155
+ }
156
+ }
package/src/message.ts ADDED
@@ -0,0 +1,110 @@
1
+ import {
2
+ TYPE as FormatElementType,
3
+ parse as parseMessageFormat,
4
+ } from '@formatjs/icu-messageformat-parser';
5
+ import {
6
+ Formats,
7
+ Formatters,
8
+ MessageFormatPart,
9
+ formatToParts as libFormatToParts,
10
+ } from 'intl-messageformat';
11
+
12
+ import type { MessageFormatElement } from '@formatjs/icu-messageformat-parser';
13
+
14
+ export class InternalIntlMessage {
15
+ locale: string;
16
+ message?: string;
17
+ ast: MessageFormatElement[];
18
+ /**
19
+ * A stripped-down representation of the message with no rich formatting
20
+ * elements preserved. This will be lazily created the first time it is
21
+ * requested (e.g., by calling `formatToPlainString`).
22
+ */
23
+ plainAst?: MessageFormatElement[];
24
+
25
+ constructor(messageOrAst: string | MessageFormatElement[], locale: string) {
26
+ this.locale = locale;
27
+
28
+ if (typeof messageOrAst === 'string') {
29
+ // TODO: This part should pre-parse our Markdown shorthand, too, so that
30
+ // the AST that comes out of this is a standardized format, and we don't
31
+ // have to do any special handling after calling `format`. That also
32
+ // removes the need for handling "unsafe" values, since they'll be
33
+ // parsed before values get interpolated. We can actually do that in the
34
+ // loader, too, and avoid any runtime cost for it.
35
+
36
+ this.message = messageOrAst;
37
+ this.ast = parseMessageFormat(messageOrAst);
38
+ } else {
39
+ this.ast = messageOrAst;
40
+ }
41
+
42
+ this.plainAst = undefined;
43
+ }
44
+
45
+ /**
46
+ * Format this message with the given locale into an AST of chunks that can
47
+ * then be used or rendered into strings, React elements, or anything else as
48
+ * needed.
49
+ */
50
+ formatToParts<T>(
51
+ formatters: Formatters,
52
+ formats: Formats,
53
+ values?: Record<string, any>,
54
+ ): Array<MessageFormatPart<T>> {
55
+ return libFormatToParts(this.ast, this.locale, formatters, formats, values);
56
+ }
57
+
58
+ /**
59
+ * Formats this message just like `formatToParts`, but strips all stylistic
60
+ * elements and tags and returns a plain string with only the literal text of
61
+ * the message remaining.
62
+ *
63
+ * This method works by quickly scanning the string and removing any tag
64
+ * elements, formatting, then concatenating the pieces into a single result.
65
+ *
66
+ * Note that this function does _not_ handle serializing values other than
67
+ * plain/primitive values into strings. All values are simply converted using
68
+ * the `String()` constructor. So, for example, passing a function as a value
69
+ * will directly serialize the function definition into a string, rather than
70
+ * calling the function or evaluating the result.
71
+ */
72
+ formatToPlainString(
73
+ formatters: Formatters,
74
+ formats: Formats,
75
+ values?: Record<string, any>,
76
+ ): string {
77
+ // Lazily create the reduced AST if it hasn't already been done.
78
+ if (this.plainAst == null) {
79
+ this.plainAst = [];
80
+ for (const part of this.ast) {
81
+ this.plainAst.push(...this._removeRichTags(part));
82
+ }
83
+ }
84
+
85
+ const parts = libFormatToParts(this.plainAst, this.locale, formatters, formats, values);
86
+ let result = '';
87
+ for (const part of parts) {
88
+ result += String(part.value);
89
+ }
90
+
91
+ return result;
92
+ }
93
+
94
+ /**
95
+ * Returns the same element with any formatting tags removed. If the element
96
+ * itself is a tag, the children will be hoisted and returned, otherwise the
97
+ * element is returned as an array of itself.
98
+ */
99
+ _removeRichTags(element: MessageFormatElement): MessageFormatElement[] {
100
+ if (element.type === FormatElementType.tag) {
101
+ const childContent = [];
102
+ for (const child of element.children) {
103
+ childContent.push(...this._removeRichTags(child));
104
+ }
105
+ return childContent;
106
+ } else {
107
+ return [element];
108
+ }
109
+ }
110
+ }
package/src/react.ts ADDED
@@ -0,0 +1,59 @@
1
+ import * as React from 'react';
2
+
3
+ import { IntlManager } from './intl-manager';
4
+
5
+ import type { FormatValuesFor, RichTextElementMap, TypedIntlMessageGetter } from './types';
6
+
7
+ export class IntlManagerReact<
8
+ DefaultElements extends RichTextElementMap,
9
+ DefaultValues extends keyof DefaultElements,
10
+ > extends IntlManager<DefaultElements, DefaultValues> {
11
+ /**
12
+ * A reactive component form of `intl.format` that automatically updates when
13
+ * the application's locale changes and when new data is loaded for the
14
+ * subject message.
15
+ */
16
+ IntlMessage = <T extends TypedIntlMessageGetter<object | undefined>>(props: {
17
+ message: T;
18
+ values?: Omit<FormatValuesFor<T>, DefaultValues>;
19
+ }) => {
20
+ const { message, values } = props;
21
+ // Use the locale from this point in the application, which may be
22
+ // different from the global locale.
23
+ const locale = React.useSyncExternalStore(this.onLocaleChange, () => this.currentLocale);
24
+ // Source the actual message to render for that locale from its loader.
25
+ // TODO(faulty): This can and should be replaced by a
26
+ // `use(messagesLoadedPromise)` once `use` is shipped to stable.
27
+ React.useSyncExternalStore(message.onChange, () => message(locale));
28
+ // The return the formatted version of that string.
29
+ if (typeof message === 'string') return message;
30
+
31
+ return React.createElement(
32
+ React.Fragment,
33
+ undefined,
34
+ this.formatToParts(message, values as Omit<FormatValuesFor<T>, DefaultValues> | never),
35
+ );
36
+ };
37
+
38
+ /**
39
+ * Format the given message as a React component, allowing it to listen for
40
+ * and respond to updates about the current locale and other relevant
41
+ * information.
42
+ */
43
+ format<T extends TypedIntlMessageGetter<object | undefined>>(message: T): JSX.Element;
44
+ format<T extends TypedIntlMessageGetter<object | undefined>>(
45
+ message: T,
46
+ values: Omit<FormatValuesFor<T>, DefaultValues>,
47
+ ): JSX.Element;
48
+ format(message: any): JSX.Element;
49
+ format<T extends TypedIntlMessageGetter<object | undefined>>(
50
+ message: T,
51
+ values?: Omit<FormatValuesFor<T>, DefaultValues>,
52
+ ) {
53
+ // A string literal means there's no locale dependency, so it can just be
54
+ // formatted directly without any subscription.
55
+ if (typeof message === 'string') return this.formatToParts(message);
56
+
57
+ return React.createElement(this.IntlMessage, { message, values });
58
+ }
59
+ }
package/src/types.d.ts ADDED
@@ -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
+ }