feat: i18n

This commit is contained in:
Artemy 2022-08-12 18:18:52 +03:00
parent 37c3c3f069
commit 0a1f979891
15 changed files with 314 additions and 16 deletions

191
package-lock.json generated
View file

@ -10,8 +10,11 @@
"dependencies": { "dependencies": {
"@heroicons/react": "^1.0.6", "@heroicons/react": "^1.0.6",
"@tauri-apps/api": "^1.0.2", "@tauri-apps/api": "^1.0.2",
"@tauri-apps/tauri-forage": "^1.0.0-beta.2",
"i18next": "^21.9.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-i18next": "^11.18.3",
"react-router-dom": "^6.3.0" "react-router-dom": "^6.3.0"
}, },
"devDependencies": { "devDependencies": {
@ -716,6 +719,22 @@
"node": ">= 10" "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": { "node_modules/@types/prop-types": {
"version": "15.7.5", "version": "15.7.5",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
@ -1606,6 +1625,41 @@
"@babel/runtime": "^7.7.6" "@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": { "node_modules/is-binary-path": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@ -1689,6 +1743,14 @@
"node": ">=6" "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": { "node_modules/lilconfig": {
"version": "2.0.6", "version": "2.0.6",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz",
@ -1698,6 +1760,14 @@
"node": ">=10" "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": { "node_modules/loose-envify": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@ -1992,6 +2062,11 @@
"url": "https://github.com/sponsors/sindresorhus" "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": { "node_modules/react": {
"version": "18.2.0", "version": "18.2.0",
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
@ -2015,6 +2090,27 @@
"react": "^18.2.0" "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": { "node_modules/react-refresh": {
"version": "0.14.0", "version": "0.14.0",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz",
@ -2268,6 +2364,16 @@
"node": ">=8.0" "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": { "node_modules/update-browserslist-db": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.5.tgz", "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": { "node_modules/xtend": {
"version": "4.0.2", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
@ -2819,6 +2933,17 @@
"dev": true, "dev": true,
"optional": 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": { "@types/prop-types": {
"version": "15.7.5", "version": "15.7.5",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
@ -3370,6 +3495,27 @@
"@babel/runtime": "^7.7.6" "@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": { "is-binary-path": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@ -3426,12 +3572,28 @@
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
"dev": true "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": { "lilconfig": {
"version": "2.0.6", "version": "2.0.6",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz",
"integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==", "integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==",
"dev": true "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": { "loose-envify": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@ -3609,6 +3771,11 @@
"integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
"dev": true "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": { "react": {
"version": "18.2.0", "version": "18.2.0",
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
@ -3626,6 +3793,15 @@
"scheduler": "^0.23.0" "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": { "react-refresh": {
"version": "0.14.0", "version": "0.14.0",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz",
@ -3807,6 +3983,16 @@
"is-number": "^7.0.0" "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": { "update-browserslist-db": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.5.tgz", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.5.tgz",
@ -3836,6 +4022,11 @@
"rollup": "^2.75.6" "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": { "xtend": {
"version": "4.0.2", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",

View file

@ -12,8 +12,10 @@
"dependencies": { "dependencies": {
"@heroicons/react": "^1.0.6", "@heroicons/react": "^1.0.6",
"@tauri-apps/api": "^1.0.2", "@tauri-apps/api": "^1.0.2",
"i18next": "^21.9.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-i18next": "^11.18.3",
"react-router-dom": "^6.3.0" "react-router-dom": "^6.3.0"
}, },
"devDependencies": { "devDependencies": {

View file

@ -1,4 +1,6 @@
use crate::AppData; use std::sync::Mutex;
use crate::{AppData, Language};
use reqwest::blocking::*; use reqwest::blocking::*;
use tauri::{App, State}; use tauri::{App, State};
@ -26,3 +28,17 @@ pub fn check_auth(state: State<AppData>) -> bool {
} }
} }
} }
#[tauri::command]
pub fn change_lng(state: State<Mutex<Language>>, lng: String) {
let mut state = state.lock().unwrap();
*state = Language { lng };
}
#[tauri::command]
pub fn get_lng(state: State<Mutex<Language>>) -> String {
match state.lock() {
Ok(val) => val.lng.clone(),
Err(_) => "ru".to_string(),
}
}

View file

@ -3,21 +3,30 @@
windows_subsystem = "windows" windows_subsystem = "windows"
)] )]
use std::fs; use std::{fs, sync::Mutex};
mod types; mod types;
use types::*; use types::*;
mod commands; mod commands;
fn main() { 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::<AppData>(&op).unwrap_or(AppData {
jwt: "null".to_string(),
lng: "en".to_string(),
})
};
tauri::Builder::default() tauri::Builder::default()
.manage( .manage(Mutex::new(Language {
serde_json::from_str::<AppData>(&options).unwrap_or(AppData { lng: options.lng.clone(),
jwt: "null".to_string(), }))
}), .manage(options)
) .invoke_handler(tauri::generate_handler![
.invoke_handler(tauri::generate_handler![commands::check_auth]) commands::check_auth,
commands::change_lng,
commands::get_lng
])
.run(tauri::generate_context!()) .run(tauri::generate_context!())
.expect("error while running osma app"); .expect("error while running osma app");
} }

View file

@ -1,6 +1,11 @@
use serde::Deserialize; use serde::Deserialize;
#[derive(Deserialize)] #[derive(Deserialize, Clone)]
pub struct AppData { pub struct AppData {
pub jwt: String, pub jwt: String,
pub lng: String,
}
pub struct Language {
pub lng: String,
} }

View file

@ -2,8 +2,29 @@ import "./components/menu";
import Menu from "./components/menu"; import Menu from "./components/menu";
import { Routes, Route } from "react-router-dom"; import { Routes, Route } from "react-router-dom";
import CheckAuth from "./pages/checkAuth"; 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() { 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 ( return (
<div className="grid grid-cols-1 lg:grid-cols-5 gap-10 text-black dark:text-white"> <div className="grid grid-cols-1 lg:grid-cols-5 gap-10 text-black dark:text-white">
<Menu /> <Menu />
@ -24,7 +45,21 @@ function App() {
/> />
<Route <Route
path="options" path="options"
element={<div className="col-span-4">Options page</div>} element={
<div className="col-span-4">
{lngs.map((val) => {
return (
<button
className="bg-zinc-500 p-5 m-5 rounded-3xl"
onClick={() => changeLng(val)}
key={val}
>
{val}
</button>
);
})}
</div>
}
/> />
</Routes> </Routes>
</div> </div>

View file

@ -1,6 +1,6 @@
function Button(props) { function Button(props) {
return ( return (
<a href={props.href} className={props.className}> <a href={props.href} className={props.className} onClick={props.onClick}>
<div className="transition-transform w-48 ease-[cubic-bezier(.69,.58,.32,1.69)] delay-60 hover:scale-105 p-2 pl-6 text-lg bg-zinc-100 hover:bg-zinc-300 dark:bg-zinc-600 dark:hover:bg-zinc-800 rounded-2xl"> <div className="transition-transform w-48 ease-[cubic-bezier(.69,.58,.32,1.69)] delay-60 hover:scale-105 p-2 pl-6 text-lg bg-zinc-100 hover:bg-zinc-300 dark:bg-zinc-600 dark:hover:bg-zinc-800 rounded-2xl">
{props.children} {props.children}
</div> </div>

View file

@ -5,20 +5,23 @@ import {
ArrowCircleDownIcon, ArrowCircleDownIcon,
CogIcon, CogIcon,
} from "@heroicons/react/outline"; } from "@heroicons/react/outline";
import { useTranslation } from "react-i18next";
function Menu() { function Menu() {
const { t } = useTranslation();
return ( return (
<div className="grid grid-cols-1 gap-3 m-4"> <div className="grid grid-cols-1 gap-3 m-4">
<Button href="/main"> <Button href="/main">
<IconWithButton icon={<MenuIcon className="h-7 w-7" />}> <IconWithButton icon={<MenuIcon className="h-7 w-7" />}>
Main {t("main")}
</IconWithButton> </IconWithButton>
</Button> </Button>
<Button href="/library"> <Button href="/library">
<IconWithButton <IconWithButton
icon={<ArchiveIcon className="transform translate-z-0 h-7 w-7" />} icon={<ArchiveIcon className="transform translate-z-0 h-7 w-7" />}
> >
Library {t("library")}
</IconWithButton> </IconWithButton>
</Button> </Button>
<Button href="/downloads"> <Button href="/downloads">
@ -27,14 +30,14 @@ function Menu() {
<ArrowCircleDownIcon className="transform translate-z-0 h-7 w-7" /> <ArrowCircleDownIcon className="transform translate-z-0 h-7 w-7" />
} }
> >
Downloads {t("downloads")}
</IconWithButton> </IconWithButton>
</Button> </Button>
<Button href="/options"> <Button href="/options">
<IconWithButton <IconWithButton
icon={<CogIcon className="transform translate-z-0 h-7 w-7" />} icon={<CogIcon className="transform translate-z-0 h-7 w-7" />}
> >
Options {t("options")}
</IconWithButton> </IconWithButton>
</Button> </Button>
</div> </div>

12
src/i18n.js Normal file
View file

@ -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,
},
});

4
src/locales/all.js Normal file
View file

@ -0,0 +1,4 @@
import en from "./en";
import ru from "./ru";
export { en, ru };

10
src/locales/en.js Normal file
View file

@ -0,0 +1,10 @@
const en = {
translation: {
main: "Main",
downloads: "Downloads",
library: "Library",
options: "Options",
},
};
export default en;

10
src/locales/ru.js Normal file
View file

@ -0,0 +1,10 @@
const en = {
translation: {
main: "Главная",
downloads: "Загрузки",
library: "Библиотека",
options: "Настройки",
},
};
export default en;

View file

@ -3,6 +3,7 @@ import ReactDOM from "react-dom/client";
import App from "./App"; import App from "./App";
import "./index.css"; import "./index.css";
import { BrowserRouter } from "react-router-dom"; import { BrowserRouter } from "react-router-dom";
import "./i18n";
ReactDOM.createRoot(document.getElementById("root")).render( ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode> <React.StrictMode>

0
src/pages/auth.jsx Normal file
View file