mirror of
https://github.com/BreizhHardware/Jellystat.git
synced 2026-01-18 16:27:20 +01:00
Added Localisation Framework
Converted Below pages to use localization: Navbar Home Page Libraries (Library Overview only) Rest is WIP
This commit is contained in:
135
package-lock.json
generated
135
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "jfstat",
|
||||
"version": "1.0.9",
|
||||
"version": "1.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "jfstat",
|
||||
"version": "1.0.9",
|
||||
"version": "1.1.0",
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.10.6",
|
||||
"@emotion/styled": "^11.10.6",
|
||||
@@ -34,6 +34,9 @@
|
||||
"file-saver": "^2.0.5",
|
||||
"github-api": "^3.4.0",
|
||||
"http-proxy-middleware": "^2.0.6",
|
||||
"i18next": "^23.8.2",
|
||||
"i18next-browser-languagedetector": "^7.2.0",
|
||||
"i18next-http-backend": "^2.4.3",
|
||||
"knex": "^2.4.2",
|
||||
"moment": "^2.29.4",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
@@ -47,6 +50,7 @@
|
||||
"react-bootstrap": "^2.7.2",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-i18next": "^14.0.5",
|
||||
"react-router-dom": "^6.8.1",
|
||||
"react-scripts": "5.0.1",
|
||||
"react-toastify": "^9.1.3",
|
||||
@@ -2322,9 +2326,9 @@
|
||||
"integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA=="
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.23.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz",
|
||||
"integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==",
|
||||
"version": "7.23.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz",
|
||||
"integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==",
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.14.0"
|
||||
},
|
||||
@@ -7998,6 +8002,14 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"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",
|
||||
@@ -11026,6 +11038,14 @@
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"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/html-webpack-plugin": {
|
||||
"version": "5.5.3",
|
||||
"resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.3.tgz",
|
||||
@@ -11202,6 +11222,44 @@
|
||||
"node": ">=10.17.0"
|
||||
}
|
||||
},
|
||||
"node_modules/i18next": {
|
||||
"version": "23.8.2",
|
||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-23.8.2.tgz",
|
||||
"integrity": "sha512-Z84zyEangrlERm0ZugVy4bIt485e/H8VecGUZkZWrH7BDePG6jT73QdL9EA1tRTTVVMpry/MgWIP1FjEn0DRXA==",
|
||||
"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.23.2"
|
||||
}
|
||||
},
|
||||
"node_modules/i18next-browser-languagedetector": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.2.0.tgz",
|
||||
"integrity": "sha512-U00DbDtFIYD3wkWsr2aVGfXGAj2TgnELzOX9qv8bT0aJtvPV9CRO77h+vgmHFBMe7LAxdwvT/7VkCWGya6L3tA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.2"
|
||||
}
|
||||
},
|
||||
"node_modules/i18next-http-backend": {
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-2.4.3.tgz",
|
||||
"integrity": "sha512-jo2M03O6n1/DNb51WSQ8PsQ0xEELzLZRdYUTbf17mLw3rVwnJF9hwNgMXvEFSxxb+N8dT+o0vtigA6s5mGWyPA==",
|
||||
"dependencies": {
|
||||
"cross-fetch": "4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
@@ -14813,6 +14871,44 @@
|
||||
"tslib": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"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-fetch/node_modules/tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||
},
|
||||
"node_modules/node-fetch/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=="
|
||||
},
|
||||
"node_modules/node-fetch/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==",
|
||||
"dependencies": {
|
||||
"tr46": "~0.0.3",
|
||||
"webidl-conversions": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-forge": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
|
||||
@@ -17853,6 +17949,27 @@
|
||||
"react": ">=16.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-i18next": {
|
||||
"version": "14.0.5",
|
||||
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-14.0.5.tgz",
|
||||
"integrity": "sha512-5+bQSeEtgJrMBABBL5lO7jPdSNAbeAZ+MlFWDw//7FnVacuVu3l9EeWFzBQvZsKy+cihkbThWOAThEdH8YjGEw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.9",
|
||||
"html-parse-stringify": "^3.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"i18next": ">= 23.2.3",
|
||||
"react": ">= 16.8.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"react-native": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "18.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
|
||||
@@ -21026,6 +21143,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/w3c-hr-time": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz",
|
||||
|
||||
@@ -41,6 +41,9 @@
|
||||
"file-saver": "^2.0.5",
|
||||
"github-api": "^3.4.0",
|
||||
"http-proxy-middleware": "^2.0.6",
|
||||
"i18next": "^23.8.2",
|
||||
"i18next-browser-languagedetector": "^7.2.0",
|
||||
"i18next-http-backend": "^2.4.3",
|
||||
"knex": "^2.4.2",
|
||||
"moment": "^2.29.4",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
@@ -54,6 +57,7 @@
|
||||
"react-bootstrap": "^2.7.2",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-i18next": "^14.0.5",
|
||||
"react-router-dom": "^6.8.1",
|
||||
"react-scripts": "5.0.1",
|
||||
"react-toastify": "^9.1.3",
|
||||
|
||||
72
public/locales/en/translation.json
Normal file
72
public/locales/en/translation.json
Normal file
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"JELLYSTAT": "Jellystat",
|
||||
"MENU_TABS": {
|
||||
"HOME": "Home",
|
||||
"LIBRARIES": "Libraries",
|
||||
"USERS": "Users",
|
||||
"ACTIVITY": "Activity",
|
||||
"STATISTICS": "Statistics",
|
||||
"SETTINGS": "Settings",
|
||||
"ABOUT": "About",
|
||||
"LOGOUT": "Logout"
|
||||
},
|
||||
"HOME_PAGE": {
|
||||
"SESSIONS": "Sessions",
|
||||
"RECENTLY_ADDED": "Recently Added",
|
||||
"WATCH_STATISTIC": "Watch Statistics",
|
||||
"LIBRARY_OVERVIEW": "Library Overview"
|
||||
},
|
||||
"SESSIONS":{
|
||||
"NO_SESSIONS": "No Active Sessions Found"
|
||||
},
|
||||
"STAT_CARDS": {
|
||||
"MOST_VIEWED_MOVIES": "MOST VIEWED MOVIES",
|
||||
"MOST_POPULAR_MOVIES": "MOST POPULAR MOVIES",
|
||||
"MOST_VIEWED_SERIES": "MOST VIEWED SERIES",
|
||||
"MOST_POPULAR_SERIES": "MOST POPULAR SERIES",
|
||||
"MOST_LISTENED_MUSIC": "MOST LISTENED MUSIC",
|
||||
"MOST_POPULAR_MUSIC": "MOST POPULAR MUSIC",
|
||||
"MOST_VIEWED_LIBRARIES": "MOST VIEWED LIBRARIES",
|
||||
"MOST_USED_CLIENTS": "MOST USED CLIENTS",
|
||||
"MOST_ACTIVE_USERS": "MOST ACTIVE USERS"
|
||||
},
|
||||
"LIBRARY_OVERVIEW": {
|
||||
"MOVIE_LIBRARIES": "MOVIE LIBRARIES",
|
||||
"SHOW_LIBRARIES": "SHOW LIBRARIES",
|
||||
"MUSIC_LIBRARIES": "MUSIC LIBRARIES",
|
||||
"MIXED_LIBRARIES": "MIXED LIBRARIES"
|
||||
},
|
||||
"LIBRARY_CARD": {
|
||||
"LIBRARY": "Library",
|
||||
"TOTAL_TIME": "Total Time",
|
||||
"TOTAL_FILES": "Total Files",
|
||||
"LIBRARY_SIZE": "Library Size",
|
||||
"TOTAL_PLAYS": "Total Plays",
|
||||
"TOTAL_PLAYBACK": "Total Playback",
|
||||
"LAST_PLAYED": "Last Played",
|
||||
"LAST_ACTIVITY": "Last Activity"
|
||||
},
|
||||
"SHOW_ARCHIVED_LIBRARIES":"Show Archived Libraries",
|
||||
"HIDE_ARCHIVED_LIBRARIES":"Hide Archived Libraries",
|
||||
"UNITS":{
|
||||
"DAYS": "Days",
|
||||
"HOURS": "Hours",
|
||||
"MINUTES": "Minutes",
|
||||
"SECONDS": "Seconds",
|
||||
"PLAYS": "Plays",
|
||||
"ITEMS": "Items"
|
||||
},
|
||||
"TOTAL": "Total",
|
||||
"LAST": "Last",
|
||||
"SERIES": "Series",
|
||||
"SEASONS": "Seasons",
|
||||
"EPISODES": "Episodes",
|
||||
"MOVIES": "Movies",
|
||||
"SONGS": "Songs",
|
||||
"FILES": "Files",
|
||||
"LIBRARIES": "Libraries",
|
||||
"USERS": "Users",
|
||||
"TYPE": "Type",
|
||||
"NEW_VERSION_AVAILABLE":"New version available"
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { Suspense } from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
|
||||
@@ -7,8 +7,12 @@ import App from './App.jsx';
|
||||
import './index.css';
|
||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||
|
||||
import './localization.js';
|
||||
import Loading from './pages/components/general/loading.jsx';
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||
<React.StrictMode>
|
||||
<Suspense fallback={ <Loading />}/>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
|
||||
@@ -1,53 +1,51 @@
|
||||
|
||||
|
||||
import HomeFillIcon from 'remixicon-react/HomeFillIcon';
|
||||
// import FileListFillIcon from 'remixicon-react/FileListFillIcon';
|
||||
import BarChartFillIcon from 'remixicon-react/BarChartFillIcon';
|
||||
import HistoryFillIcon from 'remixicon-react/HistoryFillIcon';
|
||||
import SettingsFillIcon from 'remixicon-react/SettingsFillIcon';
|
||||
import GalleryFillIcon from 'remixicon-react/GalleryFillIcon';
|
||||
import UserFillIcon from 'remixicon-react/UserFillIcon';
|
||||
import InformationFillIcon from 'remixicon-react/InformationFillIcon';
|
||||
import { Trans } from 'react-i18next';
|
||||
|
||||
|
||||
// import ReactjsFillIcon from 'remixicon-react/ReactjsFillIcon';
|
||||
|
||||
export const navData = [
|
||||
{
|
||||
id: 0,
|
||||
icon: <HomeFillIcon/>,
|
||||
text: "Home",
|
||||
text: <Trans i18nKey="MENU_TABS.HOME" />,
|
||||
link: ""
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
icon: <GalleryFillIcon />,
|
||||
text: "Libraries",
|
||||
text: <Trans i18nKey="MENU_TABS.LIBRARIES" />,
|
||||
link: "libraries"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
icon: <UserFillIcon />,
|
||||
text: "Users",
|
||||
text: <Trans i18nKey="MENU_TABS.USERS" />,
|
||||
link: "users"
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
icon: <HistoryFillIcon />,
|
||||
text: "Activity",
|
||||
text: <Trans i18nKey="MENU_TABS.ACTIVITY" />,
|
||||
link: "activity"
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
icon: <BarChartFillIcon />,
|
||||
text: "Statistics",
|
||||
text: <Trans i18nKey="MENU_TABS.STATISTICS" />,
|
||||
link: "statistics"
|
||||
},
|
||||
|
||||
{
|
||||
id: 6,
|
||||
icon: <SettingsFillIcon />,
|
||||
text: "Settings",
|
||||
text: <Trans i18nKey="MENU_TABS.SETTINGS" />,
|
||||
link: "settings"
|
||||
}
|
||||
,
|
||||
@@ -55,7 +53,7 @@ export const navData = [
|
||||
{
|
||||
id: 7,
|
||||
icon: <InformationFillIcon />,
|
||||
text: "About",
|
||||
text: <Trans i18nKey="MENU_TABS.ABOUT" />,
|
||||
link: "about"
|
||||
}
|
||||
|
||||
|
||||
18
src/localization.js
Normal file
18
src/localization.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import i18n from 'i18next'
|
||||
import Backend from 'i18next-http-backend'
|
||||
import LanguageDetector from 'i18next-browser-languagedetector'
|
||||
import { initReactI18next } from 'react-i18next'
|
||||
|
||||
i18n.use(Backend).use(LanguageDetector).use(initReactI18next).init({
|
||||
fallbackLng: 'en',
|
||||
debug: true,
|
||||
detection: {
|
||||
order: ['queryString', 'cookie'],
|
||||
cache: ['cookie']
|
||||
},
|
||||
interpolation: {
|
||||
escapeValue: false
|
||||
}
|
||||
})
|
||||
|
||||
export default i18n
|
||||
@@ -1,7 +1,4 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
import MVLibraries from "./statCards/mv_libraries";
|
||||
import MVMovies from "./statCards/mv_movies";
|
||||
@@ -14,6 +11,7 @@ import MVMusic from "./statCards/mv_music";
|
||||
import MPMusic from "./statCards/mp_music";
|
||||
|
||||
import "../css/statCard.css";
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
function HomeStatisticCards() {
|
||||
const [days, setDays] = useState(30);
|
||||
@@ -34,9 +32,9 @@ function HomeStatisticCards() {
|
||||
return (
|
||||
<div className="watch-stats">
|
||||
<div className="Heading my-3">
|
||||
<h1>Watch Statistics</h1>
|
||||
<h1><Trans i18nKey="HOME_PAGE.WATCH_STATISTIC" /></h1>
|
||||
<div className="date-range">
|
||||
<div className="header">Last</div>
|
||||
<div className="header"><Trans i18nKey="LAST" /></div>
|
||||
<div className="days">
|
||||
<input
|
||||
type="number"
|
||||
@@ -46,7 +44,7 @@ function HomeStatisticCards() {
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
</div>
|
||||
<div className="trailer">Days</div>
|
||||
<div className="trailer"><Trans i18nKey="UNITS.DAYS" /></div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import LogoutBoxLineIcon from "remixicon-react/LogoutBoxLineIcon";
|
||||
import logo_dark from '../../images/icon-b-512.png';
|
||||
import "../../css/navbar.css";
|
||||
import VersionCard from "./version-card";
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
export default function Navbar() {
|
||||
const handleLogout = () => {
|
||||
@@ -20,7 +21,7 @@ export default function Navbar() {
|
||||
<BootstrapNavbar.Brand as={Link} to={"/"} className="d-none d-md-inline">
|
||||
|
||||
<img src={logo_dark} style={{height:"52px"}} className="px-2" alt=''/>
|
||||
<span>Jellystat</span>
|
||||
<span><Trans i18nKey="JELLYSTAT" /></span>
|
||||
</BootstrapNavbar.Brand>
|
||||
|
||||
|
||||
@@ -42,7 +43,7 @@ export default function Navbar() {
|
||||
})}
|
||||
<Nav.Link className="navitem p-2 logout" href="#logout" onClick={handleLogout}>
|
||||
<LogoutBoxLineIcon />
|
||||
<span className="d-none d-md-block nav-text">Logout</span>
|
||||
<span className="d-none d-md-block nav-text"><Trans i18nKey="MENU_TABS.LOGOUT" /></span>
|
||||
</Nav.Link>
|
||||
</Nav>
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import Col from 'react-bootstrap/Col';
|
||||
|
||||
import "../../css/settings/version.css";
|
||||
import { Card } from "react-bootstrap";
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
export default function VersionCard() {
|
||||
|
||||
@@ -51,13 +52,13 @@ export default function VersionCard() {
|
||||
<Card className="d-none d-md-block version rounded-0 border-0" >
|
||||
<Card.Body>
|
||||
<Row>
|
||||
<Col>Jellystat {data.current_version}</Col>
|
||||
<Col><Trans i18nKey="JELLYSTAT"/> {data.current_version}</Col>
|
||||
</Row>
|
||||
|
||||
|
||||
{data.update_available?
|
||||
<Row>
|
||||
<Col ><a href="https://github.com/CyferShepard/Jellystat" target="_blank" rel="noreferrer" style={{color:'#00A4DC'}}>New version available: {data.latest_version}</a></Col>
|
||||
<Col ><a href="https://github.com/CyferShepard/Jellystat" target="_blank" rel="noreferrer" style={{color:'#00A4DC'}}><Trans i18nKey="NEW_VERSION_AVAILABLE"/>: {data.latest_version}</a></Col>
|
||||
</Row>
|
||||
:
|
||||
<></>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, {useState} from "react";
|
||||
import {useState} from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import "../../css/library/library-card.css";
|
||||
|
||||
@@ -10,6 +10,8 @@ import TvLineIcon from "remixicon-react/TvLineIcon";
|
||||
import FilmLineIcon from "remixicon-react/FilmLineIcon";
|
||||
import FileMusicLineIcon from "remixicon-react/FileMusicLineIcon";
|
||||
import CheckboxMultipleBlankLineIcon from "remixicon-react/CheckboxMultipleBlankLineIcon";
|
||||
import { Trans } from "react-i18next";
|
||||
import i18next from "i18next";
|
||||
|
||||
function LibraryCard(props) {
|
||||
const [imageLoaded, setImageLoaded] = useState(true);
|
||||
@@ -143,62 +145,62 @@ function LibraryCard(props) {
|
||||
|
||||
<Card.Body className="library-card-details">
|
||||
<Row className="space-between-end card-row">
|
||||
<Col className="card-label">Library</Col>
|
||||
<Col className="card-label"><Trans i18nKey="LIBRARY_CARD.LIBRARY" /></Col>
|
||||
<Col className="text-end">{props.data.Name}</Col>
|
||||
</Row>
|
||||
|
||||
<Row className="space-between-end card-row">
|
||||
<Col className="card-label">Type</Col>
|
||||
<Col className="card-label"><Trans i18nKey="TYPE" /></Col>
|
||||
<Col className="text-end">{props.data.CollectionType==='tvshows' ? 'Series' : props.data.CollectionType==='movies'? "Movies" : props.data.CollectionType==='music'? "Music" : 'Mixed'}</Col>
|
||||
</Row>
|
||||
|
||||
<Row className="space-between-end card-row">
|
||||
<Col className="card-label">Total Time</Col>
|
||||
<Col className="card-label"><Trans i18nKey="LIBRARY_CARD.TOTAL_TIME" /></Col>
|
||||
<Col className="text-end">{ticksToTimeString(props.data && props.data.total_play_time ? props.data.total_play_time:0)}</Col>
|
||||
</Row>
|
||||
|
||||
<Row className="space-between-end card-row">
|
||||
<Col className="card-label">Total Files</Col>
|
||||
<Col className="card-label"><Trans i18nKey="LIBRARY_CARD.TOTAL_FILES" /></Col>
|
||||
<Col className="text-end">{props.metadata && props.metadata.files ? props.metadata.files :0}</Col>
|
||||
</Row>
|
||||
|
||||
<Row className="space-between-end card-row">
|
||||
<Col className="card-label">Library Size</Col>
|
||||
<Col className="card-label"><Trans i18nKey="LIBRARY_CARD.LIBRARY_SIZE" /></Col>
|
||||
<Col className="text-end">{formatFileSize(props.metadata && props.metadata.Size ? props.metadata.Size:0)}</Col>
|
||||
</Row>
|
||||
|
||||
<Row className="space-between-end card-row">
|
||||
<Col className="card-label">Total Plays</Col>
|
||||
<Col className="card-label"><Trans i18nKey="LIBRARY_CARD.TOTAL_PLAYS" /></Col>
|
||||
<Col className="text-end">{props.data.Plays}</Col>
|
||||
</Row>
|
||||
|
||||
<Row className="space-between-end card-row">
|
||||
<Col className="card-label">Total Playback</Col>
|
||||
<Col className="card-label"><Trans i18nKey="LIBRARY_CARD.TOTAL_PLAYBACK" /></Col>
|
||||
<Col className="text-end">{formatTotalWatchTime(props.data.total_playback_duration)}</Col>
|
||||
</Row>
|
||||
|
||||
<Row className="space-between-end card-row">
|
||||
<Col className="card-label">Last Played</Col>
|
||||
<Col className="card-label"><Trans i18nKey="LIBRARY_CARD.LAST_PLAYED" /></Col>
|
||||
<Col className="text-end">{props.data.ItemName || 'n/a'}</Col>
|
||||
</Row>
|
||||
|
||||
<Row className="space-between-end card-row">
|
||||
<Col className="card-label">Last Activity</Col>
|
||||
<Col className="card-label"><Trans i18nKey="LIBRARY_CARD.LAST_ACTIVITY" /></Col>
|
||||
<Col className="text-end">{props.data.LastActivity ? formatLastActivityTime(props.data.LastActivity) : 'never'}</Col>
|
||||
</Row>
|
||||
|
||||
<Row className="space-between-end card-row">
|
||||
<Col className="card-label">{props.data.CollectionType==='tvshows' ? 'Series' : props.data.CollectionType==='movies'? "Movies" : props.data.CollectionType==='music'? "Songs" : 'Files'}</Col>
|
||||
<Col className="card-label">{props.data.CollectionType==='tvshows' ? i18next.t("SERIES") : props.data.CollectionType==='movies'? i18next.t("MOVIES") : props.data.CollectionType==='music'? i18next.t("SONGS") : i18next.t("FILES")}</Col>
|
||||
<Col className="text-end">{props.data.Library_Count}</Col>
|
||||
</Row>
|
||||
|
||||
<Row className="space-between-end card-row" style={{opacity:props.data.CollectionType==='tvshows' ? '1' :'0'}}>
|
||||
<Col className="card-label">Seasons</Col>
|
||||
<Col className="card-label"><Trans i18nKey="SEASONS" /></Col>
|
||||
<Col className="text-end">{props.data.CollectionType==='tvshows' ? props.data.Season_Count : ''}</Col>
|
||||
</Row>
|
||||
|
||||
<Row className="space-between-end card-row" style={{opacity:props.data.CollectionType==='tvshows' ? '1' :'0'}}>
|
||||
<Col className="card-label">Episodes</Col>
|
||||
<Col className="card-label"><Trans i18nKey="EPISODES" /></Col>
|
||||
<Col className="text-end">{props.data.CollectionType==='tvshows' ? props.data.Episode_Count : ''}</Col>
|
||||
</Row>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import "../css/libraryOverview.css";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import axios from "axios";
|
||||
import Loading from "./general/loading";
|
||||
|
||||
@@ -10,6 +10,8 @@ import TvLineIcon from "remixicon-react/TvLineIcon";
|
||||
import FilmLineIcon from "remixicon-react/FilmLineIcon";
|
||||
import FileMusicLineIcon from "remixicon-react/FileMusicLineIcon";
|
||||
import CheckboxMultipleBlankLineIcon from "remixicon-react/CheckboxMultipleBlankLineIcon";
|
||||
import { Trans } from "react-i18next";
|
||||
import i18next from "i18next";
|
||||
|
||||
export default function LibraryOverView() {
|
||||
const token = localStorage.getItem('token');
|
||||
@@ -45,13 +47,13 @@ export default function LibraryOverView() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 className="my-3">Library Overview</h1>
|
||||
<h1 className="my-3"><Trans i18nKey="HOME_PAGE.LIBRARY_OVERVIEW" /></h1>
|
||||
<div className="overview-container">
|
||||
|
||||
<LibraryStatComponent data={data.filter((stat) => stat.CollectionType === "movies")} heading={"MOVIE LIBRARIES"} units={"MOVIES"} icon={MovieIcon}/>
|
||||
<LibraryStatComponent data={data.filter((stat) => stat.CollectionType === "tvshows")} heading={"SHOW LIBRARIES"} units={"SERIES / SEASONS / EPISODES"} icon={SeriesIcon}/>
|
||||
<LibraryStatComponent data={data.filter((stat) => stat.CollectionType === "music")} heading={"MUSIC LIBRARIES"} units={"SONGS"} icon={MusicIcon}/>
|
||||
<LibraryStatComponent data={data.filter((stat) => stat.CollectionType === "mixed")} heading={"MIXED LIBRARIES"} units={"ITEMS"} icon={MixedIcon}/>
|
||||
<LibraryStatComponent data={data.filter((stat) => stat.CollectionType === "movies")} heading={<Trans i18nKey="LIBRARY_OVERVIEW.MOVIE_LIBRARIES" />} units={<Trans i18nKey="MOVIES" />} icon={MovieIcon}/>
|
||||
<LibraryStatComponent data={data.filter((stat) => stat.CollectionType === "tvshows")} heading={<Trans i18nKey="LIBRARY_OVERVIEW.SHOW_LIBRARIES" />} units={`${i18next.t("SERIES")} / ${i18next.t("SEASONS")} / ${i18next.t("EPISODES")}`} icon={SeriesIcon}/>
|
||||
<LibraryStatComponent data={data.filter((stat) => stat.CollectionType === "music")} heading={<Trans i18nKey="LIBRARY_OVERVIEW.MUSIC_LIBRARIES" />} units={<Trans i18nKey="SONGS" />} icon={MusicIcon}/>
|
||||
<LibraryStatComponent data={data.filter((stat) => stat.CollectionType === "mixed")} heading={<Trans i18nKey="LIBRARY_OVERVIEW.MIXED_LIBRARIES" />} units={<Trans i18nKey="UNITS.ITEMS" />} icon={MixedIcon}/>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Row, Col, Card } from "react-bootstrap";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import axios from '../../../lib/axios_instance';
|
||||
import Config from "../../../lib/config";
|
||||
// import API from "../../../classes/jellyfin-api";
|
||||
@@ -8,6 +8,7 @@ import ErrorBoundary from "../general/ErrorBoundary";
|
||||
import SessionCard from "./session-card";
|
||||
|
||||
import Loading from "../general/loading";
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
function Sessions() {
|
||||
const [data, setData] = useState();
|
||||
@@ -104,16 +105,17 @@ function Sessions() {
|
||||
if ((!data && config) || data.length === 0) {
|
||||
return(
|
||||
<div>
|
||||
<h1 className="my-3">Sessions</h1>
|
||||
|
||||
<h1 className="my-3"><Trans i18nKey="HOME_PAGE.SESSIONS" /></h1>
|
||||
<div style={{color:"grey", fontSize:"0.8em", fontStyle:"italic"}}>
|
||||
No Active Sessions Found
|
||||
<Trans i18nKey="SESSIONS.NO_SESSIONS" />
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 className="my-3">Sessions</h1>
|
||||
<h1 className="my-3"><Trans i18nKey="HOME_PAGE.SESSIONS" /></h1>
|
||||
<div className="sessions-container">
|
||||
{data && data.length>0 &&
|
||||
data
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import axios from "axios";
|
||||
import Config from "../../../lib/config";
|
||||
import ItemStatComponent from "./ItemStatComponent";
|
||||
|
||||
|
||||
import AccountCircleFillIcon from "remixicon-react/AccountCircleFillIcon";
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
function MostActiveUsers(props) {
|
||||
const [data, setData] = useState();
|
||||
@@ -81,7 +82,7 @@ function MostActiveUsers(props) {
|
||||
};
|
||||
|
||||
return (
|
||||
<ItemStatComponent icon={loaded ? <UserImage/> : <AccountCircleFillIcon size="100%" />} data={data} heading={"MOST ACTIVE USERS"} units={"Plays"}/>
|
||||
<ItemStatComponent icon={loaded ? <UserImage/> : <AccountCircleFillIcon size="100%" />} data={data} heading={<Trans i18nKey="STAT_CARDS.MOST_ACTIVE_USERS" />} units={<Trans i18nKey="UNITS.PLAYS" />}/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import axios from "axios";
|
||||
import ItemStatComponent from "./ItemStatComponent";
|
||||
|
||||
|
||||
|
||||
import ComputerLineIcon from "remixicon-react/ComputerLineIcon";
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
function MostUsedClient(props) {
|
||||
const [data, setData] = useState();
|
||||
@@ -52,7 +53,7 @@ function MostUsedClient(props) {
|
||||
|
||||
|
||||
return (
|
||||
<ItemStatComponent icon={ <ComputerLineIcon color="white" size={'100%'}/>} data={data} heading={"MOST USED CLIENTS"} units={"Plays"}/>
|
||||
<ItemStatComponent icon={ <ComputerLineIcon color="white" size={'100%'}/>} data={data} heading={<Trans i18nKey="STAT_CARDS.MOST_USED_CLIENTS" />} units={<Trans i18nKey="UNITS.PLAYS" />}/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import axios from "axios";
|
||||
import Config from "../../../lib/config";
|
||||
|
||||
@@ -6,6 +6,7 @@ import Config from "../../../lib/config";
|
||||
|
||||
import ItemStatComponent from "./ItemStatComponent";
|
||||
import Loading from "../general/loading";
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
function MPMovies(props) {
|
||||
const [data, setData] = useState();
|
||||
@@ -75,7 +76,7 @@ function MPMovies(props) {
|
||||
}
|
||||
|
||||
return (
|
||||
<ItemStatComponent base_url={config.hostUrl} data={data} heading={"MOST POPULAR MOVIES"} units={"Users"}/>
|
||||
<ItemStatComponent base_url={config.hostUrl} data={data} heading={<Trans i18nKey="STAT_CARDS.MOST_POPULAR_MOVIES" />} units={<Trans i18nKey="USERS" />}/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
import axios from "axios";
|
||||
import Config from "../../../lib/config";
|
||||
|
||||
import ItemStatComponent from "./ItemStatComponent";
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
|
||||
|
||||
@@ -69,7 +70,7 @@ function MPMusic(props) {
|
||||
|
||||
|
||||
return (
|
||||
<ItemStatComponent base_url={config.hostUrl} data={data} heading={"MOST POPULAR MUSIC"} units={"Users"} isAudio={true}/>
|
||||
<ItemStatComponent base_url={config.hostUrl} data={data} heading={<Trans i18nKey="STAT_CARDS.MOST_POPULAR_MUSIC" />} units={<Trans i18nKey="USERS" />} isAudio={true}/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import axios from "axios";
|
||||
import Config from "../../../lib/config";
|
||||
import ItemStatComponent from "./ItemStatComponent";
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
|
||||
function MPSeries(props) {
|
||||
@@ -66,7 +67,7 @@ function MPSeries(props) {
|
||||
|
||||
|
||||
return (
|
||||
<ItemStatComponent base_url={config.hostUrl} data={data} heading={"MOST POPULAR SERIES"} units={"Users"}/>
|
||||
<ItemStatComponent base_url={config.hostUrl} data={data} heading={<Trans i18nKey="STAT_CARDS.MOST_POPULAR_SERIES" />} units={<Trans i18nKey="USERS" />}/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import axios from "axios";
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import TvLineIcon from "remixicon-react/TvLineIcon";
|
||||
import FilmLineIcon from "remixicon-react/FilmLineIcon";
|
||||
import FileMusicLineIcon from "remixicon-react/FileMusicLineIcon";
|
||||
import CheckboxMultipleBlankLineIcon from "remixicon-react/CheckboxMultipleBlankLineIcon";
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
function MVLibraries(props) {
|
||||
const [data, setData] = useState();
|
||||
@@ -60,7 +61,7 @@ function MVLibraries(props) {
|
||||
|
||||
|
||||
return (
|
||||
<ItemStatComponent icon={data[0].CollectionType==="tvshows"? SeriesIcon: data[0].CollectionType==="movies"? MovieIcon : data[0].CollectionType==="music"? MusicIcon :MixedIcon} data={data} heading={"MOST VIEWED LIBRARIES"} units={"Plays"}/>
|
||||
<ItemStatComponent icon={data[0].CollectionType==="tvshows"? SeriesIcon: data[0].CollectionType==="movies"? MovieIcon : data[0].CollectionType==="music"? MusicIcon :MixedIcon} data={data} heading={<Trans i18nKey="STAT_CARDS.MOST_VIEWED_LIBRARIES" />} units={<Trans i18nKey="UNITS.PLAYS" />}/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import axios from "axios";
|
||||
|
||||
import Config from "../../../lib/config";
|
||||
|
||||
|
||||
import ItemStatComponent from "./ItemStatComponent";
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
|
||||
function MVMusic(props) {
|
||||
@@ -73,7 +74,7 @@ function MVMusic(props) {
|
||||
|
||||
|
||||
return (
|
||||
<ItemStatComponent base_url={config.hostUrl} data={data} heading={"MOST VIEWED MOVIES"} units={"Plays"}/>
|
||||
<ItemStatComponent base_url={config.hostUrl} data={data} heading={<Trans i18nKey="STAT_CARDS.MOST_VIEWED_MOVIES" />} units={<Trans i18nKey="UNITS.PLAYS" />}/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import axios from "axios";
|
||||
import Config from "../../../lib/config";
|
||||
import ItemStatComponent from "./ItemStatComponent";
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
function MVMovies(props) {
|
||||
const [data, setData] = useState();
|
||||
@@ -68,7 +69,7 @@ function MVMovies(props) {
|
||||
|
||||
|
||||
return (
|
||||
<ItemStatComponent base_url={config.hostUrl} data={data} heading={"MOST LISTENED MUSIC"} units={"Plays"} isAudio={true}/>
|
||||
<ItemStatComponent base_url={config.hostUrl} data={data} heading={<Trans i18nKey="STAT_CARDS.MOST_LISTENED_MUSIC" />} units={<Trans i18nKey="UNITS.PLAYS" />} isAudio={true}/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import axios from "axios";
|
||||
import Config from "../../../lib/config";
|
||||
|
||||
|
||||
import ItemStatComponent from "./ItemStatComponent";
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
|
||||
function MVSeries(props) {
|
||||
@@ -69,7 +70,7 @@ function MVSeries(props) {
|
||||
|
||||
|
||||
return (
|
||||
<ItemStatComponent base_url={config.hostUrl} data={data} heading={"MOST VIEWED SERIES"} units={"Plays"}/>
|
||||
<ItemStatComponent base_url={config.hostUrl} data={data} heading={<Trans i18nKey="STAT_CARDS.MOST_VIEWED_SERIES" />} units={<Trans i18nKey="UNITS.PLAYS" />}/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import React from 'react'
|
||||
|
||||
import './css/home.css'
|
||||
|
||||
import Sessions from './components/sessions/sessions'
|
||||
|
||||
@@ -9,6 +9,8 @@ import ErrorBoundary from "./components/general/ErrorBoundary";
|
||||
import EyeOffFillIcon from 'remixicon-react/EyeOffFillIcon';
|
||||
import EyeFillIcon from 'remixicon-react/EyeFillIcon';
|
||||
import { Tooltip } from "react-bootstrap";
|
||||
import { Trans } from "react-i18next";
|
||||
import i18next from "i18next";
|
||||
|
||||
|
||||
function Libraries() {
|
||||
@@ -82,16 +84,16 @@ function Libraries() {
|
||||
return (
|
||||
<div className="libraries">
|
||||
<div className="d-flex flex-row justify-content-between align-items-center">
|
||||
<h1 className="py-4">Libraries</h1>
|
||||
<h1 className="py-4"><Trans i18nKey="LIBRARIES" /></h1>
|
||||
{
|
||||
showArchived ?
|
||||
<Tooltip title={"Hide Archived Libraries"} className="tooltip-icon-button">
|
||||
<Tooltip title={i18next.t("HIDE_ARCHIVED_LIBRARIES")} className="tooltip-icon-button">
|
||||
<button className="btn" onClick={()=> setShowArchived(!showArchived)}>
|
||||
<EyeFillIcon/>
|
||||
</button>
|
||||
</Tooltip>
|
||||
:
|
||||
<Tooltip title={"Show Archived Libraries"} className="tooltip-icon-button">
|
||||
<Tooltip title={i18next.t("SHOW_ARCHIVED_LIBRARIES")} className="tooltip-icon-button">
|
||||
<button className="btn" onClick={()=> setShowArchived(!showArchived)}>
|
||||
<EyeOffFillIcon/>
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user