@form-engine-ts/react 6.0.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 +4 -0
- package/dist/index.cjs +193 -62
- package/dist/index.d.cts +61 -8
- package/dist/index.d.ts +61 -8
- package/dist/index.js +170 -34
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -93,6 +93,10 @@ localized values and metadata; the default locale remains protected.
|
|
|
93
93
|
`onTranslationChange` with typed lifecycle payloads. The MUI workspace accepts a `confirmRemoveLocale` slot for an
|
|
94
94
|
async confirmation UI; its `onConfirm` callback completes the locale removal.
|
|
95
95
|
|
|
96
|
+
`useTranslationComparison` provides a focused, controlled comparison model for form, page, field, and option text.
|
|
97
|
+
Its items expose source text, target text, status, canonical metadata, and path-based `updateTranslation`,
|
|
98
|
+
`translateSingle`, and `translateAll` actions.
|
|
99
|
+
|
|
96
100
|
Wrap a builder or renderer in `FormEngineI18nProvider` to supply a UI locale and typed Core translator independently
|
|
97
101
|
from the schema's `defaultLocale` and `supportedLocales`:
|
|
98
102
|
|
package/dist/index.cjs
CHANGED
|
@@ -27,7 +27,7 @@ __export(index_exports, {
|
|
|
27
27
|
FormEngineI18nProvider: () => FormEngineI18nProvider,
|
|
28
28
|
FormProvider: () => FormProvider,
|
|
29
29
|
FormRenderer: () => FormRenderer,
|
|
30
|
-
FormSubmissionError: () => FormSubmissionError,
|
|
30
|
+
FormSubmissionError: () => import_core8.FormSubmissionError,
|
|
31
31
|
createLocalStorageSubmissionAttemptStore: () => createLocalStorageSubmissionAttemptStore,
|
|
32
32
|
createLocalStorageSubmissionReceiptStore: () => createLocalStorageSubmissionReceiptStore,
|
|
33
33
|
isTranslationUnresolved: () => isTranslationUnresolved,
|
|
@@ -42,6 +42,7 @@ __export(index_exports, {
|
|
|
42
42
|
useFormBuilder: () => useFormBuilder,
|
|
43
43
|
useFormEngineI18n: () => useFormEngineI18n,
|
|
44
44
|
useSubmissionReceipts: () => useSubmissionReceipts,
|
|
45
|
+
useTranslationComparison: () => useTranslationComparison,
|
|
45
46
|
useTranslationWorkspace: () => useTranslationWorkspace,
|
|
46
47
|
validateLocalePipeline: () => validateLocalePipeline
|
|
47
48
|
});
|
|
@@ -978,6 +979,8 @@ function FormEngineI18nProvider({
|
|
|
978
979
|
fallbackLocale = "en",
|
|
979
980
|
messages,
|
|
980
981
|
customCatalogs,
|
|
982
|
+
onMissingKey,
|
|
983
|
+
strict,
|
|
981
984
|
translator: customTranslator,
|
|
982
985
|
children
|
|
983
986
|
}) {
|
|
@@ -987,9 +990,11 @@ function FormEngineI18nProvider({
|
|
|
987
990
|
locale,
|
|
988
991
|
fallbackLocale,
|
|
989
992
|
...messages === void 0 ? {} : { messages },
|
|
990
|
-
...customCatalogs === void 0 ? {} : { customCatalogs }
|
|
993
|
+
...customCatalogs === void 0 ? {} : { customCatalogs },
|
|
994
|
+
...onMissingKey === void 0 ? {} : { onMissingKey },
|
|
995
|
+
...strict === void 0 ? {} : { strict }
|
|
991
996
|
});
|
|
992
|
-
}, [customCatalogs, customTranslator, fallbackLocale, locale, messages]);
|
|
997
|
+
}, [customCatalogs, customTranslator, fallbackLocale, locale, messages, onMissingKey, strict]);
|
|
993
998
|
const value = (0, import_react2.useMemo)(() => ({ uiLocale: locale, translator }), [locale, translator]);
|
|
994
999
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FormEngineI18nProviderScopeContext.Provider, { value: true, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FormEngineI18nContext.Provider, { value, children }) });
|
|
995
1000
|
}
|
|
@@ -2566,6 +2571,7 @@ function FormBuilder({
|
|
|
2566
2571
|
LocalizationSlot,
|
|
2567
2572
|
{
|
|
2568
2573
|
schema,
|
|
2574
|
+
onChange,
|
|
2569
2575
|
translate,
|
|
2570
2576
|
currentLocale: editingLocale,
|
|
2571
2577
|
onCurrentLocaleChange: setEditingLocale,
|
|
@@ -3088,18 +3094,6 @@ function FormBuilder({
|
|
|
3088
3094
|
// src/context.tsx
|
|
3089
3095
|
var import_core4 = require("@form-engine-ts/core");
|
|
3090
3096
|
var import_react4 = require("react");
|
|
3091
|
-
|
|
3092
|
-
// src/types.ts
|
|
3093
|
-
var FormSubmissionError = class extends Error {
|
|
3094
|
-
payload;
|
|
3095
|
-
constructor(message, payload) {
|
|
3096
|
-
super(message);
|
|
3097
|
-
this.name = "FormSubmissionError";
|
|
3098
|
-
this.payload = payload ?? { formError: message };
|
|
3099
|
-
}
|
|
3100
|
-
};
|
|
3101
|
-
|
|
3102
|
-
// src/context.tsx
|
|
3103
3097
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
3104
3098
|
var FormContext = (0, import_react4.createContext)(null);
|
|
3105
3099
|
function issuesByField(issues) {
|
|
@@ -3236,10 +3230,12 @@ function FormProvider({
|
|
|
3236
3230
|
setSubmitStatus("success");
|
|
3237
3231
|
return response === void 0 ? { status: "success" } : { status: "success", response };
|
|
3238
3232
|
} catch (cause) {
|
|
3239
|
-
const error = isServerErrorPayload(cause) ? new FormSubmissionError(
|
|
3240
|
-
|
|
3241
|
-
cause
|
|
3242
|
-
|
|
3233
|
+
const error = (0, import_core4.isFormSubmissionSerializedError)(cause) ? new import_core4.FormSubmissionError(cause) : isServerErrorPayload(cause) ? new import_core4.FormSubmissionError({
|
|
3234
|
+
code: "VALIDATION_FAILED",
|
|
3235
|
+
messageKey: cause.formError ?? "Submission failed",
|
|
3236
|
+
fieldErrors: cause.fieldErrors ?? {},
|
|
3237
|
+
formErrors: cause.formError === void 0 ? [] : [cause.formError]
|
|
3238
|
+
}) : cause instanceof Error ? cause : new Error(String(cause));
|
|
3243
3239
|
setSubmitError(error);
|
|
3244
3240
|
setSubmitStatus("error");
|
|
3245
3241
|
return { status: "error", error };
|
|
@@ -3307,6 +3303,10 @@ function useField(fieldId) {
|
|
|
3307
3303
|
return { field, value: form.values[fieldId], error: form.errors[fieldId], setValue };
|
|
3308
3304
|
}
|
|
3309
3305
|
|
|
3306
|
+
// src/hooks/useTranslationComparison.ts
|
|
3307
|
+
var import_core6 = require("@form-engine-ts/core");
|
|
3308
|
+
var import_react6 = require("react");
|
|
3309
|
+
|
|
3310
3310
|
// src/hooks/useTranslationWorkspace.ts
|
|
3311
3311
|
var import_core5 = require("@form-engine-ts/core");
|
|
3312
3312
|
var import_react5 = require("react");
|
|
@@ -3864,8 +3864,131 @@ function useTranslationWorkspace({
|
|
|
3864
3864
|
};
|
|
3865
3865
|
}
|
|
3866
3866
|
|
|
3867
|
+
// src/hooks/useTranslationComparison.ts
|
|
3868
|
+
function canonicalMetadata(metadata, sourceText, sourceLocale, translatedText) {
|
|
3869
|
+
if (translatedText === void 0 || translatedText.trim().length === 0) return void 0;
|
|
3870
|
+
if (metadata !== void 0 && typeof metadata.sourceLocale === "string" && typeof metadata.sourceTextHash === "string" && (metadata.translationSource === "automatic" || metadata.translationSource === "manual")) {
|
|
3871
|
+
return {
|
|
3872
|
+
sourceLocale: metadata.sourceLocale,
|
|
3873
|
+
sourceTextHash: metadata.sourceTextHash,
|
|
3874
|
+
translationSource: metadata.translationSource,
|
|
3875
|
+
...typeof metadata.translatedAt === "string" ? { translatedAt: metadata.translatedAt } : {},
|
|
3876
|
+
...typeof metadata.editedAt === "string" ? { editedAt: metadata.editedAt } : {}
|
|
3877
|
+
};
|
|
3878
|
+
}
|
|
3879
|
+
return {
|
|
3880
|
+
sourceLocale: typeof metadata?.sourceLocale === "string" ? metadata.sourceLocale : sourceLocale,
|
|
3881
|
+
sourceTextHash: (0, import_core6.computeSourceTextHash)(sourceText),
|
|
3882
|
+
translationSource: metadata?.isManual === true || metadata?.isManuallyEdited === true || metadata?.translationSource === "manual" ? "manual" : "automatic"
|
|
3883
|
+
};
|
|
3884
|
+
}
|
|
3885
|
+
function optionParentId(slot) {
|
|
3886
|
+
if (slot.kind !== "option" || slot.path === void 0) return void 0;
|
|
3887
|
+
return /^fields\.([^.]+)\.options\./u.exec(slot.path)?.[1];
|
|
3888
|
+
}
|
|
3889
|
+
function getNodeTitle(schema, slot) {
|
|
3890
|
+
if (slot.kind === "field") return schema.fields.find((field) => field.id === slot.nodeId)?.title;
|
|
3891
|
+
if (slot.kind === "page") return schema.pages?.find((page) => page.id === slot.nodeId)?.title;
|
|
3892
|
+
if (slot.kind === "option") return schema.fields.find((field) => field.id === optionParentId(slot))?.title;
|
|
3893
|
+
return void 0;
|
|
3894
|
+
}
|
|
3895
|
+
function comparisonItem(schema, slot, sourceLocale, hasAdapter) {
|
|
3896
|
+
const path = slot.path ?? `${slot.kind}.${slot.nodeId}.${slot.property}`;
|
|
3897
|
+
const nodeTitle = getNodeTitle(schema, slot);
|
|
3898
|
+
const metadata = canonicalMetadata(
|
|
3899
|
+
slot.existingTranslationMetadata,
|
|
3900
|
+
slot.sourceText,
|
|
3901
|
+
sourceLocale,
|
|
3902
|
+
slot.existingText
|
|
3903
|
+
);
|
|
3904
|
+
return {
|
|
3905
|
+
id: path,
|
|
3906
|
+
path,
|
|
3907
|
+
targetKind: slot.kind,
|
|
3908
|
+
targetProperty: slot.property,
|
|
3909
|
+
...nodeTitle === void 0 ? {} : { nodeTitle },
|
|
3910
|
+
sourceText: slot.sourceText,
|
|
3911
|
+
translatedText: slot.existingText ?? "",
|
|
3912
|
+
status: slot.status ?? "missing",
|
|
3913
|
+
...metadata === void 0 ? {} : { metadata },
|
|
3914
|
+
translatable: hasAdapter && slot.sourceText.trim().length > 0
|
|
3915
|
+
};
|
|
3916
|
+
}
|
|
3917
|
+
function summaryFor(items) {
|
|
3918
|
+
const counts = {
|
|
3919
|
+
missing: 0,
|
|
3920
|
+
translated: 0,
|
|
3921
|
+
stale: 0,
|
|
3922
|
+
manual: 0,
|
|
3923
|
+
"manual-stale": 0
|
|
3924
|
+
};
|
|
3925
|
+
for (const item of items) counts[item.status] += 1;
|
|
3926
|
+
return {
|
|
3927
|
+
total: items.length,
|
|
3928
|
+
translated: counts.translated + counts.manual,
|
|
3929
|
+
missing: counts.missing,
|
|
3930
|
+
stale: counts.stale + counts["manual-stale"],
|
|
3931
|
+
manual: counts.manual + counts["manual-stale"]
|
|
3932
|
+
};
|
|
3933
|
+
}
|
|
3934
|
+
function useTranslationComparison({
|
|
3935
|
+
schema,
|
|
3936
|
+
sourceLocale = schema.defaultLocale ?? "en",
|
|
3937
|
+
targetLocale,
|
|
3938
|
+
translationAdapter,
|
|
3939
|
+
readOnly = false,
|
|
3940
|
+
onChange,
|
|
3941
|
+
onTranslationChange
|
|
3942
|
+
}) {
|
|
3943
|
+
const workspace = useTranslationWorkspace({
|
|
3944
|
+
schema,
|
|
3945
|
+
sourceLocale,
|
|
3946
|
+
targetLocale,
|
|
3947
|
+
...translationAdapter === void 0 ? {} : { translationAdapter },
|
|
3948
|
+
readOnly,
|
|
3949
|
+
...onChange === void 0 ? {} : { onChange },
|
|
3950
|
+
...onTranslationChange === void 0 ? {} : { onTranslationChange }
|
|
3951
|
+
});
|
|
3952
|
+
const items = (0, import_react6.useMemo)(
|
|
3953
|
+
() => workspace.slots.map((slot) => comparisonItem(schema, slot, sourceLocale, translationAdapter !== void 0)),
|
|
3954
|
+
[schema, sourceLocale, translationAdapter, workspace.slots]
|
|
3955
|
+
);
|
|
3956
|
+
const itemByPath = (0, import_react6.useMemo)(() => new Map(workspace.slots.map((slot) => [slot.path, slot])), [workspace.slots]);
|
|
3957
|
+
const updateTranslation = (0, import_react6.useCallback)(
|
|
3958
|
+
(path, text) => {
|
|
3959
|
+
const slot = itemByPath.get(path);
|
|
3960
|
+
if (slot !== void 0) workspace.setTranslation(slot, text);
|
|
3961
|
+
},
|
|
3962
|
+
[itemByPath, workspace]
|
|
3963
|
+
);
|
|
3964
|
+
const translateSingle = (0, import_react6.useCallback)(
|
|
3965
|
+
async (path) => {
|
|
3966
|
+
const slot = itemByPath.get(path);
|
|
3967
|
+
if (slot === void 0) return;
|
|
3968
|
+
const result = await workspace.translateSlot(slot);
|
|
3969
|
+
if (!result.success) throw new Error(result.error?.type ?? "Translation failed.");
|
|
3970
|
+
},
|
|
3971
|
+
[itemByPath, workspace]
|
|
3972
|
+
);
|
|
3973
|
+
const translateAll = (0, import_react6.useCallback)(async () => {
|
|
3974
|
+
const result = await workspace.translateAll();
|
|
3975
|
+
if (result.report !== void 0) return result.report;
|
|
3976
|
+
throw new Error(result.error?.type ?? "Translation failed.");
|
|
3977
|
+
}, [workspace]);
|
|
3978
|
+
return {
|
|
3979
|
+
sourceLocale: workspace.sourceLocale,
|
|
3980
|
+
targetLocale: workspace.targetLocale,
|
|
3981
|
+
items,
|
|
3982
|
+
summary: summaryFor(items),
|
|
3983
|
+
isTranslating: workspace.isTranslating,
|
|
3984
|
+
updateTranslation,
|
|
3985
|
+
translateSingle,
|
|
3986
|
+
translateAll
|
|
3987
|
+
};
|
|
3988
|
+
}
|
|
3989
|
+
|
|
3867
3990
|
// src/receipt.ts
|
|
3868
|
-
var
|
|
3991
|
+
var import_react7 = require("react");
|
|
3869
3992
|
function submissionReceiptQueryKey(formId, formVersion) {
|
|
3870
3993
|
return `${formId}:v${formVersion}`;
|
|
3871
3994
|
}
|
|
@@ -3936,19 +4059,19 @@ function createLocalStorageSubmissionReceiptStore(options = {}) {
|
|
|
3936
4059
|
}
|
|
3937
4060
|
function useSubmissionReceipts(store, queries) {
|
|
3938
4061
|
const querySignature = JSON.stringify(queries.map(({ formId, formVersion }) => [formId, formVersion]));
|
|
3939
|
-
const stableQueries = (0,
|
|
4062
|
+
const stableQueries = (0, import_react7.useMemo)(() => {
|
|
3940
4063
|
const parsed = JSON.parse(querySignature);
|
|
3941
4064
|
if (!Array.isArray(parsed)) return [];
|
|
3942
4065
|
return parsed.flatMap(
|
|
3943
4066
|
(entry) => Array.isArray(entry) && typeof entry[0] === "string" && typeof entry[1] === "number" && Number.isSafeInteger(entry[1]) ? [{ formId: entry[0], formVersion: entry[1] }] : []
|
|
3944
4067
|
);
|
|
3945
4068
|
}, [querySignature]);
|
|
3946
|
-
const [state, setState] = (0,
|
|
4069
|
+
const [state, setState] = (0, import_react7.useState)({
|
|
3947
4070
|
receipts: /* @__PURE__ */ new Map(),
|
|
3948
4071
|
isLoading: stableQueries.length > 0,
|
|
3949
4072
|
error: null
|
|
3950
4073
|
});
|
|
3951
|
-
(0,
|
|
4074
|
+
(0, import_react7.useEffect)(() => {
|
|
3952
4075
|
let active = true;
|
|
3953
4076
|
if (stableQueries.length === 0) {
|
|
3954
4077
|
setState({ receipts: /* @__PURE__ */ new Map(), isLoading: false, error: null });
|
|
@@ -3982,8 +4105,8 @@ function useSubmissionReceipts(store, queries) {
|
|
|
3982
4105
|
}
|
|
3983
4106
|
|
|
3984
4107
|
// src/renderer.tsx
|
|
3985
|
-
var
|
|
3986
|
-
var
|
|
4108
|
+
var import_core7 = require("@form-engine-ts/core");
|
|
4109
|
+
var import_react8 = require("react");
|
|
3987
4110
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
3988
4111
|
var isChoiceFieldType = (type) => type === "radio" || type === "checkbox" || type === "multi-select" || type === "select";
|
|
3989
4112
|
function resolveChoiceFieldLayout(type, appearance, groupedChoiceFieldsLegacy = false) {
|
|
@@ -4495,33 +4618,33 @@ function ContextFormRenderer({
|
|
|
4495
4618
|
}) {
|
|
4496
4619
|
const form = useForm();
|
|
4497
4620
|
const i18n = useFormEngineI18n();
|
|
4498
|
-
const isProviderValue = (0,
|
|
4499
|
-
const prefix = (0,
|
|
4500
|
-
const formRef = (0,
|
|
4501
|
-
const loadedDraftKey = (0,
|
|
4502
|
-
const [draftRestored, setDraftRestored] = (0,
|
|
4503
|
-
const [currentPageIndex, setCurrentPageIndex] = (0,
|
|
4504
|
-
const [focusFieldId, setFocusFieldId] = (0,
|
|
4505
|
-
const [confirmation, setConfirmation] = (0,
|
|
4506
|
-
const [guardMessage, setGuardMessage] = (0,
|
|
4507
|
-
const [guardsPending, setGuardsPending] = (0,
|
|
4508
|
-
const [receipt, setReceipt] = (0,
|
|
4509
|
-
const [completionData, setCompletionData] = (0,
|
|
4510
|
-
const [receiptLoaded, setReceiptLoaded] = (0,
|
|
4511
|
-
const rendererSubmissionInFlight = (0,
|
|
4512
|
-
const fallbackAttemptId = (0,
|
|
4513
|
-
const completionRef = (0,
|
|
4514
|
-
const confirmationRef = (0,
|
|
4621
|
+
const isProviderValue = (0, import_react8.useContext)(FormEngineI18nProviderScopeContext);
|
|
4622
|
+
const prefix = (0, import_react8.useId)().replace(/:/g, "");
|
|
4623
|
+
const formRef = (0, import_react8.useRef)(null);
|
|
4624
|
+
const loadedDraftKey = (0, import_react8.useRef)(null);
|
|
4625
|
+
const [draftRestored, setDraftRestored] = (0, import_react8.useState)(false);
|
|
4626
|
+
const [currentPageIndex, setCurrentPageIndex] = (0, import_react8.useState)(0);
|
|
4627
|
+
const [focusFieldId, setFocusFieldId] = (0, import_react8.useState)(null);
|
|
4628
|
+
const [confirmation, setConfirmation] = (0, import_react8.useState)(null);
|
|
4629
|
+
const [guardMessage, setGuardMessage] = (0, import_react8.useState)(null);
|
|
4630
|
+
const [guardsPending, setGuardsPending] = (0, import_react8.useState)(false);
|
|
4631
|
+
const [receipt, setReceipt] = (0, import_react8.useState)(null);
|
|
4632
|
+
const [completionData, setCompletionData] = (0, import_react8.useState)(null);
|
|
4633
|
+
const [receiptLoaded, setReceiptLoaded] = (0, import_react8.useState)(receiptStore === void 0);
|
|
4634
|
+
const rendererSubmissionInFlight = (0, import_react8.useRef)(false);
|
|
4635
|
+
const fallbackAttemptId = (0, import_react8.useRef)(null);
|
|
4636
|
+
const completionRef = (0, import_react8.useRef)(null);
|
|
4637
|
+
const confirmationRef = (0, import_react8.useRef)(null);
|
|
4515
4638
|
const pages = form.schema.pages;
|
|
4516
|
-
const visiblePageIndexes = (0,
|
|
4639
|
+
const visiblePageIndexes = (0, import_react8.useMemo)(
|
|
4517
4640
|
() => pages === void 0 ? [] : pages.flatMap((page, index) => form.pageVisibility[page.id] === true ? [index] : []),
|
|
4518
4641
|
[form.pageVisibility, pages]
|
|
4519
4642
|
);
|
|
4520
4643
|
const activePage = pages?.[currentPageIndex];
|
|
4521
4644
|
const activeVisibleIndex = visiblePageIndexes.indexOf(currentPageIndex);
|
|
4522
4645
|
const fieldIds = activePage === void 0 ? void 0 : new Set(activePage.questionIds);
|
|
4523
|
-
const visibleValues = (0,
|
|
4524
|
-
const visibleItems = (0,
|
|
4646
|
+
const visibleValues = (0, import_react8.useMemo)(() => (0, import_core7.selectVisibleAnswers)(form.schema, form.values), [form.schema, form.values]);
|
|
4647
|
+
const visibleItems = (0, import_react8.useMemo)(
|
|
4525
4648
|
() => buildSubmittedItems(form.schema, form.values, form.visibility, (key) => form.translate(key), false),
|
|
4526
4649
|
[form.schema, form.translate, form.values, form.visibility]
|
|
4527
4650
|
);
|
|
@@ -4530,7 +4653,7 @@ function ContextFormRenderer({
|
|
|
4530
4653
|
const submitState = confirmation === null && !guardsPending ? form.submitStatus : "confirming";
|
|
4531
4654
|
const interactionLocked = submitState === "confirming" || submitState === "submitting";
|
|
4532
4655
|
const isReplaceMode = successRenderMode === "replace" || hideFormOnSuccess;
|
|
4533
|
-
const resolveMessage = (0,
|
|
4656
|
+
const resolveMessage = (0, import_react8.useCallback)(
|
|
4534
4657
|
(key, fallback) => {
|
|
4535
4658
|
const providerDefault = isProviderValue ? i18n.translator(`renderer.${key}`) : void 0;
|
|
4536
4659
|
const defaultText = fallback ?? (providerDefault === "" ? void 0 : providerDefault) ?? DEFAULT_RENDERER_MESSAGES[form.locale.toLowerCase().startsWith("ja") ? "ja" : "en"][key] ?? key;
|
|
@@ -4539,15 +4662,15 @@ function ContextFormRenderer({
|
|
|
4539
4662
|
},
|
|
4540
4663
|
[form.locale, i18n, isProviderValue, messageResolver, messages]
|
|
4541
4664
|
);
|
|
4542
|
-
const fieldTranslate = (0,
|
|
4665
|
+
const fieldTranslate = (0, import_react8.useCallback)(
|
|
4543
4666
|
(key, params) => key === "validation.required" && (messages.requiredField !== void 0 || messageResolver !== void 0) ? resolveMessage("requiredField") : form.translate(key, params),
|
|
4544
4667
|
[form.translate, messageResolver, messages.requiredField, resolveMessage]
|
|
4545
4668
|
);
|
|
4546
|
-
const focusSubmitButton = (0,
|
|
4669
|
+
const focusSubmitButton = (0, import_react8.useCallback)(() => {
|
|
4547
4670
|
const button = formRef.current?.querySelector(".fe-submit, button[type='submit'], button");
|
|
4548
4671
|
button?.focus();
|
|
4549
4672
|
}, []);
|
|
4550
|
-
(0,
|
|
4673
|
+
(0, import_react8.useEffect)(() => {
|
|
4551
4674
|
let active = true;
|
|
4552
4675
|
if (receiptStore === void 0) {
|
|
4553
4676
|
setReceipt(null);
|
|
@@ -4568,14 +4691,14 @@ function ContextFormRenderer({
|
|
|
4568
4691
|
active = false;
|
|
4569
4692
|
};
|
|
4570
4693
|
}, [form.schema.id, form.schema.version, receiptStore]);
|
|
4571
|
-
(0,
|
|
4694
|
+
(0, import_react8.useEffect)(() => {
|
|
4572
4695
|
if (pages === void 0 || visiblePageIndexes.length === 0) {
|
|
4573
4696
|
setCurrentPageIndex(0);
|
|
4574
4697
|
return;
|
|
4575
4698
|
}
|
|
4576
4699
|
if (!visiblePageIndexes.includes(currentPageIndex)) setCurrentPageIndex(visiblePageIndexes[0] ?? 0);
|
|
4577
4700
|
}, [currentPageIndex, pages, visiblePageIndexes]);
|
|
4578
|
-
(0,
|
|
4701
|
+
(0, import_react8.useEffect)(() => {
|
|
4579
4702
|
if (focusFieldId === null) return;
|
|
4580
4703
|
const fieldContainer = [...formRef.current?.querySelectorAll("[data-field-id]") ?? []].find(
|
|
4581
4704
|
(element) => element.dataset.fieldId === focusFieldId
|
|
@@ -4587,11 +4710,11 @@ function ContextFormRenderer({
|
|
|
4587
4710
|
setFocusFieldId(null);
|
|
4588
4711
|
}
|
|
4589
4712
|
}, [focusFieldId]);
|
|
4590
|
-
(0,
|
|
4713
|
+
(0, import_react8.useEffect)(() => {
|
|
4591
4714
|
if (!isReplaceMode || form.submitStatus !== "success") return;
|
|
4592
4715
|
completionRef.current?.focus();
|
|
4593
4716
|
}, [form.submitStatus, isReplaceMode]);
|
|
4594
|
-
(0,
|
|
4717
|
+
(0, import_react8.useEffect)(() => {
|
|
4595
4718
|
if (confirmation === null) return;
|
|
4596
4719
|
const confirmButton = confirmationRef.current?.querySelector("[data-fe-confirm], button");
|
|
4597
4720
|
confirmButton?.focus();
|
|
@@ -4623,7 +4746,7 @@ function ContextFormRenderer({
|
|
|
4623
4746
|
globalThis.addEventListener("keydown", onKeyDown);
|
|
4624
4747
|
return () => globalThis.removeEventListener("keydown", onKeyDown);
|
|
4625
4748
|
}, [confirmation, confirmationRenderMode, focusSubmitButton]);
|
|
4626
|
-
(0,
|
|
4749
|
+
(0, import_react8.useEffect)(() => {
|
|
4627
4750
|
if (autoSaveKey === void 0 || typeof globalThis.localStorage === "undefined") return;
|
|
4628
4751
|
const loadIdentity = `${autoSaveKey}:${form.schema.id}:${form.schema.version}`;
|
|
4629
4752
|
if (loadedDraftKey.current === loadIdentity) return;
|
|
@@ -4635,7 +4758,7 @@ function ContextFormRenderer({
|
|
|
4635
4758
|
form.restoreValues(draft.values);
|
|
4636
4759
|
setDraftRestored(true);
|
|
4637
4760
|
}, [autoSaveKey, form.restoreValues, form.schema.id, form.schema.version]);
|
|
4638
|
-
(0,
|
|
4761
|
+
(0, import_react8.useEffect)(() => {
|
|
4639
4762
|
if (form.submitStatus === "success") return;
|
|
4640
4763
|
const timeout = globalThis.setTimeout(() => {
|
|
4641
4764
|
onDraftSave?.(form.values);
|
|
@@ -4691,7 +4814,7 @@ function ContextFormRenderer({
|
|
|
4691
4814
|
if (rendererSubmissionInFlight.current || submitState === "submitting" || submitState === "success" || confirmation !== null && !guardsConfirmed) {
|
|
4692
4815
|
return { status: "cancelled" };
|
|
4693
4816
|
}
|
|
4694
|
-
const validation = (0,
|
|
4817
|
+
const validation = (0, import_core7.validateAnswers)(form.schema, form.values);
|
|
4695
4818
|
const firstInvalidFieldId = validation.issues[0]?.fieldId;
|
|
4696
4819
|
if (validation.valid && !guardsConfirmed) {
|
|
4697
4820
|
rendererSubmissionInFlight.current = true;
|
|
@@ -4754,8 +4877,9 @@ function ContextFormRenderer({
|
|
|
4754
4877
|
return result;
|
|
4755
4878
|
}
|
|
4756
4879
|
if (result.status === "error") {
|
|
4757
|
-
|
|
4758
|
-
|
|
4880
|
+
const payload = result.error instanceof import_core7.FormSubmissionError ? result.error.payload : (0, import_core7.isFormSubmissionSerializedError)(result.error) ? result.error : void 0;
|
|
4881
|
+
if (payload !== void 0) {
|
|
4882
|
+
const fieldErrors = payload.fieldErrors ?? {};
|
|
4759
4883
|
form.setServerErrors?.(fieldErrors);
|
|
4760
4884
|
const firstServerFieldId = form.schema.fields.find((field) => Object.hasOwn(fieldErrors, field.id))?.id ?? Object.keys(fieldErrors)[0];
|
|
4761
4885
|
if (firstServerFieldId !== void 0) {
|
|
@@ -4763,6 +4887,9 @@ function ContextFormRenderer({
|
|
|
4763
4887
|
if (invalidPageIndex !== void 0 && invalidPageIndex >= 0) setCurrentPageIndex(invalidPageIndex);
|
|
4764
4888
|
focusFirstIssue(firstServerFieldId);
|
|
4765
4889
|
}
|
|
4890
|
+
if (payload.piiFindings !== void 0 && payload.piiFindings.length > 0) {
|
|
4891
|
+
setConfirmation({ findings: payload.piiFindings, generic: false });
|
|
4892
|
+
}
|
|
4766
4893
|
}
|
|
4767
4894
|
return result;
|
|
4768
4895
|
}
|
|
@@ -4993,7 +5120,7 @@ function ContextFormRenderer({
|
|
|
4993
5120
|
...slots.renderCharacterCount === void 0 ? {} : { renderCharacterCount: slots.renderCharacterCount }
|
|
4994
5121
|
};
|
|
4995
5122
|
if (slots.renderField !== void 0) {
|
|
4996
|
-
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
5123
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react8.Fragment, { children: slots.renderField({
|
|
4997
5124
|
question: field,
|
|
4998
5125
|
value: form.values[field.id],
|
|
4999
5126
|
onChange: (value) => {
|
|
@@ -5064,8 +5191,8 @@ function ContextFormRenderer({
|
|
|
5064
5191
|
] }),
|
|
5065
5192
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "fe-status", "aria-live": "polite", children: [
|
|
5066
5193
|
form.submitStatus === "success" ? completionRegion : null,
|
|
5067
|
-
form.submitStatus === "error" && form.submitError !== null ? slots.renderSubmitError?.({ error: form.submitError, onRetry: () => void submitValues() }) ?? (form.submitError instanceof FormSubmissionError && form.submitError.payload.
|
|
5068
|
-
form.submitError.payload.
|
|
5194
|
+
form.submitStatus === "error" && form.submitError !== null ? slots.renderSubmitError?.({ error: form.submitError, onRetry: () => void submitValues() }) ?? (form.submitError instanceof import_core7.FormSubmissionError && (form.submitError.payload.formErrors?.length ?? 0) > 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { role: "alert", children: [
|
|
5195
|
+
form.submitError.payload.formErrors?.map((message) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { children: message }, message)),
|
|
5069
5196
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", onClick: () => void submitValues(), children: resolveMessage("retryButton") })
|
|
5070
5197
|
] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { role: "alert", children: [
|
|
5071
5198
|
errorMessageKey === void 0 ? resolveMessage("serverErrorSummary") : form.translate(errorMessageKey),
|
|
@@ -5121,7 +5248,7 @@ var defaultRendererTranslator = {
|
|
|
5121
5248
|
};
|
|
5122
5249
|
function FormRenderer(props) {
|
|
5123
5250
|
const i18n = useFormEngineI18n();
|
|
5124
|
-
const isProviderValue = (0,
|
|
5251
|
+
const isProviderValue = (0, import_react8.useContext)(FormEngineI18nProviderScopeContext);
|
|
5125
5252
|
if (!("schema" in props)) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ContextFormRenderer, { ...props });
|
|
5126
5253
|
const {
|
|
5127
5254
|
schema,
|
|
@@ -5148,6 +5275,9 @@ function FormRenderer(props) {
|
|
|
5148
5275
|
}
|
|
5149
5276
|
);
|
|
5150
5277
|
}
|
|
5278
|
+
|
|
5279
|
+
// src/types.ts
|
|
5280
|
+
var import_core8 = require("@form-engine-ts/core");
|
|
5151
5281
|
// Annotate the CommonJS export names for ESM import in node:
|
|
5152
5282
|
0 && (module.exports = {
|
|
5153
5283
|
BUILDER_TRANSLATION_ALIASES,
|
|
@@ -5172,6 +5302,7 @@ function FormRenderer(props) {
|
|
|
5172
5302
|
useFormBuilder,
|
|
5173
5303
|
useFormEngineI18n,
|
|
5174
5304
|
useSubmissionReceipts,
|
|
5305
|
+
useTranslationComparison,
|
|
5175
5306
|
useTranslationWorkspace,
|
|
5176
5307
|
validateLocalePipeline
|
|
5177
5308
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
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,
|
|
3
|
-
export { QuestionType } from '@form-engine-ts/core';
|
|
2
|
+
import { QuestionType, FormField, ChoiceOption, FormPage, FormSchema, DisplayCondition, JsonValue, SchemaIssue, FormPolicy, Question, FieldOption, TranslationReport, ValidationIssue, ValidationError, FormValues, LocaleOption, TranslationStatus, CanonicalTranslationMetadata, TranslationSlot, TranslationAdapter, AsyncTranslationAdapter, PopulateTranslationOptions, FormValue, AnswerValidationResult, FormEngineTranslator, FormEngineMessages, TranslationMissingKeyEvent, FieldType } from '@form-engine-ts/core';
|
|
3
|
+
export { FormSubmissionError, FormSubmissionSerializedError, QuestionType } from '@form-engine-ts/core';
|
|
4
4
|
import * as react from 'react';
|
|
5
5
|
import { ReactNode, ComponentType, KeyboardEvent, MouseEvent, CSSProperties } from 'react';
|
|
6
6
|
import { SensitiveDataFinding } from '@form-engine-ts/privacy';
|
|
@@ -307,6 +307,7 @@ interface BuilderSlotBaseProps {
|
|
|
307
307
|
readonly actions: BuilderSlotActions;
|
|
308
308
|
readonly components: Required<FormBuilderComponents>;
|
|
309
309
|
readonly translate: (key: string, params?: Record<string, unknown>) => string;
|
|
310
|
+
readonly onChange?: (schema: FormSchema) => void;
|
|
310
311
|
}
|
|
311
312
|
interface BuilderToolbarSlotProps extends BuilderSlotBaseProps {
|
|
312
313
|
readonly kind: "page" | "field" | "option";
|
|
@@ -402,6 +403,58 @@ interface TranslationSlotRowProps {
|
|
|
402
403
|
readonly onChange: (text: string) => void;
|
|
403
404
|
readonly onTranslate: () => void;
|
|
404
405
|
}
|
|
406
|
+
interface TranslationComparisonItem {
|
|
407
|
+
readonly id: string;
|
|
408
|
+
readonly path: string;
|
|
409
|
+
readonly targetKind: "form" | "page" | "field" | "option";
|
|
410
|
+
readonly targetProperty: "title" | "description" | "label" | "completionMessage";
|
|
411
|
+
readonly nodeTitle?: string;
|
|
412
|
+
readonly sourceText: string;
|
|
413
|
+
readonly translatedText: string;
|
|
414
|
+
readonly status: TranslationStatus;
|
|
415
|
+
readonly metadata?: CanonicalTranslationMetadata;
|
|
416
|
+
readonly translatable: boolean;
|
|
417
|
+
}
|
|
418
|
+
interface TranslationComparisonSummary {
|
|
419
|
+
readonly total: number;
|
|
420
|
+
readonly translated: number;
|
|
421
|
+
readonly missing: number;
|
|
422
|
+
readonly stale: number;
|
|
423
|
+
readonly manual: number;
|
|
424
|
+
}
|
|
425
|
+
interface TranslationComparisonHeaderProps {
|
|
426
|
+
readonly sourceLocale: string;
|
|
427
|
+
readonly targetLocale: string;
|
|
428
|
+
readonly summary: TranslationComparisonSummary;
|
|
429
|
+
readonly onTranslateAll: () => void;
|
|
430
|
+
readonly isTranslating: boolean;
|
|
431
|
+
readonly readOnly: boolean;
|
|
432
|
+
}
|
|
433
|
+
interface TranslationComparisonItemRowProps {
|
|
434
|
+
readonly item: TranslationComparisonItem;
|
|
435
|
+
readonly readOnly: boolean;
|
|
436
|
+
readonly onChange: (text: string) => void;
|
|
437
|
+
readonly onTranslate: () => void;
|
|
438
|
+
}
|
|
439
|
+
interface UseTranslationComparisonOptions {
|
|
440
|
+
readonly schema: FormSchema;
|
|
441
|
+
readonly sourceLocale?: string;
|
|
442
|
+
readonly targetLocale: string;
|
|
443
|
+
readonly translationAdapter?: TranslationAdapter;
|
|
444
|
+
readonly readOnly?: boolean;
|
|
445
|
+
readonly onChange?: (nextSchema: FormSchema) => void;
|
|
446
|
+
readonly onTranslationChange?: (event: TranslationSlotChangeEvent) => void;
|
|
447
|
+
}
|
|
448
|
+
interface UseTranslationComparisonResult {
|
|
449
|
+
readonly sourceLocale: string;
|
|
450
|
+
readonly targetLocale: string;
|
|
451
|
+
readonly items: readonly TranslationComparisonItem[];
|
|
452
|
+
readonly summary: TranslationComparisonSummary;
|
|
453
|
+
readonly isTranslating: boolean;
|
|
454
|
+
readonly updateTranslation: (path: string, text: string) => void;
|
|
455
|
+
readonly translateSingle: (path: string) => Promise<void>;
|
|
456
|
+
readonly translateAll: () => Promise<TranslationReport>;
|
|
457
|
+
}
|
|
405
458
|
interface LocaleSelectorProps {
|
|
406
459
|
readonly targetLocale: string;
|
|
407
460
|
readonly targetLocales: readonly string[];
|
|
@@ -551,10 +604,6 @@ interface FormServerErrorPayload {
|
|
|
551
604
|
readonly fieldErrors?: Readonly<Record<string, string>>;
|
|
552
605
|
readonly formError?: string;
|
|
553
606
|
}
|
|
554
|
-
declare class FormSubmissionError extends Error {
|
|
555
|
-
readonly payload: FormServerErrorPayload;
|
|
556
|
-
constructor(message: string, payload?: FormServerErrorPayload);
|
|
557
|
-
}
|
|
558
607
|
type FormSubmitHandler = (answers: FormValues, context: SubmitContext) => SubmitResponse | void | Promise<SubmitResponse | undefined> | Promise<void>;
|
|
559
608
|
type SubmissionGuardResult = {
|
|
560
609
|
readonly status: "allow";
|
|
@@ -771,6 +820,8 @@ interface FieldState {
|
|
|
771
820
|
}
|
|
772
821
|
declare function useField(fieldId: string): FieldState;
|
|
773
822
|
|
|
823
|
+
declare function useTranslationComparison({ schema, sourceLocale, targetLocale, translationAdapter, readOnly, onChange, onTranslationChange }: UseTranslationComparisonOptions): UseTranslationComparisonResult;
|
|
824
|
+
|
|
774
825
|
interface UseTranslationWorkspaceOptions {
|
|
775
826
|
readonly schema: FormSchema;
|
|
776
827
|
readonly onChange?: (schema: FormSchema) => void;
|
|
@@ -909,10 +960,12 @@ interface FormEngineI18nProviderProps {
|
|
|
909
960
|
readonly fallbackLocale?: string;
|
|
910
961
|
readonly messages?: FormEngineMessages;
|
|
911
962
|
readonly customCatalogs?: Record<string, FormEngineMessages>;
|
|
963
|
+
readonly onMissingKey?: (event: TranslationMissingKeyEvent) => void;
|
|
964
|
+
readonly strict?: boolean;
|
|
912
965
|
readonly translator?: FormEngineTranslator;
|
|
913
966
|
readonly children: ReactNode;
|
|
914
967
|
}
|
|
915
|
-
declare function FormEngineI18nProvider({ locale, fallbackLocale, messages, customCatalogs, translator: customTranslator, children }: FormEngineI18nProviderProps): react.JSX.Element;
|
|
968
|
+
declare function FormEngineI18nProvider({ locale, fallbackLocale, messages, customCatalogs, onMissingKey, strict, translator: customTranslator, children }: FormEngineI18nProviderProps): react.JSX.Element;
|
|
916
969
|
declare function useFormEngineI18n(): FormEngineI18nContextValue;
|
|
917
970
|
|
|
918
971
|
declare function resolveChoiceFieldLayout(type: FieldType, appearance?: FormRendererAppearance, groupedChoiceFieldsLegacy?: boolean): "default" | "grouped";
|
|
@@ -968,4 +1021,4 @@ interface StandaloneFormRendererProps extends FormRendererPresentationProps {
|
|
|
968
1021
|
type FormRendererProps = FormRendererPresentationProps | StandaloneFormRendererProps;
|
|
969
1022
|
declare function FormRenderer(props: FormRendererProps): react.JSX.Element;
|
|
970
1023
|
|
|
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,
|
|
1024
|
+
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, 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 TranslationComparisonHeaderProps, type TranslationComparisonItem, type TranslationComparisonItemRowProps, type TranslationComparisonSummary, type TranslationEventPayload, type TranslationSlotChangeEvent, type TranslationSlotRowProps, type TranslationSummary, type TranslationWorkspaceActionsProps, type TranslationWorkspaceError, type TranslationWorkspaceHeaderProps, type TranslationWorkspaceSlots, type UseFormBuilderOptions, type UseFormBuilderResult, type UseSubmissionReceiptsResult, type UseTranslationComparisonOptions, type UseTranslationComparisonResult, type UseTranslationWorkspaceOptions, type UseTranslationWorkspaceResult, createLocalStorageSubmissionAttemptStore, createLocalStorageSubmissionReceiptStore, isTranslationUnresolved, resolveChoiceFieldLayout, resolveFieldEditorControls, resolveFieldTypeSelectOptions, resolveInitialFieldType, resolveTranslation, submissionReceiptQueryKey, useField, useForm, useFormBuilder, useFormEngineI18n, useSubmissionReceipts, useTranslationComparison, useTranslationWorkspace, validateLocalePipeline };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
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,
|
|
3
|
-
export { QuestionType } from '@form-engine-ts/core';
|
|
2
|
+
import { QuestionType, FormField, ChoiceOption, FormPage, FormSchema, DisplayCondition, JsonValue, SchemaIssue, FormPolicy, Question, FieldOption, TranslationReport, ValidationIssue, ValidationError, FormValues, LocaleOption, TranslationStatus, CanonicalTranslationMetadata, TranslationSlot, TranslationAdapter, AsyncTranslationAdapter, PopulateTranslationOptions, FormValue, AnswerValidationResult, FormEngineTranslator, FormEngineMessages, TranslationMissingKeyEvent, FieldType } from '@form-engine-ts/core';
|
|
3
|
+
export { FormSubmissionError, FormSubmissionSerializedError, QuestionType } from '@form-engine-ts/core';
|
|
4
4
|
import * as react from 'react';
|
|
5
5
|
import { ReactNode, ComponentType, KeyboardEvent, MouseEvent, CSSProperties } from 'react';
|
|
6
6
|
import { SensitiveDataFinding } from '@form-engine-ts/privacy';
|
|
@@ -307,6 +307,7 @@ interface BuilderSlotBaseProps {
|
|
|
307
307
|
readonly actions: BuilderSlotActions;
|
|
308
308
|
readonly components: Required<FormBuilderComponents>;
|
|
309
309
|
readonly translate: (key: string, params?: Record<string, unknown>) => string;
|
|
310
|
+
readonly onChange?: (schema: FormSchema) => void;
|
|
310
311
|
}
|
|
311
312
|
interface BuilderToolbarSlotProps extends BuilderSlotBaseProps {
|
|
312
313
|
readonly kind: "page" | "field" | "option";
|
|
@@ -402,6 +403,58 @@ interface TranslationSlotRowProps {
|
|
|
402
403
|
readonly onChange: (text: string) => void;
|
|
403
404
|
readonly onTranslate: () => void;
|
|
404
405
|
}
|
|
406
|
+
interface TranslationComparisonItem {
|
|
407
|
+
readonly id: string;
|
|
408
|
+
readonly path: string;
|
|
409
|
+
readonly targetKind: "form" | "page" | "field" | "option";
|
|
410
|
+
readonly targetProperty: "title" | "description" | "label" | "completionMessage";
|
|
411
|
+
readonly nodeTitle?: string;
|
|
412
|
+
readonly sourceText: string;
|
|
413
|
+
readonly translatedText: string;
|
|
414
|
+
readonly status: TranslationStatus;
|
|
415
|
+
readonly metadata?: CanonicalTranslationMetadata;
|
|
416
|
+
readonly translatable: boolean;
|
|
417
|
+
}
|
|
418
|
+
interface TranslationComparisonSummary {
|
|
419
|
+
readonly total: number;
|
|
420
|
+
readonly translated: number;
|
|
421
|
+
readonly missing: number;
|
|
422
|
+
readonly stale: number;
|
|
423
|
+
readonly manual: number;
|
|
424
|
+
}
|
|
425
|
+
interface TranslationComparisonHeaderProps {
|
|
426
|
+
readonly sourceLocale: string;
|
|
427
|
+
readonly targetLocale: string;
|
|
428
|
+
readonly summary: TranslationComparisonSummary;
|
|
429
|
+
readonly onTranslateAll: () => void;
|
|
430
|
+
readonly isTranslating: boolean;
|
|
431
|
+
readonly readOnly: boolean;
|
|
432
|
+
}
|
|
433
|
+
interface TranslationComparisonItemRowProps {
|
|
434
|
+
readonly item: TranslationComparisonItem;
|
|
435
|
+
readonly readOnly: boolean;
|
|
436
|
+
readonly onChange: (text: string) => void;
|
|
437
|
+
readonly onTranslate: () => void;
|
|
438
|
+
}
|
|
439
|
+
interface UseTranslationComparisonOptions {
|
|
440
|
+
readonly schema: FormSchema;
|
|
441
|
+
readonly sourceLocale?: string;
|
|
442
|
+
readonly targetLocale: string;
|
|
443
|
+
readonly translationAdapter?: TranslationAdapter;
|
|
444
|
+
readonly readOnly?: boolean;
|
|
445
|
+
readonly onChange?: (nextSchema: FormSchema) => void;
|
|
446
|
+
readonly onTranslationChange?: (event: TranslationSlotChangeEvent) => void;
|
|
447
|
+
}
|
|
448
|
+
interface UseTranslationComparisonResult {
|
|
449
|
+
readonly sourceLocale: string;
|
|
450
|
+
readonly targetLocale: string;
|
|
451
|
+
readonly items: readonly TranslationComparisonItem[];
|
|
452
|
+
readonly summary: TranslationComparisonSummary;
|
|
453
|
+
readonly isTranslating: boolean;
|
|
454
|
+
readonly updateTranslation: (path: string, text: string) => void;
|
|
455
|
+
readonly translateSingle: (path: string) => Promise<void>;
|
|
456
|
+
readonly translateAll: () => Promise<TranslationReport>;
|
|
457
|
+
}
|
|
405
458
|
interface LocaleSelectorProps {
|
|
406
459
|
readonly targetLocale: string;
|
|
407
460
|
readonly targetLocales: readonly string[];
|
|
@@ -551,10 +604,6 @@ interface FormServerErrorPayload {
|
|
|
551
604
|
readonly fieldErrors?: Readonly<Record<string, string>>;
|
|
552
605
|
readonly formError?: string;
|
|
553
606
|
}
|
|
554
|
-
declare class FormSubmissionError extends Error {
|
|
555
|
-
readonly payload: FormServerErrorPayload;
|
|
556
|
-
constructor(message: string, payload?: FormServerErrorPayload);
|
|
557
|
-
}
|
|
558
607
|
type FormSubmitHandler = (answers: FormValues, context: SubmitContext) => SubmitResponse | void | Promise<SubmitResponse | undefined> | Promise<void>;
|
|
559
608
|
type SubmissionGuardResult = {
|
|
560
609
|
readonly status: "allow";
|
|
@@ -771,6 +820,8 @@ interface FieldState {
|
|
|
771
820
|
}
|
|
772
821
|
declare function useField(fieldId: string): FieldState;
|
|
773
822
|
|
|
823
|
+
declare function useTranslationComparison({ schema, sourceLocale, targetLocale, translationAdapter, readOnly, onChange, onTranslationChange }: UseTranslationComparisonOptions): UseTranslationComparisonResult;
|
|
824
|
+
|
|
774
825
|
interface UseTranslationWorkspaceOptions {
|
|
775
826
|
readonly schema: FormSchema;
|
|
776
827
|
readonly onChange?: (schema: FormSchema) => void;
|
|
@@ -909,10 +960,12 @@ interface FormEngineI18nProviderProps {
|
|
|
909
960
|
readonly fallbackLocale?: string;
|
|
910
961
|
readonly messages?: FormEngineMessages;
|
|
911
962
|
readonly customCatalogs?: Record<string, FormEngineMessages>;
|
|
963
|
+
readonly onMissingKey?: (event: TranslationMissingKeyEvent) => void;
|
|
964
|
+
readonly strict?: boolean;
|
|
912
965
|
readonly translator?: FormEngineTranslator;
|
|
913
966
|
readonly children: ReactNode;
|
|
914
967
|
}
|
|
915
|
-
declare function FormEngineI18nProvider({ locale, fallbackLocale, messages, customCatalogs, translator: customTranslator, children }: FormEngineI18nProviderProps): react.JSX.Element;
|
|
968
|
+
declare function FormEngineI18nProvider({ locale, fallbackLocale, messages, customCatalogs, onMissingKey, strict, translator: customTranslator, children }: FormEngineI18nProviderProps): react.JSX.Element;
|
|
916
969
|
declare function useFormEngineI18n(): FormEngineI18nContextValue;
|
|
917
970
|
|
|
918
971
|
declare function resolveChoiceFieldLayout(type: FieldType, appearance?: FormRendererAppearance, groupedChoiceFieldsLegacy?: boolean): "default" | "grouped";
|
|
@@ -968,4 +1021,4 @@ interface StandaloneFormRendererProps extends FormRendererPresentationProps {
|
|
|
968
1021
|
type FormRendererProps = FormRendererPresentationProps | StandaloneFormRendererProps;
|
|
969
1022
|
declare function FormRenderer(props: FormRendererProps): react.JSX.Element;
|
|
970
1023
|
|
|
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,
|
|
1024
|
+
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, 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 TranslationComparisonHeaderProps, type TranslationComparisonItem, type TranslationComparisonItemRowProps, type TranslationComparisonSummary, type TranslationEventPayload, type TranslationSlotChangeEvent, type TranslationSlotRowProps, type TranslationSummary, type TranslationWorkspaceActionsProps, type TranslationWorkspaceError, type TranslationWorkspaceHeaderProps, type TranslationWorkspaceSlots, type UseFormBuilderOptions, type UseFormBuilderResult, type UseSubmissionReceiptsResult, type UseTranslationComparisonOptions, type UseTranslationComparisonResult, type UseTranslationWorkspaceOptions, type UseTranslationWorkspaceResult, createLocalStorageSubmissionAttemptStore, createLocalStorageSubmissionReceiptStore, isTranslationUnresolved, resolveChoiceFieldLayout, resolveFieldEditorControls, resolveFieldTypeSelectOptions, resolveInitialFieldType, resolveTranslation, submissionReceiptQueryKey, useField, useForm, useFormBuilder, useFormEngineI18n, useSubmissionReceipts, useTranslationComparison, useTranslationWorkspace, validateLocalePipeline };
|
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,
|
|
@@ -3837,8 +3840,131 @@ function useTranslationWorkspace({
|
|
|
3837
3840
|
};
|
|
3838
3841
|
}
|
|
3839
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
|
+
|
|
3840
3966
|
// src/receipt.ts
|
|
3841
|
-
import { useEffect as useEffect2, useMemo as
|
|
3967
|
+
import { useEffect as useEffect2, useMemo as useMemo6, useState as useState5 } from "react";
|
|
3842
3968
|
function submissionReceiptQueryKey(formId, formVersion) {
|
|
3843
3969
|
return `${formId}:v${formVersion}`;
|
|
3844
3970
|
}
|
|
@@ -3909,7 +4035,7 @@ function createLocalStorageSubmissionReceiptStore(options = {}) {
|
|
|
3909
4035
|
}
|
|
3910
4036
|
function useSubmissionReceipts(store, queries) {
|
|
3911
4037
|
const querySignature = JSON.stringify(queries.map(({ formId, formVersion }) => [formId, formVersion]));
|
|
3912
|
-
const stableQueries =
|
|
4038
|
+
const stableQueries = useMemo6(() => {
|
|
3913
4039
|
const parsed = JSON.parse(querySignature);
|
|
3914
4040
|
if (!Array.isArray(parsed)) return [];
|
|
3915
4041
|
return parsed.flatMap(
|
|
@@ -3956,16 +4082,18 @@ function useSubmissionReceipts(store, queries) {
|
|
|
3956
4082
|
|
|
3957
4083
|
// src/renderer.tsx
|
|
3958
4084
|
import {
|
|
4085
|
+
FormSubmissionError as FormSubmissionError2,
|
|
4086
|
+
isFormSubmissionSerializedError as isFormSubmissionSerializedError2,
|
|
3959
4087
|
selectVisibleAnswers as selectVisibleAnswers2,
|
|
3960
4088
|
validateAnswers as validateAnswers2
|
|
3961
4089
|
} from "@form-engine-ts/core";
|
|
3962
4090
|
import {
|
|
3963
4091
|
Fragment as Fragment2,
|
|
3964
|
-
useCallback as
|
|
4092
|
+
useCallback as useCallback5,
|
|
3965
4093
|
useContext as useContext4,
|
|
3966
4094
|
useEffect as useEffect3,
|
|
3967
4095
|
useId,
|
|
3968
|
-
useMemo as
|
|
4096
|
+
useMemo as useMemo7,
|
|
3969
4097
|
useRef as useRef3,
|
|
3970
4098
|
useState as useState6
|
|
3971
4099
|
} from "react";
|
|
@@ -4498,15 +4626,15 @@ function ContextFormRenderer({
|
|
|
4498
4626
|
const completionRef = useRef3(null);
|
|
4499
4627
|
const confirmationRef = useRef3(null);
|
|
4500
4628
|
const pages = form.schema.pages;
|
|
4501
|
-
const visiblePageIndexes =
|
|
4629
|
+
const visiblePageIndexes = useMemo7(
|
|
4502
4630
|
() => pages === void 0 ? [] : pages.flatMap((page, index) => form.pageVisibility[page.id] === true ? [index] : []),
|
|
4503
4631
|
[form.pageVisibility, pages]
|
|
4504
4632
|
);
|
|
4505
4633
|
const activePage = pages?.[currentPageIndex];
|
|
4506
4634
|
const activeVisibleIndex = visiblePageIndexes.indexOf(currentPageIndex);
|
|
4507
4635
|
const fieldIds = activePage === void 0 ? void 0 : new Set(activePage.questionIds);
|
|
4508
|
-
const visibleValues =
|
|
4509
|
-
const visibleItems =
|
|
4636
|
+
const visibleValues = useMemo7(() => selectVisibleAnswers2(form.schema, form.values), [form.schema, form.values]);
|
|
4637
|
+
const visibleItems = useMemo7(
|
|
4510
4638
|
() => buildSubmittedItems(form.schema, form.values, form.visibility, (key) => form.translate(key), false),
|
|
4511
4639
|
[form.schema, form.translate, form.values, form.visibility]
|
|
4512
4640
|
);
|
|
@@ -4515,7 +4643,7 @@ function ContextFormRenderer({
|
|
|
4515
4643
|
const submitState = confirmation === null && !guardsPending ? form.submitStatus : "confirming";
|
|
4516
4644
|
const interactionLocked = submitState === "confirming" || submitState === "submitting";
|
|
4517
4645
|
const isReplaceMode = successRenderMode === "replace" || hideFormOnSuccess;
|
|
4518
|
-
const resolveMessage =
|
|
4646
|
+
const resolveMessage = useCallback5(
|
|
4519
4647
|
(key, fallback) => {
|
|
4520
4648
|
const providerDefault = isProviderValue ? i18n.translator(`renderer.${key}`) : void 0;
|
|
4521
4649
|
const defaultText = fallback ?? (providerDefault === "" ? void 0 : providerDefault) ?? DEFAULT_RENDERER_MESSAGES[form.locale.toLowerCase().startsWith("ja") ? "ja" : "en"][key] ?? key;
|
|
@@ -4524,11 +4652,11 @@ function ContextFormRenderer({
|
|
|
4524
4652
|
},
|
|
4525
4653
|
[form.locale, i18n, isProviderValue, messageResolver, messages]
|
|
4526
4654
|
);
|
|
4527
|
-
const fieldTranslate =
|
|
4655
|
+
const fieldTranslate = useCallback5(
|
|
4528
4656
|
(key, params) => key === "validation.required" && (messages.requiredField !== void 0 || messageResolver !== void 0) ? resolveMessage("requiredField") : form.translate(key, params),
|
|
4529
4657
|
[form.translate, messageResolver, messages.requiredField, resolveMessage]
|
|
4530
4658
|
);
|
|
4531
|
-
const focusSubmitButton =
|
|
4659
|
+
const focusSubmitButton = useCallback5(() => {
|
|
4532
4660
|
const button = formRef.current?.querySelector(".fe-submit, button[type='submit'], button");
|
|
4533
4661
|
button?.focus();
|
|
4534
4662
|
}, []);
|
|
@@ -4739,8 +4867,9 @@ function ContextFormRenderer({
|
|
|
4739
4867
|
return result;
|
|
4740
4868
|
}
|
|
4741
4869
|
if (result.status === "error") {
|
|
4742
|
-
|
|
4743
|
-
|
|
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 ?? {};
|
|
4744
4873
|
form.setServerErrors?.(fieldErrors);
|
|
4745
4874
|
const firstServerFieldId = form.schema.fields.find((field) => Object.hasOwn(fieldErrors, field.id))?.id ?? Object.keys(fieldErrors)[0];
|
|
4746
4875
|
if (firstServerFieldId !== void 0) {
|
|
@@ -4748,6 +4877,9 @@ function ContextFormRenderer({
|
|
|
4748
4877
|
if (invalidPageIndex !== void 0 && invalidPageIndex >= 0) setCurrentPageIndex(invalidPageIndex);
|
|
4749
4878
|
focusFirstIssue(firstServerFieldId);
|
|
4750
4879
|
}
|
|
4880
|
+
if (payload.piiFindings !== void 0 && payload.piiFindings.length > 0) {
|
|
4881
|
+
setConfirmation({ findings: payload.piiFindings, generic: false });
|
|
4882
|
+
}
|
|
4751
4883
|
}
|
|
4752
4884
|
return result;
|
|
4753
4885
|
}
|
|
@@ -5049,8 +5181,8 @@ function ContextFormRenderer({
|
|
|
5049
5181
|
] }),
|
|
5050
5182
|
/* @__PURE__ */ jsxs2("div", { className: "fe-status", "aria-live": "polite", children: [
|
|
5051
5183
|
form.submitStatus === "success" ? completionRegion : null,
|
|
5052
|
-
form.submitStatus === "error" && form.submitError !== null ? slots.renderSubmitError?.({ error: form.submitError, onRetry: () => void submitValues() }) ?? (form.submitError instanceof
|
|
5053
|
-
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)),
|
|
5054
5186
|
/* @__PURE__ */ jsx4("button", { type: "button", onClick: () => void submitValues(), children: resolveMessage("retryButton") })
|
|
5055
5187
|
] }) : /* @__PURE__ */ jsxs2("div", { role: "alert", children: [
|
|
5056
5188
|
errorMessageKey === void 0 ? resolveMessage("serverErrorSummary") : form.translate(errorMessageKey),
|
|
@@ -5133,6 +5265,9 @@ function FormRenderer(props) {
|
|
|
5133
5265
|
}
|
|
5134
5266
|
);
|
|
5135
5267
|
}
|
|
5268
|
+
|
|
5269
|
+
// src/types.ts
|
|
5270
|
+
import { FormSubmissionError as FormSubmissionError3 } from "@form-engine-ts/core";
|
|
5136
5271
|
export {
|
|
5137
5272
|
BUILDER_TRANSLATION_ALIASES,
|
|
5138
5273
|
BUILDER_TRANSLATION_KEYS,
|
|
@@ -5141,7 +5276,7 @@ export {
|
|
|
5141
5276
|
FormEngineI18nProvider,
|
|
5142
5277
|
FormProvider,
|
|
5143
5278
|
FormRenderer,
|
|
5144
|
-
FormSubmissionError,
|
|
5279
|
+
FormSubmissionError3 as FormSubmissionError,
|
|
5145
5280
|
createLocalStorageSubmissionAttemptStore,
|
|
5146
5281
|
createLocalStorageSubmissionReceiptStore,
|
|
5147
5282
|
isTranslationUnresolved,
|
|
@@ -5156,6 +5291,7 @@ export {
|
|
|
5156
5291
|
useFormBuilder,
|
|
5157
5292
|
useFormEngineI18n,
|
|
5158
5293
|
useSubmissionReceipts,
|
|
5294
|
+
useTranslationComparison,
|
|
5159
5295
|
useTranslationWorkspace,
|
|
5160
5296
|
validateLocalePipeline
|
|
5161
5297
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@form-engine-ts/react",
|
|
3
|
-
"version": "6.
|
|
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",
|