Added Localisation Framework

Converted Below pages to use localization:
Navbar
Home Page
Libraries (Library Overview only)

Rest is WIP
This commit is contained in:
Thegan Govender
2024-02-07 21:14:38 +02:00
parent 5996383e71
commit b4243e694c
24 changed files with 309 additions and 74 deletions

135
package-lock.json generated
View File

@@ -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",

View File

@@ -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",

View 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"
}

View File

@@ -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>

View File

@@ -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
View 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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>
:
<></>

View File

@@ -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>

View File

@@ -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>

View File

@@ -1,4 +1,3 @@
import React from "react";
import { Link } from "react-router-dom";
import { Row, Col, Card } from "react-bootstrap";

View File

@@ -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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,3 @@
import React from 'react'
import './css/home.css'
import Sessions from './components/sessions/sessions'

View File

@@ -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>