@bpmn-io/form-js-viewer 1.24.1 → 1.26.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
@@ -36,6 +36,46 @@ function _interopNamespaceDefault(e) {
36
36
 
37
37
  var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
38
38
 
39
+ /**
40
+ * @typedef { {
41
+ * [key: string]: string;
42
+ * } } TranslateReplacements
43
+ */
44
+
45
+ /**
46
+ * A simple translation stub to be used for multi-language support
47
+ * in diagrams. Can be easily replaced with a more sophisticated
48
+ * solution.
49
+ *
50
+ * @example
51
+ *
52
+ * ```javascript
53
+ * // use it inside any diagram component by injecting `translate`.
54
+ *
55
+ * function MyService(translate) {
56
+ * alert(translate('HELLO {you}', { you: 'You!' }));
57
+ * }
58
+ * ```
59
+ *
60
+ * @param {string} template to interpolate
61
+ * @param {TranslateReplacements} [replacements] a map with substitutes
62
+ *
63
+ * @return {string} the translated string
64
+ */
65
+ function translate(template, replacements) {
66
+ replacements = replacements || {};
67
+ return template.replace(/{([^}]+)}/g, function (_, key) {
68
+ return replacements[key] || '{' + key + '}';
69
+ });
70
+ }
71
+
72
+ /**
73
+ * @type { import('didi').ModuleDeclaration }
74
+ */
75
+ var TranslateModule = {
76
+ translate: ['value', translate]
77
+ };
78
+
39
79
  const getFlavouredFeelVariableNames = (feelString, feelFlavour = 'expression', options = {}) => {
40
80
  const {
41
81
  depth = 0,
@@ -1960,10 +2000,13 @@ function Label(props) {
1960
2000
  class: classNames('fjs-form-field-label', {
1961
2001
  'fjs-incollapsible-label': !collapseOnEmpty
1962
2002
  }, props['class']),
1963
- children: [props.children, evaluatedLabel, required && jsxRuntime.jsx("span", {
1964
- class: "fjs-asterix",
1965
- "aria-hidden": true,
1966
- children: "*"
2003
+ children: [props.children, jsxRuntime.jsxs("span", {
2004
+ class: "fjs-label-content",
2005
+ children: [evaluatedLabel, required && jsxRuntime.jsx("span", {
2006
+ class: "fjs-asterix",
2007
+ "aria-hidden": true,
2008
+ children: "*"
2009
+ })]
1967
2010
  })]
1968
2011
  });
1969
2012
  }
@@ -3064,6 +3107,7 @@ function Datetime(props) {
3064
3107
  const {
3065
3108
  formId
3066
3109
  } = hooks.useContext(FormContext);
3110
+ const translate = useService('translate');
3067
3111
 
3068
3112
  /** @type {import("preact").RefObject<HTMLDivElement>} */
3069
3113
  const dateTimeGroupRef = hooks.useRef();
@@ -3154,8 +3198,8 @@ function Datetime(props) {
3154
3198
  const allErrors = hooks.useMemo(() => {
3155
3199
  if (required || subtype !== DATETIME_SUBTYPES.DATETIME) return errors;
3156
3200
  const isOnlyOneFieldSet = isValidDate(dateTime.date) && !isValidTime(dateTime.time) || !isValidDate(dateTime.date) && isValidTime(dateTime.time);
3157
- return isOnlyOneFieldSet ? ['Date and time must both be entered.', ...errors] : errors;
3158
- }, [required, subtype, dateTime, errors]);
3201
+ return isOnlyOneFieldSet ? [translate('Date and time must both be entered.'), ...errors] : errors;
3202
+ }, [required, subtype, dateTime, errors, translate]);
3159
3203
  const setDate = hooks.useCallback(date => {
3160
3204
  setDateTimeUpdateRequest(prev => prev ? {
3161
3205
  ...prev,
@@ -3317,6 +3361,7 @@ function IFrame(props) {
3317
3361
  const evaluatedLabel = useSingleLineTemplateEvaluation(label, {
3318
3362
  debug: true
3319
3363
  });
3364
+ const translate = useService('translate');
3320
3365
  const [sandbox, allow] = useSecurityAttributesMap(security);
3321
3366
  const [iframeRefresh, setIframeRefresh] = hooks.useState(0);
3322
3367
 
@@ -3333,7 +3378,7 @@ function IFrame(props) {
3333
3378
  htmlFor: domId,
3334
3379
  label: evaluatedLabel
3335
3380
  }), !evaluatedUrl && jsxRuntime.jsx(IFramePlaceholder, {
3336
- text: "No content to show."
3381
+ text: translate('No content to show.')
3337
3382
  }), evaluatedUrl && safeUrl && jsxRuntime.jsx("iframe", {
3338
3383
  src: safeUrl,
3339
3384
  title: evaluatedLabel,
@@ -3343,7 +3388,7 @@ function IFrame(props) {
3343
3388
  sandbox: sandbox,
3344
3389
  allow
3345
3390
  }, 'iframe-' + iframeRefresh), evaluatedUrl && !safeUrl && jsxRuntime.jsx(IFramePlaceholder, {
3346
- text: "External content couldn't be loaded."
3391
+ text: translate("External content couldn't be loaded.")
3347
3392
  })]
3348
3393
  });
3349
3394
  }
@@ -3931,6 +3976,7 @@ function Numberfield(props) {
3931
3976
  const {
3932
3977
  required
3933
3978
  } = validate;
3979
+ const translate = useService('translate');
3934
3980
 
3935
3981
  /** @type {import("preact").RefObject<HTMLInputElement>} */
3936
3982
  const inputRef = hooks.useRef();
@@ -4104,7 +4150,7 @@ function Numberfield(props) {
4104
4150
  children: [jsxRuntime.jsx("button", {
4105
4151
  type: "button",
4106
4152
  class: "fjs-number-arrow-up",
4107
- "aria-label": "Increment",
4153
+ "aria-label": translate('Increment'),
4108
4154
  onClick: () => increment(),
4109
4155
  tabIndex: -1,
4110
4156
  children: jsxRuntime.jsx(SvgAngelUp, {})
@@ -4113,7 +4159,7 @@ function Numberfield(props) {
4113
4159
  }), jsxRuntime.jsx("button", {
4114
4160
  type: "button",
4115
4161
  class: "fjs-number-arrow-down",
4116
- "aria-label": "Decrement",
4162
+ "aria-label": translate('Decrement'),
4117
4163
  onClick: () => decrement(),
4118
4164
  tabIndex: -1,
4119
4165
  children: jsxRuntime.jsx(SvgAngelDown, {})
@@ -4796,6 +4842,7 @@ function Taglist(props) {
4796
4842
  /** @type {import("preact").RefObject<HTMLInputElement>} */
4797
4843
  const inputRef = hooks.useRef();
4798
4844
  const eventBus = useService('eventBus');
4845
+ const translate = useService('translate');
4799
4846
  const {
4800
4847
  loadState,
4801
4848
  options
@@ -4929,7 +4976,7 @@ function Taglist(props) {
4929
4976
  htmlFor: domId
4930
4977
  }), !disabled && !readonly && !!values.length && jsxRuntime.jsx(SkipLink, {
4931
4978
  className: "fjs-taglist-skip-link",
4932
- label: "Skip to search",
4979
+ label: translate('Skip to search'),
4933
4980
  onSkip: onSkipToSearch
4934
4981
  }), jsxRuntime.jsxs("div", {
4935
4982
  class: classNames('fjs-taglist', {
@@ -4950,7 +4997,7 @@ function Taglist(props) {
4950
4997
  children: getLabelCorrelation(entry)
4951
4998
  }), !disabled && !readonly && jsxRuntime.jsx("button", {
4952
4999
  type: "button",
4953
- title: "Remove tag",
5000
+ title: translate('Remove tag'),
4954
5001
  class: "fjs-taglist-tag-remove",
4955
5002
  onFocus: onElementFocus,
4956
5003
  onBlur: onElementBlur,
@@ -4968,7 +5015,7 @@ function Taglist(props) {
4968
5015
  onChange: onInputChange,
4969
5016
  type: "text",
4970
5017
  value: filter,
4971
- placeholder: disabled || readonly ? undefined : 'Search',
5018
+ placeholder: disabled || readonly ? undefined : translate('Search'),
4972
5019
  autoComplete: "off",
4973
5020
  onKeyDown: onInputKeyDown,
4974
5021
  onMouseDown: () => setIsEscapeClose(false),
@@ -4984,7 +5031,7 @@ function Taglist(props) {
4984
5031
  values: filteredOptions,
4985
5032
  getLabel: option => option.label,
4986
5033
  onValueSelected: option => selectValue(option.value),
4987
- emptyListMessage: hasOptionsLeft ? 'No results' : 'All values selected',
5034
+ emptyListMessage: hasOptionsLeft ? translate('No results') : translate('All values selected'),
4988
5035
  listenerElement: inputRef.current
4989
5036
  })
4990
5037
  }), jsxRuntime.jsx(Description, {
@@ -5587,6 +5634,8 @@ var SvgCaretRight = function SvgCaretRight(props) {
5587
5634
  const type$2 = 'table';
5588
5635
 
5589
5636
  /**
5637
+ * @typedef { import('diagram-js/lib/i18n/translate/translate').default } Translate
5638
+ *
5590
5639
  * @typedef {('asc'|'desc')} Direction
5591
5640
  *
5592
5641
  * @typedef Sorting
@@ -5636,6 +5685,7 @@ function Table(props) {
5636
5685
  const chunkedData = minDash.isNumber(rowCount) ? chunk(sortedData, rowCount) : [sortedData];
5637
5686
  const [currentPage, setCurrentPage] = hooks.useState(0);
5638
5687
  const currentChunk = chunkedData[currentPage] || [];
5688
+ const translate = useService('translate');
5639
5689
  hooks.useEffect(() => {
5640
5690
  setCurrentPage(0);
5641
5691
  }, [rowCount, sortBy]);
@@ -5667,7 +5717,7 @@ function Table(props) {
5667
5717
  class: classNames('fjs-table-middle-container', {
5668
5718
  'fjs-table-empty': evaluatedColumns.length === 0
5669
5719
  }),
5670
- children: [evaluatedColumns.length === 0 ? 'Nothing to show.' : jsxRuntime.jsx("div", {
5720
+ children: [evaluatedColumns.length === 0 ? translate('Nothing to show.') : jsxRuntime.jsx("div", {
5671
5721
  class: "fjs-table-inner-container",
5672
5722
  children: jsxRuntime.jsxs("table", {
5673
5723
  class: "fjs-table",
@@ -5692,7 +5742,7 @@ function Table(props) {
5692
5742
  toggleSortBy(key);
5693
5743
  }
5694
5744
  },
5695
- "aria-label": getHeaderAriaLabel(sortBy, key, displayLabel),
5745
+ "aria-label": getHeaderAriaLabel(sortBy, key, displayLabel, translate),
5696
5746
  children: jsxRuntime.jsxs("span", {
5697
5747
  class: "fjs-table-th-label",
5698
5748
  children: [displayLabel, sortBy !== null && sortBy.key === key ? jsxRuntime.jsx(jsxRuntime.Fragment, {
@@ -5713,7 +5763,7 @@ function Table(props) {
5713
5763
  children: jsxRuntime.jsx("td", {
5714
5764
  class: "fjs-table-td",
5715
5765
  colSpan: evaluatedColumns.length,
5716
- children: "Nothing to show."
5766
+ children: translate('Nothing to show.')
5717
5767
  })
5718
5768
  })
5719
5769
  }) : jsxRuntime.jsx("tbody", {
@@ -5729,9 +5779,13 @@ function Table(props) {
5729
5779
  })
5730
5780
  }), minDash.isNumber(rowCount) && chunkedData.length > 1 && evaluatedColumns.length > 0 ? jsxRuntime.jsxs("nav", {
5731
5781
  class: "fjs-table-nav",
5732
- children: [jsxRuntime.jsxs("span", {
5782
+ children: [jsxRuntime.jsx("span", {
5733
5783
  class: "fjs-table-nav-label",
5734
- children: [currentPage + 1, " of ", chunkedData.length]
5784
+ children: translate('{page} of {total}', {
5785
+ // stringify: see Validator.js
5786
+ page: (currentPage + 1).toString(),
5787
+ total: chunkedData.length.toString()
5788
+ })
5735
5789
  }), jsxRuntime.jsx("button", {
5736
5790
  type: "button",
5737
5791
  class: "fjs-table-nav-button",
@@ -5739,7 +5793,7 @@ function Table(props) {
5739
5793
  setCurrentPage(page => Math.max(page - 1, 0));
5740
5794
  },
5741
5795
  disabled: currentPage === 0,
5742
- "aria-label": "Previous page",
5796
+ "aria-label": translate('Previous page'),
5743
5797
  children: jsxRuntime.jsx(SvgCaretLeft, {})
5744
5798
  }), jsxRuntime.jsx("button", {
5745
5799
  type: "button",
@@ -5748,7 +5802,7 @@ function Table(props) {
5748
5802
  setCurrentPage(page => Math.min(page + 1, chunkedData.length - 1));
5749
5803
  },
5750
5804
  disabled: currentPage >= chunkedData.length - 1,
5751
- "aria-label": "Next page",
5805
+ "aria-label": translate('Next page'),
5752
5806
  children: jsxRuntime.jsx(SvgCaretRight, {})
5753
5807
  })]
5754
5808
  }) : null]
@@ -5898,15 +5952,20 @@ function sortByColumn(array, key, direction) {
5898
5952
  * @param {null|Sorting} sortBy
5899
5953
  * @param {string} key
5900
5954
  * @param {string} label
5955
+ * @param {Translate} translate
5901
5956
  */
5902
- function getHeaderAriaLabel(sortBy, key, label) {
5957
+ function getHeaderAriaLabel(sortBy, key, label, translate) {
5903
5958
  if (sortBy === null || sortBy.key !== key) {
5904
- return `Click to sort by ${label} descending`;
5959
+ return translate('Click to sort by {value} descending', {
5960
+ value: label
5961
+ });
5905
5962
  }
5906
5963
  if (sortBy.direction === 'asc') {
5907
- return 'Click to remove sorting';
5964
+ return translate('Click to remove sorting');
5908
5965
  }
5909
- return `Click to sort by ${label} ascending`;
5966
+ return translate('Click to sort by {value} ascending', {
5967
+ value: label
5968
+ });
5910
5969
  }
5911
5970
 
5912
5971
  /**
@@ -5977,6 +6036,7 @@ function FilePicker(props) {
5977
6036
  const errorMessageId = `${domId}-error-message`;
5978
6037
  /** @type {File[]} */
5979
6038
  const selectedFiles = fileRegistry === null ? EMPTY_ARRAY$1 : fileRegistry.getFiles(filesKey);
6039
+ const translate = useService('translate');
5980
6040
  hooks.useEffect(() => {
5981
6041
  if (filesKey && fileRegistry !== null && !fileRegistry.hasKey(filesKey)) {
5982
6042
  onChange({
@@ -6047,7 +6107,7 @@ function FilePicker(props) {
6047
6107
  children: "Browse"
6048
6108
  }), jsxRuntime.jsx("span", {
6049
6109
  className: "fjs-form-field-label",
6050
- children: getSelectedFilesLabel(selectedFiles)
6110
+ children: getSelectedFilesLabel(selectedFiles, translate)
6051
6111
  })]
6052
6112
  }), jsxRuntime.jsx(Errors, {
6053
6113
  id: errorMessageId,
@@ -6070,16 +6130,19 @@ FilePicker.config = {
6070
6130
 
6071
6131
  /**
6072
6132
  * @param {File[]} files
6133
+ * @param {Translate} translate
6073
6134
  * @returns {string}
6074
6135
  */
6075
- function getSelectedFilesLabel(files) {
6136
+ function getSelectedFilesLabel(files, translate) {
6076
6137
  if (files.length === 0) {
6077
- return 'No files selected';
6138
+ return translate('No files selected');
6078
6139
  }
6079
6140
  if (files.length === 1) {
6080
6141
  return files[0].name;
6081
6142
  }
6082
- return `${files.length} files selected`;
6143
+ return translate('{value} files selected', {
6144
+ value: files.length.toString()
6145
+ });
6083
6146
  }
6084
6147
 
6085
6148
  var _path$4;
@@ -6102,6 +6165,10 @@ var SvgDownload = function SvgDownload(props) {
6102
6165
 
6103
6166
  const type = 'documentPreview';
6104
6167
 
6168
+ /**
6169
+ * @typedef { import('diagram-js/lib/i18n/translate/translate').default } Translate
6170
+ */
6171
+
6105
6172
  /**
6106
6173
  * @typedef DocumentEndpointBuilder
6107
6174
  * @property {(document: DocumentMetadata) => string} [buildUrl]
@@ -6147,6 +6214,7 @@ function DocumentPreview(props) {
6147
6214
  const evaluatedLabel = useSingleLineTemplateEvaluation(label, {
6148
6215
  debug: true
6149
6216
  });
6217
+ const translate = useService('translate');
6150
6218
  return jsxRuntime.jsxs("div", {
6151
6219
  class: formFieldClasses(type),
6152
6220
  children: [jsxRuntime.jsx(Label, {
@@ -6172,7 +6240,7 @@ function DocumentPreview(props) {
6172
6240
  id: errorMessageId,
6173
6241
  errors: getErrors({
6174
6242
  dataSource
6175
- })
6243
+ }, translate)
6176
6244
  })]
6177
6245
  });
6178
6246
  }
@@ -6194,15 +6262,16 @@ DocumentPreview.config = {
6194
6262
  * @property {string|undefined} dataSource
6195
6263
  *
6196
6264
  * @param {GetErrorOptions} options
6265
+ * @param {Translate} translate
6197
6266
  * @returns {string[]}
6198
6267
  */
6199
- function getErrors(options) {
6268
+ function getErrors(options, translate) {
6200
6269
  const {
6201
6270
  dataSource
6202
6271
  } = options;
6203
6272
  let errors = [];
6204
6273
  if (!minDash.isString(dataSource) || dataSource.length < 1) {
6205
- errors.push('Document reference is not defined.');
6274
+ errors.push(translate('Document reference is not defined.'));
6206
6275
  }
6207
6276
  return errors;
6208
6277
  }
@@ -6255,6 +6324,7 @@ function PdfRenderer(props) {
6255
6324
  /** @type {ReturnType<typeof import("preact/hooks").useState<null | string>>} */
6256
6325
  const [pdfObjectUrl, setPdfObjectUrl] = hooks.useState(null);
6257
6326
  const [hasError, setHasError] = hooks.useState(false);
6327
+ const translate = useService('translate');
6258
6328
  hooks.useEffect(() => {
6259
6329
  /** @type {null | string} */
6260
6330
  let objectUrl = null;
@@ -6288,7 +6358,7 @@ function PdfRenderer(props) {
6288
6358
  class: `fjs-${type}-pdf-viewer`
6289
6359
  }) : null, hasError ? jsxRuntime.jsx(Errors, {
6290
6360
  id: errorMessageId,
6291
- errors: ['Unable to download document']
6361
+ errors: [translate('Unable to download document')]
6292
6362
  }) : null]
6293
6363
  });
6294
6364
  }
@@ -6368,9 +6438,10 @@ function DocumentRenderer(props) {
6368
6438
  const isInViewport = useInViewport(ref);
6369
6439
  const onError = hooks.useCallback(() => setHasError(true), []);
6370
6440
  const requestInit = hooks.useMemo(() => getDocumentRequestInit(documentEndpointBuilder, documentMetadata), [documentEndpointBuilder, documentMetadata]);
6441
+ const translate = useService('translate');
6371
6442
  const singleDocumentContainerClassName = `fjs-${type}-single-document-container`;
6372
6443
  const errorMessageId = `${domId}-error-message`;
6373
- const errorMessage = 'Unable to download document';
6444
+ const errorMessage = translate('Unable to download document');
6374
6445
  const isContentTypePresent = typeof metadata.contentType === 'string';
6375
6446
  if (isContentTypePresent && metadata.contentType.toLowerCase().startsWith('image/') && isInViewport) {
6376
6447
  return jsxRuntime.jsxs("div", {
@@ -6448,6 +6519,7 @@ function DownloadButton(props) {
6448
6519
  onDownloadError,
6449
6520
  requestInit
6450
6521
  } = props;
6522
+ const translate = useService('translate');
6451
6523
  const handleDownload = async () => {
6452
6524
  try {
6453
6525
  const response = await fetch(endpoint, requestInit);
@@ -6470,7 +6542,9 @@ function DownloadButton(props) {
6470
6542
  type: "button",
6471
6543
  onClick: handleDownload,
6472
6544
  class: classNames(`fjs-${type}-download-button`),
6473
- "aria-label": `Download ${fileName}`,
6545
+ "aria-label": translate('Download {fileName}', {
6546
+ fileName
6547
+ }),
6474
6548
  children: jsxRuntime.jsx(SvgDownload, {})
6475
6549
  });
6476
6550
  }
@@ -7816,6 +7890,7 @@ class RepeatRenderManager {
7816
7890
  const {
7817
7891
  data
7818
7892
  } = this._form._getState();
7893
+ const translate = useService('translate');
7819
7894
  const dataPath = this._pathRegistry.getValuePath(repeaterField, {
7820
7895
  indexes
7821
7896
  });
@@ -7872,16 +7947,18 @@ class RepeatRenderManager {
7872
7947
  ref: addButtonRef,
7873
7948
  onClick: onAddItem,
7874
7949
  children: jsxRuntime.jsxs(jsxRuntime.Fragment, {
7875
- children: [jsxRuntime.jsx(SvgAdd, {}), " ", 'Add new']
7950
+ children: [jsxRuntime.jsx(SvgAdd, {}), " ", translate('Add new')]
7876
7951
  })
7877
7952
  }) : null, collapseEnabled ? jsxRuntime.jsx("button", {
7878
7953
  type: "button",
7879
7954
  class: "fjs-repeat-render-collapse",
7880
7955
  onClick: toggle,
7881
7956
  children: isCollapsed ? jsxRuntime.jsxs(jsxRuntime.Fragment, {
7882
- children: [jsxRuntime.jsx(SvgExpand, {}), " ", `Expand all (${values.length - 1})`]
7957
+ children: [jsxRuntime.jsx(SvgExpand, {}), " ", translate('Expand all ({value})', {
7958
+ value: values.length - 1
7959
+ })]
7883
7960
  }) : jsxRuntime.jsxs(jsxRuntime.Fragment, {
7884
- children: [jsxRuntime.jsx(SvgCollapse, {}), " ", 'Collapse']
7961
+ children: [jsxRuntime.jsx(SvgCollapse, {}), " ", translate('Collapse')]
7885
7962
  })
7886
7963
  }) : null]
7887
7964
  });
@@ -8514,11 +8591,12 @@ const EMAIL_PATTERN = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-
8514
8591
  const PHONE_PATTERN = /(\+|00)(297|93|244|1264|358|355|376|971|54|374|1684|1268|61|43|994|257|32|229|226|880|359|973|1242|387|590|375|501|1441|591|55|1246|673|975|267|236|1|61|41|56|86|225|237|243|242|682|57|269|238|506|53|5999|61|1345|357|420|49|253|1767|45|1809|1829|1849|213|593|20|291|212|34|372|251|358|679|500|33|298|691|241|44|995|44|233|350|224|590|220|245|240|30|1473|299|502|594|1671|592|852|504|385|509|36|62|44|91|246|353|98|964|354|972|39|1876|44|962|81|76|77|254|996|855|686|1869|82|383|965|856|961|231|218|1758|423|94|266|370|352|371|853|590|212|377|373|261|960|52|692|389|223|356|95|382|976|1670|258|222|1664|596|230|265|60|262|264|687|227|672|234|505|683|31|47|977|674|64|968|92|507|64|51|63|680|675|48|1787|1939|850|351|595|970|689|974|262|40|7|250|966|249|221|65|500|4779|677|232|503|378|252|508|381|211|239|597|421|386|46|268|1721|248|963|1649|235|228|66|992|690|993|670|676|1868|216|90|688|886|255|256|380|598|1|998|3906698|379|1784|58|1284|1340|84|678|681|685|967|27|260|263)(9[976]\d|8[987530]\d|6[987]\d|5[90]\d|42\d|3[875]\d|2[98654321]\d|9[8543210]|8[6421]|6[6543210]|5[87654321]|4[987654310]|3[9643210]|2[70]|7|1)\d{4,20}$/;
8515
8592
  const VALIDATE_FEEL_PROPERTIES = ['min', 'max', 'minLength', 'maxLength'];
8516
8593
  class Validator {
8517
- constructor(expressionLanguage, conditionChecker, form, formFieldRegistry) {
8594
+ constructor(expressionLanguage, conditionChecker, form, formFieldRegistry, translate) {
8518
8595
  this._expressionLanguage = expressionLanguage;
8519
8596
  this._conditionChecker = conditionChecker;
8520
8597
  this._form = form;
8521
8598
  this._formFieldRegistry = formFieldRegistry;
8599
+ this._translate = translate;
8522
8600
  }
8523
8601
 
8524
8602
  /**
@@ -8533,13 +8611,13 @@ class Validator {
8533
8611
  } = field;
8534
8612
  let errors = [];
8535
8613
  if (type === 'number') {
8536
- errors = [...errors, ...runNumberValidation(field, value)];
8614
+ errors = [...errors, ...runNumberValidation(field, value, this._translate)];
8537
8615
  }
8538
8616
  if (!validate) {
8539
8617
  return errors;
8540
8618
  }
8541
8619
  const evaluatedValidation = oldEvaluateFEELValues(validate, this._expressionLanguage, this._conditionChecker, this._form);
8542
- errors = [...errors, ...runPresetValidation(field, evaluatedValidation, value)];
8620
+ errors = [...errors, ...runPresetValidation(field, evaluatedValidation, value, this._translate)];
8543
8621
  return errors;
8544
8622
  }
8545
8623
 
@@ -8566,31 +8644,39 @@ class Validator {
8566
8644
  } = field;
8567
8645
  let errors = [];
8568
8646
  if (type === 'number') {
8569
- errors = [...errors, ...runNumberValidation(field, value)];
8647
+ errors = [...errors, ...runNumberValidation(field, value, this._translate)];
8570
8648
  }
8571
8649
  if (!validate) {
8572
8650
  return errors;
8573
8651
  }
8574
8652
  const evaluatedValidation = evaluateFEELValues(validate, this._expressionLanguage, expressionContextInfo);
8575
- errors = [...errors, ...runPresetValidation(field, evaluatedValidation, value)];
8653
+ errors = [...errors, ...runPresetValidation(field, evaluatedValidation, value, this._translate)];
8576
8654
  return errors;
8577
8655
  }
8578
8656
  }
8579
- Validator.$inject = ['expressionLanguage', 'conditionChecker', 'form', 'formFieldRegistry'];
8657
+ Validator.$inject = ['expressionLanguage', 'conditionChecker', 'form', 'formFieldRegistry', 'translate'];
8580
8658
 
8581
8659
  // helpers //////////
8582
8660
 
8583
- function runNumberValidation(field, value) {
8661
+ function runNumberValidation(field, value, translate) {
8584
8662
  const {
8585
8663
  decimalDigits,
8586
8664
  increment
8587
8665
  } = field;
8588
8666
  const errors = [];
8589
8667
  if (value === 'NaN') {
8590
- errors.push('Value is not a number.');
8668
+ errors.push(translate('Value is not a number.'));
8591
8669
  } else if (value) {
8592
8670
  if (decimalDigits >= 0 && countDecimals(value) > decimalDigits) {
8593
- errors.push('Value is expected to ' + (decimalDigits === 0 ? 'be an integer' : `have at most ${decimalDigits} decimal digit${decimalDigits > 1 ? 's' : ''}`) + '.');
8671
+ if (decimalDigits === 0) {
8672
+ errors.push(translate('Value is expected to be an integer.'));
8673
+ } else if (decimalDigits === 1) {
8674
+ errors.push(translate('Value is expected to have at most 1 decimal digit.'));
8675
+ } else {
8676
+ errors.push(translate('Value is expected to have at most {value} decimal digits.', {
8677
+ value: decimalDigits.toString()
8678
+ }));
8679
+ }
8594
8680
  }
8595
8681
  if (increment) {
8596
8682
  const bigValue = Big(value);
@@ -8599,54 +8685,69 @@ function runNumberValidation(field, value) {
8599
8685
  if (offset.cmp(0) !== 0) {
8600
8686
  const previousValue = bigValue.minus(offset);
8601
8687
  const nextValue = previousValue.plus(bigIncrement);
8602
- errors.push(`Please select a valid value, the two nearest valid values are ${previousValue} and ${nextValue}.`);
8688
+ errors.push(translate('Please select a valid value, the two nearest valid values are {previousValue} and {nextValue}.', {
8689
+ previousValue: previousValue,
8690
+ nextValue: nextValue
8691
+ }));
8603
8692
  }
8604
8693
  }
8605
8694
  }
8606
8695
  return errors;
8607
8696
  }
8608
- function runPresetValidation(field, validation, value) {
8697
+ function runPresetValidation(field, validation, value, translate) {
8609
8698
  const errors = [];
8610
8699
  if (validation.pattern && value && !new RegExp(validation.pattern).test(value)) {
8611
- errors.push(validation.patternErrorMessage || `Field must match pattern ${validation.pattern}.`);
8700
+ errors.push(validation.patternErrorMessage || translate('Field must match pattern {value}.', {
8701
+ value: validation.pattern
8702
+ }));
8612
8703
  }
8613
8704
  if (validation.required) {
8614
8705
  const isUncheckedCheckbox = field.type === 'checkbox' && value === false;
8615
8706
  const isUnsetValue = minDash.isNil(value) || value === '';
8616
8707
  const isEmptyMultiselect = Array.isArray(value) && value.length === 0;
8617
8708
  if (isUncheckedCheckbox || isUnsetValue || isEmptyMultiselect) {
8618
- errors.push('Field is required.');
8709
+ errors.push(translate('Field is required.'));
8619
8710
  }
8620
8711
  }
8712
+
8713
+ // stringify: `translate` falls back to the raw placeholder for a falsy replacement, so 0 must not be passed as a number
8621
8714
  if ('min' in validation && (value || value === 0)) {
8622
8715
  try {
8623
8716
  if (Big(value).lt(Big(validation.min))) {
8624
- errors.push(`Field must have minimum value of ${validation.min}.`);
8717
+ errors.push(translate('Field must have minimum value of {value}.', {
8718
+ value: validation.min.toString()
8719
+ }));
8625
8720
  }
8626
8721
  } catch {
8627
- errors.push('Min validation value is not a valid number.');
8722
+ errors.push(translate('Min validation value is not a valid number.'));
8628
8723
  }
8629
8724
  }
8630
8725
  if ('max' in validation && (value || value === 0)) {
8631
8726
  try {
8632
8727
  if (Big(value).gt(Big(validation.max))) {
8633
- errors.push(`Field must have maximum value of ${validation.max}.`);
8728
+ errors.push(translate('Field must have maximum value of {value}.', {
8729
+ value: validation.max.toString()
8730
+ }));
8634
8731
  }
8635
8732
  } catch {
8636
- errors.push('Max validation value is not a valid number.');
8733
+ errors.push(translate('Max validation value is not a valid number.'));
8637
8734
  }
8638
8735
  }
8639
8736
  if ('minLength' in validation && value && value.trim().length < validation.minLength) {
8640
- errors.push(`Field must have minimum length of ${validation.minLength}.`);
8737
+ errors.push(translate('Field must have minimum length of {value}.', {
8738
+ value: validation.minLength.toString()
8739
+ }));
8641
8740
  }
8642
8741
  if ('maxLength' in validation && value && value.trim().length > validation.maxLength) {
8643
- errors.push(`Field must have maximum length of ${validation.maxLength}.`);
8742
+ errors.push(translate('Field must have maximum length of {value}.', {
8743
+ value: validation.maxLength.toString()
8744
+ }));
8644
8745
  }
8645
8746
  if ('validationType' in validation && value && validation.validationType === 'phone' && !PHONE_PATTERN.test(value)) {
8646
- errors.push('Field must be a valid international phone number. (e.g. +4930664040900)');
8747
+ errors.push(translate('Field must be a valid international phone number. (e.g. +4930664040900)'));
8647
8748
  }
8648
8749
  if ('validationType' in validation && value && validation.validationType === 'email' && !EMAIL_PATTERN.test(value)) {
8649
- errors.push('Field must be a valid email.');
8750
+ errors.push(translate('Field must be a valid email.'));
8650
8751
  }
8651
8752
  return errors;
8652
8753
  }
@@ -8804,17 +8905,24 @@ class Importer {
8804
8905
  }
8805
8906
  Importer.$inject = ['formFieldRegistry', 'pathRegistry', 'fieldFactory', 'formLayouter'];
8806
8907
 
8908
+ /**
8909
+ * @typedef { import('diagram-js/lib/i18n/translate/translate').default } Translate
8910
+ */
8911
+
8807
8912
  class FieldFactory {
8808
8913
  /**
8809
8914
  * @constructor
8810
8915
  *
8811
- * @param formFieldRegistry
8812
- * @param formFields
8916
+ * @param formFieldRegistry
8917
+ * @param pathRegistry
8918
+ * @param formFields
8919
+ * @param {Translate} translate
8813
8920
  */
8814
- constructor(formFieldRegistry, pathRegistry, formFields) {
8921
+ constructor(formFieldRegistry, pathRegistry, formFields, translate) {
8815
8922
  this._formFieldRegistry = formFieldRegistry;
8816
8923
  this._pathRegistry = pathRegistry;
8817
8924
  this._formFields = formFields;
8925
+ this._translate = translate;
8818
8926
  }
8819
8927
  create(attrs, isNewField = true) {
8820
8928
  const {
@@ -8847,13 +8955,17 @@ class FieldFactory {
8847
8955
  isClosed: true,
8848
8956
  knownAncestorIds
8849
8957
  })) {
8850
- throw new Error(`binding path '${[...parentPath, key].join('.')}' is already claimed`);
8958
+ throw new Error(this._translate("binding path '{value}' is already claimed", {
8959
+ value: [...parentPath, key].join('.')
8960
+ }));
8851
8961
  }
8852
8962
  if (config.pathed && path && !this._pathRegistry.canClaimPath([...parentPath, ...path.split('.')], {
8853
8963
  isRepeatable: config.repeatable,
8854
8964
  knownAncestorIds
8855
8965
  })) {
8856
- throw new Error(`binding path '${[...parentPath, ...path.split('.')].join('.')}' is already claimed`);
8966
+ throw new Error(this._translate("binding path '{value}' is already claimed", {
8967
+ value: [...parentPath, ...path.split('.')].join('.')
8968
+ }));
8857
8969
  }
8858
8970
  const field = config.create({
8859
8971
  ...(config.label ? {
@@ -8914,7 +9026,7 @@ class FieldFactory {
8914
9026
  return `${field.type}_${random}`;
8915
9027
  }
8916
9028
  }
8917
- FieldFactory.$inject = ['formFieldRegistry', 'pathRegistry', 'formFields'];
9029
+ FieldFactory.$inject = ['formFieldRegistry', 'pathRegistry', 'formFields', 'translate'];
8918
9030
 
8919
9031
  /**
8920
9032
  * The PathRegistry class manages a hierarchical structure of paths associated with form fields.
@@ -9924,7 +10036,7 @@ class Form {
9924
10036
  config: ['value', enrichedConfig]
9925
10037
  }, {
9926
10038
  form: ['value', this]
9927
- }, CoreModule, ...modules, ...additionalModules]);
10039
+ }, TranslateModule, CoreModule, ...modules, ...additionalModules]);
9928
10040
  }
9929
10041
 
9930
10042
  /**