Skip to content

Commit

Permalink
refactor: use i18next to toggle languages
Browse files Browse the repository at this point in the history
Use i18next to hold currently selected language rather than use our own
internal state flag in PublicFormContext. That way, a form submitter
can control the language used across the entire Form service.

- Rework LanguageControl and PublicFormContext to use i18next to hold
  currently selected language, falling back on `Language.ENGLISH`
- Rework components to lookup current lang from `useTranslation()`, not
  `PublicFormContext.selectedPublicFormLanguage` or prop passing
  • Loading branch information
LoneRifle committed Dec 16, 2024
1 parent 4d1fae5 commit 2d3a53c
Show file tree
Hide file tree
Showing 56 changed files with 256 additions and 480 deletions.
9 changes: 2 additions & 7 deletions frontend/src/components/Checkbox/Checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,6 @@ export interface CheckboxProps extends ChakraCheckboxProps {
* Background and shadow colors of checkbox.
*/
colorScheme?: FieldColorScheme

/**
* Selected language to get translated label.
*/
selectedLanguage?: Language
}

type CheckboxWithOthers = ComponentWithAs<'input', CheckboxProps> & {
Expand Down Expand Up @@ -99,7 +94,7 @@ const OthersWrapper = ({
* Wrapper for the checkbox part of the Others option.
*/
const OthersCheckbox = forwardRef<CheckboxProps, 'input'>((props, ref) => {
const { t } = useTranslation()
const { t, i18n } = useTranslation()
const { checkboxRef, inputRef } = useCheckboxOthers()
// Passing all props for cleanliness but size and colorScheme are the most relevant
const styles = useMultiStyleConfig(CHECKBOX_THEME_KEY, props)
Expand All @@ -115,7 +110,7 @@ const OthersCheckbox = forwardRef<CheckboxProps, 'input'>((props, ref) => {
}

const othersLabel =
OTHERS_TRANSLATED_LABEL[props.selectedLanguage ?? Language.ENGLISH]
OTHERS_TRANSLATED_LABEL[(i18n.language as Language) ?? Language.ENGLISH]

return (
<Checkbox
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,6 @@ export interface SingleSelectProviderProps<
/** Variant of component */
variant?: 'clear'
fullWidth?: boolean
/** Selected language of default placeholder prop */
selectedLanguage?: Language
}
export const SingleSelectProvider = ({
items: rawItems,
Expand All @@ -75,11 +73,12 @@ export const SingleSelectProvider = ({
comboboxProps = {},
variant,
fullWidth = false,
selectedLanguage = Language.ENGLISH,
}: SingleSelectProviderProps): JSX.Element => {
const { items, getItemByValue } = useItems({ rawItems })
const [isFocused, setIsFocused] = useState(false)
const { t } = useTranslation()
const { t, i18n } = useTranslation()

const selectedLanguage = i18n.language as Language

const { isInvalid, isDisabled, isReadOnly, isRequired } = useFormControlProps(
{
Expand Down
10 changes: 3 additions & 7 deletions frontend/src/components/Field/Attachment/Attachment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,6 @@ export interface AttachmentProps extends UseFormControlProps<HTMLElement> {
* Override callback function that is invoked when remove button is clicked.
*/
handleRemoveFileOverride?: () => void

/**
* Selected language used for default labels
*/
selectedLanguage?: Language
}

export const Attachment = forwardRef<AttachmentProps, 'div'>(
Expand All @@ -126,12 +121,11 @@ export const Attachment = forwardRef<AttachmentProps, 'div'>(
isRemoveDisabled,
handleDownloadFileOverride,
handleRemoveFileOverride,
selectedLanguage = Language.ENGLISH,
...props
},
ref,
) => {
const { t } = useTranslation()
const { t, i18n } = useTranslation()
// Merge given props with any form control props, if they exist.
const inputProps = useFormControl(props)
// id to set on the rendered max size FormFieldMessage component.
Expand Down Expand Up @@ -307,6 +301,8 @@ export const Attachment = forwardRef<AttachmentProps, 'div'>(
})
}, [getInputProps, inputProps, name])

const selectedLanguage = i18n.language as Language

return (
<AttachmentStylesProvider value={styles}>
<Box __css={styles.container}>
Expand Down
7 changes: 3 additions & 4 deletions frontend/src/components/Field/YesNo/YesNo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,18 +59,16 @@ export interface YesNoProps {
* Color scheme of the component to render. Defaults to `primary`.
*/
colorScheme?: FieldColorScheme

selectedLanguage?: Language
}

/**
* YesNo field component.
*/
export const YesNo = forwardRef<YesNoProps, 'input'>(
({ colorScheme, selectedLanguage = Language.ENGLISH, ...props }, ref) => {
({ colorScheme, ...props }, ref) => {
const formControlProps = useFormControlProps(props)
const { getRootProps, getRadioProps, onChange } = useRadioGroup(props)
const { t } = useTranslation()
const { t, i18n } = useTranslation()

const groupProps = getRootProps()
const [noProps, yesProps] = useMemo(() => {
Expand All @@ -97,6 +95,7 @@ export const YesNo = forwardRef<YesNoProps, 'input'>(
return [noRadioProps, yesRadioProps]
}, [formControlProps, getRadioProps, props.name])

const selectedLanguage = i18n.language as Language
const yesLabel = YES_NO_TRANSLATIONS[selectedLanguage].Yes

Check failure on line 99 in frontend/src/components/Field/YesNo/YesNo.tsx

View workflow job for this annotation

GitHub Actions / frontend_test

src/templates/Field/YesNo/YesNoField.test.tsx > ValidationRequired > renders error when field is not selected before submitting

TypeError: Cannot read properties of undefined (reading 'Yes') ❯ src/components/Field/YesNo/YesNo.tsx:99:60 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom.development.js:15486:18 ❯ updateForwardRef node_modules/react-dom/cjs/react-dom.development.js:19245:20 ❯ beginWork node_modules/react-dom/cjs/react-dom.development.js:21675:16 ❯ beginWork$1 node_modules/react-dom/cjs/react-dom.development.js:27465:14 ❯ performUnitOfWork node_modules/react-dom/cjs/react-dom.development.js:26599:12 ❯ workLoopSync node_modules/react-dom/cjs/react-dom.development.js:26505:5 ❯ renderRootSync node_modules/react-dom/cjs/react-dom.development.js:26473:7 ❯ recoverFromConcurrentError node_modules/react-dom/cjs/react-dom.development.js:25889:20 ❯ performConcurrentWorkOnRoot node_modules/react-dom/cjs/react-dom.development.js:25789:22

Check failure on line 99 in frontend/src/components/Field/YesNo/YesNo.tsx

View workflow job for this annotation

GitHub Actions / frontend_test

src/templates/Field/YesNo/YesNoField.test.tsx > ValidationRequired > renders success when selected field is submitted

TypeError: Cannot read properties of undefined (reading 'Yes') ❯ src/components/Field/YesNo/YesNo.tsx:99:60 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom.development.js:15486:18 ❯ updateForwardRef node_modules/react-dom/cjs/react-dom.development.js:19245:20 ❯ beginWork node_modules/react-dom/cjs/react-dom.development.js:21675:16 ❯ beginWork$1 node_modules/react-dom/cjs/react-dom.development.js:27465:14 ❯ performUnitOfWork node_modules/react-dom/cjs/react-dom.development.js:26599:12 ❯ workLoopSync node_modules/react-dom/cjs/react-dom.development.js:26505:5 ❯ renderRootSync node_modules/react-dom/cjs/react-dom.development.js:26473:7 ❯ recoverFromConcurrentError node_modules/react-dom/cjs/react-dom.development.js:25889:20 ❯ performConcurrentWorkOnRoot node_modules/react-dom/cjs/react-dom.development.js:25789:22

Check failure on line 99 in frontend/src/components/Field/YesNo/YesNo.tsx

View workflow job for this annotation

GitHub Actions / frontend_test

src/templates/Field/YesNo/YesNoField.test.tsx > ValidationOptional > renders success even when field is not selected before submitting

TypeError: Cannot read properties of undefined (reading 'Yes') ❯ src/components/Field/YesNo/YesNo.tsx:99:60 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom.development.js:15486:18 ❯ updateForwardRef node_modules/react-dom/cjs/react-dom.development.js:19245:20 ❯ beginWork node_modules/react-dom/cjs/react-dom.development.js:21675:16 ❯ beginWork$1 node_modules/react-dom/cjs/react-dom.development.js:27465:14 ❯ performUnitOfWork node_modules/react-dom/cjs/react-dom.development.js:26599:12 ❯ workLoopSync node_modules/react-dom/cjs/react-dom.development.js:26505:5 ❯ renderRootSync node_modules/react-dom/cjs/react-dom.development.js:26473:7 ❯ recoverFromConcurrentError node_modules/react-dom/cjs/react-dom.development.js:25889:20 ❯ performConcurrentWorkOnRoot node_modules/react-dom/cjs/react-dom.development.js:25789:22

Check failure on line 99 in frontend/src/components/Field/YesNo/YesNo.tsx

View workflow job for this annotation

GitHub Actions / frontend_test

src/templates/Field/YesNo/YesNoField.test.tsx > ValidationOptional > renders success when selected field is submitted

TypeError: Cannot read properties of undefined (reading 'Yes') ❯ src/components/Field/YesNo/YesNo.tsx:99:60 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom.development.js:15486:18 ❯ updateForwardRef node_modules/react-dom/cjs/react-dom.development.js:19245:20 ❯ beginWork node_modules/react-dom/cjs/react-dom.development.js:21675:16 ❯ beginWork$1 node_modules/react-dom/cjs/react-dom.development.js:27465:14 ❯ performUnitOfWork node_modules/react-dom/cjs/react-dom.development.js:26599:12 ❯ workLoopSync node_modules/react-dom/cjs/react-dom.development.js:26505:5 ❯ renderRootSync node_modules/react-dom/cjs/react-dom.development.js:26473:7 ❯ recoverFromConcurrentError node_modules/react-dom/cjs/react-dom.development.js:25889:20 ❯ performConcurrentWorkOnRoot node_modules/react-dom/cjs/react-dom.development.js:25789:22
const noLabel = YES_NO_TRANSLATIONS[selectedLanguage].No

Expand Down
6 changes: 3 additions & 3 deletions frontend/src/components/FormEndPage/EndPageBlock.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useEffect, useMemo, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import { Box, Text, VisuallyHidden } from '@chakra-ui/react'
import { format } from 'date-fns'

Expand All @@ -18,7 +19,6 @@ export interface EndPageBlockProps {
colorTheme?: FormColorTheme
focusOnMount?: boolean
isButtonHidden?: boolean
selectedLanguage: Language
}

export const EndPageBlock = ({
Expand All @@ -28,8 +28,8 @@ export const EndPageBlock = ({
colorTheme = FormColorTheme.Blue,
focusOnMount,
isButtonHidden,
selectedLanguage,
}: EndPageBlockProps): JSX.Element => {
const { i18n } = useTranslation()
const focusRef = useRef<HTMLDivElement>(null)
useEffect(() => {
if (focusOnMount) {
Expand All @@ -46,7 +46,7 @@ export const EndPageBlock = ({
},
})

console.log(selectedLanguage)
const selectedLanguage = i18n.language as Language

const title = getValueInSelectedLanguage({
defaultValue: endPage.title,
Expand Down
12 changes: 4 additions & 8 deletions frontend/src/components/Radio/Radio.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,6 @@ export interface RadioProps
* @default true
*/
allowDeselect?: boolean

/**
* Selected language to get translated label.
*/
selectedLanguage?: Language
}

type RadioWithSubcomponentProps = ComponentWithAs<'input', RadioProps> & {
Expand Down Expand Up @@ -261,7 +256,7 @@ export const Radio = forwardRef<RadioProps, 'input'>(
* Wrapper for the radio part of the Others option.
*/
const OthersRadio = forwardRef<RadioProps, 'input'>((props, ref) => {
const { t } = useTranslation()
const { t, i18n } = useTranslation()
const { othersRadioRef, othersInputRef } = useRadioGroupWithOthers()
const { value: valueProp } = props
const styles = useMultiStyleConfig(RADIO_THEME_KEY, {
Expand All @@ -278,9 +273,10 @@ const OthersRadio = forwardRef<RadioProps, 'input'>((props, ref) => {
isChecked = group.value === valueProp
}

const selectedLanguage = i18n.language as Language
const othersLabel = useMemo(() => {
return OTHERS_TRANSLATED_LABEL[props?.selectedLanguage ?? Language.ENGLISH]
}, [props?.selectedLanguage])
return OTHERS_TRANSLATED_LABEL[selectedLanguage ?? Language.ENGLISH]
}, [selectedLanguage])

useEffect(() => {
if (isChecked) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,6 @@ export const StartPageView = () => {
<FormInstructions
content={startPage?.paragraph}
colorTheme={startPage?.colorTheme}
title="Instructions"
/>
</Box>
{isMobile ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
FormColorTheme,
FormLogoState,
FormResponseMode,
Language,
} from '~shared/types'

import { EndPageBlock } from '~components/FormEndPage/EndPageBlock'
Expand Down Expand Up @@ -121,7 +120,6 @@ export const EndPageContent = (): JSX.Element => {
timestamp: Date.now(),
}}
colorTheme={colorTheme ?? FormColorTheme.Blue}
selectedLanguage={Language.ENGLISH}
/>
</Box>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { datadogLogs } from '@datadog/browser-logs'
import get from 'lodash/get'
import simplur from 'simplur'

import { FormAuthType, FormResponseMode, Language } from '~shared/types/form'
import { FormAuthType, FormResponseMode } from '~shared/types/form'

import { usePreviewForm } from '~/features/admin-form/common/queries'
import { FormNotFound } from '~/features/public-form/components/FormNotFound'
Expand Down Expand Up @@ -46,9 +46,6 @@ export const PreviewFormProvider = ({
/* enabled= */ !submissionData,
)

const [selectedPublicFormLanguage, setSelectedPublicFormLanguage] =
useState<Language>(Language.ENGLISH)

const { data: { useFetchForSubmissions } = {} } = useEnv()

const { isNotFormId, toast, vfnToastIdRef, expiryInMs, ...commonFormValues } =
Expand Down Expand Up @@ -342,8 +339,6 @@ export const PreviewFormProvider = ({
isPreview: true,
hasSingleSubmissionValidationError: false,
hasRespondentNotWhitelistedError: false,
setSelectedPublicFormLanguage,
selectedPublicFormLanguage,
...commonFormValues,
...data,
...rest,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { useCallback, useEffect, useMemo, useState } from 'react'
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'

import { FormAuthType, Language } from '~shared/types/form'
import { FormAuthType } from '~shared/types/form'

import { useFormTemplate } from '~/features/admin-form/common/queries'
import { FormNotFound } from '~/features/public-form/components/FormNotFound'
Expand All @@ -31,9 +31,6 @@ export const TemplateFormProvider = ({
const { isNotFormId, toast, vfnToastIdRef, expiryInMs, ...commonFormValues } =
useCommonFormProvider(formId)

const [selectedPublicFormLanguage, setSelectedPublicFormLanguage] =
useState<Language>(Language.ENGLISH)

useEffect(() => {
return () => {
document.title = 'FormSG'
Expand Down Expand Up @@ -97,8 +94,6 @@ export const TemplateFormProvider = ({
isPaymentEnabled: false,
hasSingleSubmissionValidationError: false,
hasRespondentNotWhitelistedError: false,
setSelectedPublicFormLanguage,
selectedPublicFormLanguage,
...commonFormValues,
...data,
...rest,
Expand Down
8 changes: 1 addition & 7 deletions frontend/src/features/public-form/PublicFormContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
import { UseQueryResult } from 'react-query'

import { MultirespondentSubmissionDto } from '~shared/types'
import { Language, PublicFormViewDto } from '~shared/types/form'
import { PublicFormViewDto } from '~shared/types/form'

import { decryptSubmission } from './utils/decryptSubmission'

Expand Down Expand Up @@ -76,12 +76,6 @@ export interface PublicFormContextProps
setPreviousSubmission?: (
previousSubmission: ReturnType<typeof decryptSubmission>,
) => void

// callback to set public form's language
setSelectedPublicFormLanguage: (language: Language) => void

// public form's language
selectedPublicFormLanguage: Language
}

export const PublicFormContext = createContext<
Expand Down
5 changes: 0 additions & 5 deletions frontend/src/features/public-form/PublicFormProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import { ErrorCode } from '~shared/types/errorCodes'
import {
FormAuthType,
FormResponseMode,
Language,
ProductItem,
PublicFormDto,
} from '~shared/types/form'
Expand Down Expand Up @@ -205,8 +204,6 @@ export const PublicFormProvider = ({
)

const [numVisibleFields, setNumVisibleFields] = useState(0)
const [selectedPublicFormLanguage, setSelectedPublicFormLanguage] =
useState<Language>(Language.ENGLISH)

// Respondent access error states
const [
Expand Down Expand Up @@ -951,8 +948,6 @@ export const PublicFormProvider = ({
previousSubmission,
previousAttachments,
setPreviousSubmission,
selectedPublicFormLanguage,
setSelectedPublicFormLanguage,
...commonFormValues,
...data,
...rest,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Container, Flex, Stack, StackDivider } from '@chakra-ui/react'

import { FormColorTheme, FormDto, Language } from '~shared/types/form'
import { FormColorTheme, FormDto } from '~shared/types/form'

import {
SubmissionData,
Expand All @@ -19,7 +19,6 @@ export interface FormEndPageProps {
handleSubmitFeedback: (inputs: FeedbackFormInput) => void
isFeedbackSectionHidden: boolean
colorTheme: FormColorTheme
selectedLanguage: Language
}

export const FormEndPage = ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,7 @@ import { PaymentEndPagePreview } from './components/PaymentEndPagePreview'
import { FormEndPage } from './FormEndPage'

export const FormEndPageContainer = (): JSX.Element | null => {
const {
form,
formId,
submissionData,
isPreview,
selectedPublicFormLanguage,
} = usePublicFormContext()
const { form, formId, submissionData, isPreview } = usePublicFormContext()
const { submitFormFeedbackMutation } = useSubmitFormFeedbackMutation(
formId,
submissionData?.id ?? '',
Expand Down Expand Up @@ -93,7 +87,6 @@ export const FormEndPageContainer = (): JSX.Element | null => {
endPage={form.endPage}
isFeedbackSectionHidden={isFeedbackHidden}
handleSubmitFeedback={handleSubmitFeedback}
selectedLanguage={selectedPublicFormLanguage}
/>
</Box>
)
Expand Down
Loading

0 comments on commit 2d3a53c

Please sign in to comment.