Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: i18n #182

Merged
merged 8 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/routes/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: "tooManyAttemps",
aruznieto marked this conversation as resolved.
Show resolved Hide resolved
},
});

Expand Down
4 changes: 2 additions & 2 deletions backend/services/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
}
}

Expand Down
4 changes: 4 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
61 changes: 61 additions & 0 deletions frontend/public/locales/en/common.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
{
"flowRules": "Flow Rules",
"createNetwork": "Create A Network",
"createOneNetwork": "Please create at least one network",
"controllerNetworks": "Controller networks",
"network_one": "Network",
dec0dOS marked this conversation as resolved.
Show resolved Hide resolved
"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",
"addMemberMan": "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",
"ips": "Managed IPs",
aruznieto marked this conversation as resolved.
Show resolved Hide resolved
"status": "Last seen",
aruznieto marked this conversation as resolved.
Show resolved Hide resolved
"version": "Version",
"physIp": "Physical IP",
"latency": "Latency",
"settings": "Settings",
"generalSettings": "General settings",
"netId": "Network ID",
aruznieto marked this conversation as resolved.
Show resolved Hide resolved
"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",
"enaBroadcast": "Enable Broadcast",
aruznieto marked this conversation as resolved.
Show resolved Hide resolved
"logInFailed": "Invalid username or password",
"tooManyAttemps": "Too many login attempts, please try again in 15 minutes.",
aruznieto marked this conversation as resolved.
Show resolved Hide resolved
"language": "Language"
}
61 changes: 61 additions & 0 deletions frontend/public/locales/es-ES/common.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
{
"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",
"addMemberMan": "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",
"ips": "IPs asignadas",
"status": "Visto por última vez",
"version": "Versión",
"physIp": "IP pública",
"latency": "Latencia",
"settings": "Ajustes",
"generalSettings": "Ajustes generales",
"netId": "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": "IPv4 Auto-Assign",
"addIPv4Pool": "Añadir rango IPv4",
"multicastLimit": "Límite de destinatarios multicast",
"enaBroadcast": "Habilitar broadcast",
"logInFailed": "Nombre de usuario o contraseña incorrecto",
"tooManyAttemps": "Demasiados intentos de inicio de sesión. Vuelvee a intentarlo en 15 minutos",
"language": "Idioma"
}
2 changes: 2 additions & 0 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -17,6 +18,7 @@ function App() {
<Switch>
<Route exact path="/" component={Home} />
<Route path="/network/:nwid" component={Network} />
<Route path="/settings" component={Settings} />
<Route path="/404" component={NotFound} />
<Redirect to="/404" />
</Switch>
Expand Down
15 changes: 9 additions & 6 deletions frontend/src/components/Bar/Bar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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,
},
Expand Down Expand Up @@ -115,7 +119,6 @@ function Bar() {
key={index}
onClick={() => {
closeMenu();

menuItem.onClick();
}}
>
Expand Down
16 changes: 10 additions & 6 deletions frontend/src/components/HomeLoggedIn/HomeLoggedIn.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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([]);

Expand All @@ -30,6 +32,8 @@ function HomeLoggedIn() {
fetchData();
}, []);

const { t, i18n } = useTranslation();

return (
<div className={classes.root}>
<Button
Expand All @@ -38,19 +42,19 @@ function HomeLoggedIn() {
className={classes.createBtn}
onClick={createNetwork}
>
Create A Network
{t("createNetwork")}
</Button>
<Divider />
<Grid container spacing={3} className={classes.container}>
<Grid item xs={6}>
<Typography variant="h5">Controller networks</Typography>
{networks[0] && "Network controller address"}
<Typography variant="h5">{t("controllerNetworks")}</Typography>
{networks[0] && t("controllerAddress")}
<Box fontWeight="fontWeightBold">
{networks[0] && networks[0]["id"].slice(0, 10)}
{networks[0] && String(networks[0]["id"]).slice(0, 10)}
aruznieto marked this conversation as resolved.
Show resolved Hide resolved
</Box>
</Grid>
<Grid item xs="auto">
<Typography>Networks</Typography>
<Typography>{t("network", { count: networks.length })}</Typography>
<Grid item>
{networks[0] ? (
networks.map((network) => (
Expand All @@ -59,7 +63,7 @@ function HomeLoggedIn() {
</Grid>
))
) : (
<div>Please create at least one network</div>
<div>{t("createOneNetwork")}</div>
)}
</Grid>
</Grid>
Expand Down
11 changes: 6 additions & 5 deletions frontend/src/components/HomeLoggedOut/HomeLoggedOut.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -29,6 +31,8 @@ function HomeLoggedOut() {
fetchData();
}, [history, setDisableAuth, setLoggedIn, setToken]);

const { t, i18n } = useTranslation();

return (
<Grid
container
Expand All @@ -42,14 +46,11 @@ function HomeLoggedOut() {
>
<Grid item xs={10}>
<Typography variant="h5">
<span>
ZeroUI - ZeroTier Controller Web UI - is a web user interface for a
self-hosted ZeroTier network controller.
</span>
<span>{t("zerouiDesc")}</span>
</Typography>

<Typography>
<span>Please Log In to continue</span>
<span>{t("loginToContinue")}</span>
</Typography>
</Grid>
</Grid>
Expand Down
1 change: 1 addition & 0 deletions frontend/src/components/LogIn/LogIn.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ function LogIn() {
<Divider orientation="vertical" />
</>
)}
&nbsp;
aruznieto marked this conversation as resolved.
Show resolved Hide resolved
<LogInUser />
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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("");
Expand Down Expand Up @@ -41,6 +43,8 @@ function LogInToken() {
}
};

const { t, i18n } = useTranslation();

const LogIn = () => {
if (token.length !== 32) {
setErrorText("Token length error");
Expand All @@ -55,12 +59,12 @@ function LogInToken() {
return (
<div>
<Button onClick={handleClickOpen} color="inherit" variant="outlined">
Token Log In
{t("logInToken")}
</Button>
<Dialog open={open} onClose={handleClose} onKeyPress={handleKeyPress}>
<DialogTitle>Log In</DialogTitle>
<DialogTitle>{t("logIn")}</DialogTitle>
<DialogContent>
<DialogContentText>ADVANCED FEATURE.</DialogContentText>
<DialogContentText>{t("advancedFeature")}</DialogContentText>
<TextField
value={token}
onChange={(e) => {
Expand All @@ -76,10 +80,10 @@ function LogInToken() {
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Cancel
{t("cancel")}
</Button>
<Button onClick={LogIn} color="primary">
Log In
{t("logIn")}
</Button>
</DialogActions>
</Dialog>
Expand Down
Loading