@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,146 @@
1
+ import { ALERTS_CONTEXT_FLAG, REENTRY_FLAG } from '../defaults.js';
2
+ import { createScopedLogger } from './logger.js';
3
+ /**
4
+ * Module-scoped payload reference for alert persistence. Set via
5
+ * `setAlertsContext` from the plugin's `onInit` (post-config-build,
6
+ * when `payload` is live). When null, `emitAlert` skips DB persistence
7
+ * but still fires the consumer's `onAlert` callback.
8
+ *
9
+ * `payload` carries an optional `logger` (pino) so we can emit
10
+ * structured "alerts about alerts failing" — without it, persistence
11
+ * failures swallow silently (Grafana blindness).
12
+ */ let alertsContext = null;
13
+ export function setAlertsContext(ctx) {
14
+ alertsContext = ctx;
15
+ }
16
+ export function emitEvent(config, event) {
17
+ if (!config.onEvent) {
18
+ return;
19
+ }
20
+ try {
21
+ const result = config.onEvent(event);
22
+ if (result && typeof result.catch === 'function') {
23
+ result.catch((err)=>{
24
+ logEventsFailure('events.emit.failed', err, {
25
+ eventType: event.type,
26
+ phase: 'onEvent.async'
27
+ });
28
+ });
29
+ }
30
+ } catch (err) {
31
+ // Swallow — event emission never breaks the main flow.
32
+ // But surface to pino so Grafana sees consumer callbacks failing.
33
+ logEventsFailure('events.emit.failed', err, {
34
+ eventType: event.type,
35
+ phase: 'onEvent.sync'
36
+ });
37
+ }
38
+ }
39
+ /**
40
+ * Best-effort structured log for events/alerts-pipeline failures. The
41
+ * pipeline cannot break the main translation flow; the trade-off is
42
+ * that swallowed failures used to be invisible. Routes through the
43
+ * module-scoped alerts context's payload when set; otherwise falls
44
+ * back to `console.warn` so dev environments without `onInit` still
45
+ * see the signal.
46
+ */ function logEventsFailure(eventName, err, fields) {
47
+ if (alertsContext?.payload?.logger) {
48
+ const log = createScopedLogger(alertsContext.payload, {
49
+ component: 'events'
50
+ });
51
+ log.event('warn', eventName, {
52
+ err,
53
+ ...fields
54
+ });
55
+ return;
56
+ }
57
+ // No payload logger available (pre-onInit, or consumer mock). Last-
58
+ // resort visibility — better than the previous silent swallow.
59
+ // eslint-disable-next-line no-console
60
+ console.warn(`[ai-translate] ${eventName}`, {
61
+ err,
62
+ ...fields
63
+ });
64
+ }
65
+ /**
66
+ * Best-effort alert persistence + consumer callback. Two outputs:
67
+ *
68
+ * 1. Persists a row to `translation-alerts` collection when the
69
+ * plugin's alertsContext is set (auto-registered when
70
+ * `usageTracking.enabled`). Lets the Hub surface alerts in-app.
71
+ * 2. Calls the consumer's `onAlert` callback if set (back-compat —
72
+ * pre-1.1.15 consumers wired this to Slack/email).
73
+ *
74
+ * Neither path can break the main translation flow — both are wrapped
75
+ * in best-effort try/catch with no propagation.
76
+ */ export function emitAlert(config, alert) {
77
+ // Persistence path. Doesn't depend on consumer config.
78
+ if (alertsContext) {
79
+ const ctx = alertsContext;
80
+ void (async ()=>{
81
+ try {
82
+ // Merge `code` + `context` (1.2.5+) into the metadata JSON
83
+ // column. AlertBanner reads them back via `metadata.code` /
84
+ // `metadata.context` and renders via `editorMessageFor()`.
85
+ // Pre-1.2.5 alerts without these fields fall back to the
86
+ // raw `message` string.
87
+ const baseMetadata = alert.metadata ?? null;
88
+ const mergedMetadata = alert.code || alert.context ? {
89
+ ...baseMetadata ?? {},
90
+ ...alert.code ? {
91
+ code: alert.code
92
+ } : {},
93
+ ...alert.context ? {
94
+ context: alert.context
95
+ } : {}
96
+ } : baseMetadata;
97
+ await ctx.payload.create({
98
+ collection: ctx.collectionSlug,
99
+ data: {
100
+ alertType: alert.type.replace(/^translation\./, ''),
101
+ message: alert.message,
102
+ collection: alert.collection ?? null,
103
+ documentId: alert.documentId != null ? String(alert.documentId) : null,
104
+ metadata: mergedMetadata,
105
+ dismissed: false
106
+ },
107
+ context: {
108
+ [ALERTS_CONTEXT_FLAG]: true,
109
+ [REENTRY_FLAG]: true
110
+ },
111
+ overrideAccess: true
112
+ });
113
+ } catch (err) {
114
+ // Best-effort. Persistence failure cannot break the consumer's
115
+ // `onAlert` callback (which may be the primary alerting channel,
116
+ // e.g. wired to PagerDuty). But surface as a structured pino
117
+ // event so Grafana sees the "alerts about alerts failing".
118
+ logEventsFailure('events.emit.failed', err, {
119
+ alertType: alert.type,
120
+ phase: 'persistence'
121
+ });
122
+ }
123
+ })();
124
+ }
125
+ // Consumer callback path. Unchanged from pre-1.1.15.
126
+ if (!config.onAlert) {
127
+ return;
128
+ }
129
+ try {
130
+ const result = config.onAlert(alert);
131
+ if (result && typeof result.catch === 'function') {
132
+ result.catch((err)=>{
133
+ logEventsFailure('events.emit.failed', err, {
134
+ alertType: alert.type,
135
+ phase: 'onAlert.async'
136
+ });
137
+ });
138
+ }
139
+ } catch (err) {
140
+ // Swallow — alert emission never breaks the main flow.
141
+ logEventsFailure('events.emit.failed', err, {
142
+ alertType: alert.type,
143
+ phase: 'onAlert.sync'
144
+ });
145
+ }
146
+ }
@@ -0,0 +1,3 @@
1
+ import type { AITranslatePluginConfig } from '../types.js';
2
+ export declare function getEffectiveExcludePatterns(config: Pick<AITranslatePluginConfig, 'excludeFields' | 'excludeUrlFields'>): string[];
3
+ export declare function isExcluded(fieldPath: string, patterns: string[]): boolean;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Effective exclude patterns: user-supplied `excludeFields` plus defaults:
3
+ * - `**.url` — URL fields trip the verbatim-echo validator and waste tokens;
4
+ * disable via `excludeUrlFields: false` for rare locale-specific deep links.
5
+ * - JSON-LD `@`-prefixed keys (`**.@type`, `**.@context`, `**.@id`, `**.@graph`)
6
+ * — schema.org constants that consumers parse as identifiers; translating
7
+ * `"Article"` to `"Artikel"` breaks structured-data interpretation. These
8
+ * are unconditionally excluded because translating them is never correct.
9
+ * Pattern matching uses literal `@` so `**.@type` matches paths ending in
10
+ * `.@type` only.
11
+ */ const JSON_LD_RESERVED_PATTERNS = [
12
+ '**.@type',
13
+ '**.@context',
14
+ '**.@id',
15
+ '**.@graph',
16
+ '**.@vocab'
17
+ ];
18
+ export function getEffectiveExcludePatterns(config) {
19
+ const user = config.excludeFields ?? [];
20
+ const patterns = [
21
+ ...user
22
+ ];
23
+ if (config.excludeUrlFields !== false && !user.includes('**.url')) {
24
+ // `**.url` matches any path ending in `.url` — link.url, hero.cta.url, etc.
25
+ patterns.push('**.url');
26
+ }
27
+ for (const reserved of JSON_LD_RESERVED_PATTERNS){
28
+ if (!patterns.includes(reserved)) patterns.push(reserved);
29
+ }
30
+ return patterns;
31
+ }
32
+ function matchesPattern(fieldPath, pattern) {
33
+ if (pattern === fieldPath) {
34
+ return true;
35
+ }
36
+ // Contains wildcard: '**.visibility.**' matches any path containing '.visibility.'.
37
+ // Must run before the prefix-only and suffix-only branches because those
38
+ // would otherwise claim a pattern like `**.x.**` (which starts with `**.`)
39
+ // before the contains-wildcard check ever runs.
40
+ if (pattern.startsWith('**.') && pattern.endsWith('.**')) {
41
+ const middle = pattern.slice(3, -3);
42
+ if (middle.length === 0) return true; // `**.**` matches anything
43
+ return fieldPath === middle || fieldPath.startsWith(middle + '.') || fieldPath.endsWith('.' + middle) || fieldPath.includes('.' + middle + '.');
44
+ }
45
+ // Suffix wildcard: 'visibility.**' matches 'visibility.auth', 'visibility.x.y'
46
+ if (pattern.endsWith('.**')) {
47
+ const prefix = pattern.slice(0, -3);
48
+ return fieldPath === prefix || fieldPath.startsWith(prefix + '.');
49
+ }
50
+ // Prefix wildcard: '**.visibility' matches 'content.0.visibility', 'x.y.visibility'
51
+ if (pattern.startsWith('**.')) {
52
+ const suffix = pattern.slice(3);
53
+ return fieldPath === suffix || fieldPath.endsWith('.' + suffix);
54
+ }
55
+ const patternParts = pattern.split('.');
56
+ const fieldParts = fieldPath.split('.');
57
+ if (patternParts.length !== fieldParts.length) {
58
+ return false;
59
+ }
60
+ return patternParts.every((part, i)=>part === '*' || part === fieldParts[i]);
61
+ }
62
+ export function isExcluded(fieldPath, patterns) {
63
+ return patterns.some((pattern)=>matchesPattern(fieldPath, pattern));
64
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Resolve a dotted Payload field path into a human-readable breadcrumb.
3
+ *
4
+ * Example:
5
+ * path = "sidebar.featured_sidebar_items.3.label"
6
+ * field = SiteNavigation tabs config
7
+ * → ["Sidebar Navigation", "Featured Sidebar Items", "#4", "Label"]
8
+ *
9
+ * The Translation Hub renders soft-skipped field paths to editors who
10
+ * don't think in dotted-paths — they think in "Sidebar Navigation ›
11
+ * Featured Items › Item #4 › Label." This helper walks the field config
12
+ * matching each path segment to either a `name`, a numeric array index,
13
+ * or a tab name; falls back to titleizing the raw segment when the
14
+ * config doesn't match (older row paths, deleted fields, etc).
15
+ *
16
+ * Pure function, no side effects, no I/O. Tested in isolation against
17
+ * synthetic Payload configs that cover tabs/groups/arrays/rows/blocks.
18
+ */
19
+ export type FieldBreadcrumbInput = {
20
+ /** Dotted field path, e.g. "sidebar.featured_sidebar_items.3.label" */
21
+ path: string;
22
+ /** Top-level field config from the collection or global */
23
+ fields: ReadonlyArray<unknown>;
24
+ };
25
+ /**
26
+ * Returns the resolved breadcrumb segments. Falls back to titleized
27
+ * raw segments for any part of the path that doesn't resolve, so the
28
+ * renderer can always show something. Numeric segments resolve to
29
+ * `#N` (1-based for editor familiarity).
30
+ */
31
+ export declare function resolveFieldBreadcrumb(input: FieldBreadcrumbInput): string[];
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Resolve a dotted Payload field path into a human-readable breadcrumb.
3
+ *
4
+ * Example:
5
+ * path = "sidebar.featured_sidebar_items.3.label"
6
+ * field = SiteNavigation tabs config
7
+ * → ["Sidebar Navigation", "Featured Sidebar Items", "#4", "Label"]
8
+ *
9
+ * The Translation Hub renders soft-skipped field paths to editors who
10
+ * don't think in dotted-paths — they think in "Sidebar Navigation ›
11
+ * Featured Items › Item #4 › Label." This helper walks the field config
12
+ * matching each path segment to either a `name`, a numeric array index,
13
+ * or a tab name; falls back to titleizing the raw segment when the
14
+ * config doesn't match (older row paths, deleted fields, etc).
15
+ *
16
+ * Pure function, no side effects, no I/O. Tested in isolation against
17
+ * synthetic Payload configs that cover tabs/groups/arrays/rows/blocks.
18
+ */ // ---------------------------------------------------------------------------
19
+ // Minimal local field-config shape
20
+ // ---------------------------------------------------------------------------
21
+ /**
22
+ * Returns the resolved breadcrumb segments. Falls back to titleized
23
+ * raw segments for any part of the path that doesn't resolve, so the
24
+ * renderer can always show something. Numeric segments resolve to
25
+ * `#N` (1-based for editor familiarity).
26
+ */ export function resolveFieldBreadcrumb(input) {
27
+ const { path, fields } = input;
28
+ if (!path) {
29
+ return [];
30
+ }
31
+ const parts = path.split('.');
32
+ const fieldArray = fields;
33
+ const result = walk(parts, 0, fieldArray, []);
34
+ return result.length > 0 ? result : [
35
+ titleize(path)
36
+ ];
37
+ }
38
+ // ---------------------------------------------------------------------------
39
+ // Walker
40
+ // ---------------------------------------------------------------------------
41
+ function walk(parts, index, fields, acc) {
42
+ if (index >= parts.length) {
43
+ return [
44
+ ...acc
45
+ ];
46
+ }
47
+ const part = parts[index];
48
+ // Numeric segment with no resolver context → render as `#N`. The
49
+ // array case below normally consumes the index after matching the
50
+ // array field, so reaching this branch means the path advanced into
51
+ // a block-typed array (which is unresolvable statically) or stale
52
+ // data — render the index without trying to descend further.
53
+ if (/^\d+$/.test(part)) {
54
+ const indexNum = Number(part);
55
+ return walk(parts, index + 1, fields, [
56
+ ...acc,
57
+ `#${indexNum + 1}`
58
+ ]);
59
+ }
60
+ if (!fields || fields.length === 0) {
61
+ const remaining = parts.slice(index).map(humanSegment);
62
+ return [
63
+ ...acc,
64
+ ...remaining
65
+ ];
66
+ }
67
+ const match = findInScope(fields, part);
68
+ if (!match) {
69
+ const remaining = parts.slice(index).map(humanSegment);
70
+ return [
71
+ ...acc,
72
+ ...remaining
73
+ ];
74
+ }
75
+ // Prepend any tab labels we crossed to reach the match
76
+ const acc2 = [
77
+ ...acc,
78
+ ...match.tabLabels.map((t)=>labelOf(t.label, t.name))
79
+ ];
80
+ const field = match.field;
81
+ const fieldLabel = labelOf(field.label, field.name);
82
+ switch(field.type){
83
+ case 'array':
84
+ {
85
+ const next = [
86
+ ...acc2,
87
+ fieldLabel
88
+ ];
89
+ const idxPart = parts[index + 1];
90
+ if (idxPart !== undefined && /^\d+$/.test(idxPart)) {
91
+ const idxNum = Number(idxPart);
92
+ return walk(parts, index + 2, field.fields, [
93
+ ...next,
94
+ `#${idxNum + 1}`
95
+ ]);
96
+ }
97
+ return walk(parts, index + 1, field.fields, next);
98
+ }
99
+ case 'group':
100
+ {
101
+ const next = [
102
+ ...acc2,
103
+ fieldLabel
104
+ ];
105
+ return walk(parts, index + 1, field.fields, next);
106
+ }
107
+ case 'blocks':
108
+ {
109
+ const next = [
110
+ ...acc2,
111
+ fieldLabel
112
+ ];
113
+ const idxPart = parts[index + 1];
114
+ if (idxPart !== undefined && /^\d+$/.test(idxPart)) {
115
+ const idxNum = Number(idxPart);
116
+ const tail = parts.slice(index + 2).map(humanSegment);
117
+ return [
118
+ ...next,
119
+ `#${idxNum + 1}`,
120
+ ...tail
121
+ ];
122
+ }
123
+ return walk(parts, index + 1, undefined, next);
124
+ }
125
+ default:
126
+ {
127
+ const next = [
128
+ ...acc2,
129
+ fieldLabel
130
+ ];
131
+ const remaining = parts.slice(index + 1).map(humanSegment);
132
+ return [
133
+ ...next,
134
+ ...remaining
135
+ ];
136
+ }
137
+ }
138
+ }
139
+ /**
140
+ * Find a named field within `fields`, transparently descending into
141
+ * `tabs`, `row`, and `collapsible` containers (which don't add a path
142
+ * segment). Returns the tab(s) crossed so the renderer can show
143
+ * "<TabLabel> › <Field>".
144
+ */ function findInScope(fields, name) {
145
+ for (const f of fields){
146
+ if (isNamedField(f) && f.name === name) {
147
+ return {
148
+ field: f,
149
+ tabLabels: []
150
+ };
151
+ }
152
+ }
153
+ for (const f of fields){
154
+ if (f.type === 'tabs' && f.tabs) {
155
+ for (const tab of f.tabs){
156
+ // Named tabs (`{ name: 'sidebar', fields: [...] }`) namespace
157
+ // their children under the tab name — so a path segment matching
158
+ // a tab name is the segment itself, not a child of an unnamed
159
+ // container. Treat the tab like a group: return a synthetic
160
+ // group field that the walker descends into.
161
+ if (tab.name === name) {
162
+ return {
163
+ field: {
164
+ type: 'group',
165
+ name: tab.name,
166
+ label: tab.label,
167
+ fields: tab.fields
168
+ },
169
+ tabLabels: []
170
+ };
171
+ }
172
+ const inside = tab.fields ?? [];
173
+ const inner = findInScope(inside, name);
174
+ if (inner) {
175
+ // Unnamed tab (`{ label: 'Foo', fields: [...] }`) — its
176
+ // children appear at the parent scope with no namespace, so
177
+ // the tab's label is prepended to the breadcrumb.
178
+ return {
179
+ field: inner.field,
180
+ tabLabels: tab.name ? inner.tabLabels : [
181
+ {
182
+ name: tab.name,
183
+ label: tab.label
184
+ },
185
+ ...inner.tabLabels
186
+ ]
187
+ };
188
+ }
189
+ }
190
+ }
191
+ if ((f.type === 'row' || f.type === 'collapsible') && f.fields) {
192
+ const inner = findInScope(f.fields, name);
193
+ if (inner) {
194
+ return inner;
195
+ }
196
+ }
197
+ }
198
+ return undefined;
199
+ }
200
+ function isNamedField(f) {
201
+ return typeof f.name === 'string';
202
+ }
203
+ // ---------------------------------------------------------------------------
204
+ // Label resolution
205
+ // ---------------------------------------------------------------------------
206
+ function labelOf(label, name) {
207
+ if (typeof label === 'string') {
208
+ return label;
209
+ }
210
+ if (label && typeof label === 'object') {
211
+ const first = Object.values(label).find((v)=>typeof v === 'string');
212
+ if (first) return first;
213
+ }
214
+ if (name) {
215
+ return titleize(name);
216
+ }
217
+ return '';
218
+ }
219
+ function humanSegment(s) {
220
+ if (/^\d+$/.test(s)) {
221
+ return `#${Number(s) + 1}`;
222
+ }
223
+ return titleize(s);
224
+ }
225
+ function titleize(s) {
226
+ return s.split(/[_-]/).map((w)=>w.length === 0 ? '' : w[0].toUpperCase() + w.slice(1)).join(' ');
227
+ }
@@ -0,0 +1 @@
1
+ export declare function diffFields(previousDoc: Record<string, unknown>, currentDoc: Record<string, unknown>, fieldPaths: string[]): string[];
@@ -0,0 +1,25 @@
1
+ import { hashFieldContent } from './content-hash.js';
2
+ function resolvePathValue(obj, path) {
3
+ const segments = path.split('.');
4
+ let current = obj;
5
+ for (const segment of segments){
6
+ if (current == null || typeof current !== 'object') {
7
+ return undefined;
8
+ }
9
+ current = current[segment];
10
+ }
11
+ return current;
12
+ }
13
+ export function diffFields(previousDoc, currentDoc, fieldPaths) {
14
+ const changed = [];
15
+ for (const path of fieldPaths){
16
+ const prevValue = resolvePathValue(previousDoc, path);
17
+ const currValue = resolvePathValue(currentDoc, path);
18
+ const prevHash = hashFieldContent(prevValue);
19
+ const currHash = hashFieldContent(currValue);
20
+ if (prevHash !== currHash) {
21
+ changed.push(path);
22
+ }
23
+ }
24
+ return changed;
25
+ }
@@ -0,0 +1,2 @@
1
+ import type { TranslatableFieldType } from '../types.js';
2
+ export declare function isFieldEmpty(value: unknown, fieldType: TranslatableFieldType): boolean;
@@ -0,0 +1,68 @@
1
+ function isTextEmpty(value) {
2
+ return value == null || typeof value === 'string' && value.trim() === '';
3
+ }
4
+ function isRichTextEmpty(value) {
5
+ if (value == null || typeof value !== 'object') {
6
+ return true;
7
+ }
8
+ const doc = value;
9
+ if (!doc.root || typeof doc.root !== 'object') {
10
+ return true;
11
+ }
12
+ const root = doc.root;
13
+ const children = root.children;
14
+ if (!Array.isArray(children) || children.length === 0) {
15
+ return true;
16
+ }
17
+ return children.every((child)=>{
18
+ if (child == null || typeof child !== 'object') {
19
+ return true;
20
+ }
21
+ const node = child;
22
+ const nodeChildren = node.children;
23
+ if (!Array.isArray(nodeChildren) || nodeChildren.length === 0) {
24
+ return true;
25
+ }
26
+ return nodeChildren.every((textNode)=>{
27
+ if (textNode == null || typeof textNode !== 'object') {
28
+ return true;
29
+ }
30
+ const tn = textNode;
31
+ return typeof tn.text === 'string' && tn.text.trim() === '';
32
+ });
33
+ });
34
+ }
35
+ function isArrayOrBlocksEmpty(value) {
36
+ if (value == null) {
37
+ return true;
38
+ }
39
+ if (!Array.isArray(value)) {
40
+ return true;
41
+ }
42
+ return value.length === 0;
43
+ }
44
+ function isGroupEmpty(value) {
45
+ if (value == null) {
46
+ return true;
47
+ }
48
+ if (typeof value !== 'object') {
49
+ return true;
50
+ }
51
+ return Object.values(value).every((v)=>v == null);
52
+ }
53
+ export function isFieldEmpty(value, fieldType) {
54
+ switch(fieldType){
55
+ case 'text':
56
+ case 'textarea':
57
+ return isTextEmpty(value);
58
+ case 'richText':
59
+ return isRichTextEmpty(value);
60
+ case 'array':
61
+ case 'blocks':
62
+ return isArrayOrBlocksEmpty(value);
63
+ case 'group':
64
+ return isGroupEmpty(value);
65
+ case 'json':
66
+ return isGroupEmpty(value);
67
+ }
68
+ }
@@ -0,0 +1,3 @@
1
+ import type { Field } from 'payload';
2
+ import type { TranslatableField } from '../types.js';
3
+ export declare function resolveTranslatableFields(fields: Field[], parentPath?: string, inheritedLocalized?: boolean): TranslatableField[];