Skip to content

Commit

Permalink
feat: add model fields to represent translations for form fields (#7457)
Browse files Browse the repository at this point in the history
* feat: add fields in model to represent translations for form fields

* feat: add shared types to represent form field translations

* fix: use unicode locales
  • Loading branch information
siddarth2824 committed Oct 15, 2024
1 parent 5d167b0 commit 6dad473
Show file tree
Hide file tree
Showing 16 changed files with 212 additions and 5 deletions.
6 changes: 6 additions & 0 deletions __tests__/unit/backend/helpers/generate-form-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ export const generateDefaultField = (
fieldType,
required: true,
disabled: false,
titleTranslations: [],
descriptionTranslations: [],
}
switch (fieldType) {
case BasicField.Table:
Expand All @@ -77,6 +79,7 @@ export const generateDefaultField = (
return {
...defaultParams,
fieldOptions: ['Option 1', 'Option 2'],
fieldOptionsTranslations: [],
getQuestion: () => defaultParams.title,
ValidationOptions: {
customMin: null,
Expand Down Expand Up @@ -116,6 +119,7 @@ export const generateDefaultField = (
return {
...defaultParams,
fieldOptions: ['Option 1', 'Option 2'],
fieldOptionsTranslations: [],
getQuestion: () => defaultParams.title,
...customParams,
} as IDropdownFieldSchema
Expand Down Expand Up @@ -376,6 +380,7 @@ export const generateTableDropdownColumn = (
required: true,
_id: new ObjectId().toHexString(),
fieldOptions: ['a', 'b', 'c'],
fieldOptionsTranslations: [],
...customParams,
toObject() {
// mock toObject method of mongoose document
Expand All @@ -385,6 +390,7 @@ export const generateTableDropdownColumn = (
required: true,
_id: new ObjectId().toHexString(),
fieldOptions: ['a', 'b', 'c'],
fieldOptionsTranslations: [],
...customParams,
}
},
Expand Down
4 changes: 4 additions & 0 deletions shared/constants/form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ const PUBLIC_FORM_FIELDS = [
'form_logics',
'hasCaptcha',
'hasIssueNotification',
'hasMultiLang',
'startPage',
'status',
'supportedLanguages',
'title',
'_id',
'responseMode',
Expand Down Expand Up @@ -43,6 +45,8 @@ const FORM_SETTINGS_FIELDS = [
'submissionLimit',
'title',
'webhook',
'hasMultiLang',
'supportedLanguages',
] as const

export const EMAIL_FORM_SETTINGS_FIELDS = [
Expand Down
9 changes: 9 additions & 0 deletions shared/types/field/base.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Language } from '../form'

export enum BasicField {
Section = 'section',
Statement = 'statement',
Expand Down Expand Up @@ -96,13 +98,20 @@ export type VerifiableFieldBase = {
isVerifiable: boolean
}

export type TranslationMapping = {
language: Language
translation: string
}

export type FieldBase = {
globalId?: string
title: string
description: string
required: boolean
disabled: boolean
fieldType: BasicField
titleTranslations?: TranslationMapping[]
descriptionTranslations?: TranslationMapping[]
}

export type MyInfoableFieldBase = FieldBase & AllowMyInfoBase
2 changes: 2 additions & 0 deletions shared/types/field/checkboxField.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { TranslationOptionMapping } from '../form'
import { BasicField, FieldBase } from './base'

export type CheckboxValidationOptions = {
Expand All @@ -8,6 +9,7 @@ export type CheckboxValidationOptions = {
export interface CheckboxFieldBase extends FieldBase {
fieldType: BasicField.Checkbox
fieldOptions: string[]
fieldOptionsTranslations?: TranslationOptionMapping[]
othersRadioButton: boolean
ValidationOptions: CheckboxValidationOptions
validateByValue: boolean
Expand Down
2 changes: 2 additions & 0 deletions shared/types/field/dropdownField.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { TranslationOptionMapping } from '../form'
import { BasicField, MyInfoableFieldBase } from './base'

export interface DropdownFieldBase extends MyInfoableFieldBase {
fieldType: BasicField.Dropdown
fieldOptions: string[]
fieldOptionsTranslations?: TranslationOptionMapping[]
}
2 changes: 2 additions & 0 deletions shared/types/field/radioField.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { TranslationOptionMapping } from '../form'
import { BasicField, FieldBase } from './base'

export interface RadioFieldBase extends FieldBase {
fieldType: BasicField.Radio
fieldOptions: string[]
fieldOptionsTranslations?: TranslationOptionMapping[]
othersRadioButton: boolean
}
3 changes: 2 additions & 1 deletion shared/types/field/tableField.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import type { Merge } from 'type-fest'
import { FieldBase, BasicField } from './base'
import { FieldBase, BasicField, TranslationMapping } from './base'
import { DropdownFieldBase } from './dropdownField'
import { ShortTextFieldBase } from './shortTextField'

// Column types do not have most field base props.
type ColumnBase<T extends FieldBase> = Omit<T, keyof FieldBase> & {
title: string
required: boolean
titleTranslations?: TranslationMapping[]
}
export interface ShortTextColumnBase extends ColumnBase<ShortTextFieldBase> {
columnType: BasicField.ShortText
Expand Down
30 changes: 29 additions & 1 deletion shared/types/form/form.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { PublicUserDto, UserDto } from '../user'
import { FormField, FormFieldDto, MyInfoChildData } from '../field'
import {
FormField,
FormFieldDto,
MyInfoChildData,
TranslationMapping,
} from '../field'

import { FormLogo } from './form_logo'
import type { Merge, Tagged, PartialDeep } from 'type-fest'
Expand Down Expand Up @@ -36,11 +41,17 @@ export type FormPermission = {
write: boolean
}

export type TranslationOptionMapping = {
language: Language
translation: string[]
}

export type FormStartPage = {
logo: FormLogo
colorTheme: FormColorTheme
estTimeTaken?: number
paragraph?: string
paragraphTranslations?: TranslationMapping[]
}

export type FormEndPage = {
Expand All @@ -50,6 +61,8 @@ export type FormEndPage = {
buttonText: string
paymentTitle: string
paymentParagraph: string
titleTranslations?: TranslationMapping[]
paragraphTranslations?: TranslationMapping[]
}

export enum FormAuthType {
Expand All @@ -61,12 +74,24 @@ export enum FormAuthType {
SGID_MyInfo = 'SGID_MyInfo',
}

export enum Language {
ENGLISH = 'en-SG',
CHINESE = 'zh-SG',
MALAY = 'ms-SG',
TAMIL = 'ta-SG',
}

export enum FormStatus {
Private = 'PRIVATE',
Public = 'PUBLIC',
Archived = 'ARCHIVED',
}

export type FormSupportedLanguages = {
nextSupportedLanguages?: Language[]
selectedLanguage?: Language | null
}

export type FormWebhook = {
url: string
isRetryEnabled: boolean
Expand Down Expand Up @@ -164,6 +189,9 @@ export interface FormBase {
responseMode: FormResponseMode

goLinkSuffix?: string

hasMultiLang?: boolean
supportedLanguages?: Language[]
}

export interface EmailFormBase extends FormBase {
Expand Down
13 changes: 12 additions & 1 deletion src/app/models/__tests__/form.server.model.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const MOCK_ADMIN_EMAIL = `test@${MOCK_ADMIN_DOMAIN}`
const MOCK_FORM_PARAMS = {
title: 'Test Form',
admin: MOCK_ADMIN_OBJ_ID,
supportedLanguages: [],
}
const MOCK_ENCRYPTED_FORM_PARAMS = {
...MOCK_FORM_PARAMS,
Expand Down Expand Up @@ -84,12 +85,15 @@ const FORM_DEFAULTS = {
logo: {
state: FormLogoState.Default,
},
paragraphTranslations: [],
},
endPage: {
title: 'Thank you for filling out the form.',
buttonText: 'Submit another response',
paymentTitle: 'Thank you, your payment has been made successfully.',
paymentParagraph: 'Your form has been submitted and payment has been made.',
titleTranslations: [],
paragraphTranslations: [],
},
hasCaptcha: true,
hasIssueNotification: true,
Expand Down Expand Up @@ -1873,7 +1877,11 @@ describe('Form Model', () => {
expect(actual?.toObject()).toEqual({
...form,
lastModified: expect.any(Date),
endPage: { ...updatedEndPage },
endPage: {
...updatedEndPage,
paragraphTranslations: [],
titleTranslations: [],
},
})
})

Expand Down Expand Up @@ -1904,6 +1912,8 @@ describe('Form Model', () => {
paymentParagraph:
'Your form has been submitted and payment has been made.',
paymentTitle: 'Thank you, your payment has been made successfully.',
paragraphTranslations: [],
titleTranslations: [],
},
})
})
Expand Down Expand Up @@ -2167,6 +2177,7 @@ describe('Form Model', () => {
logo: {
state: FormLogoState.Default,
},
paragraphTranslations: [],
},
})
})
Expand Down
32 changes: 31 additions & 1 deletion src/app/models/field/baseField.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Schema } from 'mongoose'
import UIDGenerator from 'uid-generator'

import { BasicField, MyInfoAttribute } from '../../../../shared/types'
import { BasicField, Language, MyInfoAttribute } from '../../../../shared/types'
import { IFieldSchema, IMyInfoSchema, ITableFieldSchema } from '../../../types'

const uidgen3 = new UIDGenerator(256, UIDGenerator.BASE62)
Expand Down Expand Up @@ -45,6 +45,36 @@ export const BaseFieldSchema = new Schema<IFieldSchema>(
enum: Object.values(BasicField),
required: true,
},
titleTranslations: {
type: [
{
language: {
type: String,
enum: Object.values(Language),
},
translation: {
type: String,
},
},
],
default: [],
_id: false,
},
descriptionTranslations: {
type: [
{
language: {
type: String,
enum: Object.values(Language),
},
translation: {
type: String,
},
},
],
default: [],
_id: false,
},
},
{
discriminatorKey: 'fieldType',
Expand Down
13 changes: 13 additions & 0 deletions src/app/models/field/checkboxField.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Schema } from 'mongoose'
import { Language } from 'shared/types'

import { ICheckboxFieldSchema } from '../../../types'

Expand All @@ -13,6 +14,18 @@ const createCheckboxFieldSchema = () => {
message: 'Please ensure that there are no duplicate checkbox options.',
},
},
fieldOptionsTranslations: {
type: [
{
language: {
type: String,
enum: Object.values(Language),
},
translation: [String],
},
],
default: [],
},
othersRadioButton: {
type: Boolean,
default: false,
Expand Down
13 changes: 13 additions & 0 deletions src/app/models/field/dropdownField.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Schema } from 'mongoose'
import { Language } from 'shared/types'

import { IDropdownFieldSchema } from '../../../types'

Expand All @@ -7,6 +8,18 @@ import { MyInfoSchema } from './baseField'
const createDropdownFieldSchema = () => {
return new Schema<IDropdownFieldSchema>({
fieldOptions: [String],
fieldOptionsTranslations: {
type: [
{
language: {
type: String,
enum: Object.values(Language),
},
translation: [String],
},
],
default: [],
},
myInfo: MyInfoSchema,
})
}
Expand Down
13 changes: 13 additions & 0 deletions src/app/models/field/radioField.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
import { Schema } from 'mongoose'
import { Language } from 'shared/types'

import { IRadioFieldSchema } from '../../../types'

const createRadioFieldSchema = () => {
return new Schema<IRadioFieldSchema>({
fieldOptions: [String],
fieldOptionsTranslations: {
type: [
{
language: {
type: String,
enum: Object.values(Language),
},
translation: [String],
},
],
default: [],
},
othersRadioButton: {
type: Boolean,
default: false,
Expand Down
Loading

0 comments on commit 6dad473

Please sign in to comment.