@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,549 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { toast, useConfig, useFormModified } from '@payloadcms/ui';
|
|
4
|
+
import { formatAdminURL } from 'payload/shared';
|
|
5
|
+
import React, { useCallback, useEffect, useState } from 'react';
|
|
6
|
+
import { createPortal } from 'react-dom';
|
|
7
|
+
import { formatCost } from '../views/shared/format.js';
|
|
8
|
+
// Cost rendering uses the shared `formatCost` helper from
|
|
9
|
+
// `views/shared/format.ts` (NEW-17 — single source of truth across
|
|
10
|
+
// every cost render site in the plugin).
|
|
11
|
+
export function TranslateModal({ docId, collectionSlug, globalSlug, onClose, onJobStarted }) {
|
|
12
|
+
const { config } = useConfig();
|
|
13
|
+
// The modal does NOT auto-save the form. A previous implementation
|
|
14
|
+
// called `useForm().submit()` here so unsaved edits would flush
|
|
15
|
+
// before the estimate/translate read the DB — but that save fires
|
|
16
|
+
// the collection's afterChange hooks, including this plugin's own
|
|
17
|
+
// auto-translate hook. The result: clicking "Continue" silently
|
|
18
|
+
// kicked off a real translation in the background while the user
|
|
19
|
+
// was still looking at the cost estimate. Cancel on the cost modal
|
|
20
|
+
// closed the dialog but never aborted the in-flight job, so the
|
|
21
|
+
// explicit "Translate" button (when clicked) actually fired a
|
|
22
|
+
// SECOND translation on top of the first. The estimate + translate
|
|
23
|
+
// endpoints both read DB state directly; if the form has unsaved
|
|
24
|
+
// edits the user is warned in the estimate step so they can save
|
|
25
|
+
// first and reopen the modal.
|
|
26
|
+
const formModified = useFormModified();
|
|
27
|
+
const locales = config.localization ? config.localization.locales : [];
|
|
28
|
+
const defaultLocale = config.localization ? config.localization.defaultLocale : 'en';
|
|
29
|
+
// Restrict the picker to the plugin's configured `targetLocales` (not
|
|
30
|
+
// every locale registered in Payload's `config.localization`). Without
|
|
31
|
+
// this filter, the drawer surfaces locales the plugin can't translate
|
|
32
|
+
// to anyway. Payload's admin bundle strips function refs from
|
|
33
|
+
// `config.custom` so we can't read `targetLocales` synchronously from
|
|
34
|
+
// `useConfig()` — fetch them from the plugin's `/ai-translate/
|
|
35
|
+
// client-config` endpoint on mount instead. Falls back to "every
|
|
36
|
+
// non-source locale" until the fetch resolves (no flash of wrong UI;
|
|
37
|
+
// the modal starts disabled below).
|
|
38
|
+
const [pluginTargetLocales, setPluginTargetLocales] = useState(null);
|
|
39
|
+
const [clientConfigLoaded, setClientConfigLoaded] = useState(false);
|
|
40
|
+
const apiRouteForConfig = config.routes?.api ?? '/api';
|
|
41
|
+
// Use `formatAdminURL` so the consumer's Next.js basePath (e.g. '/blog'
|
|
42
|
+
// for blog-wild) is prepended. Raw `${apiRoute}/...` resolves against
|
|
43
|
+
// `window.location.origin + apiRoute` and 404s on every consumer
|
|
44
|
+
// that uses a basePath. Mirrors the pattern in `excluded-fields-field.tsx`.
|
|
45
|
+
const clientConfigUrl = formatAdminURL({
|
|
46
|
+
apiRoute: apiRouteForConfig,
|
|
47
|
+
path: '/ai-translate/client-config',
|
|
48
|
+
serverURL: ''
|
|
49
|
+
});
|
|
50
|
+
useEffect(()=>{
|
|
51
|
+
let cancelled = false;
|
|
52
|
+
void fetch(clientConfigUrl, {
|
|
53
|
+
credentials: 'include'
|
|
54
|
+
}).then((res)=>res.ok ? res.json() : null).then((data)=>{
|
|
55
|
+
if (cancelled) return;
|
|
56
|
+
if (data && Array.isArray(data.targetLocales)) {
|
|
57
|
+
setPluginTargetLocales(data.targetLocales);
|
|
58
|
+
}
|
|
59
|
+
setClientConfigLoaded(true);
|
|
60
|
+
}).catch(()=>{
|
|
61
|
+
if (cancelled) return;
|
|
62
|
+
// Network / 404 → graceful fallback to "every non-source locale"
|
|
63
|
+
setClientConfigLoaded(true);
|
|
64
|
+
});
|
|
65
|
+
return ()=>{
|
|
66
|
+
cancelled = true;
|
|
67
|
+
};
|
|
68
|
+
}, [
|
|
69
|
+
clientConfigUrl
|
|
70
|
+
]);
|
|
71
|
+
const targetLocales = pluginTargetLocales && pluginTargetLocales.length > 0 ? locales.filter((l)=>pluginTargetLocales.includes(l.code)) : locales.filter((l)=>l.code !== defaultLocale);
|
|
72
|
+
const [selected, setSelected] = useState(new Set());
|
|
73
|
+
// Once the fetch settles, default-select every plugin-configured locale.
|
|
74
|
+
// Done in an effect so the selection updates after `targetLocales`
|
|
75
|
+
// resolves; without this, the modal renders with an empty selection.
|
|
76
|
+
useEffect(()=>{
|
|
77
|
+
if (clientConfigLoaded) {
|
|
78
|
+
setSelected(new Set(targetLocales.map((l)=>l.code)));
|
|
79
|
+
}
|
|
80
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
81
|
+
}, [
|
|
82
|
+
clientConfigLoaded,
|
|
83
|
+
pluginTargetLocales
|
|
84
|
+
]);
|
|
85
|
+
const [step, setStep] = useState('pick-locales');
|
|
86
|
+
const [estimate, setEstimate] = useState(null);
|
|
87
|
+
const [resultMessage, setResultMessage] = useState('');
|
|
88
|
+
const isGlobal = !!globalSlug;
|
|
89
|
+
const pathPrefix = isGlobal ? `/globals/${globalSlug}` : `/${collectionSlug}`;
|
|
90
|
+
const apiRoute = config.routes?.api ?? '/api';
|
|
91
|
+
const toggleLocale = (code)=>{
|
|
92
|
+
setSelected((prev)=>{
|
|
93
|
+
const next = new Set(prev);
|
|
94
|
+
if (next.has(code)) next.delete(code);
|
|
95
|
+
else next.add(code);
|
|
96
|
+
return next;
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
// Step 1: locale picker → fetch estimate (does NOT save the form;
|
|
100
|
+
// see the rationale on `formModified` above).
|
|
101
|
+
const handleConfirmLocales = useCallback(async ()=>{
|
|
102
|
+
if (selected.size === 0) return;
|
|
103
|
+
setStep('estimating');
|
|
104
|
+
try {
|
|
105
|
+
const body = isGlobal ? {
|
|
106
|
+
targetLocales: [
|
|
107
|
+
...selected
|
|
108
|
+
]
|
|
109
|
+
} : {
|
|
110
|
+
ids: [
|
|
111
|
+
docId
|
|
112
|
+
],
|
|
113
|
+
targetLocales: [
|
|
114
|
+
...selected
|
|
115
|
+
]
|
|
116
|
+
};
|
|
117
|
+
const estimatePath = `${pathPrefix}/ai-translate/estimate`;
|
|
118
|
+
const res = await fetch(formatAdminURL({
|
|
119
|
+
apiRoute,
|
|
120
|
+
path: estimatePath
|
|
121
|
+
}), {
|
|
122
|
+
method: 'POST',
|
|
123
|
+
credentials: 'include',
|
|
124
|
+
headers: {
|
|
125
|
+
'Content-Type': 'application/json'
|
|
126
|
+
},
|
|
127
|
+
body: JSON.stringify(body)
|
|
128
|
+
});
|
|
129
|
+
if (!res.ok) {
|
|
130
|
+
// Estimate failed — fall through, allow user to translate without it
|
|
131
|
+
toast.error('Could not fetch cost estimate; you can still proceed.');
|
|
132
|
+
setEstimate(null);
|
|
133
|
+
setStep('estimate-shown');
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const data = await res.json();
|
|
137
|
+
setEstimate(data);
|
|
138
|
+
setStep('estimate-shown');
|
|
139
|
+
} catch {
|
|
140
|
+
toast.error('Could not fetch cost estimate; you can still proceed.');
|
|
141
|
+
setEstimate(null);
|
|
142
|
+
setStep('estimate-shown');
|
|
143
|
+
}
|
|
144
|
+
}, [
|
|
145
|
+
selected,
|
|
146
|
+
docId,
|
|
147
|
+
pathPrefix,
|
|
148
|
+
apiRoute,
|
|
149
|
+
isGlobal
|
|
150
|
+
]);
|
|
151
|
+
// Step 2: estimate-shown → fire translation
|
|
152
|
+
const handleTranslate = useCallback(async ()=>{
|
|
153
|
+
setStep('translating');
|
|
154
|
+
try {
|
|
155
|
+
const body = {
|
|
156
|
+
targetLocales: [
|
|
157
|
+
...selected
|
|
158
|
+
],
|
|
159
|
+
// Run in the background. The server returns the jobId immediately
|
|
160
|
+
// and the sidebar progress widget tracks completion via SSE.
|
|
161
|
+
async: true
|
|
162
|
+
};
|
|
163
|
+
if (!isGlobal) body.id = docId;
|
|
164
|
+
const translatePath = `${pathPrefix}/ai-translate`;
|
|
165
|
+
const res = await fetch(formatAdminURL({
|
|
166
|
+
apiRoute,
|
|
167
|
+
path: translatePath
|
|
168
|
+
}), {
|
|
169
|
+
method: 'POST',
|
|
170
|
+
credentials: 'include',
|
|
171
|
+
headers: {
|
|
172
|
+
'Content-Type': 'application/json'
|
|
173
|
+
},
|
|
174
|
+
body: JSON.stringify(body)
|
|
175
|
+
});
|
|
176
|
+
if (!res.ok) {
|
|
177
|
+
const errorData = await res.json().catch(()=>({}));
|
|
178
|
+
const friendlyFallback = res.status >= 500 ? 'The translation service returned an error. Try again, or contact engineering if it keeps happening.' : 'The translation request couldn’t be completed. Try again, or refresh the page.';
|
|
179
|
+
throw new Error(errorData.error ?? friendlyFallback);
|
|
180
|
+
}
|
|
181
|
+
const data = await res.json();
|
|
182
|
+
const result = data.results[0];
|
|
183
|
+
if (result?.jobId) {
|
|
184
|
+
// Async mode — the server will run the translation in the background.
|
|
185
|
+
// Hand the jobId off to the sidebar progress widget and close. Keeping
|
|
186
|
+
// the modal open here would trap the user behind a duplicate progress
|
|
187
|
+
// UI for the same job.
|
|
188
|
+
onJobStarted?.(result.jobId);
|
|
189
|
+
toast.success('Translation started — track progress in the sidebar.');
|
|
190
|
+
onClose();
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const successCount = result?.succeeded?.length ?? 0;
|
|
194
|
+
const failCount = result?.failed?.length ?? 0;
|
|
195
|
+
setStep('done');
|
|
196
|
+
if (failCount === 0) {
|
|
197
|
+
setResultMessage(`Translated ${successCount} field(s) to ${selected.size} locale(s)`);
|
|
198
|
+
toast.success(`Translation complete: ${successCount} field(s) translated`);
|
|
199
|
+
} else {
|
|
200
|
+
setResultMessage(`${successCount} succeeded, ${failCount} failed`);
|
|
201
|
+
toast.error(`Translation finished, but ${failCount} field${failCount === 1 ? '' : 's'} couldn’t be translated. Open the document to review.`);
|
|
202
|
+
}
|
|
203
|
+
} catch (error) {
|
|
204
|
+
setStep('error');
|
|
205
|
+
const msg = error instanceof Error ? error.message : 'Translation failed';
|
|
206
|
+
setResultMessage(msg);
|
|
207
|
+
toast.error(msg);
|
|
208
|
+
}
|
|
209
|
+
}, [
|
|
210
|
+
selected,
|
|
211
|
+
docId,
|
|
212
|
+
pathPrefix,
|
|
213
|
+
apiRoute,
|
|
214
|
+
isGlobal
|
|
215
|
+
]);
|
|
216
|
+
// Escape key closes modal (except during active translation)
|
|
217
|
+
useEffect(()=>{
|
|
218
|
+
const handleKeyDown = (e)=>{
|
|
219
|
+
if (e.key === 'Escape' && step !== 'translating' && step !== 'estimating') {
|
|
220
|
+
onClose();
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
224
|
+
return ()=>document.removeEventListener('keydown', handleKeyDown);
|
|
225
|
+
}, [
|
|
226
|
+
onClose,
|
|
227
|
+
step
|
|
228
|
+
]);
|
|
229
|
+
// Portal to document.body so the modal escapes any sticky/fixed/transform
|
|
230
|
+
// ancestor that would otherwise trap it in a lower stacking context (e.g.
|
|
231
|
+
// the per-field "Translate..." button lives inside the document sidebar,
|
|
232
|
+
// whose `position: sticky` parent ranks below the rich-text fixed toolbar
|
|
233
|
+
// — without a portal the modal renders behind the toolbar).
|
|
234
|
+
if (typeof document === 'undefined') return null;
|
|
235
|
+
return /*#__PURE__*/ createPortal(/*#__PURE__*/ _jsx("div", {
|
|
236
|
+
role: "dialog",
|
|
237
|
+
"aria-modal": "true",
|
|
238
|
+
"aria-labelledby": "ai-translate-modal-title",
|
|
239
|
+
onClick: (e)=>{
|
|
240
|
+
if (e.target === e.currentTarget && step !== 'translating' && step !== 'estimating') {
|
|
241
|
+
onClose();
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
style: {
|
|
245
|
+
position: 'fixed',
|
|
246
|
+
top: 0,
|
|
247
|
+
left: 0,
|
|
248
|
+
right: 0,
|
|
249
|
+
bottom: 0,
|
|
250
|
+
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
251
|
+
display: 'flex',
|
|
252
|
+
alignItems: 'center',
|
|
253
|
+
justifyContent: 'center',
|
|
254
|
+
zIndex: 10000
|
|
255
|
+
},
|
|
256
|
+
children: /*#__PURE__*/ _jsxs("div", {
|
|
257
|
+
style: {
|
|
258
|
+
backgroundColor: 'var(--theme-elevation-0)',
|
|
259
|
+
borderRadius: '4px',
|
|
260
|
+
padding: '24px',
|
|
261
|
+
minWidth: '360px',
|
|
262
|
+
maxWidth: '480px',
|
|
263
|
+
maxHeight: '80vh',
|
|
264
|
+
overflow: 'auto',
|
|
265
|
+
color: 'var(--theme-text)'
|
|
266
|
+
},
|
|
267
|
+
children: [
|
|
268
|
+
/*#__PURE__*/ _jsx("h3", {
|
|
269
|
+
id: "ai-translate-modal-title",
|
|
270
|
+
style: {
|
|
271
|
+
margin: '0 0 16px',
|
|
272
|
+
fontSize: '18px'
|
|
273
|
+
},
|
|
274
|
+
children: "Translate Document"
|
|
275
|
+
}),
|
|
276
|
+
step === 'pick-locales' && /*#__PURE__*/ _jsxs(_Fragment, {
|
|
277
|
+
children: [
|
|
278
|
+
/*#__PURE__*/ _jsx("p", {
|
|
279
|
+
style: {
|
|
280
|
+
margin: '0 0 12px',
|
|
281
|
+
fontSize: '14px',
|
|
282
|
+
color: 'var(--theme-elevation-500)'
|
|
283
|
+
},
|
|
284
|
+
children: "Select target locales:"
|
|
285
|
+
}),
|
|
286
|
+
/*#__PURE__*/ _jsx("div", {
|
|
287
|
+
style: {
|
|
288
|
+
display: 'flex',
|
|
289
|
+
flexDirection: 'column',
|
|
290
|
+
gap: '8px',
|
|
291
|
+
marginBottom: '16px'
|
|
292
|
+
},
|
|
293
|
+
children: targetLocales.map((locale)=>/*#__PURE__*/ _jsxs("label", {
|
|
294
|
+
style: {
|
|
295
|
+
display: 'flex',
|
|
296
|
+
alignItems: 'center',
|
|
297
|
+
gap: '8px',
|
|
298
|
+
cursor: 'pointer'
|
|
299
|
+
},
|
|
300
|
+
children: [
|
|
301
|
+
/*#__PURE__*/ _jsx("input", {
|
|
302
|
+
type: "checkbox",
|
|
303
|
+
checked: selected.has(locale.code),
|
|
304
|
+
onChange: ()=>toggleLocale(locale.code)
|
|
305
|
+
}),
|
|
306
|
+
/*#__PURE__*/ _jsx("span", {
|
|
307
|
+
children: locale.label ?? locale.code
|
|
308
|
+
})
|
|
309
|
+
]
|
|
310
|
+
}, locale.code))
|
|
311
|
+
}),
|
|
312
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
313
|
+
style: {
|
|
314
|
+
display: 'flex',
|
|
315
|
+
gap: '8px',
|
|
316
|
+
justifyContent: 'flex-end'
|
|
317
|
+
},
|
|
318
|
+
children: [
|
|
319
|
+
/*#__PURE__*/ _jsx("button", {
|
|
320
|
+
type: "button",
|
|
321
|
+
onClick: onClose,
|
|
322
|
+
style: {
|
|
323
|
+
padding: '8px 16px',
|
|
324
|
+
backgroundColor: 'transparent',
|
|
325
|
+
border: '1px solid var(--theme-elevation-300)',
|
|
326
|
+
borderRadius: '4px',
|
|
327
|
+
color: 'var(--theme-text)',
|
|
328
|
+
cursor: 'pointer'
|
|
329
|
+
},
|
|
330
|
+
children: "Cancel"
|
|
331
|
+
}),
|
|
332
|
+
/*#__PURE__*/ _jsx("button", {
|
|
333
|
+
type: "button",
|
|
334
|
+
onClick: handleConfirmLocales,
|
|
335
|
+
disabled: selected.size === 0,
|
|
336
|
+
style: {
|
|
337
|
+
padding: '8px 16px',
|
|
338
|
+
backgroundColor: 'var(--theme-success-500)',
|
|
339
|
+
border: 'none',
|
|
340
|
+
borderRadius: '4px',
|
|
341
|
+
color: 'white',
|
|
342
|
+
cursor: selected.size === 0 ? 'not-allowed' : 'pointer',
|
|
343
|
+
opacity: selected.size === 0 ? 0.5 : 1
|
|
344
|
+
},
|
|
345
|
+
children: "Continue"
|
|
346
|
+
})
|
|
347
|
+
]
|
|
348
|
+
})
|
|
349
|
+
]
|
|
350
|
+
}),
|
|
351
|
+
step === 'estimating' && /*#__PURE__*/ _jsx("div", {
|
|
352
|
+
style: {
|
|
353
|
+
textAlign: 'center',
|
|
354
|
+
padding: '16px 0'
|
|
355
|
+
},
|
|
356
|
+
children: /*#__PURE__*/ _jsx("p", {
|
|
357
|
+
children: "Calculating cost estimate…"
|
|
358
|
+
})
|
|
359
|
+
}),
|
|
360
|
+
step === 'estimate-shown' && /*#__PURE__*/ _jsxs(_Fragment, {
|
|
361
|
+
children: [
|
|
362
|
+
formModified && /*#__PURE__*/ _jsx("div", {
|
|
363
|
+
role: "alert",
|
|
364
|
+
style: {
|
|
365
|
+
margin: '0 0 12px',
|
|
366
|
+
padding: '10px 12px',
|
|
367
|
+
borderRadius: '4px',
|
|
368
|
+
border: '1px solid var(--theme-warning-500)',
|
|
369
|
+
backgroundColor: 'var(--theme-warning-50, rgba(234, 179, 8, 0.08))',
|
|
370
|
+
color: 'var(--theme-warning-800, #78350f)',
|
|
371
|
+
fontSize: '13px',
|
|
372
|
+
lineHeight: 1.4
|
|
373
|
+
},
|
|
374
|
+
children: "You have unsaved edits on this document. The translation will use the last saved version — save first if you want recent changes included, then reopen this dialog."
|
|
375
|
+
}),
|
|
376
|
+
/*#__PURE__*/ _jsx("p", {
|
|
377
|
+
style: {
|
|
378
|
+
margin: '0 0 12px',
|
|
379
|
+
fontSize: '14px',
|
|
380
|
+
color: 'var(--theme-elevation-500)'
|
|
381
|
+
},
|
|
382
|
+
children: "Cost estimate"
|
|
383
|
+
}),
|
|
384
|
+
estimate ? /*#__PURE__*/ _jsxs("dl", {
|
|
385
|
+
style: {
|
|
386
|
+
margin: '0 0 16px',
|
|
387
|
+
fontSize: '14px',
|
|
388
|
+
display: 'grid',
|
|
389
|
+
gridTemplateColumns: 'auto 1fr',
|
|
390
|
+
gap: '4px 12px'
|
|
391
|
+
},
|
|
392
|
+
children: [
|
|
393
|
+
/*#__PURE__*/ _jsx("dt", {
|
|
394
|
+
children: "Characters:"
|
|
395
|
+
}),
|
|
396
|
+
/*#__PURE__*/ _jsxs("dd", {
|
|
397
|
+
style: {
|
|
398
|
+
margin: 0
|
|
399
|
+
},
|
|
400
|
+
children: [
|
|
401
|
+
estimate.totalCharacters.toLocaleString(),
|
|
402
|
+
estimate.likelySkippedCharacters && estimate.likelySkippedCharacters > 0 ? /*#__PURE__*/ _jsxs("span", {
|
|
403
|
+
style: {
|
|
404
|
+
color: 'var(--theme-elevation-500)',
|
|
405
|
+
fontSize: '12px'
|
|
406
|
+
},
|
|
407
|
+
children: [
|
|
408
|
+
' ',
|
|
409
|
+
"(",
|
|
410
|
+
estimate.billableCharacters.toLocaleString(),
|
|
411
|
+
" billable",
|
|
412
|
+
' · ',
|
|
413
|
+
estimate.likelySkippedCharacters.toLocaleString(),
|
|
414
|
+
" likely skipped)"
|
|
415
|
+
]
|
|
416
|
+
}) : null
|
|
417
|
+
]
|
|
418
|
+
}),
|
|
419
|
+
/*#__PURE__*/ _jsx("dt", {
|
|
420
|
+
children: "Tokens (est.):"
|
|
421
|
+
}),
|
|
422
|
+
/*#__PURE__*/ _jsx("dd", {
|
|
423
|
+
style: {
|
|
424
|
+
margin: 0
|
|
425
|
+
},
|
|
426
|
+
children: estimate.estimatedTokens.toLocaleString()
|
|
427
|
+
}),
|
|
428
|
+
/*#__PURE__*/ _jsx("dt", {
|
|
429
|
+
children: "Locales:"
|
|
430
|
+
}),
|
|
431
|
+
/*#__PURE__*/ _jsx("dd", {
|
|
432
|
+
style: {
|
|
433
|
+
margin: 0
|
|
434
|
+
},
|
|
435
|
+
children: estimate.localeCount
|
|
436
|
+
}),
|
|
437
|
+
/*#__PURE__*/ _jsx("dt", {
|
|
438
|
+
children: "Cost (est.):"
|
|
439
|
+
}),
|
|
440
|
+
/*#__PURE__*/ _jsx("dd", {
|
|
441
|
+
style: {
|
|
442
|
+
margin: 0,
|
|
443
|
+
fontWeight: 500
|
|
444
|
+
},
|
|
445
|
+
children: formatCost(estimate.estimatedCostUsd)
|
|
446
|
+
})
|
|
447
|
+
]
|
|
448
|
+
}) : /*#__PURE__*/ _jsx("p", {
|
|
449
|
+
style: {
|
|
450
|
+
margin: '0 0 16px',
|
|
451
|
+
fontSize: '13px',
|
|
452
|
+
color: 'var(--theme-warning-500)'
|
|
453
|
+
},
|
|
454
|
+
children: "Cost estimate unavailable."
|
|
455
|
+
}),
|
|
456
|
+
estimate && estimate.totalCharacters === 0 && /*#__PURE__*/ _jsx("p", {
|
|
457
|
+
style: {
|
|
458
|
+
margin: '0 0 16px',
|
|
459
|
+
fontSize: '13px',
|
|
460
|
+
color: 'var(--theme-warning-500)'
|
|
461
|
+
},
|
|
462
|
+
children: "No translatable content found in the source locale. Add content in the source locale before translating."
|
|
463
|
+
}),
|
|
464
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
465
|
+
style: {
|
|
466
|
+
display: 'flex',
|
|
467
|
+
gap: '8px',
|
|
468
|
+
justifyContent: 'flex-end'
|
|
469
|
+
},
|
|
470
|
+
children: [
|
|
471
|
+
/*#__PURE__*/ _jsx("button", {
|
|
472
|
+
type: "button",
|
|
473
|
+
onClick: onClose,
|
|
474
|
+
style: {
|
|
475
|
+
padding: '8px 16px',
|
|
476
|
+
backgroundColor: 'transparent',
|
|
477
|
+
border: '1px solid var(--theme-elevation-300)',
|
|
478
|
+
borderRadius: '4px',
|
|
479
|
+
color: 'var(--theme-text)',
|
|
480
|
+
cursor: 'pointer'
|
|
481
|
+
},
|
|
482
|
+
children: "Cancel"
|
|
483
|
+
}),
|
|
484
|
+
/*#__PURE__*/ _jsxs("button", {
|
|
485
|
+
type: "button",
|
|
486
|
+
onClick: handleTranslate,
|
|
487
|
+
disabled: estimate?.totalCharacters === 0,
|
|
488
|
+
style: {
|
|
489
|
+
padding: '8px 16px',
|
|
490
|
+
backgroundColor: 'var(--theme-success-500)',
|
|
491
|
+
border: 'none',
|
|
492
|
+
borderRadius: '4px',
|
|
493
|
+
color: 'white',
|
|
494
|
+
cursor: estimate?.totalCharacters === 0 ? 'not-allowed' : 'pointer',
|
|
495
|
+
opacity: estimate?.totalCharacters === 0 ? 0.5 : 1
|
|
496
|
+
},
|
|
497
|
+
children: [
|
|
498
|
+
"Translate to ",
|
|
499
|
+
selected.size,
|
|
500
|
+
" locale",
|
|
501
|
+
selected.size !== 1 ? 's' : ''
|
|
502
|
+
]
|
|
503
|
+
})
|
|
504
|
+
]
|
|
505
|
+
})
|
|
506
|
+
]
|
|
507
|
+
}),
|
|
508
|
+
step === 'translating' && /*#__PURE__*/ _jsx("div", {
|
|
509
|
+
style: {
|
|
510
|
+
textAlign: 'center',
|
|
511
|
+
padding: '16px 0'
|
|
512
|
+
},
|
|
513
|
+
children: /*#__PURE__*/ _jsx("p", {
|
|
514
|
+
children: "Starting translation…"
|
|
515
|
+
})
|
|
516
|
+
}),
|
|
517
|
+
(step === 'done' || step === 'error') && /*#__PURE__*/ _jsxs("div", {
|
|
518
|
+
children: [
|
|
519
|
+
/*#__PURE__*/ _jsx("p", {
|
|
520
|
+
style: {
|
|
521
|
+
margin: '0 0 16px'
|
|
522
|
+
},
|
|
523
|
+
children: resultMessage
|
|
524
|
+
}),
|
|
525
|
+
/*#__PURE__*/ _jsx("div", {
|
|
526
|
+
style: {
|
|
527
|
+
display: 'flex',
|
|
528
|
+
justifyContent: 'flex-end'
|
|
529
|
+
},
|
|
530
|
+
children: /*#__PURE__*/ _jsx("button", {
|
|
531
|
+
type: "button",
|
|
532
|
+
onClick: onClose,
|
|
533
|
+
style: {
|
|
534
|
+
padding: '8px 16px',
|
|
535
|
+
backgroundColor: 'var(--theme-elevation-100)',
|
|
536
|
+
border: 'none',
|
|
537
|
+
borderRadius: '4px',
|
|
538
|
+
color: 'var(--theme-text)',
|
|
539
|
+
cursor: 'pointer'
|
|
540
|
+
},
|
|
541
|
+
children: "Close"
|
|
542
|
+
})
|
|
543
|
+
})
|
|
544
|
+
]
|
|
545
|
+
})
|
|
546
|
+
]
|
|
547
|
+
})
|
|
548
|
+
}), document.body);
|
|
549
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
type TranslationProgressProps = {
|
|
3
|
+
jobId?: string;
|
|
4
|
+
collectionSlug?: string;
|
|
5
|
+
globalSlug?: string;
|
|
6
|
+
docId?: string | number;
|
|
7
|
+
onComplete?: () => void;
|
|
8
|
+
};
|
|
9
|
+
export declare function TranslationProgress({ jobId, collectionSlug, globalSlug, docId, onComplete, }: TranslationProgressProps): React.JSX.Element | null;
|
|
10
|
+
export {};
|