diff --git a/AGENTS.md b/AGENTS.md index 5eba870e3..bf34b1b17 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,6 +2,7 @@ - only rarely use comments, prefer descriptive variable and function names (leave existing comments as is) - if you encounter an existing TODO comment assume it is there for a reason and do not remove it +- task is not considered completely until `npm run checks` passes ## Commands @@ -19,6 +20,7 @@ - for constants use ALL_CAPS - always use named exports - Remeda is the utility library of choice +- date-fns should be used for date related logic ## React @@ -46,6 +48,10 @@ - database code should only be written in Repository files - down migrations are not needed, only up migrations - every database id is of type number +- `/app/db/tables.ts` contains all tables and columns available +- `db.sqlite3` is development database +- `db-test.sqlite3` is the unit test database (should be blank sans migrations ran) +- `db-prod.sqlite3` is a copy of the production environment db ## E2E testing diff --git a/app/db/tables.ts b/app/db/tables.ts index 682c08b96..cb1f9b133 100644 --- a/app/db/tables.ts +++ b/app/db/tables.ts @@ -825,6 +825,7 @@ export interface User { bannedReason: string | null; bio: string | null; commissionsOpen: Generated; + commissionsOpenedAt: number | null; commissionText: string | null; country: string | null; css: JSONColumnTypeNullable>; diff --git a/app/features/art/ArtRepository.server.ts b/app/features/art/ArtRepository.server.ts index 15475d704..ea8fb3ede 100644 --- a/app/features/art/ArtRepository.server.ts +++ b/app/features/art/ArtRepository.server.ts @@ -1,4 +1,7 @@ import { db } from "~/db/sql"; +import type { Tables } from "~/db/tables"; +import { seededRandom } from "~/utils/random"; +import type { ListedArt } from "./art-types"; export function unlinkUserFromArt({ userId, @@ -13,3 +16,123 @@ export function unlinkUserFromArt({ .where("userId", "=", userId) .execute(); } + +function getDailySeed() { + const today = new Date(); + const year = today.getFullYear(); + const month = today.getMonth() + 1; + const day = today.getDate(); + return `${year}-${month}-${day}`; +} + +export async function findShowcaseArts(): Promise { + const arts = await db + .selectFrom("Art") + .innerJoin("User", "User.id", "Art.authorId") + .innerJoin("UserSubmittedImage", "UserSubmittedImage.id", "Art.imgId") + .select([ + "Art.id", + "Art.createdAt", + "User.discordId", + "User.username", + "User.discordAvatar", + "User.commissionsOpen", + "UserSubmittedImage.url", + ]) + .where("Art.isShowcase", "=", 1) + .execute(); + + const mappedArts = arts.map((a) => ({ + id: a.id, + createdAt: a.createdAt, + url: a.url, + author: { + commissionsOpen: a.commissionsOpen, + discordAvatar: a.discordAvatar, + discordId: a.discordId, + username: a.username, + }, + })); + + const { seededShuffle } = seededRandom(getDailySeed()); + return seededShuffle(mappedArts); +} + +export async function findShowcaseArtsByTag( + tagId: Tables["ArtTag"]["id"], +): Promise { + const arts = await db + .selectFrom("TaggedArt") + .innerJoin("Art", "Art.id", "TaggedArt.artId") + .innerJoin("User", "User.id", "Art.authorId") + .innerJoin("UserSubmittedImage", "UserSubmittedImage.id", "Art.imgId") + .select([ + "Art.id", + "Art.createdAt", + "User.id as userId", + "User.discordId", + "User.username", + "User.discordAvatar", + "User.commissionsOpen", + "UserSubmittedImage.url", + ]) + .where("TaggedArt.tagId", "=", tagId) + .orderBy("Art.isShowcase", "desc") + .orderBy("Art.createdAt", "desc") + .execute(); + + const encounteredUserIds = new Set(); + + return arts + .filter((row) => { + if (encounteredUserIds.has(row.userId)) { + return false; + } + + encounteredUserIds.add(row.userId); + + return true; + }) + .map((a) => ({ + id: a.id, + createdAt: a.createdAt, + url: a.url, + author: { + commissionsOpen: a.commissionsOpen, + discordAvatar: a.discordAvatar, + discordId: a.discordId, + username: a.username, + }, + })); +} + +export async function findRecentlyUploadedArts(): Promise { + const arts = await db + .selectFrom("Art") + .innerJoin("User", "User.id", "Art.authorId") + .innerJoin("UserSubmittedImage", "UserSubmittedImage.id", "Art.imgId") + .select([ + "Art.id", + "Art.createdAt", + "User.discordId", + "User.username", + "User.discordAvatar", + "User.commissionsOpen", + "UserSubmittedImage.url", + ]) + .orderBy("Art.createdAt", "desc") + .limit(100) + .execute(); + + return arts.map((a) => ({ + id: a.id, + createdAt: a.createdAt, + url: a.url, + author: { + commissionsOpen: a.commissionsOpen, + discordAvatar: a.discordAvatar, + discordId: a.discordId, + username: a.username, + }, + })); +} diff --git a/app/features/art/components/ArtGrid.tsx b/app/features/art/components/ArtGrid.tsx index e4e4ceaeb..514547d6f 100644 --- a/app/features/art/components/ArtGrid.tsx +++ b/app/features/art/components/ArtGrid.tsx @@ -1,5 +1,6 @@ import { Link } from "@remix-run/react"; import clsx from "clsx"; +import { formatDistanceToNow } from "date-fns"; import * as React from "react"; import { useTranslation } from "react-i18next"; import { Avatar } from "~/components/Avatar"; @@ -26,10 +27,12 @@ export function ArtGrid({ arts, enablePreview = false, canEdit = false, + showUploadDate = false, }: { arts: ListedArt[]; enablePreview?: boolean; canEdit?: boolean; + showUploadDate?: boolean; }) { const { itemsToDisplay, @@ -67,18 +70,21 @@ export function ArtGrid({ art={art} canEdit={canEdit} enablePreview={enablePreview} + showUploadDate={showUploadDate} onClick={enablePreview ? () => setBigArtId(art.id) : undefined} /> ))} {!everythingVisible ? ( - +
+ +
) : null} ); @@ -154,11 +160,13 @@ function ImagePreview({ onClick, enablePreview = false, canEdit = false, + showUploadDate = false, }: { art: ListedArt; onClick?: () => void; enablePreview?: boolean; canEdit?: boolean; + showUploadDate?: boolean; }) { const [imageLoaded, setImageLoaded] = React.useState(false); const { t } = useTranslation(["common", "art"]); @@ -211,6 +219,12 @@ function ImagePreview({ } if (!art.author) return img; + const uploadDateText = showUploadDate + ? formatDistanceToNow(databaseTimestampToDate(art.createdAt), { + addSuffix: true, + }) + : null; + // whole thing is not a link so we can preview the image if (enablePreview) { return ( @@ -230,6 +244,15 @@ function ImagePreview({ {t("art:madeBy")} {art.author.username} + {uploadDateText ? ( +
+ {uploadDateText} +
+ ) : null} {canEdit ? ( {img} -
- - {art.author.username} +
+
+ + {art.author.username} +
+ {uploadDateText ? ( +
+ {uploadDateText} +
+ ) : null}
); diff --git a/app/features/art/loaders/art.server.ts b/app/features/art/loaders/art.server.ts index 662d84580..350a7a3e6 100644 --- a/app/features/art/loaders/art.server.ts +++ b/app/features/art/loaders/art.server.ts @@ -1,10 +1,7 @@ import type { LoaderFunctionArgs } from "@remix-run/node"; +import * as ArtRepository from "../ArtRepository.server"; import { FILTERED_TAG_KEY_SEARCH_PARAM_KEY } from "../art-constants"; import { allArtTags } from "../queries/allArtTags.server"; -import { - showcaseArts, - showcaseArtsByTag, -} from "../queries/showcaseArts.server"; export const loader = async ({ request }: LoaderFunctionArgs) => { const allTags = allArtTags(); @@ -15,7 +12,10 @@ export const loader = async ({ request }: LoaderFunctionArgs) => { const filteredTag = allTags.find((t) => t.name === filteredTagName); return { - arts: filteredTag ? showcaseArtsByTag(filteredTag.id) : showcaseArts(), + showcaseArts: filteredTag + ? await ArtRepository.findShowcaseArtsByTag(filteredTag.id) + : await ArtRepository.findShowcaseArts(), + recentlyUploadedArts: await ArtRepository.findRecentlyUploadedArts(), allTags, }; }; diff --git a/app/features/art/queries/showcaseArts.server.ts b/app/features/art/queries/showcaseArts.server.ts deleted file mode 100644 index 0648111c6..000000000 --- a/app/features/art/queries/showcaseArts.server.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { sql } from "~/db/sql"; -import type { Tables } from "~/db/tables"; -import type { ListedArt } from "../art-types"; - -const showcaseArtsStm = sql.prepare(/* sql */ ` - select - "Art"."id", - "User"."id" as "userId", - "User"."discordId", - "User"."username", - "User"."discordAvatar", - "User"."commissionsOpen", - "UserSubmittedImage"."url" - from - "Art" - left join "User" on "User"."id" = "Art"."authorId" - inner join "UserSubmittedImage" on "UserSubmittedImage"."id" = "Art"."imgId" - where - "Art"."isShowcase" = 1 - order by random() -`); - -export function showcaseArts(): ListedArt[] { - return showcaseArtsStm.all().map((a: any) => ({ - id: a.id, - createdAt: a.createdAt, - url: a.url, - author: { - commissionsOpen: a.commissionsOpen, - discordAvatar: a.discordAvatar, - discordId: a.discordId, - username: a.username, - }, - })); -} - -const showcaseArtsByTagStm = sql.prepare(/* sql */ ` - select - "Art"."id", - "User"."id" as "userId", - "User"."discordId", - "User"."username", - "User"."discordAvatar", - "User"."commissionsOpen", - "UserSubmittedImage"."url" - from - "TaggedArt" - inner join "Art" on "Art"."id" = "TaggedArt"."artId" - left join "User" on "User"."id" = "Art"."authorId" - inner join "UserSubmittedImage" on "UserSubmittedImage"."id" = "Art"."imgId" - where - "TaggedArt"."tagId" = @tagId - order by - "Art"."isShowcase" desc, random() - -`); - -export function showcaseArtsByTag(tagId: Tables["ArtTag"]["id"]): ListedArt[] { - const encounteredUserIds = new Set(); - - return showcaseArtsByTagStm - .all({ tagId }) - .filter((row: any) => { - if (encounteredUserIds.has(row.userId)) { - return false; - } - - encounteredUserIds.add(row.userId); - return true; - }) - .map((a: any) => ({ - id: a.id, - createdAt: a.createdAt, - url: a.url, - author: { - commissionsOpen: a.commissionsOpen, - discordAvatar: a.discordAvatar, - discordId: a.discordId, - username: a.username, - }, - })); -} diff --git a/app/features/art/routes/art.tsx b/app/features/art/routes/art.tsx index b17860faf..b89419e07 100644 --- a/app/features/art/routes/art.tsx +++ b/app/features/art/routes/art.tsx @@ -1,11 +1,18 @@ import type { MetaFunction, SerializeFrom } from "@remix-run/node"; import type { ShouldRevalidateFunction } from "@remix-run/react"; import { useLoaderData, useSearchParams } from "@remix-run/react"; +import clsx from "clsx"; import * as React from "react"; import { useTranslation } from "react-i18next"; import { AddNewButton } from "~/components/AddNewButton"; import { SendouButton } from "~/components/elements/Button"; import { SendouSwitch } from "~/components/elements/Switch"; +import { + SendouTab, + SendouTabList, + SendouTabPanel, + SendouTabs, +} from "~/components/elements/Tabs"; import { CrossIcon } from "~/components/icons/Cross"; import { Label } from "~/components/Label"; import { Main } from "~/components/Main"; @@ -15,11 +22,15 @@ import { metaTags } from "../../../utils/remix"; import { FILTERED_TAG_KEY_SEARCH_PARAM_KEY } from "../art-constants"; import { ArtGrid } from "../components/ArtGrid"; import { TagSelect } from "../components/TagSelect"; - import { loader } from "../loaders/art.server"; export { loader }; const OPEN_COMMISIONS_KEY = "open"; +const TAB_KEY = "tab"; +const TABS = { + RECENTLY_UPLOADED: "recently-uploaded", + SHOWCASE: "showcase", +} as const; export const shouldRevalidate: ShouldRevalidateFunction = (args) => { const currentFilteredTag = args.currentUrl.searchParams.get( @@ -63,12 +74,17 @@ export default function ArtPage() { const [searchParams, setSearchParams] = useSearchParams(); const switchId = React.useId(); + const selectedTab = searchParams.get(TAB_KEY) ?? TABS.RECENTLY_UPLOADED; const filteredTag = searchParams.get(FILTERED_TAG_KEY_SEARCH_PARAM_KEY); const showOpenCommissions = searchParams.get(OPEN_COMMISIONS_KEY) === "true"; - const arts = !showOpenCommissions - ? data.arts - : data.arts.filter((art) => art.author?.commissionsOpen); + const showcaseArts = !showOpenCommissions + ? data.showcaseArts + : data.showcaseArts.filter((art) => art.author?.commissionsOpen); + + const recentlyUploadedArts = !showOpenCommissions + ? data.recentlyUploadedArts + : data.recentlyUploadedArts.filter((art) => art.author?.commissionsOpen); return (
@@ -89,16 +105,25 @@ export default function ArtPage() {
- { - setSearchParams((prev) => { - prev.set(FILTERED_TAG_KEY_SEARCH_PARAM_KEY, tagName as string); - return prev; - }); - }} - /> +
+ { + setSearchParams((prev) => { + prev.set( + FILTERED_TAG_KEY_SEARCH_PARAM_KEY, + tagName as string, + ); + return prev; + }); + }} + /> +
@@ -121,7 +146,31 @@ export default function ArtPage() { ) : null} - + { + setSearchParams((prev) => { + prev.set(TAB_KEY, key as string); + if (key === TABS.RECENTLY_UPLOADED) { + prev.delete(FILTERED_TAG_KEY_SEARCH_PARAM_KEY); + } + return prev; + }); + }} + > + + + {t("art:tabs.recentlyUploaded")} + + {t("art:tabs.showcase")} + + + + + + + + ); } diff --git a/app/features/notifications/notifications-types.ts b/app/features/notifications/notifications-types.ts index a722c9a0c..690f59f26 100644 --- a/app/features/notifications/notifications-types.ts +++ b/app/features/notifications/notifications-types.ts @@ -63,7 +63,8 @@ export type Notification = | NotificationItem<"SEASON_STARTED", { seasonNth: number }> | NotificationItem<"SCRIM_NEW_REQUEST", { fromUsername: string }> | NotificationItem<"SCRIM_SCHEDULED", { id: number; at: number }> - | NotificationItem<"SCRIM_CANCELED", { id: number; at: number }>; + | NotificationItem<"SCRIM_CANCELED", { id: number; at: number }> + | NotificationItem<"COMMISSIONS_CLOSED", { discordId: string }>; type NotificationItem< T extends string, diff --git a/app/features/notifications/notifications-utils.ts b/app/features/notifications/notifications-utils.ts index f02a0e84c..66ffdcc05 100644 --- a/app/features/notifications/notifications-utils.ts +++ b/app/features/notifications/notifications-utils.ts @@ -11,6 +11,7 @@ import { tournamentRegisterPage, tournamentTeamPage, userArtPage, + userEditProfilePage, } from "~/utils/urls"; import type { Notification } from "./notifications-types"; @@ -27,6 +28,7 @@ export const notificationNavIcon = (type: Notification["type"]) => { case "SEASON_STARTED": return "sendouq"; case "TAGGED_TO_ART": + case "COMMISSIONS_CLOSED": return "art"; case "TO_ADDED_TO_TEAM": case "TO_BRACKET_STARTED": @@ -83,6 +85,9 @@ export const notificationLink = (notification: Notification) => { case "SCRIM_SCHEDULED": { return scrimPage(notification.meta.id); } + case "COMMISSIONS_CLOSED": { + return userEditProfilePage({ discordId: notification.meta.discordId }); + } default: assertUnreachable(notification); } diff --git a/app/features/tournament-bracket/tournament-bracket-utils.ts b/app/features/tournament-bracket/tournament-bracket-utils.ts index ecb9fc5b9..9fc67982e 100644 --- a/app/features/tournament-bracket/tournament-bracket-utils.ts +++ b/app/features/tournament-bracket/tournament-bracket-utils.ts @@ -6,8 +6,8 @@ import type { TournamentManagerDataSet } from "~/modules/brackets-manager/types" import type { ModeShort, StageId } from "~/modules/in-game-lists/types"; import { sourceTypes } from "~/modules/tournament-map-list-generator/constants"; import type { TournamentMaplistSource } from "~/modules/tournament-map-list-generator/types"; -import { seededRandom } from "~/modules/tournament-map-list-generator/utils"; import { logger } from "~/utils/logger"; +import { seededRandom } from "~/utils/random"; import type { TournamentLoaderData } from "../tournament/loaders/to.$id.server"; import type { FindMatchById } from "../tournament-bracket/queries/findMatchById.server"; import type { Standing } from "./core/Bracket"; @@ -40,11 +40,11 @@ const NUM_MAP = { export function resolveRoomPass(seed: number | string) { let pass = "5"; for (let i = 0; i < 3; i++) { - const { shuffle } = seededRandom(`${seed}-${i}`); + const { seededShuffle } = seededRandom(`${seed}-${i}`); const key = pass[i] as keyof typeof NUM_MAP; const opts = NUM_MAP[key]; - const next = shuffle(opts)[0]; + const next = seededShuffle(opts)[0]; pass += next; } diff --git a/app/features/user-page/UserRepository.server.ts b/app/features/user-page/UserRepository.server.ts index be799888c..8214c67c3 100644 --- a/app/features/user-page/UserRepository.server.ts +++ b/app/features/user-page/UserRepository.server.ts @@ -835,6 +835,8 @@ export function updateProfile(args: UpdateProfileArgs) { showDiscordUniqueName: args.showDiscordUniqueName, commissionText: args.commissionText, commissionsOpen: args.commissionsOpen, + commissionsOpenedAt: + args.commissionsOpen === 1 ? databaseTimestampNow() : null, }) .where("id", "=", args.userId) .returning(["User.id", "User.customUrl", "User.discordId"]) diff --git a/app/features/user-page/routes/u.$identifier.edit.tsx b/app/features/user-page/routes/u.$identifier.edit.tsx index 41044eaea..2d0505f5a 100644 --- a/app/features/user-page/routes/u.$identifier.edit.tsx +++ b/app/features/user-page/routes/u.$identifier.edit.tsx @@ -472,6 +472,9 @@ function CommissionsOpenToggle({ onChange={setChecked} name="commissionsOpen" /> + + {t("user:forms.commissionsOpen.info")} + ); } diff --git a/app/modules/tournament-map-list-generator/balanced-map-list.ts b/app/modules/tournament-map-list-generator/balanced-map-list.ts index 046dc3e82..447dc896e 100644 --- a/app/modules/tournament-map-list-generator/balanced-map-list.ts +++ b/app/modules/tournament-map-list-generator/balanced-map-list.ts @@ -3,6 +3,7 @@ import { err, ok } from "neverthrow"; import { stageIds } from "~/modules/in-game-lists/stage-ids"; import invariant from "~/utils/invariant"; import { logger } from "~/utils/logger"; +import { seededRandom } from "~/utils/random"; import type { ModeShort, StageId } from "../in-game-lists/types"; import { DEFAULT_MAP_POOL } from "./constants"; import type { @@ -10,7 +11,6 @@ import type { TournamentMaplistInput, TournamentMaplistSource, } from "./types"; -import { seededRandom } from "./utils"; type ModeWithStageAndScore = TournamentMapListMap & { score: number }; @@ -50,8 +50,8 @@ function generateWithInput( > { validateInput(input); - const { shuffle } = seededRandom(input.seed); - const stages = shuffle(resolveCommonStages()); + const { seededShuffle } = seededRandom(input.seed); + const stages = seededShuffle(resolveCommonStages()); const mapList: Array = []; const bestMapList: { maps?: Array; score: number } = { score: Number.POSITIVE_INFINITY, @@ -165,7 +165,7 @@ function generateWithInput( // no overlap so we need to use a random map for tiebreaker if (tournamentIsOneModeOnly()) { - return shuffle([...stageIds]) + return seededShuffle([...stageIds]) .filter( (stageId) => !input.teams[0].maps.hasStage(stageId) && diff --git a/app/modules/tournament-map-list-generator/index.ts b/app/modules/tournament-map-list-generator/index.ts index 8adebdbc1..623ce7529 100644 --- a/app/modules/tournament-map-list-generator/index.ts +++ b/app/modules/tournament-map-list-generator/index.ts @@ -6,4 +6,3 @@ export type { TournamentMaplistInput, TournamentMaplistSource, } from "./types"; -export { seededRandom } from "./utils"; diff --git a/app/modules/tournament-map-list-generator/starter-map.ts b/app/modules/tournament-map-list-generator/starter-map.ts index 0ef280f2b..593d55195 100644 --- a/app/modules/tournament-map-list-generator/starter-map.ts +++ b/app/modules/tournament-map-list-generator/starter-map.ts @@ -4,10 +4,10 @@ import { stageIds } from "~/modules/in-game-lists/stage-ids"; import { logger } from "~/utils/logger"; +import { seededRandom } from "~/utils/random"; import { modesShort } from "../in-game-lists/modes"; import type { ModeWithStage } from "../in-game-lists/types"; import type { TournamentMapListMap, TournamentMaplistInput } from "./types"; -import { seededRandom } from "./utils"; type StarterMapArgs = Pick< TournamentMaplistInput, @@ -15,7 +15,7 @@ type StarterMapArgs = Pick< >; export function starterMap(args: StarterMapArgs): Array { - const { shuffle } = seededRandom(args.seed); + const { seededShuffle } = seededRandom(args.seed); const isRecentlyPlayed = (map: ModeWithStage) => { return Boolean( @@ -27,7 +27,7 @@ export function starterMap(args: StarterMapArgs): Array { const commonMap = resolveRandomCommonMap( args.teams, - shuffle, + seededShuffle, isRecentlyPlayed, ); if (commonMap) { @@ -35,7 +35,7 @@ export function starterMap(args: StarterMapArgs): Array { } if (!args.tiebreakerMaps.isEmpty()) { - const tiebreakers = shuffle(args.tiebreakerMaps.stageModePairs); + const tiebreakers = seededShuffle(args.tiebreakerMaps.stageModePairs); const nonRecentTiebreaker = tiebreakers.find((tb) => !isRecentlyPlayed(tb)); const randomTiebreaker = nonRecentTiebreaker ?? tiebreakers[0]; @@ -50,7 +50,7 @@ export function starterMap(args: StarterMapArgs): Array { // should be only one mode here always but just in case // making it capable of handling many modes too - const allAvailableMaps = shuffle( + const allAvailableMaps = seededShuffle( args.modesIncluded .sort((a, b) => modesShort.indexOf(a) - modesShort.indexOf(b)) .flatMap((mode) => stageIds.map((stageId) => ({ mode, stageId }))), diff --git a/app/routines/closeExpiredCommissions.ts b/app/routines/closeExpiredCommissions.ts new file mode 100644 index 000000000..d875f9fb5 --- /dev/null +++ b/app/routines/closeExpiredCommissions.ts @@ -0,0 +1,54 @@ +import { sub } from "date-fns"; +import { dateToDatabaseTimestamp } from "~/utils/dates"; +import { logger } from "~/utils/logger"; +import { db } from "../db/sql"; +import { notify } from "../features/notifications/core/notify.server"; +import { Routine } from "./routine.server"; + +export const CloseExpiredCommissionsRoutine = new Routine({ + name: "CloseExpiredCommissions", + func: async () => { + const usersWithExpiredCommissions = await db + .selectFrom("User") + .select(["id", "discordId"]) + .where("commissionsOpen", "=", 1) + .where("commissionsOpenedAt", "is not", null) + .where( + "commissionsOpenedAt", + "<=", + dateToDatabaseTimestamp(sub(new Date(), { months: 1 })), + ) + .execute(); + + if (usersWithExpiredCommissions.length === 0) { + return; + } + + const userIds = usersWithExpiredCommissions.map((user) => user.id); + + await db + .updateTable("User") + .set({ + commissionsOpen: 0, + commissionsOpenedAt: null, + }) + .where("id", "in", userIds) + .execute(); + + logger.info( + `Closed commissions for ${usersWithExpiredCommissions.length} users`, + ); + + for (const user of usersWithExpiredCommissions) { + await notify({ + notification: { + type: "COMMISSIONS_CLOSED", + meta: { + discordId: user.discordId, + }, + }, + userIds: [user.id], + }); + } + }, +}); diff --git a/app/routines/list.server.ts b/app/routines/list.server.ts index 3daca68bf..01f22a7c9 100644 --- a/app/routines/list.server.ts +++ b/app/routines/list.server.ts @@ -1,3 +1,4 @@ +import { CloseExpiredCommissionsRoutine } from "./closeExpiredCommissions"; import { DeleteOldNotificationsRoutine } from "./deleteOldNotifications"; import { DeleteOldTrustRoutine } from "./deleteOldTrusts"; import { NotifyCheckInStartRoutine } from "./notifyCheckInStart"; @@ -20,4 +21,8 @@ export const everyHourAt30 = [ ]; /** List of Routines that should occur daily */ -export const daily = [DeleteOldTrustRoutine, DeleteOldNotificationsRoutine]; +export const daily = [ + DeleteOldTrustRoutine, + DeleteOldNotificationsRoutine, + CloseExpiredCommissionsRoutine, +]; diff --git a/app/utils/random.test.ts b/app/utils/random.test.ts new file mode 100644 index 000000000..211db11c0 --- /dev/null +++ b/app/utils/random.test.ts @@ -0,0 +1,142 @@ +import { describe, expect, it } from "vitest"; +import { seededRandom } from "./random"; + +describe("seededRandom", () => { + describe("random", () => { + it("produces same values for same seed", () => { + const rng1 = seededRandom("test-seed"); + const rng2 = seededRandom("test-seed"); + + expect(rng1.random()).toBe(rng2.random()); + expect(rng1.random()).toBe(rng2.random()); + expect(rng1.random()).toBe(rng2.random()); + }); + + it("produces different values for different seeds", () => { + const rng1 = seededRandom("seed-1"); + const rng2 = seededRandom("seed-2"); + + expect(rng1.random()).not.toBe(rng2.random()); + }); + + it("returns values between 0 and 1 by default", () => { + const rng = seededRandom("test"); + for (let i = 0; i < 100; i++) { + const value = rng.random(); + expect(value).toBeGreaterThanOrEqual(0); + expect(value).toBeLessThan(1); + } + }); + + it("returns values between lo and hi when both provided", () => { + const rng = seededRandom("test"); + for (let i = 0; i < 100; i++) { + const value = rng.random(5, 10); + expect(value).toBeGreaterThanOrEqual(5); + expect(value).toBeLessThan(10); + } + }); + + it("returns values between 0 and hi when only hi provided", () => { + const rng = seededRandom("test"); + for (let i = 0; i < 100; i++) { + const value = rng.random(5); + expect(value).toBeGreaterThanOrEqual(0); + expect(value).toBeLessThan(5); + } + }); + }); + + describe("randomInteger", () => { + it("produces same values for same seed", () => { + const rng1 = seededRandom("test-seed"); + const rng2 = seededRandom("test-seed"); + + expect(rng1.randomInteger(10)).toBe(rng2.randomInteger(10)); + expect(rng1.randomInteger(10)).toBe(rng2.randomInteger(10)); + expect(rng1.randomInteger(10)).toBe(rng2.randomInteger(10)); + }); + + it("produces different values for different seeds", () => { + const rng1 = seededRandom("seed-1"); + const rng2 = seededRandom("seed-2"); + + expect(rng1.randomInteger(100)).not.toBe(rng2.randomInteger(100)); + }); + + it("returns integers between 0 and hi when only hi provided", () => { + const rng = seededRandom("test"); + for (let i = 0; i < 100; i++) { + const value = rng.randomInteger(10); + expect(Number.isInteger(value)).toBe(true); + expect(value).toBeGreaterThanOrEqual(0); + expect(value).toBeLessThan(10); + } + }); + + it("returns integers between lo and hi when both provided", () => { + const rng = seededRandom("test"); + for (let i = 0; i < 100; i++) { + const value = rng.randomInteger(5, 10); + expect(Number.isInteger(value)).toBe(true); + expect(value).toBeGreaterThanOrEqual(5); + expect(value).toBeLessThan(10); + } + }); + }); + + describe("seededShuffle", () => { + it("produces same shuffle for same seed", () => { + const array = [1, 2, 3, 4, 5]; + const rng1 = seededRandom("test-seed"); + const rng2 = seededRandom("test-seed"); + + expect(rng1.seededShuffle(array)).toEqual(rng2.seededShuffle(array)); + }); + + it("produces different shuffles for different seeds", () => { + const array = [1, 2, 3, 4, 5]; + const rng1 = seededRandom("seed-1"); + const rng2 = seededRandom("seed-2"); + + expect(rng1.seededShuffle(array)).not.toEqual(rng2.seededShuffle(array)); + }); + + it("does not mutate original array", () => { + const array = [1, 2, 3, 4, 5]; + const original = [...array]; + const rng = seededRandom("test"); + + rng.seededShuffle(array); + + expect(array).toEqual(original); + }); + + it("returns array with same elements", () => { + const array = [1, 2, 3, 4, 5]; + const rng = seededRandom("test"); + + const shuffled = rng.seededShuffle(array); + + expect(shuffled.sort()).toEqual(array.sort()); + }); + + it("handles empty array", () => { + const array: number[] = []; + const rng = seededRandom("test"); + + const shuffled = rng.seededShuffle(array); + + expect(shuffled).toEqual([]); + }); + + it("handles single element array", () => { + const array = [1]; + const rng = seededRandom("test"); + + const shuffled = rng.seededShuffle(array); + + expect(shuffled).toEqual([1]); + }); + }); +}); diff --git a/app/modules/tournament-map-list-generator/utils.ts b/app/utils/random.ts similarity index 52% rename from app/modules/tournament-map-list-generator/utils.ts rename to app/utils/random.ts index 03abd5610..ce1167872 100644 --- a/app/modules/tournament-map-list-generator/utils.ts +++ b/app/utils/random.ts @@ -1,5 +1,3 @@ -// https://stackoverflow.com/a/68523152 - function cyrb128(str: string) { let h1 = 1779033703; let h2 = 3144134277; @@ -36,27 +34,38 @@ function mulberry32(a: number) { }; } +/** + * Creates a seeded pseudo-random number generator that produces consistent results for the same seed. + * Uses mulberry32 algorithm with cyrb128 hash function for string-to-number conversion. + * + * @param seed - String seed value (e.g., "2025-1-8" for daily rotation) + * @returns Object with random number generation methods: + * - `random(lo?, hi?)` - Returns random float between lo (inclusive) and hi (exclusive) + * - `randomInteger(lo, hi?)` - Returns random integer between lo (inclusive) and hi (exclusive) + * - `seededShuffle(array)` - Returns shuffled copy of array using seeded Fisher-Yates algorithm + * + * @example + * const { seededShuffle } = seededRandom("2025-1-8"); + * const shuffled = seededShuffle([1, 2, 3, 4, 5]); + */ export const seededRandom = (seed: string) => { const rng = mulberry32(cyrb128(seed)[0]); - const rnd = (lo: number, hi?: number, defaultHi = 1) => { - if (hi === undefined) { - // biome-ignore lint/style/noParameterAssign: biome migration - hi = lo === undefined ? defaultHi : lo; - // biome-ignore lint/style/noParameterAssign: biome migration - lo = 0; - } + const random = (lo?: number, hi?: number, defaultHi = 1) => { + const actualLo = hi === undefined ? 0 : (lo ?? 0); + const actualHi = hi === undefined ? (lo ?? defaultHi) : hi; - return rng() * (hi - lo) + lo; + return rng() * (actualHi - actualLo) + actualLo; }; - const rndInt = (lo: number, hi?: number) => Math.floor(rnd(lo, hi, 2)); + const randomInteger = (lo: number, hi?: number) => + Math.floor(random(lo, hi, 2)); - const shuffle = (o: T[]) => { + const seededShuffle = (o: T[]) => { const a = o.slice(); for (let i = a.length - 1; i > 0; i--) { - const j = rndInt(i + 1); + const j = randomInteger(i + 1); const x = a[i]; a[i] = a[j]!; a[j] = x!; @@ -65,5 +74,5 @@ export const seededRandom = (seed: string) => { return a; }; - return { rnd, rndInt, shuffle }; + return { random, randomInteger, seededShuffle }; }; diff --git a/db-test.sqlite3 b/db-test.sqlite3 index 53dc96261..6f9dcc5f4 100644 Binary files a/db-test.sqlite3 and b/db-test.sqlite3 differ diff --git a/locales/da/art.json b/locales/da/art.json index 603e80e9d..23d7fff7a 100644 --- a/locales/da/art.json +++ b/locales/da/art.json @@ -11,6 +11,8 @@ "commissionsClosed": "Lukket for bestillinger", "openCommissionsOnly": "Vis kunstnere med åbne bestillinger", "gainPerms": "Lav venligt et opslad til vores helpdesk på vores Discord-server for at få tilladelse til at uploade kunst. Bemærk venligt, at du skal være kunstneren af det kunst, som du uploader, og kun Splatoon-relateret kunst tillades.", + "tabs.recentlyUploaded": "", + "tabs.showcase": "", "forms.caveats": "Vær opmærksom på følgende: 1) Upload kun Splatoon-kunst 2) upload kun kunst, som du selv har lavet 3) Ingen NSFW-kunst. 4) Kunst skal igennem en valideringsproces før det vises til andre brugere.", "forms.description.title": "Beskrivelse", "forms.linkedUsers.title": "tilknyttede brugere", diff --git a/locales/da/common.json b/locales/da/common.json index 0310bcbda..1e1ad0dd1 100644 --- a/locales/da/common.json +++ b/locales/da/common.json @@ -77,6 +77,8 @@ "notifications.text.SCRIM_SCHEDULED": "", "notifications.title.SCRIM_CANCELED": "", "notifications.text.SCRIM_CANCELED": "", + "notifications.title.COMMISSIONS_CLOSED": "", + "notifications.text.COMMISSIONS_CLOSED": "", "auth.errors.aborted": "logindforsøg afbrudt", "auth.errors.failed": "Loginforsøg fejlet", "auth.errors.discordPermissions": "Før at du kan oprette en profil på sendou.ink, skal sendou.ink have adgang til din Discordprofils navn, brugerbillede og sociale forbindelser (de sociale medier, som du har tilknyttet din discordprofil).", diff --git a/locales/da/faq.json b/locales/da/faq.json index a333b54ed..1a95557e0 100644 --- a/locales/da/faq.json +++ b/locales/da/faq.json @@ -1,5 +1,6 @@ { "q1": "Hvad er Plus Serveren?", + "a1": "", "q2": "Hvordan får jeg et præmiemærke til min begivenhed?", "a2": "", "q3": "Hvordan opdaterer jeg min avatar eller brugernavn?", diff --git a/locales/da/q.json b/locales/da/q.json index 9f211941d..9c622e442 100644 --- a/locales/da/q.json +++ b/locales/da/q.json @@ -67,6 +67,7 @@ "settings.sounds.likeReceived": "", "settings.sounds.groupNewMember": "", "settings.sounds.matchStarted": "", + "settings.sounds.tournamentMatchStarted": "", "settings.mapPool.notOk": "", "settings.misc.header": "", "settings.banned": "", diff --git a/locales/da/tournament.json b/locales/da/tournament.json index 458f60b8f..49ea04689 100644 --- a/locales/da/tournament.json +++ b/locales/da/tournament.json @@ -137,5 +137,6 @@ "progression.error.NAME_MISSING": "", "progression.error.NEGATIVE_PROGRESSION": "", "progression.error.NO_SE_SOURCE": "", - "progression.error.NO_DE_POSITIVE": "" + "progression.error.NO_DE_POSITIVE": "", + "progression.error.SWISS_EARLY_ADVANCE_NO_DESTINATION": "" } diff --git a/locales/da/user.json b/locales/da/user.json index 8d86a6dee..3778cc05c 100644 --- a/locales/da/user.json +++ b/locales/da/user.json @@ -17,6 +17,7 @@ "forms.showDiscordUniqueName": "Vis Discord-brugernavn", "forms.showDiscordUniqueName.info": "Vil du gøre dit unikke Discord-brugernavn ({{discordUniqueName}}) synligt for offentligheden?", "forms.commissionsOpen": "Åben for bestillinger", + "forms.commissionsOpen.info": "", "forms.commissionText": "info om bestilling", "forms.commissionText.info": "Pris, åbne pladser eller andre relevante informationer der er relateret til at afgive en bestilling til dig.", "forms.customName.info": "Hvis feltet ikke udfyldes bruges dit discordbrugernavn: \"{{discordName}}\"", diff --git a/locales/de/art.json b/locales/de/art.json index 39f282c6c..195c7f423 100644 --- a/locales/de/art.json +++ b/locales/de/art.json @@ -11,6 +11,8 @@ "commissionsClosed": "", "openCommissionsOnly": "", "gainPerms": "", + "tabs.recentlyUploaded": "", + "tabs.showcase": "", "forms.caveats": "", "forms.description.title": "", "forms.linkedUsers.title": "", diff --git a/locales/de/common.json b/locales/de/common.json index bec07d624..fef83c61c 100644 --- a/locales/de/common.json +++ b/locales/de/common.json @@ -77,6 +77,8 @@ "notifications.text.SCRIM_SCHEDULED": "", "notifications.title.SCRIM_CANCELED": "", "notifications.text.SCRIM_CANCELED": "", + "notifications.title.COMMISSIONS_CLOSED": "", + "notifications.text.COMMISSIONS_CLOSED": "", "auth.errors.aborted": "Einloggen abgebrochen", "auth.errors.failed": "Einloggen fehlgeschlagen", "auth.errors.discordPermissions": "Für dein sendou.ink-Profil benötigt die Seite Zugriff auf den Namen, Avatar und verbundene Social-Media-Accounts in deinem Discord-Profil.", diff --git a/locales/de/faq.json b/locales/de/faq.json index 499221ace..7c8f68673 100644 --- a/locales/de/faq.json +++ b/locales/de/faq.json @@ -1,5 +1,6 @@ { "q1": "Was ist der Plus Server?", + "a1": "", "q2": "Wie erhalte ich ein Abzeichen als Preis für mein Event?", "a2": "", "q3": "Wie aktualisiere ich meinen Avatar oder Nutzernamen?", diff --git a/locales/de/q.json b/locales/de/q.json index 994b9e2db..8cc283fe2 100644 --- a/locales/de/q.json +++ b/locales/de/q.json @@ -67,6 +67,7 @@ "settings.sounds.likeReceived": "", "settings.sounds.groupNewMember": "", "settings.sounds.matchStarted": "", + "settings.sounds.tournamentMatchStarted": "", "settings.mapPool.notOk": "", "settings.misc.header": "", "settings.banned": "", diff --git a/locales/de/tournament.json b/locales/de/tournament.json index 201bd8f4c..a17c3ae1a 100644 --- a/locales/de/tournament.json +++ b/locales/de/tournament.json @@ -137,5 +137,6 @@ "progression.error.NAME_MISSING": "", "progression.error.NEGATIVE_PROGRESSION": "", "progression.error.NO_SE_SOURCE": "", - "progression.error.NO_DE_POSITIVE": "" + "progression.error.NO_DE_POSITIVE": "", + "progression.error.SWISS_EARLY_ADVANCE_NO_DESTINATION": "" } diff --git a/locales/de/user.json b/locales/de/user.json index 4cbb680f1..bafe936b6 100644 --- a/locales/de/user.json +++ b/locales/de/user.json @@ -17,6 +17,7 @@ "forms.showDiscordUniqueName": "", "forms.showDiscordUniqueName.info": "", "forms.commissionsOpen": "", + "forms.commissionsOpen.info": "", "forms.commissionText": "", "forms.commissionText.info": "", "forms.customName.info": "", diff --git a/locales/en/art.json b/locales/en/art.json index fcda61253..3643a1915 100644 --- a/locales/en/art.json +++ b/locales/en/art.json @@ -11,6 +11,8 @@ "commissionsClosed": "Commissions are closed", "openCommissionsOnly": "Show artists with open commissions", "gainPerms": "Please post on the helpdesk of our Discord to gain permissions to upload art. Note that you must be the artist of the art you are uploading and only Splatoon related art is allowed.", + "tabs.recentlyUploaded": "Recently Uploaded", + "tabs.showcase": "Showcase", "forms.caveats": "Few things to note: 1) Only upload Splatoon art 2) Only upload art you made yourself 3) No NSFW art. There is a validation process before art is shown to other users.", "forms.description.title": "Description", "forms.linkedUsers.title": "Linked users", diff --git a/locales/en/common.json b/locales/en/common.json index b79872f20..ed0ea39ec 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -77,6 +77,8 @@ "notifications.text.SCRIM_SCHEDULED": "New scrim scheduled at {{timeString}}", "notifications.title.SCRIM_CANCELED": "Scrim Canceled", "notifications.text.SCRIM_CANCELED": "The scrim at {{timeString}} was canceled", + "notifications.title.COMMISSIONS_CLOSED": "Commissions Closed", + "notifications.text.COMMISSIONS_CLOSED": "If your commissions are still open, please re-enable them", "auth.errors.aborted": "Login Aborted", "auth.errors.failed": "Login Failed", "auth.errors.discordPermissions": "For your sendou.ink profile, the site needs access to your Discord profile's name, avatar and social connections.", diff --git a/locales/en/user.json b/locales/en/user.json index 27becbb07..071a76f46 100644 --- a/locales/en/user.json +++ b/locales/en/user.json @@ -17,6 +17,7 @@ "forms.showDiscordUniqueName": "Show Discord username", "forms.showDiscordUniqueName.info": "Show your unique Discord name ({{discordUniqueName}}) publicly?", "forms.commissionsOpen": "Commissions open", + "forms.commissionsOpen.info": "Commissions automatically close and need to be re-enabled after one month to prevent stale listings", "forms.commissionText": "Commission info", "forms.commissionText.info": "Price, slots open or other info related to commissioning you", "forms.customName.info": "If missing, your Discord display name is used: \"{{discordName}}\"", diff --git a/locales/es-ES/art.json b/locales/es-ES/art.json index 84dcaee47..02d05bd09 100644 --- a/locales/es-ES/art.json +++ b/locales/es-ES/art.json @@ -12,6 +12,8 @@ "commissionsClosed": "Comisiones cerradas", "openCommissionsOnly": "Mostrar artistas con comisiones abiertas", "gainPerms": "Por favor manda mensaje en el 'helpdesk' de nuestro Discord para obtener permiso para subir arte. Debes ser el artista que creó el arte que subas, y solo se permite arte relacionada con Splatoon.", + "tabs.recentlyUploaded": "", + "tabs.showcase": "", "forms.caveats": "NOTAS: 1) Solo sube arte de Splatoon; 2) Solo sube arte que tu creaste; 3) No se permite arte inapropiada (NSFW). Hay un proceso de evaluación antes de que se muestre tu arte públicamente.", "forms.description.title": "Descripción", "forms.linkedUsers.title": "Enlaces de usuarios", diff --git a/locales/es-ES/common.json b/locales/es-ES/common.json index eb1e9b26e..bb3969af9 100644 --- a/locales/es-ES/common.json +++ b/locales/es-ES/common.json @@ -77,6 +77,8 @@ "notifications.text.SCRIM_SCHEDULED": "", "notifications.title.SCRIM_CANCELED": "", "notifications.text.SCRIM_CANCELED": "", + "notifications.title.COMMISSIONS_CLOSED": "", + "notifications.text.COMMISSIONS_CLOSED": "", "auth.errors.aborted": "Ingreso cancelado", "auth.errors.failed": "Ingreso fallido", "auth.errors.discordPermissions": "Para tu perfil en sendou.ink, el sitio requiere aceso a tu nombre en Discord, avatar, y redes sociales.", diff --git a/locales/es-ES/faq.json b/locales/es-ES/faq.json index 88f16f916..43b633c21 100644 --- a/locales/es-ES/faq.json +++ b/locales/es-ES/faq.json @@ -1,5 +1,6 @@ { "q1": "¿Qué es el Plus Server?", + "a1": "", "q2": "¿Cómo puedo obtener un premio de insignia para mi evento?", "a2": "", "q3": "¿Cómo puedo actualizar mi avatar o nombre de usuario?", diff --git a/locales/es-ES/q.json b/locales/es-ES/q.json index a59bb9314..e4c2144ca 100644 --- a/locales/es-ES/q.json +++ b/locales/es-ES/q.json @@ -67,6 +67,7 @@ "settings.sounds.likeReceived": "Has recibido un like", "settings.sounds.groupNewMember": "Nuevo miembro al grupo", "settings.sounds.matchStarted": "Comenzo el partido", + "settings.sounds.tournamentMatchStarted": "", "settings.mapPool.notOk": "Elige {{count}} mapas por modo que no evitaste para guardar tus preferencias", "settings.misc.header": "Misc", "settings.banned": "Prohibidos", diff --git a/locales/es-ES/tournament.json b/locales/es-ES/tournament.json index 76a1e7258..84061eb7c 100644 --- a/locales/es-ES/tournament.json +++ b/locales/es-ES/tournament.json @@ -138,5 +138,6 @@ "progression.error.NAME_MISSING": "", "progression.error.NEGATIVE_PROGRESSION": "", "progression.error.NO_SE_SOURCE": "", - "progression.error.NO_DE_POSITIVE": "" + "progression.error.NO_DE_POSITIVE": "", + "progression.error.SWISS_EARLY_ADVANCE_NO_DESTINATION": "" } diff --git a/locales/es-ES/user.json b/locales/es-ES/user.json index 5c5fe3e80..7ae20db1a 100644 --- a/locales/es-ES/user.json +++ b/locales/es-ES/user.json @@ -17,6 +17,7 @@ "forms.showDiscordUniqueName": "Mostrar usuario de Discord", "forms.showDiscordUniqueName.info": "¿Mostrar tu nombre de Discord ({{discordUniqueName}}) publicamente?", "forms.commissionsOpen": "Comisiones abiertas", + "forms.commissionsOpen.info": "", "forms.commissionText": "Info de comisiones", "forms.commissionText.info": "Precio, espacios abiertos, o cualquier otra información sobre tus comiciones.", "forms.customName.info": "", diff --git a/locales/es-US/art.json b/locales/es-US/art.json index 84dcaee47..02d05bd09 100644 --- a/locales/es-US/art.json +++ b/locales/es-US/art.json @@ -12,6 +12,8 @@ "commissionsClosed": "Comisiones cerradas", "openCommissionsOnly": "Mostrar artistas con comisiones abiertas", "gainPerms": "Por favor manda mensaje en el 'helpdesk' de nuestro Discord para obtener permiso para subir arte. Debes ser el artista que creó el arte que subas, y solo se permite arte relacionada con Splatoon.", + "tabs.recentlyUploaded": "", + "tabs.showcase": "", "forms.caveats": "NOTAS: 1) Solo sube arte de Splatoon; 2) Solo sube arte que tu creaste; 3) No se permite arte inapropiada (NSFW). Hay un proceso de evaluación antes de que se muestre tu arte públicamente.", "forms.description.title": "Descripción", "forms.linkedUsers.title": "Enlaces de usuarios", diff --git a/locales/es-US/common.json b/locales/es-US/common.json index 5c9b8f6b2..56007e244 100644 --- a/locales/es-US/common.json +++ b/locales/es-US/common.json @@ -77,6 +77,8 @@ "notifications.text.SCRIM_SCHEDULED": "", "notifications.title.SCRIM_CANCELED": "", "notifications.text.SCRIM_CANCELED": "", + "notifications.title.COMMISSIONS_CLOSED": "", + "notifications.text.COMMISSIONS_CLOSED": "", "auth.errors.aborted": "Ingreso cancelado", "auth.errors.failed": "Ingreso fallido", "auth.errors.discordPermissions": "Para tu perfil en sendou.ink, el sitio requiere aceso a tu nombre en Discord, avatar, y redes sociales.", diff --git a/locales/es-US/faq.json b/locales/es-US/faq.json index 88f16f916..43b633c21 100644 --- a/locales/es-US/faq.json +++ b/locales/es-US/faq.json @@ -1,5 +1,6 @@ { "q1": "¿Qué es el Plus Server?", + "a1": "", "q2": "¿Cómo puedo obtener un premio de insignia para mi evento?", "a2": "", "q3": "¿Cómo puedo actualizar mi avatar o nombre de usuario?", diff --git a/locales/es-US/q.json b/locales/es-US/q.json index ffaab8bf4..e0d3e9bb5 100644 --- a/locales/es-US/q.json +++ b/locales/es-US/q.json @@ -67,6 +67,7 @@ "settings.sounds.likeReceived": "Has recibido un like", "settings.sounds.groupNewMember": "Nuevo miembro al grupo", "settings.sounds.matchStarted": "Comenzo el partido", + "settings.sounds.tournamentMatchStarted": "", "settings.mapPool.notOk": "Elige {{count}} escenarios por estilo que no evitaste para guardar tus preferencias", "settings.misc.header": "Misc", "settings.banned": "Prohibidos", diff --git a/locales/es-US/tournament.json b/locales/es-US/tournament.json index 1762087a4..5544ed27f 100644 --- a/locales/es-US/tournament.json +++ b/locales/es-US/tournament.json @@ -138,5 +138,6 @@ "progression.error.NAME_MISSING": "", "progression.error.NEGATIVE_PROGRESSION": "", "progression.error.NO_SE_SOURCE": "", - "progression.error.NO_DE_POSITIVE": "" + "progression.error.NO_DE_POSITIVE": "", + "progression.error.SWISS_EARLY_ADVANCE_NO_DESTINATION": "" } diff --git a/locales/es-US/user.json b/locales/es-US/user.json index b202b445b..82208654a 100644 --- a/locales/es-US/user.json +++ b/locales/es-US/user.json @@ -17,6 +17,7 @@ "forms.showDiscordUniqueName": "Mostrar usuario de Discord", "forms.showDiscordUniqueName.info": "¿Mostrar tu nombre de Discord ({{discordUniqueName}}) publicamente?", "forms.commissionsOpen": "Comisiones abiertas", + "forms.commissionsOpen.info": "", "forms.commissionText": "Info de comisiones", "forms.commissionText.info": "Precio, espacios abiertos, o cualquier otra información sobre tus comiciones.", "forms.customName.info": "Si vacío, se mostrará tu nombre de Discord: \"{{discordName}}\"", diff --git a/locales/fr-CA/art.json b/locales/fr-CA/art.json index a479ebf2c..0c3fae8a4 100644 --- a/locales/fr-CA/art.json +++ b/locales/fr-CA/art.json @@ -12,6 +12,8 @@ "commissionsClosed": "N'accepte pas les commissions", "openCommissionsOnly": "Ne montrer que les artistes qui acceptent les commissions", "gainPerms": "", + "tabs.recentlyUploaded": "", + "tabs.showcase": "", "forms.caveats": "Quelques notes: 1) Doit être en rapport avec Splatoon 2) Doit avoir été créé par vous 3) Pas de contenu NSFW/explicite. Il y a une procédure de validation avant que votre poste puisse être vu par les autres.", "forms.description.title": "Description", "forms.linkedUsers.title": "Utilisateurs liés", diff --git a/locales/fr-CA/common.json b/locales/fr-CA/common.json index e2b39ea73..04fcce442 100644 --- a/locales/fr-CA/common.json +++ b/locales/fr-CA/common.json @@ -77,6 +77,8 @@ "notifications.text.SCRIM_SCHEDULED": "", "notifications.title.SCRIM_CANCELED": "", "notifications.text.SCRIM_CANCELED": "", + "notifications.title.COMMISSIONS_CLOSED": "", + "notifications.text.COMMISSIONS_CLOSED": "", "auth.errors.aborted": "Connexion abandonnée", "auth.errors.failed": "Connexion échouée", "auth.errors.discordPermissions": "Pour mettre en place votre profil, sendou.ink a besoin de votre nom de profil Discord, de votre avatar et de vos réseaux connectés.", diff --git a/locales/fr-CA/faq.json b/locales/fr-CA/faq.json index c05ec643f..f35cdbc47 100644 --- a/locales/fr-CA/faq.json +++ b/locales/fr-CA/faq.json @@ -1,5 +1,6 @@ { "q1": "Qu'est-ce que le Plus Server ?", + "a1": "", "q2": "Comment obtenir un badge pour mon événement ?", "a2": "", "q3": "Comment mettre à jour mon pseudo ou mon avatar ?", diff --git a/locales/fr-CA/q.json b/locales/fr-CA/q.json index ac8203954..1a643a335 100644 --- a/locales/fr-CA/q.json +++ b/locales/fr-CA/q.json @@ -67,6 +67,7 @@ "settings.sounds.likeReceived": "", "settings.sounds.groupNewMember": "", "settings.sounds.matchStarted": "", + "settings.sounds.tournamentMatchStarted": "", "settings.mapPool.notOk": "", "settings.misc.header": "", "settings.banned": "", diff --git a/locales/fr-CA/tournament.json b/locales/fr-CA/tournament.json index a30db209c..7e6c5b778 100644 --- a/locales/fr-CA/tournament.json +++ b/locales/fr-CA/tournament.json @@ -138,5 +138,6 @@ "progression.error.NAME_MISSING": "", "progression.error.NEGATIVE_PROGRESSION": "", "progression.error.NO_SE_SOURCE": "", - "progression.error.NO_DE_POSITIVE": "" + "progression.error.NO_DE_POSITIVE": "", + "progression.error.SWISS_EARLY_ADVANCE_NO_DESTINATION": "" } diff --git a/locales/fr-CA/user.json b/locales/fr-CA/user.json index 8720da5b9..81cab5775 100644 --- a/locales/fr-CA/user.json +++ b/locales/fr-CA/user.json @@ -17,6 +17,7 @@ "forms.showDiscordUniqueName": "Montrer le pseudo Discord", "forms.showDiscordUniqueName.info": "Show your unique Discord name ({{discordUniqueName}}) publicly?", "forms.commissionsOpen": "Commissions acceptées", + "forms.commissionsOpen.info": "", "forms.commissionText": "Info pour les commissions", "forms.commissionText.info": "Prix, disponibilités et tout autres info nécéssaires", "forms.customName.info": "", diff --git a/locales/fr-EU/art.json b/locales/fr-EU/art.json index d9e708a32..e300bb7e6 100644 --- a/locales/fr-EU/art.json +++ b/locales/fr-EU/art.json @@ -12,6 +12,8 @@ "commissionsClosed": "N'accepte pas les commissions", "openCommissionsOnly": "Ne montrer que les artistes qui acceptent les commissions", "gainPerms": "Vous pouvez demander dans le salon ''helpdesk'' sur notre discord pour avoir cette permission. Note: vous devez êtres l'artist pour publier votre création, celle-ci doit être seulement en rapport avec Splatoon.", + "tabs.recentlyUploaded": "", + "tabs.showcase": "", "forms.caveats": "Quelques notes: 1) Doit être en rapport avec Splatoon 2) Doit avoir été créé par vous 3) Pas de contenu NSFW/explicite. Il y a une procédure de validation avant que votre poste puisse être vu par les autres.", "forms.description.title": "Description", "forms.linkedUsers.title": "Utilisateurs liés", diff --git a/locales/fr-EU/common.json b/locales/fr-EU/common.json index 060d9d29f..dcc8aa857 100644 --- a/locales/fr-EU/common.json +++ b/locales/fr-EU/common.json @@ -77,6 +77,8 @@ "notifications.text.SCRIM_SCHEDULED": "Nouveau scrim programmé à {{timeString}}", "notifications.title.SCRIM_CANCELED": "", "notifications.text.SCRIM_CANCELED": "", + "notifications.title.COMMISSIONS_CLOSED": "", + "notifications.text.COMMISSIONS_CLOSED": "", "auth.errors.aborted": "Connexion abandonnée", "auth.errors.failed": "Connexion échouée", "auth.errors.discordPermissions": "Pour mettre en place votre profil, sendou.ink a besoin de votre nom de profil Discord, de votre avatar et de vos réseaux connectés.", diff --git a/locales/fr-EU/faq.json b/locales/fr-EU/faq.json index b443b41b4..eea0c7368 100644 --- a/locales/fr-EU/faq.json +++ b/locales/fr-EU/faq.json @@ -1,5 +1,6 @@ { "q1": "Qu'est-ce que le Plus Server ?", + "a1": "", "q2": "Comment obtenir un badge pour mon événement ?", "a2": "Depuis septembre 2024, toute personne ayant les compétences nécessaires peut réaliser un badge. Consultez le lien en bas de la page des badges pour plus d'informations.", "q3": "Comment mettre à jour mon pseudo ou mon avatar ?", diff --git a/locales/fr-EU/q.json b/locales/fr-EU/q.json index 1ced46ec8..aee9f0b0d 100644 --- a/locales/fr-EU/q.json +++ b/locales/fr-EU/q.json @@ -19,7 +19,6 @@ "privateNote.sentiment.NEUTRAL": "Neutre", "privateNote.sentiment.NEGATIVE": "Negatif", "privateNote.delete.header": "Enlever votre note à propos de {{name}}?", - "front.cities.la": "Los Angeles", "front.cities.nyc": "New York", "front.cities.paris": "Paris", @@ -51,7 +50,6 @@ "front.seasonOpen": "La saison {{nth}} est ouverte", "front.preview": "Regarder qui est dans la queue sans la rejoindre", "front.preview.explanation": "Cette fonctionnalité n'est disponible que pour les utilisateurs de niveau Supporter (ou supérieur) sur le patreon de sendou.ink.", - "settings.maps.header": "Stages et modes", "settings.maps.avoid": "Détester", "settings.maps.prefer": "Préférer", @@ -69,6 +67,7 @@ "settings.sounds.likeReceived": "Invitation accepté", "settings.sounds.groupNewMember": "Nouveau membre dans le groupe", "settings.sounds.matchStarted": "Le match a commencé", + "settings.sounds.tournamentMatchStarted": "", "settings.mapPool.notOk": "Sélectionner {{count}} stages par mode que vous n'avez pas évité pour enregistrer vos préférences", "settings.misc.header": "Divers", "settings.banned": "Bannis", @@ -78,7 +77,6 @@ "settings.trusted.trustedExplanation": "Les utilisateurs de confiance peuvent vous ajouter directement à des groupes et à des équipes de tournoi. S'il est supprimé de cette liste, vous devrez à l'avenir vous inscrire via un lien qu'il aura partager.", "settings.trusted.noTrustedExplanation": "Vous ne faites actuellement confiance à aucun utilisateur. Vous pouvez ajouter des utilisateurs de confiance lorsque vous rejoignez leur groupe SendouQ ou leur équipe de tournoi via un lien. Les utilisateurs de confiance peuvent vous ajouter directement à des groupes et à des équipes de tournoi.", "settings.trusted.teamExplanation": "En plus des utilisateurs ci-dessus, un membre de votre équipe <2>{{name}} peut vous ajouter directement.", - "looking.joiningGroupError": "Avant de rejoindre un nouveau groupe, quitté celui actuel", "looking.goToSettingsPrompt": "Pour aider votre recherche de groupe, selectionner vos armes et votre statut vocal dans les paramètres", "looking.inactiveGroup.soon": "Le groupe a été marqué comme inactif. Recherchez-vous toujours?", @@ -122,7 +120,6 @@ "looking.allTiers": "Tout les ranks", "looking.joinQPrompt": "Rejoindre la queue pour trouvez un groupe", "looking.range.or": "ou", - "match.header": "Match #{{number}}", "match.spInfo": "Les SP après que les deux teams est reportées le même résultat", "match.dispute.button": "Dispute?", @@ -165,14 +162,11 @@ "match.outcome.loss": "Perdu", "match.screen.ban": "Les armes avec {{special}} ne sont pas autoriser pendant ce match", "match.screen.allowed": "Les armes avec {{special}} sont autoriser pendant ce match", - "preparing.joinQ": "Rejoindre la queue", - "tiers.currentCriteria": "Critères actuels", "tiers.info.p1": "Par exemple, Les Léviathans font partie des 5 % des meilleurs joueurs. Le diamant est le top 15%, etc.", "tiers.info.p2": "Note: personne n'a le rang Léviathan avant qu'il n'y ait au moins {{usersMin}} joueurs dans le classement (ou {{teamsMin}} pour les équipes)", "tiers.info.p3": "Chaque rang a également un niveau '+' (voir BRONZE+ comme l'exemple ci-dessous). Cela signifie que vous faites partie des 50 % supérieurs de ce classement.", - "streams.noStreams": "Pas de match stream pour le moment", "streams.ownStreamInfo": "Vous ne voyez pas votre stream? Assurez-vous que votre compte Twitch est lié et que le jeu est défini sur Splatoon 3.", "streams.ownStreamInfo.linkText": "Consultez la FAQ pour plus d'informations sur la façon de lier votre compte Twitch." diff --git a/locales/fr-EU/tournament.json b/locales/fr-EU/tournament.json index 0264460cf..557c5d1fd 100644 --- a/locales/fr-EU/tournament.json +++ b/locales/fr-EU/tournament.json @@ -138,5 +138,6 @@ "progression.error.NAME_MISSING": "Bracket name missing", "progression.error.NEGATIVE_PROGRESSION": "Negative progression only possible for double elimination", "progression.error.NO_SE_SOURCE": "Single elimination is not a valid source bracket", - "progression.error.NO_DE_POSITIVE": "Double elimination is not valid for positive progression" + "progression.error.NO_DE_POSITIVE": "Double elimination is not valid for positive progression", + "progression.error.SWISS_EARLY_ADVANCE_NO_DESTINATION": "" } diff --git a/locales/fr-EU/user.json b/locales/fr-EU/user.json index 222873072..a5f511ab6 100644 --- a/locales/fr-EU/user.json +++ b/locales/fr-EU/user.json @@ -17,6 +17,7 @@ "forms.showDiscordUniqueName": "Montrer le pseudo Discord", "forms.showDiscordUniqueName.info": "Show your unique Discord name ({{discordUniqueName}}) publicly?", "forms.commissionsOpen": "Commissions acceptées", + "forms.commissionsOpen.info": "", "forms.commissionText": "Info pour les commissions", "forms.commissionText.info": "Prix, disponibilités et tout autres info nécéssaires", "forms.customName.info": "Si il n'est pas présent, votre pseudo discord est utilisé: \"{{discordName}}\"", diff --git a/locales/he/art.json b/locales/he/art.json index b27915d5a..a1f5f1bfd 100644 --- a/locales/he/art.json +++ b/locales/he/art.json @@ -12,6 +12,8 @@ "commissionsClosed": "בקשות סגורות", "openCommissionsOnly": "הראה אומנים עם בקשות פתוחות", "gainPerms": "נא לכתוב בערוץ helpdesk בדיסקורד כדי לקבל הרשאות להעלות ציורים. שימו לב שאתם חייבים להיות יוצר הציור ורק אמנות הקשורה ל-Splatoon מותרת..", + "tabs.recentlyUploaded": "", + "tabs.showcase": "", "forms.caveats": "כמה הבהרות: 1) רק להעלות ציור של Splatoon 2) רק להעלות ציור שנעשתה על ידכם 3) בלי NSFW. יש תהליך בדיקה לפני שציור מופיעה למשתמשים אחרים.", "forms.description.title": "תיאור", "forms.linkedUsers.title": "תיוג משתמשים", diff --git a/locales/he/common.json b/locales/he/common.json index c696fef60..15147f57e 100644 --- a/locales/he/common.json +++ b/locales/he/common.json @@ -77,6 +77,8 @@ "notifications.text.SCRIM_SCHEDULED": "", "notifications.title.SCRIM_CANCELED": "", "notifications.text.SCRIM_CANCELED": "", + "notifications.title.COMMISSIONS_CLOSED": "", + "notifications.text.COMMISSIONS_CLOSED": "", "auth.errors.aborted": "הכניסה בוטלה", "auth.errors.failed": "הכניסה נכשלה", "auth.errors.discordPermissions": "עבור פרופיל sendou.ink שלך, האתר זקוק לגישה לשם, הפרופיל והקשרים החברתיים של פרופיל ה-Discord שלך.", diff --git a/locales/he/faq.json b/locales/he/faq.json index a09d139b9..fe7077223 100644 --- a/locales/he/faq.json +++ b/locales/he/faq.json @@ -1,5 +1,6 @@ { "q1": "מה זה השרת פלוס?", + "a1": "", "q2": "איך מקבלים פרס תג לאירוע שלי?", "a2": "", "q3": "איך לעדכן את הפרופיל או את שם המשתמש שלי?", diff --git a/locales/he/q.json b/locales/he/q.json index 71c55aa6b..792d420eb 100644 --- a/locales/he/q.json +++ b/locales/he/q.json @@ -67,6 +67,7 @@ "settings.sounds.likeReceived": "", "settings.sounds.groupNewMember": "", "settings.sounds.matchStarted": "", + "settings.sounds.tournamentMatchStarted": "", "settings.mapPool.notOk": "", "settings.misc.header": "", "settings.banned": "", diff --git a/locales/he/tournament.json b/locales/he/tournament.json index 336813592..65755f9d2 100644 --- a/locales/he/tournament.json +++ b/locales/he/tournament.json @@ -138,5 +138,6 @@ "progression.error.NAME_MISSING": "", "progression.error.NEGATIVE_PROGRESSION": "", "progression.error.NO_SE_SOURCE": "", - "progression.error.NO_DE_POSITIVE": "" + "progression.error.NO_DE_POSITIVE": "", + "progression.error.SWISS_EARLY_ADVANCE_NO_DESTINATION": "" } diff --git a/locales/he/user.json b/locales/he/user.json index 79d925434..a7ce6c55f 100644 --- a/locales/he/user.json +++ b/locales/he/user.json @@ -17,6 +17,7 @@ "forms.showDiscordUniqueName": "הראה שם משתמש Discord", "forms.showDiscordUniqueName.info": "להראות את שם ה-Discord היחודי שלכם ({{discordUniqueName}}) בפומבי?", "forms.commissionsOpen": "בקשות פתוחות", + "forms.commissionsOpen.info": "", "forms.commissionText": "מידע עבור בקשות", "forms.commissionText.info": "מחיר, כמות בקשות או מידע אחר שקשור לבקשות אלכם", "forms.customName.info": "", diff --git a/locales/it/art.json b/locales/it/art.json index 52a0ba11c..3b9ca6c2b 100644 --- a/locales/it/art.json +++ b/locales/it/art.json @@ -12,6 +12,8 @@ "commissionsClosed": "Commissioni chiuse", "openCommissionsOnly": "Mostra artisti con commissioni aperte", "gainPerms": "Si prega di postare sull'helpdesk del nostro Discord per ottenere i permessi per caricare art. Nota che devi essere tu l'artista dell'art che stai caricando, e solo art relative a Splatoon sono ammesse.", + "tabs.recentlyUploaded": "", + "tabs.showcase": "", "forms.caveats": "Un paio di cose di cui tener conto: 1) Puoi caricare soltanto art relative a Splatoon 2) Puoi caricare soltanto art create da te 3) Niente art NSFW. Vi è un processo di convalida svolto prima che la propria art sia visibile agli altri utenti.", "forms.description.title": "Descrizione", "forms.linkedUsers.title": "Utenti collegati", diff --git a/locales/it/common.json b/locales/it/common.json index e0d26fc61..697e70223 100644 --- a/locales/it/common.json +++ b/locales/it/common.json @@ -77,6 +77,8 @@ "notifications.text.SCRIM_SCHEDULED": "", "notifications.title.SCRIM_CANCELED": "", "notifications.text.SCRIM_CANCELED": "", + "notifications.title.COMMISSIONS_CLOSED": "", + "notifications.text.COMMISSIONS_CLOSED": "", "auth.errors.aborted": "Accesso cancellato", "auth.errors.failed": "Accesso fallito", "auth.errors.discordPermissions": "Per il tuo profilo di sendou.ink, il sito ha bisogno di accesso al nome utente, avatar e connessioni social del tuo profilo Discord.", diff --git a/locales/it/faq.json b/locales/it/faq.json index b1bfa70da..bc105fd6d 100644 --- a/locales/it/faq.json +++ b/locales/it/faq.json @@ -1,5 +1,6 @@ { "q1": "Cos'è il Server Plus?", + "a1": "", "q2": "Come faccio ad avere una medaglia come premio per il mio evento?", "a2": "Da settembre 2024, le medaglie possono essere create da chiunque abbia le competenze necessarie. Visita il link in fondo alla pagina delle medaglie per ulteriori informazioni.", "q3": "Come faccio ad aggiornare il mio nome utente o avatar?", diff --git a/locales/it/q.json b/locales/it/q.json index c633953c4..f7b4f2b59 100644 --- a/locales/it/q.json +++ b/locales/it/q.json @@ -19,7 +19,6 @@ "privateNote.sentiment.NEUTRAL": "Neutrale", "privateNote.sentiment.NEGATIVE": "Negativo", "privateNote.delete.header": "Cancellare la tua nota su {{name}}?", - "front.cities.la": "Los Angeles", "front.cities.nyc": "New York", "front.cities.paris": "Parigi", @@ -51,7 +50,6 @@ "front.seasonOpen": "Stagione {{nth}} aperta", "front.preview": "Visualizza gruppi nella coda prima di unirti", "front.preview.explanation": "Questa funzione è disponibile solo per iscritti al Patreon di sendou.ink di tier Supporter o più", - "settings.maps.header": "Mappe e modalità", "settings.maps.avoid": "Evita", "settings.maps.prefer": "Preferisci", @@ -69,6 +67,7 @@ "settings.sounds.likeReceived": "Ricevuto un like", "settings.sounds.groupNewMember": "Nuovo membro nel gruppo", "settings.sounds.matchStarted": "Match iniziato", + "settings.sounds.tournamentMatchStarted": "", "settings.mapPool.notOk": "Scegli {{count}} mappe per modalità che non hai evitato per salvare le tue preferenze", "settings.misc.header": "Misc", "settings.banned": "Bannata", @@ -78,7 +77,6 @@ "settings.trusted.trustedExplanation": "Gli utenti fidati possono aggiungerti direttamente al proprio gruppo SendouQ o ad un torneo. Se rimossi da questa lista, nel futuro necessiterai di unirti tramite un link che loro condividono", "settings.trusted.noTrustedExplanation": "Non hai nessun utente fidato. Possono essere aggiunti quando entri nel loro gruppo SendouQ o torneo tramite un link. Gli utenti fidati possono aggiungerti direttamente al proprio gruppo SendouQ o ad un torneo.", "settings.trusted.teamExplanation": "Oltre agli utenti sopracitati, un membro del tuo team <2>{{name}} può aggiungerti direttamente.", - "looking.joiningGroupError": "Prima di unirti a un nuovo gruppo, lascia quello attuale", "looking.goToSettingsPrompt": "Per aiutarti a trovare un gruppo, imposta la tua pool armi e stato voice chat nella pagina delle impostazioni", "looking.inactiveGroup.soon": "Il gruppo verrà segnato come inattivo. Stai ancora cercando?", @@ -122,7 +120,6 @@ "looking.allTiers": "Tutti i tier", "looking.joinQPrompt": "Unisciti alla coda o trova un gruppo", "looking.range.or": "o", - "match.header": "Match #{{number}}", "match.spInfo": "Gli SP verranno sistemati una volta che entrambi i team riporteranno lo stesso punteggio", "match.dispute.button": "Disputa?", @@ -165,14 +162,11 @@ "match.outcome.loss": "sconfitta", "match.screen.ban": "Armi con {{special}} non sono permesse in questo match", "match.screen.allowed": "Armi con {{special}} non sono permesse in questo match", - "preparing.joinQ": "Unisciti alla coda", - "tiers.currentCriteria": "Criterio corrente", "tiers.info.p1": "Per esempio Leviathan è la top 5% dei giocatori. Diamante è l' 85esimo percentile etc.", "tiers.info.p2": "Nota bene: Nessuno ha rango Leviathan prima che ci siano {{usersMin}} giocatori sulla classifica (o {{teamsMin}} per i team)", "tiers.info.p3": "Ogni rango ha anche un tier + (see BRONZE+ as an example below). Ciò significa che sei nella top 50% di quel rango.", - "streams.noStreams": "Nessun match streammato al momento", "streams.ownStreamInfo": "La tua stream è mancante? Assicurati che il tuo account Twitch sia collegato e il gioco settato sia Splatoon 3.", "streams.ownStreamInfo.linkText": "Consulta il FAQ per ulteriori informazioni su come collegare il tuo account Twitch." diff --git a/locales/it/tournament.json b/locales/it/tournament.json index 5aff2fbdc..157e83797 100644 --- a/locales/it/tournament.json +++ b/locales/it/tournament.json @@ -138,5 +138,6 @@ "progression.error.NAME_MISSING": "Nome bracket mancante", "progression.error.NEGATIVE_PROGRESSION": "La progressione negativa è disponibile solo in doppia eliminazione", "progression.error.NO_SE_SOURCE": "Eliminazione singola non è un bracket sorgente valido", - "progression.error.NO_DE_POSITIVE": "Doppia eliminazione non è valida per progressione positiva" + "progression.error.NO_DE_POSITIVE": "Doppia eliminazione non è valida per progressione positiva", + "progression.error.SWISS_EARLY_ADVANCE_NO_DESTINATION": "" } diff --git a/locales/it/user.json b/locales/it/user.json index 22d6a927a..9c2215e6f 100644 --- a/locales/it/user.json +++ b/locales/it/user.json @@ -17,6 +17,7 @@ "forms.showDiscordUniqueName": "Mostra username Discord", "forms.showDiscordUniqueName.info": "Mostrare il proprio nome unico Discord ({{discordUniqueName}}) pubblicamente?", "forms.commissionsOpen": "Commissioni aperte", + "forms.commissionsOpen.info": "", "forms.commissionText": "Info sulle commissioni", "forms.commissionText.info": "Prezzo, posti liberi o altre info relative al commissionarti", "forms.customName.info": "Se mancante, viene usato il tuo nome visualizzato Discord: \"{{discordName}}\"", diff --git a/locales/ja/art.json b/locales/ja/art.json index 6df875517..e5f72a42e 100644 --- a/locales/ja/art.json +++ b/locales/ja/art.json @@ -9,6 +9,8 @@ "commissionsClosed": "依頼の受付なし", "openCommissionsOnly": "依頼を受付中のアーティストを表示", "gainPerms": "作品をアップロードしたい場合は私たちのディスコードサーバーのヘルプデスクで許可を得てください。アップロードするには作品の作者でないといけません。また、スプラトゥーン関連の作品のみアップロードできます。", + "tabs.recentlyUploaded": "", + "tabs.showcase": "", "forms.caveats": "ちょっとした注意: 1) スプラトゥーンの作品のみ追加してください 2) 自分で作成した作品のみ追加してください 3) NSFW(R18系)は NG. 他のユーザーに公表される前に確認プロセスが入ります。", "forms.description.title": "説明", "forms.linkedUsers.title": "リンクされたユーザー", diff --git a/locales/ja/common.json b/locales/ja/common.json index b73f83c49..1971ce9b5 100644 --- a/locales/ja/common.json +++ b/locales/ja/common.json @@ -77,6 +77,8 @@ "notifications.text.SCRIM_SCHEDULED": "", "notifications.title.SCRIM_CANCELED": "", "notifications.text.SCRIM_CANCELED": "", + "notifications.title.COMMISSIONS_CLOSED": "", + "notifications.text.COMMISSIONS_CLOSED": "", "auth.errors.aborted": "ログインを中断しました", "auth.errors.failed": "ログインに失敗しました", "auth.errors.discordPermissions": "sendou.ink は、Discord のプロファイル名、アバター、SNS連携をサイトのプロファイルに使用します。", diff --git a/locales/ja/faq.json b/locales/ja/faq.json index 078c5e08b..2f6721f10 100644 --- a/locales/ja/faq.json +++ b/locales/ja/faq.json @@ -1,5 +1,6 @@ { "q1": "Plus Server とはなんですか?", + "a1": "", "q2": "どうやって自分のイベントでバッジプライズを得ることができますか?", "a2": "2024の九月からバッジはうまく作る技量があれば誰でも作れます。ページの下のリンクを参照してください。", "q3": "アバターとユーザー名はどうやって更新すればよいですか?", diff --git a/locales/ja/q.json b/locales/ja/q.json index 1f4de25a7..4c5cf48ac 100644 --- a/locales/ja/q.json +++ b/locales/ja/q.json @@ -67,6 +67,7 @@ "settings.sounds.likeReceived": "いいねをもらいました", "settings.sounds.groupNewMember": "新しいメンバーをグループに入れる", "settings.sounds.matchStarted": "マッチ開始", + "settings.sounds.tournamentMatchStarted": "", "settings.mapPool.notOk": "モードにつきステージを {{count}} 個選んでください。(避けるステージに入っていない)", "settings.misc.header": "他", "settings.banned": "禁止", diff --git a/locales/ja/tournament.json b/locales/ja/tournament.json index 694e3e4cb..38f653172 100644 --- a/locales/ja/tournament.json +++ b/locales/ja/tournament.json @@ -135,5 +135,6 @@ "progression.error.NAME_MISSING": "ブラケットの名前がありません", "progression.error.NEGATIVE_PROGRESSION": "逆の進行はダブルエリ三ネーションの時のみ可能です", "progression.error.NO_SE_SOURCE": "シングルエリ三ネーションは妥当なブラケットではないです", - "progression.error.NO_DE_POSITIVE": "ダブルエリミネーションは普通の進行(前向き)では妥当ではないです" + "progression.error.NO_DE_POSITIVE": "ダブルエリミネーションは普通の進行(前向き)では妥当ではないです", + "progression.error.SWISS_EARLY_ADVANCE_NO_DESTINATION": "" } diff --git a/locales/ja/user.json b/locales/ja/user.json index d0619caf4..751ddcd3b 100644 --- a/locales/ja/user.json +++ b/locales/ja/user.json @@ -17,6 +17,7 @@ "forms.showDiscordUniqueName": "Discord のユーザー名を表示する", "forms.showDiscordUniqueName.info": "Discord のユニーク名 ({{discordUniqueName}}) 公表しますか?", "forms.commissionsOpen": "依頼を受付中", + "forms.commissionsOpen.info": "", "forms.commissionText": "依頼に関する情報", "forms.commissionText.info": "価格、受付数、その他依頼に関する情報", "forms.customName.info": "記入されてない場合ディスコードの表示名 \"{{discordName}}\"を使います", diff --git a/locales/ko/art.json b/locales/ko/art.json index 6e54dc1b8..9830f4194 100644 --- a/locales/ko/art.json +++ b/locales/ko/art.json @@ -9,6 +9,8 @@ "commissionsClosed": "", "openCommissionsOnly": "", "gainPerms": "", + "tabs.recentlyUploaded": "", + "tabs.showcase": "", "forms.caveats": "", "forms.description.title": "", "forms.linkedUsers.title": "", diff --git a/locales/ko/common.json b/locales/ko/common.json index 7048ba872..4b30683fb 100644 --- a/locales/ko/common.json +++ b/locales/ko/common.json @@ -77,6 +77,8 @@ "notifications.text.SCRIM_SCHEDULED": "", "notifications.title.SCRIM_CANCELED": "", "notifications.text.SCRIM_CANCELED": "", + "notifications.title.COMMISSIONS_CLOSED": "", + "notifications.text.COMMISSIONS_CLOSED": "", "auth.errors.aborted": "로그인 중단됨", "auth.errors.failed": "로그인 실패", "auth.errors.discordPermissions": "sendou.ink 프로필을 위해 디스코드 프로필의 이름, 아바타와 연락처에 대한 접근이 필요합니다.", diff --git a/locales/ko/faq.json b/locales/ko/faq.json index d36b6e659..bcf7ef074 100644 --- a/locales/ko/faq.json +++ b/locales/ko/faq.json @@ -1,5 +1,6 @@ { "q1": "Plus Server는 무엇인가요?", + "a1": "", "q2": "제 이벤트에 어떻게 배지 상품을 받을 수 있죠?", "a2": "", "q3": "어떻게 제 아바타 또는 닉네임을 변경할 수 있죠?", diff --git a/locales/ko/q.json b/locales/ko/q.json index 994b9e2db..8cc283fe2 100644 --- a/locales/ko/q.json +++ b/locales/ko/q.json @@ -67,6 +67,7 @@ "settings.sounds.likeReceived": "", "settings.sounds.groupNewMember": "", "settings.sounds.matchStarted": "", + "settings.sounds.tournamentMatchStarted": "", "settings.mapPool.notOk": "", "settings.misc.header": "", "settings.banned": "", diff --git a/locales/ko/tournament.json b/locales/ko/tournament.json index fab420419..fe803c63c 100644 --- a/locales/ko/tournament.json +++ b/locales/ko/tournament.json @@ -135,5 +135,6 @@ "progression.error.NAME_MISSING": "", "progression.error.NEGATIVE_PROGRESSION": "", "progression.error.NO_SE_SOURCE": "", - "progression.error.NO_DE_POSITIVE": "" + "progression.error.NO_DE_POSITIVE": "", + "progression.error.SWISS_EARLY_ADVANCE_NO_DESTINATION": "" } diff --git a/locales/ko/user.json b/locales/ko/user.json index 916205a28..f8d9e22df 100644 --- a/locales/ko/user.json +++ b/locales/ko/user.json @@ -17,6 +17,7 @@ "forms.showDiscordUniqueName": "", "forms.showDiscordUniqueName.info": "", "forms.commissionsOpen": "", + "forms.commissionsOpen.info": "", "forms.commissionText": "", "forms.commissionText.info": "", "forms.customName.info": "", diff --git a/locales/nl/art.json b/locales/nl/art.json index 39f282c6c..195c7f423 100644 --- a/locales/nl/art.json +++ b/locales/nl/art.json @@ -11,6 +11,8 @@ "commissionsClosed": "", "openCommissionsOnly": "", "gainPerms": "", + "tabs.recentlyUploaded": "", + "tabs.showcase": "", "forms.caveats": "", "forms.description.title": "", "forms.linkedUsers.title": "", diff --git a/locales/nl/common.json b/locales/nl/common.json index 9ef3d1f7c..9739810fe 100644 --- a/locales/nl/common.json +++ b/locales/nl/common.json @@ -77,6 +77,8 @@ "notifications.text.SCRIM_SCHEDULED": "", "notifications.title.SCRIM_CANCELED": "", "notifications.text.SCRIM_CANCELED": "", + "notifications.title.COMMISSIONS_CLOSED": "", + "notifications.text.COMMISSIONS_CLOSED": "", "auth.errors.aborted": "", "auth.errors.failed": "", "auth.errors.discordPermissions": "", diff --git a/locales/nl/faq.json b/locales/nl/faq.json index 9e2c9f808..7d79d81b9 100644 --- a/locales/nl/faq.json +++ b/locales/nl/faq.json @@ -1,5 +1,6 @@ { "q1": "Wat is de Plus Server?", + "a1": "", "q2": "Hoe krijg ik een badge prijs voor mijn evenement?", "a2": "", "q3": "Hoe update ik mijn avatar of gebruikersnaam?", diff --git a/locales/nl/q.json b/locales/nl/q.json index 994b9e2db..8cc283fe2 100644 --- a/locales/nl/q.json +++ b/locales/nl/q.json @@ -67,6 +67,7 @@ "settings.sounds.likeReceived": "", "settings.sounds.groupNewMember": "", "settings.sounds.matchStarted": "", + "settings.sounds.tournamentMatchStarted": "", "settings.mapPool.notOk": "", "settings.misc.header": "", "settings.banned": "", diff --git a/locales/nl/tournament.json b/locales/nl/tournament.json index 2e138bcb4..50f44195d 100644 --- a/locales/nl/tournament.json +++ b/locales/nl/tournament.json @@ -137,5 +137,6 @@ "progression.error.NAME_MISSING": "", "progression.error.NEGATIVE_PROGRESSION": "", "progression.error.NO_SE_SOURCE": "", - "progression.error.NO_DE_POSITIVE": "" + "progression.error.NO_DE_POSITIVE": "", + "progression.error.SWISS_EARLY_ADVANCE_NO_DESTINATION": "" } diff --git a/locales/nl/user.json b/locales/nl/user.json index 8c12791d8..32692b540 100644 --- a/locales/nl/user.json +++ b/locales/nl/user.json @@ -17,6 +17,7 @@ "forms.showDiscordUniqueName": "", "forms.showDiscordUniqueName.info": "", "forms.commissionsOpen": "", + "forms.commissionsOpen.info": "", "forms.commissionText": "", "forms.commissionText.info": "", "forms.customName.info": "", diff --git a/locales/pl/art.json b/locales/pl/art.json index 89d207a65..3861e6eac 100644 --- a/locales/pl/art.json +++ b/locales/pl/art.json @@ -13,6 +13,8 @@ "commissionsClosed": "", "openCommissionsOnly": "", "gainPerms": "", + "tabs.recentlyUploaded": "", + "tabs.showcase": "", "forms.caveats": "", "forms.description.title": "", "forms.linkedUsers.title": "", diff --git a/locales/pl/common.json b/locales/pl/common.json index 2ddac2801..abcc24f36 100644 --- a/locales/pl/common.json +++ b/locales/pl/common.json @@ -77,6 +77,8 @@ "notifications.text.SCRIM_SCHEDULED": "", "notifications.title.SCRIM_CANCELED": "", "notifications.text.SCRIM_CANCELED": "", + "notifications.title.COMMISSIONS_CLOSED": "", + "notifications.text.COMMISSIONS_CLOSED": "", "auth.errors.aborted": "Logowanie przerwane", "auth.errors.failed": "Logowanie nieudane", "auth.errors.discordPermissions": "Do twojego profilu sendou.ink, ta strona potrzebuje dostęp do twojej nazwy, avataru i połączeń konta Discord.", diff --git a/locales/pl/faq.json b/locales/pl/faq.json index 7d88731e8..3c16e99bf 100644 --- a/locales/pl/faq.json +++ b/locales/pl/faq.json @@ -1,5 +1,6 @@ { "q1": "Czym jest Plus Server?", + "a1": "", "q2": "Jak mieć odznake dla mojego wydarzenia?", "a2": "", "q3": "Jak zaktualizować nazwę/avatar na stronie?", diff --git a/locales/pl/q.json b/locales/pl/q.json index 994b9e2db..8cc283fe2 100644 --- a/locales/pl/q.json +++ b/locales/pl/q.json @@ -67,6 +67,7 @@ "settings.sounds.likeReceived": "", "settings.sounds.groupNewMember": "", "settings.sounds.matchStarted": "", + "settings.sounds.tournamentMatchStarted": "", "settings.mapPool.notOk": "", "settings.misc.header": "", "settings.banned": "", diff --git a/locales/pl/tournament.json b/locales/pl/tournament.json index 7c50638f8..0ca02e730 100644 --- a/locales/pl/tournament.json +++ b/locales/pl/tournament.json @@ -139,5 +139,6 @@ "progression.error.NAME_MISSING": "", "progression.error.NEGATIVE_PROGRESSION": "", "progression.error.NO_SE_SOURCE": "", - "progression.error.NO_DE_POSITIVE": "" + "progression.error.NO_DE_POSITIVE": "", + "progression.error.SWISS_EARLY_ADVANCE_NO_DESTINATION": "" } diff --git a/locales/pl/user.json b/locales/pl/user.json index fa51619f3..92ba9f723 100644 --- a/locales/pl/user.json +++ b/locales/pl/user.json @@ -17,6 +17,7 @@ "forms.showDiscordUniqueName": "", "forms.showDiscordUniqueName.info": "", "forms.commissionsOpen": "", + "forms.commissionsOpen.info": "", "forms.commissionText": "", "forms.commissionText.info": "", "forms.customName.info": "", diff --git a/locales/pt-BR/art.json b/locales/pt-BR/art.json index f2c4d45af..da2b0ec47 100644 --- a/locales/pt-BR/art.json +++ b/locales/pt-BR/art.json @@ -12,6 +12,8 @@ "commissionsClosed": "Comissões estão fechadas", "openCommissionsOnly": "Mostrar somente artistas com comissões abertas", "gainPerms": "Por favor, poste na central de ajuda (helpdesk em Inglês) do nosso Discord para ganhar permissões para fazer o upload de arte. Lembre-se que você precisa ser o artista da arte da qual você está fazendo o upload e que apenas arte relacionada com Splatoon é permitida.", + "tabs.recentlyUploaded": "", + "tabs.showcase": "", "forms.caveats": "Algumas coisas para lembrar: 1) Só faça upload de arte que envolva Splatoon 2) Só faça o upload de arte que você mesmo(a) fez 3) Sem arte +18. Há um processo de validação antes da arte ser mostrada para outros usuários.", "forms.description.title": "Descrição", "forms.linkedUsers.title": "Usuários conectados", diff --git a/locales/pt-BR/common.json b/locales/pt-BR/common.json index a6058bf61..1aa521834 100644 --- a/locales/pt-BR/common.json +++ b/locales/pt-BR/common.json @@ -77,6 +77,8 @@ "notifications.text.SCRIM_SCHEDULED": "", "notifications.title.SCRIM_CANCELED": "", "notifications.text.SCRIM_CANCELED": "", + "notifications.title.COMMISSIONS_CLOSED": "", + "notifications.text.COMMISSIONS_CLOSED": "", "auth.errors.aborted": "Login Abortado", "auth.errors.failed": "Login Falhou", "auth.errors.discordPermissions": "Para o seu perfil do sendou.ink, o site precisa de acesso ao nome do perfil do seu Discord, incluindo também o avatar e conexões sociais.", diff --git a/locales/pt-BR/faq.json b/locales/pt-BR/faq.json index 3dcc41269..f62598b51 100644 --- a/locales/pt-BR/faq.json +++ b/locales/pt-BR/faq.json @@ -1,5 +1,6 @@ { "q1": "O que é o Servidor Plus?", + "a1": "", "q2": "Como conseguir um prêmio de insígnia para o meu evento?", "a2": "", "q3": "Como atualizar meu avatar ou nome de usuário?", diff --git a/locales/pt-BR/q.json b/locales/pt-BR/q.json index 186582f0b..20d1c5561 100644 --- a/locales/pt-BR/q.json +++ b/locales/pt-BR/q.json @@ -67,6 +67,7 @@ "settings.sounds.likeReceived": "Curtida recebida", "settings.sounds.groupNewMember": "Agrupar novo membro", "settings.sounds.matchStarted": "A partida começou", + "settings.sounds.tournamentMatchStarted": "", "settings.mapPool.notOk": "Escolha {{count}} mapas por modo que você não evitou para salvar suas preferências", "settings.misc.header": "Diversos", "settings.banned": "Banido(a)", diff --git a/locales/pt-BR/tournament.json b/locales/pt-BR/tournament.json index 0907d3a77..a4d525bb5 100644 --- a/locales/pt-BR/tournament.json +++ b/locales/pt-BR/tournament.json @@ -138,5 +138,6 @@ "progression.error.NAME_MISSING": "", "progression.error.NEGATIVE_PROGRESSION": "", "progression.error.NO_SE_SOURCE": "", - "progression.error.NO_DE_POSITIVE": "" + "progression.error.NO_DE_POSITIVE": "", + "progression.error.SWISS_EARLY_ADVANCE_NO_DESTINATION": "" } diff --git a/locales/pt-BR/user.json b/locales/pt-BR/user.json index f33c9f8d1..e2ac5cd09 100644 --- a/locales/pt-BR/user.json +++ b/locales/pt-BR/user.json @@ -17,6 +17,7 @@ "forms.showDiscordUniqueName": "Mostrar nome de usuário Discord", "forms.showDiscordUniqueName.info": "Deixe ativado para mostrar seu nome de usuário único do Discord ({{discordUniqueName}}) publicamente.", "forms.commissionsOpen": "Comissões abertas", + "forms.commissionsOpen.info": "", "forms.commissionText": "Info sobre comissões", "forms.commissionText.info": "Preço, vagas abertas ou qualquer outra informação relacionada ao processo de fazer um pedido para você.", "forms.customName.info": "", diff --git a/locales/ru/art.json b/locales/ru/art.json index 1a93ecded..84580f919 100644 --- a/locales/ru/art.json +++ b/locales/ru/art.json @@ -13,6 +13,8 @@ "commissionsClosed": "Заказы закрыты", "openCommissionsOnly": "Показать художников с открытыми заказами", "gainPerms": "Пожалуйста, напишите в helpdesk на нашем Discord сервере, чтобы получить доступ к загрузке артов. Учтите, что вы должны быть автором артов, которые вы загружаете. Арты должны быть строго по тематике Splatoon.", + "tabs.recentlyUploaded": "", + "tabs.showcase": "", "forms.caveats": "Примите к сведению: 1) Загружайте только арты по Splatoon 2) Загружайте только арты, созданные вами 3) NSFW запрещено. Перед тем как арт будет показан другим пользователям, он пройдёт процесс подтверждения модераторами.", "forms.description.title": "Описание", "forms.linkedUsers.title": "Отмеченный пользователь", diff --git a/locales/ru/common.json b/locales/ru/common.json index a85768334..df4ee25d0 100644 --- a/locales/ru/common.json +++ b/locales/ru/common.json @@ -77,6 +77,8 @@ "notifications.text.SCRIM_SCHEDULED": "Новый скрим запланирован на {{timeString}}", "notifications.title.SCRIM_CANCELED": "", "notifications.text.SCRIM_CANCELED": "", + "notifications.title.COMMISSIONS_CLOSED": "", + "notifications.text.COMMISSIONS_CLOSED": "", "auth.errors.aborted": "Вход отменён", "auth.errors.failed": "Ошибка входа", "auth.errors.discordPermissions": "Для вашего профиля на sendou.ink странице нужен доступ к вашему имени, аватару и привязанным аккаунтам соц. сетей в Discord.", diff --git a/locales/ru/faq.json b/locales/ru/faq.json index 769d926ad..41bb81d35 100644 --- a/locales/ru/faq.json +++ b/locales/ru/faq.json @@ -1,5 +1,6 @@ { "q1": "Что такое Plus Server?", + "a1": "", "q2": "Как получить призовой значок для моего события?", "a2": "", "q3": "Как обновить мой аватар или ник?", diff --git a/locales/ru/q.json b/locales/ru/q.json index 14937d1e7..764c6ce23 100644 --- a/locales/ru/q.json +++ b/locales/ru/q.json @@ -19,7 +19,6 @@ "privateNote.sentiment.NEUTRAL": "Нейтральное", "privateNote.sentiment.NEGATIVE": "Отрицательное", "privateNote.delete.header": "Удалить заметку о {{name}}?", - "front.cities.la": "Лос-Анджелес", "front.cities.nyc": "Нью-Йорк", "front.cities.paris": "Париж", @@ -51,7 +50,6 @@ "front.seasonOpen": "Сезон {{nth}} открыт", "front.preview": "Предпросмотр груп в очереди без присоединения", "front.preview.explanation": "Данная функция доступна только меценатам sendou.imk уровня Supporter (или выше)", - "settings.maps.header": "Арены и режимы", "settings.maps.avoid": "Избегаю", "settings.maps.prefer": "Предпочитаю", @@ -69,6 +67,7 @@ "settings.sounds.likeReceived": "Получен лайк", "settings.sounds.groupNewMember": "Новый участник команды", "settings.sounds.matchStarted": "Матч начался", + "settings.sounds.tournamentMatchStarted": "", "settings.mapPool.notOk": "Выберите по {{count}} арен за каждый режим, который вы не избегаете, чтобы сохранить настройки", "settings.misc.header": "Прочее", "settings.banned": "Запрещённый режим", @@ -78,7 +77,6 @@ "settings.trusted.trustedExplanation": "Доверенный пользователь может добавлять вас в группы без ссылки на приглашение. При удаления из списка они теряют это право, и вам придётся пользоваться их ссылками на приглашение.", "settings.trusted.noTrustedExplanation": "На данный момент вы не доверяете ни одному пользователю. Доверенный пользователь может добавлять вас в различные группы без ссылки на приглашение", "settings.trusted.teamExplanation": "В добавок к пользователям выше, члены вашей команды <2>{{name}} также могут вас добавить.", - "looking.joiningGroupError": "Перед тем как присоединиться к новой группе, покиньте текущую", "looking.goToSettingsPrompt": "Чтобы облегчить процесс нахождения группы, настройте ваш пул оружия и статус голосового чата на странице настроек", "looking.inactiveGroup.soon": "Группа будет помечена, как неактивная. Ещё ищете?", @@ -122,7 +120,6 @@ "looking.allTiers": "Все ранги", "looking.joinQPrompt": "Присоединитесь к очереди, чтобы найти группу", "looking.range.or": "или", - "match.header": "Матч #{{number}}", "match.spInfo": "SP будет изменено после того, как обе команды сообщат одинаковый счёт.", "match.dispute.button": "Оспорить?", @@ -165,14 +162,11 @@ "match.outcome.loss": "проигрыш", "match.screen.ban": "Оружия с {{special}} запрещены в этом матче", "match.screen.allowed": "Оружия с {{special}} разрешены в этом матче", - "preparing.joinQ": "Присоединиться к очереди", - "tiers.currentCriteria": "Текущие критерии", "tiers.info.p1": "Например, Leviathan - топ 5% игроков, Diamond - 85 процентиль и т.д.", "tiers.info.p2": "Учтите, что ни у кого нет ранга Leviathan пока как минимум {{usersMin}} игроков не появилось на таблице лидеров (или {{teamsMin}} для команд)", "tiers.info.p3": "У каждого ранга также есть + ранг (например BRONZE+). Это значит, что вы в первой половине вашего ранга.", - "streams.noStreams": "Прямых трансляций матчей на данный момент нет", "streams.ownStreamInfo": "Нет вашей трансляции? Проверьте, что ваш аккаунт Twitch привязан и выбранная игра - Splatoon 3.", "streams.ownStreamInfo.linkText": "Подробная информация о привязке аккаунта Twitch в FAQ." diff --git a/locales/ru/tournament.json b/locales/ru/tournament.json index e6d0ef3d9..68599329e 100644 --- a/locales/ru/tournament.json +++ b/locales/ru/tournament.json @@ -139,5 +139,6 @@ "progression.error.NAME_MISSING": "Имя сетки отсутствует", "progression.error.NEGATIVE_PROGRESSION": "Отрицательная прогрессия возможна только в Double Elimination турнирах", "progression.error.NO_SE_SOURCE": "Single elimination не валидная сетка-исток", - "progression.error.NO_DE_POSITIVE": "Double elimination не валидно для позитивной прогрессии" + "progression.error.NO_DE_POSITIVE": "Double elimination не валидно для позитивной прогрессии", + "progression.error.SWISS_EARLY_ADVANCE_NO_DESTINATION": "" } diff --git a/locales/ru/user.json b/locales/ru/user.json index 6102f737f..71c4b2a74 100644 --- a/locales/ru/user.json +++ b/locales/ru/user.json @@ -17,6 +17,7 @@ "forms.showDiscordUniqueName": "Показать пользовательское имя Discord", "forms.showDiscordUniqueName.info": "Показывать ваше уникальное Discord имя ({{discordUniqueName}})?", "forms.commissionsOpen": "Коммишены открыты", + "forms.commissionsOpen.info": "", "forms.commissionText": "Информация о коммишенах", "forms.commissionText.info": "Цена, слоты и другая информация о ваших коммишенах", "forms.customName.info": "Если пользовательское имя отсутствует, то будет использовано ваше имя в Discord: \"{{discordName}}\"", diff --git a/locales/zh/art.json b/locales/zh/art.json index 1a6d8a387..b33f9958b 100644 --- a/locales/zh/art.json +++ b/locales/zh/art.json @@ -9,6 +9,8 @@ "commissionsClosed": "委托未开放", "openCommissionsOnly": "显示开放委托的创作者", "gainPerms": "请在我们Discord中的helpdesk申请上传作品的权限。请注意您必须是作品的原作者并且只能上传斯普拉遁相关作品。", + "tabs.recentlyUploaded": "", + "tabs.showcase": "", "forms.caveats": "请注意:1) 仅上传斯普拉遁相关作品 2) 仅上传由您本人创作的作品 3) 禁止NSFW作品。作品经过审核后才对其他用户可见。", "forms.description.title": "描述", "forms.linkedUsers.title": "关联用户", diff --git a/locales/zh/common.json b/locales/zh/common.json index 16b21efe3..a629efc86 100644 --- a/locales/zh/common.json +++ b/locales/zh/common.json @@ -77,6 +77,8 @@ "notifications.text.SCRIM_SCHEDULED": "", "notifications.title.SCRIM_CANCELED": "", "notifications.text.SCRIM_CANCELED": "", + "notifications.title.COMMISSIONS_CLOSED": "", + "notifications.text.COMMISSIONS_CLOSED": "", "auth.errors.aborted": "登录中止", "auth.errors.failed": "登录失败", "auth.errors.discordPermissions": "为了完善您的sendou.ink个人资料,网站需要获取您的Discord名字、头像和社交链接。", diff --git a/locales/zh/faq.json b/locales/zh/faq.json index 78ab81943..f07773dc7 100644 --- a/locales/zh/faq.json +++ b/locales/zh/faq.json @@ -1,5 +1,6 @@ { "q1": "什么是Plus Server?", + "a1": "", "q2": "如何在我的活动中设置徽章奖?", "a2": "", "q3": "如何更新我的头像与用户名?", diff --git a/locales/zh/q.json b/locales/zh/q.json index 71ae180cf..ecfb03ebf 100644 --- a/locales/zh/q.json +++ b/locales/zh/q.json @@ -67,6 +67,7 @@ "settings.sounds.likeReceived": "点赞", "settings.sounds.groupNewMember": "新成员加入", "settings.sounds.matchStarted": "对战开始", + "settings.sounds.tournamentMatchStarted": "", "settings.mapPool.notOk": "每个模式选择 {{count}} 个您不想避开的地图以设置地图偏好", "settings.misc.header": "其他", "settings.banned": "禁止", diff --git a/locales/zh/tournament.json b/locales/zh/tournament.json index 260b768ba..13f29bd5f 100644 --- a/locales/zh/tournament.json +++ b/locales/zh/tournament.json @@ -135,5 +135,6 @@ "progression.error.NAME_MISSING": "", "progression.error.NEGATIVE_PROGRESSION": "", "progression.error.NO_SE_SOURCE": "", - "progression.error.NO_DE_POSITIVE": "" + "progression.error.NO_DE_POSITIVE": "", + "progression.error.SWISS_EARLY_ADVANCE_NO_DESTINATION": "" } diff --git a/locales/zh/user.json b/locales/zh/user.json index caa60ef53..ad47710ac 100644 --- a/locales/zh/user.json +++ b/locales/zh/user.json @@ -17,6 +17,7 @@ "forms.showDiscordUniqueName": "显示Discord用户名", "forms.showDiscordUniqueName.info": "是否公开显示您的Discord用户名 ({{discordUniqueName}}) ?", "forms.commissionsOpen": "开放委托", + "forms.commissionsOpen.info": "", "forms.commissionText": "委托信息", "forms.commissionText.info": "你的价格、档期或者其他委托相关的信息", "forms.customName.info": "如果此栏空着,将会显示您的Discord昵称:\"{{discordName}}\"", diff --git a/migrations/096-commissions-opened-at.js b/migrations/096-commissions-opened-at.js new file mode 100644 index 000000000..8bd4e949a --- /dev/null +++ b/migrations/096-commissions-opened-at.js @@ -0,0 +1,11 @@ +export function up(db) { + db.transaction(() => { + db.prepare( + /* sql */ `alter table "User" add column "commissionsOpenedAt" integer`, + ).run(); + + db.prepare( + /* sql */ `update "User" set "commissionsOpen" = 0 where "commissionsOpen" = 1`, + ).run(); + })(); +}