@form-engine-ts/react 5.1.0 → 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/dist/index.cjs +319 -84
- package/dist/index.d.cts +99 -9
- package/dist/index.d.ts +99 -9
- package/dist/index.js +304 -64
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -922,7 +922,9 @@ function resolveTranslation(key, aliases, adapter, defaultCatalog, params = {},
|
|
|
922
922
|
}
|
|
923
923
|
|
|
924
924
|
// src/i18n/provider.tsx
|
|
925
|
-
import {
|
|
925
|
+
import {
|
|
926
|
+
createFormEngineTranslator
|
|
927
|
+
} from "@form-engine-ts/core";
|
|
926
928
|
import { createContext, useContext, useMemo as useMemo2 } from "react";
|
|
927
929
|
import { jsx } from "react/jsx-runtime";
|
|
928
930
|
var defaultTranslator = createFormEngineTranslator({ locale: "ja" });
|
|
@@ -936,6 +938,8 @@ function FormEngineI18nProvider({
|
|
|
936
938
|
fallbackLocale = "en",
|
|
937
939
|
messages,
|
|
938
940
|
customCatalogs,
|
|
941
|
+
onMissingKey,
|
|
942
|
+
strict,
|
|
939
943
|
translator: customTranslator,
|
|
940
944
|
children
|
|
941
945
|
}) {
|
|
@@ -945,9 +949,11 @@ function FormEngineI18nProvider({
|
|
|
945
949
|
locale,
|
|
946
950
|
fallbackLocale,
|
|
947
951
|
...messages === void 0 ? {} : { messages },
|
|
948
|
-
...customCatalogs === void 0 ? {} : { customCatalogs }
|
|
952
|
+
...customCatalogs === void 0 ? {} : { customCatalogs },
|
|
953
|
+
...onMissingKey === void 0 ? {} : { onMissingKey },
|
|
954
|
+
...strict === void 0 ? {} : { strict }
|
|
949
955
|
});
|
|
950
|
-
}, [customCatalogs, customTranslator, fallbackLocale, locale, messages]);
|
|
956
|
+
}, [customCatalogs, customTranslator, fallbackLocale, locale, messages, onMissingKey, strict]);
|
|
951
957
|
const value = useMemo2(() => ({ uiLocale: locale, translator }), [locale, translator]);
|
|
952
958
|
return /* @__PURE__ */ jsx(FormEngineI18nProviderScopeContext.Provider, { value: true, children: /* @__PURE__ */ jsx(FormEngineI18nContext.Provider, { value, children }) });
|
|
953
959
|
}
|
|
@@ -2524,6 +2530,7 @@ function FormBuilder({
|
|
|
2524
2530
|
LocalizationSlot,
|
|
2525
2531
|
{
|
|
2526
2532
|
schema,
|
|
2533
|
+
onChange,
|
|
2527
2534
|
translate,
|
|
2528
2535
|
currentLocale: editingLocale,
|
|
2529
2536
|
onCurrentLocaleChange: setEditingLocale,
|
|
@@ -3048,24 +3055,14 @@ import {
|
|
|
3048
3055
|
assertValidFormSchema,
|
|
3049
3056
|
calculateFieldVisibility,
|
|
3050
3057
|
calculatePageVisibility,
|
|
3058
|
+
FormSubmissionError,
|
|
3059
|
+
isFormSubmissionSerializedError,
|
|
3051
3060
|
resolveLocalizedSchema,
|
|
3052
3061
|
selectVisibleAnswers,
|
|
3053
3062
|
validateAnswers,
|
|
3054
3063
|
validatePageAnswers
|
|
3055
3064
|
} from "@form-engine-ts/core";
|
|
3056
3065
|
import { createContext as createContext3, useCallback as useCallback2, useContext as useContext3, useEffect, useMemo as useMemo3, useRef, useState as useState3 } from "react";
|
|
3057
|
-
|
|
3058
|
-
// src/types.ts
|
|
3059
|
-
var FormSubmissionError = class extends Error {
|
|
3060
|
-
payload;
|
|
3061
|
-
constructor(message, payload) {
|
|
3062
|
-
super(message);
|
|
3063
|
-
this.name = "FormSubmissionError";
|
|
3064
|
-
this.payload = payload ?? { formError: message };
|
|
3065
|
-
}
|
|
3066
|
-
};
|
|
3067
|
-
|
|
3068
|
-
// src/context.tsx
|
|
3069
3066
|
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
3070
3067
|
var FormContext = createContext3(null);
|
|
3071
3068
|
function issuesByField(issues) {
|
|
@@ -3202,10 +3199,12 @@ function FormProvider({
|
|
|
3202
3199
|
setSubmitStatus("success");
|
|
3203
3200
|
return response === void 0 ? { status: "success" } : { status: "success", response };
|
|
3204
3201
|
} catch (cause) {
|
|
3205
|
-
const error = isServerErrorPayload(cause) ? new FormSubmissionError(
|
|
3206
|
-
|
|
3207
|
-
cause
|
|
3208
|
-
|
|
3202
|
+
const error = isFormSubmissionSerializedError(cause) ? new FormSubmissionError(cause) : isServerErrorPayload(cause) ? new FormSubmissionError({
|
|
3203
|
+
code: "VALIDATION_FAILED",
|
|
3204
|
+
messageKey: cause.formError ?? "Submission failed",
|
|
3205
|
+
fieldErrors: cause.fieldErrors ?? {},
|
|
3206
|
+
formErrors: cause.formError === void 0 ? [] : [cause.formError]
|
|
3207
|
+
}) : cause instanceof Error ? cause : new Error(String(cause));
|
|
3209
3208
|
setSubmitError(error);
|
|
3210
3209
|
setSubmitStatus("error");
|
|
3211
3210
|
return { status: "error", error };
|
|
@@ -3273,6 +3272,10 @@ function useField(fieldId) {
|
|
|
3273
3272
|
return { field, value: form.values[fieldId], error: form.errors[fieldId], setValue };
|
|
3274
3273
|
}
|
|
3275
3274
|
|
|
3275
|
+
// src/hooks/useTranslationComparison.ts
|
|
3276
|
+
import { computeSourceTextHash as computeSourceTextHash2 } from "@form-engine-ts/core";
|
|
3277
|
+
import { useCallback as useCallback4, useMemo as useMemo5 } from "react";
|
|
3278
|
+
|
|
3276
3279
|
// src/hooks/useTranslationWorkspace.ts
|
|
3277
3280
|
import {
|
|
3278
3281
|
collectSchemaLocales as collectSchemaLocales2,
|
|
@@ -3282,7 +3285,7 @@ import {
|
|
|
3282
3285
|
populateSchemaTranslations as populateSchemaTranslations2,
|
|
3283
3286
|
removeLocaleFromSchema
|
|
3284
3287
|
} from "@form-engine-ts/core";
|
|
3285
|
-
import { useCallback as useCallback3, useMemo as useMemo4, useState as useState4 } from "react";
|
|
3288
|
+
import { useCallback as useCallback3, useMemo as useMemo4, useRef as useRef2, useState as useState4 } from "react";
|
|
3286
3289
|
function updateTranslationMap(translations, locale, property, text) {
|
|
3287
3290
|
if (property === "label") return translations ?? {};
|
|
3288
3291
|
const current = translations?.[locale];
|
|
@@ -3394,26 +3397,27 @@ function workspaceLocaleValidationError(validation, currentLocales, requestedLoc
|
|
|
3394
3397
|
}
|
|
3395
3398
|
return { type: "custom_validation_failed", message: error.message };
|
|
3396
3399
|
}
|
|
3397
|
-
function
|
|
3400
|
+
function translationMetadata(sourceText, sourceLocale, mode) {
|
|
3398
3401
|
return {
|
|
3399
3402
|
sourceLocale,
|
|
3400
3403
|
sourceTextHash: computeSourceTextHash(sourceText),
|
|
3401
|
-
translationSource:
|
|
3402
|
-
editedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3404
|
+
translationSource: mode,
|
|
3405
|
+
...mode === "manual" ? { editedAt: (/* @__PURE__ */ new Date()).toISOString() } : { translatedAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
3403
3406
|
};
|
|
3404
3407
|
}
|
|
3405
|
-
function setNodeMetadata(node, slot, text, sourceLocale) {
|
|
3408
|
+
function setNodeMetadata(node, slot, text, sourceLocale, mode) {
|
|
3406
3409
|
const localeMetadata = node.translationMetadata?.[slot.locale];
|
|
3407
|
-
const nextMetadata = text.trim().length === 0 ? Object.fromEntries(Object.entries(localeMetadata ?? {}).filter(([key]) => key !== slot.property)) : { ...localeMetadata, [slot.property]:
|
|
3410
|
+
const nextMetadata = text.trim().length === 0 ? Object.fromEntries(Object.entries(localeMetadata ?? {}).filter(([key]) => key !== slot.property)) : { ...localeMetadata, [slot.property]: translationMetadata(slot.sourceText, sourceLocale, mode) };
|
|
3408
3411
|
return { ...node, translationMetadata: { ...node.translationMetadata, [slot.locale]: nextMetadata } };
|
|
3409
3412
|
}
|
|
3410
|
-
function updateSchemaTranslation(schema, slot, text, sourceLocale) {
|
|
3413
|
+
function updateSchemaTranslation(schema, slot, text, sourceLocale, mode = "manual") {
|
|
3411
3414
|
if (slot.kind === "form") {
|
|
3412
3415
|
return setNodeMetadata(
|
|
3413
3416
|
{ ...schema, translations: updateTranslationMap(schema.translations, slot.locale, slot.property, text) },
|
|
3414
3417
|
slot,
|
|
3415
3418
|
text,
|
|
3416
|
-
sourceLocale
|
|
3419
|
+
sourceLocale,
|
|
3420
|
+
mode
|
|
3417
3421
|
);
|
|
3418
3422
|
}
|
|
3419
3423
|
if (slot.kind === "field") {
|
|
@@ -3422,7 +3426,7 @@ function updateSchemaTranslation(schema, slot, text, sourceLocale) {
|
|
|
3422
3426
|
fields: schema.fields.map((field) => {
|
|
3423
3427
|
if (field.id !== slot.nodeId) return field;
|
|
3424
3428
|
const translations = updateTranslationMap(field.translations, slot.locale, slot.property, text);
|
|
3425
|
-
return setNodeMetadata({ ...field, translations }, slot, text, sourceLocale);
|
|
3429
|
+
return setNodeMetadata({ ...field, translations }, slot, text, sourceLocale, mode);
|
|
3426
3430
|
})
|
|
3427
3431
|
};
|
|
3428
3432
|
}
|
|
@@ -3438,7 +3442,7 @@ function updateSchemaTranslation(schema, slot, text, sourceLocale) {
|
|
|
3438
3442
|
const translations = text.trim().length === 0 ? Object.fromEntries(
|
|
3439
3443
|
Object.entries(option.translations ?? {}).filter(([locale]) => locale !== slot.locale)
|
|
3440
3444
|
) : { ...option.translations, [slot.locale]: text };
|
|
3441
|
-
return setNodeMetadata({ ...option, translations }, slot, text, sourceLocale);
|
|
3445
|
+
return setNodeMetadata({ ...option, translations }, slot, text, sourceLocale, mode);
|
|
3442
3446
|
})
|
|
3443
3447
|
};
|
|
3444
3448
|
})
|
|
@@ -3450,7 +3454,7 @@ function updateSchemaTranslation(schema, slot, text, sourceLocale) {
|
|
|
3450
3454
|
pages: schema.pages.map((page) => {
|
|
3451
3455
|
if (page.id !== slot.nodeId) return page;
|
|
3452
3456
|
const translations = updateTranslationMap(page.translations, slot.locale, slot.property, text);
|
|
3453
|
-
return setNodeMetadata({ ...page, translations }, slot, text, sourceLocale);
|
|
3457
|
+
return setNodeMetadata({ ...page, translations }, slot, text, sourceLocale, mode);
|
|
3454
3458
|
})
|
|
3455
3459
|
}
|
|
3456
3460
|
};
|
|
@@ -3468,13 +3472,22 @@ function useTranslationWorkspace({
|
|
|
3468
3472
|
onLocaleRemoved,
|
|
3469
3473
|
onLocaleChange,
|
|
3470
3474
|
beforeRemoveLocale,
|
|
3475
|
+
confirmRemoveLocale,
|
|
3476
|
+
slots: workspaceSlots,
|
|
3477
|
+
onTranslationStart,
|
|
3478
|
+
onTranslationSuccess,
|
|
3479
|
+
onTranslationError,
|
|
3480
|
+
onTranslationChange,
|
|
3471
3481
|
validateLocale
|
|
3472
3482
|
}) {
|
|
3473
3483
|
const [draftSchema, setDraftSchema] = useState4(schema);
|
|
3474
3484
|
const [selectedLocale, setSelectedLocale] = useState4(normalizeLocale(targetLocale ?? "") ?? targetLocale ?? "");
|
|
3475
3485
|
const [isTranslating, setIsTranslating] = useState4(false);
|
|
3476
3486
|
const [error, setError] = useState4();
|
|
3487
|
+
const [pendingRemoval, setPendingRemoval] = useState4();
|
|
3488
|
+
const pendingRemovalResolver = useRef2(void 0);
|
|
3477
3489
|
const currentSchema = onChange === void 0 ? draftSchema : schema;
|
|
3490
|
+
const confirmRemoveLocaleRenderer = confirmRemoveLocale ?? workspaceSlots?.confirmRemoveLocale;
|
|
3478
3491
|
const targetLocales = useMemo4(() => {
|
|
3479
3492
|
const locales = collectSchemaLocales2(currentSchema).allUniqueLocales;
|
|
3480
3493
|
return [.../* @__PURE__ */ new Set([...currentSchema.supportedLocales ?? [], ...locales])].filter(
|
|
@@ -3524,9 +3537,17 @@ function useTranslationWorkspace({
|
|
|
3524
3537
|
const setTranslation = useCallback3(
|
|
3525
3538
|
(slot, text) => {
|
|
3526
3539
|
if (readOnly) return;
|
|
3527
|
-
|
|
3540
|
+
const metadata = translationMetadata(slot.sourceText, sourceLocale, "manual");
|
|
3541
|
+
commit(updateSchemaTranslation(currentSchema, slot, text, sourceLocale, "manual"));
|
|
3542
|
+
onTranslationChange?.({
|
|
3543
|
+
slot,
|
|
3544
|
+
...slot.existingText === void 0 ? {} : { previousText: slot.existingText },
|
|
3545
|
+
nextText: text,
|
|
3546
|
+
mode: "manual",
|
|
3547
|
+
metadata
|
|
3548
|
+
});
|
|
3528
3549
|
},
|
|
3529
|
-
[commit, currentSchema, readOnly, sourceLocale]
|
|
3550
|
+
[commit, currentSchema, onTranslationChange, readOnly, sourceLocale]
|
|
3530
3551
|
);
|
|
3531
3552
|
const addLocale = useCallback3(
|
|
3532
3553
|
(locale) => {
|
|
@@ -3587,21 +3608,36 @@ function useTranslationWorkspace({
|
|
|
3587
3608
|
onLocaleRemoved?.(normalized);
|
|
3588
3609
|
};
|
|
3589
3610
|
const slotCount = collectTranslationSlots(currentSchema, normalized).length;
|
|
3611
|
+
const translatedSlotsCount = collectTranslationSlots(currentSchema, normalized).filter(
|
|
3612
|
+
(slot) => slot.existingText !== void 0 && slot.existingText.trim().length > 0
|
|
3613
|
+
).length;
|
|
3614
|
+
const requestConfirmation = () => new Promise((resolve) => {
|
|
3615
|
+
pendingRemovalResolver.current = resolve;
|
|
3616
|
+
setPendingRemoval({
|
|
3617
|
+
locale: normalized,
|
|
3618
|
+
localeLabel: localeOptions.find((option) => option.locale === normalized)?.label ?? normalized,
|
|
3619
|
+
translatedSlotsCount
|
|
3620
|
+
});
|
|
3621
|
+
});
|
|
3622
|
+
const removeAfterApproval = () => {
|
|
3623
|
+
if (confirmRemoveLocaleRenderer === void 0) {
|
|
3624
|
+
remove();
|
|
3625
|
+
return true;
|
|
3626
|
+
}
|
|
3627
|
+
return requestConfirmation();
|
|
3628
|
+
};
|
|
3590
3629
|
if (beforeRemoveLocale === void 0) {
|
|
3591
|
-
|
|
3592
|
-
return true;
|
|
3630
|
+
return removeAfterApproval();
|
|
3593
3631
|
}
|
|
3594
3632
|
try {
|
|
3595
3633
|
const decision = beforeRemoveLocale(normalized, { slotCount });
|
|
3596
3634
|
if (typeof decision === "boolean") {
|
|
3597
3635
|
if (!decision) return false;
|
|
3598
|
-
|
|
3599
|
-
return true;
|
|
3636
|
+
return removeAfterApproval();
|
|
3600
3637
|
}
|
|
3601
3638
|
return decision.then((allowed) => {
|
|
3602
3639
|
if (!allowed) return false;
|
|
3603
|
-
|
|
3604
|
-
return true;
|
|
3640
|
+
return removeAfterApproval();
|
|
3605
3641
|
});
|
|
3606
3642
|
} catch (cause) {
|
|
3607
3643
|
const workspaceError = {
|
|
@@ -3613,8 +3649,45 @@ function useTranslationWorkspace({
|
|
|
3613
3649
|
return Promise.reject(cause);
|
|
3614
3650
|
}
|
|
3615
3651
|
},
|
|
3616
|
-
[
|
|
3652
|
+
[
|
|
3653
|
+
activeLocale,
|
|
3654
|
+
beforeRemoveLocale,
|
|
3655
|
+
commit,
|
|
3656
|
+
confirmRemoveLocaleRenderer,
|
|
3657
|
+
currentSchema,
|
|
3658
|
+
localeOptions,
|
|
3659
|
+
onLocaleRemoved,
|
|
3660
|
+
readOnly,
|
|
3661
|
+
sourceLocale
|
|
3662
|
+
]
|
|
3617
3663
|
);
|
|
3664
|
+
const confirmPendingRemoval = useCallback3(() => {
|
|
3665
|
+
const pending = pendingRemoval;
|
|
3666
|
+
const resolve = pendingRemovalResolver.current;
|
|
3667
|
+
if (pending === void 0 || resolve === void 0) return;
|
|
3668
|
+
pendingRemovalResolver.current = void 0;
|
|
3669
|
+
setPendingRemoval(void 0);
|
|
3670
|
+
commit(removeLocaleFromSchema(currentSchema, pending.locale));
|
|
3671
|
+
if (activeLocale === pending.locale) setSelectedLocale("");
|
|
3672
|
+
onLocaleRemoved?.(pending.locale);
|
|
3673
|
+
resolve(true);
|
|
3674
|
+
}, [activeLocale, commit, currentSchema, onLocaleRemoved, pendingRemoval]);
|
|
3675
|
+
const cancelPendingRemoval = useCallback3(() => {
|
|
3676
|
+
const resolve = pendingRemovalResolver.current;
|
|
3677
|
+
if (resolve === void 0) return;
|
|
3678
|
+
pendingRemovalResolver.current = void 0;
|
|
3679
|
+
setPendingRemoval(void 0);
|
|
3680
|
+
resolve(false);
|
|
3681
|
+
}, []);
|
|
3682
|
+
const removeLocaleConfirmation = useMemo4(() => {
|
|
3683
|
+
if (confirmRemoveLocaleRenderer === void 0 || pendingRemoval === void 0) return null;
|
|
3684
|
+
return confirmRemoveLocaleRenderer({
|
|
3685
|
+
...pendingRemoval,
|
|
3686
|
+
isOpen: true,
|
|
3687
|
+
onConfirm: confirmPendingRemoval,
|
|
3688
|
+
onCancel: cancelPendingRemoval
|
|
3689
|
+
});
|
|
3690
|
+
}, [cancelPendingRemoval, confirmPendingRemoval, confirmRemoveLocaleRenderer, pendingRemoval]);
|
|
3618
3691
|
const translateAll = useCallback3(
|
|
3619
3692
|
async (options = {}) => {
|
|
3620
3693
|
if (readOnly) {
|
|
@@ -3651,6 +3724,7 @@ function useTranslationWorkspace({
|
|
|
3651
3724
|
}
|
|
3652
3725
|
setIsTranslating(true);
|
|
3653
3726
|
setError(void 0);
|
|
3727
|
+
onTranslationStart?.({ targetLocale: activeLocale, mode: "automatic" });
|
|
3654
3728
|
try {
|
|
3655
3729
|
const populated = await populateSchemaTranslations2(
|
|
3656
3730
|
currentSchema,
|
|
@@ -3663,6 +3737,17 @@ function useTranslationWorkspace({
|
|
|
3663
3737
|
}
|
|
3664
3738
|
);
|
|
3665
3739
|
commit(populated.schema);
|
|
3740
|
+
const missingSlotsCount = collectTranslationSlots(populated.schema, activeLocale).filter(
|
|
3741
|
+
(slot) => slot.status === "missing"
|
|
3742
|
+
).length;
|
|
3743
|
+
onTranslationSuccess?.({
|
|
3744
|
+
sourceLocale,
|
|
3745
|
+
targetLocale: activeLocale,
|
|
3746
|
+
mode: "automatic",
|
|
3747
|
+
updatedSlots: populated.report.updatedSlots,
|
|
3748
|
+
skippedSlots: populated.report.skippedSlots,
|
|
3749
|
+
missingSlotsCount
|
|
3750
|
+
});
|
|
3666
3751
|
return { success: true, report: populated.report };
|
|
3667
3752
|
} catch (cause) {
|
|
3668
3753
|
const workspaceError = {
|
|
@@ -3671,12 +3756,25 @@ function useTranslationWorkspace({
|
|
|
3671
3756
|
cause
|
|
3672
3757
|
};
|
|
3673
3758
|
setError(workspaceError);
|
|
3759
|
+
onTranslationError?.({ targetLocale: activeLocale, error: workspaceError });
|
|
3674
3760
|
return { success: false, error: workspaceError };
|
|
3675
3761
|
} finally {
|
|
3676
3762
|
setIsTranslating(false);
|
|
3677
3763
|
}
|
|
3678
3764
|
},
|
|
3679
|
-
[
|
|
3765
|
+
[
|
|
3766
|
+
activeLocale,
|
|
3767
|
+
commit,
|
|
3768
|
+
currentSchema,
|
|
3769
|
+
localeOptions,
|
|
3770
|
+
onTranslationError,
|
|
3771
|
+
onTranslationStart,
|
|
3772
|
+
onTranslationSuccess,
|
|
3773
|
+
readOnly,
|
|
3774
|
+
slots,
|
|
3775
|
+
sourceLocale,
|
|
3776
|
+
translationAdapter
|
|
3777
|
+
]
|
|
3680
3778
|
);
|
|
3681
3779
|
const translateSlot = useCallback3(
|
|
3682
3780
|
async (slot) => {
|
|
@@ -3694,7 +3792,15 @@ function useTranslationWorkspace({
|
|
|
3694
3792
|
setError(void 0);
|
|
3695
3793
|
try {
|
|
3696
3794
|
const text = await asAsyncAdapter(translationAdapter).translateText(slot.sourceText, slot.locale, sourceLocale);
|
|
3697
|
-
|
|
3795
|
+
const metadata = translationMetadata(slot.sourceText, sourceLocale, "automatic");
|
|
3796
|
+
commit(updateSchemaTranslation(currentSchema, slot, text, sourceLocale, "automatic"));
|
|
3797
|
+
onTranslationChange?.({
|
|
3798
|
+
slot,
|
|
3799
|
+
...slot.existingText === void 0 ? {} : { previousText: slot.existingText },
|
|
3800
|
+
nextText: text,
|
|
3801
|
+
mode: "automatic",
|
|
3802
|
+
metadata
|
|
3803
|
+
});
|
|
3698
3804
|
return { success: true };
|
|
3699
3805
|
} catch (cause) {
|
|
3700
3806
|
const workspaceError = {
|
|
@@ -3708,7 +3814,7 @@ function useTranslationWorkspace({
|
|
|
3708
3814
|
setIsTranslating(false);
|
|
3709
3815
|
}
|
|
3710
3816
|
},
|
|
3711
|
-
[commit, currentSchema, readOnly, sourceLocale, translationAdapter]
|
|
3817
|
+
[commit, currentSchema, onTranslationChange, readOnly, sourceLocale, translationAdapter]
|
|
3712
3818
|
);
|
|
3713
3819
|
return {
|
|
3714
3820
|
sourceLocale,
|
|
@@ -3725,6 +3831,7 @@ function useTranslationWorkspace({
|
|
|
3725
3831
|
addLocale,
|
|
3726
3832
|
isAddLocaleAllowed,
|
|
3727
3833
|
removeLocale,
|
|
3834
|
+
...removeLocaleConfirmation === null ? {} : { removeLocaleConfirmation },
|
|
3728
3835
|
setTranslation,
|
|
3729
3836
|
translateAll,
|
|
3730
3837
|
translateSlot,
|
|
@@ -3733,8 +3840,131 @@ function useTranslationWorkspace({
|
|
|
3733
3840
|
};
|
|
3734
3841
|
}
|
|
3735
3842
|
|
|
3843
|
+
// src/hooks/useTranslationComparison.ts
|
|
3844
|
+
function canonicalMetadata(metadata, sourceText, sourceLocale, translatedText) {
|
|
3845
|
+
if (translatedText === void 0 || translatedText.trim().length === 0) return void 0;
|
|
3846
|
+
if (metadata !== void 0 && typeof metadata.sourceLocale === "string" && typeof metadata.sourceTextHash === "string" && (metadata.translationSource === "automatic" || metadata.translationSource === "manual")) {
|
|
3847
|
+
return {
|
|
3848
|
+
sourceLocale: metadata.sourceLocale,
|
|
3849
|
+
sourceTextHash: metadata.sourceTextHash,
|
|
3850
|
+
translationSource: metadata.translationSource,
|
|
3851
|
+
...typeof metadata.translatedAt === "string" ? { translatedAt: metadata.translatedAt } : {},
|
|
3852
|
+
...typeof metadata.editedAt === "string" ? { editedAt: metadata.editedAt } : {}
|
|
3853
|
+
};
|
|
3854
|
+
}
|
|
3855
|
+
return {
|
|
3856
|
+
sourceLocale: typeof metadata?.sourceLocale === "string" ? metadata.sourceLocale : sourceLocale,
|
|
3857
|
+
sourceTextHash: computeSourceTextHash2(sourceText),
|
|
3858
|
+
translationSource: metadata?.isManual === true || metadata?.isManuallyEdited === true || metadata?.translationSource === "manual" ? "manual" : "automatic"
|
|
3859
|
+
};
|
|
3860
|
+
}
|
|
3861
|
+
function optionParentId(slot) {
|
|
3862
|
+
if (slot.kind !== "option" || slot.path === void 0) return void 0;
|
|
3863
|
+
return /^fields\.([^.]+)\.options\./u.exec(slot.path)?.[1];
|
|
3864
|
+
}
|
|
3865
|
+
function getNodeTitle(schema, slot) {
|
|
3866
|
+
if (slot.kind === "field") return schema.fields.find((field) => field.id === slot.nodeId)?.title;
|
|
3867
|
+
if (slot.kind === "page") return schema.pages?.find((page) => page.id === slot.nodeId)?.title;
|
|
3868
|
+
if (slot.kind === "option") return schema.fields.find((field) => field.id === optionParentId(slot))?.title;
|
|
3869
|
+
return void 0;
|
|
3870
|
+
}
|
|
3871
|
+
function comparisonItem(schema, slot, sourceLocale, hasAdapter) {
|
|
3872
|
+
const path = slot.path ?? `${slot.kind}.${slot.nodeId}.${slot.property}`;
|
|
3873
|
+
const nodeTitle = getNodeTitle(schema, slot);
|
|
3874
|
+
const metadata = canonicalMetadata(
|
|
3875
|
+
slot.existingTranslationMetadata,
|
|
3876
|
+
slot.sourceText,
|
|
3877
|
+
sourceLocale,
|
|
3878
|
+
slot.existingText
|
|
3879
|
+
);
|
|
3880
|
+
return {
|
|
3881
|
+
id: path,
|
|
3882
|
+
path,
|
|
3883
|
+
targetKind: slot.kind,
|
|
3884
|
+
targetProperty: slot.property,
|
|
3885
|
+
...nodeTitle === void 0 ? {} : { nodeTitle },
|
|
3886
|
+
sourceText: slot.sourceText,
|
|
3887
|
+
translatedText: slot.existingText ?? "",
|
|
3888
|
+
status: slot.status ?? "missing",
|
|
3889
|
+
...metadata === void 0 ? {} : { metadata },
|
|
3890
|
+
translatable: hasAdapter && slot.sourceText.trim().length > 0
|
|
3891
|
+
};
|
|
3892
|
+
}
|
|
3893
|
+
function summaryFor(items) {
|
|
3894
|
+
const counts = {
|
|
3895
|
+
missing: 0,
|
|
3896
|
+
translated: 0,
|
|
3897
|
+
stale: 0,
|
|
3898
|
+
manual: 0,
|
|
3899
|
+
"manual-stale": 0
|
|
3900
|
+
};
|
|
3901
|
+
for (const item of items) counts[item.status] += 1;
|
|
3902
|
+
return {
|
|
3903
|
+
total: items.length,
|
|
3904
|
+
translated: counts.translated + counts.manual,
|
|
3905
|
+
missing: counts.missing,
|
|
3906
|
+
stale: counts.stale + counts["manual-stale"],
|
|
3907
|
+
manual: counts.manual + counts["manual-stale"]
|
|
3908
|
+
};
|
|
3909
|
+
}
|
|
3910
|
+
function useTranslationComparison({
|
|
3911
|
+
schema,
|
|
3912
|
+
sourceLocale = schema.defaultLocale ?? "en",
|
|
3913
|
+
targetLocale,
|
|
3914
|
+
translationAdapter,
|
|
3915
|
+
readOnly = false,
|
|
3916
|
+
onChange,
|
|
3917
|
+
onTranslationChange
|
|
3918
|
+
}) {
|
|
3919
|
+
const workspace = useTranslationWorkspace({
|
|
3920
|
+
schema,
|
|
3921
|
+
sourceLocale,
|
|
3922
|
+
targetLocale,
|
|
3923
|
+
...translationAdapter === void 0 ? {} : { translationAdapter },
|
|
3924
|
+
readOnly,
|
|
3925
|
+
...onChange === void 0 ? {} : { onChange },
|
|
3926
|
+
...onTranslationChange === void 0 ? {} : { onTranslationChange }
|
|
3927
|
+
});
|
|
3928
|
+
const items = useMemo5(
|
|
3929
|
+
() => workspace.slots.map((slot) => comparisonItem(schema, slot, sourceLocale, translationAdapter !== void 0)),
|
|
3930
|
+
[schema, sourceLocale, translationAdapter, workspace.slots]
|
|
3931
|
+
);
|
|
3932
|
+
const itemByPath = useMemo5(() => new Map(workspace.slots.map((slot) => [slot.path, slot])), [workspace.slots]);
|
|
3933
|
+
const updateTranslation = useCallback4(
|
|
3934
|
+
(path, text) => {
|
|
3935
|
+
const slot = itemByPath.get(path);
|
|
3936
|
+
if (slot !== void 0) workspace.setTranslation(slot, text);
|
|
3937
|
+
},
|
|
3938
|
+
[itemByPath, workspace]
|
|
3939
|
+
);
|
|
3940
|
+
const translateSingle = useCallback4(
|
|
3941
|
+
async (path) => {
|
|
3942
|
+
const slot = itemByPath.get(path);
|
|
3943
|
+
if (slot === void 0) return;
|
|
3944
|
+
const result = await workspace.translateSlot(slot);
|
|
3945
|
+
if (!result.success) throw new Error(result.error?.type ?? "Translation failed.");
|
|
3946
|
+
},
|
|
3947
|
+
[itemByPath, workspace]
|
|
3948
|
+
);
|
|
3949
|
+
const translateAll = useCallback4(async () => {
|
|
3950
|
+
const result = await workspace.translateAll();
|
|
3951
|
+
if (result.report !== void 0) return result.report;
|
|
3952
|
+
throw new Error(result.error?.type ?? "Translation failed.");
|
|
3953
|
+
}, [workspace]);
|
|
3954
|
+
return {
|
|
3955
|
+
sourceLocale: workspace.sourceLocale,
|
|
3956
|
+
targetLocale: workspace.targetLocale,
|
|
3957
|
+
items,
|
|
3958
|
+
summary: summaryFor(items),
|
|
3959
|
+
isTranslating: workspace.isTranslating,
|
|
3960
|
+
updateTranslation,
|
|
3961
|
+
translateSingle,
|
|
3962
|
+
translateAll
|
|
3963
|
+
};
|
|
3964
|
+
}
|
|
3965
|
+
|
|
3736
3966
|
// src/receipt.ts
|
|
3737
|
-
import { useEffect as useEffect2, useMemo as
|
|
3967
|
+
import { useEffect as useEffect2, useMemo as useMemo6, useState as useState5 } from "react";
|
|
3738
3968
|
function submissionReceiptQueryKey(formId, formVersion) {
|
|
3739
3969
|
return `${formId}:v${formVersion}`;
|
|
3740
3970
|
}
|
|
@@ -3805,7 +4035,7 @@ function createLocalStorageSubmissionReceiptStore(options = {}) {
|
|
|
3805
4035
|
}
|
|
3806
4036
|
function useSubmissionReceipts(store, queries) {
|
|
3807
4037
|
const querySignature = JSON.stringify(queries.map(({ formId, formVersion }) => [formId, formVersion]));
|
|
3808
|
-
const stableQueries =
|
|
4038
|
+
const stableQueries = useMemo6(() => {
|
|
3809
4039
|
const parsed = JSON.parse(querySignature);
|
|
3810
4040
|
if (!Array.isArray(parsed)) return [];
|
|
3811
4041
|
return parsed.flatMap(
|
|
@@ -3852,17 +4082,19 @@ function useSubmissionReceipts(store, queries) {
|
|
|
3852
4082
|
|
|
3853
4083
|
// src/renderer.tsx
|
|
3854
4084
|
import {
|
|
4085
|
+
FormSubmissionError as FormSubmissionError2,
|
|
4086
|
+
isFormSubmissionSerializedError as isFormSubmissionSerializedError2,
|
|
3855
4087
|
selectVisibleAnswers as selectVisibleAnswers2,
|
|
3856
4088
|
validateAnswers as validateAnswers2
|
|
3857
4089
|
} from "@form-engine-ts/core";
|
|
3858
4090
|
import {
|
|
3859
4091
|
Fragment as Fragment2,
|
|
3860
|
-
useCallback as
|
|
4092
|
+
useCallback as useCallback5,
|
|
3861
4093
|
useContext as useContext4,
|
|
3862
4094
|
useEffect as useEffect3,
|
|
3863
4095
|
useId,
|
|
3864
|
-
useMemo as
|
|
3865
|
-
useRef as
|
|
4096
|
+
useMemo as useMemo7,
|
|
4097
|
+
useRef as useRef3,
|
|
3866
4098
|
useState as useState6
|
|
3867
4099
|
} from "react";
|
|
3868
4100
|
import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
@@ -4378,8 +4610,8 @@ function ContextFormRenderer({
|
|
|
4378
4610
|
const i18n = useFormEngineI18n();
|
|
4379
4611
|
const isProviderValue = useContext4(FormEngineI18nProviderScopeContext);
|
|
4380
4612
|
const prefix = useId().replace(/:/g, "");
|
|
4381
|
-
const formRef =
|
|
4382
|
-
const loadedDraftKey =
|
|
4613
|
+
const formRef = useRef3(null);
|
|
4614
|
+
const loadedDraftKey = useRef3(null);
|
|
4383
4615
|
const [draftRestored, setDraftRestored] = useState6(false);
|
|
4384
4616
|
const [currentPageIndex, setCurrentPageIndex] = useState6(0);
|
|
4385
4617
|
const [focusFieldId, setFocusFieldId] = useState6(null);
|
|
@@ -4389,20 +4621,20 @@ function ContextFormRenderer({
|
|
|
4389
4621
|
const [receipt, setReceipt] = useState6(null);
|
|
4390
4622
|
const [completionData, setCompletionData] = useState6(null);
|
|
4391
4623
|
const [receiptLoaded, setReceiptLoaded] = useState6(receiptStore === void 0);
|
|
4392
|
-
const rendererSubmissionInFlight =
|
|
4393
|
-
const fallbackAttemptId =
|
|
4394
|
-
const completionRef =
|
|
4395
|
-
const confirmationRef =
|
|
4624
|
+
const rendererSubmissionInFlight = useRef3(false);
|
|
4625
|
+
const fallbackAttemptId = useRef3(null);
|
|
4626
|
+
const completionRef = useRef3(null);
|
|
4627
|
+
const confirmationRef = useRef3(null);
|
|
4396
4628
|
const pages = form.schema.pages;
|
|
4397
|
-
const visiblePageIndexes =
|
|
4629
|
+
const visiblePageIndexes = useMemo7(
|
|
4398
4630
|
() => pages === void 0 ? [] : pages.flatMap((page, index) => form.pageVisibility[page.id] === true ? [index] : []),
|
|
4399
4631
|
[form.pageVisibility, pages]
|
|
4400
4632
|
);
|
|
4401
4633
|
const activePage = pages?.[currentPageIndex];
|
|
4402
4634
|
const activeVisibleIndex = visiblePageIndexes.indexOf(currentPageIndex);
|
|
4403
4635
|
const fieldIds = activePage === void 0 ? void 0 : new Set(activePage.questionIds);
|
|
4404
|
-
const visibleValues =
|
|
4405
|
-
const visibleItems =
|
|
4636
|
+
const visibleValues = useMemo7(() => selectVisibleAnswers2(form.schema, form.values), [form.schema, form.values]);
|
|
4637
|
+
const visibleItems = useMemo7(
|
|
4406
4638
|
() => buildSubmittedItems(form.schema, form.values, form.visibility, (key) => form.translate(key), false),
|
|
4407
4639
|
[form.schema, form.translate, form.values, form.visibility]
|
|
4408
4640
|
);
|
|
@@ -4411,7 +4643,7 @@ function ContextFormRenderer({
|
|
|
4411
4643
|
const submitState = confirmation === null && !guardsPending ? form.submitStatus : "confirming";
|
|
4412
4644
|
const interactionLocked = submitState === "confirming" || submitState === "submitting";
|
|
4413
4645
|
const isReplaceMode = successRenderMode === "replace" || hideFormOnSuccess;
|
|
4414
|
-
const resolveMessage =
|
|
4646
|
+
const resolveMessage = useCallback5(
|
|
4415
4647
|
(key, fallback) => {
|
|
4416
4648
|
const providerDefault = isProviderValue ? i18n.translator(`renderer.${key}`) : void 0;
|
|
4417
4649
|
const defaultText = fallback ?? (providerDefault === "" ? void 0 : providerDefault) ?? DEFAULT_RENDERER_MESSAGES[form.locale.toLowerCase().startsWith("ja") ? "ja" : "en"][key] ?? key;
|
|
@@ -4420,11 +4652,11 @@ function ContextFormRenderer({
|
|
|
4420
4652
|
},
|
|
4421
4653
|
[form.locale, i18n, isProviderValue, messageResolver, messages]
|
|
4422
4654
|
);
|
|
4423
|
-
const fieldTranslate =
|
|
4655
|
+
const fieldTranslate = useCallback5(
|
|
4424
4656
|
(key, params) => key === "validation.required" && (messages.requiredField !== void 0 || messageResolver !== void 0) ? resolveMessage("requiredField") : form.translate(key, params),
|
|
4425
4657
|
[form.translate, messageResolver, messages.requiredField, resolveMessage]
|
|
4426
4658
|
);
|
|
4427
|
-
const focusSubmitButton =
|
|
4659
|
+
const focusSubmitButton = useCallback5(() => {
|
|
4428
4660
|
const button = formRef.current?.querySelector(".fe-submit, button[type='submit'], button");
|
|
4429
4661
|
button?.focus();
|
|
4430
4662
|
}, []);
|
|
@@ -4635,8 +4867,9 @@ function ContextFormRenderer({
|
|
|
4635
4867
|
return result;
|
|
4636
4868
|
}
|
|
4637
4869
|
if (result.status === "error") {
|
|
4638
|
-
|
|
4639
|
-
|
|
4870
|
+
const payload = result.error instanceof FormSubmissionError2 ? result.error.payload : isFormSubmissionSerializedError2(result.error) ? result.error : void 0;
|
|
4871
|
+
if (payload !== void 0) {
|
|
4872
|
+
const fieldErrors = payload.fieldErrors ?? {};
|
|
4640
4873
|
form.setServerErrors?.(fieldErrors);
|
|
4641
4874
|
const firstServerFieldId = form.schema.fields.find((field) => Object.hasOwn(fieldErrors, field.id))?.id ?? Object.keys(fieldErrors)[0];
|
|
4642
4875
|
if (firstServerFieldId !== void 0) {
|
|
@@ -4644,6 +4877,9 @@ function ContextFormRenderer({
|
|
|
4644
4877
|
if (invalidPageIndex !== void 0 && invalidPageIndex >= 0) setCurrentPageIndex(invalidPageIndex);
|
|
4645
4878
|
focusFirstIssue(firstServerFieldId);
|
|
4646
4879
|
}
|
|
4880
|
+
if (payload.piiFindings !== void 0 && payload.piiFindings.length > 0) {
|
|
4881
|
+
setConfirmation({ findings: payload.piiFindings, generic: false });
|
|
4882
|
+
}
|
|
4647
4883
|
}
|
|
4648
4884
|
return result;
|
|
4649
4885
|
}
|
|
@@ -4945,8 +5181,8 @@ function ContextFormRenderer({
|
|
|
4945
5181
|
] }),
|
|
4946
5182
|
/* @__PURE__ */ jsxs2("div", { className: "fe-status", "aria-live": "polite", children: [
|
|
4947
5183
|
form.submitStatus === "success" ? completionRegion : null,
|
|
4948
|
-
form.submitStatus === "error" && form.submitError !== null ? slots.renderSubmitError?.({ error: form.submitError, onRetry: () => void submitValues() }) ?? (form.submitError instanceof
|
|
4949
|
-
form.submitError.payload.
|
|
5184
|
+
form.submitStatus === "error" && form.submitError !== null ? slots.renderSubmitError?.({ error: form.submitError, onRetry: () => void submitValues() }) ?? (form.submitError instanceof FormSubmissionError2 && (form.submitError.payload.formErrors?.length ?? 0) > 0 ? /* @__PURE__ */ jsxs2("div", { role: "alert", children: [
|
|
5185
|
+
form.submitError.payload.formErrors?.map((message) => /* @__PURE__ */ jsx4("div", { children: message }, message)),
|
|
4950
5186
|
/* @__PURE__ */ jsx4("button", { type: "button", onClick: () => void submitValues(), children: resolveMessage("retryButton") })
|
|
4951
5187
|
] }) : /* @__PURE__ */ jsxs2("div", { role: "alert", children: [
|
|
4952
5188
|
errorMessageKey === void 0 ? resolveMessage("serverErrorSummary") : form.translate(errorMessageKey),
|
|
@@ -5029,6 +5265,9 @@ function FormRenderer(props) {
|
|
|
5029
5265
|
}
|
|
5030
5266
|
);
|
|
5031
5267
|
}
|
|
5268
|
+
|
|
5269
|
+
// src/types.ts
|
|
5270
|
+
import { FormSubmissionError as FormSubmissionError3 } from "@form-engine-ts/core";
|
|
5032
5271
|
export {
|
|
5033
5272
|
BUILDER_TRANSLATION_ALIASES,
|
|
5034
5273
|
BUILDER_TRANSLATION_KEYS,
|
|
@@ -5037,7 +5276,7 @@ export {
|
|
|
5037
5276
|
FormEngineI18nProvider,
|
|
5038
5277
|
FormProvider,
|
|
5039
5278
|
FormRenderer,
|
|
5040
|
-
FormSubmissionError,
|
|
5279
|
+
FormSubmissionError3 as FormSubmissionError,
|
|
5041
5280
|
createLocalStorageSubmissionAttemptStore,
|
|
5042
5281
|
createLocalStorageSubmissionReceiptStore,
|
|
5043
5282
|
isTranslationUnresolved,
|
|
@@ -5052,6 +5291,7 @@ export {
|
|
|
5052
5291
|
useFormBuilder,
|
|
5053
5292
|
useFormEngineI18n,
|
|
5054
5293
|
useSubmissionReceipts,
|
|
5294
|
+
useTranslationComparison,
|
|
5055
5295
|
useTranslationWorkspace,
|
|
5056
5296
|
validateLocalePipeline
|
|
5057
5297
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@form-engine-ts/react",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.1.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/privacy": "6.1.0",
|
|
46
|
+
"@form-engine-ts/core": "6.1.0"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"react": ">=18.2 <20",
|