diff --git a/api/lib/permissions.ts b/api/lib/permissions.ts index cdf06f1..0b22567 100644 --- a/api/lib/permissions.ts +++ b/api/lib/permissions.ts @@ -2,11 +2,16 @@ import { type AuthPayload, type Role } from "../model/interfaces.ts"; // Named capabilities checked at enforcement points. Extend this union as new // gated capabilities are added (e.g. "invite:manage"). -export type Permission = "dump:moderate" | "comment:moderate" | "user:manage"; +export type Permission = + | "dump:moderate" + | "comment:moderate" + | "playlist:moderate" + | "user:manage"; const ALL_PERMISSIONS: readonly Permission[] = [ "dump:moderate", "comment:moderate", + "playlist:moderate", "user:manage", ]; @@ -14,7 +19,7 @@ const ALL_PERMISSIONS: readonly Permission[] = [ // and future); `moderator` gets the content-moderation permissions only. export const ROLE_PERMISSIONS: Record = { admin: ALL_PERMISSIONS, - moderator: ["dump:moderate", "comment:moderate"], + moderator: ["dump:moderate", "comment:moderate", "playlist:moderate"], user: [], }; diff --git a/api/routes/playlists.ts b/api/routes/playlists.ts index 644f0f4..921a721 100644 --- a/api/routes/playlists.ts +++ b/api/routes/playlists.ts @@ -8,6 +8,7 @@ import { isUpdatePlaylistRequest, } from "../model/interfaces.ts"; import { authMiddleware, type AuthState } from "../middleware/auth.ts"; +import { can } from "../lib/permissions.ts"; import { addDumpToPlaylist, createPlaylist, @@ -72,13 +73,18 @@ router.put("/:playlistId", authMiddleware, async (ctx) => { ctx.params.playlistId, body, ctx.state.user.userId, + can(ctx.state.user, "playlist:moderate"), ); ctx.response.body = { success: true, data: playlist }; }); // DELETE /api/playlists/:playlistId router.delete("/:playlistId", authMiddleware, (ctx) => { - deletePlaylist(ctx.params.playlistId, ctx.state.user.userId); + deletePlaylist( + ctx.params.playlistId, + ctx.state.user.userId, + can(ctx.state.user, "playlist:moderate"), + ); ctx.response.status = 204; }); @@ -88,6 +94,7 @@ router.post("/:playlistId/dumps/:dumpId", authMiddleware, (ctx) => { ctx.params.playlistId, ctx.params.dumpId, ctx.state.user.userId, + can(ctx.state.user, "playlist:moderate"), ); ctx.response.status = 204; }); @@ -98,6 +105,7 @@ router.delete("/:playlistId/dumps/:dumpId", authMiddleware, (ctx) => { ctx.params.playlistId, ctx.params.dumpId, ctx.state.user.userId, + can(ctx.state.user, "playlist:moderate"), ); ctx.response.status = 204; }); @@ -129,6 +137,7 @@ router.post("/:playlistId/image", authMiddleware, async (ctx) => { ctx.params.playlistId, mime, ctx.state.user.userId, + can(ctx.state.user, "playlist:moderate"), ); const filePath = `${PLAYLIST_IMAGES_DIR}/${playlist.id}`; await Deno.mkdir(PLAYLIST_IMAGES_DIR, { recursive: true }); @@ -166,7 +175,12 @@ router.put("/:playlistId/order", authMiddleware, async (ctx) => { "Invalid request", ); } - reorderPlaylist(ctx.params.playlistId, body.dumpIds, ctx.state.user.userId); + reorderPlaylist( + ctx.params.playlistId, + body.dumpIds, + ctx.state.user.userId, + can(ctx.state.user, "playlist:moderate"), + ); ctx.response.body = { success: true, data: null }; }); diff --git a/api/services/playlist-service.ts b/api/services/playlist-service.ts index bb79d76..5bf50f0 100644 --- a/api/services/playlist-service.ts +++ b/api/services/playlist-service.ts @@ -159,10 +159,11 @@ export function updatePlaylist( playlistId: string, req: UpdatePlaylistRequest, requestingUserId: string, + canModerate: boolean, ): Playlist { const playlist = getPlaylistById(playlistId); - if (playlist.userId !== requestingUserId) { + if (playlist.userId !== requestingUserId && !canModerate) { throw new APIException(APIErrorCode.UNAUTHORIZED, 403, "Forbidden"); } @@ -212,10 +213,11 @@ export function updatePlaylist( export function deletePlaylist( playlistId: string, requestingUserId: string, + canModerate: boolean, ): void { const playlist = getPlaylistById(playlistId); - if (playlist.userId !== requestingUserId) { + if (playlist.userId !== requestingUserId && !canModerate) { throw new APIException(APIErrorCode.UNAUTHORIZED, 403, "Forbidden"); } @@ -227,9 +229,10 @@ export function setPlaylistImage( playlistId: string, imageMime: string, requestingUserId: string, + canModerate: boolean, ): Playlist { const playlist = getPlaylistById(playlistId); - if (playlist.userId !== requestingUserId) { + if (playlist.userId !== requestingUserId && !canModerate) { throw new APIException(APIErrorCode.UNAUTHORIZED, 403, "Forbidden"); } db.prepare(`UPDATE playlists SET image_mime = ? WHERE id = ?;`).run( @@ -245,10 +248,11 @@ export function addDumpToPlaylist( playlistId: string, dumpId: string, requestingUserId: string, + canModerate: boolean, ): void { const playlist = getPlaylistById(playlistId); - if (playlist.userId !== requestingUserId) { + if (playlist.userId !== requestingUserId && !canModerate) { throw new APIException(APIErrorCode.UNAUTHORIZED, 403, "Forbidden"); } @@ -298,10 +302,11 @@ export function removeDumpFromPlaylist( playlistId: string, dumpId: string, requestingUserId: string, + canModerate: boolean, ): void { const playlist = getPlaylistById(playlistId); - if (playlist.userId !== requestingUserId) { + if (playlist.userId !== requestingUserId && !canModerate) { throw new APIException(APIErrorCode.UNAUTHORIZED, 403, "Forbidden"); } @@ -317,10 +322,11 @@ export function reorderPlaylist( playlistId: string, dumpIds: string[], requestingUserId: string, + canModerate: boolean, ): void { const playlist = getPlaylistById(playlistId); - if (playlist.userId !== requestingUserId) { + if (playlist.userId !== requestingUserId && !canModerate) { throw new APIException(APIErrorCode.UNAUTHORIZED, 403, "Forbidden"); } diff --git a/src/locales/en.po b/src/locales/en.po index dcc943d..f4083f9 100644 --- a/src/locales/en.po +++ b/src/locales/en.po @@ -42,7 +42,7 @@ msgstr "{label} ({count})" msgid "{visibleCount, plural, one {# comment} other {# comments}}" msgstr "{visibleCount, plural, one {# comment} other {# comments}}" -#: src/pages/PlaylistDetail.tsx:560 +#: src/pages/PlaylistDetail.tsx:561 #: src/pages/UserPublicProfile.tsx:732 msgid "← Back" msgstr "← Back" @@ -180,13 +180,13 @@ msgstr "Can't connect to the live updates server. Upvotes and notifications may #: src/components/form/FormActions.tsx:32 #: src/pages/Dump.tsx:359 #: src/pages/DumpEdit.tsx:433 -#: src/pages/PlaylistDetail.tsx:908 +#: src/pages/PlaylistDetail.tsx:911 #: src/pages/UserPublicProfile.tsx:1637 #: src/pages/UserPublicProfile.tsx:1707 msgid "Cancel" msgstr "Cancel" -#: src/pages/PlaylistDetail.tsx:733 +#: src/pages/PlaylistDetail.tsx:736 msgid "Cancel removal" msgstr "Cancel removal" @@ -278,7 +278,7 @@ msgstr "Default tab" #: src/components/CommentThread.tsx:376 #: src/components/CommentThread.tsx:382 #: src/components/ConfirmModal.tsx:16 -#: src/pages/PlaylistDetail.tsx:904 +#: src/pages/PlaylistDetail.tsx:907 msgid "Delete" msgstr "Delete" @@ -288,7 +288,7 @@ msgid "Delete dump" msgstr "Delete dump" #: src/components/PlaylistCard.tsx:109 -#: src/pages/PlaylistDetail.tsx:746 +#: src/pages/PlaylistDetail.tsx:749 #: src/pages/UserPlaylists.tsx:465 msgid "Delete playlist" msgstr "Delete playlist" @@ -301,13 +301,13 @@ msgstr "Delete this comment?" msgid "Delete this dump? This cannot be undone." msgstr "Delete this dump? This cannot be undone." -#: src/pages/PlaylistDetail.tsx:745 +#: src/pages/PlaylistDetail.tsx:748 #: src/pages/UserPlaylists.tsx:464 msgid "Delete this playlist? This cannot be undone." msgstr "Delete this playlist? This cannot be undone." #: src/components/PlaylistCreateForm.tsx:78 -#: src/pages/PlaylistDetail.tsx:865 +#: src/pages/PlaylistDetail.tsx:868 msgid "Description (optional)" msgstr "Description (optional)" @@ -353,7 +353,7 @@ msgstr "Earlier" #: src/components/CommentThread.tsx:367 #: src/pages/Dump.tsx:459 -#: src/pages/PlaylistDetail.tsx:613 +#: src/pages/PlaylistDetail.tsx:616 msgid "Edit" msgstr "Edit" @@ -366,7 +366,7 @@ msgstr "Edit title" #. placeholder {0}: relativeTime(playlist.updatedAt) #: src/components/CommentThread.tsx:317 #: src/pages/Dump.tsx:412 -#: src/pages/PlaylistDetail.tsx:652 +#: src/pages/PlaylistDetail.tsx:655 msgid "edited {0}" msgstr "edited {0}" @@ -375,7 +375,7 @@ msgstr "edited {0}" #. placeholder {0}: playlist.updatedAt.toLocaleString() #: src/components/CommentThread.tsx:315 #: src/pages/Dump.tsx:410 -#: src/pages/PlaylistDetail.tsx:649 +#: src/pages/PlaylistDetail.tsx:652 msgid "Edited {0}" msgstr "Edited {0}" @@ -424,7 +424,7 @@ msgstr "Failed to post comment" msgid "Failed to post reply" msgstr "Failed to post reply" -#: src/pages/PlaylistDetail.tsx:896 +#: src/pages/PlaylistDetail.tsx:899 #: src/pages/UserPublicProfile.tsx:1640 #: src/pages/UserPublicProfile.tsx:1709 msgid "Failed to save" @@ -595,7 +595,7 @@ msgstr "Loading dump…" msgid "Loading more…" msgstr "Loading more…" -#: src/pages/PlaylistDetail.tsx:544 +#: src/pages/PlaylistDetail.tsx:545 msgid "Loading playlist…" msgstr "Loading playlist…" @@ -688,7 +688,7 @@ msgstr "New password" msgid "New playlist" msgstr "New playlist" -#: src/pages/PlaylistDetail.tsx:668 +#: src/pages/PlaylistDetail.tsx:671 msgid "No dumps in this playlist yet." msgstr "No dumps in this playlist yet." @@ -785,7 +785,7 @@ msgstr "Password updated" msgid "Passwords do not match" msgstr "Passwords do not match" -#: src/pages/PlaylistDetail.tsx:851 +#: src/pages/PlaylistDetail.tsx:854 msgid "Playlist title" msgstr "Playlist title" @@ -825,22 +825,22 @@ msgstr "Posting…" #: src/components/PlaylistCard.tsx:73 #: src/components/PlaylistMembershipPanel.tsx:55 #: src/pages/Dump.tsx:418 -#: src/pages/PlaylistDetail.tsx:632 +#: src/pages/PlaylistDetail.tsx:635 msgid "private" msgstr "private" #: src/components/form/SegmentedField.tsx:78 -#: src/pages/PlaylistDetail.tsx:890 +#: src/pages/PlaylistDetail.tsx:893 msgid "Private" msgstr "Private" #: src/components/PlaylistCard.tsx:72 -#: src/pages/PlaylistDetail.tsx:631 +#: src/pages/PlaylistDetail.tsx:634 msgid "public" msgstr "public" #: src/components/form/SegmentedField.tsx:77 -#: src/pages/PlaylistDetail.tsx:883 +#: src/pages/PlaylistDetail.tsx:886 msgid "Public" msgstr "Public" @@ -873,7 +873,7 @@ msgstr "Related" msgid "Remove file" msgstr "Remove file" -#: src/pages/PlaylistDetail.tsx:723 +#: src/pages/PlaylistDetail.tsx:726 msgid "Remove from playlist" msgstr "Remove from playlist" @@ -918,7 +918,7 @@ msgstr "Role" #: src/components/CommentThread.tsx:328 #: src/pages/Dump.tsx:351 #: src/pages/DumpEdit.tsx:436 -#: src/pages/PlaylistDetail.tsx:915 +#: src/pages/PlaylistDetail.tsx:918 #: src/pages/UserPublicProfile.tsx:1629 #: src/pages/UserPublicProfile.tsx:1699 msgid "Save" @@ -927,7 +927,7 @@ msgstr "Save" #: src/components/ChangePasswordModal.tsx:100 #: src/components/CommentThread.tsx:329 #: src/pages/Dump.tsx:350 -#: src/pages/PlaylistDetail.tsx:911 +#: src/pages/PlaylistDetail.tsx:914 #: src/pages/ResetPassword.tsx:124 #: src/pages/UserPublicProfile.tsx:1626 #: src/pages/UserPublicProfile.tsx:1696 @@ -1019,7 +1019,7 @@ msgstr "Title is required." msgid "Today" msgstr "Today" -#: src/pages/PlaylistDetail.tsx:735 +#: src/pages/PlaylistDetail.tsx:738 msgid "Undo" msgstr "Undo" diff --git a/src/locales/fr.js b/src/locales/fr.js index ca47b50..6059fc1 100644 --- a/src/locales/fr.js +++ b/src/locales/fr.js @@ -1 +1 @@ -/*eslint-disable*/module.exports={messages:JSON.parse("{\"-K9EZb\":[\"Ajouter un e-mail…\"],\"-OxI15\":[\"Collections suivies\"],\"-Ya-b9\":[\"Enregistrement échoué\"],\"-siMqD\":[\"Journal\"],\"1CalO6\":[\"Style\"],\"1HfJWf\":[\"Rechercher des recos, utilisateurs, collections…\"],\"1cbYY_\":[\"Votés (\",[\"0\"],[\"1\"],\")\"],\"1njn7W\":[\"Clair\"],\"1utXA6\":[\"Recos\"],\"26iNma\":[\"Publier le commentaire\"],\"2DoBvq\":[\"Flux\"],\"2Hlmdt\":[\"Écrire une réponse…\"],\"2ygf_L\":[\"← Retour\"],\"3KKSM4\":[\"privé\"],\"3yfh3D\":[\"<0>\",[\"0\"],\" a suivi votre collection <1>\",[\"1\"],\"\"],\"49voTZ\":[[\"label\"],\" (\",[\"count\"],\")\"],\"4B6w_o\":[\"Recommandé !\"],\"4GKuCs\":[\"Connexion échouée\"],\"4HH9iB\":[\"Aucun abonnement pour le moment.\"],\"4RtQ1k\":[\"Ne plus suivre \",[\"targetUsername\"]],\"4c-qBx\":[\"Votre mot de passe a été modifié. Vous pouvez maintenant vous connecter.\"],\"4yj9xV\":[\"Les mises à jour en direct sont temporairement interrompues. Tentative de reconnexion…\"],\"5TviPn\":[\"Annuler la recherche\"],\"5cC8f2\":[\"Modifié le \",[\"0\"]],\"5oD9f_\":[\"Plus tôt\"],\"6Qly-0\":[\"un commentaire\"],\"6gRgw8\":[\"Réessayer\"],\"7PHCIN\":[\"Annuler la suppression\"],\"7PzzBU\":[\"Utilisateur\"],\"7d1a0d\":[\"Public\"],\"7sNhEz\":[\"Nom d'utilisateur\"],\"8Ug9jB\":[\"Connexe\"],\"8ZsakT\":[\"Mot de passe\"],\"8pxhI8\":[\"Veuillez sélectionner un fichier.\"],\"9BruTc\":[\"Pourquoi ?\"],\"9l4qcT\":[\"Déposez un fichier de remplacement ici\"],\"9uI_rE\":[\"Annuler\"],\"9xDZu_\":[\"Impossible de charger.\"],\"A0y396\":[\"+ Inviter quelqu'un\"],\"A1taO8\":[\"Rechercher\"],\"AQbgNR\":[\"Suivre \",[\"targetUsername\"]],\"ATGYL1\":[\"Adresse e-mail\"],\"AZctoV\":[[\"0\",\"plural\",{\"one\":[\"#\",\" commentaire\"],\"other\":[\"#\",\" commentaires\"]}]],\"Ade-6d\":[\"Mises à jour en direct indisponibles.\"],\"AeXO77\":[\"Compte\"],\"CI50ct\":[\"Voté\"],\"Cj24wt\":[\"Inscription…\"],\"DPfwMq\":[\"Terminé\"],\"DdeHXH\":[\"Supprimer cette reco ? Cette action est irréversible.\"],\"Dp1JhP\":[\"<0>\",[\"0\"],\" a voté pour <1>\",[\"1\"],\"\"],\"ECiS12\":[\"Voir la reco →\"],\"ExR0Fr\":[\"L'URL est obligatoire.\"],\"F5Js1v\":[\"Ne plus suivre la collection\"],\"FgAxTj\":[\"Se déconnecter\"],\"Fxf4jq\":[\"Description (facultatif)\"],\"GDvlUT\":[\"Rôle\"],\"GLukaH\":[\"Modérateur\"],\"GNSsCc\":[\"Impossible de créer la collection\"],\"GbqhrN\":[[\"0\"],\"–\",[\"1\"],\" caractères : lettres, chiffres ou tirets bas\"],\"GptGxg\":[\"Changer le mot de passe\"],\"H4o4sk\":[\"Personne pour le moment.\"],\"H8pzW-\":[\"Impossible de mettre à jour l'avatar\"],\"HTLDA4\":[\"Chargement…\"],\"I-x669\":[\"Invités\"],\"IZX7TO\":[\"Impossible de générer une invitation\"],\"IagCbF\":[\"URL\"],\"ImOQa9\":[\"Répondre\"],\"J2eKUI\":[\"Fichier\"],\"JJ-Bhk\":[\"Votre adresse e-mail\"],\"JRQitQ\":[\"Confirmer le nouveau mot de passe\"],\"JXr41k\":[\"<0>\",[\"0\"],\" a commenté sur <1>\",[\"1\"],\"\"],\"Jd58Fo\":[\"Tendances\"],\"Jf0PuK\":[\"Aller à la connexion\"],\"KDGWg5\":[\"Retirer de la collection\"],\"K_F6pa\":[\"Enregistrement…\"],\"L-rMC9\":[\"Réinitialiser par défaut\"],\"LLyMkV\":[\"Suivies (\",[\"0\"],[\"1\"],\")\"],\"MHrjPM\":[\"Titre\"],\"MKEPCY\":[\"Suivre\"],\"Oprv1v\":[\"Mot de passe (min. \",[\"0\"],\" caractères)\"],\"Oz0N9s\":[\"nouveau\"],\"PiH3UR\":[\"Copié !\"],\"Pn2B7_\":[\"Mot de passe actuel\"],\"Pwqkdw\":[\"Chargement…\"],\"Q6n4F4\":[\"Actualiser les métadonnées\"],\"QGaQRo\":[\"Erreur lors de la mise à jour du rôle\"],\"QKsaQr\":[\"ou <0>parcourir les fichiers\"],\"QLtPBd\":[\"Aucune reco dans cette collection pour l'instant.\"],\"R9Khdg\":[\"Auto\"],\"RCcPrX\":[\"Supprimer cette collection ? Cette action est irréversible.\"],\"RTksSy\":[\"<0>\",[\"0\"],\" a commencé à vous suivre\"],\"RaKjrM\":[\"Impossible d'enregistrer la modification\"],\"RcUHRT\":[\"Suivi\"],\"Rrp6-J\":[\"Se connecter pour aimer\"],\"SBTElJ\":[\"Recherche…\"],\"Sad2tK\":[\"Envoi…\"],\"StovX6\":[\"Rien à voir, circulez.\"],\"Sxm8rQ\":[\"Utilisateurs\"],\"T9bjWt\":[\"<0>\",[\"0\"],\" a été ajouté à <1>\",[\"1\"],\"\"],\"TM1ZbA\":[\"Collections (\",[\"0\"],[\"1\"],\")\"],\"TN382O\":[\"Lien invalide\"],\"Tv9vbB\":[\"Suivre la collection\"],\"Tz0i8g\":[\"Paramètres\"],\"U3pytU\":[\"Administrateur\"],\"UNMVei\":[\"Mot de passe oublié ?\"],\"UOZith\":[\"Publication échouée\"],\"URAieT\":[\"Se connecter pour voter\"],\"UTiUFs\":[\"Récupération…\"],\"VCoEm-\":[\"Retour à la connexion\"],\"V_e7nf\":[\"Définir un nouveau mot de passe\"],\"VnNJbN\":[\"De collections\"],\"VyTYmS\":[\"Changer l'avatar\"],\"W9FRBT\":[\"Aimer\"],\"WhimMi\":[\"Échec de la réinitialisation\"],\"WpXcBJ\":[\"Rien ici pour l'instant.\"],\"XJy2oN\":[\"Connexion…\"],\"Xan6QP\":[\"Nouvelle reco\"],\"XgRtUf\":[\"Impossible de changer le mot de passe\"],\"Xi0Mn4\":[\"← Retour au profil\"],\"XnL-Eu\":[\"Aucun utilisateur ne correspond à « \",[\"q\"],\" ».\"],\"Xs2Lez\":[\"Ce lien de réinitialisation est absent ou malformé.\"],\"YK1Dhc\":[\"une publication\"],\"YcjhhX\":[\"Gestion des utilisateurs\"],\"Ye9RMF\":[\"<0>\",[\"0\"],\" a aimé votre commentaire sur <1>\",[\"1\"],\"\"],\"YpkCca\":[\"Pas encore de collections suivies.\"],\"ZBdbv9\":[\"Modifier le titre\"],\"ZCpU0u\":[\"Aucune collection ne correspond à « \",[\"q\"],\" ».\"],\"ZmD2o6\":[\"Créer et ajouter\"],\"_3O5R_\":[\"Échec de la demande\"],\"_DwR-n\":[\"Création…\"],\"_R_sGB\":[\"Mot de passe modifié avec succès.\"],\"_aept4\":[\"Publier la réponse\"],\"_nT6AE\":[\"Nouveau mot de passe\"],\"_t4W-i\":[\"De personnes\"],\"aAIQg2\":[\"Apparence\"],\"aDvLhk\":[\"Ajouter un commentaire…\"],\"b3Thhd\":[\"Envoi échoué\"],\"b8XMJ8\":[[\"visibleCount\",\"plural\",{\"one\":[\"#\",\" commentaire\"],\"other\":[\"#\",\" commentaires\"]}]],\"bQhwn-\":[\"Chargement de la collection…\"],\"cILfnJ\":[\"Supprimer le fichier\"],\"cYP9Sb\":[\"+ Collection\"],\"cbeBbZ\":[\"Au moins \",[\"0\"],\" caractères\"],\"cnGeoo\":[\"Supprimer\"],\"d8DZWS\":[\"Ouvrir la recherche\"],\"dAs22m\":[\"Remplacer le fichier\"],\"dEgA5A\":[\"Annuler\"],\"dMizp8\":[\"Nouvelle collection\"],\"eFSqvc\":[\"Impossible de publier la réponse\"],\"eOfXq3\":[\"Un titre est requis.\"],\"ePK91l\":[\"Modifier\"],\"eaUTwS\":[\"Envoyer le lien de réinitialisation\"],\"ecUA8p\":[\"Aujourd'hui\"],\"ef9nPf\":[\"Chargement de la reco…\"],\"en9o7K\":[\"Impossible de publier le commentaire\"],\"etFQQS\":[\"Pourquoi on en voudrait ?\"],\"fC6mXb\":[\"Voter\"],\"fI-mNw\":[\"Collections\"],\"f_akpP\":[\"Max 50 Mo\"],\"fgLNSM\":[\"S'inscrire\"],\"gANddk\":[\"Envoi…\"],\"gGx5tM\":[\"Modification\"],\"gIQQwD\":[\"Chargement échoué\"],\"gLfZlz\":[\"Ajouter à la collection\"],\"gjJ-sb\":[\"Impossible de se connecter au serveur de mises à jour en direct. Les votes et les notifications pourraient ne pas se synchroniser avant la reconnexion.\"],\"hBuUKa\":[\"Changer le mot de passe…\"],\"hD7w09\":[\"Vous avez tout lu, tout vu, tout bu.\"],\"hJSliC\":[\"<0>\",[\"0\"],\" a publié <1>\",[\"1\"],\"\"],\"hYgDIe\":[\"Créer\"],\"he3ygx\":[\"Copier\"],\"i7K_Te\":[\"Qui suis-je ?\"],\"iDNBZe\":[\"Notifications\"],\"iWpEwy\":[\"Accueil\"],\"ipYn7W\":[\"Si cette adresse est enregistrée, vous recevrez un lien de réinitialisation sous peu.\"],\"isRobC\":[\"Nouveau\"],\"jbernk\":[\"Chargement du profil…\"],\"joEmfT\":[\"Serveur inaccessible\"],\"jrZTZl\":[\"Pas encore de recos. Soyez le premier !\"],\"kLttbL\":[\"Inscription échouée\"],\"lUDifl\":[\"Créées (\",[\"0\"],[\"1\"],\")\"],\"lUanmi\":[\"Vous serez notifié lorsque quelqu'un suit vos collections, vote pour vos recos ou publie du nouveau contenu.\"],\"lY5h1V\":[[\"0\",\"plural\",{\"one\":[\"#\",\" reco\"],\"other\":[\"#\",\" recos\"]}]],\"lcfvr_\":[\"Supprimer ce commentaire ?\"],\"lpIMne\":[\"Les mots de passe ne correspondent pas\"],\"mt6O6E\":[\"C'est un mirage.\"],\"nbm5sI\":[\"Aucune reco ne correspond à « \",[\"q\"],\" ».\"],\"nrjqON\":[\"Vérification de l'invitation…\"],\"nwtY4N\":[\"Une erreur est survenue\"],\"nx4kaN\":[\"Sauvegarde impossible\"],\"ogtYkT\":[\"Mot de passe mis à jour\"],\"pCpd9p\":[\"<0>\",[\"0\"],\" vous a mentionné dans <1>\",[\"where\"],\"\"],\"pSheLH\":[\"Aucun invité pour le moment.\"],\"pvnfJD\":[\"Sombre\"],\"qIMfNQ\":[\"Supprimer la collection\"],\"qbDAcy\":[\"Recommander\"],\"qgx_78\":[\"Suivez des collections publiques pour voir leurs recos ici.\"],\"qvFa8r\":[\"public\"],\"qvz_Pp\":[\"Reco\"],\"rCbqPX\":[\"Ce lien d'invitation est manquant, expiré ou déjà utilisé.\"],\"rg9pXu\":[\"Recherche échouée\"],\"rtpJqV\":[\"Recos (\",[\"0\"],[\"1\"],\")\"],\"sGeXL3\":[\"Miniature\"],\"sQia9P\":[\"Se connecter\"],\"sTiqbm\":[\"invité par\"],\"sdP5Aa\":[\"[supprimé]\"],\"shHs8T\":[\"Saisissez une recherche.\"],\"smeBfS\":[\"Invitation invalide\"],\"tfDRzk\":[\"Enregistrer\"],\"tvmuQ0\":[\"Thème de couleur\"],\"u1lDX2\":[\"Récupération de l'aperçu…\"],\"uD0qXQ\":[\"Déposez un fichier ici\"],\"uMGUnV\":[\"Pas encore de collections.\"],\"ub1EEL\":[\"modifié \",[\"0\"]],\"vJBF1r\":[\"Publication…\"],\"vLhLLO\":[\"Notifications (\",[\"unreadNotificationCount\"],\" non lues)\"],\"vQMkHu\":[\"Retirer le vote\"],\"vuosjb\":[\"Menu utilisateur\"],\"wbXKOv\":[\"Fichier trop volumineux (max 50 Mo).\"],\"wckWOP\":[\"Administration\"],\"wixIgH\":[\"Vous avez déjà un compte ? <0>Se connecter\"],\"x6tjuK\":[\"Onglet par défaut\"],\"xEWkgZ\":[\"← Retour à toutes les recos\"],\"xPHtx0\":[\"Lancer la recherche\"],\"xVuNgt\":[\"+ Nouvelle collection\"],\"xc9O_u\":[\"Supprimer la reco\"],\"y6sq5j\":[\"Abonné\"],\"y7oaHj\":[\"Titre de la collection\"],\"yA_6BX\":[\"Tout voir →\"],\"yBBtRm\":[\"Suivez des utilisateurs pour voir leurs recos ici.\"],\"yQ2kGp\":[\"Charger plus\"],\"y_0uwd\":[\"Hier\"],\"yz7wBu\":[\"Fermer\"],\"z0ROB3\":[\"Retirer le j'aime\"],\"z1uNN0\":[\"Aucun emoji trouvé.\"],\"zVuxvN\":[\"Actualisation…\"],\"zwBp5t\":[\"Privé\"]}")}; \ No newline at end of file +/*eslint-disable*/module.exports={messages:JSON.parse("{\"-K9EZb\":[\"Ajouter un e-mail…\"],\"-OxI15\":[\"Collections suivies\"],\"-Ya-b9\":[\"Enregistrement échoué\"],\"-siMqD\":[\"Journal\"],\"1CalO6\":[\"Style\"],\"1HfJWf\":[\"Rechercher des recos, utilisateurs, collections…\"],\"1cbYY_\":[\"Votés (\",[\"0\"],[\"1\"],\")\"],\"1njn7W\":[\"Clair\"],\"1utXA6\":[\"Recos\"],\"26iNma\":[\"Publier le commentaire\"],\"2DoBvq\":[\"Flux\"],\"2Hlmdt\":[\"Écrire une réponse…\"],\"2ygf_L\":[\"← Retour\"],\"3KKSM4\":[\"privé\"],\"3yfh3D\":[\"<0>\",[\"0\"],\" a suivi votre collection <1>\",[\"1\"],\"\"],\"49voTZ\":[[\"label\"],\" (\",[\"count\"],\")\"],\"4B6w_o\":[\"Recommandé !\"],\"4GKuCs\":[\"Connexion échouée\"],\"4HH9iB\":[\"Aucun abonnement pour le moment.\"],\"4RtQ1k\":[\"Ne plus suivre \",[\"targetUsername\"]],\"4c-qBx\":[\"Votre mot de passe a été modifié. Vous pouvez maintenant vous connecter.\"],\"4yj9xV\":[\"Les mises à jour en direct sont temporairement interrompues. Tentative de reconnexion…\"],\"5TviPn\":[\"Annuler la recherche\"],\"5cC8f2\":[\"Modifié le \",[\"0\"]],\"5oD9f_\":[\"Plus tôt\"],\"6Qly-0\":[\"un commentaire\"],\"6gRgw8\":[\"Réessayer\"],\"7PHCIN\":[\"Annuler la suppression\"],\"7PzzBU\":[\"Utilisateur\"],\"7d1a0d\":[\"Public\"],\"7sNhEz\":[\"Nom d'utilisateur\"],\"8Ug9jB\":[\"Connexe\"],\"8ZsakT\":[\"Mot de passe\"],\"8pxhI8\":[\"Veuillez sélectionner un fichier.\"],\"9BruTc\":[\"Pourquoi ?\"],\"9l4qcT\":[\"Déposez un fichier de remplacement ici\"],\"9uI_rE\":[\"Annuler\"],\"9xDZu_\":[\"Impossible de charger.\"],\"A0y396\":[\"+ Inviter quelqu'un\"],\"A1taO8\":[\"Rechercher\"],\"AQbgNR\":[\"Suivre \",[\"targetUsername\"]],\"ATGYL1\":[\"Adresse e-mail\"],\"AZctoV\":[[\"0\",\"plural\",{\"one\":[\"#\",\" commentaire\"],\"other\":[\"#\",\" commentaires\"]}]],\"Ade-6d\":[\"Mises à jour en direct indisponibles.\"],\"AeXO77\":[\"Compte\"],\"CI50ct\":[\"Voté\"],\"Cj24wt\":[\"Inscription…\"],\"DPfwMq\":[\"Terminé\"],\"DdeHXH\":[\"Supprimer cette reco ? Cette action est irréversible.\"],\"Dp1JhP\":[\"<0>\",[\"0\"],\" a voté pour <1>\",[\"1\"],\"\"],\"ECiS12\":[\"Voir la reco →\"],\"ExR0Fr\":[\"L'URL est obligatoire.\"],\"F5Js1v\":[\"Ne plus suivre la collection\"],\"FgAxTj\":[\"Se déconnecter\"],\"Fxf4jq\":[\"Description (facultatif)\"],\"GDvlUT\":[\"Rôle\"],\"GLukaH\":[\"Modérateur\"],\"GNSsCc\":[\"Impossible de créer la collection\"],\"GbqhrN\":[[\"0\"],\"–\",[\"1\"],\" caractères : lettres, chiffres ou tirets bas\"],\"GptGxg\":[\"Changer le mot de passe\"],\"H4o4sk\":[\"Personne pour le moment.\"],\"H8pzW-\":[\"Impossible de mettre à jour l'avatar\"],\"HTLDA4\":[\"Chargement…\"],\"I-x669\":[\"Invités\"],\"IZX7TO\":[\"Impossible de générer une invitation\"],\"IagCbF\":[\"URL\"],\"ImOQa9\":[\"Répondre\"],\"J2eKUI\":[\"Fichier\"],\"JJ-Bhk\":[\"Votre adresse e-mail\"],\"JRQitQ\":[\"Confirmer le nouveau mot de passe\"],\"JXr41k\":[\"<0>\",[\"0\"],\" a commenté sur <1>\",[\"1\"],\"\"],\"Jd58Fo\":[\"Tendances\"],\"Jf0PuK\":[\"Aller à la connexion\"],\"KDGWg5\":[\"Retirer de la collection\"],\"K_F6pa\":[\"Enregistrement…\"],\"L-rMC9\":[\"Réinitialiser par défaut\"],\"LLyMkV\":[\"Suivies (\",[\"0\"],[\"1\"],\")\"],\"MHrjPM\":[\"Titre\"],\"MKEPCY\":[\"Suivre\"],\"Oprv1v\":[\"Mot de passe (min. \",[\"0\"],\" caractères)\"],\"Oz0N9s\":[\"nouveau\"],\"PiH3UR\":[\"Copié !\"],\"Pn2B7_\":[\"Mot de passe actuel\"],\"Pwqkdw\":[\"Chargement…\"],\"Q6n4F4\":[\"Actualiser les métadonnées\"],\"QGaQRo\":[\"Erreur lors de la mise à jour du rôle\"],\"QKsaQr\":[\"ou <0>parcourir les fichiers\"],\"QLtPBd\":[\"Aucune reco dans cette collection pour l'instant.\"],\"R9Khdg\":[\"Auto\"],\"RCcPrX\":[\"Supprimer cette collection ? Cette action est irréversible.\"],\"RTksSy\":[\"<0>\",[\"0\"],\" a commencé à vous suivre\"],\"RaKjrM\":[\"Impossible d'enregistrer la modification\"],\"RcUHRT\":[\"Suivi\"],\"Rrp6-J\":[\"Se connecter pour aimer\"],\"SBTElJ\":[\"Recherche…\"],\"Sad2tK\":[\"Envoi…\"],\"StovX6\":[\"Rien à voir, circulez.\"],\"Sxm8rQ\":[\"Utilisateurs\"],\"T9bjWt\":[\"<0>\",[\"0\"],\" a été ajouté à <1>\",[\"1\"],\"\"],\"TM1ZbA\":[\"Collections (\",[\"0\"],[\"1\"],\")\"],\"TN382O\":[\"Lien invalide\"],\"Tv9vbB\":[\"Suivre la collection\"],\"Tz0i8g\":[\"Paramètres\"],\"U3pytU\":[\"Administrateur\"],\"UNMVei\":[\"Mot de passe oublié ?\"],\"UOZith\":[\"Publication échouée\"],\"URAieT\":[\"Se connecter pour voter\"],\"UTiUFs\":[\"Récupération…\"],\"VCoEm-\":[\"Retour à la connexion\"],\"V_e7nf\":[\"Définir un nouveau mot de passe\"],\"VnNJbN\":[\"De collections\"],\"VyTYmS\":[\"Changer l'avatar\"],\"W9FRBT\":[\"Aimer\"],\"WhimMi\":[\"Échec de la réinitialisation\"],\"WpXcBJ\":[\"Rien ici pour l'instant.\"],\"XJy2oN\":[\"Connexion…\"],\"Xan6QP\":[\"Nouvelle reco\"],\"XgRtUf\":[\"Impossible de changer le mot de passe\"],\"Xi0Mn4\":[\"← Retour au profil\"],\"XnL-Eu\":[\"Aucun utilisateur ne correspond à « \",[\"q\"],\" ».\"],\"Xs2Lez\":[\"Ce lien de réinitialisation est absent ou malformé.\"],\"YK1Dhc\":[\"une publication\"],\"YcjhhX\":[\"Gestion utilisateur\"],\"Ye9RMF\":[\"<0>\",[\"0\"],\" a aimé votre commentaire sur <1>\",[\"1\"],\"\"],\"YpkCca\":[\"Pas encore de collections suivies.\"],\"ZBdbv9\":[\"Modifier le titre\"],\"ZCpU0u\":[\"Aucune collection ne correspond à « \",[\"q\"],\" ».\"],\"ZmD2o6\":[\"Créer et ajouter\"],\"_3O5R_\":[\"Échec de la demande\"],\"_DwR-n\":[\"Création…\"],\"_R_sGB\":[\"Mot de passe modifié avec succès.\"],\"_aept4\":[\"Publier la réponse\"],\"_nT6AE\":[\"Nouveau mot de passe\"],\"_t4W-i\":[\"De personnes\"],\"aAIQg2\":[\"Apparence\"],\"aDvLhk\":[\"Ajouter un commentaire…\"],\"b3Thhd\":[\"Envoi échoué\"],\"b8XMJ8\":[[\"visibleCount\",\"plural\",{\"one\":[\"#\",\" commentaire\"],\"other\":[\"#\",\" commentaires\"]}]],\"bQhwn-\":[\"Chargement de la collection…\"],\"cILfnJ\":[\"Supprimer le fichier\"],\"cYP9Sb\":[\"+ Collection\"],\"cbeBbZ\":[\"Au moins \",[\"0\"],\" caractères\"],\"cnGeoo\":[\"Supprimer\"],\"d8DZWS\":[\"Ouvrir la recherche\"],\"dAs22m\":[\"Remplacer le fichier\"],\"dEgA5A\":[\"Annuler\"],\"dMizp8\":[\"Nouvelle collection\"],\"eFSqvc\":[\"Impossible de publier la réponse\"],\"eOfXq3\":[\"Un titre est requis.\"],\"ePK91l\":[\"Modifier\"],\"eaUTwS\":[\"Envoyer le lien de réinitialisation\"],\"ecUA8p\":[\"Aujourd'hui\"],\"ef9nPf\":[\"Chargement de la reco…\"],\"en9o7K\":[\"Impossible de publier le commentaire\"],\"etFQQS\":[\"Pourquoi on en voudrait ?\"],\"fC6mXb\":[\"Voter\"],\"fI-mNw\":[\"Collections\"],\"f_akpP\":[\"Max 50 Mo\"],\"fgLNSM\":[\"S'inscrire\"],\"gANddk\":[\"Envoi…\"],\"gGx5tM\":[\"Modification\"],\"gIQQwD\":[\"Chargement échoué\"],\"gLfZlz\":[\"Ajouter à la collection\"],\"gjJ-sb\":[\"Impossible de se connecter au serveur de mises à jour en direct. Les votes et les notifications pourraient ne pas se synchroniser avant la reconnexion.\"],\"hBuUKa\":[\"Changer le mot de passe…\"],\"hD7w09\":[\"Vous avez tout lu, tout vu, tout bu.\"],\"hJSliC\":[\"<0>\",[\"0\"],\" a publié <1>\",[\"1\"],\"\"],\"hYgDIe\":[\"Créer\"],\"he3ygx\":[\"Copier\"],\"i7K_Te\":[\"Qui suis-je ?\"],\"iDNBZe\":[\"Notifications\"],\"iWpEwy\":[\"Accueil\"],\"ipYn7W\":[\"Si cette adresse est enregistrée, vous recevrez un lien de réinitialisation sous peu.\"],\"isRobC\":[\"Nouveau\"],\"jbernk\":[\"Chargement du profil…\"],\"joEmfT\":[\"Serveur inaccessible\"],\"jrZTZl\":[\"Pas encore de recos. Soyez le premier !\"],\"kLttbL\":[\"Inscription échouée\"],\"lUDifl\":[\"Créées (\",[\"0\"],[\"1\"],\")\"],\"lUanmi\":[\"Vous serez notifié lorsque quelqu'un suit vos collections, vote pour vos recos ou publie du nouveau contenu.\"],\"lY5h1V\":[[\"0\",\"plural\",{\"one\":[\"#\",\" reco\"],\"other\":[\"#\",\" recos\"]}]],\"lcfvr_\":[\"Supprimer ce commentaire ?\"],\"lpIMne\":[\"Les mots de passe ne correspondent pas\"],\"mt6O6E\":[\"C'est un mirage.\"],\"nbm5sI\":[\"Aucune reco ne correspond à « \",[\"q\"],\" ».\"],\"nrjqON\":[\"Vérification de l'invitation…\"],\"nwtY4N\":[\"Une erreur est survenue\"],\"nx4kaN\":[\"Sauvegarde impossible\"],\"ogtYkT\":[\"Mot de passe mis à jour\"],\"pCpd9p\":[\"<0>\",[\"0\"],\" vous a mentionné dans <1>\",[\"where\"],\"\"],\"pSheLH\":[\"Aucun invité pour le moment.\"],\"pvnfJD\":[\"Sombre\"],\"qIMfNQ\":[\"Supprimer la collection\"],\"qbDAcy\":[\"Recommander\"],\"qgx_78\":[\"Suivez des collections publiques pour voir leurs recos ici.\"],\"qvFa8r\":[\"public\"],\"qvz_Pp\":[\"Reco\"],\"rCbqPX\":[\"Ce lien d'invitation est manquant, expiré ou déjà utilisé.\"],\"rg9pXu\":[\"Recherche échouée\"],\"rtpJqV\":[\"Recos (\",[\"0\"],[\"1\"],\")\"],\"sGeXL3\":[\"Miniature\"],\"sQia9P\":[\"Se connecter\"],\"sTiqbm\":[\"invité par\"],\"sdP5Aa\":[\"[supprimé]\"],\"shHs8T\":[\"Saisissez une recherche.\"],\"smeBfS\":[\"Invitation invalide\"],\"tfDRzk\":[\"Enregistrer\"],\"tvmuQ0\":[\"Thème de couleur\"],\"u1lDX2\":[\"Récupération de l'aperçu…\"],\"uD0qXQ\":[\"Déposez un fichier ici\"],\"uMGUnV\":[\"Pas encore de collections.\"],\"ub1EEL\":[\"modifié \",[\"0\"]],\"vJBF1r\":[\"Publication…\"],\"vLhLLO\":[\"Notifications (\",[\"unreadNotificationCount\"],\" non lues)\"],\"vQMkHu\":[\"Retirer le vote\"],\"vuosjb\":[\"Menu utilisateur\"],\"wbXKOv\":[\"Fichier trop volumineux (max 50 Mo).\"],\"wckWOP\":[\"Administration\"],\"wixIgH\":[\"Vous avez déjà un compte ? <0>Se connecter\"],\"x6tjuK\":[\"Onglet par défaut\"],\"xEWkgZ\":[\"← Retour à toutes les recos\"],\"xPHtx0\":[\"Lancer la recherche\"],\"xVuNgt\":[\"+ Nouvelle collection\"],\"xc9O_u\":[\"Supprimer la reco\"],\"y6sq5j\":[\"Abonné\"],\"y7oaHj\":[\"Titre de la collection\"],\"yA_6BX\":[\"Tout voir →\"],\"yBBtRm\":[\"Suivez des utilisateurs pour voir leurs recos ici.\"],\"yQ2kGp\":[\"Charger plus\"],\"y_0uwd\":[\"Hier\"],\"yz7wBu\":[\"Fermer\"],\"z0ROB3\":[\"Retirer le j'aime\"],\"z1uNN0\":[\"Aucun emoji trouvé.\"],\"zVuxvN\":[\"Actualisation…\"],\"zwBp5t\":[\"Privé\"]}")}; \ No newline at end of file diff --git a/src/locales/fr.po b/src/locales/fr.po index b60000a..c324bbf 100644 --- a/src/locales/fr.po +++ b/src/locales/fr.po @@ -42,7 +42,7 @@ msgstr "{label} ({count})" msgid "{visibleCount, plural, one {# comment} other {# comments}}" msgstr "{visibleCount, plural, one {# commentaire} other {# commentaires}}" -#: src/pages/PlaylistDetail.tsx:560 +#: src/pages/PlaylistDetail.tsx:561 #: src/pages/UserPublicProfile.tsx:732 msgid "← Back" msgstr "← Retour" @@ -180,13 +180,13 @@ msgstr "Impossible de se connecter au serveur de mises à jour en direct. Les vo #: src/components/form/FormActions.tsx:32 #: src/pages/Dump.tsx:359 #: src/pages/DumpEdit.tsx:433 -#: src/pages/PlaylistDetail.tsx:908 +#: src/pages/PlaylistDetail.tsx:911 #: src/pages/UserPublicProfile.tsx:1637 #: src/pages/UserPublicProfile.tsx:1707 msgid "Cancel" msgstr "Annuler" -#: src/pages/PlaylistDetail.tsx:733 +#: src/pages/PlaylistDetail.tsx:736 msgid "Cancel removal" msgstr "Annuler la suppression" @@ -278,7 +278,7 @@ msgstr "Onglet par défaut" #: src/components/CommentThread.tsx:376 #: src/components/CommentThread.tsx:382 #: src/components/ConfirmModal.tsx:16 -#: src/pages/PlaylistDetail.tsx:904 +#: src/pages/PlaylistDetail.tsx:907 msgid "Delete" msgstr "Supprimer" @@ -288,7 +288,7 @@ msgid "Delete dump" msgstr "Supprimer la reco" #: src/components/PlaylistCard.tsx:109 -#: src/pages/PlaylistDetail.tsx:746 +#: src/pages/PlaylistDetail.tsx:749 #: src/pages/UserPlaylists.tsx:465 msgid "Delete playlist" msgstr "Supprimer la collection" @@ -301,13 +301,13 @@ msgstr "Supprimer ce commentaire ?" msgid "Delete this dump? This cannot be undone." msgstr "Supprimer cette reco ? Cette action est irréversible." -#: src/pages/PlaylistDetail.tsx:745 +#: src/pages/PlaylistDetail.tsx:748 #: src/pages/UserPlaylists.tsx:464 msgid "Delete this playlist? This cannot be undone." msgstr "Supprimer cette collection ? Cette action est irréversible." #: src/components/PlaylistCreateForm.tsx:78 -#: src/pages/PlaylistDetail.tsx:865 +#: src/pages/PlaylistDetail.tsx:868 msgid "Description (optional)" msgstr "Description (facultatif)" @@ -353,7 +353,7 @@ msgstr "Plus tôt" #: src/components/CommentThread.tsx:367 #: src/pages/Dump.tsx:459 -#: src/pages/PlaylistDetail.tsx:613 +#: src/pages/PlaylistDetail.tsx:616 msgid "Edit" msgstr "Modifier" @@ -366,7 +366,7 @@ msgstr "Modifier le titre" #. placeholder {0}: relativeTime(playlist.updatedAt) #: src/components/CommentThread.tsx:317 #: src/pages/Dump.tsx:412 -#: src/pages/PlaylistDetail.tsx:652 +#: src/pages/PlaylistDetail.tsx:655 msgid "edited {0}" msgstr "modifié {0}" @@ -375,7 +375,7 @@ msgstr "modifié {0}" #. placeholder {0}: playlist.updatedAt.toLocaleString() #: src/components/CommentThread.tsx:315 #: src/pages/Dump.tsx:410 -#: src/pages/PlaylistDetail.tsx:649 +#: src/pages/PlaylistDetail.tsx:652 msgid "Edited {0}" msgstr "Modifié le {0}" @@ -424,7 +424,7 @@ msgstr "Impossible de publier le commentaire" msgid "Failed to post reply" msgstr "Impossible de publier la réponse" -#: src/pages/PlaylistDetail.tsx:896 +#: src/pages/PlaylistDetail.tsx:899 #: src/pages/UserPublicProfile.tsx:1640 #: src/pages/UserPublicProfile.tsx:1709 msgid "Failed to save" @@ -595,7 +595,7 @@ msgstr "Chargement de la reco…" msgid "Loading more…" msgstr "Chargement…" -#: src/pages/PlaylistDetail.tsx:544 +#: src/pages/PlaylistDetail.tsx:545 msgid "Loading playlist…" msgstr "Chargement de la collection…" @@ -688,7 +688,7 @@ msgstr "Nouveau mot de passe" msgid "New playlist" msgstr "Nouvelle collection" -#: src/pages/PlaylistDetail.tsx:668 +#: src/pages/PlaylistDetail.tsx:671 msgid "No dumps in this playlist yet." msgstr "Aucune reco dans cette collection pour l'instant." @@ -785,7 +785,7 @@ msgstr "Mot de passe mis à jour" msgid "Passwords do not match" msgstr "Les mots de passe ne correspondent pas" -#: src/pages/PlaylistDetail.tsx:851 +#: src/pages/PlaylistDetail.tsx:854 msgid "Playlist title" msgstr "Titre de la collection" @@ -825,22 +825,22 @@ msgstr "Publication…" #: src/components/PlaylistCard.tsx:73 #: src/components/PlaylistMembershipPanel.tsx:55 #: src/pages/Dump.tsx:418 -#: src/pages/PlaylistDetail.tsx:632 +#: src/pages/PlaylistDetail.tsx:635 msgid "private" msgstr "privé" #: src/components/form/SegmentedField.tsx:78 -#: src/pages/PlaylistDetail.tsx:890 +#: src/pages/PlaylistDetail.tsx:893 msgid "Private" msgstr "Privé" #: src/components/PlaylistCard.tsx:72 -#: src/pages/PlaylistDetail.tsx:631 +#: src/pages/PlaylistDetail.tsx:634 msgid "public" msgstr "public" #: src/components/form/SegmentedField.tsx:77 -#: src/pages/PlaylistDetail.tsx:883 +#: src/pages/PlaylistDetail.tsx:886 msgid "Public" msgstr "Public" @@ -873,7 +873,7 @@ msgstr "Connexe" msgid "Remove file" msgstr "Supprimer le fichier" -#: src/pages/PlaylistDetail.tsx:723 +#: src/pages/PlaylistDetail.tsx:726 msgid "Remove from playlist" msgstr "Retirer de la collection" @@ -918,7 +918,7 @@ msgstr "Rôle" #: src/components/CommentThread.tsx:328 #: src/pages/Dump.tsx:351 #: src/pages/DumpEdit.tsx:436 -#: src/pages/PlaylistDetail.tsx:915 +#: src/pages/PlaylistDetail.tsx:918 #: src/pages/UserPublicProfile.tsx:1629 #: src/pages/UserPublicProfile.tsx:1699 msgid "Save" @@ -927,7 +927,7 @@ msgstr "Enregistrer" #: src/components/ChangePasswordModal.tsx:100 #: src/components/CommentThread.tsx:329 #: src/pages/Dump.tsx:350 -#: src/pages/PlaylistDetail.tsx:911 +#: src/pages/PlaylistDetail.tsx:914 #: src/pages/ResetPassword.tsx:124 #: src/pages/UserPublicProfile.tsx:1626 #: src/pages/UserPublicProfile.tsx:1696 @@ -1019,7 +1019,7 @@ msgstr "Un titre est requis." msgid "Today" msgstr "Aujourd'hui" -#: src/pages/PlaylistDetail.tsx:735 +#: src/pages/PlaylistDetail.tsx:738 msgid "Undo" msgstr "Annuler" @@ -1068,7 +1068,7 @@ msgstr "Utilisateur" #: src/pages/UserPublicProfile.tsx:1246 msgid "User management" -msgstr "Gestion des utilisateurs" +msgstr "Gestion utilisateur" #: src/components/UserMenu.tsx:37 msgid "User menu" diff --git a/src/pages/PlaylistDetail.tsx b/src/pages/PlaylistDetail.tsx index ee736e4..4e9e6f5 100644 --- a/src/pages/PlaylistDetail.tsx +++ b/src/pages/PlaylistDetail.tsx @@ -25,6 +25,7 @@ import { deserializePlaylistWithDumps, } from "../model.ts"; import { playlistUrl } from "../utils/urls.ts"; +import { can } from "../utils/permissions.ts"; import { useAuth } from "../hooks/useAuth.ts"; import { useWS } from "../hooks/useWS.ts"; import { relativeTime } from "../utils/relativeTime.ts"; @@ -566,6 +567,8 @@ export function PlaylistDetail() { const { playlist } = state; const isOwner = !!user && user.id === playlist.userId; + // Owners edit their own playlists; moderators may edit any (playlist:moderate). + const canEdit = isOwner || can(user, "playlist:moderate"); // Active dumps in playlist order; fading dumps appended so they stay visible const activeDumps = playlist.dumps.filter((d) => activeDumpIds.has(d.id)); @@ -604,7 +607,7 @@ export function PlaylistDetail() { isPublic={playlist.isPublic} /> )} - {isOwner && ( + {canEdit && (