@form-engine-ts/react 6.1.0 → 6.3.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/README.md +8 -4
- package/dist/index.cjs +237 -48
- package/dist/index.d.cts +180 -114
- package/dist/index.d.ts +180 -114
- package/dist/index.js +250 -61
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -89,13 +89,17 @@ underscore-separated values such as `EN_us` are accepted as compatibility input.
|
|
|
89
89
|
Use `isAddLocaleAllowed` to disable locale controls before submission. Removing a locale also clears its
|
|
90
90
|
localized values and metadata; the default locale remains protected.
|
|
91
91
|
|
|
92
|
-
`useTranslationWorkspace`
|
|
93
|
-
`
|
|
94
|
-
|
|
92
|
+
`useTranslationWorkspace` accepts either a synchronous or asynchronous translation adapter, forwards an optional
|
|
93
|
+
`AbortSignal`, and exposes `cancelTranslation`, progress, and typed partial-failure/cancellation errors. It can notify
|
|
94
|
+
`onTranslationStart`, `onTranslationSuccess`, `onTranslationReport`, `onTranslationError`, and `onTranslationChange`
|
|
95
|
+
with typed lifecycle payloads. The MUI workspace accepts a `confirmRemoveLocale` slot for an async confirmation UI;
|
|
96
|
+
its `onConfirm` callback completes the locale removal.
|
|
95
97
|
|
|
96
98
|
`useTranslationComparison` provides a focused, controlled comparison model for form, page, field, and option text.
|
|
97
99
|
Its items expose source text, target text, status, canonical metadata, and path-based `updateTranslation`,
|
|
98
|
-
`translateSingle`, and `translateAll` actions.
|
|
100
|
+
`translateSingle`, and `translateAll` actions. Comparison row slots receive `nodeKind`, locale labels, read-only state,
|
|
101
|
+
field type, question and option indexes, metadata, plus async single-translation actions. Use
|
|
102
|
+
`createTranslationMetadata` to add provider/model/hash fields while the canonical metadata fields are retained.
|
|
99
103
|
|
|
100
104
|
Wrap a builder or renderer in `FormEngineI18nProvider` to supply a UI locale and typed Core translator independently
|
|
101
105
|
from the schema's `defaultLocale` and `supportedLocales`:
|
package/dist/index.cjs
CHANGED
|
@@ -3325,7 +3325,7 @@ function asAsyncAdapter(adapter) {
|
|
|
3325
3325
|
)
|
|
3326
3326
|
};
|
|
3327
3327
|
}
|
|
3328
|
-
var validateLocalePipeline = (locale, schema, policy, customValidator, availableLocales) => {
|
|
3328
|
+
var validateLocalePipeline = (locale, schema, policy, customValidator, availableLocales, sourceLocale) => {
|
|
3329
3329
|
const canonicalLocale = (0, import_core5.normalizeLocale)(locale);
|
|
3330
3330
|
if (canonicalLocale === null) {
|
|
3331
3331
|
return {
|
|
@@ -3337,20 +3337,17 @@ var validateLocalePipeline = (locale, schema, policy, customValidator, available
|
|
|
3337
3337
|
};
|
|
3338
3338
|
}
|
|
3339
3339
|
const currentLocales = (schema.supportedLocales ?? []).map((candidate) => (0, import_core5.normalizeLocale)(candidate) ?? candidate);
|
|
3340
|
-
const defaultLocale =
|
|
3341
|
-
if (
|
|
3342
|
-
const value = typeof candidate === "string" ? candidate : candidate.locale;
|
|
3343
|
-
return (0, import_core5.normalizeLocale)(value) === canonicalLocale;
|
|
3344
|
-
})) {
|
|
3340
|
+
const defaultLocale = (0, import_core5.normalizeLocale)(sourceLocale ?? schema.defaultLocale ?? "") ?? sourceLocale ?? schema.defaultLocale ?? "";
|
|
3341
|
+
if (canonicalLocale === defaultLocale) {
|
|
3345
3342
|
return {
|
|
3346
3343
|
valid: false,
|
|
3347
3344
|
error: {
|
|
3348
|
-
type: "
|
|
3349
|
-
message: `Locale "${canonicalLocale}" is
|
|
3345
|
+
type: "source_locale",
|
|
3346
|
+
message: `Locale "${canonicalLocale}" is the source locale and cannot be added as a translation.`
|
|
3350
3347
|
}
|
|
3351
3348
|
};
|
|
3352
3349
|
}
|
|
3353
|
-
if (
|
|
3350
|
+
if (currentLocales.includes(canonicalLocale)) {
|
|
3354
3351
|
return {
|
|
3355
3352
|
valid: false,
|
|
3356
3353
|
error: {
|
|
@@ -3359,6 +3356,18 @@ var validateLocalePipeline = (locale, schema, policy, customValidator, available
|
|
|
3359
3356
|
}
|
|
3360
3357
|
};
|
|
3361
3358
|
}
|
|
3359
|
+
if (availableLocales !== void 0 && !availableLocales.some((candidate) => {
|
|
3360
|
+
const value = typeof candidate === "string" ? candidate : candidate.locale;
|
|
3361
|
+
return (0, import_core5.normalizeLocale)(value) === canonicalLocale;
|
|
3362
|
+
})) {
|
|
3363
|
+
return {
|
|
3364
|
+
valid: false,
|
|
3365
|
+
error: {
|
|
3366
|
+
type: "locale_not_allowed",
|
|
3367
|
+
message: `Locale "${canonicalLocale}" is not available in the locale catalog.`
|
|
3368
|
+
}
|
|
3369
|
+
};
|
|
3370
|
+
}
|
|
3362
3371
|
if (policy?.allowedLocales !== void 0 && !policy.allowedLocales.some((candidate) => (0, import_core5.normalizeLocale)(candidate) === canonicalLocale)) {
|
|
3363
3372
|
return {
|
|
3364
3373
|
valid: false,
|
|
@@ -3412,6 +3421,7 @@ function workspaceLocaleValidationError(validation, currentLocales, requestedLoc
|
|
|
3412
3421
|
if (error.type === "locale_already_exists") {
|
|
3413
3422
|
return { type: "locale_already_exists", locale: requestedLocale };
|
|
3414
3423
|
}
|
|
3424
|
+
if (error.type === "source_locale") return { type: "source_locale", locale: requestedLocale };
|
|
3415
3425
|
if (error.type === "invalid_locale_format") {
|
|
3416
3426
|
return { type: "invalid_locale_format", locale: requestedLocale };
|
|
3417
3427
|
}
|
|
@@ -3429,19 +3439,29 @@ function translationMetadata(sourceText, sourceLocale, mode) {
|
|
|
3429
3439
|
...mode === "manual" ? { editedAt: (/* @__PURE__ */ new Date()).toISOString() } : { translatedAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
3430
3440
|
};
|
|
3431
3441
|
}
|
|
3432
|
-
function
|
|
3442
|
+
function createTranslationMetadata(slot, sourceLocale, translatedText, mode, factory) {
|
|
3443
|
+
return {
|
|
3444
|
+
...factory?.({ slot, translatedText, mode }) ?? {},
|
|
3445
|
+
...translationMetadata(slot.sourceText, sourceLocale, mode)
|
|
3446
|
+
};
|
|
3447
|
+
}
|
|
3448
|
+
function setNodeMetadata(node, slot, text, sourceLocale, mode, metadataOverride) {
|
|
3433
3449
|
const localeMetadata = node.translationMetadata?.[slot.locale];
|
|
3434
|
-
const nextMetadata = text.trim().length === 0 ? Object.fromEntries(Object.entries(localeMetadata ?? {}).filter(([key]) => key !== slot.property)) : {
|
|
3450
|
+
const nextMetadata = text.trim().length === 0 ? Object.fromEntries(Object.entries(localeMetadata ?? {}).filter(([key]) => key !== slot.property)) : {
|
|
3451
|
+
...localeMetadata,
|
|
3452
|
+
[slot.property]: metadataOverride ?? translationMetadata(slot.sourceText, sourceLocale, mode)
|
|
3453
|
+
};
|
|
3435
3454
|
return { ...node, translationMetadata: { ...node.translationMetadata, [slot.locale]: nextMetadata } };
|
|
3436
3455
|
}
|
|
3437
|
-
function updateSchemaTranslation(schema, slot, text, sourceLocale, mode = "manual") {
|
|
3456
|
+
function updateSchemaTranslation(schema, slot, text, sourceLocale, mode = "manual", metadataOverride) {
|
|
3438
3457
|
if (slot.kind === "form") {
|
|
3439
3458
|
return setNodeMetadata(
|
|
3440
3459
|
{ ...schema, translations: updateTranslationMap(schema.translations, slot.locale, slot.property, text) },
|
|
3441
3460
|
slot,
|
|
3442
3461
|
text,
|
|
3443
3462
|
sourceLocale,
|
|
3444
|
-
mode
|
|
3463
|
+
mode,
|
|
3464
|
+
metadataOverride
|
|
3445
3465
|
);
|
|
3446
3466
|
}
|
|
3447
3467
|
if (slot.kind === "field") {
|
|
@@ -3450,7 +3470,14 @@ function updateSchemaTranslation(schema, slot, text, sourceLocale, mode = "manua
|
|
|
3450
3470
|
fields: schema.fields.map((field) => {
|
|
3451
3471
|
if (field.id !== slot.nodeId) return field;
|
|
3452
3472
|
const translations = updateTranslationMap(field.translations, slot.locale, slot.property, text);
|
|
3453
|
-
return setNodeMetadata(
|
|
3473
|
+
return setNodeMetadata(
|
|
3474
|
+
{ ...field, translations },
|
|
3475
|
+
slot,
|
|
3476
|
+
text,
|
|
3477
|
+
sourceLocale,
|
|
3478
|
+
mode,
|
|
3479
|
+
metadataOverride
|
|
3480
|
+
);
|
|
3454
3481
|
})
|
|
3455
3482
|
};
|
|
3456
3483
|
}
|
|
@@ -3466,7 +3493,7 @@ function updateSchemaTranslation(schema, slot, text, sourceLocale, mode = "manua
|
|
|
3466
3493
|
const translations = text.trim().length === 0 ? Object.fromEntries(
|
|
3467
3494
|
Object.entries(option.translations ?? {}).filter(([locale]) => locale !== slot.locale)
|
|
3468
3495
|
) : { ...option.translations, [slot.locale]: text };
|
|
3469
|
-
return setNodeMetadata({ ...option, translations }, slot, text, sourceLocale, mode);
|
|
3496
|
+
return setNodeMetadata({ ...option, translations }, slot, text, sourceLocale, mode, metadataOverride);
|
|
3470
3497
|
})
|
|
3471
3498
|
};
|
|
3472
3499
|
})
|
|
@@ -3478,7 +3505,7 @@ function updateSchemaTranslation(schema, slot, text, sourceLocale, mode = "manua
|
|
|
3478
3505
|
pages: schema.pages.map((page) => {
|
|
3479
3506
|
if (page.id !== slot.nodeId) return page;
|
|
3480
3507
|
const translations = updateTranslationMap(page.translations, slot.locale, slot.property, text);
|
|
3481
|
-
return setNodeMetadata({ ...page, translations }, slot, text, sourceLocale, mode);
|
|
3508
|
+
return setNodeMetadata({ ...page, translations }, slot, text, sourceLocale, mode, metadataOverride);
|
|
3482
3509
|
})
|
|
3483
3510
|
}
|
|
3484
3511
|
};
|
|
@@ -3489,6 +3516,7 @@ function useTranslationWorkspace({
|
|
|
3489
3516
|
sourceLocale = schema.defaultLocale ?? "en",
|
|
3490
3517
|
targetLocale,
|
|
3491
3518
|
translationAdapter,
|
|
3519
|
+
signal,
|
|
3492
3520
|
readOnly = false,
|
|
3493
3521
|
policy,
|
|
3494
3522
|
availableLocales,
|
|
@@ -3500,34 +3528,49 @@ function useTranslationWorkspace({
|
|
|
3500
3528
|
slots: workspaceSlots,
|
|
3501
3529
|
onTranslationStart,
|
|
3502
3530
|
onTranslationSuccess,
|
|
3531
|
+
onTranslationReport,
|
|
3503
3532
|
onTranslationError,
|
|
3504
3533
|
onTranslationChange,
|
|
3505
|
-
validateLocale
|
|
3534
|
+
validateLocale,
|
|
3535
|
+
createTranslationMetadata: metadataFactory
|
|
3506
3536
|
}) {
|
|
3507
3537
|
const [draftSchema, setDraftSchema] = (0, import_react5.useState)(schema);
|
|
3508
3538
|
const [selectedLocale, setSelectedLocale] = (0, import_react5.useState)((0, import_core5.normalizeLocale)(targetLocale ?? "") ?? targetLocale ?? "");
|
|
3509
3539
|
const [isTranslating, setIsTranslating] = (0, import_react5.useState)(false);
|
|
3540
|
+
const [progress, setProgress] = (0, import_react5.useState)();
|
|
3510
3541
|
const [error, setError] = (0, import_react5.useState)();
|
|
3511
3542
|
const [pendingRemoval, setPendingRemoval] = (0, import_react5.useState)();
|
|
3543
|
+
const activeAbortController = (0, import_react5.useRef)(void 0);
|
|
3512
3544
|
const pendingRemovalResolver = (0, import_react5.useRef)(void 0);
|
|
3513
3545
|
const currentSchema = onChange === void 0 ? draftSchema : schema;
|
|
3514
3546
|
const confirmRemoveLocaleRenderer = confirmRemoveLocale ?? workspaceSlots?.confirmRemoveLocale;
|
|
3515
3547
|
const targetLocales = (0, import_react5.useMemo)(() => {
|
|
3516
3548
|
const locales = (0, import_core5.collectSchemaLocales)(currentSchema).allUniqueLocales;
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3549
|
+
const normalizedSourceLocale = (0, import_core5.normalizeLocale)(sourceLocale) ?? sourceLocale;
|
|
3550
|
+
return [
|
|
3551
|
+
...new Set(
|
|
3552
|
+
[...currentSchema.supportedLocales ?? [], ...locales].map((locale) => (0, import_core5.normalizeLocale)(locale) ?? locale)
|
|
3553
|
+
)
|
|
3554
|
+
].filter((locale) => locale !== normalizedSourceLocale);
|
|
3520
3555
|
}, [currentSchema, sourceLocale]);
|
|
3521
3556
|
const activeLocale = selectedLocale.length > 0 ? selectedLocale : targetLocales[0] ?? "";
|
|
3522
3557
|
const localeOptions = (0, import_react5.useMemo)(() => {
|
|
3523
3558
|
if (availableLocales === void 0) {
|
|
3524
3559
|
return targetLocales.map((locale) => ({ locale, label: locale }));
|
|
3525
3560
|
}
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3561
|
+
const targetLocaleSet = new Set(targetLocales);
|
|
3562
|
+
const allowedLocales = policy?.allowedLocales?.map((locale) => (0, import_core5.normalizeLocale)(locale) ?? locale);
|
|
3563
|
+
const options = /* @__PURE__ */ new Map();
|
|
3564
|
+
for (const candidate of availableLocales) {
|
|
3565
|
+
const option = typeof candidate === "string" ? { locale: (0, import_core5.normalizeLocale)(candidate) ?? candidate, label: candidate } : { ...candidate, locale: (0, import_core5.normalizeLocale)(candidate.locale) ?? candidate.locale };
|
|
3566
|
+
const allowed = allowedLocales === void 0 || allowedLocales.includes(option.locale);
|
|
3567
|
+
if (allowed || targetLocaleSet.has(option.locale)) options.set(option.locale, option);
|
|
3568
|
+
}
|
|
3569
|
+
for (const locale of targetLocales) {
|
|
3570
|
+
if (!options.has(locale)) options.set(locale, { locale, label: locale });
|
|
3571
|
+
}
|
|
3572
|
+
return [...options.values()];
|
|
3573
|
+
}, [availableLocales, policy?.allowedLocales, targetLocales]);
|
|
3531
3574
|
const slots = (0, import_react5.useMemo)(
|
|
3532
3575
|
() => activeLocale.length === 0 ? [] : (0, import_core5.collectTranslationSlots)(currentSchema, activeLocale),
|
|
3533
3576
|
[activeLocale, currentSchema]
|
|
@@ -3561,8 +3604,8 @@ function useTranslationWorkspace({
|
|
|
3561
3604
|
const setTranslation = (0, import_react5.useCallback)(
|
|
3562
3605
|
(slot, text) => {
|
|
3563
3606
|
if (readOnly) return;
|
|
3564
|
-
const metadata =
|
|
3565
|
-
commit(updateSchemaTranslation(currentSchema, slot, text, sourceLocale, "manual"));
|
|
3607
|
+
const metadata = createTranslationMetadata(slot, sourceLocale, text, "manual", metadataFactory);
|
|
3608
|
+
commit(updateSchemaTranslation(currentSchema, slot, text, sourceLocale, "manual", metadata));
|
|
3566
3609
|
onTranslationChange?.({
|
|
3567
3610
|
slot,
|
|
3568
3611
|
...slot.existingText === void 0 ? {} : { previousText: slot.existingText },
|
|
@@ -3571,7 +3614,7 @@ function useTranslationWorkspace({
|
|
|
3571
3614
|
metadata
|
|
3572
3615
|
});
|
|
3573
3616
|
},
|
|
3574
|
-
[commit, currentSchema, onTranslationChange, readOnly, sourceLocale]
|
|
3617
|
+
[commit, currentSchema, metadataFactory, onTranslationChange, readOnly, sourceLocale]
|
|
3575
3618
|
);
|
|
3576
3619
|
const addLocale = (0, import_react5.useCallback)(
|
|
3577
3620
|
(locale) => {
|
|
@@ -3583,14 +3626,21 @@ function useTranslationWorkspace({
|
|
|
3583
3626
|
const normalized = (0, import_core5.normalizeLocale)(locale);
|
|
3584
3627
|
if (normalized === null) {
|
|
3585
3628
|
const workspaceError = workspaceLocaleValidationError(
|
|
3586
|
-
validateLocalePipeline(locale, currentSchema, policy, validateLocale, availableLocales),
|
|
3629
|
+
validateLocalePipeline(locale, currentSchema, policy, validateLocale, availableLocales, sourceLocale),
|
|
3587
3630
|
(0, import_core5.collectSchemaLocales)(currentSchema).allUniqueLocales.size,
|
|
3588
3631
|
locale
|
|
3589
3632
|
);
|
|
3590
3633
|
setError(workspaceError);
|
|
3591
3634
|
return { success: false, error: workspaceError };
|
|
3592
3635
|
}
|
|
3593
|
-
const validation = validateLocalePipeline(
|
|
3636
|
+
const validation = validateLocalePipeline(
|
|
3637
|
+
normalized,
|
|
3638
|
+
currentSchema,
|
|
3639
|
+
policy,
|
|
3640
|
+
validateLocale,
|
|
3641
|
+
availableLocales,
|
|
3642
|
+
sourceLocale
|
|
3643
|
+
);
|
|
3594
3644
|
if (!validation.valid) {
|
|
3595
3645
|
const workspaceError = workspaceLocaleValidationError(
|
|
3596
3646
|
validation,
|
|
@@ -3609,16 +3659,16 @@ function useTranslationWorkspace({
|
|
|
3609
3659
|
onLocaleAdded?.(normalized);
|
|
3610
3660
|
return { success: true };
|
|
3611
3661
|
},
|
|
3612
|
-
[availableLocales, commit, currentSchema, onLocaleAdded, policy, readOnly, validateLocale]
|
|
3662
|
+
[availableLocales, commit, currentSchema, onLocaleAdded, policy, readOnly, sourceLocale, validateLocale]
|
|
3613
3663
|
);
|
|
3614
3664
|
const isAddLocaleAllowed = (0, import_react5.useCallback)(
|
|
3615
3665
|
(locale) => {
|
|
3616
3666
|
if (readOnly) return false;
|
|
3617
3667
|
const normalized = (0, import_core5.normalizeLocale)(locale);
|
|
3618
3668
|
if (normalized === null) return false;
|
|
3619
|
-
return validateLocalePipeline(normalized, currentSchema, policy, validateLocale, availableLocales).valid;
|
|
3669
|
+
return validateLocalePipeline(normalized, currentSchema, policy, validateLocale, availableLocales, sourceLocale).valid;
|
|
3620
3670
|
},
|
|
3621
|
-
[availableLocales, currentSchema, policy, readOnly, validateLocale]
|
|
3671
|
+
[availableLocales, currentSchema, policy, readOnly, sourceLocale, validateLocale]
|
|
3622
3672
|
);
|
|
3623
3673
|
const removeLocale = (0, import_react5.useCallback)(
|
|
3624
3674
|
(locale) => {
|
|
@@ -3748,7 +3798,15 @@ function useTranslationWorkspace({
|
|
|
3748
3798
|
}
|
|
3749
3799
|
setIsTranslating(true);
|
|
3750
3800
|
setError(void 0);
|
|
3801
|
+
setProgress(void 0);
|
|
3751
3802
|
onTranslationStart?.({ targetLocale: activeLocale, mode: "automatic" });
|
|
3803
|
+
const controller = new AbortController();
|
|
3804
|
+
activeAbortController.current = controller;
|
|
3805
|
+
const operationSignal = options.signal ?? signal;
|
|
3806
|
+
const abortListener = operationSignal === void 0 ? void 0 : () => controller.abort(operationSignal.reason);
|
|
3807
|
+
if (operationSignal?.aborted) controller.abort(operationSignal.reason);
|
|
3808
|
+
else if (operationSignal !== void 0 && abortListener !== void 0)
|
|
3809
|
+
operationSignal.addEventListener("abort", abortListener, { once: true });
|
|
3752
3810
|
try {
|
|
3753
3811
|
const populated = await (0, import_core5.populateSchemaTranslations)(
|
|
3754
3812
|
currentSchema,
|
|
@@ -3757,13 +3815,37 @@ function useTranslationWorkspace({
|
|
|
3757
3815
|
{
|
|
3758
3816
|
overwrite: "stale-and-missing",
|
|
3759
3817
|
preserveManualTranslations: true,
|
|
3760
|
-
...options
|
|
3818
|
+
...options,
|
|
3819
|
+
continueOnError: true,
|
|
3820
|
+
signal: controller.signal,
|
|
3821
|
+
createMetadata: (slot, translatedText) => options.createMetadata?.(slot, translatedText) ?? createTranslationMetadata(slot, sourceLocale, translatedText, "automatic", metadataFactory),
|
|
3822
|
+
onProgress: (nextProgress) => {
|
|
3823
|
+
setProgress(nextProgress);
|
|
3824
|
+
options.onProgress?.(nextProgress);
|
|
3825
|
+
}
|
|
3761
3826
|
}
|
|
3762
3827
|
);
|
|
3763
3828
|
commit(populated.schema);
|
|
3829
|
+
onTranslationReport?.(populated.report);
|
|
3764
3830
|
const missingSlotsCount = (0, import_core5.collectTranslationSlots)(populated.schema, activeLocale).filter(
|
|
3765
3831
|
(slot) => slot.status === "missing"
|
|
3766
3832
|
).length;
|
|
3833
|
+
if (populated.report.cancelled === true) {
|
|
3834
|
+
const workspaceError = { type: "cancelled" };
|
|
3835
|
+
setError(workspaceError);
|
|
3836
|
+
onTranslationError?.({ targetLocale: activeLocale, error: workspaceError });
|
|
3837
|
+
return { success: false, report: populated.report, error: workspaceError };
|
|
3838
|
+
}
|
|
3839
|
+
if ((populated.report.failed ?? 0) > 0) {
|
|
3840
|
+
const workspaceError = {
|
|
3841
|
+
type: "partial_failure",
|
|
3842
|
+
succeeded: populated.report.succeeded ?? populated.report.updatedSlots.length,
|
|
3843
|
+
failed: populated.report.failed ?? 0
|
|
3844
|
+
};
|
|
3845
|
+
setError(workspaceError);
|
|
3846
|
+
onTranslationError?.({ targetLocale: activeLocale, error: workspaceError });
|
|
3847
|
+
return { success: false, report: populated.report, error: workspaceError };
|
|
3848
|
+
}
|
|
3767
3849
|
onTranslationSuccess?.({
|
|
3768
3850
|
sourceLocale,
|
|
3769
3851
|
targetLocale: activeLocale,
|
|
@@ -3774,6 +3856,12 @@ function useTranslationWorkspace({
|
|
|
3774
3856
|
});
|
|
3775
3857
|
return { success: true, report: populated.report };
|
|
3776
3858
|
} catch (cause) {
|
|
3859
|
+
if (controller.signal.aborted || cause instanceof DOMException && cause.name === "AbortError") {
|
|
3860
|
+
const workspaceError2 = { type: "cancelled" };
|
|
3861
|
+
setError(workspaceError2);
|
|
3862
|
+
onTranslationError?.({ targetLocale: activeLocale, error: workspaceError2 });
|
|
3863
|
+
return { success: false, error: workspaceError2 };
|
|
3864
|
+
}
|
|
3777
3865
|
const workspaceError = {
|
|
3778
3866
|
type: "translation_failed",
|
|
3779
3867
|
message: cause instanceof Error ? cause.message : String(cause),
|
|
@@ -3783,6 +3871,11 @@ function useTranslationWorkspace({
|
|
|
3783
3871
|
onTranslationError?.({ targetLocale: activeLocale, error: workspaceError });
|
|
3784
3872
|
return { success: false, error: workspaceError };
|
|
3785
3873
|
} finally {
|
|
3874
|
+
if (operationSignal !== void 0 && abortListener !== void 0) {
|
|
3875
|
+
const listener = abortListener;
|
|
3876
|
+
operationSignal.removeEventListener("abort", listener);
|
|
3877
|
+
}
|
|
3878
|
+
if (activeAbortController.current === controller) activeAbortController.current = void 0;
|
|
3786
3879
|
setIsTranslating(false);
|
|
3787
3880
|
}
|
|
3788
3881
|
},
|
|
@@ -3794,14 +3887,20 @@ function useTranslationWorkspace({
|
|
|
3794
3887
|
onTranslationError,
|
|
3795
3888
|
onTranslationStart,
|
|
3796
3889
|
onTranslationSuccess,
|
|
3890
|
+
onTranslationReport,
|
|
3797
3891
|
readOnly,
|
|
3798
3892
|
slots,
|
|
3799
3893
|
sourceLocale,
|
|
3800
|
-
|
|
3894
|
+
metadataFactory,
|
|
3895
|
+
translationAdapter,
|
|
3896
|
+
signal
|
|
3801
3897
|
]
|
|
3802
3898
|
);
|
|
3899
|
+
const cancelTranslation = (0, import_react5.useCallback)(() => {
|
|
3900
|
+
activeAbortController.current?.abort(new DOMException("The translation was cancelled.", "AbortError"));
|
|
3901
|
+
}, []);
|
|
3803
3902
|
const translateSlot = (0, import_react5.useCallback)(
|
|
3804
|
-
async (slot) => {
|
|
3903
|
+
async (slot, options = {}) => {
|
|
3805
3904
|
if (translationAdapter === void 0) {
|
|
3806
3905
|
const workspaceError = { type: "adapter_not_configured" };
|
|
3807
3906
|
setError(workspaceError);
|
|
@@ -3814,10 +3913,23 @@ function useTranslationWorkspace({
|
|
|
3814
3913
|
}
|
|
3815
3914
|
setIsTranslating(true);
|
|
3816
3915
|
setError(void 0);
|
|
3916
|
+
setProgress({ total: 1, completed: 0, succeeded: 0, failed: 0, percentage: 0 });
|
|
3917
|
+
const controller = new AbortController();
|
|
3918
|
+
activeAbortController.current = controller;
|
|
3919
|
+
const operationSignal = options.signal ?? signal;
|
|
3920
|
+
const abortListener = operationSignal === void 0 ? void 0 : () => controller.abort(operationSignal.reason);
|
|
3921
|
+
if (operationSignal?.aborted) controller.abort(operationSignal.reason);
|
|
3922
|
+
else if (operationSignal !== void 0 && abortListener !== void 0)
|
|
3923
|
+
operationSignal.addEventListener("abort", abortListener, { once: true });
|
|
3817
3924
|
try {
|
|
3818
|
-
const text = await asAsyncAdapter(translationAdapter).translateText(
|
|
3819
|
-
|
|
3820
|
-
|
|
3925
|
+
const text = await asAsyncAdapter(translationAdapter).translateText(
|
|
3926
|
+
slot.sourceText,
|
|
3927
|
+
slot.locale,
|
|
3928
|
+
sourceLocale,
|
|
3929
|
+
controller.signal
|
|
3930
|
+
);
|
|
3931
|
+
const metadata = createTranslationMetadata(slot, sourceLocale, text, "automatic", metadataFactory);
|
|
3932
|
+
commit(updateSchemaTranslation(currentSchema, slot, text, sourceLocale, "automatic", metadata));
|
|
3821
3933
|
onTranslationChange?.({
|
|
3822
3934
|
slot,
|
|
3823
3935
|
...slot.existingText === void 0 ? {} : { previousText: slot.existingText },
|
|
@@ -3825,20 +3937,43 @@ function useTranslationWorkspace({
|
|
|
3825
3937
|
mode: "automatic",
|
|
3826
3938
|
metadata
|
|
3827
3939
|
});
|
|
3940
|
+
setProgress({ total: 1, completed: 1, succeeded: 1, failed: 0, percentage: 100 });
|
|
3828
3941
|
return { success: true };
|
|
3829
3942
|
} catch (cause) {
|
|
3943
|
+
if (controller.signal.aborted || cause instanceof DOMException && cause.name === "AbortError") {
|
|
3944
|
+
const workspaceError2 = { type: "cancelled" };
|
|
3945
|
+
setError(workspaceError2);
|
|
3946
|
+
onTranslationError?.({ targetLocale: slot.locale, error: workspaceError2 });
|
|
3947
|
+
return { success: false, error: workspaceError2 };
|
|
3948
|
+
}
|
|
3830
3949
|
const workspaceError = {
|
|
3831
3950
|
type: "translation_failed",
|
|
3832
3951
|
message: cause instanceof Error ? cause.message : String(cause),
|
|
3833
3952
|
cause
|
|
3834
3953
|
};
|
|
3835
3954
|
setError(workspaceError);
|
|
3955
|
+
onTranslationError?.({ targetLocale: slot.locale, error: workspaceError });
|
|
3836
3956
|
return { success: false, error: workspaceError };
|
|
3837
3957
|
} finally {
|
|
3958
|
+
if (operationSignal !== void 0 && abortListener !== void 0) {
|
|
3959
|
+
const listener = abortListener;
|
|
3960
|
+
operationSignal.removeEventListener("abort", listener);
|
|
3961
|
+
}
|
|
3962
|
+
if (activeAbortController.current === controller) activeAbortController.current = void 0;
|
|
3838
3963
|
setIsTranslating(false);
|
|
3839
3964
|
}
|
|
3840
3965
|
},
|
|
3841
|
-
[
|
|
3966
|
+
[
|
|
3967
|
+
commit,
|
|
3968
|
+
currentSchema,
|
|
3969
|
+
metadataFactory,
|
|
3970
|
+
onTranslationChange,
|
|
3971
|
+
onTranslationError,
|
|
3972
|
+
readOnly,
|
|
3973
|
+
signal,
|
|
3974
|
+
sourceLocale,
|
|
3975
|
+
translationAdapter
|
|
3976
|
+
]
|
|
3842
3977
|
);
|
|
3843
3978
|
return {
|
|
3844
3979
|
sourceLocale,
|
|
@@ -3859,7 +3994,9 @@ function useTranslationWorkspace({
|
|
|
3859
3994
|
setTranslation,
|
|
3860
3995
|
translateAll,
|
|
3861
3996
|
translateSlot,
|
|
3997
|
+
cancelTranslation,
|
|
3862
3998
|
isTranslating,
|
|
3999
|
+
...progress === void 0 ? {} : { progress },
|
|
3863
4000
|
...error === void 0 ? {} : { error }
|
|
3864
4001
|
};
|
|
3865
4002
|
}
|
|
@@ -3869,6 +4006,7 @@ function canonicalMetadata(metadata, sourceText, sourceLocale, translatedText) {
|
|
|
3869
4006
|
if (translatedText === void 0 || translatedText.trim().length === 0) return void 0;
|
|
3870
4007
|
if (metadata !== void 0 && typeof metadata.sourceLocale === "string" && typeof metadata.sourceTextHash === "string" && (metadata.translationSource === "automatic" || metadata.translationSource === "manual")) {
|
|
3871
4008
|
return {
|
|
4009
|
+
...metadata,
|
|
3872
4010
|
sourceLocale: metadata.sourceLocale,
|
|
3873
4011
|
sourceTextHash: metadata.sourceTextHash,
|
|
3874
4012
|
translationSource: metadata.translationSource,
|
|
@@ -3876,7 +4014,10 @@ function canonicalMetadata(metadata, sourceText, sourceLocale, translatedText) {
|
|
|
3876
4014
|
...typeof metadata.editedAt === "string" ? { editedAt: metadata.editedAt } : {}
|
|
3877
4015
|
};
|
|
3878
4016
|
}
|
|
4017
|
+
const canonicalKeys = /* @__PURE__ */ new Set(["sourceLocale", "sourceTextHash", "translationSource", "translatedAt", "editedAt"]);
|
|
4018
|
+
const extensions = Object.fromEntries(Object.entries(metadata ?? {}).filter(([key]) => !canonicalKeys.has(key)));
|
|
3879
4019
|
return {
|
|
4020
|
+
...extensions,
|
|
3880
4021
|
sourceLocale: typeof metadata?.sourceLocale === "string" ? metadata.sourceLocale : sourceLocale,
|
|
3881
4022
|
sourceTextHash: (0, import_core6.computeSourceTextHash)(sourceText),
|
|
3882
4023
|
translationSource: metadata?.isManual === true || metadata?.isManuallyEdited === true || metadata?.translationSource === "manual" ? "manual" : "automatic"
|
|
@@ -3887,6 +4028,7 @@ function optionParentId(slot) {
|
|
|
3887
4028
|
return /^fields\.([^.]+)\.options\./u.exec(slot.path)?.[1];
|
|
3888
4029
|
}
|
|
3889
4030
|
function getNodeTitle(schema, slot) {
|
|
4031
|
+
if (slot.kind === "form") return schema.title;
|
|
3890
4032
|
if (slot.kind === "field") return schema.fields.find((field) => field.id === slot.nodeId)?.title;
|
|
3891
4033
|
if (slot.kind === "page") return schema.pages?.find((page) => page.id === slot.nodeId)?.title;
|
|
3892
4034
|
if (slot.kind === "option") return schema.fields.find((field) => field.id === optionParentId(slot))?.title;
|
|
@@ -3904,6 +4046,7 @@ function comparisonItem(schema, slot, sourceLocale, hasAdapter) {
|
|
|
3904
4046
|
return {
|
|
3905
4047
|
id: path,
|
|
3906
4048
|
path,
|
|
4049
|
+
nodeId: slot.nodeId,
|
|
3907
4050
|
targetKind: slot.kind,
|
|
3908
4051
|
targetProperty: slot.property,
|
|
3909
4052
|
...nodeTitle === void 0 ? {} : { nodeTitle },
|
|
@@ -3937,18 +4080,47 @@ function useTranslationComparison({
|
|
|
3937
4080
|
targetLocale,
|
|
3938
4081
|
translationAdapter,
|
|
3939
4082
|
readOnly = false,
|
|
4083
|
+
availableLocales,
|
|
4084
|
+
policy,
|
|
3940
4085
|
onChange,
|
|
3941
|
-
onTranslationChange
|
|
4086
|
+
onTranslationChange,
|
|
4087
|
+
onTranslationReport,
|
|
4088
|
+
onTranslationError,
|
|
4089
|
+
signal,
|
|
4090
|
+
onLocaleAdded,
|
|
4091
|
+
onLocaleRemoved,
|
|
4092
|
+
onLocaleChange,
|
|
4093
|
+
beforeRemoveLocale,
|
|
4094
|
+
confirmRemoveLocale,
|
|
4095
|
+
onTranslationStart,
|
|
4096
|
+
onTranslationSuccess,
|
|
4097
|
+
validateLocale,
|
|
4098
|
+
createTranslationMetadata: createTranslationMetadata2
|
|
3942
4099
|
}) {
|
|
3943
4100
|
const workspace = useTranslationWorkspace({
|
|
3944
4101
|
schema,
|
|
3945
4102
|
sourceLocale,
|
|
3946
4103
|
targetLocale,
|
|
4104
|
+
...availableLocales === void 0 ? {} : { availableLocales },
|
|
4105
|
+
...policy === void 0 ? {} : { policy },
|
|
3947
4106
|
...translationAdapter === void 0 ? {} : { translationAdapter },
|
|
3948
4107
|
readOnly,
|
|
3949
4108
|
...onChange === void 0 ? {} : { onChange },
|
|
3950
|
-
...onTranslationChange === void 0 ? {} : { onTranslationChange }
|
|
4109
|
+
...onTranslationChange === void 0 ? {} : { onTranslationChange },
|
|
4110
|
+
...onTranslationReport === void 0 ? {} : { onTranslationReport },
|
|
4111
|
+
...onTranslationError === void 0 ? {} : { onTranslationError },
|
|
4112
|
+
...signal === void 0 ? {} : { signal },
|
|
4113
|
+
...onLocaleAdded === void 0 ? {} : { onLocaleAdded },
|
|
4114
|
+
...onLocaleRemoved === void 0 ? {} : { onLocaleRemoved },
|
|
4115
|
+
...onLocaleChange === void 0 ? {} : { onLocaleChange },
|
|
4116
|
+
...beforeRemoveLocale === void 0 ? {} : { beforeRemoveLocale },
|
|
4117
|
+
...confirmRemoveLocale === void 0 ? {} : { confirmRemoveLocale },
|
|
4118
|
+
...onTranslationStart === void 0 ? {} : { onTranslationStart },
|
|
4119
|
+
...onTranslationSuccess === void 0 ? {} : { onTranslationSuccess },
|
|
4120
|
+
...validateLocale === void 0 ? {} : { validateLocale },
|
|
4121
|
+
...createTranslationMetadata2 === void 0 ? {} : { createTranslationMetadata: createTranslationMetadata2 }
|
|
3951
4122
|
});
|
|
4123
|
+
const [report, setReport] = (0, import_react6.useState)();
|
|
3952
4124
|
const items = (0, import_react6.useMemo)(
|
|
3953
4125
|
() => workspace.slots.map((slot) => comparisonItem(schema, slot, sourceLocale, translationAdapter !== void 0)),
|
|
3954
4126
|
[schema, sourceLocale, translationAdapter, workspace.slots]
|
|
@@ -3970,20 +4142,37 @@ function useTranslationComparison({
|
|
|
3970
4142
|
},
|
|
3971
4143
|
[itemByPath, workspace]
|
|
3972
4144
|
);
|
|
3973
|
-
const translateAll = (0, import_react6.useCallback)(
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
4145
|
+
const translateAll = (0, import_react6.useCallback)(
|
|
4146
|
+
async (options) => {
|
|
4147
|
+
const result = await workspace.translateAll(options);
|
|
4148
|
+
if (result.report !== void 0) {
|
|
4149
|
+
setReport(result.report);
|
|
4150
|
+
return result.report;
|
|
4151
|
+
}
|
|
4152
|
+
throw new Error(result.error?.type ?? "Translation failed.");
|
|
4153
|
+
},
|
|
4154
|
+
[workspace]
|
|
4155
|
+
);
|
|
3978
4156
|
return {
|
|
3979
4157
|
sourceLocale: workspace.sourceLocale,
|
|
3980
4158
|
targetLocale: workspace.targetLocale,
|
|
4159
|
+
targetLocales: workspace.targetLocales,
|
|
4160
|
+
localeOptions: workspace.localeOptions,
|
|
4161
|
+
setTargetLocale: workspace.setTargetLocale,
|
|
4162
|
+
addLocale: workspace.addLocale,
|
|
4163
|
+
isAddLocaleAllowed: workspace.isAddLocaleAllowed,
|
|
4164
|
+
removeLocale: workspace.removeLocale,
|
|
4165
|
+
...workspace.removeLocaleConfirmation === void 0 ? {} : { removeLocaleConfirmation: workspace.removeLocaleConfirmation },
|
|
3981
4166
|
items,
|
|
3982
4167
|
summary: summaryFor(items),
|
|
3983
4168
|
isTranslating: workspace.isTranslating,
|
|
3984
4169
|
updateTranslation,
|
|
3985
4170
|
translateSingle,
|
|
3986
|
-
translateAll
|
|
4171
|
+
translateAll,
|
|
4172
|
+
cancelTranslation: workspace.cancelTranslation,
|
|
4173
|
+
...workspace.progress === void 0 ? {} : { progress: workspace.progress },
|
|
4174
|
+
...workspace.error === void 0 ? {} : { error: workspace.error },
|
|
4175
|
+
...report === void 0 ? {} : { report }
|
|
3987
4176
|
};
|
|
3988
4177
|
}
|
|
3989
4178
|
|