@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,23 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { MessagevisorContext, useMessagevisorSnapshot } from "@messagevisor/react";
|
|
4
|
+
|
|
5
|
+
import type { IntlShape } from "./intl";
|
|
6
|
+
import { createIntlFromMessagevisor } from "./intl";
|
|
7
|
+
|
|
8
|
+
export function useIntl() {
|
|
9
|
+
const context = React.useContext(MessagevisorContext);
|
|
10
|
+
|
|
11
|
+
if (!context) {
|
|
12
|
+
throw new Error("useIntl must be used within MessagevisorProvider.");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const snapshot = useMessagevisorSnapshot();
|
|
16
|
+
|
|
17
|
+
return React.useMemo(
|
|
18
|
+
function getIntlShape(): IntlShape {
|
|
19
|
+
return createIntlFromMessagevisor(context.instance);
|
|
20
|
+
},
|
|
21
|
+
[context.instance, snapshot.version],
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
3
|
+
import "@testing-library/jest-dom";
|
|
4
|
+
|
|
5
|
+
import type { DatafileContent } from "@messagevisor/types";
|
|
6
|
+
import { createMessagevisor } from "@messagevisor/sdk";
|
|
7
|
+
import { createICUModule } from "@messagevisor/module-icu";
|
|
8
|
+
import { MessagevisorProvider } from "@messagevisor/react";
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
defineMessage,
|
|
12
|
+
defineMessages,
|
|
13
|
+
FormattedDate,
|
|
14
|
+
FormattedList,
|
|
15
|
+
FormattedMessage,
|
|
16
|
+
FormattedNumber,
|
|
17
|
+
FormattedNumberParts,
|
|
18
|
+
FormattedPlural,
|
|
19
|
+
injectIntl,
|
|
20
|
+
useIntl,
|
|
21
|
+
} from "./index";
|
|
22
|
+
|
|
23
|
+
const datafile: DatafileContent = {
|
|
24
|
+
schemaVersion: "1",
|
|
25
|
+
messagevisorVersion: "0.0.1",
|
|
26
|
+
revision: "1",
|
|
27
|
+
target: "web",
|
|
28
|
+
locale: "en-US",
|
|
29
|
+
formats: {
|
|
30
|
+
number: {
|
|
31
|
+
money: { style: "currency", currency: "USD", currencyDisplay: "symbol" },
|
|
32
|
+
},
|
|
33
|
+
date: {
|
|
34
|
+
short: { year: "numeric", month: "short", day: "numeric", timeZone: "UTC" },
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
segments: {
|
|
38
|
+
web: {
|
|
39
|
+
conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
messages: {
|
|
43
|
+
greeting: {
|
|
44
|
+
overrides: [
|
|
45
|
+
{
|
|
46
|
+
key: "web",
|
|
47
|
+
segments: "web",
|
|
48
|
+
translation: "Hello web {name}",
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
rich: {},
|
|
53
|
+
},
|
|
54
|
+
translations: {
|
|
55
|
+
greeting: "Hello {name}",
|
|
56
|
+
rich: "Read <link>terms</link>.",
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const nlDatafile: DatafileContent = {
|
|
61
|
+
...datafile,
|
|
62
|
+
revision: "2",
|
|
63
|
+
locale: "nl-NL",
|
|
64
|
+
formats: {
|
|
65
|
+
number: {
|
|
66
|
+
money: { style: "currency", currency: "EUR", currencyDisplay: "symbol" },
|
|
67
|
+
},
|
|
68
|
+
date: {
|
|
69
|
+
short: { year: "numeric", month: "short", day: "numeric", timeZone: "UTC" },
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
translations: {
|
|
73
|
+
greeting: "Hallo {name}",
|
|
74
|
+
rich: "Lees <link>voorwaarden</link>.",
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
describe("@messagevisor/react-intl-compat", function () {
|
|
79
|
+
it("supports bridge mode with locale-keyed catalogs and useIntl", function () {
|
|
80
|
+
function Example() {
|
|
81
|
+
const intl = useIntl();
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<div>
|
|
85
|
+
<span>
|
|
86
|
+
{intl.formatMessage({ id: "greeting", defaultMessage: "Hi {name}" }, { name: "Ada" })}
|
|
87
|
+
</span>
|
|
88
|
+
<span>{intl.formatNumber(12)}</span>
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const instance = createMessagevisor({
|
|
94
|
+
locale: "en-US",
|
|
95
|
+
defaultTranslations: {
|
|
96
|
+
"en-US": {
|
|
97
|
+
greeting: "Hello {name}",
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
modules: [createICUModule()],
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
render(
|
|
104
|
+
<MessagevisorProvider instance={instance}>
|
|
105
|
+
<Example />
|
|
106
|
+
</MessagevisorProvider>,
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
expect(screen.getByText("Hello Ada")).toBeInTheDocument();
|
|
110
|
+
expect(screen.getByText("12")).toBeInTheDocument();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("maps descriptor defaultMessage to SDK defaultTranslation", function () {
|
|
114
|
+
function Example() {
|
|
115
|
+
const intl = useIntl();
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<span>
|
|
119
|
+
{intl.formatMessage(
|
|
120
|
+
{ id: "missing.greeting", defaultMessage: "Hi {name}" },
|
|
121
|
+
{ name: "Ada" },
|
|
122
|
+
)}
|
|
123
|
+
</span>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const instance = createMessagevisor({
|
|
128
|
+
locale: "en-US",
|
|
129
|
+
modules: [createICUModule()],
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
render(
|
|
133
|
+
<MessagevisorProvider instance={instance}>
|
|
134
|
+
<Example />
|
|
135
|
+
</MessagevisorProvider>,
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
expect(screen.getByText("Hi Ada")).toBeInTheDocument();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("maps empty defaultMessage through defaultTranslation without falling back to the message key", function () {
|
|
142
|
+
function Example() {
|
|
143
|
+
const intl = useIntl();
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<span data-testid="empty-default">
|
|
147
|
+
{intl.formatMessage({ id: "missing.empty", defaultMessage: "" })}
|
|
148
|
+
</span>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const instance = createMessagevisor({
|
|
153
|
+
locale: "en-US",
|
|
154
|
+
modules: [createICUModule()],
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
render(
|
|
158
|
+
<MessagevisorProvider instance={instance}>
|
|
159
|
+
<Example />
|
|
160
|
+
</MessagevisorProvider>,
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
expect(screen.getByTestId("empty-default")).toBeEmptyDOMElement();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("uses defaultMessage for FormattedMessage when the id is missing, including rich text", function () {
|
|
167
|
+
render(
|
|
168
|
+
<MessagevisorProvider
|
|
169
|
+
instance={createMessagevisor({
|
|
170
|
+
datafile,
|
|
171
|
+
modules: [createICUModule({ ignoreTags: false })],
|
|
172
|
+
})}
|
|
173
|
+
textComponent="span"
|
|
174
|
+
defaultRichTextElements={{
|
|
175
|
+
link: function link(chunks) {
|
|
176
|
+
return <a href="/terms">{chunks}</a>;
|
|
177
|
+
},
|
|
178
|
+
}}
|
|
179
|
+
>
|
|
180
|
+
<FormattedMessage id="missing.rich" defaultMessage="Read <link>terms</link>." />
|
|
181
|
+
</MessagevisorProvider>,
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
expect(screen.getByRole("link", { name: "terms" })).toHaveAttribute("href", "/terms");
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("supports datafile mode and preserves Messagevisor overrides", function () {
|
|
188
|
+
render(
|
|
189
|
+
<MessagevisorProvider
|
|
190
|
+
instance={createMessagevisor({ datafile, modules: [createICUModule()] })}
|
|
191
|
+
>
|
|
192
|
+
<FormattedMessage id="greeting" values={{ name: "Ada" }} />
|
|
193
|
+
</MessagevisorProvider>,
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
expect(screen.getByText("Hello Ada")).toBeInTheDocument();
|
|
197
|
+
|
|
198
|
+
const instance = createMessagevisor({
|
|
199
|
+
datafile,
|
|
200
|
+
context: { platform: "web" },
|
|
201
|
+
modules: [createICUModule()],
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
render(
|
|
205
|
+
<MessagevisorProvider instance={instance}>
|
|
206
|
+
<FormattedMessage id="greeting" values={{ name: "Lin" }} />
|
|
207
|
+
</MessagevisorProvider>,
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
expect(screen.getByText("Hello web Lin")).toBeInTheDocument();
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("supports rich text defaults and textComponent wrapping", function () {
|
|
214
|
+
const instance = createMessagevisor({
|
|
215
|
+
datafile,
|
|
216
|
+
modules: [createICUModule({ ignoreTags: false })],
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
render(
|
|
220
|
+
<MessagevisorProvider
|
|
221
|
+
instance={instance}
|
|
222
|
+
textComponent="span"
|
|
223
|
+
defaultRichTextElements={{
|
|
224
|
+
link: function link(chunks) {
|
|
225
|
+
return <a href="/terms">{chunks}</a>;
|
|
226
|
+
},
|
|
227
|
+
}}
|
|
228
|
+
>
|
|
229
|
+
<FormattedMessage id="rich" />
|
|
230
|
+
</MessagevisorProvider>,
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
expect(screen.getByRole("link", { name: "terms" })).toHaveAttribute("href", "/terms");
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it("exports defineMessage helpers and imperative components", function () {
|
|
237
|
+
const message = defineMessage({ id: "hello", defaultMessage: "Hello" });
|
|
238
|
+
const messages = defineMessages({
|
|
239
|
+
count: { id: "count", defaultMessage: "{count, number}" },
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
expect(message.id).toEqual("hello");
|
|
243
|
+
expect(messages.count.id).toEqual("count");
|
|
244
|
+
|
|
245
|
+
render(
|
|
246
|
+
<MessagevisorProvider
|
|
247
|
+
instance={createMessagevisor({
|
|
248
|
+
locale: "en-US",
|
|
249
|
+
defaultTranslations: {
|
|
250
|
+
"en-US": {
|
|
251
|
+
hello: "Hello",
|
|
252
|
+
count: "{count, number}",
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
modules: [createICUModule()],
|
|
256
|
+
})}
|
|
257
|
+
>
|
|
258
|
+
<FormattedNumber value={12} />
|
|
259
|
+
<FormattedDate
|
|
260
|
+
value={new Date("2025-01-01T12:00:00Z")}
|
|
261
|
+
format={{ year: "numeric", timeZone: "UTC" }}
|
|
262
|
+
/>
|
|
263
|
+
<FormattedList value={["A", "B"]} />
|
|
264
|
+
<FormattedPlural value={1} one="item" other="items" />
|
|
265
|
+
<FormattedNumberParts value={12}>
|
|
266
|
+
{(parts) => <span>{parts[0].value}</span>}
|
|
267
|
+
</FormattedNumberParts>
|
|
268
|
+
</MessagevisorProvider>,
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
expect(screen.getByText("12")).toBeInTheDocument();
|
|
272
|
+
expect(document.body.textContent).toContain("A and B");
|
|
273
|
+
expect(document.body.textContent).toContain("item");
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it("supports injectIntl", function () {
|
|
277
|
+
const Base = injectIntl(function Base(props: { intl: ReturnType<typeof useIntl> }) {
|
|
278
|
+
return <span>{props.intl.formatMessage({ id: "greeting" }, { name: "Ada" })}</span>;
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
render(
|
|
282
|
+
<MessagevisorProvider
|
|
283
|
+
instance={createMessagevisor({
|
|
284
|
+
locale: "en-US",
|
|
285
|
+
defaultTranslations: {
|
|
286
|
+
"en-US": {
|
|
287
|
+
greeting: "Hello {name}",
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
modules: [createICUModule()],
|
|
291
|
+
})}
|
|
292
|
+
>
|
|
293
|
+
<Base />
|
|
294
|
+
</MessagevisorProvider>,
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
expect(screen.getByText("Hello Ada")).toBeInTheDocument();
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it("updates useIntl locale and formatted messages after SDK locale changes", function () {
|
|
301
|
+
function Example() {
|
|
302
|
+
const intl = useIntl();
|
|
303
|
+
|
|
304
|
+
return (
|
|
305
|
+
<section>
|
|
306
|
+
<p>{intl.locale}</p>
|
|
307
|
+
<p>{intl.formatMessage({ id: "greeting" }, { name: "Ada" })}</p>
|
|
308
|
+
<button
|
|
309
|
+
onClick={() => {
|
|
310
|
+
intl.messagevisor.setDatafile(nlDatafile);
|
|
311
|
+
intl.messagevisor.setLocale("nl-NL");
|
|
312
|
+
}}
|
|
313
|
+
>
|
|
314
|
+
switch
|
|
315
|
+
</button>
|
|
316
|
+
</section>
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
render(
|
|
321
|
+
<MessagevisorProvider
|
|
322
|
+
instance={createMessagevisor({ datafile, modules: [createICUModule()] })}
|
|
323
|
+
>
|
|
324
|
+
<Example />
|
|
325
|
+
</MessagevisorProvider>,
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
expect(screen.getByText("en-US")).toBeInTheDocument();
|
|
329
|
+
expect(screen.getByText("Hello Ada")).toBeInTheDocument();
|
|
330
|
+
|
|
331
|
+
fireEvent.click(screen.getByRole("button", { name: "switch" }));
|
|
332
|
+
|
|
333
|
+
expect(screen.getByText("nl-NL")).toBeInTheDocument();
|
|
334
|
+
expect(screen.getByText("Hallo Ada")).toBeInTheDocument();
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it("updates FormattedMessage after SDK locale changes", function () {
|
|
338
|
+
function Example() {
|
|
339
|
+
const intl = useIntl();
|
|
340
|
+
|
|
341
|
+
return (
|
|
342
|
+
<section>
|
|
343
|
+
<FormattedMessage id="greeting" values={{ name: "Ada" }} />
|
|
344
|
+
<button
|
|
345
|
+
onClick={() => {
|
|
346
|
+
intl.messagevisor.setDatafile(nlDatafile);
|
|
347
|
+
intl.messagevisor.setLocale("nl-NL");
|
|
348
|
+
}}
|
|
349
|
+
>
|
|
350
|
+
switch
|
|
351
|
+
</button>
|
|
352
|
+
</section>
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
render(
|
|
357
|
+
<MessagevisorProvider
|
|
358
|
+
instance={createMessagevisor({ datafile, modules: [createICUModule()] })}
|
|
359
|
+
>
|
|
360
|
+
<Example />
|
|
361
|
+
</MessagevisorProvider>,
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
expect(screen.getByText("Hello Ada")).toBeInTheDocument();
|
|
365
|
+
|
|
366
|
+
fireEvent.click(screen.getByRole("button", { name: "switch" }));
|
|
367
|
+
|
|
368
|
+
expect(screen.getByText("Hallo Ada")).toBeInTheDocument();
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it("updates formatter components after SDK locale changes", function () {
|
|
372
|
+
function Example() {
|
|
373
|
+
const intl = useIntl();
|
|
374
|
+
|
|
375
|
+
return (
|
|
376
|
+
<section>
|
|
377
|
+
<span data-testid="number">
|
|
378
|
+
<FormattedNumber value={1234.5} />
|
|
379
|
+
</span>
|
|
380
|
+
<span data-testid="currency">{intl.formatNumber(12, "money")}</span>
|
|
381
|
+
<button
|
|
382
|
+
onClick={() => {
|
|
383
|
+
intl.messagevisor.setDatafile(nlDatafile);
|
|
384
|
+
intl.messagevisor.setLocale("nl-NL");
|
|
385
|
+
}}
|
|
386
|
+
>
|
|
387
|
+
switch
|
|
388
|
+
</button>
|
|
389
|
+
</section>
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
render(
|
|
394
|
+
<MessagevisorProvider
|
|
395
|
+
instance={createMessagevisor({ datafile, modules: [createICUModule()] })}
|
|
396
|
+
>
|
|
397
|
+
<Example />
|
|
398
|
+
</MessagevisorProvider>,
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
const initialNumber = screen.getByTestId("number").textContent;
|
|
402
|
+
expect(screen.getByTestId("currency")).toHaveTextContent("$12.00");
|
|
403
|
+
|
|
404
|
+
fireEvent.click(screen.getByRole("button", { name: "switch" }));
|
|
405
|
+
|
|
406
|
+
expect(screen.getByTestId("number").textContent).not.toEqual(initialNumber);
|
|
407
|
+
expect(screen.getByTestId("currency")).toHaveTextContent("€");
|
|
408
|
+
expect(screen.getByTestId("currency")).not.toHaveTextContent("$12.00");
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
it("updates injected intl props after SDK locale changes", function () {
|
|
412
|
+
const Base = injectIntl(function Base(props: { intl: ReturnType<typeof useIntl> }) {
|
|
413
|
+
return <span>{props.intl.formatMessage({ id: "greeting" }, { name: "Ada" })}</span>;
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
function Example() {
|
|
417
|
+
const intl = useIntl();
|
|
418
|
+
|
|
419
|
+
return (
|
|
420
|
+
<section>
|
|
421
|
+
<Base />
|
|
422
|
+
<button
|
|
423
|
+
onClick={() => {
|
|
424
|
+
intl.messagevisor.setDatafile(nlDatafile);
|
|
425
|
+
intl.messagevisor.setLocale("nl-NL");
|
|
426
|
+
}}
|
|
427
|
+
>
|
|
428
|
+
switch
|
|
429
|
+
</button>
|
|
430
|
+
</section>
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
render(
|
|
435
|
+
<MessagevisorProvider
|
|
436
|
+
instance={createMessagevisor({ datafile, modules: [createICUModule()] })}
|
|
437
|
+
>
|
|
438
|
+
<Example />
|
|
439
|
+
</MessagevisorProvider>,
|
|
440
|
+
);
|
|
441
|
+
|
|
442
|
+
expect(screen.getByText("Hello Ada")).toBeInTheDocument();
|
|
443
|
+
|
|
444
|
+
fireEvent.click(screen.getByRole("button", { name: "switch" }));
|
|
445
|
+
|
|
446
|
+
expect(screen.getByText("Hallo Ada")).toBeInTheDocument();
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
it("fails clearly when ICU formatting is requested without the ICU module", function () {
|
|
450
|
+
expect(() =>
|
|
451
|
+
render(
|
|
452
|
+
<MessagevisorProvider
|
|
453
|
+
instance={createMessagevisor({
|
|
454
|
+
locale: "en-US",
|
|
455
|
+
defaultTranslations: {
|
|
456
|
+
"en-US": {
|
|
457
|
+
greeting: "Hello {name}",
|
|
458
|
+
},
|
|
459
|
+
},
|
|
460
|
+
})}
|
|
461
|
+
>
|
|
462
|
+
<FormattedMessage id="greeting" values={{ name: "Ada" }} />
|
|
463
|
+
</MessagevisorProvider>,
|
|
464
|
+
),
|
|
465
|
+
).toThrow(
|
|
466
|
+
"Message formatting requires a Messagevisor instance configured with createICUModule().",
|
|
467
|
+
);
|
|
468
|
+
});
|
|
469
|
+
});
|