diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index 2c6cc1912c..d04ad818fb 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -3,6 +3,3 @@ contact_links:
- name: Discord
url: https://discord.gg/HQgCbd6E75
about: Ask questions, get help troubleshooting, and join the Abs community here.
- - name: Matrix
- url: https://matrix.to/#/#audiobookshelf:matrix.org
- about: Ask questions, get help troubleshooting, and join the Abs community here.
diff --git a/.gitignore b/.gitignore
index 9ddb1a7074..ca3768ba3b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,8 +15,9 @@
/.nyc_output/
/ffmpeg*
/ffprobe*
+/unicode*
sw.*
.DS_STORE
.idea/*
-tailwind.compiled.css
\ No newline at end of file
+tailwind.compiled.css
diff --git a/build/debian/DEBIAN/preinst b/build/debian/DEBIAN/preinst
index c4692ed340..e30bc490c1 100644
--- a/build/debian/DEBIAN/preinst
+++ b/build/debian/DEBIAN/preinst
@@ -2,7 +2,6 @@
set -e
set -o pipefail
-FFMPEG_INSTALL_DIR="/usr/lib/audiobookshelf-ffmpeg"
DEFAULT_DATA_DIR="/usr/share/audiobookshelf"
CONFIG_PATH="/etc/default/audiobookshelf"
DEFAULT_PORT=13378
@@ -46,25 +45,6 @@ add_group() {
fi
}
-install_ffmpeg() {
- echo "Starting FFMPEG Install"
-
- WGET="wget https://johnvansickle.com/ffmpeg/builds/ffmpeg-git-amd64-static.tar.xz --output-document=ffmpeg-git-amd64-static.tar.xz"
-
- if ! cd "$FFMPEG_INSTALL_DIR"; then
- echo "Creating ffmpeg install dir at $FFMPEG_INSTALL_DIR"
- mkdir "$FFMPEG_INSTALL_DIR"
- chown -R 'audiobookshelf:audiobookshelf' "$FFMPEG_INSTALL_DIR"
- cd "$FFMPEG_INSTALL_DIR"
- fi
-
- $WGET
- tar xvf ffmpeg-git-amd64-static.tar.xz --strip-components=1 --no-same-owner
- rm ffmpeg-git-amd64-static.tar.xz
-
- echo "Good to go on Ffmpeg... hopefully"
-}
-
setup_config() {
if [ -f "$CONFIG_PATH" ]; then
echo "Existing config found."
@@ -83,8 +63,6 @@ setup_config() {
config_text="METADATA_PATH=$DEFAULT_DATA_DIR/metadata
CONFIG_PATH=$DEFAULT_DATA_DIR/config
-FFMPEG_PATH=$FFMPEG_INSTALL_DIR/ffmpeg
-FFPROBE_PATH=$FFMPEG_INSTALL_DIR/ffprobe
PORT=$DEFAULT_PORT
HOST=$DEFAULT_HOST"
@@ -101,5 +79,3 @@ add_group 'audiobookshelf' ''
add_user 'audiobookshelf' '' 'audiobookshelf' 'audiobookshelf user-daemon' '/bin/false'
setup_config
-
-install_ffmpeg
diff --git a/client/components/app/BookShelfToolbar.vue b/client/components/app/BookShelfToolbar.vue
index 54450164ce..7584de1412 100644
--- a/client/components/app/BookShelfToolbar.vue
+++ b/client/components/app/BookShelfToolbar.vue
@@ -84,11 +84,6 @@
- Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sunt earum doloremque aliquam culpa dolor nostrum consequatur quas dicta? Molestias repellendus minima pariatur libero vel, reiciendis optio magnam rerum, labore corporis.
- {{ genre }} {{ $getString('LabelXItems', [numItems]) }}Book Back
-
{{ title }}
- - - - -{{ $getString('LabelByAuthor', [authorName]) }}
- - - +{{ title }}
+{{ subtitle }}
+{{ $getString('LabelByAuthor', [authorName]) }}
${this.$strings.LabelEpisode}: ${html}
` - if (this.matchKey === 'tags') return `${this.$strings.LabelTags}: ${html}
` - if (this.matchKey === 'subtitle') return `${html}
` - if (this.matchKey === 'authors') this.$getString('LabelByAuthor', [html]) - if (this.matchKey === 'isbn') return `ISBN: ${html}
` - if (this.matchKey === 'asin') return `ASIN: ${html}
` - if (this.matchKey === 'series') return `${this.$strings.LabelSeries}: ${html}
` - if (this.matchKey === 'narrators') return `${this.$strings.LabelNarrator}: ${html}
` - return `${html}` } }, methods: {}, diff --git a/client/components/cards/LazySeriesCard.vue b/client/components/cards/LazySeriesCard.vue index ff7d2a87a1..1479d18981 100644 --- a/client/components/cards/LazySeriesCard.vue +++ b/client/components/cards/LazySeriesCard.vue @@ -81,16 +81,16 @@ export default { return this.store.getters['user/getSizeMultiplier'] }, seriesId() { - return this.series ? this.series.id : '' + return this.series?.id || '' }, title() { - return this.series ? this.series.name : '' + return this.series?.name || '' }, nameIgnorePrefix() { - return this.series ? this.series.nameIgnorePrefix : '' + return this.series?.nameIgnorePrefix || '' }, displayTitle() { - if (this.sortingIgnorePrefix) return this.nameIgnorePrefix || this.title + if (this.sortingIgnorePrefix) return this.nameIgnorePrefix || this.title || '\u00A0' return this.title || '\u00A0' }, displaySortLine() { @@ -110,13 +110,13 @@ export default { } }, books() { - return this.series ? this.series.books || [] : [] + return this.series?.books || [] }, addedAt() { - return this.series ? this.series.addedAt : 0 + return this.series?.addedAt || 0 }, totalDuration() { - return this.series ? this.series.totalDuration : 0 + return this.series?.totalDuration || 0 }, seriesBookProgress() { return this.books @@ -161,7 +161,7 @@ export default { return this.bookshelfView == constants.BookshelfView.DETAIL }, rssFeed() { - return this.series ? this.series.rssFeed : null + return this.series?.rssFeed } }, methods: { diff --git a/client/components/cards/NarratorSearchCard.vue b/client/components/cards/NarratorSearchCard.vue index d5d1dbbd26..00909c752a 100644 --- a/client/components/cards/NarratorSearchCard.vue +++ b/client/components/cards/NarratorSearchCard.vue @@ -5,6 +5,7 @@{{ narrator }}
+{{ $getString('LabelXBooks', [numBooks]) }}
{{ chap.title }}
@@ -87,4 +87,4 @@ export default { max-width: calc(100% - 150px); } } - \ No newline at end of file + diff --git a/client/components/modals/podcast/OpmlFeedsModal.vue b/client/components/modals/podcast/OpmlFeedsModal.vue index 7d7327d26a..41a7522573 100644 --- a/client/components/modals/podcast/OpmlFeedsModal.vue +++ b/client/components/modals/podcast/OpmlFeedsModal.vue @@ -16,11 +16,18 @@{{ $strings.HeaderPodcastsToAdd }}
+{{ $strings.HeaderPodcastsToAdd }}
+{{ $strings.MessageOpmlPreviewNote }}
{{ index + 1 }}.
+{{ feed.title }}
+{{ feed.feedUrl }}
+{{ userItemsFinished.length }}
+{{ $formatNumber(userItemsFinished.length) }}
{{ $strings.LabelStatsItemsFinished }}
{{ totalDaysListened }}
+{{ $formatNumber(totalDaysListened) }}
{{ $strings.LabelStatsDaysListened }}
{{ totalMinutesListening }}
+{{ $formatNumber(totalMinutesListening) }}
{{ $strings.LabelStatsMinutesListening }}
http://192.168.1.1:8337
alors vous devez mettre http://192.168.1.1:8337/notify
.",
"MessageBackupsDescription": "Les sauvegardes incluent les utilisateurs, la progression des utilisateurs, les détails des éléments de la bibliothèque, les paramètres du serveur et les images stockées dans /metadata/items
& /metadata/authors
. Les sauvegardes n’incluent pas les fichiers stockés dans les dossiers de votre bibliothèque.",
"MessageBackupsLocationEditNote": "Remarque : Mettre à jour l'emplacement de sauvegarde ne déplacera pas ou ne modifiera pas les sauvegardes existantes",
+ "MessageBackupsLocationNoEditNote": "Remarque : l’emplacement de sauvegarde est défini via une variable d’environnement et ne peut pas être modifié ici.",
"MessageBackupsLocationPathEmpty": "L'emplacement de secours ne peut pas être vide",
"MessageBatchQuickMatchDescription": "La recherche par correspondance rapide tentera d’ajouter les couvertures et métadonnées manquantes pour les éléments sélectionnés. Activez les options ci-dessous pour permettre la Recherche par correspondance d’écraser les couvertures et/ou métadonnées existantes.",
"MessageBookshelfNoCollections": "Vous n’avez pas encore de collections",
@@ -716,6 +724,9 @@
"MessageSelected": "{0} sélectionnés",
"MessageServerCouldNotBeReached": "Serveur inaccessible",
"MessageSetChaptersFromTracksDescription": "Positionne un chapitre par fichier audio, avec le titre du fichier comme titre de chapitre",
+ "MessageShareExpirationWillBe": "Expire le {0}",
+ "MessageShareExpiresIn": "Expire dans {0}",
+ "MessageShareURLWillBe": "L’adresse de partage sera {0}",
"MessageStartPlaybackAtTime": "Démarrer la lecture pour « {0} » à {1} ?",
"MessageThinking": "Je cherche…",
"MessageUploaderItemFailed": "Échec du téléversement",
@@ -730,7 +741,7 @@
"NoteChapterEditorTimes": "Information : l’horodatage du premier chapitre doit être à 0:00 et celui du dernier chapitre ne peut se situer au-delà de la durée du livre audio.",
"NoteFolderPicker": "Information : les dossiers déjà surveillés ne sont pas affichés",
"NoteRSSFeedPodcastAppsHttps": "Attention : la majorité des application de podcast nécessite une adresse de flux HTTPS",
- "NoteRSSFeedPodcastAppsPubDate": "Attention : un ou plusieurs de vos épisodes ne possèdent pas de date de publication. Certaines applications de podcast le requièrent.",
+ "NoteRSSFeedPodcastAppsPubDate": "Attention : un ou plusieurs de vos épisodes ne possèdent pas de date de publication. Certaines applications de podcast le requièrent.",
"NoteUploaderFoldersWithMediaFiles": "Les dossiers contenant des fichiers multimédias seront traités comme des éléments distincts de la bibliothèque.",
"NoteUploaderOnlyAudioFiles": "Si vous téléversez uniquement des fichiers audio, chaque fichier audio sera traité comme un livre audio distinct.",
"NoteUploaderUnsupportedFiles": "Les fichiers non pris en charge sont ignorés. Lorsque vous choisissez ou déposez un dossier, les autres fichiers qui ne sont pas dans un dossier d’élément sont ignorés.",
diff --git a/client/strings/he.json b/client/strings/he.json
index aa6eb98665..514639405d 100644
--- a/client/strings/he.json
+++ b/client/strings/he.json
@@ -9,7 +9,7 @@
"ButtonApply": "החל",
"ButtonApplyChapters": "החל פרקים",
"ButtonAuthors": "יוצרים",
- "ButtonBack": "Back",
+ "ButtonBack": "חזור",
"ButtonBrowseForFolder": "עיין בתיקייה",
"ButtonCancel": "בטל",
"ButtonCancelEncode": "בטל קידוד",
@@ -62,8 +62,8 @@
"ButtonQuickMatch": "התאמה מהירה",
"ButtonReScan": "סרוק מחדש",
"ButtonRead": "קרא",
- "ButtonReadLess": "Read less",
- "ButtonReadMore": "Read more",
+ "ButtonReadLess": "קרא פחות",
+ "ButtonReadMore": "קרא יותר",
"ButtonRefresh": "רענן",
"ButtonRemove": "הסר",
"ButtonRemoveAll": "הסר הכל",
@@ -115,7 +115,7 @@
"HeaderCollectionItems": "פריטי אוסף",
"HeaderCover": "כריכה",
"HeaderCurrentDownloads": "הורדות נוכחיות",
- "HeaderCustomMessageOnLogin": "Custom Message on Login",
+ "HeaderCustomMessageOnLogin": "הודעה מותאמת אישית בהתחברות",
"HeaderCustomMetadataProviders": "ספקי מטא-נתונים מותאמים אישית",
"HeaderDetails": "פרטים",
"HeaderDownloadQueue": "תור הורדה",
@@ -806,8 +806,8 @@
"ToastSendEbookToDeviceSuccess": "הספר נשלח אל המכשיר \"{0}\"",
"ToastSeriesUpdateFailed": "עדכון הסדרה נכשל",
"ToastSeriesUpdateSuccess": "הסדרה עודכנה בהצלחה",
- "ToastServerSettingsUpdateFailed": "Failed to update server settings",
- "ToastServerSettingsUpdateSuccess": "Server settings updated",
+ "ToastServerSettingsUpdateFailed": "כשל בעדכון הגדרות שרת",
+ "ToastServerSettingsUpdateSuccess": "הגדרות שרת עודכנו בהצלחה",
"ToastSessionDeleteFailed": "מחיקת הפעולה נכשלה",
"ToastSessionDeleteSuccess": "הפעולה נמחקה בהצלחה",
"ToastSocketConnected": "קצה תקשורת חובר",
diff --git a/client/strings/nl.json b/client/strings/nl.json
index 18bb421828..e209c3a508 100644
--- a/client/strings/nl.json
+++ b/client/strings/nl.json
@@ -1,15 +1,15 @@
{
"ButtonAdd": "Toevoegen",
"ButtonAddChapters": "Hoofdstukken toevoegen",
- "ButtonAddDevice": "Add Device",
- "ButtonAddLibrary": "Add Library",
+ "ButtonAddDevice": "Toestel toevoegen",
+ "ButtonAddLibrary": "Bibliotheek toevoegen",
"ButtonAddPodcasts": "Podcasts toevoegen",
- "ButtonAddUser": "Add User",
+ "ButtonAddUser": "Gebruiker toevoegen",
"ButtonAddYourFirstLibrary": "Voeg je eerste bibliotheek toe",
"ButtonApply": "Pas toe",
"ButtonApplyChapters": "Hoofdstukken toepassen",
"ButtonAuthors": "Auteurs",
- "ButtonBack": "Back",
+ "ButtonBack": "Terug",
"ButtonBrowseForFolder": "Bladeren naar map",
"ButtonCancel": "Annuleren",
"ButtonCancelEncode": "Encoding annuleren",
@@ -32,9 +32,9 @@
"ButtonFullPath": "Volledig pad",
"ButtonHide": "Verberg",
"ButtonHome": "Home",
- "ButtonIssues": "Issues",
- "ButtonJumpBackward": "Jump Backward",
- "ButtonJumpForward": "Jump Forward",
+ "ButtonIssues": "Problemen",
+ "ButtonJumpBackward": "Spring achteruit",
+ "ButtonJumpForward": "Spring vooruit",
"ButtonLatest": "Meest recent",
"ButtonLibrary": "Bibliotheek",
"ButtonLogout": "Log uit",
@@ -44,17 +44,17 @@
"ButtonMatchAllAuthors": "Alle auteurs matchen",
"ButtonMatchBooks": "Alle boeken matchen",
"ButtonNevermind": "Laat maar",
- "ButtonNext": "Next",
- "ButtonNextChapter": "Next Chapter",
+ "ButtonNext": "Volgende",
+ "ButtonNextChapter": "Volgend hoofdstuk",
"ButtonOk": "Ok",
"ButtonOpenFeed": "Feed openen",
"ButtonOpenManager": "Manager openen",
- "ButtonPause": "Pause",
+ "ButtonPause": "Pauze",
"ButtonPlay": "Afspelen",
"ButtonPlaying": "Speelt",
"ButtonPlaylists": "Afspeellijsten",
- "ButtonPrevious": "Previous",
- "ButtonPreviousChapter": "Previous Chapter",
+ "ButtonPrevious": "Vorige",
+ "ButtonPreviousChapter": "Vorig hoofdstuk",
"ButtonPurgeAllCache": "Volledige cache legen",
"ButtonPurgeItemsCache": "Onderdelen-cache legen",
"ButtonQueueAddItem": "In wachtrij zetten",
@@ -62,14 +62,14 @@
"ButtonQuickMatch": "Snelle match",
"ButtonReScan": "Nieuwe scan",
"ButtonRead": "Lees",
- "ButtonReadLess": "Read less",
- "ButtonReadMore": "Read more",
- "ButtonRefresh": "Refresh",
+ "ButtonReadLess": "Lees minder",
+ "ButtonReadMore": "Lees meer",
+ "ButtonRefresh": "Verversen",
"ButtonRemove": "Verwijder",
"ButtonRemoveAll": "Alles verwijderen",
"ButtonRemoveAllLibraryItems": "Verwijder volledige bibliotheekinhoud",
"ButtonRemoveFromContinueListening": "Vewijder uit Verder luisteren",
- "ButtonRemoveFromContinueReading": "Remove from Continue Reading",
+ "ButtonRemoveFromContinueReading": "Verwijder van Verder luisteren",
"ButtonRemoveSeriesFromContinueSeries": "Verwijder serie uit Serie vervolgen",
"ButtonReset": "Reset",
"ButtonResetToDefault": "Reset to default",
@@ -83,7 +83,7 @@
"ButtonSelectFolderPath": "Maplocatie selecteren",
"ButtonSeries": "Series",
"ButtonSetChaptersFromTracks": "Maak hoofdstukken op basis van tracks",
- "ButtonShare": "Share",
+ "ButtonShare": "Deel",
"ButtonShiftTimes": "Tijden verschuiven",
"ButtonShow": "Toon",
"ButtonStartM4BEncode": "Start M4B-encoding",
@@ -98,9 +98,9 @@
"ButtonUserEdit": "Wijzig gebruiker {0}",
"ButtonViewAll": "Toon alle",
"ButtonYes": "Ja",
- "ErrorUploadFetchMetadataAPI": "Error fetching metadata",
- "ErrorUploadFetchMetadataNoResults": "Could not fetch metadata - try updating title and/or author",
- "ErrorUploadLacksTitle": "Must have a title",
+ "ErrorUploadFetchMetadataAPI": "Error metadata ophalen",
+ "ErrorUploadFetchMetadataNoResults": "Kan metadata niet ophalen - probeer de titel en/of auteur te updaten",
+ "ErrorUploadLacksTitle": "Moet een titel hebben",
"HeaderAccount": "Account",
"HeaderAdvanced": "Geavanceerd",
"HeaderAppriseNotificationSettings": "Apprise-notificatie instellingen",
@@ -113,13 +113,13 @@
"HeaderChooseAFolder": "Map kiezen",
"HeaderCollection": "Collectie",
"HeaderCollectionItems": "Collectie-objecten",
- "HeaderCover": "Cover",
+ "HeaderCover": "Omslag",
"HeaderCurrentDownloads": "Huidige downloads",
"HeaderCustomMessageOnLogin": "Custom Message on Login",
"HeaderCustomMetadataProviders": "Custom Metadata Providers",
"HeaderDetails": "Details",
"HeaderDownloadQueue": "Download-wachtrij",
- "HeaderEbookFiles": "Ebook Files",
+ "HeaderEbookFiles": "Ebook bestanden",
"HeaderEmail": "E-mail",
"HeaderEmailSettings": "E-mail instellingen",
"HeaderEpisodes": "Afleveringen",
@@ -239,11 +239,11 @@
"LabelChapterTitle": "Hoofdstuktitel",
"LabelChapters": "Hoofdstukken",
"LabelChaptersFound": "Hoofdstukken gevonden",
- "LabelClickForMoreInfo": "Click for more info",
+ "LabelClickForMoreInfo": "Klik voor meer informatie",
"LabelClosePlayer": "Sluit speler",
"LabelCodec": "Codec",
"LabelCollapseSeries": "Series inklappen",
- "LabelCollection": "Collection",
+ "LabelCollection": "Collectie",
"LabelCollections": "Collecties",
"LabelComplete": "Compleet",
"LabelConfirmPassword": "Bevestig wachtwoord",
@@ -258,6 +258,7 @@
"LabelCurrently": "Op dit moment:",
"LabelCustomCronExpression": "Aangepaste Cron-uitdrukking:",
"LabelDatetime": "Datum-tijd",
+ "LabelDays": "Dagen",
"LabelDeleteFromFileSystemCheckbox": "Delete from file system (uncheck to only remove from database)",
"LabelDescription": "Beschrijving",
"LabelDeselectAll": "Deselecteer alle",
@@ -296,7 +297,7 @@
"LabelExplicitChecked": "Explicit (checked)",
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
"LabelFeedURL": "Feed URL",
- "LabelFetchingMetadata": "Fetching Metadata",
+ "LabelFetchingMetadata": "Metadata ophalen",
"LabelFile": "Bestand",
"LabelFileBirthtime": "Aanmaaktijd bestand",
"LabelFileModified": "Bestand gewijzigd",
@@ -306,7 +307,7 @@
"LabelFinished": "Voltooid",
"LabelFolder": "Map",
"LabelFolders": "Mappen",
- "LabelFontBold": "Bold",
+ "LabelFontBold": "Vetgedrukt",
"LabelFontBoldness": "Font Boldness",
"LabelFontFamily": "Lettertypefamilie",
"LabelFontItalic": "Italic",
@@ -321,6 +322,7 @@
"LabelHighestPriority": "Highest priority",
"LabelHost": "Host",
"LabelHour": "Uur",
+ "LabelHours": "Uren",
"LabelIcon": "Icoon",
"LabelImageURLFromTheWeb": "Image URL from the web",
"LabelInProgress": "Bezig",
@@ -567,7 +569,7 @@
"LabelTracksSingleTrack": "Enkele track",
"LabelType": "Type",
"LabelUnabridged": "Onverkort",
- "LabelUndo": "Undo",
+ "LabelUndo": "Ongedaan maken",
"LabelUnknown": "Onbekend",
"LabelUpdateCover": "Cover bijwerken",
"LabelUpdateCoverHelp": "Sta overschrijven van bestaande covers toe voor de geselecteerde boeken wanneer een match is gevonden",
@@ -630,7 +632,7 @@
"MessageConfirmRemoveCollection": "Weet je zeker dat je de collectie \"{0}\" wil verwijderen?",
"MessageConfirmRemoveEpisode": "Weet je zeker dat je de aflevering \"{0}\" wil verwijderen?",
"MessageConfirmRemoveEpisodes": "Weet je zeker dat je {0} afleveringen wil verwijderen?",
- "MessageConfirmRemoveListeningSessions": "Are you sure you want to remove {0} listening sessions?",
+ "MessageConfirmRemoveListeningSessions": "Weet je zeker dat je {0} luistersessies wilt verwijderen?",
"MessageConfirmRemoveNarrator": "Weet je zeker dat je verteller \"{0}\" wil verwijderen?",
"MessageConfirmRemovePlaylist": "Weet je zeker dat je je afspeellijst \"{0}\" wil verwijderen?",
"MessageConfirmRenameGenre": "Weet je zeker dat je genre \"{0}\" wil hernoemen naar \"{1}\" voor alle onderdelen?",
@@ -714,6 +716,7 @@
"MessageSelected": "{0} selected",
"MessageServerCouldNotBeReached": "Server niet bereikbaar",
"MessageSetChaptersFromTracksDescription": "Stel hoofdstukken in met ieder audiobestand als een hoofdstuk en de audiobestandsnaam als hoofdstuktitel",
+ "MessageShareExpiresIn": "Vervalt in {0}",
"MessageStartPlaybackAtTime": "Afspelen van \"{0}\" beginnen op {1}?",
"MessageThinking": "Aan het denken...",
"MessageUploaderItemFailed": "Uploaden mislukt",
diff --git a/client/strings/pl.json b/client/strings/pl.json
index 92dd2735d5..0fe8535dda 100644
--- a/client/strings/pl.json
+++ b/client/strings/pl.json
@@ -62,8 +62,8 @@
"ButtonQuickMatch": "Szybkie dopasowanie",
"ButtonReScan": "Ponowne skanowanie",
"ButtonRead": "Czytaj",
- "ButtonReadLess": "Read less",
- "ButtonReadMore": "Read more",
+ "ButtonReadLess": "Pokaż mniej",
+ "ButtonReadMore": "Pokaż więcej",
"ButtonRefresh": "Odśwież",
"ButtonRemove": "Usuń",
"ButtonRemoveAll": "Usuń wszystko",
@@ -88,6 +88,7 @@
"ButtonShow": "Pokaż",
"ButtonStartM4BEncode": "Eksportuj jako plik M4B",
"ButtonStartMetadataEmbed": "Osadź metadane",
+ "ButtonStats": "Statystyki",
"ButtonSubmit": "Zaloguj",
"ButtonTest": "Test",
"ButtonUpload": "Wgraj",
@@ -130,13 +131,13 @@
"HeaderIgnoredFiles": "Zignoruj pliki",
"HeaderItemFiles": "Pliki",
"HeaderItemMetadataUtils": "Item Metadata Utils",
- "HeaderLastListeningSession": "Ostatnio odtwarzana sesja",
+ "HeaderLastListeningSession": "Ostatnia sesja słuchania",
"HeaderLatestEpisodes": "Najnowsze odcinki",
"HeaderLibraries": "Biblioteki",
"HeaderLibraryFiles": "Pliki w bibliotece",
"HeaderLibraryStats": "Statystyki biblioteki",
"HeaderListeningSessions": "Sesje słuchania",
- "HeaderListeningStats": "Statystyki odtwarzania",
+ "HeaderListeningStats": "Statystyki słuchania",
"HeaderLogin": "Zaloguj się",
"HeaderLogs": "Logi",
"HeaderManageGenres": "Zarządzaj gatunkami",
@@ -148,12 +149,13 @@
"HeaderNewAccount": "Nowe konto",
"HeaderNewLibrary": "Nowa biblioteka",
"HeaderNotifications": "Powiadomienia",
- "HeaderOpenIDConnectAuthentication": "OpenID Connect Authentication",
+ "HeaderOpenIDConnectAuthentication": "Uwierzytelnianie OpenID Connect",
"HeaderOpenRSSFeed": "Utwórz kanał RSS",
"HeaderOtherFiles": "Inne pliki",
"HeaderPasswordAuthentication": "Uwierzytelnianie hasłem",
"HeaderPermissions": "Uprawnienia",
"HeaderPlayerQueue": "Kolejka odtwarzania",
+ "HeaderPlayerSettings": "Ustawienia Odtwarzania",
"HeaderPlaylist": "Playlista",
"HeaderPlaylistItems": "Pozycje listy odtwarzania",
"HeaderPodcastsToAdd": "Podcasty do dodania",
@@ -175,7 +177,7 @@
"HeaderSettingsScanner": "Skanowanie",
"HeaderSleepTimer": "Wyłącznik czasowy",
"HeaderStatsLargestItems": "Największe pozycje",
- "HeaderStatsLongestItems": "Najdłuższe pozycje (hrs)",
+ "HeaderStatsLongestItems": "Najdłuższe pozycje (godziny)",
"HeaderStatsMinutesListeningChart": "Czas słuchania w minutach (ostatnie 7 dni)",
"HeaderStatsRecentSessions": "Ostatnie sesje",
"HeaderStatsTop10Authors": "Top 10 Autorów",
@@ -200,8 +202,8 @@
"LabelActivity": "Aktywność",
"LabelAddToCollection": "Dodaj do kolekcji",
"LabelAddToCollectionBatch": "Dodaj {0} książki do kolekcji",
- "LabelAddToPlaylist": "Add to Playlist",
- "LabelAddToPlaylistBatch": "Add {0} Items to Playlist",
+ "LabelAddToPlaylist": "Dodaj do playlisty",
+ "LabelAddToPlaylistBatch": "Dodaj {0} pozycji do playlisty",
"LabelAdded": "Dodane",
"LabelAddedAt": "Dodano",
"LabelAdminUsersOnly": "Tylko użytkownicy administracyjni",
@@ -226,14 +228,14 @@
"LabelBackupLocation": "Lokalizacja kopii zapasowej",
"LabelBackupsEnableAutomaticBackups": "Włącz automatyczne kopie zapasowe",
"LabelBackupsEnableAutomaticBackupsHelp": "Kopie zapasowe są zapisywane w folderze /metadata/backups",
- "LabelBackupsMaxBackupSize": "Maksymalny łączny rozmiar backupów (w GB)",
+ "LabelBackupsMaxBackupSize": "Maksymalny rozmiar kopii zapasowej (w GB)",
"LabelBackupsMaxBackupSizeHelp": "Jako zabezpieczenie przed błędną konfiguracją, kopie zapasowe nie będą wykonywane, jeśli przekroczą skonfigurowany rozmiar.",
"LabelBackupsNumberToKeep": "Liczba kopii zapasowych do przechowywania",
"LabelBackupsNumberToKeepHelp": "Tylko 1 kopia zapasowa zostanie usunięta, więc jeśli masz już więcej kopii zapasowych, powinieneś je ręcznie usunąć.",
"LabelBitrate": "Bitrate",
"LabelBooks": "Książki",
"LabelButtonText": "Button Text",
- "LabelByAuthor": "by {0}",
+ "LabelByAuthor": "autorstwa {0}",
"LabelChangePassword": "Zmień hasło",
"LabelChannels": "Kanały",
"LabelChapterTitle": "Tytuł rozdziału",
@@ -247,7 +249,7 @@
"LabelCollections": "Kolekcje",
"LabelComplete": "Ukończone",
"LabelConfirmPassword": "Potwierdź hasło",
- "LabelContinueListening": "Kontynuuj odtwarzanie",
+ "LabelContinueListening": "Kontynuuj słuchanie",
"LabelContinueReading": "Kontynuuj czytanie",
"LabelContinueSeries": "Kontynuuj serię",
"LabelCover": "Okładka",
@@ -319,6 +321,7 @@
"LabelHardDeleteFile": "Usuń trwale plik",
"LabelHasEbook": "Ma ebooka",
"LabelHasSupplementaryEbook": "Posiada dodatkowy ebook",
+ "LabelHideSubtitles": "Ukryj napisy",
"LabelHighestPriority": "Najwyższy priorytet",
"LabelHost": "Host",
"LabelHour": "Godzina",
@@ -413,7 +416,7 @@
"LabelOverwrite": "Nadpisz",
"LabelPassword": "Hasło",
"LabelPath": "Ścieżka",
- "LabelPermanent": "Trwały",
+ "LabelPermanent": "Stałe",
"LabelPermissionsAccessAllLibraries": "Ma dostęp do wszystkich bibliotek",
"LabelPermissionsAccessAllTags": "Ma dostęp do wszystkich tagów",
"LabelPermissionsAccessExplicitContent": "Ma dostęp do treści oznacznych jako nieprzyzwoite",
@@ -446,6 +449,7 @@
"LabelRSSFeedPreventIndexing": "Zapobiegaj indeksowaniu",
"LabelRSSFeedSlug": "RSS Feed Slug",
"LabelRSSFeedURL": "URL kanały RSS",
+ "LabelReAddSeriesToContinueListening": "Ponownie Dodaj Serię do sekcji Kontunuuj Odtwarzanie",
"LabelRead": "Czytaj",
"LabelReadAgain": "Czytaj ponownie",
"LabelReadEbookWithoutProgress": "Czytaj książkę bez zapamiętywania postępu",
@@ -516,6 +520,7 @@
"LabelShareURL": "Link do udziału",
"LabelShowAll": "Pokaż wszystko",
"LabelShowSeconds": "Pokaż sekundy",
+ "LabelShowSubtitles": "Pokaż Napisy",
"LabelSize": "Rozmiar",
"LabelSleepTimer": "Wyłącznik czasowy",
"LabelSlug": "Slug",
@@ -534,10 +539,10 @@
"LabelStatsItemsFinished": "Pozycje zakończone",
"LabelStatsItemsInLibrary": "Pozycje w bibliotece",
"LabelStatsMinutes": "Minuty",
- "LabelStatsMinutesListening": "Minuty odtwarzania",
+ "LabelStatsMinutesListening": "Minuty słuchania",
"LabelStatsOverallDays": "Całkowity czas (dni)",
"LabelStatsOverallHours": "Całkowity czas (godziny)",
- "LabelStatsWeekListening": "Tydzień odtwarzania",
+ "LabelStatsWeekListening": "Tydzień słuchania",
"LabelSubtitle": "Podtytuł",
"LabelSupportedFileTypes": "Obsługiwane typy plików",
"LabelTag": "Tag",
@@ -592,6 +597,7 @@
"LabelVersion": "Wersja",
"LabelViewBookmarks": "Wyświetlaj zakładki",
"LabelViewChapters": "Wyświetlaj rozdziały",
+ "LabelViewPlayerSettings": "Zobacz ustawienia odtwarzacza",
"LabelViewQueue": "Wyświetlaj kolejkę odtwarzania",
"LabelVolume": "Głośność",
"LabelWeekdaysToRun": "Dni tygodnia",
@@ -642,7 +648,7 @@
"MessageConfirmRemoveEpisodes": "Czy na pewno chcesz usunąć {0} odcinki?",
"MessageConfirmRemoveListeningSessions": "Czy na pewno chcesz usunąć {0} sesji słuchania?",
"MessageConfirmRemoveNarrator": "Are you sure you want to remove narrator \"{0}\"?",
- "MessageConfirmRemovePlaylist": "Are you sure you want to remove your playlist \"{0}\"?",
+ "MessageConfirmRemovePlaylist": "Czy jesteś pewien, że chcesz usunąć twoją playlistę \"{0}\"?",
"MessageConfirmRenameGenre": "Are you sure you want to rename genre \"{0}\" to \"{1}\" for all items?",
"MessageConfirmRenameGenreMergeNote": "Note: This genre already exists so they will be merged.",
"MessageConfirmRenameGenreWarning": "Warning! A similar genre with a different casing already exists \"{0}\".",
@@ -663,7 +669,7 @@
"MessageItemsSelected": "{0} zaznaczone elementy",
"MessageItemsUpdated": "{0} Items Updated",
"MessageJoinUsOn": "Dołącz do nas na",
- "MessageListeningSessionsInTheLastYear": "{0} sesje odsłuchowe w ostatnim roku",
+ "MessageListeningSessionsInTheLastYear": "Sesje słuchania w ostatnim roku: {0}",
"MessageLoading": "Ładowanie...",
"MessageLoadingFolders": "Ładowanie folderów...",
"MessageLogsDescription": "Logi zapisane są w /metadata/logs
jako pliki JSON. Logi awaryjne są zapisane w /metadata/logs/crash_logs.txt
.",
@@ -692,7 +698,7 @@
"MessageNoIssues": "Brak problemów",
"MessageNoItems": "Brak elementów",
"MessageNoItemsFound": "Nie znaleziono żadnych elementów",
- "MessageNoListeningSessions": "Brak sesji odtwarzania",
+ "MessageNoListeningSessions": "Brak sesji słuchania",
"MessageNoLogs": "Brak logów",
"MessageNoMediaProgress": "Brak postępu",
"MessageNoNotifications": "Brak powiadomień",
@@ -709,7 +715,7 @@
"MessageOr": "lub",
"MessagePauseChapter": "Zatrzymaj odtwarzanie rozdziały",
"MessagePlayChapter": "Rozpocznij odtwarzanie od początku rozdziału",
- "MessagePlaylistCreateFromCollection": "Utwórz listę odtwarznia na podstawie kolekcji",
+ "MessagePlaylistCreateFromCollection": "Utwórz listę odtwarzania na podstawie kolekcji",
"MessagePodcastHasNoRSSFeedForMatching": "Podcast nie ma adresu url kanału RSS, który mógłby zostać użyty do dopasowania",
"MessageQuickMatchDescription": "Wypełnij puste informacje i okładkę pierwszym wynikiem dopasowania z '{0}'. Nie nadpisuje szczegółów, chyba że włączone jest ustawienie serwera 'Preferuj dopasowane metadane'.",
"MessageRemoveChapter": "Usuń rozdział",
@@ -724,8 +730,9 @@
"MessageSelected": "{0} wybranych",
"MessageServerCouldNotBeReached": "Nie udało się uzyskać połączenia z serwerem",
"MessageSetChaptersFromTracksDescription": "Set chapters using each audio file as a chapter and chapter title as the audio file name",
+ "MessageShareExpirationWillBe": "Czas udostępniania {0}",
"MessageShareExpiresIn": "Wygaśnie za {0}",
- "MessageShareURLWillBe": "URL udziału będzie {0}",
+ "MessageShareURLWillBe": "Udostępnione pod linkiem {0}",
"MessageStartPlaybackAtTime": "Rozpoczęcie odtwarzania \"{0}\" od {1}?",
"MessageThinking": "Myślę...",
"MessageUploaderItemFailed": "Nie udało się przesłać",
@@ -746,7 +753,7 @@
"NoteUploaderUnsupportedFiles": "Nieobsługiwane pliki są ignorowane. Podczas dodawania folderu, inne pliki, które nie znajdują się w folderze elementu, są ignorowane.",
"PlaceholderNewCollection": "Nowa nazwa kolekcji",
"PlaceholderNewFolderPath": "Nowa ścieżka folderu",
- "PlaceholderNewPlaylist": "New playlist name",
+ "PlaceholderNewPlaylist": "Nowa nazwa playlisty",
"PlaceholderSearch": "Szukanie..",
"PlaceholderSearchEpisode": "Szukanie odcinka..",
"ToastAccountUpdateFailed": "Nie udało się zaktualizować konta",
@@ -802,12 +809,12 @@
"ToastLibraryScanStarted": "Rozpoczęto skanowanie biblioteki",
"ToastLibraryUpdateFailed": "Nie udało się zaktualizować biblioteki",
"ToastLibraryUpdateSuccess": "Zaktualizowano \"{0}\" pozycji",
- "ToastPlaylistCreateFailed": "Failed to create playlist",
- "ToastPlaylistCreateSuccess": "Playlist created",
- "ToastPlaylistRemoveFailed": "Failed to remove playlist",
- "ToastPlaylistRemoveSuccess": "Playlist removed",
- "ToastPlaylistUpdateFailed": "Failed to update playlist",
- "ToastPlaylistUpdateSuccess": "Playlist updated",
+ "ToastPlaylistCreateFailed": "Nie udało się utworzyć playlisty",
+ "ToastPlaylistCreateSuccess": "Playlista utworzona",
+ "ToastPlaylistRemoveFailed": "Nie udało się usunąć playlisty",
+ "ToastPlaylistRemoveSuccess": "Playlista usunięta",
+ "ToastPlaylistUpdateFailed": "Nie udało się zaktualizować playlisty",
+ "ToastPlaylistUpdateSuccess": "Playlista zaktualizowana",
"ToastPodcastCreateFailed": "Nie udało się utworzyć podcastu",
"ToastPodcastCreateSuccess": "Podcast został pomyślnie utworzony",
"ToastRSSFeedCloseFailed": "Zamknięcie kanału RSS nie powiodło się",
diff --git a/docs/controllers/PodcastController.yaml b/docs/controllers/PodcastController.yaml
index a289eae387..a410eed6fd 100644
--- a/docs/controllers/PodcastController.yaml
+++ b/docs/controllers/PodcastController.yaml
@@ -58,14 +58,14 @@ paths:
404:
description: Not found
- /api/podcasts/opml:
+ /api/podcasts/opml/parse:
post:
summary: Get feeds from OPML text
+ description: Parse OPML text and return an array of feeds
operationId: getFeedsFromOPMLText
tags:
- Podcasts
requestBody:
- required: true
content:
application/json:
schema:
@@ -73,20 +73,58 @@ paths:
properties:
opmlText:
type: string
- description: The OPML text containing podcast feeds
responses:
- 200:
- description: Successfully retrieved feeds from OPML text
+ '200':
+ description: Successfully parsed OPML text and returned feeds
content:
application/json:
schema:
- type: array
- items:
- $ref: '../objects/mediaTypes/Podcast.yaml#/components/schemas/Podcast'
- 400:
- description: Bad request
- 403:
- description: Forbidden
+ type: object
+ properties:
+ feeds:
+ type: array
+ items:
+ type: object
+ properties:
+ title:
+ type: string
+ feedUrl:
+ type: string
+ '400':
+ description: Bad request, OPML text not provided
+ '403':
+ description: Forbidden, user is not admin
+ /api/podcasts/opml/create:
+ post:
+ summary: Bulk create podcasts from OPML feed URLs
+ operationId: bulkCreatePodcastsFromOpmlFeedUrls
+ tags:
+ - Podcasts
+ requestBody:
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ feeds:
+ type: array
+ items:
+ type: string
+ libraryId:
+ $ref: '../objects/Library.yaml#/components/schemas/libraryId'
+ folderId:
+ $ref: '../objects/Folder.yaml#/components/schemas/folderId'
+ autoDownloadEpisodes:
+ $ref: '../objects/mediaTypes/Podcast.yaml#/components/schemas/autoDownloadEpisodes'
+ responses:
+ '200':
+ description: Successfully created podcasts from feed URLs
+ '400':
+ description: Bad request, invalid request body
+ '403':
+ description: Forbidden, user is not admin
+ '404':
+ description: Folder not found
/api/podcasts/{id}/checknew:
parameters:
diff --git a/docs/objects/mediaTypes/Podcast.yaml b/docs/objects/mediaTypes/Podcast.yaml
index 8cc351bf17..df915fe07a 100644
--- a/docs/objects/mediaTypes/Podcast.yaml
+++ b/docs/objects/mediaTypes/Podcast.yaml
@@ -11,6 +11,9 @@ components:
nullable: true
format: 'pod_[a-z0-9]{18}'
example: pod_o78uaoeuh78h6aoeif
+ autoDownloadEpisodes:
+ type: boolean
+ description: Whether episodes are automatically downloaded.
Podcast:
type: object
@@ -37,8 +40,7 @@ components:
items:
$ref: '../entities/PodcastEpisode.yaml#/components/schemas/PodcastEpisode'
autoDownloadEpisodes:
- type: boolean
- description: Whether episodes are automatically downloaded.
+ $ref: '#/components/schemas/autoDownloadEpisodes'
autoDownloadSchedule:
type: string
description: The schedule for automatic episode downloads, in cron format.
diff --git a/docs/openapi.json b/docs/openapi.json
index 38e37ee0b7..9767f57960 100644
--- a/docs/openapi.json
+++ b/docs/openapi.json
@@ -1589,23 +1589,22 @@
}
}
},
- "/api/podcasts/opml": {
+ "/api/podcasts/opml/parse": {
"post": {
"summary": "Get feeds from OPML text",
+ "description": "Parse OPML text and return an array of feeds",
"operationId": "getFeedsFromOPMLText",
"tags": [
"Podcasts"
],
"requestBody": {
- "required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"opmlText": {
- "type": "string",
- "description": "The OPML text containing podcast feeds"
+ "type": "string"
}
}
}
@@ -1614,23 +1613,85 @@
},
"responses": {
"200": {
- "description": "Successfully retrieved feeds from OPML text",
+ "description": "Successfully parsed OPML text and returned feeds",
"content": {
"application/json": {
"schema": {
- "type": "array",
- "items": {
- "$ref": "#/components/schemas/Podcast"
+ "type": "object",
+ "properties": {
+ "feeds": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "title": {
+ "type": "string"
+ },
+ "feedUrl": {
+ "type": "string"
+ }
+ }
+ }
+ }
}
}
}
}
},
"400": {
- "description": "Bad request"
+ "description": "Bad request, OPML text not provided"
},
"403": {
- "description": "Forbidden"
+ "description": "Forbidden, user is not admin"
+ }
+ }
+ }
+ },
+ "/api/podcasts/opml/create": {
+ "post": {
+ "summary": "Bulk create podcasts from OPML feed URLs",
+ "operationId": "bulkCreatePodcastsFromOpmlFeedUrls",
+ "tags": [
+ "Podcasts"
+ ],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "feeds": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "libraryId": {
+ "$ref": "#/components/schemas/libraryId"
+ },
+ "folderId": {
+ "$ref": "#/components/schemas/folderId"
+ },
+ "autoDownloadEpisodes": {
+ "$ref": "#/components/schemas/autoDownloadEpisodes"
+ }
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successfully created podcasts from feed URLs"
+ },
+ "400": {
+ "description": "Bad request, invalid request body"
+ },
+ "403": {
+ "description": "Forbidden, user is not admin"
+ },
+ "404": {
+ "description": "Folder not found"
}
}
}
@@ -3856,6 +3917,10 @@
}
}
},
+ "autoDownloadEpisodes": {
+ "type": "boolean",
+ "description": "Whether episodes are automatically downloaded."
+ },
"Podcast": {
"type": "object",
"description": "A podcast containing multiple episodes.",
@@ -3889,8 +3954,7 @@
}
},
"autoDownloadEpisodes": {
- "type": "boolean",
- "description": "Whether episodes are automatically downloaded."
+ "$ref": "#/components/schemas/autoDownloadEpisodes"
},
"autoDownloadSchedule": {
"type": "string",
diff --git a/docs/root.yaml b/docs/root.yaml
index f4ceee509f..4d6c055db7 100644
--- a/docs/root.yaml
+++ b/docs/root.yaml
@@ -57,8 +57,10 @@ paths:
$ref: './controllers/PodcastController.yaml#/paths/~1api~1podcasts'
/api/podcasts/feed:
$ref: './controllers/PodcastController.yaml#/paths/~1api~1podcasts~1feed'
- /api/podcasts/opml:
- $ref: './controllers/PodcastController.yaml#/paths/~1api~1podcasts~1opml'
+ /api/podcasts/opml/parse:
+ $ref: './controllers/PodcastController.yaml#/paths/~1api~1podcasts~1opml~1parse'
+ /api/podcasts/opml/create:
+ $ref: './controllers/PodcastController.yaml#/paths/~1api~1podcasts~1opml~1create'
/api/podcasts/{id}/checknew:
$ref: './controllers/PodcastController.yaml#/paths/~1api~1podcasts~1{id}~1checknew'
/api/podcasts/{id}/clear-queue:
diff --git a/readme.md b/readme.md
index 0ff5541e30..ce2781ccd6 100644
--- a/readme.md
+++ b/readme.md
@@ -39,7 +39,7 @@ Audiobookshelf is a self-hosted audiobook and podcast server.
Is there a feature you are looking for? [Suggest it](https://github.com/advplyr/audiobookshelf/issues/new/choose)
-Join us on [Discord](https://discord.gg/HQgCbd6E75) or [Matrix](https://matrix.to/#/#audiobookshelf:matrix.org)
+Join us on [Discord](https://discord.gg/HQgCbd6E75)
### Android App (beta)
@@ -47,7 +47,7 @@ Try it out on the [Google Play Store](https://play.google.com/store/apps/details
### iOS App (beta)
-**Beta is currently full. Apple has a hard limit of 10k beta testers. Updates will be posted in Discord/Matrix.**
+**Beta is currently full. Apple has a hard limit of 10k beta testers. Updates will be posted in Discord.**
Using Test Flight: https://testflight.apple.com/join/wiic7QIW **_(beta is full)_**
diff --git a/server/Database.js b/server/Database.js
index 1274bb4bdb..935ddc200d 100644
--- a/server/Database.js
+++ b/server/Database.js
@@ -207,6 +207,7 @@ class Database {
try {
await this.sequelize.authenticate()
+ await this.loadExtensions([process.env.SQLEAN_UNICODE_PATH])
Logger.info(`[Database] Db connection was successful`)
return true
} catch (error) {
@@ -215,6 +216,34 @@ class Database {
}
}
+ /**
+ *
+ * @param {string[]} extensions paths to extension binaries
+ */
+ async loadExtensions(extensions) {
+ // This is a hack to get the db connection for loading extensions.
+ // The proper way would be to use the 'afterConnect' hook, but that hook is never called for sqlite due to a bug in sequelize.
+ // See https://github.com/sequelize/sequelize/issues/12487
+ // This is not a public API and may break in the future.
+ const db = await this.sequelize.dialect.connectionManager.getConnection()
+ if (typeof db?.loadExtension !== 'function') throw new Error('Failed to get db connection for loading extensions')
+
+ for (const ext of extensions) {
+ Logger.info(`[Database] Loading extension ${ext}`)
+ await new Promise((resolve, reject) => {
+ db.loadExtension(ext, (err) => {
+ if (err) {
+ Logger.error(`[Database] Failed to load extension ${ext}`, err)
+ reject(err)
+ return
+ }
+ Logger.info(`[Database] Successfully loaded extension ${ext}`)
+ resolve()
+ })
+ })
+ }
+ }
+
/**
* Disconnect from db
*/
@@ -801,6 +830,39 @@ class Database {
Logger.warn(`Removed ${badSessionsRemoved} sessions that were 3 seconds or less`)
}
}
+
+ /**
+ *
+ * @param {string} value
+ * @returns {string}
+ */
+ normalize(value) {
+ return `lower(unaccent(${value}))`
+ }
+
+ /**
+ *
+ * @param {string} query
+ * @returns {Promise