summaryrefslogtreecommitdiff
path: root/web/src
diff options
context:
space:
mode:
Diffstat (limited to 'web/src')
-rw-r--r--web/src/i18n/index.ts35
-rw-r--r--web/src/i18n/locales/en.json53
-rw-r--r--web/src/i18n/locales/es.json53
-rw-r--r--web/src/index.tsx1
-rw-r--r--web/src/views/DeviceRegistration/RegisterOneTimePassword.tsx28
-rw-r--r--web/src/views/LoadingPage/LoadingPage.tsx4
-rw-r--r--web/src/views/LoginPortal/Authenticated.tsx4
-rw-r--r--web/src/views/LoginPortal/AuthenticatedView/AuthenticatedView.tsx6
-rw-r--r--web/src/views/LoginPortal/FirstFactor/FirstFactorForm.tsx16
-rw-r--r--web/src/views/LoginPortal/SecondFactor/MethodContainer.tsx17
-rw-r--r--web/src/views/LoginPortal/SecondFactor/MethodSelectionDialog.tsx8
-rw-r--r--web/src/views/LoginPortal/SecondFactor/OneTimePasswordMethod.tsx12
-rw-r--r--web/src/views/LoginPortal/SecondFactor/SecondFactorForm.tsx14
-rw-r--r--web/src/views/LoginPortal/SignOut/SignOut.tsx10
-rw-r--r--web/src/views/ResetPassword/ResetPasswordStep1.tsx14
-rw-r--r--web/src/views/ResetPassword/ResetPasswordStep2.tsx28
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>