From 56cac0979ce1214b9afd7816cf009ecd9a9bf616 Mon Sep 17 00:00:00 2001 From: LoneRifle Date: Fri, 13 Dec 2024 10:57:30 +0800 Subject: [PATCH] feat(i18n): extract general,notifs,webhooks settings (#7955) Take the trouble to also extract the common 'Loading...' text at various points in the codebase. --- __tests__/e2e/helpers/createForm.ts | 8 +--- frontend/src/components/Spinner/Spinner.tsx | 6 ++- .../AdminFormNavbarBreadcrumbs.tsx | 4 +- .../IndividualResponsePage.tsx | 6 ++- .../settings/SettingsEmailsPage.tsx | 6 ++- .../admin-form/settings/SettingsPage.tsx | 12 +++--- .../components/EmailNotificationsHeader.tsx | 11 ++++- .../settings/components/FormCaptchaToggle.tsx | 7 ++- .../settings/components/FormEmailSection.tsx | 10 ++++- .../FormIssueNotificationToggle.tsx | 9 ++-- .../settings/components/FormLimitToggle.tsx | 20 ++++++--- .../settings/components/FormStatusToggle.tsx | 23 +++++++--- .../settings/components/GeneralTabHeader.tsx | 6 ++- .../components/MrfFormEmailSection.tsx | 43 ++++++++++++++----- .../WebhooksSection/RetryToggle.tsx | 8 +++- .../WebhooksSection/WebhookUrlInput.tsx | 10 ++++- .../i18n/locales/features/admin-form/en-sg.ts | 2 + .../i18n/locales/features/admin-form/index.ts | 11 +++-- .../settings/email-notifications/en-sg.ts | 37 ++++++++++++++++ .../settings/email-notifications/index.ts | 37 ++++++++++++++++ .../features/admin-form/settings/en-sg.ts | 15 +++++++ .../admin-form/settings/general/en-sg.ts | 40 +++++++++++++++++ .../admin-form/settings/general/index.ts | 37 ++++++++++++++++ .../features/admin-form/settings/index.ts | 17 ++++++++ .../admin-form/settings/webhooks/en-sg.ts | 12 ++++++ .../admin-form/settings/webhooks/index.ts | 14 ++++++ .../src/i18n/locales/features/common/en-sg.ts | 1 + .../src/i18n/locales/features/common/index.ts | 1 + frontend/src/i18n/locales/features/index.ts | 1 + frontend/src/i18n/locales/types.ts | 2 + 30 files changed, 358 insertions(+), 58 deletions(-) create mode 100644 frontend/src/i18n/locales/features/admin-form/settings/email-notifications/en-sg.ts create mode 100644 frontend/src/i18n/locales/features/admin-form/settings/email-notifications/index.ts create mode 100644 frontend/src/i18n/locales/features/admin-form/settings/en-sg.ts create mode 100644 frontend/src/i18n/locales/features/admin-form/settings/general/en-sg.ts create mode 100644 frontend/src/i18n/locales/features/admin-form/settings/general/index.ts create mode 100644 frontend/src/i18n/locales/features/admin-form/settings/index.ts create mode 100644 frontend/src/i18n/locales/features/admin-form/settings/webhooks/en-sg.ts create mode 100644 frontend/src/i18n/locales/features/admin-form/settings/webhooks/index.ts diff --git a/__tests__/e2e/helpers/createForm.ts b/__tests__/e2e/helpers/createForm.ts index e0b8b4f7ee..7372e630ef 100644 --- a/__tests__/e2e/helpers/createForm.ts +++ b/__tests__/e2e/helpers/createForm.ts @@ -181,9 +181,7 @@ const addSettings = async ( await page.getByRole('tab', { name: 'General' }).dispatchEvent('click') // Ensure that we are on the general settings page - await expect( - page.getByRole('heading', { name: 'General settings' }), - ).toBeVisible() + await expect(page.getByRole('heading', { name: 'General' })).toBeVisible() // Toggle form to be open await page @@ -226,9 +224,7 @@ const addGeneralSettings = async ( await page.getByRole('tab', { name: 'General' }).click() // Ensure that we are on the general settings page - await expect( - page.getByRole('heading', { name: 'General settings' }), - ).toBeVisible() + await expect(page.getByRole('heading', { name: 'General' })).toBeVisible() await expectToast(page, /your form is closed to new responses/i) diff --git a/frontend/src/components/Spinner/Spinner.tsx b/frontend/src/components/Spinner/Spinner.tsx index 4995bbc0fa..f1cc44a1a3 100644 --- a/frontend/src/components/Spinner/Spinner.tsx +++ b/frontend/src/components/Spinner/Spinner.tsx @@ -1,4 +1,5 @@ import { useMemo } from 'react' +import { useTranslation } from 'react-i18next' import { BiLoader } from 'react-icons/bi' import { Flex, @@ -47,10 +48,11 @@ const spin = keyframes({ export const Spinner = ({ speed = '2.5s', color = 'inherit', - label = 'Loading...', + label: userSpecifiedLabel, fontSize = '1rem', ...flexProps }: SpinnerProps): JSX.Element => { + const { t } = useTranslation() const prefersReducedMotion = usePrefersReducedMotion() const animation = useMemo( @@ -59,6 +61,8 @@ export const Spinner = ({ [prefersReducedMotion, speed], ) + const label = userSpecifiedLabel ?? t('features.common.loadingWithEllipsis') + return ( {label && {label}} diff --git a/frontend/src/features/admin-form/common/components/AdminFormNavbar/AdminFormNavbarBreadcrumbs.tsx b/frontend/src/features/admin-form/common/components/AdminFormNavbar/AdminFormNavbarBreadcrumbs.tsx index 9aa99cbb25..62a10ef9d0 100644 --- a/frontend/src/features/admin-form/common/components/AdminFormNavbar/AdminFormNavbarBreadcrumbs.tsx +++ b/frontend/src/features/admin-form/common/components/AdminFormNavbar/AdminFormNavbarBreadcrumbs.tsx @@ -1,3 +1,4 @@ +import { useTranslation } from 'react-i18next' import { BiHomeAlt } from 'react-icons/bi' import { Link as ReactLink } from 'react-router-dom' import { Icon, Skeleton, Stack, Text } from '@chakra-ui/react' @@ -13,6 +14,7 @@ type AdminFormNavbarDetailsProps = Pick export const AdminFormNavbarBreadcrumbs = ({ formInfo, }: AdminFormNavbarDetailsProps): JSX.Element => { + const { t } = useTranslation() const isMobile = useIsMobile() return ( @@ -44,7 +46,7 @@ export const AdminFormNavbarBreadcrumbs = ({ overflow="hidden" color="secondary.500" > - {formInfo ? formInfo.title : 'Loading...'} + {formInfo ? formInfo.title : t('features.common.loadingWithEllipsis')} diff --git a/frontend/src/features/admin-form/responses/IndividualResponsePage/IndividualResponsePage.tsx b/frontend/src/features/admin-form/responses/IndividualResponsePage/IndividualResponsePage.tsx index f9dc8cbc57..54719d1b86 100644 --- a/frontend/src/features/admin-form/responses/IndividualResponsePage/IndividualResponsePage.tsx +++ b/frontend/src/features/admin-form/responses/IndividualResponsePage/IndividualResponsePage.tsx @@ -1,4 +1,5 @@ import { memo, useCallback, useMemo } from 'react' +import { useTranslation } from 'react-i18next' import { BiDownload } from 'react-icons/bi' import { useParams } from 'react-router-dom' import { @@ -76,6 +77,7 @@ const StackRow = ({ } export const IndividualResponsePage = (): JSX.Element => { + const { t } = useTranslation() const { submissionId, formId } = useParams() if (!submissionId) throw new Error('Missing submissionId') if (!formId) throw new Error('Missing formId') @@ -156,7 +158,9 @@ export const IndividualResponsePage = (): JSX.Element => { /> diff --git a/frontend/src/features/admin-form/settings/SettingsEmailsPage.tsx b/frontend/src/features/admin-form/settings/SettingsEmailsPage.tsx index 0580aea6f8..18207f2bbe 100644 --- a/frontend/src/features/admin-form/settings/SettingsEmailsPage.tsx +++ b/frontend/src/features/admin-form/settings/SettingsEmailsPage.tsx @@ -1,3 +1,4 @@ +import { useTranslation } from 'react-i18next' import { Skeleton } from '@chakra-ui/react' import { FormResponseMode, FormSettings, FormStatus } from '~shared/types/form' @@ -37,6 +38,7 @@ const FormEmailSectionContainer = ({ } export const SettingsEmailsPage = (): JSX.Element => { + const { t } = useTranslation() const { data: settings } = useAdminFormSettings() const isFormPublic = settings?.status === FormStatus.Public @@ -54,7 +56,9 @@ export const SettingsEmailsPage = (): JSX.Element => { return ( <> - Email notifications + + {t('features.adminForm.settings.emailNotifications.title')} + {settings ? ( <> { const { formId, settingsTab } = useParams() + const { t } = useTranslation() if (!formId) throw new Error('No formId provided') @@ -49,32 +51,32 @@ export const SettingsPage = (): JSX.Element => { const tabConfig: TabEntry[] = [ { - label: 'General', + label: t('features.adminForm.settings.general.title'), icon: BiCog, component: SettingsGeneralPage, path: 'general', }, { - label: 'Singpass', + label: t('features.adminForm.settings.singpass.title'), icon: BiKey, component: SettingsAuthPage, path: 'singpass', }, { - label: 'Email notifications', + label: t('features.adminForm.settings.emailNotifications.title'), icon: BiMailSend, component: SettingsEmailsPage, path: 'email-notifications', showRedDot: true, }, { - label: 'Webhooks', + label: t('features.adminForm.settings.webhooks.title'), icon: BiCodeBlock, component: SettingsWebhooksPage, path: 'webhooks', }, { - label: 'Payments', + label: t('features.adminForm.settings.payments.title'), icon: BiDollar, component: SettingsPaymentsPage, path: 'payments', diff --git a/frontend/src/features/admin-form/settings/components/EmailNotificationsHeader.tsx b/frontend/src/features/admin-form/settings/components/EmailNotificationsHeader.tsx index 25ac202893..138576e395 100644 --- a/frontend/src/features/admin-form/settings/components/EmailNotificationsHeader.tsx +++ b/frontend/src/features/admin-form/settings/components/EmailNotificationsHeader.tsx @@ -1,3 +1,4 @@ +import { useTranslation } from 'react-i18next' import { BiBulb } from 'react-icons/bi' import { Flex, Icon } from '@chakra-ui/react' @@ -30,10 +31,13 @@ export const EmailNotificationsHeader = ({ isPaymentsEnabled, isFormResponseModeEmail, }: EmailNotificationsHeaderProps) => { + const { t } = useTranslation() if (isFormPublic) { return ( - To change email recipients, close your form to new responses. + {t( + 'features.adminForm.settings.emailNotifications.header.closeFormFirst', + )} ) } @@ -41,7 +45,10 @@ export const EmailNotificationsHeader = ({ if (isPaymentsEnabled) { return ( - {`Email notifications for payment forms are not available in FormSG. You can configure them using [Plumber](${OGP_PLUMBER}).`} + {t( + 'features.adminForm.settings.emailNotifications.header.noEmailsForPaymentForms', + { url: OGP_PLUMBER }, + )} ) } diff --git a/frontend/src/features/admin-form/settings/components/FormCaptchaToggle.tsx b/frontend/src/features/admin-form/settings/components/FormCaptchaToggle.tsx index df87c9ef90..64ea0748e6 100644 --- a/frontend/src/features/admin-form/settings/components/FormCaptchaToggle.tsx +++ b/frontend/src/features/admin-form/settings/components/FormCaptchaToggle.tsx @@ -1,4 +1,5 @@ import { useCallback, useMemo } from 'react' +import { useTranslation } from 'react-i18next' import { Skeleton } from '@chakra-ui/react' import Toggle from '~components/Toggle' @@ -7,6 +8,7 @@ import { useMutateFormSettings } from '../mutations' import { useAdminFormSettings } from '../queries' export const FormCaptchaToggle = (): JSX.Element => { + const { t } = useTranslation() const { data: settings, isLoading: isLoadingSettings } = useAdminFormSettings() @@ -25,8 +27,9 @@ export const FormCaptchaToggle = (): JSX.Element => { handleToggleCaptcha()} /> diff --git a/frontend/src/features/admin-form/settings/components/FormEmailSection.tsx b/frontend/src/features/admin-form/settings/components/FormEmailSection.tsx index 91c9c3eab4..ddf9d31b9a 100644 --- a/frontend/src/features/admin-form/settings/components/FormEmailSection.tsx +++ b/frontend/src/features/admin-form/settings/components/FormEmailSection.tsx @@ -5,6 +5,7 @@ import { useForm, useFormContext, } from 'react-hook-form' +import { useTranslation } from 'react-i18next' import { FormControl } from '@chakra-ui/react' import { get, isEmpty, isEqual } from 'lodash' import isEmail from 'validator/lib/isEmail' @@ -85,6 +86,7 @@ export const FormEmailSection = ({ isDisabled, settings, }: EmailFormSectionProps): JSX.Element => { + const { t } = useTranslation() const initialEmailSet = useMemo( () => new Set(settings.emails), [settings.emails], @@ -124,7 +126,9 @@ export const FormEmailSection = ({ useMarkdownForDescription description={DESCRIPTION_TEXT} > - Notifications for new responses + {t( + 'features.adminForm.settings.emailNotifications.section.regular.label', + )} {get(errors, 'emails.message')} @@ -134,7 +138,9 @@ export const FormEmailSection = ({ mt="0.5rem" opacity={isDisabled ? '0.3' : '1'} > - Separate multiple email addresses with a comma + {t( + 'features.adminForm.settings.emailNotifications.section.regular.description', + )} ) : null} diff --git a/frontend/src/features/admin-form/settings/components/FormIssueNotificationToggle.tsx b/frontend/src/features/admin-form/settings/components/FormIssueNotificationToggle.tsx index 08c96687f9..8f96067a80 100644 --- a/frontend/src/features/admin-form/settings/components/FormIssueNotificationToggle.tsx +++ b/frontend/src/features/admin-form/settings/components/FormIssueNotificationToggle.tsx @@ -1,4 +1,5 @@ -import { useCallback, useMemo } from 'react' +import { useCallback } from 'react' +import { useTranslation } from 'react-i18next' import { Skeleton } from '@chakra-ui/react' import Toggle from '~components/Toggle' @@ -7,6 +8,7 @@ import { useMutateFormSettings } from '../mutations' import { useAdminFormSettings } from '../queries' export const FormIssueNotificationToggle = (): JSX.Element => { + const { t } = useTranslation() const { data: settings, isLoading: isLoadingSettings } = useAdminFormSettings() @@ -26,8 +28,9 @@ export const FormIssueNotificationToggle = (): JSX.Element => { handleToggleIssueNotification()} /> diff --git a/frontend/src/features/admin-form/settings/components/FormLimitToggle.tsx b/frontend/src/features/admin-form/settings/components/FormLimitToggle.tsx index 539e035ca4..1fee2fc24f 100644 --- a/frontend/src/features/admin-form/settings/components/FormLimitToggle.tsx +++ b/frontend/src/features/admin-form/settings/components/FormLimitToggle.tsx @@ -5,6 +5,7 @@ import { useRef, useState, } from 'react' +import { useTranslation } from 'react-i18next' import { FormControl, Skeleton } from '@chakra-ui/react' import { FormResponseMode } from '~shared/types' @@ -30,6 +31,7 @@ const FormLimitBlock = ({ initialLimit, currentResponseCount, }: FormLimitBlockProps): JSX.Element => { + const { t } = useTranslation() const [value, setValue] = useState(initialLimit) const [error, setError] = useState() @@ -43,13 +45,15 @@ const FormLimitBlock = ({ setValue(nextVal) if (parseInt(nextVal, 10) <= currentResponseCount) { setError( - `Submission limit must be greater than current submission count (${currentResponseCount})`, + t('features.adminForm.settings.general.limit.limitLessThanCurrent', { + currentResponseCount, + }), ) } else if (error) { setError(undefined) } }, - [currentResponseCount, error], + [currentResponseCount, error, t], ) const handleBlur = useCallback(() => { @@ -83,10 +87,11 @@ const FormLimitBlock = ({ - Maximum number of responses allowed + {t('features.adminForm.settings.general.limit.input.label')} { + const { t } = useTranslation() const { data: settings, isLoading: isLoadingSettings } = useAdminFormSettings() @@ -157,12 +163,12 @@ export const FormLimitToggle = (): JSX.Element => { isDisabled={isMrf} isLoading={mutateFormLimit.isLoading} isChecked={isLimit} - label="Set a response limit" + label={t('features.adminForm.settings.general.limit.label')} onChange={() => handleToggleLimit()} /> {isMrf ? ( - Response limits cannot be applied for multi-respondent forms. + {t('features.adminForm.settings.general.limit.notForMRF')} ) : null} {settings && settings?.submissionLimit !== null && ( diff --git a/frontend/src/features/admin-form/settings/components/FormStatusToggle.tsx b/frontend/src/features/admin-form/settings/components/FormStatusToggle.tsx index 2323884d78..983fad1402 100644 --- a/frontend/src/features/admin-form/settings/components/FormStatusToggle.tsx +++ b/frontend/src/features/admin-form/settings/components/FormStatusToggle.tsx @@ -1,4 +1,5 @@ import { useCallback, useMemo } from 'react' +import { useTranslation } from 'react-i18next' import { Flex, Skeleton, Stack, Text, useDisclosure } from '@chakra-ui/react' import { BasicField } from '~shared/types' @@ -19,6 +20,7 @@ import { useAdminFormSettings } from '../queries' import { SecretKeyActivationModal } from './SecretKeyActivationModal' export const FormStatusToggle = (): JSX.Element => { + const { t } = useTranslation() const { data: { form_fields } = {} } = useAdminForm() const { data: formSettings, isLoading: isLoadingSettings } = useAdminFormSettings() @@ -41,7 +43,9 @@ export const FormStatusToggle = (): JSX.Element => { ) && !esrvcId ) { - return 'This form cannot be activated until a valid e-service ID is entered in the Singpass section.' + return t( + 'features.adminForm.settings.general.status.supplySingpassEServiceId', + ) } // For MRF, prevent form activation if form has an email confirmation field. @@ -52,9 +56,9 @@ export const FormStatusToggle = (): JSX.Element => { ff.fieldType === BasicField.Email && ff.autoReplyOptions.hasAutoReply, ) ) { - return 'Email confirmation is not supported in multi-respondent forms. Please remove email confirmations from email fields before activating your form.' + return t('features.adminForm.settings.general.status.noEmailsInMRF') } - }, [authType, esrvcId, formSettings?.responseMode, form_fields, status]) + }, [authType, esrvcId, formSettings?.responseMode, form_fields, status, t]) const { mutateFormStatus } = useMutateFormSettings() @@ -81,6 +85,10 @@ export const FormStatusToggle = (): JSX.Element => { status, ]) + const statusText = t( + `features.adminForm.settings.general.status.description.${isFormPublic ? 'open' : 'closed'}`, + ) + return ( @@ -98,12 +106,15 @@ export const FormStatusToggle = (): JSX.Element => { justify="space-between" > - Your form is {isFormPublic ? 'OPEN' : 'CLOSED'} to new - responses + {t('features.adminForm.settings.general.status.description.prefix')} + {statusText} + {t('features.adminForm.settings.general.status.description.suffix')} { useAdminFormSettings() const readableFormResponseMode = !settings - ? 'Loading...' + ? t('features.common.loadingWithEllipsis') : t(`features.adminForm.meta.responseModeText.${settings.responseMode}`) return ( @@ -23,7 +23,9 @@ export const GeneralTabHeader = (): JSX.Element => { justify="space-between" mb="2.5rem" > - General settings + + {t('features.adminForm.settings.general.title')} + {readableFormResponseMode} diff --git a/frontend/src/features/admin-form/settings/components/MrfFormEmailSection.tsx b/frontend/src/features/admin-form/settings/components/MrfFormEmailSection.tsx index 08ee76cf05..601d015a83 100644 --- a/frontend/src/features/admin-form/settings/components/MrfFormEmailSection.tsx +++ b/frontend/src/features/admin-form/settings/components/MrfFormEmailSection.tsx @@ -1,5 +1,6 @@ import { useCallback } from 'react' import { Controller, useForm } from 'react-hook-form' +import { useTranslation } from 'react-i18next' import { Box, FormControl, @@ -42,6 +43,7 @@ const MrfEmailNotificationsForm = ({ settings, isDisabled, }: MrfEmailNotificationsFormProps) => { + const { t } = useTranslation() const { isLoading, formWorkflow, @@ -144,11 +146,15 @@ const MrfEmailNotificationsForm = ({
- Select who to notify when the form and/or workflow is complete: + {t( + 'features.adminForm.settings.emailNotifications.section.mrf.selectRecipient', + )} - Respondent in Step 1 + {t( + 'features.adminForm.settings.emailNotifications.section.mrf.respondents.step1.label', + )} ( - Other respondents in your workflow + {t( + 'features.adminForm.settings.emailNotifications.section.mrf.respondents.stepN.label.overall', + )} step.stepNumber > 1) - .map((step) => ({ - label: `Respondent(s) in Step ${step.stepNumber}`, - value: step._id, + .map(({ stepNumber, _id: value }) => ({ + label: t( + 'features.adminForm.settings.emailNotifications.section.mrf.respondents.stepN.label.each', + { stepNumber }, + ), + value, }))} values={values} onChange={onChange} onBlur={handleSubmit(onSubmit)} - placeholder="Select respondents from your form" + placeholder={t( + 'features.adminForm.settings.emailNotifications.section.mrf.respondents.stepN.placeholder', + )} isSelectedItemFullWidth isDisabled={isLoading || isDisabled} {...rest} @@ -213,9 +228,13 @@ const MrfEmailNotificationsForm = ({ mb="0.75rem" tooltipVariant="info" tooltipPlacement="top" - tooltipText="Include the admin's email to inform them whenever a workflow is completed" + tooltipText={t( + 'features.adminForm.settings.emailNotifications.section.mrf.respondents.others.tooltipText', + )} > - Others + {t( + 'features.adminForm.settings.emailNotifications.section.mrf.respondents.others.label', + )} {isEmpty(errors[OTHER_PARTIES_EMAIL_INPUT_NAME]) ? ( - Separate multiple email addresses with a comma + {t( + 'features.adminForm.settings.emailNotifications.section.mrf.respondents.others.description', + )} ) : ( diff --git a/frontend/src/features/admin-form/settings/components/WebhooksSection/RetryToggle.tsx b/frontend/src/features/admin-form/settings/components/WebhooksSection/RetryToggle.tsx index 4138e9d7ff..3f89fc205b 100644 --- a/frontend/src/features/admin-form/settings/components/WebhooksSection/RetryToggle.tsx +++ b/frontend/src/features/admin-form/settings/components/WebhooksSection/RetryToggle.tsx @@ -1,4 +1,5 @@ import { ChangeEventHandler, useCallback } from 'react' +import { useTranslation } from 'react-i18next' import { GUIDE_WEBHOOKS } from '~constants/links' import Toggle from '~components/Toggle' @@ -7,6 +8,7 @@ import { useMutateFormSettings } from '../../mutations' import { useAdminFormSettings } from '../../queries' export const RetryToggle = (): JSX.Element | null => { + const { t } = useTranslation() const { data: settings } = useAdminFormSettings() const { mutateWebhookRetries } = useMutateFormSettings() @@ -24,8 +26,10 @@ export const RetryToggle = (): JSX.Element | null => { ) diff --git a/frontend/src/features/admin-form/settings/components/WebhooksSection/WebhookUrlInput.tsx b/frontend/src/features/admin-form/settings/components/WebhooksSection/WebhookUrlInput.tsx index 232085be28..db5334d282 100644 --- a/frontend/src/features/admin-form/settings/components/WebhooksSection/WebhookUrlInput.tsx +++ b/frontend/src/features/admin-form/settings/components/WebhooksSection/WebhookUrlInput.tsx @@ -1,5 +1,6 @@ import { KeyboardEventHandler, useCallback, useEffect, useRef } from 'react' import { useForm } from 'react-hook-form' +import { useTranslation } from 'react-i18next' import { FormControl, InputGroup, @@ -18,6 +19,7 @@ import { useMutateFormSettings } from '../../mutations' import { useAdminFormSettings } from '../../queries' export const WebhookUrlInput = (): JSX.Element => { + const { t } = useTranslation() const { data: settings, isLoading } = useAdminFormSettings() const { mutateFormWebhookUrl } = useMutateFormSettings() const { @@ -86,8 +88,12 @@ export const WebhookUrlInput = (): JSX.Element => { isReadOnly={mutateFormWebhookUrl.isLoading} isInvalid={!!errors.url} > - - Endpoint URL + + {t('features.adminForm.settings.webhooks.input.label')} 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 b194d9d51e..ceefac190a 100644 --- a/frontend/src/i18n/locales/features/admin-form/en-sg.ts +++ b/frontend/src/i18n/locales/features/admin-form/en-sg.ts @@ -1,6 +1,7 @@ import { enSG as meta } from './meta' import { enSG as modals } from './modals' import { enSG as navbar } from './navbar' +import { enSG as settings } from './settings' import { enSG as sidebar } from './sidebar' import { enSG as toasts } from './toasts' @@ -10,4 +11,5 @@ export const enSG = { meta, modals, toasts, + settings, } diff --git a/frontend/src/i18n/locales/features/admin-form/index.ts b/frontend/src/i18n/locales/features/admin-form/index.ts index 34644c6f72..836c06eec6 100644 --- a/frontend/src/i18n/locales/features/admin-form/index.ts +++ b/frontend/src/i18n/locales/features/admin-form/index.ts @@ -2,8 +2,11 @@ export * from './en-sg' export { type Meta } from './meta' export { type Modals } from './modals' export { type Navbar } from './navbar' -export { type Fields } from './sidebar' -export { type HeaderAndInstructions } from './sidebar' -export { type Logic } from './sidebar' -export { type ThankYou } from './sidebar' +export { type Settings } from './settings' +export { + type Fields, + type HeaderAndInstructions, + type Logic, + type ThankYou, +} from './sidebar' export { type Toasts } from './toasts' diff --git a/frontend/src/i18n/locales/features/admin-form/settings/email-notifications/en-sg.ts b/frontend/src/i18n/locales/features/admin-form/settings/email-notifications/en-sg.ts new file mode 100644 index 0000000000..f058c36770 --- /dev/null +++ b/frontend/src/i18n/locales/features/admin-form/settings/email-notifications/en-sg.ts @@ -0,0 +1,37 @@ +export const enSG = { + title: 'Email notifications', + header: { + closeFormFirst: + 'To change email recipients, close your form to new responses.', + noEmailsForPaymentForms: `Email notifications for payment forms are not available in FormSG. You can configure them using [Plumber]({url}).`, + }, + section: { + mrf: { + selectRecipient: + 'Select who to notify when the form and/or workflow is complete:', + respondents: { + step1: { + label: 'Respondent in Step 1', + placeholder: 'Select an email field from your form', + }, + stepN: { + label: { + overall: 'Other respondents in your workflow', + each: 'Respondent(s) in Step {stepNumber}', + }, + placeholder: 'Select respondents from your form', + }, + others: { + label: 'Others', + tooltipText: + "Include the admin's email to inform them whenever a workflow is completed", + description: 'Separate multiple email addresses with a comma', + }, + }, + }, + regular: { + label: 'Notifications for new responses', + description: 'Separate multiple email addresses with a comma', + }, + }, +} diff --git a/frontend/src/i18n/locales/features/admin-form/settings/email-notifications/index.ts b/frontend/src/i18n/locales/features/admin-form/settings/email-notifications/index.ts new file mode 100644 index 0000000000..cf7f443c4a --- /dev/null +++ b/frontend/src/i18n/locales/features/admin-form/settings/email-notifications/index.ts @@ -0,0 +1,37 @@ +import { type HasTitle } from '..' + +export * from './en-sg' + +export interface EmailNotifications extends HasTitle { + header: { + closeFormFirst: string + noEmailsForPaymentForms: string + } + section: { + mrf: { + selectRecipient: string + respondents: { + step1: { + label: string + placeholder: string + } + stepN: { + label: { + overall: string + each: string + } + placeholder: string + } + others: { + label: string + description: string + tooltipText: string + } + } + } + regular: { + label: string + description: string + } + } +} diff --git a/frontend/src/i18n/locales/features/admin-form/settings/en-sg.ts b/frontend/src/i18n/locales/features/admin-form/settings/en-sg.ts new file mode 100644 index 0000000000..3e25bf6c5a --- /dev/null +++ b/frontend/src/i18n/locales/features/admin-form/settings/en-sg.ts @@ -0,0 +1,15 @@ +import { enSG as emailNotifications } from './email-notifications' +import { enSG as general } from './general' +import { enSG as webhooks } from './webhooks' + +export const enSG = { + general, + singpass: { + title: 'Singpass', + }, + emailNotifications, + webhooks, + payments: { + title: 'Payments', + }, +} diff --git a/frontend/src/i18n/locales/features/admin-form/settings/general/en-sg.ts b/frontend/src/i18n/locales/features/admin-form/settings/general/en-sg.ts new file mode 100644 index 0000000000..1bf2aecd43 --- /dev/null +++ b/frontend/src/i18n/locales/features/admin-form/settings/general/en-sg.ts @@ -0,0 +1,40 @@ +export const enSG = { + title: 'General', + status: { + supplySingpassEServiceId: + 'This form cannot be activated until a valid e-service ID is entered in the Singpass section.', + noEmailsInMRF: + 'Email confirmation is not supported in multi-respondent forms. Please remove email confirmations from email fields before activating your form.', + description: { + prefix: 'Your form is ', + suffix: ' to new responses', + open: 'OPEN', + closed: 'CLOSED', + }, + ariaLabel: 'Toggle form status', + }, + limit: { + label: 'Set a response limit', + notForMRF: 'Response limits cannot be applied for multi-respondent forms.', + input: { + label: 'Maximum number of responses allowed', + description: + 'Your form will automatically close once it reaches the set limit. Enable reCAPTCHA to prevent spam submissions from triggering this limit.', + }, + limitLessThanCurrent: + 'Submission limit must be greater than current submission count ({currentResponseCount})', + }, + customisation: { + label: 'Set message for closed form', + }, + captcha: { + label: 'Enable reCAPTCHA', + description: + 'If you expect non-English-speaking respondents, they may have difficulty understanding the reCAPTCHA selection instructions.', + }, + issueNotifications: { + label: 'Receive email notifications for issues reported by respondents', + description: + 'You will receive a maximum of one email per form, per day if there are any issues reported.', + }, +} diff --git a/frontend/src/i18n/locales/features/admin-form/settings/general/index.ts b/frontend/src/i18n/locales/features/admin-form/settings/general/index.ts new file mode 100644 index 0000000000..f00b3f9fa6 --- /dev/null +++ b/frontend/src/i18n/locales/features/admin-form/settings/general/index.ts @@ -0,0 +1,37 @@ +import { type HasTitle } from '..' + +export * from './en-sg' + +export interface General extends HasTitle { + status: { + supplySingpassEServiceId: string + noEmailsInMRF: string + description: { + prefix: string + suffix: string + open: string + closed: string + } + ariaLabel: string + } + limit: { + label: string + notForMRF: string + input: { + label: string + description: string + } + limitLessThanCurrent: string + } + customisation: { + label: string + } + captcha: { + label: string + description: string + } + issueNotifications: { + label: string + description: string + } +} diff --git a/frontend/src/i18n/locales/features/admin-form/settings/index.ts b/frontend/src/i18n/locales/features/admin-form/settings/index.ts new file mode 100644 index 0000000000..51dfe46608 --- /dev/null +++ b/frontend/src/i18n/locales/features/admin-form/settings/index.ts @@ -0,0 +1,17 @@ +import { EmailNotifications } from './email-notifications' +import { General } from './general' +import { Webhooks } from './webhooks' + +export * from './en-sg' + +export type HasTitle = { + title: string +} + +export interface Settings { + general: General + singpass: HasTitle + emailNotifications: EmailNotifications + webhooks: Webhooks + payments: HasTitle +} diff --git a/frontend/src/i18n/locales/features/admin-form/settings/webhooks/en-sg.ts b/frontend/src/i18n/locales/features/admin-form/settings/webhooks/en-sg.ts new file mode 100644 index 0000000000..548f72ad2c --- /dev/null +++ b/frontend/src/i18n/locales/features/admin-form/settings/webhooks/en-sg.ts @@ -0,0 +1,12 @@ +export const enSG = { + title: 'Webhooks', + input: { + label: 'Endpoint URL', + description: + 'For developers and IT officers usage. We will POST encrypted form responses in real-time to the HTTPS endpoint specified here.', + }, + retry: { + label: 'Enable retries', + description: `Your system must meet certain requirements before retries can be safely enabled. [Learn more]({url})`, + }, +} diff --git a/frontend/src/i18n/locales/features/admin-form/settings/webhooks/index.ts b/frontend/src/i18n/locales/features/admin-form/settings/webhooks/index.ts new file mode 100644 index 0000000000..67d797b927 --- /dev/null +++ b/frontend/src/i18n/locales/features/admin-form/settings/webhooks/index.ts @@ -0,0 +1,14 @@ +import { type HasTitle } from '..' + +export * from './en-sg' + +export interface Webhooks extends HasTitle { + input: { + label: string + description: string + } + retry: { + label: string + description: string + } +} diff --git a/frontend/src/i18n/locales/features/common/en-sg.ts b/frontend/src/i18n/locales/features/common/en-sg.ts index 910803c2dd..c27ff181bf 100644 --- a/frontend/src/i18n/locales/features/common/en-sg.ts +++ b/frontend/src/i18n/locales/features/common/en-sg.ts @@ -30,6 +30,7 @@ export const enSG: Common = { back: 'Back', edit: 'Edit', loading: 'Loading', + loadingWithEllipsis: 'Loading...', saving: 'Saving', responses: 'Responses', feedback: 'Feedback', diff --git a/frontend/src/i18n/locales/features/common/index.ts b/frontend/src/i18n/locales/features/common/index.ts index 304c1d78c2..8d948abddd 100644 --- a/frontend/src/i18n/locales/features/common/index.ts +++ b/frontend/src/i18n/locales/features/common/index.ts @@ -30,6 +30,7 @@ export interface Common { back: string edit: string loading: string + loadingWithEllipsis: string saving: string responses: string feedback: string diff --git a/frontend/src/i18n/locales/features/index.ts b/frontend/src/i18n/locales/features/index.ts index b612fc14f5..35ce5f66ff 100644 --- a/frontend/src/i18n/locales/features/index.ts +++ b/frontend/src/i18n/locales/features/index.ts @@ -5,6 +5,7 @@ export { type Meta, type Modals, type Navbar, + type Settings, type ThankYou, type Toasts, } from './admin-form' diff --git a/frontend/src/i18n/locales/types.ts b/frontend/src/i18n/locales/types.ts index 9bcc910574..b103118ef7 100644 --- a/frontend/src/i18n/locales/types.ts +++ b/frontend/src/i18n/locales/types.ts @@ -10,6 +10,7 @@ import { Modals, Navbar, PublicForm, + Settings, ThankYou, Toasts, } from './features' @@ -28,6 +29,7 @@ interface Translation { meta?: Meta modals?: Modals toasts?: Toasts + settings?: Settings } common?: Common publicForm?: PublicForm