@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,553 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useConfig, useField, useFormFields } from '@payloadcms/ui';
4
+ import { formatAdminURL } from 'payload/shared';
5
+ import { useCallback, useEffect, useMemo, useState } from 'react';
6
+ // Lightweight module-level cache of the client-config response. The
7
+ // component re-mounts on every row collapse/expand in the array UI,
8
+ // and fetching the same payload N times per page render is a waste —
9
+ // every row in `perCollection` needs the SAME map. TTL is intentionally
10
+ // long (5 min) because the map only changes when a developer adds/
11
+ // removes localized fields and restarts the server; admins editing the
12
+ // settings global don't affect it.
13
+ const CLIENT_CONFIG_TTL_MS = 5 * 60 * 1000;
14
+ let cached = null;
15
+ let inFlight = null;
16
+ async function fetchTranslatableFieldsBySlug(url) {
17
+ const now = Date.now();
18
+ if (cached && cached.expiresAt > now) return cached.value;
19
+ if (inFlight) return inFlight;
20
+ inFlight = (async ()=>{
21
+ try {
22
+ const res = await fetch(url, {
23
+ credentials: 'include'
24
+ });
25
+ if (!res.ok) return {};
26
+ const data = await res.json();
27
+ const map = data.translatableFieldsBySlug ?? {};
28
+ cached = {
29
+ value: map,
30
+ expiresAt: Date.now() + CLIENT_CONFIG_TTL_MS
31
+ };
32
+ return map;
33
+ } catch {
34
+ // Don't cache failures — let the next mount retry.
35
+ return {};
36
+ } finally{
37
+ inFlight = null;
38
+ }
39
+ })();
40
+ return inFlight;
41
+ }
42
+ /**
43
+ * Custom admin component for `perCollection[i].excludedFieldPaths`.
44
+ *
45
+ * Stores an exclude-list (top-level field paths the admin opted OUT of
46
+ * translation). Rendered as a grouped list — one checkbox per
47
+ * top-level field on the surface. Unchecking a top-level field
48
+ * excludes EVERY descendant path under it (prefix-match in
49
+ * `isPathExcluded`).
50
+ *
51
+ * Design rationale:
52
+ *
53
+ * 1. **Editor mental model.** Editors think in terms of the fields
54
+ * they see in the doc editor ("Layout", "Meta"), not internal dot-
55
+ * paths. The earlier flat list of paths like
56
+ * `layout.columns.link.label` was technically correct but useless
57
+ * for non-developers.
58
+ * 2. **One checkbox per top-level field.** Coarse but predictable:
59
+ * "uncheck Layout" → nothing under `layout.*` is translated.
60
+ * Per-leaf exclusions are still expressible via direct DB writes
61
+ * or future per-field controls if needed; the common case is
62
+ * coverage at the section level.
63
+ * 3. **Disclosure of details.** A collapsible "Show 14 sub-paths"
64
+ * affordance reveals every descendant path the resolver found, so
65
+ * a developer / careful editor can verify what's actually
66
+ * affected. Read-only — not interactive.
67
+ * 4. **Exclude-list semantics.** Default = empty array = translate
68
+ * everything. New localized fields added later start "checked"
69
+ * automatically.
70
+ *
71
+ * Sibling slug derivation: Payload doesn't give the component direct
72
+ * access to its row neighbours, so we strip the trailing
73
+ * `.excludedFieldPaths` segment from `path` and append `.slug` to
74
+ * address the sibling.
75
+ */ export function ExcludedFieldsField({ path }) {
76
+ // `text hasMany` value is `string[]` (or null/undefined when unset).
77
+ const { value, setValue } = useField({
78
+ path
79
+ });
80
+ // Respect both `routes.api` AND Next.js `basePath` (e.g. blog-wild
81
+ // mounts everything under `/blog`, so the api lives at
82
+ // `/blog/api/...`). `formatAdminURL` reads `NEXT_BASE_PATH` and
83
+ // prepends it; using a raw fetch with `apiRoute` alone 404s in
84
+ // basePath setups.
85
+ const { config } = useConfig();
86
+ const apiRoute = config.routes?.api ?? '/api';
87
+ const endpointUrl = useMemo(()=>formatAdminURL({
88
+ apiRoute,
89
+ path: '/ai-translate/client-config',
90
+ serverURL: ''
91
+ }), [
92
+ apiRoute
93
+ ]);
94
+ // Sibling `slug` value, read from the form state. `useFormFields`
95
+ // re-renders when the selected value changes — so the widget
96
+ // updates as soon as the admin picks a slug from the dropdown.
97
+ const siblingSlugPath = useMemo(()=>path.replace(/\.excludedFieldPaths$/, '.slug'), [
98
+ path
99
+ ]);
100
+ const slug = useFormFields(([fields])=>fields?.[siblingSlugPath]?.value);
101
+ const [map, setMap] = useState(null);
102
+ const [loading, setLoading] = useState(true);
103
+ useEffect(()=>{
104
+ let alive = true;
105
+ fetchTranslatableFieldsBySlug(endpointUrl).then((m)=>{
106
+ if (alive) {
107
+ setMap(m);
108
+ setLoading(false);
109
+ }
110
+ });
111
+ return ()=>{
112
+ alive = false;
113
+ };
114
+ }, [
115
+ endpointUrl
116
+ ]);
117
+ const availablePaths = useMemo(()=>{
118
+ if (!slug || !map) return null;
119
+ return map[slug] ?? [];
120
+ }, [
121
+ slug,
122
+ map
123
+ ]);
124
+ // Group paths by their first segment. Each group becomes a single
125
+ // checkbox row — that's the editor's unit of control. Children
126
+ // surface in the disclosure under each group for transparency.
127
+ const groups = useMemo(()=>{
128
+ if (!availablePaths) return null;
129
+ const byTop = new Map();
130
+ for (const p of availablePaths){
131
+ const top = p.split('.')[0];
132
+ const list = byTop.get(top) ?? [];
133
+ list.push(p);
134
+ byTop.set(top, list);
135
+ }
136
+ return [
137
+ ...byTop.entries()
138
+ ].sort(([a], [b])=>a.localeCompare(b)).map(([top, paths])=>({
139
+ top,
140
+ paths
141
+ }));
142
+ }, [
143
+ availablePaths
144
+ ]);
145
+ const excluded = useMemo(()=>new Set(Array.isArray(value) ? value.filter((v)=>typeof v === 'string') : []), [
146
+ value
147
+ ]);
148
+ // Top-level checkbox: storing the parent path (e.g. `layout`) is all
149
+ // we need — the API filter does prefix matching, so excluding
150
+ // `layout` covers `layout.columns.richText` etc. automatically.
151
+ //
152
+ // BUG-06 fix: pre-fix, adding a parent path silently DELETED any
153
+ // descendant entries the admin had specifically configured. Toggling
154
+ // the parent off then permanently destroyed that fine-grained
155
+ // intent. Now we PRESERVE descendants — the parent's prefix-match
156
+ // is the primary effect, but descendant entries stay in storage so
157
+ // unchecking the parent restores the previous specific state.
158
+ const handleToggleTop = useCallback((topPath)=>{
159
+ const next = new Set(excluded);
160
+ if (next.has(topPath)) {
161
+ next.delete(topPath);
162
+ } else {
163
+ next.add(topPath);
164
+ // BUG-06: do NOT delete descendants. Prefix-match makes them
165
+ // redundant for the filter's purposes, but the entries are
166
+ // the admin's recorded intent. Keeping them lets unchecking
167
+ // the parent reveal the original fine-grained state.
168
+ }
169
+ // Normalize empty -> null so the DB row keeps an empty array
170
+ // instead of an explicit-empty marker. Both are semantically
171
+ // identical downstream, but `null` round-trips cleaner.
172
+ setValue(next.size === 0 ? null : [
173
+ ...next
174
+ ]);
175
+ }, [
176
+ excluded,
177
+ setValue
178
+ ]);
179
+ const handleSelectAll = useCallback(()=>{
180
+ setValue(null);
181
+ }, [
182
+ setValue
183
+ ]);
184
+ // BUG-07 fix: pre-fix, "Exclude all" REPLACED the whole set with just
185
+ // top-level paths, wiping any fine-grained entries the admin had
186
+ // configured. Now it ADDS top-level paths to the existing set —
187
+ // descendants survive so the original specificity is preserved.
188
+ const handleExcludeAll = useCallback(()=>{
189
+ if (!groups || groups.length === 0) return;
190
+ const next = new Set(excluded);
191
+ for (const g of groups)next.add(g.top);
192
+ setValue(next.size === 0 ? null : [
193
+ ...next
194
+ ]);
195
+ }, [
196
+ groups,
197
+ excluded,
198
+ setValue
199
+ ]);
200
+ // --- Empty / loading states ---
201
+ if (!slug) {
202
+ return /*#__PURE__*/ _jsx(Wrapper, {
203
+ children: /*#__PURE__*/ _jsx("p", {
204
+ style: mutedStyle,
205
+ children: "Select a slug above and the translatable fields for that collection will appear here."
206
+ })
207
+ });
208
+ }
209
+ if (loading || !map) {
210
+ return /*#__PURE__*/ _jsx(Wrapper, {
211
+ children: /*#__PURE__*/ _jsx("p", {
212
+ style: mutedStyle,
213
+ children: "Loading available fields…"
214
+ })
215
+ });
216
+ }
217
+ if (!groups || groups.length === 0) {
218
+ return /*#__PURE__*/ _jsx(Wrapper, {
219
+ children: /*#__PURE__*/ _jsxs("p", {
220
+ style: mutedStyle,
221
+ children: [
222
+ 'No translatable fields found for "',
223
+ slug,
224
+ '". A field needs',
225
+ ' ',
226
+ /*#__PURE__*/ _jsx("code", {
227
+ style: inlineCodeStyle,
228
+ children: "localized: true"
229
+ }),
230
+ " in its config to appear here."
231
+ ]
232
+ })
233
+ });
234
+ }
235
+ // Stale top-level entries — excluded paths that no longer exist as
236
+ // top-level fields on the surface. Survive a schema rename so the
237
+ // admin can clean them up without DB access.
238
+ const knownTops = new Set(groups.map((g)=>g.top));
239
+ const staleEntries = [
240
+ ...excluded
241
+ ].filter((p)=>{
242
+ const top = p.split('.')[0];
243
+ return !knownTops.has(top);
244
+ });
245
+ // BUG-09 fix: surface deeper-than-top-level exclusions in the UI.
246
+ // Pre-fix, paths like `layout.columns.richText` were invisible —
247
+ // the widget grouped only on top segment, so admins debugging
248
+ // "why isn't this translating?" had no way to discover the deep
249
+ // entry. Now we show them explicitly under an "Advanced path
250
+ // exclusions" section.
251
+ const deepEntries = [
252
+ ...excluded
253
+ ].filter((p)=>{
254
+ const top = p.split('.')[0];
255
+ return knownTops.has(top) && p.includes('.');
256
+ });
257
+ const removeDeepEntry = (path)=>{
258
+ const next = new Set(excluded);
259
+ next.delete(path);
260
+ setValue(next.size === 0 ? null : [
261
+ ...next
262
+ ]);
263
+ };
264
+ return /*#__PURE__*/ _jsxs(Wrapper, {
265
+ children: [
266
+ /*#__PURE__*/ _jsxs("p", {
267
+ style: mutedStyle,
268
+ children: [
269
+ "Pick which top-level fields get translated for this surface. Unchecked fields are",
270
+ ' ',
271
+ /*#__PURE__*/ _jsx("strong", {
272
+ children: "excluded from all translation"
273
+ }),
274
+ " — both auto-translate on publish and the manual Translate… dialog. Defaults to every field being translated."
275
+ ]
276
+ }),
277
+ /*#__PURE__*/ _jsxs("div", {
278
+ style: actionsRowStyle,
279
+ children: [
280
+ /*#__PURE__*/ _jsx("button", {
281
+ type: "button",
282
+ onClick: handleSelectAll,
283
+ style: linkButtonStyle,
284
+ children: "Translate all"
285
+ }),
286
+ /*#__PURE__*/ _jsx("span", {
287
+ style: dividerStyle,
288
+ "aria-hidden": true,
289
+ children: "·"
290
+ }),
291
+ /*#__PURE__*/ _jsx("button", {
292
+ type: "button",
293
+ onClick: handleExcludeAll,
294
+ style: linkButtonStyle,
295
+ children: "Exclude all"
296
+ })
297
+ ]
298
+ }),
299
+ /*#__PURE__*/ _jsx("ul", {
300
+ style: listStyle,
301
+ children: groups.map(({ top, paths })=>/*#__PURE__*/ _jsx(GroupRow, {
302
+ top: top,
303
+ paths: paths,
304
+ excluded: excluded.has(top),
305
+ onToggle: handleToggleTop
306
+ }, top))
307
+ }),
308
+ deepEntries.length > 0 && /*#__PURE__*/ _jsxs("details", {
309
+ style: staleDetailsStyle,
310
+ children: [
311
+ /*#__PURE__*/ _jsxs("summary", {
312
+ style: staleSummaryStyle,
313
+ children: [
314
+ deepEntries.length,
315
+ " advanced path exclusion",
316
+ deepEntries.length === 1 ? '' : 's',
317
+ " below top level (BUG-09)"
318
+ ]
319
+ }),
320
+ /*#__PURE__*/ _jsx("p", {
321
+ style: mutedStyle,
322
+ children: "These nested paths exclude specific fields below their top-level parent. They were written via SQL, migration, or an earlier version of this widget — and remain in effect independently of the top-level checkboxes above. Remove individual entries if you want to translate them."
323
+ }),
324
+ /*#__PURE__*/ _jsx("ul", {
325
+ style: staleListStyle,
326
+ children: deepEntries.map((deep)=>/*#__PURE__*/ _jsxs("li", {
327
+ style: staleItemStyle,
328
+ children: [
329
+ /*#__PURE__*/ _jsx("code", {
330
+ style: inlineCodeStyle,
331
+ children: deep
332
+ }),
333
+ /*#__PURE__*/ _jsx("button", {
334
+ type: "button",
335
+ onClick: ()=>removeDeepEntry(deep),
336
+ style: {
337
+ ...linkButtonStyle,
338
+ marginLeft: '0.75rem'
339
+ },
340
+ "aria-label": `Remove advanced exclusion ${deep}`,
341
+ children: "remove"
342
+ })
343
+ ]
344
+ }, deep))
345
+ })
346
+ ]
347
+ }),
348
+ staleEntries.length > 0 && /*#__PURE__*/ _jsxs("details", {
349
+ style: staleDetailsStyle,
350
+ children: [
351
+ /*#__PURE__*/ _jsxs("summary", {
352
+ style: staleSummaryStyle,
353
+ children: [
354
+ staleEntries.length,
355
+ " stored exclusion",
356
+ staleEntries.length === 1 ? '' : 's',
357
+ " no longer present on this surface"
358
+ ]
359
+ }),
360
+ /*#__PURE__*/ _jsx("ul", {
361
+ style: staleListStyle,
362
+ children: staleEntries.map((stale)=>/*#__PURE__*/ _jsx("li", {
363
+ style: staleItemStyle,
364
+ children: /*#__PURE__*/ _jsx("code", {
365
+ style: inlineCodeStyle,
366
+ children: stale
367
+ })
368
+ }, stale))
369
+ })
370
+ ]
371
+ })
372
+ ]
373
+ });
374
+ }
375
+ function Wrapper({ children }) {
376
+ return /*#__PURE__*/ _jsxs("div", {
377
+ className: "field-type",
378
+ children: [
379
+ /*#__PURE__*/ _jsx("label", {
380
+ className: "field-label",
381
+ children: "Translated fields"
382
+ }),
383
+ children
384
+ ]
385
+ });
386
+ }
387
+ function GroupRow({ top, paths, excluded, onToggle }) {
388
+ // Only show the disclosure when there's actually descendant info to
389
+ // disclose. A flat top-level field (e.g. `title`) has only one
390
+ // path — itself — and showing "1 sub-path: title" is just noise.
391
+ const hasChildren = paths.some((p)=>p !== top);
392
+ return /*#__PURE__*/ _jsxs("li", {
393
+ style: groupItemStyle,
394
+ children: [
395
+ /*#__PURE__*/ _jsxs("label", {
396
+ style: labelRowStyle,
397
+ children: [
398
+ /*#__PURE__*/ _jsx("input", {
399
+ type: "checkbox",
400
+ checked: !excluded,
401
+ onChange: ()=>onToggle(top)
402
+ }),
403
+ /*#__PURE__*/ _jsx("span", {
404
+ style: groupNameStyle,
405
+ children: humanize(top)
406
+ }),
407
+ /*#__PURE__*/ _jsx("code", {
408
+ style: inlineCodeStyle,
409
+ children: top
410
+ })
411
+ ]
412
+ }),
413
+ hasChildren && /*#__PURE__*/ _jsxs("details", {
414
+ style: disclosureStyle,
415
+ children: [
416
+ /*#__PURE__*/ _jsxs("summary", {
417
+ style: disclosureSummaryStyle,
418
+ children: [
419
+ "Show ",
420
+ paths.length,
421
+ " field path",
422
+ paths.length === 1 ? '' : 's',
423
+ " covered"
424
+ ]
425
+ }),
426
+ /*#__PURE__*/ _jsx("ul", {
427
+ style: pathListStyle,
428
+ children: paths.map((p)=>/*#__PURE__*/ _jsx("li", {
429
+ style: pathListItemStyle,
430
+ children: /*#__PURE__*/ _jsx("code", {
431
+ style: inlineCodeStyle,
432
+ children: p
433
+ })
434
+ }, p))
435
+ })
436
+ ]
437
+ })
438
+ ]
439
+ });
440
+ }
441
+ /**
442
+ * Turn a kebab/snake/camel field name into a human label.
443
+ * `metaTitle` -> `Meta Title`. `social_links` -> `Social Links`.
444
+ * `meta-data` -> `Meta Data`.
445
+ *
446
+ * Best-effort only — Payload field labels can be arbitrary strings or
447
+ * functions in the field config. Exposing them through the public
448
+ * client-config endpoint would mean expanding the API contract; this
449
+ * heuristic covers 90% of cases for the common camelCase / snake_case
450
+ * patterns Payload generates by default.
451
+ */ function humanize(name) {
452
+ return name.replace(/[-_]+/g, ' ').replace(/([a-z])([A-Z])/g, '$1 $2').replace(/\b\w/g, (c)=>c.toUpperCase());
453
+ }
454
+ // ---------------------------------------------------------------------------
455
+ // Inline styles — keeps the component zero-dep on consumer CSS. Uses
456
+ // Payload's CSS custom properties so the widget matches the active
457
+ // theme (light/dark) without an additional stylesheet import.
458
+ // ---------------------------------------------------------------------------
459
+ const mutedStyle = {
460
+ color: 'var(--theme-elevation-500)',
461
+ fontSize: '0.85em',
462
+ margin: '0.25em 0 0.75em'
463
+ };
464
+ const actionsRowStyle = {
465
+ display: 'flex',
466
+ alignItems: 'center',
467
+ gap: '0.5em',
468
+ margin: '0 0 0.5em',
469
+ fontSize: '0.85em'
470
+ };
471
+ const linkButtonStyle = {
472
+ background: 'none',
473
+ border: 'none',
474
+ color: 'var(--theme-text)',
475
+ cursor: 'pointer',
476
+ padding: 0,
477
+ textDecoration: 'underline',
478
+ font: 'inherit'
479
+ };
480
+ const dividerStyle = {
481
+ color: 'var(--theme-elevation-400)'
482
+ };
483
+ const listStyle = {
484
+ listStyle: 'none',
485
+ margin: 0,
486
+ padding: '0.5em',
487
+ border: '1px solid var(--theme-elevation-150)',
488
+ borderRadius: '4px',
489
+ maxHeight: '420px',
490
+ overflowY: 'auto'
491
+ };
492
+ const groupItemStyle = {
493
+ padding: '0.4em 0',
494
+ borderBottom: '1px solid var(--theme-elevation-100)'
495
+ };
496
+ const labelRowStyle = {
497
+ display: 'flex',
498
+ alignItems: 'center',
499
+ gap: '0.5em',
500
+ cursor: 'pointer'
501
+ };
502
+ const groupNameStyle = {
503
+ fontWeight: 500
504
+ };
505
+ const inlineCodeStyle = {
506
+ fontFamily: 'var(--font-mono, monospace)',
507
+ fontSize: '0.8em',
508
+ color: 'var(--theme-elevation-500)',
509
+ background: 'var(--theme-elevation-100)',
510
+ padding: '0.05em 0.4em',
511
+ borderRadius: '3px'
512
+ };
513
+ const disclosureStyle = {
514
+ marginTop: '0.3em',
515
+ marginLeft: '1.6em',
516
+ fontSize: '0.85em'
517
+ };
518
+ const disclosureSummaryStyle = {
519
+ cursor: 'pointer',
520
+ color: 'var(--theme-elevation-500)',
521
+ userSelect: 'none'
522
+ };
523
+ const pathListStyle = {
524
+ listStyle: 'none',
525
+ margin: '0.3em 0 0',
526
+ padding: 0,
527
+ display: 'flex',
528
+ flexDirection: 'column',
529
+ gap: '0.15em'
530
+ };
531
+ const pathListItemStyle = {
532
+ paddingLeft: '0.4em'
533
+ };
534
+ const staleDetailsStyle = {
535
+ marginTop: '0.75em',
536
+ fontSize: '0.85em'
537
+ };
538
+ const staleSummaryStyle = {
539
+ cursor: 'pointer',
540
+ color: 'var(--theme-warning-700, var(--theme-elevation-500))',
541
+ userSelect: 'none'
542
+ };
543
+ const staleListStyle = {
544
+ listStyle: 'none',
545
+ margin: '0.3em 0 0',
546
+ padding: 0,
547
+ display: 'flex',
548
+ flexDirection: 'column',
549
+ gap: '0.15em'
550
+ };
551
+ const staleItemStyle = {
552
+ paddingLeft: '1em'
553
+ };
@@ -0,0 +1,6 @@
1
+ import type React from 'react';
2
+ type FieldTranslateButtonProps = {
3
+ path: string;
4
+ };
5
+ export declare function FieldTranslateButton(props: FieldTranslateButtonProps): React.JSX.Element;
6
+ export {};