@focus-reactive/payload-plugin-translator 0.7.1 → 0.8.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.
Files changed (141) hide show
  1. package/README.md +18 -3
  2. package/dist/client/entities/translation/api/mutations/useDismissStaleness.d.ts +12 -0
  3. package/dist/client/entities/translation/api/mutations/useDismissStaleness.js +40 -0
  4. package/dist/client/entities/translation/api/mutations/useQueueDocumentTranslation.js +6 -0
  5. package/dist/client/entities/translation/api/mutations/useRunDocumentTranslation.js +6 -0
  6. package/dist/client/entities/translation/api/queries/useDocumentStaleness.d.ts +17 -0
  7. package/dist/client/entities/translation/api/queries/useDocumentStaleness.js +35 -0
  8. package/dist/client/entities/translation/api/queries/useDocumentTranslation.js +23 -1
  9. package/dist/client/entities/translation/index.d.ts +12 -5
  10. package/dist/client/entities/translation/index.js +10 -5
  11. package/dist/client/entities/translation/model/panelStatus.d.ts +60 -0
  12. package/dist/client/entities/translation/model/panelStatus.js +101 -0
  13. package/dist/client/entities/translation/model/statusRows.d.ts +42 -0
  14. package/dist/client/entities/translation/model/statusRows.js +79 -0
  15. package/dist/client/entities/translation/model/types.d.ts +11 -0
  16. package/dist/client/entities/translation/model/types.js +1 -1
  17. package/dist/client/entities/translation/ui/ActionButton/ActionButton.d.ts +14 -0
  18. package/dist/client/entities/translation/ui/ActionButton/ActionButton.js +28 -0
  19. package/dist/client/entities/translation/ui/ActionButton/index.d.ts +1 -0
  20. package/dist/client/entities/translation/ui/ActionButton/index.js +3 -0
  21. package/dist/client/entities/translation/ui/ActionButton/styles.module.scss +11 -0
  22. package/dist/client/entities/translation/ui/PanelStatusMarker/PanelStatusMarker.d.ts +12 -0
  23. package/dist/client/entities/translation/ui/PanelStatusMarker/PanelStatusMarker.js +26 -0
  24. package/dist/client/entities/translation/ui/PanelStatusMarker/index.d.ts +1 -0
  25. package/dist/client/entities/translation/ui/PanelStatusMarker/index.js +3 -0
  26. package/dist/client/entities/translation/ui/PanelStatusMarker/styles.module.scss +12 -0
  27. package/dist/client/entities/translation/ui/TranslationDirection/styles.module.scss +2 -2
  28. package/dist/client/entities/translation/ui/TranslationStatusList/StatusBadge.d.ts +10 -0
  29. package/dist/client/entities/translation/ui/TranslationStatusList/StatusBadge.js +27 -0
  30. package/dist/client/entities/translation/ui/TranslationStatusList/TranslationStatusList.d.ts +14 -0
  31. package/dist/client/entities/translation/ui/TranslationStatusList/TranslationStatusList.js +194 -0
  32. package/dist/client/entities/translation/ui/TranslationStatusList/index.d.ts +1 -0
  33. package/dist/client/entities/translation/ui/TranslationStatusList/index.js +3 -0
  34. package/dist/client/entities/translation/ui/TranslationStatusList/styles.module.scss +72 -0
  35. package/dist/client/features/collection-translation-form/ui/CollectionTranslationForm.js +17 -21
  36. package/dist/client/features/collection-translation-form/ui/styles.module.scss +14 -14
  37. package/dist/client/features/collection-translation-popup/ui/CollectionTranslationPopup.d.ts +4 -2
  38. package/dist/client/features/collection-translation-popup/ui/CollectionTranslationPopup.js +9 -9
  39. package/dist/client/features/collection-translation-popup/ui/styles.module.scss +5 -3
  40. package/dist/client/features/collection-translation-progress/ui/CollectionTranslationProgress.d.ts +5 -1
  41. package/dist/client/features/collection-translation-progress/ui/CollectionTranslationProgress.js +100 -93
  42. package/dist/client/features/collection-translation-progress/ui/styles.module.scss +52 -3
  43. package/dist/client/features/open-document-translation-popup/ui/OpenDocumentTranslationPopup.d.ts +4 -1
  44. package/dist/client/features/open-document-translation-popup/ui/OpenDocumentTranslationPopup.js +16 -5
  45. package/dist/client/features/open-document-translation-popup/ui/styles.module.scss +5 -3
  46. package/dist/client/features/translate-document-form/ui/DocumentTranslationForm.js +5 -2
  47. package/dist/client/features/translate-document-form/ui/styles.module.scss +8 -2
  48. package/dist/client/shared/lib/assets/icons/AlertTriangleIcon.d.ts +1 -0
  49. package/dist/client/shared/lib/assets/icons/AlertTriangleIcon.js +14 -0
  50. package/dist/client/shared/lib/assets/icons/CheckIcon.d.ts +1 -0
  51. package/dist/client/shared/lib/assets/icons/CheckIcon.js +17 -0
  52. package/dist/client/shared/lib/assets/icons/CloseIcon.d.ts +1 -0
  53. package/dist/client/shared/lib/assets/icons/CloseIcon.js +16 -0
  54. package/dist/client/shared/ui/Button/Button.js +2 -1
  55. package/dist/client/shared/ui/ColorIndicator/ColorIndicator.d.ts +1 -1
  56. package/dist/client/shared/ui/ColorIndicator/styles.module.scss +4 -0
  57. package/dist/client/shared/ui/Label/Label.d.ts +2 -0
  58. package/dist/client/shared/ui/Label/Label.js +3 -2
  59. package/dist/client/shared/ui/Label/styles.module.scss +11 -0
  60. package/dist/client/shared/ui/Loading/Loading.d.ts +6 -2
  61. package/dist/client/shared/ui/Loading/Loading.js +4 -2
  62. package/dist/client/shared/ui/Loading/barCount.d.ts +3 -0
  63. package/dist/client/shared/ui/Loading/barCount.js +11 -0
  64. package/dist/client/shared/ui/Loading/styles.module.scss +20 -27
  65. package/dist/client/shared/ui/Popup/Popup.js +3 -0
  66. package/dist/client/shared/ui/Select/Select.js +1 -1
  67. package/dist/client/shared/ui/form/FormSelect/FormSelect.js +1 -0
  68. package/dist/client/widgets/bulk-translation-dashboard/ui/BulkTranslationDashboard.js +37 -17
  69. package/dist/client/widgets/bulk-translation-dashboard/ui/styles.module.scss +27 -0
  70. package/dist/client/widgets/translate-document/ui/TranslateDocument.js +53 -27
  71. package/dist/client/widgets/translate-document/ui/styles.module.scss +27 -0
  72. package/dist/composition/levels/useDocTranslationApi.d.ts +3 -2
  73. package/dist/composition/levels/useDocTranslationApi.js +6 -3
  74. package/dist/core/field-traversal/types.d.ts +4 -2
  75. package/dist/core/provenance/ProvenanceStore.interface.d.ts +18 -0
  76. package/dist/core/provenance/index.d.ts +1 -0
  77. package/dist/core/provenance/index.js +1 -1
  78. package/dist/core/provenance/staleness.d.ts +19 -0
  79. package/dist/core/provenance/staleness.js +21 -0
  80. package/dist/core/translation-pipeline/stages/data-reconciler/DataReconciler.js +33 -10
  81. package/dist/plugin.js +2 -1
  82. package/dist/server/features/_lib/sourceDocument.d.ts +9 -0
  83. package/dist/server/features/_lib/sourceDocument.js +16 -0
  84. package/dist/server/features/createTranslationRoutes.d.ts +12 -4
  85. package/dist/server/features/createTranslationRoutes.js +15 -5
  86. package/dist/server/features/get-document-status/model.js +7 -2
  87. package/dist/server/features/staleness/dismissStaleness.handler.d.ts +8 -0
  88. package/dist/server/features/staleness/dismissStaleness.handler.js +27 -0
  89. package/dist/server/features/staleness/getDocumentStaleness.handler.d.ts +12 -0
  90. package/dist/server/features/staleness/getDocumentStaleness.handler.js +43 -0
  91. package/dist/server/features/staleness/index.d.ts +2 -0
  92. package/dist/server/features/staleness/index.js +3 -0
  93. package/dist/server/features/staleness/model.d.ts +52 -0
  94. package/dist/server/features/staleness/model.js +17 -0
  95. package/dist/server/features/staleness/route.d.ts +7 -0
  96. package/dist/server/features/staleness/route.js +21 -0
  97. package/dist/server/features/staleness/service.d.ts +15 -0
  98. package/dist/server/features/staleness/service.js +78 -0
  99. package/dist/server/features/translate-document/handler.js +22 -8
  100. package/dist/server/modules/provenance/PayloadProvenanceStore.d.ts +2 -0
  101. package/dist/server/modules/provenance/PayloadProvenanceStore.js +37 -14
  102. package/dist/server/modules/translation-levels/PluginConfigBuilder.d.ts +3 -0
  103. package/dist/server/modules/translation-levels/PluginConfigBuilder.js +2 -0
  104. package/dist/server/modules/translation-levels/types.d.ts +3 -0
  105. package/dist/server/shared/http/index.d.ts +1 -0
  106. package/dist/server/shared/http/index.js +1 -0
  107. package/dist/server/shared/http/toClientErrorMessage.d.ts +15 -0
  108. package/dist/server/shared/http/toClientErrorMessage.js +26 -0
  109. package/dist/server/shared/index.d.ts +1 -1
  110. package/dist/server/shared/index.js +1 -1
  111. package/package.json +1 -1
  112. package/dist/client/entities/translation/ui/CompletedTranslationStatus/CompletedTranslationStatus.d.ts +0 -7
  113. package/dist/client/entities/translation/ui/CompletedTranslationStatus/CompletedTranslationStatus.js +0 -45
  114. package/dist/client/entities/translation/ui/CompletedTranslationStatus/index.d.ts +0 -1
  115. package/dist/client/entities/translation/ui/CompletedTranslationStatus/index.js +0 -3
  116. package/dist/client/entities/translation/ui/CompletedTranslationStatus/styles.module.scss +0 -3
  117. package/dist/client/entities/translation/ui/FailedTranslationStatus/FailedTranslationStatus.d.ts +0 -9
  118. package/dist/client/entities/translation/ui/FailedTranslationStatus/FailedTranslationStatus.js +0 -54
  119. package/dist/client/entities/translation/ui/FailedTranslationStatus/index.d.ts +0 -1
  120. package/dist/client/entities/translation/ui/FailedTranslationStatus/index.js +0 -3
  121. package/dist/client/entities/translation/ui/FailedTranslationStatus/styles.module.scss +0 -3
  122. package/dist/client/entities/translation/ui/PendingTranslationStatus/PendingTranslationStatus.d.ts +0 -11
  123. package/dist/client/entities/translation/ui/PendingTranslationStatus/PendingTranslationStatus.js +0 -83
  124. package/dist/client/entities/translation/ui/PendingTranslationStatus/index.d.ts +0 -1
  125. package/dist/client/entities/translation/ui/PendingTranslationStatus/index.js +0 -3
  126. package/dist/client/entities/translation/ui/RunningTranslationStatus/RunningTranslationStatus.d.ts +0 -10
  127. package/dist/client/entities/translation/ui/RunningTranslationStatus/RunningTranslationStatus.js +0 -66
  128. package/dist/client/entities/translation/ui/RunningTranslationStatus/index.d.ts +0 -1
  129. package/dist/client/entities/translation/ui/RunningTranslationStatus/index.js +0 -3
  130. package/dist/client/features/document-translation-progress-failed/index.d.ts +0 -1
  131. package/dist/client/features/document-translation-progress-failed/index.js +0 -3
  132. package/dist/client/features/document-translation-progress-failed/ui/DocumentTranslationProgressFailed.d.ts +0 -6
  133. package/dist/client/features/document-translation-progress-failed/ui/DocumentTranslationProgressFailed.js +0 -28
  134. package/dist/client/features/document-translation-progress-pending/index.d.ts +0 -1
  135. package/dist/client/features/document-translation-progress-pending/index.js +0 -3
  136. package/dist/client/features/document-translation-progress-pending/ui/DocumentTranslationProgressPending.d.ts +0 -6
  137. package/dist/client/features/document-translation-progress-pending/ui/DocumentTranslationProgressPending.js +0 -44
  138. package/dist/client/features/document-translation-progress-running/index.d.ts +0 -1
  139. package/dist/client/features/document-translation-progress-running/index.js +0 -3
  140. package/dist/client/features/document-translation-progress-running/ui/DocumentTranslationProgressRunning.d.ts +0 -6
  141. package/dist/client/features/document-translation-progress-running/ui/DocumentTranslationProgressRunning.js +0 -28
@@ -0,0 +1,11 @@
1
+ // `button.action` (element + class) outranks the Button variant's own single-class rules, so the
2
+ // neutral colour + visible border win regardless of stylesheet order.
3
+ button.action {
4
+ color: var(--theme-elevation-600, #666);
5
+ border: 1px solid var(--theme-elevation-150, #d9d9d9);
6
+ }
7
+
8
+ button.action:hover:not(:disabled) {
9
+ color: var(--theme-text, #222);
10
+ border-color: var(--theme-elevation-300, #b3b3b3);
11
+ }
@@ -0,0 +1,12 @@
1
+ import type { MarkerTone } from "../../model/panelStatus";
2
+ /**
3
+ * The aggregate status dot overlaid on a translate trigger's top-right corner. Uses the SAME
4
+ * coloured-dot vocabulary as the popup's per-locale status list ({@link MARKER_DOT}), so the panel
5
+ * badge and the popup rows read as one system. Shared by the document and collection triggers.
6
+ *
7
+ * Colour is not the sole cue: the host button carries a `title` + `aria-label` naming the exact
8
+ * state for assistive tech (this dot is `aria-hidden`), and in-progress carries a pulse.
9
+ */
10
+ export declare function PanelStatusMarker({ tone }: {
11
+ tone?: MarkerTone;
12
+ }): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,26 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import ColorIndicator from "../../../../shared/ui/ColorIndicator";
3
+ import { MARKER_DOT } from "../../model/panelStatus";
4
+ import styles from "./styles.module.scss";
5
+ /**
6
+ * The aggregate status dot overlaid on a translate trigger's top-right corner. Uses the SAME
7
+ * coloured-dot vocabulary as the popup's per-locale status list ({@link MARKER_DOT}), so the panel
8
+ * badge and the popup rows read as one system. Shared by the document and collection triggers.
9
+ *
10
+ * Colour is not the sole cue: the host button carries a `title` + `aria-label` naming the exact
11
+ * state for assistive tech (this dot is `aria-hidden`), and in-progress carries a pulse.
12
+ */ export function PanelStatusMarker({ tone }) {
13
+ if (!tone) {
14
+ return null;
15
+ }
16
+ return /*#__PURE__*/ _jsx("span", {
17
+ className: styles.marker,
18
+ "aria-hidden": "true",
19
+ children: /*#__PURE__*/ _jsx(ColorIndicator, {
20
+ $color: MARKER_DOT[tone].color,
21
+ $animated: MARKER_DOT[tone].animated
22
+ })
23
+ });
24
+ }
25
+
26
+ //# sourceMappingURL=PanelStatusMarker.js.map
@@ -0,0 +1 @@
1
+ export { PanelStatusMarker } from "./PanelStatusMarker";
@@ -0,0 +1,3 @@
1
+ export { PanelStatusMarker } from "./PanelStatusMarker";
2
+
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,12 @@
1
+ // Aggregate status dot at the trigger's top-right corner. Positioning + a ring in the panel
2
+ // background so the dot reads as a badge against the button fill.
3
+ .marker {
4
+ position: absolute;
5
+ top: -2px;
6
+ right: -2px;
7
+ display: flex;
8
+ }
9
+
10
+ .marker > div {
11
+ box-shadow: 0 0 0 2px var(--theme-elevation-0, #fff);
12
+ }
@@ -1,8 +1,8 @@
1
1
  .container {
2
2
  display: flex;
3
3
  align-items: center;
4
- gap: 0.5rem;
5
- font-size: 1rem;
4
+ gap: 0.4rem;
5
+ font-size: 0.9rem;
6
6
  font-weight: 500;
7
7
  }
8
8
 
@@ -0,0 +1,10 @@
1
+ import type { TranslationRowState } from "../../model/statusRows";
2
+ /** Human label per state — also the row's accessible name (state is not conveyed by colour alone). */
3
+ export declare const STATE_LABEL: Record<TranslationRowState, string>;
4
+ /**
5
+ * Small per-row status dot. The colour is a secondary cue only — every row carries the state as
6
+ * text ({@link STATE_LABEL}) in its meta line, so the dot never conveys state by colour alone.
7
+ */
8
+ export declare function StatusBadge({ state }: {
9
+ state: TranslationRowState;
10
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,27 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import ColorIndicator from "../../../../shared/ui/ColorIndicator";
3
+ import { STATE_DOT } from "../../model/statusRows";
4
+ import styles from "./styles.module.scss";
5
+ /** Human label per state — also the row's accessible name (state is not conveyed by colour alone). */ export const STATE_LABEL = {
6
+ failed: "Failed",
7
+ running: "Translating",
8
+ pending: "Queued",
9
+ stale: "Out of date",
10
+ translated: "Translated"
11
+ };
12
+ /**
13
+ * Small per-row status dot. The colour is a secondary cue only — every row carries the state as
14
+ * text ({@link STATE_LABEL}) in its meta line, so the dot never conveys state by colour alone.
15
+ */ export function StatusBadge({ state }) {
16
+ const dot = STATE_DOT[state];
17
+ return /*#__PURE__*/ _jsx("span", {
18
+ className: styles.badge,
19
+ "aria-hidden": "true",
20
+ children: /*#__PURE__*/ _jsx(ColorIndicator, {
21
+ $color: dot.color,
22
+ $animated: dot.animated
23
+ })
24
+ });
25
+ }
26
+
27
+ //# sourceMappingURL=StatusBadge.js.map
@@ -0,0 +1,14 @@
1
+ import type { CollectionSlug } from "payload";
2
+ import type { TranslationStatusRow } from "../../model/statusRows";
3
+ type TranslationStatusListProps = {
4
+ rows: TranslationStatusRow[];
5
+ collection: CollectionSlug;
6
+ id: string;
7
+ };
8
+ /**
9
+ * The unified per-locale status list: one consistent row per target locale
10
+ * (`[badge] [en→fr] [when] [action]`). Replaces the old corner chip, the "out of date" column, and
11
+ * the separate running/pending/failed chips. Row actions reuse the existing mutation hooks.
12
+ */
13
+ export declare function TranslationStatusList({ rows, collection, id }: TranslationStatusListProps): import("react/jsx-runtime").JSX.Element;
14
+ export {};
@@ -0,0 +1,194 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { toast } from "@payloadcms/ui";
3
+ import { useState } from "react";
4
+ import { CheckIcon } from "../../../../shared/lib/assets/icons/CheckIcon";
5
+ import { CloseIcon } from "../../../../shared/lib/assets/icons/CloseIcon";
6
+ import { ReloadIcon } from "../../../../shared/lib/assets/icons/ReloadIcon";
7
+ import { useCancelDocumentTranslation } from "../../api/mutations/useCancelDocumentTranslation";
8
+ import { useDismissStaleness } from "../../api/mutations/useDismissStaleness";
9
+ import { useQueueDocumentTranslation } from "../../api/mutations/useQueueDocumentTranslation";
10
+ import { useRunDocumentTranslation } from "../../api/mutations/useRunDocumentTranslation";
11
+ import { ActionButton } from "../ActionButton";
12
+ import { TranslationDirection } from "../TranslationDirection";
13
+ import { STATE_LABEL, StatusBadge } from "./StatusBadge";
14
+ import styles from "./styles.module.scss";
15
+ const shortDate = (iso)=>new Date(iso).toLocaleDateString(undefined, {
16
+ month: "short",
17
+ day: "numeric"
18
+ });
19
+ const fullDate = (iso)=>new Date(iso).toLocaleString(undefined, {
20
+ year: "numeric",
21
+ month: "short",
22
+ day: "numeric",
23
+ hour: "2-digit",
24
+ minute: "2-digit"
25
+ });
26
+ const withToast = async (run, fallback)=>{
27
+ try {
28
+ await run();
29
+ } catch (e) {
30
+ toast.error(e instanceof Error ? e.message : fallback);
31
+ }
32
+ };
33
+ /**
34
+ * The unified per-locale status list: one consistent row per target locale
35
+ * (`[badge] [en→fr] [when] [action]`). Replaces the old corner chip, the "out of date" column, and
36
+ * the separate running/pending/failed chips. Row actions reuse the existing mutation hooks.
37
+ */ export function TranslationStatusList({ rows, collection, id }) {
38
+ const runApi = useRunDocumentTranslation();
39
+ const cancelApi = useCancelDocumentTranslation();
40
+ const dismissApi = useDismissStaleness();
41
+ const queueApi = useQueueDocumentTranslation();
42
+ const [dismissing, setDismissing] = useState(new Set());
43
+ const dismiss = async (targetLocale)=>{
44
+ setDismissing((prev)=>new Set(prev).add(targetLocale));
45
+ await withToast(()=>dismissApi.mutateAsync({
46
+ collection,
47
+ id,
48
+ target_lng: targetLocale
49
+ }), "Failed to dismiss out-of-date notice");
50
+ setDismissing((prev)=>{
51
+ const next = new Set(prev);
52
+ next.delete(targetLocale);
53
+ return next;
54
+ });
55
+ };
56
+ // Re-translate one locale in place: a fresh source→target job (overwrite). Reuses the same queue
57
+ // endpoint as the form; onSuccess invalidation refreshes this list.
58
+ const reTranslate = (row)=>withToast(()=>queueApi.mutateAsync({
59
+ source_lng: row.sourceLocale,
60
+ target_lng: row.targetLocale,
61
+ collection_slug: collection,
62
+ collection_id: [
63
+ id
64
+ ],
65
+ strategy: "overwrite"
66
+ }), "Failed to queue translation");
67
+ const retry = (row)=>row.jobId && withToast(()=>runApi.mutateAsync({
68
+ id: row.jobId
69
+ }), "Error retrying");
70
+ const runNow = (row)=>row.jobId && withToast(()=>runApi.mutateAsync({
71
+ id: row.jobId
72
+ }), "Error running");
73
+ const cancel = (row)=>row.jobId && withToast(()=>cancelApi.mutateAsync({
74
+ id: row.jobId
75
+ }), "Error cancelling");
76
+ const renderAction = (row)=>{
77
+ switch(row.state){
78
+ case "failed":
79
+ return /*#__PURE__*/ _jsx("span", {
80
+ className: styles.actions,
81
+ children: /*#__PURE__*/ _jsx(ActionButton, {
82
+ icon: /*#__PURE__*/ _jsx(ReloadIcon, {}),
83
+ title: "Retry",
84
+ onClick: ()=>retry(row),
85
+ loading: runApi.isPending
86
+ })
87
+ });
88
+ case "running":
89
+ return /*#__PURE__*/ _jsx("span", {
90
+ className: styles.actions,
91
+ children: /*#__PURE__*/ _jsx(ActionButton, {
92
+ icon: /*#__PURE__*/ _jsx(CloseIcon, {}),
93
+ title: "Cancel",
94
+ onClick: ()=>cancel(row),
95
+ loading: cancelApi.isPending
96
+ })
97
+ });
98
+ case "pending":
99
+ return /*#__PURE__*/ _jsxs("span", {
100
+ className: styles.actions,
101
+ children: [
102
+ /*#__PURE__*/ _jsx(ActionButton, {
103
+ icon: /*#__PURE__*/ _jsx(ReloadIcon, {}),
104
+ title: "Run now",
105
+ onClick: ()=>runNow(row),
106
+ loading: runApi.isPending
107
+ }),
108
+ /*#__PURE__*/ _jsx(ActionButton, {
109
+ icon: /*#__PURE__*/ _jsx(CloseIcon, {}),
110
+ title: "Cancel",
111
+ onClick: ()=>cancel(row),
112
+ loading: cancelApi.isPending
113
+ })
114
+ ]
115
+ });
116
+ case "stale":
117
+ return /*#__PURE__*/ _jsxs("span", {
118
+ className: styles.actions,
119
+ children: [
120
+ /*#__PURE__*/ _jsx(ActionButton, {
121
+ icon: /*#__PURE__*/ _jsx(ReloadIcon, {}),
122
+ title: `Re-translate from ${row.sourceLocale}`,
123
+ onClick: ()=>reTranslate(row),
124
+ loading: queueApi.isPending
125
+ }),
126
+ /*#__PURE__*/ _jsx(ActionButton, {
127
+ icon: /*#__PURE__*/ _jsx(CheckIcon, {}),
128
+ title: "Keep as is — hide until the source changes again",
129
+ onClick: ()=>dismiss(row.targetLocale),
130
+ disabled: dismissing.has(row.targetLocale),
131
+ loading: dismissing.has(row.targetLocale)
132
+ })
133
+ ]
134
+ });
135
+ default:
136
+ return null;
137
+ }
138
+ };
139
+ return /*#__PURE__*/ _jsx("ul", {
140
+ className: styles.list,
141
+ children: rows.map((row)=>/*#__PURE__*/ _jsxs("li", {
142
+ className: styles.row,
143
+ "aria-label": `${STATE_LABEL[row.state]}: ${row.sourceLocale} to ${row.targetLocale}` + (row.error ? ` — ${row.error}` : ""),
144
+ children: [
145
+ /*#__PURE__*/ _jsxs("div", {
146
+ className: styles.top,
147
+ children: [
148
+ /*#__PURE__*/ _jsxs("div", {
149
+ className: styles.lead,
150
+ children: [
151
+ /*#__PURE__*/ _jsx(StatusBadge, {
152
+ state: row.state
153
+ }),
154
+ /*#__PURE__*/ _jsx(TranslationDirection, {
155
+ sourceLocale: row.sourceLocale,
156
+ targetLocale: row.targetLocale
157
+ })
158
+ ]
159
+ }),
160
+ renderAction(row)
161
+ ]
162
+ }),
163
+ /*#__PURE__*/ _jsxs("div", {
164
+ className: styles.meta,
165
+ children: [
166
+ /*#__PURE__*/ _jsx("span", {
167
+ children: STATE_LABEL[row.state]
168
+ }),
169
+ row.at && /*#__PURE__*/ _jsxs(_Fragment, {
170
+ children: [
171
+ /*#__PURE__*/ _jsx("span", {
172
+ "aria-hidden": "true",
173
+ children: "·"
174
+ }),
175
+ /*#__PURE__*/ _jsx("time", {
176
+ dateTime: row.at,
177
+ title: fullDate(row.at),
178
+ children: shortDate(row.at)
179
+ })
180
+ ]
181
+ })
182
+ ]
183
+ }),
184
+ row.state === "failed" && row.error && /*#__PURE__*/ _jsx("p", {
185
+ className: styles.error,
186
+ title: row.error,
187
+ children: row.error
188
+ })
189
+ ]
190
+ }, row.targetLocale))
191
+ });
192
+ }
193
+
194
+ //# sourceMappingURL=TranslationStatusList.js.map
@@ -0,0 +1 @@
1
+ export { TranslationStatusList } from "./TranslationStatusList";
@@ -0,0 +1,3 @@
1
+ export { TranslationStatusList } from "./TranslationStatusList";
2
+
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,72 @@
1
+ .list {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: 0.5rem;
5
+ margin: 0;
6
+ padding: 0;
7
+ list-style: none;
8
+ }
9
+
10
+ .row {
11
+ display: flex;
12
+ flex-direction: column;
13
+ gap: 0.125rem;
14
+ font-size: 0.8125rem;
15
+ }
16
+
17
+ // First line: [dot + direction] on the left, action on the right.
18
+ .top {
19
+ display: flex;
20
+ align-items: center;
21
+ justify-content: space-between;
22
+ gap: 0.5rem;
23
+ // Constant first-line height (with or without an action button) keeps vertical rhythm even.
24
+ min-height: 1.5rem;
25
+ }
26
+
27
+ // Dot + direction share one flex line so they centre together regardless of the action's height.
28
+ .lead {
29
+ display: flex;
30
+ align-items: center;
31
+ gap: 0.5rem;
32
+ min-width: 0;
33
+ }
34
+
35
+ .meta {
36
+ display: flex;
37
+ align-items: center;
38
+ gap: 0.35rem;
39
+ // Align under the direction: dot width (8px) + .lead gap (0.5rem).
40
+ padding-left: calc(8px + 0.5rem);
41
+ font-size: 0.6875rem;
42
+ color: var(--theme-elevation-500, #8c8c8c);
43
+ }
44
+
45
+ // Failure reason for a failed row. Sparing red: a 2px accent rule + red dot carry the semantics,
46
+ // the message text stays caption-neutral so the row never becomes a red block. Clamped to 2 lines;
47
+ // full text lives in the native title (and the row's aria-label).
48
+ .error {
49
+ margin: 0.125rem 0 0 calc(8px + 0.5rem);
50
+ padding-left: 0.5rem;
51
+ border-left: 2px solid var(--theme-error-600, #dc2626);
52
+ font-size: 0.6875rem;
53
+ font-weight: 400;
54
+ line-height: 1.35;
55
+ color: var(--theme-elevation-500, #8c8c8c);
56
+ display: -webkit-box;
57
+ -webkit-line-clamp: 2;
58
+ -webkit-box-orient: vertical;
59
+ overflow: hidden;
60
+ }
61
+
62
+ // Inline wrapper so the dot centres on the direction line via .lead's align-items.
63
+ .badge {
64
+ display: inline-flex;
65
+ align-items: center;
66
+ flex-shrink: 0;
67
+ }
68
+
69
+ .actions {
70
+ display: inline-flex;
71
+ gap: 0.25rem;
72
+ }
@@ -16,30 +16,22 @@ export function CollectionTranslationForm({ form, onSubmit, selectedCount, hasDr
16
16
  /*#__PURE__*/ _jsxs("div", {
17
17
  className: styles.row,
18
18
  children: [
19
- /*#__PURE__*/ _jsxs("div", {
20
- className: styles["locale-group"],
21
- children: [
22
- /*#__PURE__*/ _jsx(FormSelectLocale, {
23
- label: "From",
24
- className: styles["select-locale-field"],
25
- size: "md",
26
- name: FORM_FIELDS.SOURCE_LNG
27
- }),
28
- /*#__PURE__*/ _jsx(FormSelectLocale, {
29
- label: "To",
30
- className: styles["select-locale-field"],
31
- size: "md",
32
- name: FORM_FIELDS.TARGET_LNG
33
- })
34
- ]
19
+ /*#__PURE__*/ _jsx(FormSelectLocale, {
20
+ size: "sm",
21
+ label: "From",
22
+ name: FORM_FIELDS.SOURCE_LNG
35
23
  }),
36
- /*#__PURE__*/ _jsx(FormSelectStrategy, {
37
- className: styles["strategy-field"],
38
- name: FORM_FIELDS.STRATEGY,
39
- size: "md"
24
+ /*#__PURE__*/ _jsx(FormSelectLocale, {
25
+ size: "sm",
26
+ label: "To",
27
+ name: FORM_FIELDS.TARGET_LNG
40
28
  })
41
29
  ]
42
30
  }),
31
+ /*#__PURE__*/ _jsx(FormSelectStrategy, {
32
+ size: "sm",
33
+ name: FORM_FIELDS.STRATEGY
34
+ }),
43
35
  hasDrafts && /*#__PURE__*/ _jsx(FormCheckboxPublish, {
44
36
  name: FORM_FIELDS.PUBLISH_ON_TRANSLATION
45
37
  }),
@@ -53,10 +45,14 @@ export function CollectionTranslationForm({ form, onSubmit, selectedCount, hasDr
53
45
  className: styles["submit-button"],
54
46
  onClick: form.handleSubmit(onSubmit),
55
47
  type: "submit",
56
- $size: "md",
48
+ $size: "sm",
57
49
  $isLoading: form.formState.isSubmitting,
58
50
  $startContent: /*#__PURE__*/ _jsx(SendIcon, {}),
59
51
  children: selectedCount ? `Queue ${selectedCount} translations` : "Queue translations"
52
+ }),
53
+ selectedCount === 0 && /*#__PURE__*/ _jsx("p", {
54
+ className: styles.caption,
55
+ children: "Select rows in the list to translate."
60
56
  })
61
57
  ]
62
58
  })
@@ -1,7 +1,7 @@
1
1
  .fieldset {
2
2
  display: flex;
3
3
  flex-direction: column;
4
- gap: 0.75rem;
4
+ gap: 0.5rem;
5
5
  border-width: 0;
6
6
  margin: 0;
7
7
  padding: 0;
@@ -9,23 +9,23 @@
9
9
 
10
10
  .row {
11
11
  display: flex;
12
- align-items: flex-end;
13
- gap: 0.75rem;
12
+ gap: 0.5rem;
14
13
  }
15
14
 
16
- .locale-group {
17
- display: flex;
18
- gap: 1rem;
19
- }
20
-
21
- .select-locale-field {
22
- min-width: 70px;
23
- }
24
-
25
- .strategy-field {
26
- min-width: 150px;
15
+ // From / To share the row equally and are allowed to shrink with the narrow popup.
16
+ .row > * {
17
+ flex: 1 1 0;
18
+ min-width: 0;
27
19
  }
28
20
 
29
21
  .submit-button {
22
+ margin: 0;
30
23
  width: 100%;
31
24
  }
25
+
26
+ // Empty-state hint: only shown when nothing is selected, so the disabled button isn't a dead end.
27
+ .caption {
28
+ margin: 0;
29
+ font-size: 0.6875rem;
30
+ color: var(--theme-elevation-500, #8c8c8c);
31
+ }
@@ -1,7 +1,9 @@
1
1
  import type { PropsWithChildren } from "react";
2
+ import type { PanelStatus } from "../../../entities/translation";
2
3
  type CollectionTranslationPopupProps = PropsWithChildren<{
3
- translationInProgress: boolean;
4
+ /** Aggregate status across the collection's jobs → the corner marker (same as the document trigger). */
5
+ status?: PanelStatus;
4
6
  selectedCount: number;
5
7
  }>;
6
- declare function CollectionTranslationPopup({ children, translationInProgress, selectedCount, }: CollectionTranslationPopupProps): import("react/jsx-runtime").JSX.Element;
8
+ declare function CollectionTranslationPopup({ children, status, selectedCount, }: CollectionTranslationPopupProps): import("react/jsx-runtime").JSX.Element;
7
9
  export default CollectionTranslationPopup;
@@ -1,13 +1,14 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { describePanelStatus, PanelStatusMarker } from "../../../entities/translation";
3
4
  import { LanguageTranslateIcon } from "../../../shared/lib/assets/icons/LanguageTranslateIcon";
4
5
  import { useToggle } from "../../../shared/lib/utils/react/useToggle";
5
6
  import Button from "../../../shared/ui/Button";
6
- import ColorIndicator from "../../../shared/ui/ColorIndicator";
7
7
  import Popup from "../../../shared/ui/Popup";
8
8
  import styles from "./styles.module.scss";
9
- function CollectionTranslationPopup({ children, translationInProgress, selectedCount }) {
9
+ function CollectionTranslationPopup({ children, status, selectedCount }) {
10
10
  const [isPopupOpen, popupOpen] = useToggle();
11
+ const { tone, title } = describePanelStatus(status, "Bulk translation");
11
12
  return /*#__PURE__*/ _jsx(Popup, {
12
13
  $align: "start",
13
14
  onOpenChange: popupOpen.setValue,
@@ -15,16 +16,12 @@ function CollectionTranslationPopup({ children, translationInProgress, selectedC
15
16
  $size: "md",
16
17
  $variant: "outlined-light",
17
18
  className: styles["popup-trigger-button"],
18
- "aria-label": "Open translation options",
19
+ "aria-label": `Open translation options — ${title}`,
20
+ title: title,
19
21
  onClick: popupOpen.setTrue,
20
22
  children: [
21
- translationInProgress && /*#__PURE__*/ _jsx(ColorIndicator, {
22
- title: "Translations In Progress",
23
- $animated: true,
24
- $color: "blue"
25
- }),
26
23
  /*#__PURE__*/ _jsx(LanguageTranslateIcon, {}),
27
- selectedCount > 0 && !translationInProgress && /*#__PURE__*/ _jsxs(_Fragment, {
24
+ selectedCount > 0 && /*#__PURE__*/ _jsxs(_Fragment, {
28
25
  children: [
29
26
  /*#__PURE__*/ _jsx("span", {
30
27
  className: styles.arrow,
@@ -33,6 +30,9 @@ function CollectionTranslationPopup({ children, translationInProgress, selectedC
33
30
  }),
34
31
  selectedCount
35
32
  ]
33
+ }),
34
+ /*#__PURE__*/ _jsx(PanelStatusMarker, {
35
+ tone: tone
36
36
  })
37
37
  ]
38
38
  }),
@@ -1,15 +1,17 @@
1
1
  .popup-content {
2
2
  display: flex;
3
3
  flex-direction: column;
4
- gap: 1rem;
5
- padding: 1.5rem 1rem;
6
- max-width: 350px;
4
+ gap: 0.75rem;
5
+ padding: 0.75rem;
6
+ width: 18rem; // match the document popup — same form shape, so same width
7
+ max-width: calc(100vw - 24px);
7
8
  }
8
9
 
9
10
  .popup-trigger-button {
10
11
  font-size: 1.3rem;
11
12
  display: flex;
12
13
  gap: 0.25rem;
14
+ position: relative; // anchor for the corner status marker
13
15
  }
14
16
 
15
17
  .arrow {
@@ -4,5 +4,9 @@ type CollectionTranslationProgressProps = {
4
4
  data: GroupedCollectionTranslationStatus | undefined;
5
5
  isPending: boolean;
6
6
  };
7
- export declare function CollectionTranslationProgress({ collection, data, isPending, }: CollectionTranslationProgressProps): import("react/jsx-runtime").JSX.Element;
7
+ /**
8
+ * Collection status as a compact list of grouped counts — one `[dot] label … count` row per
9
+ * non-zero group, mirroring the document popup's per-locale list rhythm (dot vocabulary shared).
10
+ */
11
+ export declare function CollectionTranslationProgress({ collection, data, isPending, }: CollectionTranslationProgressProps): import("react/jsx-runtime").JSX.Element | null;
8
12
  export {};