@form-engine-ts/react 4.5.0 → 4.7.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 +6 -0
- package/dist/index.cjs +105 -10
- package/dist/index.d.cts +24 -3
- package/dist/index.d.ts +24 -3
- package/dist/index.js +105 -10
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -79,6 +79,12 @@ single-slot or batch translation with stale/manual status handling. The MUI pack
|
|
|
79
79
|
`setActiveFieldId` to keep one field editor open at a time. Set `submissionSettingsOptions={{ enabled: true }}` to expose
|
|
80
80
|
schema-driven pre-submit confirmation controls in the builder.
|
|
81
81
|
|
|
82
|
+
`useTranslationWorkspace` validates added locales against `policy.allowedLocales` and `policy.maxLocales` and returns
|
|
83
|
+
a structured `{ success, error }` result from `addLocale`. Built-in BCP 47, duplicate, and policy checks always run
|
|
84
|
+
before the optional `validateLocale` callback, which receives the locale, default locale, current locales, and policy.
|
|
85
|
+
Use `isAddLocaleAllowed` to disable locale controls before submission. Removing a locale also clears its
|
|
86
|
+
localized values and metadata; the default locale remains protected.
|
|
87
|
+
|
|
82
88
|
## Headless builder and renderer lifecycle
|
|
83
89
|
|
|
84
90
|
`useFormBuilder({ schema, onChange, policy, idFactory, factories })` exposes controlled field, option, page, condition,
|
package/dist/index.cjs
CHANGED
|
@@ -39,7 +39,8 @@ __export(index_exports, {
|
|
|
39
39
|
useForm: () => useForm,
|
|
40
40
|
useFormBuilder: () => useFormBuilder,
|
|
41
41
|
useSubmissionReceipts: () => useSubmissionReceipts,
|
|
42
|
-
useTranslationWorkspace: () => useTranslationWorkspace
|
|
42
|
+
useTranslationWorkspace: () => useTranslationWorkspace,
|
|
43
|
+
validateLocalePipeline: () => validateLocalePipeline
|
|
43
44
|
});
|
|
44
45
|
module.exports = __toCommonJS(index_exports);
|
|
45
46
|
|
|
@@ -3281,6 +3282,88 @@ function asAsyncAdapter(adapter) {
|
|
|
3281
3282
|
)
|
|
3282
3283
|
};
|
|
3283
3284
|
}
|
|
3285
|
+
function isValidBcp47Locale(locale) {
|
|
3286
|
+
const language = locale.split("-")[0];
|
|
3287
|
+
if (language === void 0 || !/^[a-z]{2,3}$/iu.test(language)) return false;
|
|
3288
|
+
try {
|
|
3289
|
+
Intl.getCanonicalLocales(locale);
|
|
3290
|
+
return true;
|
|
3291
|
+
} catch {
|
|
3292
|
+
return false;
|
|
3293
|
+
}
|
|
3294
|
+
}
|
|
3295
|
+
var validateLocalePipeline = (locale, schema, policy, customValidator) => {
|
|
3296
|
+
const currentLocales = schema.supportedLocales ?? [];
|
|
3297
|
+
const defaultLocale = schema.defaultLocale ?? "";
|
|
3298
|
+
if (!isValidBcp47Locale(locale)) {
|
|
3299
|
+
return {
|
|
3300
|
+
valid: false,
|
|
3301
|
+
error: {
|
|
3302
|
+
type: "invalid_locale_format",
|
|
3303
|
+
message: `Invalid BCP 47 locale format: "${locale}"`
|
|
3304
|
+
}
|
|
3305
|
+
};
|
|
3306
|
+
}
|
|
3307
|
+
if (locale === defaultLocale || currentLocales.includes(locale)) {
|
|
3308
|
+
return {
|
|
3309
|
+
valid: false,
|
|
3310
|
+
error: {
|
|
3311
|
+
type: "locale_already_exists",
|
|
3312
|
+
message: `Locale "${locale}" is already registered.`
|
|
3313
|
+
}
|
|
3314
|
+
};
|
|
3315
|
+
}
|
|
3316
|
+
if (policy?.allowedLocales !== void 0 && !policy.allowedLocales.includes(locale)) {
|
|
3317
|
+
return {
|
|
3318
|
+
valid: false,
|
|
3319
|
+
error: {
|
|
3320
|
+
type: "locale_not_allowed",
|
|
3321
|
+
message: `Locale "${locale}" is not allowed by policy.`
|
|
3322
|
+
}
|
|
3323
|
+
};
|
|
3324
|
+
}
|
|
3325
|
+
const totalLocalesCount = 1 + currentLocales.length;
|
|
3326
|
+
if (policy?.maxLocales !== void 0 && totalLocalesCount >= policy.maxLocales) {
|
|
3327
|
+
return {
|
|
3328
|
+
valid: false,
|
|
3329
|
+
error: {
|
|
3330
|
+
type: "max_locales_exceeded",
|
|
3331
|
+
message: `Cannot add locale: maximum allowed locales limit (${policy.maxLocales}) reached.`
|
|
3332
|
+
}
|
|
3333
|
+
};
|
|
3334
|
+
}
|
|
3335
|
+
if (customValidator !== void 0) {
|
|
3336
|
+
const customContext = policy === void 0 ? Object.assign([...currentLocales], { locale, defaultLocale, currentLocales }) : Object.assign([...currentLocales], { locale, defaultLocale, currentLocales, policy });
|
|
3337
|
+
const customResult = customValidator(locale, customContext);
|
|
3338
|
+
if (customResult === false) {
|
|
3339
|
+
return {
|
|
3340
|
+
valid: false,
|
|
3341
|
+
error: {
|
|
3342
|
+
type: "custom_validation_failed",
|
|
3343
|
+
message: `Custom validation rejected locale "${locale}".`
|
|
3344
|
+
}
|
|
3345
|
+
};
|
|
3346
|
+
}
|
|
3347
|
+
if (typeof customResult === "string") {
|
|
3348
|
+
return {
|
|
3349
|
+
valid: false,
|
|
3350
|
+
error: { type: "custom_validation_failed", message: customResult }
|
|
3351
|
+
};
|
|
3352
|
+
}
|
|
3353
|
+
if (typeof customResult === "object" && customResult !== null && !customResult.valid) return customResult;
|
|
3354
|
+
}
|
|
3355
|
+
return { valid: true };
|
|
3356
|
+
};
|
|
3357
|
+
function workspaceLocaleValidationMessage(validation) {
|
|
3358
|
+
if (validation.error?.type === "locale_not_allowed") {
|
|
3359
|
+
return validation.error.message.replace(" is not allowed by policy.", " is not allowed by the form policy.");
|
|
3360
|
+
}
|
|
3361
|
+
if (validation.error?.type === "max_locales_exceeded") {
|
|
3362
|
+
const maximum = validation.error.message.match(/\((\d+)\)/u)?.[1];
|
|
3363
|
+
return maximum === void 0 ? validation.error.message : `At most ${maximum} locales are allowed by the form policy.`;
|
|
3364
|
+
}
|
|
3365
|
+
return validation.error?.message ?? "Locale is not valid.";
|
|
3366
|
+
}
|
|
3284
3367
|
function manualMetadata(sourceText, sourceLocale) {
|
|
3285
3368
|
return {
|
|
3286
3369
|
sourceLocale,
|
|
@@ -3348,7 +3431,9 @@ function useTranslationWorkspace({
|
|
|
3348
3431
|
sourceLocale = schema.defaultLocale ?? "en",
|
|
3349
3432
|
targetLocale,
|
|
3350
3433
|
translationAdapter,
|
|
3351
|
-
readOnly = false
|
|
3434
|
+
readOnly = false,
|
|
3435
|
+
policy,
|
|
3436
|
+
validateLocale
|
|
3352
3437
|
}) {
|
|
3353
3438
|
const [draftSchema, setDraftSchema] = (0, import_react4.useState)(schema);
|
|
3354
3439
|
const [selectedLocale, setSelectedLocale] = (0, import_react4.useState)(targetLocale ?? "");
|
|
@@ -3401,23 +3486,31 @@ function useTranslationWorkspace({
|
|
|
3401
3486
|
);
|
|
3402
3487
|
const addLocale = (0, import_react4.useCallback)(
|
|
3403
3488
|
(locale) => {
|
|
3404
|
-
if (readOnly
|
|
3489
|
+
if (readOnly) return { success: false, error: "Workspace is read-only." };
|
|
3405
3490
|
const normalized = locale.trim();
|
|
3491
|
+
const validation = validateLocalePipeline(normalized, currentSchema, policy, validateLocale);
|
|
3492
|
+
if (!validation.valid) return { success: false, error: workspaceLocaleValidationMessage(validation) };
|
|
3406
3493
|
commit({
|
|
3407
3494
|
...currentSchema,
|
|
3408
3495
|
supportedLocales: [.../* @__PURE__ */ new Set([...currentSchema.supportedLocales ?? [], normalized])]
|
|
3409
3496
|
});
|
|
3410
3497
|
setSelectedLocale(normalized);
|
|
3498
|
+
return { success: true };
|
|
3499
|
+
},
|
|
3500
|
+
[commit, currentSchema, policy, readOnly, validateLocale]
|
|
3501
|
+
);
|
|
3502
|
+
const isAddLocaleAllowed = (0, import_react4.useCallback)(
|
|
3503
|
+
(locale) => {
|
|
3504
|
+
if (readOnly) return false;
|
|
3505
|
+
const normalized = locale.trim();
|
|
3506
|
+
return validateLocalePipeline(normalized, currentSchema, policy, validateLocale).valid;
|
|
3411
3507
|
},
|
|
3412
|
-
[
|
|
3508
|
+
[currentSchema, policy, readOnly, validateLocale]
|
|
3413
3509
|
);
|
|
3414
3510
|
const removeLocale = (0, import_react4.useCallback)(
|
|
3415
3511
|
(locale) => {
|
|
3416
|
-
if (readOnly || locale === sourceLocale) return;
|
|
3417
|
-
commit(
|
|
3418
|
-
...currentSchema,
|
|
3419
|
-
supportedLocales: (currentSchema.supportedLocales ?? []).filter((candidate) => candidate !== locale)
|
|
3420
|
-
});
|
|
3512
|
+
if (readOnly || locale === sourceLocale || locale === currentSchema.defaultLocale) return;
|
|
3513
|
+
commit((0, import_core4.removeLocaleFromSchema)(currentSchema, locale));
|
|
3421
3514
|
if (activeLocale === locale) setSelectedLocale("");
|
|
3422
3515
|
},
|
|
3423
3516
|
[activeLocale, commit, currentSchema, readOnly, sourceLocale]
|
|
@@ -3478,6 +3571,7 @@ function useTranslationWorkspace({
|
|
|
3478
3571
|
slots,
|
|
3479
3572
|
summary,
|
|
3480
3573
|
addLocale,
|
|
3574
|
+
isAddLocaleAllowed,
|
|
3481
3575
|
removeLocale,
|
|
3482
3576
|
setTranslation,
|
|
3483
3577
|
translateAll,
|
|
@@ -4784,5 +4878,6 @@ function FormRenderer(props) {
|
|
|
4784
4878
|
useForm,
|
|
4785
4879
|
useFormBuilder,
|
|
4786
4880
|
useSubmissionReceipts,
|
|
4787
|
-
useTranslationWorkspace
|
|
4881
|
+
useTranslationWorkspace,
|
|
4882
|
+
validateLocalePipeline
|
|
4788
4883
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -707,6 +707,22 @@ interface UseTranslationWorkspaceOptions {
|
|
|
707
707
|
readonly targetLocale?: string;
|
|
708
708
|
readonly translationAdapter?: TranslationAdapter | AsyncTranslationAdapter;
|
|
709
709
|
readonly readOnly?: boolean;
|
|
710
|
+
readonly policy?: FormPolicy;
|
|
711
|
+
readonly validateLocale?: ((locale: string, currentLocales: readonly string[]) => LocaleValidationResult) | CustomLocaleValidator;
|
|
712
|
+
}
|
|
713
|
+
interface LocaleValidationContext {
|
|
714
|
+
readonly locale: string;
|
|
715
|
+
readonly defaultLocale: string;
|
|
716
|
+
readonly currentLocales: readonly string[];
|
|
717
|
+
readonly policy?: FormPolicy;
|
|
718
|
+
}
|
|
719
|
+
type CustomLocaleValidator = (locale: string, context: LocaleValidationContext) => LocaleValidationResult | boolean | string | undefined | void;
|
|
720
|
+
interface LocaleValidationResult {
|
|
721
|
+
readonly valid: boolean;
|
|
722
|
+
readonly error?: {
|
|
723
|
+
readonly type: "locale_not_allowed" | "max_locales_exceeded" | "invalid_locale_format" | "locale_already_exists" | "custom_validation_failed";
|
|
724
|
+
readonly message: string;
|
|
725
|
+
};
|
|
710
726
|
}
|
|
711
727
|
interface TranslationSummary {
|
|
712
728
|
readonly totalSlots: number;
|
|
@@ -723,7 +739,11 @@ interface UseTranslationWorkspaceResult {
|
|
|
723
739
|
readonly setTargetLocale: (locale: string) => void;
|
|
724
740
|
readonly slots: readonly TranslationSlot[];
|
|
725
741
|
readonly summary: TranslationSummary;
|
|
726
|
-
readonly addLocale: (locale: string) =>
|
|
742
|
+
readonly addLocale: (locale: string) => {
|
|
743
|
+
readonly success: boolean;
|
|
744
|
+
readonly error?: string;
|
|
745
|
+
};
|
|
746
|
+
readonly isAddLocaleAllowed: (locale: string) => boolean;
|
|
727
747
|
readonly removeLocale: (locale: string) => void;
|
|
728
748
|
readonly setTranslation: (slot: TranslationSlot, text: string) => void;
|
|
729
749
|
readonly translateAll: (options?: PopulateTranslationOptions) => Promise<TranslationReport>;
|
|
@@ -731,7 +751,8 @@ interface UseTranslationWorkspaceResult {
|
|
|
731
751
|
readonly isTranslating: boolean;
|
|
732
752
|
readonly error?: string;
|
|
733
753
|
}
|
|
734
|
-
declare
|
|
754
|
+
declare const validateLocalePipeline: (locale: string, schema: FormSchema, policy?: FormPolicy, customValidator?: ((locale: string, currentLocales: readonly string[]) => LocaleValidationResult) | CustomLocaleValidator) => LocaleValidationResult;
|
|
755
|
+
declare function useTranslationWorkspace({ schema, onChange, sourceLocale, targetLocale, translationAdapter, readOnly, policy, validateLocale }: UseTranslationWorkspaceOptions): UseTranslationWorkspaceResult;
|
|
735
756
|
|
|
736
757
|
declare const BUILDER_TRANSLATION_KEYS: {
|
|
737
758
|
readonly ADD_FIELD: "builder.actions.addField";
|
|
@@ -805,4 +826,4 @@ interface StandaloneFormRendererProps extends FormRendererPresentationProps {
|
|
|
805
826
|
type FormRendererProps = FormRendererPresentationProps | StandaloneFormRendererProps;
|
|
806
827
|
declare function FormRenderer(props: FormRendererProps): react.JSX.Element;
|
|
807
828
|
|
|
808
|
-
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 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, type FormFieldsSlotProps, FormProvider, type FormProviderProps, FormRenderer, type FormRendererAppearance, type FormRendererMessages, type FormRendererPresentationProps, type FormRendererProps, type FormRendererSlotProps, type FormRendererSlots, type FormServerErrorPayload, FormSubmissionError, type FormSubmitHandler, type FormSubmitState, type FormSubmitStatus, type FormSubmittedAnswerItem, type FormSuccessRenderMode, type IconButtonProps, type InputComponentProps, type LocalizationSummaryContext, type ManualTranslationContext, type ManualTranslationTarget, type RenderSubmitButtonProps, type SelectComponentProps, type StandaloneFormRendererProps, type SubmissionAttempt, type SubmissionAttemptStore, type SubmissionConfirmationOptions, type SubmissionConfirmationRenderMode, type SubmissionConfirmationSlotProps, type SubmissionGuard, type SubmissionGuardResult, type SubmissionProtectionProps, type SubmissionReceipt, type SubmissionReceiptQuery, type SubmissionReceiptStore, type SubmitContext, type SubmitResponse, type SubmitResult, type SubmitStatus, type TranslationSummary, type UseFormBuilderOptions, type UseFormBuilderResult, type UseSubmissionReceiptsResult, type UseTranslationWorkspaceOptions, type UseTranslationWorkspaceResult, createLocalStorageSubmissionAttemptStore, createLocalStorageSubmissionReceiptStore, isTranslationUnresolved, resolveChoiceFieldLayout, resolveFieldEditorControls, resolveFieldTypeSelectOptions, resolveInitialFieldType, resolveTranslation, submissionReceiptQueryKey, useField, useForm, useFormBuilder, useSubmissionReceipts, useTranslationWorkspace };
|
|
829
|
+
export { BUILDER_TRANSLATION_ALIASES, BUILDER_TRANSLATION_KEYS, type BeforeSubmit, type BuilderActionContext, type BuilderActionError, type BuilderActionIconType, type BuilderActionResult, type BuilderButtonProps, type BuilderCheckboxProps, type BuilderErrorMessageProps, type BuilderFactories, type BuilderFieldEditorSlotProps, type BuilderFieldsetProps, type BuilderIconButtonProps, type BuilderIdKind, type BuilderLocalizationSlotProps, type BuilderOptionEditorSlotProps, type BuilderPagesSlotProps, type BuilderPolicy, type BuilderSectionProps, type BuilderSelectOption, type BuilderSelectProps, type BuilderSlotActions, type BuilderTextAreaProps, type BuilderTextInputProps, type BuilderTextTarget, type BuilderToolbarSlotProps, type BuilderTranslationActionsSlotProps, type BuilderTranslationKey, type ChoiceFieldLayoutMode, type ChoiceFieldTypeLayoutMap, type ChoiceGroupSlotProps, type ComponentBaseProps, type CustomLocaleValidator, type FieldComponentProps, type FieldComponents, type FieldEditorControlsConfig, type FieldEditorHeaderSlotProps, type FieldEditorMode, type FieldError, type FieldPropertyControlMode, type FieldState, type FieldTypeSelectOptionsConfig, type FieldTypeSelectOptionsContext, type FieldTypeSelectOptionsSorter, type FieldTypeSelectOptionsTransformer, type FieldTypeSelectSlotProps, FormBuilder, type FormBuilderActions, type FormBuilderComponents, type FormBuilderFeatures, type FormBuilderOptions, type FormBuilderProps, type FormBuilderResult, type FormBuilderSectionName, type FormBuilderSlots, type FormBuilderSubmissionSettingsOptions, type FormCompletionSlotProps, type FormContextValue, type FormFieldsSlotProps, FormProvider, type FormProviderProps, FormRenderer, type FormRendererAppearance, type FormRendererMessages, type FormRendererPresentationProps, type FormRendererProps, type FormRendererSlotProps, type FormRendererSlots, type FormServerErrorPayload, FormSubmissionError, type FormSubmitHandler, type FormSubmitState, type FormSubmitStatus, type FormSubmittedAnswerItem, type FormSuccessRenderMode, type IconButtonProps, type InputComponentProps, type LocaleValidationContext, type LocaleValidationResult, type LocalizationSummaryContext, type ManualTranslationContext, type ManualTranslationTarget, type RenderSubmitButtonProps, type SelectComponentProps, type StandaloneFormRendererProps, type SubmissionAttempt, type SubmissionAttemptStore, type SubmissionConfirmationOptions, type SubmissionConfirmationRenderMode, type SubmissionConfirmationSlotProps, type SubmissionGuard, type SubmissionGuardResult, type SubmissionProtectionProps, type SubmissionReceipt, type SubmissionReceiptQuery, type SubmissionReceiptStore, type SubmitContext, type SubmitResponse, type SubmitResult, type SubmitStatus, type TranslationSummary, type UseFormBuilderOptions, type UseFormBuilderResult, type UseSubmissionReceiptsResult, type UseTranslationWorkspaceOptions, type UseTranslationWorkspaceResult, createLocalStorageSubmissionAttemptStore, createLocalStorageSubmissionReceiptStore, isTranslationUnresolved, resolveChoiceFieldLayout, resolveFieldEditorControls, resolveFieldTypeSelectOptions, resolveInitialFieldType, resolveTranslation, submissionReceiptQueryKey, useField, useForm, useFormBuilder, useSubmissionReceipts, useTranslationWorkspace, validateLocalePipeline };
|
package/dist/index.d.ts
CHANGED
|
@@ -707,6 +707,22 @@ interface UseTranslationWorkspaceOptions {
|
|
|
707
707
|
readonly targetLocale?: string;
|
|
708
708
|
readonly translationAdapter?: TranslationAdapter | AsyncTranslationAdapter;
|
|
709
709
|
readonly readOnly?: boolean;
|
|
710
|
+
readonly policy?: FormPolicy;
|
|
711
|
+
readonly validateLocale?: ((locale: string, currentLocales: readonly string[]) => LocaleValidationResult) | CustomLocaleValidator;
|
|
712
|
+
}
|
|
713
|
+
interface LocaleValidationContext {
|
|
714
|
+
readonly locale: string;
|
|
715
|
+
readonly defaultLocale: string;
|
|
716
|
+
readonly currentLocales: readonly string[];
|
|
717
|
+
readonly policy?: FormPolicy;
|
|
718
|
+
}
|
|
719
|
+
type CustomLocaleValidator = (locale: string, context: LocaleValidationContext) => LocaleValidationResult | boolean | string | undefined | void;
|
|
720
|
+
interface LocaleValidationResult {
|
|
721
|
+
readonly valid: boolean;
|
|
722
|
+
readonly error?: {
|
|
723
|
+
readonly type: "locale_not_allowed" | "max_locales_exceeded" | "invalid_locale_format" | "locale_already_exists" | "custom_validation_failed";
|
|
724
|
+
readonly message: string;
|
|
725
|
+
};
|
|
710
726
|
}
|
|
711
727
|
interface TranslationSummary {
|
|
712
728
|
readonly totalSlots: number;
|
|
@@ -723,7 +739,11 @@ interface UseTranslationWorkspaceResult {
|
|
|
723
739
|
readonly setTargetLocale: (locale: string) => void;
|
|
724
740
|
readonly slots: readonly TranslationSlot[];
|
|
725
741
|
readonly summary: TranslationSummary;
|
|
726
|
-
readonly addLocale: (locale: string) =>
|
|
742
|
+
readonly addLocale: (locale: string) => {
|
|
743
|
+
readonly success: boolean;
|
|
744
|
+
readonly error?: string;
|
|
745
|
+
};
|
|
746
|
+
readonly isAddLocaleAllowed: (locale: string) => boolean;
|
|
727
747
|
readonly removeLocale: (locale: string) => void;
|
|
728
748
|
readonly setTranslation: (slot: TranslationSlot, text: string) => void;
|
|
729
749
|
readonly translateAll: (options?: PopulateTranslationOptions) => Promise<TranslationReport>;
|
|
@@ -731,7 +751,8 @@ interface UseTranslationWorkspaceResult {
|
|
|
731
751
|
readonly isTranslating: boolean;
|
|
732
752
|
readonly error?: string;
|
|
733
753
|
}
|
|
734
|
-
declare
|
|
754
|
+
declare const validateLocalePipeline: (locale: string, schema: FormSchema, policy?: FormPolicy, customValidator?: ((locale: string, currentLocales: readonly string[]) => LocaleValidationResult) | CustomLocaleValidator) => LocaleValidationResult;
|
|
755
|
+
declare function useTranslationWorkspace({ schema, onChange, sourceLocale, targetLocale, translationAdapter, readOnly, policy, validateLocale }: UseTranslationWorkspaceOptions): UseTranslationWorkspaceResult;
|
|
735
756
|
|
|
736
757
|
declare const BUILDER_TRANSLATION_KEYS: {
|
|
737
758
|
readonly ADD_FIELD: "builder.actions.addField";
|
|
@@ -805,4 +826,4 @@ interface StandaloneFormRendererProps extends FormRendererPresentationProps {
|
|
|
805
826
|
type FormRendererProps = FormRendererPresentationProps | StandaloneFormRendererProps;
|
|
806
827
|
declare function FormRenderer(props: FormRendererProps): react.JSX.Element;
|
|
807
828
|
|
|
808
|
-
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 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, type FormFieldsSlotProps, FormProvider, type FormProviderProps, FormRenderer, type FormRendererAppearance, type FormRendererMessages, type FormRendererPresentationProps, type FormRendererProps, type FormRendererSlotProps, type FormRendererSlots, type FormServerErrorPayload, FormSubmissionError, type FormSubmitHandler, type FormSubmitState, type FormSubmitStatus, type FormSubmittedAnswerItem, type FormSuccessRenderMode, type IconButtonProps, type InputComponentProps, type LocalizationSummaryContext, type ManualTranslationContext, type ManualTranslationTarget, type RenderSubmitButtonProps, type SelectComponentProps, type StandaloneFormRendererProps, type SubmissionAttempt, type SubmissionAttemptStore, type SubmissionConfirmationOptions, type SubmissionConfirmationRenderMode, type SubmissionConfirmationSlotProps, type SubmissionGuard, type SubmissionGuardResult, type SubmissionProtectionProps, type SubmissionReceipt, type SubmissionReceiptQuery, type SubmissionReceiptStore, type SubmitContext, type SubmitResponse, type SubmitResult, type SubmitStatus, type TranslationSummary, type UseFormBuilderOptions, type UseFormBuilderResult, type UseSubmissionReceiptsResult, type UseTranslationWorkspaceOptions, type UseTranslationWorkspaceResult, createLocalStorageSubmissionAttemptStore, createLocalStorageSubmissionReceiptStore, isTranslationUnresolved, resolveChoiceFieldLayout, resolveFieldEditorControls, resolveFieldTypeSelectOptions, resolveInitialFieldType, resolveTranslation, submissionReceiptQueryKey, useField, useForm, useFormBuilder, useSubmissionReceipts, useTranslationWorkspace };
|
|
829
|
+
export { BUILDER_TRANSLATION_ALIASES, BUILDER_TRANSLATION_KEYS, type BeforeSubmit, type BuilderActionContext, type BuilderActionError, type BuilderActionIconType, type BuilderActionResult, type BuilderButtonProps, type BuilderCheckboxProps, type BuilderErrorMessageProps, type BuilderFactories, type BuilderFieldEditorSlotProps, type BuilderFieldsetProps, type BuilderIconButtonProps, type BuilderIdKind, type BuilderLocalizationSlotProps, type BuilderOptionEditorSlotProps, type BuilderPagesSlotProps, type BuilderPolicy, type BuilderSectionProps, type BuilderSelectOption, type BuilderSelectProps, type BuilderSlotActions, type BuilderTextAreaProps, type BuilderTextInputProps, type BuilderTextTarget, type BuilderToolbarSlotProps, type BuilderTranslationActionsSlotProps, type BuilderTranslationKey, type ChoiceFieldLayoutMode, type ChoiceFieldTypeLayoutMap, type ChoiceGroupSlotProps, type ComponentBaseProps, type CustomLocaleValidator, type FieldComponentProps, type FieldComponents, type FieldEditorControlsConfig, type FieldEditorHeaderSlotProps, type FieldEditorMode, type FieldError, type FieldPropertyControlMode, type FieldState, type FieldTypeSelectOptionsConfig, type FieldTypeSelectOptionsContext, type FieldTypeSelectOptionsSorter, type FieldTypeSelectOptionsTransformer, type FieldTypeSelectSlotProps, FormBuilder, type FormBuilderActions, type FormBuilderComponents, type FormBuilderFeatures, type FormBuilderOptions, type FormBuilderProps, type FormBuilderResult, type FormBuilderSectionName, type FormBuilderSlots, type FormBuilderSubmissionSettingsOptions, type FormCompletionSlotProps, type FormContextValue, type FormFieldsSlotProps, FormProvider, type FormProviderProps, FormRenderer, type FormRendererAppearance, type FormRendererMessages, type FormRendererPresentationProps, type FormRendererProps, type FormRendererSlotProps, type FormRendererSlots, type FormServerErrorPayload, FormSubmissionError, type FormSubmitHandler, type FormSubmitState, type FormSubmitStatus, type FormSubmittedAnswerItem, type FormSuccessRenderMode, type IconButtonProps, type InputComponentProps, type LocaleValidationContext, type LocaleValidationResult, type LocalizationSummaryContext, type ManualTranslationContext, type ManualTranslationTarget, type RenderSubmitButtonProps, type SelectComponentProps, type StandaloneFormRendererProps, type SubmissionAttempt, type SubmissionAttemptStore, type SubmissionConfirmationOptions, type SubmissionConfirmationRenderMode, type SubmissionConfirmationSlotProps, type SubmissionGuard, type SubmissionGuardResult, type SubmissionProtectionProps, type SubmissionReceipt, type SubmissionReceiptQuery, type SubmissionReceiptStore, type SubmitContext, type SubmitResponse, type SubmitResult, type SubmitStatus, type TranslationSummary, type UseFormBuilderOptions, type UseFormBuilderResult, type UseSubmissionReceiptsResult, type UseTranslationWorkspaceOptions, type UseTranslationWorkspaceResult, createLocalStorageSubmissionAttemptStore, createLocalStorageSubmissionReceiptStore, isTranslationUnresolved, resolveChoiceFieldLayout, resolveFieldEditorControls, resolveFieldTypeSelectOptions, resolveInitialFieldType, resolveTranslation, submissionReceiptQueryKey, useField, useForm, useFormBuilder, useSubmissionReceipts, useTranslationWorkspace, validateLocalePipeline };
|
package/dist/index.js
CHANGED
|
@@ -3238,7 +3238,8 @@ import {
|
|
|
3238
3238
|
collectSchemaLocales as collectSchemaLocales2,
|
|
3239
3239
|
collectTranslationSlots,
|
|
3240
3240
|
computeSourceTextHash,
|
|
3241
|
-
populateSchemaTranslations as populateSchemaTranslations2
|
|
3241
|
+
populateSchemaTranslations as populateSchemaTranslations2,
|
|
3242
|
+
removeLocaleFromSchema
|
|
3242
3243
|
} from "@form-engine-ts/core";
|
|
3243
3244
|
import { useCallback as useCallback3, useMemo as useMemo3, useState as useState4 } from "react";
|
|
3244
3245
|
function updateTranslationMap(translations, locale, property, text) {
|
|
@@ -3256,6 +3257,88 @@ function asAsyncAdapter(adapter) {
|
|
|
3256
3257
|
)
|
|
3257
3258
|
};
|
|
3258
3259
|
}
|
|
3260
|
+
function isValidBcp47Locale(locale) {
|
|
3261
|
+
const language = locale.split("-")[0];
|
|
3262
|
+
if (language === void 0 || !/^[a-z]{2,3}$/iu.test(language)) return false;
|
|
3263
|
+
try {
|
|
3264
|
+
Intl.getCanonicalLocales(locale);
|
|
3265
|
+
return true;
|
|
3266
|
+
} catch {
|
|
3267
|
+
return false;
|
|
3268
|
+
}
|
|
3269
|
+
}
|
|
3270
|
+
var validateLocalePipeline = (locale, schema, policy, customValidator) => {
|
|
3271
|
+
const currentLocales = schema.supportedLocales ?? [];
|
|
3272
|
+
const defaultLocale = schema.defaultLocale ?? "";
|
|
3273
|
+
if (!isValidBcp47Locale(locale)) {
|
|
3274
|
+
return {
|
|
3275
|
+
valid: false,
|
|
3276
|
+
error: {
|
|
3277
|
+
type: "invalid_locale_format",
|
|
3278
|
+
message: `Invalid BCP 47 locale format: "${locale}"`
|
|
3279
|
+
}
|
|
3280
|
+
};
|
|
3281
|
+
}
|
|
3282
|
+
if (locale === defaultLocale || currentLocales.includes(locale)) {
|
|
3283
|
+
return {
|
|
3284
|
+
valid: false,
|
|
3285
|
+
error: {
|
|
3286
|
+
type: "locale_already_exists",
|
|
3287
|
+
message: `Locale "${locale}" is already registered.`
|
|
3288
|
+
}
|
|
3289
|
+
};
|
|
3290
|
+
}
|
|
3291
|
+
if (policy?.allowedLocales !== void 0 && !policy.allowedLocales.includes(locale)) {
|
|
3292
|
+
return {
|
|
3293
|
+
valid: false,
|
|
3294
|
+
error: {
|
|
3295
|
+
type: "locale_not_allowed",
|
|
3296
|
+
message: `Locale "${locale}" is not allowed by policy.`
|
|
3297
|
+
}
|
|
3298
|
+
};
|
|
3299
|
+
}
|
|
3300
|
+
const totalLocalesCount = 1 + currentLocales.length;
|
|
3301
|
+
if (policy?.maxLocales !== void 0 && totalLocalesCount >= policy.maxLocales) {
|
|
3302
|
+
return {
|
|
3303
|
+
valid: false,
|
|
3304
|
+
error: {
|
|
3305
|
+
type: "max_locales_exceeded",
|
|
3306
|
+
message: `Cannot add locale: maximum allowed locales limit (${policy.maxLocales}) reached.`
|
|
3307
|
+
}
|
|
3308
|
+
};
|
|
3309
|
+
}
|
|
3310
|
+
if (customValidator !== void 0) {
|
|
3311
|
+
const customContext = policy === void 0 ? Object.assign([...currentLocales], { locale, defaultLocale, currentLocales }) : Object.assign([...currentLocales], { locale, defaultLocale, currentLocales, policy });
|
|
3312
|
+
const customResult = customValidator(locale, customContext);
|
|
3313
|
+
if (customResult === false) {
|
|
3314
|
+
return {
|
|
3315
|
+
valid: false,
|
|
3316
|
+
error: {
|
|
3317
|
+
type: "custom_validation_failed",
|
|
3318
|
+
message: `Custom validation rejected locale "${locale}".`
|
|
3319
|
+
}
|
|
3320
|
+
};
|
|
3321
|
+
}
|
|
3322
|
+
if (typeof customResult === "string") {
|
|
3323
|
+
return {
|
|
3324
|
+
valid: false,
|
|
3325
|
+
error: { type: "custom_validation_failed", message: customResult }
|
|
3326
|
+
};
|
|
3327
|
+
}
|
|
3328
|
+
if (typeof customResult === "object" && customResult !== null && !customResult.valid) return customResult;
|
|
3329
|
+
}
|
|
3330
|
+
return { valid: true };
|
|
3331
|
+
};
|
|
3332
|
+
function workspaceLocaleValidationMessage(validation) {
|
|
3333
|
+
if (validation.error?.type === "locale_not_allowed") {
|
|
3334
|
+
return validation.error.message.replace(" is not allowed by policy.", " is not allowed by the form policy.");
|
|
3335
|
+
}
|
|
3336
|
+
if (validation.error?.type === "max_locales_exceeded") {
|
|
3337
|
+
const maximum = validation.error.message.match(/\((\d+)\)/u)?.[1];
|
|
3338
|
+
return maximum === void 0 ? validation.error.message : `At most ${maximum} locales are allowed by the form policy.`;
|
|
3339
|
+
}
|
|
3340
|
+
return validation.error?.message ?? "Locale is not valid.";
|
|
3341
|
+
}
|
|
3259
3342
|
function manualMetadata(sourceText, sourceLocale) {
|
|
3260
3343
|
return {
|
|
3261
3344
|
sourceLocale,
|
|
@@ -3323,7 +3406,9 @@ function useTranslationWorkspace({
|
|
|
3323
3406
|
sourceLocale = schema.defaultLocale ?? "en",
|
|
3324
3407
|
targetLocale,
|
|
3325
3408
|
translationAdapter,
|
|
3326
|
-
readOnly = false
|
|
3409
|
+
readOnly = false,
|
|
3410
|
+
policy,
|
|
3411
|
+
validateLocale
|
|
3327
3412
|
}) {
|
|
3328
3413
|
const [draftSchema, setDraftSchema] = useState4(schema);
|
|
3329
3414
|
const [selectedLocale, setSelectedLocale] = useState4(targetLocale ?? "");
|
|
@@ -3376,23 +3461,31 @@ function useTranslationWorkspace({
|
|
|
3376
3461
|
);
|
|
3377
3462
|
const addLocale = useCallback3(
|
|
3378
3463
|
(locale) => {
|
|
3379
|
-
if (readOnly
|
|
3464
|
+
if (readOnly) return { success: false, error: "Workspace is read-only." };
|
|
3380
3465
|
const normalized = locale.trim();
|
|
3466
|
+
const validation = validateLocalePipeline(normalized, currentSchema, policy, validateLocale);
|
|
3467
|
+
if (!validation.valid) return { success: false, error: workspaceLocaleValidationMessage(validation) };
|
|
3381
3468
|
commit({
|
|
3382
3469
|
...currentSchema,
|
|
3383
3470
|
supportedLocales: [.../* @__PURE__ */ new Set([...currentSchema.supportedLocales ?? [], normalized])]
|
|
3384
3471
|
});
|
|
3385
3472
|
setSelectedLocale(normalized);
|
|
3473
|
+
return { success: true };
|
|
3474
|
+
},
|
|
3475
|
+
[commit, currentSchema, policy, readOnly, validateLocale]
|
|
3476
|
+
);
|
|
3477
|
+
const isAddLocaleAllowed = useCallback3(
|
|
3478
|
+
(locale) => {
|
|
3479
|
+
if (readOnly) return false;
|
|
3480
|
+
const normalized = locale.trim();
|
|
3481
|
+
return validateLocalePipeline(normalized, currentSchema, policy, validateLocale).valid;
|
|
3386
3482
|
},
|
|
3387
|
-
[
|
|
3483
|
+
[currentSchema, policy, readOnly, validateLocale]
|
|
3388
3484
|
);
|
|
3389
3485
|
const removeLocale = useCallback3(
|
|
3390
3486
|
(locale) => {
|
|
3391
|
-
if (readOnly || locale === sourceLocale) return;
|
|
3392
|
-
commit(
|
|
3393
|
-
...currentSchema,
|
|
3394
|
-
supportedLocales: (currentSchema.supportedLocales ?? []).filter((candidate) => candidate !== locale)
|
|
3395
|
-
});
|
|
3487
|
+
if (readOnly || locale === sourceLocale || locale === currentSchema.defaultLocale) return;
|
|
3488
|
+
commit(removeLocaleFromSchema(currentSchema, locale));
|
|
3396
3489
|
if (activeLocale === locale) setSelectedLocale("");
|
|
3397
3490
|
},
|
|
3398
3491
|
[activeLocale, commit, currentSchema, readOnly, sourceLocale]
|
|
@@ -3453,6 +3546,7 @@ function useTranslationWorkspace({
|
|
|
3453
3546
|
slots,
|
|
3454
3547
|
summary,
|
|
3455
3548
|
addLocale,
|
|
3549
|
+
isAddLocaleAllowed,
|
|
3456
3550
|
removeLocale,
|
|
3457
3551
|
setTranslation,
|
|
3458
3552
|
translateAll,
|
|
@@ -4769,5 +4863,6 @@ export {
|
|
|
4769
4863
|
useForm,
|
|
4770
4864
|
useFormBuilder,
|
|
4771
4865
|
useSubmissionReceipts,
|
|
4772
|
-
useTranslationWorkspace
|
|
4866
|
+
useTranslationWorkspace,
|
|
4867
|
+
validateLocalePipeline
|
|
4773
4868
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@form-engine-ts/react",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.7.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
"typescript"
|
|
43
43
|
],
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@form-engine-ts/core": "4.
|
|
46
|
-
"@form-engine-ts/privacy": "4.
|
|
45
|
+
"@form-engine-ts/core": "4.7.0",
|
|
46
|
+
"@form-engine-ts/privacy": "4.7.0"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"react": ">=18.2 <20",
|