@form-engine-ts/react 5.0.1 → 6.0.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 +4 -0
- package/dist/index.cjs +191 -35
- package/dist/index.d.cts +93 -4
- package/dist/index.d.ts +93 -4
- package/dist/index.js +199 -43
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -89,6 +89,10 @@ 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` can notify `onTranslationStart`, `onTranslationSuccess`, `onTranslationError`, and
|
|
93
|
+
`onTranslationChange` with typed lifecycle payloads. The MUI workspace accepts a `confirmRemoveLocale` slot for an
|
|
94
|
+
async confirmation UI; its `onConfirm` callback completes the locale removal.
|
|
95
|
+
|
|
92
96
|
Wrap a builder or renderer in `FormEngineI18nProvider` to supply a UI locale and typed Core translator independently
|
|
93
97
|
from the schema's `defaultLocale` and `supportedLocales`:
|
|
94
98
|
|
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) => {
|
|
3328
|
+
var validateLocalePipeline = (locale, schema, policy, customValidator, availableLocales) => {
|
|
3329
3329
|
const canonicalLocale = (0, import_core5.normalizeLocale)(locale);
|
|
3330
3330
|
if (canonicalLocale === null) {
|
|
3331
3331
|
return {
|
|
@@ -3338,6 +3338,18 @@ var validateLocalePipeline = (locale, schema, policy, customValidator) => {
|
|
|
3338
3338
|
}
|
|
3339
3339
|
const currentLocales = (schema.supportedLocales ?? []).map((candidate) => (0, import_core5.normalizeLocale)(candidate) ?? candidate);
|
|
3340
3340
|
const defaultLocale = schema.defaultLocale === void 0 ? "" : (0, import_core5.normalizeLocale)(schema.defaultLocale) ?? schema.defaultLocale;
|
|
3341
|
+
if (availableLocales !== void 0 && !availableLocales.some((candidate) => {
|
|
3342
|
+
const value = typeof candidate === "string" ? candidate : candidate.locale;
|
|
3343
|
+
return (0, import_core5.normalizeLocale)(value) === canonicalLocale;
|
|
3344
|
+
})) {
|
|
3345
|
+
return {
|
|
3346
|
+
valid: false,
|
|
3347
|
+
error: {
|
|
3348
|
+
type: "locale_not_allowed",
|
|
3349
|
+
message: `Locale "${canonicalLocale}" is not available in the locale catalog.`
|
|
3350
|
+
}
|
|
3351
|
+
};
|
|
3352
|
+
}
|
|
3341
3353
|
if (canonicalLocale === defaultLocale || currentLocales.includes(canonicalLocale)) {
|
|
3342
3354
|
return {
|
|
3343
3355
|
valid: false,
|
|
@@ -3409,26 +3421,27 @@ function workspaceLocaleValidationError(validation, currentLocales, requestedLoc
|
|
|
3409
3421
|
}
|
|
3410
3422
|
return { type: "custom_validation_failed", message: error.message };
|
|
3411
3423
|
}
|
|
3412
|
-
function
|
|
3424
|
+
function translationMetadata(sourceText, sourceLocale, mode) {
|
|
3413
3425
|
return {
|
|
3414
3426
|
sourceLocale,
|
|
3415
3427
|
sourceTextHash: (0, import_core5.computeSourceTextHash)(sourceText),
|
|
3416
|
-
translationSource:
|
|
3417
|
-
editedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3428
|
+
translationSource: mode,
|
|
3429
|
+
...mode === "manual" ? { editedAt: (/* @__PURE__ */ new Date()).toISOString() } : { translatedAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
3418
3430
|
};
|
|
3419
3431
|
}
|
|
3420
|
-
function setNodeMetadata(node, slot, text, sourceLocale) {
|
|
3432
|
+
function setNodeMetadata(node, slot, text, sourceLocale, mode) {
|
|
3421
3433
|
const localeMetadata = node.translationMetadata?.[slot.locale];
|
|
3422
|
-
const nextMetadata = text.trim().length === 0 ? Object.fromEntries(Object.entries(localeMetadata ?? {}).filter(([key]) => key !== slot.property)) : { ...localeMetadata, [slot.property]:
|
|
3434
|
+
const nextMetadata = text.trim().length === 0 ? Object.fromEntries(Object.entries(localeMetadata ?? {}).filter(([key]) => key !== slot.property)) : { ...localeMetadata, [slot.property]: translationMetadata(slot.sourceText, sourceLocale, mode) };
|
|
3423
3435
|
return { ...node, translationMetadata: { ...node.translationMetadata, [slot.locale]: nextMetadata } };
|
|
3424
3436
|
}
|
|
3425
|
-
function updateSchemaTranslation(schema, slot, text, sourceLocale) {
|
|
3437
|
+
function updateSchemaTranslation(schema, slot, text, sourceLocale, mode = "manual") {
|
|
3426
3438
|
if (slot.kind === "form") {
|
|
3427
3439
|
return setNodeMetadata(
|
|
3428
3440
|
{ ...schema, translations: updateTranslationMap(schema.translations, slot.locale, slot.property, text) },
|
|
3429
3441
|
slot,
|
|
3430
3442
|
text,
|
|
3431
|
-
sourceLocale
|
|
3443
|
+
sourceLocale,
|
|
3444
|
+
mode
|
|
3432
3445
|
);
|
|
3433
3446
|
}
|
|
3434
3447
|
if (slot.kind === "field") {
|
|
@@ -3437,7 +3450,7 @@ function updateSchemaTranslation(schema, slot, text, sourceLocale) {
|
|
|
3437
3450
|
fields: schema.fields.map((field) => {
|
|
3438
3451
|
if (field.id !== slot.nodeId) return field;
|
|
3439
3452
|
const translations = updateTranslationMap(field.translations, slot.locale, slot.property, text);
|
|
3440
|
-
return setNodeMetadata({ ...field, translations }, slot, text, sourceLocale);
|
|
3453
|
+
return setNodeMetadata({ ...field, translations }, slot, text, sourceLocale, mode);
|
|
3441
3454
|
})
|
|
3442
3455
|
};
|
|
3443
3456
|
}
|
|
@@ -3453,7 +3466,7 @@ function updateSchemaTranslation(schema, slot, text, sourceLocale) {
|
|
|
3453
3466
|
const translations = text.trim().length === 0 ? Object.fromEntries(
|
|
3454
3467
|
Object.entries(option.translations ?? {}).filter(([locale]) => locale !== slot.locale)
|
|
3455
3468
|
) : { ...option.translations, [slot.locale]: text };
|
|
3456
|
-
return setNodeMetadata({ ...option, translations }, slot, text, sourceLocale);
|
|
3469
|
+
return setNodeMetadata({ ...option, translations }, slot, text, sourceLocale, mode);
|
|
3457
3470
|
})
|
|
3458
3471
|
};
|
|
3459
3472
|
})
|
|
@@ -3465,7 +3478,7 @@ function updateSchemaTranslation(schema, slot, text, sourceLocale) {
|
|
|
3465
3478
|
pages: schema.pages.map((page) => {
|
|
3466
3479
|
if (page.id !== slot.nodeId) return page;
|
|
3467
3480
|
const translations = updateTranslationMap(page.translations, slot.locale, slot.property, text);
|
|
3468
|
-
return setNodeMetadata({ ...page, translations }, slot, text, sourceLocale);
|
|
3481
|
+
return setNodeMetadata({ ...page, translations }, slot, text, sourceLocale, mode);
|
|
3469
3482
|
})
|
|
3470
3483
|
}
|
|
3471
3484
|
};
|
|
@@ -3478,14 +3491,27 @@ function useTranslationWorkspace({
|
|
|
3478
3491
|
translationAdapter,
|
|
3479
3492
|
readOnly = false,
|
|
3480
3493
|
policy,
|
|
3494
|
+
availableLocales,
|
|
3495
|
+
onLocaleAdded,
|
|
3496
|
+
onLocaleRemoved,
|
|
3497
|
+
onLocaleChange,
|
|
3481
3498
|
beforeRemoveLocale,
|
|
3499
|
+
confirmRemoveLocale,
|
|
3500
|
+
slots: workspaceSlots,
|
|
3501
|
+
onTranslationStart,
|
|
3502
|
+
onTranslationSuccess,
|
|
3503
|
+
onTranslationError,
|
|
3504
|
+
onTranslationChange,
|
|
3482
3505
|
validateLocale
|
|
3483
3506
|
}) {
|
|
3484
3507
|
const [draftSchema, setDraftSchema] = (0, import_react5.useState)(schema);
|
|
3485
|
-
const [selectedLocale, setSelectedLocale] = (0, import_react5.useState)(targetLocale ?? "");
|
|
3508
|
+
const [selectedLocale, setSelectedLocale] = (0, import_react5.useState)((0, import_core5.normalizeLocale)(targetLocale ?? "") ?? targetLocale ?? "");
|
|
3486
3509
|
const [isTranslating, setIsTranslating] = (0, import_react5.useState)(false);
|
|
3487
3510
|
const [error, setError] = (0, import_react5.useState)();
|
|
3511
|
+
const [pendingRemoval, setPendingRemoval] = (0, import_react5.useState)();
|
|
3512
|
+
const pendingRemovalResolver = (0, import_react5.useRef)(void 0);
|
|
3488
3513
|
const currentSchema = onChange === void 0 ? draftSchema : schema;
|
|
3514
|
+
const confirmRemoveLocaleRenderer = confirmRemoveLocale ?? workspaceSlots?.confirmRemoveLocale;
|
|
3489
3515
|
const targetLocales = (0, import_react5.useMemo)(() => {
|
|
3490
3516
|
const locales = (0, import_core5.collectSchemaLocales)(currentSchema).allUniqueLocales;
|
|
3491
3517
|
return [.../* @__PURE__ */ new Set([...currentSchema.supportedLocales ?? [], ...locales])].filter(
|
|
@@ -3493,6 +3519,15 @@ function useTranslationWorkspace({
|
|
|
3493
3519
|
);
|
|
3494
3520
|
}, [currentSchema, sourceLocale]);
|
|
3495
3521
|
const activeLocale = selectedLocale.length > 0 ? selectedLocale : targetLocales[0] ?? "";
|
|
3522
|
+
const localeOptions = (0, import_react5.useMemo)(() => {
|
|
3523
|
+
if (availableLocales === void 0) {
|
|
3524
|
+
return targetLocales.map((locale) => ({ locale, label: locale }));
|
|
3525
|
+
}
|
|
3526
|
+
return availableLocales.map((candidate) => {
|
|
3527
|
+
if (typeof candidate !== "string") return candidate;
|
|
3528
|
+
return { locale: (0, import_core5.normalizeLocale)(candidate) ?? candidate, label: candidate };
|
|
3529
|
+
});
|
|
3530
|
+
}, [availableLocales, targetLocales]);
|
|
3496
3531
|
const slots = (0, import_react5.useMemo)(
|
|
3497
3532
|
() => activeLocale.length === 0 ? [] : (0, import_core5.collectTranslationSlots)(currentSchema, activeLocale),
|
|
3498
3533
|
[activeLocale, currentSchema]
|
|
@@ -3526,9 +3561,17 @@ function useTranslationWorkspace({
|
|
|
3526
3561
|
const setTranslation = (0, import_react5.useCallback)(
|
|
3527
3562
|
(slot, text) => {
|
|
3528
3563
|
if (readOnly) return;
|
|
3529
|
-
|
|
3564
|
+
const metadata = translationMetadata(slot.sourceText, sourceLocale, "manual");
|
|
3565
|
+
commit(updateSchemaTranslation(currentSchema, slot, text, sourceLocale, "manual"));
|
|
3566
|
+
onTranslationChange?.({
|
|
3567
|
+
slot,
|
|
3568
|
+
...slot.existingText === void 0 ? {} : { previousText: slot.existingText },
|
|
3569
|
+
nextText: text,
|
|
3570
|
+
mode: "manual",
|
|
3571
|
+
metadata
|
|
3572
|
+
});
|
|
3530
3573
|
},
|
|
3531
|
-
[commit, currentSchema, readOnly, sourceLocale]
|
|
3574
|
+
[commit, currentSchema, onTranslationChange, readOnly, sourceLocale]
|
|
3532
3575
|
);
|
|
3533
3576
|
const addLocale = (0, import_react5.useCallback)(
|
|
3534
3577
|
(locale) => {
|
|
@@ -3540,14 +3583,14 @@ function useTranslationWorkspace({
|
|
|
3540
3583
|
const normalized = (0, import_core5.normalizeLocale)(locale);
|
|
3541
3584
|
if (normalized === null) {
|
|
3542
3585
|
const workspaceError = workspaceLocaleValidationError(
|
|
3543
|
-
validateLocalePipeline(locale, currentSchema, policy, validateLocale),
|
|
3586
|
+
validateLocalePipeline(locale, currentSchema, policy, validateLocale, availableLocales),
|
|
3544
3587
|
(0, import_core5.collectSchemaLocales)(currentSchema).allUniqueLocales.size,
|
|
3545
3588
|
locale
|
|
3546
3589
|
);
|
|
3547
3590
|
setError(workspaceError);
|
|
3548
3591
|
return { success: false, error: workspaceError };
|
|
3549
3592
|
}
|
|
3550
|
-
const validation = validateLocalePipeline(normalized, currentSchema, policy, validateLocale);
|
|
3593
|
+
const validation = validateLocalePipeline(normalized, currentSchema, policy, validateLocale, availableLocales);
|
|
3551
3594
|
if (!validation.valid) {
|
|
3552
3595
|
const workspaceError = workspaceLocaleValidationError(
|
|
3553
3596
|
validation,
|
|
@@ -3563,42 +3606,62 @@ function useTranslationWorkspace({
|
|
|
3563
3606
|
supportedLocales: [.../* @__PURE__ */ new Set([...currentSchema.supportedLocales ?? [], normalized])]
|
|
3564
3607
|
});
|
|
3565
3608
|
setSelectedLocale(normalized);
|
|
3609
|
+
onLocaleAdded?.(normalized);
|
|
3566
3610
|
return { success: true };
|
|
3567
3611
|
},
|
|
3568
|
-
[commit, currentSchema, policy, readOnly, validateLocale]
|
|
3612
|
+
[availableLocales, commit, currentSchema, onLocaleAdded, policy, readOnly, validateLocale]
|
|
3569
3613
|
);
|
|
3570
3614
|
const isAddLocaleAllowed = (0, import_react5.useCallback)(
|
|
3571
3615
|
(locale) => {
|
|
3572
3616
|
if (readOnly) return false;
|
|
3573
3617
|
const normalized = (0, import_core5.normalizeLocale)(locale);
|
|
3574
3618
|
if (normalized === null) return false;
|
|
3575
|
-
return validateLocalePipeline(normalized, currentSchema, policy, validateLocale).valid;
|
|
3619
|
+
return validateLocalePipeline(normalized, currentSchema, policy, validateLocale, availableLocales).valid;
|
|
3576
3620
|
},
|
|
3577
|
-
[currentSchema, policy, readOnly, validateLocale]
|
|
3621
|
+
[availableLocales, currentSchema, policy, readOnly, validateLocale]
|
|
3578
3622
|
);
|
|
3579
3623
|
const removeLocale = (0, import_react5.useCallback)(
|
|
3580
3624
|
(locale) => {
|
|
3581
|
-
|
|
3625
|
+
const normalized = (0, import_core5.normalizeLocale)(locale) ?? locale;
|
|
3626
|
+
const normalizedSourceLocale = (0, import_core5.normalizeLocale)(sourceLocale) ?? sourceLocale;
|
|
3627
|
+
const normalizedDefaultLocale = currentSchema.defaultLocale === void 0 ? void 0 : (0, import_core5.normalizeLocale)(currentSchema.defaultLocale) ?? currentSchema.defaultLocale;
|
|
3628
|
+
if (readOnly || normalized === normalizedSourceLocale || normalized === normalizedDefaultLocale) return false;
|
|
3582
3629
|
const remove = () => {
|
|
3583
|
-
commit((0, import_core5.removeLocaleFromSchema)(currentSchema,
|
|
3584
|
-
if (activeLocale ===
|
|
3630
|
+
commit((0, import_core5.removeLocaleFromSchema)(currentSchema, normalized));
|
|
3631
|
+
if (activeLocale === normalized) setSelectedLocale("");
|
|
3632
|
+
onLocaleRemoved?.(normalized);
|
|
3633
|
+
};
|
|
3634
|
+
const slotCount = (0, import_core5.collectTranslationSlots)(currentSchema, normalized).length;
|
|
3635
|
+
const translatedSlotsCount = (0, import_core5.collectTranslationSlots)(currentSchema, normalized).filter(
|
|
3636
|
+
(slot) => slot.existingText !== void 0 && slot.existingText.trim().length > 0
|
|
3637
|
+
).length;
|
|
3638
|
+
const requestConfirmation = () => new Promise((resolve) => {
|
|
3639
|
+
pendingRemovalResolver.current = resolve;
|
|
3640
|
+
setPendingRemoval({
|
|
3641
|
+
locale: normalized,
|
|
3642
|
+
localeLabel: localeOptions.find((option) => option.locale === normalized)?.label ?? normalized,
|
|
3643
|
+
translatedSlotsCount
|
|
3644
|
+
});
|
|
3645
|
+
});
|
|
3646
|
+
const removeAfterApproval = () => {
|
|
3647
|
+
if (confirmRemoveLocaleRenderer === void 0) {
|
|
3648
|
+
remove();
|
|
3649
|
+
return true;
|
|
3650
|
+
}
|
|
3651
|
+
return requestConfirmation();
|
|
3585
3652
|
};
|
|
3586
|
-
const slotCount = (0, import_core5.collectTranslationSlots)(currentSchema, locale).length;
|
|
3587
3653
|
if (beforeRemoveLocale === void 0) {
|
|
3588
|
-
|
|
3589
|
-
return true;
|
|
3654
|
+
return removeAfterApproval();
|
|
3590
3655
|
}
|
|
3591
3656
|
try {
|
|
3592
|
-
const decision = beforeRemoveLocale(
|
|
3657
|
+
const decision = beforeRemoveLocale(normalized, { slotCount });
|
|
3593
3658
|
if (typeof decision === "boolean") {
|
|
3594
3659
|
if (!decision) return false;
|
|
3595
|
-
|
|
3596
|
-
return true;
|
|
3660
|
+
return removeAfterApproval();
|
|
3597
3661
|
}
|
|
3598
3662
|
return decision.then((allowed) => {
|
|
3599
3663
|
if (!allowed) return false;
|
|
3600
|
-
|
|
3601
|
-
return true;
|
|
3664
|
+
return removeAfterApproval();
|
|
3602
3665
|
});
|
|
3603
3666
|
} catch (cause) {
|
|
3604
3667
|
const workspaceError = {
|
|
@@ -3610,8 +3673,45 @@ function useTranslationWorkspace({
|
|
|
3610
3673
|
return Promise.reject(cause);
|
|
3611
3674
|
}
|
|
3612
3675
|
},
|
|
3613
|
-
[
|
|
3676
|
+
[
|
|
3677
|
+
activeLocale,
|
|
3678
|
+
beforeRemoveLocale,
|
|
3679
|
+
commit,
|
|
3680
|
+
confirmRemoveLocaleRenderer,
|
|
3681
|
+
currentSchema,
|
|
3682
|
+
localeOptions,
|
|
3683
|
+
onLocaleRemoved,
|
|
3684
|
+
readOnly,
|
|
3685
|
+
sourceLocale
|
|
3686
|
+
]
|
|
3614
3687
|
);
|
|
3688
|
+
const confirmPendingRemoval = (0, import_react5.useCallback)(() => {
|
|
3689
|
+
const pending = pendingRemoval;
|
|
3690
|
+
const resolve = pendingRemovalResolver.current;
|
|
3691
|
+
if (pending === void 0 || resolve === void 0) return;
|
|
3692
|
+
pendingRemovalResolver.current = void 0;
|
|
3693
|
+
setPendingRemoval(void 0);
|
|
3694
|
+
commit((0, import_core5.removeLocaleFromSchema)(currentSchema, pending.locale));
|
|
3695
|
+
if (activeLocale === pending.locale) setSelectedLocale("");
|
|
3696
|
+
onLocaleRemoved?.(pending.locale);
|
|
3697
|
+
resolve(true);
|
|
3698
|
+
}, [activeLocale, commit, currentSchema, onLocaleRemoved, pendingRemoval]);
|
|
3699
|
+
const cancelPendingRemoval = (0, import_react5.useCallback)(() => {
|
|
3700
|
+
const resolve = pendingRemovalResolver.current;
|
|
3701
|
+
if (resolve === void 0) return;
|
|
3702
|
+
pendingRemovalResolver.current = void 0;
|
|
3703
|
+
setPendingRemoval(void 0);
|
|
3704
|
+
resolve(false);
|
|
3705
|
+
}, []);
|
|
3706
|
+
const removeLocaleConfirmation = (0, import_react5.useMemo)(() => {
|
|
3707
|
+
if (confirmRemoveLocaleRenderer === void 0 || pendingRemoval === void 0) return null;
|
|
3708
|
+
return confirmRemoveLocaleRenderer({
|
|
3709
|
+
...pendingRemoval,
|
|
3710
|
+
isOpen: true,
|
|
3711
|
+
onConfirm: confirmPendingRemoval,
|
|
3712
|
+
onCancel: cancelPendingRemoval
|
|
3713
|
+
});
|
|
3714
|
+
}, [cancelPendingRemoval, confirmPendingRemoval, confirmRemoveLocaleRenderer, pendingRemoval]);
|
|
3615
3715
|
const translateAll = (0, import_react5.useCallback)(
|
|
3616
3716
|
async (options = {}) => {
|
|
3617
3717
|
if (readOnly) {
|
|
@@ -3629,8 +3729,26 @@ function useTranslationWorkspace({
|
|
|
3629
3729
|
setError(workspaceError);
|
|
3630
3730
|
return { success: false, error: workspaceError };
|
|
3631
3731
|
}
|
|
3732
|
+
const selectedLocaleOption = localeOptions.find(
|
|
3733
|
+
(option) => ((0, import_core5.normalizeLocale)(option.locale) ?? option.locale) === activeLocale
|
|
3734
|
+
);
|
|
3735
|
+
if (selectedLocaleOption?.translatable === false) {
|
|
3736
|
+
setError(void 0);
|
|
3737
|
+
return {
|
|
3738
|
+
success: true,
|
|
3739
|
+
report: {
|
|
3740
|
+
updatedSlots: [],
|
|
3741
|
+
skippedSlots: slots,
|
|
3742
|
+
staleSlots: [],
|
|
3743
|
+
skippedReasons: Object.fromEntries(
|
|
3744
|
+
slots.map((slot) => [slot.path ?? `${slot.kind}.${slot.nodeId}.${slot.property}`, "unsupported"])
|
|
3745
|
+
)
|
|
3746
|
+
}
|
|
3747
|
+
};
|
|
3748
|
+
}
|
|
3632
3749
|
setIsTranslating(true);
|
|
3633
3750
|
setError(void 0);
|
|
3751
|
+
onTranslationStart?.({ targetLocale: activeLocale, mode: "automatic" });
|
|
3634
3752
|
try {
|
|
3635
3753
|
const populated = await (0, import_core5.populateSchemaTranslations)(
|
|
3636
3754
|
currentSchema,
|
|
@@ -3643,6 +3761,17 @@ function useTranslationWorkspace({
|
|
|
3643
3761
|
}
|
|
3644
3762
|
);
|
|
3645
3763
|
commit(populated.schema);
|
|
3764
|
+
const missingSlotsCount = (0, import_core5.collectTranslationSlots)(populated.schema, activeLocale).filter(
|
|
3765
|
+
(slot) => slot.status === "missing"
|
|
3766
|
+
).length;
|
|
3767
|
+
onTranslationSuccess?.({
|
|
3768
|
+
sourceLocale,
|
|
3769
|
+
targetLocale: activeLocale,
|
|
3770
|
+
mode: "automatic",
|
|
3771
|
+
updatedSlots: populated.report.updatedSlots,
|
|
3772
|
+
skippedSlots: populated.report.skippedSlots,
|
|
3773
|
+
missingSlotsCount
|
|
3774
|
+
});
|
|
3646
3775
|
return { success: true, report: populated.report };
|
|
3647
3776
|
} catch (cause) {
|
|
3648
3777
|
const workspaceError = {
|
|
@@ -3651,12 +3780,25 @@ function useTranslationWorkspace({
|
|
|
3651
3780
|
cause
|
|
3652
3781
|
};
|
|
3653
3782
|
setError(workspaceError);
|
|
3783
|
+
onTranslationError?.({ targetLocale: activeLocale, error: workspaceError });
|
|
3654
3784
|
return { success: false, error: workspaceError };
|
|
3655
3785
|
} finally {
|
|
3656
3786
|
setIsTranslating(false);
|
|
3657
3787
|
}
|
|
3658
3788
|
},
|
|
3659
|
-
[
|
|
3789
|
+
[
|
|
3790
|
+
activeLocale,
|
|
3791
|
+
commit,
|
|
3792
|
+
currentSchema,
|
|
3793
|
+
localeOptions,
|
|
3794
|
+
onTranslationError,
|
|
3795
|
+
onTranslationStart,
|
|
3796
|
+
onTranslationSuccess,
|
|
3797
|
+
readOnly,
|
|
3798
|
+
slots,
|
|
3799
|
+
sourceLocale,
|
|
3800
|
+
translationAdapter
|
|
3801
|
+
]
|
|
3660
3802
|
);
|
|
3661
3803
|
const translateSlot = (0, import_react5.useCallback)(
|
|
3662
3804
|
async (slot) => {
|
|
@@ -3674,7 +3816,15 @@ function useTranslationWorkspace({
|
|
|
3674
3816
|
setError(void 0);
|
|
3675
3817
|
try {
|
|
3676
3818
|
const text = await asAsyncAdapter(translationAdapter).translateText(slot.sourceText, slot.locale, sourceLocale);
|
|
3677
|
-
|
|
3819
|
+
const metadata = translationMetadata(slot.sourceText, sourceLocale, "automatic");
|
|
3820
|
+
commit(updateSchemaTranslation(currentSchema, slot, text, sourceLocale, "automatic"));
|
|
3821
|
+
onTranslationChange?.({
|
|
3822
|
+
slot,
|
|
3823
|
+
...slot.existingText === void 0 ? {} : { previousText: slot.existingText },
|
|
3824
|
+
nextText: text,
|
|
3825
|
+
mode: "automatic",
|
|
3826
|
+
metadata
|
|
3827
|
+
});
|
|
3678
3828
|
return { success: true };
|
|
3679
3829
|
} catch (cause) {
|
|
3680
3830
|
const workspaceError = {
|
|
@@ -3688,18 +3838,24 @@ function useTranslationWorkspace({
|
|
|
3688
3838
|
setIsTranslating(false);
|
|
3689
3839
|
}
|
|
3690
3840
|
},
|
|
3691
|
-
[commit, currentSchema, readOnly, sourceLocale, translationAdapter]
|
|
3841
|
+
[commit, currentSchema, onTranslationChange, readOnly, sourceLocale, translationAdapter]
|
|
3692
3842
|
);
|
|
3693
3843
|
return {
|
|
3694
3844
|
sourceLocale,
|
|
3695
3845
|
targetLocale: activeLocale,
|
|
3696
3846
|
targetLocales,
|
|
3697
|
-
|
|
3847
|
+
localeOptions,
|
|
3848
|
+
setTargetLocale: (locale) => {
|
|
3849
|
+
const normalized = (0, import_core5.normalizeLocale)(locale) ?? locale;
|
|
3850
|
+
setSelectedLocale(normalized);
|
|
3851
|
+
onLocaleChange?.(normalized);
|
|
3852
|
+
},
|
|
3698
3853
|
slots,
|
|
3699
3854
|
summary,
|
|
3700
3855
|
addLocale,
|
|
3701
3856
|
isAddLocaleAllowed,
|
|
3702
3857
|
removeLocale,
|
|
3858
|
+
...removeLocaleConfirmation === null ? {} : { removeLocaleConfirmation },
|
|
3703
3859
|
setTranslation,
|
|
3704
3860
|
translateAll,
|
|
3705
3861
|
translateSlot,
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as _form_engine_ts_core from '@form-engine-ts/core';
|
|
2
|
+
import { QuestionType, FormField, ChoiceOption, FormPage, FormSchema, DisplayCondition, JsonValue, SchemaIssue, FormPolicy, Question, FieldOption, TranslationReport, ValidationIssue, ValidationError, FormValues, LocaleOption, TranslationSlot, CanonicalTranslationMetadata, TranslationAdapter, AsyncTranslationAdapter, PopulateTranslationOptions, FormValue, AnswerValidationResult, FormEngineTranslator, FormEngineMessages, FieldType } from '@form-engine-ts/core';
|
|
2
3
|
export { QuestionType } from '@form-engine-ts/core';
|
|
3
4
|
import * as react from 'react';
|
|
4
5
|
import { ReactNode, ComponentType, KeyboardEvent, MouseEvent, CSSProperties } from 'react';
|
|
@@ -379,6 +380,76 @@ interface BuilderLocalizationSlotProps extends BuilderSlotBaseProps {
|
|
|
379
380
|
readonly policy?: FormPolicy;
|
|
380
381
|
readonly translationAdapterAvailable?: boolean;
|
|
381
382
|
}
|
|
383
|
+
interface TranslationWorkspaceHeaderProps {
|
|
384
|
+
readonly schema: FormSchema;
|
|
385
|
+
readonly sourceLocale: string;
|
|
386
|
+
readonly targetLocale: string;
|
|
387
|
+
readonly summary: {
|
|
388
|
+
readonly totalSlots: number;
|
|
389
|
+
readonly translatedCount: number;
|
|
390
|
+
readonly missingCount: number;
|
|
391
|
+
readonly staleCount: number;
|
|
392
|
+
readonly manualCount: number;
|
|
393
|
+
readonly completionPercentage: number;
|
|
394
|
+
};
|
|
395
|
+
readonly onTranslateAll: () => void;
|
|
396
|
+
readonly isTranslating: boolean;
|
|
397
|
+
readonly readOnly: boolean;
|
|
398
|
+
}
|
|
399
|
+
interface TranslationSlotRowProps {
|
|
400
|
+
readonly slot: _form_engine_ts_core.TranslationSlot;
|
|
401
|
+
readonly readOnly: boolean;
|
|
402
|
+
readonly onChange: (text: string) => void;
|
|
403
|
+
readonly onTranslate: () => void;
|
|
404
|
+
}
|
|
405
|
+
interface LocaleSelectorProps {
|
|
406
|
+
readonly targetLocale: string;
|
|
407
|
+
readonly targetLocales: readonly string[];
|
|
408
|
+
readonly localeOptions: readonly LocaleOption[];
|
|
409
|
+
readonly newLocale: string;
|
|
410
|
+
readonly readOnly: boolean;
|
|
411
|
+
readonly onTargetLocaleChange: (locale: string) => void;
|
|
412
|
+
readonly onNewLocaleChange: (locale: string) => void;
|
|
413
|
+
readonly onAddLocale: () => void;
|
|
414
|
+
}
|
|
415
|
+
interface TranslationWorkspaceActionsProps {
|
|
416
|
+
readonly onTranslateAll: () => void;
|
|
417
|
+
readonly isTranslating: boolean;
|
|
418
|
+
readonly readOnly: boolean;
|
|
419
|
+
}
|
|
420
|
+
interface TranslationEventPayload {
|
|
421
|
+
readonly sourceLocale: string;
|
|
422
|
+
readonly targetLocale: string;
|
|
423
|
+
readonly mode: "manual" | "automatic";
|
|
424
|
+
readonly updatedSlots: readonly TranslationSlot[];
|
|
425
|
+
readonly skippedSlots: readonly TranslationSlot[];
|
|
426
|
+
readonly missingSlotsCount: number;
|
|
427
|
+
}
|
|
428
|
+
interface TranslationSlotChangeEvent {
|
|
429
|
+
readonly slot: TranslationSlot;
|
|
430
|
+
readonly previousText?: string;
|
|
431
|
+
readonly nextText: string;
|
|
432
|
+
readonly mode: "manual" | "automatic";
|
|
433
|
+
readonly metadata: CanonicalTranslationMetadata;
|
|
434
|
+
}
|
|
435
|
+
interface ConfirmRemoveLocaleSlotProps {
|
|
436
|
+
readonly locale: string;
|
|
437
|
+
readonly localeLabel: string;
|
|
438
|
+
readonly translatedSlotsCount: number;
|
|
439
|
+
readonly isOpen: boolean;
|
|
440
|
+
readonly onConfirm: () => void;
|
|
441
|
+
readonly onCancel: () => void;
|
|
442
|
+
}
|
|
443
|
+
interface TranslationWorkspaceSlots {
|
|
444
|
+
readonly renderHeader?: (props: TranslationWorkspaceHeaderProps) => ReactNode;
|
|
445
|
+
readonly renderSlotRow?: (props: TranslationSlotRowProps) => ReactNode;
|
|
446
|
+
readonly renderLocaleSelector?: (props: LocaleSelectorProps) => ReactNode;
|
|
447
|
+
readonly renderStatusBadge?: (props: {
|
|
448
|
+
readonly status: _form_engine_ts_core.TranslationStatus;
|
|
449
|
+
}) => ReactNode;
|
|
450
|
+
readonly renderActions?: (props: TranslationWorkspaceActionsProps) => ReactNode;
|
|
451
|
+
readonly confirmRemoveLocale?: (props: ConfirmRemoveLocaleSlotProps) => ReactNode;
|
|
452
|
+
}
|
|
382
453
|
interface LocalizationSummaryContext {
|
|
383
454
|
readonly defaultLocale: string;
|
|
384
455
|
readonly supportedLocales: readonly string[];
|
|
@@ -708,9 +779,25 @@ interface UseTranslationWorkspaceOptions {
|
|
|
708
779
|
readonly translationAdapter?: TranslationAdapter | AsyncTranslationAdapter;
|
|
709
780
|
readonly readOnly?: boolean;
|
|
710
781
|
readonly policy?: FormPolicy;
|
|
782
|
+
readonly availableLocales?: readonly (string | LocaleOption)[];
|
|
783
|
+
readonly onLocaleAdded?: (locale: string) => void;
|
|
784
|
+
readonly onLocaleRemoved?: (locale: string) => void;
|
|
785
|
+
readonly onLocaleChange?: (targetLocale: string) => void;
|
|
711
786
|
readonly beforeRemoveLocale?: (locale: string, context: {
|
|
712
787
|
readonly slotCount: number;
|
|
713
788
|
}) => Promise<boolean> | boolean;
|
|
789
|
+
readonly confirmRemoveLocale?: (props: ConfirmRemoveLocaleSlotProps) => ReactNode;
|
|
790
|
+
readonly slots?: Pick<TranslationWorkspaceSlots, "confirmRemoveLocale">;
|
|
791
|
+
readonly onTranslationStart?: (params: {
|
|
792
|
+
readonly targetLocale: string;
|
|
793
|
+
readonly mode: "manual" | "automatic";
|
|
794
|
+
}) => void;
|
|
795
|
+
readonly onTranslationSuccess?: (payload: TranslationEventPayload) => void;
|
|
796
|
+
readonly onTranslationError?: (params: {
|
|
797
|
+
readonly targetLocale: string;
|
|
798
|
+
readonly error: TranslationWorkspaceError;
|
|
799
|
+
}) => void;
|
|
800
|
+
readonly onTranslationChange?: (event: TranslationSlotChangeEvent) => void;
|
|
714
801
|
readonly validateLocale?: ((locale: string, currentLocales: readonly string[]) => LocaleValidationResult) | CustomLocaleValidator;
|
|
715
802
|
}
|
|
716
803
|
interface LocaleValidationContext {
|
|
@@ -766,6 +853,7 @@ interface UseTranslationWorkspaceResult {
|
|
|
766
853
|
readonly sourceLocale: string;
|
|
767
854
|
readonly targetLocale: string;
|
|
768
855
|
readonly targetLocales: readonly string[];
|
|
856
|
+
readonly localeOptions: readonly LocaleOption[];
|
|
769
857
|
readonly setTargetLocale: (locale: string) => void;
|
|
770
858
|
readonly slots: readonly TranslationSlot[];
|
|
771
859
|
readonly summary: TranslationSummary;
|
|
@@ -775,6 +863,7 @@ interface UseTranslationWorkspaceResult {
|
|
|
775
863
|
};
|
|
776
864
|
readonly isAddLocaleAllowed: (locale: string) => boolean;
|
|
777
865
|
readonly removeLocale: (locale: string) => boolean | Promise<boolean>;
|
|
866
|
+
readonly removeLocaleConfirmation?: ReactNode;
|
|
778
867
|
readonly setTranslation: (slot: TranslationSlot, text: string) => void;
|
|
779
868
|
readonly translateAll: (options?: PopulateTranslationOptions) => Promise<{
|
|
780
869
|
readonly success: boolean;
|
|
@@ -788,8 +877,8 @@ interface UseTranslationWorkspaceResult {
|
|
|
788
877
|
readonly isTranslating: boolean;
|
|
789
878
|
readonly error?: TranslationWorkspaceError;
|
|
790
879
|
}
|
|
791
|
-
declare const validateLocalePipeline: (locale: string, schema: FormSchema, policy?: FormPolicy, customValidator?: ((locale: string, currentLocales: readonly string[]) => LocaleValidationResult) | CustomLocaleValidator) => LocaleValidationResult;
|
|
792
|
-
declare function useTranslationWorkspace({ schema, onChange, sourceLocale, targetLocale, translationAdapter, readOnly, policy, beforeRemoveLocale, validateLocale }: UseTranslationWorkspaceOptions): UseTranslationWorkspaceResult;
|
|
880
|
+
declare const validateLocalePipeline: (locale: string, schema: FormSchema, policy?: FormPolicy, customValidator?: ((locale: string, currentLocales: readonly string[]) => LocaleValidationResult) | CustomLocaleValidator, availableLocales?: readonly (string | LocaleOption)[]) => LocaleValidationResult;
|
|
881
|
+
declare function useTranslationWorkspace({ schema, onChange, sourceLocale, targetLocale, translationAdapter, readOnly, policy, availableLocales, onLocaleAdded, onLocaleRemoved, onLocaleChange, beforeRemoveLocale, confirmRemoveLocale, slots: workspaceSlots, onTranslationStart, onTranslationSuccess, onTranslationError, onTranslationChange, validateLocale }: UseTranslationWorkspaceOptions): UseTranslationWorkspaceResult;
|
|
793
882
|
|
|
794
883
|
declare const BUILDER_TRANSLATION_KEYS: {
|
|
795
884
|
readonly ADD_FIELD: "builder.actions.addField";
|
|
@@ -879,4 +968,4 @@ interface StandaloneFormRendererProps extends FormRendererPresentationProps {
|
|
|
879
968
|
type FormRendererProps = FormRendererPresentationProps | StandaloneFormRendererProps;
|
|
880
969
|
declare function FormRenderer(props: FormRendererProps): react.JSX.Element;
|
|
881
970
|
|
|
882
|
-
export { BUILDER_TRANSLATION_ALIASES, BUILDER_TRANSLATION_KEYS, type BeforeSubmit, type BuilderActionContext, type BuilderActionError, type BuilderActionIconType, type BuilderActionResult, type BuilderButtonProps, type BuilderCheckboxProps, type BuilderErrorMessageProps, type BuilderFactories, type BuilderFieldEditorSlotProps, type BuilderFieldsetProps, type BuilderIconButtonProps, type BuilderIdKind, type BuilderLocalizationSlotProps, type BuilderOptionEditorSlotProps, type BuilderPagesSlotProps, type BuilderPolicy, type BuilderSectionProps, type BuilderSelectOption, type BuilderSelectProps, type BuilderSlotActions, type BuilderTextAreaProps, type BuilderTextInputProps, type BuilderTextTarget, type BuilderToolbarSlotProps, type BuilderTranslationActionsSlotProps, type BuilderTranslationKey, type ChoiceFieldLayoutMode, type ChoiceFieldTypeLayoutMap, type ChoiceGroupSlotProps, type ComponentBaseProps, type CustomLocaleValidator, type FieldComponentProps, type FieldComponents, type FieldEditorControlsConfig, type FieldEditorHeaderSlotProps, type FieldEditorMode, type FieldError, type FieldPropertyControlMode, type FieldState, type FieldTypeSelectOptionsConfig, type FieldTypeSelectOptionsContext, type FieldTypeSelectOptionsSorter, type FieldTypeSelectOptionsTransformer, type FieldTypeSelectSlotProps, FormBuilder, type FormBuilderActions, type FormBuilderComponents, type FormBuilderFeatures, type FormBuilderOptions, type FormBuilderProps, type FormBuilderResult, type FormBuilderSectionName, type FormBuilderSlots, type FormBuilderSubmissionSettingsOptions, type FormCompletionSlotProps, type FormContextValue, FormEngineI18nContext, type FormEngineI18nContextValue, FormEngineI18nProvider, type FormEngineI18nProviderProps, type FormFieldsSlotProps, FormProvider, type FormProviderProps, FormRenderer, type FormRendererAppearance, type FormRendererMessages, type FormRendererPresentationProps, type FormRendererProps, type FormRendererSlotProps, type FormRendererSlots, type FormServerErrorPayload, FormSubmissionError, type FormSubmitHandler, type FormSubmitState, type FormSubmitStatus, type FormSubmittedAnswerItem, type FormSuccessRenderMode, type IconButtonProps, type InputComponentProps, type LocaleValidationContext, type LocaleValidationResult, type LocalizationSummaryContext, type ManualTranslationContext, type ManualTranslationTarget, type RenderSubmitButtonProps, type SelectComponentProps, type StandaloneFormRendererProps, type SubmissionAttempt, type SubmissionAttemptStore, type SubmissionConfirmationOptions, type SubmissionConfirmationRenderMode, type SubmissionConfirmationSlotProps, type SubmissionGuard, type SubmissionGuardResult, type SubmissionProtectionProps, type SubmissionReceipt, type SubmissionReceiptQuery, type SubmissionReceiptStore, type SubmitContext, type SubmitResponse, type SubmitResult, type SubmitStatus, type TranslationSummary, type TranslationWorkspaceError, type UseFormBuilderOptions, type UseFormBuilderResult, type UseSubmissionReceiptsResult, type UseTranslationWorkspaceOptions, type UseTranslationWorkspaceResult, createLocalStorageSubmissionAttemptStore, createLocalStorageSubmissionReceiptStore, isTranslationUnresolved, resolveChoiceFieldLayout, resolveFieldEditorControls, resolveFieldTypeSelectOptions, resolveInitialFieldType, resolveTranslation, submissionReceiptQueryKey, useField, useForm, useFormBuilder, useFormEngineI18n, useSubmissionReceipts, useTranslationWorkspace, validateLocalePipeline };
|
|
971
|
+
export { BUILDER_TRANSLATION_ALIASES, BUILDER_TRANSLATION_KEYS, type BeforeSubmit, type BuilderActionContext, type BuilderActionError, type BuilderActionIconType, type BuilderActionResult, type BuilderButtonProps, type BuilderCheckboxProps, type BuilderErrorMessageProps, type BuilderFactories, type BuilderFieldEditorSlotProps, type BuilderFieldsetProps, type BuilderIconButtonProps, type BuilderIdKind, type BuilderLocalizationSlotProps, type BuilderOptionEditorSlotProps, type BuilderPagesSlotProps, type BuilderPolicy, type BuilderSectionProps, type BuilderSelectOption, type BuilderSelectProps, type BuilderSlotActions, type BuilderTextAreaProps, type BuilderTextInputProps, type BuilderTextTarget, type BuilderToolbarSlotProps, type BuilderTranslationActionsSlotProps, type BuilderTranslationKey, type ChoiceFieldLayoutMode, type ChoiceFieldTypeLayoutMap, type ChoiceGroupSlotProps, type ComponentBaseProps, type ConfirmRemoveLocaleSlotProps, type CustomLocaleValidator, type FieldComponentProps, type FieldComponents, type FieldEditorControlsConfig, type FieldEditorHeaderSlotProps, type FieldEditorMode, type FieldError, type FieldPropertyControlMode, type FieldState, type FieldTypeSelectOptionsConfig, type FieldTypeSelectOptionsContext, type FieldTypeSelectOptionsSorter, type FieldTypeSelectOptionsTransformer, type FieldTypeSelectSlotProps, FormBuilder, type FormBuilderActions, type FormBuilderComponents, type FormBuilderFeatures, type FormBuilderOptions, type FormBuilderProps, type FormBuilderResult, type FormBuilderSectionName, type FormBuilderSlots, type FormBuilderSubmissionSettingsOptions, type FormCompletionSlotProps, type FormContextValue, FormEngineI18nContext, type FormEngineI18nContextValue, FormEngineI18nProvider, type FormEngineI18nProviderProps, type FormFieldsSlotProps, FormProvider, type FormProviderProps, FormRenderer, type FormRendererAppearance, type FormRendererMessages, type FormRendererPresentationProps, type FormRendererProps, type FormRendererSlotProps, type FormRendererSlots, type FormServerErrorPayload, FormSubmissionError, type FormSubmitHandler, type FormSubmitState, type FormSubmitStatus, type FormSubmittedAnswerItem, type FormSuccessRenderMode, type IconButtonProps, type InputComponentProps, type LocaleSelectorProps, type LocaleValidationContext, type LocaleValidationResult, type LocalizationSummaryContext, type ManualTranslationContext, type ManualTranslationTarget, type RenderSubmitButtonProps, type SelectComponentProps, type StandaloneFormRendererProps, type SubmissionAttempt, type SubmissionAttemptStore, type SubmissionConfirmationOptions, type SubmissionConfirmationRenderMode, type SubmissionConfirmationSlotProps, type SubmissionGuard, type SubmissionGuardResult, type SubmissionProtectionProps, type SubmissionReceipt, type SubmissionReceiptQuery, type SubmissionReceiptStore, type SubmitContext, type SubmitResponse, type SubmitResult, type SubmitStatus, type TranslationEventPayload, type TranslationSlotChangeEvent, type TranslationSlotRowProps, type TranslationSummary, type TranslationWorkspaceActionsProps, type TranslationWorkspaceError, type TranslationWorkspaceHeaderProps, type TranslationWorkspaceSlots, type UseFormBuilderOptions, type UseFormBuilderResult, type UseSubmissionReceiptsResult, type UseTranslationWorkspaceOptions, type UseTranslationWorkspaceResult, createLocalStorageSubmissionAttemptStore, createLocalStorageSubmissionReceiptStore, isTranslationUnresolved, resolveChoiceFieldLayout, resolveFieldEditorControls, resolveFieldTypeSelectOptions, resolveInitialFieldType, resolveTranslation, submissionReceiptQueryKey, useField, useForm, useFormBuilder, useFormEngineI18n, useSubmissionReceipts, useTranslationWorkspace, validateLocalePipeline };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as _form_engine_ts_core from '@form-engine-ts/core';
|
|
2
|
+
import { QuestionType, FormField, ChoiceOption, FormPage, FormSchema, DisplayCondition, JsonValue, SchemaIssue, FormPolicy, Question, FieldOption, TranslationReport, ValidationIssue, ValidationError, FormValues, LocaleOption, TranslationSlot, CanonicalTranslationMetadata, TranslationAdapter, AsyncTranslationAdapter, PopulateTranslationOptions, FormValue, AnswerValidationResult, FormEngineTranslator, FormEngineMessages, FieldType } from '@form-engine-ts/core';
|
|
2
3
|
export { QuestionType } from '@form-engine-ts/core';
|
|
3
4
|
import * as react from 'react';
|
|
4
5
|
import { ReactNode, ComponentType, KeyboardEvent, MouseEvent, CSSProperties } from 'react';
|
|
@@ -379,6 +380,76 @@ interface BuilderLocalizationSlotProps extends BuilderSlotBaseProps {
|
|
|
379
380
|
readonly policy?: FormPolicy;
|
|
380
381
|
readonly translationAdapterAvailable?: boolean;
|
|
381
382
|
}
|
|
383
|
+
interface TranslationWorkspaceHeaderProps {
|
|
384
|
+
readonly schema: FormSchema;
|
|
385
|
+
readonly sourceLocale: string;
|
|
386
|
+
readonly targetLocale: string;
|
|
387
|
+
readonly summary: {
|
|
388
|
+
readonly totalSlots: number;
|
|
389
|
+
readonly translatedCount: number;
|
|
390
|
+
readonly missingCount: number;
|
|
391
|
+
readonly staleCount: number;
|
|
392
|
+
readonly manualCount: number;
|
|
393
|
+
readonly completionPercentage: number;
|
|
394
|
+
};
|
|
395
|
+
readonly onTranslateAll: () => void;
|
|
396
|
+
readonly isTranslating: boolean;
|
|
397
|
+
readonly readOnly: boolean;
|
|
398
|
+
}
|
|
399
|
+
interface TranslationSlotRowProps {
|
|
400
|
+
readonly slot: _form_engine_ts_core.TranslationSlot;
|
|
401
|
+
readonly readOnly: boolean;
|
|
402
|
+
readonly onChange: (text: string) => void;
|
|
403
|
+
readonly onTranslate: () => void;
|
|
404
|
+
}
|
|
405
|
+
interface LocaleSelectorProps {
|
|
406
|
+
readonly targetLocale: string;
|
|
407
|
+
readonly targetLocales: readonly string[];
|
|
408
|
+
readonly localeOptions: readonly LocaleOption[];
|
|
409
|
+
readonly newLocale: string;
|
|
410
|
+
readonly readOnly: boolean;
|
|
411
|
+
readonly onTargetLocaleChange: (locale: string) => void;
|
|
412
|
+
readonly onNewLocaleChange: (locale: string) => void;
|
|
413
|
+
readonly onAddLocale: () => void;
|
|
414
|
+
}
|
|
415
|
+
interface TranslationWorkspaceActionsProps {
|
|
416
|
+
readonly onTranslateAll: () => void;
|
|
417
|
+
readonly isTranslating: boolean;
|
|
418
|
+
readonly readOnly: boolean;
|
|
419
|
+
}
|
|
420
|
+
interface TranslationEventPayload {
|
|
421
|
+
readonly sourceLocale: string;
|
|
422
|
+
readonly targetLocale: string;
|
|
423
|
+
readonly mode: "manual" | "automatic";
|
|
424
|
+
readonly updatedSlots: readonly TranslationSlot[];
|
|
425
|
+
readonly skippedSlots: readonly TranslationSlot[];
|
|
426
|
+
readonly missingSlotsCount: number;
|
|
427
|
+
}
|
|
428
|
+
interface TranslationSlotChangeEvent {
|
|
429
|
+
readonly slot: TranslationSlot;
|
|
430
|
+
readonly previousText?: string;
|
|
431
|
+
readonly nextText: string;
|
|
432
|
+
readonly mode: "manual" | "automatic";
|
|
433
|
+
readonly metadata: CanonicalTranslationMetadata;
|
|
434
|
+
}
|
|
435
|
+
interface ConfirmRemoveLocaleSlotProps {
|
|
436
|
+
readonly locale: string;
|
|
437
|
+
readonly localeLabel: string;
|
|
438
|
+
readonly translatedSlotsCount: number;
|
|
439
|
+
readonly isOpen: boolean;
|
|
440
|
+
readonly onConfirm: () => void;
|
|
441
|
+
readonly onCancel: () => void;
|
|
442
|
+
}
|
|
443
|
+
interface TranslationWorkspaceSlots {
|
|
444
|
+
readonly renderHeader?: (props: TranslationWorkspaceHeaderProps) => ReactNode;
|
|
445
|
+
readonly renderSlotRow?: (props: TranslationSlotRowProps) => ReactNode;
|
|
446
|
+
readonly renderLocaleSelector?: (props: LocaleSelectorProps) => ReactNode;
|
|
447
|
+
readonly renderStatusBadge?: (props: {
|
|
448
|
+
readonly status: _form_engine_ts_core.TranslationStatus;
|
|
449
|
+
}) => ReactNode;
|
|
450
|
+
readonly renderActions?: (props: TranslationWorkspaceActionsProps) => ReactNode;
|
|
451
|
+
readonly confirmRemoveLocale?: (props: ConfirmRemoveLocaleSlotProps) => ReactNode;
|
|
452
|
+
}
|
|
382
453
|
interface LocalizationSummaryContext {
|
|
383
454
|
readonly defaultLocale: string;
|
|
384
455
|
readonly supportedLocales: readonly string[];
|
|
@@ -708,9 +779,25 @@ interface UseTranslationWorkspaceOptions {
|
|
|
708
779
|
readonly translationAdapter?: TranslationAdapter | AsyncTranslationAdapter;
|
|
709
780
|
readonly readOnly?: boolean;
|
|
710
781
|
readonly policy?: FormPolicy;
|
|
782
|
+
readonly availableLocales?: readonly (string | LocaleOption)[];
|
|
783
|
+
readonly onLocaleAdded?: (locale: string) => void;
|
|
784
|
+
readonly onLocaleRemoved?: (locale: string) => void;
|
|
785
|
+
readonly onLocaleChange?: (targetLocale: string) => void;
|
|
711
786
|
readonly beforeRemoveLocale?: (locale: string, context: {
|
|
712
787
|
readonly slotCount: number;
|
|
713
788
|
}) => Promise<boolean> | boolean;
|
|
789
|
+
readonly confirmRemoveLocale?: (props: ConfirmRemoveLocaleSlotProps) => ReactNode;
|
|
790
|
+
readonly slots?: Pick<TranslationWorkspaceSlots, "confirmRemoveLocale">;
|
|
791
|
+
readonly onTranslationStart?: (params: {
|
|
792
|
+
readonly targetLocale: string;
|
|
793
|
+
readonly mode: "manual" | "automatic";
|
|
794
|
+
}) => void;
|
|
795
|
+
readonly onTranslationSuccess?: (payload: TranslationEventPayload) => void;
|
|
796
|
+
readonly onTranslationError?: (params: {
|
|
797
|
+
readonly targetLocale: string;
|
|
798
|
+
readonly error: TranslationWorkspaceError;
|
|
799
|
+
}) => void;
|
|
800
|
+
readonly onTranslationChange?: (event: TranslationSlotChangeEvent) => void;
|
|
714
801
|
readonly validateLocale?: ((locale: string, currentLocales: readonly string[]) => LocaleValidationResult) | CustomLocaleValidator;
|
|
715
802
|
}
|
|
716
803
|
interface LocaleValidationContext {
|
|
@@ -766,6 +853,7 @@ interface UseTranslationWorkspaceResult {
|
|
|
766
853
|
readonly sourceLocale: string;
|
|
767
854
|
readonly targetLocale: string;
|
|
768
855
|
readonly targetLocales: readonly string[];
|
|
856
|
+
readonly localeOptions: readonly LocaleOption[];
|
|
769
857
|
readonly setTargetLocale: (locale: string) => void;
|
|
770
858
|
readonly slots: readonly TranslationSlot[];
|
|
771
859
|
readonly summary: TranslationSummary;
|
|
@@ -775,6 +863,7 @@ interface UseTranslationWorkspaceResult {
|
|
|
775
863
|
};
|
|
776
864
|
readonly isAddLocaleAllowed: (locale: string) => boolean;
|
|
777
865
|
readonly removeLocale: (locale: string) => boolean | Promise<boolean>;
|
|
866
|
+
readonly removeLocaleConfirmation?: ReactNode;
|
|
778
867
|
readonly setTranslation: (slot: TranslationSlot, text: string) => void;
|
|
779
868
|
readonly translateAll: (options?: PopulateTranslationOptions) => Promise<{
|
|
780
869
|
readonly success: boolean;
|
|
@@ -788,8 +877,8 @@ interface UseTranslationWorkspaceResult {
|
|
|
788
877
|
readonly isTranslating: boolean;
|
|
789
878
|
readonly error?: TranslationWorkspaceError;
|
|
790
879
|
}
|
|
791
|
-
declare const validateLocalePipeline: (locale: string, schema: FormSchema, policy?: FormPolicy, customValidator?: ((locale: string, currentLocales: readonly string[]) => LocaleValidationResult) | CustomLocaleValidator) => LocaleValidationResult;
|
|
792
|
-
declare function useTranslationWorkspace({ schema, onChange, sourceLocale, targetLocale, translationAdapter, readOnly, policy, beforeRemoveLocale, validateLocale }: UseTranslationWorkspaceOptions): UseTranslationWorkspaceResult;
|
|
880
|
+
declare const validateLocalePipeline: (locale: string, schema: FormSchema, policy?: FormPolicy, customValidator?: ((locale: string, currentLocales: readonly string[]) => LocaleValidationResult) | CustomLocaleValidator, availableLocales?: readonly (string | LocaleOption)[]) => LocaleValidationResult;
|
|
881
|
+
declare function useTranslationWorkspace({ schema, onChange, sourceLocale, targetLocale, translationAdapter, readOnly, policy, availableLocales, onLocaleAdded, onLocaleRemoved, onLocaleChange, beforeRemoveLocale, confirmRemoveLocale, slots: workspaceSlots, onTranslationStart, onTranslationSuccess, onTranslationError, onTranslationChange, validateLocale }: UseTranslationWorkspaceOptions): UseTranslationWorkspaceResult;
|
|
793
882
|
|
|
794
883
|
declare const BUILDER_TRANSLATION_KEYS: {
|
|
795
884
|
readonly ADD_FIELD: "builder.actions.addField";
|
|
@@ -879,4 +968,4 @@ interface StandaloneFormRendererProps extends FormRendererPresentationProps {
|
|
|
879
968
|
type FormRendererProps = FormRendererPresentationProps | StandaloneFormRendererProps;
|
|
880
969
|
declare function FormRenderer(props: FormRendererProps): react.JSX.Element;
|
|
881
970
|
|
|
882
|
-
export { BUILDER_TRANSLATION_ALIASES, BUILDER_TRANSLATION_KEYS, type BeforeSubmit, type BuilderActionContext, type BuilderActionError, type BuilderActionIconType, type BuilderActionResult, type BuilderButtonProps, type BuilderCheckboxProps, type BuilderErrorMessageProps, type BuilderFactories, type BuilderFieldEditorSlotProps, type BuilderFieldsetProps, type BuilderIconButtonProps, type BuilderIdKind, type BuilderLocalizationSlotProps, type BuilderOptionEditorSlotProps, type BuilderPagesSlotProps, type BuilderPolicy, type BuilderSectionProps, type BuilderSelectOption, type BuilderSelectProps, type BuilderSlotActions, type BuilderTextAreaProps, type BuilderTextInputProps, type BuilderTextTarget, type BuilderToolbarSlotProps, type BuilderTranslationActionsSlotProps, type BuilderTranslationKey, type ChoiceFieldLayoutMode, type ChoiceFieldTypeLayoutMap, type ChoiceGroupSlotProps, type ComponentBaseProps, type CustomLocaleValidator, type FieldComponentProps, type FieldComponents, type FieldEditorControlsConfig, type FieldEditorHeaderSlotProps, type FieldEditorMode, type FieldError, type FieldPropertyControlMode, type FieldState, type FieldTypeSelectOptionsConfig, type FieldTypeSelectOptionsContext, type FieldTypeSelectOptionsSorter, type FieldTypeSelectOptionsTransformer, type FieldTypeSelectSlotProps, FormBuilder, type FormBuilderActions, type FormBuilderComponents, type FormBuilderFeatures, type FormBuilderOptions, type FormBuilderProps, type FormBuilderResult, type FormBuilderSectionName, type FormBuilderSlots, type FormBuilderSubmissionSettingsOptions, type FormCompletionSlotProps, type FormContextValue, FormEngineI18nContext, type FormEngineI18nContextValue, FormEngineI18nProvider, type FormEngineI18nProviderProps, type FormFieldsSlotProps, FormProvider, type FormProviderProps, FormRenderer, type FormRendererAppearance, type FormRendererMessages, type FormRendererPresentationProps, type FormRendererProps, type FormRendererSlotProps, type FormRendererSlots, type FormServerErrorPayload, FormSubmissionError, type FormSubmitHandler, type FormSubmitState, type FormSubmitStatus, type FormSubmittedAnswerItem, type FormSuccessRenderMode, type IconButtonProps, type InputComponentProps, type LocaleValidationContext, type LocaleValidationResult, type LocalizationSummaryContext, type ManualTranslationContext, type ManualTranslationTarget, type RenderSubmitButtonProps, type SelectComponentProps, type StandaloneFormRendererProps, type SubmissionAttempt, type SubmissionAttemptStore, type SubmissionConfirmationOptions, type SubmissionConfirmationRenderMode, type SubmissionConfirmationSlotProps, type SubmissionGuard, type SubmissionGuardResult, type SubmissionProtectionProps, type SubmissionReceipt, type SubmissionReceiptQuery, type SubmissionReceiptStore, type SubmitContext, type SubmitResponse, type SubmitResult, type SubmitStatus, type TranslationSummary, type TranslationWorkspaceError, type UseFormBuilderOptions, type UseFormBuilderResult, type UseSubmissionReceiptsResult, type UseTranslationWorkspaceOptions, type UseTranslationWorkspaceResult, createLocalStorageSubmissionAttemptStore, createLocalStorageSubmissionReceiptStore, isTranslationUnresolved, resolveChoiceFieldLayout, resolveFieldEditorControls, resolveFieldTypeSelectOptions, resolveInitialFieldType, resolveTranslation, submissionReceiptQueryKey, useField, useForm, useFormBuilder, useFormEngineI18n, useSubmissionReceipts, useTranslationWorkspace, validateLocalePipeline };
|
|
971
|
+
export { BUILDER_TRANSLATION_ALIASES, BUILDER_TRANSLATION_KEYS, type BeforeSubmit, type BuilderActionContext, type BuilderActionError, type BuilderActionIconType, type BuilderActionResult, type BuilderButtonProps, type BuilderCheckboxProps, type BuilderErrorMessageProps, type BuilderFactories, type BuilderFieldEditorSlotProps, type BuilderFieldsetProps, type BuilderIconButtonProps, type BuilderIdKind, type BuilderLocalizationSlotProps, type BuilderOptionEditorSlotProps, type BuilderPagesSlotProps, type BuilderPolicy, type BuilderSectionProps, type BuilderSelectOption, type BuilderSelectProps, type BuilderSlotActions, type BuilderTextAreaProps, type BuilderTextInputProps, type BuilderTextTarget, type BuilderToolbarSlotProps, type BuilderTranslationActionsSlotProps, type BuilderTranslationKey, type ChoiceFieldLayoutMode, type ChoiceFieldTypeLayoutMap, type ChoiceGroupSlotProps, type ComponentBaseProps, type ConfirmRemoveLocaleSlotProps, type CustomLocaleValidator, type FieldComponentProps, type FieldComponents, type FieldEditorControlsConfig, type FieldEditorHeaderSlotProps, type FieldEditorMode, type FieldError, type FieldPropertyControlMode, type FieldState, type FieldTypeSelectOptionsConfig, type FieldTypeSelectOptionsContext, type FieldTypeSelectOptionsSorter, type FieldTypeSelectOptionsTransformer, type FieldTypeSelectSlotProps, FormBuilder, type FormBuilderActions, type FormBuilderComponents, type FormBuilderFeatures, type FormBuilderOptions, type FormBuilderProps, type FormBuilderResult, type FormBuilderSectionName, type FormBuilderSlots, type FormBuilderSubmissionSettingsOptions, type FormCompletionSlotProps, type FormContextValue, FormEngineI18nContext, type FormEngineI18nContextValue, FormEngineI18nProvider, type FormEngineI18nProviderProps, type FormFieldsSlotProps, FormProvider, type FormProviderProps, FormRenderer, type FormRendererAppearance, type FormRendererMessages, type FormRendererPresentationProps, type FormRendererProps, type FormRendererSlotProps, type FormRendererSlots, type FormServerErrorPayload, FormSubmissionError, type FormSubmitHandler, type FormSubmitState, type FormSubmitStatus, type FormSubmittedAnswerItem, type FormSuccessRenderMode, type IconButtonProps, type InputComponentProps, type LocaleSelectorProps, type LocaleValidationContext, type LocaleValidationResult, type LocalizationSummaryContext, type ManualTranslationContext, type ManualTranslationTarget, type RenderSubmitButtonProps, type SelectComponentProps, type StandaloneFormRendererProps, type SubmissionAttempt, type SubmissionAttemptStore, type SubmissionConfirmationOptions, type SubmissionConfirmationRenderMode, type SubmissionConfirmationSlotProps, type SubmissionGuard, type SubmissionGuardResult, type SubmissionProtectionProps, type SubmissionReceipt, type SubmissionReceiptQuery, type SubmissionReceiptStore, type SubmitContext, type SubmitResponse, type SubmitResult, type SubmitStatus, type TranslationEventPayload, type TranslationSlotChangeEvent, type TranslationSlotRowProps, type TranslationSummary, type TranslationWorkspaceActionsProps, type TranslationWorkspaceError, type TranslationWorkspaceHeaderProps, type TranslationWorkspaceSlots, type UseFormBuilderOptions, type UseFormBuilderResult, type UseSubmissionReceiptsResult, type UseTranslationWorkspaceOptions, type UseTranslationWorkspaceResult, createLocalStorageSubmissionAttemptStore, createLocalStorageSubmissionReceiptStore, isTranslationUnresolved, resolveChoiceFieldLayout, resolveFieldEditorControls, resolveFieldTypeSelectOptions, resolveInitialFieldType, resolveTranslation, submissionReceiptQueryKey, useField, useForm, useFormBuilder, useFormEngineI18n, useSubmissionReceipts, useTranslationWorkspace, validateLocalePipeline };
|
package/dist/index.js
CHANGED
|
@@ -3282,7 +3282,7 @@ import {
|
|
|
3282
3282
|
populateSchemaTranslations as populateSchemaTranslations2,
|
|
3283
3283
|
removeLocaleFromSchema
|
|
3284
3284
|
} from "@form-engine-ts/core";
|
|
3285
|
-
import { useCallback as useCallback3, useMemo as useMemo4, useState as useState4 } from "react";
|
|
3285
|
+
import { useCallback as useCallback3, useMemo as useMemo4, useRef as useRef2, useState as useState4 } from "react";
|
|
3286
3286
|
function updateTranslationMap(translations, locale, property, text) {
|
|
3287
3287
|
if (property === "label") return translations ?? {};
|
|
3288
3288
|
const current = translations?.[locale];
|
|
@@ -3298,7 +3298,7 @@ function asAsyncAdapter(adapter) {
|
|
|
3298
3298
|
)
|
|
3299
3299
|
};
|
|
3300
3300
|
}
|
|
3301
|
-
var validateLocalePipeline = (locale, schema, policy, customValidator) => {
|
|
3301
|
+
var validateLocalePipeline = (locale, schema, policy, customValidator, availableLocales) => {
|
|
3302
3302
|
const canonicalLocale = normalizeLocale(locale);
|
|
3303
3303
|
if (canonicalLocale === null) {
|
|
3304
3304
|
return {
|
|
@@ -3311,6 +3311,18 @@ var validateLocalePipeline = (locale, schema, policy, customValidator) => {
|
|
|
3311
3311
|
}
|
|
3312
3312
|
const currentLocales = (schema.supportedLocales ?? []).map((candidate) => normalizeLocale(candidate) ?? candidate);
|
|
3313
3313
|
const defaultLocale = schema.defaultLocale === void 0 ? "" : normalizeLocale(schema.defaultLocale) ?? schema.defaultLocale;
|
|
3314
|
+
if (availableLocales !== void 0 && !availableLocales.some((candidate) => {
|
|
3315
|
+
const value = typeof candidate === "string" ? candidate : candidate.locale;
|
|
3316
|
+
return normalizeLocale(value) === canonicalLocale;
|
|
3317
|
+
})) {
|
|
3318
|
+
return {
|
|
3319
|
+
valid: false,
|
|
3320
|
+
error: {
|
|
3321
|
+
type: "locale_not_allowed",
|
|
3322
|
+
message: `Locale "${canonicalLocale}" is not available in the locale catalog.`
|
|
3323
|
+
}
|
|
3324
|
+
};
|
|
3325
|
+
}
|
|
3314
3326
|
if (canonicalLocale === defaultLocale || currentLocales.includes(canonicalLocale)) {
|
|
3315
3327
|
return {
|
|
3316
3328
|
valid: false,
|
|
@@ -3382,26 +3394,27 @@ function workspaceLocaleValidationError(validation, currentLocales, requestedLoc
|
|
|
3382
3394
|
}
|
|
3383
3395
|
return { type: "custom_validation_failed", message: error.message };
|
|
3384
3396
|
}
|
|
3385
|
-
function
|
|
3397
|
+
function translationMetadata(sourceText, sourceLocale, mode) {
|
|
3386
3398
|
return {
|
|
3387
3399
|
sourceLocale,
|
|
3388
3400
|
sourceTextHash: computeSourceTextHash(sourceText),
|
|
3389
|
-
translationSource:
|
|
3390
|
-
editedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3401
|
+
translationSource: mode,
|
|
3402
|
+
...mode === "manual" ? { editedAt: (/* @__PURE__ */ new Date()).toISOString() } : { translatedAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
3391
3403
|
};
|
|
3392
3404
|
}
|
|
3393
|
-
function setNodeMetadata(node, slot, text, sourceLocale) {
|
|
3405
|
+
function setNodeMetadata(node, slot, text, sourceLocale, mode) {
|
|
3394
3406
|
const localeMetadata = node.translationMetadata?.[slot.locale];
|
|
3395
|
-
const nextMetadata = text.trim().length === 0 ? Object.fromEntries(Object.entries(localeMetadata ?? {}).filter(([key]) => key !== slot.property)) : { ...localeMetadata, [slot.property]:
|
|
3407
|
+
const nextMetadata = text.trim().length === 0 ? Object.fromEntries(Object.entries(localeMetadata ?? {}).filter(([key]) => key !== slot.property)) : { ...localeMetadata, [slot.property]: translationMetadata(slot.sourceText, sourceLocale, mode) };
|
|
3396
3408
|
return { ...node, translationMetadata: { ...node.translationMetadata, [slot.locale]: nextMetadata } };
|
|
3397
3409
|
}
|
|
3398
|
-
function updateSchemaTranslation(schema, slot, text, sourceLocale) {
|
|
3410
|
+
function updateSchemaTranslation(schema, slot, text, sourceLocale, mode = "manual") {
|
|
3399
3411
|
if (slot.kind === "form") {
|
|
3400
3412
|
return setNodeMetadata(
|
|
3401
3413
|
{ ...schema, translations: updateTranslationMap(schema.translations, slot.locale, slot.property, text) },
|
|
3402
3414
|
slot,
|
|
3403
3415
|
text,
|
|
3404
|
-
sourceLocale
|
|
3416
|
+
sourceLocale,
|
|
3417
|
+
mode
|
|
3405
3418
|
);
|
|
3406
3419
|
}
|
|
3407
3420
|
if (slot.kind === "field") {
|
|
@@ -3410,7 +3423,7 @@ function updateSchemaTranslation(schema, slot, text, sourceLocale) {
|
|
|
3410
3423
|
fields: schema.fields.map((field) => {
|
|
3411
3424
|
if (field.id !== slot.nodeId) return field;
|
|
3412
3425
|
const translations = updateTranslationMap(field.translations, slot.locale, slot.property, text);
|
|
3413
|
-
return setNodeMetadata({ ...field, translations }, slot, text, sourceLocale);
|
|
3426
|
+
return setNodeMetadata({ ...field, translations }, slot, text, sourceLocale, mode);
|
|
3414
3427
|
})
|
|
3415
3428
|
};
|
|
3416
3429
|
}
|
|
@@ -3426,7 +3439,7 @@ function updateSchemaTranslation(schema, slot, text, sourceLocale) {
|
|
|
3426
3439
|
const translations = text.trim().length === 0 ? Object.fromEntries(
|
|
3427
3440
|
Object.entries(option.translations ?? {}).filter(([locale]) => locale !== slot.locale)
|
|
3428
3441
|
) : { ...option.translations, [slot.locale]: text };
|
|
3429
|
-
return setNodeMetadata({ ...option, translations }, slot, text, sourceLocale);
|
|
3442
|
+
return setNodeMetadata({ ...option, translations }, slot, text, sourceLocale, mode);
|
|
3430
3443
|
})
|
|
3431
3444
|
};
|
|
3432
3445
|
})
|
|
@@ -3438,7 +3451,7 @@ function updateSchemaTranslation(schema, slot, text, sourceLocale) {
|
|
|
3438
3451
|
pages: schema.pages.map((page) => {
|
|
3439
3452
|
if (page.id !== slot.nodeId) return page;
|
|
3440
3453
|
const translations = updateTranslationMap(page.translations, slot.locale, slot.property, text);
|
|
3441
|
-
return setNodeMetadata({ ...page, translations }, slot, text, sourceLocale);
|
|
3454
|
+
return setNodeMetadata({ ...page, translations }, slot, text, sourceLocale, mode);
|
|
3442
3455
|
})
|
|
3443
3456
|
}
|
|
3444
3457
|
};
|
|
@@ -3451,14 +3464,27 @@ function useTranslationWorkspace({
|
|
|
3451
3464
|
translationAdapter,
|
|
3452
3465
|
readOnly = false,
|
|
3453
3466
|
policy,
|
|
3467
|
+
availableLocales,
|
|
3468
|
+
onLocaleAdded,
|
|
3469
|
+
onLocaleRemoved,
|
|
3470
|
+
onLocaleChange,
|
|
3454
3471
|
beforeRemoveLocale,
|
|
3472
|
+
confirmRemoveLocale,
|
|
3473
|
+
slots: workspaceSlots,
|
|
3474
|
+
onTranslationStart,
|
|
3475
|
+
onTranslationSuccess,
|
|
3476
|
+
onTranslationError,
|
|
3477
|
+
onTranslationChange,
|
|
3455
3478
|
validateLocale
|
|
3456
3479
|
}) {
|
|
3457
3480
|
const [draftSchema, setDraftSchema] = useState4(schema);
|
|
3458
|
-
const [selectedLocale, setSelectedLocale] = useState4(targetLocale ?? "");
|
|
3481
|
+
const [selectedLocale, setSelectedLocale] = useState4(normalizeLocale(targetLocale ?? "") ?? targetLocale ?? "");
|
|
3459
3482
|
const [isTranslating, setIsTranslating] = useState4(false);
|
|
3460
3483
|
const [error, setError] = useState4();
|
|
3484
|
+
const [pendingRemoval, setPendingRemoval] = useState4();
|
|
3485
|
+
const pendingRemovalResolver = useRef2(void 0);
|
|
3461
3486
|
const currentSchema = onChange === void 0 ? draftSchema : schema;
|
|
3487
|
+
const confirmRemoveLocaleRenderer = confirmRemoveLocale ?? workspaceSlots?.confirmRemoveLocale;
|
|
3462
3488
|
const targetLocales = useMemo4(() => {
|
|
3463
3489
|
const locales = collectSchemaLocales2(currentSchema).allUniqueLocales;
|
|
3464
3490
|
return [.../* @__PURE__ */ new Set([...currentSchema.supportedLocales ?? [], ...locales])].filter(
|
|
@@ -3466,6 +3492,15 @@ function useTranslationWorkspace({
|
|
|
3466
3492
|
);
|
|
3467
3493
|
}, [currentSchema, sourceLocale]);
|
|
3468
3494
|
const activeLocale = selectedLocale.length > 0 ? selectedLocale : targetLocales[0] ?? "";
|
|
3495
|
+
const localeOptions = useMemo4(() => {
|
|
3496
|
+
if (availableLocales === void 0) {
|
|
3497
|
+
return targetLocales.map((locale) => ({ locale, label: locale }));
|
|
3498
|
+
}
|
|
3499
|
+
return availableLocales.map((candidate) => {
|
|
3500
|
+
if (typeof candidate !== "string") return candidate;
|
|
3501
|
+
return { locale: normalizeLocale(candidate) ?? candidate, label: candidate };
|
|
3502
|
+
});
|
|
3503
|
+
}, [availableLocales, targetLocales]);
|
|
3469
3504
|
const slots = useMemo4(
|
|
3470
3505
|
() => activeLocale.length === 0 ? [] : collectTranslationSlots(currentSchema, activeLocale),
|
|
3471
3506
|
[activeLocale, currentSchema]
|
|
@@ -3499,9 +3534,17 @@ function useTranslationWorkspace({
|
|
|
3499
3534
|
const setTranslation = useCallback3(
|
|
3500
3535
|
(slot, text) => {
|
|
3501
3536
|
if (readOnly) return;
|
|
3502
|
-
|
|
3537
|
+
const metadata = translationMetadata(slot.sourceText, sourceLocale, "manual");
|
|
3538
|
+
commit(updateSchemaTranslation(currentSchema, slot, text, sourceLocale, "manual"));
|
|
3539
|
+
onTranslationChange?.({
|
|
3540
|
+
slot,
|
|
3541
|
+
...slot.existingText === void 0 ? {} : { previousText: slot.existingText },
|
|
3542
|
+
nextText: text,
|
|
3543
|
+
mode: "manual",
|
|
3544
|
+
metadata
|
|
3545
|
+
});
|
|
3503
3546
|
},
|
|
3504
|
-
[commit, currentSchema, readOnly, sourceLocale]
|
|
3547
|
+
[commit, currentSchema, onTranslationChange, readOnly, sourceLocale]
|
|
3505
3548
|
);
|
|
3506
3549
|
const addLocale = useCallback3(
|
|
3507
3550
|
(locale) => {
|
|
@@ -3513,14 +3556,14 @@ function useTranslationWorkspace({
|
|
|
3513
3556
|
const normalized = normalizeLocale(locale);
|
|
3514
3557
|
if (normalized === null) {
|
|
3515
3558
|
const workspaceError = workspaceLocaleValidationError(
|
|
3516
|
-
validateLocalePipeline(locale, currentSchema, policy, validateLocale),
|
|
3559
|
+
validateLocalePipeline(locale, currentSchema, policy, validateLocale, availableLocales),
|
|
3517
3560
|
collectSchemaLocales2(currentSchema).allUniqueLocales.size,
|
|
3518
3561
|
locale
|
|
3519
3562
|
);
|
|
3520
3563
|
setError(workspaceError);
|
|
3521
3564
|
return { success: false, error: workspaceError };
|
|
3522
3565
|
}
|
|
3523
|
-
const validation = validateLocalePipeline(normalized, currentSchema, policy, validateLocale);
|
|
3566
|
+
const validation = validateLocalePipeline(normalized, currentSchema, policy, validateLocale, availableLocales);
|
|
3524
3567
|
if (!validation.valid) {
|
|
3525
3568
|
const workspaceError = workspaceLocaleValidationError(
|
|
3526
3569
|
validation,
|
|
@@ -3536,42 +3579,62 @@ function useTranslationWorkspace({
|
|
|
3536
3579
|
supportedLocales: [.../* @__PURE__ */ new Set([...currentSchema.supportedLocales ?? [], normalized])]
|
|
3537
3580
|
});
|
|
3538
3581
|
setSelectedLocale(normalized);
|
|
3582
|
+
onLocaleAdded?.(normalized);
|
|
3539
3583
|
return { success: true };
|
|
3540
3584
|
},
|
|
3541
|
-
[commit, currentSchema, policy, readOnly, validateLocale]
|
|
3585
|
+
[availableLocales, commit, currentSchema, onLocaleAdded, policy, readOnly, validateLocale]
|
|
3542
3586
|
);
|
|
3543
3587
|
const isAddLocaleAllowed = useCallback3(
|
|
3544
3588
|
(locale) => {
|
|
3545
3589
|
if (readOnly) return false;
|
|
3546
3590
|
const normalized = normalizeLocale(locale);
|
|
3547
3591
|
if (normalized === null) return false;
|
|
3548
|
-
return validateLocalePipeline(normalized, currentSchema, policy, validateLocale).valid;
|
|
3592
|
+
return validateLocalePipeline(normalized, currentSchema, policy, validateLocale, availableLocales).valid;
|
|
3549
3593
|
},
|
|
3550
|
-
[currentSchema, policy, readOnly, validateLocale]
|
|
3594
|
+
[availableLocales, currentSchema, policy, readOnly, validateLocale]
|
|
3551
3595
|
);
|
|
3552
3596
|
const removeLocale = useCallback3(
|
|
3553
3597
|
(locale) => {
|
|
3554
|
-
|
|
3598
|
+
const normalized = normalizeLocale(locale) ?? locale;
|
|
3599
|
+
const normalizedSourceLocale = normalizeLocale(sourceLocale) ?? sourceLocale;
|
|
3600
|
+
const normalizedDefaultLocale = currentSchema.defaultLocale === void 0 ? void 0 : normalizeLocale(currentSchema.defaultLocale) ?? currentSchema.defaultLocale;
|
|
3601
|
+
if (readOnly || normalized === normalizedSourceLocale || normalized === normalizedDefaultLocale) return false;
|
|
3555
3602
|
const remove = () => {
|
|
3556
|
-
commit(removeLocaleFromSchema(currentSchema,
|
|
3557
|
-
if (activeLocale ===
|
|
3603
|
+
commit(removeLocaleFromSchema(currentSchema, normalized));
|
|
3604
|
+
if (activeLocale === normalized) setSelectedLocale("");
|
|
3605
|
+
onLocaleRemoved?.(normalized);
|
|
3606
|
+
};
|
|
3607
|
+
const slotCount = collectTranslationSlots(currentSchema, normalized).length;
|
|
3608
|
+
const translatedSlotsCount = collectTranslationSlots(currentSchema, normalized).filter(
|
|
3609
|
+
(slot) => slot.existingText !== void 0 && slot.existingText.trim().length > 0
|
|
3610
|
+
).length;
|
|
3611
|
+
const requestConfirmation = () => new Promise((resolve) => {
|
|
3612
|
+
pendingRemovalResolver.current = resolve;
|
|
3613
|
+
setPendingRemoval({
|
|
3614
|
+
locale: normalized,
|
|
3615
|
+
localeLabel: localeOptions.find((option) => option.locale === normalized)?.label ?? normalized,
|
|
3616
|
+
translatedSlotsCount
|
|
3617
|
+
});
|
|
3618
|
+
});
|
|
3619
|
+
const removeAfterApproval = () => {
|
|
3620
|
+
if (confirmRemoveLocaleRenderer === void 0) {
|
|
3621
|
+
remove();
|
|
3622
|
+
return true;
|
|
3623
|
+
}
|
|
3624
|
+
return requestConfirmation();
|
|
3558
3625
|
};
|
|
3559
|
-
const slotCount = collectTranslationSlots(currentSchema, locale).length;
|
|
3560
3626
|
if (beforeRemoveLocale === void 0) {
|
|
3561
|
-
|
|
3562
|
-
return true;
|
|
3627
|
+
return removeAfterApproval();
|
|
3563
3628
|
}
|
|
3564
3629
|
try {
|
|
3565
|
-
const decision = beforeRemoveLocale(
|
|
3630
|
+
const decision = beforeRemoveLocale(normalized, { slotCount });
|
|
3566
3631
|
if (typeof decision === "boolean") {
|
|
3567
3632
|
if (!decision) return false;
|
|
3568
|
-
|
|
3569
|
-
return true;
|
|
3633
|
+
return removeAfterApproval();
|
|
3570
3634
|
}
|
|
3571
3635
|
return decision.then((allowed) => {
|
|
3572
3636
|
if (!allowed) return false;
|
|
3573
|
-
|
|
3574
|
-
return true;
|
|
3637
|
+
return removeAfterApproval();
|
|
3575
3638
|
});
|
|
3576
3639
|
} catch (cause) {
|
|
3577
3640
|
const workspaceError = {
|
|
@@ -3583,8 +3646,45 @@ function useTranslationWorkspace({
|
|
|
3583
3646
|
return Promise.reject(cause);
|
|
3584
3647
|
}
|
|
3585
3648
|
},
|
|
3586
|
-
[
|
|
3649
|
+
[
|
|
3650
|
+
activeLocale,
|
|
3651
|
+
beforeRemoveLocale,
|
|
3652
|
+
commit,
|
|
3653
|
+
confirmRemoveLocaleRenderer,
|
|
3654
|
+
currentSchema,
|
|
3655
|
+
localeOptions,
|
|
3656
|
+
onLocaleRemoved,
|
|
3657
|
+
readOnly,
|
|
3658
|
+
sourceLocale
|
|
3659
|
+
]
|
|
3587
3660
|
);
|
|
3661
|
+
const confirmPendingRemoval = useCallback3(() => {
|
|
3662
|
+
const pending = pendingRemoval;
|
|
3663
|
+
const resolve = pendingRemovalResolver.current;
|
|
3664
|
+
if (pending === void 0 || resolve === void 0) return;
|
|
3665
|
+
pendingRemovalResolver.current = void 0;
|
|
3666
|
+
setPendingRemoval(void 0);
|
|
3667
|
+
commit(removeLocaleFromSchema(currentSchema, pending.locale));
|
|
3668
|
+
if (activeLocale === pending.locale) setSelectedLocale("");
|
|
3669
|
+
onLocaleRemoved?.(pending.locale);
|
|
3670
|
+
resolve(true);
|
|
3671
|
+
}, [activeLocale, commit, currentSchema, onLocaleRemoved, pendingRemoval]);
|
|
3672
|
+
const cancelPendingRemoval = useCallback3(() => {
|
|
3673
|
+
const resolve = pendingRemovalResolver.current;
|
|
3674
|
+
if (resolve === void 0) return;
|
|
3675
|
+
pendingRemovalResolver.current = void 0;
|
|
3676
|
+
setPendingRemoval(void 0);
|
|
3677
|
+
resolve(false);
|
|
3678
|
+
}, []);
|
|
3679
|
+
const removeLocaleConfirmation = useMemo4(() => {
|
|
3680
|
+
if (confirmRemoveLocaleRenderer === void 0 || pendingRemoval === void 0) return null;
|
|
3681
|
+
return confirmRemoveLocaleRenderer({
|
|
3682
|
+
...pendingRemoval,
|
|
3683
|
+
isOpen: true,
|
|
3684
|
+
onConfirm: confirmPendingRemoval,
|
|
3685
|
+
onCancel: cancelPendingRemoval
|
|
3686
|
+
});
|
|
3687
|
+
}, [cancelPendingRemoval, confirmPendingRemoval, confirmRemoveLocaleRenderer, pendingRemoval]);
|
|
3588
3688
|
const translateAll = useCallback3(
|
|
3589
3689
|
async (options = {}) => {
|
|
3590
3690
|
if (readOnly) {
|
|
@@ -3602,8 +3702,26 @@ function useTranslationWorkspace({
|
|
|
3602
3702
|
setError(workspaceError);
|
|
3603
3703
|
return { success: false, error: workspaceError };
|
|
3604
3704
|
}
|
|
3705
|
+
const selectedLocaleOption = localeOptions.find(
|
|
3706
|
+
(option) => (normalizeLocale(option.locale) ?? option.locale) === activeLocale
|
|
3707
|
+
);
|
|
3708
|
+
if (selectedLocaleOption?.translatable === false) {
|
|
3709
|
+
setError(void 0);
|
|
3710
|
+
return {
|
|
3711
|
+
success: true,
|
|
3712
|
+
report: {
|
|
3713
|
+
updatedSlots: [],
|
|
3714
|
+
skippedSlots: slots,
|
|
3715
|
+
staleSlots: [],
|
|
3716
|
+
skippedReasons: Object.fromEntries(
|
|
3717
|
+
slots.map((slot) => [slot.path ?? `${slot.kind}.${slot.nodeId}.${slot.property}`, "unsupported"])
|
|
3718
|
+
)
|
|
3719
|
+
}
|
|
3720
|
+
};
|
|
3721
|
+
}
|
|
3605
3722
|
setIsTranslating(true);
|
|
3606
3723
|
setError(void 0);
|
|
3724
|
+
onTranslationStart?.({ targetLocale: activeLocale, mode: "automatic" });
|
|
3607
3725
|
try {
|
|
3608
3726
|
const populated = await populateSchemaTranslations2(
|
|
3609
3727
|
currentSchema,
|
|
@@ -3616,6 +3734,17 @@ function useTranslationWorkspace({
|
|
|
3616
3734
|
}
|
|
3617
3735
|
);
|
|
3618
3736
|
commit(populated.schema);
|
|
3737
|
+
const missingSlotsCount = collectTranslationSlots(populated.schema, activeLocale).filter(
|
|
3738
|
+
(slot) => slot.status === "missing"
|
|
3739
|
+
).length;
|
|
3740
|
+
onTranslationSuccess?.({
|
|
3741
|
+
sourceLocale,
|
|
3742
|
+
targetLocale: activeLocale,
|
|
3743
|
+
mode: "automatic",
|
|
3744
|
+
updatedSlots: populated.report.updatedSlots,
|
|
3745
|
+
skippedSlots: populated.report.skippedSlots,
|
|
3746
|
+
missingSlotsCount
|
|
3747
|
+
});
|
|
3619
3748
|
return { success: true, report: populated.report };
|
|
3620
3749
|
} catch (cause) {
|
|
3621
3750
|
const workspaceError = {
|
|
@@ -3624,12 +3753,25 @@ function useTranslationWorkspace({
|
|
|
3624
3753
|
cause
|
|
3625
3754
|
};
|
|
3626
3755
|
setError(workspaceError);
|
|
3756
|
+
onTranslationError?.({ targetLocale: activeLocale, error: workspaceError });
|
|
3627
3757
|
return { success: false, error: workspaceError };
|
|
3628
3758
|
} finally {
|
|
3629
3759
|
setIsTranslating(false);
|
|
3630
3760
|
}
|
|
3631
3761
|
},
|
|
3632
|
-
[
|
|
3762
|
+
[
|
|
3763
|
+
activeLocale,
|
|
3764
|
+
commit,
|
|
3765
|
+
currentSchema,
|
|
3766
|
+
localeOptions,
|
|
3767
|
+
onTranslationError,
|
|
3768
|
+
onTranslationStart,
|
|
3769
|
+
onTranslationSuccess,
|
|
3770
|
+
readOnly,
|
|
3771
|
+
slots,
|
|
3772
|
+
sourceLocale,
|
|
3773
|
+
translationAdapter
|
|
3774
|
+
]
|
|
3633
3775
|
);
|
|
3634
3776
|
const translateSlot = useCallback3(
|
|
3635
3777
|
async (slot) => {
|
|
@@ -3647,7 +3789,15 @@ function useTranslationWorkspace({
|
|
|
3647
3789
|
setError(void 0);
|
|
3648
3790
|
try {
|
|
3649
3791
|
const text = await asAsyncAdapter(translationAdapter).translateText(slot.sourceText, slot.locale, sourceLocale);
|
|
3650
|
-
|
|
3792
|
+
const metadata = translationMetadata(slot.sourceText, sourceLocale, "automatic");
|
|
3793
|
+
commit(updateSchemaTranslation(currentSchema, slot, text, sourceLocale, "automatic"));
|
|
3794
|
+
onTranslationChange?.({
|
|
3795
|
+
slot,
|
|
3796
|
+
...slot.existingText === void 0 ? {} : { previousText: slot.existingText },
|
|
3797
|
+
nextText: text,
|
|
3798
|
+
mode: "automatic",
|
|
3799
|
+
metadata
|
|
3800
|
+
});
|
|
3651
3801
|
return { success: true };
|
|
3652
3802
|
} catch (cause) {
|
|
3653
3803
|
const workspaceError = {
|
|
@@ -3661,18 +3811,24 @@ function useTranslationWorkspace({
|
|
|
3661
3811
|
setIsTranslating(false);
|
|
3662
3812
|
}
|
|
3663
3813
|
},
|
|
3664
|
-
[commit, currentSchema, readOnly, sourceLocale, translationAdapter]
|
|
3814
|
+
[commit, currentSchema, onTranslationChange, readOnly, sourceLocale, translationAdapter]
|
|
3665
3815
|
);
|
|
3666
3816
|
return {
|
|
3667
3817
|
sourceLocale,
|
|
3668
3818
|
targetLocale: activeLocale,
|
|
3669
3819
|
targetLocales,
|
|
3670
|
-
|
|
3820
|
+
localeOptions,
|
|
3821
|
+
setTargetLocale: (locale) => {
|
|
3822
|
+
const normalized = normalizeLocale(locale) ?? locale;
|
|
3823
|
+
setSelectedLocale(normalized);
|
|
3824
|
+
onLocaleChange?.(normalized);
|
|
3825
|
+
},
|
|
3671
3826
|
slots,
|
|
3672
3827
|
summary,
|
|
3673
3828
|
addLocale,
|
|
3674
3829
|
isAddLocaleAllowed,
|
|
3675
3830
|
removeLocale,
|
|
3831
|
+
...removeLocaleConfirmation === null ? {} : { removeLocaleConfirmation },
|
|
3676
3832
|
setTranslation,
|
|
3677
3833
|
translateAll,
|
|
3678
3834
|
translateSlot,
|
|
@@ -3810,7 +3966,7 @@ import {
|
|
|
3810
3966
|
useEffect as useEffect3,
|
|
3811
3967
|
useId,
|
|
3812
3968
|
useMemo as useMemo6,
|
|
3813
|
-
useRef as
|
|
3969
|
+
useRef as useRef3,
|
|
3814
3970
|
useState as useState6
|
|
3815
3971
|
} from "react";
|
|
3816
3972
|
import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
@@ -4326,8 +4482,8 @@ function ContextFormRenderer({
|
|
|
4326
4482
|
const i18n = useFormEngineI18n();
|
|
4327
4483
|
const isProviderValue = useContext4(FormEngineI18nProviderScopeContext);
|
|
4328
4484
|
const prefix = useId().replace(/:/g, "");
|
|
4329
|
-
const formRef =
|
|
4330
|
-
const loadedDraftKey =
|
|
4485
|
+
const formRef = useRef3(null);
|
|
4486
|
+
const loadedDraftKey = useRef3(null);
|
|
4331
4487
|
const [draftRestored, setDraftRestored] = useState6(false);
|
|
4332
4488
|
const [currentPageIndex, setCurrentPageIndex] = useState6(0);
|
|
4333
4489
|
const [focusFieldId, setFocusFieldId] = useState6(null);
|
|
@@ -4337,10 +4493,10 @@ function ContextFormRenderer({
|
|
|
4337
4493
|
const [receipt, setReceipt] = useState6(null);
|
|
4338
4494
|
const [completionData, setCompletionData] = useState6(null);
|
|
4339
4495
|
const [receiptLoaded, setReceiptLoaded] = useState6(receiptStore === void 0);
|
|
4340
|
-
const rendererSubmissionInFlight =
|
|
4341
|
-
const fallbackAttemptId =
|
|
4342
|
-
const completionRef =
|
|
4343
|
-
const confirmationRef =
|
|
4496
|
+
const rendererSubmissionInFlight = useRef3(false);
|
|
4497
|
+
const fallbackAttemptId = useRef3(null);
|
|
4498
|
+
const completionRef = useRef3(null);
|
|
4499
|
+
const confirmationRef = useRef3(null);
|
|
4344
4500
|
const pages = form.schema.pages;
|
|
4345
4501
|
const visiblePageIndexes = useMemo6(
|
|
4346
4502
|
() => pages === void 0 ? [] : pages.flatMap((page, index) => form.pageVisibility[page.id] === true ? [index] : []),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@form-engine-ts/react",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.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/core": "
|
|
46
|
-
"@form-engine-ts/privacy": "
|
|
45
|
+
"@form-engine-ts/core": "6.0.0",
|
|
46
|
+
"@form-engine-ts/privacy": "6.0.0"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"react": ">=18.2 <20",
|