@formatjs/editor 1.5.2 → 1.6.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/README.md +53 -21
- package/index.d.ts +30 -1
- package/index.js +73 -11
- package/index.js.map +1 -1
- package/package.json +2 -2
- package/ui.d.ts +24 -8
- package/ui.js +93 -18
- package/ui.js.map +1 -1
package/README.md
CHANGED
|
@@ -96,6 +96,21 @@ reflect saved translations, so typing does not move a message out of the list.
|
|
|
96
96
|
Locales may load asynchronously; an absent selection falls back to the first
|
|
97
97
|
available locale. Pagination clamps when messages or page size change.
|
|
98
98
|
|
|
99
|
+
General search matches IDs, source, translation, and description. The independent
|
|
100
|
+
`descriptionQuery` / `setDescriptionQuery` pair matches descriptions only; both
|
|
101
|
+
queries are case-insensitive and conjunctive. Controlled server-side views can use
|
|
102
|
+
the same two-search interface without loading a full catalog into the hook.
|
|
103
|
+
|
|
104
|
+
`hasTranslationForEveryLocale(translations, locales)` and
|
|
105
|
+
`matchesMessageStatus(translations, locales, status)` expose the multi-locale
|
|
106
|
+
status contract: translated means a defined entry exists for every selected locale (including an
|
|
107
|
+
empty string),
|
|
108
|
+
while missing means at least one selected locale has no entry.
|
|
109
|
+
|
|
110
|
+
`useTranslationLayout({storageKey})` supplies a grid-by-default `grid | list`
|
|
111
|
+
preference. It restores and writes browser localStorage when available, tolerates
|
|
112
|
+
unavailable storage, and accepts a storage adapter for non-browser hosts and tests.
|
|
113
|
+
|
|
99
114
|
Drafts, reset baselines, errors, and pending saves are scoped by message ID and
|
|
100
115
|
locale. Switching messages, filters, or locales retains drafts. `save()` validates
|
|
101
116
|
the selected draft, ignores duplicate submissions for that key, and reports
|
|
@@ -238,6 +253,9 @@ For server-side search, pass the loaded page directly as `messages`, your search
|
|
|
238
253
|
value/callback as `search`, and your externally selected detail as
|
|
239
254
|
`selectedMessage`. Selection can remain outside the loaded page. `loading` marks
|
|
240
255
|
navigation busy and displays a status; it does not clear the controlled list.
|
|
256
|
+
Pass `descriptionSearch` for a separately controlled description-only field. The
|
|
257
|
+
view does not combine or debounce requests; those data-source concerns remain with
|
|
258
|
+
the caller.
|
|
241
259
|
|
|
242
260
|
### Component adapters
|
|
243
261
|
|
|
@@ -299,14 +317,14 @@ controls.
|
|
|
299
317
|
|
|
300
318
|
Each component has an exported props contract:
|
|
301
319
|
|
|
302
|
-
| Component | Inputs
|
|
303
|
-
| -------------------------------------- |
|
|
304
|
-
| `Button` / `EditorButtonProps` | `children`, `variant`, optional `disabled`
|
|
305
|
-
| `TextInput` / `EditorTextInputProps` | `id`, `value`, `type` (`text` or `search`), optional disabled/error-description attributes
|
|
306
|
-
| `TextArea` / `EditorTextAreaProps` | `id`, `value`,
|
|
307
|
-
| `MessageRow` / `EditorMessageRowProps` | `children`, controlled `selected`
|
|
308
|
-
| `Panel` / `EditorPanelProps` | `children`, accessible `label`, `kind` (`source` or `translation`)
|
|
309
|
-
| `Layout` / `EditorLayoutProps` | `toolbar`, `navigation`, `content`, optional `sidebar` nodes
|
|
320
|
+
| Component | Inputs | Output callback |
|
|
321
|
+
| -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ |
|
|
322
|
+
| `Button` / `EditorButtonProps` | `children`, `variant`, optional `disabled` | `onPress(): void` |
|
|
323
|
+
| `TextInput` / `EditorTextInputProps` | `id`, `value`, `type` (`text` or `search`), optional disabled/error-description attributes | `onValueChange(value: string): void` |
|
|
324
|
+
| `TextArea` / `EditorTextAreaProps` | `id`, `value`, `minRows` / `maxRows` bounds (one and ten by default), optional legacy `rows`, disabled/error-description attributes | `onValueChange(value: string): void` |
|
|
325
|
+
| `MessageRow` / `EditorMessageRowProps` | `children`, controlled `selected` | `onSelect(): void` |
|
|
326
|
+
| `Panel` / `EditorPanelProps` | `children`, accessible `label`, `kind` (`source` or `translation`) | None; layout only |
|
|
327
|
+
| `Layout` / `EditorLayoutProps` | `toolbar`, `navigation`, `content`, optional `sidebar` nodes | None; layout only |
|
|
310
328
|
|
|
311
329
|
`EditorInputProps` defines the shared input attributes explicitly: `id`, `value`,
|
|
312
330
|
`onValueChange`, optional `disabled`, `aria-invalid`, and `aria-describedby`.
|
|
@@ -321,7 +339,10 @@ Adapter requirements:
|
|
|
321
339
|
|
|
322
340
|
- Inputs forward `id`, `value`, `disabled`, `aria-invalid`, and
|
|
323
341
|
`aria-describedby` to their focusable control. They report strings through
|
|
324
|
-
`onValueChange` and stay associated with the view's visible label.
|
|
342
|
+
`onValueChange` and stay associated with the view's visible label. Textarea
|
|
343
|
+
adapters honor `minRows` and `maxRows` by growing and shrinking with controlled
|
|
344
|
+
content, then scrolling above the maximum. The native adapter also resizes when its width
|
|
345
|
+
changes.
|
|
325
346
|
- Buttons honor `disabled`, support keyboard activation, and do not submit a
|
|
326
347
|
surrounding form. `onPress` and `onSelect` take no event argument.
|
|
327
348
|
- Message rows expose selection (the native adapter uses `aria-current`) and
|
|
@@ -356,11 +377,16 @@ function TranslationTools() {
|
|
|
356
377
|
selectedLocales={selectedLocales}
|
|
357
378
|
onChange={setSelectedLocales}
|
|
358
379
|
getLocaleLabel={locale => displayNames.of(locale) ?? locale}
|
|
380
|
+
renderLocaleLabel={(locale, label) => (
|
|
381
|
+
<>
|
|
382
|
+
<strong>{locale}</strong> {label}
|
|
383
|
+
</>
|
|
384
|
+
)}
|
|
359
385
|
/>
|
|
360
386
|
|
|
361
387
|
<MessagePreview message={draft.value} />
|
|
362
388
|
<CopyTextButton value={draft.value} label="translation" />
|
|
363
|
-
<MessageContext message={selectedMessage} />
|
|
389
|
+
<MessageContext message={selectedMessage} copyCatalogs copyLocations />
|
|
364
390
|
</>
|
|
365
391
|
)
|
|
366
392
|
}
|
|
@@ -374,6 +400,8 @@ function TranslationTools() {
|
|
|
374
400
|
disable select-all. `getLocaleLabel` defaults to the code so display language
|
|
375
401
|
stays explicit. `labels` overrides `title`, `empty`, `clear`, and the formatter
|
|
376
402
|
callbacks `selectAll(count)`, `selected(count)`, and `trigger(summary)`.
|
|
403
|
+
`renderLocaleLabel(locale, label)` adds visual structure while
|
|
404
|
+
`getLocaleLabel` remains the plain accessible text.
|
|
377
405
|
- **MessagePreview** shows ICU structure, not a formatted sample result: all plural
|
|
378
406
|
and select branches, ordinal types, plural offsets, rich-text tags, and exact
|
|
379
407
|
number/date/time format tokens (including skeletons). Literals retain whitespace;
|
|
@@ -391,11 +419,17 @@ function TranslationTools() {
|
|
|
391
419
|
Native clipboard access happens only when the user presses the button.
|
|
392
420
|
- **MessageContext** renders an ID, optional description, catalogs, and source
|
|
393
421
|
locations from the existing `EditorMessage` metadata contract. Locations render
|
|
394
|
-
as plain text with optional start/end ranges; `renderLocation(location)` can
|
|
422
|
+
as plain text with optional start/end ranges; `renderLocation(location, text)` can
|
|
395
423
|
customize them. `copyId` defaults to true and `copyOptions` passes clipboard
|
|
396
|
-
callbacks, labels, writer, and timing to
|
|
397
|
-
`
|
|
398
|
-
|
|
424
|
+
callbacks, labels, writer, and timing to copy controls. `copyCatalogs` and
|
|
425
|
+
`copyLocations` opt exact metadata values into the same feedback path. `labels`
|
|
426
|
+
also exposes their copy-label formatters. A missing message clears the content;
|
|
427
|
+
absent metadata sections are omitted.
|
|
428
|
+
|
|
429
|
+
`hasMeaningfulIcuStructure(message)` returns true only when parsing succeeds and the
|
|
430
|
+
AST contains a non-literal element (arguments, selectors, formatters, pound tokens,
|
|
431
|
+
or rich-text tags). Use it to omit structural preview chrome for plain literals,
|
|
432
|
+
escaped braces, and incomplete drafts while keeping validation feedback separate.
|
|
399
433
|
|
|
400
434
|
Tool adapters are optional in `EditorComponents`, so existing complete registries
|
|
401
435
|
remain valid. The hook resolves all of them to native defaults. Override them in
|
|
@@ -514,22 +548,20 @@ library. The workflow demo demonstrates a React Intl consumer without making
|
|
|
514
548
|
|
|
515
549
|
Write native Playwright `*.spec.ts` files in `packages/editor/vrt/`. The
|
|
516
550
|
runner supplies `baseURL`, so specs can use `page.goto('/')`,
|
|
517
|
-
accessible locators, clicks, and web-first assertions. VRT captures are generated from the `.visual.tsx` module. Both targets use the built application and
|
|
551
|
+
accessible locators, clicks, and web-first assertions. VRT captures are generated from the `.visual.tsx` module. Both targets use the built application. E2E, component tests, and VRT use the same declared Linux runtime in actiond; provisioning is documented in the browser setup guide.
|
|
518
552
|
|
|
519
553
|
```sh
|
|
520
|
-
|
|
521
|
-
bazel test //packages/editor/vrt:e2e_test --test_arg=--grep=translation
|
|
554
|
+
bash .github/scripts/actiond-vrt.sh
|
|
522
555
|
```
|
|
523
556
|
|
|
524
557
|
E2E covers editing, search, selection, copy/clear, ICU error recovery, locale
|
|
525
|
-
drafts, and saving. It
|
|
558
|
+
drafts, and saving. It uses checksum-pinned Chromium and declared fixtures in a native Bazel test action on isolated Linux. Bazel retries and repeated runs launch Chromium again. See [browser setup](vrt/README.md).
|
|
526
559
|
CI should explicitly select both `e2e_test` and `visual_test`. Failures retain
|
|
527
560
|
JUnit, screenshots, and Playwright traces in undeclared test outputs.
|
|
528
561
|
|
|
529
562
|
## Component browser tests
|
|
530
563
|
|
|
531
|
-
`
|
|
532
|
-
Playwright 1.63 native `mount()` specs for the real editor. The typed
|
|
564
|
+
`component_test` runs Playwright 1.63 native `mount()` specs for the real editor. The typed
|
|
533
565
|
`editor.visual.tsx` uses the existing provider shell and demo; `gallery.tsx` owns
|
|
534
566
|
mount/update/unmount. The tests check provider updates without losing a draft,
|
|
535
567
|
clear, ICU validation, and isolation between mounts.
|
|
@@ -538,7 +570,7 @@ clear, ICU validation, and isolation between mounts.
|
|
|
538
570
|
E2E uses a compiled custom server adapter serving the same built app. The runtime
|
|
539
571
|
selects compiled `*.browser.spec.js` separately from E2E `*.spec.js` and generated
|
|
540
572
|
VRT captures. CI should explicitly run all three
|
|
541
|
-
|
|
573
|
+
browser targets through the `Editor browser tests` workflow. Screenshot baselines and updates remain in `visual_test`.
|
|
542
574
|
|
|
543
575
|
The default export of `editor.visual.tsx` is a `ComponentVisualModule`: it declares
|
|
544
576
|
renderable cases, browser-side capture hooks, and VRT options. The gallery registers
|
package/index.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ type ParsedMessage = {
|
|
|
10
10
|
};
|
|
11
11
|
/** Retains every ICU branch and skeleton; incomplete edits are valid input. */
|
|
12
12
|
export declare function parseMessage(message: string): ParsedMessage;
|
|
13
|
+
/** True only for valid ICU messages containing non-literal syntax. */
|
|
14
|
+
export declare function hasMeaningfulIcuStructure(message: string): boolean;
|
|
13
15
|
interface MessageProps {
|
|
14
16
|
message: string;
|
|
15
17
|
children: (parsed: ParsedMessage) => ReactNode;
|
|
@@ -37,6 +39,8 @@ interface EditorState {
|
|
|
37
39
|
selectMessage: (id: string) => void;
|
|
38
40
|
query: string;
|
|
39
41
|
setQuery: (query: string) => void;
|
|
42
|
+
descriptionQuery: string;
|
|
43
|
+
setDescriptionQuery: (query: string) => void;
|
|
40
44
|
source: ParsedMessage | undefined;
|
|
41
45
|
translation: ParsedMessage | undefined;
|
|
42
46
|
setTranslation: (value: string) => void;
|
|
@@ -50,6 +54,25 @@ interface EditorProps extends EditorOptions {
|
|
|
50
54
|
}
|
|
51
55
|
export declare function Editor({ children, ...options }: EditorProps): ReactNode;
|
|
52
56
|
//#endregion
|
|
57
|
+
//#region packages/editor/layout.d.ts
|
|
58
|
+
type TranslationLayout = "grid" | "list";
|
|
59
|
+
interface EditorPreferenceStorage {
|
|
60
|
+
getItem(key: string): string | null;
|
|
61
|
+
setItem(key: string, value: string): void;
|
|
62
|
+
}
|
|
63
|
+
interface TranslationLayoutOptions {
|
|
64
|
+
storageKey: string;
|
|
65
|
+
defaultLayout?: TranslationLayout;
|
|
66
|
+
/** Defaults to browser localStorage when available. Pass null to disable persistence. */
|
|
67
|
+
storage?: EditorPreferenceStorage | null;
|
|
68
|
+
}
|
|
69
|
+
interface TranslationLayoutState {
|
|
70
|
+
layout: TranslationLayout;
|
|
71
|
+
setLayout: (layout: TranslationLayout) => void;
|
|
72
|
+
}
|
|
73
|
+
/** Layout preference state with guarded browser persistence and a grid default. */
|
|
74
|
+
export declare function useTranslationLayout({ storageKey, defaultLayout, storage }: TranslationLayoutOptions): TranslationLayoutState;
|
|
75
|
+
//#endregion
|
|
53
76
|
//#region packages/editor/validation.d.ts
|
|
54
77
|
type TranslationValidationError = "empty" | "invalid-source" | "invalid-translation" | "structure";
|
|
55
78
|
/** Returns a stable error code; consumers own localized error copy. */
|
|
@@ -140,5 +163,11 @@ interface TranslationEditorState<TContext = void, TResult = void> {
|
|
|
140
163
|
/** Per-message, per-locale drafts layered on the existing headless editor. */
|
|
141
164
|
export declare function useTranslationEditor<TContext = void, TResult = void>({ messages, locales, onSave, defaultLocale, pageSize }: TranslationEditorOptions<TContext, TResult>): TranslationEditorState<TContext, TResult>;
|
|
142
165
|
//#endregion
|
|
143
|
-
|
|
166
|
+
//#region packages/editor/message-status.d.ts
|
|
167
|
+
type MessageTranslations = Readonly<Record<string, string | undefined>>;
|
|
168
|
+
/** Translated means a catalog entry exists for every selected locale. */
|
|
169
|
+
export declare function hasTranslationForEveryLocale(translations: MessageTranslations, locales: readonly string[]): boolean;
|
|
170
|
+
export declare function matchesMessageStatus(translations: MessageTranslations, locales: readonly string[], status: MessageStatus): boolean;
|
|
171
|
+
//#endregion
|
|
172
|
+
export type { EditorMessage, EditorOptions, EditorPreferenceStorage, EditorProps, EditorState, MessageProps, MessageStatus, MessageTranslations, ParsedMessage, SourceLocation, TranslatedMessage, TranslationDraftState, TranslationEditorOptions, TranslationEditorState, TranslationLayout, TranslationLayoutOptions, TranslationLayoutState, TranslationSaveResult, TranslationSaveSnapshot, TranslationUpdate, TranslationValidationError };
|
|
144
173
|
//# sourceMappingURL=index.d.ts.map
|
package/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
-
import { isLiteralElement, isPluralElement, isPoundElement, isSelectElement, isTagElement, parse } from "@formatjs/icu-messageformat-parser";
|
|
2
|
+
import { TYPE, isLiteralElement, isPluralElement, isPoundElement, isSelectElement, isTagElement, parse } from "@formatjs/icu-messageformat-parser";
|
|
3
3
|
//#region packages/editor/message.tsx
|
|
4
4
|
/** Retains every ICU branch and skeleton; incomplete edits are valid input. */
|
|
5
5
|
function parseMessage(message) {
|
|
@@ -15,6 +15,10 @@ function parseMessage(message) {
|
|
|
15
15
|
};
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
|
+
/** True only for valid ICU messages containing non-literal syntax. */
|
|
19
|
+
function hasMeaningfulIcuStructure(message) {
|
|
20
|
+
return parseMessage(message).ast?.some((element) => element.type !== TYPE.literal) ?? false;
|
|
21
|
+
}
|
|
18
22
|
function Message({ message, children }) {
|
|
19
23
|
return children(useMemo(() => parseMessage(message), [message]));
|
|
20
24
|
}
|
|
@@ -24,16 +28,24 @@ function Message({ message, children }) {
|
|
|
24
28
|
function useMessageEditor({ messages, onMessageChange, defaultSelectedId }) {
|
|
25
29
|
const [selectedId, selectMessage] = useState(defaultSelectedId);
|
|
26
30
|
const [query, setQuery] = useState("");
|
|
31
|
+
const [descriptionQuery, setDescriptionQuery] = useState("");
|
|
27
32
|
const selectedMessage = messages.find((message) => message.id === selectedId) ?? messages[0];
|
|
28
33
|
const visibleMessages = useMemo(() => {
|
|
29
34
|
const search = query.trim().toLowerCase();
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
const descriptionSearch = descriptionQuery.trim().toLowerCase();
|
|
36
|
+
return messages.filter((message) => {
|
|
37
|
+
return [
|
|
38
|
+
message.id,
|
|
39
|
+
message.defaultMessage,
|
|
40
|
+
message.translatedMessage,
|
|
41
|
+
message.description ?? ""
|
|
42
|
+
].some((value) => value.toLowerCase().includes(search)) && (!descriptionSearch || message.description?.toLowerCase().includes(descriptionSearch));
|
|
43
|
+
});
|
|
44
|
+
}, [
|
|
45
|
+
descriptionQuery,
|
|
46
|
+
messages,
|
|
47
|
+
query
|
|
48
|
+
]);
|
|
37
49
|
const sourceText = selectedMessage?.defaultMessage;
|
|
38
50
|
const translationText = selectedMessage?.translatedMessage;
|
|
39
51
|
const source = useMemo(() => sourceText === void 0 ? void 0 : parseMessage(sourceText), [sourceText]);
|
|
@@ -50,6 +62,8 @@ function useMessageEditor({ messages, onMessageChange, defaultSelectedId }) {
|
|
|
50
62
|
selectMessage,
|
|
51
63
|
query,
|
|
52
64
|
setQuery,
|
|
65
|
+
descriptionQuery,
|
|
66
|
+
setDescriptionQuery,
|
|
53
67
|
source,
|
|
54
68
|
translation,
|
|
55
69
|
setTranslation,
|
|
@@ -63,6 +77,49 @@ function Editor({ children, ...options }) {
|
|
|
63
77
|
return children(useMessageEditor(options));
|
|
64
78
|
}
|
|
65
79
|
//#endregion
|
|
80
|
+
//#region packages/editor/layout.ts
|
|
81
|
+
function browserStorage() {
|
|
82
|
+
try {
|
|
83
|
+
return globalThis.localStorage ?? null;
|
|
84
|
+
} catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function readLayout(storage, storageKey, fallback) {
|
|
89
|
+
try {
|
|
90
|
+
const stored = storage?.getItem(storageKey);
|
|
91
|
+
if (stored === "grid" || stored === "list") return stored;
|
|
92
|
+
const value = stored === null || stored === void 0 ? null : JSON.parse(stored);
|
|
93
|
+
return value === "grid" || value === "list" ? value : fallback;
|
|
94
|
+
} catch {
|
|
95
|
+
return fallback;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/** Layout preference state with guarded browser persistence and a grid default. */
|
|
99
|
+
function useTranslationLayout({ storageKey, defaultLayout = "grid", storage = browserStorage() }) {
|
|
100
|
+
const [layout, updateLayout] = useState(() => readLayout(storage, storageKey, defaultLayout));
|
|
101
|
+
return {
|
|
102
|
+
layout,
|
|
103
|
+
setLayout: (value) => {
|
|
104
|
+
updateLayout(value);
|
|
105
|
+
try {
|
|
106
|
+
storage?.setItem(storageKey, JSON.stringify(value));
|
|
107
|
+
} catch {}
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
//#endregion
|
|
112
|
+
//#region packages/editor/message-status.ts
|
|
113
|
+
/** Translated means a catalog entry exists for every selected locale. */
|
|
114
|
+
function hasTranslationForEveryLocale(translations, locales) {
|
|
115
|
+
return locales.length > 0 && locales.every((locale) => Object.prototype.hasOwnProperty.call(translations, locale) && translations[locale] !== void 0);
|
|
116
|
+
}
|
|
117
|
+
function matchesMessageStatus(translations, locales, status) {
|
|
118
|
+
if (status === "all") return true;
|
|
119
|
+
const translated = hasTranslationForEveryLocale(translations, locales);
|
|
120
|
+
return status === "translated" ? translated : !translated;
|
|
121
|
+
}
|
|
122
|
+
//#endregion
|
|
66
123
|
//#region packages/editor/validation.ts
|
|
67
124
|
function group(elements) {
|
|
68
125
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -165,8 +222,9 @@ function useTranslationEditor({ messages, locales, onSave, defaultLocale, pageSi
|
|
|
165
222
|
}
|
|
166
223
|
const editor = useMessageEditor({
|
|
167
224
|
messages: locale === void 0 ? [] : messages.filter((message) => {
|
|
168
|
-
const
|
|
169
|
-
|
|
225
|
+
const draft = getDraft(message.id);
|
|
226
|
+
const translations = Object.prototype.hasOwnProperty.call(message.translations, locale) && message.translations[locale] !== void 0 || draft.baseline ? { [locale]: draft.baseline } : {};
|
|
227
|
+
return (!catalog || message.catalogs?.includes(catalog)) && matchesMessageStatus(translations, [locale], status);
|
|
170
228
|
}).map((message) => ({
|
|
171
229
|
id: message.id,
|
|
172
230
|
defaultMessage: message.defaultMessage,
|
|
@@ -295,6 +353,10 @@ function useTranslationEditor({ messages, locales, onSave, defaultLocale, pageSi
|
|
|
295
353
|
setQuery: (query) => {
|
|
296
354
|
editor.setQuery(query);
|
|
297
355
|
requestPage(0);
|
|
356
|
+
},
|
|
357
|
+
setDescriptionQuery: (query) => {
|
|
358
|
+
editor.setDescriptionQuery(query);
|
|
359
|
+
requestPage(0);
|
|
298
360
|
}
|
|
299
361
|
},
|
|
300
362
|
selectedMessage,
|
|
@@ -332,6 +394,6 @@ function useTranslationEditor({ messages, locales, onSave, defaultLocale, pageSi
|
|
|
332
394
|
};
|
|
333
395
|
}
|
|
334
396
|
//#endregion
|
|
335
|
-
export { Editor, Message, parseMessage, useMessageEditor, useTranslationEditor, validateTranslation };
|
|
397
|
+
export { Editor, Message, hasMeaningfulIcuStructure, hasTranslationForEveryLocale, matchesMessageStatus, parseMessage, useMessageEditor, useTranslationEditor, useTranslationLayout, validateTranslation };
|
|
336
398
|
|
|
337
399
|
//# sourceMappingURL=index.js.map
|
package/index.js.map
CHANGED
|
@@ -1 +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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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../message.tsx","../core.tsx","../layout.ts","../message-status.ts","../validation.ts","../workflow.ts"],"sourcesContent":["import {\n parse,\n TYPE,\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\n/** True only for valid ICU messages containing non-literal syntax. */\nexport function hasMeaningfulIcuStructure(message: string): boolean {\n const parsed = parseMessage(message)\n return parsed.ast?.some(element => element.type !== TYPE.literal) ?? false\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 descriptionQuery: string\n setDescriptionQuery: (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 [descriptionQuery, setDescriptionQuery] = useState('')\n const selectedMessage =\n messages.find(message => message.id === selectedId) ?? messages[0]\n const visibleMessages = useMemo(() => {\n const search = query.trim().toLowerCase()\n const descriptionSearch = descriptionQuery.trim().toLowerCase()\n return messages.filter(message => {\n const matchesSearch = [\n message.id,\n message.defaultMessage,\n message.translatedMessage,\n message.description ?? '',\n ].some(value => value.toLowerCase().includes(search))\n return (\n matchesSearch &&\n (!descriptionSearch ||\n message.description?.toLowerCase().includes(descriptionSearch))\n )\n })\n }, [descriptionQuery, 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 descriptionQuery,\n setDescriptionQuery,\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 {useState} from 'react'\n\nexport type TranslationLayout = 'grid' | 'list'\n\nexport interface EditorPreferenceStorage {\n getItem(key: string): string | null\n setItem(key: string, value: string): void\n}\n\nexport interface TranslationLayoutOptions {\n storageKey: string\n defaultLayout?: TranslationLayout\n /** Defaults to browser localStorage when available. Pass null to disable persistence. */\n storage?: EditorPreferenceStorage | null\n}\n\nexport interface TranslationLayoutState {\n layout: TranslationLayout\n setLayout: (layout: TranslationLayout) => void\n}\n\nfunction browserStorage(): EditorPreferenceStorage | null {\n try {\n return globalThis.localStorage ?? null\n } catch {\n return null\n }\n}\n\nfunction readLayout(\n storage: EditorPreferenceStorage | null,\n storageKey: string,\n fallback: TranslationLayout\n): TranslationLayout {\n try {\n const stored = storage?.getItem(storageKey)\n if (stored === 'grid' || stored === 'list') return stored\n const value =\n stored === null || stored === undefined ? null : JSON.parse(stored)\n return value === 'grid' || value === 'list' ? value : fallback\n } catch {\n return fallback\n }\n}\n\n/** Layout preference state with guarded browser persistence and a grid default. */\nexport function useTranslationLayout({\n storageKey,\n defaultLayout = 'grid',\n storage = browserStorage(),\n}: TranslationLayoutOptions): TranslationLayoutState {\n const [layout, updateLayout] = useState(() =>\n readLayout(storage, storageKey, defaultLayout)\n )\n return {\n layout,\n setLayout: value => {\n updateLayout(value)\n try {\n storage?.setItem(storageKey, JSON.stringify(value))\n } catch {\n // Persistence is best-effort; the in-memory preference still applies.\n }\n },\n }\n}\n","import type {MessageStatus} from '#packages/editor/workflow.js'\n\nexport type MessageTranslations = Readonly<Record<string, string | undefined>>\n\n/** Translated means a catalog entry exists for every selected locale. */\nexport function hasTranslationForEveryLocale(\n translations: MessageTranslations,\n locales: readonly string[]\n): boolean {\n return (\n locales.length > 0 &&\n locales.every(\n locale =>\n Object.prototype.hasOwnProperty.call(translations, locale) &&\n translations[locale] !== undefined\n )\n )\n}\n\nexport function matchesMessageStatus(\n translations: MessageTranslations,\n locales: readonly string[],\n status: MessageStatus\n): boolean {\n if (status === 'all') return true\n const translated = hasTranslationForEveryLocale(translations, locales)\n return status === 'translated' ? translated : !translated\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'\nimport {matchesMessageStatus} from '#packages/editor/message-status.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 draft = getDraft(message.id)\n const translations =\n (Object.prototype.hasOwnProperty.call(\n message.translations,\n locale\n ) &&\n message.translations[locale] !== undefined) ||\n draft.baseline\n ? {[locale]: draft.baseline}\n : {}\n return (\n (!catalog || message.catalogs?.includes(catalog)) &&\n matchesMessageStatus(translations, [locale], status)\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 setDescriptionQuery: query => {\n editor.setDescriptionQuery(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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@formatjs/editor",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Headless ICU MessageFormat editor for React",
|
|
6
6
|
"keywords": [
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
}
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@formatjs/icu-messageformat-parser": "3.5.
|
|
44
|
+
"@formatjs/icu-messageformat-parser": "3.5.19"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"@types/react": "^19.0.0",
|
package/ui.d.ts
CHANGED
|
@@ -16,13 +16,19 @@ interface EditorInputProps {
|
|
|
16
16
|
disabled?: boolean;
|
|
17
17
|
"aria-invalid"?: boolean;
|
|
18
18
|
"aria-describedby"?: string;
|
|
19
|
+
"aria-label"?: string;
|
|
19
20
|
}
|
|
20
21
|
interface EditorTextInputProps extends EditorInputProps {
|
|
21
22
|
type: "text" | "search";
|
|
23
|
+
placeholder?: string;
|
|
22
24
|
}
|
|
23
25
|
interface EditorTextAreaProps extends EditorInputProps {
|
|
24
|
-
/**
|
|
26
|
+
/** Initial visible rows retained for compatibility. Prefer minRows. */
|
|
25
27
|
rows?: number;
|
|
28
|
+
/** Minimum visible rows while the controlled field grows and shrinks. */
|
|
29
|
+
minRows?: number;
|
|
30
|
+
/** Maximum visible rows before the controlled field scrolls. */
|
|
31
|
+
maxRows?: number;
|
|
26
32
|
}
|
|
27
33
|
interface EditorMessageRowProps {
|
|
28
34
|
children: ReactNode;
|
|
@@ -60,7 +66,7 @@ interface EditorCheckboxProps {
|
|
|
60
66
|
interface EditorLocalePickerLayoutProps {
|
|
61
67
|
id: string;
|
|
62
68
|
title: string;
|
|
63
|
-
summary:
|
|
69
|
+
summary: ReactNode;
|
|
64
70
|
triggerLabel: string;
|
|
65
71
|
open: boolean;
|
|
66
72
|
onOpenChange: (open: boolean) => void;
|
|
@@ -92,7 +98,6 @@ interface EditorToolComponents {
|
|
|
92
98
|
Metadata: ComponentType<EditorMetadataProps>;
|
|
93
99
|
}
|
|
94
100
|
type ResolvedEditorComponents = Required<EditorComponents>;
|
|
95
|
-
/** Unstyled native controls; no CSS, icons, or localization provider is required. */
|
|
96
101
|
export declare const nativeEditorComponents: ResolvedEditorComponents;
|
|
97
102
|
interface EditorDesignSystemProviderProps {
|
|
98
103
|
/** Overrides inherit unspecified components from the nearest provider. */
|
|
@@ -120,9 +125,11 @@ interface LocalePickerProps {
|
|
|
120
125
|
onChange: (locales: string[]) => void;
|
|
121
126
|
/** Defaults to the locale code; consumers choose the display language. */
|
|
122
127
|
getLocaleLabel?: (locale: string) => string;
|
|
128
|
+
/** Adds presentation without changing the text used for accessible names. */
|
|
129
|
+
renderLocaleLabel?: (locale: string, label: string) => ReactNode;
|
|
123
130
|
labels?: Partial<LocalePickerLabels>;
|
|
124
131
|
}
|
|
125
|
-
export declare function LocalePicker({ locales, selectedLocales, onChange, getLocaleLabel, labels }: LocalePickerProps): ReactNode;
|
|
132
|
+
export declare function LocalePicker({ locales, selectedLocales, onChange, getLocaleLabel, renderLocaleLabel, labels }: LocalePickerProps): ReactNode;
|
|
126
133
|
//#endregion
|
|
127
134
|
//#region packages/editor/message-preview.d.ts
|
|
128
135
|
interface MessagePreviewProps {
|
|
@@ -205,20 +212,25 @@ interface MessageContextLabels {
|
|
|
205
212
|
description: string;
|
|
206
213
|
catalogs: string;
|
|
207
214
|
locations: string;
|
|
215
|
+
catalogCopy: (catalog: string) => string;
|
|
216
|
+
locationCopy: (location: string) => string;
|
|
208
217
|
}
|
|
209
218
|
interface MessageContextProps {
|
|
210
219
|
message?: Pick<EditorMessage, "id" | "description" | "catalogs" | "locations"> | null;
|
|
211
220
|
labels?: Partial<MessageContextLabels>;
|
|
212
221
|
copyId?: boolean;
|
|
222
|
+
copyCatalogs?: boolean;
|
|
223
|
+
copyLocations?: boolean;
|
|
213
224
|
copyOptions?: Pick<CopyTextButtonProps, "writeText" | "onCopy" | "onError" | "labels" | "feedbackDurationMs">;
|
|
214
225
|
/** Default locations are plain text, never file URLs or HTML. */
|
|
215
|
-
renderLocation?: (location: SourceLocation) => ReactNode;
|
|
226
|
+
renderLocation?: (location: SourceLocation, text: string) => ReactNode;
|
|
216
227
|
}
|
|
217
|
-
export declare function MessageContext({ message, labels, copyId, copyOptions, renderLocation }: MessageContextProps): ReactNode;
|
|
228
|
+
export declare function MessageContext({ message, labels, copyId, copyCatalogs, copyLocations, copyOptions, renderLocation }: MessageContextProps): ReactNode;
|
|
218
229
|
//#endregion
|
|
219
230
|
//#region packages/editor/ui.d.ts
|
|
220
231
|
export interface EditorLabels {
|
|
221
232
|
search: string;
|
|
233
|
+
descriptionSearch: string;
|
|
222
234
|
messages: string;
|
|
223
235
|
source: string;
|
|
224
236
|
noMessages: string;
|
|
@@ -252,6 +264,8 @@ export interface MessageListProps<Message extends EditorViewMessage = EditorView
|
|
|
252
264
|
selectedId?: string;
|
|
253
265
|
onSelect: (id: string) => void;
|
|
254
266
|
search?: EditorSearch;
|
|
267
|
+
/** Independent description-only search, conjunctive with general search. */
|
|
268
|
+
descriptionSearch?: EditorSearch;
|
|
255
269
|
loading?: boolean;
|
|
256
270
|
pagination?: ReactNode;
|
|
257
271
|
/** Summary or controls between search and the loaded rows. */
|
|
@@ -262,7 +276,7 @@ export interface MessageListProps<Message extends EditorViewMessage = EditorView
|
|
|
262
276
|
renderMessageActions?: (message: Message, state: EditorMessageRenderState) => ReactNode;
|
|
263
277
|
}
|
|
264
278
|
/** Controlled list: it never filters, paginates, fetches, or changes selection itself. */
|
|
265
|
-
export declare function MessageList<Message extends EditorViewMessage = EditorViewMessage>({ messages, selectedId, onSelect, search, loading, pagination, listSummary, renderMessage, renderMessageActions, labels }: MessageListProps<Message>): ReactNode;
|
|
279
|
+
export declare function MessageList<Message extends EditorViewMessage = EditorViewMessage>({ messages, selectedId, onSelect, search, descriptionSearch, loading, pagination, listSummary, renderMessage, renderMessageActions, labels }: MessageListProps<Message>): ReactNode;
|
|
266
280
|
export interface SourceMessageProps extends ViewOptions {
|
|
267
281
|
message: EditorViewMessage;
|
|
268
282
|
preview?: ReactNode;
|
|
@@ -281,8 +295,10 @@ export interface TranslationFieldProps extends ViewOptions {
|
|
|
281
295
|
/** Overrides the default copy/reset/save actions, including with null. */
|
|
282
296
|
actions?: ReactNode;
|
|
283
297
|
preview?: ReactNode;
|
|
298
|
+
minRows?: number;
|
|
299
|
+
maxRows?: number;
|
|
284
300
|
}
|
|
285
|
-
export declare function TranslationField({ locale, label, source, draft, onSave, actions, preview, labels }: TranslationFieldProps): ReactNode;
|
|
301
|
+
export declare function TranslationField({ locale, label, source, draft, onSave, actions, preview, minRows, maxRows, labels }: TranslationFieldProps): ReactNode;
|
|
286
302
|
export type EditorTranslation = Omit<TranslationFieldProps, "source">;
|
|
287
303
|
export interface TranslationEditorViewProps<Message extends EditorViewMessage = EditorViewMessage> extends MessageListProps<Message> {
|
|
288
304
|
/** May be outside the loaded list, e.g. during a server-side page change. */
|
package/ui.js
CHANGED
|
@@ -1,8 +1,56 @@
|
|
|
1
|
-
import { createContext, createElement, useContext, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
1
|
+
import { createContext, createElement, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { TYPE, parse } from "@formatjs/icu-messageformat-parser";
|
|
4
4
|
//#region packages/editor/design-system.tsx
|
|
5
5
|
/** Unstyled native controls; no CSS, icons, or localization provider is required. */
|
|
6
|
+
function finiteRows(value, fallback) {
|
|
7
|
+
return Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
|
|
8
|
+
}
|
|
9
|
+
function NativeTextArea({ onValueChange, value, rows, minRows, maxRows, ...props }) {
|
|
10
|
+
const ref = useRef(null);
|
|
11
|
+
const minimum = finiteRows(minRows ?? rows, 1);
|
|
12
|
+
const maximum = Math.max(minimum, finiteRows(maxRows, 10));
|
|
13
|
+
useLayoutEffect(() => {
|
|
14
|
+
const field = ref.current;
|
|
15
|
+
if (!field) return;
|
|
16
|
+
const resize = () => {
|
|
17
|
+
const computed = getComputedStyle(field);
|
|
18
|
+
const parsedLineHeight = Number.parseFloat(computed.lineHeight);
|
|
19
|
+
const fontSize = Number.parseFloat(computed.fontSize);
|
|
20
|
+
const lineHeight = Number.isFinite(parsedLineHeight) ? parsedLineHeight : fontSize * 1.2;
|
|
21
|
+
if (!Number.isFinite(lineHeight)) return;
|
|
22
|
+
const pixels = (value) => Number.parseFloat(value) || 0;
|
|
23
|
+
const chrome = pixels(computed.paddingTop) + pixels(computed.paddingBottom) + pixels(computed.borderTopWidth) + pixels(computed.borderBottomWidth);
|
|
24
|
+
const minimumHeight = minimum * lineHeight + chrome;
|
|
25
|
+
const maximumHeight = maximum * lineHeight + chrome;
|
|
26
|
+
field.style.height = "auto";
|
|
27
|
+
field.style.height = `${Math.min(Math.max(field.scrollHeight, minimumHeight), maximumHeight)}px`;
|
|
28
|
+
field.style.overflowY = field.scrollHeight > maximumHeight ? "auto" : "hidden";
|
|
29
|
+
};
|
|
30
|
+
resize();
|
|
31
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
32
|
+
let width = field.getBoundingClientRect().width;
|
|
33
|
+
const observer = new ResizeObserver(() => {
|
|
34
|
+
const nextWidth = field.getBoundingClientRect().width;
|
|
35
|
+
if (nextWidth === width) return;
|
|
36
|
+
width = nextWidth;
|
|
37
|
+
resize();
|
|
38
|
+
});
|
|
39
|
+
observer.observe(field);
|
|
40
|
+
return () => observer.disconnect();
|
|
41
|
+
}, [
|
|
42
|
+
maximum,
|
|
43
|
+
minimum,
|
|
44
|
+
value
|
|
45
|
+
]);
|
|
46
|
+
return /* @__PURE__ */ jsx("textarea", {
|
|
47
|
+
...props,
|
|
48
|
+
ref,
|
|
49
|
+
rows: minimum,
|
|
50
|
+
value,
|
|
51
|
+
onChange: (event) => onValueChange(event.target.value)
|
|
52
|
+
});
|
|
53
|
+
}
|
|
6
54
|
const nativeEditorComponents = {
|
|
7
55
|
Checkbox: function NativeCheckbox({ checked, onCheckedChange, ...props }) {
|
|
8
56
|
const ref = useRef(null);
|
|
@@ -63,11 +111,7 @@ const nativeEditorComponents = {
|
|
|
63
111
|
...props,
|
|
64
112
|
onChange: (event) => onValueChange(event.target.value)
|
|
65
113
|
}),
|
|
66
|
-
TextArea:
|
|
67
|
-
...props,
|
|
68
|
-
rows,
|
|
69
|
-
onChange: (event) => onValueChange(event.target.value)
|
|
70
|
-
}),
|
|
114
|
+
TextArea: NativeTextArea,
|
|
71
115
|
MessageRow: ({ children, selected, onSelect }) => /* @__PURE__ */ jsx("button", {
|
|
72
116
|
type: "button",
|
|
73
117
|
"aria-current": selected ? "true" : void 0,
|
|
@@ -112,7 +156,7 @@ const defaults$2 = {
|
|
|
112
156
|
trigger: (summary) => `Locales: ${summary}`
|
|
113
157
|
};
|
|
114
158
|
const localeCode = (locale) => locale;
|
|
115
|
-
function LocalePicker({ locales, selectedLocales, onChange, getLocaleLabel = localeCode, labels }) {
|
|
159
|
+
function LocalePicker({ locales, selectedLocales, onChange, getLocaleLabel = localeCode, renderLocaleLabel = (_locale, label) => label, labels }) {
|
|
116
160
|
const { Checkbox, Button, LocalePickerLayout } = useEditorDesignSystem();
|
|
117
161
|
const id = useId();
|
|
118
162
|
const [open, setOpen] = useState(false);
|
|
@@ -123,7 +167,8 @@ function LocalePicker({ locales, selectedLocales, onChange, getLocaleLabel = loc
|
|
|
123
167
|
...defaults$2,
|
|
124
168
|
...labels
|
|
125
169
|
};
|
|
126
|
-
const
|
|
170
|
+
const summaryText = active.length === 0 ? text.empty : active.length === 1 ? getLocaleLabel(active[0]) : text.selected(active.length);
|
|
171
|
+
const summary = active.length === 1 ? renderLocaleLabel(active[0], summaryText) : summaryText;
|
|
127
172
|
const all = available.length > 0 && active.length === available.length;
|
|
128
173
|
const midpoint = Math.ceil(available.length / 2);
|
|
129
174
|
const columns = [available.slice(0, midpoint), available.slice(midpoint)];
|
|
@@ -131,7 +176,7 @@ function LocalePicker({ locales, selectedLocales, onChange, getLocaleLabel = loc
|
|
|
131
176
|
id,
|
|
132
177
|
title: text.title,
|
|
133
178
|
summary,
|
|
134
|
-
triggerLabel: text.trigger(
|
|
179
|
+
triggerLabel: text.trigger(summaryText),
|
|
135
180
|
open,
|
|
136
181
|
onOpenChange: setOpen,
|
|
137
182
|
controls: /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("label", {
|
|
@@ -161,7 +206,7 @@ function LocalePicker({ locales, selectedLocales, onChange, getLocaleLabel = loc
|
|
|
161
206
|
id: checkboxId,
|
|
162
207
|
checked: selected.has(locale),
|
|
163
208
|
onCheckedChange: (checked) => onChange(available.filter((candidate) => candidate === locale ? checked : selected.has(candidate)))
|
|
164
|
-
}), getLocaleLabel(locale)]
|
|
209
|
+
}), renderLocaleLabel(locale, getLocaleLabel(locale))]
|
|
165
210
|
}, locale);
|
|
166
211
|
}))
|
|
167
212
|
});
|
|
@@ -316,14 +361,16 @@ const defaults = {
|
|
|
316
361
|
id: "Message ID",
|
|
317
362
|
description: "Description",
|
|
318
363
|
catalogs: "Source catalogs",
|
|
319
|
-
locations: "Source locations"
|
|
364
|
+
locations: "Source locations",
|
|
365
|
+
catalogCopy: (catalog) => `source catalog ${catalog}`,
|
|
366
|
+
locationCopy: (location) => `source location ${location}`
|
|
320
367
|
};
|
|
321
368
|
function locationText(location) {
|
|
322
369
|
const start = location.start === void 0 ? "" : `:${location.start}`;
|
|
323
370
|
const end = location.end === void 0 ? "" : `${location.start === void 0 ? ":" : "–"}${location.end}`;
|
|
324
371
|
return `${location.file}${start}${end}`;
|
|
325
372
|
}
|
|
326
|
-
function MessageContext({ message, labels, copyId = true, copyOptions, renderLocation = locationText }) {
|
|
373
|
+
function MessageContext({ message, labels, copyId = true, copyCatalogs = false, copyLocations = false, copyOptions, renderLocation = locationText }) {
|
|
327
374
|
const { Metadata } = useEditorDesignSystem();
|
|
328
375
|
const text = {
|
|
329
376
|
...defaults,
|
|
@@ -338,8 +385,19 @@ function MessageContext({ message, labels, copyId = true, copyOptions, renderLoc
|
|
|
338
385
|
label: text.id
|
|
339
386
|
})] })] }),
|
|
340
387
|
message.description && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: text.description }), /* @__PURE__ */ jsx("dd", { children: message.description })] }),
|
|
341
|
-
!!message.catalogs?.length && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: text.catalogs }), /* @__PURE__ */ jsx("dd", { children: /* @__PURE__ */ jsx("ul", { children: message.catalogs.map((catalog, index) => /* @__PURE__ */
|
|
342
|
-
|
|
388
|
+
!!message.catalogs?.length && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: text.catalogs }), /* @__PURE__ */ jsx("dd", { children: /* @__PURE__ */ jsx("ul", { children: message.catalogs.map((catalog, index) => /* @__PURE__ */ jsxs("li", { children: [/* @__PURE__ */ jsx("span", { children: catalog }), copyCatalogs && /* @__PURE__ */ jsx(CopyTextButton, {
|
|
389
|
+
...copyOptions,
|
|
390
|
+
value: catalog,
|
|
391
|
+
label: text.catalogCopy(catalog)
|
|
392
|
+
})] }, `${catalog}:${index}`)) }) })] }),
|
|
393
|
+
!!message.locations?.length && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: text.locations }), /* @__PURE__ */ jsx("dd", { children: /* @__PURE__ */ jsx("ul", { children: message.locations.map((location, index) => {
|
|
394
|
+
const value = locationText(location);
|
|
395
|
+
return /* @__PURE__ */ jsxs("li", { children: [/* @__PURE__ */ jsx("span", { children: renderLocation(location, value) }), copyLocations && /* @__PURE__ */ jsx(CopyTextButton, {
|
|
396
|
+
...copyOptions,
|
|
397
|
+
value,
|
|
398
|
+
label: text.locationCopy(value)
|
|
399
|
+
})] }, `${value}:${index}`);
|
|
400
|
+
}) }) })] })
|
|
343
401
|
] })
|
|
344
402
|
});
|
|
345
403
|
}
|
|
@@ -347,6 +405,7 @@ function MessageContext({ message, labels, copyId = true, copyOptions, renderLoc
|
|
|
347
405
|
//#region packages/editor/ui.tsx
|
|
348
406
|
const defaultLabels = {
|
|
349
407
|
search: "Search messages",
|
|
408
|
+
descriptionSearch: "Search descriptions",
|
|
350
409
|
messages: "Messages",
|
|
351
410
|
source: "Source message",
|
|
352
411
|
noMessages: "No matching messages",
|
|
@@ -377,10 +436,11 @@ function resolveLabels(labels) {
|
|
|
377
436
|
};
|
|
378
437
|
}
|
|
379
438
|
/** Controlled list: it never filters, paginates, fetches, or changes selection itself. */
|
|
380
|
-
function MessageList({ messages, selectedId, onSelect, search, loading = false, pagination, listSummary, renderMessage, renderMessageActions, labels }) {
|
|
439
|
+
function MessageList({ messages, selectedId, onSelect, search, descriptionSearch, loading = false, pagination, listSummary, renderMessage, renderMessageActions, labels }) {
|
|
381
440
|
const { TextInput, MessageRow } = useEditorDesignSystem();
|
|
382
441
|
const text = resolveLabels(labels);
|
|
383
442
|
const searchId = useId();
|
|
443
|
+
const descriptionSearchId = useId();
|
|
384
444
|
return /* @__PURE__ */ jsxs("nav", {
|
|
385
445
|
"aria-label": text.messages,
|
|
386
446
|
"aria-busy": loading,
|
|
@@ -392,7 +452,20 @@ function MessageList({ messages, selectedId, onSelect, search, loading = false,
|
|
|
392
452
|
id: searchId,
|
|
393
453
|
type: "search",
|
|
394
454
|
value: search.value,
|
|
395
|
-
onValueChange: search.onValueChange
|
|
455
|
+
onValueChange: search.onValueChange,
|
|
456
|
+
"aria-label": text.search,
|
|
457
|
+
placeholder: text.search
|
|
458
|
+
})] }),
|
|
459
|
+
descriptionSearch && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
|
|
460
|
+
htmlFor: descriptionSearchId,
|
|
461
|
+
children: text.descriptionSearch
|
|
462
|
+
}), /* @__PURE__ */ jsx(TextInput, {
|
|
463
|
+
id: descriptionSearchId,
|
|
464
|
+
type: "search",
|
|
465
|
+
value: descriptionSearch.value,
|
|
466
|
+
onValueChange: descriptionSearch.onValueChange,
|
|
467
|
+
"aria-label": text.descriptionSearch,
|
|
468
|
+
placeholder: text.descriptionSearch
|
|
396
469
|
})] }),
|
|
397
470
|
listSummary,
|
|
398
471
|
loading && /* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsx("output", { children: text.loading }) }),
|
|
@@ -428,7 +501,7 @@ function SourceMessage({ message, preview, context, labels }) {
|
|
|
428
501
|
]
|
|
429
502
|
});
|
|
430
503
|
}
|
|
431
|
-
function TranslationField({ locale, label = locale, source, draft, onSave, actions, preview, labels }) {
|
|
504
|
+
function TranslationField({ locale, label = locale, source, draft, onSave, actions, preview, minRows = 1, maxRows = 10, labels }) {
|
|
432
505
|
const { Panel, TextArea, Button } = useEditorDesignSystem();
|
|
433
506
|
const text = resolveLabels(labels);
|
|
434
507
|
const id = useId();
|
|
@@ -449,7 +522,9 @@ function TranslationField({ locale, label = locale, source, draft, onSave, actio
|
|
|
449
522
|
value: draft.value,
|
|
450
523
|
onValueChange: draft.setTranslation,
|
|
451
524
|
"aria-invalid": !!validation,
|
|
452
|
-
"aria-describedby": error ? `${errorId} ${statusId}` : statusId
|
|
525
|
+
"aria-describedby": error ? `${errorId} ${statusId}` : statusId,
|
|
526
|
+
minRows,
|
|
527
|
+
maxRows
|
|
453
528
|
}),
|
|
454
529
|
error && /* @__PURE__ */ jsx("p", {
|
|
455
530
|
id: errorId,
|
package/ui.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui.js","names":["defaults","defaults"],"sources":["../design-system.tsx","../locale-picker.tsx","../message-preview.tsx","../copy-text-button.tsx","../message-context.tsx","../ui.tsx"],"sourcesContent":["import {\n createContext,\n useContext,\n useEffect,\n useMemo,\n useRef,\n type ComponentType,\n type ReactNode,\n} from 'react'\nexport interface EditorButtonProps {\n children: ReactNode\n /** Called once per activation, without a DOM event; disabled controls must not call it. */\n onPress: () => void\n disabled?: boolean\n variant: 'primary' | 'secondary'\n}\nexport interface EditorInputProps {\n id: string\n value: string\n /** Reports the complete next string value; never a DOM event. */\n onValueChange: (value: string) => void\n disabled?: boolean\n 'aria-invalid'?: boolean\n 'aria-describedby'?: string\n}\nexport interface EditorTextInputProps extends EditorInputProps {\n type: 'text' | 'search'\n}\nexport interface EditorTextAreaProps extends EditorInputProps {\n /** Visible rows when supported by the control; defaults to six. */\n rows?: number\n}\nexport interface EditorMessageRowProps {\n children: ReactNode\n selected: boolean\n /** Activates this row without changing controlled selection itself. */\n onSelect: () => void\n}\nexport interface EditorPanelProps {\n children: ReactNode\n label: string\n kind: 'source' | 'translation'\n}\nexport interface EditorLayoutProps {\n toolbar: ReactNode\n navigation: ReactNode\n content: ReactNode\n /** Optional secondary content, arranged by the design-system layout. */\n sidebar?: ReactNode\n}\n/** Define adapters outside render so controls retain focus across edits. */\nexport interface EditorComponents extends Partial<EditorToolComponents> {\n Button: ComponentType<EditorButtonProps>\n TextInput: ComponentType<EditorTextInputProps>\n TextArea: ComponentType<EditorTextAreaProps>\n MessageRow: ComponentType<EditorMessageRowProps>\n Panel: ComponentType<EditorPanelProps>\n Layout: ComponentType<EditorLayoutProps>\n}\nexport interface EditorCheckboxProps {\n id: string\n checked: boolean | 'indeterminate'\n disabled?: boolean\n onCheckedChange: (checked: boolean) => void\n}\nexport interface EditorLocalePickerLayoutProps {\n id: string\n title: string\n summary: string\n triggerLabel: string\n open: boolean\n onOpenChange: (open: boolean) => void\n controls: ReactNode\n /** Contiguous, balanced columns in the caller's locale order. */\n columns: readonly ReactNode[]\n}\nexport interface EditorPreviewTokenProps {\n children: ReactNode\n kind:\n | 'argument'\n | 'number'\n | 'date'\n | 'time'\n | 'tag'\n | 'selector'\n | 'plural'\n | 'pound'\n | 'syntax'\n}\nexport type EditorCopyStatus = 'idle' | 'copying' | 'copied' | 'error'\nexport interface EditorCopyButtonProps {\n label: string\n status: EditorCopyStatus\n disabled: boolean\n onPress: () => void\n}\nexport interface EditorMetadataProps {\n label: string\n children: ReactNode\n}\n/** Optional additions preserve existing complete design-system registries. */\nexport interface EditorToolComponents {\n Checkbox: ComponentType<EditorCheckboxProps>\n LocalePickerLayout: ComponentType<EditorLocalePickerLayoutProps>\n PreviewToken: ComponentType<EditorPreviewTokenProps>\n CopyButton: ComponentType<EditorCopyButtonProps>\n Metadata: ComponentType<EditorMetadataProps>\n}\nexport type ResolvedEditorComponents = Required<EditorComponents>\n/** Unstyled native controls; no CSS, icons, or localization provider is required. */\nexport const nativeEditorComponents: ResolvedEditorComponents = {\n Checkbox: function NativeCheckbox({checked, onCheckedChange, ...props}) {\n const ref = useRef<HTMLInputElement>(null)\n useEffect(() => {\n if (ref.current) ref.current.indeterminate = checked === 'indeterminate'\n }, [checked])\n return (\n <input\n {...props}\n ref={ref}\n type=\"checkbox\"\n checked={checked === true}\n aria-checked={checked === 'indeterminate' ? 'mixed' : checked}\n onChange={event => onCheckedChange(event.target.checked)}\n />\n )\n },\n LocalePickerLayout: ({\n id,\n title,\n summary,\n triggerLabel,\n open,\n onOpenChange,\n controls,\n columns,\n }) => (\n <div>\n <button\n type=\"button\"\n aria-label={triggerLabel}\n aria-expanded={open}\n aria-controls={id}\n onClick={() => onOpenChange(!open)}\n >\n {summary}\n </button>\n <fieldset id={id} hidden={!open}>\n <legend>{title}</legend>\n {controls}\n <div\n style={{\n display: 'grid',\n gridTemplateColumns:\n 'repeat(auto-fit, minmax(min(100%, 16rem), 1fr))',\n maxHeight: '60vh',\n overflowY: 'auto',\n }}\n >\n {columns.map((column, index) => (\n <div key={index}>{column}</div>\n ))}\n </div>\n </fieldset>\n </div>\n ),\n PreviewToken: ({children}) => <code>{children}</code>,\n CopyButton: ({label, onPress, disabled}) => (\n <button type=\"button\" disabled={disabled} onClick={() => onPress()}>\n {label}\n </button>\n ),\n Metadata: ({label, children}) => <aside aria-label={label}>{children}</aside>,\n Button: ({children, onPress, disabled}) => (\n <button type=\"button\" disabled={disabled} onClick={() => onPress()}>\n {children}\n </button>\n ),\n TextInput: ({onValueChange, ...props}) => (\n <input {...props} onChange={event => onValueChange(event.target.value)} />\n ),\n TextArea: ({onValueChange, rows = 6, ...props}) => (\n <textarea\n {...props}\n rows={rows}\n onChange={event => onValueChange(event.target.value)}\n />\n ),\n MessageRow: ({children, selected, onSelect}) => (\n <button\n type=\"button\"\n aria-current={selected ? 'true' : undefined}\n onClick={() => onSelect()}\n >\n {children}\n </button>\n ),\n Panel: ({children, label}) => (\n <section aria-label={label}>{children}</section>\n ),\n Layout: ({toolbar, navigation, content, sidebar}) => (\n <div>\n {toolbar}\n <div>\n {navigation}\n {content}\n {sidebar}\n </div>\n </div>\n ),\n}\nconst EditorDesignSystemContext = createContext<\n Readonly<ResolvedEditorComponents>\n>(nativeEditorComponents)\n\nexport interface EditorDesignSystemProviderProps {\n /** Overrides inherit unspecified components from the nearest provider. */\n components: Partial<EditorComponents>\n children: ReactNode\n}\n/** Configure a tree once; sibling providers remain independent. */\nexport function EditorDesignSystemProvider({\n components,\n children,\n}: EditorDesignSystemProviderProps): ReactNode {\n const parent = useEditorDesignSystem()\n const value = useMemo(\n () => ({\n ...parent,\n ...Object.fromEntries(\n Object.entries(components).filter(([, value]) => value !== undefined)\n ),\n }),\n [parent, components]\n )\n return (\n <EditorDesignSystemContext value={value}>\n {children}\n </EditorDesignSystemContext>\n )\n}\n/** Read the resolved controls, including native defaults outside a provider. */\nexport function useEditorDesignSystem(): Readonly<ResolvedEditorComponents> {\n return useContext(EditorDesignSystemContext)\n}\n","import {useId, useState, type ReactNode} from 'react'\nimport {useEditorDesignSystem} from '#packages/editor/design-system.js'\n\nexport interface LocalePickerLabels {\n title: string\n empty: string\n clear: string\n selectAll: (count: number) => string\n selected: (count: number) => string\n trigger: (summary: string) => string\n}\nexport interface LocalePickerProps {\n locales: readonly string[]\n selectedLocales: readonly string[]\n /** Reports unique available locales in their input order; never mutates selection. */\n onChange: (locales: string[]) => void\n /** Defaults to the locale code; consumers choose the display language. */\n getLocaleLabel?: (locale: string) => string\n labels?: Partial<LocalePickerLabels>\n}\nconst defaults: LocalePickerLabels = {\n title: 'Locales',\n empty: 'No locales selected',\n clear: 'Clear',\n selectAll: count =>\n `Select all ${count} ${count === 1 ? 'locale' : 'locales'}`,\n selected: count => `${count} locales selected`,\n trigger: summary => `Locales: ${summary}`,\n}\nconst localeCode = (locale: string): string => locale\n\nexport function LocalePicker({\n locales,\n selectedLocales,\n onChange,\n getLocaleLabel = localeCode,\n labels,\n}: LocalePickerProps): ReactNode {\n const {Checkbox, Button, LocalePickerLayout} = useEditorDesignSystem()\n const id = useId()\n const [open, setOpen] = useState(false)\n const available = [...new Set(locales)]\n const selected = new Set(selectedLocales)\n const active = available.filter(locale => selected.has(locale))\n const text = {...defaults, ...labels}\n const summary =\n active.length === 0\n ? text.empty\n : active.length === 1\n ? getLocaleLabel(active[0]!)\n : text.selected(active.length)\n const all = available.length > 0 && active.length === available.length\n const midpoint = Math.ceil(available.length / 2)\n const columns = [available.slice(0, midpoint), available.slice(midpoint)]\n return (\n <LocalePickerLayout\n id={id}\n title={text.title}\n summary={summary}\n triggerLabel={text.trigger(summary)}\n open={open}\n onOpenChange={setOpen}\n controls={\n <div>\n <label htmlFor={`${id}-all`}>\n <Checkbox\n id={`${id}-all`}\n disabled={available.length === 0}\n checked={all ? true : active.length ? 'indeterminate' : false}\n onCheckedChange={checked => onChange(checked ? available : [])}\n />\n {text.selectAll(available.length)}\n </label>\n <Button\n variant=\"secondary\"\n disabled={active.length === 0}\n onPress={() => onChange([])}\n >\n {text.clear}\n </Button>\n </div>\n }\n columns={columns.map((column, columnIndex) =>\n column.map((locale, index) => {\n const checkboxId = `${id}-${columnIndex}-${index}`\n return (\n <label\n key={locale}\n htmlFor={checkboxId}\n style={{display: 'flex', alignItems: 'center', gap: 8}}\n >\n <Checkbox\n id={checkboxId}\n checked={selected.has(locale)}\n onCheckedChange={checked =>\n onChange(\n available.filter(candidate =>\n candidate === locale ? checked : selected.has(candidate)\n )\n )\n }\n />\n {getLocaleLabel(locale)}\n </label>\n )\n })\n )}\n />\n )\n}\n","import {\n parse,\n TYPE,\n type MessageFormatElement,\n} from '@formatjs/icu-messageformat-parser'\nimport {useMemo, type ReactNode} from 'react'\nimport {\n useEditorDesignSystem,\n type EditorPreviewTokenProps,\n} from '#packages/editor/design-system.js'\n\nexport interface MessagePreviewProps {\n message: string\n /** Customize a parser error without suppressing the alert semantics. */\n formatError?: (error: Error) => ReactNode\n}\n\n/** Structural ICU preview: shows every branch, without evaluating values or HTML. */\nexport function MessagePreview({\n message,\n formatError,\n}: MessagePreviewProps): ReactNode {\n const {PreviewToken} = useEditorDesignSystem()\n const parsed = useMemo(() => {\n try {\n return {ast: parse(message, {captureLocation: true}), error: null}\n } catch (error) {\n return {\n ast: null,\n error: error instanceof Error ? error : new Error(String(error)),\n }\n }\n }, [message])\n if (parsed.error)\n return (\n <p role=\"alert\">\n {formatError ? formatError(parsed.error) : parsed.error.message}\n </p>\n )\n const token = (\n text: string,\n kind: EditorPreviewTokenProps['kind'],\n key: string\n ): ReactNode => (\n <PreviewToken key={key} kind={kind}>\n <bdi dir=\"ltr\">{text}</bdi>\n </PreviewToken>\n )\n const elements = (ast: MessageFormatElement[], prefix: string): ReactNode[] =>\n ast.flatMap((element, index) => {\n const key = `${prefix}-${index}`\n switch (element.type) {\n case TYPE.literal:\n return <span key={key}>{element.value}</span>\n case TYPE.pound:\n return token('#', 'pound', key)\n case TYPE.argument:\n case TYPE.number:\n case TYPE.date:\n case TYPE.time:\n return token(\n message.slice(\n element.location!.start.offset,\n element.location!.end.offset\n ),\n TYPE[element.type] as 'argument' | 'number' | 'date' | 'time',\n key\n )\n case TYPE.tag:\n return [\n token(`<${element.value}>`, 'tag', `${key}-open`),\n ...elements(element.children, `${key}-children`),\n token(`</${element.value}>`, 'tag', `${key}-close`),\n ]\n case TYPE.select:\n case TYPE.plural: {\n const kind =\n element.type === TYPE.select\n ? 'select'\n : element.pluralType === 'ordinal'\n ? 'selectordinal'\n : 'plural'\n const offset =\n element.type === TYPE.plural && element.offset\n ? ` offset:${element.offset}`\n : ''\n return [\n token(\n `{${element.value}, ${kind},${offset}`,\n element.type === TYPE.select ? 'selector' : 'plural',\n `${key}-open`\n ),\n ...Object.entries(element.options).flatMap(([selector, option]) => [\n token(`${selector} {`, 'selector', `${key}-${selector}`),\n ...elements(option.value, `${key}-${selector}`),\n token('}', 'syntax', `${key}-${selector}-close`),\n ]),\n token('}', 'syntax', `${key}-close`),\n ]\n }\n }\n })\n return (\n <div dir=\"auto\" style={{whiteSpace: 'pre-wrap', overflowWrap: 'anywhere'}}>\n {elements(parsed.ast!, 'message')}\n </div>\n )\n}\n","import {useEffect, useRef, useState, type ReactNode} from 'react'\nimport {\n useEditorDesignSystem,\n type EditorCopyStatus,\n} from '#packages/editor/design-system.js'\n\nexport interface CopyTextLabels {\n copy: (label: string) => string\n copying: (label: string) => string\n copied: (label: string) => string\n failed: (label: string) => string\n}\nexport interface CopyTextButtonProps {\n value: string\n label: string\n disabled?: boolean\n /** Injectable clipboard boundary; rejects when the write fails. */\n writeText?: (value: string) => Promise<void>\n onCopy?: (value: string) => void\n onError?: (error: Error, value: string) => void\n feedbackDurationMs?: number\n labels?: Partial<CopyTextLabels>\n}\nconst defaults: CopyTextLabels = {\n copy: label => `Copy ${label}`,\n copying: label => `Copying ${label}…`,\n copied: label => `Copied ${label}`,\n failed: label => `Could not copy ${label}`,\n}\nasync function writeClipboard(value: string): Promise<void> {\n if (typeof navigator === 'undefined' || !navigator.clipboard?.writeText) {\n throw new Error('Clipboard access is unavailable')\n }\n await navigator.clipboard.writeText(value)\n}\n\n/** Copies exact text; feedback belongs to the current value and mounted control. */\nexport function CopyTextButton({\n value,\n label,\n disabled = false,\n writeText = writeClipboard,\n onCopy,\n onError,\n feedbackDurationMs = 1500,\n labels,\n}: CopyTextButtonProps): ReactNode {\n const {CopyButton} = useEditorDesignSystem()\n const [status, setStatus] = useState<EditorCopyStatus>('idle')\n const operation = useRef({\n generation: 0,\n pending: false,\n timer: undefined as ReturnType<typeof setTimeout> | undefined,\n })\n useEffect(() => {\n const current = operation.current\n current.generation++\n current.pending = false\n setStatus('idle')\n clearTimeout(current.timer)\n return () => {\n current.generation++\n clearTimeout(current.timer)\n }\n }, [value, writeText])\n const copy = async (): Promise<void> => {\n const current = operation.current\n if (disabled || current.pending) return\n current.pending = true\n const request = ++current.generation\n clearTimeout(current.timer)\n setStatus('copying')\n let error: Error | undefined\n try {\n await writeText(value)\n } catch (failure) {\n error = failure instanceof Error ? failure : new Error(String(failure))\n }\n if (request !== current.generation) return\n current.pending = false\n setStatus(error ? 'error' : 'copied')\n current.timer = setTimeout(\n () => {\n if (request === current.generation) setStatus('idle')\n },\n Math.max(0, feedbackDurationMs)\n )\n if (error) onError?.(error, value)\n else onCopy?.(value)\n }\n const text = {...defaults, ...labels}\n const buttonLabel = (\n status === 'copying'\n ? text.copying\n : status === 'copied'\n ? text.copied\n : status === 'error'\n ? text.failed\n : text.copy\n )(label)\n return (\n <span>\n <CopyButton\n label={buttonLabel}\n status={status}\n disabled={disabled || status === 'copying'}\n onPress={() => {\n void copy()\n }}\n />\n {status === 'copied' && <output>{text.copied(label)}</output>}\n {status === 'error' && <span role=\"alert\">{text.failed(label)}</span>}\n </span>\n )\n}\n","import type {ReactNode} from 'react'\nimport {useEditorDesignSystem} from '#packages/editor/design-system.js'\nimport {\n CopyTextButton,\n type CopyTextButtonProps,\n} from '#packages/editor/copy-text-button.js'\nimport type {EditorMessage, SourceLocation} from '#packages/editor/workflow.js'\n\nexport interface MessageContextLabels {\n title: string\n id: string\n description: string\n catalogs: string\n locations: string\n}\nexport interface MessageContextProps {\n message?: Pick<\n EditorMessage,\n 'id' | 'description' | 'catalogs' | 'locations'\n > | null\n labels?: Partial<MessageContextLabels>\n copyId?: boolean\n copyOptions?: Pick<\n CopyTextButtonProps,\n 'writeText' | 'onCopy' | 'onError' | 'labels' | 'feedbackDurationMs'\n >\n /** Default locations are plain text, never file URLs or HTML. */\n renderLocation?: (location: SourceLocation) => ReactNode\n}\nconst defaults: MessageContextLabels = {\n title: 'Message context',\n id: 'Message ID',\n description: 'Description',\n catalogs: 'Source catalogs',\n locations: 'Source locations',\n}\nfunction locationText(location: SourceLocation): string {\n const start = location.start === undefined ? '' : `:${location.start}`\n const end =\n location.end === undefined\n ? ''\n : `${location.start === undefined ? ':' : '–'}${location.end}`\n return `${location.file}${start}${end}`\n}\n\nexport function MessageContext({\n message,\n labels,\n copyId = true,\n copyOptions,\n renderLocation = locationText,\n}: MessageContextProps): ReactNode {\n const {Metadata} = useEditorDesignSystem()\n const text = {...defaults, ...labels}\n return (\n <Metadata label={text.title}>\n {message && (\n <dl>\n <div>\n <dt>{text.id}</dt>\n <dd>\n <code>{message.id}</code>\n {copyId && (\n <CopyTextButton\n {...copyOptions}\n value={message.id}\n label={text.id}\n />\n )}\n </dd>\n </div>\n {message.description && (\n <div>\n <dt>{text.description}</dt>\n <dd>{message.description}</dd>\n </div>\n )}\n {!!message.catalogs?.length && (\n <div>\n <dt>{text.catalogs}</dt>\n <dd>\n <ul>\n {message.catalogs.map((catalog, index) => (\n <li key={index}>{catalog}</li>\n ))}\n </ul>\n </dd>\n </div>\n )}\n {!!message.locations?.length && (\n <div>\n <dt>{text.locations}</dt>\n <dd>\n <ul>\n {message.locations.map((location, index) => (\n <li key={index}>{renderLocation(location)}</li>\n ))}\n </ul>\n </dd>\n </div>\n )}\n </dl>\n )}\n </Metadata>\n )\n}\n","import {useId, type ReactNode} from 'react'\nimport {useEditorDesignSystem} from '#packages/editor/design-system.js'\nexport {\n type EditorButtonProps,\n type EditorInputProps,\n type EditorTextInputProps,\n type EditorTextAreaProps,\n type EditorMessageRowProps,\n type EditorPanelProps,\n type EditorLayoutProps,\n type EditorComponents,\n type EditorCheckboxProps,\n type EditorLocalePickerLayoutProps,\n type EditorPreviewTokenProps,\n type EditorCopyStatus,\n type EditorCopyButtonProps,\n type EditorMetadataProps,\n type EditorToolComponents,\n type ResolvedEditorComponents,\n nativeEditorComponents,\n type EditorDesignSystemProviderProps,\n EditorDesignSystemProvider,\n useEditorDesignSystem,\n} from '#packages/editor/design-system.js'\nexport {\n type LocalePickerLabels,\n type LocalePickerProps,\n LocalePicker,\n} from '#packages/editor/locale-picker.js'\nexport {\n type MessagePreviewProps,\n MessagePreview,\n} from '#packages/editor/message-preview.js'\nexport {\n type CopyTextLabels,\n type CopyTextButtonProps,\n CopyTextButton,\n} from '#packages/editor/copy-text-button.js'\nexport {\n type MessageContextLabels,\n type MessageContextProps,\n MessageContext,\n} from '#packages/editor/message-context.js'\nimport type {\n EditorMessage,\n TranslationDraftState,\n} from '#packages/editor/workflow.js'\nimport type {TranslationValidationError} from '#packages/editor/validation.js'\n\nexport interface EditorLabels {\n search: string\n messages: string\n source: string\n noMessages: string\n noSelection: string\n loading: string\n copySource: string\n reset: string\n save: string\n saving: string\n saved: string\n unsaved: string\n unchanged: string\n validation: Record<TranslationValidationError, string>\n}\nconst defaultLabels: EditorLabels = {\n search: 'Search messages',\n messages: 'Messages',\n source: 'Source message',\n noMessages: 'No matching messages',\n noSelection: 'Select a message',\n loading: 'Loading messages…',\n copySource: 'Copy source',\n reset: 'Reset',\n save: 'Save translation',\n saving: 'Saving…',\n saved: 'Translation saved.',\n unsaved: 'Unsaved changes',\n unchanged: 'No unsaved changes',\n validation: {\n empty: 'Enter a translation before saving.',\n 'invalid-source': 'The source contains invalid ICU syntax.',\n 'invalid-translation': 'The translation contains invalid ICU syntax.',\n structure:\n 'Preserve ICU arguments, tags, formatting styles, and selector branches.',\n },\n}\nexport type EditorLabelOverrides = Partial<Omit<EditorLabels, 'validation'>> & {\n validation?: Partial<EditorLabels['validation']>\n}\nfunction resolveLabels(labels?: EditorLabelOverrides): EditorLabels {\n return {\n ...defaultLabels,\n ...labels,\n validation: {...defaultLabels.validation, ...labels?.validation},\n }\n}\ninterface ViewOptions {\n labels?: EditorLabelOverrides\n}\nexport type EditorViewMessage = Pick<\n EditorMessage,\n 'id' | 'defaultMessage' | 'description'\n>\nexport interface EditorSearch {\n value: string\n onValueChange: (value: string) => void\n}\nexport interface EditorMessageRenderState {\n selected: boolean\n}\nexport interface MessageListProps<\n Message extends EditorViewMessage = EditorViewMessage,\n> extends ViewOptions {\n messages: readonly Message[]\n selectedId?: string\n onSelect: (id: string) => void\n search?: EditorSearch\n loading?: boolean\n pagination?: ReactNode\n /** Summary or controls between search and the loaded rows. */\n listSummary?: ReactNode\n /** Noninteractive content inside the design-system selection control. */\n renderMessage?: (\n message: Message,\n state: EditorMessageRenderState\n ) => ReactNode\n /** Interactive actions rendered beside, never inside, the selection control. */\n renderMessageActions?: (\n message: Message,\n state: EditorMessageRenderState\n ) => ReactNode\n}\n/** Controlled list: it never filters, paginates, fetches, or changes selection itself. */\nexport function MessageList<\n Message extends EditorViewMessage = EditorViewMessage,\n>({\n messages,\n selectedId,\n onSelect,\n search,\n loading = false,\n pagination,\n listSummary,\n renderMessage,\n renderMessageActions,\n labels,\n}: MessageListProps<Message>): ReactNode {\n const {TextInput, MessageRow} = useEditorDesignSystem()\n const text = resolveLabels(labels)\n const searchId = useId()\n return (\n <nav aria-label={text.messages} aria-busy={loading}>\n {search && (\n <div>\n <label htmlFor={searchId}>{text.search}</label>\n <TextInput\n id={searchId}\n type=\"search\"\n value={search.value}\n onValueChange={search.onValueChange}\n />\n </div>\n )}\n {listSummary}\n {loading && (\n <p>\n <output>{text.loading}</output>\n </p>\n )}\n <ul>\n {messages.map(message => {\n const state = {selected: message.id === selectedId}\n return (\n <li key={message.id}>\n <MessageRow\n selected={state.selected}\n onSelect={() => onSelect(message.id)}\n >\n {renderMessage ? (\n renderMessage(message, state)\n ) : (\n <>\n <span>{message.defaultMessage}</span>{' '}\n <code>{message.id}</code>\n </>\n )}\n </MessageRow>\n {renderMessageActions?.(message, state)}\n </li>\n )\n })}\n </ul>\n {!loading && messages.length === 0 && (\n <p>\n <output>{text.noMessages}</output>\n </p>\n )}\n {pagination}\n </nav>\n )\n}\nexport interface SourceMessageProps extends ViewOptions {\n message: EditorViewMessage\n preview?: ReactNode\n context?: ReactNode\n}\nexport function SourceMessage({\n message,\n preview,\n context,\n labels,\n}: SourceMessageProps): ReactNode {\n const {Panel} = useEditorDesignSystem()\n const text = resolveLabels(labels)\n return (\n <Panel kind=\"source\" label={text.source}>\n <h2>{text.source}</h2>\n <code>{message.id}</code>\n {preview ?? <pre>{message.defaultMessage}</pre>}\n {message.description && <p>{message.description}</p>}\n {context}\n </Panel>\n )\n}\nexport type TranslationFieldDraft = Pick<\n TranslationDraftState,\n | 'value'\n | 'validationError'\n | 'changed'\n | 'isSaving'\n | 'saveError'\n | 'saved'\n | 'setTranslation'\n | 'reset'\n>\nexport interface TranslationFieldProps extends ViewOptions {\n locale: string\n /** Human-readable label; defaults to the locale identifier. */\n label?: string\n source: string\n draft: TranslationFieldDraft\n /** The caller owns confirmation, context, persistence, and receipt presentation. */\n onSave?: () => void\n /** Overrides the default copy/reset/save actions, including with null. */\n actions?: ReactNode\n preview?: ReactNode\n}\nexport function TranslationField({\n locale,\n label = locale,\n source,\n draft,\n onSave,\n actions,\n preview,\n labels,\n}: TranslationFieldProps): ReactNode {\n const {Panel, TextArea, Button} = useEditorDesignSystem()\n const text = resolveLabels(labels)\n const id = useId()\n const errorId = `${id}-error`\n const statusId = `${id}-status`\n const validation = draft.validationError\n ? text.validation[draft.validationError]\n : null\n const error = validation ?? draft.saveError?.message\n return (\n <Panel kind=\"translation\" label={label}>\n <label htmlFor={id}>{label}</label>\n <TextArea\n id={id}\n value={draft.value}\n onValueChange={draft.setTranslation}\n aria-invalid={!!validation}\n aria-describedby={error ? `${errorId} ${statusId}` : statusId}\n />\n {error && (\n <p id={errorId} role=\"alert\">\n {error}\n </p>\n )}\n <p>\n <output id={statusId}>\n {draft.isSaving\n ? text.saving\n : draft.saved\n ? text.saved\n : draft.changed\n ? text.unsaved\n : text.unchanged}\n </output>\n </p>\n {preview}\n {actions !== undefined ? (\n actions\n ) : (\n <div>\n <Button\n variant=\"secondary\"\n disabled={draft.isSaving}\n onPress={() => draft.setTranslation(source)}\n >\n {text.copySource}\n </Button>\n <Button\n variant=\"secondary\"\n disabled={!draft.changed || draft.isSaving}\n onPress={draft.reset}\n >\n {text.reset}\n </Button>\n {onSave && (\n <Button\n variant=\"primary\"\n disabled={\n !draft.changed || !!draft.validationError || draft.isSaving\n }\n onPress={onSave}\n >\n {draft.isSaving ? text.saving : text.save}\n </Button>\n )}\n </div>\n )}\n </Panel>\n )\n}\nexport type EditorTranslation = Omit<TranslationFieldProps, 'source'>\nexport interface TranslationEditorViewProps<\n Message extends EditorViewMessage = EditorViewMessage,\n> extends MessageListProps<Message> {\n /** May be outside the loaded list, e.g. during a server-side page change. */\n selectedMessage?: EditorViewMessage\n translations: readonly EditorTranslation[]\n filters?: ReactNode\n context?: ReactNode\n sourcePreview?: ReactNode\n notice?: ReactNode\n sidebar?: ReactNode\n /** Replaces the no-selection status; null suppresses it. */\n emptyState?: ReactNode\n /** Wrap the generated locale fields without recreating their wiring. */\n renderTranslations?: (fields: ReactNode) => ReactNode\n /** Wrap the entire detail region, including notices and the empty state. */\n renderContent?: (content: ReactNode) => ReactNode\n}\n/** A stateless composition over caller-owned navigation and locale drafts. */\nexport function TranslationEditorView<\n Message extends EditorViewMessage = EditorViewMessage,\n>({\n selectedMessage,\n selectedId = selectedMessage?.id,\n translations,\n filters,\n context,\n sourcePreview,\n notice,\n sidebar,\n emptyState,\n renderTranslations,\n renderContent,\n labels,\n ...list\n}: TranslationEditorViewProps<Message>): ReactNode {\n const {Layout} = useEditorDesignSystem()\n const text = resolveLabels(labels)\n const fields = selectedMessage\n ? translations.map(translation => (\n <TranslationField\n {...translation}\n key={`${selectedMessage.id}:${translation.locale}`}\n source={selectedMessage.defaultMessage}\n labels={{\n ...labels,\n ...translation.labels,\n validation: {\n ...labels?.validation,\n ...translation.labels?.validation,\n },\n }}\n />\n ))\n : null\n const content = (\n <>\n {notice}\n {selectedMessage ? (\n <>\n <SourceMessage\n message={selectedMessage}\n preview={sourcePreview}\n context={context}\n labels={labels}\n />\n {renderTranslations ? renderTranslations(fields) : fields}\n </>\n ) : emptyState !== undefined ? (\n emptyState\n ) : (\n <p>\n <output>{text.noSelection}</output>\n </p>\n )}\n </>\n )\n return (\n <Layout\n toolbar={filters}\n navigation={\n <MessageList {...list} selectedId={selectedId} labels={labels} />\n }\n content={renderContent ? renderContent(content) : content}\n sidebar={sidebar}\n />\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ui.js","names":["defaults","defaults"],"sources":["../design-system.tsx","../locale-picker.tsx","../message-preview.tsx","../copy-text-button.tsx","../message-context.tsx","../ui.tsx"],"sourcesContent":["import {\n createContext,\n useContext,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n type ComponentType,\n type ReactNode,\n} from 'react'\nexport interface EditorButtonProps {\n children: ReactNode\n /** Called once per activation, without a DOM event; disabled controls must not call it. */\n onPress: () => void\n disabled?: boolean\n variant: 'primary' | 'secondary'\n}\nexport interface EditorInputProps {\n id: string\n value: string\n /** Reports the complete next string value; never a DOM event. */\n onValueChange: (value: string) => void\n disabled?: boolean\n 'aria-invalid'?: boolean\n 'aria-describedby'?: string\n 'aria-label'?: string\n}\nexport interface EditorTextInputProps extends EditorInputProps {\n type: 'text' | 'search'\n placeholder?: string\n}\nexport interface EditorTextAreaProps extends EditorInputProps {\n /** Initial visible rows retained for compatibility. Prefer minRows. */\n rows?: number\n /** Minimum visible rows while the controlled field grows and shrinks. */\n minRows?: number\n /** Maximum visible rows before the controlled field scrolls. */\n maxRows?: number\n}\nexport interface EditorMessageRowProps {\n children: ReactNode\n selected: boolean\n /** Activates this row without changing controlled selection itself. */\n onSelect: () => void\n}\nexport interface EditorPanelProps {\n children: ReactNode\n label: string\n kind: 'source' | 'translation'\n}\nexport interface EditorLayoutProps {\n toolbar: ReactNode\n navigation: ReactNode\n content: ReactNode\n /** Optional secondary content, arranged by the design-system layout. */\n sidebar?: ReactNode\n}\n/** Define adapters outside render so controls retain focus across edits. */\nexport interface EditorComponents extends Partial<EditorToolComponents> {\n Button: ComponentType<EditorButtonProps>\n TextInput: ComponentType<EditorTextInputProps>\n TextArea: ComponentType<EditorTextAreaProps>\n MessageRow: ComponentType<EditorMessageRowProps>\n Panel: ComponentType<EditorPanelProps>\n Layout: ComponentType<EditorLayoutProps>\n}\nexport interface EditorCheckboxProps {\n id: string\n checked: boolean | 'indeterminate'\n disabled?: boolean\n onCheckedChange: (checked: boolean) => void\n}\nexport interface EditorLocalePickerLayoutProps {\n id: string\n title: string\n summary: ReactNode\n triggerLabel: string\n open: boolean\n onOpenChange: (open: boolean) => void\n controls: ReactNode\n /** Contiguous, balanced columns in the caller's locale order. */\n columns: readonly ReactNode[]\n}\nexport interface EditorPreviewTokenProps {\n children: ReactNode\n kind:\n | 'argument'\n | 'number'\n | 'date'\n | 'time'\n | 'tag'\n | 'selector'\n | 'plural'\n | 'pound'\n | 'syntax'\n}\nexport type EditorCopyStatus = 'idle' | 'copying' | 'copied' | 'error'\nexport interface EditorCopyButtonProps {\n label: string\n status: EditorCopyStatus\n disabled: boolean\n onPress: () => void\n}\nexport interface EditorMetadataProps {\n label: string\n children: ReactNode\n}\n/** Optional additions preserve existing complete design-system registries. */\nexport interface EditorToolComponents {\n Checkbox: ComponentType<EditorCheckboxProps>\n LocalePickerLayout: ComponentType<EditorLocalePickerLayoutProps>\n PreviewToken: ComponentType<EditorPreviewTokenProps>\n CopyButton: ComponentType<EditorCopyButtonProps>\n Metadata: ComponentType<EditorMetadataProps>\n}\nexport type ResolvedEditorComponents = Required<EditorComponents>\n/** Unstyled native controls; no CSS, icons, or localization provider is required. */\nfunction finiteRows(value: number | undefined, fallback: number): number {\n return Number.isFinite(value) && value! > 0 ? Math.floor(value!) : fallback\n}\n\nfunction NativeTextArea({\n onValueChange,\n value,\n rows,\n minRows,\n maxRows,\n ...props\n}: EditorTextAreaProps): ReactNode {\n const ref = useRef<HTMLTextAreaElement>(null)\n const minimum = finiteRows(minRows ?? rows, 1)\n const maximum = Math.max(minimum, finiteRows(maxRows, 10))\n useLayoutEffect(() => {\n const field = ref.current\n if (!field) return\n const resize = (): void => {\n const computed = getComputedStyle(field)\n const parsedLineHeight = Number.parseFloat(computed.lineHeight)\n const fontSize = Number.parseFloat(computed.fontSize)\n const lineHeight = Number.isFinite(parsedLineHeight)\n ? parsedLineHeight\n : fontSize * 1.2\n if (!Number.isFinite(lineHeight)) return\n const pixels = (value: string): number => Number.parseFloat(value) || 0\n const chrome =\n pixels(computed.paddingTop) +\n pixels(computed.paddingBottom) +\n pixels(computed.borderTopWidth) +\n pixels(computed.borderBottomWidth)\n const minimumHeight = minimum * lineHeight + chrome\n const maximumHeight = maximum * lineHeight + chrome\n field.style.height = 'auto'\n field.style.height = `${Math.min(Math.max(field.scrollHeight, minimumHeight), maximumHeight)}px`\n field.style.overflowY =\n field.scrollHeight > maximumHeight ? 'auto' : 'hidden'\n }\n resize()\n if (typeof ResizeObserver === 'undefined') return\n let width = field.getBoundingClientRect().width\n const observer = new ResizeObserver(() => {\n const nextWidth = field.getBoundingClientRect().width\n if (nextWidth === width) return\n width = nextWidth\n resize()\n })\n observer.observe(field)\n return () => observer.disconnect()\n }, [maximum, minimum, value])\n return (\n <textarea\n {...props}\n ref={ref}\n rows={minimum}\n value={value}\n onChange={event => onValueChange(event.target.value)}\n />\n )\n}\n\nexport const nativeEditorComponents: ResolvedEditorComponents = {\n Checkbox: function NativeCheckbox({checked, onCheckedChange, ...props}) {\n const ref = useRef<HTMLInputElement>(null)\n useEffect(() => {\n if (ref.current) ref.current.indeterminate = checked === 'indeterminate'\n }, [checked])\n return (\n <input\n {...props}\n ref={ref}\n type=\"checkbox\"\n checked={checked === true}\n aria-checked={checked === 'indeterminate' ? 'mixed' : checked}\n onChange={event => onCheckedChange(event.target.checked)}\n />\n )\n },\n LocalePickerLayout: ({\n id,\n title,\n summary,\n triggerLabel,\n open,\n onOpenChange,\n controls,\n columns,\n }) => (\n <div>\n <button\n type=\"button\"\n aria-label={triggerLabel}\n aria-expanded={open}\n aria-controls={id}\n onClick={() => onOpenChange(!open)}\n >\n {summary}\n </button>\n <fieldset id={id} hidden={!open}>\n <legend>{title}</legend>\n {controls}\n <div\n style={{\n display: 'grid',\n gridTemplateColumns:\n 'repeat(auto-fit, minmax(min(100%, 16rem), 1fr))',\n maxHeight: '60vh',\n overflowY: 'auto',\n }}\n >\n {columns.map((column, index) => (\n <div key={index}>{column}</div>\n ))}\n </div>\n </fieldset>\n </div>\n ),\n PreviewToken: ({children}) => <code>{children}</code>,\n CopyButton: ({label, onPress, disabled}) => (\n <button type=\"button\" disabled={disabled} onClick={() => onPress()}>\n {label}\n </button>\n ),\n Metadata: ({label, children}) => <aside aria-label={label}>{children}</aside>,\n Button: ({children, onPress, disabled}) => (\n <button type=\"button\" disabled={disabled} onClick={() => onPress()}>\n {children}\n </button>\n ),\n TextInput: ({onValueChange, ...props}) => (\n <input {...props} onChange={event => onValueChange(event.target.value)} />\n ),\n TextArea: NativeTextArea,\n MessageRow: ({children, selected, onSelect}) => (\n <button\n type=\"button\"\n aria-current={selected ? 'true' : undefined}\n onClick={() => onSelect()}\n >\n {children}\n </button>\n ),\n Panel: ({children, label}) => (\n <section aria-label={label}>{children}</section>\n ),\n Layout: ({toolbar, navigation, content, sidebar}) => (\n <div>\n {toolbar}\n <div>\n {navigation}\n {content}\n {sidebar}\n </div>\n </div>\n ),\n}\nconst EditorDesignSystemContext = createContext<\n Readonly<ResolvedEditorComponents>\n>(nativeEditorComponents)\n\nexport interface EditorDesignSystemProviderProps {\n /** Overrides inherit unspecified components from the nearest provider. */\n components: Partial<EditorComponents>\n children: ReactNode\n}\n/** Configure a tree once; sibling providers remain independent. */\nexport function EditorDesignSystemProvider({\n components,\n children,\n}: EditorDesignSystemProviderProps): ReactNode {\n const parent = useEditorDesignSystem()\n const value = useMemo(\n () => ({\n ...parent,\n ...Object.fromEntries(\n Object.entries(components).filter(([, value]) => value !== undefined)\n ),\n }),\n [parent, components]\n )\n return (\n <EditorDesignSystemContext value={value}>\n {children}\n </EditorDesignSystemContext>\n )\n}\n/** Read the resolved controls, including native defaults outside a provider. */\nexport function useEditorDesignSystem(): Readonly<ResolvedEditorComponents> {\n return useContext(EditorDesignSystemContext)\n}\n","import {useId, useState, type ReactNode} from 'react'\nimport {useEditorDesignSystem} from '#packages/editor/design-system.js'\n\nexport interface LocalePickerLabels {\n title: string\n empty: string\n clear: string\n selectAll: (count: number) => string\n selected: (count: number) => string\n trigger: (summary: string) => string\n}\nexport interface LocalePickerProps {\n locales: readonly string[]\n selectedLocales: readonly string[]\n /** Reports unique available locales in their input order; never mutates selection. */\n onChange: (locales: string[]) => void\n /** Defaults to the locale code; consumers choose the display language. */\n getLocaleLabel?: (locale: string) => string\n /** Adds presentation without changing the text used for accessible names. */\n renderLocaleLabel?: (locale: string, label: string) => ReactNode\n labels?: Partial<LocalePickerLabels>\n}\nconst defaults: LocalePickerLabels = {\n title: 'Locales',\n empty: 'No locales selected',\n clear: 'Clear',\n selectAll: count =>\n `Select all ${count} ${count === 1 ? 'locale' : 'locales'}`,\n selected: count => `${count} locales selected`,\n trigger: summary => `Locales: ${summary}`,\n}\nconst localeCode = (locale: string): string => locale\n\nexport function LocalePicker({\n locales,\n selectedLocales,\n onChange,\n getLocaleLabel = localeCode,\n renderLocaleLabel = (_locale, label) => label,\n labels,\n}: LocalePickerProps): ReactNode {\n const {Checkbox, Button, LocalePickerLayout} = useEditorDesignSystem()\n const id = useId()\n const [open, setOpen] = useState(false)\n const available = [...new Set(locales)]\n const selected = new Set(selectedLocales)\n const active = available.filter(locale => selected.has(locale))\n const text = {...defaults, ...labels}\n const summaryText =\n active.length === 0\n ? text.empty\n : active.length === 1\n ? getLocaleLabel(active[0]!)\n : text.selected(active.length)\n const summary =\n active.length === 1\n ? renderLocaleLabel(active[0]!, summaryText)\n : summaryText\n const all = available.length > 0 && active.length === available.length\n const midpoint = Math.ceil(available.length / 2)\n const columns = [available.slice(0, midpoint), available.slice(midpoint)]\n return (\n <LocalePickerLayout\n id={id}\n title={text.title}\n summary={summary}\n triggerLabel={text.trigger(summaryText)}\n open={open}\n onOpenChange={setOpen}\n controls={\n <div>\n <label htmlFor={`${id}-all`}>\n <Checkbox\n id={`${id}-all`}\n disabled={available.length === 0}\n checked={all ? true : active.length ? 'indeterminate' : false}\n onCheckedChange={checked => onChange(checked ? available : [])}\n />\n {text.selectAll(available.length)}\n </label>\n <Button\n variant=\"secondary\"\n disabled={active.length === 0}\n onPress={() => onChange([])}\n >\n {text.clear}\n </Button>\n </div>\n }\n columns={columns.map((column, columnIndex) =>\n column.map((locale, index) => {\n const checkboxId = `${id}-${columnIndex}-${index}`\n return (\n <label\n key={locale}\n htmlFor={checkboxId}\n style={{display: 'flex', alignItems: 'center', gap: 8}}\n >\n <Checkbox\n id={checkboxId}\n checked={selected.has(locale)}\n onCheckedChange={checked =>\n onChange(\n available.filter(candidate =>\n candidate === locale ? checked : selected.has(candidate)\n )\n )\n }\n />\n {renderLocaleLabel(locale, getLocaleLabel(locale))}\n </label>\n )\n })\n )}\n />\n )\n}\n","import {\n parse,\n TYPE,\n type MessageFormatElement,\n} from '@formatjs/icu-messageformat-parser'\nimport {useMemo, type ReactNode} from 'react'\nimport {\n useEditorDesignSystem,\n type EditorPreviewTokenProps,\n} from '#packages/editor/design-system.js'\n\nexport interface MessagePreviewProps {\n message: string\n /** Customize a parser error without suppressing the alert semantics. */\n formatError?: (error: Error) => ReactNode\n}\n\n/** Structural ICU preview: shows every branch, without evaluating values or HTML. */\nexport function MessagePreview({\n message,\n formatError,\n}: MessagePreviewProps): ReactNode {\n const {PreviewToken} = useEditorDesignSystem()\n const parsed = useMemo(() => {\n try {\n return {ast: parse(message, {captureLocation: true}), error: null}\n } catch (error) {\n return {\n ast: null,\n error: error instanceof Error ? error : new Error(String(error)),\n }\n }\n }, [message])\n if (parsed.error)\n return (\n <p role=\"alert\">\n {formatError ? formatError(parsed.error) : parsed.error.message}\n </p>\n )\n const token = (\n text: string,\n kind: EditorPreviewTokenProps['kind'],\n key: string\n ): ReactNode => (\n <PreviewToken key={key} kind={kind}>\n <bdi dir=\"ltr\">{text}</bdi>\n </PreviewToken>\n )\n const elements = (ast: MessageFormatElement[], prefix: string): ReactNode[] =>\n ast.flatMap((element, index) => {\n const key = `${prefix}-${index}`\n switch (element.type) {\n case TYPE.literal:\n return <span key={key}>{element.value}</span>\n case TYPE.pound:\n return token('#', 'pound', key)\n case TYPE.argument:\n case TYPE.number:\n case TYPE.date:\n case TYPE.time:\n return token(\n message.slice(\n element.location!.start.offset,\n element.location!.end.offset\n ),\n TYPE[element.type] as 'argument' | 'number' | 'date' | 'time',\n key\n )\n case TYPE.tag:\n return [\n token(`<${element.value}>`, 'tag', `${key}-open`),\n ...elements(element.children, `${key}-children`),\n token(`</${element.value}>`, 'tag', `${key}-close`),\n ]\n case TYPE.select:\n case TYPE.plural: {\n const kind =\n element.type === TYPE.select\n ? 'select'\n : element.pluralType === 'ordinal'\n ? 'selectordinal'\n : 'plural'\n const offset =\n element.type === TYPE.plural && element.offset\n ? ` offset:${element.offset}`\n : ''\n return [\n token(\n `{${element.value}, ${kind},${offset}`,\n element.type === TYPE.select ? 'selector' : 'plural',\n `${key}-open`\n ),\n ...Object.entries(element.options).flatMap(([selector, option]) => [\n token(`${selector} {`, 'selector', `${key}-${selector}`),\n ...elements(option.value, `${key}-${selector}`),\n token('}', 'syntax', `${key}-${selector}-close`),\n ]),\n token('}', 'syntax', `${key}-close`),\n ]\n }\n }\n })\n return (\n <div dir=\"auto\" style={{whiteSpace: 'pre-wrap', overflowWrap: 'anywhere'}}>\n {elements(parsed.ast!, 'message')}\n </div>\n )\n}\n","import {useEffect, useRef, useState, type ReactNode} from 'react'\nimport {\n useEditorDesignSystem,\n type EditorCopyStatus,\n} from '#packages/editor/design-system.js'\n\nexport interface CopyTextLabels {\n copy: (label: string) => string\n copying: (label: string) => string\n copied: (label: string) => string\n failed: (label: string) => string\n}\nexport interface CopyTextButtonProps {\n value: string\n label: string\n disabled?: boolean\n /** Injectable clipboard boundary; rejects when the write fails. */\n writeText?: (value: string) => Promise<void>\n onCopy?: (value: string) => void\n onError?: (error: Error, value: string) => void\n feedbackDurationMs?: number\n labels?: Partial<CopyTextLabels>\n}\nconst defaults: CopyTextLabels = {\n copy: label => `Copy ${label}`,\n copying: label => `Copying ${label}…`,\n copied: label => `Copied ${label}`,\n failed: label => `Could not copy ${label}`,\n}\nasync function writeClipboard(value: string): Promise<void> {\n if (typeof navigator === 'undefined' || !navigator.clipboard?.writeText) {\n throw new Error('Clipboard access is unavailable')\n }\n await navigator.clipboard.writeText(value)\n}\n\n/** Copies exact text; feedback belongs to the current value and mounted control. */\nexport function CopyTextButton({\n value,\n label,\n disabled = false,\n writeText = writeClipboard,\n onCopy,\n onError,\n feedbackDurationMs = 1500,\n labels,\n}: CopyTextButtonProps): ReactNode {\n const {CopyButton} = useEditorDesignSystem()\n const [status, setStatus] = useState<EditorCopyStatus>('idle')\n const operation = useRef({\n generation: 0,\n pending: false,\n timer: undefined as ReturnType<typeof setTimeout> | undefined,\n })\n useEffect(() => {\n const current = operation.current\n current.generation++\n current.pending = false\n setStatus('idle')\n clearTimeout(current.timer)\n return () => {\n current.generation++\n clearTimeout(current.timer)\n }\n }, [value, writeText])\n const copy = async (): Promise<void> => {\n const current = operation.current\n if (disabled || current.pending) return\n current.pending = true\n const request = ++current.generation\n clearTimeout(current.timer)\n setStatus('copying')\n let error: Error | undefined\n try {\n await writeText(value)\n } catch (failure) {\n error = failure instanceof Error ? failure : new Error(String(failure))\n }\n if (request !== current.generation) return\n current.pending = false\n setStatus(error ? 'error' : 'copied')\n current.timer = setTimeout(\n () => {\n if (request === current.generation) setStatus('idle')\n },\n Math.max(0, feedbackDurationMs)\n )\n if (error) onError?.(error, value)\n else onCopy?.(value)\n }\n const text = {...defaults, ...labels}\n const buttonLabel = (\n status === 'copying'\n ? text.copying\n : status === 'copied'\n ? text.copied\n : status === 'error'\n ? text.failed\n : text.copy\n )(label)\n return (\n <span>\n <CopyButton\n label={buttonLabel}\n status={status}\n disabled={disabled || status === 'copying'}\n onPress={() => {\n void copy()\n }}\n />\n {status === 'copied' && <output>{text.copied(label)}</output>}\n {status === 'error' && <span role=\"alert\">{text.failed(label)}</span>}\n </span>\n )\n}\n","import type {ReactNode} from 'react'\nimport {useEditorDesignSystem} from '#packages/editor/design-system.js'\nimport {\n CopyTextButton,\n type CopyTextButtonProps,\n} from '#packages/editor/copy-text-button.js'\nimport type {EditorMessage, SourceLocation} from '#packages/editor/workflow.js'\n\nexport interface MessageContextLabels {\n title: string\n id: string\n description: string\n catalogs: string\n locations: string\n catalogCopy: (catalog: string) => string\n locationCopy: (location: string) => string\n}\nexport interface MessageContextProps {\n message?: Pick<\n EditorMessage,\n 'id' | 'description' | 'catalogs' | 'locations'\n > | null\n labels?: Partial<MessageContextLabels>\n copyId?: boolean\n copyCatalogs?: boolean\n copyLocations?: boolean\n copyOptions?: Pick<\n CopyTextButtonProps,\n 'writeText' | 'onCopy' | 'onError' | 'labels' | 'feedbackDurationMs'\n >\n /** Default locations are plain text, never file URLs or HTML. */\n renderLocation?: (location: SourceLocation, text: string) => ReactNode\n}\nconst defaults: MessageContextLabels = {\n title: 'Message context',\n id: 'Message ID',\n description: 'Description',\n catalogs: 'Source catalogs',\n locations: 'Source locations',\n catalogCopy: catalog => `source catalog ${catalog}`,\n locationCopy: location => `source location ${location}`,\n}\nfunction locationText(location: SourceLocation): string {\n const start = location.start === undefined ? '' : `:${location.start}`\n const end =\n location.end === undefined\n ? ''\n : `${location.start === undefined ? ':' : '–'}${location.end}`\n return `${location.file}${start}${end}`\n}\n\nexport function MessageContext({\n message,\n labels,\n copyId = true,\n copyCatalogs = false,\n copyLocations = false,\n copyOptions,\n renderLocation = locationText,\n}: MessageContextProps): ReactNode {\n const {Metadata} = useEditorDesignSystem()\n const text = {...defaults, ...labels}\n return (\n <Metadata label={text.title}>\n {message && (\n <dl>\n <div>\n <dt>{text.id}</dt>\n <dd>\n <code>{message.id}</code>\n {copyId && (\n <CopyTextButton\n {...copyOptions}\n value={message.id}\n label={text.id}\n />\n )}\n </dd>\n </div>\n {message.description && (\n <div>\n <dt>{text.description}</dt>\n <dd>{message.description}</dd>\n </div>\n )}\n {!!message.catalogs?.length && (\n <div>\n <dt>{text.catalogs}</dt>\n <dd>\n <ul>\n {message.catalogs.map((catalog, index) => (\n <li key={`${catalog}:${index}`}>\n <span>{catalog}</span>\n {copyCatalogs && (\n <CopyTextButton\n {...copyOptions}\n value={catalog}\n label={text.catalogCopy(catalog)}\n />\n )}\n </li>\n ))}\n </ul>\n </dd>\n </div>\n )}\n {!!message.locations?.length && (\n <div>\n <dt>{text.locations}</dt>\n <dd>\n <ul>\n {message.locations.map((location, index) => {\n const value = locationText(location)\n return (\n <li key={`${value}:${index}`}>\n <span>{renderLocation(location, value)}</span>\n {copyLocations && (\n <CopyTextButton\n {...copyOptions}\n value={value}\n label={text.locationCopy(value)}\n />\n )}\n </li>\n )\n })}\n </ul>\n </dd>\n </div>\n )}\n </dl>\n )}\n </Metadata>\n )\n}\n","import {useId, type ReactNode} from 'react'\nimport {useEditorDesignSystem} from '#packages/editor/design-system.js'\nexport {\n type EditorButtonProps,\n type EditorInputProps,\n type EditorTextInputProps,\n type EditorTextAreaProps,\n type EditorMessageRowProps,\n type EditorPanelProps,\n type EditorLayoutProps,\n type EditorComponents,\n type EditorCheckboxProps,\n type EditorLocalePickerLayoutProps,\n type EditorPreviewTokenProps,\n type EditorCopyStatus,\n type EditorCopyButtonProps,\n type EditorMetadataProps,\n type EditorToolComponents,\n type ResolvedEditorComponents,\n nativeEditorComponents,\n type EditorDesignSystemProviderProps,\n EditorDesignSystemProvider,\n useEditorDesignSystem,\n} from '#packages/editor/design-system.js'\nexport {\n type LocalePickerLabels,\n type LocalePickerProps,\n LocalePicker,\n} from '#packages/editor/locale-picker.js'\nexport {\n type MessagePreviewProps,\n MessagePreview,\n} from '#packages/editor/message-preview.js'\nexport {\n type CopyTextLabels,\n type CopyTextButtonProps,\n CopyTextButton,\n} from '#packages/editor/copy-text-button.js'\nexport {\n type MessageContextLabels,\n type MessageContextProps,\n MessageContext,\n} from '#packages/editor/message-context.js'\nimport type {\n EditorMessage,\n TranslationDraftState,\n} from '#packages/editor/workflow.js'\nimport type {TranslationValidationError} from '#packages/editor/validation.js'\n\nexport interface EditorLabels {\n search: string\n descriptionSearch: string\n messages: string\n source: string\n noMessages: string\n noSelection: string\n loading: string\n copySource: string\n reset: string\n save: string\n saving: string\n saved: string\n unsaved: string\n unchanged: string\n validation: Record<TranslationValidationError, string>\n}\nconst defaultLabels: EditorLabels = {\n search: 'Search messages',\n descriptionSearch: 'Search descriptions',\n messages: 'Messages',\n source: 'Source message',\n noMessages: 'No matching messages',\n noSelection: 'Select a message',\n loading: 'Loading messages…',\n copySource: 'Copy source',\n reset: 'Reset',\n save: 'Save translation',\n saving: 'Saving…',\n saved: 'Translation saved.',\n unsaved: 'Unsaved changes',\n unchanged: 'No unsaved changes',\n validation: {\n empty: 'Enter a translation before saving.',\n 'invalid-source': 'The source contains invalid ICU syntax.',\n 'invalid-translation': 'The translation contains invalid ICU syntax.',\n structure:\n 'Preserve ICU arguments, tags, formatting styles, and selector branches.',\n },\n}\nexport type EditorLabelOverrides = Partial<Omit<EditorLabels, 'validation'>> & {\n validation?: Partial<EditorLabels['validation']>\n}\nfunction resolveLabels(labels?: EditorLabelOverrides): EditorLabels {\n return {\n ...defaultLabels,\n ...labels,\n validation: {...defaultLabels.validation, ...labels?.validation},\n }\n}\ninterface ViewOptions {\n labels?: EditorLabelOverrides\n}\nexport type EditorViewMessage = Pick<\n EditorMessage,\n 'id' | 'defaultMessage' | 'description'\n>\nexport interface EditorSearch {\n value: string\n onValueChange: (value: string) => void\n}\nexport interface EditorMessageRenderState {\n selected: boolean\n}\nexport interface MessageListProps<\n Message extends EditorViewMessage = EditorViewMessage,\n> extends ViewOptions {\n messages: readonly Message[]\n selectedId?: string\n onSelect: (id: string) => void\n search?: EditorSearch\n /** Independent description-only search, conjunctive with general search. */\n descriptionSearch?: EditorSearch\n loading?: boolean\n pagination?: ReactNode\n /** Summary or controls between search and the loaded rows. */\n listSummary?: ReactNode\n /** Noninteractive content inside the design-system selection control. */\n renderMessage?: (\n message: Message,\n state: EditorMessageRenderState\n ) => ReactNode\n /** Interactive actions rendered beside, never inside, the selection control. */\n renderMessageActions?: (\n message: Message,\n state: EditorMessageRenderState\n ) => ReactNode\n}\n/** Controlled list: it never filters, paginates, fetches, or changes selection itself. */\nexport function MessageList<\n Message extends EditorViewMessage = EditorViewMessage,\n>({\n messages,\n selectedId,\n onSelect,\n search,\n descriptionSearch,\n loading = false,\n pagination,\n listSummary,\n renderMessage,\n renderMessageActions,\n labels,\n}: MessageListProps<Message>): ReactNode {\n const {TextInput, MessageRow} = useEditorDesignSystem()\n const text = resolveLabels(labels)\n const searchId = useId()\n const descriptionSearchId = useId()\n return (\n <nav aria-label={text.messages} aria-busy={loading}>\n {search && (\n <div>\n <label htmlFor={searchId}>{text.search}</label>\n <TextInput\n id={searchId}\n type=\"search\"\n value={search.value}\n onValueChange={search.onValueChange}\n aria-label={text.search}\n placeholder={text.search}\n />\n </div>\n )}\n {descriptionSearch && (\n <div>\n <label htmlFor={descriptionSearchId}>{text.descriptionSearch}</label>\n <TextInput\n id={descriptionSearchId}\n type=\"search\"\n value={descriptionSearch.value}\n onValueChange={descriptionSearch.onValueChange}\n aria-label={text.descriptionSearch}\n placeholder={text.descriptionSearch}\n />\n </div>\n )}\n {listSummary}\n {loading && (\n <p>\n <output>{text.loading}</output>\n </p>\n )}\n <ul>\n {messages.map(message => {\n const state = {selected: message.id === selectedId}\n return (\n <li key={message.id}>\n <MessageRow\n selected={state.selected}\n onSelect={() => onSelect(message.id)}\n >\n {renderMessage ? (\n renderMessage(message, state)\n ) : (\n <>\n <span>{message.defaultMessage}</span>{' '}\n <code>{message.id}</code>\n </>\n )}\n </MessageRow>\n {renderMessageActions?.(message, state)}\n </li>\n )\n })}\n </ul>\n {!loading && messages.length === 0 && (\n <p>\n <output>{text.noMessages}</output>\n </p>\n )}\n {pagination}\n </nav>\n )\n}\nexport interface SourceMessageProps extends ViewOptions {\n message: EditorViewMessage\n preview?: ReactNode\n context?: ReactNode\n}\nexport function SourceMessage({\n message,\n preview,\n context,\n labels,\n}: SourceMessageProps): ReactNode {\n const {Panel} = useEditorDesignSystem()\n const text = resolveLabels(labels)\n return (\n <Panel kind=\"source\" label={text.source}>\n <h2>{text.source}</h2>\n <code>{message.id}</code>\n {preview ?? <pre>{message.defaultMessage}</pre>}\n {message.description && <p>{message.description}</p>}\n {context}\n </Panel>\n )\n}\nexport type TranslationFieldDraft = Pick<\n TranslationDraftState,\n | 'value'\n | 'validationError'\n | 'changed'\n | 'isSaving'\n | 'saveError'\n | 'saved'\n | 'setTranslation'\n | 'reset'\n>\nexport interface TranslationFieldProps extends ViewOptions {\n locale: string\n /** Human-readable label; defaults to the locale identifier. */\n label?: string\n source: string\n draft: TranslationFieldDraft\n /** The caller owns confirmation, context, persistence, and receipt presentation. */\n onSave?: () => void\n /** Overrides the default copy/reset/save actions, including with null. */\n actions?: ReactNode\n preview?: ReactNode\n minRows?: number\n maxRows?: number\n}\nexport function TranslationField({\n locale,\n label = locale,\n source,\n draft,\n onSave,\n actions,\n preview,\n minRows = 1,\n maxRows = 10,\n labels,\n}: TranslationFieldProps): ReactNode {\n const {Panel, TextArea, Button} = useEditorDesignSystem()\n const text = resolveLabels(labels)\n const id = useId()\n const errorId = `${id}-error`\n const statusId = `${id}-status`\n const validation = draft.validationError\n ? text.validation[draft.validationError]\n : null\n const error = validation ?? draft.saveError?.message\n return (\n <Panel kind=\"translation\" label={label}>\n <label htmlFor={id}>{label}</label>\n <TextArea\n id={id}\n value={draft.value}\n onValueChange={draft.setTranslation}\n aria-invalid={!!validation}\n aria-describedby={error ? `${errorId} ${statusId}` : statusId}\n minRows={minRows}\n maxRows={maxRows}\n />\n {error && (\n <p id={errorId} role=\"alert\">\n {error}\n </p>\n )}\n <p>\n <output id={statusId}>\n {draft.isSaving\n ? text.saving\n : draft.saved\n ? text.saved\n : draft.changed\n ? text.unsaved\n : text.unchanged}\n </output>\n </p>\n {preview}\n {actions !== undefined ? (\n actions\n ) : (\n <div>\n <Button\n variant=\"secondary\"\n disabled={draft.isSaving}\n onPress={() => draft.setTranslation(source)}\n >\n {text.copySource}\n </Button>\n <Button\n variant=\"secondary\"\n disabled={!draft.changed || draft.isSaving}\n onPress={draft.reset}\n >\n {text.reset}\n </Button>\n {onSave && (\n <Button\n variant=\"primary\"\n disabled={\n !draft.changed || !!draft.validationError || draft.isSaving\n }\n onPress={onSave}\n >\n {draft.isSaving ? text.saving : text.save}\n </Button>\n )}\n </div>\n )}\n </Panel>\n )\n}\nexport type EditorTranslation = Omit<TranslationFieldProps, 'source'>\nexport interface TranslationEditorViewProps<\n Message extends EditorViewMessage = EditorViewMessage,\n> extends MessageListProps<Message> {\n /** May be outside the loaded list, e.g. during a server-side page change. */\n selectedMessage?: EditorViewMessage\n translations: readonly EditorTranslation[]\n filters?: ReactNode\n context?: ReactNode\n sourcePreview?: ReactNode\n notice?: ReactNode\n sidebar?: ReactNode\n /** Replaces the no-selection status; null suppresses it. */\n emptyState?: ReactNode\n /** Wrap the generated locale fields without recreating their wiring. */\n renderTranslations?: (fields: ReactNode) => ReactNode\n /** Wrap the entire detail region, including notices and the empty state. */\n renderContent?: (content: ReactNode) => ReactNode\n}\n/** A stateless composition over caller-owned navigation and locale drafts. */\nexport function TranslationEditorView<\n Message extends EditorViewMessage = EditorViewMessage,\n>({\n selectedMessage,\n selectedId = selectedMessage?.id,\n translations,\n filters,\n context,\n sourcePreview,\n notice,\n sidebar,\n emptyState,\n renderTranslations,\n renderContent,\n labels,\n ...list\n}: TranslationEditorViewProps<Message>): ReactNode {\n const {Layout} = useEditorDesignSystem()\n const text = resolveLabels(labels)\n const fields = selectedMessage\n ? translations.map(translation => (\n <TranslationField\n {...translation}\n key={`${selectedMessage.id}:${translation.locale}`}\n source={selectedMessage.defaultMessage}\n labels={{\n ...labels,\n ...translation.labels,\n validation: {\n ...labels?.validation,\n ...translation.labels?.validation,\n },\n }}\n />\n ))\n : null\n const content = (\n <>\n {notice}\n {selectedMessage ? (\n <>\n <SourceMessage\n message={selectedMessage}\n preview={sourcePreview}\n context={context}\n labels={labels}\n />\n {renderTranslations ? renderTranslations(fields) : fields}\n </>\n ) : emptyState !== undefined ? (\n emptyState\n ) : (\n <p>\n <output>{text.noSelection}</output>\n </p>\n )}\n </>\n )\n return (\n <Layout\n toolbar={filters}\n navigation={\n <MessageList {...list} selectedId={selectedId} labels={labels} />\n }\n content={renderContent ? renderContent(content) : content}\n sidebar={sidebar}\n />\n )\n}\n"],"mappings":"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"}
|