@form-engine-ts/mui 5.0.1 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1546,10 +1546,16 @@ function normalizeLocaleOptions(options, getLocaleLabel) {
1546
1546
  const seen = /* @__PURE__ */ new Set();
1547
1547
  const normalized = [];
1548
1548
  for (const item of options) {
1549
- const value = typeof item === "string" ? item : item.value;
1549
+ const value = typeof item === "string" ? item : "value" in item ? item.value : item.locale;
1550
1550
  if (value.length === 0 || seen.has(value)) continue;
1551
1551
  seen.add(value);
1552
- normalized.push({ value, label: typeof item === "string" ? getLocaleLabel?.(value) ?? value : item.label });
1552
+ normalized.push({
1553
+ value,
1554
+ label: typeof item === "string" ? getLocaleLabel?.(value) ?? value : item.label,
1555
+ ...typeof item === "string" || item.translatable === void 0 ? {} : { translatable: item.translatable },
1556
+ ...typeof item === "string" || item.removable === void 0 ? {} : { removable: item.removable },
1557
+ ...typeof item === "string" || item.metadata === void 0 ? {} : { metadata: item.metadata }
1558
+ });
1553
1559
  }
1554
1560
  return normalized;
1555
1561
  }
@@ -1592,6 +1598,8 @@ function createMuiLocalizationSlot(options) {
1592
1598
  const hasLocaleSelector = filteredAvailableLocales !== void 0;
1593
1599
  const localeLimitReached = policy?.maxLocales !== void 0 && registeredLocales.size >= policy.maxLocales;
1594
1600
  const normalizedLocale = newLocale.trim();
1601
+ const currentLocaleOption = availableLocaleOptions?.find((locale) => locale.value === currentLocale);
1602
+ const currentLocaleTranslatable = currentLocaleOption?.translatable !== false;
1595
1603
  const selectedLocaleAvailable = !hasLocaleSelector || filteredAvailableLocales.some((locale) => locale.value === normalizedLocale);
1596
1604
  const localeAllowed = policy?.allowedLocales === void 0 || policy.allowedLocales.includes(normalizedLocale);
1597
1605
  const canAddLocale = !readOnly && normalizedLocale.length > 0 && selectedLocaleAvailable && localeAllowed && !registeredLocales.has(normalizedLocale) && !localeLimitReached;
@@ -1757,7 +1765,7 @@ function createMuiLocalizationSlot(options) {
1757
1765
  {
1758
1766
  variant: "secondary",
1759
1767
  noWrap: localizationOptions.noWrapActions ?? true,
1760
- disabled: readOnly || !editingLocaleConfigured || isTranslating,
1768
+ disabled: readOnly || !editingLocaleConfigured || isTranslating || !currentLocaleTranslatable,
1761
1769
  onClick: onAutoTranslate,
1762
1770
  children: isTranslating ? translate("builder.translating") : translate("builder.autoTranslate")
1763
1771
  }
@@ -1895,6 +1903,7 @@ function MuiFormBuilder({
1895
1903
  muiSlotProps,
1896
1904
  components: customComponents,
1897
1905
  slots: customSlots,
1906
+ i18n,
1898
1907
  sectionOrder,
1899
1908
  ...props
1900
1909
  }) {
@@ -1906,7 +1915,14 @@ function MuiFormBuilder({
1906
1915
  }),
1907
1916
  [layoutOptions, localizationOptions, muiOptions, muiSlotProps]
1908
1917
  );
1909
- const contextValue = (0, import_react5.useMemo)(() => ({ options: contextOptions }), [contextOptions]);
1918
+ const resolvedMuiOptions = (0, import_react5.useMemo)(
1919
+ () => mergeMuiAdapterOptions(contextOptions, {
1920
+ ...i18n?.getLocaleLabel === void 0 ? {} : { getLocaleLabel: i18n.getLocaleLabel },
1921
+ ...i18n?.getActionLabel === void 0 ? {} : { getActionLabel: i18n.getActionLabel }
1922
+ }),
1923
+ [contextOptions, i18n?.getActionLabel, i18n?.getLocaleLabel]
1924
+ );
1925
+ const contextValue = (0, import_react5.useMemo)(() => ({ options: resolvedMuiOptions }), [resolvedMuiOptions]);
1910
1926
  const components = (0, import_react5.useMemo)(() => ({ ...muiBuilderComponents, ...customComponents }), [customComponents]);
1911
1927
  const slots = (0, import_react5.useMemo)(() => ({ ...muiBuilderSlots, ...customSlots }), [customSlots]);
1912
1928
  const placement = contextOptions.localizationOptions?.placement;
@@ -1927,7 +1943,7 @@ function MuiFormBuilder({
1927
1943
  );
1928
1944
  return order;
1929
1945
  })();
1930
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(MuiFormBuilderContext.Provider, { value: contextValue, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1946
+ const content = /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(MuiFormBuilderContext.Provider, { value: contextValue, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1931
1947
  import_react4.FormBuilder,
1932
1948
  {
1933
1949
  ...props,
@@ -1938,6 +1954,17 @@ function MuiFormBuilder({
1938
1954
  ...submissionSettingsOptions === void 0 ? {} : { submissionSettingsOptions }
1939
1955
  }
1940
1956
  ) });
1957
+ if (i18n === void 0) return content;
1958
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1959
+ import_react4.FormEngineI18nProvider,
1960
+ {
1961
+ ...i18n.locale === void 0 ? {} : { locale: i18n.locale },
1962
+ ...i18n.fallbackLocale === void 0 ? {} : { fallbackLocale: i18n.fallbackLocale },
1963
+ ...i18n.messages === void 0 ? {} : { messages: i18n.messages },
1964
+ ...i18n.translator === void 0 ? {} : { translator: i18n.translator },
1965
+ children: content
1966
+ }
1967
+ );
1941
1968
  }
1942
1969
 
1943
1970
  // src/workspace/TranslationWorkspace.tsx
@@ -1945,28 +1972,31 @@ var import_react6 = require("@form-engine-ts/react");
1945
1972
  var import_material16 = require("@mui/material");
1946
1973
  var import_react7 = require("react");
1947
1974
  var import_jsx_runtime18 = require("react/jsx-runtime");
1948
- var statusLabel = {
1949
- missing: "Missing",
1950
- translated: "Translated",
1951
- stale: "Source changed",
1952
- manual: "Manual",
1953
- "manual-stale": "Manual / source changed"
1975
+ var statusKey = {
1976
+ missing: "workspace.status.missing",
1977
+ translated: "workspace.status.translated",
1978
+ stale: "workspace.status.stale",
1979
+ manual: "workspace.status.manual",
1980
+ "manual-stale": "workspace.status.manualStale"
1954
1981
  };
1955
1982
  function SlotCard({
1956
1983
  slot,
1957
1984
  readOnly,
1958
1985
  onChange,
1959
- onTranslate
1986
+ onTranslate,
1987
+ translate,
1988
+ renderStatusBadge
1960
1989
  }) {
1990
+ const status = slot.status ?? "missing";
1961
1991
  return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_material16.Card, { variant: "outlined", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_material16.CardContent, { children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_material16.Stack, { spacing: 1.5, children: [
1962
1992
  /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_material16.Stack, { direction: "row", justifyContent: "space-between", alignItems: "center", children: [
1963
1993
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_material16.Typography, { variant: "subtitle2", children: slot.path }),
1964
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_material16.Chip, { size: "small", label: statusLabel[slot.status ?? "missing"] })
1994
+ renderStatusBadge?.({ status }) ?? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_material16.Chip, { size: "small", label: translate(statusKey[status]) })
1965
1995
  ] }),
1966
1996
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1967
1997
  import_material16.TextField,
1968
1998
  {
1969
- label: "Source",
1999
+ label: translate("workspace.slot.sourceText"),
1970
2000
  value: slot.sourceText,
1971
2001
  multiline: true,
1972
2002
  fullWidth: true,
@@ -1976,7 +2006,7 @@ function SlotCard({
1976
2006
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1977
2007
  import_material16.TextField,
1978
2008
  {
1979
- label: "Translation",
2009
+ label: translate("workspace.slot.translatedText"),
1980
2010
  value: slot.existingText ?? "",
1981
2011
  multiline: true,
1982
2012
  fullWidth: true,
@@ -1984,28 +2014,28 @@ function SlotCard({
1984
2014
  onChange: (event) => onChange(event.target.value)
1985
2015
  }
1986
2016
  ),
1987
- slot.status === "stale" || slot.status === "manual-stale" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_material16.Typography, { color: "warning.main", variant: "body2", children: "The source text has changed since this translation was created." }) : null,
1988
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_material16.Button, { variant: "outlined", onClick: onTranslate, disabled: readOnly, children: "Translate this slot" })
2017
+ slot.status === "stale" || slot.status === "manual-stale" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_material16.Typography, { color: "warning.main", variant: "body2", children: translate("workspace.status.stale") }) : null,
2018
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_material16.Button, { variant: "outlined", onClick: onTranslate, disabled: readOnly, children: translate("workspace.slot.translateSingle") })
1989
2019
  ] }) }) });
1990
2020
  }
1991
- function workspaceErrorMessage(error) {
2021
+ function workspaceErrorMessage(error, translate) {
1992
2022
  switch (error.type) {
1993
2023
  case "locale_not_allowed":
1994
- return `Locale "${error.locale}" is not allowed.`;
2024
+ return translate("workspace.errors.localeNotAllowed", { locale: error.locale });
1995
2025
  case "locale_already_exists":
1996
- return `Locale "${error.locale}" is already registered.`;
2026
+ return translate("workspace.errors.localeNotAllowed", { locale: error.locale });
1997
2027
  case "invalid_locale_format":
1998
- return `Invalid locale format: "${error.locale}"`;
2028
+ return translate("workspace.errors.localeNotAllowed", { locale: error.locale });
1999
2029
  case "max_locales_exceeded":
2000
- return `At most ${error.max} locales are allowed.`;
2030
+ return translate("workspace.errors.maxLocalesExceeded", { max: error.max });
2001
2031
  case "read_only_mode":
2002
- return "This workspace is read-only.";
2032
+ return translate("workspace.errors.readOnly");
2003
2033
  case "adapter_not_configured":
2004
- return "A translation adapter is not configured.";
2034
+ return translate("workspace.errors.adapterNotConfigured");
2005
2035
  case "target_locale_missing":
2006
- return "A target locale is required.";
2036
+ return translate("workspace.empty.noTargetLocales");
2007
2037
  case "translation_failed":
2008
- return error.message;
2038
+ return translate("workspace.errors.translationFailed");
2009
2039
  case "custom_validation_failed":
2010
2040
  return error.message;
2011
2041
  }
@@ -2016,81 +2046,133 @@ function TranslationWorkspace({
2016
2046
  sourceLocale,
2017
2047
  targetLocale,
2018
2048
  translationAdapter,
2019
- readOnly = false
2049
+ readOnly = false,
2050
+ availableLocales,
2051
+ onLocaleAdded,
2052
+ onLocaleRemoved,
2053
+ onLocaleChange,
2054
+ beforeRemoveLocale,
2055
+ onTranslationStart,
2056
+ onTranslationSuccess,
2057
+ onTranslationError,
2058
+ onTranslationChange,
2059
+ slots: workspaceSlots
2020
2060
  }) {
2061
+ const { translator } = (0, import_react6.useFormEngineI18n)();
2062
+ const translate = (key, params = {}) => translator(key, params);
2021
2063
  const workspace = (0, import_react6.useTranslationWorkspace)({
2022
2064
  schema,
2023
2065
  ...onChange === void 0 ? {} : { onChange },
2024
2066
  ...sourceLocale === void 0 ? {} : { sourceLocale },
2025
2067
  ...targetLocale === void 0 ? {} : { targetLocale },
2026
2068
  ...translationAdapter === void 0 ? {} : { translationAdapter },
2027
- readOnly
2069
+ readOnly,
2070
+ ...availableLocales === void 0 ? {} : { availableLocales },
2071
+ ...onLocaleAdded === void 0 ? {} : { onLocaleAdded },
2072
+ ...onLocaleRemoved === void 0 ? {} : { onLocaleRemoved },
2073
+ ...onLocaleChange === void 0 ? {} : { onLocaleChange },
2074
+ ...beforeRemoveLocale === void 0 ? {} : { beforeRemoveLocale },
2075
+ ...workspaceSlots?.confirmRemoveLocale === void 0 ? {} : { confirmRemoveLocale: workspaceSlots.confirmRemoveLocale },
2076
+ ...onTranslationStart === void 0 ? {} : { onTranslationStart },
2077
+ ...onTranslationSuccess === void 0 ? {} : { onTranslationSuccess },
2078
+ ...onTranslationError === void 0 ? {} : { onTranslationError },
2079
+ ...onTranslationChange === void 0 ? {} : { onTranslationChange }
2028
2080
  });
2029
2081
  const [newLocale, setNewLocale] = (0, import_react7.useState)("");
2082
+ const headerProps = {
2083
+ schema,
2084
+ sourceLocale: workspace.sourceLocale,
2085
+ targetLocale: workspace.targetLocale,
2086
+ summary: workspace.summary,
2087
+ onTranslateAll: () => void workspace.translateAll(),
2088
+ isTranslating: workspace.isTranslating,
2089
+ readOnly
2090
+ };
2091
+ const actionsProps = {
2092
+ onTranslateAll: headerProps.onTranslateAll,
2093
+ isTranslating: workspace.isTranslating,
2094
+ readOnly
2095
+ };
2096
+ const localeSelectorProps = {
2097
+ targetLocale: workspace.targetLocale,
2098
+ targetLocales: workspace.targetLocales,
2099
+ localeOptions: workspace.localeOptions,
2100
+ newLocale,
2101
+ readOnly,
2102
+ onTargetLocaleChange: workspace.setTargetLocale,
2103
+ onNewLocaleChange: setNewLocale,
2104
+ onAddLocale: () => {
2105
+ const result = workspace.addLocale(newLocale);
2106
+ if (result.success) setNewLocale("");
2107
+ }
2108
+ };
2030
2109
  return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_material16.Stack, { spacing: 2, "data-testid": "translation-workspace", children: [
2031
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_material16.Stack, { direction: { xs: "column", sm: "row" }, spacing: 2, alignItems: { sm: "center" }, children: [
2032
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_material16.Typography, { variant: "h6", children: "Translations" }),
2033
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_material16.Typography, { variant: "body2", children: [
2034
- workspace.summary.completionPercentage,
2035
- "% complete (",
2036
- workspace.summary.translatedCount,
2037
- "/",
2038
- workspace.summary.totalSlots,
2039
- ")"
2040
- ] }),
2041
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2110
+ workspaceSlots?.renderHeader?.(headerProps) ?? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_material16.Stack, { direction: { xs: "column", sm: "row" }, spacing: 2, alignItems: { sm: "center" }, children: [
2111
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_material16.Typography, { variant: "h6", children: translate("workspace.header.title") }),
2112
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_material16.Typography, { variant: "body2", children: translate("workspace.header.progress", {
2113
+ translated: workspace.summary.translatedCount,
2114
+ total: workspace.summary.totalSlots,
2115
+ percent: workspace.summary.completionPercentage
2116
+ }) }),
2117
+ workspaceSlots?.renderActions?.(actionsProps) ?? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2042
2118
  import_material16.Button,
2043
2119
  {
2044
2120
  variant: "contained",
2045
- onClick: () => void workspace.translateAll(),
2121
+ onClick: headerProps.onTranslateAll,
2046
2122
  disabled: readOnly || workspace.isTranslating,
2047
- children: "Translate all"
2123
+ children: translate("workspace.header.translateAll")
2048
2124
  }
2049
2125
  )
2050
2126
  ] }),
2051
2127
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_material16.LinearProgress, { variant: "determinate", value: workspace.summary.completionPercentage }),
2052
- /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_material16.Stack, { direction: "row", spacing: 1, alignItems: "center", children: [
2128
+ workspaceSlots?.renderLocaleSelector?.(localeSelectorProps) ?? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_material16.Stack, { direction: "row", spacing: 1, alignItems: "center", children: [
2129
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_material16.Typography, { variant: "body2", children: [
2130
+ translate("workspace.header.sourceLocale"),
2131
+ ": ",
2132
+ workspace.sourceLocale
2133
+ ] }),
2134
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_material16.Typography, { variant: "body2", children: [
2135
+ translate("workspace.header.targetLocale"),
2136
+ ":"
2137
+ ] }),
2053
2138
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2054
2139
  import_material16.Tabs,
2055
2140
  {
2056
2141
  value: workspace.targetLocale,
2057
2142
  onChange: (_event, value) => workspace.setTargetLocale(value),
2058
- "aria-label": "Translation locales",
2059
- children: workspace.targetLocales.map((locale) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_material16.Tab, { value: locale, label: locale }, locale))
2143
+ "aria-label": translate("workspace.header.targetLocale"),
2144
+ children: workspace.targetLocales.map((locale) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2145
+ import_material16.Tab,
2146
+ {
2147
+ value: locale,
2148
+ label: workspace.localeOptions.find((option) => option.locale === locale)?.label ?? locale
2149
+ },
2150
+ locale
2151
+ ))
2060
2152
  }
2061
2153
  ),
2062
2154
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2063
2155
  import_material16.TextField,
2064
2156
  {
2065
2157
  size: "small",
2066
- label: "Add locale",
2158
+ label: translate("workspace.header.targetLocale"),
2067
2159
  value: newLocale,
2068
2160
  onChange: (event) => setNewLocale(event.target.value)
2069
2161
  }
2070
2162
  ),
2071
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2072
- import_material16.Button,
2073
- {
2074
- onClick: () => {
2075
- workspace.addLocale(newLocale);
2076
- setNewLocale("");
2077
- },
2078
- disabled: readOnly,
2079
- children: "Add"
2080
- }
2081
- )
2163
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_material16.Button, { onClick: localeSelectorProps.onAddLocale, disabled: readOnly, children: translate("builder.addLocale") })
2082
2164
  ] }),
2083
- workspace.error === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_material16.Typography, { color: "error", children: workspaceErrorMessage(workspace.error) }),
2084
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_material16.Stack, { spacing: 1.5, children: workspace.slots.map((slot) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2085
- SlotCard,
2086
- {
2165
+ workspace.error === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_material16.Typography, { color: "error", children: workspaceErrorMessage(workspace.error, translate) }),
2166
+ workspace.removeLocaleConfirmation,
2167
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_material16.Stack, { spacing: 1.5, children: workspace.slots.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_material16.Typography, { color: "text.secondary", children: workspace.targetLocales.length === 0 ? translate("workspace.empty.noTargetLocales") : translate("workspace.empty.noSlotsToTranslate") }) : workspace.slots.map((slot) => {
2168
+ const rowProps = {
2087
2169
  slot,
2088
2170
  readOnly,
2089
2171
  onChange: (text) => workspace.setTranslation(slot, text),
2090
2172
  onTranslate: () => void workspace.translateSlot(slot)
2091
- },
2092
- slot.path
2093
- )) })
2173
+ };
2174
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { children: workspaceSlots?.renderSlotRow?.(rowProps) ?? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(SlotCard, { ...rowProps, translate, renderStatusBadge: workspaceSlots?.renderStatusBadge }) }, slot.path);
2175
+ }) })
2094
2176
  ] });
2095
2177
  }
2096
2178
  // Annotate the CommonJS export names for ESM import in node:
package/dist/index.d.cts CHANGED
@@ -1,8 +1,8 @@
1
- import { BuilderActionIconType, FieldPropertyControlMode, QuestionType, FieldEditorControlsConfig, FieldTypeSelectOptionsConfig, LocalizationSummaryContext, BuilderButtonProps, BuilderCheckboxProps, BuilderErrorMessageProps, BuilderFieldsetProps, BuilderIconButtonProps, BuilderSectionProps, BuilderSelectProps, BuilderTextAreaProps, BuilderTextInputProps, FormBuilderComponents, FormBuilderSlots, FormBuilderProps, BuilderFieldEditorSlotProps, BuilderLocalizationSlotProps, ChoiceGroupSlotProps, BuilderOptionEditorSlotProps, BuilderToolbarSlotProps } from '@form-engine-ts/react';
1
+ import { BuilderActionIconType, FieldPropertyControlMode, QuestionType, FieldEditorControlsConfig, FieldTypeSelectOptionsConfig, LocalizationSummaryContext, BuilderButtonProps, BuilderCheckboxProps, BuilderErrorMessageProps, BuilderFieldsetProps, BuilderIconButtonProps, BuilderSectionProps, BuilderSelectProps, BuilderTextAreaProps, BuilderTextInputProps, FormBuilderComponents, FormBuilderSlots, FormBuilderProps, BuilderFieldEditorSlotProps, BuilderLocalizationSlotProps, ChoiceGroupSlotProps, BuilderOptionEditorSlotProps, BuilderToolbarSlotProps, TranslationEventPayload, TranslationWorkspaceError, TranslationSlotChangeEvent, TranslationWorkspaceSlots } from '@form-engine-ts/react';
2
2
  import * as react from 'react';
3
3
  import { ReactNode, ComponentType, ReactElement } from 'react';
4
+ import { FormEngineMessages, FormEngineTranslator, LocaleOption, FormSchema, DisplayRule, TranslationAdapter, AsyncTranslationAdapter } from '@form-engine-ts/core';
4
5
  import { CardProps, PaperProps, AccordionProps, StackProps, TextFieldProps, SelectProps, MenuProps, CheckboxProps, ButtonProps, IconButtonProps } from '@mui/material';
5
- import { FormSchema, DisplayRule, TranslationAdapter, AsyncTranslationAdapter } from '@form-engine-ts/core';
6
6
 
7
7
  type BuilderSectionName = "basicSettings" | "completionMessage" | "questions" | "addQuestion" | "localization" | "submissionSettings";
8
8
  type MuiButtonVariant = "contained" | "outlined" | "text";
@@ -12,6 +12,18 @@ type MuiComponentSlotProps<T> = Partial<T> & {
12
12
  interface LocaleOptionItem {
13
13
  readonly value: string;
14
14
  readonly label: string;
15
+ readonly translatable?: boolean;
16
+ readonly removable?: boolean;
17
+ readonly metadata?: Readonly<Record<string, unknown>>;
18
+ }
19
+ type MuiLocaleOption = LocaleOption | LocaleOptionItem;
20
+ interface MuiFormEngineI18nOptions {
21
+ readonly locale?: string;
22
+ readonly fallbackLocale?: string;
23
+ readonly messages?: FormEngineMessages;
24
+ readonly translator?: FormEngineTranslator;
25
+ readonly getLocaleLabel?: (locale: string) => string;
26
+ readonly getActionLabel?: (actionType: string) => string;
15
27
  }
16
28
  type LocalizationSectionPlacement = "top" | "beforeQuestions" | "afterQuestions" | "bottom";
17
29
  interface MuiLayoutOptions {
@@ -174,8 +186,9 @@ interface MuiFormBuilderProps extends Omit<FormBuilderProps, "components" | "dis
174
186
  readonly muiSlotProps?: MuiBuilderSlotProps;
175
187
  readonly components?: Partial<FormBuilderComponents>;
176
188
  readonly slots?: Partial<FormBuilderSlots>;
189
+ readonly i18n?: MuiFormEngineI18nOptions;
177
190
  }
178
- declare function MuiFormBuilder({ muiOptions, layoutOptions, localizationOptions, submissionSettingsOptions, muiSlotProps, components: customComponents, slots: customSlots, sectionOrder, ...props }: MuiFormBuilderProps): react.JSX.Element;
191
+ declare function MuiFormBuilder({ muiOptions, layoutOptions, localizationOptions, submissionSettingsOptions, muiSlotProps, components: customComponents, slots: customSlots, i18n, sectionOrder, ...props }: MuiFormBuilderProps): react.JSX.Element;
179
192
 
180
193
  interface ConditionEditorProps {
181
194
  readonly schema: FormSchema;
@@ -210,7 +223,25 @@ interface TranslationWorkspaceProps {
210
223
  readonly targetLocale?: string;
211
224
  readonly translationAdapter?: TranslationAdapter | AsyncTranslationAdapter;
212
225
  readonly readOnly?: boolean;
226
+ readonly availableLocales?: readonly (string | LocaleOption)[];
227
+ readonly onLocaleAdded?: (locale: string) => void;
228
+ readonly onLocaleRemoved?: (locale: string) => void;
229
+ readonly onLocaleChange?: (locale: string) => void;
230
+ readonly beforeRemoveLocale?: (locale: string, context: {
231
+ readonly slotCount: number;
232
+ }) => Promise<boolean> | boolean;
233
+ readonly onTranslationStart?: (params: {
234
+ readonly targetLocale: string;
235
+ readonly mode: "manual" | "automatic";
236
+ }) => void;
237
+ readonly onTranslationSuccess?: (payload: TranslationEventPayload) => void;
238
+ readonly onTranslationError?: (params: {
239
+ readonly targetLocale: string;
240
+ readonly error: TranslationWorkspaceError;
241
+ }) => void;
242
+ readonly onTranslationChange?: (event: TranslationSlotChangeEvent) => void;
243
+ readonly slots?: TranslationWorkspaceSlots;
213
244
  }
214
- declare function TranslationWorkspace({ schema, onChange, sourceLocale, targetLocale, translationAdapter, readOnly }: TranslationWorkspaceProps): react.JSX.Element;
245
+ declare function TranslationWorkspace({ schema, onChange, sourceLocale, targetLocale, translationAdapter, readOnly, availableLocales, onLocaleAdded, onLocaleRemoved, onLocaleChange, beforeRemoveLocale, onTranslationStart, onTranslationSuccess, onTranslationError, onTranslationChange, slots: workspaceSlots }: TranslationWorkspaceProps): react.JSX.Element;
215
246
 
216
- export { type BuilderSectionName, ConditionEditor, type ConditionEditorProps, DEFAULT_MUI_SECTION_ORDER, type LocaleOptionItem, type LocalizationSectionPlacement, MUI_LOCALIZATION_SECTION_ORDERS, type MuiAdapterOptions, type MuiBuilderOverrides, type MuiBuilderSlotProps, MuiButtonAdapter, type MuiButtonVariant, MuiCheckboxAdapter, MuiChoiceGroupSlot, MuiErrorMessageAdapter, type MuiFieldEditorOptions, MuiFieldEditorSlot, MuiFieldsetAdapter, MuiFormBuilder, MuiFormBuilderContext, type MuiFormBuilderContextValue, type MuiFormBuilderProps, MuiIconButtonAdapter, type MuiLayoutOptions, type MuiLocalizationOptions, MuiLocalizationSlot, MuiOptionEditorSlot, MuiSectionAdapter, MuiSelectAdapter, type MuiSlotProps, type MuiSubmissionSettingsOptions, MuiTextAreaAdapter, MuiTextInputAdapter, MuiToolbarSlot, type ResolvedMuiAdapterOptions, TranslationWorkspace, type TranslationWorkspaceProps, createMuiBuilderComponents, createMuiBuilderProps, createMuiBuilderSlots, createMuiButtonAdapter, createMuiCheckboxAdapter, createMuiErrorMessageAdapter, createMuiFieldEditorSlot, createMuiFieldsetAdapter, createMuiIconButtonAdapter, createMuiLocalizationSlot, createMuiOptionEditorSlot, createMuiSectionAdapter, createMuiSelectAdapter, createMuiTextAreaAdapter, createMuiTextInputAdapter, createMuiToolbarSlot, mergeMuiAdapterOptions, muiBuilderComponents, muiBuilderSlots, muiDefaultFieldTypeIcon, muiDefaultIconResolver, resolveMuiAdapterOptions, useResolvedMuiAdapterOptions };
247
+ export { type BuilderSectionName, ConditionEditor, type ConditionEditorProps, DEFAULT_MUI_SECTION_ORDER, type LocaleOptionItem, type LocalizationSectionPlacement, MUI_LOCALIZATION_SECTION_ORDERS, type MuiAdapterOptions, type MuiBuilderOverrides, type MuiBuilderSlotProps, MuiButtonAdapter, type MuiButtonVariant, MuiCheckboxAdapter, MuiChoiceGroupSlot, MuiErrorMessageAdapter, type MuiFieldEditorOptions, MuiFieldEditorSlot, MuiFieldsetAdapter, MuiFormBuilder, MuiFormBuilderContext, type MuiFormBuilderContextValue, type MuiFormBuilderProps, type MuiFormEngineI18nOptions, MuiIconButtonAdapter, type MuiLayoutOptions, type MuiLocaleOption, type MuiLocalizationOptions, MuiLocalizationSlot, MuiOptionEditorSlot, MuiSectionAdapter, MuiSelectAdapter, type MuiSlotProps, type MuiSubmissionSettingsOptions, MuiTextAreaAdapter, MuiTextInputAdapter, MuiToolbarSlot, type ResolvedMuiAdapterOptions, TranslationWorkspace, type TranslationWorkspaceProps, createMuiBuilderComponents, createMuiBuilderProps, createMuiBuilderSlots, createMuiButtonAdapter, createMuiCheckboxAdapter, createMuiErrorMessageAdapter, createMuiFieldEditorSlot, createMuiFieldsetAdapter, createMuiIconButtonAdapter, createMuiLocalizationSlot, createMuiOptionEditorSlot, createMuiSectionAdapter, createMuiSelectAdapter, createMuiTextAreaAdapter, createMuiTextInputAdapter, createMuiToolbarSlot, mergeMuiAdapterOptions, muiBuilderComponents, muiBuilderSlots, muiDefaultFieldTypeIcon, muiDefaultIconResolver, resolveMuiAdapterOptions, useResolvedMuiAdapterOptions };
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { BuilderActionIconType, FieldPropertyControlMode, QuestionType, FieldEditorControlsConfig, FieldTypeSelectOptionsConfig, LocalizationSummaryContext, BuilderButtonProps, BuilderCheckboxProps, BuilderErrorMessageProps, BuilderFieldsetProps, BuilderIconButtonProps, BuilderSectionProps, BuilderSelectProps, BuilderTextAreaProps, BuilderTextInputProps, FormBuilderComponents, FormBuilderSlots, FormBuilderProps, BuilderFieldEditorSlotProps, BuilderLocalizationSlotProps, ChoiceGroupSlotProps, BuilderOptionEditorSlotProps, BuilderToolbarSlotProps } from '@form-engine-ts/react';
1
+ import { BuilderActionIconType, FieldPropertyControlMode, QuestionType, FieldEditorControlsConfig, FieldTypeSelectOptionsConfig, LocalizationSummaryContext, BuilderButtonProps, BuilderCheckboxProps, BuilderErrorMessageProps, BuilderFieldsetProps, BuilderIconButtonProps, BuilderSectionProps, BuilderSelectProps, BuilderTextAreaProps, BuilderTextInputProps, FormBuilderComponents, FormBuilderSlots, FormBuilderProps, BuilderFieldEditorSlotProps, BuilderLocalizationSlotProps, ChoiceGroupSlotProps, BuilderOptionEditorSlotProps, BuilderToolbarSlotProps, TranslationEventPayload, TranslationWorkspaceError, TranslationSlotChangeEvent, TranslationWorkspaceSlots } from '@form-engine-ts/react';
2
2
  import * as react from 'react';
3
3
  import { ReactNode, ComponentType, ReactElement } from 'react';
4
+ import { FormEngineMessages, FormEngineTranslator, LocaleOption, FormSchema, DisplayRule, TranslationAdapter, AsyncTranslationAdapter } from '@form-engine-ts/core';
4
5
  import { CardProps, PaperProps, AccordionProps, StackProps, TextFieldProps, SelectProps, MenuProps, CheckboxProps, ButtonProps, IconButtonProps } from '@mui/material';
5
- import { FormSchema, DisplayRule, TranslationAdapter, AsyncTranslationAdapter } from '@form-engine-ts/core';
6
6
 
7
7
  type BuilderSectionName = "basicSettings" | "completionMessage" | "questions" | "addQuestion" | "localization" | "submissionSettings";
8
8
  type MuiButtonVariant = "contained" | "outlined" | "text";
@@ -12,6 +12,18 @@ type MuiComponentSlotProps<T> = Partial<T> & {
12
12
  interface LocaleOptionItem {
13
13
  readonly value: string;
14
14
  readonly label: string;
15
+ readonly translatable?: boolean;
16
+ readonly removable?: boolean;
17
+ readonly metadata?: Readonly<Record<string, unknown>>;
18
+ }
19
+ type MuiLocaleOption = LocaleOption | LocaleOptionItem;
20
+ interface MuiFormEngineI18nOptions {
21
+ readonly locale?: string;
22
+ readonly fallbackLocale?: string;
23
+ readonly messages?: FormEngineMessages;
24
+ readonly translator?: FormEngineTranslator;
25
+ readonly getLocaleLabel?: (locale: string) => string;
26
+ readonly getActionLabel?: (actionType: string) => string;
15
27
  }
16
28
  type LocalizationSectionPlacement = "top" | "beforeQuestions" | "afterQuestions" | "bottom";
17
29
  interface MuiLayoutOptions {
@@ -174,8 +186,9 @@ interface MuiFormBuilderProps extends Omit<FormBuilderProps, "components" | "dis
174
186
  readonly muiSlotProps?: MuiBuilderSlotProps;
175
187
  readonly components?: Partial<FormBuilderComponents>;
176
188
  readonly slots?: Partial<FormBuilderSlots>;
189
+ readonly i18n?: MuiFormEngineI18nOptions;
177
190
  }
178
- declare function MuiFormBuilder({ muiOptions, layoutOptions, localizationOptions, submissionSettingsOptions, muiSlotProps, components: customComponents, slots: customSlots, sectionOrder, ...props }: MuiFormBuilderProps): react.JSX.Element;
191
+ declare function MuiFormBuilder({ muiOptions, layoutOptions, localizationOptions, submissionSettingsOptions, muiSlotProps, components: customComponents, slots: customSlots, i18n, sectionOrder, ...props }: MuiFormBuilderProps): react.JSX.Element;
179
192
 
180
193
  interface ConditionEditorProps {
181
194
  readonly schema: FormSchema;
@@ -210,7 +223,25 @@ interface TranslationWorkspaceProps {
210
223
  readonly targetLocale?: string;
211
224
  readonly translationAdapter?: TranslationAdapter | AsyncTranslationAdapter;
212
225
  readonly readOnly?: boolean;
226
+ readonly availableLocales?: readonly (string | LocaleOption)[];
227
+ readonly onLocaleAdded?: (locale: string) => void;
228
+ readonly onLocaleRemoved?: (locale: string) => void;
229
+ readonly onLocaleChange?: (locale: string) => void;
230
+ readonly beforeRemoveLocale?: (locale: string, context: {
231
+ readonly slotCount: number;
232
+ }) => Promise<boolean> | boolean;
233
+ readonly onTranslationStart?: (params: {
234
+ readonly targetLocale: string;
235
+ readonly mode: "manual" | "automatic";
236
+ }) => void;
237
+ readonly onTranslationSuccess?: (payload: TranslationEventPayload) => void;
238
+ readonly onTranslationError?: (params: {
239
+ readonly targetLocale: string;
240
+ readonly error: TranslationWorkspaceError;
241
+ }) => void;
242
+ readonly onTranslationChange?: (event: TranslationSlotChangeEvent) => void;
243
+ readonly slots?: TranslationWorkspaceSlots;
213
244
  }
214
- declare function TranslationWorkspace({ schema, onChange, sourceLocale, targetLocale, translationAdapter, readOnly }: TranslationWorkspaceProps): react.JSX.Element;
245
+ declare function TranslationWorkspace({ schema, onChange, sourceLocale, targetLocale, translationAdapter, readOnly, availableLocales, onLocaleAdded, onLocaleRemoved, onLocaleChange, beforeRemoveLocale, onTranslationStart, onTranslationSuccess, onTranslationError, onTranslationChange, slots: workspaceSlots }: TranslationWorkspaceProps): react.JSX.Element;
215
246
 
216
- export { type BuilderSectionName, ConditionEditor, type ConditionEditorProps, DEFAULT_MUI_SECTION_ORDER, type LocaleOptionItem, type LocalizationSectionPlacement, MUI_LOCALIZATION_SECTION_ORDERS, type MuiAdapterOptions, type MuiBuilderOverrides, type MuiBuilderSlotProps, MuiButtonAdapter, type MuiButtonVariant, MuiCheckboxAdapter, MuiChoiceGroupSlot, MuiErrorMessageAdapter, type MuiFieldEditorOptions, MuiFieldEditorSlot, MuiFieldsetAdapter, MuiFormBuilder, MuiFormBuilderContext, type MuiFormBuilderContextValue, type MuiFormBuilderProps, MuiIconButtonAdapter, type MuiLayoutOptions, type MuiLocalizationOptions, MuiLocalizationSlot, MuiOptionEditorSlot, MuiSectionAdapter, MuiSelectAdapter, type MuiSlotProps, type MuiSubmissionSettingsOptions, MuiTextAreaAdapter, MuiTextInputAdapter, MuiToolbarSlot, type ResolvedMuiAdapterOptions, TranslationWorkspace, type TranslationWorkspaceProps, createMuiBuilderComponents, createMuiBuilderProps, createMuiBuilderSlots, createMuiButtonAdapter, createMuiCheckboxAdapter, createMuiErrorMessageAdapter, createMuiFieldEditorSlot, createMuiFieldsetAdapter, createMuiIconButtonAdapter, createMuiLocalizationSlot, createMuiOptionEditorSlot, createMuiSectionAdapter, createMuiSelectAdapter, createMuiTextAreaAdapter, createMuiTextInputAdapter, createMuiToolbarSlot, mergeMuiAdapterOptions, muiBuilderComponents, muiBuilderSlots, muiDefaultFieldTypeIcon, muiDefaultIconResolver, resolveMuiAdapterOptions, useResolvedMuiAdapterOptions };
247
+ export { type BuilderSectionName, ConditionEditor, type ConditionEditorProps, DEFAULT_MUI_SECTION_ORDER, type LocaleOptionItem, type LocalizationSectionPlacement, MUI_LOCALIZATION_SECTION_ORDERS, type MuiAdapterOptions, type MuiBuilderOverrides, type MuiBuilderSlotProps, MuiButtonAdapter, type MuiButtonVariant, MuiCheckboxAdapter, MuiChoiceGroupSlot, MuiErrorMessageAdapter, type MuiFieldEditorOptions, MuiFieldEditorSlot, MuiFieldsetAdapter, MuiFormBuilder, MuiFormBuilderContext, type MuiFormBuilderContextValue, type MuiFormBuilderProps, type MuiFormEngineI18nOptions, MuiIconButtonAdapter, type MuiLayoutOptions, type MuiLocaleOption, type MuiLocalizationOptions, MuiLocalizationSlot, MuiOptionEditorSlot, MuiSectionAdapter, MuiSelectAdapter, type MuiSlotProps, type MuiSubmissionSettingsOptions, MuiTextAreaAdapter, MuiTextInputAdapter, MuiToolbarSlot, type ResolvedMuiAdapterOptions, TranslationWorkspace, type TranslationWorkspaceProps, createMuiBuilderComponents, createMuiBuilderProps, createMuiBuilderSlots, createMuiButtonAdapter, createMuiCheckboxAdapter, createMuiErrorMessageAdapter, createMuiFieldEditorSlot, createMuiFieldsetAdapter, createMuiIconButtonAdapter, createMuiLocalizationSlot, createMuiOptionEditorSlot, createMuiSectionAdapter, createMuiSelectAdapter, createMuiTextAreaAdapter, createMuiTextInputAdapter, createMuiToolbarSlot, mergeMuiAdapterOptions, muiBuilderComponents, muiBuilderSlots, muiDefaultFieldTypeIcon, muiDefaultIconResolver, resolveMuiAdapterOptions, useResolvedMuiAdapterOptions };
package/dist/index.js CHANGED
@@ -1526,10 +1526,16 @@ function normalizeLocaleOptions(options, getLocaleLabel) {
1526
1526
  const seen = /* @__PURE__ */ new Set();
1527
1527
  const normalized = [];
1528
1528
  for (const item of options) {
1529
- const value = typeof item === "string" ? item : item.value;
1529
+ const value = typeof item === "string" ? item : "value" in item ? item.value : item.locale;
1530
1530
  if (value.length === 0 || seen.has(value)) continue;
1531
1531
  seen.add(value);
1532
- normalized.push({ value, label: typeof item === "string" ? getLocaleLabel?.(value) ?? value : item.label });
1532
+ normalized.push({
1533
+ value,
1534
+ label: typeof item === "string" ? getLocaleLabel?.(value) ?? value : item.label,
1535
+ ...typeof item === "string" || item.translatable === void 0 ? {} : { translatable: item.translatable },
1536
+ ...typeof item === "string" || item.removable === void 0 ? {} : { removable: item.removable },
1537
+ ...typeof item === "string" || item.metadata === void 0 ? {} : { metadata: item.metadata }
1538
+ });
1533
1539
  }
1534
1540
  return normalized;
1535
1541
  }
@@ -1572,6 +1578,8 @@ function createMuiLocalizationSlot(options) {
1572
1578
  const hasLocaleSelector = filteredAvailableLocales !== void 0;
1573
1579
  const localeLimitReached = policy?.maxLocales !== void 0 && registeredLocales.size >= policy.maxLocales;
1574
1580
  const normalizedLocale = newLocale.trim();
1581
+ const currentLocaleOption = availableLocaleOptions?.find((locale) => locale.value === currentLocale);
1582
+ const currentLocaleTranslatable = currentLocaleOption?.translatable !== false;
1575
1583
  const selectedLocaleAvailable = !hasLocaleSelector || filteredAvailableLocales.some((locale) => locale.value === normalizedLocale);
1576
1584
  const localeAllowed = policy?.allowedLocales === void 0 || policy.allowedLocales.includes(normalizedLocale);
1577
1585
  const canAddLocale = !readOnly && normalizedLocale.length > 0 && selectedLocaleAvailable && localeAllowed && !registeredLocales.has(normalizedLocale) && !localeLimitReached;
@@ -1737,7 +1745,7 @@ function createMuiLocalizationSlot(options) {
1737
1745
  {
1738
1746
  variant: "secondary",
1739
1747
  noWrap: localizationOptions.noWrapActions ?? true,
1740
- disabled: readOnly || !editingLocaleConfigured || isTranslating,
1748
+ disabled: readOnly || !editingLocaleConfigured || isTranslating || !currentLocaleTranslatable,
1741
1749
  onClick: onAutoTranslate,
1742
1750
  children: isTranslating ? translate("builder.translating") : translate("builder.autoTranslate")
1743
1751
  }
@@ -1865,7 +1873,8 @@ function createMuiBuilderProps(options, overrides = {}) {
1865
1873
 
1866
1874
  // src/MuiFormBuilder.tsx
1867
1875
  import {
1868
- FormBuilder
1876
+ FormBuilder,
1877
+ FormEngineI18nProvider
1869
1878
  } from "@form-engine-ts/react";
1870
1879
  import { useMemo } from "react";
1871
1880
  import { jsx as jsx17 } from "react/jsx-runtime";
@@ -1877,6 +1886,7 @@ function MuiFormBuilder({
1877
1886
  muiSlotProps,
1878
1887
  components: customComponents,
1879
1888
  slots: customSlots,
1889
+ i18n,
1880
1890
  sectionOrder,
1881
1891
  ...props
1882
1892
  }) {
@@ -1888,7 +1898,14 @@ function MuiFormBuilder({
1888
1898
  }),
1889
1899
  [layoutOptions, localizationOptions, muiOptions, muiSlotProps]
1890
1900
  );
1891
- const contextValue = useMemo(() => ({ options: contextOptions }), [contextOptions]);
1901
+ const resolvedMuiOptions = useMemo(
1902
+ () => mergeMuiAdapterOptions(contextOptions, {
1903
+ ...i18n?.getLocaleLabel === void 0 ? {} : { getLocaleLabel: i18n.getLocaleLabel },
1904
+ ...i18n?.getActionLabel === void 0 ? {} : { getActionLabel: i18n.getActionLabel }
1905
+ }),
1906
+ [contextOptions, i18n?.getActionLabel, i18n?.getLocaleLabel]
1907
+ );
1908
+ const contextValue = useMemo(() => ({ options: resolvedMuiOptions }), [resolvedMuiOptions]);
1892
1909
  const components = useMemo(() => ({ ...muiBuilderComponents, ...customComponents }), [customComponents]);
1893
1910
  const slots = useMemo(() => ({ ...muiBuilderSlots, ...customSlots }), [customSlots]);
1894
1911
  const placement = contextOptions.localizationOptions?.placement;
@@ -1909,7 +1926,7 @@ function MuiFormBuilder({
1909
1926
  );
1910
1927
  return order;
1911
1928
  })();
1912
- return /* @__PURE__ */ jsx17(MuiFormBuilderContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx17(
1929
+ const content = /* @__PURE__ */ jsx17(MuiFormBuilderContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx17(
1913
1930
  FormBuilder,
1914
1931
  {
1915
1932
  ...props,
@@ -1920,10 +1937,24 @@ function MuiFormBuilder({
1920
1937
  ...submissionSettingsOptions === void 0 ? {} : { submissionSettingsOptions }
1921
1938
  }
1922
1939
  ) });
1940
+ if (i18n === void 0) return content;
1941
+ return /* @__PURE__ */ jsx17(
1942
+ FormEngineI18nProvider,
1943
+ {
1944
+ ...i18n.locale === void 0 ? {} : { locale: i18n.locale },
1945
+ ...i18n.fallbackLocale === void 0 ? {} : { fallbackLocale: i18n.fallbackLocale },
1946
+ ...i18n.messages === void 0 ? {} : { messages: i18n.messages },
1947
+ ...i18n.translator === void 0 ? {} : { translator: i18n.translator },
1948
+ children: content
1949
+ }
1950
+ );
1923
1951
  }
1924
1952
 
1925
1953
  // src/workspace/TranslationWorkspace.tsx
1926
- import { useTranslationWorkspace } from "@form-engine-ts/react";
1954
+ import {
1955
+ useFormEngineI18n,
1956
+ useTranslationWorkspace
1957
+ } from "@form-engine-ts/react";
1927
1958
  import {
1928
1959
  Button as Button3,
1929
1960
  Card as Card2,
@@ -1938,28 +1969,31 @@ import {
1938
1969
  } from "@mui/material";
1939
1970
  import { useState as useState2 } from "react";
1940
1971
  import { jsx as jsx18, jsxs as jsxs11 } from "react/jsx-runtime";
1941
- var statusLabel = {
1942
- missing: "Missing",
1943
- translated: "Translated",
1944
- stale: "Source changed",
1945
- manual: "Manual",
1946
- "manual-stale": "Manual / source changed"
1972
+ var statusKey = {
1973
+ missing: "workspace.status.missing",
1974
+ translated: "workspace.status.translated",
1975
+ stale: "workspace.status.stale",
1976
+ manual: "workspace.status.manual",
1977
+ "manual-stale": "workspace.status.manualStale"
1947
1978
  };
1948
1979
  function SlotCard({
1949
1980
  slot,
1950
1981
  readOnly,
1951
1982
  onChange,
1952
- onTranslate
1983
+ onTranslate,
1984
+ translate,
1985
+ renderStatusBadge
1953
1986
  }) {
1987
+ const status = slot.status ?? "missing";
1954
1988
  return /* @__PURE__ */ jsx18(Card2, { variant: "outlined", children: /* @__PURE__ */ jsx18(CardContent, { children: /* @__PURE__ */ jsxs11(Stack6, { spacing: 1.5, children: [
1955
1989
  /* @__PURE__ */ jsxs11(Stack6, { direction: "row", justifyContent: "space-between", alignItems: "center", children: [
1956
1990
  /* @__PURE__ */ jsx18(Typography5, { variant: "subtitle2", children: slot.path }),
1957
- /* @__PURE__ */ jsx18(Chip2, { size: "small", label: statusLabel[slot.status ?? "missing"] })
1991
+ renderStatusBadge?.({ status }) ?? /* @__PURE__ */ jsx18(Chip2, { size: "small", label: translate(statusKey[status]) })
1958
1992
  ] }),
1959
1993
  /* @__PURE__ */ jsx18(
1960
1994
  TextField4,
1961
1995
  {
1962
- label: "Source",
1996
+ label: translate("workspace.slot.sourceText"),
1963
1997
  value: slot.sourceText,
1964
1998
  multiline: true,
1965
1999
  fullWidth: true,
@@ -1969,7 +2003,7 @@ function SlotCard({
1969
2003
  /* @__PURE__ */ jsx18(
1970
2004
  TextField4,
1971
2005
  {
1972
- label: "Translation",
2006
+ label: translate("workspace.slot.translatedText"),
1973
2007
  value: slot.existingText ?? "",
1974
2008
  multiline: true,
1975
2009
  fullWidth: true,
@@ -1977,28 +2011,28 @@ function SlotCard({
1977
2011
  onChange: (event) => onChange(event.target.value)
1978
2012
  }
1979
2013
  ),
1980
- slot.status === "stale" || slot.status === "manual-stale" ? /* @__PURE__ */ jsx18(Typography5, { color: "warning.main", variant: "body2", children: "The source text has changed since this translation was created." }) : null,
1981
- /* @__PURE__ */ jsx18(Button3, { variant: "outlined", onClick: onTranslate, disabled: readOnly, children: "Translate this slot" })
2014
+ slot.status === "stale" || slot.status === "manual-stale" ? /* @__PURE__ */ jsx18(Typography5, { color: "warning.main", variant: "body2", children: translate("workspace.status.stale") }) : null,
2015
+ /* @__PURE__ */ jsx18(Button3, { variant: "outlined", onClick: onTranslate, disabled: readOnly, children: translate("workspace.slot.translateSingle") })
1982
2016
  ] }) }) });
1983
2017
  }
1984
- function workspaceErrorMessage(error) {
2018
+ function workspaceErrorMessage(error, translate) {
1985
2019
  switch (error.type) {
1986
2020
  case "locale_not_allowed":
1987
- return `Locale "${error.locale}" is not allowed.`;
2021
+ return translate("workspace.errors.localeNotAllowed", { locale: error.locale });
1988
2022
  case "locale_already_exists":
1989
- return `Locale "${error.locale}" is already registered.`;
2023
+ return translate("workspace.errors.localeNotAllowed", { locale: error.locale });
1990
2024
  case "invalid_locale_format":
1991
- return `Invalid locale format: "${error.locale}"`;
2025
+ return translate("workspace.errors.localeNotAllowed", { locale: error.locale });
1992
2026
  case "max_locales_exceeded":
1993
- return `At most ${error.max} locales are allowed.`;
2027
+ return translate("workspace.errors.maxLocalesExceeded", { max: error.max });
1994
2028
  case "read_only_mode":
1995
- return "This workspace is read-only.";
2029
+ return translate("workspace.errors.readOnly");
1996
2030
  case "adapter_not_configured":
1997
- return "A translation adapter is not configured.";
2031
+ return translate("workspace.errors.adapterNotConfigured");
1998
2032
  case "target_locale_missing":
1999
- return "A target locale is required.";
2033
+ return translate("workspace.empty.noTargetLocales");
2000
2034
  case "translation_failed":
2001
- return error.message;
2035
+ return translate("workspace.errors.translationFailed");
2002
2036
  case "custom_validation_failed":
2003
2037
  return error.message;
2004
2038
  }
@@ -2009,81 +2043,133 @@ function TranslationWorkspace({
2009
2043
  sourceLocale,
2010
2044
  targetLocale,
2011
2045
  translationAdapter,
2012
- readOnly = false
2046
+ readOnly = false,
2047
+ availableLocales,
2048
+ onLocaleAdded,
2049
+ onLocaleRemoved,
2050
+ onLocaleChange,
2051
+ beforeRemoveLocale,
2052
+ onTranslationStart,
2053
+ onTranslationSuccess,
2054
+ onTranslationError,
2055
+ onTranslationChange,
2056
+ slots: workspaceSlots
2013
2057
  }) {
2058
+ const { translator } = useFormEngineI18n();
2059
+ const translate = (key, params = {}) => translator(key, params);
2014
2060
  const workspace = useTranslationWorkspace({
2015
2061
  schema,
2016
2062
  ...onChange === void 0 ? {} : { onChange },
2017
2063
  ...sourceLocale === void 0 ? {} : { sourceLocale },
2018
2064
  ...targetLocale === void 0 ? {} : { targetLocale },
2019
2065
  ...translationAdapter === void 0 ? {} : { translationAdapter },
2020
- readOnly
2066
+ readOnly,
2067
+ ...availableLocales === void 0 ? {} : { availableLocales },
2068
+ ...onLocaleAdded === void 0 ? {} : { onLocaleAdded },
2069
+ ...onLocaleRemoved === void 0 ? {} : { onLocaleRemoved },
2070
+ ...onLocaleChange === void 0 ? {} : { onLocaleChange },
2071
+ ...beforeRemoveLocale === void 0 ? {} : { beforeRemoveLocale },
2072
+ ...workspaceSlots?.confirmRemoveLocale === void 0 ? {} : { confirmRemoveLocale: workspaceSlots.confirmRemoveLocale },
2073
+ ...onTranslationStart === void 0 ? {} : { onTranslationStart },
2074
+ ...onTranslationSuccess === void 0 ? {} : { onTranslationSuccess },
2075
+ ...onTranslationError === void 0 ? {} : { onTranslationError },
2076
+ ...onTranslationChange === void 0 ? {} : { onTranslationChange }
2021
2077
  });
2022
2078
  const [newLocale, setNewLocale] = useState2("");
2079
+ const headerProps = {
2080
+ schema,
2081
+ sourceLocale: workspace.sourceLocale,
2082
+ targetLocale: workspace.targetLocale,
2083
+ summary: workspace.summary,
2084
+ onTranslateAll: () => void workspace.translateAll(),
2085
+ isTranslating: workspace.isTranslating,
2086
+ readOnly
2087
+ };
2088
+ const actionsProps = {
2089
+ onTranslateAll: headerProps.onTranslateAll,
2090
+ isTranslating: workspace.isTranslating,
2091
+ readOnly
2092
+ };
2093
+ const localeSelectorProps = {
2094
+ targetLocale: workspace.targetLocale,
2095
+ targetLocales: workspace.targetLocales,
2096
+ localeOptions: workspace.localeOptions,
2097
+ newLocale,
2098
+ readOnly,
2099
+ onTargetLocaleChange: workspace.setTargetLocale,
2100
+ onNewLocaleChange: setNewLocale,
2101
+ onAddLocale: () => {
2102
+ const result = workspace.addLocale(newLocale);
2103
+ if (result.success) setNewLocale("");
2104
+ }
2105
+ };
2023
2106
  return /* @__PURE__ */ jsxs11(Stack6, { spacing: 2, "data-testid": "translation-workspace", children: [
2024
- /* @__PURE__ */ jsxs11(Stack6, { direction: { xs: "column", sm: "row" }, spacing: 2, alignItems: { sm: "center" }, children: [
2025
- /* @__PURE__ */ jsx18(Typography5, { variant: "h6", children: "Translations" }),
2026
- /* @__PURE__ */ jsxs11(Typography5, { variant: "body2", children: [
2027
- workspace.summary.completionPercentage,
2028
- "% complete (",
2029
- workspace.summary.translatedCount,
2030
- "/",
2031
- workspace.summary.totalSlots,
2032
- ")"
2033
- ] }),
2034
- /* @__PURE__ */ jsx18(
2107
+ workspaceSlots?.renderHeader?.(headerProps) ?? /* @__PURE__ */ jsxs11(Stack6, { direction: { xs: "column", sm: "row" }, spacing: 2, alignItems: { sm: "center" }, children: [
2108
+ /* @__PURE__ */ jsx18(Typography5, { variant: "h6", children: translate("workspace.header.title") }),
2109
+ /* @__PURE__ */ jsx18(Typography5, { variant: "body2", children: translate("workspace.header.progress", {
2110
+ translated: workspace.summary.translatedCount,
2111
+ total: workspace.summary.totalSlots,
2112
+ percent: workspace.summary.completionPercentage
2113
+ }) }),
2114
+ workspaceSlots?.renderActions?.(actionsProps) ?? /* @__PURE__ */ jsx18(
2035
2115
  Button3,
2036
2116
  {
2037
2117
  variant: "contained",
2038
- onClick: () => void workspace.translateAll(),
2118
+ onClick: headerProps.onTranslateAll,
2039
2119
  disabled: readOnly || workspace.isTranslating,
2040
- children: "Translate all"
2120
+ children: translate("workspace.header.translateAll")
2041
2121
  }
2042
2122
  )
2043
2123
  ] }),
2044
2124
  /* @__PURE__ */ jsx18(LinearProgress, { variant: "determinate", value: workspace.summary.completionPercentage }),
2045
- /* @__PURE__ */ jsxs11(Stack6, { direction: "row", spacing: 1, alignItems: "center", children: [
2125
+ workspaceSlots?.renderLocaleSelector?.(localeSelectorProps) ?? /* @__PURE__ */ jsxs11(Stack6, { direction: "row", spacing: 1, alignItems: "center", children: [
2126
+ /* @__PURE__ */ jsxs11(Typography5, { variant: "body2", children: [
2127
+ translate("workspace.header.sourceLocale"),
2128
+ ": ",
2129
+ workspace.sourceLocale
2130
+ ] }),
2131
+ /* @__PURE__ */ jsxs11(Typography5, { variant: "body2", children: [
2132
+ translate("workspace.header.targetLocale"),
2133
+ ":"
2134
+ ] }),
2046
2135
  /* @__PURE__ */ jsx18(
2047
2136
  Tabs2,
2048
2137
  {
2049
2138
  value: workspace.targetLocale,
2050
2139
  onChange: (_event, value) => workspace.setTargetLocale(value),
2051
- "aria-label": "Translation locales",
2052
- children: workspace.targetLocales.map((locale) => /* @__PURE__ */ jsx18(Tab2, { value: locale, label: locale }, locale))
2140
+ "aria-label": translate("workspace.header.targetLocale"),
2141
+ children: workspace.targetLocales.map((locale) => /* @__PURE__ */ jsx18(
2142
+ Tab2,
2143
+ {
2144
+ value: locale,
2145
+ label: workspace.localeOptions.find((option) => option.locale === locale)?.label ?? locale
2146
+ },
2147
+ locale
2148
+ ))
2053
2149
  }
2054
2150
  ),
2055
2151
  /* @__PURE__ */ jsx18(
2056
2152
  TextField4,
2057
2153
  {
2058
2154
  size: "small",
2059
- label: "Add locale",
2155
+ label: translate("workspace.header.targetLocale"),
2060
2156
  value: newLocale,
2061
2157
  onChange: (event) => setNewLocale(event.target.value)
2062
2158
  }
2063
2159
  ),
2064
- /* @__PURE__ */ jsx18(
2065
- Button3,
2066
- {
2067
- onClick: () => {
2068
- workspace.addLocale(newLocale);
2069
- setNewLocale("");
2070
- },
2071
- disabled: readOnly,
2072
- children: "Add"
2073
- }
2074
- )
2160
+ /* @__PURE__ */ jsx18(Button3, { onClick: localeSelectorProps.onAddLocale, disabled: readOnly, children: translate("builder.addLocale") })
2075
2161
  ] }),
2076
- workspace.error === void 0 ? null : /* @__PURE__ */ jsx18(Typography5, { color: "error", children: workspaceErrorMessage(workspace.error) }),
2077
- /* @__PURE__ */ jsx18(Stack6, { spacing: 1.5, children: workspace.slots.map((slot) => /* @__PURE__ */ jsx18(
2078
- SlotCard,
2079
- {
2162
+ workspace.error === void 0 ? null : /* @__PURE__ */ jsx18(Typography5, { color: "error", children: workspaceErrorMessage(workspace.error, translate) }),
2163
+ workspace.removeLocaleConfirmation,
2164
+ /* @__PURE__ */ jsx18(Stack6, { spacing: 1.5, children: workspace.slots.length === 0 ? /* @__PURE__ */ jsx18(Typography5, { color: "text.secondary", children: workspace.targetLocales.length === 0 ? translate("workspace.empty.noTargetLocales") : translate("workspace.empty.noSlotsToTranslate") }) : workspace.slots.map((slot) => {
2165
+ const rowProps = {
2080
2166
  slot,
2081
2167
  readOnly,
2082
2168
  onChange: (text) => workspace.setTranslation(slot, text),
2083
2169
  onTranslate: () => void workspace.translateSlot(slot)
2084
- },
2085
- slot.path
2086
- )) })
2170
+ };
2171
+ return /* @__PURE__ */ jsx18("span", { children: workspaceSlots?.renderSlotRow?.(rowProps) ?? /* @__PURE__ */ jsx18(SlotCard, { ...rowProps, translate, renderStatusBadge: workspaceSlots?.renderStatusBadge }) }, slot.path);
2172
+ }) })
2087
2173
  ] });
2088
2174
  }
2089
2175
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@form-engine-ts/mui",
3
- "version": "5.0.1",
3
+ "version": "6.0.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -46,8 +46,8 @@
46
46
  "@mui/material": "^6.0.0 || ^7.0.0",
47
47
  "react": "^18.0.0 || ^19.0.0",
48
48
  "react-dom": "^18.0.0 || ^19.0.0",
49
- "@form-engine-ts/core": "5.0.1",
50
- "@form-engine-ts/react": "5.0.1"
49
+ "@form-engine-ts/core": "6.0.0",
50
+ "@form-engine-ts/react": "6.0.0"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@emotion/react": "^11.14.0",
@@ -58,8 +58,8 @@
58
58
  "@types/react-dom": "^19.2.4",
59
59
  "react": "^19.2.8",
60
60
  "react-dom": "^19.2.8",
61
- "@form-engine-ts/core": "5.0.1",
62
- "@form-engine-ts/react": "5.0.1"
61
+ "@form-engine-ts/core": "6.0.0",
62
+ "@form-engine-ts/react": "6.0.0"
63
63
  },
64
64
  "scripts": {
65
65
  "build": "tsup src/index.ts --format esm,cjs --dts --clean --external @emotion/react --external @emotion/styled --external @form-engine-ts/core --external @form-engine-ts/react --external @mui/icons-material --external @mui/material --external react --external react-dom",