@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.
- 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/field-traversal/types.d.ts +4 -2
- 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/core/translation-pipeline/stages/data-reconciler/DataReconciler.js +33 -10
- 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
|
|
|
@@ -11,13 +11,15 @@ export interface BlockLike {
|
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
13
13
|
* A tab as this layer needs to read it: an optional `name` (present → it opens a data boundary;
|
|
14
|
-
* absent → it flattens into the parent scope)
|
|
15
|
-
*
|
|
14
|
+
* absent → it flattens into the parent scope), its child `fields`, and `localized` (a *named* tab
|
|
15
|
+
* may be `localized`, partitioning its subtree per locale — Payload's sanitizer propagates it the
|
|
16
|
+
* same as a group). Payload's `Tab` (`NamedTab | UnnamedTab`) is assignable to this.
|
|
16
17
|
*
|
|
17
18
|
* @public
|
|
18
19
|
*/
|
|
19
20
|
export interface TabLike {
|
|
20
21
|
name?: string;
|
|
22
|
+
localized?: boolean;
|
|
21
23
|
fields: FieldLike[];
|
|
22
24
|
}
|
|
23
25
|
/**
|
|
@@ -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
|
|
@@ -5,9 +5,9 @@ const asObject = (value)=>isObject(value) ? value : {};
|
|
|
5
5
|
/**
|
|
6
6
|
* Reconcile walker: produces the full document shape from source + target, with target
|
|
7
7
|
* priority (source fills empty target slots). Iteration is driven by `source`; a field with
|
|
8
|
-
* no source value is dropped, `
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* no source value is dropped, `blockType` is preserved, non-object array items / unknown blocks
|
|
9
|
+
* pass through unchanged, and array/block `id` is kept for shared rows but stripped for per-locale
|
|
10
|
+
* rows (see {@link Cursor.sharedRow} — the data-loss-critical bit).
|
|
11
11
|
*
|
|
12
12
|
* Array/block elements are paired with their target counterpart by `id`, not by position (see
|
|
13
13
|
* {@link matchElementById}); output order always follows `source`.
|
|
@@ -15,9 +15,12 @@ const asObject = (value)=>isObject(value) ? value : {};
|
|
|
15
15
|
enterObject (field, cursor) {
|
|
16
16
|
const sourceValue = cursor.source[field.name];
|
|
17
17
|
if (!isObject(sourceValue)) return "skip"; // undefined / non-object source → drop the field
|
|
18
|
+
// A localized group or named tab partitions its whole subtree per locale → descendants are
|
|
19
|
+
// per-locale rows (Payload's sanitizer propagates `tab.localized` the same as a group).
|
|
18
20
|
return {
|
|
19
21
|
source: sourceValue,
|
|
20
|
-
target: asObject(cursor.target[field.name])
|
|
22
|
+
target: asObject(cursor.target[field.name]),
|
|
23
|
+
sharedRow: cursor.sharedRow && !field.localized
|
|
21
24
|
};
|
|
22
25
|
},
|
|
23
26
|
enterList (field, cursor) {
|
|
@@ -26,6 +29,8 @@ const asObject = (value)=>isObject(value) ? value : {};
|
|
|
26
29
|
const targetValue = cursor.target[field.name];
|
|
27
30
|
const targetArr = Array.isArray(targetValue) ? targetValue : [];
|
|
28
31
|
const isBlocks = field.type === "blocks";
|
|
32
|
+
// Elements are shared rows only when this container AND every ancestor is non-localized.
|
|
33
|
+
const childShared = cursor.sharedRow && !field.localized;
|
|
29
34
|
const children = [];
|
|
30
35
|
sourceValue.forEach((item, index)=>{
|
|
31
36
|
if (!isObject(item)) return; // non-object element → passthrough (rebuilt in combine)
|
|
@@ -35,7 +40,8 @@ const asObject = (value)=>isObject(value) ? value : {};
|
|
|
35
40
|
children.push({
|
|
36
41
|
cursor: {
|
|
37
42
|
source: item,
|
|
38
|
-
target: matchElementById(targetArr, item, isBlocks)
|
|
43
|
+
target: matchElementById(targetArr, item, isBlocks),
|
|
44
|
+
sharedRow: childShared
|
|
39
45
|
},
|
|
40
46
|
fields,
|
|
41
47
|
key: index
|
|
@@ -58,13 +64,29 @@ const asObject = (value)=>isObject(value) ? value : {};
|
|
|
58
64
|
child.key,
|
|
59
65
|
child.out
|
|
60
66
|
]));
|
|
61
|
-
|
|
67
|
+
// Passthrough items (unknown blockType / unresolved fields) never reach the element branch's
|
|
68
|
+
// id guard, so apply the same rule here: strip a per-locale row's id to avoid an insert
|
|
69
|
+
// collision. Elements are shared only when this container and every ancestor is non-localized.
|
|
70
|
+
const childShared = cursor.sharedRow && !container.field.localized;
|
|
71
|
+
return sourceArr.map((item, index)=>{
|
|
72
|
+
if (byIndex.has(index)) return byIndex.get(index);
|
|
73
|
+
if (!childShared && isObject(item) && item.id != null) {
|
|
74
|
+
const { id: _dropped, ...rest } = item;
|
|
75
|
+
return rest;
|
|
76
|
+
}
|
|
77
|
+
return item;
|
|
78
|
+
});
|
|
62
79
|
}
|
|
63
80
|
const result = {};
|
|
64
81
|
for (const child of children)result[child.key] = child.out;
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
result.blockType = cursor.source.blockType;
|
|
82
|
+
if (container.kind === "element") {
|
|
83
|
+
// Block elements keep their blockType.
|
|
84
|
+
if (container.field.type === "blocks") result.blockType = cursor.source.blockType;
|
|
85
|
+
// Keep `id` only for a shared (non-localized, no localized ancestor) row so Payload updates it
|
|
86
|
+
// in place; stripping it there makes Payload recreate the row and wipe other locales. Under a
|
|
87
|
+
// localized container/ancestor the rows are per-locale, so the id is dropped to avoid an
|
|
88
|
+
// insert collision with the source locale's row. Guard against writing `id: undefined`.
|
|
89
|
+
if (cursor.sharedRow && cursor.source.id != null) result.id = cursor.source.id;
|
|
68
90
|
}
|
|
69
91
|
return result;
|
|
70
92
|
}
|
|
@@ -88,7 +110,8 @@ const asObject = (value)=>isObject(value) ? value : {};
|
|
|
88
110
|
*/ reconcile(sourceData, targetData) {
|
|
89
111
|
const root = {
|
|
90
112
|
source: sourceData,
|
|
91
|
-
target: targetData ?? {}
|
|
113
|
+
target: targetData ?? {},
|
|
114
|
+
sharedRow: true
|
|
92
115
|
};
|
|
93
116
|
return walkFields(this.schema, root, reconcileWalker) ?? {};
|
|
94
117
|
}
|
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
|