@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/dist/index.js
CHANGED
|
@@ -3274,7 +3274,7 @@ function useField(fieldId) {
|
|
|
3274
3274
|
|
|
3275
3275
|
// src/hooks/useTranslationComparison.ts
|
|
3276
3276
|
import { computeSourceTextHash as computeSourceTextHash2 } from "@form-engine-ts/core";
|
|
3277
|
-
import { useCallback as useCallback4, useMemo as useMemo5 } from "react";
|
|
3277
|
+
import { useCallback as useCallback4, useMemo as useMemo5, useState as useState5 } from "react";
|
|
3278
3278
|
|
|
3279
3279
|
// src/hooks/useTranslationWorkspace.ts
|
|
3280
3280
|
import {
|
|
@@ -3301,7 +3301,7 @@ function asAsyncAdapter(adapter) {
|
|
|
3301
3301
|
)
|
|
3302
3302
|
};
|
|
3303
3303
|
}
|
|
3304
|
-
var validateLocalePipeline = (locale, schema, policy, customValidator, availableLocales) => {
|
|
3304
|
+
var validateLocalePipeline = (locale, schema, policy, customValidator, availableLocales, sourceLocale) => {
|
|
3305
3305
|
const canonicalLocale = normalizeLocale(locale);
|
|
3306
3306
|
if (canonicalLocale === null) {
|
|
3307
3307
|
return {
|
|
@@ -3313,20 +3313,17 @@ var validateLocalePipeline = (locale, schema, policy, customValidator, available
|
|
|
3313
3313
|
};
|
|
3314
3314
|
}
|
|
3315
3315
|
const currentLocales = (schema.supportedLocales ?? []).map((candidate) => normalizeLocale(candidate) ?? candidate);
|
|
3316
|
-
const defaultLocale = schema.defaultLocale
|
|
3317
|
-
if (
|
|
3318
|
-
const value = typeof candidate === "string" ? candidate : candidate.locale;
|
|
3319
|
-
return normalizeLocale(value) === canonicalLocale;
|
|
3320
|
-
})) {
|
|
3316
|
+
const defaultLocale = normalizeLocale(sourceLocale ?? schema.defaultLocale ?? "") ?? sourceLocale ?? schema.defaultLocale ?? "";
|
|
3317
|
+
if (canonicalLocale === defaultLocale) {
|
|
3321
3318
|
return {
|
|
3322
3319
|
valid: false,
|
|
3323
3320
|
error: {
|
|
3324
|
-
type: "
|
|
3325
|
-
message: `Locale "${canonicalLocale}" is
|
|
3321
|
+
type: "source_locale",
|
|
3322
|
+
message: `Locale "${canonicalLocale}" is the source locale and cannot be added as a translation.`
|
|
3326
3323
|
}
|
|
3327
3324
|
};
|
|
3328
3325
|
}
|
|
3329
|
-
if (
|
|
3326
|
+
if (currentLocales.includes(canonicalLocale)) {
|
|
3330
3327
|
return {
|
|
3331
3328
|
valid: false,
|
|
3332
3329
|
error: {
|
|
@@ -3335,6 +3332,18 @@ var validateLocalePipeline = (locale, schema, policy, customValidator, available
|
|
|
3335
3332
|
}
|
|
3336
3333
|
};
|
|
3337
3334
|
}
|
|
3335
|
+
if (availableLocales !== void 0 && !availableLocales.some((candidate) => {
|
|
3336
|
+
const value = typeof candidate === "string" ? candidate : candidate.locale;
|
|
3337
|
+
return normalizeLocale(value) === canonicalLocale;
|
|
3338
|
+
})) {
|
|
3339
|
+
return {
|
|
3340
|
+
valid: false,
|
|
3341
|
+
error: {
|
|
3342
|
+
type: "locale_not_allowed",
|
|
3343
|
+
message: `Locale "${canonicalLocale}" is not available in the locale catalog.`
|
|
3344
|
+
}
|
|
3345
|
+
};
|
|
3346
|
+
}
|
|
3338
3347
|
if (policy?.allowedLocales !== void 0 && !policy.allowedLocales.some((candidate) => normalizeLocale(candidate) === canonicalLocale)) {
|
|
3339
3348
|
return {
|
|
3340
3349
|
valid: false,
|
|
@@ -3388,6 +3397,7 @@ function workspaceLocaleValidationError(validation, currentLocales, requestedLoc
|
|
|
3388
3397
|
if (error.type === "locale_already_exists") {
|
|
3389
3398
|
return { type: "locale_already_exists", locale: requestedLocale };
|
|
3390
3399
|
}
|
|
3400
|
+
if (error.type === "source_locale") return { type: "source_locale", locale: requestedLocale };
|
|
3391
3401
|
if (error.type === "invalid_locale_format") {
|
|
3392
3402
|
return { type: "invalid_locale_format", locale: requestedLocale };
|
|
3393
3403
|
}
|
|
@@ -3405,19 +3415,29 @@ function translationMetadata(sourceText, sourceLocale, mode) {
|
|
|
3405
3415
|
...mode === "manual" ? { editedAt: (/* @__PURE__ */ new Date()).toISOString() } : { translatedAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
3406
3416
|
};
|
|
3407
3417
|
}
|
|
3408
|
-
function
|
|
3418
|
+
function createTranslationMetadata(slot, sourceLocale, translatedText, mode, factory) {
|
|
3419
|
+
return {
|
|
3420
|
+
...factory?.({ slot, translatedText, mode }) ?? {},
|
|
3421
|
+
...translationMetadata(slot.sourceText, sourceLocale, mode)
|
|
3422
|
+
};
|
|
3423
|
+
}
|
|
3424
|
+
function setNodeMetadata(node, slot, text, sourceLocale, mode, metadataOverride) {
|
|
3409
3425
|
const localeMetadata = node.translationMetadata?.[slot.locale];
|
|
3410
|
-
const nextMetadata = text.trim().length === 0 ? Object.fromEntries(Object.entries(localeMetadata ?? {}).filter(([key]) => key !== slot.property)) : {
|
|
3426
|
+
const nextMetadata = text.trim().length === 0 ? Object.fromEntries(Object.entries(localeMetadata ?? {}).filter(([key]) => key !== slot.property)) : {
|
|
3427
|
+
...localeMetadata,
|
|
3428
|
+
[slot.property]: metadataOverride ?? translationMetadata(slot.sourceText, sourceLocale, mode)
|
|
3429
|
+
};
|
|
3411
3430
|
return { ...node, translationMetadata: { ...node.translationMetadata, [slot.locale]: nextMetadata } };
|
|
3412
3431
|
}
|
|
3413
|
-
function updateSchemaTranslation(schema, slot, text, sourceLocale, mode = "manual") {
|
|
3432
|
+
function updateSchemaTranslation(schema, slot, text, sourceLocale, mode = "manual", metadataOverride) {
|
|
3414
3433
|
if (slot.kind === "form") {
|
|
3415
3434
|
return setNodeMetadata(
|
|
3416
3435
|
{ ...schema, translations: updateTranslationMap(schema.translations, slot.locale, slot.property, text) },
|
|
3417
3436
|
slot,
|
|
3418
3437
|
text,
|
|
3419
3438
|
sourceLocale,
|
|
3420
|
-
mode
|
|
3439
|
+
mode,
|
|
3440
|
+
metadataOverride
|
|
3421
3441
|
);
|
|
3422
3442
|
}
|
|
3423
3443
|
if (slot.kind === "field") {
|
|
@@ -3426,7 +3446,14 @@ function updateSchemaTranslation(schema, slot, text, sourceLocale, mode = "manua
|
|
|
3426
3446
|
fields: schema.fields.map((field) => {
|
|
3427
3447
|
if (field.id !== slot.nodeId) return field;
|
|
3428
3448
|
const translations = updateTranslationMap(field.translations, slot.locale, slot.property, text);
|
|
3429
|
-
return setNodeMetadata(
|
|
3449
|
+
return setNodeMetadata(
|
|
3450
|
+
{ ...field, translations },
|
|
3451
|
+
slot,
|
|
3452
|
+
text,
|
|
3453
|
+
sourceLocale,
|
|
3454
|
+
mode,
|
|
3455
|
+
metadataOverride
|
|
3456
|
+
);
|
|
3430
3457
|
})
|
|
3431
3458
|
};
|
|
3432
3459
|
}
|
|
@@ -3442,7 +3469,7 @@ function updateSchemaTranslation(schema, slot, text, sourceLocale, mode = "manua
|
|
|
3442
3469
|
const translations = text.trim().length === 0 ? Object.fromEntries(
|
|
3443
3470
|
Object.entries(option.translations ?? {}).filter(([locale]) => locale !== slot.locale)
|
|
3444
3471
|
) : { ...option.translations, [slot.locale]: text };
|
|
3445
|
-
return setNodeMetadata({ ...option, translations }, slot, text, sourceLocale, mode);
|
|
3472
|
+
return setNodeMetadata({ ...option, translations }, slot, text, sourceLocale, mode, metadataOverride);
|
|
3446
3473
|
})
|
|
3447
3474
|
};
|
|
3448
3475
|
})
|
|
@@ -3454,7 +3481,7 @@ function updateSchemaTranslation(schema, slot, text, sourceLocale, mode = "manua
|
|
|
3454
3481
|
pages: schema.pages.map((page) => {
|
|
3455
3482
|
if (page.id !== slot.nodeId) return page;
|
|
3456
3483
|
const translations = updateTranslationMap(page.translations, slot.locale, slot.property, text);
|
|
3457
|
-
return setNodeMetadata({ ...page, translations }, slot, text, sourceLocale, mode);
|
|
3484
|
+
return setNodeMetadata({ ...page, translations }, slot, text, sourceLocale, mode, metadataOverride);
|
|
3458
3485
|
})
|
|
3459
3486
|
}
|
|
3460
3487
|
};
|
|
@@ -3465,6 +3492,7 @@ function useTranslationWorkspace({
|
|
|
3465
3492
|
sourceLocale = schema.defaultLocale ?? "en",
|
|
3466
3493
|
targetLocale,
|
|
3467
3494
|
translationAdapter,
|
|
3495
|
+
signal,
|
|
3468
3496
|
readOnly = false,
|
|
3469
3497
|
policy,
|
|
3470
3498
|
availableLocales,
|
|
@@ -3476,34 +3504,49 @@ function useTranslationWorkspace({
|
|
|
3476
3504
|
slots: workspaceSlots,
|
|
3477
3505
|
onTranslationStart,
|
|
3478
3506
|
onTranslationSuccess,
|
|
3507
|
+
onTranslationReport,
|
|
3479
3508
|
onTranslationError,
|
|
3480
3509
|
onTranslationChange,
|
|
3481
|
-
validateLocale
|
|
3510
|
+
validateLocale,
|
|
3511
|
+
createTranslationMetadata: metadataFactory
|
|
3482
3512
|
}) {
|
|
3483
3513
|
const [draftSchema, setDraftSchema] = useState4(schema);
|
|
3484
3514
|
const [selectedLocale, setSelectedLocale] = useState4(normalizeLocale(targetLocale ?? "") ?? targetLocale ?? "");
|
|
3485
3515
|
const [isTranslating, setIsTranslating] = useState4(false);
|
|
3516
|
+
const [progress, setProgress] = useState4();
|
|
3486
3517
|
const [error, setError] = useState4();
|
|
3487
3518
|
const [pendingRemoval, setPendingRemoval] = useState4();
|
|
3519
|
+
const activeAbortController = useRef2(void 0);
|
|
3488
3520
|
const pendingRemovalResolver = useRef2(void 0);
|
|
3489
3521
|
const currentSchema = onChange === void 0 ? draftSchema : schema;
|
|
3490
3522
|
const confirmRemoveLocaleRenderer = confirmRemoveLocale ?? workspaceSlots?.confirmRemoveLocale;
|
|
3491
3523
|
const targetLocales = useMemo4(() => {
|
|
3492
3524
|
const locales = collectSchemaLocales2(currentSchema).allUniqueLocales;
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3525
|
+
const normalizedSourceLocale = normalizeLocale(sourceLocale) ?? sourceLocale;
|
|
3526
|
+
return [
|
|
3527
|
+
...new Set(
|
|
3528
|
+
[...currentSchema.supportedLocales ?? [], ...locales].map((locale) => normalizeLocale(locale) ?? locale)
|
|
3529
|
+
)
|
|
3530
|
+
].filter((locale) => locale !== normalizedSourceLocale);
|
|
3496
3531
|
}, [currentSchema, sourceLocale]);
|
|
3497
3532
|
const activeLocale = selectedLocale.length > 0 ? selectedLocale : targetLocales[0] ?? "";
|
|
3498
3533
|
const localeOptions = useMemo4(() => {
|
|
3499
3534
|
if (availableLocales === void 0) {
|
|
3500
3535
|
return targetLocales.map((locale) => ({ locale, label: locale }));
|
|
3501
3536
|
}
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3537
|
+
const targetLocaleSet = new Set(targetLocales);
|
|
3538
|
+
const allowedLocales = policy?.allowedLocales?.map((locale) => normalizeLocale(locale) ?? locale);
|
|
3539
|
+
const options = /* @__PURE__ */ new Map();
|
|
3540
|
+
for (const candidate of availableLocales) {
|
|
3541
|
+
const option = typeof candidate === "string" ? { locale: normalizeLocale(candidate) ?? candidate, label: candidate } : { ...candidate, locale: normalizeLocale(candidate.locale) ?? candidate.locale };
|
|
3542
|
+
const allowed = allowedLocales === void 0 || allowedLocales.includes(option.locale);
|
|
3543
|
+
if (allowed || targetLocaleSet.has(option.locale)) options.set(option.locale, option);
|
|
3544
|
+
}
|
|
3545
|
+
for (const locale of targetLocales) {
|
|
3546
|
+
if (!options.has(locale)) options.set(locale, { locale, label: locale });
|
|
3547
|
+
}
|
|
3548
|
+
return [...options.values()];
|
|
3549
|
+
}, [availableLocales, policy?.allowedLocales, targetLocales]);
|
|
3507
3550
|
const slots = useMemo4(
|
|
3508
3551
|
() => activeLocale.length === 0 ? [] : collectTranslationSlots(currentSchema, activeLocale),
|
|
3509
3552
|
[activeLocale, currentSchema]
|
|
@@ -3537,8 +3580,8 @@ function useTranslationWorkspace({
|
|
|
3537
3580
|
const setTranslation = useCallback3(
|
|
3538
3581
|
(slot, text) => {
|
|
3539
3582
|
if (readOnly) return;
|
|
3540
|
-
const metadata =
|
|
3541
|
-
commit(updateSchemaTranslation(currentSchema, slot, text, sourceLocale, "manual"));
|
|
3583
|
+
const metadata = createTranslationMetadata(slot, sourceLocale, text, "manual", metadataFactory);
|
|
3584
|
+
commit(updateSchemaTranslation(currentSchema, slot, text, sourceLocale, "manual", metadata));
|
|
3542
3585
|
onTranslationChange?.({
|
|
3543
3586
|
slot,
|
|
3544
3587
|
...slot.existingText === void 0 ? {} : { previousText: slot.existingText },
|
|
@@ -3547,7 +3590,7 @@ function useTranslationWorkspace({
|
|
|
3547
3590
|
metadata
|
|
3548
3591
|
});
|
|
3549
3592
|
},
|
|
3550
|
-
[commit, currentSchema, onTranslationChange, readOnly, sourceLocale]
|
|
3593
|
+
[commit, currentSchema, metadataFactory, onTranslationChange, readOnly, sourceLocale]
|
|
3551
3594
|
);
|
|
3552
3595
|
const addLocale = useCallback3(
|
|
3553
3596
|
(locale) => {
|
|
@@ -3559,14 +3602,21 @@ function useTranslationWorkspace({
|
|
|
3559
3602
|
const normalized = normalizeLocale(locale);
|
|
3560
3603
|
if (normalized === null) {
|
|
3561
3604
|
const workspaceError = workspaceLocaleValidationError(
|
|
3562
|
-
validateLocalePipeline(locale, currentSchema, policy, validateLocale, availableLocales),
|
|
3605
|
+
validateLocalePipeline(locale, currentSchema, policy, validateLocale, availableLocales, sourceLocale),
|
|
3563
3606
|
collectSchemaLocales2(currentSchema).allUniqueLocales.size,
|
|
3564
3607
|
locale
|
|
3565
3608
|
);
|
|
3566
3609
|
setError(workspaceError);
|
|
3567
3610
|
return { success: false, error: workspaceError };
|
|
3568
3611
|
}
|
|
3569
|
-
const validation = validateLocalePipeline(
|
|
3612
|
+
const validation = validateLocalePipeline(
|
|
3613
|
+
normalized,
|
|
3614
|
+
currentSchema,
|
|
3615
|
+
policy,
|
|
3616
|
+
validateLocale,
|
|
3617
|
+
availableLocales,
|
|
3618
|
+
sourceLocale
|
|
3619
|
+
);
|
|
3570
3620
|
if (!validation.valid) {
|
|
3571
3621
|
const workspaceError = workspaceLocaleValidationError(
|
|
3572
3622
|
validation,
|
|
@@ -3585,16 +3635,16 @@ function useTranslationWorkspace({
|
|
|
3585
3635
|
onLocaleAdded?.(normalized);
|
|
3586
3636
|
return { success: true };
|
|
3587
3637
|
},
|
|
3588
|
-
[availableLocales, commit, currentSchema, onLocaleAdded, policy, readOnly, validateLocale]
|
|
3638
|
+
[availableLocales, commit, currentSchema, onLocaleAdded, policy, readOnly, sourceLocale, validateLocale]
|
|
3589
3639
|
);
|
|
3590
3640
|
const isAddLocaleAllowed = useCallback3(
|
|
3591
3641
|
(locale) => {
|
|
3592
3642
|
if (readOnly) return false;
|
|
3593
3643
|
const normalized = normalizeLocale(locale);
|
|
3594
3644
|
if (normalized === null) return false;
|
|
3595
|
-
return validateLocalePipeline(normalized, currentSchema, policy, validateLocale, availableLocales).valid;
|
|
3645
|
+
return validateLocalePipeline(normalized, currentSchema, policy, validateLocale, availableLocales, sourceLocale).valid;
|
|
3596
3646
|
},
|
|
3597
|
-
[availableLocales, currentSchema, policy, readOnly, validateLocale]
|
|
3647
|
+
[availableLocales, currentSchema, policy, readOnly, sourceLocale, validateLocale]
|
|
3598
3648
|
);
|
|
3599
3649
|
const removeLocale = useCallback3(
|
|
3600
3650
|
(locale) => {
|
|
@@ -3724,7 +3774,15 @@ function useTranslationWorkspace({
|
|
|
3724
3774
|
}
|
|
3725
3775
|
setIsTranslating(true);
|
|
3726
3776
|
setError(void 0);
|
|
3777
|
+
setProgress(void 0);
|
|
3727
3778
|
onTranslationStart?.({ targetLocale: activeLocale, mode: "automatic" });
|
|
3779
|
+
const controller = new AbortController();
|
|
3780
|
+
activeAbortController.current = controller;
|
|
3781
|
+
const operationSignal = options.signal ?? signal;
|
|
3782
|
+
const abortListener = operationSignal === void 0 ? void 0 : () => controller.abort(operationSignal.reason);
|
|
3783
|
+
if (operationSignal?.aborted) controller.abort(operationSignal.reason);
|
|
3784
|
+
else if (operationSignal !== void 0 && abortListener !== void 0)
|
|
3785
|
+
operationSignal.addEventListener("abort", abortListener, { once: true });
|
|
3728
3786
|
try {
|
|
3729
3787
|
const populated = await populateSchemaTranslations2(
|
|
3730
3788
|
currentSchema,
|
|
@@ -3733,13 +3791,37 @@ function useTranslationWorkspace({
|
|
|
3733
3791
|
{
|
|
3734
3792
|
overwrite: "stale-and-missing",
|
|
3735
3793
|
preserveManualTranslations: true,
|
|
3736
|
-
...options
|
|
3794
|
+
...options,
|
|
3795
|
+
continueOnError: true,
|
|
3796
|
+
signal: controller.signal,
|
|
3797
|
+
createMetadata: (slot, translatedText) => options.createMetadata?.(slot, translatedText) ?? createTranslationMetadata(slot, sourceLocale, translatedText, "automatic", metadataFactory),
|
|
3798
|
+
onProgress: (nextProgress) => {
|
|
3799
|
+
setProgress(nextProgress);
|
|
3800
|
+
options.onProgress?.(nextProgress);
|
|
3801
|
+
}
|
|
3737
3802
|
}
|
|
3738
3803
|
);
|
|
3739
3804
|
commit(populated.schema);
|
|
3805
|
+
onTranslationReport?.(populated.report);
|
|
3740
3806
|
const missingSlotsCount = collectTranslationSlots(populated.schema, activeLocale).filter(
|
|
3741
3807
|
(slot) => slot.status === "missing"
|
|
3742
3808
|
).length;
|
|
3809
|
+
if (populated.report.cancelled === true) {
|
|
3810
|
+
const workspaceError = { type: "cancelled" };
|
|
3811
|
+
setError(workspaceError);
|
|
3812
|
+
onTranslationError?.({ targetLocale: activeLocale, error: workspaceError });
|
|
3813
|
+
return { success: false, report: populated.report, error: workspaceError };
|
|
3814
|
+
}
|
|
3815
|
+
if ((populated.report.failed ?? 0) > 0) {
|
|
3816
|
+
const workspaceError = {
|
|
3817
|
+
type: "partial_failure",
|
|
3818
|
+
succeeded: populated.report.succeeded ?? populated.report.updatedSlots.length,
|
|
3819
|
+
failed: populated.report.failed ?? 0
|
|
3820
|
+
};
|
|
3821
|
+
setError(workspaceError);
|
|
3822
|
+
onTranslationError?.({ targetLocale: activeLocale, error: workspaceError });
|
|
3823
|
+
return { success: false, report: populated.report, error: workspaceError };
|
|
3824
|
+
}
|
|
3743
3825
|
onTranslationSuccess?.({
|
|
3744
3826
|
sourceLocale,
|
|
3745
3827
|
targetLocale: activeLocale,
|
|
@@ -3750,6 +3832,12 @@ function useTranslationWorkspace({
|
|
|
3750
3832
|
});
|
|
3751
3833
|
return { success: true, report: populated.report };
|
|
3752
3834
|
} catch (cause) {
|
|
3835
|
+
if (controller.signal.aborted || cause instanceof DOMException && cause.name === "AbortError") {
|
|
3836
|
+
const workspaceError2 = { type: "cancelled" };
|
|
3837
|
+
setError(workspaceError2);
|
|
3838
|
+
onTranslationError?.({ targetLocale: activeLocale, error: workspaceError2 });
|
|
3839
|
+
return { success: false, error: workspaceError2 };
|
|
3840
|
+
}
|
|
3753
3841
|
const workspaceError = {
|
|
3754
3842
|
type: "translation_failed",
|
|
3755
3843
|
message: cause instanceof Error ? cause.message : String(cause),
|
|
@@ -3759,6 +3847,11 @@ function useTranslationWorkspace({
|
|
|
3759
3847
|
onTranslationError?.({ targetLocale: activeLocale, error: workspaceError });
|
|
3760
3848
|
return { success: false, error: workspaceError };
|
|
3761
3849
|
} finally {
|
|
3850
|
+
if (operationSignal !== void 0 && abortListener !== void 0) {
|
|
3851
|
+
const listener = abortListener;
|
|
3852
|
+
operationSignal.removeEventListener("abort", listener);
|
|
3853
|
+
}
|
|
3854
|
+
if (activeAbortController.current === controller) activeAbortController.current = void 0;
|
|
3762
3855
|
setIsTranslating(false);
|
|
3763
3856
|
}
|
|
3764
3857
|
},
|
|
@@ -3770,14 +3863,20 @@ function useTranslationWorkspace({
|
|
|
3770
3863
|
onTranslationError,
|
|
3771
3864
|
onTranslationStart,
|
|
3772
3865
|
onTranslationSuccess,
|
|
3866
|
+
onTranslationReport,
|
|
3773
3867
|
readOnly,
|
|
3774
3868
|
slots,
|
|
3775
3869
|
sourceLocale,
|
|
3776
|
-
|
|
3870
|
+
metadataFactory,
|
|
3871
|
+
translationAdapter,
|
|
3872
|
+
signal
|
|
3777
3873
|
]
|
|
3778
3874
|
);
|
|
3875
|
+
const cancelTranslation = useCallback3(() => {
|
|
3876
|
+
activeAbortController.current?.abort(new DOMException("The translation was cancelled.", "AbortError"));
|
|
3877
|
+
}, []);
|
|
3779
3878
|
const translateSlot = useCallback3(
|
|
3780
|
-
async (slot) => {
|
|
3879
|
+
async (slot, options = {}) => {
|
|
3781
3880
|
if (translationAdapter === void 0) {
|
|
3782
3881
|
const workspaceError = { type: "adapter_not_configured" };
|
|
3783
3882
|
setError(workspaceError);
|
|
@@ -3790,10 +3889,23 @@ function useTranslationWorkspace({
|
|
|
3790
3889
|
}
|
|
3791
3890
|
setIsTranslating(true);
|
|
3792
3891
|
setError(void 0);
|
|
3892
|
+
setProgress({ total: 1, completed: 0, succeeded: 0, failed: 0, percentage: 0 });
|
|
3893
|
+
const controller = new AbortController();
|
|
3894
|
+
activeAbortController.current = controller;
|
|
3895
|
+
const operationSignal = options.signal ?? signal;
|
|
3896
|
+
const abortListener = operationSignal === void 0 ? void 0 : () => controller.abort(operationSignal.reason);
|
|
3897
|
+
if (operationSignal?.aborted) controller.abort(operationSignal.reason);
|
|
3898
|
+
else if (operationSignal !== void 0 && abortListener !== void 0)
|
|
3899
|
+
operationSignal.addEventListener("abort", abortListener, { once: true });
|
|
3793
3900
|
try {
|
|
3794
|
-
const text = await asAsyncAdapter(translationAdapter).translateText(
|
|
3795
|
-
|
|
3796
|
-
|
|
3901
|
+
const text = await asAsyncAdapter(translationAdapter).translateText(
|
|
3902
|
+
slot.sourceText,
|
|
3903
|
+
slot.locale,
|
|
3904
|
+
sourceLocale,
|
|
3905
|
+
controller.signal
|
|
3906
|
+
);
|
|
3907
|
+
const metadata = createTranslationMetadata(slot, sourceLocale, text, "automatic", metadataFactory);
|
|
3908
|
+
commit(updateSchemaTranslation(currentSchema, slot, text, sourceLocale, "automatic", metadata));
|
|
3797
3909
|
onTranslationChange?.({
|
|
3798
3910
|
slot,
|
|
3799
3911
|
...slot.existingText === void 0 ? {} : { previousText: slot.existingText },
|
|
@@ -3801,20 +3913,43 @@ function useTranslationWorkspace({
|
|
|
3801
3913
|
mode: "automatic",
|
|
3802
3914
|
metadata
|
|
3803
3915
|
});
|
|
3916
|
+
setProgress({ total: 1, completed: 1, succeeded: 1, failed: 0, percentage: 100 });
|
|
3804
3917
|
return { success: true };
|
|
3805
3918
|
} catch (cause) {
|
|
3919
|
+
if (controller.signal.aborted || cause instanceof DOMException && cause.name === "AbortError") {
|
|
3920
|
+
const workspaceError2 = { type: "cancelled" };
|
|
3921
|
+
setError(workspaceError2);
|
|
3922
|
+
onTranslationError?.({ targetLocale: slot.locale, error: workspaceError2 });
|
|
3923
|
+
return { success: false, error: workspaceError2 };
|
|
3924
|
+
}
|
|
3806
3925
|
const workspaceError = {
|
|
3807
3926
|
type: "translation_failed",
|
|
3808
3927
|
message: cause instanceof Error ? cause.message : String(cause),
|
|
3809
3928
|
cause
|
|
3810
3929
|
};
|
|
3811
3930
|
setError(workspaceError);
|
|
3931
|
+
onTranslationError?.({ targetLocale: slot.locale, error: workspaceError });
|
|
3812
3932
|
return { success: false, error: workspaceError };
|
|
3813
3933
|
} finally {
|
|
3934
|
+
if (operationSignal !== void 0 && abortListener !== void 0) {
|
|
3935
|
+
const listener = abortListener;
|
|
3936
|
+
operationSignal.removeEventListener("abort", listener);
|
|
3937
|
+
}
|
|
3938
|
+
if (activeAbortController.current === controller) activeAbortController.current = void 0;
|
|
3814
3939
|
setIsTranslating(false);
|
|
3815
3940
|
}
|
|
3816
3941
|
},
|
|
3817
|
-
[
|
|
3942
|
+
[
|
|
3943
|
+
commit,
|
|
3944
|
+
currentSchema,
|
|
3945
|
+
metadataFactory,
|
|
3946
|
+
onTranslationChange,
|
|
3947
|
+
onTranslationError,
|
|
3948
|
+
readOnly,
|
|
3949
|
+
signal,
|
|
3950
|
+
sourceLocale,
|
|
3951
|
+
translationAdapter
|
|
3952
|
+
]
|
|
3818
3953
|
);
|
|
3819
3954
|
return {
|
|
3820
3955
|
sourceLocale,
|
|
@@ -3835,7 +3970,9 @@ function useTranslationWorkspace({
|
|
|
3835
3970
|
setTranslation,
|
|
3836
3971
|
translateAll,
|
|
3837
3972
|
translateSlot,
|
|
3973
|
+
cancelTranslation,
|
|
3838
3974
|
isTranslating,
|
|
3975
|
+
...progress === void 0 ? {} : { progress },
|
|
3839
3976
|
...error === void 0 ? {} : { error }
|
|
3840
3977
|
};
|
|
3841
3978
|
}
|
|
@@ -3845,6 +3982,7 @@ function canonicalMetadata(metadata, sourceText, sourceLocale, translatedText) {
|
|
|
3845
3982
|
if (translatedText === void 0 || translatedText.trim().length === 0) return void 0;
|
|
3846
3983
|
if (metadata !== void 0 && typeof metadata.sourceLocale === "string" && typeof metadata.sourceTextHash === "string" && (metadata.translationSource === "automatic" || metadata.translationSource === "manual")) {
|
|
3847
3984
|
return {
|
|
3985
|
+
...metadata,
|
|
3848
3986
|
sourceLocale: metadata.sourceLocale,
|
|
3849
3987
|
sourceTextHash: metadata.sourceTextHash,
|
|
3850
3988
|
translationSource: metadata.translationSource,
|
|
@@ -3852,7 +3990,10 @@ function canonicalMetadata(metadata, sourceText, sourceLocale, translatedText) {
|
|
|
3852
3990
|
...typeof metadata.editedAt === "string" ? { editedAt: metadata.editedAt } : {}
|
|
3853
3991
|
};
|
|
3854
3992
|
}
|
|
3993
|
+
const canonicalKeys = /* @__PURE__ */ new Set(["sourceLocale", "sourceTextHash", "translationSource", "translatedAt", "editedAt"]);
|
|
3994
|
+
const extensions = Object.fromEntries(Object.entries(metadata ?? {}).filter(([key]) => !canonicalKeys.has(key)));
|
|
3855
3995
|
return {
|
|
3996
|
+
...extensions,
|
|
3856
3997
|
sourceLocale: typeof metadata?.sourceLocale === "string" ? metadata.sourceLocale : sourceLocale,
|
|
3857
3998
|
sourceTextHash: computeSourceTextHash2(sourceText),
|
|
3858
3999
|
translationSource: metadata?.isManual === true || metadata?.isManuallyEdited === true || metadata?.translationSource === "manual" ? "manual" : "automatic"
|
|
@@ -3863,6 +4004,7 @@ function optionParentId(slot) {
|
|
|
3863
4004
|
return /^fields\.([^.]+)\.options\./u.exec(slot.path)?.[1];
|
|
3864
4005
|
}
|
|
3865
4006
|
function getNodeTitle(schema, slot) {
|
|
4007
|
+
if (slot.kind === "form") return schema.title;
|
|
3866
4008
|
if (slot.kind === "field") return schema.fields.find((field) => field.id === slot.nodeId)?.title;
|
|
3867
4009
|
if (slot.kind === "page") return schema.pages?.find((page) => page.id === slot.nodeId)?.title;
|
|
3868
4010
|
if (slot.kind === "option") return schema.fields.find((field) => field.id === optionParentId(slot))?.title;
|
|
@@ -3880,6 +4022,7 @@ function comparisonItem(schema, slot, sourceLocale, hasAdapter) {
|
|
|
3880
4022
|
return {
|
|
3881
4023
|
id: path,
|
|
3882
4024
|
path,
|
|
4025
|
+
nodeId: slot.nodeId,
|
|
3883
4026
|
targetKind: slot.kind,
|
|
3884
4027
|
targetProperty: slot.property,
|
|
3885
4028
|
...nodeTitle === void 0 ? {} : { nodeTitle },
|
|
@@ -3913,18 +4056,47 @@ function useTranslationComparison({
|
|
|
3913
4056
|
targetLocale,
|
|
3914
4057
|
translationAdapter,
|
|
3915
4058
|
readOnly = false,
|
|
4059
|
+
availableLocales,
|
|
4060
|
+
policy,
|
|
3916
4061
|
onChange,
|
|
3917
|
-
onTranslationChange
|
|
4062
|
+
onTranslationChange,
|
|
4063
|
+
onTranslationReport,
|
|
4064
|
+
onTranslationError,
|
|
4065
|
+
signal,
|
|
4066
|
+
onLocaleAdded,
|
|
4067
|
+
onLocaleRemoved,
|
|
4068
|
+
onLocaleChange,
|
|
4069
|
+
beforeRemoveLocale,
|
|
4070
|
+
confirmRemoveLocale,
|
|
4071
|
+
onTranslationStart,
|
|
4072
|
+
onTranslationSuccess,
|
|
4073
|
+
validateLocale,
|
|
4074
|
+
createTranslationMetadata: createTranslationMetadata2
|
|
3918
4075
|
}) {
|
|
3919
4076
|
const workspace = useTranslationWorkspace({
|
|
3920
4077
|
schema,
|
|
3921
4078
|
sourceLocale,
|
|
3922
4079
|
targetLocale,
|
|
4080
|
+
...availableLocales === void 0 ? {} : { availableLocales },
|
|
4081
|
+
...policy === void 0 ? {} : { policy },
|
|
3923
4082
|
...translationAdapter === void 0 ? {} : { translationAdapter },
|
|
3924
4083
|
readOnly,
|
|
3925
4084
|
...onChange === void 0 ? {} : { onChange },
|
|
3926
|
-
...onTranslationChange === void 0 ? {} : { onTranslationChange }
|
|
4085
|
+
...onTranslationChange === void 0 ? {} : { onTranslationChange },
|
|
4086
|
+
...onTranslationReport === void 0 ? {} : { onTranslationReport },
|
|
4087
|
+
...onTranslationError === void 0 ? {} : { onTranslationError },
|
|
4088
|
+
...signal === void 0 ? {} : { signal },
|
|
4089
|
+
...onLocaleAdded === void 0 ? {} : { onLocaleAdded },
|
|
4090
|
+
...onLocaleRemoved === void 0 ? {} : { onLocaleRemoved },
|
|
4091
|
+
...onLocaleChange === void 0 ? {} : { onLocaleChange },
|
|
4092
|
+
...beforeRemoveLocale === void 0 ? {} : { beforeRemoveLocale },
|
|
4093
|
+
...confirmRemoveLocale === void 0 ? {} : { confirmRemoveLocale },
|
|
4094
|
+
...onTranslationStart === void 0 ? {} : { onTranslationStart },
|
|
4095
|
+
...onTranslationSuccess === void 0 ? {} : { onTranslationSuccess },
|
|
4096
|
+
...validateLocale === void 0 ? {} : { validateLocale },
|
|
4097
|
+
...createTranslationMetadata2 === void 0 ? {} : { createTranslationMetadata: createTranslationMetadata2 }
|
|
3927
4098
|
});
|
|
4099
|
+
const [report, setReport] = useState5();
|
|
3928
4100
|
const items = useMemo5(
|
|
3929
4101
|
() => workspace.slots.map((slot) => comparisonItem(schema, slot, sourceLocale, translationAdapter !== void 0)),
|
|
3930
4102
|
[schema, sourceLocale, translationAdapter, workspace.slots]
|
|
@@ -3946,25 +4118,42 @@ function useTranslationComparison({
|
|
|
3946
4118
|
},
|
|
3947
4119
|
[itemByPath, workspace]
|
|
3948
4120
|
);
|
|
3949
|
-
const translateAll = useCallback4(
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
4121
|
+
const translateAll = useCallback4(
|
|
4122
|
+
async (options) => {
|
|
4123
|
+
const result = await workspace.translateAll(options);
|
|
4124
|
+
if (result.report !== void 0) {
|
|
4125
|
+
setReport(result.report);
|
|
4126
|
+
return result.report;
|
|
4127
|
+
}
|
|
4128
|
+
throw new Error(result.error?.type ?? "Translation failed.");
|
|
4129
|
+
},
|
|
4130
|
+
[workspace]
|
|
4131
|
+
);
|
|
3954
4132
|
return {
|
|
3955
4133
|
sourceLocale: workspace.sourceLocale,
|
|
3956
4134
|
targetLocale: workspace.targetLocale,
|
|
4135
|
+
targetLocales: workspace.targetLocales,
|
|
4136
|
+
localeOptions: workspace.localeOptions,
|
|
4137
|
+
setTargetLocale: workspace.setTargetLocale,
|
|
4138
|
+
addLocale: workspace.addLocale,
|
|
4139
|
+
isAddLocaleAllowed: workspace.isAddLocaleAllowed,
|
|
4140
|
+
removeLocale: workspace.removeLocale,
|
|
4141
|
+
...workspace.removeLocaleConfirmation === void 0 ? {} : { removeLocaleConfirmation: workspace.removeLocaleConfirmation },
|
|
3957
4142
|
items,
|
|
3958
4143
|
summary: summaryFor(items),
|
|
3959
4144
|
isTranslating: workspace.isTranslating,
|
|
3960
4145
|
updateTranslation,
|
|
3961
4146
|
translateSingle,
|
|
3962
|
-
translateAll
|
|
4147
|
+
translateAll,
|
|
4148
|
+
cancelTranslation: workspace.cancelTranslation,
|
|
4149
|
+
...workspace.progress === void 0 ? {} : { progress: workspace.progress },
|
|
4150
|
+
...workspace.error === void 0 ? {} : { error: workspace.error },
|
|
4151
|
+
...report === void 0 ? {} : { report }
|
|
3963
4152
|
};
|
|
3964
4153
|
}
|
|
3965
4154
|
|
|
3966
4155
|
// src/receipt.ts
|
|
3967
|
-
import { useEffect as useEffect2, useMemo as useMemo6, useState as
|
|
4156
|
+
import { useEffect as useEffect2, useMemo as useMemo6, useState as useState6 } from "react";
|
|
3968
4157
|
function submissionReceiptQueryKey(formId, formVersion) {
|
|
3969
4158
|
return `${formId}:v${formVersion}`;
|
|
3970
4159
|
}
|
|
@@ -4042,7 +4231,7 @@ function useSubmissionReceipts(store, queries) {
|
|
|
4042
4231
|
(entry) => Array.isArray(entry) && typeof entry[0] === "string" && typeof entry[1] === "number" && Number.isSafeInteger(entry[1]) ? [{ formId: entry[0], formVersion: entry[1] }] : []
|
|
4043
4232
|
);
|
|
4044
4233
|
}, [querySignature]);
|
|
4045
|
-
const [state, setState] =
|
|
4234
|
+
const [state, setState] = useState6({
|
|
4046
4235
|
receipts: /* @__PURE__ */ new Map(),
|
|
4047
4236
|
isLoading: stableQueries.length > 0,
|
|
4048
4237
|
error: null
|
|
@@ -4095,7 +4284,7 @@ import {
|
|
|
4095
4284
|
useId,
|
|
4096
4285
|
useMemo as useMemo7,
|
|
4097
4286
|
useRef as useRef3,
|
|
4098
|
-
useState as
|
|
4287
|
+
useState as useState7
|
|
4099
4288
|
} from "react";
|
|
4100
4289
|
import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
4101
4290
|
var isChoiceFieldType = (type) => type === "radio" || type === "checkbox" || type === "multi-select" || type === "select";
|
|
@@ -4612,15 +4801,15 @@ function ContextFormRenderer({
|
|
|
4612
4801
|
const prefix = useId().replace(/:/g, "");
|
|
4613
4802
|
const formRef = useRef3(null);
|
|
4614
4803
|
const loadedDraftKey = useRef3(null);
|
|
4615
|
-
const [draftRestored, setDraftRestored] =
|
|
4616
|
-
const [currentPageIndex, setCurrentPageIndex] =
|
|
4617
|
-
const [focusFieldId, setFocusFieldId] =
|
|
4618
|
-
const [confirmation, setConfirmation] =
|
|
4619
|
-
const [guardMessage, setGuardMessage] =
|
|
4620
|
-
const [guardsPending, setGuardsPending] =
|
|
4621
|
-
const [receipt, setReceipt] =
|
|
4622
|
-
const [completionData, setCompletionData] =
|
|
4623
|
-
const [receiptLoaded, setReceiptLoaded] =
|
|
4804
|
+
const [draftRestored, setDraftRestored] = useState7(false);
|
|
4805
|
+
const [currentPageIndex, setCurrentPageIndex] = useState7(0);
|
|
4806
|
+
const [focusFieldId, setFocusFieldId] = useState7(null);
|
|
4807
|
+
const [confirmation, setConfirmation] = useState7(null);
|
|
4808
|
+
const [guardMessage, setGuardMessage] = useState7(null);
|
|
4809
|
+
const [guardsPending, setGuardsPending] = useState7(false);
|
|
4810
|
+
const [receipt, setReceipt] = useState7(null);
|
|
4811
|
+
const [completionData, setCompletionData] = useState7(null);
|
|
4812
|
+
const [receiptLoaded, setReceiptLoaded] = useState7(receiptStore === void 0);
|
|
4624
4813
|
const rendererSubmissionInFlight = useRef3(false);
|
|
4625
4814
|
const fallbackAttemptId = useRef3(null);
|
|
4626
4815
|
const completionRef = useRef3(null);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@form-engine-ts/react",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.3.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
"typescript"
|
|
43
43
|
],
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@form-engine-ts/
|
|
46
|
-
"@form-engine-ts/
|
|
45
|
+
"@form-engine-ts/core": "6.3.0",
|
|
46
|
+
"@form-engine-ts/privacy": "6.3.0"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"react": ">=18.2 <20",
|