Skip to content

Commit 4a5f846

Browse files
authored
Merge pull request #182 from aruznieto/i18n
feat: i18n
2 parents 213c949 + ee73374 commit 4a5f846

File tree

30 files changed

+555
-97
lines changed

30 files changed

+555
-97
lines changed

backend/routes/auth.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const loginLimiter = rateLimit({
1010
max: Number(process.env.ZU_LOGIN_LIMIT_ATTEMPTS) || 50, // limit each IP to 50 requests per windowMs
1111
message: {
1212
status: 429,
13-
error: "Too many login attempts, please try again in 15 minutes.",
13+
error: "tooManyAttempts",
1414
},
1515
});
1616

backend/services/auth.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ export async function authorize(username, password, callback) {
88
throw err;
99
}
1010
const user = users.find({ username: username });
11-
if (!user.value()) return callback(new Error("Invalid username or password")); // If return "user not found" someone can do a user listing
11+
if (!user.value()) return callback(new Error("logInFailed")); // If return "user not found" someone can do a user listing
1212
const verified = await verifyHash(password, user.value()["password_hash"]);
1313
if (verified) {
1414
return callback(null, user.value());
1515
} else {
16-
return callback(new Error("Invalid username or password"));
16+
return callback(new Error("logInFailed"));
1717
}
1818
}
1919

frontend/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,15 @@
1111
"codemirror": "^5.62.3",
1212
"date-fns": "^2.29.2",
1313
"history": "^5.3.0",
14+
"i18next": "^23.5.1",
15+
"i18next-browser-languagedetector": "^7.1.0",
16+
"i18next-http-backend": "^2.2.2",
1417
"ipaddr.js": "^2.0.1",
1518
"lodash": "^4.17.21",
1619
"react": "^17.0.2",
1720
"react-data-table-component": "^6.11.8",
1821
"react-dom": "^17.0.2",
22+
"react-i18next": "^13.3.0",
1923
"react-is": "^17.0.2",
2024
"react-router-dom": "^5.2.0",
2125
"react-use": "^17.4.0",
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
{
2+
"flowRules": "Flow Rules",
3+
"createNetwork": "Create A Network",
4+
"createOneNetwork": "Please create at least one network",
5+
"controllerNetworks": "Controller networks",
6+
"network_one": "Network",
7+
"network_other": "Networks",
8+
"controllerAddress": "Network controller address",
9+
"loginToContinue": "Please, Log In to continue",
10+
"zerouiDesc": "ZeroUI - ZeroTier Controller Web UI - is a web user interface for a self-hosted ZeroTier network controller.",
11+
"logIn": "Log In",
12+
"logInToken": "Token Log In",
13+
"cancel": "Cancel",
14+
"management": "Management",
15+
"deleteNetwork": "Delete Network",
16+
"deleteAlert": "This action cannot be undone.",
17+
"deleteNetworkConfirm": "Are you sure you want to delete this network?",
18+
"deleteMemberConfirm": "Are you sure you want to delete this member?",
19+
"delete": "Delete",
20+
"logOut": "Log out",
21+
"advancedFeature": "ADVANCED FEATURE",
22+
"noDevices": "No devices have joined this network. Use the app on your devices to join",
23+
"member_one": "Member",
24+
"member_other": "Members",
25+
"addMemberManually": "Manually Add Member",
26+
"name": "Name",
27+
"description": "Description",
28+
"allowBridging": "Allow Ethernet Bridging",
29+
"noAutoIP": "Do Not Auto-Assign IPs",
30+
"capabilities": "Capabilities",
31+
"noCapDef": "No capabilities defined",
32+
"tags": "Tags",
33+
"noTagDef": "No tags defined",
34+
"authorized": "Authorized",
35+
"address": "Address",
36+
"managedIPs": "Managed IPs",
37+
"lastSeen": "Last seen",
38+
"version": "Version",
39+
"physIp": "Physical IP",
40+
"latency": "Latency",
41+
"settings": "Settings",
42+
"generalSettings": "General settings",
43+
"networkId": "Network ID",
44+
"accessControl": "Access control",
45+
"public": "Public",
46+
"private": "Private",
47+
"managedRoutes": "Managed routes",
48+
"addRoute": "Add route",
49+
"target": "Target",
50+
"via": "Via",
51+
"start": "Start",
52+
"end": "End",
53+
"ipv4AutoAssign": "IPv4 Auto-Assign",
54+
"autoAssignPool": "IPv4 Auto-Assign",
55+
"addIPv4Pool": "Add IPv4 Pool",
56+
"multicastLimit": "Multicast Recipient Limit",
57+
"enableBroadcast": "Enable Broadcast",
58+
"logInFailed": "Invalid username or password",
59+
"tooManyAttempts": "Too many login attempts, please try again in 15 minutes.",
60+
"language": "Language",
61+
"notAuthorized": "You are not authorized. Please Log In.",
62+
"saveChanges": "Save changes",
63+
"optional": "Optional",
64+
"destination": "Destination",
65+
"username": "Username",
66+
"password": "Password"
67+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
{
2+
"flowRules": "Reglas de flujo",
3+
"createNetwork": "Crear una red",
4+
"createOneNetwork": "Por favor, crea al menos una red",
5+
"controllerNetworks": "Controlador de redes",
6+
"network_one": "Red",
7+
"network_other": "Redes",
8+
"controllerAddress": "Dirección del controlador",
9+
"loginToContinue": "Por favor, inicia sesión para continuar",
10+
"zerouiDesc": "ZeroUI - ZeroTier Controller Web UI - es una interfaz de usuario web para un controlador de red ZeroTier self-hosted.",
11+
"logIn": "Iniciar sesión",
12+
"logInToken": "Iniciar sesión con token",
13+
"cancel": "Cancelar",
14+
"management": "Gestión",
15+
"deleteNetwork": "Borrar red",
16+
"deleteAlert": "Esta acción no puede ser revertida.",
17+
"deleteNetworkConfirm": "¿Seguro que deseas borrar esta red?",
18+
"deleteMemberConfirm": "¿Seguro que deseas borrar este usuario?",
19+
"delete": "Borrar",
20+
"logOut": "Cerrar sesión",
21+
"advancedFeature": "CARACTERÍSTICA AVANZADA",
22+
"noDevices": "Ningún dispositivo se ha unido a esta red. Utilice la aplicación en sus dispositivos para unirse",
23+
"member_one": "Miembro",
24+
"member_other": "Miembros",
25+
"addMemberManually": "Añadir miembro manualmente",
26+
"name": "Nombre",
27+
"description": "Descripción",
28+
"allowBridging": "Permitir puente Ethernet",
29+
"noAutoIP": "No autoasignar IPs",
30+
"capabilities": "Permisos",
31+
"noCapDef": "No hay permisos definidos",
32+
"tags": "Etiquetas",
33+
"noTagDef": "No hay etiquetas definidas",
34+
"authorized": "Autorizado",
35+
"address": "Dirección",
36+
"managedIPs": "IPs asignadas",
37+
"lastSeen": "Visto por última vez",
38+
"version": "Versión",
39+
"physIp": "IP pública",
40+
"latency": "Latencia",
41+
"settings": "Ajustes",
42+
"generalSettings": "Ajustes generales",
43+
"networkId": "ID de red",
44+
"accessControl": "Control de acceso",
45+
"public": "Público",
46+
"private": "Privado",
47+
"managedRoutes": "Rutas gestionadas",
48+
"addRoute": "Añadir ruta",
49+
"target": "Objetivo",
50+
"via": "Vía",
51+
"start": "Inicio",
52+
"end": "Final",
53+
"autoAssignPool": "Rango de IPv4 autoasignables",
54+
"ipv4AutoAssign": "Rangos de IPv4 automáticos",
55+
"addIPv4Pool": "Añadir rango IPv4",
56+
"multicastLimit": "Límite de destinatarios multicast",
57+
"enableBroadcast": "Habilitar broadcast",
58+
"logInFailed": "Nombre de usuario o contraseña incorrecto",
59+
"tooManyAttempts": "Demasiados intentos de inicio de sesión. Vuelvee a intentarlo en 15 minutos",
60+
"language": "Idioma",
61+
"notAuthorized": "No estás autorizado. Por favor, inicia sesión.",
62+
"saveChanges": "Guardar cambios",
63+
"optional": "Opcional",
64+
"destination": "Destino",
65+
"username": "Nombre de usuario",
66+
"password": "Contraseña"
67+
}

frontend/src/App.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import Bar from "./components/Bar";
88
import Home from "./routes/Home";
99
import NotFound from "./routes/NotFound";
1010
import Network from "./routes/Network/Network";
11+
import Settings from "./routes/Settings";
1112

1213
function App() {
1314
return (
@@ -17,6 +18,7 @@ function App() {
1718
<Switch>
1819
<Route exact path="/" component={Home} />
1920
<Route path="/network/:nwid" component={Network} />
21+
<Route path="/settings" component={Settings} />
2022
<Route path="/404" component={NotFound} />
2123
<Redirect to="/404" />
2224
</Switch>

frontend/src/components/Bar/Bar.jsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import MenuIcon from "@material-ui/icons/Menu";
1919

2020
import LogIn from "components/LogIn";
2121

22+
import { useTranslation } from "react-i18next";
23+
2224
function Bar() {
2325
const [loggedIn, setLoggedIn] = useLocalStorage("loggedIn", false);
2426
const [disabledAuth] = useLocalStorage("disableAuth", false);
@@ -41,16 +43,18 @@ function Bar() {
4143
history.go(0);
4244
};
4345

46+
const { t, i18n } = useTranslation();
47+
4448
const menuItems = [
4549
// TODO: add settings page
46-
// {
47-
// name: "Settings",
48-
// to: "/settings",
49-
// },
50+
{
51+
name: t("settings"),
52+
to: "/settings",
53+
},
5054
...(!disabledAuth
5155
? [
5256
{
53-
name: "Log out",
57+
name: t("logOut"),
5458
divide: true,
5559
onClick: onLogOutClick,
5660
},
@@ -115,7 +119,6 @@ function Bar() {
115119
key={index}
116120
onClick={() => {
117121
closeMenu();
118-
119122
menuItem.onClick();
120123
}}
121124
>

frontend/src/components/HomeLoggedIn/HomeLoggedIn.jsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import NetworkButton from "./components/NetworkButton";
99
import API from "utils/API";
1010
import { generateNetworkConfig } from "utils/NetworkConfig";
1111

12+
import { useTranslation } from "react-i18next";
13+
1214
function HomeLoggedIn() {
1315
const [networks, setNetworks] = useState([]);
1416

@@ -30,6 +32,8 @@ function HomeLoggedIn() {
3032
fetchData();
3133
}, []);
3234

35+
const { t, i18n } = useTranslation();
36+
3337
return (
3438
<div className={classes.root}>
3539
<Button
@@ -38,19 +42,19 @@ function HomeLoggedIn() {
3842
className={classes.createBtn}
3943
onClick={createNetwork}
4044
>
41-
Create A Network
45+
{t("createNetwork")}
4246
</Button>
4347
<Divider />
4448
<Grid container spacing={3} className={classes.container}>
4549
<Grid item xs={6}>
46-
<Typography variant="h5">Controller networks</Typography>
47-
{networks[0] && "Network controller address"}
50+
<Typography variant="h5">{t("controllerNetworks")}</Typography>
51+
{networks[0] && t("controllerAddress")}
4852
<Box fontWeight="fontWeightBold">
4953
{networks[0] && networks[0]["id"].slice(0, 10)}
5054
</Box>
5155
</Grid>
5256
<Grid item xs="auto">
53-
<Typography>Networks</Typography>
57+
<Typography>{t("network", { count: networks.length })}</Typography>
5458
<Grid item>
5559
{networks[0] ? (
5660
networks.map((network) => (
@@ -59,7 +63,7 @@ function HomeLoggedIn() {
5963
</Grid>
6064
))
6165
) : (
62-
<div>Please create at least one network</div>
66+
<div>{t("createOneNetwork")}</div>
6367
)}
6468
</Grid>
6569
</Grid>

frontend/src/components/HomeLoggedOut/HomeLoggedOut.jsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { Grid, Typography } from "@material-ui/core";
33
import { useLocalStorage } from "react-use";
44
import { useHistory } from "react-router-dom";
55

6+
import { useTranslation } from "react-i18next";
7+
68
import axios from "axios";
79

810
function HomeLoggedOut() {
@@ -29,6 +31,8 @@ function HomeLoggedOut() {
2931
fetchData();
3032
}, [history, setDisableAuth, setLoggedIn, setToken]);
3133

34+
const { t, i18n } = useTranslation();
35+
3236
return (
3337
<Grid
3438
container
@@ -42,14 +46,11 @@ function HomeLoggedOut() {
4246
>
4347
<Grid item xs={10}>
4448
<Typography variant="h5">
45-
<span>
46-
ZeroUI - ZeroTier Controller Web UI - is a web user interface for a
47-
self-hosted ZeroTier network controller.
48-
</span>
49+
<span>{t("zerouiDesc")}</span>
4950
</Typography>
5051

5152
<Typography>
52-
<span>Please Log In to continue</span>
53+
<span>{t("loginToContinue")}</span>
5354
</Typography>
5455
</Grid>
5556
</Grid>

frontend/src/components/LogIn/components/LogInToken/LogInToken.jsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import {
1212
DialogTitle,
1313
} from "@material-ui/core";
1414

15+
import { useTranslation } from "react-i18next";
16+
1517
function LogInToken() {
1618
const [open, setOpen] = useState(false);
1719
const [errorText, setErrorText] = useState("");
@@ -41,6 +43,8 @@ function LogInToken() {
4143
}
4244
};
4345

46+
const { t, i18n } = useTranslation();
47+
4448
const LogIn = () => {
4549
if (token.length !== 32) {
4650
setErrorText("Token length error");
@@ -55,12 +59,12 @@ function LogInToken() {
5559
return (
5660
<div>
5761
<Button onClick={handleClickOpen} color="inherit" variant="outlined">
58-
Token Log In
62+
{t("logInToken")}
5963
</Button>
6064
<Dialog open={open} onClose={handleClose} onKeyPress={handleKeyPress}>
61-
<DialogTitle>Log In</DialogTitle>
65+
<DialogTitle>{t("logIn")}</DialogTitle>
6266
<DialogContent>
63-
<DialogContentText>ADVANCED FEATURE.</DialogContentText>
67+
<DialogContentText>{t("advancedFeature")}</DialogContentText>
6468
<TextField
6569
value={token}
6670
onChange={(e) => {
@@ -76,10 +80,10 @@ function LogInToken() {
7680
</DialogContent>
7781
<DialogActions>
7882
<Button onClick={handleClose} color="primary">
79-
Cancel
83+
{t("cancel")}
8084
</Button>
8185
<Button onClick={LogIn} color="primary">
82-
Log In
86+
{t("logIn")}
8387
</Button>
8488
</DialogActions>
8589
</Dialog>

0 commit comments

Comments
 (0)