Skip to content

Commit

Permalink
refactor(i18n): use locale-specific errors in public form
Browse files Browse the repository at this point in the history
- Replace hard-coded text in PublicFormProvider and related components
  with i18next equivalents
- Default server error messages to i18next keys, and in
  PublicFormProvider, attempt to use them as such. If they are not,
  use the response messages as-is.
  • Loading branch information
LoneRifle committed Apr 25, 2024
1 parent 073dab0 commit 70ed6f3
Show file tree
Hide file tree
Showing 7 changed files with 51 additions and 34 deletions.
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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<SubmissionData>()

Expand Down Expand Up @@ -340,7 +342,13 @@ export const PreviewFormProvider = ({
...rest,
}}
>
<Helmet title={isFormNotFound ? 'Form not found' : data?.form.title} />
<Helmet
title={
isFormNotFound
? t('features.publicForm.errors.notFound')
: data?.form.title
}
/>
{isFormNotFound ? <FormNotFound message={error?.message} /> : children}
</PublicFormContext.Provider>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -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'

Expand All @@ -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 } =
Expand Down Expand Up @@ -95,7 +97,13 @@ export const TemplateFormProvider = ({
...rest,
}}
>
<Helmet title={isFormNotFound ? 'Form not found' : data?.form.title} />
<Helmet
title={
isFormNotFound
? t('features.publicForm.errors.notFound')
: data?.form.title
}
/>
{isFormNotFound ? <FormNotFound message={error?.message} /> : children}
</PublicFormContext.Provider>
)
Expand Down
44 changes: 22 additions & 22 deletions frontend/src/features/public-form/PublicFormProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<SubmissionData>()
const [numVisibleFields, setNumVisibleFields] = useState(0)
Expand Down Expand Up @@ -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) => {
Expand All @@ -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(() => {
Expand All @@ -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<typeof t>[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) {
Expand All @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions src/app/modules/form/form.errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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',
})
})

Expand Down
4 changes: 3 additions & 1 deletion src/app/modules/form/public-form/public-form.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
}),
)

Expand Down Expand Up @@ -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',
}),
)

Expand Down Expand Up @@ -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',
}),
)

Expand Down Expand Up @@ -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',
}),
)

Expand Down

0 comments on commit 70ed6f3

Please sign in to comment.