@discord/intl 0.1.3-rc.0 → 0.2.0

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,155 @@
1
+ import {
2
+ AnyIntlMessage,
3
+ FormatterImplementation,
4
+ FunctionTypes,
5
+ type RichTextFormattingMap,
6
+ RichTextTagNames,
7
+ } from '../types';
8
+ import { IntlManager } from '../intl-manager';
9
+ import { FormatBuilder } from '../format';
10
+
11
+ /**
12
+ * Types for formatting functions when calling `formatToParts`, ensuring the
13
+ * functions yield value AST nodes.
14
+ */
15
+ export type AstFunctionTypes = FunctionTypes<RichTextNode>;
16
+
17
+ // This structure aims to match `simple-markdown`'s AST, but that type is very
18
+ // loosely defined as just "type" and "arbitrary map of content". This is more
19
+ // explicit and type-safe.
20
+ export enum RichTextNodeType {
21
+ Text = 'text',
22
+ Strong = 'strong',
23
+ Emphasis = 'emphasis',
24
+ Code = 'code',
25
+ Link = 'link',
26
+ Paragraph = 'paragraph',
27
+ /**
28
+ * A special extension for when the value of a placeholder is an object.
29
+ * Rather than casting to a string here, the object is preserved as-is for a
30
+ * future formatter to render as appropriate (i.e., using an object as a
31
+ * value when formatting to an AST should keep that object structure so that
32
+ * the final AST is accurate for what the user expects, but when rendering to
33
+ * a plain string, the final formatter can just cast that object to a string
34
+ * using its `toString` method or some other mechanism).
35
+ */
36
+ Object = 'object',
37
+ }
38
+
39
+ const RICH_TEXT_TAG_TYPES: Record<RichTextTagNames, RichTextNodeType> = {
40
+ $_: RichTextNodeType.Text,
41
+ $b: RichTextNodeType.Strong,
42
+ $i: RichTextNodeType.Emphasis,
43
+ $code: RichTextNodeType.Code,
44
+ $link: RichTextNodeType.Link,
45
+ $p: RichTextNodeType.Paragraph,
46
+ };
47
+
48
+ interface RichTextNodeBase<ContentType> {
49
+ type: RichTextNodeType;
50
+ content: ContentType;
51
+ }
52
+ interface RichTextTextNode extends RichTextNodeBase<string> {
53
+ type: RichTextNodeType.Text;
54
+ }
55
+ interface RichTextStrongNode extends RichTextNodeBase<RichTextNode[]> {
56
+ type: RichTextNodeType.Strong;
57
+ }
58
+ interface RichTextEmphasisNode extends RichTextNodeBase<RichTextNode[]> {
59
+ type: RichTextNodeType.Emphasis;
60
+ }
61
+ interface RichTextCodeNode extends RichTextNodeBase<RichTextNode[]> {
62
+ type: RichTextNodeType.Code;
63
+ }
64
+ interface RichTextParagraphNode extends RichTextNodeBase<RichTextNode[]> {
65
+ type: RichTextNodeType.Paragraph;
66
+ }
67
+ interface RichTextLinkNode extends RichTextNodeBase<RichTextNode[]> {
68
+ type: RichTextNodeType.Link;
69
+ target: string;
70
+ }
71
+ interface RichTextObjectNode extends RichTextNodeBase<object> {
72
+ type: RichTextNodeType.Object;
73
+ }
74
+
75
+ export type RichTextNode =
76
+ | RichTextTextNode
77
+ | RichTextStrongNode
78
+ | RichTextEmphasisNode
79
+ | RichTextCodeNode
80
+ | RichTextParagraphNode
81
+ | RichTextLinkNode
82
+ | RichTextObjectNode;
83
+
84
+ const AST_RICH_TEXT_ELEMENTS: RichTextFormattingMap<AstFunctionTypes['hook']> = {
85
+ $_: () => ({ type: RichTextNodeType.Text, content: '' }),
86
+ $b: (content) => ({ type: RichTextNodeType.Strong, content }),
87
+ $code: (content) => ({ type: RichTextNodeType.Code, content }),
88
+ $i: (content) => ({ type: RichTextNodeType.Emphasis, content }),
89
+ $link: ([target, ...content]) => ({
90
+ type: RichTextNodeType.Link,
91
+ target: target.content as string,
92
+ content,
93
+ }),
94
+ $p: (content) => ({ type: RichTextNodeType.Paragraph, content }),
95
+ };
96
+
97
+ class AstBuilder extends FormatBuilder<RichTextNode> {
98
+ result: RichTextNode[] = [];
99
+
100
+ pushRichTextTag(tag: RichTextTagNames, children: RichTextNode[]) {
101
+ if (!(tag in AST_RICH_TEXT_ELEMENTS)) {
102
+ throw `${tag} is not a known rich text formatting tag`;
103
+ }
104
+ const result = AST_RICH_TEXT_ELEMENTS[tag](children);
105
+ if (Array.isArray(result)) {
106
+ this.result.push(...result);
107
+ } else {
108
+ this.result.push(result);
109
+ }
110
+ }
111
+
112
+ pushLiteralText(text: string) {
113
+ const last = this.result[this.result.length - 1];
114
+ if (last.type === RichTextNodeType.Text) {
115
+ last.content += text;
116
+ } else {
117
+ this.result.push({ type: RichTextNodeType.Text, content: text });
118
+ }
119
+ }
120
+
121
+ pushObject(value: object) {
122
+ this.result.push({ type: RichTextNodeType.Object, content: value });
123
+ }
124
+
125
+ finish(): RichTextNode[] {
126
+ return this.result;
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Format the given message in the current locale with the provided values.
132
+ * The returned values is _always_ an Array of parts, even if the message is
133
+ * a simple string value.
134
+ *
135
+ * This function is the basis of how messages are normally formatted, and can
136
+ * be used anywhere. However, it is not reactive and only functions on the
137
+ * data that is currently loaded and known. For a reactive function that
138
+ * automatically updates when the locale changes or when new data is loaded,
139
+ * use `format`, which will wrap the formatting in a React component that
140
+ * subscribes to the current locale and state of loaded messages.
141
+ */
142
+ export function formatToAst(
143
+ this: IntlManager,
144
+ message: AnyIntlMessage,
145
+ values: object,
146
+ ): RichTextNode[] {
147
+ if (typeof message === 'string') return [{ type: RichTextNodeType.Text, content: message }];
148
+
149
+ return this.bindFormatValues(AstBuilder, message, values);
150
+ }
151
+
152
+ export const astFormatter: FormatterImplementation<AstFunctionTypes, RichTextNode[]> = {
153
+ format: formatToAst,
154
+ builder: AstBuilder,
155
+ };
@@ -0,0 +1,9 @@
1
+ export { astFormatter, type AstFunctionTypes, type RichTextNode, RichTextNodeType } from './ast';
2
+ export { markdownFormatter, type MarkdownFunctionTypes } from './markdown';
3
+ export {
4
+ reactFormatter,
5
+ makeReactFormatter,
6
+ DEFAULT_REACT_RICH_TEXT_ELEMENTS,
7
+ type ReactFunctionTypes,
8
+ } from './react';
9
+ export { stringFormatter, type StringFunctionTypes } from './string';
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Similar to `formatToPlainString`, format the given message with the provided
3
+ * values, but convert all rich text formatting back to Markdown syntax rather
4
+ * than rendering the actual rich content. The result is a plain string that
5
+ * can be sent through a separate Markdown renderer to get an equivalent
6
+ * result to formatting this message directly.
7
+ */
8
+ import {
9
+ AnyIntlMessage,
10
+ FormatterImplementation,
11
+ FunctionTypes,
12
+ RichTextFormattingMap,
13
+ RichTextTagNames,
14
+ } from '../types';
15
+ import { IntlManager } from '../intl-manager';
16
+ import { FormatBuilderConstructor } from '../format';
17
+ import { StringBuilder } from './string';
18
+
19
+ export type MarkdownFunctionTypes = FunctionTypes<string>;
20
+
21
+ const MARKDOWN_RICH_TEXT_ELEMENTS: RichTextFormattingMap<MarkdownFunctionTypes['hook']> = {
22
+ $_: () => '',
23
+ $b: (content) => '**' + content.join('') + '**',
24
+ $code: (content) => '`' + content.join('') + '`',
25
+ $i: (content) => '*' + content.join('') + '*',
26
+ $link: ([target, ...content]) => '[' + content.join('') + '](' + target + ')',
27
+ $p: (content) => content.join('') + '\n\n',
28
+ };
29
+
30
+ class MarkdownBuilder extends StringBuilder {
31
+ result: string = '';
32
+
33
+ pushRichTextTag(tag: RichTextTagNames, children: string[]) {
34
+ this.result += MARKDOWN_RICH_TEXT_ELEMENTS[tag](children);
35
+ }
36
+ }
37
+
38
+ export function formatToMarkdownString(
39
+ this: IntlManager,
40
+ message: AnyIntlMessage,
41
+ values: object,
42
+ Builder: FormatBuilderConstructor<string> = MarkdownBuilder,
43
+ ): string {
44
+ if (typeof message === 'string') return message;
45
+
46
+ const result = this.bindFormatValues(Builder, message, values);
47
+ // MarkdownBuilder always creates a single-element array with the string value.
48
+ return result[0];
49
+ }
50
+
51
+ export const markdownFormatter: FormatterImplementation<MarkdownFunctionTypes, string> = {
52
+ format: formatToMarkdownString,
53
+ builder: MarkdownBuilder,
54
+ };
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Format the given message as a React component, allowing it to listen for
3
+ * and respond to updates about the current locale and other relevant
4
+ * information.
5
+ */
6
+ import type {
7
+ AnyIntlMessage,
8
+ FormatterImplementation,
9
+ FunctionTypes,
10
+ RichTextFormattingMap,
11
+ RichTextTagNames,
12
+ } from '../types';
13
+ import * as React from 'react';
14
+ import { IntlManager } from '../intl-manager';
15
+ import { FormatBuilder, FormatBuilderConstructor } from '../format';
16
+
17
+ export type ReactHandlerEvent = React.MouseEvent | React.KeyboardEvent;
18
+ export type ReactClickHandler = (e: ReactHandlerEvent) => void;
19
+
20
+ export type ReactFunctionTypes = FunctionTypes<
21
+ React.ReactNode,
22
+ | ReactClickHandler
23
+ | { onClick: ReactClickHandler }
24
+ | { onContextMenu: ReactClickHandler }
25
+ | { onClick: ReactClickHandler; onContextMenu: ReactClickHandler }
26
+ >;
27
+
28
+ const h = React.createElement;
29
+ export const DEFAULT_REACT_RICH_TEXT_ELEMENTS: RichTextFormattingMap<ReactFunctionTypes['hook']> = {
30
+ $_: () => '',
31
+ $b: (content) => h('strong', undefined, content),
32
+ $code: (content) => h('code', undefined, content),
33
+ $i: (content) => h('em', undefined, content),
34
+ // $link will always be [href, <empty>, ...content]
35
+ $link: ([href, ...content]) => h('a', { href }, content),
36
+ $p: (content) => h('p', undefined, content),
37
+ };
38
+
39
+ /**
40
+ * Creates a new `FormatBuilder` class that constructs a React element tree
41
+ * from the message using the given `richTextElements` to apply formatting.
42
+ * This allows consumers to inject their own design system components and
43
+ * overrides for rendering elements like links, paragraphs, and code blocks.
44
+ * @param richTextElements
45
+ */
46
+ function createReactBuilder(richTextElements: RichTextFormattingMap<ReactFunctionTypes['hook']>): {
47
+ new (): FormatBuilder<React.ReactNode>;
48
+ } {
49
+ return class extends FormatBuilder<React.ReactNode> {
50
+ result: React.ReactNode[] = [];
51
+
52
+ pushRichTextTag(tag: RichTextTagNames, children: React.ReactNode[]) {
53
+ this.result.push(richTextElements[tag](children));
54
+ }
55
+
56
+ pushLiteralText(text: string) {
57
+ this.result.push(text);
58
+ }
59
+
60
+ pushObject(value: object) {
61
+ // @ts-expect-error this is technically invalid, but we'll just assume that if a format returns
62
+ // an object, it'll be acting as some form of ReactNode.
63
+ this.result.push(value);
64
+ }
65
+
66
+ finish(): React.ReactNode[] {
67
+ return this.result;
68
+ }
69
+ };
70
+ }
71
+
72
+ export function formatReact(
73
+ this: IntlManager,
74
+ message: AnyIntlMessage,
75
+ values: object,
76
+ Builder: FormatBuilderConstructor<React.ReactElement>,
77
+ ): React.ReactElement {
78
+ if (typeof message === 'string') {
79
+ return React.createElement(React.Fragment, undefined, message);
80
+ }
81
+
82
+ const parts = this.bindFormatValues(Builder, message, values);
83
+ return React.createElement(React.Fragment, undefined, parts);
84
+ }
85
+
86
+ /**
87
+ * Create a new React formatter with the given rich text elements replacing the defaults. Use this
88
+ * function to inject custom components for things like links and paragraphs, which may be best
89
+ * suited to use components from a Design System or other library rather than native DOM elements.
90
+ */
91
+ export function makeReactFormatter(
92
+ richTextElements: RichTextFormattingMap<ReactFunctionTypes['hook']>,
93
+ ): FormatterImplementation<ReactFunctionTypes, React.ReactElement, React.ReactNode> {
94
+ return {
95
+ format: formatReact,
96
+ builder: createReactBuilder(richTextElements),
97
+ };
98
+ }
99
+
100
+ export const reactFormatter = makeReactFormatter(DEFAULT_REACT_RICH_TEXT_ELEMENTS);
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Format the given message with the provided values, removing any styling
3
+ * and non-textual content from the message, returning a plain string.
4
+ */
5
+ import { AnyIntlMessage, FormatterImplementation, FunctionTypes, RichTextTagNames } from '../types';
6
+ import { FormatBuilder } from '../format';
7
+ import { IntlManager } from '../intl-manager';
8
+
9
+ /**
10
+ * Types for formatting functions when calling `bindFormatValues`, ensuring the
11
+ * functions always yield plain strings.
12
+ */
13
+ export type StringFunctionTypes = FunctionTypes<string>;
14
+
15
+ export class StringBuilder extends FormatBuilder<string> {
16
+ result: string = '';
17
+
18
+ pushRichTextTag(tag: RichTextTagNames, children: string[]) {
19
+ // Plain string formatting ignores rich text tags and just takes the
20
+ // visible content from the children. This means the `target` element
21
+ // of a link is ignored, otherwise all the children are just joined
22
+ // together directly.
23
+ if (tag === '$link') {
24
+ this.result += children.slice(1).join('');
25
+ return;
26
+ }
27
+ this.result += children.join('');
28
+ }
29
+
30
+ pushLiteralText(text: string) {
31
+ this.result += text;
32
+ }
33
+
34
+ pushObject(value: object) {
35
+ // Objects are only included in the result if they specify a toString value directly.
36
+ // Otherwise, they would be rendered as `[object Object]`, which is never helpful.
37
+ if ('toString' in value) {
38
+ this.result += value.toString();
39
+ }
40
+ }
41
+
42
+ finish(): string[] {
43
+ return [this.result];
44
+ }
45
+ }
46
+
47
+ export function formatToPlainString(
48
+ this: IntlManager,
49
+ message: AnyIntlMessage,
50
+ values: object,
51
+ ): string {
52
+ if (typeof message === 'string') return message;
53
+
54
+ const result = this.bindFormatValues(StringBuilder, message, values);
55
+ // StringBuilder always creates a single element array with the string value.
56
+ return result[0];
57
+ }
58
+
59
+ export const stringFormatter: FormatterImplementation<StringFunctionTypes, string> = {
60
+ format: formatToPlainString,
61
+ builder: StringBuilder,
62
+ };
package/src/index.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { IntlManager } from './intl-manager';
2
- import { IntlManagerReact } from './react';
1
+ import * as React from 'react';
2
+ import { RichTextNode } from './formatters';
3
3
 
4
- export { IntlManager, createLoader, DEFAULT_LOCALE } from './intl-manager';
5
- export { IntlManagerReact } from './react';
4
+ export { IntlManager, createLoader, DEFAULT_LOCALE, type FormatFunction } from './intl-manager';
5
+ export * from './formatters';
6
6
 
7
7
  export type * from './types.d.ts';
8
8
 
@@ -21,7 +21,7 @@ export type * from './types.d.ts';
21
21
  * intl message, even if the actual value comes from elsewhere (like a
22
22
  * user-generated string).
23
23
  */
24
- export type ReactIntlMessage = string | ReturnType<IntlManagerReact<any>['format']>;
24
+ export type ReactIntlMessage = string | React.ReactElement;
25
25
 
26
26
  /**
27
27
  * The return value of `formatToParts` from `@discord/intl`, this type
@@ -33,4 +33,4 @@ export type ReactIntlMessage = string | ReturnType<IntlManagerReact<any>['format
33
33
  * from a call to `formatToParts`, meaning they are intended to be fully static
34
34
  * structures passed around for custom rendering functions to use.
35
35
  */
36
- export type IntlMessageAst = ReturnType<IntlManager<any, any>['formatToParts']>;
36
+ export type IntlMessageAst = RichTextNode;
@@ -1,30 +1,16 @@
1
1
  import { createIntl, IntlShape } from '@formatjs/intl';
2
- import {
3
- Formats,
4
- Formatters,
5
- IntlMessageFormat,
6
- PART_TYPE as FormatPartType,
7
- } from 'intl-messageformat';
2
+ import { Formats, Formatters, IntlMessageFormat } from 'intl-messageformat';
8
3
 
9
4
  import { LocaleId, LocaleImportMap, MessageLoader } from './message-loader';
10
5
 
11
6
  import type {
12
- FunctionTypeMap,
7
+ FormatterImplementation,
13
8
  IntlMessageGetter,
14
9
  RequiredFormatValues,
15
- RichTextElementMap,
16
10
  TypedIntlMessageGetter,
17
11
  } from './types';
18
-
19
- /**
20
- * Types for formatting functions when calling `formatToParts`, ensuring the
21
- * functions yield value AST nodes.
22
- */
23
- type AstFunctionTypes = {
24
- link: undefined | object | ((children: object) => Array<string | any>);
25
- hook: undefined | object | ((children: object) => Array<string | any>);
26
- handler: undefined | object | ((children: object) => Array<string | any>);
27
- };
12
+ import { InternalIntlMessage } from './message';
13
+ import { bindFormatValues, FormatBuilderConstructor } from './format';
28
14
 
29
15
  /**
30
16
  * Fallback locale used for all internationalization when an operation in the
@@ -32,26 +18,35 @@ type AstFunctionTypes = {
32
18
  */
33
19
  export const DEFAULT_LOCALE: string = 'en-US';
34
20
 
35
- export class IntlManager<
36
- DefaultElements extends RichTextElementMap,
37
- FunctionTypes extends FunctionTypeMap,
38
- > {
21
+ type FormatterReturnType<F extends FormatterImplementation<any, any>> =
22
+ F extends FormatterImplementation<any, infer Return> ? Return : never;
23
+
24
+ type FormatterFunctionTypes<F extends FormatterImplementation<any, any>> =
25
+ F extends FormatterImplementation<infer Functions, any> ? Functions : never;
26
+
27
+ export type FormatFunction<F extends FormatterImplementation<any, any>> = <
28
+ T extends IntlMessageGetter,
29
+ >(
30
+ this: IntlManager,
31
+ message: T,
32
+ values: RequiredFormatValues<T, FormatterFunctionTypes<F>>,
33
+ ) => FormatterReturnType<F>;
34
+
35
+ type ThisWithFormatters<
36
+ This,
37
+ T extends Record<string, FormatterImplementation<any, any>>,
38
+ > = This & {
39
+ [K in keyof T]: FormatFunction<T[K]>;
40
+ };
41
+
42
+ export class IntlManager {
39
43
  defaultLocale: string;
40
44
  currentLocale: string;
41
45
  intl: IntlShape;
42
46
 
43
- /**
44
- * When formatting tag elements like `<b>value</b>`, these functions will
45
- * automatically be used by all messages, unless they are explicitly removed
46
- * for a specific call, either by overriding the value or by calling a
47
- * dedicated method like `formatToPlainString` which will remove all
48
- * rich elements from the string and return only the plain text result.
49
- */
50
- defaultRichTextElements: DefaultElements;
51
-
52
47
  _localeSubscriptions: Set<(locale: string) => void>;
53
48
 
54
- constructor(defaultLocale: string = DEFAULT_LOCALE, defaultRichTextElements: DefaultElements) {
49
+ constructor(defaultLocale: string = DEFAULT_LOCALE) {
55
50
  this.defaultLocale = defaultLocale;
56
51
  this.currentLocale = defaultLocale;
57
52
  this.intl = createIntl({
@@ -60,10 +55,38 @@ export class IntlManager<
60
55
  locale: defaultLocale,
61
56
  });
62
57
 
63
- this.defaultRichTextElements = defaultRichTextElements;
64
58
  this._localeSubscriptions = new Set();
65
59
  }
66
60
 
61
+ /**
62
+ * Add a set of formatter implementations to this manager, making each available as a direct
63
+ * property
64
+ */
65
+ withFormatters<const T extends Record<string, FormatterImplementation<any, any>>>(
66
+ formatters: T,
67
+ ): ThisWithFormatters<this, T> {
68
+ for (const [name, formatter] of Object.entries(formatters)) {
69
+ this[name] = this.makeFormatFunction(formatter);
70
+ }
71
+
72
+ return this as ThisWithFormatters<this, T>;
73
+ }
74
+
75
+ /**
76
+ * Return a new function bound to this manager that uses the given `FormatterImplementation` to
77
+ * format a message after it has been resolved for the current locale and potentially processed in
78
+ * other ways by the manager.
79
+ */
80
+ makeFormatFunction<F extends FormatterImplementation<any, any>>({
81
+ format,
82
+ builder,
83
+ }: F): FormatFunction<FormatterReturnType<F>> {
84
+ const formatter = format.bind(this);
85
+ return (message, values) => {
86
+ return formatter(message(this.currentLocale), values, builder);
87
+ };
88
+ }
89
+
67
90
  /**
68
91
  * Set the locale for this package to use. This is a global change, and all
69
92
  * methods on this object will use this locale for formatting.
@@ -89,124 +112,23 @@ export class IntlManager<
89
112
  };
90
113
 
91
114
  /**
92
- * Format the given message in the current locale with the provided values.
93
- * The returned values is _always_ an Array of parts, even if the message is
94
- * a simple string value.
95
- *
96
- * This function is the basis of how messages are normally formatted, and can
97
- * be used anywhere. However, it is not reactive and only functions on the
98
- * data that is currently loaded and known. For a reactive function that
99
- * automatically updates when the locale changes or when new data is loaded,
100
- * use `format`, which will wrap the formatting in a React component that
101
- * subscribes to the current locale and state of loaded messages.
115
+ * For static messages with no rich text and no dynamic placeholders, use this method to
116
+ * immediately return the plain string value of the message in the current locale.
102
117
  */
103
- formatToParts<T extends TypedIntlMessageGetter<undefined>>(message: T): Array<string | any>;
104
- formatToParts<T extends IntlMessageGetter>(
105
- message: T,
106
- values: RequiredFormatValues<T, DefaultElements, AstFunctionTypes>,
107
- ): Array<string | any>;
108
- formatToParts<T extends IntlMessageGetter>(
109
- message: T,
110
- values?: RequiredFormatValues<T, DefaultElements, AstFunctionTypes>,
111
- ): Array<string | any> {
112
- if (typeof message === 'string') return [message];
113
- const resolvedMessage = typeof message === 'function' ? message(this.currentLocale) : message;
114
- if (typeof resolvedMessage === 'string') return [resolvedMessage];
115
-
116
- const resolvedValues =
117
- values != null
118
- ? { ...this.defaultRichTextElements, ...values }
119
- : this.defaultRichTextElements;
120
- const parts = resolvedMessage.formatToParts(
121
- this.intl.formatters as Formatters,
122
- this.intl.formats as Formats,
123
- resolvedValues,
124
- );
125
-
126
- const result = [];
127
- let inLiteral = false;
128
- for (const part of parts) {
129
- // This condition merges consecutive literal elements (static strings)
130
- // into single parts to reduce the number of nodes in the result. This
131
- // condition will never be true on the first loop, ensuring that the
132
- // array must at least have one entry before it attempts to concatenate
133
- // onto that value again.
134
- if (inLiteral && (inLiteral = part.type === FormatPartType.literal)) {
135
- result[result.length - 1] += part.value;
136
- continue;
137
- }
138
-
139
- inLiteral = part.type === FormatPartType.literal;
140
- result.push(part.value);
141
- }
142
-
143
- return result;
118
+ string<T extends TypedIntlMessageGetter<undefined>>(message: T): string {
119
+ // TODO: Figure out how to make this typing exact.
120
+ return message(this.currentLocale).message;
144
121
  }
145
122
 
146
- /**
147
- * For messages with no formatting values (i.e., plain strings, they can skip
148
- * a majority of the processing work and just resolve the message itself for
149
- * the current locale, then return the plain string directly.
150
- *
151
- * This is separate from `formatToPlainString`, which still allows for
152
- * formatting variables and other elements but reduces the output to only
153
- * include textual elements in a plain string.
154
- */
155
- string(message: TypedIntlMessageGetter<undefined>): string {
156
- // TODO(faulty): Optimize this so that plain strings are hotpathed even faster.
157
- return this.formatToPlainString(message);
158
- }
159
-
160
- /**
161
- * Format the given message with the provided values, removing any styling
162
- * and non-textual content from the message, returning a plain string.
163
- */
164
- formatToPlainString<T extends TypedIntlMessageGetter<undefined>>(message: T): string;
165
- formatToPlainString<T extends IntlMessageGetter>(
166
- message: T,
167
- values: RequiredFormatValues<T, DefaultElements, FunctionTypes>,
168
- ): string;
169
- formatToPlainString<T extends IntlMessageGetter>(
170
- message: T,
171
- values?: RequiredFormatValues<T, DefaultElements, FunctionTypes>,
172
- ): string {
173
- if (typeof message === 'string') return message;
174
- const resolvedMessage = message(this.currentLocale);
175
- if (typeof resolvedMessage === 'string') return resolvedMessage;
176
-
177
- // No need to pass in `defaultRichTextElements`, since the stylistic tags
178
- // will be removed from the string anyway.
179
- return resolvedMessage.formatToPlainString(
180
- this.intl.formatters as Formatters,
181
- this.intl.formats as Formats,
182
- values,
183
- );
184
- }
185
-
186
- /**
187
- * Similar to `formatToPlainString`, format the given message with the provided
188
- * values, but convert all rich text formatting back to Markdown syntax rather
189
- * than rendering the actual rich content. The result is a plain string that
190
- * can be sent through a separate Markdown renderer to get an equivalent
191
- * result to formatting this message directly.
192
- */
193
- formatToMarkdownString<T extends TypedIntlMessageGetter<undefined>>(message: T): string;
194
- formatToMarkdownString<T extends IntlMessageGetter>(
195
- message: T,
196
- values: RequiredFormatValues<T, DefaultElements, FunctionTypes>,
197
- ): string;
198
- formatToMarkdownString<T extends IntlMessageGetter>(
199
- message: T,
200
- values?: RequiredFormatValues<T, DefaultElements, FunctionTypes>,
201
- ): string {
202
- // TODO(faulty): Implement the markdown syntax conversion here.
203
- if (typeof message === 'string') return message;
204
- const resolvedMessage = message(this.currentLocale);
205
- if (typeof resolvedMessage === 'string') return resolvedMessage;
206
-
207
- // No need to pass in `defaultRichTextElements`, since the stylistic tags
208
- // will be removed from the string anyway.
209
- return resolvedMessage.formatToPlainString(
123
+ bindFormatValues<T>(
124
+ Builder: FormatBuilderConstructor<T>,
125
+ message: InternalIntlMessage,
126
+ values: Record<string, any>,
127
+ ): T[] {
128
+ return bindFormatValues(
129
+ Builder,
130
+ message.ast,
131
+ [this.currentLocale, this.defaultLocale],
210
132
  this.intl.formatters as Formatters,
211
133
  this.intl.formats as Formats,
212
134
  values,