@formatjs/editor 1.1.48 → 1.4.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/index.js CHANGED
@@ -1,121 +1,337 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CoreApp = void 0;
4
- var tslib_1 = require("tslib");
5
- var react_1 = (0, tslib_1.__importStar)(require("react"));
6
- var core_1 = require("@material-ui/core");
7
- var icons_1 = require("@material-ui/icons");
8
- var header_1 = (0, tslib_1.__importDefault)(require("./header"));
9
- var react_intl_1 = require("react-intl");
10
- var messages_1 = (0, tslib_1.__importDefault)(require("./messages"));
11
- var MESSAGES_COUNT = 50;
12
- function fetchData() {
13
- return (0, tslib_1.__awaiter)(this, void 0, void 0, function () {
14
- var en, ru;
15
- return (0, tslib_1.__generator)(this, function (_a) {
16
- switch (_a.label) {
17
- case 0: return [4 /*yield*/, fetch('/fixtures/en.json')];
18
- case 1: return [4 /*yield*/, (_a.sent()).json()];
19
- case 2:
20
- en = _a.sent();
21
- return [4 /*yield*/, fetch('/fixtures/ru.json')];
22
- case 3: return [4 /*yield*/, (_a.sent()).json()];
23
- case 4:
24
- ru = _a.sent();
25
- return [2 /*return*/, Object.keys(en)
26
- .slice(MESSAGES_COUNT)
27
- .map(function (id) { return ({
28
- id: id,
29
- defaultMessage: en[id],
30
- translatedMessage: ru[id],
31
- }); })];
32
- }
33
- });
34
- });
1
+ import { useEffect, useMemo, useRef, useState } from "react";
2
+ import { isLiteralElement, isPluralElement, isPoundElement, isSelectElement, isTagElement, parse } from "@formatjs/icu-messageformat-parser";
3
+ //#region packages/editor/message.tsx
4
+ /** Retains every ICU branch and skeleton; incomplete edits are valid input. */
5
+ function parseMessage(message) {
6
+ try {
7
+ return {
8
+ ast: parse(message),
9
+ error: null
10
+ };
11
+ } catch (error) {
12
+ return {
13
+ ast: null,
14
+ error: error instanceof Error ? error : new Error(String(error))
15
+ };
16
+ }
35
17
  }
36
- function CoreApp() {
37
- var intl = (0, react_intl_1.useIntl)();
38
- var _a = (0, react_1.useState)([]), messages = _a[0], setMessages = _a[1];
39
- var _b = (0, react_1.useState)(true), isLoading = _b[0], setIsLoading = _b[1];
40
- (0, react_1.useEffect)(function () {
41
- ;
42
- (function () {
43
- return (0, tslib_1.__awaiter)(this, void 0, void 0, function () {
44
- var _a;
45
- return (0, tslib_1.__generator)(this, function (_b) {
46
- switch (_b.label) {
47
- case 0:
48
- _a = setMessages;
49
- return [4 /*yield*/, fetchData()];
50
- case 1:
51
- _a.apply(void 0, [_b.sent()]);
52
- setIsLoading(false);
53
- return [2 /*return*/];
54
- }
55
- });
56
- });
57
- })();
58
- }, []);
59
- return (react_1.default.createElement(react_1.default.Fragment, null,
60
- react_1.default.createElement(core_1.CssBaseline, null),
61
- react_1.default.createElement(core_1.Box, { height: "100vh" },
62
- react_1.default.createElement(core_1.AppBar, { position: "static" },
63
- react_1.default.createElement(core_1.Toolbar, { variant: "dense" },
64
- react_1.default.createElement(core_1.IconButton, { edge: "start", color: "inherit", "aria-label": "menu" },
65
- react_1.default.createElement(icons_1.Menu, null)),
66
- react_1.default.createElement(core_1.Drawer, { anchor: "left" },
67
- react_1.default.createElement(core_1.List, null, ['Inbox', 'Starred', 'Send email', 'Drafts'].map(function (text) { return (react_1.default.createElement(core_1.ListItem, { button: true, key: text },
68
- react_1.default.createElement(core_1.ListItemText, { primary: text }))); }))),
69
- react_1.default.createElement(header_1.default, null),
70
- react_1.default.createElement(icons_1.AddAlert, null),
71
- react_1.default.createElement(icons_1.NotificationsOff, null))),
72
- react_1.default.createElement(core_1.Grid, { container: true, spacing: 0 },
73
- react_1.default.createElement(core_1.Grid, { xs: 3 },
74
- react_1.default.createElement("form", { noValidate: true, autoComplete: "off" },
75
- react_1.default.createElement(core_1.TextField, { type: "search", fullWidth: true, margin: "none", size: "small", variant: "filled", label: intl.formatMessage({
76
- id: 'search-bar-label',
77
- defaultMessage: 'Search message',
78
- description: 'label in search bar',
79
- }) })),
80
- react_1.default.createElement(core_1.Box, { borderRight: 1, overflow: "auto", height: "calc(100vh - 108px)" },
81
- react_1.default.createElement(messages_1.default, { messages: messages, isLoading: isLoading, count: MESSAGES_COUNT }))),
82
- react_1.default.createElement(core_1.Grid, { xs: 6 },
83
- react_1.default.createElement(core_1.Box, { borderRight: 1, overflow: "auto", height: "calc(100vh - 48px)" },
84
- react_1.default.createElement(core_1.Box, { p: 2 },
85
- react_1.default.createElement(core_1.Typography, { variant: "h6", component: "h6" }, "English message"),
86
- react_1.default.createElement(core_1.Typography, { variant: "body2", component: "span" },
87
- "Description",
88
- ' ',
89
- react_1.default.createElement(core_1.Typography, { variant: "body2", color: "textSecondary", component: "span" }, "This is a description"))),
90
- react_1.default.createElement(core_1.TextField, { multiline: true, fullWidth: true, margin: "dense", size: "small", variant: "filled", rows: 4, label: intl.formatMessage({
91
- id: 'translated-box-label',
92
- defaultMessage: 'Translate',
93
- description: 'translated box label',
94
- }) }),
95
- react_1.default.createElement(core_1.Box, { px: 2, py: 1, display: "flex", justifyContent: "space-between", alignItems: "center" },
96
- react_1.default.createElement(core_1.Typography, { variant: "body2", component: "span" }, "71 / 101"),
97
- react_1.default.createElement(core_1.Box, null,
98
- react_1.default.createElement(core_1.ButtonGroup, { variant: "text", color: "primary", "aria-label": "text primary button group" },
99
- react_1.default.createElement(core_1.Button, null, "Copy"),
100
- react_1.default.createElement(core_1.Button, null, "Clear")),
101
- react_1.default.createElement(core_1.Button, { variant: "contained", color: "primary" }, "Translate"))))),
102
- react_1.default.createElement(core_1.Grid, { xs: 3 },
103
- react_1.default.createElement(core_1.Tabs, { indicatorColor: "primary", textColor: "primary", centered: true, "aria-label": "disabled tabs example", value: 0, variant: "fullWidth" },
104
- react_1.default.createElement(core_1.Tab, { label: "Active" }),
105
- react_1.default.createElement(core_1.Tab, { label: "Active" })))))));
18
+ function Message({ message, children }) {
19
+ return children(useMemo(() => parseMessage(message), [message]));
106
20
  }
107
- exports.CoreApp = CoreApp;
108
- function App() {
109
- var prefersDarkMode = (0, core_1.useMediaQuery)('(prefers-color-scheme: dark)');
110
- var theme = react_1.default.useMemo(function () {
111
- return (0, core_1.createMuiTheme)({
112
- palette: {
113
- type: prefersDarkMode ? 'dark' : 'light',
114
- },
115
- });
116
- }, [prefersDarkMode]);
117
- return (react_1.default.createElement(react_intl_1.IntlProvider, { locale: "en", messages: {} },
118
- react_1.default.createElement(core_1.ThemeProvider, { theme: theme },
119
- react_1.default.createElement(CoreApp, null))));
21
+ //#endregion
22
+ //#region packages/editor/core.tsx
23
+ /** Controlled message data with no DOM, styling, providers, or network access. */
24
+ function useMessageEditor({ messages, onMessageChange, defaultSelectedId }) {
25
+ const [selectedId, selectMessage] = useState(defaultSelectedId);
26
+ const [query, setQuery] = useState("");
27
+ const selectedMessage = messages.find((message) => message.id === selectedId) ?? messages[0];
28
+ const visibleMessages = useMemo(() => {
29
+ const search = query.trim().toLowerCase();
30
+ return messages.filter((message) => [
31
+ message.id,
32
+ message.defaultMessage,
33
+ message.translatedMessage,
34
+ message.description ?? ""
35
+ ].some((value) => value.toLowerCase().includes(search)));
36
+ }, [messages, query]);
37
+ const sourceText = selectedMessage?.defaultMessage;
38
+ const translationText = selectedMessage?.translatedMessage;
39
+ const source = useMemo(() => sourceText === void 0 ? void 0 : parseMessage(sourceText), [sourceText]);
40
+ const translation = useMemo(() => translationText === void 0 ? void 0 : parseMessage(translationText), [translationText]);
41
+ function setTranslation(value) {
42
+ if (selectedMessage) onMessageChange({
43
+ ...selectedMessage,
44
+ translatedMessage: value
45
+ });
46
+ }
47
+ return {
48
+ messages: visibleMessages,
49
+ selectedMessage,
50
+ selectMessage,
51
+ query,
52
+ setQuery,
53
+ source,
54
+ translation,
55
+ setTranslation,
56
+ copySource: () => {
57
+ if (selectedMessage) setTranslation(selectedMessage.defaultMessage);
58
+ },
59
+ clearTranslation: () => setTranslation("")
60
+ };
120
61
  }
121
- exports.default = App;
62
+ function Editor({ children, ...options }) {
63
+ return children(useMessageEditor(options));
64
+ }
65
+ //#endregion
66
+ //#region packages/editor/validation.ts
67
+ function group(elements) {
68
+ const groups = /* @__PURE__ */ new Map();
69
+ for (const element of elements) {
70
+ if (isLiteralElement(element) || isPoundElement(element)) continue;
71
+ const key = JSON.stringify([
72
+ element.type,
73
+ element.value,
74
+ "style" in element ? element.style : null
75
+ ]);
76
+ groups.set(key, [...groups.get(key) ?? [], element]);
77
+ }
78
+ return groups;
79
+ }
80
+ function compatibleElement(source, target) {
81
+ if (isTagElement(source) && isTagElement(target)) return compatible(source.children, target.children);
82
+ if (isSelectElement(source) && isSelectElement(target)) {
83
+ const keys = Object.keys(source.options);
84
+ return keys.length === Object.keys(target.options).length && keys.every((key) => target.options[key] && compatible(source.options[key].value, target.options[key].value));
85
+ }
86
+ if (isPluralElement(source) && isPluralElement(target)) {
87
+ if (source.offset !== target.offset || source.pluralType !== target.pluralType) return false;
88
+ const exactKeys = (element) => Object.keys(element.options).filter((key) => key.startsWith("=")).sort().join(",");
89
+ if (exactKeys(source) !== exactKeys(target)) return false;
90
+ return Object.keys(target.options).every((key) => compatible((source.options[key] ?? source.options.other).value, target.options[key].value));
91
+ }
92
+ return true;
93
+ }
94
+ function compatible(source, target) {
95
+ const sourceGroups = group(source);
96
+ const targetGroups = group(target);
97
+ if (sourceGroups.size !== targetGroups.size) return false;
98
+ return [...targetGroups].every(([key, elements]) => {
99
+ const originals = sourceGroups.get(key);
100
+ return originals !== void 0 && elements.every((element) => originals.some((original) => compatibleElement(original, element))) && originals.every((original) => elements.some((element) => compatibleElement(original, element)));
101
+ });
102
+ }
103
+ /** Returns a stable error code; consumers own localized error copy. */
104
+ function validateTranslation(source, translation) {
105
+ if (!translation.trim()) return "empty";
106
+ let sourceAst;
107
+ let translationAst;
108
+ try {
109
+ sourceAst = parse(source);
110
+ } catch {
111
+ return "invalid-source";
112
+ }
113
+ try {
114
+ translationAst = parse(translation);
115
+ } catch {
116
+ return "invalid-translation";
117
+ }
118
+ return compatible(sourceAst, translationAst) ? null : "structure";
119
+ }
120
+ //#endregion
121
+ //#region packages/editor/workflow.ts
122
+ const draftKey = (id, locale) => JSON.stringify([id, locale]);
123
+ function reconcile(draft, remote) {
124
+ if (!draft) return {
125
+ value: remote,
126
+ baseline: remote,
127
+ remote,
128
+ saving: false,
129
+ error: null,
130
+ saved: false
131
+ };
132
+ if (draft.remote === remote) return draft;
133
+ return {
134
+ ...draft,
135
+ remote,
136
+ baseline: remote,
137
+ value: draft.value === draft.baseline ? remote : draft.value
138
+ };
139
+ }
140
+ /** Per-message, per-locale drafts layered on the existing headless editor. */
141
+ function useTranslationEditor({ messages, locales, onSave, defaultLocale, pageSize = 100 }) {
142
+ const [requestedLocale, requestLocale] = useState(defaultLocale);
143
+ const locale = requestedLocale && locales.includes(requestedLocale) ? requestedLocale : locales[0];
144
+ const [requestedCatalog, requestCatalog] = useState("");
145
+ const [status, updateStatus] = useState("all");
146
+ const [requestedPage, requestPage] = useState(0);
147
+ const [drafts, setDrafts] = useState({});
148
+ const pending = useRef(/* @__PURE__ */ new Set());
149
+ const remoteValues = useRef(/* @__PURE__ */ new Map());
150
+ const values = /* @__PURE__ */ new Map();
151
+ for (const message of messages) for (const value of locales) values.set(draftKey(message.id, value), message.translations[value] ?? "");
152
+ remoteValues.current = values;
153
+ const catalogs = useMemo(() => {
154
+ const values = /* @__PURE__ */ new Set();
155
+ for (const message of messages) for (const catalog of message.catalogs ?? []) values.add(catalog);
156
+ return [...values].sort();
157
+ }, [messages]);
158
+ const catalog = catalogs.includes(requestedCatalog) ? requestedCatalog : "";
159
+ const getDraft = (id, targetLocale = locale ?? "") => reconcile(drafts[draftKey(id, targetLocale)], remoteValues.current.get(draftKey(id, targetLocale)) ?? "");
160
+ function update(key, change) {
161
+ setDrafts((current) => ({
162
+ ...current,
163
+ [key]: change(reconcile(current[key], remoteValues.current.get(key) ?? current[key]?.remote ?? ""))
164
+ }));
165
+ }
166
+ const editor = useMessageEditor({
167
+ messages: locale === void 0 ? [] : messages.filter((message) => {
168
+ const translated = !!getDraft(message.id).baseline;
169
+ return (!catalog || message.catalogs?.includes(catalog)) && (status === "all" || (status === "translated" ? translated : !translated));
170
+ }).map((message) => ({
171
+ id: message.id,
172
+ defaultMessage: message.defaultMessage,
173
+ description: message.description,
174
+ translatedMessage: getDraft(message.id).value
175
+ })),
176
+ onMessageChange: (message) => {
177
+ if (locale !== void 0) update(draftKey(message.id, locale), (draft) => ({
178
+ ...draft,
179
+ value: message.translatedMessage,
180
+ error: null,
181
+ saved: false
182
+ }));
183
+ }
184
+ });
185
+ const selectedMessage = messages.find((message) => message.id === editor.selectedMessage?.id);
186
+ const size = Number.isFinite(pageSize) && pageSize > 0 ? Math.max(1, Math.floor(pageSize)) : 100;
187
+ const pageCount = Math.max(1, Math.ceil(editor.messages.length / size));
188
+ const page = Math.min(requestedPage, pageCount - 1);
189
+ useEffect(() => {
190
+ requestPage((current) => Math.min(current, pageCount - 1));
191
+ }, [pageCount]);
192
+ function getTranslation(id, targetLocale) {
193
+ const message = messages.find((value) => value.id === id);
194
+ if (!message || !locales.includes(targetLocale)) return void 0;
195
+ const key = draftKey(id, targetLocale);
196
+ const draft = getDraft(id, targetLocale);
197
+ const source = message.defaultMessage;
198
+ const validationError = validateTranslation(source, draft.value);
199
+ const changed = draft.value !== draft.baseline;
200
+ return {
201
+ value: draft.value,
202
+ baseline: draft.baseline,
203
+ validationError,
204
+ changed,
205
+ isSaving: draft.saving,
206
+ saveError: draft.error,
207
+ saved: draft.saved && !changed,
208
+ setTranslation: (value) => {
209
+ if (!remoteValues.current.has(key)) return;
210
+ update(key, (current) => ({
211
+ ...current,
212
+ value,
213
+ error: null,
214
+ saved: false
215
+ }));
216
+ },
217
+ reset: () => {
218
+ if (!remoteValues.current.has(key)) return;
219
+ update(key, (current) => ({
220
+ ...current,
221
+ value: current.baseline,
222
+ error: null,
223
+ saved: false
224
+ }));
225
+ },
226
+ save: async (context) => {
227
+ if (!remoteValues.current.has(key)) return {
228
+ status: "skipped",
229
+ reason: "unavailable"
230
+ };
231
+ if (pending.current.has(key)) return {
232
+ status: "skipped",
233
+ reason: "pending"
234
+ };
235
+ if (!changed) return {
236
+ status: "skipped",
237
+ reason: "unchanged"
238
+ };
239
+ if (validationError) return {
240
+ status: "invalid",
241
+ validationError
242
+ };
243
+ const submitted = draft.value;
244
+ const snapshot = Object.freeze({
245
+ source,
246
+ baselineTranslation: draft.baseline,
247
+ context
248
+ });
249
+ pending.current.add(key);
250
+ update(key, (current) => ({
251
+ ...current,
252
+ saving: true,
253
+ error: null,
254
+ saved: false
255
+ }));
256
+ try {
257
+ const value = await onSave({
258
+ id,
259
+ locale: targetLocale,
260
+ translation: submitted
261
+ }, snapshot);
262
+ update(key, (current) => ({
263
+ ...current,
264
+ baseline: submitted,
265
+ saved: true
266
+ }));
267
+ return {
268
+ status: "saved",
269
+ value
270
+ };
271
+ } catch (error) {
272
+ const saveError = error instanceof Error ? error : new Error(String(error));
273
+ update(key, (current) => ({
274
+ ...current,
275
+ error: saveError
276
+ }));
277
+ return {
278
+ status: "failed",
279
+ error: saveError
280
+ };
281
+ } finally {
282
+ pending.current.delete(key);
283
+ update(key, (current) => ({
284
+ ...current,
285
+ saving: false
286
+ }));
287
+ }
288
+ }
289
+ };
290
+ }
291
+ const selectedTranslation = selectedMessage && locale !== void 0 ? getTranslation(selectedMessage.id, locale) : void 0;
292
+ return {
293
+ editor: {
294
+ ...editor,
295
+ setQuery: (query) => {
296
+ editor.setQuery(query);
297
+ requestPage(0);
298
+ }
299
+ },
300
+ selectedMessage,
301
+ locale,
302
+ setLocale: (value) => {
303
+ requestLocale(value);
304
+ requestPage(0);
305
+ },
306
+ catalogs,
307
+ catalog,
308
+ setCatalog: (value) => {
309
+ requestCatalog(value);
310
+ requestPage(0);
311
+ },
312
+ status,
313
+ setStatus: (value) => {
314
+ updateStatus(value);
315
+ requestPage(0);
316
+ },
317
+ page,
318
+ pageCount,
319
+ setPage: (value) => requestPage(Number.isFinite(value) ? Math.max(0, Math.min(Math.floor(value), pageCount - 1)) : 0),
320
+ pageMessages: editor.messages.slice(page * size, (page + 1) * size),
321
+ validationError: selectedTranslation?.validationError ?? null,
322
+ changed: selectedTranslation?.changed ?? false,
323
+ isSaving: selectedTranslation?.isSaving ?? false,
324
+ saveError: selectedTranslation?.saveError ?? null,
325
+ saved: selectedTranslation?.saved ?? false,
326
+ save: async (context) => selectedTranslation ? selectedTranslation.save(context) : {
327
+ status: "skipped",
328
+ reason: "unavailable"
329
+ },
330
+ reset: () => selectedTranslation?.reset(),
331
+ getTranslation
332
+ };
333
+ }
334
+ //#endregion
335
+ export { Editor, Message, parseMessage, useMessageEditor, useTranslationEditor, validateTranslation };
336
+
337
+ //# sourceMappingURL=index.js.map
package/index.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../message.tsx","../core.tsx","../validation.ts","../workflow.ts"],"sourcesContent":["import {\n parse,\n type MessageFormatElement,\n} from '@formatjs/icu-messageformat-parser'\nimport {useMemo, type ReactNode} from 'react'\n\nexport type ParsedMessage =\n | {ast: MessageFormatElement[]; error: null}\n | {ast: null; error: Error}\n\n/** Retains every ICU branch and skeleton; incomplete edits are valid input. */\nexport function parseMessage(message: string): ParsedMessage {\n try {\n return {ast: parse(message), error: null}\n } catch (error) {\n return {\n ast: null,\n error: error instanceof Error ? error : new Error(String(error)),\n }\n }\n}\n\nexport interface MessageProps {\n message: string\n children: (parsed: ParsedMessage) => ReactNode\n}\n\nexport function Message({message, children}: MessageProps): ReactNode {\n const parsed = useMemo(() => parseMessage(message), [message])\n return children(parsed)\n}\n","import {useMemo, useState, type ReactNode} from 'react'\nimport {parseMessage, type ParsedMessage} from './message.js'\nimport type {TranslatedMessage} from './types.js'\n\nexport interface EditorOptions {\n messages: readonly TranslatedMessage[]\n /** Apply the edit to consumer state; persistence stays with the consumer. */\n onMessageChange: (message: TranslatedMessage) => void\n defaultSelectedId?: string\n}\n\nexport interface EditorState {\n messages: readonly TranslatedMessage[]\n selectedMessage: TranslatedMessage | undefined\n selectMessage: (id: string) => void\n query: string\n setQuery: (query: string) => void\n source: ParsedMessage | undefined\n translation: ParsedMessage | undefined\n setTranslation: (value: string) => void\n copySource: () => void\n clearTranslation: () => void\n}\n\n/** Controlled message data with no DOM, styling, providers, or network access. */\nexport function useMessageEditor({\n messages,\n onMessageChange,\n defaultSelectedId,\n}: EditorOptions): EditorState {\n const [selectedId, selectMessage] = useState(defaultSelectedId)\n const [query, setQuery] = useState('')\n const selectedMessage =\n messages.find(message => message.id === selectedId) ?? messages[0]\n const visibleMessages = useMemo(() => {\n const search = query.trim().toLowerCase()\n return messages.filter(message =>\n [\n message.id,\n message.defaultMessage,\n message.translatedMessage,\n message.description ?? '',\n ].some(value => value.toLowerCase().includes(search))\n )\n }, [messages, query])\n const sourceText = selectedMessage?.defaultMessage\n const translationText = selectedMessage?.translatedMessage\n const source = useMemo(\n () => (sourceText === undefined ? undefined : parseMessage(sourceText)),\n [sourceText]\n )\n const translation = useMemo(\n () =>\n translationText === undefined ? undefined : parseMessage(translationText),\n [translationText]\n )\n function setTranslation(value: string): void {\n if (selectedMessage)\n onMessageChange({...selectedMessage, translatedMessage: value})\n }\n return {\n messages: visibleMessages,\n selectedMessage,\n selectMessage,\n query,\n setQuery,\n source,\n translation,\n setTranslation,\n copySource: () => {\n if (selectedMessage) setTranslation(selectedMessage.defaultMessage)\n },\n clearTranslation: () => setTranslation(''),\n }\n}\n\nexport interface EditorProps extends EditorOptions {\n children: (editor: EditorState) => ReactNode\n}\n\nexport function Editor({children, ...options}: EditorProps): ReactNode {\n return children(useMessageEditor(options))\n}\n","import {\n isLiteralElement,\n isPluralElement,\n isPoundElement,\n isSelectElement,\n isTagElement,\n parse,\n type MessageFormatElement,\n} from '@formatjs/icu-messageformat-parser'\n\nexport type TranslationValidationError =\n | 'empty'\n | 'invalid-source'\n | 'invalid-translation'\n | 'structure'\n\nfunction group(\n elements: MessageFormatElement[]\n): Map<string, MessageFormatElement[]> {\n const groups = new Map<string, MessageFormatElement[]>()\n for (const element of elements) {\n if (isLiteralElement(element) || isPoundElement(element)) continue\n const key = JSON.stringify([\n element.type,\n element.value,\n 'style' in element ? element.style : null,\n ])\n groups.set(key, [...(groups.get(key) ?? []), element])\n }\n return groups\n}\n\nfunction compatibleElement(\n source: MessageFormatElement,\n target: MessageFormatElement\n): boolean {\n if (isTagElement(source) && isTagElement(target))\n return compatible(source.children, target.children)\n if (isSelectElement(source) && isSelectElement(target)) {\n const keys = Object.keys(source.options)\n return (\n keys.length === Object.keys(target.options).length &&\n keys.every(\n key =>\n target.options[key] &&\n compatible(source.options[key].value, target.options[key].value)\n )\n )\n }\n if (isPluralElement(source) && isPluralElement(target)) {\n if (\n source.offset !== target.offset ||\n source.pluralType !== target.pluralType\n )\n return false\n const exactKeys = (element: typeof source): string =>\n Object.keys(element.options)\n .filter(key => key.startsWith('='))\n .sort()\n .join(',')\n if (exactKeys(source) !== exactKeys(target)) return false\n // New locale categories inherit the source's fallback contract.\n return Object.keys(target.options).every(key =>\n compatible(\n (source.options[key] ?? source.options.other).value,\n target.options[key].value\n )\n )\n }\n return true\n}\n\nfunction compatible(\n source: MessageFormatElement[],\n target: MessageFormatElement[]\n): boolean {\n const sourceGroups = group(source)\n const targetGroups = group(target)\n if (sourceGroups.size !== targetGroups.size) return false\n return [...targetGroups].every(([key, elements]) => {\n const originals = sourceGroups.get(key)\n return (\n originals !== undefined &&\n elements.every(element =>\n originals.some(original => compatibleElement(original, element))\n ) &&\n originals.every(original =>\n elements.some(element => compatibleElement(original, element))\n )\n )\n })\n}\n\n/** Returns a stable error code; consumers own localized error copy. */\nexport function validateTranslation(\n source: string,\n translation: string\n): TranslationValidationError | null {\n if (!translation.trim()) return 'empty'\n let sourceAst: MessageFormatElement[]\n let translationAst: MessageFormatElement[]\n try {\n sourceAst = parse(source)\n } catch {\n return 'invalid-source'\n }\n try {\n translationAst = parse(translation)\n } catch {\n return 'invalid-translation'\n }\n return compatible(sourceAst, translationAst) ? null : 'structure'\n}\n","import {useEffect, useMemo, useRef, useState} from 'react'\nimport {useMessageEditor, type EditorState} from '#packages/editor/core.js'\nimport {\n validateTranslation,\n type TranslationValidationError,\n} from '#packages/editor/validation.js'\n\nexport interface SourceLocation {\n file: string\n start?: number\n end?: number\n}\nexport interface EditorMessage {\n id: string\n defaultMessage: string\n description?: string\n catalogs?: readonly string[]\n locations?: readonly SourceLocation[]\n translations: Readonly<Record<string, string | undefined>>\n}\nexport interface TranslationUpdate {\n id: string\n locale: string\n translation: string\n}\nexport type MessageStatus = 'all' | 'translated' | 'missing'\n/** Render-snapshot metadata plus consumer-owned context supplied to save. */\nexport interface TranslationSaveSnapshot<TContext = void> {\n readonly source: string\n readonly baselineTranslation: string\n readonly context: TContext | undefined\n}\nexport type TranslationSaveResult<TResult = void> =\n | {status: 'saved'; value: TResult}\n | {status: 'failed'; error: Error}\n | {status: 'invalid'; validationError: TranslationValidationError}\n | {status: 'skipped'; reason: 'unavailable' | 'unchanged' | 'pending'}\nexport interface TranslationEditorOptions<TContext = void, TResult = void> {\n messages: readonly EditorMessage[]\n locales: readonly string[]\n onSave: (\n update: TranslationUpdate,\n snapshot: TranslationSaveSnapshot<TContext>\n ) => TResult | Promise<TResult>\n defaultLocale?: string\n pageSize?: number\n}\n/** A render snapshot and actions for one message/locale pair. */\nexport interface TranslationDraftState<TContext = void, TResult = void> {\n readonly value: string\n readonly baseline: string\n readonly validationError: TranslationValidationError | null\n readonly changed: boolean\n readonly isSaving: boolean\n readonly saveError: Error | null\n readonly saved: boolean\n setTranslation: (value: string) => void\n reset: () => void\n save: (context?: TContext) => Promise<TranslationSaveResult<TResult>>\n}\nexport interface TranslationEditorState<TContext = void, TResult = void> {\n editor: EditorState\n selectedMessage: EditorMessage | undefined\n locale: string | undefined\n setLocale: (locale: string) => void\n catalogs: readonly string[]\n catalog: string\n setCatalog: (catalog: string) => void\n status: MessageStatus\n setStatus: (status: MessageStatus) => void\n page: number\n pageCount: number\n setPage: (page: number) => void\n pageMessages: EditorState['messages']\n validationError: TranslationValidationError | null\n changed: boolean\n isSaving: boolean\n saveError: Error | null\n saved: boolean\n save: (context?: TContext) => Promise<TranslationSaveResult<TResult>>\n reset: () => void\n getTranslation: (\n id: string,\n locale: string\n ) => TranslationDraftState<TContext, TResult> | undefined\n}\ninterface Draft {\n value: string\n baseline: string\n remote: string\n saving: boolean\n error: Error | null\n saved: boolean\n}\nconst draftKey = (id: string, locale: string): string =>\n JSON.stringify([id, locale])\nfunction reconcile(draft: Draft | undefined, remote: string): Draft {\n if (!draft)\n return {\n value: remote,\n baseline: remote,\n remote,\n saving: false,\n error: null,\n saved: false,\n }\n if (draft.remote === remote) return draft\n return {\n ...draft,\n remote,\n baseline: remote,\n value: draft.value === draft.baseline ? remote : draft.value,\n }\n}\n\n/** Per-message, per-locale drafts layered on the existing headless editor. */\nexport function useTranslationEditor<TContext = void, TResult = void>({\n messages,\n locales,\n onSave,\n defaultLocale,\n pageSize = 100,\n}: TranslationEditorOptions<TContext, TResult>): TranslationEditorState<\n TContext,\n TResult\n> {\n const [requestedLocale, requestLocale] = useState(defaultLocale)\n const locale =\n requestedLocale && locales.includes(requestedLocale)\n ? requestedLocale\n : locales[0]\n const [requestedCatalog, requestCatalog] = useState('')\n const [status, updateStatus] = useState<MessageStatus>('all')\n const [requestedPage, requestPage] = useState(0)\n const [drafts, setDrafts] = useState<Record<string, Draft>>({})\n const pending = useRef(new Set<string>())\n const remoteValues = useRef(new Map<string, string>())\n const values = new Map<string, string>()\n for (const message of messages) {\n for (const value of locales) {\n values.set(draftKey(message.id, value), message.translations[value] ?? '')\n }\n }\n remoteValues.current = values\n const catalogs = useMemo(() => {\n const values = new Set<string>()\n for (const message of messages) {\n for (const catalog of message.catalogs ?? []) values.add(catalog)\n }\n return [...values].sort()\n }, [messages])\n const catalog = catalogs.includes(requestedCatalog) ? requestedCatalog : ''\n const getDraft = (id: string, targetLocale = locale ?? ''): Draft =>\n reconcile(\n drafts[draftKey(id, targetLocale)],\n remoteValues.current.get(draftKey(id, targetLocale)) ?? ''\n )\n function update(key: string, change: (draft: Draft) => Draft): void {\n setDrafts(current => ({\n ...current,\n [key]: change(\n reconcile(\n current[key],\n remoteValues.current.get(key) ?? current[key]?.remote ?? ''\n )\n ),\n }))\n }\n const editor = useMessageEditor({\n messages:\n locale === undefined\n ? []\n : messages\n .filter(message => {\n const translated = !!getDraft(message.id).baseline\n return (\n (!catalog || message.catalogs?.includes(catalog)) &&\n (status === 'all' ||\n (status === 'translated' ? translated : !translated))\n )\n })\n .map(message => ({\n id: message.id,\n defaultMessage: message.defaultMessage,\n description: message.description,\n translatedMessage: getDraft(message.id).value,\n })),\n onMessageChange: message => {\n if (locale !== undefined)\n update(draftKey(message.id, locale), draft => ({\n ...draft,\n value: message.translatedMessage,\n error: null,\n saved: false,\n }))\n },\n })\n const selectedMessage = messages.find(\n message => message.id === editor.selectedMessage?.id\n )\n const size =\n Number.isFinite(pageSize) && pageSize > 0\n ? Math.max(1, Math.floor(pageSize))\n : 100\n const pageCount = Math.max(1, Math.ceil(editor.messages.length / size))\n const page = Math.min(requestedPage, pageCount - 1)\n useEffect(() => {\n requestPage(current => Math.min(current, pageCount - 1))\n }, [pageCount])\n function getTranslation(\n id: string,\n targetLocale: string\n ): TranslationDraftState<TContext, TResult> | undefined {\n const message = messages.find(value => value.id === id)\n if (!message || !locales.includes(targetLocale)) return undefined\n const key = draftKey(id, targetLocale)\n const draft = getDraft(id, targetLocale)\n const source = message.defaultMessage\n const validationError = validateTranslation(source, draft.value)\n const changed = draft.value !== draft.baseline\n return {\n value: draft.value,\n baseline: draft.baseline,\n validationError,\n changed,\n isSaving: draft.saving,\n saveError: draft.error,\n saved: draft.saved && !changed,\n setTranslation: value => {\n if (!remoteValues.current.has(key)) return\n update(key, current => ({...current, value, error: null, saved: false}))\n },\n reset: () => {\n if (!remoteValues.current.has(key)) return\n update(key, current => ({\n ...current,\n value: current.baseline,\n error: null,\n saved: false,\n }))\n },\n save: async context => {\n if (!remoteValues.current.has(key))\n return {status: 'skipped', reason: 'unavailable'}\n if (pending.current.has(key))\n return {status: 'skipped', reason: 'pending'}\n if (!changed) return {status: 'skipped', reason: 'unchanged'}\n if (validationError) return {status: 'invalid', validationError}\n const submitted = draft.value\n const snapshot: TranslationSaveSnapshot<TContext> = Object.freeze({\n source,\n baselineTranslation: draft.baseline,\n context,\n })\n pending.current.add(key)\n update(key, current => ({\n ...current,\n saving: true,\n error: null,\n saved: false,\n }))\n try {\n const value = await onSave(\n {id, locale: targetLocale, translation: submitted},\n snapshot\n )\n update(key, current => ({\n ...current,\n baseline: submitted,\n saved: true,\n }))\n return {status: 'saved', value}\n } catch (error) {\n const saveError =\n error instanceof Error ? error : new Error(String(error))\n update(key, current => ({...current, error: saveError}))\n return {status: 'failed', error: saveError}\n } finally {\n pending.current.delete(key)\n update(key, current => ({...current, saving: false}))\n }\n },\n }\n }\n const selectedTranslation =\n selectedMessage && locale !== undefined\n ? getTranslation(selectedMessage.id, locale)\n : undefined\n return {\n editor: {\n ...editor,\n setQuery: query => {\n editor.setQuery(query)\n requestPage(0)\n },\n },\n selectedMessage,\n locale,\n setLocale: value => {\n requestLocale(value)\n requestPage(0)\n },\n catalogs,\n catalog,\n setCatalog: value => {\n requestCatalog(value)\n requestPage(0)\n },\n status,\n setStatus: value => {\n updateStatus(value)\n requestPage(0)\n },\n page,\n pageCount,\n setPage: value =>\n requestPage(\n Number.isFinite(value)\n ? Math.max(0, Math.min(Math.floor(value), pageCount - 1))\n : 0\n ),\n pageMessages: editor.messages.slice(page * size, (page + 1) * size),\n validationError: selectedTranslation?.validationError ?? null,\n changed: selectedTranslation?.changed ?? false,\n isSaving: selectedTranslation?.isSaving ?? false,\n saveError: selectedTranslation?.saveError ?? null,\n saved: selectedTranslation?.saved ?? false,\n save: async context =>\n selectedTranslation\n ? selectedTranslation.save(context)\n : {status: 'skipped', reason: 'unavailable'},\n reset: () => selectedTranslation?.reset(),\n getTranslation,\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,42 +1,50 @@
1
1
  {
2
2
  "name": "@formatjs/editor",
3
- "version": "1.1.48",
4
- "description": "A ICU MessageFormat Editor UI",
3
+ "version": "1.4.0",
4
+ "private": false,
5
+ "description": "Headless ICU MessageFormat editor for React",
5
6
  "keywords": [
6
- "intl",
7
+ "cli",
8
+ "format",
9
+ "formatjs",
10
+ "formatting",
11
+ "globalization",
7
12
  "i18n",
8
13
  "internationalization",
14
+ "intl",
9
15
  "locale",
10
16
  "localization",
11
- "globalization",
12
17
  "react",
13
18
  "react-intl",
14
19
  "reactjs",
15
- "format",
16
- "formatjs",
17
- "formatting",
18
20
  "translate",
19
- "translation",
20
- "cli"
21
+ "translation"
21
22
  ],
22
- "author": "Long Ho <holevietlong@gmail.com>",
23
23
  "homepage": "https://github.com/formatjs/formatjs",
24
+ "bugs": {
25
+ "url": "https://github.com/formatjs/formatjs/issues"
26
+ },
24
27
  "license": "MIT",
25
- "main": "index.js",
28
+ "author": "Long Ho <holevietlong@gmail.com>",
26
29
  "repository": {
27
30
  "type": "git",
28
31
  "url": "git+ssh://git@github.com/formatjs/formatjs.git"
29
32
  },
33
+ "type": "module",
34
+ "sideEffects": false,
35
+ "types": "index.d.ts",
36
+ "exports": {
37
+ ".": "./index.js",
38
+ "./ui": {
39
+ "types": "./ui.d.ts",
40
+ "default": "./ui.js"
41
+ }
42
+ },
30
43
  "dependencies": {
31
- "@formatjs/icu-messageformat-parser": "2.1.0",
32
- "@material-ui/core": "^4.11.3",
33
- "@material-ui/icons": "^4.11.2",
34
- "@material-ui/lab": "4.0.0-alpha.57",
35
- "@types/react": "17",
36
- "react": "17",
37
- "react-intl": "5.25.1"
44
+ "@formatjs/icu-messageformat-parser": "3.5.17"
38
45
  },
39
- "bugs": {
40
- "url": "https://github.com/formatjs/formatjs/issues"
46
+ "peerDependencies": {
47
+ "@types/react": "^19.0.0",
48
+ "react": "^19.0.0"
41
49
  }
42
- }
50
+ }