@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.
Files changed (139) 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/provenance/ProvenanceStore.interface.d.ts +18 -0
  75. package/dist/core/provenance/index.d.ts +1 -0
  76. package/dist/core/provenance/index.js +1 -1
  77. package/dist/core/provenance/staleness.d.ts +19 -0
  78. package/dist/core/provenance/staleness.js +21 -0
  79. package/dist/plugin.js +2 -1
  80. package/dist/server/features/_lib/sourceDocument.d.ts +9 -0
  81. package/dist/server/features/_lib/sourceDocument.js +16 -0
  82. package/dist/server/features/createTranslationRoutes.d.ts +12 -4
  83. package/dist/server/features/createTranslationRoutes.js +15 -5
  84. package/dist/server/features/get-document-status/model.js +7 -2
  85. package/dist/server/features/staleness/dismissStaleness.handler.d.ts +8 -0
  86. package/dist/server/features/staleness/dismissStaleness.handler.js +27 -0
  87. package/dist/server/features/staleness/getDocumentStaleness.handler.d.ts +12 -0
  88. package/dist/server/features/staleness/getDocumentStaleness.handler.js +43 -0
  89. package/dist/server/features/staleness/index.d.ts +2 -0
  90. package/dist/server/features/staleness/index.js +3 -0
  91. package/dist/server/features/staleness/model.d.ts +52 -0
  92. package/dist/server/features/staleness/model.js +17 -0
  93. package/dist/server/features/staleness/route.d.ts +7 -0
  94. package/dist/server/features/staleness/route.js +21 -0
  95. package/dist/server/features/staleness/service.d.ts +15 -0
  96. package/dist/server/features/staleness/service.js +78 -0
  97. package/dist/server/features/translate-document/handler.js +22 -8
  98. package/dist/server/modules/provenance/PayloadProvenanceStore.d.ts +2 -0
  99. package/dist/server/modules/provenance/PayloadProvenanceStore.js +37 -14
  100. package/dist/server/modules/translation-levels/PluginConfigBuilder.d.ts +3 -0
  101. package/dist/server/modules/translation-levels/PluginConfigBuilder.js +2 -0
  102. package/dist/server/modules/translation-levels/types.d.ts +3 -0
  103. package/dist/server/shared/http/index.d.ts +1 -0
  104. package/dist/server/shared/http/index.js +1 -0
  105. package/dist/server/shared/http/toClientErrorMessage.d.ts +15 -0
  106. package/dist/server/shared/http/toClientErrorMessage.js +26 -0
  107. package/dist/server/shared/index.d.ts +1 -1
  108. package/dist/server/shared/index.js +1 -1
  109. package/package.json +1 -1
  110. package/dist/client/entities/translation/ui/CompletedTranslationStatus/CompletedTranslationStatus.d.ts +0 -7
  111. package/dist/client/entities/translation/ui/CompletedTranslationStatus/CompletedTranslationStatus.js +0 -45
  112. package/dist/client/entities/translation/ui/CompletedTranslationStatus/index.d.ts +0 -1
  113. package/dist/client/entities/translation/ui/CompletedTranslationStatus/index.js +0 -3
  114. package/dist/client/entities/translation/ui/CompletedTranslationStatus/styles.module.scss +0 -3
  115. package/dist/client/entities/translation/ui/FailedTranslationStatus/FailedTranslationStatus.d.ts +0 -9
  116. package/dist/client/entities/translation/ui/FailedTranslationStatus/FailedTranslationStatus.js +0 -54
  117. package/dist/client/entities/translation/ui/FailedTranslationStatus/index.d.ts +0 -1
  118. package/dist/client/entities/translation/ui/FailedTranslationStatus/index.js +0 -3
  119. package/dist/client/entities/translation/ui/FailedTranslationStatus/styles.module.scss +0 -3
  120. package/dist/client/entities/translation/ui/PendingTranslationStatus/PendingTranslationStatus.d.ts +0 -11
  121. package/dist/client/entities/translation/ui/PendingTranslationStatus/PendingTranslationStatus.js +0 -83
  122. package/dist/client/entities/translation/ui/PendingTranslationStatus/index.d.ts +0 -1
  123. package/dist/client/entities/translation/ui/PendingTranslationStatus/index.js +0 -3
  124. package/dist/client/entities/translation/ui/RunningTranslationStatus/RunningTranslationStatus.d.ts +0 -10
  125. package/dist/client/entities/translation/ui/RunningTranslationStatus/RunningTranslationStatus.js +0 -66
  126. package/dist/client/entities/translation/ui/RunningTranslationStatus/index.d.ts +0 -1
  127. package/dist/client/entities/translation/ui/RunningTranslationStatus/index.js +0 -3
  128. package/dist/client/features/document-translation-progress-failed/index.d.ts +0 -1
  129. package/dist/client/features/document-translation-progress-failed/index.js +0 -3
  130. package/dist/client/features/document-translation-progress-failed/ui/DocumentTranslationProgressFailed.d.ts +0 -6
  131. package/dist/client/features/document-translation-progress-failed/ui/DocumentTranslationProgressFailed.js +0 -28
  132. package/dist/client/features/document-translation-progress-pending/index.d.ts +0 -1
  133. package/dist/client/features/document-translation-progress-pending/index.js +0 -3
  134. package/dist/client/features/document-translation-progress-pending/ui/DocumentTranslationProgressPending.d.ts +0 -6
  135. package/dist/client/features/document-translation-progress-pending/ui/DocumentTranslationProgressPending.js +0 -44
  136. package/dist/client/features/document-translation-progress-running/index.d.ts +0 -1
  137. package/dist/client/features/document-translation-progress-running/index.js +0 -3
  138. package/dist/client/features/document-translation-progress-running/ui/DocumentTranslationProgressRunning.d.ts +0 -6
  139. package/dist/client/features/document-translation-progress-running/ui/DocumentTranslationProgressRunning.js +0 -28
package/README.md CHANGED
@@ -180,6 +180,22 @@ translatorPlugin({
180
180
  });
181
181
  ```
182
182
 
183
+ ### Stale-translation detection
184
+
185
+ _Since v0.8.0._
186
+
187
+ With `provenance` enabled, the document translation control shows an **"out of date"** marker (with a
188
+ tooltip naming the affected locales) when a target locale's source content changed after it was
189
+ translated. Open the translation popup to see the per-locale list, where each locale can be
190
+ **re-translated** or its out-of-date notice **dismissed**.
191
+ Staleness is derived on read by comparing the current source fingerprint against the one recorded at
192
+ translation time — no extra configuration, and no write-side hook on your collections. Editing a
193
+ source locale marks its already-translated locales stale on the next panel view; re-translating clears it.
194
+
195
+ Dismiss acknowledges the drift without re-translating; the marker stays hidden until the source
196
+ changes again. When `provenance` is disabled nothing is shown. Note the fingerprint is text-only, so
197
+ formatting-only edits to rich text do not mark a locale stale.
198
+
183
199
  ### Lifecycle callbacks
184
200
 
185
201
  _Since v0.7.0._
@@ -382,11 +398,10 @@ Every public API is annotated with `@since x.y.z` in its JSDoc, and features car
382
398
 
383
399
  ## Roadmap
384
400
 
385
- Planned features building on the v0.7.0 provenance foundation:
401
+ Planned features building on the provenance foundation:
386
402
 
387
- - **Stale-translation detection** — surface which target locales are out of sync with their source, using the recorded provenance fingerprint.
388
403
  - **Global translation dashboard** — translate across all collections from one place, with project-wide progress.
389
- - **Auto-translate on source change** — retranslate automatically when default-locale content changes.
404
+ - **Auto-translate on source change** — retranslate automatically when default-locale content changes, driven by stale-translation detection.
390
405
 
391
406
  ## License
392
407
 
@@ -0,0 +1,12 @@
1
+ import type { CollectionSlug } from "payload";
2
+ type Variables = {
3
+ collection: CollectionSlug;
4
+ id: string;
5
+ target_lng: string;
6
+ };
7
+ /**
8
+ * Dismisses (acknowledges) staleness of one target locale, then refreshes the staleness query so the
9
+ * indicator disappears until the source changes again (#50).
10
+ */
11
+ export declare function useDismissStaleness(): import("@tanstack/react-query").UseMutationResult<void, Error, Variables, unknown>;
12
+ export {};
@@ -0,0 +1,40 @@
1
+ import { useMutation, useQueryClient } from "@tanstack/react-query";
2
+ import { ofetch } from "ofetch";
3
+ import { useTranslateKitConfig } from "../../../../app/config";
4
+ import { handleNextApiError } from "../../../../shared/lib/errors/handleApiError";
5
+ import { DOCUMENT_STALENESS_QUERY_KEY } from "../queries/useDocumentStaleness";
6
+ /**
7
+ * Dismisses (acknowledges) staleness of one target locale, then refreshes the staleness query so the
8
+ * indicator disappears until the source changes again (#50).
9
+ */ export function useDismissStaleness() {
10
+ const queryClient = useQueryClient();
11
+ const { basePath } = useTranslateKitConfig();
12
+ return useMutation({
13
+ mutationKey: [
14
+ "dismiss-staleness"
15
+ ],
16
+ mutationFn: async ({ collection, id, target_lng })=>{
17
+ await handleNextApiError(()=>{
18
+ return ofetch(`/api${basePath}/stale/dismiss`, {
19
+ method: "post",
20
+ body: {
21
+ collection_slug: collection,
22
+ collection_id: id,
23
+ target_lng
24
+ }
25
+ });
26
+ });
27
+ },
28
+ onSuccess: (_data, { collection, id })=>{
29
+ queryClient.invalidateQueries({
30
+ queryKey: [
31
+ DOCUMENT_STALENESS_QUERY_KEY,
32
+ collection,
33
+ id
34
+ ]
35
+ });
36
+ }
37
+ });
38
+ }
39
+
40
+ //# sourceMappingURL=useDismissStaleness.js.map
@@ -2,6 +2,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
2
2
  import { ofetch } from "ofetch";
3
3
  import { useTranslateKitConfig } from "../../../../app/config";
4
4
  import { handleNextApiError } from "../../../../shared/lib/errors/handleApiError";
5
+ import { DOCUMENT_STALENESS_QUERY_KEY } from "../queries/useDocumentStaleness";
5
6
  export function useQueueDocumentTranslation() {
6
7
  const queryClient = useQueryClient();
7
8
  const { basePath } = useTranslateKitConfig();
@@ -26,6 +27,11 @@ export function useQueueDocumentTranslation() {
26
27
  "collection-translation-status"
27
28
  ]
28
29
  });
30
+ queryClient.invalidateQueries({
31
+ queryKey: [
32
+ DOCUMENT_STALENESS_QUERY_KEY
33
+ ]
34
+ });
29
35
  }
30
36
  });
31
37
  }
@@ -2,6 +2,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
2
2
  import { ofetch } from "ofetch";
3
3
  import { useTranslateKitConfig } from "../../../../app/config";
4
4
  import { handleNextApiError } from "../../../../shared/lib/errors/handleApiError";
5
+ import { DOCUMENT_STALENESS_QUERY_KEY } from "../queries/useDocumentStaleness";
5
6
  export function useRunDocumentTranslation() {
6
7
  const queryClient = useQueryClient();
7
8
  const { basePath } = useTranslateKitConfig();
@@ -20,6 +21,11 @@ export function useRunDocumentTranslation() {
20
21
  "document-translation-status"
21
22
  ]
22
23
  });
24
+ queryClient.invalidateQueries({
25
+ queryKey: [
26
+ DOCUMENT_STALENESS_QUERY_KEY
27
+ ]
28
+ });
23
29
  }
24
30
  });
25
31
  }
@@ -0,0 +1,17 @@
1
+ import type { CollectionSlug } from "payload";
2
+ import type { DocumentStaleness } from "../../model/types";
3
+ type Props = {
4
+ collection: CollectionSlug;
5
+ id: string;
6
+ };
7
+ type Options = {
8
+ enabled?: boolean;
9
+ };
10
+ export declare const DOCUMENT_STALENESS_QUERY_KEY = "document-staleness";
11
+ /**
12
+ * Reads per-locale staleness for a document (#50). Computed lazily on the server, so there is no
13
+ * polling — it refetches on mount / window focus and whenever the dismiss mutation invalidates its
14
+ * key. Reports nothing when provenance is disabled.
15
+ */
16
+ export declare function useDocumentStaleness({ collection, id }: Props, options?: Options): import("@tanstack/react-query").UseQueryResult<NoInfer<DocumentStaleness>, Error>;
17
+ export {};
@@ -0,0 +1,35 @@
1
+ import { useQuery } from "@tanstack/react-query";
2
+ import { ofetch } from "ofetch";
3
+ import { useTranslateKitConfig } from "../../../../app/config";
4
+ import { handleNextApiError } from "../../../../shared/lib/errors/handleApiError";
5
+ export const DOCUMENT_STALENESS_QUERY_KEY = "document-staleness";
6
+ const getQueryKey = ({ collection, id })=>[
7
+ DOCUMENT_STALENESS_QUERY_KEY,
8
+ collection,
9
+ id
10
+ ];
11
+ /**
12
+ * Reads per-locale staleness for a document (#50). Computed lazily on the server, so there is no
13
+ * polling — it refetches on mount / window focus and whenever the dismiss mutation invalidates its
14
+ * key. Reports nothing when provenance is disabled.
15
+ */ export function useDocumentStaleness({ collection, id }, options) {
16
+ const { basePath } = useTranslateKitConfig();
17
+ return useQuery({
18
+ queryKey: getQueryKey({
19
+ collection,
20
+ id
21
+ }),
22
+ queryFn: async ({ signal })=>{
23
+ return handleNextApiError(async ()=>{
24
+ const response = await ofetch(`/api${basePath}/stale/${collection}/${id}`, {
25
+ method: "get",
26
+ signal
27
+ });
28
+ return response.data;
29
+ });
30
+ },
31
+ enabled: options?.enabled
32
+ });
33
+ }
34
+
35
+ //# sourceMappingURL=useDocumentStaleness.js.map
@@ -1,8 +1,9 @@
1
1
  import { useQuery, useQueryClient } from "@tanstack/react-query";
2
2
  import { ofetch } from "ofetch";
3
- import { useCallback } from "react";
3
+ import { useCallback, useEffect, useRef } from "react";
4
4
  import { useTranslateKitConfig } from "../../../../app/config";
5
5
  import { handleNextApiError } from "../../../../shared/lib/errors/handleApiError";
6
+ import { DOCUMENT_STALENESS_QUERY_KEY } from "./useDocumentStaleness";
6
7
  const getDocumentTranslationQueryKey = ({ collection, id })=>{
7
8
  return [
8
9
  "document-translation-status",
@@ -45,6 +46,27 @@ export function useDocumentTranslation({ collection, id }, options) {
45
46
  id,
46
47
  queryClient
47
48
  ]);
49
+ // When a queued (async) translation transitions to "completed" in-session, the provenance
50
+ // record's sourceFingerprint has just been updated server-side — invalidate staleness so the
51
+ // "Out of date" notice clears without waiting for the next focus/remount refetch (#50).
52
+ // Seeded lazily (undefined) and gated on a *defined, non-completed → completed* transition, so a
53
+ // cold mount of an already-completed document does NOT fire a spurious extra refetch.
54
+ const previousStatusRef = useRef(undefined);
55
+ useEffect(()=>{
56
+ const previousStatus = previousStatusRef.current;
57
+ const currentStatus = query.data?.status;
58
+ if (currentStatus === "completed" && previousStatus !== undefined && previousStatus !== "completed") {
59
+ queryClient.invalidateQueries({
60
+ queryKey: [
61
+ DOCUMENT_STALENESS_QUERY_KEY
62
+ ]
63
+ });
64
+ }
65
+ previousStatusRef.current = currentStatus;
66
+ }, [
67
+ query.data?.status,
68
+ queryClient
69
+ ]);
48
70
  return {
49
71
  ...query,
50
72
  invalidate
@@ -1,20 +1,27 @@
1
1
  import { useCancelCollectionTranslations } from "./api/mutations/useCancelCollectionTranslations";
2
2
  import { useCancelDocumentTranslation } from "./api/mutations/useCancelDocumentTranslation";
3
+ import { useDismissStaleness } from "./api/mutations/useDismissStaleness";
3
4
  import { useQueueDocumentTranslation } from "./api/mutations/useQueueDocumentTranslation";
4
5
  import { useRunDocumentTranslation } from "./api/mutations/useRunDocumentTranslation";
5
6
  import { useCollectionTranslationStatus } from "./api/queries/useCollectionTranslationStatus";
7
+ import { useDocumentStaleness } from "./api/queries/useDocumentStaleness";
6
8
  import { useDocumentTranslation } from "./api/queries/useDocumentTranslation";
7
- export { CompletedTranslationStatus } from "./ui/CompletedTranslationStatus";
8
- export { FailedTranslationStatus } from "./ui/FailedTranslationStatus";
9
- export { RunningTranslationStatus } from "./ui/RunningTranslationStatus";
9
+ export { TranslationStatusList } from "./ui/TranslationStatusList";
10
10
  export declare const TranslationsApi: {
11
11
  useRunDocumentTranslation: typeof useRunDocumentTranslation;
12
12
  useQueueDocumentTranslation: typeof useQueueDocumentTranslation;
13
13
  useDocumentTranslation: typeof useDocumentTranslation;
14
+ useDocumentStaleness: typeof useDocumentStaleness;
14
15
  useCancelDocumentTranslation: typeof useCancelDocumentTranslation;
15
16
  useCollectionTranslationStatus: typeof useCollectionTranslationStatus;
16
17
  useCancelCollectionTranslations: typeof useCancelCollectionTranslations;
18
+ useDismissStaleness: typeof useDismissStaleness;
17
19
  };
18
- export { PendingTranslationStatus } from "./ui/PendingTranslationStatus";
19
- export type { DocumentTranslation, DocumentTranslationCompleted, DocumentTranslationFailed, DocumentTranslationPending, DocumentTranslationRunning, CollectionTranslationStatus, CollectionTranslationStatusItem, GroupedCollectionTranslationStatus, } from "./model/types";
20
+ export type { DocumentTranslation, DocumentTranslationCompleted, DocumentTranslationFailed, DocumentTranslationPending, DocumentTranslationRunning, CollectionTranslationStatus, CollectionTranslationStatusItem, GroupedCollectionTranslationStatus, DocumentStaleness, StalenessLocale, } from "./model/types";
20
21
  export { DocumentTranslationStatus } from "./model/enums";
22
+ export { derivePanelStatus, deriveCollectionPanelStatus, describePanelStatus, } from "./model/panelStatus";
23
+ export type { PanelStatus, MarkerTone } from "./model/panelStatus";
24
+ export { PanelStatusMarker } from "./ui/PanelStatusMarker";
25
+ export { ActionButton } from "./ui/ActionButton";
26
+ export { buildTranslationStatusRows } from "./model/statusRows";
27
+ export type { TranslationStatusRow, TranslationRowState } from "./model/statusRows";
@@ -1,21 +1,26 @@
1
1
  import { useCancelCollectionTranslations } from "./api/mutations/useCancelCollectionTranslations";
2
2
  import { useCancelDocumentTranslation } from "./api/mutations/useCancelDocumentTranslation";
3
+ import { useDismissStaleness } from "./api/mutations/useDismissStaleness";
3
4
  import { useQueueDocumentTranslation } from "./api/mutations/useQueueDocumentTranslation";
4
5
  import { useRunDocumentTranslation } from "./api/mutations/useRunDocumentTranslation";
5
6
  import { useCollectionTranslationStatus } from "./api/queries/useCollectionTranslationStatus";
7
+ import { useDocumentStaleness } from "./api/queries/useDocumentStaleness";
6
8
  import { useDocumentTranslation } from "./api/queries/useDocumentTranslation";
7
- export { CompletedTranslationStatus } from "./ui/CompletedTranslationStatus";
8
- export { FailedTranslationStatus } from "./ui/FailedTranslationStatus";
9
- export { RunningTranslationStatus } from "./ui/RunningTranslationStatus";
9
+ export { TranslationStatusList } from "./ui/TranslationStatusList";
10
10
  export const TranslationsApi = {
11
11
  useRunDocumentTranslation,
12
12
  useQueueDocumentTranslation,
13
13
  useDocumentTranslation,
14
+ useDocumentStaleness,
14
15
  useCancelDocumentTranslation,
15
16
  useCollectionTranslationStatus,
16
- useCancelCollectionTranslations
17
+ useCancelCollectionTranslations,
18
+ useDismissStaleness
17
19
  };
18
- export { PendingTranslationStatus } from "./ui/PendingTranslationStatus";
19
20
  export { DocumentTranslationStatus } from "./model/enums";
21
+ export { derivePanelStatus, deriveCollectionPanelStatus, describePanelStatus } from "./model/panelStatus";
22
+ export { PanelStatusMarker } from "./ui/PanelStatusMarker";
23
+ export { ActionButton } from "./ui/ActionButton";
24
+ export { buildTranslationStatusRows } from "./model/statusRows";
20
25
 
21
26
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,60 @@
1
+ import { DocumentTranslationStatus } from "./enums";
2
+ import type { StatusDotColor } from "./statusRows";
3
+ import type { GroupedCollectionTranslationStatus } from "./types";
4
+ /**
5
+ * The single aggregate signal shown on a translate trigger (document or collection). Detail lives in
6
+ * the popup; the panel only surfaces the most urgent state at a glance (see the panel-UI design doc).
7
+ */
8
+ export type PanelStatus = {
9
+ kind: "failed";
10
+ } | {
11
+ kind: "running";
12
+ } | {
13
+ kind: "stale";
14
+ staleLocales: string[];
15
+ } | {
16
+ kind: "fresh";
17
+ } | {
18
+ kind: "none";
19
+ };
20
+ /** The marker tones — every panel state except `none` (which shows no marker). */
21
+ export type MarkerTone = Exclude<PanelStatus["kind"], "none">;
22
+ /**
23
+ * Dot colour + motion per aggregate marker tone. Mirrors the status list's `STATE_DOT` vocabulary
24
+ * (failed=red, in-progress=blue+pulse, stale=amber, fresh=green) so the trigger badge and the popup
25
+ * rows read as one system.
26
+ */
27
+ export declare const MARKER_DOT: Record<MarkerTone, {
28
+ color: StatusDotColor;
29
+ animated?: boolean;
30
+ }>;
31
+ /**
32
+ * Map an aggregate {@link PanelStatus} to the trigger marker's tone + hover/AT text. Shared by the
33
+ * document and collection triggers so both name their states the same way. `fallbackTitle` is the
34
+ * "nothing to signal" label (each surface passes its own, e.g. "Translate this document").
35
+ */
36
+ export declare function describePanelStatus(status: PanelStatus | undefined, fallbackTitle: string): {
37
+ tone?: MarkerTone;
38
+ title: string;
39
+ };
40
+ type PanelStatusInput = {
41
+ /** Status of the latest translation job, if any. */
42
+ runStatus?: DocumentTranslationStatus | null;
43
+ /** Target locales whose translation is out of date (from staleness detection). */
44
+ staleLocales: string[];
45
+ };
46
+ /**
47
+ * Collapse the latest run status + per-locale staleness into one panel marker, by priority:
48
+ * `failed → running → stale → fresh → none`. A more urgent transient state (a failed or in-flight
49
+ * job) outranks the durable "out of date" signal; `fresh` means a completed translation with nothing
50
+ * stale; `none` means nothing has been translated yet.
51
+ */
52
+ export declare function derivePanelStatus({ runStatus, staleLocales }: PanelStatusInput): PanelStatus;
53
+ /**
54
+ * Collapse a collection's grouped job counts into one trigger marker. Priority:
55
+ * `in-progress (running/pending) → failed → fresh → none`. In-progress outranks failures because
56
+ * work is actively happening and self-resolves; once nothing is running, a failure is surfaced.
57
+ * There is no `stale` here — staleness is a per-document signal, not tracked for the bulk dashboard.
58
+ */
59
+ export declare function deriveCollectionPanelStatus(status: GroupedCollectionTranslationStatus | undefined): PanelStatus;
60
+ export {};
@@ -0,0 +1,101 @@
1
+ import { DocumentTranslationStatus } from "./enums";
2
+ /**
3
+ * Dot colour + motion per aggregate marker tone. Mirrors the status list's `STATE_DOT` vocabulary
4
+ * (failed=red, in-progress=blue+pulse, stale=amber, fresh=green) so the trigger badge and the popup
5
+ * rows read as one system.
6
+ */ export const MARKER_DOT = {
7
+ failed: {
8
+ color: "red"
9
+ },
10
+ running: {
11
+ color: "blue",
12
+ animated: true
13
+ },
14
+ stale: {
15
+ color: "amber"
16
+ },
17
+ fresh: {
18
+ color: "green"
19
+ }
20
+ };
21
+ /**
22
+ * Map an aggregate {@link PanelStatus} to the trigger marker's tone + hover/AT text. Shared by the
23
+ * document and collection triggers so both name their states the same way. `fallbackTitle` is the
24
+ * "nothing to signal" label (each surface passes its own, e.g. "Translate this document").
25
+ */ export function describePanelStatus(status, fallbackTitle) {
26
+ switch(status?.kind){
27
+ case "failed":
28
+ return {
29
+ tone: "failed",
30
+ title: "Last translation failed"
31
+ };
32
+ case "running":
33
+ return {
34
+ tone: "running",
35
+ title: "Translation in progress…"
36
+ };
37
+ case "stale":
38
+ return {
39
+ tone: "stale",
40
+ title: `Out of date: ${status.staleLocales.map((l)=>l.toLowerCase()).join(", ")}`
41
+ };
42
+ case "fresh":
43
+ return {
44
+ tone: "fresh",
45
+ title: "Translations up to date"
46
+ };
47
+ default:
48
+ return {
49
+ title: fallbackTitle
50
+ };
51
+ }
52
+ }
53
+ /**
54
+ * Collapse the latest run status + per-locale staleness into one panel marker, by priority:
55
+ * `failed → running → stale → fresh → none`. A more urgent transient state (a failed or in-flight
56
+ * job) outranks the durable "out of date" signal; `fresh` means a completed translation with nothing
57
+ * stale; `none` means nothing has been translated yet.
58
+ */ export function derivePanelStatus({ runStatus, staleLocales }) {
59
+ if (runStatus === DocumentTranslationStatus.FAILED) return {
60
+ kind: "failed"
61
+ };
62
+ if (runStatus === DocumentTranslationStatus.RUNNING || runStatus === DocumentTranslationStatus.PENDING) {
63
+ return {
64
+ kind: "running"
65
+ };
66
+ }
67
+ if (staleLocales.length > 0) return {
68
+ kind: "stale",
69
+ staleLocales
70
+ };
71
+ if (runStatus === DocumentTranslationStatus.COMPLETED) return {
72
+ kind: "fresh"
73
+ };
74
+ return {
75
+ kind: "none"
76
+ };
77
+ }
78
+ /**
79
+ * Collapse a collection's grouped job counts into one trigger marker. Priority:
80
+ * `in-progress (running/pending) → failed → fresh → none`. In-progress outranks failures because
81
+ * work is actively happening and self-resolves; once nothing is running, a failure is surfaced.
82
+ * There is no `stale` here — staleness is a per-document signal, not tracked for the bulk dashboard.
83
+ */ export function deriveCollectionPanelStatus(status) {
84
+ if (!status) return {
85
+ kind: "none"
86
+ };
87
+ if (status.running.length > 0 || status.pending.length > 0) return {
88
+ kind: "running"
89
+ };
90
+ if (status.failed.length > 0) return {
91
+ kind: "failed"
92
+ };
93
+ if (status.completed.length > 0) return {
94
+ kind: "fresh"
95
+ };
96
+ return {
97
+ kind: "none"
98
+ };
99
+ }
100
+
101
+ //# sourceMappingURL=panelStatus.js.map
@@ -0,0 +1,42 @@
1
+ import type { DocumentStaleness, DocumentTranslation } from "./types";
2
+ /**
3
+ * One row in the unified per-locale STATUS list. Collapses what used to be three separate visual
4
+ * treatments (the completed chip, the "out of date" list, and the running/pending/failed chips) into
5
+ * a single consistent shape keyed by target locale.
6
+ */
7
+ export type TranslationRowState = "failed" | "running" | "pending" | "stale" | "translated";
8
+ export type TranslationStatusRow = {
9
+ /** Stable key — the target locale (one row per target). */
10
+ targetLocale: string;
11
+ sourceLocale: string;
12
+ state: TranslationRowState;
13
+ /** ISO timestamp for the row's caption (translation time, or the job's last update). */
14
+ at?: string;
15
+ /** Set together with a transient `state` (failed/running/pending) — the job id run/cancel act on. */
16
+ jobId?: string;
17
+ /** Failure reason, present only for `state === "failed"`, so the row can surface why it failed. */
18
+ error?: string;
19
+ };
20
+ /** The status dot vocabulary (a `ColorIndicator` colour), shared by the list badge and the panel marker. */
21
+ export type StatusDotColor = "red" | "green" | "blue" | "gray" | "amber";
22
+ /**
23
+ * Dot colour + motion per row state. Only the actively-processing state pulses, so motion means
24
+ * "in progress now". The single source of truth for the status colour vocabulary — the panel
25
+ * trigger marker mirrors it (see `MARKER_DOT`).
26
+ */
27
+ export declare const STATE_DOT: Record<TranslationRowState, {
28
+ color: StatusDotColor;
29
+ animated?: boolean;
30
+ }>;
31
+ /**
32
+ * Build the unified status rows from per-locale staleness + the single latest job.
33
+ *
34
+ * Rows come from `staleness.locales` (one per translated target: `stale` or `translated`). The latest
35
+ * job is overlaid onto its target locale: a **transient** job state (failed/running/pending) wins over
36
+ * the durable signal for that locale, and a job for a target with no provenance row yet adds a row.
37
+ * Sorted `failed → running → pending → stale → translated`, newest first within a group.
38
+ */
39
+ export declare function buildTranslationStatusRows(input: {
40
+ staleness?: DocumentStaleness | null;
41
+ run?: DocumentTranslation;
42
+ }): TranslationStatusRow[];
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Dot colour + motion per row state. Only the actively-processing state pulses, so motion means
3
+ * "in progress now". The single source of truth for the status colour vocabulary — the panel
4
+ * trigger marker mirrors it (see `MARKER_DOT`).
5
+ */ export const STATE_DOT = {
6
+ failed: {
7
+ color: "red"
8
+ },
9
+ running: {
10
+ color: "blue",
11
+ animated: true
12
+ },
13
+ pending: {
14
+ color: "gray"
15
+ },
16
+ stale: {
17
+ color: "amber"
18
+ },
19
+ translated: {
20
+ color: "green"
21
+ }
22
+ };
23
+ // Most urgent first. Transient job states outrank the durable stale/translated signal.
24
+ const ORDER = {
25
+ failed: 0,
26
+ running: 1,
27
+ pending: 2,
28
+ stale: 3,
29
+ translated: 4
30
+ };
31
+ const TRANSIENT = new Set([
32
+ "failed",
33
+ "running",
34
+ "pending"
35
+ ]);
36
+ // Maps the closed 4-member job-status set to a row state. "completed" → "translated"; the transient
37
+ // three pass through. If a new DocumentTranslationStatus is ever added, extend this explicitly —
38
+ // otherwise it would silently render as "translated".
39
+ const toRowState = (jobStatus)=>jobStatus === "failed" || jobStatus === "running" || jobStatus === "pending" ? jobStatus : "translated";
40
+ /**
41
+ * Build the unified status rows from per-locale staleness + the single latest job.
42
+ *
43
+ * Rows come from `staleness.locales` (one per translated target: `stale` or `translated`). The latest
44
+ * job is overlaid onto its target locale: a **transient** job state (failed/running/pending) wins over
45
+ * the durable signal for that locale, and a job for a target with no provenance row yet adds a row.
46
+ * Sorted `failed → running → pending → stale → translated`, newest first within a group.
47
+ */ export function buildTranslationStatusRows(input) {
48
+ const { staleness, run } = input;
49
+ const byTarget = new Map();
50
+ for (const locale of staleness?.locales ?? []){
51
+ byTarget.set(locale.target_lng, {
52
+ targetLocale: locale.target_lng,
53
+ sourceLocale: locale.source_lng,
54
+ state: locale.is_stale ? "stale" : "translated",
55
+ at: locale.translated_at
56
+ });
57
+ }
58
+ if (run) {
59
+ const state = toRowState(run.status);
60
+ const target = run.input.target_lng;
61
+ const existing = byTarget.get(target);
62
+ // Overlay the job only when it adds information: a transient state, or a locale not yet listed.
63
+ if (TRANSIENT.has(state) || !existing) {
64
+ byTarget.set(target, {
65
+ targetLocale: target,
66
+ sourceLocale: run.input.source_lng,
67
+ state: existing && !TRANSIENT.has(state) ? existing.state : state,
68
+ at: run.updated_at,
69
+ jobId: TRANSIENT.has(state) ? run.id : existing?.jobId,
70
+ error: run.status === "failed" ? run.error.message : undefined
71
+ });
72
+ }
73
+ }
74
+ return [
75
+ ...byTarget.values()
76
+ ].sort((a, b)=>ORDER[a.state] - ORDER[b.state] || (b.at ?? "").localeCompare(a.at ?? ""));
77
+ }
78
+
79
+ //# sourceMappingURL=statusRows.js.map
@@ -44,4 +44,15 @@ export type CollectionTranslationStatus = {
44
44
  docs: Array<CollectionTranslationStatusItem>;
45
45
  };
46
46
  export type GroupedCollectionTranslationStatus = Record<DocumentTranslationStatus, CollectionTranslationStatusItem[]>;
47
+ /** One translated target locale's staleness relative to its source (#50). */
48
+ export type StalenessLocale = {
49
+ target_lng: string;
50
+ source_lng: string;
51
+ is_stale: boolean;
52
+ translated_at: string;
53
+ };
54
+ /** Per-locale staleness for a single document. */
55
+ export type DocumentStaleness = {
56
+ locales: StalenessLocale[];
57
+ };
47
58
  export {};
@@ -1,3 +1,3 @@
1
- export { };
1
+ /** Per-locale staleness for a single document. */ export { };
2
2
 
3
3
  //# sourceMappingURL=types.js.map
@@ -0,0 +1,14 @@
1
+ import type { ReactNode } from "react";
2
+ /**
3
+ * A row action shared by the document and collection status lists: a bordered, neutral icon button
4
+ * + tooltip. One source of truth so both popovers' actions look identical. The icon gives the
5
+ * affordance; the tooltip carries the explanation (a bare label can't say "hide until the source
6
+ * changes again").
7
+ */
8
+ export declare function ActionButton({ icon, title, onClick, disabled, loading, }: {
9
+ icon: ReactNode;
10
+ title: string;
11
+ onClick: () => void;
12
+ disabled?: boolean;
13
+ loading?: boolean;
14
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import Button from "../../../../shared/ui/Button";
3
+ import Tooltip from "../../../../shared/ui/Tooltip";
4
+ import styles from "./styles.module.scss";
5
+ /**
6
+ * A row action shared by the document and collection status lists: a bordered, neutral icon button
7
+ * + tooltip. One source of truth so both popovers' actions look identical. The icon gives the
8
+ * affordance; the tooltip carries the explanation (a bare label can't say "hide until the source
9
+ * changes again").
10
+ */ export function ActionButton({ icon, title, onClick, disabled, loading }) {
11
+ return /*#__PURE__*/ _jsx(Tooltip, {
12
+ content: title,
13
+ children: /*#__PURE__*/ _jsx(Button, {
14
+ type: "button",
15
+ $variant: "transparent",
16
+ $size: "sm",
17
+ $isIconButton: true,
18
+ className: styles.action,
19
+ "aria-label": title,
20
+ onClick: onClick,
21
+ disabled: disabled,
22
+ $isLoading: loading,
23
+ children: icon
24
+ })
25
+ });
26
+ }
27
+
28
+ //# sourceMappingURL=ActionButton.js.map
@@ -0,0 +1 @@
1
+ export { ActionButton } from "./ActionButton";
@@ -0,0 +1,3 @@
1
+ export { ActionButton } from "./ActionButton";
2
+
3
+ //# sourceMappingURL=index.js.map