feat(web): i18n asset overrides (#3040)

This allows overriding translation files in folders with lowercase RFC5646 / BCP47 Format language codes. This also fixes an issues where languages which don't expressly match the language code specified due to having a variant will also match the existing codes.

Co-authored-by: Amir Zarrinkafsh <nightah@me.com>
pull/3107/head^2
James Elliott 2022-04-04 12:15:26 +10:00 committed by GitHub
parent ee9ce27ccf
commit aac4c4772c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 442 additions and 311 deletions

View File

@ -35,7 +35,7 @@ logged for users.
The following changes occurred in 4.30.0:
| Previous Key | New Key |
|:-----------:|:--------------------:|
|:-------------:|:----------------------:|
| host | server.host |
| port | server.port |
| tls_key | server.tls.key |

View File

@ -108,13 +108,15 @@ Example:
```console
/config/assets/
├── favicon.ico
└── logo.png
├── logo.png
└── locales/<lang>[-[variant]]/<namespace>.json
```
| Asset | File name |
|:-----:|:---------------:|
|:-------:|:-------------:|
| Favicon | favicon.ico |
| Logo | logo.png |
| locales | see [locales] |
### read_buffer_size
<div markdown="1">
@ -242,3 +244,43 @@ you're able to tune these individually depending on your needs.
If replacing the Logo for your Authelia portal it is recommended to upload a transparent PNG of your desired logo.
Authelia will automatically resize the logo to an appropriate size to present in the frontend.
#### locales
The locales folder holds folders of internationalization locales. This folder can be utilized to override these locales.
They are the names of locales that are returned by the `navigator.langauge` ECMAScript command. These are generally
those in the [RFC5646 / BCP47 Format](https://datatracker.ietf.org/doc/html/rfc5646) except the name normalized to
lowercase for consistency ease; for example the `en-US` locale should be in the directory `en-us`.
Each directory has json files which you can explore the format of in the
[internal/server/locales](https://github.com/authelia/authelia/tree/master/internal/server/locales) directory on
GitHub. The important part is the key names you wish to override. Each file represents a translation namespace. The list
of current namespaces are below:
| Namespace | Purpose |
|:---------:|:-------------------:|
| portal | Portal translations |
A full example for the `en-US` locale for the portal namespace is `locales/en-us/portal.json`.
Languages in browsers are supported in two forms. In their language only form such as `en` for English, and in their
variant form such as `en-AU` for English (Australian). If a user has the browser language `en-AU` we automatically load
the `en` and `en-AU` languages, where any keys in the `en-AU` language take precedence over the `en` language, and the
translations for the `en` language only applying when a translation from `en-AU` is not available.
List of supported languages and variants:
| Description | Language | Additional Variants | Location |
|:-----------:|:--------:|:-------------------:|:----------------------:|
| English | en | N/A | locales/en/portal.json |
| Spanish | es | N/A | locales/es/portal.json |
| German | de | N/A | locales/de/portal.json |
_**Important Note** Currently users can only override languages that already exist in this list either by overriding
the language itself, or adding a variant form of that language. If you'd like support for another language feel free
to make a PR. We also encourage people to make PR's for variants where the difference in the variants is important._
_**Important Note** Overriding these files will not guarantee any form of stability. Users who planning to utilize these
overrides should either check for changes to the files in the
[en](https://github.com/authelia/authelia/tree/master/internal/server/locales/en) translation prior to upgrading or PR
their translation to ensure it is maintained._

View File

@ -2,28 +2,27 @@ package middlewares
import (
"os"
"strings"
"path/filepath"
"github.com/valyala/fasthttp"
"github.com/authelia/authelia/v4/internal/utils"
)
// AssetOverrideMiddleware allows overriding and serving of specific embedded assets from disk.
func AssetOverrideMiddleware(assetPath string, next fasthttp.RequestHandler) fasthttp.RequestHandler {
func AssetOverrideMiddleware(root string, strip int, next fasthttp.RequestHandler) fasthttp.RequestHandler {
return func(ctx *fasthttp.RequestCtx) {
uri := string(ctx.RequestURI())
file := uri[strings.LastIndex(uri, "/")+1:]
if root == "" {
next(ctx)
if assetPath != "" && utils.IsStringInSlice(file, validOverrideAssets) {
_, err := os.Stat(assetPath + file)
return
}
_, err := os.Stat(filepath.Join(root, string(fasthttp.NewPathSlashesStripper(strip)(ctx))))
if err != nil {
next(ctx)
} else {
fasthttp.FSHandler(assetPath, strings.Count(uri, "/")-1)(ctx)
}
} else {
next(ctx)
return
}
fasthttp.FSHandler(root, strip)(ctx)
}
}

View File

@ -57,6 +57,5 @@ const (
)
var protoHostSeparator = []byte("://")
var validOverrideAssets = []string{"favicon.ico", "logo.png"}
var errPasswordPolicyNoMet = errors.New("the supplied password does not met the security policy")

View File

@ -0,0 +1,87 @@
package server
import (
"embed"
"errors"
"fmt"
"io/fs"
"net/http"
"github.com/valyala/fasthttp"
"github.com/valyala/fasthttp/fasthttpadaptor"
"github.com/authelia/authelia/v4/internal/utils"
)
//go:embed locales
var locales embed.FS
//go:embed public_html
var assets embed.FS
func newPublicHTMLEmbeddedHandler() fasthttp.RequestHandler {
embeddedPath, _ := fs.Sub(assets, "public_html")
return fasthttpadaptor.NewFastHTTPHandler(http.FileServer(http.FS(embeddedPath)))
}
func newLocalesEmbeddedHandler() (handler fasthttp.RequestHandler) {
var languages []string
entries, err := locales.ReadDir("locales")
if err == nil {
for _, entry := range entries {
if entry.IsDir() && len(entry.Name()) == 2 {
languages = append(languages, entry.Name())
}
}
}
return func(ctx *fasthttp.RequestCtx) {
var (
language, variant, locale, namespace string
)
language = ctx.UserValue("language").(string)
namespace = ctx.UserValue("namespace").(string)
locale = language
if v := ctx.UserValue("variant"); v != nil {
variant = v.(string)
locale = fmt.Sprintf("%s-%s", language, locale)
}
var data []byte
if data, err = locales.ReadFile(fmt.Sprintf("locales/%s/%s.json", locale, namespace)); err != nil {
if variant != "" && utils.IsStringInSliceFold(language, languages) {
data = []byte("{}")
}
if len(data) == 0 {
hfsHandleErr(ctx, err)
return
}
}
ctx.SetContentType("application/json")
ctx.SetBody(data)
}
}
func hfsHandleErr(ctx *fasthttp.RequestCtx, err error) {
switch {
case errors.Is(err, fs.ErrNotExist):
writeStatus(ctx, fasthttp.StatusNotFound)
case errors.Is(err, fs.ErrPermission):
writeStatus(ctx, fasthttp.StatusForbidden)
default:
writeStatus(ctx, fasthttp.StatusInternalServerError)
}
}
func writeStatus(ctx *fasthttp.RequestCtx, status int) {
ctx.SetStatusCode(status)
ctx.SetBodyString(fmt.Sprintf("%d %s", status, fasthttp.StatusMessage(status)))
}

View File

@ -9,7 +9,7 @@ const (
)
var (
rootFiles = []string{"favicon.ico", "manifest.json", "robots.txt"}
rootFiles = []string{"manifest.json", "robots.txt"}
swaggerFiles = []string{
"favicon-16x16.png",
"favicon-32x32.png",

View File

@ -0,0 +1,58 @@
{
"An email has been sent to your address to complete the process": "Es wurde eine E-Mail an Ihre Adresse geschickt, um den Vorgang abzuschließen.",
"Authenticated": "Authentifiziert",
"Cancel": "Abbrechen",
"Contact your administrator to register a device": "Wenden Sie sich an Ihren Administrator, um ein Gerät zu registrieren.",
"Could not obtain user settings": "Benutzereinstellungen konnten nicht abgerufen werden",
"Done": "Erledigt",
"Enter new password": "Neues Passwort eingeben",
"Enter one-time password": "Einmal-Passwort eingeben",
"Failed to register device, the provided link is expired or has already been used": "Gerät konnte nicht registriert werden, der angegebene Link ist abgelaufen oder wurde bereits verwendet",
"Hi": "Hallo",
"Incorrect username or password": "Falscher Benutzername oder falsches Passwort.",
"Loading": "Laden",
"Logout": "Abmelden",
"Lost your device?": "Haben Sie Ihr Gerät verloren?",
"Methods": "Verfahren",
"Need Google Authenticator?": "Benötigen Sie Google Authenticator?",
"New password": "Neues Passwort",
"No verification token provided": "Kein Verifizierungs-Token vorhanden",
"OTP Secret copied to clipboard": "OTP Secret in die Zwischenablage kopiert.",
"OTP URL copied to clipboard": "OTP-URL in die Zwischenablage kopiert.",
"One-Time Password": "One-Time-Passwort",
"Password has been reset": "Das Passwort wurde zurückgesetzt.",
"Password": "Passwort",
"Passwords do not match": "Die Passwörter stimmen nicht überein.",
"Push Notification": "Push-Benachrichtigung",
"Register device": "Gerät registrieren",
"Register your first device by clicking on the link below": "Registrieren Sie Ihr erstes Gerät, indem Sie auf den unten stehenden Link klicken.",
"Remember me": "Angemeldet bleiben",
"Repeat new password": "Neues Passwort wiederholen",
"Reset password": "Passwort zurücksetzen",
"Reset password?": "Passwort zurücksetzen?",
"Reset": "Zurücksetzen",
"Scan QR Code": "QR-Code scannen",
"Secret": "Geheimnis",
"Security Key - WebAuthN": "Sicherheitsschlüssel - WebAuthN",
"Select a Device": "Gerät auswählen",
"Sign in": "Anmelden",
"Sign out": "Abmelden",
"The resource you're attempting to access requires two-factor authentication": "Die Ressource, auf die Sie zuzugreifen versuchen, erfordert eine Zwei-Faktor-Authentifizierung.",
"There was a problem initiating the registration process": "Es gab ein Problem beim Starten des Registrierungsprozesses.",
"There was an issue completing the process. The verification token might have expired": "Es gab ein Problem beim Abschluss des Prozesses. Das Verifizierungs-Token könnte abgelaufen sein.",
"There was an issue initiating the password reset process": "Es gab ein Problem beim beim Starten des Passwortrücksetzprozesses.",
"There was an issue resetting the password": "Es gab ein Problem beim Zurücksetzen des Passworts",
"There was an issue signing out": "Es gab ein Problem bei der Abmeldung",
"Time-based One-Time Password": "Zeitbasiertes One-Time-Passwort",
"Username": "Benutzername",
"You must open the link from the same device and browser that initiated the registration process": "Sie müssen den Link mit demselben Gerät und demselben Browser öffnen, mit dem Sie den Registrierungsprozess gestartet haben.",
"You're being signed out and redirected": "Sie werden abgemeldet und umgeleitet",
"Your supplied password does not meet the password policy requirements": "Ihr angegebenes Passwort entspricht nicht den Anforderungen der Passwortrichtlinie.",
"Use OpenID to verify your identity": "Verwenden Sie OpenID, um Ihre Identität zu überprüfen",
"Access your display name": "Zugriff auf Ihren Anzeigenamen",
"Access your group membership": "Zugriff auf Ihre Gruppenmitgliedschaft",
"Access your email addresses": "Zugriff auf Ihre E-Mail-Adressen",
"Accept": "Annehmen",
"Deny": "Ablehnen",
"The above application is requesting the following permissions": "Die oben genannte Anwendung bittet um die folgenden Berechtigungen"
}

View File

@ -0,0 +1,65 @@
{
"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 - WebAuthN": "Security Key - WebAuthN",
"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.",
"Use OpenID to verify your identity": "Use OpenID to verify your identity",
"Access your display name": "Access your display name",
"Access your group membership": "Access your group membership",
"Access your email addresses": "Access your email addresses",
"Accept": "Accept",
"Deny": "Deny",
"The above application is requesting the following permissions": "The above application is requesting the following permissions",
"The password does not meet the password policy": "The password does not meet the password policy",
"Must have at least one lowercase letter": "Must have at least one lowercase letter",
"Must have at least one UPPERCASE letter": "Must have at least one UPPERCASE letter",
"Must have at least one number": "Must have at least one number",
"Must have at least one special character": "Must have at least one special character",
"Must be at least {{len}} characters in length": "Must be at least {{len}} characters in length",
"Must not be more than {{len}} characters in length": "Must not be more than {{len}} characters in length"
}

View File

@ -0,0 +1,65 @@
{
"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 - WebAuthN": "Llave de Seguridad - WebAuthN",
"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",
"Use OpenID to verify your identity": "Utilizar OpenID para verificar su identidad",
"Access your display name": "Acceso a su nombre",
"Access your group membership": "Acceso a su(s) grupo(s)",
"Access your email addresses": "Acceso a su dirección de correo",
"Accept": "Aceptar",
"Deny": "Denegar",
"The above application is requesting the following permissions": "La aplicación solicita los siguientes permisos",
"The password does not meet the password policy": "La contraseña no cumple con la política de contraseñas",
"Must have at least one lowercase letter": "Debe contener al menos una letra minúscula",
"Must have at least one UPPERCASE letter": "Debe contener al menos una letra MAYUSCULA",
"Must have at least one number": "Debe contener al menos un número",
"Must have at least one special character": "Debe contener al menos un caracter especial",
"Must be at least {{len}} characters in length": "La longitud mínima es de {{len}} caracteres",
"Must not be more than {{len}} characters in length": "La longitud máxima es de {{len}} caracteres"
}

View File

@ -1,10 +1,7 @@
package server
import (
"embed"
"io/fs"
"net"
"net/http"
"os"
"strconv"
"time"
@ -13,7 +10,6 @@ import (
"github.com/fasthttp/router"
"github.com/valyala/fasthttp"
"github.com/valyala/fasthttp/expvarhandler"
"github.com/valyala/fasthttp/fasthttpadaptor"
"github.com/valyala/fasthttp/pprofhandler"
"github.com/authelia/authelia/v4/internal/configuration/schema"
@ -23,9 +19,6 @@ import (
"github.com/authelia/authelia/v4/internal/middlewares"
)
//go:embed public_html
var assets embed.FS
func registerRoutes(configuration schema.Configuration, providers middlewares.Providers) fasthttp.RequestHandler {
autheliaMiddleware := middlewares.AutheliaMiddleware(configuration, providers)
rememberMe := strconv.FormatBool(configuration.Session.RememberMeDuration != schema.RememberMeDisabled)
@ -36,8 +29,8 @@ func registerRoutes(configuration schema.Configuration, providers middlewares.Pr
duoSelfEnrollment = strconv.FormatBool(configuration.DuoAPI.EnableSelfEnrollment)
}
embeddedPath, _ := fs.Sub(assets, "public_html")
embeddedFS := fasthttpadaptor.NewFastHTTPHandler(http.FileServer(http.FS(embeddedPath)))
handlerPublicHTML := newPublicHTMLEmbeddedHandler()
handlerLocales := newLocalesEmbeddedHandler()
https := configuration.Server.TLS.Key != "" && configuration.Server.TLS.Certificate != ""
@ -50,17 +43,22 @@ func registerRoutes(configuration schema.Configuration, providers middlewares.Pr
r.OPTIONS("/", autheliaMiddleware(handleOPTIONS))
for _, f := range rootFiles {
r.GET("/"+f, middlewares.AssetOverrideMiddleware(configuration.Server.AssetPath, embeddedFS))
r.GET("/"+f, handlerPublicHTML)
}
r.GET("/api/", autheliaMiddleware(serveSwaggerHandler))
r.GET("/api/"+apiFile, autheliaMiddleware(serveSwaggerAPIHandler))
for _, file := range swaggerFiles {
r.GET("/api/"+file, embeddedFS)
r.GET("/api/"+file, handlerPublicHTML)
}
r.GET("/static/{filepath:*}", middlewares.AssetOverrideMiddleware(configuration.Server.AssetPath, embeddedFS))
r.GET("/favicon.ico", middlewares.AssetOverrideMiddleware(configuration.Server.AssetPath, 0, handlerPublicHTML))
r.GET("/static/media/logo.png", middlewares.AssetOverrideMiddleware(configuration.Server.AssetPath, 2, handlerPublicHTML))
r.GET("/static/{filepath:*}", handlerPublicHTML)
r.GET("/locales/{language:[a-z]{1,3}}-{variant:[a-z0-9-]+}/{namespace:[a-z]+}.json", middlewares.AssetOverrideMiddleware(configuration.Server.AssetPath, 0, handlerLocales))
r.GET("/locales/{language:[a-z]{1,3}}/{namespace:[a-z]+}.json", middlewares.AssetOverrideMiddleware(configuration.Server.AssetPath, 0, handlerLocales))
r.GET("/api/health", autheliaMiddleware(handlers.HealthGet))
r.GET("/api/state", autheliaMiddleware(handlers.StateGet))

View File

@ -43,7 +43,7 @@ func ServeTemplatedFile(publicDir, file, assetPath, duoSelfEnrollment, rememberM
logoOverride := f
if assetPath != "" {
if _, err := os.Stat(assetPath + logoFile); err == nil {
if _, err := os.Stat(filepath.Join(assetPath, logoFile)); err == nil {
logoOverride = t
}
}

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react";
import React, { useState, useEffect, Suspense } from "react";
import { config as faConfig } from "@fortawesome/fontawesome-svg-core";
import { CssBaseline, ThemeProvider } from "@material-ui/core";
@ -21,6 +21,7 @@ import { getBasePath } from "@utils/BasePath";
import { getDuoSelfEnrollment, getRememberMe, getResetPassword, getTheme } from "@utils/Configuration";
import RegisterOneTimePassword from "@views/DeviceRegistration/RegisterOneTimePassword";
import RegisterWebauthn from "@views/DeviceRegistration/RegisterWebauthn";
import BaseLoadingPage from "@views/LoadingPage/BaseLoadingPage";
import ConsentView from "@views/LoginPortal/ConsentView/ConsentView";
import LoginPortal from "@views/LoginPortal/LoginPortal";
import SignOut from "@views/LoginPortal/SignOut/SignOut";
@ -60,6 +61,7 @@ const App: React.FC = () => {
}, []);
return (
<ThemeProvider theme={theme}>
<Suspense fallback={<BaseLoadingPage message={"Loading"} />}>
<CssBaseline />
<NotificationsContext.Provider value={{ notification, setNotification }}>
<Router basename={getBasePath()}>
@ -84,6 +86,7 @@ const App: React.FC = () => {
</Routes>
</Router>
</NotificationsContext.Provider>
</Suspense>
</ThemeProvider>
);
};

View File

@ -1,33 +1,28 @@
import i18n from "i18next";
import LanguageDetector from "i18next-browser-languagedetector";
import XHR from "i18next-http-backend";
import Backend from "i18next-http-backend";
import { initReactI18next } from "react-i18next";
import langDe from "@i18n/locales/de.json";
import langEn from "@i18n/locales/en.json";
import langEs from "@i18n/locales/es.json";
const resources = {
en: langEn,
es: langEs,
de: langDe,
};
const options = {
order: ["querystring", "navigator"],
lookupQuerystring: "lng",
};
i18n.use(XHR)
i18n.use(Backend)
.use(LanguageDetector)
.use(initReactI18next)
.init({
detection: options,
resources,
ns: [""],
defaultNS: "",
fallbackLng: "en",
detection: {
order: ["querystring", "navigator"],
lookupQuerystring: "lng",
},
backend: {
loadPath: "/locales/{{lng}}/{{ns}}.json",
},
ns: ["portal"],
defaultNS: "portal",
fallbackLng: {
default: ["en"],
},
load: "all",
supportedLngs: ["en", "es", "de"],
lowerCaseLng: true,
nonExplicitSupportedLngs: true,
interpolation: {
escapeValue: false,
},

View File

@ -1,60 +0,0 @@
{
"Portal": {
"An email has been sent to your address to complete the process": "Es wurde eine E-Mail an Ihre Adresse geschickt, um den Vorgang abzuschließen.",
"Authenticated": "Authentifiziert",
"Cancel": "Abbrechen",
"Contact your administrator to register a device": "Wenden Sie sich an Ihren Administrator, um ein Gerät zu registrieren.",
"Could not obtain user settings": "Benutzereinstellungen konnten nicht abgerufen werden",
"Done": "Erledigt",
"Enter new password": "Neues Passwort eingeben",
"Enter one-time password": "Einmal-Passwort eingeben",
"Failed to register device, the provided link is expired or has already been used": "Gerät konnte nicht registriert werden, der angegebene Link ist abgelaufen oder wurde bereits verwendet",
"Hi": "Hallo",
"Incorrect username or password": "Falscher Benutzername oder falsches Passwort.",
"Loading": "Laden",
"Logout": "Abmelden",
"Lost your device?": "Haben Sie Ihr Gerät verloren?",
"Methods": "Verfahren",
"Need Google Authenticator?": "Benötigen Sie Google Authenticator?",
"New password": "Neues Passwort",
"No verification token provided": "Kein Verifizierungs-Token vorhanden",
"OTP Secret copied to clipboard": "OTP Secret in die Zwischenablage kopiert.",
"OTP URL copied to clipboard": "OTP-URL in die Zwischenablage kopiert.",
"One-Time Password": "One-Time-Passwort",
"Password has been reset": "Das Passwort wurde zurückgesetzt.",
"Password": "Passwort",
"Passwords do not match": "Die Passwörter stimmen nicht überein.",
"Push Notification": "Push-Benachrichtigung",
"Register device": "Gerät registrieren",
"Register your first device by clicking on the link below": "Registrieren Sie Ihr erstes Gerät, indem Sie auf den unten stehenden Link klicken.",
"Remember me": "Angemeldet bleiben",
"Repeat new password": "Neues Passwort wiederholen",
"Reset password": "Passwort zurücksetzen",
"Reset password?": "Passwort zurücksetzen?",
"Reset": "Zurücksetzen",
"Scan QR Code": "QR-Code scannen",
"Secret": "Geheimnis",
"Security Key - WebAuthN": "Sicherheitsschlüssel - WebAuthN",
"Select a Device": "Gerät auswählen",
"Sign in": "Anmelden",
"Sign out": "Abmelden",
"The resource you're attempting to access requires two-factor authentication": "Die Ressource, auf die Sie zuzugreifen versuchen, erfordert eine Zwei-Faktor-Authentifizierung.",
"There was a problem initiating the registration process": "Es gab ein Problem beim Starten des Registrierungsprozesses.",
"There was an issue completing the process. The verification token might have expired": "Es gab ein Problem beim Abschluss des Prozesses. Das Verifizierungs-Token könnte abgelaufen sein.",
"There was an issue initiating the password reset process": "Es gab ein Problem beim beim Starten des Passwortrücksetzprozesses.",
"There was an issue resetting the password": "Es gab ein Problem beim Zurücksetzen des Passworts",
"There was an issue signing out": "Es gab ein Problem bei der Abmeldung",
"Time-based One-Time Password": "Zeitbasiertes One-Time-Passwort",
"Username": "Benutzername",
"You must open the link from the same device and browser that initiated the registration process": "Sie müssen den Link mit demselben Gerät und demselben Browser öffnen, mit dem Sie den Registrierungsprozess gestartet haben.",
"You're being signed out and redirected": "Sie werden abgemeldet und umgeleitet",
"Your supplied password does not meet the password policy requirements": "Ihr angegebenes Passwort entspricht nicht den Anforderungen der Passwortrichtlinie.",
"Use OpenID to verify your identity": "Verwenden Sie OpenID, um Ihre Identität zu überprüfen",
"Access your display name": "Zugriff auf Ihren Anzeigenamen",
"Access your group membership": "Zugriff auf Ihre Gruppenmitgliedschaft",
"Access your email addresses": "Zugriff auf Ihre E-Mail-Adressen",
"Accept": "Annehmen",
"Deny": "Ablehnen",
"The above application is requesting the following permissions": "Die oben genannte Anwendung bittet um die folgenden Berechtigungen"
}
}

View File

@ -1,67 +0,0 @@
{
"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 - WebAuthN": "Security Key - WebAuthN",
"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.",
"Use OpenID to verify your identity": "Use OpenID to verify your identity",
"Access your display name": "Access your display name",
"Access your group membership": "Access your group membership",
"Access your email addresses": "Access your email addresses",
"Accept": "Accept",
"Deny": "Deny",
"The above application is requesting the following permissions": "The above application is requesting the following permissions",
"The password does not meet the password policy": "The password does not meet the password policy",
"Must have at least one lowercase letter": "Must have at least one lowercase letter",
"Must have at least one UPPERCASE letter": "Must have at least one UPPERCASE letter",
"Must have at least one number": "Must have at least one number",
"Must have at least one special character": "Must have at least one special character",
"Must be at least {{len}} characters in length": "Must be at least {{len}} characters in length",
"Must not be more than {{len}} characters in length": "Must not be more than {{len}} characters in length"
}
}

View File

@ -1,67 +0,0 @@
{
"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 - WebAuthN": "Llave de Seguridad - WebAuthN",
"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",
"Use OpenID to verify your identity": "Utilizar OpenID para verificar su identidad",
"Access your display name": "Acceso a su nombre",
"Access your group membership": "Acceso a su(s) grupo(s)",
"Access your email addresses": "Acceso a su dirección de correo",
"Accept": "Aceptar",
"Deny": "Denegar",
"The above application is requesting the following permissions": "La aplicación solicita los siguientes permisos",
"The password does not meet the password policy": "La contraseña no cumple con la política de contraseñas",
"Must have at least one lowercase letter": "Debe contener al menos una letra minúscula",
"Must have at least one UPPERCASE letter": "Debe contener al menos una letra MAYUSCULA",
"Must have at least one number": "Debe contener al menos un número",
"Must have at least one special character": "Debe contener al menos un caracter especial",
"Must be at least {{len}} characters in length": "La longitud mínima es de {{len}} caracteres",
"Must not be more than {{len}} characters in length": "La longitud máxima es de {{len}} caracteres"
}
}

View File

@ -27,7 +27,7 @@ const RegisterOneTimePassword = function () {
const { createSuccessNotification, createErrorNotification } = useNotifications();
const [hasErrored, setHasErrored] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const { t: translate } = useTranslation("Portal");
const { t: translate } = useTranslation();
// Get the token from the query param to give it back to the API when requesting
// the secret for OTP.

View File

@ -0,0 +1,22 @@
import React from "react";
import { Grid, Typography, useTheme } from "@material-ui/core";
import ReactLoading from "react-loading";
export interface Props {
message: string;
}
const BaseLoadingPage = function (props: Props) {
const theme = useTheme();
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>{props.message}...</Typography>
</Grid>
</Grid>
);
};
export default BaseLoadingPage;

View File

@ -1,20 +1,12 @@
import React from "react";
import { useTheme, Typography, Grid } from "@material-ui/core";
import { useTranslation } from "react-i18next";
import ReactLoading from "react-loading";
import BaseLoadingPage from "@views/LoadingPage/BaseLoadingPage";
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>{translate("Loading")}...</Typography>
</Grid>
</Grid>
);
const { t: translate } = useTranslation();
return <BaseLoadingPage message={translate("Loading")} />;
};
export default LoadingPage;

View File

@ -7,7 +7,7 @@ import SuccessIcon from "@components/SuccessIcon";
const Authenticated = function () {
const classes = useStyles();
const { t: translate } = useTranslation("Portal");
const { t: translate } = useTranslation();
return (
<div id="authenticated-stage">
<div className={classes.iconContainer}>

View File

@ -15,7 +15,7 @@ export interface Props {
const AuthenticatedView = function (props: Props) {
const style = useStyles();
const navigate = useNavigate();
const { t: translate } = useTranslation("Portal");
const { t: translate } = useTranslation();
const handleLogoutClick = () => {
navigate(SignOutRoute);

View File

@ -46,7 +46,7 @@ const ConsentView = function (props: Props) {
const redirect = useRedirector();
const { createErrorNotification, resetNotification } = useNotifications();
const [resp, fetch, , err] = useRequestedScopes();
const { t: translate } = useTranslation("Portal");
const { t: translate } = useTranslation();
useEffect(() => {
if (err) {

View File

@ -38,7 +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");
const { t: translate } = useTranslation();
useEffect(() => {
const timeout = setTimeout(() => usernameRef.current.focus(), 10);
return () => clearTimeout(timeout);

View File

@ -28,7 +28,7 @@ export interface Props {
const DefaultMethodContainer = function (props: Props) {
const style = useStyles();
const { t: translate } = useTranslation("Portal");
const { t: translate } = useTranslation();
const registerMessage = props.registered
? props.title === "Push Notification"
? ""
@ -97,7 +97,7 @@ interface NotRegisteredContainerProps {
}
function NotRegisteredContainer(props: NotRegisteredContainerProps) {
const { t: translate } = useTranslation("Portal");
const { t: translate } = useTranslation();
const theme = useTheme();
return (
<Fragment>

View File

@ -29,7 +29,7 @@ export interface Props {
const MethodSelectionDialog = function (props: Props) {
const style = useStyles();
const theme = useTheme();
const { t: translate } = useTranslation("Portal");
const { t: translate } = useTranslation();
const pieChartIcon = (
<TimerIcon width={24} height={24} period={15} color={theme.palette.primary.main} backgroundColor={"white"} />

View File

@ -33,7 +33,7 @@ const OneTimePasswordMethod = function (props: Props) {
props.authenticationLevel === AuthenticationLevel.TwoFactor ? State.Success : State.Idle,
);
const redirectionURL = useRedirectionURL();
const { t: translate } = useTranslation("Portal");
const { t: translate } = useTranslation();
const { onSignInSuccess, onSignInError } = props;
const onSignInErrorCallback = useRef(onSignInError).current;

View File

@ -41,7 +41,7 @@ const SecondFactorForm = function (props: Props) {
const { createInfoNotification, createErrorNotification } = useNotifications();
const [registrationInProgress, setRegistrationInProgress] = useState(false);
const [webauthnSupported, setWebauthnSupported] = useState(false);
const { t: translate } = useTranslation("Portal");
const { t: translate } = useTranslation();
useEffect(() => {
setWebauthnSupported(isWebauthnSupported());

View File

@ -22,7 +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 { t: translate } = useTranslation();
const doSignOut = useCallback(async () => {
try {

View File

@ -16,7 +16,7 @@ const ResetPasswordStep1 = function () {
const [error, setError] = useState(false);
const { createInfoNotification, createErrorNotification } = useNotifications();
const navigate = useNavigate();
const { t: translate } = useTranslation("Portal");
const { t: translate } = useTranslation();
const doInitiateResetPasswordProcess = async () => {
if (username === "") {

View File

@ -25,7 +25,7 @@ const ResetPasswordStep2 = function () {
const [errorPassword1, setErrorPassword1] = useState(false);
const [errorPassword2, setErrorPassword2] = useState(false);
const { createSuccessNotification, createErrorNotification } = useNotifications();
const { t: translate } = useTranslation("Portal");
const { t: translate } = useTranslation();
const navigate = useNavigate();
const [showPassword, setShowPassword] = useState(false);