diff --git a/backend/routes/auth.js b/backend/routes/auth.js
index 7cc1a80c..93ffb2de 100644
--- a/backend/routes/auth.js
+++ b/backend/routes/auth.js
@@ -9,7 +9,7 @@ const loginLimiter = rateLimit({
max: Number(process.env.ZT_TRIES_TO_BAN) || 50, // limit each IP to 50 requests per windowMs
message: {
status: 429,
- error: "Too many login attempts, please try again in 15 minutes.",
+ error: "tooManyAttempts",
},
});
diff --git a/backend/services/auth.js b/backend/services/auth.js
index c7e1d7cf..43832a5e 100644
--- a/backend/services/auth.js
+++ b/backend/services/auth.js
@@ -8,12 +8,12 @@ export async function authorize(username, password, callback) {
throw err;
}
const user = users.find({ username: username });
- if (!user.value()) return callback(new Error("Invalid username or password")); // If return "user not found" someone can do a user listing
+ if (!user.value()) return callback(new Error("logInFailed")); // If return "user not found" someone can do a user listing
const verified = await verifyHash(password, user.value()["password_hash"]);
if (verified) {
return callback(null, user.value());
} else {
- return callback(new Error("Invalid username or password"));
+ return callback(new Error("logInFailed"));
}
}
diff --git a/frontend/package.json b/frontend/package.json
index ff003128..e55d3799 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -11,11 +11,15 @@
"codemirror": "^5.62.3",
"date-fns": "^2.29.2",
"history": "^5.3.0",
+ "i18next": "^23.5.1",
+ "i18next-browser-languagedetector": "^7.1.0",
+ "i18next-http-backend": "^2.2.2",
"ipaddr.js": "^2.0.1",
"lodash": "^4.17.21",
"react": "^17.0.2",
"react-data-table-component": "^6.11.8",
"react-dom": "^17.0.2",
+ "react-i18next": "^13.3.0",
"react-is": "^17.0.2",
"react-router-dom": "^5.2.0",
"react-use": "^17.4.0",
diff --git a/frontend/public/locales/en/common.json b/frontend/public/locales/en/common.json
new file mode 100644
index 00000000..3dc09054
--- /dev/null
+++ b/frontend/public/locales/en/common.json
@@ -0,0 +1,67 @@
+{
+ "flowRules": "Flow Rules",
+ "createNetwork": "Create A Network",
+ "createOneNetwork": "Please create at least one network",
+ "controllerNetworks": "Controller networks",
+ "network_one": "Network",
+ "network_other": "Networks",
+ "controllerAddress": "Network controller address",
+ "loginToContinue": "Please, Log In to continue",
+ "zerouiDesc": "ZeroUI - ZeroTier Controller Web UI - is a web user interface for a self-hosted ZeroTier network controller.",
+ "logIn": "Log In",
+ "logInToken": "Token Log In",
+ "cancel": "Cancel",
+ "management": "Management",
+ "deleteNetwork": "Delete Network",
+ "deleteAlert": "This action cannot be undone.",
+ "deleteNetworkConfirm": "Are you sure you want to delete this network?",
+ "deleteMemberConfirm": "Are you sure you want to delete this member?",
+ "delete": "Delete",
+ "logOut": "Log out",
+ "advancedFeature": "ADVANCED FEATURE",
+ "noDevices": "No devices have joined this network. Use the app on your devices to join",
+ "member_one": "Member",
+ "member_other": "Members",
+ "addMemberManually": "Manually Add Member",
+ "name": "Name",
+ "description": "Description",
+ "allowBridging": "Allow Ethernet Bridging",
+ "noAutoIP": "Do Not Auto-Assign IPs",
+ "capabilities": "Capabilities",
+ "noCapDef": "No capabilities defined",
+ "tags": "Tags",
+ "noTagDef": "No tags defined",
+ "authorized": "Authorized",
+ "address": "Address",
+ "managedIPs": "Managed IPs",
+ "lastSeen": "Last seen",
+ "version": "Version",
+ "physIp": "Physical IP",
+ "latency": "Latency",
+ "settings": "Settings",
+ "generalSettings": "General settings",
+ "networkId": "Network ID",
+ "accessControl": "Access control",
+ "public": "Public",
+ "private": "Private",
+ "managedRoutes": "Managed routes",
+ "addRoute": "Add route",
+ "target": "Target",
+ "via": "Via",
+ "start": "Start",
+ "end": "End",
+ "ipv4AutoAssign": "IPv4 Auto-Assign",
+ "autoAssignPool": "IPv4 Auto-Assign",
+ "addIPv4Pool": "Add IPv4 Pool",
+ "multicastLimit": "Multicast Recipient Limit",
+ "enableBroadcast": "Enable Broadcast",
+ "logInFailed": "Invalid username or password",
+ "tooManyAttempts": "Too many login attempts, please try again in 15 minutes.",
+ "language": "Language",
+ "notAuthorized": "You are not authorized. Please Log In.",
+ "saveChanges": "Save changes",
+ "optional": "Optional",
+ "destination": "Destination",
+ "username": "Username",
+ "password": "Password"
+}
diff --git a/frontend/public/locales/es-ES/common.json b/frontend/public/locales/es-ES/common.json
new file mode 100644
index 00000000..f314e3c4
--- /dev/null
+++ b/frontend/public/locales/es-ES/common.json
@@ -0,0 +1,67 @@
+{
+ "flowRules": "Reglas de flujo",
+ "createNetwork": "Crear una red",
+ "createOneNetwork": "Por favor, crea al menos una red",
+ "controllerNetworks": "Controlador de redes",
+ "network_one": "Red",
+ "network_other": "Redes",
+ "controllerAddress": "Dirección del controlador",
+ "loginToContinue": "Por favor, inicia sesión para continuar",
+ "zerouiDesc": "ZeroUI - ZeroTier Controller Web UI - es una interfaz de usuario web para un controlador de red ZeroTier self-hosted.",
+ "logIn": "Iniciar sesión",
+ "logInToken": "Iniciar sesión con token",
+ "cancel": "Cancelar",
+ "management": "Gestión",
+ "deleteNetwork": "Borrar red",
+ "deleteAlert": "Esta acción no puede ser revertida.",
+ "deleteNetworkConfirm": "¿Seguro que deseas borrar esta red?",
+ "deleteMemberConfirm": "¿Seguro que deseas borrar este usuario?",
+ "delete": "Borrar",
+ "logOut": "Cerrar sesión",
+ "advancedFeature": "CARACTERÍSTICA AVANZADA",
+ "noDevices": "Ningún dispositivo se ha unido a esta red. Utilice la aplicación en sus dispositivos para unirse",
+ "member_one": "Miembro",
+ "member_other": "Miembros",
+ "addMemberManually": "Añadir miembro manualmente",
+ "name": "Nombre",
+ "description": "Descripción",
+ "allowBridging": "Permitir puente Ethernet",
+ "noAutoIP": "No autoasignar IPs",
+ "capabilities": "Permisos",
+ "noCapDef": "No hay permisos definidos",
+ "tags": "Etiquetas",
+ "noTagDef": "No hay etiquetas definidas",
+ "authorized": "Autorizado",
+ "address": "Dirección",
+ "managedIPs": "IPs asignadas",
+ "lastSeen": "Visto por última vez",
+ "version": "Versión",
+ "physIp": "IP pública",
+ "latency": "Latencia",
+ "settings": "Ajustes",
+ "generalSettings": "Ajustes generales",
+ "networkId": "ID de red",
+ "accessControl": "Control de acceso",
+ "public": "Público",
+ "private": "Privado",
+ "managedRoutes": "Rutas gestionadas",
+ "addRoute": "Añadir ruta",
+ "target": "Objetivo",
+ "via": "Vía",
+ "start": "Inicio",
+ "end": "Final",
+ "autoAssignPool": "Rango de IPv4 autoasignables",
+ "ipv4AutoAssign": "Rangos de IPv4 automáticos",
+ "addIPv4Pool": "Añadir rango IPv4",
+ "multicastLimit": "Límite de destinatarios multicast",
+ "enableBroadcast": "Habilitar broadcast",
+ "logInFailed": "Nombre de usuario o contraseña incorrecto",
+ "tooManyAttempts": "Demasiados intentos de inicio de sesión. Vuelvee a intentarlo en 15 minutos",
+ "language": "Idioma",
+ "notAuthorized": "No estás autorizado. Por favor, inicia sesión.",
+ "saveChanges": "Guardar cambios",
+ "optional": "Opcional",
+ "destination": "Destino",
+ "username": "Nombre de usuario",
+ "password": "Contraseña"
+}
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index 04c9dc19..9142994f 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -8,6 +8,7 @@ import Bar from "./components/Bar";
import Home from "./routes/Home";
import NotFound from "./routes/NotFound";
import Network from "./routes/Network/Network";
+import Settings from "./routes/Settings";
function App() {
return (
@@ -17,6 +18,7 @@ function App() {
+
diff --git a/frontend/src/components/Bar/Bar.jsx b/frontend/src/components/Bar/Bar.jsx
index 2b91eda4..e27b56c9 100644
--- a/frontend/src/components/Bar/Bar.jsx
+++ b/frontend/src/components/Bar/Bar.jsx
@@ -19,6 +19,8 @@ import MenuIcon from "@material-ui/icons/Menu";
import LogIn from "components/LogIn";
+import { useTranslation } from "react-i18next";
+
function Bar() {
const [loggedIn, setLoggedIn] = useLocalStorage("loggedIn", false);
const [disabledAuth] = useLocalStorage("disableAuth", false);
@@ -41,16 +43,18 @@ function Bar() {
history.go(0);
};
+ const { t, i18n } = useTranslation();
+
const menuItems = [
// TODO: add settings page
- // {
- // name: "Settings",
- // to: "/settings",
- // },
+ {
+ name: t("settings"),
+ to: "/settings",
+ },
...(!disabledAuth
? [
{
- name: "Log out",
+ name: t("logOut"),
divide: true,
onClick: onLogOutClick,
},
@@ -115,7 +119,6 @@ function Bar() {
key={index}
onClick={() => {
closeMenu();
-
menuItem.onClick();
}}
>
diff --git a/frontend/src/components/HomeLoggedIn/HomeLoggedIn.jsx b/frontend/src/components/HomeLoggedIn/HomeLoggedIn.jsx
index ff45f768..974a18b2 100644
--- a/frontend/src/components/HomeLoggedIn/HomeLoggedIn.jsx
+++ b/frontend/src/components/HomeLoggedIn/HomeLoggedIn.jsx
@@ -9,6 +9,8 @@ import NetworkButton from "./components/NetworkButton";
import API from "utils/API";
import { generateNetworkConfig } from "utils/NetworkConfig";
+import { useTranslation } from "react-i18next";
+
function HomeLoggedIn() {
const [networks, setNetworks] = useState([]);
@@ -30,6 +32,8 @@ function HomeLoggedIn() {
fetchData();
}, []);
+ const { t, i18n } = useTranslation();
+
return (
- Controller networks
- {networks[0] && "Network controller address"}
+ {t("controllerNetworks")}
+ {networks[0] && t("controllerAddress")}
{networks[0] && networks[0]["id"].slice(0, 10)}
- Networks
+ {t("network", { count: networks.length })}
{networks[0] ? (
networks.map((network) => (
@@ -59,7 +63,7 @@ function HomeLoggedIn() {
))
) : (
- Please create at least one network
+ {t("createOneNetwork")}
)}
diff --git a/frontend/src/components/HomeLoggedOut/HomeLoggedOut.jsx b/frontend/src/components/HomeLoggedOut/HomeLoggedOut.jsx
index 0de5572d..7ec32181 100644
--- a/frontend/src/components/HomeLoggedOut/HomeLoggedOut.jsx
+++ b/frontend/src/components/HomeLoggedOut/HomeLoggedOut.jsx
@@ -3,6 +3,8 @@ import { Grid, Typography } from "@material-ui/core";
import { useLocalStorage } from "react-use";
import { useHistory } from "react-router-dom";
+import { useTranslation } from "react-i18next";
+
import axios from "axios";
function HomeLoggedOut() {
@@ -29,6 +31,8 @@ function HomeLoggedOut() {
fetchData();
}, [history, setDisableAuth, setLoggedIn, setToken]);
+ const { t, i18n } = useTranslation();
+
return (
-
- ZeroUI - ZeroTier Controller Web UI - is a web user interface for a
- self-hosted ZeroTier network controller.
-
+ {t("zerouiDesc")}
- Please Log In to continue
+ {t("loginToContinue")}
diff --git a/frontend/src/components/LogIn/components/LogInToken/LogInToken.jsx b/frontend/src/components/LogIn/components/LogInToken/LogInToken.jsx
index a4140755..b39b4156 100644
--- a/frontend/src/components/LogIn/components/LogInToken/LogInToken.jsx
+++ b/frontend/src/components/LogIn/components/LogInToken/LogInToken.jsx
@@ -12,6 +12,8 @@ import {
DialogTitle,
} from "@material-ui/core";
+import { useTranslation } from "react-i18next";
+
function LogInToken() {
const [open, setOpen] = useState(false);
const [errorText, setErrorText] = useState("");
@@ -41,6 +43,8 @@ function LogInToken() {
}
};
+ const { t, i18n } = useTranslation();
+
const LogIn = () => {
if (token.length !== 32) {
setErrorText("Token length error");
@@ -55,12 +59,12 @@ function LogInToken() {
return (
diff --git a/frontend/src/components/LogIn/components/LogInUser/LogInUser.jsx b/frontend/src/components/LogIn/components/LogInUser/LogInUser.jsx
index 0658d3c3..bb14cb1f 100644
--- a/frontend/src/components/LogIn/components/LogInUser/LogInUser.jsx
+++ b/frontend/src/components/LogIn/components/LogInUser/LogInUser.jsx
@@ -13,6 +13,8 @@ import {
import axios from "axios";
+import { useTranslation } from "react-i18next";
+
function LogInUser() {
const [open, setOpen] = useState(false);
const [snackbarOpen, setSnackbarOpen] = useState(false);
@@ -72,13 +74,15 @@ function LogInUser() {
});
};
+ const { t, i18n } = useTranslation();
+
return (
<>
@@ -117,7 +121,7 @@ function LogInUser() {
vertical: "top",
horizontal: "center",
}}
- message={error}
+ message={t(error)}
/>
>
);
diff --git a/frontend/src/components/NetworkManagement/NetworkManagement.jsx b/frontend/src/components/NetworkManagement/NetworkManagement.jsx
index fab98cce..ad4a0a84 100644
--- a/frontend/src/components/NetworkManagement/NetworkManagement.jsx
+++ b/frontend/src/components/NetworkManagement/NetworkManagement.jsx
@@ -18,6 +18,8 @@ import DeleteIcon from "@material-ui/icons/Delete";
import API from "utils/API";
+import { useTranslation } from "react-i18next";
+
function NetworkManagement() {
const { nwid } = useParams();
const history = useHistory();
@@ -42,10 +44,12 @@ function NetworkManagement() {
history.go(0);
};
+ const { t, i18n } = useTranslation();
+
return (
}>
- Management
+ {t("management")}
}
onClick={handleClickOpen}
>
- Delete Network
+ {t("deleteNetwork")}
diff --git a/frontend/src/components/NetworkMembers/NetworkMembers.jsx b/frontend/src/components/NetworkMembers/NetworkMembers.jsx
index b6f843c8..faedfb50 100644
--- a/frontend/src/components/NetworkMembers/NetworkMembers.jsx
+++ b/frontend/src/components/NetworkMembers/NetworkMembers.jsx
@@ -21,6 +21,8 @@ import ManagedIP from "./components/ManagedIP";
import MemberName from "./components/MemberName";
import MemberSettings from "./components/MemberSettings";
+import { useTranslation } from "react-i18next";
+
function NetworkMembers({ network }) {
const { nwid } = useParams();
const [members, setMembers] = useState([]);
@@ -46,6 +48,8 @@ function NetworkMembers({ network }) {
console.log("Action:", req);
};
+ const { t, i18n } = useTranslation();
+
const handleChange =
(member, key1, key2 = null, mode = "text", id = null) =>
(event) => {
@@ -67,7 +71,7 @@ function NetworkMembers({ network }) {
const columns = [
{
id: "auth",
- name: "Authorized",
+ name: t("authorized"),
minWidth: "80px",
cell: (row) => (
(
{row.config.address}
@@ -87,19 +91,19 @@ function NetworkMembers({ network }) {
},
{
id: "name",
- name: "Name / Description",
+ name: t("name") + "/" + t("description"),
minWidth: "250px",
cell: (row) => ,
},
{
id: "ips",
- name: "Managed IPs",
+ name: t("managedIPs"),
minWidth: "220px",
cell: (row) => ,
},
{
- id: "status",
- name: "Last Seen",
+ id: "lastSeen",
+ name: t("lastSeen"),
minWidth: "100px",
cell: (row) =>
row.online === 1 ? (
@@ -121,7 +125,7 @@ function NetworkMembers({ network }) {
},
{
id: "physicalip",
- name: "Version / Physical IP / Latency",
+ name: t("version") + " / " + t("physIp") + " / " + t("latency"),
minWidth: "220px",
cell: (row) =>
row.online === 1 ? (
@@ -143,7 +147,7 @@ function NetworkMembers({ network }) {
},
{
id: "delete",
- name: "",
+ name: t("settings"),
minWidth: "50px",
right: true,
cell: (row) => (
@@ -162,7 +166,7 @@ function NetworkMembers({ network }) {
return (
}>
- Members
+ {t("member", { count: members.length })}
@@ -188,8 +192,7 @@ function NetworkMembers({ network }) {
}}
>
- No devices have joined this network. Use the app on your
- devices to join {nwid}.
+ {t("noDevices")} {nwid}.
)}
diff --git a/frontend/src/components/NetworkMembers/components/AddMember/AddMember.jsx b/frontend/src/components/NetworkMembers/components/AddMember/AddMember.jsx
index af256d63..56c71ffc 100644
--- a/frontend/src/components/NetworkMembers/components/AddMember/AddMember.jsx
+++ b/frontend/src/components/NetworkMembers/components/AddMember/AddMember.jsx
@@ -5,6 +5,8 @@ import AddIcon from "@material-ui/icons/Add";
import API from "utils/API";
+import { useTranslation } from "react-i18next";
+
function AddMember({ nwid, callback }) {
const [member, setMember] = useState("");
@@ -24,9 +26,11 @@ function AddMember({ nwid, callback }) {
setMember("");
};
+ const { t, i18n } = useTranslation();
+
return (
<>
- Manually Add Member
+ {t("addMemberManually")}
{
@@ -37,18 +39,16 @@ function DeleteMember({ nwid, mid, callback }) {
diff --git a/frontend/src/components/NetworkMembers/components/MemberName/MemberName.jsx b/frontend/src/components/NetworkMembers/components/MemberName/MemberName.jsx
index 65032cc0..ea7f8e9d 100644
--- a/frontend/src/components/NetworkMembers/components/MemberName/MemberName.jsx
+++ b/frontend/src/components/NetworkMembers/components/MemberName/MemberName.jsx
@@ -1,12 +1,14 @@
import { Grid, TextField } from "@material-ui/core";
+import { useTranslation } from "react-i18next";
function MemberName({ member, handleChange }) {
+ const { t, i18n } = useTranslation();
return (
{
@@ -30,7 +33,9 @@ function MemberSettings({ member, network, handleChange }) {
}>
+
+
,
document.getElementById("root")
);
diff --git a/frontend/src/routes/Network/Network.jsx b/frontend/src/routes/Network/Network.jsx
index 272ba2ad..0a0a76a4 100644
--- a/frontend/src/routes/Network/Network.jsx
+++ b/frontend/src/routes/Network/Network.jsx
@@ -11,7 +11,10 @@ import { useLocalStorage } from "react-use";
import API from "utils/API";
import useStyles from "./Network.styles";
+import { useTranslation } from "react-i18next";
+
function Network() {
+ const { t, i18n } = useTranslation();
const { nwid } = useParams();
const [loggedIn] = useLocalStorage("loggedIn", false);
const [network, setNetwork] = useState({});
@@ -42,7 +45,7 @@ function Network() {
- Networks
+ {t("network", { count: 2 })}
@@ -73,9 +76,7 @@ function Network() {
}}
>
-
- You are not authorized. Please Log In
-
+ {t("notAuthorized")}
);
diff --git a/frontend/src/routes/Settings/Settings.jsx b/frontend/src/routes/Settings/Settings.jsx
new file mode 100644
index 00000000..175edd8c
--- /dev/null
+++ b/frontend/src/routes/Settings/Settings.jsx
@@ -0,0 +1,52 @@
+import { Grid, Link, Typography } from "@material-ui/core";
+import ArrowBackIcon from "@material-ui/icons/ArrowBack";
+import SettingsComponent from "components/Settings";
+
+import { Link as RouterLink } from "react-router-dom";
+import { useLocalStorage } from "react-use";
+
+import useStyles from "./Settings.styles";
+
+import { useTranslation } from "react-i18next";
+
+function Settings() {
+ const { t, i18n } = useTranslation();
+ const [loggedIn] = useLocalStorage("loggedIn", false);
+
+ const classes = useStyles();
+
+ if (loggedIn) {
+ return (
+ <>
+
+
+
+ {t("settings")}
+
+
+
+
+
+ >
+ );
+ } else {
+ return (
+
+
+ {t("notAuthorized")}
+
+
+ );
+ }
+}
+
+export default Settings;
diff --git a/frontend/src/routes/Settings/Settings.styles.jsx b/frontend/src/routes/Settings/Settings.styles.jsx
new file mode 100644
index 00000000..ac884a75
--- /dev/null
+++ b/frontend/src/routes/Settings/Settings.styles.jsx
@@ -0,0 +1,16 @@
+import { makeStyles } from "@material-ui/core/styles";
+
+const useStyles = makeStyles((theme) => ({
+ backIcon: {
+ fontSize: 12,
+ },
+ container: {
+ margin: "3%",
+ },
+ breadcrumbs: {
+ paddingTop: "2%",
+ paddingLeft: "2%",
+ },
+}));
+
+export default useStyles;
diff --git a/frontend/src/routes/Settings/index.jsx b/frontend/src/routes/Settings/index.jsx
new file mode 100644
index 00000000..41d66223
--- /dev/null
+++ b/frontend/src/routes/Settings/index.jsx
@@ -0,0 +1 @@
+export { default } from "./Settings";
diff --git a/yarn.lock b/yarn.lock
index 4ca548d9..52a62fa1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -265,6 +265,15 @@ __metadata:
languageName: node
linkType: hard
+"@babel/runtime@npm:^7.19.4, @babel/runtime@npm:^7.22.5":
+ version: 7.23.2
+ resolution: "@babel/runtime@npm:7.23.2"
+ dependencies:
+ regenerator-runtime: "npm:^0.14.0"
+ checksum: abdcbdd590c7e31762e1bdab94dd466823c8bcedd3ff2fde85eeb94dac7cccaef151ac37c428bda7018ededd27c9a82b4dfeb621f978ad934232475a902f8e3a
+ languageName: node
+ linkType: hard
+
"@babel/template@npm:^7.22.15":
version: 7.22.15
resolution: "@babel/template@npm:7.22.15"
@@ -2992,6 +3001,15 @@ __metadata:
languageName: node
linkType: hard
+"cross-fetch@npm:3.1.6":
+ version: 3.1.6
+ resolution: "cross-fetch@npm:3.1.6"
+ dependencies:
+ node-fetch: "npm:^2.6.11"
+ checksum: e08325b813da37f2d5312b3e630af992c35681c1737707b029e8ef1c48ea034bda8b960000fc8bee6e0485e133347198aa6ecccadb530b06c47472f6c76bc27b
+ languageName: node
+ linkType: hard
+
"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.1, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3":
version: 7.0.3
resolution: "cross-spawn@npm:7.0.3"
@@ -4344,11 +4362,15 @@ __metadata:
eslint-plugin-react-hooks: "npm:^4.6.0"
eslint-plugin-react-refresh: "npm:^0.4.3"
history: "npm:^5.3.0"
+ i18next: "npm:^23.5.1"
+ i18next-browser-languagedetector: "npm:^7.1.0"
+ i18next-http-backend: "npm:^2.2.2"
ipaddr.js: "npm:^2.0.1"
lodash: "npm:^4.17.21"
react: "npm:^17.0.2"
react-data-table-component: "npm:^6.11.8"
react-dom: "npm:^17.0.2"
+ react-i18next: "npm:^13.3.0"
react-is: "npm:^17.0.2"
react-router-dom: "npm:^5.2.0"
react-use: "npm:^17.4.0"
@@ -4888,6 +4910,15 @@ __metadata:
languageName: node
linkType: hard
+"html-parse-stringify@npm:^3.0.1":
+ version: 3.0.1
+ resolution: "html-parse-stringify@npm:3.0.1"
+ dependencies:
+ void-elements: "npm:3.1.0"
+ checksum: 8743b76cc50e46d1956c1ad879d18eb9613b0d2d81e24686d633f9f69bb26b84676f64a926973de793cca479997017a63219278476d617b6c42d68246d7c07fe
+ languageName: node
+ linkType: hard
+
"http-cache-semantics@npm:^4.1.1":
version: 4.1.1
resolution: "http-cache-semantics@npm:4.1.1"
@@ -4968,6 +4999,33 @@ __metadata:
languageName: node
linkType: hard
+"i18next-browser-languagedetector@npm:^7.1.0":
+ version: 7.1.0
+ resolution: "i18next-browser-languagedetector@npm:7.1.0"
+ dependencies:
+ "@babel/runtime": "npm:^7.19.4"
+ checksum: 3b06c8a5df09092cffc0b6637b542bb572e8a25dcba97d0d8a5e5dd7539b90bf00000f3a279654693f4b5908c5fc4d1d4f3766dfb461dacab46be3d071266384
+ languageName: node
+ linkType: hard
+
+"i18next-http-backend@npm:^2.2.2":
+ version: 2.2.2
+ resolution: "i18next-http-backend@npm:2.2.2"
+ dependencies:
+ cross-fetch: "npm:3.1.6"
+ checksum: dbf09f2f309cb6070e691d0d382ccff3e94d2cfa9f30315dc4c03faa5d7d1f3b408d48c46c766b7e07527ec10c0542fde19240845905314ce0134ac10d6a6adb
+ languageName: node
+ linkType: hard
+
+"i18next@npm:^23.5.1":
+ version: 23.5.1
+ resolution: "i18next@npm:23.5.1"
+ dependencies:
+ "@babel/runtime": "npm:^7.22.5"
+ checksum: 38e62d582b0f67eb2eee4f079c9cd512246496f2fb970f50a0be26c7c5e6ac5e772de9763ac1943919ecd816b2c0375f4b2071c67b1485a6a980c4d37348408f
+ languageName: node
+ linkType: hard
+
"iconv-lite@npm:0.4.24, iconv-lite@npm:^0.4.24":
version: 0.4.24
resolution: "iconv-lite@npm:0.4.24"
@@ -6532,6 +6590,20 @@ __metadata:
languageName: node
linkType: hard
+"node-fetch@npm:^2.6.11":
+ version: 2.7.0
+ resolution: "node-fetch@npm:2.7.0"
+ dependencies:
+ whatwg-url: "npm:^5.0.0"
+ peerDependencies:
+ encoding: ^0.1.0
+ peerDependenciesMeta:
+ encoding:
+ optional: true
+ checksum: b24f8a3dc937f388192e59bcf9d0857d7b6940a2496f328381641cb616efccc9866e89ec43f2ec956bbd6c3d3ee05524ce77fe7b29ccd34692b3a16f237d6676
+ languageName: node
+ linkType: hard
+
"node-gyp@npm:latest":
version: 9.4.0
resolution: "node-gyp@npm:9.4.0"
@@ -7288,6 +7360,24 @@ __metadata:
languageName: node
linkType: hard
+"react-i18next@npm:^13.3.0":
+ version: 13.3.0
+ resolution: "react-i18next@npm:13.3.0"
+ dependencies:
+ "@babel/runtime": "npm:^7.22.5"
+ html-parse-stringify: "npm:^3.0.1"
+ peerDependencies:
+ i18next: ">= 23.2.3"
+ react: ">= 16.8.0"
+ peerDependenciesMeta:
+ react-dom:
+ optional: true
+ react-native:
+ optional: true
+ checksum: 2ef46245ba1ba9fca8c43dbe1bcab4ac63ca68e67de7159cb5f93cd7fd10407599bcdf1999f6567091987be7b9aaba77cdb515a70d5ee2b935b985f4c40d6d9d
+ languageName: node
+ linkType: hard
+
"react-is@npm:^16.13.1, react-is@npm:^16.6.0, react-is@npm:^16.7.0":
version: 16.13.1
resolution: "react-is@npm:16.13.1"
@@ -8654,6 +8744,13 @@ __metadata:
languageName: node
linkType: hard
+"tr46@npm:~0.0.3":
+ version: 0.0.3
+ resolution: "tr46@npm:0.0.3"
+ checksum: 8f1f5aa6cb232f9e1bdc86f485f916b7aa38caee8a778b378ffec0b70d9307873f253f5cbadbe2955ece2ac5c83d0dc14a77513166ccd0a0c7fe197e21396695
+ languageName: node
+ linkType: hard
+
"tree-kill@npm:^1.2.2":
version: 1.2.2
resolution: "tree-kill@npm:1.2.2"
@@ -9046,6 +9143,13 @@ __metadata:
languageName: node
linkType: hard
+"void-elements@npm:3.1.0":
+ version: 3.1.0
+ resolution: "void-elements@npm:3.1.0"
+ checksum: 0390f818107fa8fce55bb0a5c3f661056001c1d5a2a48c28d582d4d847347c2ab5b7f8272314cac58acf62345126b6b09bea623a185935f6b1c3bbce0dfd7f7f
+ languageName: node
+ linkType: hard
+
"wcwidth@npm:^1.0.1":
version: 1.0.1
resolution: "wcwidth@npm:1.0.1"
@@ -9055,6 +9159,23 @@ __metadata:
languageName: node
linkType: hard
+"webidl-conversions@npm:^3.0.0":
+ version: 3.0.1
+ resolution: "webidl-conversions@npm:3.0.1"
+ checksum: b65b9f8d6854572a84a5c69615152b63371395f0c5dcd6729c45789052296df54314db2bc3e977df41705eacb8bc79c247cee139a63fa695192f95816ed528ad
+ languageName: node
+ linkType: hard
+
+"whatwg-url@npm:^5.0.0":
+ version: 5.0.0
+ resolution: "whatwg-url@npm:5.0.0"
+ dependencies:
+ tr46: "npm:~0.0.3"
+ webidl-conversions: "npm:^3.0.0"
+ checksum: f95adbc1e80820828b45cc671d97da7cd5e4ef9deb426c31bcd5ab00dc7103042291613b3ef3caec0a2335ed09e0d5ed026c940755dbb6d404e2b27f940fdf07
+ languageName: node
+ linkType: hard
+
"which-boxed-primitive@npm:^1.0.2":
version: 1.0.2
resolution: "which-boxed-primitive@npm:1.0.2"