diff --git a/client/components/app/ConfigSideNav.vue b/client/components/app/ConfigSideNav.vue index adc99e5a..108dc42d 100644 --- a/client/components/app/ConfigSideNav.vue +++ b/client/components/app/ConfigSideNav.vue @@ -19,7 +19,7 @@
{{ Source }}
- Latest: {{ $config.version }} + Latest: {{ versionData.latestVersion }} diff --git a/client/components/modals/AccountModal.vue b/client/components/modals/AccountModal.vue index 1ea24fd0..9c70e728 100644 --- a/client/components/modals/AccountModal.vue +++ b/client/components/modals/AccountModal.vue @@ -69,6 +69,15 @@ +{{ $strings.LabelPermissionsCreateEreader }}
+{{ $strings.LabelPermissionsAccessExplicitContent }}
@@ -354,7 +363,8 @@ export default { accessExplicitContent: type === 'admin', accessAllLibraries: true, accessAllTags: true, - selectedTagsNotAccessible: false + selectedTagsNotAccessible: false, + createEreader: type === 'admin' } }, init() { @@ -387,7 +397,8 @@ export default { accessAllLibraries: true, accessAllTags: true, accessExplicitContent: false, - selectedTagsNotAccessible: false + selectedTagsNotAccessible: false, + createEreader: false }, librariesAccessible: [], itemTagsSelected: [] diff --git a/client/components/modals/emails/UserEReaderDeviceModal.vue b/client/components/modals/emails/UserEReaderDeviceModal.vue new file mode 100644 index 00000000..b1706305 --- /dev/null +++ b/client/components/modals/emails/UserEReaderDeviceModal.vue @@ -0,0 +1,188 @@ + +{{ title }}
+- {{ $strings.LabelSettingsSquareBookCovers }} - info -
-{{ $strings.LabelSettingsEnableWatcherForLibrary }}
-*{{ $strings.MessageWatcherIsDisabledGlobally }}
-- {{ $strings.LabelSettingsAudiobooksOnly }} - info -
-{{ $strings.LabelSettingsSkipMatchingBooksWithASIN }}
-{{ $strings.LabelSettingsSkipMatchingBooksWithISBN }}
-- {{ $strings.LabelSettingsHideSingleBookSeries }} +
+ {{ $strings.LabelSettingsSquareBookCovers }} info
- {{ $strings.LabelSettingsOnlyShowLaterBooksInContinueSeries }} +
{{ $strings.LabelSettingsEnableWatcherForLibrary }}
+*{{ $strings.MessageWatcherIsDisabledGlobally }}
++ {{ $strings.LabelSettingsAudiobooksOnly }} info
- {{ $strings.LabelSettingsEpubsAllowScriptedContent }} - info -
-{{ $strings.LabelSettingsSkipMatchingBooksWithASIN }}
+{{ $strings.LabelSettingsSkipMatchingBooksWithISBN }}
++ {{ $strings.LabelSettingsHideSingleBookSeries }} + info +
++ {{ $strings.LabelSettingsOnlyShowLaterBooksInContinueSeries }} + info +
++ {{ $strings.LabelSettingsEpubsAllowScriptedContent }} + info +
+{{ $strings.LabelName }} | +{{ $strings.LabelEmail }} | ++ |
---|---|---|
+ {{ device.name }} + |
+
+ {{ device.email }} + |
+
+
+
+ |
+
{{ $strings.MessageNoDevices }}
+Page {{ currentPage + 1 }} of {{ numPages }}
+{{ $getString('LabelPaginationPageXOfY', [currentPage + 1, numPages]) }}
Open Listening Sessions
+{{ $strings.HeaderOpenListeningSessions }}
Page {{ currentPage + 1 }} of {{ numPages }}
+{{ $getString('LabelPaginationPageXOfY', [currentPage + 1, numPages]) }}
groups
bezeichnet. Wenn konfiguriert, wird die Anwendung automatisch Rollen basierend auf den Gruppenmitgliedschaften des Benutzers zuweisen, vorausgesetzt, dass diese Gruppen im Claim als 'admin', 'user' oder 'guest' benannt sind (Groß/Kleinschreibung ist irrelevant). Der Claim eine Liste sein, und wenn ein Benutzer mehreren Gruppen angehört, wird die Anwendung die Rolle zuordnen, die dem höchsten Zugriffslevel entspricht. Wenn keine Gruppe übereinstimmt, wird der Zugang verweigert.",
"LabelOpenRSSFeed": "Öffne RSS-Feed",
"LabelOverwrite": "Überschreiben",
+ "LabelPaginationPageXOfY": "Seite {0} von {1}",
"LabelPassword": "Passwort",
"LabelPath": "Pfad",
"LabelPermanent": "Dauerhaft",
"LabelPermissionsAccessAllLibraries": "Zugriff auf alle Bibliotheken",
"LabelPermissionsAccessAllTags": "Zugriff auf alle Schlagwörter",
"LabelPermissionsAccessExplicitContent": "Zugriff auf explizite (alterbeschränkte) Inhalte",
+ "LabelPermissionsCreateEreader": "Kann E-Reader erstellen",
"LabelPermissionsDelete": "Darf Löschen",
"LabelPermissionsDownload": "Herunterladen",
"LabelPermissionsUpdate": "Aktualisieren",
@@ -499,12 +517,17 @@
"LabelRedo": "Wiederholen",
"LabelRegion": "Region",
"LabelReleaseDate": "Veröffentlichungsdatum",
+ "LabelRemoveAllMetadataAbs": "Alle metadata.abs Dateien löschen",
+ "LabelRemoveAllMetadataJson": "Alle metadata.json Dateien löschen",
"LabelRemoveCover": "Entferne Titelbild",
+ "LabelRemoveMetadataFile": "Metadaten-Dateien in Bibliotheksordnern löschen",
+ "LabelRemoveMetadataFileHelp": "Alle metadata.json und metadata.abs Dateien aus den Ordnern {0} löschen.",
"LabelRowsPerPage": "Zeilen pro Seite",
"LabelSearchTerm": "Begriff suchen",
"LabelSearchTitle": "Titel suchen",
"LabelSearchTitleOrASIN": "Titel oder ASIN suchen",
"LabelSeason": "Staffel",
+ "LabelSeasonNumber": "Staffel #{0}",
"LabelSelectAll": "Alles auswählen",
"LabelSelectAllEpisodes": "Alle Episoden auswählen",
"LabelSelectEpisodesShowing": "{0} ausgewählte Episoden werden angezeigt",
@@ -539,6 +562,9 @@
"LabelSettingsHideSingleBookSeriesHelp": "Serien, die nur ein einzelnes Buch enthalten, werden auf der Startseite und in der Serienansicht ausgeblendet.",
"LabelSettingsHomePageBookshelfView": "Startseite verwendet die Bücherregalansicht",
"LabelSettingsLibraryBookshelfView": "Bibliothek verwendet die Bücherregalansicht",
+ "LabelSettingsLibraryMarkAsFinishedPercentComplete": "In Prozent gehört größer als",
+ "LabelSettingsLibraryMarkAsFinishedTimeRemaining": "Verbleibende Zeit ist weniger als (Sekunden)",
+ "LabelSettingsLibraryMarkAsFinishedWhen": "Markiere Mediendateien als fertig, wenn",
"LabelSettingsOnlyShowLaterBooksInContinueSeries": "Überspringe vorherige Bücher in fortführender Serie",
"LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "Die Startseite von \"Fortführende Serien\" zeigt das erste noch nicht begonnene Buch in Serien an, die mindestens ein Buch abgeschlossen und keine Bücher begonnen haben. Wenn diese Einstellung aktiviert wird, werden Serien ab dem letzten abgeschlossenen Buch fortgesetzt und nicht ab dem ersten nicht begonnenen Buch.",
"LabelSettingsParseSubtitles": "Analysiere Untertitel",
@@ -603,6 +629,7 @@
"LabelTimeDurationXMinutes": "{0} Minuten",
"LabelTimeDurationXSeconds": "{0} Sekunden",
"LabelTimeInMinutes": "Zeit in Minuten",
+ "LabelTimeLeft": "{0} verbleibend",
"LabelTimeListened": "Gehörte Zeit",
"LabelTimeListenedToday": "Heute gehörte Zeit",
"LabelTimeRemaining": "{0} verbleibend",
@@ -623,6 +650,7 @@
"LabelTracksMultiTrack": "Mehrfachdatei",
"LabelTracksNone": "Keine Dateien",
"LabelTracksSingleTrack": "Einzeldatei",
+ "LabelTrailer": "Vorschau",
"LabelType": "Typ",
"LabelUnabridged": "Ungekürzt",
"LabelUndo": "Rückgängig machen",
@@ -639,6 +667,7 @@
"LabelUseAdvancedOptions": "Nutze Erweiterte Optionen",
"LabelUseChapterTrack": "Kapiteldatei verwenden",
"LabelUseFullTrack": "Gesamte Datei verwenden",
+ "LabelUseZeroForUnlimited": "0 für unbegrenzt",
"LabelUser": "Benutzer",
"LabelUsername": "Benutzername",
"LabelValue": "Wert",
@@ -697,6 +726,7 @@
"MessageConfirmPurgeCache": "Cache leeren wird das ganze Verzeichnis /metadata/cache
löschen. /metadata/cache/items
gelöscht.groups
. If configured, the application will automatically assign roles based on the user's group memberships, provided that these groups are named case-insensitively 'admin', 'user', or 'guest' in the claim. The claim should contain a list, and if a user belongs to multiple groups, the application will assign the role corresponding to the highest level of access. If no group matches, access will be denied.",
"LabelOpenRSSFeed": "Open RSS Feed",
"LabelOverwrite": "Overwrite",
+ "LabelPaginationPageXOfY": "Page {0} of {1}",
"LabelPassword": "Password",
"LabelPath": "Path",
"LabelPermanent": "Permanent",
"LabelPermissionsAccessAllLibraries": "Can Access All Libraries",
"LabelPermissionsAccessAllTags": "Can Access All Tags",
"LabelPermissionsAccessExplicitContent": "Can Access Explicit Content",
+ "LabelPermissionsCreateEreader": "Can Create Ereader",
"LabelPermissionsDelete": "Can Delete",
"LabelPermissionsDownload": "Can Download",
"LabelPermissionsUpdate": "Can Update",
@@ -559,6 +563,9 @@
"LabelSettingsHideSingleBookSeriesHelp": "Series that have a single book will be hidden from the series page and home page shelves.",
"LabelSettingsHomePageBookshelfView": "Home page use bookshelf view",
"LabelSettingsLibraryBookshelfView": "Library use bookshelf view",
+ "LabelSettingsLibraryMarkAsFinishedPercentComplete": "Percent complete is greater than",
+ "LabelSettingsLibraryMarkAsFinishedTimeRemaining": "Time remaining is less than (seconds)",
+ "LabelSettingsLibraryMarkAsFinishedWhen": "Mark media item as finished when",
"LabelSettingsOnlyShowLaterBooksInContinueSeries": "Skip earlier books in Continue Series",
"LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "The Continue Series home page shelf shows the first book not started in series that have at least one book finished and no books in progress. Enabling this setting will continue series from the furthest completed book instead of the first book not started.",
"LabelSettingsParseSubtitles": "Parse subtitles",
diff --git a/client/strings/es.json b/client/strings/es.json
index d9410d7a..dbd8bbc6 100644
--- a/client/strings/es.json
+++ b/client/strings/es.json
@@ -251,6 +251,7 @@
"LabelBackupsNumberToKeep": "Numero de respaldos para conservar",
"LabelBackupsNumberToKeepHelp": "Solamente 1 respaldo se removerá a la vez. Si tiene mas respaldos guardados, debe removerlos manualmente.",
"LabelBitrate": "Tasa de bits",
+ "LabelBonus": "Bonus",
"LabelBooks": "Libros",
"LabelButtonText": "Texto del botón",
"LabelByAuthor": "por {0}",
@@ -329,6 +330,7 @@
"LabelEpisodeType": "Tipo de Episodio",
"LabelEpisodeUrlFromRssFeed": "URL del episodio del feed RSS",
"LabelEpisodes": "Episodios",
+ "LabelEpisodic": "Episodios",
"LabelExample": "Ejemplo",
"LabelExpandSeries": "Ampliar serie",
"LabelExpandSubSeries": "Expandir la subserie",
diff --git a/client/strings/fr.json b/client/strings/fr.json
index 3674acc3..d31c5971 100644
--- a/client/strings/fr.json
+++ b/client/strings/fr.json
@@ -250,11 +250,13 @@
"LabelBackupsNumberToKeep": "Nombre de sauvegardes à conserver",
"LabelBackupsNumberToKeepHelp": "Seule une sauvegarde sera supprimée à la fois. Si vous avez déjà plus de sauvegardes à effacer, vous devez les supprimer manuellement.",
"LabelBitrate": "Débit binaire",
+ "LabelBonus": "Bonus",
"LabelBooks": "Livres",
"LabelButtonText": "Texte du bouton",
"LabelByAuthor": "par {0}",
"LabelChangePassword": "Modifier le mot de passe",
"LabelChannels": "Canaux",
+ "LabelChapterCount": "{0} Chapitres",
"LabelChapterTitle": "Titre du chapitre",
"LabelChapters": "Chapitres",
"LabelChaptersFound": "chapitres trouvés",
diff --git a/client/strings/hr.json b/client/strings/hr.json
index cd15ec67..d7d0fde5 100644
--- a/client/strings/hr.json
+++ b/client/strings/hr.json
@@ -163,6 +163,7 @@
"HeaderNotificationUpdate": "Ažuriraj obavijest",
"HeaderNotifications": "Obavijesti",
"HeaderOpenIDConnectAuthentication": "Prijava na OpenID Connect",
+ "HeaderOpenListeningSessions": "Otvorene sesije slušanja",
"HeaderOpenRSSFeed": "Otvori RSS izvor",
"HeaderOtherFiles": "Druge datoteke",
"HeaderPasswordAuthentication": "Provjera autentičnosti zaporkom",
@@ -226,6 +227,7 @@
"LabelAllUsersExcludingGuests": "Svi korisnici osim gostiju",
"LabelAllUsersIncludingGuests": "Svi korisnici uključujući i goste",
"LabelAlreadyInYourLibrary": "Već u vašoj knjižnici",
+ "LabelApiToken": "API Token",
"LabelAppend": "Pridodaj",
"LabelAudioBitrate": "Kvaliteta zvučnog zapisa (npr. 128k)",
"LabelAudioChannels": "Broj zvučnih kanala (1 ili 2)",
@@ -252,9 +254,9 @@
"LabelBackupsNumberToKeepHelp": "Moguće je izbrisati samo jednu po jednu sigurnosnu kopiju, ako ih već imate više trebat ćete ih ručno ukloniti.",
"LabelBitrate": "Protok",
"LabelBonus": "Bonus",
- "LabelBooks": "knjiga/e",
+ "LabelBooks": "Knjige",
"LabelButtonText": "Tekst gumba",
- "LabelByAuthor": "po {0}",
+ "LabelByAuthor": "autor: {0}",
"LabelChangePassword": "Promijeni zaporku",
"LabelChannels": "Kanali",
"LabelChapterCount": "{0} Poglavlje/a",
@@ -268,7 +270,7 @@
"LabelCollapseSeries": "Serijale prikaži sažeto",
"LabelCollapseSubSeries": "Podserijale prikaži sažeto",
"LabelCollection": "Zbirka",
- "LabelCollections": "Zbirka/i",
+ "LabelCollections": "Zbirke",
"LabelComplete": "Dovršeno",
"LabelConfirmPassword": "Potvrda zaporke",
"LabelContinueListening": "Nastavi slušati",
@@ -358,6 +360,7 @@
"LabelFontScale": "Veličina slova",
"LabelFontStrikethrough": "Precrtano",
"LabelFormat": "Format",
+ "LabelFull": "Cijeli",
"LabelGenre": "Žanr",
"LabelGenres": "Žanrovi",
"LabelHardDeleteFile": "Obriši datoteku zauvijek",
@@ -462,12 +465,14 @@
"LabelOpenIDGroupClaimDescription": "Naziv OpenID zahtjeva koji sadrži popis korisnikovih grupa. Često se naziva groups
. Ako se konfigurira, aplikacija će automatski dodijeliti uloge temeljem korisnikovih članstava u grupama, pod uvjetom da se iste zovu 'admin', 'user' ili 'guest' u zahtjevu (ne razlikuju se velika i mala slova). Zahtjev treba sadržavati popis i ako je korisnik član više grupa, aplikacija će dodijeliti ulogu koja odgovara najvišoj razini pristupa. Ukoliko se niti jedna grupa ne podudara, pristup će biti onemogućen.",
"LabelOpenRSSFeed": "Otvori RSS Feed",
"LabelOverwrite": "Prepiši",
+ "LabelPaginationPageXOfY": "Stranica {0} od {1}",
"LabelPassword": "Zaporka",
"LabelPath": "Putanja",
"LabelPermanent": "Trajno",
"LabelPermissionsAccessAllLibraries": "Ima pristup svim knjižnicama",
"LabelPermissionsAccessAllTags": "Ima pristup svim oznakama",
"LabelPermissionsAccessExplicitContent": "Ima pristup eksplicitnom sadržaju",
+ "LabelPermissionsCreateEreader": "Može stvoriti e-čitač",
"LabelPermissionsDelete": "Smije brisati",
"LabelPermissionsDownload": "Smije preuzimati",
"LabelPermissionsUpdate": "Smije ažurirati",
@@ -491,8 +496,8 @@
"LabelPubDate": "Datum izdavanja",
"LabelPublishYear": "Godina objavljivanja",
"LabelPublishedDate": "Objavljeno {0}",
- "LabelPublishedDecade": "Desetljeće objavljivanja",
- "LabelPublishedDecades": "Desetljeća objavljivanja",
+ "LabelPublishedDecade": "Desetljeće izdanja",
+ "LabelPublishedDecades": "Desetljeća izdanja",
"LabelPublisher": "Izdavač",
"LabelPublishers": "Izdavači",
"LabelRSSFeedCustomOwnerEmail": "Prilagođena adresa e-pošte vlasnika",
@@ -530,7 +535,7 @@
"LabelSendEbookToDevice": "Pošalji e-knjigu",
"LabelSequence": "Slijed",
"LabelSerial": "Serijal",
- "LabelSeries": "Serijal/a",
+ "LabelSeries": "Serijal",
"LabelSeriesName": "Ime serijala",
"LabelSeriesProgress": "Napredak u serijalu",
"LabelServerLogLevel": "Razina zapisa poslužitelja",
@@ -558,6 +563,9 @@
"LabelSettingsHideSingleBookSeriesHelp": "Serijali koji se sastoje od samo jedne knjige neće se prikazivati na stranici serijala i na policama početne stranice.",
"LabelSettingsHomePageBookshelfView": "Prikaži početnu stranicu kao policu s knjigama",
"LabelSettingsLibraryBookshelfView": "Prikaži knjižnicu kao policu s knjigama",
+ "LabelSettingsLibraryMarkAsFinishedPercentComplete": "Postotak dovršenosti veći od",
+ "LabelSettingsLibraryMarkAsFinishedTimeRemaining": "Preostalo vrijeme je manje od (sekundi)",
+ "LabelSettingsLibraryMarkAsFinishedWhen": "Označi medij dovršenim kada",
"LabelSettingsOnlyShowLaterBooksInContinueSeries": "Preskoči ranije knjige u funkciji Nastavi serijal",
"LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "Na polici početne stranice Nastavi serijal prikazuje se prva nezapočeta knjiga serijala koji imaju barem jednu dovršenu knjigu i nijednu započetu knjigu. Ako uključite ovu opciju, serijal će vam se nastaviti od zadnje dovršene knjige umjesto od prve nezapočete knjige.",
"LabelSettingsParseSubtitles": "Raščlani podnaslove",
@@ -639,7 +647,7 @@
"LabelTotalTimeListened": "Sveukupno vrijeme slušanja",
"LabelTrackFromFilename": "Naslov iz imena datoteke",
"LabelTrackFromMetadata": "Naslov iz meta-podataka",
- "LabelTracks": "Naslovi",
+ "LabelTracks": "Zvučni zapisi",
"LabelTracksMultiTrack": "Više zvučnih zapisa",
"LabelTracksNone": "Nema zapisa",
"LabelTracksSingleTrack": "Jedan zvučni zapis",
@@ -740,7 +748,7 @@
"MessageConfirmSendEbookToDevice": "Sigurno želite poslati {0} e-knjiga/u \"{1}\" na uređaj \"{2}\"?",
"MessageConfirmUnlinkOpenId": "Sigurno želite odspojiti ovog korisnika s OpenID-ja?",
"MessageDownloadingEpisode": "Preuzimam nastavak",
- "MessageDragFilesIntoTrackOrder": "Ispravi redoslijed zapisa prevlačenje datoteka",
+ "MessageDragFilesIntoTrackOrder": "Prevlačenjem datoteka složite pravilan redoslijed",
"MessageEmbedFailed": "Ugrađivanje nije uspjelo!",
"MessageEmbedFinished": "Ugrađivanje je dovršeno!",
"MessageEmbedQueue": "Ugrađivanje meta-podataka dodano u red obrade ({0} u redu)",
diff --git a/client/strings/hu.json b/client/strings/hu.json
index 79852a9c..8ab6be9e 100644
--- a/client/strings/hu.json
+++ b/client/strings/hu.json
@@ -66,6 +66,7 @@
"ButtonPurgeItemsCache": "Elemek gyorsítótárának törlése",
"ButtonQueueAddItem": "Hozzáadás a sorhoz",
"ButtonQueueRemoveItem": "Eltávolítás a sorból",
+ "ButtonQuickEmbed": "Gyors beágyazás",
"ButtonQuickEmbedMetadata": "Metaadat gyors beágyazása",
"ButtonQuickMatch": "Gyors egyeztetés",
"ButtonReScan": "Újraszkennelés",
@@ -343,7 +344,7 @@
"LabelHasSupplementaryEbook": "Van kiegészítő e-könyve",
"LabelHideSubtitles": "Alcím elrejtése",
"LabelHighestPriority": "Legmagasabb prioritás",
- "LabelHost": "Házigazda",
+ "LabelHost": "Kiszolgáló",
"LabelHour": "Óra",
"LabelHours": "Órák",
"LabelIcon": "Ikon",
diff --git a/client/strings/nl.json b/client/strings/nl.json
index aad7f301..bc5a40ca 100644
--- a/client/strings/nl.json
+++ b/client/strings/nl.json
@@ -30,6 +30,8 @@
"ButtonEditChapters": "Hoofdstukken wijzigen",
"ButtonEditPodcast": "Podcast wijzigen",
"ButtonEnable": "Aanzetten",
+ "ButtonFireAndFail": "Fire and Fail",
+ "ButtonFireOnTest": "Fire onTest event",
"ButtonForceReScan": "Forceer nieuwe scan",
"ButtonFullPath": "Volledig pad",
"ButtonHide": "Verberg",
@@ -65,6 +67,7 @@
"ButtonQueueAddItem": "In wachtrij zetten",
"ButtonQueueRemoveItem": "Uit wachtrij verwijderen",
"ButtonQuickEmbed": "Snel Embedden",
+ "ButtonQuickEmbedMetadata": "Snel Metadata Insluiten",
"ButtonQuickMatch": "Snelle match",
"ButtonReScan": "Nieuwe scan",
"ButtonRead": "Lees",
@@ -97,6 +100,8 @@
"ButtonStats": "Statistieken",
"ButtonSubmit": "Indienen",
"ButtonTest": "Testen",
+ "ButtonUnlinkOpenId": "OpenID Ontkoppelen",
+ "ButtonUpload": "Upload",
"ButtonUploadBackup": "Upload back-up",
"ButtonUploadCover": "Upload cover",
"ButtonUploadOPMLFile": "Upload OPML-bestand",
@@ -108,10 +113,12 @@
"ErrorUploadFetchMetadataNoResults": "Kan metadata niet ophalen - probeer de titel en/of auteur te updaten",
"ErrorUploadLacksTitle": "Moet een titel hebben",
"HeaderAccount": "Account",
+ "HeaderAddCustomMetadataProvider": "Aangepaste Metadataprovider Toevoegen",
"HeaderAdvanced": "Geavanceerd",
"HeaderAppriseNotificationSettings": "Apprise-notificatie instellingen",
"HeaderAudioTracks": "Audiotracks",
"HeaderAudiobookTools": "Audioboekbestandbeheer tools",
+ "HeaderAuthentication": "Authenticatie",
"HeaderBackups": "Back-ups",
"HeaderChangePassword": "Wachtwoord wijzigen",
"HeaderChapters": "Hoofdstukken",
@@ -120,6 +127,8 @@
"HeaderCollectionItems": "Collectie-objecten",
"HeaderCover": "Omslag",
"HeaderCurrentDownloads": "Huidige downloads",
+ "HeaderCustomMessageOnLogin": "Aangepast Bericht bij Aanmelden",
+ "HeaderCustomMetadataProviders": "Aangepaste Metadata Providers",
"HeaderDetails": "Details",
"HeaderDownloadQueue": "Download-wachtrij",
"HeaderEbookFiles": "Ebook bestanden",
@@ -140,16 +149,27 @@
"HeaderLibraryStats": "Bibliotheekstatistieken",
"HeaderListeningSessions": "Luistersessies",
"HeaderListeningStats": "Luisterstatistieken",
+ "HeaderLogin": "Aanmelden",
+ "HeaderLogs": "Logboek",
"HeaderManageGenres": "Genres beheren",
"HeaderManageTags": "Tags beheren",
+ "HeaderMapDetails": "Map details",
+ "HeaderMatch": "Vergelijken",
+ "HeaderMetadataOrderOfPrecedence": "Metadata volgorde",
"HeaderMetadataToEmbed": "In te sluiten metadata",
"HeaderNewAccount": "Nieuwe account",
"HeaderNewLibrary": "Nieuwe bibliotheek",
+ "HeaderNotificationCreate": "Notificatie Aanmaken",
+ "HeaderNotificationUpdate": "Update Notificatie",
"HeaderNotifications": "Notificaties",
+ "HeaderOpenIDConnectAuthentication": "OpenID Connect Authenticatie",
+ "HeaderOpenListeningSessions": "Open Luistersessies",
"HeaderOpenRSSFeed": "Open RSS-feed",
"HeaderOtherFiles": "Andere bestanden",
+ "HeaderPasswordAuthentication": "Wachtwoord Authenticatie",
"HeaderPermissions": "Toestemmingen",
"HeaderPlayerQueue": "Afspeelwachtrij",
+ "HeaderPlayerSettings": "Speler Instellingen",
"HeaderPlaylist": "Afspeellijst",
"HeaderPlaylistItems": "Onderdelen in afspeellijst",
"HeaderPodcastsToAdd": "Toe te voegen podcasts",
@@ -161,6 +181,7 @@
"HeaderRemoveEpisodes": "Verwijder {0} afleveringen",
"HeaderSavedMediaProgress": "Opgeslagen mediavoortgang",
"HeaderSchedule": "Schema",
+ "HeaderScheduleEpisodeDownloads": "Automatische afleveringsdownloads plannen",
"HeaderScheduleLibraryScans": "Schema automatische bibliotheekscans",
"HeaderSession": "Sessie",
"HeaderSetBackupSchedule": "Kies schema voor back-up",
@@ -168,6 +189,7 @@
"HeaderSettingsDisplay": "Toon",
"HeaderSettingsExperimental": "Experimentele functies",
"HeaderSettingsGeneral": "Algemeen",
+ "HeaderSettingsScanner": "Scanner",
"HeaderSleepTimer": "Slaaptimer",
"HeaderStatsLargestItems": "Grootste items",
"HeaderStatsLongestItems": "Langste items (uren)",
@@ -176,13 +198,18 @@
"HeaderStatsTop10Authors": "Top 10 auteurs",
"HeaderStatsTop5Genres": "Top 5 genres",
"HeaderTableOfContents": "Inhoudsopgave",
+ "HeaderTools": "Gereedschap",
"HeaderUpdateAccount": "Account bijwerken",
"HeaderUpdateAuthor": "Auteur bijwerken",
"HeaderUpdateDetails": "Details bijwerken",
"HeaderUpdateLibrary": "Bibliotheek bijwerken",
"HeaderUsers": "Gebruikers",
+ "HeaderYearReview": "Jaar {0} in Review",
"HeaderYourStats": "Je statistieken",
"LabelAbridged": "Verkort",
+ "LabelAbridgedChecked": "Verkort (gechecked)",
+ "LabelAbridgedUnchecked": "Onverkort (niet gechecked)",
+ "LabelAccessibleBy": "Toegankelijk door",
"LabelAccountType": "Accounttype",
"LabelAccountTypeAdmin": "Beheerder",
"LabelAccountTypeGuest": "Gast",
@@ -193,32 +220,55 @@
"LabelAddToPlaylist": "Toevoegen aan afspeellijst",
"LabelAddToPlaylistBatch": "{0} onderdelen toevoegen aan afspeellijst",
"LabelAddedAt": "Toegevoegd op",
+ "LabelAddedDate": "Toegevoegd {0}",
+ "LabelAdminUsersOnly": "Enkel Admin gebruikers",
"LabelAll": "Alle",
"LabelAllUsers": "Alle gebruikers",
+ "LabelAllUsersExcludingGuests": "Alle gebruikers exclusief gasten",
+ "LabelAllUsersIncludingGuests": "Alle gebruikers inclusief gasten",
"LabelAlreadyInYourLibrary": "Reeds in je bibliotheek",
+ "LabelApiToken": "API Token",
"LabelAppend": "Achteraan toevoegen",
+ "LabelAudioBitrate": "Audio Bitrate (b.v. 128k)",
+ "LabelAudioChannels": "Audio Kanalen (1 of 2)",
+ "LabelAudioCodec": "Audio Codec",
"LabelAuthor": "Auteur",
"LabelAuthorFirstLast": "Auteur (Voornaam Achternaam)",
"LabelAuthorLastFirst": "Auteur (Achternaam, Voornaam)",
"LabelAuthors": "Auteurs",
"LabelAutoDownloadEpisodes": "Afleveringen automatisch downloaden",
+ "LabelAutoFetchMetadata": "Automatisch Metadata Ophalen",
+ "LabelAutoFetchMetadataHelp": "Haalt metadata op voor titel, auteur en serie om het uploaden te stroomlijnen. Aanvullende metadata moet mogelijk worden gematcht na het uploaden.",
+ "LabelAutoLaunch": "Automatisch Openen",
+ "LabelAutoLaunchDescription": "Automatisch doorverwijzen naar de auth-provider bij het navigeren naar de inlogpagina (handmatig pad /login?autoLaunch=0
)",
+ "LabelAutoRegister": "Automatisch Registreren",
+ "LabelAutoRegisterDescription": "Automatisch nieuwe gebruikers aanmaken na inloggen",
"LabelBackToUser": "Terug naar gebruiker",
+ "LabelBackupAudioFiles": "Back-up audiobestanden",
"LabelBackupLocation": "Back-up locatie",
"LabelBackupsEnableAutomaticBackups": "Automatische back-ups inschakelen",
"LabelBackupsEnableAutomaticBackupsHelp": "Back-ups opgeslagen in /metadata/backups",
- "LabelBackupsMaxBackupSize": "Maximale back-up-grootte (in GB)",
+ "LabelBackupsMaxBackupSize": "Maximale back-up-grootte (in GB) (0 voor ongelimiteerd)",
"LabelBackupsMaxBackupSizeHelp": "Als een beveiliging tegen verkeerde instelling, zullen back-up mislukken als ze de ingestelde grootte overschrijden.",
"LabelBackupsNumberToKeep": "Aantal te bewaren back-ups",
"LabelBackupsNumberToKeepHelp": "Er wordt slechts 1 back-up per keer verwijderd, dus als je reeds meer back-ups dan dit hebt moet je ze handmatig verwijderen.",
+ "LabelBitrate": "Bitrate",
+ "LabelBonus": "Bonus",
"LabelBooks": "Boeken",
+ "LabelButtonText": "Knop Tekst",
+ "LabelByAuthor": "Door {0}",
"LabelChangePassword": "Wachtwoord wijzigen",
"LabelChannels": "Kanalen",
+ "LabelChapterCount": "{0} Hoofdstukken",
"LabelChapterTitle": "Hoofdstuktitel",
"LabelChapters": "Hoofdstukken",
"LabelChaptersFound": "Hoofdstukken gevonden",
"LabelClickForMoreInfo": "Klik voor meer informatie",
+ "LabelClickToUseCurrentValue": "Klik om huidige waarde te gebruiken",
"LabelClosePlayer": "Sluit speler",
+ "LabelCodec": "Codec",
"LabelCollapseSeries": "Series inklappen",
+ "LabelCollapseSubSeries": "Subserie samenvouwen",
"LabelCollection": "Collectie",
"LabelCollections": "Collecties",
"LabelComplete": "Compleet",
@@ -226,6 +276,7 @@
"LabelContinueListening": "Verder Luisteren",
"LabelContinueReading": "Verder lezen",
"LabelContinueSeries": "Doorgaan met Serie",
+ "LabelCover": "Omslag",
"LabelCoverImageURL": "Coverafbeelding URL",
"LabelCreatedAt": "Gecreëerd op",
"LabelCronExpression": "Cron-uitdrukking",
@@ -234,38 +285,68 @@
"LabelCustomCronExpression": "Aangepaste Cron-uitdrukking:",
"LabelDatetime": "Datum-tijd",
"LabelDays": "Dagen",
+ "LabelDeleteFromFileSystemCheckbox": "Verwijderen uit bestandssysteem (uncheck om alleen uit database te verwijderen)",
"LabelDescription": "Beschrijving",
"LabelDeselectAll": "Deselecteer alle",
"LabelDevice": "Apparaat",
"LabelDeviceInfo": "Apparaat info",
+ "LabelDeviceIsAvailableTo": "Apparaat is beschikbaar voor...",
"LabelDirectory": "Map",
"LabelDiscFromFilename": "Schijf uit bestandsnaam",
"LabelDiscFromMetadata": "Schijf uit metadata",
"LabelDiscover": "Ontdekken",
"LabelDownload": "Download",
+ "LabelDownloadNEpisodes": "Download {0} afleveringen",
"LabelDuration": "Duur",
+ "LabelDurationComparisonExactMatch": "(exacte overeenkomst)",
+ "LabelDurationComparisonLonger": "({0} langer)",
+ "LabelDurationComparisonShorter": "({0} korter)",
"LabelDurationFound": "Gevonden duur:",
"LabelEbook": "Ebook",
"LabelEbooks": "Eboeken",
"LabelEdit": "Wijzig",
+ "LabelEmail": "Email",
"LabelEmailSettingsFromAddress": "Van-adres",
+ "LabelEmailSettingsRejectUnauthorized": "Ongeautoriseerde certificaten afwijzen",
+ "LabelEmailSettingsRejectUnauthorizedHelp": "Het uitschakelen van SSL-certificaatvalidatie kan uw verbinding blootstellen aan beveiligingsrisico's, zoals man-in-the-middle-aanvallen. Schakel deze optie alleen uit als u de implicaties begrijpt en de mailserver waarmee u verbinding maakt vertrouwt.",
"LabelEmailSettingsSecure": "Veilig",
"LabelEmailSettingsSecureHelp": "Als 'waar', dan gebruikt de verbinding TLS om met de server te verbinden. Als 'onwaar', dan wordt TLS gebruikt als de server de STARTTLS-extensie ondersteunt. In de meeste gevallen kies je voor 'waar' verbindt met poort 465. Voo poort 587 of 25, laat op 'onwaar'. (van nodemailer.com/smtp/#authentication)",
"LabelEmailSettingsTestAddress": "Test-adres",
"LabelEmbeddedCover": "Ingesloten cover",
"LabelEnable": "Inschakelen",
+ "LabelEncodingBackupLocation": "Er wordt een back-up van uw originele audiobestanden opgeslagen in:",
+ "LabelEncodingChaptersNotEmbedded": "Hoofdstukken zijn niet ingesloten in audioboeken met meerdere sporen.",
+ "LabelEncodingClearItemCache": "Zorg ervoor dat u de cache van items regelmatig wist.",
+ "LabelEncodingFinishedM4B": "Een voltooide M4B wordt in uw audioboekfolder geplaatst in:",
+ "LabelEncodingInfoEmbedded": "Metagegevens worden ingesloten in de audiotracks in uw audioboekmap.",
+ "LabelEncodingStartedNavigation": "Eenmaal de taak is gestart kan u weg navigeren van deze pagina.",
+ "LabelEncodingTimeWarning": "Encoding kan tot 30 minuten duren.",
+ "LabelEncodingWarningAdvancedSettings": "Waarschuwing: update deze instellingen niet tenzij u bekend bent met de coderingsopties van ffmpeg.",
+ "LabelEncodingWatcherDisabled": "Als u de watcher hebt uitgeschakeld, moet u het audioboek daarna opnieuw scannen.",
"LabelEnd": "Einde",
"LabelEndOfChapter": "Einde van het Hoofdstuk",
"LabelEpisode": "Aflevering",
+ "LabelEpisodeNotLinkedToRssFeed": "Aflevering niet gelinkt aan RSS feed",
+ "LabelEpisodeNumber": "Aflevering #{0}",
"LabelEpisodeTitle": "Afleveringtitel",
"LabelEpisodeType": "Afleveringtype",
+ "LabelEpisodeUrlFromRssFeed": "Aflevering URL van RSS feed",
+ "LabelEpisodes": "Afleveringen",
+ "LabelEpisodic": "Episodisch",
"LabelExample": "Voorbeeld",
+ "LabelExpandSeries": "Serie Uitvouwen",
+ "LabelExpandSubSeries": "Subserie Uitvouwen",
"LabelExplicit": "Expliciet",
+ "LabelExplicitChecked": "Expliciet (gechecked)",
+ "LabelExplicitUnchecked": "Niet Expliciet (niet gechecked)",
+ "LabelExportOPML": "OPML exporteren",
"LabelFeedURL": "Feed URL",
"LabelFetchingMetadata": "Metadata ophalen",
"LabelFile": "Bestand",
"LabelFileBirthtime": "Aanmaaktijd bestand",
+ "LabelFileBornDate": "Geboren {0}",
"LabelFileModified": "Bestand gewijzigd",
+ "LabelFileModifiedDate": "Gewijzigd {0}",
"LabelFilename": "Bestandsnaam",
"LabelFilterByUser": "Filter op gebruiker",
"LabelFindEpisodes": "Zoek afleveringen",
@@ -275,20 +356,27 @@
"LabelFontBold": "Vetgedrukt",
"LabelFontBoldness": "Font Boldness",
"LabelFontFamily": "Lettertypefamilie",
+ "LabelFontItalic": "Cursief",
"LabelFontScale": "Lettertype schaal",
+ "LabelFontStrikethrough": "Doorgestreept",
"LabelFormat": "Formaat",
+ "LabelFull": "Vol",
"LabelGenre": "Genre",
"LabelGenres": "Genres",
"LabelHardDeleteFile": "Hard-delete bestand",
"LabelHasEbook": "Heeft Ebook",
"LabelHasSupplementaryEbook": "Heeft aanvullend Ebook",
+ "LabelHideSubtitles": "Ondertitels Verstoppen",
+ "LabelHighestPriority": "Hoogste Prioriteit",
"LabelHost": "Host",
"LabelHour": "Uur",
"LabelHours": "Uren",
"LabelIcon": "Icoon",
+ "LabelImageURLFromTheWeb": "Afbeelding URL van web",
"LabelInProgress": "Bezig",
"LabelIncludeInTracklist": "Includeer in tracklijst",
"LabelIncomplete": "Incompleet",
+ "LabelInterval": "Interval",
"LabelIntervalCustomDailyWeekly": "Aangepast dagelijks/wekelijks",
"LabelIntervalEvery12Hours": "Iedere 12 uur",
"LabelIntervalEvery15Minutes": "Iedere 15 minuten",
@@ -299,8 +387,11 @@
"LabelIntervalEveryHour": "Ieder uur",
"LabelInvert": "Omdraaien",
"LabelItem": "Onderdeel",
+ "LabelJumpBackwardAmount": "Terugspoelen hoeveelheid",
+ "LabelJumpForwardAmount": "Vooruitspoelen hoeveelheid",
"LabelLanguage": "Taal",
"LabelLanguageDefaultServer": "Standaard servertaal",
+ "LabelLanguages": "Talen",
"LabelLastBookAdded": "Laatst toegevoegde boek",
"LabelLastBookUpdated": "Laatst bijgewerkte boek",
"LabelLastSeen": "Laatst gezien",
@@ -312,20 +403,36 @@
"LabelLess": "Minder",
"LabelLibrariesAccessibleToUser": "Voor gebruiker toegankelijke bibliotheken",
"LabelLibrary": "Bibliotheek",
+ "LabelLibraryFilterSublistEmpty": "Nee {0}",
"LabelLibraryItem": "Bibliotheekonderdeel",
"LabelLibraryName": "Bibliotheeknaam",
"LabelLimit": "Limiet",
"LabelLineSpacing": "Regelruimte",
"LabelListenAgain": "Opnieuw Beluisteren",
+ "LabelLogLevelDebug": "Debug",
+ "LabelLogLevelInfo": "Informatie",
"LabelLogLevelWarn": "Waarschuwing",
"LabelLookForNewEpisodesAfterDate": "Zoek naar nieuwe afleveringen na deze datum",
+ "LabelLowestPriority": "Laagste Prioriteit",
+ "LabelMatchExistingUsersBy": "Bestaande gebruikers matchen op",
+ "LabelMatchExistingUsersByDescription": "Wordt gebruikt om bestaande gebruikers te verbinden. Zodra ze verbonden zijn, worden gebruikers gekoppeld aan een unieke id van uw SSO-provider.",
+ "LabelMaxEpisodesToDownload": "Maximale # afleveringen om te downloaden. Gebruik 0 voor ongelimiteerd.",
+ "LabelMaxEpisodesToDownloadPerCheck": "Maximale # nieuwe afleveringen om te downloaden per check",
+ "LabelMaxEpisodesToKeep": "Maximale # afleveringen om te houden",
+ "LabelMaxEpisodesToKeepHelp": "Waarde van 0 stelt geen maximumlimiet in. Nadat een nieuwe aflevering automatisch is gedownload, wordt de oudste aflevering verwijderd als u meer dan X afleveringen hebt. Hiermee wordt slechts 1 aflevering per nieuwe download verwijderd.",
"LabelMediaPlayer": "Mediaspeler",
"LabelMediaType": "Mediatype",
"LabelMetaTag": "Meta-tag",
"LabelMetaTags": "Meta-tags",
+ "LabelMetadataOrderOfPrecedenceDescription": "Metadatabronnen met een hogere prioriteit zullen metadatabronnen met een lagere prioriteit overschrijven",
"LabelMetadataProvider": "Metadatabron",
"LabelMinute": "Minuut",
+ "LabelMinutes": "Minuten",
"LabelMissing": "Ontbrekend",
+ "LabelMissingEbook": "Heeft geen ebook",
+ "LabelMissingSupplementaryEbook": "Heeft geen supplementair ebook",
+ "LabelMobileRedirectURIs": "Toegestane mobiele omleidings-URL's",
+ "LabelMobileRedirectURIsDescription": "Dit is een whitelist met geldige redirect-URI's voor mobiele apps. De standaard is audiobookshelf://oauth
, die u kunt verwijderen of aanvullen met extra URI's voor integratie met apps van derden. Als u een asterisk (*
) als enige invoer gebruikt, is elke URI toegestaan.",
"LabelMore": "Meer",
"LabelMoreInfo": "Meer info",
"LabelName": "Naam",
@@ -337,10 +444,12 @@
"LabelNewestEpisodes": "Nieuwste Afleveringen",
"LabelNextBackupDate": "Volgende back-up datum",
"LabelNextScheduledRun": "Volgende geplande run",
+ "LabelNoCustomMetadataProviders": "Geen custom metadata bronnen",
"LabelNoEpisodesSelected": "Geen afleveringen geselecteerd",
"LabelNotFinished": "Niet Voltooid",
"LabelNotStarted": "Niet Gestart",
"LabelNotes": "Notities",
+ "LabelNotificationAppriseURL": "URL(s) van kennisgeving",
"LabelNotificationAvailableVariables": "Beschikbare variabelen",
"LabelNotificationBodyTemplate": "Body-template",
"LabelNotificationEvent": "Notificatie gebeurtenis",
@@ -351,10 +460,15 @@
"LabelNotificationsMaxQueueSizeHelp": "Gebeurtenissen zijn beperkt tot 1 aftrap per seconde. Gebeurtenissen zullen genegeerd worden als de rij aan de maximale grootte zit. Dit voorkomt notificatie-spamming.",
"LabelNumberOfBooks": "Aantal Boeken",
"LabelNumberOfEpisodes": "# afleveringen",
+ "LabelOpenIDAdvancedPermsClaimDescription": "Naam van de OpenID-claim die geavanceerde machtigingen bevat voor gebruikersacties binnen de applicatie die van toepassing zijn op niet-beheerdersrollen (indien geconfigureerd). Als de claim ontbreekt in het antwoord, wordt toegang tot ABS geweigerd. Als er één optie ontbreekt, wordt deze behandeld als false
. Zorg ervoor dat de claim van de identiteitsprovider overeenkomt met de verwachte structuur:",
+ "LabelOpenIDClaims": "Laat de volgende opties leeg om geavanceerde groeps- en machtigingstoewijzing uit te schakelen en de groep 'Gebruiker' automatisch toe te wijzen.",
+ "LabelOpenIDGroupClaimDescription": "Naam van de OpenID-claim die een lijst met de groepen van de gebruiker bevat. Vaak aangeduid als groepen
. Indien geconfigureerd, zal de applicatie automatisch rollen toewijzen op basis van de groepslidmaatschappen van de gebruiker, op voorwaarde dat deze groepen hoofdlettergevoelig 'admin', 'gebruiker' of 'gast' worden genoemd in de claim. De claim moet een lijst bevatten en als een gebruiker tot meerdere groepen behoort, zal de applicatie de rol toewijzen die overeenkomt met het hoogste toegangsniveau. Als er geen groep overeenkomt, wordt de toegang geweigerd.",
"LabelOpenRSSFeed": "Open RSS-feed",
"LabelOverwrite": "Overschrijf",
+ "LabelPaginationPageXOfY": "Pagina {0} van {1}",
"LabelPassword": "Wachtwoord",
"LabelPath": "Pad",
+ "LabelPermanent": "Permanent",
"LabelPermissionsAccessAllLibraries": "Heeft toegang tot all bibliotheken",
"LabelPermissionsAccessAllTags": "Heeft toegang tot alle tags",
"LabelPermissionsAccessExplicitContent": "Heeft toegang tot expliciete inhoud",
@@ -362,21 +476,29 @@
"LabelPermissionsDownload": "Kan downloaden",
"LabelPermissionsUpdate": "Kan bijwerken",
"LabelPermissionsUpload": "Kan uploaden",
+ "LabelPersonalYearReview": "Jouw jaar in review ({0})",
"LabelPhotoPathURL": "Foto pad/URL",
"LabelPlayMethod": "Afspeelwijze",
+ "LabelPlayerChapterNumberMarker": "{0} van {1}",
"LabelPlaylists": "Afspeellijsten",
"LabelPodcast": "Podcast",
"LabelPodcastSearchRegion": "Podcast zoekregio",
"LabelPodcastType": "Podcasttype",
+ "LabelPodcasts": "Podcasts",
"LabelPort": "Poort",
"LabelPrefixesToIgnore": "Te negeren voorzetsels (ongeacht hoofdlettergebruik)",
"LabelPreventIndexing": "Voorkom indexering van je feed door iTunes- en Google podcastmappen",
"LabelPrimaryEbook": "Primair ebook",
"LabelProgress": "Voortgang",
"LabelProvider": "Bron",
+ "LabelProviderAuthorizationValue": "Autorisatie Header Waarde",
"LabelPubDate": "Publicatiedatum",
"LabelPublishYear": "Jaar van uitgave",
+ "LabelPublishedDate": "Gepubliceerd {0}",
+ "LabelPublishedDecade": "Gepubliceerd Decennium",
+ "LabelPublishedDecades": "Gepubliceerd Decennia",
"LabelPublisher": "Uitgever",
+ "LabelPublishers": "Uitgevers",
"LabelRSSFeedCustomOwnerEmail": "Aangepast e-mailadres eigenaar",
"LabelRSSFeedCustomOwnerName": "Aangepaste naam eigenaar",
"LabelRSSFeedOpen": "RSS-feed open",
@@ -384,31 +506,45 @@
"LabelRSSFeedSlug": "RSS-feed slug",
"LabelRSSFeedURL": "RSS-feed URL",
"LabelRandomly": "Willekeurig",
+ "LabelReAddSeriesToContinueListening": "Serie opnieuw toevoegen aan verder luisteren",
"LabelRead": "Lees",
"LabelReadAgain": "Opnieuw Lezen",
"LabelReadEbookWithoutProgress": "Lees ebook zonder voortgang bij te houden",
"LabelRecentSeries": "Recente Serie",
"LabelRecentlyAdded": "Recent Toegevoegd",
"LabelRecommended": "Aangeraden",
+ "LabelRedo": "Opnieuw",
"LabelRegion": "Regio",
"LabelReleaseDate": "Verschijningsdatum",
+ "LabelRemoveAllMetadataAbs": "Verwijder alle metadata.abs bestanden",
+ "LabelRemoveAllMetadataJson": "Verwijder alle metadata.json bestanden",
"LabelRemoveCover": "Verwijder cover",
+ "LabelRemoveMetadataFile": "Verwijder metadata bestanden in bibliotheek item folders",
+ "LabelRemoveMetadataFileHelp": "Verwijder alle metadata.json en metadata.abs bestanden in uw {0} folders.",
+ "LabelRowsPerPage": "Rijen per pagina",
"LabelSearchTerm": "Zoekterm",
"LabelSearchTitle": "Zoek titel",
"LabelSearchTitleOrASIN": "Zoek titel of ASIN",
"LabelSeason": "Seizoen",
+ "LabelSeasonNumber": "Seizoen #{0}",
+ "LabelSelectAll": "Alles selecteren",
"LabelSelectAllEpisodes": "Selecteer alle afleveringen",
"LabelSelectEpisodesShowing": "Selecteer {0} afleveringen laten zien",
+ "LabelSelectUsers": "Selecteer gebruikers",
"LabelSendEbookToDevice": "Stuur ebook naar...",
"LabelSequence": "Sequentie",
+ "LabelSerial": "Serie",
"LabelSeries": "Serie",
"LabelSeriesName": "Naam serie",
"LabelSeriesProgress": "Voortgang serie",
+ "LabelServerLogLevel": "Server Log Niveau",
+ "LabelServerYearReview": "Server Jaar in Review ({0})",
"LabelSetEbookAsPrimary": "Stel in als primair",
"LabelSetEbookAsSupplementary": "Stel in als supplementair",
"LabelSettingsAudiobooksOnly": "Alleen audiobooks",
"LabelSettingsAudiobooksOnlyHelp": "Deze instelling inschakelen zorgt ervoor dat ebook-bestanden genegeerd worden tenzij ze in een audiobook-map staan, in welk geval ze worden ingesteld als supplementaire ebooks",
"LabelSettingsBookshelfViewHelp": "Skeumorphisch design met houten planken",
+ "LabelSettingsChromecastSupport": "Chromecast ondersteuning",
"LabelSettingsDateFormat": "Datum format",
"LabelSettingsDisableWatcher": "Watcher uitschakelen",
"LabelSettingsDisableWatcherForLibrary": "Map-watcher voor bibliotheek uitschakelen",
@@ -416,6 +552,8 @@
"LabelSettingsEnableWatcher": "Watcher inschakelen",
"LabelSettingsEnableWatcherForLibrary": "Map-watcher voor bibliotheek inschakelen",
"LabelSettingsEnableWatcherHelp": "Zorgt voor het automatisch toevoegen/bijwerken van onderdelen als bestandswijzigingen worden gedetecteerd. *Vereist herstarten van server",
+ "LabelSettingsEpubsAllowScriptedContent": "Sta scripted content toe in epubs",
+ "LabelSettingsEpubsAllowScriptedContentHelp": "Sta toe dat epub-bestanden scripts uitvoeren. Het wordt aanbevolen om deze instelling uitgeschakeld te houden, tenzij u de bron van de epub-bestanden vertrouwt.",
"LabelSettingsExperimentalFeatures": "Experimentele functies",
"LabelSettingsExperimentalFeaturesHelp": "Functies in ontwikkeling die je feedback en testing kunnen gebruiken. Klik om de Github-discussie te openen.",
"LabelSettingsFindCovers": "Zoek covers",
@@ -424,6 +562,8 @@
"LabelSettingsHideSingleBookSeriesHelp": "Series die slechts een enkel boek bevatten worden verborgen op de seriespagina en de homepagina-planken.",
"LabelSettingsHomePageBookshelfView": "Boekenplank-view voor homepagina",
"LabelSettingsLibraryBookshelfView": "Boekenplank-view voor bibliotheek",
+ "LabelSettingsOnlyShowLaterBooksInContinueSeries": "Sla eedere boeken in Serie Verderzetten over",
+ "LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "De Continue Series home page shelf toont het eerste boek dat nog niet is begonnen in series waarvan er minstens één is voltooid en er geen boeken in uitvoering zijn. Als u deze instelling inschakelt, wordt de serie voortgezet vanaf het boek dat het verst is voltooid in plaats van het eerste boek dat nog niet is begonnen.",
"LabelSettingsParseSubtitles": "Parseer subtitel",
"LabelSettingsParseSubtitlesHelp": "Haal subtitels uit mapnaam van audioboek.http://192.168.1.1:8337
dan zou je http://192.168.1.1:8337/notify
gebruiken.",
"MessageBackupsDescription": "Back-ups omvatten gebruikers, gebruikers' voortgang, bibliotheekonderdeeldetails, serverinstellingen en afbeeldingen bewaard in /metadata/items
& /metadata/authors
. Back-ups bevatten niet de bestanden bewaard in je bibliotheekmappen.",
+ "MessageBackupsLocationEditNote": "Let op: het bijwerken van de back-uplocatie zal bestaande back-ups niet verplaatsen of wijzigen",
+ "MessageBackupsLocationNoEditNote": "Let op: De back-uplocatie wordt ingesteld via een omgevingsvariabele en kan hier niet worden gewijzigd.",
+ "MessageBackupsLocationPathEmpty": "Backup locatie pad kan niet leeg zijn",
"MessageBatchQuickMatchDescription": "Quick Match zal proberen ontbrekende covers en metadata voor de geselecteerde onderdelen te matchten. Schakel de opties hieronder in om Quick Match toe te staan bestaande covers en/of metadata te overschrijven.",
"MessageBookshelfNoCollections": "Je hebt nog geen collecties gemaakt",
"MessageBookshelfNoRSSFeeds": "Geen RSS-feeds geopend",
"MessageBookshelfNoResultsForFilter": "Geen resultaten voor filter \"{0}: {1}\"",
+ "MessageBookshelfNoResultsForQuery": "Geen resultaten voor query",
"MessageBookshelfNoSeries": "Je hebt geen series",
"MessageChapterEndIsAfter": "Hoofdstukeinde is na het einde van je audioboek",
"MessageChapterErrorFirstNotZero": "Eerste hoofdstuk moet starten op 0",
@@ -529,21 +701,37 @@
"MessageChapterErrorStartLtPrev": "Ongeldig: starttijd moet be groter zijn dan of equal aan starttijd van vorig hoofdstuk",
"MessageChapterStartIsAfter": "Start van hoofdstuk is na het einde van je audioboek",
"MessageCheckingCron": "Cron aan het checken...",
+ "MessageConfirmCloseFeed": "Ben je zeker dat je deze feed wil sluiten?",
"MessageConfirmDeleteBackup": "Weet je zeker dat je de backup voor {0} wil verwijderen?",
+ "MessageConfirmDeleteDevice": "Ben je zeker dat je e-reader apparaat \"{0}\" wil verwijderen?",
"MessageConfirmDeleteFile": "Dit verwijdert het bestand uit het bestandssysteem. Weet je het zeker?",
"MessageConfirmDeleteLibrary": "Weet je zeker dat je de bibliotheek \"{0}\" permanent wil verwijderen?",
+ "MessageConfirmDeleteLibraryItem": "Hiermee wordt het bibliotheekitem uit de database en uw bestandssysteem verwijderd. Bent u zeker?",
+ "MessageConfirmDeleteLibraryItems": "Hiermee worden {0} bibliotheekitems uit de database en uw bestandssysteem verwijderd. Bent u zeker?",
+ "MessageConfirmDeleteMetadataProvider": "Weet u zeker dat u de aangepaste metadataprovider \"{0}\" wilt verwijderen?",
+ "MessageConfirmDeleteNotification": "Weet u zeker dat u deze melding wil verwijderen?",
"MessageConfirmDeleteSession": "Weet je zeker dat je deze sessie wil verwijderen?",
+ "MessageConfirmEmbedMetadataInAudioFiles": "Weet u zeker dat u metagegevens wilt insluiten in {0} audiobestanden?",
"MessageConfirmForceReScan": "Weet je zeker dat je geforceerd opnieuw wil scannen?",
"MessageConfirmMarkAllEpisodesFinished": "Weet je zeker dat je alle afleveringen als voltooid wil markeren?",
"MessageConfirmMarkAllEpisodesNotFinished": "Weet je zeker dat je alle afleveringen als niet-voltooid wil markeren?",
+ "MessageConfirmMarkItemFinished": "Weet u zeker dat u \"{0}\" als voltooid wilt markeren?",
+ "MessageConfirmMarkItemNotFinished": "Weet u zeker dat u \"{0}\" als niet voltooid wilt markeren?",
"MessageConfirmMarkSeriesFinished": "Weet je zeker dat je alle boeken in deze serie wil markeren als voltooid?",
"MessageConfirmMarkSeriesNotFinished": "Weet je zeker dat je alle boeken in deze serie wil markeren als niet voltooid?",
+ "MessageConfirmNotificationTestTrigger": "Trigger deze melding met test data?",
+ "MessageConfirmPurgeCache": "Met Purge cache wordt de gehele directory op /metadata/cache
verwijderd. /metadata/cache/items
verwijderd./metadata/logs
als JSON-bestanden. Crashlogs worden opgeslagen in /metadata/logs/crash_logs.txt
.",
"MessageM4BFailed": "M4B mislukt!",
"MessageM4BFinished": "M4B voltooid!",
"MessageMapChapterTitles": "Map hoofdstuktitels naar je bestaande audioboekhoofdstukken zonder aanpassing van tijden",
@@ -584,6 +778,7 @@
"MessageNoCollections": "Geen collecties",
"MessageNoCoversFound": "Geen covers gevonden",
"MessageNoDescription": "Geen beschrijving",
+ "MessageNoDevices": "Geen Apparaten",
"MessageNoDownloadsInProgress": "Geen downloads bezig op dit moment",
"MessageNoDownloadsQueued": "Geen downloads in de wachtrij",
"MessageNoEpisodeMatchesFound": "Geen afleveringsmatches gevonden",
@@ -597,6 +792,7 @@
"MessageNoLogs": "Geen logs",
"MessageNoMediaProgress": "Geen mediavoortgang",
"MessageNoNotifications": "Geen notificaties",
+ "MessageNoPodcastFeed": "Ongeldige podcast: Geen Feed",
"MessageNoPodcastsFound": "Geen podcasts gevonden",
"MessageNoResults": "Geen resultaten",
"MessageNoSearchResultsFor": "Geen zoekresultaten voor \"{0}\"",
@@ -606,11 +802,17 @@
"MessageNoUpdatesWereNecessary": "Geen bijwerkingen waren noodzakelijk",
"MessageNoUserPlaylists": "Je hebt geen afspeellijsten",
"MessageNotYetImplemented": "Nog niet geimplementeerd",
+ "MessageOpmlPreviewNote": "Let op: Dit is een preview van het geparseerde OPML-bestand. De werkelijke podcasttitel wordt overgenomen uit de RSS-feed.",
"MessageOr": "of",
"MessagePauseChapter": "Pauzeer afspelen hoofdstuk",
"MessagePlayChapter": "Luister naar begin van hoofdstuk",
"MessagePlaylistCreateFromCollection": "Afspeellijst aanmaken vanuit collectie",
+ "MessagePleaseWait": "Even geduld...",
"MessagePodcastHasNoRSSFeedForMatching": "Podcast heeft geen RSS-feed URL om te gebruiken voor matching",
+ "MessagePodcastSearchField": "Voer zoekterm of RSS-feed-URL in",
+ "MessageQuickEmbedInProgress": "Snelle inbedding in uitvoering",
+ "MessageQuickEmbedQueue": "In de wachtrij voor snelle insluiting ({0} in wachtrij)",
+ "MessageQuickMatchAllEpisodes": "Alle Afleveringen Snel Matchen",
"MessageQuickMatchDescription": "Vul lege onderdeeldetails & cover met eerste matchresultaat van '{0}'. Overschrijft geen details tenzij 'Prefereer gematchte metadata' serverinstelling is ingeschakeld.",
"MessageRemoveChapter": "Verwijder hoofdstuk",
"MessageRemoveEpisodes": "Verwijder {0} aflevering(en)",
@@ -621,10 +823,48 @@
"MessageRestoreBackupConfirm": "Weet je zeker dat je wil herstellen met behulp van de back-up gemaakt op",
"MessageRestoreBackupWarning": "Herstellen met een back-up zal de volledige database in /config en de covers in /metadata/items & /metadata/authors overschrijven.groups
. Если эта настройка настроена, приложение будет автоматически назначать роли на основе членства пользователя в группах при условии, что эти группы названы в утверждении без учета регистра \"admin\", \"user\" или \"guest\". Утверждение должно содержать список, и если пользователь принадлежит к нескольким группам, то приложение назначит роль, соответствующую самому высокому уровню доступа. Если ни одна из групп не совпадает, доступ будет запрещен.",
"LabelOpenRSSFeed": "Открыть RSS-канал",
"LabelOverwrite": "Перезаписать",
+ "LabelPaginationPageXOfY": "Страница {0} из {1}",
"LabelPassword": "Пароль",
"LabelPath": "Путь",
"LabelPermanent": "Постоянный",
"LabelPermissionsAccessAllLibraries": "Есть доступ ко всем библиотекам",
"LabelPermissionsAccessAllTags": "Есть доступ ко всем тегам",
"LabelPermissionsAccessExplicitContent": "Есть доступ к явному содержимому",
+ "LabelPermissionsCreateEreader": "Можно создать читалку",
"LabelPermissionsDelete": "Может удалять",
"LabelPermissionsDownload": "Может скачивать",
"LabelPermissionsUpdate": "Может обновлять",
@@ -465,6 +496,8 @@
"LabelPubDate": "Дата публикации",
"LabelPublishYear": "Год публикации",
"LabelPublishedDate": "Опубликовано {0}",
+ "LabelPublishedDecade": "Опубликованное десятилетие",
+ "LabelPublishedDecades": "Опубликованные десятилетия",
"LabelPublisher": "Издатель",
"LabelPublishers": "Издатели",
"LabelRSSFeedCustomOwnerEmail": "Пользовательский Email владельца",
@@ -484,21 +517,28 @@
"LabelRedo": "Повторить",
"LabelRegion": "Регион",
"LabelReleaseDate": "Дата выхода",
+ "LabelRemoveAllMetadataAbs": "Удалите все файлы metadata.abs",
+ "LabelRemoveAllMetadataJson": "Удалите все файлы metadata.json",
"LabelRemoveCover": "Удалить обложку",
+ "LabelRemoveMetadataFile": "Удаление файлов метаданных в папках элементов библиотеки",
+ "LabelRemoveMetadataFileHelp": "Удалите все файлы metadata.json и metadata.abs из ваших папок {0}.",
"LabelRowsPerPage": "Строк на странице",
"LabelSearchTerm": "Поисковый запрос",
"LabelSearchTitle": "Поиск по названию",
"LabelSearchTitleOrASIN": "Поиск по названию или ASIN",
"LabelSeason": "Сезон",
+ "LabelSeasonNumber": "Сезон #{0}",
"LabelSelectAll": "Выбрать все",
"LabelSelectAllEpisodes": "Выбрать все эпизоды",
"LabelSelectEpisodesShowing": "Выберите {0} эпизодов для показа",
"LabelSelectUsers": "Выбор пользователей",
"LabelSendEbookToDevice": "Отправить e-книгу в...",
"LabelSequence": "Последовательность",
+ "LabelSerial": "Серийный",
"LabelSeries": "Серия",
"LabelSeriesName": "Имя серии",
"LabelSeriesProgress": "Прогресс серии",
+ "LabelServerLogLevel": "Уровень журнала сервера",
"LabelServerYearReview": "Итоги года всего сервера ({0})",
"LabelSetEbookAsPrimary": "Установить как основную",
"LabelSetEbookAsSupplementary": "Установить как дополнительную",
@@ -523,6 +563,9 @@
"LabelSettingsHideSingleBookSeriesHelp": "Серии, в которых всего одна книга, будут скрыты со страницы серий и полок домашней страницы.",
"LabelSettingsHomePageBookshelfView": "Вид книжной полки на Домашней странице",
"LabelSettingsLibraryBookshelfView": "Вид книжной полки в Библиотеке",
+ "LabelSettingsLibraryMarkAsFinishedPercentComplete": "Процент выполнения больше, чем",
+ "LabelSettingsLibraryMarkAsFinishedTimeRemaining": "Оставшееся время составляет менее (секунд)",
+ "LabelSettingsLibraryMarkAsFinishedWhen": "Отметьте мультимедийный элемент как законченный, когда",
"LabelSettingsOnlyShowLaterBooksInContinueSeries": "Пропустить предыдущие книги в \"Продолжить серию\"",
"LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "На домашней странице \"Продолжить серию\" отображается первая книга, не начатая в серии, в которой закончена хотя бы одна книга и нет начатых книг. При включении этого параметра серия будет продолжена с самой последней завершенной книги, а не с первой, которая не начата.",
"LabelSettingsParseSubtitles": "Разбор подзаголовков",
@@ -587,13 +630,15 @@
"LabelTimeDurationXMinutes": "{0} минут",
"LabelTimeDurationXSeconds": "{0} секунд",
"LabelTimeInMinutes": "Время в минутах",
+ "LabelTimeLeft": "{0} осталось",
"LabelTimeListened": "Время прослушивания",
"LabelTimeListenedToday": "Время прослушивания сегодня",
"LabelTimeRemaining": "{0} осталось",
- "LabelTimeToShift": "Время смещения в сек.",
+ "LabelTimeToShift": "Время смещения в секундах",
"LabelTitle": "Название",
"LabelToolsEmbedMetadata": "Встроить метаданные",
"LabelToolsEmbedMetadataDescription": "Встроить метаданные в аудио файлы, включая обложку и главы.",
+ "LabelToolsM4bEncoder": "Кодировщик M4B",
"LabelToolsMakeM4b": "Создать M4B файл аудиокниги",
"LabelToolsMakeM4bDescription": "Создает .M4B файл аудиокниги с встроенными метаданными, обложкой и главами.",
"LabelToolsSplitM4b": "Разделить M4B на MP3 файлы",
@@ -606,6 +651,7 @@
"LabelTracksMultiTrack": "Мультитрек",
"LabelTracksNone": "Нет треков",
"LabelTracksSingleTrack": "Один трек",
+ "LabelTrailer": "Трейлер",
"LabelType": "Тип",
"LabelUnabridged": "Полное издание",
"LabelUndo": "Отменить",
@@ -619,8 +665,10 @@
"LabelUploaderDragAndDrop": "Перетащите файлы или каталоги",
"LabelUploaderDropFiles": "Перетащите файлы",
"LabelUploaderItemFetchMetadataHelp": "Автоматическое извлечение названия, автора и серии",
+ "LabelUseAdvancedOptions": "Используйте расширенные опции",
"LabelUseChapterTrack": "Показывать время главы",
"LabelUseFullTrack": "Показывать время книги",
+ "LabelUseZeroForUnlimited": "Используйте 0 для неограниченного количества",
"LabelUser": "Пользователь",
"LabelUsername": "Имя пользователя",
"LabelValue": "Значение",
@@ -667,6 +715,7 @@
"MessageConfirmDeleteMetadataProvider": "Вы уверены, что хотите удалить пользовательский поставщик метаданных \"{0}\"?",
"MessageConfirmDeleteNotification": "Вы уверены, что хотите удалить это уведомление?",
"MessageConfirmDeleteSession": "Вы уверены, что хотите удалить этот сеанс?",
+ "MessageConfirmEmbedMetadataInAudioFiles": "Вы уверены, что хотите вставить метаданные в {0} аудиофайлов?",
"MessageConfirmForceReScan": "Вы уверены, что хотите принудительно выполнить повторное сканирование?",
"MessageConfirmMarkAllEpisodesFinished": "Вы уверены, что хотите отметить все эпизоды как завершенные?",
"MessageConfirmMarkAllEpisodesNotFinished": "Вы уверены, что хотите отметить все эпизоды как не завершенные?",
@@ -678,6 +727,7 @@
"MessageConfirmPurgeCache": "Очистка кэша удалит весь каталог в /metadata/cache
. /metadata/cache/items
.false
. Zagotovite, da se zahtevek ponudnika identitete ujema s pričakovano strukturo:",
"LabelOpenIDClaims": "Pustite naslednje možnosti prazne, da onemogočite napredno dodeljevanje skupin in dovoljenj, nato pa samodejno dodelite skupino 'Uporabnik'.",
"LabelOpenIDGroupClaimDescription": "Ime zahtevka OpenID, ki vsebuje seznam uporabnikovih skupin. Običajno imenovane skupine
. Če je konfigurirana, bo aplikacija samodejno dodelila vloge na podlagi članstva v skupini uporabnika, pod pogojem, da so te skupine v zahtevku poimenovane 'admin', 'user' ali 'guest' brez razlikovanja med velikimi in malimi črkami. Zahtevek mora vsebovati seznam in če uporabnik pripada več skupinam, mu aplikacija dodeli vlogo, ki ustreza najvišjemu nivoju dostopa. Če se nobena skupina ne ujema, bo dostop zavrnjen.",
"LabelOpenRSSFeed": "Odpri vir RSS",
"LabelOverwrite": "Prepiši",
+ "LabelPaginationPageXOfY": "Stran {0} od {1}",
"LabelPassword": "Geslo",
"LabelPath": "Pot",
"LabelPermanent": "Trajno",
"LabelPermissionsAccessAllLibraries": "Lahko dostopa do vseh knjižnic",
"LabelPermissionsAccessAllTags": "Lahko dostopa do vseh oznak",
"LabelPermissionsAccessExplicitContent": "Lahko dostopa do eksplicitne vsebine",
+ "LabelPermissionsCreateEreader": "Lahko ustvari e-bralnik",
"LabelPermissionsDelete": "Lahko briše",
"LabelPermissionsDownload": "Lahko prenaša",
"LabelPermissionsUpdate": "Lahko posodablja",
@@ -559,6 +563,9 @@
"LabelSettingsHideSingleBookSeriesHelp": "Serije, ki imajo eno knjigo, bodo skrite na strani serije in policah domače strani.",
"LabelSettingsHomePageBookshelfView": "Domača stran bo imela pogled knjižne police",
"LabelSettingsLibraryBookshelfView": "Knjižnična uporaba pogleda knjižne police",
+ "LabelSettingsLibraryMarkAsFinishedPercentComplete": "Odstotek dokončanega je večji od",
+ "LabelSettingsLibraryMarkAsFinishedTimeRemaining": "Preostali čas je manj kot (sekund)",
+ "LabelSettingsLibraryMarkAsFinishedWhen": "Označi medijski element kot končan, ko",
"LabelSettingsOnlyShowLaterBooksInContinueSeries": "Preskoči prejšnje knjige v nadaljevanju serije",
"LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "Polica z domačo stranjo Nadaljuj serijo prikazuje prvo nezačeto knjigo v seriji, ki ima vsaj eno dokončano knjigo in ni nobene knjige v teku. Če omogočite to nastavitev, se bo serija nadaljevala od najbolj dokončane knjige namesto od prve nezačete knjige.",
"LabelSettingsParseSubtitles": "Uporabi podnapise",
@@ -586,7 +593,7 @@
"LabelSleepTimer": "Časovnik za spanje",
"LabelSlug": "Slug",
"LabelStart": "Začetek",
- "LabelStartTime": "Začetni čas",
+ "LabelStartTime": "Čas začetka",
"LabelStarted": "Začeto",
"LabelStartedAt": "Začeto ob",
"LabelStatsAudioTracks": "Zvočni posnetki",
diff --git a/client/strings/zh-cn.json b/client/strings/zh-cn.json
index 37bf38fc..2830a710 100644
--- a/client/strings/zh-cn.json
+++ b/client/strings/zh-cn.json
@@ -163,6 +163,7 @@
"HeaderNotificationUpdate": "更新通知",
"HeaderNotifications": "通知",
"HeaderOpenIDConnectAuthentication": "OpenID 连接身份验证",
+ "HeaderOpenListeningSessions": "打开收听会话",
"HeaderOpenRSSFeed": "打开 RSS 源",
"HeaderOtherFiles": "其他文件",
"HeaderPasswordAuthentication": "密码认证",
@@ -226,6 +227,7 @@
"LabelAllUsersExcludingGuests": "除访客外的所有用户",
"LabelAllUsersIncludingGuests": "包括访客的所有用户",
"LabelAlreadyInYourLibrary": "已存在你的库中",
+ "LabelApiToken": "API 令牌",
"LabelAppend": "附加",
"LabelAudioBitrate": "音频比特率 (例如: 128k)",
"LabelAudioChannels": "音频通道 (1 或 2)",
@@ -463,12 +465,14 @@
"LabelOpenIDGroupClaimDescription": "OpenID 声明的名称, 该声明包含用户组的列表. 通常称为组
如果已配置, 应用程序将根据用户的组成员身份自动分配角色, 前提是这些组在声明中以不区分大小写的方式命名为 'Admin', 'User' 或 'Guest'. 声明应包含一个列表, 如果用户属于多个组, 则应用程序将分配与最高访问级别相对应的角色. 如果没有组匹配, 访问将被拒绝.",
"LabelOpenRSSFeed": "打开 RSS 源",
"LabelOverwrite": "覆盖",
+ "LabelPaginationPageXOfY": "第 {0} 页 共 {1} 页",
"LabelPassword": "密码",
"LabelPath": "路径",
"LabelPermanent": "永久的",
"LabelPermissionsAccessAllLibraries": "可以访问所有媒体库",
"LabelPermissionsAccessAllTags": "可以访问所有标签",
"LabelPermissionsAccessExplicitContent": "可以访问显式内容",
+ "LabelPermissionsCreateEreader": "可以创建电子阅读器",
"LabelPermissionsDelete": "可以删除",
"LabelPermissionsDownload": "可以下载",
"LabelPermissionsUpdate": "可以更新",
@@ -559,6 +563,9 @@
"LabelSettingsHideSingleBookSeriesHelp": "只有一本书的系列将从系列页面和主页书架中隐藏.",
"LabelSettingsHomePageBookshelfView": "首页使用书架视图",
"LabelSettingsLibraryBookshelfView": "媒体库使用书架视图",
+ "LabelSettingsLibraryMarkAsFinishedPercentComplete": "完成百分比大于",
+ "LabelSettingsLibraryMarkAsFinishedTimeRemaining": "剩余时间少于 (秒)",
+ "LabelSettingsLibraryMarkAsFinishedWhen": "当发生以下情况时将媒体项目标记为已完成",
"LabelSettingsOnlyShowLaterBooksInContinueSeries": "跳过继续系列中的早期书籍",
"LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "继续系列主页书架显示系列中未开始的第一本书, 该系列至少有一本书已完成且没有正在进行的书. 启用此设置将从最远完成的书开始系列, 而不是从第一本书开始.",
"LabelSettingsParseSubtitles": "解析副标题",
diff --git a/package-lock.json b/package-lock.json
index e17041a4..189781ba 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "audiobookshelf",
- "version": "2.15.1",
+ "version": "2.16.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "audiobookshelf",
- "version": "2.15.1",
+ "version": "2.16.1",
"license": "GPL-3.0",
"dependencies": {
"axios": "^0.27.2",
diff --git a/package.json b/package.json
index 26ab93db..09c79711 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "audiobookshelf",
- "version": "2.15.1",
+ "version": "2.16.1",
"buildNumber": 1,
"description": "Self-hosted audiobook and podcast server",
"main": "index.js",
diff --git a/server/controllers/LibraryController.js b/server/controllers/LibraryController.js
index bf535bba..61ffb5bd 100644
--- a/server/controllers/LibraryController.js
+++ b/server/controllers/LibraryController.js
@@ -235,12 +235,14 @@ class LibraryController {
for (const key of keysToCheck) {
if (!req.body[key]) continue
if (typeof req.body[key] !== 'string') {
+ Logger.error(`[LibraryController] Invalid request. ${key} must be a string`)
return res.status(400).send(`Invalid request. ${key} must be a string`)
}
updatePayload[key] = req.body[key]
}
if (req.body.displayOrder !== undefined) {
if (isNaN(req.body.displayOrder)) {
+ Logger.error(`[LibraryController] Invalid request. displayOrder must be a number`)
return res.status(400).send('Invalid request. displayOrder must be a number')
}
updatePayload.displayOrder = req.body.displayOrder
@@ -255,18 +257,29 @@ class LibraryController {
}
// Validate settings
+ const defaultLibrarySettings = Database.libraryModel.getDefaultLibrarySettingsForMediaType(req.library.mediaType)
const updatedSettings = {
- ...(req.library.settings || Database.libraryModel.getDefaultLibrarySettingsForMediaType(req.library.mediaType))
+ ...(req.library.settings || defaultLibrarySettings)
}
+ // In case new settings are added in the future, ensure all settings are present
+ for (const key in defaultLibrarySettings) {
+ if (updatedSettings[key] === undefined) {
+ updatedSettings[key] = defaultLibrarySettings[key]
+ }
+ }
+
let hasUpdates = false
let hasUpdatedDisableWatcher = false
let hasUpdatedScanCron = false
if (req.body.settings) {
for (const key in req.body.settings) {
- if (updatedSettings[key] === undefined) continue
+ if (!Object.keys(defaultLibrarySettings).includes(key)) {
+ continue
+ }
if (key === 'metadataPrecedence') {
if (!Array.isArray(req.body.settings[key])) {
+ Logger.error(`[LibraryController] Invalid request. Settings "metadataPrecedence" must be an array`)
return res.status(400).send('Invalid request. Settings "metadataPrecedence" must be an array')
}
if (JSON.stringify(req.body.settings[key]) !== JSON.stringify(updatedSettings[key])) {
@@ -276,6 +289,7 @@ class LibraryController {
}
} else if (key === 'autoScanCronExpression' || key === 'podcastSearchRegion') {
if (req.body.settings[key] !== null && typeof req.body.settings[key] !== 'string') {
+ Logger.error(`[LibraryController] Invalid request. Settings "${key}" must be a string`)
return res.status(400).send(`Invalid request. Settings "${key}" must be a string`)
}
if (req.body.settings[key] !== updatedSettings[key]) {
@@ -285,8 +299,35 @@ class LibraryController {
updatedSettings[key] = req.body.settings[key]
Logger.debug(`[LibraryController] Library "${req.library.name}" updating setting "${key}" to "${updatedSettings[key]}"`)
}
+ } else if (key === 'markAsFinishedPercentComplete') {
+ if (req.body.settings[key] !== null && isNaN(req.body.settings[key])) {
+ Logger.error(`[LibraryController] Invalid request. Setting "${key}" must be a number`)
+ return res.status(400).send(`Invalid request. Setting "${key}" must be a number`)
+ } else if (req.body.settings[key] !== null && (Number(req.body.settings[key]) < 0 || Number(req.body.settings[key]) > 100)) {
+ Logger.error(`[LibraryController] Invalid request. Setting "${key}" must be between 0 and 100`)
+ return res.status(400).send(`Invalid request. Setting "${key}" must be between 0 and 100`)
+ }
+ if (req.body.settings[key] !== updatedSettings[key]) {
+ hasUpdates = true
+ updatedSettings[key] = Number(req.body.settings[key])
+ Logger.debug(`[LibraryController] Library "${req.library.name}" updating setting "${key}" to "${updatedSettings[key]}"`)
+ }
+ } else if (key === 'markAsFinishedTimeRemaining') {
+ if (req.body.settings[key] !== null && isNaN(req.body.settings[key])) {
+ Logger.error(`[LibraryController] Invalid request. Setting "${key}" must be a number`)
+ return res.status(400).send(`Invalid request. Setting "${key}" must be a number`)
+ } else if (req.body.settings[key] !== null && Number(req.body.settings[key]) < 0) {
+ Logger.error(`[LibraryController] Invalid request. Setting "${key}" must be greater than or equal to 0`)
+ return res.status(400).send(`Invalid request. Setting "${key}" must be greater than or equal to 0`)
+ }
+ if (req.body.settings[key] !== updatedSettings[key]) {
+ hasUpdates = true
+ updatedSettings[key] = Number(req.body.settings[key])
+ Logger.debug(`[LibraryController] Library "${req.library.name}" updating setting "${key}" to "${updatedSettings[key]}"`)
+ }
} else {
if (typeof req.body.settings[key] !== typeof updatedSettings[key]) {
+ Logger.error(`[LibraryController] Invalid request. Setting "${key}" must be of type ${typeof updatedSettings[key]}`)
return res.status(400).send(`Invalid request. Setting "${key}" must be of type ${typeof updatedSettings[key]}`)
}
if (req.body.settings[key] !== updatedSettings[key]) {
@@ -328,6 +369,7 @@ class LibraryController {
return false
})
if (!success) {
+ Logger.error(`[LibraryController] Invalid folder directory "${path}"`)
return res.status(400).send(`Invalid folder directory "${path}"`)
}
}
diff --git a/server/controllers/LibraryItemController.js b/server/controllers/LibraryItemController.js
index fe8539bc..a51a6e06 100644
--- a/server/controllers/LibraryItemController.js
+++ b/server/controllers/LibraryItemController.js
@@ -115,6 +115,16 @@ class LibraryItemController {
res.sendStatus(200)
}
+ static handleDownloadError(error, res) {
+ if (!res.headersSent) {
+ if (error.code === 'ENOENT') {
+ return res.status(404).send('File not found')
+ } else {
+ return res.status(500).send('Download failed')
+ }
+ }
+ }
+
/**
* GET: /api/items/:id/download
* Download library item. Zip file if multiple files.
@@ -122,7 +132,7 @@ class LibraryItemController {
* @param {RequestWithUser} req
* @param {Response} res
*/
- download(req, res) {
+ async download(req, res) {
if (!req.user.canDownload) {
Logger.warn(`User "${req.user.username}" attempted to download without permission`)
return res.sendStatus(403)
@@ -130,21 +140,26 @@ class LibraryItemController {
const libraryItemPath = req.libraryItem.path
const itemTitle = req.libraryItem.media.metadata.title
- // If library item is a single file in root dir then no need to zip
- if (req.libraryItem.isFile) {
- // Express does not set the correct mimetype for m4b files so use our defined mimetypes if available
- const audioMimeType = getAudioMimeTypeFromExtname(Path.extname(libraryItemPath))
- if (audioMimeType) {
- res.setHeader('Content-Type', audioMimeType)
- }
- Logger.info(`[LibraryItemController] User "${req.user.username}" requested download for item "${itemTitle}" at "${libraryItemPath}"`)
- res.download(libraryItemPath, req.libraryItem.relPath)
- return
- }
-
Logger.info(`[LibraryItemController] User "${req.user.username}" requested download for item "${itemTitle}" at "${libraryItemPath}"`)
- const filename = `${itemTitle}.zip`
- zipHelpers.zipDirectoryPipe(libraryItemPath, filename, res)
+
+ try {
+ // If library item is a single file in root dir then no need to zip
+ if (req.libraryItem.isFile) {
+ // Express does not set the correct mimetype for m4b files so use our defined mimetypes if available
+ const audioMimeType = getAudioMimeTypeFromExtname(Path.extname(libraryItemPath))
+ if (audioMimeType) {
+ res.setHeader('Content-Type', audioMimeType)
+ }
+ await new Promise((resolve, reject) => res.download(libraryItemPath, req.libraryItem.relPath, (error) => (error ? reject(error) : resolve())))
+ } else {
+ const filename = `${itemTitle}.zip`
+ await zipHelpers.zipDirectoryPipe(libraryItemPath, filename, res)
+ }
+ Logger.info(`[LibraryItemController] Downloaded item "${itemTitle}" at "${libraryItemPath}"`)
+ } catch (error) {
+ Logger.error(`[LibraryItemController] Download failed for item "${itemTitle}" at "${libraryItemPath}"`, error)
+ LibraryItemController.handleDownloadError(error, res)
+ }
}
/**
@@ -845,7 +860,13 @@ class LibraryItemController {
res.setHeader('Content-Type', audioMimeType)
}
- res.download(libraryFile.metadata.path, libraryFile.metadata.filename)
+ try {
+ await new Promise((resolve, reject) => res.download(libraryFile.metadata.path, libraryFile.metadata.filename, (error) => (error ? reject(error) : resolve())))
+ Logger.info(`[LibraryItemController] Downloaded file "${libraryFile.metadata.path}"`)
+ } catch (error) {
+ Logger.error(`[LibraryItemController] Failed to download file "${libraryFile.metadata.path}"`, error)
+ LibraryItemController.handleDownloadError(error, res)
+ }
}
/**
@@ -883,7 +904,13 @@ class LibraryItemController {
return res.status(204).header({ 'X-Accel-Redirect': encodedURI }).send()
}
- res.sendFile(ebookFilePath)
+ try {
+ await new Promise((resolve, reject) => res.sendFile(ebookFilePath, (error) => (error ? reject(error) : resolve())))
+ Logger.info(`[LibraryItemController] Downloaded ebook file "${ebookFilePath}"`)
+ } catch (error) {
+ Logger.error(`[LibraryItemController] Failed to download ebook file "${ebookFilePath}"`, error)
+ LibraryItemController.handleDownloadError(error, res)
+ }
}
/**
diff --git a/server/controllers/MeController.js b/server/controllers/MeController.js
index c7abbc23..cc67b320 100644
--- a/server/controllers/MeController.js
+++ b/server/controllers/MeController.js
@@ -394,6 +394,58 @@ class MeController {
res.json(req.user.toOldJSONForBrowser())
}
+ /**
+ * POST: /api/me/ereader-devices
+ *
+ * @param {RequestWithUser} req
+ * @param {Response} res
+ */
+ async updateUserEReaderDevices(req, res) {
+ if (!req.body.ereaderDevices || !Array.isArray(req.body.ereaderDevices)) {
+ return res.status(400).send('Invalid payload. ereaderDevices array required')
+ }
+
+ const userEReaderDevices = req.body.ereaderDevices
+ for (const device of userEReaderDevices) {
+ if (!device.name || !device.email) {
+ return res.status(400).send('Invalid payload. ereaderDevices array items must have name and email')
+ } else if (device.availabilityOption !== 'specificUsers' || device.users?.length !== 1 || device.users[0] !== req.user.id) {
+ return res.status(400).send('Invalid payload. ereaderDevices array items must have availabilityOption "specificUsers" and only the current user')
+ }
+ }
+
+ const otherDevices = Database.emailSettings.ereaderDevices.filter((device) => {
+ return !Database.emailSettings.checkUserCanAccessDevice(device, req.user) || device.users?.length !== 1
+ })
+
+ const ereaderDevices = otherDevices.concat(userEReaderDevices)
+
+ // Check for duplicate names
+ const nameSet = new Set()
+ const hasDupes = ereaderDevices.some((device) => {
+ if (nameSet.has(device.name)) {
+ return true // Duplicate found
+ }
+ nameSet.add(device.name)
+ return false
+ })
+
+ if (hasDupes) {
+ return res.status(400).send('Invalid payload. Duplicate "name" field found.')
+ }
+
+ const updated = Database.emailSettings.update({ ereaderDevices })
+ if (updated) {
+ await Database.updateSetting(Database.emailSettings)
+ SocketAuthority.clientEmitter(req.user.id, 'ereader-devices-updated', {
+ ereaderDevices: Database.emailSettings.ereaderDevices
+ })
+ }
+ res.json({
+ ereaderDevices: Database.emailSettings.getEReaderDevices(req.user)
+ })
+ }
+
/**
* GET: /api/me/stats/year/:year
*
diff --git a/server/managers/PlaybackSessionManager.js b/server/managers/PlaybackSessionManager.js
index a26a5c81..ce43fc8c 100644
--- a/server/managers/PlaybackSessionManager.js
+++ b/server/managers/PlaybackSessionManager.js
@@ -119,6 +119,7 @@ class PlaybackSessionManager {
* @returns
*/
async syncLocalSession(user, sessionJson, deviceInfo) {
+ // TODO: Combine libraryItem query with library query
const libraryItem = await Database.libraryItemModel.getOldById(sessionJson.libraryItemId)
const episode = sessionJson.episodeId && libraryItem && libraryItem.isPodcast ? libraryItem.media.getEpisode(sessionJson.episodeId) : null
if (!libraryItem || (libraryItem.isPodcast && !episode)) {
@@ -130,6 +131,16 @@ class PlaybackSessionManager {
}
}
+ const library = await Database.libraryModel.findByPk(libraryItem.libraryId)
+ if (!library) {
+ Logger.error(`[PlaybackSessionManager] syncLocalSession: Library not found for session "${sessionJson.displayTitle}" (${sessionJson.id})`)
+ return {
+ id: sessionJson.id,
+ success: false,
+ error: 'Library not found'
+ }
+ }
+
sessionJson.userId = user.id
sessionJson.serverVersion = serverVersion
@@ -199,7 +210,9 @@ class PlaybackSessionManager {
const updateResponse = await user.createUpdateMediaProgressFromPayload({
libraryItemId: libraryItem.id,
episodeId: session.episodeId,
- ...session.mediaProgressObject
+ ...session.mediaProgressObject,
+ markAsFinishedPercentComplete: library.librarySettings.markAsFinishedPercentComplete,
+ markAsFinishedTimeRemaining: library.librarySettings.markAsFinishedTimeRemaining
})
result.progressSynced = !!updateResponse.mediaProgress
if (result.progressSynced) {
@@ -211,7 +224,9 @@ class PlaybackSessionManager {
const updateResponse = await user.createUpdateMediaProgressFromPayload({
libraryItemId: libraryItem.id,
episodeId: session.episodeId,
- ...session.mediaProgressObject
+ ...session.mediaProgressObject,
+ markAsFinishedPercentComplete: library.librarySettings.markAsFinishedPercentComplete,
+ markAsFinishedTimeRemaining: library.librarySettings.markAsFinishedTimeRemaining
})
result.progressSynced = !!updateResponse.mediaProgress
if (result.progressSynced) {
@@ -330,12 +345,19 @@ class PlaybackSessionManager {
* @returns
*/
async syncSession(user, session, syncData) {
+ // TODO: Combine libraryItem query with library query
const libraryItem = await Database.libraryItemModel.getOldById(session.libraryItemId)
if (!libraryItem) {
Logger.error(`[PlaybackSessionManager] syncSession Library Item not found "${session.libraryItemId}"`)
return null
}
+ const library = await Database.libraryModel.findByPk(libraryItem.libraryId)
+ if (!library) {
+ Logger.error(`[PlaybackSessionManager] syncSession Library not found "${libraryItem.libraryId}"`)
+ return null
+ }
+
session.currentTime = syncData.currentTime
session.addListeningTime(syncData.timeListened)
Logger.debug(`[PlaybackSessionManager] syncSession "${session.id}" (Device: ${session.deviceDescription}) | Total Time Listened: ${session.timeListening}`)
@@ -344,12 +366,11 @@ class PlaybackSessionManager {
libraryItemId: libraryItem.id,
episodeId: session.episodeId,
// duration no longer required (v2.15.1) but used if available
- duration: syncData.duration || libraryItem.media.duration || 0,
+ duration: syncData.duration || session.duration || 0,
currentTime: syncData.currentTime,
- progress: session.progress
- // TODO: Add support for passing in these values from library settings
- // markAsFinishedTimeRemaining: 5,
- // markAsFinishedPercentageComplete: 95
+ progress: session.progress,
+ markAsFinishedTimeRemaining: library.librarySettings.markAsFinishedTimeRemaining,
+ markAsFinishedPercentComplete: library.librarySettings.markAsFinishedPercentComplete
})
if (updateResponse.mediaProgress) {
SocketAuthority.clientEmitter(user.id, 'user_item_progress_updated', {
diff --git a/server/models/Library.js b/server/models/Library.js
index 90dd2512..4a69e4cd 100644
--- a/server/models/Library.js
+++ b/server/models/Library.js
@@ -12,6 +12,8 @@ const Logger = require('../Logger')
* @property {boolean} hideSingleBookSeries Do not show series that only have 1 book
* @property {boolean} onlyShowLaterBooksInContinueSeries Skip showing books that are earlier than the max sequence read
* @property {string[]} metadataPrecedence
+ * @property {number} markAsFinishedTimeRemaining Time remaining in seconds to mark as finished. (defaults to 10s)
+ * @property {number} markAsFinishedPercentComplete Percent complete to mark as finished (0-100). If this is set it will be used over markAsFinishedTimeRemaining.
*/
class Library extends Model {
@@ -57,7 +59,9 @@ class Library extends Model {
coverAspectRatio: 1, // Square
disableWatcher: false,
autoScanCronExpression: null,
- podcastSearchRegion: 'us'
+ podcastSearchRegion: 'us',
+ markAsFinishedPercentComplete: null,
+ markAsFinishedTimeRemaining: 10
}
} else {
return {
@@ -70,7 +74,9 @@ class Library extends Model {
epubsAllowScriptedContent: false,
hideSingleBookSeries: false,
onlyShowLaterBooksInContinueSeries: false,
- metadataPrecedence: this.defaultMetadataPrecedence
+ metadataPrecedence: this.defaultMetadataPrecedence,
+ markAsFinishedPercentComplete: null,
+ markAsFinishedTimeRemaining: 10
}
}
}
@@ -196,6 +202,13 @@ class Library extends Model {
return this.extraData?.lastScanMetadataPrecedence || []
}
+ /**
+ * @returns {LibrarySettingsObject}
+ */
+ get librarySettings() {
+ return this.settings || Library.getDefaultLibrarySettingsForMediaType(this.mediaType)
+ }
+
/**
* TODO: Update to use new model
*/
diff --git a/server/models/MediaProgress.js b/server/models/MediaProgress.js
index 052c8f74..d6a527f7 100644
--- a/server/models/MediaProgress.js
+++ b/server/models/MediaProgress.js
@@ -229,30 +229,30 @@ class MediaProgress extends Model {
const timeRemaining = this.duration - this.currentTime
// Check if progress is far enough to mark as finished
- // - If markAsFinishedPercentageComplete is provided, use that otherwise use markAsFinishedTimeRemaining (default 5 seconds)
+ // - If markAsFinishedPercentComplete is provided, use that otherwise use markAsFinishedTimeRemaining (default 10 seconds)
let shouldMarkAsFinished = false
- if (!this.isFinished && this.duration) {
- if (!isNullOrNaN(progressPayload.markAsFinishedPercentageComplete)) {
- const markAsFinishedPercentageComplete = Number(progressPayload.markAsFinishedPercentageComplete) / 100
- shouldMarkAsFinished = markAsFinishedPercentageComplete <= this.progress
+ if (this.duration) {
+ if (!isNullOrNaN(progressPayload.markAsFinishedPercentComplete) && progressPayload.markAsFinishedPercentComplete > 0) {
+ const markAsFinishedPercentComplete = Number(progressPayload.markAsFinishedPercentComplete) / 100
+ shouldMarkAsFinished = markAsFinishedPercentComplete < this.progress
if (shouldMarkAsFinished) {
- Logger.debug(`[MediaProgress] Marking media progress as finished because progress (${this.progress}) is greater than ${markAsFinishedPercentageComplete}`)
+ Logger.debug(`[MediaProgress] Marking media progress as finished because progress (${this.progress}) is greater than ${markAsFinishedPercentComplete}`)
}
} else {
- const markAsFinishedTimeRemaining = isNullOrNaN(progressPayload.markAsFinishedTimeRemaining) ? 5 : Number(progressPayload.markAsFinishedTimeRemaining)
- shouldMarkAsFinished = timeRemaining <= markAsFinishedTimeRemaining
+ const markAsFinishedTimeRemaining = isNullOrNaN(progressPayload.markAsFinishedTimeRemaining) ? 10 : Number(progressPayload.markAsFinishedTimeRemaining)
+ shouldMarkAsFinished = timeRemaining < markAsFinishedTimeRemaining
if (shouldMarkAsFinished) {
Logger.debug(`[MediaProgress] Marking media progress as finished because time remaining (${timeRemaining}) is less than ${markAsFinishedTimeRemaining} seconds`)
}
}
}
- if (shouldMarkAsFinished) {
+ if (!this.isFinished && shouldMarkAsFinished) {
this.isFinished = true
this.finishedAt = this.finishedAt || Date.now()
this.extraData.progress = 1
this.changed('extraData', true)
- } else if (this.isFinished && this.changed('currentTime') && this.currentTime < this.duration) {
+ } else if (this.isFinished && this.changed('currentTime') && !shouldMarkAsFinished) {
this.isFinished = false
this.finishedAt = null
}
diff --git a/server/models/User.js b/server/models/User.js
index 84f471e9..c1810beb 100644
--- a/server/models/User.js
+++ b/server/models/User.js
@@ -28,7 +28,7 @@ const { DataTypes, Model } = sequelize
* @property {string} [finishedAt]
* @property {number} [lastUpdate]
* @property {number} [markAsFinishedTimeRemaining]
- * @property {number} [markAsFinishedPercentageComplete]
+ * @property {number} [markAsFinishedPercentComplete]
*/
class User extends Model {
@@ -82,6 +82,7 @@ class User extends Model {
canAccessExplicitContent: 'accessExplicitContent',
canAccessAllLibraries: 'accessAllLibraries',
canAccessAllTags: 'accessAllTags',
+ canCreateEReader: 'createEreader',
tagsAreDenylist: 'selectedTagsNotAccessible',
// Direct mapping for array-based permissions
allowedLibraries: 'librariesAccessible',
@@ -122,6 +123,7 @@ class User extends Model {
update: type === 'root' || type === 'admin',
delete: type === 'root',
upload: type === 'root' || type === 'admin',
+ createEreader: type === 'root' || type === 'admin',
accessAllLibraries: true,
accessAllTags: true,
accessExplicitContent: type === 'root' || type === 'admin',
diff --git a/server/routers/ApiRouter.js b/server/routers/ApiRouter.js
index 57067ad8..f81bc26d 100644
--- a/server/routers/ApiRouter.js
+++ b/server/routers/ApiRouter.js
@@ -190,6 +190,7 @@ class ApiRouter {
this.router.get('/me/series/:id/remove-from-continue-listening', MeController.removeSeriesFromContinueListening.bind(this))
this.router.get('/me/series/:id/readd-to-continue-listening', MeController.readdSeriesFromContinueListening.bind(this))
this.router.get('/me/stats/year/:year', MeController.getStatsForYear.bind(this))
+ this.router.post('/me/ereader-devices', MeController.updateUserEReaderDevices.bind(this))
//
// Backup Routes