diff --git a/package-lock.json b/package-lock.json index aed344f..37d36fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,8 +10,11 @@ "dependencies": { "@heroicons/react": "^1.0.6", "@tauri-apps/api": "^1.0.2", + "@tauri-apps/tauri-forage": "^1.0.0-beta.2", + "i18next": "^21.9.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-i18next": "^11.18.3", "react-router-dom": "^6.3.0" }, "devDependencies": { @@ -716,6 +719,22 @@ "node": ">= 10" } }, + "node_modules/@tauri-apps/tauri-forage": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/tauri-forage/-/tauri-forage-1.0.0-beta.2.tgz", + "integrity": "sha512-aGObWlY8sgEfImt8+Cv4D+hqWhWB4KwPLVRQTv2uPtHSrXpXrvkJ8vTjy+QsT7tGPyVhcLPBchbyGu0JReErSQ==", + "dependencies": { + "localforage": "^1.7.3", + "ramda": "^0.26.1", + "tweetnacl": "^1.0.1", + "tweetnacl-util": "^0.15.0" + }, + "engines": { + "node": ">= 10.18.1", + "npm": ">= 6.13.4", + "yarn": ">= 1.21.1" + } + }, "node_modules/@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", @@ -1606,6 +1625,41 @@ "@babel/runtime": "^7.7.6" } }, + "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==", + "dependencies": { + "void-elements": "3.1.0" + } + }, + "node_modules/i18next": { + "version": "21.9.0", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-21.9.0.tgz", + "integrity": "sha512-B+6/yd7rCpJidyPuBaEApUECx7G8Ai6+tqYhrChsY4MmQqJhG7qJ4eT6Lm1OnRhieVelEtfxh4aAQktdNVZtDA==", + "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" + } + ], + "dependencies": { + "@babel/runtime": "^7.17.2" + } + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -1689,6 +1743,14 @@ "node": ">=6" } }, + "node_modules/lie": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", + "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lilconfig": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz", @@ -1698,6 +1760,14 @@ "node": ">=10" } }, + "node_modules/localforage": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", + "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", + "dependencies": { + "lie": "3.1.1" + } + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -1992,6 +2062,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ramda": { + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.26.1.tgz", + "integrity": "sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ==" + }, "node_modules/react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", @@ -2015,6 +2090,27 @@ "react": "^18.2.0" } }, + "node_modules/react-i18next": { + "version": "11.18.3", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.18.3.tgz", + "integrity": "sha512-EttTX31HbqzZymUM3SIrMPuvamfSXFZVsDHm/ZAqoDfTLjhzlwyxqfbDNxcKNAGOi2mjZaXfR7hSNMlvLNpB/g==", + "dependencies": { + "@babel/runtime": "^7.14.5", + "html-parse-stringify": "^3.0.1" + }, + "peerDependencies": { + "i18next": ">= 19.0.0", + "react": ">= 16.8.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/react-refresh": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", @@ -2268,6 +2364,16 @@ "node": ">=8.0" } }, + "node_modules/tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" + }, + "node_modules/tweetnacl-util": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz", + "integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==" + }, "node_modules/update-browserslist-db": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.5.tgz", @@ -2341,6 +2447,14 @@ } } }, + "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==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -2819,6 +2933,17 @@ "dev": true, "optional": true }, + "@tauri-apps/tauri-forage": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/tauri-forage/-/tauri-forage-1.0.0-beta.2.tgz", + "integrity": "sha512-aGObWlY8sgEfImt8+Cv4D+hqWhWB4KwPLVRQTv2uPtHSrXpXrvkJ8vTjy+QsT7tGPyVhcLPBchbyGu0JReErSQ==", + "requires": { + "localforage": "^1.7.3", + "ramda": "^0.26.1", + "tweetnacl": "^1.0.1", + "tweetnacl-util": "^0.15.0" + } + }, "@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", @@ -3370,6 +3495,27 @@ "@babel/runtime": "^7.7.6" } }, + "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": "21.9.0", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-21.9.0.tgz", + "integrity": "sha512-B+6/yd7rCpJidyPuBaEApUECx7G8Ai6+tqYhrChsY4MmQqJhG7qJ4eT6Lm1OnRhieVelEtfxh4aAQktdNVZtDA==", + "requires": { + "@babel/runtime": "^7.17.2" + } + }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + }, "is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -3426,12 +3572,28 @@ "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", "dev": true }, + "lie": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", + "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", + "requires": { + "immediate": "~3.0.5" + } + }, "lilconfig": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz", "integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==", "dev": true }, + "localforage": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", + "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", + "requires": { + "lie": "3.1.1" + } + }, "loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -3609,6 +3771,11 @@ "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", "dev": true }, + "ramda": { + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.26.1.tgz", + "integrity": "sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ==" + }, "react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", @@ -3626,6 +3793,15 @@ "scheduler": "^0.23.0" } }, + "react-i18next": { + "version": "11.18.3", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.18.3.tgz", + "integrity": "sha512-EttTX31HbqzZymUM3SIrMPuvamfSXFZVsDHm/ZAqoDfTLjhzlwyxqfbDNxcKNAGOi2mjZaXfR7hSNMlvLNpB/g==", + "requires": { + "@babel/runtime": "^7.14.5", + "html-parse-stringify": "^3.0.1" + } + }, "react-refresh": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", @@ -3807,6 +3983,16 @@ "is-number": "^7.0.0" } }, + "tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" + }, + "tweetnacl-util": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz", + "integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==" + }, "update-browserslist-db": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.5.tgz", @@ -3836,6 +4022,11 @@ "rollup": "^2.75.6" } }, + "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==" + }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index 237f27c..8eea13a 100644 --- a/package.json +++ b/package.json @@ -12,8 +12,10 @@ "dependencies": { "@heroicons/react": "^1.0.6", "@tauri-apps/api": "^1.0.2", + "i18next": "^21.9.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-i18next": "^11.18.3", "react-router-dom": "^6.3.0" }, "devDependencies": { diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index 5e9aae8..87699c9 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -1,4 +1,6 @@ -use crate::AppData; +use std::sync::Mutex; + +use crate::{AppData, Language}; use reqwest::blocking::*; use tauri::{App, State}; @@ -26,3 +28,17 @@ pub fn check_auth(state: State) -> bool { } } } + +#[tauri::command] +pub fn change_lng(state: State>, lng: String) { + let mut state = state.lock().unwrap(); + *state = Language { lng }; +} + +#[tauri::command] +pub fn get_lng(state: State>) -> String { + match state.lock() { + Ok(val) => val.lng.clone(), + Err(_) => "ru".to_string(), + } +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 2e3ae48..f80d0d7 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -3,21 +3,30 @@ windows_subsystem = "windows" )] -use std::fs; +use std::{fs, sync::Mutex}; mod types; use types::*; mod commands; fn main() { - let options = fs::read_to_string("options.json").unwrap_or("{\"jwt\":null}".to_string()); + let options = { + let op = fs::read_to_string("options.json").unwrap_or("{\"jwt\":null}".to_string()); + serde_json::from_str::(&op).unwrap_or(AppData { + jwt: "null".to_string(), + lng: "en".to_string(), + }) + }; tauri::Builder::default() - .manage( - serde_json::from_str::(&options).unwrap_or(AppData { - jwt: "null".to_string(), - }), - ) - .invoke_handler(tauri::generate_handler![commands::check_auth]) + .manage(Mutex::new(Language { + lng: options.lng.clone(), + })) + .manage(options) + .invoke_handler(tauri::generate_handler![ + commands::check_auth, + commands::change_lng, + commands::get_lng + ]) .run(tauri::generate_context!()) .expect("error while running osma app"); } diff --git a/src-tauri/src/types.rs b/src-tauri/src/types.rs index a4ede8c..91f6705 100644 --- a/src-tauri/src/types.rs +++ b/src-tauri/src/types.rs @@ -1,6 +1,11 @@ use serde::Deserialize; -#[derive(Deserialize)] +#[derive(Deserialize, Clone)] pub struct AppData { pub jwt: String, + pub lng: String, +} + +pub struct Language { + pub lng: String, } diff --git a/src/App.jsx b/src/App.jsx index 5061083..e5ad50f 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -2,8 +2,29 @@ import "./components/menu"; import Menu from "./components/menu"; import { Routes, Route } from "react-router-dom"; import CheckAuth from "./pages/checkAuth"; +import { useTranslation } from "react-i18next"; +import { useEffect } from "react"; +import { invoke } from "@tauri-apps/api/tauri"; +let lngs = ["en", "ru"]; function App() { + const { i18n } = useTranslation(); + + let setLng = async () => { + let lng = await invoke("get_lng"); + if (lng == i18n.language) return; + i18n.changeLanguage(lng); + }; + + let changeLng = async (lang) => { + let lng = await invoke("change_lng", { lng: lang }); + i18n.changeLanguage(lang); + }; + + useEffect(() => { + setLng(); + }); + return (
@@ -24,7 +45,21 @@ function App() { /> Options page
} + element={ +
+ {lngs.map((val) => { + return ( + + ); + })} +
+ } /> diff --git a/src/components/button.jsx b/src/components/button.jsx index 8e15d7a..391362e 100644 --- a/src/components/button.jsx +++ b/src/components/button.jsx @@ -1,6 +1,6 @@ function Button(props) { return ( - +
{props.children}
diff --git a/src/components/menu.jsx b/src/components/menu.jsx index 60a5aa5..08eb085 100644 --- a/src/components/menu.jsx +++ b/src/components/menu.jsx @@ -5,20 +5,23 @@ import { ArrowCircleDownIcon, CogIcon, } from "@heroicons/react/outline"; +import { useTranslation } from "react-i18next"; function Menu() { + const { t } = useTranslation(); + return (
diff --git a/src/pages/regAuth.jsx b/src/functions/changeLng.js similarity index 100% rename from src/pages/regAuth.jsx rename to src/functions/changeLng.js diff --git a/src/i18n.js b/src/i18n.js new file mode 100644 index 0000000..06c8bbf --- /dev/null +++ b/src/i18n.js @@ -0,0 +1,12 @@ +import i18next from "i18next"; +import { initReactI18next } from "react-i18next"; +import { en, ru } from "./locales/all"; + +i18next.use(initReactI18next).init({ + fallbackLng: "en", + debug: true, + resources: { + en, + ru, + }, +}); diff --git a/src/locales/all.js b/src/locales/all.js new file mode 100644 index 0000000..6d73d30 --- /dev/null +++ b/src/locales/all.js @@ -0,0 +1,4 @@ +import en from "./en"; +import ru from "./ru"; + +export { en, ru }; diff --git a/src/locales/en.js b/src/locales/en.js new file mode 100644 index 0000000..74826a0 --- /dev/null +++ b/src/locales/en.js @@ -0,0 +1,10 @@ +const en = { + translation: { + main: "Main", + downloads: "Downloads", + library: "Library", + options: "Options", + }, +}; + +export default en; diff --git a/src/locales/ru.js b/src/locales/ru.js new file mode 100644 index 0000000..ebe48af --- /dev/null +++ b/src/locales/ru.js @@ -0,0 +1,10 @@ +const en = { + translation: { + main: "Главная", + downloads: "Загрузки", + library: "Библиотека", + options: "Настройки", + }, +}; + +export default en; diff --git a/src/main.jsx b/src/main.jsx index 9a01ed4..af5a978 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -3,6 +3,7 @@ import ReactDOM from "react-dom/client"; import App from "./App"; import "./index.css"; import { BrowserRouter } from "react-router-dom"; +import "./i18n"; ReactDOM.createRoot(document.getElementById("root")).render( diff --git a/src/pages/auth.jsx b/src/pages/auth.jsx new file mode 100644 index 0000000..e69de29