Skip to content

Commit

Permalink
Merge pull request #8027 from opengovsg/release_v6.173.0
Browse files Browse the repository at this point in the history
* fix(deps): bump fp-ts from 2.16.8 to 2.16.9 (#8017)

Bumps [fp-ts](https://github.com/gcanti/fp-ts) from 2.16.8 to 2.16.9.
- [Release notes](https://github.com/gcanti/fp-ts/releases)
- [Changelog](https://github.com/gcanti/fp-ts/blob/master/CHANGELOG.md)
- [Commits](https://github.com/gcanti/fp-ts/commits/2.16.9)

---
updated-dependencies:
- dependency-name: fp-ts
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* feat: multi lang feature (#8022)

* feat: add model fields to represent translations for form fields (#7457)

* feat: add fields in model to represent translations for form fields

* feat: add shared types to represent form field translations

* fix: use unicode locales

* feat: add settings tab and toggle for multi lang feature (#7561)

* feat: add settings tab and toggle for multi lang feature

* feat: add fields to joi validations

* fix: refactor code to address comments

* fix: failing tests

* feat: add tooltip over icons

* feat: add chromatic tests

* feat: add form field list for translation (#7778)

* feat: add model fields to represent translations for form fields (#7457)

* feat: add fields in model to represent translations for form fields

* feat: add shared types to represent form field translations

* fix: use unicode locales

* feat: add settings tab and toggle for multi lang feature (#7561)

* feat: add settings tab and toggle for multi lang feature

* feat: add fields to joi validations

* fix: refactor code to address comments

* fix: failing tests

* feat: add tooltip over icons

* feat: add chromatic tests

* feat: add view to list form fields for translation

* feat: update logic for checking completion of translations and add storybook tests

* chore: uncomment growthbook middleware usage

* chore: update storybook function used

* chore: address comments

* chore: update app router

* feat: add view for translation input (#7895)

* feat: add view for translation input

* fix: refactor code and remove console log statements

* chore: clean up code

* chore: fix alignment of translation input and header

* chore: address comments

* feat: add translations to public form (#7976)

* feat: add model fields to represent translations for form fields (#7457)

* feat: add fields in model to represent translations for form fields

* feat: add shared types to represent form field translations

* fix: use unicode locales

* feat: add settings tab and toggle for multi lang feature (#7561)

* feat: add settings tab and toggle for multi lang feature

* feat: add fields to joi validations

* fix: refactor code to address comments

* fix: failing tests

* feat: add tooltip over icons

* feat: add chromatic tests

* feat: add translations on public form

* fix: missing provider values for preview and template providers

* feat: add fixed translations for yes and no field

* chore: fix code

* chore: remove unused code and add import

* feat: add fixed translations for est time taken string

* chore: remove unnecessary loggin

* feat: add translations for verifiable fields

* chore: fix tests

* feat: add error if user adds less than required translations for form fields with options

* feat: add beta flag for multi language translation feature for admins

* chore: refactor code

* feat: add fixed translations for default placeholders on public form

* feat: add fixed translations for not found label

* feat: add translations for maximum file size label

* feat: add translations for others label

* feat: add fixed translations for add row label

* feat: add translation for prevent submission messages

* fix: make prevent submission translations optional

* fix: tests

* refactor: use i18next to toggle languages

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

* fix: stub i18next for YesNo,Email,Verify fields

* refactor(i18n): move translations from `fixedTranslations`

* refactor(i18n): extract hard-coded translations

* feat: add title translations for MyInfo fields

* feat: always default language back to English on CreatePage

* fix: update myInfo translations and address comments

* feat: add custom header to track user selected form language

---------

Co-authored-by: LoneRifle <[email protected]>

---------

Co-authored-by: LoneRifle <[email protected]>

* fix: update mongodb binary version to 6 (#8032)

* chore: bump version to v6.173.0

---------

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Siddarth Nandanahosur Suresh <[email protected]>
Co-authored-by: LoneRifle <[email protected]>
  • Loading branch information
4 people authored Jan 8, 2025
2 parents 69137b3 + 54e544d commit 2d36463
Show file tree
Hide file tree
Showing 121 changed files with 4,022 additions and 243 deletions.
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,31 @@ All notable changes to this project will be documented in this file. Dates are d

Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).

#### [v6.173.0](https://github.com/opengovsg/FormSG/compare/v6.173.0...v6.173.0)

- fix: update mongodb binary version to 6 [`#8032`](https://github.com/opengovsg/FormSG/pull/8032)

#### [v6.173.0](https://github.com/opengovsg/FormSG/compare/v6.172.0...v6.173.0)

> 7 January 2025

- feat: multi lang feature [`#8022`](https://github.com/opengovsg/FormSG/pull/8022)
- fix(deps): bump fp-ts from 2.16.8 to 2.16.9 [`#8017`](https://github.com/opengovsg/FormSG/pull/8017)
- build: merge release v6.172.0 to develop [`#8016`](https://github.com/opengovsg/FormSG/pull/8016)
- build: release v6.172.0 [`#8015`](https://github.com/opengovsg/FormSG/pull/8015)
- chore: bump version to v6.173.0 [`1cb7772`](https://github.com/opengovsg/FormSG/commit/1cb7772374249830091d1fb3278a035957d23bcb)

#### [v6.172.0](https://github.com/opengovsg/FormSG/compare/v6.171.0...v6.172.0)

> 23 December 2024

- fix(deps): bump next and react-email [`#8012`](https://github.com/opengovsg/FormSG/pull/8012)
- fix(deps): bump type-fest from 4.26.1 to 4.30.2 in /shared [`#8009`](https://github.com/opengovsg/FormSG/pull/8009)
- build(deps): bump next and react-email in /react-email-preview [`#8011`](https://github.com/opengovsg/FormSG/pull/8011)
- fix(workflow): set approval toggle header size to h4 [`#8010`](https://github.com/opengovsg/FormSG/pull/8010)
- build: merge release v6.171.0 to develop [`#8006`](https://github.com/opengovsg/FormSG/pull/8006)
- build: release v6.171.0 [`#8003`](https://github.com/opengovsg/FormSG/pull/8003)
- chore: bump version to v6.172.0 [`84600fa`](https://github.com/opengovsg/FormSG/commit/84600faa9303791a3b438928adb5c357e32c5236)

#### [v6.171.0](https://github.com/opengovsg/FormSG/compare/v6.170.0...v6.171.0)

Expand Down
2 changes: 1 addition & 1 deletion __tests__/setup/.test-env
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ PAYMENT_PROOF_S3_BUCKET=local-payment-proof-bucket
NODE_ENV=test
FORMSG_SDK_MODE=test

MONGO_BINARY_VERSION=4.0.22
MONGO_BINARY_VERSION=6.0.19
MOCK_WEBHOOK_CONFIG_FILE=webhook-server-config.csv
MOCK_WEBHOOK_PORT=4000

Expand Down
2 changes: 1 addition & 1 deletion __tests__/setup/database.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ class MemoryDatabaseServer {
constructor() {
this.mongod = new MongoMemoryServer({
binary: {
version: process.env.MONGO_BINARY_VERSION || '4.0.22',
version: process.env.MONGO_BINARY_VERSION || '6.0.19',
checkMD5: true,
},
instance: {},
Expand Down
6 changes: 6 additions & 0 deletions __tests__/unit/backend/helpers/generate-form-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ export const generateDefaultField = (
fieldType,
required: true,
disabled: false,
titleTranslations: [],
descriptionTranslations: [],
}
switch (fieldType) {
case BasicField.Table:
Expand All @@ -102,6 +104,7 @@ export const generateDefaultField = (
return {
...defaultParams,
fieldOptions: ['Option 1', 'Option 2'],
fieldOptionsTranslations: [],
getQuestion: () => defaultParams.title,
ValidationOptions: {
customMin: null,
Expand Down Expand Up @@ -141,6 +144,7 @@ export const generateDefaultField = (
return {
...defaultParams,
fieldOptions: ['Option 1', 'Option 2'],
fieldOptionsTranslations: [],
getQuestion: () => defaultParams.title,
...customParams,
} as IDropdownFieldSchema
Expand Down Expand Up @@ -401,6 +405,7 @@ export const generateTableDropdownColumn = (
required: true,
_id: new ObjectId().toHexString(),
fieldOptions: ['a', 'b', 'c'],
fieldOptionsTranslations: [],
...customParams,
toObject() {
// mock toObject method of mongoose document
Expand All @@ -410,6 +415,7 @@ export const generateTableDropdownColumn = (
required: true,
_id: new ObjectId().toHexString(),
fieldOptions: ['a', 'b', 'c'],
fieldOptionsTranslations: [],
...customParams,
}
},
Expand Down
4 changes: 2 additions & 2 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "form-frontend",
"version": "6.172.0",
"version": "6.173.0",
"homepage": ".",
"type": "module",
"private": true,
Expand Down
16 changes: 16 additions & 0 deletions frontend/src/assets/icons/LanguageTranslation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export const LanguageTranslation = (
props: React.SVGProps<SVGSVGElement>,
): JSX.Element => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={24}
height={24}
fill="currentColor"
className="bi bi-translate"
viewBox="0 0 16 16"
{...props}
>
<path d="M4.545 6.714 4.11 8H3l1.862-5h1.284L8 8H6.833l-.435-1.286zm1.634-.736L5.5 3.956h-.049l-.679 2.022z" />
<path d="M0 2a2 2 0 0 1 2-2h7a2 2 0 0 1 2 2v3h3a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2v-3H2a2 2 0 0 1-2-2zm2-1a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h7a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zm7.138 9.995q.289.451.63.846c-.748.575-1.673 1.001-2.768 1.292.178.217.451.635.555.867 1.125-.359 2.08-.844 2.886-1.494.777.665 1.739 1.165 2.93 1.472.133-.254.414-.673.629-.89-1.125-.253-2.057-.694-2.82-1.284.681-.747 1.222-1.651 1.621-2.757H14V8h-3v1.047h.765c-.318.844-.74 1.546-1.272 2.13a6 6 0 0 1-.415-.492 2 2 0 0 1-.94.31" />
</svg>
)
2 changes: 1 addition & 1 deletion frontend/src/components/Checkbox/Checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ const OthersCheckbox = forwardRef<CheckboxProps, 'input'>((props, ref) => {
{...props}
onChange={handleCheckboxChange}
>
{t('features.adminForm.sidebar.fields.radio.others')}
{t('features.publicForm.components.fields.option.others')}
</Checkbox>
)
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ export const SingleSelectProvider = ({
onChange,
name,
filter = defaultFilter,
nothingFoundLabel = 'No matching results',
placeholder: placeholderProp,
clearButtonLabel = 'Clear selection',
isClearable = true,
Expand Down Expand Up @@ -83,9 +82,16 @@ export const SingleSelectProvider = ({

const placeholder = useMemo(() => {
if (placeholderProp === null) return ''
return placeholderProp ?? t('features.common.dropdown.placeholder')
return (
placeholderProp ??
t('features.publicForm.components.fields.dropdown.placeholder')
)
}, [placeholderProp, t])

const nothingFoundLabel = t(
'features.publicForm.components.fields.dropdown.nothingFound',
)

const getFilteredItems = useCallback(
(filterValue?: string) =>
filterValue ? filter(items, filterValue) : items,
Expand Down
14 changes: 7 additions & 7 deletions frontend/src/components/Field/Attachment/Attachment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,14 +148,14 @@ export const Attachment = forwardRef<AttachmentProps, 'div'>(
case 'file-invalid-type': {
const fileExt = getFileExtension(rejectedFiles[0].file.name)
errorMessage = t(
`features.adminForm.sidebar.fields.imageAttachment.error.fileInvalidType`,
`features.publicForm.components.fields.attachment.error.fileInvalidType`,
{ fileExt },
)
break
}
case 'too-many-files': {
errorMessage = t(
`features.adminForm.sidebar.fields.imageAttachment.error.tooManyFiles`,
`features.publicForm.components.fields.attachment.error.tooManyFiles`,
)
break
}
Expand All @@ -178,15 +178,15 @@ export const Attachment = forwardRef<AttachmentProps, 'div'>(
const stringOfInvalidExtensions = invalidFilesInZip.join(', ')
return onError?.(
t(
'features.adminForm.sidebar.fields.imageAttachment.error.zipFileInvalidType',
'features.publicForm.components.fields.attachment.error.zipFileInvalidType',
{ stringOfInvalidExtensions },
),
)
}
} catch {
return onError?.(
t(
'features.adminForm.sidebar.fields.imageAttachment.error.zipParsing',
'features.publicForm.components.fields.attachment.error.zipParsing',
),
)
}
Expand Down Expand Up @@ -224,7 +224,7 @@ export const Attachment = forwardRef<AttachmentProps, 'div'>(
return {
code: 'file-too-large',
message: t(
'features.adminForm.sidebar.fields.imageAttachment.error.fileTooLarge',
'features.publicForm.components.fields.attachment.error.fileTooLarge',
{ readableMaxSize },
),
}
Expand All @@ -233,7 +233,7 @@ export const Attachment = forwardRef<AttachmentProps, 'div'>(
return {
code: 'file-empty',
message: t(
'features.adminForm.sidebar.fields.imageAttachment.error.zipParsing',
'features.publicForm.components.fields.attachment.error.zipParsing',
),
}
}
Expand Down Expand Up @@ -336,7 +336,7 @@ export const Attachment = forwardRef<AttachmentProps, 'div'>(
aria-hidden
>
{t(
'features.adminForm.sidebar.fields.imageAttachment.maxFileSize',
'features.publicForm.components.fields.attachment.maxFileSize',
{
readableMaxSize,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ export const AttachmentDropzone = ({
<Text aria-hidden>
<Link isDisabled={inputProps.disabled}>
{t(
'features.adminForm.sidebar.fields.imageAttachment.fileUploaderLink',
'features.publicForm.components.fields.attachment.fileUploaderLink',
)}
</Link>
{t('features.adminForm.sidebar.fields.imageAttachment.dragAndDrop')}
{t('features.publicForm.components.fields.attachment.dragAndDrop')}
</Text>
)}
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export const AttachmentFileInfo = ({
variant="clear"
colorScheme="danger"
aria-label={t(
'features.adminForm.sidebar.fields.imageAttachment.ariaLabelRemove',
'features.publicForm.components.fields.attachment.ariaLabelRemove',
)}
icon={<BiTrash />}
onClick={handleRemoveFile}
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/Field/YesNo/YesNo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export const YesNo = forwardRef<YesNoProps, 'input'>(
{...noProps}
onChange={(value) => onChange(value as YesNoOptionValue)}
leftIcon={BiX}
label={t('features.adminForm.sidebar.fields.yesNo.no')}
label={t('features.publicForm.components.fields.yesNo.no')}
// Ref is set here for tracking current value, and also so any errors
// can focus this input.
ref={ref}
Expand All @@ -100,7 +100,7 @@ export const YesNo = forwardRef<YesNoProps, 'input'>(
{...yesProps}
onChange={(value) => onChange(value as YesNoOptionValue)}
leftIcon={BiCheck}
label={t('features.adminForm.sidebar.fields.yesNo.yes')}
label={t('features.publicForm.components.fields.yesNo.yes')}
title={props.title}
/>
</HStack>
Expand Down
27 changes: 21 additions & 6 deletions frontend/src/components/FormEndPage/EndPageBlock.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { useEffect, useMemo, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import { Box, Text, VisuallyHidden } from '@chakra-ui/react'
import { format } from 'date-fns'

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

import { useMdComponents } from '~hooks/useMdComponents'
import { getValueInSelectedLanguage } from '~utils/multiLanguage'
import Button from '~components/Button'
import { MarkdownText } from '~components/MarkdownText'

Expand All @@ -27,6 +29,7 @@ export const EndPageBlock = ({
focusOnMount,
isButtonHidden,
}: EndPageBlockProps): JSX.Element => {
const { i18n } = useTranslation()
const focusRef = useRef<HTMLDivElement>(null)
useEffect(() => {
if (focusOnMount) {
Expand All @@ -43,6 +46,20 @@ export const EndPageBlock = ({
},
})

const selectedLanguage = i18n.language as Language

const title = getValueInSelectedLanguage({
defaultValue: endPage.title,
translations: endPage.titleTranslations,
selectedLanguage,
})

const paragraph = getValueInSelectedLanguage({
defaultValue: endPage.paragraph ?? '',
translations: endPage.paragraphTranslations,
selectedLanguage,
})

const submissionTimestamp = useMemo(
() => format(new Date(submissionData.timestamp), 'dd MMM yyyy, HH:mm:ss z'),
[submissionData.timestamp],
Expand All @@ -62,13 +79,11 @@ export const EndPageBlock = ({
{submittedAriaText}
</VisuallyHidden>
<Text as="h2" textStyle="h2" textColor="secondary.500">
{endPage.title}
{title}
</Text>
{endPage.paragraph ? (
{paragraph ? (
<Box mt="0.75rem">
<MarkdownText components={mdComponents}>
{endPage.paragraph}
</MarkdownText>
<MarkdownText components={mdComponents}>{paragraph}</MarkdownText>
</Box>
) : null}
</Box>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/Radio/Radio.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ const OthersRadio = forwardRef<RadioProps, 'input'>((props, ref) => {
// Required should apply to radio group rather than individual radio.
isRequired={false}
>
{t('features.adminForm.sidebar.fields.radio.others')}
{t('features.publicForm.components.fields.option.others')}
</Radio>
)
})
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/constants/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,7 @@ export const ACTIVE_ADMINFORM_RESULTS_ROUTE_REGEX = new RegExp(

export const PAYMENT_PAGE_SUBROUTE = 'payment/:paymentId'
export const EDIT_SUBMISSION_PAGE_SUBROUTE = 'edit/:submissionId'

// Search param keys for multi-language
export const UNICODE_LOCALE = 'unicodeLocale'
export const TRANSLATION_INPUT = 'translationInput'
2 changes: 0 additions & 2 deletions frontend/src/constants/validation.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
export const REQUIRED_ERROR = 'This field is required'

export const INVALID_EMAIL_ERROR = 'Please enter a valid email'
export const INVALID_EMAIL_DOMAIN_ERROR =
'The entered email does not belong to an allowed email domain'

export const INVALID_DROPDOWN_OPTION_ERROR =
'Entered value is not a valid dropdown option'
Expand Down
9 changes: 8 additions & 1 deletion frontend/src/features/admin-form/create/CreatePage.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { useEffect, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { useNavigate, useParams } from 'react-router-dom'
import { Flex } from '@chakra-ui/react'

import { Language } from '~shared/types'

import { FEATURE_TOUR_KEY_PREFIX } from '~constants/localStorage'
import { ADMINFORM_RESULTS_SUBROUTE, ADMINFORM_ROUTE } from '~constants/routes'
import { useLocalStorage } from '~hooks/useLocalStorage'
Expand All @@ -28,12 +31,16 @@ export const CreatePage = (): JSX.Element => {
const { hasEditAccess, isLoading: isCollabLoading } =
useAdminFormCollaborators(formId)
const navigate = useNavigate()
const { i18n } = useTranslation()

// Redirect view-only collaborators to results screen.
useEffect(() => {
// Always default language key back to English
i18n.changeLanguage(Language.ENGLISH)

if (!isCollabLoading && !hasEditAccess)
navigate(`${ADMINFORM_ROUTE}/${formId}/${ADMINFORM_RESULTS_SUBROUTE}`)
}, [formId, hasEditAccess, isCollabLoading, navigate])
}, [formId, hasEditAccess, i18n, isCollabLoading, navigate])

const { user, isLoading } = useUser()
const localStorageFeatureTourKey = useMemo(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ export const EditCheckbox = ({ field }: EditCheckboxProps): JSX.Element => {
<FormControl isReadOnly={isLoading}>
<Toggle
{...register('othersRadioButton')}
label={t('features.adminForm.sidebar.fields.radio.others')}
label={t('features.publicForm.components.fields.option.others')}
/>
</FormControl>
<FormControl
Expand Down
Loading

0 comments on commit 2d36463

Please sign in to comment.