@bpmn-io/form-js-viewer 1.25.0 → 1.26.1

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;
@@ -6269,7 +6336,13 @@ function PdfRenderer(props) {
6269
6336
  onError();
6270
6337
  return;
6271
6338
  }
6272
- const blob = await response.blob();
6339
+
6340
+ // Re-type the response instead of trusting its content type. An object URL
6341
+ // inherits the form's origin and <embed> renders it by the blob's own type,
6342
+ // so a response served as HTML would execute script in that origin.
6343
+ const blob = new Blob([await response.arrayBuffer()], {
6344
+ type: 'application/pdf'
6345
+ });
6273
6346
  objectUrl = URL.createObjectURL(blob);
6274
6347
  setPdfObjectUrl(objectUrl);
6275
6348
  } catch {
@@ -6291,7 +6364,7 @@ function PdfRenderer(props) {
6291
6364
  class: `fjs-${type}-pdf-viewer`
6292
6365
  }) : null, hasError ? jsxRuntime.jsx(Errors, {
6293
6366
  id: errorMessageId,
6294
- errors: ['Unable to download document']
6367
+ errors: [translate('Unable to download document')]
6295
6368
  }) : null]
6296
6369
  });
6297
6370
  }
@@ -6371,9 +6444,10 @@ function DocumentRenderer(props) {
6371
6444
  const isInViewport = useInViewport(ref);
6372
6445
  const onError = hooks.useCallback(() => setHasError(true), []);
6373
6446
  const requestInit = hooks.useMemo(() => getDocumentRequestInit(documentEndpointBuilder, documentMetadata), [documentEndpointBuilder, documentMetadata]);
6447
+ const translate = useService('translate');
6374
6448
  const singleDocumentContainerClassName = `fjs-${type}-single-document-container`;
6375
6449
  const errorMessageId = `${domId}-error-message`;
6376
- const errorMessage = 'Unable to download document';
6450
+ const errorMessage = translate('Unable to download document');
6377
6451
  const isContentTypePresent = typeof metadata.contentType === 'string';
6378
6452
  if (isContentTypePresent && metadata.contentType.toLowerCase().startsWith('image/') && isInViewport) {
6379
6453
  return jsxRuntime.jsxs("div", {
@@ -6451,6 +6525,7 @@ function DownloadButton(props) {
6451
6525
  onDownloadError,
6452
6526
  requestInit
6453
6527
  } = props;
6528
+ const translate = useService('translate');
6454
6529
  const handleDownload = async () => {
6455
6530
  try {
6456
6531
  const response = await fetch(endpoint, requestInit);
@@ -6473,7 +6548,9 @@ function DownloadButton(props) {
6473
6548
  type: "button",
6474
6549
  onClick: handleDownload,
6475
6550
  class: classNames(`fjs-${type}-download-button`),
6476
- "aria-label": `Download ${fileName}`,
6551
+ "aria-label": translate('Download {fileName}', {
6552
+ fileName
6553
+ }),
6477
6554
  children: jsxRuntime.jsx(SvgDownload, {})
6478
6555
  });
6479
6556
  }
@@ -7819,6 +7896,7 @@ class RepeatRenderManager {
7819
7896
  const {
7820
7897
  data
7821
7898
  } = this._form._getState();
7899
+ const translate = useService('translate');
7822
7900
  const dataPath = this._pathRegistry.getValuePath(repeaterField, {
7823
7901
  indexes
7824
7902
  });
@@ -7875,16 +7953,18 @@ class RepeatRenderManager {
7875
7953
  ref: addButtonRef,
7876
7954
  onClick: onAddItem,
7877
7955
  children: jsxRuntime.jsxs(jsxRuntime.Fragment, {
7878
- children: [jsxRuntime.jsx(SvgAdd, {}), " ", 'Add new']
7956
+ children: [jsxRuntime.jsx(SvgAdd, {}), " ", translate('Add new')]
7879
7957
  })
7880
7958
  }) : null, collapseEnabled ? jsxRuntime.jsx("button", {
7881
7959
  type: "button",
7882
7960
  class: "fjs-repeat-render-collapse",
7883
7961
  onClick: toggle,
7884
7962
  children: isCollapsed ? jsxRuntime.jsxs(jsxRuntime.Fragment, {
7885
- children: [jsxRuntime.jsx(SvgExpand, {}), " ", `Expand all (${values.length - 1})`]
7963
+ children: [jsxRuntime.jsx(SvgExpand, {}), " ", translate('Expand all ({value})', {
7964
+ value: values.length - 1
7965
+ })]
7886
7966
  }) : jsxRuntime.jsxs(jsxRuntime.Fragment, {
7887
- children: [jsxRuntime.jsx(SvgCollapse, {}), " ", 'Collapse']
7967
+ children: [jsxRuntime.jsx(SvgCollapse, {}), " ", translate('Collapse')]
7888
7968
  })
7889
7969
  }) : null]
7890
7970
  });
@@ -8517,11 +8597,12 @@ const EMAIL_PATTERN = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-
8517
8597
  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
8598
  const VALIDATE_FEEL_PROPERTIES = ['min', 'max', 'minLength', 'maxLength'];
8519
8599
  class Validator {
8520
- constructor(expressionLanguage, conditionChecker, form, formFieldRegistry) {
8600
+ constructor(expressionLanguage, conditionChecker, form, formFieldRegistry, translate) {
8521
8601
  this._expressionLanguage = expressionLanguage;
8522
8602
  this._conditionChecker = conditionChecker;
8523
8603
  this._form = form;
8524
8604
  this._formFieldRegistry = formFieldRegistry;
8605
+ this._translate = translate;
8525
8606
  }
8526
8607
 
8527
8608
  /**
@@ -8536,13 +8617,13 @@ class Validator {
8536
8617
  } = field;
8537
8618
  let errors = [];
8538
8619
  if (type === 'number') {
8539
- errors = [...errors, ...runNumberValidation(field, value)];
8620
+ errors = [...errors, ...runNumberValidation(field, value, this._translate)];
8540
8621
  }
8541
8622
  if (!validate) {
8542
8623
  return errors;
8543
8624
  }
8544
8625
  const evaluatedValidation = oldEvaluateFEELValues(validate, this._expressionLanguage, this._conditionChecker, this._form);
8545
- errors = [...errors, ...runPresetValidation(field, evaluatedValidation, value)];
8626
+ errors = [...errors, ...runPresetValidation(field, evaluatedValidation, value, this._translate)];
8546
8627
  return errors;
8547
8628
  }
8548
8629
 
@@ -8569,31 +8650,39 @@ class Validator {
8569
8650
  } = field;
8570
8651
  let errors = [];
8571
8652
  if (type === 'number') {
8572
- errors = [...errors, ...runNumberValidation(field, value)];
8653
+ errors = [...errors, ...runNumberValidation(field, value, this._translate)];
8573
8654
  }
8574
8655
  if (!validate) {
8575
8656
  return errors;
8576
8657
  }
8577
8658
  const evaluatedValidation = evaluateFEELValues(validate, this._expressionLanguage, expressionContextInfo);
8578
- errors = [...errors, ...runPresetValidation(field, evaluatedValidation, value)];
8659
+ errors = [...errors, ...runPresetValidation(field, evaluatedValidation, value, this._translate)];
8579
8660
  return errors;
8580
8661
  }
8581
8662
  }
8582
- Validator.$inject = ['expressionLanguage', 'conditionChecker', 'form', 'formFieldRegistry'];
8663
+ Validator.$inject = ['expressionLanguage', 'conditionChecker', 'form', 'formFieldRegistry', 'translate'];
8583
8664
 
8584
8665
  // helpers //////////
8585
8666
 
8586
- function runNumberValidation(field, value) {
8667
+ function runNumberValidation(field, value, translate) {
8587
8668
  const {
8588
8669
  decimalDigits,
8589
8670
  increment
8590
8671
  } = field;
8591
8672
  const errors = [];
8592
8673
  if (value === 'NaN') {
8593
- errors.push('Value is not a number.');
8674
+ errors.push(translate('Value is not a number.'));
8594
8675
  } else if (value) {
8595
8676
  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' : ''}`) + '.');
8677
+ if (decimalDigits === 0) {
8678
+ errors.push(translate('Value is expected to be an integer.'));
8679
+ } else if (decimalDigits === 1) {
8680
+ errors.push(translate('Value is expected to have at most 1 decimal digit.'));
8681
+ } else {
8682
+ errors.push(translate('Value is expected to have at most {value} decimal digits.', {
8683
+ value: decimalDigits.toString()
8684
+ }));
8685
+ }
8597
8686
  }
8598
8687
  if (increment) {
8599
8688
  const bigValue = Big(value);
@@ -8602,54 +8691,69 @@ function runNumberValidation(field, value) {
8602
8691
  if (offset.cmp(0) !== 0) {
8603
8692
  const previousValue = bigValue.minus(offset);
8604
8693
  const nextValue = previousValue.plus(bigIncrement);
8605
- errors.push(`Please select a valid value, the two nearest valid values are ${previousValue} and ${nextValue}.`);
8694
+ errors.push(translate('Please select a valid value, the two nearest valid values are {previousValue} and {nextValue}.', {
8695
+ previousValue: previousValue,
8696
+ nextValue: nextValue
8697
+ }));
8606
8698
  }
8607
8699
  }
8608
8700
  }
8609
8701
  return errors;
8610
8702
  }
8611
- function runPresetValidation(field, validation, value) {
8703
+ function runPresetValidation(field, validation, value, translate) {
8612
8704
  const errors = [];
8613
8705
  if (validation.pattern && value && !new RegExp(validation.pattern).test(value)) {
8614
- errors.push(validation.patternErrorMessage || `Field must match pattern ${validation.pattern}.`);
8706
+ errors.push(validation.patternErrorMessage || translate('Field must match pattern {value}.', {
8707
+ value: validation.pattern
8708
+ }));
8615
8709
  }
8616
8710
  if (validation.required) {
8617
8711
  const isUncheckedCheckbox = field.type === 'checkbox' && value === false;
8618
8712
  const isUnsetValue = minDash.isNil(value) || value === '';
8619
8713
  const isEmptyMultiselect = Array.isArray(value) && value.length === 0;
8620
8714
  if (isUncheckedCheckbox || isUnsetValue || isEmptyMultiselect) {
8621
- errors.push('Field is required.');
8715
+ errors.push(translate('Field is required.'));
8622
8716
  }
8623
8717
  }
8718
+
8719
+ // stringify: `translate` falls back to the raw placeholder for a falsy replacement, so 0 must not be passed as a number
8624
8720
  if ('min' in validation && (value || value === 0)) {
8625
8721
  try {
8626
8722
  if (Big(value).lt(Big(validation.min))) {
8627
- errors.push(`Field must have minimum value of ${validation.min}.`);
8723
+ errors.push(translate('Field must have minimum value of {value}.', {
8724
+ value: validation.min.toString()
8725
+ }));
8628
8726
  }
8629
8727
  } catch {
8630
- errors.push('Min validation value is not a valid number.');
8728
+ errors.push(translate('Min validation value is not a valid number.'));
8631
8729
  }
8632
8730
  }
8633
8731
  if ('max' in validation && (value || value === 0)) {
8634
8732
  try {
8635
8733
  if (Big(value).gt(Big(validation.max))) {
8636
- errors.push(`Field must have maximum value of ${validation.max}.`);
8734
+ errors.push(translate('Field must have maximum value of {value}.', {
8735
+ value: validation.max.toString()
8736
+ }));
8637
8737
  }
8638
8738
  } catch {
8639
- errors.push('Max validation value is not a valid number.');
8739
+ errors.push(translate('Max validation value is not a valid number.'));
8640
8740
  }
8641
8741
  }
8642
8742
  if ('minLength' in validation && value && value.trim().length < validation.minLength) {
8643
- errors.push(`Field must have minimum length of ${validation.minLength}.`);
8743
+ errors.push(translate('Field must have minimum length of {value}.', {
8744
+ value: validation.minLength.toString()
8745
+ }));
8644
8746
  }
8645
8747
  if ('maxLength' in validation && value && value.trim().length > validation.maxLength) {
8646
- errors.push(`Field must have maximum length of ${validation.maxLength}.`);
8748
+ errors.push(translate('Field must have maximum length of {value}.', {
8749
+ value: validation.maxLength.toString()
8750
+ }));
8647
8751
  }
8648
8752
  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)');
8753
+ errors.push(translate('Field must be a valid international phone number. (e.g. +4930664040900)'));
8650
8754
  }
8651
8755
  if ('validationType' in validation && value && validation.validationType === 'email' && !EMAIL_PATTERN.test(value)) {
8652
- errors.push('Field must be a valid email.');
8756
+ errors.push(translate('Field must be a valid email.'));
8653
8757
  }
8654
8758
  return errors;
8655
8759
  }
@@ -8807,17 +8911,24 @@ class Importer {
8807
8911
  }
8808
8912
  Importer.$inject = ['formFieldRegistry', 'pathRegistry', 'fieldFactory', 'formLayouter'];
8809
8913
 
8914
+ /**
8915
+ * @typedef { import('diagram-js/lib/i18n/translate/translate').default } Translate
8916
+ */
8917
+
8810
8918
  class FieldFactory {
8811
8919
  /**
8812
8920
  * @constructor
8813
8921
  *
8814
- * @param formFieldRegistry
8815
- * @param formFields
8922
+ * @param formFieldRegistry
8923
+ * @param pathRegistry
8924
+ * @param formFields
8925
+ * @param {Translate} translate
8816
8926
  */
8817
- constructor(formFieldRegistry, pathRegistry, formFields) {
8927
+ constructor(formFieldRegistry, pathRegistry, formFields, translate) {
8818
8928
  this._formFieldRegistry = formFieldRegistry;
8819
8929
  this._pathRegistry = pathRegistry;
8820
8930
  this._formFields = formFields;
8931
+ this._translate = translate;
8821
8932
  }
8822
8933
  create(attrs, isNewField = true) {
8823
8934
  const {
@@ -8850,13 +8961,17 @@ class FieldFactory {
8850
8961
  isClosed: true,
8851
8962
  knownAncestorIds
8852
8963
  })) {
8853
- throw new Error(`binding path '${[...parentPath, key].join('.')}' is already claimed`);
8964
+ throw new Error(this._translate("binding path '{value}' is already claimed", {
8965
+ value: [...parentPath, key].join('.')
8966
+ }));
8854
8967
  }
8855
8968
  if (config.pathed && path && !this._pathRegistry.canClaimPath([...parentPath, ...path.split('.')], {
8856
8969
  isRepeatable: config.repeatable,
8857
8970
  knownAncestorIds
8858
8971
  })) {
8859
- throw new Error(`binding path '${[...parentPath, ...path.split('.')].join('.')}' is already claimed`);
8972
+ throw new Error(this._translate("binding path '{value}' is already claimed", {
8973
+ value: [...parentPath, ...path.split('.')].join('.')
8974
+ }));
8860
8975
  }
8861
8976
  const field = config.create({
8862
8977
  ...(config.label ? {
@@ -8917,7 +9032,7 @@ class FieldFactory {
8917
9032
  return `${field.type}_${random}`;
8918
9033
  }
8919
9034
  }
8920
- FieldFactory.$inject = ['formFieldRegistry', 'pathRegistry', 'formFields'];
9035
+ FieldFactory.$inject = ['formFieldRegistry', 'pathRegistry', 'formFields', 'translate'];
8921
9036
 
8922
9037
  /**
8923
9038
  * The PathRegistry class manages a hierarchical structure of paths associated with form fields.
@@ -9927,7 +10042,7 @@ class Form {
9927
10042
  config: ['value', enrichedConfig]
9928
10043
  }, {
9929
10044
  form: ['value', this]
9930
- }, CoreModule, ...modules, ...additionalModules]);
10045
+ }, TranslateModule, CoreModule, ...modules, ...additionalModules]);
9931
10046
  }
9932
10047
 
9933
10048
  /**