From b313f70d31390da3fd9652c799682e5532edb466 Mon Sep 17 00:00:00 2001 From: khannurien Date: Tue, 22 Sep 2026 19:25:19 +0000 Subject: [PATCH] =?UTF-8?q?v3:=20chat=20mentions=20stop=20going=20missing,?= =?UTF-8?q?=20and=20live=20events=20get=20a=20light=20sound=20=E2=80=94=20?= =?UTF-8?q?the=20server=20skips=20a=20mention=20notification=20for=20anyon?= =?UTF-8?q?e=20with=20the=20chatbox=20open,=20on=20the=20grounds=20that=20?= =?UTF-8?q?they're=20reading=20it,=20but=20"open"=20also=20meant=20a=20cha?= =?UTF-8?q?tbox=20left=20open=20in=20a=20background=20tab,=20and=20the=20s?= =?UTF-8?q?ame=20modal=20cleared=20the=20unread=20badge=20on=20every=20arr?= =?UTF-8?q?ival,=20so=20a=20mention=20there=20left=20no=20bell,=20no=20bad?= =?UTF-8?q?ge=20and=20no=20trace.=20Reading=20the=20chat=20now=20means=20o?= =?UTF-8?q?pen=20and=20on=20screen:=20the=20focus=20flag=20is=20re-announc?= =?UTF-8?q?ed=20on=20every=20visibilitychange,=20and=20the=20unread=20coun?= =?UTF-8?q?t=20only=20clears=20while=20the=20tab=20is=20visible.=20A=20rec?= =?UTF-8?q?onnect=20with=20the=20chatbox=20open=20refetches=20the=20latest?= =?UTF-8?q?=20page,=20since=20whatever=20was=20said=20while=20the=20socket?= =?UTF-8?q?=20was=20down=20never=20reached=20the=20live=20buffer.=20The=20?= =?UTF-8?q?server's=20mention=20regex=20excluded=20a=20backslash=20and=20t?= =?UTF-8?q?he=20letter=20w=20rather=20than=20word=20characters=20(`\\w`=20?= =?UTF-8?q?inside=20a=20class),=20so=20an=20email=20address=20mentioned=20?= =?UTF-8?q?its=20domain;=20it=20now=20matches=20the=20autocomplete's=20rul?= =?UTF-8?q?e.=20On=20top=20of=20that,=20sounds=20synthesised=20with=20Web?= =?UTF-8?q?=20Audio=20=E2=80=94=20a=20soft=20blip=20for=20chat=20you=20are?= =?UTF-8?q?n't=20looking=20at,=20throttled,=20and=20a=20two-note=20chime?= =?UTF-8?q?=20for=20notifications=20and=20for=20a=20mention=20you're=20rea?= =?UTF-8?q?ding=20live,=20which=20gets=20no=20notification=20of=20its=20ow?= =?UTF-8?q?n=20=E2=80=94=20claimed=20through=20localStorage=20so=20several?= =?UTF-8?q?=20tabs=20chime=20once,=20with=20an=20on/off=20toggle=20under?= =?UTF-8?q?=20a=20new=20Notifications=20section=20in=20settings.=20The=20t?= =?UTF-8?q?ab=20title=20carries=20the=20unread=20count,=20composed=20with?= =?UTF-8?q?=20the=20page=20title=20in=20useDocumentTitle=20so=20navigation?= =?UTF-8?q?=20doesn't=20drop=20it.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- api/services/notification-service.ts | 3 +- src/components/ChatModal.tsx | 40 +++++- src/contexts/WSProvider.tsx | 66 ++++++++- src/hooks/useDocumentTitle.ts | 23 ++- src/hooks/useSoundsEnabled.ts | 18 +++ src/locales/en.js | 2 +- src/locales/en.po | 207 ++++++++++++++------------- src/locales/fr.js | 2 +- src/locales/fr.po | 207 ++++++++++++++------------- src/pages/UserPublicProfile.tsx | 35 +++++ src/utils/sound.ts | 123 ++++++++++++++++ 11 files changed, 520 insertions(+), 206 deletions(-) create mode 100644 src/hooks/useSoundsEnabled.ts create mode 100644 src/utils/sound.ts diff --git a/api/services/notification-service.ts b/api/services/notification-service.ts index 8c0111d0..a859282f 100644 --- a/api/services/notification-service.ts +++ b/api/services/notification-service.ts @@ -9,7 +9,8 @@ import { db, isNotificationRow, notificationRowToApi } from "../model/db.ts"; import { sendToUser } from "./ws-service.ts"; // Regex: matches @username not already inside a markdown link ([...] or (...) -const MENTION_RE = /(? void }) { clearUnreadChat, setChatTyping, setChatOpen, + connectionEpoch, } = useWS(); const { authFetch, user } = useAuth(); const canModerate = can(user, "chat:moderate"); @@ -352,10 +353,43 @@ export function ChatModal({ onClose }: { onClose: () => void }) { }; }, [authFetch]); - // Clear the unread badge on open and as new messages arrive while open. + // Messages sent while the socket was down never reached the live buffer: + // after a reconnect, fetch the latest page again and fold it into history + // (the merge above dedupes against what's already shown). + const seenEpochRef = useRef(connectionEpoch); useEffect(() => { - clearUnreadChat(); - }, [chatMessages, clearUnreadChat]); + if (connectionEpoch === seenEpochRef.current) return; + seenEpochRef.current = connectionEpoch; + let cancelled = false; + (async () => { + try { + const res = await authFetch(`${API_URL}/api/chat?limit=${PAGE_SIZE}`); + const json = await res.json(); + if (cancelled || !json.success) return; + const items = (json.data as RawChatMessage[]).map(deserializeChatMessage); + setHistory((prev) => [...prev, ...items]); + } catch { + // The next reconnect or reopen will try again. + } + })(); + return () => { + cancelled = true; + }; + }, [authFetch, connectionEpoch]); + + // Whether the tab is on screen: a chatbox left open in a background tab isn't + // being read, so its messages should stay unread until the user comes back. + const [tabVisible, setTabVisible] = useState(() => !document.hidden); + useEffect(() => { + const onVisibility = () => setTabVisible(!document.hidden); + document.addEventListener("visibilitychange", onVisibility); + return () => document.removeEventListener("visibilitychange", onVisibility); + }, []); + + // Clear the unread badge on open and as new messages arrive while read. + useEffect(() => { + if (tabVisible) clearUnreadChat(); + }, [chatMessages, clearUnreadChat, tabVisible]); // Tell the server the chatbox is open so it skips redundant mention // notifications while the user is reading; clear the flag on close. diff --git a/src/contexts/WSProvider.tsx b/src/contexts/WSProvider.tsx index d58004e3..65b21253 100644 --- a/src/contexts/WSProvider.tsx +++ b/src/contexts/WSProvider.tsx @@ -39,6 +39,8 @@ import { } from "../model.ts"; import { t } from "@lingui/core/macro"; import { useAuth } from "../hooks/useAuth.ts"; +import { setTitleBadge } from "../hooks/useDocumentTitle.ts"; +import { playChime } from "../utils/sound.ts"; interface WSProviderProps { children: ReactNode; @@ -55,6 +57,17 @@ const MAX_RECENT_DUMPS = 100; // Older messages remain reachable via the /api/chat history endpoint. const MAX_CHAT_MESSAGES = 200; +// Mirrors MENTION_RE in api/services/notification-service.ts. +const MENTION_RE = /(?; rollback: () => void; @@ -167,6 +180,7 @@ export function WSProvider({ children }: WSProviderProps) { const likeCountsRef = useRef(likeCounts); const myLikesRef = useRef(myLikes); const userIdRef = useRef(userId); + const usernameRef = useRef(user?.username ?? null); // Stable ref for logout so the effect doesn't reconnect when the function // reference changes on re-renders. const onForceLogoutRef = useRef(logout); @@ -176,6 +190,7 @@ export function WSProvider({ children }: WSProviderProps) { likeCountsRef.current = likeCounts; myLikesRef.current = myLikes; userIdRef.current = userId; + usernameRef.current = user?.username ?? null; onForceLogoutRef.current = logout; }); @@ -183,6 +198,13 @@ export function WSProvider({ children }: WSProviderProps) { // Tracks whether the chatbox is open so it can be re-announced to the server // after a reconnect (the fresh socket starts with the flag cleared). const chatOpenRef = useRef(false); + // The user is only actually reading the chat when the chatbox is open *and* + // the tab is on screen: a chatbox left open in a background tab must not + // swallow mention notifications or unread counts. + const isViewingChat = useCallback( + () => chatOpenRef.current && !document.hidden, + [], + ); // Tracks pending optimistic votes: dumpId → pending rollback handler const pendingRef = useRef>( new Map(), @@ -309,7 +331,7 @@ export function WSProvider({ children }: WSProviderProps) { if (welcomeCount > 1) setConnectionEpoch((n) => n + 1); // Restore the chat-open flag on the fresh connection so mention // suppression keeps working across reconnects. - if (chatOpenRef.current) { + if (isViewingChat()) { ws.send( JSON.stringify( { type: "chat_focus", open: true } satisfies OutgoingWSMessage, @@ -496,6 +518,7 @@ export function WSProvider({ children }: WSProviderProps) { const notification = deserializeNotification(msg.notification); setLastNotification(notification); setUnreadNotificationCount((prev) => prev + 1); + playChime("notify", notification.id); break; } @@ -508,6 +531,15 @@ export function WSProvider({ children }: WSProviderProps) { // Count as unread unless it's the current user's own message. if (message.userId !== userIdRef.current) { setUnreadChatCount((prev) => prev + 1); + const username = usernameRef.current; + if (username && mentionsUser(message.body, username)) { + // Someone reading the chat gets no notification for a mention + // (the server skips it), so the chime has to come from here; + // otherwise the notification that follows brings its own. + if (isViewingChat()) playChime("notify", message.id); + } else if (!isViewingChat()) { + playChime("chat", message.id); + } } break; } @@ -610,6 +642,7 @@ export function WSProvider({ children }: WSProviderProps) { clearPendingVote, clearAllPendingLikes, clearPendingLike, + isViewingChat, token, ]); @@ -817,16 +850,39 @@ export function WSProvider({ children }: WSProviderProps) { } }, []); - const setChatOpen = useCallback((open: boolean) => { - chatOpenRef.current = open; + // Tell the server whether the user is reading the chat right now (chatbox + // open and tab visible), so it knows when a mention notification is moot. + const sendChatFocus = useCallback(() => { if (socketRef.current?.readyState === WebSocket.OPEN) { socketRef.current.send( JSON.stringify( - { type: "chat_focus", open } satisfies OutgoingWSMessage, + { + type: "chat_focus", + open: isViewingChat(), + } satisfies OutgoingWSMessage, ), ); } - }, []); + }, [isViewingChat]); + + const setChatOpen = useCallback((open: boolean) => { + chatOpenRef.current = open; + sendChatFocus(); + }, [sendChatFocus]); + + // Re-announce chat focus whenever the tab is hidden or shown. + useEffect(() => { + const onVisibility = () => { + if (chatOpenRef.current) sendChatFocus(); + }; + document.addEventListener("visibilitychange", onVisibility); + return () => document.removeEventListener("visibilitychange", onVisibility); + }, [sendChatFocus]); + + // Unread count in the tab title, so a background tab shows there's news. + useEffect(() => { + setTitleBadge(unreadNotificationCount + unreadChatCount); + }, [unreadNotificationCount, unreadChatCount]); const value: WSContextValue = useMemo(() => ({ wsStatus, diff --git a/src/hooks/useDocumentTitle.ts b/src/hooks/useDocumentTitle.ts index dcd06270..d0365593 100644 --- a/src/hooks/useDocumentTitle.ts +++ b/src/hooks/useDocumentTitle.ts @@ -23,6 +23,19 @@ function readSiteEmoji(): string { export const SITE_EMOJI = readSiteEmoji(); +// The page title and the unread badge are set from different places (each +// page, and the live-updates provider), so both are kept here and composed. +// `null` base means no page has set one yet: keep the server-rendered title. +let baseTitle: string | null = null; +let badge = 0; + +function applyTitle() { + const base = baseTitle ?? document.title.replace(/^\(\d+\+?\) /, ""); + document.title = badge > 0 + ? `(${badge > 99 ? "99+" : badge}) ${base}` + : base; +} + /** * Keeps document.title in sync with the current page during client-side * navigation. Without this the tab title only ever reflects the server-rendered @@ -37,6 +50,14 @@ export const SITE_EMOJI = readSiteEmoji(); export function useDocumentTitle(title: string | null | undefined): void { useEffect(() => { if (title === undefined) return; - document.title = title ? `${title} · ${SITE_NAME}` : SITE_NAME; + baseTitle = title ? `${title} · ${SITE_NAME}` : SITE_NAME; + applyTitle(); }, [title]); } + +/** Prefixes the tab title with an unread count, e.g. "(3) Chat · gerbeur". */ +export function setTitleBadge(count: number): void { + if (count === badge) return; + badge = count; + applyTitle(); +} diff --git a/src/hooks/useSoundsEnabled.ts b/src/hooks/useSoundsEnabled.ts new file mode 100644 index 00000000..98357fce --- /dev/null +++ b/src/hooks/useSoundsEnabled.ts @@ -0,0 +1,18 @@ +import { useCallback, useState } from "react"; +import { readSoundsEnabled, writeSoundsEnabled } from "../utils/sound.ts"; + +/** + * User preference for notification and chat sounds, persisted to localStorage + * (on by default). The chime itself reads the stored value at play time, so + * this hook only drives the settings toggle. + */ +export function useSoundsEnabled(): [boolean, (enabled: boolean) => void] { + const [enabled, setEnabledState] = useState(readSoundsEnabled); + + const setEnabled = useCallback((next: boolean) => { + writeSoundsEnabled(next); + setEnabledState(next); + }, []); + + return [enabled, setEnabled]; +} diff --git a/src/locales/en.js b/src/locales/en.js index 1f525ceb..5c3e7bc5 100644 --- a/src/locales/en.js +++ b/src/locales/en.js @@ -1 +1 @@ -/*eslint-disable*/module.exports={messages:JSON.parse("{\"-K9EZb\":[\"Add email…\"],\"-OxI15\":[\"Followed playlists\"],\"-Ya-b9\":[\"Failed to save\"],\"-siMqD\":[\"Journal\"],\"1CalO6\":[\"Style\"],\"1HfJWf\":[\"Search dumps, users, playlists…\"],\"1cbYY_\":[\"Upvoted (\",[\"0\"],[\"1\"],\")\"],\"1njn7W\":[\"Light\"],\"1utXA6\":[\"Dumps\"],\"26iNma\":[\"Post comment\"],\"2DoBvq\":[\"Feeds\"],\"2Hlmdt\":[\"Write a reply…\"],\"2_DHgA\":[\"URL slug\"],\"2nrsEn\":[\"deleted message\"],\"2ygf_L\":[\"← Back\"],\"3KKSM4\":[\"private\"],\"3QuNUr\":[\"Several people are typing…\"],\"3SEwY1\":[\"Delete category \\\"\",[\"0\"],\"\\\"? This cannot be undone.\"],\"3yfh3D\":[\"<0>\",[\"0\"],\" followed your playlist <1>\",[\"1\"],\"\"],\"49voTZ\":[[\"label\"],\" (\",[\"count\"],\")\"],\"4B6w_o\":[\"Dumped!\"],\"4GKuCs\":[\"Login failed\"],\"4HH9iB\":[\"Not following anyone yet.\"],\"4OjqAQ\":[\"Keep editing\"],\"4RtQ1k\":[\"Unfollow \",[\"targetUsername\"]],\"4c-qBx\":[\"Your password has been changed. You can now log in.\"],\"4yj9xV\":[\"Live updates are temporarily disconnected. Trying to reconnect…\"],\"5KHDCi\":[[\"0\"],\" and \",[\"1\"],\" are typing…\"],\"5TviPn\":[\"Cancel search\"],\"5cC8f2\":[\"Edited \",[\"0\"]],\"5oD9f_\":[\"Earlier\"],\"6Qly-0\":[\"a comment\"],\"6YE0P6\":[\"Editing \",[\"0\"]],\"6YtxFj\":[\"Name\"],\"6gRgw8\":[\"Retry\"],\"7PHCIN\":[\"Cancel removal\"],\"7PzzBU\":[\"User\"],\"7d1a0d\":[\"Public\"],\"7sNhEz\":[\"Username\"],\"8F1i42\":[\"Page not found\"],\"8Ug9jB\":[\"Related\"],\"8ZsakT\":[\"Password\"],\"92o1-U\":[\"Failed to delete\"],\"9BruTc\":[\"Why?\"],\"9l4qcT\":[\"Drop a replacement here\"],\"9uI_rE\":[\"Undo\"],\"9xDZu_\":[\"Could not load.\"],\"A0y396\":[\"+ Invite someone\"],\"A1taO8\":[\"Search\"],\"AEfu-D\":[\"In collections\"],\"AHZflp\":[\"Chat\"],\"AQbgNR\":[\"Follow \",[\"targetUsername\"]],\"AS7WoE\":[\"Start fresh\"],\"ATGYL1\":[\"Email address\"],\"AZctoV\":[[\"0\",\"plural\",{\"one\":[\"#\",\" comment\"],\"other\":[\"#\",\" comments\"]}]],\"Ade-6d\":[\"Live updates unavailable.\"],\"AeXO77\":[\"Account\"],\"AnRu_j\":[\"Cancel reply\"],\"BlpJ2G\":[\"Picked up where you left off.\"],\"CI50ct\":[\"Upvoted\"],\"C_qQE4\":[\"Already dumped by \",[\"0\"],\" \",[\"1\"]],\"Cj24wt\":[\"Registering…\"],\"D8DRYV\":[\"Delete this message?\"],\"DPfwMq\":[\"Done\"],\"DdeHXH\":[\"Delete this dump? This cannot be undone.\"],\"Dp1JhP\":[\"<0>\",[\"0\"],\" upvoted <1>\",[\"1\"],\"\"],\"ECiS12\":[\"View dump →\"],\"ED8n0z\":[\"the chat\"],\"Evcm2Z\":[\"Close without posting? The attached file won't be kept — everything you typed will.\"],\"ExR0Fr\":[\"URL is required.\"],\"F5Js1v\":[\"Unfollow playlist\"],\"FdTlEG\":[\"Track \",[\"0\"]],\"FgAxTj\":[\"Log out\"],\"Fxf4jq\":[\"Description (optional)\"],\"GCWCCo\":[\"Close player\"],\"GDvlUT\":[\"Role\"],\"GLukaH\":[\"Moderator\"],\"GNSsCc\":[\"Failed to create playlist\"],\"GQ38dl\":[\"Chat (\",[\"unreadChatCount\"],\" unread)\"],\"GY6uSc\":[\"Next →\"],\"GbqhrN\":[[\"0\"],\"–\",[\"1\"],\" characters: letters, numbers, or underscores\"],\"GptGxg\":[\"Change password\"],\"H4MGkI\":[\"Expand player\"],\"H4o4sk\":[\"No one yet.\"],\"H8pzW-\":[\"Failed to update avatar\"],\"HTLDA4\":[\"Loading more…\"],\"I-x669\":[\"Invitees\"],\"IO9KL_\":[\"Next track\"],\"IZX7TO\":[\"Failed to generate invite\"],\"IagCbF\":[\"URL\"],\"ImOQa9\":[\"Reply\"],\"JJ-Bhk\":[\"Your email address\"],\"JRQitQ\":[\"Confirm new password\"],\"JXr41k\":[\"<0>\",[\"0\"],\" commented on <1>\",[\"1\"],\"\"],\"Jd58Fo\":[\"Hot\"],\"Jf0PuK\":[\"Go to login\"],\"JlFcis\":[\"Send\"],\"K7tIrx\":[\"Category\"],\"KDGWg5\":[\"Remove from playlist\"],\"KKZxBU\":[\"Uploading \",[\"0\"],\"%\"],\"K_F6pa\":[\"Saving…\"],\"L-rMC9\":[\"Reset to default\"],\"LLyMkV\":[\"Followed (\",[\"0\"],[\"1\"],\")\"],\"MHrjPM\":[\"Title\"],\"MKEPCY\":[\"Follow\"],\"MTb2Ot\":[\"No messages yet. Say hello!\"],\"Mj6oCJ\":[[\"0\"],\" is typing…\"],\"N40H-G\":[\"All\"],\"N6j2JH\":[\"Edit \",[\"0\"]],\"NUrY9o\":[\"Categories\"],\"Oprv1v\":[\"Password (min. \",[\"0\"],\" characters)\"],\"Oz0N9s\":[\"new\"],\"PUe8Yk\":[\"Collapse player\"],\"PiH3UR\":[\"Copied!\"],\"Pn2B7_\":[\"Current password\"],\"Pwqkdw\":[\"Loading…\"],\"Q6n4F4\":[\"Refresh metadata\"],\"QGaQRo\":[\"Failed to update role\"],\"QKsaQr\":[\"or <0>browse files\"],\"QLtPBd\":[\"No dumps in this playlist yet.\"],\"R9Khdg\":[\"Auto\"],\"RCcPrX\":[\"Delete this playlist? This cannot be undone.\"],\"RTksSy\":[\"<0>\",[\"0\"],\" started following you\"],\"RaKjrM\":[\"Failed to save edit\"],\"RcUHRT\":[\"Followed\"],\"RfwZxd\":[\"Reset password\"],\"Rrp6-J\":[\"Log in to like\"],\"RvVJBH\":[\"Refreshing stream…\"],\"SBTElJ\":[\"Searching…\"],\"Sad2tK\":[\"Sending…\"],\"So7gCD\":[\"Paste a link…\"],\"StovX6\":[\"This page does not exist.\"],\"Sxm8rQ\":[\"Users\"],\"T9bjWt\":[\"<0>\",[\"0\"],\" was added to <1>\",[\"1\"],\"\"],\"TM1ZbA\":[\"Playlists (\",[\"0\"],[\"1\"],\")\"],\"TN382O\":[\"Invalid link\"],\"Tv9vbB\":[\"Follow playlist\"],\"Tz0i8g\":[\"Settings\"],\"U3pytU\":[\"Admin\"],\"UNMVei\":[\"Forgot password?\"],\"UOZith\":[\"Failed to post\"],\"URAieT\":[\"Log in to vote\"],\"VCoEm-\":[\"Back to login\"],\"VKsaTi\":[\"Replying to\"],\"V_e7nf\":[\"Set new password\"],\"VnNJbN\":[\"From playlists\"],\"VskHIx\":[\"Delete category\"],\"VyTYmS\":[\"Change avatar\"],\"W9FRBT\":[\"Like\"],\"WE_r9K\":[\"Failed to create category\"],\"WfffrI\":[\"slug\"],\"WhimMi\":[\"Reset failed\"],\"WpXcBJ\":[\"Nothing here yet.\"],\"XJVYo8\":[\"Why & where\"],\"XJy2oN\":[\"Logging in…\"],\"Xan6QP\":[\"New dump\"],\"XgRtUf\":[\"Could not change password\"],\"Xi0Mn4\":[\"← Back to profile\"],\"XnL-Eu\":[\"No users match \\\"\",[\"q\"],\"\\\".\"],\"Xs2Lez\":[\"This reset link is missing or malformed.\"],\"YK1Dhc\":[\"a post\"],\"YcjhhX\":[\"User management\"],\"Ye9RMF\":[\"<0>\",[\"0\"],\" liked your comment on <1>\",[\"1\"],\"\"],\"YpkCca\":[\"No followed playlists yet.\"],\"ZBdbv9\":[\"Edit title\"],\"ZCpU0u\":[\"No playlists match \\\"\",[\"q\"],\"\\\".\"],\"ZmD2o6\":[\"Create & Add\"],\"_3O5R_\":[\"Request failed\"],\"_DwR-n\":[\"Creating…\"],\"_R_sGB\":[\"Password changed successfully.\"],\"_aept4\":[\"Post reply\"],\"_nT6AE\":[\"New password\"],\"_t4W-i\":[\"From people\"],\"aAIQg2\":[\"Appearance\"],\"aDvLhk\":[\"Add a comment…\"],\"b3Thhd\":[\"Upload failed\"],\"b8XMJ8\":[[\"visibleCount\",\"plural\",{\"one\":[\"#\",\" comment\"],\"other\":[\"#\",\" comments\"]}]],\"bQhwn-\":[\"Loading playlist…\"],\"cILfnJ\":[\"Remove file\"],\"cYP9Sb\":[\"+ Playlist\"],\"cbeBbZ\":[\"At least \",[\"0\"],\" characters\"],\"cnGeoo\":[\"Delete\"],\"d8DZWS\":[\"Open search\"],\"dAs22m\":[\"Replace file\"],\"dEgA5A\":[\"Cancel\"],\"dMizp8\":[\"New playlist\"],\"dqwCfR\":[\"Couldn't load a preview for this link — check it, or post it as-is and refresh the preview later.\"],\"eFSqvc\":[\"Failed to post reply\"],\"eOfXq3\":[\"Title is required.\"],\"ePK91l\":[\"Edit\"],\"eaUTwS\":[\"Send reset link\"],\"ecUA8p\":[\"Today\"],\"ef9nPf\":[\"Loading dump…\"],\"eloqXQ\":[\"You can post it anyway.\"],\"en9o7K\":[\"Failed to post comment\"],\"etFQQS\":[\"What makes it worth it?\"],\"fC6mXb\":[\"Upvote\"],\"fI-mNw\":[\"Playlists\"],\"f_akpP\":[\"Max 50 MB\"],\"fgLNSM\":[\"Register\"],\"gANddk\":[\"Uploading…\"],\"gGx5tM\":[\"Editing\"],\"gIQQwD\":[\"Failed to load\"],\"gL9KKi\":[\"Who can see it\"],\"gLfZlz\":[\"Add to playlist\"],\"gjJ-sb\":[\"Can't connect to the live updates server. Upvotes and notifications may not sync until it reconnects.\"],\"goWEhR\":[\"Previous track\"],\"hBuUKa\":[\"Change password…\"],\"hD7w09\":[\"You've reached the end.\"],\"hJSliC\":[\"<0>\",[\"0\"],\" posted <1>\",[\"1\"],\"\"],\"hT8hgO\":[\"Reset to the suggested title\"],\"hYgDIe\":[\"Create\"],\"he3ygx\":[\"Copy\"],\"hpCc_Z\":[\"Close anyway\"],\"i7K_Te\":[\"Who am I?\"],\"iDNBZe\":[\"Notifications\"],\"iWpEwy\":[\"Go home\"],\"ipYn7W\":[\"If that address is registered you'll receive a reset link shortly.\"],\"isRobC\":[\"New\"],\"jbernk\":[\"Loading profile…\"],\"joEmfT\":[\"Server unreachable\"],\"jrZTZl\":[\"No dumps yet. Be the first!\"],\"k112DD\":[\"Load older messages\"],\"k36iJ-\":[\"Failed to save category\"],\"kLttbL\":[\"Registration failed\"],\"lUDifl\":[\"Created (\",[\"0\"],[\"1\"],\")\"],\"lUanmi\":[\"You'll be notified when someone follows your playlists, upvotes your dumps, or posts new content.\"],\"lY5h1V\":[[\"0\",\"plural\",{\"one\":[\"#\",\" dump\"],\"other\":[\"#\",\" dumps\"]}]],\"lcfvr_\":[\"Delete this comment?\"],\"lpIMne\":[\"Passwords do not match\"],\"mb_Wju\":[\"Drop a file here, or paste one\"],\"mt6O6E\":[\"This is a mirage.\"],\"nbm5sI\":[\"No dumps match \\\"\",[\"q\"],\"\\\".\"],\"nrjqON\":[\"Checking invite…\"],\"nwtY4N\":[\"Something went wrong\"],\"nx4kaN\":[\"Could not save\"],\"oTYvvN\":[[\"0\",\"plural\",{\"one\":[\"and \",\"#\",\" earlier dump\"],\"other\":[\"and \",\"#\",\" earlier dumps\"]}]],\"ogtYkT\":[\"Password updated\"],\"pCpd9p\":[\"<0>\",[\"0\"],\" mentioned you in <1>\",[\"where\"],\"\"],\"pSheLH\":[\"No invitees yet.\"],\"pvnfJD\":[\"Dark\"],\"qIMfNQ\":[\"Delete playlist\"],\"qbDAcy\":[\"Dump it\"],\"qgx_78\":[\"Follow some public playlists to see their dumps here.\"],\"qsqt0w\":[\"Invite link\"],\"qvFa8r\":[\"public\"],\"qvz_Pp\":[\"Dump\"],\"rCbqPX\":[\"This invite link is missing, expired, or already used.\"],\"rDMzDU\":[\"Type a message…\"],\"rg9pXu\":[\"Search failed\"],\"rtpJqV\":[\"Dumps (\",[\"0\"],[\"1\"],\")\"],\"sEXJPi\":[\"Link or file\"],\"sGeXL3\":[\"Thumbnail\"],\"sQia9P\":[\"Log in\"],\"sTiqbm\":[\"invited by\"],\"sdP5Aa\":[\"[deleted]\"],\"sgX7B-\":[\"Single use, valid for 7 days.\"],\"shHs8T\":[\"Enter a query to search.\"],\"smeBfS\":[\"Invalid invite\"],\"tfDRzk\":[\"Save\"],\"tvmuQ0\":[\"Color scheme\"],\"u1lDX2\":[\"Fetching preview…\"],\"uD0qXQ\":[\"Drop a file here\"],\"uMGUnV\":[\"No playlists yet.\"],\"ub1EEL\":[\"edited \",[\"0\"]],\"uomLas\":[\"Add category\"],\"vJBF1r\":[\"Posting…\"],\"vLhLLO\":[\"Notifications (\",[\"unreadNotificationCount\"],\" unread)\"],\"vQMkHu\":[\"Remove vote\"],\"vuosjb\":[\"User menu\"],\"wbXKOv\":[\"File too large (max 50 MB).\"],\"wckWOP\":[\"Manage\"],\"wixIgH\":[\"Already have an account? <0>Log in\"],\"x6tjuK\":[\"Default tab\"],\"xEWkgZ\":[\"← Back to all dumps\"],\"xPHtx0\":[\"Submit search\"],\"xVuNgt\":[\"+ New playlist\"],\"xc9O_u\":[\"Delete dump\"],\"y6sq5j\":[\"Following\"],\"y7oaHj\":[\"Playlist title\"],\"yA_6BX\":[\"View all →\"],\"yBBtRm\":[\"Follow some users to see their dumps here.\"],\"yQ2kGp\":[\"Load more\"],\"y_0uwd\":[\"Yesterday\"],\"yz7wBu\":[\"Close\"],\"yzF66j\":[\"Link\"],\"z0ROB3\":[\"Remove like\"],\"z1uNN0\":[\"No emoji found.\"],\"zVuxvN\":[\"Refreshing…\"],\"zwBp5t\":[\"Private\"]}")}; \ No newline at end of file +/*eslint-disable*/module.exports={messages:JSON.parse("{\"-K9EZb\":[\"Add email…\"],\"-OxI15\":[\"Followed playlists\"],\"-Ya-b9\":[\"Failed to save\"],\"-siMqD\":[\"Journal\"],\"1CalO6\":[\"Style\"],\"1HfJWf\":[\"Search dumps, users, playlists…\"],\"1cbYY_\":[\"Upvoted (\",[\"0\"],[\"1\"],\")\"],\"1njn7W\":[\"Light\"],\"1utXA6\":[\"Dumps\"],\"26iNma\":[\"Post comment\"],\"2DoBvq\":[\"Feeds\"],\"2Hlmdt\":[\"Write a reply…\"],\"2_DHgA\":[\"URL slug\"],\"2nrsEn\":[\"deleted message\"],\"2ygf_L\":[\"← Back\"],\"3KKSM4\":[\"private\"],\"3QuNUr\":[\"Several people are typing…\"],\"3SEwY1\":[\"Delete category \\\"\",[\"0\"],\"\\\"? This cannot be undone.\"],\"3yfh3D\":[\"<0>\",[\"0\"],\" followed your playlist <1>\",[\"1\"],\"\"],\"49voTZ\":[[\"label\"],\" (\",[\"count\"],\")\"],\"4B6w_o\":[\"Dumped!\"],\"4GKuCs\":[\"Login failed\"],\"4HH9iB\":[\"Not following anyone yet.\"],\"4OjqAQ\":[\"Keep editing\"],\"4RtQ1k\":[\"Unfollow \",[\"targetUsername\"]],\"4c-qBx\":[\"Your password has been changed. You can now log in.\"],\"4yj9xV\":[\"Live updates are temporarily disconnected. Trying to reconnect…\"],\"5KHDCi\":[[\"0\"],\" and \",[\"1\"],\" are typing…\"],\"5TviPn\":[\"Cancel search\"],\"5cC8f2\":[\"Edited \",[\"0\"]],\"5oD9f_\":[\"Earlier\"],\"6Qly-0\":[\"a comment\"],\"6YE0P6\":[\"Editing \",[\"0\"]],\"6YtxFj\":[\"Name\"],\"6gRgw8\":[\"Retry\"],\"7PHCIN\":[\"Cancel removal\"],\"7PzzBU\":[\"User\"],\"7d1a0d\":[\"Public\"],\"7sNhEz\":[\"Username\"],\"8F1i42\":[\"Page not found\"],\"8Ug9jB\":[\"Related\"],\"8ZsakT\":[\"Password\"],\"92o1-U\":[\"Failed to delete\"],\"9BruTc\":[\"Why?\"],\"9l4qcT\":[\"Drop a replacement here\"],\"9uI_rE\":[\"Undo\"],\"9xDZu_\":[\"Could not load.\"],\"A0y396\":[\"+ Invite someone\"],\"A1taO8\":[\"Search\"],\"AEfu-D\":[\"In collections\"],\"AHZflp\":[\"Chat\"],\"AQbgNR\":[\"Follow \",[\"targetUsername\"]],\"AS7WoE\":[\"Start fresh\"],\"ATGYL1\":[\"Email address\"],\"AZctoV\":[[\"0\",\"plural\",{\"one\":[\"#\",\" comment\"],\"other\":[\"#\",\" comments\"]}]],\"Ade-6d\":[\"Live updates unavailable.\"],\"AeXO77\":[\"Account\"],\"AnRu_j\":[\"Cancel reply\"],\"BlpJ2G\":[\"Picked up where you left off.\"],\"CI50ct\":[\"Upvoted\"],\"C_qQE4\":[\"Already dumped by \",[\"0\"],\" \",[\"1\"]],\"Cj24wt\":[\"Registering…\"],\"D8DRYV\":[\"Delete this message?\"],\"DPfwMq\":[\"Done\"],\"DdeHXH\":[\"Delete this dump? This cannot be undone.\"],\"Dp1JhP\":[\"<0>\",[\"0\"],\" upvoted <1>\",[\"1\"],\"\"],\"ECiS12\":[\"View dump →\"],\"ED8n0z\":[\"the chat\"],\"Evcm2Z\":[\"Close without posting? The attached file won't be kept — everything you typed will.\"],\"ExR0Fr\":[\"URL is required.\"],\"F5Js1v\":[\"Unfollow playlist\"],\"FdTlEG\":[\"Track \",[\"0\"]],\"FgAxTj\":[\"Log out\"],\"Fxf4jq\":[\"Description (optional)\"],\"GCWCCo\":[\"Close player\"],\"GDvlUT\":[\"Role\"],\"GLukaH\":[\"Moderator\"],\"GNSsCc\":[\"Failed to create playlist\"],\"GQ38dl\":[\"Chat (\",[\"unreadChatCount\"],\" unread)\"],\"GY6uSc\":[\"Next →\"],\"GbqhrN\":[[\"0\"],\"–\",[\"1\"],\" characters: letters, numbers, or underscores\"],\"GptGxg\":[\"Change password\"],\"H4MGkI\":[\"Expand player\"],\"H4o4sk\":[\"No one yet.\"],\"H8pzW-\":[\"Failed to update avatar\"],\"HTLDA4\":[\"Loading more…\"],\"I-x669\":[\"Invitees\"],\"IO9KL_\":[\"Next track\"],\"IZX7TO\":[\"Failed to generate invite\"],\"IagCbF\":[\"URL\"],\"ImOQa9\":[\"Reply\"],\"JJ-Bhk\":[\"Your email address\"],\"JRQitQ\":[\"Confirm new password\"],\"JXr41k\":[\"<0>\",[\"0\"],\" commented on <1>\",[\"1\"],\"\"],\"Jd58Fo\":[\"Hot\"],\"Jf0PuK\":[\"Go to login\"],\"JlFcis\":[\"Send\"],\"K7tIrx\":[\"Category\"],\"KDGWg5\":[\"Remove from playlist\"],\"KKZxBU\":[\"Uploading \",[\"0\"],\"%\"],\"K_F6pa\":[\"Saving…\"],\"KtYF-t\":[\"Sounds\"],\"L-rMC9\":[\"Reset to default\"],\"LLyMkV\":[\"Followed (\",[\"0\"],[\"1\"],\")\"],\"MHrjPM\":[\"Title\"],\"MKEPCY\":[\"Follow\"],\"MTb2Ot\":[\"No messages yet. Say hello!\"],\"Mj6oCJ\":[[\"0\"],\" is typing…\"],\"N40H-G\":[\"All\"],\"N6j2JH\":[\"Edit \",[\"0\"]],\"NUrY9o\":[\"Categories\"],\"Oprv1v\":[\"Password (min. \",[\"0\"],\" characters)\"],\"Oz0N9s\":[\"new\"],\"PUe8Yk\":[\"Collapse player\"],\"PiH3UR\":[\"Copied!\"],\"Pn2B7_\":[\"Current password\"],\"Pwqkdw\":[\"Loading…\"],\"Q6n4F4\":[\"Refresh metadata\"],\"QGaQRo\":[\"Failed to update role\"],\"QKsaQr\":[\"or <0>browse files\"],\"QLtPBd\":[\"No dumps in this playlist yet.\"],\"R9Khdg\":[\"Auto\"],\"RCcPrX\":[\"Delete this playlist? This cannot be undone.\"],\"RTksSy\":[\"<0>\",[\"0\"],\" started following you\"],\"RaKjrM\":[\"Failed to save edit\"],\"RcUHRT\":[\"Followed\"],\"RfwZxd\":[\"Reset password\"],\"Rrp6-J\":[\"Log in to like\"],\"RvVJBH\":[\"Refreshing stream…\"],\"SBTElJ\":[\"Searching…\"],\"Sad2tK\":[\"Sending…\"],\"So7gCD\":[\"Paste a link…\"],\"StovX6\":[\"This page does not exist.\"],\"Sxm8rQ\":[\"Users\"],\"T9bjWt\":[\"<0>\",[\"0\"],\" was added to <1>\",[\"1\"],\"\"],\"TM1ZbA\":[\"Playlists (\",[\"0\"],[\"1\"],\")\"],\"TN382O\":[\"Invalid link\"],\"Tv9vbB\":[\"Follow playlist\"],\"Tz0i8g\":[\"Settings\"],\"U3pytU\":[\"Admin\"],\"UNMVei\":[\"Forgot password?\"],\"UOZith\":[\"Failed to post\"],\"URAieT\":[\"Log in to vote\"],\"VCoEm-\":[\"Back to login\"],\"VKsaTi\":[\"Replying to\"],\"V_e7nf\":[\"Set new password\"],\"VnNJbN\":[\"From playlists\"],\"VskHIx\":[\"Delete category\"],\"VyTYmS\":[\"Change avatar\"],\"W9FRBT\":[\"Like\"],\"WE_r9K\":[\"Failed to create category\"],\"WfffrI\":[\"slug\"],\"WhimMi\":[\"Reset failed\"],\"WpXcBJ\":[\"Nothing here yet.\"],\"XJVYo8\":[\"Why & where\"],\"XJy2oN\":[\"Logging in…\"],\"Xan6QP\":[\"New dump\"],\"XgRtUf\":[\"Could not change password\"],\"Xi0Mn4\":[\"← Back to profile\"],\"XnL-Eu\":[\"No users match \\\"\",[\"q\"],\"\\\".\"],\"Xs2Lez\":[\"This reset link is missing or malformed.\"],\"YK1Dhc\":[\"a post\"],\"YcjhhX\":[\"User management\"],\"Ye9RMF\":[\"<0>\",[\"0\"],\" liked your comment on <1>\",[\"1\"],\"\"],\"YpkCca\":[\"No followed playlists yet.\"],\"Z5HWHd\":[\"On\"],\"ZBdbv9\":[\"Edit title\"],\"ZCpU0u\":[\"No playlists match \\\"\",[\"q\"],\"\\\".\"],\"ZmD2o6\":[\"Create & Add\"],\"_3O5R_\":[\"Request failed\"],\"_DwR-n\":[\"Creating…\"],\"_R_sGB\":[\"Password changed successfully.\"],\"_aept4\":[\"Post reply\"],\"_nT6AE\":[\"New password\"],\"_t4W-i\":[\"From people\"],\"aAIQg2\":[\"Appearance\"],\"aDvLhk\":[\"Add a comment…\"],\"az8lvo\":[\"Off\"],\"b3Thhd\":[\"Upload failed\"],\"b8XMJ8\":[[\"visibleCount\",\"plural\",{\"one\":[\"#\",\" comment\"],\"other\":[\"#\",\" comments\"]}]],\"bQhwn-\":[\"Loading playlist…\"],\"cILfnJ\":[\"Remove file\"],\"cYP9Sb\":[\"+ Playlist\"],\"cbeBbZ\":[\"At least \",[\"0\"],\" characters\"],\"cnGeoo\":[\"Delete\"],\"d8DZWS\":[\"Open search\"],\"dAs22m\":[\"Replace file\"],\"dEgA5A\":[\"Cancel\"],\"dMizp8\":[\"New playlist\"],\"dqwCfR\":[\"Couldn't load a preview for this link — check it, or post it as-is and refresh the preview later.\"],\"eFSqvc\":[\"Failed to post reply\"],\"eOfXq3\":[\"Title is required.\"],\"ePK91l\":[\"Edit\"],\"eaUTwS\":[\"Send reset link\"],\"ecUA8p\":[\"Today\"],\"ef9nPf\":[\"Loading dump…\"],\"eloqXQ\":[\"You can post it anyway.\"],\"en9o7K\":[\"Failed to post comment\"],\"etFQQS\":[\"What makes it worth it?\"],\"fC6mXb\":[\"Upvote\"],\"fI-mNw\":[\"Playlists\"],\"f_akpP\":[\"Max 50 MB\"],\"fgLNSM\":[\"Register\"],\"gANddk\":[\"Uploading…\"],\"gGx5tM\":[\"Editing\"],\"gIQQwD\":[\"Failed to load\"],\"gL9KKi\":[\"Who can see it\"],\"gLfZlz\":[\"Add to playlist\"],\"gjJ-sb\":[\"Can't connect to the live updates server. Upvotes and notifications may not sync until it reconnects.\"],\"goWEhR\":[\"Previous track\"],\"hBuUKa\":[\"Change password…\"],\"hD7w09\":[\"You've reached the end.\"],\"hJSliC\":[\"<0>\",[\"0\"],\" posted <1>\",[\"1\"],\"\"],\"hT8hgO\":[\"Reset to the suggested title\"],\"hYgDIe\":[\"Create\"],\"he3ygx\":[\"Copy\"],\"hpCc_Z\":[\"Close anyway\"],\"i7K_Te\":[\"Who am I?\"],\"iDNBZe\":[\"Notifications\"],\"iWpEwy\":[\"Go home\"],\"ipYn7W\":[\"If that address is registered you'll receive a reset link shortly.\"],\"isRobC\":[\"New\"],\"jbernk\":[\"Loading profile…\"],\"joEmfT\":[\"Server unreachable\"],\"jrZTZl\":[\"No dumps yet. Be the first!\"],\"k112DD\":[\"Load older messages\"],\"k36iJ-\":[\"Failed to save category\"],\"kLttbL\":[\"Registration failed\"],\"lUDifl\":[\"Created (\",[\"0\"],[\"1\"],\")\"],\"lUanmi\":[\"You'll be notified when someone follows your playlists, upvotes your dumps, or posts new content.\"],\"lY5h1V\":[[\"0\",\"plural\",{\"one\":[\"#\",\" dump\"],\"other\":[\"#\",\" dumps\"]}]],\"lcfvr_\":[\"Delete this comment?\"],\"lpIMne\":[\"Passwords do not match\"],\"mb_Wju\":[\"Drop a file here, or paste one\"],\"mt6O6E\":[\"This is a mirage.\"],\"nbm5sI\":[\"No dumps match \\\"\",[\"q\"],\"\\\".\"],\"nrjqON\":[\"Checking invite…\"],\"nwtY4N\":[\"Something went wrong\"],\"nx4kaN\":[\"Could not save\"],\"oTYvvN\":[[\"0\",\"plural\",{\"one\":[\"and \",\"#\",\" earlier dump\"],\"other\":[\"and \",\"#\",\" earlier dumps\"]}]],\"ogtYkT\":[\"Password updated\"],\"pCpd9p\":[\"<0>\",[\"0\"],\" mentioned you in <1>\",[\"where\"],\"\"],\"pSheLH\":[\"No invitees yet.\"],\"pvnfJD\":[\"Dark\"],\"qIMfNQ\":[\"Delete playlist\"],\"qbDAcy\":[\"Dump it\"],\"qgx_78\":[\"Follow some public playlists to see their dumps here.\"],\"qsqt0w\":[\"Invite link\"],\"qvFa8r\":[\"public\"],\"qvz_Pp\":[\"Dump\"],\"rCbqPX\":[\"This invite link is missing, expired, or already used.\"],\"rDMzDU\":[\"Type a message…\"],\"rg9pXu\":[\"Search failed\"],\"rtpJqV\":[\"Dumps (\",[\"0\"],[\"1\"],\")\"],\"sEXJPi\":[\"Link or file\"],\"sGeXL3\":[\"Thumbnail\"],\"sQia9P\":[\"Log in\"],\"sTiqbm\":[\"invited by\"],\"sdP5Aa\":[\"[deleted]\"],\"sgX7B-\":[\"Single use, valid for 7 days.\"],\"shHs8T\":[\"Enter a query to search.\"],\"smeBfS\":[\"Invalid invite\"],\"tfDRzk\":[\"Save\"],\"tvmuQ0\":[\"Color scheme\"],\"u1lDX2\":[\"Fetching preview…\"],\"uD0qXQ\":[\"Drop a file here\"],\"uMGUnV\":[\"No playlists yet.\"],\"ub1EEL\":[\"edited \",[\"0\"]],\"uomLas\":[\"Add category\"],\"vJBF1r\":[\"Posting…\"],\"vLhLLO\":[\"Notifications (\",[\"unreadNotificationCount\"],\" unread)\"],\"vQMkHu\":[\"Remove vote\"],\"vuosjb\":[\"User menu\"],\"wbXKOv\":[\"File too large (max 50 MB).\"],\"wckWOP\":[\"Manage\"],\"wixIgH\":[\"Already have an account? <0>Log in\"],\"x6tjuK\":[\"Default tab\"],\"xEWkgZ\":[\"← Back to all dumps\"],\"xPHtx0\":[\"Submit search\"],\"xVuNgt\":[\"+ New playlist\"],\"xc9O_u\":[\"Delete dump\"],\"y6sq5j\":[\"Following\"],\"y7oaHj\":[\"Playlist title\"],\"yA_6BX\":[\"View all →\"],\"yBBtRm\":[\"Follow some users to see their dumps here.\"],\"yQ2kGp\":[\"Load more\"],\"y_0uwd\":[\"Yesterday\"],\"yz7wBu\":[\"Close\"],\"yzF66j\":[\"Link\"],\"z0ROB3\":[\"Remove like\"],\"z1uNN0\":[\"No emoji found.\"],\"zVuxvN\":[\"Refreshing…\"],\"zwBp5t\":[\"Private\"]}")}; \ No newline at end of file diff --git a/src/locales/en.po b/src/locales/en.po index 0e03c18e..a30c25a9 100644 --- a/src/locales/en.po +++ b/src/locales/en.po @@ -35,12 +35,12 @@ msgstr "{0, plural, one {and # earlier dump} other {and # earlier dumps}}" #. placeholder {0}: typingNames[0] #. placeholder {1}: typingNames[1] -#: src/components/ChatModal.tsx:534 +#: src/components/ChatModal.tsx:568 msgid "{0} and {1} are typing…" msgstr "{0} and {1} are typing…" #. placeholder {0}: typingNames[0] -#: src/components/ChatModal.tsx:532 +#: src/components/ChatModal.tsx:566 msgid "{0} is typing…" msgstr "{0} is typing…" @@ -60,7 +60,7 @@ msgstr "{visibleCount, plural, one {# comment} other {# comments}}" #: src/components/DumpCreateModal.tsx:936 #: src/pages/PlaylistDetail.tsx:572 -#: src/pages/UserPublicProfile.tsx:798 +#: src/pages/UserPublicProfile.tsx:801 msgid "← Back" msgstr "← Back" @@ -76,7 +76,7 @@ msgstr "← Back to all dumps" msgid "← Back to profile" msgstr "← Back to profile" -#: src/pages/UserPublicProfile.tsx:134 +#: src/pages/UserPublicProfile.tsx:136 msgid "+ Invite someone" msgstr "+ Invite someone" @@ -142,7 +142,7 @@ msgstr "a comment" msgid "a post" msgstr "a post" -#: src/pages/UserPublicProfile.tsx:1179 +#: src/pages/UserPublicProfile.tsx:1182 msgid "Account" msgstr "Account" @@ -155,7 +155,7 @@ msgstr "Add a comment…" msgid "Add category" msgstr "Add category" -#: src/pages/UserPublicProfile.tsx:876 +#: src/pages/UserPublicProfile.tsx:879 msgid "Add email…" msgstr "Add email…" @@ -164,7 +164,7 @@ msgstr "Add email…" msgid "Add to playlist" msgstr "Add to playlist" -#: src/pages/UserPublicProfile.tsx:1604 +#: src/pages/UserPublicProfile.tsx:1646 msgid "Admin" msgstr "Admin" @@ -183,7 +183,7 @@ msgstr "Already dumped by {0} {1}" msgid "Already have an account? <0>Log in" msgstr "Already have an account? <0>Log in" -#: src/pages/UserPublicProfile.tsx:1198 +#: src/pages/UserPublicProfile.tsx:1201 msgid "Appearance" msgstr "Appearance" @@ -194,7 +194,7 @@ msgstr "Appearance" msgid "At least {0} characters" msgstr "At least {0} characters" -#: src/pages/UserPublicProfile.tsx:1253 +#: src/pages/UserPublicProfile.tsx:1263 msgid "Auto" msgstr "Auto" @@ -203,8 +203,8 @@ msgstr "Auto" msgid "Back to login" msgstr "Back to login" -#: src/contexts/WSProvider.tsx:270 -#: src/contexts/WSProvider.tsx:576 +#: src/contexts/WSProvider.tsx:292 +#: src/contexts/WSProvider.tsx:608 msgid "Can't connect to the live updates server. Upvotes and notifications may not sync until it reconnects." msgstr "Can't connect to the live updates server. Upvotes and notifications may not sync until it reconnects." @@ -218,8 +218,8 @@ msgstr "Can't connect to the live updates server. Upvotes and notifications may #: src/pages/Dump.tsx:406 #: src/pages/DumpEdit.tsx:484 #: src/pages/PlaylistDetail.tsx:923 -#: src/pages/UserPublicProfile.tsx:1733 -#: src/pages/UserPublicProfile.tsx:1806 +#: src/pages/UserPublicProfile.tsx:1775 +#: src/pages/UserPublicProfile.tsx:1848 msgid "Cancel" msgstr "Cancel" @@ -227,8 +227,8 @@ msgstr "Cancel" msgid "Cancel removal" msgstr "Cancel removal" -#: src/components/ChatModal.tsx:624 -#: src/components/ChatModal.tsx:625 +#: src/components/ChatModal.tsx:658 +#: src/components/ChatModal.tsx:659 msgid "Cancel reply" msgstr "Cancel reply" @@ -237,7 +237,7 @@ msgid "Cancel search" msgstr "Cancel search" #: src/components/CategorySelect.tsx:22 -#: src/pages/UserPublicProfile.tsx:1336 +#: src/pages/UserPublicProfile.tsx:1378 msgid "Categories" msgstr "Categories" @@ -245,7 +245,7 @@ msgstr "Categories" msgid "Category" msgstr "Category" -#: src/pages/UserPublicProfile.tsx:825 +#: src/pages/UserPublicProfile.tsx:828 msgid "Change avatar" msgstr "Change avatar" @@ -254,14 +254,14 @@ msgstr "Change avatar" msgid "Change password" msgstr "Change password" -#: src/pages/UserPublicProfile.tsx:1191 +#: src/pages/UserPublicProfile.tsx:1194 msgid "Change password…" msgstr "Change password…" #: src/components/ChatButton.tsx:17 #: src/components/ChatFab.tsx:49 #: src/components/ChatFab.tsx:50 -#: src/components/ChatModal.tsx:538 +#: src/components/ChatModal.tsx:572 msgid "Chat" msgstr "Chat" @@ -295,7 +295,7 @@ msgstr "Close without posting? The attached file won't be kept — everything yo msgid "Collapse player" msgstr "Collapse player" -#: src/pages/UserPublicProfile.tsx:1245 +#: src/pages/UserPublicProfile.tsx:1255 msgid "Color scheme" msgstr "Color scheme" @@ -304,11 +304,11 @@ msgstr "Color scheme" msgid "Confirm new password" msgstr "Confirm new password" -#: src/pages/UserPublicProfile.tsx:159 +#: src/pages/UserPublicProfile.tsx:161 msgid "Copied!" msgstr "Copied!" -#: src/pages/UserPublicProfile.tsx:159 +#: src/pages/UserPublicProfile.tsx:161 msgid "Copy" msgstr "Copy" @@ -350,11 +350,11 @@ msgstr "Creating…" msgid "Current password" msgstr "Current password" -#: src/pages/UserPublicProfile.tsx:1267 +#: src/pages/UserPublicProfile.tsx:1277 msgid "Dark" msgstr "Dark" -#: src/pages/UserPublicProfile.tsx:1280 +#: src/pages/UserPublicProfile.tsx:1290 msgid "Default tab" msgstr "Default tab" @@ -446,13 +446,13 @@ msgstr "Dumped!" #: src/pages/Search.tsx:174 #: src/pages/UserDumps.tsx:80 -#: src/pages/UserPublicProfile.tsx:948 +#: src/pages/UserPublicProfile.tsx:951 msgid "Dumps" msgstr "Dumps" #. placeholder {0}: dumps.items.length #. placeholder {1}: dumps.hasMore ? "+" : "" -#: src/pages/UserPublicProfile.tsx:968 +#: src/pages/UserPublicProfile.tsx:971 msgid "Dumps ({0}{1})" msgstr "Dumps ({0}{1})" @@ -530,9 +530,9 @@ msgstr "Failed to create playlist" msgid "Failed to delete" msgstr "Failed to delete" -#: src/pages/UserPublicProfile.tsx:104 -#: src/pages/UserPublicProfile.tsx:107 -#: src/pages/UserPublicProfile.tsx:168 +#: src/pages/UserPublicProfile.tsx:106 +#: src/pages/UserPublicProfile.tsx:109 +#: src/pages/UserPublicProfile.tsx:170 msgid "Failed to generate invite" msgstr "Failed to generate invite" @@ -541,9 +541,9 @@ msgstr "Failed to generate invite" #: src/pages/index/JournalFeed.tsx:33 #: src/pages/index/NewFeed.tsx:36 #: src/pages/Notifications.tsx:374 -#: src/pages/UserPublicProfile.tsx:1070 -#: src/pages/UserPublicProfile.tsx:1112 -#: src/pages/UserPublicProfile.tsx:1157 +#: src/pages/UserPublicProfile.tsx:1073 +#: src/pages/UserPublicProfile.tsx:1115 +#: src/pages/UserPublicProfile.tsx:1160 msgid "Failed to load" msgstr "Failed to load" @@ -560,8 +560,8 @@ msgid "Failed to post reply" msgstr "Failed to post reply" #: src/pages/PlaylistDetail.tsx:911 -#: src/pages/UserPublicProfile.tsx:1736 -#: src/pages/UserPublicProfile.tsx:1808 +#: src/pages/UserPublicProfile.tsx:1778 +#: src/pages/UserPublicProfile.tsx:1850 msgid "Failed to save" msgstr "Failed to save" @@ -573,15 +573,15 @@ msgstr "Failed to save category" msgid "Failed to save edit" msgstr "Failed to save edit" -#: src/pages/UserPublicProfile.tsx:885 +#: src/pages/UserPublicProfile.tsx:888 msgid "Failed to update avatar" msgstr "Failed to update avatar" -#: src/pages/UserPublicProfile.tsx:1674 +#: src/pages/UserPublicProfile.tsx:1716 msgid "Failed to update role" msgstr "Failed to update role" -#: src/pages/UserPublicProfile.tsx:1275 +#: src/pages/UserPublicProfile.tsx:1285 msgid "Feeds" msgstr "Feeds" @@ -615,8 +615,8 @@ msgid "Follow some users to see their dumps here." msgstr "Follow some users to see their dumps here." #: src/components/FeedTabBar.tsx:34 -#: src/pages/UserPublicProfile.tsx:950 -#: src/pages/UserPublicProfile.tsx:1309 +#: src/pages/UserPublicProfile.tsx:953 +#: src/pages/UserPublicProfile.tsx:1319 msgid "Followed" msgstr "Followed" @@ -626,13 +626,13 @@ msgstr "Followed" msgid "Followed ({0}{1})" msgstr "Followed ({0}{1})" -#: src/pages/UserPublicProfile.tsx:1101 +#: src/pages/UserPublicProfile.tsx:1104 msgid "Followed playlists" msgstr "Followed playlists" #: src/components/FollowButton.tsx:37 #: src/components/FollowButton.tsx:64 -#: src/pages/UserPublicProfile.tsx:1059 +#: src/pages/UserPublicProfile.tsx:1062 msgid "Following" msgstr "Following" @@ -657,7 +657,7 @@ msgid "Go to login" msgstr "Go to login" #: src/components/FeedTabBar.tsx:30 -#: src/pages/UserPublicProfile.tsx:1288 +#: src/pages/UserPublicProfile.tsx:1298 msgid "Hot" msgstr "Hot" @@ -677,22 +677,22 @@ msgstr "Invalid invite" msgid "Invalid link" msgstr "Invalid link" -#: src/pages/UserPublicProfile.tsx:142 -#: src/pages/UserPublicProfile.tsx:149 +#: src/pages/UserPublicProfile.tsx:144 +#: src/pages/UserPublicProfile.tsx:151 msgid "Invite link" msgstr "Invite link" -#: src/pages/UserPublicProfile.tsx:847 +#: src/pages/UserPublicProfile.tsx:850 msgid "invited by" msgstr "invited by" -#: src/pages/UserPublicProfile.tsx:951 -#: src/pages/UserPublicProfile.tsx:1146 +#: src/pages/UserPublicProfile.tsx:954 +#: src/pages/UserPublicProfile.tsx:1149 msgid "Invitees" msgstr "Invitees" #: src/components/FeedTabBar.tsx:32 -#: src/pages/UserPublicProfile.tsx:1302 +#: src/pages/UserPublicProfile.tsx:1312 msgid "Journal" msgstr "Journal" @@ -700,7 +700,7 @@ msgstr "Journal" msgid "Keep editing" msgstr "Keep editing" -#: src/pages/UserPublicProfile.tsx:1260 +#: src/pages/UserPublicProfile.tsx:1270 msgid "Light" msgstr "Light" @@ -716,7 +716,7 @@ msgstr "Link" msgid "Link or file" msgstr "Link or file" -#: src/contexts/WSProvider.tsx:575 +#: src/contexts/WSProvider.tsx:607 msgid "Live updates are temporarily disconnected. Trying to reconnect…" msgstr "Live updates are temporarily disconnected. Trying to reconnect…" @@ -729,7 +729,7 @@ msgstr "Live updates unavailable." msgid "Load more" msgstr "Load more" -#: src/components/ChatModal.tsx:551 +#: src/components/ChatModal.tsx:585 msgid "Load older messages" msgstr "Load older messages" @@ -740,7 +740,7 @@ msgstr "Loading dump…" #: src/pages/index/FollowedFeed.tsx:111 #: src/pages/index/HotFeed.tsx:64 -#: src/pages/index/JournalFeed.tsx:62 +#: src/pages/index/JournalFeed.tsx:73 #: src/pages/index/NewFeed.tsx:64 #: src/pages/Search.tsx:244 #: src/pages/UserDumps.tsx:121 @@ -754,12 +754,12 @@ msgstr "Loading more…" msgid "Loading playlist…" msgstr "Loading playlist…" -#: src/pages/UserPublicProfile.tsx:781 +#: src/pages/UserPublicProfile.tsx:784 msgid "Loading profile…" msgstr "Loading profile…" #: src/components/CategoryManager.tsx:52 -#: src/components/ChatModal.tsx:550 +#: src/components/ChatModal.tsx:584 #: src/components/PlaylistMembershipPanel.tsx:28 #: src/components/TextEditor.tsx:311 #: src/components/UserListPopover.tsx:192 @@ -772,9 +772,9 @@ msgstr "Loading profile…" #: src/pages/Notifications.tsx:446 #: src/pages/UserDumps.tsx:54 #: src/pages/UserPlaylists.tsx:345 -#: src/pages/UserPublicProfile.tsx:1064 -#: src/pages/UserPublicProfile.tsx:1106 -#: src/pages/UserPublicProfile.tsx:1151 +#: src/pages/UserPublicProfile.tsx:1067 +#: src/pages/UserPublicProfile.tsx:1109 +#: src/pages/UserPublicProfile.tsx:1154 #: src/pages/UserUpvoted.tsx:125 msgid "Loading…" msgstr "Loading…" @@ -794,8 +794,8 @@ msgstr "Log in to like" msgid "Log in to vote" msgstr "Log in to vote" -#: src/pages/UserPublicProfile.tsx:802 -#: src/pages/UserPublicProfile.tsx:898 +#: src/pages/UserPublicProfile.tsx:805 +#: src/pages/UserPublicProfile.tsx:901 msgid "Log out" msgstr "Log out" @@ -807,7 +807,7 @@ msgstr "Logging in…" msgid "Login failed" msgstr "Login failed" -#: src/pages/UserPublicProfile.tsx:956 +#: src/pages/UserPublicProfile.tsx:959 msgid "Manage" msgstr "Manage" @@ -815,7 +815,7 @@ msgstr "Manage" msgid "Max 50 MB" msgstr "Max 50 MB" -#: src/pages/UserPublicProfile.tsx:1603 +#: src/pages/UserPublicProfile.tsx:1645 msgid "Moderator" msgstr "Moderator" @@ -829,7 +829,7 @@ msgid "new" msgstr "new" #: src/components/FeedTabBar.tsx:31 -#: src/pages/UserPublicProfile.tsx:1295 +#: src/pages/UserPublicProfile.tsx:1305 msgid "New" msgstr "New" @@ -837,7 +837,7 @@ msgstr "New" #: src/components/DumpFab.tsx:65 #: src/components/DumpFab.tsx:66 #: src/pages/UserDumps.tsx:88 -#: src/pages/UserPublicProfile.tsx:1382 +#: src/pages/UserPublicProfile.tsx:1424 msgid "New dump" msgstr "New dump" @@ -878,15 +878,15 @@ msgid "No emoji found." msgstr "No emoji found." #: src/pages/UserPlaylists.tsx:442 -#: src/pages/UserPublicProfile.tsx:1119 +#: src/pages/UserPublicProfile.tsx:1122 msgid "No followed playlists yet." msgstr "No followed playlists yet." -#: src/pages/UserPublicProfile.tsx:1164 +#: src/pages/UserPublicProfile.tsx:1167 msgid "No invitees yet." msgstr "No invitees yet." -#: src/components/ChatModal.tsx:558 +#: src/components/ChatModal.tsx:592 msgid "No messages yet. Say hello!" msgstr "No messages yet. Say hello!" @@ -900,7 +900,7 @@ msgstr "No playlists match \"{q}\"." #: src/components/PlaylistMembershipPanel.tsx:34 #: src/pages/UserPlaylists.tsx:400 -#: src/pages/UserPublicProfile.tsx:1030 +#: src/pages/UserPublicProfile.tsx:1033 msgid "No playlists yet." msgstr "No playlists yet." @@ -908,14 +908,14 @@ msgstr "No playlists yet." msgid "No users match \"{q}\"." msgstr "No users match \"{q}\"." -#: src/pages/UserPublicProfile.tsx:1077 +#: src/pages/UserPublicProfile.tsx:1080 msgid "Not following anyone yet." msgstr "Not following anyone yet." #: src/pages/Notifications.tsx:381 #: src/pages/UserDumps.tsx:99 -#: src/pages/UserPublicProfile.tsx:1393 -#: src/pages/UserPublicProfile.tsx:1515 +#: src/pages/UserPublicProfile.tsx:1435 +#: src/pages/UserPublicProfile.tsx:1557 #: src/pages/UserUpvoted.tsx:159 msgid "Nothing here yet." msgstr "Nothing here yet." @@ -923,6 +923,7 @@ msgstr "Nothing here yet." #: src/components/NotificationBell.tsx:42 #: src/pages/Notifications.tsx:265 #: src/pages/Notifications.tsx:359 +#: src/pages/UserPublicProfile.tsx:1327 msgid "Notifications" msgstr "Notifications" @@ -930,6 +931,14 @@ msgstr "Notifications" msgid "Notifications ({unreadNotificationCount} unread)" msgstr "Notifications ({unreadNotificationCount} unread)" +#: src/pages/UserPublicProfile.tsx:1351 +msgid "Off" +msgstr "Off" + +#: src/pages/UserPublicProfile.tsx:1344 +msgid "On" +msgstr "On" + #: src/components/SearchBar.tsx:96 msgid "Open search" msgstr "Open search" @@ -943,7 +952,7 @@ msgid "Page not found" msgstr "Page not found" #: src/pages/UserLogin.tsx:76 -#: src/pages/UserPublicProfile.tsx:1184 +#: src/pages/UserPublicProfile.tsx:1187 msgid "Password" msgstr "Password" @@ -982,13 +991,13 @@ msgstr "Playlist title" #: src/components/UserMenu.tsx:62 #: src/pages/Search.tsx:177 #: src/pages/UserPlaylists.tsx:371 -#: src/pages/UserPublicProfile.tsx:949 +#: src/pages/UserPublicProfile.tsx:952 msgid "Playlists" msgstr "Playlists" #. placeholder {0}: playlists.items.length #. placeholder {1}: playlists.hasMore ? "+" : "" -#: src/pages/UserPublicProfile.tsx:999 +#: src/pages/UserPublicProfile.tsx:1002 msgid "Playlists ({0}{1})" msgstr "Playlists ({0}{1})" @@ -1090,7 +1099,7 @@ msgstr "Replace file" msgid "Reply" msgstr "Reply" -#: src/components/ChatModal.tsx:614 +#: src/components/ChatModal.tsx:648 msgid "Replying to" msgstr "Replying to" @@ -1121,7 +1130,7 @@ msgstr "Reset to the suggested title" msgid "Retry" msgstr "Retry" -#: src/pages/UserPublicProfile.tsx:1658 +#: src/pages/UserPublicProfile.tsx:1700 msgid "Role" msgstr "Role" @@ -1130,8 +1139,8 @@ msgstr "Role" #: src/pages/Dump.tsx:398 #: src/pages/DumpEdit.tsx:487 #: src/pages/PlaylistDetail.tsx:930 -#: src/pages/UserPublicProfile.tsx:1725 -#: src/pages/UserPublicProfile.tsx:1798 +#: src/pages/UserPublicProfile.tsx:1767 +#: src/pages/UserPublicProfile.tsx:1840 msgid "Save" msgstr "Save" @@ -1140,8 +1149,8 @@ msgstr "Save" #: src/pages/Dump.tsx:397 #: src/pages/PlaylistDetail.tsx:926 #: src/pages/ResetPassword.tsx:126 -#: src/pages/UserPublicProfile.tsx:1722 -#: src/pages/UserPublicProfile.tsx:1795 +#: src/pages/UserPublicProfile.tsx:1764 +#: src/pages/UserPublicProfile.tsx:1837 msgid "Saving…" msgstr "Saving…" @@ -1163,7 +1172,7 @@ msgstr "Search failed" msgid "Searching…" msgstr "Searching…" -#: src/components/ChatModal.tsx:660 +#: src/components/ChatModal.tsx:694 msgid "Send" msgstr "Send" @@ -1184,15 +1193,15 @@ msgstr "Server unreachable" msgid "Set new password" msgstr "Set new password" -#: src/pages/UserPublicProfile.tsx:953 +#: src/pages/UserPublicProfile.tsx:956 msgid "Settings" msgstr "Settings" -#: src/components/ChatModal.tsx:535 +#: src/components/ChatModal.tsx:569 msgid "Several people are typing…" msgstr "Several people are typing…" -#: src/pages/UserPublicProfile.tsx:162 +#: src/pages/UserPublicProfile.tsx:164 msgid "Single use, valid for 7 days." msgstr "Single use, valid for 7 days." @@ -1205,11 +1214,15 @@ msgstr "slug" msgid "Something went wrong" msgstr "Something went wrong" +#: src/pages/UserPublicProfile.tsx:1332 +msgid "Sounds" +msgstr "Sounds" + #: src/components/DumpCreateModal.tsx:707 msgid "Start fresh" msgstr "Start fresh" -#: src/pages/UserPublicProfile.tsx:1203 +#: src/pages/UserPublicProfile.tsx:1206 msgid "Style" msgstr "Style" @@ -1261,7 +1274,7 @@ msgstr "Today" msgid "Track {0}" msgstr "Track {0}" -#: src/components/ChatModal.tsx:637 +#: src/components/ChatModal.tsx:671 msgid "Type a message…" msgstr "Type a message…" @@ -1277,7 +1290,7 @@ msgstr "Unfollow {targetUsername}" msgid "Unfollow playlist" msgstr "Unfollow playlist" -#: src/pages/UserPublicProfile.tsx:748 +#: src/pages/UserPublicProfile.tsx:751 msgid "Upload failed" msgstr "Upload failed" @@ -1300,7 +1313,7 @@ msgstr "Upvoted" #. placeholder {0}: votes.items.length #. placeholder {1}: votes.hasMore ? "+" : "" -#: src/pages/UserPublicProfile.tsx:979 +#: src/pages/UserPublicProfile.tsx:982 msgid "Upvoted ({0}{1})" msgstr "Upvoted ({0}{1})" @@ -1316,11 +1329,11 @@ msgstr "URL is required." msgid "URL slug" msgstr "URL slug" -#: src/pages/UserPublicProfile.tsx:1602 +#: src/pages/UserPublicProfile.tsx:1644 msgid "User" msgstr "User" -#: src/pages/UserPublicProfile.tsx:1321 +#: src/pages/UserPublicProfile.tsx:1363 msgid "User management" msgstr "User management" @@ -1337,11 +1350,11 @@ msgstr "Username" msgid "Users" msgstr "Users" -#: src/pages/UserPublicProfile.tsx:1049 -#: src/pages/UserPublicProfile.tsx:1092 -#: src/pages/UserPublicProfile.tsx:1134 -#: src/pages/UserPublicProfile.tsx:1414 -#: src/pages/UserPublicProfile.tsx:1545 +#: src/pages/UserPublicProfile.tsx:1052 +#: src/pages/UserPublicProfile.tsx:1095 +#: src/pages/UserPublicProfile.tsx:1137 +#: src/pages/UserPublicProfile.tsx:1456 +#: src/pages/UserPublicProfile.tsx:1587 msgid "View all →" msgstr "View all →" @@ -1354,8 +1367,8 @@ msgstr "View dump →" msgid "What makes it worth it?" msgstr "What makes it worth it?" -#: src/pages/UserPublicProfile.tsx:933 -#: src/pages/UserPublicProfile.tsx:1787 +#: src/pages/UserPublicProfile.tsx:936 +#: src/pages/UserPublicProfile.tsx:1829 msgid "Who am I?" msgstr "Who am I?" @@ -1391,7 +1404,7 @@ msgstr "You'll be notified when someone follows your playlists, upvotes your dum #: src/pages/index/FollowedFeed.tsx:116 #: src/pages/index/HotFeed.tsx:69 -#: src/pages/index/JournalFeed.tsx:67 +#: src/pages/index/JournalFeed.tsx:78 #: src/pages/index/NewFeed.tsx:69 #: src/pages/Search.tsx:249 #: src/pages/UserDumps.tsx:126 diff --git a/src/locales/fr.js b/src/locales/fr.js index 3f56c1cd..5e42dd49 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…\"],\"2_DHgA\":[\"Identifiant d’URL\"],\"2nrsEn\":[\"message supprimé\"],\"2ygf_L\":[\"← Retour\"],\"3KKSM4\":[\"privé\"],\"3QuNUr\":[\"Plusieurs personnes écrivent…\"],\"3SEwY1\":[\"Supprimer la catégorie \\\"\",[\"0\"],\"\\\" ? Cette action est irréversible.\"],\"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.\"],\"4OjqAQ\":[\"Continuer l'édition\"],\"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…\"],\"5KHDCi\":[[\"0\"],\" et \",[\"1\"],\" sont en train d'écrire…\"],\"5TviPn\":[\"Annuler la recherche\"],\"5cC8f2\":[\"Modifié le \",[\"0\"]],\"5oD9f_\":[\"Plus tôt\"],\"6Qly-0\":[\"un commentaire\"],\"6YE0P6\":[\"Édition de \",[\"0\"]],\"6YtxFj\":[\"Nom\"],\"6gRgw8\":[\"Réessayer\"],\"7PHCIN\":[\"Annuler la suppression\"],\"7PzzBU\":[\"Utilisateur\"],\"7d1a0d\":[\"Public\"],\"7sNhEz\":[\"Nom d'utilisateur\"],\"8F1i42\":[\"Page introuvable\"],\"8Ug9jB\":[\"Connexe\"],\"8ZsakT\":[\"Mot de passe\"],\"92o1-U\":[\"Suppression échouée\"],\"9BruTc\":[\"Pourquoi ?\"],\"9l4qcT\":[\"Déposez un fichier de remplacement ici\"],\"9uI_rE\":[\"Annuler\"],\"9xDZu_\":[\"Impossible de charger.\"],\"A0y396\":[\"+ Inviter quelqu'un\"],\"A1taO8\":[\"Rechercher\"],\"AEfu-D\":[\"Dans les collections\"],\"AHZflp\":[\"Tribune\"],\"AQbgNR\":[\"Suivre \",[\"targetUsername\"]],\"AS7WoE\":[\"Repartir de zéro\"],\"ATGYL1\":[\"Adresse e-mail\"],\"AZctoV\":[[\"0\",\"plural\",{\"one\":[\"#\",\" commentaire\"],\"other\":[\"#\",\" commentaires\"]}]],\"Ade-6d\":[\"Mises à jour en direct indisponibles.\"],\"AeXO77\":[\"Compte\"],\"AnRu_j\":[\"Annuler la réponse\"],\"BlpJ2G\":[\"Reprise de votre brouillon.\"],\"CI50ct\":[\"Voté\"],\"C_qQE4\":[\"Déjà recommandé par \",[\"0\"],\" \",[\"1\"]],\"Cj24wt\":[\"Inscription…\"],\"D8DRYV\":[\"Supprimer ce message ?\"],\"DPfwMq\":[\"Terminé\"],\"DdeHXH\":[\"Supprimer cette reco ? Cette action est irréversible.\"],\"Dp1JhP\":[\"<0>\",[\"0\"],\" a voté pour <1>\",[\"1\"],\"\"],\"ECiS12\":[\"Voir la reco →\"],\"ED8n0z\":[\"la tribune\"],\"Evcm2Z\":[\"Fermer sans publier ? Le fichier joint sera perdu — ce que vous avez écrit sera conservé.\"],\"ExR0Fr\":[\"L'URL est obligatoire.\"],\"F5Js1v\":[\"Ne plus suivre la collection\"],\"FdTlEG\":[\"Piste \",[\"0\"]],\"FgAxTj\":[\"Se déconnecter\"],\"Fxf4jq\":[\"Description (facultatif)\"],\"GCWCCo\":[\"Fermer le lecteur\"],\"GDvlUT\":[\"Rôle\"],\"GLukaH\":[\"Modérateur\"],\"GNSsCc\":[\"Impossible de créer la collection\"],\"GQ38dl\":[[\"unreadChatCount\"],\" messages non lus\"],\"GY6uSc\":[\"Suivant →\"],\"GbqhrN\":[[\"0\"],\"–\",[\"1\"],\" caractères : lettres, chiffres ou tirets bas\"],\"GptGxg\":[\"Changer le mot de passe\"],\"H4MGkI\":[\"Agrandir le lecteur\"],\"H4o4sk\":[\"Personne pour le moment.\"],\"H8pzW-\":[\"Impossible de mettre à jour l'avatar\"],\"HTLDA4\":[\"Chargement…\"],\"I-x669\":[\"Invités\"],\"IO9KL_\":[\"Piste suivante\"],\"IZX7TO\":[\"Impossible de générer une invitation\"],\"IagCbF\":[\"URL\"],\"ImOQa9\":[\"Répondre\"],\"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\"],\"JlFcis\":[\"Envoyer\"],\"K7tIrx\":[\"Catégorie\"],\"KDGWg5\":[\"Retirer de la collection\"],\"KKZxBU\":[\"Envoi \",[\"0\"],\" %\"],\"K_F6pa\":[\"Enregistrement…\"],\"L-rMC9\":[\"Réinitialiser par défaut\"],\"LLyMkV\":[\"Suivies (\",[\"0\"],[\"1\"],\")\"],\"MHrjPM\":[\"Titre\"],\"MKEPCY\":[\"Suivre\"],\"MTb2Ot\":[\"Aucun message pour l'instant. Dites bonjour !\"],\"Mj6oCJ\":[[\"0\"],\" est en train d'écrire…\"],\"N40H-G\":[\"Tout\"],\"N6j2JH\":[\"Modifier \",[\"0\"]],\"NUrY9o\":[\"Catégories\"],\"Oprv1v\":[\"Mot de passe (min. \",[\"0\"],\" caractères)\"],\"Oz0N9s\":[\"nouveau\"],\"PUe8Yk\":[\"Réduire le lecteur\"],\"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\"],\"RfwZxd\":[\"Réinitialiser le mot de passe\"],\"Rrp6-J\":[\"Se connecter pour aimer\"],\"RvVJBH\":[\"Actualisation du flux…\"],\"SBTElJ\":[\"Recherche…\"],\"Sad2tK\":[\"Envoi…\"],\"So7gCD\":[\"Collez un lien…\"],\"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\"],\"VCoEm-\":[\"Retour à la connexion\"],\"VKsaTi\":[\"En réponse à\"],\"V_e7nf\":[\"Définir un nouveau mot de passe\"],\"VnNJbN\":[\"De collections\"],\"VskHIx\":[\"Supprimer la catégorie\"],\"VyTYmS\":[\"Changer l'avatar\"],\"W9FRBT\":[\"Aimer\"],\"WE_r9K\":[\"Échec de la création de la catégorie\"],\"WfffrI\":[\"identifiant\"],\"WhimMi\":[\"Échec de la réinitialisation\"],\"WpXcBJ\":[\"Rien ici pour l'instant.\"],\"XJVYo8\":[\"Pourquoi & où\"],\"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\"],\"dqwCfR\":[\"Impossible de charger un aperçu pour ce lien — vérifiez-le, ou publiez-le tel quel et actualisez l'aperçu plus tard.\"],\"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…\"],\"eloqXQ\":[\"Vous pouvez la publier quand même.\"],\"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é\"],\"gL9KKi\":[\"Qui peut la voir\"],\"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.\"],\"goWEhR\":[\"Piste précédente\"],\"hBuUKa\":[\"Changer le mot de passe…\"],\"hD7w09\":[\"Vous avez tout lu, tout vu, tout bu.\"],\"hJSliC\":[\"<0>\",[\"0\"],\" a publié <1>\",[\"1\"],\"\"],\"hT8hgO\":[\"Rétablir le titre suggéré\"],\"hYgDIe\":[\"Créer\"],\"he3ygx\":[\"Copier\"],\"hpCc_Z\":[\"Fermer quand même\"],\"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 !\"],\"k112DD\":[\"Charger les messages plus anciens\"],\"k36iJ-\":[\"Échec de l’enregistrement de la catégorie\"],\"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\"],\"mb_Wju\":[\"Déposez un fichier ici, ou collez-en un\"],\"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\"],\"oTYvvN\":[[\"0\",\"plural\",{\"one\":[\"et \",\"#\",\" reco plus ancienne\"],\"other\":[\"et \",\"#\",\" recos plus anciennes\"]}]],\"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.\"],\"qsqt0w\":[\"Lien d'invitation\"],\"qvFa8r\":[\"public\"],\"qvz_Pp\":[\"Reco\"],\"rCbqPX\":[\"Ce lien d'invitation est manquant, expiré ou déjà utilisé.\"],\"rDMzDU\":[\"Écrivez un message…\"],\"rg9pXu\":[\"Recherche échouée\"],\"rtpJqV\":[\"Recos (\",[\"0\"],[\"1\"],\")\"],\"sEXJPi\":[\"Lien ou fichier\"],\"sGeXL3\":[\"Miniature\"],\"sQia9P\":[\"Se connecter\"],\"sTiqbm\":[\"invité par\"],\"sdP5Aa\":[\"[supprimé]\"],\"sgX7B-\":[\"Usage unique, valable 7 jours.\"],\"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\"]],\"uomLas\":[\"Ajouter une catégorie\"],\"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\"],\"yzF66j\":[\"Lien\"],\"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…\"],\"2_DHgA\":[\"Identifiant d’URL\"],\"2nrsEn\":[\"message supprimé\"],\"2ygf_L\":[\"← Retour\"],\"3KKSM4\":[\"privé\"],\"3QuNUr\":[\"Plusieurs personnes écrivent…\"],\"3SEwY1\":[\"Supprimer la catégorie \\\"\",[\"0\"],\"\\\" ? Cette action est irréversible.\"],\"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.\"],\"4OjqAQ\":[\"Continuer l'édition\"],\"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…\"],\"5KHDCi\":[[\"0\"],\" et \",[\"1\"],\" sont en train d'écrire…\"],\"5TviPn\":[\"Annuler la recherche\"],\"5cC8f2\":[\"Modifié le \",[\"0\"]],\"5oD9f_\":[\"Plus tôt\"],\"6Qly-0\":[\"un commentaire\"],\"6YE0P6\":[\"Édition de \",[\"0\"]],\"6YtxFj\":[\"Nom\"],\"6gRgw8\":[\"Réessayer\"],\"7PHCIN\":[\"Annuler la suppression\"],\"7PzzBU\":[\"Utilisateur\"],\"7d1a0d\":[\"Public\"],\"7sNhEz\":[\"Nom d'utilisateur\"],\"8F1i42\":[\"Page introuvable\"],\"8Ug9jB\":[\"Connexe\"],\"8ZsakT\":[\"Mot de passe\"],\"92o1-U\":[\"Suppression échouée\"],\"9BruTc\":[\"Pourquoi ?\"],\"9l4qcT\":[\"Déposez un fichier de remplacement ici\"],\"9uI_rE\":[\"Annuler\"],\"9xDZu_\":[\"Impossible de charger.\"],\"A0y396\":[\"+ Inviter quelqu'un\"],\"A1taO8\":[\"Rechercher\"],\"AEfu-D\":[\"Dans les collections\"],\"AHZflp\":[\"Tribune\"],\"AQbgNR\":[\"Suivre \",[\"targetUsername\"]],\"AS7WoE\":[\"Repartir de zéro\"],\"ATGYL1\":[\"Adresse e-mail\"],\"AZctoV\":[[\"0\",\"plural\",{\"one\":[\"#\",\" commentaire\"],\"other\":[\"#\",\" commentaires\"]}]],\"Ade-6d\":[\"Mises à jour en direct indisponibles.\"],\"AeXO77\":[\"Compte\"],\"AnRu_j\":[\"Annuler la réponse\"],\"BlpJ2G\":[\"Reprise de votre brouillon.\"],\"CI50ct\":[\"Voté\"],\"C_qQE4\":[\"Déjà recommandé par \",[\"0\"],\" \",[\"1\"]],\"Cj24wt\":[\"Inscription…\"],\"D8DRYV\":[\"Supprimer ce message ?\"],\"DPfwMq\":[\"Terminé\"],\"DdeHXH\":[\"Supprimer cette reco ? Cette action est irréversible.\"],\"Dp1JhP\":[\"<0>\",[\"0\"],\" a voté pour <1>\",[\"1\"],\"\"],\"ECiS12\":[\"Voir la reco →\"],\"ED8n0z\":[\"la tribune\"],\"Evcm2Z\":[\"Fermer sans publier ? Le fichier joint sera perdu — ce que vous avez écrit sera conservé.\"],\"ExR0Fr\":[\"L'URL est obligatoire.\"],\"F5Js1v\":[\"Ne plus suivre la collection\"],\"FdTlEG\":[\"Piste \",[\"0\"]],\"FgAxTj\":[\"Se déconnecter\"],\"Fxf4jq\":[\"Description (facultatif)\"],\"GCWCCo\":[\"Fermer le lecteur\"],\"GDvlUT\":[\"Rôle\"],\"GLukaH\":[\"Modérateur\"],\"GNSsCc\":[\"Impossible de créer la collection\"],\"GQ38dl\":[[\"unreadChatCount\"],\" messages non lus\"],\"GY6uSc\":[\"Suivant →\"],\"GbqhrN\":[[\"0\"],\"–\",[\"1\"],\" caractères : lettres, chiffres ou tirets bas\"],\"GptGxg\":[\"Changer le mot de passe\"],\"H4MGkI\":[\"Agrandir le lecteur\"],\"H4o4sk\":[\"Personne pour le moment.\"],\"H8pzW-\":[\"Impossible de mettre à jour l'avatar\"],\"HTLDA4\":[\"Chargement…\"],\"I-x669\":[\"Invités\"],\"IO9KL_\":[\"Piste suivante\"],\"IZX7TO\":[\"Impossible de générer une invitation\"],\"IagCbF\":[\"URL\"],\"ImOQa9\":[\"Répondre\"],\"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\"],\"JlFcis\":[\"Envoyer\"],\"K7tIrx\":[\"Catégorie\"],\"KDGWg5\":[\"Retirer de la collection\"],\"KKZxBU\":[\"Envoi \",[\"0\"],\" %\"],\"K_F6pa\":[\"Enregistrement…\"],\"KtYF-t\":[\"Sons\"],\"L-rMC9\":[\"Réinitialiser par défaut\"],\"LLyMkV\":[\"Suivies (\",[\"0\"],[\"1\"],\")\"],\"MHrjPM\":[\"Titre\"],\"MKEPCY\":[\"Suivre\"],\"MTb2Ot\":[\"Aucun message pour l'instant. Dites bonjour !\"],\"Mj6oCJ\":[[\"0\"],\" est en train d'écrire…\"],\"N40H-G\":[\"Tout\"],\"N6j2JH\":[\"Modifier \",[\"0\"]],\"NUrY9o\":[\"Catégories\"],\"Oprv1v\":[\"Mot de passe (min. \",[\"0\"],\" caractères)\"],\"Oz0N9s\":[\"nouveau\"],\"PUe8Yk\":[\"Réduire le lecteur\"],\"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\"],\"RfwZxd\":[\"Réinitialiser le mot de passe\"],\"Rrp6-J\":[\"Se connecter pour aimer\"],\"RvVJBH\":[\"Actualisation du flux…\"],\"SBTElJ\":[\"Recherche…\"],\"Sad2tK\":[\"Envoi…\"],\"So7gCD\":[\"Collez un lien…\"],\"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\"],\"VCoEm-\":[\"Retour à la connexion\"],\"VKsaTi\":[\"En réponse à\"],\"V_e7nf\":[\"Définir un nouveau mot de passe\"],\"VnNJbN\":[\"De collections\"],\"VskHIx\":[\"Supprimer la catégorie\"],\"VyTYmS\":[\"Changer l'avatar\"],\"W9FRBT\":[\"Aimer\"],\"WE_r9K\":[\"Échec de la création de la catégorie\"],\"WfffrI\":[\"identifiant\"],\"WhimMi\":[\"Échec de la réinitialisation\"],\"WpXcBJ\":[\"Rien ici pour l'instant.\"],\"XJVYo8\":[\"Pourquoi & où\"],\"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.\"],\"Z5HWHd\":[\"Activés\"],\"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…\"],\"az8lvo\":[\"Désactivés\"],\"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\"],\"dqwCfR\":[\"Impossible de charger un aperçu pour ce lien — vérifiez-le, ou publiez-le tel quel et actualisez l'aperçu plus tard.\"],\"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…\"],\"eloqXQ\":[\"Vous pouvez la publier quand même.\"],\"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é\"],\"gL9KKi\":[\"Qui peut la voir\"],\"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.\"],\"goWEhR\":[\"Piste précédente\"],\"hBuUKa\":[\"Changer le mot de passe…\"],\"hD7w09\":[\"Vous avez tout lu, tout vu, tout bu.\"],\"hJSliC\":[\"<0>\",[\"0\"],\" a publié <1>\",[\"1\"],\"\"],\"hT8hgO\":[\"Rétablir le titre suggéré\"],\"hYgDIe\":[\"Créer\"],\"he3ygx\":[\"Copier\"],\"hpCc_Z\":[\"Fermer quand même\"],\"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 !\"],\"k112DD\":[\"Charger les messages plus anciens\"],\"k36iJ-\":[\"Échec de l’enregistrement de la catégorie\"],\"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\"],\"mb_Wju\":[\"Déposez un fichier ici, ou collez-en un\"],\"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\"],\"oTYvvN\":[[\"0\",\"plural\",{\"one\":[\"et \",\"#\",\" reco plus ancienne\"],\"other\":[\"et \",\"#\",\" recos plus anciennes\"]}]],\"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.\"],\"qsqt0w\":[\"Lien d'invitation\"],\"qvFa8r\":[\"public\"],\"qvz_Pp\":[\"Reco\"],\"rCbqPX\":[\"Ce lien d'invitation est manquant, expiré ou déjà utilisé.\"],\"rDMzDU\":[\"Écrivez un message…\"],\"rg9pXu\":[\"Recherche échouée\"],\"rtpJqV\":[\"Recos (\",[\"0\"],[\"1\"],\")\"],\"sEXJPi\":[\"Lien ou fichier\"],\"sGeXL3\":[\"Miniature\"],\"sQia9P\":[\"Se connecter\"],\"sTiqbm\":[\"invité par\"],\"sdP5Aa\":[\"[supprimé]\"],\"sgX7B-\":[\"Usage unique, valable 7 jours.\"],\"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\"]],\"uomLas\":[\"Ajouter une catégorie\"],\"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\"],\"yzF66j\":[\"Lien\"],\"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 82075dd9..99aa5c2b 100644 --- a/src/locales/fr.po +++ b/src/locales/fr.po @@ -35,12 +35,12 @@ msgstr "{0, plural, one {et # reco plus ancienne} other {et # recos plus ancienn #. placeholder {0}: typingNames[0] #. placeholder {1}: typingNames[1] -#: src/components/ChatModal.tsx:534 +#: src/components/ChatModal.tsx:568 msgid "{0} and {1} are typing…" msgstr "{0} et {1} sont en train d'écrire…" #. placeholder {0}: typingNames[0] -#: src/components/ChatModal.tsx:532 +#: src/components/ChatModal.tsx:566 msgid "{0} is typing…" msgstr "{0} est en train d'écrire…" @@ -60,7 +60,7 @@ msgstr "{visibleCount, plural, one {# commentaire} other {# commentaires}}" #: src/components/DumpCreateModal.tsx:936 #: src/pages/PlaylistDetail.tsx:572 -#: src/pages/UserPublicProfile.tsx:798 +#: src/pages/UserPublicProfile.tsx:801 msgid "← Back" msgstr "← Retour" @@ -76,7 +76,7 @@ msgstr "← Retour à toutes les recos" msgid "← Back to profile" msgstr "← Retour au profil" -#: src/pages/UserPublicProfile.tsx:134 +#: src/pages/UserPublicProfile.tsx:136 msgid "+ Invite someone" msgstr "+ Inviter quelqu'un" @@ -142,7 +142,7 @@ msgstr "un commentaire" msgid "a post" msgstr "une publication" -#: src/pages/UserPublicProfile.tsx:1179 +#: src/pages/UserPublicProfile.tsx:1182 msgid "Account" msgstr "Compte" @@ -155,7 +155,7 @@ msgstr "Ajouter un commentaire…" msgid "Add category" msgstr "Ajouter une catégorie" -#: src/pages/UserPublicProfile.tsx:876 +#: src/pages/UserPublicProfile.tsx:879 msgid "Add email…" msgstr "Ajouter un e-mail…" @@ -164,7 +164,7 @@ msgstr "Ajouter un e-mail…" msgid "Add to playlist" msgstr "Ajouter à la collection" -#: src/pages/UserPublicProfile.tsx:1604 +#: src/pages/UserPublicProfile.tsx:1646 msgid "Admin" msgstr "Administrateur" @@ -183,7 +183,7 @@ msgstr "Déjà recommandé par {0} {1}" msgid "Already have an account? <0>Log in" msgstr "Vous avez déjà un compte ? <0>Se connecter" -#: src/pages/UserPublicProfile.tsx:1198 +#: src/pages/UserPublicProfile.tsx:1201 msgid "Appearance" msgstr "Apparence" @@ -194,7 +194,7 @@ msgstr "Apparence" msgid "At least {0} characters" msgstr "Au moins {0} caractères" -#: src/pages/UserPublicProfile.tsx:1253 +#: src/pages/UserPublicProfile.tsx:1263 msgid "Auto" msgstr "Auto" @@ -203,8 +203,8 @@ msgstr "Auto" msgid "Back to login" msgstr "Retour à la connexion" -#: src/contexts/WSProvider.tsx:270 -#: src/contexts/WSProvider.tsx:576 +#: src/contexts/WSProvider.tsx:292 +#: src/contexts/WSProvider.tsx:608 msgid "Can't connect to the live updates server. Upvotes and notifications may not sync until it reconnects." msgstr "Impossible de se connecter au serveur de mises à jour en direct. Les votes et les notifications pourraient ne pas se synchroniser avant la reconnexion." @@ -218,8 +218,8 @@ msgstr "Impossible de se connecter au serveur de mises à jour en direct. Les vo #: src/pages/Dump.tsx:406 #: src/pages/DumpEdit.tsx:484 #: src/pages/PlaylistDetail.tsx:923 -#: src/pages/UserPublicProfile.tsx:1733 -#: src/pages/UserPublicProfile.tsx:1806 +#: src/pages/UserPublicProfile.tsx:1775 +#: src/pages/UserPublicProfile.tsx:1848 msgid "Cancel" msgstr "Annuler" @@ -227,8 +227,8 @@ msgstr "Annuler" msgid "Cancel removal" msgstr "Annuler la suppression" -#: src/components/ChatModal.tsx:624 -#: src/components/ChatModal.tsx:625 +#: src/components/ChatModal.tsx:658 +#: src/components/ChatModal.tsx:659 msgid "Cancel reply" msgstr "Annuler la réponse" @@ -237,7 +237,7 @@ msgid "Cancel search" msgstr "Annuler la recherche" #: src/components/CategorySelect.tsx:22 -#: src/pages/UserPublicProfile.tsx:1336 +#: src/pages/UserPublicProfile.tsx:1378 msgid "Categories" msgstr "Catégories" @@ -245,7 +245,7 @@ msgstr "Catégories" msgid "Category" msgstr "Catégorie" -#: src/pages/UserPublicProfile.tsx:825 +#: src/pages/UserPublicProfile.tsx:828 msgid "Change avatar" msgstr "Changer l'avatar" @@ -254,14 +254,14 @@ msgstr "Changer l'avatar" msgid "Change password" msgstr "Changer le mot de passe" -#: src/pages/UserPublicProfile.tsx:1191 +#: src/pages/UserPublicProfile.tsx:1194 msgid "Change password…" msgstr "Changer le mot de passe…" #: src/components/ChatButton.tsx:17 #: src/components/ChatFab.tsx:49 #: src/components/ChatFab.tsx:50 -#: src/components/ChatModal.tsx:538 +#: src/components/ChatModal.tsx:572 msgid "Chat" msgstr "Tribune" @@ -295,7 +295,7 @@ msgstr "Fermer sans publier ? Le fichier joint sera perdu — ce que vous avez msgid "Collapse player" msgstr "Réduire le lecteur" -#: src/pages/UserPublicProfile.tsx:1245 +#: src/pages/UserPublicProfile.tsx:1255 msgid "Color scheme" msgstr "Thème de couleur" @@ -304,11 +304,11 @@ msgstr "Thème de couleur" msgid "Confirm new password" msgstr "Confirmer le nouveau mot de passe" -#: src/pages/UserPublicProfile.tsx:159 +#: src/pages/UserPublicProfile.tsx:161 msgid "Copied!" msgstr "Copié !" -#: src/pages/UserPublicProfile.tsx:159 +#: src/pages/UserPublicProfile.tsx:161 msgid "Copy" msgstr "Copier" @@ -350,11 +350,11 @@ msgstr "Création…" msgid "Current password" msgstr "Mot de passe actuel" -#: src/pages/UserPublicProfile.tsx:1267 +#: src/pages/UserPublicProfile.tsx:1277 msgid "Dark" msgstr "Sombre" -#: src/pages/UserPublicProfile.tsx:1280 +#: src/pages/UserPublicProfile.tsx:1290 msgid "Default tab" msgstr "Onglet par défaut" @@ -446,13 +446,13 @@ msgstr "Recommandé !" #: src/pages/Search.tsx:174 #: src/pages/UserDumps.tsx:80 -#: src/pages/UserPublicProfile.tsx:948 +#: src/pages/UserPublicProfile.tsx:951 msgid "Dumps" msgstr "Recos" #. placeholder {0}: dumps.items.length #. placeholder {1}: dumps.hasMore ? "+" : "" -#: src/pages/UserPublicProfile.tsx:968 +#: src/pages/UserPublicProfile.tsx:971 msgid "Dumps ({0}{1})" msgstr "Recos ({0}{1})" @@ -530,9 +530,9 @@ msgstr "Impossible de créer la collection" msgid "Failed to delete" msgstr "Suppression échouée" -#: src/pages/UserPublicProfile.tsx:104 -#: src/pages/UserPublicProfile.tsx:107 -#: src/pages/UserPublicProfile.tsx:168 +#: src/pages/UserPublicProfile.tsx:106 +#: src/pages/UserPublicProfile.tsx:109 +#: src/pages/UserPublicProfile.tsx:170 msgid "Failed to generate invite" msgstr "Impossible de générer une invitation" @@ -541,9 +541,9 @@ msgstr "Impossible de générer une invitation" #: src/pages/index/JournalFeed.tsx:33 #: src/pages/index/NewFeed.tsx:36 #: src/pages/Notifications.tsx:374 -#: src/pages/UserPublicProfile.tsx:1070 -#: src/pages/UserPublicProfile.tsx:1112 -#: src/pages/UserPublicProfile.tsx:1157 +#: src/pages/UserPublicProfile.tsx:1073 +#: src/pages/UserPublicProfile.tsx:1115 +#: src/pages/UserPublicProfile.tsx:1160 msgid "Failed to load" msgstr "Chargement échoué" @@ -560,8 +560,8 @@ msgid "Failed to post reply" msgstr "Impossible de publier la réponse" #: src/pages/PlaylistDetail.tsx:911 -#: src/pages/UserPublicProfile.tsx:1736 -#: src/pages/UserPublicProfile.tsx:1808 +#: src/pages/UserPublicProfile.tsx:1778 +#: src/pages/UserPublicProfile.tsx:1850 msgid "Failed to save" msgstr "Enregistrement échoué" @@ -573,15 +573,15 @@ msgstr "Échec de l’enregistrement de la catégorie" msgid "Failed to save edit" msgstr "Impossible d'enregistrer la modification" -#: src/pages/UserPublicProfile.tsx:885 +#: src/pages/UserPublicProfile.tsx:888 msgid "Failed to update avatar" msgstr "Impossible de mettre à jour l'avatar" -#: src/pages/UserPublicProfile.tsx:1674 +#: src/pages/UserPublicProfile.tsx:1716 msgid "Failed to update role" msgstr "Erreur lors de la mise à jour du rôle" -#: src/pages/UserPublicProfile.tsx:1275 +#: src/pages/UserPublicProfile.tsx:1285 msgid "Feeds" msgstr "Flux" @@ -615,8 +615,8 @@ msgid "Follow some users to see their dumps here." msgstr "Suivez des utilisateurs pour voir leurs recos ici." #: src/components/FeedTabBar.tsx:34 -#: src/pages/UserPublicProfile.tsx:950 -#: src/pages/UserPublicProfile.tsx:1309 +#: src/pages/UserPublicProfile.tsx:953 +#: src/pages/UserPublicProfile.tsx:1319 msgid "Followed" msgstr "Suivi" @@ -626,13 +626,13 @@ msgstr "Suivi" msgid "Followed ({0}{1})" msgstr "Suivies ({0}{1})" -#: src/pages/UserPublicProfile.tsx:1101 +#: src/pages/UserPublicProfile.tsx:1104 msgid "Followed playlists" msgstr "Collections suivies" #: src/components/FollowButton.tsx:37 #: src/components/FollowButton.tsx:64 -#: src/pages/UserPublicProfile.tsx:1059 +#: src/pages/UserPublicProfile.tsx:1062 msgid "Following" msgstr "Abonné" @@ -657,7 +657,7 @@ msgid "Go to login" msgstr "Aller à la connexion" #: src/components/FeedTabBar.tsx:30 -#: src/pages/UserPublicProfile.tsx:1288 +#: src/pages/UserPublicProfile.tsx:1298 msgid "Hot" msgstr "Tendances" @@ -677,22 +677,22 @@ msgstr "Invitation invalide" msgid "Invalid link" msgstr "Lien invalide" -#: src/pages/UserPublicProfile.tsx:142 -#: src/pages/UserPublicProfile.tsx:149 +#: src/pages/UserPublicProfile.tsx:144 +#: src/pages/UserPublicProfile.tsx:151 msgid "Invite link" msgstr "Lien d'invitation" -#: src/pages/UserPublicProfile.tsx:847 +#: src/pages/UserPublicProfile.tsx:850 msgid "invited by" msgstr "invité par" -#: src/pages/UserPublicProfile.tsx:951 -#: src/pages/UserPublicProfile.tsx:1146 +#: src/pages/UserPublicProfile.tsx:954 +#: src/pages/UserPublicProfile.tsx:1149 msgid "Invitees" msgstr "Invités" #: src/components/FeedTabBar.tsx:32 -#: src/pages/UserPublicProfile.tsx:1302 +#: src/pages/UserPublicProfile.tsx:1312 msgid "Journal" msgstr "Journal" @@ -700,7 +700,7 @@ msgstr "Journal" msgid "Keep editing" msgstr "Continuer l'édition" -#: src/pages/UserPublicProfile.tsx:1260 +#: src/pages/UserPublicProfile.tsx:1270 msgid "Light" msgstr "Clair" @@ -716,7 +716,7 @@ msgstr "Lien" msgid "Link or file" msgstr "Lien ou fichier" -#: src/contexts/WSProvider.tsx:575 +#: src/contexts/WSProvider.tsx:607 msgid "Live updates are temporarily disconnected. Trying to reconnect…" msgstr "Les mises à jour en direct sont temporairement interrompues. Tentative de reconnexion…" @@ -729,7 +729,7 @@ msgstr "Mises à jour en direct indisponibles." msgid "Load more" msgstr "Charger plus" -#: src/components/ChatModal.tsx:551 +#: src/components/ChatModal.tsx:585 msgid "Load older messages" msgstr "Charger les messages plus anciens" @@ -740,7 +740,7 @@ msgstr "Chargement de la reco…" #: src/pages/index/FollowedFeed.tsx:111 #: src/pages/index/HotFeed.tsx:64 -#: src/pages/index/JournalFeed.tsx:62 +#: src/pages/index/JournalFeed.tsx:73 #: src/pages/index/NewFeed.tsx:64 #: src/pages/Search.tsx:244 #: src/pages/UserDumps.tsx:121 @@ -754,12 +754,12 @@ msgstr "Chargement…" msgid "Loading playlist…" msgstr "Chargement de la collection…" -#: src/pages/UserPublicProfile.tsx:781 +#: src/pages/UserPublicProfile.tsx:784 msgid "Loading profile…" msgstr "Chargement du profil…" #: src/components/CategoryManager.tsx:52 -#: src/components/ChatModal.tsx:550 +#: src/components/ChatModal.tsx:584 #: src/components/PlaylistMembershipPanel.tsx:28 #: src/components/TextEditor.tsx:311 #: src/components/UserListPopover.tsx:192 @@ -772,9 +772,9 @@ msgstr "Chargement du profil…" #: src/pages/Notifications.tsx:446 #: src/pages/UserDumps.tsx:54 #: src/pages/UserPlaylists.tsx:345 -#: src/pages/UserPublicProfile.tsx:1064 -#: src/pages/UserPublicProfile.tsx:1106 -#: src/pages/UserPublicProfile.tsx:1151 +#: src/pages/UserPublicProfile.tsx:1067 +#: src/pages/UserPublicProfile.tsx:1109 +#: src/pages/UserPublicProfile.tsx:1154 #: src/pages/UserUpvoted.tsx:125 msgid "Loading…" msgstr "Chargement…" @@ -794,8 +794,8 @@ msgstr "Se connecter pour aimer" msgid "Log in to vote" msgstr "Se connecter pour voter" -#: src/pages/UserPublicProfile.tsx:802 -#: src/pages/UserPublicProfile.tsx:898 +#: src/pages/UserPublicProfile.tsx:805 +#: src/pages/UserPublicProfile.tsx:901 msgid "Log out" msgstr "Se déconnecter" @@ -807,7 +807,7 @@ msgstr "Connexion…" msgid "Login failed" msgstr "Connexion échouée" -#: src/pages/UserPublicProfile.tsx:956 +#: src/pages/UserPublicProfile.tsx:959 msgid "Manage" msgstr "Administration" @@ -815,7 +815,7 @@ msgstr "Administration" msgid "Max 50 MB" msgstr "Max 50 Mo" -#: src/pages/UserPublicProfile.tsx:1603 +#: src/pages/UserPublicProfile.tsx:1645 msgid "Moderator" msgstr "Modérateur" @@ -829,7 +829,7 @@ msgid "new" msgstr "nouveau" #: src/components/FeedTabBar.tsx:31 -#: src/pages/UserPublicProfile.tsx:1295 +#: src/pages/UserPublicProfile.tsx:1305 msgid "New" msgstr "Nouveau" @@ -837,7 +837,7 @@ msgstr "Nouveau" #: src/components/DumpFab.tsx:65 #: src/components/DumpFab.tsx:66 #: src/pages/UserDumps.tsx:88 -#: src/pages/UserPublicProfile.tsx:1382 +#: src/pages/UserPublicProfile.tsx:1424 msgid "New dump" msgstr "Nouvelle reco" @@ -878,15 +878,15 @@ msgid "No emoji found." msgstr "Aucun emoji trouvé." #: src/pages/UserPlaylists.tsx:442 -#: src/pages/UserPublicProfile.tsx:1119 +#: src/pages/UserPublicProfile.tsx:1122 msgid "No followed playlists yet." msgstr "Pas encore de collections suivies." -#: src/pages/UserPublicProfile.tsx:1164 +#: src/pages/UserPublicProfile.tsx:1167 msgid "No invitees yet." msgstr "Aucun invité pour le moment." -#: src/components/ChatModal.tsx:558 +#: src/components/ChatModal.tsx:592 msgid "No messages yet. Say hello!" msgstr "Aucun message pour l'instant. Dites bonjour !" @@ -900,7 +900,7 @@ msgstr "Aucune collection ne correspond à « {q} »." #: src/components/PlaylistMembershipPanel.tsx:34 #: src/pages/UserPlaylists.tsx:400 -#: src/pages/UserPublicProfile.tsx:1030 +#: src/pages/UserPublicProfile.tsx:1033 msgid "No playlists yet." msgstr "Pas encore de collections." @@ -908,14 +908,14 @@ msgstr "Pas encore de collections." msgid "No users match \"{q}\"." msgstr "Aucun utilisateur ne correspond à « {q} »." -#: src/pages/UserPublicProfile.tsx:1077 +#: src/pages/UserPublicProfile.tsx:1080 msgid "Not following anyone yet." msgstr "Aucun abonnement pour le moment." #: src/pages/Notifications.tsx:381 #: src/pages/UserDumps.tsx:99 -#: src/pages/UserPublicProfile.tsx:1393 -#: src/pages/UserPublicProfile.tsx:1515 +#: src/pages/UserPublicProfile.tsx:1435 +#: src/pages/UserPublicProfile.tsx:1557 #: src/pages/UserUpvoted.tsx:159 msgid "Nothing here yet." msgstr "Rien ici pour l'instant." @@ -923,6 +923,7 @@ msgstr "Rien ici pour l'instant." #: src/components/NotificationBell.tsx:42 #: src/pages/Notifications.tsx:265 #: src/pages/Notifications.tsx:359 +#: src/pages/UserPublicProfile.tsx:1327 msgid "Notifications" msgstr "Notifications" @@ -930,6 +931,14 @@ msgstr "Notifications" msgid "Notifications ({unreadNotificationCount} unread)" msgstr "Notifications ({unreadNotificationCount} non lues)" +#: src/pages/UserPublicProfile.tsx:1351 +msgid "Off" +msgstr "Désactivés" + +#: src/pages/UserPublicProfile.tsx:1344 +msgid "On" +msgstr "Activés" + #: src/components/SearchBar.tsx:96 msgid "Open search" msgstr "Ouvrir la recherche" @@ -943,7 +952,7 @@ msgid "Page not found" msgstr "Page introuvable" #: src/pages/UserLogin.tsx:76 -#: src/pages/UserPublicProfile.tsx:1184 +#: src/pages/UserPublicProfile.tsx:1187 msgid "Password" msgstr "Mot de passe" @@ -982,13 +991,13 @@ msgstr "Titre de la collection" #: src/components/UserMenu.tsx:62 #: src/pages/Search.tsx:177 #: src/pages/UserPlaylists.tsx:371 -#: src/pages/UserPublicProfile.tsx:949 +#: src/pages/UserPublicProfile.tsx:952 msgid "Playlists" msgstr "Collections" #. placeholder {0}: playlists.items.length #. placeholder {1}: playlists.hasMore ? "+" : "" -#: src/pages/UserPublicProfile.tsx:999 +#: src/pages/UserPublicProfile.tsx:1002 msgid "Playlists ({0}{1})" msgstr "Collections ({0}{1})" @@ -1090,7 +1099,7 @@ msgstr "Remplacer le fichier" msgid "Reply" msgstr "Répondre" -#: src/components/ChatModal.tsx:614 +#: src/components/ChatModal.tsx:648 msgid "Replying to" msgstr "En réponse à" @@ -1121,7 +1130,7 @@ msgstr "Rétablir le titre suggéré" msgid "Retry" msgstr "Réessayer" -#: src/pages/UserPublicProfile.tsx:1658 +#: src/pages/UserPublicProfile.tsx:1700 msgid "Role" msgstr "Rôle" @@ -1130,8 +1139,8 @@ msgstr "Rôle" #: src/pages/Dump.tsx:398 #: src/pages/DumpEdit.tsx:487 #: src/pages/PlaylistDetail.tsx:930 -#: src/pages/UserPublicProfile.tsx:1725 -#: src/pages/UserPublicProfile.tsx:1798 +#: src/pages/UserPublicProfile.tsx:1767 +#: src/pages/UserPublicProfile.tsx:1840 msgid "Save" msgstr "Enregistrer" @@ -1140,8 +1149,8 @@ msgstr "Enregistrer" #: src/pages/Dump.tsx:397 #: src/pages/PlaylistDetail.tsx:926 #: src/pages/ResetPassword.tsx:126 -#: src/pages/UserPublicProfile.tsx:1722 -#: src/pages/UserPublicProfile.tsx:1795 +#: src/pages/UserPublicProfile.tsx:1764 +#: src/pages/UserPublicProfile.tsx:1837 msgid "Saving…" msgstr "Enregistrement…" @@ -1163,7 +1172,7 @@ msgstr "Recherche échouée" msgid "Searching…" msgstr "Recherche…" -#: src/components/ChatModal.tsx:660 +#: src/components/ChatModal.tsx:694 msgid "Send" msgstr "Envoyer" @@ -1184,15 +1193,15 @@ msgstr "Serveur inaccessible" msgid "Set new password" msgstr "Définir un nouveau mot de passe" -#: src/pages/UserPublicProfile.tsx:953 +#: src/pages/UserPublicProfile.tsx:956 msgid "Settings" msgstr "Paramètres" -#: src/components/ChatModal.tsx:535 +#: src/components/ChatModal.tsx:569 msgid "Several people are typing…" msgstr "Plusieurs personnes écrivent…" -#: src/pages/UserPublicProfile.tsx:162 +#: src/pages/UserPublicProfile.tsx:164 msgid "Single use, valid for 7 days." msgstr "Usage unique, valable 7 jours." @@ -1205,11 +1214,15 @@ msgstr "identifiant" msgid "Something went wrong" msgstr "Une erreur est survenue" +#: src/pages/UserPublicProfile.tsx:1332 +msgid "Sounds" +msgstr "Sons" + #: src/components/DumpCreateModal.tsx:707 msgid "Start fresh" msgstr "Repartir de zéro" -#: src/pages/UserPublicProfile.tsx:1203 +#: src/pages/UserPublicProfile.tsx:1206 msgid "Style" msgstr "Style" @@ -1261,7 +1274,7 @@ msgstr "Aujourd'hui" msgid "Track {0}" msgstr "Piste {0}" -#: src/components/ChatModal.tsx:637 +#: src/components/ChatModal.tsx:671 msgid "Type a message…" msgstr "Écrivez un message…" @@ -1277,7 +1290,7 @@ msgstr "Ne plus suivre {targetUsername}" msgid "Unfollow playlist" msgstr "Ne plus suivre la collection" -#: src/pages/UserPublicProfile.tsx:748 +#: src/pages/UserPublicProfile.tsx:751 msgid "Upload failed" msgstr "Envoi échoué" @@ -1300,7 +1313,7 @@ msgstr "Voté" #. placeholder {0}: votes.items.length #. placeholder {1}: votes.hasMore ? "+" : "" -#: src/pages/UserPublicProfile.tsx:979 +#: src/pages/UserPublicProfile.tsx:982 msgid "Upvoted ({0}{1})" msgstr "Votés ({0}{1})" @@ -1316,11 +1329,11 @@ msgstr "L'URL est obligatoire." msgid "URL slug" msgstr "Identifiant d’URL" -#: src/pages/UserPublicProfile.tsx:1602 +#: src/pages/UserPublicProfile.tsx:1644 msgid "User" msgstr "Utilisateur" -#: src/pages/UserPublicProfile.tsx:1321 +#: src/pages/UserPublicProfile.tsx:1363 msgid "User management" msgstr "Gestion utilisateur" @@ -1337,11 +1350,11 @@ msgstr "Nom d'utilisateur" msgid "Users" msgstr "Utilisateurs" -#: src/pages/UserPublicProfile.tsx:1049 -#: src/pages/UserPublicProfile.tsx:1092 -#: src/pages/UserPublicProfile.tsx:1134 -#: src/pages/UserPublicProfile.tsx:1414 -#: src/pages/UserPublicProfile.tsx:1545 +#: src/pages/UserPublicProfile.tsx:1052 +#: src/pages/UserPublicProfile.tsx:1095 +#: src/pages/UserPublicProfile.tsx:1137 +#: src/pages/UserPublicProfile.tsx:1456 +#: src/pages/UserPublicProfile.tsx:1587 msgid "View all →" msgstr "Tout voir →" @@ -1354,8 +1367,8 @@ msgstr "Voir la reco →" msgid "What makes it worth it?" msgstr "Pourquoi on en voudrait ?" -#: src/pages/UserPublicProfile.tsx:933 -#: src/pages/UserPublicProfile.tsx:1787 +#: src/pages/UserPublicProfile.tsx:936 +#: src/pages/UserPublicProfile.tsx:1829 msgid "Who am I?" msgstr "Qui suis-je ?" @@ -1391,7 +1404,7 @@ msgstr "Vous serez notifié lorsque quelqu'un suit vos collections, vote pour vo #: src/pages/index/FollowedFeed.tsx:116 #: src/pages/index/HotFeed.tsx:69 -#: src/pages/index/JournalFeed.tsx:67 +#: src/pages/index/JournalFeed.tsx:78 #: src/pages/index/NewFeed.tsx:69 #: src/pages/Search.tsx:249 #: src/pages/UserDumps.tsx:126 diff --git a/src/pages/UserPublicProfile.tsx b/src/pages/UserPublicProfile.tsx index d0dceb6c..3df8869e 100644 --- a/src/pages/UserPublicProfile.tsx +++ b/src/pages/UserPublicProfile.tsx @@ -42,6 +42,8 @@ import { useAuth } from "../hooks/useAuth.ts"; import { useDocumentTitle } from "../hooks/useDocumentTitle.ts"; import { useTheme } from "../hooks/useTheme.ts"; import { useDefaultFeedTab } from "../hooks/useDefaultFeedTab.ts"; +import { useSoundsEnabled } from "../hooks/useSoundsEnabled.ts"; +import { previewChime } from "../utils/sound.ts"; import { useWS } from "../hooks/useWS.ts"; import { useDumpListSync } from "../hooks/useDumpListSync.ts"; import { useFading } from "../hooks/useFading.ts"; @@ -235,6 +237,7 @@ export function UserPublicProfile() { const navigate = useNavigate(); const { user: me, authFetch, login, logout, token } = useAuth(); const { style, colorScheme, setStyle, setColorScheme } = useTheme(); + const [soundsEnabled, setSoundsEnabled] = useSoundsEnabled(); const [defaultFeedTab, setDefaultFeedTab] = useDefaultFeedTab(); const { voteCounts, @@ -1319,6 +1322,38 @@ export function UserPublicProfile() { +
+

+ Notifications +

+
+
+ + Sounds + +
+ + +
+
+
+
)} diff --git a/src/utils/sound.ts b/src/utils/sound.ts new file mode 100644 index 00000000..ec0613e5 --- /dev/null +++ b/src/utils/sound.ts @@ -0,0 +1,123 @@ +// Light UI sounds for live notifications and chat, synthesised with Web Audio +// so there's no asset to ship or cache. Two voices: a single soft blip for a +// chat message, and a two-note rising chime for a notification (a mention, a +// follow, an upvote…). + +const STORAGE_KEY = "sounds"; +// Shared across tabs: the id of the last event that chimed, so a user with the +// site open in several tabs hears one chime per event rather than one per tab. +const CLAIM_KEY = "sounds-last-claim"; +// A burst of chat messages shouldn't machine-gun blips; one per window is +// enough to say "the chat is moving". +const CHAT_THROTTLE = 4_000; + +export type ChimeKind = "chat" | "notify"; + +export function readSoundsEnabled(): boolean { + try { + return localStorage.getItem(STORAGE_KEY) !== "off"; + } catch { + return true; + } +} + +export function writeSoundsEnabled(enabled: boolean): void { + try { + localStorage.setItem(STORAGE_KEY, enabled ? "on" : "off"); + } catch { + // Private mode / blocked storage: the choice just won't persist. + } +} + +let ctx: AudioContext | null = null; +let lastChatAt = 0; + +function audio(): AudioContext | null { + if (ctx) return ctx; + try { + ctx = new AudioContext(); + } catch { + return null; + } + return ctx; +} + +// Browsers keep an AudioContext suspended until the page sees a user gesture. +// Resume it on the first one so a chime arriving later — possibly while the +// tab is in the background — can actually be heard. +function unlock() { + const ac = audio(); + if (ac?.state === "suspended") void ac.resume(); + globalThis.removeEventListener("pointerdown", unlock); + globalThis.removeEventListener("keydown", unlock); +} +globalThis.addEventListener("pointerdown", unlock); +globalThis.addEventListener("keydown", unlock); + +function claim(eventId: string): boolean { + try { + if (localStorage.getItem(CLAIM_KEY) === eventId) return false; + localStorage.setItem(CLAIM_KEY, eventId); + } catch { + // No storage: every tab chimes, which is the lesser evil. + } + return true; +} + +function tone( + ac: AudioContext, + freq: number, + start: number, + duration: number, + peak: number, +) { + const osc = ac.createOscillator(); + const gain = ac.createGain(); + osc.type = "sine"; + osc.frequency.setValueAtTime(freq, start); + // Fast attack, exponential tail: a struck, glassy note rather than a beep. + gain.gain.setValueAtTime(0.0001, start); + gain.gain.exponentialRampToValueAtTime(peak, start + 0.012); + gain.gain.exponentialRampToValueAtTime(0.0001, start + duration); + osc.connect(gain).connect(ac.destination); + osc.start(start); + osc.stop(start + duration + 0.02); +} + +function render(kind: ChimeKind) { + const ac = audio(); + if (!ac || ac.state !== "running") return; + const now = ac.currentTime; + if (kind === "chat") { + tone(ac, 1046.5, now, 0.14, 0.05); // C6 + } else { + tone(ac, 659.25, now, 0.22, 0.07); // E5 + tone(ac, 987.77, now + 0.09, 0.32, 0.06); // B5 + } +} + +/** + * Plays a chime for a live event, unless sounds are off, another tab already + * chimed for the same event, or (for chat) one played a moment ago. + */ +export function playChime(kind: ChimeKind, eventId: string): void { + if (!readSoundsEnabled()) return; + if (kind === "chat") { + const now = Date.now(); + if (now - lastChatAt < CHAT_THROTTLE) return; + lastChatAt = now; + } + if (!claim(`${kind}:${eventId}`)) return; + render(kind); +} + +/** Plays a chime unconditionally — used to preview it from the settings. */ +export function previewChime(kind: ChimeKind): void { + const ac = audio(); + if (!ac) return; + if (ac.state === "suspended") { + void ac.resume().then(() => render(kind)); + } else { + render(kind); + } +}