Merge branch 'advplyr:master' into master

This commit is contained in:
Dmitry 2023-02-03 23:09:09 +03:00 committed by GitHub
commit 96a8e74d38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 336 additions and 286 deletions

View File

@ -1,6 +1,6 @@
<template>
<div>
<app-settings-content :header-text="'Item Metadata Utils'">
<app-settings-content :header-text="$strings.HeaderItemMetadataUtils">
<nuxt-link to="/config/item-metadata-utils/tags" class="block w-full rounded bg-primary/40 hover:bg-primary/60 text-gray-300 hover:text-white p-4 mt-6 mb-2">
<div class="flex justify-between">
<p>{{ $strings.HeaderManageTags }}</p>

View File

@ -1,70 +1,70 @@
{
"ButtonAdd": "Ajouter",
"ButtonAddChapters": "Ajouter Chapitre",
"ButtonAddPodcasts": "Ajouter Podcasts",
"ButtonAddYourFirstLibrary": "Ajouter votre Première Bibliothèque",
"ButtonAddChapters": "Ajouter le chapitre",
"ButtonAddPodcasts": "Ajouter des podcasts",
"ButtonAddYourFirstLibrary": "Ajouter votre première bibliothèque",
"ButtonApply": "Appliquer",
"ButtonApplyChapters": "Appliquer les Chapitres",
"ButtonApplyChapters": "Appliquer les chapitres",
"ButtonAuthors": "Auteurs",
"ButtonBrowseForFolder": "Naviguer vers le Répertoire",
"ButtonBrowseForFolder": "Naviguer vers le répertoire",
"ButtonCancel": "Annuler",
"ButtonCancelEncode": "Annuler l'encodage",
"ButtonChangeRootPassword": "Changer le mot de passe Administrateur",
"ButtonCheckAndDownloadNewEpisodes": "Vérifier & Télécharger de Nouveaux Episodes",
"ButtonChooseAFolder": "Choisir un Dossier",
"ButtonChooseFiles": "Choisir les Fichiers",
"ButtonClearFilter": "Effacer le Filtre",
"ButtonCloseFeed": "Fermer le Flux",
"ButtonCheckAndDownloadNewEpisodes": "Vérifier & télécharger de nouveaux épisodes",
"ButtonChooseAFolder": "Choisir un dossier",
"ButtonChooseFiles": "Choisir les fichiers",
"ButtonClearFilter": "Effacer le filtre",
"ButtonCloseFeed": "Fermer le flux",
"ButtonCollections": "Collections",
"ButtonConfigureScanner": "Configurer le Scan",
"ButtonConfigureScanner": "Configurer l'analyse",
"ButtonCreate": "Créer",
"ButtonCreateBackup": "Créer une Sauvegarde",
"ButtonCreateBackup": "Créer une sauvegarde",
"ButtonDelete": "Effacer",
"ButtonEdit": "Editer",
"ButtonEditChapters": "Editer Chapitre",
"ButtonEditPodcast": "Editer Podcast",
"ButtonForceReScan": "Forcer un Re-Scan",
"ButtonFullPath": "Chemin Complet",
"ButtonEdit": "Modifier",
"ButtonEditChapters": "Modifier les chapitres",
"ButtonEditPodcast": "Modifier les podcasts",
"ButtonForceReScan": "Forcer une nouvelle analyse",
"ButtonFullPath": "Chemin complet",
"ButtonHide": "Cacher",
"ButtonHome": "Accueil",
"ButtonIssues": "Parutions",
"ButtonLatest": "Dernière Version",
"ButtonLatest": "Dernière version",
"ButtonLibrary": "Bibliothèque",
"ButtonLogout": "Se Déconnecter",
"ButtonLookup": "Rechercher",
"ButtonManageTracks": "Gérer les pistes",
"ButtonMapChapterTitles": "Correspondance des titres de chapitres",
"ButtonMatchAllAuthors": "Rechercher tous les Auteurs",
"ButtonMatchAllAuthors": "Rechercher tous les auteurs",
"ButtonMatchBooks": "Rechercher les Livres",
"ButtonNevermind": "Oubliez cela",
"ButtonOk": "Ok",
"ButtonOpenFeed": "Ouvrir le Flux",
"ButtonOpenManager": "Ouvrir le Gestionnaire",
"ButtonPlay": "Ecouter",
"ButtonPlaying": "En Lecture",
"ButtonPlaylists": "Listes de Lecture",
"ButtonPurgeAllCache": "Purger Tout le Cache",
"ButtonPurgeItemsCache": "Purger le Cache des Articles",
"ButtonPurgeMediaProgress": "Purger la Progression des Médias",
"ButtonQueueAddItem": "Ajouter à la Liste de Lecture",
"ButtonQueueRemoveItem": "Supprimer de la Liste de Lecture",
"ButtonQuickMatch": "Recherche Rapide",
"ButtonPlay": "Écouter",
"ButtonPlaying": "En lecture",
"ButtonPlaylists": "Listes de lecture",
"ButtonPurgeAllCache": "Purger le cache",
"ButtonPurgeItemsCache": "Purger le cache des articles",
"ButtonPurgeMediaProgress": "Purger la progression des médias",
"ButtonQueueAddItem": "Ajouter à la liste de lecture",
"ButtonQueueRemoveItem": "Supprimer de la liste de lecture",
"ButtonQuickMatch": "Recherche rapide",
"ButtonRead": "Lire",
"ButtonRemove": "Supprimer",
"ButtonRemoveAll": "Supprimer tout",
"ButtonRemoveAllLibraryItems": "Supprimer tous les Articles de la Bibliothèque",
"ButtonRemoveAllLibraryItems": "Supprimer tous les articles de la bibliothèque",
"ButtonRemoveFromContinueListening": "Ne plus continuer à écouter",
"ButtonRemoveSeriesFromContinueSeries": "Ne plus continuer à écouter la Série",
"ButtonReScan": "Re-Scan",
"ButtonRemoveSeriesFromContinueSeries": "Ne plus continuer à écouter la série",
"ButtonReScan": "Nouvelle analyse",
"ButtonReset": "Réinitialiser",
"ButtonRestore": "Rétablir",
"ButtonSave": "Sauvegarder",
"ButtonSaveAndClose": "Sauvegarder & Fermer",
"ButtonSaveTracklist": "Sauvegarder la liste de lecture",
"ButtonScan": "Scanner",
"ButtonScanLibrary": "Scanner la Bibliothèque",
"ButtonScan": "Analyser",
"ButtonScanLibrary": "Analyser la bibliothèque",
"ButtonSearch": "Rechercher",
"ButtonSelectFolderPath": "Sélectionner le Chemin du Dossier",
"ButtonSelectFolderPath": "Sélectionner le Chemin du dossier",
"ButtonSeries": "Séries",
"ButtonSetChaptersFromTracks": "Positionner les Chapitre par rapports aux Pistes",
"ButtonShiftTimes": "Décaler le Temps",
@ -78,88 +78,88 @@
"ButtonUploadOPMLFile": "Téléverser un Fichier OPML",
"ButtonUserDelete": "Effacer l'utilisateur {0}",
"ButtonUserEdit": "Modifier l'utilisateur {0}",
"ButtonViewAll": "Afficher Tout",
"ButtonViewAll": "Afficher tout",
"ButtonYes": "Oui",
"HeaderAccount": "Compte",
"HeaderAdvanced": "Avancé",
"HeaderAppriseNotificationSettings": "Configuration des Notifications Apprise",
"HeaderAudiobookTools": "Outils de Gestion de Fichier Audiobook",
"HeaderAudioTracks": "Pistes Audio",
"HeaderAudioTracks": "Pistes zudio",
"HeaderBackups": "Sauvegardes",
"HeaderChangePassword": "Chager le mot de passe",
"HeaderChapters": "Chapitres",
"HeaderChooseAFolder": "Choisir un Dossier",
"HeaderChooseAFolder": "Choisir un dossier",
"HeaderCollection": "Collection",
"HeaderCollectionItems": "Entrées de la Collection",
"HeaderCover": "Couverture",
"HeaderDetails": "Détails",
"HeaderEpisodes": "Episodes",
"HeaderEpisodes": "Épisodes",
"HeaderFiles": "Fichiers",
"HeaderFindChapters": "Trouver les Chapitres",
"HeaderFindChapters": "Trouver les chapitres",
"HeaderIgnoredFiles": "Fichiers Ignorés",
"HeaderItemFiles": "Fichiers des Articles",
"HeaderItemMetadataUtils": "Outils de Gestion des Métadonnées",
"HeaderLastListeningSession": "Dernière Session d'Ecoute",
"HeaderLatestEpisodes": "Dernier Episodes",
"HeaderItemMetadataUtils": "Outils de gestion des métadonnées",
"HeaderLastListeningSession": "Dernière Session d'écoute",
"HeaderLatestEpisodes": "Dernier épisodes",
"HeaderLibraries": "Bibliothèque",
"HeaderLibraryFiles": "Fichier de Bibliothèque",
"HeaderLibraryStats": "Statistiques de Bibliothèque",
"HeaderListeningSessions": "Sessions d'Ecoute",
"HeaderListeningStats": "Statistiques d'Ecoute",
"HeaderLibraryFiles": "Fichier de bibliothèque",
"HeaderLibraryStats": "Statistiques de bibliothèque",
"HeaderListeningSessions": "Sessions d'écoute",
"HeaderListeningStats": "Statistiques d'écoute",
"HeaderLogin": "Connexion",
"HeaderLogs": "Fichiers Journaux",
"HeaderManageGenres": "Gérer les Genres",
"HeaderManageTags": "Gérer les Etiquettes",
"HeaderMapDetails": "Edition en Masse",
"HeaderManageGenres": "Gérer les genres",
"HeaderManageTags": "Gérer les étiquettes",
"HeaderMapDetails": "Édition en Masse",
"HeaderMatch": "Rechercher",
"HeaderMetadataToEmbed": "Métadonnée à Intégrer",
"HeaderNewAccount": "Nouveau Compte",
"HeaderNewLibrary": "Nouvelle Bibliothèque",
"HeaderNotifications": "Notifications",
"HeaderOpenRSSFeed": "Ouvrir Flux RSS",
"HeaderOtherFiles": "Autres Fichiers",
"HeaderOtherFiles": "Autres fichiers",
"HeaderPermissions": "Permissions",
"HeaderPlayerQueue": "Liste d'Ecoute",
"HeaderPlaylist": "Liste de Lecture",
"HeaderPlaylistItems": "Elements de la Liste de Lecture",
"HeaderPodcastsToAdd": "Podcasts à Ajouter",
"HeaderPreviewCover": "Prévisualiser la Couverture",
"HeaderRemoveEpisode": "Supprimer l'Episode",
"HeaderRemoveEpisodes": "Suppression de {0} Episodes",
"HeaderPlayerQueue": "Liste d'écoute",
"HeaderPlaylist": "Liste de lecture",
"HeaderPlaylistItems": "Éléments de la liste de lecture",
"HeaderPodcastsToAdd": "Podcasts à ajouter",
"HeaderPreviewCover": "Prévisualiser la couverture",
"HeaderRemoveEpisode": "Supprimer l'épisode",
"HeaderRemoveEpisodes": "Suppression de {0} épisodes",
"HeaderRSSFeedIsOpen": "Le Flux RSS et Ouvert",
"HeaderSavedMediaProgress": "Progression de la Sauvegarde des Médias",
"HeaderSavedMediaProgress": "Progression de la sauvegarde des médias",
"HeaderSchedule": "Programmation",
"HeaderScheduleLibraryScans": "Scan Automatique de la Bibliothèque",
"HeaderScheduleLibraryScans": "Analyse automatique de la bibliothèque",
"HeaderSession": "Session",
"HeaderSetBackupSchedule": "Activer la Sauvegarde Automatique",
"HeaderSettings": "Paramètres",
"HeaderSettingsDisplay": "Affichage",
"HeaderSettingsExperimental": "Fonctionnalités Expérimentales",
"HeaderSettingsExperimental": "Fonctionnalités expérimentales",
"HeaderSettingsGeneral": "Général",
"HeaderSettingsScanner": "Scanneur",
"HeaderSleepTimer": "Minuterie",
"HeaderStatsLongestItems": "Articles les Plus Long (heures)",
"HeaderStatsMinutesListeningChart": "Minutes d'Ecoute (7 derniers jours)",
"HeaderStatsRecentSessions": "Sessions Récentes",
"HeaderStatsLongestItems": "Articles les plus long (heures)",
"HeaderStatsMinutesListeningChart": "Minutes d'écoute (7 derniers jours)",
"HeaderStatsRecentSessions": "Sessions récentes",
"HeaderStatsTop10Authors": "Top 10 Auteurs",
"HeaderStatsTop5Genres": "Top 5 Genres",
"HeaderTools": "Outils",
"HeaderUpdateAccount": "Mettre à jour le Compte",
"HeaderUpdateAuthor": "Mettre à jour l'Auteur",
"HeaderUpdateDetails": "Mettre à jour les Détails",
"HeaderUpdateLibrary": "Mettre à jour la Bibliothèque",
"HeaderUpdateAccount": "Mettre à jour le compte",
"HeaderUpdateAuthor": "Mettre à jour l'auteur",
"HeaderUpdateDetails": "Mettre à jour les détails",
"HeaderUpdateLibrary": "Mettre à jour la bibliothèque",
"HeaderUsers": "Utilisateurs",
"HeaderYourStats": "Vos Statistiques",
"LabelAccountType": "Type de Compte",
"HeaderYourStats": "Vos statistiques",
"LabelAccountType": "Type de compte",
"LabelAccountTypeAdmin": "Admin",
"LabelAccountTypeGuest": "Invité",
"LabelAccountTypeUser": "Utilisateur",
"LabelActivity": "Activité",
"LabelAddedAt": "Date d'Ajout",
"LabelAddToCollection": "Ajouter à la Collection",
"LabelAddToCollectionBatch": "Ajout de {0} Livres à la Collection",
"LabelAddToPlaylist": "Ajouter à la Liste de Lecture",
"LabelAddToPlaylistBatch": "{0} Elements Ajoutés à la Liste de Lecture",
"LabelAddedAt": "Date d'ajout",
"LabelAddToCollection": "Ajouter à la collection",
"LabelAddToCollectionBatch": "Ajout de {0} livres à la lollection",
"LabelAddToPlaylist": "Ajouter à la liste de lecture",
"LabelAddToPlaylistBatch": "{0} éléments ajoutés à la liste de lecture",
"LabelAll": "Tout",
"LabelAllUsers": "Tous les Utilisateurs",
"LabelAppend": "Ajouter",
@ -167,7 +167,7 @@
"LabelAuthorFirstLast": "Auteur (Prénom Nom)",
"LabelAuthorLastFirst": "Auteur (Nom, Prénom)",
"LabelAuthors": "Auteurs",
"LabelAutoDownloadEpisodes": "Téléchargement Automatique d'Episode",
"LabelAutoDownloadEpisodes": "Téléchargement automatique d'épisode",
"LabelBackToUser": "Revenir à l'Utilisateur",
"LabelBackupsEnableAutomaticBackups": "Activer les Sauvegardes Automatiques",
"LabelBackupsEnableAutomaticBackupsHelp": "Sauvegardes Enregistrées dans /metadata/backups",
@ -176,60 +176,60 @@
"LabelBackupsNumberToKeep": "Nombre de Sauvegardes à maintenir",
"LabelBackupsNumberToKeepHelp": "Une seule sauvegarde sera effacée à la fois. Si vous avez plus de sauvegardes à effacer, vous devrez le faire manuellement.",
"LabelBooks": "Livres",
"LabelChangePassword": "Changer le Mot de Passe",
"LabelChaptersFound": "Chapitres Trouvés",
"LabelChapterTitle": "Titres du Chapitre",
"LabelClosePlayer": "Fermer le Lecteur",
"LabelCollapseSeries": "Réduire les Séries",
"LabelChangePassword": "Changer le mot de passe",
"LabelChaptersFound": "Chapitres trouvés",
"LabelChapterTitle": "Titres du chapitre",
"LabelClosePlayer": "Fermer le lecteur",
"LabelCollapseSeries": "Réduire les séries",
"LabelCollections": "Collections",
"LabelComplete": "Complet",
"LabelConfirmPassword": "Confirmer le Mot de Passe",
"LabelContinueListening": "Continuer la Lecture",
"LabelContinueSeries": "Continuer la Série",
"LabelConfirmPassword": "Confirmer le mot de passe",
"LabelContinueListening": "Continuer la lecture",
"LabelContinueSeries": "Continuer la série",
"LabelCover": "Couverture",
"LabelCoverImageURL": "URL vers l'image de Couverture",
"LabelCoverImageURL": "URL vers l'image de couverture",
"LabelCreatedAt": "Créé le",
"LabelCronExpression": "Expression Cron",
"LabelCurrent": "Courrant",
"LabelCurrently": "En ce Moment:",
"LabelCurrently": "En ce moment :",
"LabelDatetime": "Datetime",
"LabelDescription": "Description",
"LabelDeselectAll": "Tout Déselectionner",
"LabelDevice": "Appareil",
"LabelDeviceInfo": "Détail de l'Appareil",
"LabelDeviceInfo": "Détail de l'appareil",
"LabelDirectory": "Répertoire",
"LabelDiscFromFilename": "Disque depuis le Fichier",
"LabelDiscFromMetadata": "Disque depuis les Métadonnées",
"LabelDiscFromFilename": "Disque depuis le fichier",
"LabelDiscFromMetadata": "Disque depuis les métadonnées",
"LabelDownload": "Téléchargement",
"LabelDuration": "Durée",
"LabelDurationFound": "Durée Trouvée:",
"LabelEdit": "Editer",
"LabelDurationFound": "Durée trouvée :",
"LabelEdit": "Modifier",
"LabelEnable": "Activer",
"LabelEnd": "Fin",
"LabelEpisode": "Episode",
"LabelEpisodeTitle": "Titre de l'Episode",
"LabelEpisodeType": "Type de l'Episode",
"LabelEpisode": "Épisode",
"LabelEpisodeTitle": "Titre de l'épisode",
"LabelEpisodeType": "Type de l'épisode",
"LabelExplicit": "Restriction",
"LabelFeedURL": "URL de Flux",
"LabelFeedURL": "URL deu flux",
"LabelFile": "Fichier",
"LabelFileBirthtime": "Creation du Fichier",
"LabelFileModified": "Modification du Fichier",
"LabelFileBirthtime": "Creation du fichier",
"LabelFileModified": "Modification du fichier",
"LabelFilename": "Nom de Fichier",
"LabelFilterByUser": "Filtrer par l'Utilisateur",
"LabelFindEpisodes": "Trouver des Episodes",
"LabelFilterByUser": "Filtrer par l'utilisateur",
"LabelFindEpisodes": "Trouver des épisodes",
"LabelFinished": "Fini(e)",
"LabelFolder": "Dossier",
"LabelFolders": "Dossiers",
"LabelGenre": "Genre",
"LabelGenres": "Genres",
"LabelHardDeleteFile": "Effacement du Fichier",
"LabelHardDeleteFile": "Suppression du fichier",
"LabelHour": "Heure",
"LabelIcon": "Icone",
"LabelIncludeInTracklist": "Inclure dans la Liste des Pistes",
"LabelIncludeInTracklist": "Inclure dans la liste des pistes",
"LabelIncomplete": "Incomplet",
"LabelInProgress": "En Cours",
"LabelInProgress": "En cours",
"LabelInterval": "Interval",
"LabelIntervalCustomDailyWeekly": "Journalier/Hebdomadaire Personnalisé",
"LabelIntervalCustomDailyWeekly": "Journalier / Hebdomadaire personnalisé",
"LabelIntervalEvery12Hours": "Toutes les 12 heures",
"LabelIntervalEvery15Minutes": "Toutes les 15 minutes",
"LabelIntervalEvery2Hours": "Toutes les 2 heures",
@ -237,46 +237,46 @@
"LabelIntervalEvery6Hours": "Toutes les 6 heures",
"LabelIntervalEveryDay": "Tous les jours",
"LabelIntervalEveryHour": "Toutes les heures",
"LabelInvalidParts": "Parties Invalides",
"LabelInvalidParts": "Parties invalides",
"LabelItem": "Article",
"LabelLanguage": "Langue",
"LabelLanguageDefaultServer": "Langue par Défaut",
"LabelLastSeen": "Vu Dernièrement",
"LabelLanguageDefaultServer": "Langue par défaut",
"LabelLastSeen": "Vu dernièrement",
"LabelLastTime": "Progression",
"LabelLastUpdate": "Dernière Mise à Jour",
"LabelLastUpdate": "Dernière mise à jour",
"LabelLess": "Moins",
"LabelLibrariesAccessibleToUser": "Bibliothèque Accessible à l'Utilisateur",
"LabelLibrariesAccessibleToUser": "Bibliothèque accessible à l'utilisateur",
"LabelLibrary": "Bibliothèque",
"LabelLibraryItem": "Article de Bibliothèque",
"LabelLibraryName": "Nom de Bibliothèque",
"LabelLibraryItem": "Article de bibliothèque",
"LabelLibraryName": "Nom de bibliothèque",
"LabelLimit": "Limite",
"LabelListenAgain": "Ecouter à Nouveau",
"LabelListenAgain": "Écouter à nouveau",
"LabelLogLevelDebug": "Debug",
"LabelLogLevelInfo": "Info",
"LabelLogLevelWarn": "Warn",
"LabelLookForNewEpisodesAfterDate": "Rechercher de Nouveaux Episode après cette Date",
"LabelMediaPlayer": "Lecteur Multimédia",
"LabelMediaType": "Type de Média",
"LabelMetadataProvider": "Fournisseur de Métadonnées",
"LabelMetaTag": "Etiquette de Métadonnée",
"LabelLookForNewEpisodesAfterDate": "Rechercher de nouveaux épisode après cette date",
"LabelMediaPlayer": "Lecteur multimédia",
"LabelMediaType": "Type de média",
"LabelMetadataProvider": "Fournisseur de métadonnées",
"LabelMetaTag": "Etiquette de métadonnée",
"LabelMinute": "Minute",
"LabelMissing": "Manquant",
"LabelMissingParts": "Parties Manquantes",
"LabelMissingParts": "Parties manquantes",
"LabelMore": "Plus",
"LabelName": "Nom",
"LabelNarrator": "Narrateur",
"LabelNarrators": "Narrateurs",
"LabelNew": "Nouveau",
"LabelNewestAuthors": "Nouveaux Auteurs",
"LabelNewestEpisodes": "Derniers Episodes",
"LabelNewPassword": "Nouveau Mot de Passe",
"LabelNewestAuthors": "Nouveaux auteurs",
"LabelNewestEpisodes": "Derniers épisodes",
"LabelNewPassword": "Nouveau mot de passe",
"LabelNotes": "Notes",
"LabelNotFinished": "Non Terminé(e)",
"LabelNotificationAppriseURL": "URL(s) d'Apprise",
"LabelNotificationAvailableVariables": "Variables Disponibles",
"LabelNotFinished": "Non terminé(e)",
"LabelNotificationAppriseURL": "URL(s) d'apprise",
"LabelNotificationAvailableVariables": "Variables disponibles",
"LabelNotificationBodyTemplate": "Modèle de Message",
"LabelNotificationEvent": "Evènement de Notification",
"LabelNotificationsMaxFailedAttempts": "Nombres de Tentatives d'Envoi",
"LabelNotificationsMaxFailedAttempts": "Nombres de tentatives d'envoi",
"LabelNotificationsMaxFailedAttemptsHelp": "La notification est abandonnée une fois ce seuil atteint",
"LabelNotificationsMaxQueueSize": "Nombres de notifications maximum à mettre en attente",
"LabelNotificationsMaxQueueSizeHelp": "La limite de notification est de un évènement par seconde. Le notification seront ignorées si la file d'attente est à son maximum. Cela empêche un flot trop important.",
@ -288,58 +288,58 @@
"LabelOverwrite": "Ecraser",
"LabelPassword": "Mot de Passe",
"LabelPath": "Chemin",
"LabelPermissionsAccessAllLibraries": "Peut Acceder à Toutes les Bibliothèque",
"LabelPermissionsAccessAllTags": "Peut Acceder à Toutes les Etiquettes",
"LabelPermissionsAccessExplicitContent": "Peut Acceter au Contenu Restreint",
"LabelPermissionsDelete": "Peut Supprimer",
"LabelPermissionsDownload": "Peut Télécharger",
"LabelPermissionsUpdate": "Peut Mettre à Jour",
"LabelPermissionsUpload": "Peut Téléverser",
"LabelPhotoPathURL": "Chemin/URL des photos",
"LabelPlaylists": "Listes de Lecture",
"LabelPlayMethod": "Méthode d'Ecoute",
"LabelPermissionsAccessAllLibraries": "Peut accéder à toutes les bibliothèque",
"LabelPermissionsAccessAllTags": "Peut accéder à toutes les étiquettes",
"LabelPermissionsAccessExplicitContent": "Peut acceter au contenu restreint",
"LabelPermissionsDelete": "Peut supprimer",
"LabelPermissionsDownload": "Peut télécharger",
"LabelPermissionsUpdate": "Peut mettre à Jour",
"LabelPermissionsUpload": "Peut téléverser",
"LabelPhotoPathURL": "Chemin / URL des photos",
"LabelPlaylists": "Listes de lecture",
"LabelPlayMethod": "Méthode d'écoute",
"LabelPodcast": "Podcast",
"LabelPodcasts": "Podcasts",
"LabelPrefixesToIgnore": "Préfixes à Ignorer (Insensible à la Casse)",
"LabelProgress": "Progression",
"LabelProvider": "Fournisseur",
"LabelPubDate": "Date de Publication",
"LabelPublisher": "Editeur",
"LabelPublishYear": "Année d'Edition",
"LabelRecentlyAdded": "Derniers Ajouts",
"LabelRecentSeries": "Séries Récentes",
"LabelRecommended": "Recommended",
"LabelPubDate": "Date de publication",
"LabelPublisher": "Éditeur",
"LabelPublishYear": "Année d'édition",
"LabelRecentlyAdded": "Derniers ajouts",
"LabelRecentSeries": "Séries récentes",
"LabelRecommended": "Recommandé",
"LabelRegion": "Région",
"LabelReleaseDate": "Date de Parution",
"LabelRemoveCover": "Supprimer la Couverture",
"LabelRSSFeedOpen": "Flux RSS Ouvert",
"LabelRSSFeedSlug": "Flux RSS Slug",
"LabelRSSFeedURL": "URL du Flux RSS",
"LabelSearchTerm": "Terme de Recherche",
"LabelSearchTitle": "Titre de Recherche",
"LabelSearchTitleOrASIN": "Recherche du Titre ou ASIN",
"LabelReleaseDate": "Date de parution",
"LabelRemoveCover": "Supprimer la couverture",
"LabelRSSFeedOpen": "Flux RSS ouvert",
"LabelRSSFeedSlug": "Identificateur d'adresse du Flux RSS ",
"LabelRSSFeedURL": "Adresse du flux RSS",
"LabelSearchTerm": "Terme de recherche",
"LabelSearchTitle": "Titre de recherche",
"LabelSearchTitleOrASIN": "Recherche du titre ou ASIN",
"LabelSeason": "Saison",
"LabelSequence": "Séquence",
"LabelSeries": "Séries",
"LabelSeriesName": "Nom de la Série",
"LabelSeriesProgress": "Progression de Séries",
"LabelSettingsBookshelfViewHelp": "Design Skeumorphic avec une Etagère en Bois",
"LabelSeriesName": "Nom de la série",
"LabelSeriesProgress": "Progression de séries",
"LabelSettingsBookshelfViewHelp": "Design Skeuomorphic avec une étagère en bois",
"LabelSettingsChromecastSupport": "Support Chromecast",
"LabelSettingsDateFormat": "Format de Date",
"LabelSettingsDisableWatcher": "Désactiver la Surveillance",
"LabelSettingsDisableWatcherForLibrary": "Désactiver la surveillance du dossier pour la Bibliothèque",
"LabelSettingsDateFormat": "Format de date",
"LabelSettingsDisableWatcher": "Désactiver la surveillance",
"LabelSettingsDisableWatcherForLibrary": "Désactiver la surveillance du dossier pour la bibliothèque",
"LabelSettingsDisableWatcherHelp": "Désactive la mise à jour automatique lorsque les fichiers changent. *Nécessite un redémarrage*",
"LabelSettingsEnableEReader": "Active E-reader pour tous les utilisateurs",
"LabelSettingsEnableEReaderHelp": "E-reader est toujours en cours de développement, mais ce paramètre l'active pour tous les utilisateurs (ou utiliser l'interrupteur \"Fonctionnalités Expérimentales\" pour l'activer seulement pour vous)",
"LabelSettingsExperimentalFeatures": "Fonctionnalités Expérimentales",
"LabelSettingsExperimentalFeaturesHelp": "Fonctionnalités en cours de développement sur lesquels nous attendons votre retour et expérience. Cliquer pour ouvrir la discussion Github.",
"LabelSettingsFindCovers": "Rechercher des Couvertures",
"LabelSettingsFindCoversHelp": "Si votre Livre Audio ne possède pas de couverture intégrée ou une image de couverture dans le dossier, le scanner tentera de récupérer une couverture.<br>Attention, cela peut augmenter le temps de scan.",
"LabelSettingsHomePageBookshelfView": "La page d'Accueil utilise la vue étagère",
"LabelSettingsFindCoversHelp": "Si votre livre audio ne possède pas de couverture intégrée ou une image de couverture dans le dossier, l'analyser tentera de récupérer une couverture.<br>Attention, cela peut augmenter le temps d'analyse.",
"LabelSettingsHomePageBookshelfView": "La page d'accueil utilise la vue étagère",
"LabelSettingsLibraryBookshelfView": "La bibliothèque utilise la vue étagère",
"LabelSettingsOverdriveMediaMarkers": "Utiliser Overdrive Media Marker pour les chapitres",
"LabelSettingsOverdriveMediaMarkersHelp": "Les fichiers MP3 d'Overdrive viennent avec les minutages des chapitres intégrés en métadonnées. Activer ce paramètre utilisera ces minutages pour les chapitres automatiquement.",
"LabelSettingsParseSubtitles": "Analyse des Sous-titres",
"LabelSettingsParseSubtitles": "Analyse des sous-titres",
"LabelSettingsParseSubtitlesHelp": "Extrait les sous-titres depuis le dossier du Livre Audio.<br>Les sous-titres doivent être séparés par \" - \"<br>i.e. \"Titre du Livre - Ceci est un sous-titre\" aura le sous-titre \"Ceci est un sous-titre\"",
"LabelSettingsPreferAudioMetadata": "Préférer les Métadonnées Audio",
"LabelSettingsPreferAudioMetadataHelp": "Les méta étiquettes ID3 des fichiers audios seront utilisés à la place des noms de dossier pour les détails du livre audio",
@ -378,12 +378,12 @@
"LabelStatsMinutesListening": "Minutes d'écoute",
"LabelStatsOverallDays": "Jours au total",
"LabelStatsOverallHours": "Heures au total",
"LabelStatsWeekListening": "Ecoute de la Semaine",
"LabelStatsWeekListening": "Écoute de la semaine",
"LabelSubtitle": "Sous-Titre",
"LabelSupportedFileTypes": "Types de Fichiers Supportés",
"LabelTag": "Etiquette",
"LabelTags": "Etiquettes",
"LabelTagsAccessibleToUser": "Etiquettes Accessibles à l'Utilisateur",
"LabelSupportedFileTypes": "Types de fichiers Supportés",
"LabelTag": "Étiquette",
"LabelTags": "Étiquettes",
"LabelTagsAccessibleToUser": "Étiquettes accessibles à l'utilisateur",
"LabelTimeListened": "Temps d'écoute",
"LabelTimeListenedToday": "Nombres d'écoutes Aujourd'hui",
"LabelTimeRemaining": "{0} restantes",
@ -396,9 +396,9 @@
"LabelToolsSplitM4b": "Scinde le fichier M4B en fichiers MP3",
"LabelToolsSplitM4bDescription": "Créer plusieurs fichier MP3 à partir du découpage par chapitre, en incluant les métadonnées, l'image de couverture et les chapitres.",
"LabelTotalDuration": "Durée Totale",
"LabelTotalTimeListened": "Temps d'Ecoute Total",
"LabelTrackFromFilename": "Piste depuis le Fichier",
"LabelTrackFromMetadata": "Piste depuis les Métadonnées",
"LabelTotalTimeListened": "Temps d'écoute total",
"LabelTrackFromFilename": "Piste depuis le fichier",
"LabelTrackFromMetadata": "Piste depuis les métadonnées",
"LabelTracks": "Pistes",
"LabelTracksMultiTrack": "Piste Multiple",
"LabelTracksSingleTrack": "Piste Simple",
@ -409,8 +409,8 @@
"LabelUpdatedAt": "Mis à jour à",
"LabelUpdateDetails": "Mettre à jours les Détails",
"LabelUpdateDetailsHelp": "Autoriser la mise à jour des détails existants lorsqu'une correspondance est trouvée",
"LabelUploaderDragAndDrop": "Glisser & Déposer des Fichiers ou Dossiers",
"LabelUploaderDropFiles": "Déposer des Fichiers",
"LabelUploaderDragAndDrop": "Glisser & Déposer des fichiers ou dossiers",
"LabelUploaderDropFiles": "Déposer des fichiers",
"LabelUseChapterTrack": "Utiliser la Piste du Chapitre",
"LabelUseFullTrack": "Utiliser la Piste Complète",
"LabelUser": "Utilisateur",
@ -419,14 +419,14 @@
"LabelVersion": "Version",
"LabelViewBookmarks": "Afficher les Signets",
"LabelViewChapters": "Afficher les Chapitres",
"LabelViewQueue": "Afficher la Liste de Lecture",
"LabelViewQueue": "Afficher la liste de lecture",
"LabelVolume": "Volume",
"LabelWeekdaysToRun": "Jours de la semaine à exécuter",
"LabelYourAudiobookDuration": "Durée de vos Livres Audios",
"LabelYourBookmarks": "Vos Signets",
"LabelYourPlaylists": "Vos Listes de Lecture",
"LabelYourProgress": "Votre Progression",
"MessageAddToPlayerQueue": "Ajouter en Queue d'Ecoute",
"LabelYourPlaylists": "Vos listes de lecture",
"LabelYourProgress": "Votre progression",
"MessageAddToPlayerQueue": "Ajouter en file d'attente",
"MessageAppriseDescription": "Nécessite une instance d'<a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">API Apprise</a> pour utiliser cette fonctionnalité ou une api qui prend en charge les mêmes requêtes. <br />L'URL de l'API Apprise doit comprendre le chemin complet pour envoyer la notification. Par exemple, si votre instance écoute sur <code>http://192.168.1.1:8337</code> alors vous devez mettre <code>http://192.168.1.1:8337/notify</code>.",
"MessageBackupsDescription": "Les Sauvegardes incluent les utilisateurs, la progression de lecture par utilisateur, les détails des articles des bibliothèques, les paramètres du serveur et les images sauvegardées. Les Sauvegardes n'incluent pas les fichiers de votre bibliothèque.",
"MessageBatchQuickMatchDescription": "La Recherche par Correspondance Rapide tentera d'ajouter les couvertures et les métadonnées manquantes pour les articles sélectionnés. Activer l'option suivante pour autoriser la Recherche par Correspondance à écraser les données existantes.",
@ -440,108 +440,108 @@
"MessageChapterErrorStartLtPrev": "Horodatage invalide car il doit débuter au moins après le précédent chapitre",
"MessageChapterStartIsAfter": "Le Chapitre Début est situé au début de votre Livre Audio",
"MessageCheckingCron": "Vérification du cron...",
"MessageConfirmDeleteBackup": "Etes vous certain de vouloir supprimer la Sauvegarde de {0}?",
"MessageConfirmDeleteLibrary": "Etes vous certain de vouloir supprimer définitivement la bibliothèque \"{0}\"?",
"MessageConfirmDeleteSession": "Etes vous certain de vouloir supprimer cette session?",
"MessageConfirmForceReScan": "Etes vous certain de vouloir lancer une Analyse Forcée?",
"MessageConfirmMarkSeriesFinished": "Etes vous certain de vouloir marquer comme terminé tous les livres de cette série?",
"MessageConfirmMarkSeriesNotFinished": "Etes vous certain de vouloir marquer comme non terminé tous les livres de cette série?",
"MessageConfirmRemoveCollection": "Etes vous certain de vouloir supprimer la collection \"{0}\"?",
"MessageConfirmRemoveEpisode": "Etes vous certain de vouloir supprimer l'épisode \"{0}\"?",
"MessageConfirmRemoveEpisodes": "Etes vous certain de vouloir supprimer {0} épisodes?",
"MessageConfirmRemovePlaylist": "Etes vous certain de vouloir supprimer la liste de lecture \"{0}\"?",
"MessageConfirmRenameGenre": "Etes vous certain de vouloir renommer le genre \"{0}\" vers \"{1}\" pour tous les articles?",
"MessageConfirmDeleteBackup": "Êtes-vous sûr de vouloir supprimer la Sauvegarde de {0} ?",
"MessageConfirmDeleteLibrary": "Êtes-vous sûr de vouloir supprimer définitivement la bibliothèque \"{0}\" ?",
"MessageConfirmDeleteSession": "Êtes-vous sûr de vouloir supprimer cette session ?",
"MessageConfirmForceReScan": "Êtes-vous sûr de vouloir lancer une Analyse Forcée ?",
"MessageConfirmMarkSeriesFinished": "Êtes-vous sûr de vouloir marquer comme terminé tous les livres de cette série ?",
"MessageConfirmMarkSeriesNotFinished": "Êtes-vous sûr de vouloir marquer comme non terminé tous les livres de cette série ?",
"MessageConfirmRemoveCollection": "Êtes-vous sûr de vouloir supprimer la collection \"{0}\" ?",
"MessageConfirmRemoveEpisode": "Êtes-vous sûr de vouloir supprimer l'épisode \"{0}\" ?",
"MessageConfirmRemoveEpisodes": "Êtes-vous sûr de vouloir supprimer {0} épisodes ?",
"MessageConfirmRemovePlaylist": "Êtes-vous sûr de vouloir supprimer la liste de lecture \"{0}\" ?",
"MessageConfirmRenameGenre": "Êtes-vous sûr de vouloir renommer le genre \"{0}\" vers \"{1}\" pour tous les articles ?",
"MessageConfirmRenameGenreMergeNote": "Information: Ce genre existe déjà et sera fusionné.",
"MessageConfirmRenameGenreWarning": "Attention! Un genre similaire avec une casse différente existe déjà \"{0}\".",
"MessageConfirmRenameTag": "Etes vous certain de vouloir renommer l'étiquette \"{0}\" vers \"{1}\" pour tous les articles?",
"MessageConfirmRenameGenreWarning": "Attention ! Un genre similaire avec une casse différente existe déjà \"{0}\".",
"MessageConfirmRenameTag": "Êtes-vous sûr de vouloir renommer l'étiquette \"{0}\" vers \"{1}\" pour tous les articles ?",
"MessageConfirmRenameTagMergeNote": "Information: Cette étiquette existe déjà et sera fusionnée.",
"MessageConfirmRenameTagWarning": "Attention! Une étiquette similaire avec une casse différente existe déjà \"{0}\".",
"MessageConfirmRenameTagWarning": "Attention ! Une étiquette similaire avec une casse différente existe déjà \"{0}\".",
"MessageDownloadingEpisode": "Téléchargement de l'épisode",
"MessageDragFilesIntoTrackOrder": "Faire glisser les fichiers dans l'ordre correct",
"MessageEmbedFinished": "Intégration Terminée!",
"MessageEpisodesQueuedForDownload": "{0} Episode(s) mis en file pour téléchargement",
"MessageEmbedFinished": "Intégration Terminée !",
"MessageEpisodesQueuedForDownload": "{0} épisode(s) mis en file pour téléchargement",
"MessageFeedURLWillBe": "L'URL du Flux sera {0}",
"MessageFetching": "Récupération...",
"MessageForceReScanDescription": "Analysera tous les fichiers de nouveau. Les étiquettes ID3 des fichiers audios, Fichiers OPF, et les fichiers textes seront analysés comme s'ils étaient nouveaux.",
"MessageImportantNotice": "Information Importante!",
"MessageForceReScanDescription": "Analysera tous les fichiers de nouveau. Les étiquettes ID3 des fichiers audios, fichiers OPF, et les fichiers textes seront analysés comme s'ils étaient nouveaux.",
"MessageImportantNotice": "Information Importante !",
"MessageInsertChapterBelow": "Insérer le chapitre ci-dessous",
"MessageItemsSelected": "{0} Articles Sélectionnés",
"MessageItemsUpdated": "{0} Articles Mis à Jour",
"MessageItemsSelected": "{0} articles sélectionnés",
"MessageItemsUpdated": "{0} articles mis à jour",
"MessageJoinUsOn": "Rejoignez-nous sur",
"MessageListeningSessionsInTheLastYear": "{0} sessions d'écoute l'an dernier",
"MessageLoading": "Chargement...",
"MessageLoadingFolders": "Chargement des Dossiers...",
"MessageM4BFailed": "M4B en échec!",
"MessageM4BFinished": "M4B terminé!",
"MessageMapChapterTitles": "Faire correspondre les titres des chapitres aux chapitres existants de votre Livre Audio sans ajuster l'horodatage.",
"MessageMarkAsFinished": "Marquer comme Terminé",
"MessageLoadingFolders": "Chargement des dossiers...",
"MessageM4BFailed": "M4B en échec !",
"MessageM4BFinished": "M4B terminé !",
"MessageMapChapterTitles": "Faire correspondre les titres des chapitres aux chapitres existants de votre livre audio sans ajuster l'horodatage.",
"MessageMarkAsFinished": "Marquer comme terminé",
"MessageMarkAsNotFinished": "Marquer comme non Terminé",
"MessageMatchBooksDescription": "tentera de faire correspondre les livres de la bibliothèque avec les livres du fournisseur sélectionné pour combler les détails et couverture manquants. N'écrase pas les données existantes.",
"MessageNoAudioTracks": "Pas de pistes audio",
"MessageNoAuthors": "Pas d'Auteurs",
"MessageNoBackups": "Pas de Sauvegardes",
"MessageNoBookmarks": "Pas de Signets",
"MessageNoChapters": "Pas de Chapitres",
"MessageNoCollections": "Pas de Collections",
"MessageNoCoversFound": "Pas de Couvertures Trouvées",
"MessageNoDescription": "Pas de Description",
"MessageNoBookmarks": "Pas de signets",
"MessageNoChapters": "Pas de chapitres",
"MessageNoCollections": "Pas de collections",
"MessageNoCoversFound": "Aucune couverture trouvée",
"MessageNoDescription": "Pas de description",
"MessageNoEpisodeMatchesFound": "Pas de correspondance d'épisode trouvée",
"MessageNoEpisodes": "Pas d'Episodes",
"MessageNoFoldersAvailable": "Pas de Dossiers Disponibles",
"MessageNoGenres": "Pas de Genres",
"MessageNoIssues": "Pas de Parution",
"MessageNoEpisodes": "Aucun épisode",
"MessageNoFoldersAvailable": "Aucun dossier disponible",
"MessageNoGenres": "Pas de genres",
"MessageNoIssues": "Pas de parution",
"MessageNoItems": "Pas d'Articles",
"MessageNoItemsFound": "Pas d'Articles Trouvés",
"MessageNoListeningSessions": "Pas de Sessions d'Ecoutes",
"MessageNoLogs": "Pas de Journaux",
"MessageNoListeningSessions": "Pas de sessions d'écoutes",
"MessageNoLogs": "Pas de journaux",
"MessageNoMediaProgress": "Pas de Média en cours",
"MessageNoNotifications": "Pas de Notifications",
"MessageNoPodcastsFound": "Pas de podcasts trouvés",
"MessageNoResults": "Pas de Résultats",
"MessageNoResults": "Pas de résultats",
"MessageNoSearchResultsFor": "Pas de résultats de recherche pour \"{0}\"",
"MessageNoSeries": "Pas de Séries",
"MessageNoTags": "Pas d'Etiquettes",
"MessageNoSeries": "Pas de séries",
"MessageNoTags": "Pas d'étiquettes",
"MessageNotYetImplemented": "Non implémenté",
"MessageNoUpdateNecessary": "Pas de mise à jour nécessaire",
"MessageNoUpdatesWereNecessary": "Aucune mise à jour n'était nécessaire",
"MessageNoUserPlaylists": "Vous n'avez aucune liste de lecture",
"MessageOr": "ou",
"MessagePauseChapter": "Suspendre la lecture du chapitre",
"MessagePlayChapter": "Ecouter depuis le début du chapitre",
"MessagePlayChapter": "Écouter depuis le début du chapitre",
"MessagePlaylistCreateFromCollection": "Créer une liste de lecture depuis la collection",
"MessagePodcastHasNoRSSFeedForMatching": "Le Podcast n'a pas d'URL de flux RSS à utiliser pour la correspondance",
"MessageQuickMatchDescription": "Renseigne les détails manquants ainsi que la couverture avec la première correspondance de '{0}'. N'écrase pas les données présentes à moins que le paramètre 'Préférer les Métadonnées par correspondance' soit activé.",
"MessageRemoveAllItemsWarning": "ATTENTION! Cette action supprimera toute la base de données de la bibliothèque ainsi que les mises à jour ou correspondances qui auraient été effectuées. Cela n'a aucune incidence sur les fichiers de la bibliothèque. Voulez-vous continuer?",
"MessageRemoveAllItemsWarning": "ATTENTION ! Cette action supprimera toute la base de données de la bibliothèque ainsi que les mises à jour ou correspondances qui auraient été effectuées. Cela n'a aucune incidence sur les fichiers de la bibliothèque. Souhaitez-vous continuer ?",
"MessageRemoveChapter": "Supprimer le chapitre",
"MessageRemoveEpisodes": "Suppression de {0} épisode(s)",
"MessageRemoveFromPlayerQueue": "Supprimer de la liste d'écoute",
"MessageRemoveUserWarning": "Etes-vous certain de vouloir supprimer définitivement l'utilisateur \"{0}\"?",
"MessageRemoveUserWarning": "Êtes-vous certain de vouloir supprimer définitivement l'utilisateur \"{0}\" ?",
"MessageReportBugsAndContribute": "Remonter des anomalies, demander des fonctionnalités et contribuer sur",
"MessageResetChaptersConfirm": "Etes-vous certain de vouloir réinitialiser les chapitres et annuler les changements effectués?",
"MessageRestoreBackupConfirm": "Etes-vous certain de vouloir restaurer la sauvegarde créée le",
"MessageResetChaptersConfirm": "Êtes-vous certain de vouloir réinitialiser les chapitres et annuler les changements effectués ?",
"MessageRestoreBackupConfirm": "Êtes-vous certain de vouloir restaurer la sauvegarde créée le",
"MessageRestoreBackupWarning": "Restaurer la sauvegarde écrasera la base de donnée située dans le dossier /config ainsi que les images sur /metadata/items & /metadata/authors.<br /><br />Les sauvegardes ne touchent pas aux fichiers de la bibliothèque. Si vous avez activé le paramètre pour sauvegarder les métadonnées et les images de couverture dans le même dossier que les fichiers, ceux-ci ne ni sauvegardés, ni écrasés lors de la restauration.<br /><br />Tous les clients utilisant votre serveur seront automatiquement mis à jour.",
"MessageSearchResultsFor": "Résultats de recherche pour",
"MessageServerCouldNotBeReached": "Serveur inaccessible",
"MessageSetChaptersFromTracksDescription": "Positionne un chapitre par fichier audio, avec le titre du fichier comme titre de chapitre",
"MessageStartPlaybackAtTime": "Démarrer la lecture pour \"{0}\" à {1}?",
"MessageStartPlaybackAtTime": "Démarrer la lecture pour \"{0}\" à {1} ?",
"MessageThinking": "On réfléchit...",
"MessageUploaderItemFailed": "Échec du téléversement",
"MessageUploaderItemSuccess": "Téléversement effectué!",
"MessageUploaderItemSuccess": "Téléversement effectué !",
"MessageUploading": "Téléversement...",
"MessageValidCronExpression": "Expression cron valide",
"MessageWatcherIsDisabledGlobally": "La Surveillance est désactivée par un paramètre global du serveur",
"MessageXLibraryIsEmpty": "La Bibliothèque {0} est vide!",
"MessageXLibraryIsEmpty": "La bibliothèque {0} est vide !",
"MessageYourAudiobookDurationIsLonger": "La durée de votre Livre Audio est plus longue que la durée trouvée",
"MessageYourAudiobookDurationIsShorter": "La durée de votre Livre Audio est plus courte que la durée trouvée",
"NoteChangeRootPassword": "L'utilisateur Root est le seul a pouvoir utiliser un mote de passe vide",
"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",
"NoteFolderPickerDebian": "Information: La sélection de dossier sur une installation debian n'est pas finalisée. Merci de renseigner le chemin complet vers votre bibliothèque manuellement.",
"NoteRSSFeedPodcastAppsHttps": "Attention: La majorité des application de podcast nécessite une URL de flux en HTTPS.",
"NoteRSSFeedPodcastAppsPubDate": "Warning: Un ou plus de vos épisodes ne possèdent pas de Pub Date. Certaines applications de podcast le requièrent.",
"NoteUploaderFoldersWithMediaFiles": "Les dossiers avec des fichiers médias seront traités en tant qu'articles séparés.",
"NoteUploaderOnlyAudioFiles": "En téléversant seulement des fichiers audio, chaque fichier sera traité comme un Livre Audio séparé.",
"NoteUploaderUnsupportedFiles": "Les fichiers non-supportés seront ignorés. En sélectionnant ou déponsant un dossier, les autres fichiers qui ne sont pas un dossier contenant un article seront ignorés.",
"NoteRSSFeedPodcastAppsHttps": "Attention : la majorité des application de podcast nécessite une adresse de flux en HTTPS.",
"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éverser 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.",
"PlaceholderNewCollection": "Nom de la nouvelle collection",
"PlaceholderNewFolderPath": "Nouveau chemin de dossier",
"PlaceholderNewPlaylist": "Nouveau nom de liste de lecture",
@ -615,4 +615,4 @@
"ToastSocketFailedToConnect": "Échec de la connexion WebSocket",
"ToastUserDeleteFailed": "Échec de la suppression de l'utilisateur",
"ToastUserDeleteSuccess": "Utilisateur supprimé"
}
}

View File

@ -89,8 +89,11 @@ class SessionController {
}
async middleware(req, res, next) {
var playbackSession = await this.db.getPlaybackSession(req.params.id)
if (!playbackSession) return res.sendStatus(404)
const playbackSession = await this.db.getPlaybackSession(req.params.id)
if (!playbackSession) {
Logger.error(`[SessionController] Unable to find playback session with id=${req.params.id}`)
return res.sendStatus(404)
}
if (req.method == 'DELETE' && !req.user.canDelete) {
Logger.warn(`[SessionController] User attempted to delete without permission`, req.user)

View File

@ -154,7 +154,7 @@ class PlaybackSessionManager {
if (libraryItem.mediaType === 'video') {
if (shouldDirectPlay) {
Logger.debug(`[PlaybackSessionManager] "${user.username}" starting direct play session for item "${libraryItem.id}"`)
Logger.debug(`[PlaybackSessionManager] "${user.username}" starting direct play session for item "${libraryItem.id}" with id ${newPlaybackSession.id}`)
newPlaybackSession.videoTrack = libraryItem.media.getVideoTrack()
newPlaybackSession.playMethod = PlayMethod.DIRECTPLAY
} else {
@ -163,7 +163,7 @@ class PlaybackSessionManager {
} else {
let audioTracks = []
if (shouldDirectPlay) {
Logger.debug(`[PlaybackSessionManager] "${user.username}" starting direct play session for item "${libraryItem.id}"`)
Logger.debug(`[PlaybackSessionManager] "${user.username}" starting direct play session for item "${libraryItem.id}" with id ${newPlaybackSession.id}`)
audioTracks = libraryItem.getDirectPlayTracklist(episodeId)
newPlaybackSession.playMethod = PlayMethod.DIRECTPLAY
} else {

View File

@ -252,12 +252,19 @@ class Book {
if (metadataAbs) {
Logger.debug(`[Book] Found metadata.abs file for "${this.metadata.title}"`)
const metadataText = await readTextFile(metadataAbs.metadata.path)
const abmetadataUpdates = abmetadataGenerator.parseAndCheckForUpdates(metadataText, this.metadata, 'book')
const abmetadataUpdates = abmetadataGenerator.parseAndCheckForUpdates(metadataText, this, 'book')
if (abmetadataUpdates && Object.keys(abmetadataUpdates).length) {
Logger.debug(`[Book] "${this.metadata.title}" changes found in metadata.abs file`, abmetadataUpdates)
metadataUpdatePayload = {
...metadataUpdatePayload,
...abmetadataUpdates
if (abmetadataUpdates.tags) { // Set media tags if updated
this.tags = abmetadataUpdates.tags
tagsUpdated = true
}
if (abmetadataUpdates.metadata) {
metadataUpdatePayload = {
...metadataUpdatePayload,
...abmetadataUpdates.metadata
}
}
}
}
@ -489,4 +496,4 @@ class Book {
return this.metadata.authorName
}
}
module.exports = Book
module.exports = Book

View File

@ -188,22 +188,33 @@ class Podcast {
}
async syncMetadataFiles(textMetadataFiles, opfMetadataOverrideDetails) {
var metadataUpdatePayload = {}
let metadataUpdatePayload = {}
let tagsUpdated = false
var metadataAbs = textMetadataFiles.find(lf => lf.metadata.filename === 'metadata.abs')
const metadataAbs = textMetadataFiles.find(lf => lf.metadata.filename === 'metadata.abs')
if (metadataAbs) {
var metadataText = await readTextFile(metadataAbs.metadata.path)
var abmetadataUpdates = abmetadataGenerator.parseAndCheckForUpdates(metadataText, this.metadata, 'podcast')
const metadataText = await readTextFile(metadataAbs.metadata.path)
const abmetadataUpdates = abmetadataGenerator.parseAndCheckForUpdates(metadataText, this, 'podcast')
if (abmetadataUpdates && Object.keys(abmetadataUpdates).length) {
Logger.debug(`[Podcast] "${this.metadata.title}" changes found in metadata.abs file`, abmetadataUpdates)
metadataUpdatePayload = abmetadataUpdates
if (abmetadataUpdates.tags) { // Set media tags if updated
this.tags = abmetadataUpdates.tags
tagsUpdated = true
}
if (abmetadataUpdates.metadata) {
metadataUpdatePayload = {
...metadataUpdatePayload,
...abmetadataUpdates.metadata
}
}
}
}
if (Object.keys(metadataUpdatePayload).length) {
return this.metadata.update(metadataUpdatePayload)
return this.metadata.update(metadataUpdatePayload) || tagsUpdated
}
return false
return tagsUpdated
}
searchEpisodes(query) {
@ -305,4 +316,4 @@ class Podcast {
return this.episodes.find(ep => ep.id == episodeId)
}
}
module.exports = Podcast
module.exports = Podcast

View File

@ -2,7 +2,7 @@ const fs = require('../libs/fsExtra')
const filePerms = require('./filePerms')
const package = require('../../package.json')
const Logger = require('../Logger')
const { getId } = require('./index')
const { getId, copyValue } = require('./index')
const CurrentAbMetadataVersion = 2
@ -130,12 +130,13 @@ const metadataMappers = {
}
function generate(libraryItem, outputPath) {
var fileString = `;ABMETADATA${CurrentAbMetadataVersion}\n`
let fileString = `;ABMETADATA${CurrentAbMetadataVersion}\n`
fileString += `#audiobookshelf v${package.version}\n\n`
const mediaType = libraryItem.mediaType
fileString += `media=${mediaType}\n`
fileString += `tags=${JSON.stringify(libraryItem.media.tags)}\n`
const metadataMapper = metadataMappers[mediaType]
var mediaMetadata = libraryItem.media.metadata
@ -159,7 +160,6 @@ function generate(libraryItem, outputPath) {
fileString += `title=${chapter.title}\n`
})
}
return fs.writeFile(outputPath, fileString).then(() => {
return filePerms.setDefault(outputPath, true).then(() => true)
}).catch((error) => {
@ -223,17 +223,31 @@ function parseChapterLines(lines) {
return chapter
}
function parseTags(value) {
if (!value) return null
try {
const parsedTags = []
JSON.parse(value).forEach((loadedTag) => {
if (loadedTag.trim()) parsedTags.push(loadedTag) // Only push tags that are non-empty
})
return parsedTags
} catch (err) {
Logger.error(`[abmetadataGenerator] Error parsing TAGS "${value}":`, err.message)
return null
}
}
function parseAbMetadataText(text, mediaType) {
if (!text) return null
var lines = text.split(/\r?\n/)
let lines = text.split(/\r?\n/)
// Check first line and get abmetadata version number
var firstLine = lines.shift().toLowerCase()
const firstLine = lines.shift().toLowerCase()
if (!firstLine.startsWith(';abmetadata')) {
Logger.error(`Invalid abmetadata file first line is not ;abmetadata "${firstLine}"`)
return null
}
var abmetadataVersion = Number(firstLine.replace(';abmetadata', '').trim())
const abmetadataVersion = Number(firstLine.replace(';abmetadata', '').trim())
if (isNaN(abmetadataVersion) || abmetadataVersion != CurrentAbMetadataVersion) {
Logger.warn(`Invalid abmetadata version ${abmetadataVersion} - must use version ${CurrentAbMetadataVersion}`)
return null
@ -244,9 +258,9 @@ function parseAbMetadataText(text, mediaType) {
lines = lines.filter(line => !!line.trim() && !ignoreFirstChars.includes(line[0]))
// Get lines that map to book details (all lines before the first chapter or description section)
var firstSectionLine = lines.findIndex(l => l.startsWith('['))
var detailLines = firstSectionLine > 0 ? lines.slice(0, firstSectionLine) : lines
var remainingLines = firstSectionLine > 0 ? lines.slice(firstSectionLine) : []
const firstSectionLine = lines.findIndex(l => l.startsWith('['))
const detailLines = firstSectionLine > 0 ? lines.slice(0, firstSectionLine) : lines
const remainingLines = firstSectionLine > 0 ? lines.slice(firstSectionLine) : []
if (!detailLines.length) {
Logger.error(`Invalid abmetadata file no detail lines`)
@ -255,8 +269,8 @@ function parseAbMetadataText(text, mediaType) {
// Check the media type saved for this abmetadata file show warning if not matching expected
if (detailLines[0].toLowerCase().startsWith('media=')) {
var mediaLine = detailLines.shift() // Remove media line
var abMediaType = mediaLine.toLowerCase().split('=')[1].trim()
const mediaLine = detailLines.shift() // Remove media line
const abMediaType = mediaLine.toLowerCase().split('=')[1].trim()
if (abMediaType != mediaType) {
Logger.warn(`Invalid media type in abmetadata file ${abMediaType} expecting ${mediaType}`)
}
@ -266,43 +280,46 @@ function parseAbMetadataText(text, mediaType) {
const metadataMapper = metadataMappers[mediaType]
// Put valid book detail values into map
const mediaMetadataDetails = {}
const mediaDetails = {
metadata: {},
chapters: [],
tags: null // When tags are null it will not be used
}
for (let i = 0; i < detailLines.length; i++) {
var line = detailLines[i]
var keyValue = line.split('=')
const line = detailLines[i]
const keyValue = line.split('=')
if (keyValue.length < 2) {
Logger.warn('abmetadata invalid line has no =', line)
} else if (!metadataMapper[keyValue[0].trim()]) {
} else if (keyValue[0].trim() === 'tags') { // Parse tags
const value = keyValue.slice(1).join('=').trim() // Everything after "tags="
mediaDetails.tags = parseTags(value)
} else if (!metadataMapper[keyValue[0].trim()]) { // Ensure valid media metadata key
Logger.warn(`abmetadata key "${keyValue[0].trim()}" is not a valid ${mediaType} metadata key`)
} else {
var key = keyValue.shift().trim()
var value = keyValue.join('=').trim()
mediaMetadataDetails[key] = metadataMapper[key].from(value)
const key = keyValue.shift().trim()
const value = keyValue.join('=').trim()
mediaDetails.metadata[key] = metadataMapper[key].from(value)
}
}
const chapters = []
// Parse sections for description and chapters
var sections = parseSections(remainingLines)
const sections = parseSections(remainingLines)
sections.forEach((section) => {
var sectionHeader = section.shift()
const sectionHeader = section.shift()
if (sectionHeader.toLowerCase().startsWith('[description]')) {
mediaMetadataDetails.description = section.join('\n')
mediaDetails.metadata.description = section.join('\n')
} else if (sectionHeader.toLowerCase().startsWith('[chapter]')) {
var chapter = parseChapterLines(section)
const chapter = parseChapterLines(section)
if (chapter) {
chapters.push(chapter)
mediaDetails.chapters.push(chapter)
}
}
})
chapters.sort((a, b) => a.start - b.start)
mediaDetails.chapters.sort((a, b) => a.start - b.start)
return {
metadata: mediaMetadataDetails,
chapters
}
return mediaDetails
}
module.exports.parse = parseAbMetadataText
@ -376,42 +393,54 @@ function checkArraysChanged(abmetadataArray, mediaArray) {
return abmetadataArray.join(',') != mediaArray.join(',')
}
// Input text from abmetadata file and return object of metadata changes from media metadata
function parseAndCheckForUpdates(text, mediaMetadata, mediaType) {
if (!text || !mediaMetadata || !mediaType) {
// Input text from abmetadata file and return object of media changes
// only returns object of changes. empty object means no changes
function parseAndCheckForUpdates(text, media, mediaType) {
if (!text || !media || !media.metadata || !mediaType) {
Logger.error(`Invalid inputs to parseAndCheckForUpdates`)
return null
}
const mediaMetadata = media.metadata
const metadataUpdatePayload = {} // Only updated key/values
var updatePayload = {} // Only updated key/values
var abmetadataData = parseAbMetadataText(text, mediaType)
const abmetadataData = parseAbMetadataText(text, mediaType)
if (!abmetadataData || !abmetadataData.metadata) {
return null
}
var abMetadata = abmetadataData.metadata // Metadata from abmetadata file
const abMetadata = abmetadataData.metadata // Metadata from abmetadata file
for (const key in abMetadata) {
if (mediaMetadata[key] !== undefined) {
if (key === 'authors') {
var authorUpdatePayload = checkUpdatedBookAuthors(abMetadata[key], mediaMetadata[key])
if (authorUpdatePayload.hasUpdates) updatePayload.authors = authorUpdatePayload.authors
const authorUpdatePayload = checkUpdatedBookAuthors(abMetadata[key], mediaMetadata[key])
if (authorUpdatePayload.hasUpdates) metadataUpdatePayload.authors = authorUpdatePayload.authors
} else if (key === 'series') {
var seriesUpdatePayload = checkUpdatedBookSeries(abMetadata[key], mediaMetadata[key])
if (seriesUpdatePayload.hasUpdates) updatePayload.series = seriesUpdatePayload.series
const seriesUpdatePayload = checkUpdatedBookSeries(abMetadata[key], mediaMetadata[key])
if (seriesUpdatePayload.hasUpdates) metadataUpdatePayload.series = seriesUpdatePayload.series
} else if (key === 'genres' || key === 'narrators') { // Compare array differences
if (checkArraysChanged(abMetadata[key], mediaMetadata[key])) {
updatePayload[key] = abMetadata[key]
metadataUpdatePayload[key] = abMetadata[key]
}
} else if (abMetadata[key] !== mediaMetadata[key]) {
updatePayload[key] = abMetadata[key]
metadataUpdatePayload[key] = abMetadata[key]
}
} else {
Logger.warn('[abmetadataGenerator] Invalid key', key)
}
}
const updatePayload = {} // Only updated key/values
// Check update tags
if (abmetadataData.tags) {
if (checkArraysChanged(abmetadataData.tags, media.tags)) {
updatePayload.tags = abmetadataData.tags
}
}
if (Object.keys(metadataUpdatePayload).length) {
updatePayload.metadata = metadataUpdatePayload
}
return updatePayload
}
module.exports.parseAndCheckForUpdates = parseAndCheckForUpdates
module.exports.parseAndCheckForUpdates = parseAndCheckForUpdates