diff --git a/frontend/src/features/admin-form/preview/PreviewFormProvider.tsx b/frontend/src/features/admin-form/preview/PreviewFormProvider.tsx index f7e136241a..0244c66437 100644 --- a/frontend/src/features/admin-form/preview/PreviewFormProvider.tsx +++ b/frontend/src/features/admin-form/preview/PreviewFormProvider.tsx @@ -1,6 +1,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react' import { Helmet } from 'react-helmet-async' import { SubmitHandler } from 'react-hook-form' +import { useTranslation } from 'react-i18next' import { datadogLogs } from '@datadog/browser-logs' import get from 'lodash/get' import simplur from 'simplur' @@ -34,6 +35,7 @@ export const PreviewFormProvider = ({ formId, children, }: PreviewFormProviderProps): JSX.Element => { + const { t } = useTranslation() // Once form has been submitted, submission data will be set here. const [submissionData, setSubmissionData] = useState() @@ -340,7 +342,13 @@ export const PreviewFormProvider = ({ ...rest, }} > - + {isFormNotFound ? : children} ) diff --git a/frontend/src/features/admin-form/template/TemplateFormProvider.tsx b/frontend/src/features/admin-form/template/TemplateFormProvider.tsx index db685bbc44..c759795764 100644 --- a/frontend/src/features/admin-form/template/TemplateFormProvider.tsx +++ b/frontend/src/features/admin-form/template/TemplateFormProvider.tsx @@ -1,5 +1,6 @@ import { useCallback, useEffect, useMemo } from 'react' import { Helmet } from 'react-helmet-async' +import { useTranslation } from 'react-i18next' import get from 'lodash/get' import simplur from 'simplur' @@ -24,6 +25,7 @@ export const TemplateFormProvider = ({ formId, children, }: PreviewFormProviderProps): JSX.Element => { + const { t } = useTranslation() const { data, isLoading, error, ...rest } = useFormTemplate(formId) const { isNotFormId, toast, vfnToastIdRef, expiryInMs, ...commonFormValues } = @@ -95,7 +97,13 @@ export const TemplateFormProvider = ({ ...rest, }} > - + {isFormNotFound ? : children} ) diff --git a/frontend/src/features/public-form/PublicFormProvider.tsx b/frontend/src/features/public-form/PublicFormProvider.tsx index 53b267faf7..6e70f08811 100644 --- a/frontend/src/features/public-form/PublicFormProvider.tsx +++ b/frontend/src/features/public-form/PublicFormProvider.tsx @@ -8,13 +8,13 @@ import { } from 'react' import { Helmet } from 'react-helmet-async' import { SubmitHandler } from 'react-hook-form' +import { useTranslation } from 'react-i18next' import { useNavigate, useSearchParams } from 'react-router-dom' import { useDisclosure } from '@chakra-ui/react' import { datadogLogs } from '@datadog/browser-logs' import { useGrowthBook } from '@growthbook/growthbook-react' import { differenceInMilliseconds, isPast } from 'date-fns' import get from 'lodash/get' -import simplur from 'simplur' import { featureFlags, @@ -125,6 +125,8 @@ export const PublicFormProvider = ({ children, startTime, }: PublicFormProviderProps): JSX.Element => { + const { t } = useTranslation() + // Once form has been submitted, submission data will be set here. const [submissionData, setSubmissionData] = useState() const [numVisibleFields, setNumVisibleFields] = useState(0) @@ -277,11 +279,10 @@ export const PublicFormProvider = ({ if (data?.myInfoError) { toast({ status: 'danger', - description: - 'Your Myinfo details could not be retrieved. Refresh your browser and log in, or try again later.', + description: t('features.publicForm.errors.myinfo'), }) } - }, [data, toast]) + }, [data, toast, t]) const showErrorToast = useCallback( (error, form: PublicFormDto) => { @@ -290,11 +291,11 @@ export const PublicFormProvider = ({ description: error instanceof Error ? error.message - : 'An error occurred whilst processing your submission. Please refresh and try again.', + : t('features.publicForm.errors.submitFailure'), }) trackSubmitFormFailure(form) }, - [toast], + [toast, t], ) useEffect(() => { @@ -313,23 +314,24 @@ export const PublicFormProvider = ({ !!previousSubmissionId if (isFormNotFound || isNonMultirespondentFormWithPreviousSubmissionId) { + const title = t('features.publicForm.errors.notFound') + const message = error?.message + ? (t(error.message as Parameters[0], { + defaultValue: error.message, + }) as string) + : title return { - title: 'Form not found', - header: 'This form is not available.', - message: error?.message || 'Form not found', + title, + header: t('features.publicForm.errors.notAvailable'), + message, } } // Decryption failed for previous submission if (isSubmissionSecretKeyInvalid) { - return { - title: 'Invalid form link', - header: 'This form link is no longer valid.', - message: - 'A submission may have already been made using this link. If you think this is a mistake, please contact the agency that gave you the form link.', - } + return t('features.publicForm.errors.submissionSecretKeyInvalid') } - }, [error, data, previousSubmissionId, isSubmissionSecretKeyInvalid]) + }, [error, data, previousSubmissionId, isSubmissionSecretKeyInvalid, t]) const generateVfnExpiryToast = useCallback(() => { if (vfnToastIdRef.current) { @@ -344,14 +346,12 @@ export const PublicFormProvider = ({ duration: null, status: 'warning', isClosable: true, - description: simplur`Your verified field[|s] ${[ - numVerifiable, - ]} [has|have] expired. Please verify [the|those] ${[ - numVerifiable, - ]} field[|s] again.`, + description: t('features.publicForm.errors.verifiedFieldExpired', { + count: numVerifiable, + }), }) } - }, [data?.form.form_fields, toast, vfnToastIdRef]) + }, [data?.form.form_fields, toast, vfnToastIdRef, t]) const { submitEmailModeFormMutation, diff --git a/src/app/modules/form/form.errors.ts b/src/app/modules/form/form.errors.ts index 5ef8cdc8cc..722a51fa68 100644 --- a/src/app/modules/form/form.errors.ts +++ b/src/app/modules/form/form.errors.ts @@ -8,7 +8,7 @@ export class FormNotFoundError extends ApplicationError { } export class FormDeletedError extends ApplicationError { - constructor(message = 'This form is no longer active') { + constructor(message = 'features.publicForm.errors.deleted') { super(message) } } @@ -26,7 +26,7 @@ export class PrivateFormError extends ApplicationError { * @param formTitle Extra meta for form title */ constructor( - message = 'If you think this is a mistake, please contact the agency that gave you the form link.', + message = 'features.publicForm.errors.private', formTitle: string, ) { super(message) diff --git a/src/app/modules/form/public-form/__tests__/public-form.controller.spec.ts b/src/app/modules/form/public-form/__tests__/public-form.controller.spec.ts index b94e02874d..ed7e85e9e4 100644 --- a/src/app/modules/form/public-form/__tests__/public-form.controller.spec.ts +++ b/src/app/modules/form/public-form/__tests__/public-form.controller.spec.ts @@ -643,11 +643,10 @@ describe('public-form.controller', () => { // Arrange // 1. Mock the response const mockRes = expressHandler.mockResponse() - const MOCK_ERROR_STRING = 'Your form was eaten up by a monster' // 2. Mock the call to retrieve the form MockAuthService.getFormIfPublic.mockReturnValueOnce( - errAsync(new FormNotFoundError(MOCK_ERROR_STRING)), + errAsync(new FormNotFoundError()), ) // Act @@ -668,7 +667,7 @@ describe('public-form.controller', () => { ).not.toHaveBeenCalled() expect(mockRes.status).toHaveBeenCalledWith(404) expect(mockRes.json).toHaveBeenCalledWith({ - message: MOCK_ERROR_STRING, + message: 'features.publicForm.errors.notFound', }) }) diff --git a/src/app/modules/form/public-form/public-form.utils.ts b/src/app/modules/form/public-form/public-form.utils.ts index b31bd4f4df..063696ad63 100644 --- a/src/app/modules/form/public-form/public-form.utils.ts +++ b/src/app/modules/form/public-form/public-form.utils.ts @@ -24,7 +24,9 @@ export const mapRouteError = ( case FormErrors.FormNotFoundError: return { statusCode: StatusCodes.NOT_FOUND, - errorMessage: error.message, + // Replace standard message in FormNotFoundError with + // i18next equivalent for localization + errorMessage: 'features.publicForm.errors.notFound', } case FormErrors.FormDeletedError: return { diff --git a/src/app/routes/api/v3/forms/__tests__/public-forms.form.routes.spec.ts b/src/app/routes/api/v3/forms/__tests__/public-forms.form.routes.spec.ts index f9bd606ff6..8c9f0747ba 100644 --- a/src/app/routes/api/v3/forms/__tests__/public-forms.form.routes.spec.ts +++ b/src/app/routes/api/v3/forms/__tests__/public-forms.form.routes.spec.ts @@ -238,7 +238,7 @@ describe('public-form.form.routes', () => { const MOCK_FORM_ID = new ObjectId().toHexString() const expectedResponseBody = JSON.parse( JSON.stringify({ - message: 'Form not found', + message: 'features.publicForm.errors.notFound', }), ) @@ -283,7 +283,7 @@ describe('public-form.form.routes', () => { }) const expectedResponseBody = JSON.parse( JSON.stringify({ - message: 'This form is no longer active', + message: 'features.publicForm.errors.deleted', }), ) @@ -350,7 +350,7 @@ describe('public-form.form.routes', () => { const MOCK_FORM_ID = new ObjectId().toHexString() const expectedResponseBody = JSON.parse( JSON.stringify({ - message: 'Form not found', + message: 'features.publicForm.errors.notFound', }), ) @@ -394,7 +394,7 @@ describe('public-form.form.routes', () => { }) const expectedResponseBody = JSON.parse( JSON.stringify({ - message: 'This form is no longer active', + message: 'features.publicForm.errors.deleted', }), )