@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.
- package/LICENSE +21 -0
- package/README.md +714 -0
- package/dist/alerts-collection.d.ts +21 -0
- package/dist/alerts-collection.js +159 -0
- package/dist/api.d.ts +4 -0
- package/dist/api.js +918 -0
- package/dist/bulk-translate-batches-collection.d.ts +29 -0
- package/dist/bulk-translate-batches-collection.js +404 -0
- package/dist/bulk-translate-units-collection.d.ts +35 -0
- package/dist/bulk-translate-units-collection.js +310 -0
- package/dist/client/estimated-cost-cell.d.ts +6 -0
- package/dist/client/estimated-cost-cell.js +12 -0
- package/dist/client/excluded-fields-field.d.ts +45 -0
- package/dist/client/excluded-fields-field.js +553 -0
- package/dist/client/field-translate-button.d.ts +6 -0
- package/dist/client/field-translate-button.js +199 -0
- package/dist/client/index.d.ts +6 -0
- package/dist/client/index.js +6 -0
- package/dist/client/lib/use-global-kill-switches.d.ts +20 -0
- package/dist/client/lib/use-global-kill-switches.js +58 -0
- package/dist/client/translate-button.d.ts +2 -0
- package/dist/client/translate-button.js +228 -0
- package/dist/client/translate-modal.d.ts +16 -0
- package/dist/client/translate-modal.js +549 -0
- package/dist/client/translation-progress.d.ts +10 -0
- package/dist/client/translation-progress.js +297 -0
- package/dist/components/TranslationNavGroup.d.ts +45 -0
- package/dist/components/TranslationNavGroup.js +104 -0
- package/dist/defaults.d.ts +11 -0
- package/dist/defaults.js +16 -0
- package/dist/endpoints/client-config.d.ts +44 -0
- package/dist/endpoints/client-config.js +145 -0
- package/dist/endpoints/estimate.d.ts +5 -0
- package/dist/endpoints/estimate.js +237 -0
- package/dist/endpoints/progress.d.ts +2 -0
- package/dist/endpoints/progress.js +314 -0
- package/dist/endpoints/translate.d.ts +11 -0
- package/dist/endpoints/translate.js +376 -0
- package/dist/endpoints/translation-hub/_helpers.d.ts +140 -0
- package/dist/endpoints/translation-hub/_helpers.js +297 -0
- package/dist/endpoints/translation-hub/active.d.ts +21 -0
- package/dist/endpoints/translation-hub/active.js +220 -0
- package/dist/endpoints/translation-hub/cancel.d.ts +22 -0
- package/dist/endpoints/translation-hub/cancel.js +233 -0
- package/dist/endpoints/translation-hub/enqueue.d.ts +70 -0
- package/dist/endpoints/translation-hub/enqueue.js +529 -0
- package/dist/endpoints/translation-hub/failures.d.ts +12 -0
- package/dist/endpoints/translation-hub/failures.js +67 -0
- package/dist/endpoints/translation-hub/force-reset.d.ts +20 -0
- package/dist/endpoints/translation-hub/force-reset.js +144 -0
- package/dist/endpoints/translation-hub/index.d.ts +21 -0
- package/dist/endpoints/translation-hub/index.js +20 -0
- package/dist/endpoints/translation-hub/list.d.ts +40 -0
- package/dist/endpoints/translation-hub/list.js +182 -0
- package/dist/endpoints/translation-hub/preflight.d.ts +19 -0
- package/dist/endpoints/translation-hub/preflight.js +141 -0
- package/dist/endpoints/translation-hub/retry-failed.d.ts +38 -0
- package/dist/endpoints/translation-hub/retry-failed.js +235 -0
- package/dist/endpoints/translation-hub/revert.d.ts +88 -0
- package/dist/endpoints/translation-hub/revert.js +405 -0
- package/dist/endpoints/translation-hub/status.d.ts +45 -0
- package/dist/endpoints/translation-hub/status.js +391 -0
- package/dist/endpoints/translation-hub/usage-summary.d.ts +114 -0
- package/dist/endpoints/translation-hub/usage-summary.js +481 -0
- package/dist/exports/client.d.ts +6 -0
- package/dist/exports/client.js +6 -0
- package/dist/exports/components.d.ts +6 -0
- package/dist/exports/components.js +5 -0
- package/dist/exports/index.d.ts +8 -0
- package/dist/exports/index.js +7 -0
- package/dist/exports/providers.d.ts +9 -0
- package/dist/exports/providers.js +5 -0
- package/dist/exports/views-client.d.ts +23 -0
- package/dist/exports/views-client.js +22 -0
- package/dist/exports/views.d.ts +30 -0
- package/dist/exports/views.js +29 -0
- package/dist/hooks/after-change-global.d.ts +4 -0
- package/dist/hooks/after-change-global.js +109 -0
- package/dist/hooks/after-change.d.ts +16 -0
- package/dist/hooks/after-change.js +205 -0
- package/dist/hooks/after-delete.d.ts +30 -0
- package/dist/hooks/after-delete.js +95 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/jobs-collection.d.ts +17 -0
- package/dist/jobs-collection.js +139 -0
- package/dist/lexical/classifier.d.ts +3 -0
- package/dist/lexical/classifier.js +108 -0
- package/dist/lexical/deserializer.d.ts +4 -0
- package/dist/lexical/deserializer.js +263 -0
- package/dist/lexical/placeholder-integrity.d.ts +6 -0
- package/dist/lexical/placeholder-integrity.js +21 -0
- package/dist/lexical/placeholders.d.ts +21 -0
- package/dist/lexical/placeholders.js +117 -0
- package/dist/lexical/serializer.d.ts +21 -0
- package/dist/lexical/serializer.js +233 -0
- package/dist/lexical/types.d.ts +32 -0
- package/dist/lexical/types.js +1 -0
- package/dist/lib/auth-diagnostics.d.ts +14 -0
- package/dist/lib/auth-diagnostics.js +19 -0
- package/dist/lib/batch-counts.d.ts +58 -0
- package/dist/lib/batch-counts.js +105 -0
- package/dist/lib/bulk-translate-migrations.d.ts +92 -0
- package/dist/lib/bulk-translate-migrations.js +153 -0
- package/dist/lib/coalescing-queue.d.ts +38 -0
- package/dist/lib/coalescing-queue.js +69 -0
- package/dist/lib/content-extractor.d.ts +16 -0
- package/dist/lib/content-extractor.js +410 -0
- package/dist/lib/content-hash.d.ts +1 -0
- package/dist/lib/content-hash.js +19 -0
- package/dist/lib/content-patcher.d.ts +15 -0
- package/dist/lib/content-patcher.js +293 -0
- package/dist/lib/cost-guards.d.ts +2 -0
- package/dist/lib/cost-guards.js +18 -0
- package/dist/lib/daily-spend-cap.d.ts +58 -0
- package/dist/lib/daily-spend-cap.js +233 -0
- package/dist/lib/effective-locales.d.ts +181 -0
- package/dist/lib/effective-locales.js +302 -0
- package/dist/lib/error-messages.d.ts +245 -0
- package/dist/lib/error-messages.js +626 -0
- package/dist/lib/events.d.ts +39 -0
- package/dist/lib/events.js +146 -0
- package/dist/lib/exclude-fields.d.ts +3 -0
- package/dist/lib/exclude-fields.js +64 -0
- package/dist/lib/field-breadcrumb.d.ts +31 -0
- package/dist/lib/field-breadcrumb.js +227 -0
- package/dist/lib/field-diff.d.ts +1 -0
- package/dist/lib/field-diff.js +25 -0
- package/dist/lib/field-empty.d.ts +2 -0
- package/dist/lib/field-empty.js +68 -0
- package/dist/lib/field-resolver.d.ts +3 -0
- package/dist/lib/field-resolver.js +164 -0
- package/dist/lib/group-soft-skips.d.ts +39 -0
- package/dist/lib/group-soft-skips.js +45 -0
- package/dist/lib/locale-merge.d.ts +44 -0
- package/dist/lib/locale-merge.js +357 -0
- package/dist/lib/locale-row-check.d.ts +30 -0
- package/dist/lib/locale-row-check.js +64 -0
- package/dist/lib/logger.d.ts +74 -0
- package/dist/lib/logger.js +97 -0
- package/dist/lib/manual-edit-guard.d.ts +128 -0
- package/dist/lib/manual-edit-guard.js +393 -0
- package/dist/lib/output-validation.d.ts +48 -0
- package/dist/lib/output-validation.js +148 -0
- package/dist/lib/payload-read.d.ts +16 -0
- package/dist/lib/payload-read.js +51 -0
- package/dist/lib/per-doc-claim.d.ts +90 -0
- package/dist/lib/per-doc-claim.js +140 -0
- package/dist/lib/per-doc-lock.d.ts +94 -0
- package/dist/lib/per-doc-lock.js +119 -0
- package/dist/lib/persist-usage.d.ts +91 -0
- package/dist/lib/persist-usage.js +116 -0
- package/dist/lib/progress-store.d.ts +103 -0
- package/dist/lib/progress-store.js +314 -0
- package/dist/lib/rate-limiter.d.ts +3 -0
- package/dist/lib/rate-limiter.js +53 -0
- package/dist/lib/snapshot-select.d.ts +43 -0
- package/dist/lib/snapshot-select.js +108 -0
- package/dist/lib/translate-prompt.d.ts +31 -0
- package/dist/lib/translate-prompt.js +66 -0
- package/dist/lib/translation-token-bucket.d.ts +57 -0
- package/dist/lib/translation-token-bucket.js +365 -0
- package/dist/lib/truncate-source-value.d.ts +1 -0
- package/dist/lib/truncate-source-value.js +27 -0
- package/dist/manual-edit-collection.d.ts +22 -0
- package/dist/manual-edit-collection.js +124 -0
- package/dist/plugin.d.ts +3 -0
- package/dist/plugin.js +934 -0
- package/dist/providers/ai-sdk-adapter.d.ts +35 -0
- package/dist/providers/ai-sdk-adapter.js +100 -0
- package/dist/providers/anthropic.d.ts +31 -0
- package/dist/providers/anthropic.js +66 -0
- package/dist/providers/custom.d.ts +36 -0
- package/dist/providers/custom.js +24 -0
- package/dist/providers/gemini.d.ts +20 -0
- package/dist/providers/gemini.js +48 -0
- package/dist/providers/mock.d.ts +2 -0
- package/dist/providers/mock.js +29 -0
- package/dist/providers/openai.d.ts +28 -0
- package/dist/providers/openai.js +69 -0
- package/dist/settings-global.d.ts +74 -0
- package/dist/settings-global.js +216 -0
- package/dist/tasks/bulk-translate-coordinator.d.ts +115 -0
- package/dist/tasks/bulk-translate-coordinator.js +708 -0
- package/dist/tasks/bulk-translate-doc-task.d.ts +142 -0
- package/dist/tasks/bulk-translate-doc-task.js +1000 -0
- package/dist/tasks/bulk-translate-janitor.d.ts +87 -0
- package/dist/tasks/bulk-translate-janitor.js +311 -0
- package/dist/tasks/translate-job-task.d.ts +51 -0
- package/dist/tasks/translate-job-task.js +154 -0
- package/dist/translate.d.ts +113 -0
- package/dist/translate.js +911 -0
- package/dist/translation-daily-spend-collection.d.ts +24 -0
- package/dist/translation-daily-spend-collection.js +133 -0
- package/dist/translation-rate-limits-collection.d.ts +30 -0
- package/dist/translation-rate-limits-collection.js +144 -0
- package/dist/types.d.ts +672 -0
- package/dist/types.js +1 -0
- package/dist/usage-collection.d.ts +14 -0
- package/dist/usage-collection.js +377 -0
- package/dist/views/BulkRunsHub/BatchRow.d.ts +32 -0
- package/dist/views/BulkRunsHub/BatchRow.js +1222 -0
- package/dist/views/BulkRunsHub/BucketRow.d.ts +62 -0
- package/dist/views/BulkRunsHub/BucketRow.js +982 -0
- package/dist/views/BulkRunsHub/BulkRunsHub.client.d.ts +18 -0
- package/dist/views/BulkRunsHub/BulkRunsHub.client.js +331 -0
- package/dist/views/BulkRunsHub/EmptyState.d.ts +6 -0
- package/dist/views/BulkRunsHub/EmptyState.js +64 -0
- package/dist/views/BulkRunsHub/FilterBar.d.ts +16 -0
- package/dist/views/BulkRunsHub/FilterBar.js +284 -0
- package/dist/views/BulkRunsHub/InFlightBanner.d.ts +14 -0
- package/dist/views/BulkRunsHub/InFlightBanner.js +59 -0
- package/dist/views/BulkRunsHub/StatusBadge.d.ts +64 -0
- package/dist/views/BulkRunsHub/StatusBadge.js +248 -0
- package/dist/views/BulkRunsHub/SummaryStrip.d.ts +22 -0
- package/dist/views/BulkRunsHub/SummaryStrip.js +249 -0
- package/dist/views/BulkRunsHub/bucket-grouping.d.ts +200 -0
- package/dist/views/BulkRunsHub/bucket-grouping.js +344 -0
- package/dist/views/BulkRunsHub/bucketFailureSummary.d.ts +9 -0
- package/dist/views/BulkRunsHub/bucketFailureSummary.js +36 -0
- package/dist/views/BulkRunsHub/dedupedStatusFetch.d.ts +5 -0
- package/dist/views/BulkRunsHub/dedupedStatusFetch.js +45 -0
- package/dist/views/BulkRunsHub/index.d.ts +17 -0
- package/dist/views/BulkRunsHub/index.js +80 -0
- package/dist/views/BulkRunsHub/urlFilters.d.ts +14 -0
- package/dist/views/BulkRunsHub/urlFilters.js +50 -0
- package/dist/views/BulkRunsHub/useBulkRunsList.d.ts +26 -0
- package/dist/views/BulkRunsHub/useBulkRunsList.js +204 -0
- package/dist/views/BulkRunsHub/useUrlFilters.d.ts +10 -0
- package/dist/views/BulkRunsHub/useUrlFilters.js +88 -0
- package/dist/views/TranslationHub/ActiveJobs.d.ts +6 -0
- package/dist/views/TranslationHub/ActiveJobs.js +320 -0
- package/dist/views/TranslationHub/AdvancedPanel.d.ts +17 -0
- package/dist/views/TranslationHub/AdvancedPanel.js +996 -0
- package/dist/views/TranslationHub/AlertBanner.d.ts +6 -0
- package/dist/views/TranslationHub/AlertBanner.js +568 -0
- package/dist/views/TranslationHub/AuditPanel.d.ts +6 -0
- package/dist/views/TranslationHub/AuditPanel.helpers.d.ts +44 -0
- package/dist/views/TranslationHub/AuditPanel.helpers.js +71 -0
- package/dist/views/TranslationHub/AuditPanel.js +1367 -0
- package/dist/views/TranslationHub/BulkTranslate.types.d.ts +242 -0
- package/dist/views/TranslationHub/BulkTranslate.types.js +36 -0
- package/dist/views/TranslationHub/BulkTranslateFailureDrawer.d.ts +19 -0
- package/dist/views/TranslationHub/BulkTranslateFailureDrawer.js +332 -0
- package/dist/views/TranslationHub/BulkTranslateMonitor.d.ts +28 -0
- package/dist/views/TranslationHub/BulkTranslateMonitor.js +305 -0
- package/dist/views/TranslationHub/BulkTranslateNarrowViewportBanner.d.ts +3 -0
- package/dist/views/TranslationHub/BulkTranslateNarrowViewportBanner.js +42 -0
- package/dist/views/TranslationHub/BulkTranslatePostEnqueueTransition.d.ts +26 -0
- package/dist/views/TranslationHub/BulkTranslatePostEnqueueTransition.js +95 -0
- package/dist/views/TranslationHub/BulkTranslatePreflightModal.d.ts +22 -0
- package/dist/views/TranslationHub/BulkTranslatePreflightModal.js +879 -0
- package/dist/views/TranslationHub/BulkTranslateTerminalCard.d.ts +29 -0
- package/dist/views/TranslationHub/BulkTranslateTerminalCard.js +445 -0
- package/dist/views/TranslationHub/BulkTranslateTrigger.d.ts +66 -0
- package/dist/views/TranslationHub/BulkTranslateTrigger.js +161 -0
- package/dist/views/TranslationHub/EditorRecentRunsPanel.d.ts +33 -0
- package/dist/views/TranslationHub/EditorRecentRunsPanel.js +290 -0
- package/dist/views/TranslationHub/Hub.client.d.ts +74 -0
- package/dist/views/TranslationHub/Hub.client.js +357 -0
- package/dist/views/TranslationHub/ModelCombobox.d.ts +14 -0
- package/dist/views/TranslationHub/ModelCombobox.js +415 -0
- package/dist/views/TranslationHub/PerCollectionConfig.d.ts +10 -0
- package/dist/views/TranslationHub/PerCollectionConfig.helpers.d.ts +16 -0
- package/dist/views/TranslationHub/PerCollectionConfig.helpers.js +19 -0
- package/dist/views/TranslationHub/PerCollectionConfig.js +759 -0
- package/dist/views/TranslationHub/SettingsRail.d.ts +11 -0
- package/dist/views/TranslationHub/SettingsRail.js +382 -0
- package/dist/views/TranslationHub/StatusStrip.d.ts +6 -0
- package/dist/views/TranslationHub/StatusStrip.js +451 -0
- package/dist/views/TranslationHub/UsageTable.d.ts +6 -0
- package/dist/views/TranslationHub/UsageTable.helpers.d.ts +69 -0
- package/dist/views/TranslationHub/UsageTable.helpers.js +49 -0
- package/dist/views/TranslationHub/UsageTable.js +1240 -0
- package/dist/views/TranslationHub/alertGrouping.d.ts +70 -0
- package/dist/views/TranslationHub/alertGrouping.js +99 -0
- package/dist/views/TranslationHub/index.d.ts +20 -0
- package/dist/views/TranslationHub/index.js +109 -0
- package/dist/views/TranslationHub/tabNavigation.d.ts +53 -0
- package/dist/views/TranslationHub/tabNavigation.js +74 -0
- package/dist/views/TranslationHub/terminalBannerVisibility.d.ts +33 -0
- package/dist/views/TranslationHub/terminalBannerVisibility.js +124 -0
- package/dist/views/TranslationHub/useBulkTranslateActive.d.ts +49 -0
- package/dist/views/TranslationHub/useBulkTranslateActive.js +251 -0
- package/dist/views/TranslationHub/useFocusTrap.d.ts +6 -0
- package/dist/views/TranslationHub/useFocusTrap.js +81 -0
- package/dist/views/TranslationHub/useTranslationHubUsageSummary.d.ts +77 -0
- package/dist/views/TranslationHub/useTranslationHubUsageSummary.js +267 -0
- package/dist/views/shared/EditorError.d.ts +97 -0
- package/dist/views/shared/EditorError.js +205 -0
- package/dist/views/shared/ModelCell.d.ts +18 -0
- package/dist/views/shared/ModelCell.js +31 -0
- package/dist/views/shared/docHref.d.ts +16 -0
- package/dist/views/shared/docHref.js +26 -0
- package/dist/views/shared/fetch-error-body.d.ts +25 -0
- package/dist/views/shared/fetch-error-body.js +42 -0
- package/dist/views/shared/filterPillStyle.d.ts +35 -0
- package/dist/views/shared/filterPillStyle.js +40 -0
- package/dist/views/shared/format.d.ts +75 -0
- package/dist/views/shared/format.js +131 -0
- package/package.json +141 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import type { Payload } from 'payload';
|
|
2
|
+
import type { AITranslatePluginConfig } from '../types.js';
|
|
3
|
+
export declare function invalidateSettingsCache(slug?: string): void;
|
|
4
|
+
/** @internal test helper */
|
|
5
|
+
export declare function _resetEffectiveLocalesCache(): void;
|
|
6
|
+
/**
|
|
7
|
+
* Resolved auto-translate decision for a specific surface.
|
|
8
|
+
*
|
|
9
|
+
* - `locales`: the (possibly empty) list of target locales that should
|
|
10
|
+
* auto-translate on the next publish.
|
|
11
|
+
* - `skipAutoOnPublish`: when `true`, the auto-translate path is gated
|
|
12
|
+
* off — the hook should bail before creating a job. Manual Translate…
|
|
13
|
+
* (via the dialog or per-field button) is NOT affected by this flag.
|
|
14
|
+
* See W-4 in the panel review for the intentional scope: this layer
|
|
15
|
+
* only narrows automation, never the manual entry points.
|
|
16
|
+
*/
|
|
17
|
+
export type EffectiveLocaleDecision = {
|
|
18
|
+
locales: string[];
|
|
19
|
+
skipAutoOnPublish: boolean;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Read the per-doc auto-translate locale-narrow override from a saved doc.
|
|
23
|
+
*
|
|
24
|
+
* The plugin injects `_aiTranslateAutoLocales` (`select hasMany`) into
|
|
25
|
+
* every tracked collection / global as a NARROW filter — only locales
|
|
26
|
+
* the editor explicitly picks fan out. Two states are meaningful:
|
|
27
|
+
*
|
|
28
|
+
* - `undefined` / not-an-array / empty array → no narrowing (inherit
|
|
29
|
+
* the collection-level decision; fan out to every enabled locale).
|
|
30
|
+
* - non-empty array → restrict the fan-out to these locales.
|
|
31
|
+
*
|
|
32
|
+
* Editors who want to OPT A DOC OUT of auto-translate entirely use the
|
|
33
|
+
* sibling `_aiTranslateOptOut` boolean field — see `getDocOptOut`.
|
|
34
|
+
*
|
|
35
|
+
* Historical note: pre-fix versions of the plugin documented a 3-state
|
|
36
|
+
* contract where `[]` meant "explicit opt-out." That state was
|
|
37
|
+
* unreachable at the storage layer because Payload's hasMany select
|
|
38
|
+
* serializes both "admin never touched it" and "admin cleared every
|
|
39
|
+
* checkbox" as 0 rows in the join table → empty array on read. The
|
|
40
|
+
* collapse meant auto-translate was effectively dead by default. The
|
|
41
|
+
* fix splits the two responsibilities: this field narrows locales when
|
|
42
|
+
* non-empty; the new boolean opts the whole doc out when true.
|
|
43
|
+
*/
|
|
44
|
+
export declare function getDocAutoLocaleOverride(doc: unknown): string[] | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* Read the per-doc opt-out flag. `true` means "skip auto-translate
|
|
47
|
+
* entirely for this document, regardless of collection or site-wide
|
|
48
|
+
* settings." Manual Translate from the dialog is NOT gated by this
|
|
49
|
+
* flag — same scope as the collection-level `enabled` flag.
|
|
50
|
+
*
|
|
51
|
+
* Defaults to `false`: a document that has never had the field touched
|
|
52
|
+
* (or is missing it on legacy/pre-migration data) participates in
|
|
53
|
+
* auto-translate normally.
|
|
54
|
+
*/
|
|
55
|
+
export declare function getDocOptOut(doc: unknown): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Compute the locales that should auto-translate on the next publish for
|
|
58
|
+
* the given surface (collection slug OR global slug), by intersecting:
|
|
59
|
+
*
|
|
60
|
+
* 1. Plugin config `targetLocales` — the universe.
|
|
61
|
+
* 2. Site-wide `enabledTargetLocales` from `translation-settings`.
|
|
62
|
+
* Empty / unset = "all enabled" (back-compat default).
|
|
63
|
+
* 3. Per-surface `perCollection` row, if any:
|
|
64
|
+
* - `enabled: false` → no locales + `skipAutoOnPublish: true`.
|
|
65
|
+
* - `autoOnPublish: false` → no locales + `skipAutoOnPublish: true`.
|
|
66
|
+
* - `targetLocalesOverride` non-empty → narrows further.
|
|
67
|
+
*
|
|
68
|
+
* Each layer NARROWS the previous — never widens.
|
|
69
|
+
*
|
|
70
|
+
* **Fail-safe on read failure**: a `findGlobal` exception (DB blip,
|
|
71
|
+
* permission misconfig, slug mismatch) returns
|
|
72
|
+
* `{ locales: [], skipAutoOnPublish: true }`. Auto-translate skips, the
|
|
73
|
+
* editor doesn't see a phantom job, and no LLM spend is incurred
|
|
74
|
+
* against locales the admin may have disabled but we couldn't read.
|
|
75
|
+
* Manual translate still works because it doesn't route through this
|
|
76
|
+
* function. This was a behavior flip from "fail-open" — see W-3 in the
|
|
77
|
+
* 2026-05-15 panel review for the rationale.
|
|
78
|
+
*/
|
|
79
|
+
export declare function getEffectiveAutoLocales(payload: Payload, config: AITranslatePluginConfig, surfaceSlug?: string): Promise<EffectiveLocaleDecision>;
|
|
80
|
+
/**
|
|
81
|
+
* Returns `true` when the admin set `perCollection[surfaceSlug].enabled = false`.
|
|
82
|
+
*
|
|
83
|
+
* Used by the manual-translate endpoint to gate the dialog AND per-field
|
|
84
|
+
* button so the `enabled` checkbox is a true kill switch (BUG-08 fix).
|
|
85
|
+
* The sibling `autoOnPublish` flag remains automation-only — admins
|
|
86
|
+
* who want "manual translate yes, auto-translate no" should use that
|
|
87
|
+
* flag instead of `enabled: false`.
|
|
88
|
+
*
|
|
89
|
+
* **Fail-safe-OPEN on read failure**: returns `false` (allows translate)
|
|
90
|
+
* if settings can't be read. Blocking manual translate on a DB blip is
|
|
91
|
+
* more disruptive than letting the user proceed.
|
|
92
|
+
*/
|
|
93
|
+
export declare function isSurfaceDisabledByAdmin(payload: Payload, config: AITranslatePluginConfig, surfaceSlug: string): Promise<boolean>;
|
|
94
|
+
/**
|
|
95
|
+
* Resolved state of the three plugin-wide kill switches added in v1.2.8.
|
|
96
|
+
* Each flag supersedes every per-collection setting — when `false`, the
|
|
97
|
+
* corresponding subsystem refuses to do work regardless of what the
|
|
98
|
+
* `perCollection` row says.
|
|
99
|
+
*
|
|
100
|
+
* Fail-safe direction matches the existing per-domain reasoning:
|
|
101
|
+
*
|
|
102
|
+
* - `autoEnabled` fails CLOSED on read failure (returns `false`). This
|
|
103
|
+
* mirrors `getEffectiveAutoLocales`: when we can't read settings, we
|
|
104
|
+
* don't know whether the admin disabled auto-translate, so we don't
|
|
105
|
+
* spend tokens. The after-change hook becomes a no-op until settings
|
|
106
|
+
* read recovers — same as if every locale were narrowed away.
|
|
107
|
+
*
|
|
108
|
+
* - `manualEnabled` fails OPEN on read failure (returns `true`). The
|
|
109
|
+
* manual entry points are user-initiated; blocking them on a DB blip
|
|
110
|
+
* is more disruptive than letting the editor through. This mirrors
|
|
111
|
+
* `isSurfaceDisabledByAdmin`'s open-on-failure direction.
|
|
112
|
+
*
|
|
113
|
+
* - `bulkEnabled` fails OPEN on read failure (returns `true`). New bulk
|
|
114
|
+
* runs are user-initiated; same reasoning as manual.
|
|
115
|
+
*/
|
|
116
|
+
export type GlobalKillSwitches = {
|
|
117
|
+
autoEnabled: boolean;
|
|
118
|
+
manualEnabled: boolean;
|
|
119
|
+
bulkEnabled: boolean;
|
|
120
|
+
};
|
|
121
|
+
export declare function getGlobalKillSwitches(payload: Payload, config: AITranslatePluginConfig): Promise<GlobalKillSwitches>;
|
|
122
|
+
/**
|
|
123
|
+
* Return the set of field paths the admin opted out of translation
|
|
124
|
+
* for `surfaceSlug`, stored on the matching `perCollection` row of the
|
|
125
|
+
* `translation-settings` global.
|
|
126
|
+
*
|
|
127
|
+
* Scope: applies to BOTH manual and automation paths. The admin
|
|
128
|
+
* decision "don't translate `meta.description` on posts" is global —
|
|
129
|
+
* it would be surprising if Manual Translate… ignored it. This is the
|
|
130
|
+
* intentional difference vs `enabled` / `autoOnPublish`, which are
|
|
131
|
+
* automation-only gates.
|
|
132
|
+
*
|
|
133
|
+
* **Fail-safe-OPEN on read failure**: returns an empty set if the
|
|
134
|
+
* settings global can't be read. Blocking a manual translate on a DB
|
|
135
|
+
* blip would be more disruptive than letting the (already-tracked)
|
|
136
|
+
* field through. The locale-resolution function above flips the other
|
|
137
|
+
* direction because it's gating LLM SPEND, not which fields go in.
|
|
138
|
+
*/
|
|
139
|
+
export declare function getExcludedFieldPaths(payload: Payload, config: AITranslatePluginConfig, surfaceSlug: string): Promise<Set<string>>;
|
|
140
|
+
/**
|
|
141
|
+
* Surface-aware variant of `getEffectiveExcludePatterns`.
|
|
142
|
+
*
|
|
143
|
+
* Reads the `perCollection[surfaceSlug].translateSlug` flag from the
|
|
144
|
+
* translation-settings global. When `true`, drops `'slug'` from the
|
|
145
|
+
* effective patterns so the resolver picks it up. When `false` (the
|
|
146
|
+
* default), behaviour is identical to `getEffectiveExcludePatterns`.
|
|
147
|
+
*
|
|
148
|
+
* Use this in every translation path that operates on a known
|
|
149
|
+
* surface — `translateDocument`, `translateGlobal`, the estimate
|
|
150
|
+
* endpoint, the cost-guard pre-flight, and the admin UI's
|
|
151
|
+
* client-config map. The static `getEffectiveExcludePatterns` is
|
|
152
|
+
* still appropriate for config-build-time paths (per-field button
|
|
153
|
+
* injection) that have no async context AND no per-row knowledge.
|
|
154
|
+
*
|
|
155
|
+
* **Fail-safe-CLOSED on read failure**: returns the base patterns
|
|
156
|
+
* unchanged. This is the conservative direction — if we can't read
|
|
157
|
+
* the toggle, we keep slug excluded (no per-locale URL churn from a
|
|
158
|
+
* DB blip). Mirrors `getExcludedFieldPaths`' policy of "blocking
|
|
159
|
+
* manual translate on a settings-read blip is more disruptive than
|
|
160
|
+
* letting the field through" — except in this case the opposite
|
|
161
|
+
* applies because the default IS exclusion.
|
|
162
|
+
*/
|
|
163
|
+
export declare function getEffectiveExcludePatternsForSurface(payload: Payload, config: AITranslatePluginConfig, surfaceSlug: string): Promise<string[]>;
|
|
164
|
+
/**
|
|
165
|
+
* Prefix-aware match: `path` is excluded if it appears in `excluded`
|
|
166
|
+
* OR any ancestor segment of it does. Excluding `layout` therefore
|
|
167
|
+
* excludes `layout.columns`, `layout.columns.richText`, etc.
|
|
168
|
+
*
|
|
169
|
+
* Why prefix and not exact: editors think in top-level fields. The
|
|
170
|
+
* admin UI offers ONE checkbox per top-level field — unchecking
|
|
171
|
+
* "Layout" should skip everything under it. Exact-match would leave
|
|
172
|
+
* `layout.columns.richText` translating even when the editor opted
|
|
173
|
+
* the entire Layout out. Per-leaf exclusions are still expressible
|
|
174
|
+
* via the more specific path (e.g. `meta.description`).
|
|
175
|
+
*
|
|
176
|
+
* Implementation note: O(D × |excluded|) per call where D is the
|
|
177
|
+
* path's segment depth (small constant in practice). Acceptable for
|
|
178
|
+
* the per-fire filter; would warrant a trie if exclusion lists grew
|
|
179
|
+
* to hundreds.
|
|
180
|
+
*/
|
|
181
|
+
export declare function isPathExcluded(path: string, excluded: Set<string>): boolean;
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { DEFAULT_SETTINGS_GLOBAL_SLUG } from '../settings-global.js';
|
|
2
|
+
import { getEffectiveExcludePatterns } from './exclude-fields.js';
|
|
3
|
+
const SETTINGS_CACHE_TTL_MS = 30_000;
|
|
4
|
+
const settingsCache = new Map();
|
|
5
|
+
export function invalidateSettingsCache(slug) {
|
|
6
|
+
if (slug) {
|
|
7
|
+
settingsCache.delete(slug);
|
|
8
|
+
} else {
|
|
9
|
+
settingsCache.clear();
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/** @internal test helper */ export function _resetEffectiveLocalesCache() {
|
|
13
|
+
settingsCache.clear();
|
|
14
|
+
}
|
|
15
|
+
async function readSettings(payload, config) {
|
|
16
|
+
const slug = config.settings?.globalSlug ?? DEFAULT_SETTINGS_GLOBAL_SLUG;
|
|
17
|
+
const cached = settingsCache.get(slug);
|
|
18
|
+
const now = Date.now();
|
|
19
|
+
if (cached && cached.expiresAt > now) {
|
|
20
|
+
return {
|
|
21
|
+
failed: false,
|
|
22
|
+
settings: cached.value
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const settings = await payload.findGlobal({
|
|
27
|
+
slug,
|
|
28
|
+
depth: 0,
|
|
29
|
+
overrideAccess: true
|
|
30
|
+
});
|
|
31
|
+
settingsCache.set(slug, {
|
|
32
|
+
value: settings,
|
|
33
|
+
expiresAt: now + SETTINGS_CACHE_TTL_MS
|
|
34
|
+
});
|
|
35
|
+
return {
|
|
36
|
+
failed: false,
|
|
37
|
+
settings
|
|
38
|
+
};
|
|
39
|
+
} catch (err) {
|
|
40
|
+
payload.logger?.error?.(`[ai-translate] Failed to read "${slug}" global — settings-driven gates will fall back to safe defaults. Underlying error: ${err instanceof Error ? err.message : 'Unknown error'}`);
|
|
41
|
+
// Do NOT cache the failure — next fire should retry.
|
|
42
|
+
return {
|
|
43
|
+
failed: true,
|
|
44
|
+
settings: null
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Read the per-doc auto-translate locale-narrow override from a saved doc.
|
|
50
|
+
*
|
|
51
|
+
* The plugin injects `_aiTranslateAutoLocales` (`select hasMany`) into
|
|
52
|
+
* every tracked collection / global as a NARROW filter — only locales
|
|
53
|
+
* the editor explicitly picks fan out. Two states are meaningful:
|
|
54
|
+
*
|
|
55
|
+
* - `undefined` / not-an-array / empty array → no narrowing (inherit
|
|
56
|
+
* the collection-level decision; fan out to every enabled locale).
|
|
57
|
+
* - non-empty array → restrict the fan-out to these locales.
|
|
58
|
+
*
|
|
59
|
+
* Editors who want to OPT A DOC OUT of auto-translate entirely use the
|
|
60
|
+
* sibling `_aiTranslateOptOut` boolean field — see `getDocOptOut`.
|
|
61
|
+
*
|
|
62
|
+
* Historical note: pre-fix versions of the plugin documented a 3-state
|
|
63
|
+
* contract where `[]` meant "explicit opt-out." That state was
|
|
64
|
+
* unreachable at the storage layer because Payload's hasMany select
|
|
65
|
+
* serializes both "admin never touched it" and "admin cleared every
|
|
66
|
+
* checkbox" as 0 rows in the join table → empty array on read. The
|
|
67
|
+
* collapse meant auto-translate was effectively dead by default. The
|
|
68
|
+
* fix splits the two responsibilities: this field narrows locales when
|
|
69
|
+
* non-empty; the new boolean opts the whole doc out when true.
|
|
70
|
+
*/ export function getDocAutoLocaleOverride(doc) {
|
|
71
|
+
if (!doc || typeof doc !== 'object') return undefined;
|
|
72
|
+
const value = doc._aiTranslateAutoLocales;
|
|
73
|
+
if (!Array.isArray(value)) return undefined;
|
|
74
|
+
// Defensive copy + ensure all elements are strings (the field is a
|
|
75
|
+
// typed select; this filters out any stray malformed entries from
|
|
76
|
+
// direct DB writes or older data).
|
|
77
|
+
const cleaned = value.filter((v)=>typeof v === 'string');
|
|
78
|
+
// Empty array = inherit. Explicit opt-out lives on the
|
|
79
|
+
// `_aiTranslateOptOut` boolean instead.
|
|
80
|
+
if (cleaned.length === 0) return undefined;
|
|
81
|
+
return cleaned;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Read the per-doc opt-out flag. `true` means "skip auto-translate
|
|
85
|
+
* entirely for this document, regardless of collection or site-wide
|
|
86
|
+
* settings." Manual Translate from the dialog is NOT gated by this
|
|
87
|
+
* flag — same scope as the collection-level `enabled` flag.
|
|
88
|
+
*
|
|
89
|
+
* Defaults to `false`: a document that has never had the field touched
|
|
90
|
+
* (or is missing it on legacy/pre-migration data) participates in
|
|
91
|
+
* auto-translate normally.
|
|
92
|
+
*/ export function getDocOptOut(doc) {
|
|
93
|
+
if (!doc || typeof doc !== 'object') return false;
|
|
94
|
+
const value = doc._aiTranslateOptOut;
|
|
95
|
+
return value === true;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Compute the locales that should auto-translate on the next publish for
|
|
99
|
+
* the given surface (collection slug OR global slug), by intersecting:
|
|
100
|
+
*
|
|
101
|
+
* 1. Plugin config `targetLocales` — the universe.
|
|
102
|
+
* 2. Site-wide `enabledTargetLocales` from `translation-settings`.
|
|
103
|
+
* Empty / unset = "all enabled" (back-compat default).
|
|
104
|
+
* 3. Per-surface `perCollection` row, if any:
|
|
105
|
+
* - `enabled: false` → no locales + `skipAutoOnPublish: true`.
|
|
106
|
+
* - `autoOnPublish: false` → no locales + `skipAutoOnPublish: true`.
|
|
107
|
+
* - `targetLocalesOverride` non-empty → narrows further.
|
|
108
|
+
*
|
|
109
|
+
* Each layer NARROWS the previous — never widens.
|
|
110
|
+
*
|
|
111
|
+
* **Fail-safe on read failure**: a `findGlobal` exception (DB blip,
|
|
112
|
+
* permission misconfig, slug mismatch) returns
|
|
113
|
+
* `{ locales: [], skipAutoOnPublish: true }`. Auto-translate skips, the
|
|
114
|
+
* editor doesn't see a phantom job, and no LLM spend is incurred
|
|
115
|
+
* against locales the admin may have disabled but we couldn't read.
|
|
116
|
+
* Manual translate still works because it doesn't route through this
|
|
117
|
+
* function. This was a behavior flip from "fail-open" — see W-3 in the
|
|
118
|
+
* 2026-05-15 panel review for the rationale.
|
|
119
|
+
*/ export async function getEffectiveAutoLocales(payload, config, surfaceSlug) {
|
|
120
|
+
const universe = config.targetLocales ?? [];
|
|
121
|
+
if (universe.length === 0) {
|
|
122
|
+
return {
|
|
123
|
+
locales: [],
|
|
124
|
+
skipAutoOnPublish: false
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
const read = await readSettings(payload, config);
|
|
128
|
+
if (read.failed) {
|
|
129
|
+
// Locale resolution is fail-safe-skip: we don't know which locales
|
|
130
|
+
// the admin disabled, so spending tokens against any of them is a
|
|
131
|
+
// policy violation. Manual translate still works because it doesn't
|
|
132
|
+
// route through this function.
|
|
133
|
+
return {
|
|
134
|
+
locales: [],
|
|
135
|
+
skipAutoOnPublish: true
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
const settings = read.settings;
|
|
139
|
+
// Per-surface override is evaluated FIRST when surfaceSlug is provided.
|
|
140
|
+
// `enabled: false` and `autoOnPublish: false` short-circuit the entire
|
|
141
|
+
// resolution — there's no point computing the site-wide intersection
|
|
142
|
+
// when we're about to return an empty list anyway. Saves a small bit
|
|
143
|
+
// of work per fire, scales meaningfully under bulk-publish.
|
|
144
|
+
if (surfaceSlug && Array.isArray(settings?.perCollection)) {
|
|
145
|
+
const row = settings.perCollection.find((r)=>Boolean(r) && r?.slug === surfaceSlug);
|
|
146
|
+
if (row) {
|
|
147
|
+
if (row.enabled === false) {
|
|
148
|
+
return {
|
|
149
|
+
locales: [],
|
|
150
|
+
skipAutoOnPublish: true
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
if (row.autoOnPublish === false) {
|
|
154
|
+
return {
|
|
155
|
+
locales: [],
|
|
156
|
+
skipAutoOnPublish: true
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// Site-wide allow-list. Empty / unset = "all configured target locales".
|
|
162
|
+
const enabled = settings?.enabledTargetLocales;
|
|
163
|
+
let siteWide = universe;
|
|
164
|
+
if (Array.isArray(enabled) && enabled.length > 0) {
|
|
165
|
+
siteWide = universe.filter((l)=>enabled.includes(l));
|
|
166
|
+
}
|
|
167
|
+
// Per-surface target-locale narrowing (applied after the
|
|
168
|
+
// `enabled` / `autoOnPublish` gate above).
|
|
169
|
+
if (surfaceSlug && Array.isArray(settings?.perCollection)) {
|
|
170
|
+
const row = settings.perCollection.find((r)=>r?.slug === surfaceSlug);
|
|
171
|
+
const override = row?.targetLocalesOverride;
|
|
172
|
+
if (Array.isArray(override) && override.length > 0) {
|
|
173
|
+
return {
|
|
174
|
+
locales: siteWide.filter((l)=>override.includes(l)),
|
|
175
|
+
skipAutoOnPublish: false
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
locales: siteWide,
|
|
181
|
+
skipAutoOnPublish: false
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Returns `true` when the admin set `perCollection[surfaceSlug].enabled = false`.
|
|
186
|
+
*
|
|
187
|
+
* Used by the manual-translate endpoint to gate the dialog AND per-field
|
|
188
|
+
* button so the `enabled` checkbox is a true kill switch (BUG-08 fix).
|
|
189
|
+
* The sibling `autoOnPublish` flag remains automation-only — admins
|
|
190
|
+
* who want "manual translate yes, auto-translate no" should use that
|
|
191
|
+
* flag instead of `enabled: false`.
|
|
192
|
+
*
|
|
193
|
+
* **Fail-safe-OPEN on read failure**: returns `false` (allows translate)
|
|
194
|
+
* if settings can't be read. Blocking manual translate on a DB blip is
|
|
195
|
+
* more disruptive than letting the user proceed.
|
|
196
|
+
*/ export async function isSurfaceDisabledByAdmin(payload, config, surfaceSlug) {
|
|
197
|
+
const read = await readSettings(payload, config);
|
|
198
|
+
if (read.failed || !read.settings?.perCollection) return false;
|
|
199
|
+
const row = read.settings.perCollection.find((r)=>r?.slug === surfaceSlug);
|
|
200
|
+
return row?.enabled === false;
|
|
201
|
+
}
|
|
202
|
+
export async function getGlobalKillSwitches(payload, config) {
|
|
203
|
+
const read = await readSettings(payload, config);
|
|
204
|
+
if (read.failed) {
|
|
205
|
+
return {
|
|
206
|
+
autoEnabled: false,
|
|
207
|
+
manualEnabled: true,
|
|
208
|
+
bulkEnabled: true
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
const settings = read.settings;
|
|
212
|
+
return {
|
|
213
|
+
autoEnabled: settings?.globalAutoTranslateEnabled !== false,
|
|
214
|
+
manualEnabled: settings?.globalManualTranslateEnabled !== false,
|
|
215
|
+
bulkEnabled: settings?.globalBulkTranslateEnabled !== false
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Return the set of field paths the admin opted out of translation
|
|
220
|
+
* for `surfaceSlug`, stored on the matching `perCollection` row of the
|
|
221
|
+
* `translation-settings` global.
|
|
222
|
+
*
|
|
223
|
+
* Scope: applies to BOTH manual and automation paths. The admin
|
|
224
|
+
* decision "don't translate `meta.description` on posts" is global —
|
|
225
|
+
* it would be surprising if Manual Translate… ignored it. This is the
|
|
226
|
+
* intentional difference vs `enabled` / `autoOnPublish`, which are
|
|
227
|
+
* automation-only gates.
|
|
228
|
+
*
|
|
229
|
+
* **Fail-safe-OPEN on read failure**: returns an empty set if the
|
|
230
|
+
* settings global can't be read. Blocking a manual translate on a DB
|
|
231
|
+
* blip would be more disruptive than letting the (already-tracked)
|
|
232
|
+
* field through. The locale-resolution function above flips the other
|
|
233
|
+
* direction because it's gating LLM SPEND, not which fields go in.
|
|
234
|
+
*/ export async function getExcludedFieldPaths(payload, config, surfaceSlug) {
|
|
235
|
+
const read = await readSettings(payload, config);
|
|
236
|
+
if (read.failed || !read.settings) return new Set();
|
|
237
|
+
const rows = read.settings.perCollection;
|
|
238
|
+
if (!Array.isArray(rows)) return new Set();
|
|
239
|
+
const row = rows.find((r)=>r?.slug === surfaceSlug);
|
|
240
|
+
const paths = row?.excludedFieldPaths;
|
|
241
|
+
if (!Array.isArray(paths)) return new Set();
|
|
242
|
+
return new Set(paths.filter((p)=>typeof p === 'string'));
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Surface-aware variant of `getEffectiveExcludePatterns`.
|
|
246
|
+
*
|
|
247
|
+
* Reads the `perCollection[surfaceSlug].translateSlug` flag from the
|
|
248
|
+
* translation-settings global. When `true`, drops `'slug'` from the
|
|
249
|
+
* effective patterns so the resolver picks it up. When `false` (the
|
|
250
|
+
* default), behaviour is identical to `getEffectiveExcludePatterns`.
|
|
251
|
+
*
|
|
252
|
+
* Use this in every translation path that operates on a known
|
|
253
|
+
* surface — `translateDocument`, `translateGlobal`, the estimate
|
|
254
|
+
* endpoint, the cost-guard pre-flight, and the admin UI's
|
|
255
|
+
* client-config map. The static `getEffectiveExcludePatterns` is
|
|
256
|
+
* still appropriate for config-build-time paths (per-field button
|
|
257
|
+
* injection) that have no async context AND no per-row knowledge.
|
|
258
|
+
*
|
|
259
|
+
* **Fail-safe-CLOSED on read failure**: returns the base patterns
|
|
260
|
+
* unchanged. This is the conservative direction — if we can't read
|
|
261
|
+
* the toggle, we keep slug excluded (no per-locale URL churn from a
|
|
262
|
+
* DB blip). Mirrors `getExcludedFieldPaths`' policy of "blocking
|
|
263
|
+
* manual translate on a settings-read blip is more disruptive than
|
|
264
|
+
* letting the field through" — except in this case the opposite
|
|
265
|
+
* applies because the default IS exclusion.
|
|
266
|
+
*/ export async function getEffectiveExcludePatternsForSurface(payload, config, surfaceSlug) {
|
|
267
|
+
const base = getEffectiveExcludePatterns(config);
|
|
268
|
+
const read = await readSettings(payload, config);
|
|
269
|
+
if (read.failed || !read.settings?.perCollection) return base;
|
|
270
|
+
const row = read.settings.perCollection.find((r)=>r?.slug === surfaceSlug);
|
|
271
|
+
if (row?.translateSlug !== true) return base;
|
|
272
|
+
// Drop only the exact `'slug'` pattern. Don't touch suffix/glob
|
|
273
|
+
// patterns like `'**.slug'` if a consumer ever uses them — those
|
|
274
|
+
// are a deliberate broader exclusion the editor isn't opting out of.
|
|
275
|
+
return base.filter((p)=>p !== 'slug');
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Prefix-aware match: `path` is excluded if it appears in `excluded`
|
|
279
|
+
* OR any ancestor segment of it does. Excluding `layout` therefore
|
|
280
|
+
* excludes `layout.columns`, `layout.columns.richText`, etc.
|
|
281
|
+
*
|
|
282
|
+
* Why prefix and not exact: editors think in top-level fields. The
|
|
283
|
+
* admin UI offers ONE checkbox per top-level field — unchecking
|
|
284
|
+
* "Layout" should skip everything under it. Exact-match would leave
|
|
285
|
+
* `layout.columns.richText` translating even when the editor opted
|
|
286
|
+
* the entire Layout out. Per-leaf exclusions are still expressible
|
|
287
|
+
* via the more specific path (e.g. `meta.description`).
|
|
288
|
+
*
|
|
289
|
+
* Implementation note: O(D × |excluded|) per call where D is the
|
|
290
|
+
* path's segment depth (small constant in practice). Acceptable for
|
|
291
|
+
* the per-fire filter; would warrant a trie if exclusion lists grew
|
|
292
|
+
* to hundreds.
|
|
293
|
+
*/ export function isPathExcluded(path, excluded) {
|
|
294
|
+
if (excluded.size === 0) return false;
|
|
295
|
+
if (excluded.has(path)) return true;
|
|
296
|
+
const segments = path.split('.');
|
|
297
|
+
for(let i = segments.length - 1; i > 0; i--){
|
|
298
|
+
const prefix = segments.slice(0, i).join('.');
|
|
299
|
+
if (excluded.has(prefix)) return true;
|
|
300
|
+
}
|
|
301
|
+
return false;
|
|
302
|
+
}
|