From 149957d3428b7e8a379dd54818133012bf3b72e2 Mon Sep 17 00:00:00 2001 From: Phelra Date: Wed, 28 Aug 2024 16:53:43 +0200 Subject: [PATCH 1/5] feat: Add multi-languages support --- src/lib/apis/tmdb/tmdb-api.ts | 17 +- .../LibraryItemContextItems.svelte | 13 +- .../components/Dialog/ConfirmDialog.svelte | 5 +- src/lib/components/Ghosts/ButtonGhost.svelte | 3 +- .../Integrations/JellyfinIntegration.svelte | 7 +- .../Integrations/RadarrIntegration.svelte | 5 +- .../Integrations/SonarrIntegration.svelte | 5 +- src/lib/components/Lang/I18n.svelte | 6 +- src/lib/components/LanguageSettings.svelte | 55 +++++ src/lib/components/Login.svelte | 17 +- .../MMAddToRadarrDialog.svelte | 25 +- .../MMAddToSonarrDialog.svelte | 15 +- .../components/SeriesPage/SeriesPage.svelte | 33 +-- src/lib/components/Sidebar/Sidebar.svelte | 11 +- src/lib/lang/de.json | 207 ++++++++++------- src/lib/lang/en.json | 211 ++++++++++------- src/lib/lang/es.json | 199 +++++++++------- src/lib/lang/fr.json | 213 +++++++++++------- src/lib/lang/it.json | 207 ++++++++++------- src/lib/pages/EpisodePage.svelte | 29 +-- src/lib/pages/ManagePage.svelte | 44 ++-- src/lib/pages/MoviePage.svelte | 37 +-- src/lib/pages/MoviesHomePage.svelte | 11 +- src/lib/pages/PersonPage.svelte | 3 +- src/lib/pages/SearchPage.svelte | 3 +- src/lib/pages/SeriesHomePage.svelte | 11 +- src/lib/stores/localstorage.store.ts | 4 +- 27 files changed, 860 insertions(+), 536 deletions(-) create mode 100644 src/lib/components/LanguageSettings.svelte diff --git a/src/lib/apis/tmdb/tmdb-api.ts b/src/lib/apis/tmdb/tmdb-api.ts index 6a6a3aeb..9f35fef5 100644 --- a/src/lib/apis/tmdb/tmdb-api.ts +++ b/src/lib/apis/tmdb/tmdb-api.ts @@ -7,6 +7,7 @@ import { settings } from '../../stores/settings.store'; import type { TitleType } from '../../types'; import type { Api } from '../api.interface'; import { user } from '../../stores/user.store'; +import { localSettings } from '../../stores/localstorage.store'; const CACHE_ONE_DAY = 'max-age=86400'; const CACHE_FOUR_DAYS = 'max-age=345600'; @@ -113,8 +114,9 @@ export class TmdbApi implements Api { movie_id: tmdbId }, query: { + language: get(localSettings)?.language, append_to_response: 'videos,credits,external_ids,images', - ...({ include_image_language: get(settings)?.language + ',en,null' } as any) + ...({ include_image_language: get(localSettings)?.language + ',en,null' } as any) } } }) @@ -126,7 +128,7 @@ export class TmdbApi implements Api { ?.GET('/3/movie/popular', { params: { query: { - language: get(settings)?.language, + language: get(localSettings)?.language, region: get(settings)?.discover.region } } @@ -168,7 +170,7 @@ export class TmdbApi implements Api { }, query: { append_to_response: 'videos,aggregate_credits,external_ids,images', - ...({ include_image_language: get(settings)?.language + ',en,null' } as any) + ...({ include_image_language: get(localSettings)?.language + ',en,null' } as any) } }, headers: { @@ -213,7 +215,7 @@ export class TmdbApi implements Api { .GET('/3/tv/popular', { params: { query: { - language: get(settings)?.language + language: get(localSettings)?.language } } }) @@ -280,6 +282,7 @@ export class TmdbApi implements Api { person_id: person_id }, query: { + language: get(localSettings)?.language, append_to_response: 'images,movie_credits,tv_credits,external_ids' } } @@ -536,7 +539,7 @@ export const getTmdbMovie = async (tmdbId: number) => }, query: { append_to_response: 'videos,credits,external_ids,images', - ...({ include_image_language: get(settings)?.language + ',en,null' } as any) + ...({ include_image_language: get(localSettings)?.language + ',en,null' } as any) } } }).then((res) => res.data as TmdbMovieFull2 | undefined); @@ -643,7 +646,7 @@ export const getTmdbMovieBackdrop = async (tmdbId: number) => .then( (r) => ( - r?.backdrops?.find((b) => b.iso_639_1 === get(settings)?.language) || + r?.backdrops?.find((b) => b.iso_639_1 === get(localSettings)?.language) || r?.backdrops?.find((b) => b.iso_639_1 === 'en') || r?.backdrops?.find((b) => b.iso_639_1) || r?.backdrops?.[0] @@ -735,7 +738,7 @@ export const getTmdbItemBackdrop = (item: { images: { backdrops: { file_path: string; iso_639_1: string }[] }; }) => ( - item?.images?.backdrops?.find((b) => b.iso_639_1 === get(settings)?.language) || + item?.images?.backdrops?.find((b) => b.iso_639_1 === get(localSettings)?.language) || item?.images?.backdrops?.find((b) => b.iso_639_1 === 'en') || item?.images?.backdrops?.find((b) => b.iso_639_1) || item?.images?.backdrops?.[0] diff --git a/src/lib/components/ContextMenu/LibraryItemContextItems.svelte b/src/lib/components/ContextMenu/LibraryItemContextItems.svelte index 90f8d715..3f87c385 100644 --- a/src/lib/components/ContextMenu/LibraryItemContextItems.svelte +++ b/src/lib/components/ContextMenu/LibraryItemContextItems.svelte @@ -11,6 +11,7 @@ import type { TitleType } from '../../types'; import ContextMenuDivider from './ContextMenuDivider.svelte'; import ContextMenuItem from './ContextMenuItem.svelte'; + import { _ } from 'svelte-i18n'; export let jellyfinItem: JellyfinItem | undefined = undefined; export let sonarrSeries: SonarrSeries | undefined = undefined; @@ -42,30 +43,30 @@ - Mark as watched + {_('contextMenu.markAsWatched')} - Mark as unwatched + {_('contextMenu.markAsUnwatched')} - Open in Jellyfin + {_('contextMenu.openInJellyfin')} {#if type === 'movie'} window.open($settings.radarr.baseUrl + '/movie/' + radarrMovie?.tmdbId)} > - Open in Radarr + {_('contextMenu.openInRadarr')} {:else} window.open($settings.sonarr.baseUrl + '/series/' + sonarrSeries?.titleSlug)} > - Open in Sonarr + {_('contextMenu.openInSonarr')} {/if} window.open(`https://www.themoviedb.org/${type}/${tmdbId}`)}> - Open in TMDB + {_('contextMenu.openInTMDB')} diff --git a/src/lib/components/Dialog/ConfirmDialog.svelte b/src/lib/components/Dialog/ConfirmDialog.svelte index 8e6fa8b7..c9777e1f 100644 --- a/src/lib/components/Dialog/ConfirmDialog.svelte +++ b/src/lib/components/Dialog/ConfirmDialog.svelte @@ -3,6 +3,7 @@ import Button from '../Button.svelte'; import { modalStack } from '../Modal/modal.store'; import Dialog from './Dialog.svelte'; + import { _ } from 'svelte-i18n'; type ActionFn = (() => Promise) | (() => any); @@ -38,10 +39,10 @@ {$_('confirmDialogs.cancel')} diff --git a/src/lib/components/Ghosts/ButtonGhost.svelte b/src/lib/components/Ghosts/ButtonGhost.svelte index 4fff89a7..f51cb579 100644 --- a/src/lib/components/Ghosts/ButtonGhost.svelte +++ b/src/lib/components/Ghosts/ButtonGhost.svelte @@ -1,11 +1,12 @@
- Loading... + {$_('navbar.loading')}
diff --git a/src/lib/components/Integrations/JellyfinIntegration.svelte b/src/lib/components/Integrations/JellyfinIntegration.svelte index 888c491c..de16296e 100644 --- a/src/lib/components/Integrations/JellyfinIntegration.svelte +++ b/src/lib/components/Integrations/JellyfinIntegration.svelte @@ -5,6 +5,7 @@ import SelectField from '../SelectField.svelte'; import { jellyfinApi, type JellyfinUser } from '../../apis/jellyfin/jellyfin-api'; import { derived, get } from 'svelte/store'; + import { _ } from 'svelte-i18n'; const dispatch = createEventDispatcher<{ 'click-user': { @@ -108,14 +109,14 @@ isValid={jellyfinUsers?.then((u) => !!u?.length)} on:change={handleChange} > - Base Url + {$_('settings.integrations.baseUrl')} !!u?.length)} on:change={handleChange} > - API Key + {$_('settings.integrations.apiKey')} @@ -127,7 +128,7 @@ dispatch('click-user', { user: jellyfinUser, users, setJellyfinUser })} class="mb-4" > - User + {$_('settings.integrations.user')} {/if} {/await} diff --git a/src/lib/components/Integrations/RadarrIntegration.svelte b/src/lib/components/Integrations/RadarrIntegration.svelte index 079a79e5..4accfba4 100644 --- a/src/lib/components/Integrations/RadarrIntegration.svelte +++ b/src/lib/components/Integrations/RadarrIntegration.svelte @@ -4,6 +4,7 @@ import { radarrApi } from '../../apis/radarr/radarr-api'; import { user } from '../../stores/user.store'; import { derived, get } from 'svelte/store'; + import { _ } from 'svelte-i18n'; let baseUrl = get(user)?.settings.radarr.baseUrl || ''; let apiKey = get(user)?.settings.radarr.apiKey || ''; @@ -85,9 +86,9 @@
- Base Url + {$_('settings.integrations.baseUrl')} - API Key + {$_('settings.integrations.apiKey')}
{#if error} diff --git a/src/lib/components/Integrations/SonarrIntegration.svelte b/src/lib/components/Integrations/SonarrIntegration.svelte index 446e5874..b9fc0b3f 100644 --- a/src/lib/components/Integrations/SonarrIntegration.svelte +++ b/src/lib/components/Integrations/SonarrIntegration.svelte @@ -4,6 +4,7 @@ import { createEventDispatcher } from 'svelte'; import { user } from '../../stores/user.store'; import { derived, get } from 'svelte/store'; + import { _ } from 'svelte-i18n'; let baseUrl = get(user)?.settings.sonarr.baseUrl || ''; let apiKey = get(user)?.settings.sonarr.apiKey || ''; @@ -85,9 +86,9 @@
- Base Url + {$_('settings.integrations.baseUrl')} - API Key + {$_('settings.integrations.apiKey')}
{#if error} diff --git a/src/lib/components/Lang/I18n.svelte b/src/lib/components/Lang/I18n.svelte index 340772e2..899aacb4 100644 --- a/src/lib/components/Lang/I18n.svelte +++ b/src/lib/components/Lang/I18n.svelte @@ -1,5 +1,7 @@ diff --git a/src/lib/components/LanguageSettings.svelte b/src/lib/components/LanguageSettings.svelte new file mode 100644 index 00000000..1a8d3601 --- /dev/null +++ b/src/lib/components/LanguageSettings.svelte @@ -0,0 +1,55 @@ + + + + + {#if showLanguageDialog} + (showLanguageDialog = false)}> + {#each Object.entries(AVAILABLE_LANGUAGES) as [code, language]} + handleSelectLanguage(code)} > + {language.name} + + {/each} + + {/if} diff --git a/src/lib/components/Login.svelte b/src/lib/components/Login.svelte index 84456d65..1ceba9c4 100644 --- a/src/lib/components/Login.svelte +++ b/src/lib/components/Login.svelte @@ -5,6 +5,7 @@ import { createLocalStorageStore } from '../stores/localstorage.store'; import { sessions } from '../stores/session.store'; import { createEventDispatcher } from 'svelte'; + import { _ } from 'svelte-i18n'; const dispatch = createEventDispatcher<{ login: null }>(); @@ -23,9 +24,9 @@ .then((res) => { console.log('res', res); if (res?.request?.status === 401) { - error = 'Invalid credentials. Please try again.'; + error = $_('login.invalidCredentials'); } else if (res?.request.status !== 200) { - error = 'Error occurred: ' + res.request.statusText; + error = $_('login.errorOccurred') + ': ' + res.request.statusText; } else { dispatch('login'); } @@ -40,22 +41,22 @@ -

Login to Reiverr

+

{$_('login.title')}

- If this is your first time logging in, a new account will be created based on your credentials. + {$_('login.firstTime')}
baseUrl.set(e.detail)} class="mb-4 w-full"> - Server + {$_('login.server')} name.set(detail)} class="mb-4 w-full"> - Name + {$_('login.name')} - Password + {$_('login.password')} {$_('login.submit')} {#if error} diff --git a/src/lib/components/MediaManagerModal/MMAddToRadarrDialog.svelte b/src/lib/components/MediaManagerModal/MMAddToRadarrDialog.svelte index 9baf4b74..ef0a6521 100644 --- a/src/lib/components/MediaManagerModal/MMAddToRadarrDialog.svelte +++ b/src/lib/components/MediaManagerModal/MMAddToRadarrDialog.svelte @@ -15,6 +15,7 @@ import { capitalize, formatSize } from '../../utils'; import { ArrowRight, Check, Plus } from 'radix-icons-svelte'; import Button from '../Button.svelte'; + import { _ } from 'svelte-i18n'; type AddOptionsStore = { rootFolderPath: string | null; @@ -126,9 +127,9 @@ >
-

Add {title} to Sonarr?

+

{$_('dialogs.add')} {title} {$_('dialogs.toRadarr')}

- Before you can fetch episodes, you need to add this series to Sonarr. + {$_('dialogs.addToRadarrMessage')}
-

Root Folder

+

{$_('dialogs.rootFolder')}

{selectedRootFolder?.path} - ({formatSize(selectedRootFolder?.freeSpace || 0)} left) + ({formatSize(selectedRootFolder?.freeSpace || 0)} {$_('dialogs.left')})
@@ -150,7 +151,7 @@ >

- Quality Profile + {$_('dialogs.qualityProfile')}

{selectedQualityProfile?.name} @@ -166,7 +167,7 @@ >

- Minimum Availability + {$_('dialogs.minimumAvailability')}

{capitalize($addOptionsStore.minimumAvailability || 'released')} @@ -187,10 +188,10 @@ @@ -200,7 +201,7 @@ class={tabClasses(tab === 'root-folders', true)} bind:selectable={rootFoldersTab} > -

Root Folder

+

{$_('dialogs.rootFolder')}

{#each rootFolders as rootFolder}
- {rootFolder.path} ({formatSize(rootFolder.freeSpace || 0)} left) + {rootFolder.path} ({formatSize(rootFolder.freeSpace || 0)} {$_('dialogs.left')})
{#if selectedRootFolder?.id === rootFolder.id} @@ -227,7 +228,7 @@ class={tabClasses(tab === 'quality-profiles', true)} bind:selectable={qualityProfilesTab} > -

Quality Profile

+

{$_('dialogs.qualityProfile')}

{#each qualityProfiles as qualityProfile} -

Monitor Episodes

+

{$_('dialogs.monitorEpisodes')}

{#each movieAvailabilities as availibility}

Add {title} to Sonarr?

- Before you can fetch episodes, you need to add this series to Sonarr. + {$_('dialogs.addToSonarrMessage')}
-

Root Folder

+

{$_('dialogs.rootFolder')}

{selectedRootFolder?.path} - ({formatSize(selectedRootFolder?.freeSpace || 0)} left) + ({formatSize(selectedRootFolder?.freeSpace || 0)} {$_('dialogs.left')})
@@ -151,7 +152,7 @@ >

- Quality Profile + {$_('dialogs.qualityProfile')}

{selectedQualityProfile?.name} @@ -167,7 +168,7 @@ >

- Monitor Strategy + {$_('dialogs.monitorStrategy')}

{capitalize($addOptionsStore.monitorOptions || 'none')} @@ -228,7 +229,7 @@ class={tabClasses(tab === 'quality-profiles', true)} bind:selectable={qualityProfilesTab} > -

Quality Profile

+

{$_('dialogs.qualityProfile')}

{#each qualityProfiles as qualityProfile} -

Monitor Episodes

+

{$_('dialogs.monitorEpisodes')}

{#each sonarrMonitorOptions as monitorOption}

{#if series.status !== 'Ended'} - Since {new Date(series.first_air_date || Date.now())?.getFullYear()} + {$_('library.content.since')} {new Date(series.first_air_date || Date.now())?.getFullYear()} {:else} - Ended {new Date(series.last_air_date || Date.now())?.getFullYear()} + {$_('library.content.ended')} {new Date(series.last_air_date || Date.now())?.getFullYear()} {/if}

@@ -205,11 +206,11 @@ {#if PLATFORM_WEB} {/if} @@ -221,7 +222,7 @@ {#await tmdbMovie then movie} -
Show Cast
+
{$_('library.content.castAndCrew')}
{#each movie?.credits?.cast?.slice(0, 15) || [] as credit} {/each} @@ -229,7 +230,7 @@ {/await} {#await recommendations then recommendations} -
Recommendations
+
{$_('library.content.recommendations')}
{#each recommendations || [] as recommendation} {/each} @@ -238,11 +239,11 @@
{#await tmdbMovie then movie} -

More Information

+

{$_('library.content.moreInformation')}

-

Directed By

+

{$_('library.content.directedBy')}

{movie?.credits.crew ?.filter((c) => c.job === 'Director') @@ -251,7 +252,7 @@
-

Written By

+

{$_('library.content.writtenBy')}

{movie?.credits.crew ?.filter((c) => c.job === 'Writer') @@ -262,13 +263,13 @@
-

Languages

+

{$_('library.content.languages')}

{movie?.spoken_languages?.map((language) => language.name).join(', ')}
-

Release Date

+

{$_('library.content.releaseDate')}

{new Date(movie?.release_date || 0).toLocaleDateString('en-US', { year: 'numeric', @@ -287,7 +288,7 @@ class="flex-1 bg-secondary-950 pt-8 pb-16 px-32 flex flex-col" on:enter={scrollIntoView({ top: 32 })} > -

Local Files

+

{$_('library.content.localFiles')}

{#each downloads as download} @@ -377,13 +378,13 @@ {#if files?.length} {/if} {#if downloads?.length} {/if} diff --git a/src/lib/pages/MoviesHomePage.svelte b/src/lib/pages/MoviesHomePage.svelte index 0fd16860..3046781b 100644 --- a/src/lib/pages/MoviesHomePage.svelte +++ b/src/lib/pages/MoviesHomePage.svelte @@ -11,6 +11,7 @@ import TmdbCard from '../components/Card/TmdbCard.svelte'; import { navigate } from '../components/StackRouter/StackRouter'; import DetachedPage from '../components/DetachedPage/DetachedPage.svelte'; + import { _ } from 'svelte-i18n'; const continueWatching = jellyfinApi.getContinueWatching('movie'); const recentlyAdded = jellyfinApi.getRecentlyAdded('movie'); @@ -72,7 +73,7 @@ {#await continueWatching then continueWatching} {#if continueWatching?.length} - Continue Watching + {$_('discover.continueWatching')} {#each continueWatching as item (item.Id)} {/each} @@ -81,7 +82,7 @@ {#await recentlyAdded then recentlyAdded} {#if recentlyAdded?.length} - Recently Added + {$_('discover.recentlyAdded')} {#each recentlyAdded as item (item.Id)} {/each} @@ -93,7 +94,7 @@ {#await popularMovies then popularMovies} - Popular + {$_('discover.trending')} {#each popularMovies as item} {/each} @@ -115,7 +116,7 @@ {#await newDigitalReleases then nowStreaming} - New Digital Releases + {$_('discover.newDigitalReleases')} {#each nowStreaming as item} {/each} @@ -137,7 +138,7 @@ {#await upcomingMovies then upcomingSeries} - Upcoming Movies + {$_('discover.upcomingMovies')} {#each upcomingSeries as item} {/each} diff --git a/src/lib/pages/PersonPage.svelte b/src/lib/pages/PersonPage.svelte index 5aafe5b2..abbf73d0 100644 --- a/src/lib/pages/PersonPage.svelte +++ b/src/lib/pages/PersonPage.svelte @@ -8,6 +8,7 @@ import TmdbCard from '../components/Card/TmdbCard.svelte'; import Container from '../../Container.svelte'; import { scrollIntoView } from '../selectable'; + import { _ } from 'svelte-i18n'; export let id: string; $: person = tmdbApi.getPerson(Number(id)); @@ -63,7 +64,7 @@ class="flex items-center gap-1 uppercase text-zinc-300 font-semibold tracking-wider mt-2 text-lg" >

- Born {new Date(person.birthday || 0).toLocaleDateString('en-US', { + {$_('personPage.born')} {new Date(person.birthday || 0).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' diff --git a/src/lib/pages/SearchPage.svelte b/src/lib/pages/SearchPage.svelte index 77729cb9..1dc1ff71 100644 --- a/src/lib/pages/SearchPage.svelte +++ b/src/lib/pages/SearchPage.svelte @@ -10,6 +10,7 @@ import AnimateScale from '../components/AnimateScale.svelte'; import type { Readable } from 'svelte/store'; import DetachedPage from '../components/DetachedPage/DetachedPage.svelte'; + import { _ } from 'svelte-i18n'; let searchQuery = ''; let typingTimeout: ReturnType | undefined = undefined; @@ -65,7 +66,7 @@ class="bg-transparent outline-none placeholder:text-secondary-400" bind:value={searchQuery} on:input={() => handleInput(searchQuery)} - placeholder="Search titles..." + placeholder={$_('search.placeHolder')} bind:this={searchInput} /> diff --git a/src/lib/pages/SeriesHomePage.svelte b/src/lib/pages/SeriesHomePage.svelte index 07492302..bb668e14 100644 --- a/src/lib/pages/SeriesHomePage.svelte +++ b/src/lib/pages/SeriesHomePage.svelte @@ -12,6 +12,7 @@ import { navigate } from '../components/StackRouter/StackRouter'; import { TMDB_SERIES_GENRES } from '../apis/tmdb/tmdb-api.js'; import DetachedPage from '../components/DetachedPage/DetachedPage.svelte'; + import { _ } from 'svelte-i18n'; const continueWatching = jellyfinApi.getContinueWatchingSeries(); const recentlyAdded = jellyfinApi.getRecentlyAdded('series'); @@ -65,7 +66,7 @@ {#await continueWatching then continueWatching} {#if continueWatching?.length} - Continue Watching + {$_('discover.continueWatching')} {#each continueWatching as item (item.Id)} {/each} @@ -74,7 +75,7 @@ {#await recentlyAdded then recentlyAdded} {#if recentlyAdded?.length} - Recently Added + {$_('discover.recentlyAdded')} {#each recentlyAdded as item (item.Id)} {/each} @@ -86,7 +87,7 @@ {#await popular then popular} - Popular + {$_('discover.trending')} {#each popular as item} {/each} @@ -108,7 +109,7 @@ {#await nowStreaming then nowStreaming} - Now Streaming + {$_('discover.streamingNow')} {#each nowStreaming as item} {/each} @@ -130,7 +131,7 @@ {#await upcomingSeries then upcomingSeries} - Upcoming Series + {$_('discover.upcomingSeries')} {#each upcomingSeries as item} {/each} diff --git a/src/lib/stores/localstorage.store.ts b/src/lib/stores/localstorage.store.ts index 95af4a16..67cd86a7 100644 --- a/src/lib/stores/localstorage.store.ts +++ b/src/lib/stores/localstorage.store.ts @@ -35,9 +35,11 @@ export const localSettings = createLocalStorageStore<{ useCssTransitions: boolean; checkForUpdates: boolean; skippedVersion: string; + language: string; }>('settings', { animateScrolling: true, useCssTransitions: true, checkForUpdates: true, - skippedVersion: '' + skippedVersion: '', + language: 'en' }); From d5731268e78df437e2f323464d777860050e92c8 Mon Sep 17 00:00:00 2001 From: Phelra <40712317+Phelra@users.noreply.github.com> Date: Wed, 28 Aug 2024 17:13:03 +0200 Subject: [PATCH 2/5] feat: Implement infinite scroll in LibraryPage --- src/lib/pages/LibraryPage.svelte | 59 +++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/src/lib/pages/LibraryPage.svelte b/src/lib/pages/LibraryPage.svelte index 3ccd0f2e..a23018b9 100644 --- a/src/lib/pages/LibraryPage.svelte +++ b/src/lib/pages/LibraryPage.svelte @@ -13,6 +13,8 @@ import type { ComponentProps } from 'svelte'; import TmdbCard from '../components/Card/TmdbCard.svelte'; import { tmdbApi, type TmdbMovie2, type TmdbSeries2 } from '../apis/tmdb/tmdb-api'; + import { writable } from 'svelte/store'; + import { onMount } from 'svelte'; const libraryItemsP = jellyfinApi.getLibraryItems(); const sonarrDownloads: Promise = sonarrApi @@ -44,6 +46,53 @@ userId: import.meta.env.VITE_JELLYFIN_USER_ID } })); + + const displayedItems = writable([]); + let currentIndex = 0; + let allItems = []; + + const ITEMS_PER_ROW = 3; + const ITEMS_PER_COLUMN = 4; + const ITEMS_PER_PAGE = ITEMS_PER_ROW * ITEMS_PER_COLUMN; + + const loadMoreItems = () => { + const nextIndex = currentIndex + ITEMS_PER_PAGE; + const newItems = allItems.slice(currentIndex, nextIndex); + if (newItems.length > 0) { + displayedItems.update(curr => [...curr, ...newItems]); + currentIndex = nextIndex; + } + }; + + const observerCallback = (entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + loadMoreItems(); + } + }); + }; + + onMount(() => { + const observer = new IntersectionObserver(observerCallback, { + root: null, + rootMargin: '0px', + threshold: 1.0 + }); + const sentinel = document.querySelector('#sentinel'); + if (sentinel) { + observer.observe(sentinel); + } + return () => { + if (sentinel) { + observer.unobserve(sentinel); + } + }; + }); + + libraryItemsP.then(items => { + allItems = items; + loadMoreItems(); + }); @@ -66,10 +115,10 @@

Library
- {#await libraryItemsP} + {#if $displayedItems.length === 0} - {:then items} - {#each items as item} + {:else} + {#each $displayedItems as item} {/each} - {/await} + {/if} + +
From 262c12f5c5778bdfbee5409723ecb9fd8226ddf1 Mon Sep 17 00:00:00 2001 From: Phelra Date: Fri, 30 Aug 2024 20:50:24 +0200 Subject: [PATCH 3/5] add: new translations --- .../Dialog/CreateOrEditProfileModal.svelte | 23 +++++++------- src/lib/lang/de.json | 28 +++++++++++++++-- src/lib/lang/en.json | 31 ++++++++++++++++--- src/lib/lang/es.json | 28 +++++++++++++++-- src/lib/lang/fr.json | 27 ++++++++++++++-- src/lib/lang/it.json | 28 +++++++++++++++-- src/lib/pages/ManagePage.svelte | 17 +++++----- src/lib/pages/UsersPage.svelte | 5 +-- 8 files changed, 154 insertions(+), 33 deletions(-) diff --git a/src/lib/components/Dialog/CreateOrEditProfileModal.svelte b/src/lib/components/Dialog/CreateOrEditProfileModal.svelte index bac0bcb1..86fd009b 100644 --- a/src/lib/components/Dialog/CreateOrEditProfileModal.svelte +++ b/src/lib/components/Dialog/CreateOrEditProfileModal.svelte @@ -18,6 +18,7 @@ import { navigate } from '../StackRouter/StackRouter'; import Toggle from '../Toggle.svelte'; import { get } from 'svelte/store'; + import { _ } from 'svelte-i18n'; enum Tabs { EditProfile, @@ -166,11 +167,11 @@

- {createNew ? 'Create Account' : 'Edit Profile'} -

- name + {createNew ? $_('settings.profile.createProfile') : $_('settings.profile.editProfile')} + + {$_('settings.profile.name')} tab.set(Tabs.ProfilePictures)}> - Profile Picture + {$_('settings.profile.profilePicture')} {#if !createNew} @@ -179,7 +180,7 @@ bind:value={oldPassword} type={oldPasswordVisible ? 'text' : 'password'} > - Old Password + {$_('settings.profile.oldPassword')} (oldPasswordVisible = !oldPasswordVisible)} @@ -193,7 +194,7 @@ bind:value={newPassword} type={newPasswordVisible ? 'text' : 'password'} > - New Password + {$_('settings.profile.newPassword')} (newPasswordVisible = !newPasswordVisible)} @@ -202,7 +203,7 @@ {#if isAdmin || admin}
- + Admin
{/if} @@ -211,7 +212,7 @@ {/if} {#if !createNew} - + {$_('settings.profile.deleteAccount')} {:else} - + {/if}
@@ -236,7 +237,7 @@ detail.stopPropagation(); }} > -

Select Profile Picture

+

{$_('settings.profile.profilePicture')}

- Accounts + {$_('settings.accounts.accounts')}
-

My Profile

+

{$_('settings.accounts.myprofile')}

- Logged in as + {$_('settings.accounts.loginAs')} - + {#await users then usersR} {#if usersR?.length}
-

Server Accounts

+

{$_('settings.accounts.serverAccounts')} +

{#each usersR.filter((u) => u.id !== $user?.id) as user} {/each} { createModal(EditProfileModal, { createNew: true, @@ -219,7 +220,7 @@ }); }} > - Create + {$_('settings.accounts.create')} @@ -259,7 +260,7 @@ class="bg-primary-800 rounded-xl p-8" on:enter={scrollIntoView({ vertical: 64 })} > -

{$_('settings.integrations.tmdbAccount')}

+

{$_('settings.accounts.newaccount')}

{#if !connected}
diff --git a/src/lib/pages/UsersPage.svelte b/src/lib/pages/UsersPage.svelte index 8852a763..b25801b3 100644 --- a/src/lib/pages/UsersPage.svelte +++ b/src/lib/pages/UsersPage.svelte @@ -12,6 +12,7 @@ import { Plus, Trash } from 'radix-icons-svelte'; import ProfileIcon from '../components/ProfileIcon.svelte'; import { profilePictures } from '../profile-pictures'; + import { _ } from 'svelte-i18n'; $: users = getUsers($sessions.sessions); @@ -35,7 +36,7 @@ {#await users then users} {#if users?.length} -

Who is watching?

+

{$_('login.whoIsWatching')}

{#each users as item} {@const user = item.user} @@ -66,7 +67,7 @@ }} icon={Trash} > - Remove all Accounts + {$_('login.removeAllAccounts')} {:else} From dc03d244cfb1ad6baf8a0525830b1662167befcc Mon Sep 17 00:00:00 2001 From: Phelra Date: Fri, 30 Aug 2024 23:52:05 +0200 Subject: [PATCH 4/5] fix: bugs and missing translations --- src/lib/apis/tmdb/tmdb-api.ts | 12 +++++-- .../Dialog/CreateOrEditProfileModal.svelte | 23 ++++++------ .../TmdbIntegrationConnect.svelte | 9 ++--- src/lib/lang/de.json | 32 +++++++++++++++-- src/lib/lang/en.json | 35 ++++++++++++++++--- src/lib/lang/es.json | 32 +++++++++++++++-- src/lib/lang/fr.json | 31 ++++++++++++++-- src/lib/lang/it.json | 32 +++++++++++++++-- src/lib/pages/ManagePage.svelte | 15 ++++---- src/lib/pages/MoviesHomePage.svelte | 8 +++-- src/lib/pages/UsersPage.svelte | 5 +-- 11 files changed, 193 insertions(+), 41 deletions(-) diff --git a/src/lib/apis/tmdb/tmdb-api.ts b/src/lib/apis/tmdb/tmdb-api.ts index 9f35fef5..4d8656dd 100644 --- a/src/lib/apis/tmdb/tmdb-api.ts +++ b/src/lib/apis/tmdb/tmdb-api.ts @@ -145,6 +145,7 @@ export class TmdbApi implements Api { external_id: tvdbId }, query: { + external_source: 'tvdb_id' } }, @@ -169,8 +170,9 @@ export class TmdbApi implements Api { series_id: tmdbId }, query: { + language: get(localSettings)?.language, append_to_response: 'videos,aggregate_credits,external_ids,images', - ...({ include_image_language: get(localSettings)?.language + ',en,null' } as any) + ...({ include_image_language: get(localSettings)?.language + ',en,null' } as any) } }, headers: { @@ -257,6 +259,7 @@ export class TmdbApi implements Api { episode_number: episode }, query: { + language: get(localSettings)?.language, // Spécifiez la langue ici append_to_response: 'credits,external_ids,images' } } @@ -341,6 +344,7 @@ export class TmdbApi implements Api { account_object_id: userId }, query: { + language: get(localSettings)?.language, page: i + 1 } } @@ -416,6 +420,7 @@ export class TmdbApi implements Api { account_object_id: userId }, query: { + language: get(localSettings)?.language, page: i + 1 } } @@ -551,7 +556,8 @@ export const getTmdbSeriesFromTvdbId = async (tvdbId: string) => external_id: tvdbId }, query: { - external_source: 'tvdb_id' + external_source: 'tvdb_id', + language: get(localSettings)?.language } }, headers: { @@ -574,7 +580,7 @@ export const getTmdbSeries = async (tmdbId: number): Promise

- {createNew ? 'Create Account' : 'Edit Profile'} -

- name + {createNew ? $_('settings.profile.createProfile') : $_('settings.profile.editProfile')} + + {$_('settings.profile.name')} tab.set(Tabs.ProfilePictures)}> - Profile Picture + {$_('settings.profile.profilePicture')} {#if !createNew} @@ -179,7 +180,7 @@ bind:value={oldPassword} type={oldPasswordVisible ? 'text' : 'password'} > - Old Password + {$_('settings.profile.oldPassword')} (oldPasswordVisible = !oldPasswordVisible)} @@ -193,7 +194,7 @@ bind:value={newPassword} type={newPasswordVisible ? 'text' : 'password'} > - New Password + {$_('settings.profile.newPassword')} (newPasswordVisible = !newPasswordVisible)} @@ -202,7 +203,7 @@ {#if isAdmin || admin}
- + Admin
{/if} @@ -211,7 +212,7 @@ {/if} {#if !createNew} - + {$_('settings.profile.deleteAccount')} {:else} - + {/if}
@@ -236,7 +237,7 @@ detail.stopPropagation(); }} > -

Select Profile Picture

+

{$_('settings.profile.profilePicture')}

(); @@ -51,9 +52,9 @@ } -

Connect a TMDB Account

+

{$_('settings.integrations.connectTmdb')}

- To connect your TMDB account, log in via the link below and then click "Complete Connection". + {$_('settings.integrations.tmdbText')}
{#if tmdbConnectQrCode} @@ -71,9 +72,9 @@ {#if tmdbConnectLink} - + {/if} diff --git a/src/lib/lang/de.json b/src/lib/lang/de.json index f7a203fb..3e8b73db 100644 --- a/src/lib/lang/de.json +++ b/src/lib/lang/de.json @@ -53,7 +53,9 @@ "password": "Passwort", "submit": "Einreichen", "invalidCredentials": "Ungültige Anmeldeinformationen. Bitte versuchen Sie es erneut.", - "errorOccurred": "Ein Fehler ist aufgetreten" + "errorOccurred": "Ein Fehler ist aufgetreten", + "whoIsWatching": "Wer schaut?", + "removeAllAccounts": "Alle Konten entfernen" }, "settings": { "general": { @@ -69,7 +71,11 @@ "apiKey": "API Schlüssel", "save": "Speichern", "tmdbAccount": "Tmdb Konto", + "tmdbText": "Um Ihr TMDB-Konto zu verbinden, melden Sie sich über den untenstehenden Link an und klicken Sie dann auf 'Verbindung abschließen'.", + "connectTmdb": "Ein TMDB-Konto verbinden", "connectedTo": "Verbunden mit", + "completeConnectionTmdb": "Verbindung abschließen", + "tmdbOpenLink": "Link öffnen", "tmdb": { "connect": "Verbinden" }, @@ -91,7 +97,29 @@ "lastKey": "Letzter Schlüssel", "tizenMediaKey": "Tizen Media Key", "logOut": "Ausloggen" - } + }, + "accounts": { + "accounts": "Konten", + "myprofile": "Mein Profil", + "loginAs": "Angemeldet als", + "logOut": "Abmelden", + "create": "Erstellen", + "newaccount": "Neues Konto", + "serverAccounts": "Serverkonten" + }, + "profile": { + "createProfile": "Ein Konto erstellen", + "editProfile": "Profil Bearbeiten", + "name": "Name", + "profilePicture": "Profilbild", + "custom": "Benutzerdefiniert", + "oldPassword": "Altes Passwort", + "newPassword": "Neues Passwort", + "admin": "Administrator", + "save": "Speichern", + "deleteAccount": "Konto Löschen" + } + }, "discover": { "continueWatching": "Weiter ansehen", diff --git a/src/lib/lang/en.json b/src/lib/lang/en.json index a2804246..41ed08e2 100644 --- a/src/lib/lang/en.json +++ b/src/lib/lang/en.json @@ -54,7 +54,9 @@ "password": "Password", "submit": "Submit", "invalidCredentials": "Invalid credentials. Please try again.", - "errorOccurred": "Error occurred" + "errorOccurred": "Error occurred", + "whoIsWatching": "Who is watching ?", + "removeAllAccounts": "Remove all accounts" }, "settings": { "general": { @@ -70,6 +72,10 @@ "apiKey": "API Key", "save": "Save", "tmdbAccount": "Tmdb Account", + "tmdbText": "To connect your TMDB account, log in via the link below and then click 'Complete Connection'.", + "connectTmdb": "Connect a TMDB Account", + "completeConnectionTmdb": "Complete connection", + "tmdbOpenLink": "Open Link", "connectedTo": "Connected to", "tmdb": { "connect": "Connect" @@ -90,9 +96,30 @@ "userAgent": "User agent", "lastKeyCode": "Last key code", "lastKey": "Last key", - "tizenMediaKey": "Tizen media key", - "logOut": "Log Out" - } + "tizenMediaKey": "Tizen media key" + }, + "accounts": { + "accounts": "Accounts", + "myprofile": "My Profile", + "loginAs": "Logged in as", + "logOut": "Log Out", + "create": "Create", + "newaccount": "New account", + "serverAccounts": "Serveur Accounts" + + }, + "profile": { + "createProfile": "Create an account", + "editProfile": "Edit Profile", + "name": "Name", + "profilePicture": "Profile Picture", + "custom": "Custom", + "oldPassword": "Old Password", + "newPassword": "New Password", + "admin": "Admin", + "save": "Save", + "deleteAccount": "Delete Account" + } }, "discover": { "continueWatching": "Continue Watching", diff --git a/src/lib/lang/es.json b/src/lib/lang/es.json index 6e0be3f6..2d29e284 100644 --- a/src/lib/lang/es.json +++ b/src/lib/lang/es.json @@ -46,7 +46,9 @@ "password": "Contraseña", "submit": "Enviar", "invalidCredentials": "Credenciales inválidas. Por favor, inténtalo de nuevo.", - "errorOccurred": "Ocurrió un error" + "errorOccurred": "Ocurrió un error", + "whoIsWatching": "¿Quién está viendo?", + "removeAllAccounts": "Eliminar todas las cuentas" }, "settings": { "general": { @@ -62,7 +64,11 @@ "apiKey": "Llave API", "save": "Guardar", "tmdbAccount": "Cuenta de TMDB", + "tmdbText": "Para conectar tu cuenta de TMDB, inicia sesión a través del enlace de abajo y luego haz clic en 'Completar Conexión'.", + "connectTmdb": "Conectar una cuenta de TMDB", "connectedTo": "Conectado a", + "completeConnectionTmdb": "Completar Conexión", + "tmdbOpenLink": "Abrir enlace", "tmdb": { "connect": "Conectar" }, @@ -84,7 +90,29 @@ "lastKey": "Última tecla", "tizenMediaKey": "Tecla de medios Tizen", "logOut": "Cerrar sesión" - } + }, + "accounts": { + "accounts": "Cuentas", + "myprofile": "Mi Perfil", + "loginAs": "Conectado como", + "logOut": "Cerrar sesión", + "create": "Crear", + "newaccount": "Nueva cuenta", + "serverAccounts": "Cuentas del Servidor" + }, + "profile": { + "createProfile": "Crear una cuenta", + "editProfile": "Editar Perfil", + "name": "Nombre", + "profilePicture": "Foto de Perfil", + "custom": "Personalizado", + "oldPassword": "Contraseña Antigua", + "newPassword": "Nueva Contraseña", + "admin": "Administrador", + "save": "Guardar", + "deleteAccount": "Eliminar Cuenta" + } + }, "discover": { "continueWatching": "Seguir viendo", diff --git a/src/lib/lang/fr.json b/src/lib/lang/fr.json index 09b4afa8..6a876b04 100644 --- a/src/lib/lang/fr.json +++ b/src/lib/lang/fr.json @@ -54,7 +54,9 @@ "password": "Mot de passe", "submit": "Connexion", "invalidCredentials": "Identifiants invalides. Veuillez réessayer.", - "errorOccurred": "Une erreur s'est produite" + "errorOccurred": "Une erreur s'est produite", + "whoIsWatching": "Qui regarde ?", + "removeAllAccounts": "Supprimer tous les comptes" }, "settings": { "general": { @@ -70,7 +72,11 @@ "apiKey": "Clé API", "save": "Sauvegarder", "tmdbAccount": "Compte TMDB", + "tmdbText": "Pour connecter votre compte TMDB, connectez-vous via le lien ci-dessous, puis cliquez sur 'Compléter la connexion'.", + "connectTmdb": "Connecter un compte TMDB", "connectedTo": "Connecté à", + "completeConnectionTmdb": "Compléter la connexion", + "tmdbOpenLink": "Ouvrir le lien", "tmdb": { "connect": "Connecter" }, @@ -92,7 +98,28 @@ "lastKey": "Dernière touche", "tizenMediaKey": "Touche média Tizen", "logOut": "Déconnexion" - } + }, + "accounts": { + "accounts": "Comptes", + "myprofile": "Mon Profil", + "loginAs": "Connecté en tant que", + "logOut": "Déconnexion", + "create": "Créer", + "newaccount": "Nouveau compte", + "serverAccounts": "Gestion des comptes" + }, + "profile": { + "createProfile": "Créer un compte", + "editProfile": "Modifier le profil", + "name": "Nom", + "profilePicture": "Photo de profil", + "custom": "Personnalisé", + "oldPassword": "Ancien mot de passe", + "newPassword": "Nouveau mot de passe", + "admin": "Administrateur", + "save": "Enregistrer", + "deleteAccount": "Supprimer le compte" + } }, "discover": { "continueWatching": "Continuer à regarder", diff --git a/src/lib/lang/it.json b/src/lib/lang/it.json index 2cd7c7ef..e3751a4d 100644 --- a/src/lib/lang/it.json +++ b/src/lib/lang/it.json @@ -53,7 +53,9 @@ "password": "Password", "submit": "Invia", "invalidCredentials": "Credenziali non valide. Per favore, riprova.", - "errorOccurred": "Si è verificato un errore" + "errorOccurred": "Si è verificato un errore", + "whoIsWatching": "Chi sta guardando?", + "removeAllAccounts": "Rimuovi tutti gli account" }, "settings": { "general": { @@ -70,6 +72,10 @@ "save": "Salva", "tmdbAccount": "Account TMDB", "connectedTo": "Connesso a", + "tmdbText": "Per connettere il tuo account TMDB, accedi tramite il link sottostante e poi clicca su 'Completa Connessione'.", + "connectTmdb": "Connetti un account TMDB", + "completeConnectionTmdb": "Completa Connessione", + "tmdbOpenLink": "Apri link", "tmdb": { "connect": "Connetti" }, @@ -91,7 +97,29 @@ "lastKey": "Ultimo tasto", "tizenMediaKey": "Tasto media Tizen", "logOut": "Disconnetti" - } + }, + "accounts": { + "accounts": "Conti", + "myprofile": "Il Mio Profilo", + "loginAs": "Connesso come", + "logOut": "Disconnettersi", + "create": "Creare", + "newaccount": "Nuovo account", + "serverAccounts": "Conti del Server" + }, + "profile": { + "createProfile": "Creare un account", + "editProfile": "Modifica Profilo", + "name": "Nome", + "profilePicture": "Immagine del Profilo", + "custom": "Personalizzato", + "oldPassword": "Vecchia Password", + "newPassword": "Nuova Password", + "admin": "Amministratore", + "save": "Salva", + "deleteAccount": "Elimina Account" + } + }, "discover": { "continueWatching": "Continua a guardare", diff --git a/src/lib/pages/ManagePage.svelte b/src/lib/pages/ManagePage.svelte index c554e316..5f7f7f58 100644 --- a/src/lib/pages/ManagePage.svelte +++ b/src/lib/pages/ManagePage.svelte @@ -103,7 +103,7 @@ 'text-primary-500': hasFocus })} > - Accounts + {$_('settings.accounts.accounts')}
-

My Profile

+

{$_('settings.accounts.myprofile')}

- Logged in as + {$_('settings.accounts.loginAs')} - + {#await users then usersR} {#if usersR?.length}
-

Server Accounts

+

{$_('settings.accounts.serverAccounts')} +

{#each usersR.filter((u) => u.id !== $user?.id) as user} {/each} { createModal(EditProfileModal, { createNew: true, @@ -219,7 +220,7 @@ }); }} > - Create + {$_('settings.accounts.create')} diff --git a/src/lib/pages/MoviesHomePage.svelte b/src/lib/pages/MoviesHomePage.svelte index 3046781b..4ebdc8d1 100644 --- a/src/lib/pages/MoviesHomePage.svelte +++ b/src/lib/pages/MoviesHomePage.svelte @@ -13,8 +13,12 @@ import DetachedPage from '../components/DetachedPage/DetachedPage.svelte'; import { _ } from 'svelte-i18n'; - const continueWatching = jellyfinApi.getContinueWatching('movie'); - const recentlyAdded = jellyfinApi.getRecentlyAdded('movie'); + const continueWatching = jellyfinApi.getContinueWatching('movie').then(items => { + return items.filter(item => item.Type === 'Movie'); + }); + const recentlyAdded = jellyfinApi.getRecentlyAdded('movie').then(items => { + return items.filter(item => item.Type === 'Movie'); + }); const popularMovies = tmdbApi.getPopularMovies(); diff --git a/src/lib/pages/UsersPage.svelte b/src/lib/pages/UsersPage.svelte index 8852a763..b25801b3 100644 --- a/src/lib/pages/UsersPage.svelte +++ b/src/lib/pages/UsersPage.svelte @@ -12,6 +12,7 @@ import { Plus, Trash } from 'radix-icons-svelte'; import ProfileIcon from '../components/ProfileIcon.svelte'; import { profilePictures } from '../profile-pictures'; + import { _ } from 'svelte-i18n'; $: users = getUsers($sessions.sessions); @@ -35,7 +36,7 @@ {#await users then users} {#if users?.length} -

Who is watching?

+

{$_('login.whoIsWatching')}

{#each users as item} {@const user = item.user} @@ -66,7 +67,7 @@ }} icon={Trash} > - Remove all Accounts + {$_('login.removeAllAccounts')} {:else} From 06091cdbea8fbea77a63fd3a49f15fd7516856d3 Mon Sep 17 00:00:00 2001 From: Phelra Date: Sat, 7 Sep 2024 12:47:35 +0200 Subject: [PATCH 5/5] Fix: LanguageSettings --- src/lib/components/LanguageSettings.svelte | 18 ++++++++++-------- src/lib/pages/LibraryPage.svelte | 3 ++- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/lib/components/LanguageSettings.svelte b/src/lib/components/LanguageSettings.svelte index 1a8d3601..b81e64aa 100644 --- a/src/lib/components/LanguageSettings.svelte +++ b/src/lib/components/LanguageSettings.svelte @@ -44,12 +44,14 @@ {#if showLanguageDialog} (showLanguageDialog = false)}> - {#each Object.entries(AVAILABLE_LANGUAGES) as [code, language]} - handleSelectLanguage(code)} > - {language.name} - - {/each} - +
+ {#each Object.entries(AVAILABLE_LANGUAGES) as [code, language]} + handleSelectLanguage(code)}> + {language.name} + + {/each} +
+
{/if} diff --git a/src/lib/pages/LibraryPage.svelte b/src/lib/pages/LibraryPage.svelte index a23018b9..47a5c6c9 100644 --- a/src/lib/pages/LibraryPage.svelte +++ b/src/lib/pages/LibraryPage.svelte @@ -15,6 +15,7 @@ import { tmdbApi, type TmdbMovie2, type TmdbSeries2 } from '../apis/tmdb/tmdb-api'; import { writable } from 'svelte/store'; import { onMount } from 'svelte'; + import { _ } from 'svelte-i18n'; const libraryItemsP = jellyfinApi.getLibraryItems(); const sonarrDownloads: Promise = sonarrApi @@ -112,7 +113,7 @@ {/await}
-
Library
+
{$_('navbar.library')}
{#if $displayedItems.length === 0}