From e6d99d07f0e5c649d873d7ba6df40be2e0b9a727 Mon Sep 17 00:00:00 2001 From: sir-wilhelm Date: Sun, 28 Dec 2025 11:28:36 -0600 Subject: [PATCH 001/124] Display localized/styled text for selected filter. The selected filter was using the id before. --- client/components/controls/LibraryFilterSelect.vue | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/client/components/controls/LibraryFilterSelect.vue b/client/components/controls/LibraryFilterSelect.vue index 62a9b8037..4834a1a25 100644 --- a/client/components/controls/LibraryFilterSelect.vue +++ b/client/components/controls/LibraryFilterSelect.vue @@ -338,6 +338,18 @@ export default { const series = this.series.find((se) => se.id == decoded) if (series) filterValue = series.name } + } else if (parts[0] === 'progress') { + const item = this.progress.find((p) => p.id == decoded) + if (item) filterValue = item.name + } else if (parts[0] === 'tracks') { + const item = this.tracks.find((t) => t.id == decoded) + if (item) filterValue = item.name + } else if (parts[0] === 'ebooks') { + const item = this.ebooks.find((e) => e.id == decoded) + if (item) filterValue = item.name + } else if (parts[0] === 'missing') { + const item = this.missing.find((m) => m.id == decoded) + if (item) filterValue = item.name } else { filterValue = decoded } From 3e4225bced841944b24ffde05ce6a8933a17e131 Mon Sep 17 00:00:00 2001 From: KiwiHour Date: Fri, 9 Jan 2026 14:26:56 +0000 Subject: [PATCH 002/124] Fix aria-label for jumpBackward button --- client/components/player/PlayerPlaybackControls.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/components/player/PlayerPlaybackControls.vue b/client/components/player/PlayerPlaybackControls.vue index 368e3f35b..c7cbba63c 100644 --- a/client/components/player/PlayerPlaybackControls.vue +++ b/client/components/player/PlayerPlaybackControls.vue @@ -8,7 +8,7 @@ - From 5107b0307c7a39c3344c3e380d41271631fcfca5 Mon Sep 17 00:00:00 2001 From: Charlie Date: Wed, 24 Dec 2025 20:22:40 +0100 Subject: [PATCH 003/124] Translated using Weblate (French) Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/ --- client/strings/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/strings/fr.json b/client/strings/fr.json index 4841ec9f6..4ab98d805 100644 --- a/client/strings/fr.json +++ b/client/strings/fr.json @@ -962,7 +962,7 @@ "PlaceholderNewFolderPath": "Nouveau chemin de dossier", "PlaceholderNewPlaylist": "Nouveau nom de liste de lecture", "PlaceholderSearch": "Recherche…", - "PlaceholderSearchEpisode": "Rechercher un épisode..", + "PlaceholderSearchEpisode": "Rechercher un épisode…", "StatsAuthorsAdded": "auteurs ajoutés", "StatsBooksAdded": "livres ajoutés", "StatsBooksAdditional": "Les ajouts comprennent…", From c089336e416857e5d04030a9b423f45fb412ac22 Mon Sep 17 00:00:00 2001 From: "J. Lavoie" Date: Thu, 1 Jan 2026 23:00:03 +0100 Subject: [PATCH 004/124] Translated using Weblate (Italian) Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/it/ --- client/strings/it.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/strings/it.json b/client/strings/it.json index 46ecb0f53..d2178cbf6 100644 --- a/client/strings/it.json +++ b/client/strings/it.json @@ -275,7 +275,7 @@ "LabelBonus": "Bonus", "LabelBooks": "Libri", "LabelButtonText": "Buttone Testo", - "LabelByAuthor": "da {0}", + "LabelByAuthor": "di {0}", "LabelChangePassword": "Cambia Password", "LabelChannels": "Canali", "LabelChapterCount": "{0} Capitoli", From b921a08809b23bd7e591fef7cdef37e5ef1714c0 Mon Sep 17 00:00:00 2001 From: "J. Lavoie" Date: Thu, 1 Jan 2026 22:59:35 +0100 Subject: [PATCH 005/124] Translated using Weblate (French) Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/ --- client/strings/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/strings/fr.json b/client/strings/fr.json index 4ab98d805..6ff72c7ef 100644 --- a/client/strings/fr.json +++ b/client/strings/fr.json @@ -275,7 +275,7 @@ "LabelBonus": "Bonus", "LabelBooks": "Livres", "LabelButtonText": "Texte du bouton", - "LabelByAuthor": "par {0}", + "LabelByAuthor": "de {0}", "LabelChangePassword": "Modifier le mot de passe", "LabelChannels": "Canaux", "LabelChapterCount": "{0} Chapitres", From 32276aacd94b207ecb116c6145e10b91e3a218a3 Mon Sep 17 00:00:00 2001 From: xxzp3 Date: Wed, 7 Jan 2026 18:48:31 +0100 Subject: [PATCH 006/124] Translated using Weblate (Danish) Currently translated at 99.8% (1161 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/da/ --- client/strings/da.json | 45 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/client/strings/da.json b/client/strings/da.json index ab1444970..0f4c24ec9 100644 --- a/client/strings/da.json +++ b/client/strings/da.json @@ -127,6 +127,7 @@ "HeaderAudiobookTools": "Audiobog Filhåndteringsværktøjer", "HeaderAuthentication": "Autentificering", "HeaderBackups": "Sikkerhedskopier", + "HeaderBulkChapterModal": "Tilføj flere kapitler", "HeaderChangePassword": "Skift Adgangskode", "HeaderChapters": "Kapitler", "HeaderChooseAFolder": "Vælg en Mappe", @@ -199,6 +200,7 @@ "HeaderSettingsExperimental": "Eksperimentelle Funktioner", "HeaderSettingsGeneral": "Generelt", "HeaderSettingsScanner": "Scanner", + "HeaderSettingsSecurity": "Sikkerhed", "HeaderSettingsWebClient": "Webklient", "HeaderSleepTimer": "Søvntimer", "HeaderStatsLargestItems": "Største Elementer", @@ -293,6 +295,7 @@ "LabelContinueListening": "Fortsæt med at lytte", "LabelContinueReading": "Fortsæt med at læse", "LabelContinueSeries": "Fortsæt Serien", + "LabelCorsAllowed": "Tilladte CORS-oprindelser", "LabelCover": "Omslag", "LabelCoverImageURL": "Omslagsbillede URL", "LabelCoverProvider": "Cover billede udbyder", @@ -306,6 +309,7 @@ "LabelDeleteFromFileSystemCheckbox": "Slet fra filsystem (afmarker kun for at fjerne fra databasen)", "LabelDescription": "Beskrivelse", "LabelDeselectAll": "Fravælg Alle", + "LabelDetectedPattern": "Identificeret mønster:", "LabelDevice": "Enheds", "LabelDeviceInfo": "Enhedsinformation", "LabelDeviceIsAvailableTo": "Enhed er tilgængelig for...", @@ -374,11 +378,12 @@ "LabelFilterByUser": "Filtrér efter bruger", "LabelFindEpisodes": "Find episoder", "LabelFinished": "Færdig", + "LabelFinishedDate": "Færdig {0}", "LabelFolder": "Mappe", "LabelFolders": "Mapper", "LabelFontBold": "Fed", "LabelFontBoldness": "Skrift tykkelse", - "LabelFontFamily": "Fontfamilie", + "LabelFontFamily": "Skrifttypefamilie", "LabelFontItalic": "Kursiv", "LabelFontScale": "Skriftstørrelse", "LabelFontStrikethrough": "Gennemstreget", @@ -418,6 +423,7 @@ "LabelLanguages": "Sprog", "LabelLastBookAdded": "Senest tilføjede bog", "LabelLastBookUpdated": "Senest opdaterede bog", + "LabelLastProgressDate": "Sidste fremgang: {0}", "LabelLastSeen": "Sidst set", "LabelLastTime": "Sidste gang", "LabelLastUpdate": "Seneste opdatering", @@ -430,6 +436,9 @@ "LabelLibraryFilterSublistEmpty": "Nej {0}", "LabelLibraryItem": "Bibliotekselement", "LabelLibraryName": "Biblioteksnavn", + "LabelLibrarySortByProgress": "Fremgang: Sidst opdateret", + "LabelLibrarySortByProgressFinished": "Fremgang: Afsluttet", + "LabelLibrarySortByProgressStarted": "Fremgang: Startet", "LabelLimit": "Grænse", "LabelLineSpacing": "Linjeafstand", "LabelListenAgain": "Lyt igen", @@ -467,6 +476,7 @@ "LabelNewestAuthors": "Nyeste forfattere", "LabelNewestEpisodes": "Nyeste episoder", "LabelNextBackupDate": "Næste sikkerhedskopi dato", + "LabelNextChapters": "Næste kapitler vil være:", "LabelNextScheduledRun": "Næste planlagte kørsel", "LabelNoApiKeys": "Ingen API-nøgler", "LabelNoCustomMetadataProviders": "Ingen brugerdefinerede metadata udbydere", @@ -484,6 +494,7 @@ "LabelNotificationsMaxQueueSize": "Maksimal køstørrelse for meddelelseshændelser", "LabelNotificationsMaxQueueSizeHelp": "Hændelser begrænses til at udløse en gang pr. sekund. Hændelser ignoreres, hvis køen er fyldt. Dette forhindrer meddelelsesspam.", "LabelNumberOfBooks": "Antal bøger", + "LabelNumberOfChapters": "Antal kapitler:", "LabelNumberOfEpisodes": "# afsnit", "LabelOpenIDAdvancedPermsClaimDescription": "Navnet af OpenID claimet som indeholder avancerede brugerhandlinger inden i applikationen som vil gælde for ikke administrative roller (hvis konfigureret). Hvis et claim mangler fra svaret vil adgang til ABS blive nægtet. Hvis en enkelt indstilling/option mangler, vil det bliver behandlet som false. Sørg for at identity provider's claim matcher den forventede struktur:", "LabelOpenIDClaims": "Efterlad de følgende indstillinger tomme for at deaktivere avanceret gruppe og adgangsindstilling, ved automatisk at assigne 'Bruger' grupper.", @@ -576,8 +587,8 @@ "LabelSettingsBookshelfViewHelp": "Skeumorfisk design med træhylder", "LabelSettingsChromecastSupport": "Chromecast-understøttelse", "LabelSettingsDateFormat": "Datoformat", - "LabelSettingsEnableWatcher": "Scan automatisk bibliotek for ændringer", - "LabelSettingsEnableWatcherForLibrary": "Scan automatisk bibliotek for ændringer", + "LabelSettingsEnableWatcher": "Automatisk biblioteksovervåger", + "LabelSettingsEnableWatcherForLibrary": "Automatisk biblioteksovervåger", "LabelSettingsEnableWatcherHelp": "Aktiverer automatisk tilføjelse/opdatering af elementer, når filændringer registreres. *Kræver servergenstart", "LabelSettingsEpubsAllowScriptedContent": "Tillad scriptet indhold i epub", "LabelSettingsEpubsAllowScriptedContentHelp": "Tillad epub filer at køre scripts. Det anbefales at holde denne indstilling deaktiveret med mindre du stoler på kilderne af epub filerne.", @@ -626,6 +637,7 @@ "LabelStartTime": "Starttid", "LabelStarted": "Startet", "LabelStartedAt": "Startet klokken", + "LabelStartedDate": "Startet {0}", "LabelStatsAudioTracks": "Lydspor", "LabelStatsAuthors": "Forfattere", "LabelStatsBestDay": "Bedste dag", @@ -655,6 +667,7 @@ "LabelTheme": "Tema", "LabelThemeDark": "Mørk", "LabelThemeLight": "Lys", + "LabelThemeSepia": "Sepia", "LabelTimeBase": "Tidsbase", "LabelTimeDurationXHours": "{0} timer", "LabelTimeDurationXMinutes": "{0} minutter", @@ -739,6 +752,7 @@ "MessageBookshelfNoResultsForFilter": "Ingen resultater for filter \"{0}: {1}\"", "MessageBookshelfNoResultsForQuery": "Intet resultat for query", "MessageBookshelfNoSeries": "Du har ingen serier", + "MessageBulkChapterPattern": "Hvor mange kapitler vil du tilføje med dette nummereringsmønster?", "MessageChapterEndIsAfter": "Kapitelslutningen er efter slutningen af din lydbog", "MessageChapterErrorFirstNotZero": "Første kapitel skal starte ved 0", "MessageChapterErrorStartGteDuration": "Ugyldig starttid skal være mindre end lydbogens varighed", @@ -775,6 +789,7 @@ "MessageConfirmRemoveAuthor": "Er du sikker på, at du vil fjerne forfatteren \"{0}\"?", "MessageConfirmRemoveCollection": "Er du sikker på, at du vil fjerne samlingen \"{0}\"?", "MessageConfirmRemoveEpisode": "Er du sikker på, at du vil fjerne episoden \"{0}\"?", + "MessageConfirmRemoveEpisodeNote": "Obs: Dette sletter ikke lydfilen medmindre \"Permanent sletning af fil\" er aktiveret", "MessageConfirmRemoveEpisodes": "Er du sikker på, at du vil fjerne {0} episoder?", "MessageConfirmRemoveListeningSessions": "Er du sikker på at du vil fjerne {0} lytte sessioner?", "MessageConfirmRemoveMetadataFiles": "Er du sikker på at du vil fjerne alle metadata.{0} filer i dine biblioteksfoldere?", @@ -800,6 +815,8 @@ "MessageFeedURLWillBe": "Feed-URL vil være {0}", "MessageFetching": "Henter...", "MessageForceReScanDescription": "vil scanne alle filer igen som en frisk scanning. Lydfilens ID3-tags, OPF-filer og tekstfiler scannes som nye.", + "MessageHeatmapListeningTimeTooltip": "{0} lytter på {1}", + "MessageHeatmapNoListeningSessions": "Ingen lyttesessioner på {0}", "MessageImportantNotice": "Vigtig besked!", "MessageInsertChapterBelow": "Indsæt kapitel nedenfor", "MessageInvalidAsin": "Ugyldig ASIN", @@ -870,7 +887,7 @@ "MessageResetChaptersConfirm": "Er du sikker på, at du vil nulstille kapitler og annullere ændringerne, du har foretaget?", "MessageRestoreBackupConfirm": "Er du sikker på, at du vil gendanne sikkerhedskopien oprettet den", "MessageRestoreBackupWarning": "Gendannelse af en sikkerhedskopi vil overskrive hele databasen, som er placeret på /config, og omslagsbilleder i /metadata/items & /metadata/authors.

Sikkerhedskopier ændrer ikke nogen filer i dine biblioteksmapper. Hvis du har aktiveret serverindstillinger for at gemme omslagskunst og metadata i dine biblioteksmapper, sikkerhedskopieres eller overskrives disse ikke.

Alle klienter, der bruger din server, opdateres automatisk.", - "MessageScheduleLibraryScanNote": "For de fleste brugere, er det anbefalet at efterlade denne funktion deaktiveret for at holde mappe lurer indstilling aktiveret. Mappe lureren vil automatisk opdage ændringer i biblioteksmapper. Mappe lureren virker ikke for alle filsystemer (så som NFS) så schedulerede biblioteksscans vil blive anvendt.", + "MessageScheduleLibraryScanNote": "For de fleste brugere er det anbefalet, at efterlade denne funktion deaktiveret, og lade biblioteksovervågeren være aktiveret - den vil automatisk opdage ændringer i dine biblioteksmapper. Aktiver denne funktion, hvis biblioteksovervågeren ikke virker med dit filsystem (f. eks. NFS).", "MessageScheduleRunEveryWeekdayAtTime": "Kør hvert {0} af {1}", "MessageSearchResultsFor": "Søgeresultater for", "MessageSelected": "{0} valgt", @@ -939,6 +956,7 @@ "NotificationOnRSSFeedDisabledDescription": "Aktiveret når automatiske episode-downloads er slået fra, på grund af for mange forsøg", "NotificationOnRSSFeedFailedDescription": "Aktiveret når anmodning om RSS-feedet fejler for en automatisk episode-download", "NotificationOnTestDescription": "Event for test af notifikationssystemet", + "PlaceholderBulkChapterInput": "Indtast kapiteltitel eller brug nummerering (f.eks. 'Episode 1', 'Kapitel 10', '1.')", "PlaceholderNewCollection": "Nyt samlingnavn", "PlaceholderNewFolderPath": "Ny mappes sti", "PlaceholderNewPlaylist": "Nyt afspilningslistnavn", @@ -992,9 +1010,15 @@ "ToastBookmarkCreateFailed": "Mislykkedes oprettelse af bogmærke", "ToastBookmarkCreateSuccess": "Bogmærke tilføjet", "ToastBookmarkRemoveSuccess": "Bogmærke fjernet", + "ToastBulkChapterInvalidCount": "Indtast et tal mellem 1 og 150", "ToastCachePurgeFailed": "Fejlede at opryde cache", "ToastCachePurgeSuccess": "Cache ryddet op i succesfuldt", + "ToastChapterLocked": "Kapitel er låst.", + "ToastChapterStartTimeAdjusted": "Kapitelstarttid justeret med {0} sekunder", + "ToastChaptersAllLocked": "Alle kapitler er låst. Lås op for nogle kapitler for at ændre deres tider.", "ToastChaptersHaveErrors": "Kapitler har fejl", + "ToastChaptersInvalidShiftAmountLast": "Ugyldig ændring. Det sidste kapitels starttid ville fortsætte længere end varigheden på denne lydbog.", + "ToastChaptersInvalidShiftAmountStart": "Ugyldig ændring. Første kapitel ville have en længde på nul eller negativt og ville blive overskrevet af andet kapitel. Udvid startvarigheden på andet kapitel.", "ToastChaptersMustHaveTitles": "Kapitler skal have titler", "ToastChaptersRemoved": "Kapitler fjernet", "ToastChaptersUpdated": "Kapitler opdateret", @@ -1002,6 +1026,7 @@ "ToastCollectionRemoveSuccess": "Samling fjernet", "ToastCollectionUpdateSuccess": "Samling opdateret", "ToastConnectionNotAvailable": "Forbindelse mislykkedes. Prøv igen senere", + "ToastCoverSearchFailed": "Cover-søgning mislykkedes", "ToastCoverUpdateFailed": "Cover opdatering fejlede", "ToastDateTimeInvalidOrIncomplete": "Dato og tid er ugyldig eller ufærdig", "ToastDeleteFileFailed": "Sletning af fil fejlede", @@ -1051,6 +1076,7 @@ "ToastMustHaveAtLeastOnePath": "Skal have mindst en sti", "ToastNameEmailRequired": "Navn og email påkrævet", "ToastNameRequired": "Navn påkrævet", + "ToastNewApiKeyUserError": "En bruger skal vælges", "ToastNewEpisodesFound": "{0} nye afsnit fundet", "ToastNewUserCreatedFailed": "Fejlede at oprette konto: \"{0}\"", "ToastNewUserCreatedSuccess": "Ny konto oprettet", @@ -1075,6 +1101,7 @@ "ToastPlaylistUpdateSuccess": "Afspilningsliste opdateret", "ToastPodcastCreateFailed": "Mislykkedes oprettelse af podcast", "ToastPodcastCreateSuccess": "Podcast oprettet med succes", + "ToastPodcastEpisodeUpdated": "Episode opdateret", "ToastPodcastGetFeedFailed": "Fejlede at hente podcast feed", "ToastPodcastNoEpisodesInFeed": "Ingen nye afsnit fundet i RSS feed", "ToastPodcastNoRssFeed": "Podcast har ingen RSS feed", @@ -1125,5 +1152,13 @@ "ToastUserPasswordChangeSuccess": "Password ændret", "ToastUserPasswordMismatch": "Passwords passer ikke sammen", "ToastUserPasswordMustChange": "Nyt password må ikke være det gamle", - "ToastUserRootRequireName": "Skal indholde et root brugernavn" + "ToastUserRootRequireName": "Skal indholde et root brugernavn", + "TooltipAddChapters": "Tilføj kapitler", + "TooltipAddOneSecond": "Tilføj 1 sekund", + "TooltipAdjustChapterStart": "Klik for at ændre starttiden", + "TooltipLockAllChapters": "Lås alle kapitler", + "TooltipLockChapter": "Lås kapitel (Shift+click for at markere flere)", + "TooltipSubtractOneSecond": "Fratag 1 sekund", + "TooltipUnlockAllChapters": "Lås alle kapitaler op", + "TooltipUnlockChapter": "Lås kapitel op (Shift+click for at markere flere)" } From dcbeecff7a14282f35a109ed156ef2620cf76c52 Mon Sep 17 00:00:00 2001 From: herny ucet Date: Sun, 11 Jan 2026 15:25:45 +0100 Subject: [PATCH 007/124] Translated using Weblate (Slovak) Currently translated at 99.7% (1160 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sk/ --- client/strings/sk.json | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/client/strings/sk.json b/client/strings/sk.json index e0d02898c..e9d1d4c60 100644 --- a/client/strings/sk.json +++ b/client/strings/sk.json @@ -275,7 +275,7 @@ "LabelBonus": "Bonus", "LabelBooks": "Knihy", "LabelButtonText": "Text tlačidla", - "LabelByAuthor": "od {0}", + "LabelByAuthor": "od", "LabelChangePassword": "Zmeniť heslo", "LabelChannels": "Kanály", "LabelChapterCount": "{0} kapitol", @@ -383,7 +383,7 @@ "LabelFolders": "Priečinky", "LabelFontBold": "Tučné", "LabelFontBoldness": "Hrúbka písma", - "LabelFontFamily": "Rodina písiem", + "LabelFontFamily": "Písmo", "LabelFontItalic": "Kurzíva", "LabelFontScale": "Veľkosť písma", "LabelFontStrikethrough": "Preškrtnuté", @@ -588,8 +588,8 @@ "LabelSettingsBookshelfViewHelp": "Skeuomorfný dizajn s drevenými poličkami", "LabelSettingsChromecastSupport": "Podpora chromecastu", "LabelSettingsDateFormat": "Formát dátumu", - "LabelSettingsEnableWatcher": "Automatické skenovanie knižníc pre zmeny", - "LabelSettingsEnableWatcherForLibrary": "Automaticky skenovať knižnicu pre zmeny", + "LabelSettingsEnableWatcher": "Automatické sledovanie zmien v knižniciach", + "LabelSettingsEnableWatcherForLibrary": "Automatické sledovanie zmien v knižnici", "LabelSettingsEnableWatcherHelp": "Povoliť automatické pridávanie/aktualizácie položiek pri zmene súborov. *Vyžaduje reštart servera", "LabelSettingsEpubsAllowScriptedContent": "Povoliť v e-knihách skriptovaný obsah", "LabelSettingsEpubsAllowScriptedContentHelp": "Povoliť e-knihám spúšťanie skriptov. Odporúča sa túto voľbu nepovolovať, pokiaľ plne nedôverujete zdrojom súborov e-kníh.", @@ -888,7 +888,7 @@ "MessageResetChaptersConfirm": "Ste si istý, že chcete resetnúť kapitoly a zahodiť zmeny, ktoré ste vykonali?", "MessageRestoreBackupConfirm": "Ste si istí, že chcete obnoviť zálohu vytvorenú", "MessageRestoreBackupWarning": "Obnovenie zálohy spôsobí kompletný prepis databázy umiestnenej v /config a obrázkov prebalov a autorov v /metadata/items a /metadata/authors.

Zálohy nemenia žiadne súbory v priečinkoch vašej knižnice. Ak ste povolili v nastaveniach servera ukladanie obrázkov prebalov a metadát v priečinkoch knižnice, tieto nie sú zálohované a teda ani prepisované.

Všetky klienti používajúci váš server budú automaticky obnovené.", - "MessageScheduleLibraryScanNote": "Pre väčšinu používateľov sa odporúča ponechať túto funkciu vypnutú a povoliť nastavenia funkcie sledovania obsahu priečinku. Funkcia sledovania priečinku bude automaticky detekovať zmeny v priečinkoch knižnice. Táto funkcia však nefunguje pre všetky súborové systémy (ako napr. NFS), v tom prípade využite funkciu plánovaného skenovania knižnice.", + "MessageScheduleLibraryScanNote": "Pre väčšinu používateľov sa odporúča nechať túto funkciu vypnutú a ponechať zapnuté nastavenie „Automatické sledovanie zmien v knižnici“ – táto funkcia automaticky zistí zmeny vo vašich priečinkoch knižnice. Túto funkciu zapnite, ak „Automatické sledovanie zmien v knižnici“ nefunguje vo vašom súborovom systéme (napr. NFS).", "MessageScheduleRunEveryWeekdayAtTime": "Spustiť každú {0} o {1}", "MessageSearchResultsFor": "Výsledky vyhľadávania pre", "MessageSelected": "{0} vybrané", @@ -1026,6 +1026,8 @@ "ToastCollectionItemsAddFailed": "Pridanie položky/-iek do zbierky zlyhalo", "ToastCollectionRemoveSuccess": "Zbierka odstránená", "ToastCollectionUpdateSuccess": "Zbierka aktualizovaná", + "ToastConnectionNotAvailable": "Pripojenie je nedostupné. Skúste to neskôr.", + "ToastCoverSearchFailed": "Vyhľadanie obalu sa nepodarilo", "ToastCoverUpdateFailed": "Aktualizácia prebalu zlyhala", "ToastDateTimeInvalidOrIncomplete": "Dátum a čas sú neplatné alebo neúplné", "ToastDeleteFileFailed": "Odstránenie súboru zlyhalo", From e55fed4a33f42d9c91043fcb4ac1510789a2d8d8 Mon Sep 17 00:00:00 2001 From: Henrik Lynge Date: Sun, 11 Jan 2026 05:32:43 +0100 Subject: [PATCH 008/124] Translated using Weblate (Danish) Currently translated at 99.9% (1162 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/da/ --- client/strings/da.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/strings/da.json b/client/strings/da.json index 0f4c24ec9..6cf517fa3 100644 --- a/client/strings/da.json +++ b/client/strings/da.json @@ -275,7 +275,7 @@ "LabelBonus": "Bonus", "LabelBooks": "Bøger", "LabelButtonText": "Knap tekst", - "LabelByAuthor": "af {0}", + "LabelByAuthor": "Efter Forfatter", "LabelChangePassword": "Ændre Adgangskode", "LabelChannels": "Kanaler", "LabelChapterCount": "{0} Kapitler", @@ -447,6 +447,7 @@ "LabelLogLevelWarn": "Advarsel", "LabelLookForNewEpisodesAfterDate": "Søg efter nye episoder efter denne dato", "LabelLowestPriority": "Laveste prioritet", + "LabelMatchConfidence": "Confidens", "LabelMatchExistingUsersBy": "Match eksisterende brugere ved", "LabelMatchExistingUsersByDescription": "Anvendt for at forbinde brugere. Når forbundet, brugere vil blive matchet ved unikt id fra din SSO udbyder", "LabelMaxEpisodesToDownload": "Max # afsnit for at downloade. Anvend 0 for ubegrænset.", From 4f8fbbc9793124c9d9e9a23a42ab00683934be5d Mon Sep 17 00:00:00 2001 From: N Visi Date: Thu, 15 Jan 2026 12:43:53 +0100 Subject: [PATCH 009/124] Translated using Weblate (Japanese) Currently translated at 22.8% (266 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ja/ --- client/strings/ja.json | 87 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/client/strings/ja.json b/client/strings/ja.json index 7c2acb889..28f6de6a6 100644 --- a/client/strings/ja.json +++ b/client/strings/ja.json @@ -72,6 +72,7 @@ "ButtonQueueRemoveItem": "次に再生から削除", "ButtonQuickEmbed": "クイック埋め込み", "ButtonQuickEmbedMetadata": "メタデータの埋め込み", + "ButtonQuickMatch": "クイックマッチ", "ButtonReScan": "再スキャン", "ButtonRead": "読む", "ButtonReadLess": "閉じる", @@ -80,71 +81,157 @@ "ButtonRemove": "削除", "ButtonRemoveAll": "全て削除", "ButtonRemoveAllLibraryItems": "ライブラリーの項目を全て削除", + "ButtonRemoveFromContinueListening": "「続きを聴く」から削除", + "ButtonRemoveFromContinueReading": "「続きを読む」から削除", + "ButtonRemoveSeriesFromContinueSeries": "「シリーズを続く」からシリーズを削除", "ButtonReset": "元に戻す", "ButtonResetToDefault": "デフォルトに戻す", "ButtonRestore": "復元", "ButtonSave": "保存", "ButtonSaveAndClose": "保存して閉じる", + "ButtonSaveTracklist": "トラックリストを保存", "ButtonScan": "スキャン", "ButtonScanLibrary": "ライブラリーをスキャン", "ButtonScrollLeft": "左にスクロール", "ButtonScrollRight": "右にスクロール", "ButtonSearch": "検索", + "ButtonSelectFolderPath": "保存先フォルダを選択", "ButtonSeries": "シリーズ", + "ButtonSetChaptersFromTracks": "トラックからチャプターを設定する", + "ButtonShare": "共有", + "ButtonShiftTimes": "再生時間の移動", + "ButtonShow": "表示", + "ButtonStartM4BEncode": "M4Bエンコード開始", + "ButtonStartMetadataEmbed": "メタデータ埋め込み開始", + "ButtonStats": "統計", "ButtonSubmit": "送信", + "ButtonTest": "テスト", + "ButtonUnlinkOpenId": "OpenID 連携解除", + "ButtonUpload": "アップロード", + "ButtonUploadBackup": "バックアップのアップロード", + "ButtonUploadCover": "カバー画像をアップロード", + "ButtonUploadOPMLFile": "OPMLファイルをアップロード", + "ButtonUserDelete": "ユーザーを削除 {0}", + "ButtonUserEdit": "ユーザを編集 {0}", + "ButtonViewAll": "すべて表示", "ButtonYes": "はい", + "ErrorUploadFetchMetadataAPI": "メタデータの取得中にエラーが発生しました", + "ErrorUploadFetchMetadataNoResults": "メタデータ取得に失敗しました。タイトルや著者名を更新してください", + "ErrorUploadLacksTitle": "タイトルは必須です", "HeaderAccount": "アカウント", + "HeaderAddCustomMetadataProvider": "カスタムメタデータプロバイダーを追加", "HeaderAdvanced": "上級者向け", + "HeaderApiKeys": "APIキー", + "HeaderAppriseNotificationSettings": "Apprise 通知設定", "HeaderAudioTracks": "オーディオトラック", + "HeaderAudiobookTools": "オーディオブックのファイル管理ツール", + "HeaderAuthentication": "認証", + "HeaderBackups": "バックアップ", + "HeaderBulkChapterModal": "チャプターをまとめて追加", + "HeaderChangePassword": "パスワードを変更", "HeaderChapters": "チャプター", + "HeaderChooseAFolder": "フォルダを選択", "HeaderCollection": "コレクション", "HeaderCollectionItems": "コレクションの項目", + "HeaderCover": "カバー", + "HeaderCurrentDownloads": "現在のダウンロード", + "HeaderCustomMessageOnLogin": "ログイン時のカスタムメッセージ", + "HeaderCustomMetadataProviders": "カスタムメタデータプロバイダー", "HeaderDetails": "詳細", + "HeaderDownloadQueue": "ダウンロード待ち", "HeaderEbookFiles": "電子書籍ファイル", + "HeaderEmail": "メール", + "HeaderEmailSettings": "メール設定", "HeaderEpisodes": "エピソード", + "HeaderEreaderDevices": "電子書籍リーダー端末", "HeaderEreaderSettings": "電子書籍リーダーの設定", + "HeaderFiles": "ファイル", + "HeaderFindChapters": "チャプターを検索", + "HeaderIgnoredFiles": "無視されたファイル", + "HeaderItemFiles": "アイテムファイル", + "HeaderItemMetadataUtils": "アイテムメタデータユーティリティ", + "HeaderLastListeningSession": "直近の再生セッション", "HeaderLatestEpisodes": "最新のエピソード", "HeaderLibraries": "ライブラリー", + "HeaderLibraryFiles": "ライブラリファイル", + "HeaderLibraryStats": "ライブラリ統計", + "HeaderListeningSessions": "再生セッション", + "HeaderListeningStats": "再生統計", + "HeaderLogin": "ログイン", + "HeaderLogs": "ログ", + "HeaderManageGenres": "ジャンルを管理", + "HeaderManageTags": "タグを管理", + "HeaderMapDetails": "マップの詳細", + "HeaderMatch": "マッチ", + "HeaderMetadataOrderOfPrecedence": "メタデータの優先順", + "HeaderMetadataToEmbed": "埋め込むメタデータ", + "HeaderNewAccount": "新規アカウント", + "HeaderNewApiKey": "新規APIキー", + "HeaderNewLibrary": "新規ライブラリー", + "HeaderNotificationCreate": "通知を作成", + "HeaderNotificationUpdate": "通知を更新", + "HeaderNotifications": "通知", + "HeaderOpenIDConnectAuthentication": "OpenID Connect 認証", "HeaderOpenRSSFeed": "RSS Feedを開く", "HeaderPlayerSettings": "プレーヤーの設定", "HeaderPlaylist": "プレイリスト", "HeaderPlaylistItems": "プレイリストアイテム", "HeaderRSSFeedGeneral": "RSS 詳細", + "HeaderRSSFeedIsOpen": "RSSフィードが開いています", "HeaderSettings": "設定", "HeaderSettingsGeneral": "一般", "HeaderSettingsScanner": "スキャナー", "HeaderSleepTimer": "スリープタイマー", "HeaderStatsMinutesListeningChart": "過去7日間の視聴時間(分)", + "HeaderStatsRecentSessions": "最近の再生履歴", + "HeaderTableOfContents": "目次", + "HeaderYourStats": "再生統計", "LabelAddToPlaylist": "プレイリストの追加", + "LabelAddedAt": "追加日時", + "LabelAddedDate": "追加日時 ­­{0}", + "LabelAll": "すべて", "LabelAuthor": "著者", "LabelAuthorFirstLast": "著者(名 氏)", "LabelAuthorLastFirst": "著者(氏 名)", "LabelAuthors": "著者", "LabelAutoDownloadEpisodes": "エピソードの自動ダウンロード", "LabelBooks": "ほん", + "LabelByAuthor": "著 {0}", "LabelChapters": "チャプター", "LabelClosePlayer": "プレイヤーを閉じる", + "LabelCollapseSeries": "シリーズを折りたたむ", "LabelComplete": "完了", "LabelContinueListening": "続きから聞く", + "LabelContinueReading": "続きを読む", + "LabelContinueSeries": "シリーズを続く", "LabelDescription": "説明", + "LabelDiscover": "おすすめ", "LabelDownload": "ダウンロード", "LabelDuration": "長さ", "LabelEbook": "Eブック", "LabelEbooks": "Eブック", "LabelEnable": "有効", + "LabelEnd": "終了", "LabelEndOfChapter": "チャプターの最後", "LabelEpisode": "エピソード", + "LabelExplicit": "", "LabelFeedURL": "Feed URL", "LabelFile": "ファイル", + "LabelFileBirthtime": "ファイル作成日時", + "LabelFileModified": "ファイル更新日時", "LabelFilename": "ファイル名", "LabelFinished": "完了", "LabelFolder": "フォルダ", "LabelFontBoldness": "フォントの太さ", + "LabelFontFamily": "フォントファミリー", "LabelFontScale": "フォントサイズ", "LabelGenre": "ジャンル", "LabelGenres": "ジャンル", + "LabelHasEbook": "eBookあり", + "LabelHasSupplementaryEbook": "付属eBookあり", "LabelHost": "ホスト", "LabelInProgress": "進行中", + "LabelIncomplete": "未完了", "LabelLanguage": "言語", "LabelLanguages": "言語", "LabelLayout": "レイアウト", From 7201cced4287e07dc595d01fe4176f397f7cb755 Mon Sep 17 00:00:00 2001 From: Kabika82 Date: Mon, 19 Jan 2026 13:42:10 +0100 Subject: [PATCH 010/124] Translated using Weblate (Hungarian) Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hu/ --- client/strings/hu.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/strings/hu.json b/client/strings/hu.json index 574fd1c9b..1c177a09c 100644 --- a/client/strings/hu.json +++ b/client/strings/hu.json @@ -499,7 +499,7 @@ "LabelNumberOfEpisodes": "Epizódok száma", "LabelOpenIDAdvancedPermsClaimDescription": "Az OpenID-igény neve, amely a felhasználói műveletekre vonatkozó haladó jogosultságokat tartalmazza az alkalmazáson belül, és amely a nem adminisztrátori szerepkörökre vonatkozik (ha konfigurálva van). Ha az igény hiányzik a válaszból, az ABS-hez való hozzáférés megtagadásra kerül. Ha egyetlen opció hiányzik, azt false-ként fogja kezelni. Győződj meg arról, hogy az identitásszolgáltató igénye megfelel a várt struktúrának:", "LabelOpenIDClaims": "Hagyd üresen a következő opciókat, hogy letiltsd a haladó csoport- és jogosultság-hozzárendelést, ekkor automatikusan a ‘Felhasználó’ csoport kerül hozzárendelésre.", - "LabelOpenIDGroupClaimDescription": "Az OpenID-igény neve, amely a felhasználó csoportjainak listáját tartalmazza. Általában groups néven hivatkoznak rá. Ha konfigurálva van, az alkalmazás automatikusan hozzárendeli a szerepköröket a felhasználó csoporttagságai alapján, feltéve, hogy ezek a csoportok az igényben kis- és nagybetűkre érzéketlenül ‘admin’, ‘user’ vagy ‘guest’ néven szerepelnek. Az igénynek egy listát kell tartalmaznia, és ha egy felhasználó több csoport tagja, az alkalmazás a legmagasabb szintű hozzáféréssel rendelkező szerepkört rendeli hozzá. Ha egyetlen csoport sem felel meg, a hozzáférés megtagadásra kerül.", + "LabelOpenIDGroupClaimDescription": "Az OpenID-igény neve, amely a felhasználó csoportjainak listáját tartalmazza. Általában groups néven hivatkoznak rá. Ha konfigurálva van, az alkalmazás automatikusan hozzárendeli a szerepköröket a felhasználó csoporttagságai alapján, feltéve, hogy ezek a csoportok az igényben kis- és nagybetűkre érzéketlenül ‘admin’, ‘user’ vagy ‘guest’ néven szerepelnek. Az igénynek egy listát kell tartalmaznia, és ha egy felhasználó több csoport tagja, az alkalmazás a legmagasabb szintű hozzáféréssel rendelkező szerepkört rendeli hozzá. Ha egyetlen csoport sem felel meg, a hozzáférés megtagadásra kerül.", "LabelOpenRSSFeed": "RSS hírcsatorna megnyitása", "LabelOverwrite": "Felülírás", "LabelPaginationPageXOfY": "{0} oldal {1}-ból/ből", From d08cef11edd448fe60212b975599a0fe6184c9a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej?= <20731216+Jarsey45@users.noreply.github.com> Date: Mon, 26 Jan 2026 15:30:11 +0100 Subject: [PATCH 011/124] Translated using Weblate (Polish) Currently translated at 94.5% (1100 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/pl/ --- client/strings/pl.json | 68 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 65 insertions(+), 3 deletions(-) diff --git a/client/strings/pl.json b/client/strings/pl.json index 8b70a1341..c50ebe85e 100644 --- a/client/strings/pl.json +++ b/client/strings/pl.json @@ -915,10 +915,16 @@ "MessageTaskNoFilesToScan": "Brak plików do skanowania", "MessageTaskOpmlImport": "Importuj OPML", "MessageTaskOpmlImportDescription": "Tworzenie {0} podcastów z kanałów RSS", + "MessageTaskOpmlImportFeedPodcastDescription": "Tworzenie podcastu \"{0}\"", + "MessageTaskOpmlImportFeedPodcastExists": "Podcast już istnieje pod podaną ścieżką", + "MessageTaskOpmlImportFeedPodcastFailed": "Nie udało się utworzyć podcastu", + "MessageTaskOpmlImportFinished": "Dodano {0} podcastów", + "MessageTaskOpmlParseFailed": "Błąd parsowania pliku OPML", "MessageTaskScanItemsAdded": "Dodano {0}", "MessageTaskScanItemsMissing": "Brakuje {0}", "MessageTaskScanItemsUpdated": "Zaktualizowano {0}", "MessageTaskScanNoChangesNeeded": "Brak zmian", + "MessageTaskScanningLibrary": "Skanowanie biblioteki \"{0}\"", "MessageTaskTargetDirectoryNotWritable": "Brak prawa zapisu do folderu docelowego", "MessageThinking": "Myślę...", "MessageUploaderItemFailed": "Nie udało się przesłać", @@ -937,6 +943,7 @@ "NoteUploaderFoldersWithMediaFiles": "Foldery z plikami multimedialnymi będą traktowane jako osobne elementy w bibliotece.", "NoteUploaderOnlyAudioFiles": "Jeśli przesyłasz tylko pliki audio, każdy plik audio będzie traktowany jako osobny audiobook.", "NoteUploaderUnsupportedFiles": "Nieobsługiwane pliki są ignorowane. Podczas dodawania folderu, inne pliki, które nie znajdują się w folderze elementu, są ignorowane.", + "NotificationOnTestDescription": "Zdarzenie używane do testowania systemu powiadomień", "PlaceholderNewCollection": "Nowa nazwa kolekcji", "PlaceholderNewFolderPath": "Nowa ścieżka folderu", "PlaceholderNewPlaylist": "Nowa nazwa playlisty", @@ -957,26 +964,50 @@ "StatsTopMonth": "TOPOWY MIESIĄC", "StatsTopNarrator": "TOPOWY NARRATOR", "StatsTopNarrators": "TOPOWI NARRATORZY", + "StatsTotalDuration": "O sumarycznej długości…", "StatsYearInReview": "PRZEGLĄD ROKU", "ToastAccountUpdateSuccess": "Zaktualizowano konto", + "ToastAsinRequired": "ASIN jest wymagany", "ToastAuthorImageRemoveSuccess": "Zdjęcie autora usunięte", + "ToastAuthorNotFound": "Autor \"{0}\" nie został znaleziony", + "ToastAuthorRemoveSuccess": "Autor usunięty", + "ToastAuthorSearchNotFound": "Autor nie odnaleziony", "ToastAuthorUpdateMerged": "Autor scalony", "ToastAuthorUpdateSuccess": "Autor zaktualizowany", "ToastAuthorUpdateSuccessNoImageFound": "Autor zaktualizowany (nie znaleziono obrazu)", + "ToastBackupAppliedSuccess": "Kopia zapasowa została przywrócona", "ToastBackupCreateFailed": "Nie udało się utworzyć kopii zapasowej", "ToastBackupCreateSuccess": "Utworzono kopię zapasową", "ToastBackupDeleteFailed": "Nie udało się usunąć kopii zapasowej", "ToastBackupDeleteSuccess": "Udało się usunąć kopie zapasowej", + "ToastBackupInvalidMaxKeep": "Nieprawidłowa ilość kopii zapasowych do przechowania", + "ToastBackupInvalidMaxSize": "Nieprawidłowy rozmiar maksymalny kopii zapasowej", "ToastBackupRestoreFailed": "Nie udało się przywrócić kopii zapasowej", "ToastBackupUploadFailed": "Nie udało się przesłać kopii zapasowej", "ToastBackupUploadSuccess": "Kopia zapasowa została przesłana", - "ToastBatchUpdateFailed": "Aktualizacja wsadowa nie powiodła się", - "ToastBatchUpdateSuccess": "Aktualizacja wsadowa powiodła się", + "ToastBatchDeleteFailed": "Usuwanie zbiorcze nie powiodło się", + "ToastBatchDeleteSuccess": "Usuwanie zbiorcze powiodło się", + "ToastBatchUpdateFailed": "Aktualizacja zbiorcza nie powiodła się", + "ToastBatchUpdateSuccess": "Aktualizacja zbiorcza powiodła się", "ToastBookmarkCreateFailed": "Nie udało się utworzyć zakładki", "ToastBookmarkCreateSuccess": "Dodano zakładkę", "ToastBookmarkRemoveSuccess": "Zakładka została usunięta", + "ToastBulkChapterInvalidCount": "Wprowadź liczbę z przedziału od 1 do 150", + "ToastCachePurgeFailed": "Nie udało się wyczyścić pamięci cache", + "ToastCachePurgeSuccess": "Wyczyszczono pamięć cache", + "ToastChapterLocked": "Rozdział jest zablokowany.", + "ToastChapterStartTimeAdjusted": "Czas rozpoczęcia rozdziału przesunięty o \"{0}\" sekund", + "ToastChaptersAllLocked": "Wszystkie rozdziały są zablokowane. Odblokuj edycję, aby użyć przesunięcia czasowego.", + "ToastChaptersHaveErrors": "Rozdziały posiadają błędy", + "ToastChaptersInvalidShiftAmountLast": "Niepoprawna wartość przesunięcia. Czas rozpoczęcia ostatniego rozdziału wykroczyłby poza długość tego audiobooka.", + "ToastChaptersInvalidShiftAmountStart": "Niepoprawna wartość przesunięcia. Pierwszy rozdział miałby długość mniejszą lub równą zeru oraz on zostałby nadpisany przez rozdział drugi. Ustaw późniejszy czas rozpoczęcia drugiego rozdziału.", + "ToastChaptersMustHaveTitles": "Rozdziały muszą posiadać tytuł", + "ToastChaptersRemoved": "Rozdziały usunięte", + "ToastChaptersUpdated": "Rozdziały zaktualizowane", + "ToastCollectionItemsAddFailed": "Dodanie elementów do kolekcji nie powiodło się", "ToastCollectionRemoveSuccess": "Kolekcja usunięta", "ToastCollectionUpdateSuccess": "Zaktualizowano kolekcję", + "ToastConnectionNotAvailable": "Brak połączenia. Spróbuj ponownie później", "ToastCoverSearchFailed": "Nieudane wyszukiwanie okładki", "ToastCoverUpdateFailed": "Nieudana aktualizacja okładki", "ToastDateTimeInvalidOrIncomplete": "Niepoprawna data i czas", @@ -993,6 +1024,8 @@ "ToastInvalidUrl": "Nieprawidłowy URL", "ToastInvalidUrls": "Jeden lub więcej URL-i są nieprawidłowe", "ToastItemCoverUpdateSuccess": "Zaktualizowano okładkę", + "ToastItemDeletedFailed": "Nie udało się usunąć elementu", + "ToastItemDeletedSuccess": "Element usunięty", "ToastItemDetailsUpdateSuccess": "Zaktualizowano szczegóły", "ToastItemMarkedAsFinishedFailed": "Nie udało się oznaczyć jako ukończone", "ToastItemMarkedAsFinishedSuccess": "Pozycja oznaczona jako ukończona", @@ -1031,12 +1064,41 @@ "ToastRSSFeedCloseSuccess": "Zamknięcie kanału RSS powiodło się", "ToastRemoveItemFromCollectionFailed": "Nie udało się usunąć elementu z kolekcji", "ToastRemoveItemFromCollectionSuccess": "Pozycja usunięta z kolekcji", + "ToastRemoveItemsWithIssuesFailed": "Nie udało się usunąć wadliwych elementów z biblioteki", + "ToastRemoveItemsWithIssuesSuccess": "Usunięto wadliwe elementy z biblioteki", + "ToastRenameFailed": "Nie udało się zmienić nazwy", + "ToastRescanFailed": "Ponowne skanowanie nie powiodło się dla {0}", + "ToastRescanRemoved": "Ponowne skanowanie powiodło się – element został usunięty", + "ToastRescanUpToDate": "Ponowne skanowanie powiodło się – element był aktualny", + "ToastRescanUpdated": "Ponowne skanowanie powiodło się – element został zaktualizowany", + "ToastScanFailed": "Nie powiódł się skan elementu biblioteki", + "ToastSelectAtLeastOneUser": "Zaznacz co najmniej jednego użytkownika", "ToastSendEbookToDeviceFailed": "Failed to Send Ebook to device", + "ToastSendEbookToDeviceSuccess": "Ebook wysłany na urządzenie \"{0}\"", + "ToastSeriesSubmitFailedSameName": "Nie można dodać dwóch serii pod tą samą nazwą", + "ToastSeriesUpdateFailed": "Aktualizacja serii nie powiodła się", + "ToastSeriesUpdateSuccess": "Aktualizacja serii powiodła się", + "ToastServerSettingsUpdateSuccess": "Zaktualizowano ustawienia serwera", + "ToastSessionCloseFailed": "Nie udało się zamknąć sesji", "ToastSessionDeleteFailed": "Nie udało się usunąć sesji", "ToastSessionDeleteSuccess": "Sesja usunięta", + "ToastSleepTimerDone": "Słodkich snów... zZzzZz", "ToastSocketConnected": "Nawiązano połączenie z serwerem", "ToastSocketDisconnected": "Połączenie z serwerem zostało zamknięte", "ToastSocketFailedToConnect": "Poączenie z serwerem nie powiodło się", + "ToastTitleRequired": "Tytuł jest wymagany", + "ToastUnknownError": "Nieznany błąd", + "ToastUnlinkOpenIdFailed": "Nie udało się odpiąć użytkownika z OpenID", + "ToastUnlinkOpenIdSuccess": "Użytkownik odpięty z OpenID", + "ToastUploaderFilepathExistsError": "Ścieżka \"{0}\" już istnieje na serwerze", "ToastUserDeleteFailed": "Nie udało się usunąć użytkownika", - "ToastUserDeleteSuccess": "Użytkownik usunięty" + "ToastUserDeleteSuccess": "Użytkownik usunięty", + "TooltipAddChapters": "Dodaj rozdział(y)", + "TooltipAddOneSecond": "Dodaj sekundę", + "TooltipAdjustChapterStart": "Kliknij, aby skorygować czas początkowy", + "TooltipLockAllChapters": "Zablokuj wszystkie rozdziały", + "TooltipLockChapter": "Zablokuj rozdział (przytrzymaj Shift i kliknij, aby zaznaczyć zakres)", + "TooltipSubtractOneSecond": "Odejmij sekundę", + "TooltipUnlockAllChapters": "Odblokuj wszystkie rozdziały", + "TooltipUnlockChapter": "Odblokuj rozdział (przytrzymaj Shift i kliknij, aby zaznaczyć zakres)" } From 87f376629927b381620bf8eeb484c8592fea1ce2 Mon Sep 17 00:00:00 2001 From: Mantas Date: Fri, 30 Jan 2026 00:08:32 +0100 Subject: [PATCH 012/124] Translated using Weblate (Lithuanian) Currently translated at 59.9% (697 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/lt/ --- client/strings/lt.json | 1 + 1 file changed, 1 insertion(+) diff --git a/client/strings/lt.json b/client/strings/lt.json index b5ca66630..2480cdf35 100644 --- a/client/strings/lt.json +++ b/client/strings/lt.json @@ -1,5 +1,6 @@ { "ButtonAdd": "Pridėti", + "ButtonAddApiKey": "Pridėti API raktą", "ButtonAddChapters": "Pridėti skyrius", "ButtonAddDevice": "Pridėti įrenginį", "ButtonAddLibrary": "Pridėti Biblioteką", From 1f3fa80ddd78a929958cf8714288b92b88d38e30 Mon Sep 17 00:00:00 2001 From: Samuel Guerrero Date: Fri, 30 Jan 2026 22:56:07 +0100 Subject: [PATCH 013/124] Translated using Weblate (Spanish) Currently translated at 97.5% (1134 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/ --- client/strings/es.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/strings/es.json b/client/strings/es.json index 4a611b3de..73ec1cd2d 100644 --- a/client/strings/es.json +++ b/client/strings/es.json @@ -275,7 +275,7 @@ "LabelBonus": "Bonus", "LabelBooks": "Libros", "LabelButtonText": "Texto del botón", - "LabelByAuthor": "por {0}", + "LabelByAuthor": "por", "LabelChangePassword": "Cambiar contraseña", "LabelChannels": "Canales", "LabelChapterCount": "{0} capítulos", From 6c7221d37d781a44d67ba8044e11993e82dfce01 Mon Sep 17 00:00:00 2001 From: dv4yGY2U Date: Fri, 30 Jan 2026 23:31:49 +0100 Subject: [PATCH 014/124] Translated using Weblate (Turkish) Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/tr/ --- client/strings/tr.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/client/strings/tr.json b/client/strings/tr.json index af64a618d..770521cb8 100644 --- a/client/strings/tr.json +++ b/client/strings/tr.json @@ -275,7 +275,7 @@ "LabelBonus": "Bonus", "LabelBooks": "Kitaplar", "LabelButtonText": "Buton Metni", - "LabelByAuthor": "Yazar: {0}", + "LabelByAuthor": "{0} tarafından", "LabelChangePassword": "Şifreyi Değiştir", "LabelChannels": "Kanallar", "LabelChapterCount": "{0} Bölüm", @@ -383,7 +383,7 @@ "LabelFolders": "Klasörler", "LabelFontBold": "Kalın", "LabelFontBoldness": "Yazı Tipi Kalınlığı", - "LabelFontFamily": "Yazı Tipi Ailesi", + "LabelFontFamily": "Yazı tipi ailesi", "LabelFontItalic": "İtalik", "LabelFontScale": "Yazı Tipi Ölçeği", "LabelFontStrikethrough": "Üstü Çizili", @@ -588,8 +588,8 @@ "LabelSettingsBookshelfViewHelp": "Ahşap raflı skeuomorfik tasarım", "LabelSettingsChromecastSupport": "Chromecast desteği", "LabelSettingsDateFormat": "Tarih Formatı", - "LabelSettingsEnableWatcher": "Değişiklikler için kütüphaneleri otomatik olarak tara", - "LabelSettingsEnableWatcherForLibrary": "Değişiklikler için kütüphaneyi otomatik olarak tara", + "LabelSettingsEnableWatcher": "Kütüphanelerdeki değişiklikleri otomatik olarak izle", + "LabelSettingsEnableWatcherForLibrary": "Kütüphanedeki değişiklikleri otomatik olarak izle", "LabelSettingsEnableWatcherHelp": "Dosya değişiklikleri algılandığında öğelerin otomatik olarak eklenmesini/güncellenmesini sağlar. *Sunucunun yeniden başlatılmasını gerektirir", "LabelSettingsEpubsAllowScriptedContent": "Epub'larda betiklenmiş içeriğe izin ver", "LabelSettingsEpubsAllowScriptedContentHelp": "Epub dosyalarının betik çalıştırmasına izin verin. Epub dosyalarının kaynağına güvenmiyorsanız bu ayarı devre dışı bırakmanız önerilir.", @@ -888,7 +888,7 @@ "MessageResetChaptersConfirm": "Bölümleri sıfırlamak ve yaptığınız değişiklikleri geri almak istediğinizden emin misiniz?", "MessageRestoreBackupConfirm": "Şu tarihte oluşturulan yedeği geri yüklemek istediğinizden emin misiniz", "MessageRestoreBackupWarning": "Bir yedeği geri yüklemek, /config konumundaki tüm veritabanının ve /metadata/items & /metadata/authors içindeki kapak resimlerinin üzerine yazacaktır.

Yedekler, kütüphane klasörlerinizdeki hiçbir dosyayı değiştirmez. Sunucu ayarlarını kütüphane klasörlerinizde kapak resmi ve üst veri saklamak için etkinleştirdiyseniz, bunlar yedeklenmez veya üzerine yazılmaz.

Sunucunuzu kullanan tüm istemciler otomatik olarak yenilenecektir.", - "MessageScheduleLibraryScanNote": "Çoğu kullanıcı için, bu özelliği devre dışı bırakıp klasör izleyici ayarını etkin tutmaları önerilir. Klasör izleyici, kütüphane klasörlerinizdeki değişiklikleri otomatik olarak algılayacaktır. Klasör izleyici her dosya sistemi için (NFS gibi) çalışmaz, bu nedenle bunun yerine zamanlanmış kütüphane taramaları kullanılabilir.", + "MessageScheduleLibraryScanNote": "Çoğu kullanıcı için bu ayarı pasif bırakması ve \"Kütüphanedeki değişiklikleri otomatik olarak izle\" seçeneğini aktif etmesi önerilir. O seçenek kütüphane dizinlerindeki herhangi bir değişikliği otomatik olarak tespit edecektir. Eğer dosya sisteminiz \"Kütüphanedeki değişiklikleri otomatik olarak izle\" yöntemini desteklemiyorsa (örn; NFS dosya sistemi) bu özelliği aktif edebilirsiniz.", "MessageScheduleRunEveryWeekdayAtTime": "Her {0} günü saat {1}'de çalıştır", "MessageSearchResultsFor": "Arama sonuçları", "MessageSelected": "{0} seçildi", From e7cb0466e6678f81ccddd6396623a19251607292 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20Ku=C5=BAnicki?= Date: Mon, 2 Feb 2026 07:04:06 +0100 Subject: [PATCH 015/124] Translated using Weblate (Polish) Currently translated at 94.5% (1100 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/pl/ --- client/strings/pl.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/strings/pl.json b/client/strings/pl.json index c50ebe85e..3a9593db0 100644 --- a/client/strings/pl.json +++ b/client/strings/pl.json @@ -437,8 +437,8 @@ "LabelLibraryItem": "Element biblioteki", "LabelLibraryName": "Nazwa biblioteki", "LabelLibrarySortByProgress": "Postęp: Ostatnio zaktualizowane", - "LabelLibrarySortByProgressFinished": "Postęp: Ukończone", - "LabelLibrarySortByProgressStarted": "Postęp: Rozpoczęte", + "LabelLibrarySortByProgressFinished": "Postęp: Ukończony", + "LabelLibrarySortByProgressStarted": "Postęp: Rozpoczęty", "LabelLimit": "Limit", "LabelLineSpacing": "Odstęp między wierszami", "LabelListenAgain": "Słuchaj ponownie", From 6e5feee78ad43a709f93b45d43acdcaf930abd76 Mon Sep 17 00:00:00 2001 From: FiendFEARing Date: Sun, 1 Feb 2026 08:13:41 +0100 Subject: [PATCH 016/124] Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/ --- client/strings/zh-cn.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/strings/zh-cn.json b/client/strings/zh-cn.json index 64c41619e..14c70cb10 100644 --- a/client/strings/zh-cn.json +++ b/client/strings/zh-cn.json @@ -436,9 +436,9 @@ "LabelLibraryFilterSublistEmpty": "没有 {0}", "LabelLibraryItem": "媒体库项目", "LabelLibraryName": "媒体库名称", - "LabelLibrarySortByProgress": "收听进度: 上次收听时间", - "LabelLibrarySortByProgressFinished": "收听进度: 已完成的", - "LabelLibrarySortByProgressStarted": "收听进度: 已开始的", + "LabelLibrarySortByProgress": "进度: 上次更新", + "LabelLibrarySortByProgressFinished": "进度: 已完成", + "LabelLibrarySortByProgressStarted": "进度: 已开始", "LabelLimit": "限制", "LabelLineSpacing": "行间距", "LabelListenAgain": "再次收听", From b8942c59319911a5edffdba1d46464b9e80e636a Mon Sep 17 00:00:00 2001 From: enosh Date: Sun, 1 Feb 2026 15:02:25 +0100 Subject: [PATCH 017/124] Translated using Weblate (Hebrew) Currently translated at 81.4% (947 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/he/ --- client/strings/he.json | 102 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 100 insertions(+), 2 deletions(-) diff --git a/client/strings/he.json b/client/strings/he.json index 0efc1ec9c..786683a5d 100644 --- a/client/strings/he.json +++ b/client/strings/he.json @@ -81,7 +81,7 @@ "ButtonRemove": "הסר", "ButtonRemoveAll": "הסר הכל", "ButtonRemoveAllLibraryItems": "הסר את כל פריטי הספרייה", - "ButtonRemoveFromContinueListening": "הסר מ- המשך האזנה", + "ButtonRemoveFromContinueListening": "הסר מ״המשך האזנה״", "ButtonRemoveFromContinueReading": "הסר מ- המשך קריאה", "ButtonRemoveSeriesFromContinueSeries": "הסר סדרה מ- המשך סדרה", "ButtonReset": "איפוס", @@ -121,6 +121,7 @@ "HeaderAccount": "חשבון", "HeaderAddCustomMetadataProvider": "הוסף ספק מטא-נתונים מותאם אישית", "HeaderAdvanced": "מתקדם", + "HeaderApiKeys": "מפתחות API", "HeaderAppriseNotificationSettings": "הגדרות התראות של Apprise", "HeaderAudioTracks": "רצועות קול", "HeaderAudiobookTools": "כלים לניהול קבצי ספרים קוליים", @@ -165,6 +166,7 @@ "HeaderMetadataOrderOfPrecedence": "סדר העדפת מטא-נתונים", "HeaderMetadataToEmbed": "מטא-נתונים להטמעה", "HeaderNewAccount": "חשבון חדש", + "HeaderNewApiKey": "מפתח API חדש", "HeaderNewLibrary": "ספרייה חדשה", "HeaderNotificationCreate": "צור התראה", "HeaderNotificationUpdate": "עדכון התראה", @@ -210,6 +212,7 @@ "HeaderTableOfContents": "תוכן עניינים", "HeaderTools": "כלים", "HeaderUpdateAccount": "עדכן חשבון", + "HeaderUpdateApiKey": "עדכן מפתח API", "HeaderUpdateAuthor": "עדכן יוצר", "HeaderUpdateDetails": "עדכן פרטים", "HeaderUpdateLibrary": "עדכן ספרייה", @@ -239,7 +242,10 @@ "LabelAllUsersExcludingGuests": "כל המשתמשים, ללא אורחים", "LabelAllUsersIncludingGuests": "כל המשתמשים כולל אורחים", "LabelAlreadyInYourLibrary": "כבר קיים בספרייה שלך", + "LabelApiKeyCreated": "מפתח API ״{0}״ נוצר בהצלחה.", + "LabelApiKeyCreatedDescription": "אנא העתק את מפתח ה־API כעת, לא ניתן יהיה להציגו שוב.", "LabelApiKeyUser": "פעל בשם המשתמש", + "LabelApiKeyUserDescription": "למפתח ה־API יהיו הרשאות זהות למשתמש שעל שמו הוא פועל. ביומני הרישום (logs), הפעולות יופיעו כאילו בוצעו על ידי המשתמש עצמו.", "LabelApiToken": "טוקן API", "LabelAppend": "הוסף לסוף", "LabelAudioBitrate": "קצב סיביות (לדוגמא 128k)", @@ -289,6 +295,7 @@ "LabelContinueListening": "המשך האזנה", "LabelContinueReading": "המשך קריאה", "LabelContinueSeries": "המשך סדרה", + "LabelCorsAllowed": "מקורות CORS מורשים", "LabelCover": "כריכה", "LabelCoverImageURL": "כתובת התמונה ברשת", "LabelCoverProvider": "ספק כריכה", @@ -302,6 +309,7 @@ "LabelDeleteFromFileSystemCheckbox": "מחיקה מהמערכת הקבצים (הסר סימון למחיקה רק ממסד הנתונים)", "LabelDescription": "תיאור", "LabelDeselectAll": "הסר בחירת כל הפריטים", + "LabelDetectedPattern": "תבנית שזוהתה:", "LabelDevice": "התקן", "LabelDeviceInfo": "מידע על התקן", "LabelDeviceIsAvailableTo": "התקן זמין ל...", @@ -351,6 +359,10 @@ "LabelExample": "דוגמה", "LabelExpandSeries": "הרחב סדרה", "LabelExpandSubSeries": "הרחב תת סדרה", + "LabelExpired": "פג תוקף", + "LabelExpiresAt": "יפוג בתאריך", + "LabelExpiresInSeconds": "יפוג בעוד (שניות)", + "LabelExpiresNever": "ללא הגבלת זמן", "LabelExplicit": "מפורש", "LabelExplicitChecked": "בוטה (מסומן)", "LabelExplicitUnchecked": "לא בוטה (לא מסומן)", @@ -366,6 +378,7 @@ "LabelFilterByUser": "סינון לפי משתמש", "LabelFindEpisodes": "מצא פרקים", "LabelFinished": "הושלם", + "LabelFinishedDate": "הושלם {0}", "LabelFolder": "תיקייה", "LabelFolders": "תיקיות", "LabelFontBold": "מודגש", @@ -410,6 +423,7 @@ "LabelLanguages": "שפות", "LabelLastBookAdded": "הספר האחרון שנוסף", "LabelLastBookUpdated": "הספר האחרון שעודכן", + "LabelLastProgressDate": "התקדמות אחרונה: {0}", "LabelLastSeen": "נראה לאחרונה", "LabelLastTime": "הזמן האחרון", "LabelLastUpdate": "עדכון אחרון", @@ -422,6 +436,9 @@ "LabelLibraryFilterSublistEmpty": "לא {0}", "LabelLibraryItem": "פריט ספרייה", "LabelLibraryName": "שם הספרייה", + "LabelLibrarySortByProgress": "התקדמות: עודכן לאחרונה", + "LabelLibrarySortByProgressFinished": "התקדמות: הושלם", + "LabelLibrarySortByProgressStarted": "התקדמות: הותחל", "LabelLimit": "מגבלה", "LabelLineSpacing": "מרווח שורה", "LabelListenAgain": "האזן שוב", @@ -430,6 +447,7 @@ "LabelLogLevelWarn": "אזהרה", "LabelLookForNewEpisodesAfterDate": "חפש פרקים חדשים לאחר תאריך זה", "LabelLowestPriority": "העדיפות הנמוכה ביותר", + "LabelMatchConfidence": "רמת ודאות", "LabelMatchExistingUsersBy": "התאם משתמשים קיימים לפי", "LabelMatchExistingUsersByDescription": "משמש לחיבור משתמשים קיימים. לאחר החיבור, המשתמשים יותאמו לפי זיהוי ייחודי מספק ה-SSO שלך", "LabelMaxEpisodesToDownload": "מספר פרקים מקסימלי להורדה. 0 - ללא הגבלה.", @@ -459,7 +477,9 @@ "LabelNewestAuthors": "הסופרים האחרונים", "LabelNewestEpisodes": "הפרקים החדשים ביותר", "LabelNextBackupDate": "תאריך הגיבוי הבא", + "LabelNextChapters": "הפרקים הבא יהיו:", "LabelNextScheduledRun": "הרצה מתוזמנת הבאה", + "LabelNoApiKeys": "אין מפתחות API", "LabelNoCustomMetadataProviders": "אין ספקי מטא-נתונים מותאמים אישית", "LabelNoEpisodesSelected": "לא נבחרו פרקים", "LabelNotFinished": "לא הושלם", @@ -475,16 +495,21 @@ "LabelNotificationsMaxQueueSize": "גודל התור המרבי לאירועי התראה", "LabelNotificationsMaxQueueSizeHelp": "האירועים מוגבלים לשליחה אחת לשנייה. האירועים יתעלמו אם התור מלא. הגדרה זו נועדה למנוע ספאם התראות.", "LabelNumberOfBooks": "מספר הספרים", + "LabelNumberOfChapters": "מספר הפרקים:", "LabelNumberOfEpisodes": "# פרקים", "LabelOpenIDAdvancedPermsClaimDescription": "שם OpenID claim המכילה הרשאות מתקדמות לפעולות משתמש בתוך האפליקציה, אשר יחולו על תפקידים שאינם מנהלי מערכת (אם הוגדרה). אם התביעה חסרה בתגובה, הגישה ל-ABS תידחה. אם אפשרות אחת חסרה, היא תטופל כ-false יש לוודא שטענת ספק הזהויות תואמת את המבנה הצפוי:", "LabelOpenIDClaims": "השאר את האפשרויות הבאות ריקות כדי להשבית הקצאת קבוצות והרשאות מתקדמת, ולאחר מכן להקצות אוטומטית את קבוצת 'משתמש'.", + "LabelOpenIDGroupClaimDescription": "שם ה־OpenID claim המכיל את רשימת הקבוצות של המשתמש. בדרך כלל נקרא groups. אם הוגדרה, האפליקציה תקצה תפקידים באופן אוטומטי על סמך השיוך לקבוצות, בתנאי ששמות הקבוצות ב־claim הם 'admin', 'user' או 'guest' (ללא רגישות לרישיות - Case-insensitive). ה־claim צריך להכיל רשימה; אם המשתמש משויך למספר קבוצות, האפליקציה תקצה את התפקיד בעל רמת הגישה הגבוהה ביותר. במידה ולא נמצאה קבוצה תואמת, הגישה תיחסם.", "LabelOpenRSSFeed": "פתח ערוץ RSS", "LabelOverwrite": "לשכפל", + "LabelPaginationPageXOfY": "עמוד {0} מתוך {1}", "LabelPassword": "סיסמה", "LabelPath": "נתיב", + "LabelPermanent": "קבוע", "LabelPermissionsAccessAllLibraries": "ניתן לגשת לכל הספריות", "LabelPermissionsAccessAllTags": "ניתן לגשת לכל התגיות", "LabelPermissionsAccessExplicitContent": "ניתן לגשת לתוכן בוטה", + "LabelPermissionsCreateEreader": "ניתן ליצור קורא ספרים דיגיטלי", "LabelPermissionsDelete": "מותר למחוק", "LabelPermissionsDownload": "מותר להוריד", "LabelPermissionsUpdate": "מותר לעדכן", @@ -492,6 +517,8 @@ "LabelPersonalYearReview": "השנה שלך בסקירה ({0})", "LabelPhotoPathURL": "נתיב/URL לתמונה", "LabelPlayMethod": "שיטת הפעלה", + "LabelPlaybackRateIncrementDecrement": "שיעור הגדלה/הפחתה של מהירות ההשמעה", + "LabelPlayerChapterNumberMarker": "{0} מתוך {1}", "LabelPlaylists": "רשימות השמעה", "LabelPodcast": "פודקאסט", "LabelPodcastSearchRegion": "אזור חיפוש פודקאסט", @@ -503,10 +530,14 @@ "LabelPrimaryEbook": "ספר אלקטרוני ראשי", "LabelProgress": "התקדמות", "LabelProvider": "ספק", + "LabelProviderAuthorizationValue": "ערך כותרת האימות (Authorization Header)", "LabelPubDate": "תאריך פרסום", "LabelPublishYear": "שנת הפרסום", "LabelPublishedDate": "פורסם {0}", + "LabelPublishedDecade": "עשור פרסום", + "LabelPublishedDecades": "עשורי פרסום", "LabelPublisher": "מוציא לאור", + "LabelPublishers": "מוצאים לאור", "LabelRSSFeedCustomOwnerEmail": "אימייל בעלים מותאם אישית", "LabelRSSFeedCustomOwnerName": "שם בעלים מותאם אישית", "LabelRSSFeedOpen": "ערוץ RSS פתוח", @@ -514,6 +545,7 @@ "LabelRSSFeedSlug": "Slug של ערוץ ה-RSS", "LabelRSSFeedURL": "כתובת ערוץ ה-RSS", "LabelRandomly": "באופן אקראי", + "LabelReAddSeriesToContinueListening": "הוסף סדרה בחזרה אל ״המשך האזנה״", "LabelRead": "קריאה", "LabelReadAgain": "קרא שוב", "LabelReadEbookWithoutProgress": "קרא/י ספר אלקטרוני ללא שמירת התקדמות", @@ -523,29 +555,44 @@ "LabelRedo": "עשה שוב", "LabelRegion": "אזור", "LabelReleaseDate": "תאריך הוצאה לאור", + "LabelRemoveAllMetadataAbs": "הסר את כל קבצי metadata.abs", + "LabelRemoveAllMetadataJson": "הסר את כל קבצי metadata.json", + "LabelRemoveAudibleBranding": "הסר פתיח וסיום של Audible מהפרקים", "LabelRemoveCover": "הסר כריכה", + "LabelRemoveMetadataFile": "הסר קבצי מטא־נתונים מתיקיות הפריטים בספרייה", + "LabelRemoveMetadataFileHelp": "הסר את כל קבצי metadata.json ו־metadata.abs מתיקיות {0}.", "LabelRowsPerPage": "שורות לעמוד", "LabelSearchTerm": "מונח חיפוש", "LabelSearchTitle": "כותרת חיפוש", "LabelSearchTitleOrASIN": "כותרת חיפוש או ASIN", "LabelSeason": "עונה", + "LabelSeasonNumber": "עונה #{0}", + "LabelSelectAll": "בחר הכל", "LabelSelectAllEpisodes": "בחר את כל הפרקים", "LabelSelectEpisodesShowing": "בחר {0} פרקים המוצגים", + "LabelSelectUser": "בחר משתמש", "LabelSelectUsers": "בחר משתמשים", "LabelSendEbookToDevice": "שלח ספר אלקטרוני ל...", "LabelSequence": "רצף", + "LabelSerial": "מספר סידורי", "LabelSeries": "סדרה", "LabelSeriesName": "שם הסדרה", "LabelSeriesProgress": "התקדמות בסדרה", + "LabelServerLogLevel": "רמת פירוט יומני הרישום", "LabelServerYearReview": "השנה בסקירה של השרת ({0})", "LabelSetEbookAsPrimary": "קבע כראשי", "LabelSetEbookAsSupplementary": "קבע כמשלים", + "LabelSettingsAllowIframe": "אפשר הטמעה בתוך iframe", "LabelSettingsAudiobooksOnly": "רק ספרי קול", "LabelSettingsAudiobooksOnlyHelp": "הפעלת ההגדרה הזו תתעלם מקבצי ספרים אלקטרוניים אלא אם כן הם נמצאים בתיקיית ספרי קול, שבמקרה זה יקבעו כספרים אלקטרוניים נלווים", "LabelSettingsBookshelfViewHelp": "עיצוב סקאומורפי עם מדפי עץ", "LabelSettingsChromecastSupport": "תמיכה ב-Chromecast", "LabelSettingsDateFormat": "פורמט תאריך", + "LabelSettingsEnableWatcher": "הפעל מעקב שינויים בספריות", + "LabelSettingsEnableWatcherForLibrary": "הפעל מעקב שינויים בספרייה", "LabelSettingsEnableWatcherHelp": "מאפשר הוספת/עדכון אוטומטי של פריטים כאשר שינויי קבצים זוהים. *דורש איתחול שרת", + "LabelSettingsEpubsAllowScriptedContent": "אפשור תוכן הכולל סקריפטים ב־ePubs", + "LabelSettingsEpubsAllowScriptedContentHelp": "אפשר לקובצי EPUB להריץ סקריפטים. מומלץ להשאיר את ההגדרה כבויה, אלא אם כן מקור קובצי ה־ePub מהימן.", "LabelSettingsExperimentalFeatures": "תכונות ניסיוניות", "LabelSettingsExperimentalFeaturesHelp": "תכונות בפיתוח שדורשות משובך ובדיקה. לחץ לפתיחת דיון ב-GitHub.", "LabelSettingsFindCovers": "מצא כריכות", @@ -554,7 +601,8 @@ "LabelSettingsHideSingleBookSeriesHelp": "סדרות הכוללות ספר אחד יוסתרו מדף הסדרות ומדף הבית.", "LabelSettingsHomePageBookshelfView": "השתמש בתצוגת מדף בדף הבית", "LabelSettingsLibraryBookshelfView": "השתמש בתצוגת מדף בספרייה", - "LabelSettingsOnlyShowLaterBooksInContinueSeries": "דלג על ספרים קודמים ב-המשך סדרה", + "LabelSettingsLibraryMarkAsFinishedWhen": "סמן פריט מדיה כהושלם כאשר", + "LabelSettingsOnlyShowLaterBooksInContinueSeries": "דלג על ספרים קודמים ב״המשך סדרה״", "LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "מדף המשך סדרות מציג את הספר הראשון שלא הושמע בסדרה שיש בה לפחות ספר אחד שהושלם ואין ספרים שכבר באמצע שמיעה. הפעלת הגדרה זו תמשיך סדרות מהספר שהושלם הכי מתקדם בסדרה במקום מהספר הראשון שלא הושמע.", "LabelSettingsParseSubtitles": "פענח כתוביות", "LabelSettingsParseSubtitlesHelp": "העתק כותרת משנה משם תיקיית הספר.
כותרת המשנה חייבת להיות מופרדת עם התו ״-״
לדוגמא, כותרת המשנה לספר ״שם הספר - כותרת משנה״, היא ״כותרת משנה״", @@ -571,13 +619,22 @@ "LabelSettingsStoreMetadataWithItem": "אחסן מטה-נתונים עם הפריט", "LabelSettingsStoreMetadataWithItemHelp": "כברירת מחדל, קבצי מטה-נתונים מאוחסנים ב- /metadata/items, הפעלת ההגדרה תאחסן קבצי מטה-נתונים בתיקיית פריט שלך בספרייה", "LabelSettingsTimeFormat": "פורמט זמן", + "LabelShare": "שתף", + "LabelShareDownloadableHelp": "אפשר למי שיש ברשותו קישור שיתוף להוריד קובץ ZIP של פריט הספרייה.", + "LabelShareURL": "שתף קישור", "LabelShowAll": "הצג הכל", + "LabelShowSeconds": "הצג שניות", + "LabelShowSubtitles": "הצג כתוביות", "LabelSize": "גודל", "LabelSleepTimer": "טיימר שינה", + "LabelSortAscending": "סדר עולה", + "LabelSortDescending": "סדר יורד", + "LabelSortPubDate": "מיין לפי תאריך פרסום", "LabelStart": "התחל", "LabelStartTime": "זמן התחלה", "LabelStarted": "התחיל", "LabelStartedAt": "התחיל ב", + "LabelStartedDate": "הותחל {0}", "LabelStatsAudioTracks": "רצועות שמע", "LabelStatsAuthors": "מחברים", "LabelStatsBestDay": "היום הטוב ביותר", @@ -607,7 +664,13 @@ "LabelTheme": "ערכת נושא", "LabelThemeDark": "כהה", "LabelThemeLight": "בהיר", + "LabelThemeSepia": "ספיה", "LabelTimeBase": "בסיס זמן", + "LabelTimeDurationXHours": "{0} שעות", + "LabelTimeDurationXMinutes": "{0} דקות", + "LabelTimeDurationXSeconds": "{0} שניות", + "LabelTimeInMinutes": "זמן בשניות", + "LabelTimeLeft": "נותרו {0}", "LabelTimeListened": "זמן האזנה", "LabelTimeListenedToday": "זמן האזנה היום", "LabelTimeRemaining": "{0} נותרו", @@ -615,6 +678,7 @@ "LabelTitle": "כותרת", "LabelToolsEmbedMetadata": "הטמעת מטה-נתונים", "LabelToolsEmbedMetadataDescription": "הטמעת מטה-נתונים לקבצי שמע כולל תמונות כריכה ופרקים.", + "LabelToolsM4bEncoder": "מקודד M4B", "LabelToolsMakeM4b": "יצירת קובץ אודיו M4B", "LabelToolsMakeM4bDescription": "יצירת קובץ אודיו .M4B עם מטה-נתונים מוטמעים, תמונת שער ופרקים.", "LabelToolsSplitM4b": "פיצול M4B ל-MP3", @@ -627,29 +691,39 @@ "LabelTracksMultiTrack": "רב-ערוצי", "LabelTracksNone": "אין ערוצים", "LabelTracksSingleTrack": "רצועה יחידה", + "LabelTrailer": "קדימון", "LabelType": "סוג", "LabelUnabridged": "לא מקוצר", "LabelUndo": "בטל", "LabelUnknown": "לא ידוע", + "LabelUnknownPublishDate": "תאריך הוצאה לאור לא ידוע", "LabelUpdateCover": "עדכן כריכה", "LabelUpdateCoverHelp": "אפשר החלפה של כריכות קיימות עבור הספרים הנבחרים כאשר נמצאה התאמה", "LabelUpdateDetails": "עדכון פרטים", "LabelUpdateDetailsHelp": "אפשר החלפה של פרטים קיימים עבור הספרים הנבחרים כאשר נמצאה התאמה", "LabelUpdatedAt": "עודכן ב-", "LabelUploaderDragAndDrop": "גרור ושחרר קבצים או תיקיות", + "LabelUploaderDragAndDropFilesOnly": "גרור ושחרר קבצים", "LabelUploaderDropFiles": "שחרר קבצים", "LabelUploaderItemFetchMetadataHelp": "משיכת כותרת, סופר וסדרה באופן אוטומטי", + "LabelUseAdvancedOptions": "השתמש באפשרויות מתקדמות", "LabelUseChapterTrack": "השתמש ברצועות הפרקים", "LabelUseFullTrack": "השתמש ברצועה המלאה", + "LabelUseZeroForUnlimited": "השתמש ב־0 מתוך אין־סוף", "LabelUser": "משתמש", "LabelUsername": "שם משתמש", "LabelValue": "ערך", "LabelVersion": "גרסה", "LabelViewBookmarks": "הצג סימניות", "LabelViewChapters": "הצג פרקים", + "LabelViewPlayerSettings": "הצג הגדרות נגן", "LabelViewQueue": "הצג תור נגן", "LabelVolume": "עוצמת קול", + "LabelWebRedirectURLsDescription": "יש לאשר את הכתובות הבאות אצל ספק ה־OAuth כדי לאפשר הפניה חזרה לאפליקציית הדפדפן לאחר ההתחברות:", + "LabelWebRedirectURLsSubfolder": "תיקיית משנה לכתובות הפניה", "LabelWeekdaysToRun": "ימי השבוע להרצה", + "LabelXBooks": "{0} ספרים", + "LabelXItems": "{0} פריטים", "LabelYearReviewHide": "הסתר סקירת שנה", "LabelYearReviewShow": "הצג סקירת שנה", "LabelYourAudiobookDuration": "משך הספר הקולי שלך", @@ -658,31 +732,55 @@ "LabelYourProgress": "ההתקדמות שלך", "MessageAddToPlayerQueue": "הוסף לתור הנגן", "MessageAppriseDescription": "כדי להשתמש בתכונה זו יש לך להריץ מופע של ממשק התכנית האפליקציה או API שיטפל בבקשות אלו.
כתובת URL של ממשק ה-Apprise API צריכה להיות הנתיב המלא לשליחת ההתראה, לדוגמה, אם המופע של ה-API שלך מוצע ב-http://192.168.1.1:8337 אז עליך לשים http://192.168.1.1:8337/notify.", + "MessageAsinCheck": "יש לוודא שימוש ב־ASIN מאזור ה־Audible הנכון, ולא מ־Amazon.", + "MessageAuthenticationLegacyTokenWarning": "אסימוני API ישנים יוסרו בעתיד. יש להשתמש ב מפתחות API במקום.", + "MessageAuthenticationOIDCChangesRestart": "יש להפעיל מחדש את השרת לאחר השמירה כדי להחיל את שינויי ה־OIDC.", + "MessageAuthenticationSecurityMessage": "האימות שופר מטעמי אבטחה. כל המשתמשים נדרשים להתחבר מחדש.", "MessageBackupsDescription": "גיבויים כוללים משתמשים, התקדמות משתמש, פרטי פריטי ספרייה, הגדרות שרת ותמונות השמורות ב-/metadata/items & /metadata/authors. גיבויים לא כוללים קבצים שמורים בתיקיות הספרייה שלך.", + "MessageBackupsLocationEditNote": "הערה: שינוי מיקום הגיבוי לא יגרום להעברה או לשינוי של גיבויים קיימים", + "MessageBackupsLocationNoEditNote": "הערה: מיקום הגיבוי מוגדר באמצעות משתנה סביבה ולא ניתן לשנותו כאן.", + "MessageBackupsLocationPathEmpty": "נתיב מיקום הגיבוי אינו יכול להיות ריק", + "MessageBatchEditPopulateMapDetailsAllHelp": "מלא את השדות הפעילים בנתונים מכל הפריטים. שדות בעלי ערכים מרובים ימוזגו", + "MessageBatchEditPopulateMapDetailsItemHelp": "מלא את שדות פרטי המיפוי הפעילים בנתונים מפריט זה", "MessageBatchQuickMatchDescription": "התאמה מהירה תנסה להוסיף כריכות ומטה-נתונים חסרים עבור הפריטים הנבחרים. הפעל את האפשרויות למטה כדי לאפשר להתאמה מהירה להחליף כריכות קיימות ו/או מטה-נתונים.", "MessageBookshelfNoCollections": "עדיין לא יצרת אוספים", + "MessageBookshelfNoCollectionsHelp": "האוספים ציבוריים. כל המשתמשים בעלי גישה לספרייה יכולים לראות אותם.", "MessageBookshelfNoRSSFeeds": "אין ערוצי RSS פתוחים", "MessageBookshelfNoResultsForFilter": "אין תוצאות עבור סינון \"{0}: {1}\"", + "MessageBookshelfNoResultsForQuery": "אין תוצאות עבור השאילתה", "MessageBookshelfNoSeries": "אין לך סדרות", + "MessageBulkChapterPattern": "כמה פרקים להוסיף לפי תבנית מספור זו?", "MessageChapterEndIsAfter": "זמן סיום הפרק אחרי סיום הספר הקולי שלך", "MessageChapterErrorFirstNotZero": "הפרק הראשון חייב להתחיל ב-0", "MessageChapterErrorStartGteDuration": "זמן התחלה לא תקין, חייב להיות פחות ממשך הספר הקולי", "MessageChapterErrorStartLtPrev": "זמן התחלה לא תקין, חייב להיות גדול או שווה לזמן ההתחלה של הפרק הקודם", "MessageChapterStartIsAfter": "התחלת הפרק אחרי סיום הספר הקולי שלך", + "MessageChaptersNotFound": "לא נמצאו פרקים", "MessageCheckingCron": "בודק את תזמון העבודה...", "MessageConfirmCloseFeed": "האם אתה בטוח שאתה רוצה לסגור את הערוץ הזה?", + "MessageConfirmDeleteApiKey": "האם למחוק את מפתח ה־API \"{0}\"?", "MessageConfirmDeleteBackup": "האם אתה בטוח שברצונך למחוק גיבוי עבור {0}?", + "MessageConfirmDeleteDevice": "האם למחוק את הקורא האלקטרוני \"{0}\"?", "MessageConfirmDeleteFile": "הקובץ ימחק לצמיתות מהמערכת שלך. האם אתה בטוח?", "MessageConfirmDeleteLibrary": "האם אתה בטוח שברצונך למחוק לצמיתות את הספרייה \"{0}\"?", "MessageConfirmDeleteLibraryItem": "פריט הספרייה יימחק לצמיתות ממסד הנתונים ומהמערכת שלך. האם אתה בטוח?", "MessageConfirmDeleteLibraryItems": "פריטי הספרייה {0} יימחקו ממסד הנתונים ומהמערכת שלך. האם אתה בטוח?", + "MessageConfirmDeleteMetadataProvider": "האם למחוק את ספק המטא־נתונים המותאם \"{0}\"?", + "MessageConfirmDeleteNotification": "האם למחוק התראה זו?", "MessageConfirmDeleteSession": "האם אתה בטוח שאתה רוצה למחוק את ההפעלה הזו?", + "MessageConfirmEmbedMetadataInAudioFiles": "האם להטמיע מטא־נתונים ב־{0} קובצי שמע?", "MessageConfirmForceReScan": "האם אתה בטוח שאתה רוצה להכריח סריקה מחדש?", "MessageConfirmMarkAllEpisodesFinished": "האם אתה בטוח שברצונך לסמן את כל הפרקים כהסתיימו?", "MessageConfirmMarkAllEpisodesNotFinished": "האם אתה בטוח שברצונך לסמן את כל הפרקים כלא הסתיימו?", + "MessageConfirmMarkItemFinished": "האם לסמן את \"{0}\" כהושלם?", + "MessageConfirmMarkItemNotFinished": "האם לסמן את \"{0}\" כלא הושלם?", "MessageConfirmMarkSeriesFinished": "האם אתה בטוח שברצונך לסמן את כל הספרים בסדרה זו כהסתיימו?", "MessageConfirmMarkSeriesNotFinished": "האם אתה בטוח שברצונך לסמן את כל הספרים בסדרה זו כלא הסתיימו?", + "MessageConfirmNotificationTestTrigger": "האם להפעיל התראה זו עם נתוני בדיקה?", + "MessageConfirmPurgeCache": "ניקוי המטמון ימחק את כל התיקייה ב־/metadata/cache.

האם למחוק את תיקיית המטמון?", + "MessageConfirmPurgeItemsCache": "ניקוי מטמון הפריטים ימחק את כל התיקייה ב־metadata/cache/items/.
האם למחוק?", "MessageConfirmQuickEmbed": "אזהרה! הטמעה מהירה לא תגבה גיבוי של קבצי האודיו שלך. וודא שיש לך גיבוי של קבצי האודיו שלך.

האם ברצונך להמשיך?", + "MessageConfirmQuickMatchEpisodes": "התאמה מהירה תדרוס פרטים עבור פרקים תואמים. רק פרקים ללא התאמה יעודכנו. האם להמשיך?", "MessageConfirmReScanLibraryItems": "האם אתה בטוח שברצונך לסרוק מחדש {0} פריטים?", "MessageConfirmRemoveAllChapters": "האם אתה בטוח שברצונך להסיר את כל הפרקים?", "MessageConfirmRemoveAuthor": "האם אתה בטוח שברצונך להסיר את המחבר \"{0}\"?", From cc5244c5966ce1a3041148eea466fe16bda1be7e Mon Sep 17 00:00:00 2001 From: Jan-Eric Myhrgren Date: Mon, 2 Feb 2026 08:50:32 +0100 Subject: [PATCH 018/124] Translated using Weblate (Swedish) Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sv/ --- client/strings/sv.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/strings/sv.json b/client/strings/sv.json index d2fb254ed..e1c08efa5 100644 --- a/client/strings/sv.json +++ b/client/strings/sv.json @@ -436,9 +436,9 @@ "LabelLibraryFilterSublistEmpty": "Ingen {0}", "LabelLibraryItem": "Objekt", "LabelLibraryName": "Biblioteksnamn", - "LabelLibrarySortByProgress": "Framsteg: senast uppdaterat", - "LabelLibrarySortByProgressFinished": "Framsteg: avslutad", - "LabelLibrarySortByProgressStarted": "Framsteg: påbörjad", + "LabelLibrarySortByProgress": "Status: Senast uppdaterad", + "LabelLibrarySortByProgressFinished": "Status: Avslutad", + "LabelLibrarySortByProgressStarted": "Status: Startad", "LabelLimit": "Begränsning", "LabelLineSpacing": "Radavstånd", "LabelListenAgain": "Lyssna igen", From edfce4605856f34e68400f072a3dee73f3b10622 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Delta=20Umh=C3=B6fer?= Date: Sat, 31 Jan 2026 20:09:59 +0100 Subject: [PATCH 019/124] Translated using Weblate (German) Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/ --- client/strings/de.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/strings/de.json b/client/strings/de.json index 970c94233..cf36b07fd 100644 --- a/client/strings/de.json +++ b/client/strings/de.json @@ -436,7 +436,7 @@ "LabelLibraryFilterSublistEmpty": "Keine {0}", "LabelLibraryItem": "Bibliothekseintrag", "LabelLibraryName": "Bibliotheksname", - "LabelLibrarySortByProgress": "Fortschritt: Zuletzt aktualisiert", + "LabelLibrarySortByProgress": "Fortschritt: Letzte Aktualisierung", "LabelLibrarySortByProgressFinished": "Fortschritt: Beendet", "LabelLibrarySortByProgressStarted": "Fortschritt: Gestartet", "LabelLimit": "Begrenzung", From 31630f50a59d598478db6692aaba6bf3089435be Mon Sep 17 00:00:00 2001 From: dapitch666 Date: Mon, 2 Feb 2026 12:17:14 +0100 Subject: [PATCH 020/124] Translated using Weblate (French) Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/ --- client/strings/fr.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/strings/fr.json b/client/strings/fr.json index 6ff72c7ef..497426d37 100644 --- a/client/strings/fr.json +++ b/client/strings/fr.json @@ -436,11 +436,11 @@ "LabelLibraryFilterSublistEmpty": "Aucun {0}", "LabelLibraryItem": "Élément de bibliothèque", "LabelLibraryName": "Nom de la bibliothèque", - "LabelLibrarySortByProgress": "Progression : dernière mise à jour", + "LabelLibrarySortByProgress": "Progression : Mise à jour", "LabelLibrarySortByProgressFinished": "Progression : Terminé", - "LabelLibrarySortByProgressStarted": "Progression : Commencé", + "LabelLibrarySortByProgressStarted": "Progression : En cours", "LabelLimit": "Limite", - "LabelLineSpacing": "Espacement des lignes", + "LabelLineSpacing": "Interligne", "LabelListenAgain": "Écouter à nouveau", "LabelLogLevelDebug": "Débogage", "LabelLogLevelInfo": "Info", @@ -961,7 +961,7 @@ "PlaceholderNewCollection": "Nom de la nouvelle collection", "PlaceholderNewFolderPath": "Nouveau chemin de dossier", "PlaceholderNewPlaylist": "Nouveau nom de liste de lecture", - "PlaceholderSearch": "Recherche…", + "PlaceholderSearch": "Recherche...", "PlaceholderSearchEpisode": "Rechercher un épisode…", "StatsAuthorsAdded": "auteurs ajoutés", "StatsBooksAdded": "livres ajoutés", From 2ee893062f1eb829107f7a358ec9ff128949e6f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Llu=C3=ADs=20Forns?= Date: Tue, 3 Feb 2026 11:37:12 +0100 Subject: [PATCH 021/124] Translated using Weblate (Catalan) Currently translated at 92.2% (1073 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ca/ --- client/strings/ca.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/client/strings/ca.json b/client/strings/ca.json index dbda8b8e4..4180b2518 100644 --- a/client/strings/ca.json +++ b/client/strings/ca.json @@ -166,6 +166,7 @@ "HeaderMetadataOrderOfPrecedence": "Ordre de Precedència de Metadades", "HeaderMetadataToEmbed": "Metadades a Inserir", "HeaderNewAccount": "Nou Compte", + "HeaderNewApiKey": "Nova clau API", "HeaderNewLibrary": "Nova Biblioteca", "HeaderNotificationCreate": "Crea Notificació", "HeaderNotificationUpdate": "Actualització de Notificació", @@ -199,6 +200,7 @@ "HeaderSettingsExperimental": "Funcionalitats experimentals", "HeaderSettingsGeneral": "Generals", "HeaderSettingsScanner": "Escàner", + "HeaderSettingsSecurity": "Seguretat", "HeaderSettingsWebClient": "Client web", "HeaderSleepTimer": "Temporitzador de son", "HeaderStatsLargestItems": "Elements més grans", @@ -421,6 +423,9 @@ "LabelLibraryFilterSublistEmpty": "Sense {0}", "LabelLibraryItem": "Element de Biblioteca", "LabelLibraryName": "Nom de Biblioteca", + "LabelLibrarySortByProgress": "Progrés: Última actualització", + "LabelLibrarySortByProgressFinished": "Progrés: Finalitzat", + "LabelLibrarySortByProgressStarted": "Progrés: Començat", "LabelLimit": "Límits", "LabelLineSpacing": "Interlineat", "LabelListenAgain": "Escoltar de nou", @@ -443,7 +448,7 @@ "LabelMetadataProvider": "Proveïdor de metadades", "LabelMinute": "Minut", "LabelMinutes": "Minuts", - "LabelMissing": "Absent", + "LabelMissing": "Falta", "LabelMissingEbook": "No té llibre electrònic", "LabelMissingSupplementaryEbook": "No té ebook complementari", "LabelMobileRedirectURIs": "URI de redirecció mòbil permeses", From c15cb48def796006807ff105945f22829f82bab8 Mon Sep 17 00:00:00 2001 From: Khashayar Toodehfallah Date: Fri, 6 Feb 2026 14:31:09 -0500 Subject: [PATCH 022/124] Improved subtitle parsing to account for bare colon in title --- server/finders/BookFinder.js | 8 ++++---- server/utils/parsers/parseNfoMetadata.js | 2 +- test/server/finders/BookFinder.test.js | 3 +++ test/server/utils/parsers/parseNfoMetadata.test.js | 14 ++++++++++++++ 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/server/finders/BookFinder.js b/server/finders/BookFinder.js index 6d31b2ab9..90fed951f 100644 --- a/server/finders/BookFinder.js +++ b/server/finders/BookFinder.js @@ -227,7 +227,7 @@ class BookFinder { title = this.#removeAuthorFromTitle(title) const titleTransformers = [ - [/([,:;_]| by ).*/g, ''], // Remove subtitle + [/(: |[,;_]| by ).*/g, ''], // Remove subtitle [/(^| )\d+k(bps)?( |$)/, ' '], // Remove bitrate [/ (2nd|3rd|\d+th)\s+ed(\.|ition)?/g, ''], // Remove edition [/(^| |\.)(m4b|m4a|mp3)( |$)/g, ''], // Remove file-type @@ -646,11 +646,11 @@ class BookFinder { module.exports = new BookFinder() function hasSubtitle(title) { - return title.includes(':') || title.includes(' - ') + return title.includes(': ') || title.includes(' - ') } function stripSubtitle(title) { - if (title.includes(':')) { - return title.split(':')[0].trim() + if (title.includes(': ')) { + return title.split(': ')[0].trim() } else if (title.includes(' - ')) { return title.split(' - ')[0].trim() } diff --git a/server/utils/parsers/parseNfoMetadata.js b/server/utils/parsers/parseNfoMetadata.js index 6682a0078..4276443d0 100644 --- a/server/utils/parsers/parseNfoMetadata.js +++ b/server/utils/parsers/parseNfoMetadata.js @@ -22,7 +22,7 @@ function parseNfoMetadata(nfoText) { switch (key) { case 'title': { - const titleMatch = value.match(/^(.*?):(.*)$/) + const titleMatch = value.match(/^(.*?): (.*)$/) if (titleMatch) { metadata.title = titleMatch[1].trim() metadata.subtitle = titleMatch[2].trim() diff --git a/test/server/finders/BookFinder.test.js b/test/server/finders/BookFinder.test.js index 6578ca82a..cb697b81a 100644 --- a/test/server/finders/BookFinder.test.js +++ b/test/server/finders/BookFinder.test.js @@ -35,7 +35,10 @@ describe('TitleCandidates', () => { ['adds candidate, removing author', `anna karenina by ${cleanAuthor}`, ['anna karenina']], ['does not add empty candidate after removing author', cleanAuthor, []], ['adds candidate, removing subtitle', 'anna karenina: subtitle', ['anna karenina']], + ['adds candidate, not stripping subtitle for bare colon in title', '10:04', ['10:04']], + ['adds candidate, not stripping subtitle for colon between words without space', 'making the mission:impossible movies', ['making the mission:impossible movies']], ['adds candidate + variant, removing "by ..."', 'anna karenina by arnold schwarzenegger', ['anna karenina', 'anna karenina by arnold schwarzenegger']], + ['adds candidate + variant, removing "by ..." when title has bare colon', '10:04 by ben lerner', ['10:04', '10:04 by ben lerner']], ['adds candidate + variant, removing bitrate', 'anna karenina 64kbps', ['anna karenina', 'anna karenina 64kbps']], ['adds candidate + variant, removing edition 1', 'anna karenina 2nd edition', ['anna karenina', 'anna karenina 2nd edition']], ['adds candidate + variant, removing edition 2', 'anna karenina 4th ed.', ['anna karenina', 'anna karenina 4th ed.']], diff --git a/test/server/utils/parsers/parseNfoMetadata.test.js b/test/server/utils/parsers/parseNfoMetadata.test.js index 9ff51fbe5..ba11ddff2 100644 --- a/test/server/utils/parsers/parseNfoMetadata.test.js +++ b/test/server/utils/parsers/parseNfoMetadata.test.js @@ -21,6 +21,20 @@ describe('parseNfoMetadata', () => { expect(result.subtitle).to.equal('A Novel') }) + it('does not split title on bare colon without space', () => { + const nfoText = 'Title: 10:04' + const result = parseNfoMetadata(nfoText) + expect(result.title).to.equal('10:04') + expect(result.subtitle).to.be.undefined + }) + + it('does not split title on colon between words without space', () => { + const nfoText = 'Title: Making the Mission:Impossible Movies' + const result = parseNfoMetadata(nfoText) + expect(result.title).to.equal('Making the Mission:Impossible Movies') + expect(result.subtitle).to.be.undefined + }) + it('parses authors', () => { const nfoText = 'Author: F. Scott Fitzgerald' const result = parseNfoMetadata(nfoText) From dd4fc09909a0a2862756292b11fd684b2f427c1f Mon Sep 17 00:00:00 2001 From: Michael Tuttle Date: Sun, 8 Feb 2026 00:19:23 -0700 Subject: [PATCH 023/124] Fix OpenAPI spec description --- custom-metadata-provider-specification.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom-metadata-provider-specification.yaml b/custom-metadata-provider-specification.yaml index 71cbba23a..a0ec172ae 100644 --- a/custom-metadata-provider-specification.yaml +++ b/custom-metadata-provider-specification.yaml @@ -127,7 +127,7 @@ components: duration: type: integer format: int64 - description: Duration in seconds + description: Duration in minutes SeriesMetadata: type: object From fa5fa7b7884adad3d020745aa97f5a52ca6e6156 Mon Sep 17 00:00:00 2001 From: advplyr Date: Sat, 14 Feb 2026 17:17:12 -0600 Subject: [PATCH 024/124] Fix server crash on /me/progress/:libraryItemId/:episodeId? when episodeId is not passed in for a podcast library item #5058 --- server/models/User.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/server/models/User.js b/server/models/User.js index 36b2eca98..936efde13 100644 --- a/server/models/User.js +++ b/server/models/User.js @@ -782,7 +782,14 @@ class User extends Model { error: 'Library item not found', statusCode: 404 } + } else if (libraryItem.mediaType !== 'book') { + Logger.error(`[User] createUpdateMediaProgress: library item ${progressPayload.libraryItemId} is not a book`) + return { + error: 'Library item is not a book', + statusCode: 400 + } } + mediaItemId = libraryItem.media.id mediaProgress = libraryItem.media.mediaProgresses?.[0] } From ade1752e979216bcc206cc423a1a7a89978204e1 Mon Sep 17 00:00:00 2001 From: Matt Andreko Date: Sun, 15 Feb 2026 22:01:36 -0500 Subject: [PATCH 025/124] Fix IDOR bugs --- server/controllers/MeController.js | 48 ++- test/server/MeController.test.js | 638 +++++++++++++++++++++++++++++ 2 files changed, 682 insertions(+), 4 deletions(-) create mode 100644 test/server/MeController.test.js diff --git a/server/controllers/MeController.js b/server/controllers/MeController.js index 51773a5ad..c5968f521 100644 --- a/server/controllers/MeController.js +++ b/server/controllers/MeController.js @@ -63,7 +63,7 @@ class MeController { * @param {Response} res */ async getItemListeningSessions(req, res) { - const libraryItem = await Database.libraryItemModel.findByPk(req.params.libraryItemId) + const libraryItem = await Database.libraryItemModel.getExpandedById(req.params.libraryItemId) const episode = await Database.podcastEpisodeModel.findByPk(req.params.episodeId) if (!libraryItem || (libraryItem.isPodcast && !episode)) { @@ -71,6 +71,12 @@ class MeController { return res.sendStatus(404) } + // Check if user has access to this library item + if (!req.user.checkCanAccessLibraryItem(libraryItem)) { + Logger.error(`[MeController] User "${req.user.username}" attempted to access listening sessions for library item "${req.params.libraryItemId}" without access`) + return res.sendStatus(403) + } + const mediaItemId = episode?.id || libraryItem.mediaId let listeningSessions = await this.getUserItemListeningSessionsHelper(req.user.id, mediaItemId) @@ -125,6 +131,13 @@ class MeController { * @param {Response} res */ async removeMediaProgress(req, res) { + // Verify the media progress belongs to the current user + const mediaProgress = req.user.mediaProgresses.find((mp) => mp.id === req.params.id) + if (!mediaProgress) { + Logger.error(`[MeController] Media progress not found or does not belong to user "${req.user.username}"`) + return res.sendStatus(404) + } + await Database.mediaProgressModel.removeById(req.params.id) req.user.mediaProgresses = req.user.mediaProgresses.filter((mp) => mp.id !== req.params.id) @@ -192,7 +205,16 @@ class MeController { * @param {Response} res */ async createBookmark(req, res) { - if (!(await Database.libraryItemModel.checkExistsById(req.params.id))) return res.sendStatus(404) + const libraryItem = await Database.libraryItemModel.getExpandedById(req.params.id) + if (!libraryItem) { + return res.sendStatus(404) + } + + // Check if user has access to this library item + if (!req.user.checkCanAccessLibraryItem(libraryItem)) { + Logger.error(`[MeController] User "${req.user.username}" attempted to create bookmark for library item "${req.params.id}" without access`) + return res.sendStatus(403) + } const { time, title } = req.body if (isNullOrNaN(time)) { @@ -216,7 +238,16 @@ class MeController { * @param {Response} res */ async updateBookmark(req, res) { - if (!(await Database.libraryItemModel.checkExistsById(req.params.id))) return res.sendStatus(404) + const libraryItem = await Database.libraryItemModel.getExpandedById(req.params.id) + if (!libraryItem) { + return res.sendStatus(404) + } + + // Check if user has access to this library item + if (!req.user.checkCanAccessLibraryItem(libraryItem)) { + Logger.error(`[MeController] User "${req.user.username}" attempted to update bookmark for library item "${req.params.id}" without access`) + return res.sendStatus(403) + } const { time, title } = req.body if (isNullOrNaN(time)) { @@ -245,7 +276,16 @@ class MeController { * @param {Response} res */ async removeBookmark(req, res) { - if (!(await Database.libraryItemModel.checkExistsById(req.params.id))) return res.sendStatus(404) + const libraryItem = await Database.libraryItemModel.getExpandedById(req.params.id) + if (!libraryItem) { + return res.sendStatus(404) + } + + // Check if user has access to this library item + if (!req.user.checkCanAccessLibraryItem(libraryItem)) { + Logger.error(`[MeController] User "${req.user.username}" attempted to remove bookmark for library item "${req.params.id}" without access`) + return res.sendStatus(403) + } const time = Number(req.params.time) if (isNaN(time)) { diff --git a/test/server/MeController.test.js b/test/server/MeController.test.js new file mode 100644 index 000000000..2f6b502db --- /dev/null +++ b/test/server/MeController.test.js @@ -0,0 +1,638 @@ +const { expect } = require('chai') +const { Sequelize } = require('sequelize') +const sinon = require('sinon') + +const Database = require('../../../server/Database') +const ApiRouter = require('../../../server/routers/ApiRouter') +const MeController = require('../../../server/controllers/MeController') +const Auth = require('../../../server/Auth') +const Logger = require('../../../server/Logger') +const SocketAuthority = require('../../../server/SocketAuthority') + +describe('MeController - IDOR Security Tests', () => { + /** @type {ApiRouter} */ + let apiRouter + + beforeEach(async () => { + global.ServerSettings = {} + Database.sequelize = new Sequelize({ dialect: 'sqlite', storage: ':memory:', logging: false }) + Database.sequelize.uppercaseFirst = (str) => (str ? `${str[0].toUpperCase()}${str.substr(1)}` : '') + await Database.buildModels() + + // Create mock server object with required dependencies + const mockServer = { + auth: new Auth(), + playbackSessionManager: { sessions: [] }, + abMergeManager: {}, + backupManager: {}, + podcastManager: {}, + audioMetadataManager: {}, + cronManager: {}, + emailManager: {}, + apiCacheManager: { middleware: (req, res, next) => next() } + } + + apiRouter = new ApiRouter(mockServer) + + sinon.stub(Logger, 'info') + sinon.stub(Logger, 'error') + sinon.stub(SocketAuthority, 'clientEmitter') + }) + + afterEach(async () => { + sinon.restore() + + // Clear all tables + await Database.sequelize.sync({ force: true }) + }) + + describe('removeMediaProgress - IDOR Protection', () => { + let user1, user2 + let mediaProgress1, mediaProgress2 + + beforeEach(async () => { + // Create two users + user1 = await Database.userModel.create({ + username: 'user1', + pash: 'hashed_password_1', + type: 'user', + isActive: true + }) + + user2 = await Database.userModel.create({ + username: 'user2', + pash: 'hashed_password_2', + type: 'user', + isActive: true + }) + + // Create library and book + const library = await Database.libraryModel.create({ name: 'Test Library', mediaType: 'book' }) + const libraryFolder = await Database.libraryFolderModel.create({ path: '/test', libraryId: library.id }) + const book = await Database.bookModel.create({ title: 'Test Book', audioFiles: [], tags: [], narrators: [], genres: [], chapters: [] }) + const libraryItem = await Database.libraryItemModel.create({ + libraryFiles: [], + mediaId: book.id, + mediaType: 'book', + libraryId: library.id, + libraryFolderId: libraryFolder.id + }) + + // Create media progress for each user + mediaProgress1 = await Database.mediaProgressModel.create({ + userId: user1.id, + mediaItemId: book.id, + mediaItemType: 'book', + duration: 1000, + currentTime: 500, + isFinished: false + }) + + mediaProgress2 = await Database.mediaProgressModel.create({ + userId: user2.id, + mediaItemId: book.id, + mediaItemType: 'book', + duration: 1000, + currentTime: 300, + isFinished: false + }) + + // Load media progresses into users + user1.mediaProgresses = await user1.getMediaProgresses() + user2.mediaProgresses = await user2.getMediaProgresses() + }) + + it('should allow user to delete their own media progress', async () => { + const fakeReq = { + user: user1, + params: { id: mediaProgress1.id } + } + const fakeRes = { + sendStatus: sinon.spy(), + status: sinon.stub().returnsThis(), + send: sinon.spy() + } + + await MeController.removeMediaProgress(fakeReq, fakeRes) + + expect(fakeRes.sendStatus.calledWith(200)).to.be.true + + // Verify media progress was deleted + const deletedProgress = await Database.mediaProgressModel.findByPk(mediaProgress1.id) + expect(deletedProgress).to.be.null + }) + + it('should prevent user from deleting another users media progress (IDOR)', async () => { + const fakeReq = { + user: user1, + params: { id: mediaProgress2.id } // Trying to delete user2's progress + } + const fakeRes = { + sendStatus: sinon.spy(), + status: sinon.stub().returnsThis(), + send: sinon.spy() + } + + await MeController.removeMediaProgress(fakeReq, fakeRes) + + expect(fakeRes.sendStatus.calledWith(404)).to.be.true + + // Verify media progress was NOT deleted + const existingProgress = await Database.mediaProgressModel.findByPk(mediaProgress2.id) + expect(existingProgress).to.not.be.null + expect(existingProgress.userId).to.equal(user2.id) + }) + + it('should return 404 for non-existent media progress', async () => { + const fakeReq = { + user: user1, + params: { id: 'non-existent-id' } + } + const fakeRes = { + sendStatus: sinon.spy(), + status: sinon.stub().returnsThis(), + send: sinon.spy() + } + + await MeController.removeMediaProgress(fakeReq, fakeRes) + + expect(fakeRes.sendStatus.calledWith(404)).to.be.true + }) + }) + + describe('Bookmark Operations - Authorization Checks', () => { + let user1, user2 + let library1, library2 + let libraryItem1, libraryItem2 + + beforeEach(async () => { + // Create two users with different library access + user1 = await Database.userModel.create({ + username: 'user1', + pash: 'hashed_password_1', + type: 'user', + isActive: true, + librariesAccessible: null // Access to all libraries + }) + + user2 = await Database.userModel.create({ + username: 'user2', + pash: 'hashed_password_2', + type: 'user', + isActive: true, + librariesAccessible: [] // Will be set to specific library + }) + + // Create two libraries + library1 = await Database.libraryModel.create({ name: 'Library 1', mediaType: 'book' }) + library2 = await Database.libraryModel.create({ name: 'Library 2', mediaType: 'book' }) + + // User2 only has access to library1 + user2.librariesAccessible = [library1.id] + await user2.save() + + const libraryFolder1 = await Database.libraryFolderModel.create({ path: '/test1', libraryId: library1.id }) + const libraryFolder2 = await Database.libraryFolderModel.create({ path: '/test2', libraryId: library2.id }) + + const book1 = await Database.bookModel.create({ title: 'Book 1', audioFiles: [], tags: [], narrators: [], genres: [], chapters: [] }) + const book2 = await Database.bookModel.create({ title: 'Book 2', audioFiles: [], tags: [], narrators: [], genres: [], chapters: [] }) + + libraryItem1 = await Database.libraryItemModel.create({ + libraryFiles: [], + mediaId: book1.id, + mediaType: 'book', + libraryId: library1.id, + libraryFolderId: libraryFolder1.id + }) + + libraryItem2 = await Database.libraryItemModel.create({ + libraryFiles: [], + mediaId: book2.id, + mediaType: 'book', + libraryId: library2.id, + libraryFolderId: libraryFolder2.id + }) + + // Initialize bookmarks + user1.bookmarks = [] + user2.bookmarks = [] + }) + + describe('createBookmark', () => { + it('should allow user to create bookmark for accessible library item', async () => { + const expandedItem = await Database.libraryItemModel.getExpandedById(libraryItem1.id) + + const bookmark = { libraryItemId: libraryItem1.id, time: 100, title: 'Test Bookmark', createdAt: Date.now() } + + const fakeReq = { + user: { + ...user2.toJSON(), + id: user2.id, + username: user2.username, + checkCanAccessLibraryItem: () => true, + createBookmark: sinon.stub().resolves(bookmark), + toOldJSONForBrowser: () => ({ id: user2.id, username: user2.username }) + }, + params: { id: libraryItem1.id }, + body: { time: 100, title: 'Test Bookmark' } + } + const fakeRes = { + sendStatus: sinon.spy(), + status: sinon.stub().returnsThis(), + send: sinon.spy(), + json: sinon.spy() + } + + sinon.stub(Database.libraryItemModel, 'getExpandedById').resolves(expandedItem) + + await MeController.createBookmark(fakeReq, fakeRes) + + expect(fakeRes.json.calledOnce).to.be.true + expect(fakeRes.json.calledWith(bookmark)).to.be.true + + Database.libraryItemModel.getExpandedById.restore() + }) + + it('should prevent user from creating bookmark for inaccessible library item (IDOR)', async () => { + const expandedItem = await Database.libraryItemModel.getExpandedById(libraryItem2.id) + + const fakeReq = { + user: user2, // user2 doesn't have access to library2 + params: { id: libraryItem2.id }, + body: { time: 100, title: 'Test Bookmark' } + } + const fakeRes = { + sendStatus: sinon.spy(), + status: sinon.stub().returnsThis(), + send: sinon.spy(), + json: sinon.spy() + } + + // Mock getExpandedById + sinon.stub(Database.libraryItemModel, 'getExpandedById').resolves(expandedItem) + + await MeController.createBookmark(fakeReq, fakeRes) + + expect(fakeRes.sendStatus.calledWith(403)).to.be.true + expect(fakeRes.json.called).to.be.false + + Database.libraryItemModel.getExpandedById.restore() + }) + + it('should return 404 for non-existent library item', async () => { + const fakeReq = { + user: user1, + params: { id: 'non-existent-id' }, + body: { time: 100, title: 'Test Bookmark' } + } + const fakeRes = { + sendStatus: sinon.spy(), + status: sinon.stub().returnsThis(), + send: sinon.spy(), + json: sinon.spy() + } + + // Mock getExpandedById to return null + sinon.stub(Database.libraryItemModel, 'getExpandedById').resolves(null) + + await MeController.createBookmark(fakeReq, fakeRes) + + expect(fakeRes.sendStatus.calledWith(404)).to.be.true + + Database.libraryItemModel.getExpandedById.restore() + }) + + it('should validate bookmark time parameter', async () => { + const expandedItem = await Database.libraryItemModel.getExpandedById(libraryItem1.id) + + const fakeReq = { + user: { + ...user1.toJSON(), + id: user1.id, + username: user1.username, + checkCanAccessLibraryItem: () => true + }, + params: { id: libraryItem1.id }, + body: { time: null, title: 'Test Bookmark' } // null time is invalid + } + const fakeRes = { + sendStatus: sinon.spy(), + status: sinon.stub().returnsThis(), + send: sinon.spy(), + json: sinon.spy() + } + + sinon.stub(Database.libraryItemModel, 'getExpandedById').resolves(expandedItem) + + await MeController.createBookmark(fakeReq, fakeRes) + + expect(fakeRes.status.calledWith(400)).to.be.true + expect(fakeRes.send.calledWith('Invalid time')).to.be.true + + Database.libraryItemModel.getExpandedById.restore() + }) + }) + + describe('updateBookmark', () => { + beforeEach(async () => { + // Add existing bookmark to user1 + user1.bookmarks = [{ libraryItemId: libraryItem1.id, time: 100, title: 'Original Title' }] + await user1.save() + }) + + it('should allow user to update bookmark for accessible library item', async () => { + const expandedItem = await Database.libraryItemModel.getExpandedById(libraryItem1.id) + + const bookmark = { libraryItemId: libraryItem1.id, time: 100, title: 'Updated Title' } + + const fakeReq = { + user: { + ...user1.toJSON(), + id: user1.id, + username: user1.username, + checkCanAccessLibraryItem: () => true, + updateBookmark: sinon.stub().resolves(bookmark), + toOldJSONForBrowser: () => ({ id: user1.id, username: user1.username }) + }, + params: { id: libraryItem1.id }, + body: { time: 100, title: 'Updated Title' } + } + const fakeRes = { + sendStatus: sinon.spy(), + status: sinon.stub().returnsThis(), + send: sinon.spy(), + json: sinon.spy() + } + + sinon.stub(Database.libraryItemModel, 'getExpandedById').resolves(expandedItem) + + await MeController.updateBookmark(fakeReq, fakeRes) + + expect(fakeRes.json.calledOnce).to.be.true + expect(fakeRes.json.calledWith(bookmark)).to.be.true + + Database.libraryItemModel.getExpandedById.restore() + }) + + it('should prevent user from updating bookmark for inaccessible library item (IDOR)', async () => { + const expandedItem = await Database.libraryItemModel.getExpandedById(libraryItem2.id) + + const fakeReq = { + user: user2, // user2 doesn't have access to library2 + params: { id: libraryItem2.id }, + body: { time: 100, title: 'Updated Title' } + } + const fakeRes = { + sendStatus: sinon.spy(), + status: sinon.stub().returnsThis(), + send: sinon.spy(), + json: sinon.spy() + } + + sinon.stub(Database.libraryItemModel, 'getExpandedById').resolves(expandedItem) + + await MeController.updateBookmark(fakeReq, fakeRes) + + expect(fakeRes.sendStatus.calledWith(403)).to.be.true + + Database.libraryItemModel.getExpandedById.restore() + }) + }) + + describe('removeBookmark', () => { + beforeEach(async () => { + // Add existing bookmark to user1 + user1.bookmarks = [{ libraryItemId: libraryItem1.id, time: 100, title: 'Test Bookmark' }] + await user1.save() + }) + + it('should allow user to remove bookmark for accessible library item', async () => { + const expandedItem = await Database.libraryItemModel.getExpandedById(libraryItem1.id) + + const fakeReq = { + user: { + ...user1.toJSON(), + id: user1.id, + username: user1.username, + checkCanAccessLibraryItem: () => true, + findBookmark: sinon.stub().returns({ libraryItemId: libraryItem1.id, time: 100, title: 'Test Bookmark' }), + removeBookmark: sinon.stub().resolves(true), + toOldJSONForBrowser: () => ({ id: user1.id, username: user1.username }) + }, + params: { id: libraryItem1.id, time: '100' } + } + const fakeRes = { + sendStatus: sinon.spy(), + status: sinon.stub().returnsThis(), + send: sinon.spy() + } + + sinon.stub(Database.libraryItemModel, 'getExpandedById').resolves(expandedItem) + + await MeController.removeBookmark(fakeReq, fakeRes) + + expect(fakeRes.sendStatus.calledWith(200)).to.be.true + + Database.libraryItemModel.getExpandedById.restore() + }) + + it('should prevent user from removing bookmark for inaccessible library item (IDOR)', async () => { + const expandedItem = await Database.libraryItemModel.getExpandedById(libraryItem2.id) + + const fakeReq = { + user: user2, // user2 doesn't have access to library2 + params: { id: libraryItem2.id, time: '100' } + } + const fakeRes = { + sendStatus: sinon.spy(), + status: sinon.stub().returnsThis(), + send: sinon.spy() + } + + sinon.stub(Database.libraryItemModel, 'getExpandedById').resolves(expandedItem) + + await MeController.removeBookmark(fakeReq, fakeRes) + + expect(fakeRes.sendStatus.calledWith(403)).to.be.true + + Database.libraryItemModel.getExpandedById.restore() + }) + + it('should validate time parameter is a number', async () => { + const expandedItem = await Database.libraryItemModel.getExpandedById(libraryItem1.id) + + const fakeReq = { + user: { + ...user1.toJSON(), + id: user1.id, + username: user1.username, + checkCanAccessLibraryItem: () => true + }, + params: { id: libraryItem1.id, time: 'not-a-number' } + } + const fakeRes = { + sendStatus: sinon.spy(), + status: sinon.stub().returnsThis(), + send: sinon.spy() + } + + sinon.stub(Database.libraryItemModel, 'getExpandedById').resolves(expandedItem) + + await MeController.removeBookmark(fakeReq, fakeRes) + + expect(fakeRes.status.calledWith(400)).to.be.true + expect(fakeRes.send.calledWith('Invalid time')).to.be.true + + Database.libraryItemModel.getExpandedById.restore() + }) + }) + }) + + describe('getItemListeningSessions - Authorization Check', () => { + let user1, user2 + let library1, library2 + let libraryItem1, libraryItem2 + + beforeEach(async () => { + // Create two users with different library access + user1 = await Database.userModel.create({ + username: 'user1', + pash: 'hashed_password_1', + type: 'user', + isActive: true, + librariesAccessible: null // Access to all libraries + }) + + user2 = await Database.userModel.create({ + username: 'user2', + pash: 'hashed_password_2', + type: 'user', + isActive: true, + librariesAccessible: [] // Will be set to specific library + }) + + // Create two libraries + library1 = await Database.libraryModel.create({ name: 'Library 1', mediaType: 'book' }) + library2 = await Database.libraryModel.create({ name: 'Library 2', mediaType: 'book' }) + + // User2 only has access to library1 + user2.librariesAccessible = [library1.id] + await user2.save() + + const libraryFolder1 = await Database.libraryFolderModel.create({ path: '/test1', libraryId: library1.id }) + const libraryFolder2 = await Database.libraryFolderModel.create({ path: '/test2', libraryId: library2.id }) + + const book1 = await Database.bookModel.create({ title: 'Book 1', audioFiles: [], tags: [], narrators: [], genres: [], chapters: [] }) + const book2 = await Database.bookModel.create({ title: 'Book 2', audioFiles: [], tags: [], narrators: [], genres: [], chapters: [] }) + + libraryItem1 = await Database.libraryItemModel.create({ + libraryFiles: [], + mediaId: book1.id, + mediaType: 'book', + libraryId: library1.id, + libraryFolderId: libraryFolder1.id + }) + + libraryItem2 = await Database.libraryItemModel.create({ + libraryFiles: [], + mediaId: book2.id, + mediaType: 'book', + libraryId: library2.id, + libraryFolderId: libraryFolder2.id + }) + }) + + it('should allow user to view listening sessions for accessible library item', async () => { + const expandedItem = await Database.libraryItemModel.getExpandedById(libraryItem1.id) + + // Create mock context with getUserItemListeningSessionsHelper + const mockContext = { + getUserItemListeningSessionsHelper: sinon.stub().resolves([ + { id: 'session1', timeListening: 300, startedAt: Date.now() } + ]) + } + + const fakeReq = { + user: { + ...user1.toJSON(), + id: user1.id, + username: user1.username, + checkCanAccessLibraryItem: () => true + }, + params: { libraryItemId: libraryItem1.id }, + query: {} + } + const fakeRes = { + sendStatus: sinon.spy(), + status: sinon.stub().returnsThis(), + send: sinon.spy(), + json: sinon.spy() + } + + sinon.stub(Database.libraryItemModel, 'getExpandedById').resolves(expandedItem) + sinon.stub(Database.podcastEpisodeModel, 'findByPk').resolves(null) + + await MeController.getItemListeningSessions.bind(mockContext)(fakeReq, fakeRes) + + expect(fakeRes.json.calledOnce).to.be.true + expect(fakeRes.sendStatus.called).to.be.false + + // Verify the payload structure + const payload = fakeRes.json.firstCall.args[0] + expect(payload).to.have.property('total') + expect(payload).to.have.property('sessions') + + Database.libraryItemModel.getExpandedById.restore() + Database.podcastEpisodeModel.findByPk.restore() + }) + + it('should prevent user from viewing listening sessions for inaccessible library item (IDOR)', async () => { + const expandedItem = await Database.libraryItemModel.getExpandedById(libraryItem2.id) + + const fakeReq = { + user: user2, // user2 doesn't have access to library2 + params: { libraryItemId: libraryItem2.id }, + query: {} + } + const fakeRes = { + sendStatus: sinon.spy(), + status: sinon.stub().returnsThis(), + send: sinon.spy(), + json: sinon.spy() + } + + sinon.stub(Database.libraryItemModel, 'getExpandedById').resolves(expandedItem) + sinon.stub(Database.podcastEpisodeModel, 'findByPk').resolves(null) + + await MeController.getItemListeningSessions.bind(apiRouter)(fakeReq, fakeRes) + + expect(fakeRes.sendStatus.calledWith(403)).to.be.true + expect(fakeRes.json.called).to.be.false + + Database.libraryItemModel.getExpandedById.restore() + Database.podcastEpisodeModel.findByPk.restore() + }) + + it('should return 404 for non-existent library item', async () => { + const fakeReq = { + user: user1, + params: { libraryItemId: 'non-existent-id' }, + query: {} + } + const fakeRes = { + sendStatus: sinon.spy(), + status: sinon.stub().returnsThis(), + send: sinon.spy(), + json: sinon.spy() + } + + sinon.stub(Database.libraryItemModel, 'getExpandedById').resolves(null) + + await MeController.getItemListeningSessions.bind(apiRouter)(fakeReq, fakeRes) + + expect(fakeRes.sendStatus.calledWith(404)).to.be.true + + Database.libraryItemModel.getExpandedById.restore() + }) + }) +}) From e5af2f336bc2b4571e593eec9a6379e65659cec3 Mon Sep 17 00:00:00 2001 From: Matt Andreko Date: Sun, 15 Feb 2026 22:06:20 -0500 Subject: [PATCH 026/124] Move file to correct folder... --- test/server/{ => controllers}/MeController.test.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/server/{ => controllers}/MeController.test.js (100%) diff --git a/test/server/MeController.test.js b/test/server/controllers/MeController.test.js similarity index 100% rename from test/server/MeController.test.js rename to test/server/controllers/MeController.test.js From 75eed9d09a2ff2255bf9a06667d3c1656765fa0a Mon Sep 17 00:00:00 2001 From: pavel-miniutka Date: Thu, 19 Feb 2026 10:36:28 +0300 Subject: [PATCH 027/124] Add Belarusian language option & Belarus podcast region --- client/plugins/i18n.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/plugins/i18n.js b/client/plugins/i18n.js index 83e748f25..1ab0379c7 100644 --- a/client/plugins/i18n.js +++ b/client/plugins/i18n.js @@ -6,6 +6,7 @@ const defaultCode = 'en-us' const languageCodeMap = { ar: { label: 'عربي', dateFnsLocale: 'ar' }, + be: { label: 'Беларуская', dateFnsLocale: 'be' }, bg: { label: 'Български', dateFnsLocale: 'bg' }, bn: { label: 'বাংলা', dateFnsLocale: 'bn' }, ca: { label: 'Català', dateFnsLocale: 'ca' }, @@ -48,6 +49,7 @@ const podcastSearchRegionMap = { au: { label: 'Australia' }, br: { label: 'Brasil' }, be: { label: 'België / Belgique / Belgien' }, + by: { label: 'Беларусь' }, cz: { label: 'Česko' }, dk: { label: 'Danmark' }, de: { label: 'Deutschland' }, From 05d9ab81f98fffd8becbfca41aca22bf0e05dcf3 Mon Sep 17 00:00:00 2001 From: Kevin Gatera Date: Thu, 19 Feb 2026 20:11:24 -0500 Subject: [PATCH 028/124] Improve API cache invalidation for high-churn models --- server/managers/ApiCacheManager.js | 41 +++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/server/managers/ApiCacheManager.js b/server/managers/ApiCacheManager.js index 2d8eece85..8a67e4898 100644 --- a/server/managers/ApiCacheManager.js +++ b/server/managers/ApiCacheManager.js @@ -5,6 +5,9 @@ const Database = require('../Database') class ApiCacheManager { defaultCacheOptions = { max: 1000, maxSize: 10 * 1000 * 1000, sizeCalculation: (item) => item.body.length + JSON.stringify(item.headers).length } defaultTtlOptions = { ttl: 30 * 60 * 1000 } + highChurnModels = new Set(['session', 'mediaProgress', 'playbackSession', 'device']) + modelsInvalidatingPersonalized = new Set(['mediaProgress']) + modelsInvalidatingMe = new Set(['session', 'mediaProgress', 'playbackSession', 'device']) constructor(cache = new LRUCache(this.defaultCacheOptions), ttlOptions = this.defaultTtlOptions) { this.cache = cache @@ -16,8 +19,44 @@ class ApiCacheManager { hooks.forEach((hook) => database.sequelize.addHook(hook, (model) => this.clear(model, hook))) } + getModelName(model) { + if (typeof model?.name === 'string') return model.name + if (typeof model?.model?.name === 'string') return model.model.name + if (typeof model?.constructor?.name === 'string' && model.constructor.name !== 'Object') return model.constructor.name + return 'unknown' + } + + clearByUrlPattern(urlPattern) { + let removed = 0 + for (const key of this.cache.keys()) { + try { + const parsed = JSON.parse(key) + if (typeof parsed?.url === 'string' && urlPattern.test(parsed.url)) { + if (this.cache.delete(key)) removed++ + } + } catch { + if (this.cache.delete(key)) removed++ + } + } + return removed + } + + clearUserProgressSlices(modelName, hook) { + const removedPersonalized = this.modelsInvalidatingPersonalized.has(modelName) ? this.clearByUrlPattern(/^\/libraries\/[^/]+\/personalized/) : 0 + const removedMe = this.modelsInvalidatingMe.has(modelName) ? this.clearByUrlPattern(/^\/me(\/|\?|$)/) : 0 + Logger.debug( + `[ApiCacheManager] ${modelName}.${hook}: cleared user-progress cache slices (personalized=${removedPersonalized}, me=${removedMe})` + ) + } + clear(model, hook) { - Logger.debug(`[ApiCacheManager] ${model.constructor.name}.${hook}: Clearing cache`) + const modelName = this.getModelName(model) + if (this.highChurnModels.has(modelName)) { + this.clearUserProgressSlices(modelName, hook) + return + } + + Logger.debug(`[ApiCacheManager] ${modelName}.${hook}: Clearing cache`) this.cache.clear() } From d2915e689fb6644fea7af3b216d1cfe7d8c24f0f Mon Sep 17 00:00:00 2001 From: Kevin Gatera Date: Thu, 19 Feb 2026 20:11:38 -0500 Subject: [PATCH 029/124] Speed up personalized shelves and reduce search payload size --- server/models/LibraryItem.js | 202 +++++++++++------- .../utils/queries/libraryItemsBookFilters.js | 113 +++++++--- .../queries/libraryItemsPodcastFilters.js | 4 +- 3 files changed, 211 insertions(+), 108 deletions(-) diff --git a/server/models/LibraryItem.js b/server/models/LibraryItem.js index 16a521615..b33fa585c 100644 --- a/server/models/LibraryItem.js +++ b/server/models/LibraryItem.js @@ -340,6 +340,15 @@ class LibraryItem extends Model { const shelves = [] + const timed = async (loader) => { + const start = Date.now() + const payload = await loader() + return { + payload, + elapsedSeconds: ((Date.now() - start) / 1000).toFixed(2) + } + } + // "Continue Listening" shelf const itemsInProgressPayload = await libraryFilters.getMediaItemsInProgress(library, user, include, limit, false) if (itemsInProgressPayload.items.length) { @@ -371,11 +380,18 @@ class LibraryItem extends Model { } Logger.debug(`Loaded ${itemsInProgressPayload.items.length} of ${itemsInProgressPayload.count} items for "Continue Listening/Reading" in ${((Date.now() - fullStart) / 1000).toFixed(2)}s`) - let start = Date.now() if (library.isBook) { - start = Date.now() + const [continueSeriesResult, mostRecentResult, seriesMostRecentResult, discoverResult, mediaFinishedResult, newestAuthorsResult] = await Promise.all([ + timed(() => libraryFilters.getLibraryItemsContinueSeries(library, user, include, limit)), + timed(() => libraryFilters.getLibraryItemsMostRecentlyAdded(library, user, include, limit)), + timed(() => libraryFilters.getSeriesMostRecentlyAdded(library, user, include, 5)), + timed(() => libraryFilters.getLibraryItemsToDiscover(library, user, include, limit)), + timed(() => libraryFilters.getMediaFinished(library, user, include, limit)), + timed(() => libraryFilters.getNewestAuthors(library, user, limit)) + ]) + + const continueSeriesPayload = continueSeriesResult.payload // "Continue Series" shelf - const continueSeriesPayload = await libraryFilters.getLibraryItemsContinueSeries(library, user, include, limit) if (continueSeriesPayload.libraryItems.length) { shelves.push({ id: 'continue-series', @@ -386,42 +402,24 @@ class LibraryItem extends Model { total: continueSeriesPayload.count }) } - Logger.debug(`Loaded ${continueSeriesPayload.libraryItems.length} of ${continueSeriesPayload.count} items for "Continue Series" in ${((Date.now() - start) / 1000).toFixed(2)}s`) - } else if (library.isPodcast) { - // "Newest Episodes" shelf - const newestEpisodesPayload = await libraryFilters.getNewestPodcastEpisodes(library, user, limit) - if (newestEpisodesPayload.libraryItems.length) { + Logger.debug(`Loaded ${continueSeriesPayload.libraryItems.length} of ${continueSeriesPayload.count} items for "Continue Series" in ${continueSeriesResult.elapsedSeconds}s`) + + const mostRecentPayload = mostRecentResult.payload + // "Recently Added" shelf + if (mostRecentPayload.libraryItems.length) { shelves.push({ - id: 'newest-episodes', - label: 'Newest Episodes', - labelStringKey: 'LabelNewestEpisodes', - type: 'episode', - entities: newestEpisodesPayload.libraryItems, - total: newestEpisodesPayload.count + id: 'recently-added', + label: 'Recently Added', + labelStringKey: 'LabelRecentlyAdded', + type: library.mediaType, + entities: mostRecentPayload.libraryItems, + total: mostRecentPayload.count }) } - Logger.debug(`Loaded ${newestEpisodesPayload.libraryItems.length} of ${newestEpisodesPayload.count} episodes for "Newest Episodes" in ${((Date.now() - start) / 1000).toFixed(2)}s`) - } + Logger.debug(`Loaded ${mostRecentPayload.libraryItems.length} of ${mostRecentPayload.count} items for "Recently Added" in ${mostRecentResult.elapsedSeconds}s`) - start = Date.now() - // "Recently Added" shelf - const mostRecentPayload = await libraryFilters.getLibraryItemsMostRecentlyAdded(library, user, include, limit) - if (mostRecentPayload.libraryItems.length) { - shelves.push({ - id: 'recently-added', - label: 'Recently Added', - labelStringKey: 'LabelRecentlyAdded', - type: library.mediaType, - entities: mostRecentPayload.libraryItems, - total: mostRecentPayload.count - }) - } - Logger.debug(`Loaded ${mostRecentPayload.libraryItems.length} of ${mostRecentPayload.count} items for "Recently Added" in ${((Date.now() - start) / 1000).toFixed(2)}s`) - - if (library.isBook) { - start = Date.now() + const seriesMostRecentPayload = seriesMostRecentResult.payload // "Recent Series" shelf - const seriesMostRecentPayload = await libraryFilters.getSeriesMostRecentlyAdded(library, user, include, 5) if (seriesMostRecentPayload.series.length) { shelves.push({ id: 'recent-series', @@ -432,11 +430,10 @@ class LibraryItem extends Model { total: seriesMostRecentPayload.count }) } - Logger.debug(`Loaded ${seriesMostRecentPayload.series.length} of ${seriesMostRecentPayload.count} series for "Recent Series" in ${((Date.now() - start) / 1000).toFixed(2)}s`) + Logger.debug(`Loaded ${seriesMostRecentPayload.series.length} of ${seriesMostRecentPayload.count} series for "Recent Series" in ${seriesMostRecentResult.elapsedSeconds}s`) - start = Date.now() + const discoverLibraryItemsPayload = discoverResult.payload // "Discover" shelf - const discoverLibraryItemsPayload = await libraryFilters.getLibraryItemsToDiscover(library, user, include, limit) if (discoverLibraryItemsPayload.libraryItems.length) { shelves.push({ id: 'discover', @@ -447,45 +444,41 @@ class LibraryItem extends Model { total: discoverLibraryItemsPayload.count }) } - Logger.debug(`Loaded ${discoverLibraryItemsPayload.libraryItems.length} of ${discoverLibraryItemsPayload.count} items for "Discover" in ${((Date.now() - start) / 1000).toFixed(2)}s`) - } + Logger.debug(`Loaded ${discoverLibraryItemsPayload.libraryItems.length} of ${discoverLibraryItemsPayload.count} items for "Discover" in ${discoverResult.elapsedSeconds}s`) - start = Date.now() - // "Listen Again" shelf - const mediaFinishedPayload = await libraryFilters.getMediaFinished(library, user, include, limit) - if (mediaFinishedPayload.items.length) { - const ebookOnlyItemsInProgress = mediaFinishedPayload.items.filter((li) => li.media.ebookFormat && !li.media.numTracks) - const audioItemsInProgress = mediaFinishedPayload.items.filter((li) => li.media.numTracks || li.mediaType === 'podcast') + const mediaFinishedPayload = mediaFinishedResult.payload + // "Listen Again" shelf + if (mediaFinishedPayload.items.length) { + const ebookOnlyItemsInProgress = mediaFinishedPayload.items.filter((li) => li.media.ebookFormat && !li.media.numTracks) + const audioItemsInProgress = mediaFinishedPayload.items.filter((li) => li.media.numTracks || li.mediaType === 'podcast') - if (audioItemsInProgress.length) { - shelves.push({ - id: 'listen-again', - label: 'Listen Again', - labelStringKey: 'LabelListenAgain', - type: library.isPodcast ? 'episode' : 'book', - entities: audioItemsInProgress, - total: mediaFinishedPayload.count - }) + if (audioItemsInProgress.length) { + shelves.push({ + id: 'listen-again', + label: 'Listen Again', + labelStringKey: 'LabelListenAgain', + type: library.isPodcast ? 'episode' : 'book', + entities: audioItemsInProgress, + total: mediaFinishedPayload.count + }) + } + + if (ebookOnlyItemsInProgress.length) { + // "Read Again" shelf + shelves.push({ + id: 'read-again', + label: 'Read Again', + labelStringKey: 'LabelReadAgain', + type: 'book', + entities: ebookOnlyItemsInProgress, + total: mediaFinishedPayload.count + }) + } } + Logger.debug(`Loaded ${mediaFinishedPayload.items.length} of ${mediaFinishedPayload.count} items for "Listen/Read Again" in ${mediaFinishedResult.elapsedSeconds}s`) - // "Read Again" shelf - if (ebookOnlyItemsInProgress.length) { - shelves.push({ - id: 'read-again', - label: 'Read Again', - labelStringKey: 'LabelReadAgain', - type: 'book', - entities: ebookOnlyItemsInProgress, - total: mediaFinishedPayload.count - }) - } - } - Logger.debug(`Loaded ${mediaFinishedPayload.items.length} of ${mediaFinishedPayload.count} items for "Listen/Read Again" in ${((Date.now() - start) / 1000).toFixed(2)}s`) - - if (library.isBook) { - start = Date.now() + const newestAuthorsPayload = newestAuthorsResult.payload // "Newest Authors" shelf - const newestAuthorsPayload = await libraryFilters.getNewestAuthors(library, user, limit) if (newestAuthorsPayload.authors.length) { shelves.push({ id: 'newest-authors', @@ -496,7 +489,72 @@ class LibraryItem extends Model { total: newestAuthorsPayload.count }) } - Logger.debug(`Loaded ${newestAuthorsPayload.authors.length} of ${newestAuthorsPayload.count} authors for "Newest Authors" in ${((Date.now() - start) / 1000).toFixed(2)}s`) + Logger.debug(`Loaded ${newestAuthorsPayload.authors.length} of ${newestAuthorsPayload.count} authors for "Newest Authors" in ${newestAuthorsResult.elapsedSeconds}s`) + } else if (library.isPodcast) { + const [newestEpisodesResult, mostRecentResult, mediaFinishedResult] = await Promise.all([ + timed(() => libraryFilters.getNewestPodcastEpisodes(library, user, limit)), + timed(() => libraryFilters.getLibraryItemsMostRecentlyAdded(library, user, include, limit)), + timed(() => libraryFilters.getMediaFinished(library, user, include, limit)) + ]) + + const newestEpisodesPayload = newestEpisodesResult.payload + // "Newest Episodes" shelf + if (newestEpisodesPayload.libraryItems.length) { + shelves.push({ + id: 'newest-episodes', + label: 'Newest Episodes', + labelStringKey: 'LabelNewestEpisodes', + type: 'episode', + entities: newestEpisodesPayload.libraryItems, + total: newestEpisodesPayload.count + }) + } + Logger.debug(`Loaded ${newestEpisodesPayload.libraryItems.length} of ${newestEpisodesPayload.count} episodes for "Newest Episodes" in ${newestEpisodesResult.elapsedSeconds}s`) + + const mostRecentPayload = mostRecentResult.payload + // "Recently Added" shelf + if (mostRecentPayload.libraryItems.length) { + shelves.push({ + id: 'recently-added', + label: 'Recently Added', + labelStringKey: 'LabelRecentlyAdded', + type: library.mediaType, + entities: mostRecentPayload.libraryItems, + total: mostRecentPayload.count + }) + } + Logger.debug(`Loaded ${mostRecentPayload.libraryItems.length} of ${mostRecentPayload.count} items for "Recently Added" in ${mostRecentResult.elapsedSeconds}s`) + + const mediaFinishedPayload = mediaFinishedResult.payload + // "Listen Again" shelf + if (mediaFinishedPayload.items.length) { + const ebookOnlyItemsInProgress = mediaFinishedPayload.items.filter((li) => li.media.ebookFormat && !li.media.numTracks) + const audioItemsInProgress = mediaFinishedPayload.items.filter((li) => li.media.numTracks || li.mediaType === 'podcast') + + if (audioItemsInProgress.length) { + shelves.push({ + id: 'listen-again', + label: 'Listen Again', + labelStringKey: 'LabelListenAgain', + type: 'episode', + entities: audioItemsInProgress, + total: mediaFinishedPayload.count + }) + } + + if (ebookOnlyItemsInProgress.length) { + // "Read Again" shelf + shelves.push({ + id: 'read-again', + label: 'Read Again', + labelStringKey: 'LabelReadAgain', + type: 'book', + entities: ebookOnlyItemsInProgress, + total: mediaFinishedPayload.count + }) + } + } + Logger.debug(`Loaded ${mediaFinishedPayload.items.length} of ${mediaFinishedPayload.count} items for "Listen/Read Again" in ${mediaFinishedResult.elapsedSeconds}s`) } Logger.debug(`Loaded ${shelves.length} personalized shelves in ${((Date.now() - fullStart) / 1000).toFixed(2)}s`) diff --git a/server/utils/queries/libraryItemsBookFilters.js b/server/utils/queries/libraryItemsBookFilters.js index 7ae1dc866..407b18a94 100644 --- a/server/utils/queries/libraryItemsBookFilters.js +++ b/server/utils/queries/libraryItemsBookFilters.js @@ -888,28 +888,80 @@ module.exports = { }) } - // Step 2: Get books not started and not in a series OR is the first book of a series not started (ordered randomly) - const { rows: books, count } = await Database.bookModel.findAndCountAll({ - where: [ - { - '$mediaProgresses.isFinished$': { - [Sequelize.Op.or]: [null, 0] - }, - '$mediaProgresses.currentTime$': { - [Sequelize.Op.or]: [null, 0] - }, - [Sequelize.Op.or]: [ - Sequelize.where(Sequelize.literal(`(SELECT COUNT(*) FROM bookSeries bs where bs.bookId = book.id)`), 0), - { - id: { - [Sequelize.Op.in]: booksFromSeriesToInclude - } + const discoverWhere = [ + { + [Sequelize.Op.and]: [ + Sequelize.where( + Sequelize.literal( + `(SELECT COUNT(*) FROM mediaProgresses mp WHERE mp.mediaItemId = book.id AND mp.userId = :userId AND (mp.isFinished = 1 OR mp.currentTime > 0))` + ), + 0 + ) + ], + [Sequelize.Op.or]: [ + Sequelize.where(Sequelize.literal(`(SELECT COUNT(*) FROM bookSeries bs where bs.bookId = book.id)`), 0), + { + id: { + [Sequelize.Op.in]: booksFromSeriesToInclude } - ] - }, - ...userPermissionBookWhere.bookWhere - ], - replacements: userPermissionBookWhere.replacements, + } + ] + }, + ...userPermissionBookWhere.bookWhere + ] + + const baseDiscoverInclude = [ + { + model: Database.libraryItemModel, + where: { + libraryId + } + } + ] + + // Step 2a: Count with lightweight includes only + const count = await Database.bookModel.count({ + where: discoverWhere, + replacements: { + userId: user.id, + ...userPermissionBookWhere.replacements + }, + include: baseDiscoverInclude, + distinct: true, + col: 'id', + subQuery: false + }) + + // Step 2b: Select random IDs with lightweight includes only + const randomSelection = await Database.bookModel.findAll({ + attributes: ['id'], + where: discoverWhere, + replacements: { + userId: user.id, + ...userPermissionBookWhere.replacements + }, + include: baseDiscoverInclude, + subQuery: false, + distinct: true, + limit, + order: Database.sequelize.random() + }) + + const selectedIds = randomSelection.map((b) => b.id).filter(Boolean) + if (!selectedIds.length) { + return { + libraryItems: [], + count + } + } + + // Step 2c: Hydrate selected IDs with full metadata for API response + const books = await Database.bookModel.findAll({ + where: { + id: { + [Sequelize.Op.in]: selectedIds + } + }, include: [ { model: Database.libraryItemModel, @@ -918,13 +970,6 @@ module.exports = { }, include: libraryItemIncludes }, - { - model: Database.mediaProgressModel, - where: { - userId: user.id - }, - required: false - }, { model: Database.bookAuthorModel, attributes: ['authorId'], @@ -942,14 +987,14 @@ module.exports = { separate: true } ], - subQuery: false, - distinct: true, - limit, - order: Database.sequelize.random() + subQuery: false }) + const booksById = new Map(books.map((b) => [b.id, b])) + const orderedBooks = selectedIds.map((id) => booksById.get(id)).filter(Boolean) + // Step 3: Map books to library items - const libraryItems = books.map((bookExpanded) => { + const libraryItems = orderedBooks.map((bookExpanded) => { const libraryItem = bookExpanded.libraryItem const book = bookExpanded delete book.libraryItem @@ -1122,7 +1167,7 @@ module.exports = { libraryItem.media = book itemMatches.push({ - libraryItem: libraryItem.toOldJSONExpanded() + libraryItem: libraryItem.toOldJSONMinified() }) } diff --git a/server/utils/queries/libraryItemsPodcastFilters.js b/server/utils/queries/libraryItemsPodcastFilters.js index 8bb5dc110..05a7b083f 100644 --- a/server/utils/queries/libraryItemsPodcastFilters.js +++ b/server/utils/queries/libraryItemsPodcastFilters.js @@ -410,7 +410,7 @@ module.exports = { libraryItem.media = podcast libraryItem.media.podcastEpisodes = [] itemMatches.push({ - libraryItem: libraryItem.toOldJSONExpanded() + libraryItem: libraryItem.toOldJSONMinified() }) } @@ -444,7 +444,7 @@ module.exports = { libraryItem.media = episode.podcast libraryItem.media.podcastEpisodes = [] const oldPodcastEpisodeJson = episode.toOldJSONExpanded(libraryItem.id) - const libraryItemJson = libraryItem.toOldJSONExpanded() + const libraryItemJson = libraryItem.toOldJSONMinified() libraryItemJson.recentEpisode = oldPodcastEpisodeJson episodeMatches.push({ libraryItem: libraryItemJson From f1a2e56054ab2c9e9ee9c518ee84b4b607ca5248 Mon Sep 17 00:00:00 2001 From: Kevin Gatera Date: Thu, 19 Feb 2026 20:11:49 -0500 Subject: [PATCH 030/124] Add database indexes for discover query performance --- .../v2.32.2-add-discover-query-indexes.js | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 server/migrations/v2.32.2-add-discover-query-indexes.js diff --git a/server/migrations/v2.32.2-add-discover-query-indexes.js b/server/migrations/v2.32.2-add-discover-query-indexes.js new file mode 100644 index 000000000..26b12383d --- /dev/null +++ b/server/migrations/v2.32.2-add-discover-query-indexes.js @@ -0,0 +1,74 @@ +/** + * @typedef MigrationContext + * @property {import('sequelize').QueryInterface} queryInterface + * @property {import('../Logger')} logger + * + * @typedef MigrationOptions + * @property {MigrationContext} context + */ + +const migrationVersion = '2.32.2' +const migrationName = `${migrationVersion}-add-discover-query-indexes` +const loggerPrefix = `[${migrationVersion} migration]` + +const indexes = [ + { + table: 'mediaProgresses', + name: 'media_progress_user_item_finished_time', + fields: ['userId', 'mediaItemId', 'isFinished', 'currentTime'] + }, + { + table: 'bookSeries', + name: 'book_series_series_book', + fields: ['seriesId', 'bookId'] + } +] + +async function up({ context: { queryInterface, logger } }) { + logger.info(`${loggerPrefix} UPGRADE BEGIN: ${migrationName}`) + + for (const index of indexes) { + await addIndexIfMissing(queryInterface, logger, index) + } + + logger.info(`${loggerPrefix} UPGRADE END: ${migrationName}`) +} + +async function down({ context: { queryInterface, logger } }) { + logger.info(`${loggerPrefix} DOWNGRADE BEGIN: ${migrationName}`) + + for (const index of indexes) { + await removeIndexIfExists(queryInterface, logger, index) + } + + logger.info(`${loggerPrefix} DOWNGRADE END: ${migrationName}`) +} + +async function addIndexIfMissing(queryInterface, logger, index) { + const existing = await queryInterface.showIndex(index.table) + if (existing.some((i) => i.name === index.name)) { + logger.info(`${loggerPrefix} index ${index.name} already exists on ${index.table}`) + return + } + + logger.info(`${loggerPrefix} adding index ${index.name} on ${index.table}(${index.fields.join(', ')})`) + await queryInterface.addIndex(index.table, { + name: index.name, + fields: index.fields + }) + logger.info(`${loggerPrefix} added index ${index.name}`) +} + +async function removeIndexIfExists(queryInterface, logger, index) { + const existing = await queryInterface.showIndex(index.table) + if (!existing.some((i) => i.name === index.name)) { + logger.info(`${loggerPrefix} index ${index.name} does not exist on ${index.table}`) + return + } + + logger.info(`${loggerPrefix} removing index ${index.name}`) + await queryInterface.removeIndex(index.table, index.name) + logger.info(`${loggerPrefix} removed index ${index.name}`) +} + +module.exports = { up, down } From ee6016f70e33cd4f7782c1966aca906aaa4b47ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Bel=C3=A1k?= Date: Sat, 21 Feb 2026 09:11:50 +0100 Subject: [PATCH 031/124] Add Slovak (sk) language to the language selector The Slovak translation file (client/strings/sk.json) already exists with a complete translation but was missing from the languageCodeMap in i18n.js, making it inaccessible from the language dropdown. Also adds Slovakia to the podcast search region map. Co-Authored-By: Claude Opus 4.6 --- client/plugins/i18n.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/plugins/i18n.js b/client/plugins/i18n.js index 83e748f25..5a672ba06 100644 --- a/client/plugins/i18n.js +++ b/client/plugins/i18n.js @@ -28,6 +28,7 @@ const languageCodeMap = { pl: { label: 'Polski', dateFnsLocale: 'pl' }, 'pt-br': { label: 'Português (Brasil)', dateFnsLocale: 'ptBR' }, ru: { label: 'Русский', dateFnsLocale: 'ru' }, + sk: { label: 'Slovenčina', dateFnsLocale: 'sk' }, sl: { label: 'Slovenščina', dateFnsLocale: 'sl' }, sv: { label: 'Svenska', dateFnsLocale: 'sv' }, tr: { label: 'Türkçe', dateFnsLocale: 'tr' }, @@ -67,6 +68,7 @@ const podcastSearchRegionMap = { pt: { label: 'Portugal' }, ru: { label: 'Россия' }, ch: { label: 'Schweiz / Suisse / Svizzera' }, + sk: { label: 'Slovensko' }, se: { label: 'Sverige' }, vn: { label: 'Việt Nam' }, ua: { label: 'Україна' }, From 6e0da3bf7a7b81893dbcca542519cf594d7d3232 Mon Sep 17 00:00:00 2001 From: advplyr Date: Sat, 21 Feb 2026 16:00:38 -0600 Subject: [PATCH 032/124] Fix updating author name merging with same name authors in a different library #4628 --- server/controllers/AuthorController.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server/controllers/AuthorController.js b/server/controllers/AuthorController.js index 50eeda31a..82ed3e50a 100644 --- a/server/controllers/AuthorController.js +++ b/server/controllers/AuthorController.js @@ -113,7 +113,7 @@ class AuthorController { payload.lastFirst = Database.authorModel.getLastFirst(payload.name) } - // Check if author name matches another author and merge the authors + // Check if author name matches another author in the same library and merge the authors let existingAuthor = null if (authorNameUpdate) { existingAuthor = await Database.authorModel.findOne({ @@ -121,7 +121,8 @@ class AuthorController { id: { [sequelize.Op.not]: req.author.id }, - name: payload.name + name: payload.name, + libraryId: req.author.libraryId } }) } From c0319ebbac6c5ba6f0473453b7ba0528036e2316 Mon Sep 17 00:00:00 2001 From: Kevin Gatera Date: Mon, 23 Feb 2026 19:06:33 -0500 Subject: [PATCH 033/124] Adjust discover/search changes for API compatibility --- .../utils/queries/libraryItemsBookFilters.js | 33 +++++++++---------- .../queries/libraryItemsPodcastFilters.js | 4 +-- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/server/utils/queries/libraryItemsBookFilters.js b/server/utils/queries/libraryItemsBookFilters.js index 407b18a94..fbe0c4f0d 100644 --- a/server/utils/queries/libraryItemsBookFilters.js +++ b/server/utils/queries/libraryItemsBookFilters.js @@ -890,14 +890,12 @@ module.exports = { const discoverWhere = [ { - [Sequelize.Op.and]: [ - Sequelize.where( - Sequelize.literal( - `(SELECT COUNT(*) FROM mediaProgresses mp WHERE mp.mediaItemId = book.id AND mp.userId = :userId AND (mp.isFinished = 1 OR mp.currentTime > 0))` - ), - 0 - ) - ], + '$mediaProgresses.isFinished$': { + [Sequelize.Op.or]: [null, 0] + }, + '$mediaProgresses.currentTime$': { + [Sequelize.Op.or]: [null, 0] + }, [Sequelize.Op.or]: [ Sequelize.where(Sequelize.literal(`(SELECT COUNT(*) FROM bookSeries bs where bs.bookId = book.id)`), 0), { @@ -916,16 +914,20 @@ module.exports = { where: { libraryId } + }, + { + model: Database.mediaProgressModel, + where: { + userId: user.id + }, + required: false } ] // Step 2a: Count with lightweight includes only const count = await Database.bookModel.count({ where: discoverWhere, - replacements: { - userId: user.id, - ...userPermissionBookWhere.replacements - }, + replacements: userPermissionBookWhere.replacements, include: baseDiscoverInclude, distinct: true, col: 'id', @@ -936,10 +938,7 @@ module.exports = { const randomSelection = await Database.bookModel.findAll({ attributes: ['id'], where: discoverWhere, - replacements: { - userId: user.id, - ...userPermissionBookWhere.replacements - }, + replacements: userPermissionBookWhere.replacements, include: baseDiscoverInclude, subQuery: false, distinct: true, @@ -1167,7 +1166,7 @@ module.exports = { libraryItem.media = book itemMatches.push({ - libraryItem: libraryItem.toOldJSONMinified() + libraryItem: libraryItem.toOldJSONExpanded() }) } diff --git a/server/utils/queries/libraryItemsPodcastFilters.js b/server/utils/queries/libraryItemsPodcastFilters.js index 05a7b083f..8bb5dc110 100644 --- a/server/utils/queries/libraryItemsPodcastFilters.js +++ b/server/utils/queries/libraryItemsPodcastFilters.js @@ -410,7 +410,7 @@ module.exports = { libraryItem.media = podcast libraryItem.media.podcastEpisodes = [] itemMatches.push({ - libraryItem: libraryItem.toOldJSONMinified() + libraryItem: libraryItem.toOldJSONExpanded() }) } @@ -444,7 +444,7 @@ module.exports = { libraryItem.media = episode.podcast libraryItem.media.podcastEpisodes = [] const oldPodcastEpisodeJson = episode.toOldJSONExpanded(libraryItem.id) - const libraryItemJson = libraryItem.toOldJSONMinified() + const libraryItemJson = libraryItem.toOldJSONExpanded() libraryItemJson.recentEpisode = oldPodcastEpisodeJson episodeMatches.push({ libraryItem: libraryItemJson From a9e12657f572d4ac1ef9b27d09ae1137f6f9f7c0 Mon Sep 17 00:00:00 2001 From: meek2100 Date: Thu, 26 Feb 2026 14:29:28 -0800 Subject: [PATCH 034/124] Add autocomplete attributes to login and setup fields for password manager support --- client/components/ui/TextInput.vue | 5 +++-- client/components/ui/TextInputWithLabel.vue | 5 +++-- client/pages/login.vue | 10 +++++----- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/client/components/ui/TextInput.vue b/client/components/ui/TextInput.vue index dd0eaa738..cd3bc6d2d 100644 --- a/client/components/ui/TextInput.vue +++ b/client/components/ui/TextInput.vue @@ -1,6 +1,6 @@ @@ -26,7 +26,8 @@ export default { disabled: Boolean, inputClass: String, showCopy: Boolean, - trimWhitespace: Boolean + trimWhitespace: Boolean, + autocomplete: String }, data() { return {} diff --git a/client/pages/login.vue b/client/pages/login.vue index 8e5cde098..cec150e71 100644 --- a/client/pages/login.vue +++ b/client/pages/login.vue @@ -17,9 +17,9 @@

Create Root User

- - - + + +

Directory Paths

@@ -51,10 +51,10 @@ - + - +
{{ processing ? 'Checking...' : $strings.ButtonSubmit }}
From 6d3404272c2308464bd00aa9668afdc529ac296e Mon Sep 17 00:00:00 2001 From: advplyr Date: Sat, 28 Feb 2026 11:32:28 -0600 Subject: [PATCH 035/124] Fix Match click to use current label string --- client/components/modals/item/tabs/Match.vue | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/client/components/modals/item/tabs/Match.vue b/client/components/modals/item/tabs/Match.vue index 4b92f6cd8..a1fc41091 100644 --- a/client/components/modals/item/tabs/Match.vue +++ b/client/components/modals/item/tabs/Match.vue @@ -78,7 +78,7 @@ @@ -87,7 +87,7 @@

- {{ $strings.LabelCurrently }} {{ mediaMetadata.narratorName }} + {{ $strings.LabelCurrently }} {{ mediaMetadata.narratorName }}

@@ -96,7 +96,7 @@ @@ -105,7 +105,7 @@

- {{ $strings.LabelCurrently }} {{ mediaMetadata.publisher }} + {{ $strings.LabelCurrently }} {{ mediaMetadata.publisher }}

@@ -114,7 +114,7 @@

- {{ $strings.LabelCurrently }} {{ mediaMetadata.publishedYear }} + {{ $strings.LabelCurrently }} {{ mediaMetadata.publishedYear }}

From e6d49a2d53c90350a31cd418302a5c22d279f9b5 Mon Sep 17 00:00:00 2001 From: advplyr Date: Wed, 4 Mar 2026 16:50:36 -0600 Subject: [PATCH 036/124] Fix home page check current user for hide from continue listening --- client/components/app/BookShelfCategorized.vue | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/components/app/BookShelfCategorized.vue b/client/components/app/BookShelfCategorized.vue index 4bf8cfbbf..a8e8ae469 100644 --- a/client/components/app/BookShelfCategorized.vue +++ b/client/components/app/BookShelfCategorized.vue @@ -300,6 +300,8 @@ export default { }) }, userUpdated(user) { + if (user.id !== this.$store.state.user.user.id) return + if (user.seriesHideFromContinueListening && user.seriesHideFromContinueListening.length) { this.removeAllSeriesFromContinueSeries(user.seriesHideFromContinueListening) } From 2f2d026b06730aa2233f9aa4a98ccd40ef78a126 Mon Sep 17 00:00:00 2001 From: advplyr Date: Sun, 8 Mar 2026 17:25:52 -0500 Subject: [PATCH 037/124] Auto format --- test/server/controllers/MeController.test.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/server/controllers/MeController.test.js b/test/server/controllers/MeController.test.js index 2f6b502db..3cc5496d9 100644 --- a/test/server/controllers/MeController.test.js +++ b/test/server/controllers/MeController.test.js @@ -547,9 +547,7 @@ describe('MeController - IDOR Security Tests', () => { // Create mock context with getUserItemListeningSessionsHelper const mockContext = { - getUserItemListeningSessionsHelper: sinon.stub().resolves([ - { id: 'session1', timeListening: 300, startedAt: Date.now() } - ]) + getUserItemListeningSessionsHelper: sinon.stub().resolves([{ id: 'session1', timeListening: 300, startedAt: Date.now() }]) } const fakeReq = { From d0ba455ed64d372e2fc275c9c90ed3af92f3be52 Mon Sep 17 00:00:00 2001 From: Luis Landeiro Date: Tue, 3 Feb 2026 20:12:10 +0100 Subject: [PATCH 038/124] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/pt_BR/ --- client/strings/pt-br.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/strings/pt-br.json b/client/strings/pt-br.json index c6c9781cf..d2189698c 100644 --- a/client/strings/pt-br.json +++ b/client/strings/pt-br.json @@ -436,8 +436,8 @@ "LabelLibraryFilterSublistEmpty": "Sem {0}", "LabelLibraryItem": "Item da Biblioteca", "LabelLibraryName": "Nome da Biblioteca", - "LabelLibrarySortByProgress": "Última Atualização", - "LabelLibrarySortByProgressFinished": "Concluído", + "LabelLibrarySortByProgress": "Progresso: Ultima Atualização", + "LabelLibrarySortByProgressFinished": "Progresso: Terminado", "LabelLibrarySortByProgressStarted": "Progresso: Iniciado", "LabelLimit": "Limite", "LabelLineSpacing": "Espaçamento entre linhas", From cd8640f00ed2da178199b68ee17be7fb46f95188 Mon Sep 17 00:00:00 2001 From: Yusuke Sakai Date: Thu, 5 Feb 2026 14:59:07 +0100 Subject: [PATCH 039/124] Translated using Weblate (Japanese) Currently translated at 23.1% (269 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ja/ --- client/strings/ja.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/strings/ja.json b/client/strings/ja.json index 28f6de6a6..0621614f4 100644 --- a/client/strings/ja.json +++ b/client/strings/ja.json @@ -214,7 +214,9 @@ "LabelEnd": "終了", "LabelEndOfChapter": "チャプターの最後", "LabelEpisode": "エピソード", - "LabelExplicit": "", + "LabelEpisodes": "エピソード", + "LabelEpisodic": "エピソード", + "LabelExplicit": "露骨な表現", "LabelFeedURL": "Feed URL", "LabelFile": "ファイル", "LabelFileBirthtime": "ファイル作成日時", From 31120ad1110a9a610ef0f45372844fef68c5a685 Mon Sep 17 00:00:00 2001 From: ugyes Date: Sat, 14 Feb 2026 17:45:24 +0100 Subject: [PATCH 040/124] Translated using Weblate (Hungarian) Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hu/ --- client/strings/hu.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/strings/hu.json b/client/strings/hu.json index 1c177a09c..90f58343a 100644 --- a/client/strings/hu.json +++ b/client/strings/hu.json @@ -205,7 +205,7 @@ "HeaderSleepTimer": "Alvásidőzítő", "HeaderStatsLargestItems": "Legnagyobb elemek", "HeaderStatsLongestItems": "Leghosszabb elemek (órában)", - "HeaderStatsMinutesListeningChart": "Hallgatási grafikon percekben (az elmúlt 7 napból)", + "HeaderStatsMinutesListeningChart": "Hallgatási grafikon percben (az elmúlt 7 napból)", "HeaderStatsRecentSessions": "Legutóbbi munkamenetek", "HeaderStatsTop10Authors": "Top 10 szerző", "HeaderStatsTop5Genres": "Top 5 műfaj", @@ -643,8 +643,8 @@ "LabelStatsAuthors": "Szerző", "LabelStatsBestDay": "Legjobb nap", "LabelStatsDailyAverage": "Napi átlag", - "LabelStatsDays": "Napok", - "LabelStatsDaysListened": "Hallgatással töltött napok", + "LabelStatsDays": "Nap", + "LabelStatsDaysListened": "Hallgatással töltött nap", "LabelStatsHours": "Órák", "LabelStatsInARow": "egymás után", "LabelStatsItemsFinished": "Befejezett elem", From cf4b9e938d1be0085484b780b25fcb6ead25d0f8 Mon Sep 17 00:00:00 2001 From: Maxklos Date: Fri, 13 Feb 2026 12:55:15 +0100 Subject: [PATCH 041/124] Translated using Weblate (German) Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/ --- client/strings/de.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/client/strings/de.json b/client/strings/de.json index cf36b07fd..754f9a3a4 100644 --- a/client/strings/de.json +++ b/client/strings/de.json @@ -116,7 +116,7 @@ "ButtonViewAll": "Alles anzeigen", "ButtonYes": "Ja", "ErrorUploadFetchMetadataAPI": "Fehler beim Abrufen der Metadaten", - "ErrorUploadFetchMetadataNoResults": "Metadaten konnten nicht abgerufen werden. Versuche den Titel und/oder den Autor zu aktualisieren.", + "ErrorUploadFetchMetadataNoResults": "Metadaten konnten nicht abgerufen werden. Versuche, den Titel und/oder den Autor zu aktualisieren.", "ErrorUploadLacksTitle": "Es muss ein Titel eingegeben werden", "HeaderAccount": "Konto", "HeaderAddCustomMetadataProvider": "Benutzerdefinierten Metadatenanbieter hinzufügen", @@ -710,7 +710,7 @@ "LabelUploaderDragAndDropFilesOnly": "Dateien per Drag & Drop hierher ziehen", "LabelUploaderDropFiles": "Dateien löschen", "LabelUploaderItemFetchMetadataHelp": "Automatisches Aktualisieren von Titel, Autor und Serie", - "LabelUseAdvancedOptions": "Nutze Erweiterte Optionen", + "LabelUseAdvancedOptions": "Erweiterte Optionen nutzen", "LabelUseChapterTrack": "Kapiteldatei verwenden", "LabelUseFullTrack": "Gesamte Datei verwenden", "LabelUseZeroForUnlimited": "0 für unbegrenzt", @@ -737,7 +737,7 @@ "MessageAddToPlayerQueue": "Zur Abspielwarteliste hinzufügen", "MessageAppriseDescription": "Um diese Funktion nutzen zu können, musst du eine Instanz von Apprise API laufen haben oder eine API verwenden welche dieselbe Anfragen bearbeiten kann.
Die Apprise API Url muss der vollständige URL-Pfad sein, an den die Benachrichtigung gesendet werden soll, z.B. wenn Ihre API-Instanz unter http://192.168.1.1:8337 läuft, würdest du http://192.168.1.1:8337/notify eingeben.", "MessageAsinCheck": "Stelle sicher, dass die ASIN aus der richtigen Audible Region verwendet wird, nicht Amazon.", - "MessageAuthenticationLegacyTokenWarning": "Alte API tokens werden in Zukunft entfernt. Benutze stattdessen API Keys.", + "MessageAuthenticationLegacyTokenWarning": "Alte API-Token werden in Zukunft entfernt. Benutze stattdessen API Keys.", "MessageAuthenticationOIDCChangesRestart": "Nach dem Speichern muss der Server neugestartet werden um die OIDC Änderungen zu übernehmen.", "MessageAuthenticationSecurityMessage": "Die Anmeldung wurde abgesichert. Benutzersitzungen werden getrennt, alle Benutzer müssen sich erneut anmelden.", "MessageBackupsDescription": "In einer Sicherung werden Benutzer, Benutzerfortschritte, Details zu den Bibliotheksobjekten, Servereinstellungen und Bilder welche in /metadata/items & /metadata/authors gespeichert sind gespeichert. Sicherungen enthalten keine Dateien welche in den einzelnen Bibliotheksordnern (Medien-Ordnern) gespeichert sind.", @@ -1103,11 +1103,11 @@ "ToastPodcastCreateFailed": "Podcast konnte nicht erstellt werden", "ToastPodcastCreateSuccess": "Podcast erstellt", "ToastPodcastEpisodeUpdated": "Podcast-Folge aktualisiert", - "ToastPodcastGetFeedFailed": "Fehler beim abrufen des Podcast Feeds", + "ToastPodcastGetFeedFailed": "Fehler beim abrufen des Podcast-Feeds", "ToastPodcastNoEpisodesInFeed": "Keine Episoden in RSS Feed gefunden", "ToastPodcastNoRssFeed": "Podcast enthält keinen RSS Feed", "ToastProgressIsNotBeingSynced": "Fortschritt wird nicht synchronisiert, Wiedergabe wird neu gestartet", - "ToastProviderCreatedFailed": "Fehler beim hinzufügen des Anbieters", + "ToastProviderCreatedFailed": "Fehler beim Hinzufügen des Anbieters", "ToastProviderCreatedSuccess": "Neuer Anbieter hinzugefügt", "ToastProviderNameAndUrlRequired": "Name und URL notwendig", "ToastProviderRemoveSuccess": "Anbieter entfernt", From 40f42b2ab6c870e1658e1d72145d444bc5e05278 Mon Sep 17 00:00:00 2001 From: B0rax Date: Fri, 13 Feb 2026 12:58:05 +0100 Subject: [PATCH 042/124] Translated using Weblate (German) Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/ --- client/strings/de.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/strings/de.json b/client/strings/de.json index 754f9a3a4..276348656 100644 --- a/client/strings/de.json +++ b/client/strings/de.json @@ -710,7 +710,7 @@ "LabelUploaderDragAndDropFilesOnly": "Dateien per Drag & Drop hierher ziehen", "LabelUploaderDropFiles": "Dateien löschen", "LabelUploaderItemFetchMetadataHelp": "Automatisches Aktualisieren von Titel, Autor und Serie", - "LabelUseAdvancedOptions": "Erweiterte Optionen nutzen", + "LabelUseAdvancedOptions": "Erweiterte Optionen verwenden", "LabelUseChapterTrack": "Kapiteldatei verwenden", "LabelUseFullTrack": "Gesamte Datei verwenden", "LabelUseZeroForUnlimited": "0 für unbegrenzt", From a35ba05600be1542f5d3e755c6c31c7486601a7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20Dobra=C4=8Da?= Date: Mon, 16 Feb 2026 04:12:03 +0100 Subject: [PATCH 043/124] Translated using Weblate (Croatian) Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hr/ --- client/strings/hr.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/strings/hr.json b/client/strings/hr.json index eccd12563..65150409f 100644 --- a/client/strings/hr.json +++ b/client/strings/hr.json @@ -436,9 +436,9 @@ "LabelLibraryFilterSublistEmpty": "Br {0}", "LabelLibraryItem": "Stavka knjižnice", "LabelLibraryName": "Ime knjižnice", - "LabelLibrarySortByProgress": "Napredak: zadnje ažurirano", - "LabelLibrarySortByProgressFinished": "Napredak: završeno", - "LabelLibrarySortByProgressStarted": "Napredak: započeto", + "LabelLibrarySortByProgress": "Napredak: Zadnje ažuriranje", + "LabelLibrarySortByProgressFinished": "Napredak: Završeno", + "LabelLibrarySortByProgressStarted": "Napredak: Započeto", "LabelLimit": "Ograničenje", "LabelLineSpacing": "Razmak između redaka", "LabelListenAgain": "Ponovno poslušaj", From 69d7c399b8a8110b29fdbaafef489bc44677577b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karl=20Bernst=C3=A5l?= Date: Sun, 15 Feb 2026 13:54:38 +0100 Subject: [PATCH 044/124] Translated using Weblate (Swedish) Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sv/ --- client/strings/sv.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/client/strings/sv.json b/client/strings/sv.json index e1c08efa5..fa0267e82 100644 --- a/client/strings/sv.json +++ b/client/strings/sv.json @@ -1,6 +1,6 @@ { "ButtonAdd": "Lägg till", - "ButtonAddApiKey": "Addera API-nyckel", + "ButtonAddApiKey": "Lägg till API-nyckel", "ButtonAddChapters": "Lägg till kapitel", "ButtonAddDevice": "Lägg till enhet", "ButtonAddLibrary": "Lägg till bibliotek", @@ -48,7 +48,7 @@ "ButtonLogout": "Logga ut", "ButtonLookup": "Sök", "ButtonManageTracks": "Hantera spår", - "ButtonMapChapterTitles": "Karta kapitelrubriker", + "ButtonMapChapterTitles": "Mappa kapitelrubriker", "ButtonMatchAllAuthors": "Matcha alla författare", "ButtonMatchBooks": "Matcha böcker", "ButtonNevermind": "Glöm det", @@ -104,7 +104,7 @@ "ButtonStartM4BEncode": "Starta M4B-omkodning", "ButtonStartMetadataEmbed": "Infoga metadata", "ButtonStats": "Statistik", - "ButtonSubmit": "Spara", + "ButtonSubmit": "Skicka", "ButtonTest": "Testa", "ButtonUnlinkOpenId": "Koppla ifrån OpenID", "ButtonUpload": "Ladda upp", @@ -123,7 +123,7 @@ "HeaderAdvanced": "Avancerad", "HeaderApiKeys": "API-nyckel", "HeaderAppriseNotificationSettings": "Inställningar av meddelanden med Apprise", - "HeaderAudioTracks": "Ljudfiler", + "HeaderAudioTracks": "Ljudspår", "HeaderAudiobookTools": "Hantering av ljudboksfiler", "HeaderAuthentication": "Autentisering", "HeaderBackups": "Säkerhetskopior", @@ -202,7 +202,7 @@ "HeaderSettingsScanner": "Skanner", "HeaderSettingsSecurity": "Säkerhet", "HeaderSettingsWebClient": "Webklient", - "HeaderSleepTimer": "Timer för att sova", + "HeaderSleepTimer": "Insomningstimer", "HeaderStatsLargestItems": "Största objekten", "HeaderStatsLongestItems": "Längsta objekten (timmar)", "HeaderStatsMinutesListeningChart": "Minuters lyssning (senaste 7 dagarna)", @@ -231,10 +231,10 @@ "LabelActivity": "Aktivitet", "LabelAddToCollection": "Lägg till i en samling", "LabelAddToCollectionBatch": "Lägg till {0} böcker i samlingen", - "LabelAddToPlaylist": "Lägg till i en spellista", + "LabelAddToPlaylist": "Lägg till i spellista", "LabelAddToPlaylistBatch": "Lägg till {0} objekt i Spellistan", "LabelAddedAt": "Datum adderad", - "LabelAddedDate": "Adderad {0}", + "LabelAddedDate": "Tillagd {0}", "LabelAdminUsersOnly": "Endast administratörer", "LabelAll": "Alla", "LabelAllEpisodesDownloaded": "Alla avsnitt är nedladdade", @@ -363,14 +363,14 @@ "LabelExpiresAt": "Gäller till och med", "LabelExpiresInSeconds": "Upphör om (sekunder)", "LabelExpiresNever": "Aldrig", - "LabelExplicit": "Bestämd", + "LabelExplicit": "Vuxeninnehåll", "LabelExplicitChecked": "Explicit version (markerad)", "LabelExplicitUnchecked": "Ej Explicit version (ej markerad)", "LabelExportOPML": "Exportera OPML-information", "LabelFeedURL": "URL-adress för flödet", "LabelFetchingMetadata": "Hämtar metadata", "LabelFile": "Fil", - "LabelFileBirthtime": "Tidpunkt, adderad", + "LabelFileBirthtime": "Tidpunkt, tillagd", "LabelFileBornDate": "Skapad {0}", "LabelFileModified": "Tidpunkt, ändrad", "LabelFileModifiedDate": "Ändrad {0}", @@ -629,7 +629,7 @@ "LabelShowSeconds": "Visa i sekunder", "LabelShowSubtitles": "Visa underrubriker", "LabelSize": "Storlek", - "LabelSleepTimer": "Sovtimer", + "LabelSleepTimer": "Insomningstimer", "LabelSlug": "Kortnamn", "LabelSortAscending": "Stigande", "LabelSortDescending": "Fallande", From fb8ca043ad5d34086ef8ac2b431ee1664fd69861 Mon Sep 17 00:00:00 2001 From: Silviu Bajenaru Date: Wed, 18 Feb 2026 18:45:55 +0100 Subject: [PATCH 045/124] Translated using Weblate (Romanian) Currently translated at 55.1% (641 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ro/ --- client/strings/ro.json | 447 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 444 insertions(+), 3 deletions(-) diff --git a/client/strings/ro.json b/client/strings/ro.json index f6e8977ef..441ccc400 100644 --- a/client/strings/ro.json +++ b/client/strings/ro.json @@ -11,140 +11,581 @@ "ButtonApplyChapters": "Aplică Capitole", "ButtonAuthors": "Autori", "ButtonBack": "Înapoi", + "ButtonBatchEditPopulateFromExisting": "Populează din existente", + "ButtonBatchEditPopulateMapDetails": "Populează detaliile hărții", + "ButtonBrowseForFolder": "Caută un dosar", "ButtonCancel": "Anulează", + "ButtonCancelEncode": "Anulare codificare", + "ButtonChangeRootPassword": "Schimbare parolă de root", + "ButtonCheckAndDownloadNewEpisodes": "Verifică și descarcă episoade noi", + "ButtonChooseAFolder": "Alege un dosar", + "ButtonChooseFiles": "Alege fișiere", "ButtonClearFilter": "Șterge filtrul", "ButtonClose": "Închide", "ButtonCloseFeed": "Închide sursa", "ButtonCloseSession": "Închide Sesiunea Curentă", "ButtonCollections": "Colecții", + "ButtonConfigureScanner": "Configurare scaner", "ButtonCreate": "Creează", + "ButtonCreateBackup": "Creează backup", "ButtonDelete": "Șterge", + "ButtonDownloadQueue": "Coadă", + "ButtonEdit": "Editare", + "ButtonEditChapters": "Editare capitole", + "ButtonEditPodcast": "Editare podcast", + "ButtonEnable": "Activează", + "ButtonForceReScan": "Forțează rescanare", + "ButtonFullPath": "Calea completă", "ButtonHide": "Ascunde", "ButtonHome": "Acasă", - "ButtonIssues": "Erori", + "ButtonIssues": "Probleme", + "ButtonJumpBackward": "Sari înapoi", + "ButtonJumpForward": "Sari înainte", "ButtonLatest": "Noutăți", "ButtonLibrary": "Bibliotecă", + "ButtonLogout": "Deconectare", + "ButtonLookup": "Căutare", + "ButtonManageTracks": "Gestionează pista", + "ButtonMapChapterTitles": "Maparea titlurilor capitolelor", + "ButtonMatchAllAuthors": "Potriviește toți autorii", + "ButtonMatchBooks": "Potrivește Cărți", + "ButtonNevermind": "Anulează", + "ButtonNext": "Următorul", + "ButtonNextChapter": "Următorul Capitol", + "ButtonNextItemInQueue": "Următorul Articol în Coadă", "ButtonOk": "OK", "ButtonOpenFeed": "Vezi noutățile", + "ButtonOpenManager": "Deschide Managerul", "ButtonPause": "Pauză", "ButtonPlay": "Redă", + "ButtonPlayAll": "Redă tot", + "ButtonPlaying": "Redare", "ButtonPlaylists": "Liste", + "ButtonPrevious": "Anterior", + "ButtonPreviousChapter": "Capitolul Anterior", + "ButtonProbeAudioFile": "Analizare Fișier Audio", + "ButtonPurgeAllCache": "Golire Cache Completă", + "ButtonPurgeItemsCache": "Golire Cache Articole", + "ButtonQueueAddItem": "Adaugă la Coadă", + "ButtonQueueRemoveItem": "Sterge din Coadă", + "ButtonQuickEmbed": "Încorporare Rapidă", + "ButtonQuickEmbedMetadata": "Metadate pentru Încorporare Rapidă", + "ButtonQuickMatch": "Potrivire Rapidă", + "ButtonReScan": "Rescanare", "ButtonRead": "Citește", - "ButtonReadLess": "Mai puțin", + "ButtonReadLess": "Citește Mai Puțin", "ButtonReadMore": "Afișează mai mult", + "ButtonRefresh": "Reîmprospătare", "ButtonRemove": "Elimină", + "ButtonRemoveAll": "Eliminați Tot", + "ButtonRemoveAllLibraryItems": "Ștergerea tuturor Articolelor din Librărie", + "ButtonRemoveFromContinueListening": "Ștergere din \"Continuă să Asculți\"", + "ButtonRemoveFromContinueReading": "Ștergere din \"Continuă să citești\"", + "ButtonRemoveSeriesFromContinueSeries": "Ștergere Serie din \"Continuă Seria\"", + "ButtonReset": "Resetează", + "ButtonResetToDefault": "Resetează la valorile implicite", + "ButtonRestore": "Restaurare", "ButtonSave": "Salvează", + "ButtonSaveAndClose": "Salvează și Închide", + "ButtonSaveTracklist": "Salvare Pistă", + "ButtonScan": "Scanează", + "ButtonScanLibrary": "Scanează Librăria", + "ButtonScrollLeft": "Derulează spre stânga", + "ButtonScrollRight": "Derulează spre Dreapta", "ButtonSearch": "Caută", + "ButtonSelectFolderPath": "Selectează Calea către Dosar", "ButtonSeries": "Serii", + "ButtonSetChaptersFromTracks": "Setează capitole din piste", + "ButtonShare": "Distribuie", + "ButtonShiftTimes": "Aliniează timpi", + "ButtonShow": "Arată", + "ButtonStartM4BEncode": "Începe Codarea M4B", + "ButtonStartMetadataEmbed": "Începe Încorporarea Metadatelor", + "ButtonStats": "Statistici", "ButtonSubmit": "Trimite", + "ButtonTest": "Testează", + "ButtonUnlinkOpenId": "Deconectare OpenID", + "ButtonUpload": "Încarcă", + "ButtonUploadBackup": "Încarcă Backup", + "ButtonUploadCover": "Încarcă Copertă", + "ButtonUploadOPMLFile": "Încarcă Fișier OPML", + "ButtonUserDelete": "Șterge userul {0}", + "ButtonUserEdit": "Editează userul {0}", + "ButtonViewAll": "Vizualizează tot", "ButtonYes": "Da", + "ErrorUploadFetchMetadataAPI": "Eroare în descărcarea metadatelor", + "ErrorUploadFetchMetadataNoResults": "Nu s-au putut prelua metadatele - încearcă să editezi titlul și/sau autorul", + "ErrorUploadLacksTitle": "Trebuie să aibă un titlu", "HeaderAccount": "Cont", + "HeaderAddCustomMetadataProvider": "Adaugă Furnizor de Metadate Personalizat", "HeaderAdvanced": "Avansat", + "HeaderApiKeys": "Chei API", + "HeaderAppriseNotificationSettings": "Setări Notificări Apprise", "HeaderAudioTracks": "Înregistrări audio", + "HeaderAudiobookTools": "Instrumente pentru Gestionarea Fișierelor Audiobook", + "HeaderAuthentication": "Autentificare", + "HeaderBackups": "Copii de siguranță", + "HeaderBulkChapterModal": "Adaugă Multiple Capitole", + "HeaderChangePassword": "Schimbă Parola", "HeaderChapters": "Capitole", + "HeaderChooseAFolder": "Alege Dosar", "HeaderCollection": "Colecție", "HeaderCollectionItems": "Conținutul colecției", + "HeaderCover": "Copertă", + "HeaderCurrentDownloads": "Descărcări Curente", + "HeaderCustomMessageOnLogin": "Mesaj Personalizat la Autentificare", + "HeaderCustomMetadataProviders": "Furnizor de Metadate Personalizat", "HeaderDetails": "Detalii", + "HeaderDownloadQueue": "Coadă de Descărcare", "HeaderEbookFiles": "Ebook-uri", + "HeaderEmail": "Email", + "HeaderEmailSettings": "Setări Email", "HeaderEpisodes": "Episoade", + "HeaderEreaderDevices": "Dispozitive eReader", "HeaderEreaderSettings": "Setări eReader", + "HeaderFiles": "Fișiere", + "HeaderFindChapters": "Caută Capitol", + "HeaderIgnoredFiles": "Fișiere Ignorate", + "HeaderItemFiles": "Fișiere Articol", + "HeaderLastListeningSession": "Ultima Sesiune de Ascultare", "HeaderLatestEpisodes": "Episoade recente", "HeaderLibraries": "Biblioteci", + "HeaderLibraryFiles": "Fișiere in Librărie", + "HeaderLibraryStats": "Statistici Librărie", + "HeaderListeningSessions": "Sesiuni de Ascultare", + "HeaderListeningStats": "Statistici Ascultare", + "HeaderLogin": "Autentifică", + "HeaderLogs": "Loguri", + "HeaderManageGenres": "Gestionează Genuri", + "HeaderManageTags": "Gestionează Etichete", + "HeaderMapDetails": "Detaliile Hărții", + "HeaderMatch": "Potrivește", + "HeaderMetadataOrderOfPrecedence": "Prioritatea Metadatelor", + "HeaderMetadataToEmbed": "Metadate pentru Încorporare", + "HeaderNewAccount": "Cont nou", + "HeaderNewApiKey": "Cheie API Nouă", + "HeaderNewLibrary": "Librărie Nouă", + "HeaderNotificationCreate": "Creează Notificare", + "HeaderNotificationUpdate": "Actualizare Notificare", + "HeaderNotifications": "Notificări", + "HeaderOpenIDConnectAuthentication": "Autentificare prin OpenID", + "HeaderOpenListeningSessions": "Deschide Sesiuni de Ascultare", "HeaderOpenRSSFeed": "Deschide flux RSS", + "HeaderOtherFiles": "Alte Fișiere", + "HeaderPasswordAuthentication": "Autentificare cu Parolă", + "HeaderPermissions": "Permisiuni", + "HeaderPlayerQueue": "Coadă Player", + "HeaderPlayerSettings": "Setări Player", "HeaderPlaylist": "Listă de redare", "HeaderPlaylistItems": "Conținut listă", + "HeaderPodcastsToAdd": "Podcast de Adăugat", + "HeaderPresets": "Presetări", + "HeaderPreviewCover": "Previzualizare Copertă", "HeaderRSSFeedGeneral": "Date RSS", "HeaderRSSFeedIsOpen": "RSS activ", + "HeaderRSSFeeds": "Fluxuri RSS", + "HeaderRemoveEpisode": "Elimină Episod", + "HeaderRemoveEpisodes": "Elimină {0} Episoade", + "HeaderSavedMediaProgress": "Progres Media Salvat", + "HeaderSchedule": "Planifică", + "HeaderScheduleEpisodeDownloads": "Planifică Descărcare Automată a Episoadelor", + "HeaderScheduleLibraryScans": "Planifică Scanarea Automată a Librăriei", + "HeaderSession": "Sesiuni", + "HeaderSetBackupSchedule": "Planifică Backup", "HeaderSettings": "Setări", + "HeaderSettingsDisplay": "Afișaj", + "HeaderSettingsExperimental": "Caracteristici Experimentale", + "HeaderSettingsGeneral": "General", + "HeaderSettingsScanner": "Scaner", + "HeaderSettingsSecurity": "Securitate", + "HeaderSettingsWebClient": "Client Web", "HeaderSleepTimer": "Timer de somn", + "HeaderStatsLargestItems": "Cele mai mari articole", + "HeaderStatsLongestItems": "Cele mai lungi articole (ore)", "HeaderStatsMinutesListeningChart": "Minute ascultate (ultimele 7 zile)", "HeaderStatsRecentSessions": "Sesiuni recente", + "HeaderStatsTop10Authors": "Top 10 Autori", + "HeaderStatsTop5Genres": "Top 5 Genuri", "HeaderTableOfContents": "Cuprins", + "HeaderTools": "Unelte", + "HeaderUpdateAccount": "Actualizare Cont", + "HeaderUpdateApiKey": "Actualizare Cheie API", + "HeaderUpdateAuthor": "Actualizare Autor", + "HeaderUpdateDetails": "Actualizare Detalii", + "HeaderUpdateLibrary": "Actualizare Librărie", + "HeaderUsers": "Utilizatori", + "HeaderYearReview": "Trecere în revistă a anului {0}", "HeaderYourStats": "Progresul tău", + "LabelAbridged": "Abreviat", + "LabelAbridgedChecked": "Abreviat (verificat)", + "LabelAbridgedUnchecked": "Neprescurtat (neverificat)", + "LabelAccessibleBy": "Accesibil prin", + "LabelAccountType": "Tip de Cont", + "LabelAccountTypeAdmin": "Administrator", + "LabelAccountTypeGuest": "Oaspete", + "LabelAccountTypeUser": "Utilizator", + "LabelActivities": "Activități", + "LabelActivity": "Activitate", + "LabelAddToCollection": "Adaugă la Colecție", + "LabelAddToCollectionBatch": "Adaugare {0} Cărți la Colecție", "LabelAddToPlaylist": "Adaugă în listă", + "LabelAddToPlaylistBatch": "Adaugare {0} Articole la Listă", "LabelAddedAt": "Adăugat la", "LabelAddedDate": "Adăugat {0}", + "LabelAdminUsersOnly": "Doar Administratori", "LabelAll": "Toate", + "LabelAllEpisodesDownloaded": "Toate episoadele descărcate", + "LabelAllUsers": "Toți Utilizatorii", + "LabelAllUsersExcludingGuests": "Toți utilizatorii cu excepția oaspeților", + "LabelAllUsersIncludingGuests": "Toți utilizatorii inclusiv oaspeții", + "LabelAlreadyInYourLibrary": "Deja în bibliotecă", + "LabelApiKeyCreated": "Cheia API \"{0}\" creată cu succes.", + "LabelApiKeyCreatedDescription": "Copiază cheia API acum deoarece nu va mai fi disponibilă pentru vizualizare.", + "LabelApiKeyUser": "Acționează în numele utilizatorului", + "LabelApiKeyUserDescription": "Această cheie API va avea aceleași permisiuni ca utilizatorul în numele căruia acționează. In loguri va părea că utilizatorul lansa cererile.", + "LabelApiToken": "Token API", + "LabelAppend": "Atașează", + "LabelAudioBitrate": "Rata de Biți Audio (e.g. 128k)", + "LabelAudioChannels": "Canale Audio (1 sau 2)", + "LabelAudioCodec": "Codec Audio", "LabelAuthor": "Autor", "LabelAuthorFirstLast": "Autor (Prenume Nume)", "LabelAuthorLastFirst": "Autor (Nume, Prenume)", "LabelAuthors": "Autori", "LabelAutoDownloadEpisodes": "Descarcă automat episoadele", + "LabelAutoFetchMetadata": "Descarcă Automat Metadate", + "LabelAutoFetchMetadataHelp": "Descarcă metadate pentru titlu, autor si serii pentru eficientizarea încărcării. Metadatele suplimentare s-ar putea să trebuiască potrivite după încărcare.", + "LabelAutoLaunch": "Lansare automată", + "LabelAutoLaunchDescription": "Redirecționează automat către furnizorul de autentificare când navighez la pagina de autentificare (cale de suprascriere manuală /login?autoLaunch=0)", + "LabelAutoRegister": "Înregistrare Automată", + "LabelAutoRegisterDescription": "Creează utilizatori automat dupa autentificare", + "LabelBackToUser": "Înapoi la Utilizator", + "LabelBackupAudioFiles": "Copii de rezervă a Fișierelor Audio", + "LabelBackupLocation": "Locația Copiilor de Rezervă", + "LabelBackupsEnableAutomaticBackups": "Copii de Rezervă Automate", + "LabelBackupsEnableAutomaticBackupsHelp": "Copiile de Rezervă au fost salvate în /metadata/backups", + "LabelBackupsMaxBackupSize": "Dimensiunea maximă a copiilor de rezervă (în GB) (0 pentru nelimitat)", + "LabelBackupsMaxBackupSizeHelp": "Ca protecție împotriva configurațiilor greșite, backup-ul va eșua dacă trece de limita de dimensiune configurată.", + "LabelBackupsNumberToKeep": "Numărul copiilor de siguranță de păstrat", + "LabelBackupsNumberToKeepHelp": "Doar 1 copie de siguranță va fi ștearsă odata deci dacă există mai multe copii de siguranță vor trebui șterse manual.", + "LabelBitrate": "Rată de biți", + "LabelBonus": "Bonus", "LabelBooks": "Cărți", + "LabelButtonText": "Textul Butonului", + "LabelByAuthor": "de {0}", + "LabelChangePassword": "Schimbare Parolă", + "LabelChannels": "Canale", + "LabelChapterCount": "{0} Capitole", + "LabelChapterTitle": "Titlul Capitolului", "LabelChapters": "Capitole", + "LabelChaptersFound": "capitole găsite", + "LabelClickForMoreInfo": "Click pentru mai multe informații", + "LabelClickToUseCurrentValue": "Click pentru a folosi valoarea curentă", "LabelClosePlayer": "Închide playerul", + "LabelCodec": "Codec", "LabelCollapseSeries": "Restrânge seriile", + "LabelCollapseSubSeries": "Restrânge Sub-Seriile", + "LabelCollection": "Colecție", + "LabelCollections": "Colecții", "LabelComplete": "Finalizat", + "LabelConfirmPassword": "Confirmare Parolă", "LabelContinueListening": "Ascultă în continuare", "LabelContinueReading": "Continuă lectura", "LabelContinueSeries": "Continuă seria", + "LabelCorsAllowed": "Origini CORS Permise", + "LabelCover": "Copertă", + "LabelCoverImageURL": "URL-ul Imaginii de Copertă", + "LabelCoverProvider": "Furnizor Copertă", + "LabelCreatedAt": "Creat la", + "LabelCronExpression": "Expresie Cron", + "LabelCurrent": "Curent", + "LabelCurrently": "Acum:", + "LabelCustomCronExpression": "Expresie Cron Personalizată:", + "LabelDatetime": "Data și ora", + "LabelDays": "Zile", + "LabelDeleteFromFileSystemCheckbox": "Șterge fișierele din sistem (debifeaza pentru a șterge doar din baza de date)", "LabelDescription": "Descriere", + "LabelDeselectAll": "Deselectați Tot", + "LabelDetectedPattern": "Tipar Identificat:", + "LabelDevice": "Dispozitiv", + "LabelDeviceInfo": "Informații Dispozitiv", + "LabelDeviceIsAvailableTo": "Dispozitiv accesibil lui...", + "LabelDirectory": "Dosar", + "LabelDiscFromFilename": "Disc din Numele Fișierului", + "LabelDiscFromMetadata": "Disc din Metadate", "LabelDiscover": "Descoperă", "LabelDownload": "Descarcă", + "LabelDownloadNEpisodes": "Descarcă {0} episoade", + "LabelDownloadable": "Descărcabil", "LabelDuration": "Durată", + "LabelDurationComparisonExactMatch": "(potrivire exactă)", + "LabelDurationComparisonLonger": "({0} mai lung)", + "LabelDurationComparisonShorter": "({0} mai scurt)", + "LabelDurationFound": "Durată identificată:", "LabelEbook": "Carte electronică", "LabelEbooks": "Cărți electronice", + "LabelEdit": "Editare", + "LabelEmail": "Email", + "LabelEmailSettingsFromAddress": "De la Adresa", + "LabelEmailSettingsRejectUnauthorized": "Respingere certificate neautorizate", + "LabelEmailSettingsRejectUnauthorizedHelp": "Dezactivarea verificării certificatelor SSL vă poate expune conexiunea la riscuri de securitate, cum ar fi atacuri de tip man-in-the-middle. Dezactivați această opțiune dacă înțelegeti implicațiile și aveți încredere în serverul de mail la care vă conectați.", + "LabelEmailSettingsSecure": "Sigur", + "LabelEmailSettingsSecureHelp": "Dacă e adevărat, conexiunea se va realiza prin TLS către server. Dacă e fals, TLS este folosit dacă serverul suporta extensia STARTTLS. În majoritatea cazurilor setati adevărat dacă folosiți portul 465. Pentru portul 587 sau 25 setati fals. (referinta nodemailer.com/smtp/#authentication)", + "LabelEmailSettingsTestAddress": "Adresă de Test", + "LabelEmbeddedCover": "Încorporează Copertă", "LabelEnable": "Activează", + "LabelEncodingBackupLocation": "O copie de siguranță a fișierului audio original va fi salvată în:", + "LabelEncodingChaptersNotEmbedded": "Capitolele nu sunt încorporate în cărțile audio cu mai multe track-uri.", + "LabelEncodingClearItemCache": "Asigurați-vă că ștergeți articolele din cache periodic.", + "LabelEncodingFinishedM4B": "Fișierul M4B va fi adaugat în dosarul dvs. de cărți audio când codificarea e terminată:", + "LabelEncodingInfoEmbedded": "Metadatele vor fi încorporate în fișierele audio din interiorul dosarului dvs. cu cărți audio.", + "LabelEncodingStartedNavigation": "Odată pornită sarcina poti naviga din această pagină.", + "LabelEncodingTimeWarning": "Codificarea poate dura până la 30 de minute.", + "LabelEncodingWarningAdvancedSettings": "Avertizare: Nu modificați aceste setări dacă nu sunteți familiar cu opțiunile de codare ffmpeg .", + "LabelEncodingWatcherDisabled": "Dacă ați dezactivat funcția de urmările va trebui sa rescanați acestă carte audio la ulterior.", "LabelEnd": "Sfârșit", "LabelEndOfChapter": "Sfârșitul capitolului", "LabelEpisode": "Episod", + "LabelEpisodeNotLinkedToRssFeed": "Episoade nelegate de un flux RSS", + "LabelEpisodeNumber": "Episodul #{0}", + "LabelEpisodeTitle": "Titlul Episodului", + "LabelEpisodeType": "Tipul Episodului", + "LabelEpisodeUrlFromRssFeed": "URL-ul Episodului din Fluxul RSS", + "LabelEpisodes": "Episoade", + "LabelEpisodic": "Episodic", + "LabelExample": "Exemplu", + "LabelExpandSeries": "Extinde Seriile", + "LabelExpandSubSeries": "Extinde Sub-Seriile", + "LabelExpired": "Expirat", + "LabelExpiresAt": "Expiră La", + "LabelExpiresInSeconds": "Expiră în (secunde)", + "LabelExpiresNever": "Niciodată", "LabelExplicit": "Explicit", - "LabelFeedURL": "URL flux", + "LabelExplicitChecked": "Explicit (verificat)", + "LabelExplicitUnchecked": "Neexplicit (neverificat)", + "LabelExportOPML": "Exportă OPML", + "LabelFeedURL": "Flux URL", + "LabelFetchingMetadata": "Aducere Metadate", "LabelFile": "Fișier", "LabelFileBirthtime": "Data creării fișierului", + "LabelFileBornDate": "Creat {0}", "LabelFileModified": "Fișier modificat", + "LabelFileModifiedDate": "Modificat {0}", "LabelFilename": "Nume fișier", + "LabelFilterByUser": "Filtrare după Utilizator", + "LabelFindEpisodes": "Găsire Episoade", "LabelFinished": "Finalizat", + "LabelFinishedDate": "Finalizat {0}", "LabelFolder": "Dosar", + "LabelFolders": "Dosare", + "LabelFontBold": "Îngroșat", "LabelFontBoldness": "Grosimea fontului", + "LabelFontFamily": "Familia Fontului", + "LabelFontItalic": "Cursiv", "LabelFontScale": "Mărimea fontului", + "LabelFontStrikethrough": "Tăiat cu o linie", + "LabelFormat": "Format", + "LabelFull": "Întreg", "LabelGenre": "Gen", "LabelGenres": "Genuri", + "LabelHardDeleteFile": "Ștergere definitivă a fișierului", "LabelHasEbook": "Are carte electronică", "LabelHasSupplementaryEbook": "Are carte electronică suplimentară", + "LabelHideSubtitles": "Ascunde Subtitrări", + "LabelHighestPriority": "Prioritatea cea mai ridicată", "LabelHost": "Gazdă", + "LabelHour": "Ora", + "LabelHours": "Ore", + "LabelIcon": "Pictogramă", + "LabelImageURLFromTheWeb": "URL-ul imaginii de pe web", "LabelInProgress": "În desfășurare", + "LabelIncludeInTracklist": "Include în Lista de Melodii", "LabelIncomplete": "Incomplet", + "LabelInterval": "Interval", + "LabelIntervalCustomDailyWeekly": "Personalizat zilnic/saptămânal", + "LabelIntervalEvery12Hours": "La fiecare 12 ore", + "LabelIntervalEvery15Minutes": "La fiecare 15 minute", + "LabelIntervalEvery2Hours": "La fiecare 2 ore", + "LabelIntervalEvery30Minutes": "La fiecare 30 minute", + "LabelIntervalEvery6Hours": "La fiecare 6 ore", + "LabelIntervalEveryDay": "În fiecare zi", + "LabelIntervalEveryHour": "În fiecare oră", + "LabelIntervalEveryMinute": "La fiecare minut", + "LabelInvert": "Inversează", + "LabelItem": "Articol", + "LabelJumpBackwardAmount": "Sari înapoi cu", + "LabelJumpForwardAmount": "Sari înainte cu", "LabelLanguage": "Limbă", + "LabelLanguageDefaultServer": "Limba Prestabilită a Serverului", + "LabelLanguages": "Limbi", + "LabelLastBookAdded": "Ultima Carte Adăugată", + "LabelLastBookUpdated": "Ultima Carte Actualizată", + "LabelLastProgressDate": "Ultimul progres: {0}", + "LabelLastSeen": "Ultima dată văzut", + "LabelLastTime": "Ultima dată", + "LabelLastUpdate": "Ultima actualizare", "LabelLayout": "Aspect", "LabelLayoutSinglePage": "Pagină unică", + "LabelLayoutSplitPage": "Pagină împărțită", + "LabelLess": "Mai puțin", + "LabelLibrariesAccessibleToUser": "Biblioteci Accesibile Utilizatorului", + "LabelLibrary": "Bibliotecă", + "LabelLibraryFilterSublistEmpty": "Numărul {0}", + "LabelLibraryItem": "Articol din Bibliotecă", + "LabelLibraryName": "Numele Bibliotecii", + "LabelLibrarySortByProgress": "Progres: Ultima Actualizare", + "LabelLibrarySortByProgressFinished": "Progres: Finalizat", + "LabelLibrarySortByProgressStarted": "Progres: Început", + "LabelLimit": "Limită", "LabelLineSpacing": "Spațiere între rânduri", "LabelListenAgain": "Ascultă din nou", + "LabelLogLevelDebug": "Depanare", + "LabelLogLevelInfo": "Informații", + "LabelLogLevelWarn": "Avertizare", + "LabelLookForNewEpisodesAfterDate": "Caută episoade noi după această dată", + "LabelLowestPriority": "Cea Mai Scăzută Prioritate", + "LabelMatchConfidence": "Încredere", + "LabelMatchExistingUsersBy": "Potrivire utilizatori existenți prin", + "LabelMatchExistingUsersByDescription": "Folosit pentru a conecta utilizatorii existenți. Odata conectați, utilizatorii vor fi potriviți după un ID unic trimis de furnizorul SSO", + "LabelMaxEpisodesToDownload": "Numarul maxim # de episoade de descărcat. Folosiți 0 pentru nelimitat.", + "LabelMaxEpisodesToDownloadPerCheck": "Numărul maxim # de episoade de descărcat per verificare", + "LabelMaxEpisodesToKeep": "Numarul maxim # de episoade păstrate", + "LabelMaxEpisodesToKeepHelp": "Valorea 0 nu stabilește o limită maximă. După ce un episod nou a fost descărcat automat, cel mai vechi episod va fi șters dacă aveți mai mult de X episoade. Se va șterge câte un episod vechi pentru fiecare episod nou descărcat.", + "LabelMediaPlayer": "Player Media", "LabelMediaType": "Tip media", + "LabelMetaTag": "Etichetă Meta", + "LabelMetaTags": "Etichete Meta", + "LabelMetadataOrderOfPrecedenceDescription": "Sursele de metadate cu prioritate mai mare o să suprascrie sursele de metadate cu prioritate mai mică", + "LabelMetadataProvider": "Furnizor Metadate", + "LabelMinute": "Minut", + "LabelMinutes": "Minute", "LabelMissing": "Lipsă", + "LabelMissingEbook": "Nu are carte electronică", + "LabelMissingSupplementaryEbook": "Nu are carte electronică adițională", + "LabelMobileRedirectURIs": "URL-uri de redirecționare Mobile Permise", + "LabelMobileRedirectURIsDescription": "Aceasta este o listă cu URI-uri valide pentru redirectionare a aplicațiilor mobile. URI-ul predefinit este audiobookshelf://oauth,, care poate fi sters sau suplimentat cu URI-uri adiționale pentru integrarea cu alte aplicații. Folosirea unui asterisc (*) ca singur element permite orice URI.", "LabelMore": "Mai multe", "LabelMoreInfo": "Mai multe informații", "LabelName": "Nume", "LabelNarrator": "Narator", "LabelNarrators": "Naratori", + "LabelNew": "Nou", + "LabelNewPassword": "Parolă Nouă", "LabelNewestAuthors": "Autori noi", "LabelNewestEpisodes": "Episoade noi", + "LabelNextBackupDate": "Următoarea dată a copiilor de siguranță", + "LabelNextChapters": "Următoarele capitole vor fi:", + "LabelNextScheduledRun": "Urmatoarea rulare programată", + "LabelNoApiKeys": "Nu exista chei API", + "LabelNoCustomMetadataProviders": "Nu există furnizori de metadate personalizați", + "LabelNoEpisodesSelected": "Nici un episod selectat", "LabelNotFinished": "Nefinalizat", "LabelNotStarted": "Neînceput", + "LabelNotes": "Note", + "LabelNotificationAppriseURL": "URL-ul(urile) Apprise", + "LabelNotificationAvailableVariables": "Variabile disponibile", + "LabelNotificationBodyTemplate": "Corpul Șablonului", + "LabelNotificationEvent": "Eveniment de notificare", + "LabelNotificationTitleTemplate": "Titlul Șablonului", + "LabelNotificationsMaxFailedAttempts": "Număr de încercări eșuate maxim", + "LabelNotificationsMaxFailedAttemptsHelp": "Notificările sunt dezactivate dacă nu reușesc să fie trimise de acest număr de ori", + "LabelNotificationsMaxQueueSize": "Dimensiunea maximă a cozii pentru evenimentele de notificare", + "LabelNotificationsMaxQueueSizeHelp": "Evenimentele sunt limitate la 1 per secunda. Evenimentele vor fi ignorate dacă coada este plină. Previne spamarea cu notificări.", + "LabelNumberOfBooks": "Numărul de Cărți", + "LabelNumberOfChapters": "Număr de capitole:", "LabelNumberOfEpisodes": "# de Episoade", + "LabelOpenIDAdvancedPermsClaimDescription": "Numele revendicării OpenID care conține permisiuni avansate pentru acțiunile utilizatorului în interiorul aplicației care vor fi aplicate rolurilor non-administrator (dacă e configurat). Dacă revendicarea nu e prezentă în răspunsul primit, accesul către ABS e refuzat. Dacă o singură opțiune lipsește, va fi tratată ca falsă. Asigurați-vă că revendicările furnizorului de securitate corespund structurii așteptate:", + "LabelOpenIDClaims": "Lăsați urmatoarele opțiuni goale pentru a dezactiva atribuirea avansată de grupuri și permisiuni, asignând grupul \"Utilizatori\" automat.", + "LabelOpenRSSFeed": "Flux Open RSS", + "LabelOverwrite": "Suprascrie", + "LabelPaginationPageXOfY": "Pagina {0} din {1}", "LabelPassword": "Parolă", "LabelPath": "Cale", + "LabelPermanent": "Permanent", + "LabelPermissionsAccessAllLibraries": "Poate accesa toate bibliotecile", + "LabelPermissionsAccessAllTags": "Poate accesa toate etichetele", + "LabelPermissionsAccessExplicitContent": "Poate Accesa Conținut Explicit", + "LabelPermissionsCreateEreader": "Poate Crea Cititoare Electronice", + "LabelPermissionsDelete": "Poate Șterge", + "LabelPermissionsDownload": "Poate Descărca", + "LabelPermissionsUpdate": "Poate Actualiza", + "LabelPermissionsUpload": "Poate Încărca", + "LabelPersonalYearReview": "Recapitularea Anului tău ({0})", + "LabelPhotoPathURL": "Calea/URL-ul Fotografiei", + "LabelPlayMethod": "Metoda de Redare", + "LabelPlaybackRateIncrementDecrement": "Incrementare/Decrementare a Ratei de Redare cu", + "LabelPlayerChapterNumberMarker": "{0} din {1}", + "LabelPlaylists": "Liste de redare", "LabelPodcast": "Podcast", + "LabelPodcastSearchRegion": "Regiunea căutării podcastului", + "LabelPodcastType": "Tipul Podcastului", "LabelPodcasts": "Podcasturi", + "LabelPort": "Portul", + "LabelPrefixesToIgnore": "Prefix de ignorat (fără a ține cont de majuscule)", "LabelPreventIndexing": "Împiedică indexarea fluxului în directoarele iTunes și Google Podcasts", + "LabelPrimaryEbook": "eCarte Principală", "LabelProgress": "Progres", + "LabelProvider": "Furnizor", + "LabelProviderAuthorizationValue": "Valoarea Antetului de Autorizare", "LabelPubDate": "Data publicării", "LabelPublishYear": "Anul publicării", "LabelPublishedDate": "Publicat la {0}", + "LabelPublishedDecade": "Deceniul Publicării", + "LabelPublishedDecades": "Deceniile Publicării", + "LabelPublisher": "Editor", + "LabelPublishers": "Editori", "LabelRSSFeedCustomOwnerEmail": "Email personalizat al proprietarului", "LabelRSSFeedCustomOwnerName": "Nume personalizat al proprietarului", "LabelRSSFeedOpen": "Flux RSS deschis", "LabelRSSFeedPreventIndexing": "Previne indexarea", "LabelRSSFeedSlug": "Identificator flux RSS", + "LabelRSSFeedURL": "URL-ul Fluxului RSS", "LabelRandomly": "Aleatoriu", + "LabelReAddSeriesToContinueListening": "Readăugare serie la \"Continuă să asculți\"", "LabelRead": "Citește", "LabelReadAgain": "Citește din nou", + "LabelReadEbookWithoutProgress": "Citire eCarte fără a memora progresul", "LabelRecentSeries": "Serii recente", "LabelRecentlyAdded": "Adăugate recent", + "LabelRecommended": "Recomandat", + "LabelRedo": "Refă", + "LabelRegion": "Regiune", + "LabelReleaseDate": "Data Lansării", + "LabelRemoveAllMetadataAbs": "Ștergerea tuturor fișierelor metadata.abs", + "LabelRemoveAllMetadataJson": "Ștergerea tuturor fișierelor metadata.json", + "LabelRemoveAudibleBranding": "Ștergerea Audible intro și outro din capitole", + "LabelRemoveCover": "Șterge coperta", + "LabelRemoveMetadataFile": "Șterge fisierele metadate din dosarele bibliotecii", + "LabelRemoveMetadataFileHelp": "Șterge toate fișierele metadata.json și metadata.abs din {0} dosare.", + "LabelRowsPerPage": "Rânduri pe pagină", + "LabelSearchTerm": "Termen de căutat", + "LabelSearchTitle": "Titlu de căutat", + "LabelSearchTitleOrASIN": "Titlu de căutat sau ASN", "LabelSeason": "Sezon", + "LabelSeasonNumber": "Sezonul #{0}", + "LabelSelectAll": "Selectează tot", + "LabelSelectAllEpisodes": "Selectează toate episoadele", + "LabelSelectEpisodesShowing": "Selectează {0} episoade dintre cele afișate", + "LabelSelectUser": "Selectare utilizator", + "LabelSelectUsers": "Selectare utilizatori", + "LabelSendEbookToDevice": "Trimite eCarte către...", + "LabelSequence": "Secvență", + "LabelSerial": "Serie", "LabelSeries": "Serii", + "LabelSeriesName": "Numele Seriilor", + "LabelSeriesProgress": "Progresul Seriilor", + "LabelServerLogLevel": "Nivelul de Jurnal al Serverului", + "LabelServerYearReview": "Anul Serverului în Retrospectivă ({0})", "LabelSetEbookAsPrimary": "Setează ca principală", "LabelSetEbookAsSupplementary": "Setează ca suplimentară", + "LabelSettingsAllowIframe": "Permite încorporarea intr-un iframe", + "LabelSettingsAudiobooksOnly": "Doar cărți audio", + "LabelSettingsAudiobooksOnlyHelp": "Activarea acestei set[ri va ignora fișierele eBook daca acestea nu se află într-un dosar al unei cărți audio, caz în care vor fi setate ca eBook suplimentar", + "LabelSettingsBookshelfViewHelp": "Design scheumorf cu rafturi de lemn", + "LabelSettingsChromecastSupport": "Suport Chromecast", + "LabelSettingsDateFormat": "Formatul Datei", + "LabelSettingsEnableWatcher": "Urmărește în mod automat bibliotecile pentru schimbări", + "LabelSettingsEnableWatcherForLibrary": "Urmărește în mod automat biblioteca pentru schimbări", "LabelShowAll": "Afișează tot", "LabelSize": "Dimensiune", "LabelSleepTimer": "Timer de somn", From 210fa55b6a62c3b6b3881dd7d0eb61dd5e313481 Mon Sep 17 00:00:00 2001 From: Plazec Date: Tue, 17 Feb 2026 18:44:48 +0100 Subject: [PATCH 046/124] Translated using Weblate (Czech) Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/cs/ --- client/strings/cs.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/strings/cs.json b/client/strings/cs.json index f70d6693c..49b0d2bd7 100644 --- a/client/strings/cs.json +++ b/client/strings/cs.json @@ -385,7 +385,7 @@ "LabelFontBoldness": "Výraznost písma", "LabelFontFamily": "Rodina písem", "LabelFontItalic": "Kurzíva", - "LabelFontScale": "Měřítko písma", + "LabelFontScale": "Velikost písma", "LabelFontStrikethrough": "Přeškrtnutí", "LabelFormat": "Formát", "LabelFull": "Plné", From 6c2e13fb4e50556a4f6ec0ab005ea577521adbe8 Mon Sep 17 00:00:00 2001 From: Gneb Date: Tue, 17 Feb 2026 18:46:03 +0100 Subject: [PATCH 047/124] Translated using Weblate (Slovak) Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sk/ --- client/strings/sk.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/client/strings/sk.json b/client/strings/sk.json index e9d1d4c60..3ea1e4b4a 100644 --- a/client/strings/sk.json +++ b/client/strings/sk.json @@ -23,7 +23,7 @@ "ButtonClearFilter": "Zrušiť filter", "ButtonClose": "Uzavrieť", "ButtonCloseFeed": "Zatvoriť zdroj", - "ButtonCloseSession": "Ukončiť otvorené pripojenie", + "ButtonCloseSession": "Ukončiť aktívne relácie", "ButtonCollections": "Kolekcie", "ButtonConfigureScanner": "Nastaviť skener", "ButtonCreate": "Vytvoriť", @@ -436,9 +436,9 @@ "LabelLibraryFilterSublistEmpty": "Žiadne {0}", "LabelLibraryItem": "Položka knižnice", "LabelLibraryName": "Názov knižnice", - "LabelLibrarySortByProgress": "Pokrok: Aktualizované", - "LabelLibrarySortByProgressFinished": "Pokrok: Dokončené", - "LabelLibrarySortByProgressStarted": "Pokrok: Začiatok", + "LabelLibrarySortByProgress": "Stav: Naposledy aktualizované", + "LabelLibrarySortByProgressFinished": "Stav: Dokončené", + "LabelLibrarySortByProgressStarted": "Stav: Začal", "LabelLimit": "Limit", "LabelLineSpacing": "Riadkovanie", "LabelListenAgain": "Počúvať znova", @@ -1015,7 +1015,7 @@ "ToastCachePurgeFailed": "Vyčistenie vyrovnávacej pamäte zlyhalo", "ToastCachePurgeSuccess": "Vyrovnávacia pamäť vyčistená", "ToastChapterLocked": "Kapitola je zamknutá.", - "ToastChapterStartTimeAdjusted": "Čas začiatku kapitoly upravený o {0} sek.", + "ToastChapterStartTimeAdjusted": "Čas začiatku kapitoly upravený o {0} sekúnd", "ToastChaptersAllLocked": "Všetky kapitoly sú zamknuté. Odomknite niektoré kapitoly, aby ste posunuli ich časy.", "ToastChaptersHaveErrors": "Kapitoly obsahujú chyby", "ToastChaptersInvalidShiftAmountLast": "Neplatná hodnota veľkosti posunutia. Začiatok poslednej kapitoly by ležal za koncom audioknihy.", @@ -1026,7 +1026,7 @@ "ToastCollectionItemsAddFailed": "Pridanie položky/-iek do zbierky zlyhalo", "ToastCollectionRemoveSuccess": "Zbierka odstránená", "ToastCollectionUpdateSuccess": "Zbierka aktualizovaná", - "ToastConnectionNotAvailable": "Pripojenie je nedostupné. Skúste to neskôr.", + "ToastConnectionNotAvailable": "Pripojenie je nedostupné. Skúste to neskôr", "ToastCoverSearchFailed": "Vyhľadanie obalu sa nepodarilo", "ToastCoverUpdateFailed": "Aktualizácia prebalu zlyhala", "ToastDateTimeInvalidOrIncomplete": "Dátum a čas sú neplatné alebo neúplné", From c1a6b51d78ac8d5c198868c41027e54b7ef5b213 Mon Sep 17 00:00:00 2001 From: herny ucet Date: Tue, 17 Feb 2026 18:45:51 +0100 Subject: [PATCH 048/124] Translated using Weblate (Slovak) Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sk/ --- client/strings/sk.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/strings/sk.json b/client/strings/sk.json index 3ea1e4b4a..f0d3c487b 100644 --- a/client/strings/sk.json +++ b/client/strings/sk.json @@ -383,7 +383,7 @@ "LabelFolders": "Priečinky", "LabelFontBold": "Tučné", "LabelFontBoldness": "Hrúbka písma", - "LabelFontFamily": "Písmo", + "LabelFontFamily": "písmo", "LabelFontItalic": "Kurzíva", "LabelFontScale": "Veľkosť písma", "LabelFontStrikethrough": "Preškrtnuté", From 2f326739914157535d965ef0a8ebfdbcee9836a9 Mon Sep 17 00:00:00 2001 From: peter cerny Date: Tue, 17 Feb 2026 18:48:31 +0100 Subject: [PATCH 049/124] Translated using Weblate (Slovak) Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sk/ --- client/strings/sk.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/client/strings/sk.json b/client/strings/sk.json index f0d3c487b..6101eba7d 100644 --- a/client/strings/sk.json +++ b/client/strings/sk.json @@ -150,12 +150,12 @@ "HeaderIgnoredFiles": "Ignorované súbory", "HeaderItemFiles": "Položka Súbory", "HeaderItemMetadataUtils": "Položka Nástroje metadát", - "HeaderLastListeningSession": "Posledné pripojenie", + "HeaderLastListeningSession": "Posledná relácia", "HeaderLatestEpisodes": "Posledné epizódy", "HeaderLibraries": "Knižnice", "HeaderLibraryFiles": "Súbory knižnice", "HeaderLibraryStats": "Štatistiky knižnice", - "HeaderListeningSessions": "Pripojenia", + "HeaderListeningSessions": "Relácie", "HeaderListeningStats": "Štatistiky počúvania", "HeaderLogin": "Prihlásenie", "HeaderLogs": "Záznamy udalostí", @@ -172,7 +172,7 @@ "HeaderNotificationUpdate": "Aktualizovať notifikáciu", "HeaderNotifications": "Notifikácie", "HeaderOpenIDConnectAuthentication": "Overenie pripojenia OpenID", - "HeaderOpenListeningSessions": "Aktívne pripojenia", + "HeaderOpenListeningSessions": "Aktívne relácie", "HeaderOpenRSSFeed": "Otvoriť RSS zdroj", "HeaderOtherFiles": "Ostatné súbory", "HeaderPasswordAuthentication": "Overenie heslom", @@ -189,7 +189,7 @@ "HeaderRSSFeeds": "RSS zdroje", "HeaderRemoveEpisode": "Odstrániť epizódu", "HeaderRemoveEpisodes": "Odstrániť {0} epizód", - "HeaderSavedMediaProgress": "Priebeh uložených médií", + "HeaderSavedMediaProgress": "Stav uložených médií", "HeaderSchedule": "Plán", "HeaderScheduleEpisodeDownloads": "Naplánovať automatické sťahovanie epizód", "HeaderScheduleLibraryScans": "Naplánovanovať automatické skenovanie knižnice", @@ -222,7 +222,7 @@ "LabelAbridged": "Skrátená verzia", "LabelAbridgedChecked": "Skrátená verzia (zaškrtnuté)", "LabelAbridgedUnchecked": "Neskrátená verzia (nezaškrtnuté)", - "LabelAccessibleBy": "Prístupné pre", + "LabelAccessibleBy": "Dostupné pre", "LabelAccountType": "Typ účtu", "LabelAccountTypeAdmin": "Administrátor", "LabelAccountTypeGuest": "Hosť", @@ -230,7 +230,7 @@ "LabelActivities": "Aktivity", "LabelActivity": "Aktivita", "LabelAddToCollection": "Pridať do zbierky", - "LabelAddToCollectionBatch": "Pridať {0} kníh do kolekcie", + "LabelAddToCollectionBatch": "Pridať {0} kníh do zbierky", "LabelAddToPlaylist": "Pridať do playlistu", "LabelAddToPlaylistBatch": "Pridať {0} položie do playlistu", "LabelAddedAt": "Pridané", @@ -288,8 +288,8 @@ "LabelCodec": "Kodek", "LabelCollapseSeries": "Zbaliť série", "LabelCollapseSubSeries": "Zbaliť podsérie", - "LabelCollection": "Kolekcia", - "LabelCollections": "Kolekcie", + "LabelCollection": "Zbierka", + "LabelCollections": "Zbierky", "LabelComplete": "Hotovo", "LabelConfirmPassword": "Potvrdiť heslo", "LabelContinueListening": "Pokračovať v počúvaní", @@ -528,7 +528,7 @@ "LabelPrefixesToIgnore": "Ignorované predpony (bez ohľadu na veľkosť písmen)", "LabelPreventIndexing": "Zabráni indexácii vašich zdrojov službami iTunes a Google podcast directories", "LabelPrimaryEbook": "Primárny e-book", - "LabelProgress": "Stav", + "LabelProgress": "Aktuálny stav", "LabelProvider": "Poskytovateľ", "LabelProviderAuthorizationValue": "Obsah hlavičky autorizácie", "LabelPubDate": "Dátum publikovania", @@ -548,7 +548,7 @@ "LabelReAddSeriesToContinueListening": "Znova pridať série do pokračujúceho počúvania", "LabelRead": "Načítať", "LabelReadAgain": "Čítať znova", - "LabelReadEbookWithoutProgress": "Čítať e-knihu bez sledovania pokroku", + "LabelReadEbookWithoutProgress": "Čítať e-knihu bez zmeny stavu", "LabelRecentSeries": "Posledné série", "LabelRecentlyAdded": "Posledné pridané", "LabelRecommended": "Odporúčané", @@ -577,7 +577,7 @@ "LabelSerial": "Na pokračovanie", "LabelSeries": "Série", "LabelSeriesName": "Názov série", - "LabelSeriesProgress": "Pokrok série", + "LabelSeriesProgress": "Aktuálny stav série", "LabelServerLogLevel": "Úroveň logovania servera", "LabelServerYearReview": "Rok servera v prehľade ({0})", "LabelSetEbookAsPrimary": "Nastaviť ako primárny", From a1d439b8d53ad456ae0315dd0f04de2ba8538a4a Mon Sep 17 00:00:00 2001 From: lambolighting Date: Thu, 19 Feb 2026 12:29:55 +0100 Subject: [PATCH 050/124] Translated using Weblate (Greek) Currently translated at 27.6% (322 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/el/ --- client/strings/el.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/client/strings/el.json b/client/strings/el.json index 881bd971b..2160b6e14 100644 --- a/client/strings/el.json +++ b/client/strings/el.json @@ -100,9 +100,11 @@ "ButtonShiftTimes": "Χρόνοι Μετακίνησης", "ButtonShow": "Εμφάνιση", "ButtonStartM4BEncode": "Έναρξη Κωδικοποίησης M4B", + "ButtonStartMetadataEmbed": "Έναρξη Ενσωμάτωσης Μεταδεδομένων", "ButtonStats": "Στατιστικά", "ButtonSubmit": "Υποβολή", "ButtonTest": "Δοκιμή", + "ButtonUnlinkOpenId": "Αποσύνδεση OpenID", "ButtonUpload": "Μεταφόρτωση", "ButtonUploadBackup": "Μεταφόρτωση Αντιγράφου Ασφαλείας", "ButtonUploadCover": "Μεταφόρτωση Εξωφύλλου", @@ -111,11 +113,17 @@ "ButtonUserEdit": "Επεξεργασίας χρήστη {0}", "ButtonViewAll": "Εμφάνιση Όλων", "ButtonYes": "Ναι", + "ErrorUploadFetchMetadataAPI": "Σφάλμα κατά την ανάκτηση μεταδεδομένων", + "ErrorUploadFetchMetadataNoResults": "Δεν ήταν δυνατή η ανάκτηση των μεταδεδομένων - δοκιμάστε να ενημερώσετε τον τίτλο και/ή τον συγγραφέα", "ErrorUploadLacksTitle": "Πρέπει να έχει τίτλο", "HeaderAccount": "Λογαριασμός", + "HeaderAddCustomMetadataProvider": "Προσθήκη Προσαρμοσμένου Παρόχου Μεταδεδομένων", "HeaderAdvanced": "Για Προχωρημένους", "HeaderApiKeys": "Κλειδιά API", + "HeaderAppriseNotificationSettings": "Ρυθμίσεις Ειδοποιήσεων Apprise", "HeaderAudioTracks": "Κομμάτια Ήχου", + "HeaderAudiobookTools": "Εργαλεία Διαχείρισης Αρχείων Audiobooks", + "HeaderAuthentication": "Αυθεντικοποίηση", "HeaderBackups": "Αντίγραφα Ασφαλείας", "HeaderBulkChapterModal": "Προσθήκη Πολλαπλών Κεφαλαίων", "HeaderChangePassword": "Αλλαγή Κωδικού Πρόσβασης", @@ -216,6 +224,7 @@ "LabelChapters": "Κεφάλαια", "LabelChaptersFound": "κεφάλαια βρέθηκαν", "LabelClosePlayer": "Κλείσιμο αναπαραγωγής", + "LabelCollapseSeries": "Σύμπτυξη Σειράς", "LabelCollection": "Συλλογή", "LabelCollections": "Συλλογές", "LabelComplete": "Ολοκλήρωση", From fa6dae1a53638e3d7e49e9e6d48e9578c8fcc4e7 Mon Sep 17 00:00:00 2001 From: Pavel Miniutka Date: Tue, 24 Feb 2026 20:09:01 +0100 Subject: [PATCH 051/124] Translated using Weblate (Belarusian) Currently translated at 79.8% (929 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/be/ --- client/strings/be.json | 446 ++++++++++++++++++++++++++++------------- 1 file changed, 309 insertions(+), 137 deletions(-) diff --git a/client/strings/be.json b/client/strings/be.json index f812a13c9..799511d4c 100644 --- a/client/strings/be.json +++ b/client/strings/be.json @@ -7,18 +7,18 @@ "ButtonAddPodcasts": "Дадаць падкасты", "ButtonAddUser": "Дадаць карыстальніка", "ButtonAddYourFirstLibrary": "Дадайце сваю першую бібліятэку", - "ButtonApply": "Ужыць", - "ButtonApplyChapters": "Ужыць раздзелы", + "ButtonApply": "Прымяніць", + "ButtonApplyChapters": "Прымяніць раздзелы", "ButtonAuthors": "Аўтары", "ButtonBack": "Назад", "ButtonBatchEditPopulateFromExisting": "Запоўніць з існуючага", "ButtonBatchEditPopulateMapDetails": "Запоўніць падрабязнасці карты", - "ButtonBrowseForFolder": "Знайсці тэчку", + "ButtonBrowseForFolder": "Агляд папак", "ButtonCancel": "Скасаваць", "ButtonCancelEncode": "Скасаваць кадзіраванне", "ButtonChangeRootPassword": "Зменіце Root пароль", "ButtonCheckAndDownloadNewEpisodes": "Праверыць і спампаваць новыя эпізоды", - "ButtonChooseAFolder": "Выбраць тэчку", + "ButtonChooseAFolder": "Выбраць папку", "ButtonChooseFiles": "Выбраць файлы", "ButtonClearFilter": "Ачысціць фільтр", "ButtonClose": "Закрыць", @@ -26,7 +26,7 @@ "ButtonCloseSession": "Закрыць адкрыты сеанс", "ButtonCollections": "Калекцыі", "ButtonConfigureScanner": "Наладзіць сканер", - "ButtonCreate": "Ствараць", + "ButtonCreate": "Стварыць", "ButtonCreateBackup": "Стварыць рэзервовую копію", "ButtonDelete": "Выдаліць", "ButtonDownloadQueue": "Чарга", @@ -47,22 +47,22 @@ "ButtonLibrary": "Бібліятэка", "ButtonLogout": "Выйсці", "ButtonLookup": "Пошук", - "ButtonManageTracks": "Кіраванне дарожкамі", - "ButtonMapChapterTitles": "Супаставіць назвы раздзелаў", + "ButtonManageTracks": "Кіраванне трэкамі", + "ButtonMapChapterTitles": "Супаставіць загалоўкі раздзелаў", "ButtonMatchAllAuthors": "Супадзенне ўсіх аўтараў", "ButtonMatchBooks": "Падбор кніг", "ButtonNevermind": "Няважна", "ButtonNext": "Далей", "ButtonNextChapter": "Наступны раздзел", "ButtonNextItemInQueue": "Наступны элемент у чарзе", - "ButtonOk": "Добра", + "ButtonOk": "ОК", "ButtonOpenFeed": "Адкрыць стужку", "ButtonOpenManager": "Адкрыць менеджар", - "ButtonPause": "Паўза", + "ButtonPause": "Прыпыніць", "ButtonPlay": "Прайграць", "ButtonPlayAll": "Прайграць усё", "ButtonPlaying": "Прайграваецца", - "ButtonPlaylists": "Спісы прайгравання", + "ButtonPlaylists": "Плэй-лісты", "ButtonPrevious": "Папярэдні", "ButtonPreviousChapter": "Папярэдні раздзел", "ButtonProbeAudioFile": "Праверыць аўдыяфайл", @@ -71,7 +71,7 @@ "ButtonQueueAddItem": "Дадаць у чаргу", "ButtonQueueRemoveItem": "Выдаліць з чаргі", "ButtonQuickEmbed": "Хуткае ўбудаванне", - "ButtonQuickEmbedMetadata": "Хуткае ўбудаванне метададзеных", + "ButtonQuickEmbedMetadata": "Хуткае ўбудаванне метаданых", "ButtonQuickMatch": "Хуткі пошук", "ButtonReScan": "Паўторнае сканаванне", "ButtonRead": "Чытаць", @@ -85,7 +85,7 @@ "ButtonRemoveFromContinueReading": "Выдаліць з Працягваць чытанне", "ButtonRemoveSeriesFromContinueSeries": "Выдаліць серыю з Працягваць серыю", "ButtonReset": "Скінуць", - "ButtonResetToDefault": "Скінуць па змаўчанні", + "ButtonResetToDefault": "Скінуць да прадвызначаных", "ButtonRestore": "Аднавіць", "ButtonSave": "Захаваць", "ButtonSaveAndClose": "Захаваць і зачыніць", @@ -95,14 +95,14 @@ "ButtonScrollLeft": "Пракруціць улева", "ButtonScrollRight": "Пракруціць направа", "ButtonSearch": "Пошук", - "ButtonSelectFolderPath": "Выбраць шлях да тэчкі", + "ButtonSelectFolderPath": "Выбраць шлях да папкі", "ButtonSeries": "Серыі", - "ButtonSetChaptersFromTracks": "Усталяваць раздзелы з трэкаў", + "ButtonSetChaptersFromTracks": "Задаць раздзелы з трэкаў", "ButtonShare": "Падзяліцца", "ButtonShiftTimes": "Карэкцыя часу", "ButtonShow": "Паказаць", "ButtonStartM4BEncode": "Пачаць кадзіраванне ў M4B", - "ButtonStartMetadataEmbed": "Пачаць убудаванне метададзеных", + "ButtonStartMetadataEmbed": "Пачаць убудаванне метаданых", "ButtonStats": "Статыстыка", "ButtonSubmit": "Адправіць", "ButtonTest": "Тэст", @@ -115,27 +115,28 @@ "ButtonUserEdit": "Рэдагаваць карыстальніка {0}", "ButtonViewAll": "Прагледзець усе", "ButtonYes": "Так", - "ErrorUploadFetchMetadataAPI": "Памылка пры атрыманні метададзеных", - "ErrorUploadFetchMetadataNoResults": "Не ўдалося атрымаць метададзеныя – паспрабуйце абнавіць назву і/або аўтара", - "ErrorUploadLacksTitle": "Павінна быць назва", + "ErrorUploadFetchMetadataAPI": "Памылка пры атрыманні метаданых", + "ErrorUploadFetchMetadataNoResults": "Не ўдалося атрымаць метаданыя – паспрабуйце абнавіць загаловак і/або аўтара", + "ErrorUploadLacksTitle": "Павінен быць загаловак", "HeaderAccount": "Уліковы запіс", - "HeaderAddCustomMetadataProvider": "Дадаць карыстальніцкага пастаўшчыка метададзенных", + "HeaderAddCustomMetadataProvider": "Дадаванне карыстальніцкага пастаўшчыка метаданых", "HeaderAdvanced": "Дадаткова", "HeaderApiKeys": "API-ключы", "HeaderAppriseNotificationSettings": "Налады апавяшчэнняў Apprise", - "HeaderAudioTracks": "Аўдыядарожкі", + "HeaderAudioTracks": "Аўдыятрэкі", "HeaderAudiobookTools": "Сродкі кіравання файламі аўдыякніг", "HeaderAuthentication": "Аўтэнтыфікацыя", "HeaderBackups": "Рэзервовыя копіі", + "HeaderBulkChapterModal": "Дадаць некалькі раздзелаў", "HeaderChangePassword": "Змяніць пароль", "HeaderChapters": "Раздзелы", - "HeaderChooseAFolder": "Выбраць тэчку", + "HeaderChooseAFolder": "Выберыце папку", "HeaderCollection": "Калекцыя", "HeaderCollectionItems": "Элементы калекцыі", "HeaderCover": "Вокладка", "HeaderCurrentDownloads": "Бягучыя спампоўкі", "HeaderCustomMessageOnLogin": "Карыстальніцкае паведамленне пры ўваходзе", - "HeaderCustomMetadataProviders": "Карыстальніцкія крыніцы метададзеных", + "HeaderCustomMetadataProviders": "Карыстальніцкія пастаўшчыкі метаданых", "HeaderDetails": "Падрабязнасці", "HeaderDownloadQueue": "Чарга спамповак", "HeaderEbookFiles": "Файлы электронных кніг", @@ -148,7 +149,7 @@ "HeaderFindChapters": "Знайсці раздзелы", "HeaderIgnoredFiles": "Ігнараваныя файлы", "HeaderItemFiles": "Файлы элементаў", - "HeaderItemMetadataUtils": "Утыліты для метададзеных элементаў", + "HeaderItemMetadataUtils": "Утыліты для метаданых элементаў", "HeaderLastListeningSession": "Апошні сеанс праслухоўвання", "HeaderLatestEpisodes": "Апошнія эпізоды", "HeaderLibraries": "Бібліятэкі", @@ -162,8 +163,8 @@ "HeaderManageTags": "Кіраванне тэгамі", "HeaderMapDetails": "Падрабязнасці адлюстравання", "HeaderMatch": "Супадзенне", - "HeaderMetadataOrderOfPrecedence": "Парадак прыярытэтнасці метададзеных", - "HeaderMetadataToEmbed": "Метададзеныя для ўбудавання", + "HeaderMetadataOrderOfPrecedence": "Парадак прыярытэту метаданых", + "HeaderMetadataToEmbed": "Метаданыя для ўбудавання", "HeaderNewAccount": "Новы ўліковы запіс", "HeaderNewApiKey": "Новы API-ключ", "HeaderNewLibrary": "Новая бібліятэка", @@ -178,8 +179,8 @@ "HeaderPermissions": "Дазволы", "HeaderPlayerQueue": "Чарга прайгравання", "HeaderPlayerSettings": "Налады прайгравальніка", - "HeaderPlaylist": "Спіс прайгравання", - "HeaderPlaylistItems": "Элементы спіса прайгравання", + "HeaderPlaylist": "Плэй-ліст", + "HeaderPlaylistItems": "Элементы плэй-ліста", "HeaderPodcastsToAdd": "Падкасты для дадання", "HeaderPresets": "Прадустаноўкі", "HeaderPreviewCover": "Прадпрагляд вокладкі", @@ -199,6 +200,7 @@ "HeaderSettingsExperimental": "Эксперыментальныя функцыі", "HeaderSettingsGeneral": "Агульныя", "HeaderSettingsScanner": "Сканер", + "HeaderSettingsSecurity": "Бяспека", "HeaderSettingsWebClient": "Вэб-кліент", "HeaderSleepTimer": "Таймер сну", "HeaderStatsLargestItems": "Найбуйнейшыя элементы", @@ -229,8 +231,8 @@ "LabelActivity": "Дзеянне", "LabelAddToCollection": "Дадаць у калекцыю", "LabelAddToCollectionBatch": "Дадаць {0} кніг у калекцыю", - "LabelAddToPlaylist": "Дадаць у спіс прайгравання", - "LabelAddToPlaylistBatch": "Дадаць {0} элементаў у спіс прайгравання", + "LabelAddToPlaylist": "Дадаць у плэй-ліст", + "LabelAddToPlaylistBatch": "Дадаць {0} элементаў у плэй-ліст", "LabelAddedAt": "Дата дабаўлення", "LabelAddedDate": "Дададзена {0}", "LabelAdminUsersOnly": "Толькі для адміністратараў", @@ -246,22 +248,22 @@ "LabelApiKeyUserDescription": "Гэты API-ключ будзе мець тыя ж правы, што і карыстальнік, ад імя якога ён дзейнічае. У журналах гэта будзе выглядаць так, быццам запыт робіць сам карыстальнік.", "LabelApiToken": "Токен API", "LabelAppend": "Дадаць", - "LabelAudioBitrate": "Бітрэйт аўдыё (напрыклад, 128к)", - "LabelAudioChannels": "Аўдыёканалы (1 або 2)", - "LabelAudioCodec": "Аўдыёкодэк", + "LabelAudioBitrate": "Бітрэйт аўдыя (напрыклад, 128к)", + "LabelAudioChannels": "Аўдыяканалы (1 або 2)", + "LabelAudioCodec": "Аўдыякодэк", "LabelAuthor": "Аўтар", "LabelAuthorFirstLast": "Аўтар (Імя Прозвішча)", "LabelAuthorLastFirst": "Аўтар (Прозвішча, Імя)", "LabelAuthors": "Аўтары", "LabelAutoDownloadEpisodes": "Аўтаматычнае спампаванне эпізодаў", - "LabelAutoFetchMetadata": "Аўтаматычнае атрыманне метададзеных", - "LabelAutoFetchMetadataHelp": "Атрыманне звестак пра назву, аўтара і серыю для падыходнага фарматавання перад загрузкай. Далей можа быць неабходна дапоўніць метададзеныя.", + "LabelAutoFetchMetadata": "Аўтаматычнае атрыманне метаданых", + "LabelAutoFetchMetadataHelp": "Атрыманне звестак пра загаловак, аўтара і серыю для спрашчэння запампоўвання. Пасля запампоўвання, магчыма, спатрэбіцца супаставіць дадатковыя метаданыя.", "LabelAutoLaunch": "Аўтазапуск", - "LabelAutoLaunchDescription": "Аўтаматычна перанакіроўваць да пастаўшчыка аўтэнтыфікацыі пры переходзе на старонку ўваходу (ручное пераключэнне праз шлях /login?autoLaunch=0)", + "LabelAutoLaunchDescription": "Аўтаматычна перанакіроўваць да пастаўшчыка аўтэнтыфікацыі пры пераходзе на старонку ўваходу (ручное пераключэнне праз шлях /login?autoLaunch=0)", "LabelAutoRegister": "Аўтарэгістрацыя", "LabelAutoRegisterDescription": "Аўтаматычна ствараць новых карыстальнікаў пасля ўваходу ў сістэму", "LabelBackToUser": "Вярнуцца да карыстальніка", - "LabelBackupAudioFiles": "Рэзервовае капіраванне аўдыёфайлаў", + "LabelBackupAudioFiles": "Рэзервовае капіраванне аўдыяфайлаў", "LabelBackupLocation": "Месцазнаходжанне рэзервовых копій", "LabelBackupsEnableAutomaticBackups": "Аўтаматычнае рэзервовае капіраванне", "LabelBackupsEnableAutomaticBackupsHelp": "Рэзервовыя копіі захаваныя ў /metadata/backups", @@ -273,11 +275,11 @@ "LabelBonus": "Бонус", "LabelBooks": "Кнігі", "LabelButtonText": "Тэкст кнопкі", - "LabelByAuthor": "ад {0}", + "LabelByAuthor": "– {0}", "LabelChangePassword": "Змяніць пароль", "LabelChannels": "Каналы", "LabelChapterCount": "{0} раздзелаў", - "LabelChapterTitle": "Назва раздзела", + "LabelChapterTitle": "Загаловак раздзела", "LabelChapters": "Раздзелы", "LabelChaptersFound": "раздзелаў знойдзена", "LabelClickForMoreInfo": "Націсніце для больш падрабязнай інфармацыі", @@ -294,8 +296,8 @@ "LabelContinueReading": "Працягнуць чытанне", "LabelContinueSeries": "Працягнуць серыі", "LabelCover": "Вокладка", - "LabelCoverImageURL": "URL выявы вокладкі", - "LabelCoverProvider": "Крыніца вокладак", + "LabelCoverImageURL": "URL-адрас відарыса вокладкі", + "LabelCoverProvider": "Пастаўшчык вокладак", "LabelCreatedAt": "Дата стварэння", "LabelCronExpression": "Запіс Cron", "LabelCurrent": "Бягучы", @@ -306,12 +308,13 @@ "LabelDeleteFromFileSystemCheckbox": "Выдаліць з файлавай сістэмы (зніміце галачку, каб выдаліць толькі з базы даных)", "LabelDescription": "Апісанне", "LabelDeselectAll": "Скасаваць выбар усяго", + "LabelDetectedPattern": "Выяўлены ўзор:", "LabelDevice": "Прылада", "LabelDeviceInfo": "Інфармацыя пра прыладу", "LabelDeviceIsAvailableTo": "Прылада даступная для...", "LabelDirectory": "Каталог", - "LabelDiscFromFilename": "Дыск з імя файла", - "LabelDiscFromMetadata": "Дыск па метададзеных", + "LabelDiscFromFilename": "Дыск з назвы файла", + "LabelDiscFromMetadata": "Дыск з метаданых", "LabelDiscover": "Знайсці", "LabelDownload": "Спампаваць", "LabelDownloadNEpisodes": "Спампована {0} эпізодаў", @@ -333,17 +336,19 @@ "LabelEmailSettingsTestAddress": "Тэставы адрас", "LabelEmbeddedCover": "Убудаваная вокладка", "LabelEnable": "Уключыць", - "LabelEncodingBackupLocation": "Рэзервовая копія вашых арыгінальных аўдыёфайлаў будзе захавана ў:", - "LabelEncodingChaptersNotEmbedded": "Раздзелы не ўбудаваны ў шматдарожкавыя аўдыякнігі.", + "LabelEncodingBackupLocation": "Рэзервовая копія арыгінальных аўдыяфайлаў будзе захавана ў:", + "LabelEncodingChaptersNotEmbedded": "Раздзелы не ўбудоўваюцца ў аўдыякнігі з некалькімі трэкамі.", "LabelEncodingClearItemCache": "Пераканайцеся, што перыядычна ачышчаеце кэш элементаў.", - "LabelEncodingFinishedM4B": "Гатовы файл M4B будзе змешчаны ў вашу тэчку з аўдыякнігамі па адрасе:", - "LabelEncodingInfoEmbedded": "Метададзеныя будуць убудаваны ў аўдыядарожкі ўнутры вашай тэчкі з аўдыякнігамі.", + "LabelEncodingFinishedM4B": "Гатовы файл M4B будзе змешчаны ў вашу папку з аўдыякнігамі ў:", + "LabelEncodingInfoEmbedded": "Метаданыя будуць убудаваны ў аўдыятрэкі ўнутры папкі з аўдыякнігамі.", "LabelEncodingStartedNavigation": "Пасля запуску задачы вы можаце перайсці на іншую старонку.", "LabelEncodingTimeWarning": "Кадаванне можа заняць да 30 хвілін.", + "LabelEncodingWarningAdvancedSettings": "Увага: Не абнаўляйце гэтыя налады, калі вы не знаёмыя з параметрамі кадавання ffmpeg.", "LabelEnd": "Канец", "LabelEndOfChapter": "Канец раздзела", "LabelEpisode": "Эпізод", "LabelEpisodeNotLinkedToRssFeed": "Эпізод не звязаны з RSS-стужкай", + "LabelEpisodeTitle": "Загаловак эпізоду", "LabelEpisodeUrlFromRssFeed": "URL эпізоду з RSS-стужкі", "LabelEpisodic": "Эпізадычны", "LabelExample": "Прыклад", @@ -356,23 +361,36 @@ "LabelExplicit": "Відверты", "LabelExportOPML": "Экспарт OPML", "LabelFeedURL": "URL стужкі", - "LabelFetchingMetadata": "Атрыманне метададзеных", + "LabelFetchingMetadata": "Атрыманне метаданых", "LabelFile": "Файл", "LabelFileBirthtime": "Час стварэння файла", "LabelFileModified": "Час змянення файла", - "LabelFilename": "Імя файла", - "LabelFinished": "Скончана", - "LabelFolder": "Тэчка", + "LabelFileModifiedDate": "Зменены {0}", + "LabelFilename": "Назва файла", + "LabelFinished": "Завершана", + "LabelFinishedDate": "Завершана {0}", + "LabelFolder": "Папка", + "LabelFolders": "Папкі", + "LabelFontBold": "Тоўсты", "LabelFontBoldness": "Таўшчыня шрыфта", + "LabelFontFamily": "Сямейства шрыфтоў", + "LabelFontItalic": "Курсіў", "LabelFontScale": "Памер шрыфту", + "LabelFontStrikethrough": "Перакрэслены", + "LabelFormat": "Фармат", + "LabelFull": "Поўны", "LabelGenre": "Жанр", "LabelGenres": "Жанры", "LabelHasEbook": "Мае электронную кнігу", "LabelHasSupplementaryEbook": "Мае дадатковую электронную кнігу", "LabelHideSubtitles": "Схаваць падзагалоўкі", + "LabelHighestPriority": "Найвышэйшы прыярытэт", "LabelHost": "Хост", - "LabelImageURLFromTheWeb": "URL выявы з інтэрнэту", + "LabelHour": "Гадзіна", + "LabelIcon": "Значок", + "LabelImageURLFromTheWeb": "URL-адрас відарыса з інтэрнэту", "LabelInProgress": "У працэсе", + "LabelIncludeInTracklist": "Уключыць у спіс трэкаў", "LabelIncomplete": "Незавершана", "LabelIntervalCustomDailyWeekly": "Карыстальніцкі штодзённы/штотыднёвы", "LabelIntervalEvery12Hours": "Кожныя 12 гадзін", @@ -386,10 +404,11 @@ "LabelInvert": "Інвертаваць", "LabelItem": "Элемент", "LabelLanguage": "Мова", - "LabelLanguageDefaultServer": "Мова сервера па змаўчанні", + "LabelLanguageDefaultServer": "Прадвызначаная мова сервера", "LabelLanguages": "Мовы", "LabelLastBookAdded": "Апошняя дададзеная кніга", "LabelLastBookUpdated": "Апошняя абноўленая кніга", + "LabelLastProgressDate": "Апошні прагрэс: {0}", "LabelLastSeen": "Апошні прагляд", "LabelLastTime": "Апошні раз", "LabelLastUpdate": "Апошняе абнаўленне", @@ -401,40 +420,63 @@ "LabelLibrary": "Бібліятэка", "LabelLibraryFilterSublistEmpty": "Не {0}", "LabelLibraryItem": "Элемент бібліятэкі", - "LabelLibraryName": "Імя бібліятэкі", - "LabelLibrarySortByProgress": "Прагрэс абноўлены", + "LabelLibraryName": "Назва бібліятэкі", + "LabelLibrarySortByProgress": "Прагрэс: апошняе абнаўленне", + "LabelLibrarySortByProgressFinished": "Прагрэс: завершана", + "LabelLibrarySortByProgressStarted": "Прагрэс: пачата", "LabelLimit": "Абмежаванне", "LabelLineSpacing": "Міжрадковы інтэрвал", "LabelListenAgain": "Паслухаць зноў", + "LabelLogLevelDebug": "Debug", + "LabelLogLevelInfo": "Info", + "LabelLogLevelWarn": "Warn", + "LabelLowestPriority": "Найніжэйшы прыярытэт", + "LabelMatchExistingUsersByDescription": "Выкарыстоўваецца для падключэння існуючых карыстальнікаў. Пасля падключэння карыстальнікі будуць супастаўляцца з дапамогай унікальнага ідэнтыфікатара ад пастаўшчыка SSO", "LabelMaxEpisodesToDownload": "Максімальная колькасць эпізодаў для спампоўкі. Выкарыстоўвайце 0 для неабмежаванай колькасці.", "LabelMaxEpisodesToDownloadPerCheck": "Максімальная колькасць новых эпізодаў для спампоўкі за праверку", "LabelMaxEpisodesToKeepHelp": "Значэнне 0 не ўстанаўлівае максімальнага абмежавання. Пасля аўтаматычнай спампоўкі новага эпізоду будзе выдалены самы стары эпізод, калі ў вас больш за X эпізодаў. Пры кожнай новай спампоўцы будзе выдаляцца толькі 1 эпізод.", "LabelMediaPlayer": "Медыяпрайгравальнік", "LabelMediaType": "Тып медыя", - "LabelMetadataOrderOfPrecedenceDescription": "Крыніцы метададзеных з вышэйшым прыярытэтам будуць замяняць крыніцы з ніжэйшым прыярытэтам", - "LabelMetadataProvider": "Пастаўшчык метададзеных", + "LabelMetadataOrderOfPrecedenceDescription": "Крыніцы метаданых з вышэйшым прыярытэтам будуць замяняць крыніцы з ніжэйшым прыярытэтам", + "LabelMetadataProvider": "Пастаўшчык метаданых", "LabelMissing": "Адсутнічае", "LabelMore": "Больш", "LabelMoreInfo": "Больш інфармацыі", - "LabelName": "Імя", + "LabelName": "Назва", "LabelNarrator": "Чытальнік", "LabelNarrators": "Чытальнікі", "LabelNewestAuthors": "Новыя аўтары", "LabelNewestEpisodes": "Новыя эпізоды", - "LabelNoCustomMetadataProviders": "Няма карыстацкіх пастаўшчыкоў метададзеных", - "LabelNotFinished": "Не скончана", + "LabelNextChapters": "Наступныя раздзелы:", + "LabelNextScheduledRun": "Наступны запланаваны запуск", + "LabelNoApiKeys": "Няма ключоў API", + "LabelNoCustomMetadataProviders": "Няма карыстальніцкіх пастаўшчыкоў метаданых", + "LabelNotFinished": "Не завершана", "LabelNotStarted": "Не пачата", + "LabelNotificationAvailableVariables": "Даступныя пераменныя", + "LabelNotificationTitleTemplate": "Шаблон загалоўка", "LabelNotificationsMaxFailedAttemptsHelp": "Апавяшчэнні адключаюцца пасля таго, як не ўдаецца іх адправіць гэтулькі разоў", + "LabelNumberOfBooks": "Колькасць кніг", + "LabelNumberOfChapters": "Колькасць раздзелаў:", "LabelNumberOfEpisodes": "# з эпізодаў", "LabelOpenRSSFeed": "Адкрыць RSS-стужку", + "LabelOverwrite": "Перазапісаць", + "LabelPaginationPageXOfY": "Старонка {0} з {1}", "LabelPassword": "Пароль", "LabelPath": "Шлях", "LabelPermissionsDownload": "Можна спампаваць", - "LabelPlaylists": "Cпісs прайгравання", + "LabelPhotoPathURL": "Шлях/URL-адрас фота", + "LabelPlayerChapterNumberMarker": "{0} з {1}", + "LabelPlaylists": "Плэй-лісты", "LabelPodcast": "Падкаст", + "LabelPodcastSearchRegion": "Рэгіён пошуку падкастаў", + "LabelPodcastType": "Тып падкаста", "LabelPodcasts": "Падкасты", + "LabelPort": "Порт", "LabelPreventIndexing": "Прадухіліць індэксацыю вашай стужкі каталогамі падкастаў iTunes і Google", "LabelProgress": "Прагрэс", + "LabelProvider": "Пастаўшчык", + "LabelProviderAuthorizationValue": "Значэнне загалоўка аўтарызацыі", "LabelPubDate": "Дата публікацыі", "LabelPublishYear": "Год публікацыі", "LabelPublishedDate": "Апублікавана {0}", @@ -448,13 +490,17 @@ "LabelReAddSeriesToContinueListening": "Дадаць серыю зноў у Працягваць слухаць", "LabelRead": "Чытаць", "LabelReadAgain": "Чытаць зноў", + "LabelReadEbookWithoutProgress": "Чытаць электронную кнігу без захавання прагрэсу", "LabelRecentSeries": "Апошнія серыі", "LabelRecentlyAdded": "Нядаўна дададзеныя", + "LabelRecommended": "Рэкамендаваныя", + "LabelRedo": "Узнавіць", + "LabelRegion": "Рэгіён", "LabelRemoveAllMetadataAbs": "Выдаліць усе файлы metadata.abs", "LabelRemoveAllMetadataJson": "Выдаліць усе файлы metadata.json", "LabelRemoveCover": "Выдаліць вокладку", - "LabelRemoveMetadataFile": "Выдаліць файлы метададзеных у тэчках элементаў бібліятэкі", - "LabelRemoveMetadataFileHelp": "Выдаліць усе файлы metadata.json і metadata.abs у вашых {0} тэчках.", + "LabelRemoveMetadataFile": "Выдаліць файлы метаданых у папках элементаў бібліятэкі", + "LabelRemoveMetadataFileHelp": "Выдаліць усе файлы metadata.json і metadata.abs у {0} папках.", "LabelRowsPerPage": "Радкоў на старонку", "LabelSearchTerm": "Пошукавы запыт", "LabelSearchTitle": "Пошук па загалоўку", @@ -464,6 +510,7 @@ "LabelSelectAll": "Выбраць усё", "LabelSelectAllEpisodes": "Выбраць усе эпізоды", "LabelSelectEpisodesShowing": "Выбраць {0} эпізодаў для паказу", + "LabelSelectUser": "Выберыце карыстальніка", "LabelSelectUsers": "Выбраць карыстальнікаў", "LabelSendEbookToDevice": "Адправіць электронную кнігу на...", "LabelSequence": "Паслядоўнасць", @@ -477,40 +524,48 @@ "LabelSetEbookAsSupplementary": "Зрабіць дадатковым", "LabelSettingsAllowIframe": "Дазволіць убудоўванне ў iframe", "LabelSettingsAudiobooksOnly": "Толькі аўдыякнігі", - "LabelSettingsAudiobooksOnlyHelp": "Уключэнне гэтай налады будзе ігнараваць файлы электронных кніг, калі толькі яны не знаходзяцца ў тэчцы з аўдыякнігамі. У такім выпадку яны будуць пазначаны як дадатковыя электронныя кнігі.", + "LabelSettingsAudiobooksOnlyHelp": "Пры ўключэнні гэтай налады файлы электронных кніг будуць ігнаравацца, калі толькі яны не знаходзяцца ў папцы з аўдыякнігамі. У такім выпадку яны будуць пазначаны як дадатковыя электронныя кнігі.", "LabelSettingsBookshelfViewHelp": "Рэалістычны дызайн з драўлянымі паліцамі", - "LabelSettingsEnableWatcherForLibrary": "Аўтаматычна правяраць бібліятэку на змены", + "LabelSettingsDateFormat": "Фарматы даты", + "LabelSettingsEnableWatcherForLibrary": "Аўтаматычна сачыць за зменамі ў бібліятэцы", "LabelSettingsEnableWatcherHelp": "Адключае аўтаматычнае дадаванне/абнаўленне элементаў пры выяўленні змен у файлах. *Патрабуецца перазапуск сервера", "LabelSettingsEpubsAllowScriptedContent": "Дазволіць скрыптавы кантэнт у EPUB", "LabelSettingsEpubsAllowScriptedContentHelp": "Дазволіць EPUB-файлам выконваць скрыпты. Рэкамендуецца пакінуць гэтую наладу выключанай, калі вы не давяраеце крыніцы EPUB-файлаў.", "LabelSettingsExperimentalFeatures": "Эксперыментальныя функцыі", "LabelSettingsExperimentalFeaturesHelp": "Функцыі ў распрацоўцы, для якіх вашы водгукі і дапамога ў тэставанні будуць карыснымі. Націсніце, каб адкрыць абмеркаванне на GitHub.", "LabelSettingsFindCovers": "Знайсці вокладкі", - "LabelSettingsFindCoversHelp": "Калі ў вашай аўдыякнізе няма ўбудаванай вокладкі або выявы вокладкі ў тэчцы, сканер паспрабуе знайсці вокладку.
Заўвага: гэта павялічыць час сканавання", + "LabelSettingsFindCoversHelp": "Калі ў вашай аўдыякнізе няма ўбудаванай вокладкі або відарыса вокладкі ў папцы, сканер паспрабуе знайсці вокладку.
Заўвага: гэта павялічыць час сканіравання", "LabelSettingsHideSingleBookSeries": "Схаваць серыі з адной кнігай", "LabelSettingsHideSingleBookSeriesHelp": "Серыі, якія змяшчаюць толькі адну кнігу, будуць схаваны са старонкі серый і паліц на галоўнай старонцы.", "LabelSettingsHomePageBookshelfView": "Галоўная старонка выкарыстоўвае выгляд кніжнай паліцы", "LabelSettingsLibraryBookshelfView": "Бібліятэка выкарыстоўвае выгляд кніжнай паліцы", + "LabelSettingsLibraryMarkAsFinishedPercentComplete": "Працэнт завяршэння большы за", "LabelSettingsLibraryMarkAsFinishedTimeRemaining": "Час, што застаўся, менш за (секунды)", "LabelSettingsLibraryMarkAsFinishedWhen": "Пазначыць элемент медыя як скончаны, калі", "LabelSettingsOnlyShowLaterBooksInContinueSeries": "Пропусціць папярэднія кнігі ў \"Працягнуць серыю\"", "LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "Палка \"Працягнуць серыю\" на галоўнай старонцы паказвае першую не пачатую кнігу ў серыях, дзе завершана хаця б адна кніга і няма кніг у працэсе чытання. Уключэнне гэтай налады дазволіць працягваць серыю з самай апошняй завершанай кнігі замест першай не пачатай.", - "LabelSettingsParseSubtitles": "Разабраць падзагалоўкі", - "LabelSettingsParseSubtitlesHelp": "Выдзяляць падзагаловак з назваў тэчак аўдыякніг.
Падзагаловак павінен быць аддзелены знакам \" - \".
Напрыклад, \"Назва кнігі - Падзагаловак тут\" мае падзагаловак \"Падзагаловак тут\"", - "LabelSettingsPreferMatchedMetadata": "Аддаваць перавагу супадаючым метададзеным", - "LabelSettingsPreferMatchedMetadataHelp": "Супадаючыя дадзеныя будуць замяняць дэталі элемента пры выкарыстанні функцыі Хуткі пошук. Па змаўчанні Хуткі пошук запаўняе толькі адсутныя дэталі.", - "LabelSettingsStoreCoversWithItemHelp": "Па змаўчанні вокладкі захоўваюцца ў /metadata/items, уключэнне гэтай опцыі забяспечыць захоўванне вокладак у тэчцы элемента вашай бібліятэкі. Захоўвацца будзе толькі адзін файл з назвай \"cover\"", - "LabelSettingsStoreMetadataWithItem": "Захоўваць метададзеныя разам з элементам", - "LabelSettingsStoreMetadataWithItemHelp": "Па змаўчанні метададзеныя захоўваюцца ў /metadata/items. Уключэнне гэтай опцыі забяспечыць захоўванне файлаў метададзеных у тэчках элементаў вашай бібліятэкі", + "LabelSettingsParseSubtitles": "Аналізаваць падзагалоўкі", + "LabelSettingsParseSubtitlesHelp": "Выдзяляць падзагаловак з назваў папак аўдыякніг.
Падзагаловак павінен быць аддзелены сімвалам \" - \".
Напрыклад, \"Назва кнігі - Падзагаловак тут\" мае падзагаловак \"Падзагаловак тут\"", + "LabelSettingsPreferMatchedMetadata": "Аддаваць перавагу супадаючым метаданым", + "LabelSettingsPreferMatchedMetadataHelp": "Супадаючыя даныя будуць замяняць звесткі элемента пры выкарыстанні функцыі Хуткі пошук. Прадвызначана Хуткі пошук запаўняе толькі адсутныя звесткі.", + "LabelSettingsSortingIgnorePrefixesHelp": "напрыклад, для прэфікса \"the\" загаловак кнігі \"The Book Title\" будзе сартавацца як \"Book Title, The\"", + "LabelSettingsSquareBookCovers": "Выкарыстоўваць квадратныя вокладкі кніг", + "LabelSettingsStoreCoversWithItemHelp": "Прадвызначана вокладкі захоўваюцца ў /metadata/items, уключэнне гэтай опцыі забяспечыць захоўванне вокладак у папцы элемента бібліятэкі. Захоўвацца будзе толькі адзін файл з назвай \"cover\"", + "LabelSettingsStoreMetadataWithItem": "Захоўваць метаданыя разам з элементам", + "LabelSettingsStoreMetadataWithItemHelp": "Прадвызначана метаданыя захоўваюцца ў /metadata/items. Пры ўключэнні гэтай опцыі файлаў метаданых будуць захоўвацца ў папках элементаў бібліятэкі", "LabelSettingsTimeFormat": "Фармат часу", + "LabelShare": "Абагуліць", "LabelShareDownloadableHelp": "Дазваляе карыстальнікам, якія маюць спасылку на доступ, спампаваць ZIP-файл элемента бібліятэкі.", "LabelShowAll": "Паказаць усё", + "LabelShowSeconds": "Паказваць секунды", "LabelShowSubtitles": "Паказаць падзагалоўкі", "LabelSize": "Памер", "LabelSleepTimer": "Таймер сну", + "LabelSortAscending": "Па ўзрастанні", + "LabelSortDescending": "Па ўбыванні", "LabelStart": "Пачаць", "LabelStartTime": "Час пачатку", - "LabelStatsAudioTracks": "Аўдыядарожкі", + "LabelStatsAudioTracks": "Аўдыятрэкі", "LabelStatsAuthors": "Аўтары", "LabelStatsBestDay": "Лепшы дзень", "LabelStatsDailyAverage": "У сярэднім за дзень", @@ -518,7 +573,7 @@ "LabelStatsDaysListened": "Дзён праслухана", "LabelStatsHours": "Гадзін", "LabelStatsInARow": "без перапынку", - "LabelStatsItemsFinished": "Скончаныя элементы", + "LabelStatsItemsFinished": "Элементаў завершана", "LabelStatsItemsInLibrary": "Элементаў у бібліятэцы", "LabelStatsMinutes": "хвілін", "LabelStatsMinutesListening": "Хвілін праслухоўвання", @@ -539,6 +594,7 @@ "LabelTheme": "Тэма", "LabelThemeDark": "Цёмная", "LabelThemeLight": "Светлая", + "LabelThemeSepia": "Сепія", "LabelTimeBase": "Часавая база", "LabelTimeDurationXHours": "{0} гадзін", "LabelTimeDurationXMinutes": "{0} хвілін", @@ -549,21 +605,22 @@ "LabelTimeListenedToday": "Час праслухоўвання сёння", "LabelTimeRemaining": "Засталося {0}", "LabelTimeToShift": "Час зрушэння ў секундах", - "LabelTitle": "Назва", - "LabelToolsEmbedMetadata": "Убудаваць метададзеныя", - "LabelToolsEmbedMetadataDescription": "Убудаваць метададзеныя ў аўдыёфайлы, уключаючы выяву вокладкі і раздзелы.", - "LabelToolsMakeM4bDescription": "Стварыць аўдыёкнігу ў фармаце .M4B з убудаванымі метададзенымі, выявай вокладкі і раздзеламі.", - "LabelToolsSplitM4bDescription": "Стварэнне MP3 з M4B, падзеленага па раздзелах, з убудаванымі метададзенымі, выявай вокладкі і раздзеламі.", + "LabelTitle": "Загаловак", + "LabelToolsEmbedMetadata": "Убудаваць метаданыя", + "LabelToolsEmbedMetadataDescription": "Убудаваць метаданыя ў аўдыяфайлы, уключаючы відарыс вокладкі і раздзелы.", + "LabelToolsM4bEncoder": "Кадавальнік M4B", + "LabelToolsMakeM4bDescription": "Стварыць аўдыякнігу ў фармаце .M4B з убудаванымі метаданымі, відарысам вокладкі і раздзеламі.", + "LabelToolsSplitM4bDescription": "Стварэнне MP3 з M4B, падзеленага па раздзелах, з убудаванымі метаданымі, відарысам вокладкі і раздзеламі.", "LabelTotalDuration": "Агульная працягласць", "LabelTotalTimeListened": "Агульны час праслухоўвання", - "LabelTrackFromFilename": "Дарожка з імя файла", - "LabelTrackFromMetadata": "Дарожка з метададзеных", - "LabelTracks": "Дарожкі", - "LabelTracksMultiTrack": "Шматдарожкавы", - "LabelTracksNone": "Няма дарожак", - "LabelTracksSingleTrack": "Аднадарожкавы", + "LabelTrackFromFilename": "Трэк з назвы файла", + "LabelTrackFromMetadata": "Трэк з метаданых", + "LabelTracks": "Трэкі", + "LabelTracksMultiTrack": "Некалькі трэкаў", + "LabelTracksNone": "Няма трэкаў", + "LabelTracksSingleTrack": "Адзін трэк", "LabelType": "Тып", - "LabelUndo": "Адмяніць", + "LabelUndo": "Адрабіць", "LabelUnknown": "Невядома", "LabelUnknownPublishDate": "Невядомая дата публікацыі", "LabelUpdateCover": "Абнавіць вокладку", @@ -571,13 +628,13 @@ "LabelUpdateDetails": "Абнавіць падрабязнасці", "LabelUpdateDetailsHelp": "Дазволіць замену існуючых падрабязнасцей для выбраных кніг пры выяўленні адпаведнасці", "LabelUpdatedAt": "Абноўлена ў", - "LabelUploaderDragAndDrop": "Перацягвайце і скідайце файлы або тэчкі", + "LabelUploaderDragAndDrop": "Перацягніце файлы або папкі", "LabelUploaderDragAndDropFilesOnly": "Перацягвайце і скідайце файлы", "LabelUploaderDropFiles": "Скідайце файлы", - "LabelUploaderItemFetchMetadataHelp": "Аўтаматычна атрымліваць назву, аўтара і серыю", + "LabelUploaderItemFetchMetadataHelp": "Аўтаматычна атрымліваць загаловак, аўтара і серыю", "LabelUseAdvancedOptions": "Выкарыстоўваць пашыраныя параметры", - "LabelUseChapterTrack": "Выкарыстоўваць дарожку раздзелаў", - "LabelUseFullTrack": "Выкарыстоўваць поўную дарожку", + "LabelUseChapterTrack": "Выкарыстоўваць трэк раздзела", + "LabelUseFullTrack": "Выкарыстоўваць увесь трэк", "LabelUser": "Карыстальнік", "LabelUsername": "Імя карыстальніка", "LabelValue": "Значэнне", @@ -587,8 +644,8 @@ "LabelViewPlayerSettings": "Праглядзець налады прайгравальніка", "LabelViewQueue": "Праглядзець чаргу прайгравальніка", "LabelVolume": "Гучнасць", - "LabelWebRedirectURLsDescription": "Аўтарызуйце гэтыя URL у вашым OAuth-правайдары для перанакіравання ў вэб-дадатак пасля ўваходу:", - "LabelWebRedirectURLsSubfolder": "Падтэчка для URL-перанакіраванняў", + "LabelWebRedirectURLsDescription": "Аўтарызуйце гэтыя URL-адрасы ў вашым пастаўшчыку OAuth для перанакіравання ў вэб-праграму пасля ўваходу:", + "LabelWebRedirectURLsSubfolder": "Падпапка для URL-адрасоў перанакіравання", "LabelWeekdaysToRun": "Дні тыдня для запуску", "LabelXBooks": "{0} кніг", "LabelXItems": "{0} элементаў", @@ -596,80 +653,115 @@ "LabelYearReviewShow": "Азнаёміцца з вынікамі года", "LabelYourAudiobookDuration": "Працягласць вашай аўдыякнігі", "LabelYourBookmarks": "Вашы закладкі", - "LabelYourPlaylists": "Вашы спісы прайгравання", + "LabelYourPlaylists": "Вашы плэй-лісты", "LabelYourProgress": "Ваш прагрэс", "MessageAddToPlayerQueue": "Дадаць у чаргу прайгравальніка", "MessageAppriseDescription": "Каб выкарыстоўваць гэтую функцыю, вам спатрэбіцца запусціць асобнік Apprise API або API, які будзе апрацоўваць тыя ж запыты.
URL Apprise API павінен быць поўным шляхам для адпраўкі апавяшчэння, напрыклад, калі ваш API працуе па адрасе http://192.168.1.1:8337, то вы павінны ўвесці http://192.168.1.1:8337/notify.", - "MessageBackupsDescription": "Рэзервовыя копіі ўключаюць карыстальнікаў, іх прагрэс, падрабязнасці элементаў бібліятэкі, налады сервера і выявы, якія захоўваюцца ў /metadata/items і /metadata/authors. Рэзервовыя копіі не ўключаюць файлы, якія захоўваюцца ў вашых тэчках бібліятэкі.", + "MessageAuthenticationOIDCChangesRestart": "Перазапусціце сервер пасля захавання, каб прымяніць змены OIDC.", + "MessageBackupsDescription": "Рэзервовыя копіі ўключаюць карыстальнікаў, іх прагрэс, падрабязнасці элементаў бібліятэкі, налады сервера і відарысы, якія захоўваюцца ў /metadata/items і /metadata/authors. Рэзервовыя копіі не ўключаюць файлы, якія захоўваюцца ў папках бібліятэкі.", "MessageBackupsLocationEditNote": "Заўвага: Абнаўленне месцазнаходжання рэзервовых копій не перамяшчае і не змяняе існуючыя рэзервовыя копіі", "MessageBackupsLocationNoEditNote": "Заўвага: Месцазнаходжанне рэзервовых копій задаецца праз зменную асяроддзя і не можа быць зменена тут.", "MessageBackupsLocationPathEmpty": "Шлях да месцазнаходжання рэзервовых копій не можа быць пустым", - "MessageBatchEditPopulateMapDetailsAllHelp": "Запоўніце ўключаныя палі дадзенымі з усіх элементаў. Палі з некалькімі значэннямі будуць аб'яднаны", - "MessageBatchEditPopulateMapDetailsItemHelp": "Запоўніце ўключаныя палі падрабязнасцей карты дадзенымі з гэтага элемента", - "MessageBatchQuickMatchDescription": "Хуткі пошук паспрабуе дадаць адсутныя вокладкі і метададзеныя для выбраных элементаў. Уключыце ніжэй выкладзеныя опцыі, каб дазволіць Хуткаму пошуку замяняць існуючыя вокладкі і/або метададзеныя.", + "MessageBatchEditPopulateMapDetailsAllHelp": "Запоўніце ўключаныя палі данымі з усіх элементаў. Палі з некалькімі значэннямі будуць аб'яднаны", + "MessageBatchEditPopulateMapDetailsItemHelp": "Запоўніце ўключаныя палі падрабязнасцей карты данымі з гэтага элемента", + "MessageBatchQuickMatchDescription": "Хуткі пошук паспрабуе дадаць адсутныя вокладкі і метаданыя для выбраных элементаў. Уключыце параметры ніжэй, каб дазволіць Хуткаму пошуку замяняць існуючыя вокладкі і/або метаданыя.", + "MessageBookshelfNoCollections": "Вы пакуль не стварылі ніводнай калекцый", "MessageBookshelfNoRSSFeeds": "Няма адкрытых RSS-стужак", + "MessageBookshelfNoResultsForFilter": "Няма вынікаў для фільтра \"{0}: {1}\"", + "MessageBookshelfNoResultsForQuery": "Няма вынікаў па запыце", + "MessageChapterErrorFirstNotZero": "Першы раздзел павінен пачынацца з 0", "MessageChapterErrorStartGteDuration": "Няправільны час пачатку: ён павінен быць меншым за працягласць аўдыякнігі", "MessageChapterErrorStartLtPrev": "Няправільны час пачатку: ён павінен быць большым або роўным часу пачатку папярэдняга раздзела", + "MessageChaptersNotFound": "Раздзелы не знойдзены", + "MessageCheckingCron": "Праверка cron...", "MessageConfirmCloseFeed": "Вы ўпэўнены, што жадаеце закрыць гэтую стужку?", - "MessageConfirmDeleteMetadataProvider": "Ці ўпэўненыя вы, што жадаеце выдаліць карыстацкага пастаўшчыка метададзеных \"{0}\"?", - "MessageConfirmEmbedMetadataInAudioFiles": "Ці ўпэўненыя вы, што жадаеце ўбудаваць метададзеныя ў {0} аўдыёфайлаў?", - "MessageConfirmPurgeCache": "Ачышчэнне кэша выдаліць увесь каталог па адрасе /metadata/cache.

Ці сапраўды вы жадаеце выдаліць каталог кэша?", - "MessageConfirmPurgeItemsCache": "Ачышчэнне кэша элементаў выдаліць увесь каталог па адрасе /metadata/cache/items.
Вы ўпэўнены?", + "MessageConfirmDeleteApiKey": "Вы ўпэўнены, што хочаце выдаліць ключ API \"{0}\"?", + "MessageConfirmDeleteBackup": "Вы ўпэўнены, што хочаце выдаліць рэзервовую копію для {0}?", + "MessageConfirmDeleteFile": "Будзе выдалены файл з файлавай сістэмы. Вы ўпэўнены?", + "MessageConfirmDeleteLibrary": "Вы ўпэўнены, што хочаце назаўжды выдаліць бібліятэку \"{0}\"?", + "MessageConfirmDeleteLibraryItem": "Будзе выдалены элемент бібліятэкі з базы даных і файлавай сістэмы. Вы ўпэўнены?", + "MessageConfirmDeleteLibraryItems": "Будзе выдалена {0} элементаў бібліятэкі з базы даных і файлавай сістэмы. Вы ўпэўнены?", + "MessageConfirmDeleteMetadataProvider": "Вы ўпэўнены, што хочаце выдаліць карыстальніцкага пастаўшчыка метаданых \"{0}\"?", + "MessageConfirmDeleteNotification": "Вы ўпэўнены, што хочаце выдаліць гэта апавяшчэнне?", + "MessageConfirmDeleteSession": "Вы ўпэўнены, што хочаце выдаліць гэты сеанс?", + "MessageConfirmEmbedMetadataInAudioFiles": "Вы ўпэўнены, што хочаце ўбудаваць метаданыя ў {0} аўдыяфайлаў?", + "MessageConfirmPurgeCache": "Ачышчэнне кэшу выдаліць увесь каталог па адрасе /metadata/cache.

Вы сапраўды хочаце выдаліць каталог кэшу?", + "MessageConfirmPurgeItemsCache": "Ачышчэнне кэшу элементаў выдаліць увесь каталог па адрасе /metadata/cache/items.
Вы ўпэўнены?", "MessageConfirmQuickMatchEpisodes": "Хуткае супадзенне эпізодаў перазапіша дэталі, калі супадзенне будзе знойдзена. Будуць абноўлены толькі эпізоды, якія не супадаюць. Вы ўпэўнены?", + "MessageConfirmRemoveAllChapters": "Вы ўпэўнены, што хочаце выдаліць усе раздзелы?", + "MessageConfirmRemoveAuthor": "Вы ўпэўнены, што хочаце выдаліць аўтара \"{0}\"?", + "MessageConfirmRemoveCollection": "Вы ўпэўнены, што хочаце выдаліць калекцыю \"{0}\"?", "MessageConfirmRemoveListeningSessions": "Вы ўпэўнены, што жадаеце выдаліць {0} сеансаў праслухоўвання?", - "MessageConfirmRemoveMetadataFiles": "Ці ўпэўненыя вы, што жадаеце выдаліць усе файлы метададзеных{0} у тэчках элементаў вашай бібліятэкі?", - "MessageConfirmRemovePlaylist": "Вы ўпэўненыя, што жадаеце выдаліць свой спіс прайгравання \"{0}\"?", + "MessageConfirmRemoveMetadataFiles": "Вы ўпэўнены, што хочаце выдаліць усе файлы metadata.{0} у папках элементаў бібліятэкі?", + "MessageConfirmRemovePlaylist": "Вы ўпэўнены, што хочаце выдаліць плэй-ліст \"{0}\"?", + "MessageConfirmRenameGenre": "Вы ўпэўнены, што хочаце перайменаваць жанр \"{0}\" на \"{1}\" для ўсіх элементаў?", + "MessageConfirmRenameGenreMergeNote": "Заўвага: Гэты жанр ужо існуе, таму яны будуць аб'яднаны.", + "MessageConfirmRenameGenreWarning": "Увага! Падобны жанр з іншым рэгістрам літар ужо існуе — \"{0}\".", + "MessageConfirmRenameTag": "Вы ўпэўнены, што хочаце перайменаваць тэг \"{0}\" на \"{1}\" для ўсіх элементаў?", + "MessageConfirmRenameTagMergeNote": "Заўвага: Гэты тэг ужо існуе, таму яны будуць аб'яднаны.", + "MessageConfirmRenameTagWarning": "Увага! Падобны тэг з іншым рэгістрам ужо існуе: \"{0}\".", + "MessageConfirmResetProgress": "Вы ўпэўнены, што хочаце скінуць свой прагрэс?", "MessageConfirmSendEbookToDevice": "Вы ўпэўнены, што хочаце адправіць {0} электронную кнігу \"{1}\" на прыладу \"{2}\"?", "MessageDownloadingEpisode": "Спампоўка эпізоду", - "MessageEmbedQueue": "У чарзе на ўбудаванне метададзеных (у чарзе {0})", + "MessageDragFilesIntoTrackOrder": "Перацягніце файлы ў правільным парадку трэкаў", + "MessageEmbedQueue": "У чарзе на ўбудаванне метаданых (у чарзе {0})", "MessageEpisodesQueuedForDownload": "{0} эпізод(аў) у чарзе для спампоўкі", "MessageEreaderDevices": "Каб забяспечыць дастаўку электронных кніг, вам можа спатрэбіцца дадаць вышэйзгаданы адрас электроннай пошты як дазволенага адпраўніка для кожнай прылады, пералічанай ніжэй.", "MessageFeedURLWillBe": "URL стужкі будзе {0}", "MessageFetching": "Атрыманне...", + "MessageImportantNotice": "Важная заўвага!", "MessageInvalidAsin": "Няправільны ASIN", "MessageItemsUpdated": "{0} элементаў абноўлена", + "MessageJoinUsOn": "Далучайцеся да нас у", "MessageLoading": "Загрузка...", + "MessageLoadingFolders": "Загрузка папак...", "MessageLogsDescription": "Журналы захоўваюцца ў каталогу /metadata/logs у фармаце JSON. Журналы памылак захоўваюцца ў файле /metadata/logs/crashlogs.txt.", - "MessageMapChapterTitles": "Супаставіць назвы раздзелаў з вашымі існуючымі раздзеламі аўдыякнігі без змянення часовых метак", - "MessageMarkAsFinished": "Пазначыць як скончана", + "MessageMapChapterTitles": "Супаставіць загалоўкі раздзелаў з існуючымі раздзеламі аўдыякнігі без змянення пазнак часу", + "MessageMarkAsFinished": "Пазначыць як завершаную", + "MessageMatchBooksDescription": "паспрабуе параўнаць кнігі ў бібліятэцы з кнігай ад выбранай пошукавай сістэмы і запоўніць пустыя палі і вокладку. Не перазапісвае звесткі.", + "MessageNoAudioTracks": "Няма аўдыятрэкаў", "MessageNoBookmarks": "Няма закладак", "MessageNoChapters": "Няма раздзелаў", "MessageNoCollections": "Няма калекцый", - "MessageNoDownloadsInProgress": "Зараз няма актыўных спамповак", + "MessageNoDownloadsInProgress": "Зараз няма актыўных спампованняў", "MessageNoDownloadsQueued": "Няма спамповак у чарзе", "MessageNoItems": "Няма элементаў", "MessageNoItemsFound": "Элементы не знойдзены", "MessageNoListeningSessions": "Няма сеансаў праслухоўвання", + "MessageNoLogs": "Няма журналаў", "MessageNoMediaProgress": "Няма прагрэсу медыя", "MessageNoPodcastFeed": "Няправільны падкаст: Няма стужкі", "MessageNoPodcastsFound": "Падкасты не знойдзены", "MessageNoTasksRunning": "Няма запушчаных задач", "MessageNoUpdatesWereNecessary": "Абнаўленні не патрабаваліся", - "MessageNoUserPlaylists": "У вас няма спісаў прайгравання", - "MessageNoUserPlaylistsHelp": "Спісы прайгравання прыватныя. Толькі карыстальнік, які іх стварыў, можа іх бачыць.", - "MessageOpmlPreviewNote": "Заўвага: гэта папярэдні прагляд разабранага файла OPML. Фактычная назва падкаста будзе ўзятая з RSS-стужкі.", - "MessagePlaylistCreateFromCollection": "Стварыць спіс прайгравання з калекцыі", + "MessageNoUserPlaylists": "У вас няма плэй-лістоў", + "MessageNoUserPlaylistsHelp": "Плэй-лісты прыватныя. Толькі карыстальнік, які іх стварыў, можа іх бачыць.", + "MessageOpmlPreviewNote": "Заўвага: гэта перадпрагляд прааналізаванага файла OPML. Фактычны загаловак падкаста будзе ўзяты з RSS-ленты.", + "MessagePauseChapter": "Прыпыніць прайграванне раздзела", + "MessagePlaylistCreateFromCollection": "Стварыць плэй-ліст з калекцыі", "MessagePodcastHasNoRSSFeedForMatching": "У падкаста няма URL RSS-стужкі для супадзення", "MessagePodcastSearchField": "Увядзіце пошукавы запыт або URL RSS-стужкі", - "MessageQuickMatchDescription": "Запоўніць пустыя дэталі элемента і вокладку першым вынікам супадзення з '{0}'. Не замяняе дэталі, калі опцыя «Аддаваць перавагу супадаючым метададзеным» на серверы не ўключана.", + "MessageQuickEmbedInProgress": "Выконваецца хуткае ўбудаванне", + "MessageQuickMatchDescription": "Запоўніць пустыя звесткі элемента і вокладку першым вынікам супадзення з '{0}'. Не замяняе звесткіі, калі параметр \"Аддаваць перавагу супадаючым метаданым\" на серверы не ўключана.", "MessageReportBugsAndContribute": "Паведамляйце пра памылкі, прапануйце новыя функцыі і ўдзельнічайце на", - "MessageRestoreBackupWarning": "Аднаўленне рэзервовай копіі перазапіша ўсю базу даных, размешчаную ў /config, а таксама выявы вокладкі ў /metadata/items і /metadata/authors.

Рэзервовыя копіі не змяняюць файлы ў вашых тэчках бібліятэкі. Калі вы ўключылі наладкі сервера для захоўвання воклак і метададзеных у тэчках бібліятэкі, гэтыя файлы не будуць захаваныя ў рэзервовых копіях і не зменяцца.

Усе кліенты, якія карыстаюцца вашым серверам, будуць аўтаматычна абноўлены.", + "MessageRestoreBackupWarning": "Аднаўленне рэзервовай копіі перазапіша ўсю базу даных, размешчаную ў /config, а таксама відарысы вокладкі ў /metadata/items і /metadata/authors.

Рэзервовыя копіі не змяняюць файлы ў папках бібліятэкі. Калі вы ўключылі налады сервера для захоўвання воклак і метаданых у папках бібліятэкі, гэтыя файлы не будуць захаваныя ў рэзервовых копіях і не зменяцца.

Усе кліенты, якія карыстаюцца вашым серверам, будуць аўтаматычна абноўлены.", "MessageScheduleRunEveryWeekdayAtTime": "Выконваць кожныя {0} у {1}", + "MessageSetChaptersFromTracksDescription": "Задаць раздзелы, выкарыстоўваючы кожны аўдыяфайл у якасці раздзела і назву аўдыяфайла ў якасці загалоўка раздзела", "MessageStartPlaybackAtTime": "Пачаць прайграванне для \"{0}\" з {1}?", - "MessageTaskAudioFileNotWritable": "Аўдыёфайл \"{0}\" недаступны для запісу", + "MessageTaskAudioFileNotWritable": "Аўдыяфайл \"{0}\" недаступны для запісу", "MessageTaskCanceledByUser": "Задача скасавана карыстальнікам", "MessageTaskDownloadingEpisodeDescription": "Спампоўка эпізоду \"{0}\"", - "MessageTaskEmbeddingMetadata": "Убудаванне метададзеных", - "MessageTaskEmbeddingMetadataDescription": "Убудаванне метададзеных у аўдыёкнігу \"{0}\"", + "MessageTaskEmbeddingMetadata": "Убудаванне метаданых", + "MessageTaskEmbeddingMetadataDescription": "Убудаванне метаданых у аўдыякнігу \"{0}\"", "MessageTaskEncodingM4b": "Кадаванне M4B", "MessageTaskEncodingM4bDescription": "Кадаванне аўдыякнігі \"{0}\" у адзін файл m4b", "MessageTaskFailed": "Не ўдалося", - "MessageTaskFailedToBackupAudioFile": "Не ўдалося зрабіць рэзервовую копію аўдыёфайла \"{0}\"", - "MessageTaskFailedToCreateCacheDirectory": "Не ўдалося стварыць каталог кэша", - "MessageTaskFailedToEmbedMetadataInFile": "Не ўдалося ўбудаваць метададзеныя ў файл \"{0}\"", - "MessageTaskFailedToMergeAudioFiles": "Не ўдалося аб’яднаць аўдыёфайлы", + "MessageTaskFailedToBackupAudioFile": "Не ўдалося зрабіць рэзервовую копію аўдыяфайла \"{0}\"", + "MessageTaskFailedToCreateCacheDirectory": "Не ўдалося стварыць каталог кэшу", + "MessageTaskFailedToEmbedMetadataInFile": "Не ўдалося ўбудаваць метаданыя ў файл \"{0}\"", + "MessageTaskFailedToMergeAudioFiles": "Не ўдалося аб’яднаць аўдыяфайлы", "MessageTaskFailedToMoveM4bFile": "Не ўдалося перамясціць файл m4b", - "MessageTaskFailedToWriteMetadataFile": "Не ўдалося захаваць файл метададзеных", + "MessageTaskFailedToWriteMetadataFile": "Не ўдалося захаваць файл метаданых", "MessageTaskMatchingBooksInLibrary": "Пошук супадзенняў кніг у бібліятэцы \"{0}\"", "MessageTaskNoFilesToScan": "Няма файлаў для сканавання", "MessageTaskOpmlImport": "Імпарт OPML", @@ -681,8 +773,8 @@ "MessageTaskOpmlImportFeedPodcastExists": "Падкаст ужо існуе па гэтым шляху", "MessageTaskOpmlImportFeedPodcastFailed": "Не ўдалося стварыць падкаст", "MessageTaskOpmlImportFinished": "Дададзена {0} падкастаў", - "MessageTaskOpmlParseFailed": "Не ўдалося разабраць файл OPML", - "MessageTaskOpmlParseFastFail": "Неправільны файл OPML: тэг не знойдзены АБО тэг не знойдзены", + "MessageTaskOpmlParseFailed": "Не ўдалося прааналізаваць файл OPML", + "MessageTaskOpmlParseFastFail": "Памылковы файл OPML: тэг не знойдзены АБО тэг не знойдзены", "MessageTaskOpmlParseNoneFound": "У файле OPML не знойдзена стужак", "MessageTaskScanItemsAdded": "{0} дададзена", "MessageTaskScanItemsMissing": "{0} адсутнічае", @@ -694,32 +786,80 @@ "NoteChapterEditorTimes": "Заўвага: Час пачатку першага раздзела павінен заставацца 0:00, а час пачатку апошняга раздзела не можа перавышаць працягласць гэтай аўдыякнігі.", "NoteRSSFeedPodcastAppsHttps": "Папярэджанне: большасць праграм для падкастаў патрабуюць, каб URL RSS-стужкі выкарыстоўваў HTTPS", "NoteRSSFeedPodcastAppsPubDate": "Папярэджанне: адзін ці больш вашых эпізодаў не маюць даты публікацыі. Некаторыя праграмы для падкастаў патрабуюць гэтага.", - "NoteUploaderFoldersWithMediaFiles": "Тэчкі з медыяфайламі будуць апрацоўвацца як асобныя элементы бібліятэкі.", + "NoteUploaderFoldersWithMediaFiles": "Папкі з медыяфайламі будуць апрацоўвацца як асобныя элементы бібліятэкі.", "NotificationOnEpisodeDownloadedDescription": "Выклікаецца, калі эпізод падкаста аўтаматычна спампоўваецца", - "PlaceholderNewPlaylist": "Імя новага спіса прайгравання", + "NotificationOnTestDescription": "Падзея для тэсціравання сістэмы апавяшчэнняў", + "PlaceholderBulkChapterInput": "Увядзіце загаловак раздзела або выкарыстоўвайце нумарацыю (напрыклад, «Эпізод 1», «Раздзел 10», «1.»)", + "PlaceholderNewCollection": "Назва новай калекцыі", + "PlaceholderNewFolderPath": "Шлях да новай папкі", + "PlaceholderNewPlaylist": "Назва новага плэй-ліста", + "PlaceholderSearch": "Пошук..", + "StatsAuthorsAdded": "дададзена аўтараў", + "StatsBooksAdded": "дададзена кніг", "StatsBooksFinished": "кнігі скончаны", "StatsBooksFinishedThisYear": "Некаторыя кнігі скончаны ў гэтым годзе…", "StatsBooksListenedTo": "кнігі, якія былі праслуханы", "StatsCollectionGrewTo": "Ваша калекцыя кніг павялічылася да…", + "StatsSessions": "сеансаў", "ToastAccountUpdateSuccess": "Уліковы запіс абноўлены", - "ToastAuthorImageRemoveSuccess": "Выява аўтара выдалена", + "ToastAsinRequired": "ASIN абавязковы", + "ToastAuthorImageRemoveSuccess": "Відарыс аўтара выдалены", + "ToastAuthorNotFound": "Аўтар \"{0}\" не знойдзены", + "ToastAuthorRemoveSuccess": "Аўтар выдалены", + "ToastAuthorSearchNotFound": "Аўтар не знойдзены", + "ToastAuthorUpdateMerged": "Аўтар аб'яднаны", "ToastAuthorUpdateSuccess": "Аўтар абноўлены", - "ToastAuthorUpdateSuccessNoImageFound": "Аўтар абноўлены (малюнак не знойдзены)", + "ToastAuthorUpdateSuccessNoImageFound": "Аўтар абноўлены (відарыс не знойдзены)", + "ToastBackupCreateFailed": "Не ўдалося стварыць рэзервовую копію", + "ToastBackupCreateSuccess": "Рэзервовая копія створана", + "ToastBackupDeleteFailed": "Не ўдалося выдаліць рэзервовую копію", + "ToastBackupDeleteSuccess": "Рэзервовая копія выдалена", "ToastBackupInvalidMaxKeep": "Няправільная колькасць рэзервовых копій для захоўвання", "ToastBackupInvalidMaxSize": "Няправільны максімальны памер рэзервовай копіі", + "ToastBackupRestoreFailed": "Не ўдалося аднавіць рэзервовую копію", + "ToastBackupUploadFailed": "Не ўдалося запампаваць рэзервовую копію", + "ToastBackupUploadSuccess": "Рэзервовая копія запампавана", "ToastBookmarkCreateFailed": "Не ўдалося стварыць закладку", + "ToastBookmarkCreateSuccess": "Закладка дададзена", + "ToastBookmarkRemoveSuccess": "Закладка выдалена", + "ToastBulkChapterInvalidCount": "Увядзіце лік ад 1 да 150", + "ToastCachePurgeSuccess": "Кэш паспяхова ачышчаны", + "ToastChapterLocked": "Раздзел заблакіраваны.", + "ToastChaptersMustHaveTitles": "Раздзелы павінны мець загалоўкі", + "ToastChaptersRemoved": "Раздзелы выдалены", + "ToastChaptersUpdated": "Раздзелы абноўлены", + "ToastCollectionItemsAddFailed": "Не ўдалося дадаць элемент(ы) у калекцыю", + "ToastCollectionRemoveSuccess": "Калекцыя выдалена", + "ToastCollectionUpdateSuccess": "Калекцыя абноўлена", + "ToastConnectionNotAvailable": "Падключэнне недаступна. Паспрабуйце яшчэ раз пазней", + "ToastCoverSearchFailed": "Не ўдалося знайсці вокладку", + "ToastCoverUpdateFailed": "Не ўдалося абнавіць вокладку", "ToastDateTimeInvalidOrIncomplete": "Дата і час указаны некарэктна або не цалкам", + "ToastDeleteFileSuccess": "Файл выдалены", + "ToastDeviceAddFailed": "Не ўдалося дадаць прыладу", + "ToastDeviceNameAlreadyExists": "Прылада для чытання электронных кніг з такой назвай ужо існуе", "ToastDeviceTestEmailFailed": "Не ўдалося адправіць тэставае электроннае пісьмо", + "ToastEmailSettingsUpdateSuccess": "Налады электроннай пошты абноўлены", "ToastEncodeCancelFailed": "Не ўдалося скасаваць кадаванне", "ToastEncodeCancelSucces": "Кадаванне скасавана", "ToastEpisodeDownloadQueueClearFailed": "Не ўдалося ачысціць чаргу", "ToastEpisodeDownloadQueueClearSuccess": "Чарга спампоўкі эпізодаў ачышчана", - "ToastInvalidImageUrl": "Няправільны URL выявы", + "ToastFailedToCreate": "Не ўдалося стварыць", + "ToastFailedToDelete": "Не ўдалося выдаліць", + "ToastFailedToLoadData": "Не ўдалося загрузіць даныя", + "ToastFailedToMatch": "Не атрымалася знайсці супадзенне", + "ToastFailedToShare": "Не ўдалося абагуліць", + "ToastFailedToUpdate": "Не здалося абнавіць", + "ToastInvalidImageUrl": "Памылковы URL-адрас відарыса", "ToastInvalidMaxEpisodesToDownload": "Няправільная максімальная колькасць эпізодаў для спампоўкі", - "ToastItemMarkedAsFinishedFailed": "Не ўдалося пазначыць як Скончана", - "ToastItemMarkedAsFinishedSuccess": "Элемент пазначаны як Завершаны", - "ToastItemMarkedAsNotFinishedFailed": "Не ўдалося пазначыць як Незавершанае", - "ToastItemMarkedAsNotFinishedSuccess": "Элемент пазначаны як Незавершаны", + "ToastItemCoverUpdateSuccess": "Вокладка элемента абноўлена", + "ToastItemDeletedFailed": "Не ўдалося выдаліць элемент", + "ToastItemDeletedSuccess": "Выдалены элемент", + "ToastItemDetailsUpdateSuccess": "Звесткі элемента абноўлены", + "ToastItemMarkedAsFinishedFailed": "Не ўдалося пазначыць як завершаны", + "ToastItemMarkedAsFinishedSuccess": "Элемент пазначаны як завершаны", + "ToastItemMarkedAsNotFinishedFailed": "Не ўдалося пазначыць як незавершаны", + "ToastItemMarkedAsNotFinishedSuccess": "Элемент пазначаны як незавершаны", "ToastItemUpdateSuccess": "Элемент абноўлены", "ToastLibraryCreateFailed": "Не ўдалося стварыць бібліятэку", "ToastLibraryCreateSuccess": "Бібліятэка \"{0}\" створана", @@ -738,22 +878,54 @@ "ToastNameRequired": "Імя абавязковае", "ToastNewUserCreatedFailed": "Не ўдалося стварыць уліковы запіс: \"{0}\"", "ToastNewUserCreatedSuccess": "Новы ўліковы запіс створаны", + "ToastNewUserTagError": "Трэбаа выбраць хаця б адзін тэг", + "ToastNewUserUsernameError": "Увядзіце імя карыстальніка", "ToastNoRSSFeed": "У падкаста няма RSS-стужкі", - "ToastPlaylistCreateFailed": "Не ўдалося стварыць спіс прайгравання", - "ToastPlaylistCreateSuccess": "Спіс прайгравання створаны", - "ToastPlaylistRemoveSuccess": "Спіс прайгравання выдалены", - "ToastPlaylistUpdateSuccess": "Спіс прайгравання абноўлены", + "ToastNoUpdatesNecessary": "Абнаўленні не патрэбныя", + "ToastNotificationCreateFailed": "Не ўдалося стварыць апавяшчэнне", + "ToastNotificationDeleteFailed": "Не ўдалося выдаліць апавяшчэнне", + "ToastNotificationSettingsUpdateSuccess": "Налады апавяшчэнняў абноўлены", + "ToastNotificationUpdateSuccess": "Апавяшчэнне абноўлена", + "ToastPlaylistCreateFailed": "Не ўдалося стварыць плэй-ліст", + "ToastPlaylistCreateSuccess": "Плэй-ліст створаны", + "ToastPlaylistRemoveSuccess": "Плэй-ліст выдалены", + "ToastPlaylistUpdateSuccess": "Плэй-ліст абноўлены", "ToastPodcastCreateFailed": "Не ўдалося стварыць падкаст", "ToastPodcastCreateSuccess": "Падкаст паспяхова створаны", "ToastPodcastGetFeedFailed": "Не ўдалося атрымаць стужку падкаста", "ToastPodcastNoEpisodesInFeed": "У RSS-стужцы не знойдзена эпізодаў", "ToastPodcastNoRssFeed": "У падкаста няма RSS-стужкі", + "ToastProgressIsNotBeingSynced": "Прагрэс не сінхранізуецца, перазапусціце прайграванне", + "ToastProviderCreatedFailed": "Не ўдалося дадаць пастаўшчыка", + "ToastProviderCreatedSuccess": "Новы пастаўшчык дададзены", + "ToastProviderNameAndUrlRequired": "Назва і URL-адрас абавязковыя", + "ToastProviderRemoveSuccess": "Пастаўшчык выдалены", "ToastRSSFeedCloseFailed": "Не ўдалося закрыць RSS-стужку", "ToastRSSFeedCloseSuccess": "RSS-стужка закрыта", + "ToastRemoveFailed": "Не ўдалося выдаліць", + "ToastRemoveItemFromCollectionFailed": "Не ўдалося выдаліць элемент з калекцыі", + "ToastRemoveItemFromCollectionSuccess": "Элемент выдалены з калекцыі", + "ToastRenameFailed": "Не ўдалося перайменаваць", + "ToastSelectAtLeastOneUser": "Выберыце прынамсі аднаго карыстальніка", "ToastSendEbookToDeviceFailed": "Не ўдалося адправіць электронную кнігу на прыладу", "ToastSendEbookToDeviceSuccess": "Электронная кніга адпраўлена на прыладу \"{0}\"", + "ToastSessionCloseFailed": "Не ўдалося закрыць сеанс", + "ToastSessionDeleteFailed": "Не ўдалося выдаліць сеанс", + "ToastSessionDeleteSuccess": "Сеанс выдалены", "ToastSleepTimerDone": "Таймер сну скончыўся... Хр-р-р", + "ToastTitleRequired": "Загаловак абавязковы", + "ToastUnknownError": "Невядомая памылка", "ToastUploaderItemExistsInSubdirectoryError": "Элемент \"{0}\" выкарыстоўвае падкаталог шляху загрузкі.", + "ToastUserDeleteFailed": "Не ўдалося выдаліць карыстальніка", + "ToastUserDeleteSuccess": "Карыстальнік выдалены", + "ToastUserPasswordChangeSuccess": "Пароль паспяхова зменены", + "ToastUserPasswordMismatch": "Паролі не супадаюць", "ToastUserPasswordMustChange": "Новы пароль не можа супадаць са старым", - "ToastUserRootRequireName": "Неабходна ўвесці імя карыстальніка адміністратара" + "ToastUserRootRequireName": "Неабходна ўвесці імя карыстальніка адміністратара", + "TooltipAddChapters": "Дадаць раздзел(ы)", + "TooltipAddOneSecond": "Дадаць 1 секунду", + "TooltipLockAllChapters": "Заблакіраваць усе раздзелы", + "TooltipLockChapter": "Заблакіраваць раздзел (Shift+націсканне для дыяпазону)", + "TooltipSubtractOneSecond": "Адняць 1 секунду", + "TooltipUnlockAllChapters": "Разблакіраваць усе раздзелы" } From 5f8450602e302b67781d2b4d25e6d6c4e13a309e Mon Sep 17 00:00:00 2001 From: Pavel Miniutka Date: Thu, 26 Feb 2026 09:19:58 +0100 Subject: [PATCH 052/124] Translated using Weblate (Belarusian) Currently translated at 92.1% (1072 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/be/ --- client/strings/be.json | 242 ++++++++++++++++++++++++++++++++--------- 1 file changed, 192 insertions(+), 50 deletions(-) diff --git a/client/strings/be.json b/client/strings/be.json index 799511d4c..cc5dc7bc5 100644 --- a/client/strings/be.json +++ b/client/strings/be.json @@ -17,7 +17,7 @@ "ButtonCancel": "Скасаваць", "ButtonCancelEncode": "Скасаваць кадзіраванне", "ButtonChangeRootPassword": "Зменіце Root пароль", - "ButtonCheckAndDownloadNewEpisodes": "Праверыць і спампаваць новыя эпізоды", + "ButtonCheckAndDownloadNewEpisodes": "Праверыць і спампаваць новыя выпускі", "ButtonChooseAFolder": "Выбраць папку", "ButtonChooseFiles": "Выбраць файлы", "ButtonClearFilter": "Ачысціць фільтр", @@ -134,7 +134,7 @@ "HeaderCollection": "Калекцыя", "HeaderCollectionItems": "Элементы калекцыі", "HeaderCover": "Вокладка", - "HeaderCurrentDownloads": "Бягучыя спампоўкі", + "HeaderCurrentDownloads": "Бягучыя спампоўванні", "HeaderCustomMessageOnLogin": "Карыстальніцкае паведамленне пры ўваходзе", "HeaderCustomMetadataProviders": "Карыстальніцкія пастаўшчыкі метаданых", "HeaderDetails": "Падрабязнасці", @@ -142,7 +142,7 @@ "HeaderEbookFiles": "Файлы электронных кніг", "HeaderEmail": "Электронная пошта", "HeaderEmailSettings": "Налады электроннай пошты", - "HeaderEpisodes": "Эпізоды", + "HeaderEpisodes": "Выпускі", "HeaderEreaderDevices": "Прылады для чытання", "HeaderEreaderSettings": "Налады прылады для чытання", "HeaderFiles": "Файлы", @@ -151,7 +151,7 @@ "HeaderItemFiles": "Файлы элементаў", "HeaderItemMetadataUtils": "Утыліты для метаданых элементаў", "HeaderLastListeningSession": "Апошні сеанс праслухоўвання", - "HeaderLatestEpisodes": "Апошнія эпізоды", + "HeaderLatestEpisodes": "Апошнія выпускі", "HeaderLibraries": "Бібліятэкі", "HeaderLibraryFiles": "Файлы бібліятэкі", "HeaderLibraryStats": "Статыстыка бібліятэкі", @@ -187,11 +187,11 @@ "HeaderRSSFeedGeneral": "Падрабязнасці RSS", "HeaderRSSFeedIsOpen": "RSS-стужка адкрытая", "HeaderRSSFeeds": "RSS-стужкі", - "HeaderRemoveEpisode": "Выдаліць эпізод", - "HeaderRemoveEpisodes": "Выдаліць {0} эпізодаў", + "HeaderRemoveEpisode": "Выдаліць выпуск", + "HeaderRemoveEpisodes": "Выдаліць {0} выпускаў", "HeaderSavedMediaProgress": "Захаваны прагрэс медыя", "HeaderSchedule": "Расклад", - "HeaderScheduleEpisodeDownloads": "Расклад аўтаматычных спамповак эпізодаў", + "HeaderScheduleEpisodeDownloads": "Расклад аўтаматычных спампоўванняў выпускаў", "HeaderScheduleLibraryScans": "Расклад аўтаматычнага сканавання бібліятэкі", "HeaderSession": "Сеанс", "HeaderSetBackupSchedule": "Наладзіць расклад рэзервовага капіравання", @@ -237,7 +237,7 @@ "LabelAddedDate": "Дададзена {0}", "LabelAdminUsersOnly": "Толькі для адміністратараў", "LabelAll": "Усе", - "LabelAllEpisodesDownloaded": "Усе эпізоды спампаваныя", + "LabelAllEpisodesDownloaded": "Усе выпускі спампаваныя", "LabelAllUsers": "Усе карыстальнікі", "LabelAllUsersExcludingGuests": "Усе карыстальнікі, акрамя гасцей", "LabelAllUsersIncludingGuests": "Усе карыстальнікі, уключаючы гасцей", @@ -255,7 +255,7 @@ "LabelAuthorFirstLast": "Аўтар (Імя Прозвішча)", "LabelAuthorLastFirst": "Аўтар (Прозвішча, Імя)", "LabelAuthors": "Аўтары", - "LabelAutoDownloadEpisodes": "Аўтаматычнае спампаванне эпізодаў", + "LabelAutoDownloadEpisodes": "Аўтаматычна спампоўваць выпускі", "LabelAutoFetchMetadata": "Аўтаматычнае атрыманне метаданых", "LabelAutoFetchMetadataHelp": "Атрыманне звестак пра загаловак, аўтара і серыю для спрашчэння запампоўвання. Пасля запампоўвання, магчыма, спатрэбіцца супаставіць дадатковыя метаданыя.", "LabelAutoLaunch": "Аўтазапуск", @@ -290,11 +290,12 @@ "LabelCollapseSubSeries": "Згарнуць падсерыі", "LabelCollection": "Калекцыя", "LabelCollections": "Калекцыі", - "LabelComplete": "Завершана", + "LabelComplete": "Завяршыць", "LabelConfirmPassword": "Пацвердзіце пароль", "LabelContinueListening": "Працягваць слухаць", "LabelContinueReading": "Працягнуць чытанне", "LabelContinueSeries": "Працягнуць серыі", + "LabelCorsAllowed": "Дазволеныя крыніцы CORS", "LabelCover": "Вокладка", "LabelCoverImageURL": "URL-адрас відарыса вокладкі", "LabelCoverProvider": "Пастаўшчык вокладак", @@ -317,7 +318,7 @@ "LabelDiscFromMetadata": "Дыск з метаданых", "LabelDiscover": "Знайсці", "LabelDownload": "Спампаваць", - "LabelDownloadNEpisodes": "Спампована {0} эпізодаў", + "LabelDownloadNEpisodes": "Спампавана {0} выпускаў", "LabelDownloadable": "Спампоўваецца", "LabelDuration": "Працягласць", "LabelDurationComparisonExactMatch": "(дакладнае супадзенне)", @@ -346,10 +347,13 @@ "LabelEncodingWarningAdvancedSettings": "Увага: Не абнаўляйце гэтыя налады, калі вы не знаёмыя з параметрамі кадавання ffmpeg.", "LabelEnd": "Канец", "LabelEndOfChapter": "Канец раздзела", - "LabelEpisode": "Эпізод", - "LabelEpisodeNotLinkedToRssFeed": "Эпізод не звязаны з RSS-стужкай", - "LabelEpisodeTitle": "Загаловак эпізоду", - "LabelEpisodeUrlFromRssFeed": "URL эпізоду з RSS-стужкі", + "LabelEpisode": "Выпуск", + "LabelEpisodeNotLinkedToRssFeed": "Выпуск не звязаны з RSS-стужкай", + "LabelEpisodeNumber": "Выпуск №{0}", + "LabelEpisodeTitle": "Загаловак выпуску", + "LabelEpisodeType": "Тып выпуску", + "LabelEpisodeUrlFromRssFeed": "URL-адрас выпуску з RSS-стужкі", + "LabelEpisodes": "Выпускі", "LabelEpisodic": "Эпізадычны", "LabelExample": "Прыклад", "LabelExpandSeries": "Разгарнуць серыю", @@ -358,15 +362,20 @@ "LabelExpiresAt": "Тэрмін дзеяння заканчваецца ў", "LabelExpiresInSeconds": "Тэрмін дзеяння заканчваецца праз (секунд)", "LabelExpiresNever": "Ніколі", - "LabelExplicit": "Відверты", + "LabelExplicit": "Непрыстойнае", + "LabelExplicitChecked": "Непрыстойнае (пазначана)", + "LabelExplicitUnchecked": "Прыстойнае (не пазначана)", "LabelExportOPML": "Экспарт OPML", "LabelFeedURL": "URL стужкі", "LabelFetchingMetadata": "Атрыманне метаданых", "LabelFile": "Файл", "LabelFileBirthtime": "Час стварэння файла", + "LabelFileBornDate": "Створаны {0}", "LabelFileModified": "Час змянення файла", "LabelFileModifiedDate": "Зменены {0}", "LabelFilename": "Назва файла", + "LabelFilterByUser": "Фільтраваць па карыстальніку", + "LabelFindEpisodes": "Знайсці выпускі", "LabelFinished": "Завершана", "LabelFinishedDate": "Завершана {0}", "LabelFolder": "Папка", @@ -387,11 +396,13 @@ "LabelHighestPriority": "Найвышэйшы прыярытэт", "LabelHost": "Хост", "LabelHour": "Гадзіна", + "LabelHours": "Гадзіны", "LabelIcon": "Значок", "LabelImageURLFromTheWeb": "URL-адрас відарыса з інтэрнэту", "LabelInProgress": "У працэсе", "LabelIncludeInTracklist": "Уключыць у спіс трэкаў", "LabelIncomplete": "Незавершана", + "LabelInterval": "Інтэрвал", "LabelIntervalCustomDailyWeekly": "Карыстальніцкі штодзённы/штотыднёвы", "LabelIntervalEvery12Hours": "Кожныя 12 гадзін", "LabelIntervalEvery15Minutes": "Кожныя 15 хвілін", @@ -430,42 +441,73 @@ "LabelLogLevelDebug": "Debug", "LabelLogLevelInfo": "Info", "LabelLogLevelWarn": "Warn", + "LabelLookForNewEpisodesAfterDate": "Шукаць новыя выпускі пасля гэтай даты", "LabelLowestPriority": "Найніжэйшы прыярытэт", + "LabelMatchConfidence": "Упэўненасць", "LabelMatchExistingUsersByDescription": "Выкарыстоўваецца для падключэння існуючых карыстальнікаў. Пасля падключэння карыстальнікі будуць супастаўляцца з дапамогай унікальнага ідэнтыфікатара ад пастаўшчыка SSO", - "LabelMaxEpisodesToDownload": "Максімальная колькасць эпізодаў для спампоўкі. Выкарыстоўвайце 0 для неабмежаванай колькасці.", - "LabelMaxEpisodesToDownloadPerCheck": "Максімальная колькасць новых эпізодаў для спампоўкі за праверку", - "LabelMaxEpisodesToKeepHelp": "Значэнне 0 не ўстанаўлівае максімальнага абмежавання. Пасля аўтаматычнай спампоўкі новага эпізоду будзе выдалены самы стары эпізод, калі ў вас больш за X эпізодаў. Пры кожнай новай спампоўцы будзе выдаляцца толькі 1 эпізод.", + "LabelMaxEpisodesToDownload": "Максімальная колькасць выпускаў для спампоўвання. 0 – неабмежаваная колькасць.", + "LabelMaxEpisodesToDownloadPerCheck": "Максімальная колькасць новых выпускаў для спампоўвання за праверку", + "LabelMaxEpisodesToKeep": "Максімальная колькасць выпускаў, якія будуць захоўвацца", + "LabelMaxEpisodesToKeepHelp": "Значэнне 0 не задае максімальнага абмежавання. Пасля аўтаматычнага спампоўвання новага выпуску будзе выдалены самы стары выпуск, калі ў вас больш за X выпускаў. Пры кожным новым спампоўванні будзе выдаляцца толькі 1 выпуск.", "LabelMediaPlayer": "Медыяпрайгравальнік", "LabelMediaType": "Тып медыя", + "LabelMetaTag": "Метатэг", + "LabelMetaTags": "Метатэгі", "LabelMetadataOrderOfPrecedenceDescription": "Крыніцы метаданых з вышэйшым прыярытэтам будуць замяняць крыніцы з ніжэйшым прыярытэтам", "LabelMetadataProvider": "Пастаўшчык метаданых", + "LabelMinute": "Хвіліна", + "LabelMinutes": "Хвіліны", "LabelMissing": "Адсутнічае", + "LabelMissingEbook": "Няма электроннай кнігі", + "LabelMissingSupplementaryEbook": "Няма дадатковай электроннай кнігі", + "LabelMobileRedirectURIs": "Дазволеныя URI перанакіравання для мабільных прылад", + "LabelMobileRedirectURIsDescription": "Гэта белы спіс дапушчальных URI перанакіравання для мабільных праграм. Стандартным з'яўляецца audiobookshelf://oauth, які вы можаце выдаліць або дапоўніць дадатковымі URI для інтэграцыі са староннімі праграмамі. Выкарыстанне зорачкі (*) у якасці адзінага запісу дазваляе любы URI.", "LabelMore": "Больш", "LabelMoreInfo": "Больш інфармацыі", "LabelName": "Назва", - "LabelNarrator": "Чытальнік", - "LabelNarrators": "Чытальнікі", + "LabelNarrator": "Дыктар", + "LabelNarrators": "Дыктары", + "LabelNew": "Новы", + "LabelNewPassword": "Новы пароль", "LabelNewestAuthors": "Новыя аўтары", - "LabelNewestEpisodes": "Новыя эпізоды", + "LabelNewestEpisodes": "Найноўшыя выпускі", + "LabelNextBackupDate": "Дата наступнага рэзервовага капіравання", "LabelNextChapters": "Наступныя раздзелы:", "LabelNextScheduledRun": "Наступны запланаваны запуск", "LabelNoApiKeys": "Няма ключоў API", "LabelNoCustomMetadataProviders": "Няма карыстальніцкіх пастаўшчыкоў метаданых", - "LabelNotFinished": "Не завершана", + "LabelNoEpisodesSelected": "Не выбрана ніводнага выпуску", + "LabelNotFinished": "Незавершана", "LabelNotStarted": "Не пачата", + "LabelNotificationAppriseURL": "URL-адрасы Apprise", "LabelNotificationAvailableVariables": "Даступныя пераменныя", + "LabelNotificationBodyTemplate": "Шаблон зместу", + "LabelNotificationEvent": "Падзея апавяшчэння", "LabelNotificationTitleTemplate": "Шаблон загалоўка", + "LabelNotificationsMaxFailedAttempts": "Максімальная колькасць няўдалых спроб", "LabelNotificationsMaxFailedAttemptsHelp": "Апавяшчэнні адключаюцца пасля таго, як не ўдаецца іх адправіць гэтулькі разоў", + "LabelNotificationsMaxQueueSize": "Максімальны памер чаргі для падзей апавяшчэнняў", + "LabelNotificationsMaxQueueSizeHelp": "Падзеі могуць спрацоўваць толькі адзін раз у секунду. Падзеі будуць ігнаравацца пры дасягненні максімальнага памеру чаргі. Гэта прадухіляе рассылку спаму.", "LabelNumberOfBooks": "Колькасць кніг", "LabelNumberOfChapters": "Колькасць раздзелаў:", - "LabelNumberOfEpisodes": "# з эпізодаў", + "LabelNumberOfEpisodes": "Колькасць выпускаў", "LabelOpenRSSFeed": "Адкрыць RSS-стужку", "LabelOverwrite": "Перазапісаць", "LabelPaginationPageXOfY": "Старонка {0} з {1}", "LabelPassword": "Пароль", "LabelPath": "Шлях", - "LabelPermissionsDownload": "Можна спампаваць", + "LabelPermanent": "Пастаянны", + "LabelPermissionsAccessAllLibraries": "Мае доступ да ўсіх бібліятэк", + "LabelPermissionsAccessAllTags": "Мае доступ да ўсіх тэгаў", + "LabelPermissionsAccessExplicitContent": "Мае доступ да непрыстойнага змесціва", + "LabelPermissionsCreateEreader": "Можа ствараць прыладу для чытання", + "LabelPermissionsDelete": "Можа выдаляць", + "LabelPermissionsDownload": "Можа спампоўваць", + "LabelPermissionsUpdate": "Можа абнаўляць", + "LabelPermissionsUpload": "Можа запампоўваць", + "LabelPersonalYearReview": "Вынікі года ({0})", "LabelPhotoPathURL": "Шлях/URL-адрас фота", + "LabelPlayMethod": "Метад прайгравання", "LabelPlayerChapterNumberMarker": "{0} з {1}", "LabelPlaylists": "Плэй-лісты", "LabelPodcast": "Падкаст", @@ -480,6 +522,8 @@ "LabelPubDate": "Дата публікацыі", "LabelPublishYear": "Год публікацыі", "LabelPublishedDate": "Апублікавана {0}", + "LabelPublisher": "Выдавец", + "LabelPublishers": "Выдаўцы", "LabelRSSFeedCustomOwnerEmail": "Карыстальніцкая электронная пошта ўладальніка", "LabelRSSFeedCustomOwnerName": "Карыстальніцкае імя ўладальніка", "LabelRSSFeedOpen": "RSS-стужка адкрыта", @@ -508,8 +552,8 @@ "LabelSeason": "Сезон", "LabelSeasonNumber": "Сезон #{0}", "LabelSelectAll": "Выбраць усё", - "LabelSelectAllEpisodes": "Выбраць усе эпізоды", - "LabelSelectEpisodesShowing": "Выбраць {0} эпізодаў для паказу", + "LabelSelectAllEpisodes": "Выбраць усе выпускі", + "LabelSelectEpisodesShowing": "Выбраць {0} выпускаў для паказу", "LabelSelectUser": "Выберыце карыстальніка", "LabelSelectUsers": "Выбраць карыстальнікаў", "LabelSendEbookToDevice": "Адправіць электронную кнігу на...", @@ -529,8 +573,8 @@ "LabelSettingsDateFormat": "Фарматы даты", "LabelSettingsEnableWatcherForLibrary": "Аўтаматычна сачыць за зменамі ў бібліятэцы", "LabelSettingsEnableWatcherHelp": "Адключае аўтаматычнае дадаванне/абнаўленне элементаў пры выяўленні змен у файлах. *Патрабуецца перазапуск сервера", - "LabelSettingsEpubsAllowScriptedContent": "Дазволіць скрыптавы кантэнт у EPUB", - "LabelSettingsEpubsAllowScriptedContentHelp": "Дазволіць EPUB-файлам выконваць скрыпты. Рэкамендуецца пакінуць гэтую наладу выключанай, калі вы не давяраеце крыніцы EPUB-файлаў.", + "LabelSettingsEpubsAllowScriptedContent": "Дазваляць скрыпты ў файлах EPUB", + "LabelSettingsEpubsAllowScriptedContentHelp": "Дазволіць файлам EPUB выконваць скрыпты. Рэкамендуецца пакінуць гэтую наладу выключанай, калі вы не давяраеце крыніцы файлаў EPUB.", "LabelSettingsExperimentalFeatures": "Эксперыментальныя функцыі", "LabelSettingsExperimentalFeaturesHelp": "Функцыі ў распрацоўцы, для якіх вашы водгукі і дапамога ў тэставанні будуць карыснымі. Націсніце, каб адкрыць абмеркаванне на GitHub.", "LabelSettingsFindCovers": "Знайсці вокладкі", @@ -540,10 +584,10 @@ "LabelSettingsHomePageBookshelfView": "Галоўная старонка выкарыстоўвае выгляд кніжнай паліцы", "LabelSettingsLibraryBookshelfView": "Бібліятэка выкарыстоўвае выгляд кніжнай паліцы", "LabelSettingsLibraryMarkAsFinishedPercentComplete": "Працэнт завяршэння большы за", - "LabelSettingsLibraryMarkAsFinishedTimeRemaining": "Час, што застаўся, менш за (секунды)", - "LabelSettingsLibraryMarkAsFinishedWhen": "Пазначыць элемент медыя як скончаны, калі", + "LabelSettingsLibraryMarkAsFinishedTimeRemaining": "Час, што застаўся, менш за (секунд)", + "LabelSettingsLibraryMarkAsFinishedWhen": "Пазначыць элемент медыя як завершаны, калі", "LabelSettingsOnlyShowLaterBooksInContinueSeries": "Пропусціць папярэднія кнігі ў \"Працягнуць серыю\"", - "LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "Палка \"Працягнуць серыю\" на галоўнай старонцы паказвае першую не пачатую кнігу ў серыях, дзе завершана хаця б адна кніга і няма кніг у працэсе чытання. Уключэнне гэтай налады дазволіць працягваць серыю з самай апошняй завершанай кнігі замест першай не пачатай.", + "LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "Паліца \"Працягнуць серыю\" на галоўнай старонцы паказвае першую не пачатую кнігу ў серыях, дзе завершана хаця б адна кніга і няма кніг у працэсе чытання. Уключэнне гэтай налады дазволіць працягваць серыю з самай апошняй завершанай кнігі замест першай не пачатай.", "LabelSettingsParseSubtitles": "Аналізаваць падзагалоўкі", "LabelSettingsParseSubtitlesHelp": "Выдзяляць падзагаловак з назваў папак аўдыякніг.
Падзагаловак павінен быць аддзелены сімвалам \" - \".
Напрыклад, \"Назва кнігі - Падзагаловак тут\" мае падзагаловак \"Падзагаловак тут\"", "LabelSettingsPreferMatchedMetadata": "Аддаваць перавагу супадаючым метаданым", @@ -555,7 +599,7 @@ "LabelSettingsStoreMetadataWithItemHelp": "Прадвызначана метаданыя захоўваюцца ў /metadata/items. Пры ўключэнні гэтай опцыі файлаў метаданых будуць захоўвацца ў папках элементаў бібліятэкі", "LabelSettingsTimeFormat": "Фармат часу", "LabelShare": "Абагуліць", - "LabelShareDownloadableHelp": "Дазваляе карыстальнікам, якія маюць спасылку на доступ, спампаваць ZIP-файл элемента бібліятэкі.", + "LabelShareDownloadableHelp": "Дазваляе карыстальнікам, якія маюць спасылку, спампоўваць ZIP-архіў элемента бібліятэкі.", "LabelShowAll": "Паказаць усё", "LabelShowSeconds": "Паказваць секунды", "LabelShowSubtitles": "Паказаць падзагалоўкі", @@ -565,6 +609,9 @@ "LabelSortDescending": "Па ўбыванні", "LabelStart": "Пачаць", "LabelStartTime": "Час пачатку", + "LabelStarted": "Пачата", + "LabelStartedAt": "Пачата ў", + "LabelStartedDate": "Пачата {0}", "LabelStatsAudioTracks": "Аўдыятрэкі", "LabelStatsAuthors": "Аўтары", "LabelStatsBestDay": "Лепшы дзень", @@ -609,7 +656,9 @@ "LabelToolsEmbedMetadata": "Убудаваць метаданыя", "LabelToolsEmbedMetadataDescription": "Убудаваць метаданыя ў аўдыяфайлы, уключаючы відарыс вокладкі і раздзелы.", "LabelToolsM4bEncoder": "Кадавальнік M4B", + "LabelToolsMakeM4b": "Стварыць файл аўдыякнігі M4B", "LabelToolsMakeM4bDescription": "Стварыць аўдыякнігу ў фармаце .M4B з убудаванымі метаданымі, відарысам вокладкі і раздзеламі.", + "LabelToolsSplitM4b": "Падзяліць M4B на MP3", "LabelToolsSplitM4bDescription": "Стварэнне MP3 з M4B, падзеленага па раздзелах, з убудаванымі метаданымі, відарысам вокладкі і раздзеламі.", "LabelTotalDuration": "Агульная працягласць", "LabelTotalTimeListened": "Агульны час праслухоўвання", @@ -619,7 +668,9 @@ "LabelTracksMultiTrack": "Некалькі трэкаў", "LabelTracksNone": "Няма трэкаў", "LabelTracksSingleTrack": "Адзін трэк", + "LabelTrailer": "Трэйлер", "LabelType": "Тып", + "LabelUnabridged": "Поўная версія", "LabelUndo": "Адрабіць", "LabelUnknown": "Невядома", "LabelUnknownPublishDate": "Невядомая дата публікацыі", @@ -635,6 +686,7 @@ "LabelUseAdvancedOptions": "Выкарыстоўваць пашыраныя параметры", "LabelUseChapterTrack": "Выкарыстоўваць трэк раздзела", "LabelUseFullTrack": "Выкарыстоўваць увесь трэк", + "LabelUseZeroForUnlimited": "0 – неабмежавана", "LabelUser": "Карыстальнік", "LabelUsername": "Імя карыстальніка", "LabelValue": "Значэнне", @@ -658,6 +710,7 @@ "MessageAddToPlayerQueue": "Дадаць у чаргу прайгравальніка", "MessageAppriseDescription": "Каб выкарыстоўваць гэтую функцыю, вам спатрэбіцца запусціць асобнік Apprise API або API, які будзе апрацоўваць тыя ж запыты.
URL Apprise API павінен быць поўным шляхам для адпраўкі апавяшчэння, напрыклад, калі ваш API працуе па адрасе http://192.168.1.1:8337, то вы павінны ўвесці http://192.168.1.1:8337/notify.", "MessageAuthenticationOIDCChangesRestart": "Перазапусціце сервер пасля захавання, каб прымяніць змены OIDC.", + "MessageAuthenticationSecurityMessage": "Дзеля бяспекі была палепшана аўтэнтыфікацыя. Усім карыстальнікам трэба паўторна ўвайсці ў сістэму.", "MessageBackupsDescription": "Рэзервовыя копіі ўключаюць карыстальнікаў, іх прагрэс, падрабязнасці элементаў бібліятэкі, налады сервера і відарысы, якія захоўваюцца ў /metadata/items і /metadata/authors. Рэзервовыя копіі не ўключаюць файлы, якія захоўваюцца ў папках бібліятэкі.", "MessageBackupsLocationEditNote": "Заўвага: Абнаўленне месцазнаходжання рэзервовых копій не перамяшчае і не змяняе існуючыя рэзервовыя копіі", "MessageBackupsLocationNoEditNote": "Заўвага: Месцазнаходжанне рэзервовых копій задаецца праз зменную асяроддзя і не можа быць зменена тут.", @@ -666,17 +719,20 @@ "MessageBatchEditPopulateMapDetailsItemHelp": "Запоўніце ўключаныя палі падрабязнасцей карты данымі з гэтага элемента", "MessageBatchQuickMatchDescription": "Хуткі пошук паспрабуе дадаць адсутныя вокладкі і метаданыя для выбраных элементаў. Уключыце параметры ніжэй, каб дазволіць Хуткаму пошуку замяняць існуючыя вокладкі і/або метаданыя.", "MessageBookshelfNoCollections": "Вы пакуль не стварылі ніводнай калекцый", + "MessageBookshelfNoCollectionsHelp": "Калекцыі публічныя. Усе карыстальнікі, якія маюць доступ да бібліятэкі, могуць іх бачыць.", "MessageBookshelfNoRSSFeeds": "Няма адкрытых RSS-стужак", "MessageBookshelfNoResultsForFilter": "Няма вынікаў для фільтра \"{0}: {1}\"", "MessageBookshelfNoResultsForQuery": "Няма вынікаў па запыце", + "MessageBookshelfNoSeries": "У вас няма серый", "MessageChapterErrorFirstNotZero": "Першы раздзел павінен пачынацца з 0", "MessageChapterErrorStartGteDuration": "Няправільны час пачатку: ён павінен быць меншым за працягласць аўдыякнігі", "MessageChapterErrorStartLtPrev": "Няправільны час пачатку: ён павінен быць большым або роўным часу пачатку папярэдняга раздзела", "MessageChaptersNotFound": "Раздзелы не знойдзены", "MessageCheckingCron": "Праверка cron...", - "MessageConfirmCloseFeed": "Вы ўпэўнены, што жадаеце закрыць гэтую стужку?", + "MessageConfirmCloseFeed": "Вы ўпэўнены, што хочаце закрыць гэту стужку?", "MessageConfirmDeleteApiKey": "Вы ўпэўнены, што хочаце выдаліць ключ API \"{0}\"?", "MessageConfirmDeleteBackup": "Вы ўпэўнены, што хочаце выдаліць рэзервовую копію для {0}?", + "MessageConfirmDeleteDevice": "Вы ўпэўнены, што хочаце выдаліць прыладу для чытання \"{0}\"?", "MessageConfirmDeleteFile": "Будзе выдалены файл з файлавай сістэмы. Вы ўпэўнены?", "MessageConfirmDeleteLibrary": "Вы ўпэўнены, што хочаце назаўжды выдаліць бібліятэку \"{0}\"?", "MessageConfirmDeleteLibraryItem": "Будзе выдалены элемент бібліятэкі з базы даных і файлавай сістэмы. Вы ўпэўнены?", @@ -685,14 +741,23 @@ "MessageConfirmDeleteNotification": "Вы ўпэўнены, што хочаце выдаліць гэта апавяшчэнне?", "MessageConfirmDeleteSession": "Вы ўпэўнены, што хочаце выдаліць гэты сеанс?", "MessageConfirmEmbedMetadataInAudioFiles": "Вы ўпэўнены, што хочаце ўбудаваць метаданыя ў {0} аўдыяфайлаў?", - "MessageConfirmPurgeCache": "Ачышчэнне кэшу выдаліць увесь каталог па адрасе /metadata/cache.

Вы сапраўды хочаце выдаліць каталог кэшу?", + "MessageConfirmMarkAllEpisodesFinished": "Вы ўпэўнены, што хочаце пазначыць усе выпускі як завершаныя?", + "MessageConfirmMarkAllEpisodesNotFinished": "Вы ўпэўнены, што хочаце пазначыць усе выпускі як незавершаныя?", + "MessageConfirmMarkItemFinished": "Вы ўпэўнены, што хочаце пазначыць \"{0}\" як завершаны?", + "MessageConfirmMarkItemNotFinished": "Вы ўпэўнены, што хочаце пазначыць \"{0}\" як незавершаны?", + "MessageConfirmMarkSeriesFinished": "Вы ўпэўнены, што хочаце пазначыць усе кнігі ў гэтай серыі як завершаныя?", + "MessageConfirmMarkSeriesNotFinished": "Вы ўпэўнены, што хочаце пазначыць усе кнігі ў гэтай серыі як незавершаныя?", + "MessageConfirmPurgeCache": "Ачышчэнне кэшу выдаліць увесь каталог па адрасе /metadata/cache.

Вы ўпэўнены, што хочаце выдаліць каталог кэшу?", "MessageConfirmPurgeItemsCache": "Ачышчэнне кэшу элементаў выдаліць увесь каталог па адрасе /metadata/cache/items.
Вы ўпэўнены?", - "MessageConfirmQuickMatchEpisodes": "Хуткае супадзенне эпізодаў перазапіша дэталі, калі супадзенне будзе знойдзена. Будуць абноўлены толькі эпізоды, якія не супадаюць. Вы ўпэўнены?", + "MessageConfirmQuickMatchEpisodes": "Хуткае супадзенне выпускаў перазапіша дэталі, калі супадзенне будзе знойдзена. Будуць абноўлены толькі выпускі, якія не супадаюць. Вы ўпэўнены?", "MessageConfirmRemoveAllChapters": "Вы ўпэўнены, што хочаце выдаліць усе раздзелы?", "MessageConfirmRemoveAuthor": "Вы ўпэўнены, што хочаце выдаліць аўтара \"{0}\"?", "MessageConfirmRemoveCollection": "Вы ўпэўнены, што хочаце выдаліць калекцыю \"{0}\"?", - "MessageConfirmRemoveListeningSessions": "Вы ўпэўнены, што жадаеце выдаліць {0} сеансаў праслухоўвання?", + "MessageConfirmRemoveEpisode": "Вы ўпэўнены, што хочаце выдаліць выпуск \"{0}\"?", + "MessageConfirmRemoveEpisodes": "Вы ўпэўнены, што хочаце выдаліць {0} выпускаў?", + "MessageConfirmRemoveListeningSessions": "Вы ўпэўнены, што хочаце выдаліць {0} сеансаў праслухоўвання?", "MessageConfirmRemoveMetadataFiles": "Вы ўпэўнены, што хочаце выдаліць усе файлы metadata.{0} у папках элементаў бібліятэкі?", + "MessageConfirmRemoveNarrator": "Вы ўпэўнены, што хочаце выдаліць дыктара \"{0}\"?", "MessageConfirmRemovePlaylist": "Вы ўпэўнены, што хочаце выдаліць плэй-ліст \"{0}\"?", "MessageConfirmRenameGenre": "Вы ўпэўнены, што хочаце перайменаваць жанр \"{0}\" на \"{1}\" для ўсіх элементаў?", "MessageConfirmRenameGenreMergeNote": "Заўвага: Гэты жанр ужо існуе, таму яны будуць аб'яднаны.", @@ -702,55 +767,94 @@ "MessageConfirmRenameTagWarning": "Увага! Падобны тэг з іншым рэгістрам ужо існуе: \"{0}\".", "MessageConfirmResetProgress": "Вы ўпэўнены, што хочаце скінуць свой прагрэс?", "MessageConfirmSendEbookToDevice": "Вы ўпэўнены, што хочаце адправіць {0} электронную кнігу \"{1}\" на прыладу \"{2}\"?", - "MessageDownloadingEpisode": "Спампоўка эпізоду", + "MessageDownloadingEpisode": "Спампоўванне выпуску", "MessageDragFilesIntoTrackOrder": "Перацягніце файлы ў правільным парадку трэкаў", + "MessageEmbedFinished": "Убудаванне завершана!", "MessageEmbedQueue": "У чарзе на ўбудаванне метаданых (у чарзе {0})", - "MessageEpisodesQueuedForDownload": "{0} эпізод(аў) у чарзе для спампоўкі", + "MessageEpisodesQueuedForDownload": "{0} выпуск(-аў) у чарзе спампоўвання", "MessageEreaderDevices": "Каб забяспечыць дастаўку электронных кніг, вам можа спатрэбіцца дадаць вышэйзгаданы адрас электроннай пошты як дазволенага адпраўніка для кожнай прылады, пералічанай ніжэй.", "MessageFeedURLWillBe": "URL стужкі будзе {0}", "MessageFetching": "Атрыманне...", + "MessageHeatmapNoListeningSessions": "Няма сеансаў праслухоўвання на {0}", "MessageImportantNotice": "Важная заўвага!", + "MessageInsertChapterBelow": "Уставіць раздзел ніжэй", "MessageInvalidAsin": "Няправільны ASIN", + "MessageItemsSelected": "Выбрана элементаў: {0}", "MessageItemsUpdated": "{0} элементаў абноўлена", "MessageJoinUsOn": "Далучайцеся да нас у", "MessageLoading": "Загрузка...", "MessageLoadingFolders": "Загрузка папак...", "MessageLogsDescription": "Журналы захоўваюцца ў каталогу /metadata/logs у фармаце JSON. Журналы памылак захоўваюцца ў файле /metadata/logs/crashlogs.txt.", + "MessageM4BFinished": "M4B завершана!", "MessageMapChapterTitles": "Супаставіць загалоўкі раздзелаў з існуючымі раздзеламі аўдыякнігі без змянення пазнак часу", + "MessageMarkAllEpisodesFinished": "Пазначыць усе выпускі як завершаныя", + "MessageMarkAllEpisodesNotFinished": "Пазначыць усе выпускі як незавершаныя", "MessageMarkAsFinished": "Пазначыць як завершаную", + "MessageMarkAsNotFinished": "Пазначыць як незавершаную", "MessageMatchBooksDescription": "паспрабуе параўнаць кнігі ў бібліятэцы з кнігай ад выбранай пошукавай сістэмы і запоўніць пустыя палі і вокладку. Не перазапісвае звесткі.", "MessageNoAudioTracks": "Няма аўдыятрэкаў", + "MessageNoAuthors": "Няма аўтараў", + "MessageNoBackups": "Няма рэзервовых копій", "MessageNoBookmarks": "Няма закладак", "MessageNoChapters": "Няма раздзелаў", "MessageNoCollections": "Няма калекцый", + "MessageNoCoversFound": "Не знойдзена вокладак", + "MessageNoDescription": "Няма апісання", + "MessageNoDevices": "Няма прылад", "MessageNoDownloadsInProgress": "Зараз няма актыўных спампованняў", "MessageNoDownloadsQueued": "Няма спамповак у чарзе", + "MessageNoEpisodeMatchesFound": "Адпаведных выпускаў не знойдзена", + "MessageNoEpisodes": "Няма выпускаў", + "MessageNoFoldersAvailable": "Няма даступных папак", + "MessageNoGenres": "Няма жанраў", + "MessageNoIssues": "Няма праблем", "MessageNoItems": "Няма элементаў", "MessageNoItemsFound": "Элементы не знойдзены", "MessageNoListeningSessions": "Няма сеансаў праслухоўвання", "MessageNoLogs": "Няма журналаў", "MessageNoMediaProgress": "Няма прагрэсу медыя", + "MessageNoNotifications": "Няма апавяшчэнняў", "MessageNoPodcastFeed": "Няправільны падкаст: Няма стужкі", "MessageNoPodcastsFound": "Падкасты не знойдзены", + "MessageNoResults": "Няма вынікаў", + "MessageNoSearchResultsFor": "Няма вынікаў пошуку па запыце \"{0}\"", + "MessageNoSeries": "Няма серый", + "MessageNoTags": "Няма тэгаў", "MessageNoTasksRunning": "Няма запушчаных задач", "MessageNoUpdatesWereNecessary": "Абнаўленні не патрабаваліся", "MessageNoUserPlaylists": "У вас няма плэй-лістоў", "MessageNoUserPlaylistsHelp": "Плэй-лісты прыватныя. Толькі карыстальнік, які іх стварыў, можа іх бачыць.", - "MessageOpmlPreviewNote": "Заўвага: гэта перадпрагляд прааналізаванага файла OPML. Фактычны загаловак падкаста будзе ўзяты з RSS-ленты.", + "MessageNotYetImplemented": "Пакуль не рэалізавана", + "MessageOpmlPreviewNote": "Заўвага: гэта перадпрагляд прааналізаванага файла OPML. Фактычны загаловак падкаста будзе ўзяты з RSS-стужкі.", + "MessageOr": "або", "MessagePauseChapter": "Прыпыніць прайграванне раздзела", + "MessagePlayChapter": "Паслухаць пачатак раздзела", "MessagePlaylistCreateFromCollection": "Стварыць плэй-ліст з калекцыі", + "MessagePleaseWait": "Пачакайце...", "MessagePodcastHasNoRSSFeedForMatching": "У падкаста няма URL RSS-стужкі для супадзення", "MessagePodcastSearchField": "Увядзіце пошукавы запыт або URL RSS-стужкі", "MessageQuickEmbedInProgress": "Выконваецца хуткае ўбудаванне", "MessageQuickMatchDescription": "Запоўніць пустыя звесткі элемента і вокладку першым вынікам супадзення з '{0}'. Не замяняе звесткіі, калі параметр \"Аддаваць перавагу супадаючым метаданым\" на серверы не ўключана.", + "MessageRemoveChapter": "Выдаліць раздзел", + "MessageRemoveEpisodes": "Выдаліць выпускі ({0})", + "MessageRemoveFromPlayerQueue": "Выдаліць з чаргі прагравання", + "MessageRemoveUserWarning": "Вы ўпэўнены, што хочаце назаўжды выдаліць карыстальніка \"{0}\"?", "MessageReportBugsAndContribute": "Паведамляйце пра памылкі, прапануйце новыя функцыі і ўдзельнічайце на", + "MessageResetChaptersConfirm": "Вы ўпэўнены, што хочаце скінуць раздзелы і адрабіць зробленыя вамі змены?", + "MessageRestoreBackupConfirm": "Вы ўпэўнены, што хочаце аднавіць рэзервовую копію, створаную", "MessageRestoreBackupWarning": "Аднаўленне рэзервовай копіі перазапіша ўсю базу даных, размешчаную ў /config, а таксама відарысы вокладкі ў /metadata/items і /metadata/authors.

Рэзервовыя копіі не змяняюць файлы ў папках бібліятэкі. Калі вы ўключылі налады сервера для захоўвання воклак і метаданых у папках бібліятэкі, гэтыя файлы не будуць захаваныя ў рэзервовых копіях і не зменяцца.

Усе кліенты, якія карыстаюцца вашым серверам, будуць аўтаматычна абноўлены.", "MessageScheduleRunEveryWeekdayAtTime": "Выконваць кожныя {0} у {1}", + "MessageSearchResultsFor": "Вынікі пошуку для", + "MessageSelected": "Выбрана: {0}", + "MessageSeriesSequenceCannotContainSpaces": "Паслядоўнасць серый не можа ўтрымліваць прабелы", + "MessageServerCouldNotBeReached": "Сервер недаступны", "MessageSetChaptersFromTracksDescription": "Задаць раздзелы, выкарыстоўваючы кожны аўдыяфайл у якасці раздзела і назву аўдыяфайла ў якасці загалоўка раздзела", + "MessageShareExpirationWillBe": "Тэрмін дзеяння будзе {0}", + "MessageShareExpiresIn": "Тэрмін дзеяння заканчваецца праз {0}", "MessageStartPlaybackAtTime": "Пачаць прайграванне для \"{0}\" з {1}?", "MessageTaskAudioFileNotWritable": "Аўдыяфайл \"{0}\" недаступны для запісу", "MessageTaskCanceledByUser": "Задача скасавана карыстальнікам", - "MessageTaskDownloadingEpisodeDescription": "Спампоўка эпізоду \"{0}\"", + "MessageTaskDownloadingEpisodeDescription": "Спампоўванне выпуску \"{0}\"", "MessageTaskEmbeddingMetadata": "Убудаванне метаданых", "MessageTaskEmbeddingMetadataDescription": "Убудаванне метаданых у аўдыякнігу \"{0}\"", "MessageTaskEncodingM4b": "Кадаванне M4B", @@ -785,23 +889,34 @@ "MessageTaskTargetDirectoryNotWritable": "Мэтавы каталог недаступны для запісу", "NoteChapterEditorTimes": "Заўвага: Час пачатку першага раздзела павінен заставацца 0:00, а час пачатку апошняга раздзела не можа перавышаць працягласць гэтай аўдыякнігі.", "NoteRSSFeedPodcastAppsHttps": "Папярэджанне: большасць праграм для падкастаў патрабуюць, каб URL RSS-стужкі выкарыстоўваў HTTPS", - "NoteRSSFeedPodcastAppsPubDate": "Папярэджанне: адзін ці больш вашых эпізодаў не маюць даты публікацыі. Некаторыя праграмы для падкастаў патрабуюць гэтага.", + "NoteRSSFeedPodcastAppsPubDate": "Папярэджанне: адзін ці больш выпускаў не маюць даты публікацыі. Некаторыя праграмы для падкастаў патрабуюць гэтага.", "NoteUploaderFoldersWithMediaFiles": "Папкі з медыяфайламі будуць апрацоўвацца як асобныя элементы бібліятэкі.", - "NotificationOnEpisodeDownloadedDescription": "Выклікаецца, калі эпізод падкаста аўтаматычна спампоўваецца", + "NotificationOnEpisodeDownloadedDescription": "Спрацоўвае, калі выпуск падкаста аўтаматычна спампоўваецца", + "NotificationOnRSSFeedDisabledDescription": "Спрацоўвае, калі аўтаматычнае спампоўванне выпускаў адключана з-за занадта вялікай колькасці няўдалых спроб", + "NotificationOnRSSFeedFailedDescription": "Спрацоўвае, пры памылцы запыту RSS-стужкі для аўтаматычнага спампоўвання выпуску", "NotificationOnTestDescription": "Падзея для тэсціравання сістэмы апавяшчэнняў", - "PlaceholderBulkChapterInput": "Увядзіце загаловак раздзела або выкарыстоўвайце нумарацыю (напрыклад, «Эпізод 1», «Раздзел 10», «1.»)", + "PlaceholderBulkChapterInput": "Увядзіце загаловак раздзела або выкарыстоўвайце нумарацыю (напрыклад, «Выпуск 1», «Раздзел 10», «1.»)", "PlaceholderNewCollection": "Назва новай калекцыі", "PlaceholderNewFolderPath": "Шлях да новай папкі", "PlaceholderNewPlaylist": "Назва новага плэй-ліста", "PlaceholderSearch": "Пошук..", + "PlaceholderSearchEpisode": "Пошук выпуску...", "StatsAuthorsAdded": "дададзена аўтараў", "StatsBooksAdded": "дададзена кніг", - "StatsBooksFinished": "кнігі скончаны", - "StatsBooksFinishedThisYear": "Некаторыя кнігі скончаны ў гэтым годзе…", + "StatsBooksFinished": "завершана кніг", + "StatsBooksFinishedThisYear": "Некаторыя кнігі завершаны ў гэтым годзе…", "StatsBooksListenedTo": "кнігі, якія былі праслуханы", "StatsCollectionGrewTo": "Ваша калекцыя кніг павялічылася да…", "StatsSessions": "сеансаў", + "StatsTopAuthor": "ТОП АЎТАР", + "StatsTopAuthors": "ТОП АЎТАРЫ", + "StatsTopGenre": "ТОП ЖАНР", + "StatsTopGenres": "ТОП ЖАНРЫ", + "StatsTopMonth": "ТОП МЕСЯЦ", + "StatsTopNarrator": "ТОП ДЫКТАР", + "StatsTopNarrators": "ТОП ДЫКТАРЫ", "ToastAccountUpdateSuccess": "Уліковы запіс абноўлены", + "ToastAppriseUrlRequired": "Неабходна ўвесці URL-адрас Apprise", "ToastAsinRequired": "ASIN абавязковы", "ToastAuthorImageRemoveSuccess": "Відарыс аўтара выдалены", "ToastAuthorNotFound": "Аўтар \"{0}\" не знойдзены", @@ -810,6 +925,7 @@ "ToastAuthorUpdateMerged": "Аўтар аб'яднаны", "ToastAuthorUpdateSuccess": "Аўтар абноўлены", "ToastAuthorUpdateSuccessNoImageFound": "Аўтар абноўлены (відарыс не знойдзены)", + "ToastBackupAppliedSuccess": "Рэзервовая копія прыменена", "ToastBackupCreateFailed": "Не ўдалося стварыць рэзервовую копію", "ToastBackupCreateSuccess": "Рэзервовая копія створана", "ToastBackupDeleteFailed": "Не ўдалося выдаліць рэзервовую копію", @@ -819,12 +935,15 @@ "ToastBackupRestoreFailed": "Не ўдалося аднавіць рэзервовую копію", "ToastBackupUploadFailed": "Не ўдалося запампаваць рэзервовую копію", "ToastBackupUploadSuccess": "Рэзервовая копія запампавана", + "ToastBatchApplyDetailsToItemsSuccess": "Звесткі прыменены да элементаў", "ToastBookmarkCreateFailed": "Не ўдалося стварыць закладку", "ToastBookmarkCreateSuccess": "Закладка дададзена", "ToastBookmarkRemoveSuccess": "Закладка выдалена", "ToastBulkChapterInvalidCount": "Увядзіце лік ад 1 да 150", + "ToastCachePurgeFailed": "Не ўдалося ачысціць кэш", "ToastCachePurgeSuccess": "Кэш паспяхова ачышчаны", "ToastChapterLocked": "Раздзел заблакіраваны.", + "ToastChaptersHaveErrors": "Раздзелы маюць памылкі", "ToastChaptersMustHaveTitles": "Раздзелы павінны мець загалоўкі", "ToastChaptersRemoved": "Раздзелы выдалены", "ToastChaptersUpdated": "Раздзелы абноўлены", @@ -835,15 +954,18 @@ "ToastCoverSearchFailed": "Не ўдалося знайсці вокладку", "ToastCoverUpdateFailed": "Не ўдалося абнавіць вокладку", "ToastDateTimeInvalidOrIncomplete": "Дата і час указаны некарэктна або не цалкам", + "ToastDeleteFileFailed": "Не ўдалося выдаліць файл", "ToastDeleteFileSuccess": "Файл выдалены", "ToastDeviceAddFailed": "Не ўдалося дадаць прыладу", "ToastDeviceNameAlreadyExists": "Прылада для чытання электронных кніг з такой назвай ужо існуе", "ToastDeviceTestEmailFailed": "Не ўдалося адправіць тэставае электроннае пісьмо", + "ToastDeviceTestEmailSuccess": "Тэставы электронны ліст адпраўлены", "ToastEmailSettingsUpdateSuccess": "Налады электроннай пошты абноўлены", "ToastEncodeCancelFailed": "Не ўдалося скасаваць кадаванне", "ToastEncodeCancelSucces": "Кадаванне скасавана", "ToastEpisodeDownloadQueueClearFailed": "Не ўдалося ачысціць чаргу", - "ToastEpisodeDownloadQueueClearSuccess": "Чарга спампоўкі эпізодаў ачышчана", + "ToastEpisodeDownloadQueueClearSuccess": "Чарга спампоўвання выпускаў ачышчана", + "ToastEpisodeUpdateSuccess": "Абноўлена выпускаў: {0}", "ToastFailedToCreate": "Не ўдалося стварыць", "ToastFailedToDelete": "Не ўдалося выдаліць", "ToastFailedToLoadData": "Не ўдалося загрузіць даныя", @@ -851,7 +973,7 @@ "ToastFailedToShare": "Не ўдалося абагуліць", "ToastFailedToUpdate": "Не здалося абнавіць", "ToastInvalidImageUrl": "Памылковы URL-адрас відарыса", - "ToastInvalidMaxEpisodesToDownload": "Няправільная максімальная колькасць эпізодаў для спампоўкі", + "ToastInvalidMaxEpisodesToDownload": "Няправільная максімальная колькасць выпускаў для спампоўвання", "ToastItemCoverUpdateSuccess": "Вокладка элемента абноўлена", "ToastItemDeletedFailed": "Не ўдалося выдаліць элемент", "ToastItemDeletedSuccess": "Выдалены элемент", @@ -876,15 +998,24 @@ "ToastMustHaveAtLeastOnePath": "Павінен быць хаця б адзін шлях", "ToastNameEmailRequired": "Імя і электронная пошта абавязковыя", "ToastNameRequired": "Імя абавязковае", + "ToastNewApiKeyUserError": "Трэба выбраць карыстальніка", + "ToastNewEpisodesFound": "Знойдзена новых выпускаў: {0}", "ToastNewUserCreatedFailed": "Не ўдалося стварыць уліковы запіс: \"{0}\"", "ToastNewUserCreatedSuccess": "Новы ўліковы запіс створаны", + "ToastNewUserLibraryError": "Трэба выбраць хаця б адну бібліятэку", + "ToastNewUserPasswordError": "Мусіць мець пароль, толькі карыстальнік root можа мець пусты пароль", "ToastNewUserTagError": "Трэбаа выбраць хаця б адзін тэг", "ToastNewUserUsernameError": "Увядзіце імя карыстальніка", + "ToastNoNewEpisodesFound": "Новых выпускаў не знойдзена", "ToastNoRSSFeed": "У падкаста няма RSS-стужкі", "ToastNoUpdatesNecessary": "Абнаўленні не патрэбныя", "ToastNotificationCreateFailed": "Не ўдалося стварыць апавяшчэнне", "ToastNotificationDeleteFailed": "Не ўдалося выдаліць апавяшчэнне", + "ToastNotificationFailedMaximum": "Максімальная колькасць няўдалых спроб павінна быць >= 0", + "ToastNotificationQueueMaximum": "Максімальная чарга апавяшчэнняў павінна быць >= 0", "ToastNotificationSettingsUpdateSuccess": "Налады апавяшчэнняў абноўлены", + "ToastNotificationTestTriggerFailed": "Не ўдалося ўключыць тэставае апавяшчэнне", + "ToastNotificationTestTriggerSuccess": "Уключана тэставае апавяшчэнне", "ToastNotificationUpdateSuccess": "Апавяшчэнне абноўлена", "ToastPlaylistCreateFailed": "Не ўдалося стварыць плэй-ліст", "ToastPlaylistCreateSuccess": "Плэй-ліст створаны", @@ -892,8 +1023,9 @@ "ToastPlaylistUpdateSuccess": "Плэй-ліст абноўлены", "ToastPodcastCreateFailed": "Не ўдалося стварыць падкаст", "ToastPodcastCreateSuccess": "Падкаст паспяхова створаны", + "ToastPodcastEpisodeUpdated": "Выпуск абноўлены", "ToastPodcastGetFeedFailed": "Не ўдалося атрымаць стужку падкаста", - "ToastPodcastNoEpisodesInFeed": "У RSS-стужцы не знойдзена эпізодаў", + "ToastPodcastNoEpisodesInFeed": "У RSS-ленце не знойдзена выпускаў", "ToastPodcastNoRssFeed": "У падкаста няма RSS-стужкі", "ToastProgressIsNotBeingSynced": "Прагрэс не сінхранізуецца, перазапусціце прайграванне", "ToastProviderCreatedFailed": "Не ўдалося дадаць пастаўшчыка", @@ -905,16 +1037,24 @@ "ToastRemoveFailed": "Не ўдалося выдаліць", "ToastRemoveItemFromCollectionFailed": "Не ўдалося выдаліць элемент з калекцыі", "ToastRemoveItemFromCollectionSuccess": "Элемент выдалены з калекцыі", + "ToastRemoveItemsWithIssuesFailed": "Не ўдалося выдаліць элементы бібліятэкі з праблемамі", + "ToastRemoveItemsWithIssuesSuccess": "Выдалены элементы бібліятэкі з праблемамі", "ToastRenameFailed": "Не ўдалося перайменаваць", + "ToastScanFailed": "Не ўдалося адсканіраваць элемент бібліятэкі", "ToastSelectAtLeastOneUser": "Выберыце прынамсі аднаго карыстальніка", "ToastSendEbookToDeviceFailed": "Не ўдалося адправіць электронную кнігу на прыладу", "ToastSendEbookToDeviceSuccess": "Электронная кніга адпраўлена на прыладу \"{0}\"", + "ToastSeriesSubmitFailedSameName": "Немагчыма дадаць дзве серыі з аднолькавай назвай", + "ToastServerSettingsUpdateSuccess": "Налады сервера абноўлены", "ToastSessionCloseFailed": "Не ўдалося закрыць сеанс", "ToastSessionDeleteFailed": "Не ўдалося выдаліць сеанс", "ToastSessionDeleteSuccess": "Сеанс выдалены", "ToastSleepTimerDone": "Таймер сну скончыўся... Хр-р-р", + "ToastSortingPrefixesEmptyError": "Мусіць мець хаця б адзін прэфікс сартавання", + "ToastSortingPrefixesUpdateSuccess": "Прэфіксы сартавання абноўлены ({0} элементаў)", "ToastTitleRequired": "Загаловак абавязковы", "ToastUnknownError": "Невядомая памылка", + "ToastUploaderFilepathExistsError": "Файл \"{0}\" ужо існуе на серверы", "ToastUploaderItemExistsInSubdirectoryError": "Элемент \"{0}\" выкарыстоўвае падкаталог шляху загрузкі.", "ToastUserDeleteFailed": "Не ўдалося выдаліць карыстальніка", "ToastUserDeleteSuccess": "Карыстальнік выдалены", @@ -924,8 +1064,10 @@ "ToastUserRootRequireName": "Неабходна ўвесці імя карыстальніка адміністратара", "TooltipAddChapters": "Дадаць раздзел(ы)", "TooltipAddOneSecond": "Дадаць 1 секунду", + "TooltipAdjustChapterStart": "Націсніце, каб наладзіць час пачатку", "TooltipLockAllChapters": "Заблакіраваць усе раздзелы", "TooltipLockChapter": "Заблакіраваць раздзел (Shift+націсканне для дыяпазону)", "TooltipSubtractOneSecond": "Адняць 1 секунду", - "TooltipUnlockAllChapters": "Разблакіраваць усе раздзелы" + "TooltipUnlockAllChapters": "Разблакіраваць усе раздзелы", + "TooltipUnlockChapter": "Разблакіраваць раздзел (Shift+націсканне для выбару дыяпазону)" } From 9afa39e29e55c3b94a218e6242c1fa9646f04435 Mon Sep 17 00:00:00 2001 From: Pavel Miniutka Date: Thu, 26 Feb 2026 13:42:50 +0100 Subject: [PATCH 053/124] Translated using Weblate (Belarusian) Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/be/ --- client/strings/be.json | 104 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 98 insertions(+), 6 deletions(-) diff --git a/client/strings/be.json b/client/strings/be.json index cc5dc7bc5..6dbef9cb9 100644 --- a/client/strings/be.json +++ b/client/strings/be.json @@ -36,7 +36,7 @@ "ButtonEnable": "Уключыць", "ButtonFireAndFail": "Агонь і няўдача", "ButtonFireOnTest": "Тэст на вогнеўстойлівасць", - "ButtonForceReScan": "Прымусовае паўторнае сканаванне", + "ButtonForceReScan": "Прымусова паўторна сканіраваць", "ButtonFullPath": "Поўны шлях", "ButtonHide": "Схаваць", "ButtonHome": "Галоўная", @@ -73,7 +73,7 @@ "ButtonQuickEmbed": "Хуткае ўбудаванне", "ButtonQuickEmbedMetadata": "Хуткае ўбудаванне метаданых", "ButtonQuickMatch": "Хуткі пошук", - "ButtonReScan": "Паўторнае сканаванне", + "ButtonReScan": "Паўторна сканіраваць", "ButtonRead": "Чытаць", "ButtonReadLess": "Чытаць менш", "ButtonReadMore": "Чытаць больш", @@ -314,7 +314,7 @@ "LabelDeviceInfo": "Інфармацыя пра прыладу", "LabelDeviceIsAvailableTo": "Прылада даступная для...", "LabelDirectory": "Каталог", - "LabelDiscFromFilename": "Дыск з назвы файла", + "LabelDiscFromFilename": "Дыск з файла", "LabelDiscFromMetadata": "Дыск з метаданых", "LabelDiscover": "Знайсці", "LabelDownload": "Спампаваць", @@ -345,6 +345,7 @@ "LabelEncodingStartedNavigation": "Пасля запуску задачы вы можаце перайсці на іншую старонку.", "LabelEncodingTimeWarning": "Кадаванне можа заняць да 30 хвілін.", "LabelEncodingWarningAdvancedSettings": "Увага: Не абнаўляйце гэтыя налады, калі вы не знаёмыя з параметрамі кадавання ffmpeg.", + "LabelEncodingWatcherDisabled": "Калі ў вас адключана адсочванне змен у папцы, пасля трэба будзе паўторна сканіраваць гэту аўдыякнігу.", "LabelEnd": "Канец", "LabelEndOfChapter": "Канец раздзела", "LabelEpisode": "Выпуск", @@ -390,6 +391,7 @@ "LabelFull": "Поўны", "LabelGenre": "Жанр", "LabelGenres": "Жанры", + "LabelHardDeleteFile": "Жорстка выдаляць файл", "LabelHasEbook": "Мае электронную кнігу", "LabelHasSupplementaryEbook": "Мае дадатковую электронную кнігу", "LabelHideSubtitles": "Схаваць падзагалоўкі", @@ -414,6 +416,8 @@ "LabelIntervalEveryMinute": "Кожную хвіліну", "LabelInvert": "Інвертаваць", "LabelItem": "Элемент", + "LabelJumpBackwardAmount": "Час пераходу назад", + "LabelJumpForwardAmount": "Час пераходу наперад", "LabelLanguage": "Мова", "LabelLanguageDefaultServer": "Прадвызначаная мова сервера", "LabelLanguages": "Мовы", @@ -444,6 +448,7 @@ "LabelLookForNewEpisodesAfterDate": "Шукаць новыя выпускі пасля гэтай даты", "LabelLowestPriority": "Найніжэйшы прыярытэт", "LabelMatchConfidence": "Упэўненасць", + "LabelMatchExistingUsersBy": "Параўноўваць існуючых карыстальнікаў па", "LabelMatchExistingUsersByDescription": "Выкарыстоўваецца для падключэння існуючых карыстальнікаў. Пасля падключэння карыстальнікі будуць супастаўляцца з дапамогай унікальнага ідэнтыфікатара ад пастаўшчыка SSO", "LabelMaxEpisodesToDownload": "Максімальная колькасць выпускаў для спампоўвання. 0 – неабмежаваная колькасць.", "LabelMaxEpisodesToDownloadPerCheck": "Максімальная колькасць новых выпускаў для спампоўвання за праверку", @@ -479,6 +484,7 @@ "LabelNoEpisodesSelected": "Не выбрана ніводнага выпуску", "LabelNotFinished": "Незавершана", "LabelNotStarted": "Не пачата", + "LabelNotes": "Заўвагі", "LabelNotificationAppriseURL": "URL-адрасы Apprise", "LabelNotificationAvailableVariables": "Даступныя пераменныя", "LabelNotificationBodyTemplate": "Шаблон зместу", @@ -491,6 +497,9 @@ "LabelNumberOfBooks": "Колькасць кніг", "LabelNumberOfChapters": "Колькасць раздзелаў:", "LabelNumberOfEpisodes": "Колькасць выпускаў", + "LabelOpenIDAdvancedPermsClaimDescription": "Назва прэтэнзіі OpenID, якая змяшчае пашыраныя дазволы для дзеянняў карыстальніка ў праграме, якія будуць прымяняцца да роляў, якія не з'яўляюцца адміністратарамі (калі наладжана). Калі прэтэнзія адсутнічае ў адказе, доступ да ABS будзе забаронены. Калі адсутнічае адзін параметр, ён будзе разглядацца як false. Пераканайцеся, што прэтэнзія пастаўшчыка ідэнтыфікацыі адпавядае чаканай структуры:", + "LabelOpenIDClaims": "Пакіньце наступныя параметры пустымі, каб адключыць пашыранае прызначэнне груп і дазволаў, аўтаматычна прызначаючы групу \"Карыстальнік\".", + "LabelOpenIDGroupClaimDescription": "Назва прэтэнзіі OpenID, якая змяшчае спіс груп карыстальніка. Звычайна іх называюць групамі. Калі наладжана, праграма будзе аўтаматычна прызначаць ролі на аснове членства карыстальніка ў групах, пры ўмове, што ў прэтэнзіі гэтыя групы названы без уліку рэгістра: \"адміністратар\", \"карыстальнік або \"госць\". Прэтэнзія павінна ўтрымліваць спіс, і калі карыстальнік належыць да некалькіх груп, праграма прызначыць ролю, якая адпавядае найвышэйшаму ўзроўню доступу. Калі ніводная група не супадае, доступ будзе забаронены.", "LabelOpenRSSFeed": "Адкрыць RSS-стужку", "LabelOverwrite": "Перазапісаць", "LabelPaginationPageXOfY": "Старонка {0} з {1}", @@ -508,6 +517,7 @@ "LabelPersonalYearReview": "Вынікі года ({0})", "LabelPhotoPathURL": "Шлях/URL-адрас фота", "LabelPlayMethod": "Метад прайгравання", + "LabelPlaybackRateIncrementDecrement": "Павелічэнне/памяншэнне хуткасці прайгравання", "LabelPlayerChapterNumberMarker": "{0} з {1}", "LabelPlaylists": "Плэй-лісты", "LabelPodcast": "Падкаст", @@ -515,13 +525,17 @@ "LabelPodcastType": "Тып падкаста", "LabelPodcasts": "Падкасты", "LabelPort": "Порт", + "LabelPrefixesToIgnore": "Прэфіксы, якія трэба ігнараваць (без уліку рэгістра)", "LabelPreventIndexing": "Прадухіліць індэксацыю вашай стужкі каталогамі падкастаў iTunes і Google", + "LabelPrimaryEbook": "Асноўная электронная кніга", "LabelProgress": "Прагрэс", "LabelProvider": "Пастаўшчык", "LabelProviderAuthorizationValue": "Значэнне загалоўка аўтарызацыі", "LabelPubDate": "Дата публікацыі", "LabelPublishYear": "Год публікацыі", "LabelPublishedDate": "Апублікавана {0}", + "LabelPublishedDecade": "Дзесяцігоддзе публікацыі", + "LabelPublishedDecades": "Дзесяцігоддзі публікацыі", "LabelPublisher": "Выдавец", "LabelPublishers": "Выдаўцы", "LabelRSSFeedCustomOwnerEmail": "Карыстальніцкая электронная пошта ўладальніка", @@ -540,8 +554,10 @@ "LabelRecommended": "Рэкамендаваныя", "LabelRedo": "Узнавіць", "LabelRegion": "Рэгіён", + "LabelReleaseDate": "Дата выпуску", "LabelRemoveAllMetadataAbs": "Выдаліць усе файлы metadata.abs", "LabelRemoveAllMetadataJson": "Выдаліць усе файлы metadata.json", + "LabelRemoveAudibleBranding": "Выдаляць уступленне і завяршэнне Audible з раздзелаў", "LabelRemoveCover": "Выдаліць вокладку", "LabelRemoveMetadataFile": "Выдаліць файлы метаданых у папках элементаў бібліятэкі", "LabelRemoveMetadataFileHelp": "Выдаліць усе файлы metadata.json і metadata.abs у {0} папках.", @@ -570,7 +586,9 @@ "LabelSettingsAudiobooksOnly": "Толькі аўдыякнігі", "LabelSettingsAudiobooksOnlyHelp": "Пры ўключэнні гэтай налады файлы электронных кніг будуць ігнаравацца, калі толькі яны не знаходзяцца ў папцы з аўдыякнігамі. У такім выпадку яны будуць пазначаны як дадатковыя электронныя кнігі.", "LabelSettingsBookshelfViewHelp": "Рэалістычны дызайн з драўлянымі паліцамі", + "LabelSettingsChromecastSupport": "Падтрымка Chromecast", "LabelSettingsDateFormat": "Фарматы даты", + "LabelSettingsEnableWatcher": "Аўтаматычна сачыць за зменамі ў бібліятэцках", "LabelSettingsEnableWatcherForLibrary": "Аўтаматычна сачыць за зменамі ў бібліятэцы", "LabelSettingsEnableWatcherHelp": "Адключае аўтаматычнае дадаванне/абнаўленне элементаў пры выяўленні змен у файлах. *Патрабуецца перазапуск сервера", "LabelSettingsEpubsAllowScriptedContent": "Дазваляць скрыпты ў файлах EPUB", @@ -592,21 +610,30 @@ "LabelSettingsParseSubtitlesHelp": "Выдзяляць падзагаловак з назваў папак аўдыякніг.
Падзагаловак павінен быць аддзелены сімвалам \" - \".
Напрыклад, \"Назва кнігі - Падзагаловак тут\" мае падзагаловак \"Падзагаловак тут\"", "LabelSettingsPreferMatchedMetadata": "Аддаваць перавагу супадаючым метаданым", "LabelSettingsPreferMatchedMetadataHelp": "Супадаючыя даныя будуць замяняць звесткі элемента пры выкарыстанні функцыі Хуткі пошук. Прадвызначана Хуткі пошук запаўняе толькі адсутныя звесткі.", + "LabelSettingsSkipMatchingBooksWithASIN": "Прапусціць параўнанне кніг, якія ўжо маюць ASIN", + "LabelSettingsSkipMatchingBooksWithISBN": "Прапусціць параўнанне кніг, якія ўжо маюць ISBN", + "LabelSettingsSortingIgnorePrefixes": "Ігнараваць прэфіксы пры сартаванні", "LabelSettingsSortingIgnorePrefixesHelp": "напрыклад, для прэфікса \"the\" загаловак кнігі \"The Book Title\" будзе сартавацца як \"Book Title, The\"", "LabelSettingsSquareBookCovers": "Выкарыстоўваць квадратныя вокладкі кніг", + "LabelSettingsSquareBookCoversHelp": "Аддаваць перавагу квадратным вокладкам замест стандартных вокладак з суадносінамі бакоў 1.6:1", + "LabelSettingsStoreCoversWithItem": "Захоўваць вокладкі з элементам", "LabelSettingsStoreCoversWithItemHelp": "Прадвызначана вокладкі захоўваюцца ў /metadata/items, уключэнне гэтай опцыі забяспечыць захоўванне вокладак у папцы элемента бібліятэкі. Захоўвацца будзе толькі адзін файл з назвай \"cover\"", "LabelSettingsStoreMetadataWithItem": "Захоўваць метаданыя разам з элементам", "LabelSettingsStoreMetadataWithItemHelp": "Прадвызначана метаданыя захоўваюцца ў /metadata/items. Пры ўключэнні гэтай опцыі файлаў метаданых будуць захоўвацца ў папках элементаў бібліятэкі", "LabelSettingsTimeFormat": "Фармат часу", "LabelShare": "Абагуліць", "LabelShareDownloadableHelp": "Дазваляе карыстальнікам, якія маюць спасылку, спампоўваць ZIP-архіў элемента бібліятэкі.", + "LabelShareOpen": "Абагульванне адкрыта", + "LabelShareURL": "URL-адрас для абагульвання", "LabelShowAll": "Паказаць усё", "LabelShowSeconds": "Паказваць секунды", "LabelShowSubtitles": "Паказаць падзагалоўкі", "LabelSize": "Памер", "LabelSleepTimer": "Таймер сну", + "LabelSlug": "Ідэнтыфікатар", "LabelSortAscending": "Па ўзрастанні", "LabelSortDescending": "Па ўбыванні", + "LabelSortPubDate": "Сартаваць па даце публікацыі", "LabelStart": "Пачаць", "LabelStartTime": "Час пачатку", "LabelStarted": "Пачата", @@ -662,7 +689,7 @@ "LabelToolsSplitM4bDescription": "Стварэнне MP3 з M4B, падзеленага па раздзелах, з убудаванымі метаданымі, відарысам вокладкі і раздзеламі.", "LabelTotalDuration": "Агульная працягласць", "LabelTotalTimeListened": "Агульны час праслухоўвання", - "LabelTrackFromFilename": "Трэк з назвы файла", + "LabelTrackFromFilename": "Трэк з файла", "LabelTrackFromMetadata": "Трэк з метаданых", "LabelTracks": "Трэкі", "LabelTracksMultiTrack": "Некалькі трэкаў", @@ -703,12 +730,14 @@ "LabelXItems": "{0} элементаў", "LabelYearReviewHide": "Схаваць вынікі года", "LabelYearReviewShow": "Азнаёміцца з вынікамі года", - "LabelYourAudiobookDuration": "Працягласць вашай аўдыякнігі", + "LabelYourAudiobookDuration": "Працягласць аўдыякнігі", "LabelYourBookmarks": "Вашы закладкі", "LabelYourPlaylists": "Вашы плэй-лісты", "LabelYourProgress": "Ваш прагрэс", "MessageAddToPlayerQueue": "Дадаць у чаргу прайгравальніка", "MessageAppriseDescription": "Каб выкарыстоўваць гэтую функцыю, вам спатрэбіцца запусціць асобнік Apprise API або API, які будзе апрацоўваць тыя ж запыты.
URL Apprise API павінен быць поўным шляхам для адпраўкі апавяшчэння, напрыклад, калі ваш API працуе па адрасе http://192.168.1.1:8337, то вы павінны ўвесці http://192.168.1.1:8337/notify.", + "MessageAsinCheck": "Пераканайцеся, што выкарыстоўваеце ASIN з правільнага рэгіёна Audible, а не Amazon.", + "MessageAuthenticationLegacyTokenWarning": "Састарэлыя токены API будуць выдалены ў будучыні. Замест іх выкарыстоўвайце ключы API.", "MessageAuthenticationOIDCChangesRestart": "Перазапусціце сервер пасля захавання, каб прымяніць змены OIDC.", "MessageAuthenticationSecurityMessage": "Дзеля бяспекі была палепшана аўтэнтыфікацыя. Усім карыстальнікам трэба паўторна ўвайсці ў сістэму.", "MessageBackupsDescription": "Рэзервовыя копіі ўключаюць карыстальнікаў, іх прагрэс, падрабязнасці элементаў бібліятэкі, налады сервера і відарысы, якія захоўваюцца ў /metadata/items і /metadata/authors. Рэзервовыя копіі не ўключаюць файлы, якія захоўваюцца ў папках бібліятэкі.", @@ -724,9 +753,12 @@ "MessageBookshelfNoResultsForFilter": "Няма вынікаў для фільтра \"{0}: {1}\"", "MessageBookshelfNoResultsForQuery": "Няма вынікаў па запыце", "MessageBookshelfNoSeries": "У вас няма серый", + "MessageBulkChapterPattern": "Колькі раздзелаў вы хочаце дадаць з дапамогай гэтага ўзору нумарацыі?", + "MessageChapterEndIsAfter": "Канец раздзела ідзе пасля канца аўдыякнігі", "MessageChapterErrorFirstNotZero": "Першы раздзел павінен пачынацца з 0", "MessageChapterErrorStartGteDuration": "Няправільны час пачатку: ён павінен быць меншым за працягласць аўдыякнігі", "MessageChapterErrorStartLtPrev": "Няправільны час пачатку: ён павінен быць большым або роўным часу пачатку папярэдняга раздзела", + "MessageChapterStartIsAfter": "Пачатак раздзела ідзе пасля канца аўдыякнігі", "MessageChaptersNotFound": "Раздзелы не знойдзены", "MessageCheckingCron": "Праверка cron...", "MessageConfirmCloseFeed": "Вы ўпэўнены, што хочаце закрыць гэту стужку?", @@ -741,19 +773,24 @@ "MessageConfirmDeleteNotification": "Вы ўпэўнены, што хочаце выдаліць гэта апавяшчэнне?", "MessageConfirmDeleteSession": "Вы ўпэўнены, што хочаце выдаліць гэты сеанс?", "MessageConfirmEmbedMetadataInAudioFiles": "Вы ўпэўнены, што хочаце ўбудаваць метаданыя ў {0} аўдыяфайлаў?", + "MessageConfirmForceReScan": "Вы ўпэўнены, што хочаце прымусова паўторна сканіраваць?", "MessageConfirmMarkAllEpisodesFinished": "Вы ўпэўнены, што хочаце пазначыць усе выпускі як завершаныя?", "MessageConfirmMarkAllEpisodesNotFinished": "Вы ўпэўнены, што хочаце пазначыць усе выпускі як незавершаныя?", "MessageConfirmMarkItemFinished": "Вы ўпэўнены, што хочаце пазначыць \"{0}\" як завершаны?", "MessageConfirmMarkItemNotFinished": "Вы ўпэўнены, што хочаце пазначыць \"{0}\" як незавершаны?", "MessageConfirmMarkSeriesFinished": "Вы ўпэўнены, што хочаце пазначыць усе кнігі ў гэтай серыі як завершаныя?", "MessageConfirmMarkSeriesNotFinished": "Вы ўпэўнены, што хочаце пазначыць усе кнігі ў гэтай серыі як незавершаныя?", + "MessageConfirmNotificationTestTrigger": "Уключыць гэта апавяшчэнне з тэставымі данымі?", "MessageConfirmPurgeCache": "Ачышчэнне кэшу выдаліць увесь каталог па адрасе /metadata/cache.

Вы ўпэўнены, што хочаце выдаліць каталог кэшу?", "MessageConfirmPurgeItemsCache": "Ачышчэнне кэшу элементаў выдаліць увесь каталог па адрасе /metadata/cache/items.
Вы ўпэўнены?", + "MessageConfirmQuickEmbed": "Увага! Хуткае ўбудаванне не стварае рэзервовых копій аўдыяфайлаў. Пераканайцеся, што ў вас ёсць рэзервовая копія аўдыяфайлаў.

Хочаце працягнуць?", "MessageConfirmQuickMatchEpisodes": "Хуткае супадзенне выпускаў перазапіша дэталі, калі супадзенне будзе знойдзена. Будуць абноўлены толькі выпускі, якія не супадаюць. Вы ўпэўнены?", + "MessageConfirmReScanLibraryItems": "Вы ўпэўнены, што хочаце паўторна сканіраваць {0} элементаў?", "MessageConfirmRemoveAllChapters": "Вы ўпэўнены, што хочаце выдаліць усе раздзелы?", "MessageConfirmRemoveAuthor": "Вы ўпэўнены, што хочаце выдаліць аўтара \"{0}\"?", "MessageConfirmRemoveCollection": "Вы ўпэўнены, што хочаце выдаліць калекцыю \"{0}\"?", "MessageConfirmRemoveEpisode": "Вы ўпэўнены, што хочаце выдаліць выпуск \"{0}\"?", + "MessageConfirmRemoveEpisodeNote": "Заўвага: Аўдыяфайл не будзе выдалены, калі не ўключыць параметр \"Жорстка выдаляць файл\"", "MessageConfirmRemoveEpisodes": "Вы ўпэўнены, што хочаце выдаліць {0} выпускаў?", "MessageConfirmRemoveListeningSessions": "Вы ўпэўнены, што хочаце выдаліць {0} сеансаў праслухоўвання?", "MessageConfirmRemoveMetadataFiles": "Вы ўпэўнены, што хочаце выдаліць усе файлы metadata.{0} у папках элементаў бібліятэкі?", @@ -767,14 +804,19 @@ "MessageConfirmRenameTagWarning": "Увага! Падобны тэг з іншым рэгістрам ужо існуе: \"{0}\".", "MessageConfirmResetProgress": "Вы ўпэўнены, што хочаце скінуць свой прагрэс?", "MessageConfirmSendEbookToDevice": "Вы ўпэўнены, што хочаце адправіць {0} электронную кнігу \"{1}\" на прыладу \"{2}\"?", + "MessageConfirmUnlinkOpenId": "Вы ўпэўнены, што хочаце адвязаць гэтага карыстальніка ад OpenID?", + "MessageDaysListenedInTheLastYear": "{0} дзён праслухоўвання за апошні год", "MessageDownloadingEpisode": "Спампоўванне выпуску", "MessageDragFilesIntoTrackOrder": "Перацягніце файлы ў правільным парадку трэкаў", + "MessageEmbedFailed": "Не ўдалося ўбудаваць!", "MessageEmbedFinished": "Убудаванне завершана!", "MessageEmbedQueue": "У чарзе на ўбудаванне метаданых (у чарзе {0})", "MessageEpisodesQueuedForDownload": "{0} выпуск(-аў) у чарзе спампоўвання", "MessageEreaderDevices": "Каб забяспечыць дастаўку электронных кніг, вам можа спатрэбіцца дадаць вышэйзгаданы адрас электроннай пошты як дазволенага адпраўніка для кожнай прылады, пералічанай ніжэй.", "MessageFeedURLWillBe": "URL стужкі будзе {0}", "MessageFetching": "Атрыманне...", + "MessageForceReScanDescription": "прасканіруе ўсе файлы зноў, як пры новым сканаванні. Тэгі ID3 аўдыёфайлаў, файлы OPF і тэкставыя файлы будуць сканіравацца як новыя.", + "MessageHeatmapListeningTimeTooltip": "{0} праслухана on {1}", "MessageHeatmapNoListeningSessions": "Няма сеансаў праслухоўвання на {0}", "MessageImportantNotice": "Важная заўвага!", "MessageInsertChapterBelow": "Уставіць раздзел ніжэй", @@ -785,6 +827,7 @@ "MessageLoading": "Загрузка...", "MessageLoadingFolders": "Загрузка папак...", "MessageLogsDescription": "Журналы захоўваюцца ў каталогу /metadata/logs у фармаце JSON. Журналы памылак захоўваюцца ў файле /metadata/logs/crashlogs.txt.", + "MessageM4BFailed": "Памылка M4B!", "MessageM4BFinished": "M4B завершана!", "MessageMapChapterTitles": "Супаставіць загалоўкі раздзелаў з існуючымі раздзеламі аўдыякнігі без змянення пазнак часу", "MessageMarkAllEpisodesFinished": "Пазначыць усе выпускі як завершаныя", @@ -834,6 +877,8 @@ "MessagePodcastHasNoRSSFeedForMatching": "У падкаста няма URL RSS-стужкі для супадзення", "MessagePodcastSearchField": "Увядзіце пошукавы запыт або URL RSS-стужкі", "MessageQuickEmbedInProgress": "Выконваецца хуткае ўбудаванне", + "MessageQuickEmbedQueue": "Пастаўлена ў чаргу для хуткага ўбудавання ({0} у чарзе)", + "MessageQuickMatchAllEpisodes": "Хуткае параўнанне ўсіх выпускаў", "MessageQuickMatchDescription": "Запоўніць пустыя звесткі элемента і вокладку першым вынікам супадзення з '{0}'. Не замяняе звесткіі, калі параметр \"Аддаваць перавагу супадаючым метаданым\" на серверы не ўключана.", "MessageRemoveChapter": "Выдаліць раздзел", "MessageRemoveEpisodes": "Выдаліць выпускі ({0})", @@ -843,6 +888,7 @@ "MessageResetChaptersConfirm": "Вы ўпэўнены, што хочаце скінуць раздзелы і адрабіць зробленыя вамі змены?", "MessageRestoreBackupConfirm": "Вы ўпэўнены, што хочаце аднавіць рэзервовую копію, створаную", "MessageRestoreBackupWarning": "Аднаўленне рэзервовай копіі перазапіша ўсю базу даных, размешчаную ў /config, а таксама відарысы вокладкі ў /metadata/items і /metadata/authors.

Рэзервовыя копіі не змяняюць файлы ў папках бібліятэкі. Калі вы ўключылі налады сервера для захоўвання воклак і метаданых у папках бібліятэкі, гэтыя файлы не будуць захаваныя ў рэзервовых копіях і не зменяцца.

Усе кліенты, якія карыстаюцца вашым серверам, будуць аўтаматычна абноўлены.", + "MessageScheduleLibraryScanNote": "Большасці карыстальнікаў рэкамендуецца не выключаць гэтую функцыю і пакідаць уключанай наладу \"Аўтаматычна сачыць за зменамі ў бібліятэцы\" — яна будзе аўтаматычна выяўляць змены ў папках бібліятэкі. Уключыце гэту функцыю, калі \"Аўтаматычна сачыць за зменамі ў бібліятэцы\" не працуе для вашай файлавай сістэмы (напрыклад, NFS).", "MessageScheduleRunEveryWeekdayAtTime": "Выконваць кожныя {0} у {1}", "MessageSearchResultsFor": "Вынікі пошуку для", "MessageSelected": "Выбрана: {0}", @@ -851,6 +897,7 @@ "MessageSetChaptersFromTracksDescription": "Задаць раздзелы, выкарыстоўваючы кожны аўдыяфайл у якасці раздзела і назву аўдыяфайла ў якасці загалоўка раздзела", "MessageShareExpirationWillBe": "Тэрмін дзеяння будзе {0}", "MessageShareExpiresIn": "Тэрмін дзеяння заканчваецца праз {0}", + "MessageShareURLWillBe": "URL-адрас для абагульвання будзе {0", "MessageStartPlaybackAtTime": "Пачаць прайграванне для \"{0}\" з {1}?", "MessageTaskAudioFileNotWritable": "Аўдыяфайл \"{0}\" недаступны для запісу", "MessageTaskCanceledByUser": "Задача скасавана карыстальнікам", @@ -887,10 +934,25 @@ "MessageTaskScanningFileChanges": "Сканіраванне змяненняў у файле \"{0}\"", "MessageTaskScanningLibrary": "Сканіраванне бібліятэкі \"{0}\"", "MessageTaskTargetDirectoryNotWritable": "Мэтавы каталог недаступны для запісу", + "MessageThinking": "Думаю...", + "MessageUploaderItemFailed": "Не ўдалося запампаваць", + "MessageUploaderItemSuccess": "Паспяхова запампавана!", + "MessageUploading": "Запампоўванне...", + "MessageValidCronExpression": "Карэктны выраз cron", + "MessageWatcherIsDisabledGlobally": "Адсочванне змен у папках адключана глабальна ў наладах сервера", + "MessageXLibraryIsEmpty": "{0} Бібліятэка пустая!", + "MessageYourAudiobookDurationIsLonger": "Працягласць аўдыякнігі большая за знойдзеную працягласць", + "MessageYourAudiobookDurationIsShorter": "Працягласць аўдыякнігі карацейшая за знойдзеную працягласць", + "NoteChangeRootPassword": "Толькі карыстальнік root можа мець пусты пароль", "NoteChapterEditorTimes": "Заўвага: Час пачатку першага раздзела павінен заставацца 0:00, а час пачатку апошняга раздзела не можа перавышаць працягласць гэтай аўдыякнігі.", + "NoteFolderPicker": "Заўвага: ужо супастаўленыя папкі адлюстроўвацца не будуць", "NoteRSSFeedPodcastAppsHttps": "Папярэджанне: большасць праграм для падкастаў патрабуюць, каб URL RSS-стужкі выкарыстоўваў HTTPS", "NoteRSSFeedPodcastAppsPubDate": "Папярэджанне: адзін ці больш выпускаў не маюць даты публікацыі. Некаторыя праграмы для падкастаў патрабуюць гэтага.", "NoteUploaderFoldersWithMediaFiles": "Папкі з медыяфайламі будуць апрацоўвацца як асобныя элементы бібліятэкі.", + "NoteUploaderOnlyAudioFiles": "Пры запампоўванні толькі аўдыяфайлаў кожны аўдыяфайл будзе апрацоўвацца як асобная аўдыякніга.", + "NoteUploaderUnsupportedFiles": "Файлы, якія не падтрымліваюцца, ігнаруюцца. Пры выбары або выдаленні папкі іншыя файлы, якія не знаходзяцца ў папцы элемента, ігнаруюцца.", + "NotificationOnBackupCompletedDescription": "Спрацоўвае пасля завяршэння рэзервовага капіравання", + "NotificationOnBackupFailedDescription": "Спрацоўвае пры збоі рэзервовага капіравання", "NotificationOnEpisodeDownloadedDescription": "Спрацоўвае, калі выпуск падкаста аўтаматычна спампоўваецца", "NotificationOnRSSFeedDisabledDescription": "Спрацоўвае, калі аўтаматычнае спампоўванне выпускаў адключана з-за занадта вялікай колькасці няўдалых спроб", "NotificationOnRSSFeedFailedDescription": "Спрацоўвае, пры памылцы запыту RSS-стужкі для аўтаматычнага спампоўвання выпуску", @@ -903,11 +965,13 @@ "PlaceholderSearchEpisode": "Пошук выпуску...", "StatsAuthorsAdded": "дададзена аўтараў", "StatsBooksAdded": "дададзена кніг", + "StatsBooksAdditional": "Некаторыя дапаўненні ўключаюць…", "StatsBooksFinished": "завершана кніг", "StatsBooksFinishedThisYear": "Некаторыя кнігі завершаны ў гэтым годзе…", "StatsBooksListenedTo": "кнігі, якія былі праслуханы", "StatsCollectionGrewTo": "Ваша калекцыя кніг павялічылася да…", "StatsSessions": "сеансаў", + "StatsSpentListening": "праслухана", "StatsTopAuthor": "ТОП АЎТАР", "StatsTopAuthors": "ТОП АЎТАРЫ", "StatsTopGenre": "ТОП ЖАНР", @@ -915,6 +979,8 @@ "StatsTopMonth": "ТОП МЕСЯЦ", "StatsTopNarrator": "ТОП ДЫКТАР", "StatsTopNarrators": "ТОП ДЫКТАРЫ", + "StatsTotalDuration": "З агульнай працягласцю…", + "StatsYearInReview": "ВЫНІКІ ГОДА", "ToastAccountUpdateSuccess": "Уліковы запіс абноўлены", "ToastAppriseUrlRequired": "Неабходна ўвесці URL-адрас Apprise", "ToastAsinRequired": "ASIN абавязковы", @@ -936,6 +1002,12 @@ "ToastBackupUploadFailed": "Не ўдалося запампаваць рэзервовую копію", "ToastBackupUploadSuccess": "Рэзервовая копія запампавана", "ToastBatchApplyDetailsToItemsSuccess": "Звесткі прыменены да элементаў", + "ToastBatchDeleteFailed": "Памылка групавога выдалення", + "ToastBatchDeleteSuccess": "Групавое выдаленне выканана", + "ToastBatchQuickMatchFailed": "Памылка групавога хуткага параўнання!", + "ToastBatchQuickMatchStarted": "Групавое хуткае параўнанне {0} кніг запушчана!", + "ToastBatchUpdateFailed": "Памылка групавога абнаўлення", + "ToastBatchUpdateSuccess": "Групавое абнаўленне выканана", "ToastBookmarkCreateFailed": "Не ўдалося стварыць закладку", "ToastBookmarkCreateSuccess": "Закладка дададзена", "ToastBookmarkRemoveSuccess": "Закладка выдалена", @@ -943,7 +1015,11 @@ "ToastCachePurgeFailed": "Не ўдалося ачысціць кэш", "ToastCachePurgeSuccess": "Кэш паспяхова ачышчаны", "ToastChapterLocked": "Раздзел заблакіраваны.", + "ToastChapterStartTimeAdjusted": "Час пачатку раздзела адкарэктаваны на {0} секунд", + "ToastChaptersAllLocked": "Усе раздзелы заблакіраваны. Разблакіруйце некаторыя раздзелы, каб зрушыць іх час.", "ToastChaptersHaveErrors": "Раздзелы маюць памылкі", + "ToastChaptersInvalidShiftAmountLast": "Памылковая велічыня зруху. Час пачатку апошняга раздзела перавышае працягласць гэтай аўдыякнігі.", + "ToastChaptersInvalidShiftAmountStart": "Памылковая велічыня зруху. Першы раздзел будзе мець нулявую або адмоўную працягласць і будзе перазапісаны другім раздзелам. Павялічце пачатковую працягласць другога раздзела.", "ToastChaptersMustHaveTitles": "Раздзелы павінны мець загалоўкі", "ToastChaptersRemoved": "Раздзелы выдалены", "ToastChaptersUpdated": "Раздзелы абноўлены", @@ -966,6 +1042,7 @@ "ToastEpisodeDownloadQueueClearFailed": "Не ўдалося ачысціць чаргу", "ToastEpisodeDownloadQueueClearSuccess": "Чарга спампоўвання выпускаў ачышчана", "ToastEpisodeUpdateSuccess": "Абноўлена выпускаў: {0}", + "ToastErrorCannotShare": "Немагчыма абагуліць на гэтай прыладзе", "ToastFailedToCreate": "Не ўдалося стварыць", "ToastFailedToDelete": "Не ўдалося выдаліць", "ToastFailedToLoadData": "Не ўдалося загрузіць даныя", @@ -974,6 +1051,8 @@ "ToastFailedToUpdate": "Не здалося абнавіць", "ToastInvalidImageUrl": "Памылковы URL-адрас відарыса", "ToastInvalidMaxEpisodesToDownload": "Няправільная максімальная колькасць выпускаў для спампоўвання", + "ToastInvalidUrl": "Памылковы URL-адрас", + "ToastInvalidUrls": "Адзін або некалькі URL-адрасоў памылковыя", "ToastItemCoverUpdateSuccess": "Вокладка элемента абноўлена", "ToastItemDeletedFailed": "Не ўдалося выдаліць элемент", "ToastItemDeletedSuccess": "Выдалены элемент", @@ -1040,20 +1119,33 @@ "ToastRemoveItemsWithIssuesFailed": "Не ўдалося выдаліць элементы бібліятэкі з праблемамі", "ToastRemoveItemsWithIssuesSuccess": "Выдалены элементы бібліятэкі з праблемамі", "ToastRenameFailed": "Не ўдалося перайменаваць", + "ToastRescanFailed": "Не ўдалося паўторна прасканіраваць {0}", + "ToastRescanRemoved": "Паўторнае сканаванне завершана, элемент быў выдалены", + "ToastRescanUpToDate": "Паўторнае сканаванне завершана, элемент быў у актуальным стане", + "ToastRescanUpdated": "Паўторнае сканаванне завершана, элемент быў абноўлены", "ToastScanFailed": "Не ўдалося адсканіраваць элемент бібліятэкі", "ToastSelectAtLeastOneUser": "Выберыце прынамсі аднаго карыстальніка", "ToastSendEbookToDeviceFailed": "Не ўдалося адправіць электронную кнігу на прыладу", "ToastSendEbookToDeviceSuccess": "Электронная кніга адпраўлена на прыладу \"{0}\"", "ToastSeriesSubmitFailedSameName": "Немагчыма дадаць дзве серыі з аднолькавай назвай", + "ToastSeriesUpdateFailed": "Не ўдалося абнавіць серыі", + "ToastSeriesUpdateSuccess": "Серыі абноўлены", "ToastServerSettingsUpdateSuccess": "Налады сервера абноўлены", "ToastSessionCloseFailed": "Не ўдалося закрыць сеанс", "ToastSessionDeleteFailed": "Не ўдалося выдаліць сеанс", "ToastSessionDeleteSuccess": "Сеанс выдалены", "ToastSleepTimerDone": "Таймер сну скончыўся... Хр-р-р", + "ToastSlugMustChange": "Ідэнтыфікатар змяшчае недапушчальныя сімвалы", + "ToastSlugRequired": "Ідэнтыфікатар абавязковы", + "ToastSocketConnected": "Сокет падключаны", + "ToastSocketDisconnected": "Сокет адключаны", + "ToastSocketFailedToConnect": "Не ўдалося падключыць сокет", "ToastSortingPrefixesEmptyError": "Мусіць мець хаця б адзін прэфікс сартавання", "ToastSortingPrefixesUpdateSuccess": "Прэфіксы сартавання абноўлены ({0} элементаў)", "ToastTitleRequired": "Загаловак абавязковы", "ToastUnknownError": "Невядомая памылка", + "ToastUnlinkOpenIdFailed": "Не ўдалося адвязаць карыстальніка ад OpenID", + "ToastUnlinkOpenIdSuccess": "Карыстальнік адвязаны ад OpenID", "ToastUploaderFilepathExistsError": "Файл \"{0}\" ужо існуе на серверы", "ToastUploaderItemExistsInSubdirectoryError": "Элемент \"{0}\" выкарыстоўвае падкаталог шляху загрузкі.", "ToastUserDeleteFailed": "Не ўдалося выдаліць карыстальніка", @@ -1064,7 +1156,7 @@ "ToastUserRootRequireName": "Неабходна ўвесці імя карыстальніка адміністратара", "TooltipAddChapters": "Дадаць раздзел(ы)", "TooltipAddOneSecond": "Дадаць 1 секунду", - "TooltipAdjustChapterStart": "Націсніце, каб наладзіць час пачатку", + "TooltipAdjustChapterStart": "Націсніце, каб адкарэкціраваць час пачатку", "TooltipLockAllChapters": "Заблакіраваць усе раздзелы", "TooltipLockChapter": "Заблакіраваць раздзел (Shift+націсканне для дыяпазону)", "TooltipSubtractOneSecond": "Адняць 1 секунду", From 7c956b15820efaaf731a906075f21a74cbfcbfb5 Mon Sep 17 00:00:00 2001 From: Torstein Eide Date: Sat, 28 Feb 2026 20:31:23 +0100 Subject: [PATCH 054/124] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegi?= =?UTF-8?q?an=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/nb_NO/ --- client/strings/no.json | 113 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 109 insertions(+), 4 deletions(-) diff --git a/client/strings/no.json b/client/strings/no.json index 1a19cfa1e..87840c3c1 100644 --- a/client/strings/no.json +++ b/client/strings/no.json @@ -309,6 +309,7 @@ "LabelDeleteFromFileSystemCheckbox": "Slett fra filsystemet (fjern haken for kun å ta bort fra databasen)", "LabelDescription": "Beskrivelse", "LabelDeselectAll": "Fjern valg", + "LabelDetectedPattern": "Oppdaget mønster:", "LabelDevice": "Enhet", "LabelDeviceInfo": "Enhetsinformasjon", "LabelDeviceIsAvailableTo": "Enheten er tilgjengelig for...", @@ -377,11 +378,12 @@ "LabelFilterByUser": "Filtrer etter bruker", "LabelFindEpisodes": "Finn episoder", "LabelFinished": "Fullført", + "LabelFinishedDate": "Fullført {0}", "LabelFolder": "Mappe", "LabelFolders": "Mapper", "LabelFontBold": "Fet", "LabelFontBoldness": "Skrifttykkelse", - "LabelFontFamily": "Fontfamilie", + "LabelFontFamily": "Skriftfamilie", "LabelFontItalic": "Kursiv", "LabelFontScale": "Font størrelse", "LabelFontStrikethrough": "Gjennomstreking", @@ -434,7 +436,9 @@ "LabelLibraryFilterSublistEmpty": "Ingen {0}", "LabelLibraryItem": "Bibliotek enhet", "LabelLibraryName": "Bibliotek navn", - "LabelLibrarySortByProgress": "Fremgang: Sist oppdatert", + "LabelLibrarySortByProgress": "Fremdrift: Sist oppdatert", + "LabelLibrarySortByProgressFinished": "Fremdrift: Fullført", + "LabelLibrarySortByProgressStarted": "Fremdrift: Startet", "LabelLimit": "Begrensning", "LabelLineSpacing": "Linjemellomrom", "LabelListenAgain": "Lytt igjen", @@ -443,8 +447,9 @@ "LabelLogLevelWarn": "Varsel", "LabelLookForNewEpisodesAfterDate": "Se etter nye episoder etter denne datoen", "LabelLowestPriority": "Laveste prioritet", + "LabelMatchConfidence": "Konfidens", "LabelMatchExistingUsersBy": "Knytt sammen eksisterende brukere basert på", - "LabelMatchExistingUsersByDescription": "Brukes for å koble til eksisterende brukere. Når koblingen er i orden vil brukerne bli identifisert med en unik id fra SSO-tilbyderen.", + "LabelMatchExistingUsersByDescription": "Brukes for å koble til eksisterende brukere. Når koblingen er i orden vil brukerne bli identifisert med en unik id fra SSO-tilbyderen", "LabelMaxEpisodesToDownload": "Maksimalt antall episoder som skal lastes ned. Bruk 0 for ubegrenset.", "LabelMaxEpisodesToDownloadPerCheck": "Maksimalt antall nye episoder som skal lastes ned per sjekk", "LabelMaxEpisodesToKeep": "Maksimalt antall episoder som skal beholdes", @@ -472,7 +477,9 @@ "LabelNewestAuthors": "Nyeste forfattere", "LabelNewestEpisodes": "Nyeste episoder", "LabelNextBackupDate": "Neste sikkerhetskopi dato", + "LabelNextChapters": "Neste kapitler blir:", "LabelNextScheduledRun": "Neste planlagte kjøring", + "LabelNoApiKeys": "Ingen API-nøkler", "LabelNoCustomMetadataProviders": "Ingen egendefinerte tilbydere for metadata", "LabelNoEpisodesSelected": "Ingen episoder valgt", "LabelNotFinished": "Ikke fullført", @@ -488,6 +495,7 @@ "LabelNotificationsMaxQueueSize": "Maksimalt antall varslinger i kø", "LabelNotificationsMaxQueueSizeHelp": "Hendelser er begrenset til avfyre én gang per sekund. Hendelser blir ignorert om køen er full. Dette forhindrer overflod av varslinger.", "LabelNumberOfBooks": "Antall bøker", + "LabelNumberOfChapters": "Antall kapitler:", "LabelNumberOfEpisodes": "# episoder", "LabelOpenIDAdvancedPermsClaimDescription": "Navnet på OpenID claim'et som inneholder avanserte tilganger for brukerhandlinger i applikasjonen som vil brukes for ikke-administratorroller (hvis konfigurert). Hvis claim'et mangler fra responsen, nektes tilgang til ABS. Hvis en enkelt opsjon mangler, blir behandlet som false. Påse at identitetstilbyderens claim stemmer overens med den forventede strukturen:", "LabelOpenIDClaims": "La følge valg være tomme for å slå av avanserte gruppe og tillatelser. Gruppen \"Bruker\" vil da også automatisk legges til.", @@ -509,6 +517,7 @@ "LabelPersonalYearReview": "Oppsummering av året ditt ({0})", "LabelPhotoPathURL": "Bilde sti/URL", "LabelPlayMethod": "Avspillingsmetode", + "LabelPlaybackRateIncrementDecrement": "Trinnstørrelse for økning/senking av avspillingshastighet", "LabelPlayerChapterNumberMarker": "{0} av {1}", "LabelPlaylists": "Spilleliste", "LabelPodcast": "Podcast", @@ -561,6 +570,7 @@ "LabelSelectAll": "Velg alt", "LabelSelectAllEpisodes": "Velg alle episoder", "LabelSelectEpisodesShowing": "Velg {0} episoder vist", + "LabelSelectUser": "Velg bruker", "LabelSelectUsers": "Velg brukere", "LabelSendEbookToDevice": "Send Ebok til...", "LabelSequence": "Sekvens", @@ -628,6 +638,7 @@ "LabelStartTime": "Start Tid", "LabelStarted": "Startet", "LabelStartedAt": "Startet", + "LabelStartedDate": "Startet {0}", "LabelStatsAudioTracks": "Lydspor", "LabelStatsAuthors": "Forfattere", "LabelStatsBestDay": "Beste dag", @@ -657,6 +668,7 @@ "LabelTheme": "Tema", "LabelThemeDark": "Mørk", "LabelThemeLight": "Lys", + "LabelThemeSepia": "Sepia", "LabelTimeBase": "Tidsbase", "LabelTimeDurationXHours": "{0} timer", "LabelTimeDurationXMinutes": "{0} minutter", @@ -725,24 +737,32 @@ "MessageAddToPlayerQueue": "Legg til i kø", "MessageAppriseDescription": "For å bruke denne funksjonen trenger du en instans av Apprise API kjørende eller et API som håndterer disse forespørslene.
Apprise API URL skal være hele URL-en til varslingen, f.eks., hvis din API-instans er på http://192.168.1.1:8337 så skal du bruke http://192.168.1.1:8337/notify.", "MessageAsinCheck": "Påse at du bruker ASIN fra den riktige Audible-regionen, ikke Amazon.", + "MessageAuthenticationLegacyTokenWarning": "Eldre API-tokener vil bli fjernet i fremtiden. Bruk API-nøkler i stedet.", "MessageAuthenticationOIDCChangesRestart": "Etter å ha lagret, start serveren din på nytt for at OIDC-endringene skal tre i kraft.", + "MessageAuthenticationSecurityMessage": "Autentisering er forbedret av sikkerhetshensyn. Alle brukere må logge inn på nytt.", "MessageBackupsDescription": "Sikkerhetskopier inkluderer, brukerfremgang, detaljer om bibliotekgjenstander, tjener instillinger og bilder lagret under /metadata/items og /metadata/authors. Sikkerhetskopier vil ikke inkludere filer som er lagret i bibliotek mappene.", "MessageBackupsLocationEditNote": "Viktig: Endring av mappen for sikkerhetskopi hverken endrer eller flytter eksisterende sikkerhetskopier!", "MessageBackupsLocationNoEditNote": "NB: Mappen for sikkerhetskopi settes i en miljøvariabel og kan ikke endres her.", "MessageBackupsLocationPathEmpty": "Mappen for sikkerhetskopiering må angis", + "MessageBatchEditPopulateMapDetailsAllHelp": "Fyll aktiverte felt med data fra alle elementer. Felt med flere verdier blir slått sammen", + "MessageBatchEditPopulateMapDetailsItemHelp": "Fyll aktiverte kartdetaljfelt med data fra dette elementet", "MessageBatchQuickMatchDescription": "Kjapt søk vil forsøke å legge til manglende omslag og metadata for de valgte gjenstandene. Aktiver dette valget for å tillate Kjapt søk til å overskrive eksisterende omslag og/eller metadata.", "MessageBookshelfNoCollections": "Du har ikke laget noen samlinger ennå", + "MessageBookshelfNoCollectionsHelp": "Samlinger er offentlige. Alle brukere med tilgang til biblioteket kan se dem.", "MessageBookshelfNoRSSFeeds": "Ingen RSS feed er åpen", "MessageBookshelfNoResultsForFilter": "Ingen resultat for filter \"{0}: {1}\"", "MessageBookshelfNoResultsForQuery": "Ingen resultater for søket", "MessageBookshelfNoSeries": "Du har ingen serier", + "MessageBulkChapterPattern": "Hvor mange kapitler vil du legge til med dette nummereringsmønsteret?", "MessageChapterEndIsAfter": "Kapittel slutt er etter slutt av lydboken", "MessageChapterErrorFirstNotZero": "Første kapittel starter på 0", "MessageChapterErrorStartGteDuration": "Feil start tid, må være mindre enn lengde på lydbok", "MessageChapterErrorStartLtPrev": "Feil start tid, må være større eller det samme som forrige kapittel start tid", "MessageChapterStartIsAfter": "Kapittel start er etter slutten av din lydbok", + "MessageChaptersNotFound": "Fant ikke kapitler", "MessageCheckingCron": "Sjekker cron...", "MessageConfirmCloseFeed": "Er du sikker på at du vil lukke denne feeden?", + "MessageConfirmDeleteApiKey": "Er du sikker på at du vil slette API-nøkkelen \"{0}\"?", "MessageConfirmDeleteBackup": "Er du sikker på at du vil slette sikkerhetskopi for {0}?", "MessageConfirmDeleteDevice": "Er du sikker på at du vil slette e-leser enheten \"{0}\"?", "MessageConfirmDeleteFile": "Dette vil slette filen fra filsystemet. Er du sikker?", @@ -770,6 +790,7 @@ "MessageConfirmRemoveAuthor": "Er du sikker på at du vil fjerne forfatteren \"{0}\"?", "MessageConfirmRemoveCollection": "Er du sikker på at du vil fjerne samling\"{0}\"?", "MessageConfirmRemoveEpisode": "Er du sikker på at du vil fjerne episode \"{0}\"?", + "MessageConfirmRemoveEpisodeNote": "Merk: Dette sletter ikke lydfilen med mindre du slår på \"Hard delete file\"", "MessageConfirmRemoveEpisodes": "Er du sikker på at du vil fjerne {0} episoder?", "MessageConfirmRemoveListeningSessions": "Er du sikker på at du vil fjerne {0} lytte-sesjoner?", "MessageConfirmRemoveMetadataFiles": "Er du sikker på at du vil fjerne alle metadata.{0}-filer i mappene for biblioteks-elementer?", @@ -795,8 +816,11 @@ "MessageFeedURLWillBe": "Feed URL vil bli {0}", "MessageFetching": "Henter...", "MessageForceReScanDescription": "vil skanne alle filene igjen som en ny skann. Lyd fil ID3 tagger, OPF filer og tekstfiler vil bli skannet som nye.", + "MessageHeatmapListeningTimeTooltip": "{0} lytter på {1}", + "MessageHeatmapNoListeningSessions": "Ingen lytteøkter på {0}", "MessageImportantNotice": "Viktig varsel!", "MessageInsertChapterBelow": "Sett inn kapittel under", + "MessageInvalidAsin": "Ugyldig ASIN", "MessageItemsSelected": "{0} Gjenstander valgt", "MessageItemsUpdated": "{0} Gjenstander oppdatert", "MessageJoinUsOn": "Følg oss nå", @@ -842,6 +866,7 @@ "MessageNoTasksRunning": "Ingen oppgaver kjører", "MessageNoUpdatesWereNecessary": "Ingen oppdatering var nødvendig", "MessageNoUserPlaylists": "Du har ingen spillelister", + "MessageNoUserPlaylistsHelp": "Spillelister er private. Bare brukeren som oppretter dem kan se dem.", "MessageNotYetImplemented": "Ikke implementert ennå", "MessageOpmlPreviewNote": "PS: Dette er en forhåndvisning av en OPML-fil. Den faktiske podcast-tittelen hentes direkte fra RSS-feeden.", "MessageOr": "eller", @@ -864,8 +889,10 @@ "MessageRestoreBackupConfirm": "Er du sikker på at du vil gjenopprette sikkerhetskopien som var laget", "MessageRestoreBackupWarning": "gjenoppretting av sikkerhetskopi vil overskrive hele databasen under /config og omslagsbilde under /metadata/items og /metadata/authors.

Sikkerhetskopier endrer ikke noen filer under dine bibliotekmapper. Hvis du har aktivert tjenerinstillingen for å lagre omslagsbilder og metadata i bibliotekmapper så vil ikke de filene bli tatt sikkerhetskopi eller overskrevet.

Alle klientene som bruker din tjener vil bli fornyet automatisk.", "MessageScheduleLibraryScanNote": "For de fleste brukere er det anbefalt å la denne funksjonen være slått av, og la mappeovervåkeren stå på. Mappeovervåkeren oppdager automatisk endringer i biblioteksmappene. Mappeovervåkeren fungerer ikke med alle filsystemer (f.eks. NFS) og da kan planlagt skanning av bibliotekene brukes i steden for.", + "MessageScheduleRunEveryWeekdayAtTime": "Kjør hver {0} kl. {1}", "MessageSearchResultsFor": "Søk resultat for", "MessageSelected": "{0} valgt", + "MessageSeriesSequenceCannotContainSpaces": "Serienummer kan ikke inneholde mellomrom", "MessageServerCouldNotBeReached": "Tjener kunne ikke bli nådd", "MessageSetChaptersFromTracksDescription": "Sett kapitler ved å bruke hver lydfil som kapittel og kapitteltittel som lydfilnavnet", "MessageShareExpirationWillBe": "Utløp vil være {0}", @@ -886,6 +913,27 @@ "MessageTaskFailedToMergeAudioFiles": "Kunne ikke slå sammen lydfiler", "MessageTaskFailedToMoveM4bFile": "Kunne ikke flytte M4B-fil", "MessageTaskFailedToWriteMetadataFile": "Kunne ikke lagre metadata-fil", + "MessageTaskMatchingBooksInLibrary": "Samsvarende bøker i biblioteket \"{0}\"", + "MessageTaskNoFilesToScan": "Ingen filer å skanne", + "MessageTaskOpmlImport": "OPML-import", + "MessageTaskOpmlImportDescription": "Oppretter podkaster fra {0} RSS-feeder", + "MessageTaskOpmlImportFeed": "OPML-importfeed", + "MessageTaskOpmlImportFeedDescription": "Importerer RSS-feed \"{0}\"", + "MessageTaskOpmlImportFeedFailed": "Kunne ikke hente podcast-feed", + "MessageTaskOpmlImportFeedPodcastDescription": "Oppretter podkast \"{0}\"", + "MessageTaskOpmlImportFeedPodcastExists": "Podkast finnes allerede på stien", + "MessageTaskOpmlImportFeedPodcastFailed": "Misslykkes å opprette podcast", + "MessageTaskOpmlImportFinished": "La til {0} podkaster", + "MessageTaskOpmlParseFailed": "Klarte ikke å tolke OPML-fil", + "MessageTaskOpmlParseFastFail": "Ugyldig OPML-fil: -tagg ble ikke funnet ELLER en -tagg ble ikke funnet", + "MessageTaskOpmlParseNoneFound": "Fant ingen feeder i OPML-filen", + "MessageTaskScanItemsAdded": "{0} lagt til", + "MessageTaskScanItemsMissing": "{0} mangler", + "MessageTaskScanItemsUpdated": "{0} oppdatert", + "MessageTaskScanNoChangesNeeded": "Ingen endringer nødvendig", + "MessageTaskScanningFileChanges": "Skanner filendringer i \"{0}\"", + "MessageTaskScanningLibrary": "Skanner biblioteket \"{0}\"", + "MessageTaskTargetDirectoryNotWritable": "Målkatalogen er ikke skrivbar", "MessageThinking": "Tenker...", "MessageUploaderItemFailed": "Opplastning mislykkes", "MessageUploaderItemSuccess": "Opplastning fullført!", @@ -903,13 +951,43 @@ "NoteUploaderFoldersWithMediaFiles": "Mapper med mediefiler vil bli behandlet som separate bibliotekgjenstander.", "NoteUploaderOnlyAudioFiles": "Om man laster opp kun lydfiler så vil hver lydfil bli behandlet som en separat lydbok.", "NoteUploaderUnsupportedFiles": "Filer som ikke er støttet vil bli ignorert. Når man velger eller slipper en mappe, filer som ikke er en mappe vil bli ignorert.", + "NotificationOnBackupCompletedDescription": "Utløses når en sikkerhetskopi er fullført", + "NotificationOnBackupFailedDescription": "Utløses når en sikkerhetskopi mislykkes", + "NotificationOnEpisodeDownloadedDescription": "Utløses når en podkastepisode lastes ned automatisk", + "NotificationOnRSSFeedDisabledDescription": "Utløses når automatiske episodenedlastinger deaktiveres på grunn av for mange mislykkede forsøk", + "NotificationOnRSSFeedFailedDescription": "Utløses når RSS-feedforespørselen mislykkes for en automatisk episodenedlasting", + "NotificationOnTestDescription": "Hendelse for testing av varslingssystemet", + "PlaceholderBulkChapterInput": "Skriv inn kapitteltittel eller bruk nummerering (f.eks. 'Episode 1', 'Kapittel 10', '1.')", "PlaceholderNewCollection": "Ny samlingsnavn", "PlaceholderNewFolderPath": "Ny mappesti", "PlaceholderNewPlaylist": "Ny spillelistenavn", "PlaceholderSearch": "Søk..", "PlaceholderSearchEpisode": "Søk episode..", + "StatsAuthorsAdded": "forfattere lagt til", + "StatsBooksAdded": "bøker lagt til", + "StatsBooksAdditional": "Noen av tilleggene inkluderer…", + "StatsBooksFinished": "bøker fullført", + "StatsBooksFinishedThisYear": "Noen bøker fullført i år…", + "StatsBooksListenedTo": "bøker lyttet til", + "StatsCollectionGrewTo": "Boksamlingen din vokste til…", + "StatsSessions": "økter", + "StatsSpentListening": "brukt på lytting", + "StatsTopAuthor": "BESTE FORFATTER", + "StatsTopAuthors": "BESTE FORFATTERE", + "StatsTopGenre": "BESTE SJANGER", + "StatsTopGenres": "BESTE SJANGRE", + "StatsTopMonth": "BESTE MÅNED", + "StatsTopNarrator": "BESTE FORTELLER", + "StatsTopNarrators": "BESTE FORTELLERE", + "StatsTotalDuration": "Med en total varighet på…", + "StatsYearInReview": "ÅRET SOM GIKK", "ToastAccountUpdateSuccess": "Konto oppdatert", + "ToastAppriseUrlRequired": "Du må angi en Apprise-URL", + "ToastAsinRequired": "ASIN er påkrevd", "ToastAuthorImageRemoveSuccess": "Forfatter bilde fjernet", + "ToastAuthorNotFound": "Fant ikke forfatter \"{0}\"", + "ToastAuthorRemoveSuccess": "Forfatter fjernet", + "ToastAuthorSearchNotFound": "Fant ikke forfatter", "ToastAuthorUpdateMerged": "Forfatter slått sammen", "ToastAuthorUpdateSuccess": "Forfatter oppdatert", "ToastAuthorUpdateSuccessNoImageFound": "Forfatter oppdater (ingen bilde funnet)", @@ -923,6 +1001,7 @@ "ToastBackupRestoreFailed": "Misslykkes å gjenopprette sikkerhetskopi", "ToastBackupUploadFailed": "Misslykkes å laste opp sikkerhetskopi", "ToastBackupUploadSuccess": "Sikkerhetskopi lastet opp", + "ToastBatchApplyDetailsToItemsSuccess": "Detaljer brukt på elementene", "ToastBatchDeleteFailed": "Sletting feilet på utvalget", "ToastBatchDeleteSuccess": "Sletting av samling utført", "ToastBatchQuickMatchFailed": "Feil ved rask integrering av metadata!", @@ -932,17 +1011,25 @@ "ToastBookmarkCreateFailed": "Misslykkes å opprette bokmerke", "ToastBookmarkCreateSuccess": "Bokmerke lagt til", "ToastBookmarkRemoveSuccess": "Bokmerke fjernet", + "ToastBulkChapterInvalidCount": "Skriv inn et tall mellom 1 og 150", "ToastCachePurgeFailed": "Kunne ikke å slette mellomlager", "ToastCachePurgeSuccess": "Mellomlager slettet", + "ToastChapterLocked": "Kapittelet er låst.", + "ToastChapterStartTimeAdjusted": "Kapittelstart ble justert med {0} sekunder", + "ToastChaptersAllLocked": "Alle kapitler er låst. Lås opp noen kapitler for å flytte tidene.", "ToastChaptersHaveErrors": "Kapittel har feil", + "ToastChaptersInvalidShiftAmountLast": "Ugyldig forskyvningsverdi. Starttid for siste kapittel vil gå utover varigheten til denne lydboken.", + "ToastChaptersInvalidShiftAmountStart": "Ugyldig forskyvningsverdi. Det første kapitlet ville fått null eller negativ lengde og blitt overskrevet av det andre kapitlet. Øk starttiden til det andre kapitlet.", "ToastChaptersMustHaveTitles": "Kapittel må ha titler", "ToastChaptersRemoved": "Kapitler fjernet", "ToastChaptersUpdated": "Kapitler oppdatert", "ToastCollectionItemsAddFailed": "Feil med å legge til element(er)", "ToastCollectionRemoveSuccess": "Samling fjernet", "ToastCollectionUpdateSuccess": "samlingupdated", + "ToastConnectionNotAvailable": "Tilkobling er ikke tilgjengelig. Prøv igjen senere", "ToastCoverSearchFailed": "Finner ikke bokomslag", "ToastCoverUpdateFailed": "Oppdatering av bilde feilet", + "ToastDateTimeInvalidOrIncomplete": "Dato og klokkeslett er ugyldig eller ufullstendig", "ToastDeleteFileFailed": "Kunne ikke slette fil", "ToastDeleteFileSuccess": "Fil slettet", "ToastDeviceAddFailed": "Kunne ikke legge til enhet", @@ -955,6 +1042,9 @@ "ToastEpisodeDownloadQueueClearFailed": "Kunne ikke tømme køen", "ToastEpisodeDownloadQueueClearSuccess": "Nedlastingskø for eposider tømt", "ToastEpisodeUpdateSuccess": "{0} episoder oppdatert", + "ToastErrorCannotShare": "Kan ikke dele direkte på denne enheten", + "ToastFailedToCreate": "Kunne ikke opprette", + "ToastFailedToDelete": "Kunne ikke slette", "ToastFailedToLoadData": "Kunne ikke laste inn data", "ToastFailedToMatch": "Kunne ikke matche", "ToastFailedToShare": "Deling feilet", @@ -962,6 +1052,7 @@ "ToastInvalidImageUrl": "Ugyldig URL for bilde", "ToastInvalidMaxEpisodesToDownload": "Ugyldig maksimalt antall for nedlasting av episoder", "ToastInvalidUrl": "Ugyldig URL", + "ToastInvalidUrls": "Én eller flere URL-er er ugyldige", "ToastItemCoverUpdateSuccess": "Omslag oppdatert", "ToastItemDeletedFailed": "Kunne ikke slette element", "ToastItemDeletedSuccess": "Element slettet", @@ -986,6 +1077,7 @@ "ToastMustHaveAtLeastOnePath": "Påkrevd med minst én mappe", "ToastNameEmailRequired": "Navn og e-post påkrevd", "ToastNameRequired": "Navn er påkrevd", + "ToastNewApiKeyUserError": "Du må velge en bruker", "ToastNewEpisodesFound": "{0} nye episoder funnet", "ToastNewUserCreatedFailed": "Kunne ikke opprette konto: \"{0}\"", "ToastNewUserCreatedSuccess": "Ny konto opprettet", @@ -994,6 +1086,7 @@ "ToastNewUserTagError": "Velg minst en tag", "ToastNewUserUsernameError": "Skriv inn brukernavn", "ToastNoNewEpisodesFound": "Ingen nye episoder funnet", + "ToastNoRSSFeed": "Podkasten har ikke en RSS-feed", "ToastNoUpdatesNecessary": "Ingen oppdateringer nødvendig", "ToastNotificationCreateFailed": "Kunne ikke opprette varsling", "ToastNotificationDeleteFailed": "Kunne ikke slette varsling", @@ -1009,6 +1102,7 @@ "ToastPlaylistUpdateSuccess": "Spilleliste oppdatert", "ToastPodcastCreateFailed": "Misslykkes å opprette podcast", "ToastPodcastCreateSuccess": "Podcast opprettet", + "ToastPodcastEpisodeUpdated": "Episode oppdatert", "ToastPodcastGetFeedFailed": "Kunne ikke hente podcast-feed", "ToastPodcastNoEpisodesInFeed": "Ingen episoder funnet i RSS-feed", "ToastPodcastNoRssFeed": "Podcast har ingen RSS-feed", @@ -1033,6 +1127,7 @@ "ToastSelectAtLeastOneUser": "Velg minst én bruker", "ToastSendEbookToDeviceFailed": "Misslykkes å sende ebok", "ToastSendEbookToDeviceSuccess": "Ebok sendt til \"{0}\"", + "ToastSeriesSubmitFailedSameName": "Kan ikke legge til to serier med samme navn", "ToastSeriesUpdateFailed": "Misslykkes å oppdatere serie", "ToastSeriesUpdateSuccess": "Serie oppdatert", "ToastServerSettingsUpdateSuccess": "Server-innstillinger oppdatert", @@ -1051,10 +1146,20 @@ "ToastUnknownError": "Ukjent feil", "ToastUnlinkOpenIdFailed": "Kunne ikke koble bruker fra OpenID", "ToastUnlinkOpenIdSuccess": "Bruker koblet fra OpenID", + "ToastUploaderFilepathExistsError": "Filstien \"{0}\" finnes allerede på serveren", + "ToastUploaderItemExistsInSubdirectoryError": "Elementet \"{0}\" bruker en underkatalog av opplastingsstien.", "ToastUserDeleteFailed": "Misslykkes å slette bruker", "ToastUserDeleteSuccess": "Bruker slettet", "ToastUserPasswordChangeSuccess": "Passord ble endret", "ToastUserPasswordMismatch": "Passord må stemme overens", "ToastUserPasswordMustChange": "Nytt passord kan ikke være identisk med gammelt passord", - "ToastUserRootRequireName": "Root-brukernavn er påkrevd" + "ToastUserRootRequireName": "Root-brukernavn er påkrevd", + "TooltipAddChapters": "Legg til kapittel(er)", + "TooltipAddOneSecond": "Legg til 1 sekund", + "TooltipAdjustChapterStart": "Klikk for å justere starttid", + "TooltipLockAllChapters": "Lås alle kapitler", + "TooltipLockChapter": "Lås kapittel (Shift+klikk for område)", + "TooltipSubtractOneSecond": "Trekk fra 1 sekund", + "TooltipUnlockAllChapters": "Lås opp alle kapitler", + "TooltipUnlockChapter": "Lås opp kapittel (Shift+klikk for område)" } From 45dd843ce106f164771294e2d58b8f588cd5ab35 Mon Sep 17 00:00:00 2001 From: thehijacker Date: Sun, 1 Mar 2026 10:44:10 +0100 Subject: [PATCH 055/124] Translated using Weblate (Slovenian) Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sl/ --- client/strings/sl.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/strings/sl.json b/client/strings/sl.json index 3b7ce53a2..c88be4ba1 100644 --- a/client/strings/sl.json +++ b/client/strings/sl.json @@ -104,7 +104,7 @@ "ButtonStartM4BEncode": "Zaženi M4B prekodiranje", "ButtonStartMetadataEmbed": "Začni vdelavo metapodatkov", "ButtonStats": "Statistika", - "ButtonSubmit": "Potrdi", + "ButtonSubmit": "Pošlji", "ButtonTest": "Test", "ButtonUnlinkOpenId": "Prekini povezavo OpenID", "ButtonUpload": "Naloži", @@ -436,9 +436,9 @@ "LabelLibraryFilterSublistEmpty": "Ne {0}", "LabelLibraryItem": "Element knjižnice", "LabelLibraryName": "Ime knjižnice", - "LabelLibrarySortByProgress": "Napredek: Nazadnje posodobljen", - "LabelLibrarySortByProgressFinished": "Napredej: Končano", - "LabelLibrarySortByProgressStarted": "Napredek: Začeto", + "LabelLibrarySortByProgress": "Napredek: Zadnja posodobitev", + "LabelLibrarySortByProgressFinished": "Napredek: Končano", + "LabelLibrarySortByProgressStarted": "Napredek: Začelo se je", "LabelLimit": "Omejitev", "LabelLineSpacing": "Vrstični razmak", "LabelListenAgain": "Poslušaj znova", From 635e132325e086b34964ccc401e6726922ebc5df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98ystein=20S=2E=20Hegnander?= Date: Sat, 28 Feb 2026 20:38:16 +0100 Subject: [PATCH 056/124] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegi?= =?UTF-8?q?an=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/nb_NO/ --- client/strings/no.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/strings/no.json b/client/strings/no.json index 87840c3c1..bc39982ea 100644 --- a/client/strings/no.json +++ b/client/strings/no.json @@ -34,7 +34,7 @@ "ButtonEditChapters": "Rediger kapittel", "ButtonEditPodcast": "Rediger podcast", "ButtonEnable": "Aktiver", - "ButtonFireAndFail": "Kjør ved feil", + "ButtonFireAndFail": "Utfør og feil", "ButtonFireOnTest": "Kjør onTest-kommando", "ButtonForceReScan": "Tving skann", "ButtonFullPath": "Full sti", From 6479cdb66deeec828137e808ff1e356fd0b78cef Mon Sep 17 00:00:00 2001 From: "weblate.user.1274" Date: Sat, 28 Feb 2026 20:38:28 +0100 Subject: [PATCH 057/124] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegi?= =?UTF-8?q?an=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/nb_NO/ --- client/strings/no.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/strings/no.json b/client/strings/no.json index bc39982ea..9f79312d7 100644 --- a/client/strings/no.json +++ b/client/strings/no.json @@ -113,7 +113,7 @@ "ButtonUploadOPMLFile": "Last opp OPML fil", "ButtonUserDelete": "Slett bruker {0}", "ButtonUserEdit": "Rediger bruker {0}", - "ButtonViewAll": "Vis alle", + "ButtonViewAll": "Vis alt", "ButtonYes": "Ja", "ErrorUploadFetchMetadataAPI": "Feil ved innhenting av metadata", "ErrorUploadFetchMetadataNoResults": "Kunne ikke hente metadata - forsøk å oppdatere tittel og/eller forfatter", From 6e6c43c53ceb0985e85d4e40372f39fb5cea67a9 Mon Sep 17 00:00:00 2001 From: Torstein Eide Date: Sat, 28 Feb 2026 20:44:56 +0100 Subject: [PATCH 058/124] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegi?= =?UTF-8?q?an=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/nb_NO/ --- client/strings/no.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/strings/no.json b/client/strings/no.json index 9f79312d7..8e1c9cfbb 100644 --- a/client/strings/no.json +++ b/client/strings/no.json @@ -458,7 +458,7 @@ "LabelMediaType": "Medie type", "LabelMetaTag": "Meta tag", "LabelMetaTags": "Meta tags", - "LabelMetadataOrderOfPrecedenceDescription": "Høyere prioritert kilder for metadata overstyrer laverer prioriterte kilder for metadata.", + "LabelMetadataOrderOfPrecedenceDescription": "Høyere prioritert kilder for metadata overstyrer laverer prioriterte kilder for metadata", "LabelMetadataProvider": "Metadata Leverandør", "LabelMinute": "Minutt", "LabelMinutes": "Minutter", @@ -499,7 +499,7 @@ "LabelNumberOfEpisodes": "# episoder", "LabelOpenIDAdvancedPermsClaimDescription": "Navnet på OpenID claim'et som inneholder avanserte tilganger for brukerhandlinger i applikasjonen som vil brukes for ikke-administratorroller (hvis konfigurert). Hvis claim'et mangler fra responsen, nektes tilgang til ABS. Hvis en enkelt opsjon mangler, blir behandlet som false. Påse at identitetstilbyderens claim stemmer overens med den forventede strukturen:", "LabelOpenIDClaims": "La følge valg være tomme for å slå av avanserte gruppe og tillatelser. Gruppen \"Bruker\" vil da også automatisk legges til.", - "LabelOpenIDGroupClaimDescription": "Navn på OpenID-forespørsel som inneholder en lite over brukerens grupper. Vanligvis kalt grupper. Om konfigurert, vil applikasjonen tildele roller baseret på brukerens gruppemedlemsskaper, gitt disse grupper er navngitt (uten forbehold for store og små bokstaver) 'admin', 'user' eller 'guest' i forespørsel. Forespørselen burde inneholde en liste (og hvis brukeren tilhører flere grupper), applikasjonen vil tildele rolle med høyeste adgangsnivå. Hvis ingen grupper matcher vil adgang bli nektet.", + "LabelOpenIDGroupClaimDescription": "Navn på OpenID-forespørsel som inneholder en lite over brukerens grupper. Vanligvis kalt grupper. Om konfigurert, vil applikasjonen tildele roller baseret på brukerens gruppemedlemsskaper, gitt disse grupper er navngitt (uten forbehold for store og små bokstaver) 'admin', 'user' eller 'guest' i forespørsel. Forespørselen burde inneholde en liste (og hvis brukeren tilhører flere grupper), applikasjonen vil tildele rolle med høyeste adgangsnivå. Hvis ingen grupper matcher vil adgang bli nektet.", "LabelOpenRSSFeed": "Åpne RSS Feed", "LabelOverwrite": "Overskriv", "LabelPaginationPageXOfY": "Side {0} av {1}", @@ -742,7 +742,7 @@ "MessageAuthenticationSecurityMessage": "Autentisering er forbedret av sikkerhetshensyn. Alle brukere må logge inn på nytt.", "MessageBackupsDescription": "Sikkerhetskopier inkluderer, brukerfremgang, detaljer om bibliotekgjenstander, tjener instillinger og bilder lagret under /metadata/items og /metadata/authors. Sikkerhetskopier vil ikke inkludere filer som er lagret i bibliotek mappene.", "MessageBackupsLocationEditNote": "Viktig: Endring av mappen for sikkerhetskopi hverken endrer eller flytter eksisterende sikkerhetskopier!", - "MessageBackupsLocationNoEditNote": "NB: Mappen for sikkerhetskopi settes i en miljøvariabel og kan ikke endres her.", + "MessageBackupsLocationNoEditNote": "Viktig: Mappen for sikkerhetskopi satt i en miljøvariabel og kan ikke endres her.", "MessageBackupsLocationPathEmpty": "Mappen for sikkerhetskopiering må angis", "MessageBatchEditPopulateMapDetailsAllHelp": "Fyll aktiverte felt med data fra alle elementer. Felt med flere verdier blir slått sammen", "MessageBatchEditPopulateMapDetailsItemHelp": "Fyll aktiverte kartdetaljfelt med data fra dette elementet", @@ -781,7 +781,7 @@ "MessageConfirmMarkSeriesFinished": "Er du sikker på at du vil markere alle bøkene i serien som fullført?", "MessageConfirmMarkSeriesNotFinished": "Er du sikker på at du vil markere alle bøkene i serien som ikke fullført?", "MessageConfirmNotificationTestTrigger": "Utløs dette varselet med test-data?", - "MessageConfirmPurgeCache": "Tømming av mellomlagring vil slette hele mappen /metadata/cache.

Er du sikker på at du du vil slette mappen?", + "MessageConfirmPurgeCache": "Tømming av mellomlagring vil slette hele mappen /metadata/cache.

Er du sikker på at du vil slette mappen?", "MessageConfirmPurgeItemsCache": "(Purge items cache) Dette vil sletter hele mappen /metadata/cache/items.
Er du sikker?", "MessageConfirmQuickEmbed": "Advarsel! Rask innbygging av metadata tar ikke backup av lyd-filene først. Forsikre deg om at du har sikkerhetskopi av filene.

Fortsett?", "MessageConfirmQuickMatchEpisodes": "Hurtig gjenkjenning av episoder overskriver detaljene hvis en match blir funnet. Kun episoder som ikke allerede er matchet blir oppdatert. Er du sikker?", From 367826ce64c7b042d6750cced698ac6660c76c6e Mon Sep 17 00:00:00 2001 From: Artur Date: Mon, 2 Mar 2026 18:25:52 +0100 Subject: [PATCH 059/124] Translated using Weblate (Polish) Currently translated at 95.7% (1113 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/pl/ --- client/strings/pl.json | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/client/strings/pl.json b/client/strings/pl.json index 3a9593db0..3be1c2dfe 100644 --- a/client/strings/pl.json +++ b/client/strings/pl.json @@ -11,7 +11,7 @@ "ButtonApplyChapters": "Zatwierdź rozdziały", "ButtonAuthors": "Autorzy", "ButtonBack": "Wstecz", - "ButtonBatchEditPopulateFromExisting": "Powiel z poprzednich", + "ButtonBatchEditPopulateFromExisting": "Uzupełnij na podstawie istniejących", "ButtonBatchEditPopulateMapDetails": "Powiel szczegóły mapy", "ButtonBrowseForFolder": "Wyszukaj folder", "ButtonCancel": "Anuluj", @@ -55,7 +55,7 @@ "ButtonNext": "Następny", "ButtonNextChapter": "Następny rozdział", "ButtonNextItemInQueue": "Następny element w kolejce", - "ButtonOk": "Ok", + "ButtonOk": "OK", "ButtonOpenFeed": "Otwórz feed", "ButtonOpenManager": "Otwórz menadżera", "ButtonPause": "Wstrzymaj", @@ -127,7 +127,7 @@ "HeaderAudiobookTools": "Narzędzia do zarządzania audiobookami", "HeaderAuthentication": "Uwierzytelnianie", "HeaderBackups": "Kopie zapasowe", - "HeaderBulkChapterModal": "Dodaj wiele rozdziałów", + "HeaderBulkChapterModal": "Dodaj kilka rozdziałów", "HeaderChangePassword": "Zmień hasło", "HeaderChapters": "Rozdziały", "HeaderChooseAFolder": "Wybierz folder", @@ -275,7 +275,7 @@ "LabelBonus": "Bonus", "LabelBooks": "Książki", "LabelButtonText": "Tekst przycisku", - "LabelByAuthor": "autorstwa {0}", + "LabelByAuthor": "Autor {0}", "LabelChangePassword": "Zmień hasło", "LabelChannels": "Kanały", "LabelChapterCount": "{0} rozdziałów", @@ -286,7 +286,7 @@ "LabelClickToUseCurrentValue": "Kliknij by zastosować aktualną wartość", "LabelClosePlayer": "Zamknij odtwarzacz", "LabelCodec": "Kodek", - "LabelCollapseSeries": "Podsumuj serię", + "LabelCollapseSeries": "Zwiń serię", "LabelCollapseSubSeries": "Zwiń podserie", "LabelCollection": "Kolekcja", "LabelCollections": "Kolekcje", @@ -300,7 +300,7 @@ "LabelCoverImageURL": "URL okładki", "LabelCoverProvider": "Dostawca okładki", "LabelCreatedAt": "Utworzone", - "LabelCronExpression": "Wyrażenie CRON", + "LabelCronExpression": "Wyrażenie harmonogramowania zadań cron", "LabelCurrent": "Aktualny", "LabelCurrently": "Obecnie:", "LabelCustomCronExpression": "Niestandardowe wyrażenie Cron:", @@ -339,11 +339,11 @@ "LabelEnable": "Włącz", "LabelEncodingBackupLocation": "Kopia zapasowa twoich oryginalnych plików audio będzie się znajdować w:", "LabelEncodingChaptersNotEmbedded": "W audiobookach wielościeżkowych rozdziały nie są osadzone.", - "LabelEncodingClearItemCache": "Pamiętaj o okresowym czyszczeniu pamięci podręcznej elementów.", + "LabelEncodingClearItemCache": "Pamiętaj, aby okresowo czyścić pamięć podręczną elementów.", "LabelEncodingFinishedM4B": "Ukończony plik M4B zostanie umieszczony w folderze audiobooka pod adresem:", "LabelEncodingInfoEmbedded": "Metadane zostaną osadzone w ścieżkach audio w folderze z audiobookiem.", "LabelEncodingStartedNavigation": "Po uruchomieniu zadania możesz opuścić tę stronę.", - "LabelEncodingTimeWarning": "Konwersja może potrwać do 30 minut.", + "LabelEncodingTimeWarning": "Kodowanie może potrwać do 30 minut.", "LabelEncodingWarningAdvancedSettings": "Ostrzeżenie: Nie aktualizuj tych ustawień, jeśli nie jesteś zaznajomiony ze sposobem działania ffmpeg i opcji konwersji.", "LabelEncodingWatcherDisabled": "Jeśli monitorowanie folderów jest wyłączone, należy ponownie przeskanować audiobooka.", "LabelEnd": "Zakończ", @@ -436,7 +436,7 @@ "LabelLibraryFilterSublistEmpty": "Brak {0}", "LabelLibraryItem": "Element biblioteki", "LabelLibraryName": "Nazwa biblioteki", - "LabelLibrarySortByProgress": "Postęp: Ostatnio zaktualizowane", + "LabelLibrarySortByProgress": "Postęp: Ostatnia aktualizacja", "LabelLibrarySortByProgressFinished": "Postęp: Ukończony", "LabelLibrarySortByProgressStarted": "Postęp: Rozpoczęty", "LabelLimit": "Limit", @@ -450,7 +450,7 @@ "LabelMatchConfidence": "Zaufanie", "LabelMatchExistingUsersBy": "Dopasuje istniejących użytkowników poprzez", "LabelMatchExistingUsersByDescription": "Służy do łączenia istniejących użytkowników. Po połączeniu użytkownicy zostaną dopasowani za pomocą unikalnego identyfikatora od dostawcy SSO", - "LabelMaxEpisodesToDownload": "Maksymalna liczba odcinków do pobrania. Użyj 0, aby wyłączyć ograniczenie.", + "LabelMaxEpisodesToDownload": "Maksymalna liczba odcinków do pobrania. Użyj 0, aby uzyskać nieograniczoną liczbę.", "LabelMaxEpisodesToDownloadPerCheck": "Maksymalna liczba nowych odcinków do pobrania na jedno sprawdzenie", "LabelMaxEpisodesToKeep": "Maksymalna liczba odcinków do zachowania", "LabelMaxEpisodesToKeepHelp": "Wartość 0 wyłącza maksymalny limit. Po automatycznym pobraniu nowego odcinka, najstarszy odcinek zostanie usunięty, jeśli masz ich więcej niż X. Spowoduje to usunięcie tylko 1 odcinka na nowe pobieranie.", @@ -498,7 +498,8 @@ "LabelNumberOfChapters": "Liczba rozdziałów:", "LabelNumberOfEpisodes": "# Odcinków", "LabelOpenIDAdvancedPermsClaimDescription": "Nazwa deklaracji OpenID zawierającej zaawansowane uprawnienia do działań użytkownika w aplikacji, które będą miały zastosowanie do ról innych niż administracyjne (jeśli skonfigurowano). Jeśli deklaracja nie zostanie uwzględniona w odpowiedzi, dostęp do ABS zostanie zablokowany. Brak jednej opcji zostanie uznany za fałsz. Upewnij się, że deklaracja dostawcy tożsamości jest zgodna z oczekiwaną strukturą:", - "LabelOpenIDClaims": "Pozostaw poniższe opcje puste, aby wyłączyć zaawansowane przypisywanie grup i uprawnień. Automatycznie zostanie przypisana grupa „Użytkownik”.", + "LabelOpenIDClaims": "Pozostaw poniższe opcje puste, aby wyłączyć zaawansowane przypisywanie grup i uprawnień. Wówczas automatycznie zostanie przypisana grupa „Użytkownik”.", + "LabelOpenIDGroupClaimDescription": "Nazwa roszczenia OpenID zawierającego listę grup użytkownika. Powszechnie nazywane grupami. Jeśli skonfigurowano, aplikacja automatycznie przypisze role na podstawie przynależności użytkownika do grup, pod warunkiem, że te grupy są nazwane bez uwzględniania wielkości liter „admin”, „user” lub „guest” w roszczeniu. Roszczenie powinno zawierać listę, a jeśli użytkownik należy do wielu grup, aplikacja przypisze rolę odpowiadającą najwyższemu poziomowi dostępu. Jeśli żadna grupa nie będzie pasować, dostęp zostanie odrzucony.", "LabelOpenRSSFeed": "Otwórz kanał RSS", "LabelOverwrite": "Nadpisz", "LabelPaginationPageXOfY": "Strona {0} z {1}", @@ -577,7 +578,7 @@ "LabelSeries": "Serie", "LabelSeriesName": "Nazwy serii", "LabelSeriesProgress": "Postęp w serii", - "LabelServerLogLevel": "Poziom logowania servera", + "LabelServerLogLevel": "Poziom logów servera", "LabelServerYearReview": "Podsumowanie serwera w roku ({0})", "LabelSetEbookAsPrimary": "Ustaw jako pierwszy", "LabelSetEbookAsSupplementary": "Ustaw jako dodatkowy", @@ -686,7 +687,7 @@ "LabelToolsMakeM4bDescription": "Tworzy plik w formacie .M4B, który zawiera metadane, okładkę oraz rozdziały.", "LabelToolsSplitM4b": "Podziel plik .M4B na pliki .MP3", "LabelToolsSplitM4bDescription": "Podziel plik .M4B na pliki .MP3 na rozdziały z załączonymi metadanymi oraz okładką.", - "LabelTotalDuration": "TCałkowita długość", + "LabelTotalDuration": "Całkowita długość", "LabelTotalTimeListened": "Całkowity czas odtwarzania", "LabelTrackFromFilename": "Ścieżka z nazwy pliku", "LabelTrackFromMetadata": "Ścieżka z metadanych", @@ -723,6 +724,7 @@ "LabelViewQueue": "Wyświetlaj kolejkę odtwarzania", "LabelVolume": "Głośność", "LabelWebRedirectURLsDescription": "Zezwól na te adresy URL w swoim dostawcy OAuth, aby umożliwić przekierowanie z powrotem do aplikacji internetowej po zalogowaniu:", + "LabelWebRedirectURLsSubfolder": "Podfolder dla adresów URL przekierowań", "LabelWeekdaysToRun": "Dni tygodnia", "LabelXBooks": "{0} książek", "LabelXItems": "{0} elementów", @@ -915,15 +917,21 @@ "MessageTaskNoFilesToScan": "Brak plików do skanowania", "MessageTaskOpmlImport": "Importuj OPML", "MessageTaskOpmlImportDescription": "Tworzenie {0} podcastów z kanałów RSS", + "MessageTaskOpmlImportFeed": "Importuje plik OPML", + "MessageTaskOpmlImportFeedDescription": "Importowanie kanału RSS „{0}”", + "MessageTaskOpmlImportFeedFailed": "Nie udało się pobrać kanału podcastowego", "MessageTaskOpmlImportFeedPodcastDescription": "Tworzenie podcastu \"{0}\"", "MessageTaskOpmlImportFeedPodcastExists": "Podcast już istnieje pod podaną ścieżką", "MessageTaskOpmlImportFeedPodcastFailed": "Nie udało się utworzyć podcastu", "MessageTaskOpmlImportFinished": "Dodano {0} podcastów", "MessageTaskOpmlParseFailed": "Błąd parsowania pliku OPML", + "MessageTaskOpmlParseFastFail": "Nieprawidłowy plik OPML. Nie znaleziono tagu LUB nie znaleziono tagu .", + "MessageTaskOpmlParseNoneFound": "Nie znaleziono kanałów w pliku OPML", "MessageTaskScanItemsAdded": "Dodano {0}", "MessageTaskScanItemsMissing": "Brakuje {0}", "MessageTaskScanItemsUpdated": "Zaktualizowano {0}", "MessageTaskScanNoChangesNeeded": "Brak zmian", + "MessageTaskScanningFileChanges": "Skanowanie zmian w plikach w „{0}”", "MessageTaskScanningLibrary": "Skanowanie biblioteki \"{0}\"", "MessageTaskTargetDirectoryNotWritable": "Brak prawa zapisu do folderu docelowego", "MessageThinking": "Myślę...", @@ -944,6 +952,7 @@ "NoteUploaderOnlyAudioFiles": "Jeśli przesyłasz tylko pliki audio, każdy plik audio będzie traktowany jako osobny audiobook.", "NoteUploaderUnsupportedFiles": "Nieobsługiwane pliki są ignorowane. Podczas dodawania folderu, inne pliki, które nie znajdują się w folderze elementu, są ignorowane.", "NotificationOnTestDescription": "Zdarzenie używane do testowania systemu powiadomień", + "PlaceholderBulkChapterInput": "Wpisz tytuł rozdziału lub użyj numeracji (np. „Odcinek 1”, „Rozdział 10”, „1.”)", "PlaceholderNewCollection": "Nowa nazwa kolekcji", "PlaceholderNewFolderPath": "Nowa ścieżka folderu", "PlaceholderNewPlaylist": "Nowa nazwa playlisty", @@ -1018,8 +1027,12 @@ "ToastDeviceTestEmailFailed": "NIeudana próba wysłania testowego maila", "ToastDeviceTestEmailSuccess": "Testowy email został wysłany", "ToastEmailSettingsUpdateSuccess": "Ustawienia email zaktualizowane", + "ToastEncodeCancelFailed": "Nie udało się anulować kodowania", + "ToastEncodeCancelSucces": "Kodowanie anulowane", + "ToastEpisodeDownloadQueueClearFailed": "Nie udało się wyczyścić kolejki", "ToastEpisodeDownloadQueueClearSuccess": "Wyczyszczono kolejkę epizodów do ściągnięcia", "ToastEpisodeUpdateSuccess": "Zaktualizowano {0} odcinków", + "ToastErrorCannotShare": "Nie można udostępniać natywnie na tym urządzeniu.", "ToastInvalidImageUrl": "Nieprawidłowy URL obrazu", "ToastInvalidUrl": "Nieprawidłowy URL", "ToastInvalidUrls": "Jeden lub więcej URL-i są nieprawidłowe", From e83aca572e901c7bfa17fb61f8a06cd38ef39f0e Mon Sep 17 00:00:00 2001 From: Grzegorz Orlowski Date: Mon, 2 Mar 2026 18:22:18 +0100 Subject: [PATCH 060/124] Translated using Weblate (Polish) Currently translated at 95.7% (1113 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/pl/ --- client/strings/pl.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/client/strings/pl.json b/client/strings/pl.json index 3be1c2dfe..d1bc6c062 100644 --- a/client/strings/pl.json +++ b/client/strings/pl.json @@ -359,8 +359,8 @@ "LabelExample": "Przykład", "LabelExpandSeries": "Rozwiń serie", "LabelExpandSubSeries": "Rozwiń podserie", - "LabelExpired": "Przeterminowane", - "LabelExpiresAt": "Wygasa w", + "LabelExpired": "Wygasły", + "LabelExpiresAt": "Wygasa o", "LabelExpiresInSeconds": "Wygasa za (sekund)", "LabelExpiresNever": "Nigdy", "LabelExplicit": "18+", @@ -574,7 +574,7 @@ "LabelSelectUsers": "Wybór użytkowników", "LabelSendEbookToDevice": "Wyślij ebook do...", "LabelSequence": "Kolejność", - "LabelSerial": "Seria", + "LabelSerial": "Numer serii", "LabelSeries": "Serie", "LabelSeriesName": "Nazwy serii", "LabelSeriesProgress": "Postęp w serii", @@ -622,7 +622,7 @@ "LabelSettingsStoreMetadataWithItemHelp": "Domyślnie metadane są przechowywane w folderze /metadata/items, włączenie tej opcji spowoduje, że okładka będzie przechowywana w folderze ksiązki. Tylko jedna okładka o nazwie pliku \"cover\" będzie przechowywana", "LabelSettingsTimeFormat": "Format czasu", "LabelShare": "Udostępnij", - "LabelShareDownloadableHelp": "Użytkownicy mogą przy pomocy linka ściągnąć archiwum ZIP pozycji biblioteki", + "LabelShareDownloadableHelp": "Zezwala użytkownikom z linkiem udostępniania na pobranie pliku zip elementu biblioteki.", "LabelShareOpen": "Otwórz udział", "LabelShareURL": "Link do udziału", "LabelShowAll": "Pokaż wszystko", @@ -661,11 +661,11 @@ "LabelTagsAccessibleToUser": "Tagi dostępne dla użytkownika", "LabelTagsNotAccessibleToUser": "Znaczniki niedostępne dla użytkownika", "LabelTasks": "Uruchomione zadania", - "LabelTextEditorBulletedList": "Lista punktowana", + "LabelTextEditorBulletedList": "Lista wypunktowana", "LabelTextEditorLink": "Link", "LabelTextEditorNumberedList": "Lista numerowana", "LabelTextEditorUnlink": "Usuń link", - "LabelTheme": "Kompozycja", + "LabelTheme": "Motyw", "LabelThemeDark": "Ciemny", "LabelThemeLight": "Jasny", "LabelThemeSepia": "Sepia", @@ -693,7 +693,7 @@ "LabelTrackFromMetadata": "Ścieżka z metadanych", "LabelTracks": "Ścieżki", "LabelTracksMultiTrack": "Wielościeżkowy", - "LabelTracksNone": "Brak ścieżek", + "LabelTracksNone": "Brak utworów", "LabelTracksSingleTrack": "Pojedyncza ścieżka", "LabelTrailer": "Zwiastun", "LabelType": "Typ", @@ -710,7 +710,7 @@ "LabelUploaderDragAndDropFilesOnly": "Przeciągnij i upuść pliki", "LabelUploaderDropFiles": "Puść pliki", "LabelUploaderItemFetchMetadataHelp": "Automatycznie pobierz tytuł, autora i serie", - "LabelUseAdvancedOptions": "Opcje zaawansowane", + "LabelUseAdvancedOptions": "Użyj ustawień zaawansowanych", "LabelUseChapterTrack": "Użyj ścieżki rozdziału", "LabelUseFullTrack": "Użycie ścieżki rozdziału", "LabelUseZeroForUnlimited": "Użyj 0, aby wyłączyć ograniczenia", @@ -745,7 +745,7 @@ "MessageBackupsLocationNoEditNote": "Uwaga: Lokalizacja kopii zapasowej jest ustawiona poprzez zmienną środowiskową i nie może być tutaj zmieniona.", "MessageBackupsLocationPathEmpty": "Ścieżka do kopii zapasowej nie może być pusta", "MessageBatchEditPopulateMapDetailsAllHelp": "Wypełnij włączone pola danymi ze wszystkich elementów. Pola z wieloma wartościami zostaną scalone.", - "MessageBatchEditPopulateMapDetailsItemHelp": "Wypełnij pola szczegółów mapy włączonej danymi z tego elementu", + "MessageBatchEditPopulateMapDetailsItemHelp": "Wypełnia włączone pola szczegółów mapy danymi z tego elementu", "MessageBatchQuickMatchDescription": "Quick Match będzie próbował dodać brakujące okładki i metadane dla wybranych elementów. Włącz poniższe opcje, aby umożliwić Quick Match nadpisanie istniejących okładek i/lub metadanych.", "MessageBookshelfNoCollections": "Nie posiadasz jeszcze żadnych kolekcji", "MessageBookshelfNoCollectionsHelp": "Kolekcje są publiczne. Wszyscy użytkownicy mający dostęp do biblioteki mogą je zobaczyć.", From d9355ac3aa175e9184db64e42c62a934aab933a0 Mon Sep 17 00:00:00 2001 From: Oliver Marriott Date: Tue, 10 Mar 2026 23:51:57 +1100 Subject: [PATCH 061/124] Force AAC transcode when streaming mka+opus to desktop client Matroska audio containers (aka mka files) with Opus codec streams inside were unplayable on the desktop client because hls.js was unable to decode the stream, resulting in an infinitely "spinning" play button. When configuring a stream, we now check for the opus codec and force AAC transcoding. Matroska containers support other codecs besides Opus, eg: mp3, which do not require transcoding and work fine before this patch, which is why we check for opus in codecsToForceAAC instead of AudioMimeType.MKA in mimeTypesToForceAAC. The AudioMimeType.OPUS mimetype is already marked as requiring transcoding but since its inside a container this check does not evaluate to true, we must check the codec explicitly. --- server/objects/Stream.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/objects/Stream.js b/server/objects/Stream.js index 5aa013e8e..70361463f 100644 --- a/server/objects/Stream.js +++ b/server/objects/Stream.js @@ -73,7 +73,7 @@ class Stream extends EventEmitter { return [AudioMimeType.FLAC, AudioMimeType.OPUS, AudioMimeType.WMA, AudioMimeType.AIFF, AudioMimeType.WEBM, AudioMimeType.WEBMA, AudioMimeType.AWB, AudioMimeType.CAF] } get codecsToForceAAC() { - return ['alac', 'ac3', 'eac3'] + return ['alac', 'ac3', 'eac3', 'opus'] } get userToken() { return this.user.token From 690a7e0da91485d8c7521b930c0ae9f4909ce24f Mon Sep 17 00:00:00 2001 From: advplyr Date: Wed, 11 Mar 2026 17:03:07 -0500 Subject: [PATCH 062/124] Update session DeviceInfo with sanitize on clientDeviceInfo --- client/pages/config/sessions.vue | 26 +++++++++++----- client/pages/config/users/_id/sessions.vue | 16 ++++++---- server/objects/DeviceInfo.js | 36 +++++++++------------- server/utils/htmlSanitizer.js | 2 ++ 4 files changed, 46 insertions(+), 34 deletions(-) diff --git a/client/pages/config/sessions.vue b/client/pages/config/sessions.vue index 86ec7eec6..4341ea07d 100644 --- a/client/pages/config/sessions.vue +++ b/client/pages/config/sessions.vue @@ -66,7 +66,11 @@

{{ getPlayMethodName(session.playMethod) }}

-

+

+ +

{{ $elapsedPrettyLocalized(session.timeListening) }}

@@ -130,7 +134,11 @@

{{ getPlayMethodName(session.playMethod) }}

-

+

+ +

{{ $elapsedPretty(session.timeListening) }}

@@ -172,7 +180,11 @@

{{ getPlayMethodName(session.playMethod) }}

-

+

+ +

{{ $secondsToTimestamp(session.currentTime) }}

@@ -433,16 +445,16 @@ export default { this.selectedSession = session this.showSessionModal = true }, - getDeviceInfoString(deviceInfo) { - if (!deviceInfo) return '' - var lines = [] + getDeviceInfoLines(deviceInfo) { + if (!deviceInfo) return [] + const lines = [] if (deviceInfo.clientName) lines.push(`${deviceInfo.clientName} ${deviceInfo.clientVersion || ''}`) if (deviceInfo.osName) lines.push(`${deviceInfo.osName} ${deviceInfo.osVersion}`) if (deviceInfo.browserName) lines.push(deviceInfo.browserName) if (deviceInfo.manufacturer && deviceInfo.model) lines.push(`${deviceInfo.manufacturer} ${deviceInfo.model}`) if (deviceInfo.sdkVersion) lines.push(`SDK Version: ${deviceInfo.sdkVersion}`) - return lines.join('
') + return lines }, getPlayMethodName(playMethod) { if (playMethod === this.$constants.PlayMethod.DIRECTPLAY) return 'Direct Play' diff --git a/client/pages/config/users/_id/sessions.vue b/client/pages/config/users/_id/sessions.vue index 060f34be8..cb18f5ee3 100644 --- a/client/pages/config/users/_id/sessions.vue +++ b/client/pages/config/users/_id/sessions.vue @@ -38,8 +38,12 @@

{{ getPlayMethodName(session.playMethod) }}

-

- +

+ +

+

{{ $elapsedPrettyLocalized(session.timeListening) }}

@@ -193,16 +197,16 @@ export default { this.selectedSession = session this.showSessionModal = true }, - getDeviceInfoString(deviceInfo) { - if (!deviceInfo) return '' - var lines = [] + getDeviceInfoLines(deviceInfo) { + if (!deviceInfo) return [] + const lines = [] if (deviceInfo.clientName) lines.push(`${deviceInfo.clientName} ${deviceInfo.clientVersion || ''}`) if (deviceInfo.osName) lines.push(`${deviceInfo.osName} ${deviceInfo.osVersion}`) if (deviceInfo.browserName) lines.push(deviceInfo.browserName) if (deviceInfo.manufacturer && deviceInfo.model) lines.push(`${deviceInfo.manufacturer} ${deviceInfo.model}`) if (deviceInfo.sdkVersion) lines.push(`SDK Version: ${deviceInfo.sdkVersion}`) - return lines.join('
') + return lines }, getPlayMethodName(playMethod) { if (playMethod === this.$constants.PlayMethod.DIRECTPLAY) return 'Direct Play' diff --git a/server/objects/DeviceInfo.js b/server/objects/DeviceInfo.js index ceff6c32e..22ebfbea4 100644 --- a/server/objects/DeviceInfo.js +++ b/server/objects/DeviceInfo.js @@ -1,6 +1,10 @@ -const uuidv4 = require("uuid").v4 +const uuidv4 = require('uuid').v4 +const { stripAllTags } = require('../utils/htmlSanitizer') class DeviceInfo { + /** @type {string[]} Fields to sanitize when loading from stored data */ + static stringFields = ['deviceId', 'clientVersion', 'manufacturer', 'model', 'sdkVersion', 'clientName', 'deviceName'] + constructor(deviceInfo = null) { this.id = null this.userId = null @@ -31,7 +35,7 @@ class DeviceInfo { construct(deviceInfo) { for (const key in deviceInfo) { if (deviceInfo[key] !== undefined && this[key] !== undefined) { - this[key] = deviceInfo[key] + this[key] = DeviceInfo.stringFields.includes(key) ? stripAllTags(deviceInfo[key]) : deviceInfo[key] } } } @@ -63,7 +67,8 @@ class DeviceInfo { } get deviceDescription() { - if (this.model) { // Set from mobile apps + if (this.model) { + // Set from mobile apps if (this.sdkVersion) return `${this.model} SDK ${this.sdkVersion} / v${this.clientVersion}` return `${this.model} / v${this.clientVersion}` } @@ -72,18 +77,7 @@ class DeviceInfo { // When client doesn't send a device id getTempDeviceId() { - const keys = [ - this.userId, - this.browserName, - this.browserVersion, - this.osName, - this.osVersion, - this.clientVersion, - this.manufacturer, - this.model, - this.sdkVersion, - this.ipAddress - ].map(k => k || '') + const keys = [this.userId, this.browserName, this.browserVersion, this.osName, this.osVersion, this.clientVersion, this.manufacturer, this.model, this.sdkVersion, this.ipAddress].map((k) => k || '') return 'temp-' + Buffer.from(keys.join('-'), 'utf-8').toString('base64') } @@ -99,12 +93,12 @@ class DeviceInfo { this.osVersion = ua?.os.version || null this.deviceType = ua?.device.type || null - this.clientVersion = clientDeviceInfo?.clientVersion || serverVersion - this.manufacturer = clientDeviceInfo?.manufacturer || null - this.model = clientDeviceInfo?.model || null - this.sdkVersion = clientDeviceInfo?.sdkVersion || null + this.clientVersion = stripAllTags(clientDeviceInfo?.clientVersion) || serverVersion + this.manufacturer = stripAllTags(clientDeviceInfo?.manufacturer) || null + this.model = stripAllTags(clientDeviceInfo?.model) || null + this.sdkVersion = stripAllTags(clientDeviceInfo?.sdkVersion) || null - this.clientName = clientDeviceInfo?.clientName || null + this.clientName = stripAllTags(clientDeviceInfo?.clientName) || null if (this.sdkVersion) { if (!this.clientName) this.clientName = 'Abs Android' this.deviceName = `${this.manufacturer || 'Unknown'} ${this.model || ''}` @@ -149,4 +143,4 @@ class DeviceInfo { return hasUpdates } } -module.exports = DeviceInfo \ No newline at end of file +module.exports = DeviceInfo diff --git a/server/utils/htmlSanitizer.js b/server/utils/htmlSanitizer.js index 4ed30e727..dbac1e5e0 100644 --- a/server/utils/htmlSanitizer.js +++ b/server/utils/htmlSanitizer.js @@ -27,6 +27,8 @@ function sanitize(html) { module.exports.sanitize = sanitize function stripAllTags(html, shouldDecodeEntities = true) { + if (typeof html !== 'string') return '' + const sanitizerOptions = { allowedTags: [], disallowedTagsMode: 'discard' From 972193b19352752966f5afbbbb1e264868ff78d9 Mon Sep 17 00:00:00 2001 From: advplyr Date: Wed, 11 Mar 2026 17:18:05 -0500 Subject: [PATCH 063/124] Update server settings authLoginCustomMessage to sanitize on save and load --- server/objects/settings/ServerSettings.js | 8 ++++++-- server/utils/htmlSanitizer.js | 3 +-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/server/objects/settings/ServerSettings.js b/server/objects/settings/ServerSettings.js index a03e17c75..99f5b76aa 100644 --- a/server/objects/settings/ServerSettings.js +++ b/server/objects/settings/ServerSettings.js @@ -3,6 +3,7 @@ const packageJson = require('../../../package.json') const { BookshelfView } = require('../../utils/constants') const Logger = require('../../Logger') const User = require('../../models/User') +const { sanitize } = require('../../utils/htmlSanitizer') class ServerSettings { constructor(settings) { @@ -126,7 +127,7 @@ class ServerSettings { this.version = settings.version || null this.buildNumber = settings.buildNumber || 0 // Added v2.4.5 - this.authLoginCustomMessage = settings.authLoginCustomMessage || null // Added v2.8.0 + this.authLoginCustomMessage = sanitize(settings.authLoginCustomMessage) || null // Added v2.8.0 this.authActiveAuthMethods = settings.authActiveAuthMethods || ['local'] this.authOpenIDIssuerURL = settings.authOpenIDIssuerURL || null @@ -309,7 +310,7 @@ class ServerSettings { get authFormData() { const clientFormData = { - authLoginCustomMessage: this.authLoginCustomMessage + authLoginCustomMessage: sanitize(this.authLoginCustomMessage) } if (this.authActiveAuthMethods.includes('openid')) { clientFormData.authOpenIDButtonText = this.authOpenIDButtonText @@ -327,6 +328,9 @@ class ServerSettings { update(payload) { let hasUpdates = false for (const key in payload) { + if (key === 'authLoginCustomMessage') { + payload[key] = sanitize(payload[key]) + } if (key === 'sortingPrefixes') { // Sorting prefixes are updated with the /api/sorting-prefixes endpoint continue diff --git a/server/utils/htmlSanitizer.js b/server/utils/htmlSanitizer.js index dbac1e5e0..be839b7c2 100644 --- a/server/utils/htmlSanitizer.js +++ b/server/utils/htmlSanitizer.js @@ -5,11 +5,10 @@ const { entities } = require('./htmlEntities') * * @param {string} html * @returns {string} - * @throws {Error} if input is not a string */ function sanitize(html) { if (typeof html !== 'string') { - throw new Error('sanitizeHtml: input must be a string') + return '' } const sanitizerOptions = { From e66ffb9c23cdc7f7b45538c443b1bc96828b1f7a Mon Sep 17 00:00:00 2001 From: advplyr Date: Thu, 12 Mar 2026 16:37:59 -0500 Subject: [PATCH 064/124] Add indexes to MediaProgress and BookSeries models --- server/migrations/v2.32.2-add-discover-query-indexes.js | 2 +- server/models/BookSeries.js | 4 ++++ server/models/MediaProgress.js | 4 ++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/server/migrations/v2.32.2-add-discover-query-indexes.js b/server/migrations/v2.32.2-add-discover-query-indexes.js index 26b12383d..801561fc9 100644 --- a/server/migrations/v2.32.2-add-discover-query-indexes.js +++ b/server/migrations/v2.32.2-add-discover-query-indexes.js @@ -14,7 +14,7 @@ const loggerPrefix = `[${migrationVersion} migration]` const indexes = [ { table: 'mediaProgresses', - name: 'media_progress_user_item_finished_time', + name: 'media_progresses_user_item_finished_time', fields: ['userId', 'mediaItemId', 'isFinished', 'currentTime'] }, { diff --git a/server/models/BookSeries.js b/server/models/BookSeries.js index 31eccb9fa..e71f28a8e 100644 --- a/server/models/BookSeries.js +++ b/server/models/BookSeries.js @@ -48,6 +48,10 @@ class BookSeries extends Model { { name: 'bookSeries_seriesId', fields: ['seriesId'] + }, + { + name: 'book_series_series_book', + fields: ['seriesId', 'bookId'] } ] } diff --git a/server/models/MediaProgress.js b/server/models/MediaProgress.js index 0ebe2f598..9c0269a9e 100644 --- a/server/models/MediaProgress.js +++ b/server/models/MediaProgress.js @@ -80,6 +80,10 @@ class MediaProgress extends Model { indexes: [ { fields: ['updatedAt'] + }, + { + name: 'media_progresses_user_item_finished_time', + fields: ['userId', 'mediaItemId', 'isFinished', 'currentTime'] } ] } From a47c869d0b252c9bfd8ecc0b902e5a06d0cf1742 Mon Sep 17 00:00:00 2001 From: advplyr Date: Thu, 12 Mar 2026 16:45:08 -0500 Subject: [PATCH 065/124] Update migration file to v2.33.0 --- ...r-query-indexes.js => v2.33.0-add-discover-query-indexes.js} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename server/migrations/{v2.32.2-add-discover-query-indexes.js => v2.33.0-add-discover-query-indexes.js} (98%) diff --git a/server/migrations/v2.32.2-add-discover-query-indexes.js b/server/migrations/v2.33.0-add-discover-query-indexes.js similarity index 98% rename from server/migrations/v2.32.2-add-discover-query-indexes.js rename to server/migrations/v2.33.0-add-discover-query-indexes.js index 801561fc9..ebd92bbaa 100644 --- a/server/migrations/v2.32.2-add-discover-query-indexes.js +++ b/server/migrations/v2.33.0-add-discover-query-indexes.js @@ -7,7 +7,7 @@ * @property {MigrationContext} context */ -const migrationVersion = '2.32.2' +const migrationVersion = '2.33.0' const migrationName = `${migrationVersion}-add-discover-query-indexes` const loggerPrefix = `[${migrationVersion} migration]` From 6d3773a0b83b61b07bb29632c8deda404f865d3a Mon Sep 17 00:00:00 2001 From: advplyr Date: Thu, 12 Mar 2026 17:01:54 -0500 Subject: [PATCH 066/124] Version bump v2.33.0 --- client/package-lock.json | 4 ++-- client/package.json | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 1e2d52c11..299741bd5 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1,12 +1,12 @@ { "name": "audiobookshelf-client", - "version": "2.32.1", + "version": "2.33.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "audiobookshelf-client", - "version": "2.32.1", + "version": "2.33.0", "license": "ISC", "dependencies": { "@nuxtjs/axios": "^5.13.6", diff --git a/client/package.json b/client/package.json index 0eaffb106..a1503a506 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "audiobookshelf-client", - "version": "2.32.1", + "version": "2.33.0", "buildNumber": 1, "description": "Self-hosted audiobook and podcast client", "main": "index.js", diff --git a/package-lock.json b/package-lock.json index 08707893d..e07fba51d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "audiobookshelf", - "version": "2.32.1", + "version": "2.33.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "audiobookshelf", - "version": "2.32.1", + "version": "2.33.0", "license": "GPL-3.0", "dependencies": { "axios": "^0.27.2", diff --git a/package.json b/package.json index 3ee3fb391..3108b5170 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "audiobookshelf", - "version": "2.32.1", + "version": "2.33.0", "buildNumber": 1, "description": "Self-hosted audiobook and podcast server", "main": "index.js", From 874e9e18564650bcedd33d63b5cdbb3ba86f5541 Mon Sep 17 00:00:00 2001 From: advplyr Date: Wed, 18 Mar 2026 16:17:45 -0500 Subject: [PATCH 067/124] Update API Key jwtAuthCheck to check user active status --- server/auth/TokenManager.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/server/auth/TokenManager.js b/server/auth/TokenManager.js index faa6774a3..5efeb7a64 100644 --- a/server/auth/TokenManager.js +++ b/server/auth/TokenManager.js @@ -234,6 +234,13 @@ class TokenManager { } const user = await Database.userModel.getUserById(apiKey.userId) + + if (!user?.isActive) { + // deny login + done(null, null) + return + } + done(null, user) } else { // JWT based authentication From 7c0d9efe9165ea07cf1ff7ff5404b6a8d35af148 Mon Sep 17 00:00:00 2001 From: advplyr Date: Wed, 18 Mar 2026 16:51:51 -0500 Subject: [PATCH 068/124] Update Confirm component to support allowHtml prompt option --- client/components/app/Appbar.vue | 1 + client/components/modals/item/tabs/Tools.vue | 1 + client/components/prompt/Confirm.vue | 22 ++++++++++++++++++- client/pages/config/index.vue | 2 +- .../config/item-metadata-utils/genres.vue | 4 ++-- .../pages/config/item-metadata-utils/tags.vue | 4 ++-- 6 files changed, 28 insertions(+), 6 deletions(-) diff --git a/client/components/app/Appbar.vue b/client/components/app/Appbar.vue index f74134041..428ae6ebf 100644 --- a/client/components/app/Appbar.vue +++ b/client/components/app/Appbar.vue @@ -196,6 +196,7 @@ export default { requestBatchQuickEmbed() { const payload = { message: this.$strings.MessageConfirmQuickEmbed, + allowHtml: true, callback: (confirmed) => { if (confirmed) { this.$axios diff --git a/client/components/modals/item/tabs/Tools.vue b/client/components/modals/item/tabs/Tools.vue index d96550887..8e40ca3c0 100644 --- a/client/components/modals/item/tabs/Tools.vue +++ b/client/components/modals/item/tabs/Tools.vue @@ -107,6 +107,7 @@ export default { quickEmbed() { const payload = { message: this.$strings.MessageConfirmQuickEmbed, + allowHtml: true, callback: (confirmed) => { if (confirmed) { this.$axios diff --git a/client/components/prompt/Confirm.vue b/client/components/prompt/Confirm.vue index 361765e2d..d0828c7d1 100644 --- a/client/components/prompt/Confirm.vue +++ b/client/components/prompt/Confirm.vue @@ -3,7 +3,8 @@
-

+

+

{{ message }}

@@ -52,6 +53,17 @@ export default { message() { return this.confirmPromptOptions.message || '' }, + allowHtmlMessage() { + return !!this.confirmPromptOptions.allowHtml + }, + sanitizedMessage() { + if (!this.allowHtmlMessage) return this.message + + return this.escapeHtml(this.message) + .replace(/<br\s*\/?>/gi, '
') + .replace(/<code>/gi, '') + .replace(/<\/code>/gi, '') + }, callback() { return this.confirmPromptOptions.callback }, @@ -103,6 +115,14 @@ export default { if (this.callback) this.callback(true, this.checkboxValue) this.show = false }, + escapeHtml(value) { + return String(value) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, ''') + }, setShow() { this.checkboxValue = this.checkboxDefaultValue this.$eventBus.$emit('showing-prompt', true) diff --git a/client/pages/config/index.vue b/client/pages/config/index.vue index b8cf3cff2..7fb84d468 100644 --- a/client/pages/config/index.vue +++ b/client/pages/config/index.vue @@ -390,8 +390,8 @@ export default { }, purgeItemsCache() { const payload = { - // message: `This will delete the entire folder at /metadata/cache/items.
Are you sure you want to purge items cache?`, message: this.$strings.MessageConfirmPurgeItemsCache, + allowHtml: true, callback: (confirmed) => { if (confirmed) { this.sendPurgeItemsCache() diff --git a/client/pages/config/item-metadata-utils/genres.vue b/client/pages/config/item-metadata-utils/genres.vue index 87e14bbd8..8abe34eef 100644 --- a/client/pages/config/item-metadata-utils/genres.vue +++ b/client/pages/config/item-metadata-utils/genres.vue @@ -90,9 +90,9 @@ export default { let message = this.$getString('MessageConfirmRenameGenre', [this.editingGenre, this.newGenreName]) if (genreNameExists) { - message += `
${this.$strings.MessageConfirmRenameGenreMergeNote}` + message += ` ${this.$strings.MessageConfirmRenameGenreMergeNote}` } else if (genreNameExistsOfDifferentCase) { - message += `
${this.$getString('MessageConfirmRenameGenreWarning', [genreNameExistsOfDifferentCase])}` + message += ` ${this.$getString('MessageConfirmRenameGenreWarning', [genreNameExistsOfDifferentCase])}` } const payload = { diff --git a/client/pages/config/item-metadata-utils/tags.vue b/client/pages/config/item-metadata-utils/tags.vue index 2fe5c0f1a..4dfc39424 100644 --- a/client/pages/config/item-metadata-utils/tags.vue +++ b/client/pages/config/item-metadata-utils/tags.vue @@ -86,9 +86,9 @@ export default { let message = this.$getString('MessageConfirmRenameTag', [this.editingTag, this.newTagName]) if (tagNameExists) { - message += `
${this.$strings.MessageConfirmRenameTagMergeNote}` + message += ` ${this.$strings.MessageConfirmRenameTagMergeNote}` } else if (tagNameExistsOfDifferentCase) { - message += `
${this.$getString('MessageConfirmRenameTagWarning', [tagNameExistsOfDifferentCase])}` + message += ` ${this.$getString('MessageConfirmRenameTagWarning', [tagNameExistsOfDifferentCase])}` } const payload = { From 4bdd76d94c450f2de329379cecf4a672dbd23a9d Mon Sep 17 00:00:00 2001 From: advplyr Date: Wed, 18 Mar 2026 17:01:19 -0500 Subject: [PATCH 069/124] Update podcast episode update endpoint to sanitize subtitle --- server/controllers/PodcastController.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/server/controllers/PodcastController.js b/server/controllers/PodcastController.js index 1ebe1d110..c70287600 100644 --- a/server/controllers/PodcastController.js +++ b/server/controllers/PodcastController.js @@ -412,6 +412,12 @@ class PodcastController { Logger.debug(`[PodcastController] Sanitized description from "${req.body[key]}" to "${sanitizedDescription}"`) req.body[key] = sanitizedDescription } + } else if (key === 'subtitle' && req.body[key]) { + const sanitizedSubtitle = htmlSanitizer.sanitize(req.body[key]) + if (sanitizedSubtitle !== req.body[key]) { + Logger.debug(`[PodcastController] Sanitized subtitle from "${req.body[key]}" to "${sanitizedSubtitle}"`) + req.body[key] = sanitizedSubtitle + } } updatePayload[key] = req.body[key] From 5d9682410a62d54ba71df7f89e2542ed529c69b2 Mon Sep 17 00:00:00 2001 From: Charlie Date: Fri, 13 Mar 2026 03:00:30 +0100 Subject: [PATCH 070/124] Translated using Weblate (French) Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/ --- client/strings/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/strings/fr.json b/client/strings/fr.json index 497426d37..507574033 100644 --- a/client/strings/fr.json +++ b/client/strings/fr.json @@ -622,7 +622,7 @@ "LabelSettingsStoreMetadataWithItemHelp": "Par défaut, les fichiers de métadonnées sont stockés dans /metadata/items. En activant ce paramètre, les fichiers de métadonnées seront stockés dans les dossiers des éléments de votre bibliothèque", "LabelSettingsTimeFormat": "Format d’heure", "LabelShare": "Partager", - "LabelShareDownloadableHelp": "Permet aux utilisateurs de télécharger un fichier ZIP de l'élément de la bibliothèque.", + "LabelShareDownloadableHelp": "Permet aux utilisateurs disposant du lien de partage de télécharger un fichier zip contenant l'élément de la bibliothèque.", "LabelShareOpen": "Ouvrir le partage", "LabelShareURL": "Partager l’URL", "LabelShowAll": "Tout afficher", From 15af7407ffed805645eca78034a9350e7ad4c3b6 Mon Sep 17 00:00:00 2001 From: Pavel Miniutka Date: Fri, 13 Mar 2026 14:22:29 +0100 Subject: [PATCH 071/124] Translated using Weblate (Belarusian) Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/be/ --- client/strings/be.json | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/client/strings/be.json b/client/strings/be.json index 6dbef9cb9..90504121e 100644 --- a/client/strings/be.json +++ b/client/strings/be.json @@ -81,7 +81,7 @@ "ButtonRemove": "Выдаліць", "ButtonRemoveAll": "Выдаліць усе", "ButtonRemoveAllLibraryItems": "Выдаліць усе элементы бібліятэкі", - "ButtonRemoveFromContinueListening": "Выдаліць з Працягваць слухаць", + "ButtonRemoveFromContinueListening": "Выдаліць з Працягнуць праслухоўванне", "ButtonRemoveFromContinueReading": "Выдаліць з Працягваць чытанне", "ButtonRemoveSeriesFromContinueSeries": "Выдаліць серыю з Працягваць серыю", "ButtonReset": "Скінуць", @@ -107,10 +107,10 @@ "ButtonSubmit": "Адправіць", "ButtonTest": "Тэст", "ButtonUnlinkOpenId": "Адвязаць OpenID", - "ButtonUpload": "Загрузіць", - "ButtonUploadBackup": "Загрузіць рэзервовую копію", - "ButtonUploadCover": "Загрузіць вокладку", - "ButtonUploadOPMLFile": "Загрузіць файл OPML", + "ButtonUpload": "Запампаваць", + "ButtonUploadBackup": "Запампаваць рэзервовую копію", + "ButtonUploadCover": "Запампаваць вокладку", + "ButtonUploadOPMLFile": "Запампаваць файл OPML", "ButtonUserDelete": "Выдаліць карыстальніка {0}", "ButtonUserEdit": "Рэдагаваць карыстальніка {0}", "ButtonViewAll": "Прагледзець усе", @@ -149,7 +149,7 @@ "HeaderFindChapters": "Знайсці раздзелы", "HeaderIgnoredFiles": "Ігнараваныя файлы", "HeaderItemFiles": "Файлы элементаў", - "HeaderItemMetadataUtils": "Утыліты для метаданых элементаў", + "HeaderItemMetadataUtils": "Утыліты для метаданых", "HeaderLastListeningSession": "Апошні сеанс праслухоўвання", "HeaderLatestEpisodes": "Апошнія выпускі", "HeaderLibraries": "Бібліятэкі", @@ -207,8 +207,8 @@ "HeaderStatsLongestItems": "Найдаўжэйшыя элементы (гадзіны)", "HeaderStatsMinutesListeningChart": "Хвілін праслухоўвання (апошнія 7 дзён)", "HeaderStatsRecentSessions": "Апошнія сеансы", - "HeaderStatsTop10Authors": "10 лепшых аўтараў", - "HeaderStatsTop5Genres": "5 лепшых жанраў", + "HeaderStatsTop10Authors": "Топ 10 аўтараў", + "HeaderStatsTop5Genres": "Топ 5 жанраў", "HeaderTableOfContents": "Змест", "HeaderTools": "Інструменты", "HeaderUpdateAccount": "Абнавіць уліковы запіс", @@ -292,7 +292,7 @@ "LabelCollections": "Калекцыі", "LabelComplete": "Завяршыць", "LabelConfirmPassword": "Пацвердзіце пароль", - "LabelContinueListening": "Працягваць слухаць", + "LabelContinueListening": "Працягнуць праслухоўванне", "LabelContinueReading": "Працягнуць чытанне", "LabelContinueSeries": "Працягнуць серыі", "LabelCorsAllowed": "Дазволеныя крыніцы CORS", @@ -545,7 +545,7 @@ "LabelRSSFeedSlug": "Ідэнтыфікатар RSS-стужкі", "LabelRSSFeedURL": "URL RSS-стужкі", "LabelRandomly": "Выпадкова", - "LabelReAddSeriesToContinueListening": "Дадаць серыю зноў у Працягваць слухаць", + "LabelReAddSeriesToContinueListening": "Дадаць серыю зноў у Працягнуць праслухоўванне", "LabelRead": "Чытаць", "LabelReadAgain": "Чытаць зноў", "LabelReadEbookWithoutProgress": "Чытаць электронную кнігу без захавання прагрэсу", @@ -588,7 +588,7 @@ "LabelSettingsBookshelfViewHelp": "Рэалістычны дызайн з драўлянымі паліцамі", "LabelSettingsChromecastSupport": "Падтрымка Chromecast", "LabelSettingsDateFormat": "Фарматы даты", - "LabelSettingsEnableWatcher": "Аўтаматычна сачыць за зменамі ў бібліятэцках", + "LabelSettingsEnableWatcher": "Аўтаматычна сачыць за зменамі ў бібліятэках", "LabelSettingsEnableWatcherForLibrary": "Аўтаматычна сачыць за зменамі ў бібліятэцы", "LabelSettingsEnableWatcherHelp": "Адключае аўтаматычнае дадаванне/абнаўленне элементаў пры выяўленні змен у файлах. *Патрабуецца перазапуск сервера", "LabelSettingsEpubsAllowScriptedContent": "Дазваляць скрыпты ў файлах EPUB", @@ -639,9 +639,9 @@ "LabelStarted": "Пачата", "LabelStartedAt": "Пачата ў", "LabelStartedDate": "Пачата {0}", - "LabelStatsAudioTracks": "Аўдыятрэкі", - "LabelStatsAuthors": "Аўтары", - "LabelStatsBestDay": "Лепшы дзень", + "LabelStatsAudioTracks": "Аўдыятрэкаў", + "LabelStatsAuthors": "Аўтараў", + "LabelStatsBestDay": "Найлепшы дзень", "LabelStatsDailyAverage": "У сярэднім за дзень", "LabelStatsDays": "Дзён", "LabelStatsDaysListened": "Дзён праслухана", @@ -968,7 +968,7 @@ "StatsBooksAdditional": "Некаторыя дапаўненні ўключаюць…", "StatsBooksFinished": "завершана кніг", "StatsBooksFinishedThisYear": "Некаторыя кнігі завершаны ў гэтым годзе…", - "StatsBooksListenedTo": "кнігі, якія былі праслуханы", + "StatsBooksListenedTo": "кніг праслухана", "StatsCollectionGrewTo": "Ваша калекцыя кніг павялічылася да…", "StatsSessions": "сеансаў", "StatsSpentListening": "праслухана", @@ -1147,7 +1147,7 @@ "ToastUnlinkOpenIdFailed": "Не ўдалося адвязаць карыстальніка ад OpenID", "ToastUnlinkOpenIdSuccess": "Карыстальнік адвязаны ад OpenID", "ToastUploaderFilepathExistsError": "Файл \"{0}\" ужо існуе на серверы", - "ToastUploaderItemExistsInSubdirectoryError": "Элемент \"{0}\" выкарыстоўвае падкаталог шляху загрузкі.", + "ToastUploaderItemExistsInSubdirectoryError": "Элемент \"{0}\" выкарыстоўвае падкаталог шляху запампоўкі.", "ToastUserDeleteFailed": "Не ўдалося выдаліць карыстальніка", "ToastUserDeleteSuccess": "Карыстальнік выдалены", "ToastUserPasswordChangeSuccess": "Пароль паспяхова зменены", From dc54d42dcfd204c93c82068d19c0f83bf53c3124 Mon Sep 17 00:00:00 2001 From: Pavel Miniutka Date: Fri, 13 Mar 2026 15:46:52 +0100 Subject: [PATCH 072/124] Translated using Weblate (Belarusian) Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/be/ --- client/strings/be.json | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/client/strings/be.json b/client/strings/be.json index 90504121e..d598ef7bf 100644 --- a/client/strings/be.json +++ b/client/strings/be.json @@ -50,7 +50,7 @@ "ButtonManageTracks": "Кіраванне трэкамі", "ButtonMapChapterTitles": "Супаставіць загалоўкі раздзелаў", "ButtonMatchAllAuthors": "Супадзенне ўсіх аўтараў", - "ButtonMatchBooks": "Падбор кніг", + "ButtonMatchBooks": "Параўнаць кнігі", "ButtonNevermind": "Няважна", "ButtonNext": "Далей", "ButtonNextChapter": "Наступны раздзел", @@ -196,7 +196,7 @@ "HeaderSession": "Сеанс", "HeaderSetBackupSchedule": "Наладзіць расклад рэзервовага капіравання", "HeaderSettings": "Налады", - "HeaderSettingsDisplay": "Дысплей", + "HeaderSettingsDisplay": "Выгляд", "HeaderSettingsExperimental": "Эксперыментальныя функцыі", "HeaderSettingsGeneral": "Агульныя", "HeaderSettingsScanner": "Сканер", @@ -239,8 +239,8 @@ "LabelAll": "Усе", "LabelAllEpisodesDownloaded": "Усе выпускі спампаваныя", "LabelAllUsers": "Усе карыстальнікі", - "LabelAllUsersExcludingGuests": "Усе карыстальнікі, акрамя гасцей", - "LabelAllUsersIncludingGuests": "Усе карыстальнікі, уключаючы гасцей", + "LabelAllUsersExcludingGuests": "Усіх карыстальнікаў, акрамя гасцей", + "LabelAllUsersIncludingGuests": "Усіх карыстальнікаў, уключаючы гасцей", "LabelAlreadyInYourLibrary": "Ужо ў вашай бібліятэцы", "LabelApiKeyCreated": "API-ключ \"{0}\" паспяхова створаны.", "LabelApiKeyCreatedDescription": "Пераканайцеся, што вы скапіявалі API-ключ зараз, бо паўторна яго ўбачыць не атрымаецца.", @@ -264,7 +264,7 @@ "LabelAutoRegisterDescription": "Аўтаматычна ствараць новых карыстальнікаў пасля ўваходу ў сістэму", "LabelBackToUser": "Вярнуцца да карыстальніка", "LabelBackupAudioFiles": "Рэзервовае капіраванне аўдыяфайлаў", - "LabelBackupLocation": "Месцазнаходжанне рэзервовых копій", + "LabelBackupLocation": "Размяшчэнне рэзервовых копій", "LabelBackupsEnableAutomaticBackups": "Аўтаматычнае рэзервовае капіраванне", "LabelBackupsEnableAutomaticBackupsHelp": "Рэзервовыя копіі захаваныя ў /metadata/backups", "LabelBackupsMaxBackupSize": "Максімальны памер рэзервовай копіі (у ГБ) (0 — неабмежавана)", @@ -316,7 +316,7 @@ "LabelDirectory": "Каталог", "LabelDiscFromFilename": "Дыск з файла", "LabelDiscFromMetadata": "Дыск з метаданых", - "LabelDiscover": "Знайсці", + "LabelDiscover": "Знаходкі", "LabelDownload": "Спампаваць", "LabelDownloadNEpisodes": "Спампавана {0} выпускаў", "LabelDownloadable": "Спампоўваецца", @@ -332,7 +332,7 @@ "LabelEmailSettingsFromAddress": "Адрас адпраўніка", "LabelEmailSettingsRejectUnauthorized": "Адхіляць неаўтарызаваныя сертыфікаты", "LabelEmailSettingsRejectUnauthorizedHelp": "Адключэнне праверкі SSL-сертыфіката можа зрабіць ваша злучэнне ўразлівым перад пагрозамі бяспекі, такімі як атакі \"чалавек пасярэдзіне\". Адключайце гэтую опцыю толькі калі цалкам разумееце наступствы і ўпэўнены ў надзейнасці паштовага сервера.", - "LabelEmailSettingsSecure": "Бяспечныя", + "LabelEmailSettingsSecure": "Бяспечна", "LabelEmailSettingsSecureHelp": "Калі ўключана, злучэнне будзе выкарыстоўваць TLS пры падключэнні да сервера. Калі выключана, TLS будзе выкарыстоўвацца толькі ў выпадку падтрымкі пашырэння STARTTLS на серверы. У большасці выпадкаў усталюйце значэнне true пры падключэнні да порта 465. Для партоў 587 або 25 не ўключайце яго. (інфармацыя з nodemailer.com/smtp/#authentication)", "LabelEmailSettingsTestAddress": "Тэставы адрас", "LabelEmbeddedCover": "Убудаваная вокладка", @@ -595,16 +595,16 @@ "LabelSettingsEpubsAllowScriptedContentHelp": "Дазволіць файлам EPUB выконваць скрыпты. Рэкамендуецца пакінуць гэтую наладу выключанай, калі вы не давяраеце крыніцы файлаў EPUB.", "LabelSettingsExperimentalFeatures": "Эксперыментальныя функцыі", "LabelSettingsExperimentalFeaturesHelp": "Функцыі ў распрацоўцы, для якіх вашы водгукі і дапамога ў тэставанні будуць карыснымі. Націсніце, каб адкрыць абмеркаванне на GitHub.", - "LabelSettingsFindCovers": "Знайсці вокладкі", + "LabelSettingsFindCovers": "Шукаць вокладкі", "LabelSettingsFindCoversHelp": "Калі ў вашай аўдыякнізе няма ўбудаванай вокладкі або відарыса вокладкі ў папцы, сканер паспрабуе знайсці вокладку.
Заўвага: гэта павялічыць час сканіравання", "LabelSettingsHideSingleBookSeries": "Схаваць серыі з адной кнігай", "LabelSettingsHideSingleBookSeriesHelp": "Серыі, якія змяшчаюць толькі адну кнігу, будуць схаваны са старонкі серый і паліц на галоўнай старонцы.", - "LabelSettingsHomePageBookshelfView": "Галоўная старонка выкарыстоўвае выгляд кніжнай паліцы", - "LabelSettingsLibraryBookshelfView": "Бібліятэка выкарыстоўвае выгляд кніжнай паліцы", + "LabelSettingsHomePageBookshelfView": "Кніжныя паліцы на галоўнай старонцы", + "LabelSettingsLibraryBookshelfView": "Кніжныя паліцы ў бібліятэцы", "LabelSettingsLibraryMarkAsFinishedPercentComplete": "Працэнт завяршэння большы за", - "LabelSettingsLibraryMarkAsFinishedTimeRemaining": "Час, што застаўся, менш за (секунд)", + "LabelSettingsLibraryMarkAsFinishedTimeRemaining": "Час, што застаўся, меншы за (секунд)", "LabelSettingsLibraryMarkAsFinishedWhen": "Пазначыць элемент медыя як завершаны, калі", - "LabelSettingsOnlyShowLaterBooksInContinueSeries": "Пропусціць папярэднія кнігі ў \"Працягнуць серыю\"", + "LabelSettingsOnlyShowLaterBooksInContinueSeries": "Прапусціць папярэднія кнігі ў \"Працягнуць серыю\"", "LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "Паліца \"Працягнуць серыю\" на галоўнай старонцы паказвае першую не пачатую кнігу ў серыях, дзе завершана хаця б адна кніга і няма кніг у працэсе чытання. Уключэнне гэтай налады дазволіць працягваць серыю з самай апошняй завершанай кнігі замест першай не пачатай.", "LabelSettingsParseSubtitles": "Аналізаваць падзагалоўкі", "LabelSettingsParseSubtitlesHelp": "Выдзяляць падзагаловак з назваў папак аўдыякніг.
Падзагаловак павінен быць аддзелены сімвалам \" - \".
Напрыклад, \"Назва кнігі - Падзагаловак тут\" мае падзагаловак \"Падзагаловак тут\"", @@ -618,7 +618,7 @@ "LabelSettingsSquareBookCoversHelp": "Аддаваць перавагу квадратным вокладкам замест стандартных вокладак з суадносінамі бакоў 1.6:1", "LabelSettingsStoreCoversWithItem": "Захоўваць вокладкі з элементам", "LabelSettingsStoreCoversWithItemHelp": "Прадвызначана вокладкі захоўваюцца ў /metadata/items, уключэнне гэтай опцыі забяспечыць захоўванне вокладак у папцы элемента бібліятэкі. Захоўвацца будзе толькі адзін файл з назвай \"cover\"", - "LabelSettingsStoreMetadataWithItem": "Захоўваць метаданыя разам з элементам", + "LabelSettingsStoreMetadataWithItem": "Захоўваць метаданыя з элементам", "LabelSettingsStoreMetadataWithItemHelp": "Прадвызначана метаданыя захоўваюцца ў /metadata/items. Пры ўключэнні гэтай опцыі файлаў метаданых будуць захоўвацца ў папках элементаў бібліятэкі", "LabelSettingsTimeFormat": "Фармат часу", "LabelShare": "Абагуліць", @@ -655,7 +655,7 @@ "LabelStatsOverallHours": "Агульная колькасць гадзін", "LabelStatsWeekListening": "Праслухана за тыдзень", "LabelSubtitle": "Падзагаловак", - "LabelSupportedFileTypes": "Падтрымліваемыя тыпы файлаў", + "LabelSupportedFileTypes": "Падтрымліваюцца тыпы файлаў", "LabelTag": "Метка", "LabelTags": "Меткі", "LabelTagsAccessibleToUser": "Меткі, даступныя карыстальніку", @@ -742,7 +742,7 @@ "MessageAuthenticationSecurityMessage": "Дзеля бяспекі была палепшана аўтэнтыфікацыя. Усім карыстальнікам трэба паўторна ўвайсці ў сістэму.", "MessageBackupsDescription": "Рэзервовыя копіі ўключаюць карыстальнікаў, іх прагрэс, падрабязнасці элементаў бібліятэкі, налады сервера і відарысы, якія захоўваюцца ў /metadata/items і /metadata/authors. Рэзервовыя копіі не ўключаюць файлы, якія захоўваюцца ў папках бібліятэкі.", "MessageBackupsLocationEditNote": "Заўвага: Абнаўленне месцазнаходжання рэзервовых копій не перамяшчае і не змяняе існуючыя рэзервовыя копіі", - "MessageBackupsLocationNoEditNote": "Заўвага: Месцазнаходжанне рэзервовых копій задаецца праз зменную асяроддзя і не можа быць зменена тут.", + "MessageBackupsLocationNoEditNote": "Заўвага: Размяшчэнне рэзервовых копій задаецца праз зменную асяроддзя і не можа быць зменена тут.", "MessageBackupsLocationPathEmpty": "Шлях да месцазнаходжання рэзервовых копій не можа быць пустым", "MessageBatchEditPopulateMapDetailsAllHelp": "Запоўніце ўключаныя палі данымі з усіх элементаў. Палі з некалькімі значэннямі будуць аб'яднаны", "MessageBatchEditPopulateMapDetailsItemHelp": "Запоўніце ўключаныя палі падрабязнасцей карты данымі з гэтага элемента", From f84831d6f1b8563dc779ebec9ed22b3fee3906f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20J=C3=BClich?= Date: Sat, 14 Mar 2026 00:00:58 +0100 Subject: [PATCH 073/124] Translated using Weblate (German) Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/ --- client/strings/de.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/strings/de.json b/client/strings/de.json index 276348656..ed0cae52c 100644 --- a/client/strings/de.json +++ b/client/strings/de.json @@ -331,7 +331,7 @@ "LabelEmail": "E-Mail", "LabelEmailSettingsFromAddress": "Sender", "LabelEmailSettingsRejectUnauthorized": "Nicht autorisierte Zertifikate ablehnen", - "LabelEmailSettingsRejectUnauthorizedHelp": "Durch das Deaktivieren der SSL-Zertifikatsüberprüfung kann deine Verbindung Sicherheitsrisiken wie Man-in-the-Middle-Angriffen ausgesetzt sein. Deaktiviere diese Option nur, wenn due die Auswirkungen verstehst und dem E-Mail-Server vertraust, mit dem eine Verbindung hergestellt wird.", + "LabelEmailSettingsRejectUnauthorizedHelp": "Durch das Deaktivieren der SSL-Zertifikatsüberprüfung kann deine Verbindung Sicherheitsrisiken wie Man-in-the-Middle-Angriffen ausgesetzt sein. Deaktiviere diese Option nur, wenn du die Auswirkungen verstehst und dem E-Mail-Server vertraust, mit dem eine Verbindung hergestellt wird.", "LabelEmailSettingsSecure": "Sicher", "LabelEmailSettingsSecureHelp": "Wenn an, verwendet die Verbindung TLS, wenn du eine Verbindung zum Server herstellst. Bei „aus“ wird TLS verwendet, wenn der Server die STARTTLS-Erweiterung unterstützt. In den meisten Fällen solltest du diesen Wert auf „an“ schalten, wenn du eine Verbindung zu Port 465 herstellst. Für Port 587 oder 25 behalte den Wert „aus“ bei. (von nodemailer.com/smtp/#authentication)", "LabelEmailSettingsTestAddress": "Test-Adresse", From 32a17c0044919edbea24f1fc7b22e88bde635b83 Mon Sep 17 00:00:00 2001 From: Francisco Serrador Date: Wed, 18 Mar 2026 22:15:11 +0100 Subject: [PATCH 074/124] Translated using Weblate (Spanish) Currently translated at 99.7% (1160 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/ --- client/strings/es.json | 138 ++++++++++++++++++++++++----------------- 1 file changed, 81 insertions(+), 57 deletions(-) diff --git a/client/strings/es.json b/client/strings/es.json index 73ec1cd2d..4569f04bd 100644 --- a/client/strings/es.json +++ b/client/strings/es.json @@ -20,10 +20,10 @@ "ButtonCheckAndDownloadNewEpisodes": "Comprobar y descargar episodios nuevos", "ButtonChooseAFolder": "Elegir una carpeta", "ButtonChooseFiles": "Elegir archivos", - "ButtonClearFilter": "Quitar filtros", + "ButtonClearFilter": "Vaciar filtro", "ButtonClose": "Cerrar", "ButtonCloseFeed": "Cerrar suministro", - "ButtonCloseSession": "Cerrar la sesión abierta", + "ButtonCloseSession": "Cerrar sesión abierta", "ButtonCollections": "Colecciones", "ButtonConfigureScanner": "Configurar Escáner", "ButtonCreate": "Crear", @@ -33,27 +33,27 @@ "ButtonEdit": "Editar", "ButtonEditChapters": "Editar capítulos", "ButtonEditPodcast": "Editar pódcast", - "ButtonEnable": "Permitir", + "ButtonEnable": "Habilitar", "ButtonFireAndFail": "Ejecutado y fallido", "ButtonFireOnTest": "Activar evento de prueba", "ButtonForceReScan": "Forzar Re-Escaneo", "ButtonFullPath": "Ruta completa", "ButtonHide": "Ocultar", "ButtonHome": "Inicio", - "ButtonIssues": "Problemas", + "ButtonIssues": "Cuestiones", "ButtonJumpBackward": "Retroceder", "ButtonJumpForward": "Adelantar", "ButtonLatest": "Más recientes", "ButtonLibrary": "Biblioteca", - "ButtonLogout": "Salir", - "ButtonLookup": "Buscar", + "ButtonLogout": "Cerrar Sesión", + "ButtonLookup": "Averiguar", "ButtonManageTracks": "Gestionar pistas", "ButtonMapChapterTitles": "Asignar Títulos a Capítulos", "ButtonMatchAllAuthors": "Encontrar Todos los Autores", - "ButtonMatchBooks": "Encontrar Libros", + "ButtonMatchBooks": "Cotejar Libros", "ButtonNevermind": "Olvidar", "ButtonNext": "Siguiente", - "ButtonNextChapter": "Siguiente Capítulo", + "ButtonNextChapter": "Siguiente capítulo", "ButtonNextItemInQueue": "El siguiente elemento en cola", "ButtonOk": "Aceptar", "ButtonOpenFeed": "Abrir suministro", @@ -64,26 +64,26 @@ "ButtonPlaying": "Reproduciendo", "ButtonPlaylists": "Listas de reproducción", "ButtonPrevious": "Anterior", - "ButtonPreviousChapter": "Capítulo Anterior", + "ButtonPreviousChapter": "Capítulo anterior", "ButtonProbeAudioFile": "Examinar archivo de audio", - "ButtonPurgeAllCache": "Purgar toda la antememoria", - "ButtonPurgeItemsCache": "Purgar antememoria de elementos", - "ButtonQueueAddItem": "Añadir a la cola", - "ButtonQueueRemoveItem": "Quitar de la cola", + "ButtonPurgeAllCache": "Purgar toda la caché", + "ButtonPurgeItemsCache": "Purgar caché de elementos", + "ButtonQueueAddItem": "Añadir a cola", + "ButtonQueueRemoveItem": "Quitar de cola", "ButtonQuickEmbed": "Inserción rápida", - "ButtonQuickEmbedMetadata": "Agregue metadatos rápidamente", - "ButtonQuickMatch": "Encontrar Rápido", + "ButtonQuickEmbedMetadata": "Empotrar metadatos rápidamente", + "ButtonQuickMatch": "Cotejo Rápido", "ButtonReScan": "Re-Escanear", "ButtonRead": "Leer", "ButtonReadLess": "Leer menos", "ButtonReadMore": "Leer más", - "ButtonRefresh": "Actualizar", + "ButtonRefresh": "Recargar", "ButtonRemove": "Quitar", "ButtonRemoveAll": "Quitar todo", "ButtonRemoveAllLibraryItems": "Quitar todos los elementos de la biblioteca", - "ButtonRemoveFromContinueListening": "Quitar de Continuar escuchando", - "ButtonRemoveFromContinueReading": "Quitar de Continuar leyendo", - "ButtonRemoveSeriesFromContinueSeries": "Quitar serie de Continuar serie", + "ButtonRemoveFromContinueListening": "Quitar desde Escucha Continua", + "ButtonRemoveFromContinueReading": "Quitar desde Continuar Leyendo", + "ButtonRemoveSeriesFromContinueSeries": "Quitar Series desde Series Continuas", "ButtonReset": "Restablecer", "ButtonResetToDefault": "Restaurar valores predeterminados", "ButtonRestore": "Restaurar", @@ -92,47 +92,47 @@ "ButtonSaveTracklist": "Guardar lista de pistas", "ButtonScan": "Escanear", "ButtonScanLibrary": "Escanear biblioteca", - "ButtonScrollLeft": "Desplazarse hacia la izquierda", - "ButtonScrollRight": "Desplazarse hacia la derecha", + "ButtonScrollLeft": "Desplazarse a la izquierda", + "ButtonScrollRight": "Desplazarse a la derecha", "ButtonSearch": "Buscar", "ButtonSelectFolderPath": "Seleccionar ruta de carpeta", "ButtonSeries": "Series", - "ButtonSetChaptersFromTracks": "Seleccionar Capítulos Según las Pistas", + "ButtonSetChaptersFromTracks": "Establecer capítulos según las pistas", "ButtonShare": "Compartir", - "ButtonShiftTimes": "Desplazar Tiempos", + "ButtonShiftTimes": "Veces de Desplazo", "ButtonShow": "Mostrar", "ButtonStartM4BEncode": "Iniciar Codificación M4B", - "ButtonStartMetadataEmbed": "Iniciar la Inserción de Metadata", + "ButtonStartMetadataEmbed": "Iniciar Inserción de Metadatos", "ButtonStats": "Estadísticas", - "ButtonSubmit": "Enviar", + "ButtonSubmit": "Entregar", "ButtonTest": "Prueba", - "ButtonUnlinkOpenId": "Desvincular OpenID", - "ButtonUpload": "Cargar", - "ButtonUploadBackup": "Cargar respaldo", - "ButtonUploadCover": "Cargar cubierta", - "ButtonUploadOPMLFile": "Cargar archivo OPML", + "ButtonUnlinkOpenId": "Desenlazar OpenID", + "ButtonUpload": "Subir", + "ButtonUploadBackup": "Subir Respaldo", + "ButtonUploadCover": "Subir Cubierta", + "ButtonUploadOPMLFile": "Subir archivo OPML", "ButtonUserDelete": "Eliminar usuario {0}", "ButtonUserEdit": "Editar usuario {0}", "ButtonViewAll": "Ver todo", "ButtonYes": "Sí", "ErrorUploadFetchMetadataAPI": "Error al recuperar los metadatos", - "ErrorUploadFetchMetadataNoResults": "No se pudieron recuperar los metadatos; pruebe a actualizar el título o autor", - "ErrorUploadLacksTitle": "Debe tener título", + "ErrorUploadFetchMetadataNoResults": "No se pudieron recuperar los metadatos; pruebe a actualizar el título y/o autor", + "ErrorUploadLacksTitle": "Debe tener un título", "HeaderAccount": "Cuenta", "HeaderAddCustomMetadataProvider": "Añadir proveedor de metadatos personalizado", "HeaderAdvanced": "Avanzado", "HeaderApiKeys": "Claves API", - "HeaderAppriseNotificationSettings": "Configuración de notificaciones de Apprise", - "HeaderAudioTracks": "Pistas de audio", + "HeaderAppriseNotificationSettings": "Ajustes de notificaciones de Apprise", + "HeaderAudioTracks": "Pistas de Audio", "HeaderAudiobookTools": "Herramientas de Gestión de Archivos de Audiolibro", "HeaderAuthentication": "Autenticación", "HeaderBackups": "Respaldos", "HeaderBulkChapterModal": "Añadir Múltiples Capítulos", - "HeaderChangePassword": "Cambiar contraseña", + "HeaderChangePassword": "Cambiar Contraseña", "HeaderChapters": "Capítulos", "HeaderChooseAFolder": "Escoger una Carpeta", "HeaderCollection": "Colección", - "HeaderCollectionItems": "Elementos en la colección", + "HeaderCollectionItems": "Elementos de colección", "HeaderCover": "Cubierta", "HeaderCurrentDownloads": "Descargas actuales", "HeaderCustomMessageOnLogin": "Mensaje personalizado al acceder", @@ -144,7 +144,7 @@ "HeaderEmailSettings": "Configuración de correo electrónico", "HeaderEpisodes": "Episodios", "HeaderEreaderDevices": "Dispositivos Ereader", - "HeaderEreaderSettings": "Configuración del lector", + "HeaderEreaderSettings": "Ajustes del lector", "HeaderFiles": "Archivos", "HeaderFindChapters": "Buscar capítulos", "HeaderIgnoredFiles": "Archivos ignorados", @@ -195,14 +195,14 @@ "HeaderScheduleLibraryScans": "Programar Escaneo Automático de Biblioteca", "HeaderSession": "Sesión", "HeaderSetBackupSchedule": "Programar Respaldo", - "HeaderSettings": "Configuración", + "HeaderSettings": "Ajustes", "HeaderSettingsDisplay": "Interfaz", "HeaderSettingsExperimental": "Funcionalidades experimentales", "HeaderSettingsGeneral": "Generales", "HeaderSettingsScanner": "Escáner", "HeaderSettingsSecurity": "Seguridad", "HeaderSettingsWebClient": "Cliente web", - "HeaderSleepTimer": "Temporizador de apagado", + "HeaderSleepTimer": "Cronómetro de dormida", "HeaderStatsLargestItems": "Elementos más grandes", "HeaderStatsLongestItems": "Elementos más extensos (h)", "HeaderStatsMinutesListeningChart": "Minutos escuchando (últimos 7 días)", @@ -212,7 +212,7 @@ "HeaderTableOfContents": "Sumario", "HeaderTools": "Herramientas", "HeaderUpdateAccount": "Actualizar cuenta", - "HeaderUpdateApiKey": "Actualizar clave API", + "HeaderUpdateApiKey": "Actualizar Clave API", "HeaderUpdateAuthor": "Actualizar autor", "HeaderUpdateDetails": "Actualizar detalles", "HeaderUpdateLibrary": "Actualizar biblioteca", @@ -233,8 +233,8 @@ "LabelAddToCollectionBatch": "Añadir {0} libros a colección", "LabelAddToPlaylist": "Añadir a lista de reproducción", "LabelAddToPlaylistBatch": "Añadir {0} elementos a lista de reproducción", - "LabelAddedAt": "Añadido", - "LabelAddedDate": "{0} Añadido", + "LabelAddedAt": "Añadido en", + "LabelAddedDate": "Añadido {0}", "LabelAdminUsersOnly": "Solamente usuarios administradores", "LabelAll": "Todos", "LabelAllEpisodesDownloaded": "Todos los episodios descargados", @@ -255,7 +255,7 @@ "LabelAuthorFirstLast": "Autor (Nombre Apellido)", "LabelAuthorLastFirst": "Autor (Apellido, Nombre)", "LabelAuthors": "Autores", - "LabelAutoDownloadEpisodes": "Descargar episodios automáticamente", + "LabelAutoDownloadEpisodes": "Auto-Descargar episodios", "LabelAutoFetchMetadata": "Recuperar metadatos automáticamente", "LabelAutoFetchMetadataHelp": "Obtiene metadatos de título, autor y serie para agilizar la carga. Es posible que haya que cotejar metadatos adicionales después de la carga.", "LabelAutoLaunch": "Lanzamiento automático", @@ -275,7 +275,7 @@ "LabelBonus": "Bonus", "LabelBooks": "Libros", "LabelButtonText": "Texto del botón", - "LabelByAuthor": "por", + "LabelByAuthor": "por {0}", "LabelChangePassword": "Cambiar contraseña", "LabelChannels": "Canales", "LabelChapterCount": "{0} capítulos", @@ -286,13 +286,13 @@ "LabelClickToUseCurrentValue": "Pulse para utilizar el valor actual", "LabelClosePlayer": "Cerrar reproductor", "LabelCodec": "Codec", - "LabelCollapseSeries": "Colapsar serie", + "LabelCollapseSeries": "Colapsar Series", "LabelCollapseSubSeries": "Contraer la subserie", "LabelCollection": "Colección", "LabelCollections": "Colecciones", "LabelComplete": "Completo", "LabelConfirmPassword": "Confirmar contraseña", - "LabelContinueListening": "Seguir escuchando", + "LabelContinueListening": "Seguir Escuchando", "LabelContinueReading": "Continuar leyendo", "LabelContinueSeries": "Continuar series", "LabelCorsAllowed": "Orígenes CORS Permitidos", @@ -332,7 +332,7 @@ "LabelEmailSettingsFromAddress": "Remitente", "LabelEmailSettingsRejectUnauthorized": "Rechazar certificados no autorizados", "LabelEmailSettingsRejectUnauthorizedHelp": "Desactivar la validación de certificados SSL puede exponer su conexión a riesgos de seguridad, como los ataques por intermediario. Desactive esta opción solo si conoce las implicaciones y confía en el servidor de correo al que se conecta.", - "LabelEmailSettingsSecure": "Seguridad", + "LabelEmailSettingsSecure": "Seguro", "LabelEmailSettingsSecureHelp": "Si está activado, se usará TLS para conectarse al servidor. Si está apagado, se usará TLS si su servidor tiene soporte para la extensión STARTTLS. En la mayoría de los casos, puede dejar esta opción activada si se está conectando al puerto 465. Apáguela en el caso de usar los puertos 587 o 25. (de nodemailer.com/smtp/#authentication)", "LabelEmailSettingsTestAddress": "Probar dirección", "LabelEmbeddedCover": "Cubierta incrustada", @@ -378,6 +378,7 @@ "LabelFilterByUser": "Filtrar por Usuario", "LabelFindEpisodes": "Buscar Episodio", "LabelFinished": "Terminado", + "LabelFinishedDate": "Finalizado {0}", "LabelFolder": "Carpeta", "LabelFolders": "Carpetas", "LabelFontBold": "Negrilla", @@ -422,6 +423,7 @@ "LabelLanguages": "Idiomas", "LabelLastBookAdded": "Último libro añadido", "LabelLastBookUpdated": "Último libro actualizado", + "LabelLastProgressDate": "Último progreso: {0}", "LabelLastSeen": "Última Vez Visto", "LabelLastTime": "Última Vez", "LabelLastUpdate": "Última Actualización", @@ -434,6 +436,9 @@ "LabelLibraryFilterSublistEmpty": "Sin {0}", "LabelLibraryItem": "Elemento de Biblioteca", "LabelLibraryName": "Nombre de Biblioteca", + "LabelLibrarySortByProgress": "Progreso: Último actualizado", + "LabelLibrarySortByProgressFinished": "Progreso: Finalizado", + "LabelLibrarySortByProgressStarted": "Progreso: Iniciado", "LabelLimit": "Limites", "LabelLineSpacing": "Interlineado", "LabelListenAgain": "Volver a escuchar", @@ -442,6 +447,7 @@ "LabelLogLevelWarn": "Advertencia", "LabelLookForNewEpisodesAfterDate": "Buscar Nuevos Episodios a partir de esta Fecha", "LabelLowestPriority": "Menor prioridad", + "LabelMatchConfidence": "Confidencia", "LabelMatchExistingUsersBy": "Emparejar a los usuarios existentes por", "LabelMatchExistingUsersByDescription": "Se utiliza para conectar usuarios existentes. Una vez conectados, los usuarios serán emparejados por un identificador único de su proveedor de SSO", "LabelMaxEpisodesToDownload": "Número máximo # de episodios para descargar. Usa 0 para descargar una cantidad ilimitada.", @@ -460,7 +466,7 @@ "LabelMissingEbook": "No tiene libro electrónico", "LabelMissingSupplementaryEbook": "No tiene libro electrónico suplementario", "LabelMobileRedirectURIs": "URIs de redirección a móviles permitidos", - "LabelMobileRedirectURIsDescription": "Esta es una lista blanca de URI de redireccionamiento válidos para aplicaciones móviles. El predeterminado es audiobookshelf , que puede eliminar o complementar con URI adicionales para la integración de aplicaciones de terceros. Usando un asterisco ( * ) como única entrada que permite cualquier URI.", + "LabelMobileRedirectURIsDescription": "Esta es una lista en blanco de las URI de re‐direccionamiento válidos para aplicaciones móviles. El predeterminado es audiobookshelf , que puede retirar o sustituir con las URI adicionales para la integración de aplicaciones de terceros. Usando un asterisco (* ) como única entrada que permite cualquier URI.", "LabelMore": "Más", "LabelMoreInfo": "Más información", "LabelName": "Nombre", @@ -473,6 +479,7 @@ "LabelNextBackupDate": "Fecha del siguiente respaldo", "LabelNextChapters": "Los próximos capítulos serán:", "LabelNextScheduledRun": "Próxima ejecución programada", + "LabelNoApiKeys": "Sin claves API", "LabelNoCustomMetadataProviders": "Sin proveedores de metadatos personalizados", "LabelNoEpisodesSelected": "Ningún Episodio Seleccionado", "LabelNotFinished": "No terminado", @@ -563,6 +570,7 @@ "LabelSelectAll": "Seleccionar todo", "LabelSelectAllEpisodes": "Seleccionar todos los episodios", "LabelSelectEpisodesShowing": "Seleccionar los {0} episodios visibles", + "LabelSelectUser": "Seleccionar usuario", "LabelSelectUsers": "Seleccionar usuarios", "LabelSendEbookToDevice": "Enviar libro electrónico a...", "LabelSequence": "Secuencia", @@ -580,8 +588,8 @@ "LabelSettingsBookshelfViewHelp": "Diseño Esqueuomorfo con Estantes de Madera", "LabelSettingsChromecastSupport": "Compatibilidad con Chromecast", "LabelSettingsDateFormat": "Formato de Fecha", - "LabelSettingsEnableWatcher": "Buscar cambios automáticamente en las bibliotecas", - "LabelSettingsEnableWatcherForLibrary": "Buscar cambios automáticamente en la biblioteca", + "LabelSettingsEnableWatcher": "Vigilar automáticamente los cambios en bibliotecas", + "LabelSettingsEnableWatcherForLibrary": "Vigilar automáticamente los cambios de biblioteca", "LabelSettingsEnableWatcherHelp": "Permite agregar/actualizar elementos automáticamente cuando se detectan cambios en los archivos. *Requiere reiniciar el servidor", "LabelSettingsEpubsAllowScriptedContent": "Permitir scripts en epubs", "LabelSettingsEpubsAllowScriptedContentHelp": "Permitir que los archivos epub ejecuten scripts. Se recomienda mantener esta opción desactivada a menos que confíe en el origen de los archivos epub.", @@ -614,7 +622,7 @@ "LabelSettingsStoreMetadataWithItemHelp": "Por defecto, los archivos de metadatos se almacenan en /metadata/items. Si habilita esta opción, los archivos de metadatos se guardarán en la carpeta de elementos de su biblioteca", "LabelSettingsTimeFormat": "Formato de Tiempo", "LabelShare": "Compartir", - "LabelShareDownloadableHelp": "Permite a quienes posean el enlace de compartición descargar un archivo ZIP del elemento de la biblioteca.", + "LabelShareDownloadableHelp": "Permite a quienes posean el enlace de compartición descargar un archivo zip del elemento de la biblioteca.", "LabelShareOpen": "abrir un recurso compartido", "LabelShareURL": "Compartir la URL", "LabelShowAll": "Mostrar todo", @@ -630,6 +638,7 @@ "LabelStartTime": "Tiempo de Inicio", "LabelStarted": "Iniciado", "LabelStartedAt": "Iniciado En", + "LabelStartedDate": "Iniciado {0}", "LabelStatsAudioTracks": "Pistas de Audio", "LabelStatsAuthors": "Autores", "LabelStatsBestDay": "Mejor día", @@ -659,6 +668,7 @@ "LabelTheme": "Tema", "LabelThemeDark": "Oscuro", "LabelThemeLight": "Claro", + "LabelThemeSepia": "Sepia", "LabelTimeBase": "Tiempo Base", "LabelTimeDurationXHours": "{0} horas", "LabelTimeDurationXMinutes": "{0} minutos", @@ -727,8 +737,10 @@ "MessageAddToPlayerQueue": "Agregar a fila del Reproductor", "MessageAppriseDescription": "Para usar esta función deberás tener la API de Apprise corriendo o una API que maneje los mismos resultados.
La URL de la API de Apprise debe tener la misma ruta de archivos que donde se envían las notificaciones. Por ejemplo: si su API esta en http://192.168.1.1:8337 entonces pondría http://192.168.1.1:8337/notify.", "MessageAsinCheck": "Cerciórese de usar el ASIN de la región correcta de Audible, no de Amazon.", + "MessageAuthenticationLegacyTokenWarning": "Los vales de API heredados serán retirados en el futuro. Utilice las claves de API en su lugar.", "MessageAuthenticationOIDCChangesRestart": "Reinicie el servidor tras el guardado para aplicar los cambios de OIDC.", - "MessageBackupsDescription": "Los respaldos incluyen: usuarios, el progreso del los usuarios, los detalles de los elementos de la biblioteca, la configuración del servidor y las imágenes en /metadata/items y /metadata/authors. Los Respaldos NO incluyen ningún archivo guardado en la carpeta de tu biblioteca.", + "MessageAuthenticationSecurityMessage": "La autenticación ha sido mejorada para seguridad. Todos los usuarios requieren reiniciar sesión.", + "MessageBackupsDescription": "Los respaldos incluyen: usuarios, el progreso del los usuarios, los detalles de los elementos de la biblioteca, la configuración del servidor y las imágenes en /metadata/items y /metadata/authors. Los Respaldos no incluyen ningún archivo guardado en la carpeta de tu biblioteca.", "MessageBackupsLocationEditNote": "Nota: actualizar la ubicación de la copia de respaldo no moverá ni modificará los respaldos existentes", "MessageBackupsLocationNoEditNote": "Nota: la ubicación de la copia de respaldo se establece a través de una variable de entorno y no se puede cambiar aquí.", "MessageBackupsLocationPathEmpty": "La ruta de la copia de seguridad no puede estar vacía", @@ -750,6 +762,7 @@ "MessageChaptersNotFound": "Capítulos no encontrados", "MessageCheckingCron": "Revisando cron...", "MessageConfirmCloseFeed": "¿Confirma que quiere cerrar este suministro?", + "MessageConfirmDeleteApiKey": "¿Está seguro que desea eliminar la clave API «{0}»?", "MessageConfirmDeleteBackup": "¿Confirma que quiere eliminar el respaldo de {0}?", "MessageConfirmDeleteDevice": "¿Confirma que quiere eliminar el lector electrónico «{0}»?", "MessageConfirmDeleteFile": "Esto eliminará el archivo del sistema de archivos. ¿Quiere continuar?", @@ -768,8 +781,8 @@ "MessageConfirmMarkSeriesFinished": "¿Confirma que quiere marcar todos los libros de esta serie como terminados?", "MessageConfirmMarkSeriesNotFinished": "¿Confirma que quiere marcar todos los libros de esta serie como no terminados?", "MessageConfirmNotificationTestTrigger": "¿Activar esta notificación con datos de prueba?", - "MessageConfirmPurgeCache": "Purgar la antememoria eliminará el directorio completo ubicado en /metadata/cache.

¿Confirma que quiere eliminar el directorio de antememoria?", - "MessageConfirmPurgeItemsCache": "Purgar la antememoria de elementos eliminará el directorio completo ubicado en /metadata/cache/items.
¿Lo confirma?", + "MessageConfirmPurgeCache": "La purga del caché eliminará el directorio completo en /metadata/cache.

¿Confirma que desea quitar el directorio de caché?", + "MessageConfirmPurgeItemsCache": "Purgar el caché de elementos eliminará el directorio completo ubicado en /metadata/cache/items.
¿Lo confirma?", "MessageConfirmQuickEmbed": "Atención: la incrustación rápida no realiza copias de respaldo a ninguno de sus archivos de audio. Cerciórese de haber realizado una copia de los mismos previamente.

¿Quiere continuar?", "MessageConfirmQuickMatchEpisodes": "El reconocimiento rápido de extensiones sobrescribirá los detalles si se encuentra una coincidencia. Se actualizarán las extensiones no reconocidas. ¿Quiere continuar?", "MessageConfirmReScanLibraryItems": "¿Confirma que quiere volver a analizar {0} elementos?", @@ -795,14 +808,16 @@ "MessageDaysListenedInTheLastYear": "{0} días escuchados el año pasado", "MessageDownloadingEpisode": "Descargando episodio", "MessageDragFilesIntoTrackOrder": "Arrastre los archivos al orden correcto de las pistas", - "MessageEmbedFailed": "Falló la incrustación.", - "MessageEmbedFinished": "Finalizó la incrustación.", + "MessageEmbedFailed": "Incorporación incorrecta.", + "MessageEmbedFinished": "Incorporación finalizada.", "MessageEmbedQueue": "En cola para incrustar metadatos ({0} en cola)", "MessageEpisodesQueuedForDownload": "{0} episodio(s) en cola para descargar", "MessageEreaderDevices": "Para garantizar la entrega de libros electrónicos, es posible que tenga que agregar la dirección de correo electrónico anterior como remitente válido para cada dispositivo enumerado a continuación.", "MessageFeedURLWillBe": "El URL del suministro será {0}", "MessageFetching": "Recuperando...", "MessageForceReScanDescription": "Escaneará todos los archivos como un nuevo escaneo. Archivos de audio con etiquetas ID3, archivos OPF y archivos de texto serán escaneados como nuevos.", + "MessageHeatmapListeningTimeTooltip": "{0} escuchando en {1}", + "MessageHeatmapNoListeningSessions": "No enumera sesiones en {0}", "MessageImportantNotice": "¡Notificación importante!", "MessageInsertChapterBelow": "Insertar capítulo debajo", "MessageInvalidAsin": "ASIN no válido", @@ -873,7 +888,7 @@ "MessageResetChaptersConfirm": "¿Confirma que quiere deshacer los cambios y restablecer los capítulos a su estado original?", "MessageRestoreBackupConfirm": "¿Confirma que quiere restaurar el respaldo creado el", "MessageRestoreBackupWarning": "Restaurar sobrescribirá toda la base de datos localizada en /config y las imágenes de portadas en /metadata/items y /metadata/authors.

El respaldo no modifica ningún archivo en las carpetas de su biblioteca. Si ha habilitado la opción del servidor para almacenar portadas y metadata en las carpetas de su biblioteca, esos archivos no se respaldan o sobrescriben.

Todos los clientes que usen su servidor se actualizarán automáticamente.", - "MessageScheduleLibraryScanNote": "Para la mayoría de los usuarios, se recomienda dejar esta función desactivada y mantener activada la configuración del observador de carpetas. El observador de carpetas detectará automáticamente los cambios en las carpetas de la biblioteca. El observador de carpetas no funciona para todos los sistemas de archivos (como NFS), por lo que se pueden utilizar exploraciones programadas de la biblioteca en su lugar.", + "MessageScheduleLibraryScanNote": "Para muchos usuarios, es recomendado dejar esta característica inhabilitada y mantener habilitados los ajustes de la «Vigía automática de cambio de biblioteca»: detectará automáticamente los cambios en sus carpetas de bibliotecas. Habilitar esta características si «Vigía automática de cambio de biblioteca» no funciona en su sistema de archivo (como NFS).", "MessageScheduleRunEveryWeekdayAtTime": "Ejecutar cada {0} a las {1}", "MessageSearchResultsFor": "Resultados de la búsqueda de", "MessageSelected": "{0} seleccionado(s)", @@ -999,6 +1014,8 @@ "ToastBulkChapterInvalidCount": "Por favor ingrese un número válido entre 1 y 150", "ToastCachePurgeFailed": "No se pudo purgar la antememoria", "ToastCachePurgeSuccess": "Se purgó la antememoria correctamente", + "ToastChapterLocked": "El capítulo está bloqueado.", + "ToastChapterStartTimeAdjusted": "El capítulo inicia el tiempo ajustado en {0} segundos", "ToastChaptersAllLocked": "Todos los capítulos están bloqueados. Desbloquee algunos capítulos para cambiar sus tiempos.", "ToastChaptersHaveErrors": "Los capítulos tienen errores", "ToastChaptersInvalidShiftAmountLast": "Cantidad de desplazamiento no válida. La hora de inicio del último capítulo se extendería más allá de la duración de este audiolibro.", @@ -1009,6 +1026,8 @@ "ToastCollectionItemsAddFailed": "Artículo(s) añadido(s) a la colección fallido(s)", "ToastCollectionRemoveSuccess": "Colección quitada", "ToastCollectionUpdateSuccess": "Colección actualizada", + "ToastConnectionNotAvailable": "Conexión no disponible. Intenta de nuevo más tarde", + "ToastCoverSearchFailed": "Cobertura de búsqueda incorrecta", "ToastCoverUpdateFailed": "Error al actualizar la cubierta", "ToastDateTimeInvalidOrIncomplete": "Fecha y hora no válidas o incompletas", "ToastDeleteFileFailed": "Falló la eliminación del archivo", @@ -1024,6 +1043,8 @@ "ToastEpisodeDownloadQueueClearSuccess": "Se borró la cola de descargas de los episodios", "ToastEpisodeUpdateSuccess": "{0} episodio(s) actualizado(s)", "ToastErrorCannotShare": "No se puede compartir de forma nativa en este dispositivo", + "ToastFailedToCreate": "Ha fallado al crear", + "ToastFailedToDelete": "Ha fallado al eliminar", "ToastFailedToLoadData": "Error al cargar data", "ToastFailedToMatch": "Error al emparejar", "ToastFailedToShare": "Error al compartir", @@ -1031,6 +1052,7 @@ "ToastInvalidImageUrl": "URL de la imagen no válida", "ToastInvalidMaxEpisodesToDownload": "Número máximo de episodios para descargar no válidos", "ToastInvalidUrl": "URL no válida", + "ToastInvalidUrls": "Una o más URL son inválidas", "ToastItemCoverUpdateSuccess": "Cubierta del elemento actualizada", "ToastItemDeletedFailed": "Error al eliminar el elemento", "ToastItemDeletedSuccess": "Elemento borrado", @@ -1055,6 +1077,7 @@ "ToastMustHaveAtLeastOnePath": "Debe tener al menos una ruta", "ToastNameEmailRequired": "Son obligatorios el nombre y el correo electrónico", "ToastNameRequired": "Nombre obligatorio", + "ToastNewApiKeyUserError": "Debe seleccionar un usuario", "ToastNewEpisodesFound": "{0} nuevo(s) episodio(s) encontrado(s)", "ToastNewUserCreatedFailed": "No se pudo crear la cuenta: «{0}»", "ToastNewUserCreatedSuccess": "Nueva cuenta creada", @@ -1133,6 +1156,7 @@ "ToastUserRootRequireName": "Debe introducir un nombre de usuario administrativo", "TooltipAddChapters": "Añadir capítulo(s)", "TooltipAddOneSecond": "Añadir 1 segundo", + "TooltipAdjustChapterStart": "Pulse para ajustar la hora de inicio", "TooltipLockAllChapters": "Bloquear todos los capítulos", "TooltipLockChapter": "Bloquear capítulo (Mayús+clic para rango)", "TooltipSubtractOneSecond": "Restar 1 segundo", From e781ff5eaeaace14985573a6c757f9dc3831e30d Mon Sep 17 00:00:00 2001 From: Francisco Serrador Date: Wed, 18 Mar 2026 22:51:19 +0100 Subject: [PATCH 075/124] Translated using Weblate (Spanish) Currently translated at 99.7% (1160 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/ --- client/strings/es.json | 80 +++++++++++++++++++++--------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/client/strings/es.json b/client/strings/es.json index 4569f04bd..9e64107e2 100644 --- a/client/strings/es.json +++ b/client/strings/es.json @@ -140,49 +140,49 @@ "HeaderDetails": "Detalles", "HeaderDownloadQueue": "Cola de descargas", "HeaderEbookFiles": "Archivos de libros digitales", - "HeaderEmail": "Correo electrónico", - "HeaderEmailSettings": "Configuración de correo electrónico", + "HeaderEmail": "Correo-e", + "HeaderEmailSettings": "Ajustes de correo-e", "HeaderEpisodes": "Episodios", - "HeaderEreaderDevices": "Dispositivos Ereader", - "HeaderEreaderSettings": "Ajustes del lector", + "HeaderEreaderDevices": "Dispositivos Lector-e", + "HeaderEreaderSettings": "Ajustes del Lector-e", "HeaderFiles": "Archivos", "HeaderFindChapters": "Buscar capítulos", "HeaderIgnoredFiles": "Archivos ignorados", - "HeaderItemFiles": "Archivos de elementos", - "HeaderItemMetadataUtils": "Utilidades de metadatos de elementos", + "HeaderItemFiles": "Archivos del elemento", + "HeaderItemMetadataUtils": "Utilidades de metadatos del elemento", "HeaderLastListeningSession": "Última sesión de escucha", "HeaderLatestEpisodes": "Episodios más recientes", "HeaderLibraries": "Bibliotecas", "HeaderLibraryFiles": "Archivos de biblioteca", "HeaderLibraryStats": "Estadísticas de biblioteca", - "HeaderListeningSessions": "Sesión", + "HeaderListeningSessions": "Sesiones Listadas", "HeaderListeningStats": "Estadísticas de Tiempo Escuchado", - "HeaderLogin": "Acceder", - "HeaderLogs": "Registros", + "HeaderLogin": "Inicio de Sesión", + "HeaderLogs": "Bitácoras", "HeaderManageGenres": "Gestionar géneros", "HeaderManageTags": "Gestionar etiquetas", "HeaderMapDetails": "Asignar Detalles", - "HeaderMatch": "Encontrar", + "HeaderMatch": "Coincidir", "HeaderMetadataOrderOfPrecedence": "Orden de precedencia de metadatos", - "HeaderMetadataToEmbed": "Metadatos para Insertar", - "HeaderNewAccount": "Cuenta nueva", + "HeaderMetadataToEmbed": "Metadatos para empotrar", + "HeaderNewAccount": "Crear Cuenta", "HeaderNewApiKey": "Nueva clave API", "HeaderNewLibrary": "Biblioteca nueva", - "HeaderNotificationCreate": "Crear notificación", - "HeaderNotificationUpdate": "Notificación de actualización", + "HeaderNotificationCreate": "Crear Notificación", + "HeaderNotificationUpdate": "Notificación de Actualización", "HeaderNotifications": "Notificaciones", "HeaderOpenIDConnectAuthentication": "Autenticación OpenID Connect", - "HeaderOpenListeningSessions": "Sesiones públicas de escucha", + "HeaderOpenListeningSessions": "Abrir escucha de sesiones", "HeaderOpenRSSFeed": "Abrir suministro RSS", "HeaderOtherFiles": "Otros archivos", "HeaderPasswordAuthentication": "Autenticación por contraseña", "HeaderPermissions": "Permisos", "HeaderPlayerQueue": "Cola del reproductor", - "HeaderPlayerSettings": "Configuración del reproductor", + "HeaderPlayerSettings": "Ajustes del reproductor", "HeaderPlaylist": "Lista de reproducción", "HeaderPlaylistItems": "Elementos de lista de reproducción", "HeaderPodcastsToAdd": "Pódcast para añadir", - "HeaderPresets": "Preconfiguraciones", + "HeaderPresets": "Preajustes", "HeaderPreviewCover": "Previsualizar cubierta", "HeaderRSSFeedGeneral": "Detalles de RSS", "HeaderRSSFeedIsOpen": "El suministro RSS está abierto", @@ -191,13 +191,13 @@ "HeaderRemoveEpisodes": "Quitar {0} episodios", "HeaderSavedMediaProgress": "Guardar Progreso de Multimedia", "HeaderSchedule": "Horario", - "HeaderScheduleEpisodeDownloads": "Programar descargas automáticas de episodios", - "HeaderScheduleLibraryScans": "Programar Escaneo Automático de Biblioteca", + "HeaderScheduleEpisodeDownloads": "Planificador de auto‐descargas de episodios", + "HeaderScheduleLibraryScans": "Planificar Auto‐Escaneo de Biblioteca", "HeaderSession": "Sesión", - "HeaderSetBackupSchedule": "Programar Respaldo", + "HeaderSetBackupSchedule": "Establecer Planificación de Respaldo", "HeaderSettings": "Ajustes", "HeaderSettingsDisplay": "Interfaz", - "HeaderSettingsExperimental": "Funcionalidades experimentales", + "HeaderSettingsExperimental": "Características experimentales", "HeaderSettingsGeneral": "Generales", "HeaderSettingsScanner": "Escáner", "HeaderSettingsSecurity": "Seguridad", @@ -241,21 +241,21 @@ "LabelAllUsers": "Todos los usuarios", "LabelAllUsersExcludingGuests": "Todos los usuarios excepto invitados", "LabelAllUsersIncludingGuests": "Todos los usuarios e invitados", - "LabelAlreadyInYourLibrary": "Ya existe en la Biblioteca", - "LabelApiKeyCreated": "La clave de API “{0}” se ha creado con éxito.", + "LabelAlreadyInYourLibrary": "Ya dentro de tu biblioteca", + "LabelApiKeyCreated": "La clave de API “{0}” se ha creado correctamente.", "LabelApiKeyCreatedDescription": "Asegúrate de copiar la clave de API ahora, no la volverás a ver otra vez.", "LabelApiKeyUser": "Actuar en nombre del usuario", "LabelApiKeyUserDescription": "Esta clave de API tendrá los mismos permisos que el usuario al que representa. En los registros se verá como si la solicitud la hubiera hecho el usuario directamente.", - "LabelApiToken": "Token de la API", + "LabelApiToken": "Vale del API", "LabelAppend": "Adjuntar", - "LabelAudioBitrate": "Tasa de bits del audio (por ejemplo, 128k)", + "LabelAudioBitrate": "Tasa de bit del audio (p.ej., 128k)", "LabelAudioChannels": "Canales de audio (1 o 2)", "LabelAudioCodec": "Códec de audio", "LabelAuthor": "Autor", "LabelAuthorFirstLast": "Autor (Nombre Apellido)", "LabelAuthorLastFirst": "Autor (Apellido, Nombre)", "LabelAuthors": "Autores", - "LabelAutoDownloadEpisodes": "Auto-Descargar episodios", + "LabelAutoDownloadEpisodes": "Auto‐Descargar episodios", "LabelAutoFetchMetadata": "Recuperar metadatos automáticamente", "LabelAutoFetchMetadataHelp": "Obtiene metadatos de título, autor y serie para agilizar la carga. Es posible que haya que cotejar metadatos adicionales después de la carga.", "LabelAutoLaunch": "Lanzamiento automático", @@ -325,8 +325,8 @@ "LabelDurationComparisonLonger": "({0} más largo)", "LabelDurationComparisonShorter": "({0} más corto)", "LabelDurationFound": "Duración Comprobada:", - "LabelEbook": "Libro electrónico", - "LabelEbooks": "Libros electrónicos", + "LabelEbook": "Libro-e", + "LabelEbooks": "Libros-e", "LabelEdit": "Editar", "LabelEmail": "Correo electrónico", "LabelEmailSettingsFromAddress": "Remitente", @@ -377,12 +377,12 @@ "LabelFilename": "Nombre del archivo", "LabelFilterByUser": "Filtrar por Usuario", "LabelFindEpisodes": "Buscar Episodio", - "LabelFinished": "Terminado", + "LabelFinished": "Finalizado", "LabelFinishedDate": "Finalizado {0}", "LabelFolder": "Carpeta", "LabelFolders": "Carpetas", "LabelFontBold": "Negrilla", - "LabelFontBoldness": "Peso tipográfico", + "LabelFontBoldness": "Tipográfico sin Negrita", "LabelFontFamily": "Familia tipográfica", "LabelFontItalic": "Itálica", "LabelFontScale": "Escala de letra", @@ -392,8 +392,8 @@ "LabelGenre": "Género", "LabelGenres": "Géneros", "LabelHardDeleteFile": "Eliminar Definitivamente", - "LabelHasEbook": "Tiene un libro", - "LabelHasSupplementaryEbook": "Tiene un libro complementario", + "LabelHasEbook": "Tiene libro-e", + "LabelHasSupplementaryEbook": "Tiene un libro-e suplementario", "LabelHideSubtitles": "Ocultar subtítulos", "LabelHighestPriority": "Mayor prioridad", "LabelHost": "Anfitrión", @@ -482,7 +482,7 @@ "LabelNoApiKeys": "Sin claves API", "LabelNoCustomMetadataProviders": "Sin proveedores de metadatos personalizados", "LabelNoEpisodesSelected": "Ningún Episodio Seleccionado", - "LabelNotFinished": "No terminado", + "LabelNotFinished": "No finalizado", "LabelNotStarted": "Sin iniciar", "LabelNotes": "Notas", "LabelNotificationAppriseURL": "URL(s) de Apprise", @@ -496,7 +496,7 @@ "LabelNotificationsMaxQueueSizeHelp": "Las notificaciones están limitadas a 1 por segundo. Las notificaciones serán ignoradas si llegan al numero máximo de cola para prevenir spam de eventos.", "LabelNumberOfBooks": "Número de libros", "LabelNumberOfChapters": "Número de capítulos:", - "LabelNumberOfEpisodes": "N.º de episodios", + "LabelNumberOfEpisodes": "Nº de episodios", "LabelOpenIDAdvancedPermsClaimDescription": "Nombre de la notificación de OpenID que contiene permisos avanzados para acciones de usuario dentro de la aplicación que se aplicarán a roles que no sean de administrador (si están configurados). Si el reclamo no aparece en la respuesta, se denegará el acceso a ABS. Si falta una sola opción, se tratará como falsa. Asegúrese de que la notificación del proveedor de identidades coincida con la estructura esperada:", "LabelOpenIDClaims": "Deje las siguientes opciones vacías para desactivar la asignación avanzada de grupos y permisos, lo que asignaría de manera automática al grupo «Usuario».", "LabelOpenIDGroupClaimDescription": "Nombre de la declaración OpenID que contiene una lista de grupos del usuario. Comúnmente conocidos como grupos. Si se configura, la aplicación asignará automáticamente roles en función de la pertenencia a grupos del usuario, siempre que estos grupos se denominen \"admin\", \"user\" o \"guest\" en la notificación. La solicitud debe contener una lista, y si un usuario pertenece a varios grupos, la aplicación asignará el rol correspondiente al mayor nivel de acceso. Si ningún grupo coincide, se denegará el acceso.", @@ -526,7 +526,7 @@ "LabelPodcasts": "Pódcast", "LabelPort": "Puerto", "LabelPrefixesToIgnore": "Prefijos para ignorar (no distingue entre mayúsculas y minúsculas)", - "LabelPreventIndexing": "Evite que los directorios de pódcast de iTunes y Google indicen su suministro", + "LabelPreventIndexing": "Evite que los directorios de pódcast de iTunes y Google indexen su suministro", "LabelPrimaryEbook": "Libro electrónico principal", "LabelProgress": "Progreso", "LabelProvider": "Proveedor", @@ -538,11 +538,11 @@ "LabelPublishedDecades": "Décadas publicadas", "LabelPublisher": "Editor", "LabelPublishers": "Editores", - "LabelRSSFeedCustomOwnerEmail": "Correo electrónico de dueño personalizado", - "LabelRSSFeedCustomOwnerName": "Nombre de dueño personalizado", + "LabelRSSFeedCustomOwnerEmail": "Correo-e de propietario personalizado", + "LabelRSSFeedCustomOwnerName": "Nombre de propietario personalizado", "LabelRSSFeedOpen": "Fuente RSS Abierta", "LabelRSSFeedPreventIndexing": "Evitar indización", - "LabelRSSFeedSlug": "«Slug» de suministro RSS", + "LabelRSSFeedSlug": "Ficha de suministro RSS", "LabelRSSFeedURL": "URL de suministro RSS", "LabelRandomly": "Aleatorio", "LabelReAddSeriesToContinueListening": "Volver a agregar la serie para continuar escuchándola", @@ -575,7 +575,7 @@ "LabelSendEbookToDevice": "Enviar libro electrónico a...", "LabelSequence": "Secuencia", "LabelSerial": "En serie", - "LabelSeries": "Serie", + "LabelSeries": "Series", "LabelSeriesName": "Nombre de la serie", "LabelSeriesProgress": "Progreso de la serie", "LabelServerLogLevel": "Nivel de registro del servidor", @@ -629,7 +629,7 @@ "LabelShowSeconds": "Mostrar segundos", "LabelShowSubtitles": "Mostrar subtítulos", "LabelSize": "Tamaño", - "LabelSleepTimer": "Temporizador de apagado", + "LabelSleepTimer": "Temporizador de dormida", "LabelSlug": "Slug", "LabelSortAscending": "Ascendente", "LabelSortDescending": "Descendente", From 5f8db24b961bd59a3069d9c86140eafa7cf65d03 Mon Sep 17 00:00:00 2001 From: Francisco Serrador Date: Wed, 18 Mar 2026 22:52:38 +0100 Subject: [PATCH 076/124] Translated using Weblate (Spanish) Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/ --- client/strings/es.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/strings/es.json b/client/strings/es.json index 9e64107e2..c3b14742e 100644 --- a/client/strings/es.json +++ b/client/strings/es.json @@ -65,7 +65,7 @@ "ButtonPlaylists": "Listas de reproducción", "ButtonPrevious": "Anterior", "ButtonPreviousChapter": "Capítulo anterior", - "ButtonProbeAudioFile": "Examinar archivo de audio", + "ButtonProbeAudioFile": "Sonda del archivo de audio", "ButtonPurgeAllCache": "Purgar toda la caché", "ButtonPurgeItemsCache": "Purgar caché de elementos", "ButtonQueueAddItem": "Añadir a cola", From 9634c46bc593074f0a38e2daefafa044fc0a19b0 Mon Sep 17 00:00:00 2001 From: Francisco Serrador Date: Wed, 18 Mar 2026 22:56:54 +0100 Subject: [PATCH 077/124] Translated using Weblate (Spanish) Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/ --- client/strings/es.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/strings/es.json b/client/strings/es.json index c3b14742e..eae32bd9f 100644 --- a/client/strings/es.json +++ b/client/strings/es.json @@ -212,7 +212,7 @@ "HeaderTableOfContents": "Sumario", "HeaderTools": "Herramientas", "HeaderUpdateAccount": "Actualizar cuenta", - "HeaderUpdateApiKey": "Actualizar Clave API", + "HeaderUpdateApiKey": "Actualizar clave API", "HeaderUpdateAuthor": "Actualizar autor", "HeaderUpdateDetails": "Actualizar detalles", "HeaderUpdateLibrary": "Actualizar biblioteca", From 9821c31f8eb49aea9d6507c9381066e56a59d40a Mon Sep 17 00:00:00 2001 From: advplyr Date: Thu, 19 Mar 2026 16:53:21 -0500 Subject: [PATCH 078/124] Update collection create/update endpoints to strip html tags from collection name --- .../modals/collections/AddCreateModal.vue | 2 +- server/controllers/CollectionController.js | 16 +++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/client/components/modals/collections/AddCreateModal.vue b/client/components/modals/collections/AddCreateModal.vue index 24e8695d6..f0d43c14f 100644 --- a/client/components/modals/collections/AddCreateModal.vue +++ b/client/components/modals/collections/AddCreateModal.vue @@ -227,7 +227,7 @@ export default { .catch((error) => { console.error('Failed to create collection', error) var errMsg = error.response ? error.response.data || '' : '' - this.$toast.error(this.$strings.ToastCollectionCreateFailed + ': ' + errMsg) + this.$toast.error(errMsg) this.processing = false }) } diff --git a/server/controllers/CollectionController.js b/server/controllers/CollectionController.js index 475adfe0f..1476b0f81 100644 --- a/server/controllers/CollectionController.js +++ b/server/controllers/CollectionController.js @@ -3,6 +3,7 @@ const Sequelize = require('sequelize') const Logger = require('../Logger') const SocketAuthority = require('../SocketAuthority') const Database = require('../Database') +const htmlSanitizer = require('../utils/htmlSanitizer') const RssFeedManager = require('../managers/RssFeedManager') @@ -31,8 +32,10 @@ class CollectionController { async create(req, res) { const reqBody = req.body || {} + const nameCleaned = htmlSanitizer.stripAllTags(reqBody.name) + // Validation - if (!reqBody.name || !reqBody.libraryId) { + if (!nameCleaned || !reqBody.libraryId) { return res.status(400).send('Invalid collection data') } if (reqBody.description && typeof reqBody.description !== 'string') { @@ -65,7 +68,7 @@ class CollectionController { newCollection = await Database.collectionModel.create( { libraryId: reqBody.libraryId, - name: reqBody.name, + name: nameCleaned, description: reqBody.description || null }, { transaction } @@ -145,9 +148,12 @@ class CollectionController { collectionUpdatePayload.description = req.body.description wasUpdated = true } - if (req.body.name !== undefined && req.body.name !== req.collection.name) { - collectionUpdatePayload.name = req.body.name - wasUpdated = true + if (req.body.name !== undefined && typeof req.body.name === 'string') { + const nameCleaned = htmlSanitizer.stripAllTags(req.body.name) + if (nameCleaned !== req.collection.name) { + collectionUpdatePayload.name = nameCleaned + wasUpdated = true + } } if (wasUpdated) { From 3faa6f3e7d1ad52101a9355fa07efd26f13ea60d Mon Sep 17 00:00:00 2001 From: advplyr Date: Thu, 19 Mar 2026 16:57:22 -0500 Subject: [PATCH 079/124] Update playlist create/update endpoint to strip all html tags --- server/controllers/PlaylistController.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/server/controllers/PlaylistController.js b/server/controllers/PlaylistController.js index 972c352a4..bc1a7a455 100644 --- a/server/controllers/PlaylistController.js +++ b/server/controllers/PlaylistController.js @@ -2,6 +2,7 @@ const { Request, Response, NextFunction } = require('express') const Logger = require('../Logger') const SocketAuthority = require('../SocketAuthority') const Database = require('../Database') +const htmlSanitizer = require('../utils/htmlSanitizer') /** * @typedef RequestUserObject @@ -29,7 +30,8 @@ class PlaylistController { const reqBody = req.body || {} // Validation - if (!reqBody.name || !reqBody.libraryId) { + const nameCleaned = htmlSanitizer.stripAllTags(reqBody.name) + if (!nameCleaned || !reqBody.libraryId) { return res.status(400).send('Invalid playlist data') } if (reqBody.description && typeof reqBody.description !== 'string') { @@ -84,7 +86,7 @@ class PlaylistController { { libraryId: reqBody.libraryId, userId: req.user.id, - name: reqBody.name, + name: nameCleaned, description: reqBody.description || null }, { transaction } @@ -174,7 +176,11 @@ class PlaylistController { } const playlistUpdatePayload = {} - if (reqBody.name) playlistUpdatePayload.name = reqBody.name + + const nameCleaned = htmlSanitizer.stripAllTags(reqBody.name) + if (nameCleaned) { + playlistUpdatePayload.name = nameCleaned + } if (reqBody.description) playlistUpdatePayload.description = reqBody.description // Update name and description From 8b89b276544be16bdc353f793604d6d8a066463c Mon Sep 17 00:00:00 2001 From: advplyr Date: Thu, 19 Mar 2026 17:09:14 -0500 Subject: [PATCH 080/124] Version bump v2.33.1 --- client/package-lock.json | 4 ++-- client/package.json | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 299741bd5..71bcf26a8 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1,12 +1,12 @@ { "name": "audiobookshelf-client", - "version": "2.33.0", + "version": "2.33.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "audiobookshelf-client", - "version": "2.33.0", + "version": "2.33.1", "license": "ISC", "dependencies": { "@nuxtjs/axios": "^5.13.6", diff --git a/client/package.json b/client/package.json index a1503a506..782f411bd 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "audiobookshelf-client", - "version": "2.33.0", + "version": "2.33.1", "buildNumber": 1, "description": "Self-hosted audiobook and podcast client", "main": "index.js", diff --git a/package-lock.json b/package-lock.json index e07fba51d..d8cc43bc8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "audiobookshelf", - "version": "2.33.0", + "version": "2.33.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "audiobookshelf", - "version": "2.33.0", + "version": "2.33.1", "license": "GPL-3.0", "dependencies": { "axios": "^0.27.2", diff --git a/package.json b/package.json index 3108b5170..d44df1165 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "audiobookshelf", - "version": "2.33.0", + "version": "2.33.1", "buildNumber": 1, "description": "Self-hosted audiobook and podcast server", "main": "index.js", From 5de92d08f9018ed6e0b3af686f00c54b43d0fa27 Mon Sep 17 00:00:00 2001 From: advplyr Date: Sun, 29 Mar 2026 15:36:07 -0500 Subject: [PATCH 081/124] Fix share playback session not including coverAspectRatio --- server/objects/PlaybackSession.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/objects/PlaybackSession.js b/server/objects/PlaybackSession.js index ba031b665..ace0256e8 100644 --- a/server/objects/PlaybackSession.js +++ b/server/objects/PlaybackSession.js @@ -110,7 +110,8 @@ class PlaybackSession { startedAt: this.startedAt, updatedAt: this.updatedAt, audioTracks: this.audioTracks.map((at) => at.toJSON?.() || { ...at }), - libraryItem: libraryItem?.toOldJSONExpanded() || null + libraryItem: libraryItem?.toOldJSONExpanded() || null, + coverAspectRatio: this.coverAspectRatio !== null ? this.coverAspectRatio : undefined // Used for share sessions } } From 093124aac694fbcb18184f84ecbb8dc8aaca7554 Mon Sep 17 00:00:00 2001 From: mikiher Date: Mon, 30 Mar 2026 22:02:56 +0300 Subject: [PATCH 082/124] Emit proper author_updated/added events when updating book media --- server/models/Author.js | 5 +++-- server/models/Book.js | 13 ++++++++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/server/models/Author.js b/server/models/Author.js index 287b66976..d83eef15f 100644 --- a/server/models/Author.js +++ b/server/models/Author.js @@ -115,12 +115,13 @@ class Author extends Model { */ static async findOrCreateByNameAndLibrary(name, libraryId) { const author = await this.getByNameAndLibrary(name, libraryId) - if (author) return author - return this.create({ + if (author) return { author, created: false } + const newAuthor = await this.create({ name, lastFirst: this.getLastFirst(name), libraryId }) + return { author: newAuthor, created: true } } /** diff --git a/server/models/Book.js b/server/models/Book.js index 96371f3a2..d9f2ff132 100644 --- a/server/models/Book.js +++ b/server/models/Book.js @@ -4,6 +4,7 @@ const { getTitlePrefixAtEnd, getTitleIgnorePrefix } = require('../utils') const parseNameString = require('../utils/parsers/parseNameString') const htmlSanitizer = require('../utils/htmlSanitizer') const libraryItemsBookFilters = require('../utils/queries/libraryItemsBookFilters') +const SocketAuthority = require('../SocketAuthority') /** * @typedef EBookFileObject @@ -470,13 +471,23 @@ class Book extends Model { for (const author of authorsRemoved) { await bookAuthorModel.removeByIds(author.id, this.id) + const numBooks = await bookAuthorModel.getCountForAuthor(author.id) + if (numBooks > 0) { + SocketAuthority.emitter('author_updated', author.toOldJSONExpanded(numBooks)) + } Logger.debug(`[Book] "${this.title}" Removed author "${author.name}"`) this.authors = this.authors.filter((au) => au.id !== author.id) } const authorsAdded = [] for (const authorName of newAuthorNames) { - const author = await authorModel.findOrCreateByNameAndLibrary(authorName, libraryId) + const { author, created } = await authorModel.findOrCreateByNameAndLibrary(authorName, libraryId) await bookAuthorModel.create({ bookId: this.id, authorId: author.id }) + if (created) { + SocketAuthority.emitter('author_added', author.toOldJSON()) + } else { + const numBooks = await bookAuthorModel.getCountForAuthor(author.id) + SocketAuthority.emitter('author_updated', author.toOldJSONExpanded(numBooks)) + } Logger.debug(`[Book] "${this.title}" Added author "${author.name}"`) this.authors.push(author) authorsAdded.push(author) From ab3bd6f4a170d7ddf6e50dbeb4e5794f1a011e41 Mon Sep 17 00:00:00 2001 From: advplyr Date: Mon, 30 Mar 2026 16:22:27 -0500 Subject: [PATCH 083/124] Update JS docs --- server/models/Author.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/models/Author.js b/server/models/Author.js index d83eef15f..65561e211 100644 --- a/server/models/Author.js +++ b/server/models/Author.js @@ -111,7 +111,7 @@ class Author extends Model { * * @param {string} name * @param {string} libraryId - * @returns {Promise} + * @returns {Promise<{ author: Author, created: boolean }>} */ static async findOrCreateByNameAndLibrary(name, libraryId) { const author = await this.getByNameAndLibrary(name, libraryId) From fda1a6ea9bac122b3b84e742f244e2c6eb6c660b Mon Sep 17 00:00:00 2001 From: mikiher Date: Tue, 31 Mar 2026 22:02:52 +0300 Subject: [PATCH 084/124] Fix item_removed payload to include libraryId --- server/controllers/LibraryController.js | 6 +++--- server/controllers/LibraryItemController.js | 4 ++-- server/routers/ApiRouter.js | 6 ++++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/server/controllers/LibraryController.js b/server/controllers/LibraryController.js index 55ef45690..be9d03328 100644 --- a/server/controllers/LibraryController.js +++ b/server/controllers/LibraryController.js @@ -462,7 +462,7 @@ class LibraryController { } } Logger.info(`[LibraryController] Removing library item "${libraryItem.id}" from folder "${folder.path}"`) - await this.handleDeleteLibraryItem(libraryItem.id, mediaItemIds) + await this.handleDeleteLibraryItem(libraryItem.id, mediaItemIds, req.library.id) } if (authorIds.length) { @@ -563,7 +563,7 @@ class LibraryController { mediaItemIds.push(libraryItem.mediaId) } Logger.info(`[LibraryController] Removing library item "${libraryItem.id}" from library "${req.library.name}"`) - await this.handleDeleteLibraryItem(libraryItem.id, mediaItemIds) + await this.handleDeleteLibraryItem(libraryItem.id, mediaItemIds, req.library.id) } // Set PlaybackSessions libraryId to null @@ -714,7 +714,7 @@ class LibraryController { } } Logger.info(`[LibraryController] Removing library item "${libraryItem.id}" with issue`) - await this.handleDeleteLibraryItem(libraryItem.id, mediaItemIds) + await this.handleDeleteLibraryItem(libraryItem.id, mediaItemIds, req.library.id) } if (authorIds.length) { diff --git a/server/controllers/LibraryItemController.js b/server/controllers/LibraryItemController.js index 5247dbb06..5f7bd9736 100644 --- a/server/controllers/LibraryItemController.js +++ b/server/controllers/LibraryItemController.js @@ -111,7 +111,7 @@ class LibraryItemController { } } - await this.handleDeleteLibraryItem(req.libraryItem.id, mediaItemIds) + await this.handleDeleteLibraryItem(req.libraryItem.id, mediaItemIds, req.libraryItem.libraryId) if (hardDelete) { Logger.info(`[LibraryItemController] Deleting library item from file system at "${libraryItemPath}"`) await fs.remove(libraryItemPath).catch((error) => { @@ -565,7 +565,7 @@ class LibraryItemController { authorIds.push(...libraryItem.media.authors.map((au) => au.id)) } } - await this.handleDeleteLibraryItem(libraryItem.id, mediaItemIds) + await this.handleDeleteLibraryItem(libraryItem.id, mediaItemIds, libraryItem.libraryId) if (hardDelete) { Logger.info(`[LibraryItemController] Deleting library item from file system at "${libraryItemPath}"`) await fs.remove(libraryItemPath).catch((error) => { diff --git a/server/routers/ApiRouter.js b/server/routers/ApiRouter.js index db04bf5ec..e89b364f3 100644 --- a/server/routers/ApiRouter.js +++ b/server/routers/ApiRouter.js @@ -363,8 +363,9 @@ class ApiRouter { * Remove library item and associated entities * @param {string} libraryItemId * @param {string[]} mediaItemIds array of bookId or podcastEpisodeId + * @param {string} libraryId */ - async handleDeleteLibraryItem(libraryItemId, mediaItemIds) { + async handleDeleteLibraryItem(libraryItemId, mediaItemIds, libraryId) { const numProgressRemoved = await Database.mediaProgressModel.destroy({ where: { mediaItemId: mediaItemIds @@ -395,7 +396,8 @@ class ApiRouter { await Database.libraryItemModel.removeById(libraryItemId) SocketAuthority.emitter('item_removed', { - id: libraryItemId + id: libraryItemId, + libraryId }) } From 5a6b3d8e6158730cd7dede97234884d9137c1285 Mon Sep 17 00:00:00 2001 From: "peter.kottke" Date: Wed, 1 Apr 2026 21:05:48 -0400 Subject: [PATCH 085/124] updates to allow share t argument to over-ride server stored position --- client/pages/share/_slug.vue | 4 +++- server/controllers/ShareController.js | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/client/pages/share/_slug.vue b/client/pages/share/_slug.vue index 64c099632..15748b48f 100644 --- a/client/pages/share/_slug.vue +++ b/client/pages/share/_slug.vue @@ -363,7 +363,9 @@ export default { console.log('Loaded media item share', this.mediaItemShare) } - const startTime = this.playbackSession.currentTime || 0 + const startTime = this.$route.query.t && !isNaN(this.$route.query.t) + ? parseFloat(this.$route.query.t) + : (this.playbackSession.currentTime || 0) this.localAudioPlayer.set(null, this.audioTracks, false, startTime, false) this.localAudioPlayer.on('stateChange', this.playerStateChange.bind(this)) this.localAudioPlayer.on('timeupdate', this.playerTimeUpdate.bind(this)) diff --git a/server/controllers/ShareController.js b/server/controllers/ShareController.js index 3e7ea1deb..f7dd36f83 100644 --- a/server/controllers/ShareController.js +++ b/server/controllers/ShareController.js @@ -20,7 +20,7 @@ const ShareManager = require('../managers/ShareManager') */ class ShareController { - constructor() {} + constructor() { } /** * Public route @@ -53,6 +53,10 @@ class ShareController { if (playbackSession) { if (mediaItemShare.id === playbackSession.mediaItemShareId) { Logger.debug(`[ShareController] Found share playback session ${req.cookies.share_session_id}`) + // If ?t was provided, override the cached currentTime + if (startTime > 0) { + playbackSession.currentTime = startTime + } mediaItemShare.playbackSession = playbackSession.toJSONForClient() return res.json(mediaItemShare) } else { From 522b9735e22dabc111eb47cb5d5e814bc1c1c351 Mon Sep 17 00:00:00 2001 From: Oliver Marriott Date: Thu, 9 Apr 2026 20:49:48 +1000 Subject: [PATCH 086/124] Add `audio/(x-)matroska` to client player MIME types to avoid transcode Firefox, at least, supports playing `matroska/audio` containers natively but the client was not checking for support. Clients that do not support playing `matroska/audio` containers will fallback to transcoding. --- client/players/LocalAudioPlayer.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/client/players/LocalAudioPlayer.js b/client/players/LocalAudioPlayer.js index 7fc17e7aa..377818089 100644 --- a/client/players/LocalAudioPlayer.js +++ b/client/players/LocalAudioPlayer.js @@ -46,7 +46,14 @@ export default class LocalAudioPlayer extends EventEmitter { this.player.addEventListener('loadedmetadata', this.evtLoadedMetadata.bind(this)) this.player.addEventListener('timeupdate', this.evtTimeupdate.bind(this)) - var mimeTypes = ['audio/flac', 'audio/mpeg', 'audio/mp4', 'audio/ogg', 'audio/aac', 'audio/x-ms-wma', 'audio/x-aiff', 'audio/webm'] + var mimeTypes = [ + 'audio/flac', 'audio/mpeg', 'audio/mp4', 'audio/ogg', 'audio/aac', + 'audio/x-ms-wma', 'audio/x-aiff', 'audio/webm', + // `audio/matroska` is the correct mimetype, but at least as of 2026-04-09, + // the detected mimetype for matroska files by the server is `audio/x-matroska`. + // ref: https://www.iana.org/assignments/media-types/media-types.xhtml + 'audio/matroska', 'audio/x-matroska' + ] var mimeTypeCanPlayMap = {} mimeTypes.forEach((mt) => { var canPlay = this.player.canPlayType(mt) From 94c426bd971a40771e8a3503af3eafd01cc89c73 Mon Sep 17 00:00:00 2001 From: advplyr Date: Fri, 10 Apr 2026 16:42:39 -0500 Subject: [PATCH 087/124] Update comments on matroska --- client/players/LocalAudioPlayer.js | 16 +++++++++++----- server/utils/constants.js | 2 ++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/client/players/LocalAudioPlayer.js b/client/players/LocalAudioPlayer.js index 377818089..a0384d54d 100644 --- a/client/players/LocalAudioPlayer.js +++ b/client/players/LocalAudioPlayer.js @@ -47,12 +47,18 @@ export default class LocalAudioPlayer extends EventEmitter { this.player.addEventListener('timeupdate', this.evtTimeupdate.bind(this)) var mimeTypes = [ - 'audio/flac', 'audio/mpeg', 'audio/mp4', 'audio/ogg', 'audio/aac', - 'audio/x-ms-wma', 'audio/x-aiff', 'audio/webm', - // `audio/matroska` is the correct mimetype, but at least as of 2026-04-09, - // the detected mimetype for matroska files by the server is `audio/x-matroska`. + 'audio/flac', + 'audio/mpeg', + 'audio/mp4', + 'audio/ogg', + 'audio/aac', + 'audio/x-ms-wma', + 'audio/x-aiff', + 'audio/webm', + // `audio/matroska` is the correct mimetype, but the server still uses `audio/x-matroska` // ref: https://www.iana.org/assignments/media-types/media-types.xhtml - 'audio/matroska', 'audio/x-matroska' + 'audio/matroska', + 'audio/x-matroska' ] var mimeTypeCanPlayMap = {} mimeTypes.forEach((mt) => { diff --git a/server/utils/constants.js b/server/utils/constants.js index cc5217f41..925035e17 100644 --- a/server/utils/constants.js +++ b/server/utils/constants.js @@ -48,6 +48,8 @@ module.exports.AudioMimeType = { AIF: 'audio/x-aiff', WEBM: 'audio/webm', WEBMA: 'audio/webm', + // TODO: Switch to `audio/matroska`? marked as deprecated in IANA registry + // ref: https://datatracker.ietf.org/doc/html/rfc9559 MKA: 'audio/x-matroska', AWB: 'audio/amr-wb', CAF: 'audio/x-caf', From 455e6051624316856c8ccaf8bcdb83bd289e2112 Mon Sep 17 00:00:00 2001 From: advplyr Date: Fri, 17 Apr 2026 16:30:08 -0500 Subject: [PATCH 088/124] Update author & library item image endpoints to clamp width/height query params --- server/controllers/AuthorController.js | 6 +++--- server/controllers/LibraryItemController.js | 6 +++--- server/utils/index.js | 10 ++++++++++ 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/server/controllers/AuthorController.js b/server/controllers/AuthorController.js index 82ed3e50a..80471ec47 100644 --- a/server/controllers/AuthorController.js +++ b/server/controllers/AuthorController.js @@ -10,7 +10,7 @@ const CacheManager = require('../managers/CacheManager') const CoverManager = require('../managers/CoverManager') const AuthorFinder = require('../finders/AuthorFinder') -const { reqSupportsWebp, isValidASIN } = require('../utils/index') +const { reqSupportsWebp, isValidASIN, clampPositiveInt } = require('../utils/index') const naturalSort = createNewSortInstance({ comparer: new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }).compare @@ -412,8 +412,8 @@ class AuthorController { const options = { format: format || (reqSupportsWebp(req) ? 'webp' : 'jpeg'), - height: height ? parseInt(height) : null, - width: width ? parseInt(width) : null + height: clampPositiveInt(height ? parseInt(height) : null, 4096), + width: clampPositiveInt(width ? parseInt(width) : null, 4096) } return CacheManager.handleAuthorCache(res, authorId, options) } diff --git a/server/controllers/LibraryItemController.js b/server/controllers/LibraryItemController.js index 5f7bd9736..1a6b8ac11 100644 --- a/server/controllers/LibraryItemController.js +++ b/server/controllers/LibraryItemController.js @@ -7,7 +7,7 @@ const SocketAuthority = require('../SocketAuthority') const Database = require('../Database') const zipHelpers = require('../utils/zipHelpers') -const { reqSupportsWebp } = require('../utils/index') +const { reqSupportsWebp, clampPositiveInt } = require('../utils/index') const { ScanResult, AudioMimeType } = require('../utils/constants') const { getAudioMimeTypeFromExtname, encodeUriPath } = require('../utils/fileUtils') const LibraryItemScanner = require('../scanner/LibraryItemScanner') @@ -398,8 +398,8 @@ class LibraryItemController { const options = { format: format || (reqSupportsWebp(req) ? 'webp' : 'jpeg'), - height: height ? parseInt(height) : null, - width: width ? parseInt(width) : null + height: clampPositiveInt(height ? parseInt(height) : null, 4096), + width: clampPositiveInt(width ? parseInt(width) : null, 4096) } return CacheManager.handleCoverCache(res, libraryItemId, options) } diff --git a/server/utils/index.js b/server/utils/index.js index c7700a783..49a7c8e67 100644 --- a/server/utils/index.js +++ b/server/utils/index.js @@ -54,6 +54,16 @@ module.exports.isNullOrNaN = (num) => { return num === null || isNaN(num) } +/** + * @param {number|null|undefined} value + * @param {number} max + * @returns {number|null} + */ +module.exports.clampPositiveInt = (value, max) => { + if (value == null || !Number.isFinite(value) || value <= 0) return null + return Math.min(Math.floor(value), max) +} + const xmlToJSON = (xml) => { return new Promise((resolve, reject) => { parseString(xml, (err, results) => { From 09fa0b38f5af5a9bc8d07b1541dc21718585e697 Mon Sep 17 00:00:00 2001 From: advplyr Date: Fri, 17 Apr 2026 16:51:22 -0500 Subject: [PATCH 089/124] Update podcast create path validation & fix relPath --- server/controllers/PodcastController.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/server/controllers/PodcastController.js b/server/controllers/PodcastController.js index c70287600..f099d05ed 100644 --- a/server/controllers/PodcastController.js +++ b/server/controllers/PodcastController.js @@ -7,7 +7,7 @@ const Database = require('../Database') const fs = require('../libs/fsExtra') const { getPodcastFeed, findMatchingEpisodes } = require('../utils/podcastUtils') -const { getFileTimestampsWithIno, filePathToPOSIX } = require('../utils/fileUtils') +const { getFileTimestampsWithIno, filePathToPOSIX, isSameOrSubPath } = require('../utils/fileUtils') const { validateUrl } = require('../utils/index') const htmlSanitizer = require('../utils/htmlSanitizer') @@ -58,8 +58,18 @@ class PodcastController { return res.status(404).send('Folder not found') } + if (typeof payload.path !== 'string' || !payload.path.trim()) { + return res.status(400).send('Invalid request body. "path" must be a non-empty string') + } + + const libraryFolderPath = filePathToPOSIX(folder.path) const podcastPath = filePathToPOSIX(payload.path) + if (!isSameOrSubPath(libraryFolderPath, podcastPath)) { + Logger.error(`[PodcastController] Create: Podcast path is outside library folder "${libraryFolderPath}": "${podcastPath}"`) + return res.status(400).send('Podcast path must be inside the selected library folder') + } + // Check if a library item with this podcast folder exists already const existingLibraryItem = (await Database.libraryItemModel.count({ @@ -83,7 +93,7 @@ class PodcastController { const libraryItemFolderStats = await getFileTimestampsWithIno(podcastPath) - let relPath = payload.path.replace(folder.fullPath, '') + let relPath = podcastPath.replace(libraryFolderPath, '') if (relPath.startsWith('/')) relPath = relPath.slice(1) let newLibraryItem = null From b27f21fd95b413c5ecd74f5b14d8f2c0284ba6d7 Mon Sep 17 00:00:00 2001 From: advplyr Date: Fri, 17 Apr 2026 16:59:22 -0500 Subject: [PATCH 090/124] Update podcastUtils to sanitize episode subtitle from rss feed --- server/utils/podcastUtils.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/utils/podcastUtils.js b/server/utils/podcastUtils.js index 2042a8e39..1cb0c4cb4 100644 --- a/server/utils/podcastUtils.js +++ b/server/utils/podcastUtils.js @@ -217,6 +217,10 @@ function extractEpisodeData(item) { episode[cleanKey] = extractFirstArrayItemString(item, key) }) + if (episode.subtitle) { + episode.subtitle = htmlSanitizer.sanitize(episode.subtitle.trim()) + } + // Extract psc:chapters if duration is set episode.durationSeconds = episode.duration ? timestampToSeconds(episode.duration) : null From 24cab79c66984b86652161b51290df641eb656f1 Mon Sep 17 00:00:00 2001 From: advplyr Date: Sat, 18 Apr 2026 16:24:48 -0500 Subject: [PATCH 091/124] Update filesystem/pathexists endpoint to use existing isSameOrSubPath func --- server/controllers/FileSystemController.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/controllers/FileSystemController.js b/server/controllers/FileSystemController.js index 4b0a94b39..41e082fd4 100644 --- a/server/controllers/FileSystemController.js +++ b/server/controllers/FileSystemController.js @@ -117,7 +117,7 @@ class FileSystemController { filepath = fileUtils.filePathToPOSIX(filepath) // Ensure filepath is inside library folder (prevents directory traversal) - if (!filepath.startsWith(libraryFolder.path)) { + if (!fileUtils.isSameOrSubPath(libraryFolder.path, filepath)) { Logger.error(`[FileSystemController] Filepath is not inside library folder: ${filepath}`) return res.sendStatus(400) } From 39adefb63281fb4d1dd0c20bd8b706b85f104a89 Mon Sep 17 00:00:00 2001 From: advplyr Date: Sat, 18 Apr 2026 17:03:37 -0500 Subject: [PATCH 092/124] Update backup load & upload to remove tempfile on failed backups, validate details filesize & close zip --- server/managers/BackupManager.js | 39 +++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/server/managers/BackupManager.js b/server/managers/BackupManager.js index 2697f94ea..a7b531e62 100644 --- a/server/managers/BackupManager.js +++ b/server/managers/BackupManager.js @@ -126,13 +126,31 @@ class BackupManager { } catch (error) { // Not a valid zip file Logger.error('[BackupManager] Failed to read backup file - backup might not be a valid .zip file', tempPath, error) + await zip.close().catch(() => {}) + await fs.remove(tempPath).catch((err) => Logger.error(`[BackupManager] Failed to remove rejected backup file "${tempPath}"`, err)) return res.status(400).send('Failed to read backup file - backup might not be a valid .zip file') } - if (!Object.keys(entries).includes('absdatabase.sqlite')) { + if (!entries['absdatabase.sqlite']) { Logger.error(`[BackupManager] Invalid backup with no absdatabase.sqlite file - might be a backup created on an old Audiobookshelf server.`) + await zip.close().catch(() => {}) + await fs.remove(tempPath).catch((err) => Logger.error(`[BackupManager] Failed to remove rejected backup file "${tempPath}"`, err)) return res.status(500).send('Invalid backup with no absdatabase.sqlite file - might be a backup created on an old Audiobookshelf server.') } + const detailsEntry = entries['details'] + if (!detailsEntry) { + Logger.error('[BackupManager] Invalid backup - missing details entry') + await zip.close().catch(() => {}) + await fs.remove(tempPath).catch((err) => Logger.error(`[BackupManager] Failed to remove rejected backup file "${tempPath}"`, err)) + return res.status(400).send('Invalid backup file - missing details entry') + } + if (detailsEntry.size > 1024 * 1024) { + Logger.error(`[BackupManager] Backup details entry too large: ${detailsEntry.size} bytes`) + await zip.close().catch(() => {}) + await fs.remove(tempPath).catch((err) => Logger.error(`[BackupManager] Failed to remove rejected backup file "${tempPath}"`, err)) + return res.status(400).send('Invalid backup file - details entry too large') + } + const data = await zip.entryData('details') const details = data.toString('utf8').split('\n') @@ -140,9 +158,13 @@ class BackupManager { if (!backup.serverVersion) { Logger.error(`[BackupManager] Invalid backup with no server version - might be a backup created before version 2.0.0`) + await zip.close().catch(() => {}) + await fs.remove(tempPath).catch((err) => Logger.error(`[BackupManager] Failed to remove rejected backup file "${tempPath}"`, err)) return res.status(500).send('Invalid backup. Might be a backup created before version 2.0.0.') } + await zip.close().catch(() => {}) + backup.fileSize = await getFileSize(backup.fullPath) const existingBackupIndex = this.backups.findIndex((b) => b.id === backup.id) @@ -257,9 +279,24 @@ class BackupManager { let data = null try { zip = new StreamZip.async({ file: fullFilePath }) + const entries = await zip.entries() + + const detailsEntry = entries['details'] + if (!detailsEntry) { + Logger.error(`[BackupManager] Backup "${fullFilePath}" missing details entry - skipping`) + await zip.close().catch(() => {}) + continue + } + if (detailsEntry.size > 1024 * 1024) { + Logger.error(`[BackupManager] Backup "${fullFilePath}" details entry too large (${detailsEntry.size} bytes) - skipping`) + await zip.close().catch(() => {}) + continue + } + data = await zip.entryData('details') } catch (error) { Logger.error(`[BackupManager] Failed to unzip backup "${fullFilePath}"`, error) + if (zip) await zip.close().catch(() => {}) continue } From b7e8a0474a9df3b175b587405133898a4b1f7c1c Mon Sep 17 00:00:00 2001 From: advplyr Date: Sun, 19 Apr 2026 16:20:31 -0500 Subject: [PATCH 093/124] Update bulk download endpoint ensure items are from the same library requested --- server/controllers/LibraryController.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/server/controllers/LibraryController.js b/server/controllers/LibraryController.js index be9d03328..73b3d5c60 100644 --- a/server/controllers/LibraryController.js +++ b/server/controllers/LibraryController.js @@ -1435,10 +1435,15 @@ class LibraryController { const libraryItems = await Database.libraryItemModel.findAll({ attributes: ['id', 'libraryId', 'path', 'isFile'], where: { - id: itemIds + id: itemIds, + libraryId: req.library.id } }) + if (libraryItems.length < itemIds.length) { + Logger.warn(`[LibraryController] User "${req.user.username}" requested ${itemIds.length} items but only ${libraryItems.length} are in library "${req.library.id}"`) + } + Logger.info(`[LibraryController] User "${req.user.username}" requested download for items "${itemIds}"`) const filename = `LibraryItems-${Date.now()}.zip` From d73b64a19c42ccd0e4416991ce23ce6b9993a171 Mon Sep 17 00:00:00 2001 From: Pavel Miniutka Date: Fri, 20 Mar 2026 10:41:58 +0100 Subject: [PATCH 094/124] Translated using Weblate (Belarusian) Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/be/ --- client/strings/be.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/client/strings/be.json b/client/strings/be.json index d598ef7bf..018694130 100644 --- a/client/strings/be.json +++ b/client/strings/be.json @@ -81,7 +81,7 @@ "ButtonRemove": "Выдаліць", "ButtonRemoveAll": "Выдаліць усе", "ButtonRemoveAllLibraryItems": "Выдаліць усе элементы бібліятэкі", - "ButtonRemoveFromContinueListening": "Выдаліць з Працягнуць праслухоўванне", + "ButtonRemoveFromContinueListening": "Выдаліць з Працяг праслухоўвання", "ButtonRemoveFromContinueReading": "Выдаліць з Працягваць чытанне", "ButtonRemoveSeriesFromContinueSeries": "Выдаліць серыю з Працягваць серыю", "ButtonReset": "Скінуць", @@ -292,7 +292,7 @@ "LabelCollections": "Калекцыі", "LabelComplete": "Завяршыць", "LabelConfirmPassword": "Пацвердзіце пароль", - "LabelContinueListening": "Працягнуць праслухоўванне", + "LabelContinueListening": "Працяг праслухоўвання", "LabelContinueReading": "Працягнуць чытанне", "LabelContinueSeries": "Працягнуць серыі", "LabelCorsAllowed": "Дазволеныя крыніцы CORS", @@ -545,7 +545,7 @@ "LabelRSSFeedSlug": "Ідэнтыфікатар RSS-стужкі", "LabelRSSFeedURL": "URL RSS-стужкі", "LabelRandomly": "Выпадкова", - "LabelReAddSeriesToContinueListening": "Дадаць серыю зноў у Працягнуць праслухоўванне", + "LabelReAddSeriesToContinueListening": "Дадаць серыю зноў у Працяг праслухоўвання", "LabelRead": "Чытаць", "LabelReadAgain": "Чытаць зноў", "LabelReadEbookWithoutProgress": "Чытаць электронную кнігу без захавання прагрэсу", @@ -634,12 +634,12 @@ "LabelSortAscending": "Па ўзрастанні", "LabelSortDescending": "Па ўбыванні", "LabelSortPubDate": "Сартаваць па даце публікацыі", - "LabelStart": "Пачаць", + "LabelStart": "Пачатак", "LabelStartTime": "Час пачатку", "LabelStarted": "Пачата", "LabelStartedAt": "Пачата ў", "LabelStartedDate": "Пачата {0}", - "LabelStatsAudioTracks": "Аўдыятрэкаў", + "LabelStatsAudioTracks": "Аўдыятрэкі", "LabelStatsAuthors": "Аўтараў", "LabelStatsBestDay": "Найлепшы дзень", "LabelStatsDailyAverage": "У сярэднім за дзень", From 2d4df273f0ca5c46455ba5e1ce9e65ba4bace950 Mon Sep 17 00:00:00 2001 From: Francisco Serrador Date: Thu, 19 Mar 2026 18:28:11 +0100 Subject: [PATCH 095/124] Translated using Weblate (Spanish) Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/ --- client/strings/es.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/strings/es.json b/client/strings/es.json index eae32bd9f..e9655563d 100644 --- a/client/strings/es.json +++ b/client/strings/es.json @@ -40,7 +40,7 @@ "ButtonFullPath": "Ruta completa", "ButtonHide": "Ocultar", "ButtonHome": "Inicio", - "ButtonIssues": "Cuestiones", + "ButtonIssues": "Incidencias", "ButtonJumpBackward": "Retroceder", "ButtonJumpForward": "Adelantar", "ButtonLatest": "Más recientes", @@ -850,7 +850,7 @@ "MessageNoEpisodes": "Ningún episodio", "MessageNoFoldersAvailable": "Ninguna carpeta disponible", "MessageNoGenres": "Ningún género", - "MessageNoIssues": "Ningún número", + "MessageNoIssues": "Sin incidencias", "MessageNoItems": "Ningún elemento", "MessageNoItemsFound": "Ningún elemento encontrado", "MessageNoListeningSessions": "Ninguna sesión de escucha", @@ -1116,8 +1116,8 @@ "ToastRemoveFailed": "Error al eliminar", "ToastRemoveItemFromCollectionFailed": "Error al eliminar el elemento de la colección", "ToastRemoveItemFromCollectionSuccess": "Elemento eliminado de la colección", - "ToastRemoveItemsWithIssuesFailed": "Error en la eliminación de artículos de biblioteca incorrectos", - "ToastRemoveItemsWithIssuesSuccess": "Se eliminaron artículos de biblioteca incorrectos", + "ToastRemoveItemsWithIssuesFailed": "Error en la eliminación de artículos de biblioteca con incidencias", + "ToastRemoveItemsWithIssuesSuccess": "Se eliminaron artículos de biblioteca con incidencias", "ToastRenameFailed": "Error al cambiar el nombre", "ToastRescanFailed": "Error al volver a escanear para {0}", "ToastRescanRemoved": "Se eliminó el elemento reescaneado", From 2755204168d7f49a9bef25fe66431e89b502ab74 Mon Sep 17 00:00:00 2001 From: Vadzim Kurdzesau Date: Sun, 29 Mar 2026 15:06:51 +0200 Subject: [PATCH 096/124] Translated using Weblate (Russian) Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ru/ --- client/strings/ru.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/strings/ru.json b/client/strings/ru.json index c84fe9dcf..4375fe056 100644 --- a/client/strings/ru.json +++ b/client/strings/ru.json @@ -437,8 +437,8 @@ "LabelLibraryItem": "Элемент библиотеки", "LabelLibraryName": "Имя библиотеки", "LabelLibrarySortByProgress": "Прогресс: Последнее обновление", - "LabelLibrarySortByProgressFinished": "Прогресс: Завершено", - "LabelLibrarySortByProgressStarted": "Прогресс: Начато", + "LabelLibrarySortByProgressFinished": "Прогресс: Закончена", + "LabelLibrarySortByProgressStarted": "Прогресс: Начата", "LabelLimit": "Лимит", "LabelLineSpacing": "Межстрочный интервал", "LabelListenAgain": "Послушать снова", From bc6bfbe80408282528dbb296ae1fc74b8b4e30d4 Mon Sep 17 00:00:00 2001 From: tfr tint Date: Sun, 29 Mar 2026 10:52:39 +0200 Subject: [PATCH 097/124] Translated using Weblate (Italian) Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/it/ --- client/strings/it.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/client/strings/it.json b/client/strings/it.json index d2178cbf6..b2b2b19a8 100644 --- a/client/strings/it.json +++ b/client/strings/it.json @@ -34,7 +34,7 @@ "ButtonEditChapters": "Modifica Capitoli", "ButtonEditPodcast": "Modifica Podcast", "ButtonEnable": "Abilita", - "ButtonFireAndFail": "Fire and Fail", + "ButtonFireAndFail": "Centro e fallimento", "ButtonFireOnTest": "Fire onTest event", "ButtonForceReScan": "Forza Re-Scan", "ButtonFullPath": "Percorso Completo", @@ -182,7 +182,7 @@ "HeaderPlaylist": "Playlist", "HeaderPlaylistItems": "Elementi della playlist", "HeaderPodcastsToAdd": "Podcasts da Aggiungere", - "HeaderPresets": "Presets", + "HeaderPresets": "Preimpostazioni", "HeaderPreviewCover": "Anteprima Cover", "HeaderRSSFeedGeneral": "Dettagli RSS", "HeaderRSSFeedIsOpen": "RSS Feed è aperto", @@ -306,7 +306,7 @@ "LabelCustomCronExpression": "Espressione Cron personalizzata:", "LabelDatetime": "Data & Ora", "LabelDays": "Giorni", - "LabelDeleteFromFileSystemCheckbox": "Elimina dal file system (togli la spunta per eliminarla solo dal DB)", + "LabelDeleteFromFileSystemCheckbox": "Elimina dal file system (despunta per rimuoverla solo dal database)", "LabelDescription": "Descrizione", "LabelDeselectAll": "Deseleziona Tutto", "LabelDetectedPattern": "Trovato pattern:", @@ -436,9 +436,9 @@ "LabelLibraryFilterSublistEmpty": "Nessuno {0}", "LabelLibraryItem": "Elementi della biblioteca", "LabelLibraryName": "Nome della biblioteca", - "LabelLibrarySortByProgress": "Progressi: Ultimi aggiornamenti", - "LabelLibrarySortByProgressFinished": "Progressi: Completati", - "LabelLibrarySortByProgressStarted": "Progressi: Iniziati", + "LabelLibrarySortByProgress": "Progresso: ultimo aggiornamento", + "LabelLibrarySortByProgressFinished": "Progresso: finito", + "LabelLibrarySortByProgressStarted": "Progresso: iniziato", "LabelLimit": "Limiti", "LabelLineSpacing": "Interlinea", "LabelListenAgain": "Ascolta ancora", @@ -497,7 +497,7 @@ "LabelNumberOfBooks": "Numero di libri", "LabelNumberOfChapters": "Numero di capitoli:", "LabelNumberOfEpisodes": "Numero di episodi", - "LabelOpenIDAdvancedPermsClaimDescription": "Nome dell'attestazione OpenID che contiene autorizzazioni avanzate per le azioni dell'utente all'interno dell'applicazione che verranno applicate ai ruoli non amministratori (se configurato). Se il reclamo manca nella risposta, l'accesso ad ABS verrà negato. Se manca una singola opzione, verrà trattata comefalsa. Assicurati che l'attestazione del provider di identità corrisponda alla struttura prevista:", + "LabelOpenIDAdvancedPermsClaimDescription": "Nome dell'attestazione OpenID che contiene autorizzazioni avanzate per le azioni dell'utente all'interno dell'applicazione che verranno applicate ai ruoli non amministrativi (se configurato). Se il reclamo manca nella risposta, l'accesso ad ABS verrà negato. Se manca una singola opzione, verrà trattata come falso. Assicurati che l'attestazione del provider di identità corrisponda alla struttura prevista:", "LabelOpenIDClaims": "Lasciare vuote le seguenti opzioni per disabilitare l'assegnazione avanzata di gruppi e autorizzazioni, assegnando quindi automaticamente il gruppo \"Utente\".", "LabelOpenIDGroupClaimDescription": "Nome dell'attestazione OpenID che contiene un elenco dei gruppi dell'utente. Comunemente indicato come gruppo. se configurato, l'applicazione assegnerà automaticamente i ruoli in base alle appartenenze ai gruppi dell'utente, a condizione che tali gruppi siano denominati \"admin\", \"utente\" o \"ospite\" senza distinzione tra maiuscole e minuscole nell'attestazione. L'attestazione deve contenere un elenco e, se un utente appartiene a più gruppi, l'applicazione assegnerà il ruolo corrispondente al livello di accesso più alto. Se nessun gruppo corrisponde, l'accesso verrà negato.", "LabelOpenRSSFeed": "Apri RSS Feed", @@ -530,7 +530,7 @@ "LabelPrimaryEbook": "Libro principale", "LabelProgress": "Cominciati", "LabelProvider": "Fornitore", - "LabelProviderAuthorizationValue": "Authorization Header Value", + "LabelProviderAuthorizationValue": "Valore intestazione di autorizzazione", "LabelPubDate": "Data di pubblicazione", "LabelPublishYear": "Anno di pubblicazione", "LabelPublishedDate": "Pubblicati {0}", @@ -682,7 +682,7 @@ "LabelTitle": "Titolo", "LabelToolsEmbedMetadata": "Incorpora Metadata", "LabelToolsEmbedMetadataDescription": "Incorpora i metadati nei file audio, inclusi l'immagine di copertina e i capitoli.", - "LabelToolsM4bEncoder": "M4B Encoder", + "LabelToolsM4bEncoder": "Codificatore M4B", "LabelToolsMakeM4b": "Crea un file M4B", "LabelToolsMakeM4bDescription": "Genera un file audiolibro M4B con metadati incorporati, immagine di copertina e capitoli.", "LabelToolsSplitM4b": "Converti M4B in MP3", @@ -854,7 +854,7 @@ "MessageNoItems": "Nessun oggetto", "MessageNoItemsFound": "Nessun oggetto trovato", "MessageNoListeningSessions": "Nessuna sessione di ascolto", - "MessageNoLogs": "Nessun Log", + "MessageNoLogs": "Nessun rapporto", "MessageNoMediaProgress": "Nessun progresso multimediale", "MessageNoNotifications": "Nessuna notifica", "MessageNoPodcastFeed": "Podcast non valido: nessun feed", @@ -1109,7 +1109,7 @@ "ToastProgressIsNotBeingSynced": "L'avanzamento non è sincronizzato, riavviare la riproduzione", "ToastProviderCreatedFailed": "Impossibile aggiungere il provider", "ToastProviderCreatedSuccess": "Aggiunto nuovo provider", - "ToastProviderNameAndUrlRequired": "Nome e URL richiesti", + "ToastProviderNameAndUrlRequired": "Nome e Url richiesti", "ToastProviderRemoveSuccess": "Provider rimosso", "ToastRSSFeedCloseFailed": "Errore chiusura flusso RSS", "ToastRSSFeedCloseSuccess": "Flusso RSS chiuso", From 0e2cdde731cb86de7d5452add2ebd1af10967926 Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 29 Mar 2026 15:01:47 +0200 Subject: [PATCH 098/124] Translated using Weblate (Slovak) Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sk/ --- client/strings/sk.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/strings/sk.json b/client/strings/sk.json index 6101eba7d..23ed276e8 100644 --- a/client/strings/sk.json +++ b/client/strings/sk.json @@ -275,7 +275,7 @@ "LabelBonus": "Bonus", "LabelBooks": "Knihy", "LabelButtonText": "Text tlačidla", - "LabelByAuthor": "od", + "LabelByAuthor": "od {0}", "LabelChangePassword": "Zmeniť heslo", "LabelChannels": "Kanály", "LabelChapterCount": "{0} kapitol", From 0bbf8bde5cfb0fa576a3f701de6d0d3d6a75e1ae Mon Sep 17 00:00:00 2001 From: A L Date: Mon, 30 Mar 2026 13:23:36 +0200 Subject: [PATCH 099/124] Translated using Weblate (Bulgarian) Currently translated at 91.2% (1061 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/bg/ --- client/strings/bg.json | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/client/strings/bg.json b/client/strings/bg.json index dacd62087..460f0ff83 100644 --- a/client/strings/bg.json +++ b/client/strings/bg.json @@ -436,7 +436,7 @@ "LabelLibraryFilterSublistEmpty": "Не {0}", "LabelLibraryItem": "Елемент на Библиотека", "LabelLibraryName": "Име на Библиотека", - "LabelLibrarySortByProgress": "Прогрес: Последно Обновен", + "LabelLibrarySortByProgress": "Прогрес: Последно обновление", "LabelLibrarySortByProgressFinished": "Прогрес: Приключено", "LabelLibrarySortByProgressStarted": "Прогрес: Започнато", "LabelLimit": "Лимит", @@ -892,7 +892,7 @@ "MessageScheduleRunEveryWeekdayAtTime": "Изпълни всеки {0} в {1}", "MessageSearchResultsFor": "Резултати от търсенето за", "MessageSelected": "{0} избрани", - "MessageSeriesSequenceCannotContainSpaces": "Подредбата в серия не може да съдържа шпации.", + "MessageSeriesSequenceCannotContainSpaces": "Подредбата в серия не може да съдържа шпации", "MessageServerCouldNotBeReached": "Сървърът не може да бъде достигнат", "MessageSetChaptersFromTracksDescription": "Задайте глави, като използвате всеки аудио файл като глава и заглавие на главата като име на аудио файла", "MessageShareExpirationWillBe": "Изтичането ще бъде на {0}", @@ -956,6 +956,8 @@ "NotificationOnEpisodeDownloadedDescription": "Изпълнява се при автоматично изтегляне на подкаст епизод", "NotificationOnRSSFeedDisabledDescription": "Изпълнява се, когато автоматичното изтегляне на епизодите е деактивирано, поради твърде много неуспешни опити", "NotificationOnRSSFeedFailedDescription": "Пуска се когато заявката за RSS фийд е неуспешна за автоматично сваляне на епизод", + "NotificationOnTestDescription": "Event за тестване на системата за нотификации", + "PlaceholderBulkChapterInput": "Въведете име на глава или използвайте номериране (прим. 'Епизод 1', 'Глава 10', '1.')", "PlaceholderNewCollection": "Ново име на колекцията", "PlaceholderNewFolderPath": "Нов път на папката", "PlaceholderNewPlaylist": "Ново име на плейлиста", @@ -963,26 +965,58 @@ "PlaceholderSearchEpisode": "Търсене на Епизоди...", "StatsAuthorsAdded": "добаврени автори", "StatsBooksAdded": "добавени книги", + "StatsBooksAdditional": "Някой от вкючените добавки…", "StatsBooksFinished": "завършени книги", + "StatsBooksFinishedThisYear": "Някой от книгите приключени тази година…", + "StatsBooksListenedTo": "слушани книги", + "StatsCollectionGrewTo": "Твоята книжна колекция израсна до…", + "StatsSessions": "сесии", + "StatsSpentListening": "прекарано в слушане", + "StatsTopAuthor": "ТОП АВТОР", + "StatsTopAuthors": "ТОП АВТОРИ", + "StatsTopGenre": "ТОП ЖАНР", + "StatsTopGenres": "ТОП ЖАНРА", + "StatsTopMonth": "ТОП МЕСЕЦ", + "StatsTopNarrator": "ТОП РАЗКАЗВАЧ", + "StatsTopNarrators": "ТОП РАЗКАЗВАЧИ", + "StatsTotalDuration": "С пълно времетраене…", + "StatsYearInReview": "ГОДИНАТА В ПРЕГЛЕД", "ToastAccountUpdateSuccess": "Успешно обновяване на акаунта", + "ToastAppriseUrlRequired": "Трябва да въведете Apprise URL", + "ToastAsinRequired": "ASIN-а е задължителен", "ToastAuthorImageRemoveSuccess": "Авторската снимка е премахната", + "ToastAuthorNotFound": "Автор \"{0}\" не е намерен", + "ToastAuthorRemoveSuccess": "Арторът е премахнат", + "ToastAuthorSearchNotFound": "Авторът не е намерен", "ToastAuthorUpdateMerged": "Обновяване на автора сливано", "ToastAuthorUpdateSuccess": "Автора обновен", "ToastAuthorUpdateSuccessNoImageFound": "Автор обновен (не е намерена снимка)", + "ToastBackupAppliedSuccess": "Архивът е приложен", "ToastBackupCreateFailed": "Неуспешно създаване на архив", "ToastBackupCreateSuccess": "Архивът е създаден", "ToastBackupDeleteFailed": "Неуспешно изтриване на архив", "ToastBackupDeleteSuccess": "Архивът е изтрит", + "ToastBackupInvalidMaxKeep": "Невалиден брой за архиви за запазване", + "ToastBackupInvalidMaxSize": "Невалиден максимален рамер на архив", "ToastBackupRestoreFailed": "Неуспешно възстановяване на архив", "ToastBackupUploadFailed": "Неуспешно качване на архив", "ToastBackupUploadSuccess": "Архивът е качен", + "ToastBatchApplyDetailsToItemsSuccess": "Детайли приложени на предмети", + "ToastBatchDeleteFailed": "Груповото изтриване се провали", + "ToastBatchDeleteSuccess": "Успешно групово изтриване", + "ToastBatchQuickMatchFailed": "Груповото Бързо Съвпадение се провали!", + "ToastBatchQuickMatchStarted": "Груповото Бързо Съвпадение на {0} книги започна!", "ToastBatchUpdateFailed": "Неуспешно групово актуализиране", "ToastBatchUpdateSuccess": "Успешно групово актуализиране", "ToastBookmarkCreateFailed": "Неуспешно създаване на отметка", "ToastBookmarkCreateSuccess": "Отметката е създадена", "ToastBookmarkRemoveSuccess": "Отметката е премахната", + "ToastBulkChapterInvalidCount": "Въведете число между 1 и 150", "ToastCachePurgeFailed": "Неуспешно изчистване на кеша", "ToastCachePurgeSuccess": "Успешно изчистване на кеша", + "ToastChapterLocked": "Главата е заключена.", + "ToastChapterStartTimeAdjusted": "Начално време на главате е настоено с {0} секунди", + "ToastChaptersAllLocked": "Всички глави са заключени. Оключете някой глави за да преместите техните времена.", "ToastChaptersHaveErrors": "Главите имат грешки", "ToastChaptersMustHaveTitles": "Главите трябва да имат заглавия", "ToastCollectionRemoveSuccess": "Колекцията е премахната", From a30fe15b106c905b5e235326c268c96328895791 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BB=D0=B0=D0=B4=D0=B8=D0=BC=D0=B8=D1=80=20=D0=9C?= =?UTF-8?q?=D0=B0=D0=BA=D0=B5=D0=B5=D0=B2?= Date: Thu, 9 Apr 2026 12:03:20 +0200 Subject: [PATCH 100/124] Translated using Weblate (Russian) Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ru/ --- client/strings/ru.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/strings/ru.json b/client/strings/ru.json index 4375fe056..ed3f18b89 100644 --- a/client/strings/ru.json +++ b/client/strings/ru.json @@ -392,7 +392,7 @@ "LabelGenre": "Жанр", "LabelGenres": "Жанры", "LabelHardDeleteFile": "Жесткое удаление файла", - "LabelHasEbook": "Есть e-книга", + "LabelHasEbook": "Есть электронная книга", "LabelHasSupplementaryEbook": "Есть дополнительная e-книга", "LabelHideSubtitles": "Скрыть серии", "LabelHighestPriority": "Наивысший приоритет", From f558182d94f9c6a7c61319ced8f23592a6e43afb Mon Sep 17 00:00:00 2001 From: tizio04 Date: Wed, 8 Apr 2026 15:12:13 +0200 Subject: [PATCH 101/124] Translated using Weblate (Italian) Currently translated at 99.9% (1162 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/it/ --- client/strings/it.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/strings/it.json b/client/strings/it.json index b2b2b19a8..4b3921624 100644 --- a/client/strings/it.json +++ b/client/strings/it.json @@ -674,7 +674,7 @@ "LabelTimeDurationXMinutes": "{0} minuti", "LabelTimeDurationXSeconds": "{0} secondi", "LabelTimeInMinutes": "Tempo in minuti", - "LabelTimeLeft": "{0} sinistra", + "LabelTimeLeft": "{0} rimasti", "LabelTimeListened": "Tempo di Ascolto", "LabelTimeListenedToday": "Tempo di Ascolto Oggi", "LabelTimeRemaining": "{0} rimanente", From 3e0099e8d9f240c468ac184aded63613dffb4d4b Mon Sep 17 00:00:00 2001 From: Pavel Miniutka Date: Fri, 10 Apr 2026 15:29:08 +0200 Subject: [PATCH 102/124] Translated using Weblate (Belarusian) Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/be/ --- client/strings/be.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/strings/be.json b/client/strings/be.json index 018694130..43668acb5 100644 --- a/client/strings/be.json +++ b/client/strings/be.json @@ -16,7 +16,7 @@ "ButtonBrowseForFolder": "Агляд папак", "ButtonCancel": "Скасаваць", "ButtonCancelEncode": "Скасаваць кадзіраванне", - "ButtonChangeRootPassword": "Зменіце Root пароль", + "ButtonChangeRootPassword": "Змяніць пароль root", "ButtonCheckAndDownloadNewEpisodes": "Праверыць і спампаваць новыя выпускі", "ButtonChooseAFolder": "Выбраць папку", "ButtonChooseFiles": "Выбраць файлы", @@ -252,8 +252,8 @@ "LabelAudioChannels": "Аўдыяканалы (1 або 2)", "LabelAudioCodec": "Аўдыякодэк", "LabelAuthor": "Аўтар", - "LabelAuthorFirstLast": "Аўтар (Імя Прозвішча)", - "LabelAuthorLastFirst": "Аўтар (Прозвішча, Імя)", + "LabelAuthorFirstLast": "Аўтар (імя, прозвішча)", + "LabelAuthorLastFirst": "Аўтар (прозвішча, імя)", "LabelAuthors": "Аўтары", "LabelAutoDownloadEpisodes": "Аўтаматычна спампоўваць выпускі", "LabelAutoFetchMetadata": "Аўтаматычнае атрыманне метаданых", @@ -424,7 +424,7 @@ "LabelLastBookAdded": "Апошняя дададзеная кніга", "LabelLastBookUpdated": "Апошняя абноўленая кніга", "LabelLastProgressDate": "Апошні прагрэс: {0}", - "LabelLastSeen": "Апошні прагляд", + "LabelLastSeen": "Апошняя актыўнасць", "LabelLastTime": "Апошні раз", "LabelLastUpdate": "Апошняе абнаўленне", "LabelLayout": "Знешні выгляд", From 88879f140923aa6b92c111245539ea16dbea6a86 Mon Sep 17 00:00:00 2001 From: Mario Date: Thu, 16 Apr 2026 11:41:30 +0200 Subject: [PATCH 103/124] Translated using Weblate (German) Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/ --- client/strings/de.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/strings/de.json b/client/strings/de.json index ed0cae52c..0e8cb051f 100644 --- a/client/strings/de.json +++ b/client/strings/de.json @@ -622,7 +622,7 @@ "LabelSettingsStoreMetadataWithItemHelp": "Standardmäßig werden die Metadaten in /metadata/items gespeichert. Wenn diese Option aktiviert ist, werden die Metadaten als OPF-Datei (Textdatei) in dem gleichen Ordner gespeichert in welchem sich auch das Medium befindet", "LabelSettingsTimeFormat": "Zeitformat", "LabelShare": "Freigeben", - "LabelShareDownloadableHelp": "Erlaubt es einem Nutzer, mit dem Link, die Dateien des Mediums als ZIP herunterzuladen.", + "LabelShareDownloadableHelp": "Erlaubt es einem Nutzer, mit dem Link die Dateien des Mediums als ZIP herunterzuladen.", "LabelShareOpen": "Freigeben", "LabelShareURL": "Freigabe URL", "LabelShowAll": "Alles anzeigen", @@ -1103,7 +1103,7 @@ "ToastPodcastCreateFailed": "Podcast konnte nicht erstellt werden", "ToastPodcastCreateSuccess": "Podcast erstellt", "ToastPodcastEpisodeUpdated": "Podcast-Folge aktualisiert", - "ToastPodcastGetFeedFailed": "Fehler beim abrufen des Podcast-Feeds", + "ToastPodcastGetFeedFailed": "Fehler beim Abrufen des Podcast Feeds", "ToastPodcastNoEpisodesInFeed": "Keine Episoden in RSS Feed gefunden", "ToastPodcastNoRssFeed": "Podcast enthält keinen RSS Feed", "ToastProgressIsNotBeingSynced": "Fortschritt wird nicht synchronisiert, Wiedergabe wird neu gestartet", From e3388d4446d5d18a8fb99e1dd9a5fd86f7ba72d3 Mon Sep 17 00:00:00 2001 From: Laurin Sorgend Date: Thu, 16 Apr 2026 11:39:35 +0200 Subject: [PATCH 104/124] Translated using Weblate (German) Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/ --- client/strings/de.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/strings/de.json b/client/strings/de.json index 0e8cb051f..2ace31eba 100644 --- a/client/strings/de.json +++ b/client/strings/de.json @@ -816,7 +816,7 @@ "MessageFeedURLWillBe": "Feed-URL wird {0} sein", "MessageFetching": "Wird abgerufen …", "MessageForceReScanDescription": "Durchsucht alle Dateien erneut, wie bei einem frischen Scan. ID3-Tags von Audiodateien, OPF-Dateien und Textdateien werden neu durchsucht.", - "MessageHeatmapListeningTimeTooltip": "{0} auf {1} gehört", + "MessageHeatmapListeningTimeTooltip": "{0} gehört auf {1}", "MessageHeatmapNoListeningSessions": "Keine Hörsitzungen am {0}", "MessageImportantNotice": "Wichtiger Hinweis!", "MessageInsertChapterBelow": "Kapitel unten einfügen", From e431ea047245322ae5531257e7b331476efaecc4 Mon Sep 17 00:00:00 2001 From: Jan Date: Thu, 16 Apr 2026 11:40:38 +0200 Subject: [PATCH 105/124] Translated using Weblate (German) Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/ --- client/strings/de.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/strings/de.json b/client/strings/de.json index 2ace31eba..2372d5a41 100644 --- a/client/strings/de.json +++ b/client/strings/de.json @@ -116,7 +116,7 @@ "ButtonViewAll": "Alles anzeigen", "ButtonYes": "Ja", "ErrorUploadFetchMetadataAPI": "Fehler beim Abrufen der Metadaten", - "ErrorUploadFetchMetadataNoResults": "Metadaten konnten nicht abgerufen werden. Versuche, den Titel und/oder den Autor zu aktualisieren.", + "ErrorUploadFetchMetadataNoResults": "Metadaten konnten nicht abgerufen werden - versuche den Titel und/oder den Autor zu aktualisieren", "ErrorUploadLacksTitle": "Es muss ein Titel eingegeben werden", "HeaderAccount": "Konto", "HeaderAddCustomMetadataProvider": "Benutzerdefinierten Metadatenanbieter hinzufügen", From aa4a191567ce0758b48d002f31644206ad3e20f0 Mon Sep 17 00:00:00 2001 From: Gernomaly Date: Thu, 16 Apr 2026 11:41:09 +0200 Subject: [PATCH 106/124] Translated using Weblate (German) Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/ --- client/strings/de.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/strings/de.json b/client/strings/de.json index 2372d5a41..4d9ecd412 100644 --- a/client/strings/de.json +++ b/client/strings/de.json @@ -737,7 +737,7 @@ "MessageAddToPlayerQueue": "Zur Abspielwarteliste hinzufügen", "MessageAppriseDescription": "Um diese Funktion nutzen zu können, musst du eine Instanz von Apprise API laufen haben oder eine API verwenden welche dieselbe Anfragen bearbeiten kann.
Die Apprise API Url muss der vollständige URL-Pfad sein, an den die Benachrichtigung gesendet werden soll, z.B. wenn Ihre API-Instanz unter http://192.168.1.1:8337 läuft, würdest du http://192.168.1.1:8337/notify eingeben.", "MessageAsinCheck": "Stelle sicher, dass die ASIN aus der richtigen Audible Region verwendet wird, nicht Amazon.", - "MessageAuthenticationLegacyTokenWarning": "Alte API-Token werden in Zukunft entfernt. Benutze stattdessen API Keys.", + "MessageAuthenticationLegacyTokenWarning": "Nicht mehr unterstützte API tokens werden in der Zukunft entfernt. Nutze stattdessen API Schlüssel.", "MessageAuthenticationOIDCChangesRestart": "Nach dem Speichern muss der Server neugestartet werden um die OIDC Änderungen zu übernehmen.", "MessageAuthenticationSecurityMessage": "Die Anmeldung wurde abgesichert. Benutzersitzungen werden getrennt, alle Benutzer müssen sich erneut anmelden.", "MessageBackupsDescription": "In einer Sicherung werden Benutzer, Benutzerfortschritte, Details zu den Bibliotheksobjekten, Servereinstellungen und Bilder welche in /metadata/items & /metadata/authors gespeichert sind gespeichert. Sicherungen enthalten keine Dateien welche in den einzelnen Bibliotheksordnern (Medien-Ordnern) gespeichert sind.", From b41db23994cb678241e0aeb19681f15ee6739c3a Mon Sep 17 00:00:00 2001 From: advplyr Date: Sun, 19 Apr 2026 16:46:10 -0500 Subject: [PATCH 107/124] Version bump v2.33.2 --- client/package-lock.json | 4 ++-- client/package.json | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 71bcf26a8..57d2da716 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1,12 +1,12 @@ { "name": "audiobookshelf-client", - "version": "2.33.1", + "version": "2.33.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "audiobookshelf-client", - "version": "2.33.1", + "version": "2.33.2", "license": "ISC", "dependencies": { "@nuxtjs/axios": "^5.13.6", diff --git a/client/package.json b/client/package.json index 782f411bd..f747cf90f 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "audiobookshelf-client", - "version": "2.33.1", + "version": "2.33.2", "buildNumber": 1, "description": "Self-hosted audiobook and podcast client", "main": "index.js", diff --git a/package-lock.json b/package-lock.json index d8cc43bc8..250390c67 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "audiobookshelf", - "version": "2.33.1", + "version": "2.33.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "audiobookshelf", - "version": "2.33.1", + "version": "2.33.2", "license": "GPL-3.0", "dependencies": { "axios": "^0.27.2", diff --git a/package.json b/package.json index d44df1165..810863f97 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "audiobookshelf", - "version": "2.33.1", + "version": "2.33.2", "buildNumber": 1, "description": "Self-hosted audiobook and podcast server", "main": "index.js", From 80b39abaa2723cf9bc0065fec702f7353721f340 Mon Sep 17 00:00:00 2001 From: advplyr Date: Tue, 21 Apr 2026 17:13:06 -0500 Subject: [PATCH 108/124] Update library item batch api endpoints check users per-item access & return 403 --- server/controllers/LibraryItemController.js | 39 +++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/server/controllers/LibraryItemController.js b/server/controllers/LibraryItemController.js index 1a6b8ac11..e524ebcfd 100644 --- a/server/controllers/LibraryItemController.js +++ b/server/controllers/LibraryItemController.js @@ -36,6 +36,24 @@ const ShareManager = require('../managers/ShareManager') * @typedef {RequestWithUser & RequestEntityObject & RequestLibraryFileObject} LibraryItemControllerRequestWithFile */ +/** + * Enforce per-item access for batch item routes + * + * @param {RequestWithUser} req + * @param {Response} res + * @param {import('../models/LibraryItem')[]} libraryItems + * @returns {boolean} true if the user may access every item; false if 403 was sent + */ +function ensureUserCanAccessLibraryItemsForBatch(req, res, libraryItems) { + for (const libraryItem of libraryItems) { + if (!req.user.checkCanAccessLibraryItem(libraryItem)) { + res.sendStatus(403) + return false + } + } + return true +} + class LibraryItemController { constructor() {} @@ -547,7 +565,13 @@ class LibraryItemController { return res.sendStatus(404) } + // Ensure user has permission to delete these library items + if (!ensureUserCanAccessLibraryItemsForBatch(req, res, itemsToDelete)) { + return + } + const libraryId = itemsToDelete[0].libraryId + for (const libraryItem of itemsToDelete) { const libraryItemPath = libraryItem.path Logger.info(`[LibraryItemController] (${hardDelete ? 'Hard' : 'Soft'}) deleting Library Item "${libraryItem.media.title}" with id "${libraryItem.id}"`) @@ -581,6 +605,7 @@ class LibraryItemController { } await Database.resetLibraryIssuesFilterData(libraryId) + res.sendStatus(200) } @@ -593,6 +618,11 @@ class LibraryItemController { * @param {Response} res */ async batchUpdate(req, res) { + if (!req.user.canUpdate) { + Logger.warn(`[LibraryItemController] User "${req.user.username}" attempted to batch update without permission`) + return res.sendStatus(403) + } + const updatePayloads = req.body if (!Array.isArray(updatePayloads) || !updatePayloads.length) { Logger.error(`[LibraryItemController] Batch update failed. Invalid payload`) @@ -615,6 +645,11 @@ class LibraryItemController { return res.sendStatus(404) } + // Ensure user has permission to update these library items + if (!ensureUserCanAccessLibraryItemsForBatch(req, res, libraryItems)) { + return + } + let itemsUpdated = 0 const seriesIdsRemoved = [] @@ -695,6 +730,10 @@ class LibraryItemController { const libraryItems = await Database.libraryItemModel.findAllExpandedWhere({ id: libraryItemIds }) + // Ensure user has permission to access these library items + if (!ensureUserCanAccessLibraryItemsForBatch(req, res, libraryItems)) { + return + } res.json({ libraryItems: libraryItems.map((li) => li.toOldJSONExpanded()) }) From 5b2a788cfc9afd8f38a2a21a2f5fe65a5a8a1006 Mon Sep 17 00:00:00 2001 From: advplyr Date: Tue, 21 Apr 2026 17:13:52 -0500 Subject: [PATCH 109/124] Update LibraryItemController test with 403 tests --- .../controllers/LibraryItemController.test.js | 102 +++++++++++++++++- 1 file changed, 101 insertions(+), 1 deletion(-) diff --git a/test/server/controllers/LibraryItemController.test.js b/test/server/controllers/LibraryItemController.test.js index 5a0422392..1d57219d3 100644 --- a/test/server/controllers/LibraryItemController.test.js +++ b/test/server/controllers/LibraryItemController.test.js @@ -123,7 +123,9 @@ describe('LibraryItemController', () => { const fakeReq = { query: {}, user: { - canDelete: true + username: 'test', + canDelete: true, + checkCanAccessLibraryItem: () => true }, body: { libraryItemIds: [libraryItem1Id] @@ -199,4 +201,102 @@ describe('LibraryItemController', () => { expect(series2Exists).to.be.true }) }) + + describe('batch item access control', () => { + let lib1Id + let itemLib1Id + let itemLib2Id + + beforeEach(async () => { + const lib1 = await Database.libraryModel.create({ name: 'Lib 1', mediaType: 'book' }) + const folder1 = await Database.libraryFolderModel.create({ path: '/l1', libraryId: lib1.id }) + const book1 = await Database.bookModel.create({ title: 'B1', audioFiles: [], tags: [], narrators: [], genres: [], chapters: [] }) + const li1 = await Database.libraryItemModel.create({ + libraryFiles: [], + mediaId: book1.id, + mediaType: 'book', + libraryId: lib1.id, + libraryFolderId: folder1.id + }) + lib1Id = lib1.id + itemLib1Id = li1.id + + const lib2 = await Database.libraryModel.create({ name: 'Lib 2', mediaType: 'book' }) + const folder2 = await Database.libraryFolderModel.create({ path: '/l2', libraryId: lib2.id }) + const book2 = await Database.bookModel.create({ title: 'B2', audioFiles: [], tags: [], narrators: [], genres: [], chapters: [] }) + const li2 = await Database.libraryItemModel.create({ + libraryFiles: [], + mediaId: book2.id, + mediaType: 'book', + libraryId: lib2.id, + libraryFolderId: folder2.id + }) + itemLib2Id = li2.id + }) + + const userLimitedToLib1 = () => ({ + username: 'limited', + canDelete: true, + canUpdate: true, + checkCanAccessLibraryItem(li) { + return li.libraryId === lib1Id + } + }) + + it('batchGet returns 403 for a library item the user cannot access', async () => { + const fakeRes = { sendStatus: sinon.spy(), json: sinon.spy() } + const fakeReq = { + body: { libraryItemIds: [itemLib2Id] }, + user: userLimitedToLib1() + } + await LibraryItemController.batchGet.bind(apiRouter)(fakeReq, fakeRes) + expect(fakeRes.sendStatus.calledWith(403)).to.be.true + }) + + it('batchGet returns items when the user can access them', async () => { + const fakeRes = { sendStatus: sinon.spy(), json: sinon.spy() } + const fakeReq = { + body: { libraryItemIds: [itemLib1Id] }, + user: userLimitedToLib1() + } + await LibraryItemController.batchGet.bind(apiRouter)(fakeReq, fakeRes) + expect(fakeRes.json.calledOnce).to.be.true + const payload = fakeRes.json.firstCall.args[0] + expect(payload.libraryItems).to.have.length(1) + expect(payload.libraryItems[0].id).to.equal(itemLib1Id) + }) + + it('batchUpdate returns 403 for a library item the user cannot access', async () => { + const fakeRes = { sendStatus: sinon.spy(), json: sinon.spy() } + const fakeReq = { + user: userLimitedToLib1(), + body: [{ id: itemLib2Id, mediaPayload: {} }] + } + await LibraryItemController.batchUpdate.bind(apiRouter)(fakeReq, fakeRes) + expect(fakeRes.sendStatus.calledWith(403)).to.be.true + }) + + it('batchUpdate returns 403 when the user lacks canUpdate', async () => { + const u = userLimitedToLib1() + u.canUpdate = false + const fakeRes = { sendStatus: sinon.spy(), json: sinon.spy() } + const fakeReq = { + user: u, + body: [{ id: itemLib1Id, mediaPayload: {} }] + } + await LibraryItemController.batchUpdate.bind(apiRouter)(fakeReq, fakeRes) + expect(fakeRes.sendStatus.calledWith(403)).to.be.true + }) + + it('batchDelete returns 403 for a library item the user cannot access', async () => { + const fakeRes = { sendStatus: sinon.spy() } + const fakeReq = { + query: {}, + user: userLimitedToLib1(), + body: { libraryItemIds: [itemLib2Id] } + } + await LibraryItemController.batchDelete.bind(apiRouter)(fakeReq, fakeRes) + expect(fakeRes.sendStatus.calledWith(403)).to.be.true + }) + }) }) From 79cc9765cf343e30515b6d4609cb741aad4a017d Mon Sep 17 00:00:00 2001 From: advplyr Date: Wed, 22 Apr 2026 16:29:47 -0500 Subject: [PATCH 110/124] Update collection endpoints to check user library access --- server/controllers/CollectionController.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/server/controllers/CollectionController.js b/server/controllers/CollectionController.js index 1476b0f81..bb00ea346 100644 --- a/server/controllers/CollectionController.js +++ b/server/controllers/CollectionController.js @@ -41,6 +41,10 @@ class CollectionController { if (reqBody.description && typeof reqBody.description !== 'string') { return res.status(400).send('Invalid collection description') } + if (!req.user.checkCanAccessLibrary(reqBody.libraryId)) { + Logger.warn(`[CollectionController] User "${req.user.username}" attempted to create collection in inaccessible library ${reqBody.libraryId}`) + return res.sendStatus(403) + } const libraryItemIds = (reqBody.books || []).filter((b) => !!b && typeof b == 'string') if (!libraryItemIds.length) { return res.status(400).send('Invalid collection data. No books') @@ -109,8 +113,9 @@ class CollectionController { */ async findAll(req, res) { const collectionsExpanded = await Database.collectionModel.getOldCollectionsJsonExpanded(req.user) + const accessibleCollections = collectionsExpanded.filter((c) => req.user.checkCanAccessLibrary(c.libraryId)) res.json({ - collections: collectionsExpanded + collections: accessibleCollections }) } @@ -431,6 +436,10 @@ class CollectionController { if (!collection) { return res.status(404).send('Collection not found') } + if (!req.user.checkCanAccessLibrary(collection.libraryId)) { + Logger.warn(`[CollectionController] User "${req.user.username}" attempted to access collection ${collection.id} in inaccessible library ${collection.libraryId}`) + return res.status(404).send('Collection not found') + } req.collection = collection } From 9ab35ef418090fa5b8f9b36f366dbea1c078a660 Mon Sep 17 00:00:00 2001 From: advplyr Date: Wed, 22 Apr 2026 16:42:58 -0500 Subject: [PATCH 111/124] Update playlist endpoints to check user still has library access --- server/controllers/PlaylistController.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/server/controllers/PlaylistController.js b/server/controllers/PlaylistController.js index bc1a7a455..6ad7cff9e 100644 --- a/server/controllers/PlaylistController.js +++ b/server/controllers/PlaylistController.js @@ -37,6 +37,10 @@ class PlaylistController { if (reqBody.description && typeof reqBody.description !== 'string') { return res.status(400).send('Invalid playlist description') } + if (!req.user.checkCanAccessLibrary(reqBody.libraryId)) { + Logger.warn(`[PlaylistController] User "${req.user.username}" attempted to create playlist in inaccessible library ${reqBody.libraryId}`) + return res.sendStatus(403) + } const items = reqBody.items || [] const isPodcast = items.some((i) => i.episodeId) const libraryItemIds = new Set() @@ -133,8 +137,9 @@ class PlaylistController { */ async findAllForUser(req, res) { const playlistsForUser = await Database.playlistModel.getOldPlaylistsForUserAndLibrary(req.user.id) + const accessiblePlaylists = playlistsForUser.filter((p) => req.user.checkCanAccessLibrary(p.libraryId)) res.json({ - playlists: playlistsForUser + playlists: accessiblePlaylists }) } @@ -508,6 +513,10 @@ class PlaylistController { if (!collection) { return res.status(404).send('Collection not found') } + if (!req.user.checkCanAccessLibrary(collection.libraryId)) { + Logger.warn(`[PlaylistController] User "${req.user.username}" attempted to create playlist from collection ${collection.id} in inaccessible library ${collection.libraryId}`) + return res.status(404).send('Collection not found') + } // Expand collection to get library items const collectionExpanded = await collection.getOldJsonExpanded(req.user) if (!collectionExpanded) { @@ -573,6 +582,10 @@ class PlaylistController { Logger.warn(`[PlaylistController] Playlist ${req.params.id} requested by user ${req.user.id} that is not the owner`) return res.sendStatus(403) } + if (!req.user.checkCanAccessLibrary(playlist.libraryId)) { + Logger.warn(`[PlaylistController] User "${req.user.username}" attempted to access playlist ${playlist.id} in inaccessible library ${playlist.libraryId}`) + return res.status(404).send('Playlist not found') + } req.playlist = playlist } From a5362de9cc0a6d69c96679bce94eb831d381ebba Mon Sep 17 00:00:00 2001 From: advplyr Date: Thu, 23 Apr 2026 14:34:59 -0500 Subject: [PATCH 112/124] Update podcast createFromRequest to sanitize html description --- server/models/Podcast.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server/models/Podcast.js b/server/models/Podcast.js index a96e1dd02..02f8981c8 100644 --- a/server/models/Podcast.js +++ b/server/models/Podcast.js @@ -89,6 +89,9 @@ class Podcast extends Model { } }) + const rawDescription = typeof payload.metadata.description === 'string' ? payload.metadata.description : null + const description = rawDescription ? htmlSanitizer.sanitize(rawDescription) : null + return this.create( { title, @@ -97,7 +100,7 @@ class Podcast extends Model { releaseDate: typeof payload.metadata.releaseDate === 'string' ? payload.metadata.releaseDate : null, feedURL: typeof payload.metadata.feedUrl === 'string' ? payload.metadata.feedUrl : null, imageURL: typeof payload.metadata.imageUrl === 'string' ? payload.metadata.imageUrl : null, - description: typeof payload.metadata.description === 'string' ? payload.metadata.description : null, + description, itunesPageURL: typeof payload.metadata.itunesPageUrl === 'string' ? payload.metadata.itunesPageUrl : null, itunesId: typeof payload.metadata.itunesId === 'string' ? payload.metadata.itunesId : null, itunesArtistId: typeof payload.metadata.itunesArtistId === 'string' ? payload.metadata.itunesArtistId : null, From d6a2e5596b70702f176f8fa0da2546993f049d13 Mon Sep 17 00:00:00 2001 From: advplyr Date: Fri, 24 Apr 2026 16:18:56 -0500 Subject: [PATCH 113/124] Fix undefined variable in error log for when podcast cron is invalid --- server/managers/CronManager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/managers/CronManager.js b/server/managers/CronManager.js index d3e652129..b000413c5 100644 --- a/server/managers/CronManager.js +++ b/server/managers/CronManager.js @@ -167,7 +167,7 @@ class CronManager { task }) } catch (error) { - Logger.error(`[PodcastManager] Failed to schedule podcast cron ${this.serverSettings.podcastEpisodeSchedule}`, error) + Logger.error(`[PodcastManager] Failed to schedule podcast cron ${expression}`, error) } } From 7c0ca44727cfa789a2e7147ae9424a95536f447c Mon Sep 17 00:00:00 2001 From: advplyr Date: Fri, 24 Apr 2026 16:55:42 -0500 Subject: [PATCH 114/124] Update podcast create/update endpoints to validate autoDownloadSchedule cron expression, validate cron expression before starting in CronManager --- client/components/modals/item/tabs/Schedule.vue | 2 ++ server/controllers/LibraryItemController.js | 11 +++++++++++ server/controllers/MiscController.js | 12 +++++------- server/controllers/PodcastController.js | 6 ++++++ server/managers/CronManager.js | 5 +++++ server/models/Podcast.js | 2 ++ 6 files changed, 31 insertions(+), 7 deletions(-) diff --git a/client/components/modals/item/tabs/Schedule.vue b/client/components/modals/item/tabs/Schedule.vue index 2cd3af514..0faa29630 100644 --- a/client/components/modals/item/tabs/Schedule.vue +++ b/client/components/modals/item/tabs/Schedule.vue @@ -158,6 +158,8 @@ export default { this.isProcessing = true var updateResult = await this.$axios.$patch(`/api/items/${this.libraryItemId}/media`, updatePayload).catch((error) => { console.error('Failed to update', error) + const errorMessage = typeof error?.response?.data === 'string' ? error?.response?.data : null + this.$toast.error(errorMessage || this.$strings.ToastFailedToUpdate) return false }) this.isProcessing = false diff --git a/server/controllers/LibraryItemController.js b/server/controllers/LibraryItemController.js index e524ebcfd..07b4ee67f 100644 --- a/server/controllers/LibraryItemController.js +++ b/server/controllers/LibraryItemController.js @@ -1,6 +1,7 @@ const { Request, Response, NextFunction } = require('express') const Path = require('path') const fs = require('../libs/fsExtra') +const cron = require('../libs/nodeCron') const uaParserJs = require('../libs/uaParser') const Logger = require('../Logger') const SocketAuthority = require('../SocketAuthority') @@ -220,6 +221,11 @@ class LibraryItemController { } else if (mediaPayload.autoDownloadSchedule !== undefined && req.libraryItem.media.autoDownloadSchedule !== mediaPayload.autoDownloadSchedule) { isPodcastAutoDownloadUpdated = true } + + if (mediaPayload.autoDownloadSchedule && !cron.validate(mediaPayload.autoDownloadSchedule)) { + Logger.error(`[LibraryItemController] Invalid auto download schedule cron expression "${mediaPayload.autoDownloadSchedule}" for library item "${req.libraryItem.media.title}"`) + return res.status(400).send('Invalid auto download schedule cron expression') + } } let hasUpdates = (await req.libraryItem.media.updateFromRequest(mediaPayload)) || mediaPayload.url @@ -659,6 +665,11 @@ class LibraryItemController { const mediaPayload = updatePayload.mediaPayload const libraryItem = libraryItems.find((li) => li.id === updatePayload.id) + if (libraryItem.isPodcast && mediaPayload.autoDownloadSchedule && !cron.validate(mediaPayload.autoDownloadSchedule)) { + Logger.warn(`[LibraryItemController] Invalid auto download schedule cron expression "${mediaPayload.autoDownloadSchedule}" for library item "${libraryItem.media.title}" - skipping update`) + continue + } + let hasUpdates = await libraryItem.media.updateFromRequest(mediaPayload) if (libraryItem.isBook && Array.isArray(mediaPayload.metadata?.series)) { diff --git a/server/controllers/MiscController.js b/server/controllers/MiscController.js index 490cb27d2..591f8ccf2 100644 --- a/server/controllers/MiscController.js +++ b/server/controllers/MiscController.js @@ -8,7 +8,7 @@ const Database = require('../Database') const Watcher = require('../Watcher') const libraryItemFilters = require('../utils/queries/libraryItemFilters') -const patternValidation = require('../libs/nodeCron/pattern-validation') +const cron = require('../libs/nodeCron') const { isObject, getTitleIgnorePrefix } = require('../utils/index') const { sanitizeFilename } = require('../utils/fileUtils') @@ -605,13 +605,11 @@ class MiscController { return res.sendStatus(400) } - try { - patternValidation(expression) - res.sendStatus(200) - } catch (error) { - Logger.warn(`[MiscController] Invalid cron expression ${expression}`, error.message) - res.status(400).send(error.message) + if (!cron.validate(expression)) { + Logger.warn(`[MiscController] Invalid cron expression ${expression}`) + return res.status(400).send('Invalid cron expression') } + res.sendStatus(200) } /** diff --git a/server/controllers/PodcastController.js b/server/controllers/PodcastController.js index f099d05ed..85173e622 100644 --- a/server/controllers/PodcastController.js +++ b/server/controllers/PodcastController.js @@ -5,6 +5,7 @@ const SocketAuthority = require('../SocketAuthority') const Database = require('../Database') const fs = require('../libs/fsExtra') +const cron = require('../libs/nodeCron') const { getPodcastFeed, findMatchingEpisodes } = require('../utils/podcastUtils') const { getFileTimestampsWithIno, filePathToPOSIX, isSameOrSubPath } = require('../utils/fileUtils') @@ -46,6 +47,11 @@ class PodcastController { return res.status(400).send('Invalid request body. "media" and "media.metadata" are required') } + if (payload.media.autoDownloadSchedule && !cron.validate(payload.media.autoDownloadSchedule)) { + Logger.error(`[PodcastController] Invalid auto download schedule cron expression "${payload.media.autoDownloadSchedule}"`) + return res.status(400).send('Invalid auto download schedule cron expression') + } + const library = await Database.libraryModel.findByIdWithFolders(payload.libraryId) if (!library) { Logger.error(`[PodcastController] Create: Library not found "${payload.libraryId}"`) diff --git a/server/managers/CronManager.js b/server/managers/CronManager.js index b000413c5..7138d26a8 100644 --- a/server/managers/CronManager.js +++ b/server/managers/CronManager.js @@ -153,6 +153,11 @@ class CronManager { startPodcastCron(expression, libraryItemIds) { try { + if (!cron.validate(expression)) { + Logger.error(`[CronManager] Invalid auto download schedule cron expression "${expression}" - not starting podcast episode check cron`) + return + } + Logger.debug(`[CronManager] Scheduling podcast episode check cron "${expression}" for ${libraryItemIds.length} item(s)`) const task = cron.schedule(expression, () => { if (this.podcastCronExpressionsExecuting.includes(expression)) { diff --git a/server/models/Podcast.js b/server/models/Podcast.js index 02f8981c8..6dbe1cd19 100644 --- a/server/models/Podcast.js +++ b/server/models/Podcast.js @@ -78,6 +78,7 @@ class Podcast extends Model { */ static async createFromRequest(payload, transaction) { const title = typeof payload.metadata.title === 'string' ? payload.metadata.title : null + // cron expression validated in controller const autoDownloadSchedule = typeof payload.autoDownloadSchedule === 'string' ? payload.autoDownloadSchedule : null const genres = Array.isArray(payload.metadata.genres) && payload.metadata.genres.every((g) => typeof g === 'string' && g.length) ? payload.metadata.genres : [] const tags = Array.isArray(payload.tags) && payload.tags.every((t) => typeof t === 'string' && t.length) ? payload.tags : [] @@ -273,6 +274,7 @@ class Podcast extends Model { hasUpdates = true } if (typeof payload.autoDownloadSchedule === 'string' && payload.autoDownloadSchedule !== this.autoDownloadSchedule) { + // cron expression validated in controller this.autoDownloadSchedule = payload.autoDownloadSchedule hasUpdates = true } From f47bbc7886689ecbd8f0ed7c5be5cfa8104d0b2d Mon Sep 17 00:00:00 2001 From: na3shkw <48012204+na3shkw@users.noreply.github.com> Date: Sat, 25 Apr 2026 15:56:16 +0000 Subject: [PATCH 115/124] Add Japanese language and Japan podcast search region --- client/plugins/i18n.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/plugins/i18n.js b/client/plugins/i18n.js index 0184ca9e5..a56fbd267 100644 --- a/client/plugins/i18n.js +++ b/client/plugins/i18n.js @@ -21,6 +21,7 @@ const languageCodeMap = { he: { label: 'עברית', dateFnsLocale: 'he' }, hr: { label: 'Hrvatski', dateFnsLocale: 'hr' }, it: { label: 'Italiano', dateFnsLocale: 'it' }, + ja: { label: '日本語', dateFnsLocale: 'ja' }, lt: { label: 'Lietuvių', dateFnsLocale: 'lt' }, hu: { label: 'Magyar', dateFnsLocale: 'hu' }, ko: { label: '한국어', dateFnsLocale: 'ko' }, @@ -60,6 +61,7 @@ const podcastSearchRegionMap = { hr: { label: 'Hrvatska' }, il: { label: 'ישראל / إسرائيل' }, it: { label: 'Italia' }, + jp: { label: '日本' }, lu: { label: 'Luxembourg / Luxemburg / Lëtezebuerg' }, hu: { label: 'Magyarország' }, nl: { label: 'Nederland' }, From 3ccdcaec1a1a035bbcb1140d1057f98dfef035ae Mon Sep 17 00:00:00 2001 From: advplyr Date: Sat, 25 Apr 2026 16:46:54 -0500 Subject: [PATCH 116/124] Implement SSRF filter for podcast episode downloads --- server/utils/ffmpegHelpers.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/server/utils/ffmpegHelpers.js b/server/utils/ffmpegHelpers.js index 80832cc77..7ad2a3aee 100644 --- a/server/utils/ffmpegHelpers.js +++ b/server/utils/ffmpegHelpers.js @@ -1,4 +1,5 @@ const axios = require('axios') +const ssrfFilter = require('ssrf-req-filter') const Ffmpeg = require('../libs/fluentFfmpeg') const ffmpgegUtils = require('../libs/fluentFfmpeg/utils') const fs = require('../libs/fsExtra') @@ -97,6 +98,8 @@ async function resizeImage(filePath, outputPath, width, height) { module.exports.resizeImage = resizeImage /** + * Download podcast episode + * Uses SSRF filter to prevent internal URLs * * @param {import('../objects/PodcastEpisodeDownload')} podcastEpisodeDownload * @returns {Promise<{success: boolean, isRequestError?: boolean}>} @@ -121,7 +124,9 @@ module.exports.downloadPodcastEpisode = (podcastEpisodeDownload) => { Accept: '*/*', 'User-Agent': userAgent }, - timeout: global.PodcastDownloadTimeout + timeout: global.PodcastDownloadTimeout, + httpAgent: global.DisableSsrfRequestFilter?.(podcastEpisodeDownload.url) ? null : ssrfFilter(podcastEpisodeDownload.url), + httpsAgent: global.DisableSsrfRequestFilter?.(podcastEpisodeDownload.url) ? null : ssrfFilter(podcastEpisodeDownload.url) }) Logger.debug(`[ffmpegHelpers] Successfully connected with User-Agent: ${userAgent}`) From 928051744aa419f8ac311153419a3756997f42d2 Mon Sep 17 00:00:00 2001 From: advplyr Date: Sat, 25 Apr 2026 17:13:22 -0500 Subject: [PATCH 117/124] ShareController check ?t param is less than duration, revert frontend mounted usage of param --- client/pages/share/_slug.vue | 5 ++--- server/controllers/ShareController.js | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/client/pages/share/_slug.vue b/client/pages/share/_slug.vue index 15748b48f..fdcbbe500 100644 --- a/client/pages/share/_slug.vue +++ b/client/pages/share/_slug.vue @@ -363,9 +363,8 @@ export default { console.log('Loaded media item share', this.mediaItemShare) } - const startTime = this.$route.query.t && !isNaN(this.$route.query.t) - ? parseFloat(this.$route.query.t) - : (this.playbackSession.currentTime || 0) + const startTime = this.playbackSession.currentTime || 0 + this.localAudioPlayer.set(null, this.audioTracks, false, startTime, false) this.localAudioPlayer.on('stateChange', this.playerStateChange.bind(this)) this.localAudioPlayer.on('timeupdate', this.playerTimeUpdate.bind(this)) diff --git a/server/controllers/ShareController.js b/server/controllers/ShareController.js index f7dd36f83..73da84a8a 100644 --- a/server/controllers/ShareController.js +++ b/server/controllers/ShareController.js @@ -20,7 +20,7 @@ const ShareManager = require('../managers/ShareManager') */ class ShareController { - constructor() { } + constructor() {} /** * Public route @@ -54,7 +54,7 @@ class ShareController { if (mediaItemShare.id === playbackSession.mediaItemShareId) { Logger.debug(`[ShareController] Found share playback session ${req.cookies.share_session_id}`) // If ?t was provided, override the cached currentTime - if (startTime > 0) { + if (startTime > 0 && startTime < playbackSession.duration) { playbackSession.currentTime = startTime } mediaItemShare.playbackSession = playbackSession.toJSONForClient() From 92df92ec99fd8b53006ba24d53e887b044494aff Mon Sep 17 00:00:00 2001 From: advplyr Date: Sun, 26 Apr 2026 16:51:08 -0500 Subject: [PATCH 118/124] Fix recent episodes endpoint cache not being cleared when updating media progress #5159 --- server/managers/ApiCacheManager.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/server/managers/ApiCacheManager.js b/server/managers/ApiCacheManager.js index 8a67e4898..e34e4c055 100644 --- a/server/managers/ApiCacheManager.js +++ b/server/managers/ApiCacheManager.js @@ -42,11 +42,14 @@ class ApiCacheManager { } clearUserProgressSlices(modelName, hook) { - const removedPersonalized = this.modelsInvalidatingPersonalized.has(modelName) ? this.clearByUrlPattern(/^\/libraries\/[^/]+\/personalized/) : 0 + let removedPersonalized = 0 + let removedRecentEpisodes = 0 + if (this.modelsInvalidatingPersonalized.has(modelName)) { + removedPersonalized = this.clearByUrlPattern(/^\/libraries\/[^/]+\/personalized/) + removedRecentEpisodes = this.clearByUrlPattern(/^\/libraries\/[^/]+\/recent-episodes/) + } const removedMe = this.modelsInvalidatingMe.has(modelName) ? this.clearByUrlPattern(/^\/me(\/|\?|$)/) : 0 - Logger.debug( - `[ApiCacheManager] ${modelName}.${hook}: cleared user-progress cache slices (personalized=${removedPersonalized}, me=${removedMe})` - ) + Logger.debug(`[ApiCacheManager] ${modelName}.${hook}: cleared user-progress cache slices (personalized=${removedPersonalized}, recentEpisodes=${removedRecentEpisodes}, me=${removedMe})`) } clear(model, hook) { From 62d7097e23dbabb6da3def612622ed4b6a3123af Mon Sep 17 00:00:00 2001 From: advplyr Date: Sun, 26 Apr 2026 16:51:39 -0500 Subject: [PATCH 119/124] Add ApiCacheManager test for should remove recent-episodes cache entries --- test/server/managers/ApiCacheManager.test.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/server/managers/ApiCacheManager.test.js b/test/server/managers/ApiCacheManager.test.js index 19bbeecf6..2e9ad5b77 100644 --- a/test/server/managers/ApiCacheManager.test.js +++ b/test/server/managers/ApiCacheManager.test.js @@ -1,6 +1,7 @@ // Import dependencies and modules for testing const { expect } = require('chai') const sinon = require('sinon') +const { LRUCache } = require('lru-cache') const ApiCacheManager = require('../../../server/managers/ApiCacheManager') describe('ApiCacheManager', () => { @@ -94,4 +95,17 @@ describe('ApiCacheManager', () => { expect(res.originalSend.calledWith(body)).to.be.true }) }) + + describe('clear on mediaProgress', () => { + it('should remove recent-episodes cache entries', () => { + const key = JSON.stringify({ user: 'u', url: '/libraries/abc-123/recent-episodes?limit=50&page=0' }) + const cache = new LRUCache({ max: 10 }) + cache.set(key, { body: '[]', headers: {}, statusCode: 200 }) + const manager = new ApiCacheManager(cache) + + manager.clear({ name: 'mediaProgress' }, 'afterUpdate') + + expect(cache.get(key)).to.be.undefined + }) + }) }) From f8ef56c6bc3cfd8d45e72a0c3690a35273623d14 Mon Sep 17 00:00:00 2001 From: ugyes Date: Mon, 20 Apr 2026 18:22:36 +0200 Subject: [PATCH 120/124] Translated using Weblate (Hungarian) Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hu/ --- client/strings/hu.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/strings/hu.json b/client/strings/hu.json index 90f58343a..4c36af869 100644 --- a/client/strings/hu.json +++ b/client/strings/hu.json @@ -16,7 +16,7 @@ "ButtonBrowseForFolder": "Mappa keresése", "ButtonCancel": "Mégse", "ButtonCancelEncode": "Kódolás megszakítása", - "ButtonChangeRootPassword": "Gyökérjelszó megváltoztatása", + "ButtonChangeRootPassword": "Root jelszó megváltoztatása", "ButtonCheckAndDownloadNewEpisodes": "Új epizódok ellenőrzése és letöltése", "ButtonChooseAFolder": "Válassz egy mappát", "ButtonChooseFiles": "Fájlok kiválasztása", From e79256d0fb00087a423a0e6f154fa8db867f88ea Mon Sep 17 00:00:00 2001 From: Pavel Miniutka Date: Wed, 22 Apr 2026 22:27:58 +0200 Subject: [PATCH 121/124] Translated using Weblate (Belarusian) Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/be/ --- client/strings/be.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/client/strings/be.json b/client/strings/be.json index 43668acb5..b4e4df86a 100644 --- a/client/strings/be.json +++ b/client/strings/be.json @@ -1,6 +1,6 @@ { "ButtonAdd": "Дадаць", - "ButtonAddApiKey": "Дадаць API-ключ", + "ButtonAddApiKey": "Дадаць ключ API", "ButtonAddChapters": "Дадаць раздзелы", "ButtonAddDevice": "Дадаць прыладу", "ButtonAddLibrary": "Дадаць бібліятэку", @@ -121,7 +121,7 @@ "HeaderAccount": "Уліковы запіс", "HeaderAddCustomMetadataProvider": "Дадаванне карыстальніцкага пастаўшчыка метаданых", "HeaderAdvanced": "Дадаткова", - "HeaderApiKeys": "API-ключы", + "HeaderApiKeys": "Ключы API", "HeaderAppriseNotificationSettings": "Налады апавяшчэнняў Apprise", "HeaderAudioTracks": "Аўдыятрэкі", "HeaderAudiobookTools": "Сродкі кіравання файламі аўдыякніг", @@ -166,7 +166,7 @@ "HeaderMetadataOrderOfPrecedence": "Парадак прыярытэту метаданых", "HeaderMetadataToEmbed": "Метаданыя для ўбудавання", "HeaderNewAccount": "Новы ўліковы запіс", - "HeaderNewApiKey": "Новы API-ключ", + "HeaderNewApiKey": "Новы ключ API", "HeaderNewLibrary": "Новая бібліятэка", "HeaderNotificationCreate": "Стварыць апавяшчэнне", "HeaderNotificationUpdate": "Абнавіць апавяшчэнне", @@ -212,7 +212,7 @@ "HeaderTableOfContents": "Змест", "HeaderTools": "Інструменты", "HeaderUpdateAccount": "Абнавіць уліковы запіс", - "HeaderUpdateApiKey": "Абнавіць API-ключ", + "HeaderUpdateApiKey": "Абнавіць ключ API", "HeaderUpdateAuthor": "Абнавіць аўтара", "HeaderUpdateDetails": "Абнавіць падрабязнасці", "HeaderUpdateLibrary": "Абнавіць бібліятэку", @@ -242,10 +242,10 @@ "LabelAllUsersExcludingGuests": "Усіх карыстальнікаў, акрамя гасцей", "LabelAllUsersIncludingGuests": "Усіх карыстальнікаў, уключаючы гасцей", "LabelAlreadyInYourLibrary": "Ужо ў вашай бібліятэцы", - "LabelApiKeyCreated": "API-ключ \"{0}\" паспяхова створаны.", - "LabelApiKeyCreatedDescription": "Пераканайцеся, што вы скапіявалі API-ключ зараз, бо паўторна яго ўбачыць не атрымаецца.", + "LabelApiKeyCreated": "Ключ API \"{0}\" паспяхова створаны.", + "LabelApiKeyCreatedDescription": "Абавязкова скапіюйце ключ API зараз, бо паўторна яго ўбачыць не атрымаецца.", "LabelApiKeyUser": "Дзейнічаць ад імя карыстальніка", - "LabelApiKeyUserDescription": "Гэты API-ключ будзе мець тыя ж правы, што і карыстальнік, ад імя якога ён дзейнічае. У журналах гэта будзе выглядаць так, быццам запыт робіць сам карыстальнік.", + "LabelApiKeyUserDescription": "Гэты ключ API будзе мець тыя ж правы, што і карыстальнік, ад імя якога ён дзейнічае. У журналах гэта будзе выглядаць так, быццам запыт робіць сам карыстальнік.", "LabelApiToken": "Токен API", "LabelAppend": "Дадаць", "LabelAudioBitrate": "Бітрэйт аўдыя (напрыклад, 128к)", @@ -884,7 +884,7 @@ "MessageRemoveEpisodes": "Выдаліць выпускі ({0})", "MessageRemoveFromPlayerQueue": "Выдаліць з чаргі прагравання", "MessageRemoveUserWarning": "Вы ўпэўнены, што хочаце назаўжды выдаліць карыстальніка \"{0}\"?", - "MessageReportBugsAndContribute": "Паведамляйце пра памылкі, прапануйце новыя функцыі і ўдзельнічайце на", + "MessageReportBugsAndContribute": "Паведамляйце пра памылкі, прапануйце функцыі і ўносьце свой уклад на", "MessageResetChaptersConfirm": "Вы ўпэўнены, што хочаце скінуць раздзелы і адрабіць зробленыя вамі змены?", "MessageRestoreBackupConfirm": "Вы ўпэўнены, што хочаце аднавіць рэзервовую копію, створаную", "MessageRestoreBackupWarning": "Аднаўленне рэзервовай копіі перазапіша ўсю базу даных, размешчаную ў /config, а таксама відарысы вокладкі ў /metadata/items і /metadata/authors.

Рэзервовыя копіі не змяняюць файлы ў папках бібліятэкі. Калі вы ўключылі налады сервера для захоўвання воклак і метаданых у папках бібліятэкі, гэтыя файлы не будуць захаваныя ў рэзервовых копіях і не зменяцца.

Усе кліенты, якія карыстаюцца вашым серверам, будуць аўтаматычна абноўлены.", From cbc103cf05956be3aff0ed5927039170d1f88dc3 Mon Sep 17 00:00:00 2001 From: Naoto Ishikawa <48012204+na3shkw@users.noreply.github.com> Date: Sat, 25 Apr 2026 16:33:49 +0200 Subject: [PATCH 122/124] Translated using Weblate (Japanese) Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ja/ --- client/strings/ja.json | 966 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 930 insertions(+), 36 deletions(-) diff --git a/client/strings/ja.json b/client/strings/ja.json index 0621614f4..95cfba2b8 100644 --- a/client/strings/ja.json +++ b/client/strings/ja.json @@ -16,7 +16,7 @@ "ButtonBrowseForFolder": "フォルダーを選択する", "ButtonCancel": "キャンセル", "ButtonCancelEncode": "エンコードを取り消す", - "ButtonChangeRootPassword": "Rootのパスワードを変更する", + "ButtonChangeRootPassword": "ルートのパスワードを変更する", "ButtonCheckAndDownloadNewEpisodes": "新しいエピソードを確認してダウンロード", "ButtonChooseAFolder": "フォルダーを選ぶ", "ButtonChooseFiles": "ファイルを選ぶ", @@ -29,7 +29,7 @@ "ButtonCreate": "作成", "ButtonCreateBackup": "バックアップを作成する", "ButtonDelete": "削除", - "ButtonDownloadQueue": "次に再生", + "ButtonDownloadQueue": "ダウンロードキュー", "ButtonEdit": "編集", "ButtonEditChapters": "チャプターの編集", "ButtonEditPodcast": "ポッドキャストの編集", @@ -52,9 +52,9 @@ "ButtonMatchAllAuthors": "すべての作者と紐付け", "ButtonMatchBooks": "本と紐付け", "ButtonNevermind": "中止", - "ButtonNext": "次", + "ButtonNext": "次へ", "ButtonNextChapter": "次のチャプター", - "ButtonNextItemInQueue": "キューの中の次のアイテム", + "ButtonNextItemInQueue": "キューにある次のアイテム", "ButtonOk": "はい", "ButtonOpenFeed": "フィードを開く", "ButtonOpenManager": "管理画面を開く", @@ -63,19 +63,19 @@ "ButtonPlayAll": "全て再生", "ButtonPlaying": "プレイ中", "ButtonPlaylists": "プレイリスト", - "ButtonPrevious": "先", + "ButtonPrevious": "前へ", "ButtonPreviousChapter": "前のチャプター", "ButtonProbeAudioFile": "オーディオファイルを解析", "ButtonPurgeAllCache": "全てのキャッシュを削除", "ButtonPurgeItemsCache": "項目のキャッシュを削除", "ButtonQueueAddItem": "次に再生する", - "ButtonQueueRemoveItem": "次に再生から削除", + "ButtonQueueRemoveItem": "「次に再生」から削除", "ButtonQuickEmbed": "クイック埋め込み", "ButtonQuickEmbedMetadata": "メタデータの埋め込み", "ButtonQuickMatch": "クイックマッチ", "ButtonReScan": "再スキャン", "ButtonRead": "読む", - "ButtonReadLess": "閉じる", + "ButtonReadLess": "少なく表示", "ButtonReadMore": "もっと見る", "ButtonRefresh": "再読み込み", "ButtonRemove": "削除", @@ -83,7 +83,7 @@ "ButtonRemoveAllLibraryItems": "ライブラリーの項目を全て削除", "ButtonRemoveFromContinueListening": "「続きを聴く」から削除", "ButtonRemoveFromContinueReading": "「続きを読む」から削除", - "ButtonRemoveSeriesFromContinueSeries": "「シリーズを続く」からシリーズを削除", + "ButtonRemoveSeriesFromContinueSeries": "「シリーズを続ける」からシリーズを削除", "ButtonReset": "元に戻す", "ButtonResetToDefault": "デフォルトに戻す", "ButtonRestore": "復元", @@ -108,15 +108,15 @@ "ButtonTest": "テスト", "ButtonUnlinkOpenId": "OpenID 連携解除", "ButtonUpload": "アップロード", - "ButtonUploadBackup": "バックアップのアップロード", + "ButtonUploadBackup": "バックアップをアップロード", "ButtonUploadCover": "カバー画像をアップロード", "ButtonUploadOPMLFile": "OPMLファイルをアップロード", "ButtonUserDelete": "ユーザーを削除 {0}", - "ButtonUserEdit": "ユーザを編集 {0}", + "ButtonUserEdit": "ユーザーを編集 {0}", "ButtonViewAll": "すべて表示", "ButtonYes": "はい", "ErrorUploadFetchMetadataAPI": "メタデータの取得中にエラーが発生しました", - "ErrorUploadFetchMetadataNoResults": "メタデータ取得に失敗しました。タイトルや著者名を更新してください", + "ErrorUploadFetchMetadataNoResults": "メタデータの取得に失敗しました。タイトルや作者名を更新してください", "ErrorUploadLacksTitle": "タイトルは必須です", "HeaderAccount": "アカウント", "HeaderAddCustomMetadataProvider": "カスタムメタデータプロバイダーを追加", @@ -153,8 +153,8 @@ "HeaderLastListeningSession": "直近の再生セッション", "HeaderLatestEpisodes": "最新のエピソード", "HeaderLibraries": "ライブラリー", - "HeaderLibraryFiles": "ライブラリファイル", - "HeaderLibraryStats": "ライブラリ統計", + "HeaderLibraryFiles": "ライブラリーファイル", + "HeaderLibraryStats": "ライブラリー統計", "HeaderListeningSessions": "再生セッション", "HeaderListeningStats": "再生統計", "HeaderLogin": "ログイン", @@ -172,100 +172,994 @@ "HeaderNotificationUpdate": "通知を更新", "HeaderNotifications": "通知", "HeaderOpenIDConnectAuthentication": "OpenID Connect 認証", + "HeaderOpenListeningSessions": "再生中のセッション", "HeaderOpenRSSFeed": "RSS Feedを開く", + "HeaderOtherFiles": "その他のファイル", + "HeaderPasswordAuthentication": "パスワード認証", + "HeaderPermissions": "権限", + "HeaderPlayerQueue": "次に再生", "HeaderPlayerSettings": "プレーヤーの設定", "HeaderPlaylist": "プレイリスト", "HeaderPlaylistItems": "プレイリストアイテム", + "HeaderPodcastsToAdd": "追加するポッドキャスト", + "HeaderPresets": "プリセット", + "HeaderPreviewCover": "カバープレビュー", "HeaderRSSFeedGeneral": "RSS 詳細", "HeaderRSSFeedIsOpen": "RSSフィードが開いています", + "HeaderRSSFeeds": "RSSフィード", + "HeaderRemoveEpisode": "エピソードを削除", + "HeaderRemoveEpisodes": "{0}個のエピソードを削除", + "HeaderSavedMediaProgress": "保存済みメディア進捗", + "HeaderSchedule": "スケジュール", + "HeaderScheduleEpisodeDownloads": "エピソード自動ダウンロードスケジュール", + "HeaderScheduleLibraryScans": "ライブラリー自動スキャンスケジュール", + "HeaderSession": "セッション", + "HeaderSetBackupSchedule": "バックアップスケジュールの設定", "HeaderSettings": "設定", + "HeaderSettingsDisplay": "表示", + "HeaderSettingsExperimental": "実験的な機能", "HeaderSettingsGeneral": "一般", "HeaderSettingsScanner": "スキャナー", + "HeaderSettingsSecurity": "セキュリティ", + "HeaderSettingsWebClient": "Webクライアント", "HeaderSleepTimer": "スリープタイマー", - "HeaderStatsMinutesListeningChart": "過去7日間の視聴時間(分)", + "HeaderStatsLargestItems": "最大のアイテム", + "HeaderStatsLongestItems": "最長のアイテム(時間)", + "HeaderStatsMinutesListeningChart": "過去7日間のリスニング時間(分)", "HeaderStatsRecentSessions": "最近の再生履歴", + "HeaderStatsTop10Authors": "上位10作者", + "HeaderStatsTop5Genres": "上位5ジャンル", "HeaderTableOfContents": "目次", + "HeaderTools": "ツール", + "HeaderUpdateAccount": "アカウントの更新", + "HeaderUpdateApiKey": "APIキーの更新", + "HeaderUpdateAuthor": "作者の更新", + "HeaderUpdateDetails": "詳細の更新", + "HeaderUpdateLibrary": "ライブラリーの更新", + "HeaderUsers": "ユーザー", + "HeaderYearReview": "{0}年の振り返り", "HeaderYourStats": "再生統計", + "LabelAbridged": "要約版", + "LabelAbridgedChecked": "要約版(チェック済み)", + "LabelAbridgedUnchecked": "完全版(未チェック)", + "LabelAccessibleBy": "アクセス可能なユーザー", + "LabelAccountType": "アカウントの種類", + "LabelAccountTypeAdmin": "管理者", + "LabelAccountTypeGuest": "ゲスト", + "LabelAccountTypeUser": "ユーザー", + "LabelActivities": "アクティビティー", + "LabelActivity": "アクティビティー", + "LabelAddToCollection": "コレクションに追加", + "LabelAddToCollectionBatch": "コレクションに{0}冊追加", "LabelAddToPlaylist": "プレイリストの追加", + "LabelAddToPlaylistBatch": "プレイリストに{0}件追加", "LabelAddedAt": "追加日時", - "LabelAddedDate": "追加日時 ­­{0}", + "LabelAddedDate": "追加日時 {0}", + "LabelAdminUsersOnly": "管理者ユーザーのみ", "LabelAll": "すべて", - "LabelAuthor": "著者", - "LabelAuthorFirstLast": "著者(名 氏)", - "LabelAuthorLastFirst": "著者(氏 名)", - "LabelAuthors": "著者", + "LabelAllEpisodesDownloaded": "すべてのエピソードをダウンロード済み", + "LabelAllUsers": "すべてのユーザー", + "LabelAllUsersExcludingGuests": "ゲストを除くすべてのユーザー", + "LabelAllUsersIncludingGuests": "ゲストを含むすべてのユーザー", + "LabelAlreadyInYourLibrary": "すでにライブラリーにあります", + "LabelApiKeyCreated": "APIキー \"{0}\" が正常に作成されました。", + "LabelApiKeyCreatedDescription": "このAPIキーを今すぐコピーしてください。二度と確認できません。", + "LabelApiKeyUser": "ユーザーの代わりに動作", + "LabelApiKeyUserDescription": "このAPIキーは代わりに動作するユーザーと同じ権限を持ちます。ログではユーザーがリクエストを行っているのと同じように表示されます。", + "LabelApiToken": "APIトークン", + "LabelAppend": "追記", + "LabelAudioBitrate": "オーディオビットレート(例: 128k)", + "LabelAudioChannels": "オーディオチャンネル(1または2)", + "LabelAudioCodec": "オーディオコーデック", + "LabelAuthor": "作者", + "LabelAuthorFirstLast": "作者(名 氏)", + "LabelAuthorLastFirst": "作者(氏 名)", + "LabelAuthors": "作者", "LabelAutoDownloadEpisodes": "エピソードの自動ダウンロード", - "LabelBooks": "ほん", - "LabelByAuthor": "著 {0}", + "LabelAutoFetchMetadata": "メタデータの自動取得", + "LabelAutoFetchMetadataHelp": "タイトル・作者・シリーズのメタデータを取得してアップロードを効率化します。アップロード後に追加のメタデータのマッチングが必要になることがあります。", + "LabelAutoLaunch": "自動起動", + "LabelAutoLaunchDescription": "ログインページにアクセスすると自動的に認証プロバイダーにリダイレクトします(手動上書きパス /login?autoLaunch=0)", + "LabelAutoRegister": "自動登録", + "LabelAutoRegisterDescription": "ログイン後に新しいユーザーを自動的に作成", + "LabelBackToUser": "ユーザーに戻る", + "LabelBackupAudioFiles": "オーディオファイルをバックアップ", + "LabelBackupLocation": "バックアップ場所", + "LabelBackupsEnableAutomaticBackups": "自動バックアップ", + "LabelBackupsEnableAutomaticBackupsHelp": "/metadata/backups に保存されたバックアップ", + "LabelBackupsMaxBackupSize": "最大バックアップサイズ(GB)(無制限は0)", + "LabelBackupsMaxBackupSizeHelp": "設定ミスを防ぐため、バックアップが設定サイズを超えると失敗します。", + "LabelBackupsNumberToKeep": "保持するバックアップ数", + "LabelBackupsNumberToKeepHelp": "一度に1つのバックアップのみ削除されます。すでにこれより多くのバックアップがある場合は手動で削除してください。", + "LabelBitrate": "ビットレート", + "LabelBonus": "ボーナス", + "LabelBooks": "本", + "LabelButtonText": "ボタンテキスト", + "LabelByAuthor": "by {0}", + "LabelChangePassword": "パスワードを変更", + "LabelChannels": "チャンネル", + "LabelChapterCount": "{0}チャプター", + "LabelChapterTitle": "チャプタータイトル", "LabelChapters": "チャプター", - "LabelClosePlayer": "プレイヤーを閉じる", + "LabelChaptersFound": "チャプターが見つかりました", + "LabelClickForMoreInfo": "クリックして詳細を表示", + "LabelClickToUseCurrentValue": "クリックして現在の値を使用", + "LabelClosePlayer": "プレーヤーを閉じる", + "LabelCodec": "コーデック", "LabelCollapseSeries": "シリーズを折りたたむ", + "LabelCollapseSubSeries": "サブシリーズを折りたたむ", + "LabelCollection": "コレクション", + "LabelCollections": "コレクション", "LabelComplete": "完了", - "LabelContinueListening": "続きから聞く", + "LabelConfirmPassword": "パスワード確認", + "LabelContinueListening": "続きから聴く", "LabelContinueReading": "続きを読む", - "LabelContinueSeries": "シリーズを続く", + "LabelContinueSeries": "シリーズを続ける", + "LabelCorsAllowed": "許可されたCORSオリジン", + "LabelCover": "カバー", + "LabelCoverImageURL": "カバー画像URL", + "LabelCoverProvider": "カバープロバイダー", + "LabelCreatedAt": "作成日時", + "LabelCronExpression": "Cron式", + "LabelCurrent": "現在", + "LabelCurrently": "現在:", + "LabelCustomCronExpression": "カスタムCron式:", + "LabelDatetime": "日時", + "LabelDays": "日", + "LabelDeleteFromFileSystemCheckbox": "ファイルシステムから削除(チェック解除するとデータベースからのみ削除)", "LabelDescription": "説明", + "LabelDeselectAll": "すべて解除", + "LabelDetectedPattern": "検出されたパターン:", + "LabelDevice": "デバイス", + "LabelDeviceInfo": "デバイス情報", + "LabelDeviceIsAvailableTo": "デバイスが使用可能な対象...", + "LabelDirectory": "ディレクトリ", + "LabelDiscFromFilename": "ファイル名からディスク", + "LabelDiscFromMetadata": "メタデータからディスク", "LabelDiscover": "おすすめ", "LabelDownload": "ダウンロード", + "LabelDownloadNEpisodes": "{0}エピソードをダウンロード", + "LabelDownloadable": "ダウンロード可能", "LabelDuration": "長さ", - "LabelEbook": "Eブック", - "LabelEbooks": "Eブック", + "LabelDurationComparisonExactMatch": "(完全一致)", + "LabelDurationComparisonLonger": "({0} 長い)", + "LabelDurationComparisonShorter": "({0} 短い)", + "LabelDurationFound": "見つかった長さ:", + "LabelEbook": "電子書籍", + "LabelEbooks": "電子書籍", + "LabelEdit": "編集", + "LabelEmail": "メール", + "LabelEmailSettingsFromAddress": "送信元アドレス", + "LabelEmailSettingsRejectUnauthorized": "未認証の証明書を拒否", + "LabelEmailSettingsRejectUnauthorizedHelp": "SSL証明書の検証を無効にすると、中間者攻撃などのセキュリティリスクにさらされる可能性があります。接続するメールサーバーを信頼している場合にのみ無効にしてください。", + "LabelEmailSettingsSecure": "セキュア", + "LabelEmailSettingsSecureHelp": "trueの場合、サーバーへの接続にTLSを使用します。falseの場合、サーバーがSTARTTLS拡張をサポートしていればTLSが使用されます。ポート465に接続する場合はtrueに設定してください。ポート587または25の場合はfalseのままにしてください。(nodemailer.com/smtp/#authentication より)", + "LabelEmailSettingsTestAddress": "テストアドレス", + "LabelEmbeddedCover": "埋め込みカバー", "LabelEnable": "有効", + "LabelEncodingBackupLocation": "元のオーディオファイルのバックアップは以下に保存されます:", + "LabelEncodingChaptersNotEmbedded": "マルチトラックのオーディオブックにはチャプターが埋め込まれません。", + "LabelEncodingClearItemCache": "定期的にアイテムキャッシュを削除してください。", + "LabelEncodingFinishedM4B": "完成したM4Bはオーディオブックフォルダーに配置されます:", + "LabelEncodingInfoEmbedded": "メタデータはオーディオブックフォルダー内のオーディオトラックに埋め込まれます。", + "LabelEncodingStartedNavigation": "タスクが開始されたら、このページから離れることができます。", + "LabelEncodingTimeWarning": "エンコードには最大30分かかる場合があります。", + "LabelEncodingWarningAdvancedSettings": "警告: ffmpegエンコードオプションに詳しくない場合はこれらの設定を変更しないでください。", + "LabelEncodingWatcherDisabled": "ウォッチャーを無効にしている場合は、後でこのオーディオブックを再スキャンする必要があります。", "LabelEnd": "終了", "LabelEndOfChapter": "チャプターの最後", "LabelEpisode": "エピソード", + "LabelEpisodeNotLinkedToRssFeed": "RSSフィードにリンクされていないエピソード", + "LabelEpisodeNumber": "エピソード#{0}", + "LabelEpisodeTitle": "エピソードタイトル", + "LabelEpisodeType": "エピソードタイプ", + "LabelEpisodeUrlFromRssFeed": "RSSフィードのエピソードURL", "LabelEpisodes": "エピソード", "LabelEpisodic": "エピソード", + "LabelExample": "例", + "LabelExpandSeries": "シリーズを展開", + "LabelExpandSubSeries": "サブシリーズを展開", + "LabelExpired": "期限切れ", + "LabelExpiresAt": "有効期限", + "LabelExpiresInSeconds": "有効期限(秒)", + "LabelExpiresNever": "なし", "LabelExplicit": "露骨な表現", - "LabelFeedURL": "Feed URL", + "LabelExplicitChecked": "露骨な表現あり(チェック済み)", + "LabelExplicitUnchecked": "露骨な表現なし(未チェック)", + "LabelExportOPML": "OPMLをエクスポート", + "LabelFeedURL": "フィードURL", + "LabelFetchingMetadata": "メタデータ取得中", "LabelFile": "ファイル", "LabelFileBirthtime": "ファイル作成日時", + "LabelFileBornDate": "{0}に生成", "LabelFileModified": "ファイル更新日時", + "LabelFileModifiedDate": "{0}に更新", "LabelFilename": "ファイル名", + "LabelFilterByUser": "ユーザーでフィルター", + "LabelFindEpisodes": "エピソードを検索", "LabelFinished": "完了", - "LabelFolder": "フォルダ", + "LabelFinishedDate": "{0}に完了", + "LabelFolder": "フォルダー", + "LabelFolders": "フォルダー", + "LabelFontBold": "太字", "LabelFontBoldness": "フォントの太さ", "LabelFontFamily": "フォントファミリー", + "LabelFontItalic": "斜体", "LabelFontScale": "フォントサイズ", + "LabelFontStrikethrough": "打消線", + "LabelFormat": "形式", + "LabelFull": "完全", "LabelGenre": "ジャンル", "LabelGenres": "ジャンル", - "LabelHasEbook": "eBookあり", - "LabelHasSupplementaryEbook": "付属eBookあり", + "LabelHardDeleteFile": "ファイルを完全削除", + "LabelHasEbook": "電子書籍あり", + "LabelHasSupplementaryEbook": "付属電子書籍あり", + "LabelHideSubtitles": "字幕を非表示", + "LabelHighestPriority": "最高優先度", "LabelHost": "ホスト", + "LabelHour": "時間", + "LabelHours": "時間", + "LabelIcon": "アイコン", + "LabelImageURLFromTheWeb": "ウェブからの画像URL", "LabelInProgress": "進行中", + "LabelIncludeInTracklist": "トラックリストに含める", "LabelIncomplete": "未完了", + "LabelInterval": "間隔", + "LabelIntervalCustomDailyWeekly": "カスタム(毎日/曜日指定)", + "LabelIntervalEvery12Hours": "12時間ごと", + "LabelIntervalEvery15Minutes": "15分ごと", + "LabelIntervalEvery2Hours": "2時間ごと", + "LabelIntervalEvery30Minutes": "30分ごと", + "LabelIntervalEvery6Hours": "6時間ごと", + "LabelIntervalEveryDay": "毎日", + "LabelIntervalEveryHour": "1時間ごと", + "LabelIntervalEveryMinute": "1分ごと", + "LabelInvert": "反転", + "LabelItem": "アイテム", + "LabelJumpBackwardAmount": "巻き戻し量", + "LabelJumpForwardAmount": "早送り量", "LabelLanguage": "言語", + "LabelLanguageDefaultServer": "デフォルトサーバー言語", "LabelLanguages": "言語", + "LabelLastBookAdded": "最後に追加された本", + "LabelLastBookUpdated": "最後に更新された本", + "LabelLastProgressDate": "最終進捗: {0}", + "LabelLastSeen": "最終確認", + "LabelLastTime": "終了時刻", + "LabelLastUpdate": "最終更新", "LabelLayout": "レイアウト", "LabelLayoutSinglePage": "単ページ", + "LabelLayoutSplitPage": "ページを分割", + "LabelLess": "少ない", + "LabelLibrariesAccessibleToUser": "ユーザーがアクセス可能なライブラリー", + "LabelLibrary": "ライブラリー", + "LabelLibraryFilterSublistEmpty": "{0}がない", + "LabelLibraryItem": "ライブラリーアイテム", + "LabelLibraryName": "ライブラリー名", + "LabelLibrarySortByProgress": "進捗: 最終更新", + "LabelLibrarySortByProgressFinished": "進捗: 完了", + "LabelLibrarySortByProgressStarted": "進捗: 開始済み", + "LabelLimit": "制限", "LabelLineSpacing": "行間", - "LabelListenAgain": "再度視聴", + "LabelListenAgain": "もう一度聴く", + "LabelLogLevelDebug": "デバッグ", + "LabelLogLevelInfo": "情報", + "LabelLogLevelWarn": "警告", + "LabelLookForNewEpisodesAfterDate": "この日以降の新しいエピソードを検索", + "LabelLowestPriority": "最低優先度", + "LabelMatchConfidence": "信頼度", + "LabelMatchExistingUsersBy": "既存ユーザーの照合方法", + "LabelMatchExistingUsersByDescription": "既存ユーザーの接続に使用されます。接続後はSSOプロバイダーの一意のIDでユーザーが照合されます", + "LabelMaxEpisodesToDownload": "最大ダウンロードエピソード数(無制限は0)。", + "LabelMaxEpisodesToDownloadPerCheck": "1回のチェックでダウンロードする最大の新着エピソード数", + "LabelMaxEpisodesToKeep": "保持する最大エピソード数", + "LabelMaxEpisodesToKeepHelp": "0は上限なし。新しいエピソードの自動ダウンロード後、X件以上のエピソードがある場合は最も古いエピソードを削除します。1回のダウンロードにつき1件のみ削除されます。", + "LabelMediaPlayer": "メディアプレーヤー", "LabelMediaType": "メディアの種類", + "LabelMetaTag": "メタタグ", + "LabelMetaTags": "メタタグ", + "LabelMetadataOrderOfPrecedenceDescription": "優先度の高いメタデータソースが優先度の低いメタデータソースを上書きします", + "LabelMetadataProvider": "メタデータプロバイダー", + "LabelMinute": "分", + "LabelMinutes": "分", + "LabelMissing": "消失", + "LabelMissingEbook": "電子書籍なし", + "LabelMissingSupplementaryEbook": "付属電子書籍なし", + "LabelMobileRedirectURIs": "許可されたモバイルリダイレクトURI", + "LabelMobileRedirectURIsDescription": "これはモバイルアプリの有効なリダイレクトURIのホワイトリストです。デフォルトは audiobookshelf://oauth です。削除するか、サードパーティーアプリ統合用の追加URIで補足できます。アスタリスク(*)を唯一のエントリとして使用すると、任意のURIが許可されます。", + "LabelMore": "多い", "LabelMoreInfo": "追加情報", - "LabelName": "名", + "LabelName": "名前", "LabelNarrator": "ナレーター", "LabelNarrators": "ナレーター", "LabelNew": "新しい", - "LabelNewPassword": "新しいのパスワード", - "LabelNewestAuthors": "最新の著者", + "LabelNewPassword": "新しいパスワード", + "LabelNewestAuthors": "最新の作者", "LabelNewestEpisodes": "最新エピソード", + "LabelNextBackupDate": "次回バックアップ日", + "LabelNextChapters": "次のチャプター:", + "LabelNextScheduledRun": "次のスケジュール実行", + "LabelNoApiKeys": "APIキーなし", + "LabelNoCustomMetadataProviders": "カスタムメタデータプロバイダーなし", + "LabelNoEpisodesSelected": "エピソードが選択されていません", + "LabelNotFinished": "未完了", + "LabelNotStarted": "未開始", + "LabelNotes": "ノート", + "LabelNotificationAppriseURL": "Apprise URL", + "LabelNotificationAvailableVariables": "利用可能な変数", + "LabelNotificationBodyTemplate": "本文テンプレート", + "LabelNotificationEvent": "通知イベント", + "LabelNotificationTitleTemplate": "タイトルテンプレート", + "LabelNotificationsMaxFailedAttempts": "最大失敗試行回数", + "LabelNotificationsMaxFailedAttemptsHelp": "この回数失敗すると通知は無効になります", + "LabelNotificationsMaxQueueSize": "通知イベントの最大キューサイズ", + "LabelNotificationsMaxQueueSizeHelp": "イベントは1秒に1回の発火に制限されます。キューが最大サイズの場合、イベントは無視されます。これは通知スパムを防ぎます。", + "LabelNumberOfBooks": "本の冊数", + "LabelNumberOfChapters": "チャプター数:", + "LabelNumberOfEpisodes": "エピソード数", + "LabelOpenIDAdvancedPermsClaimDescription": "管理者以外のロールに適用されるアプリケーション内のユーザーアクション用の高度な権限を含むOpenIDクレーム名(設定されている場合)。クレームがレスポンスに含まれていない場合、ABSへのアクセスは拒否されます。単一のオプションがない場合は false として扱われます。IDプロバイダーのクレームが期待される構造と一致することを確認してください:", + "LabelOpenIDClaims": "以下のオプションを空のままにすると、高度なグループと権限の割り当てが無効になり、自動的に「User」グループが割り当てられます。", + "LabelOpenIDGroupClaimDescription": "ユーザーのグループリストを含むOpenIDクレーム名。一般的には groups と呼ばれます。設定されている場合、アプリケーションはユーザーのグループメンバーシップに基づいて自動的にロールを割り当てます。クレームのグループ名が大文字小文字を区別せずに「admin」「user」「guest」である場合、対応するロールが割り当てられます。グループが一致しない場合、アクセスは拒否されます。", + "LabelOpenRSSFeed": "RSSフィードを開く", + "LabelOverwrite": "上書き", + "LabelPaginationPageXOfY": "{0}/{1}ページ", "LabelPassword": "パスワード", "LabelPath": "パス", + "LabelPermanent": "無期限", + "LabelPermissionsAccessAllLibraries": "すべてのライブラリーにアクセス可能", + "LabelPermissionsAccessAllTags": "すべてのタグにアクセス可能", + "LabelPermissionsAccessExplicitContent": "露骨なコンテンツにアクセス可能", + "LabelPermissionsCreateEreader": "電子書籍リーダー作成可能", + "LabelPermissionsDelete": "削除可能", + "LabelPermissionsDownload": "ダウンロード可能", + "LabelPermissionsUpdate": "更新可能", + "LabelPermissionsUpload": "アップロード可能", + "LabelPersonalYearReview": "個人の年間振り返り({0})", + "LabelPhotoPathURL": "写真パス/URL", + "LabelPlayMethod": "再生方法", + "LabelPlaybackRateIncrementDecrement": "再生速度の増減量", + "LabelPlayerChapterNumberMarker": "{0}/{1}", "LabelPlaylists": "プレイリスト", "LabelPodcast": "ポッドキャスト", + "LabelPodcastSearchRegion": "ポッドキャスト検索地域", + "LabelPodcastType": "ポッドキャストタイプ", "LabelPodcasts": "ポッドキャスト", + "LabelPort": "ポート", + "LabelPrefixesToIgnore": "無視するプレフィックス(大文字小文字を区別しない)", "LabelPreventIndexing": "フィードがiTunesおよびGoogleのポッドキャストディレクトリにインデックス登録されるのを防ぎます", + "LabelPrimaryEbook": "メイン電子書籍", + "LabelProgress": "進捗", + "LabelProvider": "プロバイダー", + "LabelProviderAuthorizationValue": "認証ヘッダーの値", + "LabelPubDate": "公開日", "LabelPublishYear": "公開年", + "LabelPublishedDate": "{0}発行", + "LabelPublishedDecade": "発行年代", + "LabelPublishedDecades": "発行年代", + "LabelPublisher": "出版社", + "LabelPublishers": "出版社", + "LabelRSSFeedCustomOwnerEmail": "カスタムオーナーメール", + "LabelRSSFeedCustomOwnerName": "カスタムオーナー名", + "LabelRSSFeedOpen": "RSSフィードを公開", + "LabelRSSFeedPreventIndexing": "インデックス化を防止", + "LabelRSSFeedSlug": "RSSフィードスラグ", + "LabelRSSFeedURL": "RSSフィードURL", + "LabelRandomly": "ランダム", + "LabelReAddSeriesToContinueListening": "シリーズを再追加して「続きを聴く」に戻す", + "LabelRead": "読む", + "LabelReadAgain": "もう一度読む", + "LabelReadEbookWithoutProgress": "進捗を記録せずに電子書籍を読む", + "LabelRecentSeries": "最近のシリーズ", + "LabelRecentlyAdded": "最近追加", + "LabelRecommended": "おすすめ", + "LabelRedo": "やり直す", + "LabelRegion": "地域", + "LabelReleaseDate": "リリース日", + "LabelRemoveAllMetadataAbs": "すべてのmetadata.absファイルを削除", + "LabelRemoveAllMetadataJson": "すべてのmetadata.jsonファイルを削除", + "LabelRemoveAudibleBranding": "Audibleのイントロとアウトロをチャプターから削除", + "LabelRemoveCover": "カバーを削除", + "LabelRemoveMetadataFile": "ライブラリーアイテムフォルダー内のメタデータファイルを削除", + "LabelRemoveMetadataFileHelp": "{0}フォルダー内のすべてのmetadata.jsonおよびmetadata.absファイルを削除します。", + "LabelRowsPerPage": "1ページあたりの行数", + "LabelSearchTerm": "検索ワード", + "LabelSearchTitle": "タイトルを検索", + "LabelSearchTitleOrASIN": "タイトルまたはASINを検索", + "LabelSeason": "シーズン", + "LabelSeasonNumber": "シーズン#{0}", + "LabelSelectAll": "すべて選択", + "LabelSelectAllEpisodes": "すべてのエピソードを選択", + "LabelSelectEpisodesShowing": "表示中の{0}件のエピソードを選択", + "LabelSelectUser": "ユーザーを選択", + "LabelSelectUsers": "ユーザーを選択", + "LabelSendEbookToDevice": "電子書籍を送信...", + "LabelSequence": "シーケンス", + "LabelSerial": "シリアル", + "LabelSeries": "シリーズ", + "LabelSeriesName": "シリーズ名", + "LabelSeriesProgress": "シリーズ進捗", + "LabelServerLogLevel": "サーバーログレベル", + "LabelServerYearReview": "サーバーの年間振り返り({0})", + "LabelSetEbookAsPrimary": "メインとして設定", + "LabelSetEbookAsSupplementary": "付属として設定", + "LabelSettingsAllowIframe": "iframeへの埋め込みを許可", + "LabelSettingsAudiobooksOnly": "オーディオブックのみ", + "LabelSettingsAudiobooksOnlyHelp": "この設定を有効にすると電子書籍ファイルは無視されます。ただしオーディオブックフォルダー内の電子書籍は付属電子書籍として設定されます", + "LabelSettingsBookshelfViewHelp": "木製の棚を備えたスキューモーフィックデザイン", + "LabelSettingsChromecastSupport": "Chromecastサポート", + "LabelSettingsDateFormat": "日付フォーマット", + "LabelSettingsEnableWatcher": "ライブラリーの変更を自動的に監視する", + "LabelSettingsEnableWatcherForLibrary": "ライブラリーの変更を自動的に監視する", + "LabelSettingsEnableWatcherHelp": "ファイルの変更が検出されたときにアイテムの自動追加/更新を有効にします。*サーバーの再起動が必要です", + "LabelSettingsEpubsAllowScriptedContent": "epubのスクリプトコンテンツを許可", + "LabelSettingsEpubsAllowScriptedContentHelp": "epubファイルがスクリプトを実行することを許可します。epubファイルのソースを信頼しない限り、この設定を無効のままにすることをお勧めします。", + "LabelSettingsExperimentalFeatures": "実験的機能", + "LabelSettingsExperimentalFeaturesHelp": "開発中の機能でご意見とテストのご協力をお願いしています。クリックしてGitHubディスカッションを開く。", "LabelSettingsFindCovers": "表紙を探す", - "LabelSettingsFindCoversHelp": "もしオーディオブックに表紙が埋め込まれていない、もしくは表紙画像がフォルダー内に見つからなければ、スキャナーは表紙を探そうとします。
注記: これによってスキャン時間が長くなります", + "LabelSettingsFindCoversHelp": "もしオーディオブックに表紙が埋め込まれていない、もしくは表紙画像がフォルダー内に見つからなければ、スキャナーは表紙を探そうとします。
注: これによってスキャン時間が長くなります", + "LabelSettingsHideSingleBookSeries": "単一の本のシリーズを非表示", + "LabelSettingsHideSingleBookSeriesHelp": "単一の本を持つシリーズはシリーズページとホームページの棚から非表示になります。", + "LabelSettingsHomePageBookshelfView": "ホームページは本棚ビューを使用", + "LabelSettingsLibraryBookshelfView": "ライブラリーは本棚ビューを使用", + "LabelSettingsLibraryMarkAsFinishedPercentComplete": "完了率が次より大きい", + "LabelSettingsLibraryMarkAsFinishedTimeRemaining": "残り時間が次より少ない(秒)", + "LabelSettingsLibraryMarkAsFinishedWhen": "メディアアイテムを完了としてマークする条件", + "LabelSettingsOnlyShowLaterBooksInContinueSeries": "「シリーズを続ける」で前の本をスキップ", + "LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "「シリーズを続ける」のホームページ棚は、1冊以上完了していて進行中の本がないシリーズの最初の未開始の本を表示します。この設定を有効にすると、最初の未開始の本ではなく、最後に完了した本からシリーズが継続されます。", "LabelSettingsParseSubtitles": "サブタイトルを抽出する", "LabelSettingsParseSubtitlesHelp": "オーディオブックのフォルダー名からサブタイトルを抽出します。
サブタイトルは \"-\" で区切ってください
例: \"本のタイトル - ここにサブタイトル\" という名前だと \"ここにサブタイトル\" というサブタイトルになります", "LabelSettingsPreferMatchedMetadata": "一致したメタデータを優先する", "LabelSettingsPreferMatchedMetadataHelp": "クイックマッチを使用する時、一致したデータは書籍の詳細を上書きします。デフォルトでは、埋まっていない項目のみ入力されます。", + "LabelSettingsSkipMatchingBooksWithASIN": "ASINを持つ本のマッチングをスキップ", + "LabelSettingsSkipMatchingBooksWithISBN": "ISBNを持つ本のマッチングをスキップ", "LabelSettingsSortingIgnorePrefixes": "並び替えでプレフィックスを無視する", "LabelSettingsSortingIgnorePrefixesHelp": "例: プレフィックス \"the\" の付いた本のタイトル \"The Book Title\" は \"Book Title, The\" として並び替えられます", + "LabelSettingsSquareBookCovers": "正方形の本のカバーを使用", + "LabelSettingsSquareBookCoversHelp": "標準の1.6:1のカバーより正方形のカバーを優先", "LabelSettingsStoreCoversWithItem": "表紙を項目と一緒に保存する", "LabelSettingsStoreCoversWithItemHelp": "デフォルトでは表紙は /metadata/items に保存されますが、この設定をオンにするとライブラリーの項目のフォルダーに保存されます。\"cover\" という名前のファイル一つのみが保持されます", "LabelSettingsStoreMetadataWithItem": "メタデータを項目と一緒に保存する", - "LabelSettingsStoreMetadataWithItemHelp": "デフォルトではメタデータは/metadata/itemsに保存されますが、この設定をオンにするとライブラリーの項目のフォルダーに保存されます" + "LabelSettingsStoreMetadataWithItemHelp": "デフォルトではメタデータは/metadata/itemsに保存されますが、この設定をオンにするとライブラリーの項目のフォルダーに保存されます", + "LabelSettingsTimeFormat": "時刻フォーマット", + "LabelShare": "共有", + "LabelShareDownloadableHelp": "共有リンクを持つユーザーがライブラリーアイテムのzipファイルをダウンロードできるようにします。", + "LabelShareOpen": "共有を公開", + "LabelShareURL": "共有URL", + "LabelShowAll": "すべて表示", + "LabelShowSeconds": "秒を表示", + "LabelShowSubtitles": "字幕を表示", + "LabelSize": "サイズ", + "LabelSleepTimer": "スリープタイマー", + "LabelSlug": "スラグ", + "LabelSortAscending": "昇順", + "LabelSortDescending": "降順", + "LabelSortPubDate": "公開日でソート", + "LabelStart": "開始", + "LabelStartTime": "開始時刻", + "LabelStarted": "開始済み", + "LabelStartedAt": "開始日時", + "LabelStartedDate": "{0}に開始", + "LabelStatsAudioTracks": "オーディオトラック", + "LabelStatsAuthors": "作者", + "LabelStatsBestDay": "日ごとの最長", + "LabelStatsDailyAverage": "日ごとの平均", + "LabelStatsDays": "日数", + "LabelStatsDaysListened": "聴いた日数", + "LabelStatsHours": "時間", + "LabelStatsInARow": "連続", + "LabelStatsItemsFinished": "完了したアイテム", + "LabelStatsItemsInLibrary": "ライブラリー内のアイテム", + "LabelStatsMinutes": "分", + "LabelStatsMinutesListening": "聴いた時間(分)", + "LabelStatsOverallDays": "総日数", + "LabelStatsOverallHours": "総時間", + "LabelStatsWeekListening": "週間リスニング", + "LabelSubtitle": "サブタイトル", + "LabelSupportedFileTypes": "サポートされているファイルタイプ", + "LabelTag": "タグ", + "LabelTags": "タグ", + "LabelTagsAccessibleToUser": "ユーザーがアクセス可能なタグ", + "LabelTagsNotAccessibleToUser": "ユーザーがアクセスできないタグ", + "LabelTasks": "実行中のタスク", + "LabelTextEditorBulletedList": "箇条書きリスト", + "LabelTextEditorLink": "リンク", + "LabelTextEditorNumberedList": "番号付きリスト", + "LabelTextEditorUnlink": "リンク解除", + "LabelTheme": "テーマ", + "LabelThemeDark": "ダーク", + "LabelThemeLight": "ライト", + "LabelThemeSepia": "セピア", + "LabelTimeBase": "タイムベース", + "LabelTimeDurationXHours": "{0}時間", + "LabelTimeDurationXMinutes": "{0}分", + "LabelTimeDurationXSeconds": "{0}秒", + "LabelTimeInMinutes": "時間(分単位)", + "LabelTimeLeft": "残り{0}", + "LabelTimeListened": "リスニング時間", + "LabelTimeListenedToday": "本日のリスニング時間", + "LabelTimeRemaining": "残り{0}", + "LabelTimeToShift": "シフトする時間(秒単位)", + "LabelTitle": "タイトル", + "LabelToolsEmbedMetadata": "メタデータの埋め込み", + "LabelToolsEmbedMetadataDescription": "カバー画像とチャプターを含むメタデータをオーディオファイルに埋め込みます。", + "LabelToolsM4bEncoder": "M4Bエンコーダー", + "LabelToolsMakeM4b": "M4Bオーディオブックファイルを作成", + "LabelToolsMakeM4bDescription": "メタデータ、カバー画像、チャプターを埋め込んだ.M4Bオーディオブックファイルを生成します。", + "LabelToolsSplitM4b": "M4BをMP3に分割", + "LabelToolsSplitM4bDescription": "メタデータ、カバー画像、チャプターを埋め込んだチャプター分割MP3ファイルをM4Bから作成します。", + "LabelTotalDuration": "総再生時間", + "LabelTotalTimeListened": "合計リスニング時間", + "LabelTrackFromFilename": "ファイル名からトラック", + "LabelTrackFromMetadata": "メタデータからトラック", + "LabelTracks": "トラック", + "LabelTracksMultiTrack": "マルチトラック", + "LabelTracksNone": "トラックなし", + "LabelTracksSingleTrack": "シングルトラック", + "LabelTrailer": "トレーラー", + "LabelType": "タイプ", + "LabelUnabridged": "完全版", + "LabelUndo": "元に戻す", + "LabelUnknown": "不明", + "LabelUnknownPublishDate": "公開日不明", + "LabelUpdateCover": "カバーを更新", + "LabelUpdateCoverHelp": "マッチが見つかった場合、選択した本の既存のカバーの上書きを許可", + "LabelUpdateDetails": "詳細を更新", + "LabelUpdateDetailsHelp": "マッチが見つかった場合、選択した本の既存の詳細の上書きを許可", + "LabelUpdatedAt": "更新日時", + "LabelUploaderDragAndDrop": "ファイルまたはフォルダーをドラッグ&ドロップ", + "LabelUploaderDragAndDropFilesOnly": "ファイルをドラッグ&ドロップ", + "LabelUploaderDropFiles": "ファイルをドロップ", + "LabelUploaderItemFetchMetadataHelp": "タイトル、作者、シリーズを自動取得", + "LabelUseAdvancedOptions": "詳細オプションを使用", + "LabelUseChapterTrack": "チャプタートラックを使用", + "LabelUseFullTrack": "フルトラックを使用", + "LabelUseZeroForUnlimited": "0で無制限", + "LabelUser": "ユーザー", + "LabelUsername": "ユーザー名", + "LabelValue": "値", + "LabelVersion": "バージョン", + "LabelViewBookmarks": "ブックマークを表示", + "LabelViewChapters": "チャプターを表示", + "LabelViewPlayerSettings": "プレーヤー設定を表示", + "LabelViewQueue": "「次に再生」を表示", + "LabelVolume": "ボリューム", + "LabelWebRedirectURLsDescription": "ログイン後にウェブアプリへのリダイレクトを許可するため、OAuthプロバイダーでこれらのURLを認可してください:", + "LabelWebRedirectURLsSubfolder": "リダイレクトURLのサブフォルダー", + "LabelWeekdaysToRun": "実行する曜日", + "LabelXBooks": "{0}冊の本", + "LabelXItems": "{0}件のアイテム", + "LabelYearReviewHide": "年間振り返りを非表示", + "LabelYearReviewShow": "年間振り返りを表示", + "LabelYourAudiobookDuration": "オーディオブックの再生時間", + "LabelYourBookmarks": "ブックマーク", + "LabelYourPlaylists": "プレイリスト", + "LabelYourProgress": "進捗状況", + "MessageAddToPlayerQueue": "「次に再生」に追加", + "MessageAppriseDescription": "この機能を使用するには、Apprise API のインスタンスを起動するか、同じリクエストを処理できるAPIが必要です。
Apprise API URLは通知を送信するための完全なURLパスである必要があります。例えば、APIインスタンスが http://192.168.1.1:8337 で起動している場合は http://192.168.1.1:8337/notify を入力してください。", + "MessageAsinCheck": "AmazonではなくAudibleの正しいリージョンのASINを使用していることを確認してください。", + "MessageAuthenticationLegacyTokenWarning": "レガシーAPIトークンは将来削除されます。代わりに APIキー を使用してください。", + "MessageAuthenticationOIDCChangesRestart": "OIDCの変更を適用するには、保存後にサーバーを再起動してください。", + "MessageAuthenticationSecurityMessage": "セキュリティのため認証が改善されました。すべてのユーザーは再ログインが必要です。", + "MessageBackupsDescription": "バックアップにはユーザー、ユーザーの進捗状況、ライブラリーアイテムの詳細、サーバー設定、および /metadata/items/metadata/authors に保存されている画像が含まれます。バックアップにはライブラリーフォルダーに保存されているファイルは含まれません。", + "MessageBackupsLocationEditNote": "注: バックアップ場所を更新しても、既存のバックアップは移動または変更されません", + "MessageBackupsLocationNoEditNote": "注: バックアップ場所は環境変数で設定されており、ここでは変更できません。", + "MessageBackupsLocationPathEmpty": "バックアップ場所のパスは空にできません", + "MessageBatchEditPopulateMapDetailsAllHelp": "すべてのアイテムのデータで有効なフィールドを設定します。複数の値があるフィールドはマージされます", + "MessageBatchEditPopulateMapDetailsItemHelp": "このアイテムのデータで有効なマップ詳細フィールドを設定します", + "MessageBatchQuickMatchDescription": "クイックマッチは選択したアイテムの不足しているカバーとメタデータを追加しようとします。クイックマッチが既存のカバーやメタデータを上書きできるようにするには、以下のオプションを有効にしてください。", + "MessageBookshelfNoCollections": "まだコレクションを作成していません", + "MessageBookshelfNoCollectionsHelp": "コレクションは公開されています。ライブラリーにアクセスできるすべてのユーザーが見ることができます。", + "MessageBookshelfNoRSSFeeds": "公開中のRSSフィードはありません", + "MessageBookshelfNoResultsForFilter": "フィルター \"{0}: {1}\" に一致する結果がありません", + "MessageBookshelfNoResultsForQuery": "クエリに一致する結果がありません", + "MessageBookshelfNoSeries": "シリーズがありません", + "MessageBulkChapterPattern": "この番号パターンでいくつのチャプターを追加しますか?", + "MessageChapterEndIsAfter": "チャプターの終了がオーディオブックの末尾を超えています", + "MessageChapterErrorFirstNotZero": "最初のチャプターは0から始まる必要があります", + "MessageChapterErrorStartGteDuration": "開始時間はオーディオブックの長さより短くなければなりません", + "MessageChapterErrorStartLtPrev": "開始時間は前のチャプターの開始時間以上でなければなりません", + "MessageChapterStartIsAfter": "チャプターの開始がオーディオブックの末尾を超えています", + "MessageChaptersNotFound": "チャプターが見つかりません", + "MessageCheckingCron": "cronを確認中...", + "MessageConfirmCloseFeed": "このフィードを閉じてもよろしいですか?", + "MessageConfirmDeleteApiKey": "APIキー \"{0}\" を削除してもよろしいですか?", + "MessageConfirmDeleteBackup": "{0} のバックアップを削除してもよろしいですか?", + "MessageConfirmDeleteDevice": "電子書籍リーダー端末 \"{0}\" を削除してもよろしいですか?", + "MessageConfirmDeleteFile": "ファイルシステムからファイルを削除します。よろしいですか?", + "MessageConfirmDeleteLibrary": "ライブラリー \"{0}\" を完全に削除してもよろしいですか?", + "MessageConfirmDeleteLibraryItem": "データベースとファイルシステムからライブラリーアイテムを削除します。よろしいですか?", + "MessageConfirmDeleteLibraryItems": "データベースとファイルシステムから {0}件のライブラリーアイテムを削除します。よろしいですか?", + "MessageConfirmDeleteMetadataProvider": "カスタムメタデータプロバイダー \"{0}\" を削除してもよろしいですか?", + "MessageConfirmDeleteNotification": "この通知を削除してもよろしいですか?", + "MessageConfirmDeleteSession": "このセッションを削除してもよろしいですか?", + "MessageConfirmEmbedMetadataInAudioFiles": "{0}件のオーディオファイルにメタデータを埋め込んでもよろしいですか?", + "MessageConfirmForceReScan": "強制的に再スキャンしてもよろしいですか?", + "MessageConfirmMarkAllEpisodesFinished": "すべてのエピソードを完了済みにしてもよろしいですか?", + "MessageConfirmMarkAllEpisodesNotFinished": "すべてのエピソードを未完了にしてもよろしいですか?", + "MessageConfirmMarkItemFinished": "\"{0}\" を完了済みにしてもよろしいですか?", + "MessageConfirmMarkItemNotFinished": "\"{0}\" を未完了にしてもよろしいですか?", + "MessageConfirmMarkSeriesFinished": "このシリーズのすべての本を完了済みにしてもよろしいですか?", + "MessageConfirmMarkSeriesNotFinished": "このシリーズのすべての本を未完了にしてもよろしいですか?", + "MessageConfirmNotificationTestTrigger": "テストデータでこの通知をトリガーしてもよろしいですか?", + "MessageConfirmPurgeCache": "キャッシュの削除により /metadata/cache ディレクトリ全体が削除されます。

キャッシュディレクトリを削除してもよろしいですか?", + "MessageConfirmPurgeItemsCache": "アイテムキャッシュの削除により /metadata/cache/items ディレクトリ全体が削除されます。
よろしいですか?", + "MessageConfirmQuickEmbed": "警告!クイック埋め込みはオーディオファイルをバックアップしません。オーディオファイルのバックアップがあることを確認してください。

続行しますか?", + "MessageConfirmQuickMatchEpisodes": "エピソードのクイックマッチは一致が見つかった場合に詳細を上書きします。未マッチのエピソードのみ更新されます。よろしいですか?", + "MessageConfirmReScanLibraryItems": "{0}件のアイテムを再スキャンしてもよろしいですか?", + "MessageConfirmRemoveAllChapters": "すべてのチャプターを削除してもよろしいですか?", + "MessageConfirmRemoveAuthor": "作者 \"{0}\" を削除してもよろしいですか?", + "MessageConfirmRemoveCollection": "コレクション \"{0}\" を削除してもよろしいですか?", + "MessageConfirmRemoveEpisode": "エピソード \"{0}\" を削除してもよろしいですか?", + "MessageConfirmRemoveEpisodeNote": "注: 「ファイルも削除する」を有効にしない限り、オーディオファイルは削除されません", + "MessageConfirmRemoveEpisodes": "{0}件のエピソードを削除してもよろしいですか?", + "MessageConfirmRemoveListeningSessions": "{0}件のリスニングセッションを削除してもよろしいですか?", + "MessageConfirmRemoveMetadataFiles": "ライブラリーアイテムフォルダー内のすべての metadata.{0} ファイルを削除してもよろしいですか?", + "MessageConfirmRemoveNarrator": "ナレーター \"{0}\" を削除してもよろしいですか?", + "MessageConfirmRemovePlaylist": "プレイリスト \"{0}\" を削除してもよろしいですか?", + "MessageConfirmRenameGenre": "すべてのアイテムのジャンル \"{0}\" を \"{1}\" に変更してもよろしいですか?", + "MessageConfirmRenameGenreMergeNote": "注: このジャンルはすでに存在するためマージされます。", + "MessageConfirmRenameGenreWarning": "警告!大文字・小文字が異なる類似したジャンル \"{0}\" がすでに存在します。", + "MessageConfirmRenameTag": "すべてのアイテムのタグ \"{0}\" を \"{1}\" に変更してもよろしいですか?", + "MessageConfirmRenameTagMergeNote": "注: このタグはすでに存在するためマージされます。", + "MessageConfirmRenameTagWarning": "警告!大文字・小文字が異なる類似したタグ \"{0}\" がすでに存在します。", + "MessageConfirmResetProgress": "進捗状況をリセットしてもよろしいですか?", + "MessageConfirmSendEbookToDevice": "{0} の電子書籍 \"{1}\" を端末 \"{2}\" に送信してもよろしいですか?", + "MessageConfirmUnlinkOpenId": "このユーザーをOpenIDからリンク解除してもよろしいですか?", + "MessageDaysListenedInTheLastYear": "過去1年間に {0}日間聴きました", + "MessageDownloadingEpisode": "エピソードをダウンロード中", + "MessageDragFilesIntoTrackOrder": "ファイルを正しいトラック順にドラッグしてください", + "MessageEmbedFailed": "埋め込み失敗!", + "MessageEmbedFinished": "埋め込み完了!", + "MessageEmbedQueue": "メタデータ埋め込みキューに追加されました(キュー内: {0}件)", + "MessageEpisodesQueuedForDownload": "{0}件のエピソードがダウンロードキューに追加されました", + "MessageEreaderDevices": "電子書籍を確実に配信するため、以下の各端末で上記のメールアドレスを送信元として許可する設定が必要になることがあります。", + "MessageFeedURLWillBe": "フィードURLは {0} になります", + "MessageFetching": "取得中...", + "MessageForceReScanDescription": "すべてのファイルを新規スキャンのように再スキャンします。オーディオファイルのID3タグ、OPFファイル、テキストファイルが新規として読み込まれます。", + "MessageHeatmapListeningTimeTooltip": "{1} に {0} のリスニング", + "MessageHeatmapNoListeningSessions": "{0} にリスニングセッションはありません", + "MessageImportantNotice": "重要なお知らせ!", + "MessageInsertChapterBelow": "下にチャプターを挿入", + "MessageInvalidAsin": "無効なASIN", + "MessageItemsSelected": "{0}件選択中", + "MessageItemsUpdated": "{0}件更新されました", + "MessageJoinUsOn": "参加する:", + "MessageLoading": "読み込み中...", + "MessageLoadingFolders": "フォルダーを読み込み中...", + "MessageLogsDescription": "ログは /metadata/logs にJSONファイルとして保存されます。クラッシュログは /metadata/logs/crash_logs.txt に保存されます。", + "MessageM4BFailed": "M4B変換失敗!", + "MessageM4BFinished": "M4B変換完了!", + "MessageMapChapterTitles": "タイムスタンプを調整せずにチャプタータイトルを既存のオーディオブックのチャプターに割り当てます", + "MessageMarkAllEpisodesFinished": "すべてのエピソードを完了済みにする", + "MessageMarkAllEpisodesNotFinished": "すべてのエピソードを未完了にする", + "MessageMarkAsFinished": "完了済みにする", + "MessageMarkAsNotFinished": "未完了にする", + "MessageMatchBooksDescription": "ライブラリー内の本を選択した検索プロバイダーの本と照合し、空の詳細とカバーアートを補完しようとします。既存の詳細は上書きしません。", + "MessageNoAudioTracks": "オーディオトラックなし", + "MessageNoAuthors": "作者なし", + "MessageNoBackups": "バックアップなし", + "MessageNoBookmarks": "ブックマークなし", + "MessageNoChapters": "チャプターなし", + "MessageNoCollections": "コレクションなし", + "MessageNoCoversFound": "カバーが見つかりません", + "MessageNoDescription": "説明なし", + "MessageNoDevices": "端末なし", + "MessageNoDownloadsInProgress": "現在進行中のダウンロードはありません", + "MessageNoDownloadsQueued": "ダウンロードキューが空です", + "MessageNoEpisodeMatchesFound": "エピソードの一致が見つかりません", + "MessageNoEpisodes": "エピソードなし", + "MessageNoFoldersAvailable": "利用可能なフォルダーがありません", + "MessageNoGenres": "ジャンルなし", + "MessageNoIssues": "問題なし", + "MessageNoItems": "アイテムなし", + "MessageNoItemsFound": "アイテムが見つかりません", + "MessageNoListeningSessions": "リスニングセッションなし", + "MessageNoLogs": "ログなし", + "MessageNoMediaProgress": "メディアの進捗状況なし", + "MessageNoNotifications": "通知なし", + "MessageNoPodcastFeed": "無効なポッドキャスト: フィードなし", + "MessageNoPodcastsFound": "ポッドキャストが見つかりません", + "MessageNoResults": "結果なし", + "MessageNoSearchResultsFor": "\"{0}\" の検索結果はありません", + "MessageNoSeries": "シリーズなし", + "MessageNoTags": "タグなし", + "MessageNoTasksRunning": "実行中のタスクなし", + "MessageNoUpdatesWereNecessary": "更新は不要でした", + "MessageNoUserPlaylists": "プレイリストがありません", + "MessageNoUserPlaylistsHelp": "プレイリストは非公開です。作成したユーザーのみが見ることができます。", + "MessageNotYetImplemented": "未実装", + "MessageOpmlPreviewNote": "注: これは解析されたOPMLファイルのプレビューです。実際のポッドキャストタイトルはRSSフィードから取得されます。", + "MessageOr": "または", + "MessagePauseChapter": "チャプターの再生を一時停止", + "MessagePlayChapter": "チャプターの最初から聴く", + "MessagePlaylistCreateFromCollection": "コレクションからプレイリストを作成", + "MessagePleaseWait": "しばらくお待ちください...", + "MessagePodcastHasNoRSSFeedForMatching": "このポッドキャストには照合に使用できるRSSフィードURLがありません", + "MessagePodcastSearchField": "検索キーワードまたはRSSフィードURLを入力", + "MessageQuickEmbedInProgress": "クイック埋め込みを実行中", + "MessageQuickEmbedQueue": "クイック埋め込みキューに追加されました(キュー内: {0}件)", + "MessageQuickMatchAllEpisodes": "すべてのエピソードをクイックマッチ", + "MessageQuickMatchDescription": "'{0}' の最初の一致結果でアイテムの空の詳細とカバーを設定します。サーバー設定の「一致したメタデータを優先」が有効な場合を除き、既存の詳細は上書きされません。", + "MessageRemoveChapter": "チャプターを削除", + "MessageRemoveEpisodes": "{0}件のエピソードを削除", + "MessageRemoveFromPlayerQueue": "「次に再生」から削除", + "MessageRemoveUserWarning": "ユーザー \"{0}\" を完全に削除してもよろしいですか?", + "MessageReportBugsAndContribute": "バグ報告、機能リクエスト、コントリビューション:", + "MessageResetChaptersConfirm": "チャプターをリセットして変更を元に戻してもよろしいですか?", + "MessageRestoreBackupConfirm": "以下の日時に作成されたバックアップを復元してもよろしいですか:", + "MessageRestoreBackupWarning": "バックアップを復元すると、/config にあるデータベース全体と /metadata/items および /metadata/authors のカバー画像が上書きされます。

バックアップはライブラリーフォルダー内のファイルを変更しません。カバーアートとメタデータをライブラリーフォルダーに保存するサーバー設定を有効にしている場合、それらはバックアップされず上書きもされません。

サーバーを使用しているすべてのクライアントは自動的に更新されます。", + "MessageScheduleLibraryScanNote": "ほとんどのユーザーには、この機能を無効のままにして「ライブラリーの変更を自動的に監視する」設定を有効にしておくことをお勧めします。これによりライブラリーフォルダーの変更が自動的に検出されます。NFS などのファイルシステムで「ライブラリーの変更を自動的に監視する」が機能しない場合はこの機能を有効にしてください。", + "MessageScheduleRunEveryWeekdayAtTime": "毎{0} {1} に実行", + "MessageSearchResultsFor": "検索結果:", + "MessageSelected": "{0}件選択中", + "MessageSeriesSequenceCannotContainSpaces": "シリーズのシーケンスにスペースを含めることはできません", + "MessageServerCouldNotBeReached": "サーバーに接続できませんでした", + "MessageSetChaptersFromTracksDescription": "各オーディオファイルをチャプターとして設定し、チャプタータイトルにはファイル名を使用します", + "MessageShareExpirationWillBe": "有効期限は {0} になります", + "MessageShareExpiresIn": "{0} 後に期限切れ", + "MessageShareURLWillBe": "共有URLは {0} になります", + "MessageStartPlaybackAtTime": "\"{0}\" を {1} から再生しますか?", + "MessageTaskAudioFileNotWritable": "オーディオファイル \"{0}\" は書き込み不可です", + "MessageTaskCanceledByUser": "タスクがユーザーによりキャンセルされました", + "MessageTaskDownloadingEpisodeDescription": "エピソード \"{0}\" をダウンロード中", + "MessageTaskEmbeddingMetadata": "メタデータを埋め込み中", + "MessageTaskEmbeddingMetadataDescription": "オーディオブック \"{0}\" にメタデータを埋め込み中", + "MessageTaskEncodingM4b": "M4Bをエンコード中", + "MessageTaskEncodingM4bDescription": "オーディオブック \"{0}\" を単一のm4bファイルにエンコード中", + "MessageTaskFailed": "失敗", + "MessageTaskFailedToBackupAudioFile": "オーディオファイル \"{0}\" のバックアップに失敗しました", + "MessageTaskFailedToCreateCacheDirectory": "キャッシュディレクトリの作成に失敗しました", + "MessageTaskFailedToEmbedMetadataInFile": "ファイル \"{0}\" へのメタデータ埋め込みに失敗しました", + "MessageTaskFailedToMergeAudioFiles": "オーディオファイルのマージに失敗しました", + "MessageTaskFailedToMoveM4bFile": "m4bファイルの移動に失敗しました", + "MessageTaskFailedToWriteMetadataFile": "メタデータファイルの書き込みに失敗しました", + "MessageTaskMatchingBooksInLibrary": "ライブラリー \"{0}\" の本を照合中", + "MessageTaskNoFilesToScan": "スキャンするファイルがありません", + "MessageTaskOpmlImport": "OPMLインポート", + "MessageTaskOpmlImportDescription": "{0}件のRSSフィードからポッドキャストを作成中", + "MessageTaskOpmlImportFeed": "OPMLインポートフィード", + "MessageTaskOpmlImportFeedDescription": "RSSフィード \"{0}\" をインポート中", + "MessageTaskOpmlImportFeedFailed": "ポッドキャストフィードの取得に失敗しました", + "MessageTaskOpmlImportFeedPodcastDescription": "ポッドキャスト \"{0}\" を作成中", + "MessageTaskOpmlImportFeedPodcastExists": "このパスにポッドキャストがすでに存在します", + "MessageTaskOpmlImportFeedPodcastFailed": "ポッドキャストの作成に失敗しました", + "MessageTaskOpmlImportFinished": "{0}件のポッドキャストを追加しました", + "MessageTaskOpmlParseFailed": "OPMLファイルの解析に失敗しました", + "MessageTaskOpmlParseFastFail": "無効なOPMLファイル: タグが見つからないか タグが見つかりません", + "MessageTaskOpmlParseNoneFound": "OPMLファイルにフィードが見つかりません", + "MessageTaskScanItemsAdded": "{0}件追加", + "MessageTaskScanItemsMissing": "{0}件不明", + "MessageTaskScanItemsUpdated": "{0}件更新", + "MessageTaskScanNoChangesNeeded": "変更は不要でした", + "MessageTaskScanningFileChanges": "\"{0}\" のファイル変更をスキャン中", + "MessageTaskScanningLibrary": "\"{0}\" ライブラリーをスキャン中", + "MessageTaskTargetDirectoryNotWritable": "ターゲットディレクトリは書き込み不可です", + "MessageThinking": "処理中...", + "MessageUploaderItemFailed": "アップロード失敗", + "MessageUploaderItemSuccess": "アップロード成功!", + "MessageUploading": "アップロード中...", + "MessageValidCronExpression": "有効なCron式です", + "MessageWatcherIsDisabledGlobally": "ウォッチャーはサーバー設定でグローバルに無効化されています", + "MessageXLibraryIsEmpty": "{0} ライブラリーは空です!", + "MessageYourAudiobookDurationIsLonger": "オーディオブックの長さが見つかった長さより長くなっています", + "MessageYourAudiobookDurationIsShorter": "オーディオブックの長さが見つかった長さより短くなっています", + "NoteChangeRootPassword": "ルートユーザーのみ空のパスワードを設定できます", + "NoteChapterEditorTimes": "注: 最初のチャプターの開始時間は必ず0:00のままにしてください。また最後のチャプターの開始時間はオーディオブックの長さを超えることはできません。", + "NoteFolderPicker": "注: すでにマッピング済みのフォルダーは表示されません", + "NoteRSSFeedPodcastAppsHttps": "警告: ほとんどのポッドキャストアプリはHTTPSのRSSフィードURLが必要です", + "NoteRSSFeedPodcastAppsPubDate": "警告: 1件以上のエピソードに公開日がありません。一部のポッドキャストアプリではこれが必要です。", + "NoteUploaderFoldersWithMediaFiles": "メディアファイルを含むフォルダーは別々のライブラリーアイテムとして処理されます。", + "NoteUploaderOnlyAudioFiles": "オーディオファイルのみをアップロードする場合、各オーディオファイルは個別のオーディオブックとして処理されます。", + "NoteUploaderUnsupportedFiles": "サポートされていないファイルは無視されます。フォルダーを選択またはドロップする場合、アイテムフォルダー内にない他のファイルは無視されます。", + "NotificationOnBackupCompletedDescription": "バックアップが完了したときにトリガーされます", + "NotificationOnBackupFailedDescription": "バックアップが失敗したときにトリガーされます", + "NotificationOnEpisodeDownloadedDescription": "ポッドキャストエピソードが自動ダウンロードされたときにトリガーされます", + "NotificationOnRSSFeedDisabledDescription": "失敗が多すぎたため自動エピソードダウンロードが無効になったときにトリガーされます", + "NotificationOnRSSFeedFailedDescription": "自動エピソードダウンロードのRSSフィードリクエストが失敗したときにトリガーされます", + "NotificationOnTestDescription": "通知システムのテスト用イベント", + "PlaceholderBulkChapterInput": "チャプタータイトルを入力するか、番号付けを使用(例: 「エピソード 1」「チャプター 10」「1.」)", + "PlaceholderNewCollection": "新しいコレクション名", + "PlaceholderNewFolderPath": "新しいフォルダーパス", + "PlaceholderNewPlaylist": "新しいプレイリスト名", + "PlaceholderSearch": "検索...", + "PlaceholderSearchEpisode": "エピソードを検索...", + "StatsAuthorsAdded": "追加された作者", + "StatsBooksAdded": "追加された本", + "StatsBooksAdditional": "その他の追加内容…", + "StatsBooksFinished": "完了した本", + "StatsBooksFinishedThisYear": "今年完了した本…", + "StatsBooksListenedTo": "聴いた本", + "StatsCollectionGrewTo": "あなたの本のコレクションが増えました…", + "StatsSessions": "セッション", + "StatsSpentListening": "リスニング時間", + "StatsTopAuthor": "トップ作者", + "StatsTopAuthors": "トップ作者", + "StatsTopGenre": "トップジャンル", + "StatsTopGenres": "トップジャンル", + "StatsTopMonth": "トップ月", + "StatsTopNarrator": "トップナレーター", + "StatsTopNarrators": "トップナレーター", + "StatsTotalDuration": "合計再生時間…", + "StatsYearInReview": "年間振り返り", + "ToastAccountUpdateSuccess": "アカウントを更新しました", + "ToastAppriseUrlRequired": "Apprise URLを入力してください", + "ToastAsinRequired": "ASINを入力してください", + "ToastAuthorImageRemoveSuccess": "作者の画像を削除しました", + "ToastAuthorNotFound": "作者「{0}」が見つかりません", + "ToastAuthorRemoveSuccess": "作者を削除しました", + "ToastAuthorSearchNotFound": "作者が見つかりません", + "ToastAuthorUpdateMerged": "作者をマージしました", + "ToastAuthorUpdateSuccess": "作者を更新しました", + "ToastAuthorUpdateSuccessNoImageFound": "作者を更新しました(画像が見つかりません)", + "ToastBackupAppliedSuccess": "バックアップを適用しました", + "ToastBackupCreateFailed": "バックアップの作成に失敗しました", + "ToastBackupCreateSuccess": "バックアップを作成しました", + "ToastBackupDeleteFailed": "バックアップの削除に失敗しました", + "ToastBackupDeleteSuccess": "バックアップを削除しました", + "ToastBackupInvalidMaxKeep": "保持するバックアップ数が無効です", + "ToastBackupInvalidMaxSize": "バックアップの最大サイズが無効です", + "ToastBackupRestoreFailed": "バックアップの復元に失敗しました", + "ToastBackupUploadFailed": "バックアップのアップロードに失敗しました", + "ToastBackupUploadSuccess": "バックアップをアップロードしました", + "ToastBatchApplyDetailsToItemsSuccess": "詳細をアイテムに適用しました", + "ToastBatchDeleteFailed": "一括削除に失敗しました", + "ToastBatchDeleteSuccess": "一括削除に成功しました", + "ToastBatchQuickMatchFailed": "一括クイックマッチに失敗しました!", + "ToastBatchQuickMatchStarted": "{0}冊の一括クイックマッチを開始しました!", + "ToastBatchUpdateFailed": "一括更新に失敗しました", + "ToastBatchUpdateSuccess": "一括更新に成功しました", + "ToastBookmarkCreateFailed": "ブックマークの作成に失敗しました", + "ToastBookmarkCreateSuccess": "ブックマークを追加しました", + "ToastBookmarkRemoveSuccess": "ブックマークを削除しました", + "ToastBulkChapterInvalidCount": "1〜150の数値を入力してください", + "ToastCachePurgeFailed": "キャッシュのクリアに失敗しました", + "ToastCachePurgeSuccess": "キャッシュをクリアしました", + "ToastChapterLocked": "チャプターはロックされています。", + "ToastChapterStartTimeAdjusted": "チャプター開始時間を{0}秒調整しました", + "ToastChaptersAllLocked": "すべてのチャプターがロックされています。時間をシフトするにはいくつかのチャプターのロックを解除してください。", + "ToastChaptersHaveErrors": "チャプターにエラーがあります", + "ToastChaptersInvalidShiftAmountLast": "シフト量が無効です。最後のチャプターの開始時間がこのオーディオブックの長さを超えてしまいます。", + "ToastChaptersInvalidShiftAmountStart": "シフト量が無効です。最初のチャプターがゼロまたは負の長さになり、2番目のチャプターに上書きされます。2番目のチャプターの開始時間を長くしてください。", + "ToastChaptersMustHaveTitles": "チャプターにはタイトルが必要です", + "ToastChaptersRemoved": "チャプターを削除しました", + "ToastChaptersUpdated": "チャプターを更新しました", + "ToastCollectionItemsAddFailed": "アイテムのコレクションへの追加に失敗しました", + "ToastCollectionRemoveSuccess": "コレクションを削除しました", + "ToastCollectionUpdateSuccess": "コレクションを更新しました", + "ToastConnectionNotAvailable": "接続が利用できません。後でもう一度お試しください", + "ToastCoverSearchFailed": "カバー検索に失敗しました", + "ToastCoverUpdateFailed": "カバーの更新に失敗しました", + "ToastDateTimeInvalidOrIncomplete": "日時が無効または不完全です", + "ToastDeleteFileFailed": "ファイルの削除に失敗しました", + "ToastDeleteFileSuccess": "ファイルを削除しました", + "ToastDeviceAddFailed": "デバイスの追加に失敗しました", + "ToastDeviceNameAlreadyExists": "その名前の電子書籍リーダー端末はすでに存在します", + "ToastDeviceTestEmailFailed": "テストメールの送信に失敗しました", + "ToastDeviceTestEmailSuccess": "テストメールを送信しました", + "ToastEmailSettingsUpdateSuccess": "メール設定を更新しました", + "ToastEncodeCancelFailed": "エンコードのキャンセルに失敗しました", + "ToastEncodeCancelSucces": "エンコードをキャンセルしました", + "ToastEpisodeDownloadQueueClearFailed": "ダウンロードキューのクリアに失敗しました", + "ToastEpisodeDownloadQueueClearSuccess": "エピソードのダウンロードキューをクリアしました", + "ToastEpisodeUpdateSuccess": "{0}個のエピソードを更新しました", + "ToastErrorCannotShare": "このデバイスではネイティブ共有ができません", + "ToastFailedToCreate": "作成に失敗しました", + "ToastFailedToDelete": "削除に失敗しました", + "ToastFailedToLoadData": "データの読み込みに失敗しました", + "ToastFailedToMatch": "マッチングに失敗しました", + "ToastFailedToShare": "共有に失敗しました", + "ToastFailedToUpdate": "更新に失敗しました", + "ToastInvalidImageUrl": "画像のURLが無効です", + "ToastInvalidMaxEpisodesToDownload": "ダウンロード可能な最大エピソード数が無効です", + "ToastInvalidUrl": "URLが無効です", + "ToastInvalidUrls": "1つ以上のURLが無効です", + "ToastItemCoverUpdateSuccess": "アイテムのカバーを更新しました", + "ToastItemDeletedFailed": "アイテムの削除に失敗しました", + "ToastItemDeletedSuccess": "アイテムを削除しました", + "ToastItemDetailsUpdateSuccess": "アイテムの詳細を更新しました", + "ToastItemMarkedAsFinishedFailed": "完了としてマークするのに失敗しました", + "ToastItemMarkedAsFinishedSuccess": "アイテムを完了としてマークしました", + "ToastItemMarkedAsNotFinishedFailed": "未完了としてマークするのに失敗しました", + "ToastItemMarkedAsNotFinishedSuccess": "アイテムを未完了としてマークしました", + "ToastItemUpdateSuccess": "アイテムを更新しました", + "ToastLibraryCreateFailed": "ライブラリーの作成に失敗しました", + "ToastLibraryCreateSuccess": "ライブラリー「{0}」を作成しました", + "ToastLibraryDeleteFailed": "ライブラリーの削除に失敗しました", + "ToastLibraryDeleteSuccess": "ライブラリーを削除しました", + "ToastLibraryScanFailedToStart": "スキャンの開始に失敗しました", + "ToastLibraryScanStarted": "ライブラリースキャンを開始しました", + "ToastLibraryUpdateSuccess": "ライブラリー「{0}」を更新しました", + "ToastMatchAllAuthorsFailed": "すべての作者のマッチングに失敗しました", + "ToastMetadataFilesRemovedError": "metadata.{0}ファイルの削除中にエラーが発生しました", + "ToastMetadataFilesRemovedNoneFound": "ライブラリー内にmetadata.{0}ファイルが見つかりません", + "ToastMetadataFilesRemovedNoneRemoved": "削除されたmetadata.{0}ファイルはありません", + "ToastMetadataFilesRemovedSuccess": "{0}個のmetadata.{1}ファイルを削除しました", + "ToastMustHaveAtLeastOnePath": "少なくとも1つのパスが必要です", + "ToastNameEmailRequired": "名前とメールアドレスが必要です", + "ToastNameRequired": "名前を入力してください", + "ToastNewApiKeyUserError": "ユーザーを選択してください", + "ToastNewEpisodesFound": "{0}個の新しいエピソードが見つかりました", + "ToastNewUserCreatedFailed": "アカウントの作成に失敗しました:「{0}」", + "ToastNewUserCreatedSuccess": "新しいアカウントを作成しました", + "ToastNewUserLibraryError": "少なくとも1つのライブラリーを選択してください", + "ToastNewUserPasswordError": "パスワードが必要です。ルートユーザーのみ空のパスワードを使用できます", + "ToastNewUserTagError": "少なくとも1つのタグを選択してください", + "ToastNewUserUsernameError": "ユーザー名を入力してください", + "ToastNoNewEpisodesFound": "新しいエピソードは見つかりません", + "ToastNoRSSFeed": "ポッドキャストにはRSSフィードがありません", + "ToastNoUpdatesNecessary": "更新の必要はありません", + "ToastNotificationCreateFailed": "通知の作成に失敗しました", + "ToastNotificationDeleteFailed": "通知の削除に失敗しました", + "ToastNotificationFailedMaximum": "最大失敗試行回数は0以上である必要があります", + "ToastNotificationQueueMaximum": "最大通知キューは0以上である必要があります", + "ToastNotificationSettingsUpdateSuccess": "通知設定を更新しました", + "ToastNotificationTestTriggerFailed": "テスト通知のトリガーに失敗しました", + "ToastNotificationTestTriggerSuccess": "テスト通知をトリガーしました", + "ToastNotificationUpdateSuccess": "通知を更新しました", + "ToastPlaylistCreateFailed": "プレイリストの作成に失敗しました", + "ToastPlaylistCreateSuccess": "プレイリストを作成しました", + "ToastPlaylistRemoveSuccess": "プレイリストを削除しました", + "ToastPlaylistUpdateSuccess": "プレイリストを更新しました", + "ToastPodcastCreateFailed": "ポッドキャストの作成に失敗しました", + "ToastPodcastCreateSuccess": "ポッドキャストを作成しました", + "ToastPodcastEpisodeUpdated": "エピソードを更新しました", + "ToastPodcastGetFeedFailed": "ポッドキャストフィードの取得に失敗しました", + "ToastPodcastNoEpisodesInFeed": "RSSフィードにエピソードが見つかりません", + "ToastPodcastNoRssFeed": "ポッドキャストにはRSSフィードがありません", + "ToastProgressIsNotBeingSynced": "進捗が同期されていません。再生を再開してください", + "ToastProviderCreatedFailed": "プロバイダーの追加に失敗しました", + "ToastProviderCreatedSuccess": "新しいプロバイダーを追加しました", + "ToastProviderNameAndUrlRequired": "名前とURLが必要です", + "ToastProviderRemoveSuccess": "プロバイダーを削除しました", + "ToastRSSFeedCloseFailed": "RSSフィードのクローズに失敗しました", + "ToastRSSFeedCloseSuccess": "RSSフィードをクローズしました", + "ToastRemoveFailed": "削除に失敗しました", + "ToastRemoveItemFromCollectionFailed": "コレクションからのアイテム削除に失敗しました", + "ToastRemoveItemFromCollectionSuccess": "アイテムをコレクションから削除しました", + "ToastRemoveItemsWithIssuesFailed": "問題のあるライブラリーアイテムの削除に失敗しました", + "ToastRemoveItemsWithIssuesSuccess": "問題のあるライブラリーアイテムを削除しました", + "ToastRenameFailed": "名前変更に失敗しました", + "ToastRescanFailed": "{0}の再スキャンに失敗しました", + "ToastRescanRemoved": "再スキャンが完了しました。アイテムが削除されました", + "ToastRescanUpToDate": "再スキャンが完了しました。アイテムは最新です", + "ToastRescanUpdated": "再スキャンが完了しました。アイテムが更新されました", + "ToastScanFailed": "ライブラリーアイテムのスキャンに失敗しました", + "ToastSelectAtLeastOneUser": "少なくとも1人のユーザーを選択してください", + "ToastSendEbookToDeviceFailed": "電子書籍のデバイスへの送信に失敗しました", + "ToastSendEbookToDeviceSuccess": "電子書籍をデバイス「{0}」に送信しました", + "ToastSeriesSubmitFailedSameName": "同じ名前のシリーズを2つ追加することはできません", + "ToastSeriesUpdateFailed": "シリーズの更新に失敗しました", + "ToastSeriesUpdateSuccess": "シリーズを更新しました", + "ToastServerSettingsUpdateSuccess": "サーバー設定を更新しました", + "ToastSessionCloseFailed": "セッションのクローズに失敗しました", + "ToastSessionDeleteFailed": "セッションの削除に失敗しました", + "ToastSessionDeleteSuccess": "セッションを削除しました", + "ToastSleepTimerDone": "スリープタイマーが終了しました... zZzzZz", + "ToastSlugMustChange": "スラグに無効な文字が含まれています", + "ToastSlugRequired": "スラグを入力してください", + "ToastSocketConnected": "ソケットが接続されました", + "ToastSocketDisconnected": "ソケットが切断されました", + "ToastSocketFailedToConnect": "ソケット接続に失敗しました", + "ToastSortingPrefixesEmptyError": "少なくとも1つのソート接頭辞が必要です", + "ToastSortingPrefixesUpdateSuccess": "ソート接頭辞を更新しました({0}アイテム)", + "ToastTitleRequired": "タイトルを入力してください", + "ToastUnknownError": "不明なエラー", + "ToastUnlinkOpenIdFailed": "OpenIDからのユーザーのリンク解除に失敗しました", + "ToastUnlinkOpenIdSuccess": "ユーザーをOpenIDからリンク解除しました", + "ToastUploaderFilepathExistsError": "ファイルパス「{0}」はサーバーにすでに存在しています", + "ToastUploaderItemExistsInSubdirectoryError": "アイテム「{0}」はアップロードパスのサブディレクトリーを使用しています。", + "ToastUserDeleteFailed": "ユーザーの削除に失敗しました", + "ToastUserDeleteSuccess": "ユーザーを削除しました", + "ToastUserPasswordChangeSuccess": "パスワードを変更しました", + "ToastUserPasswordMismatch": "パスワードが一致しません", + "ToastUserPasswordMustChange": "新しいパスワードは古いパスワードと同じにすることはできません", + "ToastUserRootRequireName": "ルートユーザー名を入力してください", + "TooltipAddChapters": "チャプターを追加", + "TooltipAddOneSecond": "1秒追加", + "TooltipAdjustChapterStart": "クリックして開始時刻を調整", + "TooltipLockAllChapters": "すべてのチャプターをロック", + "TooltipLockChapter": "チャプターをロック(Shift+クリックで範囲選択)", + "TooltipSubtractOneSecond": "1秒減算", + "TooltipUnlockAllChapters": "すべてのチャプターをロック解除", + "TooltipUnlockChapter": "チャプターをロック解除(Shift+クリックで範囲選択)" } From 5dc01261c13ad35f58f77de6c2218a9faf105050 Mon Sep 17 00:00:00 2001 From: LvanAlphen Date: Sun, 26 Apr 2026 20:18:36 +0200 Subject: [PATCH 123/124] Translated using Weblate (Dutch) Currently translated at 100.0% (1163 of 1163 strings) Translation: Audiobookshelf/Abs Web Client Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/nl/ --- client/strings/nl.json | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/client/strings/nl.json b/client/strings/nl.json index 708ea7a97..41a5a814b 100644 --- a/client/strings/nl.json +++ b/client/strings/nl.json @@ -2,7 +2,7 @@ "ButtonAdd": "Toevoegen", "ButtonAddApiKey": "API Key toevoegen", "ButtonAddChapters": "Hoofdstukken toevoegen", - "ButtonAddDevice": "Toestel toevoegen", + "ButtonAddDevice": "Apparaat toevoegen", "ButtonAddLibrary": "Bibliotheek toevoegen", "ButtonAddPodcasts": "Podcasts toevoegen", "ButtonAddUser": "Gebruiker toevoegen", @@ -139,7 +139,7 @@ "HeaderCustomMetadataProviders": "Aangepaste Metadata Providers", "HeaderDetails": "Details", "HeaderDownloadQueue": "Download-wachtrij", - "HeaderEbookFiles": "Ebook bestanden", + "HeaderEbookFiles": "E-book bestanden", "HeaderEmail": "E-mail", "HeaderEmailSettings": "E-mail instellingen", "HeaderEpisodes": "Afleveringen", @@ -275,7 +275,7 @@ "LabelBonus": "Bonus", "LabelBooks": "Boeken", "LabelButtonText": "Knop Tekst", - "LabelByAuthor": "Door {0}", + "LabelByAuthor": "door {0}", "LabelChangePassword": "Wachtwoord wijzigen", "LabelChannels": "Kanalen", "LabelChapterCount": "{0} Hoofdstukken", @@ -383,7 +383,7 @@ "LabelFolders": "Mappen", "LabelFontBold": "Vetgedrukt", "LabelFontBoldness": "Lettertype Dikte", - "LabelFontFamily": "Lettertypefamilie", + "LabelFontFamily": "Letterfamilie", "LabelFontItalic": "Cursief", "LabelFontScale": "Lettertype schaal", "LabelFontStrikethrough": "Doorgestreept", @@ -436,9 +436,9 @@ "LabelLibraryFilterSublistEmpty": "Nee {0}", "LabelLibraryItem": "Bibliotheekonderdeel", "LabelLibraryName": "Bibliotheeknaam", - "LabelLibrarySortByProgress": "Voortuigang geüpdatet", - "LabelLibrarySortByProgressFinished": "Datum voltooid", - "LabelLibrarySortByProgressStarted": "Datum gestart", + "LabelLibrarySortByProgress": "Voortgang: Laatst geüpdatet", + "LabelLibrarySortByProgressFinished": "Voortgang: Voltooid", + "LabelLibrarySortByProgressStarted": "Voortgang: Gestart", "LabelLimit": "Limiet", "LabelLineSpacing": "Regelruimte", "LabelListenAgain": "Opnieuw Beluisteren", @@ -588,8 +588,8 @@ "LabelSettingsBookshelfViewHelp": "Skeumorphisch design met houten planken", "LabelSettingsChromecastSupport": "Chromecast ondersteuning", "LabelSettingsDateFormat": "Datumnotatie", - "LabelSettingsEnableWatcher": "Bibliotheken automatisch scannen op wijzigingen", - "LabelSettingsEnableWatcherForLibrary": "Bibliotheek automatisch scannen op wijzigingen", + "LabelSettingsEnableWatcher": "Bibliotheken automatisch monitoren op wijzigingen", + "LabelSettingsEnableWatcherForLibrary": "Bibliotheek automatisch monitoren op wijzigingen", "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.", @@ -888,7 +888,7 @@ "MessageResetChaptersConfirm": "Weet je zeker dat je de hoofdstukken wil resetten en de wijzigingen die je gemaakt hebt ongedaan wil maken?", "MessageRestoreBackupConfirm": "Weet je zeker dat je wil herstellen met behulp van de back-up gemaakt op", "MessageRestoreBackupWarning": "Een back-up herstellen zal de volledige database in /config en de omslagen in /metadata/items & /metadata/authors overschrijven.

Back-ups wijzigen geen bestanden in je bibliotheekmappen. Als je de serverinstelling gebruikt om omslagen en metadata in je bibliotheekmappen te bewaren dan worden deze niet geback-upt of overschreven.

Alle apparaten die je server gebruiken, worden automatisch ververst.", - "MessageScheduleLibraryScanNote": "Voor de meeste gebruikers is het raadzaam om deze functie uitgeschakeld te laten en de folder watcher-instelling ingeschakeld te houden. De folder watcher detecteert automatisch wijzigingen in uw bibliotheekmappen. De folder watcher werkt niet voor elk bestandssysteem (zoals NFS), dus geplande bibliotheekscans kunnen in plaats daarvan worden gebruikt.", + "MessageScheduleLibraryScanNote": "Voor de meeste gebruikers is het aangeraden om deze functie uitgeschakeld te laten en de \"Bibliotheek automatisch monitoren op wijzigingen\" instelling ingeschakeld te houden - deze detecteert automatisch wijzigingen in uw bibliotheekmappen. Activeer deze instelling als \"Bibliotheek automatisch monitoren op wijzigingen\" niet werkt voor uw bestandssysteem (zoals NFS).", "MessageScheduleRunEveryWeekdayAtTime": "Elke {0} uitvoeren op {1}", "MessageSearchResultsFor": "Zoekresultaten voor", "MessageSelected": "{0} geselecteerd", @@ -1026,6 +1026,8 @@ "ToastCollectionItemsAddFailed": "Item(s) toegevoegd aan collectie mislukt", "ToastCollectionRemoveSuccess": "Collectie verwijderd", "ToastCollectionUpdateSuccess": "Collectie bijgewerkt", + "ToastConnectionNotAvailable": "Verbinding niet beschikbaar. Gelieve later opnieuw te proberen", + "ToastCoverSearchFailed": "Omslag zoeken mislukt", "ToastCoverUpdateFailed": "Omslag bijwerken mislukt", "ToastDateTimeInvalidOrIncomplete": "Datum en tijd ongeldig of onvolledig", "ToastDeleteFileFailed": "Bestand verwijderen mislukt", From 47457ee1e76a3e073230dd95a0faafdb1b17508b Mon Sep 17 00:00:00 2001 From: advplyr Date: Mon, 27 Apr 2026 16:51:34 -0500 Subject: [PATCH 124/124] Version bump v2.34.0 --- client/package-lock.json | 4 ++-- client/package.json | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 57d2da716..79ac53250 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1,12 +1,12 @@ { "name": "audiobookshelf-client", - "version": "2.33.2", + "version": "2.34.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "audiobookshelf-client", - "version": "2.33.2", + "version": "2.34.0", "license": "ISC", "dependencies": { "@nuxtjs/axios": "^5.13.6", diff --git a/client/package.json b/client/package.json index f747cf90f..dd0f3a0c9 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "audiobookshelf-client", - "version": "2.33.2", + "version": "2.34.0", "buildNumber": 1, "description": "Self-hosted audiobook and podcast client", "main": "index.js", diff --git a/package-lock.json b/package-lock.json index 250390c67..8950b4903 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "audiobookshelf", - "version": "2.33.2", + "version": "2.34.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "audiobookshelf", - "version": "2.33.2", + "version": "2.34.0", "license": "GPL-3.0", "dependencies": { "axios": "^0.27.2", diff --git a/package.json b/package.json index 810863f97..10ba26f65 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "audiobookshelf", - "version": "2.33.2", + "version": "2.34.0", "buildNumber": 1, "description": "Self-hosted audiobook and podcast server", "main": "index.js",