@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,153 @@
|
|
|
1
|
+
import { DEFAULT_BULK_TRANSLATE_BATCHES_COLLECTION_SLUG } from '../bulk-translate-batches-collection.js';
|
|
2
|
+
import { DEFAULT_BULK_TRANSLATE_UNITS_COLLECTION_SLUG } from '../bulk-translate-units-collection.js';
|
|
3
|
+
import { DEFAULT_TRANSLATION_DAILY_SPEND_COLLECTION_SLUG } from '../translation-daily-spend-collection.js';
|
|
4
|
+
export async function ensureBulkTranslateSchema(payload, opts = {}) {
|
|
5
|
+
const unitsSlug = opts.bulkTranslateUnitsSlug ?? DEFAULT_BULK_TRANSLATE_UNITS_COLLECTION_SLUG;
|
|
6
|
+
const dailySpendSlug = opts.translationDailySpendSlug ?? DEFAULT_TRANSLATION_DAILY_SPEND_COLLECTION_SLUG;
|
|
7
|
+
// Convert slug to table name. Payload's Postgres adapter snake-cases
|
|
8
|
+
// collection slugs into table names: `bulk-translate-units` →
|
|
9
|
+
// `bulk_translate_units`. If a consumer customizes the slug, the
|
|
10
|
+
// same transform applies.
|
|
11
|
+
const unitsTable = slugToTable(unitsSlug);
|
|
12
|
+
const dailySpendTable = slugToTable(dailySpendSlug);
|
|
13
|
+
const db = getDrizzle(payload);
|
|
14
|
+
if (!db) {
|
|
15
|
+
payload.logger?.warn?.('[ai-translate] ensureBulkTranslateSchema: payload.db.drizzle unavailable. Skipping. ' + 'Hand-write the indexes in your consumer migration if your adapter is not Postgres.');
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
// Partial unique index on bulk-translate-units. The CREATE INDEX
|
|
19
|
+
// IF NOT EXISTS keyword is Postgres-specific; this helper is
|
|
20
|
+
// explicitly Postgres-only (per the plugin's existing adapter
|
|
21
|
+
// assumption in `utilities/helpers.ts:atomicUpdateLastCounter`).
|
|
22
|
+
const unitsIndexSql = `
|
|
23
|
+
CREATE UNIQUE INDEX IF NOT EXISTS ${unitsTable}_active_dedup
|
|
24
|
+
ON ${unitsTable} (collection, document_id, locale)
|
|
25
|
+
WHERE status IN ('pending','running');
|
|
26
|
+
`;
|
|
27
|
+
const dailySpendUniqueSql = `
|
|
28
|
+
CREATE UNIQUE INDEX IF NOT EXISTS ${dailySpendTable}_date_consumer_uniq
|
|
29
|
+
ON ${dailySpendTable} (date, consumer_key);
|
|
30
|
+
`;
|
|
31
|
+
await runIdempotent(payload, db, unitsIndexSql, `${unitsTable}_active_dedup`);
|
|
32
|
+
await runIdempotent(payload, db, dailySpendUniqueSql, `${dailySpendTable}_date_consumer_uniq`);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* One-off data migration: prefix existing `ai-translate-meta`
|
|
36
|
+
* source-hash rows with the `v1:` version tag (R2 / F-ENG-07).
|
|
37
|
+
*
|
|
38
|
+
* Without this, the first bulk run after a plugin upgrade would treat
|
|
39
|
+
* every previously-translated field as "source changed" — burning
|
|
40
|
+
* tokens to re-translate everything that was already in sync.
|
|
41
|
+
*
|
|
42
|
+
* Idempotent: rows that already start with `v1:` are skipped. Safe
|
|
43
|
+
* to run repeatedly. Consumer should run ONCE per deploy that bumps
|
|
44
|
+
* past 1.1.16. Returns a count of rows updated.
|
|
45
|
+
*/ export async function migrateHashesToV1Prefix(payload, opts = {}) {
|
|
46
|
+
const metaSlug = opts.metaSlug ?? 'ai-translate-meta';
|
|
47
|
+
const metaTable = slugToTable(metaSlug);
|
|
48
|
+
const db = getDrizzle(payload);
|
|
49
|
+
if (!db) {
|
|
50
|
+
throw new Error('[ai-translate] migrateHashesToV1Prefix: payload.db.drizzle unavailable. Non-Postgres adapters must roll their own migration.');
|
|
51
|
+
}
|
|
52
|
+
const scanResult = await db.execute(`SELECT count(*) AS count FROM ${metaTable} WHERE last_source_hash IS NOT NULL AND last_source_hash NOT LIKE 'v1:%'`);
|
|
53
|
+
const needsMigration = Number(scanResult.rows[0]?.count ?? 0);
|
|
54
|
+
const alreadyResult = await db.execute(`SELECT count(*) AS count FROM ${metaTable} WHERE last_source_hash LIKE 'v1:%'`);
|
|
55
|
+
const alreadyMigrated = Number(alreadyResult.rows[0]?.count ?? 0);
|
|
56
|
+
if (opts.dryRun) {
|
|
57
|
+
return {
|
|
58
|
+
scanned: needsMigration,
|
|
59
|
+
updated: 0,
|
|
60
|
+
alreadyMigrated
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
if (needsMigration === 0) {
|
|
64
|
+
return {
|
|
65
|
+
scanned: 0,
|
|
66
|
+
updated: 0,
|
|
67
|
+
alreadyMigrated
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
await db.execute(`UPDATE ${metaTable} SET last_source_hash = 'v1:' || last_source_hash WHERE last_source_hash IS NOT NULL AND last_source_hash NOT LIKE 'v1:%'`);
|
|
71
|
+
payload.logger?.info?.(`[ai-translate] migrateHashesToV1Prefix: prefixed ${needsMigration} rows; ${alreadyMigrated} already v1.`);
|
|
72
|
+
return {
|
|
73
|
+
scanned: needsMigration,
|
|
74
|
+
updated: needsMigration,
|
|
75
|
+
alreadyMigrated
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* v1.2.4 data migration: align `bulk_translate_batches.status` vocabulary
|
|
80
|
+
* on `'success'` (matching the units collection + the
|
|
81
|
+
* `BulkTranslateBatchStatus` TS union). Pre-1.2.4 the schema declared
|
|
82
|
+
* `'completed'` as the terminal-success value while the type union used
|
|
83
|
+
* `'success'`, so badges, filter chips, and the Hub's "recently
|
|
84
|
+
* terminal" query were all silently dead for successful runs. Worker
|
|
85
|
+
* + schema now write `'success'`; this helper backfills existing rows.
|
|
86
|
+
*
|
|
87
|
+
* Idempotent: only updates rows where status = 'completed'. Safe to run
|
|
88
|
+
* repeatedly; subsequent calls are no-ops.
|
|
89
|
+
*
|
|
90
|
+
* Consumer wiring:
|
|
91
|
+
* - Add to a forward migration's up() OR
|
|
92
|
+
* - Call from plugin onInit() once per deploy (boot-time self-heal).
|
|
93
|
+
*/ export async function migrateBatchStatusVocabulary(payload, opts = {}) {
|
|
94
|
+
const batchesSlug = opts.batchesSlug ?? DEFAULT_BULK_TRANSLATE_BATCHES_COLLECTION_SLUG;
|
|
95
|
+
const batchesTable = slugToTable(batchesSlug);
|
|
96
|
+
const db = getDrizzle(payload);
|
|
97
|
+
if (!db) {
|
|
98
|
+
throw new Error('[ai-translate] migrateBatchStatusVocabulary: payload.db.drizzle unavailable. Non-Postgres adapters must roll their own migration.');
|
|
99
|
+
}
|
|
100
|
+
const scanResult = await db.execute(`SELECT count(*) AS count FROM ${batchesTable} WHERE status = 'completed'`);
|
|
101
|
+
const needs = Number(scanResult.rows[0]?.count ?? 0);
|
|
102
|
+
const alreadyResult = await db.execute(`SELECT count(*) AS count FROM ${batchesTable} WHERE status = 'success'`);
|
|
103
|
+
const already = Number(alreadyResult.rows[0]?.count ?? 0);
|
|
104
|
+
if (opts.dryRun) {
|
|
105
|
+
return {
|
|
106
|
+
scanned: needs,
|
|
107
|
+
updated: 0,
|
|
108
|
+
alreadyAligned: already
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
if (needs === 0) {
|
|
112
|
+
return {
|
|
113
|
+
scanned: 0,
|
|
114
|
+
updated: 0,
|
|
115
|
+
alreadyAligned: already
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
await db.execute(`UPDATE ${batchesTable} SET status = 'success', updated_at = now() WHERE status = 'completed'`);
|
|
119
|
+
payload.logger?.info?.(`[ai-translate] migrateBatchStatusVocabulary: updated ${needs} rows from 'completed' to 'success'; ${already} already aligned.`);
|
|
120
|
+
return {
|
|
121
|
+
scanned: needs,
|
|
122
|
+
updated: needs,
|
|
123
|
+
alreadyAligned: already
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
export function slugToTable(slug) {
|
|
127
|
+
// Match Payload's snake_case table-naming. Slugs are usually
|
|
128
|
+
// already kebab-case; replace dashes with underscores. Validate
|
|
129
|
+
// identifier shape to avoid SQL injection via consumer-provided
|
|
130
|
+
// slug overrides (AUDIT-H-06).
|
|
131
|
+
if (!/^[a-z][a-z0-9_-]{0,62}$/.test(slug)) {
|
|
132
|
+
throw new Error(`[ai-translate] slugToTable: invalid collection slug "${slug}" (must match /^[a-z][a-z0-9_-]{0,62}$/)`);
|
|
133
|
+
}
|
|
134
|
+
return slug.replace(/-/g, '_');
|
|
135
|
+
}
|
|
136
|
+
export function getDrizzle(payload) {
|
|
137
|
+
const db = payload.db?.drizzle;
|
|
138
|
+
if (!db || typeof db.execute !== 'function') return null;
|
|
139
|
+
return db;
|
|
140
|
+
}
|
|
141
|
+
async function runIdempotent(payload, db, sql, label) {
|
|
142
|
+
try {
|
|
143
|
+
await db.execute(sql);
|
|
144
|
+
payload.logger?.info?.(`[ai-translate] schema: ensured ${label}.`);
|
|
145
|
+
} catch (err) {
|
|
146
|
+
// The IF NOT EXISTS guard should prevent most "already exists"
|
|
147
|
+
// errors, but if the table itself is missing (plugin booting
|
|
148
|
+
// before consumer ran their initial migrations) this can throw.
|
|
149
|
+
// Log as warn — boot proceeds, the engine will fail at first
|
|
150
|
+
// bulk run with a clear error.
|
|
151
|
+
payload.logger?.warn?.(`[ai-translate] schema: failed to ensure ${label}: ${err instanceof Error ? err.message : String(err)}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Payload, PayloadRequest } from 'payload';
|
|
2
|
+
export type CoalescingEntry = {
|
|
3
|
+
payload: Payload;
|
|
4
|
+
/**
|
|
5
|
+
* Discriminator so the queue worker dispatches to `translateDocument`
|
|
6
|
+
* for collections and `translateGlobal` for globals. Defaults to
|
|
7
|
+
* `'collection'` for backwards compatibility with consumers that
|
|
8
|
+
* haven't yet started passing globals through this queue.
|
|
9
|
+
*/
|
|
10
|
+
kind?: 'collection' | 'global';
|
|
11
|
+
/** Collection slug or global slug, depending on `kind`. */
|
|
12
|
+
collection: string;
|
|
13
|
+
id: string | number;
|
|
14
|
+
doc: Record<string, unknown>;
|
|
15
|
+
previousDoc: Record<string, unknown>;
|
|
16
|
+
/**
|
|
17
|
+
* Optional jobId so the queue worker reuses an already-created progress job
|
|
18
|
+
* (the after-change hook seeds one for instant UI feedback).
|
|
19
|
+
*/
|
|
20
|
+
jobId?: string;
|
|
21
|
+
/** Originating request — threaded into locale writes for audit attribution. */
|
|
22
|
+
req?: PayloadRequest;
|
|
23
|
+
/**
|
|
24
|
+
* Effective locale list for this dispatch — overrides config.targetLocales
|
|
25
|
+
* downstream. The after-change hook computes this from the
|
|
26
|
+
* translation-settings global so admin opt-outs are honored. Omitted =
|
|
27
|
+
* fall back to config.targetLocales in the translate API.
|
|
28
|
+
*/
|
|
29
|
+
targetLocales?: string[];
|
|
30
|
+
};
|
|
31
|
+
export type CoalescingCallback = (entry: CoalescingEntry) => Promise<void>;
|
|
32
|
+
export type CoalescingQueue = {
|
|
33
|
+
enqueue(entry: CoalescingEntry): void;
|
|
34
|
+
cancel(collection: string, id: string | number): void;
|
|
35
|
+
pending(): number;
|
|
36
|
+
flush(): Promise<void>;
|
|
37
|
+
};
|
|
38
|
+
export declare function createCoalescingQueue(windowMs: number, callback: CoalescingCallback): CoalescingQueue;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Implementation
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
export function createCoalescingQueue(windowMs, callback) {
|
|
5
|
+
const entries = new Map();
|
|
6
|
+
function makeKey(collection, id) {
|
|
7
|
+
// Globals key on `<slug>:<slug>` — same pattern collections use, just
|
|
8
|
+
// with id == slug since globals are singletons.
|
|
9
|
+
return `${collection}:${String(id)}`;
|
|
10
|
+
}
|
|
11
|
+
async function fire(key) {
|
|
12
|
+
const pending = entries.get(key);
|
|
13
|
+
if (!pending) return;
|
|
14
|
+
entries.delete(key);
|
|
15
|
+
try {
|
|
16
|
+
await callback(pending.entry);
|
|
17
|
+
} catch (error) {
|
|
18
|
+
const msg = error instanceof Error ? error.message : 'Unknown error';
|
|
19
|
+
console.error(`[ai-translate] Coalescing callback failed for ${key}: ${msg}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
enqueue (entry) {
|
|
24
|
+
const key = makeKey(entry.collection, entry.id);
|
|
25
|
+
// Cancel existing pending entry for this key
|
|
26
|
+
const existing = entries.get(key);
|
|
27
|
+
if (existing) {
|
|
28
|
+
clearTimeout(existing.timer);
|
|
29
|
+
// Keep the original previousDoc so field-diff captures all changes
|
|
30
|
+
// from the first save, not just the latest delta
|
|
31
|
+
entry = {
|
|
32
|
+
...entry,
|
|
33
|
+
previousDoc: existing.entry.previousDoc
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
// Set new timer with updated entry
|
|
37
|
+
const timer = setTimeout(()=>{
|
|
38
|
+
void fire(key);
|
|
39
|
+
}, windowMs);
|
|
40
|
+
entries.set(key, {
|
|
41
|
+
entry,
|
|
42
|
+
timer
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
cancel (collection, id) {
|
|
46
|
+
const key = makeKey(collection, id);
|
|
47
|
+
const existing = entries.get(key);
|
|
48
|
+
if (existing) {
|
|
49
|
+
clearTimeout(existing.timer);
|
|
50
|
+
entries.delete(key);
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
pending () {
|
|
54
|
+
return entries.size;
|
|
55
|
+
},
|
|
56
|
+
async flush () {
|
|
57
|
+
const keys = [
|
|
58
|
+
...entries.keys()
|
|
59
|
+
];
|
|
60
|
+
for (const key of keys){
|
|
61
|
+
const existing = entries.get(key);
|
|
62
|
+
if (existing) {
|
|
63
|
+
clearTimeout(existing.timer);
|
|
64
|
+
}
|
|
65
|
+
await fire(key);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Block, Field } from 'payload';
|
|
2
|
+
import type { TranslatableField, TranslationUnit } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Build a `blockType → Block` map from a Payload collection / global's
|
|
5
|
+
* raw `fields` array. Walks `type: 'blocks'` fields anywhere in the tree
|
|
6
|
+
* (including under tabs, groups, arrays) and gathers their registered
|
|
7
|
+
* `blocks: Block[]` configurations.
|
|
8
|
+
*
|
|
9
|
+
* The result lets the extractor gate which block-internal keys are
|
|
10
|
+
* translatable based on the SCHEMA rather than the heuristic
|
|
11
|
+
* "every-string-is-text" walk that produces false positives for select
|
|
12
|
+
* values, color codes, etc. (the BUG-04 family).
|
|
13
|
+
*/
|
|
14
|
+
export declare function collectBlocksConfig(fields: Field[]): Map<string, Block>;
|
|
15
|
+
export declare function extractTranslationUnits(doc: Record<string, unknown>, fields: TranslatableField[], excludePatterns: string[], blocksConfig?: Map<string, Block> | null): TranslationUnit[];
|
|
16
|
+
export declare function getValueAtPath(obj: Record<string, unknown>, path: string): unknown;
|