mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-03-21 18:04:39 -05:00
Migrate /u/:identifier/edit to SendouForm, fix bad IGN (#2849)
This commit is contained in:
parent
c90b0b4ee3
commit
c1cc82c807
|
|
@ -28,12 +28,16 @@ type ContrastArray = {
|
|||
|
||||
export function CustomizedColorsInput({
|
||||
initialColors,
|
||||
value: controlledValue,
|
||||
onChange,
|
||||
}: {
|
||||
initialColors?: Record<string, string> | null;
|
||||
value?: Record<string, string> | null;
|
||||
onChange?: (value: Record<string, string> | null) => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [colors, setColors] = React.useState<CustomColorsRecord>(
|
||||
initialColors ?? {},
|
||||
controlledValue ?? initialColors ?? {},
|
||||
);
|
||||
|
||||
const [defaultColors, setDefaultColors] = React.useState<
|
||||
|
|
@ -41,6 +45,15 @@ export function CustomizedColorsInput({
|
|||
>([]);
|
||||
const [contrasts, setContrast] = React.useState<ContrastArray>([]);
|
||||
|
||||
const updateColors = (newColors: CustomColorsRecord) => {
|
||||
setColors(newColors);
|
||||
if (onChange) {
|
||||
const filtered = colorsWithDefaultsFilteredOut(newColors, defaultColors);
|
||||
const hasValues = Object.keys(filtered).length > 0;
|
||||
onChange(hasValues ? (filtered as Record<string, string>) : null);
|
||||
}
|
||||
};
|
||||
|
||||
useDebounce(
|
||||
() => {
|
||||
for (const color in colors) {
|
||||
|
|
@ -79,13 +92,15 @@ export function CustomizedColorsInput({
|
|||
</summary>
|
||||
<div>
|
||||
<Label>{t("custom.colors.title")}</Label>
|
||||
<input
|
||||
type="hidden"
|
||||
name="css"
|
||||
value={JSON.stringify(
|
||||
colorsWithDefaultsFilteredOut(colors, defaultColors),
|
||||
)}
|
||||
/>
|
||||
{!onChange ? (
|
||||
<input
|
||||
type="hidden"
|
||||
name="css"
|
||||
value={JSON.stringify(
|
||||
colorsWithDefaultsFilteredOut(colors, defaultColors),
|
||||
)}
|
||||
/>
|
||||
) : null}
|
||||
<div className="colors__container colors__grid">
|
||||
{CUSTOM_CSS_VAR_COLORS.filter(
|
||||
(cssVar) => cssVar !== "bg-lightest",
|
||||
|
|
@ -102,7 +117,7 @@ export function CustomizedColorsInput({
|
|||
if (cssVar === "bg-lighter") {
|
||||
extras["bg-lightest"] = `${e.target.value}80`;
|
||||
}
|
||||
setColors({
|
||||
updateColors({
|
||||
...colors,
|
||||
...extras,
|
||||
[cssVar]: e.target.value,
|
||||
|
|
@ -122,7 +137,7 @@ export function CustomizedColorsInput({
|
|||
(color) => color["bg-lightest"],
|
||||
)?.["bg-lightest"];
|
||||
}
|
||||
setColors({
|
||||
updateColors({
|
||||
...newColors,
|
||||
[cssVar]: defaultColors.find((color) => color[cssVar])?.[
|
||||
cssVar
|
||||
|
|
|
|||
|
|
@ -1,79 +1,85 @@
|
|||
import { type ActionFunction, redirect } from "react-router";
|
||||
import { requireUser } from "~/features/auth/core/user.server";
|
||||
import { BADGE } from "~/features/badges/badges-constants";
|
||||
import * as TournamentTeamRepository from "~/features/tournament/TournamentTeamRepository.server";
|
||||
import { clearTournamentDataCache } from "~/features/tournament-bracket/core/Tournament.server";
|
||||
import * as UserRepository from "~/features/user-page/UserRepository.server";
|
||||
import { safeParseRequestFormData } from "~/utils/remix.server";
|
||||
import { errorIsSqliteUniqueConstraintFailure } from "~/utils/sql";
|
||||
import { parseFormData } from "~/form/parse.server";
|
||||
import { userPage } from "~/utils/urls";
|
||||
import { userEditActionSchema } from "../user-page-schemas";
|
||||
import { userEditProfileSchemaServer } from "../user-page-schemas.server";
|
||||
|
||||
export const action: ActionFunction = async ({ request }) => {
|
||||
const parsedInput = await safeParseRequestFormData({
|
||||
const user = requireUser();
|
||||
|
||||
const result = await parseFormData({
|
||||
request,
|
||||
schema: userEditActionSchema,
|
||||
schema: userEditProfileSchemaServer,
|
||||
});
|
||||
|
||||
if (!parsedInput.success) {
|
||||
return {
|
||||
errors: parsedInput.errors,
|
||||
};
|
||||
if (!result.success) {
|
||||
return { fieldErrors: result.fieldErrors };
|
||||
}
|
||||
|
||||
const {
|
||||
inGameNameText,
|
||||
inGameNameDiscriminator,
|
||||
newProfileEnabled,
|
||||
...data
|
||||
} = parsedInput.data;
|
||||
const data = result.data;
|
||||
|
||||
const user = requireUser();
|
||||
const inGameName =
|
||||
inGameNameText && inGameNameDiscriminator
|
||||
? `${inGameNameText}#${inGameNameDiscriminator}`
|
||||
const [subjectPronoun, objectPronoun] = data.pronouns ?? [null, null];
|
||||
const pronouns =
|
||||
subjectPronoun && objectPronoun
|
||||
? JSON.stringify({ subject: subjectPronoun, object: objectPronoun })
|
||||
: null;
|
||||
|
||||
try {
|
||||
const pronouns =
|
||||
data.subjectPronoun && data.objectPronoun
|
||||
? JSON.stringify({
|
||||
subject: data.subjectPronoun,
|
||||
object: data.objectPronoun,
|
||||
})
|
||||
: null;
|
||||
const [motionSens, stickSens] = data.sensitivity ?? [null, null];
|
||||
|
||||
const editedUser = await UserRepository.updateProfile({
|
||||
...data,
|
||||
pronouns,
|
||||
inGameName,
|
||||
userId: user.id,
|
||||
});
|
||||
const weapons = data.weapons.map((w) => ({
|
||||
weaponSplId: w.id,
|
||||
isFavorite: w.isFavorite ? (1 as const) : (0 as const),
|
||||
}));
|
||||
|
||||
await UserRepository.updatePreferences(user.id, {
|
||||
newProfileEnabled: Boolean(newProfileEnabled),
|
||||
});
|
||||
const css = data.css ? JSON.stringify(data.css) : null;
|
||||
|
||||
// TODO: to transaction
|
||||
if (inGameName) {
|
||||
const tournamentIdsAffected =
|
||||
await TournamentTeamRepository.updateMemberInGameNameForNonStarted({
|
||||
inGameName,
|
||||
userId: user.id,
|
||||
});
|
||||
const isSupporter = user.roles?.includes("SUPPORTER");
|
||||
const isArtist = user.roles?.includes("ARTIST");
|
||||
|
||||
for (const tournamentId of tournamentIdsAffected) {
|
||||
clearTournamentDataCache(tournamentId);
|
||||
}
|
||||
const maxBadgeCount = isSupporter
|
||||
? BADGE.SMALL_BADGES_PER_DISPLAY_PAGE + 1
|
||||
: 1;
|
||||
const limitedBadgeIds = data.favoriteBadgeIds.slice(0, maxBadgeCount);
|
||||
|
||||
const editedUser = await UserRepository.updateProfile({
|
||||
userId: user.id,
|
||||
country: data.country,
|
||||
bio: data.bio,
|
||||
customUrl: data.customUrl,
|
||||
customName: data.customName,
|
||||
motionSens: motionSens !== null ? Number(motionSens) : null,
|
||||
stickSens: stickSens !== null ? Number(stickSens) : null,
|
||||
pronouns,
|
||||
inGameName: data.inGameName,
|
||||
css: isSupporter ? css : null,
|
||||
battlefy: data.battlefy,
|
||||
weapons,
|
||||
favoriteBadgeIds: limitedBadgeIds.length > 0 ? limitedBadgeIds : null,
|
||||
showDiscordUniqueName: data.showDiscordUniqueName ? 1 : 0,
|
||||
commissionsOpen: isArtist && data.commissionsOpen ? 1 : 0,
|
||||
commissionText: isArtist ? data.commissionText : null,
|
||||
});
|
||||
|
||||
await UserRepository.updatePreferences(user.id, {
|
||||
newProfileEnabled: isSupporter ? data.newProfileEnabled : false,
|
||||
});
|
||||
|
||||
// TODO: to transaction
|
||||
if (data.inGameName) {
|
||||
const tournamentIdsAffected =
|
||||
await TournamentTeamRepository.updateMemberInGameNameForNonStarted({
|
||||
inGameName: data.inGameName,
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
for (const tournamentId of tournamentIdsAffected) {
|
||||
clearTournamentDataCache(tournamentId);
|
||||
}
|
||||
|
||||
throw redirect(userPage(editedUser));
|
||||
} catch (e) {
|
||||
if (!errorIsSqliteUniqueConstraintFailure(e)) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
return {
|
||||
errors: ["forms.errors.invalidCustomUrl.duplicate"],
|
||||
};
|
||||
}
|
||||
|
||||
throw redirect(userPage(editedUser));
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,33 +1,30 @@
|
|||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import type { MainWeaponId } from "~/modules/in-game-lists/types";
|
||||
import { dbInsertUsers, dbReset, wrappedAction } from "~/utils/Test";
|
||||
import type { userEditActionSchema } from "../user-page-schemas";
|
||||
import type { userEditProfileBaseSchema } from "../user-page-schemas";
|
||||
import { action as editUserProfileAction } from "./u.$identifier.edit";
|
||||
|
||||
const action = wrappedAction<typeof userEditActionSchema>({
|
||||
const action = wrappedAction<typeof userEditProfileBaseSchema>({
|
||||
action: editUserProfileAction,
|
||||
isJsonSubmission: true,
|
||||
});
|
||||
|
||||
const DEFAULT_FIELDS = {
|
||||
battlefy: null,
|
||||
bio: null,
|
||||
commissionsOpen: 1,
|
||||
commissionsOpen: false,
|
||||
commissionText: null,
|
||||
country: "FI",
|
||||
customName: null,
|
||||
customUrl: null,
|
||||
favoriteBadgeIds: null,
|
||||
inGameNameDiscriminator: null,
|
||||
inGameNameText: null,
|
||||
motionSens: null,
|
||||
showDiscordUniqueName: 1,
|
||||
newProfileEnabled: 0,
|
||||
stickSens: null,
|
||||
subjectPronoun: null,
|
||||
objectPronoun: null,
|
||||
weapons: JSON.stringify([
|
||||
{ weaponSplId: 1 as MainWeaponId, isFavorite: 0 },
|
||||
]) as any,
|
||||
favoriteBadgeIds: [],
|
||||
inGameName: null,
|
||||
sensitivity: [null, null] as [null, null],
|
||||
pronouns: [null, null] as [null, null],
|
||||
weapons: [{ id: 1 as MainWeaponId, isFavorite: false }],
|
||||
showDiscordUniqueName: true,
|
||||
newProfileEnabled: false,
|
||||
css: null,
|
||||
};
|
||||
|
||||
describe("user page editing", () => {
|
||||
|
|
@ -41,8 +38,8 @@ describe("user page editing", () => {
|
|||
it("adds valid custom css vars", async () => {
|
||||
const response = await action(
|
||||
{
|
||||
css: JSON.stringify({ bg: "#fff" }),
|
||||
...DEFAULT_FIELDS,
|
||||
css: { bg: "#fff" },
|
||||
},
|
||||
{ user: "regular", params: { identifier: "2" } },
|
||||
);
|
||||
|
|
@ -53,28 +50,28 @@ describe("user page editing", () => {
|
|||
it("prevents adding custom css var of unknown property", async () => {
|
||||
const res = await action(
|
||||
{
|
||||
css: JSON.stringify({
|
||||
"backdrop-filter": "#fff",
|
||||
}),
|
||||
...DEFAULT_FIELDS,
|
||||
css: {
|
||||
"backdrop-filter": "#fff",
|
||||
} as any,
|
||||
},
|
||||
{ user: "regular", params: { identifier: "2" } },
|
||||
);
|
||||
|
||||
expect(res.errors[0]).toBe("Invalid custom CSS var object");
|
||||
expect(res.fieldErrors.css).toBeDefined();
|
||||
});
|
||||
|
||||
it("prevents adding custom css var of unknown value", async () => {
|
||||
const res = await action(
|
||||
{
|
||||
css: JSON.stringify({
|
||||
bg: "url(https://sendou.ink/u?q=1&_data=features%2Fuser-search%2Froutes%2Fu)",
|
||||
}),
|
||||
...DEFAULT_FIELDS,
|
||||
css: {
|
||||
bg: "url(https://sendou.ink/u?q=1&_data=features%2Fuser-search%2Froutes%2Fu)",
|
||||
},
|
||||
},
|
||||
{ user: "regular", params: { identifier: "2" } },
|
||||
);
|
||||
|
||||
expect(res.errors[0]).toBe("Invalid custom CSS var object");
|
||||
expect(res.fieldErrors.css).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,37 +1,26 @@
|
|||
import clsx from "clsx";
|
||||
import * as React from "react";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { Form, Link, useLoaderData, useMatches } from "react-router";
|
||||
import { Link, useLoaderData, useMatches } from "react-router";
|
||||
import { CustomizedColorsInput } from "~/components/CustomizedColorsInput";
|
||||
import { SendouButton } from "~/components/elements/Button";
|
||||
import { SendouSelect, SendouSelectItem } from "~/components/elements/Select";
|
||||
import { SendouSwitch } from "~/components/elements/Switch";
|
||||
import { FormErrors } from "~/components/FormErrors";
|
||||
import { FormMessage } from "~/components/FormMessage";
|
||||
import { WeaponImage } from "~/components/Image";
|
||||
import { Input } from "~/components/Input";
|
||||
import { StarIcon } from "~/components/icons/Star";
|
||||
import { StarFilledIcon } from "~/components/icons/StarFilled";
|
||||
import { TrashIcon } from "~/components/icons/Trash";
|
||||
import { Label } from "~/components/Label";
|
||||
import { SubmitButton } from "~/components/SubmitButton";
|
||||
import { WeaponSelect } from "~/components/WeaponSelect";
|
||||
import { OBJECT_PRONOUNS, SUBJECT_PRONOUNS, type Tables } from "~/db/tables";
|
||||
import { BADGE } from "~/features/badges/badges-constants";
|
||||
import { BadgesSelector } from "~/features/badges/components/BadgesSelector";
|
||||
import type { CustomFieldRenderProps } from "~/form/FormField";
|
||||
import { SendouForm } from "~/form/SendouForm";
|
||||
import { useIsMounted } from "~/hooks/useIsMounted";
|
||||
import { useHasRole } from "~/modules/permissions/hooks";
|
||||
import { countryCodeToTranslatedName } from "~/utils/i18n";
|
||||
import invariant from "~/utils/invariant";
|
||||
import { rawSensToString } from "~/utils/strings";
|
||||
import type { SendouRouteHandle } from "~/utils/remix.server";
|
||||
import { FAQ_PAGE } from "~/utils/urls";
|
||||
import { action } from "../actions/u.$identifier.edit.server";
|
||||
import { loader } from "../loaders/u.$identifier.edit.server";
|
||||
import type { UserPageLoaderData } from "../loaders/u.$identifier.server";
|
||||
import { COUNTRY_CODES, USER } from "../user-page-constants";
|
||||
import { COUNTRY_CODES } from "../user-page-constants";
|
||||
import { userEditProfileBaseSchema } from "../user-page-schemas";
|
||||
export { loader, action };
|
||||
|
||||
import styles from "~/styles/u.$identifier.module.css";
|
||||
export const handle: SendouRouteHandle = {
|
||||
i18n: ["user"],
|
||||
};
|
||||
|
||||
export default function UserEditPage() {
|
||||
const { t } = useTranslation(["common", "user"]);
|
||||
|
|
@ -43,545 +32,141 @@ export default function UserEditPage() {
|
|||
const isSupporter = useHasRole("SUPPORTER");
|
||||
const isArtist = useHasRole("ARTIST");
|
||||
|
||||
const countryOptions = useCountryOptions();
|
||||
|
||||
const badgeOptions = data.user.badges.map((badge) => ({
|
||||
id: badge.id,
|
||||
displayName: badge.displayName,
|
||||
code: badge.code,
|
||||
hue: badge.hue,
|
||||
}));
|
||||
|
||||
const defaultValues = {
|
||||
css: layoutData.css ?? null,
|
||||
customName: data.user.customName ?? "",
|
||||
customUrl: layoutData.user.customUrl ?? "",
|
||||
inGameName: data.user.inGameName ?? "",
|
||||
sensitivity: sensDefaultValue(data.user.motionSens, data.user.stickSens),
|
||||
pronouns: pronounsDefaultValue(data.user.pronouns),
|
||||
battlefy: data.user.battlefy ?? "",
|
||||
country: data.user.country ?? null,
|
||||
favoriteBadgeIds: data.favoriteBadgeIds ?? [],
|
||||
weapons: data.user.weapons.map((w) => ({
|
||||
id: w.weaponSplId,
|
||||
isFavorite: Boolean(w.isFavorite),
|
||||
})),
|
||||
bio: data.user.bio ?? "",
|
||||
showDiscordUniqueName: Boolean(data.user.showDiscordUniqueName),
|
||||
commissionsOpen: Boolean(layoutData.user.commissionsOpen),
|
||||
commissionText: layoutData.user.commissionText ?? "",
|
||||
newProfileEnabled: isSupporter && data.newProfileEnabled,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="half-width">
|
||||
<Form className={styles.container} method="post">
|
||||
{isSupporter ? (
|
||||
<CustomizedColorsInput initialColors={layoutData.css} />
|
||||
) : null}
|
||||
<CustomNameInput />
|
||||
<CustomUrlInput parentRouteData={layoutData} />
|
||||
<InGameNameInputs />
|
||||
<SensSelects />
|
||||
<PronounsSelect />
|
||||
<BattlefyInput />
|
||||
<CountrySelect />
|
||||
<FavBadgeSelect />
|
||||
<WeaponPoolSelect />
|
||||
<BioTextarea initialValue={data.user.bio} />
|
||||
{data.discordUniqueName ? (
|
||||
<ShowUniqueDiscordNameToggle />
|
||||
) : (
|
||||
<input type="hidden" name="showDiscordUniqueName" value="on" />
|
||||
)}
|
||||
{isArtist ? (
|
||||
<SendouForm
|
||||
schema={userEditProfileBaseSchema}
|
||||
defaultValues={defaultValues}
|
||||
submitButtonText={t("common:actions.save")}
|
||||
>
|
||||
{({ FormField }) => (
|
||||
<>
|
||||
<CommissionsOpenToggle parentRouteData={layoutData} />
|
||||
<CommissionTextArea initialValue={layoutData.user.commissionText} />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<input type="hidden" name="commissionsOpen" value="off" />
|
||||
<input type="hidden" name="commissionText" value="" />
|
||||
{isSupporter ? (
|
||||
<FormField name="css">
|
||||
{(props: CustomFieldRenderProps) => (
|
||||
<CssCustomField {...props} initialColors={layoutData.css} />
|
||||
)}
|
||||
</FormField>
|
||||
) : null}
|
||||
<FormField name="customName" />
|
||||
<FormField name="customUrl" />
|
||||
<FormField name="inGameName" />
|
||||
<FormField name="sensitivity" />
|
||||
<FormField name="pronouns" />
|
||||
<FormField name="battlefy" />
|
||||
<FormField name="country" options={countryOptions} />
|
||||
{data.user.badges.length >= 2 ? (
|
||||
<FormField
|
||||
name="favoriteBadgeIds"
|
||||
options={badgeOptions}
|
||||
maxCount={
|
||||
isSupporter ? BADGE.SMALL_BADGES_PER_DISPLAY_PAGE + 1 : 1
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
<FormField name="weapons" />
|
||||
<FormField name="bio" />
|
||||
{data.discordUniqueName ? (
|
||||
<FormField name="showDiscordUniqueName" />
|
||||
) : null}
|
||||
{isArtist ? (
|
||||
<>
|
||||
<FormField name="commissionsOpen" />
|
||||
<FormField name="commissionText" />
|
||||
</>
|
||||
) : null}
|
||||
<FormField name="newProfileEnabled" disabled={!isSupporter} />
|
||||
<FormMessage type="info">
|
||||
<Trans i18nKey={"user:discordExplanation"} t={t}>
|
||||
Username, profile picture, YouTube, Bluesky and Twitch accounts
|
||||
come from your Discord account. See{" "}
|
||||
<Link to={FAQ_PAGE}>FAQ</Link> for more information.
|
||||
</Trans>
|
||||
</FormMessage>
|
||||
</>
|
||||
)}
|
||||
<NewProfileToggle />
|
||||
<FormMessage type="info">
|
||||
<Trans i18nKey={"user:discordExplanation"} t={t}>
|
||||
Username, profile picture, YouTube, Bluesky and Twitch accounts come
|
||||
from your Discord account. See <Link to={FAQ_PAGE}>FAQ</Link> for
|
||||
more information.
|
||||
</Trans>
|
||||
</FormMessage>
|
||||
<SubmitButton>{t("common:actions.save")}</SubmitButton>
|
||||
<FormErrors namespace="user" />
|
||||
</Form>
|
||||
</SendouForm>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function CustomUrlInput({
|
||||
parentRouteData,
|
||||
}: {
|
||||
parentRouteData: UserPageLoaderData;
|
||||
}) {
|
||||
const { t } = useTranslation(["user"]);
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<Label htmlFor="customUrl">{t("user:customUrl")}</Label>
|
||||
<Input
|
||||
name="customUrl"
|
||||
id="customUrl"
|
||||
leftAddon="https://sendou.ink/u/"
|
||||
maxLength={USER.CUSTOM_URL_MAX_LENGTH}
|
||||
defaultValue={parentRouteData.user.customUrl ?? undefined}
|
||||
/>
|
||||
<FormMessage type="info">{t("user:forms.info.customUrl")}</FormMessage>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function CustomNameInput() {
|
||||
const { t } = useTranslation(["user"]);
|
||||
const data = useLoaderData<typeof loader>();
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<Label htmlFor="customName">{t("user:customName")}</Label>
|
||||
<Input
|
||||
name="customName"
|
||||
id="customName"
|
||||
maxLength={USER.CUSTOM_NAME_MAX_LENGTH}
|
||||
defaultValue={data.user.customName ?? undefined}
|
||||
/>
|
||||
<FormMessage type="info">
|
||||
{t("user:forms.customName.info", {
|
||||
discordName: data.user.discordName,
|
||||
})}
|
||||
</FormMessage>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function InGameNameInputs() {
|
||||
const { t } = useTranslation(["user"]);
|
||||
const data = useLoaderData<typeof loader>();
|
||||
|
||||
const inGameNameParts = data.user.inGameName?.split("#");
|
||||
|
||||
return (
|
||||
<div className="stack items-start">
|
||||
<Label>{t("user:ign")}</Label>
|
||||
<div className="stack horizontal sm items-center">
|
||||
<Input
|
||||
className={styles.inGameNameText}
|
||||
name="inGameNameText"
|
||||
aria-label="In game name"
|
||||
maxLength={USER.IN_GAME_NAME_TEXT_MAX_LENGTH}
|
||||
defaultValue={inGameNameParts?.[0]}
|
||||
/>
|
||||
<div className={styles.inGameNameHashtag}>#</div>
|
||||
<Input
|
||||
className={styles.inGameNameDiscriminator}
|
||||
name="inGameNameDiscriminator"
|
||||
aria-label="In game name discriminator"
|
||||
maxLength={USER.IN_GAME_NAME_DISCRIMINATOR_MAX_LENGTH}
|
||||
pattern="[0-9a-z]{4,5}"
|
||||
defaultValue={inGameNameParts?.[1]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const SENS_OPTIONS = [
|
||||
-50, -45, -40, -35, -30, -25, -20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35,
|
||||
40, 45, 50,
|
||||
];
|
||||
function SensSelects() {
|
||||
const { t } = useTranslation(["user"]);
|
||||
const data = useLoaderData<typeof loader>();
|
||||
|
||||
return (
|
||||
<div className="stack horizontal md">
|
||||
<div>
|
||||
<Label htmlFor="motionSens">{t("user:motionSens")}</Label>
|
||||
<select
|
||||
id="motionSens"
|
||||
name="motionSens"
|
||||
defaultValue={data.user.motionSens ?? undefined}
|
||||
className={styles.sensSelect}
|
||||
>
|
||||
<option value="">{"-"}</option>
|
||||
{SENS_OPTIONS.map((sens) => (
|
||||
<option key={sens} value={sens}>
|
||||
{rawSensToString(Number(sens))}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="stickSens">{t("user:stickSens")}</Label>
|
||||
<select
|
||||
id="stickSens"
|
||||
name="stickSens"
|
||||
defaultValue={data.user.stickSens ?? undefined}
|
||||
className={styles.sensSelect}
|
||||
>
|
||||
<option value="">{"-"}</option>
|
||||
{SENS_OPTIONS.map((sens) => (
|
||||
<option key={sens} value={sens}>
|
||||
{rawSensToString(Number(sens))}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PronounsSelect() {
|
||||
const { t } = useTranslation(["user"]);
|
||||
const data = useLoaderData<typeof loader>();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="stack horizontal md">
|
||||
<div>
|
||||
<Label htmlFor="subjectPronoun">{t("user:pronoun")}</Label>
|
||||
<select
|
||||
id="subjectPronoun"
|
||||
name="subjectPronoun"
|
||||
defaultValue={data.user.pronouns?.subject ?? undefined}
|
||||
className={styles.sensSelect}
|
||||
>
|
||||
<option value="">{"-"}</option>
|
||||
{SUBJECT_PRONOUNS.map((pronoun) => (
|
||||
<option key={pronoun} value={pronoun}>
|
||||
{pronoun}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<span className={styles.seperator}>/</span>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="objectPronoun">{t("user:pronoun")}</Label>
|
||||
<select
|
||||
id="objectPronoun"
|
||||
name="objectPronoun"
|
||||
defaultValue={data.user.pronouns?.object ?? undefined}
|
||||
className={styles.sensSelect}
|
||||
>
|
||||
<option value="">{"-"}</option>
|
||||
{OBJECT_PRONOUNS.map((pronoun) => (
|
||||
<option key={pronoun} value={pronoun}>
|
||||
{pronoun}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<FormMessage type="info">{t("user:pronounsInfo")}</FormMessage>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function CountrySelect() {
|
||||
const { t, i18n } = useTranslation(["user"]);
|
||||
const data = useLoaderData<typeof loader>();
|
||||
function useCountryOptions() {
|
||||
const { i18n } = useTranslation();
|
||||
const isMounted = useIsMounted();
|
||||
const [value, setValue] = React.useState(data.user.country ?? null);
|
||||
|
||||
// TODO: if react-aria-components start supporting "suppressHydrationWarning" it would likely be a better solution here
|
||||
const items = COUNTRY_CODES.map((countryCode) => ({
|
||||
name: isMounted
|
||||
return COUNTRY_CODES.map((countryCode) => ({
|
||||
value: countryCode,
|
||||
label: isMounted
|
||||
? countryCodeToTranslatedName({
|
||||
countryCode,
|
||||
language: i18n.language,
|
||||
})
|
||||
: countryCode,
|
||||
id: countryCode,
|
||||
key: countryCode,
|
||||
})).sort((a, b) =>
|
||||
a.name.localeCompare(b.name, i18n.language, { sensitivity: "base" }),
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* TODO: this is a workaround for clearable not working with uncontrolled values, in future the component should handle this one way or another */}
|
||||
<input type="hidden" name="country" value={value ?? ""} />
|
||||
<SendouSelect
|
||||
items={items}
|
||||
label={t("user:country")}
|
||||
search={{
|
||||
placeholder: t("user:forms.country.search.placeholder"),
|
||||
}}
|
||||
selectedKey={value}
|
||||
onSelectionChange={(value) => setValue(value as string | null)}
|
||||
clearable
|
||||
>
|
||||
{({ key, ...item }) => (
|
||||
<SendouSelectItem key={key} {...item}>
|
||||
{item.name}
|
||||
</SendouSelectItem>
|
||||
)}
|
||||
</SendouSelect>
|
||||
</>
|
||||
a.label.localeCompare(b.label, i18n.language, { sensitivity: "base" }),
|
||||
);
|
||||
}
|
||||
|
||||
function BattlefyInput() {
|
||||
const { t } = useTranslation(["user"]);
|
||||
const data = useLoaderData<typeof loader>();
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<Label htmlFor="customName">{t("user:battlefy")}</Label>
|
||||
<Input
|
||||
name="battlefy"
|
||||
id="battlefy"
|
||||
maxLength={USER.BATTLEFY_MAX_LENGTH}
|
||||
defaultValue={data.user.battlefy ?? undefined}
|
||||
leftAddon="https://battlefy.com/users/"
|
||||
/>
|
||||
<FormMessage type="info">{t("user:forms.info.battlefy")}</FormMessage>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function WeaponPoolSelect() {
|
||||
const data = useLoaderData<typeof loader>();
|
||||
const [weapons, setWeapons] = React.useState(data.user.weapons);
|
||||
const { t } = useTranslation(["user"]);
|
||||
|
||||
const latestWeapon = weapons[weapons.length - 1];
|
||||
|
||||
return (
|
||||
<div className={clsx("stack md", styles.weaponPool)}>
|
||||
<input type="hidden" name="weapons" value={JSON.stringify(weapons)} />
|
||||
{weapons.length < USER.WEAPON_POOL_MAX_SIZE ? (
|
||||
<WeaponSelect
|
||||
label={t("user:weaponPool")}
|
||||
onChange={(weaponSplId) => {
|
||||
setWeapons([
|
||||
...weapons,
|
||||
{
|
||||
weaponSplId,
|
||||
isFavorite: 0,
|
||||
},
|
||||
]);
|
||||
}}
|
||||
disabledWeaponIds={weapons.map((w) => w.weaponSplId)}
|
||||
// empty on selection
|
||||
key={latestWeapon?.weaponSplId ?? "empty"}
|
||||
/>
|
||||
) : (
|
||||
<span className="text-xs text-warning">
|
||||
{t("user:forms.errors.maxWeapons")}
|
||||
</span>
|
||||
)}
|
||||
<div className="stack horizontal sm justify-center">
|
||||
{weapons.map((weapon) => {
|
||||
return (
|
||||
<div key={weapon.weaponSplId} className="stack xs">
|
||||
<div className="u__weapon">
|
||||
<WeaponImage
|
||||
weaponSplId={weapon.weaponSplId}
|
||||
variant={weapon.isFavorite ? "badge-5-star" : "badge"}
|
||||
width={38}
|
||||
height={38}
|
||||
/>
|
||||
</div>
|
||||
<div className="stack sm horizontal items-center justify-center">
|
||||
<SendouButton
|
||||
icon={weapon.isFavorite ? <StarFilledIcon /> : <StarIcon />}
|
||||
variant="minimal"
|
||||
aria-label="Favorite weapon"
|
||||
onPress={() =>
|
||||
setWeapons(
|
||||
weapons.map((w) =>
|
||||
w.weaponSplId === weapon.weaponSplId
|
||||
? {
|
||||
...weapon,
|
||||
isFavorite: weapon.isFavorite === 1 ? 0 : 1,
|
||||
}
|
||||
: w,
|
||||
),
|
||||
)
|
||||
}
|
||||
/>
|
||||
<SendouButton
|
||||
icon={<TrashIcon />}
|
||||
variant="minimal-destructive"
|
||||
aria-label="Delete weapon"
|
||||
onPress={() =>
|
||||
setWeapons(
|
||||
weapons.filter(
|
||||
(w) => w.weaponSplId !== weapon.weaponSplId,
|
||||
),
|
||||
)
|
||||
}
|
||||
data-testid={`delete-weapon-${weapon.weaponSplId}`}
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function BioTextarea({
|
||||
initialValue,
|
||||
}: {
|
||||
initialValue: Tables["User"]["bio"];
|
||||
function CssCustomField({
|
||||
value,
|
||||
onChange,
|
||||
initialColors,
|
||||
}: CustomFieldRenderProps & {
|
||||
initialColors?: Record<string, string> | null;
|
||||
}) {
|
||||
const { t } = useTranslation("user");
|
||||
const [value, setValue] = React.useState(initialValue ?? "");
|
||||
|
||||
return (
|
||||
<div className={styles.bioContainer}>
|
||||
<Label
|
||||
htmlFor="bio"
|
||||
valueLimits={{ current: value.length, max: USER.BIO_MAX_LENGTH }}
|
||||
>
|
||||
{t("bio")}
|
||||
</Label>
|
||||
<textarea
|
||||
id="bio"
|
||||
name="bio"
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
maxLength={USER.BIO_MAX_LENGTH}
|
||||
/>
|
||||
</div>
|
||||
<CustomizedColorsInput
|
||||
initialColors={initialColors}
|
||||
value={value as Record<string, string> | null}
|
||||
onChange={onChange as (value: Record<string, string> | null) => void}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function FavBadgeSelect() {
|
||||
const data = useLoaderData<typeof loader>();
|
||||
const { t } = useTranslation(["user"]);
|
||||
const [value, setValue] = React.useState(data.favoriteBadgeIds ?? []);
|
||||
const isSupporter = useHasRole("SUPPORTER");
|
||||
|
||||
// doesn't make sense to select favorite badge
|
||||
// if user has no badges or only has 1 badge
|
||||
if (data.user.badges.length < 2) return null;
|
||||
|
||||
const onChange = (newBadges: number[]) => {
|
||||
if (isSupporter) {
|
||||
setValue(newBadges);
|
||||
} else {
|
||||
// non-supporters can only set which badge is the big one
|
||||
setValue(newBadges.length > 0 ? [newBadges[0]] : []);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<input
|
||||
type="hidden"
|
||||
name="favoriteBadgeIds"
|
||||
value={JSON.stringify(value)}
|
||||
/>
|
||||
<label htmlFor="favoriteBadgeIds">{t("user:favoriteBadges")}</label>
|
||||
<BadgesSelector
|
||||
options={data.user.badges}
|
||||
selectedBadges={value}
|
||||
onChange={onChange}
|
||||
maxCount={BADGE.SMALL_BADGES_PER_DISPLAY_PAGE + 1}
|
||||
showSelect={isSupporter || value.length === 0}
|
||||
>
|
||||
{!isSupporter ? (
|
||||
<div className="text-sm text-lighter font-semi-bold text-center">
|
||||
{t("user:forms.favoriteBadges.nonSupporter")}
|
||||
</div>
|
||||
) : null}
|
||||
</BadgesSelector>
|
||||
</div>
|
||||
);
|
||||
function pronounsDefaultValue(
|
||||
pronouns: { subject: string; object: string } | null,
|
||||
): [string | null, string | null] {
|
||||
if (!pronouns) return [null, null];
|
||||
return [pronouns.subject, pronouns.object];
|
||||
}
|
||||
|
||||
function NewProfileToggle() {
|
||||
const { t } = useTranslation(["user"]);
|
||||
const data = useLoaderData<typeof loader>();
|
||||
const isSupporter = useHasRole("SUPPORTER");
|
||||
const [checked, setChecked] = React.useState(
|
||||
isSupporter && data.newProfileEnabled,
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<label htmlFor="newProfileEnabled">
|
||||
{t("user:forms.newProfileEnabled")}
|
||||
</label>
|
||||
<SendouSwitch
|
||||
isSelected={checked}
|
||||
onChange={setChecked}
|
||||
name="newProfileEnabled"
|
||||
isDisabled={!isSupporter}
|
||||
/>
|
||||
<FormMessage type="info">
|
||||
{t("user:forms.newProfileEnabled.info")}
|
||||
</FormMessage>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ShowUniqueDiscordNameToggle() {
|
||||
const { t } = useTranslation(["user"]);
|
||||
const data = useLoaderData<typeof loader>();
|
||||
const [checked, setChecked] = React.useState(
|
||||
Boolean(data.user.showDiscordUniqueName),
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<label htmlFor="showDiscordUniqueName">
|
||||
{t("user:forms.showDiscordUniqueName")}
|
||||
</label>
|
||||
<SendouSwitch
|
||||
isSelected={checked}
|
||||
onChange={setChecked}
|
||||
name="showDiscordUniqueName"
|
||||
/>
|
||||
<FormMessage type="info">
|
||||
{t("user:forms.showDiscordUniqueName.info", {
|
||||
discordUniqueName: data.discordUniqueName,
|
||||
})}
|
||||
</FormMessage>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function CommissionsOpenToggle({
|
||||
parentRouteData,
|
||||
}: {
|
||||
parentRouteData: UserPageLoaderData;
|
||||
}) {
|
||||
const { t } = useTranslation(["user"]);
|
||||
const [checked, setChecked] = React.useState(
|
||||
Boolean(parentRouteData.user.commissionsOpen),
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<label htmlFor="commissionsOpen">{t("user:forms.commissionsOpen")}</label>
|
||||
<SendouSwitch
|
||||
isSelected={checked}
|
||||
onChange={setChecked}
|
||||
name="commissionsOpen"
|
||||
/>
|
||||
<FormMessage type="info">
|
||||
{t("user:forms.commissionsOpen.info")}
|
||||
</FormMessage>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function CommissionTextArea({
|
||||
initialValue,
|
||||
}: {
|
||||
initialValue: Tables["User"]["commissionText"];
|
||||
}) {
|
||||
const { t } = useTranslation(["user"]);
|
||||
const [value, setValue] = React.useState(initialValue ?? "");
|
||||
|
||||
return (
|
||||
<div className={styles.bioContainer}>
|
||||
<Label
|
||||
htmlFor="commissionText"
|
||||
valueLimits={{
|
||||
current: value.length,
|
||||
max: USER.COMMISSION_TEXT_MAX_LENGTH,
|
||||
}}
|
||||
>
|
||||
{t("user:forms.commissionText")}
|
||||
</Label>
|
||||
<textarea
|
||||
id="commissionText"
|
||||
name="commissionText"
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
maxLength={USER.COMMISSION_TEXT_MAX_LENGTH}
|
||||
/>
|
||||
<FormMessage type="info">
|
||||
{t("user:forms.commissionText.info")}
|
||||
</FormMessage>
|
||||
</div>
|
||||
);
|
||||
function sensDefaultValue(
|
||||
motionSens: number | null,
|
||||
stickSens: number | null,
|
||||
): [string | null, string | null] {
|
||||
if (motionSens === null && stickSens === null) return [null, null];
|
||||
return [
|
||||
motionSens !== null ? String(motionSens) : null,
|
||||
stickSens !== null ? String(stickSens) : null,
|
||||
];
|
||||
}
|
||||
|
|
|
|||
39
app/features/user-page/user-page-constants.test.ts
Normal file
39
app/features/user-page/user-page-constants.test.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { IN_GAME_NAME_REGEXP } from "./user-page-constants";
|
||||
|
||||
describe("IN_GAME_NAME_REGEXP", () => {
|
||||
it("should pass valid in-game names", () => {
|
||||
const validNames = [
|
||||
"Sendou#12345",
|
||||
"The Player#12345",
|
||||
" a#1234",
|
||||
"A#1234",
|
||||
"Player#abcd",
|
||||
"名前テスト1234#ab12c",
|
||||
];
|
||||
|
||||
for (const name of validNames) {
|
||||
expect(IN_GAME_NAME_REGEXP.test(name), `expected "${name}" to pass`).toBe(
|
||||
true,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("should not pass invalid in-game names", () => {
|
||||
const invalidNames = [
|
||||
"#1234",
|
||||
"Sendou1234",
|
||||
"Sendou#123",
|
||||
"Sendou# 1234",
|
||||
"Sendou#123456",
|
||||
"Sendou#ABCD",
|
||||
"12345678901#1234",
|
||||
];
|
||||
|
||||
for (const name of invalidNames) {
|
||||
expect(IN_GAME_NAME_REGEXP.test(name), `expected "${name}" to fail`).toBe(
|
||||
false,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -18,6 +18,8 @@ export const USER = {
|
|||
GAME_BADGES_SMALL_MAX: 4,
|
||||
};
|
||||
|
||||
export const IN_GAME_NAME_REGEXP = /^.{1,10}#[0-9a-z]{4,5}$/;
|
||||
|
||||
export const MATCHES_PER_SEASONS_PAGE = 8;
|
||||
export const RESULTS_PER_PAGE = 25;
|
||||
export const DEFAULT_BUILD_SORT = ["WEAPON_POOL", "UPDATED_AT"] as const;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,29 @@
|
|||
import { z } from "zod";
|
||||
import { requireUser } from "~/features/auth/core/user.server";
|
||||
import * as BuildRepository from "~/features/builds/BuildRepository.server";
|
||||
import { gearAllOrNoneRefine, newBuildBaseSchema } from "./user-page-schemas";
|
||||
import * as UserRepository from "./UserRepository.server";
|
||||
import {
|
||||
gearAllOrNoneRefine,
|
||||
newBuildBaseSchema,
|
||||
userEditProfileBaseSchema,
|
||||
} from "./user-page-schemas";
|
||||
|
||||
export const userEditProfileSchemaServer =
|
||||
userEditProfileBaseSchema.superRefine(async (data, ctx) => {
|
||||
if (!data.customUrl) return;
|
||||
|
||||
const existingUser = await UserRepository.findByCustomUrl(data.customUrl);
|
||||
if (!existingUser) return;
|
||||
|
||||
const currentUser = requireUser();
|
||||
if (existingUser.id === currentUser.id) return;
|
||||
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "forms:errors.profileCustomUrlDuplicate",
|
||||
path: ["customUrl"],
|
||||
});
|
||||
});
|
||||
|
||||
export const newBuildSchemaServer = newBuildBaseSchema
|
||||
.refine(gearAllOrNoneRefine.fn, gearAllOrNoneRefine.opts)
|
||||
|
|
|
|||
|
|
@ -3,12 +3,16 @@ import { OBJECT_PRONOUNS, SUBJECT_PRONOUNS } from "~/db/tables";
|
|||
import { BADGE } from "~/features/badges/badges-constants";
|
||||
import * as Seasons from "~/features/mmr/core/Seasons";
|
||||
import {
|
||||
badges,
|
||||
checkboxGroup,
|
||||
customField,
|
||||
dualSelectOptional,
|
||||
idConstantOptional,
|
||||
selectDynamicOptional,
|
||||
stringConstant,
|
||||
textAreaOptional,
|
||||
textAreaRequired,
|
||||
textFieldOptional,
|
||||
textFieldRequired,
|
||||
toggle,
|
||||
weaponPool,
|
||||
|
|
@ -18,32 +22,23 @@ import {
|
|||
headGearIds,
|
||||
shoesGearIds,
|
||||
} from "~/modules/in-game-lists/gear-ids";
|
||||
import { rawSensToString } from "~/utils/strings";
|
||||
import { isCustomUrl } from "~/utils/urls";
|
||||
import {
|
||||
_action,
|
||||
actualNumber,
|
||||
checkboxValueToDbBoolean,
|
||||
clothesMainSlotAbility,
|
||||
customCssVarObject,
|
||||
dbBoolean,
|
||||
emptyArrayToNull,
|
||||
falsyToNull,
|
||||
headMainSlotAbility,
|
||||
id,
|
||||
nullLiteraltoNull,
|
||||
processMany,
|
||||
safeJSONParse,
|
||||
safeNullableStringSchema,
|
||||
shoesMainSlotAbility,
|
||||
stackableAbility,
|
||||
undefinedToNull,
|
||||
weaponSplId,
|
||||
} from "~/utils/zod";
|
||||
import { allWidgetsFlat, findWidgetById } from "./core/widgets/portfolio";
|
||||
import {
|
||||
COUNTRY_CODES,
|
||||
CUSTOM_CSS_VAR_COLORS,
|
||||
HIGHLIGHT_CHECKBOX_NAME,
|
||||
HIGHLIGHT_TOURNAMENT_CHECKBOX_NAME,
|
||||
IN_GAME_NAME_REGEXP,
|
||||
USER,
|
||||
} from "./user-page-constants";
|
||||
|
||||
|
|
@ -58,116 +53,135 @@ export const seasonsSearchParamsSchema = z.object({
|
|||
.refine((nth) => !nth || Seasons.allStarted(new Date()).includes(nth)),
|
||||
});
|
||||
|
||||
export const userEditActionSchema = z
|
||||
.object({
|
||||
country: z.preprocess(
|
||||
falsyToNull,
|
||||
z
|
||||
.string()
|
||||
.refine((val) => !val || COUNTRY_CODES.includes(val as any))
|
||||
.nullable(),
|
||||
),
|
||||
bio: z.preprocess(
|
||||
falsyToNull,
|
||||
z.string().max(USER.BIO_MAX_LENGTH).nullable(),
|
||||
),
|
||||
customUrl: z.preprocess(
|
||||
falsyToNull,
|
||||
z
|
||||
.string()
|
||||
.max(USER.CUSTOM_URL_MAX_LENGTH)
|
||||
.refine((val) => val === null || isCustomUrl(val), {
|
||||
message: "forms.errors.invalidCustomUrl.numbers",
|
||||
})
|
||||
.refine((val) => val === null || /^[a-zA-Z0-9-_]+$/.test(val), {
|
||||
message: "forms.errors.invalidCustomUrl.strangeCharacter",
|
||||
})
|
||||
.transform((val) => val?.toLowerCase())
|
||||
.nullable(),
|
||||
),
|
||||
customName: safeNullableStringSchema({ max: USER.CUSTOM_NAME_MAX_LENGTH }),
|
||||
battlefy: z.preprocess(
|
||||
falsyToNull,
|
||||
z.string().max(USER.BATTLEFY_MAX_LENGTH).nullable(),
|
||||
),
|
||||
stickSens: z.preprocess(
|
||||
processMany(actualNumber, undefinedToNull),
|
||||
z
|
||||
.number()
|
||||
.min(-50)
|
||||
.max(50)
|
||||
.refine((val) => val % 5 === 0)
|
||||
.nullable(),
|
||||
),
|
||||
motionSens: z.preprocess(
|
||||
processMany(actualNumber, undefinedToNull),
|
||||
z
|
||||
.number()
|
||||
.min(-50)
|
||||
.max(50)
|
||||
.refine((val) => val % 5 === 0)
|
||||
.nullable(),
|
||||
),
|
||||
subjectPronoun: z.preprocess(
|
||||
processMany(nullLiteraltoNull, falsyToNull),
|
||||
z.enum(SUBJECT_PRONOUNS).nullable(),
|
||||
),
|
||||
objectPronoun: z.preprocess(
|
||||
processMany(nullLiteraltoNull, falsyToNull),
|
||||
z.enum(OBJECT_PRONOUNS).nullable(),
|
||||
),
|
||||
inGameNameText: z.preprocess(
|
||||
falsyToNull,
|
||||
z.string().max(USER.IN_GAME_NAME_TEXT_MAX_LENGTH).nullable(),
|
||||
),
|
||||
inGameNameDiscriminator: z.preprocess(
|
||||
falsyToNull,
|
||||
z
|
||||
.string()
|
||||
.refine((val) => /^[0-9a-z]{4,5}$/.test(val))
|
||||
.nullable(),
|
||||
),
|
||||
css: customCssVarObject,
|
||||
weapons: z.preprocess(
|
||||
safeJSONParse,
|
||||
z
|
||||
.array(
|
||||
z.object({
|
||||
weaponSplId,
|
||||
isFavorite: dbBoolean,
|
||||
}),
|
||||
)
|
||||
.max(USER.WEAPON_POOL_MAX_SIZE),
|
||||
),
|
||||
favoriteBadgeIds: z.preprocess(
|
||||
processMany(safeJSONParse, emptyArrayToNull),
|
||||
z
|
||||
.array(id)
|
||||
.min(1)
|
||||
.max(BADGE.SMALL_BADGES_PER_DISPLAY_PAGE + 1)
|
||||
.nullish(),
|
||||
),
|
||||
showDiscordUniqueName: z.preprocess(checkboxValueToDbBoolean, dbBoolean),
|
||||
newProfileEnabled: z.preprocess(checkboxValueToDbBoolean, dbBoolean),
|
||||
commissionsOpen: z.preprocess(checkboxValueToDbBoolean, dbBoolean),
|
||||
commissionText: z.preprocess(
|
||||
falsyToNull,
|
||||
z.string().max(USER.COMMISSION_TEXT_MAX_LENGTH).nullable(),
|
||||
),
|
||||
})
|
||||
const cssObjectSchema = z
|
||||
.record(z.string(), z.string())
|
||||
.nullable()
|
||||
.refine(
|
||||
(val) => {
|
||||
if (val.motionSens !== null && val.stickSens === null) {
|
||||
return false;
|
||||
if (!val) return true;
|
||||
for (const [key, value] of Object.entries(val)) {
|
||||
if (!CUSTOM_CSS_VAR_COLORS.includes(key as never)) return false;
|
||||
if (!/^#(?:[0-9a-fA-F]{3}){1,2}[0-9]{0,2}$/.test(value)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message: "forms.errors.invalidSens",
|
||||
},
|
||||
{ message: "Invalid custom CSS colors" },
|
||||
);
|
||||
|
||||
const SENS_ITEMS = [
|
||||
-50, -45, -40, -35, -30, -25, -20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35,
|
||||
40, 45, 50,
|
||||
].map((val) => ({
|
||||
label: () => rawSensToString(val),
|
||||
value: String(val),
|
||||
}));
|
||||
|
||||
export const userEditProfileBaseSchema = z.object({
|
||||
css: customField({ initialValue: null }, cssObjectSchema),
|
||||
customName: textFieldOptional({
|
||||
label: "labels.profileCustomName",
|
||||
bottomText: "bottomTexts.profileCustomName",
|
||||
maxLength: USER.CUSTOM_NAME_MAX_LENGTH,
|
||||
}),
|
||||
customUrl: textFieldOptional({
|
||||
label: "labels.profileCustomUrl",
|
||||
bottomText: "bottomTexts.profileCustomUrl",
|
||||
leftAddon: "https://sendou.ink/u/",
|
||||
maxLength: USER.CUSTOM_URL_MAX_LENGTH,
|
||||
toLowerCase: true,
|
||||
regExp: {
|
||||
pattern: /^[a-zA-Z0-9-_]+$/,
|
||||
message: "forms:errors.profileCustomUrlStrangeChar",
|
||||
},
|
||||
validate: {
|
||||
func: isCustomUrl,
|
||||
message: "forms:errors.profileCustomUrlNumbers",
|
||||
},
|
||||
}),
|
||||
inGameName: textFieldOptional({
|
||||
label: "labels.profileInGameName",
|
||||
bottomText: "bottomTexts.profileInGameName",
|
||||
maxLength: 10 + 1 + 5, // 10 for name, 1 for #, 5 for discriminator
|
||||
regExp: {
|
||||
pattern: IN_GAME_NAME_REGEXP,
|
||||
message: "forms:errors.profileInGameName",
|
||||
},
|
||||
}),
|
||||
sensitivity: dualSelectOptional({
|
||||
fields: [
|
||||
{ label: "labels.profileMotionSens", items: SENS_ITEMS },
|
||||
{ label: "labels.profileStickSens", items: SENS_ITEMS },
|
||||
],
|
||||
validate: {
|
||||
func: ([motion, stick]) => {
|
||||
if (motion !== null && stick === null) return false;
|
||||
return true;
|
||||
},
|
||||
message: "errors.profileSensBothOrNeither",
|
||||
},
|
||||
}),
|
||||
pronouns: dualSelectOptional({
|
||||
bottomText: "bottomTexts.profilePronouns",
|
||||
fields: [
|
||||
{
|
||||
label: "labels.pronoun",
|
||||
items: SUBJECT_PRONOUNS.map((p) => ({ label: () => p, value: p })),
|
||||
},
|
||||
{
|
||||
label: "labels.pronoun",
|
||||
items: OBJECT_PRONOUNS.map((p) => ({ label: () => p, value: p })),
|
||||
},
|
||||
],
|
||||
validate: {
|
||||
func: ([subject, object]) => {
|
||||
if (subject === null && object === null) return true;
|
||||
if (subject !== null && object !== null) return true;
|
||||
return false;
|
||||
},
|
||||
message: "errors.profilePronounsBothOrNeither",
|
||||
},
|
||||
}),
|
||||
battlefy: textFieldOptional({
|
||||
label: "labels.profileBattlefy",
|
||||
bottomText: "bottomTexts.profileBattlefy",
|
||||
leftAddon: "https://battlefy.com/users/",
|
||||
maxLength: USER.BATTLEFY_MAX_LENGTH,
|
||||
}),
|
||||
country: selectDynamicOptional({
|
||||
label: "labels.profileCountry",
|
||||
searchable: true,
|
||||
}),
|
||||
favoriteBadgeIds: badges({
|
||||
label: "labels.profileFavoriteBadges",
|
||||
maxCount: BADGE.SMALL_BADGES_PER_DISPLAY_PAGE + 1,
|
||||
}),
|
||||
weapons: weaponPool({
|
||||
label: "labels.weaponPool",
|
||||
maxCount: USER.WEAPON_POOL_MAX_SIZE,
|
||||
}),
|
||||
bio: textAreaOptional({
|
||||
label: "labels.bio",
|
||||
maxLength: USER.BIO_MAX_LENGTH,
|
||||
}),
|
||||
showDiscordUniqueName: toggle({
|
||||
label: "labels.profileShowDiscordUniqueName",
|
||||
bottomText: "bottomTexts.profileShowDiscordUniqueName",
|
||||
}),
|
||||
commissionsOpen: toggle({
|
||||
label: "labels.profileCommissionsOpen",
|
||||
bottomText: "bottomTexts.profileCommissionsOpen",
|
||||
}),
|
||||
commissionText: textAreaOptional({
|
||||
label: "labels.profileCommissionText",
|
||||
bottomText: "bottomTexts.profileCommissionText",
|
||||
maxLength: USER.COMMISSION_TEXT_MAX_LENGTH,
|
||||
}),
|
||||
newProfileEnabled: toggle({
|
||||
label: "labels.profileNewProfileEnabled",
|
||||
bottomText: "bottomTexts.profileNewProfileEnabled",
|
||||
}),
|
||||
});
|
||||
|
||||
export const editHighlightsActionSchema = z.object({
|
||||
[HIGHLIGHT_CHECKBOX_NAME]: z.optional(
|
||||
z.union([z.array(z.string()), z.string()]),
|
||||
|
|
|
|||
|
|
@ -43,6 +43,8 @@ export type { CustomFieldRenderProps };
|
|||
interface FormFieldProps {
|
||||
name: string;
|
||||
label?: string;
|
||||
disabled?: boolean;
|
||||
maxCount?: number;
|
||||
field?: z.ZodType;
|
||||
children?:
|
||||
| ((props: CustomFieldRenderProps) => React.ReactNode)
|
||||
|
|
@ -54,6 +56,8 @@ interface FormFieldProps {
|
|||
export function FormField({
|
||||
name,
|
||||
label,
|
||||
disabled,
|
||||
maxCount,
|
||||
field,
|
||||
children,
|
||||
options,
|
||||
|
|
@ -134,6 +138,7 @@ export function FormField({
|
|||
<InputFormField
|
||||
{...commonProps}
|
||||
{...formField}
|
||||
disabled={disabled}
|
||||
value={value as string}
|
||||
onChange={handleChange as (v: string) => void}
|
||||
/>
|
||||
|
|
@ -145,6 +150,7 @@ export function FormField({
|
|||
<SwitchFormField
|
||||
{...commonProps}
|
||||
{...formField}
|
||||
isDisabled={disabled}
|
||||
checked={value as boolean}
|
||||
onChange={handleChange as (v: boolean) => void}
|
||||
/>
|
||||
|
|
@ -156,6 +162,7 @@ export function FormField({
|
|||
<TextareaFormField
|
||||
{...commonProps}
|
||||
{...formField}
|
||||
disabled={disabled}
|
||||
value={value as string}
|
||||
onChange={handleChange as (v: string) => void}
|
||||
/>
|
||||
|
|
@ -285,7 +292,6 @@ export function FormField({
|
|||
}
|
||||
|
||||
if (formField.type === "array") {
|
||||
// @ts-expect-error Type instantiation is excessively deep and possibly infinite
|
||||
const innerFieldMeta = formRegistry.get(formField.field) as
|
||||
| FormFieldType
|
||||
| undefined;
|
||||
|
|
@ -372,6 +378,7 @@ export function FormField({
|
|||
value={value as number[]}
|
||||
onChange={handleChange as (v: number[]) => void}
|
||||
options={options as BadgeOption[]}
|
||||
{...(maxCount !== undefined ? { maxCount } : {})}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -415,6 +415,7 @@ function buildInitialValues<T extends z.ZodRawShape>(
|
|||
const result: Record<string, unknown> = {};
|
||||
|
||||
for (const [key, fieldSchema] of Object.entries(schema.shape)) {
|
||||
// @ts-expect-error Type instantiation is excessively deep with complex schemas
|
||||
const formField = formRegistry.get(fieldSchema as z.ZodType) as
|
||||
| FormField
|
||||
| undefined;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { ariaAttributes } from "../utils";
|
|||
import { FormFieldWrapper } from "./FormFieldWrapper";
|
||||
|
||||
type InputFormFieldProps = FormFieldProps<"text-field"> & {
|
||||
disabled?: boolean;
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
};
|
||||
|
|
@ -18,6 +19,7 @@ export function InputFormField({
|
|||
onBlur,
|
||||
required,
|
||||
inputType = "text",
|
||||
disabled,
|
||||
value,
|
||||
onChange,
|
||||
}: InputFormFieldProps) {
|
||||
|
|
@ -32,7 +34,7 @@ export function InputFormField({
|
|||
error={error}
|
||||
bottomText={bottomText}
|
||||
>
|
||||
<div className={leftAddon ? "input-with-addon" : undefined}>
|
||||
<div className={leftAddon ? "input-container" : undefined}>
|
||||
{leftAddon ? <span className="input-addon">{leftAddon}</span> : null}
|
||||
<input
|
||||
id={id}
|
||||
|
|
@ -41,6 +43,7 @@ export function InputFormField({
|
|||
onChange={(e) => onChange(e.target.value)}
|
||||
onBlur={() => onBlur?.()}
|
||||
maxLength={maxLength}
|
||||
disabled={disabled}
|
||||
{...ariaAttributes({
|
||||
id,
|
||||
bottomText,
|
||||
|
|
|
|||
3
app/form/fields/SelectFormField.module.css
Normal file
3
app/form/fields/SelectFormField.module.css
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
.searchable {
|
||||
--select-width: 100%;
|
||||
}
|
||||
|
|
@ -1,12 +1,18 @@
|
|||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { SendouSelect, SendouSelectItem } from "~/components/elements/Select";
|
||||
import type { FormFieldItems, FormFieldProps } from "../types";
|
||||
import { ariaAttributes } from "../utils";
|
||||
import { FormFieldWrapper } from "./FormFieldWrapper";
|
||||
import {
|
||||
FormFieldMessages,
|
||||
FormFieldWrapper,
|
||||
useTranslatedTexts,
|
||||
} from "./FormFieldWrapper";
|
||||
import styles from "./SelectFormField.module.css";
|
||||
|
||||
type SelectFormFieldProps<V extends string> = Omit<
|
||||
FormFieldProps<"select">,
|
||||
"items" | "clearable" | "onBlur" | "name"
|
||||
"items" | "clearable" | "onBlur" | "name" | "searchable"
|
||||
> & {
|
||||
name?: string;
|
||||
items: FormFieldItems<V>;
|
||||
|
|
@ -15,6 +21,7 @@ type SelectFormFieldProps<V extends string> = Omit<
|
|||
onSelect?: (value: V) => void;
|
||||
onBlur?: () => void;
|
||||
clearable?: boolean;
|
||||
searchable?: boolean;
|
||||
};
|
||||
|
||||
export function SelectFormField<V extends string>({
|
||||
|
|
@ -28,8 +35,9 @@ export function SelectFormField<V extends string>({
|
|||
onChange,
|
||||
onSelect,
|
||||
clearable,
|
||||
searchable,
|
||||
}: SelectFormFieldProps<V>) {
|
||||
const { t, i18n } = useTranslation();
|
||||
const { t, i18n } = useTranslation(["common"]);
|
||||
const id = React.useId();
|
||||
|
||||
const itemsWithResolvedLabels = items.map((item) => {
|
||||
|
|
@ -47,6 +55,23 @@ export function SelectFormField<V extends string>({
|
|||
};
|
||||
});
|
||||
|
||||
if (searchable) {
|
||||
return (
|
||||
<SearchableSelect
|
||||
name={name}
|
||||
label={label}
|
||||
bottomText={bottomText}
|
||||
error={error}
|
||||
items={itemsWithResolvedLabels}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
onBlur={onBlur}
|
||||
clearable={clearable}
|
||||
searchPlaceholder={t("common:actions.search")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const newValue = e.target.value === "" ? null : (e.target.value as V);
|
||||
onChange(newValue);
|
||||
|
|
@ -81,3 +106,58 @@ export function SelectFormField<V extends string>({
|
|||
</FormFieldWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
function SearchableSelect<V extends string>({
|
||||
name,
|
||||
label,
|
||||
bottomText,
|
||||
error,
|
||||
items,
|
||||
value,
|
||||
onChange,
|
||||
onBlur,
|
||||
clearable,
|
||||
searchPlaceholder,
|
||||
}: {
|
||||
name?: string;
|
||||
label?: string;
|
||||
bottomText?: string;
|
||||
error?: string;
|
||||
items: Array<{ value: V; resolvedLabel: string }>;
|
||||
value: V | null;
|
||||
onChange: (value: V | null) => void;
|
||||
onBlur?: () => void;
|
||||
clearable?: boolean;
|
||||
searchPlaceholder: string;
|
||||
}) {
|
||||
const { translatedLabel } = useTranslatedTexts({ label });
|
||||
|
||||
const selectItems = items.map((item) => ({
|
||||
id: item.value,
|
||||
textValue: item.resolvedLabel,
|
||||
}));
|
||||
|
||||
return (
|
||||
<div className={styles.searchable}>
|
||||
<SendouSelect
|
||||
label={translatedLabel}
|
||||
selectedKey={value}
|
||||
onSelectionChange={(key) => {
|
||||
const newValue = key === "" ? null : (key as V);
|
||||
onChange(newValue);
|
||||
onBlur?.();
|
||||
}}
|
||||
items={selectItems}
|
||||
search={{ placeholder: searchPlaceholder }}
|
||||
clearable={clearable}
|
||||
>
|
||||
{(item) => (
|
||||
<SendouSelectItem id={item.id} textValue={item.textValue}>
|
||||
{item.textValue}
|
||||
</SendouSelectItem>
|
||||
)}
|
||||
</SendouSelect>
|
||||
<FormFieldMessages name={name} error={error} bottomText={bottomText} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { ariaAttributes } from "../utils";
|
|||
import { FormFieldWrapper } from "./FormFieldWrapper";
|
||||
|
||||
type TextareaFormFieldProps = FormFieldProps<"text-area"> & {
|
||||
disabled?: boolean;
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
};
|
||||
|
|
@ -15,6 +16,7 @@ export function TextareaFormField({
|
|||
maxLength,
|
||||
error,
|
||||
onBlur,
|
||||
disabled,
|
||||
value,
|
||||
onChange,
|
||||
}: TextareaFormFieldProps) {
|
||||
|
|
@ -36,6 +38,7 @@ export function TextareaFormField({
|
|||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
onBlur={() => onBlur?.()}
|
||||
disabled={disabled}
|
||||
{...ariaAttributes({
|
||||
id,
|
||||
bottomText,
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ export interface FormFieldSelect<T extends string, V extends string>
|
|||
extends FormFieldBase<T> {
|
||||
items: FormFieldItems<V>;
|
||||
clearable: boolean;
|
||||
searchable?: boolean;
|
||||
}
|
||||
|
||||
type FormFieldDualSelectField<T extends string, V extends string> = Omit<
|
||||
|
|
@ -142,6 +143,7 @@ interface FormFieldBadges<T extends string> extends FormFieldBase<T> {
|
|||
|
||||
interface FormFieldSelectDynamic<T extends string> extends FormFieldBase<T> {
|
||||
clearable: boolean;
|
||||
searchable?: boolean;
|
||||
}
|
||||
|
||||
interface FormFieldStageSelect<T extends string> extends FormFieldBase<T> {
|
||||
|
|
@ -241,6 +243,8 @@ export type TypedFormFieldProps<
|
|||
> = {
|
||||
name: TName;
|
||||
label?: string;
|
||||
disabled?: boolean;
|
||||
maxCount?: number;
|
||||
children?:
|
||||
| ((props: FormFieldChildrenProps) => React.ReactNode)
|
||||
| ((props: ArrayItemRenderContext) => React.ReactNode);
|
||||
|
|
@ -255,6 +259,8 @@ type NestedPath = `${string}.${string}` | `${string}[${string}`;
|
|||
export type FlexibleFormFieldProps = {
|
||||
name: NestedPath;
|
||||
label?: string;
|
||||
disabled?: boolean;
|
||||
maxCount?: number;
|
||||
children?:
|
||||
| ((props: FormFieldChildrenProps) => React.ReactNode)
|
||||
| ((props: ArrayItemRenderContext) => React.ReactNode);
|
||||
|
|
|
|||
|
|
@ -6,34 +6,10 @@
|
|||
gap: var(--s-6);
|
||||
}
|
||||
|
||||
.inGameNameText {
|
||||
max-width: 8rem;
|
||||
}
|
||||
|
||||
.inGameNameHashtag {
|
||||
font-size: var(--fonts-lg);
|
||||
}
|
||||
|
||||
.inGameNameDiscriminator {
|
||||
width: 5rem;
|
||||
}
|
||||
|
||||
.sensSelect {
|
||||
width: 6rem;
|
||||
}
|
||||
|
||||
.seperator {
|
||||
margin-left: var(--s-4);
|
||||
}
|
||||
|
||||
.weaponPool {
|
||||
width: 20rem;
|
||||
}
|
||||
|
||||
.bioContainer {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.bioContainer > textarea {
|
||||
width: 100%;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,3 @@
|
|||
export function errorIsSqliteUniqueConstraintFailure(error: any) {
|
||||
return error?.code === "SQLITE_CONSTRAINT_UNIQUE";
|
||||
}
|
||||
|
||||
export function errorIsSqliteForeignKeyConstraintFailure(
|
||||
error: unknown,
|
||||
): error is Error {
|
||||
|
|
|
|||
|
|
@ -245,7 +245,7 @@ export function nullLiteraltoNull(value: unknown): unknown {
|
|||
return value;
|
||||
}
|
||||
|
||||
export function undefinedToNull(value: unknown): unknown {
|
||||
function undefinedToNull(value: unknown): unknown {
|
||||
if (value === undefined) return null;
|
||||
|
||||
return value;
|
||||
|
|
|
|||
|
|
@ -45,7 +45,6 @@ test.describe("Scrims", () => {
|
|||
});
|
||||
await page.getByLabel("Visibility").selectOption("2");
|
||||
|
||||
// Schema-defined field - use form helper
|
||||
await form.fill("postText", "Test scrim");
|
||||
|
||||
await submit(page);
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
import type { Page } from "@playwright/test";
|
||||
import { NZAP_TEST_DISCORD_ID, NZAP_TEST_ID } from "~/db/seed/constants";
|
||||
import { ADMIN_DISCORD_ID } from "~/features/admin/admin-constants";
|
||||
import { userEditProfileBaseSchema } from "~/features/user-page/user-page-schemas";
|
||||
import {
|
||||
expect,
|
||||
impersonate,
|
||||
isNotVisible,
|
||||
navigate,
|
||||
seed,
|
||||
selectWeapon,
|
||||
submit,
|
||||
test,
|
||||
} from "~/utils/playwright";
|
||||
import { createFormHelpers } from "~/utils/playwright-form";
|
||||
import { userEditProfilePage, userPage } from "~/utils/urls";
|
||||
|
||||
const goToEditPage = (page: Page) =>
|
||||
|
|
@ -95,23 +96,20 @@ test.describe("User page", () => {
|
|||
await page.getByTestId("flag-FI").isVisible();
|
||||
await goToEditPage(page);
|
||||
|
||||
await page
|
||||
.getByRole("textbox", { name: "In game name", exact: true })
|
||||
.fill("Lean");
|
||||
await page
|
||||
.getByRole("textbox", { name: "In game name discriminator" })
|
||||
.fill("1234");
|
||||
const form = createFormHelpers(page, userEditProfileBaseSchema);
|
||||
|
||||
await form.fill("inGameName", "Lean#1234");
|
||||
await page.getByLabel("R-stick sens").selectOption("0");
|
||||
await page.getByLabel("Motion sens").selectOption("-50");
|
||||
|
||||
await page.getByLabel("Country").click();
|
||||
await page.getByPlaceholder("Search countries").fill("Sweden");
|
||||
await page.getByRole("searchbox", { name: "Search" }).fill("Sweden");
|
||||
await page.getByRole("option", { name: "Sweden" }).click();
|
||||
|
||||
await page.getByLabel("Bio").fill("My awesome bio");
|
||||
await submitEditForm(page);
|
||||
await form.fill("bio", "My awesome bio");
|
||||
await form.submit();
|
||||
|
||||
await page.getByTestId("flag-SV").isVisible();
|
||||
await page.getByTestId("flag-SE").isVisible();
|
||||
await page.getByText("My awesome bio").isVisible();
|
||||
await page.getByText("Lean#1234").isVisible();
|
||||
await page.getByText("Stick 0 / Motion -5").isVisible();
|
||||
|
|
@ -180,11 +178,16 @@ test.describe("User page", () => {
|
|||
}
|
||||
|
||||
await goToEditPage(page);
|
||||
await selectWeapon({ name: "Range Blaster", page });
|
||||
await page.getByText("Max weapon count reached").isVisible();
|
||||
await page.getByTestId("delete-weapon-1100").click();
|
||||
|
||||
await submitEditForm(page);
|
||||
const form = createFormHelpers(page, userEditProfileBaseSchema);
|
||||
|
||||
await form.selectWeapons("weapons", ["Range Blaster"]);
|
||||
await page
|
||||
.getByRole("button", { name: /Inkbrush/ })
|
||||
.getByRole("button", { name: "Delete" })
|
||||
.click();
|
||||
|
||||
await form.submit();
|
||||
|
||||
for (const [i, id] of [200, 2000, 4000, 220].entries()) {
|
||||
await expect(page.getByTestId(`${id}-${i + 1}`)).toBeVisible();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"submit": "Fremlæg",
|
||||
"labels.name": "Navn",
|
||||
"labels.bio": "Bio",
|
||||
"labels.bio": "Biografi",
|
||||
"labels.tag": "",
|
||||
"labels.teamBsky": "",
|
||||
"labels.clockFormat": "",
|
||||
|
|
@ -24,7 +24,7 @@
|
|||
"errors.atLeastOneOption": "",
|
||||
"errors.duplicateName": "Holdnavnet er taget af et andet hold",
|
||||
"errors.noOnlySpecialCharacters": "",
|
||||
"labels.weaponPool": "",
|
||||
"labels.weaponPool": "Våbenpulje",
|
||||
"placeholders.weaponPoolFull": "",
|
||||
"labels.voiceChat": "",
|
||||
"labels.languages": "",
|
||||
|
|
@ -187,5 +187,33 @@
|
|||
"labels.comment": "",
|
||||
"errors.plusAlreadySuggested": "",
|
||||
"errors.plusAlreadyMember": "",
|
||||
"errors.plusCannotSuggest": ""
|
||||
"errors.plusCannotSuggest": "",
|
||||
"labels.profileCustomName": "Brugerdefineret Navn",
|
||||
"labels.profileCustomUrl": "Brugerdefineret URL",
|
||||
"labels.profileInGameName": "Splatoon 3 Brugernavn",
|
||||
"labels.profileBattlefy": "Battlefy brugernavn",
|
||||
"labels.profileMotionSens": "Bevægelsesfølsomhed",
|
||||
"labels.profileStickSens": "Styrepindsfølsomhed",
|
||||
"labels.profileCountry": "Land",
|
||||
"labels.profileFavoriteBadges": "",
|
||||
"labels.profileShowDiscordUniqueName": "Vis Discord-brugernavn",
|
||||
"labels.profileCommissionsOpen": "Åben for bestillinger",
|
||||
"labels.profileCommissionText": "info om bestilling",
|
||||
"labels.profileNewProfileEnabled": "",
|
||||
"bottomTexts.profileCustomName": "Hvis feltet ikke udfyldes bruges dit discordbrugernavn: \"{{discordName}}\"",
|
||||
"bottomTexts.profileCustomUrl": "",
|
||||
"bottomTexts.profileInGameName": "",
|
||||
"bottomTexts.profileBattlefy": "Battlefy-brugernavn bruges til seeding og bekræftelse i nogle turneringer",
|
||||
"bottomTexts.profileShowDiscordUniqueName": "Vil du gøre dit unikke Discord-brugernavn ({{discordUniqueName}}) synligt for offentligheden?",
|
||||
"bottomTexts.profileCommissionsOpen": "",
|
||||
"bottomTexts.profileCommissionText": "Pris, åbne pladser eller andre relevante informationer der er relateret til at afgive en bestilling til dig.",
|
||||
"bottomTexts.profileNewProfileEnabled": "",
|
||||
"errors.profileCustomUrlStrangeChar": "Brugerdefineret URL må ikke indeholde specialtegn (Gælder også æ, ø og å)",
|
||||
"errors.profileCustomUrlNumbers": "Brugerdefineret URL må ikke kun indeholde numre",
|
||||
"errors.profileCustomUrlDuplicate": "Brugerdefineret URL er allerede i brug",
|
||||
"errors.profileSensBothOrNeither": "Bevægelsesfølsomhed kan ikke indstilles før at Styrepindsfølsomheden er indstillet",
|
||||
"errors.profileInGameName": "",
|
||||
"labels.pronoun": "",
|
||||
"bottomTexts.profilePronouns": "",
|
||||
"errors.profilePronounsBothOrNeither": ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
{
|
||||
"customUrl": "Brugerdefineret URL",
|
||||
"customName": "Brugerdefineret Navn",
|
||||
"ign": "Splatoon 3 Brugernavn",
|
||||
"ign.short": "Splatnavn",
|
||||
"country": "Land",
|
||||
"bio": "Biografi",
|
||||
"widget.bio": "",
|
||||
"widget.bio-md": "",
|
||||
"widget.badges-owned": "",
|
||||
|
|
@ -143,24 +139,8 @@
|
|||
"motion": "Bevægelse",
|
||||
"stick": "Styrepind",
|
||||
"sens": "Følsomhed",
|
||||
"pronoun": "",
|
||||
"usesPronouns": "",
|
||||
"pronounsInfo": "",
|
||||
"weaponPool": "Våbenpulje",
|
||||
"discordExplanation": "Brugernavn, Profilbillede, Youtube-, Bluesky- og Twitch-konter er hentet via din Discord-konto. Se <1>FAQ</1> for yderligere information.",
|
||||
"favoriteBadges": "",
|
||||
"battlefy": "Battlefy brugernavn",
|
||||
"forms.newProfileEnabled": "",
|
||||
"forms.newProfileEnabled.info": "",
|
||||
"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}}\"",
|
||||
"forms.country.search.placeholder": "",
|
||||
"forms.favoriteBadges.nonSupporter": "",
|
||||
"results.title": "Alle resultater",
|
||||
"results.placing": "Placering",
|
||||
"results.team": "Hold",
|
||||
|
|
@ -175,12 +155,6 @@
|
|||
"results.button.showHighlights": "Vis højdepunkter",
|
||||
"results.button.showAll": "Vis alt",
|
||||
"forms.errors.maxWeapons": "Maks antal våben nået",
|
||||
"forms.errors.invalidCustomUrl.numbers": "Brugerdefineret URL må ikke kun indeholde numre",
|
||||
"forms.errors.invalidCustomUrl.strangeCharacter": "Brugerdefineret URL må ikke indeholde specialtegn (Gælder også æ, ø og å)",
|
||||
"forms.errors.invalidCustomUrl.duplicate": "Brugerdefineret URL er allerede i brug",
|
||||
"forms.errors.invalidSens": "Bevægelsesfølsomhed kan ikke indstilles før at Styrepindsfølsomheden er indstillet",
|
||||
"forms.info.customUrl": "",
|
||||
"forms.info.battlefy": "Battlefy-brugernavn bruges til seeding og bekræftelse i nogle turneringer",
|
||||
"search.info": "",
|
||||
"search.noResults": "Søgningen ’{{query}}’ fandt ingen brugere",
|
||||
"search.pleaseLogIn.header": "",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"submit": "Senden",
|
||||
"labels.name": "Name",
|
||||
"labels.bio": "Bio",
|
||||
"labels.bio": "Über mich",
|
||||
"labels.tag": "",
|
||||
"labels.teamBsky": "",
|
||||
"labels.clockFormat": "",
|
||||
|
|
@ -24,7 +24,7 @@
|
|||
"errors.atLeastOneOption": "",
|
||||
"errors.duplicateName": "Es gibt bereits ein Team mit diesem Namen",
|
||||
"errors.noOnlySpecialCharacters": "",
|
||||
"labels.weaponPool": "",
|
||||
"labels.weaponPool": "Waffenpool",
|
||||
"placeholders.weaponPoolFull": "",
|
||||
"labels.voiceChat": "",
|
||||
"labels.languages": "",
|
||||
|
|
@ -187,5 +187,33 @@
|
|||
"labels.comment": "",
|
||||
"errors.plusAlreadySuggested": "",
|
||||
"errors.plusAlreadyMember": "",
|
||||
"errors.plusCannotSuggest": ""
|
||||
"errors.plusCannotSuggest": "",
|
||||
"labels.profileCustomName": "",
|
||||
"labels.profileCustomUrl": "Benutzerdefinierte URL",
|
||||
"labels.profileInGameName": "Name im Spiel",
|
||||
"labels.profileBattlefy": "",
|
||||
"labels.profileMotionSens": "Empfindlichkeit Bewegungssteuerung",
|
||||
"labels.profileStickSens": "Empfindlichkeit R-Stick",
|
||||
"labels.profileCountry": "Land",
|
||||
"labels.profileFavoriteBadges": "",
|
||||
"labels.profileShowDiscordUniqueName": "",
|
||||
"labels.profileCommissionsOpen": "",
|
||||
"labels.profileCommissionText": "",
|
||||
"labels.profileNewProfileEnabled": "",
|
||||
"bottomTexts.profileCustomName": "",
|
||||
"bottomTexts.profileCustomUrl": "",
|
||||
"bottomTexts.profileInGameName": "",
|
||||
"bottomTexts.profileBattlefy": "",
|
||||
"bottomTexts.profileShowDiscordUniqueName": "",
|
||||
"bottomTexts.profileCommissionsOpen": "",
|
||||
"bottomTexts.profileCommissionText": "",
|
||||
"bottomTexts.profileNewProfileEnabled": "",
|
||||
"errors.profileCustomUrlStrangeChar": "Benutzerdefinierte URL kann nicht aus speziellen Zeichen bestehen",
|
||||
"errors.profileCustomUrlNumbers": "Benutzerdefinierte URL kann nicht nur aus Zahlen bestehen",
|
||||
"errors.profileCustomUrlDuplicate": "Diese Benutzerdefinierte URL wird bereits verwendet",
|
||||
"errors.profileSensBothOrNeither": "Empfindlichkeit der Bewegungssteuerung kann nur festgelegt werden, wenn Empfindlichkeit R-Stick festgelegt ist",
|
||||
"errors.profileInGameName": "",
|
||||
"labels.pronoun": "",
|
||||
"bottomTexts.profilePronouns": "",
|
||||
"errors.profilePronounsBothOrNeither": ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
{
|
||||
"customUrl": "Benutzerdefinierte URL",
|
||||
"customName": "",
|
||||
"ign": "Name im Spiel",
|
||||
"ign.short": "IGN",
|
||||
"country": "Land",
|
||||
"bio": "Über mich",
|
||||
"widget.bio": "",
|
||||
"widget.bio-md": "",
|
||||
"widget.badges-owned": "",
|
||||
|
|
@ -143,24 +139,8 @@
|
|||
"motion": "Bewegungssteuerung",
|
||||
"stick": "Stick",
|
||||
"sens": "Empfindlichkeit",
|
||||
"pronoun": "",
|
||||
"usesPronouns": "",
|
||||
"pronounsInfo": "",
|
||||
"weaponPool": "Waffenpool",
|
||||
"discordExplanation": "Der Username, Profilbild, YouTube-, Bluesky- und Twitch-Konten stammen von deinem Discord-Konto. Mehr Infos in den <1>FAQ</1>.",
|
||||
"favoriteBadges": "",
|
||||
"battlefy": "",
|
||||
"forms.newProfileEnabled": "",
|
||||
"forms.newProfileEnabled.info": "",
|
||||
"forms.showDiscordUniqueName": "",
|
||||
"forms.showDiscordUniqueName.info": "",
|
||||
"forms.commissionsOpen": "",
|
||||
"forms.commissionsOpen.info": "",
|
||||
"forms.commissionText": "",
|
||||
"forms.commissionText.info": "",
|
||||
"forms.customName.info": "",
|
||||
"forms.country.search.placeholder": "",
|
||||
"forms.favoriteBadges.nonSupporter": "",
|
||||
"results.title": "",
|
||||
"results.placing": "Platzierung",
|
||||
"results.team": "Team",
|
||||
|
|
@ -175,12 +155,6 @@
|
|||
"results.button.showHighlights": "",
|
||||
"results.button.showAll": "",
|
||||
"forms.errors.maxWeapons": "Maximale Zahl an Waffen erreicht",
|
||||
"forms.errors.invalidCustomUrl.numbers": "Benutzerdefinierte URL kann nicht nur aus Zahlen bestehen",
|
||||
"forms.errors.invalidCustomUrl.strangeCharacter": "Benutzerdefinierte URL kann nicht aus speziellen Zeichen bestehen",
|
||||
"forms.errors.invalidCustomUrl.duplicate": "Diese Benutzerdefinierte URL wird bereits verwendet",
|
||||
"forms.errors.invalidSens": "Empfindlichkeit der Bewegungssteuerung kann nur festgelegt werden, wenn Empfindlichkeit R-Stick festgelegt ist",
|
||||
"forms.info.customUrl": "",
|
||||
"forms.info.battlefy": "",
|
||||
"search.info": "",
|
||||
"search.noResults": "Keine Nutzer gefunden, die '{{query}}' entsprechen",
|
||||
"search.pleaseLogIn.header": "",
|
||||
|
|
|
|||
|
|
@ -187,5 +187,33 @@
|
|||
"labels.comment": "Comment",
|
||||
"errors.plusAlreadySuggested": "This user has already been suggested",
|
||||
"errors.plusAlreadyMember": "This user is already a member of this tier",
|
||||
"errors.plusCannotSuggest": "Can't make a suggestion right now"
|
||||
"errors.plusCannotSuggest": "Can't make a suggestion right now",
|
||||
"labels.profileCustomName": "Custom name",
|
||||
"labels.profileCustomUrl": "Custom URL",
|
||||
"labels.profileInGameName": "In-game name",
|
||||
"labels.profileBattlefy": "Battlefy account name",
|
||||
"labels.profileMotionSens": "Motion sens",
|
||||
"labels.profileStickSens": "R-stick sens",
|
||||
"labels.profileCountry": "Country",
|
||||
"labels.profileFavoriteBadges": "Favorite badges",
|
||||
"labels.profileShowDiscordUniqueName": "Show Discord username",
|
||||
"labels.profileCommissionsOpen": "Commissions open",
|
||||
"labels.profileCommissionText": "Commission info",
|
||||
"labels.profileNewProfileEnabled": "New profile page",
|
||||
"bottomTexts.profileCustomName": "If empty, your Discord display name is shown",
|
||||
"bottomTexts.profileCustomUrl": "For patrons (Supporter & above) short link is available. E.g. instead of sendou.ink/u/sendou, snd.ink/sendou can be used.",
|
||||
"bottomTexts.profileInGameName": "Format: Name#disc (e.g. Player#1234)",
|
||||
"bottomTexts.profileBattlefy": "Used for seeding and verification in some tournaments",
|
||||
"bottomTexts.profileShowDiscordUniqueName": "Show your Discord username publicly on your profile",
|
||||
"bottomTexts.profileCommissionsOpen": "Commissions automatically close after one month",
|
||||
"bottomTexts.profileCommissionText": "Price, slots open or other commission info",
|
||||
"bottomTexts.profileNewProfileEnabled": "Enable the new widget-based profile page (supporter only)",
|
||||
"errors.profileCustomUrlStrangeChar": "Custom URL can only contain letters, numbers, hyphens and underscores",
|
||||
"errors.profileCustomUrlNumbers": "Custom URL can't only contain numbers",
|
||||
"errors.profileCustomUrlDuplicate": "Someone is already using this custom URL",
|
||||
"errors.profileSensBothOrNeither": "Motion sens can't be set if R-stick sens isn't",
|
||||
"errors.profileInGameName": "Must match format: Name#disc (1-10 characters, #, 4-5 alphanumeric)",
|
||||
"labels.pronoun": "Pronoun",
|
||||
"bottomTexts.profilePronouns": "This setting is optional! Your pronouns will be displayed on your profile, tournament rosters, SendouQ groups, and text channels.",
|
||||
"errors.profilePronounsBothOrNeither": "Select both pronouns or neither"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
{
|
||||
"customUrl": "Custom URL",
|
||||
"customName": "Custom name",
|
||||
"ign": "In-game name",
|
||||
"ign.short": "IGN",
|
||||
"country": "Country",
|
||||
"bio": "Bio",
|
||||
"widget.bio": "Bio",
|
||||
"widget.bio-md": "Bio",
|
||||
"widget.badges-owned": "Badges",
|
||||
|
|
@ -143,24 +139,8 @@
|
|||
"motion": "Motion",
|
||||
"stick": "Stick",
|
||||
"sens": "Sens",
|
||||
"pronoun": "Pronoun",
|
||||
"usesPronouns": "Uses",
|
||||
"pronounsInfo": "This setting is optional! Your pronouns will be displayed on your profile, tournament rosters, SendouQ groups, and text channels.",
|
||||
"weaponPool": "Weapon pool",
|
||||
"discordExplanation": "Username, profile picture, YouTube, Bluesky and Twitch accounts come from your Discord account. See <1>FAQ</1> for more information.",
|
||||
"favoriteBadges": "Favorite badges",
|
||||
"battlefy": "Battlefy account name",
|
||||
"forms.newProfileEnabled": "New profile page",
|
||||
"forms.newProfileEnabled.info": "Enable the new widget-based profile page. Currently available as an early preview for supporters.",
|
||||
"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}}\"",
|
||||
"forms.country.search.placeholder": "Search countries",
|
||||
"forms.favoriteBadges.nonSupporter": "Become supporter to set which badges appear on the first page and the order",
|
||||
"results.title": "All results",
|
||||
"results.placing": "Placing",
|
||||
"results.team": "Team",
|
||||
|
|
@ -175,12 +155,6 @@
|
|||
"results.button.showHighlights": "Show highlights",
|
||||
"results.button.showAll": "Show all",
|
||||
"forms.errors.maxWeapons": "Max weapon count reached",
|
||||
"forms.errors.invalidCustomUrl.numbers": "Custom URL can't only contain numbers",
|
||||
"forms.errors.invalidCustomUrl.strangeCharacter": "Custom URL can't contain special characters",
|
||||
"forms.errors.invalidCustomUrl.duplicate": "Someone is already using this custom URL",
|
||||
"forms.errors.invalidSens": "Motion sens can't be set if R-stick sens isn't",
|
||||
"forms.info.customUrl": "For patrons (Supporter & above) short link is available. E.g. instead of sendou.ink/u/sendou, snd.ink/sendou can be used.",
|
||||
"forms.info.battlefy": "Battlefy account name is used for seeding and verification in some tournaments",
|
||||
"search.info": "Search for users by Discord or Splatoon 3 name",
|
||||
"search.noResults": "No users found matching '{{query}}'",
|
||||
"search.pleaseLogIn.header": "Please log in to search for users",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"submit": "Finalizar",
|
||||
"labels.name": "Nombre",
|
||||
"labels.bio": "Bio",
|
||||
"labels.bio": "Biografía",
|
||||
"labels.tag": "",
|
||||
"labels.teamBsky": "",
|
||||
"labels.clockFormat": "",
|
||||
|
|
@ -24,7 +24,7 @@
|
|||
"errors.atLeastOneOption": "",
|
||||
"errors.duplicateName": "Ya existe un equipo con ese nombre",
|
||||
"errors.noOnlySpecialCharacters": "",
|
||||
"labels.weaponPool": "",
|
||||
"labels.weaponPool": "Grupo de armas",
|
||||
"placeholders.weaponPoolFull": "",
|
||||
"labels.voiceChat": "",
|
||||
"labels.languages": "",
|
||||
|
|
@ -187,5 +187,33 @@
|
|||
"labels.comment": "",
|
||||
"errors.plusAlreadySuggested": "",
|
||||
"errors.plusAlreadyMember": "",
|
||||
"errors.plusCannotSuggest": ""
|
||||
"errors.plusCannotSuggest": "",
|
||||
"labels.profileCustomName": "",
|
||||
"labels.profileCustomUrl": "Enlace personalizado",
|
||||
"labels.profileInGameName": "Nombre en el juego",
|
||||
"labels.profileBattlefy": "",
|
||||
"labels.profileMotionSens": "Sens del giroscopio",
|
||||
"labels.profileStickSens": "Sens de palanca",
|
||||
"labels.profileCountry": "País",
|
||||
"labels.profileFavoriteBadges": "",
|
||||
"labels.profileShowDiscordUniqueName": "Mostrar usuario de Discord",
|
||||
"labels.profileCommissionsOpen": "Comisiones abiertas",
|
||||
"labels.profileCommissionText": "Info de comisiones",
|
||||
"labels.profileNewProfileEnabled": "",
|
||||
"bottomTexts.profileCustomName": "",
|
||||
"bottomTexts.profileCustomUrl": "",
|
||||
"bottomTexts.profileInGameName": "",
|
||||
"bottomTexts.profileBattlefy": "",
|
||||
"bottomTexts.profileShowDiscordUniqueName": "¿Mostrar tu nombre de Discord ({{discordUniqueName}}) publicamente?",
|
||||
"bottomTexts.profileCommissionsOpen": "",
|
||||
"bottomTexts.profileCommissionText": "Precio, espacios abiertos, o cualquier otra información sobre tus comiciones.",
|
||||
"bottomTexts.profileNewProfileEnabled": "",
|
||||
"errors.profileCustomUrlStrangeChar": "Enlace personalizado no puede contener caracteres especiales",
|
||||
"errors.profileCustomUrlNumbers": "Enlace personalizado no puede ser solo numeros",
|
||||
"errors.profileCustomUrlDuplicate": "Alguien ya tiene ese enlace personalizado",
|
||||
"errors.profileSensBothOrNeither": "Motion sens can't be set if R-stick sens isn't",
|
||||
"errors.profileInGameName": "",
|
||||
"labels.pronoun": "",
|
||||
"bottomTexts.profilePronouns": "",
|
||||
"errors.profilePronounsBothOrNeither": ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
{
|
||||
"customUrl": "Enlace personalizado",
|
||||
"customName": "",
|
||||
"ign": "Nombre en el juego",
|
||||
"ign.short": "IGN",
|
||||
"country": "País",
|
||||
"bio": "Biografía",
|
||||
"widget.bio": "",
|
||||
"widget.bio-md": "",
|
||||
"widget.badges-owned": "",
|
||||
|
|
@ -143,24 +139,8 @@
|
|||
"motion": "Giroscopio",
|
||||
"stick": "Palanca",
|
||||
"sens": "Sens",
|
||||
"pronoun": "",
|
||||
"usesPronouns": "",
|
||||
"pronounsInfo": "",
|
||||
"weaponPool": "Grupo de armas",
|
||||
"discordExplanation": "Tu nombre, foto, y cuentas de YouTube, Bluesky y Twitch se obtienen por tu cuenta en Discord. Ver <1>FAQ</1> para más información.",
|
||||
"favoriteBadges": "",
|
||||
"battlefy": "",
|
||||
"forms.newProfileEnabled": "",
|
||||
"forms.newProfileEnabled.info": "",
|
||||
"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": "",
|
||||
"forms.country.search.placeholder": "",
|
||||
"forms.favoriteBadges.nonSupporter": "",
|
||||
"results.title": "",
|
||||
"results.placing": "Lugar",
|
||||
"results.team": "Equipo",
|
||||
|
|
@ -175,12 +155,6 @@
|
|||
"results.button.showHighlights": "",
|
||||
"results.button.showAll": "",
|
||||
"forms.errors.maxWeapons": "Máxima cantidad de armas",
|
||||
"forms.errors.invalidCustomUrl.numbers": "Enlace personalizado no puede ser solo numeros",
|
||||
"forms.errors.invalidCustomUrl.strangeCharacter": "Enlace personalizado no puede contener caracteres especiales",
|
||||
"forms.errors.invalidCustomUrl.duplicate": "Alguien ya tiene ese enlace personalizado",
|
||||
"forms.errors.invalidSens": "Motion sens can't be set if R-stick sens isn't",
|
||||
"forms.info.customUrl": "",
|
||||
"forms.info.battlefy": "",
|
||||
"search.info": "",
|
||||
"search.noResults": "No se encontraron usuarios que coincidan con '{{query}}'",
|
||||
"search.pleaseLogIn.header": "",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"submit": "Finalizar",
|
||||
"labels.name": "Nombre",
|
||||
"labels.bio": "Bio",
|
||||
"labels.bio": "Biografía",
|
||||
"labels.tag": "",
|
||||
"labels.teamBsky": "",
|
||||
"labels.clockFormat": "",
|
||||
|
|
@ -24,7 +24,7 @@
|
|||
"errors.atLeastOneOption": "",
|
||||
"errors.duplicateName": "Ya existe un equipo con ese nombre",
|
||||
"errors.noOnlySpecialCharacters": "",
|
||||
"labels.weaponPool": "",
|
||||
"labels.weaponPool": "Grupo de armas",
|
||||
"placeholders.weaponPoolFull": "",
|
||||
"labels.voiceChat": "",
|
||||
"labels.languages": "",
|
||||
|
|
@ -187,5 +187,33 @@
|
|||
"labels.comment": "",
|
||||
"errors.plusAlreadySuggested": "",
|
||||
"errors.plusAlreadyMember": "",
|
||||
"errors.plusCannotSuggest": ""
|
||||
"errors.plusCannotSuggest": "",
|
||||
"labels.profileCustomName": "Nombre personalizado",
|
||||
"labels.profileCustomUrl": "Enlace personalizado",
|
||||
"labels.profileInGameName": "Nombre en el juego",
|
||||
"labels.profileBattlefy": "Nombre de cuenta de Battlefy",
|
||||
"labels.profileMotionSens": "Sens del giroscopio",
|
||||
"labels.profileStickSens": "Sens de palanca",
|
||||
"labels.profileCountry": "País",
|
||||
"labels.profileFavoriteBadges": "",
|
||||
"labels.profileShowDiscordUniqueName": "Mostrar usuario de Discord",
|
||||
"labels.profileCommissionsOpen": "Comisiones abiertas",
|
||||
"labels.profileCommissionText": "Info de comisiones",
|
||||
"labels.profileNewProfileEnabled": "",
|
||||
"bottomTexts.profileCustomName": "Si vacío, se mostrará tu nombre de Discord: \"{{discordName}}\"",
|
||||
"bottomTexts.profileCustomUrl": "",
|
||||
"bottomTexts.profileInGameName": "",
|
||||
"bottomTexts.profileBattlefy": "El nombre de tu cuenta de Battlefy se utiliza para la clasificación y verificación en algunos torneos.",
|
||||
"bottomTexts.profileShowDiscordUniqueName": "¿Mostrar tu nombre de Discord ({{discordUniqueName}}) publicamente?",
|
||||
"bottomTexts.profileCommissionsOpen": "",
|
||||
"bottomTexts.profileCommissionText": "Precio, espacios abiertos, o cualquier otra información sobre tus comiciones.",
|
||||
"bottomTexts.profileNewProfileEnabled": "",
|
||||
"errors.profileCustomUrlStrangeChar": "Enlace personalizado no puede contener caracteres especiales",
|
||||
"errors.profileCustomUrlNumbers": "Enlace personalizado no puede ser solo numeros",
|
||||
"errors.profileCustomUrlDuplicate": "Alguien ya tiene ese enlace personalizado",
|
||||
"errors.profileSensBothOrNeither": "Sens de giroscopio no se poner sin la sens de palanca",
|
||||
"errors.profileInGameName": "",
|
||||
"labels.pronoun": "",
|
||||
"bottomTexts.profilePronouns": "",
|
||||
"errors.profilePronounsBothOrNeither": ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
{
|
||||
"customUrl": "Enlace personalizado",
|
||||
"customName": "Nombre personalizado",
|
||||
"ign": "Nombre en el juego",
|
||||
"ign.short": "IGN",
|
||||
"country": "País",
|
||||
"bio": "Biografía",
|
||||
"widget.bio": "",
|
||||
"widget.bio-md": "",
|
||||
"widget.badges-owned": "",
|
||||
|
|
@ -143,24 +139,8 @@
|
|||
"motion": "Giroscopio",
|
||||
"stick": "Palanca",
|
||||
"sens": "Sens",
|
||||
"pronoun": "",
|
||||
"usesPronouns": "",
|
||||
"pronounsInfo": "",
|
||||
"weaponPool": "Grupo de armas",
|
||||
"discordExplanation": "Tu nombre, foto, y cuentas de YouTube, Bluesky y Twitch se obtienen por tu cuenta en Discord. Ver <1>FAQ</1> para más información.",
|
||||
"favoriteBadges": "",
|
||||
"battlefy": "Nombre de cuenta de Battlefy",
|
||||
"forms.newProfileEnabled": "",
|
||||
"forms.newProfileEnabled.info": "",
|
||||
"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}}\"",
|
||||
"forms.country.search.placeholder": "",
|
||||
"forms.favoriteBadges.nonSupporter": "",
|
||||
"results.title": "Todos los resultados",
|
||||
"results.placing": "Lugar",
|
||||
"results.team": "Equipo",
|
||||
|
|
@ -175,12 +155,6 @@
|
|||
"results.button.showHighlights": "Mostrar resaltos",
|
||||
"results.button.showAll": "Mostrar todos",
|
||||
"forms.errors.maxWeapons": "Máxima cantidad de armas",
|
||||
"forms.errors.invalidCustomUrl.numbers": "Enlace personalizado no puede ser solo numeros",
|
||||
"forms.errors.invalidCustomUrl.strangeCharacter": "Enlace personalizado no puede contener caracteres especiales",
|
||||
"forms.errors.invalidCustomUrl.duplicate": "Alguien ya tiene ese enlace personalizado",
|
||||
"forms.errors.invalidSens": "Sens de giroscopio no se poner sin la sens de palanca",
|
||||
"forms.info.customUrl": "",
|
||||
"forms.info.battlefy": "El nombre de tu cuenta de Battlefy se utiliza para la clasificación y verificación en algunos torneos.",
|
||||
"search.info": "",
|
||||
"search.noResults": "No se encontraron usuarios que coincidan con '{{query}}'",
|
||||
"search.pleaseLogIn.header": "",
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
"errors.atLeastOneOption": "",
|
||||
"errors.duplicateName": "Il y a déjà une équipe avec ce nom",
|
||||
"errors.noOnlySpecialCharacters": "",
|
||||
"labels.weaponPool": "",
|
||||
"labels.weaponPool": "Armes jouées",
|
||||
"placeholders.weaponPoolFull": "",
|
||||
"labels.voiceChat": "",
|
||||
"labels.languages": "",
|
||||
|
|
@ -187,5 +187,33 @@
|
|||
"labels.comment": "",
|
||||
"errors.plusAlreadySuggested": "",
|
||||
"errors.plusAlreadyMember": "",
|
||||
"errors.plusCannotSuggest": ""
|
||||
"errors.plusCannotSuggest": "",
|
||||
"labels.profileCustomName": "",
|
||||
"labels.profileCustomUrl": "URL personnalisée",
|
||||
"labels.profileInGameName": "Pseudo en jeu",
|
||||
"labels.profileBattlefy": "",
|
||||
"labels.profileMotionSens": "Sensibilité du gyroscope",
|
||||
"labels.profileStickSens": "Sensibilité du stick droit",
|
||||
"labels.profileCountry": "Pays",
|
||||
"labels.profileFavoriteBadges": "",
|
||||
"labels.profileShowDiscordUniqueName": "Montrer le pseudo Discord",
|
||||
"labels.profileCommissionsOpen": "Commissions acceptées",
|
||||
"labels.profileCommissionText": "Info pour les commissions",
|
||||
"labels.profileNewProfileEnabled": "",
|
||||
"bottomTexts.profileCustomName": "",
|
||||
"bottomTexts.profileCustomUrl": "",
|
||||
"bottomTexts.profileInGameName": "",
|
||||
"bottomTexts.profileBattlefy": "",
|
||||
"bottomTexts.profileShowDiscordUniqueName": "Show your unique Discord name ({{discordUniqueName}}) publicly?",
|
||||
"bottomTexts.profileCommissionsOpen": "",
|
||||
"bottomTexts.profileCommissionText": "Prix, disponibilités et tout autres info nécéssaires",
|
||||
"bottomTexts.profileNewProfileEnabled": "",
|
||||
"errors.profileCustomUrlStrangeChar": "Votre URL personnalisée ne peut pas contenir de caractères spéciaux",
|
||||
"errors.profileCustomUrlNumbers": "Votre URL personnalisée ne peut pas contenir que des nombres",
|
||||
"errors.profileCustomUrlDuplicate": "Cette URL a déjà été choisie par quelqu'un",
|
||||
"errors.profileSensBothOrNeither": "La sensibilité du gyroscope ne peut pas être choisie si la sensibilité du stick droit ne l'est pas",
|
||||
"errors.profileInGameName": "",
|
||||
"labels.pronoun": "",
|
||||
"bottomTexts.profilePronouns": "",
|
||||
"errors.profilePronounsBothOrNeither": ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
{
|
||||
"customUrl": "URL personnalisée",
|
||||
"customName": "",
|
||||
"ign": "Pseudo en jeu",
|
||||
"ign.short": "PEJ",
|
||||
"country": "Pays",
|
||||
"bio": "Bio",
|
||||
"widget.bio": "",
|
||||
"widget.bio-md": "",
|
||||
"widget.badges-owned": "",
|
||||
|
|
@ -143,24 +139,8 @@
|
|||
"motion": "Gyro",
|
||||
"stick": "Stick",
|
||||
"sens": "Sens",
|
||||
"pronoun": "",
|
||||
"usesPronouns": "",
|
||||
"pronounsInfo": "",
|
||||
"weaponPool": "Armes jouées",
|
||||
"discordExplanation": "Votre pseudo, votre photo de profil et vos comptes Youtube, Bluesky et Twitch viennent de votre compte Discord. Voir la <1>FAQ</1> pour plus d'informations.",
|
||||
"favoriteBadges": "",
|
||||
"battlefy": "",
|
||||
"forms.newProfileEnabled": "",
|
||||
"forms.newProfileEnabled.info": "",
|
||||
"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": "",
|
||||
"forms.country.search.placeholder": "",
|
||||
"forms.favoriteBadges.nonSupporter": "",
|
||||
"results.title": "",
|
||||
"results.placing": "Placement",
|
||||
"results.team": "Équipe",
|
||||
|
|
@ -175,12 +155,6 @@
|
|||
"results.button.showHighlights": "",
|
||||
"results.button.showAll": "",
|
||||
"forms.errors.maxWeapons": "Nombre d'armes maximum atteint",
|
||||
"forms.errors.invalidCustomUrl.numbers": "Votre URL personnalisée ne peut pas contenir que des nombres",
|
||||
"forms.errors.invalidCustomUrl.strangeCharacter": "Votre URL personnalisée ne peut pas contenir de caractères spéciaux",
|
||||
"forms.errors.invalidCustomUrl.duplicate": "Cette URL a déjà été choisie par quelqu'un",
|
||||
"forms.errors.invalidSens": "La sensibilité du gyroscope ne peut pas être choisie si la sensibilité du stick droit ne l'est pas",
|
||||
"forms.info.customUrl": "",
|
||||
"forms.info.battlefy": "",
|
||||
"search.info": "",
|
||||
"search.noResults": "Aucun utilisateur correspondant à '{{query}}' n'a été trouvé",
|
||||
"search.pleaseLogIn.header": "",
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
"errors.atLeastOneOption": "",
|
||||
"errors.duplicateName": "Il y a déjà une équipe avec ce nom",
|
||||
"errors.noOnlySpecialCharacters": "",
|
||||
"labels.weaponPool": "",
|
||||
"labels.weaponPool": "Armes jouées",
|
||||
"placeholders.weaponPoolFull": "",
|
||||
"labels.voiceChat": "",
|
||||
"labels.languages": "",
|
||||
|
|
@ -187,5 +187,33 @@
|
|||
"labels.comment": "",
|
||||
"errors.plusAlreadySuggested": "",
|
||||
"errors.plusAlreadyMember": "",
|
||||
"errors.plusCannotSuggest": ""
|
||||
"errors.plusCannotSuggest": "",
|
||||
"labels.profileCustomName": "Nom personnalisée",
|
||||
"labels.profileCustomUrl": "URL personnalisée",
|
||||
"labels.profileInGameName": "Pseudo en jeu",
|
||||
"labels.profileBattlefy": "Nom du compte Battlefy",
|
||||
"labels.profileMotionSens": "Sensibilité du gyroscope",
|
||||
"labels.profileStickSens": "Sensibilité du stick droit",
|
||||
"labels.profileCountry": "Pays",
|
||||
"labels.profileFavoriteBadges": "",
|
||||
"labels.profileShowDiscordUniqueName": "Montrer le pseudo Discord",
|
||||
"labels.profileCommissionsOpen": "Commissions acceptées",
|
||||
"labels.profileCommissionText": "Info pour les commissions",
|
||||
"labels.profileNewProfileEnabled": "",
|
||||
"bottomTexts.profileCustomName": "Si il n'est pas présent, votre pseudo discord est utilisé: \"{{discordName}}\"",
|
||||
"bottomTexts.profileCustomUrl": "Pour les Supporter patrons (& plus), les liens courts sont disponibles. Exemple: Au mieux de sendou.ink/u/sendou, snd.ink/sendou peut être utilisé.",
|
||||
"bottomTexts.profileInGameName": "",
|
||||
"bottomTexts.profileBattlefy": "Votre nom Battlefy est utiliser pour le seeding et la verification de certains tournois",
|
||||
"bottomTexts.profileShowDiscordUniqueName": "Show your unique Discord name ({{discordUniqueName}}) publicly?",
|
||||
"bottomTexts.profileCommissionsOpen": "",
|
||||
"bottomTexts.profileCommissionText": "Prix, disponibilités et tout autres info nécéssaires",
|
||||
"bottomTexts.profileNewProfileEnabled": "",
|
||||
"errors.profileCustomUrlStrangeChar": "Votre URL personnalisée ne peut pas contenir de caractères spéciaux",
|
||||
"errors.profileCustomUrlNumbers": "Votre URL personnalisée ne peut pas contenir que des nombres",
|
||||
"errors.profileCustomUrlDuplicate": "Cette URL a déjà été choisie par quelqu'un",
|
||||
"errors.profileSensBothOrNeither": "La sensibilité du gyroscope ne peut pas être choisie si la sensibilité du stick droit ne l'est pas",
|
||||
"errors.profileInGameName": "",
|
||||
"labels.pronoun": "",
|
||||
"bottomTexts.profilePronouns": "",
|
||||
"errors.profilePronounsBothOrNeither": ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
{
|
||||
"customUrl": "URL personnalisée",
|
||||
"customName": "Nom personnalisée",
|
||||
"ign": "Pseudo en jeu",
|
||||
"ign.short": "PEJ",
|
||||
"country": "Pays",
|
||||
"bio": "Bio",
|
||||
"widget.bio": "",
|
||||
"widget.bio-md": "",
|
||||
"widget.badges-owned": "",
|
||||
|
|
@ -143,24 +139,8 @@
|
|||
"motion": "Gyro",
|
||||
"stick": "Stick",
|
||||
"sens": "Sens",
|
||||
"pronoun": "",
|
||||
"usesPronouns": "",
|
||||
"pronounsInfo": "",
|
||||
"weaponPool": "Armes jouées",
|
||||
"discordExplanation": "Votre pseudo, votre photo de profil et vos comptes Youtube, Bluesky et Twitch viennent de votre compte Discord. Voir la <1>FAQ</1> pour plus d'informations.",
|
||||
"favoriteBadges": "Badge favori",
|
||||
"battlefy": "Nom du compte Battlefy",
|
||||
"forms.newProfileEnabled": "",
|
||||
"forms.newProfileEnabled.info": "",
|
||||
"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}}\"",
|
||||
"forms.country.search.placeholder": "Rechercher de pays",
|
||||
"forms.favoriteBadges.nonSupporter": "Devenez supporter pour définir l'ordre des badges qui apparaissent sur la première page",
|
||||
"results.title": "Tout les résultats",
|
||||
"results.placing": "Placement",
|
||||
"results.team": "Équipe",
|
||||
|
|
@ -175,12 +155,6 @@
|
|||
"results.button.showHighlights": "Montrer les highlights",
|
||||
"results.button.showAll": "Tout montrer",
|
||||
"forms.errors.maxWeapons": "Nombre d'armes maximum atteint",
|
||||
"forms.errors.invalidCustomUrl.numbers": "Votre URL personnalisée ne peut pas contenir que des nombres",
|
||||
"forms.errors.invalidCustomUrl.strangeCharacter": "Votre URL personnalisée ne peut pas contenir de caractères spéciaux",
|
||||
"forms.errors.invalidCustomUrl.duplicate": "Cette URL a déjà été choisie par quelqu'un",
|
||||
"forms.errors.invalidSens": "La sensibilité du gyroscope ne peut pas être choisie si la sensibilité du stick droit ne l'est pas",
|
||||
"forms.info.customUrl": "Pour les Supporter patrons (& plus), les liens courts sont disponibles. Exemple: Au mieux de sendou.ink/u/sendou, snd.ink/sendou peut être utilisé.",
|
||||
"forms.info.battlefy": "Votre nom Battlefy est utiliser pour le seeding et la verification de certains tournois",
|
||||
"search.info": "Recherchez avec le pseudo Discord ou Splatoon 3 du compte",
|
||||
"search.noResults": "Aucun utilisateur correspondant à '{{query}}' n'a été trouvé",
|
||||
"search.pleaseLogIn.header": "Veuillez vous connecter pour rechercher des utilisateurs",
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
"errors.atLeastOneOption": "",
|
||||
"errors.duplicateName": "יש כבר צוות בשם הזה",
|
||||
"errors.noOnlySpecialCharacters": "",
|
||||
"labels.weaponPool": "",
|
||||
"labels.weaponPool": "מאגר נשקים",
|
||||
"placeholders.weaponPoolFull": "",
|
||||
"labels.voiceChat": "",
|
||||
"labels.languages": "",
|
||||
|
|
@ -187,5 +187,33 @@
|
|||
"labels.comment": "",
|
||||
"errors.plusAlreadySuggested": "",
|
||||
"errors.plusAlreadyMember": "",
|
||||
"errors.plusCannotSuggest": ""
|
||||
"errors.plusCannotSuggest": "",
|
||||
"labels.profileCustomName": "",
|
||||
"labels.profileCustomUrl": "כתובת URL מותאמת אישית",
|
||||
"labels.profileInGameName": "שם במשחק",
|
||||
"labels.profileBattlefy": "",
|
||||
"labels.profileMotionSens": "רגישות תנועה",
|
||||
"labels.profileStickSens": "רגישות סטיק ימני",
|
||||
"labels.profileCountry": "מדינה",
|
||||
"labels.profileFavoriteBadges": "",
|
||||
"labels.profileShowDiscordUniqueName": "הראה שם משתמש Discord",
|
||||
"labels.profileCommissionsOpen": "בקשות פתוחות",
|
||||
"labels.profileCommissionText": "מידע עבור בקשות",
|
||||
"labels.profileNewProfileEnabled": "",
|
||||
"bottomTexts.profileCustomName": "",
|
||||
"bottomTexts.profileCustomUrl": "",
|
||||
"bottomTexts.profileInGameName": "",
|
||||
"bottomTexts.profileBattlefy": "",
|
||||
"bottomTexts.profileShowDiscordUniqueName": "להראות את שם ה-Discord היחודי שלכם ({{discordUniqueName}}) בפומבי?",
|
||||
"bottomTexts.profileCommissionsOpen": "",
|
||||
"bottomTexts.profileCommissionText": "מחיר, כמות בקשות או מידע אחר שקשור לבקשות אלכם",
|
||||
"bottomTexts.profileNewProfileEnabled": "",
|
||||
"errors.profileCustomUrlStrangeChar": "כתובת URL מותאמת אישית לא יכולה להכיל תווים מיוחדים",
|
||||
"errors.profileCustomUrlNumbers": "כתובת URL מותאמת אישית לא יכולה להכיל רק מספרים",
|
||||
"errors.profileCustomUrlDuplicate": "מישהו כבר משתמש בכתובת URL המותאמת אישית הזו",
|
||||
"errors.profileSensBothOrNeither": "לא ניתן להגדיר את רגישות התנועה אם רגישות הסטיק לא מוגדרת",
|
||||
"errors.profileInGameName": "",
|
||||
"labels.pronoun": "",
|
||||
"bottomTexts.profilePronouns": "",
|
||||
"errors.profilePronounsBothOrNeither": ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
{
|
||||
"customUrl": "כתובת URL מותאמת אישית",
|
||||
"customName": "",
|
||||
"ign": "שם במשחק",
|
||||
"ign.short": "IGN",
|
||||
"country": "מדינה",
|
||||
"bio": "ביו",
|
||||
"widget.bio": "",
|
||||
"widget.bio-md": "",
|
||||
"widget.badges-owned": "",
|
||||
|
|
@ -143,24 +139,8 @@
|
|||
"motion": "תנועה",
|
||||
"stick": "סטיק",
|
||||
"sens": "רגישות",
|
||||
"pronoun": "",
|
||||
"usesPronouns": "",
|
||||
"pronounsInfo": "",
|
||||
"weaponPool": "מאגר נשקים",
|
||||
"discordExplanation": "שם משתמש, תמונת פרופיל, חשבונות YouTube, Bluesky ו-Twitch מגיעים מחשבון Discord שלך. ראו <1>שאלות נפוצות</1> למידע נוסף.",
|
||||
"favoriteBadges": "",
|
||||
"battlefy": "",
|
||||
"forms.newProfileEnabled": "",
|
||||
"forms.newProfileEnabled.info": "",
|
||||
"forms.showDiscordUniqueName": "הראה שם משתמש Discord",
|
||||
"forms.showDiscordUniqueName.info": "להראות את שם ה-Discord היחודי שלכם ({{discordUniqueName}}) בפומבי?",
|
||||
"forms.commissionsOpen": "בקשות פתוחות",
|
||||
"forms.commissionsOpen.info": "",
|
||||
"forms.commissionText": "מידע עבור בקשות",
|
||||
"forms.commissionText.info": "מחיר, כמות בקשות או מידע אחר שקשור לבקשות אלכם",
|
||||
"forms.customName.info": "",
|
||||
"forms.country.search.placeholder": "",
|
||||
"forms.favoriteBadges.nonSupporter": "",
|
||||
"results.title": "",
|
||||
"results.placing": "מיקום",
|
||||
"results.team": "צוות",
|
||||
|
|
@ -175,12 +155,6 @@
|
|||
"results.button.showHighlights": "",
|
||||
"results.button.showAll": "",
|
||||
"forms.errors.maxWeapons": "הגעה לכמות מקסימלית של מספר נשקים",
|
||||
"forms.errors.invalidCustomUrl.numbers": "כתובת URL מותאמת אישית לא יכולה להכיל רק מספרים",
|
||||
"forms.errors.invalidCustomUrl.strangeCharacter": "כתובת URL מותאמת אישית לא יכולה להכיל תווים מיוחדים",
|
||||
"forms.errors.invalidCustomUrl.duplicate": "מישהו כבר משתמש בכתובת URL המותאמת אישית הזו",
|
||||
"forms.errors.invalidSens": "לא ניתן להגדיר את רגישות התנועה אם רגישות הסטיק לא מוגדרת",
|
||||
"forms.info.customUrl": "",
|
||||
"forms.info.battlefy": "",
|
||||
"search.info": "",
|
||||
"search.noResults": "לא נמצאו משתמשים התואמים '{{query}}'",
|
||||
"search.pleaseLogIn.header": "",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"submit": "Invia",
|
||||
"labels.name": "Nome",
|
||||
"labels.bio": "Bio",
|
||||
"labels.bio": "Biografia",
|
||||
"labels.tag": "",
|
||||
"labels.teamBsky": "Bluesky del team",
|
||||
"labels.clockFormat": "",
|
||||
|
|
@ -24,7 +24,7 @@
|
|||
"errors.atLeastOneOption": "",
|
||||
"errors.duplicateName": "Esiste già un team con questo nome",
|
||||
"errors.noOnlySpecialCharacters": "",
|
||||
"labels.weaponPool": "",
|
||||
"labels.weaponPool": "Pool armi",
|
||||
"placeholders.weaponPoolFull": "",
|
||||
"labels.voiceChat": "",
|
||||
"labels.languages": "",
|
||||
|
|
@ -187,5 +187,33 @@
|
|||
"labels.comment": "",
|
||||
"errors.plusAlreadySuggested": "",
|
||||
"errors.plusAlreadyMember": "",
|
||||
"errors.plusCannotSuggest": ""
|
||||
"errors.plusCannotSuggest": "",
|
||||
"labels.profileCustomName": "Nome personalizzato",
|
||||
"labels.profileCustomUrl": "URL personalizzato",
|
||||
"labels.profileInGameName": "Nome nel gioco",
|
||||
"labels.profileBattlefy": "Nome account Battlefy",
|
||||
"labels.profileMotionSens": "Sensitività Giroscopio",
|
||||
"labels.profileStickSens": "Sensitività Joystick Dx",
|
||||
"labels.profileCountry": "Paese",
|
||||
"labels.profileFavoriteBadges": "",
|
||||
"labels.profileShowDiscordUniqueName": "Mostra username Discord",
|
||||
"labels.profileCommissionsOpen": "Commissioni aperte",
|
||||
"labels.profileCommissionText": "Info sulle commissioni",
|
||||
"labels.profileNewProfileEnabled": "",
|
||||
"bottomTexts.profileCustomName": "Se mancante, viene usato il tuo nome visualizzato Discord: \"{{discordName}}\"",
|
||||
"bottomTexts.profileCustomUrl": "Per gli iscritti al Patreon (Supporter compreso in su) è disponibile il link corto. Es. invece di sendou.ink/u/sendou, può essere usato snd.ink/sendou.",
|
||||
"bottomTexts.profileInGameName": "",
|
||||
"bottomTexts.profileBattlefy": "Il nome dell'account Battlefy è usato per il seeding e verifica in alcuni tornei",
|
||||
"bottomTexts.profileShowDiscordUniqueName": "Mostrare il proprio nome unico Discord ({{discordUniqueName}}) pubblicamente?",
|
||||
"bottomTexts.profileCommissionsOpen": "",
|
||||
"bottomTexts.profileCommissionText": "Prezzo, posti liberi o altre info relative al commissionarti",
|
||||
"bottomTexts.profileNewProfileEnabled": "",
|
||||
"errors.profileCustomUrlStrangeChar": "L'URL personalizzato non può contenere caratteri speciali",
|
||||
"errors.profileCustomUrlNumbers": "L'URL personalizzato non può contenere solo numeri",
|
||||
"errors.profileCustomUrlDuplicate": "L'URL personalizzato è già in uso da un altro utente",
|
||||
"errors.profileSensBothOrNeither": "La sensibilità del giroscopio non può essere impostata se non hai impostato la sensibilità del joystick destro",
|
||||
"errors.profileInGameName": "",
|
||||
"labels.pronoun": "",
|
||||
"bottomTexts.profilePronouns": "",
|
||||
"errors.profilePronounsBothOrNeither": ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
{
|
||||
"customUrl": "URL personalizzato",
|
||||
"customName": "Nome personalizzato",
|
||||
"ign": "Nome nel gioco",
|
||||
"ign.short": "IGN",
|
||||
"country": "Paese",
|
||||
"bio": "Biografia",
|
||||
"widget.bio": "",
|
||||
"widget.bio-md": "",
|
||||
"widget.badges-owned": "",
|
||||
|
|
@ -143,24 +139,8 @@
|
|||
"motion": "Giroscopio",
|
||||
"stick": "Joystick",
|
||||
"sens": "Sens.",
|
||||
"pronoun": "",
|
||||
"usesPronouns": "",
|
||||
"pronounsInfo": "",
|
||||
"weaponPool": "Pool armi",
|
||||
"discordExplanation": "Username, foto profilo, account YouTube, Bluesky e Twitch vengono dal tuo account Discord. Visita <1>FAQ</1> per ulteriori informazioni.",
|
||||
"favoriteBadges": "",
|
||||
"battlefy": "Nome account Battlefy",
|
||||
"forms.newProfileEnabled": "",
|
||||
"forms.newProfileEnabled.info": "",
|
||||
"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}}\"",
|
||||
"forms.country.search.placeholder": "",
|
||||
"forms.favoriteBadges.nonSupporter": "",
|
||||
"results.title": "Tutti i risultati",
|
||||
"results.placing": "Risultato",
|
||||
"results.team": "Team",
|
||||
|
|
@ -175,12 +155,6 @@
|
|||
"results.button.showHighlights": "Mostra highlight",
|
||||
"results.button.showAll": "Mostra tutti",
|
||||
"forms.errors.maxWeapons": "Massimo numero di armi raggiunto",
|
||||
"forms.errors.invalidCustomUrl.numbers": "L'URL personalizzato non può contenere solo numeri",
|
||||
"forms.errors.invalidCustomUrl.strangeCharacter": "L'URL personalizzato non può contenere caratteri speciali",
|
||||
"forms.errors.invalidCustomUrl.duplicate": "L'URL personalizzato è già in uso da un altro utente",
|
||||
"forms.errors.invalidSens": "La sensibilità del giroscopio non può essere impostata se non hai impostato la sensibilità del joystick destro",
|
||||
"forms.info.customUrl": "Per gli iscritti al Patreon (Supporter compreso in su) è disponibile il link corto. Es. invece di sendou.ink/u/sendou, può essere usato snd.ink/sendou.",
|
||||
"forms.info.battlefy": "Il nome dell'account Battlefy è usato per il seeding e verifica in alcuni tornei",
|
||||
"search.info": "Cerca utenti tramite nome Discord o Splatoon 3",
|
||||
"search.noResults": "Nessun utente trovato per '{{query}}'",
|
||||
"search.pleaseLogIn.header": "",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"submit": "送信",
|
||||
"labels.name": "名前",
|
||||
"labels.bio": "Bio",
|
||||
"labels.bio": "自己紹介",
|
||||
"labels.tag": "",
|
||||
"labels.teamBsky": "チームの Bluesky",
|
||||
"labels.clockFormat": "",
|
||||
|
|
@ -24,7 +24,7 @@
|
|||
"errors.atLeastOneOption": "",
|
||||
"errors.duplicateName": "そのチーム名はすでに使用されています",
|
||||
"errors.noOnlySpecialCharacters": "",
|
||||
"labels.weaponPool": "",
|
||||
"labels.weaponPool": "使用ブキ",
|
||||
"placeholders.weaponPoolFull": "",
|
||||
"labels.voiceChat": "",
|
||||
"labels.languages": "",
|
||||
|
|
@ -187,5 +187,33 @@
|
|||
"labels.comment": "",
|
||||
"errors.plusAlreadySuggested": "",
|
||||
"errors.plusAlreadyMember": "",
|
||||
"errors.plusCannotSuggest": ""
|
||||
"errors.plusCannotSuggest": "",
|
||||
"labels.profileCustomName": "カスタム 名前",
|
||||
"labels.profileCustomUrl": "カスタム URL",
|
||||
"labels.profileInGameName": "ゲーム中の名前",
|
||||
"labels.profileBattlefy": "Battlefyアカウント名",
|
||||
"labels.profileMotionSens": "モーション感度",
|
||||
"labels.profileStickSens": "右スティック感度",
|
||||
"labels.profileCountry": "国",
|
||||
"labels.profileFavoriteBadges": "",
|
||||
"labels.profileShowDiscordUniqueName": "Discord のユーザー名を表示する",
|
||||
"labels.profileCommissionsOpen": "依頼を受付中",
|
||||
"labels.profileCommissionText": "依頼に関する情報",
|
||||
"labels.profileNewProfileEnabled": "",
|
||||
"bottomTexts.profileCustomName": "記入されてない場合ディスコードの表示名 \"{{discordName}}\"を使います",
|
||||
"bottomTexts.profileCustomUrl": "",
|
||||
"bottomTexts.profileInGameName": "",
|
||||
"bottomTexts.profileBattlefy": "Battlefyのアカウント名は特定のトーナメンでシーディング及びにプレイヤー情報の確認に使用されます。",
|
||||
"bottomTexts.profileShowDiscordUniqueName": "Discord のユニーク名 ({{discordUniqueName}}) 公表しますか?",
|
||||
"bottomTexts.profileCommissionsOpen": "",
|
||||
"bottomTexts.profileCommissionText": "価格、受付数、その他依頼に関する情報",
|
||||
"bottomTexts.profileNewProfileEnabled": "",
|
||||
"errors.profileCustomUrlStrangeChar": "カスタム URL は特殊文字を含めることはできません",
|
||||
"errors.profileCustomUrlNumbers": "カスタム URL は数字のみで作成することはできません",
|
||||
"errors.profileCustomUrlDuplicate": "このカスタム URL はすでに使用されています",
|
||||
"errors.profileSensBothOrNeither": "右スティックの感度が設定されていない場合、感度を設定することはできません",
|
||||
"errors.profileInGameName": "",
|
||||
"labels.pronoun": "",
|
||||
"bottomTexts.profilePronouns": "",
|
||||
"errors.profilePronounsBothOrNeither": ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
{
|
||||
"customUrl": "カスタム URL",
|
||||
"customName": "カスタム 名前",
|
||||
"ign": "ゲーム中の名前",
|
||||
"ign.short": "ゲーム中の名前",
|
||||
"country": "国",
|
||||
"bio": "自己紹介",
|
||||
"widget.bio": "",
|
||||
"widget.bio-md": "",
|
||||
"widget.badges-owned": "",
|
||||
|
|
@ -143,24 +139,8 @@
|
|||
"motion": "モーション",
|
||||
"stick": "スティック",
|
||||
"sens": "感度",
|
||||
"pronoun": "",
|
||||
"usesPronouns": "",
|
||||
"pronounsInfo": "",
|
||||
"weaponPool": "使用ブキ",
|
||||
"discordExplanation": "ユーザー名、プロファイル画像、YouTube、Bluesky と Twitch アカウントは Discord のアカウントに設定されているものが使用されます。詳しくは <1>FAQ</1> をご覧ください。",
|
||||
"favoriteBadges": "",
|
||||
"battlefy": "Battlefyアカウント名",
|
||||
"forms.newProfileEnabled": "",
|
||||
"forms.newProfileEnabled.info": "",
|
||||
"forms.showDiscordUniqueName": "Discord のユーザー名を表示する",
|
||||
"forms.showDiscordUniqueName.info": "Discord のユニーク名 ({{discordUniqueName}}) 公表しますか?",
|
||||
"forms.commissionsOpen": "依頼を受付中",
|
||||
"forms.commissionsOpen.info": "",
|
||||
"forms.commissionText": "依頼に関する情報",
|
||||
"forms.commissionText.info": "価格、受付数、その他依頼に関する情報",
|
||||
"forms.customName.info": "記入されてない場合ディスコードの表示名 \"{{discordName}}\"を使います",
|
||||
"forms.country.search.placeholder": "",
|
||||
"forms.favoriteBadges.nonSupporter": "",
|
||||
"results.title": "全ての結果",
|
||||
"results.placing": "順位",
|
||||
"results.team": "チーム",
|
||||
|
|
@ -175,12 +155,6 @@
|
|||
"results.button.showHighlights": "ハイライトを表示",
|
||||
"results.button.showAll": "全て表示",
|
||||
"forms.errors.maxWeapons": "最大ブキ数を超えました",
|
||||
"forms.errors.invalidCustomUrl.numbers": "カスタム URL は数字のみで作成することはできません",
|
||||
"forms.errors.invalidCustomUrl.strangeCharacter": "カスタム URL は特殊文字を含めることはできません",
|
||||
"forms.errors.invalidCustomUrl.duplicate": "このカスタム URL はすでに使用されています",
|
||||
"forms.errors.invalidSens": "右スティックの感度が設定されていない場合、感度を設定することはできません",
|
||||
"forms.info.customUrl": "",
|
||||
"forms.info.battlefy": "Battlefyのアカウント名は特定のトーナメンでシーディング及びにプレイヤー情報の確認に使用されます。",
|
||||
"search.info": "",
|
||||
"search.noResults": "該当ユーザーが見つかりません '{{query}}'",
|
||||
"search.pleaseLogIn.header": "",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"submit": "제출",
|
||||
"labels.name": "이름",
|
||||
"labels.bio": "",
|
||||
"labels.bio": "소개",
|
||||
"labels.tag": "",
|
||||
"labels.teamBsky": "",
|
||||
"labels.clockFormat": "",
|
||||
|
|
@ -187,5 +187,33 @@
|
|||
"labels.comment": "",
|
||||
"errors.plusAlreadySuggested": "",
|
||||
"errors.plusAlreadyMember": "",
|
||||
"errors.plusCannotSuggest": ""
|
||||
"errors.plusCannotSuggest": "",
|
||||
"labels.profileCustomName": "",
|
||||
"labels.profileCustomUrl": "",
|
||||
"labels.profileInGameName": "",
|
||||
"labels.profileBattlefy": "",
|
||||
"labels.profileMotionSens": "",
|
||||
"labels.profileStickSens": "",
|
||||
"labels.profileCountry": "국가",
|
||||
"labels.profileFavoriteBadges": "",
|
||||
"labels.profileShowDiscordUniqueName": "",
|
||||
"labels.profileCommissionsOpen": "",
|
||||
"labels.profileCommissionText": "",
|
||||
"labels.profileNewProfileEnabled": "",
|
||||
"bottomTexts.profileCustomName": "",
|
||||
"bottomTexts.profileCustomUrl": "",
|
||||
"bottomTexts.profileInGameName": "",
|
||||
"bottomTexts.profileBattlefy": "",
|
||||
"bottomTexts.profileShowDiscordUniqueName": "",
|
||||
"bottomTexts.profileCommissionsOpen": "",
|
||||
"bottomTexts.profileCommissionText": "",
|
||||
"bottomTexts.profileNewProfileEnabled": "",
|
||||
"errors.profileCustomUrlStrangeChar": "",
|
||||
"errors.profileCustomUrlNumbers": "",
|
||||
"errors.profileCustomUrlDuplicate": "",
|
||||
"errors.profileSensBothOrNeither": "",
|
||||
"errors.profileInGameName": "",
|
||||
"labels.pronoun": "",
|
||||
"bottomTexts.profilePronouns": "",
|
||||
"errors.profilePronounsBothOrNeither": ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
{
|
||||
"customUrl": "",
|
||||
"customName": "",
|
||||
"ign": "",
|
||||
"ign.short": "",
|
||||
"country": "국가",
|
||||
"bio": "소개",
|
||||
"widget.bio": "",
|
||||
"widget.bio-md": "",
|
||||
"widget.badges-owned": "",
|
||||
|
|
@ -143,24 +139,8 @@
|
|||
"motion": "",
|
||||
"stick": "",
|
||||
"sens": "",
|
||||
"pronoun": "",
|
||||
"usesPronouns": "",
|
||||
"pronounsInfo": "",
|
||||
"weaponPool": "",
|
||||
"discordExplanation": "",
|
||||
"favoriteBadges": "",
|
||||
"battlefy": "",
|
||||
"forms.newProfileEnabled": "",
|
||||
"forms.newProfileEnabled.info": "",
|
||||
"forms.showDiscordUniqueName": "",
|
||||
"forms.showDiscordUniqueName.info": "",
|
||||
"forms.commissionsOpen": "",
|
||||
"forms.commissionsOpen.info": "",
|
||||
"forms.commissionText": "",
|
||||
"forms.commissionText.info": "",
|
||||
"forms.customName.info": "",
|
||||
"forms.country.search.placeholder": "",
|
||||
"forms.favoriteBadges.nonSupporter": "",
|
||||
"results.title": "",
|
||||
"results.placing": "순위",
|
||||
"results.team": "팀",
|
||||
|
|
@ -175,12 +155,6 @@
|
|||
"results.button.showHighlights": "",
|
||||
"results.button.showAll": "",
|
||||
"forms.errors.maxWeapons": "",
|
||||
"forms.errors.invalidCustomUrl.numbers": "",
|
||||
"forms.errors.invalidCustomUrl.strangeCharacter": "",
|
||||
"forms.errors.invalidCustomUrl.duplicate": "",
|
||||
"forms.errors.invalidSens": "",
|
||||
"forms.info.customUrl": "",
|
||||
"forms.info.battlefy": "",
|
||||
"search.info": "",
|
||||
"search.noResults": "",
|
||||
"search.pleaseLogIn.header": "",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"submit": "Verzenden",
|
||||
"labels.name": "Naam",
|
||||
"labels.bio": "",
|
||||
"labels.bio": "Bio",
|
||||
"labels.tag": "",
|
||||
"labels.teamBsky": "",
|
||||
"labels.clockFormat": "",
|
||||
|
|
@ -187,5 +187,33 @@
|
|||
"labels.comment": "",
|
||||
"errors.plusAlreadySuggested": "",
|
||||
"errors.plusAlreadyMember": "",
|
||||
"errors.plusCannotSuggest": ""
|
||||
"errors.plusCannotSuggest": "",
|
||||
"labels.profileCustomName": "",
|
||||
"labels.profileCustomUrl": "Eigen URL",
|
||||
"labels.profileInGameName": "In-game naam",
|
||||
"labels.profileBattlefy": "",
|
||||
"labels.profileMotionSens": "Bewegingsgevoeligheid",
|
||||
"labels.profileStickSens": "R-stick gevoeligheid",
|
||||
"labels.profileCountry": "Land",
|
||||
"labels.profileFavoriteBadges": "",
|
||||
"labels.profileShowDiscordUniqueName": "",
|
||||
"labels.profileCommissionsOpen": "",
|
||||
"labels.profileCommissionText": "",
|
||||
"labels.profileNewProfileEnabled": "",
|
||||
"bottomTexts.profileCustomName": "",
|
||||
"bottomTexts.profileCustomUrl": "",
|
||||
"bottomTexts.profileInGameName": "",
|
||||
"bottomTexts.profileBattlefy": "",
|
||||
"bottomTexts.profileShowDiscordUniqueName": "",
|
||||
"bottomTexts.profileCommissionsOpen": "",
|
||||
"bottomTexts.profileCommissionText": "",
|
||||
"bottomTexts.profileNewProfileEnabled": "",
|
||||
"errors.profileCustomUrlStrangeChar": "Een eigen URL mag geen speciale karakters bevatten",
|
||||
"errors.profileCustomUrlNumbers": "Een eigen URL kan niet alleen uit nummers bestaan",
|
||||
"errors.profileCustomUrlDuplicate": "Deze URL is al in gebruik",
|
||||
"errors.profileSensBothOrNeither": "Bewegingsgevoeligheid kan niet worden ingesteld als er niets voor de R-stick ingevoerd is",
|
||||
"errors.profileInGameName": "",
|
||||
"labels.pronoun": "",
|
||||
"bottomTexts.profilePronouns": "",
|
||||
"errors.profilePronounsBothOrNeither": ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
{
|
||||
"customUrl": "Eigen URL",
|
||||
"customName": "",
|
||||
"ign": "In-game naam",
|
||||
"ign.short": "IGN",
|
||||
"country": "Land",
|
||||
"bio": "Bio",
|
||||
"widget.bio": "",
|
||||
"widget.bio-md": "",
|
||||
"widget.badges-owned": "",
|
||||
|
|
@ -143,24 +139,8 @@
|
|||
"motion": "Beweging",
|
||||
"stick": "Stick",
|
||||
"sens": "Gevoeligheid",
|
||||
"pronoun": "",
|
||||
"usesPronouns": "",
|
||||
"pronounsInfo": "",
|
||||
"weaponPool": "",
|
||||
"discordExplanation": "",
|
||||
"favoriteBadges": "",
|
||||
"battlefy": "",
|
||||
"forms.newProfileEnabled": "",
|
||||
"forms.newProfileEnabled.info": "",
|
||||
"forms.showDiscordUniqueName": "",
|
||||
"forms.showDiscordUniqueName.info": "",
|
||||
"forms.commissionsOpen": "",
|
||||
"forms.commissionsOpen.info": "",
|
||||
"forms.commissionText": "",
|
||||
"forms.commissionText.info": "",
|
||||
"forms.customName.info": "",
|
||||
"forms.country.search.placeholder": "",
|
||||
"forms.favoriteBadges.nonSupporter": "",
|
||||
"results.title": "",
|
||||
"results.placing": "Plaatsing",
|
||||
"results.team": "Team",
|
||||
|
|
@ -175,12 +155,6 @@
|
|||
"results.button.showHighlights": "",
|
||||
"results.button.showAll": "",
|
||||
"forms.errors.maxWeapons": "",
|
||||
"forms.errors.invalidCustomUrl.numbers": "Een eigen URL kan niet alleen uit nummers bestaan",
|
||||
"forms.errors.invalidCustomUrl.strangeCharacter": "Een eigen URL mag geen speciale karakters bevatten",
|
||||
"forms.errors.invalidCustomUrl.duplicate": "Deze URL is al in gebruik",
|
||||
"forms.errors.invalidSens": "Bewegingsgevoeligheid kan niet worden ingesteld als er niets voor de R-stick ingevoerd is",
|
||||
"forms.info.customUrl": "",
|
||||
"forms.info.battlefy": "",
|
||||
"search.info": "",
|
||||
"search.noResults": "",
|
||||
"search.pleaseLogIn.header": "",
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
"errors.atLeastOneOption": "",
|
||||
"errors.duplicateName": "Istnieje już drużyna o tym imieniu",
|
||||
"errors.noOnlySpecialCharacters": "",
|
||||
"labels.weaponPool": "",
|
||||
"labels.weaponPool": "Pula broni",
|
||||
"placeholders.weaponPoolFull": "",
|
||||
"labels.voiceChat": "",
|
||||
"labels.languages": "",
|
||||
|
|
@ -187,5 +187,33 @@
|
|||
"labels.comment": "",
|
||||
"errors.plusAlreadySuggested": "",
|
||||
"errors.plusAlreadyMember": "",
|
||||
"errors.plusCannotSuggest": ""
|
||||
"errors.plusCannotSuggest": "",
|
||||
"labels.profileCustomName": "",
|
||||
"labels.profileCustomUrl": "Niestandardowe URL",
|
||||
"labels.profileInGameName": "Imię In-game",
|
||||
"labels.profileBattlefy": "",
|
||||
"labels.profileMotionSens": "Motion sens",
|
||||
"labels.profileStickSens": "R-stick sens",
|
||||
"labels.profileCountry": "Kraj",
|
||||
"labels.profileFavoriteBadges": "",
|
||||
"labels.profileShowDiscordUniqueName": "",
|
||||
"labels.profileCommissionsOpen": "",
|
||||
"labels.profileCommissionText": "",
|
||||
"labels.profileNewProfileEnabled": "",
|
||||
"bottomTexts.profileCustomName": "",
|
||||
"bottomTexts.profileCustomUrl": "",
|
||||
"bottomTexts.profileInGameName": "",
|
||||
"bottomTexts.profileBattlefy": "",
|
||||
"bottomTexts.profileShowDiscordUniqueName": "",
|
||||
"bottomTexts.profileCommissionsOpen": "",
|
||||
"bottomTexts.profileCommissionText": "",
|
||||
"bottomTexts.profileNewProfileEnabled": "",
|
||||
"errors.profileCustomUrlStrangeChar": "Niestandardowe URL nie może zawierać znaków specjalnych",
|
||||
"errors.profileCustomUrlNumbers": "Niestandardowe URL nie może zawierać tylko liczby",
|
||||
"errors.profileCustomUrlDuplicate": "Te niestandardowe URl jest już przez kogoś zajęte",
|
||||
"errors.profileSensBothOrNeither": "Motion sens nie może być ustawione jeśli R-stick sens nie jest",
|
||||
"errors.profileInGameName": "",
|
||||
"labels.pronoun": "",
|
||||
"bottomTexts.profilePronouns": "",
|
||||
"errors.profilePronounsBothOrNeither": ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
{
|
||||
"customUrl": "Niestandardowe URL",
|
||||
"customName": "",
|
||||
"ign": "Imię In-game",
|
||||
"ign.short": "IGN",
|
||||
"country": "Kraj",
|
||||
"bio": "Opis",
|
||||
"widget.bio": "",
|
||||
"widget.bio-md": "",
|
||||
"widget.badges-owned": "",
|
||||
|
|
@ -143,24 +139,8 @@
|
|||
"motion": "Motion",
|
||||
"stick": "Stick",
|
||||
"sens": "Sens",
|
||||
"pronoun": "",
|
||||
"usesPronouns": "",
|
||||
"pronounsInfo": "",
|
||||
"weaponPool": "Pula broni",
|
||||
"discordExplanation": "Nazwa, profilowe oraz połączone konta brane są z konta Discord. Zobacz <1>FAQ</1> by dowiedzieć się więcej.",
|
||||
"favoriteBadges": "",
|
||||
"battlefy": "",
|
||||
"forms.newProfileEnabled": "",
|
||||
"forms.newProfileEnabled.info": "",
|
||||
"forms.showDiscordUniqueName": "",
|
||||
"forms.showDiscordUniqueName.info": "",
|
||||
"forms.commissionsOpen": "",
|
||||
"forms.commissionsOpen.info": "",
|
||||
"forms.commissionText": "",
|
||||
"forms.commissionText.info": "",
|
||||
"forms.customName.info": "",
|
||||
"forms.country.search.placeholder": "",
|
||||
"forms.favoriteBadges.nonSupporter": "",
|
||||
"results.title": "",
|
||||
"results.placing": "Placing",
|
||||
"results.team": "Drużyna",
|
||||
|
|
@ -175,12 +155,6 @@
|
|||
"results.button.showHighlights": "",
|
||||
"results.button.showAll": "",
|
||||
"forms.errors.maxWeapons": "Maksymalna ilość broni osiągnięta",
|
||||
"forms.errors.invalidCustomUrl.numbers": "Niestandardowe URL nie może zawierać tylko liczby",
|
||||
"forms.errors.invalidCustomUrl.strangeCharacter": "Niestandardowe URL nie może zawierać znaków specjalnych",
|
||||
"forms.errors.invalidCustomUrl.duplicate": "Te niestandardowe URl jest już przez kogoś zajęte",
|
||||
"forms.errors.invalidSens": "Motion sens nie może być ustawione jeśli R-stick sens nie jest",
|
||||
"forms.info.customUrl": "",
|
||||
"forms.info.battlefy": "",
|
||||
"search.info": "",
|
||||
"search.noResults": "Nie znaleziono użytkownika o nazwie '{{query}}'",
|
||||
"search.pleaseLogIn.header": "",
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
"errors.atLeastOneOption": "",
|
||||
"errors.duplicateName": "Já existe um time com esse nome",
|
||||
"errors.noOnlySpecialCharacters": "",
|
||||
"labels.weaponPool": "",
|
||||
"labels.weaponPool": "Seleção de armas",
|
||||
"placeholders.weaponPoolFull": "",
|
||||
"labels.voiceChat": "",
|
||||
"labels.languages": "",
|
||||
|
|
@ -187,5 +187,33 @@
|
|||
"labels.comment": "",
|
||||
"errors.plusAlreadySuggested": "",
|
||||
"errors.plusAlreadyMember": "",
|
||||
"errors.plusCannotSuggest": ""
|
||||
"errors.plusCannotSuggest": "",
|
||||
"labels.profileCustomName": "",
|
||||
"labels.profileCustomUrl": "URL personalizado",
|
||||
"labels.profileInGameName": "Nome no jogo",
|
||||
"labels.profileBattlefy": "",
|
||||
"labels.profileMotionSens": "Sensibilidade do Controle de Movimento (Giroscópio)",
|
||||
"labels.profileStickSens": "Sensibilidade do Analógico Direito",
|
||||
"labels.profileCountry": "País",
|
||||
"labels.profileFavoriteBadges": "",
|
||||
"labels.profileShowDiscordUniqueName": "Mostrar nome de usuário Discord",
|
||||
"labels.profileCommissionsOpen": "Comissões abertas",
|
||||
"labels.profileCommissionText": "Info sobre comissões",
|
||||
"labels.profileNewProfileEnabled": "",
|
||||
"bottomTexts.profileCustomName": "",
|
||||
"bottomTexts.profileCustomUrl": "",
|
||||
"bottomTexts.profileInGameName": "",
|
||||
"bottomTexts.profileBattlefy": "",
|
||||
"bottomTexts.profileShowDiscordUniqueName": "Deixe ativado para mostrar seu nome de usuário único do Discord ({{discordUniqueName}}) publicamente.",
|
||||
"bottomTexts.profileCommissionsOpen": "",
|
||||
"bottomTexts.profileCommissionText": "Preço, vagas abertas ou qualquer outra informação relacionada ao processo de fazer um pedido para você.",
|
||||
"bottomTexts.profileNewProfileEnabled": "",
|
||||
"errors.profileCustomUrlStrangeChar": "URL personalizado não pode conter caracteres especiais",
|
||||
"errors.profileCustomUrlNumbers": "URL personalizado não pode conter apenas números",
|
||||
"errors.profileCustomUrlDuplicate": "Alguém já está usando esse URL personalizado",
|
||||
"errors.profileSensBothOrNeither": "A sensibilidade de Movimento não pode ser definida se a sensibilidade do Analógico Direito não está",
|
||||
"errors.profileInGameName": "",
|
||||
"labels.pronoun": "",
|
||||
"bottomTexts.profilePronouns": "",
|
||||
"errors.profilePronounsBothOrNeither": ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
{
|
||||
"customUrl": "URL personalizado",
|
||||
"customName": "",
|
||||
"ign": "Nome no jogo",
|
||||
"ign.short": "NNJ (IGN)",
|
||||
"country": "País",
|
||||
"bio": "Bio",
|
||||
"widget.bio": "",
|
||||
"widget.bio-md": "",
|
||||
"widget.badges-owned": "",
|
||||
|
|
@ -143,24 +139,8 @@
|
|||
"motion": "Movimento (Giroscópio)",
|
||||
"stick": "Analógico",
|
||||
"sens": "Sens",
|
||||
"pronoun": "",
|
||||
"usesPronouns": "",
|
||||
"pronounsInfo": "",
|
||||
"weaponPool": "Seleção de armas",
|
||||
"discordExplanation": "Nome de usuário, foto de perfil, conta do YouTube, Bluesky e Twitch vêm da sua conta do Discord. Veja o <1>Perguntas Frequentes</1> para mais informações.",
|
||||
"favoriteBadges": "",
|
||||
"battlefy": "",
|
||||
"forms.newProfileEnabled": "",
|
||||
"forms.newProfileEnabled.info": "",
|
||||
"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": "",
|
||||
"forms.country.search.placeholder": "",
|
||||
"forms.favoriteBadges.nonSupporter": "",
|
||||
"results.title": "",
|
||||
"results.placing": "Classificação",
|
||||
"results.team": "Time",
|
||||
|
|
@ -175,12 +155,6 @@
|
|||
"results.button.showHighlights": "",
|
||||
"results.button.showAll": "",
|
||||
"forms.errors.maxWeapons": "Número máximo de armas no perfil atingido.",
|
||||
"forms.errors.invalidCustomUrl.numbers": "URL personalizado não pode conter apenas números",
|
||||
"forms.errors.invalidCustomUrl.strangeCharacter": "URL personalizado não pode conter caracteres especiais",
|
||||
"forms.errors.invalidCustomUrl.duplicate": "Alguém já está usando esse URL personalizado",
|
||||
"forms.errors.invalidSens": "A sensibilidade de Movimento não pode ser definida se a sensibilidade do Analógico Direito não está",
|
||||
"forms.info.customUrl": "",
|
||||
"forms.info.battlefy": "",
|
||||
"search.info": "",
|
||||
"search.noResults": "Nenhum usuário encontrado com o termo '{{query}}'",
|
||||
"search.pleaseLogIn.header": "",
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
"errors.atLeastOneOption": "",
|
||||
"errors.duplicateName": "Уже существует команда с таким названием",
|
||||
"errors.noOnlySpecialCharacters": "",
|
||||
"labels.weaponPool": "",
|
||||
"labels.weaponPool": "Используемое оружие",
|
||||
"placeholders.weaponPoolFull": "",
|
||||
"labels.voiceChat": "",
|
||||
"labels.languages": "",
|
||||
|
|
@ -187,5 +187,33 @@
|
|||
"labels.comment": "",
|
||||
"errors.plusAlreadySuggested": "",
|
||||
"errors.plusAlreadyMember": "",
|
||||
"errors.plusCannotSuggest": ""
|
||||
"errors.plusCannotSuggest": "",
|
||||
"labels.profileCustomName": "Пользовательское имя",
|
||||
"labels.profileCustomUrl": "Пользовательский URL",
|
||||
"labels.profileInGameName": "Внутриигровое имя",
|
||||
"labels.profileBattlefy": "Аккаунт Battlefy",
|
||||
"labels.profileMotionSens": "Чувствительность наклона",
|
||||
"labels.profileStickSens": "Чувствительность стика",
|
||||
"labels.profileCountry": "Страна",
|
||||
"labels.profileFavoriteBadges": "",
|
||||
"labels.profileShowDiscordUniqueName": "Показать пользовательское имя Discord",
|
||||
"labels.profileCommissionsOpen": "Коммишены открыты",
|
||||
"labels.profileCommissionText": "Информация о коммишенах",
|
||||
"labels.profileNewProfileEnabled": "",
|
||||
"bottomTexts.profileCustomName": "Если пользовательское имя отсутствует, то будет использовано ваше имя в Discord: \"{{discordName}}\"",
|
||||
"bottomTexts.profileCustomUrl": "Для меценатов (Supporter и выше) доступна короткая ссылка. Например, вместо sendou.ink/u/sendou может быть использована сссылка snd.ink/sendou.",
|
||||
"bottomTexts.profileInGameName": "",
|
||||
"bottomTexts.profileBattlefy": "Имя на Battlefy может быть использовано для посева и верификации в некоторых турнирах",
|
||||
"bottomTexts.profileShowDiscordUniqueName": "Показывать ваше уникальное Discord имя ({{discordUniqueName}})?",
|
||||
"bottomTexts.profileCommissionsOpen": "",
|
||||
"bottomTexts.profileCommissionText": "Цена, слоты и другая информация о ваших коммишенах",
|
||||
"bottomTexts.profileNewProfileEnabled": "",
|
||||
"errors.profileCustomUrlStrangeChar": "Пользовательский URL не может содержать особые символы",
|
||||
"errors.profileCustomUrlNumbers": "Пользовательский URL не может содержать только цифры",
|
||||
"errors.profileCustomUrlDuplicate": "Кто-то уже использует этот пользовательский URL",
|
||||
"errors.profileSensBothOrNeither": "Чувствительность наклона не может быть указана, если не указана чувствительность стика",
|
||||
"errors.profileInGameName": "",
|
||||
"labels.pronoun": "",
|
||||
"bottomTexts.profilePronouns": "",
|
||||
"errors.profilePronounsBothOrNeither": ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
{
|
||||
"customUrl": "Пользовательский URL",
|
||||
"customName": "Пользовательское имя",
|
||||
"ign": "Внутриигровое имя",
|
||||
"ign.short": "Ник",
|
||||
"country": "Страна",
|
||||
"bio": "О себе",
|
||||
"widget.bio": "",
|
||||
"widget.bio-md": "",
|
||||
"widget.badges-owned": "",
|
||||
|
|
@ -143,24 +139,8 @@
|
|||
"motion": "Наклон",
|
||||
"stick": "Стик",
|
||||
"sens": "Чувствительность",
|
||||
"pronoun": "",
|
||||
"usesPronouns": "",
|
||||
"pronounsInfo": "",
|
||||
"weaponPool": "Используемое оружие",
|
||||
"discordExplanation": "Имя пользователя, аватар, ссылка на аккаунты YouTube, Bluesky и Twitch берутся из вашего аккаунта в Discord. Посмотрите <1>FAQ</1> для дополнительной информации.",
|
||||
"favoriteBadges": "Любимые награды",
|
||||
"battlefy": "Аккаунт Battlefy",
|
||||
"forms.newProfileEnabled": "",
|
||||
"forms.newProfileEnabled.info": "",
|
||||
"forms.showDiscordUniqueName": "Показать пользовательское имя Discord",
|
||||
"forms.showDiscordUniqueName.info": "Показывать ваше уникальное Discord имя ({{discordUniqueName}})?",
|
||||
"forms.commissionsOpen": "Коммишены открыты",
|
||||
"forms.commissionsOpen.info": "",
|
||||
"forms.commissionText": "Информация о коммишенах",
|
||||
"forms.commissionText.info": "Цена, слоты и другая информация о ваших коммишенах",
|
||||
"forms.customName.info": "Если пользовательское имя отсутствует, то будет использовано ваше имя в Discord: \"{{discordName}}\"",
|
||||
"forms.country.search.placeholder": "Искать страны",
|
||||
"forms.favoriteBadges.nonSupporter": "Станьте суппортером sendou.ink, чтобы выбрать какие награды и в каком порядке отображаются на первой странице",
|
||||
"results.title": "Все результаты",
|
||||
"results.placing": "Место",
|
||||
"results.team": "Команда",
|
||||
|
|
@ -175,12 +155,6 @@
|
|||
"results.button.showHighlights": "Показать избранные",
|
||||
"results.button.showAll": "Показать все",
|
||||
"forms.errors.maxWeapons": "Достигнут максимум",
|
||||
"forms.errors.invalidCustomUrl.numbers": "Пользовательский URL не может содержать только цифры",
|
||||
"forms.errors.invalidCustomUrl.strangeCharacter": "Пользовательский URL не может содержать особые символы",
|
||||
"forms.errors.invalidCustomUrl.duplicate": "Кто-то уже использует этот пользовательский URL",
|
||||
"forms.errors.invalidSens": "Чувствительность наклона не может быть указана, если не указана чувствительность стика",
|
||||
"forms.info.customUrl": "Для меценатов (Supporter и выше) доступна короткая ссылка. Например, вместо sendou.ink/u/sendou может быть использована сссылка snd.ink/sendou.",
|
||||
"forms.info.battlefy": "Имя на Battlefy может быть использовано для посева и верификации в некоторых турнирах",
|
||||
"search.info": "Поиск пользователей по имени Discord или Splatoon 3",
|
||||
"search.noResults": "По запросу '{{query}}' пользователь не найден",
|
||||
"search.pleaseLogIn.header": "Пожалуйста, войдите в аккаунт для поиска пользователя",
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
"errors.atLeastOneOption": "",
|
||||
"errors.duplicateName": "该队名已被使用",
|
||||
"errors.noOnlySpecialCharacters": "",
|
||||
"labels.weaponPool": "",
|
||||
"labels.weaponPool": "武器池",
|
||||
"placeholders.weaponPoolFull": "",
|
||||
"labels.voiceChat": "",
|
||||
"labels.languages": "",
|
||||
|
|
@ -187,5 +187,33 @@
|
|||
"labels.comment": "",
|
||||
"errors.plusAlreadySuggested": "",
|
||||
"errors.plusAlreadyMember": "",
|
||||
"errors.plusCannotSuggest": ""
|
||||
"errors.plusCannotSuggest": "",
|
||||
"labels.profileCustomName": "自定义昵称",
|
||||
"labels.profileCustomUrl": "自定义URL",
|
||||
"labels.profileInGameName": "游戏ID",
|
||||
"labels.profileBattlefy": "Battlefy用户名",
|
||||
"labels.profileMotionSens": "体感感度",
|
||||
"labels.profileStickSens": "摇杆感度",
|
||||
"labels.profileCountry": "国家",
|
||||
"labels.profileFavoriteBadges": "",
|
||||
"labels.profileShowDiscordUniqueName": "显示Discord用户名",
|
||||
"labels.profileCommissionsOpen": "开放委托",
|
||||
"labels.profileCommissionText": "委托信息",
|
||||
"labels.profileNewProfileEnabled": "",
|
||||
"bottomTexts.profileCustomName": "如果此栏空着,将会显示您的Discord昵称:\"{{discordName}}\"",
|
||||
"bottomTexts.profileCustomUrl": "",
|
||||
"bottomTexts.profileInGameName": "",
|
||||
"bottomTexts.profileBattlefy": "Battlefy用户名会在部分比赛被用于种子排名和验证",
|
||||
"bottomTexts.profileShowDiscordUniqueName": "是否公开显示您的Discord用户名 ({{discordUniqueName}}) ?",
|
||||
"bottomTexts.profileCommissionsOpen": "",
|
||||
"bottomTexts.profileCommissionText": "你的价格、档期或者其他委托相关的信息",
|
||||
"bottomTexts.profileNewProfileEnabled": "",
|
||||
"errors.profileCustomUrlStrangeChar": "自定义URL不能包含特殊符号",
|
||||
"errors.profileCustomUrlNumbers": "自定义URL不能只包含数字",
|
||||
"errors.profileCustomUrlDuplicate": "这个自定义URL已被使用",
|
||||
"errors.profileSensBothOrNeither": "设置体感感度前请先设置摇杆感度",
|
||||
"errors.profileInGameName": "",
|
||||
"labels.pronoun": "",
|
||||
"bottomTexts.profilePronouns": "",
|
||||
"errors.profilePronounsBothOrNeither": ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
{
|
||||
"customUrl": "自定义URL",
|
||||
"customName": "自定义昵称",
|
||||
"ign": "游戏ID",
|
||||
"ign.short": "游戏ID",
|
||||
"country": "国家",
|
||||
"bio": "个人简介",
|
||||
"widget.bio": "",
|
||||
"widget.bio-md": "",
|
||||
"widget.badges-owned": "",
|
||||
|
|
@ -143,24 +139,8 @@
|
|||
"motion": "体感",
|
||||
"stick": "摇杆",
|
||||
"sens": "感度",
|
||||
"pronoun": "",
|
||||
"usesPronouns": "",
|
||||
"pronounsInfo": "",
|
||||
"weaponPool": "武器池",
|
||||
"discordExplanation": "用户名、头像、Youtube、Bluesky和Twitch账号皆来自您的Discord账号。查看 <1>FAQ</1> 以获得更多相关信息。",
|
||||
"favoriteBadges": "",
|
||||
"battlefy": "Battlefy用户名",
|
||||
"forms.newProfileEnabled": "",
|
||||
"forms.newProfileEnabled.info": "",
|
||||
"forms.showDiscordUniqueName": "显示Discord用户名",
|
||||
"forms.showDiscordUniqueName.info": "是否公开显示您的Discord用户名 ({{discordUniqueName}}) ?",
|
||||
"forms.commissionsOpen": "开放委托",
|
||||
"forms.commissionsOpen.info": "",
|
||||
"forms.commissionText": "委托信息",
|
||||
"forms.commissionText.info": "你的价格、档期或者其他委托相关的信息",
|
||||
"forms.customName.info": "如果此栏空着,将会显示您的Discord昵称:\"{{discordName}}\"",
|
||||
"forms.country.search.placeholder": "",
|
||||
"forms.favoriteBadges.nonSupporter": "",
|
||||
"results.title": "所有成绩",
|
||||
"results.placing": "排名",
|
||||
"results.team": "队伍",
|
||||
|
|
@ -175,12 +155,6 @@
|
|||
"results.button.showHighlights": "显示高光成绩",
|
||||
"results.button.showAll": "显示全部成绩",
|
||||
"forms.errors.maxWeapons": "已达到武器数量上限",
|
||||
"forms.errors.invalidCustomUrl.numbers": "自定义URL不能只包含数字",
|
||||
"forms.errors.invalidCustomUrl.strangeCharacter": "自定义URL不能包含特殊符号",
|
||||
"forms.errors.invalidCustomUrl.duplicate": "这个自定义URL已被使用",
|
||||
"forms.errors.invalidSens": "设置体感感度前请先设置摇杆感度",
|
||||
"forms.info.customUrl": "",
|
||||
"forms.info.battlefy": "Battlefy用户名会在部分比赛被用于种子排名和验证",
|
||||
"search.info": "",
|
||||
"search.noResults": "没有符合 '{{query}}' 的用户",
|
||||
"search.pleaseLogIn.header": "",
|
||||
|
|
|
|||
29
scripts/fix-in-game-names.ts
Normal file
29
scripts/fix-in-game-names.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import "dotenv/config";
|
||||
import { sql } from "~/db/sql";
|
||||
import { IN_GAME_NAME_REGEXP } from "~/features/user-page/user-page-constants";
|
||||
import { logger } from "~/utils/logger";
|
||||
|
||||
const users = sql
|
||||
.prepare('SELECT id, "inGameName" FROM "User" WHERE "inGameName" IS NOT NULL')
|
||||
.all() as { id: number; inGameName: string }[];
|
||||
|
||||
const invalidUsers = users.filter(
|
||||
(user) => !IN_GAME_NAME_REGEXP.test(user.inGameName),
|
||||
);
|
||||
|
||||
logger.info("Invalid in-game names:");
|
||||
for (const user of invalidUsers) {
|
||||
logger.info(`- ${user.inGameName} (id: ${user.id})`);
|
||||
}
|
||||
|
||||
const updateStmt = sql.prepare(
|
||||
'UPDATE "User" SET "inGameName" = NULL WHERE id = @id',
|
||||
);
|
||||
|
||||
for (const user of invalidUsers) {
|
||||
updateStmt.run({ id: user.id });
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`Fixed ${invalidUsers.length} invalid in-game names (out of ${users.length} total)`,
|
||||
);
|
||||
Loading…
Reference in New Issue
Block a user