Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: multi-lang feature #7450

Draft
wants to merge 36 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
3ad1914
Edit form settings schema to store default language
siddarth2824 Jan 29, 2024
495128c
Add views for multi language in settings page
siddarth2824 Jan 29, 2024
e394f6a
Add mutations to select default form language
siddarth2824 Jan 29, 2024
4472c60
Make default language first in list of languages
siddarth2824 Jan 30, 2024
1229402
Add ability to toggle on and off which languages are supported
siddarth2824 Jan 30, 2024
4a4041c
Add question list views for each language
siddarth2824 Feb 4, 2024
36fe1de
Add tooltip to show that myinfo fields cannot be translated
siddarth2824 Feb 4, 2024
3cdd756
feat: add routing to ensure views in multi-language persist
siddarth2824 Feb 14, 2024
563d16e
Add translation page
siddarth2824 Feb 19, 2024
9daff0e
feat: add title translation field in schema
siddarth2824 Feb 28, 2024
4bb15c0
feat: add previous translation as default value
siddarth2824 Feb 28, 2024
1f0729c
feat: add icons to show completion of translations
siddarth2824 Feb 28, 2024
7ec0d6a
feat: add description translation input
siddarth2824 Mar 6, 2024
b357cf9
feat: display translations
siddarth2824 May 8, 2024
bc3ef8c
feat: display translations on public form
siddarth2824 May 21, 2024
edcffa9
feat: show translations for start and end page
siddarth2824 May 21, 2024
807ce79
chore: amend comments
siddarth2824 May 21, 2024
2013738
feat: add translations for section
siddarth2824 May 23, 2024
f48e3e3
feat: add translations for field options
siddarth2824 May 24, 2024
3baa595
fix: extend width of question row in translations section
siddarth2824 May 26, 2024
51c3b24
fix: remove divider below Tamil language
siddarth2824 May 26, 2024
f387a52
fix: support all languages on the first instance of turning on multi …
siddarth2824 May 26, 2024
a6b5675
fix: update mutation
siddarth2824 May 26, 2024
d069efb
fix: remove language dropdown if form does not support multi lang
siddarth2824 May 26, 2024
11a5063
fix: change padding between languages and toggle section
siddarth2824 May 26, 2024
d0c332c
feat: add padding to language and size control component for mobile view
siddarth2824 May 26, 2024
5b3a0e1
feat: add translations for table field
siddarth2824 Jun 4, 2024
c2b14f4
fix: merge conflicts
siddarth2824 Jun 6, 2024
b956b1b
fix: validation errors for empty title translations
siddarth2824 Jun 10, 2024
7048d0b
fix: duplicate variable export
siddarth2824 Jun 10, 2024
40d39bd
fix: use translations as only labels in multi option fields
siddarth2824 Jun 17, 2024
9aee902
chore: remove text resize
siddarth2824 Jun 17, 2024
571daeb
feat: do not render language control in form end page
siddarth2824 Jun 17, 2024
994e7e3
fix: failing tests
siddarth2824 Jun 18, 2024
56706ea
fix: add translated description for section
siddarth2824 Jul 2, 2024
f07fcf5
fix: show default english field options when no translations are avai…
siddarth2824 Jul 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"eslint.validate": ["javascript", "typescript"],
"eslint.validate": [
"javascript",
"typescript"
],
"editor.formatOnSave": true,
"[javascript]": {
"editor.formatOnSave": false
Expand All @@ -16,10 +19,11 @@
},
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"typescript.enablePromptUseWorkspaceTsdk": true,
"typescript.tsdk": "node_modules/typescript/lib",
"markdown.extension.toc.omittedFromToc": {
"README.md": [
"## Table of Contents"
]
]
}
}
}
5 changes: 5 additions & 0 deletions __tests__/unit/backend/helpers/generate-form-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ export const generateDefaultField = (
fieldType,
required: true,
disabled: false,
descriptionTranslations: [],
titleTranslations: [],
}
switch (fieldType) {
case BasicField.Table:
Expand All @@ -77,6 +79,7 @@ export const generateDefaultField = (
return {
...defaultParams,
fieldOptions: ['Option 1', 'Option 2'],
fieldOptionsTranslations: [],
getQuestion: () => defaultParams.title,
ValidationOptions: {
customMin: null,
Expand Down Expand Up @@ -116,6 +119,7 @@ export const generateDefaultField = (
return {
...defaultParams,
fieldOptions: ['Option 1', 'Option 2'],
fieldOptionsTranslations: [],
getQuestion: () => defaultParams.title,
...customParams,
} as IDropdownFieldSchema
Expand Down Expand Up @@ -385,6 +389,7 @@ export const generateTableDropdownColumn = (
required: true,
_id: new ObjectId().toHexString(),
fieldOptions: ['a', 'b', 'c'],
fieldOptionsTranslations: [],
...customParams,
}
},
Expand Down
2 changes: 1 addition & 1 deletion frontend/public/index.html
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we shouldn't commit this change!

Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
name="twitter:image"
content="%PUBLIC_URL%/static/images/__OG_IMAGE__"
/>
</head>
<script src="../static/js/datadog-chunk.2f85e1b6f6216b65d9d0.js"></script><script src="../static/js/datadog-chunk.2f85e1b6f6216b65d9d0.js"></script><script src="../static/js/datadog-chunk.2f85e1b6f6216b65d9d0.js"></script><script src="../static/js/datadog-chunk.2f85e1b6f6216b65d9d0.js"></script><script src="../static/js/datadog-chunk.2f85e1b6f6216b65d9d0.js"></script></head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
Expand Down
10 changes: 9 additions & 1 deletion frontend/src/app/AppRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
ADMINFORM_PREVIEW_ROUTE,
ADMINFORM_RESULTS_SUBROUTE,
ADMINFORM_ROUTE,
ADMINFORM_SETTINGS_MULTI_LANGUAGE_TRANSLATION_SUBROUTE,
ADMINFORM_SETTINGS_SUBROUTE,
ADMINFORM_USETEMPLATE_ROUTE,
BILLING_ROUTE,
Expand Down Expand Up @@ -165,7 +166,14 @@ export const AppRouter = (): JSX.Element => {
>
<Route index element={<CreatePage />} />
<Route path={ADMINFORM_SETTINGS_SUBROUTE} element={<SettingsPage />}>
<Route path={':settingsTab'} element={<SettingsPage />} />
<Route path={':settingsTab'} element={<SettingsPage />}>
<Route path={':language'} element={<SettingsPage />}>
<Route
path={ADMINFORM_SETTINGS_MULTI_LANGUAGE_TRANSLATION_SUBROUTE}
element={<SettingsPage />}
/>
</Route>
</Route>
</Route>
<Route
path={ADMINFORM_RESULTS_SUBROUTE}
Expand Down
15 changes: 15 additions & 0 deletions frontend/src/assets/icons/LanguageTranslation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export const LanguageTranslation = (
props: React.SVGProps<SVGSVGElement>,

Check warning on line 2 in frontend/src/assets/icons/LanguageTranslation.tsx

View workflow job for this annotation

GitHub Actions / frontend_lint

'props' is defined but never used

Check warning on line 2 in frontend/src/assets/icons/LanguageTranslation.tsx

View workflow job for this annotation

GitHub Actions / frontend_lint

'props' is defined but never used
): 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"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
viewBox="0 0 16 16"
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>
)
46 changes: 40 additions & 6 deletions frontend/src/components/FormEndPage/EndPageBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useEffect, useMemo, useRef } from 'react'
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 Button from '~components/Button'
Expand All @@ -17,6 +17,7 @@ export interface EndPageBlockProps {
colorTheme?: FormColorTheme
focusOnMount?: boolean
isButtonHidden?: boolean
selectedLanguage: Language
}

export const EndPageBlock = ({
Expand All @@ -26,6 +27,7 @@ export const EndPageBlock = ({
colorTheme = FormColorTheme.Blue,
focusOnMount,
isButtonHidden,
selectedLanguage,
}: EndPageBlockProps): JSX.Element => {
const focusRef = useRef<HTMLDivElement>(null)
useEffect(() => {
Expand All @@ -43,6 +45,40 @@ export const EndPageBlock = ({
},
})

const title = useMemo(() => {
let content = endPage.title

if (selectedLanguage !== Language.ENGLISH) {
const translations = endPage.titleTranslations ?? []
const titleTranslationIdx = translations.findIndex(
(translation) => translation.language === selectedLanguage,
)

if (titleTranslationIdx !== -1) {
content = translations[titleTranslationIdx].translation
}
}

return content
}, [endPage.title, endPage.titleTranslations, selectedLanguage])

const paragraph = useMemo(() => {
let content = endPage?.paragraph

if (selectedLanguage !== Language.ENGLISH) {
const translations = endPage.paragraphTranslations ?? []
const paragraphTranslationIdx = translations.findIndex(
(translation) => translation.language === selectedLanguage,
)

if (paragraphTranslationIdx !== -1) {
content = translations[paragraphTranslationIdx].translation
}
}

return content
}, [endPage?.paragraph, endPage.paragraphTranslations, selectedLanguage])

const submissionTimestamp = useMemo(
() => format(new Date(submissionData.timestamp), 'dd MMM yyyy, HH:mm:ss z'),
[submissionData.timestamp],
Expand All @@ -62,13 +98,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
4 changes: 4 additions & 0 deletions frontend/src/constants/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ export const ADMINFORM_USETEMPLATE_ROUTE = 'use-template'
export const ADMINFORM_SETTINGS_SINGPASS_SUBROUTE = `${ADMINFORM_SETTINGS_SUBROUTE}/singpass`
export const ADMINFORM_SETTINGS_PAYMENTS_SUBROUTE = `${ADMINFORM_SETTINGS_SUBROUTE}/payments`

// sub route for multi-language translation
export const ADMINFORM_SETTINGS_MULTI_LANGUAGE_TRANSLATION_SUBROUTE =
'translation'

/**
* Regex for active path matching on adminform builder routes/subroutes.
* @example Breakdown of regex:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ export const StartPageView = () => {
/>
<FormHeader
title={form?.title}
showZoomControl={false}
showHeader
loggedInId={
form && form.authType !== FormAuthType.NIL
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import { useEditFieldForm } from '../common/useEditFieldForm'

import { EditTableColumns } from './EditTableColumns'

const EDIT_TABLE_FIELD_KEYS = [
export const EDIT_TABLE_FIELD_KEYS = [
'title',
'description',
'required',
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/features/admin-form/preview/PreviewFormPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { FormFooter } from '~features/public-form/components/FormFooter'
import FormInstructions from '~features/public-form/components/FormInstructions'
import { PublicFormLogo } from '~features/public-form/components/FormLogo'
import FormStartPage from '~features/public-form/components/FormStartPage'
import LanguageControl from '~features/public-form/components/LanguageControl'
import { PublicFormWrapper } from '~features/public-form/components/PublicFormWrapper'

import { PreviewFormBannerContainer } from '../common/components/PreviewFormBanner'
Expand All @@ -31,6 +32,7 @@ export const PreviewFormPage = (): JSX.Element => {
<FormSectionsProvider>
<PublicFormLogo />
<FormStartPage />
<LanguageControl />
<PublicFormWrapper>
<FormInstructions />
<FormFields />
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 } from '~shared/types/form'
import { FormAuthType, FormResponseMode, Language } from '~shared/types/form'

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

const [publicFormLanguage, setPublicFormLanguage] = useState<Language>(
Language.ENGLISH,
)

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

const { isNotFormId, toast, vfnToastIdRef, expiryInMs, ...commonFormValues } =
Expand Down Expand Up @@ -337,6 +341,8 @@ export const PreviewFormProvider = ({
handleLogout: undefined,
isPaymentEnabled,
isPreview: true,
setPublicFormLanguage,
publicFormLanguage,
...commonFormValues,
...data,
...rest,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { MultiLanguageSection } from './components/MultiLanguageSection/MultiLangugageSection'

export const SettingsMultiLangPage = (): JSX.Element => {
return (
<>
<MultiLanguageSection />
</>
)
}
52 changes: 49 additions & 3 deletions frontend/src/features/admin-form/settings/SettingsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useEffect, useState } from 'react'
import { useEffect, useMemo, useState } from 'react'
import { BiCodeBlock, BiCog, BiDollar, BiKey, BiMessage } from 'react-icons/bi'
import { useNavigate, useParams } from 'react-router-dom'
import { useLocation, useNavigate, useParams } from 'react-router-dom'
import {
Box,
Flex,
Expand All @@ -11,11 +11,15 @@ import {
Tabs,
} from '@chakra-ui/react'

import { LanguageTranslation } from '~assets/icons/LanguageTranslation'
import { ADMINFORM_RESULTS_SUBROUTE, ADMINFORM_ROUTE } from '~constants/routes'
import { useDraggable } from '~hooks/useDraggable'

import { useAdminFormCollaborators } from '../common/queries'

import { MultiLanguageSection } from './components/MultiLanguageSection/MultiLangugageSection'
import { TranslationListSection } from './components/MultiLanguageSection/TranslationListSection'
import { TranslationSection } from './components/MultiLanguageSection/TranslationSection'
import { SettingsTab } from './components/SettingsTab'
import { SettingsAuthPage } from './SettingsAuthPage'
import { SettingsGeneralPage } from './SettingsGeneralPage'
Expand All @@ -29,10 +33,19 @@ const settingsTabsOrder = [
'twilio',
'webhooks',
'payments',
'multi-language',
]

export const SettingsPage = (): JSX.Element => {
const { formId, settingsTab } = useParams()
const { formId, settingsTab, language } = useParams()
const { state } = useLocation()

const translationParams = state as {
isTranslation: boolean
formFieldNum: number
isStartPage: boolean
isEndPage: boolean
}

if (!formId) throw new Error('No formId provided')

Expand All @@ -52,6 +65,23 @@ export const SettingsPage = (): JSX.Element => {
settingsTabsOrder.indexOf(settingsTab ?? ''),
)

const startPageTranslations = useMemo(() => {
return translationParams?.isStartPage
}, [translationParams?.isStartPage])

const endPageTranslations = useMemo(() => {
return translationParams?.isEndPage
}, [translationParams?.isEndPage])

const currentIsTranslation = useMemo(() => {
return translationParams?.isTranslation
}, [translationParams?.isTranslation])

const formFieldNumToBeTranslated = useMemo(() => {
return (state as { isTranslation?: boolean; formFieldNum: number })
?.formFieldNum
}, [state])

const handleTabChange = (index: number) => {
setTabIndex(index)
navigate(
Expand Down Expand Up @@ -103,6 +133,7 @@ export const SettingsPage = (): JSX.Element => {
<SettingsTab label="Twilio credentials" icon={BiMessage} />
<SettingsTab label="Webhooks" icon={BiCodeBlock} />
<SettingsTab label="Payments" icon={BiDollar} />
<SettingsTab label="Multi-language" icon={LanguageTranslation} />
</TabList>
</Flex>
<TabPanels
Expand All @@ -125,6 +156,21 @@ export const SettingsPage = (): JSX.Element => {
<TabPanel>
<SettingsPaymentsPage />
</TabPanel>
<TabPanel>
{!language && !currentIsTranslation && <MultiLanguageSection />}
{language && !currentIsTranslation && (
<TranslationListSection language={language} />
)}
{language && currentIsTranslation && (
<TranslationSection
language={language}
formFieldNumToBeTranslated={formFieldNumToBeTranslated}
isFormField={formFieldNumToBeTranslated !== -1}
isStartPageTranslations={startPageTranslations}
isEndPageTranslations={endPageTranslations}
/>
)}
</TabPanel>
</TabPanels>
<Spacer />
</Tabs>
Expand Down
17 changes: 17 additions & 0 deletions frontend/src/features/admin-form/settings/SettingsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,23 @@ export const updateFormLimit: UpdateFormFn<'submissionLimit'> = async (
return updateFormSettings(formId, { submissionLimit: newLimit })
}

export const updateFormHasMultiLang: UpdateFormFn<'hasMultiLang'> = async (
formId,
newHasMultiLang,
) => {
return updateFormSettings(formId, {
hasMultiLang: newHasMultiLang,
})
}

export const updateFormSupportedLanguages: UpdateFormFn<
'supportedLanguages'
> = async (formId, newSupportedLanguages) => {
return updateFormSettings(formId, {
supportedLanguages: newSupportedLanguages,
})
}

export const updateFormCaptcha: UpdateFormFn<'hasCaptcha'> = async (
formId,
newHasCaptcha,
Expand Down
Loading
Loading