Added translation and utils scripts

master
Tudor Stanciu 2021-07-05 20:16:56 +03:00
parent 5be2561abb
commit 0586f20d52
12 changed files with 493 additions and 22 deletions

115
package-lock.json generated
View File

@ -3493,6 +3493,37 @@
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.2.3.tgz",
"integrity": "sha512-pXnVMfJKSIWU2Ml4JHP7pZEPIrgBO1Fd3WGx+fPBsS+KRGhE4vxooD8XBGWbQOIVSZsVK7pUDBBkCicNu80yzQ=="
},
"axios": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz",
"integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==",
"requires": {
"follow-redirects": "1.5.10"
},
"dependencies": {
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"requires": {
"ms": "2.0.0"
}
},
"follow-redirects": {
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
"integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
"requires": {
"debug": "=3.1.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
}
}
},
"axobject-query": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz",
@ -4904,6 +4935,21 @@
"sha.js": "^2.4.8"
}
},
"cross-fetch": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.4.tgz",
"integrity": "sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ==",
"requires": {
"node-fetch": "2.6.1"
},
"dependencies": {
"node-fetch": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
"integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
}
}
},
"cross-spawn": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
@ -7799,6 +7845,14 @@
"terser": "^4.6.3"
}
},
"html-parse-stringify": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
"integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
"requires": {
"void-elements": "3.1.0"
}
},
"html-webpack-plugin": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-4.5.0.tgz",
@ -8058,6 +8112,30 @@
"resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz",
"integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ=="
},
"i18next": {
"version": "19.9.2",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-19.9.2.tgz",
"integrity": "sha512-0i6cuo6ER6usEOtKajUUDj92zlG+KArFia0857xxiEHAQcUwh/RtOQocui1LPJwunSYT574Pk64aNva1kwtxZg==",
"requires": {
"@babel/runtime": "^7.12.0"
}
},
"i18next-browser-languagedetector": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-4.3.1.tgz",
"integrity": "sha512-KIToAzf8zwWvacgnRwJp63ase26o24AuNUlfNVJ5YZAFmdGhsJpmFClxXPuk9rv1FMI4lnc8zLSqgZPEZMrW4g==",
"requires": {
"@babel/runtime": "^7.5.5"
}
},
"i18next-http-backend": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-1.2.6.tgz",
"integrity": "sha512-NeNNRofj+rR6Cw+/Elf8bCVaCiqWg2Y6F+CrmDvHiPzAW2Dtxxlk8O0na2et/rr1n3ST6rJr4nMXH/QOFuhaeA==",
"requires": {
"cross-fetch": "3.1.4"
}
},
"iconv-lite": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz",
@ -10915,6 +10993,11 @@
"minimist": "^1.2.5"
}
},
"moment": {
"version": "2.29.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ=="
},
"move-concurrently": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
@ -13321,6 +13404,14 @@
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz",
"integrity": "sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew=="
},
"react-flags": {
"version": "0.1.18",
"resolved": "https://registry.npmjs.org/react-flags/-/react-flags-0.1.18.tgz",
"integrity": "sha512-fa8D6DIZS6DWRqLcmKGIHVT13r4viHAfIRth9cFO7cDyxEPfTBbZei6p0Xeao6of4C/K4XU/j35aMjPC15ePIg==",
"requires": {
"prop-types": "^15.5.10"
}
},
"react-google-maps": {
"version": "9.4.5",
"resolved": "https://registry.npmjs.org/react-google-maps/-/react-google-maps-9.4.5.tgz",
@ -13339,6 +13430,25 @@
"warning": "^3.0.0"
}
},
"react-i18next": {
"version": "11.11.0",
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.11.0.tgz",
"integrity": "sha512-p1jHmoyJgDFQmyubUEjrx6kCsr1izW/C8i9pOiJy+9lJqLYwNA8sElVplm0VAnop3kH68edT0/g3wB3UvAcRCQ==",
"requires": {
"@babel/runtime": "^7.14.5",
"html-parse-stringify": "^3.0.1"
},
"dependencies": {
"@babel/runtime": {
"version": "7.14.6",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.6.tgz",
"integrity": "sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==",
"requires": {
"regenerator-runtime": "^0.13.4"
}
}
}
},
"react-is": {
"version": "17.0.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz",
@ -16182,6 +16292,11 @@
"resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz",
"integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ=="
},
"void-elements": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
"integrity": "sha1-YU9/v42AHwu18GYfWy9XhXUOTwk="
},
"w3c-hr-time": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz",

View File

@ -26,8 +26,15 @@
"react-scripts": "4.0.1",
"react-syntax-highlighter": "^15.4.3",
"react-toastify": "^7.0.3",
"react-i18next": "^11.4.0",
"react-flags": "^0.1.18",
"recharts": "^2.0.4",
"tinycolor2": "^1.4.2"
"tinycolor2": "^1.4.2",
"axios": "^0.19.2",
"i18next": "^19.4.4",
"i18next-browser-languagedetector": "^4.1.1",
"i18next-http-backend": "^1.0.10",
"moment": "^2.25.3"
},
"scripts": {
"start": "react-scripts start",

View File

@ -0,0 +1,41 @@
{
"DATE": "{{date,intlDate}}",
"LONG_DATE": "{{date,intlLongDate}}",
"DATE_FORMAT": "{{date, format}}",
"TIME_FROM_X": "{{date,intlTimeFromX}}",
"H_FROM_X": "{{date,intlHoursFromX}}",
"H_FROM_M": "{{number,intlHoursFromMinutes}}",
"NUMBER": "{{number,intlNumber}}",
"DECIMAL": "{{number,intlDecimal}}",
"DECIMAL2": "{{number,intlDecimal2}}",
"Language": {
"English": "English",
"Romanian": "Romanian"
},
"Menu": {
"Dashboard": "Dashboard",
"Resources": "Resources"
},
"Login": {
"Username": "Username",
"Password": "Password",
"Label": "Login",
"ChangeUser": "Change user",
"UserChanged": "User changed",
"Logout": "Logout",
"IncorrectCredentials": "Incorrect credentials.",
"Hello": "Hi, {{username}}",
"AuthenticationDate": "Authentication date"
},
"Machine": {
"FullName": "Full machine name",
"Name": "Machine name",
"IP": "IP",
"MAC": "MAC address",
"PoweredOn": "Powered on",
"Actions": {
"Wake": "Wake",
"Ping": "Ping"
}
}
}

View File

@ -0,0 +1,32 @@
{
"Language": {
"English": "Engleză",
"Romanian": "Română"
},
"Menu": {
"Dashboard": "Dashboard",
"Resources": "Resurse"
},
"Login": {
"Username": "Utilizator",
"Password": "Parolă",
"Label": "Autentificare",
"ChangeUser": "Schimbă utilizatorul",
"UserChanged": "Utilizator schimbat",
"Logout": "Deconectare",
"IncorrectCredentials": "Credențiale incorecte.",
"Hello": "Salut, {{username}}",
"AuthenticationDate": "Momentul autentificării"
},
"Machine": {
"FullName": "Nume intreg masina",
"Name": "Nume masina",
"IP": "IP",
"MAC": "Adresa MAC",
"PoweredOn": "Pornit",
"Actions": {
"Wake": "Pornește",
"Ping": "Ping"
}
}
}

View File

@ -1,4 +1,4 @@
import React from "react";
import React, { Suspense } from "react";
import { HashRouter, Route, Switch, Redirect } from "react-router-dom";
// components
@ -16,19 +16,25 @@ export default function App() {
var { isAuthenticated } = useUserState();
return (
<HashRouter basename={process.env.PUBLIC_URL || ""}>
<Switch>
<Route exact path="/" render={() => <Redirect to="/app/dashboard" />} />
<Route
exact
path="/app"
render={() => <Redirect to="/app/dashboard" />}
/>
<PrivateRoute path="/app" component={Layout} />
<PublicRoute path="/login" component={Login} />
<Route component={Error} />
</Switch>
</HashRouter>
<Suspense fallback={<div>Loading...</div>}>
<HashRouter basename={process.env.PUBLIC_URL || ""}>
<Switch>
<Route
exact
path="/"
render={() => <Redirect to="/app/dashboard" />}
/>
<Route
exact
path="/app"
render={() => <Redirect to="/app/dashboard" />}
/>
<PrivateRoute path="/app" component={Layout} />
<PublicRoute path="/login" component={Login} />
<Route component={Error} />
</Switch>
</HashRouter>
</Suspense>
);
function PrivateRoute({ component, ...rest }) {

View File

@ -11,6 +11,7 @@ import {
import { Inbox as InboxIcon } from "@material-ui/icons";
import { Link } from "react-router-dom";
import classnames from "classnames";
import { useTranslation } from "react-i18next";
// styles
import useStyles from "./styles";
@ -29,7 +30,8 @@ export default function SidebarLink({
type
}) {
var classes = useStyles();
const { t } = useTranslation();
const _label = t(label);
// local
var [isOpen, setIsOpen] = useState(false);
var isLinkActive =
@ -44,7 +46,7 @@ export default function SidebarLink({
[classes.linkTextHidden]: !isSidebarOpened
})}
>
{label}
{_label}
</Typography>
);
@ -76,7 +78,7 @@ export default function SidebarLink({
[classes.linkTextHidden]: !isSidebarOpened
})
}}
primary={label}
primary={_label}
/>
</a>
</ListItem>
@ -112,7 +114,7 @@ export default function SidebarLink({
[classes.linkTextHidden]: !isSidebarOpened
})
}}
primary={label}
primary={_label}
/>
</ListItem>
);
@ -141,7 +143,7 @@ export default function SidebarLink({
[classes.linkTextHidden]: !isSidebarOpened
})
}}
primary={label}
primary={_label}
/>
</ListItem>
{children && (

View File

@ -2,12 +2,12 @@ import React from "react";
import ReactDOM from "react-dom";
import { ThemeProvider } from "@material-ui/styles";
import { CssBaseline } from "@material-ui/core";
import Themes from "./themes";
import App from "./components/App";
import * as serviceWorker from "./serviceWorker";
import { LayoutProvider } from "./context/LayoutContext";
import { UserProvider } from "./context/UserContext";
import "./utils/i18n";
ReactDOM.render(
<LayoutProvider>
@ -18,7 +18,7 @@ ReactDOM.render(
</ThemeProvider>
</UserProvider>
</LayoutProvider>,
document.getElementById("root"),
document.getElementById("root")
);
// If you want your app to work offline and load faster, you can change

75
src/utils/axios.js Normal file
View File

@ -0,0 +1,75 @@
import axios from "axios";
import i18next from "i18next";
import { getItem } from "./localStorage";
import { storageKeys } from "./identity";
function getHeaders() {
const token = getItem(storageKeys.TOKEN);
const language = i18next.language;
return {
"Content-Type": "application/json",
Authorization: `Basic ${token.raw}`,
"Accept-Language": `${language}`
};
}
async function internalRequest(url, options) {
try {
const res = await axios.request(url, options);
return res.data;
} catch (error) {
if (error.response && error.response.data) {
throw (
{
...error.response.data,
message: error.response.data.detail || error.response.data.title
} || error
);
}
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
throw error;
}
}
export const request = (url, options) => internalRequest(url, options);
export function post(url, data) {
const options = {
method: "post",
data: JSON.stringify(data),
headers: getHeaders()
};
return internalRequest(url, options);
}
export function put(url, data) {
const options = {
method: "put",
data: JSON.stringify(data),
headers: getHeaders()
};
return internalRequest(url, options);
}
export function del(url, data) {
const options = {
method: "delete",
data: JSON.stringify(data),
headers: getHeaders()
};
return internalRequest(url, options);
}
export function get(url) {
const options = {
method: "GET",
headers: getHeaders()
};
return internalRequest(url, options);
}

21
src/utils/dataType.js Normal file
View File

@ -0,0 +1,21 @@
const isArray = (parameter) => {
const _isArray = parameter.constructor === Array;
return _isArray;
};
const isObject = (parameter) => {
const _isObject = typeof parameter === "object" && parameter !== null;
return _isObject;
};
function isJson(str) {
try {
const data = JSON.parse(str);
return { data, success: true };
} catch (e) {
return { data: null, success: false };
}
}
export { isArray, isObject, isJson };

101
src/utils/i18n.js Normal file
View File

@ -0,0 +1,101 @@
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import Backend from "i18next-http-backend";
import LanguageDetector from "i18next-browser-languagedetector";
import moment from "moment";
import "moment/locale/ro.js";
import "moment/locale/de.js";
i18n
.use(Backend)
.use(LanguageDetector)
.use(initReactI18next) // passes i18n down to react-i18next
.init(
{
fallbackLng: "en",
debug: true,
ns: ["translations"],
defaultNS: "translations",
//whitelist: ["en", "ro"],
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
format: function (value, format, lng) {
if (format === "uppercase") return value.toUpperCase();
if (format === "intlDate") {
if (value && moment(value).isValid()) {
return moment(value).format("L");
}
return "";
}
if (format === "intlLongDate") {
if (value && moment(value).isValid()) {
return moment(value).format("LLLL");
}
return "";
}
if (format === "intlTimeFromX") {
if (value && moment(value.start).isValid()) {
let startDate = moment(value.start);
let endDate = moment(value.end);
return moment(endDate).from(startDate, true);
}
return "";
}
if (format === "intlHoursFromX") {
if (value && moment(value.start).isValid()) {
let startDate = moment(value.start);
let endDate = moment(value.end);
let span = moment.duration(endDate - startDate);
return `${parseInt(span.asHours(), 10)}h ${parseInt(
span.asMinutes() % 60,
10
)}m`;
}
return "";
}
if (format === "intlNumber")
return new Intl.NumberFormat(lng).format(value);
if (format === "intlDecimal")
return new Intl.NumberFormat(lng, {
minimumFractionDigits: 2
}).format(value);
if (format === "intlDecimal2")
return new Intl.NumberFormat(lng, {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(value);
//dateformat
if (value && value.format) {
if (value.value && moment(value).isValid()) {
return moment(value.value).format(value.format);
}
return "";
}
return value;
}
},
backend: {
loadPath: `${process.env.PUBLIC_URL || ""}/locales/{{lng}}/{{ns}}.json`
}
},
() => {
const currentLang = i18n.language;
if (!currentLang || !currentLang.startsWith("ro")) {
i18n.changeLanguage("en");
} else {
i18n.changeLanguage("ro");
}
}
);
i18n.on("languageChanged", function (lng) {
moment.locale(lng);
});
export default i18n;

35
src/utils/identity.js Normal file
View File

@ -0,0 +1,35 @@
import { request } from "./axios";
import { setItem, getItem, removeItem } from "./localStorage";
const storageKeys = {
TOKEN: "AUTHORIZATION_TOKEN",
USER: "USER_NAME"
};
const authenticate = async (userName, password) => {
const urlTemplate = process.env.REACT_APP_IDENTITY_AUTHENTICATION_URL;
const url = urlTemplate
.replace("{username}", userName)
.replace("{password}", password);
const options = {
method: "post"
};
const response = await request(url, options);
if (response.status === "SUCCESS") {
setItem(storageKeys.TOKEN, response.token);
setItem(storageKeys.USER, userName);
}
return response;
};
const invalidate = () => {
const token = getItem(storageKeys.TOKEN);
if (token) {
removeItem(storageKeys.TOKEN);
removeItem(storageKeys.USER);
}
};
export { storageKeys, authenticate, invalidate };

36
src/utils/localStorage.js Normal file
View File

@ -0,0 +1,36 @@
import { isArray, isObject, isJson } from "./dataType";
const setItem = (key, value) => {
let valueToStore = value;
if (isArray(value) || isObject(value)) {
valueToStore = JSON.stringify(value);
}
window.localStorage.setItem(key, valueToStore);
};
const getItem = (key) => {
var value = window.localStorage.getItem(key);
var { data, success } = isJson(value);
if (success) {
return data;
} else {
return value;
}
};
const removeItem = (key) => {
window.localStorage.removeItem(key);
};
const clear = () => {
window.localStorage.clear();
};
const key = (index) => {
var keyName = window.localStorage.key(index);
return keyName;
};
export { setItem, getItem, removeItem, clear, key };