diff --git a/.gitignore b/.gitignore index 379a7329..29e4e585 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,9 @@ conf.json *.exe build/ /mod_packs/* +/config/* +/mods/* +/saves/* npm-debug.log .idea/ factorio.auth diff --git a/package-lock.json b/package-lock.json index eae1b4f2..925db0fe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,11 @@ "@fortawesome/react-fontawesome": "^0.2.0", "axios": "^1.6.0", "fuse.js": "^7.0.0", + "i18next": "^25.5.2", + "i18next-browser-languagedetector": "^8.2.0", + "i18next-http-backend": "^3.0.2", "react-hook-form": "^7.47.0", + "react-i18next": "^15.7.3", "regenerator-runtime": "^0.14.0", "semver": "^7.3.7", "tailwindcss": "^3.3.5" @@ -1831,23 +1835,14 @@ "dev": true }, "node_modules/@babel/runtime": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.6.tgz", - "integrity": "sha512-t9wi7/AW6XtKahAe20Yw0/mMljKq0B1r2fPdvaAdV/KPDZewFXdaaa6K7lxmZBZ8FBNpCiAT6iHPmd6QO9bKfQ==", - "dev": true, - "dependencies": { - "regenerator-runtime": "^0.13.4" - }, + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/runtime/node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", - "dev": true - }, "node_modules/@babel/template": { "version": "7.22.15", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", @@ -3029,6 +3024,15 @@ "yarn": ">=1" } }, + "node_modules/cross-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "license": "MIT", + "dependencies": { + "node-fetch": "^2.6.12" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -3923,6 +3927,64 @@ "node": ">= 0.4" } }, + "node_modules/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==", + "license": "MIT", + "dependencies": { + "void-elements": "3.1.0" + } + }, + "node_modules/i18next": { + "version": "25.5.2", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.5.2.tgz", + "integrity": "sha512-lW8Zeh37i/o0zVr+NoCHfNnfvVw+M6FQbRp36ZZ/NyHDJ3NJVpp2HhAUyU9WafL5AssymNoOjMRB48mmx2P6Hw==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.6" + }, + "peerDependencies": { + "typescript": "^5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/i18next-browser-languagedetector": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.2.0.tgz", + "integrity": "sha512-P+3zEKLnOF0qmiesW383vsLdtQVyKtCNA9cjSoKCppTKPQVfKd2W8hbVo5ZhNJKDqeM7BOcvNoKJOjpHh4Js9g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, + "node_modules/i18next-http-backend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-3.0.2.tgz", + "integrity": "sha512-PdlvPnvIp4E1sYi46Ik4tBYh/v/NbYfFFgTjkwFl0is8A18s7/bx9aXqsrOax9WUbeNS6mD2oix7Z0yGGf6m5g==", + "license": "MIT", + "dependencies": { + "cross-fetch": "4.0.0" + } + }, "node_modules/icss-utils": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", @@ -4490,6 +4552,26 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-releases": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", @@ -5410,6 +5492,32 @@ "react": "^16.8.0 || ^17 || ^18" } }, + "node_modules/react-i18next": { + "version": "15.7.3", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.7.3.tgz", + "integrity": "sha512-AANws4tOE+QSq/IeMF/ncoHlMNZaVLxpa5uUGW1wjike68elVYr0018L9xYoqBr1OFO7G7boDPrbn0HpMCJxTw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.6", + "html-parse-stringify": "^3.0.1" + }, + "peerDependencies": { + "i18next": ">= 25.4.1", + "react": ">= 16.8.0", + "typescript": "^5" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -6174,6 +6282,12 @@ "node": ">=8.0" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", @@ -6263,6 +6377,15 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", @@ -6276,6 +6399,12 @@ "node": ">=10.13.0" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, "node_modules/webpack": { "version": "5.89.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz", @@ -6444,6 +6573,16 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -7722,21 +7861,9 @@ "dev": true }, "@babel/runtime": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.6.tgz", - "integrity": "sha512-t9wi7/AW6XtKahAe20Yw0/mMljKq0B1r2fPdvaAdV/KPDZewFXdaaa6K7lxmZBZ8FBNpCiAT6iHPmd6QO9bKfQ==", - "dev": true, - "requires": { - "regenerator-runtime": "^0.13.4" - }, - "dependencies": { - "regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", - "dev": true - } - } + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==" }, "@babel/template": { "version": "7.22.15", @@ -8642,6 +8769,14 @@ "cross-spawn": "^7.0.1" } }, + "cross-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "requires": { + "node-fetch": "^2.6.12" + } + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -9245,6 +9380,38 @@ "function-bind": "^1.1.2" } }, + "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" + } + }, + "i18next": { + "version": "25.5.2", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.5.2.tgz", + "integrity": "sha512-lW8Zeh37i/o0zVr+NoCHfNnfvVw+M6FQbRp36ZZ/NyHDJ3NJVpp2HhAUyU9WafL5AssymNoOjMRB48mmx2P6Hw==", + "requires": { + "@babel/runtime": "^7.27.6" + } + }, + "i18next-browser-languagedetector": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.2.0.tgz", + "integrity": "sha512-P+3zEKLnOF0qmiesW383vsLdtQVyKtCNA9cjSoKCppTKPQVfKd2W8hbVo5ZhNJKDqeM7BOcvNoKJOjpHh4Js9g==", + "requires": { + "@babel/runtime": "^7.23.2" + } + }, + "i18next-http-backend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-3.0.2.tgz", + "integrity": "sha512-PdlvPnvIp4E1sYi46Ik4tBYh/v/NbYfFFgTjkwFl0is8A18s7/bx9aXqsrOax9WUbeNS6mD2oix7Z0yGGf6m5g==", + "requires": { + "cross-fetch": "4.0.0" + } + }, "icss-utils": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", @@ -9660,6 +9827,14 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, "node-releases": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", @@ -10228,6 +10403,15 @@ "integrity": "sha512-F/TroLjTICipmHeFlMrLtNLceO2xr1jU3CyiNla5zdwsGUGu2UOxxR4UyJgLlhMwLW/Wzp4cpJ7CPfgJIeKdSg==", "requires": {} }, + "react-i18next": { + "version": "15.7.3", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.7.3.tgz", + "integrity": "sha512-AANws4tOE+QSq/IeMF/ncoHlMNZaVLxpa5uUGW1wjike68elVYr0018L9xYoqBr1OFO7G7boDPrbn0HpMCJxTw==", + "requires": { + "@babel/runtime": "^7.27.6", + "html-parse-stringify": "^3.0.1" + } + }, "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -10755,6 +10939,11 @@ "is-number": "^7.0.0" } }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", @@ -10812,6 +11001,11 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==" + }, "watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", @@ -10822,6 +11016,11 @@ "graceful-fs": "^4.1.2" } }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, "webpack": { "version": "5.89.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz", @@ -10931,6 +11130,15 @@ "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", "dev": true }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index c0abfe46..97aaa9f9 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,11 @@ "@fortawesome/react-fontawesome": "^0.2.0", "axios": "^1.6.0", "fuse.js": "^7.0.0", + "i18next": "^25.5.2", + "i18next-browser-languagedetector": "^8.2.0", + "i18next-http-backend": "^3.0.2", "react-hook-form": "^7.47.0", + "react-i18next": "^15.7.3", "regenerator-runtime": "^0.14.0", "semver": "^7.3.7", "tailwindcss": "^3.3.5" diff --git a/ui/App/components/ChangeLangDialog.jsx b/ui/App/components/ChangeLangDialog.jsx new file mode 100644 index 00000000..58ebae22 --- /dev/null +++ b/ui/App/components/ChangeLangDialog.jsx @@ -0,0 +1,35 @@ +import React, {useState} from 'react'; +import Modal from "./Modal"; +import Button from "./Button"; +import { useTranslation } from "react-i18next"; + +function ChangeLangDialog({isOpen, close, onSuccess}) { + + const { t, i18n } = useTranslation(); + + const changeLang = (langKey) => { + i18n.changeLanguage(langKey) + close() + } + + return ( + + + + {/* Other languages */} + + } + actions={ + <> + + } + isOpen={isOpen} + onSuccess={onSuccess} + /> + ); +} + +export default ChangeLangDialog; \ No newline at end of file diff --git a/ui/App/components/ConfirmDialog.jsx b/ui/App/components/ConfirmDialog.jsx index 7f96c9bc..4704c475 100644 --- a/ui/App/components/ConfirmDialog.jsx +++ b/ui/App/components/ConfirmDialog.jsx @@ -1,9 +1,12 @@ import React, {useState} from 'react'; import Modal from "./Modal"; import Button from "./Button"; +import { useTranslation } from "react-i18next"; function ConfirmDialog({title, content, isOpen, close, onSuccess}) { + const { t, i18n } = useTranslation(); + const [isLoading, setIsLoading] = useState(false); const confirm = () => { @@ -21,8 +24,8 @@ function ConfirmDialog({title, content, isOpen, close, onSuccess}) { content={content} actions={ <> - - + + } isOpen={isOpen} diff --git a/ui/App/components/Layout.jsx b/ui/App/components/Layout.jsx index 2f47a676..d5086fea 100644 --- a/ui/App/components/Layout.jsx +++ b/ui/App/components/Layout.jsx @@ -4,21 +4,26 @@ import Button from "./Button"; import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; import {faBars} from "@fortawesome/free-solid-svg-icons"; import {Flash} from "./Flash"; +import ChangeLangDialog from "./ChangeLangDialog"; +import { useTranslation } from "react-i18next"; const Layout = ({handleLogout, serverStatus}) => { + const { t, i18n } = useTranslation(); + const [isNavCollapsed, setIsNavCollapsed] = useState(true); + const [isChangingLang, setIsChangingLang] = useState(false); const Status = ({info}) => { - let text = 'Unknown'; + let text = t("controls.unknown"); let color = 'gray-light'; if (info && info.running) { - text = 'Running'; + text = t("controls.running"); color = 'green'; } else if (info && !info.running) { - text = 'Stopped'; + text = t("controls.stopped"); color = 'red'; } @@ -48,7 +53,7 @@ const Layout = ({handleLogout, serverStatus}) => {
- Factorio Server Manager + {t("main_title")}
-

Server Status

+

{t("server_status")}

-

Server Management

+

{t("server_management")}

- Controls - Saves - Mods - Server Settings - Game Settings - Console - Logs + {t("controls.title")} + {t("saves.title")} + {t("mods.title")} + {t("server_settings.title")} + {t("game_settings.title")} + {t("console.title")} + {t("logs.title")}
-

FSM Administration

+

{t("FSM_administration")}

- Users - Help + {t("users.title")} + + {t("help.title")} + setIsChangingLang(false)} + onSuccess={() => console.log("new lang apply")} + />
- +
diff --git a/ui/App/i18n.js b/ui/App/i18n.js new file mode 100644 index 00000000..8f318336 --- /dev/null +++ b/ui/App/i18n.js @@ -0,0 +1,39 @@ +import i18n from "i18next"; +import { initReactI18next } from "react-i18next"; +import Backend from 'i18next-http-backend' +import LanguageDetector from "i18next-browser-languagedetector"; +import enTranslations from "./locales/en.json"; +import ruTranslations from "./locales/ru.json"; + +const resources = +{ + en: + { + translation: enTranslations, + }, + ru: + { + translation: ruTranslations, + } +}; + +i18n + .use(Backend) + .use(LanguageDetector) + .use(initReactI18next) + .init({ + resources, + fallbackLng: "en", // Default Language + // Detecting and caching of language cookies + detection: + { + order: ["localStorage", "cookie", "navigator"], + cache: ["localStorage", "cookie"] + }, + interpolation: + { + escapeValue: false // react already safes from xss + } + }); + +export default i18n; \ No newline at end of file diff --git a/ui/App/locales/en.json b/ui/App/locales/en.json new file mode 100644 index 00000000..de95006e --- /dev/null +++ b/ui/App/locales/en.json @@ -0,0 +1,214 @@ +{ + "save": "Save", + "upload": "Upload", + "saved": "Settings saved.", + "create": "Create", + "cancel": "Cancel", + "confirm": "Confirm", + "name": "Name", + "username": "Username", + "password": "Password", + "logout": "Logout", + "install": "Install", + "actions": "Actions", + "role": "Role", + "email": "Email", + "change": "Change", + "load": "Load", + "server_status": "Server Status", + "server_management": "Server Management", + "FSM_administration": "FSM Administration", + "main_title": "Factorio Server Manager", + "server_settings": + { + "title": "Server settings", + "admins": "Admins", + "name": "Name", + "description": "Description", + "tags": "Tags", + "_comment_max_players": "Maximum number of players allowed, admins can join even a full server. 0 means unlimited.", + "max_players": "Max players", + "_comment_visibility": "Public: Game will be published on the official Factorio matching server. Lan: Game will be broadcast on LAN", + "visibility______": + { + "public_": "Public", + "lan_": "Lan" + }, + "visibility": "Visibility", + "_comment_credentials": "Your factorio.com login credentials. Required for games with visibility public", + "username": "Username", + "password": "Password", + "_comment_token": "Authentication token. May be used instead of 'password' above.", + "token": "Token", + "game_password": "Game password", + "_comment_require_user_verification": "When set to true, the server will only allow clients that have a valid Factorio.com account", + "require_user_verification": "Require user verification", + "_comment_max_upload_in_kilobytes_per_second": "Optional, default value is 0. 0 means unlimited.", + "max_upload_in_kilobytes_per_second": "Max upload in kilobytes per second", + "_comment_max_upload_slots": "Optional, default value is 5. 0 means unlimited.", + "max_upload_slots": "Max upload slots", + "_comment_minimum_latency_in_ticks": "Optional one tick is 16ms in default speed, default value is 0. 0 means no minimum.", + "minimum_latency_in_ticks": "Minimum latency in ticks", + "_comment_max_heartbeats_per_second": "Network tick rate. Maximum rate game updates packets are sent at before bundling them together. Minimum value is 6, maximum value is 240.", + "max_heartbeats_per_second": "Max heartbeats per second", + "_comment_ignore_player_limit_for_returning_players": "Players that played on this map already can join even when the max player limit was reached.", + "ignore_player_limit_for_returning_players": "Ignore player limit for returning players", + "_comment_allow_commands": "Possible values are: true, false and admins-only", + "allow_commands": "Allow commands", + "_comment_autosave_interval": "Autosave interval in minutes", + "autosave_interval": "Autosave interval", + "_comment_autosave_slots": "Server autosave slots, it is cycled through when the server autosaves.", + "autosave_slots": "Autosave slots", + "_comment_afk_autokick_interval": "How many minutes until someone is kicked when doing nothing, 0 for never.", + "afk_autokick_interval": "Afk autokick interval", + "_comment_auto_pause": "Whether should the server be paused when no players are present.", + "auto_pause": "Auto pause", + "_comment_auto_pause_when_players_connect": "Whether should the server be paused when someone is connecting to the server.", + "auto_pause_when_players_connect": "Auto pause when players connect", + "only_admins_can_pause_the_game": "Only admins can pause the game", + "_comment_autosave_only_on_server": "Whether autosaves should be saved only on server or also on all connected clients. Default is true.", + "autosave_only_on_server": "Autosave only on server", + "_comment_non_blocking_saving": "Highly experimental feature, enable only at your own risk of losing your saves. On UNIX systems, server will fork itself to create an autosave. Autosaving on connected Windows clients will be disabled regardless of autosave_only_on_server option.", + "non_blocking_saving": "Non blocking saving", + "_comment_segment_sizes": "Long network messages are split into segments that are sent over multiple ticks. Their size depends on the number of peers currently connected. Increasing the segment size will increase upload bandwidth requirement for the server and download bandwidth requirement for clients. This setting only affects server outbound messages. Changing these settings can have a negative impact on connection stability for some clients.", + "minimum_segment_size": "Minimum segment size", + "minimum_segment_size_peer_count": "Minimum segment size peer count", + "maximum_segment_size": "Maximum segment size", + "maximum_segment_size_peer_count": "Maximum segment size peer count" + }, + "saves": + { + "create_save": "Create Save", + "upload_save": "Upload Save", + "title": "Saves", + "last_modified": "Last Modified At", + "size": "Size", + "actions": "Actions", + "create_new_save_only_when_server_not_running": "Create a new Save is only possible if the Factorio server is not running.", + "save_form": + { + "file_name": "Savefile Name", + "save_file_error_message": "Savefile Name is required", + "create_save": "Create Save" + }, + "upload_form": + { + "select_file": "Select File ...", + "file_name": "Savefile", + "save_file_error_message": "Savefile is required", + "upload": "Upload" + } + }, + "controls": + { + "title": "Server Status", + "status": "Status", + "unknown": "Unknown", + "running": "Running", + "stopped": "Stopped", + "port": "Port", + "IP_error_message": "IP is required and must be valid.", + "port_error_message": "Port is required within range 1-65535", + "save_error_message": "Save is required and must be valid.", + "f_version": "Factorio version", + "save": "Save", + "save&stop": "Save & Stop Server", + "kill_server": "Kill Server", + "start_server": "Start Server" + }, + "mods": + { + "change_mods_while_running_error_message": "Changing mods is disabled while the server is running!", + "install_mod": "Install Mod", + "upload_mod": "Upload Mod", + "load_mods": "Load Mods from Save", + "title": "Mods", + "delete_all": "Delete all Mods", + "update_all": "Update all Mods", + "download_all": "Download all Mods", + "mod_packs": "Mod packs", + "select_file": "Select File ...", + "mods_loaded_from_save": "Mods are loaded from save file @@@.", + "load_mods_from_save": "Load Mods from Save", + "load_confirm_dialog": "Loading the Mods from Save @@@ will remove all currently installed Mods.", + "add_modpack_with_current_mods": "Add ModPack with current installed Mods", + "add_mod": + { + "loading_mod_list": "Loading List of Mods from", + "version": "Version", + "compatibility": "Compatibility" + }, + "mod_list": + { + "enabled": "Enabled", + "compatibility": "Compatibility", + "mod_version": "Mod Version", + "factorio_version": "Factorio Version" + }, + "mod_pack": + { + "load_modpack": "Load ModPack", + "load_confirm_dialog": "Loading the ModPack @@@ will remove all installed Mods." + } + }, + "login": + { + "login_failed_message": "Login failed. Username or Password wrong.", + "title": "Login", + "login": "Login", + "username_error_message": "Username is required", + "password_error_message": "Password is required", + "sign_in": "Sign In", + "factorio_login_error_message": "Given username or email and password do not match any account." + }, + "console": + { + "title": "Console", + "error": "The console is not available, because Factorio is not running." + }, + "logs": + { + "title": "Logs" + }, + "help": + { + "title": "Help", + "fsm": "Factorio Server Manager", + "fsm_content": "The Factorio Server Manager (FSM) is an open source project and is not affiliated to the game Factorio or Wube Software.", + "bugs_help": "Bugs and Help", + "bugs_help_content": "Please use the <0>GitHub repository to report bugs or seek for help.", + "helpful_resources": "Helpful Resources", + "factorio_link_text": "Official Factorio Wiki about Multiplayer" + }, + "users": + { + "title": "Users", + "user_list": "List of Users", + "change_password": + { + "title": "Change Password", + "update_successful": "Password changed", + "old_password": "Old Password", + "old_password_error": "Old Password is required", + "new_password": "New Password", + "new_password_error": "New Password is required", + "new_password_confirmation": "New Password Confirmation", + "new_password_confirmation_error": "New Password Confirmation is required" + }, + "create_user": + { + "title": "Create User", + "username_error": "Username is required", + "role_error": "Role is required", + "email_error": "Email is required", + "password_error": "Password is required", + "password_confirmation": "Password Confirmation", + "password_confirmation_error": "Password Confirmation is required and must match the Password" + } + }, + "game_settings": + { + "title": "Game Settings" + }, + "lang": "Language" +} diff --git a/ui/App/locales/ru.json b/ui/App/locales/ru.json new file mode 100644 index 00000000..fd7a0126 --- /dev/null +++ b/ui/App/locales/ru.json @@ -0,0 +1,213 @@ +{ + "save": "Сохранить", + "upload": "Загрузить", + "saved": "Настройки сохранены.", + "create": "Создать", + "cancel": "Отмена", + "confirm": "Подтвердить", + "name": "Имя", + "username": "Имя пользователя", + "password": "Пароль", + "logout": "Выйти", + "install": "Установить", + "actions": "Действия", + "role": "Роль", + "email": "Email", + "change": "Изменить", + "load": "Загрузить", + "server_status": "Состояние сервера", + "server_management": "Управление сервером", + "FSM_administration": "Администрирование FSM", + "main_title": "Factorio Server Manager", + "server_settings": + { + "title": "Настройки сервера", + "admins": "Админы", + "name": "Название", + "description": "Описание", + "tags": "Теги", + "_comment_max_players": "Максимальное количество игроков. Администраторы могут присоединиться даже к полному серверу. 0 означает без ограничений.", + "max_players": "Максимум игроков", + "_comment_visibility": "Public: Игра будет опубликована на официальном сервере подбора Factorio. Lan: Игра будет транслироваться в локальной сети", + "visibility_____": + { + "public_": "Публичный", + "lan_": "Локальная сеть" + }, + "visibility": "Видимость", + "_comment_credentials": "Ваши учётные данные для входа на factorio.com. Требуется для игр с публичной видимостью", + "username": "Имя пользователя", + "password": "Пароль", + "_comment_token": "Токен аутентификации. Может использоваться вместо 'пароля' выше.", + "token": "Токен", + "game_password": "Пароль игры", + "_comment_require_user_verification": "Если установлено значение true, сервер будет разрешать подключение только клиентам с действующей учётной записью Factorio.com", + "require_user_verification": "Требуется проверка пользователя", + "_comment_max_upload_in_kilobytes_per_second": "Необязательно, значение по умолчанию — 0. 0 означает без ограничений.", + "max_upload_in_kilobytes_per_second": "Максимальная загрузка в килобайтах в секунду", + "_comment_max_upload_slots": "Необязательно, значение по умолчанию — 5. 0 означает без ограничений.", + "max_upload_slots": "Максимальное количество слотов загрузки", + "_comment_minimum_latency_in_ticks": "Необязательно, один тик — это 16 мс при стандартной скорости, значение по умолчанию — 0. 0 означает отсутствие минимума.", + "minimum_latency_in_ticks": "Минимальная задержка в тиках", + "_comment_max_heartbeats_per_second": "Сетевая частота тиков. Максимальная частота, с которой пакеты обновлений игры отправляются перед их объединением. Минимальное значение — 6, максимальное — 240.", + "max_heartbeats_per_second": "Максимум сердечных сокращений в секунду", + "_comment_ignore_player_limit_for_returning_players": "Игроки, которые уже играли на этой карте, могут присоединиться даже при достижении лимита игроков.", + "ignore_player_limit_for_returning_players": "Игнорировать лимит игроков для возвращающихся игроков", + "_comment_allow_commands": "Возможные значения: true, false и только-админы", + "allow_commands": "Разрешить команды", + "_comment_autosave_interval": "Интервал автосохранения в минутах", + "autosave_interval": "Интервал автосохранения", + "_comment_autosave_slots": "Слоты автосохранения сервера, циклически используемые при автосохранении.", + "autosave_slots": "Слоты автосохранения", + "_comment_afk_autokick_interval": "Сколько минут до кика игрока за бездействие, 0 — никогда.", + "afk_autokick_interval": "Интервал автокика за AFK", + "_comment_auto_pause": "Следует ли ставить игру на паузу, когда нет игроков.", + "auto_pause": "Автопауза", + "_comment_auto_pause_when_players_connect": "Следует ли ставить игру на паузу, когда игрок подключается к серверу.", + "auto_pause_when_players_connect": "Автопауза при подключении игроков", + "only_admins_can_pause_the_game": "Только админы могут ставить игру на паузу", + "_comment_autosave_only_on_server": "Следует ли сохранять автосохранения только на сервере или также на всех подключённых клиентах. По умолчанию — true.", + "autosave_only_on_server": "Автосохранение только на сервере", + "_comment_non_blocking_saving": "Экспериментальная функция, включайте на свой страх и риск потери сохранений. На UNIX-системах сервер будет форкаться для создания автосохранения. Автосохранение на подключённых Windows-клиентах будет отключено независимо от опции autosave_only_on_server.", + "non_blocking_saving": "Неблокирующее сохранение", + "_comment_segment_sizes": "Длинные сетевые сообщения разбиваются на сегменты, которые отправляются за несколько тиков. Их размер зависит от количества подключённых пиров. Увеличение размера сегмента увеличит требования к пропускной способности загрузки для сервера и скачивания для клиентов. Эта настройка влияет только на исходящие сообщения сервера. Изменение этих настроек может негативно сказаться на стабильности соединения для некоторых клиентов.", + "minimum_segment_size": "Минимальный размер сегмента", + "minimum_segment_size_peer_count": "Количество пиров для минимального размера сегмента", + "maximum_segment_size": "Максимальный размер сегмента", + "maximum_segment_size_peer_count": "Количество пиров для максимального размера сегмента" + }, + "saves": + { + "create_save": "Создать сохранение", + "upload_save": "Загрузить сохранение", + "title": "Сохранения", + "last_modified": "Последние изменение", + "size": "Вес", + "actions": "Действия", + "create_new_save_only_when_server_not_running": "Создать новый сохранённый файл возможно только если сервер Factorio не запущен.", + "save_form": + { + "file_name": "Имя файла", + "save_file_error": "Имя файла обязательное поле", + "create_save": "Создать сохранение" + }, + "upload_form": + { + "select_file": "Выберите сохранение ...", + "file_name": "Файл сохранения", + "save_file_error": "Файл сохранения обязателен", + "upload": "Загрузить" + } + }, + "controls": + { + "title": "Статус сервера", + "status": "Статус", + "running": "Запущен", + "stopped": "Остановлен", + "port": "Порт", + "IP_error_message": "Требуется IP-адрес, который должен быть действительным.", + "port_error_message": "Требуется порт в диапазоне 1-65535", + "save_error_message": "Сохранение обязательно и должно быть действительным.", + "f_version": "Версия Factorio", + "save": "Сохранить", + "save&stop": "Сохранить и остановить сервер", + "kill_server": "Убить сервер", + "start_server": "Запустить сервер" + }, + "mods": + { + "change_mods_while_running_error_message": "Изменение модов отключено, пока сервер работает!", + "install_mod": "Установить мод", + "upload_mod": "Загрузить мод", + "load_mods": "Загрузить моды из сохранения", + "title": "Моды", + "delete_all": "Удалить все моды", + "update_all": "Обновить все моды", + "download_all": "Скачать все моды", + "mod_packs": "Паки модов", + "select_file": "Выбрать файл...", + "mods_loaded_from_save": "Моды загружены из файла сохранения @@@.", + "load_mods_from_save": "Загрузить моды из сохранения", + "load_confirm_dialog": "Загрузка модов из сохранения @@@ удалит все текущие установленные моды.", + "add_modpack_with_current_mods": "Добавить пак модов с текущими установленными модами", + "add_mod": + { + "loading_mod_list": "Загрузка списка модов из", + "version": "Версия", + "compatibility": "Совместимость" + }, + "mod_list": + { + "enabled": "Включён", + "compatibility": "Совместимость", + "mod_version": "Версия мода", + "factorio_version": "Версия Factorio" + }, + "mod_pack": + { + "load_modpack": "Загрузить пак модов", + "load_confirm_dialog": "Загрузка пака модов @@@ удалит все установленные моды." + } + }, + "login": + { + "login_failed_message": "Ошибка входа. Неверное имя пользователя или пароль.", + "title": "Вход", + "login": "Вход", + "username_error_message": "Требуется имя пользователя", + "password_error_message": "Требуется пароль", + "sing_in": "Войти", + "factorio_login_error_message": "Указанные имя пользователя, email или пароль не соответствуют ни одному аккаунту." + }, + "console": + { + "title": "Консоль", + "error": "Консоль недоступна, так как Factorio не запущен." + }, + "logs": + { + "title": "Логи" + }, + "help": + { + "title": "Помощь", + "fsm": "Менеджер серверов Factorio", + "fsm_content": "Factorio Server Manager (FSM) — проект с открытым исходным кодом и не связан с игрой Factorio или компанией Wube Software.", + "bugs_help": "Сообщения об ошибках и помощь", + "bugs_help_content": "Пожалуйста, используйте <0>репозиторий GitHub для сообщения об ошибках или запроса помощи.", + "helpful_resources": "Полезные ресурсы", + "factorio_link_text": "Официальная вики Factorio о многопользовательской игре" + }, + "users": + { + "title": "Пользователи", + "user_list": "Список пользователей", + "change_password": + { + "title": "Изменить пароль", + "update_successful": "Пароль изменён", + "old_password": "Старый пароль", + "old_password_error": "Требуется старый пароль", + "new_password": "Новый пароль", + "new_password_error": "Требуется новый пароль", + "new_password_confirmation": "Подтверждение нового пароля", + "new_password_confirmation_error": "Требуется подтверждение нового пароля" + }, + "create_user": + { + "title": "Создать пользователя", + "username_error": "Требуется имя пользователя", + "role_error": "Требуется роль", + "email_error": "Требуется email", + "password_error": "Требуется пароль", + "password_confirmation": "Подтверждение пароля", + "password_confirmation_error": "Требуется подтверждение пароля, и оно должно совпадать с паролем" + } + }, + "game_settings": + { + "title": "Настройки игры" + }, + "lang": "Язык" +} diff --git a/ui/App/views/Console.jsx b/ui/App/views/Console.jsx index 16d633f4..4b31ad2e 100644 --- a/ui/App/views/Console.jsx +++ b/ui/App/views/Console.jsx @@ -1,9 +1,12 @@ import Panel from "../components/Panel"; import React, {useEffect, useRef, useState} from "react"; import socket from "../../api/socket"; +import { useTranslation } from "react-i18next"; const Console = ({serverStatus}) => { + const { t, i18n } = useTranslation(); + const [logs, setLogs] = useState([]); const consoleInput = useRef(null); @@ -25,7 +28,7 @@ const Console = ({serverStatus}) => { return ( @@ -44,7 +47,7 @@ const Console = ({serverStatus}) => { /> :

- The console is not available, because Factorio is not running. + {t("console.error")}

} /> diff --git a/ui/App/views/Controls.jsx b/ui/App/views/Controls.jsx index 62b31d9e..be0e4171 100644 --- a/ui/App/views/Controls.jsx +++ b/ui/App/views/Controls.jsx @@ -7,10 +7,13 @@ import {useForm} from "react-hook-form"; import Select from "../components/Select"; import Input from "../components/Input"; import Error from "../components/Error"; +import { useTranslation } from "react-i18next"; const Controls = ({serverStatus}) => { - const factorioVersion = serverStatus.fac_version ? serverStatus.fac_version : 'Unknown'; + const { t, i18n } = useTranslation(); + + const factorioVersion = serverStatus.fac_version ? serverStatus.fac_version : "Unknown"; const [saves, setSaves] = useState([]); const [isDisabled, setIsDisabled] = useState(true); const [isStopping, setIsStopping] = useState(false); @@ -48,75 +51,75 @@ const Controls = ({serverStatus}) => { return (
{ serverStatus.running ? <>
-
Status
-
{serverStatus.running ? 'Running' : 'Stopped'}
+
{t("controls.status")}
+
{serverStatus.running ? "Running" : "Stopped"}
IP
{serverStatus.bindip}
-
Port
+
{t("controls.port")}
{serverStatus.port}
-
Factorio Version
+
{t("controls.f_version")}
{factorioVersion}
-
Save
+
{t("controls.save")}
{serverStatus.savefile}
: <>
-
Status
-
{serverStatus.running ? 'Running' : 'Stopped'}
+
{t("controls.status")}
+
{serverStatus.running ? t("controls.running") : t("controls.stopped")}
IP
- +
-
Port
+
{t("controls.port")}
- +
-
Factorio Version
+
{t("controls.f_version")}
{factorioVersion}
-
Save
+
{t("save")}
- +
-
- +
} diff --git a/ui/App/views/Logs.jsx b/ui/App/views/Logs.jsx index e416402a..096e44da 100644 --- a/ui/App/views/Logs.jsx +++ b/ui/App/views/Logs.jsx @@ -1,9 +1,12 @@ import React, {useEffect, useState} from "react"; import log from "../../api/resources/log"; import Panel from "../components/Panel"; +import { useTranslation } from "react-i18next"; const Logs = () => { + const { t, i18n } = useTranslation(); + const [logs, setLogs] = useState([]) useEffect(() => { @@ -15,7 +18,7 @@ const Logs = () => { return ( {logs.map((log,index) => (
  • {log}
  • ))} diff --git a/ui/App/views/Mods/Mods.jsx b/ui/App/views/Mods/Mods.jsx index 6376ae0a..de579c05 100644 --- a/ui/App/views/Mods/Mods.jsx +++ b/ui/App/views/Mods/Mods.jsx @@ -12,9 +12,12 @@ import Fuse from "fuse.js"; import CreateModPack from "./components/CreateModPack"; import ModPack from "./components/ModPack"; import ModList from "./components/ModList"; +import { useTranslation } from "react-i18next"; const Mods = ({serverStatus}) => { + const { t, i18n } = useTranslation(); + const [installedMods, setInstalledMods] = useState([]); const [modPacks, setModPacks] = useState([]) const [factorioVersion, setFactorioVersion] = useState(null); @@ -111,25 +114,25 @@ const Mods = ({serverStatus}) => { - Changing mods is disabled while the server is running! + {t("mods.change_mods_while_running_error_message")}
    } /> : - + - + - + } { { !disabled && && + onClick={deleteAllMods}>{t("mods.delete_all")} && + onClick={updateAllMods}>{t("mods.update_all")} } Download all Mods + href={modsResource.downloadAllURL}>{t("mods.download_all")} } /> { return Mod @@ -16,6 +17,8 @@ const LinkModPortal = () => { const AddModForm = ({setIsFactorioAuthenticated, fuse, refetchInstalledMods}) => { + const { t, i18n } = useTranslation(); + const {register, watch, setValue, handleSubmit} = useForm(); const [suggestedMods, setSuggestedMods] = useState([]); const [selectedMod, setSelectedMod] = useState(null); @@ -105,7 +108,7 @@ const AddModForm = ({setIsFactorioAuthenticated, fuse, refetchInstalledMods}) => { typeof fuse !== "undefined" ? :
    - Loading List of Mods from + {t("mods.add_mod.loading_mod_list")}
    } {suggestedMods.length > 0 && @@ -114,8 +117,8 @@ const AddModForm = ({setIsFactorioAuthenticated, fuse, refetchInstalledMods}) => }
    - - + + ) diff --git a/ui/App/views/Mods/components/AddMod/components/FactorioLogin.jsx b/ui/App/views/Mods/components/AddMod/components/FactorioLogin.jsx index 7450333e..9ce23824 100644 --- a/ui/App/views/Mods/components/AddMod/components/FactorioLogin.jsx +++ b/ui/App/views/Mods/components/AddMod/components/FactorioLogin.jsx @@ -4,9 +4,12 @@ import Input from "../../../../../components/Input"; import Label from "../../../../../components/Label"; import Button from "../../../../../components/Button"; import modsResource from "../../../../../../api/resources/mods"; +import { useTranslation } from "react-i18next"; const FactorioLogin = ({setIsFactorioAuthenticated}) => { + const { t, i18n } = useTranslation(); + const {register, handleSubmit} = useForm(); const [isLoading, setIsLoading] = useState(false); @@ -16,7 +19,7 @@ const FactorioLogin = ({setIsFactorioAuthenticated}) => { .then(res => { setIsFactorioAuthenticated(true) }) - .catch(() => window.flash("Given username or email and password do not match any account.", "red")) + .catch(() => window.flash(t("login.factorio_login_error_message"), "red")) .finally(() => setIsLoading(false)); } @@ -24,15 +27,15 @@ const FactorioLogin = ({setIsFactorioAuthenticated}) => {
    -
    -
    - +
    ) } diff --git a/ui/App/views/Mods/components/AddMod/components/SelectVersionForm.jsx b/ui/App/views/Mods/components/AddMod/components/SelectVersionForm.jsx index dd2815f9..9cfee030 100644 --- a/ui/App/views/Mods/components/AddMod/components/SelectVersionForm.jsx +++ b/ui/App/views/Mods/components/AddMod/components/SelectVersionForm.jsx @@ -5,9 +5,12 @@ import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; import {faCloudDownloadAlt} from "@fortawesome/free-solid-svg-icons/faCloudDownloadAlt"; import {faCheck} from "@fortawesome/free-solid-svg-icons/faCheck"; import {faTimes} from "@fortawesome/free-solid-svg-icons/faTimes"; +import { useTranslation } from "react-i18next"; const SelectVersionForm = ({releases, isOpen, close, install}) => { + const { t, i18n } = useTranslation(); + const download = release => { install(release) close() @@ -22,9 +25,9 @@ const SelectVersionForm = ({releases, isOpen, close, install}) => { - - - + + + @@ -45,7 +48,7 @@ const SelectVersionForm = ({releases, isOpen, close, install}) => { } actions={ - + } /> ) diff --git a/ui/App/views/Mods/components/CreateModPack.jsx b/ui/App/views/Mods/components/CreateModPack.jsx index 94262200..e9757a87 100644 --- a/ui/App/views/Mods/components/CreateModPack.jsx +++ b/ui/App/views/Mods/components/CreateModPack.jsx @@ -5,9 +5,12 @@ import Label from "../../../components/Label"; import Input from "../../../components/Input"; import {useForm} from "react-hook-form"; import modsResource from "../../../../api/resources/mods"; +import { useTranslation } from "react-i18next"; const CreateModPack = ({onSuccess}) => { + const { t, i18n } = useTranslation(); + const [isCreating, setIsCreating] = useState(false); const [isOpen, setIsOpen] = useState(false); @@ -26,18 +29,18 @@ const CreateModPack = ({onSuccess}) => { } return <> - +
    -
    - + } actions={ - + } /> diff --git a/ui/App/views/Mods/components/LoadMods.jsx b/ui/App/views/Mods/components/LoadMods.jsx index 331c3527..806f5158 100644 --- a/ui/App/views/Mods/components/LoadMods.jsx +++ b/ui/App/views/Mods/components/LoadMods.jsx @@ -8,9 +8,11 @@ import modsResource from "../../../../api/resources/mods"; import modResource from "../../../../api/resources/mods"; import FactorioLogin from "./AddMod/components/FactorioLogin"; import ConfirmDialog from "../../../components/ConfirmDialog"; +import { useTranslation } from "react-i18next"; const LoadMods = ({refreshMods}) => { + const { t, i18n } = useTranslation(); const [saves, setSaves] = useState([]); const {register, reset, handleSubmit} = useForm(); const [isLoading, setIsLoading] = useState(false); @@ -46,7 +48,8 @@ const LoadMods = ({refreshMods}) => { await modResource.portal.installMultiple(mods) .then(() => { refreshMods(); - window.flash(`Mods are loaded from save file ${data.save}.`, "green"); + // window.flash(`Mods are loaded from save file ${data.save}.`, "green"); + window.flash(t("mods_loaded_from_save").replace("@@@", data.save), "green"); }).finally(() => { setIsLoading(false); setLoadModsData(undefined); @@ -55,7 +58,7 @@ const LoadMods = ({refreshMods}) => { return isFactorioAuthenticated ?
    -
    VersionCompatibilityActions{t("mods.add_mod.version")}{t("mods.add_mod.vompatibility")}{t("actions")}
    - - - - - + + + + + diff --git a/ui/App/views/Mods/components/ModPack.jsx b/ui/App/views/Mods/components/ModPack.jsx index 1b8bca0a..374a02e6 100644 --- a/ui/App/views/Mods/components/ModPack.jsx +++ b/ui/App/views/Mods/components/ModPack.jsx @@ -4,9 +4,12 @@ import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; import modsResource from "../../../../api/resources/mods"; import ModList from "./ModList"; import ConfirmDialog from "../../../components/ConfirmDialog"; +import { useTranslation } from "react-i18next"; const ModPack = ({modPack, reloadModPacks, factorioVersion, reloadMods, disabled = false}) => { + const { t, i18n } = useTranslation(); + const [isLoading, setIsLoading] = useState(false); const [isLoadModPackDialogOpen, setIsLoadModPackDialogOpen] = useState(false); @@ -63,8 +66,8 @@ const ModPack = ({modPack, reloadModPacks, factorioVersion, reloadMods, disabled icon={isLoading ? faSpinner : faUpload} /> setIsLoadModPackDialogOpen(false)} onSuccess={() => loadModPack(modPack.name)} diff --git a/ui/App/views/Mods/components/UploadMod.jsx b/ui/App/views/Mods/components/UploadMod.jsx index f1083508..fec15fbe 100644 --- a/ui/App/views/Mods/components/UploadMod.jsx +++ b/ui/App/views/Mods/components/UploadMod.jsx @@ -3,10 +3,13 @@ import Button from "../../../components/Button"; import Label from "../../../components/Label"; import {useForm} from "react-hook-form"; import modsResource from "../../../../api/resources/mods"; +import { useTranslation } from "react-i18next"; const UploadMod = ({refetchInstalledMods}) => { - const defaultFileName = 'Select File ...' + const { t, i18n } = useTranslation(); + + const defaultFileName = t("mods.select_file") const [fileName, setFileName] = useState(defaultFileName); const {register, handleSubmit} = useForm(); const [isUploading, setIsUploading] = useState(false); @@ -24,7 +27,7 @@ const UploadMod = ({refetchInstalledMods}) => { return ( -
    NameEnabledCompatibilityMod VersionFactorio Version{t("name")}{t("mods.mod_list.enabled")}{t("mods.mod_list.compatibility")}{t("mods.mod_list.mod_version")}{t("mods.mod_list.factorio_version")}
    - - - - + + + + diff --git a/ui/App/views/Saves/components/CreateSaveForm.jsx b/ui/App/views/Saves/components/CreateSaveForm.jsx index 10b75fb9..5d0c71bc 100644 --- a/ui/App/views/Saves/components/CreateSaveForm.jsx +++ b/ui/App/views/Saves/components/CreateSaveForm.jsx @@ -5,11 +5,15 @@ import saves from "../../../../api/resources/saves"; import Label from "../../../components/Label"; import Input from "../../../components/Input"; import Error from "../../../components/Error"; +import { useTranslation } from "react-i18next"; const CreateSaveForm = ({onSuccess}) => { const {register, handleSubmit, formState: {errors}} = useForm(); const [isLoading, setIsLoading] = useState(false); + const { t, i18n } = useTranslation(); + + const onSubmit = async (data, e) => { setIsLoading(true) saves.create(data.savefile) @@ -23,11 +27,11 @@ const CreateSaveForm = ({onSuccess}) => { return (
    -
    - + ) } diff --git a/ui/App/views/Saves/components/UploadSaveForm.jsx b/ui/App/views/Saves/components/UploadSaveForm.jsx index 1db27160..58d67260 100644 --- a/ui/App/views/Saves/components/UploadSaveForm.jsx +++ b/ui/App/views/Saves/components/UploadSaveForm.jsx @@ -3,11 +3,13 @@ import React, {useState} from "react"; import {useForm} from "react-hook-form"; import saves from "../../../../api/resources/saves"; import Error from "../../../components/Error"; +import { useTranslation } from "react-i18next"; const UploadSaveForm = ({onSuccess}) => { + const { t, i18n } = useTranslation(); const {register, handleSubmit, formState: {errors}} = useForm(); - const [fileName, setFileName] = useState('Select File ...'); + const [fileName, setFileName] = useState(t("saves.upload_form.select_file")); const onSubmit = (data, e) => { saves.upload(data.savefile[0]).then(_ => { @@ -20,19 +22,19 @@ const UploadSaveForm = ({onSuccess}) => {
    setFileName(e.currentTarget.files[0].name)} type="file"/>
    {fileName}
    - +
    - + ) } diff --git a/ui/App/views/ServerSettings.jsx b/ui/App/views/ServerSettings.jsx index 754feef8..aa7eaf23 100644 --- a/ui/App/views/ServerSettings.jsx +++ b/ui/App/views/ServerSettings.jsx @@ -7,9 +7,12 @@ import Checkbox from "../components/Checkbox"; import InputPassword from "../components/InputPassword"; import Button from "../components/Button"; import {useForm} from "react-hook-form"; +import { useTranslation } from "react-i18next"; const ServerSettings = () => { + const { t, i18n } = useTranslation(); + const [settings, setSettings] = useState(); const [numberInputs, setNumberInputs] = useState([]); @@ -36,7 +39,7 @@ const ServerSettings = () => { settingsResource.server.update(data) .then(() => { fetchSettings() - .then(() => window.flash("Settings saved.", "green")) + .then(() => window.flash(t("saved"), "green")) }); } @@ -101,7 +104,7 @@ const ServerSettings = () => { } else if (name.includes("visibility")) { return ( <> - - - - - + + + + @@ -57,12 +60,12 @@ const UserManagement = () => { className="mb-4" /> } className="mb-4" /> } className="mb-4" /> diff --git a/ui/App/views/UserManagement/components/ChangePasswordForm.jsx b/ui/App/views/UserManagement/components/ChangePasswordForm.jsx index ca00c4d5..599516e6 100644 --- a/ui/App/views/UserManagement/components/ChangePasswordForm.jsx +++ b/ui/App/views/UserManagement/components/ChangePasswordForm.jsx @@ -5,8 +5,12 @@ import Button from "../../../components/Button"; import Label from "../../../components/Label"; import Input from "../../../components/Input"; import Error from "../../../components/Error"; +import { useTranslation } from "react-i18next"; const ChangePasswordForm = () => { + + const { t, i18n } = useTranslation(); + const {register, handleSubmit, reset, formState: {errors}, watch} = useForm(); const new_password = watch("new_password"); @@ -15,7 +19,7 @@ const ChangePasswordForm = () => { const res = await user.changePassword(data); if (res) { // Update successful - window.flash("Password changed", "green") + window.flash(t("users.change_password.update_successful"), "green") reset(); } } @@ -23,30 +27,30 @@ const ChangePasswordForm = () => { return (
    -
    -
    -
    - + ) } diff --git a/ui/App/views/UserManagement/components/CreateUserForm.jsx b/ui/App/views/UserManagement/components/CreateUserForm.jsx index ce3fb369..d1595aea 100644 --- a/ui/App/views/UserManagement/components/CreateUserForm.jsx +++ b/ui/App/views/UserManagement/components/CreateUserForm.jsx @@ -5,8 +5,12 @@ import Button from "../../../components/Button"; import Label from "../../../components/Label"; import Input from "../../../components/Input"; import Error from "../../../components/Error"; +import { useTranslation } from "react-i18next"; const CreateUserForm = ({updateUserList}) => { + + const { t, i18n } = useTranslation(); + const roleValue = "admin"; const { @@ -31,52 +35,52 @@ const CreateUserForm = ({updateUserList}) => { return (
    -
    -
    -
    -
    -
    - + ) } diff --git a/ui/index.js b/ui/index.js index 60afb9cd..2f4f8ffa 100644 --- a/ui/index.js +++ b/ui/index.js @@ -2,6 +2,7 @@ import regeneratorRuntime from "regenerator-runtime" import Bus from "./notifications" import React from 'react'; import ReactDOM from 'react-dom/client'; +import './App/i18n'; import App from './App/App.jsx'; window.flash = (message, color="gray-light") => Bus.emit('flash', ({message, color}));
    NameLast Modified AtSizeActions{t("name")}{t("saves.last_modified")}{t("saves.size")}{t("saves.actions")}
    NameRoleEmailActions{t("name")}{t("role")}{t("email")}{t("actions")}