@bpmn-io/form-js-viewer 1.25.0 → 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,
@@ -3067,6 +3107,7 @@ function Datetime(props) {
3067
3107
  const {
3068
3108
  formId
3069
3109
  } = hooks.useContext(FormContext);
3110
+ const translate = useService('translate');
3070
3111
 
3071
3112
  /** @type {import("preact").RefObject<HTMLDivElement>} */
3072
3113
  const dateTimeGroupRef = hooks.useRef();
@@ -3157,8 +3198,8 @@ function Datetime(props) {
3157
3198
  const allErrors = hooks.useMemo(() => {
3158
3199
  if (required || subtype !== DATETIME_SUBTYPES.DATETIME) return errors;
3159
3200
  const isOnlyOneFieldSet = isValidDate(dateTime.date) && !isValidTime(dateTime.time) || !isValidDate(dateTime.date) && isValidTime(dateTime.time);
3160
- return isOnlyOneFieldSet ? ['Date and time must both be entered.', ...errors] : errors;
3161
- }, [required, subtype, dateTime, errors]);
3201
+ return isOnlyOneFieldSet ? [translate('Date and time must both be entered.'), ...errors] : errors;
3202
+ }, [required, subtype, dateTime, errors, translate]);
3162
3203
  const setDate = hooks.useCallback(date => {
3163
3204
  setDateTimeUpdateRequest(prev => prev ? {
3164
3205
  ...prev,
@@ -3320,6 +3361,7 @@ function IFrame(props) {
3320
3361
  const evaluatedLabel = useSingleLineTemplateEvaluation(label, {
3321
3362
  debug: true
3322
3363
  });
3364
+ const translate = useService('translate');
3323
3365
  const [sandbox, allow] = useSecurityAttributesMap(security);
3324
3366
  const [iframeRefresh, setIframeRefresh] = hooks.useState(0);
3325
3367
 
@@ -3336,7 +3378,7 @@ function IFrame(props) {
3336
3378
  htmlFor: domId,
3337
3379
  label: evaluatedLabel
3338
3380
  }), !evaluatedUrl && jsxRuntime.jsx(IFramePlaceholder, {
3339
- text: "No content to show."
3381
+ text: translate('No content to show.')
3340
3382
  }), evaluatedUrl && safeUrl && jsxRuntime.jsx("iframe", {
3341
3383
  src: safeUrl,
3342
3384
  title: evaluatedLabel,
@@ -3346,7 +3388,7 @@ function IFrame(props) {
3346
3388
  sandbox: sandbox,
3347
3389
  allow
3348
3390
  }, 'iframe-' + iframeRefresh), evaluatedUrl && !safeUrl && jsxRuntime.jsx(IFramePlaceholder, {
3349
- text: "External content couldn't be loaded."
3391
+ text: translate("External content couldn't be loaded.")
3350
3392
  })]
3351
3393
  });
3352
3394
  }
@@ -3934,6 +3976,7 @@ function Numberfield(props) {
3934
3976
  const {
3935
3977
  required
3936
3978
  } = validate;
3979
+ const translate = useService('translate');
3937
3980
 
3938
3981
  /** @type {import("preact").RefObject<HTMLInputElement>} */
3939
3982
  const inputRef = hooks.useRef();
@@ -4107,7 +4150,7 @@ function Numberfield(props) {
4107
4150
  children: [jsxRuntime.jsx("button", {
4108
4151
  type: "button",
4109
4152
  class: "fjs-number-arrow-up",
4110
- "aria-label": "Increment",
4153
+ "aria-label": translate('Increment'),
4111
4154
  onClick: () => increment(),
4112
4155
  tabIndex: -1,
4113
4156
  children: jsxRuntime.jsx(SvgAngelUp, {})
@@ -4116,7 +4159,7 @@ function Numberfield(props) {
4116
4159
  }), jsxRuntime.jsx("button", {
4117
4160
  type: "button",
4118
4161
  class: "fjs-number-arrow-down",
4119
- "aria-label": "Decrement",
4162
+ "aria-label": translate('Decrement'),
4120
4163
  onClick: () => decrement(),
4121
4164
  tabIndex: -1,
4122
4165
  children: jsxRuntime.jsx(SvgAngelDown, {})
@@ -4799,6 +4842,7 @@ function Taglist(props) {
4799
4842
  /** @type {import("preact").RefObject<HTMLInputElement>} */
4800
4843
  const inputRef = hooks.useRef();
4801
4844
  const eventBus = useService('eventBus');
4845
+ const translate = useService('translate');
4802
4846
  const {
4803
4847
  loadState,
4804
4848
  options
@@ -4932,7 +4976,7 @@ function Taglist(props) {
4932
4976
  htmlFor: domId
4933
4977
  }), !disabled && !readonly && !!values.length && jsxRuntime.jsx(SkipLink, {
4934
4978
  className: "fjs-taglist-skip-link",
4935
- label: "Skip to search",
4979
+ label: translate('Skip to search'),
4936
4980
  onSkip: onSkipToSearch
4937
4981
  }), jsxRuntime.jsxs("div", {
4938
4982
  class: classNames('fjs-taglist', {
@@ -4953,7 +4997,7 @@ function Taglist(props) {
4953
4997
  children: getLabelCorrelation(entry)
4954
4998
  }), !disabled && !readonly && jsxRuntime.jsx("button", {
4955
4999
  type: "button",
4956
- title: "Remove tag",
5000
+ title: translate('Remove tag'),
4957
5001
  class: "fjs-taglist-tag-remove",
4958
5002
  onFocus: onElementFocus,
4959
5003
  onBlur: onElementBlur,
@@ -4971,7 +5015,7 @@ function Taglist(props) {
4971
5015
  onChange: onInputChange,
4972
5016
  type: "text",
4973
5017
  value: filter,
4974
- placeholder: disabled || readonly ? undefined : 'Search',
5018
+ placeholder: disabled || readonly ? undefined : translate('Search'),
4975
5019
  autoComplete: "off",
4976
5020
  onKeyDown: onInputKeyDown,
4977
5021
  onMouseDown: () => setIsEscapeClose(false),
@@ -4987,7 +5031,7 @@ function Taglist(props) {
4987
5031
  values: filteredOptions,
4988
5032
  getLabel: option => option.label,
4989
5033
  onValueSelected: option => selectValue(option.value),
4990
- emptyListMessage: hasOptionsLeft ? 'No results' : 'All values selected',
5034
+ emptyListMessage: hasOptionsLeft ? translate('No results') : translate('All values selected'),
4991
5035
  listenerElement: inputRef.current
4992
5036
  })
4993
5037
  }), jsxRuntime.jsx(Description, {
@@ -5590,6 +5634,8 @@ var SvgCaretRight = function SvgCaretRight(props) {
5590
5634
  const type$2 = 'table';
5591
5635
 
5592
5636
  /**
5637
+ * @typedef { import('diagram-js/lib/i18n/translate/translate').default } Translate
5638
+ *
5593
5639
  * @typedef {('asc'|'desc')} Direction
5594
5640
  *
5595
5641
  * @typedef Sorting
@@ -5639,6 +5685,7 @@ function Table(props) {
5639
5685
  const chunkedData = minDash.isNumber(rowCount) ? chunk(sortedData, rowCount) : [sortedData];
5640
5686
  const [currentPage, setCurrentPage] = hooks.useState(0);
5641
5687
  const currentChunk = chunkedData[currentPage] || [];
5688
+ const translate = useService('translate');
5642
5689
  hooks.useEffect(() => {
5643
5690
  setCurrentPage(0);
5644
5691
  }, [rowCount, sortBy]);
@@ -5670,7 +5717,7 @@ function Table(props) {
5670
5717
  class: classNames('fjs-table-middle-container', {
5671
5718
  'fjs-table-empty': evaluatedColumns.length === 0
5672
5719
  }),
5673
- children: [evaluatedColumns.length === 0 ? 'Nothing to show.' : jsxRuntime.jsx("div", {
5720
+ children: [evaluatedColumns.length === 0 ? translate('Nothing to show.') : jsxRuntime.jsx("div", {
5674
5721
  class: "fjs-table-inner-container",
5675
5722
  children: jsxRuntime.jsxs("table", {
5676
5723
  class: "fjs-table",
@@ -5695,7 +5742,7 @@ function Table(props) {
5695
5742
  toggleSortBy(key);
5696
5743
  }
5697
5744
  },
5698
- "aria-label": getHeaderAriaLabel(sortBy, key, displayLabel),
5745
+ "aria-label": getHeaderAriaLabel(sortBy, key, displayLabel, translate),
5699
5746
  children: jsxRuntime.jsxs("span", {
5700
5747
  class: "fjs-table-th-label",
5701
5748
  children: [displayLabel, sortBy !== null && sortBy.key === key ? jsxRuntime.jsx(jsxRuntime.Fragment, {
@@ -5716,7 +5763,7 @@ function Table(props) {
5716
5763
  children: jsxRuntime.jsx("td", {
5717
5764
  class: "fjs-table-td",
5718
5765
  colSpan: evaluatedColumns.length,
5719
- children: "Nothing to show."
5766
+ children: translate('Nothing to show.')
5720
5767
  })
5721
5768
  })
5722
5769
  }) : jsxRuntime.jsx("tbody", {
@@ -5732,9 +5779,13 @@ function Table(props) {
5732
5779
  })
5733
5780
  }), minDash.isNumber(rowCount) && chunkedData.length > 1 && evaluatedColumns.length > 0 ? jsxRuntime.jsxs("nav", {
5734
5781
  class: "fjs-table-nav",
5735
- children: [jsxRuntime.jsxs("span", {
5782
+ children: [jsxRuntime.jsx("span", {
5736
5783
  class: "fjs-table-nav-label",
5737
- 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
+ })
5738
5789
  }), jsxRuntime.jsx("button", {
5739
5790
  type: "button",
5740
5791
  class: "fjs-table-nav-button",
@@ -5742,7 +5793,7 @@ function Table(props) {
5742
5793
  setCurrentPage(page => Math.max(page - 1, 0));
5743
5794
  },
5744
5795
  disabled: currentPage === 0,
5745
- "aria-label": "Previous page",
5796
+ "aria-label": translate('Previous page'),
5746
5797
  children: jsxRuntime.jsx(SvgCaretLeft, {})
5747
5798
  }), jsxRuntime.jsx("button", {
5748
5799
  type: "button",
@@ -5751,7 +5802,7 @@ function Table(props) {
5751
5802
  setCurrentPage(page => Math.min(page + 1, chunkedData.length - 1));
5752
5803
  },
5753
5804
  disabled: currentPage >= chunkedData.length - 1,
5754
- "aria-label": "Next page",
5805
+ "aria-label": translate('Next page'),
5755
5806
  children: jsxRuntime.jsx(SvgCaretRight, {})
5756
5807
  })]
5757
5808
  }) : null]
@@ -5901,15 +5952,20 @@ function sortByColumn(array, key, direction) {
5901
5952
  * @param {null|Sorting} sortBy
5902
5953
  * @param {string} key
5903
5954
  * @param {string} label
5955
+ * @param {Translate} translate
5904
5956
  */
5905
- function getHeaderAriaLabel(sortBy, key, label) {
5957
+ function getHeaderAriaLabel(sortBy, key, label, translate) {
5906
5958
  if (sortBy === null || sortBy.key !== key) {
5907
- return `Click to sort by ${label} descending`;
5959
+ return translate('Click to sort by {value} descending', {
5960
+ value: label
5961
+ });
5908
5962
  }
5909
5963
  if (sortBy.direction === 'asc') {
5910
- return 'Click to remove sorting';
5964
+ return translate('Click to remove sorting');
5911
5965
  }
5912
- return `Click to sort by ${label} ascending`;
5966
+ return translate('Click to sort by {value} ascending', {
5967
+ value: label
5968
+ });
5913
5969
  }
5914
5970
 
5915
5971
  /**
@@ -5980,6 +6036,7 @@ function FilePicker(props) {
5980
6036
  const errorMessageId = `${domId}-error-message`;
5981
6037
  /** @type {File[]} */
5982
6038
  const selectedFiles = fileRegistry === null ? EMPTY_ARRAY$1 : fileRegistry.getFiles(filesKey);
6039
+ const translate = useService('translate');
5983
6040
  hooks.useEffect(() => {
5984
6041
  if (filesKey && fileRegistry !== null && !fileRegistry.hasKey(filesKey)) {
5985
6042
  onChange({
@@ -6050,7 +6107,7 @@ function FilePicker(props) {
6050
6107
  children: "Browse"
6051
6108
  }), jsxRuntime.jsx("span", {
6052
6109
  className: "fjs-form-field-label",
6053
- children: getSelectedFilesLabel(selectedFiles)
6110
+ children: getSelectedFilesLabel(selectedFiles, translate)
6054
6111
  })]
6055
6112
  }), jsxRuntime.jsx(Errors, {
6056
6113
  id: errorMessageId,
@@ -6073,16 +6130,19 @@ FilePicker.config = {
6073
6130
 
6074
6131
  /**
6075
6132
  * @param {File[]} files
6133
+ * @param {Translate} translate
6076
6134
  * @returns {string}
6077
6135
  */
6078
- function getSelectedFilesLabel(files) {
6136
+ function getSelectedFilesLabel(files, translate) {
6079
6137
  if (files.length === 0) {
6080
- return 'No files selected';
6138
+ return translate('No files selected');
6081
6139
  }
6082
6140
  if (files.length === 1) {
6083
6141
  return files[0].name;
6084
6142
  }
6085
- return `${files.length} files selected`;
6143
+ return translate('{value} files selected', {
6144
+ value: files.length.toString()
6145
+ });
6086
6146
  }
6087
6147
 
6088
6148
  var _path$4;
@@ -6105,6 +6165,10 @@ var SvgDownload = function SvgDownload(props) {
6105
6165
 
6106
6166
  const type = 'documentPreview';
6107
6167
 
6168
+ /**
6169
+ * @typedef { import('diagram-js/lib/i18n/translate/translate').default } Translate
6170
+ */
6171
+
6108
6172
  /**
6109
6173
  * @typedef DocumentEndpointBuilder
6110
6174
  * @property {(document: DocumentMetadata) => string} [buildUrl]
@@ -6150,6 +6214,7 @@ function DocumentPreview(props) {
6150
6214
  const evaluatedLabel = useSingleLineTemplateEvaluation(label, {
6151
6215
  debug: true
6152
6216
  });
6217
+ const translate = useService('translate');
6153
6218
  return jsxRuntime.jsxs("div", {
6154
6219
  class: formFieldClasses(type),
6155
6220
  children: [jsxRuntime.jsx(Label, {
@@ -6175,7 +6240,7 @@ function DocumentPreview(props) {
6175
6240
  id: errorMessageId,
6176
6241
  errors: getErrors({
6177
6242
  dataSource
6178
- })
6243
+ }, translate)
6179
6244
  })]
6180
6245
  });
6181
6246
  }
@@ -6197,15 +6262,16 @@ DocumentPreview.config = {
6197
6262
  * @property {string|undefined} dataSource
6198
6263
  *
6199
6264
  * @param {GetErrorOptions} options
6265
+ * @param {Translate} translate
6200
6266
  * @returns {string[]}
6201
6267
  */
6202
- function getErrors(options) {
6268
+ function getErrors(options, translate) {
6203
6269
  const {
6204
6270
  dataSource
6205
6271
  } = options;
6206
6272
  let errors = [];
6207
6273
  if (!minDash.isString(dataSource) || dataSource.length < 1) {
6208
- errors.push('Document reference is not defined.');
6274
+ errors.push(translate('Document reference is not defined.'));
6209
6275
  }
6210
6276
  return errors;
6211
6277
  }
@@ -6258,6 +6324,7 @@ function PdfRenderer(props) {
6258
6324
  /** @type {ReturnType<typeof import("preact/hooks").useState<null | string>>} */
6259
6325
  const [pdfObjectUrl, setPdfObjectUrl] = hooks.useState(null);
6260
6326
  const [hasError, setHasError] = hooks.useState(false);
6327
+ const translate = useService('translate');
6261
6328
  hooks.useEffect(() => {
6262
6329
  /** @type {null | string} */
6263
6330
  let objectUrl = null;
@@ -6291,7 +6358,7 @@ function PdfRenderer(props) {
6291
6358
  class: `fjs-${type}-pdf-viewer`
6292
6359
  }) : null, hasError ? jsxRuntime.jsx(Errors, {
6293
6360
  id: errorMessageId,
6294
- errors: ['Unable to download document']
6361
+ errors: [translate('Unable to download document')]
6295
6362
  }) : null]
6296
6363
  });
6297
6364
  }
@@ -6371,9 +6438,10 @@ function DocumentRenderer(props) {
6371
6438
  const isInViewport = useInViewport(ref);
6372
6439
  const onError = hooks.useCallback(() => setHasError(true), []);
6373
6440
  const requestInit = hooks.useMemo(() => getDocumentRequestInit(documentEndpointBuilder, documentMetadata), [documentEndpointBuilder, documentMetadata]);
6441
+ const translate = useService('translate');
6374
6442
  const singleDocumentContainerClassName = `fjs-${type}-single-document-container`;
6375
6443
  const errorMessageId = `${domId}-error-message`;
6376
- const errorMessage = 'Unable to download document';
6444
+ const errorMessage = translate('Unable to download document');
6377
6445
  const isContentTypePresent = typeof metadata.contentType === 'string';
6378
6446
  if (isContentTypePresent && metadata.contentType.toLowerCase().startsWith('image/') && isInViewport) {
6379
6447
  return jsxRuntime.jsxs("div", {
@@ -6451,6 +6519,7 @@ function DownloadButton(props) {
6451
6519
  onDownloadError,
6452
6520
  requestInit
6453
6521
  } = props;
6522
+ const translate = useService('translate');
6454
6523
  const handleDownload = async () => {
6455
6524
  try {
6456
6525
  const response = await fetch(endpoint, requestInit);
@@ -6473,7 +6542,9 @@ function DownloadButton(props) {
6473
6542
  type: "button",
6474
6543
  onClick: handleDownload,
6475
6544
  class: classNames(`fjs-${type}-download-button`),
6476
- "aria-label": `Download ${fileName}`,
6545
+ "aria-label": translate('Download {fileName}', {
6546
+ fileName
6547
+ }),
6477
6548
  children: jsxRuntime.jsx(SvgDownload, {})
6478
6549
  });
6479
6550
  }
@@ -7819,6 +7890,7 @@ class RepeatRenderManager {
7819
7890
  const {
7820
7891
  data
7821
7892
  } = this._form._getState();
7893
+ const translate = useService('translate');
7822
7894
  const dataPath = this._pathRegistry.getValuePath(repeaterField, {
7823
7895
  indexes
7824
7896
  });
@@ -7875,16 +7947,18 @@ class RepeatRenderManager {
7875
7947
  ref: addButtonRef,
7876
7948
  onClick: onAddItem,
7877
7949
  children: jsxRuntime.jsxs(jsxRuntime.Fragment, {
7878
- children: [jsxRuntime.jsx(SvgAdd, {}), " ", 'Add new']
7950
+ children: [jsxRuntime.jsx(SvgAdd, {}), " ", translate('Add new')]
7879
7951
  })
7880
7952
  }) : null, collapseEnabled ? jsxRuntime.jsx("button", {
7881
7953
  type: "button",
7882
7954
  class: "fjs-repeat-render-collapse",
7883
7955
  onClick: toggle,
7884
7956
  children: isCollapsed ? jsxRuntime.jsxs(jsxRuntime.Fragment, {
7885
- children: [jsxRuntime.jsx(SvgExpand, {}), " ", `Expand all (${values.length - 1})`]
7957
+ children: [jsxRuntime.jsx(SvgExpand, {}), " ", translate('Expand all ({value})', {
7958
+ value: values.length - 1
7959
+ })]
7886
7960
  }) : jsxRuntime.jsxs(jsxRuntime.Fragment, {
7887
- children: [jsxRuntime.jsx(SvgCollapse, {}), " ", 'Collapse']
7961
+ children: [jsxRuntime.jsx(SvgCollapse, {}), " ", translate('Collapse')]
7888
7962
  })
7889
7963
  }) : null]
7890
7964
  });
@@ -8517,11 +8591,12 @@ const EMAIL_PATTERN = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-
8517
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}$/;
8518
8592
  const VALIDATE_FEEL_PROPERTIES = ['min', 'max', 'minLength', 'maxLength'];
8519
8593
  class Validator {
8520
- constructor(expressionLanguage, conditionChecker, form, formFieldRegistry) {
8594
+ constructor(expressionLanguage, conditionChecker, form, formFieldRegistry, translate) {
8521
8595
  this._expressionLanguage = expressionLanguage;
8522
8596
  this._conditionChecker = conditionChecker;
8523
8597
  this._form = form;
8524
8598
  this._formFieldRegistry = formFieldRegistry;
8599
+ this._translate = translate;
8525
8600
  }
8526
8601
 
8527
8602
  /**
@@ -8536,13 +8611,13 @@ class Validator {
8536
8611
  } = field;
8537
8612
  let errors = [];
8538
8613
  if (type === 'number') {
8539
- errors = [...errors, ...runNumberValidation(field, value)];
8614
+ errors = [...errors, ...runNumberValidation(field, value, this._translate)];
8540
8615
  }
8541
8616
  if (!validate) {
8542
8617
  return errors;
8543
8618
  }
8544
8619
  const evaluatedValidation = oldEvaluateFEELValues(validate, this._expressionLanguage, this._conditionChecker, this._form);
8545
- errors = [...errors, ...runPresetValidation(field, evaluatedValidation, value)];
8620
+ errors = [...errors, ...runPresetValidation(field, evaluatedValidation, value, this._translate)];
8546
8621
  return errors;
8547
8622
  }
8548
8623
 
@@ -8569,31 +8644,39 @@ class Validator {
8569
8644
  } = field;
8570
8645
  let errors = [];
8571
8646
  if (type === 'number') {
8572
- errors = [...errors, ...runNumberValidation(field, value)];
8647
+ errors = [...errors, ...runNumberValidation(field, value, this._translate)];
8573
8648
  }
8574
8649
  if (!validate) {
8575
8650
  return errors;
8576
8651
  }
8577
8652
  const evaluatedValidation = evaluateFEELValues(validate, this._expressionLanguage, expressionContextInfo);
8578
- errors = [...errors, ...runPresetValidation(field, evaluatedValidation, value)];
8653
+ errors = [...errors, ...runPresetValidation(field, evaluatedValidation, value, this._translate)];
8579
8654
  return errors;
8580
8655
  }
8581
8656
  }
8582
- Validator.$inject = ['expressionLanguage', 'conditionChecker', 'form', 'formFieldRegistry'];
8657
+ Validator.$inject = ['expressionLanguage', 'conditionChecker', 'form', 'formFieldRegistry', 'translate'];
8583
8658
 
8584
8659
  // helpers //////////
8585
8660
 
8586
- function runNumberValidation(field, value) {
8661
+ function runNumberValidation(field, value, translate) {
8587
8662
  const {
8588
8663
  decimalDigits,
8589
8664
  increment
8590
8665
  } = field;
8591
8666
  const errors = [];
8592
8667
  if (value === 'NaN') {
8593
- errors.push('Value is not a number.');
8668
+ errors.push(translate('Value is not a number.'));
8594
8669
  } else if (value) {
8595
8670
  if (decimalDigits >= 0 && countDecimals(value) > decimalDigits) {
8596
- 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
+ }
8597
8680
  }
8598
8681
  if (increment) {
8599
8682
  const bigValue = Big(value);
@@ -8602,54 +8685,69 @@ function runNumberValidation(field, value) {
8602
8685
  if (offset.cmp(0) !== 0) {
8603
8686
  const previousValue = bigValue.minus(offset);
8604
8687
  const nextValue = previousValue.plus(bigIncrement);
8605
- 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
+ }));
8606
8692
  }
8607
8693
  }
8608
8694
  }
8609
8695
  return errors;
8610
8696
  }
8611
- function runPresetValidation(field, validation, value) {
8697
+ function runPresetValidation(field, validation, value, translate) {
8612
8698
  const errors = [];
8613
8699
  if (validation.pattern && value && !new RegExp(validation.pattern).test(value)) {
8614
- 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
+ }));
8615
8703
  }
8616
8704
  if (validation.required) {
8617
8705
  const isUncheckedCheckbox = field.type === 'checkbox' && value === false;
8618
8706
  const isUnsetValue = minDash.isNil(value) || value === '';
8619
8707
  const isEmptyMultiselect = Array.isArray(value) && value.length === 0;
8620
8708
  if (isUncheckedCheckbox || isUnsetValue || isEmptyMultiselect) {
8621
- errors.push('Field is required.');
8709
+ errors.push(translate('Field is required.'));
8622
8710
  }
8623
8711
  }
8712
+
8713
+ // stringify: `translate` falls back to the raw placeholder for a falsy replacement, so 0 must not be passed as a number
8624
8714
  if ('min' in validation && (value || value === 0)) {
8625
8715
  try {
8626
8716
  if (Big(value).lt(Big(validation.min))) {
8627
- 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
+ }));
8628
8720
  }
8629
8721
  } catch {
8630
- errors.push('Min validation value is not a valid number.');
8722
+ errors.push(translate('Min validation value is not a valid number.'));
8631
8723
  }
8632
8724
  }
8633
8725
  if ('max' in validation && (value || value === 0)) {
8634
8726
  try {
8635
8727
  if (Big(value).gt(Big(validation.max))) {
8636
- 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
+ }));
8637
8731
  }
8638
8732
  } catch {
8639
- errors.push('Max validation value is not a valid number.');
8733
+ errors.push(translate('Max validation value is not a valid number.'));
8640
8734
  }
8641
8735
  }
8642
8736
  if ('minLength' in validation && value && value.trim().length < validation.minLength) {
8643
- 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
+ }));
8644
8740
  }
8645
8741
  if ('maxLength' in validation && value && value.trim().length > validation.maxLength) {
8646
- 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
+ }));
8647
8745
  }
8648
8746
  if ('validationType' in validation && value && validation.validationType === 'phone' && !PHONE_PATTERN.test(value)) {
8649
- 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)'));
8650
8748
  }
8651
8749
  if ('validationType' in validation && value && validation.validationType === 'email' && !EMAIL_PATTERN.test(value)) {
8652
- errors.push('Field must be a valid email.');
8750
+ errors.push(translate('Field must be a valid email.'));
8653
8751
  }
8654
8752
  return errors;
8655
8753
  }
@@ -8807,17 +8905,24 @@ class Importer {
8807
8905
  }
8808
8906
  Importer.$inject = ['formFieldRegistry', 'pathRegistry', 'fieldFactory', 'formLayouter'];
8809
8907
 
8908
+ /**
8909
+ * @typedef { import('diagram-js/lib/i18n/translate/translate').default } Translate
8910
+ */
8911
+
8810
8912
  class FieldFactory {
8811
8913
  /**
8812
8914
  * @constructor
8813
8915
  *
8814
- * @param formFieldRegistry
8815
- * @param formFields
8916
+ * @param formFieldRegistry
8917
+ * @param pathRegistry
8918
+ * @param formFields
8919
+ * @param {Translate} translate
8816
8920
  */
8817
- constructor(formFieldRegistry, pathRegistry, formFields) {
8921
+ constructor(formFieldRegistry, pathRegistry, formFields, translate) {
8818
8922
  this._formFieldRegistry = formFieldRegistry;
8819
8923
  this._pathRegistry = pathRegistry;
8820
8924
  this._formFields = formFields;
8925
+ this._translate = translate;
8821
8926
  }
8822
8927
  create(attrs, isNewField = true) {
8823
8928
  const {
@@ -8850,13 +8955,17 @@ class FieldFactory {
8850
8955
  isClosed: true,
8851
8956
  knownAncestorIds
8852
8957
  })) {
8853
- 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
+ }));
8854
8961
  }
8855
8962
  if (config.pathed && path && !this._pathRegistry.canClaimPath([...parentPath, ...path.split('.')], {
8856
8963
  isRepeatable: config.repeatable,
8857
8964
  knownAncestorIds
8858
8965
  })) {
8859
- 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
+ }));
8860
8969
  }
8861
8970
  const field = config.create({
8862
8971
  ...(config.label ? {
@@ -8917,7 +9026,7 @@ class FieldFactory {
8917
9026
  return `${field.type}_${random}`;
8918
9027
  }
8919
9028
  }
8920
- FieldFactory.$inject = ['formFieldRegistry', 'pathRegistry', 'formFields'];
9029
+ FieldFactory.$inject = ['formFieldRegistry', 'pathRegistry', 'formFields', 'translate'];
8921
9030
 
8922
9031
  /**
8923
9032
  * The PathRegistry class manages a hierarchical structure of paths associated with form fields.
@@ -9927,7 +10036,7 @@ class Form {
9927
10036
  config: ['value', enrichedConfig]
9928
10037
  }, {
9929
10038
  form: ['value', this]
9930
- }, CoreModule, ...modules, ...additionalModules]);
10039
+ }, TranslateModule, CoreModule, ...modules, ...additionalModules]);
9931
10040
  }
9932
10041
 
9933
10042
  /**