diff --git a/frontend/src/components/Calendar/CalendarBase/CalendarTodayButton.tsx b/frontend/src/components/Calendar/CalendarBase/CalendarTodayButton.tsx
index 2e25ac4a4e..17ec57815b 100644
--- a/frontend/src/components/Calendar/CalendarBase/CalendarTodayButton.tsx
+++ b/frontend/src/components/Calendar/CalendarBase/CalendarTodayButton.tsx
@@ -1,3 +1,4 @@
+import { useTranslation } from 'react-i18next'
import { Box } from '@chakra-ui/react'
import Button from '~components/Button'
@@ -8,16 +9,20 @@ import { useCalendarStyles } from './CalendarStyleProvider'
export const CalendarTodayButton = (): JSX.Element => {
const styles = useCalendarStyles()
const { handleTodayClick, colorScheme } = useCalendar()
+ const { t } = useTranslation('translation', {
+ keyPrefix: 'features.publicForm.components.fields.calendar',
+ })
+
return (
)
diff --git a/frontend/src/components/DatePicker/DatePickerContext.tsx b/frontend/src/components/DatePicker/DatePickerContext.tsx
index 03ccd5165f..4652aa1736 100644
--- a/frontend/src/components/DatePicker/DatePickerContext.tsx
+++ b/frontend/src/components/DatePicker/DatePickerContext.tsx
@@ -10,6 +10,7 @@ import React, {
useMemo,
useRef,
} from 'react'
+import { useTranslation } from 'react-i18next'
import {
CSSObject,
FormControlProps,
@@ -101,6 +102,9 @@ const useProvideDatePicker = ({
const inputRef = useRef(null)
const isMobile = useIsMobile()
+ const { t } = useTranslation('translation', {
+ keyPrefix: 'features.publicForm.components.fields.datePicker',
+ })
const disclosureProps = useDisclosure({
onClose: () => {
@@ -161,16 +165,18 @@ const useProvideDatePicker = ({
)
const calendarButtonAria = useMemo(() => {
- let ariaLabel = 'Select from date picker. '
+ let ariaLabel = t('selectFromDatePicker')
if (internalValue) {
if (isValid(internalValue)) {
- ariaLabel += `Selected date is ${internalValue.toLocaleDateString()}.`
+ ariaLabel += t('selectedDateIs', {
+ date: internalValue.toLocaleDateString(),
+ })
} else {
- ariaLabel += 'The current selected date is invalid.'
+ ariaLabel += t('invalidDate')
}
}
return ariaLabel
- }, [internalValue])
+ }, [internalValue, t])
const handleDateChange = useCallback(
(date: Date | null) => {
diff --git a/frontend/src/components/Dropdown/MultiSelect/MultiSelectProvider.tsx b/frontend/src/components/Dropdown/MultiSelect/MultiSelectProvider.tsx
index fc8473ad51..36f59994dd 100644
--- a/frontend/src/components/Dropdown/MultiSelect/MultiSelectProvider.tsx
+++ b/frontend/src/components/Dropdown/MultiSelect/MultiSelectProvider.tsx
@@ -1,4 +1,5 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
+import { useTranslation } from 'react-i18next'
import { VirtuosoHandle } from 'react-virtuoso'
import {
FormControlOptions,
@@ -73,9 +74,9 @@ export const MultiSelectProvider = ({
onBlur,
name,
filter = defaultFilter,
- nothingFoundLabel = 'No matching results',
+ nothingFoundLabel,
placeholder: placeholderProp,
- clearButtonLabel = 'Clear selection',
+ clearButtonLabel,
isSearchable = true,
defaultIsOpen,
isInvalid: isInvalidProp,
@@ -92,6 +93,7 @@ export const MultiSelectProvider = ({
}: MultiSelectProviderProps): JSX.Element => {
const { items, getItemByValue } = useItems({ rawItems })
const [isFocused, setIsFocused] = useState(false)
+ const { t } = useTranslation()
// Inject for components to manipulate
const inputRef = useRef(null)
@@ -171,8 +173,19 @@ export const MultiSelectProvider = ({
const dynamicPlaceholder = useMemo(() => {
if (placeholderProp === null || selectedItems.length > 0) return ''
- return placeholderProp ?? 'Select options'
- }, [placeholderProp, selectedItems.length])
+ return (
+ placeholderProp ??
+ t('features.publicForm.components.fields.dropdown.selectOptions')
+ )
+ }, [placeholderProp, selectedItems.length, t])
+
+ const nothingFoundLabelTranslated =
+ nothingFoundLabel ??
+ t('features.publicForm.components.fields.dropdown.nothingFound')
+
+ const clearButtonLabelTranslated =
+ clearButtonLabel ??
+ t('features.publicForm.components.fields.dropdown.clearSelection')
const {
toggleMenu,
@@ -313,11 +326,11 @@ export const MultiSelectProvider = ({
selectItem,
highlightedIndex,
items: filteredItems,
- nothingFoundLabel,
+ nothingFoundLabel: nothingFoundLabelTranslated,
inputValue,
isSearchable,
name,
- clearButtonLabel,
+ clearButtonLabel: clearButtonLabelTranslated,
placeholder: dynamicPlaceholder,
styles,
isFocused,
diff --git a/frontend/src/components/Dropdown/SingleSelect/SingleSelectProvider.tsx b/frontend/src/components/Dropdown/SingleSelect/SingleSelectProvider.tsx
index 5746e64cba..3404f077d9 100644
--- a/frontend/src/components/Dropdown/SingleSelect/SingleSelectProvider.tsx
+++ b/frontend/src/components/Dropdown/SingleSelect/SingleSelectProvider.tsx
@@ -53,7 +53,7 @@ export const SingleSelectProvider = ({
name,
filter = defaultFilter,
placeholder: placeholderProp,
- clearButtonLabel = 'Clear selection',
+ clearButtonLabel,
isClearable = true,
isSearchable = true,
initialIsOpen,
@@ -94,6 +94,10 @@ export const SingleSelectProvider = ({
'features.publicForm.components.fields.dropdown.nothingFound',
)
+ const clearButtonLabelTranslated =
+ clearButtonLabel ??
+ t('features.publicForm.components.fields.dropdown.clearSelection')
+
const getFilteredItems = useCallback(
(filterValue?: string) =>
filterValue ? filter(items, filterValue) : items,
@@ -274,7 +278,7 @@ export const SingleSelectProvider = ({
isReadOnly,
isRequired,
name,
- clearButtonLabel,
+ clearButtonLabel: clearButtonLabelTranslated,
placeholder,
styles,
isFocused,
diff --git a/frontend/src/features/admin-form/common/components/PreviewFormBanner/PreviewFormBanner.tsx b/frontend/src/features/admin-form/common/components/PreviewFormBanner/PreviewFormBanner.tsx
index 755b65c081..d37057d6cf 100644
--- a/frontend/src/features/admin-form/common/components/PreviewFormBanner/PreviewFormBanner.tsx
+++ b/frontend/src/features/admin-form/common/components/PreviewFormBanner/PreviewFormBanner.tsx
@@ -57,7 +57,10 @@ const textProps: TextProps = {
export const PreviewFormBanner = ({
isTemplate,
}: PreviewFormBannerProps): JSX.Element => {
- const { t } = useTranslation()
+ const { t } = useTranslation('translation', {
+ keyPrefix: 'features.adminForm.previewFormBanner',
+ })
+ const { t: tCommon } = useTranslation()
const { formId, isPaymentEnabled } = usePublicFormContext()
const { data: { secretEnv } = {} } = useEnv()
const {
@@ -99,7 +102,7 @@ export const PreviewFormBanner = ({
mr={{ base: '0.5rem', md: '1rem' }}
/>
- {isTemplate ? 'Template Preview' : 'Form Preview'}
+ {isTemplate ? t('templatePreview') : t('formPreview')}
{isTemplate ? (
@@ -111,35 +114,35 @@ export const PreviewFormBanner = ({
>
- Back to FormSG
+ {t('backToFormSG')}
}
/>
>
) : (
)}
@@ -165,7 +168,7 @@ export const PreviewFormBanner = ({
isFullWidth={true}
{...mobileDrawerButtonProps}
>
- Use this template
+ {t('useTemplate')}
}
{...mobileDrawerButtonProps}
>
- Back to FormSG
+ {t('backToFormSG')}
@@ -184,27 +187,20 @@ export const PreviewFormBanner = ({
{secretEnv === 'production' ? (
- To test your payment form, replicate this form on our{' '}
+ {t('paymentWarning.production.prefix')}
- testing platform.
+ {t('paymentWarning.production.linkText')}
) : (
-
- You will not be able to make a test payment, or view submitted
- answers or attachments in Form Preview mode. Open your form to
- make a test payment or form submission.
-
+ {t('paymentWarning.nonProduction')}
)}
)}
{!isPaymentEnabled && (
{!(secretEnv === 'production') && (
-
- You will not be able to view submitted answers or attachments in
- Form Preview mode. Open your form to test a form submission.
-
+ {t('previewWarning.withoutPayment')}
)}
)}
diff --git a/frontend/src/features/admin-form/create/builder-and-design/MagicFormBuilder/components/MagicFormBuilderAcceptDeny.tsx b/frontend/src/features/admin-form/create/builder-and-design/MagicFormBuilder/components/MagicFormBuilderAcceptDeny.tsx
index fde66a59d1..2de2a021c9 100644
--- a/frontend/src/features/admin-form/create/builder-and-design/MagicFormBuilder/components/MagicFormBuilderAcceptDeny.tsx
+++ b/frontend/src/features/admin-form/create/builder-and-design/MagicFormBuilder/components/MagicFormBuilderAcceptDeny.tsx
@@ -1,3 +1,4 @@
+import { useTranslation } from 'react-i18next'
import { Flex, Portal, Text } from '@chakra-ui/react'
import { NextAndBackButtonGroup } from '~components/Button'
@@ -12,20 +13,22 @@ const MagicFormBuilderAcceptDeny = ({
onAccept: () => void
onDeny: () => void
}) => {
+ const { t } = useTranslation('translation', {
+ keyPrefix: 'features.adminForm.magicFormBuilder.acceptDeny',
+ })
+
return isOpen ? (
-
- The created fields have been saved.
-
- Keep them?
+
+ {t('message')}
diff --git a/frontend/src/features/admin-form/create/builder-and-design/MagicFormBuilder/components/MagicFormBuilderButton.tsx b/frontend/src/features/admin-form/create/builder-and-design/MagicFormBuilder/components/MagicFormBuilderButton.tsx
index e5ac5a0f6a..3efb43a0b6 100644
--- a/frontend/src/features/admin-form/create/builder-and-design/MagicFormBuilder/components/MagicFormBuilderButton.tsx
+++ b/frontend/src/features/admin-form/create/builder-and-design/MagicFormBuilder/components/MagicFormBuilderButton.tsx
@@ -1,3 +1,4 @@
+import { useTranslation } from 'react-i18next'
import { BiSolidMagicWand } from 'react-icons/bi'
import Button from '~components/Button'
@@ -7,6 +8,10 @@ const MagicFormBuilderButton = ({
}: {
onClick: () => void
}): JSX.Element => {
+ const { t } = useTranslation('translation', {
+ keyPrefix: 'features.adminForm.magicFormBuilder.button',
+ })
+
return (
)
}
diff --git a/frontend/src/features/admin-form/create/builder-and-design/MagicFormBuilder/components/MagicFormBuilderPromptModal.tsx b/frontend/src/features/admin-form/create/builder-and-design/MagicFormBuilder/components/MagicFormBuilderPromptModal.tsx
index db3df4aaee..84b774b643 100644
--- a/frontend/src/features/admin-form/create/builder-and-design/MagicFormBuilder/components/MagicFormBuilderPromptModal.tsx
+++ b/frontend/src/features/admin-form/create/builder-and-design/MagicFormBuilder/components/MagicFormBuilderPromptModal.tsx
@@ -9,6 +9,7 @@ import {
UseFormSetError,
UseFormSetValue,
} from 'react-hook-form'
+import { useTranslation } from 'react-i18next'
import { BiSolidMagicWand } from 'react-icons/bi'
import {
Box,
@@ -75,9 +76,13 @@ const PromptSelectorBar = ({
}[]
onClick: (prompt: string) => void
}) => {
+ const { t } = useTranslation('translation', {
+ keyPrefix: 'features.adminForm.magicFormBuilder.promptModal.textTab',
+ })
+
return (
- Need inspiration? Try one of these:
+ {t('inspirationLabel')}
errors: FieldErrors
}) => {
+ const { t } = useTranslation('translation', {
+ keyPrefix: 'features.adminForm.magicFormBuilder.promptModal.textTab',
+ })
+
return (
<>
-
- I want to create a form that collects...
-
+ {t('promptLabel')}
@@ -174,16 +178,18 @@ const VisionPromptModalBodyContent = ({
setError: UseFormSetError
isVisionPromptSubmitLoading: boolean
}) => {
+ const { t } = useTranslation('translation', {
+ keyPrefix: 'features.adminForm.magicFormBuilder.promptModal.pdfTab',
+ })
+
return (
<>
-
- Create a form based on this pdf
-
+ {t('uploadLabel')}
(
setError('attachment', { message })}
@@ -224,6 +232,13 @@ const MagicFormBuilderCreateFormPrompt = ({
isVisionPromptSubmitLoading: MagicFormBuilderPromptModalProps['isVisionPromptSubmitLoading']
onCancel: () => void
}) => {
+ const { t } = useTranslation('translation', {
+ keyPrefix: 'features.adminForm.magicFormBuilder.promptModal',
+ })
+ const { t: tPdf } = useTranslation('translation', {
+ keyPrefix: 'features.adminForm.magicFormBuilder.promptModal.pdfTab',
+ })
+
const {
register,
handleSubmit,
@@ -251,7 +266,7 @@ const MagicFormBuilderCreateFormPrompt = ({
return (
<>
- Create fields with AI
+ {t('header')}
<>
@@ -262,13 +277,13 @@ const MagicFormBuilderCreateFormPrompt = ({
isDisabled={isVisionPromptSubmitLoading}
value={PROMPT_TYPE.TEXT}
>
- Text
+ {t('tabs.text')}
- Pdf
+ {t('tabs.pdf')}
@@ -324,7 +339,7 @@ const MagicFormBuilderCreateFormPrompt = ({
if (imageDataUrls.length > MFB_VISION_MAX_IMAGES_COUNT) {
setVisionError('attachment', {
type: 'manual',
- message: `Your pdf file must have less than or equal ${MFB_VISION_MAX_IMAGES_COUNT} pages.`,
+ message: tPdf('conversionError.fileSizeTooLarge'),
})
return
}
@@ -333,7 +348,7 @@ const MagicFormBuilderCreateFormPrompt = ({
} catch (error) {
setVisionError('attachment', {
type: 'manual',
- message: 'Failed to convert PDF file to images.',
+ message: tPdf('conversionError.unknown'),
})
}
})
@@ -345,8 +360,8 @@ const MagicFormBuilderCreateFormPrompt = ({
isTextPromptSubmitLoading || isVisionPromptSubmitLoading
}
handleBack={onCancel}
- nextButtonLabel="Create fields"
- backButtonLabel="Cancel"
+ nextButtonLabel={t('actions.create')}
+ backButtonLabel={t('actions.cancel')}
/>
>
diff --git a/frontend/src/features/admin-form/create/builder-and-design/MagicFormBuilder/components/MagicFormBuilderSmallButton.tsx b/frontend/src/features/admin-form/create/builder-and-design/MagicFormBuilder/components/MagicFormBuilderSmallButton.tsx
index 4bb697db4b..752a4ef34a 100644
--- a/frontend/src/features/admin-form/create/builder-and-design/MagicFormBuilder/components/MagicFormBuilderSmallButton.tsx
+++ b/frontend/src/features/admin-form/create/builder-and-design/MagicFormBuilder/components/MagicFormBuilderSmallButton.tsx
@@ -1,3 +1,4 @@
+import { useTranslation } from 'react-i18next'
import { BiSolidMagicWand } from 'react-icons/bi'
import { Button, Icon, Tooltip } from '@chakra-ui/react'
@@ -8,8 +9,12 @@ const MagicFormBuilderSmallButton = ({
}: { isActive: boolean; onClick: () => void } & React.ComponentProps<
typeof Button
>) => {
+ const { t } = useTranslation('translation', {
+ keyPrefix: 'features.adminForm.magicFormBuilder.smallButton',
+ })
+
return (
-
+
diff --git a/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/ActiveStepBlock/ActiveStepBlock.tsx b/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/ActiveStepBlock/ActiveStepBlock.tsx
index 4abee2c60f..cb5d07dbba 100644
--- a/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/ActiveStepBlock/ActiveStepBlock.tsx
+++ b/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/ActiveStepBlock/ActiveStepBlock.tsx
@@ -1,4 +1,5 @@
import { useCallback } from 'react'
+import { useTranslation } from 'react-i18next'
import { FormWorkflowStep, FormWorkflowStepDto } from '~shared/types'
@@ -43,6 +44,9 @@ export const ActiveStepBlock = ({
step,
handleOpenDeleteModal,
}: ActiveStepBlockProps): JSX.Element => {
+ const { t } = useTranslation('translation', {
+ keyPrefix: 'features.adminForm.sidebar.workflow',
+ })
const { updateStepMutation } = useWorkflowMutations()
const setToInactive = useAdminWorkflowStore(setToInactiveSelector)
@@ -69,7 +73,7 @@ export const ActiveStepBlock = ({
handleOpenDeleteModal={handleOpenDeleteModal}
onSubmit={handleSubmit}
defaultValues={step}
- submitButtonLabel="Save step"
+ submitButtonLabel={t('saveStep')}
/>
)
}
diff --git a/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/RespondentBlock/Components/StaticRespondentOption.tsx b/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/RespondentBlock/Components/StaticRespondentOption.tsx
index 774caa7d9f..52c68bc702 100644
--- a/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/RespondentBlock/Components/StaticRespondentOption.tsx
+++ b/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/RespondentBlock/Components/StaticRespondentOption.tsx
@@ -1,4 +1,5 @@
import { Controller } from 'react-hook-form'
+import { useTranslation } from 'react-i18next'
import { FormControl, Text } from '@chakra-ui/react'
import { get } from 'lodash'
import isEmail from 'validator/lib/isEmail'
@@ -17,6 +18,9 @@ export const StaticRespondentOption = ({
formMethods,
selectedWorkflowType,
}: RespondentOptionProps) => {
+ const { t } = useTranslation('translation', {
+ keyPrefix: 'features.adminForm.sidebar.workflow.staticRespondent',
+ })
const {
register,
control,
@@ -40,7 +44,7 @@ export const StaticRespondentOption = ({
},
}}
>
- Specific email(s)
+ {t('title')}
{selectedWorkflowType === WorkflowType.Static ? (
!emails || emails.length === 0
- ? 'You must enter at least one email to receive responses'
+ ? t('validation.required')
: true,
isEmails: (emails) =>
!emails ||
emails.every((email) => isEmail(email)) ||
- 'Please enter valid email(s) (e.g. me@example.com) separated by commas, as invalid emails will not be saved',
+ t('validation.invalidEmails'),
},
}}
render={({ field }) => (
@@ -77,7 +81,7 @@ export const StaticRespondentOption = ({
{staticTagInputErrorMessage}
{!staticTagInputErrorMessage ? (
- Separate multiple emails with a comma
+ {t('helperText')}
) : null}
diff --git a/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/InactiveStepBlock/InactiveStepBlock.tsx b/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/InactiveStepBlock/InactiveStepBlock.tsx
index 9d65d101a1..a296bded0d 100644
--- a/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/InactiveStepBlock/InactiveStepBlock.tsx
+++ b/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/InactiveStepBlock/InactiveStepBlock.tsx
@@ -33,11 +33,13 @@ interface InactiveStepBlockProps {
interface RespondentBadgeProps {
step: FormWorkflowStepDto
idToFieldMap: Dictionary>
+ t: (key: string) => string
}
const SubsequentStepRespondentBadges = ({
step,
idToFieldMap,
+ t,
}: RespondentBadgeProps): JSX.Element => {
switch (step.workflow_type) {
case WorkflowType.Static:
@@ -80,7 +82,9 @@ const SubsequentStepRespondentBadges = ({
) : null}
@@ -122,7 +126,9 @@ export const InactiveStepBlock = ({
)
@@ -135,8 +141,9 @@ export const InactiveStepBlock = ({
)
@@ -148,11 +155,13 @@ export const InactiveStepBlock = ({
field={idToFieldMap[fieldId]}
defaults={{
variant: 'info',
- message: 'This field was deleted, please select another field',
+ message: t(
+ 'features.adminForm.sidebar.workflow.fieldBadges.fieldDeleted',
+ ),
}}
/>
))
- }, [idToFieldMap, step.edit])
+ }, [idToFieldMap, step.edit, t])
return (
@@ -195,6 +204,7 @@ export const InactiveStepBlock = ({
)}
diff --git a/frontend/src/i18n/locales/features/admin-form/en-sg.ts b/frontend/src/i18n/locales/features/admin-form/en-sg.ts
index 0b4e8df75e..17dc0f3ee9 100644
--- a/frontend/src/i18n/locales/features/admin-form/en-sg.ts
+++ b/frontend/src/i18n/locales/features/admin-form/en-sg.ts
@@ -3,14 +3,17 @@ import { enSG as responsesComponents } from './responses/components'
import { enSG as responsesIndividualResponse } from './responses/individual-response'
import { enSG as responsesResponsesPage } from './responses/responses-page'
import { enSG as feedback } from './feedback'
+import { enSG as magicFormBuilder } from './magic-form-builder'
import { enSG as meta } from './meta'
import { enSG as modals } from './modals'
import { enSG as navbar } from './navbar'
+import { enSG as previewFormBanner } from './preview-form-banner'
import { enSG as settings } from './settings'
import { enSG as sidebar } from './sidebar'
import { enSG as toasts } from './toasts'
export const enSG = {
+ previewFormBanner,
responses: {
charts: responsesCharts,
components: responsesComponents,
@@ -24,4 +27,5 @@ export const enSG = {
toasts,
settings,
feedback,
+ magicFormBuilder,
}
diff --git a/frontend/src/i18n/locales/features/admin-form/index.ts b/frontend/src/i18n/locales/features/admin-form/index.ts
index 9a6698fdcc..d12f16045c 100644
--- a/frontend/src/i18n/locales/features/admin-form/index.ts
+++ b/frontend/src/i18n/locales/features/admin-form/index.ts
@@ -1,8 +1,10 @@
export * from './en-sg'
export { type Feedback } from './feedback'
+export { type MagicFormBuilder } from './magic-form-builder'
export { type Meta } from './meta'
export { type Modals } from './modals'
export { type Navbar } from './navbar'
+export { type PreviewFormBanner } from './preview-form-banner'
export { type ResponsesCharts } from './responses/charts'
export { type ResponsesComponents } from './responses/components'
export { type ResponsesIndividualResponse } from './responses/individual-response'
diff --git a/frontend/src/i18n/locales/features/admin-form/magic-form-builder/en-sg.ts b/frontend/src/i18n/locales/features/admin-form/magic-form-builder/en-sg.ts
new file mode 100644
index 0000000000..c425944144
--- /dev/null
+++ b/frontend/src/i18n/locales/features/admin-form/magic-form-builder/en-sg.ts
@@ -0,0 +1,55 @@
+import { MagicFormBuilder } from '.'
+
+export const enSG: MagicFormBuilder = {
+ acceptDeny: {
+ message: 'The created fields have been saved.\nKeep them?',
+ keepButton: 'Yes, keep them',
+ deleteButton: 'No, delete them',
+ },
+ button: {
+ createFields: 'Create fields with AI',
+ },
+ smallButton: {
+ tooltip: 'Create fields with AI',
+ },
+ promptModal: {
+ header: 'Create fields with AI',
+ tabs: {
+ text: 'Text',
+ pdf: 'Pdf',
+ },
+ textTab: {
+ inspirationLabel: 'Need inspiration? Try one of these:',
+ promptLabel: 'I want to create a form that collects...',
+ promptPlaceholder:
+ 'Describe your form, including fields and sections to create',
+ validation: {
+ required: 'Please enter a prompt',
+ maxLength: 'Please enter at most 500 characters',
+ },
+ },
+ pdfTab: {
+ uploadLabel: 'Create a form based on this pdf',
+ uploadError: 'Please upload a pdf file',
+ fileConstraints: 'Files should not be more than {maxPages} pages long.',
+ conversionError: {
+ unknown:
+ 'We encountered an unknown error while converting your PDF. Please try again with a different PDF.',
+ timeout:
+ 'We encountered a timeout while converting your PDF. Please try again with a smaller PDF.',
+ fileSizeTooLarge:
+ 'Your PDF is too large. Please try again with a smaller PDF.',
+ invalidInputPdf:
+ 'We encountered an error while reading your PDF. Please ensure your PDF is valid and try again.',
+ pdfEncrypted:
+ 'Your PDF is encrypted. Please remove the encryption and try again.',
+ pdfHasNoText:
+ 'Your PDF does not contain any text. Please try again with a PDF that contains text.',
+ },
+ },
+ actions: {
+ create: 'Create fields',
+ cancel: 'Cancel',
+ },
+ },
+}
diff --git a/frontend/src/i18n/locales/features/admin-form/magic-form-builder/index.ts b/frontend/src/i18n/locales/features/admin-form/magic-form-builder/index.ts
new file mode 100644
index 0000000000..c9b76590cb
--- /dev/null
+++ b/frontend/src/i18n/locales/features/admin-form/magic-form-builder/index.ts
@@ -0,0 +1,48 @@
+export * from './en-sg'
+
+export interface MagicFormBuilder {
+ acceptDeny: {
+ message: string
+ keepButton: string
+ deleteButton: string
+ }
+ button: {
+ createFields: string
+ }
+ smallButton: {
+ tooltip: string
+ }
+ promptModal: {
+ header: string
+ tabs: {
+ text: string
+ pdf: string
+ }
+ textTab: {
+ inspirationLabel: string
+ promptLabel: string
+ promptPlaceholder: string
+ validation: {
+ required: string
+ maxLength: string
+ }
+ }
+ pdfTab: {
+ uploadLabel: string
+ uploadError: string
+ fileConstraints: string
+ conversionError: {
+ unknown: string
+ timeout: string
+ fileSizeTooLarge: string
+ invalidInputPdf: string
+ pdfEncrypted: string
+ pdfHasNoText: string
+ }
+ }
+ actions: {
+ create: string
+ cancel: string
+ }
+ }
+}
diff --git a/frontend/src/i18n/locales/features/admin-form/preview-form-banner/en-sg.ts b/frontend/src/i18n/locales/features/admin-form/preview-form-banner/en-sg.ts
new file mode 100644
index 0000000000..43dc934a86
--- /dev/null
+++ b/frontend/src/i18n/locales/features/admin-form/preview-form-banner/en-sg.ts
@@ -0,0 +1,23 @@
+import { PreviewFormBanner } from '.'
+
+export const enSG: PreviewFormBanner = {
+ templatePreview: 'Template Preview',
+ formPreview: 'Form Preview',
+ backToDashboardAriaLabel: 'Click to return to the admin dashboard',
+ backToFormSG: 'Back to FormSG',
+ useTemplateAriaLabel: 'Click to use this template',
+ useTemplate: 'Use this template',
+ templateActionsAriaLabel: 'Template preview actions',
+ paymentWarning: {
+ production: {
+ prefix: 'To test your payment form, replicate this form on our ',
+ linkText: 'testing platform.',
+ },
+ nonProduction:
+ 'You will not be able to make a test payment, or view submitted answers or attachments in Form Preview mode. Open your form to make a test payment or form submission.',
+ },
+ previewWarning: {
+ withoutPayment:
+ 'You will not be able to view submitted answers or attachments in Form Preview mode. Open your form to test a form submission.',
+ },
+}
diff --git a/frontend/src/i18n/locales/features/admin-form/preview-form-banner/index.ts b/frontend/src/i18n/locales/features/admin-form/preview-form-banner/index.ts
new file mode 100644
index 0000000000..533687e636
--- /dev/null
+++ b/frontend/src/i18n/locales/features/admin-form/preview-form-banner/index.ts
@@ -0,0 +1,21 @@
+export interface PreviewFormBanner {
+ templatePreview: string
+ formPreview: string
+ backToDashboardAriaLabel: string
+ backToFormSG: string
+ useTemplateAriaLabel: string
+ useTemplate: string
+ templateActionsAriaLabel: string
+ paymentWarning: {
+ production: {
+ prefix: string
+ linkText: string
+ }
+ nonProduction: string
+ }
+ previewWarning: {
+ withoutPayment: string
+ }
+}
+
+export * from './en-sg'
diff --git a/frontend/src/i18n/locales/features/admin-form/sidebar/workflow/en-sg.ts b/frontend/src/i18n/locales/features/admin-form/sidebar/workflow/en-sg.ts
index 42fa9f5a54..d4a5d3256b 100644
--- a/frontend/src/i18n/locales/features/admin-form/sidebar/workflow/en-sg.ts
+++ b/frontend/src/i18n/locales/features/admin-form/sidebar/workflow/en-sg.ts
@@ -10,6 +10,15 @@ import { Workflow } from '.'
export const enSG: Workflow = {
title: 'Add workflow',
+ saveStep: 'Save step',
+ emptyWorkflow: {
+ title:
+ 'Create a workflow to collect responses from multiple respondents in the same form submission',
+ description:
+ 'Assign respondents to specific steps, and control which fields they can fill.',
+ learnMore: 'Learn how to create a workflow',
+ createButton: 'Create workflow',
+ },
respondentBlock: {
stepRespondent: 'Respondent in this step',
anyone: 'Anyone who has access to your form',
@@ -17,6 +26,23 @@ export const enSG: Workflow = {
fieldsToFill: 'Fields to fill',
clickToEdit: 'Click to edit',
},
+ staticRespondent: {
+ title: 'Specific email(s)',
+ placeholder: 'me@example.com',
+ helperText: 'Separate multiple emails with a comma',
+ validation: {
+ required: 'You must enter at least one email to receive responses',
+ invalidEmails:
+ 'Please enter valid email(s) (e.g. me@example.com) separated by commas, as invalid emails will not be saved',
+ },
+ },
+ fieldBadges: {
+ updateCsvOptionsEmails: 'Please update your CSV options and emails',
+ noFieldsSelected: 'No fields selected',
+ allFieldsDeleted:
+ 'All fields were deleted, please select at least one field',
+ fieldDeleted: 'This field was deleted, please select another field',
+ },
dynamicRespondent: {
title: 'An email field from the form',
required: 'Please select a field.',
diff --git a/frontend/src/i18n/locales/features/admin-form/sidebar/workflow/index.ts b/frontend/src/i18n/locales/features/admin-form/sidebar/workflow/index.ts
index 4055cb3da7..33a75e2f1f 100644
--- a/frontend/src/i18n/locales/features/admin-form/sidebar/workflow/index.ts
+++ b/frontend/src/i18n/locales/features/admin-form/sidebar/workflow/index.ts
@@ -8,6 +8,13 @@ interface CsvColumnText {
export interface Workflow {
title: string
+ saveStep: string
+ emptyWorkflow: {
+ title: string
+ description: string
+ learnMore: string
+ createButton: string
+ }
respondentBlock: {
stepRespondent: string
anyone: string
@@ -15,6 +22,21 @@ export interface Workflow {
fieldsToFill: string
clickToEdit: string
}
+ staticRespondent: {
+ title: string
+ placeholder: string
+ helperText: string
+ validation: {
+ required: string
+ invalidEmails: string
+ }
+ }
+ fieldBadges: {
+ updateCsvOptionsEmails: string
+ noFieldsSelected: string
+ allFieldsDeleted: string
+ fieldDeleted: string
+ }
dynamicRespondent: {
title: string
required: string
diff --git a/frontend/src/i18n/locales/features/public-form/fields/en-sg.ts b/frontend/src/i18n/locales/features/public-form/fields/en-sg.ts
index a258ed65d4..c38866c093 100644
--- a/frontend/src/i18n/locales/features/public-form/fields/en-sg.ts
+++ b/frontend/src/i18n/locales/features/public-form/fields/en-sg.ts
@@ -11,6 +11,8 @@ export const enSG: Fields = {
dropdown: {
placeholder: 'Select an option',
nothingFound: 'No matching results',
+ clearSelection: 'Clear selection',
+ selectOptions: 'Select options',
},
attachment: {
disabled: 'Attachment upload is disabled for you',
@@ -64,4 +66,13 @@ export const enSG: Fields = {
title: 'Send the following emails a copy of my responses upon submission',
info: 'Separate multiple email addresses with a comma',
},
+ calendar: {
+ today: 'Today',
+ todayAriaLabel: "Focus on today's date",
+ },
+ datePicker: {
+ selectFromDatePicker: 'Select from date picker. ',
+ selectedDateIs: 'Selected date is {date}.',
+ invalidDate: 'The current selected date is invalid.',
+ },
}
diff --git a/frontend/src/i18n/locales/features/public-form/fields/index.ts b/frontend/src/i18n/locales/features/public-form/fields/index.ts
index 2bb314f040..a908eff81c 100644
--- a/frontend/src/i18n/locales/features/public-form/fields/index.ts
+++ b/frontend/src/i18n/locales/features/public-form/fields/index.ts
@@ -9,6 +9,8 @@ export interface Fields {
dropdown: {
placeholder: string
nothingFound: string
+ clearSelection: string
+ selectOptions: string
}
email: {
validation: {
@@ -55,6 +57,15 @@ export interface Fields {
title: string
info: string
}
+ calendar: {
+ today: string
+ todayAriaLabel: string
+ }
+ datePicker: {
+ selectFromDatePicker: string
+ selectedDateIs: string
+ invalidDate: string
+ }
}
export * from './en-sg'
diff --git a/frontend/src/i18n/locales/features/public-form/fields/ms-sg.ts b/frontend/src/i18n/locales/features/public-form/fields/ms-sg.ts
index 4feace59c8..afbc025432 100644
--- a/frontend/src/i18n/locales/features/public-form/fields/ms-sg.ts
+++ b/frontend/src/i18n/locales/features/public-form/fields/ms-sg.ts
@@ -13,6 +13,8 @@ export const msSG: PartialDeep = {
dropdown: {
placeholder: 'Pilih satu pilihan',
nothingFound: 'Tiada hasil yang sepadan',
+ clearSelection: 'Kosongkan pilihan',
+ selectOptions: 'Pilih pilihan',
},
attachment: {
maxFileSize: 'Saiz fail maksimum: {readableMaxSize}',
@@ -43,4 +45,13 @@ export const msSG: PartialDeep = {
},
},
},
+ calendar: {
+ today: 'Hari ini',
+ todayAriaLabel: 'Fokus pada tarikh hari ini',
+ },
+ datePicker: {
+ selectFromDatePicker: 'Pilih dari pemilih tarikh. ',
+ selectedDateIs: 'Tarikh yang dipilih ialah {date}.',
+ invalidDate: 'Tarikh yang dipilih semasa tidak sah.',
+ },
}
diff --git a/frontend/src/i18n/locales/features/public-form/fields/ta-sg.ts b/frontend/src/i18n/locales/features/public-form/fields/ta-sg.ts
index 61a2d36983..138c59c751 100644
--- a/frontend/src/i18n/locales/features/public-form/fields/ta-sg.ts
+++ b/frontend/src/i18n/locales/features/public-form/fields/ta-sg.ts
@@ -13,6 +13,8 @@ export const taSG: PartialDeep = {
dropdown: {
placeholder: 'ஒரு விருப்பத்தை தேர்வு செய்யவும்',
nothingFound: 'முடிவுகள் எதுவும் பொருந்தவில்லை',
+ clearSelection: 'தேர்வை அழிக்கவும்',
+ selectOptions: 'விருப்பங்களைத் தேர்ந்தெடுக்கவும்',
},
attachment: {
maxFileSize: 'கோப்பின் அதிகபட்ச அளவு: {readableMaxSize}',
@@ -43,4 +45,13 @@ export const taSG: PartialDeep = {
},
},
},
+ calendar: {
+ today: 'இன்று',
+ todayAriaLabel: 'இன்றைய தேதியைக் கவனியுங்கள்',
+ },
+ datePicker: {
+ selectFromDatePicker: 'தேதி தேர்வியிலிருந்து தேர்ந்தெடுக்கவும். ',
+ selectedDateIs: 'தேர்ந்தெடுக்கப்பட்ட தேதி {date}.',
+ invalidDate: 'தற்போது தேர்ந்தெடுக்கப்பட்ட தேதி தவறானது.',
+ },
}
diff --git a/frontend/src/i18n/locales/features/public-form/fields/zh-sg.ts b/frontend/src/i18n/locales/features/public-form/fields/zh-sg.ts
index 2817c80298..7f91b9884b 100644
--- a/frontend/src/i18n/locales/features/public-form/fields/zh-sg.ts
+++ b/frontend/src/i18n/locales/features/public-form/fields/zh-sg.ts
@@ -13,6 +13,8 @@ export const zhSG: PartialDeep = {
dropdown: {
placeholder: '请选择一个选项',
nothingFound: '没有匹配结果',
+ clearSelection: '清除选择',
+ selectOptions: '选择选项',
},
attachment: {
maxFileSize: '文件限制:不超过 {readableMaxSize}',
@@ -40,4 +42,13 @@ export const zhSG: PartialDeep = {
},
},
},
+ calendar: {
+ today: '今天',
+ todayAriaLabel: '专注于今天的日期',
+ },
+ datePicker: {
+ selectFromDatePicker: '从日期选择器中选择。',
+ selectedDateIs: '选择的日期是 {date}。',
+ invalidDate: '当前选择的日期无效。',
+ },
}