form-craft-package 1.8.2-dev.2 → 1.8.2-dev.3

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.
Files changed (36) hide show
  1. package/package.json +1 -1
  2. package/src/components/common/custom-hooks/index.ts +1 -0
  3. package/src/components/common/custom-hooks/use-translation.hook/hook.ts +28 -0
  4. package/src/components/common/custom-hooks/use-translation.hook/store.ts +130 -0
  5. package/src/components/form/1-list/index.tsx +7 -0
  6. package/src/components/form/2-details/index.tsx +7 -0
  7. package/src/components/form/layout-renderer/1-row/header-render.tsx +12 -2
  8. package/src/components/form/layout-renderer/1-row/header.tsx +1 -0
  9. package/src/components/form/layout-renderer/1-row/index.tsx +7 -1
  10. package/src/components/form/layout-renderer/1-row/repeatable-render.tsx +17 -3
  11. package/src/components/form/layout-renderer/3-element/1-dynamic-button/index.tsx +32 -14
  12. package/src/components/form/layout-renderer/3-element/10-currency.tsx +12 -1
  13. package/src/components/form/layout-renderer/3-element/11-breadcrumb.tsx +14 -6
  14. package/src/components/form/layout-renderer/3-element/12-picker-field.tsx +10 -6
  15. package/src/components/form/layout-renderer/3-element/13-language-selector.tsx +42 -0
  16. package/src/components/form/layout-renderer/3-element/2-field-element.tsx +31 -17
  17. package/src/components/form/layout-renderer/3-element/3-read-field-data.tsx +13 -4
  18. package/src/components/form/layout-renderer/3-element/4-rich-text-editor.tsx +10 -9
  19. package/src/components/form/layout-renderer/3-element/6-signature.tsx +7 -4
  20. package/src/components/form/layout-renderer/3-element/7-file-upload.tsx +44 -10
  21. package/src/components/form/layout-renderer/3-element/8-fields-with-options.tsx +25 -6
  22. package/src/components/form/layout-renderer/3-element/9-form-data-render.tsx +7 -0
  23. package/src/components/form/layout-renderer/3-element/index.tsx +35 -15
  24. package/src/constants.ts +2 -1
  25. package/src/enums/index.ts +28 -0
  26. package/src/functions/companies/use-company-config.tsx +13 -2
  27. package/src/functions/forms/create-form-rules.ts +18 -6
  28. package/src/functions/forms/data-render-functions.tsx +44 -23
  29. package/src/functions/forms/get-element-props.ts +1 -1
  30. package/src/types/forms/data-list/index.ts +1 -0
  31. package/src/types/forms/index.ts +10 -3
  32. package/src/types/forms/layout-elements/button.ts +2 -11
  33. package/src/types/forms/layout-elements/index.ts +10 -42
  34. package/src/types/forms/layout-elements/read-field-data-props.ts +0 -1
  35. package/src/types/forms/layout-elements/validation.ts +0 -1
  36. package/src/types/index.ts +2 -0
@@ -13,35 +13,49 @@ import { Checkbox, Form, Input, Select, Radio, InputNumber, DatePicker, Modal, S
13
13
  interface ILayoutRenderer_FieldElement {
14
14
  children: () => IMapperFieldObj | IMapperFieldObj[]
15
15
  formRef?: FormInstance
16
+ elementKey?: string
17
+ formId?: number
16
18
  }
17
19
 
18
- export const LayoutRenderer_FieldElement = memo(({ children, formRef }: ILayoutRenderer_FieldElement): JSX.Element => {
19
- const elems = children()
20
- if (Array.isArray(elems)) {
21
- return (
22
- <>
23
- {elems
24
- .filter(({ isHidden = false }) => !isHidden)
25
- .map((field: IMapperFieldObj, idx: Key) => (
26
- <FieldFormItemWrapper key={idx} field={field} fieldIdx={idx} formRef={formRef} />
27
- ))}
28
- </>
29
- )
30
- }
20
+ export const LayoutRenderer_FieldElement = memo(
21
+ ({ children, formRef, elementKey, formId }: ILayoutRenderer_FieldElement): JSX.Element => {
22
+ const elems = children()
23
+ if (Array.isArray(elems))
24
+ return (
25
+ <>
26
+ {elems
27
+ .filter(({ isHidden = false }) => !isHidden)
28
+ .map((field: IMapperFieldObj, idx: Key) => (
29
+ <FieldFormItemWrapper
30
+ key={idx}
31
+ field={field}
32
+ fieldIdx={idx}
33
+ formRef={formRef}
34
+ elementKey={elementKey}
35
+ formId={formId}
36
+ />
37
+ ))}
38
+ </>
39
+ )
31
40
 
32
- if (elems.isHidden) return <></>
41
+ if (elems.isHidden) return <></>
33
42
 
34
- return <FieldFormItemWrapper field={elems} fieldIdx={1} formRef={formRef} />
35
- })
43
+ return <FieldFormItemWrapper field={elems} fieldIdx={1} formRef={formRef} elementKey={elementKey} formId={formId} />
44
+ },
45
+ )
36
46
 
37
47
  const FieldFormItemWrapper = ({
38
48
  field,
39
49
  fieldIdx,
40
50
  formRef,
51
+ elementKey,
52
+ formId,
41
53
  }: {
42
54
  field: IMapperFieldObj
43
55
  fieldIdx: Key
44
56
  formRef?: FormInstance
57
+ elementKey?: string
58
+ formId?: number
45
59
  }) => {
46
60
  const { name, nameFullPath, label, hasNoLabel, type, validations, sanitization, disabled } = field
47
61
 
@@ -53,7 +67,7 @@ const FieldFormItemWrapper = ({
53
67
  labelAlign="left"
54
68
  messageVariables={{ [String(name)]: formRef?.getFieldValue(name) }}
55
69
  valuePropName={type && [ElementTypeEnum.Checkbox, ElementTypeEnum.Switch].includes(type) ? 'checked' : 'value'}
56
- rules={mapToFormItemRules(validations ?? [], String(name))}
70
+ rules={mapToFormItemRules(elementKey, validations ?? [], formId)}
57
71
  normalize={(value) => {
58
72
  if (!sanitization || sanitization.type === DataSanitizationTypeEnum.Default) return value
59
73
 
@@ -2,13 +2,21 @@ import { Form } from 'antd'
2
2
  import { renderData } from '../../../../functions/forms'
3
3
  import { useFormPreservedItemValues } from '../../../common/custom-hooks/use-preserved-form-items.hook'
4
4
  import { memo, useMemo } from 'react'
5
- import { DataRenderTypeEnum, ReadFieldDataValueTypeEnum } from '../../../../enums'
5
+ import { DataRenderTypeEnum, ReadFieldDataValueTypeEnum, TranslationTextTypeEnum } from '../../../../enums'
6
6
  import { IReadFieldDataElementProps } from '../../../../types'
7
7
  import { IFormContext } from '../1-row'
8
8
  import { evaluateValue } from '../../../../functions/forms/evaluate-value'
9
9
  import { ELEMENTS_DEFAULT_CLASS } from '../../../../constants'
10
+ import { IElementBaseProps } from '.'
11
+ import { useTranslation } from '../../../common/custom-hooks/use-translation.hook/hook'
10
12
 
11
- function LayoutRenderer_ReadFieldData({ formContext, elementProps, style = {} }: ILayoutRenderer_ReadFieldData) {
13
+ function LayoutRenderer_ReadFieldData({
14
+ formContext,
15
+ elementProps,
16
+ style = {},
17
+ elementKey,
18
+ }: ILayoutRenderer_ReadFieldData) {
19
+ const { t } = useTranslation(formContext.formId)
12
20
  const { formRef, companyKey } = formContext
13
21
  const fieldValue =
14
22
  elementProps.valueType === ReadFieldDataValueTypeEnum.Evaluated
@@ -31,9 +39,10 @@ function LayoutRenderer_ReadFieldData({ formContext, elementProps, style = {} }:
31
39
  return 0
32
40
  }, [formValues, elementProps])
33
41
 
42
+ const label = t({ key: elementKey, type: TranslationTextTypeEnum.Label })
34
43
  return (
35
44
  <div className={`${ELEMENTS_DEFAULT_CLASS.ReadFieldData} flex items-center gap-2`}>
36
- {elementProps.label && <span>{elementProps.label}: </span>}
45
+ {label && <span>{label}: </span>}
37
46
  <span style={style}>
38
47
  {renderData(
39
48
  elementProps.renderConfig?.type === DataRenderTypeEnum.Image
@@ -56,4 +65,4 @@ type ILayoutRenderer_ReadFieldData = {
56
65
  formContext: IFormContext
57
66
  elementProps: IReadFieldDataElementProps
58
67
  style?: { [key: string]: any }
59
- }
68
+ } & IElementBaseProps
@@ -5,9 +5,10 @@ import { IElementBaseProps } from '.'
5
5
  import { IInputElementProps, IValidationRule } from '../../../../types'
6
6
  import { IFormContext } from '../1-row'
7
7
  import { mapToFormItemRules } from '../../../../functions'
8
- import { FieldValidationEnum } from '../../../../enums'
8
+ import { FieldValidationEnum, TranslationTextTypeEnum } from '../../../../enums'
9
9
  import { ELEMENTS_DEFAULT_CLASS, REGEX_PATTERNS } from '../../../../constants'
10
10
  import { memo } from 'react'
11
+ import { useTranslation } from '../../../common/custom-hooks/use-translation.hook/hook'
11
12
 
12
13
  function LayoutRenderer_RichEditor({
13
14
  formContext,
@@ -15,30 +16,30 @@ function LayoutRenderer_RichEditor({
15
16
  formItem,
16
17
  validations,
17
18
  isDisabled,
19
+ elementKey,
18
20
  }: ILayoutRenderer_RichEditor) {
19
- const { formRef } = formContext
21
+ const { formRef, formId } = formContext
22
+ const { t } = useTranslation(formId)
20
23
  const fieldValue = Form.useWatch(formItem.path, { form: formRef, preserve: true })
21
24
  const requiredValidation = validations?.find((v) => v.rule === FieldValidationEnum.Required)
22
25
 
26
+ const label = t({ key: elementKey, type: TranslationTextTypeEnum.Label })
23
27
  return (
24
28
  <Form.Item
25
29
  name={formItem.name}
26
30
  rules={
27
31
  requiredValidation
28
32
  ? mapToFormItemRules(
33
+ elementKey,
29
34
  [
30
35
  requiredValidation,
31
- {
32
- rule: FieldValidationEnum.Regex,
33
- value: REGEX_PATTERNS.NonEmptyHtmlString.toString(),
34
- message: requiredValidation.message,
35
- },
36
+ { rule: FieldValidationEnum.Regex, value: REGEX_PATTERNS.NonEmptyHtmlString.toString() },
36
37
  ],
37
- formItem.name,
38
+ formId,
38
39
  )
39
40
  : []
40
41
  }
41
- label={elementProps.hasNoLabel ? '' : elementProps.label}
42
+ label={elementProps.hasNoLabel ? '' : label}
42
43
  labelAlign="left"
43
44
  >
44
45
  <ReactQuill
@@ -1,7 +1,7 @@
1
1
  import { Form } from 'antd'
2
2
  import { memo, useEffect, useMemo, useRef } from 'react'
3
3
  import SignatureCanvas from 'react-signature-canvas'
4
- import { FormPreservedItemKeys } from '../../../../enums'
4
+ import { FormPreservedItemKeys, TranslationTextTypeEnum } from '../../../../enums'
5
5
  import { Button_FillerPortal } from '../../../common/button'
6
6
  import { useFormPreservedItemValues } from '../../../common/custom-hooks/use-preserved-form-items.hook'
7
7
  import { IElementBaseProps } from '.'
@@ -9,6 +9,7 @@ import { ISignatureElementProps, IValidationRule } from '../../../../types'
9
9
  import { IFormContext } from '../1-row'
10
10
  import { isNewFormDataPage, mapToFormItemRules } from '../../../../functions'
11
11
  import { ELEMENTS_DEFAULT_CLASS } from '../../../../constants'
12
+ import { useTranslation } from '../../../common/custom-hooks/use-translation.hook/hook'
12
13
 
13
14
  function LayoutRenderer_Signature({
14
15
  formItem,
@@ -16,8 +17,10 @@ function LayoutRenderer_Signature({
16
17
  isDisabled,
17
18
  formContext,
18
19
  validations,
20
+ elementKey,
19
21
  }: ILayoutRenderer_Signature) {
20
- const { formRef, formDataId, companyKey } = formContext
22
+ const { formRef, formDataId, companyKey, formId } = formContext
23
+ const { t } = useTranslation(formId)
21
24
  const sigCanvasRef = useRef<SignatureCanvas>(null)
22
25
  const savedSignatureBlobName = Form.useWatch(formItem.path, formRef)
23
26
  const { isGeneratingPdf, baseServerUrl } = useFormPreservedItemValues(formRef)
@@ -35,11 +38,11 @@ function LayoutRenderer_Signature({
35
38
  return (
36
39
  <Form.Item
37
40
  name={formItem.name}
38
- rules={Array.isArray(validations) ? mapToFormItemRules(validations, formItem.name) : []}
41
+ rules={Array.isArray(validations) ? mapToFormItemRules(elementKey, validations, formId) : []}
39
42
  label={
40
43
  !elementProps.hasNoLabel && (
41
44
  <div className="flex items-center gap-1">
42
- <span>{elementProps.label}</span>
45
+ <span>{t({ key: elementKey, type: TranslationTextTypeEnum.Label })}</span>
43
46
  <Button_FillerPortal
44
47
  link
45
48
  onClick={() => {
@@ -10,6 +10,9 @@ import { saveFile } from '../../../../functions/forms/form'
10
10
  import { IElementBaseProps } from '.'
11
11
  import { IFormContext } from '../1-row'
12
12
  import { ELEMENTS_DEFAULT_CLASS } from '../../../../constants'
13
+ import { TranslationTextSubTypeEnum, TranslationTextTypeEnum } from '../../../../enums'
14
+ import { useTranslation } from '../../../common/custom-hooks/use-translation.hook/hook'
15
+ import { translationStore } from '../../../common/custom-hooks/use-translation.hook/store'
13
16
 
14
17
  const { VITE_API_BASE_URL } = import.meta.env
15
18
 
@@ -19,7 +22,9 @@ function LayoutRenderer_FileUpload({
19
22
  elementProps,
20
23
  uploadRules,
21
24
  isDisabled,
25
+ elementKey,
22
26
  }: ILayoutRenderer_FileUpload) {
27
+ const { t } = useTranslation(formContext.formId)
23
28
  const { formRef, companyKey } = formContext
24
29
  const { warningModal, confirmModal } = useNotification()
25
30
  const [loading, setLoading] = useState(false)
@@ -56,7 +61,7 @@ function LayoutRenderer_FileUpload({
56
61
  }
57
62
 
58
63
  const beforeUpload = useCallback((file: any) => {
59
- const ruleMsg = getUploadRuleErrorMsg(file, uploadRules)
64
+ const ruleMsg = getUploadRuleErrorMsg(elementKey, file, uploadRules, formContext.formId)
60
65
  if (ruleMsg) {
61
66
  warningModal({ title: '', content: ruleMsg, okText: 'Okay' })
62
67
  return false
@@ -65,17 +70,23 @@ function LayoutRenderer_FileUpload({
65
70
  return true
66
71
  }, [])
67
72
 
73
+ const [label, placeholder, hint] = t([
74
+ { key: elementKey, type: TranslationTextTypeEnum.Label },
75
+ { key: elementKey, type: TranslationTextTypeEnum.Placeholder },
76
+ { key: elementKey, type: TranslationTextTypeEnum.Description, subType: TranslationTextSubTypeEnum.Secondary },
77
+ ])
78
+
68
79
  return (
69
80
  <>
70
81
  <Form.Item name={formItem.name} labelAlign="left" className="hidden">
71
82
  <Input />
72
83
  </Form.Item>
73
- {!elementProps.hasNoLabel && elementProps.label && <span>{elementProps.label}</span>}
84
+ {!elementProps.hasNoLabel && label && <span>{label}</span>}
74
85
  {savedSignatureBlobName ? (
75
86
  elementProps.isDragger ? (
76
87
  <div className="flex flex-col items-center p-4 bg-background rounded-md">
77
88
  <FaFileCircleCheck size={24} className="text-success" />
78
- <span className="text-secondary">{elementProps.placeholder ?? 'Uploaded'}:</span>
89
+ <span className="text-secondary">{placeholder ?? 'Uploaded'}:</span>
79
90
  <div className="flex gap-2 items-center">
80
91
  <u className="text-neutral">{savedSignatureBlobName}</u>
81
92
  <Button_FillerPortal
@@ -109,7 +120,7 @@ function LayoutRenderer_FileUpload({
109
120
  ) : (
110
121
  <div className="flex items-center gap-2 px-4 py-1 bg-background rounded-md">
111
122
  <FaFileCircleCheck size={16} className="text-success" />
112
- <span className="text-secondary">{elementProps.placeholder ?? 'Uploaded'}:</span>
123
+ <span className="text-secondary">{placeholder ?? 'Uploaded'}:</span>
113
124
  <u className="text-neutral">{savedSignatureBlobName}</u>
114
125
  <div className="flex gap-2 items-center ml-auto">
115
126
  <Button_FillerPortal
@@ -146,8 +157,8 @@ function LayoutRenderer_FileUpload({
146
157
  <Upload.Dragger {...uploadProps} disabled={isDisabled} beforeUpload={(file) => beforeUpload(file)}>
147
158
  <div className="flex flex-col items-center">
148
159
  <FaUpload size={24} className="text-primary" />
149
- <span className="font-semibold mt-2 text-secondary">{elementProps.placeholder}</span>
150
- <span className="text-opacity-50 text-neutral text-12">{elementProps.hint}</span>
160
+ <span className="font-semibold mt-2 text-secondary">{placeholder}</span>
161
+ <span className="text-opacity-50 text-neutral text-12">{hint}</span>
151
162
  </div>
152
163
  </Upload.Dragger>
153
164
  </Spin>
@@ -156,7 +167,7 @@ function LayoutRenderer_FileUpload({
156
167
  <Upload {...uploadProps} disabled={isDisabled} beforeUpload={(file) => beforeUpload(file)}>
157
168
  <Button_FillerPortal disabled={isDisabled} outline onClick={() => {}} className="w-full">
158
169
  <FaUpload />
159
- {elementProps.placeholder}
170
+ {placeholder}
160
171
  </Button_FillerPortal>
161
172
  </Upload>
162
173
  </Spin>
@@ -173,7 +184,12 @@ type ILayoutRenderer_FileUpload = {
173
184
  elementProps: IFileUploadElementProps
174
185
  } & IElementBaseProps
175
186
 
176
- const getUploadRuleErrorMsg = (file: any, uploadRules?: IFileUploadElementRules): null | string => {
187
+ const getUploadRuleErrorMsg = (
188
+ elementKey: string = '',
189
+ file: any,
190
+ uploadRules?: IFileUploadElementRules,
191
+ formId?: number,
192
+ ): null | string => {
177
193
  if (!uploadRules) return null
178
194
 
179
195
  if (
@@ -181,12 +197,30 @@ const getUploadRuleErrorMsg = (file: any, uploadRules?: IFileUploadElementRules)
181
197
  uploadRules.allowedTypes.types.length > 0 &&
182
198
  !uploadRules.allowedTypes.types.includes(file.type)
183
199
  ) {
184
- return uploadRules.allowedTypes.errorMsg ?? 'Invalid file type!'
200
+ const errorMsg = translationStore.translate(
201
+ {
202
+ key: elementKey,
203
+ type: TranslationTextTypeEnum.ValidationError,
204
+ subType: TranslationTextSubTypeEnum.UploadFileType,
205
+ },
206
+ formId,
207
+ )
208
+ return errorMsg ?? 'Invalid file type!'
185
209
  }
186
210
 
187
211
  if (uploadRules.maxSize && uploadRules.maxSize.size > 0) {
188
212
  const fileSizeInMb = file.size / 1024 / 1024
189
- if (fileSizeInMb > uploadRules.maxSize.size) return uploadRules.maxSize.errorMsg ?? 'File is too large!'
213
+ if (fileSizeInMb > uploadRules.maxSize.size) {
214
+ const errorMsg = translationStore.translate(
215
+ {
216
+ key: elementKey,
217
+ type: TranslationTextTypeEnum.ValidationError,
218
+ subType: TranslationTextSubTypeEnum.UploadSizeLimit,
219
+ },
220
+ formId,
221
+ )
222
+ return errorMsg ?? 'File is too large!'
223
+ }
190
224
  }
191
225
 
192
226
  return null
@@ -1,5 +1,10 @@
1
1
  import { memo, ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react'
2
- import { PageViewTypEnum, FieldElementOptionSourceEnum, FilterConfigTypeEnum } from '../../../../enums'
2
+ import {
3
+ PageViewTypEnum,
4
+ FieldElementOptionSourceEnum,
5
+ FilterConfigTypeEnum,
6
+ TranslationTextTypeEnum,
7
+ } from '../../../../enums'
3
8
  import { IElementBaseProps } from '.'
4
9
  import { LayoutRenderer_FieldElement } from './2-field-element'
5
10
  import { getElementGeneralizedProps } from '../../../../functions/forms/get-element-props'
@@ -24,13 +29,16 @@ import {
24
29
  ISelectElement,
25
30
  ISelectElementProps,
26
31
  } from '../../../../types'
32
+ import { useTranslation } from '../../../common/custom-hooks/use-translation.hook/hook'
27
33
 
28
34
  function LayoutRenderer_FieldsWithOptions({
29
35
  formContext,
30
36
  formItem,
31
37
  elementData,
32
38
  isDisabled,
39
+ elementKey,
33
40
  }: ILayoutRenderer_FieldsWithOptions) {
41
+ const { t } = useTranslation(formContext.formId)
34
42
  const navigate = useNavigate()
35
43
  const { getFormById } = useFindDynamiForm()
36
44
 
@@ -194,7 +202,12 @@ function LayoutRenderer_FieldsWithOptions({
194
202
  if (!props.optionSource) return
195
203
 
196
204
  if (props.optionSource.type === FieldElementOptionSourceEnum.Static) {
197
- setOptions(props.optionSource.options.map((op: IFormLayoutFieldOption) => ({ value: op.id, label: op.value })))
205
+ setOptions(
206
+ props.optionSource.options.map((op: IFormLayoutFieldOption) => ({
207
+ value: op.id,
208
+ label: t({ key: elementKey, type: TranslationTextTypeEnum.OptionValue, subType: op.id }),
209
+ })),
210
+ )
198
211
  setLoading(false)
199
212
  } else {
200
213
  switch (props.optionSource.type) {
@@ -213,7 +226,7 @@ function LayoutRenderer_FieldsWithOptions({
213
226
  return
214
227
  }
215
228
  }
216
- }, [props.optionSource, fetchLinkedFormData, fetchFormData, cachedConfig])
229
+ }, [props.optionSource, fetchLinkedFormData, fetchFormData, cachedConfig, elementKey])
217
230
 
218
231
  useEffect(() => {
219
232
  return () => {
@@ -223,14 +236,20 @@ function LayoutRenderer_FieldsWithOptions({
223
236
  }
224
237
  }, [])
225
238
 
239
+ const [label, placeholder] = t([
240
+ { key: elementData.key, type: TranslationTextTypeEnum.Label },
241
+ { key: elementData.key, type: TranslationTextTypeEnum.Placeholder },
242
+ ])
243
+
226
244
  return (
227
245
  <div className="relative">
228
- <LayoutRenderer_FieldElement formRef={formRef}>
246
+ <LayoutRenderer_FieldElement formRef={formRef} elementKey={elementData.key} formId={formContext.formId}>
229
247
  {() => ({
230
248
  ...props,
231
- label: props.label && (
249
+ placeholder,
250
+ label: label && (
232
251
  <div className="flex items-center gap-2 w-full">
233
- <span>{props.label}</span>
252
+ <span>{label}</span>
234
253
  {(props as ISelectElementProps).goToDetails?.enabled &&
235
254
  (props as ISelectElementProps).goToDetails?.text &&
236
255
  selectedValue && (
@@ -14,6 +14,7 @@ import {
14
14
  IFormRelationshipConfig,
15
15
  IFormRelationshipConfig_OneToMany,
16
16
  } from '../../../../types'
17
+ import { translationStore } from '../../../common/custom-hooks/use-translation.hook/store'
17
18
 
18
19
  function LayoutRenderer_LoadFormData({ formContext, elementProps }: ILayoutRenderer_LoadFormData) {
19
20
  const { formId, formRef, formDataId } = formContext
@@ -68,6 +69,12 @@ function LayoutRenderer_LoadFormData({ formContext, elementProps }: ILayoutRende
68
69
  [joins, baseFormId, initialFilter],
69
70
  )
70
71
 
72
+ useEffect(() => {
73
+ if (!cachedConfig?.id || !cachedConfig?.translations) return
74
+
75
+ translationStore.setTranslations(cachedConfig.id, cachedConfig.translations)
76
+ }, [cachedConfig])
77
+
71
78
  useEffect(() => {
72
79
  if (isNewFormDataPage(formDataId) || !cachedConfig) return
73
80
 
@@ -1,13 +1,14 @@
1
1
  import { Divider, Spin } from 'antd'
2
2
  import { lazy, memo, ReactElement, useMemo } from 'react'
3
3
  import { getElementGeneralizedProps } from '../../../../functions/forms/get-element-props'
4
- import { ElementTypeEnum, TextElementTypeEnum } from '../../../../enums'
4
+ import { ElementTypeEnum, TextElementTypeEnum, TranslationTextTypeEnum } from '../../../../enums'
5
5
  import useGetCurrentBreakpoint from '../../../common/custom-hooks/use-window-width.hook'
6
6
  import { findMaxAndMinValues, kebabCaseToCamelCase } from '../../../../functions/forms'
7
7
  import { IFormContext } from '../1-row'
8
8
  import { LayoutRenderer_FieldElement } from './2-field-element'
9
9
  import { IDynamicButton_DisplayStateProps } from './1-dynamic-button'
10
10
  import { ELEMENTS_DEFAULT_CLASS } from '../../../../constants'
11
+ import { useTranslation } from '../../../common/custom-hooks/use-translation.hook/hook'
11
12
  import {
12
13
  useIsNodeDisabled,
13
14
  useIsNodeHidden,
@@ -17,7 +18,6 @@ import {
17
18
  IFormDataLoadElementProps,
18
19
  IDndLayoutElement,
19
20
  IReadFieldDataElementProps,
20
- IBreadcrumbElementProps,
21
21
  IFileUploadElement,
22
22
  IReadFieldDataElement,
23
23
  ITextElement,
@@ -37,6 +37,7 @@ const LayoutRenderer_LoadFormData = lazy(() => import('./9-form-data-render'))
37
37
  const LayoutRenderer_CurrencyField = lazy(() => import('./10-currency'))
38
38
  const LayoutRenderer_Breadcrumb = lazy(() => import('./11-breadcrumb'))
39
39
  const LayoutRenderer_PickerField = lazy(() => import('./12-picker-field'))
40
+ const LayoutRenderer_LanguageSelector = lazy(() => import('./13-language-selector'))
40
41
 
41
42
  interface ILayoutRendererElement {
42
43
  basePath: (string | number)[]
@@ -46,18 +47,20 @@ interface ILayoutRendererElement {
46
47
  renderButton?: (props: IDynamicButton_DisplayStateProps) => ReactElement
47
48
  }
48
49
 
49
- function LayoutRendererElementWrapper({ ...restProps }: ILayoutRendererElement) {
50
- const isHidden = useIsNodeHidden(restProps.elementData.key)
50
+ function LayoutRendererElementWrapper(props: ILayoutRendererElement) {
51
+ const { t } = useTranslation(props.formContext.formId)
52
+ const isHidden = useIsNodeHidden(props.elementData.key)
51
53
 
54
+ const description = t({ key: props.elementData.key, type: TranslationTextTypeEnum.Description })
52
55
  return (
53
56
  <div
54
57
  className={`${ELEMENTS_DEFAULT_CLASS.ElementContainer} flex flex-col ${isHidden ? 'hidden' : ''}`}
55
- id={restProps.elementData.key}
58
+ id={props.elementData.key}
56
59
  >
57
- <LayoutRendererElement {...restProps} />
58
- {restProps.elementData.props && 'description' in restProps.elementData.props && (
60
+ <LayoutRendererElement {...props} />
61
+ {description && (
59
62
  <span className={`${ELEMENTS_DEFAULT_CLASS.ElementDescription} text-neutral text-12 italic`}>
60
- {restProps.elementData.props.description}
63
+ {description}
61
64
  </span>
62
65
  )}
63
66
  </div>
@@ -76,6 +79,7 @@ function LayoutRendererElement({
76
79
  const currentBreakpoint = useGetCurrentBreakpoint()
77
80
  const props = getElementGeneralizedProps(elementData.props)
78
81
  const { elementType, key, validations } = elementData
82
+ const { t } = useTranslation(formContext.formId)
79
83
  const { maxValue, minValue } = useMemo(() => findMaxAndMinValues(validations), [validations])
80
84
  const isElementDisabled = useIsNodeDisabled(key)
81
85
 
@@ -89,9 +93,17 @@ function LayoutRendererElement({
89
93
  }, [basePath, key])
90
94
 
91
95
  const elementBaseProps = useMemo(
92
- () => ({ formItem: { name: formItemName, path: fullPath }, isDisabled: isElementDisabled }),
93
- [formItemName, fullPath, isElementDisabled],
96
+ () => ({
97
+ formItem: { name: formItemName, path: fullPath },
98
+ isDisabled: isElementDisabled,
99
+ elementKey: key,
100
+ }),
101
+ [formItemName, fullPath, isElementDisabled, key],
94
102
  )
103
+ const [label, placeholder] = t([
104
+ { key, type: TranslationTextTypeEnum.Label },
105
+ { key, type: TranslationTextTypeEnum.Placeholder },
106
+ ])
95
107
 
96
108
  const renderers: Partial<Record<ElementTypeEnum, () => ReactElement>> = {
97
109
  [ElementTypeEnum.RichTextEditor]: () => {
@@ -140,12 +152,13 @@ function LayoutRendererElement({
140
152
  elementProps={props as IReadFieldDataElementProps}
141
153
  style={(elementData as IReadFieldDataElement).style?.[currentBreakpoint]}
142
154
  formContext={formContext}
155
+ {...elementBaseProps}
143
156
  />
144
157
  ),
145
158
 
146
159
  [ElementTypeEnum.Divider]: () => (
147
160
  <Divider className="m-0" orientation={props.labelPlacement}>
148
- {props.label && <span className="text-12">{props.label}</span>}
161
+ {label && <span className="text-12">{label}</span>}
149
162
  </Divider>
150
163
  ),
151
164
 
@@ -157,13 +170,13 @@ function LayoutRendererElement({
157
170
  if (props.type === TextElementTypeEnum.Plain)
158
171
  return (
159
172
  <div style={textStyle}>
160
- {props.label}{' '}
173
+ {label}{' '}
161
174
  {(typeof dataCount === 'number' || dataCount === 'pending') && (
162
175
  <>{dataCount === 'pending' ? <Spin size="small" /> : `(${dataCount})`}</>
163
176
  )}
164
177
  </div>
165
178
  )
166
- return <div dangerouslySetInnerHTML={{ __html: props.label }} style={textStyle} />
179
+ return <div dangerouslySetInnerHTML={{ __html: label }} style={textStyle} />
167
180
  },
168
181
 
169
182
  [ElementTypeEnum.Select]: () => (
@@ -198,7 +211,7 @@ function LayoutRendererElement({
198
211
  />
199
212
  ),
200
213
 
201
- [ElementTypeEnum.Breadcrumb]: () => <LayoutRenderer_Breadcrumb elementProps={props as IBreadcrumbElementProps} />,
214
+ [ElementTypeEnum.Breadcrumb]: () => <LayoutRenderer_Breadcrumb formId={formContext.formId} {...elementBaseProps} />,
202
215
 
203
216
  [ElementTypeEnum.Placeholder]: () => <div />,
204
217
 
@@ -225,15 +238,21 @@ function LayoutRendererElement({
225
238
  {...elementBaseProps}
226
239
  />
227
240
  ),
241
+
242
+ [ElementTypeEnum.LanguageSwitch]: () => (
243
+ <LayoutRenderer_LanguageSelector formId={formContext.formId} {...elementBaseProps} />
244
+ ),
228
245
  }
229
246
 
230
247
  const renderer = renderers[elementType]
231
248
  return renderer ? (
232
249
  renderer()
233
250
  ) : (
234
- <LayoutRenderer_FieldElement formRef={formContext.formRef}>
251
+ <LayoutRenderer_FieldElement formRef={formContext.formRef} elementKey={key} formId={formContext.formId}>
235
252
  {() => ({
236
253
  ...props,
254
+ label,
255
+ placeholder,
237
256
  numberFieldMax: maxValue,
238
257
  numberFieldMin: minValue,
239
258
  type: elementType,
@@ -250,4 +269,5 @@ function LayoutRendererElement({
250
269
  export interface IElementBaseProps {
251
270
  formItem: { name: string | (string | number)[]; path: string | (string | number)[] }
252
271
  isDisabled: boolean
272
+ elementKey: string
253
273
  }
package/src/constants.ts CHANGED
@@ -133,7 +133,7 @@ export const DEFAULT_CONFIG = {
133
133
  },
134
134
  siteIdentity: { logoUrl: '/favicon.png', iconUrl: '/favicon.png', title: 'Site Title' },
135
135
  siteMenus: [],
136
- siteLanguages: [{ value: 'en', label: 'English', defaultSelected: true }],
136
+ siteLanguages: [{ value: CountryEnum.US, label: 'English', defaultSelected: true }],
137
137
  loginLayout: {
138
138
  layout: 'center',
139
139
  backgroundType: 'color',
@@ -182,6 +182,7 @@ export const DEFAULT_FORM_SCHEMA_DATA: IFormSchema = {
182
182
  },
183
183
  generateConfig: { submissionPdf: { enabled: false } },
184
184
  relationships: [],
185
+ translations: {},
185
186
  }
186
187
  export const REACT_QUERY_CLIENT = new QueryClient({
187
188
  defaultOptions: {
@@ -36,6 +36,7 @@ export enum ElementTypeEnum {
36
36
  OrderedList = 'OrderedList',
37
37
  UnorderedList = 'UnorderedList',
38
38
  Image = 'Image',
39
+ LanguageSwitch = 'LanguageSwitch',
39
40
  }
40
41
  export enum TextElementTypeEnum {
41
42
  Plain = 'Plain',
@@ -63,6 +64,10 @@ export enum LOCAL_STORAGE_KEYS_ENUM {
63
64
  Breadcrumb = '83dead30755d4b2555a44a62c1de87a7',
64
65
  DisabledElements = '46ef33c492a6abccc4c89dcb485ffdf9',
65
66
  HiddenElements = 'c268df125295ca301609b3984d370c3e',
67
+ Languages = '63597792947dcaed2025e9b61aa57e88',
68
+ SelectedLanguage = 'b7cce86301b745f05631bf5c24524e23',
69
+ DefaultLanguage = '848ec029fe071913b5cfa0231493eff3',
70
+ FormTranslations = 'adeac05cba725ab79bf7075a66db6c05',
66
71
  }
67
72
  export enum CountryEnum {
68
73
  US = 'us',
@@ -109,3 +114,26 @@ export enum RelationalOperatorsEnum {
109
114
  In = '@in',
110
115
  NotIn = '@nin',
111
116
  }
117
+ export enum TranslationTextTypeEnum {
118
+ Label = 'L',
119
+ Placeholder = 'P',
120
+ Description = 'D',
121
+ ValidationError = 'VE',
122
+ OptionValue = 'OP',
123
+ }
124
+ export enum TranslationTextSubTypeEnum {
125
+ RepeatButtonAdd = 'RepeatAdd',
126
+ RepeatButtonRemove = 'RepeatRemove',
127
+ Secondary = '2',
128
+ UploadFileType = 'FileType',
129
+ UploadSizeLimit = 'SizeLimit',
130
+ BreadcrumbDetail = 'Detail',
131
+ BreadcrumbNew = 'New',
132
+ BreadcrumbList = 'List',
133
+ ConfirmationMsg = 'BtnConfMsg',
134
+ ConfirmationOk = 'BtnConfOk',
135
+ ConfirmationCancel = 'BtnConfCcl',
136
+ SuccessMsg = 'BtnSucMsg',
137
+ ErrorMsg = 'BtnErrMsg',
138
+ ButtonsGroup = 'BtnsGroup',
139
+ }