@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,410 @@
1
+ import { serializeLexicalTree } from '../lexical/serializer.js';
2
+ import { isExcluded } from './exclude-fields.js';
3
+ /**
4
+ * Field types whose string values are translatable when found inside a
5
+ * `blocks` field. Mirror of the leaf-type set in `field-resolver.ts` —
6
+ * used by the schema-aware path in `extractBlockFields` to skip select /
7
+ * radio / checkbox / number / date / color values that would otherwise
8
+ * be heuristically translated and reject Payload's locale-write
9
+ * validation downstream.
10
+ */ const BLOCK_TRANSLATABLE_LEAF_TYPES = new Set([
11
+ 'text',
12
+ 'textarea',
13
+ 'richText',
14
+ 'json'
15
+ ]);
16
+ /**
17
+ * Build a `blockType → Block` map from a Payload collection / global's
18
+ * raw `fields` array. Walks `type: 'blocks'` fields anywhere in the tree
19
+ * (including under tabs, groups, arrays) and gathers their registered
20
+ * `blocks: Block[]` configurations.
21
+ *
22
+ * The result lets the extractor gate which block-internal keys are
23
+ * translatable based on the SCHEMA rather than the heuristic
24
+ * "every-string-is-text" walk that produces false positives for select
25
+ * values, color codes, etc. (the BUG-04 family).
26
+ */ export function collectBlocksConfig(fields) {
27
+ const out = new Map();
28
+ walkFieldsForBlocks(fields, out);
29
+ return out;
30
+ }
31
+ function walkFieldsForBlocks(fields, out) {
32
+ for (const raw of fields){
33
+ const f = raw;
34
+ const type = typeof f.type === 'string' ? f.type : undefined;
35
+ if (type === 'blocks' && Array.isArray(f.blocks)) {
36
+ for (const block of f.blocks){
37
+ const slug = block.slug;
38
+ if (slug && !out.has(slug)) out.set(slug, block);
39
+ if (Array.isArray(block.fields)) {
40
+ walkFieldsForBlocks(block.fields, out);
41
+ }
42
+ }
43
+ continue;
44
+ }
45
+ if ((type === 'group' || type === 'array' || type === 'row' || type === 'collapsible') && Array.isArray(f.fields)) {
46
+ walkFieldsForBlocks(f.fields, out);
47
+ continue;
48
+ }
49
+ if (type === 'tabs' && Array.isArray(f.tabs)) {
50
+ for (const tab of f.tabs){
51
+ if (Array.isArray(tab.fields)) {
52
+ walkFieldsForBlocks(tab.fields, out);
53
+ }
54
+ }
55
+ }
56
+ }
57
+ }
58
+ /**
59
+ * Returns a `keyName → declared field type` map for a Block. Recurses
60
+ * into nested group/array sub-fields so paths like `link.url` resolve.
61
+ */ function buildBlockFieldTypeMap(block) {
62
+ const map = new Map();
63
+ walkBlockFieldsForTypeMap(block.fields ?? [], '', map);
64
+ return map;
65
+ }
66
+ function walkBlockFieldsForTypeMap(fields, prefix, map) {
67
+ for (const raw of fields){
68
+ const f = raw;
69
+ const type = typeof f.type === 'string' ? f.type : undefined;
70
+ const name = typeof f.name === 'string' ? f.name : undefined;
71
+ if (type === 'row' || type === 'collapsible') {
72
+ if (Array.isArray(f.fields)) {
73
+ walkBlockFieldsForTypeMap(f.fields, prefix, map);
74
+ }
75
+ continue;
76
+ }
77
+ if (type === 'tabs' && Array.isArray(f.tabs)) {
78
+ for (const tab of f.tabs){
79
+ const tabName = typeof tab.name === 'string' ? tab.name : '';
80
+ const tabPath = tabName ? prefix ? `${prefix}.${tabName}` : tabName : prefix;
81
+ if (Array.isArray(tab.fields)) {
82
+ walkBlockFieldsForTypeMap(tab.fields, tabPath, map);
83
+ }
84
+ }
85
+ continue;
86
+ }
87
+ if (!name || !type) continue;
88
+ const path = prefix ? `${prefix}.${name}` : name;
89
+ map.set(path, type);
90
+ if ((type === 'group' || type === 'array') && Array.isArray(f.fields)) {
91
+ walkBlockFieldsForTypeMap(f.fields, path, map);
92
+ }
93
+ }
94
+ }
95
+ const blockTypeMapCache = new WeakMap();
96
+ function getCachedBlockTypeMap(block) {
97
+ let map = blockTypeMapCache.get(block);
98
+ if (!map) {
99
+ map = buildBlockFieldTypeMap(block);
100
+ blockTypeMapCache.set(block, map);
101
+ }
102
+ return map;
103
+ }
104
+ // ---------------------------------------------------------------------------
105
+ // Public API
106
+ // ---------------------------------------------------------------------------
107
+ export function extractTranslationUnits(doc, fields, excludePatterns, blocksConfig) {
108
+ const units = [];
109
+ for (const field of fields){
110
+ if (isExcluded(field.path, excludePatterns)) continue;
111
+ const value = getValueAtPath(doc, field.path);
112
+ if (value === undefined || value === null) continue;
113
+ switch(field.type){
114
+ case 'text':
115
+ case 'textarea':
116
+ {
117
+ if (typeof value === 'string' && value.trim().length > 0) {
118
+ units.push({
119
+ id: `${field.path}::0`,
120
+ fieldPath: field.path,
121
+ text: value,
122
+ kind: 'plain'
123
+ });
124
+ }
125
+ break;
126
+ }
127
+ case 'richText':
128
+ {
129
+ if (isLexicalRoot(value)) {
130
+ const serialized = serializeLexicalTree(value);
131
+ for (const unit of serialized){
132
+ units.push({
133
+ id: `${field.path}::${unit.blockId}`,
134
+ fieldPath: field.path,
135
+ text: unit.text,
136
+ kind: 'block'
137
+ });
138
+ }
139
+ }
140
+ break;
141
+ }
142
+ case 'json':
143
+ {
144
+ if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
145
+ extractJsonUnits(value, field.path, excludePatterns, units);
146
+ }
147
+ break;
148
+ }
149
+ case 'group':
150
+ {
151
+ if (typeof value === 'object' && !Array.isArray(value)) {
152
+ extractGroupUnits(value, field.path, fields, excludePatterns, units);
153
+ }
154
+ break;
155
+ }
156
+ case 'array':
157
+ {
158
+ if (Array.isArray(value)) {
159
+ extractArrayUnits(value, field.path, fields, excludePatterns, units, blocksConfig);
160
+ }
161
+ break;
162
+ }
163
+ case 'blocks':
164
+ {
165
+ if (Array.isArray(value)) {
166
+ extractBlocksUnits(value, field.path, excludePatterns, units, blocksConfig);
167
+ }
168
+ break;
169
+ }
170
+ }
171
+ }
172
+ return units;
173
+ }
174
+ // ---------------------------------------------------------------------------
175
+ // JSON extractor
176
+ // ---------------------------------------------------------------------------
177
+ function extractJsonUnits(obj, basePath, excludePatterns, units) {
178
+ for (const [key, value] of Object.entries(obj)){
179
+ const fieldPath = `${basePath}.${key}`;
180
+ if (isExcluded(fieldPath, excludePatterns)) continue;
181
+ if (typeof value === 'string' && value.trim().length > 0) {
182
+ units.push({
183
+ id: `${fieldPath}::0`,
184
+ fieldPath,
185
+ text: value,
186
+ kind: 'plain'
187
+ });
188
+ } else if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
189
+ // Recurse into nested objects
190
+ extractJsonUnits(value, fieldPath, excludePatterns, units);
191
+ }
192
+ // Skip arrays, numbers, booleans, nulls — not translatable
193
+ }
194
+ }
195
+ // ---------------------------------------------------------------------------
196
+ // Container extractors
197
+ // ---------------------------------------------------------------------------
198
+ function extractGroupUnits(groupValue, groupPath, allFields, excludePatterns, units) {
199
+ // Find child leaf fields that are under this group's path
200
+ const childLeaves = allFields.filter((f)=>f.path.startsWith(`${groupPath}.`) && (f.type === 'text' || f.type === 'textarea' || f.type === 'richText'));
201
+ for (const leaf of childLeaves){
202
+ if (isExcluded(leaf.path, excludePatterns)) continue;
203
+ // Resolve relative to the group value
204
+ const relativePath = leaf.path.slice(groupPath.length + 1);
205
+ const value = getValueAtPath(groupValue, relativePath);
206
+ if (value === undefined || value === null) continue;
207
+ extractLeafValue(value, leaf, units);
208
+ }
209
+ }
210
+ function extractArrayUnits(items, arrayPath, allFields, excludePatterns, units, blocksConfig) {
211
+ // Direct text leaves under this array — only those NOT reached through
212
+ // a deeper array/blocks container. Leaves that traverse another array
213
+ // are handled by recursing into that array below.
214
+ const childLeaves = allFields.filter((f)=>f.path.startsWith(`${arrayPath}.`) && (f.type === 'text' || f.type === 'textarea' || f.type === 'richText') && !pathTraversesNestedArray(f.path, arrayPath, allFields));
215
+ // Direct nested arrays/blocks under this array (not under another nested one)
216
+ const nestedContainers = allFields.filter((f)=>f.path.startsWith(`${arrayPath}.`) && (f.type === 'array' || f.type === 'blocks') && !pathTraversesNestedArray(f.path, arrayPath, allFields));
217
+ for(let i = 0; i < items.length; i++){
218
+ const item = items[i];
219
+ if (!item || typeof item !== 'object') continue;
220
+ for (const leaf of childLeaves){
221
+ const relativePath = leaf.path.slice(arrayPath.length + 1);
222
+ const indexedPath = `${arrayPath}.${i}.${relativePath}`;
223
+ if (isExcluded(indexedPath, excludePatterns)) continue;
224
+ const value = getValueAtPath(item, relativePath);
225
+ if (value === undefined || value === null) continue;
226
+ extractLeafValueWithPath(value, leaf.type, indexedPath, units);
227
+ }
228
+ for (const container of nestedContainers){
229
+ const relativePath = container.path.slice(arrayPath.length + 1);
230
+ const indexedContainerPath = `${arrayPath}.${i}.${relativePath}`;
231
+ const nestedValue = getValueAtPath(item, relativePath);
232
+ if (!Array.isArray(nestedValue)) continue;
233
+ const mappedFields = remapFieldsUnderPath(allFields, container.path, indexedContainerPath);
234
+ if (container.type === 'array') {
235
+ extractArrayUnits(nestedValue, indexedContainerPath, mappedFields, excludePatterns, units, blocksConfig);
236
+ } else {
237
+ extractBlocksUnits(nestedValue, indexedContainerPath, excludePatterns, units, blocksConfig);
238
+ }
239
+ }
240
+ }
241
+ }
242
+ function pathTraversesNestedArray(path, containerPath, allFields) {
243
+ for (const f of allFields){
244
+ if (f.type !== 'array' && f.type !== 'blocks') continue;
245
+ if (f.path === containerPath) continue;
246
+ if (!f.path.startsWith(`${containerPath}.`)) continue;
247
+ if (path.startsWith(`${f.path}.`)) return true;
248
+ }
249
+ return false;
250
+ }
251
+ function remapFieldsUnderPath(allFields, fromPath, toPath) {
252
+ return allFields.filter((f)=>f.path === fromPath || f.path.startsWith(`${fromPath}.`)).map((f)=>({
253
+ ...f,
254
+ path: toPath + f.path.slice(fromPath.length)
255
+ }));
256
+ }
257
+ function extractBlocksUnits(blocks, blocksPath, excludePatterns, units, blocksConfig) {
258
+ for(let i = 0; i < blocks.length; i++){
259
+ const block = blocks[i];
260
+ if (!block || typeof block !== 'object') continue;
261
+ const blockType = block.blockType;
262
+ const prefix = `${blocksPath}.${i}`;
263
+ extractBlockFields(block, prefix, blockType, excludePatterns, units, blocksConfig);
264
+ }
265
+ }
266
+ /**
267
+ * Walk a block's data. When `blocksConfig` has an entry for the current
268
+ * `blockType`, this becomes schema-aware: only keys whose declared type
269
+ * is text/textarea/richText/json get emitted as translation units.
270
+ * Select / radio / number / date / color / etc. are silently skipped,
271
+ * which fixes BUG-04 (non-text strings translated as text → locale-write
272
+ * validator rejects). Without a config entry the walker falls back to
273
+ * the previous heuristic so third-party blocks the plugin doesn't know
274
+ * about still translate.
275
+ */ function extractBlockFields(obj, pathPrefix, blockType, excludePatterns, units, blocksConfig, fieldTypeMap, fieldTypeMapPrefix = '') {
276
+ // Resolve the typeMap for the current block (if known). Falls back to
277
+ // an inherited map when recursing into nested groups inside the same
278
+ // block — sub-paths are still gated by the same schema.
279
+ const block = blockType && blocksConfig ? blocksConfig.get(blockType) : undefined;
280
+ const typeMap = block ? getCachedBlockTypeMap(block) : fieldTypeMap ?? null;
281
+ for (const [key, value] of Object.entries(obj)){
282
+ // Skip Payload internal block metadata
283
+ if (key === 'id' || key === 'blockType' || key === 'blockName') continue;
284
+ const fieldPath = `${pathPrefix}.${key}`;
285
+ if (isExcluded(fieldPath, excludePatterns)) continue;
286
+ // Schema-aware gating: only allow keys whose declared type is a
287
+ // translatable leaf, group, array, or blocks. Anything else (select,
288
+ // radio, number, date, color, upload, relationship, point, code) is
289
+ // skipped silently — non-translatable, but they're stored as plain
290
+ // strings/numbers that the heuristic walker used to translate.
291
+ const declared = typeMap ? typeMap.get(fieldTypeMapPrefix ? `${fieldTypeMapPrefix}.${key}` : key) : undefined;
292
+ if (typeMap && declared && !isTranslatableOrContainerType(declared)) continue;
293
+ if (typeof value === 'string' && value.trim().length > 0) {
294
+ // When the schema declares this key as a non-text leaf (e.g. an
295
+ // arbitrary `code` field that happens to be string-typed), skip.
296
+ // When the schema declares it as a leaf-text type, emit. When the
297
+ // schema doesn't know about this key (declared=undefined), only
298
+ // emit if there's no typeMap (no schema configured at all) — with
299
+ // a typeMap present, unknown keys are out-of-schema data that
300
+ // shouldn't be translated.
301
+ if (typeMap) {
302
+ if (!declared) continue;
303
+ if (!BLOCK_TRANSLATABLE_LEAF_TYPES.has(declared)) continue;
304
+ }
305
+ units.push({
306
+ id: `${fieldPath}::0`,
307
+ fieldPath,
308
+ text: value,
309
+ kind: 'plain'
310
+ });
311
+ } else if (isLexicalRoot(value)) {
312
+ if (typeMap && declared && declared !== 'richText') continue;
313
+ const serialized = serializeLexicalTree(value);
314
+ for (const unit of serialized){
315
+ units.push({
316
+ id: `${fieldPath}::${unit.blockId}`,
317
+ fieldPath,
318
+ text: unit.text,
319
+ kind: 'block'
320
+ });
321
+ }
322
+ } else if (Array.isArray(value)) {
323
+ // Nested array of blocks / items / strings.
324
+ // Object items recurse; primitive strings are extracted at indexed paths
325
+ // so JSON-typed fields with arrays of strings (e.g. `tags: ["a", "b"]`)
326
+ // are translated rather than passed through verbatim.
327
+ for(let i = 0; i < value.length; i++){
328
+ const item = value[i];
329
+ if (item && typeof item === 'object' && !Array.isArray(item)) {
330
+ const childBlockType = item.blockType;
331
+ extractBlockFields(item, `${fieldPath}.${i}`, childBlockType, excludePatterns, units, blocksConfig, // When recursing into a non-block array (e.g. `link.options[]`)
332
+ // we want to keep walking with the same typeMap if it covers
333
+ // sub-paths, otherwise fall back to heuristic.
334
+ childBlockType ? undefined : typeMap ?? undefined, childBlockType ? '' : `${fieldTypeMapPrefix || ''}${fieldTypeMapPrefix ? '.' : ''}${key}`);
335
+ } else if (typeof item === 'string' && item.trim().length > 0) {
336
+ units.push({
337
+ id: `${fieldPath}.${i}::0`,
338
+ fieldPath: `${fieldPath}.${i}`,
339
+ text: item,
340
+ kind: 'plain'
341
+ });
342
+ }
343
+ }
344
+ } else if (value && typeof value === 'object' && !Array.isArray(value)) {
345
+ // Nested group within the block — keep the same typeMap so paths
346
+ // like `link.url`, `link.type` are gated by the block's schema.
347
+ extractBlockFields(value, fieldPath, undefined, excludePatterns, units, blocksConfig, typeMap ?? undefined, fieldTypeMapPrefix ? `${fieldTypeMapPrefix}.${key}` : key);
348
+ }
349
+ }
350
+ }
351
+ function isTranslatableOrContainerType(declared) {
352
+ return BLOCK_TRANSLATABLE_LEAF_TYPES.has(declared) || declared === 'group' || declared === 'array' || declared === 'blocks';
353
+ }
354
+ // ---------------------------------------------------------------------------
355
+ // Leaf helpers
356
+ // ---------------------------------------------------------------------------
357
+ function extractLeafValue(value, field, units) {
358
+ extractLeafValueWithPath(value, field.type, field.path, units);
359
+ }
360
+ function extractLeafValueWithPath(value, fieldType, fieldPath, units) {
361
+ if (fieldType === 'text' || fieldType === 'textarea') {
362
+ if (typeof value === 'string' && value.trim().length > 0) {
363
+ units.push({
364
+ id: `${fieldPath}::0`,
365
+ fieldPath,
366
+ text: value,
367
+ kind: 'plain'
368
+ });
369
+ }
370
+ } else if (fieldType === 'richText') {
371
+ if (isLexicalRoot(value)) {
372
+ const serialized = serializeLexicalTree(value);
373
+ for (const unit of serialized){
374
+ units.push({
375
+ id: `${fieldPath}::${unit.blockId}`,
376
+ fieldPath,
377
+ text: unit.text,
378
+ kind: 'block'
379
+ });
380
+ }
381
+ }
382
+ }
383
+ }
384
+ // ---------------------------------------------------------------------------
385
+ // Path utilities
386
+ // ---------------------------------------------------------------------------
387
+ export function getValueAtPath(obj, path) {
388
+ const segments = path.split('.');
389
+ let current = obj;
390
+ for (const segment of segments){
391
+ if (current === null || current === undefined) return undefined;
392
+ if (typeof current !== 'object') return undefined;
393
+ if (Array.isArray(current)) {
394
+ const index = Number(segment);
395
+ if (Number.isNaN(index)) return undefined;
396
+ current = current[index];
397
+ } else {
398
+ current = current[segment];
399
+ }
400
+ }
401
+ return current;
402
+ }
403
+ // ---------------------------------------------------------------------------
404
+ // Type guards
405
+ // ---------------------------------------------------------------------------
406
+ function isLexicalRoot(value) {
407
+ if (value === null || typeof value !== 'object') return false;
408
+ const obj = value;
409
+ return 'root' in obj && obj.root !== null && typeof obj.root === 'object';
410
+ }
@@ -0,0 +1 @@
1
+ export declare function hashFieldContent(value: unknown): string;
@@ -0,0 +1,19 @@
1
+ import { createHash } from 'node:crypto';
2
+ function sortKeys(value) {
3
+ if (value == null || typeof value !== 'object') {
4
+ return value;
5
+ }
6
+ if (Array.isArray(value)) {
7
+ return value.map(sortKeys);
8
+ }
9
+ const sorted = {};
10
+ for (const key of Object.keys(value).sort()){
11
+ sorted[key] = sortKeys(value[key]);
12
+ }
13
+ return sorted;
14
+ }
15
+ export function hashFieldContent(value) {
16
+ const normalized = sortKeys(value);
17
+ const serialized = JSON.stringify(normalized) ?? 'undefined';
18
+ return createHash('sha256').update(serialized).digest('hex');
19
+ }
@@ -0,0 +1,15 @@
1
+ import type { TranslatableField } from '../types.js';
2
+ /**
3
+ * @param base Merge base — target-locale-first state that will be mutated
4
+ * (via structured clone) and returned with translated leaves written in.
5
+ * This was previously misleadingly called `sourceDoc`; it's the BASE that
6
+ * accumulates translated text on top of prior target-locale state.
7
+ * @param translatedUnits Map of unitId → translated text.
8
+ * @param fields Translatable field descriptors.
9
+ * @param sourceDoc The TRUE source-locale doc. Read-only. Used to recover
10
+ * structural skeletons (e.g. Lexical trees) when the base's target-locale
11
+ * row is empty or otherwise missing the source shape — without this the
12
+ * deserializer walks an empty/stale skeleton and discards source structure.
13
+ */
14
+ export declare function patchTranslatedContent(base: Record<string, unknown>, translatedUnits: Map<string, string>, fields: TranslatableField[], sourceDoc: Record<string, unknown>): Record<string, unknown>;
15
+ export declare function setValueAtPath(obj: Record<string, unknown>, path: string, value: unknown): void;