From acafff08f821c1a0f88763e02e47f46febde3773 Mon Sep 17 00:00:00 2001 From: LoneRifle Date: Mon, 16 Dec 2024 16:06:49 +0800 Subject: [PATCH] fix: stub i18next for YesNo,Email,Verify fields --- frontend/src/components/Field/YesNo/YesNo.tsx | 25 +-------- frontend/src/constants/validation.ts | 11 ---- .../EditCondition/EditConditionBlock.tsx | 4 +- .../Email/VerifiableEmailField.tsx | 9 +-- .../VerifiableFieldContainer.tsx | 15 ++--- .../VerifiableFieldContainer/constants.ts | 16 ------ .../VerificationBox/VerificationBox.tsx | 29 +++++----- .../components/VerificationBox/constants.ts | 56 ------------------- .../admin-form/sidebar/fields/en-sg.ts | 4 -- .../admin-form/sidebar/fields/index.ts | 4 -- .../locales/features/public-form/en-sg.ts | 2 + .../features/public-form/fields/en-sg.ts | 34 +++++++++++ .../features/public-form/fields/index.ts | 34 +++++++++++ .../features/public-form/fields/ms-sg.ts | 36 ++++++++++++ .../features/public-form/fields/ta-sg.ts | 36 ++++++++++++ .../features/public-form/fields/zh-sg.ts | 33 +++++++++++ .../locales/features/public-form/index.ts | 6 ++ .../locales/features/public-form/ms-sg.ts | 7 +++ .../locales/features/public-form/ta-sg.ts | 7 +++ .../locales/features/public-form/zh-sg.ts | 7 +++ frontend/src/i18n/locales/index.ts | 10 +++- frontend/src/i18n/locales/ms-sg.ts | 12 ++++ frontend/src/i18n/locales/ta-sg.ts | 12 ++++ frontend/src/i18n/locales/types.ts | 4 +- frontend/src/i18n/locales/zh-sg.ts | 2 + .../templates/Field/Email/EmailField.test.tsx | 10 +--- .../templates/Field/Email/EmailFieldInput.tsx | 11 ++-- frontend/src/utils/fieldValidation.ts | 24 +++++--- 28 files changed, 294 insertions(+), 166 deletions(-) delete mode 100644 frontend/src/features/verifiable-fields/components/VerifiableFieldContainer/constants.ts delete mode 100644 frontend/src/features/verifiable-fields/components/VerificationBox/constants.ts create mode 100644 frontend/src/i18n/locales/features/public-form/fields/en-sg.ts create mode 100644 frontend/src/i18n/locales/features/public-form/fields/index.ts create mode 100644 frontend/src/i18n/locales/features/public-form/fields/ms-sg.ts create mode 100644 frontend/src/i18n/locales/features/public-form/fields/ta-sg.ts create mode 100644 frontend/src/i18n/locales/features/public-form/fields/zh-sg.ts create mode 100644 frontend/src/i18n/locales/features/public-form/ms-sg.ts create mode 100644 frontend/src/i18n/locales/features/public-form/ta-sg.ts create mode 100644 frontend/src/i18n/locales/features/public-form/zh-sg.ts create mode 100644 frontend/src/i18n/locales/ms-sg.ts create mode 100644 frontend/src/i18n/locales/ta-sg.ts diff --git a/frontend/src/components/Field/YesNo/YesNo.tsx b/frontend/src/components/Field/YesNo/YesNo.tsx index e283770e00..b89ecab914 100644 --- a/frontend/src/components/Field/YesNo/YesNo.tsx +++ b/frontend/src/components/Field/YesNo/YesNo.tsx @@ -9,27 +9,12 @@ import { } from '@chakra-ui/react' import pick from 'lodash/pick' -import { Language } from '~shared/types' - import { FieldColorScheme } from '~theme/foundations/colours' import { YesNoOption } from './YesNoOption' export type YesNoOptionValue = 'Yes' | 'No' -// TODO: port to i18next -type YesNoTranslations = { - Yes: string - No: string -} - -const YES_NO_TRANSLATIONS: Record = { - [Language.ENGLISH]: { Yes: 'Yes', No: 'No' }, - [Language.CHINESE]: { Yes: '是', No: '否' }, - [Language.MALAY]: { Yes: 'Ya', No: 'Tidak' }, - [Language.TAMIL]: { Yes: 'ஆம்', No: 'இல்லை' }, -} - export interface YesNoProps { /** * Whether YesNo component is disabled. @@ -68,7 +53,7 @@ export const YesNo = forwardRef( ({ colorScheme, ...props }, ref) => { const formControlProps = useFormControlProps(props) const { getRootProps, getRadioProps, onChange } = useRadioGroup(props) - const { t, i18n } = useTranslation() + const { t } = useTranslation() const groupProps = getRootProps() const [noProps, yesProps] = useMemo(() => { @@ -95,10 +80,6 @@ export const YesNo = forwardRef( return [noRadioProps, yesRadioProps] }, [formControlProps, getRadioProps, props.name]) - const selectedLanguage = i18n.language as Language - const yesLabel = YES_NO_TRANSLATIONS[selectedLanguage].Yes - const noLabel = YES_NO_TRANSLATIONS[selectedLanguage].No - return ( ( {...noProps} onChange={(value) => onChange(value as YesNoOptionValue)} leftIcon={BiX} - label={noLabel ?? 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} @@ -119,7 +100,7 @@ export const YesNo = forwardRef( {...yesProps} onChange={(value) => onChange(value as YesNoOptionValue)} leftIcon={BiCheck} - label={yesLabel ?? t('features.adminForm.sidebar.fields.yesNo.yes')} + label={t('features.publicForm.components.fields.yesNo.yes')} title={props.title} /> diff --git a/frontend/src/constants/validation.ts b/frontend/src/constants/validation.ts index be6c551ac4..0bcda64011 100644 --- a/frontend/src/constants/validation.ts +++ b/frontend/src/constants/validation.ts @@ -1,17 +1,6 @@ -import { Language } from '~shared/types' - export const REQUIRED_ERROR = 'This field is required' export const INVALID_EMAIL_ERROR = 'Please enter a valid email' -export const INVALID_EMAIL_DOMAIN_ERROR: Record = { - [Language.ENGLISH]: - 'The entered email does not belong to an allowed email domain', - [Language.CHINESE]: '输入的电子邮箱不在允许域名之列', - [Language.MALAY]: - 'E-mel yang dimasukkan bukan milik domain e-mel yang dibenarkan', - [Language.TAMIL]: - 'உள்ளிடப்பட்ட மின்னஞ்சல் அனுமதிக்கப்பட்ட மின்னஞ்சலுக்குச் சொந்தமானதல்ல', -} export const INVALID_DROPDOWN_OPTION_ERROR = 'Entered value is not a valid dropdown option' diff --git a/frontend/src/features/admin-form/create/logic/components/LogicContent/EditLogicBlock/EditCondition/EditConditionBlock.tsx b/frontend/src/features/admin-form/create/logic/components/LogicContent/EditLogicBlock/EditCondition/EditConditionBlock.tsx index 33b04ecff3..ca7a698971 100644 --- a/frontend/src/features/admin-form/create/logic/components/LogicContent/EditLogicBlock/EditCondition/EditConditionBlock.tsx +++ b/frontend/src/features/admin-form/create/logic/components/LogicContent/EditLogicBlock/EditCondition/EditConditionBlock.tsx @@ -156,8 +156,8 @@ export const EditConditionBlock = ({ switch (mappedField.fieldType) { case BasicField.YesNo: return [ - t('features.adminForm.sidebar.fields.yesNo.yes'), - t('features.adminForm.sidebar.fields.yesNo.no'), + t('features.publicForm.components.fields.yesNo.yes'), + t('features.publicForm.components.fields.yesNo.no'), ] case BasicField.Radio: if (mappedField.othersRadioButton) { diff --git a/frontend/src/features/verifiable-fields/Email/VerifiableEmailField.tsx b/frontend/src/features/verifiable-fields/Email/VerifiableEmailField.tsx index 48ae702302..d06132ab0c 100644 --- a/frontend/src/features/verifiable-fields/Email/VerifiableEmailField.tsx +++ b/frontend/src/features/verifiable-fields/Email/VerifiableEmailField.tsx @@ -2,8 +2,6 @@ import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import { Box, VisuallyHidden } from '@chakra-ui/react' -import { Language } from '~shared/types' - import { baseEmailValidationFn } from '~utils/fieldValidation' import { EmailFieldInput, EmailFieldProps } from '~templates/Field/Email' import { EmailFieldSchema } from '~templates/Field/types' @@ -71,10 +69,13 @@ export const VerifiableEmailField = ({ schema, ...props }: VerifiableEmailFieldProps) => { - const { i18n } = useTranslation() + const { t } = useTranslation() const validateInputForVfn = baseEmailValidationFn({ schema, - selectedLanguage: i18n.language as Language, + validationErrorMessages: t( + 'features.publicForm.components.fields.email.validation', + { allowObjects: true }, + ), }) return ( > } @@ -32,7 +30,7 @@ export const VerifiableFieldContainer = ({ colorTheme = FormColorTheme.Blue, children, }: VerifiableFieldContainerProps): JSX.Element => { - const { i18n } = useTranslation() + const { t } = useTranslation() const { isVfnBoxOpen, otpPrefix, @@ -56,10 +54,6 @@ export const VerifiableFieldContainer = ({ } }, [hasSignature, schema.fieldType]) - const selectedLanguage = i18n.language as Language - const verifyLabel = VERIFY_LABEL_TRANSLATIONS[selectedLanguage].Verify - const verifiedLabel = VERIFY_LABEL_TRANSLATIONS[selectedLanguage].Verified - return ( @@ -83,7 +77,9 @@ export const VerifiableFieldContainer = ({ // with new lines within the button. whiteSpace="nowrap" > - {hasSignature ? verifiedLabel : verifyLabel} + {t( + `features.publicForm.components.fields.verification.button.label.${hasSignature ? 'verified' : 'verify'}`, + )} @@ -94,7 +90,6 @@ export const VerifiableFieldContainer = ({ handleResendOtp={handleResendOtp} fieldType={schema.fieldType} otpPrefix={otpPrefix} - selectedLanguage={selectedLanguage} /> )} diff --git a/frontend/src/features/verifiable-fields/components/VerifiableFieldContainer/constants.ts b/frontend/src/features/verifiable-fields/components/VerifiableFieldContainer/constants.ts deleted file mode 100644 index 06f559549f..0000000000 --- a/frontend/src/features/verifiable-fields/components/VerifiableFieldContainer/constants.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Language } from '~shared/types' - -type VerifyTranslations = { - Verify: string - Verified: string -} - -export const VERIFY_LABEL_TRANSLATIONS: Record = { - [Language.ENGLISH]: { Verify: 'Verify', Verified: 'Verified' }, - [Language.CHINESE]: { Verify: '验证', Verified: '已验证' }, - [Language.MALAY]: { Verify: 'Sahkan', Verified: 'Disahkan' }, - [Language.TAMIL]: { - Verify: 'சரிபார்க்கவும்', - Verified: 'சரிபார்க்கப்பட்டது', - }, -} diff --git a/frontend/src/features/verifiable-fields/components/VerificationBox/VerificationBox.tsx b/frontend/src/features/verifiable-fields/components/VerificationBox/VerificationBox.tsx index b2e298917f..acd7ca7e62 100644 --- a/frontend/src/features/verifiable-fields/components/VerificationBox/VerificationBox.tsx +++ b/frontend/src/features/verifiable-fields/components/VerificationBox/VerificationBox.tsx @@ -1,4 +1,4 @@ -import { useCallback, useMemo } from 'react' +import { useCallback } from 'react' import { useForm } from 'react-hook-form' import { useTranslation } from 'react-i18next' import { @@ -9,7 +9,7 @@ import { InputLeftAddon, } from '@chakra-ui/react' -import { Language } from '~shared/types' +import { BasicField } from '~shared/types' import ResendOtpButton from '~/templates/ResendOtpButton' @@ -21,7 +21,8 @@ import Input from '~components/Input' import { VerifiableFieldType } from '../../types' -import { VFN_RENDER_DATA } from './constants' +import { EmailOtpSvgr } from './EmailOtpSvgr' +import { MobileOtpSvgr } from './MobileOtpSvgr' type VfnFieldValues = { otp: string @@ -32,7 +33,6 @@ export interface VerificationBoxProps { otpPrefix: string handleVerifyOtp: (otp: string) => Promise handleResendOtp: () => Promise - selectedLanguage?: Language } type UseVerificationBoxProps = Pick @@ -72,7 +72,7 @@ export const VerificationBox = ({ handleResendOtp, handleVerifyOtp, }: VerificationBoxProps): JSX.Element => { - const { i18n } = useTranslation() + const { t } = useTranslation() const { formMethods: { register, @@ -84,13 +84,16 @@ export const VerificationBox = ({ handleVerifyOtp, }) - const { - logo: Logo, - header, - subheader, - } = useMemo(() => VFN_RENDER_DATA[fieldType], [fieldType]) + const Logo = { + [BasicField.Mobile]: MobileOtpSvgr, + [BasicField.Email]: EmailOtpSvgr, + }[fieldType] + + const { title, description } = t( + `features.publicForm.components.fields.verification.modal.${fieldType}`, + { allowObjects: true }, + ) - const selectedLanguage = i18n.language as Language return ( - - {header[selectedLanguage]} - + {title} {otpPrefix ? ( diff --git a/frontend/src/features/verifiable-fields/components/VerificationBox/constants.ts b/frontend/src/features/verifiable-fields/components/VerificationBox/constants.ts deleted file mode 100644 index 6fefcfd683..0000000000 --- a/frontend/src/features/verifiable-fields/components/VerificationBox/constants.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { ChakraComponent } from '@chakra-ui/react' - -import { Language } from '~shared/types' -import { BasicField } from '~shared/types/field' - -import { VerifiableFieldType } from '~features/verifiable-fields/types' - -import { EmailOtpSvgr } from './EmailOtpSvgr' -import { MobileOtpSvgr } from './MobileOtpSvgr' - -type VerificationBoxRenderData = { - logo: ChakraComponent<(props: React.SVGProps) => JSX.Element> - header: Record - subheader: Record -} - -export const VFN_RENDER_DATA: Record< - VerifiableFieldType, - VerificationBoxRenderData -> = { - [BasicField.Mobile]: { - logo: MobileOtpSvgr, - header: { - [Language.ENGLISH]: 'Verify your mobile number', - [Language.CHINESE]: '验证您的手机号码', - [Language.MALAY]: 'Sahkan nombor telefon bimbit anda', - [Language.TAMIL]: 'உங்கள் மொபைல் எண்ணைச் சரிபார்க்கவும்', - }, - subheader: { - [Language.ENGLISH]: - 'An SMS with a 6-digit verification code was sent to you. It will be valid for 30 minutes.', - [Language.CHINESE]: '已通过短信发送6 位数的验证码,30分钟内有效。', - [Language.MALAY]: - 'SMS dengan kod pengesahan 6 digit telah dihantar kepada anda. Kod itu sah selama 30 minit. ', - [Language.TAMIL]: - '6 இலக்க சரிபார்ப்புக் குறியீடு கொண்ட எஸ்எம்எஸ் உங்களுக்கு அனுப்பப்பட்டுள்ளது. 30 நிமிடங்களுக்கு இது செல்லுபடியாகும்.', - }, - }, - [BasicField.Email]: { - logo: EmailOtpSvgr, - header: { - [Language.ENGLISH]: 'Verify your email', - [Language.CHINESE]: '验证您的电子邮箱', - [Language.MALAY]: 'Sahkan e-mel anda', - [Language.TAMIL]: 'உங்கள் மின்னஞ்சல் முகவரியைச் சரிபார்க்கவும்', - }, - subheader: { - [Language.ENGLISH]: - 'An email with a 6-digit verification code was sent to you. It will be valid for 30 minutes.', - [Language.CHINESE]: '已通过电邮发送6位数的验证码,30分钟内有效。', - [Language.MALAY]: - 'E-mel dengan kod pengesahan 6 digit telah dihantar kepada anda. Kod itu sah selama 30 minit. ', - [Language.TAMIL]: 'மின்னஞ்சல் முகவரியை சரிபார்க்கவும்', - }, - }, -} diff --git a/frontend/src/i18n/locales/features/admin-form/sidebar/fields/en-sg.ts b/frontend/src/i18n/locales/features/admin-form/sidebar/fields/en-sg.ts index 73b8f7eb67..81c0a1088e 100644 --- a/frontend/src/i18n/locales/features/admin-form/sidebar/fields/en-sg.ts +++ b/frontend/src/i18n/locales/features/admin-form/sidebar/fields/en-sg.ts @@ -52,10 +52,6 @@ export const enSG: Fields = { maximum: 'Maximum', }, }, - yesNo: { - yes: 'Yes', - no: 'No', - }, paragraph: 'Paragraph', section: { heading: 'Section heading', diff --git a/frontend/src/i18n/locales/features/admin-form/sidebar/fields/index.ts b/frontend/src/i18n/locales/features/admin-form/sidebar/fields/index.ts index b369be54e0..e0ba154542 100644 --- a/frontend/src/i18n/locales/features/admin-form/sidebar/fields/index.ts +++ b/frontend/src/i18n/locales/features/admin-form/sidebar/fields/index.ts @@ -51,10 +51,6 @@ export interface Fields { maximum: string } } - yesNo: { - yes: string - no: string - } paragraph: string section: { heading: string diff --git a/frontend/src/i18n/locales/features/public-form/en-sg.ts b/frontend/src/i18n/locales/features/public-form/en-sg.ts index f6fa06dc1f..5de122e6c1 100644 --- a/frontend/src/i18n/locales/features/public-form/en-sg.ts +++ b/frontend/src/i18n/locales/features/public-form/en-sg.ts @@ -1,3 +1,4 @@ +import { enSG as fields } from './fields' import { enSG as table } from './table' import { PublicForm } from '.' @@ -32,6 +33,7 @@ export const enSG: PublicForm = { submitNow: 'Submit now', }, table, + fields, feedbackBlock: { title: { payment: 'How was your experience making payment on this form?', diff --git a/frontend/src/i18n/locales/features/public-form/fields/en-sg.ts b/frontend/src/i18n/locales/features/public-form/fields/en-sg.ts new file mode 100644 index 0000000000..52afe0eebc --- /dev/null +++ b/frontend/src/i18n/locales/features/public-form/fields/en-sg.ts @@ -0,0 +1,34 @@ +import { Fields } from '.' + +export const enSG: Fields = { + yesNo: { + yes: 'Yes', + no: 'No', + }, + email: { + validation: { + domainDisallowed: + 'The entered email does not belong to an allowed email domain', + }, + }, + verification: { + button: { + label: { + verify: 'Verify', + verified: 'Verified', + }, + }, + modal: { + email: { + title: 'Verify your email', + description: + 'An email with a 6-digit verification code was sent to you. It will be valid for 30 minutes.', + }, + mobile: { + title: 'Verify your mobile number', + description: + 'An SMS with a 6-digit verification code was sent to you. It will be valid for 30 minutes.', + }, + }, + }, +} diff --git a/frontend/src/i18n/locales/features/public-form/fields/index.ts b/frontend/src/i18n/locales/features/public-form/fields/index.ts new file mode 100644 index 0000000000..f3d27af646 --- /dev/null +++ b/frontend/src/i18n/locales/features/public-form/fields/index.ts @@ -0,0 +1,34 @@ +export interface Fields { + yesNo: { + yes: string + no: string + } + email: { + validation: { + domainDisallowed: string + } + } + verification: { + button: { + label: { + verify: string + verified: string + } + } + modal: { + email: { + title: string + description: string + } + mobile: { + title: string + description: string + } + } + } +} + +export * from './en-sg' +export * from './ms-sg' +export * from './ta-sg' +export * from './zh-sg' diff --git a/frontend/src/i18n/locales/features/public-form/fields/ms-sg.ts b/frontend/src/i18n/locales/features/public-form/fields/ms-sg.ts new file mode 100644 index 0000000000..972f8685d2 --- /dev/null +++ b/frontend/src/i18n/locales/features/public-form/fields/ms-sg.ts @@ -0,0 +1,36 @@ +import { PartialDeep } from 'type-fest' + +import { Fields } from '.' + +export const msSG: PartialDeep = { + yesNo: { + yes: 'Ya', + no: 'Tidak', + }, + email: { + validation: { + domainDisallowed: + 'E-mel yang dimasukkan bukan milik domain e-mel yang dibenarkan', + }, + }, + verification: { + button: { + label: { + verify: 'Sahkan', + verified: 'Disahkan', + }, + }, + modal: { + email: { + title: 'Sahkan e-mel anda', + description: + 'E-mel dengan kod pengesahan 6 digit telah dihantar kepada anda. Kod itu sah selama 30 minit.', + }, + mobile: { + title: 'Sahkan nombor telefon bimbit anda', + description: + 'SMS dengan kod pengesahan 6 digit telah dihantar kepada anda. Kod itu sah selama 30 minit.', + }, + }, + }, +} diff --git a/frontend/src/i18n/locales/features/public-form/fields/ta-sg.ts b/frontend/src/i18n/locales/features/public-form/fields/ta-sg.ts new file mode 100644 index 0000000000..bc89956f19 --- /dev/null +++ b/frontend/src/i18n/locales/features/public-form/fields/ta-sg.ts @@ -0,0 +1,36 @@ +import { PartialDeep } from 'type-fest' + +import { Fields } from '.' + +export const taSG: PartialDeep = { + yesNo: { + yes: 'ஆம்', + no: 'இல்லை', + }, + email: { + validation: { + domainDisallowed: + 'உள்ளிடப்பட்ட மின்னஞ்சல் அனுமதிக்கப்பட்ட மின்னஞ்சலுக்குச் சொந்தமானதல்ல', + }, + }, + verification: { + button: { + label: { + verify: 'சரிபார்க்கவும்', + verified: 'சரிபார்க்கப்பட்டது', + }, + }, + modal: { + email: { + title: 'உங்கள் மின்னஞ்சல் முகவரியைச் சரிபார்க்கவும்', + description: + '6 இலக்க சரிபார்ப்புக் குறியீடு கொண்ட மின்னஞ்சல் உங்களுக்கு அனுப்பப்பட்டுள்ளது. 30 நிமிடங்களுக்கு இது செல்லுபடியாகும்.', + }, + mobile: { + title: 'உங்கள் மொபைல் எண்ணைச் சரிபார்க்கவும்', + description: + '6 இலக்க சரிபார்ப்புக் குறியீடு கொண்ட எஸ்எம்எஸ் உங்களுக்கு அனுப்பப்பட்டுள்ளது. 30 நிமிடங்களுக்கு இது செல்லுபடியாகும்.', + }, + }, + }, +} diff --git a/frontend/src/i18n/locales/features/public-form/fields/zh-sg.ts b/frontend/src/i18n/locales/features/public-form/fields/zh-sg.ts new file mode 100644 index 0000000000..fcda0c0eac --- /dev/null +++ b/frontend/src/i18n/locales/features/public-form/fields/zh-sg.ts @@ -0,0 +1,33 @@ +import { PartialDeep } from 'type-fest' + +import { Fields } from '.' + +export const zhSG: PartialDeep = { + yesNo: { + yes: '是', + no: '否', + }, + email: { + validation: { + domainDisallowed: '输入的电子邮箱不在允许域名之列', + }, + }, + verification: { + button: { + label: { + verify: '验证', + verified: '已验证', + }, + }, + modal: { + email: { + title: '验证您的电子邮箱', + description: '已通过电邮发送6位数的验证码,30分钟内有效。', + }, + mobile: { + title: '验证您的手机号码', + description: '已通过短信发送6 位数的验证码,30分钟内有效。', + }, + }, + }, +} diff --git a/frontend/src/i18n/locales/features/public-form/index.ts b/frontend/src/i18n/locales/features/public-form/index.ts index d91bdb53a0..ed3a08144a 100644 --- a/frontend/src/i18n/locales/features/public-form/index.ts +++ b/frontend/src/i18n/locales/features/public-form/index.ts @@ -1,3 +1,4 @@ +import { Fields } from './fields' import { Table } from './table' export * from './en-sg' @@ -27,6 +28,7 @@ export interface PublicForm { submitNow: string } table: Table + fields: Fields feedbackBlock: { title: { payment: string @@ -41,3 +43,7 @@ export interface PublicForm { } } } + +export * from './ms-sg' +export * from './ta-sg' +export * from './zh-sg' diff --git a/frontend/src/i18n/locales/features/public-form/ms-sg.ts b/frontend/src/i18n/locales/features/public-form/ms-sg.ts new file mode 100644 index 0000000000..5e12affd2f --- /dev/null +++ b/frontend/src/i18n/locales/features/public-form/ms-sg.ts @@ -0,0 +1,7 @@ +import { msSG as fields } from './fields' + +export const msSG = { + components: { + fields, + }, +} diff --git a/frontend/src/i18n/locales/features/public-form/ta-sg.ts b/frontend/src/i18n/locales/features/public-form/ta-sg.ts new file mode 100644 index 0000000000..418f70de47 --- /dev/null +++ b/frontend/src/i18n/locales/features/public-form/ta-sg.ts @@ -0,0 +1,7 @@ +import { taSG as fields } from './fields' + +export const taSG = { + components: { + fields, + }, +} diff --git a/frontend/src/i18n/locales/features/public-form/zh-sg.ts b/frontend/src/i18n/locales/features/public-form/zh-sg.ts new file mode 100644 index 0000000000..be9f8d233e --- /dev/null +++ b/frontend/src/i18n/locales/features/public-form/zh-sg.ts @@ -0,0 +1,7 @@ +import { zhSG as fields } from './fields' + +export const zhSG = { + components: { + fields, + }, +} diff --git a/frontend/src/i18n/locales/index.ts b/frontend/src/i18n/locales/index.ts index d075f0d6a6..0bd6e3587b 100644 --- a/frontend/src/i18n/locales/index.ts +++ b/frontend/src/i18n/locales/index.ts @@ -1,9 +1,15 @@ import { ResourceLanguage } from 'i18next' +import { Language } from '~shared/types' + import { enSG } from './en-sg' +import { msSG } from './ms-sg' +import { taSG } from './ta-sg' import { zhSG } from './zh-sg' export const locales = { - 'en-SG': enSG as unknown as ResourceLanguage, - 'zh-SG': zhSG as unknown as ResourceLanguage, + [Language.ENGLISH]: enSG as unknown as ResourceLanguage, + [Language.CHINESE]: zhSG as unknown as ResourceLanguage, + [Language.MALAY]: msSG as unknown as ResourceLanguage, + [Language.TAMIL]: taSG as unknown as ResourceLanguage, } diff --git a/frontend/src/i18n/locales/ms-sg.ts b/frontend/src/i18n/locales/ms-sg.ts new file mode 100644 index 0000000000..9e5fe2733b --- /dev/null +++ b/frontend/src/i18n/locales/ms-sg.ts @@ -0,0 +1,12 @@ +import { PartialDeep } from 'type-fest' + +import { msSG as publicForm } from './features/public-form' +import Translation from './types' + +export const msSG: PartialDeep = { + translation: { + features: { + publicForm, + }, + }, +} diff --git a/frontend/src/i18n/locales/ta-sg.ts b/frontend/src/i18n/locales/ta-sg.ts new file mode 100644 index 0000000000..a41bf553cb --- /dev/null +++ b/frontend/src/i18n/locales/ta-sg.ts @@ -0,0 +1,12 @@ +import { PartialDeep } from 'type-fest' + +import { taSG as publicForm } from './features/public-form' +import Translation from './types' + +export const taSG: PartialDeep = { + translation: { + features: { + publicForm, + }, + }, +} diff --git a/frontend/src/i18n/locales/types.ts b/frontend/src/i18n/locales/types.ts index b103118ef7..63ce5b3b0d 100644 --- a/frontend/src/i18n/locales/types.ts +++ b/frontend/src/i18n/locales/types.ts @@ -1,4 +1,4 @@ -import { DeepRequired } from 'ts-essentials' +import { RequiredDeep } from 'type-fest' import { Common, @@ -40,7 +40,7 @@ interface Translation { export interface FallbackTranslation extends Translation { translation: { - features: DeepRequired + features: RequiredDeep } } diff --git a/frontend/src/i18n/locales/zh-sg.ts b/frontend/src/i18n/locales/zh-sg.ts index 8c8c5708ee..ae59977219 100644 --- a/frontend/src/i18n/locales/zh-sg.ts +++ b/frontend/src/i18n/locales/zh-sg.ts @@ -1,11 +1,13 @@ import { PartialDeep } from 'type-fest' import { zhSG as login } from './features/login' +import { zhSG as publicForm } from './features/public-form' import Translation from './types' export const zhSG: PartialDeep = { translation: { features: { + publicForm, login, }, }, diff --git a/frontend/src/templates/Field/Email/EmailField.test.tsx b/frontend/src/templates/Field/Email/EmailField.test.tsx index dfdc4db464..2eb1570003 100644 --- a/frontend/src/templates/Field/Email/EmailField.test.tsx +++ b/frontend/src/templates/Field/Email/EmailField.test.tsx @@ -2,13 +2,9 @@ import { composeStories } from '@storybook/react' import { render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import { Language } from '~shared/types' +import { enSG } from '~/i18n/locales/features/public-form/fields' -import { - INVALID_EMAIL_DOMAIN_ERROR, - INVALID_EMAIL_ERROR, - REQUIRED_ERROR, -} from '~constants/validation' +import { INVALID_EMAIL_ERROR, REQUIRED_ERROR } from '~constants/validation' import * as stories from './EmailField.stories' @@ -149,7 +145,7 @@ describe('email validation', () => { // Assert // Should show error message. - const errorMessage = INVALID_EMAIL_DOMAIN_ERROR[Language.ENGLISH] + const errorMessage = enSG.email.validation.domainDisallowed expect(screen.getByText(errorMessage)).not.toBeNull() }) }) diff --git a/frontend/src/templates/Field/Email/EmailFieldInput.tsx b/frontend/src/templates/Field/Email/EmailFieldInput.tsx index 224674dfc9..5a48a7f51c 100644 --- a/frontend/src/templates/Field/Email/EmailFieldInput.tsx +++ b/frontend/src/templates/Field/Email/EmailFieldInput.tsx @@ -33,16 +33,19 @@ export const EmailFieldInput = ({ handleInputChange, inputProps = {}, }: EmailFieldInputProps): JSX.Element => { - const { i18n } = useTranslation() - const selectedLanguage = i18n.language as Language + const { t } = useTranslation() + const validationErrorMessages = t( + 'features.publicForm.components.fields.email.validation', + { allowObjects: true }, + ) const validationRules = useMemo( () => createEmailValidationRules( schema, disableRequiredValidation, - selectedLanguage, + validationErrorMessages, ), - [schema, disableRequiredValidation, selectedLanguage], + [schema, disableRequiredValidation, validationErrorMessages], ) const { control } = useFormContext() diff --git a/frontend/src/utils/fieldValidation.ts b/frontend/src/utils/fieldValidation.ts index 8b6032d09d..ea825f0f4e 100644 --- a/frontend/src/utils/fieldValidation.ts +++ b/frontend/src/utils/fieldValidation.ts @@ -9,7 +9,6 @@ import simplur from 'simplur' import validator from 'validator' import { DATE_PARSE_FORMAT } from '~shared/constants/dates' -import { Language } from '~shared/types' import { AttachmentFieldBase, BasicField, @@ -42,10 +41,11 @@ import { } from '~shared/utils/phone-num-validation' import { isUenValid } from '~shared/utils/uen-validation' +import { Fields } from '~/i18n/locales/features/public-form/fields' + import { INVALID_COUNTRY_REGION_OPTION_ERROR, INVALID_DROPDOWN_OPTION_ERROR, - INVALID_EMAIL_DOMAIN_ERROR, INVALID_EMAIL_ERROR, REQUIRED_ERROR, } from '~constants/validation' @@ -65,6 +65,8 @@ import { } from './date' import { formatNumberToLocaleString } from './stringFormat' +type EmailValidationErrorMessages = Fields['email']['validation'] + // Omit unused props type MinimumFieldValidationProps = Omit< T, @@ -85,7 +87,7 @@ type ValidationRuleFn = ( type ValidationRuleFnEmailAndMobile = ( schema: MinimumFieldValidationPropsEmailAndMobile, disableRequiredValidation?: boolean, - selectedLanguage?: Language, + validationErrorMessages?: EmailValidationErrorMessages, ) => RegisterOptions const requiredSingleAnswerValidationFn = @@ -554,11 +556,17 @@ export const createRadioValidationRules: ValidationRuleFn = ( export const createEmailValidationRules: ValidationRuleFnEmailAndMobile< EmailFieldBase -> = (schema, disableRequiredValidation, selectedLanguage): RegisterOptions => { +> = ( + schema, + disableRequiredValidation, + validationErrorMessages, +): RegisterOptions => { return { validate: { baseValidations: (val?: VerifiableFieldValues) => { - return baseEmailValidationFn({ schema, selectedLanguage })(val?.value) + return baseEmailValidationFn({ schema, validationErrorMessages })( + val?.value, + ) }, ...createBaseVfnFieldValidationRules(schema, disableRequiredValidation) .validate, @@ -573,10 +581,10 @@ export const createEmailValidationRules: ValidationRuleFnEmailAndMobile< export const baseEmailValidationFn = ({ schema, - selectedLanguage = Language.ENGLISH, + validationErrorMessages = { domainDisallowed: 'Domain disallowed' }, }: { schema: MinimumFieldValidationProps - selectedLanguage?: Language + validationErrorMessages?: EmailValidationErrorMessages }) => (inputValue?: string) => { if (!inputValue) return true @@ -591,7 +599,7 @@ export const baseEmailValidationFn = if (allowedDomains.size !== 0) { const domainInValue = trimmedInputValue.split('@')[1].toLowerCase() if (domainInValue && !allowedDomains.has(`@${domainInValue}`)) { - return INVALID_EMAIL_DOMAIN_ERROR[selectedLanguage] + return validationErrorMessages.domainDisallowed } }