From b6851288f38652869ead121b3bb7e17da43aa8b4 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Wed, 15 Apr 2026 17:30:18 +0300 Subject: [PATCH] Report weapons initial --- app/components/match-page/MatchActionTab.tsx | 4 + .../match-page/WeaponReporter.module.css | 57 ++++++++ app/components/match-page/WeaponReporter.tsx | 131 ++++++++++++++++++ .../ReportedWeaponRepository.server.ts | 25 ++++ .../actions/q.match.$id.server.ts | 22 ++- .../components/SendouQMatchActionTab.tsx | 58 +++++++- .../components/SendouQMatchTabs.tsx | 61 +++++--- .../loaders/q.match.$id.server.ts | 10 +- app/features/sendouq-match/q-match-schemas.ts | 4 + locales/da/q.json | 2 + locales/de/q.json | 2 + locales/en/q.json | 2 + locales/es-ES/q.json | 2 + locales/es-US/q.json | 2 + locales/fr-CA/q.json | 2 + locales/fr-EU/q.json | 2 + locales/he/q.json | 2 + locales/it/q.json | 2 + locales/ja/q.json | 2 + locales/ko/q.json | 2 + locales/nl/q.json | 2 + locales/pl/q.json | 2 + locales/pt-BR/q.json | 2 + locales/ru/q.json | 2 + locales/zh/q.json | 2 + 25 files changed, 381 insertions(+), 23 deletions(-) create mode 100644 app/components/match-page/WeaponReporter.module.css create mode 100644 app/components/match-page/WeaponReporter.tsx diff --git a/app/components/match-page/MatchActionTab.tsx b/app/components/match-page/MatchActionTab.tsx index 1248c3863..1342c6569 100644 --- a/app/components/match-page/MatchActionTab.tsx +++ b/app/components/match-page/MatchActionTab.tsx @@ -19,6 +19,7 @@ import { type MatchTimelineProps, type TimelineMap, } from "./MatchTimeline"; +import { WeaponReporter, type WeaponReporterProps } from "./WeaponReporter"; interface ActionTabTeam { id: number; @@ -41,6 +42,7 @@ interface MatchActionTabProps { isSubmitting?: boolean; setEnding?: SetEndingData; actionButtons?: React.ReactNode; + weaponReport?: WeaponReporterProps; } export function MatchActionTab({ @@ -53,6 +55,7 @@ export function MatchActionTab({ isSubmitting, setEnding, actionButtons, + weaponReport, }: MatchActionTabProps) { const { t } = useTranslation(["q", "game-misc", "common"]); const [winnerId, setWinnerId] = useState(null); @@ -170,6 +173,7 @@ export function MatchActionTab({ )} + {weaponReport ? : null} ); } diff --git a/app/components/match-page/WeaponReporter.module.css b/app/components/match-page/WeaponReporter.module.css new file mode 100644 index 000000000..962d07648 --- /dev/null +++ b/app/components/match-page/WeaponReporter.module.css @@ -0,0 +1,57 @@ +.root { + display: flex; + flex-direction: column; + align-items: center; + gap: var(--s-4); + background-color: var(--color-bg-higher); + border-radius: 0 0 var(--radius-box) var(--radius-box); + padding: var(--s-4); + margin: var(--s-4) calc(-1 * var(--s-4)) calc(-1 * var(--s-4)); +} + +.pastRow { + display: flex; + align-items: center; + gap: var(--s-2); +} + +.mapRow { + display: flex; + align-items: center; + gap: var(--s-3); +} + +.mapInfo { + display: flex; + flex-direction: column; + align-items: center; + gap: var(--s-1); +} + +.mapLabel { + display: flex; + align-items: center; + gap: var(--s-1); + font-size: var(--font-3xs); + font-weight: var(--weight-semi); + color: var(--color-text-high); +} + +.stageImage { + border-radius: var(--radius-box); +} + +.inputRow { + display: flex; + align-items: flex-end; + gap: var(--s-3); +} + +.weaponSelectContainer { + min-width: 200px; +} + +.unreportedRow { + display: flex; + gap: var(--s-1); +} diff --git a/app/components/match-page/WeaponReporter.tsx b/app/components/match-page/WeaponReporter.tsx new file mode 100644 index 000000000..f863803f1 --- /dev/null +++ b/app/components/match-page/WeaponReporter.tsx @@ -0,0 +1,131 @@ +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { shortStageName } from "~/modules/in-game-lists/stage-ids"; +import type { + MainWeaponId, + ModeShort, + StageId, +} from "~/modules/in-game-lists/types"; +import { abilityImageUrl } from "~/utils/urls"; +import { SendouButton } from "../elements/Button"; +import { Image, ModeImage, StageImage, WeaponImage } from "../Image"; +import { WeaponSelect } from "../WeaponSelect"; +import styles from "./WeaponReporter.module.css"; + +interface WeaponReporterMap { + stageId: StageId; + mode: ModeShort; +} + +export interface WeaponReporterProps { + maps: WeaponReporterMap[]; + pastReported: MainWeaponId[]; + quickSelectWeaponIds?: MainWeaponId[]; + onSubmit: (weaponSplId: MainWeaponId) => void; + onUndo: () => void; + isSubmitting?: boolean; +} + +// xxx: default collapsed, small minimal button to uncollapse +// xxx: on sendouq all weapons report different / component tab..? or not? check usage +export function WeaponReporter({ + maps, + pastReported, + quickSelectWeaponIds, + onSubmit, + onUndo, + isSubmitting, +}: WeaponReporterProps) { + const { t } = useTranslation(["q", "game-misc", "common"]); + const [selectedWeapon, setSelectedWeapon] = useState( + null, + ); + + const inputTargetIndex = pastReported.length; + + const inputTargetMap = maps[inputTargetIndex]; + const unreportedCount = + maps.length - inputTargetIndex - (inputTargetMap ? 1 : 0); + + return ( +
+ {pastReported.length > 0 ? ( +
+ {pastReported.map((weaponId, i) => ( + + ))} + + {t("q:match.weapon.undoWeapon")} + +
+ ) : null} + {inputTargetMap ? ( +
+ +
+
+ +
+ { + if (selectedWeapon === null) return; + onSubmit(selectedWeapon); + setSelectedWeapon(null); + }} + > + {t("common:actions.submit")} + +
+
+ ) : null} + {unreportedCount > 0 ? ( +
+ {Array.from({ length: unreportedCount }, (_, i) => ( + ? + ))} +
+ ) : null} +
+ ); +} + +function MapInfo({ map }: { map: WeaponReporterMap }) { + const { t } = useTranslation(["game-misc"]); + + return ( +
+ +
+ + {shortStageName(t(`game-misc:STAGE_${map.stageId}`))} +
+
+ ); +} diff --git a/app/features/sendouq-match/ReportedWeaponRepository.server.ts b/app/features/sendouq-match/ReportedWeaponRepository.server.ts index 4e886255d..281207cf1 100644 --- a/app/features/sendouq-match/ReportedWeaponRepository.server.ts +++ b/app/features/sendouq-match/ReportedWeaponRepository.server.ts @@ -40,6 +40,31 @@ export async function replaceByMatchId( } } +export async function deleteByUserMapIndex({ + matchId, + userId, + mapIndex, +}: { + matchId: number; + userId: number; + mapIndex: number; +}) { + const groupMatchMap = await db + .selectFrom("GroupMatchMap") + .select("id") + .where("matchId", "=", matchId) + .where("index", "=", mapIndex) + .executeTakeFirst(); + + if (!groupMatchMap) return; + + await db + .deleteFrom("ReportedWeapon") + .where("groupMatchMapId", "=", groupMatchMap.id) + .where("userId", "=", userId) + .execute(); +} + export async function findByMatchId(matchId: number) { const rows = await db .selectFrom("ReportedWeapon") diff --git a/app/features/sendouq-match/actions/q.match.$id.server.ts b/app/features/sendouq-match/actions/q.match.$id.server.ts index 97ab273ba..bd0ab3cf1 100644 --- a/app/features/sendouq-match/actions/q.match.$id.server.ts +++ b/app/features/sendouq-match/actions/q.match.$id.server.ts @@ -135,9 +135,18 @@ export const action = async ({ request, params }: ActionFunctionArgs) => { throw redirect(SENDOUQ_PREPARING_PAGE); } + // xxx: why not REPORT_WEAPON case "REPORT_WEAPONS": { const match = notFoundIfFalsy(await SQMatchRepository.findById(matchId)); - errorToastIfFalsy(match.reportedAt, "Match has not been reported yet"); + + const members = [ + ...match.groupAlpha.members, + ...match.groupBravo.members, + ]; + invariant( + members.some((m) => m.id === user.id), + "User is not a member of any group", + ); const oldReportedWeapons = (await ReportedWeaponRepository.findByMatchId(matchId)) ?? []; @@ -161,6 +170,17 @@ export const action = async ({ request, params }: ActionFunctionArgs) => { break; } + case "UNDO_WEAPON_REPORT": { + notFoundIfFalsy(await SQMatchRepository.findById(matchId)); + + await ReportedWeaponRepository.deleteByUserMapIndex({ + matchId, + userId: user.id, + mapIndex: data.mapIndex, + }); + + break; + } case "ADD_PRIVATE_USER_NOTE": { await PrivateUserNoteRepository.upsert({ authorId: user.id, diff --git a/app/features/sendouq-match/components/SendouQMatchActionTab.tsx b/app/features/sendouq-match/components/SendouQMatchActionTab.tsx index ad3901a4f..a6df614dc 100644 --- a/app/features/sendouq-match/components/SendouQMatchActionTab.tsx +++ b/app/features/sendouq-match/components/SendouQMatchActionTab.tsx @@ -7,8 +7,13 @@ import { FormWithConfirm } from "~/components/FormWithConfirm"; import { MatchActionTab } from "~/components/match-page/MatchActionTab"; import { TAB_KEYS } from "~/components/match-page/MatchTabs"; import { SENDOUQ_BEST_OF } from "~/features/sendouq/q-constants"; +import { useRecentlyReportedWeapons } from "~/features/sendouq/q-hooks"; import { isSetOverByScore } from "~/features/tournament-bracket/tournament-bracket-utils"; -import type { ModeShort, StageId } from "~/modules/in-game-lists/types"; +import type { + MainWeaponId, + ModeShort, + StageId, +} from "~/modules/in-game-lists/types"; import type { SendouQMatchLoaderData } from "../loaders/q.match.$id.server"; import styles from "./SendouQMatchActionTab.module.css"; @@ -16,17 +21,22 @@ export function SendouQMatchActionTab({ data, currentMap, ownTeamId, + ownUserId, reportedCount, }: { data: SendouQMatchLoaderData; currentMap: { stageId: StageId; mode: ModeShort }; ownTeamId: number; + ownUserId: number; reportedCount: number; }) { const { t } = useTranslation(["q", "common"]); const fetcher = useFetcher(); const undoFetcher = useFetcher(); const cancelFetcher = useFetcher(); + const weaponFetcher = useFetcher(); + const { recentlyReportedWeapons, addRecentlyReportedWeapon } = + useRecentlyReportedWeapons(); const alphaScore = data.match.mapList.filter( (m) => m.winnerGroupId === data.match.groupAlpha.id, @@ -131,6 +141,16 @@ export function SendouQMatchActionTab({ const scoreIsNotZero = alphaScore > 0 || bravoScore > 0; + const weaponReportMaps = data.match.mapList + .slice(0, reportedCount + 1) + .map((m) => ({ stageId: m.stageId, mode: m.mode })); + + const weaponPastReported: MainWeaponId[] = data.reportedWeapons + ? data.reportedWeapons + .filter((w) => w.userId === ownUserId) + .map((w) => w.weaponSplId) + : []; + return ( { + addRecentlyReportedWeapon(weaponSplId); + const mapIndex = weaponPastReported.length; + const map = data.match.mapList[mapIndex]; + weaponFetcher.submit( + { + _action: "REPORT_WEAPONS", + weapons: JSON.stringify([ + { + weaponSplId, + userId: ownUserId, + mapIndex, + groupMatchMapId: map.id, + }, + ]), + }, + { method: "post" }, + ); + }, + onUndo: () => { + const mapIndex = weaponPastReported.length - 1; + if (mapIndex < 0) return; + weaponFetcher.submit( + { + _action: "UNDO_WEAPON_REPORT", + mapIndex: String(mapIndex), + }, + { method: "post" }, + ); + }, + }} actionButtons={ <> {data.match.cancelRequestedByUserId ? ( @@ -175,6 +175,8 @@ export function SendouQMatchTabs({ data }: { data: SendouQMatchLoaderData }) { data={data} currentMap={currentMap} ownTeamId={ownTeamId} + // xxx: why not just useUser in SendouQMatchActionTab? + ownUserId={user!.id} reportedCount={reportedCount} /> ) : null} @@ -208,7 +210,7 @@ function ConfirmerTab({ (m) => m.winnerGroupId === data.match.groupBravo.id, ).length, }} - maps={resolveTimelineMaps(data.match)} + maps={resolveTimelineMaps(data.match, data.reportedWeapons)} />
m.winnerGroupId === data.match.groupBravo.id, ).length, }} - maps={resolveTimelineMaps(data.match)} + maps={resolveTimelineMaps(data.match, data.reportedWeapons)} />

@@ -292,22 +294,47 @@ function resolveTimelineTeams(match: MatchData) { }; } -function resolveTimelineMaps(match: MatchData): TimelineMap[] { +function resolveTimelineMaps( + match: MatchData, + reportedWeapons: SendouQMatchLoaderData["reportedWeapons"], +): TimelineMap[] { return match.mapList .filter((m) => m.winnerGroupId !== null) - .map((map) => ({ - stageId: map.stageId, - mode: map.mode, - timestamp: match.createdAt, - winner: - map.winnerGroupId === match.groupAlpha.id - ? ("ALPHA" as const) - : ("BRAVO" as const), - rosters: { - alpha: match.groupAlpha.members, - bravo: match.groupBravo.members, - }, - })); + .map((map) => { + const alphaWeapons = match.groupAlpha.members.map((member) => { + const w = reportedWeapons?.find( + (rw) => rw.groupMatchMapId === map.id && rw.userId === member.id, + ); + return w ? w.weaponSplId : null; + }); + const bravoWeapons = match.groupBravo.members.map((member) => { + const w = reportedWeapons?.find( + (rw) => rw.groupMatchMapId === map.id && rw.userId === member.id, + ); + return w ? w.weaponSplId : null; + }); + + const hasAnyWeapon = + alphaWeapons.some((w) => w !== null) || + bravoWeapons.some((w) => w !== null); + + return { + stageId: map.stageId, + mode: map.mode, + timestamp: match.createdAt, + winner: + map.winnerGroupId === match.groupAlpha.id + ? ("ALPHA" as const) + : ("BRAVO" as const), + rosters: { + alpha: match.groupAlpha.members, + bravo: match.groupBravo.members, + }, + weapons: hasAnyWeapon + ? { alpha: alphaWeapons, bravo: bravoWeapons } + : undefined, + }; + }); } function resolveTimelineSpChanges( diff --git a/app/features/sendouq-match/loaders/q.match.$id.server.ts b/app/features/sendouq-match/loaders/q.match.$id.server.ts index f04aa942c..afef68ca7 100644 --- a/app/features/sendouq-match/loaders/q.match.$id.server.ts +++ b/app/features/sendouq-match/loaders/q.match.$id.server.ts @@ -4,6 +4,7 @@ import { chatAccessible } from "~/features/chat/chat-utils"; import * as RoomLinkRepository from "~/features/chat/RoomLinkRepository.server"; import { SendouQ } from "~/features/sendouq/core/SendouQ.server"; import * as PrivateUserNoteRepository from "~/features/sendouq/PrivateUserNoteRepository.server"; +import * as ReportedWeaponRepository from "~/features/sendouq-match/ReportedWeaponRepository.server"; import * as SQMatchRepository from "~/features/sendouq-match/SQMatchRepository.server"; import * as UserRepository from "~/features/user-page/UserRepository.server"; import { databaseTimestampToDate } from "~/utils/dates"; @@ -27,15 +28,15 @@ export const loader = async ({ params }: LoaderFunctionArgs) => { ...matchUnmapped.groupBravo.members, ].map((m) => m.id); - const [privateNotes, roomLinks, anyUserPrefersNoSplatnet] = await Promise.all( - [ + const [privateNotes, roomLinks, anyUserPrefersNoSplatnet, reportedWeapons] = + await Promise.all([ user ? PrivateUserNoteRepository.byAuthorUserId(user.id, matchUsers) : undefined, RoomLinkRepository.findByUserIds(matchUsers, 3), UserRepository.anyUserPrefersNoSplatnet(matchUsers), - ], - ); + ReportedWeaponRepository.findByMatchId(matchId), + ]); const match = SendouQ.mapMatch(matchUnmapped, user, privateNotes); @@ -43,6 +44,7 @@ export const loader = async ({ params }: LoaderFunctionArgs) => { match, roomLinks, anyUserPrefersNoSplatnet, + reportedWeapons, chatCode: (() => { const isStaff = user?.roles.includes("STAFF") ?? false; const isParticipant = user && matchUsers.includes(user.id); diff --git a/app/features/sendouq-match/q-match-schemas.ts b/app/features/sendouq-match/q-match-schemas.ts index df238128e..d374a96aa 100644 --- a/app/features/sendouq-match/q-match-schemas.ts +++ b/app/features/sendouq-match/q-match-schemas.ts @@ -54,6 +54,10 @@ export const matchSchema = z.union([ z.object({ _action: _action("UNDO_MAP_REPORT"), }), + z.object({ + _action: _action("UNDO_WEAPON_REPORT"), + mapIndex: z.coerce.number().int().nonnegative(), + }), z.object({ _action: _action("REQUEST_CANCEL"), }), diff --git a/locales/da/q.json b/locales/da/q.json index ce702dad8..f5e6fbf4a 100644 --- a/locales/da/q.json +++ b/locales/da/q.json @@ -193,6 +193,8 @@ "match.timeline.teamSp": "", "match.waitingForConfirmation": "", "match.undoReport": "", + "match.weapon.yourWeapon": "", + "match.weapon.undoWeapon": "", "match.confirmScore": "", "match.confirmScore.wrongHint": "", "preparing.joinQ": "", diff --git a/locales/de/q.json b/locales/de/q.json index 0c6c2fc39..4fab155b2 100644 --- a/locales/de/q.json +++ b/locales/de/q.json @@ -193,6 +193,8 @@ "match.timeline.teamSp": "", "match.waitingForConfirmation": "", "match.undoReport": "", + "match.weapon.yourWeapon": "", + "match.weapon.undoWeapon": "", "match.confirmScore": "", "match.confirmScore.wrongHint": "", "preparing.joinQ": "", diff --git a/locales/en/q.json b/locales/en/q.json index 300b4d9dd..dfdce6aa3 100644 --- a/locales/en/q.json +++ b/locales/en/q.json @@ -193,6 +193,8 @@ "match.timeline.teamSp": "Team SP", "match.waitingForConfirmation": "Waiting for the other team to confirm the result", "match.undoReport": "Undo report", + "match.weapon.yourWeapon": "Your weapon", + "match.weapon.undoWeapon": "Undo weapon", "match.confirmScore": "Confirm score", "match.confirmScore.wrongHint": "Wrong score? Ask the other team to undo their report and adjust it.", "preparing.joinQ": "Join the queue", diff --git a/locales/es-ES/q.json b/locales/es-ES/q.json index 1f339302d..aea3a4330 100644 --- a/locales/es-ES/q.json +++ b/locales/es-ES/q.json @@ -193,6 +193,8 @@ "match.timeline.teamSp": "", "match.waitingForConfirmation": "", "match.undoReport": "", + "match.weapon.yourWeapon": "", + "match.weapon.undoWeapon": "", "match.confirmScore": "", "match.confirmScore.wrongHint": "", "preparing.joinQ": "Unirte a la fila", diff --git a/locales/es-US/q.json b/locales/es-US/q.json index 896a99481..c1ae17900 100644 --- a/locales/es-US/q.json +++ b/locales/es-US/q.json @@ -193,6 +193,8 @@ "match.timeline.teamSp": "", "match.waitingForConfirmation": "", "match.undoReport": "", + "match.weapon.yourWeapon": "", + "match.weapon.undoWeapon": "", "match.confirmScore": "", "match.confirmScore.wrongHint": "", "preparing.joinQ": "Unirte a la fila", diff --git a/locales/fr-CA/q.json b/locales/fr-CA/q.json index 1b3ff63e1..b7d837816 100644 --- a/locales/fr-CA/q.json +++ b/locales/fr-CA/q.json @@ -193,6 +193,8 @@ "match.timeline.teamSp": "", "match.waitingForConfirmation": "", "match.undoReport": "", + "match.weapon.yourWeapon": "", + "match.weapon.undoWeapon": "", "match.confirmScore": "", "match.confirmScore.wrongHint": "", "preparing.joinQ": "", diff --git a/locales/fr-EU/q.json b/locales/fr-EU/q.json index 85736a010..f39ff34d2 100644 --- a/locales/fr-EU/q.json +++ b/locales/fr-EU/q.json @@ -193,6 +193,8 @@ "match.timeline.teamSp": "", "match.waitingForConfirmation": "", "match.undoReport": "", + "match.weapon.yourWeapon": "", + "match.weapon.undoWeapon": "", "match.confirmScore": "", "match.confirmScore.wrongHint": "", "preparing.joinQ": "Rejoindre la queue", diff --git a/locales/he/q.json b/locales/he/q.json index f46d04f27..e5778835a 100644 --- a/locales/he/q.json +++ b/locales/he/q.json @@ -193,6 +193,8 @@ "match.timeline.teamSp": "", "match.waitingForConfirmation": "", "match.undoReport": "", + "match.weapon.yourWeapon": "", + "match.weapon.undoWeapon": "", "match.confirmScore": "", "match.confirmScore.wrongHint": "", "preparing.joinQ": "", diff --git a/locales/it/q.json b/locales/it/q.json index bd45aaca5..de883e897 100644 --- a/locales/it/q.json +++ b/locales/it/q.json @@ -193,6 +193,8 @@ "match.timeline.teamSp": "", "match.waitingForConfirmation": "", "match.undoReport": "", + "match.weapon.yourWeapon": "", + "match.weapon.undoWeapon": "", "match.confirmScore": "", "match.confirmScore.wrongHint": "", "preparing.joinQ": "Unisciti alla coda", diff --git a/locales/ja/q.json b/locales/ja/q.json index 36aaed50b..8e45b4828 100644 --- a/locales/ja/q.json +++ b/locales/ja/q.json @@ -193,6 +193,8 @@ "match.timeline.teamSp": "", "match.waitingForConfirmation": "", "match.undoReport": "", + "match.weapon.yourWeapon": "", + "match.weapon.undoWeapon": "", "match.confirmScore": "", "match.confirmScore.wrongHint": "", "preparing.joinQ": "列に入る", diff --git a/locales/ko/q.json b/locales/ko/q.json index 0c6c2fc39..4fab155b2 100644 --- a/locales/ko/q.json +++ b/locales/ko/q.json @@ -193,6 +193,8 @@ "match.timeline.teamSp": "", "match.waitingForConfirmation": "", "match.undoReport": "", + "match.weapon.yourWeapon": "", + "match.weapon.undoWeapon": "", "match.confirmScore": "", "match.confirmScore.wrongHint": "", "preparing.joinQ": "", diff --git a/locales/nl/q.json b/locales/nl/q.json index 0c6c2fc39..4fab155b2 100644 --- a/locales/nl/q.json +++ b/locales/nl/q.json @@ -193,6 +193,8 @@ "match.timeline.teamSp": "", "match.waitingForConfirmation": "", "match.undoReport": "", + "match.weapon.yourWeapon": "", + "match.weapon.undoWeapon": "", "match.confirmScore": "", "match.confirmScore.wrongHint": "", "preparing.joinQ": "", diff --git a/locales/pl/q.json b/locales/pl/q.json index 0c6c2fc39..4fab155b2 100644 --- a/locales/pl/q.json +++ b/locales/pl/q.json @@ -193,6 +193,8 @@ "match.timeline.teamSp": "", "match.waitingForConfirmation": "", "match.undoReport": "", + "match.weapon.yourWeapon": "", + "match.weapon.undoWeapon": "", "match.confirmScore": "", "match.confirmScore.wrongHint": "", "preparing.joinQ": "", diff --git a/locales/pt-BR/q.json b/locales/pt-BR/q.json index fb929ae0f..4a15f6d30 100644 --- a/locales/pt-BR/q.json +++ b/locales/pt-BR/q.json @@ -193,6 +193,8 @@ "match.timeline.teamSp": "", "match.waitingForConfirmation": "", "match.undoReport": "", + "match.weapon.yourWeapon": "", + "match.weapon.undoWeapon": "", "match.confirmScore": "", "match.confirmScore.wrongHint": "", "preparing.joinQ": "Entrar na fila", diff --git a/locales/ru/q.json b/locales/ru/q.json index ce92a0f56..8dccd2fbe 100644 --- a/locales/ru/q.json +++ b/locales/ru/q.json @@ -193,6 +193,8 @@ "match.timeline.teamSp": "", "match.waitingForConfirmation": "", "match.undoReport": "", + "match.weapon.yourWeapon": "", + "match.weapon.undoWeapon": "", "match.confirmScore": "", "match.confirmScore.wrongHint": "", "preparing.joinQ": "Присоединиться к очереди", diff --git a/locales/zh/q.json b/locales/zh/q.json index 6ec495cad..dab27d3dc 100644 --- a/locales/zh/q.json +++ b/locales/zh/q.json @@ -193,6 +193,8 @@ "match.timeline.teamSp": "", "match.waitingForConfirmation": "", "match.undoReport": "", + "match.weapon.yourWeapon": "", + "match.weapon.undoWeapon": "", "match.confirmScore": "", "match.confirmScore.wrongHint": "", "preparing.joinQ": "开始匹配",