diff --git a/api/middleware/og.ts b/api/middleware/og.ts index 2c22dc0..4ecc8f5 100644 --- a/api/middleware/og.ts +++ b/api/middleware/og.ts @@ -73,7 +73,8 @@ async function loadIndexHtml(): Promise { } const DUMP_RE = /^\/dumps\/([^/]+)$/; -const USER_RE = /^\/users\/([^/]+)$/; +// Allow an optional `/~/` suffix so linkable profile tabs keep OG meta. +const USER_RE = /^\/users\/([^/]+)(?:\/~\/[^/]+)?$/; const PLAYLIST_RE = /^\/playlists\/([^/]+)$/; export async function ogMiddleware(ctx: Context, next: Next) { diff --git a/api/model/db.ts b/api/model/db.ts index 588137b..5571ecf 100644 --- a/api/model/db.ts +++ b/api/model/db.ts @@ -83,6 +83,20 @@ export interface DumpRow { [key: string]: SQLOutputValue; // Index signature } +// Shared `dumps` column list — keep in sync with DumpRow/isDumpRow above. +// Services import these instead of repeating the column list themselves. +export const DUMP_COLUMNS = + "id, kind, title, slug, comment, user_id, created_at, updated_at, url, rich_content, file_name, file_mime, file_size, vote_count, is_private, custom_thumbnail_mime"; + +export const DUMP_COLUMNS_ALIASED = + "d.id, d.kind, d.title, d.slug, d.comment, d.user_id, d.created_at, d.updated_at, d.url, d.rich_content, d.file_name, d.file_mime, d.file_size, d.vote_count, d.is_private, d.custom_thumbnail_mime"; + +export const DUMP_SELECT_COLUMNS = `${DUMP_COLUMNS}, + (SELECT COUNT(*) FROM comments WHERE dump_id = dumps.id AND deleted = 0) as comment_count`; + +export const DUMP_SELECT_COLUMNS_ALIASED = `${DUMP_COLUMNS_ALIASED}, + (SELECT COUNT(*) FROM comments WHERE dump_id = d.id AND deleted = 0) as comment_count`; + export interface UserRow { id: string; username: string; diff --git a/api/services/backlink-service.ts b/api/services/backlink-service.ts index 9c63c20..267c649 100644 --- a/api/services/backlink-service.ts +++ b/api/services/backlink-service.ts @@ -1,13 +1,12 @@ import { type Dump } from "../model/interfaces.ts"; -import { db, dumpRowToApi, isDumpRow } from "../model/db.ts"; +import { + db, + DUMP_SELECT_COLUMNS as SELECT_COLS, + dumpRowToApi, + isDumpRow, +} from "../model/db.ts"; import { UUID_RE } from "../lib/slugify.ts"; -// Mirrors dump-service SELECT_COLS — kept local to avoid a circular import -// (dump-service.ts must import relinkBacklinks from this file). -const SELECT_COLS = - "id, kind, title, slug, comment, user_id, created_at, updated_at, url, rich_content, file_name, file_mime, file_size, vote_count, is_private, custom_thumbnail_mime," + - " (SELECT COUNT(*) FROM comments WHERE dump_id = dumps.id AND deleted = 0) as comment_count"; - // Matches http(s) URLs in free text — stops at whitespace or markdown-link // delimiters (so `[text](url)` and `` don't swallow the closing char). const URL_RE = /https?:\/\/[^\s)\]<>"']+/g; diff --git a/api/services/dump-service.ts b/api/services/dump-service.ts index 4b94c2c..d22ee7e 100644 --- a/api/services/dump-service.ts +++ b/api/services/dump-service.ts @@ -5,7 +5,14 @@ import { type Dump, type UpdateDumpRequest, } from "../model/interfaces.ts"; -import { db, dumpApiToRow, dumpRowToApi, isDumpRow } from "../model/db.ts"; +import { + db, + DUMP_SELECT_COLUMNS as SELECT_COLS, + DUMP_SELECT_COLUMNS_ALIASED as SELECT_COLS_ALIASED, + dumpApiToRow, + dumpRowToApi, + isDumpRow, +} from "../model/db.ts"; import { fetchRichContent, isValidHttpUrl } from "./rich-content-service.ts"; import { broadcastDumpDeleted, @@ -40,16 +47,6 @@ function titleFromUrl(url: string): string { } } -const BASE_COLS = - "id, kind, title, slug, comment, user_id, created_at, updated_at, url, rich_content, file_name, file_mime, file_size, vote_count, is_private, custom_thumbnail_mime"; - -const SELECT_COLS = `${BASE_COLS}, - (SELECT COUNT(*) FROM comments WHERE dump_id = dumps.id AND deleted = 0) as comment_count`; - -const SELECT_COLS_ALIASED = - "d.id, d.kind, d.title, d.slug, d.comment, d.user_id, d.created_at, d.updated_at, d.url, d.rich_content, d.file_name, d.file_mime, d.file_size, d.vote_count, d.is_private, d.custom_thumbnail_mime," + - " (SELECT COUNT(*) FROM comments WHERE dump_id = d.id AND deleted = 0) as comment_count"; - export async function createUrlDump( request: CreateUrlDumpRequest, userId: string, diff --git a/api/services/follow-service.ts b/api/services/follow-service.ts index 550a0e9..38fab09 100644 --- a/api/services/follow-service.ts +++ b/api/services/follow-service.ts @@ -12,6 +12,7 @@ import { } from "./notification-service.ts"; import { db, + DUMP_SELECT_COLUMNS_ALIASED as SELECT_COLS_ALIASED, dumpRowToApi, isDumpRow, isFollowRow, @@ -21,12 +22,6 @@ import { userRowToApi, } from "../model/db.ts"; -// Mirrors dump-service SELECT_COLS_ALIASED — kept local to avoid circular imports -const SELECT_COLS_ALIASED = - "d.id, d.kind, d.title, d.slug, d.comment, d.user_id, d.created_at, d.updated_at, d.url, d.rich_content, " + - "d.file_name, d.file_mime, d.file_size, d.vote_count, d.is_private, d.custom_thumbnail_mime," + - " (SELECT COUNT(*) FROM comments WHERE dump_id = d.id AND deleted = 0) as comment_count"; - // ── Follow / unfollow a user ────────────────────────────────────────────────── export function followUser(followerId: string, followedUserId: string): void { diff --git a/api/services/playlist-service.ts b/api/services/playlist-service.ts index 6bf46cc..bb79d76 100644 --- a/api/services/playlist-service.ts +++ b/api/services/playlist-service.ts @@ -10,6 +10,7 @@ import { } from "../model/interfaces.ts"; import { db, + DUMP_COLUMNS_ALIASED, dumpRowToApi, isDumpRow, isPlaylistRow, @@ -28,9 +29,6 @@ import { import { makeSlug, UUID_RE } from "../lib/slugify.ts"; import { linkAttachments } from "./attachment-service.ts"; -const DUMP_SELECT_COLS = - "id, kind, title, slug, comment, user_id, created_at, updated_at, url, rich_content, file_name, file_mime, file_size, vote_count, is_private, custom_thumbnail_mime"; - const PLAYLIST_SELECT = `p.*, u.username as owner_username, (SELECT COUNT(*) FROM playlist_dumps pd WHERE pd.playlist_id = p.id) as dump_count FROM playlists p LEFT JOIN users u ON u.id = p.user_id`; @@ -91,12 +89,11 @@ export function getPlaylist( throw new APIException(APIErrorCode.NOT_FOUND, 404, "Playlist not found"); } - const dumpCols = DUMP_SELECT_COLS.split(", ").map((c) => `d.${c}`).join(", "); const isOwner = requestingUserId === playlist.userId; // For public playlists (or when viewed by non-owner), filter out private dumps const rows = db.prepare( - `SELECT ${dumpCols}, + `SELECT ${DUMP_COLUMNS_ALIASED}, (SELECT COUNT(*) FROM comments WHERE dump_id = d.id AND deleted = 0) as comment_count FROM dumps d INNER JOIN playlist_dumps pd ON d.id = pd.dump_id diff --git a/src/App.tsx b/src/App.tsx index e5dcab6..86d5cc5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,5 @@ import { lazy, Suspense } from "react"; -import { BrowserRouter, Route, Routes } from "react-router"; +import { BrowserRouter, Navigate, Route, Routes } from "react-router"; import { RestrictedGuest } from "./pages/RestrictedGuest.tsx"; import { RestrictedLoggedIn } from "./pages/RestrictedLoggedIn.tsx"; @@ -70,7 +70,8 @@ function AppRoutes() { return ( - } /> + } /> + } /> } /> } /> + } + /> } /> } /> } /> } /> + } /> } />
- + 🚚 {" "} {document.querySelector('meta[name="site-name"]') diff --git a/src/components/ChangePasswordModal.tsx b/src/components/ChangePasswordModal.tsx index dee090f..09d6a27 100644 --- a/src/components/ChangePasswordModal.tsx +++ b/src/components/ChangePasswordModal.tsx @@ -24,7 +24,7 @@ export function ChangePasswordModal({ onClose }: ChangePasswordModalProps) { const tooShort = newPassword.length > 0 && newPassword.length < VALIDATION.PASSWORD_MIN; - const handleSubmit = async (e: React.FormEvent) => { + const handleSubmit = async (e: React.SubmitEvent) => { e.preventDefault(); if (mismatch || tooShort || !currentPassword || !newPassword) return; diff --git a/src/components/DumpCreateModal.tsx b/src/components/DumpCreateModal.tsx index 57687d2..acac827 100644 --- a/src/components/DumpCreateModal.tsx +++ b/src/components/DumpCreateModal.tsx @@ -188,7 +188,7 @@ export function DumpCreateModal( return () => globalThis.removeEventListener("paste", handler); }, []); - const handleSubmit = async (e: React.SubmitEvent) => { + const handleSubmit = async (e: React.SubmitEvent) => { e.preventDefault(); if (comment.length > VALIDATION.DUMP_COMMENT_MAX) return; setSubmitState({ status: "submitting" }); diff --git a/src/components/FeedTabBar.tsx b/src/components/FeedTabBar.tsx index a113c6f..a35deb0 100644 --- a/src/components/FeedTabBar.tsx +++ b/src/components/FeedTabBar.tsx @@ -1,16 +1,12 @@ -import { useLocation, useNavigate } from "react-router"; import { Trans } from "@lingui/react/macro"; import { useAuth } from "../hooks/useAuth.ts"; -import { type FeedTab, VALID_TABS } from "../config/feedTabs.ts"; +import { type FeedTab, FEED_TABS } from "../config/feedTabs.ts"; +import { useTabParam } from "../hooks/useTabParam.ts"; import { TabBar } from "./TabBar.tsx"; export function FeedTabBar() { - const location = useLocation(); - const navigate = useNavigate(); const { user } = useAuth(); - - const rawTab = new URLSearchParams(location.search).get("tab") ?? "hot"; - const tab: FeedTab = VALID_TABS.has(rawTab) ? (rawTab as FeedTab) : "hot"; + const [tab, setTab] = useTabParam(FEED_TABS, "hot"); const tabs = [ { key: "hot" as FeedTab, label: Hot }, @@ -25,7 +21,7 @@ export function FeedTabBar() { navigate(`/?tab=${t}`, { replace: true })} + onChange={setTab} /> ); } diff --git a/src/components/SearchBar.tsx b/src/components/SearchBar.tsx index 9d7ab58..a2e11f1 100644 --- a/src/components/SearchBar.tsx +++ b/src/components/SearchBar.tsx @@ -1,4 +1,4 @@ -import { type FormEvent, useEffect, useRef, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { useNavigate } from "react-router"; import { t } from "@lingui/core/macro"; @@ -41,11 +41,11 @@ export function SearchBar( } } - function handleSubmit(e: FormEvent) { + function handleSubmit(e: React.SubmitEvent) { e.preventDefault(); const q = value.trim(); if (!q) return; - navigate(`/search?q=${encodeURIComponent(q)}&tab=dumps`); + navigate(`/search?q=${encodeURIComponent(q)}`); if (collapsible || isControlled) { setExpanded(false); setValue(""); diff --git a/src/config/feedTabs.ts b/src/config/feedTabs.ts index 9db1a96..2695db1 100644 --- a/src/config/feedTabs.ts +++ b/src/config/feedTabs.ts @@ -1,3 +1,2 @@ export const FEED_TABS = ["hot", "new", "journal", "followed"] as const; export type FeedTab = (typeof FEED_TABS)[number]; -export const VALID_TABS = new Set(FEED_TABS); diff --git a/src/hooks/useTabParam.ts b/src/hooks/useTabParam.ts new file mode 100644 index 0000000..6b363cb --- /dev/null +++ b/src/hooks/useTabParam.ts @@ -0,0 +1,47 @@ +import { useCallback } from "react"; +import { useLocation, useNavigate } from "react-router"; + +const SEP = "/~/"; + +/** + * Reflects the active tab in the URL path using a `/~/` delimiter, e.g. + * `/users/vincent/~/playlists` or `/~/new`. The segment before `/~/` is the + * base path and the segment after is the tab. When the base is non-empty the + * default tab collapses to the clean base path (`/users/vincent`); at the root + * the base is empty, so the default stays explicit (`/~/hot`). Generic and + * path-agnostic: the base is derived from the current location, so the same + * hook works for any route. + * + * Returns the validated active tab (falling back to `defaultTab`) and a setter + * that navigates to the matching path (replacing history). + */ +export function useTabParam( + validTabs: Iterable, + defaultTab: T, +): [T, (tab: T) => void] { + const location = useLocation(); + const navigate = useNavigate(); + + const valid = new Set(validTabs as Iterable); + const path = location.pathname; + const sepAt = path.indexOf(SEP); + + let base = sepAt === -1 ? path : path.slice(0, sepAt); + if (base.endsWith("/")) base = base.slice(0, -1); // root "/" → "" + + const raw = sepAt === -1 + ? null + : path.slice(sepAt + SEP.length).replace(/\/$/, ""); + const tab = (raw && valid.has(raw) ? raw : defaultTab) as T; + + const setTab = useCallback( + (t: T) => { + const target = t === defaultTab && base ? base : `${base}${SEP}${t}`; + // Preserve any unrelated query string (e.g. the search page's `?q=`). + navigate(`${target}${location.search}`, { replace: true }); + }, + [navigate, base, defaultTab, location.search], + ); + + return [tab, setTab]; +} diff --git a/src/locales/en.po b/src/locales/en.po index 24f6406..8aa93bc 100644 --- a/src/locales/en.po +++ b/src/locales/en.po @@ -42,7 +42,7 @@ msgstr "{days}d ago" msgid "{hrs}h ago" msgstr "{hrs}h ago" -#: src/pages/Search.tsx:182 +#: src/pages/Search.tsx:176 msgid "{label} ({count})" msgstr "{label} ({count})" @@ -55,7 +55,7 @@ msgid "{visibleCount, plural, one {# comment} other {# comments}}" msgstr "{visibleCount, plural, one {# comment} other {# comments}}" #: src/pages/PlaylistDetail.tsx:617 -#: src/pages/UserPublicProfile.tsx:753 +#: src/pages/UserPublicProfile.tsx:766 msgid "← Back" msgstr "← Back" @@ -71,7 +71,7 @@ msgstr "← Back to all dumps" msgid "← Back to profile" msgstr "← Back to profile" -#: src/pages/UserPublicProfile.tsx:101 +#: src/pages/UserPublicProfile.tsx:102 msgid "+ Invite someone" msgstr "+ Invite someone" @@ -142,7 +142,7 @@ msgstr "a comment" msgid "a post" msgstr "a post" -#: src/pages/UserPublicProfile.tsx:1204 +#: src/pages/UserPublicProfile.tsx:1217 msgid "Account" msgstr "Account" @@ -150,7 +150,7 @@ msgstr "Account" msgid "Add a comment…" msgstr "Add a comment…" -#: src/pages/UserPublicProfile.tsx:871 +#: src/pages/UserPublicProfile.tsx:884 msgid "Add email…" msgstr "Add email…" @@ -171,7 +171,7 @@ msgstr "All {0, plural, one {# upvoted dump} other {# upvoted dumps}} loaded." msgid "Already have an account? <0>Log in" msgstr "Already have an account? <0>Log in" -#: src/pages/UserPublicProfile.tsx:1223 +#: src/pages/UserPublicProfile.tsx:1236 msgid "Appearance" msgstr "Appearance" @@ -181,7 +181,7 @@ msgstr "Appearance" msgid "At least {0} characters" msgstr "At least {0} characters" -#: src/pages/UserPublicProfile.tsx:1257 +#: src/pages/UserPublicProfile.tsx:1270 msgid "Auto" msgstr "Auto" @@ -205,8 +205,8 @@ msgstr "Can't connect to the live updates server. Upvotes and notifications may #: src/pages/Dump.tsx:355 #: src/pages/DumpEdit.tsx:391 #: src/pages/PlaylistDetail.tsx:686 -#: src/pages/UserPublicProfile.tsx:850 -#: src/pages/UserPublicProfile.tsx:932 +#: src/pages/UserPublicProfile.tsx:863 +#: src/pages/UserPublicProfile.tsx:945 msgid "Cancel" msgstr "Cancel" @@ -218,7 +218,7 @@ msgstr "Cancel removal" msgid "Cancel search" msgstr "Cancel search" -#: src/pages/UserPublicProfile.tsx:780 +#: src/pages/UserPublicProfile.tsx:793 msgid "Change avatar" msgstr "Change avatar" @@ -227,7 +227,7 @@ msgstr "Change avatar" msgid "Change password" msgstr "Change password" -#: src/pages/UserPublicProfile.tsx:1216 +#: src/pages/UserPublicProfile.tsx:1229 msgid "Change password…" msgstr "Change password…" @@ -240,7 +240,7 @@ msgstr "Checking invite…" msgid "Close" msgstr "Close" -#: src/pages/UserPublicProfile.tsx:1249 +#: src/pages/UserPublicProfile.tsx:1262 msgid "Color scheme" msgstr "Color scheme" @@ -249,11 +249,11 @@ msgstr "Color scheme" msgid "Confirm new password" msgstr "Confirm new password" -#: src/pages/UserPublicProfile.tsx:92 +#: src/pages/UserPublicProfile.tsx:93 msgid "Copied!" msgstr "Copied!" -#: src/pages/UserPublicProfile.tsx:92 +#: src/pages/UserPublicProfile.tsx:93 msgid "Copy" msgstr "Copy" @@ -298,7 +298,7 @@ msgstr "Creating…" msgid "Current password" msgstr "Current password" -#: src/pages/UserPublicProfile.tsx:1271 +#: src/pages/UserPublicProfile.tsx:1284 msgid "Dark" msgstr "Dark" @@ -362,15 +362,15 @@ msgstr "Dump it" msgid "Dumped!" msgstr "Dumped!" -#: src/pages/Search.tsx:178 +#: src/pages/Search.tsx:172 #: src/pages/UserDumps.tsx:107 -#: src/pages/UserPublicProfile.tsx:976 +#: src/pages/UserPublicProfile.tsx:989 msgid "Dumps" msgstr "Dumps" #. placeholder {0}: dumps.items.length #. placeholder {1}: dumps.hasMore ? "+" : "" -#: src/pages/UserPublicProfile.tsx:993 +#: src/pages/UserPublicProfile.tsx:1006 msgid "Dumps ({0}{1})" msgstr "Dumps ({0}{1})" @@ -414,7 +414,7 @@ msgstr "Editing" msgid "Email address" msgstr "Email address" -#: src/pages/Search.tsx:211 +#: src/pages/Search.tsx:205 msgid "Enter a query to search." msgstr "Enter a query to search." @@ -427,9 +427,9 @@ msgstr "Failed to change password" msgid "Failed to create playlist" msgstr "Failed to create playlist" -#: src/pages/UserPublicProfile.tsx:73 -#: src/pages/UserPublicProfile.tsx:76 -#: src/pages/UserPublicProfile.tsx:104 +#: src/pages/UserPublicProfile.tsx:74 +#: src/pages/UserPublicProfile.tsx:77 +#: src/pages/UserPublicProfile.tsx:105 msgid "Failed to generate invite" msgstr "Failed to generate invite" @@ -438,9 +438,9 @@ msgstr "Failed to generate invite" #: src/pages/index/JournalFeed.tsx:48 #: src/pages/index/NewFeed.tsx:36 #: src/pages/Notifications.tsx:367 -#: src/pages/UserPublicProfile.tsx:1095 -#: src/pages/UserPublicProfile.tsx:1137 -#: src/pages/UserPublicProfile.tsx:1182 +#: src/pages/UserPublicProfile.tsx:1108 +#: src/pages/UserPublicProfile.tsx:1150 +#: src/pages/UserPublicProfile.tsx:1195 msgid "Failed to load" msgstr "Failed to load" @@ -457,10 +457,10 @@ msgid "Failed to post reply" msgstr "Failed to post reply" #: src/pages/PlaylistDetail.tsx:795 -#: src/pages/UserPublicProfile.tsx:688 -#: src/pages/UserPublicProfile.tsx:726 -#: src/pages/UserPublicProfile.tsx:855 -#: src/pages/UserPublicProfile.tsx:935 +#: src/pages/UserPublicProfile.tsx:701 +#: src/pages/UserPublicProfile.tsx:739 +#: src/pages/UserPublicProfile.tsx:868 +#: src/pages/UserPublicProfile.tsx:948 msgid "Failed to save" msgstr "Failed to save" @@ -468,7 +468,7 @@ msgstr "Failed to save" msgid "Failed to save edit" msgstr "Failed to save edit" -#: src/pages/UserPublicProfile.tsx:880 +#: src/pages/UserPublicProfile.tsx:893 msgid "Failed to update avatar" msgstr "Failed to update avatar" @@ -510,8 +510,8 @@ msgstr "Follow some public playlists to see their dumps here." msgid "Follow some users to see their dumps here." msgstr "Follow some users to see their dumps here." -#: src/components/FeedTabBar.tsx:20 -#: src/pages/UserPublicProfile.tsx:978 +#: src/components/FeedTabBar.tsx:16 +#: src/pages/UserPublicProfile.tsx:991 msgid "Followed" msgstr "Followed" @@ -521,13 +521,13 @@ msgstr "Followed" msgid "Followed ({0}{1})" msgstr "Followed ({0}{1})" -#: src/pages/UserPublicProfile.tsx:1126 +#: src/pages/UserPublicProfile.tsx:1139 msgid "Followed playlists" msgstr "Followed playlists" #: src/components/FollowButton.tsx:37 #: src/components/FollowButton.tsx:64 -#: src/pages/UserPublicProfile.tsx:1084 +#: src/pages/UserPublicProfile.tsx:1097 msgid "Following" msgstr "Following" @@ -551,7 +551,7 @@ msgstr "Go home" msgid "Go to login" msgstr "Go to login" -#: src/components/FeedTabBar.tsx:16 +#: src/components/FeedTabBar.tsx:12 msgid "Hot" msgstr "Hot" @@ -567,16 +567,16 @@ msgstr "Invalid invite" msgid "Invalid link" msgstr "Invalid link" -#: src/pages/UserPublicProfile.tsx:799 +#: src/pages/UserPublicProfile.tsx:812 msgid "invited by" msgstr "invited by" -#: src/pages/UserPublicProfile.tsx:979 -#: src/pages/UserPublicProfile.tsx:1171 +#: src/pages/UserPublicProfile.tsx:992 +#: src/pages/UserPublicProfile.tsx:1184 msgid "Invitees" msgstr "Invitees" -#: src/components/FeedTabBar.tsx:18 +#: src/components/FeedTabBar.tsx:14 msgid "Journal" msgstr "Journal" @@ -584,7 +584,7 @@ msgstr "Journal" msgid "just now" msgstr "just now" -#: src/pages/UserPublicProfile.tsx:1264 +#: src/pages/UserPublicProfile.tsx:1277 msgid "Light" msgstr "Light" @@ -614,7 +614,7 @@ msgstr "Loading dump…" #: src/pages/index/HotFeed.tsx:64 #: src/pages/index/JournalFeed.tsx:77 #: src/pages/index/NewFeed.tsx:64 -#: src/pages/Search.tsx:248 +#: src/pages/Search.tsx:242 #: src/pages/UserDumps.tsx:93 #: src/pages/UserPlaylists.tsx:417 #: src/pages/UserPlaylists.tsx:452 @@ -626,7 +626,7 @@ msgstr "Loading more…" msgid "Loading playlist…" msgstr "Loading playlist…" -#: src/pages/UserPublicProfile.tsx:736 +#: src/pages/UserPublicProfile.tsx:749 msgid "Loading profile…" msgstr "Loading profile…" @@ -642,9 +642,9 @@ msgstr "Loading profile…" #: src/pages/Notifications.tsx:439 #: src/pages/UserDumps.tsx:51 #: src/pages/UserPlaylists.tsx:342 -#: src/pages/UserPublicProfile.tsx:1089 -#: src/pages/UserPublicProfile.tsx:1131 -#: src/pages/UserPublicProfile.tsx:1176 +#: src/pages/UserPublicProfile.tsx:1102 +#: src/pages/UserPublicProfile.tsx:1144 +#: src/pages/UserPublicProfile.tsx:1189 #: src/pages/UserUpvoted.tsx:122 msgid "Loading…" msgstr "Loading…" @@ -663,8 +663,8 @@ msgstr "Log in to like" msgid "Log in to vote" msgstr "Log in to vote" -#: src/pages/UserPublicProfile.tsx:757 -#: src/pages/UserPublicProfile.tsx:894 +#: src/pages/UserPublicProfile.tsx:770 +#: src/pages/UserPublicProfile.tsx:907 msgid "Log out" msgstr "Log out" @@ -684,13 +684,13 @@ msgstr "Max 50 MB" msgid "new" msgstr "new" -#: src/components/FeedTabBar.tsx:17 +#: src/components/FeedTabBar.tsx:13 msgid "New" msgstr "New" #: src/components/DumpCreateModal.tsx:302 #: src/pages/UserDumps.tsx:115 -#: src/pages/UserPublicProfile.tsx:1320 +#: src/pages/UserPublicProfile.tsx:1333 msgid "New dump" msgstr "New dump" @@ -708,7 +708,7 @@ msgstr "New playlist" msgid "No dumps in this playlist yet." msgstr "No dumps in this playlist yet." -#: src/pages/Search.tsx:228 +#: src/pages/Search.tsx:222 msgid "No dumps match \"{q}\"." msgstr "No dumps match \"{q}\"." @@ -723,11 +723,11 @@ msgid "No emoji found." msgstr "No emoji found." #: src/pages/UserPlaylists.tsx:439 -#: src/pages/UserPublicProfile.tsx:1144 +#: src/pages/UserPublicProfile.tsx:1157 msgid "No followed playlists yet." msgstr "No followed playlists yet." -#: src/pages/UserPublicProfile.tsx:1189 +#: src/pages/UserPublicProfile.tsx:1202 msgid "No invitees yet." msgstr "No invitees yet." @@ -735,28 +735,28 @@ msgstr "No invitees yet." msgid "No one yet." msgstr "No one yet." -#: src/pages/Search.tsx:287 +#: src/pages/Search.tsx:281 msgid "No playlists match \"{q}\"." msgstr "No playlists match \"{q}\"." #: src/components/PlaylistMembershipPanel.tsx:34 #: src/pages/UserPlaylists.tsx:397 -#: src/pages/UserPublicProfile.tsx:1055 +#: src/pages/UserPublicProfile.tsx:1068 msgid "No playlists yet." msgstr "No playlists yet." -#: src/pages/Search.tsx:261 +#: src/pages/Search.tsx:255 msgid "No users match \"{q}\"." msgstr "No users match \"{q}\"." -#: src/pages/UserPublicProfile.tsx:1102 +#: src/pages/UserPublicProfile.tsx:1115 msgid "Not following anyone yet." msgstr "Not following anyone yet." #: src/pages/Notifications.tsx:374 #: src/pages/UserDumps.tsx:125 -#: src/pages/UserPublicProfile.tsx:1331 -#: src/pages/UserPublicProfile.tsx:1453 +#: src/pages/UserPublicProfile.tsx:1344 +#: src/pages/UserPublicProfile.tsx:1466 #: src/pages/UserUpvoted.tsx:194 msgid "Nothing here yet." msgstr "Nothing here yet." @@ -779,7 +779,7 @@ msgid "or <0>browse files" msgstr "or <0>browse files" #: src/pages/UserLogin.tsx:106 -#: src/pages/UserPublicProfile.tsx:1209 +#: src/pages/UserPublicProfile.tsx:1222 msgid "Password" msgstr "Password" @@ -803,15 +803,15 @@ msgstr "Passwords do not match" #: src/components/AppHeader.tsx:85 #: src/components/UserMenu.tsx:62 -#: src/pages/Search.tsx:181 +#: src/pages/Search.tsx:175 #: src/pages/UserPlaylists.tsx:368 -#: src/pages/UserPublicProfile.tsx:977 +#: src/pages/UserPublicProfile.tsx:990 msgid "Playlists" msgstr "Playlists" #. placeholder {0}: playlists.items.length #. placeholder {1}: playlists.hasMore ? "+" : "" -#: src/pages/UserPublicProfile.tsx:1024 +#: src/pages/UserPublicProfile.tsx:1037 msgid "Playlists ({0}{1})" msgstr "Playlists ({0}{1})" @@ -930,8 +930,8 @@ msgstr "Retry" #: src/pages/Dump.tsx:347 #: src/pages/DumpEdit.tsx:400 #: src/pages/PlaylistDetail.tsx:679 -#: src/pages/UserPublicProfile.tsx:842 -#: src/pages/UserPublicProfile.tsx:924 +#: src/pages/UserPublicProfile.tsx:855 +#: src/pages/UserPublicProfile.tsx:937 msgid "Save" msgstr "Save" @@ -940,8 +940,8 @@ msgstr "Save" #: src/pages/Dump.tsx:347 #: src/pages/PlaylistDetail.tsx:679 #: src/pages/ResetPassword.tsx:152 -#: src/pages/UserPublicProfile.tsx:841 -#: src/pages/UserPublicProfile.tsx:924 +#: src/pages/UserPublicProfile.tsx:854 +#: src/pages/UserPublicProfile.tsx:937 msgid "Saving…" msgstr "Saving…" @@ -954,11 +954,11 @@ msgstr "Search" msgid "Search dumps, users, playlists…" msgstr "Search dumps, users, playlists…" -#: src/pages/Search.tsx:222 +#: src/pages/Search.tsx:216 msgid "Search failed" msgstr "Search failed" -#: src/pages/Search.tsx:217 +#: src/pages/Search.tsx:211 msgid "Searching…" msgstr "Searching…" @@ -979,7 +979,7 @@ msgstr "Server unreachable" msgid "Set new password" msgstr "Set new password" -#: src/pages/UserPublicProfile.tsx:981 +#: src/pages/UserPublicProfile.tsx:994 msgid "Settings" msgstr "Settings" @@ -987,7 +987,7 @@ msgstr "Settings" msgid "Something went wrong" msgstr "Something went wrong" -#: src/pages/UserPublicProfile.tsx:1228 +#: src/pages/UserPublicProfile.tsx:1241 msgid "Style" msgstr "Style" @@ -1042,7 +1042,7 @@ msgstr "Unfollow playlist" msgid "Unknown error" msgstr "Unknown error" -#: src/pages/UserPublicProfile.tsx:657 +#: src/pages/UserPublicProfile.tsx:670 msgid "Upload failed" msgstr "Upload failed" @@ -1060,7 +1060,7 @@ msgstr "Upvoted" #. placeholder {0}: votes.items.length #. placeholder {1}: votes.hasMore ? "+" : "" -#: src/pages/UserPublicProfile.tsx:1004 +#: src/pages/UserPublicProfile.tsx:1017 msgid "Upvoted ({0}{1})" msgstr "Upvoted ({0}{1})" @@ -1082,15 +1082,15 @@ msgstr "User menu" msgid "Username" msgstr "Username" -#: src/pages/Search.tsx:180 +#: src/pages/Search.tsx:174 msgid "Users" msgstr "Users" -#: src/pages/UserPublicProfile.tsx:1074 -#: src/pages/UserPublicProfile.tsx:1117 -#: src/pages/UserPublicProfile.tsx:1159 -#: src/pages/UserPublicProfile.tsx:1352 -#: src/pages/UserPublicProfile.tsx:1483 +#: src/pages/UserPublicProfile.tsx:1087 +#: src/pages/UserPublicProfile.tsx:1130 +#: src/pages/UserPublicProfile.tsx:1172 +#: src/pages/UserPublicProfile.tsx:1365 +#: src/pages/UserPublicProfile.tsx:1496 msgid "View all →" msgstr "View all →" @@ -1103,8 +1103,8 @@ msgstr "View dump →" msgid "What makes it worth it?" msgstr "What makes it worth it?" -#: src/pages/UserPublicProfile.tsx:912 -#: src/pages/UserPublicProfile.tsx:961 +#: src/pages/UserPublicProfile.tsx:925 +#: src/pages/UserPublicProfile.tsx:974 msgid "Who am I?" msgstr "Who am I?" @@ -1129,7 +1129,7 @@ msgstr "You'll be notified when someone follows your playlists, upvotes your dum #: src/pages/index/HotFeed.tsx:69 #: src/pages/index/JournalFeed.tsx:82 #: src/pages/index/NewFeed.tsx:69 -#: src/pages/Search.tsx:253 +#: src/pages/Search.tsx:247 #: src/pages/UserDumps.tsx:98 #: src/pages/UserPlaylists.tsx:422 #: src/pages/UserPlaylists.tsx:457 diff --git a/src/locales/fr.js b/src/locales/fr.js index 559203c..39df087 100644 --- a/src/locales/fr.js +++ b/src/locales/fr.js @@ -1 +1 @@ -/*eslint-disable*/module.exports={messages:JSON.parse("{\"-K9EZb\":[\"Ajouter un e-mail…\"],\"-OxI15\":[\"Collections suivies\"],\"-Ya-b9\":[\"Enregistrement échoué\"],\"-siMqD\":[\"Journal\"],\"1CalO6\":[\"Style\"],\"1HfJWf\":[\"Rechercher des recos, utilisateurs, collections…\"],\"1cbYY_\":[\"Votés (\",[\"0\"],[\"1\"],\")\"],\"1njn7W\":[\"Clair\"],\"1utXA6\":[\"Recos\"],\"26iNma\":[\"Publier le commentaire\"],\"29VNqC\":[\"Erreur inconnue\"],\"2Hlmdt\":[\"Écrire une réponse…\"],\"2ygf_L\":[\"← Retour\"],\"3KKSM4\":[\"privé\"],\"3yfh3D\":[\"<0>\",[\"0\"],\" a suivi votre collection <1>\",[\"1\"],\"\"],\"49voTZ\":[[\"label\"],\" (\",[\"count\"],\")\"],\"4B6w_o\":[\"Recommandé !\"],\"4GKuCs\":[\"Connexion échouée\"],\"4HH9iB\":[\"Aucun abonnement pour le moment.\"],\"4RtQ1k\":[\"Ne plus suivre \",[\"targetUsername\"]],\"4c-qBx\":[\"Votre mot de passe a été modifié. Vous pouvez maintenant vous connecter.\"],\"4yj9xV\":[\"Les mises à jour en direct sont temporairement interrompues. Tentative de reconnexion…\"],\"5TviPn\":[\"Annuler la recherche\"],\"5cC8f2\":[\"Modifié le \",[\"0\"]],\"5oD9f_\":[\"Plus tôt\"],\"6Qly-0\":[\"un commentaire\"],\"6gRgw8\":[\"Réessayer\"],\"7PHCIN\":[\"Annuler la suppression\"],\"7d1a0d\":[\"Public\"],\"7sNhEz\":[\"Nom d'utilisateur\"],\"8Ug9jB\":[\"Apparentés\"],\"8ZsakT\":[\"Mot de passe\"],\"8pxhI8\":[\"Veuillez sélectionner un fichier.\"],\"9BruTc\":[\"Pourquoi ?\"],\"9l4qcT\":[\"Déposez un fichier de remplacement ici\"],\"9uI_rE\":[\"Annuler\"],\"9xDZu_\":[\"Impossible de charger.\"],\"A0y396\":[\"+ Inviter quelqu'un\"],\"A1taO8\":[\"Rechercher\"],\"AQbgNR\":[\"Suivre \",[\"targetUsername\"]],\"ATGYL1\":[\"Adresse e-mail\"],\"AZctoV\":[[\"0\",\"plural\",{\"one\":[\"#\",\" commentaire\"],\"other\":[\"#\",\" commentaires\"]}]],\"Ade-6d\":[\"Mises à jour en direct indisponibles.\"],\"AeXO77\":[\"Compte\"],\"CI50ct\":[\"Voté\"],\"Cj24wt\":[\"Inscription…\"],\"DPfwMq\":[\"Terminé\"],\"DdeHXH\":[\"Supprimer cette reco ? Cette action est irréversible.\"],\"Dp1JhP\":[\"<0>\",[\"0\"],\" a voté pour <1>\",[\"1\"],\"\"],\"ECiS12\":[\"Voir la reco →\"],\"ExR0Fr\":[\"L'URL est obligatoire.\"],\"F1O9Ep\":[\"Impossible de contacter le serveur\"],\"F5Js1v\":[\"Ne plus suivre la collection\"],\"FgAxTj\":[\"Se déconnecter\"],\"Fxf4jq\":[\"Description (facultatif)\"],\"GNSsCc\":[\"Impossible de créer la collection\"],\"GbqhrN\":[[\"0\"],\"–\",[\"1\"],\" caractères : lettres, chiffres ou tirets bas\"],\"GptGxg\":[\"Changer le mot de passe\"],\"H4o4sk\":[\"Personne pour le moment.\"],\"H8pzW-\":[\"Impossible de mettre à jour l'avatar\"],\"HTLDA4\":[\"Chargement…\"],\"I-x669\":[\"Invités\"],\"IZX7TO\":[\"Impossible de générer une invitation\"],\"IagCbF\":[\"URL\"],\"ImOQa9\":[\"Répondre\"],\"J2eKUI\":[\"Fichier\"],\"JJ-Bhk\":[\"Votre adresse e-mail\"],\"JRQitQ\":[\"Confirmer le nouveau mot de passe\"],\"JXr41k\":[\"<0>\",[\"0\"],\" a commenté sur <1>\",[\"1\"],\"\"],\"Jd58Fo\":[\"Tendances\"],\"Jf0PuK\":[\"Aller à la connexion\"],\"KDGWg5\":[\"Retirer de la collection\"],\"K_F6pa\":[\"Enregistrement…\"],\"L-rMC9\":[\"Réinitialiser par défaut\"],\"LLyMkV\":[\"Suivies (\",[\"0\"],[\"1\"],\")\"],\"LPAv9E\":[\"il y a \",[\"days\"],\"j\"],\"MHrjPM\":[\"Titre\"],\"MKEPCY\":[\"Suivre\"],\"Mq2B8E\":[\"il y a \",[\"mins\"],\"min\"],\"Nn4kr3\":[\"+ Nouvelle reco\"],\"Oprv1v\":[\"Mot de passe (min. \",[\"0\"],\" caractères)\"],\"Oz0N9s\":[\"nouveau\"],\"PiH3UR\":[\"Copié !\"],\"Pn2B7_\":[\"Mot de passe actuel\"],\"Pwqkdw\":[\"Chargement…\"],\"Q6n4F4\":[\"Actualiser les métadonnées\"],\"QKsaQr\":[\"ou <0>parcourir les fichiers\"],\"QLtPBd\":[\"Aucune reco dans cette collection pour l'instant.\"],\"R9Khdg\":[\"Auto\"],\"RCcPrX\":[\"Supprimer cette collection ? Cette action est irréversible.\"],\"RTksSy\":[\"<0>\",[\"0\"],\" a commencé à vous suivre\"],\"RaKjrM\":[\"Impossible d'enregistrer la modification\"],\"RcUHRT\":[\"Suivi\"],\"Rrp6-J\":[\"Se connecter pour aimer\"],\"SBTElJ\":[\"Recherche…\"],\"Sad2tK\":[\"Envoi…\"],\"StovX6\":[\"Rien à voir, circulez.\"],\"Sxm8rQ\":[\"Utilisateurs\"],\"T9bjWt\":[\"<0>\",[\"0\"],\" a été ajouté à <1>\",[\"1\"],\"\"],\"TM1ZbA\":[\"Collections (\",[\"0\"],[\"1\"],\")\"],\"TN382O\":[\"Lien invalide\"],\"Tv9vbB\":[\"Suivre la collection\"],\"Tz0i8g\":[\"Paramètres\"],\"UNMVei\":[\"Mot de passe oublié ?\"],\"UOZith\":[\"Publication échouée\"],\"URAieT\":[\"Se connecter pour voter\"],\"UTiUFs\":[\"Récupération…\"],\"VCoEm-\":[\"Retour à la connexion\"],\"V_e7nf\":[\"Définir un nouveau mot de passe\"],\"VnNJbN\":[\"De collections\"],\"VyTYmS\":[\"Changer l'avatar\"],\"W9FRBT\":[\"Aimer\"],\"WhimMi\":[\"Échec de la réinitialisation\"],\"WpXcBJ\":[\"Rien ici pour l'instant.\"],\"WtkMN8\":[\"Toutes les \",[\"0\",\"plural\",{\"one\":[\"#\",\" reco votée\"],\"other\":[\"#\",\" recos votées\"]}],\" chargées.\"],\"XJy2oN\":[\"Connexion…\"],\"Xan6QP\":[\"Nouvelle reco\"],\"XgRtUf\":[\"Impossible de changer le mot de passe\"],\"Xi0Mn4\":[\"← Retour au profil\"],\"XnL-Eu\":[\"Aucun utilisateur ne correspond à « \",[\"q\"],\" ».\"],\"Xs2Lez\":[\"Ce lien de réinitialisation est absent ou malformé.\"],\"YK1Dhc\":[\"une publication\"],\"Ye9RMF\":[\"<0>\",[\"0\"],\" a aimé votre commentaire sur <1>\",[\"1\"],\"\"],\"YpkCca\":[\"Pas encore de collections suivies.\"],\"ZBdbv9\":[\"Modifier le titre\"],\"ZCpU0u\":[\"Aucune collection ne correspond à « \",[\"q\"],\" ».\"],\"ZmD2o6\":[\"Créer et ajouter\"],\"_3O5R_\":[\"Échec de la demande\"],\"_84wxb\":[\"Toutes les \",[\"0\",\"plural\",{\"one\":[\"#\",\" reco\"],\"other\":[\"#\",\" recos\"]}],\" chargées.\"],\"_DwR-n\":[\"Création…\"],\"_R_sGB\":[\"Mot de passe modifié avec succès.\"],\"_aept4\":[\"Publier la réponse\"],\"_nT6AE\":[\"Nouveau mot de passe\"],\"_t4W-i\":[\"De personnes\"],\"aAIQg2\":[\"Apparence\"],\"aDvLhk\":[\"Ajouter un commentaire…\"],\"b3Thhd\":[\"Envoi échoué\"],\"b8XMJ8\":[[\"visibleCount\",\"plural\",{\"one\":[\"#\",\" commentaire\"],\"other\":[\"#\",\" commentaires\"]}]],\"bQhwn-\":[\"Chargement de la collection…\"],\"cILfnJ\":[\"Supprimer le fichier\"],\"cYP9Sb\":[\"+ Collection\"],\"cbeBbZ\":[\"Au moins \",[\"0\"],\" caractères\"],\"cnGeoo\":[\"Supprimer\"],\"d8DZWS\":[\"Ouvrir la recherche\"],\"dAs22m\":[\"Remplacer le fichier\"],\"dEgA5A\":[\"Annuler\"],\"dMizp8\":[\"Nouvelle collection\"],\"eFSqvc\":[\"Impossible de publier la réponse\"],\"ePK91l\":[\"Modifier\"],\"eaUTwS\":[\"Envoyer le lien de réinitialisation\"],\"ecUA8p\":[\"Aujourd'hui\"],\"ef9nPf\":[\"Chargement de la reco…\"],\"en9o7K\":[\"Impossible de publier le commentaire\"],\"etFQQS\":[\"Pourquoi on en voudrait ?\"],\"fC6mXb\":[\"Voter\"],\"fI-mNw\":[\"Collections\"],\"f_akpP\":[\"Max 50 Mo\"],\"fgLNSM\":[\"S'inscrire\"],\"gANddk\":[\"Envoi…\"],\"gGx5tM\":[\"Modification\"],\"gIQQwD\":[\"Chargement échoué\"],\"gLfZlz\":[\"Ajouter à la collection\"],\"gjJ-sb\":[\"Impossible de se connecter au serveur de mises à jour en direct. Les votes et les notifications pourraient ne pas se synchroniser avant la reconnexion.\"],\"hBuUKa\":[\"Changer le mot de passe…\"],\"hD7w09\":[\"Vous avez tout lu, tout vu, tout bu.\"],\"hJSliC\":[\"<0>\",[\"0\"],\" a publié <1>\",[\"1\"],\"\"],\"hYgDIe\":[\"Créer\"],\"he3ygx\":[\"Copier\"],\"i7K_Te\":[\"Qui suis-je ?\"],\"iDNBZe\":[\"Notifications\"],\"iWpEwy\":[\"Accueil\"],\"ijVyoK\":[\"Impossible de contacter le serveur. Veuillez réessayer.\"],\"ipYn7W\":[\"Si cette adresse est enregistrée, vous recevrez un lien de réinitialisation sous peu.\"],\"isRobC\":[\"Nouveau\"],\"jbernk\":[\"Chargement du profil…\"],\"joEmfT\":[\"Serveur inaccessible\"],\"jrZTZl\":[\"Pas encore de recos. Soyez le premier !\"],\"kLttbL\":[\"Inscription échouée\"],\"klOeIX\":[\"Impossible de changer le mot de passe\"],\"lUDifl\":[\"Créées (\",[\"0\"],[\"1\"],\")\"],\"lUanmi\":[\"Vous serez notifié lorsque quelqu'un suit vos collections, vote pour vos recos ou publie du nouveau contenu.\"],\"lY5h1V\":[[\"0\",\"plural\",{\"one\":[\"#\",\" reco\"],\"other\":[\"#\",\" recos\"]}]],\"lcfvr_\":[\"Supprimer ce commentaire ?\"],\"lpIMne\":[\"Les mots de passe ne correspondent pas\"],\"mt6O6E\":[\"C'est un mirage.\"],\"nbm5sI\":[\"Aucune reco ne correspond à « \",[\"q\"],\" ».\"],\"nrjqON\":[\"Vérification de l'invitation…\"],\"nwtY4N\":[\"Une erreur est survenue\"],\"ogtYkT\":[\"Mot de passe mis à jour\"],\"pCpd9p\":[\"<0>\",[\"0\"],\" vous a mentionné dans <1>\",[\"where\"],\"\"],\"pSheLH\":[\"Aucun invité pour le moment.\"],\"pvnfJD\":[\"Sombre\"],\"qIMfNQ\":[\"Supprimer la collection\"],\"qbDAcy\":[\"Recommander\"],\"qgx_78\":[\"Suivez des collections publiques pour voir leurs recos ici.\"],\"qvFa8r\":[\"public\"],\"qvz_Pp\":[\"Reco\"],\"rCbqPX\":[\"Ce lien d'invitation est manquant, expiré ou déjà utilisé.\"],\"rg9pXu\":[\"Recherche échouée\"],\"rtpJqV\":[\"Recos (\",[\"0\"],[\"1\"],\")\"],\"sGeXL3\":[\"Miniature\"],\"sQia9P\":[\"Se connecter\"],\"sTiqbm\":[\"invité par\"],\"sdP5Aa\":[\"[supprimé]\"],\"shHs8T\":[\"Saisissez une recherche.\"],\"smeBfS\":[\"Invitation invalide\"],\"tfDRzk\":[\"Enregistrer\"],\"tvmuQ0\":[\"Thème de couleur\"],\"u1lDX2\":[\"Récupération de l'aperçu…\"],\"uD0qXQ\":[\"Déposez un fichier ici\"],\"uMGUnV\":[\"Pas encore de collections.\"],\"ub1EEL\":[\"modifié \",[\"0\"]],\"vJBF1r\":[\"Publication…\"],\"vLhLLO\":[\"Notifications (\",[\"unreadNotificationCount\"],\" non lues)\"],\"vQMkHu\":[\"Retirer le vote\"],\"vuosjb\":[\"Menu utilisateur\"],\"wXO4Tg\":[\"il y a \",[\"hrs\"],\"h\"],\"wbXKOv\":[\"Fichier trop volumineux (max 50 Mo).\"],\"wixIgH\":[\"Vous avez déjà un compte ? <0>Se connecter\"],\"xEWkgZ\":[\"← Retour à toutes les recos\"],\"xOTzt5\":[\"à l'instant\"],\"xPHtx0\":[\"Lancer la recherche\"],\"xVuNgt\":[\"+ Nouvelle collection\"],\"xc9O_u\":[\"Supprimer la reco\"],\"y6sq5j\":[\"Abonné\"],\"yA_6BX\":[\"Tout voir →\"],\"yBBtRm\":[\"Suivez des utilisateurs pour voir leurs recos ici.\"],\"yQ2kGp\":[\"Charger plus\"],\"y_0uwd\":[\"Hier\"],\"yz7wBu\":[\"Fermer\"],\"z0ROB3\":[\"Retirer le j'aime\"],\"z1uNN0\":[\"Aucun emoji trouvé.\"],\"zVuxvN\":[\"Actualisation…\"],\"zwBp5t\":[\"Privé\"]}")}; \ No newline at end of file +/*eslint-disable*/module.exports={messages:JSON.parse("{\"-K9EZb\":[\"Ajouter un e-mail…\"],\"-OxI15\":[\"Collections suivies\"],\"-Ya-b9\":[\"Enregistrement échoué\"],\"-siMqD\":[\"Journal\"],\"1CalO6\":[\"Style\"],\"1HfJWf\":[\"Rechercher des recos, utilisateurs, collections…\"],\"1cbYY_\":[\"Votés (\",[\"0\"],[\"1\"],\")\"],\"1njn7W\":[\"Clair\"],\"1utXA6\":[\"Recos\"],\"26iNma\":[\"Publier le commentaire\"],\"29VNqC\":[\"Erreur inconnue\"],\"2Hlmdt\":[\"Écrire une réponse…\"],\"2ygf_L\":[\"← Retour\"],\"3KKSM4\":[\"privé\"],\"3yfh3D\":[\"<0>\",[\"0\"],\" a suivi votre collection <1>\",[\"1\"],\"\"],\"49voTZ\":[[\"label\"],\" (\",[\"count\"],\")\"],\"4B6w_o\":[\"Recommandé !\"],\"4GKuCs\":[\"Connexion échouée\"],\"4HH9iB\":[\"Aucun abonnement pour le moment.\"],\"4RtQ1k\":[\"Ne plus suivre \",[\"targetUsername\"]],\"4c-qBx\":[\"Votre mot de passe a été modifié. Vous pouvez maintenant vous connecter.\"],\"4yj9xV\":[\"Les mises à jour en direct sont temporairement interrompues. Tentative de reconnexion…\"],\"5TviPn\":[\"Annuler la recherche\"],\"5cC8f2\":[\"Modifié le \",[\"0\"]],\"5oD9f_\":[\"Plus tôt\"],\"6Qly-0\":[\"un commentaire\"],\"6gRgw8\":[\"Réessayer\"],\"7PHCIN\":[\"Annuler la suppression\"],\"7d1a0d\":[\"Public\"],\"7sNhEz\":[\"Nom d'utilisateur\"],\"8Ug9jB\":[\"Connexe\"],\"8ZsakT\":[\"Mot de passe\"],\"8pxhI8\":[\"Veuillez sélectionner un fichier.\"],\"9BruTc\":[\"Pourquoi ?\"],\"9l4qcT\":[\"Déposez un fichier de remplacement ici\"],\"9uI_rE\":[\"Annuler\"],\"9xDZu_\":[\"Impossible de charger.\"],\"A0y396\":[\"+ Inviter quelqu'un\"],\"A1taO8\":[\"Rechercher\"],\"AQbgNR\":[\"Suivre \",[\"targetUsername\"]],\"ATGYL1\":[\"Adresse e-mail\"],\"AZctoV\":[[\"0\",\"plural\",{\"one\":[\"#\",\" commentaire\"],\"other\":[\"#\",\" commentaires\"]}]],\"Ade-6d\":[\"Mises à jour en direct indisponibles.\"],\"AeXO77\":[\"Compte\"],\"CI50ct\":[\"Voté\"],\"Cj24wt\":[\"Inscription…\"],\"DPfwMq\":[\"Terminé\"],\"DdeHXH\":[\"Supprimer cette reco ? Cette action est irréversible.\"],\"Dp1JhP\":[\"<0>\",[\"0\"],\" a voté pour <1>\",[\"1\"],\"\"],\"ECiS12\":[\"Voir la reco →\"],\"ExR0Fr\":[\"L'URL est obligatoire.\"],\"F1O9Ep\":[\"Impossible de contacter le serveur\"],\"F5Js1v\":[\"Ne plus suivre la collection\"],\"FgAxTj\":[\"Se déconnecter\"],\"Fxf4jq\":[\"Description (facultatif)\"],\"GNSsCc\":[\"Impossible de créer la collection\"],\"GbqhrN\":[[\"0\"],\"–\",[\"1\"],\" caractères : lettres, chiffres ou tirets bas\"],\"GptGxg\":[\"Changer le mot de passe\"],\"H4o4sk\":[\"Personne pour le moment.\"],\"H8pzW-\":[\"Impossible de mettre à jour l'avatar\"],\"HTLDA4\":[\"Chargement…\"],\"I-x669\":[\"Invités\"],\"IZX7TO\":[\"Impossible de générer une invitation\"],\"IagCbF\":[\"URL\"],\"ImOQa9\":[\"Répondre\"],\"J2eKUI\":[\"Fichier\"],\"JJ-Bhk\":[\"Votre adresse e-mail\"],\"JRQitQ\":[\"Confirmer le nouveau mot de passe\"],\"JXr41k\":[\"<0>\",[\"0\"],\" a commenté sur <1>\",[\"1\"],\"\"],\"Jd58Fo\":[\"Tendances\"],\"Jf0PuK\":[\"Aller à la connexion\"],\"KDGWg5\":[\"Retirer de la collection\"],\"K_F6pa\":[\"Enregistrement…\"],\"L-rMC9\":[\"Réinitialiser par défaut\"],\"LLyMkV\":[\"Suivies (\",[\"0\"],[\"1\"],\")\"],\"LPAv9E\":[\"il y a \",[\"days\"],\"j\"],\"MHrjPM\":[\"Titre\"],\"MKEPCY\":[\"Suivre\"],\"Mq2B8E\":[\"il y a \",[\"mins\"],\"min\"],\"Nn4kr3\":[\"+ Nouvelle reco\"],\"Oprv1v\":[\"Mot de passe (min. \",[\"0\"],\" caractères)\"],\"Oz0N9s\":[\"nouveau\"],\"PiH3UR\":[\"Copié !\"],\"Pn2B7_\":[\"Mot de passe actuel\"],\"Pwqkdw\":[\"Chargement…\"],\"Q6n4F4\":[\"Actualiser les métadonnées\"],\"QKsaQr\":[\"ou <0>parcourir les fichiers\"],\"QLtPBd\":[\"Aucune reco dans cette collection pour l'instant.\"],\"R9Khdg\":[\"Auto\"],\"RCcPrX\":[\"Supprimer cette collection ? Cette action est irréversible.\"],\"RTksSy\":[\"<0>\",[\"0\"],\" a commencé à vous suivre\"],\"RaKjrM\":[\"Impossible d'enregistrer la modification\"],\"RcUHRT\":[\"Suivi\"],\"Rrp6-J\":[\"Se connecter pour aimer\"],\"SBTElJ\":[\"Recherche…\"],\"Sad2tK\":[\"Envoi…\"],\"StovX6\":[\"Rien à voir, circulez.\"],\"Sxm8rQ\":[\"Utilisateurs\"],\"T9bjWt\":[\"<0>\",[\"0\"],\" a été ajouté à <1>\",[\"1\"],\"\"],\"TM1ZbA\":[\"Collections (\",[\"0\"],[\"1\"],\")\"],\"TN382O\":[\"Lien invalide\"],\"Tv9vbB\":[\"Suivre la collection\"],\"Tz0i8g\":[\"Paramètres\"],\"UNMVei\":[\"Mot de passe oublié ?\"],\"UOZith\":[\"Publication échouée\"],\"URAieT\":[\"Se connecter pour voter\"],\"UTiUFs\":[\"Récupération…\"],\"VCoEm-\":[\"Retour à la connexion\"],\"V_e7nf\":[\"Définir un nouveau mot de passe\"],\"VnNJbN\":[\"De collections\"],\"VyTYmS\":[\"Changer l'avatar\"],\"W9FRBT\":[\"Aimer\"],\"WhimMi\":[\"Échec de la réinitialisation\"],\"WpXcBJ\":[\"Rien ici pour l'instant.\"],\"WtkMN8\":[\"Toutes les \",[\"0\",\"plural\",{\"one\":[\"#\",\" reco votée\"],\"other\":[\"#\",\" recos votées\"]}],\" chargées.\"],\"XJy2oN\":[\"Connexion…\"],\"Xan6QP\":[\"Nouvelle reco\"],\"XgRtUf\":[\"Impossible de changer le mot de passe\"],\"Xi0Mn4\":[\"← Retour au profil\"],\"XnL-Eu\":[\"Aucun utilisateur ne correspond à « \",[\"q\"],\" ».\"],\"Xs2Lez\":[\"Ce lien de réinitialisation est absent ou malformé.\"],\"YK1Dhc\":[\"une publication\"],\"Ye9RMF\":[\"<0>\",[\"0\"],\" a aimé votre commentaire sur <1>\",[\"1\"],\"\"],\"YpkCca\":[\"Pas encore de collections suivies.\"],\"ZBdbv9\":[\"Modifier le titre\"],\"ZCpU0u\":[\"Aucune collection ne correspond à « \",[\"q\"],\" ».\"],\"ZmD2o6\":[\"Créer et ajouter\"],\"_3O5R_\":[\"Échec de la demande\"],\"_84wxb\":[\"Toutes les \",[\"0\",\"plural\",{\"one\":[\"#\",\" reco\"],\"other\":[\"#\",\" recos\"]}],\" chargées.\"],\"_DwR-n\":[\"Création…\"],\"_R_sGB\":[\"Mot de passe modifié avec succès.\"],\"_aept4\":[\"Publier la réponse\"],\"_nT6AE\":[\"Nouveau mot de passe\"],\"_t4W-i\":[\"De personnes\"],\"aAIQg2\":[\"Apparence\"],\"aDvLhk\":[\"Ajouter un commentaire…\"],\"b3Thhd\":[\"Envoi échoué\"],\"b8XMJ8\":[[\"visibleCount\",\"plural\",{\"one\":[\"#\",\" commentaire\"],\"other\":[\"#\",\" commentaires\"]}]],\"bQhwn-\":[\"Chargement de la collection…\"],\"cILfnJ\":[\"Supprimer le fichier\"],\"cYP9Sb\":[\"+ Collection\"],\"cbeBbZ\":[\"Au moins \",[\"0\"],\" caractères\"],\"cnGeoo\":[\"Supprimer\"],\"d8DZWS\":[\"Ouvrir la recherche\"],\"dAs22m\":[\"Remplacer le fichier\"],\"dEgA5A\":[\"Annuler\"],\"dMizp8\":[\"Nouvelle collection\"],\"eFSqvc\":[\"Impossible de publier la réponse\"],\"ePK91l\":[\"Modifier\"],\"eaUTwS\":[\"Envoyer le lien de réinitialisation\"],\"ecUA8p\":[\"Aujourd'hui\"],\"ef9nPf\":[\"Chargement de la reco…\"],\"en9o7K\":[\"Impossible de publier le commentaire\"],\"etFQQS\":[\"Pourquoi on en voudrait ?\"],\"fC6mXb\":[\"Voter\"],\"fI-mNw\":[\"Collections\"],\"f_akpP\":[\"Max 50 Mo\"],\"fgLNSM\":[\"S'inscrire\"],\"gANddk\":[\"Envoi…\"],\"gGx5tM\":[\"Modification\"],\"gIQQwD\":[\"Chargement échoué\"],\"gLfZlz\":[\"Ajouter à la collection\"],\"gjJ-sb\":[\"Impossible de se connecter au serveur de mises à jour en direct. Les votes et les notifications pourraient ne pas se synchroniser avant la reconnexion.\"],\"hBuUKa\":[\"Changer le mot de passe…\"],\"hD7w09\":[\"Vous avez tout lu, tout vu, tout bu.\"],\"hJSliC\":[\"<0>\",[\"0\"],\" a publié <1>\",[\"1\"],\"\"],\"hYgDIe\":[\"Créer\"],\"he3ygx\":[\"Copier\"],\"i7K_Te\":[\"Qui suis-je ?\"],\"iDNBZe\":[\"Notifications\"],\"iWpEwy\":[\"Accueil\"],\"ijVyoK\":[\"Impossible de contacter le serveur. Veuillez réessayer.\"],\"ipYn7W\":[\"Si cette adresse est enregistrée, vous recevrez un lien de réinitialisation sous peu.\"],\"isRobC\":[\"Nouveau\"],\"jbernk\":[\"Chargement du profil…\"],\"joEmfT\":[\"Serveur inaccessible\"],\"jrZTZl\":[\"Pas encore de recos. Soyez le premier !\"],\"kLttbL\":[\"Inscription échouée\"],\"klOeIX\":[\"Impossible de changer le mot de passe\"],\"lUDifl\":[\"Créées (\",[\"0\"],[\"1\"],\")\"],\"lUanmi\":[\"Vous serez notifié lorsque quelqu'un suit vos collections, vote pour vos recos ou publie du nouveau contenu.\"],\"lY5h1V\":[[\"0\",\"plural\",{\"one\":[\"#\",\" reco\"],\"other\":[\"#\",\" recos\"]}]],\"lcfvr_\":[\"Supprimer ce commentaire ?\"],\"lpIMne\":[\"Les mots de passe ne correspondent pas\"],\"mt6O6E\":[\"C'est un mirage.\"],\"nbm5sI\":[\"Aucune reco ne correspond à « \",[\"q\"],\" ».\"],\"nrjqON\":[\"Vérification de l'invitation…\"],\"nwtY4N\":[\"Une erreur est survenue\"],\"ogtYkT\":[\"Mot de passe mis à jour\"],\"pCpd9p\":[\"<0>\",[\"0\"],\" vous a mentionné dans <1>\",[\"where\"],\"\"],\"pSheLH\":[\"Aucun invité pour le moment.\"],\"pvnfJD\":[\"Sombre\"],\"qIMfNQ\":[\"Supprimer la collection\"],\"qbDAcy\":[\"Recommander\"],\"qgx_78\":[\"Suivez des collections publiques pour voir leurs recos ici.\"],\"qvFa8r\":[\"public\"],\"qvz_Pp\":[\"Reco\"],\"rCbqPX\":[\"Ce lien d'invitation est manquant, expiré ou déjà utilisé.\"],\"rg9pXu\":[\"Recherche échouée\"],\"rtpJqV\":[\"Recos (\",[\"0\"],[\"1\"],\")\"],\"sGeXL3\":[\"Miniature\"],\"sQia9P\":[\"Se connecter\"],\"sTiqbm\":[\"invité par\"],\"sdP5Aa\":[\"[supprimé]\"],\"shHs8T\":[\"Saisissez une recherche.\"],\"smeBfS\":[\"Invitation invalide\"],\"tfDRzk\":[\"Enregistrer\"],\"tvmuQ0\":[\"Thème de couleur\"],\"u1lDX2\":[\"Récupération de l'aperçu…\"],\"uD0qXQ\":[\"Déposez un fichier ici\"],\"uMGUnV\":[\"Pas encore de collections.\"],\"ub1EEL\":[\"modifié \",[\"0\"]],\"vJBF1r\":[\"Publication…\"],\"vLhLLO\":[\"Notifications (\",[\"unreadNotificationCount\"],\" non lues)\"],\"vQMkHu\":[\"Retirer le vote\"],\"vuosjb\":[\"Menu utilisateur\"],\"wXO4Tg\":[\"il y a \",[\"hrs\"],\"h\"],\"wbXKOv\":[\"Fichier trop volumineux (max 50 Mo).\"],\"wixIgH\":[\"Vous avez déjà un compte ? <0>Se connecter\"],\"xEWkgZ\":[\"← Retour à toutes les recos\"],\"xOTzt5\":[\"à l'instant\"],\"xPHtx0\":[\"Lancer la recherche\"],\"xVuNgt\":[\"+ Nouvelle collection\"],\"xc9O_u\":[\"Supprimer la reco\"],\"y6sq5j\":[\"Abonné\"],\"yA_6BX\":[\"Tout voir →\"],\"yBBtRm\":[\"Suivez des utilisateurs pour voir leurs recos ici.\"],\"yQ2kGp\":[\"Charger plus\"],\"y_0uwd\":[\"Hier\"],\"yz7wBu\":[\"Fermer\"],\"z0ROB3\":[\"Retirer le j'aime\"],\"z1uNN0\":[\"Aucun emoji trouvé.\"],\"zVuxvN\":[\"Actualisation…\"],\"zwBp5t\":[\"Privé\"]}")}; \ No newline at end of file diff --git a/src/locales/fr.po b/src/locales/fr.po index bb1e2de..2017049 100644 --- a/src/locales/fr.po +++ b/src/locales/fr.po @@ -42,7 +42,7 @@ msgstr "il y a {days}j" msgid "{hrs}h ago" msgstr "il y a {hrs}h" -#: src/pages/Search.tsx:182 +#: src/pages/Search.tsx:176 msgid "{label} ({count})" msgstr "{label} ({count})" @@ -55,7 +55,7 @@ msgid "{visibleCount, plural, one {# comment} other {# comments}}" msgstr "{visibleCount, plural, one {# commentaire} other {# commentaires}}" #: src/pages/PlaylistDetail.tsx:617 -#: src/pages/UserPublicProfile.tsx:753 +#: src/pages/UserPublicProfile.tsx:766 msgid "← Back" msgstr "← Retour" @@ -71,7 +71,7 @@ msgstr "← Retour à toutes les recos" msgid "← Back to profile" msgstr "← Retour au profil" -#: src/pages/UserPublicProfile.tsx:101 +#: src/pages/UserPublicProfile.tsx:102 msgid "+ Invite someone" msgstr "+ Inviter quelqu'un" @@ -142,7 +142,7 @@ msgstr "un commentaire" msgid "a post" msgstr "une publication" -#: src/pages/UserPublicProfile.tsx:1204 +#: src/pages/UserPublicProfile.tsx:1217 msgid "Account" msgstr "Compte" @@ -150,7 +150,7 @@ msgstr "Compte" msgid "Add a comment…" msgstr "Ajouter un commentaire…" -#: src/pages/UserPublicProfile.tsx:871 +#: src/pages/UserPublicProfile.tsx:884 msgid "Add email…" msgstr "Ajouter un e-mail…" @@ -171,7 +171,7 @@ msgstr "Toutes les {0, plural, one {# reco votée} other {# recos votées}} char msgid "Already have an account? <0>Log in" msgstr "Vous avez déjà un compte ? <0>Se connecter" -#: src/pages/UserPublicProfile.tsx:1223 +#: src/pages/UserPublicProfile.tsx:1236 msgid "Appearance" msgstr "Apparence" @@ -181,7 +181,7 @@ msgstr "Apparence" msgid "At least {0} characters" msgstr "Au moins {0} caractères" -#: src/pages/UserPublicProfile.tsx:1257 +#: src/pages/UserPublicProfile.tsx:1270 msgid "Auto" msgstr "Auto" @@ -205,8 +205,8 @@ msgstr "Impossible de se connecter au serveur de mises à jour en direct. Les vo #: src/pages/Dump.tsx:355 #: src/pages/DumpEdit.tsx:391 #: src/pages/PlaylistDetail.tsx:686 -#: src/pages/UserPublicProfile.tsx:850 -#: src/pages/UserPublicProfile.tsx:932 +#: src/pages/UserPublicProfile.tsx:863 +#: src/pages/UserPublicProfile.tsx:945 msgid "Cancel" msgstr "Annuler" @@ -218,7 +218,7 @@ msgstr "Annuler la suppression" msgid "Cancel search" msgstr "Annuler la recherche" -#: src/pages/UserPublicProfile.tsx:780 +#: src/pages/UserPublicProfile.tsx:793 msgid "Change avatar" msgstr "Changer l'avatar" @@ -227,7 +227,7 @@ msgstr "Changer l'avatar" msgid "Change password" msgstr "Changer le mot de passe" -#: src/pages/UserPublicProfile.tsx:1216 +#: src/pages/UserPublicProfile.tsx:1229 msgid "Change password…" msgstr "Changer le mot de passe…" @@ -240,7 +240,7 @@ msgstr "Vérification de l'invitation…" msgid "Close" msgstr "Fermer" -#: src/pages/UserPublicProfile.tsx:1249 +#: src/pages/UserPublicProfile.tsx:1262 msgid "Color scheme" msgstr "Thème de couleur" @@ -249,11 +249,11 @@ msgstr "Thème de couleur" msgid "Confirm new password" msgstr "Confirmer le nouveau mot de passe" -#: src/pages/UserPublicProfile.tsx:92 +#: src/pages/UserPublicProfile.tsx:93 msgid "Copied!" msgstr "Copié !" -#: src/pages/UserPublicProfile.tsx:92 +#: src/pages/UserPublicProfile.tsx:93 msgid "Copy" msgstr "Copier" @@ -298,7 +298,7 @@ msgstr "Création…" msgid "Current password" msgstr "Mot de passe actuel" -#: src/pages/UserPublicProfile.tsx:1271 +#: src/pages/UserPublicProfile.tsx:1284 msgid "Dark" msgstr "Sombre" @@ -362,15 +362,15 @@ msgstr "Recommander" msgid "Dumped!" msgstr "Recommandé !" -#: src/pages/Search.tsx:178 +#: src/pages/Search.tsx:172 #: src/pages/UserDumps.tsx:107 -#: src/pages/UserPublicProfile.tsx:976 +#: src/pages/UserPublicProfile.tsx:989 msgid "Dumps" msgstr "Recos" #. placeholder {0}: dumps.items.length #. placeholder {1}: dumps.hasMore ? "+" : "" -#: src/pages/UserPublicProfile.tsx:993 +#: src/pages/UserPublicProfile.tsx:1006 msgid "Dumps ({0}{1})" msgstr "Recos ({0}{1})" @@ -414,7 +414,7 @@ msgstr "Modification" msgid "Email address" msgstr "Adresse e-mail" -#: src/pages/Search.tsx:211 +#: src/pages/Search.tsx:205 msgid "Enter a query to search." msgstr "Saisissez une recherche." @@ -427,9 +427,9 @@ msgstr "Impossible de changer le mot de passe" msgid "Failed to create playlist" msgstr "Impossible de créer la collection" -#: src/pages/UserPublicProfile.tsx:73 -#: src/pages/UserPublicProfile.tsx:76 -#: src/pages/UserPublicProfile.tsx:104 +#: src/pages/UserPublicProfile.tsx:74 +#: src/pages/UserPublicProfile.tsx:77 +#: src/pages/UserPublicProfile.tsx:105 msgid "Failed to generate invite" msgstr "Impossible de générer une invitation" @@ -438,9 +438,9 @@ msgstr "Impossible de générer une invitation" #: src/pages/index/JournalFeed.tsx:48 #: src/pages/index/NewFeed.tsx:36 #: src/pages/Notifications.tsx:367 -#: src/pages/UserPublicProfile.tsx:1095 -#: src/pages/UserPublicProfile.tsx:1137 -#: src/pages/UserPublicProfile.tsx:1182 +#: src/pages/UserPublicProfile.tsx:1108 +#: src/pages/UserPublicProfile.tsx:1150 +#: src/pages/UserPublicProfile.tsx:1195 msgid "Failed to load" msgstr "Chargement échoué" @@ -457,10 +457,10 @@ msgid "Failed to post reply" msgstr "Impossible de publier la réponse" #: src/pages/PlaylistDetail.tsx:795 -#: src/pages/UserPublicProfile.tsx:688 -#: src/pages/UserPublicProfile.tsx:726 -#: src/pages/UserPublicProfile.tsx:855 -#: src/pages/UserPublicProfile.tsx:935 +#: src/pages/UserPublicProfile.tsx:701 +#: src/pages/UserPublicProfile.tsx:739 +#: src/pages/UserPublicProfile.tsx:868 +#: src/pages/UserPublicProfile.tsx:948 msgid "Failed to save" msgstr "Enregistrement échoué" @@ -468,7 +468,7 @@ msgstr "Enregistrement échoué" msgid "Failed to save edit" msgstr "Impossible d'enregistrer la modification" -#: src/pages/UserPublicProfile.tsx:880 +#: src/pages/UserPublicProfile.tsx:893 msgid "Failed to update avatar" msgstr "Impossible de mettre à jour l'avatar" @@ -510,8 +510,8 @@ msgstr "Suivez des collections publiques pour voir leurs recos ici." msgid "Follow some users to see their dumps here." msgstr "Suivez des utilisateurs pour voir leurs recos ici." -#: src/components/FeedTabBar.tsx:20 -#: src/pages/UserPublicProfile.tsx:978 +#: src/components/FeedTabBar.tsx:16 +#: src/pages/UserPublicProfile.tsx:991 msgid "Followed" msgstr "Suivi" @@ -521,13 +521,13 @@ msgstr "Suivi" msgid "Followed ({0}{1})" msgstr "Suivies ({0}{1})" -#: src/pages/UserPublicProfile.tsx:1126 +#: src/pages/UserPublicProfile.tsx:1139 msgid "Followed playlists" msgstr "Collections suivies" #: src/components/FollowButton.tsx:37 #: src/components/FollowButton.tsx:64 -#: src/pages/UserPublicProfile.tsx:1084 +#: src/pages/UserPublicProfile.tsx:1097 msgid "Following" msgstr "Abonné" @@ -551,7 +551,7 @@ msgstr "Accueil" msgid "Go to login" msgstr "Aller à la connexion" -#: src/components/FeedTabBar.tsx:16 +#: src/components/FeedTabBar.tsx:12 msgid "Hot" msgstr "Tendances" @@ -567,16 +567,16 @@ msgstr "Invitation invalide" msgid "Invalid link" msgstr "Lien invalide" -#: src/pages/UserPublicProfile.tsx:799 +#: src/pages/UserPublicProfile.tsx:812 msgid "invited by" msgstr "invité par" -#: src/pages/UserPublicProfile.tsx:979 -#: src/pages/UserPublicProfile.tsx:1171 +#: src/pages/UserPublicProfile.tsx:992 +#: src/pages/UserPublicProfile.tsx:1184 msgid "Invitees" msgstr "Invités" -#: src/components/FeedTabBar.tsx:18 +#: src/components/FeedTabBar.tsx:14 msgid "Journal" msgstr "Journal" @@ -584,7 +584,7 @@ msgstr "Journal" msgid "just now" msgstr "à l'instant" -#: src/pages/UserPublicProfile.tsx:1264 +#: src/pages/UserPublicProfile.tsx:1277 msgid "Light" msgstr "Clair" @@ -614,7 +614,7 @@ msgstr "Chargement de la reco…" #: src/pages/index/HotFeed.tsx:64 #: src/pages/index/JournalFeed.tsx:77 #: src/pages/index/NewFeed.tsx:64 -#: src/pages/Search.tsx:248 +#: src/pages/Search.tsx:242 #: src/pages/UserDumps.tsx:93 #: src/pages/UserPlaylists.tsx:417 #: src/pages/UserPlaylists.tsx:452 @@ -626,7 +626,7 @@ msgstr "Chargement…" msgid "Loading playlist…" msgstr "Chargement de la collection…" -#: src/pages/UserPublicProfile.tsx:736 +#: src/pages/UserPublicProfile.tsx:749 msgid "Loading profile…" msgstr "Chargement du profil…" @@ -642,9 +642,9 @@ msgstr "Chargement du profil…" #: src/pages/Notifications.tsx:439 #: src/pages/UserDumps.tsx:51 #: src/pages/UserPlaylists.tsx:342 -#: src/pages/UserPublicProfile.tsx:1089 -#: src/pages/UserPublicProfile.tsx:1131 -#: src/pages/UserPublicProfile.tsx:1176 +#: src/pages/UserPublicProfile.tsx:1102 +#: src/pages/UserPublicProfile.tsx:1144 +#: src/pages/UserPublicProfile.tsx:1189 #: src/pages/UserUpvoted.tsx:122 msgid "Loading…" msgstr "Chargement…" @@ -663,8 +663,8 @@ msgstr "Se connecter pour aimer" msgid "Log in to vote" msgstr "Se connecter pour voter" -#: src/pages/UserPublicProfile.tsx:757 -#: src/pages/UserPublicProfile.tsx:894 +#: src/pages/UserPublicProfile.tsx:770 +#: src/pages/UserPublicProfile.tsx:907 msgid "Log out" msgstr "Se déconnecter" @@ -684,13 +684,13 @@ msgstr "Max 50 Mo" msgid "new" msgstr "nouveau" -#: src/components/FeedTabBar.tsx:17 +#: src/components/FeedTabBar.tsx:13 msgid "New" msgstr "Nouveau" #: src/components/DumpCreateModal.tsx:302 #: src/pages/UserDumps.tsx:115 -#: src/pages/UserPublicProfile.tsx:1320 +#: src/pages/UserPublicProfile.tsx:1333 msgid "New dump" msgstr "Nouvelle reco" @@ -708,7 +708,7 @@ msgstr "Nouvelle collection" msgid "No dumps in this playlist yet." msgstr "Aucune reco dans cette collection pour l'instant." -#: src/pages/Search.tsx:228 +#: src/pages/Search.tsx:222 msgid "No dumps match \"{q}\"." msgstr "Aucune reco ne correspond à « {q} »." @@ -723,11 +723,11 @@ msgid "No emoji found." msgstr "Aucun emoji trouvé." #: src/pages/UserPlaylists.tsx:439 -#: src/pages/UserPublicProfile.tsx:1144 +#: src/pages/UserPublicProfile.tsx:1157 msgid "No followed playlists yet." msgstr "Pas encore de collections suivies." -#: src/pages/UserPublicProfile.tsx:1189 +#: src/pages/UserPublicProfile.tsx:1202 msgid "No invitees yet." msgstr "Aucun invité pour le moment." @@ -735,28 +735,28 @@ msgstr "Aucun invité pour le moment." msgid "No one yet." msgstr "Personne pour le moment." -#: src/pages/Search.tsx:287 +#: src/pages/Search.tsx:281 msgid "No playlists match \"{q}\"." msgstr "Aucune collection ne correspond à « {q} »." #: src/components/PlaylistMembershipPanel.tsx:34 #: src/pages/UserPlaylists.tsx:397 -#: src/pages/UserPublicProfile.tsx:1055 +#: src/pages/UserPublicProfile.tsx:1068 msgid "No playlists yet." msgstr "Pas encore de collections." -#: src/pages/Search.tsx:261 +#: src/pages/Search.tsx:255 msgid "No users match \"{q}\"." msgstr "Aucun utilisateur ne correspond à « {q} »." -#: src/pages/UserPublicProfile.tsx:1102 +#: src/pages/UserPublicProfile.tsx:1115 msgid "Not following anyone yet." msgstr "Aucun abonnement pour le moment." #: src/pages/Notifications.tsx:374 #: src/pages/UserDumps.tsx:125 -#: src/pages/UserPublicProfile.tsx:1331 -#: src/pages/UserPublicProfile.tsx:1453 +#: src/pages/UserPublicProfile.tsx:1344 +#: src/pages/UserPublicProfile.tsx:1466 #: src/pages/UserUpvoted.tsx:194 msgid "Nothing here yet." msgstr "Rien ici pour l'instant." @@ -779,7 +779,7 @@ msgid "or <0>browse files" msgstr "ou <0>parcourir les fichiers" #: src/pages/UserLogin.tsx:106 -#: src/pages/UserPublicProfile.tsx:1209 +#: src/pages/UserPublicProfile.tsx:1222 msgid "Password" msgstr "Mot de passe" @@ -803,15 +803,15 @@ msgstr "Les mots de passe ne correspondent pas" #: src/components/AppHeader.tsx:85 #: src/components/UserMenu.tsx:62 -#: src/pages/Search.tsx:181 +#: src/pages/Search.tsx:175 #: src/pages/UserPlaylists.tsx:368 -#: src/pages/UserPublicProfile.tsx:977 +#: src/pages/UserPublicProfile.tsx:990 msgid "Playlists" msgstr "Collections" #. placeholder {0}: playlists.items.length #. placeholder {1}: playlists.hasMore ? "+" : "" -#: src/pages/UserPublicProfile.tsx:1024 +#: src/pages/UserPublicProfile.tsx:1037 msgid "Playlists ({0}{1})" msgstr "Collections ({0}{1})" @@ -883,7 +883,7 @@ msgstr "Inscription échouée" #: src/pages/Dump.tsx:467 msgid "Related" -msgstr "Apparentés" +msgstr "Connexe" #: src/components/FileDropZone.tsx:115 msgid "Remove file" @@ -930,8 +930,8 @@ msgstr "Réessayer" #: src/pages/Dump.tsx:347 #: src/pages/DumpEdit.tsx:400 #: src/pages/PlaylistDetail.tsx:679 -#: src/pages/UserPublicProfile.tsx:842 -#: src/pages/UserPublicProfile.tsx:924 +#: src/pages/UserPublicProfile.tsx:855 +#: src/pages/UserPublicProfile.tsx:937 msgid "Save" msgstr "Enregistrer" @@ -940,8 +940,8 @@ msgstr "Enregistrer" #: src/pages/Dump.tsx:347 #: src/pages/PlaylistDetail.tsx:679 #: src/pages/ResetPassword.tsx:152 -#: src/pages/UserPublicProfile.tsx:841 -#: src/pages/UserPublicProfile.tsx:924 +#: src/pages/UserPublicProfile.tsx:854 +#: src/pages/UserPublicProfile.tsx:937 msgid "Saving…" msgstr "Enregistrement…" @@ -954,11 +954,11 @@ msgstr "Rechercher" msgid "Search dumps, users, playlists…" msgstr "Rechercher des recos, utilisateurs, collections…" -#: src/pages/Search.tsx:222 +#: src/pages/Search.tsx:216 msgid "Search failed" msgstr "Recherche échouée" -#: src/pages/Search.tsx:217 +#: src/pages/Search.tsx:211 msgid "Searching…" msgstr "Recherche…" @@ -979,7 +979,7 @@ msgstr "Serveur inaccessible" msgid "Set new password" msgstr "Définir un nouveau mot de passe" -#: src/pages/UserPublicProfile.tsx:981 +#: src/pages/UserPublicProfile.tsx:994 msgid "Settings" msgstr "Paramètres" @@ -987,7 +987,7 @@ msgstr "Paramètres" msgid "Something went wrong" msgstr "Une erreur est survenue" -#: src/pages/UserPublicProfile.tsx:1228 +#: src/pages/UserPublicProfile.tsx:1241 msgid "Style" msgstr "Style" @@ -1042,7 +1042,7 @@ msgstr "Ne plus suivre la collection" msgid "Unknown error" msgstr "Erreur inconnue" -#: src/pages/UserPublicProfile.tsx:657 +#: src/pages/UserPublicProfile.tsx:670 msgid "Upload failed" msgstr "Envoi échoué" @@ -1060,7 +1060,7 @@ msgstr "Voté" #. placeholder {0}: votes.items.length #. placeholder {1}: votes.hasMore ? "+" : "" -#: src/pages/UserPublicProfile.tsx:1004 +#: src/pages/UserPublicProfile.tsx:1017 msgid "Upvoted ({0}{1})" msgstr "Votés ({0}{1})" @@ -1082,15 +1082,15 @@ msgstr "Menu utilisateur" msgid "Username" msgstr "Nom d'utilisateur" -#: src/pages/Search.tsx:180 +#: src/pages/Search.tsx:174 msgid "Users" msgstr "Utilisateurs" -#: src/pages/UserPublicProfile.tsx:1074 -#: src/pages/UserPublicProfile.tsx:1117 -#: src/pages/UserPublicProfile.tsx:1159 -#: src/pages/UserPublicProfile.tsx:1352 -#: src/pages/UserPublicProfile.tsx:1483 +#: src/pages/UserPublicProfile.tsx:1087 +#: src/pages/UserPublicProfile.tsx:1130 +#: src/pages/UserPublicProfile.tsx:1172 +#: src/pages/UserPublicProfile.tsx:1365 +#: src/pages/UserPublicProfile.tsx:1496 msgid "View all →" msgstr "Tout voir →" @@ -1103,8 +1103,8 @@ msgstr "Voir la reco →" msgid "What makes it worth it?" msgstr "Pourquoi on en voudrait ?" -#: src/pages/UserPublicProfile.tsx:912 -#: src/pages/UserPublicProfile.tsx:961 +#: src/pages/UserPublicProfile.tsx:925 +#: src/pages/UserPublicProfile.tsx:974 msgid "Who am I?" msgstr "Qui suis-je ?" @@ -1129,7 +1129,7 @@ msgstr "Vous serez notifié lorsque quelqu'un suit vos collections, vote pour vo #: src/pages/index/HotFeed.tsx:69 #: src/pages/index/JournalFeed.tsx:82 #: src/pages/index/NewFeed.tsx:69 -#: src/pages/Search.tsx:253 +#: src/pages/Search.tsx:247 #: src/pages/UserDumps.tsx:98 #: src/pages/UserPlaylists.tsx:422 #: src/pages/UserPlaylists.tsx:457 diff --git a/src/pages/Index.tsx b/src/pages/Index.tsx index 5bba146..d1b54f0 100644 --- a/src/pages/Index.tsx +++ b/src/pages/Index.tsx @@ -11,7 +11,8 @@ import { useLocation } from "react-router"; import { AppHeader } from "../components/AppHeader.tsx"; import { PresenceRow } from "../components/PresenceRow.tsx"; import { FeedTabBar } from "../components/FeedTabBar.tsx"; -import { type FeedTab, VALID_TABS } from "../config/feedTabs.ts"; +import { type FeedTab, FEED_TABS } from "../config/feedTabs.ts"; +import { useTabParam } from "../hooks/useTabParam.ts"; import { API_URL, DEFAULT_PAGE_SIZE } from "../config/api.ts"; @@ -82,20 +83,20 @@ export function Index() { ); const mainFetchDone = useRef(false); - const searchParams = new URLSearchParams(location.search); - const rawTab = searchParams.get("tab") ?? "hot"; - const tab: FeedTab = VALID_TABS.has(rawTab) ? rawTab as FeedTab : "hot"; + const [tab] = useTabParam(FEED_TABS, "hot"); // Web Share Target: Android share sheet navigates to /?share_url=... + const searchParams = new URLSearchParams(location.search); const shareUrl = searchParams.get("share_url") ?? searchParams.get("share_text") ?? ""; useEffect(() => { if (!shareUrl) return; - // Clean share params from the URL so a refresh doesn't re-open the modal - const clean = tab !== "hot" ? `?tab=${tab}` : ""; - globalThis.history.replaceState({}, "", location.pathname + clean); - }, [shareUrl, tab, location.pathname]); + // Clean share params from the URL so a refresh doesn't re-open the modal. + // The active tab already lives in the pathname (e.g. /~/new), so just drop + // the query string. + globalThis.history.replaceState({}, "", location.pathname); + }, [shareUrl, location.pathname]); // ── Main feed fetch ── diff --git a/src/pages/ResetPassword.tsx b/src/pages/ResetPassword.tsx index 3133414..1e58514 100644 --- a/src/pages/ResetPassword.tsx +++ b/src/pages/ResetPassword.tsx @@ -70,7 +70,7 @@ export function ResetPassword() { ); } - const handleSubmit = async (e: React.FormEvent) => { + const handleSubmit = async (e: React.SubmitEvent) => { e.preventDefault(); if (mismatch || tooShort || !newPassword) return; diff --git a/src/pages/Search.tsx b/src/pages/Search.tsx index 97c2c21..195cabb 100644 --- a/src/pages/Search.tsx +++ b/src/pages/Search.tsx @@ -10,6 +10,7 @@ import { ErrorCard } from "../components/ErrorCard.tsx"; import { useAuth } from "../hooks/useAuth.ts"; import { useWS } from "../hooks/useWS.ts"; import { useInfiniteScroll } from "../hooks/useInfiniteScroll.ts"; +import { useTabParam } from "../hooks/useTabParam.ts"; import { deserializeDump, deserializePlaylist, @@ -23,7 +24,8 @@ import { } from "../model.ts"; import { DEFAULT_PAGE_SIZE, SEARCH_URL } from "../config/api.ts"; -type Tab = "dumps" | "users" | "playlists"; +const SEARCH_TABS = ["dumps", "users", "playlists"] as const; +type Tab = (typeof SEARCH_TABS)[number]; type SearchState = | { status: "idle" } @@ -44,9 +46,9 @@ type SearchState = }; export function Search() { - const [searchParams, setSearchParams] = useSearchParams(); + const [searchParams] = useSearchParams(); const q = searchParams.get("q") ?? ""; - const tab = (searchParams.get("tab") ?? "dumps") as Tab; + const [tab, setTab] = useTabParam(SEARCH_TABS, "dumps"); const { token, user } = useAuth(); const { voteCounts, myVotes, castVote, removeVote } = useWS(); @@ -159,14 +161,6 @@ export function Search() { !state.dumps.loadingMore, ); - function setTab(tab: Tab) { - setSearchParams((prev) => { - const next = new URLSearchParams(prev); - next.set("tab", tab); - return next; - }, { replace: true }); - } - const dumpCount = state.status === "loaded" ? state.dumps.total : null; const userCount = state.status === "loaded" ? state.users.length : null; const playlistCount = state.status === "loaded" @@ -188,7 +182,7 @@ export function Search() {
{q && ( ({ + tabs={SEARCH_TABS.map((key) => ({ key, label: tabLabel( key, diff --git a/src/pages/UserLogin.tsx b/src/pages/UserLogin.tsx index 453b6d8..e90096c 100644 --- a/src/pages/UserLogin.tsx +++ b/src/pages/UserLogin.tsx @@ -64,7 +64,7 @@ export function UserLogin() { } }; - const handleResetRequest = async (e: React.FormEvent) => { + const handleResetRequest = async (e: React.SubmitEvent) => { e.preventDefault(); if (!resetEmail.trim()) return; setResetState({ status: "submitting" }); diff --git a/src/pages/UserPublicProfile.tsx b/src/pages/UserPublicProfile.tsx index 47ca415..35bbdaa 100644 --- a/src/pages/UserPublicProfile.tsx +++ b/src/pages/UserPublicProfile.tsx @@ -54,6 +54,7 @@ import { TextEditor } from "../components/TextEditor.tsx"; import { Markdown } from "../components/Markdown.tsx"; import { ChangePasswordModal } from "../components/ChangePasswordModal.tsx"; import { TabBar } from "../components/TabBar.tsx"; +import { useTabParam } from "../hooks/useTabParam.ts"; function InviteButton() { const { authFetch } = useAuth(); @@ -146,7 +147,14 @@ type InviteTreeState = | { status: "loaded"; entries: InviteTreeEntry[] }; type InviteTreeNode = InviteTreeEntry & { children: InviteTreeNode[] }; -type ProfileTab = "dumps" | "playlists" | "followed" | "invitees" | "settings"; +const PROFILE_TABS = [ + "dumps", + "playlists", + "followed", + "invitees", + "settings", +] as const; +type ProfileTab = (typeof PROFILE_TABS)[number]; function buildInviteTree( entries: InviteTreeEntry[], @@ -193,6 +201,13 @@ export function UserPublicProfile() { const profileUserId = state.status === "loaded" ? state.user.id : null; const isOwnProfile = me?.id === profileUserId; + // Active tab is driven by the `/~/` URL path (linkable / refresh-safe). + // `settings` is only valid on your own profile; otherwise it falls back to "dumps". + const availableTabs: ProfileTab[] = isOwnProfile + ? [...PROFILE_TABS] + : PROFILE_TABS.filter((t) => t !== "settings"); + const [tab, setTab] = useTabParam(availableTabs, "dumps"); + const setDumps = useCallback((fn: (prev: Dump[]) => Dump[]) => { setState((s) => s.status !== "loaded" @@ -287,7 +302,6 @@ export function UserPublicProfile() { const [emailError, setEmailError] = useState(null); const prevMyVotesRef = useRef | null>(null); - const [tab, setTab] = useState("dumps"); const [changePasswordOpen, setChangePasswordOpen] = useState(false); const [followedState, setFollowedState] = useState(null); const [inviteTreeState, setInviteTreeState] = useState(null); @@ -301,7 +315,6 @@ export function UserPublicProfile() { if (prevUsername !== username) { setPrevUsername(username); setState({ status: "loading" }); - setTab("dumps"); setFollowedState(null); setInviteTreeState(null); }