@form-engine-ts/mui 5.0.0 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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,123 @@ 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
+ slots: workspaceSlots
2020
2056
  }) {
2057
+ const { translator } = (0, import_react6.useFormEngineI18n)();
2058
+ const translate = (key, params = {}) => translator(key, params);
2021
2059
  const workspace = (0, import_react6.useTranslationWorkspace)({
2022
2060
  schema,
2023
2061
  ...onChange === void 0 ? {} : { onChange },
2024
2062
  ...sourceLocale === void 0 ? {} : { sourceLocale },
2025
2063
  ...targetLocale === void 0 ? {} : { targetLocale },
2026
2064
  ...translationAdapter === void 0 ? {} : { translationAdapter },
2027
- readOnly
2065
+ readOnly,
2066
+ ...availableLocales === void 0 ? {} : { availableLocales },
2067
+ ...onLocaleAdded === void 0 ? {} : { onLocaleAdded },
2068
+ ...onLocaleRemoved === void 0 ? {} : { onLocaleRemoved },
2069
+ ...onLocaleChange === void 0 ? {} : { onLocaleChange },
2070
+ ...beforeRemoveLocale === void 0 ? {} : { beforeRemoveLocale }
2028
2071
  });
2029
2072
  const [newLocale, setNewLocale] = (0, import_react7.useState)("");
2073
+ const headerProps = {
2074
+ schema,
2075
+ sourceLocale: workspace.sourceLocale,
2076
+ targetLocale: workspace.targetLocale,
2077
+ summary: workspace.summary,
2078
+ onTranslateAll: () => void workspace.translateAll(),
2079
+ isTranslating: workspace.isTranslating,
2080
+ readOnly
2081
+ };
2082
+ const actionsProps = {
2083
+ onTranslateAll: headerProps.onTranslateAll,
2084
+ isTranslating: workspace.isTranslating,
2085
+ readOnly
2086
+ };
2087
+ const localeSelectorProps = {
2088
+ targetLocale: workspace.targetLocale,
2089
+ targetLocales: workspace.targetLocales,
2090
+ localeOptions: workspace.localeOptions,
2091
+ newLocale,
2092
+ readOnly,
2093
+ onTargetLocaleChange: workspace.setTargetLocale,
2094
+ onNewLocaleChange: setNewLocale,
2095
+ onAddLocale: () => {
2096
+ const result = workspace.addLocale(newLocale);
2097
+ if (result.success) setNewLocale("");
2098
+ }
2099
+ };
2030
2100
  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)(
2101
+ workspaceSlots?.renderHeader?.(headerProps) ?? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_material16.Stack, { direction: { xs: "column", sm: "row" }, spacing: 2, alignItems: { sm: "center" }, children: [
2102
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_material16.Typography, { variant: "h6", children: translate("workspace.header.title") }),
2103
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_material16.Typography, { variant: "body2", children: translate("workspace.header.progress", {
2104
+ translated: workspace.summary.translatedCount,
2105
+ total: workspace.summary.totalSlots,
2106
+ percent: workspace.summary.completionPercentage
2107
+ }) }),
2108
+ workspaceSlots?.renderActions?.(actionsProps) ?? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2042
2109
  import_material16.Button,
2043
2110
  {
2044
2111
  variant: "contained",
2045
- onClick: () => void workspace.translateAll(),
2112
+ onClick: headerProps.onTranslateAll,
2046
2113
  disabled: readOnly || workspace.isTranslating,
2047
- children: "Translate all"
2114
+ children: translate("workspace.header.translateAll")
2048
2115
  }
2049
2116
  )
2050
2117
  ] }),
2051
2118
  /* @__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: [
2119
+ workspaceSlots?.renderLocaleSelector?.(localeSelectorProps) ?? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_material16.Stack, { direction: "row", spacing: 1, alignItems: "center", children: [
2120
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_material16.Typography, { variant: "body2", children: [
2121
+ translate("workspace.header.sourceLocale"),
2122
+ ": ",
2123
+ workspace.sourceLocale
2124
+ ] }),
2125
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_material16.Typography, { variant: "body2", children: [
2126
+ translate("workspace.header.targetLocale"),
2127
+ ":"
2128
+ ] }),
2053
2129
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2054
2130
  import_material16.Tabs,
2055
2131
  {
2056
2132
  value: workspace.targetLocale,
2057
2133
  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))
2134
+ "aria-label": translate("workspace.header.targetLocale"),
2135
+ children: workspace.targetLocales.map((locale) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2136
+ import_material16.Tab,
2137
+ {
2138
+ value: locale,
2139
+ label: workspace.localeOptions.find((option) => option.locale === locale)?.label ?? locale
2140
+ },
2141
+ locale
2142
+ ))
2060
2143
  }
2061
2144
  ),
2062
2145
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2063
2146
  import_material16.TextField,
2064
2147
  {
2065
2148
  size: "small",
2066
- label: "Add locale",
2149
+ label: translate("workspace.header.targetLocale"),
2067
2150
  value: newLocale,
2068
2151
  onChange: (event) => setNewLocale(event.target.value)
2069
2152
  }
2070
2153
  ),
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
- )
2154
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_material16.Button, { onClick: localeSelectorProps.onAddLocale, disabled: readOnly, children: translate("builder.addLocale") })
2082
2155
  ] }),
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
- {
2156
+ workspace.error === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_material16.Typography, { color: "error", children: workspaceErrorMessage(workspace.error, translate) }),
2157
+ /* @__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) => {
2158
+ const rowProps = {
2087
2159
  slot,
2088
2160
  readOnly,
2089
2161
  onChange: (text) => workspace.setTranslation(slot, text),
2090
2162
  onTranslate: () => void workspace.translateSlot(slot)
2091
- },
2092
- slot.path
2093
- )) })
2163
+ };
2164
+ 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);
2165
+ }) })
2094
2166
  ] });
2095
2167
  }
2096
2168
  // 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, 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,15 @@ 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 slots?: TranslationWorkspaceSlots;
213
234
  }
214
- declare function TranslationWorkspace({ schema, onChange, sourceLocale, targetLocale, translationAdapter, readOnly }: TranslationWorkspaceProps): react.JSX.Element;
235
+ declare function TranslationWorkspace({ schema, onChange, sourceLocale, targetLocale, translationAdapter, readOnly, availableLocales, onLocaleAdded, onLocaleRemoved, onLocaleChange, beforeRemoveLocale, slots: workspaceSlots }: TranslationWorkspaceProps): react.JSX.Element;
215
236
 
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 };
237
+ 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, 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,15 @@ 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 slots?: TranslationWorkspaceSlots;
213
234
  }
214
- declare function TranslationWorkspace({ schema, onChange, sourceLocale, targetLocale, translationAdapter, readOnly }: TranslationWorkspaceProps): react.JSX.Element;
235
+ declare function TranslationWorkspace({ schema, onChange, sourceLocale, targetLocale, translationAdapter, readOnly, availableLocales, onLocaleAdded, onLocaleRemoved, onLocaleChange, beforeRemoveLocale, slots: workspaceSlots }: TranslationWorkspaceProps): react.JSX.Element;
215
236
 
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 };
237
+ 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,123 @@ 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
+ slots: workspaceSlots
2013
2053
  }) {
2054
+ const { translator } = useFormEngineI18n();
2055
+ const translate = (key, params = {}) => translator(key, params);
2014
2056
  const workspace = useTranslationWorkspace({
2015
2057
  schema,
2016
2058
  ...onChange === void 0 ? {} : { onChange },
2017
2059
  ...sourceLocale === void 0 ? {} : { sourceLocale },
2018
2060
  ...targetLocale === void 0 ? {} : { targetLocale },
2019
2061
  ...translationAdapter === void 0 ? {} : { translationAdapter },
2020
- readOnly
2062
+ readOnly,
2063
+ ...availableLocales === void 0 ? {} : { availableLocales },
2064
+ ...onLocaleAdded === void 0 ? {} : { onLocaleAdded },
2065
+ ...onLocaleRemoved === void 0 ? {} : { onLocaleRemoved },
2066
+ ...onLocaleChange === void 0 ? {} : { onLocaleChange },
2067
+ ...beforeRemoveLocale === void 0 ? {} : { beforeRemoveLocale }
2021
2068
  });
2022
2069
  const [newLocale, setNewLocale] = useState2("");
2070
+ const headerProps = {
2071
+ schema,
2072
+ sourceLocale: workspace.sourceLocale,
2073
+ targetLocale: workspace.targetLocale,
2074
+ summary: workspace.summary,
2075
+ onTranslateAll: () => void workspace.translateAll(),
2076
+ isTranslating: workspace.isTranslating,
2077
+ readOnly
2078
+ };
2079
+ const actionsProps = {
2080
+ onTranslateAll: headerProps.onTranslateAll,
2081
+ isTranslating: workspace.isTranslating,
2082
+ readOnly
2083
+ };
2084
+ const localeSelectorProps = {
2085
+ targetLocale: workspace.targetLocale,
2086
+ targetLocales: workspace.targetLocales,
2087
+ localeOptions: workspace.localeOptions,
2088
+ newLocale,
2089
+ readOnly,
2090
+ onTargetLocaleChange: workspace.setTargetLocale,
2091
+ onNewLocaleChange: setNewLocale,
2092
+ onAddLocale: () => {
2093
+ const result = workspace.addLocale(newLocale);
2094
+ if (result.success) setNewLocale("");
2095
+ }
2096
+ };
2023
2097
  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(
2098
+ workspaceSlots?.renderHeader?.(headerProps) ?? /* @__PURE__ */ jsxs11(Stack6, { direction: { xs: "column", sm: "row" }, spacing: 2, alignItems: { sm: "center" }, children: [
2099
+ /* @__PURE__ */ jsx18(Typography5, { variant: "h6", children: translate("workspace.header.title") }),
2100
+ /* @__PURE__ */ jsx18(Typography5, { variant: "body2", children: translate("workspace.header.progress", {
2101
+ translated: workspace.summary.translatedCount,
2102
+ total: workspace.summary.totalSlots,
2103
+ percent: workspace.summary.completionPercentage
2104
+ }) }),
2105
+ workspaceSlots?.renderActions?.(actionsProps) ?? /* @__PURE__ */ jsx18(
2035
2106
  Button3,
2036
2107
  {
2037
2108
  variant: "contained",
2038
- onClick: () => void workspace.translateAll(),
2109
+ onClick: headerProps.onTranslateAll,
2039
2110
  disabled: readOnly || workspace.isTranslating,
2040
- children: "Translate all"
2111
+ children: translate("workspace.header.translateAll")
2041
2112
  }
2042
2113
  )
2043
2114
  ] }),
2044
2115
  /* @__PURE__ */ jsx18(LinearProgress, { variant: "determinate", value: workspace.summary.completionPercentage }),
2045
- /* @__PURE__ */ jsxs11(Stack6, { direction: "row", spacing: 1, alignItems: "center", children: [
2116
+ workspaceSlots?.renderLocaleSelector?.(localeSelectorProps) ?? /* @__PURE__ */ jsxs11(Stack6, { direction: "row", spacing: 1, alignItems: "center", children: [
2117
+ /* @__PURE__ */ jsxs11(Typography5, { variant: "body2", children: [
2118
+ translate("workspace.header.sourceLocale"),
2119
+ ": ",
2120
+ workspace.sourceLocale
2121
+ ] }),
2122
+ /* @__PURE__ */ jsxs11(Typography5, { variant: "body2", children: [
2123
+ translate("workspace.header.targetLocale"),
2124
+ ":"
2125
+ ] }),
2046
2126
  /* @__PURE__ */ jsx18(
2047
2127
  Tabs2,
2048
2128
  {
2049
2129
  value: workspace.targetLocale,
2050
2130
  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))
2131
+ "aria-label": translate("workspace.header.targetLocale"),
2132
+ children: workspace.targetLocales.map((locale) => /* @__PURE__ */ jsx18(
2133
+ Tab2,
2134
+ {
2135
+ value: locale,
2136
+ label: workspace.localeOptions.find((option) => option.locale === locale)?.label ?? locale
2137
+ },
2138
+ locale
2139
+ ))
2053
2140
  }
2054
2141
  ),
2055
2142
  /* @__PURE__ */ jsx18(
2056
2143
  TextField4,
2057
2144
  {
2058
2145
  size: "small",
2059
- label: "Add locale",
2146
+ label: translate("workspace.header.targetLocale"),
2060
2147
  value: newLocale,
2061
2148
  onChange: (event) => setNewLocale(event.target.value)
2062
2149
  }
2063
2150
  ),
2064
- /* @__PURE__ */ jsx18(
2065
- Button3,
2066
- {
2067
- onClick: () => {
2068
- workspace.addLocale(newLocale);
2069
- setNewLocale("");
2070
- },
2071
- disabled: readOnly,
2072
- children: "Add"
2073
- }
2074
- )
2151
+ /* @__PURE__ */ jsx18(Button3, { onClick: localeSelectorProps.onAddLocale, disabled: readOnly, children: translate("builder.addLocale") })
2075
2152
  ] }),
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
- {
2153
+ workspace.error === void 0 ? null : /* @__PURE__ */ jsx18(Typography5, { color: "error", children: workspaceErrorMessage(workspace.error, translate) }),
2154
+ /* @__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) => {
2155
+ const rowProps = {
2080
2156
  slot,
2081
2157
  readOnly,
2082
2158
  onChange: (text) => workspace.setTranslation(slot, text),
2083
2159
  onTranslate: () => void workspace.translateSlot(slot)
2084
- },
2085
- slot.path
2086
- )) })
2160
+ };
2161
+ return /* @__PURE__ */ jsx18("span", { children: workspaceSlots?.renderSlotRow?.(rowProps) ?? /* @__PURE__ */ jsx18(SlotCard, { ...rowProps, translate, renderStatusBadge: workspaceSlots?.renderStatusBadge }) }, slot.path);
2162
+ }) })
2087
2163
  ] });
2088
2164
  }
2089
2165
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@form-engine-ts/mui",
3
- "version": "5.0.0",
3
+ "version": "5.1.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.0",
50
- "@form-engine-ts/react": "5.0.0"
49
+ "@form-engine-ts/core": "5.1.0",
50
+ "@form-engine-ts/react": "5.1.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.0",
62
- "@form-engine-ts/react": "5.0.0"
61
+ "@form-engine-ts/core": "5.1.0",
62
+ "@form-engine-ts/react": "5.1.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",