@discord/intl 0.1.2 → 0.2.0-canary.cad566b
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.
- package/dist/format.d.ts +30 -0
- package/dist/format.js +214 -0
- package/dist/formatters/ast.d.ts +67 -0
- package/dist/formatters/ast.js +148 -0
- package/dist/formatters/index.d.ts +4 -0
- package/dist/formatters/index.js +14 -0
- package/dist/formatters/markdown.d.ts +13 -0
- package/dist/formatters/markdown.js +80 -0
- package/dist/formatters/react.d.ts +28 -0
- package/dist/formatters/react.js +111 -0
- package/dist/formatters/string.d.ts +21 -0
- package/dist/formatters/string.js +90 -0
- package/dist/index.d.ts +7 -6
- package/dist/index.js +19 -4
- package/dist/intl-manager.d.ts +25 -63
- package/dist/intl-manager.js +61 -89
- package/dist/keyless-json.d.ts +8 -1
- package/dist/message-loader.d.ts +35 -6
- package/dist/message-loader.js +113 -15
- package/dist/message.d.ts +3 -38
- package/dist/message.js +1 -132
- package/dist/types.d.ts +41 -45
- package/package.json +1 -2
- package/src/format.ts +224 -0
- package/src/formatters/ast.ts +155 -0
- package/src/formatters/index.ts +9 -0
- package/src/formatters/markdown.ts +54 -0
- package/src/formatters/react.ts +100 -0
- package/src/formatters/string.ts +62 -0
- package/src/index.ts +7 -7
- package/src/intl-manager.ts +73 -161
- package/src/keyless-json.ts +7 -0
- package/src/message-loader.ts +92 -19
- package/src/message.ts +4 -96
- package/src/types.d.ts +41 -45
- package/dist/react.d.ts +0 -38
- package/dist/react.js +0 -80
- package/src/react.ts +0 -80
package/dist/format.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The core formatter, responsible for applying values to placeholders in
|
|
3
|
+
* messages and creating a static CST from the result. This method does _not_
|
|
4
|
+
* replace any of the rich-text formatting values (e.g., `$b` or `$link`) and
|
|
5
|
+
* leaves those to final formatters to apply as appropriate for the context
|
|
6
|
+
* of where the message is being rendered (e.g., React versus a plain string).
|
|
7
|
+
*
|
|
8
|
+
* The majority of this method is lifted from FormatJS' own `formatToParts`
|
|
9
|
+
* implementation, but is abstracted here to provide a few benefits:
|
|
10
|
+
* - working with the keyless AST format rather than having to transpose to the
|
|
11
|
+
* exact object format that FormatJS expects.
|
|
12
|
+
* - understanding the difference between rich text formatting values and
|
|
13
|
+
* user-provided values.
|
|
14
|
+
* - provide extra options related to the above for more control over how
|
|
15
|
+
* formatting operates, while abstracting out some of the common values.
|
|
16
|
+
* - adding extra context for hook and link functions to provide a `key`
|
|
17
|
+
* parameter, important to have React treat the resulting elements nicely.
|
|
18
|
+
*/
|
|
19
|
+
import { FormatJsNode } from './keyless-json';
|
|
20
|
+
import { Formats, Formatters } from 'intl-messageformat';
|
|
21
|
+
import { RichTextTagNames } from './types';
|
|
22
|
+
export declare abstract class FormatBuilder<Result> {
|
|
23
|
+
abstract pushRichTextTag(tag: RichTextTagNames, children: Result[]): void;
|
|
24
|
+
abstract pushLiteralText(text: string): void;
|
|
25
|
+
abstract pushObject(value: object): void;
|
|
26
|
+
abstract finish(): Result[];
|
|
27
|
+
}
|
|
28
|
+
export type FormatBuilderConstructor<Result> = new () => FormatBuilder<Result>;
|
|
29
|
+
export declare function bindFormatValuesWithBuilder<T, Builder extends FormatBuilder<T>>(builder: Builder, nodes: FormatJsNode[], locales: string | string[], formatters: Formatters, formats: Formats, values?: Record<string, string | object>, currentPluralValue?: number, originalMessage?: string): void;
|
|
30
|
+
export declare function bindFormatValues<Result>(Builder: FormatBuilderConstructor<Result>, nodes: FormatJsNode[], locales: string | string[], formatters: Formatters, formats: Formats, values?: Record<string, string | object>, currentPluralValue?: number): Result[];
|
package/dist/format.js
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The core formatter, responsible for applying values to placeholders in
|
|
4
|
+
* messages and creating a static CST from the result. This method does _not_
|
|
5
|
+
* replace any of the rich-text formatting values (e.g., `$b` or `$link`) and
|
|
6
|
+
* leaves those to final formatters to apply as appropriate for the context
|
|
7
|
+
* of where the message is being rendered (e.g., React versus a plain string).
|
|
8
|
+
*
|
|
9
|
+
* The majority of this method is lifted from FormatJS' own `formatToParts`
|
|
10
|
+
* implementation, but is abstracted here to provide a few benefits:
|
|
11
|
+
* - working with the keyless AST format rather than having to transpose to the
|
|
12
|
+
* exact object format that FormatJS expects.
|
|
13
|
+
* - understanding the difference between rich text formatting values and
|
|
14
|
+
* user-provided values.
|
|
15
|
+
* - provide extra options related to the above for more control over how
|
|
16
|
+
* formatting operates, while abstracting out some of the common values.
|
|
17
|
+
* - adding extra context for hook and link functions to provide a `key`
|
|
18
|
+
* parameter, important to have React treat the resulting elements nicely.
|
|
19
|
+
*/
|
|
20
|
+
var __values = (this && this.__values) || function(o) {
|
|
21
|
+
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
|
|
22
|
+
if (m) return m.call(o);
|
|
23
|
+
if (o && typeof o.length === "number") return {
|
|
24
|
+
next: function () {
|
|
25
|
+
if (o && i >= o.length) o = void 0;
|
|
26
|
+
return { value: o && o[i++], done: !o };
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
|
|
30
|
+
};
|
|
31
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
32
|
+
exports.FormatBuilder = void 0;
|
|
33
|
+
exports.bindFormatValuesWithBuilder = bindFormatValuesWithBuilder;
|
|
34
|
+
exports.bindFormatValues = bindFormatValues;
|
|
35
|
+
var keyless_json_1 = require("./keyless-json");
|
|
36
|
+
var intl_messageformat_1 = require("intl-messageformat");
|
|
37
|
+
/**
|
|
38
|
+
* Returns true if the tag name should be considered a rich text tag that
|
|
39
|
+
* applies formatting to a message, rather than being a user-supplied value.
|
|
40
|
+
*/
|
|
41
|
+
function isRichTextTag(name) {
|
|
42
|
+
return name[0] === '$';
|
|
43
|
+
}
|
|
44
|
+
var FormatBuilder = /** @class */ (function () {
|
|
45
|
+
function FormatBuilder() {
|
|
46
|
+
}
|
|
47
|
+
return FormatBuilder;
|
|
48
|
+
}());
|
|
49
|
+
exports.FormatBuilder = FormatBuilder;
|
|
50
|
+
function bindFormatValuesWithBuilder(builder, nodes, locales, formatters, formats, values, currentPluralValue, originalMessage) {
|
|
51
|
+
var e_1, _a;
|
|
52
|
+
var _b;
|
|
53
|
+
if (values === void 0) { values = {}; }
|
|
54
|
+
// Hot path for static messages that are just parsed as a single string element.
|
|
55
|
+
if (nodes.length === 1 && nodes[0].type === keyless_json_1.FormatJsNodeType.Literal) {
|
|
56
|
+
builder.pushLiteralText(nodes[0].value);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
var _loop_1 = function (node) {
|
|
60
|
+
var e_2, _c;
|
|
61
|
+
switch (node.type) {
|
|
62
|
+
case keyless_json_1.FormatJsNodeType.Literal:
|
|
63
|
+
builder.pushLiteralText(node.value);
|
|
64
|
+
return "continue";
|
|
65
|
+
case keyless_json_1.FormatJsNodeType.Pound:
|
|
66
|
+
// Replace `#` in plural rules with the actual numeric value. Only
|
|
67
|
+
// numeric values are replaced, otherwise the value is completed ignored?
|
|
68
|
+
// Behavior copied from FormatJS directly.
|
|
69
|
+
if (typeof currentPluralValue === 'number') {
|
|
70
|
+
var value_1 = formatters.getNumberFormat(locales).format(currentPluralValue);
|
|
71
|
+
builder.pushLiteralText(value_1);
|
|
72
|
+
}
|
|
73
|
+
return "continue";
|
|
74
|
+
}
|
|
75
|
+
var variableName = node.value;
|
|
76
|
+
// Enforce that all required values are provided by the caller, even if the
|
|
77
|
+
// actual value is falsy/undefined.
|
|
78
|
+
if (!(variableName in values) && !isRichTextTag(variableName)) {
|
|
79
|
+
throw new intl_messageformat_1.MissingValueError(variableName, originalMessage);
|
|
80
|
+
}
|
|
81
|
+
var value = values[variableName];
|
|
82
|
+
switch (node.type) {
|
|
83
|
+
case keyless_json_1.FormatJsNodeType.Argument:
|
|
84
|
+
// Empty values don't need to be added at all, they are purely for AST representation.
|
|
85
|
+
if (variableName == '$_')
|
|
86
|
+
break;
|
|
87
|
+
if (typeof value === 'object') {
|
|
88
|
+
builder.pushObject(value);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
// Taken from FormatJS: non-objects (strings, numbers, and falsy
|
|
92
|
+
// values) all get cast to strings immediately as literal nodes.
|
|
93
|
+
builder.pushLiteralText(String(value));
|
|
94
|
+
}
|
|
95
|
+
break;
|
|
96
|
+
case keyless_json_1.FormatJsNodeType.Date: {
|
|
97
|
+
// Distinct from FormatJS: We don't currently parse the skeleton ahead of time in the AST,
|
|
98
|
+
// so this manages parsing the skeleton as well before passing it onto the date formatter.
|
|
99
|
+
if (typeof node.style !== 'string') {
|
|
100
|
+
throw '@discord/intl does not currently support non-string format skeletons';
|
|
101
|
+
}
|
|
102
|
+
var style = node.style in formats.date ? formats.date[node.style] : undefined; // TODO: parseSkeleton();
|
|
103
|
+
// @ts-expect-error Cast string values to dates properly.
|
|
104
|
+
builder.pushLiteralText(formatters.getDateTimeFormat(locales, style).format(value));
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
case keyless_json_1.FormatJsNodeType.Time: {
|
|
108
|
+
// Distinct from FormatJS: We don't currently parse the skeleton ahead of time in the AST,
|
|
109
|
+
// so this manages parsing the skeleton as well before passing it onto the date formatter.
|
|
110
|
+
if (typeof node.style !== 'string') {
|
|
111
|
+
throw '@discord/intl does not currently support non-string format skeletons';
|
|
112
|
+
}
|
|
113
|
+
var style = node.style in formats.time ? formats.time[node.style] : undefined; // TODO: parseSkeleton();
|
|
114
|
+
builder.pushLiteralText(
|
|
115
|
+
// @ts-expect-error Cast string values to dates properly.
|
|
116
|
+
formatters.getDateTimeFormat(locales, style).format(value));
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
case keyless_json_1.FormatJsNodeType.Number: {
|
|
120
|
+
// Distinct from FormatJS: We don't currently parse the skeleton ahead of time in the AST,
|
|
121
|
+
// so this manages parsing the skeleton as well before passing it onto the date formatter.
|
|
122
|
+
if (typeof node.style !== 'string') {
|
|
123
|
+
throw '@discord/intl does not currently support non-string format skeletons';
|
|
124
|
+
}
|
|
125
|
+
var style = node.style in formats.number ? formats.number[node.style] : undefined; // TODO: parseSkeleton();
|
|
126
|
+
// @ts-expect-error Support `scale` style property.
|
|
127
|
+
var scaledValue = value;
|
|
128
|
+
// const scaledValue = (value as number) * (style?.scale ?? 1);
|
|
129
|
+
builder.pushLiteralText(formatters.getNumberFormat(locales, style).format(scaledValue));
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
case keyless_json_1.FormatJsNodeType.Tag: {
|
|
133
|
+
var children = node.children;
|
|
134
|
+
var appliedChildren = bindFormatValues(builder.constructor, children, locales, formatters, formats, values, currentPluralValue);
|
|
135
|
+
if (isRichTextTag(variableName)) {
|
|
136
|
+
builder.pushRichTextTag(variableName, appliedChildren);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
if (typeof value !== 'function') {
|
|
140
|
+
throw "expected a function type for a Tag formatting value, ".concat(variableName, ". got ").concat(typeof value, ": ").concat(value);
|
|
141
|
+
}
|
|
142
|
+
var chunks = value(appliedChildren);
|
|
143
|
+
chunks = Array.isArray(chunks) ? chunks : [chunks];
|
|
144
|
+
try {
|
|
145
|
+
for (var chunks_1 = (e_2 = void 0, __values(chunks)), chunks_1_1 = chunks_1.next(); !chunks_1_1.done; chunks_1_1 = chunks_1.next()) {
|
|
146
|
+
var chunk = chunks_1_1.value;
|
|
147
|
+
if (typeof chunk === 'string') {
|
|
148
|
+
builder.pushLiteralText(chunk);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
builder.pushObject(chunk);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
catch (e_2_1) { e_2 = { error: e_2_1 }; }
|
|
156
|
+
finally {
|
|
157
|
+
try {
|
|
158
|
+
if (chunks_1_1 && !chunks_1_1.done && (_c = chunks_1.return)) _c.call(chunks_1);
|
|
159
|
+
}
|
|
160
|
+
finally { if (e_2) throw e_2.error; }
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
case keyless_json_1.FormatJsNodeType.Select: {
|
|
166
|
+
var option = node.value in node.options ? node.options[node.value] : node.options.other;
|
|
167
|
+
if (option == null) {
|
|
168
|
+
throw "".concat(node.value, " is not a known option for select value ").concat(variableName, ". Valid options are ").concat(Object.keys(node.options).join(', '));
|
|
169
|
+
}
|
|
170
|
+
bindFormatValuesWithBuilder(builder, option.value, locales, formatters, formats, values);
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
case keyless_json_1.FormatJsNodeType.Plural: {
|
|
174
|
+
var option = (function () {
|
|
175
|
+
var _a, _b;
|
|
176
|
+
var exactSelector = "=".concat(value);
|
|
177
|
+
if (exactSelector in node.options)
|
|
178
|
+
return node.options[exactSelector];
|
|
179
|
+
var rule = formatters
|
|
180
|
+
.getPluralRules(locales, { type: node.pluralType })
|
|
181
|
+
// @ts-expect-error Assert this `as number` properly.
|
|
182
|
+
.select(value - ((_a = node.offset) !== null && _a !== void 0 ? _a : 0));
|
|
183
|
+
return (_b = node.options[rule]) !== null && _b !== void 0 ? _b : node.options.other;
|
|
184
|
+
})();
|
|
185
|
+
if (option == null) {
|
|
186
|
+
throw "".concat(node.value, " is not a known option for plural value ").concat(variableName, ". Valid options are ").concat(Object.keys(node.options).join(', '));
|
|
187
|
+
}
|
|
188
|
+
bindFormatValuesWithBuilder(builder, option.value, locales, formatters, formats, values,
|
|
189
|
+
// @ts-expect-error assert this `as number` properly.
|
|
190
|
+
value - ((_b = node.offset) !== null && _b !== void 0 ? _b : 0));
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
try {
|
|
196
|
+
for (var nodes_1 = __values(nodes), nodes_1_1 = nodes_1.next(); !nodes_1_1.done; nodes_1_1 = nodes_1.next()) {
|
|
197
|
+
var node = nodes_1_1.value;
|
|
198
|
+
_loop_1(node);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
202
|
+
finally {
|
|
203
|
+
try {
|
|
204
|
+
if (nodes_1_1 && !nodes_1_1.done && (_a = nodes_1.return)) _a.call(nodes_1);
|
|
205
|
+
}
|
|
206
|
+
finally { if (e_1) throw e_1.error; }
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
function bindFormatValues(Builder, nodes, locales, formatters, formats, values, currentPluralValue) {
|
|
210
|
+
if (values === void 0) { values = {}; }
|
|
211
|
+
var builder = new Builder();
|
|
212
|
+
bindFormatValuesWithBuilder(builder, nodes, locales, formatters, formats, values, currentPluralValue);
|
|
213
|
+
return builder.finish();
|
|
214
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { AnyIntlMessage, FormatterImplementation, FunctionTypes } from '../types';
|
|
2
|
+
import { IntlManager } from '../intl-manager';
|
|
3
|
+
/**
|
|
4
|
+
* Types for formatting functions when calling `formatToParts`, ensuring the
|
|
5
|
+
* functions yield value AST nodes.
|
|
6
|
+
*/
|
|
7
|
+
export type AstFunctionTypes = FunctionTypes<RichTextNode>;
|
|
8
|
+
export declare enum RichTextNodeType {
|
|
9
|
+
Text = "text",
|
|
10
|
+
Strong = "strong",
|
|
11
|
+
Emphasis = "emphasis",
|
|
12
|
+
Code = "code",
|
|
13
|
+
Link = "link",
|
|
14
|
+
Paragraph = "paragraph",
|
|
15
|
+
/**
|
|
16
|
+
* A special extension for when the value of a placeholder is an object.
|
|
17
|
+
* Rather than casting to a string here, the object is preserved as-is for a
|
|
18
|
+
* future formatter to render as appropriate (i.e., using an object as a
|
|
19
|
+
* value when formatting to an AST should keep that object structure so that
|
|
20
|
+
* the final AST is accurate for what the user expects, but when rendering to
|
|
21
|
+
* a plain string, the final formatter can just cast that object to a string
|
|
22
|
+
* using its `toString` method or some other mechanism).
|
|
23
|
+
*/
|
|
24
|
+
Object = "object"
|
|
25
|
+
}
|
|
26
|
+
interface RichTextNodeBase<ContentType> {
|
|
27
|
+
type: RichTextNodeType;
|
|
28
|
+
content: ContentType;
|
|
29
|
+
}
|
|
30
|
+
interface RichTextTextNode extends RichTextNodeBase<string> {
|
|
31
|
+
type: RichTextNodeType.Text;
|
|
32
|
+
}
|
|
33
|
+
interface RichTextStrongNode extends RichTextNodeBase<RichTextNode[]> {
|
|
34
|
+
type: RichTextNodeType.Strong;
|
|
35
|
+
}
|
|
36
|
+
interface RichTextEmphasisNode extends RichTextNodeBase<RichTextNode[]> {
|
|
37
|
+
type: RichTextNodeType.Emphasis;
|
|
38
|
+
}
|
|
39
|
+
interface RichTextCodeNode extends RichTextNodeBase<RichTextNode[]> {
|
|
40
|
+
type: RichTextNodeType.Code;
|
|
41
|
+
}
|
|
42
|
+
interface RichTextParagraphNode extends RichTextNodeBase<RichTextNode[]> {
|
|
43
|
+
type: RichTextNodeType.Paragraph;
|
|
44
|
+
}
|
|
45
|
+
interface RichTextLinkNode extends RichTextNodeBase<RichTextNode[]> {
|
|
46
|
+
type: RichTextNodeType.Link;
|
|
47
|
+
target: string;
|
|
48
|
+
}
|
|
49
|
+
interface RichTextObjectNode extends RichTextNodeBase<object> {
|
|
50
|
+
type: RichTextNodeType.Object;
|
|
51
|
+
}
|
|
52
|
+
export type RichTextNode = RichTextTextNode | RichTextStrongNode | RichTextEmphasisNode | RichTextCodeNode | RichTextParagraphNode | RichTextLinkNode | RichTextObjectNode;
|
|
53
|
+
/**
|
|
54
|
+
* Format the given message in the current locale with the provided values.
|
|
55
|
+
* The returned values is _always_ an Array of parts, even if the message is
|
|
56
|
+
* a simple string value.
|
|
57
|
+
*
|
|
58
|
+
* This function is the basis of how messages are normally formatted, and can
|
|
59
|
+
* be used anywhere. However, it is not reactive and only functions on the
|
|
60
|
+
* data that is currently loaded and known. For a reactive function that
|
|
61
|
+
* automatically updates when the locale changes or when new data is loaded,
|
|
62
|
+
* use `format`, which will wrap the formatting in a React component that
|
|
63
|
+
* subscribes to the current locale and state of loaded messages.
|
|
64
|
+
*/
|
|
65
|
+
export declare function formatToAst(this: IntlManager, message: AnyIntlMessage, values: object): RichTextNode[];
|
|
66
|
+
export declare const astFormatter: FormatterImplementation<AstFunctionTypes, RichTextNode[]>;
|
|
67
|
+
export {};
|
|
@@ -0,0 +1,148 @@
|
|
|
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.astFormatter = exports.RichTextNodeType = void 0;
|
|
44
|
+
exports.formatToAst = formatToAst;
|
|
45
|
+
var format_1 = require("../format");
|
|
46
|
+
// This structure aims to match `simple-markdown`'s AST, but that type is very
|
|
47
|
+
// loosely defined as just "type" and "arbitrary map of content". This is more
|
|
48
|
+
// explicit and type-safe.
|
|
49
|
+
var RichTextNodeType;
|
|
50
|
+
(function (RichTextNodeType) {
|
|
51
|
+
RichTextNodeType["Text"] = "text";
|
|
52
|
+
RichTextNodeType["Strong"] = "strong";
|
|
53
|
+
RichTextNodeType["Emphasis"] = "emphasis";
|
|
54
|
+
RichTextNodeType["Code"] = "code";
|
|
55
|
+
RichTextNodeType["Link"] = "link";
|
|
56
|
+
RichTextNodeType["Paragraph"] = "paragraph";
|
|
57
|
+
/**
|
|
58
|
+
* A special extension for when the value of a placeholder is an object.
|
|
59
|
+
* Rather than casting to a string here, the object is preserved as-is for a
|
|
60
|
+
* future formatter to render as appropriate (i.e., using an object as a
|
|
61
|
+
* value when formatting to an AST should keep that object structure so that
|
|
62
|
+
* the final AST is accurate for what the user expects, but when rendering to
|
|
63
|
+
* a plain string, the final formatter can just cast that object to a string
|
|
64
|
+
* using its `toString` method or some other mechanism).
|
|
65
|
+
*/
|
|
66
|
+
RichTextNodeType["Object"] = "object";
|
|
67
|
+
})(RichTextNodeType || (exports.RichTextNodeType = RichTextNodeType = {}));
|
|
68
|
+
var RICH_TEXT_TAG_TYPES = {
|
|
69
|
+
$_: RichTextNodeType.Text,
|
|
70
|
+
$b: RichTextNodeType.Strong,
|
|
71
|
+
$i: RichTextNodeType.Emphasis,
|
|
72
|
+
$code: RichTextNodeType.Code,
|
|
73
|
+
$link: RichTextNodeType.Link,
|
|
74
|
+
$p: RichTextNodeType.Paragraph,
|
|
75
|
+
};
|
|
76
|
+
var AST_RICH_TEXT_ELEMENTS = {
|
|
77
|
+
$_: function () { return ({ type: RichTextNodeType.Text, content: '' }); },
|
|
78
|
+
$b: function (content) { return ({ type: RichTextNodeType.Strong, content: content }); },
|
|
79
|
+
$code: function (content) { return ({ type: RichTextNodeType.Code, content: content }); },
|
|
80
|
+
$i: function (content) { return ({ type: RichTextNodeType.Emphasis, content: content }); },
|
|
81
|
+
$link: function (_a) {
|
|
82
|
+
var _b = __read(_a), target = _b[0], content = _b.slice(1);
|
|
83
|
+
return ({
|
|
84
|
+
type: RichTextNodeType.Link,
|
|
85
|
+
target: target.content,
|
|
86
|
+
content: content,
|
|
87
|
+
});
|
|
88
|
+
},
|
|
89
|
+
$p: function (content) { return ({ type: RichTextNodeType.Paragraph, content: content }); },
|
|
90
|
+
};
|
|
91
|
+
var AstBuilder = /** @class */ (function (_super) {
|
|
92
|
+
__extends(AstBuilder, _super);
|
|
93
|
+
function AstBuilder() {
|
|
94
|
+
var _this = _super.apply(this, __spreadArray([], __read(arguments), false)) || this;
|
|
95
|
+
_this.result = [];
|
|
96
|
+
return _this;
|
|
97
|
+
}
|
|
98
|
+
AstBuilder.prototype.pushRichTextTag = function (tag, children) {
|
|
99
|
+
var _a;
|
|
100
|
+
if (!(tag in AST_RICH_TEXT_ELEMENTS)) {
|
|
101
|
+
throw "".concat(tag, " is not a known rich text formatting tag");
|
|
102
|
+
}
|
|
103
|
+
var result = AST_RICH_TEXT_ELEMENTS[tag](children);
|
|
104
|
+
if (Array.isArray(result)) {
|
|
105
|
+
(_a = this.result).push.apply(_a, __spreadArray([], __read(result), false));
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
this.result.push(result);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
AstBuilder.prototype.pushLiteralText = function (text) {
|
|
112
|
+
var last = this.result[this.result.length - 1];
|
|
113
|
+
if (last.type === RichTextNodeType.Text) {
|
|
114
|
+
last.content += text;
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
this.result.push({ type: RichTextNodeType.Text, content: text });
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
AstBuilder.prototype.pushObject = function (value) {
|
|
121
|
+
this.result.push({ type: RichTextNodeType.Object, content: value });
|
|
122
|
+
};
|
|
123
|
+
AstBuilder.prototype.finish = function () {
|
|
124
|
+
return this.result;
|
|
125
|
+
};
|
|
126
|
+
return AstBuilder;
|
|
127
|
+
}(format_1.FormatBuilder));
|
|
128
|
+
/**
|
|
129
|
+
* Format the given message in the current locale with the provided values.
|
|
130
|
+
* The returned values is _always_ an Array of parts, even if the message is
|
|
131
|
+
* a simple string value.
|
|
132
|
+
*
|
|
133
|
+
* This function is the basis of how messages are normally formatted, and can
|
|
134
|
+
* be used anywhere. However, it is not reactive and only functions on the
|
|
135
|
+
* data that is currently loaded and known. For a reactive function that
|
|
136
|
+
* automatically updates when the locale changes or when new data is loaded,
|
|
137
|
+
* use `format`, which will wrap the formatting in a React component that
|
|
138
|
+
* subscribes to the current locale and state of loaded messages.
|
|
139
|
+
*/
|
|
140
|
+
function formatToAst(message, values) {
|
|
141
|
+
if (typeof message === 'string')
|
|
142
|
+
return [{ type: RichTextNodeType.Text, content: message }];
|
|
143
|
+
return this.bindFormatValues(AstBuilder, message, values);
|
|
144
|
+
}
|
|
145
|
+
exports.astFormatter = {
|
|
146
|
+
format: formatToAst,
|
|
147
|
+
builder: AstBuilder,
|
|
148
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { astFormatter, type AstFunctionTypes, type RichTextNode, RichTextNodeType } from './ast';
|
|
2
|
+
export { markdownFormatter, type MarkdownFunctionTypes } from './markdown';
|
|
3
|
+
export { reactFormatter, makeReactFormatter, DEFAULT_REACT_RICH_TEXT_ELEMENTS, type ReactFunctionTypes, } from './react';
|
|
4
|
+
export { stringFormatter, type StringFunctionTypes } from './string';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.stringFormatter = exports.DEFAULT_REACT_RICH_TEXT_ELEMENTS = exports.makeReactFormatter = exports.reactFormatter = exports.markdownFormatter = exports.RichTextNodeType = exports.astFormatter = void 0;
|
|
4
|
+
var ast_1 = require("./ast");
|
|
5
|
+
Object.defineProperty(exports, "astFormatter", { enumerable: true, get: function () { return ast_1.astFormatter; } });
|
|
6
|
+
Object.defineProperty(exports, "RichTextNodeType", { enumerable: true, get: function () { return ast_1.RichTextNodeType; } });
|
|
7
|
+
var markdown_1 = require("./markdown");
|
|
8
|
+
Object.defineProperty(exports, "markdownFormatter", { enumerable: true, get: function () { return markdown_1.markdownFormatter; } });
|
|
9
|
+
var react_1 = require("./react");
|
|
10
|
+
Object.defineProperty(exports, "reactFormatter", { enumerable: true, get: function () { return react_1.reactFormatter; } });
|
|
11
|
+
Object.defineProperty(exports, "makeReactFormatter", { enumerable: true, get: function () { return react_1.makeReactFormatter; } });
|
|
12
|
+
Object.defineProperty(exports, "DEFAULT_REACT_RICH_TEXT_ELEMENTS", { enumerable: true, get: function () { return react_1.DEFAULT_REACT_RICH_TEXT_ELEMENTS; } });
|
|
13
|
+
var string_1 = require("./string");
|
|
14
|
+
Object.defineProperty(exports, "stringFormatter", { enumerable: true, get: function () { return string_1.stringFormatter; } });
|
|
@@ -0,0 +1,13 @@
|
|
|
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 { AnyIntlMessage, FormatterImplementation, FunctionTypes } from '../types';
|
|
9
|
+
import { IntlManager } from '../intl-manager';
|
|
10
|
+
import { FormatBuilderConstructor } from '../format';
|
|
11
|
+
export type MarkdownFunctionTypes = FunctionTypes<string>;
|
|
12
|
+
export declare function formatToMarkdownString(this: IntlManager, message: AnyIntlMessage, values: object, Builder?: FormatBuilderConstructor<string>): string;
|
|
13
|
+
export declare const markdownFormatter: FormatterImplementation<MarkdownFunctionTypes, string>;
|
|
@@ -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.markdownFormatter = void 0;
|
|
44
|
+
exports.formatToMarkdownString = formatToMarkdownString;
|
|
45
|
+
var string_1 = require("./string");
|
|
46
|
+
var MARKDOWN_RICH_TEXT_ELEMENTS = {
|
|
47
|
+
$_: function () { return ''; },
|
|
48
|
+
$b: function (content) { return '**' + content.join('') + '**'; },
|
|
49
|
+
$code: function (content) { return '`' + content.join('') + '`'; },
|
|
50
|
+
$i: function (content) { return '*' + content.join('') + '*'; },
|
|
51
|
+
$link: function (_a) {
|
|
52
|
+
var _b = __read(_a), target = _b[0], content = _b.slice(1);
|
|
53
|
+
return '[' + content.join('') + '](' + target + ')';
|
|
54
|
+
},
|
|
55
|
+
$p: function (content) { return content.join('') + '\n\n'; },
|
|
56
|
+
};
|
|
57
|
+
var MarkdownBuilder = /** @class */ (function (_super) {
|
|
58
|
+
__extends(MarkdownBuilder, _super);
|
|
59
|
+
function MarkdownBuilder() {
|
|
60
|
+
var _this = _super.apply(this, __spreadArray([], __read(arguments), false)) || this;
|
|
61
|
+
_this.result = '';
|
|
62
|
+
return _this;
|
|
63
|
+
}
|
|
64
|
+
MarkdownBuilder.prototype.pushRichTextTag = function (tag, children) {
|
|
65
|
+
this.result += MARKDOWN_RICH_TEXT_ELEMENTS[tag](children);
|
|
66
|
+
};
|
|
67
|
+
return MarkdownBuilder;
|
|
68
|
+
}(string_1.StringBuilder));
|
|
69
|
+
function formatToMarkdownString(message, values, Builder) {
|
|
70
|
+
if (Builder === void 0) { Builder = MarkdownBuilder; }
|
|
71
|
+
if (typeof message === 'string')
|
|
72
|
+
return message;
|
|
73
|
+
var result = this.bindFormatValues(Builder, message, values);
|
|
74
|
+
// MarkdownBuilder always creates a single-element array with the string value.
|
|
75
|
+
return result[0];
|
|
76
|
+
}
|
|
77
|
+
exports.markdownFormatter = {
|
|
78
|
+
format: formatToMarkdownString,
|
|
79
|
+
builder: MarkdownBuilder,
|
|
80
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
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 { AnyIntlMessage, FormatterImplementation, FunctionTypes, RichTextFormattingMap } from '../types';
|
|
7
|
+
import * as React from 'react';
|
|
8
|
+
import { IntlManager } from '../intl-manager';
|
|
9
|
+
import { FormatBuilderConstructor } from '../format';
|
|
10
|
+
export type ReactHandlerEvent = React.MouseEvent | React.KeyboardEvent;
|
|
11
|
+
export type ReactClickHandler = (e: ReactHandlerEvent) => void;
|
|
12
|
+
export type ReactFunctionTypes = FunctionTypes<React.ReactNode, ReactClickHandler | {
|
|
13
|
+
onClick: ReactClickHandler;
|
|
14
|
+
} | {
|
|
15
|
+
onContextMenu: ReactClickHandler;
|
|
16
|
+
} | {
|
|
17
|
+
onClick: ReactClickHandler;
|
|
18
|
+
onContextMenu: ReactClickHandler;
|
|
19
|
+
}>;
|
|
20
|
+
export declare const DEFAULT_REACT_RICH_TEXT_ELEMENTS: RichTextFormattingMap<ReactFunctionTypes['hook']>;
|
|
21
|
+
export declare function formatReact(this: IntlManager, message: AnyIntlMessage, values: object, Builder: FormatBuilderConstructor<React.ReactElement>): React.ReactElement;
|
|
22
|
+
/**
|
|
23
|
+
* Create a new React formatter with the given rich text elements replacing the defaults. Use this
|
|
24
|
+
* function to inject custom components for things like links and paragraphs, which may be best
|
|
25
|
+
* suited to use components from a Design System or other library rather than native DOM elements.
|
|
26
|
+
*/
|
|
27
|
+
export declare function makeReactFormatter(richTextElements: RichTextFormattingMap<ReactFunctionTypes['hook']>): FormatterImplementation<ReactFunctionTypes, React.ReactElement, React.ReactNode>;
|
|
28
|
+
export declare const reactFormatter: FormatterImplementation<ReactFunctionTypes, React.ReactElement<any, string | React.JSXElementConstructor<any>>, React.ReactNode>;
|