mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-03-21 18:04:39 -05:00
Art feature improvements (#2564)
This commit is contained in:
parent
b3de79cee8
commit
0ddc73666d
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -825,6 +825,7 @@ export interface User {
|
|||
bannedReason: string | null;
|
||||
bio: string | null;
|
||||
commissionsOpen: Generated<number | null>;
|
||||
commissionsOpenedAt: number | null;
|
||||
commissionText: string | null;
|
||||
country: string | null;
|
||||
css: JSONColumnTypeNullable<Record<string, string>>;
|
||||
|
|
|
|||
|
|
@ -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<ListedArt[]> {
|
||||
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<ListedArt[]> {
|
||||
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<number>();
|
||||
|
||||
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<ListedArt[]> {
|
||||
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,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
))}
|
||||
</ResponsiveMasonry>
|
||||
{!everythingVisible ? (
|
||||
<Pagination
|
||||
currentPage={currentPage}
|
||||
pagesCount={pagesCount}
|
||||
nextPage={nextPage}
|
||||
previousPage={previousPage}
|
||||
setPage={setPage}
|
||||
/>
|
||||
<div className="mt-6">
|
||||
<Pagination
|
||||
currentPage={currentPage}
|
||||
pagesCount={pagesCount}
|
||||
nextPage={nextPage}
|
||||
previousPage={previousPage}
|
||||
setPage={setPage}
|
||||
/>
|
||||
</div>
|
||||
) : 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({
|
|||
<Avatar user={art.author} size="xxs" />
|
||||
{t("art:madeBy")} {art.author.username}
|
||||
</Link>
|
||||
{uploadDateText ? (
|
||||
<div
|
||||
className={clsx("text-xs text-lighter", {
|
||||
invisible: !imageLoaded,
|
||||
})}
|
||||
>
|
||||
{uploadDateText}
|
||||
</div>
|
||||
) : null}
|
||||
{canEdit ? (
|
||||
<FormWithConfirm
|
||||
dialogHeading={t("art:unlink.title", {
|
||||
|
|
@ -256,13 +279,24 @@ function ImagePreview({
|
|||
return (
|
||||
<Link to={userArtPage(art.author, "MADE-BY")}>
|
||||
{img}
|
||||
<div
|
||||
className={clsx("stack sm horizontal text-xs items-center mt-1", {
|
||||
invisible: !imageLoaded,
|
||||
})}
|
||||
>
|
||||
<Avatar user={art.author} size="xxs" />
|
||||
{art.author.username}
|
||||
<div className="stack horizontal justify-between">
|
||||
<div
|
||||
className={clsx("stack sm horizontal text-xs items-center mt-1", {
|
||||
invisible: !imageLoaded,
|
||||
})}
|
||||
>
|
||||
<Avatar user={art.author} size="xxs" />
|
||||
{art.author.username}
|
||||
</div>
|
||||
{uploadDateText ? (
|
||||
<div
|
||||
className={clsx("text-xxs mt-1 text-lighter", {
|
||||
invisible: !imageLoaded,
|
||||
})}
|
||||
>
|
||||
{uploadDateText}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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<number>();
|
||||
|
||||
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,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
|
@ -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 (
|
||||
<Main className="stack lg">
|
||||
|
|
@ -89,16 +105,25 @@ export default function ArtPage() {
|
|||
</Label>
|
||||
</div>
|
||||
<div className="stack horizontal sm items-center">
|
||||
<TagSelect
|
||||
key={filteredTag}
|
||||
tags={data.allTags}
|
||||
onSelectionChange={(tagName) => {
|
||||
setSearchParams((prev) => {
|
||||
prev.set(FILTERED_TAG_KEY_SEARCH_PARAM_KEY, tagName as string);
|
||||
return prev;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
className={clsx({
|
||||
invisible: selectedTab !== TABS.SHOWCASE,
|
||||
})}
|
||||
>
|
||||
<TagSelect
|
||||
key={filteredTag}
|
||||
tags={data.allTags}
|
||||
onSelectionChange={(tagName) => {
|
||||
setSearchParams((prev) => {
|
||||
prev.set(
|
||||
FILTERED_TAG_KEY_SEARCH_PARAM_KEY,
|
||||
tagName as string,
|
||||
);
|
||||
return prev;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<AddNewButton navIcon="art" to={newArtPage()} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -121,7 +146,31 @@ export default function ArtPage() {
|
|||
</SendouButton>
|
||||
</div>
|
||||
) : null}
|
||||
<ArtGrid arts={arts} />
|
||||
<SendouTabs
|
||||
selectedKey={selectedTab}
|
||||
onSelectionChange={(key) => {
|
||||
setSearchParams((prev) => {
|
||||
prev.set(TAB_KEY, key as string);
|
||||
if (key === TABS.RECENTLY_UPLOADED) {
|
||||
prev.delete(FILTERED_TAG_KEY_SEARCH_PARAM_KEY);
|
||||
}
|
||||
return prev;
|
||||
});
|
||||
}}
|
||||
>
|
||||
<SendouTabList>
|
||||
<SendouTab id={TABS.RECENTLY_UPLOADED}>
|
||||
{t("art:tabs.recentlyUploaded")}
|
||||
</SendouTab>
|
||||
<SendouTab id={TABS.SHOWCASE}>{t("art:tabs.showcase")}</SendouTab>
|
||||
</SendouTabList>
|
||||
<SendouTabPanel id={TABS.RECENTLY_UPLOADED}>
|
||||
<ArtGrid arts={recentlyUploadedArts} showUploadDate />
|
||||
</SendouTabPanel>
|
||||
<SendouTabPanel id={TABS.SHOWCASE}>
|
||||
<ArtGrid arts={showcaseArts} />
|
||||
</SendouTabPanel>
|
||||
</SendouTabs>
|
||||
</Main>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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"])
|
||||
|
|
|
|||
|
|
@ -472,6 +472,9 @@ function CommissionsOpenToggle({
|
|||
onChange={setChecked}
|
||||
name="commissionsOpen"
|
||||
/>
|
||||
<FormMessage type="info">
|
||||
{t("user:forms.commissionsOpen.info")}
|
||||
</FormMessage>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<ModeWithStageAndScore & { score: number }> = [];
|
||||
const bestMapList: { maps?: Array<ModeWithStageAndScore>; 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) &&
|
||||
|
|
|
|||
|
|
@ -6,4 +6,3 @@ export type {
|
|||
TournamentMaplistInput,
|
||||
TournamentMaplistSource,
|
||||
} from "./types";
|
||||
export { seededRandom } from "./utils";
|
||||
|
|
|
|||
|
|
@ -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<TournamentMapListMap> {
|
||||
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<TournamentMapListMap> {
|
|||
|
||||
const commonMap = resolveRandomCommonMap(
|
||||
args.teams,
|
||||
shuffle,
|
||||
seededShuffle,
|
||||
isRecentlyPlayed,
|
||||
);
|
||||
if (commonMap) {
|
||||
|
|
@ -35,7 +35,7 @@ export function starterMap(args: StarterMapArgs): Array<TournamentMapListMap> {
|
|||
}
|
||||
|
||||
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<TournamentMapListMap> {
|
|||
|
||||
// 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 }))),
|
||||
|
|
|
|||
54
app/routines/closeExpiredCommissions.ts
Normal file
54
app/routines/closeExpiredCommissions.ts
Normal file
|
|
@ -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],
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
@ -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,
|
||||
];
|
||||
|
|
|
|||
142
app/utils/random.test.ts
Normal file
142
app/utils/random.test.ts
Normal file
|
|
@ -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]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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 = <T>(o: T[]) => {
|
||||
const seededShuffle = <T>(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 };
|
||||
};
|
||||
BIN
db-test.sqlite3
BIN
db-test.sqlite3
Binary file not shown.
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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).",
|
||||
|
|
|
|||
|
|
@ -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?",
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@
|
|||
"settings.sounds.likeReceived": "",
|
||||
"settings.sounds.groupNewMember": "",
|
||||
"settings.sounds.matchStarted": "",
|
||||
"settings.sounds.tournamentMatchStarted": "",
|
||||
"settings.mapPool.notOk": "",
|
||||
"settings.misc.header": "",
|
||||
"settings.banned": "",
|
||||
|
|
|
|||
|
|
@ -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": ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}}\"",
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@
|
|||
"commissionsClosed": "",
|
||||
"openCommissionsOnly": "",
|
||||
"gainPerms": "",
|
||||
"tabs.recentlyUploaded": "",
|
||||
"tabs.showcase": "",
|
||||
"forms.caveats": "",
|
||||
"forms.description.title": "",
|
||||
"forms.linkedUsers.title": "",
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -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?",
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@
|
|||
"settings.sounds.likeReceived": "",
|
||||
"settings.sounds.groupNewMember": "",
|
||||
"settings.sounds.matchStarted": "",
|
||||
"settings.sounds.tournamentMatchStarted": "",
|
||||
"settings.mapPool.notOk": "",
|
||||
"settings.misc.header": "",
|
||||
"settings.banned": "",
|
||||
|
|
|
|||
|
|
@ -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": ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
"forms.showDiscordUniqueName": "",
|
||||
"forms.showDiscordUniqueName.info": "",
|
||||
"forms.commissionsOpen": "",
|
||||
"forms.commissionsOpen.info": "",
|
||||
"forms.commissionText": "",
|
||||
"forms.commissionText.info": "",
|
||||
"forms.customName.info": "",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -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}}\"",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -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?",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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": ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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": "",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -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?",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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": ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}}\"",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -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 ?",
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@
|
|||
"settings.sounds.likeReceived": "",
|
||||
"settings.sounds.groupNewMember": "",
|
||||
"settings.sounds.matchStarted": "",
|
||||
"settings.sounds.tournamentMatchStarted": "",
|
||||
"settings.mapPool.notOk": "",
|
||||
"settings.misc.header": "",
|
||||
"settings.banned": "",
|
||||
|
|
|
|||
|
|
@ -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": ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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": "",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -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 ?",
|
||||
|
|
|
|||
|
|
@ -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}}</2> 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."
|
||||
|
|
|
|||
|
|
@ -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": ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}}\"",
|
||||
|
|
|
|||
|
|
@ -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": "תיוג משתמשים",
|
||||
|
|
|
|||
|
|
@ -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 שלך.",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"q1": "מה זה השרת פלוס?",
|
||||
"a1": "",
|
||||
"q2": "איך מקבלים פרס תג לאירוע שלי?",
|
||||
"a2": "",
|
||||
"q3": "איך לעדכן את הפרופיל או את שם המשתמש שלי?",
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@
|
|||
"settings.sounds.likeReceived": "",
|
||||
"settings.sounds.groupNewMember": "",
|
||||
"settings.sounds.matchStarted": "",
|
||||
"settings.sounds.tournamentMatchStarted": "",
|
||||
"settings.mapPool.notOk": "",
|
||||
"settings.misc.header": "",
|
||||
"settings.banned": "",
|
||||
|
|
|
|||
|
|
@ -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": ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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": "",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -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?",
|
||||
|
|
|
|||
|
|
@ -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}}</2> 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."
|
||||
|
|
|
|||
|
|
@ -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": ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}}\"",
|
||||
|
|
|
|||
|
|
@ -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": "リンクされたユーザー",
|
||||
|
|
|
|||
|
|
@ -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連携をサイトのプロファイルに使用します。",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"q1": "Plus Server とはなんですか?",
|
||||
"a1": "",
|
||||
"q2": "どうやって自分のイベントでバッジプライズを得ることができますか?",
|
||||
"a2": "2024の九月からバッジはうまく作る技量があれば誰でも作れます。ページの下のリンクを参照してください。",
|
||||
"q3": "アバターとユーザー名はどうやって更新すればよいですか?",
|
||||
|
|
|
|||
|
|
@ -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": "禁止",
|
||||
|
|
|
|||
|
|
@ -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": ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}}\"を使います",
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@
|
|||
"commissionsClosed": "",
|
||||
"openCommissionsOnly": "",
|
||||
"gainPerms": "",
|
||||
"tabs.recentlyUploaded": "",
|
||||
"tabs.showcase": "",
|
||||
"forms.caveats": "",
|
||||
"forms.description.title": "",
|
||||
"forms.linkedUsers.title": "",
|
||||
|
|
|
|||
|
|
@ -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 프로필을 위해 디스코드 프로필의 이름, 아바타와 연락처에 대한 접근이 필요합니다.",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"q1": "Plus Server는 무엇인가요?",
|
||||
"a1": "",
|
||||
"q2": "제 이벤트에 어떻게 배지 상품을 받을 수 있죠?",
|
||||
"a2": "",
|
||||
"q3": "어떻게 제 아바타 또는 닉네임을 변경할 수 있죠?",
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@
|
|||
"settings.sounds.likeReceived": "",
|
||||
"settings.sounds.groupNewMember": "",
|
||||
"settings.sounds.matchStarted": "",
|
||||
"settings.sounds.tournamentMatchStarted": "",
|
||||
"settings.mapPool.notOk": "",
|
||||
"settings.misc.header": "",
|
||||
"settings.banned": "",
|
||||
|
|
|
|||
|
|
@ -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": ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
"forms.showDiscordUniqueName": "",
|
||||
"forms.showDiscordUniqueName.info": "",
|
||||
"forms.commissionsOpen": "",
|
||||
"forms.commissionsOpen.info": "",
|
||||
"forms.commissionText": "",
|
||||
"forms.commissionText.info": "",
|
||||
"forms.customName.info": "",
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@
|
|||
"commissionsClosed": "",
|
||||
"openCommissionsOnly": "",
|
||||
"gainPerms": "",
|
||||
"tabs.recentlyUploaded": "",
|
||||
"tabs.showcase": "",
|
||||
"forms.caveats": "",
|
||||
"forms.description.title": "",
|
||||
"forms.linkedUsers.title": "",
|
||||
|
|
|
|||
|
|
@ -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": "",
|
||||
|
|
|
|||
|
|
@ -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?",
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@
|
|||
"settings.sounds.likeReceived": "",
|
||||
"settings.sounds.groupNewMember": "",
|
||||
"settings.sounds.matchStarted": "",
|
||||
"settings.sounds.tournamentMatchStarted": "",
|
||||
"settings.mapPool.notOk": "",
|
||||
"settings.misc.header": "",
|
||||
"settings.banned": "",
|
||||
|
|
|
|||
|
|
@ -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": ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
"forms.showDiscordUniqueName": "",
|
||||
"forms.showDiscordUniqueName.info": "",
|
||||
"forms.commissionsOpen": "",
|
||||
"forms.commissionsOpen.info": "",
|
||||
"forms.commissionText": "",
|
||||
"forms.commissionText.info": "",
|
||||
"forms.customName.info": "",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@
|
|||
"commissionsClosed": "",
|
||||
"openCommissionsOnly": "",
|
||||
"gainPerms": "",
|
||||
"tabs.recentlyUploaded": "",
|
||||
"tabs.showcase": "",
|
||||
"forms.caveats": "",
|
||||
"forms.description.title": "",
|
||||
"forms.linkedUsers.title": "",
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -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?",
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@
|
|||
"settings.sounds.likeReceived": "",
|
||||
"settings.sounds.groupNewMember": "",
|
||||
"settings.sounds.matchStarted": "",
|
||||
"settings.sounds.tournamentMatchStarted": "",
|
||||
"settings.mapPool.notOk": "",
|
||||
"settings.misc.header": "",
|
||||
"settings.banned": "",
|
||||
|
|
|
|||
|
|
@ -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": ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
"forms.showDiscordUniqueName": "",
|
||||
"forms.showDiscordUniqueName.info": "",
|
||||
"forms.commissionsOpen": "",
|
||||
"forms.commissionsOpen.info": "",
|
||||
"forms.commissionText": "",
|
||||
"forms.commissionText.info": "",
|
||||
"forms.customName.info": "",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -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?",
|
||||
|
|
|
|||
|
|
@ -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)",
|
||||
|
|
|
|||
|
|
@ -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": ""
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user