@discord/intl 0.26.2 → 0.26.4
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 +24 -3
- package/dist/format.js +67 -18
- package/dist/formatters/react.d.ts +4 -1
- package/dist/formatters/react.js +1 -1
- package/dist/intl-manager.js +9 -1
- package/package.json +2 -2
- package/src/format.ts +99 -46
- package/src/formatters/react.ts +16 -6
- package/src/intl-manager.ts +8 -12
package/dist/format.d.ts
CHANGED
|
@@ -20,12 +20,33 @@ import { AstNode } from '@discord/intl-ast';
|
|
|
20
20
|
import type { RichTextTagNames } from './types';
|
|
21
21
|
import { FormatConfigType } from './data-formatters/config';
|
|
22
22
|
import { DataFormatters } from './data-formatters';
|
|
23
|
+
export interface BuilderContext {
|
|
24
|
+
keyPrefix: string;
|
|
25
|
+
}
|
|
23
26
|
export declare abstract class FormatBuilder<Result, ObjectType = Result extends object ? Result : never> {
|
|
27
|
+
context: BuilderContext;
|
|
28
|
+
constructor(context: BuilderContext);
|
|
24
29
|
abstract pushRichTextTag(tag: RichTextTagNames, children: Result[], control: Result[]): void;
|
|
25
30
|
abstract pushLiteralText(text: string): void;
|
|
26
31
|
abstract pushObject(value: ObjectType): void;
|
|
27
32
|
abstract finish(): Result[];
|
|
28
33
|
}
|
|
29
|
-
export type FormatBuilderConstructor<Result> = new () => FormatBuilder<Result>;
|
|
30
|
-
export
|
|
31
|
-
|
|
34
|
+
export type FormatBuilderConstructor<Result> = new (context: BuilderContext) => FormatBuilder<Result>;
|
|
35
|
+
export interface BindFormatValuesBase<FormatConfig extends FormatConfigType> {
|
|
36
|
+
originalMessage?: string;
|
|
37
|
+
nodes: string | AstNode[];
|
|
38
|
+
locales: string | string[];
|
|
39
|
+
dataFormatters: DataFormatters<FormatConfig>;
|
|
40
|
+
formatConfig: FormatConfig;
|
|
41
|
+
values: Record<string, string | object>;
|
|
42
|
+
currentPluralValue?: number;
|
|
43
|
+
keyPrefix: string;
|
|
44
|
+
}
|
|
45
|
+
export interface BindFormatValuesParams<Result, FormatConfig extends FormatConfigType> extends BindFormatValuesBase<FormatConfig> {
|
|
46
|
+
Builder: FormatBuilderConstructor<Result>;
|
|
47
|
+
}
|
|
48
|
+
export interface BindFormatValuesWithBuilderParams<T, ObjectType, Builder extends FormatBuilder<T, ObjectType>, FormatConfig extends FormatConfigType> extends BindFormatValuesBase<FormatConfig> {
|
|
49
|
+
builder: Builder;
|
|
50
|
+
}
|
|
51
|
+
export declare function bindFormatValuesWithBuilder<T, ObjectType, Builder extends FormatBuilder<T, ObjectType>, FormatConfig extends FormatConfigType>(params: BindFormatValuesWithBuilderParams<T, ObjectType, Builder, FormatConfig>): void;
|
|
52
|
+
export declare function bindFormatValues<Result, FormatConfig extends FormatConfigType>(params: BindFormatValuesParams<Result, FormatConfig>): Result[];
|
package/dist/format.js
CHANGED
|
@@ -31,6 +31,9 @@ function isRichTextTag(name) {
|
|
|
31
31
|
return name[0] === '$';
|
|
32
32
|
}
|
|
33
33
|
class FormatBuilder {
|
|
34
|
+
constructor(context) {
|
|
35
|
+
this.context = context;
|
|
36
|
+
}
|
|
34
37
|
}
|
|
35
38
|
exports.FormatBuilder = FormatBuilder;
|
|
36
39
|
class MissingValueError extends Error {
|
|
@@ -41,15 +44,16 @@ class MissingValueError extends Error {
|
|
|
41
44
|
this.nodeType = nodeType;
|
|
42
45
|
}
|
|
43
46
|
}
|
|
44
|
-
function bindFormatValuesWithBuilder(
|
|
47
|
+
function bindFormatValuesWithBuilder(params) {
|
|
45
48
|
var _a;
|
|
49
|
+
const { builder, originalMessage, nodes, locales, values, dataFormatters, formatConfig, currentPluralValue, keyPrefix, } = params;
|
|
46
50
|
// Hot path for static messages that are just parsed as a single string element.
|
|
47
51
|
if (nodes.length === 1 && typeof nodes[0] === 'string') {
|
|
48
52
|
builder.pushLiteralText(nodes[0]);
|
|
49
53
|
return;
|
|
50
54
|
}
|
|
51
|
-
let keyIndex = 0;
|
|
52
|
-
|
|
55
|
+
for (let keyIndex = 0; keyIndex < nodes.length; keyIndex++) {
|
|
56
|
+
const node = nodes[keyIndex];
|
|
53
57
|
if (typeof node === 'string') {
|
|
54
58
|
builder.pushLiteralText(node);
|
|
55
59
|
continue;
|
|
@@ -61,7 +65,7 @@ function bindFormatValuesWithBuilder(builder, nodes, locales, formatters, format
|
|
|
61
65
|
// numeric values are replaced, otherwise the value is completed ignored?
|
|
62
66
|
// Behavior copied from FormatJS directly.
|
|
63
67
|
if (typeof currentPluralValue === 'number') {
|
|
64
|
-
const value =
|
|
68
|
+
const value = dataFormatters.formatNumber(currentPluralValue);
|
|
65
69
|
builder.pushLiteralText(value);
|
|
66
70
|
}
|
|
67
71
|
continue;
|
|
@@ -100,7 +104,7 @@ function bindFormatValuesWithBuilder(builder, nodes, locales, formatters, format
|
|
|
100
104
|
: nodeStyle != null
|
|
101
105
|
? (0, icu_skeleton_parser_1.parseDateTimeSkeleton)(nodeStyle)
|
|
102
106
|
: undefined;
|
|
103
|
-
builder.pushLiteralText(
|
|
107
|
+
builder.pushLiteralText(dataFormatters.formatDate(value, style));
|
|
104
108
|
break;
|
|
105
109
|
}
|
|
106
110
|
case intl_ast_1.FormatJsNodeType.Time: {
|
|
@@ -112,7 +116,7 @@ function bindFormatValuesWithBuilder(builder, nodes, locales, formatters, format
|
|
|
112
116
|
: nodeStyle != null
|
|
113
117
|
? (0, icu_skeleton_parser_1.parseDateTimeSkeleton)(nodeStyle)
|
|
114
118
|
: undefined;
|
|
115
|
-
builder.pushLiteralText(
|
|
119
|
+
builder.pushLiteralText(dataFormatters.formatTime(value, style));
|
|
116
120
|
break;
|
|
117
121
|
}
|
|
118
122
|
case intl_ast_1.FormatJsNodeType.Number: {
|
|
@@ -127,15 +131,33 @@ function bindFormatValuesWithBuilder(builder, nodes, locales, formatters, format
|
|
|
127
131
|
const scaledValue =
|
|
128
132
|
// @ts-expect-error This is a weird cast that's not accurate, but works in the short term.
|
|
129
133
|
typeof value !== 'number' ? value : value * ((_a = style === null || style === void 0 ? void 0 : style.scale) !== null && _a !== void 0 ? _a : 1);
|
|
130
|
-
builder.pushLiteralText(
|
|
134
|
+
builder.pushLiteralText(dataFormatters.formatNumber(scaledValue, style));
|
|
131
135
|
break;
|
|
132
136
|
}
|
|
133
137
|
case intl_ast_1.FormatJsNodeType.Tag: {
|
|
134
138
|
const children = node[2 /* AstNodeIndices.Children */];
|
|
135
139
|
const control = node[3 /* AstNodeIndices.Control */];
|
|
136
|
-
const appliedChildren = bindFormatValues(
|
|
140
|
+
const appliedChildren = bindFormatValues({
|
|
141
|
+
Builder: builder.constructor,
|
|
142
|
+
nodes: children,
|
|
143
|
+
locales,
|
|
144
|
+
dataFormatters: dataFormatters,
|
|
145
|
+
formatConfig,
|
|
146
|
+
values,
|
|
147
|
+
currentPluralValue,
|
|
148
|
+
keyPrefix: `${keyPrefix}.${keyIndex}`,
|
|
149
|
+
});
|
|
137
150
|
const appliedControl = control != null
|
|
138
|
-
? bindFormatValues(
|
|
151
|
+
? bindFormatValues({
|
|
152
|
+
Builder: builder.constructor,
|
|
153
|
+
nodes: control,
|
|
154
|
+
locales,
|
|
155
|
+
dataFormatters: dataFormatters,
|
|
156
|
+
formatConfig,
|
|
157
|
+
values,
|
|
158
|
+
currentPluralValue,
|
|
159
|
+
keyPrefix: `${keyPrefix}.${keyIndex}-control`,
|
|
160
|
+
})
|
|
139
161
|
: [];
|
|
140
162
|
if (isRichTextTag(variableName)) {
|
|
141
163
|
builder.pushRichTextTag(variableName, appliedChildren, appliedControl);
|
|
@@ -144,7 +166,7 @@ function bindFormatValuesWithBuilder(builder, nodes, locales, formatters, format
|
|
|
144
166
|
if (typeof value !== 'function') {
|
|
145
167
|
throw `expected a function type for a Tag formatting value, ${variableName}. got ${typeof value}: ${value}`;
|
|
146
168
|
}
|
|
147
|
-
let chunks = value(appliedChildren, `${keyIndex
|
|
169
|
+
let chunks = value(appliedChildren, `${keyPrefix}.${keyIndex}`);
|
|
148
170
|
chunks = Array.isArray(chunks) ? chunks : [chunks];
|
|
149
171
|
for (const chunk of chunks) {
|
|
150
172
|
if (typeof chunk === 'string') {
|
|
@@ -164,7 +186,15 @@ function bindFormatValuesWithBuilder(builder, nodes, locales, formatters, format
|
|
|
164
186
|
if (option == null) {
|
|
165
187
|
throw `${requestedOption} is not a known option for select value ${variableName}. Valid options are ${Object.keys(options).join(', ')}`;
|
|
166
188
|
}
|
|
167
|
-
bindFormatValuesWithBuilder(
|
|
189
|
+
bindFormatValuesWithBuilder({
|
|
190
|
+
builder,
|
|
191
|
+
nodes: option,
|
|
192
|
+
locales,
|
|
193
|
+
dataFormatters,
|
|
194
|
+
formatConfig,
|
|
195
|
+
values,
|
|
196
|
+
keyPrefix: `${keyPrefix}.${keyIndex}`,
|
|
197
|
+
});
|
|
168
198
|
break;
|
|
169
199
|
}
|
|
170
200
|
case intl_ast_1.FormatJsNodeType.Plural: {
|
|
@@ -176,7 +206,7 @@ function bindFormatValuesWithBuilder(builder, nodes, locales, formatters, format
|
|
|
176
206
|
const exactSelector = `=${value}`;
|
|
177
207
|
if (exactSelector in options)
|
|
178
208
|
return options[exactSelector];
|
|
179
|
-
const rule =
|
|
209
|
+
const rule = dataFormatters.getPluralRules({ type: pluralType }).select(
|
|
180
210
|
// @ts-expect-error Assert this `as number` properly.
|
|
181
211
|
value - (offset !== null && offset !== void 0 ? offset : 0));
|
|
182
212
|
return (_a = options[rule]) !== null && _a !== void 0 ? _a : options.other;
|
|
@@ -184,22 +214,41 @@ function bindFormatValuesWithBuilder(builder, nodes, locales, formatters, format
|
|
|
184
214
|
if (option == null) {
|
|
185
215
|
throw `${value} is not a known option for plural value ${variableName}. Valid options are ${Object.keys(options).join(', ')}`;
|
|
186
216
|
}
|
|
187
|
-
bindFormatValuesWithBuilder(
|
|
188
|
-
|
|
189
|
-
|
|
217
|
+
bindFormatValuesWithBuilder({
|
|
218
|
+
builder,
|
|
219
|
+
nodes: option,
|
|
220
|
+
locales,
|
|
221
|
+
dataFormatters,
|
|
222
|
+
formatConfig,
|
|
223
|
+
values,
|
|
224
|
+
// @ts-expect-error assert this `as number` properly
|
|
225
|
+
currentPluralValue: value - (offset !== null && offset !== void 0 ? offset : 0),
|
|
226
|
+
keyPrefix: `${keyPrefix}.${keyIndex}`,
|
|
227
|
+
});
|
|
190
228
|
break;
|
|
191
229
|
}
|
|
192
230
|
}
|
|
193
231
|
}
|
|
194
232
|
}
|
|
195
|
-
function bindFormatValues(
|
|
196
|
-
const
|
|
233
|
+
function bindFormatValues(params) {
|
|
234
|
+
const { Builder, originalMessage, nodes, locales, dataFormatters, formatConfig, values, currentPluralValue, keyPrefix, } = params;
|
|
235
|
+
const builder = new Builder({ keyPrefix });
|
|
197
236
|
if (typeof nodes === 'string') {
|
|
198
237
|
builder.pushLiteralText(nodes);
|
|
199
238
|
return builder.finish();
|
|
200
239
|
}
|
|
201
240
|
else {
|
|
202
|
-
bindFormatValuesWithBuilder(
|
|
241
|
+
bindFormatValuesWithBuilder({
|
|
242
|
+
builder,
|
|
243
|
+
originalMessage,
|
|
244
|
+
nodes,
|
|
245
|
+
locales,
|
|
246
|
+
dataFormatters,
|
|
247
|
+
formatConfig,
|
|
248
|
+
values,
|
|
249
|
+
currentPluralValue,
|
|
250
|
+
keyPrefix,
|
|
251
|
+
});
|
|
203
252
|
return builder.finish();
|
|
204
253
|
}
|
|
205
254
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import type { AnyIntlMessage, FormatterImplementation, FunctionTypes, RichTextFormattingMap } from '../types';
|
|
7
7
|
import * as React from 'react';
|
|
8
8
|
import { IntlManager } from '../intl-manager';
|
|
9
|
-
import { FormatBuilderConstructor } from '../format';
|
|
9
|
+
import { BuilderContext, FormatBuilder, FormatBuilderConstructor } from '../format';
|
|
10
10
|
export type ReactHandlerEvent = React.MouseEvent | React.KeyboardEvent;
|
|
11
11
|
export type ReactClickHandler = (e: ReactHandlerEvent) => void;
|
|
12
12
|
export type ReactFunctionTypes = FunctionTypes<React.ReactNode, ReactClickHandler | {
|
|
@@ -18,6 +18,9 @@ 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 ReactBuilder = {
|
|
22
|
+
new (context: BuilderContext): FormatBuilder<React.ReactNode>;
|
|
23
|
+
};
|
|
21
24
|
/**
|
|
22
25
|
* A type representing any message that has been formatted using one of the
|
|
23
26
|
* default React formatting methods in the `@discord/intl` system. Use this
|
package/dist/formatters/react.js
CHANGED
|
@@ -29,7 +29,7 @@ function createReactBuilder(richTextElements) {
|
|
|
29
29
|
this.result = [];
|
|
30
30
|
}
|
|
31
31
|
pushRichTextTag(tag, children, control) {
|
|
32
|
-
this.result.push(richTextElements[tag](children, `${this._nodeKey++}`, control));
|
|
32
|
+
this.result.push(richTextElements[tag](children, `${this.context.keyPrefix}.tag-${this._nodeKey++}`, control));
|
|
33
33
|
}
|
|
34
34
|
pushLiteralText(text) {
|
|
35
35
|
const last = this.result[this.result.length - 1];
|
package/dist/intl-manager.js
CHANGED
|
@@ -93,7 +93,15 @@ class IntlManager {
|
|
|
93
93
|
return resolved.reserialize();
|
|
94
94
|
}
|
|
95
95
|
bindFormatValues(Builder, message, values) {
|
|
96
|
-
return (0, format_1.bindFormatValues)(
|
|
96
|
+
return (0, format_1.bindFormatValues)({
|
|
97
|
+
Builder,
|
|
98
|
+
nodes: message.ast,
|
|
99
|
+
locales: [this.currentLocale, this.defaultLocale],
|
|
100
|
+
dataFormatters: this.data,
|
|
101
|
+
formatConfig: this.formatConfig,
|
|
102
|
+
values,
|
|
103
|
+
keyPrefix: '',
|
|
104
|
+
});
|
|
97
105
|
}
|
|
98
106
|
}
|
|
99
107
|
exports.IntlManager = IntlManager;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@discord/intl",
|
|
3
|
-
"version": "0.26.
|
|
3
|
+
"version": "0.26.4",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Client runtime for managing messages and translations in a React project.",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"@formatjs/intl": "^2.10.1",
|
|
24
24
|
"@intrnl/xxhash64": "^0.1.2",
|
|
25
25
|
"intl-messageformat": "^10.5.11",
|
|
26
|
-
"@discord/intl-ast": "0.26.
|
|
26
|
+
"@discord/intl-ast": "0.26.4"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@formatjs/intl-durationformat": "^0.7.3",
|
package/src/format.ts
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
import {
|
|
21
21
|
parseDateTimeSkeleton,
|
|
22
22
|
parseNumberSkeleton,
|
|
23
|
-
parseNumberSkeletonFromString
|
|
23
|
+
parseNumberSkeletonFromString
|
|
24
24
|
} from '@formatjs/icu-skeleton-parser';
|
|
25
25
|
|
|
26
26
|
import { AstNode, AstNodeIndices, FormatJsNodeType } from '@discord/intl-ast';
|
|
@@ -36,13 +36,22 @@ function isRichTextTag(name: string) {
|
|
|
36
36
|
return name[0] === '$';
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
export interface BuilderContext {
|
|
40
|
+
keyPrefix: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
39
43
|
export abstract class FormatBuilder<Result, ObjectType = Result extends object ? Result : never> {
|
|
44
|
+
constructor(public context: BuilderContext) {}
|
|
40
45
|
abstract pushRichTextTag(tag: RichTextTagNames, children: Result[], control: Result[]): void;
|
|
41
46
|
abstract pushLiteralText(text: string): void;
|
|
42
47
|
abstract pushObject(value: ObjectType): void;
|
|
43
48
|
abstract finish(): Result[];
|
|
44
49
|
}
|
|
45
50
|
|
|
51
|
+
export type FormatBuilderConstructor<Result> = new (
|
|
52
|
+
context: BuilderContext,
|
|
53
|
+
) => FormatBuilder<Result>;
|
|
54
|
+
|
|
46
55
|
class MissingValueError extends Error {
|
|
47
56
|
constructor(
|
|
48
57
|
public variableName: string,
|
|
@@ -55,31 +64,56 @@ class MissingValueError extends Error {
|
|
|
55
64
|
}
|
|
56
65
|
}
|
|
57
66
|
|
|
58
|
-
export
|
|
67
|
+
export interface BindFormatValuesBase<FormatConfig extends FormatConfigType> {
|
|
68
|
+
originalMessage?: string;
|
|
69
|
+
nodes: string | AstNode[];
|
|
70
|
+
locales: string | string[];
|
|
71
|
+
dataFormatters: DataFormatters<FormatConfig>;
|
|
72
|
+
formatConfig: FormatConfig;
|
|
73
|
+
values: Record<string, string | object>;
|
|
74
|
+
currentPluralValue?: number;
|
|
75
|
+
keyPrefix: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface BindFormatValuesParams<Result, FormatConfig extends FormatConfigType>
|
|
79
|
+
extends BindFormatValuesBase<FormatConfig> {
|
|
80
|
+
Builder: FormatBuilderConstructor<Result>;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface BindFormatValuesWithBuilderParams<
|
|
84
|
+
T,
|
|
85
|
+
ObjectType,
|
|
86
|
+
Builder extends FormatBuilder<T, ObjectType>,
|
|
87
|
+
FormatConfig extends FormatConfigType,
|
|
88
|
+
> extends BindFormatValuesBase<FormatConfig> {
|
|
89
|
+
builder: Builder;
|
|
90
|
+
}
|
|
59
91
|
|
|
60
92
|
export function bindFormatValuesWithBuilder<
|
|
61
93
|
T,
|
|
62
94
|
ObjectType,
|
|
63
95
|
Builder extends FormatBuilder<T, ObjectType>,
|
|
64
96
|
FormatConfig extends FormatConfigType,
|
|
65
|
-
>(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
97
|
+
>(params: BindFormatValuesWithBuilderParams<T, ObjectType, Builder, FormatConfig>) {
|
|
98
|
+
const {
|
|
99
|
+
builder,
|
|
100
|
+
originalMessage,
|
|
101
|
+
nodes,
|
|
102
|
+
locales,
|
|
103
|
+
values,
|
|
104
|
+
dataFormatters,
|
|
105
|
+
formatConfig,
|
|
106
|
+
currentPluralValue,
|
|
107
|
+
keyPrefix,
|
|
108
|
+
} = params;
|
|
75
109
|
// Hot path for static messages that are just parsed as a single string element.
|
|
76
110
|
if (nodes.length === 1 && typeof nodes[0] === 'string') {
|
|
77
111
|
builder.pushLiteralText(nodes[0]);
|
|
78
112
|
return;
|
|
79
113
|
}
|
|
80
114
|
|
|
81
|
-
let keyIndex = 0;
|
|
82
|
-
|
|
115
|
+
for (let keyIndex = 0; keyIndex < nodes.length; keyIndex++) {
|
|
116
|
+
const node = nodes[keyIndex];
|
|
83
117
|
if (typeof node === 'string') {
|
|
84
118
|
builder.pushLiteralText(node);
|
|
85
119
|
continue;
|
|
@@ -91,7 +125,7 @@ export function bindFormatValuesWithBuilder<
|
|
|
91
125
|
// numeric values are replaced, otherwise the value is completed ignored?
|
|
92
126
|
// Behavior copied from FormatJS directly.
|
|
93
127
|
if (typeof currentPluralValue === 'number') {
|
|
94
|
-
const value =
|
|
128
|
+
const value = dataFormatters.formatNumber(currentPluralValue);
|
|
95
129
|
builder.pushLiteralText(value);
|
|
96
130
|
}
|
|
97
131
|
continue;
|
|
@@ -132,7 +166,7 @@ export function bindFormatValuesWithBuilder<
|
|
|
132
166
|
: nodeStyle != null
|
|
133
167
|
? parseDateTimeSkeleton(nodeStyle)
|
|
134
168
|
: undefined;
|
|
135
|
-
builder.pushLiteralText(
|
|
169
|
+
builder.pushLiteralText(dataFormatters.formatDate(value as number | Date, style));
|
|
136
170
|
break;
|
|
137
171
|
}
|
|
138
172
|
case FormatJsNodeType.Time: {
|
|
@@ -145,7 +179,7 @@ export function bindFormatValuesWithBuilder<
|
|
|
145
179
|
: nodeStyle != null
|
|
146
180
|
? parseDateTimeSkeleton(nodeStyle)
|
|
147
181
|
: undefined;
|
|
148
|
-
builder.pushLiteralText(
|
|
182
|
+
builder.pushLiteralText(dataFormatters.formatTime(value as number | Date, style));
|
|
149
183
|
break;
|
|
150
184
|
}
|
|
151
185
|
case FormatJsNodeType.Number: {
|
|
@@ -163,33 +197,35 @@ export function bindFormatValuesWithBuilder<
|
|
|
163
197
|
const scaledValue =
|
|
164
198
|
// @ts-expect-error This is a weird cast that's not accurate, but works in the short term.
|
|
165
199
|
typeof value !== 'number' ? (value as number) : (value as number) * (style?.scale ?? 1);
|
|
166
|
-
builder.pushLiteralText(
|
|
200
|
+
builder.pushLiteralText(dataFormatters.formatNumber(scaledValue, style));
|
|
167
201
|
break;
|
|
168
202
|
}
|
|
169
203
|
|
|
170
204
|
case FormatJsNodeType.Tag: {
|
|
171
205
|
const children = node[AstNodeIndices.Children];
|
|
172
206
|
const control = node[AstNodeIndices.Control];
|
|
173
|
-
const appliedChildren = bindFormatValues(
|
|
174
|
-
builder.constructor as FormatBuilderConstructor<T>,
|
|
175
|
-
children,
|
|
207
|
+
const appliedChildren = bindFormatValues({
|
|
208
|
+
Builder: builder.constructor as FormatBuilderConstructor<T>,
|
|
209
|
+
nodes: children,
|
|
176
210
|
locales,
|
|
177
|
-
|
|
211
|
+
dataFormatters: dataFormatters,
|
|
178
212
|
formatConfig,
|
|
179
213
|
values,
|
|
180
214
|
currentPluralValue,
|
|
181
|
-
|
|
215
|
+
keyPrefix: `${keyPrefix}.${keyIndex}`,
|
|
216
|
+
});
|
|
182
217
|
const appliedControl =
|
|
183
218
|
control != null
|
|
184
|
-
? bindFormatValues(
|
|
185
|
-
builder.constructor as FormatBuilderConstructor<T>,
|
|
186
|
-
control,
|
|
219
|
+
? bindFormatValues({
|
|
220
|
+
Builder: builder.constructor as FormatBuilderConstructor<T>,
|
|
221
|
+
nodes: control,
|
|
187
222
|
locales,
|
|
188
|
-
|
|
223
|
+
dataFormatters: dataFormatters,
|
|
189
224
|
formatConfig,
|
|
190
225
|
values,
|
|
191
226
|
currentPluralValue,
|
|
192
|
-
|
|
227
|
+
keyPrefix: `${keyPrefix}.${keyIndex}-control`,
|
|
228
|
+
})
|
|
193
229
|
: [];
|
|
194
230
|
if (isRichTextTag(variableName)) {
|
|
195
231
|
builder.pushRichTextTag(
|
|
@@ -201,7 +237,7 @@ export function bindFormatValuesWithBuilder<
|
|
|
201
237
|
if (typeof value !== 'function') {
|
|
202
238
|
throw `expected a function type for a Tag formatting value, ${variableName}. got ${typeof value}: ${value}`;
|
|
203
239
|
}
|
|
204
|
-
let chunks = value(appliedChildren, `${keyIndex
|
|
240
|
+
let chunks = value(appliedChildren, `${keyPrefix}.${keyIndex}`);
|
|
205
241
|
chunks = Array.isArray(chunks) ? chunks : [chunks];
|
|
206
242
|
for (const chunk of chunks) {
|
|
207
243
|
if (typeof chunk === 'string') {
|
|
@@ -221,7 +257,15 @@ export function bindFormatValuesWithBuilder<
|
|
|
221
257
|
if (option == null) {
|
|
222
258
|
throw `${requestedOption} is not a known option for select value ${variableName}. Valid options are ${Object.keys(options).join(', ')}`;
|
|
223
259
|
}
|
|
224
|
-
bindFormatValuesWithBuilder(
|
|
260
|
+
bindFormatValuesWithBuilder({
|
|
261
|
+
builder,
|
|
262
|
+
nodes: option,
|
|
263
|
+
locales,
|
|
264
|
+
dataFormatters,
|
|
265
|
+
formatConfig,
|
|
266
|
+
values,
|
|
267
|
+
keyPrefix: `${keyPrefix}.${keyIndex}`,
|
|
268
|
+
});
|
|
225
269
|
break;
|
|
226
270
|
}
|
|
227
271
|
|
|
@@ -233,7 +277,7 @@ export function bindFormatValuesWithBuilder<
|
|
|
233
277
|
const exactSelector = `=${value}`;
|
|
234
278
|
if (exactSelector in options) return options[exactSelector];
|
|
235
279
|
|
|
236
|
-
const rule =
|
|
280
|
+
const rule = dataFormatters.getPluralRules({ type: pluralType }).select(
|
|
237
281
|
// @ts-expect-error Assert this `as number` properly.
|
|
238
282
|
(value as number) - (offset ?? 0),
|
|
239
283
|
);
|
|
@@ -244,16 +288,18 @@ export function bindFormatValuesWithBuilder<
|
|
|
244
288
|
throw `${value} is not a known option for plural value ${variableName}. Valid options are ${Object.keys(options).join(', ')}`;
|
|
245
289
|
}
|
|
246
290
|
|
|
247
|
-
bindFormatValuesWithBuilder(
|
|
291
|
+
bindFormatValuesWithBuilder({
|
|
248
292
|
builder,
|
|
249
|
-
option,
|
|
293
|
+
nodes: option,
|
|
250
294
|
locales,
|
|
251
|
-
|
|
295
|
+
dataFormatters,
|
|
252
296
|
formatConfig,
|
|
253
297
|
values,
|
|
254
298
|
// @ts-expect-error assert this `as number` properly
|
|
255
|
-
(value as number) - (offset ?? 0),
|
|
256
|
-
|
|
299
|
+
currentPluralValue: (value as number) - (offset ?? 0),
|
|
300
|
+
keyPrefix: `${keyPrefix}.${keyIndex}`,
|
|
301
|
+
});
|
|
302
|
+
|
|
257
303
|
break;
|
|
258
304
|
}
|
|
259
305
|
}
|
|
@@ -261,28 +307,35 @@ export function bindFormatValuesWithBuilder<
|
|
|
261
307
|
}
|
|
262
308
|
|
|
263
309
|
export function bindFormatValues<Result, FormatConfig extends FormatConfigType>(
|
|
264
|
-
|
|
265
|
-
nodes: string | AstNode[],
|
|
266
|
-
locales: string | string[],
|
|
267
|
-
dataFormatters: DataFormatters<FormatConfig>,
|
|
268
|
-
formatConfig: FormatConfig,
|
|
269
|
-
values: Record<string, string | object> = {},
|
|
270
|
-
currentPluralValue?: number,
|
|
310
|
+
params: BindFormatValuesParams<Result, FormatConfig>,
|
|
271
311
|
): Result[] {
|
|
272
|
-
const
|
|
312
|
+
const {
|
|
313
|
+
Builder,
|
|
314
|
+
originalMessage,
|
|
315
|
+
nodes,
|
|
316
|
+
locales,
|
|
317
|
+
dataFormatters,
|
|
318
|
+
formatConfig,
|
|
319
|
+
values,
|
|
320
|
+
currentPluralValue,
|
|
321
|
+
keyPrefix,
|
|
322
|
+
} = params;
|
|
323
|
+
const builder = new Builder({ keyPrefix });
|
|
273
324
|
if (typeof nodes === 'string') {
|
|
274
325
|
builder.pushLiteralText(nodes);
|
|
275
326
|
return builder.finish();
|
|
276
327
|
} else {
|
|
277
|
-
bindFormatValuesWithBuilder(
|
|
328
|
+
bindFormatValuesWithBuilder({
|
|
278
329
|
builder,
|
|
330
|
+
originalMessage,
|
|
279
331
|
nodes,
|
|
280
332
|
locales,
|
|
281
333
|
dataFormatters,
|
|
282
334
|
formatConfig,
|
|
283
335
|
values,
|
|
284
336
|
currentPluralValue,
|
|
285
|
-
|
|
337
|
+
keyPrefix,
|
|
338
|
+
});
|
|
286
339
|
return builder.finish();
|
|
287
340
|
}
|
|
288
341
|
}
|
package/src/formatters/react.ts
CHANGED
|
@@ -8,11 +8,11 @@ import type {
|
|
|
8
8
|
FormatterImplementation,
|
|
9
9
|
FunctionTypes,
|
|
10
10
|
RichTextFormattingMap,
|
|
11
|
-
RichTextTagNames
|
|
11
|
+
RichTextTagNames
|
|
12
12
|
} from '../types';
|
|
13
13
|
import * as React from 'react';
|
|
14
14
|
import { IntlManager } from '../intl-manager';
|
|
15
|
-
import { FormatBuilder, FormatBuilderConstructor } from '../format';
|
|
15
|
+
import { BuilderContext, FormatBuilder, FormatBuilderConstructor } from '../format';
|
|
16
16
|
|
|
17
17
|
export type ReactHandlerEvent = React.MouseEvent | React.KeyboardEvent;
|
|
18
18
|
export type ReactClickHandler = (e: ReactHandlerEvent) => void;
|
|
@@ -35,6 +35,10 @@ export const DEFAULT_REACT_RICH_TEXT_ELEMENTS: RichTextFormattingMap<ReactFuncti
|
|
|
35
35
|
$p: (content, key) => h('p', { key }, content),
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
+
export type ReactBuilder = {
|
|
39
|
+
new (context: BuilderContext): FormatBuilder<React.ReactNode>;
|
|
40
|
+
};
|
|
41
|
+
|
|
38
42
|
/**
|
|
39
43
|
* Creates a new `FormatBuilder` class that constructs a React element tree
|
|
40
44
|
* from the message using the given `richTextElements` to apply formatting.
|
|
@@ -42,9 +46,9 @@ export const DEFAULT_REACT_RICH_TEXT_ELEMENTS: RichTextFormattingMap<ReactFuncti
|
|
|
42
46
|
* overrides for rendering elements like links, paragraphs, and code blocks.
|
|
43
47
|
* @param richTextElements
|
|
44
48
|
*/
|
|
45
|
-
function createReactBuilder(
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
function createReactBuilder(
|
|
50
|
+
richTextElements: RichTextFormattingMap<ReactFunctionTypes['hook']>,
|
|
51
|
+
): ReactBuilder {
|
|
48
52
|
return class extends FormatBuilder<React.ReactNode> {
|
|
49
53
|
_nodeKey: number = 0;
|
|
50
54
|
result: React.ReactNode[] = [];
|
|
@@ -54,7 +58,13 @@ function createReactBuilder(richTextElements: RichTextFormattingMap<ReactFunctio
|
|
|
54
58
|
children: React.ReactNode[],
|
|
55
59
|
control: React.ReactNode[],
|
|
56
60
|
) {
|
|
57
|
-
this.result.push(
|
|
61
|
+
this.result.push(
|
|
62
|
+
richTextElements[tag](
|
|
63
|
+
children,
|
|
64
|
+
`${this.context.keyPrefix}.tag-${this._nodeKey++}`,
|
|
65
|
+
control,
|
|
66
|
+
),
|
|
67
|
+
);
|
|
58
68
|
}
|
|
59
69
|
|
|
60
70
|
pushLiteralText(text: string) {
|
package/src/intl-manager.ts
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import { DEFAULT_FORMAT_CONFIG, type FormatConfigType } from './data-formatters/config';
|
|
2
2
|
import { bindFormatValues, FormatBuilderConstructor } from './format';
|
|
3
|
-
import type {
|
|
4
|
-
FormatterImplementation,
|
|
5
|
-
IntlMessageGetter,
|
|
6
|
-
RequiredFormatValues,
|
|
7
|
-
TypedIntlMessageGetter,
|
|
8
|
-
} from './types';
|
|
3
|
+
import type { FormatterImplementation, IntlMessageGetter, RequiredFormatValues, TypedIntlMessageGetter } from './types';
|
|
9
4
|
import { InternalIntlMessage } from './message';
|
|
10
5
|
import { DataFormatters, makeDataFormatters } from './data-formatters';
|
|
11
6
|
|
|
@@ -193,13 +188,14 @@ export class IntlManager<
|
|
|
193
188
|
message: InternalIntlMessage,
|
|
194
189
|
values: Record<string, any>,
|
|
195
190
|
): T[] {
|
|
196
|
-
return bindFormatValues(
|
|
191
|
+
return bindFormatValues({
|
|
197
192
|
Builder,
|
|
198
|
-
message.ast,
|
|
199
|
-
[this.currentLocale, this.defaultLocale],
|
|
200
|
-
this.data,
|
|
201
|
-
this.formatConfig,
|
|
193
|
+
nodes: message.ast,
|
|
194
|
+
locales: [this.currentLocale, this.defaultLocale],
|
|
195
|
+
dataFormatters: this.data,
|
|
196
|
+
formatConfig: this.formatConfig,
|
|
202
197
|
values,
|
|
203
|
-
|
|
198
|
+
keyPrefix: '',
|
|
199
|
+
});
|
|
204
200
|
}
|
|
205
201
|
}
|