diff options
Diffstat (limited to 'web/src')
| -rw-r--r-- | web/src/i18n/index.ts | 35 | ||||
| -rw-r--r-- | web/src/i18n/locales/en.json | 53 | ||||
| -rw-r--r-- | web/src/i18n/locales/es.json | 53 | ||||
| -rw-r--r-- | web/src/index.tsx | 1 | ||||
| -rw-r--r-- | web/src/views/DeviceRegistration/RegisterOneTimePassword.tsx | 28 | ||||
| -rw-r--r-- | web/src/views/LoadingPage/LoadingPage.tsx | 4 | ||||
| -rw-r--r-- | web/src/views/LoginPortal/Authenticated.tsx | 4 | ||||
| -rw-r--r-- | web/src/views/LoginPortal/AuthenticatedView/AuthenticatedView.tsx | 6 | ||||
| -rw-r--r-- | web/src/views/LoginPortal/FirstFactor/FirstFactorForm.tsx | 16 | ||||
| -rw-r--r-- | web/src/views/LoginPortal/SecondFactor/MethodContainer.tsx | 17 | ||||
| -rw-r--r-- | web/src/views/LoginPortal/SecondFactor/MethodSelectionDialog.tsx | 8 | ||||
| -rw-r--r-- | web/src/views/LoginPortal/SecondFactor/OneTimePasswordMethod.tsx | 12 | ||||
| -rw-r--r-- | web/src/views/LoginPortal/SecondFactor/SecondFactorForm.tsx | 14 | ||||
| -rw-r--r-- | web/src/views/LoginPortal/SignOut/SignOut.tsx | 10 | ||||
| -rw-r--r-- | web/src/views/ResetPassword/ResetPasswordStep1.tsx | 14 | ||||
| -rw-r--r-- | web/src/views/ResetPassword/ResetPasswordStep2.tsx | 28 |
16 files changed, 240 insertions, 63 deletions
diff --git a/web/src/i18n/index.ts b/web/src/i18n/index.ts new file mode 100644 index 000000000..eea659647 --- /dev/null +++ b/web/src/i18n/index.ts @@ -0,0 +1,35 @@ +import i18n from "i18next"; +import LanguageDetector from "i18next-browser-languagedetector"; +import XHR from "i18next-http-backend"; +import { initReactI18next } from "react-i18next"; + +import langEn from "@i18n/locales/en.json"; +import langEs from "@i18n/locales/es.json"; + +const resources = { + en: langEn, + es: langEs, +}; + +const options = { + order: ["querystring", "navigator"], + lookupQuerystring: "lng", +}; + +i18n.use(XHR) + .use(LanguageDetector) + .use(initReactI18next) + .init({ + detection: options, + resources, + ns: [""], + defaultNS: "", + fallbackLng: "en", + supportedLngs: ["en", "es"], + interpolation: { + escapeValue: false, + }, + debug: false, + }); + +export default i18n; diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json new file mode 100644 index 000000000..8750382db --- /dev/null +++ b/web/src/i18n/locales/en.json @@ -0,0 +1,53 @@ +{ + "Portal": { + "An email has been sent to your address to complete the process": "An email has been sent to your address to complete the process.", + "Authenticated": "Authenticated", + "Cancel": "Cancel", + "Contact your administrator to register a device": "Contact your administrator to register a device.", + "Could not obtain user settings": "Could not obtain user settings", + "Done": "Done", + "Enter new password": "Enter new password", + "Enter one-time password": "Enter one-time password", + "Failed to register device, the provided link is expired or has already been used": "Failed to register device, the provided link is expired or has already been used", + "Hi": "Hi", + "Incorrect username or password": "Incorrect username or password.", + "Loading": "Loading", + "Logout": "Logout", + "Lost your device?": "Lost your device?", + "Methods": "Methods", + "Need Google Authenticator?": "Need Google Authenticator?", + "New password": "New password", + "No verification token provided": "No verification token provided", + "OTP Secret copied to clipboard": "OTP Secret copied to clipboard.", + "OTP URL copied to clipboard": "OTP URL copied to clipboard.", + "One-Time Password": "One-Time Password", + "Password has been reset": "Password has been reset.", + "Password": "Password", + "Passwords do not match": "Passwords do not match.", + "Push Notification": "Push Notification", + "Register device": "Register device", + "Register your first device by clicking on the link below": "Register your first device by clicking on the link below.", + "Remember me": "Remember me", + "Repeat new password": "Repeat new password", + "Reset password": "Reset password", + "Reset password?": "Reset password?", + "Reset": "Reset", + "Scan QR Code": "Scan QR Code", + "Secret": "Secret", + "Security Key - U2F": "Security Key - U2F", + "Select a Device": "Select a Device", + "Sign in": "Sign in", + "Sign out": "Sign out", + "The resource you're attempting to access requires two-factor authentication": "The resource you're attempting to access requires two-factor authentication.", + "There was a problem initiating the registration process": "There was a problem initiating the registration process", + "There was an issue completing the process. The verification token might have expired": "There was an issue completing the process. The verification token might have expired.", + "There was an issue initiating the password reset process": "There was an issue initiating the password reset process.", + "There was an issue resetting the password": "There was an issue resetting the password", + "There was an issue signing out": "There was an issue signing out", + "Time-based One-Time Password": "Time-based One-Time Password", + "Username": "Username", + "You must open the link from the same device and browser that initiated the registration process": "You must open the link from the same device and browser that initiated the registration process", + "You're being signed out and redirected": "You're being signed out and redirected", + "Your supplied password does not meet the password policy requirements": "Your supplied password does not meet the password policy requirements." + } +} diff --git a/web/src/i18n/locales/es.json b/web/src/i18n/locales/es.json new file mode 100644 index 000000000..30c23e7ab --- /dev/null +++ b/web/src/i18n/locales/es.json @@ -0,0 +1,53 @@ +{ + "Portal": { + "An email has been sent to your address to complete the process": "Un correo ha sido enviado a su cuenta para completar el proceso", + "Authenticated": "Autenticado", + "Cancel": "Cancelar", + "Contact your administrator to register a device": "Contacte a su administrador para registrar un dispositivo.", + "Could not obtain user settings": "Error al obtener configuración de usuario", + "Done": "Hecho", + "Enter new password": "Ingrese una nueva contraseña", + "Enter one-time password": "Ingrese contraseña de un solo uso (OTP)", + "Failed to register device, the provided link is expired or has already been used": "Error al registrar dispositivo, el link expiró o ya ha sido utilizado", + "Hi": "Hola", + "Incorrect username or password": "Usuario y/o contraseña incorrectos", + "Loading": "Cargando", + "Logout": "Cerrar Sesión", + "Lost your device?": "Perdió su dispositivo?", + "Methods": "Métodos", + "Need Google Authenticator?": "Necesita Google Authenticator?", + "New password": "Nueva contraseña", + "No verification token provided": "No se ha recibido el token de verificación", + "OTP Secret copied to clipboard": "La clave OTP ha sido copiada al portapapeles", + "OTP URL copied to clipboard": "la URL OTP ha sido copiada al portapapeles.", + "One-Time Password": "Contraseña de un solo uso (OTP)", + "Password has been reset": "La contraseña ha sido restablecida.", + "Password": "Contraseña", + "Passwords do not match": "Las contraseñas no coinciden.", + "Push Notification": "Notificaciones Push", + "Register device": "Registrar Dispositivo", + "Register your first device by clicking on the link below": "Registre su primer dispositivo, haciendo click en el siguiente link.", + "Remember me": "Recordarme", + "Repeat new password": "Repetir la contraseña", + "Reset password": "Restablecer Contraseña", + "Reset password?": "Olvidé mi contraseña", + "Reset": "Restablecer", + "Scan QR Code": "Escanear Código QR", + "Secret": "Secreto", + "Security Key - U2F": "Llave de Seguridad - U2F", + "Select a Device": "Seleccionar Dispositivo", + "Sign in": "Iniciar Sesión", + "Sign out": "Cerrar Sesión", + "The resource you're attempting to access requires two-factor authentication": "El recurso que intenta alcanzar requiere un segundo factor de autenticación (2FA).", + "There was a problem initiating the registration process": "Ocurrió un problema al iniciar el proceso de registración", + "There was an issue completing the process. The verification token might have expired": "Ocurrió un problema mientras se completaba el proceso. El token de verificación pudo haber expirado.", + "There was an issue initiating the password reset process": "Ha ocurrido un error al iniciar el proceso de proceso de restauración de contraseña.", + "There was an issue resetting the password": "Ocurrió un error al intentar restablecer la contraseña", + "There was an issue signing out": "Ocurrió un error al intentar cerrar sesión", + "Time-based One-Time Password": "Contraseña de uso único - OTP", + "Username": "Usuario", + "You must open the link from the same device and browser that initiated the registration process": "Debe abrir el link desde el mismo dispositivo y navegador desde el que inició el proceso de registración", + "You're being signed out and redirected": "Cerrando Sesión y redirigiendo", + "Your supplied password does not meet the password policy requirements": "La contraseña suministrada no cumple con los requerimientos de la política de contraseñas" + } +} diff --git a/web/src/index.tsx b/web/src/index.tsx index f306ac254..ee2d6014f 100644 --- a/web/src/index.tsx +++ b/web/src/index.tsx @@ -5,6 +5,7 @@ import ReactDOM from "react-dom"; import "@root/index.css"; import App from "@root/App"; import * as serviceWorker from "@root/serviceWorker"; +import "./i18n/index.ts"; ReactDOM.render(<App />, document.getElementById("root")); diff --git a/web/src/views/DeviceRegistration/RegisterOneTimePassword.tsx b/web/src/views/DeviceRegistration/RegisterOneTimePassword.tsx index 560ebd47a..b26eca2b0 100644 --- a/web/src/views/DeviceRegistration/RegisterOneTimePassword.tsx +++ b/web/src/views/DeviceRegistration/RegisterOneTimePassword.tsx @@ -6,6 +6,7 @@ import { makeStyles, Typography, Button, IconButton, Link, CircularProgress, Tex import { red } from "@material-ui/core/colors"; import classnames from "classnames"; import QRCode from "qrcode.react"; +import { useTranslation } from "react-i18next"; import { useLocation, useNavigate } from "react-router-dom"; import AppStoreBadges from "@components/AppStoreBadges"; @@ -26,6 +27,7 @@ const RegisterOneTimePassword = function () { const { createSuccessNotification, createErrorNotification } = useNotifications(); const [hasErrored, setHasErrored] = useState(false); const [isLoading, setIsLoading] = useState(false); + const { t: translate } = useTranslation("Portal"); // Get the token from the query param to give it back to the API when requesting // the secret for OTP. @@ -49,17 +51,19 @@ const RegisterOneTimePassword = function () { console.error(err); if ((err as Error).message.includes("Request failed with status code 403")) { createErrorNotification( - "You must open the link from the same device and browser that initiated the registration process", + translate( + "You must open the link from the same device and browser that initiated the registration process", + ), ); } else { createErrorNotification( - "Failed to register device, the provided link is expired or has already been used", + translate("Failed to register device, the provided link is expired or has already been used"), ); } setHasErrored(true); } setIsLoading(false); - }, [processToken, createErrorNotification]); + }, [processToken, createErrorNotification, translate]); useEffect(() => { completeRegistrationProcess(); @@ -82,10 +86,12 @@ const RegisterOneTimePassword = function () { const qrcodeFuzzyStyle = isLoading || hasErrored ? style.fuzzy : undefined; return ( - <LoginLayout title="Scan QR Code"> + <LoginLayout title={translate("Scan QR Code")}> <div className={style.root}> <div className={style.googleAuthenticator}> - <Typography className={style.googleAuthenticatorText}>Need Google Authenticator?</Typography> + <Typography className={style.googleAuthenticatorText}> + {translate("Need Google Authenticator?")} + </Typography> <AppStoreBadges iconSize={128} targetBlank @@ -105,7 +111,7 @@ const RegisterOneTimePassword = function () { {secretURL !== "empty" ? ( <TextField id="secret-url" - label="Secret" + label={translate("Secret")} className={style.secret} value={secretURL} InputProps={{ @@ -113,8 +119,12 @@ const RegisterOneTimePassword = function () { }} /> ) : null} - {secretBase32 ? SecretButton(secretBase32, "OTP Secret copied to clipboard.", faKey) : null} - {secretURL !== "empty" ? SecretButton(secretURL, "OTP URL copied to clipboard.", faCopy) : null} + {secretBase32 + ? SecretButton(secretBase32, translate("OTP Secret copied to clipboard"), faKey) + : null} + {secretURL !== "empty" + ? SecretButton(secretURL, translate("OTP URL copied to clipboard"), faCopy) + : null} </div> <Button variant="contained" @@ -123,7 +133,7 @@ const RegisterOneTimePassword = function () { onClick={handleDoneClick} disabled={isLoading} > - Done + {translate("Done")} </Button> </div> </LoginLayout> diff --git a/web/src/views/LoadingPage/LoadingPage.tsx b/web/src/views/LoadingPage/LoadingPage.tsx index a37dd8365..da7d0e52e 100644 --- a/web/src/views/LoadingPage/LoadingPage.tsx +++ b/web/src/views/LoadingPage/LoadingPage.tsx @@ -1,15 +1,17 @@ import React from "react"; import { useTheme, Typography, Grid } from "@material-ui/core"; +import { useTranslation } from "react-i18next"; import ReactLoading from "react-loading"; const LoadingPage = function () { const theme = useTheme(); + const { t: translate } = useTranslation("Portal"); return ( <Grid container alignItems="center" justifyContent="center" style={{ minHeight: "100vh" }}> <Grid item style={{ textAlign: "center", display: "inline-block" }}> <ReactLoading width={64} height={64} color={theme.custom.loadingBar} type="bars" /> - <Typography>Loading...</Typography> + <Typography>{translate("Loading")}...</Typography> </Grid> </Grid> ); diff --git a/web/src/views/LoginPortal/Authenticated.tsx b/web/src/views/LoginPortal/Authenticated.tsx index 1f99807bd..aa2f4dd98 100644 --- a/web/src/views/LoginPortal/Authenticated.tsx +++ b/web/src/views/LoginPortal/Authenticated.tsx @@ -1,17 +1,19 @@ import React from "react"; import { Typography, makeStyles } from "@material-ui/core"; +import { useTranslation } from "react-i18next"; import SuccessIcon from "@components/SuccessIcon"; const Authenticated = function () { const classes = useStyles(); + const { t: translate } = useTranslation("Portal"); return ( <div id="authenticated-stage"> <div className={classes.iconContainer}> <SuccessIcon /> </div> - <Typography>Authenticated</Typography> + <Typography>{translate("Authenticated")}</Typography> </div> ); }; diff --git a/web/src/views/LoginPortal/AuthenticatedView/AuthenticatedView.tsx b/web/src/views/LoginPortal/AuthenticatedView/AuthenticatedView.tsx index 40c1241cc..15a73d623 100644 --- a/web/src/views/LoginPortal/AuthenticatedView/AuthenticatedView.tsx +++ b/web/src/views/LoginPortal/AuthenticatedView/AuthenticatedView.tsx @@ -1,6 +1,7 @@ import React from "react"; import { Grid, makeStyles, Button } from "@material-ui/core"; +import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; import { LogoutRoute as SignOutRoute } from "@constants/Routes"; @@ -14,17 +15,18 @@ export interface Props { const AuthenticatedView = function (props: Props) { const style = useStyles(); const navigate = useNavigate(); + const { t: translate } = useTranslation("Portal"); const handleLogoutClick = () => { navigate(SignOutRoute); }; return ( - <LoginLayout id="authenticated-stage" title={`Hi ${props.name}`} showBrand> + <LoginLayout id="authenticated-stage" title={`${translate("Hi")} ${props.name}`} showBrand> <Grid container> <Grid item xs={12}> <Button color="secondary" onClick={handleLogoutClick} id="logout-button"> - Logout + {translate("Logout")} </Button> </Grid> <Grid item xs={12} className={style.mainContainer}> diff --git a/web/src/views/LoginPortal/FirstFactor/FirstFactorForm.tsx b/web/src/views/LoginPortal/FirstFactor/FirstFactorForm.tsx index 43e62e2bb..df92f0610 100644 --- a/web/src/views/LoginPortal/FirstFactor/FirstFactorForm.tsx +++ b/web/src/views/LoginPortal/FirstFactor/FirstFactorForm.tsx @@ -2,6 +2,7 @@ import React, { MutableRefObject, useEffect, useRef, useState } from "react"; import { makeStyles, Grid, Button, FormControlLabel, Checkbox, Link } from "@material-ui/core"; import classnames from "classnames"; +import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; import FixedTextField from "@components/FixedTextField"; @@ -37,6 +38,7 @@ const FirstFactorForm = function (props: Props) { // TODO (PR: #806, Issue: #511) potentially refactor const usernameRef = useRef() as MutableRefObject<HTMLInputElement>; const passwordRef = useRef() as MutableRefObject<HTMLInputElement>; + const { t: translate } = useTranslation("Portal"); useEffect(() => { const timeout = setTimeout(() => usernameRef.current.focus(), 10); return () => clearTimeout(timeout); @@ -66,7 +68,7 @@ const FirstFactorForm = function (props: Props) { props.onAuthenticationSuccess(res ? res.redirect : undefined); } catch (err) { console.error(err); - createErrorNotification("Incorrect username or password."); + createErrorNotification(translate("Incorrect username or password")); props.onAuthenticationFailure(); setPassword(""); passwordRef.current.focus(); @@ -78,14 +80,14 @@ const FirstFactorForm = function (props: Props) { }; return ( - <LoginLayout id="first-factor-stage" title="Sign in" showBrand> + <LoginLayout id="first-factor-stage" title={translate("Sign in")} showBrand> <Grid container spacing={2}> <Grid item xs={12}> <FixedTextField // TODO (PR: #806, Issue: #511) potentially refactor inputRef={usernameRef} id="username-textfield" - label="Username" + label={translate("Username")} variant="outlined" required value={username} @@ -115,7 +117,7 @@ const FirstFactorForm = function (props: Props) { // TODO (PR: #806, Issue: #511) potentially refactor inputRef={passwordRef} id="password-textfield" - label="Password" + label={translate("Password")} variant="outlined" required fullWidth @@ -163,7 +165,7 @@ const FirstFactorForm = function (props: Props) { /> } className={style.rememberMe} - label="Remember me" + label={translate("Remember me")} /> </Grid> ) : null} @@ -176,7 +178,7 @@ const FirstFactorForm = function (props: Props) { disabled={disabled} onClick={handleSignIn} > - Sign in + {translate("Sign in")} </Button> </Grid> {props.resetPassword ? ( @@ -187,7 +189,7 @@ const FirstFactorForm = function (props: Props) { onClick={handleResetPasswordClick} className={style.resetLink} > - Reset password? + {translate("Reset password?")} </Link> </Grid> ) : null} diff --git a/web/src/views/LoginPortal/SecondFactor/MethodContainer.tsx b/web/src/views/LoginPortal/SecondFactor/MethodContainer.tsx index 6874a50cf..1d36b1804 100644 --- a/web/src/views/LoginPortal/SecondFactor/MethodContainer.tsx +++ b/web/src/views/LoginPortal/SecondFactor/MethodContainer.tsx @@ -2,6 +2,7 @@ import React, { ReactNode, Fragment } from "react"; import { makeStyles, Typography, Link, useTheme } from "@material-ui/core"; import classnames from "classnames"; +import { useTranslation } from "react-i18next"; import InformationIcon from "@components/InformationIcon"; import Authenticated from "@views/LoginPortal/Authenticated"; @@ -27,12 +28,13 @@ export interface Props { const DefaultMethodContainer = function (props: Props) { const style = useStyles(); + const { t: translate } = useTranslation("Portal"); const registerMessage = props.registered ? props.title === "Push Notification" ? "" - : "Lost your device?" - : "Register device"; - const selectMessage = "Select a Device"; + : translate("Lost your device?") + : translate("Register device"); + const selectMessage = translate("Select a Device"); let container: ReactNode; let stateClass: string = ""; @@ -95,6 +97,7 @@ interface NotRegisteredContainerProps { } function NotRegisteredContainer(props: NotRegisteredContainerProps) { + const { t: translate } = useTranslation("Portal"); const theme = useTheme(); return ( <Fragment> @@ -102,14 +105,14 @@ function NotRegisteredContainer(props: NotRegisteredContainerProps) { <InformationIcon /> </div> <Typography style={{ color: "#5858ff" }}> - The resource you're attempting to access requires two-factor authentication. + {translate("The resource you're attempting to access requires two-factor authentication")} </Typography> <Typography style={{ color: "#5858ff" }}> {props.title === "Push Notification" ? props.duoSelfEnrollment - ? "Register your first device by clicking on the link below." - : "Contact your administrator to register a device." - : "Register your first device by clicking on the link below."} + ? translate("Register your first device by clicking on the link below") + : translate("Contact your administrator to register a device.") + : translate("Register your first device by clicking on the link below")} </Typography> </Fragment> ); diff --git a/web/src/views/LoginPortal/SecondFactor/MethodSelectionDialog.tsx b/web/src/views/LoginPortal/SecondFactor/MethodSelectionDialog.tsx index 1ba5d9038..ce9cb697d 100644 --- a/web/src/views/LoginPortal/SecondFactor/MethodSelectionDialog.tsx +++ b/web/src/views/LoginPortal/SecondFactor/MethodSelectionDialog.tsx @@ -10,6 +10,7 @@ import { Typography, useTheme, } from "@material-ui/core"; +import { useTranslation } from "react-i18next"; import FingerTouchIcon from "@components/FingerTouchIcon"; import PushNotificationIcon from "@components/PushNotificationIcon"; @@ -28,6 +29,7 @@ export interface Props { const MethodSelectionDialog = function (props: Props) { const style = useStyles(); const theme = useTheme(); + const { t: translate } = useTranslation("Portal"); const pieChartIcon = ( <TimerIcon width={24} height={24} period={15} color={theme.palette.primary.main} backgroundColor={"white"} /> @@ -40,7 +42,7 @@ const MethodSelectionDialog = function (props: Props) { {props.methods.has(SecondFactorMethod.TOTP) ? ( <MethodItem id="one-time-password-option" - method="Time-based One-Time Password" + method={translate("Time-based One-Time Password")} icon={pieChartIcon} onClick={() => props.onClick(SecondFactorMethod.TOTP)} /> @@ -48,7 +50,7 @@ const MethodSelectionDialog = function (props: Props) { {props.methods.has(SecondFactorMethod.U2F) && props.u2fSupported ? ( <MethodItem id="security-key-option" - method="Security Key - U2F" + method={translate("Security Key - U2F")} icon={<FingerTouchIcon size={32} />} onClick={() => props.onClick(SecondFactorMethod.U2F)} /> @@ -56,7 +58,7 @@ const MethodSelectionDialog = function (props: Props) { {props.methods.has(SecondFactorMethod.MobilePush) ? ( <MethodItem id="push-notification-option" - method="Push Notification" + method={translate("Push Notification")} icon={<PushNotificationIcon width={32} height={32} />} onClick={() => props.onClick(SecondFactorMethod.MobilePush)} /> diff --git a/web/src/views/LoginPortal/SecondFactor/OneTimePasswordMethod.tsx b/web/src/views/LoginPortal/SecondFactor/OneTimePasswordMethod.tsx index c0920c2ac..d8f38a779 100644 --- a/web/src/views/LoginPortal/SecondFactor/OneTimePasswordMethod.tsx +++ b/web/src/views/LoginPortal/SecondFactor/OneTimePasswordMethod.tsx @@ -1,5 +1,7 @@ import React, { useCallback, useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; + import { useRedirectionURL } from "@hooks/RedirectionURL"; import { useUserInfoTOTPConfiguration } from "@hooks/UserInfoTOTPConfiguration"; import { completeTOTPSignIn } from "@services/OneTimePassword"; @@ -31,20 +33,20 @@ const OneTimePasswordMethod = function (props: Props) { props.authenticationLevel === AuthenticationLevel.TwoFactor ? State.Success : State.Idle, ); const redirectionURL = useRedirectionURL(); + const { t: translate } = useTranslation("Portal"); const { onSignInSuccess, onSignInError } = props; const onSignInErrorCallback = useRef(onSignInError).current; const onSignInSuccessCallback = useRef(onSignInSuccess).current; - const [resp, fetch, , err] = useUserInfoTOTPConfiguration(); useEffect(() => { if (err) { console.error(err); - onSignInErrorCallback(new Error("Could not obtain user settings")); + onSignInErrorCallback(new Error(translate("Could not obtain user settings"))); setState(State.Failure); } - }, [onSignInErrorCallback, err]); + }, [onSignInErrorCallback, err, translate]); useEffect(() => { if (props.registered && props.authenticationLevel === AuthenticationLevel.OneFactor) { @@ -105,8 +107,8 @@ const OneTimePasswordMethod = function (props: Props) { return ( <MethodContainer id={props.id} - title="One-Time Password" - explanation="Enter one-time password" + title={translate("One-Time Password")} + explanation={translate("Enter one-time password")} duoSelfEnrollment={false} registered={props.registered} state={methodState} diff --git a/web/src/views/LoginPortal/SecondFactor/SecondFactorForm.tsx b/web/src/views/LoginPortal/SecondFactor/SecondFactorForm.tsx index 97c266da1..605cf954b 100644 --- a/web/src/views/LoginPortal/SecondFactor/SecondFactorForm.tsx +++ b/web/src/views/LoginPortal/SecondFactor/SecondFactorForm.tsx @@ -1,6 +1,7 @@ import React, { useState, useEffect } from "react"; import { Grid, makeStyles, Button } from "@material-ui/core"; +import { useTranslation } from "react-i18next"; import { Route, Routes, useNavigate } from "react-router-dom"; import u2fApi from "u2f-api"; @@ -23,7 +24,7 @@ import OneTimePasswordMethod from "@views/LoginPortal/SecondFactor/OneTimePasswo import PushNotificationMethod from "@views/LoginPortal/SecondFactor/PushNotificationMethod"; import SecurityKeyMethod from "@views/LoginPortal/SecondFactor/SecurityKeyMethod"; -const EMAIL_SENT_NOTIFICATION = "An email has been sent to your address to complete the process."; +const EMAIL_SENT_NOTIFICATION = "An email has been sent to your address to complete the process"; export interface Props { authenticationLevel: AuthenticationLevel; @@ -42,6 +43,7 @@ const SecondFactorForm = function (props: Props) { const { createInfoNotification, createErrorNotification } = useNotifications(); const [registrationInProgress, setRegistrationInProgress] = useState(false); const [u2fSupported, setU2fSupported] = useState(false); + const { t: translate } = useTranslation("Portal"); // Check that U2F is supported. useEffect(() => { @@ -59,10 +61,10 @@ const SecondFactorForm = function (props: Props) { setRegistrationInProgress(true); try { await initiateRegistrationFunc(); - createInfoNotification(EMAIL_SENT_NOTIFICATION); + createInfoNotification(translate(EMAIL_SENT_NOTIFICATION)); } catch (err) { console.error(err); - createErrorNotification("There was a problem initiating the registration process"); + createErrorNotification(translate("There was a problem initiating the registration process")); } setRegistrationInProgress(false); }; @@ -88,7 +90,7 @@ const SecondFactorForm = function (props: Props) { }; return ( - <LoginLayout id="second-factor-stage" title={`Hi ${props.userInfo.display_name}`} showBrand> + <LoginLayout id="second-factor-stage" title={`${translate("Hi")} ${props.userInfo.display_name}`} showBrand> <MethodSelectionDialog open={methodSelectionOpen} methods={props.configuration.available_methods} @@ -99,11 +101,11 @@ const SecondFactorForm = function (props: Props) { <Grid container> <Grid item xs={12}> <Button color="secondary" onClick={handleLogoutClick} id="logout-button"> - Logout + {translate("Logout")} </Button> {" | "} <Button color="secondary" onClick={handleMethodSelectionClick} id="methods-button"> - Methods + {translate("Methods")} </Button> </Grid> <Grid item xs={12} className={style.methodContainer}> diff --git a/web/src/views/LoginPortal/SignOut/SignOut.tsx b/web/src/views/LoginPortal/SignOut/SignOut.tsx index 2c77df1cd..4346f76d6 100644 --- a/web/src/views/LoginPortal/SignOut/SignOut.tsx +++ b/web/src/views/LoginPortal/SignOut/SignOut.tsx @@ -1,6 +1,7 @@ import React, { useEffect, useCallback, useState } from "react"; import { Typography, makeStyles } from "@material-ui/core"; +import { useTranslation } from "react-i18next"; import { Navigate } from "react-router-dom"; import { FirstFactorRoute } from "@constants/Routes"; @@ -21,6 +22,7 @@ const SignOut = function (props: Props) { const redirector = useRedirector(); const [timedOut, setTimedOut] = useState(false); const [safeRedirect, setSafeRedirect] = useState(false); + const { t: translate } = useTranslation("Portal"); const doSignOut = useCallback(async () => { try { @@ -36,9 +38,9 @@ const SignOut = function (props: Props) { }, 2000); } catch (err) { console.error(err); - createErrorNotification("There was an issue signing out"); + createErrorNotification(translate("There was an issue signing out")); } - }, [createErrorNotification, redirectionURL, setSafeRedirect, setTimedOut, mounted]); + }, [createErrorNotification, redirectionURL, setSafeRedirect, setTimedOut, mounted, translate]); useEffect(() => { doSignOut(); @@ -53,8 +55,8 @@ const SignOut = function (props: Props) { } return ( - <LoginLayout title="Sign out"> - <Typography className={style.typo}>You're being signed out and redirected...</Typography> + <LoginLayout title={translate("Sign out")}> + <Typography className={style.typo}>{translate("You're being signed out and redirected")}...</Typography> </LoginLayout> ); }; diff --git a/web/src/views/ResetPassword/ResetPasswordStep1.tsx b/web/src/views/ResetPassword/ResetPasswordStep1.tsx index 605ec5e9c..ce6f77185 100644 --- a/web/src/views/ResetPassword/ResetPasswordStep1.tsx +++ b/web/src/views/ResetPassword/ResetPasswordStep1.tsx @@ -1,6 +1,7 @@ import React, { useState } from "react"; import { Grid, Button, makeStyles } from "@material-ui/core"; +import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; import FixedTextField from "@components/FixedTextField"; @@ -15,6 +16,7 @@ const ResetPasswordStep1 = function () { const [error, setError] = useState(false); const { createInfoNotification, createErrorNotification } = useNotifications(); const navigate = useNavigate(); + const { t: translate } = useTranslation("Portal"); const doInitiateResetPasswordProcess = async () => { if (username === "") { @@ -24,9 +26,9 @@ const ResetPasswordStep1 = function () { try { await initiateResetPasswordProcess(username); - createInfoNotification("An email has been sent to your address to complete the process."); + createInfoNotification(translate("An email has been sent to your address to complete the process")); } catch (err) { - createErrorNotification("There was an issue initiating the password reset process."); + createErrorNotification(translate("There was an issue initiating the password reset process")); } }; @@ -39,12 +41,12 @@ const ResetPasswordStep1 = function () { }; return ( - <LoginLayout title="Reset password" id="reset-password-step1-stage"> + <LoginLayout title={translate("Reset password")} id="reset-password-step1-stage"> <Grid container className={style.root} spacing={2}> <Grid item xs={12}> <FixedTextField id="username-textfield" - label="Username" + label={translate("Username")} variant="outlined" fullWidth error={error} @@ -60,7 +62,7 @@ const ResetPasswordStep1 = function () { </Grid> <Grid item xs={6}> <Button id="reset-button" variant="contained" color="primary" fullWidth onClick={handleResetClick}> - Reset + {translate("Reset")} </Button> </Grid> <Grid item xs={6}> @@ -71,7 +73,7 @@ const ResetPasswordStep1 = function () { fullWidth onClick={handleCancelClick} > - Cancel + {translate("Cancel")} </Button> </Grid> </Grid> diff --git a/web/src/views/ResetPassword/ResetPasswordStep2.tsx b/web/src/views/ResetPassword/ResetPasswordStep2.tsx index 6b43b0c50..eb0f96b1c 100644 --- a/web/src/views/ResetPassword/ResetPasswordStep2.tsx +++ b/web/src/views/ResetPassword/ResetPasswordStep2.tsx @@ -2,6 +2,7 @@ import React, { useState, useCallback, useEffect } from "react"; import { Grid, Button, makeStyles } from "@material-ui/core"; import classnames from "classnames"; +import { useTranslation } from "react-i18next"; import { useLocation, useNavigate } from "react-router-dom"; import FixedTextField from "@components/FixedTextField"; @@ -20,6 +21,7 @@ const ResetPasswordStep2 = function () { const [errorPassword1, setErrorPassword1] = useState(false); const [errorPassword2, setErrorPassword2] = useState(false); const { createSuccessNotification, createErrorNotification } = useNotifications(); + const { t: translate } = useTranslation("Portal"); const navigate = useNavigate(); // Get the token from the query param to give it back to the API when requesting // the secret for OTP. @@ -28,7 +30,7 @@ const ResetPasswordStep2 = function () { const completeProcess = useCallback(async () => { if (!processToken) { setFormDisabled(true); - createErrorNotification("No verification token provided"); + createErrorNotification(translate("No verification token provided")); return; } @@ -39,11 +41,11 @@ const ResetPasswordStep2 = function () { } catch (err) { console.error(err); createErrorNotification( - "There was an issue completing the process. The verification token might have expired.", + translate("There was an issue completing the process. The verification token might have expired"), ); setFormDisabled(true); } - }, [processToken, createErrorNotification]); + }, [processToken, createErrorNotification, translate]); useEffect(() => { completeProcess(); @@ -62,21 +64,23 @@ const ResetPasswordStep2 = function () { if (password1 !== password2) { setErrorPassword1(true); setErrorPassword2(true); - createErrorNotification("Passwords do not match."); + createErrorNotification(translate("Passwords do not match")); return; } try { await resetPassword(password1); - createSuccessNotification("Password has been reset."); + createSuccessNotification(translate("Password has been reset")); setTimeout(() => navigate(FirstFactorRoute), 1500); setFormDisabled(true); } catch (err) { console.error(err); if ((err as Error).message.includes("0000052D.")) { - createErrorNotification("Your supplied password does not meet the password policy requirements."); + createErrorNotification( + translate("Your supplied password does not meet the password policy requirements"), + ); } else { - createErrorNotification("There was an issue resetting the password."); + createErrorNotification(translate("There was an issue resetting the password")); } } }; @@ -86,12 +90,12 @@ const ResetPasswordStep2 = function () { const handleCancelClick = () => navigate(FirstFactorRoute); return ( - <LoginLayout title="Enter new password" id="reset-password-step2-stage"> + <LoginLayout title={translate("Enter new password")} id="reset-password-step2-stage"> <Grid container className={style.root} spacing={2}> <Grid item xs={12}> <FixedTextField id="password1-textfield" - label="New password" + label={translate("New password")} variant="outlined" type="password" value={password1} @@ -105,7 +109,7 @@ const ResetPasswordStep2 = function () { <Grid item xs={12}> <FixedTextField id="password2-textfield" - label="Repeat new password" + label={translate("Repeat new password")} variant="outlined" type="password" disabled={formDisabled} @@ -132,7 +136,7 @@ const ResetPasswordStep2 = function () { onClick={handleResetClick} className={style.fullWidth} > - Reset + {translate("Reset")} </Button> </Grid> <Grid item xs={6}> @@ -144,7 +148,7 @@ const ResetPasswordStep2 = function () { onClick={handleCancelClick} className={style.fullWidth} > - Cancel + {translate("Cancel")} </Button> </Grid> </Grid> |
