@messagevisor/react 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 +13 -0
- package/lib/MessagevisorContext.d.ts +21 -0
- package/lib/MessagevisorContext.js +39 -0
- package/lib/MessagevisorContext.js.map +1 -0
- package/lib/MessagevisorProvider.d.ts +12 -0
- package/lib/MessagevisorProvider.js +58 -0
- package/lib/MessagevisorProvider.js.map +1 -0
- package/lib/index.d.ts +6 -0
- package/lib/index.js +23 -0
- package/lib/index.js.map +1 -0
- package/lib/useMessagevisor.d.ts +20 -0
- package/lib/useMessagevisor.js +106 -0
- package/lib/useMessagevisor.js.map +1 -0
- package/lib/useMessagevisorSnapshot.d.ts +2 -0
- package/lib/useMessagevisorSnapshot.js +77 -0
- package/lib/useMessagevisorSnapshot.js.map +1 -0
- package/lib/useReactiveMessagevisor.d.ts +31 -0
- package/lib/useReactiveMessagevisor.js +141 -0
- package/lib/useReactiveMessagevisor.js.map +1 -0
- package/lib/useRichText.d.ts +13 -0
- package/lib/useRichText.js +112 -0
- package/lib/useRichText.js.map +1 -0
- package/lib/useSdk.d.ts +2 -0
- package/lib/useSdk.js +46 -0
- package/lib/useSdk.js.map +1 -0
- package/package.json +51 -13
- package/src/MessagevisorContext.ts +28 -0
- package/src/MessagevisorProvider.spec.tsx +29 -0
- package/src/MessagevisorProvider.tsx +41 -0
- package/src/index.ts +6 -0
- package/src/testUtils.ts +72 -0
- package/src/useMessagevisor.spec.tsx +425 -0
- package/src/useMessagevisor.ts +115 -0
- package/src/useMessagevisorSnapshot.ts +65 -0
- package/src/useReactiveMessagevisor.spec.tsx +507 -0
- package/src/useReactiveMessagevisor.ts +223 -0
- package/src/useRichText.tsx +116 -0
- package/src/useSdk.spec.tsx +45 -0
- package/src/useSdk.ts +15 -0
- package/tsconfig.cjs.json +11 -0
- package/tsconfig.typecheck.json +4 -0
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import "@testing-library/jest-dom";
|
|
4
|
+
|
|
5
|
+
import { createMessagevisor } from "@messagevisor/sdk";
|
|
6
|
+
import { createICUModule } from "@messagevisor/module-icu";
|
|
7
|
+
|
|
8
|
+
import { MessagevisorProvider } from "./MessagevisorProvider";
|
|
9
|
+
import { datafile, createRichTestInstance, createTestInstance } from "./testUtils";
|
|
10
|
+
import { MESSAGEVISOR_METHODS, useMessagevisor } from "./useMessagevisor";
|
|
11
|
+
|
|
12
|
+
describe("useMessagevisor", function () {
|
|
13
|
+
it("exposes exactly the expected bound methods", function () {
|
|
14
|
+
let keys: string[] = [];
|
|
15
|
+
|
|
16
|
+
function TestComponent() {
|
|
17
|
+
keys = Object.keys(useMessagevisor());
|
|
18
|
+
|
|
19
|
+
return <p>ready</p>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
render(
|
|
23
|
+
<MessagevisorProvider instance={createTestInstance()}>
|
|
24
|
+
<TestComponent />
|
|
25
|
+
</MessagevisorProvider>,
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
expect(keys.sort()).toEqual([...MESSAGEVISOR_METHODS].sort());
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("translates through t and keeps destructured methods bound", function () {
|
|
32
|
+
let text = "";
|
|
33
|
+
let total = "";
|
|
34
|
+
|
|
35
|
+
function TestComponent() {
|
|
36
|
+
const { t, setContext } = useMessagevisor();
|
|
37
|
+
|
|
38
|
+
setContext({ platform: "web" });
|
|
39
|
+
text = t("greeting", { name: "Ada" });
|
|
40
|
+
total = t("total", { amount: 12 });
|
|
41
|
+
|
|
42
|
+
return <p>{text}</p>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
render(
|
|
46
|
+
<MessagevisorProvider instance={createTestInstance()}>
|
|
47
|
+
<TestComponent />
|
|
48
|
+
</MessagevisorProvider>,
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
expect(screen.getByText("Hello web Ada")).toBeInTheDocument();
|
|
52
|
+
expect(text).toEqual("Hello web Ada");
|
|
53
|
+
expect(total).toEqual("Total: $12.00");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("exposes formatting helpers and SDK setters/getters", function () {
|
|
57
|
+
const results: string[] = [];
|
|
58
|
+
|
|
59
|
+
function TestComponent() {
|
|
60
|
+
const {
|
|
61
|
+
formatMessage,
|
|
62
|
+
formatNumber,
|
|
63
|
+
formatDate,
|
|
64
|
+
formatTime,
|
|
65
|
+
formatDateTimeRange,
|
|
66
|
+
formatRelativeTime,
|
|
67
|
+
setCurrency,
|
|
68
|
+
getCurrency,
|
|
69
|
+
setTimeZone,
|
|
70
|
+
getTimeZone,
|
|
71
|
+
getDirection,
|
|
72
|
+
setContext,
|
|
73
|
+
getContext,
|
|
74
|
+
setDatafile,
|
|
75
|
+
getRevision,
|
|
76
|
+
} = useMessagevisor();
|
|
77
|
+
|
|
78
|
+
setCurrency("EUR");
|
|
79
|
+
setTimeZone("UTC");
|
|
80
|
+
setContext({ plan: "pro" });
|
|
81
|
+
setDatafile({
|
|
82
|
+
...datafile,
|
|
83
|
+
locale: "nl-NL",
|
|
84
|
+
revision: "2",
|
|
85
|
+
translations: {
|
|
86
|
+
greeting: "Hallo {name}",
|
|
87
|
+
total: "Totaal",
|
|
88
|
+
},
|
|
89
|
+
messages: {
|
|
90
|
+
greeting: {},
|
|
91
|
+
total: {},
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
results.push(formatMessage("Hello {name}", { name: "Ada" }));
|
|
96
|
+
results.push(formatNumber(12, "money"));
|
|
97
|
+
results.push(formatNumber(12, { style: "currency", currency: "USD" }));
|
|
98
|
+
results.push(formatDate("2025-01-02T00:00:00Z", "short"));
|
|
99
|
+
results.push(formatTime("2025-01-02T12:00:00Z", "short"));
|
|
100
|
+
results.push(formatDateTimeRange("2025-01-02T00:00:00Z", "2025-01-03T00:00:00Z", "short"));
|
|
101
|
+
results.push(formatRelativeTime(-1, "day", "short"));
|
|
102
|
+
results.push(String(getCurrency()));
|
|
103
|
+
results.push(String(getTimeZone()));
|
|
104
|
+
results.push(String(getDirection()));
|
|
105
|
+
results.push(String(getContext().plan));
|
|
106
|
+
results.push(getRevision());
|
|
107
|
+
results.push(getRevision("nl-NL"));
|
|
108
|
+
|
|
109
|
+
return <p>formatted</p>;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
render(
|
|
113
|
+
<MessagevisorProvider instance={createTestInstance()}>
|
|
114
|
+
<TestComponent />
|
|
115
|
+
</MessagevisorProvider>,
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
expect(screen.getByText("formatted")).toBeInTheDocument();
|
|
119
|
+
expect(results[0]).toEqual("Hello Ada");
|
|
120
|
+
expect(results[1]).toContain("$");
|
|
121
|
+
expect(results[2]).toContain("12");
|
|
122
|
+
expect(results[3]).toContain("1/2/25");
|
|
123
|
+
expect(results[4]).toContain("12:00");
|
|
124
|
+
expect(results[5]).toContain("Jan");
|
|
125
|
+
expect(results[6]).toEqual("yesterday");
|
|
126
|
+
expect(results.slice(7)).toEqual(["EUR", "UTC", "ltr", "pro", "1", "2"]);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("switches locale only when setLocale is called after setDatafile", function () {
|
|
130
|
+
let before = "";
|
|
131
|
+
let afterDatafile = "";
|
|
132
|
+
let afterLocale = "";
|
|
133
|
+
|
|
134
|
+
function TestComponent() {
|
|
135
|
+
const { t, setDatafile, setLocale, getLocale } = useMessagevisor();
|
|
136
|
+
|
|
137
|
+
before = String(getLocale());
|
|
138
|
+
setDatafile({
|
|
139
|
+
...datafile,
|
|
140
|
+
locale: "nl-NL",
|
|
141
|
+
translations: {
|
|
142
|
+
greeting: "Hallo {name}",
|
|
143
|
+
total: "Totaal",
|
|
144
|
+
},
|
|
145
|
+
messages: {
|
|
146
|
+
greeting: {},
|
|
147
|
+
total: {},
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
afterDatafile = t("greeting", { name: "Ada" });
|
|
151
|
+
setLocale("nl-NL");
|
|
152
|
+
afterLocale = t("greeting", { name: "Ada" });
|
|
153
|
+
|
|
154
|
+
return <p>locale</p>;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
render(
|
|
158
|
+
<MessagevisorProvider instance={createTestInstance()}>
|
|
159
|
+
<TestComponent />
|
|
160
|
+
</MessagevisorProvider>,
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
expect(before).toEqual("en-US");
|
|
164
|
+
expect(afterDatafile).toEqual("Hello Ada");
|
|
165
|
+
expect(afterLocale).toEqual("Hallo Ada");
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("keeps method references stable for the same instance and refreshes for a new instance", function () {
|
|
169
|
+
const first = createTestInstance();
|
|
170
|
+
const second = createMessagevisor({
|
|
171
|
+
datafile: {
|
|
172
|
+
...datafile,
|
|
173
|
+
translations: {
|
|
174
|
+
...datafile.translations,
|
|
175
|
+
greeting: "Hi {name}",
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
modules: [createICUModule()],
|
|
179
|
+
});
|
|
180
|
+
const refs: Array<ReturnType<typeof useMessagevisor>["t"]> = [];
|
|
181
|
+
const values: string[] = [];
|
|
182
|
+
|
|
183
|
+
function TestComponent() {
|
|
184
|
+
const { t } = useMessagevisor();
|
|
185
|
+
|
|
186
|
+
refs.push(t);
|
|
187
|
+
values.push(t("greeting", { name: "Ada" }));
|
|
188
|
+
|
|
189
|
+
return <p>refs</p>;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const { rerender } = render(
|
|
193
|
+
<MessagevisorProvider instance={first}>
|
|
194
|
+
<TestComponent />
|
|
195
|
+
</MessagevisorProvider>,
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
rerender(
|
|
199
|
+
<MessagevisorProvider instance={first}>
|
|
200
|
+
<TestComponent />
|
|
201
|
+
</MessagevisorProvider>,
|
|
202
|
+
);
|
|
203
|
+
rerender(
|
|
204
|
+
<MessagevisorProvider instance={second}>
|
|
205
|
+
<TestComponent />
|
|
206
|
+
</MessagevisorProvider>,
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
expect(refs[0]).toBe(refs[1]);
|
|
210
|
+
expect(refs[1]).not.toBe(refs[2]);
|
|
211
|
+
expect(values).toEqual(["Hello Ada", "Hello Ada", "Hi Ada"]);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("does not re-render automatically when the SDK instance mutates", function () {
|
|
215
|
+
let renderCount = 0;
|
|
216
|
+
let displayed = "";
|
|
217
|
+
|
|
218
|
+
function TestComponent() {
|
|
219
|
+
renderCount++;
|
|
220
|
+
const { t, setContext } = useMessagevisor();
|
|
221
|
+
|
|
222
|
+
displayed = t("greeting", { name: "Ada" });
|
|
223
|
+
return <button onClick={() => setContext({ platform: "web" })}>{displayed}</button>;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
render(
|
|
227
|
+
<MessagevisorProvider instance={createTestInstance()}>
|
|
228
|
+
<TestComponent />
|
|
229
|
+
</MessagevisorProvider>,
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
screen.getByRole("button").click();
|
|
233
|
+
|
|
234
|
+
expect(renderCount).toEqual(1);
|
|
235
|
+
expect(displayed).toEqual("Hello Ada");
|
|
236
|
+
expect(screen.getByText("Hello Ada")).toBeInTheDocument();
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it("supports rich text values and provider defaults through t and formatMessage", function () {
|
|
240
|
+
function TestComponent() {
|
|
241
|
+
const { t, formatMessage } = useMessagevisor();
|
|
242
|
+
const terms = t("richTerms", { product: "Messagevisor" });
|
|
243
|
+
const inline = formatMessage("Inline <strong>{name}</strong>.", {
|
|
244
|
+
name: "Ada",
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
return (
|
|
248
|
+
<section>
|
|
249
|
+
<p>{terms}</p>
|
|
250
|
+
<p>{inline}</p>
|
|
251
|
+
</section>
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
render(
|
|
256
|
+
<MessagevisorProvider
|
|
257
|
+
instance={createRichTestInstance()}
|
|
258
|
+
defaultRichTextElements={{
|
|
259
|
+
link: (chunks) => <a href="/terms">{chunks}</a>,
|
|
260
|
+
strong: (chunks) => <strong>{chunks}</strong>,
|
|
261
|
+
}}
|
|
262
|
+
>
|
|
263
|
+
<TestComponent />
|
|
264
|
+
</MessagevisorProvider>,
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
expect(screen.getByRole("link", { name: "terms" })).toHaveAttribute("href", "/terms");
|
|
268
|
+
expect(screen.getAllByText("Messagevisor")[0].tagName).toEqual("STRONG");
|
|
269
|
+
expect(screen.getByText("Ada").tagName).toEqual("STRONG");
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it("runs provider modules for t and formatMessage before fragment wrapping", function () {
|
|
273
|
+
const payloads: any[] = [];
|
|
274
|
+
|
|
275
|
+
function TestComponent() {
|
|
276
|
+
const { t, formatMessage } = useMessagevisor();
|
|
277
|
+
|
|
278
|
+
return (
|
|
279
|
+
<section>
|
|
280
|
+
<p>{t("greeting", { name: "Ada" })}</p>
|
|
281
|
+
<p>{formatMessage("Inline {name}", { name: "Ada" })}</p>
|
|
282
|
+
</section>
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
render(
|
|
287
|
+
<MessagevisorProvider
|
|
288
|
+
instance={createTestInstance()}
|
|
289
|
+
modules={[
|
|
290
|
+
{
|
|
291
|
+
name: "emphasis",
|
|
292
|
+
transform(payload) {
|
|
293
|
+
payloads.push(payload);
|
|
294
|
+
|
|
295
|
+
return <strong>{payload.translation}</strong>;
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
]}
|
|
299
|
+
>
|
|
300
|
+
<TestComponent />
|
|
301
|
+
</MessagevisorProvider>,
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
expect(screen.getByText("Hello Ada").tagName).toEqual("STRONG");
|
|
305
|
+
expect(screen.getByText("Inline Ada").tagName).toEqual("STRONG");
|
|
306
|
+
expect(payloads.map((payload) => payload.source)).toEqual(["translation", "formatMessage"]);
|
|
307
|
+
expect(payloads[0].messageKey).toEqual("greeting");
|
|
308
|
+
expect(payloads[1].messageKey).toBeUndefined();
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it("runs SDK modules before provider modules", function () {
|
|
312
|
+
const instance = createMessagevisor({
|
|
313
|
+
datafile,
|
|
314
|
+
modules: [
|
|
315
|
+
createICUModule(),
|
|
316
|
+
{
|
|
317
|
+
transform: ({ translation }) => `${translation} from sdk`,
|
|
318
|
+
},
|
|
319
|
+
],
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
function TestComponent() {
|
|
323
|
+
const { t } = useMessagevisor();
|
|
324
|
+
|
|
325
|
+
return <p>{t("greeting", { name: "Ada" })}</p>;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
render(
|
|
329
|
+
<MessagevisorProvider
|
|
330
|
+
instance={instance}
|
|
331
|
+
modules={[
|
|
332
|
+
{
|
|
333
|
+
transform: ({ translation }) => `${translation} from provider`,
|
|
334
|
+
},
|
|
335
|
+
]}
|
|
336
|
+
>
|
|
337
|
+
<TestComponent />
|
|
338
|
+
</MessagevisorProvider>,
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
expect(screen.getByText("Hello Ada from sdk from provider")).toBeInTheDocument();
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it("does not merge provider defaults into ordinary ICU placeholders", function () {
|
|
345
|
+
function TestComponent() {
|
|
346
|
+
const { t, formatMessage } = useMessagevisor();
|
|
347
|
+
|
|
348
|
+
return (
|
|
349
|
+
<section>
|
|
350
|
+
<p>{t("plainLink", { link: "this link" })}</p>
|
|
351
|
+
<p>{formatMessage("Inline {link}", { link: "plain link" })}</p>
|
|
352
|
+
</section>
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
render(
|
|
357
|
+
<MessagevisorProvider
|
|
358
|
+
instance={createRichTestInstance()}
|
|
359
|
+
defaultRichTextElements={{
|
|
360
|
+
link: (chunks) => <a href="/terms">{chunks}</a>,
|
|
361
|
+
}}
|
|
362
|
+
>
|
|
363
|
+
<TestComponent />
|
|
364
|
+
</MessagevisorProvider>,
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
expect(screen.getByText("Use this link")).toBeInTheDocument();
|
|
368
|
+
expect(screen.getByText("Inline plain link")).toBeInTheDocument();
|
|
369
|
+
expect(screen.queryByRole("link")).not.toBeInTheDocument();
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it("lets per-call rich text values override provider defaults in t", function () {
|
|
373
|
+
function TestComponent() {
|
|
374
|
+
const { t } = useMessagevisor();
|
|
375
|
+
|
|
376
|
+
return (
|
|
377
|
+
<p>
|
|
378
|
+
{t("richTerms", {
|
|
379
|
+
product: "Messagevisor",
|
|
380
|
+
link: (chunks) => <button>{chunks}</button>,
|
|
381
|
+
})}
|
|
382
|
+
</p>
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
render(
|
|
387
|
+
<MessagevisorProvider
|
|
388
|
+
instance={createRichTestInstance()}
|
|
389
|
+
defaultRichTextElements={{
|
|
390
|
+
link: (chunks) => <a href="/terms">{chunks}</a>,
|
|
391
|
+
strong: (chunks) => <strong>{chunks}</strong>,
|
|
392
|
+
}}
|
|
393
|
+
>
|
|
394
|
+
<TestComponent />
|
|
395
|
+
</MessagevisorProvider>,
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
expect(screen.getByRole("button", { name: "terms" })).toBeInTheDocument();
|
|
399
|
+
expect(screen.queryByRole("link", { name: "terms" })).not.toBeInTheDocument();
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
it("does not parse tags through t when the ICU module keeps ignoreTags enabled", function () {
|
|
403
|
+
function TestComponent() {
|
|
404
|
+
const { t } = useMessagevisor();
|
|
405
|
+
|
|
406
|
+
return <p>{t("richTerms", { product: "Messagevisor" })}</p>;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
render(
|
|
410
|
+
<MessagevisorProvider
|
|
411
|
+
instance={createTestInstance()}
|
|
412
|
+
defaultRichTextElements={{
|
|
413
|
+
link: (chunks) => <a href="/terms">{chunks}</a>,
|
|
414
|
+
strong: (chunks) => <strong>{chunks}</strong>,
|
|
415
|
+
}}
|
|
416
|
+
>
|
|
417
|
+
<TestComponent />
|
|
418
|
+
</MessagevisorProvider>,
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
expect(
|
|
422
|
+
screen.getByText("Read our <link>terms</link> for <strong>Messagevisor</strong>."),
|
|
423
|
+
).toBeInTheDocument();
|
|
424
|
+
});
|
|
425
|
+
});
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
EvaluationOptions,
|
|
5
|
+
TranslateOptions,
|
|
6
|
+
MessagePrimitiveValue,
|
|
7
|
+
Messagevisor,
|
|
8
|
+
} from "@messagevisor/sdk";
|
|
9
|
+
import type { MessageKey } from "@messagevisor/types";
|
|
10
|
+
|
|
11
|
+
import { useRichText, type ReactMessageValues, type ReactRichMessageValues } from "./useRichText";
|
|
12
|
+
import { useSdk } from "./useSdk";
|
|
13
|
+
|
|
14
|
+
const BOUND_METHODS = [
|
|
15
|
+
"formatNumber",
|
|
16
|
+
"formatNumberToParts",
|
|
17
|
+
"formatDate",
|
|
18
|
+
"formatDateToParts",
|
|
19
|
+
"formatTime",
|
|
20
|
+
"formatTimeToParts",
|
|
21
|
+
"formatDateTimeRange",
|
|
22
|
+
"formatRelativeTime",
|
|
23
|
+
"formatPlural",
|
|
24
|
+
"formatList",
|
|
25
|
+
"formatListToParts",
|
|
26
|
+
"formatDisplayName",
|
|
27
|
+
"setLocale",
|
|
28
|
+
"getLocale",
|
|
29
|
+
"getDirection",
|
|
30
|
+
"setContext",
|
|
31
|
+
"getContext",
|
|
32
|
+
"setCurrency",
|
|
33
|
+
"getCurrency",
|
|
34
|
+
"setTimeZone",
|
|
35
|
+
"getTimeZone",
|
|
36
|
+
"setDatafile",
|
|
37
|
+
"getRevision",
|
|
38
|
+
] as const satisfies readonly (keyof Messagevisor)[];
|
|
39
|
+
|
|
40
|
+
export const MESSAGEVISOR_METHODS = ["t", "formatMessage", ...BOUND_METHODS] as const;
|
|
41
|
+
|
|
42
|
+
type ReactTranslationMethod = {
|
|
43
|
+
(
|
|
44
|
+
messageKey: MessageKey,
|
|
45
|
+
values?: Record<string, MessagePrimitiveValue>,
|
|
46
|
+
options?: TranslateOptions,
|
|
47
|
+
): string;
|
|
48
|
+
(
|
|
49
|
+
messageKey: MessageKey,
|
|
50
|
+
values: ReactRichMessageValues,
|
|
51
|
+
options?: TranslateOptions,
|
|
52
|
+
): React.ReactNode;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
type ReactFormatMessageMethod = {
|
|
56
|
+
(
|
|
57
|
+
message: string,
|
|
58
|
+
values?: Record<string, MessagePrimitiveValue>,
|
|
59
|
+
options?: EvaluationOptions,
|
|
60
|
+
): string;
|
|
61
|
+
(message: string, values: ReactRichMessageValues, options?: EvaluationOptions): React.ReactNode;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export type MessagevisorApi = {
|
|
65
|
+
t: ReactTranslationMethod;
|
|
66
|
+
formatMessage: ReactFormatMessageMethod;
|
|
67
|
+
} & Pick<Messagevisor, (typeof BOUND_METHODS)[number]>;
|
|
68
|
+
|
|
69
|
+
function bindMethod<K extends keyof Messagevisor>(instance: Messagevisor, key: K): Messagevisor[K] {
|
|
70
|
+
return (instance[key] as (...args: never[]) => unknown).bind(instance) as Messagevisor[K];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function useMessagevisor(): MessagevisorApi {
|
|
74
|
+
const sdk = useSdk();
|
|
75
|
+
const richText = useRichText();
|
|
76
|
+
|
|
77
|
+
return React.useMemo(() => {
|
|
78
|
+
const result = {
|
|
79
|
+
t: ((messageKey, values, options) => {
|
|
80
|
+
const message = sdk.getRawTranslation(messageKey, options);
|
|
81
|
+
const translation = sdk.translate<React.ReactNode>(
|
|
82
|
+
messageKey,
|
|
83
|
+
richText.mergeValues(values as ReactMessageValues, message),
|
|
84
|
+
options,
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
return richText.wrapResult(
|
|
88
|
+
richText.runModules(translation, {
|
|
89
|
+
source: "translation",
|
|
90
|
+
messageKey,
|
|
91
|
+
}),
|
|
92
|
+
);
|
|
93
|
+
}) as ReactTranslationMethod,
|
|
94
|
+
formatMessage: ((message, values, options) => {
|
|
95
|
+
const translation = sdk.formatMessage(
|
|
96
|
+
message,
|
|
97
|
+
richText.mergeValues(values as ReactMessageValues, message),
|
|
98
|
+
options,
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
return richText.wrapResult(
|
|
102
|
+
richText.runModules(translation, {
|
|
103
|
+
source: "formatMessage",
|
|
104
|
+
}),
|
|
105
|
+
);
|
|
106
|
+
}) as ReactFormatMessageMethod,
|
|
107
|
+
} as MessagevisorApi;
|
|
108
|
+
|
|
109
|
+
for (const key of BOUND_METHODS) {
|
|
110
|
+
result[key] = bindMethod(sdk, key) as never;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return result;
|
|
114
|
+
}, [sdk, richText]);
|
|
115
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import type { MessagevisorSnapshot } from "@messagevisor/sdk";
|
|
4
|
+
|
|
5
|
+
import { useSdk } from "./useSdk";
|
|
6
|
+
|
|
7
|
+
type SyncExternalStore = <Snapshot>(
|
|
8
|
+
subscribe: (callback: () => void) => () => void,
|
|
9
|
+
getSnapshot: () => Snapshot,
|
|
10
|
+
getServerSnapshot?: () => Snapshot,
|
|
11
|
+
) => Snapshot;
|
|
12
|
+
|
|
13
|
+
const useSyncExternalStore =
|
|
14
|
+
(React as typeof React & { useSyncExternalStore?: SyncExternalStore }).useSyncExternalStore ||
|
|
15
|
+
function useSyncExternalStoreFallback<Snapshot>(
|
|
16
|
+
subscribe: (callback: () => void) => () => void,
|
|
17
|
+
getSnapshot: () => Snapshot,
|
|
18
|
+
) {
|
|
19
|
+
const [snapshot, setSnapshot] = React.useState(getSnapshot);
|
|
20
|
+
|
|
21
|
+
React.useEffect(() => {
|
|
22
|
+
const handleChange = () => {
|
|
23
|
+
setSnapshot(getSnapshot());
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const unsubscribe = subscribe(handleChange);
|
|
27
|
+
handleChange();
|
|
28
|
+
|
|
29
|
+
return unsubscribe;
|
|
30
|
+
}, [subscribe, getSnapshot]);
|
|
31
|
+
|
|
32
|
+
return snapshot;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export function useMessagevisorSnapshot(): MessagevisorSnapshot {
|
|
36
|
+
const sdk = useSdk();
|
|
37
|
+
const store = React.useMemo(() => {
|
|
38
|
+
let snapshot = sdk.getSnapshot();
|
|
39
|
+
|
|
40
|
+
const getSnapshot = () => {
|
|
41
|
+
const nextSnapshot = sdk.getSnapshot();
|
|
42
|
+
|
|
43
|
+
if (nextSnapshot.version === snapshot.version) {
|
|
44
|
+
return snapshot;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
snapshot = nextSnapshot;
|
|
48
|
+
|
|
49
|
+
return snapshot;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const subscribe = (callback: () => void) =>
|
|
53
|
+
sdk.subscribe(() => {
|
|
54
|
+
snapshot = sdk.getSnapshot();
|
|
55
|
+
callback();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
getSnapshot,
|
|
60
|
+
subscribe,
|
|
61
|
+
};
|
|
62
|
+
}, [sdk]);
|
|
63
|
+
|
|
64
|
+
return useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
|
|
65
|
+
}
|