@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/LICENSE.md +1 -1
- package/README.md +393 -2
- package/index.d.ts +143 -3
- package/index.js +334 -118
- package/index.js.map +1 -0
- package/package.json +29 -21
- package/ui.d.ts +177 -0
- package/ui.js +212 -0
- package/ui.js.map +1 -0
- package/header.d.ts +0 -6
- package/header.d.ts.map +0 -1
- package/header.js +0 -16
- package/index.d.ts.map +0 -1
- package/lib/header.d.ts +0 -6
- package/lib/header.d.ts.map +0 -1
- package/lib/header.js +0 -13
- package/lib/index.d.ts +0 -4
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js +0 -116
- package/lib/main.d.ts +0 -2
- package/lib/main.d.ts.map +0 -1
- package/lib/main.js +0 -4
- package/lib/message.d.ts +0 -7
- package/lib/message.d.ts.map +0 -1
- package/lib/message.js +0 -45
- package/lib/messages.d.ts +0 -10
- package/lib/messages.d.ts.map +0 -1
- package/lib/messages.js +0 -21
- package/lib/types.d.ts +0 -6
- package/lib/types.d.ts.map +0 -1
- package/lib/types.js +0 -1
- package/main.d.ts +0 -2
- package/main.d.ts.map +0 -1
- package/main.js +0 -7
- package/message.d.ts +0 -7
- package/message.d.ts.map +0 -1
- package/message.js +0 -47
- package/messages.d.ts +0 -10
- package/messages.d.ts.map +0 -1
- package/messages.js +0 -24
- package/types.d.ts +0 -6
- package/types.d.ts.map +0 -1
- package/types.js +0 -2
package/index.js
CHANGED
|
@@ -1,121 +1,337 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
|
37
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
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.
|
|
4
|
-
"
|
|
3
|
+
"version": "1.4.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Headless ICU MessageFormat editor for React",
|
|
5
6
|
"keywords": [
|
|
6
|
-
"
|
|
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
|
-
"
|
|
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": "
|
|
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
|
-
"
|
|
40
|
-
"
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"@types/react": "^19.0.0",
|
|
48
|
+
"react": "^19.0.0"
|
|
41
49
|
}
|
|
42
|
-
}
|
|
50
|
+
}
|