@purposeinplay/payload-ai-translate 0.1.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 (301) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +714 -0
  3. package/dist/alerts-collection.d.ts +21 -0
  4. package/dist/alerts-collection.js +159 -0
  5. package/dist/api.d.ts +4 -0
  6. package/dist/api.js +918 -0
  7. package/dist/bulk-translate-batches-collection.d.ts +29 -0
  8. package/dist/bulk-translate-batches-collection.js +404 -0
  9. package/dist/bulk-translate-units-collection.d.ts +35 -0
  10. package/dist/bulk-translate-units-collection.js +310 -0
  11. package/dist/client/estimated-cost-cell.d.ts +6 -0
  12. package/dist/client/estimated-cost-cell.js +12 -0
  13. package/dist/client/excluded-fields-field.d.ts +45 -0
  14. package/dist/client/excluded-fields-field.js +553 -0
  15. package/dist/client/field-translate-button.d.ts +6 -0
  16. package/dist/client/field-translate-button.js +199 -0
  17. package/dist/client/index.d.ts +6 -0
  18. package/dist/client/index.js +6 -0
  19. package/dist/client/lib/use-global-kill-switches.d.ts +20 -0
  20. package/dist/client/lib/use-global-kill-switches.js +58 -0
  21. package/dist/client/translate-button.d.ts +2 -0
  22. package/dist/client/translate-button.js +228 -0
  23. package/dist/client/translate-modal.d.ts +16 -0
  24. package/dist/client/translate-modal.js +549 -0
  25. package/dist/client/translation-progress.d.ts +10 -0
  26. package/dist/client/translation-progress.js +297 -0
  27. package/dist/components/TranslationNavGroup.d.ts +45 -0
  28. package/dist/components/TranslationNavGroup.js +104 -0
  29. package/dist/defaults.d.ts +11 -0
  30. package/dist/defaults.js +16 -0
  31. package/dist/endpoints/client-config.d.ts +44 -0
  32. package/dist/endpoints/client-config.js +145 -0
  33. package/dist/endpoints/estimate.d.ts +5 -0
  34. package/dist/endpoints/estimate.js +237 -0
  35. package/dist/endpoints/progress.d.ts +2 -0
  36. package/dist/endpoints/progress.js +314 -0
  37. package/dist/endpoints/translate.d.ts +11 -0
  38. package/dist/endpoints/translate.js +376 -0
  39. package/dist/endpoints/translation-hub/_helpers.d.ts +140 -0
  40. package/dist/endpoints/translation-hub/_helpers.js +297 -0
  41. package/dist/endpoints/translation-hub/active.d.ts +21 -0
  42. package/dist/endpoints/translation-hub/active.js +220 -0
  43. package/dist/endpoints/translation-hub/cancel.d.ts +22 -0
  44. package/dist/endpoints/translation-hub/cancel.js +233 -0
  45. package/dist/endpoints/translation-hub/enqueue.d.ts +70 -0
  46. package/dist/endpoints/translation-hub/enqueue.js +529 -0
  47. package/dist/endpoints/translation-hub/failures.d.ts +12 -0
  48. package/dist/endpoints/translation-hub/failures.js +67 -0
  49. package/dist/endpoints/translation-hub/force-reset.d.ts +20 -0
  50. package/dist/endpoints/translation-hub/force-reset.js +144 -0
  51. package/dist/endpoints/translation-hub/index.d.ts +21 -0
  52. package/dist/endpoints/translation-hub/index.js +20 -0
  53. package/dist/endpoints/translation-hub/list.d.ts +40 -0
  54. package/dist/endpoints/translation-hub/list.js +182 -0
  55. package/dist/endpoints/translation-hub/preflight.d.ts +19 -0
  56. package/dist/endpoints/translation-hub/preflight.js +141 -0
  57. package/dist/endpoints/translation-hub/retry-failed.d.ts +38 -0
  58. package/dist/endpoints/translation-hub/retry-failed.js +235 -0
  59. package/dist/endpoints/translation-hub/revert.d.ts +88 -0
  60. package/dist/endpoints/translation-hub/revert.js +405 -0
  61. package/dist/endpoints/translation-hub/status.d.ts +45 -0
  62. package/dist/endpoints/translation-hub/status.js +391 -0
  63. package/dist/endpoints/translation-hub/usage-summary.d.ts +114 -0
  64. package/dist/endpoints/translation-hub/usage-summary.js +481 -0
  65. package/dist/exports/client.d.ts +6 -0
  66. package/dist/exports/client.js +6 -0
  67. package/dist/exports/components.d.ts +6 -0
  68. package/dist/exports/components.js +5 -0
  69. package/dist/exports/index.d.ts +8 -0
  70. package/dist/exports/index.js +7 -0
  71. package/dist/exports/providers.d.ts +9 -0
  72. package/dist/exports/providers.js +5 -0
  73. package/dist/exports/views-client.d.ts +23 -0
  74. package/dist/exports/views-client.js +22 -0
  75. package/dist/exports/views.d.ts +30 -0
  76. package/dist/exports/views.js +29 -0
  77. package/dist/hooks/after-change-global.d.ts +4 -0
  78. package/dist/hooks/after-change-global.js +109 -0
  79. package/dist/hooks/after-change.d.ts +16 -0
  80. package/dist/hooks/after-change.js +205 -0
  81. package/dist/hooks/after-delete.d.ts +30 -0
  82. package/dist/hooks/after-delete.js +95 -0
  83. package/dist/index.d.ts +5 -0
  84. package/dist/index.js +5 -0
  85. package/dist/jobs-collection.d.ts +17 -0
  86. package/dist/jobs-collection.js +139 -0
  87. package/dist/lexical/classifier.d.ts +3 -0
  88. package/dist/lexical/classifier.js +108 -0
  89. package/dist/lexical/deserializer.d.ts +4 -0
  90. package/dist/lexical/deserializer.js +263 -0
  91. package/dist/lexical/placeholder-integrity.d.ts +6 -0
  92. package/dist/lexical/placeholder-integrity.js +21 -0
  93. package/dist/lexical/placeholders.d.ts +21 -0
  94. package/dist/lexical/placeholders.js +117 -0
  95. package/dist/lexical/serializer.d.ts +21 -0
  96. package/dist/lexical/serializer.js +233 -0
  97. package/dist/lexical/types.d.ts +32 -0
  98. package/dist/lexical/types.js +1 -0
  99. package/dist/lib/auth-diagnostics.d.ts +14 -0
  100. package/dist/lib/auth-diagnostics.js +19 -0
  101. package/dist/lib/batch-counts.d.ts +58 -0
  102. package/dist/lib/batch-counts.js +105 -0
  103. package/dist/lib/bulk-translate-migrations.d.ts +92 -0
  104. package/dist/lib/bulk-translate-migrations.js +153 -0
  105. package/dist/lib/coalescing-queue.d.ts +38 -0
  106. package/dist/lib/coalescing-queue.js +69 -0
  107. package/dist/lib/content-extractor.d.ts +16 -0
  108. package/dist/lib/content-extractor.js +410 -0
  109. package/dist/lib/content-hash.d.ts +1 -0
  110. package/dist/lib/content-hash.js +19 -0
  111. package/dist/lib/content-patcher.d.ts +15 -0
  112. package/dist/lib/content-patcher.js +293 -0
  113. package/dist/lib/cost-guards.d.ts +2 -0
  114. package/dist/lib/cost-guards.js +18 -0
  115. package/dist/lib/daily-spend-cap.d.ts +58 -0
  116. package/dist/lib/daily-spend-cap.js +233 -0
  117. package/dist/lib/effective-locales.d.ts +181 -0
  118. package/dist/lib/effective-locales.js +302 -0
  119. package/dist/lib/error-messages.d.ts +245 -0
  120. package/dist/lib/error-messages.js +626 -0
  121. package/dist/lib/events.d.ts +39 -0
  122. package/dist/lib/events.js +146 -0
  123. package/dist/lib/exclude-fields.d.ts +3 -0
  124. package/dist/lib/exclude-fields.js +64 -0
  125. package/dist/lib/field-breadcrumb.d.ts +31 -0
  126. package/dist/lib/field-breadcrumb.js +227 -0
  127. package/dist/lib/field-diff.d.ts +1 -0
  128. package/dist/lib/field-diff.js +25 -0
  129. package/dist/lib/field-empty.d.ts +2 -0
  130. package/dist/lib/field-empty.js +68 -0
  131. package/dist/lib/field-resolver.d.ts +3 -0
  132. package/dist/lib/field-resolver.js +164 -0
  133. package/dist/lib/group-soft-skips.d.ts +39 -0
  134. package/dist/lib/group-soft-skips.js +45 -0
  135. package/dist/lib/locale-merge.d.ts +44 -0
  136. package/dist/lib/locale-merge.js +357 -0
  137. package/dist/lib/locale-row-check.d.ts +30 -0
  138. package/dist/lib/locale-row-check.js +64 -0
  139. package/dist/lib/logger.d.ts +74 -0
  140. package/dist/lib/logger.js +97 -0
  141. package/dist/lib/manual-edit-guard.d.ts +128 -0
  142. package/dist/lib/manual-edit-guard.js +393 -0
  143. package/dist/lib/output-validation.d.ts +48 -0
  144. package/dist/lib/output-validation.js +148 -0
  145. package/dist/lib/payload-read.d.ts +16 -0
  146. package/dist/lib/payload-read.js +51 -0
  147. package/dist/lib/per-doc-claim.d.ts +90 -0
  148. package/dist/lib/per-doc-claim.js +140 -0
  149. package/dist/lib/per-doc-lock.d.ts +94 -0
  150. package/dist/lib/per-doc-lock.js +119 -0
  151. package/dist/lib/persist-usage.d.ts +91 -0
  152. package/dist/lib/persist-usage.js +116 -0
  153. package/dist/lib/progress-store.d.ts +103 -0
  154. package/dist/lib/progress-store.js +314 -0
  155. package/dist/lib/rate-limiter.d.ts +3 -0
  156. package/dist/lib/rate-limiter.js +53 -0
  157. package/dist/lib/snapshot-select.d.ts +43 -0
  158. package/dist/lib/snapshot-select.js +108 -0
  159. package/dist/lib/translate-prompt.d.ts +31 -0
  160. package/dist/lib/translate-prompt.js +66 -0
  161. package/dist/lib/translation-token-bucket.d.ts +57 -0
  162. package/dist/lib/translation-token-bucket.js +365 -0
  163. package/dist/lib/truncate-source-value.d.ts +1 -0
  164. package/dist/lib/truncate-source-value.js +27 -0
  165. package/dist/manual-edit-collection.d.ts +22 -0
  166. package/dist/manual-edit-collection.js +124 -0
  167. package/dist/plugin.d.ts +3 -0
  168. package/dist/plugin.js +934 -0
  169. package/dist/providers/ai-sdk-adapter.d.ts +35 -0
  170. package/dist/providers/ai-sdk-adapter.js +100 -0
  171. package/dist/providers/anthropic.d.ts +31 -0
  172. package/dist/providers/anthropic.js +66 -0
  173. package/dist/providers/custom.d.ts +36 -0
  174. package/dist/providers/custom.js +24 -0
  175. package/dist/providers/gemini.d.ts +20 -0
  176. package/dist/providers/gemini.js +48 -0
  177. package/dist/providers/mock.d.ts +2 -0
  178. package/dist/providers/mock.js +29 -0
  179. package/dist/providers/openai.d.ts +28 -0
  180. package/dist/providers/openai.js +69 -0
  181. package/dist/settings-global.d.ts +74 -0
  182. package/dist/settings-global.js +216 -0
  183. package/dist/tasks/bulk-translate-coordinator.d.ts +115 -0
  184. package/dist/tasks/bulk-translate-coordinator.js +708 -0
  185. package/dist/tasks/bulk-translate-doc-task.d.ts +142 -0
  186. package/dist/tasks/bulk-translate-doc-task.js +1000 -0
  187. package/dist/tasks/bulk-translate-janitor.d.ts +87 -0
  188. package/dist/tasks/bulk-translate-janitor.js +311 -0
  189. package/dist/tasks/translate-job-task.d.ts +51 -0
  190. package/dist/tasks/translate-job-task.js +154 -0
  191. package/dist/translate.d.ts +113 -0
  192. package/dist/translate.js +911 -0
  193. package/dist/translation-daily-spend-collection.d.ts +24 -0
  194. package/dist/translation-daily-spend-collection.js +133 -0
  195. package/dist/translation-rate-limits-collection.d.ts +30 -0
  196. package/dist/translation-rate-limits-collection.js +144 -0
  197. package/dist/types.d.ts +672 -0
  198. package/dist/types.js +1 -0
  199. package/dist/usage-collection.d.ts +14 -0
  200. package/dist/usage-collection.js +377 -0
  201. package/dist/views/BulkRunsHub/BatchRow.d.ts +32 -0
  202. package/dist/views/BulkRunsHub/BatchRow.js +1222 -0
  203. package/dist/views/BulkRunsHub/BucketRow.d.ts +62 -0
  204. package/dist/views/BulkRunsHub/BucketRow.js +982 -0
  205. package/dist/views/BulkRunsHub/BulkRunsHub.client.d.ts +18 -0
  206. package/dist/views/BulkRunsHub/BulkRunsHub.client.js +331 -0
  207. package/dist/views/BulkRunsHub/EmptyState.d.ts +6 -0
  208. package/dist/views/BulkRunsHub/EmptyState.js +64 -0
  209. package/dist/views/BulkRunsHub/FilterBar.d.ts +16 -0
  210. package/dist/views/BulkRunsHub/FilterBar.js +284 -0
  211. package/dist/views/BulkRunsHub/InFlightBanner.d.ts +14 -0
  212. package/dist/views/BulkRunsHub/InFlightBanner.js +59 -0
  213. package/dist/views/BulkRunsHub/StatusBadge.d.ts +64 -0
  214. package/dist/views/BulkRunsHub/StatusBadge.js +248 -0
  215. package/dist/views/BulkRunsHub/SummaryStrip.d.ts +22 -0
  216. package/dist/views/BulkRunsHub/SummaryStrip.js +249 -0
  217. package/dist/views/BulkRunsHub/bucket-grouping.d.ts +200 -0
  218. package/dist/views/BulkRunsHub/bucket-grouping.js +344 -0
  219. package/dist/views/BulkRunsHub/bucketFailureSummary.d.ts +9 -0
  220. package/dist/views/BulkRunsHub/bucketFailureSummary.js +36 -0
  221. package/dist/views/BulkRunsHub/dedupedStatusFetch.d.ts +5 -0
  222. package/dist/views/BulkRunsHub/dedupedStatusFetch.js +45 -0
  223. package/dist/views/BulkRunsHub/index.d.ts +17 -0
  224. package/dist/views/BulkRunsHub/index.js +80 -0
  225. package/dist/views/BulkRunsHub/urlFilters.d.ts +14 -0
  226. package/dist/views/BulkRunsHub/urlFilters.js +50 -0
  227. package/dist/views/BulkRunsHub/useBulkRunsList.d.ts +26 -0
  228. package/dist/views/BulkRunsHub/useBulkRunsList.js +204 -0
  229. package/dist/views/BulkRunsHub/useUrlFilters.d.ts +10 -0
  230. package/dist/views/BulkRunsHub/useUrlFilters.js +88 -0
  231. package/dist/views/TranslationHub/ActiveJobs.d.ts +6 -0
  232. package/dist/views/TranslationHub/ActiveJobs.js +320 -0
  233. package/dist/views/TranslationHub/AdvancedPanel.d.ts +17 -0
  234. package/dist/views/TranslationHub/AdvancedPanel.js +996 -0
  235. package/dist/views/TranslationHub/AlertBanner.d.ts +6 -0
  236. package/dist/views/TranslationHub/AlertBanner.js +568 -0
  237. package/dist/views/TranslationHub/AuditPanel.d.ts +6 -0
  238. package/dist/views/TranslationHub/AuditPanel.helpers.d.ts +44 -0
  239. package/dist/views/TranslationHub/AuditPanel.helpers.js +71 -0
  240. package/dist/views/TranslationHub/AuditPanel.js +1367 -0
  241. package/dist/views/TranslationHub/BulkTranslate.types.d.ts +242 -0
  242. package/dist/views/TranslationHub/BulkTranslate.types.js +36 -0
  243. package/dist/views/TranslationHub/BulkTranslateFailureDrawer.d.ts +19 -0
  244. package/dist/views/TranslationHub/BulkTranslateFailureDrawer.js +332 -0
  245. package/dist/views/TranslationHub/BulkTranslateMonitor.d.ts +28 -0
  246. package/dist/views/TranslationHub/BulkTranslateMonitor.js +305 -0
  247. package/dist/views/TranslationHub/BulkTranslateNarrowViewportBanner.d.ts +3 -0
  248. package/dist/views/TranslationHub/BulkTranslateNarrowViewportBanner.js +42 -0
  249. package/dist/views/TranslationHub/BulkTranslatePostEnqueueTransition.d.ts +26 -0
  250. package/dist/views/TranslationHub/BulkTranslatePostEnqueueTransition.js +95 -0
  251. package/dist/views/TranslationHub/BulkTranslatePreflightModal.d.ts +22 -0
  252. package/dist/views/TranslationHub/BulkTranslatePreflightModal.js +879 -0
  253. package/dist/views/TranslationHub/BulkTranslateTerminalCard.d.ts +29 -0
  254. package/dist/views/TranslationHub/BulkTranslateTerminalCard.js +445 -0
  255. package/dist/views/TranslationHub/BulkTranslateTrigger.d.ts +66 -0
  256. package/dist/views/TranslationHub/BulkTranslateTrigger.js +161 -0
  257. package/dist/views/TranslationHub/EditorRecentRunsPanel.d.ts +33 -0
  258. package/dist/views/TranslationHub/EditorRecentRunsPanel.js +290 -0
  259. package/dist/views/TranslationHub/Hub.client.d.ts +74 -0
  260. package/dist/views/TranslationHub/Hub.client.js +357 -0
  261. package/dist/views/TranslationHub/ModelCombobox.d.ts +14 -0
  262. package/dist/views/TranslationHub/ModelCombobox.js +415 -0
  263. package/dist/views/TranslationHub/PerCollectionConfig.d.ts +10 -0
  264. package/dist/views/TranslationHub/PerCollectionConfig.helpers.d.ts +16 -0
  265. package/dist/views/TranslationHub/PerCollectionConfig.helpers.js +19 -0
  266. package/dist/views/TranslationHub/PerCollectionConfig.js +759 -0
  267. package/dist/views/TranslationHub/SettingsRail.d.ts +11 -0
  268. package/dist/views/TranslationHub/SettingsRail.js +382 -0
  269. package/dist/views/TranslationHub/StatusStrip.d.ts +6 -0
  270. package/dist/views/TranslationHub/StatusStrip.js +451 -0
  271. package/dist/views/TranslationHub/UsageTable.d.ts +6 -0
  272. package/dist/views/TranslationHub/UsageTable.helpers.d.ts +69 -0
  273. package/dist/views/TranslationHub/UsageTable.helpers.js +49 -0
  274. package/dist/views/TranslationHub/UsageTable.js +1240 -0
  275. package/dist/views/TranslationHub/alertGrouping.d.ts +70 -0
  276. package/dist/views/TranslationHub/alertGrouping.js +99 -0
  277. package/dist/views/TranslationHub/index.d.ts +20 -0
  278. package/dist/views/TranslationHub/index.js +109 -0
  279. package/dist/views/TranslationHub/tabNavigation.d.ts +53 -0
  280. package/dist/views/TranslationHub/tabNavigation.js +74 -0
  281. package/dist/views/TranslationHub/terminalBannerVisibility.d.ts +33 -0
  282. package/dist/views/TranslationHub/terminalBannerVisibility.js +124 -0
  283. package/dist/views/TranslationHub/useBulkTranslateActive.d.ts +49 -0
  284. package/dist/views/TranslationHub/useBulkTranslateActive.js +251 -0
  285. package/dist/views/TranslationHub/useFocusTrap.d.ts +6 -0
  286. package/dist/views/TranslationHub/useFocusTrap.js +81 -0
  287. package/dist/views/TranslationHub/useTranslationHubUsageSummary.d.ts +77 -0
  288. package/dist/views/TranslationHub/useTranslationHubUsageSummary.js +267 -0
  289. package/dist/views/shared/EditorError.d.ts +97 -0
  290. package/dist/views/shared/EditorError.js +205 -0
  291. package/dist/views/shared/ModelCell.d.ts +18 -0
  292. package/dist/views/shared/ModelCell.js +31 -0
  293. package/dist/views/shared/docHref.d.ts +16 -0
  294. package/dist/views/shared/docHref.js +26 -0
  295. package/dist/views/shared/fetch-error-body.d.ts +25 -0
  296. package/dist/views/shared/fetch-error-body.js +42 -0
  297. package/dist/views/shared/filterPillStyle.d.ts +35 -0
  298. package/dist/views/shared/filterPillStyle.js +40 -0
  299. package/dist/views/shared/format.d.ts +75 -0
  300. package/dist/views/shared/format.js +131 -0
  301. package/package.json +141 -0
@@ -0,0 +1,199 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { toast, useConfig, useDocumentInfo, useField, useLocale } from '@payloadcms/ui';
4
+ import { formatAdminURL } from 'payload/shared';
5
+ import { Component, useState } from 'react';
6
+ import { useGlobalKillSwitches } from './lib/use-global-kill-switches.js';
7
+ // Error boundary — if useDocumentInfo throws on create view, render nothing
8
+ let FieldTranslateButtonBoundary = class FieldTranslateButtonBoundary extends Component {
9
+ constructor(props){
10
+ super(props);
11
+ this.state = {
12
+ hasError: false
13
+ };
14
+ }
15
+ static getDerivedStateFromError() {
16
+ return {
17
+ hasError: true
18
+ };
19
+ }
20
+ render() {
21
+ if (this.state.hasError) return null;
22
+ return this.props.children;
23
+ }
24
+ };
25
+ function FieldTranslateButtonInner({ path }) {
26
+ const [translating, setTranslating] = useState(false);
27
+ // Inline error message rendered next to the button. Toast notifications
28
+ // can be missed when the consumer's admin layout doesn't mount a
29
+ // `<Toaster />` correctly (Sonner version mismatch, plugin queue
30
+ // orphaned by RootProvider position, etc.). The inline indicator gives
31
+ // the user feedback even in those cases. Cleared when the user clicks
32
+ // again or a translation succeeds.
33
+ const [errorMsg, setErrorMsg] = useState(null);
34
+ const documentInfo = useDocumentInfo();
35
+ const { config } = useConfig();
36
+ const locale = useLocale();
37
+ // Hook into the surrounding field's form state so we can write the
38
+ // translated value back into the input without making the user reload.
39
+ const { setValue } = useField({
40
+ path
41
+ });
42
+ // Plugin-wide manual-translate kill switch (v1.2.8). Called here at
43
+ // the top of the component (before any early returns) to satisfy
44
+ // rules-of-hooks. The actual hide check happens further down.
45
+ const killSwitches = useGlobalKillSwitches();
46
+ const globalSlug = documentInfo.globalSlug;
47
+ const id = documentInfo.id;
48
+ const collectionSlug = documentInfo.collectionSlug ?? documentInfo.slug;
49
+ const defaultLocale = config.localization ? config.localization.defaultLocale : 'en';
50
+ const currentLocale = typeof locale === 'string' ? locale : locale?.code;
51
+ if (!currentLocale || currentLocale === defaultLocale) return null;
52
+ // Hide on manual-translate kill switch off (see top-of-component
53
+ // hook call for rules-of-hooks rationale). Optimistic while loading
54
+ // (`null`) — server-side endpoint gate is the real authority.
55
+ if (killSwitches && !killSwitches.manualEnabled) return null;
56
+ // Resolve doc context: globals don't have a numeric id, just a slug
57
+ let pathPrefix = null;
58
+ let bodyForGlobal = false;
59
+ if (globalSlug) {
60
+ pathPrefix = `/globals/${globalSlug}`;
61
+ bodyForGlobal = true;
62
+ } else if (id && collectionSlug) {
63
+ pathPrefix = `/${collectionSlug}`;
64
+ } else {
65
+ return null;
66
+ }
67
+ const handleTranslate = async ()=>{
68
+ if (translating) return;
69
+ setTranslating(true);
70
+ setErrorMsg(null);
71
+ try {
72
+ const { routes } = config;
73
+ const apiRoute = routes?.api ?? '/api';
74
+ const body = {
75
+ targetLocales: [
76
+ currentLocale
77
+ ],
78
+ fields: [
79
+ path
80
+ ],
81
+ // Per-field translate writes only the field the user clicked.
82
+ // The form patches the value into the local form state via
83
+ // `setValue` regardless — the locale write is a server-side
84
+ // side effect that should match the user's narrow intent.
85
+ writeMode: 'minimal'
86
+ };
87
+ if (!bodyForGlobal) body.id = id;
88
+ const apiPath = `${pathPrefix}/ai-translate`;
89
+ const res = await fetch(formatAdminURL({
90
+ apiRoute,
91
+ path: apiPath
92
+ }), {
93
+ method: 'POST',
94
+ credentials: 'include',
95
+ headers: {
96
+ 'Content-Type': 'application/json'
97
+ },
98
+ body: JSON.stringify(body)
99
+ });
100
+ if (!res.ok) {
101
+ const errorData = await res.json().catch(()=>({}));
102
+ throw new Error(errorData.error ?? `HTTP ${res.status}`);
103
+ }
104
+ const data = await res.json();
105
+ const result = data.results[0];
106
+ const failCount = result?.failed?.length ?? 0;
107
+ if (failCount === 0) {
108
+ // Patch the form state with the new value when the API returns it
109
+ // (plain text/textarea only — richText/blocks need a doc reload).
110
+ const successEntry = result?.succeeded?.find((s)=>s.fieldPath === path);
111
+ if (typeof successEntry?.translatedText === 'string') {
112
+ const newValue = successEntry.translatedText;
113
+ // 1) Update Payload's form state via the field-level setValue.
114
+ // This is the "right" path but doesn't always trigger the
115
+ // sibling input's re-render — Payload's text input is
116
+ // React-Compiler-memoized and may not observe a dispatch
117
+ // that originates from a different `useField` registration.
118
+ setValue(newValue);
119
+ // 2) Belt-and-suspenders: drive the DOM input directly and fire
120
+ // an `input` event the way a real keystroke would. This goes
121
+ // through the input's own onChange → its own setValue, which
122
+ // is the codepath Payload's memoization tracks.
123
+ // Runs in the next microtask so we don't fight React's
124
+ // render of step 1.
125
+ queueMicrotask(()=>{
126
+ const inputId = `field-${path.replace(/\./g, '__')}`;
127
+ const el = document.getElementById(inputId);
128
+ if (!el || el.value === newValue) return;
129
+ const proto = el instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
130
+ const setter = Object.getOwnPropertyDescriptor(proto, 'value')?.set;
131
+ if (!setter) return;
132
+ setter.call(el, newValue);
133
+ el.dispatchEvent(new Event('input', {
134
+ bubbles: true
135
+ }));
136
+ });
137
+ }
138
+ toast.success(`Translated "${path}" to ${currentLocale}`);
139
+ } else {
140
+ const reason = result?.failed?.[0]?.error;
141
+ const msg = reason ? `Failed to translate "${path}": ${reason.slice(0, 200)}` : `Failed to translate "${path}"`;
142
+ toast.error(msg);
143
+ setErrorMsg(reason ?? 'Translation failed');
144
+ }
145
+ } catch (error) {
146
+ const msg = error instanceof Error ? error.message : 'Translation failed';
147
+ toast.error(msg);
148
+ setErrorMsg(msg);
149
+ } finally{
150
+ setTranslating(false);
151
+ }
152
+ };
153
+ return /*#__PURE__*/ _jsxs(_Fragment, {
154
+ children: [
155
+ /*#__PURE__*/ _jsx("button", {
156
+ // Without an explicit type the browser defaults to "submit" inside the
157
+ // Payload edit `<form>`, so a click would save the document (bumping
158
+ // the version + showing "Updated successfully") in addition to firing
159
+ // the translation. Force "button" to keep it side-effect-free.
160
+ type: "button",
161
+ onClick: handleTranslate,
162
+ disabled: translating,
163
+ title: `Translate this field to ${currentLocale}`,
164
+ style: {
165
+ display: 'inline-flex',
166
+ alignItems: 'center',
167
+ gap: '4px',
168
+ padding: '4px 8px',
169
+ marginTop: '4px',
170
+ backgroundColor: 'transparent',
171
+ border: '1px solid var(--theme-elevation-300)',
172
+ borderRadius: '3px',
173
+ color: 'var(--theme-elevation-500)',
174
+ cursor: translating ? 'wait' : 'pointer',
175
+ fontSize: '12px',
176
+ opacity: translating ? 0.6 : 1
177
+ },
178
+ children: translating ? 'Translating...' : 'Translate'
179
+ }),
180
+ errorMsg && /*#__PURE__*/ _jsx("div", {
181
+ role: "alert",
182
+ style: {
183
+ marginTop: '4px',
184
+ color: 'var(--theme-error-500)',
185
+ fontSize: '12px',
186
+ maxWidth: '100%'
187
+ },
188
+ children: errorMsg
189
+ })
190
+ ]
191
+ });
192
+ }
193
+ export function FieldTranslateButton(props) {
194
+ return /*#__PURE__*/ _jsx(FieldTranslateButtonBoundary, {
195
+ children: /*#__PURE__*/ _jsx(FieldTranslateButtonInner, {
196
+ ...props
197
+ })
198
+ });
199
+ }
@@ -0,0 +1,6 @@
1
+ export { EstimatedCostCell } from './estimated-cost-cell.js';
2
+ export { ExcludedFieldsField } from './excluded-fields-field.js';
3
+ export { FieldTranslateButton } from './field-translate-button.js';
4
+ export { TranslateButton } from './translate-button.js';
5
+ export { TranslateModal } from './translate-modal.js';
6
+ export { TranslationProgress } from './translation-progress.js';
@@ -0,0 +1,6 @@
1
+ export { EstimatedCostCell } from './estimated-cost-cell.js';
2
+ export { ExcludedFieldsField } from './excluded-fields-field.js';
3
+ export { FieldTranslateButton } from './field-translate-button.js';
4
+ export { TranslateButton } from './translate-button.js';
5
+ export { TranslateModal } from './translate-modal.js';
6
+ export { TranslationProgress } from './translation-progress.js';
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Client-side view of the three plugin-wide kill switches (v1.2.8).
3
+ * Fetched from `/api/ai-translate/client-config` on mount; the response
4
+ * is cached at module scope with a 15-second TTL so multiple buttons
5
+ * mounting on the same admin view collapse to one network call.
6
+ *
7
+ * Optimistic default: components that consume this hook should treat
8
+ * `null` (still loading) as "show normally". On a slow connection an
9
+ * admin user briefly sees the affordance before it hides — accepted
10
+ * trade-off since the server-side gates are the real source of truth
11
+ * (Translate endpoint returns 403, Enqueue endpoint returns 403).
12
+ */
13
+ export type GlobalKillSwitchesClient = {
14
+ autoEnabled: boolean;
15
+ manualEnabled: boolean;
16
+ bulkEnabled: boolean;
17
+ };
18
+ /** @internal test helper */
19
+ export declare function _resetGlobalKillSwitchesCache(): void;
20
+ export declare function useGlobalKillSwitches(): GlobalKillSwitchesClient | null;
@@ -0,0 +1,58 @@
1
+ 'use client';
2
+ import { useConfig } from '@payloadcms/ui';
3
+ import { formatAdminURL } from 'payload/shared';
4
+ import { useEffect, useState } from 'react';
5
+ const TTL_MS = 15_000;
6
+ const cache = new Map();
7
+ const DEFAULT_OPEN = {
8
+ autoEnabled: true,
9
+ manualEnabled: true,
10
+ bulkEnabled: true
11
+ };
12
+ function fetchKillSwitches(url) {
13
+ return fetch(url, {
14
+ credentials: 'include'
15
+ }).then((res)=>res.ok ? res.json() : null).then((data)=>{
16
+ const raw = data?.globalKillSwitches;
17
+ if (!raw) return DEFAULT_OPEN;
18
+ return {
19
+ autoEnabled: raw.autoEnabled !== false,
20
+ manualEnabled: raw.manualEnabled !== false,
21
+ bulkEnabled: raw.bulkEnabled !== false
22
+ };
23
+ }).catch(()=>DEFAULT_OPEN);
24
+ }
25
+ /** @internal test helper */ export function _resetGlobalKillSwitchesCache() {
26
+ cache.clear();
27
+ }
28
+ export function useGlobalKillSwitches() {
29
+ const { config } = useConfig();
30
+ const apiRoute = config.routes?.api ?? '/api';
31
+ const url = formatAdminURL({
32
+ apiRoute,
33
+ path: '/ai-translate/client-config',
34
+ serverURL: ''
35
+ });
36
+ const [switches, setSwitches] = useState(null);
37
+ useEffect(()=>{
38
+ let cancelled = false;
39
+ const now = Date.now();
40
+ const existing = cache.get(url);
41
+ if (!existing || existing.expiresAt <= now) {
42
+ cache.set(url, {
43
+ promise: fetchKillSwitches(url),
44
+ expiresAt: now + TTL_MS
45
+ });
46
+ }
47
+ const entry = cache.get(url);
48
+ entry?.promise.then((s)=>{
49
+ if (!cancelled) setSwitches(s);
50
+ });
51
+ return ()=>{
52
+ cancelled = true;
53
+ };
54
+ }, [
55
+ url
56
+ ]);
57
+ return switches;
58
+ }
@@ -0,0 +1,2 @@
1
+ import React from 'react';
2
+ export declare function TranslateButton(): React.JSX.Element | null;
@@ -0,0 +1,228 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useConfig, useDocumentInfo, useFormProcessing } from '@payloadcms/ui';
4
+ import { formatAdminURL } from 'payload/shared';
5
+ import React, { useEffect, useState } from 'react';
6
+ import { useGlobalKillSwitches } from './lib/use-global-kill-switches.js';
7
+ import { TranslateModal } from './translate-modal.js';
8
+ import { TranslationProgress } from './translation-progress.js';
9
+ function resolveDocContext(info) {
10
+ const globalSlug = info.globalSlug;
11
+ if (globalSlug) {
12
+ return {
13
+ kind: 'global',
14
+ slug: globalSlug,
15
+ pathPrefix: `/globals/${globalSlug}`,
16
+ pollKey: globalSlug
17
+ };
18
+ }
19
+ const id = info.id;
20
+ const collectionSlug = info.collectionSlug ?? info.slug;
21
+ if (id && collectionSlug) {
22
+ return {
23
+ kind: 'collection',
24
+ slug: collectionSlug,
25
+ id,
26
+ pathPrefix: `/${collectionSlug}`,
27
+ pollKey: String(id)
28
+ };
29
+ }
30
+ return null;
31
+ }
32
+ const SUBMIT_FEEDBACK_KEYFRAMES = `
33
+ @keyframes ai-translate-pre-indeterminate {
34
+ 0% { left: -35%; right: 100%; }
35
+ 60% { left: 100%; right: -35%; }
36
+ 100% { left: 100%; right: -35%; }
37
+ }
38
+ `;
39
+ /**
40
+ * Optimistic "Saving…" indicator shown while Payload's form is
41
+ * submitting (or background-autosaving) AND we haven't yet picked up
42
+ * an active translation job.
43
+ *
44
+ * Why: Payload's publish flow round-trips through several
45
+ * POST/PATCH calls (autosave debounce + server actions + final
46
+ * publish) which can take 5-9s wall-clock before the after-change
47
+ * hook fires + `createJob` runs. Without this, the user clicks
48
+ * Publish and stares at unchanged UI for that whole window, then
49
+ * suddenly the progress bar appears mid-translation. With this,
50
+ * they get feedback within ~50ms of click.
51
+ *
52
+ * Wording is neutral ("Saving…") because this also fires on plain
53
+ * draft saves and autosave debounces, where no translation will run.
54
+ * Naming a translation here would be misleading; the real progress
55
+ * bar takes over the moment a job is registered server-side.
56
+ *
57
+ * Visually matches the indeterminate state of `TranslationProgress`
58
+ * so the transition into the real bar feels continuous.
59
+ */ function SubmitFeedback({ formProcessing, showWhileTranslating }) {
60
+ if (!formProcessing || !showWhileTranslating) return null;
61
+ return /*#__PURE__*/ _jsxs("div", {
62
+ style: {
63
+ marginTop: '8px',
64
+ padding: '12px 16px',
65
+ backgroundColor: 'var(--theme-elevation-50)',
66
+ border: '1px solid var(--theme-elevation-200)',
67
+ borderRadius: '4px',
68
+ fontSize: '13px',
69
+ color: 'var(--theme-text)'
70
+ },
71
+ role: "status",
72
+ "aria-live": "polite",
73
+ children: [
74
+ /*#__PURE__*/ _jsx("style", {
75
+ children: SUBMIT_FEEDBACK_KEYFRAMES
76
+ }),
77
+ /*#__PURE__*/ _jsx("div", {
78
+ style: {
79
+ marginBottom: '8px',
80
+ fontWeight: 500
81
+ },
82
+ children: "Saving…"
83
+ }),
84
+ /*#__PURE__*/ _jsx("div", {
85
+ style: {
86
+ position: 'relative',
87
+ width: '100%',
88
+ height: '6px',
89
+ backgroundColor: 'var(--theme-elevation-200)',
90
+ borderRadius: '3px',
91
+ overflow: 'hidden'
92
+ },
93
+ children: /*#__PURE__*/ _jsx("div", {
94
+ style: {
95
+ position: 'absolute',
96
+ top: 0,
97
+ bottom: 0,
98
+ borderRadius: '3px',
99
+ backgroundColor: 'var(--theme-success-500)',
100
+ backgroundImage: 'linear-gradient(90deg, color-mix(in srgb, var(--theme-success-500) 70%, white) 0%, var(--theme-success-500) 50%, color-mix(in srgb, var(--theme-success-500) 70%, white) 100%)',
101
+ animation: 'ai-translate-pre-indeterminate 1.6s ease-in-out infinite'
102
+ }
103
+ })
104
+ })
105
+ ]
106
+ });
107
+ }
108
+ export function TranslateButton() {
109
+ const [modalOpen, setModalOpen] = useState(false);
110
+ const [activeJobId, setActiveJobId] = useState(null);
111
+ const documentInfo = useDocumentInfo();
112
+ const { config } = useConfig();
113
+ // Only react to the real submit (Save / Publish click).
114
+ // `useFormBackgroundProcessing` also fires for every autosave
115
+ // debounce, which on a versioned collection with
116
+ // `autosave.interval: 400` happens on every keystroke — and the
117
+ // optimistic indicator below looks identical to the translation
118
+ // progress bar, so editors read "Saving…" on draft autosave as
119
+ // "translating now, did I just spend tokens?". Trade the ~5-9s
120
+ // optimistic head-start on Publish click for that confusion-free
121
+ // signal: the bar only appears when a translation is actually
122
+ // imminent (real submit) or running (`activeJobId`).
123
+ const formProcessing = useFormProcessing();
124
+ const isAnyFormActivity = formProcessing;
125
+ // Plugin-wide manual-translate kill switch (v1.2.8). Called at top
126
+ // level for rules-of-hooks; the `return null` below combines with
127
+ // the docCtx check.
128
+ const killSwitches = useGlobalKillSwitches();
129
+ const docCtx = resolveDocContext(documentInfo);
130
+ const apiRoute = config.routes?.api ?? '/api';
131
+ // Open ONE long-lived EventSource on mount. The server holds the
132
+ // connection open and pushes a 'progress' event the moment a
133
+ // translation job is created for this doc — typically within ~50ms of
134
+ // an after-change hook firing. No client polling, no race-the-server
135
+ // burst timers. Replaces the previous setInterval + form-submit burst
136
+ // that produced ~10s of dead-air UX between Publish click and
137
+ // progress bar.
138
+ //
139
+ // Effect deps are PRIMITIVES (`pollKey`, `pathPrefix`, `apiRoute`),
140
+ // not the full `docCtx` object — `resolveDocContext` returns a new
141
+ // object reference each render, which would tear down + recreate the
142
+ // EventSource constantly and defeat the whole point.
143
+ const pollKey = docCtx?.pollKey;
144
+ const pathPrefix = docCtx?.pathPrefix;
145
+ useEffect(()=>{
146
+ if (!pollKey || !pathPrefix) return;
147
+ if (typeof EventSource === 'undefined') return;
148
+ const path = `${pathPrefix}/ai-translate/progress?docId=${encodeURIComponent(pollKey)}`;
149
+ const url = formatAdminURL({
150
+ apiRoute,
151
+ path
152
+ });
153
+ const es = new EventSource(url, {
154
+ withCredentials: true
155
+ });
156
+ function handleProgress(e) {
157
+ try {
158
+ const data = JSON.parse(e.data);
159
+ if (data.status === 'running' && data.jobId) {
160
+ setActiveJobId(data.jobId);
161
+ }
162
+ } catch {
163
+ // malformed event — ignore
164
+ }
165
+ }
166
+ es.addEventListener('progress', handleProgress);
167
+ // EventSource auto-reconnects when the server closes; the
168
+ // `retry: 30000` directive paces idle reconnects.
169
+ return ()=>{
170
+ es.removeEventListener('progress', handleProgress);
171
+ es.close();
172
+ };
173
+ }, [
174
+ pollKey,
175
+ pathPrefix,
176
+ apiRoute
177
+ ]);
178
+ if (!docCtx) return null;
179
+ // Hide on manual-translate kill switch off. Optimistic while loading.
180
+ if (killSwitches && !killSwitches.manualEnabled) return null;
181
+ return /*#__PURE__*/ _jsxs("div", {
182
+ style: {
183
+ marginTop: '16px'
184
+ },
185
+ children: [
186
+ /*#__PURE__*/ _jsx("button", {
187
+ type: "button",
188
+ onClick: ()=>setModalOpen(true),
189
+ disabled: !!activeJobId,
190
+ style: {
191
+ width: '100%',
192
+ padding: '10px 16px',
193
+ backgroundColor: activeJobId ? 'var(--theme-elevation-50)' : 'var(--theme-elevation-100)',
194
+ border: '1px solid var(--theme-elevation-300)',
195
+ borderRadius: '4px',
196
+ color: 'var(--theme-text)',
197
+ cursor: activeJobId ? 'default' : 'pointer',
198
+ fontSize: '14px',
199
+ fontWeight: 500,
200
+ opacity: activeJobId ? 0.7 : 1
201
+ },
202
+ children: activeJobId ? 'Translating...' : 'Translate...'
203
+ }),
204
+ /*#__PURE__*/ _jsx(SubmitFeedback, {
205
+ formProcessing: isAnyFormActivity,
206
+ showWhileTranslating: !activeJobId
207
+ }),
208
+ activeJobId && /*#__PURE__*/ _jsx("div", {
209
+ style: {
210
+ marginTop: '8px'
211
+ },
212
+ children: /*#__PURE__*/ _jsx(TranslationProgress, {
213
+ jobId: activeJobId,
214
+ collectionSlug: docCtx.kind === 'collection' ? docCtx.slug : undefined,
215
+ globalSlug: docCtx.kind === 'global' ? docCtx.slug : undefined,
216
+ onComplete: ()=>setActiveJobId(null)
217
+ })
218
+ }),
219
+ modalOpen && /*#__PURE__*/ _jsx(TranslateModal, {
220
+ docId: docCtx.kind === 'collection' ? docCtx.id : docCtx.slug,
221
+ collectionSlug: docCtx.kind === 'collection' ? docCtx.slug : undefined,
222
+ globalSlug: docCtx.kind === 'global' ? docCtx.slug : undefined,
223
+ onClose: ()=>setModalOpen(false),
224
+ onJobStarted: setActiveJobId
225
+ })
226
+ ]
227
+ });
228
+ }
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+ type TranslateModalProps = {
3
+ docId: string | number;
4
+ collectionSlug?: string;
5
+ globalSlug?: string;
6
+ onClose: () => void;
7
+ /**
8
+ * Called when the translate POST returns a `jobId` (async mode).
9
+ * The parent TranslateButton uses this to hand off the active job to its
10
+ * sidebar progress widget so the modal can close immediately rather than
11
+ * trapping the user behind a duplicate progress UI.
12
+ */
13
+ onJobStarted?: (jobId: string) => void;
14
+ };
15
+ export declare function TranslateModal({ docId, collectionSlug, globalSlug, onClose, onJobStarted, }: TranslateModalProps): React.ReactPortal | null;
16
+ export {};