@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 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 | Output callback |
303
- | -------------------------------------- | ------------------------------------------------------------------------------------------ | ------------------------------------ |
304
- | `Button` / `EditorButtonProps` | `children`, `variant`, optional `disabled` | `onPress(): void` |
305
- | `TextInput` / `EditorTextInputProps` | `id`, `value`, `type` (`text` or `search`), optional disabled/error-description attributes | `onValueChange(value: string): void` |
306
- | `TextArea` / `EditorTextAreaProps` | `id`, `value`, optional `rows` (default six), disabled/error-description attributes | `onValueChange(value: string): void` |
307
- | `MessageRow` / `EditorMessageRowProps` | `children`, controlled `selected` | `onSelect(): void` |
308
- | `Panel` / `EditorPanelProps` | `children`, accessible `label`, `kind` (`source` or `translation`) | None; layout only |
309
- | `Layout` / `EditorLayoutProps` | `toolbar`, `navigation`, `content`, optional `sidebar` nodes | None; layout only |
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 the ID's copy control. `labels` overrides
397
- `title`, `id`, `description`, `catalogs`, and `locations`. A missing message clears
398
- the content; absent metadata sections are omitted.
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 pinned Testcontainers browser.
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
- bazel test //packages/editor/vrt:e2e_test --test_output=errors
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 requires Docker and runs manually, locally, and uncached.
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
- `bazel test //packages/editor/vrt:component_test --test_output=errors` runs
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
- manual browser targets. Screenshot baselines and updates remain in `visual_test`.
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
- export type { EditorMessage, EditorOptions, EditorProps, EditorState, MessageProps, MessageStatus, ParsedMessage, SourceLocation, TranslatedMessage, TranslationDraftState, TranslationEditorOptions, TranslationEditorState, TranslationSaveResult, TranslationSaveSnapshot, TranslationUpdate, TranslationValidationError };
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
- return messages.filter((message) => [
31
- message.id,
32
- message.defaultMessage,
33
- message.translatedMessage,
34
- message.description ?? ""
35
- ].some((value) => value.toLowerCase().includes(search)));
36
- }, [messages, query]);
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 translated = !!getDraft(message.id).baseline;
169
- return (!catalog || message.catalogs?.includes(catalog)) && (status === "all" || (status === "translated" ? translated : !translated));
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.5.2",
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.18"
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
- /** Visible rows when supported by the control; defaults to six. */
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: string;
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: ({ onValueChange, rows = 6, ...props }) => /* @__PURE__ */ jsx("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 summary = active.length === 0 ? text.empty : active.length === 1 ? getLocaleLabel(active[0]) : text.selected(active.length);
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(summary),
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__ */ jsx("li", { children: catalog }, index)) }) })] }),
342
- !!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) => /* @__PURE__ */ jsx("li", { children: renderLocation(location) }, index)) }) })] })
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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"}