diff --git a/public/locales/en-US/common.json b/public/locales/en-US/common.json new file mode 100644 index 0000000..c8704c6 --- /dev/null +++ b/public/locales/en-US/common.json @@ -0,0 +1,12 @@ +{ + "fields": { + "email": { + "label": "Email", + "placeholder": "johndoe@example.com" + }, + "2faCode": { + "label": "Code", + "placeholder": "123456" + } + } +} diff --git a/public/locales/en-US/login.json b/public/locales/en-US/login.json new file mode 100644 index 0000000..899bd47 --- /dev/null +++ b/public/locales/en-US/login.json @@ -0,0 +1,35 @@ +{ + "title": "Log in", + "forms": { + "email": { + "description": "We will send you a code to log in", + "continueActionLabel": "Send code" + }, + "confirmCode": { + "title": "You got mail!", + "description": "We sent you a code to your email. Enter it below to login", + "continueActionLabel": "Log in", + "allowLoginFromDifferentDevices": "Allow login from different devices", + "expiringSoonWarning": "Your code will expire in less than a minute.", + "fields": { + "code": { + "label": "Verification Code", + "errors": { + "invalidChars": "Invalid verification code" + } + } + } + }, + "confirmFromDifferentDevice": { + "title": "Login failed", + "description": "You could not be logged in. This could either be because you are not allowed to login from different devices or the verification code is invalid or expired." + }, + "otp": { + "title": "Two-Factor Authentication", + "description": "Enter the code from your authenticator app", + "isUnavailable": "Your OTP verification time expired or you exceeded the maximum number of attempts. Please log in again.", + "codesLostActionLabel": "I lost my codes", + "continueActionLabel": "Log in" + } + } +} diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json index 7c6ebbf..9148b99 100644 --- a/public/locales/en-US/translation.json +++ b/public/locales/en-US/translation.json @@ -26,50 +26,6 @@ "title": "Overview", "description": "Not much to see here, yet." }, - "LoginRoute": { - "forms": { - "email": { - "title": "Sign in", - "description": "We'll send you a verification code to your email.", - "continueAction": "Send Code", - "form": { - "email": { - "label": "Email", - "placeholder": "johndoe@example.com" - } - } - }, - "confirmCode": { - "title": "You got mail!", - "description": "We sent you a code to your email. Enter it below to login", - "continueAction": "Log in", - "allowLoginFromDifferentDevices": "Allow login from different devices", - "expiringSoon": "Your code will expire in less than a minute.", - "form": { - "code": { - "label": "Verification Code", - "errors": { - "invalidChars": "Invalid verification code" - } - } - } - }, - "confirmFromDifferentDevice": { - "title": "Login failed", - "description": "You could not be logged in. This could either be because you are not allowed to login from different devices or the verification code is invalid or expired." - }, - "otp": { - "title": "Two-factor authentication", - "description": "Please enter the code from your authenticator app.", - "submit": "Log in", - "lost": "I lost my codes", - "code": { - "label": "Code" - }, - "unavailable": "Your OTP verification time expired or you exceeded the maximum number of attempts. Please log in again." - } - } - }, "SignupRoute": { "forms": { "email": { diff --git a/src/route-widgets/LoginRoute/ConfirmCodeForm/ConfirmCodeForm.tsx b/src/route-widgets/LoginRoute/ConfirmCodeForm/ConfirmCodeForm.tsx index 8cc1c31..94ffa78 100644 --- a/src/route-widgets/LoginRoute/ConfirmCodeForm/ConfirmCodeForm.tsx +++ b/src/route-widgets/LoginRoute/ConfirmCodeForm/ConfirmCodeForm.tsx @@ -55,7 +55,7 @@ export default function ConfirmCodeForm({ }: ConfirmCodeFormProps): ReactElement { const settings = useLoaderData() as ServerSettings const expirationTime = isDev ? 70 : settings.emailLoginExpirationInSeconds - const {t} = useTranslation() + const {t} = useTranslation(["login", "common"]) const requestDate = useMemo(() => new Date(), []) const [isExpiringSoon, setIsExpiringSoon] = useState(false) @@ -67,7 +67,7 @@ export default function ConfirmCodeForm({ .max(settings.emailLoginTokenLength) .test( "chars", - t("routes.LoginRoute.forms.confirmCode.form.code.errors.invalidChars") as string, + t("forms.confirmCode.fields.code.errors.invalidChars") as string, code => { if (!code) { return false @@ -78,7 +78,7 @@ export default function ConfirmCodeForm({ return code.split("").every(char => chars.includes(char)) }, ) - .label(t("routes.LoginRoute.forms.confirmCode.form.code.label")), + .label(t("forms.confirmCode.fields.code.label")), }) const {mutateAsync} = useMutation< @@ -165,7 +165,7 @@ export default function ConfirmCodeForm({ > - {t("routes.LoginRoute.forms.confirmCode.title")} + {t("forms.confirmCode.title")} @@ -175,7 +175,7 @@ export default function ConfirmCodeForm({ - {t("routes.LoginRoute.forms.confirmCode.description")} + {t("forms.confirmCode.description")} @@ -196,7 +196,7 @@ export default function ConfirmCodeForm({ } labelPlacement="end" label={t( - "routes.LoginRoute.forms.confirmCode.allowLoginFromDifferentDevices", + "forms.confirmCode.allowLoginFromDifferentDevices", )} /> @@ -206,9 +206,7 @@ export default function ConfirmCodeForm({ fullWidth name="code" id="code" - label={t( - "routes.LoginRoute.forms.confirmCode.form.code.label", - )} + label={t("forms.confirmCode.fields.code.label")} value={formik.values.code} onChange={event => { formik.setFieldValue( @@ -250,7 +248,7 @@ export default function ConfirmCodeForm({ type="submit" startIcon={} > - {t("routes.LoginRoute.forms.confirmCode.continueAction")} + {t("forms.confirmCode.continueActionLabel")} @@ -260,7 +258,7 @@ export default function ConfirmCodeForm({ - {t("routes.LoginRoute.forms.confirmCode.expiringSoon")} + {t("forms.confirmCode.expiringSoonWarning")} diff --git a/src/route-widgets/LoginRoute/ConfirmFromDifferentDevice.tsx b/src/route-widgets/LoginRoute/ConfirmFromDifferentDevice.tsx index 5f0181e..c05bf8e 100644 --- a/src/route-widgets/LoginRoute/ConfirmFromDifferentDevice.tsx +++ b/src/route-widgets/LoginRoute/ConfirmFromDifferentDevice.tsx @@ -22,7 +22,7 @@ export default function ConfirmFromDifferentDevice({ token, onConfirm, }: ConfirmFromDifferentDeviceProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation(["login"]) const {mutate, isLoading, isError} = useMutation( () => verifyLoginWithEmail({ @@ -51,14 +51,12 @@ export default function ConfirmFromDifferentDevice({ - {t("routes.LoginRoute.forms.confirmFromDifferentDevice.title")} + {t("forms.confirmFromDifferentDevice.title")} - {t( - "routes.LoginRoute.forms.confirmFromDifferentDevice.description", - )} + {t("forms.confirmFromDifferentDevice.description")} diff --git a/src/route-widgets/LoginRoute/EmailForm.tsx b/src/route-widgets/LoginRoute/EmailForm.tsx index 69f07f6..5d87e7f 100644 --- a/src/route-widgets/LoginRoute/EmailForm.tsx +++ b/src/route-widgets/LoginRoute/EmailForm.tsx @@ -24,7 +24,7 @@ interface Form { } export default function EmailForm({onLogin, email: preFilledEmail}: EmailFormProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation(["login", "common"]) const $password = useRef(null) const schema = yup.object().shape({ @@ -32,7 +32,7 @@ export default function EmailForm({onLogin, email: preFilledEmail}: EmailFormPro .string() .email() .required() - .label(t("routes.LoginRoute.forms.email.form.email.label")), + .label(t("fields.email.label", {ns: "common"})), }) const {mutateAsync} = useMutation(loginWithEmail, { @@ -63,9 +63,9 @@ export default function EmailForm({onLogin, email: preFilledEmail}: EmailFormPro
@@ -77,7 +77,7 @@ export default function EmailForm({onLogin, email: preFilledEmail}: EmailFormPro name="email" id="email" label="Email" - placeholder={t("routes.LoginRoute.forms.email.form.email.placeholder")} + placeholder={t("fields.email.placeholder", {ns: "common"})} inputRef={$password} inputMode="email" value={formik.values.email} diff --git a/src/route-widgets/LoginRoute/OTPForm.tsx b/src/route-widgets/LoginRoute/OTPForm.tsx index 457c21d..a3c88c9 100644 --- a/src/route-widgets/LoginRoute/OTPForm.tsx +++ b/src/route-widgets/LoginRoute/OTPForm.tsx @@ -1,19 +1,21 @@ import * as yup from "yup" import {ReactElement} from "react" -import {useMutation} from "@tanstack/react-query" -import {ServerUser} from "~/server-types" -import {AxiosError} from "axios" -import {verifyOTP} from "~/apis" import {useTranslation} from "react-i18next" import {useFormik} from "formik" +import {BsPhone, BsShieldLockFill} from "react-icons/bs" +import {MdChevronRight} from "react-icons/md" +import {Link as RouterLink} from "react-router-dom" +import {AxiosError} from "axios" + +import {useMutation} from "@tanstack/react-query" +import {LoadingButton} from "@mui/lab" +import {Box, Button, Grid, InputAdornment, TextField, Typography} from "@mui/material" + +import {verifyOTP} from "~/apis" import {parseFastAPIError} from "~/utils" import {MultiStepFormElement} from "~/components" -import {Box, Button, Grid, InputAdornment, TextField, Typography} from "@mui/material" -import {BsPhone, BsShieldLockFill} from "react-icons/bs" -import {LoadingButton} from "@mui/lab" -import {MdChevronRight} from "react-icons/md" import {useErrorSuccessSnacks} from "~/hooks" -import {Link as RouterLink} from "react-router-dom" +import {ServerUser} from "~/server-types" interface Form { code: string @@ -30,7 +32,7 @@ export default function OTPForm({ onConfirm, onCodeUnavailable, }: OTPFormProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation(["login", "common"]) const {showError} = useErrorSuccessSnacks() const {mutateAsync} = useMutation( code => @@ -42,7 +44,7 @@ export default function OTPForm({ onSuccess: onConfirm, onError: error => { if (error.response?.status === 410 || error.response?.status === 404) { - showError(t("routes.LoginRoute.forms.otp.unavailable").toString()) + showError(t("forms.otp.isUnavailable").toString()) onCodeUnavailable() } }, @@ -50,7 +52,10 @@ export default function OTPForm({ ) const schema = yup.object().shape({ - code: yup.string().required().label(t("routes.LoginRoute.forms.otp.code.label")), + code: yup + .string() + .required() + .label(t("fields.2faCode.label", {ns: "common"})), }) const formik = useFormik({ @@ -81,7 +86,7 @@ export default function OTPForm({ > - {t("routes.LoginRoute.forms.otp.title")} + {t("forms.otp.title")} @@ -91,7 +96,7 @@ export default function OTPForm({ - {t("routes.LoginRoute.forms.otp.description")} + {t("forms.otp.description")} @@ -101,7 +106,8 @@ export default function OTPForm({ fullWidth name="code" id="code" - label={t("routes.LoginRoute.forms.otp.code.label")} + placeholder={t("fields.2faCode.placeholder", {ns: "common"})} + label={t("fields.2faCode.label", {ns: "common"})} value={formik.values.code} onChange={formik.handleChange} disabled={formik.isSubmitting} @@ -125,12 +131,12 @@ export default function OTPForm({ type="submit" startIcon={} > - {t("routes.LoginRoute.forms.otp.submit")} + {t("forms.otp.continueActionLabel")}