@discord/intl 0.17.0 → 0.18.0-canary.40cad9d

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,21 @@
1
+ import type { DurationFormat as FormatJsDurationFormat } from '@formatjs/intl-durationformat';
2
+ type Cache<Value> = Map<string, Value>;
3
+ type TemporaryIntlDurationFormat = typeof FormatJsDurationFormat;
4
+ declare class FormatterCache {
5
+ dateTime: Cache<Intl.DateTimeFormat>;
6
+ duration: Cache<TemporaryIntlDurationFormat>;
7
+ list: Cache<Intl.ListFormat>;
8
+ number: Cache<Intl.NumberFormat>;
9
+ pluralRules: Cache<Intl.PluralRules>;
10
+ relativeTime: Cache<Intl.RelativeTimeFormat>;
11
+ getDateTimeFormatter(...args: ConstructorParameters<typeof Intl.DateTimeFormat>): Intl.DateTimeFormat;
12
+ getDurationFormatter(...args: ConstructorParameters<TemporaryIntlDurationFormat>): any;
13
+ getListFormatter(...args: ConstructorParameters<typeof Intl.ListFormat>): Intl.ListFormat;
14
+ getNumberFormatter(...args: ConstructorParameters<typeof Intl.NumberFormat>): Intl.NumberFormat;
15
+ getPluralRules(...args: ConstructorParameters<typeof Intl.PluralRules>): Intl.PluralRules;
16
+ getRelativeTimeFormatter(...args: ConstructorParameters<typeof Intl.RelativeTimeFormat>): Intl.RelativeTimeFormat;
17
+ _getCached<T, Args>(cache: Cache<T>, args: Args, constructor: (args: Args) => T): T;
18
+ _getKey(...args: any): string;
19
+ }
20
+ export declare const dataFormatterCache: FormatterCache;
21
+ export {};
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.dataFormatterCache = void 0;
4
+ class FormatterCache {
5
+ constructor() {
6
+ this.dateTime = new Map();
7
+ this.duration = new Map();
8
+ this.list = new Map();
9
+ this.number = new Map();
10
+ this.pluralRules = new Map();
11
+ this.relativeTime = new Map();
12
+ }
13
+ getDateTimeFormatter(...args) {
14
+ return this._getCached(this.dateTime, args, (args) => new Intl.DateTimeFormat(...args));
15
+ }
16
+ getDurationFormatter(...args) {
17
+ return this._getCached(this.duration, args,
18
+ // @ts-expect-error DurationFormat is _not_ included in typescript
19
+ // https://github.com/microsoft/TypeScript/issues/60608
20
+ (args) => new Intl.DurationFormat(...args));
21
+ }
22
+ getListFormatter(...args) {
23
+ return this._getCached(this.list, args, (args) => new Intl.ListFormat(...args));
24
+ }
25
+ getNumberFormatter(...args) {
26
+ return this._getCached(this.number, args, (args) => new Intl.NumberFormat(...args));
27
+ }
28
+ getPluralRules(...args) {
29
+ return this._getCached(this.pluralRules, args, (args) => new Intl.PluralRules(...args));
30
+ }
31
+ getRelativeTimeFormatter(...args) {
32
+ return this._getCached(this.relativeTime, args, (args) => new Intl.RelativeTimeFormat(...args));
33
+ }
34
+ _getCached(cache, args, constructor) {
35
+ const key = this._getKey(args);
36
+ const cached = cache.get(key);
37
+ if (cached)
38
+ return cached;
39
+ const created = constructor(args);
40
+ cache.set(key, created);
41
+ return created;
42
+ }
43
+ _getKey(...args) {
44
+ return JSON.stringify(args);
45
+ }
46
+ }
47
+ exports.dataFormatterCache = new FormatterCache();
@@ -0,0 +1,81 @@
1
+ import type { DurationFormatOptions, DurationInput } from '@formatjs/intl-durationformat/src/types';
2
+ export type TemporaryDurationInput = DurationInput;
3
+ export type TemporaryDurationFormatOptions = DurationFormatOptions;
4
+ export interface FormatConfigType {
5
+ date: Record<string, Intl.DateTimeFormatOptions>;
6
+ duration: Record<string, TemporaryDurationFormatOptions>;
7
+ list: Record<string, Intl.ListFormatOptions>;
8
+ number: Record<string, Intl.NumberFormatOptions>;
9
+ relativeTime: Record<string, Intl.RelativeTimeFormatOptions>;
10
+ time: Record<string, Intl.DateTimeFormatOptions>;
11
+ }
12
+ export declare function resolveFormatConfigOptions<T extends object, const K extends string>(config: Record<K, T>, style?: T & {
13
+ format?: K;
14
+ }): T;
15
+ export declare const DEFAULT_FORMAT_CONFIG: {
16
+ duration: {};
17
+ list: {};
18
+ relativeTime: {};
19
+ /**
20
+ * Default formatting configuration options for common date, time, and number formats. This is
21
+ * taken almost directly from FormatJS's defaults here, for the sake of compatibility.
22
+ * https://github.com/formatjs/formatjs/blob/c30975bfbe2db7eb62f4dbe6c8ad6ca5e786dcb3/packages/intl-messageformat/src/core.ts#L229-L296
23
+ */
24
+ number: {
25
+ integer: {
26
+ maximumFractionDigits: number;
27
+ };
28
+ currency: {
29
+ style: "currency";
30
+ };
31
+ percent: {
32
+ style: "percent";
33
+ };
34
+ };
35
+ date: {
36
+ short: {
37
+ month: "numeric";
38
+ day: "numeric";
39
+ year: "2-digit";
40
+ };
41
+ medium: {
42
+ month: "short";
43
+ day: "numeric";
44
+ year: "numeric";
45
+ };
46
+ long: {
47
+ month: "long";
48
+ day: "numeric";
49
+ year: "numeric";
50
+ };
51
+ full: {
52
+ weekday: "long";
53
+ month: "long";
54
+ day: "numeric";
55
+ year: "numeric";
56
+ };
57
+ };
58
+ time: {
59
+ short: {
60
+ hour: "numeric";
61
+ minute: "numeric";
62
+ };
63
+ medium: {
64
+ hour: "numeric";
65
+ minute: "numeric";
66
+ second: "numeric";
67
+ };
68
+ long: {
69
+ hour: "numeric";
70
+ minute: "numeric";
71
+ second: "numeric";
72
+ timeZoneName: "short";
73
+ };
74
+ full: {
75
+ hour: "numeric";
76
+ minute: "numeric";
77
+ second: "numeric";
78
+ timeZoneName: "short";
79
+ };
80
+ };
81
+ };
@@ -1,12 +1,23 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DEFAULT_FORMAT_CONFIG = void 0;
4
- /**
5
- * Default formatting configuration options for common date, time, and number formats. This is
6
- * taken almost directly from FormatJS's defaults here, for the sake of compatibility.
7
- * https://github.com/formatjs/formatjs/blob/c30975bfbe2db7eb62f4dbe6c8ad6ca5e786dcb3/packages/intl-messageformat/src/core.ts#L229-L296
8
- */
4
+ exports.resolveFormatConfigOptions = resolveFormatConfigOptions;
5
+ function resolveFormatConfigOptions(config, style) {
6
+ if (typeof (style === null || style === void 0 ? void 0 : style.format) === 'string') {
7
+ return Object.assign(Object.assign({}, config[style.format]), style);
8
+ }
9
+ return style;
10
+ }
9
11
  exports.DEFAULT_FORMAT_CONFIG = {
12
+ // These have no known defaults, so they can't be applied easily.
13
+ duration: {},
14
+ list: {},
15
+ relativeTime: {},
16
+ /**
17
+ * Default formatting configuration options for common date, time, and number formats. This is
18
+ * taken almost directly from FormatJS's defaults here, for the sake of compatibility.
19
+ * https://github.com/formatjs/formatjs/blob/c30975bfbe2db7eb62f4dbe6c8ad6ca5e786dcb3/packages/intl-messageformat/src/core.ts#L229-L296
20
+ */
10
21
  number: {
11
22
  integer: { maximumFractionDigits: 0 },
12
23
  currency: { style: 'currency' },
@@ -0,0 +1,32 @@
1
+ import { FormatConfigType, TemporaryDurationFormatOptions, TemporaryDurationInput } from './config';
2
+ export interface DataFormatters<FormatConfig extends FormatConfigType> {
3
+ formatDate(value: number | Date, style?: Intl.DateTimeFormatOptions | {
4
+ format?: keyof FormatConfig['date'] & string;
5
+ }): string;
6
+ formatDuration(value: TemporaryDurationInput, style?: TemporaryDurationFormatOptions | {
7
+ format?: keyof FormatConfig['duration'] & string;
8
+ }): string;
9
+ formatNumber(value: number, style?: Intl.NumberFormatOptions | {
10
+ format?: keyof FormatConfig['number'] & string;
11
+ }): string;
12
+ formatList(values: any[], style?: Intl.ListFormatOptions | {
13
+ format?: keyof FormatConfig['list'] & string;
14
+ }): string;
15
+ formatListToParts<T>(values: T[], style?: Intl.ListFormatOptions | {
16
+ format?: keyof FormatConfig['list'] & string;
17
+ }): Array<T | string>;
18
+ formatRelativeTime(value: number, unit: Intl.RelativeTimeFormatUnit, style?: Intl.RelativeTimeFormatOptions | {
19
+ format?: keyof FormatConfig['relativeTime'] & string;
20
+ }): string;
21
+ formatTime(value: number | Date, style?: Intl.DateTimeFormatOptions | {
22
+ format?: keyof FormatConfig['time'] & string;
23
+ }): string;
24
+ getPluralRules(options: Intl.PluralRulesOptions): Intl.PluralRules;
25
+ }
26
+ export declare function makeDataFormatters<const FormatConfig extends FormatConfigType>(locales: string[], formatConfig: FormatConfig,
27
+ /**
28
+ * The FormatJs Polyfill for locale-matcher has really poor performance when
29
+ * using the BestFitMatcher. When this value is set, the matcher will be
30
+ * forced to use the LookupMatcher instead, which is much more performant.
31
+ */
32
+ forceLookupMatch?: boolean): DataFormatters<FormatConfig>;
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.makeDataFormatters = makeDataFormatters;
4
+ const config_1 = require("./config");
5
+ const cache_1 = require("./cache");
6
+ function makeDataFormatters(locales, formatConfig,
7
+ /**
8
+ * The FormatJs Polyfill for locale-matcher has really poor performance when
9
+ * using the BestFitMatcher. When this value is set, the matcher will be
10
+ * forced to use the LookupMatcher instead, which is much more performant.
11
+ */
12
+ forceLookupMatch = false) {
13
+ function optionsWithLocaleMatcher(options) {
14
+ return forceLookupMatch ? Object.assign(Object.assign({}, options), { localeMatcher: 'lookup' }) : options;
15
+ }
16
+ return {
17
+ formatDate(value, style) {
18
+ const options = (0, config_1.resolveFormatConfigOptions)(formatConfig.date, style);
19
+ return cache_1.dataFormatterCache
20
+ .getDateTimeFormatter(locales, optionsWithLocaleMatcher(options))
21
+ .format(value);
22
+ },
23
+ formatDuration(value, style) {
24
+ const options = (0, config_1.resolveFormatConfigOptions)(formatConfig.time, style);
25
+ return cache_1.dataFormatterCache
26
+ .getDurationFormatter(locales, optionsWithLocaleMatcher(options))
27
+ .format(value);
28
+ },
29
+ formatNumber(value, style) {
30
+ const options = (0, config_1.resolveFormatConfigOptions)(formatConfig.number, style);
31
+ return cache_1.dataFormatterCache
32
+ .getNumberFormatter(locales, optionsWithLocaleMatcher(options))
33
+ .format(value);
34
+ },
35
+ formatList(values, style) {
36
+ const options = (0, config_1.resolveFormatConfigOptions)(formatConfig.list, style);
37
+ return cache_1.dataFormatterCache
38
+ .getListFormatter(locales, optionsWithLocaleMatcher(options))
39
+ .format(values);
40
+ },
41
+ formatListToParts(values, style) {
42
+ const options = (0, config_1.resolveFormatConfigOptions)(formatConfig.list, style);
43
+ // Intl.ListFormat only accepts string arguments, even for `formatToParts`,
44
+ // but we want to support formatting of complex values like React nodes as
45
+ // part of a list (think an "and more" link at the end of a list).
46
+ //
47
+ // To enable that, we make placeholders with hopefully-unusable sentinel
48
+ // values that can be passed to `formatToParts`, then after the formatting
49
+ // we map the original values back into the parts.
50
+ const placeholders = {};
51
+ for (const index in values) {
52
+ placeholders['$+/-$placeholder.' + index] = values[index];
53
+ }
54
+ const parts = cache_1.dataFormatterCache
55
+ .getListFormatter(locales, optionsWithLocaleMatcher(options))
56
+ .formatToParts(Object.keys(placeholders));
57
+ return parts.map((part) => { var _a; return (part.value = (_a = placeholders[part.value]) !== null && _a !== void 0 ? _a : part.value); });
58
+ },
59
+ formatRelativeTime(value, unit, style) {
60
+ const options = (0, config_1.resolveFormatConfigOptions)(formatConfig.relativeTime, style);
61
+ return cache_1.dataFormatterCache
62
+ .getRelativeTimeFormatter(locales, optionsWithLocaleMatcher(options))
63
+ .format(value, unit);
64
+ },
65
+ formatTime(value, style) {
66
+ const options = (0, config_1.resolveFormatConfigOptions)(formatConfig.time, style);
67
+ return cache_1.dataFormatterCache
68
+ .getDateTimeFormatter(locales, optionsWithLocaleMatcher(options))
69
+ .format(value);
70
+ },
71
+ getPluralRules(options) {
72
+ return cache_1.dataFormatterCache.getPluralRules(locales, optionsWithLocaleMatcher(options));
73
+ },
74
+ };
75
+ }
package/dist/format.d.ts CHANGED
@@ -17,9 +17,9 @@
17
17
  * parameter, important to have React treat the resulting elements nicely.
18
18
  */
19
19
  import { AstNode } from '@discord/intl-ast';
20
- import { Formatters } from 'intl-messageformat';
21
- import type { FormatConfig } from './format-config';
22
20
  import type { RichTextTagNames } from './types';
21
+ import { FormatConfigType } from './data-formatters/config';
22
+ import { DataFormatters } from './data-formatters';
23
23
  export declare abstract class FormatBuilder<Result, ObjectType = Result extends object ? Result : never> {
24
24
  abstract pushRichTextTag(tag: RichTextTagNames, children: Result[], control: Result[]): void;
25
25
  abstract pushLiteralText(text: string): void;
@@ -27,5 +27,5 @@ export declare abstract class FormatBuilder<Result, ObjectType = Result extends
27
27
  abstract finish(): Result[];
28
28
  }
29
29
  export type FormatBuilderConstructor<Result> = new () => FormatBuilder<Result>;
30
- export declare function bindFormatValuesWithBuilder<T, ObjectType, Builder extends FormatBuilder<T, ObjectType>>(builder: Builder, nodes: AstNode[], locales: string | string[], formatters: Formatters, formatConfig: FormatConfig, values?: Record<string, string | object>, currentPluralValue?: number, originalMessage?: string): void;
31
- export declare function bindFormatValues<Result>(Builder: FormatBuilderConstructor<Result>, nodes: string | AstNode[], locales: string | string[], formatters: Formatters, formatConfig: FormatConfig, values?: Record<string, string | object>, currentPluralValue?: number): Result[];
30
+ export declare function bindFormatValuesWithBuilder<T, ObjectType, Builder extends FormatBuilder<T, ObjectType>, FormatConfig extends FormatConfigType>(builder: Builder, nodes: AstNode[], locales: string | string[], formatters: DataFormatters<FormatConfig>, formatConfig: FormatConfig, values?: Record<string, string | object>, currentPluralValue?: number, originalMessage?: string): void;
31
+ export declare function bindFormatValues<Result, FormatConfig extends FormatConfigType>(Builder: FormatBuilderConstructor<Result>, nodes: string | AstNode[], locales: string | string[], dataFormatters: DataFormatters<FormatConfig>, formatConfig: FormatConfig, values?: Record<string, string | object>, currentPluralValue?: number): Result[];
package/dist/format.js CHANGED
@@ -23,7 +23,6 @@ exports.bindFormatValuesWithBuilder = bindFormatValuesWithBuilder;
23
23
  exports.bindFormatValues = bindFormatValues;
24
24
  const icu_skeleton_parser_1 = require("@formatjs/icu-skeleton-parser");
25
25
  const intl_ast_1 = require("@discord/intl-ast");
26
- const intl_messageformat_1 = require("intl-messageformat");
27
26
  /**
28
27
  * Returns true if the tag name should be considered a rich text tag that
29
28
  * applies formatting to a message, rather than being a user-supplied value.
@@ -34,6 +33,14 @@ function isRichTextTag(name) {
34
33
  class FormatBuilder {
35
34
  }
36
35
  exports.FormatBuilder = FormatBuilder;
36
+ class MissingValueError extends Error {
37
+ constructor(variableName, originalMessage, nodeType) {
38
+ super(`No value for variable '${variableName}' was provided for the localized message '${originalMessage}'`);
39
+ this.variableName = variableName;
40
+ this.originalMessage = originalMessage;
41
+ this.nodeType = nodeType;
42
+ }
43
+ }
37
44
  function bindFormatValuesWithBuilder(builder, nodes, locales, formatters, formatConfig, values = {}, currentPluralValue, originalMessage) {
38
45
  var _a;
39
46
  // Hot path for static messages that are just parsed as a single string element.
@@ -41,6 +48,7 @@ function bindFormatValuesWithBuilder(builder, nodes, locales, formatters, format
41
48
  builder.pushLiteralText(nodes[0]);
42
49
  return;
43
50
  }
51
+ let keyIndex = 0;
44
52
  for (const node of nodes) {
45
53
  if (typeof node === 'string') {
46
54
  builder.pushLiteralText(node);
@@ -53,7 +61,7 @@ function bindFormatValuesWithBuilder(builder, nodes, locales, formatters, format
53
61
  // numeric values are replaced, otherwise the value is completed ignored?
54
62
  // Behavior copied from FormatJS directly.
55
63
  if (typeof currentPluralValue === 'number') {
56
- const value = formatters.getNumberFormat(locales).format(currentPluralValue);
64
+ const value = formatters.formatNumber(currentPluralValue);
57
65
  builder.pushLiteralText(value);
58
66
  }
59
67
  continue;
@@ -62,7 +70,7 @@ function bindFormatValuesWithBuilder(builder, nodes, locales, formatters, format
62
70
  // Enforce that all required values are provided by the caller, even if the
63
71
  // actual value is falsy/undefined.
64
72
  if (!(variableName in values) && !isRichTextTag(variableName)) {
65
- throw new intl_messageformat_1.MissingValueError(variableName, originalMessage);
73
+ throw new MissingValueError(variableName, originalMessage, nodeType);
66
74
  }
67
75
  const value = values[variableName];
68
76
  switch (nodeType) {
@@ -91,9 +99,8 @@ function bindFormatValuesWithBuilder(builder, nodes, locales, formatters, format
91
99
  ? formatConfig.date[nodeStyle]
92
100
  : nodeStyle != null
93
101
  ? (0, icu_skeleton_parser_1.parseDateTimeSkeleton)(nodeStyle)
94
- : formatConfig.time.medium;
95
- // @ts-expect-error Cast string values to dates properly.
96
- builder.pushLiteralText(formatters.getDateTimeFormat(locales, style).format(value));
102
+ : undefined;
103
+ builder.pushLiteralText(formatters.formatDate(value, style));
97
104
  break;
98
105
  }
99
106
  case intl_ast_1.FormatJsNodeType.Time: {
@@ -104,10 +111,8 @@ function bindFormatValuesWithBuilder(builder, nodes, locales, formatters, format
104
111
  ? formatConfig.time[nodeStyle]
105
112
  : nodeStyle != null
106
113
  ? (0, icu_skeleton_parser_1.parseDateTimeSkeleton)(nodeStyle)
107
- : undefined; // TODO: parseSkeleton();
108
- builder.pushLiteralText(
109
- // @ts-expect-error Cast string values to dates properly.
110
- formatters.getDateTimeFormat(locales, style).format(value));
114
+ : undefined;
115
+ builder.pushLiteralText(formatters.formatTime(value, style));
111
116
  break;
112
117
  }
113
118
  case intl_ast_1.FormatJsNodeType.Number: {
@@ -122,7 +127,7 @@ function bindFormatValuesWithBuilder(builder, nodes, locales, formatters, format
122
127
  const scaledValue =
123
128
  // @ts-expect-error This is a weird cast that's not accurate, but works in the short term.
124
129
  typeof value !== 'number' ? value : value * ((_a = style === null || style === void 0 ? void 0 : style.scale) !== null && _a !== void 0 ? _a : 1);
125
- builder.pushLiteralText(formatters.getNumberFormat(locales, style).format(scaledValue));
130
+ builder.pushLiteralText(formatters.formatNumber(scaledValue, style));
126
131
  break;
127
132
  }
128
133
  case intl_ast_1.FormatJsNodeType.Tag: {
@@ -139,7 +144,7 @@ function bindFormatValuesWithBuilder(builder, nodes, locales, formatters, format
139
144
  if (typeof value !== 'function') {
140
145
  throw `expected a function type for a Tag formatting value, ${variableName}. got ${typeof value}: ${value}`;
141
146
  }
142
- let chunks = value(appliedChildren);
147
+ let chunks = value(appliedChildren, `${keyIndex++}`);
143
148
  chunks = Array.isArray(chunks) ? chunks : [chunks];
144
149
  for (const chunk of chunks) {
145
150
  if (typeof chunk === 'string') {
@@ -171,10 +176,9 @@ function bindFormatValuesWithBuilder(builder, nodes, locales, formatters, format
171
176
  const exactSelector = `=${value}`;
172
177
  if (exactSelector in options)
173
178
  return options[exactSelector];
174
- const rule = formatters
175
- .getPluralRules(locales, { type: pluralType })
176
- // @ts-expect-error Assert this `as number` properly.
177
- .select(value - (offset !== null && offset !== void 0 ? offset : 0));
179
+ const rule = formatters.getPluralRules({ type: pluralType }).select(
180
+ // @ts-expect-error Assert this `as number` properly.
181
+ value - (offset !== null && offset !== void 0 ? offset : 0));
178
182
  return (_a = options[rule]) !== null && _a !== void 0 ? _a : options.other;
179
183
  })();
180
184
  if (option == null) {
@@ -188,14 +192,14 @@ function bindFormatValuesWithBuilder(builder, nodes, locales, formatters, format
188
192
  }
189
193
  }
190
194
  }
191
- function bindFormatValues(Builder, nodes, locales, formatters, formatConfig, values = {}, currentPluralValue) {
195
+ function bindFormatValues(Builder, nodes, locales, dataFormatters, formatConfig, values = {}, currentPluralValue) {
192
196
  const builder = new Builder();
193
197
  if (typeof nodes === 'string') {
194
198
  builder.pushLiteralText(nodes);
195
199
  return builder.finish();
196
200
  }
197
201
  else {
198
- bindFormatValuesWithBuilder(builder, nodes, locales, formatters, formatConfig, values, currentPluralValue);
202
+ bindFormatValuesWithBuilder(builder, nodes, locales, dataFormatters, formatConfig, values, currentPluralValue);
199
203
  return builder.finish();
200
204
  }
201
205
  }
@@ -1,4 +1,4 @@
1
1
  export { astFormatter, type AstFunctionTypes, type RichTextNode, RichTextNodeType } from './ast';
2
2
  export { markdownFormatter, type MarkdownFunctionTypes } from './markdown';
3
- export { reactFormatter, makeReactFormatter, DEFAULT_REACT_RICH_TEXT_ELEMENTS, type ReactFunctionTypes, ReactIntlMessage, } from './react';
3
+ export { reactFormatter, makeReactFormatter, DEFAULT_REACT_RICH_TEXT_ELEMENTS, type ReactFunctionTypes, ReactIntlMessage, ReactIntlPlainString, ReactIntlRichText, } from './react';
4
4
  export { stringFormatter, type StringFunctionTypes } from './string';
@@ -18,7 +18,36 @@ 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 type ReactIntlMessage = Array<React.ReactElement | string> & {
21
+ /**
22
+ * A type representing any message that has been formatted using one of the
23
+ * default React formatting methods in the `@discord/intl` system. Use this
24
+ * type when you don't care about whether a message is static or contains rich
25
+ * text or has dynamically-formatted values, when you need to handle both in
26
+ * tandem, or when you need a generic return type for a function returning "any
27
+ * already-formatted React message".
28
+ *
29
+ * Generally, this is like `React.ReactNode`, accepting both strings and rich
30
+ * `ReactElement`s, but it is more specific to disallow nullish values and
31
+ * other special React elements like `ReactPortal`. Seeing this type, you can
32
+ * be confident that the value is intended to be the result of formatting an
33
+ * intl message, even if the actual value comes from elsewhere (like a
34
+ * user-generated string).
35
+ */
36
+ export type ReactIntlMessage = string | ReactIntlRichText;
37
+ /**
38
+ * A branded type representing a plain string that has been rendered by the React formatter. This
39
+ * type should generally _not_ be used as a type constraint unless _absolute certainty_ that a
40
+ * message was formatted is desirable. Instead, accept plain `string`, and this will be compatible.
41
+ */
42
+ export type ReactIntlPlainString = string & {
43
+ __brand: 'discord-intl';
44
+ };
45
+ /**
46
+ * While `ReactIntlMessage` represents the result of rendering _any_ message with the React
47
+ * formatter, `ReactIntlRichText` specifically represents a message that was rendered and contains
48
+ * rich text, meaning the result contains React nodes itself and represents a CST of the message.
49
+ */
50
+ export type ReactIntlRichText = Array<React.ReactElement | string> & {
22
51
  __brand: 'discord-intl';
23
52
  };
24
53
  export declare function formatReact(this: IntlManager, message: AnyIntlMessage, values: object, Builder: FormatBuilderConstructor<React.ReactElement>): ReactIntlMessage;
@@ -49,9 +49,8 @@ function createReactBuilder(richTextElements) {
49
49
  };
50
50
  }
51
51
  function formatReact(message, values, Builder) {
52
- if (typeof message === 'string') {
53
- return [message];
54
- }
52
+ if (typeof message === 'string')
53
+ return message;
55
54
  const parts = this.bindFormatValues(Builder, message, values);
56
55
  return parts;
57
56
  }
package/dist/index.d.ts CHANGED
@@ -1,27 +1,11 @@
1
- import * as React from 'react';
2
- import { RichTextNode } from './formatters';
3
- export * from './formatters';
1
+ export { type DataFormatters, makeDataFormatters } from './data-formatters';
2
+ export { dataFormatterCache } from './data-formatters/cache';
4
3
  export { FormatBuilder, FormatBuilderConstructor, bindFormatValues } from './format';
4
+ export * from './formatters';
5
5
  export { runtimeHashMessageKey } from './hash';
6
6
  export { IntlManager, DEFAULT_LOCALE, type FormatFunction } from './intl-manager';
7
7
  export { createLoader, loadAllMessagesInLocale, waitForAllDefaultIntlMessagesLoaded, MessageLoader, } from './message-loader';
8
8
  export type * from './types.d.ts';
9
- /**
10
- * A type representing any message that has been formatted using one of the
11
- * default formatting methods in the `@discord/intl` system. Use this type
12
- * when you don't care about whether a message is static or contains rich text
13
- * or has dynamically-formatted values, when you need to handle both in tandem,
14
- * or when you need a generic return type for a function returning "any
15
- * any-already formatted message".
16
- *
17
- * Generally, this is like `React.ReactNode`, accepting both strings and rich
18
- * `ReactElement`s, but it is more specific to disallow nullish values and
19
- * other special React elements like `ReactPortal`. Seeing this type, you can
20
- * be confident that the value is intended to be the result of formatting an
21
- * intl message, even if the actual value comes from elsewhere (like a
22
- * user-generated string).
23
- */
24
- export type ReactIntlMessage = string | React.ReactElement | Array<string | React.ReactElement>;
25
9
  /**
26
10
  * The return value of `formatToParts` from `@discord/intl`, this type
27
11
  * represents any AST structure for a message rendered using this system.
@@ -32,4 +16,5 @@ export type ReactIntlMessage = string | React.ReactElement | Array<string | Reac
32
16
  * from a call to `formatToParts`, meaning they are intended to be fully static
33
17
  * structures passed around for custom rendering functions to use.
34
18
  */
19
+ import { type RichTextNode } from './formatters';
35
20
  export type IntlMessageAst = RichTextNode;
package/dist/index.js CHANGED
@@ -14,11 +14,15 @@ 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.MessageLoader = exports.waitForAllDefaultIntlMessagesLoaded = exports.loadAllMessagesInLocale = exports.createLoader = exports.DEFAULT_LOCALE = exports.IntlManager = exports.runtimeHashMessageKey = exports.bindFormatValues = exports.FormatBuilder = void 0;
18
- __exportStar(require("./formatters"), exports);
17
+ exports.MessageLoader = exports.waitForAllDefaultIntlMessagesLoaded = exports.loadAllMessagesInLocale = exports.createLoader = exports.DEFAULT_LOCALE = exports.IntlManager = exports.runtimeHashMessageKey = exports.bindFormatValues = exports.FormatBuilder = exports.dataFormatterCache = exports.makeDataFormatters = void 0;
18
+ var data_formatters_1 = require("./data-formatters");
19
+ Object.defineProperty(exports, "makeDataFormatters", { enumerable: true, get: function () { return data_formatters_1.makeDataFormatters; } });
20
+ var cache_1 = require("./data-formatters/cache");
21
+ Object.defineProperty(exports, "dataFormatterCache", { enumerable: true, get: function () { return cache_1.dataFormatterCache; } });
19
22
  var format_1 = require("./format");
20
23
  Object.defineProperty(exports, "FormatBuilder", { enumerable: true, get: function () { return format_1.FormatBuilder; } });
21
24
  Object.defineProperty(exports, "bindFormatValues", { enumerable: true, get: function () { return format_1.bindFormatValues; } });
25
+ __exportStar(require("./formatters"), exports);
22
26
  var hash_1 = require("./hash");
23
27
  Object.defineProperty(exports, "runtimeHashMessageKey", { enumerable: true, get: function () { return hash_1.runtimeHashMessageKey; } });
24
28
  var intl_manager_1 = require("./intl-manager");
@@ -1,8 +1,8 @@
1
- import { IntlShape } from '@formatjs/intl';
1
+ import { DEFAULT_FORMAT_CONFIG, type FormatConfigType } from './data-formatters/config';
2
+ import { FormatBuilderConstructor } from './format';
2
3
  import type { FormatterImplementation, IntlMessageGetter, RequiredFormatValues, TypedIntlMessageGetter } from './types';
3
4
  import { InternalIntlMessage } from './message';
4
- import { FormatBuilderConstructor } from './format';
5
- import { FormatConfig } from './format-config';
5
+ import { DataFormatters } from './data-formatters';
6
6
  /**
7
7
  * Fallback locale used for all internationalization when an operation in the
8
8
  * requested locale is not possible.
@@ -14,7 +14,7 @@ export type FormatFunction<F extends FormatterImplementation<any, any>> = <T ext
14
14
  type ThisWithFormatters<This, T extends Record<string, FormatterImplementation<any, any>>> = This & {
15
15
  [K in keyof T]: FormatFunction<T[K]>;
16
16
  };
17
- export interface IntlManagerOptions {
17
+ export interface IntlManagerOptions<FormatConfig> {
18
18
  /**
19
19
  * The locale to initially have this manager use. Useful to set when information about the user's
20
20
  * likely locale is available sooner than when that information is definitely known (at which
@@ -30,19 +30,37 @@ export interface IntlManagerOptions {
30
30
  * @default DEFAULT_LOCALE
31
31
  */
32
32
  defaultLocale?: string;
33
+ /**
34
+ * Configuration for the different kinds of data dataFormatters that can be used both inside of
35
+ * messages and in their own functions (like `intl.formatDate`) as shorthands for common sets of
36
+ * options. For example, with the default config, this enables `intl.formatDate(now, 'short')`
37
+ * rather than having to specify the exact properties of each style every time.
38
+ *
39
+ * @default DEFAULT_FORMATTER_CONFIG
40
+ */
41
+ formatConfig?: FormatConfig;
42
+ /**
43
+ * The FormatJs Polyfill for locale-matcher has really poor performance when
44
+ * using the BestFitMatcher. When this value is set, the matcher will be
45
+ * forced to use the LookupMatcher instead, which is much more performant.
46
+ *
47
+ * @default false
48
+ */
49
+ forceLookupMatcher?: boolean;
33
50
  }
34
- export declare class IntlManager {
51
+ export declare class IntlManager<const FormatConfig extends FormatConfigType = typeof DEFAULT_FORMAT_CONFIG> {
35
52
  defaultLocale: string;
36
53
  currentLocale: string;
37
- intl: IntlShape;
38
54
  formatConfig: FormatConfig;
55
+ data: DataFormatters<FormatConfig>;
39
56
  _localeSubscriptions: Set<(locale: string) => void>;
40
- constructor({ initialLocale, defaultLocale, }: IntlManagerOptions);
57
+ _forceLookupMatcher: boolean;
58
+ constructor({ initialLocale, defaultLocale, formatConfig, forceLookupMatcher, }: IntlManagerOptions<FormatConfig>);
41
59
  /**
42
60
  * Add a set of formatter implementations to this manager, making each available as a direct
43
61
  * property
44
62
  */
45
- withFormatters<const T extends Record<string, FormatterImplementation<any, any>>>(formatters: T): ThisWithFormatters<this, T>;
63
+ withFormatters<const T extends Record<string, FormatterImplementation<any, any>>>(dataFormatters: T): ThisWithFormatters<this, T>;
46
64
  /**
47
65
  * Return a new function bound to this manager that uses the given `FormatterImplementation` to
48
66
  * format a message after it has been resolved for the current locale and potentially processed in