v3: chat mentions stop going missing, and live events get a light sound — the server skips a mention notification for anyone with the chatbox open, on the grounds that they're reading it, but "open" also meant a chatbox left open in a background tab, and the same modal cleared the unread badge on every arrival, so a mention there left no bell, no badge and no trace. Reading the chat now means open and on screen: the focus flag is re-announced on every visibilitychange, and the unread count only clears while the tab is visible. A reconnect with the chatbox open refetches the latest page, since whatever was said while the socket was down never reached the live buffer. The server's mention regex excluded a backslash and the letter w rather than word characters (\\w inside a class), so an email address mentioned its domain; it now matches the autocomplete's rule. On top of that, sounds synthesised with Web Audio — a soft blip for chat you aren't looking at, throttled, and a two-note chime for notifications and for a mention you're reading live, which gets no notification of its own — claimed through localStorage so several tabs chime once, with an on/off toggle under a new Notifications section in settings. The tab title carries the unread count, composed with the page title in useDocumentTitle so navigation doesn't drop it.
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 46s
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 46s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,8 @@ import { db, isNotificationRow, notificationRowToApi } from "../model/db.ts";
|
|||||||
import { sendToUser } from "./ws-service.ts";
|
import { sendToUser } from "./ws-service.ts";
|
||||||
|
|
||||||
// Regex: matches @username not already inside a markdown link ([...] or (...)
|
// Regex: matches @username not already inside a markdown link ([...] or (...)
|
||||||
const MENTION_RE = /(?<![[(\\w])@([\w]+)/g;
|
// and not mid-word, so an email address doesn't mention its domain.
|
||||||
|
const MENTION_RE = /(?<![[(\w])@(\w+)/g;
|
||||||
|
|
||||||
// ── Core CRUD ─────────────────────────────────────────────────────────────────
|
// ── Core CRUD ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|||||||
@@ -284,6 +284,7 @@ export function ChatModal({ onClose }: { onClose: () => void }) {
|
|||||||
clearUnreadChat,
|
clearUnreadChat,
|
||||||
setChatTyping,
|
setChatTyping,
|
||||||
setChatOpen,
|
setChatOpen,
|
||||||
|
connectionEpoch,
|
||||||
} = useWS();
|
} = useWS();
|
||||||
const { authFetch, user } = useAuth();
|
const { authFetch, user } = useAuth();
|
||||||
const canModerate = can(user, "chat:moderate");
|
const canModerate = can(user, "chat:moderate");
|
||||||
@@ -352,10 +353,43 @@ export function ChatModal({ onClose }: { onClose: () => void }) {
|
|||||||
};
|
};
|
||||||
}, [authFetch]);
|
}, [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(() => {
|
useEffect(() => {
|
||||||
clearUnreadChat();
|
if (connectionEpoch === seenEpochRef.current) return;
|
||||||
}, [chatMessages, clearUnreadChat]);
|
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
|
// Tell the server the chatbox is open so it skips redundant mention
|
||||||
// notifications while the user is reading; clear the flag on close.
|
// notifications while the user is reading; clear the flag on close.
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ import {
|
|||||||
} from "../model.ts";
|
} from "../model.ts";
|
||||||
import { t } from "@lingui/core/macro";
|
import { t } from "@lingui/core/macro";
|
||||||
import { useAuth } from "../hooks/useAuth.ts";
|
import { useAuth } from "../hooks/useAuth.ts";
|
||||||
|
import { setTitleBadge } from "../hooks/useDocumentTitle.ts";
|
||||||
|
import { playChime } from "../utils/sound.ts";
|
||||||
|
|
||||||
interface WSProviderProps {
|
interface WSProviderProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
@@ -55,6 +57,17 @@ const MAX_RECENT_DUMPS = 100;
|
|||||||
// Older messages remain reachable via the /api/chat history endpoint.
|
// Older messages remain reachable via the /api/chat history endpoint.
|
||||||
const MAX_CHAT_MESSAGES = 200;
|
const MAX_CHAT_MESSAGES = 200;
|
||||||
|
|
||||||
|
// Mirrors MENTION_RE in api/services/notification-service.ts.
|
||||||
|
const MENTION_RE = /(?<![[(\w])@(\w+)/g;
|
||||||
|
|
||||||
|
function mentionsUser(body: string, username: string): boolean {
|
||||||
|
const target = username.toLowerCase();
|
||||||
|
for (const m of body.matchAll(MENTION_RE)) {
|
||||||
|
if (m[1].toLowerCase() === target) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
interface PendingVote {
|
interface PendingVote {
|
||||||
timeout: ReturnType<typeof setTimeout>;
|
timeout: ReturnType<typeof setTimeout>;
|
||||||
rollback: () => void;
|
rollback: () => void;
|
||||||
@@ -167,6 +180,7 @@ export function WSProvider({ children }: WSProviderProps) {
|
|||||||
const likeCountsRef = useRef(likeCounts);
|
const likeCountsRef = useRef(likeCounts);
|
||||||
const myLikesRef = useRef(myLikes);
|
const myLikesRef = useRef(myLikes);
|
||||||
const userIdRef = useRef(userId);
|
const userIdRef = useRef(userId);
|
||||||
|
const usernameRef = useRef(user?.username ?? null);
|
||||||
// Stable ref for logout so the effect doesn't reconnect when the function
|
// Stable ref for logout so the effect doesn't reconnect when the function
|
||||||
// reference changes on re-renders.
|
// reference changes on re-renders.
|
||||||
const onForceLogoutRef = useRef(logout);
|
const onForceLogoutRef = useRef(logout);
|
||||||
@@ -176,6 +190,7 @@ export function WSProvider({ children }: WSProviderProps) {
|
|||||||
likeCountsRef.current = likeCounts;
|
likeCountsRef.current = likeCounts;
|
||||||
myLikesRef.current = myLikes;
|
myLikesRef.current = myLikes;
|
||||||
userIdRef.current = userId;
|
userIdRef.current = userId;
|
||||||
|
usernameRef.current = user?.username ?? null;
|
||||||
onForceLogoutRef.current = logout;
|
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
|
// 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).
|
// after a reconnect (the fresh socket starts with the flag cleared).
|
||||||
const chatOpenRef = useRef(false);
|
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
|
// Tracks pending optimistic votes: dumpId → pending rollback handler
|
||||||
const pendingRef = useRef<Map<string, PendingVote>>(
|
const pendingRef = useRef<Map<string, PendingVote>>(
|
||||||
new Map(),
|
new Map(),
|
||||||
@@ -309,7 +331,7 @@ export function WSProvider({ children }: WSProviderProps) {
|
|||||||
if (welcomeCount > 1) setConnectionEpoch((n) => n + 1);
|
if (welcomeCount > 1) setConnectionEpoch((n) => n + 1);
|
||||||
// Restore the chat-open flag on the fresh connection so mention
|
// Restore the chat-open flag on the fresh connection so mention
|
||||||
// suppression keeps working across reconnects.
|
// suppression keeps working across reconnects.
|
||||||
if (chatOpenRef.current) {
|
if (isViewingChat()) {
|
||||||
ws.send(
|
ws.send(
|
||||||
JSON.stringify(
|
JSON.stringify(
|
||||||
{ type: "chat_focus", open: true } satisfies OutgoingWSMessage,
|
{ type: "chat_focus", open: true } satisfies OutgoingWSMessage,
|
||||||
@@ -496,6 +518,7 @@ export function WSProvider({ children }: WSProviderProps) {
|
|||||||
const notification = deserializeNotification(msg.notification);
|
const notification = deserializeNotification(msg.notification);
|
||||||
setLastNotification(notification);
|
setLastNotification(notification);
|
||||||
setUnreadNotificationCount((prev) => prev + 1);
|
setUnreadNotificationCount((prev) => prev + 1);
|
||||||
|
playChime("notify", notification.id);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -508,6 +531,15 @@ export function WSProvider({ children }: WSProviderProps) {
|
|||||||
// Count as unread unless it's the current user's own message.
|
// Count as unread unless it's the current user's own message.
|
||||||
if (message.userId !== userIdRef.current) {
|
if (message.userId !== userIdRef.current) {
|
||||||
setUnreadChatCount((prev) => prev + 1);
|
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;
|
break;
|
||||||
}
|
}
|
||||||
@@ -610,6 +642,7 @@ export function WSProvider({ children }: WSProviderProps) {
|
|||||||
clearPendingVote,
|
clearPendingVote,
|
||||||
clearAllPendingLikes,
|
clearAllPendingLikes,
|
||||||
clearPendingLike,
|
clearPendingLike,
|
||||||
|
isViewingChat,
|
||||||
token,
|
token,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -817,16 +850,39 @@ export function WSProvider({ children }: WSProviderProps) {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const setChatOpen = useCallback((open: boolean) => {
|
// Tell the server whether the user is reading the chat right now (chatbox
|
||||||
chatOpenRef.current = open;
|
// open and tab visible), so it knows when a mention notification is moot.
|
||||||
|
const sendChatFocus = useCallback(() => {
|
||||||
if (socketRef.current?.readyState === WebSocket.OPEN) {
|
if (socketRef.current?.readyState === WebSocket.OPEN) {
|
||||||
socketRef.current.send(
|
socketRef.current.send(
|
||||||
JSON.stringify(
|
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(() => ({
|
const value: WSContextValue = useMemo(() => ({
|
||||||
wsStatus,
|
wsStatus,
|
||||||
|
|||||||
@@ -23,6 +23,19 @@ function readSiteEmoji(): string {
|
|||||||
|
|
||||||
export const SITE_EMOJI = readSiteEmoji();
|
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
|
* Keeps document.title in sync with the current page during client-side
|
||||||
* navigation. Without this the tab title only ever reflects the server-rendered
|
* 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 {
|
export function useDocumentTitle(title: string | null | undefined): void {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (title === undefined) return;
|
if (title === undefined) return;
|
||||||
document.title = title ? `${title} · ${SITE_NAME}` : SITE_NAME;
|
baseTitle = title ? `${title} · ${SITE_NAME}` : SITE_NAME;
|
||||||
|
applyTitle();
|
||||||
}, [title]);
|
}, [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();
|
||||||
|
}
|
||||||
|
|||||||
18
src/hooks/useSoundsEnabled.ts
Normal file
18
src/hooks/useSoundsEnabled.ts
Normal file
@@ -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];
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -35,12 +35,12 @@ msgstr "{0, plural, one {and # earlier dump} other {and # earlier dumps}}"
|
|||||||
|
|
||||||
#. placeholder {0}: typingNames[0]
|
#. placeholder {0}: typingNames[0]
|
||||||
#. placeholder {1}: typingNames[1]
|
#. placeholder {1}: typingNames[1]
|
||||||
#: src/components/ChatModal.tsx:534
|
#: src/components/ChatModal.tsx:568
|
||||||
msgid "{0} and {1} are typing…"
|
msgid "{0} and {1} are typing…"
|
||||||
msgstr "{0} and {1} are typing…"
|
msgstr "{0} and {1} are typing…"
|
||||||
|
|
||||||
#. placeholder {0}: typingNames[0]
|
#. placeholder {0}: typingNames[0]
|
||||||
#: src/components/ChatModal.tsx:532
|
#: src/components/ChatModal.tsx:566
|
||||||
msgid "{0} is typing…"
|
msgid "{0} is typing…"
|
||||||
msgstr "{0} is typing…"
|
msgstr "{0} is typing…"
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ msgstr "{visibleCount, plural, one {# comment} other {# comments}}"
|
|||||||
|
|
||||||
#: src/components/DumpCreateModal.tsx:936
|
#: src/components/DumpCreateModal.tsx:936
|
||||||
#: src/pages/PlaylistDetail.tsx:572
|
#: src/pages/PlaylistDetail.tsx:572
|
||||||
#: src/pages/UserPublicProfile.tsx:798
|
#: src/pages/UserPublicProfile.tsx:801
|
||||||
msgid "← Back"
|
msgid "← Back"
|
||||||
msgstr "← Back"
|
msgstr "← Back"
|
||||||
|
|
||||||
@@ -76,7 +76,7 @@ msgstr "← Back to all dumps"
|
|||||||
msgid "← Back to profile"
|
msgid "← Back to profile"
|
||||||
msgstr "← Back to profile"
|
msgstr "← Back to profile"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:134
|
#: src/pages/UserPublicProfile.tsx:136
|
||||||
msgid "+ Invite someone"
|
msgid "+ Invite someone"
|
||||||
msgstr "+ Invite someone"
|
msgstr "+ Invite someone"
|
||||||
|
|
||||||
@@ -142,7 +142,7 @@ msgstr "a comment"
|
|||||||
msgid "a post"
|
msgid "a post"
|
||||||
msgstr "a post"
|
msgstr "a post"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:1179
|
#: src/pages/UserPublicProfile.tsx:1182
|
||||||
msgid "Account"
|
msgid "Account"
|
||||||
msgstr "Account"
|
msgstr "Account"
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ msgstr "Add a comment…"
|
|||||||
msgid "Add category"
|
msgid "Add category"
|
||||||
msgstr "Add category"
|
msgstr "Add category"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:876
|
#: src/pages/UserPublicProfile.tsx:879
|
||||||
msgid "Add email…"
|
msgid "Add email…"
|
||||||
msgstr "Add email…"
|
msgstr "Add email…"
|
||||||
|
|
||||||
@@ -164,7 +164,7 @@ msgstr "Add email…"
|
|||||||
msgid "Add to playlist"
|
msgid "Add to playlist"
|
||||||
msgstr "Add to playlist"
|
msgstr "Add to playlist"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:1604
|
#: src/pages/UserPublicProfile.tsx:1646
|
||||||
msgid "Admin"
|
msgid "Admin"
|
||||||
msgstr "Admin"
|
msgstr "Admin"
|
||||||
|
|
||||||
@@ -183,7 +183,7 @@ msgstr "Already dumped by {0} {1}"
|
|||||||
msgid "Already have an account? <0>Log in</0>"
|
msgid "Already have an account? <0>Log in</0>"
|
||||||
msgstr "Already have an account? <0>Log in</0>"
|
msgstr "Already have an account? <0>Log in</0>"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:1198
|
#: src/pages/UserPublicProfile.tsx:1201
|
||||||
msgid "Appearance"
|
msgid "Appearance"
|
||||||
msgstr "Appearance"
|
msgstr "Appearance"
|
||||||
|
|
||||||
@@ -194,7 +194,7 @@ msgstr "Appearance"
|
|||||||
msgid "At least {0} characters"
|
msgid "At least {0} characters"
|
||||||
msgstr "At least {0} characters"
|
msgstr "At least {0} characters"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:1253
|
#: src/pages/UserPublicProfile.tsx:1263
|
||||||
msgid "Auto"
|
msgid "Auto"
|
||||||
msgstr "Auto"
|
msgstr "Auto"
|
||||||
|
|
||||||
@@ -203,8 +203,8 @@ msgstr "Auto"
|
|||||||
msgid "Back to login"
|
msgid "Back to login"
|
||||||
msgstr "Back to login"
|
msgstr "Back to login"
|
||||||
|
|
||||||
#: src/contexts/WSProvider.tsx:270
|
#: src/contexts/WSProvider.tsx:292
|
||||||
#: src/contexts/WSProvider.tsx:576
|
#: src/contexts/WSProvider.tsx:608
|
||||||
msgid "Can't connect to the live updates server. Upvotes and notifications may not sync until it reconnects."
|
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."
|
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/Dump.tsx:406
|
||||||
#: src/pages/DumpEdit.tsx:484
|
#: src/pages/DumpEdit.tsx:484
|
||||||
#: src/pages/PlaylistDetail.tsx:923
|
#: src/pages/PlaylistDetail.tsx:923
|
||||||
#: src/pages/UserPublicProfile.tsx:1733
|
#: src/pages/UserPublicProfile.tsx:1775
|
||||||
#: src/pages/UserPublicProfile.tsx:1806
|
#: src/pages/UserPublicProfile.tsx:1848
|
||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr "Cancel"
|
msgstr "Cancel"
|
||||||
|
|
||||||
@@ -227,8 +227,8 @@ msgstr "Cancel"
|
|||||||
msgid "Cancel removal"
|
msgid "Cancel removal"
|
||||||
msgstr "Cancel removal"
|
msgstr "Cancel removal"
|
||||||
|
|
||||||
#: src/components/ChatModal.tsx:624
|
#: src/components/ChatModal.tsx:658
|
||||||
#: src/components/ChatModal.tsx:625
|
#: src/components/ChatModal.tsx:659
|
||||||
msgid "Cancel reply"
|
msgid "Cancel reply"
|
||||||
msgstr "Cancel reply"
|
msgstr "Cancel reply"
|
||||||
|
|
||||||
@@ -237,7 +237,7 @@ msgid "Cancel search"
|
|||||||
msgstr "Cancel search"
|
msgstr "Cancel search"
|
||||||
|
|
||||||
#: src/components/CategorySelect.tsx:22
|
#: src/components/CategorySelect.tsx:22
|
||||||
#: src/pages/UserPublicProfile.tsx:1336
|
#: src/pages/UserPublicProfile.tsx:1378
|
||||||
msgid "Categories"
|
msgid "Categories"
|
||||||
msgstr "Categories"
|
msgstr "Categories"
|
||||||
|
|
||||||
@@ -245,7 +245,7 @@ msgstr "Categories"
|
|||||||
msgid "Category"
|
msgid "Category"
|
||||||
msgstr "Category"
|
msgstr "Category"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:825
|
#: src/pages/UserPublicProfile.tsx:828
|
||||||
msgid "Change avatar"
|
msgid "Change avatar"
|
||||||
msgstr "Change avatar"
|
msgstr "Change avatar"
|
||||||
|
|
||||||
@@ -254,14 +254,14 @@ msgstr "Change avatar"
|
|||||||
msgid "Change password"
|
msgid "Change password"
|
||||||
msgstr "Change password"
|
msgstr "Change password"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:1191
|
#: src/pages/UserPublicProfile.tsx:1194
|
||||||
msgid "Change password…"
|
msgid "Change password…"
|
||||||
msgstr "Change password…"
|
msgstr "Change password…"
|
||||||
|
|
||||||
#: src/components/ChatButton.tsx:17
|
#: src/components/ChatButton.tsx:17
|
||||||
#: src/components/ChatFab.tsx:49
|
#: src/components/ChatFab.tsx:49
|
||||||
#: src/components/ChatFab.tsx:50
|
#: src/components/ChatFab.tsx:50
|
||||||
#: src/components/ChatModal.tsx:538
|
#: src/components/ChatModal.tsx:572
|
||||||
msgid "Chat"
|
msgid "Chat"
|
||||||
msgstr "Chat"
|
msgstr "Chat"
|
||||||
|
|
||||||
@@ -295,7 +295,7 @@ msgstr "Close without posting? The attached file won't be kept — everything yo
|
|||||||
msgid "Collapse player"
|
msgid "Collapse player"
|
||||||
msgstr "Collapse player"
|
msgstr "Collapse player"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:1245
|
#: src/pages/UserPublicProfile.tsx:1255
|
||||||
msgid "Color scheme"
|
msgid "Color scheme"
|
||||||
msgstr "Color scheme"
|
msgstr "Color scheme"
|
||||||
|
|
||||||
@@ -304,11 +304,11 @@ msgstr "Color scheme"
|
|||||||
msgid "Confirm new password"
|
msgid "Confirm new password"
|
||||||
msgstr "Confirm new password"
|
msgstr "Confirm new password"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:159
|
#: src/pages/UserPublicProfile.tsx:161
|
||||||
msgid "Copied!"
|
msgid "Copied!"
|
||||||
msgstr "Copied!"
|
msgstr "Copied!"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:159
|
#: src/pages/UserPublicProfile.tsx:161
|
||||||
msgid "Copy"
|
msgid "Copy"
|
||||||
msgstr "Copy"
|
msgstr "Copy"
|
||||||
|
|
||||||
@@ -350,11 +350,11 @@ msgstr "Creating…"
|
|||||||
msgid "Current password"
|
msgid "Current password"
|
||||||
msgstr "Current password"
|
msgstr "Current password"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:1267
|
#: src/pages/UserPublicProfile.tsx:1277
|
||||||
msgid "Dark"
|
msgid "Dark"
|
||||||
msgstr "Dark"
|
msgstr "Dark"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:1280
|
#: src/pages/UserPublicProfile.tsx:1290
|
||||||
msgid "Default tab"
|
msgid "Default tab"
|
||||||
msgstr "Default tab"
|
msgstr "Default tab"
|
||||||
|
|
||||||
@@ -446,13 +446,13 @@ msgstr "Dumped!"
|
|||||||
|
|
||||||
#: src/pages/Search.tsx:174
|
#: src/pages/Search.tsx:174
|
||||||
#: src/pages/UserDumps.tsx:80
|
#: src/pages/UserDumps.tsx:80
|
||||||
#: src/pages/UserPublicProfile.tsx:948
|
#: src/pages/UserPublicProfile.tsx:951
|
||||||
msgid "Dumps"
|
msgid "Dumps"
|
||||||
msgstr "Dumps"
|
msgstr "Dumps"
|
||||||
|
|
||||||
#. placeholder {0}: dumps.items.length
|
#. placeholder {0}: dumps.items.length
|
||||||
#. placeholder {1}: dumps.hasMore ? "+" : ""
|
#. placeholder {1}: dumps.hasMore ? "+" : ""
|
||||||
#: src/pages/UserPublicProfile.tsx:968
|
#: src/pages/UserPublicProfile.tsx:971
|
||||||
msgid "Dumps ({0}{1})"
|
msgid "Dumps ({0}{1})"
|
||||||
msgstr "Dumps ({0}{1})"
|
msgstr "Dumps ({0}{1})"
|
||||||
|
|
||||||
@@ -530,9 +530,9 @@ msgstr "Failed to create playlist"
|
|||||||
msgid "Failed to delete"
|
msgid "Failed to delete"
|
||||||
msgstr "Failed to delete"
|
msgstr "Failed to delete"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:104
|
#: src/pages/UserPublicProfile.tsx:106
|
||||||
#: src/pages/UserPublicProfile.tsx:107
|
#: src/pages/UserPublicProfile.tsx:109
|
||||||
#: src/pages/UserPublicProfile.tsx:168
|
#: src/pages/UserPublicProfile.tsx:170
|
||||||
msgid "Failed to generate invite"
|
msgid "Failed to generate invite"
|
||||||
msgstr "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/JournalFeed.tsx:33
|
||||||
#: src/pages/index/NewFeed.tsx:36
|
#: src/pages/index/NewFeed.tsx:36
|
||||||
#: src/pages/Notifications.tsx:374
|
#: src/pages/Notifications.tsx:374
|
||||||
#: src/pages/UserPublicProfile.tsx:1070
|
#: src/pages/UserPublicProfile.tsx:1073
|
||||||
#: src/pages/UserPublicProfile.tsx:1112
|
#: src/pages/UserPublicProfile.tsx:1115
|
||||||
#: src/pages/UserPublicProfile.tsx:1157
|
#: src/pages/UserPublicProfile.tsx:1160
|
||||||
msgid "Failed to load"
|
msgid "Failed to load"
|
||||||
msgstr "Failed to load"
|
msgstr "Failed to load"
|
||||||
|
|
||||||
@@ -560,8 +560,8 @@ msgid "Failed to post reply"
|
|||||||
msgstr "Failed to post reply"
|
msgstr "Failed to post reply"
|
||||||
|
|
||||||
#: src/pages/PlaylistDetail.tsx:911
|
#: src/pages/PlaylistDetail.tsx:911
|
||||||
#: src/pages/UserPublicProfile.tsx:1736
|
#: src/pages/UserPublicProfile.tsx:1778
|
||||||
#: src/pages/UserPublicProfile.tsx:1808
|
#: src/pages/UserPublicProfile.tsx:1850
|
||||||
msgid "Failed to save"
|
msgid "Failed to save"
|
||||||
msgstr "Failed to save"
|
msgstr "Failed to save"
|
||||||
|
|
||||||
@@ -573,15 +573,15 @@ msgstr "Failed to save category"
|
|||||||
msgid "Failed to save edit"
|
msgid "Failed to save edit"
|
||||||
msgstr "Failed to save edit"
|
msgstr "Failed to save edit"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:885
|
#: src/pages/UserPublicProfile.tsx:888
|
||||||
msgid "Failed to update avatar"
|
msgid "Failed to update avatar"
|
||||||
msgstr "Failed to update avatar"
|
msgstr "Failed to update avatar"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:1674
|
#: src/pages/UserPublicProfile.tsx:1716
|
||||||
msgid "Failed to update role"
|
msgid "Failed to update role"
|
||||||
msgstr "Failed to update role"
|
msgstr "Failed to update role"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:1275
|
#: src/pages/UserPublicProfile.tsx:1285
|
||||||
msgid "Feeds"
|
msgid "Feeds"
|
||||||
msgstr "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."
|
msgstr "Follow some users to see their dumps here."
|
||||||
|
|
||||||
#: src/components/FeedTabBar.tsx:34
|
#: src/components/FeedTabBar.tsx:34
|
||||||
#: src/pages/UserPublicProfile.tsx:950
|
#: src/pages/UserPublicProfile.tsx:953
|
||||||
#: src/pages/UserPublicProfile.tsx:1309
|
#: src/pages/UserPublicProfile.tsx:1319
|
||||||
msgid "Followed"
|
msgid "Followed"
|
||||||
msgstr "Followed"
|
msgstr "Followed"
|
||||||
|
|
||||||
@@ -626,13 +626,13 @@ msgstr "Followed"
|
|||||||
msgid "Followed ({0}{1})"
|
msgid "Followed ({0}{1})"
|
||||||
msgstr "Followed ({0}{1})"
|
msgstr "Followed ({0}{1})"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:1101
|
#: src/pages/UserPublicProfile.tsx:1104
|
||||||
msgid "Followed playlists"
|
msgid "Followed playlists"
|
||||||
msgstr "Followed playlists"
|
msgstr "Followed playlists"
|
||||||
|
|
||||||
#: src/components/FollowButton.tsx:37
|
#: src/components/FollowButton.tsx:37
|
||||||
#: src/components/FollowButton.tsx:64
|
#: src/components/FollowButton.tsx:64
|
||||||
#: src/pages/UserPublicProfile.tsx:1059
|
#: src/pages/UserPublicProfile.tsx:1062
|
||||||
msgid "Following"
|
msgid "Following"
|
||||||
msgstr "Following"
|
msgstr "Following"
|
||||||
|
|
||||||
@@ -657,7 +657,7 @@ msgid "Go to login"
|
|||||||
msgstr "Go to login"
|
msgstr "Go to login"
|
||||||
|
|
||||||
#: src/components/FeedTabBar.tsx:30
|
#: src/components/FeedTabBar.tsx:30
|
||||||
#: src/pages/UserPublicProfile.tsx:1288
|
#: src/pages/UserPublicProfile.tsx:1298
|
||||||
msgid "Hot"
|
msgid "Hot"
|
||||||
msgstr "Hot"
|
msgstr "Hot"
|
||||||
|
|
||||||
@@ -677,22 +677,22 @@ msgstr "Invalid invite"
|
|||||||
msgid "Invalid link"
|
msgid "Invalid link"
|
||||||
msgstr "Invalid link"
|
msgstr "Invalid link"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:142
|
#: src/pages/UserPublicProfile.tsx:144
|
||||||
#: src/pages/UserPublicProfile.tsx:149
|
#: src/pages/UserPublicProfile.tsx:151
|
||||||
msgid "Invite link"
|
msgid "Invite link"
|
||||||
msgstr "Invite link"
|
msgstr "Invite link"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:847
|
#: src/pages/UserPublicProfile.tsx:850
|
||||||
msgid "invited by"
|
msgid "invited by"
|
||||||
msgstr "invited by"
|
msgstr "invited by"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:951
|
#: src/pages/UserPublicProfile.tsx:954
|
||||||
#: src/pages/UserPublicProfile.tsx:1146
|
#: src/pages/UserPublicProfile.tsx:1149
|
||||||
msgid "Invitees"
|
msgid "Invitees"
|
||||||
msgstr "Invitees"
|
msgstr "Invitees"
|
||||||
|
|
||||||
#: src/components/FeedTabBar.tsx:32
|
#: src/components/FeedTabBar.tsx:32
|
||||||
#: src/pages/UserPublicProfile.tsx:1302
|
#: src/pages/UserPublicProfile.tsx:1312
|
||||||
msgid "Journal"
|
msgid "Journal"
|
||||||
msgstr "Journal"
|
msgstr "Journal"
|
||||||
|
|
||||||
@@ -700,7 +700,7 @@ msgstr "Journal"
|
|||||||
msgid "Keep editing"
|
msgid "Keep editing"
|
||||||
msgstr "Keep editing"
|
msgstr "Keep editing"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:1260
|
#: src/pages/UserPublicProfile.tsx:1270
|
||||||
msgid "Light"
|
msgid "Light"
|
||||||
msgstr "Light"
|
msgstr "Light"
|
||||||
|
|
||||||
@@ -716,7 +716,7 @@ msgstr "Link"
|
|||||||
msgid "Link or file"
|
msgid "Link or file"
|
||||||
msgstr "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…"
|
msgid "Live updates are temporarily disconnected. Trying to reconnect…"
|
||||||
msgstr "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"
|
msgid "Load more"
|
||||||
msgstr "Load more"
|
msgstr "Load more"
|
||||||
|
|
||||||
#: src/components/ChatModal.tsx:551
|
#: src/components/ChatModal.tsx:585
|
||||||
msgid "Load older messages"
|
msgid "Load older messages"
|
||||||
msgstr "Load older messages"
|
msgstr "Load older messages"
|
||||||
|
|
||||||
@@ -740,7 +740,7 @@ msgstr "Loading dump…"
|
|||||||
|
|
||||||
#: src/pages/index/FollowedFeed.tsx:111
|
#: src/pages/index/FollowedFeed.tsx:111
|
||||||
#: src/pages/index/HotFeed.tsx:64
|
#: 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/index/NewFeed.tsx:64
|
||||||
#: src/pages/Search.tsx:244
|
#: src/pages/Search.tsx:244
|
||||||
#: src/pages/UserDumps.tsx:121
|
#: src/pages/UserDumps.tsx:121
|
||||||
@@ -754,12 +754,12 @@ msgstr "Loading more…"
|
|||||||
msgid "Loading playlist…"
|
msgid "Loading playlist…"
|
||||||
msgstr "Loading playlist…"
|
msgstr "Loading playlist…"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:781
|
#: src/pages/UserPublicProfile.tsx:784
|
||||||
msgid "Loading profile…"
|
msgid "Loading profile…"
|
||||||
msgstr "Loading profile…"
|
msgstr "Loading profile…"
|
||||||
|
|
||||||
#: src/components/CategoryManager.tsx:52
|
#: src/components/CategoryManager.tsx:52
|
||||||
#: src/components/ChatModal.tsx:550
|
#: src/components/ChatModal.tsx:584
|
||||||
#: src/components/PlaylistMembershipPanel.tsx:28
|
#: src/components/PlaylistMembershipPanel.tsx:28
|
||||||
#: src/components/TextEditor.tsx:311
|
#: src/components/TextEditor.tsx:311
|
||||||
#: src/components/UserListPopover.tsx:192
|
#: src/components/UserListPopover.tsx:192
|
||||||
@@ -772,9 +772,9 @@ msgstr "Loading profile…"
|
|||||||
#: src/pages/Notifications.tsx:446
|
#: src/pages/Notifications.tsx:446
|
||||||
#: src/pages/UserDumps.tsx:54
|
#: src/pages/UserDumps.tsx:54
|
||||||
#: src/pages/UserPlaylists.tsx:345
|
#: src/pages/UserPlaylists.tsx:345
|
||||||
#: src/pages/UserPublicProfile.tsx:1064
|
#: src/pages/UserPublicProfile.tsx:1067
|
||||||
#: src/pages/UserPublicProfile.tsx:1106
|
#: src/pages/UserPublicProfile.tsx:1109
|
||||||
#: src/pages/UserPublicProfile.tsx:1151
|
#: src/pages/UserPublicProfile.tsx:1154
|
||||||
#: src/pages/UserUpvoted.tsx:125
|
#: src/pages/UserUpvoted.tsx:125
|
||||||
msgid "Loading…"
|
msgid "Loading…"
|
||||||
msgstr "Loading…"
|
msgstr "Loading…"
|
||||||
@@ -794,8 +794,8 @@ msgstr "Log in to like"
|
|||||||
msgid "Log in to vote"
|
msgid "Log in to vote"
|
||||||
msgstr "Log in to vote"
|
msgstr "Log in to vote"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:802
|
#: src/pages/UserPublicProfile.tsx:805
|
||||||
#: src/pages/UserPublicProfile.tsx:898
|
#: src/pages/UserPublicProfile.tsx:901
|
||||||
msgid "Log out"
|
msgid "Log out"
|
||||||
msgstr "Log out"
|
msgstr "Log out"
|
||||||
|
|
||||||
@@ -807,7 +807,7 @@ msgstr "Logging in…"
|
|||||||
msgid "Login failed"
|
msgid "Login failed"
|
||||||
msgstr "Login failed"
|
msgstr "Login failed"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:956
|
#: src/pages/UserPublicProfile.tsx:959
|
||||||
msgid "Manage"
|
msgid "Manage"
|
||||||
msgstr "Manage"
|
msgstr "Manage"
|
||||||
|
|
||||||
@@ -815,7 +815,7 @@ msgstr "Manage"
|
|||||||
msgid "Max 50 MB"
|
msgid "Max 50 MB"
|
||||||
msgstr "Max 50 MB"
|
msgstr "Max 50 MB"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:1603
|
#: src/pages/UserPublicProfile.tsx:1645
|
||||||
msgid "Moderator"
|
msgid "Moderator"
|
||||||
msgstr "Moderator"
|
msgstr "Moderator"
|
||||||
|
|
||||||
@@ -829,7 +829,7 @@ msgid "new"
|
|||||||
msgstr "new"
|
msgstr "new"
|
||||||
|
|
||||||
#: src/components/FeedTabBar.tsx:31
|
#: src/components/FeedTabBar.tsx:31
|
||||||
#: src/pages/UserPublicProfile.tsx:1295
|
#: src/pages/UserPublicProfile.tsx:1305
|
||||||
msgid "New"
|
msgid "New"
|
||||||
msgstr "New"
|
msgstr "New"
|
||||||
|
|
||||||
@@ -837,7 +837,7 @@ msgstr "New"
|
|||||||
#: src/components/DumpFab.tsx:65
|
#: src/components/DumpFab.tsx:65
|
||||||
#: src/components/DumpFab.tsx:66
|
#: src/components/DumpFab.tsx:66
|
||||||
#: src/pages/UserDumps.tsx:88
|
#: src/pages/UserDumps.tsx:88
|
||||||
#: src/pages/UserPublicProfile.tsx:1382
|
#: src/pages/UserPublicProfile.tsx:1424
|
||||||
msgid "New dump"
|
msgid "New dump"
|
||||||
msgstr "New dump"
|
msgstr "New dump"
|
||||||
|
|
||||||
@@ -878,15 +878,15 @@ msgid "No emoji found."
|
|||||||
msgstr "No emoji found."
|
msgstr "No emoji found."
|
||||||
|
|
||||||
#: src/pages/UserPlaylists.tsx:442
|
#: src/pages/UserPlaylists.tsx:442
|
||||||
#: src/pages/UserPublicProfile.tsx:1119
|
#: src/pages/UserPublicProfile.tsx:1122
|
||||||
msgid "No followed playlists yet."
|
msgid "No followed playlists yet."
|
||||||
msgstr "No followed playlists yet."
|
msgstr "No followed playlists yet."
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:1164
|
#: src/pages/UserPublicProfile.tsx:1167
|
||||||
msgid "No invitees yet."
|
msgid "No invitees yet."
|
||||||
msgstr "No invitees yet."
|
msgstr "No invitees yet."
|
||||||
|
|
||||||
#: src/components/ChatModal.tsx:558
|
#: src/components/ChatModal.tsx:592
|
||||||
msgid "No messages yet. Say hello!"
|
msgid "No messages yet. Say hello!"
|
||||||
msgstr "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/components/PlaylistMembershipPanel.tsx:34
|
||||||
#: src/pages/UserPlaylists.tsx:400
|
#: src/pages/UserPlaylists.tsx:400
|
||||||
#: src/pages/UserPublicProfile.tsx:1030
|
#: src/pages/UserPublicProfile.tsx:1033
|
||||||
msgid "No playlists yet."
|
msgid "No playlists yet."
|
||||||
msgstr "No playlists yet."
|
msgstr "No playlists yet."
|
||||||
|
|
||||||
@@ -908,14 +908,14 @@ msgstr "No playlists yet."
|
|||||||
msgid "No users match \"{q}\"."
|
msgid "No users match \"{q}\"."
|
||||||
msgstr "No users match \"{q}\"."
|
msgstr "No users match \"{q}\"."
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:1077
|
#: src/pages/UserPublicProfile.tsx:1080
|
||||||
msgid "Not following anyone yet."
|
msgid "Not following anyone yet."
|
||||||
msgstr "Not following anyone yet."
|
msgstr "Not following anyone yet."
|
||||||
|
|
||||||
#: src/pages/Notifications.tsx:381
|
#: src/pages/Notifications.tsx:381
|
||||||
#: src/pages/UserDumps.tsx:99
|
#: src/pages/UserDumps.tsx:99
|
||||||
#: src/pages/UserPublicProfile.tsx:1393
|
#: src/pages/UserPublicProfile.tsx:1435
|
||||||
#: src/pages/UserPublicProfile.tsx:1515
|
#: src/pages/UserPublicProfile.tsx:1557
|
||||||
#: src/pages/UserUpvoted.tsx:159
|
#: src/pages/UserUpvoted.tsx:159
|
||||||
msgid "Nothing here yet."
|
msgid "Nothing here yet."
|
||||||
msgstr "Nothing here yet."
|
msgstr "Nothing here yet."
|
||||||
@@ -923,6 +923,7 @@ msgstr "Nothing here yet."
|
|||||||
#: src/components/NotificationBell.tsx:42
|
#: src/components/NotificationBell.tsx:42
|
||||||
#: src/pages/Notifications.tsx:265
|
#: src/pages/Notifications.tsx:265
|
||||||
#: src/pages/Notifications.tsx:359
|
#: src/pages/Notifications.tsx:359
|
||||||
|
#: src/pages/UserPublicProfile.tsx:1327
|
||||||
msgid "Notifications"
|
msgid "Notifications"
|
||||||
msgstr "Notifications"
|
msgstr "Notifications"
|
||||||
|
|
||||||
@@ -930,6 +931,14 @@ msgstr "Notifications"
|
|||||||
msgid "Notifications ({unreadNotificationCount} unread)"
|
msgid "Notifications ({unreadNotificationCount} unread)"
|
||||||
msgstr "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
|
#: src/components/SearchBar.tsx:96
|
||||||
msgid "Open search"
|
msgid "Open search"
|
||||||
msgstr "Open search"
|
msgstr "Open search"
|
||||||
@@ -943,7 +952,7 @@ msgid "Page not found"
|
|||||||
msgstr "Page not found"
|
msgstr "Page not found"
|
||||||
|
|
||||||
#: src/pages/UserLogin.tsx:76
|
#: src/pages/UserLogin.tsx:76
|
||||||
#: src/pages/UserPublicProfile.tsx:1184
|
#: src/pages/UserPublicProfile.tsx:1187
|
||||||
msgid "Password"
|
msgid "Password"
|
||||||
msgstr "Password"
|
msgstr "Password"
|
||||||
|
|
||||||
@@ -982,13 +991,13 @@ msgstr "Playlist title"
|
|||||||
#: src/components/UserMenu.tsx:62
|
#: src/components/UserMenu.tsx:62
|
||||||
#: src/pages/Search.tsx:177
|
#: src/pages/Search.tsx:177
|
||||||
#: src/pages/UserPlaylists.tsx:371
|
#: src/pages/UserPlaylists.tsx:371
|
||||||
#: src/pages/UserPublicProfile.tsx:949
|
#: src/pages/UserPublicProfile.tsx:952
|
||||||
msgid "Playlists"
|
msgid "Playlists"
|
||||||
msgstr "Playlists"
|
msgstr "Playlists"
|
||||||
|
|
||||||
#. placeholder {0}: playlists.items.length
|
#. placeholder {0}: playlists.items.length
|
||||||
#. placeholder {1}: playlists.hasMore ? "+" : ""
|
#. placeholder {1}: playlists.hasMore ? "+" : ""
|
||||||
#: src/pages/UserPublicProfile.tsx:999
|
#: src/pages/UserPublicProfile.tsx:1002
|
||||||
msgid "Playlists ({0}{1})"
|
msgid "Playlists ({0}{1})"
|
||||||
msgstr "Playlists ({0}{1})"
|
msgstr "Playlists ({0}{1})"
|
||||||
|
|
||||||
@@ -1090,7 +1099,7 @@ msgstr "Replace file"
|
|||||||
msgid "Reply"
|
msgid "Reply"
|
||||||
msgstr "Reply"
|
msgstr "Reply"
|
||||||
|
|
||||||
#: src/components/ChatModal.tsx:614
|
#: src/components/ChatModal.tsx:648
|
||||||
msgid "Replying to"
|
msgid "Replying to"
|
||||||
msgstr "Replying to"
|
msgstr "Replying to"
|
||||||
|
|
||||||
@@ -1121,7 +1130,7 @@ msgstr "Reset to the suggested title"
|
|||||||
msgid "Retry"
|
msgid "Retry"
|
||||||
msgstr "Retry"
|
msgstr "Retry"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:1658
|
#: src/pages/UserPublicProfile.tsx:1700
|
||||||
msgid "Role"
|
msgid "Role"
|
||||||
msgstr "Role"
|
msgstr "Role"
|
||||||
|
|
||||||
@@ -1130,8 +1139,8 @@ msgstr "Role"
|
|||||||
#: src/pages/Dump.tsx:398
|
#: src/pages/Dump.tsx:398
|
||||||
#: src/pages/DumpEdit.tsx:487
|
#: src/pages/DumpEdit.tsx:487
|
||||||
#: src/pages/PlaylistDetail.tsx:930
|
#: src/pages/PlaylistDetail.tsx:930
|
||||||
#: src/pages/UserPublicProfile.tsx:1725
|
#: src/pages/UserPublicProfile.tsx:1767
|
||||||
#: src/pages/UserPublicProfile.tsx:1798
|
#: src/pages/UserPublicProfile.tsx:1840
|
||||||
msgid "Save"
|
msgid "Save"
|
||||||
msgstr "Save"
|
msgstr "Save"
|
||||||
|
|
||||||
@@ -1140,8 +1149,8 @@ msgstr "Save"
|
|||||||
#: src/pages/Dump.tsx:397
|
#: src/pages/Dump.tsx:397
|
||||||
#: src/pages/PlaylistDetail.tsx:926
|
#: src/pages/PlaylistDetail.tsx:926
|
||||||
#: src/pages/ResetPassword.tsx:126
|
#: src/pages/ResetPassword.tsx:126
|
||||||
#: src/pages/UserPublicProfile.tsx:1722
|
#: src/pages/UserPublicProfile.tsx:1764
|
||||||
#: src/pages/UserPublicProfile.tsx:1795
|
#: src/pages/UserPublicProfile.tsx:1837
|
||||||
msgid "Saving…"
|
msgid "Saving…"
|
||||||
msgstr "Saving…"
|
msgstr "Saving…"
|
||||||
|
|
||||||
@@ -1163,7 +1172,7 @@ msgstr "Search failed"
|
|||||||
msgid "Searching…"
|
msgid "Searching…"
|
||||||
msgstr "Searching…"
|
msgstr "Searching…"
|
||||||
|
|
||||||
#: src/components/ChatModal.tsx:660
|
#: src/components/ChatModal.tsx:694
|
||||||
msgid "Send"
|
msgid "Send"
|
||||||
msgstr "Send"
|
msgstr "Send"
|
||||||
|
|
||||||
@@ -1184,15 +1193,15 @@ msgstr "Server unreachable"
|
|||||||
msgid "Set new password"
|
msgid "Set new password"
|
||||||
msgstr "Set new password"
|
msgstr "Set new password"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:953
|
#: src/pages/UserPublicProfile.tsx:956
|
||||||
msgid "Settings"
|
msgid "Settings"
|
||||||
msgstr "Settings"
|
msgstr "Settings"
|
||||||
|
|
||||||
#: src/components/ChatModal.tsx:535
|
#: src/components/ChatModal.tsx:569
|
||||||
msgid "Several people are typing…"
|
msgid "Several people are typing…"
|
||||||
msgstr "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."
|
msgid "Single use, valid for 7 days."
|
||||||
msgstr "Single use, valid for 7 days."
|
msgstr "Single use, valid for 7 days."
|
||||||
|
|
||||||
@@ -1205,11 +1214,15 @@ msgstr "slug"
|
|||||||
msgid "Something went wrong"
|
msgid "Something went wrong"
|
||||||
msgstr "Something went wrong"
|
msgstr "Something went wrong"
|
||||||
|
|
||||||
|
#: src/pages/UserPublicProfile.tsx:1332
|
||||||
|
msgid "Sounds"
|
||||||
|
msgstr "Sounds"
|
||||||
|
|
||||||
#: src/components/DumpCreateModal.tsx:707
|
#: src/components/DumpCreateModal.tsx:707
|
||||||
msgid "Start fresh"
|
msgid "Start fresh"
|
||||||
msgstr "Start fresh"
|
msgstr "Start fresh"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:1203
|
#: src/pages/UserPublicProfile.tsx:1206
|
||||||
msgid "Style"
|
msgid "Style"
|
||||||
msgstr "Style"
|
msgstr "Style"
|
||||||
|
|
||||||
@@ -1261,7 +1274,7 @@ msgstr "Today"
|
|||||||
msgid "Track {0}"
|
msgid "Track {0}"
|
||||||
msgstr "Track {0}"
|
msgstr "Track {0}"
|
||||||
|
|
||||||
#: src/components/ChatModal.tsx:637
|
#: src/components/ChatModal.tsx:671
|
||||||
msgid "Type a message…"
|
msgid "Type a message…"
|
||||||
msgstr "Type a message…"
|
msgstr "Type a message…"
|
||||||
|
|
||||||
@@ -1277,7 +1290,7 @@ msgstr "Unfollow {targetUsername}"
|
|||||||
msgid "Unfollow playlist"
|
msgid "Unfollow playlist"
|
||||||
msgstr "Unfollow playlist"
|
msgstr "Unfollow playlist"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:748
|
#: src/pages/UserPublicProfile.tsx:751
|
||||||
msgid "Upload failed"
|
msgid "Upload failed"
|
||||||
msgstr "Upload failed"
|
msgstr "Upload failed"
|
||||||
|
|
||||||
@@ -1300,7 +1313,7 @@ msgstr "Upvoted"
|
|||||||
|
|
||||||
#. placeholder {0}: votes.items.length
|
#. placeholder {0}: votes.items.length
|
||||||
#. placeholder {1}: votes.hasMore ? "+" : ""
|
#. placeholder {1}: votes.hasMore ? "+" : ""
|
||||||
#: src/pages/UserPublicProfile.tsx:979
|
#: src/pages/UserPublicProfile.tsx:982
|
||||||
msgid "Upvoted ({0}{1})"
|
msgid "Upvoted ({0}{1})"
|
||||||
msgstr "Upvoted ({0}{1})"
|
msgstr "Upvoted ({0}{1})"
|
||||||
|
|
||||||
@@ -1316,11 +1329,11 @@ msgstr "URL is required."
|
|||||||
msgid "URL slug"
|
msgid "URL slug"
|
||||||
msgstr "URL slug"
|
msgstr "URL slug"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:1602
|
#: src/pages/UserPublicProfile.tsx:1644
|
||||||
msgid "User"
|
msgid "User"
|
||||||
msgstr "User"
|
msgstr "User"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:1321
|
#: src/pages/UserPublicProfile.tsx:1363
|
||||||
msgid "User management"
|
msgid "User management"
|
||||||
msgstr "User management"
|
msgstr "User management"
|
||||||
|
|
||||||
@@ -1337,11 +1350,11 @@ msgstr "Username"
|
|||||||
msgid "Users"
|
msgid "Users"
|
||||||
msgstr "Users"
|
msgstr "Users"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:1049
|
#: src/pages/UserPublicProfile.tsx:1052
|
||||||
#: src/pages/UserPublicProfile.tsx:1092
|
#: src/pages/UserPublicProfile.tsx:1095
|
||||||
#: src/pages/UserPublicProfile.tsx:1134
|
#: src/pages/UserPublicProfile.tsx:1137
|
||||||
#: src/pages/UserPublicProfile.tsx:1414
|
#: src/pages/UserPublicProfile.tsx:1456
|
||||||
#: src/pages/UserPublicProfile.tsx:1545
|
#: src/pages/UserPublicProfile.tsx:1587
|
||||||
msgid "View all →"
|
msgid "View all →"
|
||||||
msgstr "View all →"
|
msgstr "View all →"
|
||||||
|
|
||||||
@@ -1354,8 +1367,8 @@ msgstr "View dump →"
|
|||||||
msgid "What makes it worth it?"
|
msgid "What makes it worth it?"
|
||||||
msgstr "What makes it worth it?"
|
msgstr "What makes it worth it?"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:933
|
#: src/pages/UserPublicProfile.tsx:936
|
||||||
#: src/pages/UserPublicProfile.tsx:1787
|
#: src/pages/UserPublicProfile.tsx:1829
|
||||||
msgid "Who am I?"
|
msgid "Who am I?"
|
||||||
msgstr "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/FollowedFeed.tsx:116
|
||||||
#: src/pages/index/HotFeed.tsx:69
|
#: 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/index/NewFeed.tsx:69
|
||||||
#: src/pages/Search.tsx:249
|
#: src/pages/Search.tsx:249
|
||||||
#: src/pages/UserDumps.tsx:126
|
#: src/pages/UserDumps.tsx:126
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -35,12 +35,12 @@ msgstr "{0, plural, one {et # reco plus ancienne} other {et # recos plus ancienn
|
|||||||
|
|
||||||
#. placeholder {0}: typingNames[0]
|
#. placeholder {0}: typingNames[0]
|
||||||
#. placeholder {1}: typingNames[1]
|
#. placeholder {1}: typingNames[1]
|
||||||
#: src/components/ChatModal.tsx:534
|
#: src/components/ChatModal.tsx:568
|
||||||
msgid "{0} and {1} are typing…"
|
msgid "{0} and {1} are typing…"
|
||||||
msgstr "{0} et {1} sont en train d'écrire…"
|
msgstr "{0} et {1} sont en train d'écrire…"
|
||||||
|
|
||||||
#. placeholder {0}: typingNames[0]
|
#. placeholder {0}: typingNames[0]
|
||||||
#: src/components/ChatModal.tsx:532
|
#: src/components/ChatModal.tsx:566
|
||||||
msgid "{0} is typing…"
|
msgid "{0} is typing…"
|
||||||
msgstr "{0} est en train d'écrire…"
|
msgstr "{0} est en train d'écrire…"
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ msgstr "{visibleCount, plural, one {# commentaire} other {# commentaires}}"
|
|||||||
|
|
||||||
#: src/components/DumpCreateModal.tsx:936
|
#: src/components/DumpCreateModal.tsx:936
|
||||||
#: src/pages/PlaylistDetail.tsx:572
|
#: src/pages/PlaylistDetail.tsx:572
|
||||||
#: src/pages/UserPublicProfile.tsx:798
|
#: src/pages/UserPublicProfile.tsx:801
|
||||||
msgid "← Back"
|
msgid "← Back"
|
||||||
msgstr "← Retour"
|
msgstr "← Retour"
|
||||||
|
|
||||||
@@ -76,7 +76,7 @@ msgstr "← Retour à toutes les recos"
|
|||||||
msgid "← Back to profile"
|
msgid "← Back to profile"
|
||||||
msgstr "← Retour au profil"
|
msgstr "← Retour au profil"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:134
|
#: src/pages/UserPublicProfile.tsx:136
|
||||||
msgid "+ Invite someone"
|
msgid "+ Invite someone"
|
||||||
msgstr "+ Inviter quelqu'un"
|
msgstr "+ Inviter quelqu'un"
|
||||||
|
|
||||||
@@ -142,7 +142,7 @@ msgstr "un commentaire"
|
|||||||
msgid "a post"
|
msgid "a post"
|
||||||
msgstr "une publication"
|
msgstr "une publication"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:1179
|
#: src/pages/UserPublicProfile.tsx:1182
|
||||||
msgid "Account"
|
msgid "Account"
|
||||||
msgstr "Compte"
|
msgstr "Compte"
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ msgstr "Ajouter un commentaire…"
|
|||||||
msgid "Add category"
|
msgid "Add category"
|
||||||
msgstr "Ajouter une catégorie"
|
msgstr "Ajouter une catégorie"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:876
|
#: src/pages/UserPublicProfile.tsx:879
|
||||||
msgid "Add email…"
|
msgid "Add email…"
|
||||||
msgstr "Ajouter un e-mail…"
|
msgstr "Ajouter un e-mail…"
|
||||||
|
|
||||||
@@ -164,7 +164,7 @@ msgstr "Ajouter un e-mail…"
|
|||||||
msgid "Add to playlist"
|
msgid "Add to playlist"
|
||||||
msgstr "Ajouter à la collection"
|
msgstr "Ajouter à la collection"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:1604
|
#: src/pages/UserPublicProfile.tsx:1646
|
||||||
msgid "Admin"
|
msgid "Admin"
|
||||||
msgstr "Administrateur"
|
msgstr "Administrateur"
|
||||||
|
|
||||||
@@ -183,7 +183,7 @@ msgstr "Déjà recommandé par {0} {1}"
|
|||||||
msgid "Already have an account? <0>Log in</0>"
|
msgid "Already have an account? <0>Log in</0>"
|
||||||
msgstr "Vous avez déjà un compte ? <0>Se connecter</0>"
|
msgstr "Vous avez déjà un compte ? <0>Se connecter</0>"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:1198
|
#: src/pages/UserPublicProfile.tsx:1201
|
||||||
msgid "Appearance"
|
msgid "Appearance"
|
||||||
msgstr "Apparence"
|
msgstr "Apparence"
|
||||||
|
|
||||||
@@ -194,7 +194,7 @@ msgstr "Apparence"
|
|||||||
msgid "At least {0} characters"
|
msgid "At least {0} characters"
|
||||||
msgstr "Au moins {0} caractères"
|
msgstr "Au moins {0} caractères"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:1253
|
#: src/pages/UserPublicProfile.tsx:1263
|
||||||
msgid "Auto"
|
msgid "Auto"
|
||||||
msgstr "Auto"
|
msgstr "Auto"
|
||||||
|
|
||||||
@@ -203,8 +203,8 @@ msgstr "Auto"
|
|||||||
msgid "Back to login"
|
msgid "Back to login"
|
||||||
msgstr "Retour à la connexion"
|
msgstr "Retour à la connexion"
|
||||||
|
|
||||||
#: src/contexts/WSProvider.tsx:270
|
#: src/contexts/WSProvider.tsx:292
|
||||||
#: src/contexts/WSProvider.tsx:576
|
#: src/contexts/WSProvider.tsx:608
|
||||||
msgid "Can't connect to the live updates server. Upvotes and notifications may not sync until it reconnects."
|
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."
|
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/Dump.tsx:406
|
||||||
#: src/pages/DumpEdit.tsx:484
|
#: src/pages/DumpEdit.tsx:484
|
||||||
#: src/pages/PlaylistDetail.tsx:923
|
#: src/pages/PlaylistDetail.tsx:923
|
||||||
#: src/pages/UserPublicProfile.tsx:1733
|
#: src/pages/UserPublicProfile.tsx:1775
|
||||||
#: src/pages/UserPublicProfile.tsx:1806
|
#: src/pages/UserPublicProfile.tsx:1848
|
||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr "Annuler"
|
msgstr "Annuler"
|
||||||
|
|
||||||
@@ -227,8 +227,8 @@ msgstr "Annuler"
|
|||||||
msgid "Cancel removal"
|
msgid "Cancel removal"
|
||||||
msgstr "Annuler la suppression"
|
msgstr "Annuler la suppression"
|
||||||
|
|
||||||
#: src/components/ChatModal.tsx:624
|
#: src/components/ChatModal.tsx:658
|
||||||
#: src/components/ChatModal.tsx:625
|
#: src/components/ChatModal.tsx:659
|
||||||
msgid "Cancel reply"
|
msgid "Cancel reply"
|
||||||
msgstr "Annuler la réponse"
|
msgstr "Annuler la réponse"
|
||||||
|
|
||||||
@@ -237,7 +237,7 @@ msgid "Cancel search"
|
|||||||
msgstr "Annuler la recherche"
|
msgstr "Annuler la recherche"
|
||||||
|
|
||||||
#: src/components/CategorySelect.tsx:22
|
#: src/components/CategorySelect.tsx:22
|
||||||
#: src/pages/UserPublicProfile.tsx:1336
|
#: src/pages/UserPublicProfile.tsx:1378
|
||||||
msgid "Categories"
|
msgid "Categories"
|
||||||
msgstr "Catégories"
|
msgstr "Catégories"
|
||||||
|
|
||||||
@@ -245,7 +245,7 @@ msgstr "Catégories"
|
|||||||
msgid "Category"
|
msgid "Category"
|
||||||
msgstr "Catégorie"
|
msgstr "Catégorie"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:825
|
#: src/pages/UserPublicProfile.tsx:828
|
||||||
msgid "Change avatar"
|
msgid "Change avatar"
|
||||||
msgstr "Changer l'avatar"
|
msgstr "Changer l'avatar"
|
||||||
|
|
||||||
@@ -254,14 +254,14 @@ msgstr "Changer l'avatar"
|
|||||||
msgid "Change password"
|
msgid "Change password"
|
||||||
msgstr "Changer le mot de passe"
|
msgstr "Changer le mot de passe"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:1191
|
#: src/pages/UserPublicProfile.tsx:1194
|
||||||
msgid "Change password…"
|
msgid "Change password…"
|
||||||
msgstr "Changer le mot de passe…"
|
msgstr "Changer le mot de passe…"
|
||||||
|
|
||||||
#: src/components/ChatButton.tsx:17
|
#: src/components/ChatButton.tsx:17
|
||||||
#: src/components/ChatFab.tsx:49
|
#: src/components/ChatFab.tsx:49
|
||||||
#: src/components/ChatFab.tsx:50
|
#: src/components/ChatFab.tsx:50
|
||||||
#: src/components/ChatModal.tsx:538
|
#: src/components/ChatModal.tsx:572
|
||||||
msgid "Chat"
|
msgid "Chat"
|
||||||
msgstr "Tribune"
|
msgstr "Tribune"
|
||||||
|
|
||||||
@@ -295,7 +295,7 @@ msgstr "Fermer sans publier ? Le fichier joint sera perdu — ce que vous avez
|
|||||||
msgid "Collapse player"
|
msgid "Collapse player"
|
||||||
msgstr "Réduire le lecteur"
|
msgstr "Réduire le lecteur"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:1245
|
#: src/pages/UserPublicProfile.tsx:1255
|
||||||
msgid "Color scheme"
|
msgid "Color scheme"
|
||||||
msgstr "Thème de couleur"
|
msgstr "Thème de couleur"
|
||||||
|
|
||||||
@@ -304,11 +304,11 @@ msgstr "Thème de couleur"
|
|||||||
msgid "Confirm new password"
|
msgid "Confirm new password"
|
||||||
msgstr "Confirmer le nouveau mot de passe"
|
msgstr "Confirmer le nouveau mot de passe"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:159
|
#: src/pages/UserPublicProfile.tsx:161
|
||||||
msgid "Copied!"
|
msgid "Copied!"
|
||||||
msgstr "Copié !"
|
msgstr "Copié !"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:159
|
#: src/pages/UserPublicProfile.tsx:161
|
||||||
msgid "Copy"
|
msgid "Copy"
|
||||||
msgstr "Copier"
|
msgstr "Copier"
|
||||||
|
|
||||||
@@ -350,11 +350,11 @@ msgstr "Création…"
|
|||||||
msgid "Current password"
|
msgid "Current password"
|
||||||
msgstr "Mot de passe actuel"
|
msgstr "Mot de passe actuel"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:1267
|
#: src/pages/UserPublicProfile.tsx:1277
|
||||||
msgid "Dark"
|
msgid "Dark"
|
||||||
msgstr "Sombre"
|
msgstr "Sombre"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:1280
|
#: src/pages/UserPublicProfile.tsx:1290
|
||||||
msgid "Default tab"
|
msgid "Default tab"
|
||||||
msgstr "Onglet par défaut"
|
msgstr "Onglet par défaut"
|
||||||
|
|
||||||
@@ -446,13 +446,13 @@ msgstr "Recommandé !"
|
|||||||
|
|
||||||
#: src/pages/Search.tsx:174
|
#: src/pages/Search.tsx:174
|
||||||
#: src/pages/UserDumps.tsx:80
|
#: src/pages/UserDumps.tsx:80
|
||||||
#: src/pages/UserPublicProfile.tsx:948
|
#: src/pages/UserPublicProfile.tsx:951
|
||||||
msgid "Dumps"
|
msgid "Dumps"
|
||||||
msgstr "Recos"
|
msgstr "Recos"
|
||||||
|
|
||||||
#. placeholder {0}: dumps.items.length
|
#. placeholder {0}: dumps.items.length
|
||||||
#. placeholder {1}: dumps.hasMore ? "+" : ""
|
#. placeholder {1}: dumps.hasMore ? "+" : ""
|
||||||
#: src/pages/UserPublicProfile.tsx:968
|
#: src/pages/UserPublicProfile.tsx:971
|
||||||
msgid "Dumps ({0}{1})"
|
msgid "Dumps ({0}{1})"
|
||||||
msgstr "Recos ({0}{1})"
|
msgstr "Recos ({0}{1})"
|
||||||
|
|
||||||
@@ -530,9 +530,9 @@ msgstr "Impossible de créer la collection"
|
|||||||
msgid "Failed to delete"
|
msgid "Failed to delete"
|
||||||
msgstr "Suppression échouée"
|
msgstr "Suppression échouée"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:104
|
#: src/pages/UserPublicProfile.tsx:106
|
||||||
#: src/pages/UserPublicProfile.tsx:107
|
#: src/pages/UserPublicProfile.tsx:109
|
||||||
#: src/pages/UserPublicProfile.tsx:168
|
#: src/pages/UserPublicProfile.tsx:170
|
||||||
msgid "Failed to generate invite"
|
msgid "Failed to generate invite"
|
||||||
msgstr "Impossible de générer une invitation"
|
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/JournalFeed.tsx:33
|
||||||
#: src/pages/index/NewFeed.tsx:36
|
#: src/pages/index/NewFeed.tsx:36
|
||||||
#: src/pages/Notifications.tsx:374
|
#: src/pages/Notifications.tsx:374
|
||||||
#: src/pages/UserPublicProfile.tsx:1070
|
#: src/pages/UserPublicProfile.tsx:1073
|
||||||
#: src/pages/UserPublicProfile.tsx:1112
|
#: src/pages/UserPublicProfile.tsx:1115
|
||||||
#: src/pages/UserPublicProfile.tsx:1157
|
#: src/pages/UserPublicProfile.tsx:1160
|
||||||
msgid "Failed to load"
|
msgid "Failed to load"
|
||||||
msgstr "Chargement échoué"
|
msgstr "Chargement échoué"
|
||||||
|
|
||||||
@@ -560,8 +560,8 @@ msgid "Failed to post reply"
|
|||||||
msgstr "Impossible de publier la réponse"
|
msgstr "Impossible de publier la réponse"
|
||||||
|
|
||||||
#: src/pages/PlaylistDetail.tsx:911
|
#: src/pages/PlaylistDetail.tsx:911
|
||||||
#: src/pages/UserPublicProfile.tsx:1736
|
#: src/pages/UserPublicProfile.tsx:1778
|
||||||
#: src/pages/UserPublicProfile.tsx:1808
|
#: src/pages/UserPublicProfile.tsx:1850
|
||||||
msgid "Failed to save"
|
msgid "Failed to save"
|
||||||
msgstr "Enregistrement échoué"
|
msgstr "Enregistrement échoué"
|
||||||
|
|
||||||
@@ -573,15 +573,15 @@ msgstr "Échec de l’enregistrement de la catégorie"
|
|||||||
msgid "Failed to save edit"
|
msgid "Failed to save edit"
|
||||||
msgstr "Impossible d'enregistrer la modification"
|
msgstr "Impossible d'enregistrer la modification"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:885
|
#: src/pages/UserPublicProfile.tsx:888
|
||||||
msgid "Failed to update avatar"
|
msgid "Failed to update avatar"
|
||||||
msgstr "Impossible de mettre à jour l'avatar"
|
msgstr "Impossible de mettre à jour l'avatar"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:1674
|
#: src/pages/UserPublicProfile.tsx:1716
|
||||||
msgid "Failed to update role"
|
msgid "Failed to update role"
|
||||||
msgstr "Erreur lors de la mise à jour du rôle"
|
msgstr "Erreur lors de la mise à jour du rôle"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:1275
|
#: src/pages/UserPublicProfile.tsx:1285
|
||||||
msgid "Feeds"
|
msgid "Feeds"
|
||||||
msgstr "Flux"
|
msgstr "Flux"
|
||||||
|
|
||||||
@@ -615,8 +615,8 @@ msgid "Follow some users to see their dumps here."
|
|||||||
msgstr "Suivez des utilisateurs pour voir leurs recos ici."
|
msgstr "Suivez des utilisateurs pour voir leurs recos ici."
|
||||||
|
|
||||||
#: src/components/FeedTabBar.tsx:34
|
#: src/components/FeedTabBar.tsx:34
|
||||||
#: src/pages/UserPublicProfile.tsx:950
|
#: src/pages/UserPublicProfile.tsx:953
|
||||||
#: src/pages/UserPublicProfile.tsx:1309
|
#: src/pages/UserPublicProfile.tsx:1319
|
||||||
msgid "Followed"
|
msgid "Followed"
|
||||||
msgstr "Suivi"
|
msgstr "Suivi"
|
||||||
|
|
||||||
@@ -626,13 +626,13 @@ msgstr "Suivi"
|
|||||||
msgid "Followed ({0}{1})"
|
msgid "Followed ({0}{1})"
|
||||||
msgstr "Suivies ({0}{1})"
|
msgstr "Suivies ({0}{1})"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:1101
|
#: src/pages/UserPublicProfile.tsx:1104
|
||||||
msgid "Followed playlists"
|
msgid "Followed playlists"
|
||||||
msgstr "Collections suivies"
|
msgstr "Collections suivies"
|
||||||
|
|
||||||
#: src/components/FollowButton.tsx:37
|
#: src/components/FollowButton.tsx:37
|
||||||
#: src/components/FollowButton.tsx:64
|
#: src/components/FollowButton.tsx:64
|
||||||
#: src/pages/UserPublicProfile.tsx:1059
|
#: src/pages/UserPublicProfile.tsx:1062
|
||||||
msgid "Following"
|
msgid "Following"
|
||||||
msgstr "Abonné"
|
msgstr "Abonné"
|
||||||
|
|
||||||
@@ -657,7 +657,7 @@ msgid "Go to login"
|
|||||||
msgstr "Aller à la connexion"
|
msgstr "Aller à la connexion"
|
||||||
|
|
||||||
#: src/components/FeedTabBar.tsx:30
|
#: src/components/FeedTabBar.tsx:30
|
||||||
#: src/pages/UserPublicProfile.tsx:1288
|
#: src/pages/UserPublicProfile.tsx:1298
|
||||||
msgid "Hot"
|
msgid "Hot"
|
||||||
msgstr "Tendances"
|
msgstr "Tendances"
|
||||||
|
|
||||||
@@ -677,22 +677,22 @@ msgstr "Invitation invalide"
|
|||||||
msgid "Invalid link"
|
msgid "Invalid link"
|
||||||
msgstr "Lien invalide"
|
msgstr "Lien invalide"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:142
|
#: src/pages/UserPublicProfile.tsx:144
|
||||||
#: src/pages/UserPublicProfile.tsx:149
|
#: src/pages/UserPublicProfile.tsx:151
|
||||||
msgid "Invite link"
|
msgid "Invite link"
|
||||||
msgstr "Lien d'invitation"
|
msgstr "Lien d'invitation"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:847
|
#: src/pages/UserPublicProfile.tsx:850
|
||||||
msgid "invited by"
|
msgid "invited by"
|
||||||
msgstr "invité par"
|
msgstr "invité par"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:951
|
#: src/pages/UserPublicProfile.tsx:954
|
||||||
#: src/pages/UserPublicProfile.tsx:1146
|
#: src/pages/UserPublicProfile.tsx:1149
|
||||||
msgid "Invitees"
|
msgid "Invitees"
|
||||||
msgstr "Invités"
|
msgstr "Invités"
|
||||||
|
|
||||||
#: src/components/FeedTabBar.tsx:32
|
#: src/components/FeedTabBar.tsx:32
|
||||||
#: src/pages/UserPublicProfile.tsx:1302
|
#: src/pages/UserPublicProfile.tsx:1312
|
||||||
msgid "Journal"
|
msgid "Journal"
|
||||||
msgstr "Journal"
|
msgstr "Journal"
|
||||||
|
|
||||||
@@ -700,7 +700,7 @@ msgstr "Journal"
|
|||||||
msgid "Keep editing"
|
msgid "Keep editing"
|
||||||
msgstr "Continuer l'édition"
|
msgstr "Continuer l'édition"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:1260
|
#: src/pages/UserPublicProfile.tsx:1270
|
||||||
msgid "Light"
|
msgid "Light"
|
||||||
msgstr "Clair"
|
msgstr "Clair"
|
||||||
|
|
||||||
@@ -716,7 +716,7 @@ msgstr "Lien"
|
|||||||
msgid "Link or file"
|
msgid "Link or file"
|
||||||
msgstr "Lien ou fichier"
|
msgstr "Lien ou fichier"
|
||||||
|
|
||||||
#: src/contexts/WSProvider.tsx:575
|
#: src/contexts/WSProvider.tsx:607
|
||||||
msgid "Live updates are temporarily disconnected. Trying to reconnect…"
|
msgid "Live updates are temporarily disconnected. Trying to reconnect…"
|
||||||
msgstr "Les mises à jour en direct sont temporairement interrompues. Tentative de reconnexion…"
|
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"
|
msgid "Load more"
|
||||||
msgstr "Charger plus"
|
msgstr "Charger plus"
|
||||||
|
|
||||||
#: src/components/ChatModal.tsx:551
|
#: src/components/ChatModal.tsx:585
|
||||||
msgid "Load older messages"
|
msgid "Load older messages"
|
||||||
msgstr "Charger les messages plus anciens"
|
msgstr "Charger les messages plus anciens"
|
||||||
|
|
||||||
@@ -740,7 +740,7 @@ msgstr "Chargement de la reco…"
|
|||||||
|
|
||||||
#: src/pages/index/FollowedFeed.tsx:111
|
#: src/pages/index/FollowedFeed.tsx:111
|
||||||
#: src/pages/index/HotFeed.tsx:64
|
#: 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/index/NewFeed.tsx:64
|
||||||
#: src/pages/Search.tsx:244
|
#: src/pages/Search.tsx:244
|
||||||
#: src/pages/UserDumps.tsx:121
|
#: src/pages/UserDumps.tsx:121
|
||||||
@@ -754,12 +754,12 @@ msgstr "Chargement…"
|
|||||||
msgid "Loading playlist…"
|
msgid "Loading playlist…"
|
||||||
msgstr "Chargement de la collection…"
|
msgstr "Chargement de la collection…"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:781
|
#: src/pages/UserPublicProfile.tsx:784
|
||||||
msgid "Loading profile…"
|
msgid "Loading profile…"
|
||||||
msgstr "Chargement du profil…"
|
msgstr "Chargement du profil…"
|
||||||
|
|
||||||
#: src/components/CategoryManager.tsx:52
|
#: src/components/CategoryManager.tsx:52
|
||||||
#: src/components/ChatModal.tsx:550
|
#: src/components/ChatModal.tsx:584
|
||||||
#: src/components/PlaylistMembershipPanel.tsx:28
|
#: src/components/PlaylistMembershipPanel.tsx:28
|
||||||
#: src/components/TextEditor.tsx:311
|
#: src/components/TextEditor.tsx:311
|
||||||
#: src/components/UserListPopover.tsx:192
|
#: src/components/UserListPopover.tsx:192
|
||||||
@@ -772,9 +772,9 @@ msgstr "Chargement du profil…"
|
|||||||
#: src/pages/Notifications.tsx:446
|
#: src/pages/Notifications.tsx:446
|
||||||
#: src/pages/UserDumps.tsx:54
|
#: src/pages/UserDumps.tsx:54
|
||||||
#: src/pages/UserPlaylists.tsx:345
|
#: src/pages/UserPlaylists.tsx:345
|
||||||
#: src/pages/UserPublicProfile.tsx:1064
|
#: src/pages/UserPublicProfile.tsx:1067
|
||||||
#: src/pages/UserPublicProfile.tsx:1106
|
#: src/pages/UserPublicProfile.tsx:1109
|
||||||
#: src/pages/UserPublicProfile.tsx:1151
|
#: src/pages/UserPublicProfile.tsx:1154
|
||||||
#: src/pages/UserUpvoted.tsx:125
|
#: src/pages/UserUpvoted.tsx:125
|
||||||
msgid "Loading…"
|
msgid "Loading…"
|
||||||
msgstr "Chargement…"
|
msgstr "Chargement…"
|
||||||
@@ -794,8 +794,8 @@ msgstr "Se connecter pour aimer"
|
|||||||
msgid "Log in to vote"
|
msgid "Log in to vote"
|
||||||
msgstr "Se connecter pour voter"
|
msgstr "Se connecter pour voter"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:802
|
#: src/pages/UserPublicProfile.tsx:805
|
||||||
#: src/pages/UserPublicProfile.tsx:898
|
#: src/pages/UserPublicProfile.tsx:901
|
||||||
msgid "Log out"
|
msgid "Log out"
|
||||||
msgstr "Se déconnecter"
|
msgstr "Se déconnecter"
|
||||||
|
|
||||||
@@ -807,7 +807,7 @@ msgstr "Connexion…"
|
|||||||
msgid "Login failed"
|
msgid "Login failed"
|
||||||
msgstr "Connexion échouée"
|
msgstr "Connexion échouée"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:956
|
#: src/pages/UserPublicProfile.tsx:959
|
||||||
msgid "Manage"
|
msgid "Manage"
|
||||||
msgstr "Administration"
|
msgstr "Administration"
|
||||||
|
|
||||||
@@ -815,7 +815,7 @@ msgstr "Administration"
|
|||||||
msgid "Max 50 MB"
|
msgid "Max 50 MB"
|
||||||
msgstr "Max 50 Mo"
|
msgstr "Max 50 Mo"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:1603
|
#: src/pages/UserPublicProfile.tsx:1645
|
||||||
msgid "Moderator"
|
msgid "Moderator"
|
||||||
msgstr "Modérateur"
|
msgstr "Modérateur"
|
||||||
|
|
||||||
@@ -829,7 +829,7 @@ msgid "new"
|
|||||||
msgstr "nouveau"
|
msgstr "nouveau"
|
||||||
|
|
||||||
#: src/components/FeedTabBar.tsx:31
|
#: src/components/FeedTabBar.tsx:31
|
||||||
#: src/pages/UserPublicProfile.tsx:1295
|
#: src/pages/UserPublicProfile.tsx:1305
|
||||||
msgid "New"
|
msgid "New"
|
||||||
msgstr "Nouveau"
|
msgstr "Nouveau"
|
||||||
|
|
||||||
@@ -837,7 +837,7 @@ msgstr "Nouveau"
|
|||||||
#: src/components/DumpFab.tsx:65
|
#: src/components/DumpFab.tsx:65
|
||||||
#: src/components/DumpFab.tsx:66
|
#: src/components/DumpFab.tsx:66
|
||||||
#: src/pages/UserDumps.tsx:88
|
#: src/pages/UserDumps.tsx:88
|
||||||
#: src/pages/UserPublicProfile.tsx:1382
|
#: src/pages/UserPublicProfile.tsx:1424
|
||||||
msgid "New dump"
|
msgid "New dump"
|
||||||
msgstr "Nouvelle reco"
|
msgstr "Nouvelle reco"
|
||||||
|
|
||||||
@@ -878,15 +878,15 @@ msgid "No emoji found."
|
|||||||
msgstr "Aucun emoji trouvé."
|
msgstr "Aucun emoji trouvé."
|
||||||
|
|
||||||
#: src/pages/UserPlaylists.tsx:442
|
#: src/pages/UserPlaylists.tsx:442
|
||||||
#: src/pages/UserPublicProfile.tsx:1119
|
#: src/pages/UserPublicProfile.tsx:1122
|
||||||
msgid "No followed playlists yet."
|
msgid "No followed playlists yet."
|
||||||
msgstr "Pas encore de collections suivies."
|
msgstr "Pas encore de collections suivies."
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:1164
|
#: src/pages/UserPublicProfile.tsx:1167
|
||||||
msgid "No invitees yet."
|
msgid "No invitees yet."
|
||||||
msgstr "Aucun invité pour le moment."
|
msgstr "Aucun invité pour le moment."
|
||||||
|
|
||||||
#: src/components/ChatModal.tsx:558
|
#: src/components/ChatModal.tsx:592
|
||||||
msgid "No messages yet. Say hello!"
|
msgid "No messages yet. Say hello!"
|
||||||
msgstr "Aucun message pour l'instant. Dites bonjour !"
|
msgstr "Aucun message pour l'instant. Dites bonjour !"
|
||||||
|
|
||||||
@@ -900,7 +900,7 @@ msgstr "Aucune collection ne correspond à « {q} »."
|
|||||||
|
|
||||||
#: src/components/PlaylistMembershipPanel.tsx:34
|
#: src/components/PlaylistMembershipPanel.tsx:34
|
||||||
#: src/pages/UserPlaylists.tsx:400
|
#: src/pages/UserPlaylists.tsx:400
|
||||||
#: src/pages/UserPublicProfile.tsx:1030
|
#: src/pages/UserPublicProfile.tsx:1033
|
||||||
msgid "No playlists yet."
|
msgid "No playlists yet."
|
||||||
msgstr "Pas encore de collections."
|
msgstr "Pas encore de collections."
|
||||||
|
|
||||||
@@ -908,14 +908,14 @@ msgstr "Pas encore de collections."
|
|||||||
msgid "No users match \"{q}\"."
|
msgid "No users match \"{q}\"."
|
||||||
msgstr "Aucun utilisateur ne correspond à « {q} »."
|
msgstr "Aucun utilisateur ne correspond à « {q} »."
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:1077
|
#: src/pages/UserPublicProfile.tsx:1080
|
||||||
msgid "Not following anyone yet."
|
msgid "Not following anyone yet."
|
||||||
msgstr "Aucun abonnement pour le moment."
|
msgstr "Aucun abonnement pour le moment."
|
||||||
|
|
||||||
#: src/pages/Notifications.tsx:381
|
#: src/pages/Notifications.tsx:381
|
||||||
#: src/pages/UserDumps.tsx:99
|
#: src/pages/UserDumps.tsx:99
|
||||||
#: src/pages/UserPublicProfile.tsx:1393
|
#: src/pages/UserPublicProfile.tsx:1435
|
||||||
#: src/pages/UserPublicProfile.tsx:1515
|
#: src/pages/UserPublicProfile.tsx:1557
|
||||||
#: src/pages/UserUpvoted.tsx:159
|
#: src/pages/UserUpvoted.tsx:159
|
||||||
msgid "Nothing here yet."
|
msgid "Nothing here yet."
|
||||||
msgstr "Rien ici pour l'instant."
|
msgstr "Rien ici pour l'instant."
|
||||||
@@ -923,6 +923,7 @@ msgstr "Rien ici pour l'instant."
|
|||||||
#: src/components/NotificationBell.tsx:42
|
#: src/components/NotificationBell.tsx:42
|
||||||
#: src/pages/Notifications.tsx:265
|
#: src/pages/Notifications.tsx:265
|
||||||
#: src/pages/Notifications.tsx:359
|
#: src/pages/Notifications.tsx:359
|
||||||
|
#: src/pages/UserPublicProfile.tsx:1327
|
||||||
msgid "Notifications"
|
msgid "Notifications"
|
||||||
msgstr "Notifications"
|
msgstr "Notifications"
|
||||||
|
|
||||||
@@ -930,6 +931,14 @@ msgstr "Notifications"
|
|||||||
msgid "Notifications ({unreadNotificationCount} unread)"
|
msgid "Notifications ({unreadNotificationCount} unread)"
|
||||||
msgstr "Notifications ({unreadNotificationCount} non lues)"
|
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
|
#: src/components/SearchBar.tsx:96
|
||||||
msgid "Open search"
|
msgid "Open search"
|
||||||
msgstr "Ouvrir la recherche"
|
msgstr "Ouvrir la recherche"
|
||||||
@@ -943,7 +952,7 @@ msgid "Page not found"
|
|||||||
msgstr "Page introuvable"
|
msgstr "Page introuvable"
|
||||||
|
|
||||||
#: src/pages/UserLogin.tsx:76
|
#: src/pages/UserLogin.tsx:76
|
||||||
#: src/pages/UserPublicProfile.tsx:1184
|
#: src/pages/UserPublicProfile.tsx:1187
|
||||||
msgid "Password"
|
msgid "Password"
|
||||||
msgstr "Mot de passe"
|
msgstr "Mot de passe"
|
||||||
|
|
||||||
@@ -982,13 +991,13 @@ msgstr "Titre de la collection"
|
|||||||
#: src/components/UserMenu.tsx:62
|
#: src/components/UserMenu.tsx:62
|
||||||
#: src/pages/Search.tsx:177
|
#: src/pages/Search.tsx:177
|
||||||
#: src/pages/UserPlaylists.tsx:371
|
#: src/pages/UserPlaylists.tsx:371
|
||||||
#: src/pages/UserPublicProfile.tsx:949
|
#: src/pages/UserPublicProfile.tsx:952
|
||||||
msgid "Playlists"
|
msgid "Playlists"
|
||||||
msgstr "Collections"
|
msgstr "Collections"
|
||||||
|
|
||||||
#. placeholder {0}: playlists.items.length
|
#. placeholder {0}: playlists.items.length
|
||||||
#. placeholder {1}: playlists.hasMore ? "+" : ""
|
#. placeholder {1}: playlists.hasMore ? "+" : ""
|
||||||
#: src/pages/UserPublicProfile.tsx:999
|
#: src/pages/UserPublicProfile.tsx:1002
|
||||||
msgid "Playlists ({0}{1})"
|
msgid "Playlists ({0}{1})"
|
||||||
msgstr "Collections ({0}{1})"
|
msgstr "Collections ({0}{1})"
|
||||||
|
|
||||||
@@ -1090,7 +1099,7 @@ msgstr "Remplacer le fichier"
|
|||||||
msgid "Reply"
|
msgid "Reply"
|
||||||
msgstr "Répondre"
|
msgstr "Répondre"
|
||||||
|
|
||||||
#: src/components/ChatModal.tsx:614
|
#: src/components/ChatModal.tsx:648
|
||||||
msgid "Replying to"
|
msgid "Replying to"
|
||||||
msgstr "En réponse à"
|
msgstr "En réponse à"
|
||||||
|
|
||||||
@@ -1121,7 +1130,7 @@ msgstr "Rétablir le titre suggéré"
|
|||||||
msgid "Retry"
|
msgid "Retry"
|
||||||
msgstr "Réessayer"
|
msgstr "Réessayer"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:1658
|
#: src/pages/UserPublicProfile.tsx:1700
|
||||||
msgid "Role"
|
msgid "Role"
|
||||||
msgstr "Rôle"
|
msgstr "Rôle"
|
||||||
|
|
||||||
@@ -1130,8 +1139,8 @@ msgstr "Rôle"
|
|||||||
#: src/pages/Dump.tsx:398
|
#: src/pages/Dump.tsx:398
|
||||||
#: src/pages/DumpEdit.tsx:487
|
#: src/pages/DumpEdit.tsx:487
|
||||||
#: src/pages/PlaylistDetail.tsx:930
|
#: src/pages/PlaylistDetail.tsx:930
|
||||||
#: src/pages/UserPublicProfile.tsx:1725
|
#: src/pages/UserPublicProfile.tsx:1767
|
||||||
#: src/pages/UserPublicProfile.tsx:1798
|
#: src/pages/UserPublicProfile.tsx:1840
|
||||||
msgid "Save"
|
msgid "Save"
|
||||||
msgstr "Enregistrer"
|
msgstr "Enregistrer"
|
||||||
|
|
||||||
@@ -1140,8 +1149,8 @@ msgstr "Enregistrer"
|
|||||||
#: src/pages/Dump.tsx:397
|
#: src/pages/Dump.tsx:397
|
||||||
#: src/pages/PlaylistDetail.tsx:926
|
#: src/pages/PlaylistDetail.tsx:926
|
||||||
#: src/pages/ResetPassword.tsx:126
|
#: src/pages/ResetPassword.tsx:126
|
||||||
#: src/pages/UserPublicProfile.tsx:1722
|
#: src/pages/UserPublicProfile.tsx:1764
|
||||||
#: src/pages/UserPublicProfile.tsx:1795
|
#: src/pages/UserPublicProfile.tsx:1837
|
||||||
msgid "Saving…"
|
msgid "Saving…"
|
||||||
msgstr "Enregistrement…"
|
msgstr "Enregistrement…"
|
||||||
|
|
||||||
@@ -1163,7 +1172,7 @@ msgstr "Recherche échouée"
|
|||||||
msgid "Searching…"
|
msgid "Searching…"
|
||||||
msgstr "Recherche…"
|
msgstr "Recherche…"
|
||||||
|
|
||||||
#: src/components/ChatModal.tsx:660
|
#: src/components/ChatModal.tsx:694
|
||||||
msgid "Send"
|
msgid "Send"
|
||||||
msgstr "Envoyer"
|
msgstr "Envoyer"
|
||||||
|
|
||||||
@@ -1184,15 +1193,15 @@ msgstr "Serveur inaccessible"
|
|||||||
msgid "Set new password"
|
msgid "Set new password"
|
||||||
msgstr "Définir un nouveau mot de passe"
|
msgstr "Définir un nouveau mot de passe"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:953
|
#: src/pages/UserPublicProfile.tsx:956
|
||||||
msgid "Settings"
|
msgid "Settings"
|
||||||
msgstr "Paramètres"
|
msgstr "Paramètres"
|
||||||
|
|
||||||
#: src/components/ChatModal.tsx:535
|
#: src/components/ChatModal.tsx:569
|
||||||
msgid "Several people are typing…"
|
msgid "Several people are typing…"
|
||||||
msgstr "Plusieurs personnes écrivent…"
|
msgstr "Plusieurs personnes écrivent…"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:162
|
#: src/pages/UserPublicProfile.tsx:164
|
||||||
msgid "Single use, valid for 7 days."
|
msgid "Single use, valid for 7 days."
|
||||||
msgstr "Usage unique, valable 7 jours."
|
msgstr "Usage unique, valable 7 jours."
|
||||||
|
|
||||||
@@ -1205,11 +1214,15 @@ msgstr "identifiant"
|
|||||||
msgid "Something went wrong"
|
msgid "Something went wrong"
|
||||||
msgstr "Une erreur est survenue"
|
msgstr "Une erreur est survenue"
|
||||||
|
|
||||||
|
#: src/pages/UserPublicProfile.tsx:1332
|
||||||
|
msgid "Sounds"
|
||||||
|
msgstr "Sons"
|
||||||
|
|
||||||
#: src/components/DumpCreateModal.tsx:707
|
#: src/components/DumpCreateModal.tsx:707
|
||||||
msgid "Start fresh"
|
msgid "Start fresh"
|
||||||
msgstr "Repartir de zéro"
|
msgstr "Repartir de zéro"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:1203
|
#: src/pages/UserPublicProfile.tsx:1206
|
||||||
msgid "Style"
|
msgid "Style"
|
||||||
msgstr "Style"
|
msgstr "Style"
|
||||||
|
|
||||||
@@ -1261,7 +1274,7 @@ msgstr "Aujourd'hui"
|
|||||||
msgid "Track {0}"
|
msgid "Track {0}"
|
||||||
msgstr "Piste {0}"
|
msgstr "Piste {0}"
|
||||||
|
|
||||||
#: src/components/ChatModal.tsx:637
|
#: src/components/ChatModal.tsx:671
|
||||||
msgid "Type a message…"
|
msgid "Type a message…"
|
||||||
msgstr "Écrivez un message…"
|
msgstr "Écrivez un message…"
|
||||||
|
|
||||||
@@ -1277,7 +1290,7 @@ msgstr "Ne plus suivre {targetUsername}"
|
|||||||
msgid "Unfollow playlist"
|
msgid "Unfollow playlist"
|
||||||
msgstr "Ne plus suivre la collection"
|
msgstr "Ne plus suivre la collection"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:748
|
#: src/pages/UserPublicProfile.tsx:751
|
||||||
msgid "Upload failed"
|
msgid "Upload failed"
|
||||||
msgstr "Envoi échoué"
|
msgstr "Envoi échoué"
|
||||||
|
|
||||||
@@ -1300,7 +1313,7 @@ msgstr "Voté"
|
|||||||
|
|
||||||
#. placeholder {0}: votes.items.length
|
#. placeholder {0}: votes.items.length
|
||||||
#. placeholder {1}: votes.hasMore ? "+" : ""
|
#. placeholder {1}: votes.hasMore ? "+" : ""
|
||||||
#: src/pages/UserPublicProfile.tsx:979
|
#: src/pages/UserPublicProfile.tsx:982
|
||||||
msgid "Upvoted ({0}{1})"
|
msgid "Upvoted ({0}{1})"
|
||||||
msgstr "Votés ({0}{1})"
|
msgstr "Votés ({0}{1})"
|
||||||
|
|
||||||
@@ -1316,11 +1329,11 @@ msgstr "L'URL est obligatoire."
|
|||||||
msgid "URL slug"
|
msgid "URL slug"
|
||||||
msgstr "Identifiant d’URL"
|
msgstr "Identifiant d’URL"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:1602
|
#: src/pages/UserPublicProfile.tsx:1644
|
||||||
msgid "User"
|
msgid "User"
|
||||||
msgstr "Utilisateur"
|
msgstr "Utilisateur"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:1321
|
#: src/pages/UserPublicProfile.tsx:1363
|
||||||
msgid "User management"
|
msgid "User management"
|
||||||
msgstr "Gestion utilisateur"
|
msgstr "Gestion utilisateur"
|
||||||
|
|
||||||
@@ -1337,11 +1350,11 @@ msgstr "Nom d'utilisateur"
|
|||||||
msgid "Users"
|
msgid "Users"
|
||||||
msgstr "Utilisateurs"
|
msgstr "Utilisateurs"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:1049
|
#: src/pages/UserPublicProfile.tsx:1052
|
||||||
#: src/pages/UserPublicProfile.tsx:1092
|
#: src/pages/UserPublicProfile.tsx:1095
|
||||||
#: src/pages/UserPublicProfile.tsx:1134
|
#: src/pages/UserPublicProfile.tsx:1137
|
||||||
#: src/pages/UserPublicProfile.tsx:1414
|
#: src/pages/UserPublicProfile.tsx:1456
|
||||||
#: src/pages/UserPublicProfile.tsx:1545
|
#: src/pages/UserPublicProfile.tsx:1587
|
||||||
msgid "View all →"
|
msgid "View all →"
|
||||||
msgstr "Tout voir →"
|
msgstr "Tout voir →"
|
||||||
|
|
||||||
@@ -1354,8 +1367,8 @@ msgstr "Voir la reco →"
|
|||||||
msgid "What makes it worth it?"
|
msgid "What makes it worth it?"
|
||||||
msgstr "Pourquoi on en voudrait ?"
|
msgstr "Pourquoi on en voudrait ?"
|
||||||
|
|
||||||
#: src/pages/UserPublicProfile.tsx:933
|
#: src/pages/UserPublicProfile.tsx:936
|
||||||
#: src/pages/UserPublicProfile.tsx:1787
|
#: src/pages/UserPublicProfile.tsx:1829
|
||||||
msgid "Who am I?"
|
msgid "Who am I?"
|
||||||
msgstr "Qui suis-je ?"
|
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/FollowedFeed.tsx:116
|
||||||
#: src/pages/index/HotFeed.tsx:69
|
#: 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/index/NewFeed.tsx:69
|
||||||
#: src/pages/Search.tsx:249
|
#: src/pages/Search.tsx:249
|
||||||
#: src/pages/UserDumps.tsx:126
|
#: src/pages/UserDumps.tsx:126
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ import { useAuth } from "../hooks/useAuth.ts";
|
|||||||
import { useDocumentTitle } from "../hooks/useDocumentTitle.ts";
|
import { useDocumentTitle } from "../hooks/useDocumentTitle.ts";
|
||||||
import { useTheme } from "../hooks/useTheme.ts";
|
import { useTheme } from "../hooks/useTheme.ts";
|
||||||
import { useDefaultFeedTab } from "../hooks/useDefaultFeedTab.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 { useWS } from "../hooks/useWS.ts";
|
||||||
import { useDumpListSync } from "../hooks/useDumpListSync.ts";
|
import { useDumpListSync } from "../hooks/useDumpListSync.ts";
|
||||||
import { useFading } from "../hooks/useFading.ts";
|
import { useFading } from "../hooks/useFading.ts";
|
||||||
@@ -235,6 +237,7 @@ export function UserPublicProfile() {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { user: me, authFetch, login, logout, token } = useAuth();
|
const { user: me, authFetch, login, logout, token } = useAuth();
|
||||||
const { style, colorScheme, setStyle, setColorScheme } = useTheme();
|
const { style, colorScheme, setStyle, setColorScheme } = useTheme();
|
||||||
|
const [soundsEnabled, setSoundsEnabled] = useSoundsEnabled();
|
||||||
const [defaultFeedTab, setDefaultFeedTab] = useDefaultFeedTab();
|
const [defaultFeedTab, setDefaultFeedTab] = useDefaultFeedTab();
|
||||||
const {
|
const {
|
||||||
voteCounts,
|
voteCounts,
|
||||||
@@ -1319,6 +1322,38 @@ export function UserPublicProfile() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
<section className="profile-section">
|
||||||
|
<h2 className="profile-section-title">
|
||||||
|
<Trans>Notifications</Trans>
|
||||||
|
</h2>
|
||||||
|
<div className="profile-appearance-grid">
|
||||||
|
<div className="profile-appearance-row">
|
||||||
|
<span className="profile-appearance-label">
|
||||||
|
<Trans>Sounds</Trans>
|
||||||
|
</span>
|
||||||
|
<div className="visibility-toggle">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={soundsEnabled ? "active" : ""}
|
||||||
|
onClick={() => {
|
||||||
|
setSoundsEnabled(true);
|
||||||
|
// Let the user hear what they just turned on.
|
||||||
|
previewChime("notify");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Trans>On</Trans>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={soundsEnabled ? "" : "active"}
|
||||||
|
onClick={() => setSoundsEnabled(false)}
|
||||||
|
>
|
||||||
|
<Trans>Off</Trans>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
123
src/utils/sound.ts
Normal file
123
src/utils/sound.ts
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user