@focus-reactive/payload-plugin-translator 0.7.2 → 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.
- package/README.md +18 -3
- package/dist/client/entities/translation/api/mutations/useDismissStaleness.d.ts +12 -0
- package/dist/client/entities/translation/api/mutations/useDismissStaleness.js +40 -0
- package/dist/client/entities/translation/api/mutations/useQueueDocumentTranslation.js +6 -0
- package/dist/client/entities/translation/api/mutations/useRunDocumentTranslation.js +6 -0
- package/dist/client/entities/translation/api/queries/useDocumentStaleness.d.ts +17 -0
- package/dist/client/entities/translation/api/queries/useDocumentStaleness.js +35 -0
- package/dist/client/entities/translation/api/queries/useDocumentTranslation.js +23 -1
- package/dist/client/entities/translation/index.d.ts +12 -5
- package/dist/client/entities/translation/index.js +10 -5
- package/dist/client/entities/translation/model/panelStatus.d.ts +60 -0
- package/dist/client/entities/translation/model/panelStatus.js +101 -0
- package/dist/client/entities/translation/model/statusRows.d.ts +42 -0
- package/dist/client/entities/translation/model/statusRows.js +79 -0
- package/dist/client/entities/translation/model/types.d.ts +11 -0
- package/dist/client/entities/translation/model/types.js +1 -1
- package/dist/client/entities/translation/ui/ActionButton/ActionButton.d.ts +14 -0
- package/dist/client/entities/translation/ui/ActionButton/ActionButton.js +28 -0
- package/dist/client/entities/translation/ui/ActionButton/index.d.ts +1 -0
- package/dist/client/entities/translation/ui/ActionButton/index.js +3 -0
- package/dist/client/entities/translation/ui/ActionButton/styles.module.scss +11 -0
- package/dist/client/entities/translation/ui/PanelStatusMarker/PanelStatusMarker.d.ts +12 -0
- package/dist/client/entities/translation/ui/PanelStatusMarker/PanelStatusMarker.js +26 -0
- package/dist/client/entities/translation/ui/PanelStatusMarker/index.d.ts +1 -0
- package/dist/client/entities/translation/ui/PanelStatusMarker/index.js +3 -0
- package/dist/client/entities/translation/ui/PanelStatusMarker/styles.module.scss +12 -0
- package/dist/client/entities/translation/ui/TranslationDirection/styles.module.scss +2 -2
- package/dist/client/entities/translation/ui/TranslationStatusList/StatusBadge.d.ts +10 -0
- package/dist/client/entities/translation/ui/TranslationStatusList/StatusBadge.js +27 -0
- package/dist/client/entities/translation/ui/TranslationStatusList/TranslationStatusList.d.ts +14 -0
- package/dist/client/entities/translation/ui/TranslationStatusList/TranslationStatusList.js +194 -0
- package/dist/client/entities/translation/ui/TranslationStatusList/index.d.ts +1 -0
- package/dist/client/entities/translation/ui/TranslationStatusList/index.js +3 -0
- package/dist/client/entities/translation/ui/TranslationStatusList/styles.module.scss +72 -0
- package/dist/client/features/collection-translation-form/ui/CollectionTranslationForm.js +17 -21
- package/dist/client/features/collection-translation-form/ui/styles.module.scss +14 -14
- package/dist/client/features/collection-translation-popup/ui/CollectionTranslationPopup.d.ts +4 -2
- package/dist/client/features/collection-translation-popup/ui/CollectionTranslationPopup.js +9 -9
- package/dist/client/features/collection-translation-popup/ui/styles.module.scss +5 -3
- package/dist/client/features/collection-translation-progress/ui/CollectionTranslationProgress.d.ts +5 -1
- package/dist/client/features/collection-translation-progress/ui/CollectionTranslationProgress.js +100 -93
- package/dist/client/features/collection-translation-progress/ui/styles.module.scss +52 -3
- package/dist/client/features/open-document-translation-popup/ui/OpenDocumentTranslationPopup.d.ts +4 -1
- package/dist/client/features/open-document-translation-popup/ui/OpenDocumentTranslationPopup.js +16 -5
- package/dist/client/features/open-document-translation-popup/ui/styles.module.scss +5 -3
- package/dist/client/features/translate-document-form/ui/DocumentTranslationForm.js +5 -2
- package/dist/client/features/translate-document-form/ui/styles.module.scss +8 -2
- package/dist/client/shared/lib/assets/icons/AlertTriangleIcon.d.ts +1 -0
- package/dist/client/shared/lib/assets/icons/AlertTriangleIcon.js +14 -0
- package/dist/client/shared/lib/assets/icons/CheckIcon.d.ts +1 -0
- package/dist/client/shared/lib/assets/icons/CheckIcon.js +17 -0
- package/dist/client/shared/lib/assets/icons/CloseIcon.d.ts +1 -0
- package/dist/client/shared/lib/assets/icons/CloseIcon.js +16 -0
- package/dist/client/shared/ui/Button/Button.js +2 -1
- package/dist/client/shared/ui/ColorIndicator/ColorIndicator.d.ts +1 -1
- package/dist/client/shared/ui/ColorIndicator/styles.module.scss +4 -0
- package/dist/client/shared/ui/Label/Label.d.ts +2 -0
- package/dist/client/shared/ui/Label/Label.js +3 -2
- package/dist/client/shared/ui/Label/styles.module.scss +11 -0
- package/dist/client/shared/ui/Loading/Loading.d.ts +6 -2
- package/dist/client/shared/ui/Loading/Loading.js +4 -2
- package/dist/client/shared/ui/Loading/barCount.d.ts +3 -0
- package/dist/client/shared/ui/Loading/barCount.js +11 -0
- package/dist/client/shared/ui/Loading/styles.module.scss +20 -27
- package/dist/client/shared/ui/Popup/Popup.js +3 -0
- package/dist/client/shared/ui/Select/Select.js +1 -1
- package/dist/client/shared/ui/form/FormSelect/FormSelect.js +1 -0
- package/dist/client/widgets/bulk-translation-dashboard/ui/BulkTranslationDashboard.js +37 -17
- package/dist/client/widgets/bulk-translation-dashboard/ui/styles.module.scss +27 -0
- package/dist/client/widgets/translate-document/ui/TranslateDocument.js +53 -27
- package/dist/client/widgets/translate-document/ui/styles.module.scss +27 -0
- package/dist/composition/levels/useDocTranslationApi.d.ts +3 -2
- package/dist/composition/levels/useDocTranslationApi.js +6 -3
- package/dist/core/provenance/ProvenanceStore.interface.d.ts +18 -0
- package/dist/core/provenance/index.d.ts +1 -0
- package/dist/core/provenance/index.js +1 -1
- package/dist/core/provenance/staleness.d.ts +19 -0
- package/dist/core/provenance/staleness.js +21 -0
- package/dist/plugin.js +2 -1
- package/dist/server/features/_lib/sourceDocument.d.ts +9 -0
- package/dist/server/features/_lib/sourceDocument.js +16 -0
- package/dist/server/features/createTranslationRoutes.d.ts +12 -4
- package/dist/server/features/createTranslationRoutes.js +15 -5
- package/dist/server/features/get-document-status/model.js +7 -2
- package/dist/server/features/staleness/dismissStaleness.handler.d.ts +8 -0
- package/dist/server/features/staleness/dismissStaleness.handler.js +27 -0
- package/dist/server/features/staleness/getDocumentStaleness.handler.d.ts +12 -0
- package/dist/server/features/staleness/getDocumentStaleness.handler.js +43 -0
- package/dist/server/features/staleness/index.d.ts +2 -0
- package/dist/server/features/staleness/index.js +3 -0
- package/dist/server/features/staleness/model.d.ts +52 -0
- package/dist/server/features/staleness/model.js +17 -0
- package/dist/server/features/staleness/route.d.ts +7 -0
- package/dist/server/features/staleness/route.js +21 -0
- package/dist/server/features/staleness/service.d.ts +15 -0
- package/dist/server/features/staleness/service.js +78 -0
- package/dist/server/features/translate-document/handler.js +22 -8
- package/dist/server/modules/provenance/PayloadProvenanceStore.d.ts +2 -0
- package/dist/server/modules/provenance/PayloadProvenanceStore.js +37 -14
- package/dist/server/modules/translation-levels/PluginConfigBuilder.d.ts +3 -0
- package/dist/server/modules/translation-levels/PluginConfigBuilder.js +2 -0
- package/dist/server/modules/translation-levels/types.d.ts +3 -0
- package/dist/server/shared/http/index.d.ts +1 -0
- package/dist/server/shared/http/index.js +1 -0
- package/dist/server/shared/http/toClientErrorMessage.d.ts +15 -0
- package/dist/server/shared/http/toClientErrorMessage.js +26 -0
- package/dist/server/shared/index.d.ts +1 -1
- package/dist/server/shared/index.js +1 -1
- package/package.json +1 -1
- package/dist/client/entities/translation/ui/CompletedTranslationStatus/CompletedTranslationStatus.d.ts +0 -7
- package/dist/client/entities/translation/ui/CompletedTranslationStatus/CompletedTranslationStatus.js +0 -45
- package/dist/client/entities/translation/ui/CompletedTranslationStatus/index.d.ts +0 -1
- package/dist/client/entities/translation/ui/CompletedTranslationStatus/index.js +0 -3
- package/dist/client/entities/translation/ui/CompletedTranslationStatus/styles.module.scss +0 -3
- package/dist/client/entities/translation/ui/FailedTranslationStatus/FailedTranslationStatus.d.ts +0 -9
- package/dist/client/entities/translation/ui/FailedTranslationStatus/FailedTranslationStatus.js +0 -54
- package/dist/client/entities/translation/ui/FailedTranslationStatus/index.d.ts +0 -1
- package/dist/client/entities/translation/ui/FailedTranslationStatus/index.js +0 -3
- package/dist/client/entities/translation/ui/FailedTranslationStatus/styles.module.scss +0 -3
- package/dist/client/entities/translation/ui/PendingTranslationStatus/PendingTranslationStatus.d.ts +0 -11
- package/dist/client/entities/translation/ui/PendingTranslationStatus/PendingTranslationStatus.js +0 -83
- package/dist/client/entities/translation/ui/PendingTranslationStatus/index.d.ts +0 -1
- package/dist/client/entities/translation/ui/PendingTranslationStatus/index.js +0 -3
- package/dist/client/entities/translation/ui/RunningTranslationStatus/RunningTranslationStatus.d.ts +0 -10
- package/dist/client/entities/translation/ui/RunningTranslationStatus/RunningTranslationStatus.js +0 -66
- package/dist/client/entities/translation/ui/RunningTranslationStatus/index.d.ts +0 -1
- package/dist/client/entities/translation/ui/RunningTranslationStatus/index.js +0 -3
- package/dist/client/features/document-translation-progress-failed/index.d.ts +0 -1
- package/dist/client/features/document-translation-progress-failed/index.js +0 -3
- package/dist/client/features/document-translation-progress-failed/ui/DocumentTranslationProgressFailed.d.ts +0 -6
- package/dist/client/features/document-translation-progress-failed/ui/DocumentTranslationProgressFailed.js +0 -28
- package/dist/client/features/document-translation-progress-pending/index.d.ts +0 -1
- package/dist/client/features/document-translation-progress-pending/index.js +0 -3
- package/dist/client/features/document-translation-progress-pending/ui/DocumentTranslationProgressPending.d.ts +0 -6
- package/dist/client/features/document-translation-progress-pending/ui/DocumentTranslationProgressPending.js +0 -44
- package/dist/client/features/document-translation-progress-running/index.d.ts +0 -1
- package/dist/client/features/document-translation-progress-running/index.js +0 -3
- package/dist/client/features/document-translation-progress-running/ui/DocumentTranslationProgressRunning.d.ts +0 -6
- package/dist/client/features/document-translation-progress-running/ui/DocumentTranslationProgressRunning.js +0 -28
|
@@ -31,56 +31,49 @@
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
&__bars {
|
|
34
|
-
display:
|
|
35
|
-
gap: 7px;
|
|
34
|
+
display: flex;
|
|
36
35
|
align-items: center;
|
|
37
36
|
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
// Gap tightens with size so a small control's bars don't read as scattered.
|
|
38
|
+
&_sm {
|
|
39
|
+
gap: 4px;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
&
|
|
43
|
-
|
|
42
|
+
&_md {
|
|
43
|
+
gap: 6px;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
&_lg {
|
|
47
|
+
gap: 7px;
|
|
44
48
|
}
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
&__bar {
|
|
48
52
|
background-color: currentColor;
|
|
53
|
+
width: 2px;
|
|
49
54
|
|
|
50
|
-
&_size-
|
|
51
|
-
height:
|
|
52
|
-
width: 2px;
|
|
55
|
+
&_size-sm {
|
|
56
|
+
height: 9px;
|
|
53
57
|
}
|
|
54
58
|
|
|
55
|
-
&_size-
|
|
56
|
-
height:
|
|
57
|
-
width: 2px;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
&:nth-child(1) {
|
|
61
|
-
transform: translateY(0);
|
|
62
|
-
animation: animate-bar--odd 1.25s infinite;
|
|
59
|
+
&_size-md {
|
|
60
|
+
height: 12px;
|
|
63
61
|
}
|
|
64
62
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
animation: animate-bar--even 1.25s infinite;
|
|
63
|
+
&_size-lg {
|
|
64
|
+
height: 15px;
|
|
68
65
|
}
|
|
69
66
|
|
|
70
|
-
|
|
67
|
+
// Neighbouring bars swing in opposition, regardless of how many there are.
|
|
68
|
+
&:nth-child(odd) {
|
|
71
69
|
transform: translateY(0);
|
|
72
70
|
animation: animate-bar--odd 1.25s infinite;
|
|
73
71
|
}
|
|
74
72
|
|
|
75
|
-
&:nth-child(
|
|
73
|
+
&:nth-child(even) {
|
|
76
74
|
transform: translateY(-2px);
|
|
77
75
|
animation: animate-bar--even 1.25s infinite;
|
|
78
76
|
}
|
|
79
|
-
|
|
80
|
-
&:nth-child(5) {
|
|
81
|
-
transform: translateY(0);
|
|
82
|
-
animation: animate-bar--odd 1.25s infinite;
|
|
83
|
-
}
|
|
84
77
|
}
|
|
85
78
|
|
|
86
79
|
&__text {
|
|
@@ -15,6 +15,9 @@ function Popup({ open, children, onOpenChange, $trigger, $align, $side, onOpenAu
|
|
|
15
15
|
sideOffset: 8,
|
|
16
16
|
side: $side,
|
|
17
17
|
align: $align,
|
|
18
|
+
// Keep a gap from the viewport edges so the popup never sticks to the screen border —
|
|
19
|
+
// Radix shifts it inward to preserve this padding.
|
|
20
|
+
collisionPadding: 12,
|
|
18
21
|
className: style.popover,
|
|
19
22
|
onOpenAutoFocus: onOpenAutoFocus,
|
|
20
23
|
children: children
|
|
@@ -3,13 +3,14 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { toast, useLocale, useSelection } from "@payloadcms/ui";
|
|
4
4
|
import { SelectAllStatus } from "@payloadcms/ui/providers/Selection";
|
|
5
5
|
import { useEffect, useMemo } from "react";
|
|
6
|
-
import { TranslationsApi } from "../../../entities/translation";
|
|
6
|
+
import { deriveCollectionPanelStatus, TranslationsApi } from "../../../entities/translation";
|
|
7
7
|
import { CollectionTranslationForm, FORM_FIELDS } from "../../../features/collection-translation-form";
|
|
8
8
|
import { CollectionTranslationFormModel } from "../../../features/collection-translation-form/index.client";
|
|
9
9
|
import CollectionTranslationPopup from "../../../features/collection-translation-popup";
|
|
10
10
|
import { CollectionTranslationProgress } from "../../../features/collection-translation-progress";
|
|
11
11
|
import { handleFormError } from "../../../shared/lib/forms/handle-form-error";
|
|
12
12
|
import { useCollectionDashboardUrlParams } from "../../../shared/lib/payload/hooks/useCollectionDashboardUrlParams";
|
|
13
|
+
import styles from "./styles.module.scss";
|
|
13
14
|
export default function BulkTranslationDashboard({ hasDrafts }) {
|
|
14
15
|
const locale = useLocale();
|
|
15
16
|
const { collection } = useCollectionDashboardUrlParams();
|
|
@@ -38,7 +39,10 @@ export default function BulkTranslationDashboard({ hasDrafts }) {
|
|
|
38
39
|
});
|
|
39
40
|
const selectAll = documentsSelection.selectAll === SelectAllStatus.AllAvailable;
|
|
40
41
|
const selectedCount = selectAll ? documentsSelection.totalDocs : documentsSelection.count;
|
|
41
|
-
const
|
|
42
|
+
const status = collectionTranslationApi.data;
|
|
43
|
+
const panelStatus = deriveCollectionPanelStatus(status);
|
|
44
|
+
const hasAnyCount = !!(status && (status.completed.length || status.failed.length || status.running.length || status.pending.length));
|
|
45
|
+
const showStatus = collectionTranslationApi.isPending || hasAnyCount;
|
|
42
46
|
const handleSubmit = async (formData)=>{
|
|
43
47
|
try {
|
|
44
48
|
await queueTranslationApi.mutateAsync({
|
|
@@ -51,25 +55,41 @@ export default function BulkTranslationDashboard({ hasDrafts }) {
|
|
|
51
55
|
}
|
|
52
56
|
};
|
|
53
57
|
return /*#__PURE__*/ _jsxs(CollectionTranslationPopup, {
|
|
54
|
-
|
|
58
|
+
status: panelStatus,
|
|
55
59
|
selectedCount: selectedCount,
|
|
56
60
|
children: [
|
|
57
|
-
/*#__PURE__*/ _jsx("
|
|
58
|
-
|
|
61
|
+
/*#__PURE__*/ _jsx("h4", {
|
|
62
|
+
className: styles.title,
|
|
63
|
+
children: "Bulk translation"
|
|
59
64
|
}),
|
|
60
|
-
/*#__PURE__*/
|
|
61
|
-
|
|
65
|
+
/*#__PURE__*/ _jsxs("section", {
|
|
66
|
+
className: styles.section,
|
|
67
|
+
children: [
|
|
68
|
+
/*#__PURE__*/ _jsx("h5", {
|
|
69
|
+
className: styles.eyebrow,
|
|
70
|
+
children: "Translate"
|
|
71
|
+
}),
|
|
72
|
+
/*#__PURE__*/ _jsx(CollectionTranslationForm, {
|
|
73
|
+
form: form,
|
|
74
|
+
onSubmit: handleSubmit,
|
|
75
|
+
selectedCount: selectedCount,
|
|
76
|
+
hasDrafts: hasDrafts
|
|
77
|
+
})
|
|
78
|
+
]
|
|
62
79
|
}),
|
|
63
|
-
/*#__PURE__*/
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
80
|
+
showStatus && /*#__PURE__*/ _jsxs("section", {
|
|
81
|
+
className: `${styles.section} ${styles["section--status"]}`,
|
|
82
|
+
children: [
|
|
83
|
+
/*#__PURE__*/ _jsx("h5", {
|
|
84
|
+
className: styles.eyebrow,
|
|
85
|
+
children: "Status"
|
|
86
|
+
}),
|
|
87
|
+
/*#__PURE__*/ _jsx(CollectionTranslationProgress, {
|
|
88
|
+
collection: collection,
|
|
89
|
+
data: collectionTranslationApi.data,
|
|
90
|
+
isPending: collectionTranslationApi.isPending
|
|
91
|
+
})
|
|
92
|
+
]
|
|
73
93
|
})
|
|
74
94
|
]
|
|
75
95
|
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
.title {
|
|
2
|
+
margin: 0;
|
|
3
|
+
font-size: 0.8125rem;
|
|
4
|
+
font-weight: 600;
|
|
5
|
+
color: var(--theme-text, #222);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.section {
|
|
9
|
+
display: flex;
|
|
10
|
+
flex-direction: column;
|
|
11
|
+
gap: 0.5rem;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// One eyebrow style shared by every section header, so the sections read as one system.
|
|
15
|
+
.eyebrow {
|
|
16
|
+
margin: 0;
|
|
17
|
+
font-size: 0.6875rem;
|
|
18
|
+
font-weight: 600;
|
|
19
|
+
text-transform: uppercase;
|
|
20
|
+
letter-spacing: 0.04em;
|
|
21
|
+
color: var(--theme-elevation-500, #8c8c8c);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.section--status {
|
|
25
|
+
padding-top: 0.75rem;
|
|
26
|
+
border-top: 1px solid var(--theme-elevation-100, #e6e6e6);
|
|
27
|
+
}
|
|
@@ -2,20 +2,19 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { toast, useLocale } from "@payloadcms/ui";
|
|
4
4
|
import { useEffect, useMemo } from "react";
|
|
5
|
-
import {
|
|
5
|
+
import { buildTranslationStatusRows, derivePanelStatus, TranslationsApi, TranslationStatusList } from "../../../entities/translation";
|
|
6
6
|
import { OpenDocumentTranslationPopup } from "../../../features/open-document-translation-popup";
|
|
7
7
|
import { DocumentTranslationForm, FORM_FIELDS } from "../../../features/translate-document-form";
|
|
8
8
|
import { DocumentTranslationFormModel } from "../../../features/translate-document-form/index.client";
|
|
9
9
|
import { handleFormError } from "../../../shared/lib/forms/handle-form-error";
|
|
10
10
|
import { useCollectionDocumentUrlParams } from "../../../shared/lib/payload/hooks/useCollectionDocumentUrlParams";
|
|
11
|
-
import
|
|
12
|
-
import { DocumentTranslationProgressRunning } from "../../../features/document-translation-progress-running";
|
|
13
|
-
import { DocumentTranslationProgressPending } from "../../../features/document-translation-progress-pending";
|
|
11
|
+
import styles from "./styles.module.scss";
|
|
14
12
|
const TranslateDocument = ({ hasDrafts })=>{
|
|
15
13
|
const locale = useLocale();
|
|
16
14
|
const params = useCollectionDocumentUrlParams();
|
|
17
15
|
const queueTranslationApi = TranslationsApi.useQueueDocumentTranslation();
|
|
18
16
|
const { data, error, isLoading } = TranslationsApi.useDocumentTranslation(params);
|
|
17
|
+
const { data: staleness } = TranslationsApi.useDocumentStaleness(params);
|
|
19
18
|
useEffect(()=>{
|
|
20
19
|
if (error) {
|
|
21
20
|
toast.error(error.message);
|
|
@@ -23,6 +22,24 @@ const TranslateDocument = ({ hasDrafts })=>{
|
|
|
23
22
|
}, [
|
|
24
23
|
error
|
|
25
24
|
]);
|
|
25
|
+
const staleLocales = useMemo(()=>(staleness?.locales ?? []).filter((l)=>l.is_stale).map((l)=>l.target_lng), [
|
|
26
|
+
staleness
|
|
27
|
+
]);
|
|
28
|
+
// The panel trigger shows one aggregate marker; the popup shows the full per-locale list.
|
|
29
|
+
const panelStatus = useMemo(()=>derivePanelStatus({
|
|
30
|
+
runStatus: data?.status,
|
|
31
|
+
staleLocales
|
|
32
|
+
}), [
|
|
33
|
+
data?.status,
|
|
34
|
+
staleLocales
|
|
35
|
+
]);
|
|
36
|
+
const statusRows = useMemo(()=>buildTranslationStatusRows({
|
|
37
|
+
staleness,
|
|
38
|
+
run: data
|
|
39
|
+
}), [
|
|
40
|
+
staleness,
|
|
41
|
+
data
|
|
42
|
+
]);
|
|
26
43
|
const initialValues = useMemo(()=>({
|
|
27
44
|
[FORM_FIELDS.SOURCE_LNG]: locale.code,
|
|
28
45
|
[FORM_FIELDS.HIDDEN_COLLECTION_SLUG]: params.collection,
|
|
@@ -48,38 +65,47 @@ const TranslateDocument = ({ hasDrafts })=>{
|
|
|
48
65
|
handleFormError(e, form);
|
|
49
66
|
}
|
|
50
67
|
};
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
68
|
+
// Vertical stack: title → Translate (form) → Status (unified per-locale list). No columns, no
|
|
69
|
+
// corner chip — one consistent system so the popup stays calm as the plugin grows.
|
|
70
|
+
return /*#__PURE__*/ _jsx(OpenDocumentTranslationPopup, {
|
|
71
|
+
isLoading: isLoading,
|
|
72
|
+
status: panelStatus,
|
|
73
|
+
children: ({ close })=>/*#__PURE__*/ _jsxs(_Fragment, {
|
|
74
|
+
children: [
|
|
75
|
+
/*#__PURE__*/ _jsx("h4", {
|
|
76
|
+
className: styles.title,
|
|
77
|
+
children: "Document translation"
|
|
78
|
+
}),
|
|
79
|
+
/*#__PURE__*/ _jsxs("section", {
|
|
80
|
+
className: styles.section,
|
|
56
81
|
children: [
|
|
57
|
-
/*#__PURE__*/ _jsx("
|
|
58
|
-
|
|
82
|
+
/*#__PURE__*/ _jsx("h5", {
|
|
83
|
+
className: styles.eyebrow,
|
|
84
|
+
children: "Translate"
|
|
59
85
|
}),
|
|
60
86
|
/*#__PURE__*/ _jsx(DocumentTranslationForm, {
|
|
61
87
|
form: form,
|
|
62
|
-
onSubmit: (
|
|
88
|
+
onSubmit: (formData)=>handleSubmit(formData, close),
|
|
63
89
|
hasDrafts: hasDrafts
|
|
64
90
|
})
|
|
65
91
|
]
|
|
92
|
+
}),
|
|
93
|
+
statusRows.length > 0 && /*#__PURE__*/ _jsxs("section", {
|
|
94
|
+
className: `${styles.section} ${styles["section--status"]}`,
|
|
95
|
+
children: [
|
|
96
|
+
/*#__PURE__*/ _jsx("h5", {
|
|
97
|
+
className: styles.eyebrow,
|
|
98
|
+
children: "Status"
|
|
99
|
+
}),
|
|
100
|
+
/*#__PURE__*/ _jsx(TranslationStatusList, {
|
|
101
|
+
rows: statusRows,
|
|
102
|
+
collection: params.collection,
|
|
103
|
+
id: params.id
|
|
104
|
+
})
|
|
105
|
+
]
|
|
66
106
|
})
|
|
67
|
-
|
|
68
|
-
data?.status === "completed" && /*#__PURE__*/ _jsx(CompletedTranslationStatus, {
|
|
69
|
-
sourceLocale: data.input.source_lng,
|
|
70
|
-
targetLocale: data.input.target_lng,
|
|
71
|
-
completed_at: data.completed_at
|
|
72
|
-
}),
|
|
73
|
-
data?.status === "failed" && /*#__PURE__*/ _jsx(DocumentTranslationProgressFailed, {
|
|
74
|
-
data: data
|
|
75
|
-
}),
|
|
76
|
-
data?.status === "running" && /*#__PURE__*/ _jsx(DocumentTranslationProgressRunning, {
|
|
77
|
-
data: data
|
|
78
|
-
}),
|
|
79
|
-
data?.status === "pending" && /*#__PURE__*/ _jsx(DocumentTranslationProgressPending, {
|
|
80
|
-
data: data
|
|
107
|
+
]
|
|
81
108
|
})
|
|
82
|
-
]
|
|
83
109
|
});
|
|
84
110
|
};
|
|
85
111
|
export default TranslateDocument;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
.title {
|
|
2
|
+
margin: 0;
|
|
3
|
+
font-size: 0.8125rem;
|
|
4
|
+
font-weight: 600;
|
|
5
|
+
color: var(--theme-text, #222);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.section {
|
|
9
|
+
display: flex;
|
|
10
|
+
flex-direction: column;
|
|
11
|
+
gap: 0.5rem;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// One eyebrow style shared by every section header, so the sections read as one system.
|
|
15
|
+
.eyebrow {
|
|
16
|
+
margin: 0;
|
|
17
|
+
font-size: 0.6875rem;
|
|
18
|
+
font-weight: 600;
|
|
19
|
+
text-transform: uppercase;
|
|
20
|
+
letter-spacing: 0.04em;
|
|
21
|
+
color: var(--theme-elevation-500, #8c8c8c);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.section--status {
|
|
25
|
+
padding-top: 0.75rem;
|
|
26
|
+
border-top: 1px solid var(--theme-elevation-100, #e6e6e6);
|
|
27
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { LevelContext } from "../../server/modules/translation-levels";
|
|
2
2
|
/**
|
|
3
|
-
* Contribute the runner-agnostic document-translation API — the shared
|
|
3
|
+
* Contribute the runner-agnostic document-translation API — the shared route
|
|
4
4
|
* bundle, bound to the level context's runner. Both `documentLevel` and
|
|
5
5
|
* `collectionLevel` call this; the plugin deduplicates the endpoints by method +
|
|
6
|
-
* path, so the bundle registers exactly once.
|
|
6
|
+
* path, so the bundle registers exactly once. The schema map and provenance
|
|
7
|
+
* store factory ride along for the staleness endpoints.
|
|
7
8
|
*/
|
|
8
9
|
export declare function useDocTranslationApi(ctx: LevelContext): void;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { createTranslationRoutes } from "../../server/features/createTranslationRoutes";
|
|
2
2
|
/**
|
|
3
|
-
* Contribute the runner-agnostic document-translation API — the shared
|
|
3
|
+
* Contribute the runner-agnostic document-translation API — the shared route
|
|
4
4
|
* bundle, bound to the level context's runner. Both `documentLevel` and
|
|
5
5
|
* `collectionLevel` call this; the plugin deduplicates the endpoints by method +
|
|
6
|
-
* path, so the bundle registers exactly once.
|
|
6
|
+
* path, so the bundle registers exactly once. The schema map and provenance
|
|
7
|
+
* store factory ride along for the staleness endpoints.
|
|
7
8
|
*/ export function useDocTranslationApi(ctx) {
|
|
8
9
|
ctx.addEndpoints(createTranslationRoutes({
|
|
9
10
|
taskRunnerFactory: ctx.taskRunnerFactory,
|
|
@@ -11,7 +12,9 @@ import { createTranslationRoutes } from "../../server/features/createTranslation
|
|
|
11
12
|
availableCollections: new Set(ctx.collections.map((collection)=>collection.slug))
|
|
12
13
|
},
|
|
13
14
|
access: ctx.access,
|
|
14
|
-
basePath: ctx.basePath
|
|
15
|
+
basePath: ctx.basePath,
|
|
16
|
+
schemaMap: ctx.schemaMap,
|
|
17
|
+
provenanceStoreFactory: ctx.provenanceStoreFactory
|
|
15
18
|
}));
|
|
16
19
|
}
|
|
17
20
|
|
|
@@ -88,6 +88,24 @@ export interface ProvenanceStore {
|
|
|
88
88
|
* @returns The stored record, or `null` if none exists.
|
|
89
89
|
*/
|
|
90
90
|
find(key: ProvenanceKey): Promise<TranslationProvenanceRecord | null>;
|
|
91
|
+
/**
|
|
92
|
+
* List every record for a document — one per translated target locale. Backs #50's per-locale
|
|
93
|
+
* staleness read for a single document panel.
|
|
94
|
+
*
|
|
95
|
+
* @param collectionSlug - Slug of the translated document's collection.
|
|
96
|
+
* @param documentId - Stringified id of the translated document.
|
|
97
|
+
* @returns All provenance receipts for the document (empty when none exist).
|
|
98
|
+
*/
|
|
99
|
+
findByDocument(collectionSlug: string, documentId: string): Promise<TranslationProvenanceRecord[]>;
|
|
100
|
+
/**
|
|
101
|
+
* Acknowledge the current source drift for one locale without re-translating (#50's dismiss):
|
|
102
|
+
* persist `dismissedFingerprint` so the indicator hides until the source changes again. No-op if
|
|
103
|
+
* the key has no record.
|
|
104
|
+
*
|
|
105
|
+
* @param key - The `(collectionSlug, documentId, targetLocale)` identity to dismiss.
|
|
106
|
+
* @param dismissedFingerprint - The current source fingerprint being acknowledged.
|
|
107
|
+
*/
|
|
108
|
+
dismiss(key: ProvenanceKey, dismissedFingerprint: string): Promise<void>;
|
|
91
109
|
/**
|
|
92
110
|
* Delete every record for a document (all target locales). Used to cascade-clean when the source
|
|
93
111
|
* document is deleted.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { TranslationProvenanceRecord } from "./ProvenanceStore.interface";
|
|
2
|
+
/**
|
|
3
|
+
* Whether a translated locale is out of date relative to its source — the #50 rule, in one place.
|
|
4
|
+
*
|
|
5
|
+
* A record is stale when the current source fingerprint differs from the one the translation was
|
|
6
|
+
* derived from (`record.sourceFingerprint`) **and** the editor has not acknowledged that exact drift
|
|
7
|
+
* (`record.dismissedFingerprint`). Dismissing sets `dismissedFingerprint` to the current fingerprint,
|
|
8
|
+
* so the indicator hides until the source changes again (a new fingerprint no longer matches the
|
|
9
|
+
* dismissed one). `dismissedFingerprint` is `null` until the first dismiss, in which case the check
|
|
10
|
+
* reduces to a plain source-drift comparison.
|
|
11
|
+
*
|
|
12
|
+
* Pure and payload-free so the staleness contract is testable without a database; the server handler
|
|
13
|
+
* supplies `currentFingerprint` via {@link computeSourceFingerprint} on the live source document.
|
|
14
|
+
*
|
|
15
|
+
* @param record - The stored provenance receipt for one `(collection, document, targetLocale)`.
|
|
16
|
+
* @param currentFingerprint - `computeSourceFingerprint` of the source document right now.
|
|
17
|
+
* @returns `true` when the target locale is out of date and not dismissed.
|
|
18
|
+
*/
|
|
19
|
+
export declare function isRecordStale(record: TranslationProvenanceRecord, currentFingerprint: string): boolean;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a translated locale is out of date relative to its source — the #50 rule, in one place.
|
|
3
|
+
*
|
|
4
|
+
* A record is stale when the current source fingerprint differs from the one the translation was
|
|
5
|
+
* derived from (`record.sourceFingerprint`) **and** the editor has not acknowledged that exact drift
|
|
6
|
+
* (`record.dismissedFingerprint`). Dismissing sets `dismissedFingerprint` to the current fingerprint,
|
|
7
|
+
* so the indicator hides until the source changes again (a new fingerprint no longer matches the
|
|
8
|
+
* dismissed one). `dismissedFingerprint` is `null` until the first dismiss, in which case the check
|
|
9
|
+
* reduces to a plain source-drift comparison.
|
|
10
|
+
*
|
|
11
|
+
* Pure and payload-free so the staleness contract is testable without a database; the server handler
|
|
12
|
+
* supplies `currentFingerprint` via {@link computeSourceFingerprint} on the live source document.
|
|
13
|
+
*
|
|
14
|
+
* @param record - The stored provenance receipt for one `(collection, document, targetLocale)`.
|
|
15
|
+
* @param currentFingerprint - `computeSourceFingerprint` of the source document right now.
|
|
16
|
+
* @returns `true` when the target locale is out of date and not dismissed.
|
|
17
|
+
*/ export function isRecordStale(record, currentFingerprint) {
|
|
18
|
+
return currentFingerprint !== record.sourceFingerprint && currentFingerprint !== record.dismissedFingerprint;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
//# sourceMappingURL=staleness.js.map
|
package/dist/plugin.js
CHANGED
|
@@ -87,7 +87,8 @@ import { normalizePath } from "./server/shared";
|
|
|
87
87
|
access,
|
|
88
88
|
taskRunnerFactory,
|
|
89
89
|
schemaMap,
|
|
90
|
-
translationProvider
|
|
90
|
+
translationProvider,
|
|
91
|
+
provenanceStoreFactory
|
|
91
92
|
});
|
|
92
93
|
for (const level of activeLevels)level.extend(builder);
|
|
93
94
|
builder.addConfigModifier(runnerConfigModifier);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CollectionSlug, Payload } from "payload";
|
|
2
|
+
/**
|
|
3
|
+
* Fetch a document's source-locale data with the exact shape the provenance fingerprint depends on
|
|
4
|
+
* (`depth: 0`, the given locale). Shared by the translation **write** path (which fingerprints the
|
|
5
|
+
* source it just translated) and the staleness **read** path (which re-fingerprints the live source),
|
|
6
|
+
* so the fingerprint baseline can never drift between the two — the single biggest correctness trap in
|
|
7
|
+
* staleness detection is thus enforced structurally, not by a comment.
|
|
8
|
+
*/
|
|
9
|
+
export declare function fetchSourceDocument(payload: Payload, collection: CollectionSlug, id: string, locale: string): Promise<import("payload").JsonObject & import("payload").TypeWithID>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetch a document's source-locale data with the exact shape the provenance fingerprint depends on
|
|
3
|
+
* (`depth: 0`, the given locale). Shared by the translation **write** path (which fingerprints the
|
|
4
|
+
* source it just translated) and the staleness **read** path (which re-fingerprints the live source),
|
|
5
|
+
* so the fingerprint baseline can never drift between the two — the single biggest correctness trap in
|
|
6
|
+
* staleness detection is thus enforced structurally, not by a comment.
|
|
7
|
+
*/ export function fetchSourceDocument(payload, collection, id, locale) {
|
|
8
|
+
return payload.findByID({
|
|
9
|
+
collection,
|
|
10
|
+
id,
|
|
11
|
+
locale,
|
|
12
|
+
depth: 0
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
//# sourceMappingURL=sourceDocument.js.map
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { CollectionSlug, Endpoint } from "payload";
|
|
2
2
|
import type { AccessGuard } from "../shared";
|
|
3
3
|
import type { TaskRunnerFactory } from "../modules/task-runner";
|
|
4
|
+
import type { CollectionSchemaMap } from "../../types/CollectionSchemaMap";
|
|
5
|
+
import type { ProvenanceStoreFactory } from "../modules/provenance";
|
|
4
6
|
export type TranslationRoutesDeps = {
|
|
5
7
|
taskRunnerFactory: TaskRunnerFactory;
|
|
6
8
|
/** Collections the plugin manages — gates which slugs the routes accept. */
|
|
@@ -9,15 +11,21 @@ export type TranslationRoutesDeps = {
|
|
|
9
11
|
};
|
|
10
12
|
access?: AccessGuard;
|
|
11
13
|
basePath?: string;
|
|
14
|
+
/** Original per-collection field schema — needed to recompute source fingerprints for staleness. */
|
|
15
|
+
schemaMap: CollectionSchemaMap;
|
|
16
|
+
/** Builds a provenance store; absent when provenance is disabled (staleness then reports empty). */
|
|
17
|
+
provenanceStoreFactory?: ProvenanceStoreFactory;
|
|
12
18
|
};
|
|
13
19
|
/**
|
|
14
|
-
* The translator's HTTP surface as one shared bundle: the
|
|
20
|
+
* The translator's HTTP surface as one shared bundle: the job-API endpoints
|
|
15
21
|
* (enqueue / run / cancel / cancel-by-collection / document-status /
|
|
16
|
-
* collection-status)
|
|
22
|
+
* collection-status) plus the provenance-backed staleness endpoints
|
|
23
|
+
* (document-staleness / dismiss), all wired to the same access guard.
|
|
17
24
|
*
|
|
18
25
|
* Extracted from the plugin's `init()` so registration is a single unit. The
|
|
19
26
|
* document and collection levels each contribute this bundle via the level
|
|
20
27
|
* context; the plugin deduplicates by method + path, so it registers exactly
|
|
21
|
-
* once.
|
|
28
|
+
* once. The staleness endpoints always register; when provenance is disabled
|
|
29
|
+
* (`provenanceStoreFactory` absent) they simply report no staleness.
|
|
22
30
|
*/
|
|
23
|
-
export declare function createTranslationRoutes({ taskRunnerFactory, collectionConfig, access, basePath, }: TranslationRoutesDeps): Endpoint[];
|
|
31
|
+
export declare function createTranslationRoutes({ taskRunnerFactory, collectionConfig, access, basePath, schemaMap, provenanceStoreFactory, }: TranslationRoutesDeps): Endpoint[];
|
|
@@ -4,23 +4,33 @@ import { createCancelRoute } from "./cancel";
|
|
|
4
4
|
import { createCancelByCollectionRoute } from "./cancel-by-collection";
|
|
5
5
|
import { createGetDocumentStatusRoute } from "./get-document-status";
|
|
6
6
|
import { createGetCollectionStatusRoute } from "./get-collection-status";
|
|
7
|
+
import { createGetDocumentStalenessRoute, createDismissStalenessRoute } from "./staleness";
|
|
7
8
|
/**
|
|
8
|
-
* The translator's HTTP surface as one shared bundle: the
|
|
9
|
+
* The translator's HTTP surface as one shared bundle: the job-API endpoints
|
|
9
10
|
* (enqueue / run / cancel / cancel-by-collection / document-status /
|
|
10
|
-
* collection-status)
|
|
11
|
+
* collection-status) plus the provenance-backed staleness endpoints
|
|
12
|
+
* (document-staleness / dismiss), all wired to the same access guard.
|
|
11
13
|
*
|
|
12
14
|
* Extracted from the plugin's `init()` so registration is a single unit. The
|
|
13
15
|
* document and collection levels each contribute this bundle via the level
|
|
14
16
|
* context; the plugin deduplicates by method + path, so it registers exactly
|
|
15
|
-
* once.
|
|
16
|
-
|
|
17
|
+
* once. The staleness endpoints always register; when provenance is disabled
|
|
18
|
+
* (`provenanceStoreFactory` absent) they simply report no staleness.
|
|
19
|
+
*/ export function createTranslationRoutes({ taskRunnerFactory, collectionConfig, access, basePath, schemaMap, provenanceStoreFactory }) {
|
|
20
|
+
const stalenessConfig = {
|
|
21
|
+
availableCollections: collectionConfig.availableCollections,
|
|
22
|
+
schemaMap,
|
|
23
|
+
provenanceStoreFactory
|
|
24
|
+
};
|
|
17
25
|
return [
|
|
18
26
|
createEnqueueRoute(taskRunnerFactory, collectionConfig, access, basePath),
|
|
19
27
|
createRunRoute(taskRunnerFactory, access, basePath),
|
|
20
28
|
createCancelRoute(taskRunnerFactory, access, basePath),
|
|
21
29
|
createCancelByCollectionRoute(collectionConfig, taskRunnerFactory, access, basePath),
|
|
22
30
|
createGetDocumentStatusRoute(collectionConfig, taskRunnerFactory, access, basePath),
|
|
23
|
-
createGetCollectionStatusRoute(collectionConfig, taskRunnerFactory, access, basePath)
|
|
31
|
+
createGetCollectionStatusRoute(collectionConfig, taskRunnerFactory, access, basePath),
|
|
32
|
+
createGetDocumentStalenessRoute(stalenessConfig, access, basePath),
|
|
33
|
+
createDismissStalenessRoute(stalenessConfig, access, basePath)
|
|
24
34
|
];
|
|
25
35
|
}
|
|
26
36
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { JobIdSchema } from "../../shared";
|
|
2
|
+
import { JobIdSchema, toClientErrorMessage } from "../../shared";
|
|
3
3
|
/**
|
|
4
4
|
* Input validation schema.
|
|
5
5
|
*
|
|
@@ -28,7 +28,12 @@ import { JobIdSchema } from "../../shared";
|
|
|
28
28
|
target_lng: task.input.targetLng,
|
|
29
29
|
strategy: task.input.strategy
|
|
30
30
|
},
|
|
31
|
-
error
|
|
31
|
+
// Never ship the raw provider/runtime error to the browser outside development — it can leak
|
|
32
|
+
// implementation detail and secrets (e.g. a partial API key). The full error stays in the job
|
|
33
|
+
// record + server logs.
|
|
34
|
+
error: task.error ? {
|
|
35
|
+
message: toClientErrorMessage(task.error.message)
|
|
36
|
+
} : undefined,
|
|
32
37
|
cancelled: task.cancelled
|
|
33
38
|
};
|
|
34
39
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { PayloadRequest } from "payload";
|
|
2
|
+
import type { StalenessConfig } from "./model";
|
|
3
|
+
/** Dismisses (acknowledges) staleness of one target locale for a document. */
|
|
4
|
+
export declare class DismissStalenessHandler {
|
|
5
|
+
private readonly config;
|
|
6
|
+
constructor(config: StalenessConfig);
|
|
7
|
+
handle(req: PayloadRequest): Promise<Response>;
|
|
8
|
+
}
|