@messagevisor/react-intl-compat 0.0.1 → 0.1.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.
- package/CHANGELOG.md +16 -0
- package/LICENSE +21 -0
- package/README.md +7 -0
- package/jest.config.js +14 -0
- package/lib/IntlContext.d.ts +2 -0
- package/lib/IntlContext.js +50 -0
- package/lib/IntlContext.js.map +1 -0
- package/lib/components.d.ts +76 -0
- package/lib/components.js +188 -0
- package/lib/components.js.map +1 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.js +20 -0
- package/lib/index.js.map +1 -0
- package/lib/intl.d.ts +37 -0
- package/lib/intl.js +97 -0
- package/lib/intl.js.map +1 -0
- package/package.json +52 -13
- package/src/IntlContext.tsx +23 -0
- package/src/compat.spec.tsx +469 -0
- package/src/components.tsx +249 -0
- package/src/index.ts +3 -0
- package/src/intl.ts +199 -0
- package/tsconfig.cjs.json +13 -0
- package/tsconfig.typecheck.json +4 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { MessagevisorContext } from "@messagevisor/react";
|
|
4
|
+
|
|
5
|
+
import { useIntl } from "./IntlContext";
|
|
6
|
+
import { mergeRichTextValues, type IntlMessageValues, type WithIntlProps } from "./intl";
|
|
7
|
+
|
|
8
|
+
function useIntlContext() {
|
|
9
|
+
const context = React.useContext(MessagevisorContext);
|
|
10
|
+
|
|
11
|
+
if (!context) {
|
|
12
|
+
throw new Error("Formatted components must be used within MessagevisorProvider.");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return context;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function renderOutput(
|
|
19
|
+
output: React.ReactNode,
|
|
20
|
+
textComponent?: React.ElementType,
|
|
21
|
+
tagName?: React.ElementType,
|
|
22
|
+
wrapRichTextChunksInFragment?: boolean,
|
|
23
|
+
) {
|
|
24
|
+
const Component = tagName || textComponent;
|
|
25
|
+
const content =
|
|
26
|
+
wrapRichTextChunksInFragment !== false && Array.isArray(output) ? (
|
|
27
|
+
<React.Fragment>
|
|
28
|
+
{output.map((chunk, index) => (
|
|
29
|
+
<React.Fragment key={index}>{chunk}</React.Fragment>
|
|
30
|
+
))}
|
|
31
|
+
</React.Fragment>
|
|
32
|
+
) : (
|
|
33
|
+
output
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
if (!Component) {
|
|
37
|
+
return content;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return <Component>{content}</Component>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function FormattedMessage(props: {
|
|
44
|
+
id?: string;
|
|
45
|
+
defaultMessage?: string;
|
|
46
|
+
description?: string;
|
|
47
|
+
values?: IntlMessageValues;
|
|
48
|
+
tagName?: React.ElementType;
|
|
49
|
+
children?: (nodes: React.ReactNode) => React.ReactNode;
|
|
50
|
+
}) {
|
|
51
|
+
const intl = useIntl();
|
|
52
|
+
const context = useIntlContext();
|
|
53
|
+
const values = mergeRichTextValues(
|
|
54
|
+
context.defaultRichTextElements,
|
|
55
|
+
props.values,
|
|
56
|
+
props.defaultMessage ||
|
|
57
|
+
(props.id
|
|
58
|
+
? intl.messagevisor.getRawTranslation(props.id as any, {
|
|
59
|
+
defaultTranslation: props.defaultMessage,
|
|
60
|
+
})
|
|
61
|
+
: undefined),
|
|
62
|
+
);
|
|
63
|
+
const output = intl.formatMessage(
|
|
64
|
+
{
|
|
65
|
+
id: props.id as any,
|
|
66
|
+
defaultMessage: props.defaultMessage,
|
|
67
|
+
description: props.description,
|
|
68
|
+
},
|
|
69
|
+
values,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
if (props.children) {
|
|
73
|
+
return <>{props.children(output as React.ReactNode)}</>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<>
|
|
78
|
+
{renderOutput(
|
|
79
|
+
output as React.ReactNode,
|
|
80
|
+
context.textComponent,
|
|
81
|
+
props.tagName,
|
|
82
|
+
context.wrapRichTextChunksInFragment,
|
|
83
|
+
)}
|
|
84
|
+
</>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function FormattedDate(props: {
|
|
89
|
+
value: Date | number | string;
|
|
90
|
+
format?: any;
|
|
91
|
+
children?: (value: string) => React.ReactNode;
|
|
92
|
+
}) {
|
|
93
|
+
const intl = useIntl();
|
|
94
|
+
const context = useIntlContext();
|
|
95
|
+
const output = intl.formatDate(props.value, props.format);
|
|
96
|
+
|
|
97
|
+
if (props.children) return <>{props.children(output)}</>;
|
|
98
|
+
|
|
99
|
+
return <>{renderOutput(output, context.textComponent)}</>;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function FormattedTime(props: {
|
|
103
|
+
value: Date | number | string;
|
|
104
|
+
format?: any;
|
|
105
|
+
children?: (value: string) => React.ReactNode;
|
|
106
|
+
}) {
|
|
107
|
+
const intl = useIntl();
|
|
108
|
+
const context = useIntlContext();
|
|
109
|
+
const output = intl.formatTime(props.value, props.format);
|
|
110
|
+
|
|
111
|
+
if (props.children) return <>{props.children(output)}</>;
|
|
112
|
+
|
|
113
|
+
return <>{renderOutput(output, context.textComponent)}</>;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function FormattedNumber(props: {
|
|
117
|
+
value: number;
|
|
118
|
+
format?: any;
|
|
119
|
+
children?: (value: string) => React.ReactNode;
|
|
120
|
+
}) {
|
|
121
|
+
const intl = useIntl();
|
|
122
|
+
const context = useIntlContext();
|
|
123
|
+
const output = intl.formatNumber(props.value, props.format);
|
|
124
|
+
|
|
125
|
+
if (props.children) return <>{props.children(output)}</>;
|
|
126
|
+
|
|
127
|
+
return <>{renderOutput(output, context.textComponent)}</>;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function FormattedRelativeTime(props: {
|
|
131
|
+
value: number;
|
|
132
|
+
unit: Intl.RelativeTimeFormatUnit;
|
|
133
|
+
format?: any;
|
|
134
|
+
children?: (value: string) => React.ReactNode;
|
|
135
|
+
}) {
|
|
136
|
+
const intl = useIntl();
|
|
137
|
+
const context = useIntlContext();
|
|
138
|
+
const output = intl.formatRelativeTime(props.value, props.unit, props.format);
|
|
139
|
+
|
|
140
|
+
if (props.children) return <>{props.children(output)}</>;
|
|
141
|
+
|
|
142
|
+
return <>{renderOutput(output, context.textComponent)}</>;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function FormattedList(props: {
|
|
146
|
+
value: string[];
|
|
147
|
+
options?: any;
|
|
148
|
+
children?: (value: string) => React.ReactNode;
|
|
149
|
+
}) {
|
|
150
|
+
const intl = useIntl();
|
|
151
|
+
const context = useIntlContext();
|
|
152
|
+
const output = intl.formatList(props.value, props.options);
|
|
153
|
+
|
|
154
|
+
if (props.children) return <>{props.children(output)}</>;
|
|
155
|
+
|
|
156
|
+
return <>{renderOutput(output, context.textComponent)}</>;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function FormattedDisplayName(props: {
|
|
160
|
+
value: string;
|
|
161
|
+
type: string;
|
|
162
|
+
style?: string;
|
|
163
|
+
fallback?: string;
|
|
164
|
+
children?: (value: string | undefined) => React.ReactNode;
|
|
165
|
+
}) {
|
|
166
|
+
const intl = useIntl();
|
|
167
|
+
const context = useIntlContext();
|
|
168
|
+
const output = intl.formatDisplayName(props.value, {
|
|
169
|
+
type: props.type,
|
|
170
|
+
style: props.style,
|
|
171
|
+
fallback: props.fallback,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
if (props.children) return <>{props.children(output)}</>;
|
|
175
|
+
|
|
176
|
+
return <>{renderOutput(output || "", context.textComponent)}</>;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function FormattedPlural(props: {
|
|
180
|
+
value: number;
|
|
181
|
+
zero?: React.ReactNode;
|
|
182
|
+
one?: React.ReactNode;
|
|
183
|
+
two?: React.ReactNode;
|
|
184
|
+
few?: React.ReactNode;
|
|
185
|
+
many?: React.ReactNode;
|
|
186
|
+
other: React.ReactNode;
|
|
187
|
+
}) {
|
|
188
|
+
const intl = useIntl();
|
|
189
|
+
const context = useIntlContext();
|
|
190
|
+
const category = intl.formatPlural(props.value);
|
|
191
|
+
const byCategory: Record<string, React.ReactNode> = {
|
|
192
|
+
zero: props.zero,
|
|
193
|
+
one: props.one,
|
|
194
|
+
two: props.two,
|
|
195
|
+
few: props.few,
|
|
196
|
+
many: props.many,
|
|
197
|
+
other: props.other,
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
return <>{renderOutput(byCategory[category] || props.other, context.textComponent)}</>;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function FormattedNumberParts(props: {
|
|
204
|
+
value: number;
|
|
205
|
+
format?: any;
|
|
206
|
+
children: (parts: Intl.NumberFormatPart[]) => React.ReactNode;
|
|
207
|
+
}) {
|
|
208
|
+
const intl = useIntl();
|
|
209
|
+
return <>{props.children(intl.formatNumberToParts(props.value, props.format))}</>;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function FormattedDateParts(props: {
|
|
213
|
+
value: Date | number | string;
|
|
214
|
+
format?: any;
|
|
215
|
+
children: (parts: Intl.DateTimeFormatPart[]) => React.ReactNode;
|
|
216
|
+
}) {
|
|
217
|
+
const intl = useIntl();
|
|
218
|
+
return <>{props.children(intl.formatDateToParts(props.value, props.format))}</>;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function FormattedTimeParts(props: {
|
|
222
|
+
value: Date | number | string;
|
|
223
|
+
format?: any;
|
|
224
|
+
children: (parts: Intl.DateTimeFormatPart[]) => React.ReactNode;
|
|
225
|
+
}) {
|
|
226
|
+
const intl = useIntl();
|
|
227
|
+
return <>{props.children(intl.formatTimeToParts(props.value, props.format))}</>;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function FormattedListParts(props: {
|
|
231
|
+
value: string[];
|
|
232
|
+
options?: any;
|
|
233
|
+
children: (parts: any[]) => React.ReactNode;
|
|
234
|
+
}) {
|
|
235
|
+
const intl = useIntl();
|
|
236
|
+
return <>{props.children(intl.formatListToParts(props.value, props.options))}</>;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function injectIntl<P extends WithIntlProps>(Component: React.ComponentType<P>) {
|
|
240
|
+
function WrappedComponent(props: Omit<P, keyof WithIntlProps>) {
|
|
241
|
+
const intl = useIntl();
|
|
242
|
+
|
|
243
|
+
return <Component {...(props as P)} intl={intl} />;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
WrappedComponent.displayName = `injectIntl(${Component.displayName || Component.name || "Component"})`;
|
|
247
|
+
|
|
248
|
+
return WrappedComponent;
|
|
249
|
+
}
|
package/src/index.ts
ADDED
package/src/intl.ts
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
type EvaluationOptions,
|
|
5
|
+
type TranslateOptions,
|
|
6
|
+
type MessageFormatResult,
|
|
7
|
+
type MessagePrimitiveValue,
|
|
8
|
+
type MessageValue,
|
|
9
|
+
type MessageValues,
|
|
10
|
+
type Messagevisor,
|
|
11
|
+
} from "@messagevisor/sdk";
|
|
12
|
+
import type {
|
|
13
|
+
FormatDateTimePresetOptions,
|
|
14
|
+
FormatNumberPresetOptions,
|
|
15
|
+
FormatPresets,
|
|
16
|
+
FormatRelativeTimePresetOptions,
|
|
17
|
+
LocaleKey,
|
|
18
|
+
MessageKey,
|
|
19
|
+
} from "@messagevisor/types";
|
|
20
|
+
|
|
21
|
+
export type IntlMessageValues = MessageValues<React.ReactNode>;
|
|
22
|
+
export type PrimitiveMessageValues = Record<string, MessagePrimitiveValue>;
|
|
23
|
+
|
|
24
|
+
export interface MessageDescriptor {
|
|
25
|
+
id?: string;
|
|
26
|
+
defaultMessage?: string;
|
|
27
|
+
description?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function defineMessage<T extends MessageDescriptor>(message: T): T {
|
|
31
|
+
return message;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function defineMessages<T extends Record<string, MessageDescriptor>>(messages: T): T {
|
|
35
|
+
return messages;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface IntlShape {
|
|
39
|
+
locale: LocaleKey;
|
|
40
|
+
messages?: Record<string, string>;
|
|
41
|
+
formats?: FormatPresets;
|
|
42
|
+
timeZone?: string;
|
|
43
|
+
messagevisor: Messagevisor;
|
|
44
|
+
formatMessage(
|
|
45
|
+
descriptor: MessageDescriptor,
|
|
46
|
+
values?: PrimitiveMessageValues,
|
|
47
|
+
options?: TranslateOptions,
|
|
48
|
+
): string;
|
|
49
|
+
formatMessage(
|
|
50
|
+
descriptor: MessageDescriptor,
|
|
51
|
+
values: IntlMessageValues,
|
|
52
|
+
options?: TranslateOptions,
|
|
53
|
+
): MessageFormatResult<React.ReactNode>;
|
|
54
|
+
formatDate(
|
|
55
|
+
value: Date | number | string,
|
|
56
|
+
format?: string | FormatDateTimePresetOptions,
|
|
57
|
+
options?: EvaluationOptions,
|
|
58
|
+
): string;
|
|
59
|
+
formatDateToParts(
|
|
60
|
+
value: Date | number | string,
|
|
61
|
+
format?: string | FormatDateTimePresetOptions,
|
|
62
|
+
options?: EvaluationOptions,
|
|
63
|
+
): Intl.DateTimeFormatPart[];
|
|
64
|
+
formatTime(
|
|
65
|
+
value: Date | number | string,
|
|
66
|
+
format?: string | FormatDateTimePresetOptions,
|
|
67
|
+
options?: EvaluationOptions,
|
|
68
|
+
): string;
|
|
69
|
+
formatTimeToParts(
|
|
70
|
+
value: Date | number | string,
|
|
71
|
+
format?: string | FormatDateTimePresetOptions,
|
|
72
|
+
options?: EvaluationOptions,
|
|
73
|
+
): Intl.DateTimeFormatPart[];
|
|
74
|
+
formatNumber(
|
|
75
|
+
value: number,
|
|
76
|
+
format?: string | FormatNumberPresetOptions,
|
|
77
|
+
options?: EvaluationOptions,
|
|
78
|
+
): string;
|
|
79
|
+
formatNumberToParts(
|
|
80
|
+
value: number,
|
|
81
|
+
format?: string | FormatNumberPresetOptions,
|
|
82
|
+
options?: EvaluationOptions,
|
|
83
|
+
): Intl.NumberFormatPart[];
|
|
84
|
+
formatRelativeTime(
|
|
85
|
+
value: number,
|
|
86
|
+
unit: Intl.RelativeTimeFormatUnit,
|
|
87
|
+
format?: string | FormatRelativeTimePresetOptions,
|
|
88
|
+
options?: EvaluationOptions,
|
|
89
|
+
): string;
|
|
90
|
+
formatPlural(value: number, options?: Intl.PluralRulesOptions): string;
|
|
91
|
+
formatList(values: string[], options?: any): string;
|
|
92
|
+
formatListToParts(values: string[], options?: any): any[];
|
|
93
|
+
formatDisplayName(value: string, options?: any): string | undefined;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function createIntlFromMessagevisor(
|
|
97
|
+
messagevisor: Messagevisor,
|
|
98
|
+
config: Partial<Pick<IntlShape, "locale" | "messages" | "formats" | "timeZone">> = {},
|
|
99
|
+
): IntlShape {
|
|
100
|
+
const locale = messagevisor.getLocale() || config.locale || "";
|
|
101
|
+
|
|
102
|
+
const requireICUModule = (message: string, translation: MessageFormatResult<React.ReactNode>) => {
|
|
103
|
+
if (translation !== message) {
|
|
104
|
+
return translation;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (!/[<{]/.test(message)) {
|
|
108
|
+
return translation;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
throw new Error(
|
|
112
|
+
"Message formatting requires a Messagevisor instance configured with createICUModule().",
|
|
113
|
+
);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const formatDescriptorMessage = (
|
|
117
|
+
descriptor: MessageDescriptor,
|
|
118
|
+
values?: MessageValues<React.ReactNode>,
|
|
119
|
+
options?: TranslateOptions,
|
|
120
|
+
) => {
|
|
121
|
+
const translationOptions =
|
|
122
|
+
typeof descriptor.defaultMessage !== "undefined" &&
|
|
123
|
+
typeof options?.defaultTranslation === "undefined"
|
|
124
|
+
? { ...options, defaultTranslation: descriptor.defaultMessage }
|
|
125
|
+
: options;
|
|
126
|
+
const resolved = descriptor.id
|
|
127
|
+
? messagevisor.getRawTranslation(descriptor.id as MessageKey, translationOptions)
|
|
128
|
+
: (descriptor.defaultMessage ?? "");
|
|
129
|
+
const message = resolved;
|
|
130
|
+
|
|
131
|
+
const translation = messagevisor.formatMessage(
|
|
132
|
+
message,
|
|
133
|
+
values as MessageValues<React.ReactNode>,
|
|
134
|
+
translationOptions,
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
return requireICUModule(message, translation);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
locale,
|
|
142
|
+
messages: messagevisor.getDefaultTranslations(locale) || config.messages,
|
|
143
|
+
formats: messagevisor.getDefaultFormats(locale) || config.formats,
|
|
144
|
+
timeZone: config.timeZone ?? messagevisor.getTimeZone(),
|
|
145
|
+
messagevisor,
|
|
146
|
+
formatMessage: formatDescriptorMessage as IntlShape["formatMessage"],
|
|
147
|
+
formatDate: (value, format, options) => messagevisor.formatDate(value, format as any, options),
|
|
148
|
+
formatDateToParts: (value, format, options) =>
|
|
149
|
+
messagevisor.formatDateToParts(value, format as any, options),
|
|
150
|
+
formatTime: (value, format, options) => messagevisor.formatTime(value, format as any, options),
|
|
151
|
+
formatTimeToParts: (value, format, options) =>
|
|
152
|
+
messagevisor.formatTimeToParts(value, format as any, options),
|
|
153
|
+
formatNumber: (value, format, options) =>
|
|
154
|
+
messagevisor.formatNumber(value, format as any, options),
|
|
155
|
+
formatNumberToParts: (value, format, options) =>
|
|
156
|
+
messagevisor.formatNumberToParts(value, format as any, options),
|
|
157
|
+
formatRelativeTime: (value, unit, format, options) =>
|
|
158
|
+
messagevisor.formatRelativeTime(value, unit, format as any, options),
|
|
159
|
+
formatPlural: (value, options) => messagevisor.formatPlural(value, options),
|
|
160
|
+
formatList: (values, options) => messagevisor.formatList(values, options),
|
|
161
|
+
formatListToParts: (values, options) => messagevisor.formatListToParts(values, options),
|
|
162
|
+
formatDisplayName: (value, options) => messagevisor.formatDisplayName(value, options),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export type WithIntlProps = {
|
|
167
|
+
intl: IntlShape;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
export function mergeRichTextValues(
|
|
171
|
+
defaults: Record<string, (chunks: React.ReactNode[]) => React.ReactNode> | undefined,
|
|
172
|
+
values: Record<string, MessageValue<React.ReactNode>> | undefined,
|
|
173
|
+
message: string | undefined,
|
|
174
|
+
) {
|
|
175
|
+
if (!defaults || !message) {
|
|
176
|
+
return values;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const tagPattern = /<([A-Za-z][A-Za-z0-9_-]*)\b[^>]*>/g;
|
|
180
|
+
const matchingDefaults: Record<string, (chunks: React.ReactNode[]) => React.ReactNode> = {};
|
|
181
|
+
let match: RegExpExecArray | null;
|
|
182
|
+
|
|
183
|
+
while ((match = tagPattern.exec(message))) {
|
|
184
|
+
if (defaults[match[1]]) {
|
|
185
|
+
matchingDefaults[match[1]] = defaults[match[1]];
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const defaultKeys = Object.keys(matchingDefaults);
|
|
190
|
+
|
|
191
|
+
if (defaultKeys.length === 0) {
|
|
192
|
+
return values;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
...matchingDefaults,
|
|
197
|
+
...(values || {}),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.cjs.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "./lib",
|
|
5
|
+
"rootDir": "./src",
|
|
6
|
+
"moduleResolution": "node",
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"jsx": "react",
|
|
9
|
+
"lib": ["es2015", "dom", "es2021.intl"]
|
|
10
|
+
},
|
|
11
|
+
"include": ["./src/**/*.ts", "./src/**/*.tsx"],
|
|
12
|
+
"exclude": ["./src/**/*.spec.ts", "./src/**/*.spec.tsx"]
|
|
13
|
+
}
|