From 3aa3347da4aafc50330c8a90d1aec91e1f204f9c Mon Sep 17 00:00:00 2001 From: Luke Policinski Date: Sat, 29 Aug 2026 13:19:55 -0400 Subject: [PATCH 01/23] feature: tournament check-in, free agents, join rules and stats leaderboard --- components/tournament/DateTimePicker.vue | 18 +- .../tournament/TournamentCheckInPanel.vue | 716 ++++++++++++++++++ .../tournament/TournamentCheckInReview.vue | 321 ++++++++ components/tournament/TournamentChip.vue | 33 + .../tournament/TournamentCreateWizard.vue | 71 +- components/tournament/TournamentDetail.vue | 192 ++++- .../tournament/TournamentFreeAgents.vue | 542 +++++++++++++ .../tournament/TournamentInformationForm.vue | 208 ++++- .../tournament/TournamentRegistrationForm.vue | 622 +++++++++++++++ components/tournament/TournamentStats.vue | 388 ++++++++++ graphql/simpleTournamentFields.ts | 35 + graphql/tournamentTeamFields.ts | 25 + i18n/locales/ar_SA.json | 197 ++++- i18n/locales/da_DK.json | 197 ++++- i18n/locales/de_DE.json | 197 ++++- i18n/locales/en.json | 203 ++++- i18n/locales/es_ES.json | 197 ++++- i18n/locales/fr_FR.json | 197 ++++- i18n/locales/it_IT.json | 197 ++++- i18n/locales/ja_JP.json | 197 ++++- i18n/locales/ko_KR.json | 197 ++++- i18n/locales/pl_PL.json | 197 ++++- i18n/locales/pt_BR.json | 197 ++++- i18n/locales/ru_RU.json | 197 ++++- i18n/locales/sv_SE.json | 197 ++++- i18n/locales/tr_TR.json | 197 ++++- i18n/locales/uk_UA.json | 197 ++++- i18n/locales/zh_Hans.json | 197 ++++- i18n/locales/zh_Hant.json | 197 ++++- pages/players/[id].vue | 9 + pages/tournaments/index.vue | 5 + pages/tournaments/manage.vue | 6 + pages/watch/index.vue | 9 + stores/AuthStore.ts | 23 +- utilities/tournamentCheckIn.ts | 86 +++ utilities/tournamentRegistration.ts | 204 +++++ 36 files changed, 6770 insertions(+), 98 deletions(-) create mode 100644 components/tournament/TournamentCheckInPanel.vue create mode 100644 components/tournament/TournamentCheckInReview.vue create mode 100644 components/tournament/TournamentChip.vue create mode 100644 components/tournament/TournamentFreeAgents.vue create mode 100644 components/tournament/TournamentRegistrationForm.vue create mode 100644 components/tournament/TournamentStats.vue create mode 100644 utilities/tournamentCheckIn.ts create mode 100644 utilities/tournamentRegistration.ts diff --git a/components/tournament/DateTimePicker.vue b/components/tournament/DateTimePicker.vue index 96b294c07..1991e27db 100644 --- a/components/tournament/DateTimePicker.vue +++ b/components/tournament/DateTimePicker.vue @@ -11,13 +11,17 @@ import { @@ -52,6 +61,11 @@ export default { required: false, default: false, }, + disabled: { + type: Boolean, + required: false, + default: false, + }, }, emits: ["update:modelValue"], data() { diff --git a/components/tournament/TournamentCheckInPanel.vue b/components/tournament/TournamentCheckInPanel.vue new file mode 100644 index 000000000..b3e57e705 --- /dev/null +++ b/components/tournament/TournamentCheckInPanel.vue @@ -0,0 +1,716 @@ + + + diff --git a/components/tournament/TournamentCheckInReview.vue b/components/tournament/TournamentCheckInReview.vue new file mode 100644 index 000000000..5a3d3149f --- /dev/null +++ b/components/tournament/TournamentCheckInReview.vue @@ -0,0 +1,321 @@ + + + diff --git a/components/tournament/TournamentChip.vue b/components/tournament/TournamentChip.vue new file mode 100644 index 000000000..3eff26ccb --- /dev/null +++ b/components/tournament/TournamentChip.vue @@ -0,0 +1,33 @@ + + + diff --git a/components/tournament/TournamentCreateWizard.vue b/components/tournament/TournamentCreateWizard.vue index f5027ab06..cb3391b29 100644 --- a/components/tournament/TournamentCreateWizard.vue +++ b/components/tournament/TournamentCreateWizard.vue @@ -22,6 +22,7 @@ import CategorySelect from "~/components/tournament/CategorySelect.vue"; import DateTimePicker from "~/components/tournament/DateTimePicker.vue"; import ImageUploadTile from "~/components/ImageUploadTile.vue"; import PrizeRowsEditor from "~/components/tournament/PrizeRowsEditor.vue"; +import TournamentRegistrationForm from "~/components/tournament/TournamentRegistrationForm.vue"; import { HeightMorph, Fold } from "~/components/ui/transitions"; @@ -70,7 +71,7 @@ import { HeightMorph, Fold } from "~/components/ui/transitions"; - +
+ +
+
+ + + +
- + -
+

{{ $t("tournament.prizes.manage_hint") }}

@@ -310,6 +321,13 @@ import { useForm } from "vee-validate"; import { generateMutation } from "~/graphql/graphqlGen"; import { $ } from "~/generated/zeus"; import matchOptionsValidator from "~/utilities/match-options-validator"; +import { EXPECTED_PLAYERS } from "~/utilities/matchmakingPartySize"; +import { + REGISTRATION_FIELD, + canManageRegistrationPasscode, + registrationColumns, + registrationSchemaShape, +} from "~/utilities/tournamentRegistration"; import { effectivePlace, normalizePrize } from "~/utilities/prizes"; import { toTypedSchema } from "~/utilities/vee-validate-zod"; import { toast } from "@/components/ui/toast"; @@ -348,6 +366,7 @@ export default { categories: z.string().array().default([]), auto_start: z.boolean().default(true), negotiated_scheduling: z.boolean().default(false), + ...registrationSchemaShape(this), }, useApplicationSettingsStore().settings, ), @@ -360,6 +379,10 @@ export default { return [ { key: "information", label: this.$t("tournament.wizard.information") }, { key: "location", label: this.$t("tournament.wizard.location") }, + { + key: "registration", + label: this.$t("tournament.wizard.registration"), + }, { key: "match_options", label: this.$t("tournament.wizard.match_options"), @@ -367,6 +390,15 @@ export default { { key: "prizes", label: this.$t("tournament.wizard.prizes") }, ]; }, + // There is no tournament row yet, so the lineup minimum comes from the + // match type the organizer picked (EXPECTED_PLAYERS counts both lineups). + minPlayersPerLineup(): number | null { + const expected = + EXPECTED_PLAYERS[ + this.form.values.type as keyof typeof EXPECTED_PLAYERS + ]; + return expected ? expected / 2 : null; + }, }, methods: { addPrizeRow(prize: string, place: string) { @@ -435,14 +467,23 @@ export default { }); }, async validateStep(step: number): Promise { - // Only the Information step has required fields; the rest have defaults. - if (step !== 0) { + // Every field on the remaining steps has a default; only Information + // (required) and Registration (a range that can contradict itself) can + // be left in a state worth stopping on. + const fields: Record = { + 0: ["name", "start"], + 2: [ + REGISTRATION_FIELD.max_elo, + REGISTRATION_FIELD.check_in_opens_before_minutes, + REGISTRATION_FIELD.check_in_closes_before_minutes, + ], + }; + if (!fields[step]) { return true; } - const results = await Promise.all([ - this.form.validateField("name"), - this.form.validateField("start"), - ]); + const results = await Promise.all( + fields[step].map((field) => this.form.validateField(field)), + ); return results.every((result) => result.valid); }, // Sync on purpose — returning a promise here makes ui/Button flash its @@ -497,12 +538,17 @@ export default { this.form.setFieldValue("match_mode", "admin"); } const form = this.form.values; + const canSetPasscode = canManageRegistrationPasscode(); const { data } = await this.$apollo.mutate({ variables: setupOptionsVariables(form), mutation: generateMutation({ insert_tournaments_one: [ { + // Cast: the registration/check-in columns ship with the API + // migration, and the generated Zeus types only learn about them + // once `yarn codegen` runs against a migrated stack. Drop the + // cast then — it is hiding nothing else. object: { name: form.name, start: form.start, @@ -517,10 +563,13 @@ export default { scheduling_mode: form.negotiated_scheduling ? "negotiated" : "auto", + ...registrationColumns(form, { + includePasscode: canSetPasscode, + }), options: { data: setupOptionsSetMutation(!!form.map_pool_id), }, - }, + } as any, }, { id: true }, ], diff --git a/components/tournament/TournamentDetail.vue b/components/tournament/TournamentDetail.vue index a29579bd7..1c85b46c0 100644 --- a/components/tournament/TournamentDetail.vue +++ b/components/tournament/TournamentDetail.vue @@ -14,6 +14,10 @@ import TournamentNotifications from "~/components/tournament/TournamentNotificat import TournamentResults from "~/components/tournament/TournamentResults.vue"; import TournamentAwardsConfig from "~/components/tournament/TournamentAwardsConfig.vue"; import TournamentAwardsManage from "~/components/tournament/TournamentAwardsManage.vue"; +import TournamentCheckInPanel from "~/components/tournament/TournamentCheckInPanel.vue"; +import TournamentCheckInReview from "~/components/tournament/TournamentCheckInReview.vue"; +import TournamentFreeAgents from "~/components/tournament/TournamentFreeAgents.vue"; +import TournamentStats from "~/components/tournament/TournamentStats.vue"; import Separator from "~/components/ui/separator/Separator.vue"; import PlayerDisplay from "~/components/PlayerDisplay.vue"; import MatchOptionsDisplay from "~/components/match/MatchOptionsDisplay.vue"; @@ -494,6 +498,13 @@ function clearTeamEnterDelay(el: Element) { > {{ $t("tournament.page.match_settings") }} + + {{ $t("tournament.free_agents.title") }} + {{ $t("tournament.standings.title") }} + + {{ $t("tournament.stats.title") }} + + + +
+ + + + + + + + + + | null, + checkInTeams: [] as Array>, tournamentDialog: false, teamSearchQuery: undefined, settingsDialogOpen: false, @@ -1414,6 +1470,107 @@ export default { } }, }, + // Deliberately separate from the main tournament subscription: these + // columns only exist after the registration/check-in migration, and a + // field the server has never heard of fails the whole document. Kept + // apart, a stack that has not migrated yet loses the check-in surfaces + // and nothing else. + tournamentRegistration: { + query: generateSubscription({ + tournaments_by_pk: [ + { + id: $("tournamentId", "uuid!"), + }, + { + id: true, + registration_type: true, + check_in_required: true, + check_in_setting: true, + check_in_opens_before_minutes: true, + check_in_closes_before_minutes: true, + check_in_ends_at: true, + check_in_open: true, + check_in_started: true, + can_review_check_in: true, + missed_check_in_count: true, + }, + ], + // Zeus types for the new columns land with `yarn codegen`; until + // then the selection is asserted rather than inferred. + } as any), + variables: function (this: any) { + return { + tournamentId: this.$route.params.tournamentId, + }; + }, + result: function (this: any, { data }: { data: any }) { + this.tournamentRegistration = data?.tournaments_by_pk ?? null; + }, + }, + // Only ever opened for a tournament that actually requires check-in — + // it duplicates the roster the main subscription already carries, and + // the 99% of tournaments with check-in off should not pay for it. + checkInTeams: { + query: generateSubscription({ + tournament_teams: [ + { + where: { + tournament_id: { + _eq: $("tournamentId", "uuid!"), + }, + }, + order_by: [ + { + created_at: order_by.asc, + }, + ], + }, + { + id: true, + name: true, + short_name: true, + created_at: true, + checked_in_at: true, + owner_steam_id: true, + captain_steam_id: true, + team: { + id: true, + name: true, + short_name: true, + avatar_url: true, + }, + roster: [ + {}, + { + player_steam_id: true, + role: true, + checked_in_at: true, + player: playerFields, + }, + ], + roster_aggregate: [ + {}, + { + aggregate: { + count: true, + }, + }, + ], + }, + ], + } as any), + variables: function (this: any) { + return { + tournamentId: this.$route.params.tournamentId, + }; + }, + skip: function (this: any): boolean { + return this.tournamentRegistration?.check_in_required !== true; + }, + result: function (this: any, { data }: { data: any }) { + this.checkInTeams = data?.tournament_teams ?? []; + }, + }, tournament_teams: { query: typedGql("subscription")({ tournament_teams: [ @@ -1673,6 +1830,9 @@ export default { return "pending"; } if (s === e_tournament_status_enum.Paused) return "paused"; + // Held for an organizer, not running and not cancelled — the warning + // tier is the one that reads as "this needs a decision". + if (s === CHECK_IN_REVIEW_STATUS) return "paused"; if (s === e_tournament_status_enum.Finished) return "finished"; if ( s === e_tournament_status_enum.Cancelled || @@ -1695,10 +1855,18 @@ export default { tabs.push("match-settings"); } + if (this.freeAgentsTabVisible) { + tabs.push("free-agents"); + } + if (this.standingsTabVisible) { tabs.push("standings"); } + if (this.statsTabVisible) { + tabs.push("stats"); + } + if ( this.tournament?.status === e_tournament_status_enum.Live || this.tournament?.status === e_tournament_status_enum.Finished @@ -1727,6 +1895,28 @@ export default { status === e_tournament_status_enum.Finished ); }, + freeAgentsTabVisible() { + const type = this.tournamentRegistration?.registration_type; + return type === "free_agents" || type === "both"; + }, + // The leaderboard has nothing in it until maps have been played, which is + // exactly when standings become meaningful too. + statsTabVisible() { + return this.standingsTabVisible; + }, + // can_review_check_in already answers "is this session allowed to act on + // the hold"; the status check keeps the panel off every other screen. + checkInReviewVisible() { + const tournament = this.tournament as Record | undefined; + return ( + tournament?.status === CHECK_IN_REVIEW_STATUS && + (this.tournamentRegistration?.can_review_check_in === true || + tournament?.is_organizer === true) + ); + }, + myTeamId() { + return (this.myTeam as Record | undefined)?.id ?? null; + }, }, methods: { toggleTeamCollapsed(teamId) { diff --git a/components/tournament/TournamentFreeAgents.vue b/components/tournament/TournamentFreeAgents.vue new file mode 100644 index 000000000..e5c838c33 --- /dev/null +++ b/components/tournament/TournamentFreeAgents.vue @@ -0,0 +1,542 @@ + + + diff --git a/components/tournament/TournamentInformationForm.vue b/components/tournament/TournamentInformationForm.vue index 01dd047f7..22d112115 100644 --- a/components/tournament/TournamentInformationForm.vue +++ b/components/tournament/TournamentInformationForm.vue @@ -7,8 +7,9 @@ import ImageUploadTile from "~/components/ImageUploadTile.vue"; import AddressSearch from "~/components/AddressSearch.vue"; import CategorySelect from "~/components/tournament/CategorySelect.vue"; import DateTimePicker from "~/components/tournament/DateTimePicker.vue"; -import { ExternalLink } from "lucide-vue-next"; +import { ExternalLink, Lock } from "lucide-vue-next"; import SettingsSaveBar from "~/components/settings/SettingsSaveBar.vue"; +import TournamentRegistrationForm from "~/components/tournament/TournamentRegistrationForm.vue"; import { tacticalSectionLabelClasses as sectionLabelClasses, @@ -115,10 +116,20 @@ import { {{ $t("tournament.form.start") }} +

+ + {{ $t("tournament.check_in.frozen") }} +

@@ -162,6 +173,18 @@ import { + + +
import * as z from "zod"; import { useForm } from "vee-validate"; -import { generateMutation } from "~/graphql/graphqlGen"; +import { generateMutation, generateSubscription } from "~/graphql/graphqlGen"; import { $ } from "~/generated/zeus"; +import { + tournamentPasscodeField, + tournamentRegistrationFields, +} from "~/graphql/simpleTournamentFields"; import { toTypedSchema } from "~/utilities/vee-validate-zod"; +import { isTournamentScheduleFrozen } from "~/utilities/tournamentCheckIn"; +import { + canManageRegistrationPasscode, + registrationColumns, + registrationFormValues, + registrationSchemaShape, +} from "~/utilities/tournamentRegistration"; import { toast } from "@/components/ui/toast"; export default { @@ -193,6 +227,13 @@ export default { submitting: false, baseline: null as string | null, isDirty: false, + registrationSettings: null as Record | null, + registrationSeeded: false, + // Selecting a column the session's role cannot see fails the whole + // subscription, so the field is asked for conditionally — and a save from + // a session that could not read it leaves the column alone rather than + // blanking out a passcode it was never shown. + canReadPasscode: canManageRegistrationPasscode(), form: useForm({ keepValuesOnUnmount: true, validationSchema: toTypedSchema( @@ -205,11 +246,51 @@ export default { latitude: z.number().nullable().default(null), longitude: z.number().nullable().default(null), categories: z.string().array().default([]), + ...registrationSchemaShape(this), }), ), }), }; }, + apollo: { + $subscribe: { + tournaments_by_pk: { + // The registration columns are not part of the selection the tournament + // page hands down as `tournament`, and `check_in_started` has to stay + // live: the freeze must engage while an organizer has this form open. + query: function (this: any) { + return generateSubscription({ + tournaments_by_pk: [ + { id: $("tournamentId", "uuid!") }, + { + id: true, + start: true, + ...tournamentRegistrationFields, + ...(this.canReadPasscode ? tournamentPasscodeField : {}), + } as any, + ], + }); + }, + variables: function (this: any) { + return { tournamentId: this.tournament.id }; + }, + result: function (this: any, { data }: { data: any }) { + this.registrationSettings = data?.tournaments_by_pk ?? null; + if (this.baseline === null || !this.isDirty) { + this.populate(); + return; + } + // Dirty already (the organizer started typing before this landed): + // repopulating would throw their edit away, but the registration half + // has never been seeded, so patch just that in — including into the + // dirty baseline, or the untouched panel counts as a change. + if (!this.registrationSeeded) { + this.seedRegistration(); + } + }, + }, + }, + }, watch: { tournament: { immediate: true, @@ -229,6 +310,12 @@ export default { }, }, computed: { + // Only the live subscription carries `check_in_started`; the page's own + // tournament object never selects it, and a missing field would read as + // "not frozen" and quietly drop the lock explanation. + scheduleFrozen(): boolean { + return isTournamentScheduleFrozen(this.registrationSettings); + }, apiDomain() { return useRuntimeConfig().public.apiDomain; }, @@ -266,12 +353,32 @@ export default { categories: (this.tournament.categories ?? []).map( (category: any) => category.category, ), + ...registrationFormValues(this.registrationSettings ?? {}), }); + this.registrationSeeded = this.registrationSettings !== null; this.$nextTick(() => { this.baseline = JSON.stringify(this.form.values); this.isDirty = false; }); }, + // Seeds only the registration half, leaving the organizer's in-flight edits + // to the other sections alone. The baseline is patched in the same shape so + // JSON.stringify's key order still matches — a re-serialised baseline built + // from scratch would compare unequal and pin the form permanently dirty. + seedRegistration() { + const values = registrationFormValues(this.registrationSettings ?? {}); + this.form.setValues(values); + this.registrationSeeded = true; + this.$nextTick(() => { + if (this.baseline === null) { + return; + } + const baseline = JSON.parse(this.baseline); + Object.assign(baseline, values); + this.baseline = JSON.stringify(baseline); + this.isDirty = JSON.stringify(this.form.values) !== this.baseline; + }); + }, discardChanges() { this.populate(); }, @@ -358,29 +465,90 @@ export default { this.submitting = true; try { + const variables: Record = { + name: this.form.values.name, + start: this.form.values.start, + description: this.form.values.description, + homepage: this.form.values.homepage || null, + location: this.form.values.location || null, + latitude: this.form.values.latitude ?? null, + longitude: this.form.values.longitude ?? null, + }; + const set: Record = { + name: $("name", "String!"), + description: $("description", "String"), + homepage: $("homepage", "String"), + location: $("location", "String"), + latitude: $("latitude", "float8"), + longitude: $("longitude", "float8"), + }; + + // `start` is dropped rather than resent unchanged while frozen: the + // trigger rejects the whole statement on any distinct value, and a + // Date rebuilt in JS loses the column's sub-millisecond precision, so + // "the same time" is not guaranteed to compare equal. + if (!this.scheduleFrozen) { + set.start = $("start", "timestamptz!"); + } else { + delete variables.start; + } + + // Omitted entirely until the registration subscription has landed — + // writing the panel's fallback defaults over a tournament whose real + // settings were never loaded would silently switch check-in off. + if (this.registrationSettings) { + const columns = registrationColumns(this.form.values, { + includePasscode: this.canReadPasscode, + }); + Object.assign(variables, columns); + Object.assign(set, { + // Cast: the enum type only enters Zeus's GraphQLVariableType union + // once codegen has run against the migrated schema. + registration_type: $( + "registration_type", + "e_tournament_registration_types_enum!" as any, + ), + min_role: $("min_role", "e_player_roles_enum"), + min_elo: $("min_elo", "Int"), + max_elo: $("max_elo", "Int"), + invite_only: $("invite_only", "Boolean!"), + regions: $("regions", "[String!]!"), + check_in_required: $("check_in_required", "Boolean!"), + check_in_setting: $( + "check_in_setting", + "e_check_in_settings_enum!", + ), + }); + // Frozen along with `start`, and for the same reason. + if (!this.scheduleFrozen) { + Object.assign(set, { + check_in_opens_before_minutes: $( + "check_in_opens_before_minutes", + "Int!", + ), + check_in_closes_before_minutes: $( + "check_in_closes_before_minutes", + "Int!", + ), + }); + } else { + delete variables.check_in_opens_before_minutes; + delete variables.check_in_closes_before_minutes; + } + if (this.canReadPasscode) { + set.registration_passcode = $("registration_passcode", "String"); + } + } + await this.$apollo.mutate({ - variables: { - name: this.form.values.name, - start: this.form.values.start, - description: this.form.values.description, - homepage: this.form.values.homepage || null, - location: this.form.values.location || null, - latitude: this.form.values.latitude ?? null, - longitude: this.form.values.longitude ?? null, - }, + variables, mutation: generateMutation({ update_tournaments_by_pk: [ { pk_columns: { id: this.tournament.id }, - _set: { - name: $("name", "String!"), - start: $("start", "timestamptz!"), - description: $("description", "String"), - homepage: $("homepage", "String"), - location: $("location", "String"), - latitude: $("latitude", "float8"), - longitude: $("longitude", "float8"), - }, + // Cast: the registration columns arrive with the API migration + // and the generated Zeus types only learn them after codegen. + _set: set as any, }, { __typename: true }, ], diff --git a/components/tournament/TournamentRegistrationForm.vue b/components/tournament/TournamentRegistrationForm.vue new file mode 100644 index 000000000..b3a1d8f57 --- /dev/null +++ b/components/tournament/TournamentRegistrationForm.vue @@ -0,0 +1,622 @@ + + + + + diff --git a/components/tournament/TournamentStats.vue b/components/tournament/TournamentStats.vue new file mode 100644 index 000000000..5222ce2ee --- /dev/null +++ b/components/tournament/TournamentStats.vue @@ -0,0 +1,388 @@ + + + diff --git a/graphql/simpleTournamentFields.ts b/graphql/simpleTournamentFields.ts index 597f2d3cb..24769a097 100644 --- a/graphql/simpleTournamentFields.ts +++ b/graphql/simpleTournamentFields.ts @@ -1,6 +1,39 @@ import { order_by, Selector } from "~/generated/zeus"; import { matchOptionsFields } from "./matchOptionsFields"; +/** + * The registration / check-in columns and their computed fields. + * + * Typed as `Record` and merged in rather than written inline: + * these land with the API migration, and until `yarn codegen` runs against a + * migrated stack the generated Zeus `SelectionFunction` maps every key it does + * not recognise to `never`, so an inline entry is a compile error here AND at + * every call site that spreads the selector. Fold them into the literals below + * and delete this once codegen has run. + */ +export const tournamentRegistrationFields: Record = { + registration_type: true, + min_role: true, + min_elo: true, + max_elo: true, + invite_only: true, + regions: true, + check_in_required: true, + check_in_setting: true, + check_in_opens_before_minutes: true, + check_in_closes_before_minutes: true, + check_in_ends_at: true, + // Computed: the one-way latch the schedule freeze reads. + check_in_started: true, + check_in_open: true, +}; + +// Organizer-only select, so it is kept out of the shared selector that guest +// and player surfaces reuse — asking for it there fails the whole query. +export const tournamentPasscodeField: Record = { + registration_passcode: true, +}; + export const simpleTournamentFields = Selector("tournaments")({ id: true, name: true, @@ -104,3 +137,5 @@ export const simpleTournamentFields = Selector("tournaments")({ }, ], }); + +Object.assign(simpleTournamentFields, tournamentRegistrationFields); diff --git a/graphql/tournamentTeamFields.ts b/graphql/tournamentTeamFields.ts index 2569f6fc3..ccc00d578 100644 --- a/graphql/tournamentTeamFields.ts +++ b/graphql/tournamentTeamFields.ts @@ -1,6 +1,30 @@ import { playerFields } from "~/graphql/playerFields"; +/** + * `tournament_teams.checked_in_at` is the ONE check-in signal anything + * downstream reads — nothing outside the feature has to know which + * `check_in_setting` mode produced it. `checked_in` is its computed companion, + * and the roster's own stamp only ever matters in `Players` mode. + * + * Both chunks are spread as untyped objects on purpose: these columns land with + * the API migration, and until `yarn codegen` runs against a migrated stack the + * generated Zeus selection type maps every unrecognised key to `never` — which + * would be a compile error not just here but at every call site that reuses + * this selector. Fold the keys into the literals and delete the casts once + * codegen has run. + */ +const pendingTeamFields = { + checked_in_at: true, + checked_in: true, +} as {}; + +const pendingRosterFields = { + checked_in_at: true, + target_eligible: true, +} as {}; + export default { + ...pendingTeamFields, id: true, name: true, short_name: true, @@ -20,6 +44,7 @@ export default { roster: [ {}, { + ...pendingRosterFields, role: true, player: playerFields, }, diff --git a/i18n/locales/ar_SA.json b/i18n/locales/ar_SA.json index 92a368aa3..e156bcaa9 100644 --- a/i18n/locales/ar_SA.json +++ b/i18n/locales/ar_SA.json @@ -1229,6 +1229,10 @@ }, "system_telemetry": { "title": "قياس الأسطول", + "filter": { + "all": "الأسطول بالكامل", + "self_hosted": "المستضاف ذاتيًا فقط" + }, "no_data": "لم تُبلَّغ أي بيانات بعد", "installs": { "title": "اللوحات", @@ -3967,7 +3971,9 @@ }, "picker": { "search": "ابحث عن خريطة", - "none": "لا توجد خرائط لها رادار" + "none": "لا توجد خرائط لها رادار", + "count_public": "رميات عامة على هذه الخريطة", + "count_private": "غير عامة لكنها ظاهرة لك — رمياتك أو رميات فريقك أو قيد المراجعة. المؤرشفة غير محسوبة." }, "back_to_map": "العودة إلى الخريطة", "empty": { @@ -5946,13 +5952,31 @@ "teams": "فرق", "format": "النظام", "starts": "البداية", - "location": "الموقع" + "location": "الموقع", + "title": "الإحصائيات", + "leaderboard_title": "لوحة متصدري اللاعبين", + "matches": "مباريات", + "rounds": "جولات", + "players": "اللاعبون", + "top_rating": "أعلى تقييم", + "col_team": "فريق", + "col_rating": "التقييم", + "col_adr": "ADR", + "col_kills": "K", + "col_deaths": "D", + "col_assists": "A", + "col_kd": "K/D", + "col_hs": "HS%", + "col_maps": "خرائط", + "empty_title": "لا توجد إحصائيات بعد", + "empty_description": "تمتلئ لوحة المتصدرين مع انتهاء مباريات البطولة." }, "wizard": { "information": "المعلومات", "location": "الموقع", "match_options": "خيارات المباراة", - "prizes": "الجوائز" + "prizes": "الجوائز", + "registration": "التسجيل" }, "banner": { "label": "البانر", @@ -6006,6 +6030,173 @@ "expand": "توسيع", "collapse_all": "طي الكل", "expand_all": "توسيع الكل" + }, + "registration": { + "section": "التسجيل", + "who": { + "label": "من يمكنه التسجيل؟", + "teams": { + "label": "الفرق فقط", + "description": "يسجّل القادة فريقًا موجودًا. الخيار الافتراضي." + }, + "free_agents": { + "label": "اللاعبون الأحرار", + "description": "يسجّل اللاعبون بمفردهم، ويُوزَّعون في فرق متوازنة عند إغلاق التسجيل." + }, + "both": { + "label": "الفرق + اللاعبون الأحرار", + "description": "كلاهما. يُوزَّع اللاعبون الأحرار المتبقّون في فرق إضافية إلى جانب الفرق المسجّلة." + } + }, + "min_role": { + "label": "أدنى دور", + "none": "بدون قيود", + "description": "يُتحقَّق منه لدى اللاعب الذي تتم إضافته، وليس فقط لدى من يضغط — حتى لا يضم القائد لاعبين غير مؤهّلين إلى القائمة." + }, + "rank": { + "label": "نطاق ELO", + "min": "أدنى ELO", + "max": "أعلى ELO", + "description": "اترك أي حقل فارغًا لإلغاء الحد. تركهما فارغين يعني الانفتاح على أي رتبة.", + "invalid_range": "يجب ألا يقل أعلى ELO عن أدنى ELO." + }, + "regions": { + "label": "المناطق المفضّلة", + "description": "أين تُستضاف مباريات هذه البطولة. ليس قيدًا على اللاعبين — لا تملك 5stack بيانات منطقة لكل لاعب.", + "empty": "لا توجد مناطق متاحة حاليًا" + }, + "invite_only": { + "label": "بالدعوة فقط", + "description": "التسجيل مغلق أمام الجميع. يدعو المنظمون الفرق مباشرة، أو يُدخل المشاركون رمز دخول." + }, + "passcode": { + "label": "رمز الدخول", + "optional": "(اختياري)", + "description": "من يملك الرمز يدخل بلا دعوة. للمنظمين فقط." + } + }, + "check_in": { + "section": "تأكيد الحضور", + "required": { + "label": "اشترط تأكيد الحضور", + "description": "تؤكّد الفرق حضورها قبل بناء شجرة البطولة. تُفتح النافذة وفق الجدول، والتأكيد يدوي." + }, + "who": { + "label": "من يؤكّد الحضور؟", + "Captains": { + "label": "قادة الفرق", + "description": "يؤكّد القائد عن الفريق كله. ضغطة واحدة لفريق واحد." + }, + "Players": { + "label": "كل لاعب", + "description": "يؤكّد كل لاعب في القائمة بنفسه. يدخل الفريق فور تأكيد {count} — واللاعبون البدلاء غير مطالبين بذلك." + }, + "Admin": { + "label": "المنظم فقط", + "description": "لا يستطيع اللاعبون تأكيد حضورهم بأنفسهم؛ أنت من يضع الفرق كحاضرة." + } + }, + "opens": { + "label": "تُفتح النافذة" + }, + "closes": { + "label": "تُغلق النافذة" + }, + "minutes_before_start": "دقيقة قبل البداية", + "window_too_short": "يجب أن تبقى النافذة مفتوحة 5 دقائق على الأقل.", + "timeline": { + "opens": "تُفتح النافذة", + "closes": "تُغلق النافذة", + "start": "تبدأ البطولة" + }, + "lock_note": "بمجرد فتح النافذة يُقفل وقت البداية وكلتا الإزاحتين. الفرق تتصرّف بالفعل وفق هذا الجدول، وتحريكه أثناء النافذة سيزيح موعدًا نهائيًا جاريًا.", + "late_registration_note": "الفريق الذي يسجّل أثناء النافذة يُؤكَّد حضوره تلقائيًا — لا يمكن معاقبته على تنبيه لم يُعرض عليه قط.", + "no_show_note": "لا تُحذف الفرق الغائبة أبدًا. الفريق الذي يفوّت تأكيد الحضور يحتفظ بقائمته ولا يدخل الشجرة فحسب — ويمكنك إعادة قبوله في أي وقت قبل انطلاق البطولة.", + "frozen": "بدأ تأكيد الحضور بالفعل، لذا فإن وقت البداية وكلتا الإزاحتين مقفلة. مدّد نافذة تأكيد الحضور بدلًا من ذلك.", + "required_title": "تتطلّب هذه البطولة تأكيد الحضور.", + "required_window": "على فريقك التأكيد بين {opens} و{closes} في اليوم نفسه، وإلا فلن يدخل شجرة البطولة.", + "register_heading": "سجّل فريقك", + "register_hint": "سيصلك إشعار عند فتح تأكيد الحضور، وتذكير قبل إغلاقه.", + "register_cta": "سجّل الفريق", + "chip_required": "تأكيد الحضور مطلوب", + "opens_at_heading": "يُفتح تأكيد الحضور في {time}", + "opens_at_hint": "فريقك مسجّل. سنُعلمك عند فتح النافذة.", + "opens_in": "يُفتح خلال", + "closes_in": "يُغلق خلال", + "starts_in": "تبدأ خلال", + "chip_action_needed": "مطلوب إجراء", + "chip_open": "تأكيد الحضور مفتوح", + "observer_heading": "تأكيد الحضور مفتوح", + "observer_hint": "تؤكّد الفرق حضورها قبل بناء شجرة البطولة. أي فريق لا يؤكّد حضوره بحلول {time} يُبقي البطولة معلّقة بانتظار مراجعتك.", + "confirm_heading": "أكّد حضور فريقك", + "confirm_hint": "الفرق التي لا تؤكّد حضورها بحلول {time} لا تدخل شجرة البطولة.", + "players_progress": "أكّد {checked} من أصل {required}", + "players_heading": "على كل لاعب تأكيد حضوره", + "players_hint": "يدخل فريقك فور تأكيد {count} لاعبين. واللاعبون البدلاء غير مطالبين بذلك.", + "admin_heading": "ضع الفرق كحاضرة", + "admin_hint": "لا يستطيع اللاعبون تأكيد حضورهم بأنفسهم في هذه البطولة. ضع كل فريق كحاضر قبل إغلاق النافذة.", + "check_in": "أكّد الحضور", + "mark_present": "ضع كحاضر", + "checked_in": "تم تأكيد الحضور", + "checked_in_at": "تم تأكيد الحضور · {time}", + "waiting": "بالانتظار", + "not_needed": "غير مطلوب", + "role_captain": "قائد", + "role_starter": "أساسي", + "role_sub": "بديل", + "field_status": "حالة المشاركين", + "field_count": "أكّد {checked} من أصل {total}", + "done_heading": "أنتم في البطولة. تُبنى الشجرة في {time}.", + "done_hint": "تم التأكيد عن {team} في {time}.", + "chip_removed": "أُزيل", + "missed_heading": "فوّت فريقك تأكيد الحضور", + "missed_hint": "أُغلق تأكيد الحضور في {time} وتُبنى الشجرة بدون فريقك.", + "missed_contact": "تواصل مع أحد المنظمين إن كنت ترى أن هذا خطأ — بإمكانه إعادة قبول أي فريق في أي وقت قبل انطلاق البطولة.", + "failed": "فشل تأكيد الحضور" + }, + "check_in_review": { + "heading": "فوّتت {count} فرق تأكيد الحضور", + "subheading": "البطولة معلّقة. لم يُبنَ شيء ولم يُحذف شيء — كل فريق يحتفظ بقائمته كاملة.", + "chip_held_at": "معلّقة · تُستأنف تلقائيًا {time}", + "chip_held": "معلّقة", + "roster_size": "{count} لاعبين", + "partial_roster": "أكّد {checked} من أصل {required} · ينقص {short}", + "registered": "مسجّل", + "chip_no_check_in": "بلا تأكيد حضور", + "chip_incomplete": "غير مكتمل", + "readmit": "أعد القبول", + "readmit_failed": "تعذّر إعادة قبول الفريق", + "continue": "تابع بدونهم", + "continue_failed": "تعذّر متابعة البطولة", + "extend": "مدّد تأكيد الحضور", + "extend_by": "+{minutes} دقيقة", + "extend_failed": "تعذّر تمديد تأكيد الحضور", + "extend_hint": "التمديد يعيد فتح التسجيل ويُعيد إشعار الفرق الغائبة ({count}).", + "auto_continue": "إن لم يتحرّك أحد، تتابع البطولة من تلقاء نفسها في {time} بمن أكّد حضوره. البطولة المعلّقة لا تموت بصمت لأن أحد المنظمين كان نائمًا.", + "auto_continue_no_time": "إن لم يتحرّك أحد، تتابع البطولة من تلقاء نفسها في وقت بدايتها بمن أكّد حضوره.", + "reseed_warning": "إعادة قبول فريق تعيد بناء الشجرة. هذا آمن قبل الانطلاق، لكنه يخلط ترتيب التصنيف." + }, + "free_agents": { + "title": "اللاعبون الأحرار", + "signed_up": "سجّل {count}", + "filter_all": "الكل", + "filter_drafted": "المختارون", + "filter_waitlist": "قائمة الانتظار", + "sign_up": "سجّل", + "leave": "غادر المجموعة", + "status_drafted": "مختار", + "status_waitlisted": "قائمة الانتظار", + "status_registered": "مسجّل", + "empty_title": "لا يوجد لاعبون أحرار بعد", + "empty_description": "يظهر هنا اللاعبون الذين يسجّلون بمفردهم، ويُوزَّعون في فرق متوازنة عند إغلاق التسجيل.", + "waitlist_explainer": "{name} صاحب أعلى تقييم ومع ذلك ما زال في قائمة الانتظار — فقد سجّل بعد امتلاء المقاعد. وقت التسجيل هو ما يحدّد من يحصل على مقعد، أما ELO فيحدّد الفريق فقط. هذه هي القاعدة تعمل كما ينبغي، وليست خللًا.", + "organizer": "منظم", + "draft_summary": "{agents} لاعبًا حرًّا في {teams} فرق من {size} لاعبين، و{waitlisted} في قائمة الانتظار.", + "regenerate": "أعد توزيع الفرق", + "regenerated": "تم تكوين {count} فرق", + "regenerate_failed": "تعذّر إعادة توزيع الفرق", + "join_failed": "تعذّر الانضمام إلى مجموعة اللاعبين الأحرار", + "leave_failed": "تعذّرت مغادرة مجموعة اللاعبين الأحرار" } }, "team": { diff --git a/i18n/locales/da_DK.json b/i18n/locales/da_DK.json index 83da6da55..dd9e7e58b 100644 --- a/i18n/locales/da_DK.json +++ b/i18n/locales/da_DK.json @@ -1229,6 +1229,10 @@ }, "system_telemetry": { "title": "Flådetelemetri", + "filter": { + "all": "Hele flåden", + "self_hosted": "Kun selv-hostede" + }, "no_data": "Ingen data rapporteret endnu", "installs": { "title": "Paneler", @@ -3967,7 +3971,9 @@ }, "picker": { "search": "Find en bane", - "none": "Ingen baner med radar" + "none": "Ingen baner med radar", + "count_public": "Offentlige lineups på denne bane", + "count_private": "Ikke offentlige, men synlige for dig — dine, dit holds eller afventer gennemsyn. Arkiverede tælles ikke med." }, "back_to_map": "Tilbage til banen", "empty": { @@ -5946,13 +5952,31 @@ "teams": "Hold", "format": "Format", "starts": "Starter", - "location": "Lokation" + "location": "Lokation", + "title": "Statistik", + "leaderboard_title": "Spillerrangliste", + "matches": "Kampe", + "rounds": "Runder", + "players": "Spillere", + "top_rating": "Bedste rating", + "col_team": "Hold", + "col_rating": "Rating", + "col_adr": "ADR", + "col_kills": "K", + "col_deaths": "D", + "col_assists": "A", + "col_kd": "K/D", + "col_hs": "HS%", + "col_maps": "Maps", + "empty_title": "Ingen statistik endnu", + "empty_description": "Ranglisten fyldes ud, efterhånden som turneringens kampe bliver spillet færdig." }, "wizard": { "information": "Information", "location": "Lokation", "match_options": "Kampindstillinger", - "prizes": "Præmier" + "prizes": "Præmier", + "registration": "Tilmelding" }, "banner": { "label": "Banner", @@ -6006,6 +6030,173 @@ "expand": "Fold ud", "collapse_all": "Fold alt sammen", "expand_all": "Fold alt ud" + }, + "registration": { + "section": "Tilmelding", + "who": { + "label": "Hvem kan tilmelde sig?", + "teams": { + "label": "Kun hold", + "description": "Anførere tilmelder et eksisterende hold. Standardvalget." + }, + "free_agents": { + "label": "Free agents", + "description": "Spillere tilmelder sig alene og bliver draftet ind i balancerede hold, når tilmeldingen lukker." + }, + "both": { + "label": "Hold + free agents", + "description": "Begge dele. Overskydende free agents bliver draftet ind i ekstra hold ved siden af de tilmeldte." + } + }, + "min_role": { + "label": "Laveste rolle", + "none": "Ingen begrænsning", + "description": "Tjekkes på den spiller, der bliver tilføjet, ikke kun på den, der klikker — så en anfører ikke kan sætte spillere på truppen, der ikke må deltage." + }, + "rank": { + "label": "ELO-interval", + "min": "ELO min.", + "max": "ELO maks.", + "description": "Lad et af felterne stå tomt for ingen grænse. Begge tomme betyder åbent for alle ranks.", + "invalid_range": "ELO maks. skal mindst være lig med ELO min." + }, + "regions": { + "label": "Foretrukne regioner", + "description": "Hvor kampene i denne turnering hostes. Det er ikke et spillerfilter — 5stack har ingen regionsdata pr. spiller.", + "empty": "Ingen regioner er tilgængelige lige nu." + }, + "invite_only": { + "label": "Kun med invitation", + "description": "Tilmeldingen er lukket for offentligheden. Arrangørerne inviterer hold direkte, eller deltagerne angiver en adgangskode." + }, + "passcode": { + "label": "Adgangskode", + "optional": "(valgfri)", + "description": "Alle med koden kan komme ind uden en invitation. Kun arrangører." + } + }, + "check_in": { + "section": "Check-in", + "required": { + "label": "Kræv check-in", + "description": "Holdene bekræfter, at de deltager, før bracketen sættes op. Vinduet åbner efter tidsplanen; bekræftelsen er manuel." + }, + "who": { + "label": "Hvem checker ind?", + "Captains": { + "label": "Anførere", + "description": "Anføreren bekræfter for hele holdet. Ét klik, ét hold." + }, + "Players": { + "label": "Hver spiller", + "description": "Hver spiller på truppen bekræfter selv. Holdet er med, så snart {count} har checket ind — reserver behøver ikke." + }, + "Admin": { + "label": "Kun arrangøren", + "description": "Spillerne kan ikke checke sig selv ind; du markerer holdene som til stede." + } + }, + "opens": { + "label": "Vinduet åbner" + }, + "closes": { + "label": "Vinduet lukker" + }, + "minutes_before_start": "minutter før start", + "window_too_short": "Vinduet skal være åbent i mindst 5 minutter.", + "timeline": { + "opens": "Vinduet åbner", + "closes": "Vinduet lukker", + "start": "Turneringen starter" + }, + "lock_note": "Så snart vinduet åbner, låses starttidspunktet og begge forskydninger. Holdene handler allerede efter denne tidsplan, så at flytte den midt i vinduet ville rykke en deadline, der er i gang.", + "late_registration_note": "Et hold, der tilmelder sig i løbet af vinduet, bliver checket ind automatisk — det kan ikke straffes for en besked, det aldrig fik vist.", + "no_show_note": "Udeblevne hold bliver aldrig slettet. Et hold, der misser check-in, beholder sin trup og kommer bare ikke i bracketen — du kan genindsætte det når som helst, før turneringen går i gang.", + "frozen": "Check-in er allerede åbnet, så starttidspunktet og begge forskydninger er låst. Forlæng check-in-vinduet i stedet.", + "required_title": "Denne turnering kræver check-in.", + "required_window": "Dit hold skal bekræfte mellem {opens} og {closes} på dagen, ellers kommer det ikke i bracketen.", + "register_heading": "Tilmeld dit hold", + "register_hint": "Du får en besked, når check-in åbner, og en påmindelse, før det lukker.", + "register_cta": "Tilmeld hold", + "chip_required": "check-in påkrævet", + "opens_at_heading": "Check-in åbner kl. {time}", + "opens_at_hint": "Dit hold er tilmeldt. Vi giver besked, når vinduet åbner.", + "opens_in": "åbner om", + "closes_in": "lukker om", + "starts_in": "starter om", + "chip_action_needed": "handling påkrævet", + "chip_open": "check-in åbent", + "observer_heading": "Check-in er åbent", + "observer_hint": "Holdene bekræfter, at de deltager, før bracketen sættes op. Ethvert hold, der ikke har checket ind kl. {time}, sætter turneringen på hold til din gennemgang.", + "confirm_heading": "Bekræft at dit hold deltager", + "confirm_hint": "Hold, der ikke har checket ind kl. {time}, kommer ikke i bracketen.", + "players_progress": "{checked} ud af {required} har checket ind", + "players_heading": "Alle spillere skal checke ind", + "players_hint": "Dit hold er med, så snart {count} spillere bekræfter. Reserver behøver ikke.", + "admin_heading": "Markér hold som til stede", + "admin_hint": "Spillerne kan ikke checke sig selv ind i denne turnering. Markér hvert hold som til stede, før vinduet lukker.", + "check_in": "Check ind", + "mark_present": "Markér til stede", + "checked_in": "checket ind", + "checked_in_at": "checket ind · {time}", + "waiting": "venter", + "not_needed": "ikke nødvendigt", + "role_captain": "anfører", + "role_starter": "starter", + "role_sub": "reserve", + "field_status": "Deltagerstatus", + "field_count": "{checked} ud af {total} har checket ind", + "done_heading": "I er med. Bracketen sættes op kl. {time}.", + "done_hint": "Bekræftet for {team} kl. {time}.", + "chip_removed": "fjernet", + "missed_heading": "Dit hold missede check-in", + "missed_hint": "Check-in lukkede kl. {time}, og bracketen sættes op uden dit hold.", + "missed_contact": "Kontakt en arrangør, hvis du mener, det er forkert — de kan genindsætte et hold når som helst, før turneringen går i gang.", + "failed": "Check-in mislykkedes" + }, + "check_in_review": { + "heading": "{count} hold missede check-in", + "subheading": "Turneringen er sat på hold. Intet er sat op, og intet er slettet — hvert hold beholder hele sin trup.", + "chip_held_at": "på hold · fortsætter {time}", + "chip_held": "på hold", + "roster_size": "{count} spillere", + "partial_roster": "{checked} ud af {required} har checket ind · mangler {short}", + "registered": "tilmeldt", + "chip_no_check_in": "intet check-in", + "chip_incomplete": "ufuldstændigt", + "readmit": "Genindsæt", + "readmit_failed": "Holdet kunne ikke genindsættes", + "continue": "Fortsæt uden dem", + "continue_failed": "Turneringen kunne ikke fortsætte", + "extend": "Forlæng check-in", + "extend_by": "+{minutes} minutter", + "extend_failed": "Check-in kunne ikke forlænges", + "extend_hint": "En forlængelse genåbner tilmeldingen og giver de {count} manglende hold besked igen.", + "auto_continue": "Hvis ingen gør noget, fortsætter turneringen af sig selv kl. {time} med dem, der checkede ind. En turnering på hold kan aldrig dø stille, fordi en arrangør sov.", + "auto_continue_no_time": "Hvis ingen gør noget, fortsætter turneringen af sig selv på sit starttidspunkt med dem, der checkede ind.", + "reseed_warning": "Genindsætter du et hold, sættes bracketen op igen. Det er sikkert før start, men det blander seedingen." + }, + "free_agents": { + "title": "Free agents", + "signed_up": "{count} tilmeldt", + "filter_all": "Alle", + "filter_drafted": "Draftet", + "filter_waitlist": "Venteliste", + "sign_up": "Tilmeld dig", + "leave": "Forlad puljen", + "status_drafted": "draftet", + "status_waitlisted": "venteliste", + "status_registered": "tilmeldt", + "empty_title": "Ingen free agents endnu", + "empty_description": "Spillere, der tilmelder sig alene, dukker op her og bliver draftet ind i balancerede hold, når tilmeldingen lukker.", + "waitlist_explainer": "{name} har den højeste rating og står stadig på venteliste — tilmeldingen kom, efter pladserne var fyldt. Tilmeldingstidspunktet afgør, hvem der får en plads; ELO afgør kun hvilket hold. Det er reglen, der virker, ikke en fejl.", + "organizer": "Arrangør", + "draft_summary": "{agents} free agents → {teams} hold à {size}, {waitlisted} på venteliste.", + "regenerate": "Draft holdene igen", + "regenerated": "{count} hold draftet", + "regenerate_failed": "Holdene kunne ikke draftes igen", + "join_failed": "Kunne ikke tilmelde sig free agent-puljen", + "leave_failed": "Kunne ikke forlade free agent-puljen" } }, "team": { diff --git a/i18n/locales/de_DE.json b/i18n/locales/de_DE.json index 2c89674cf..0fb11e578 100644 --- a/i18n/locales/de_DE.json +++ b/i18n/locales/de_DE.json @@ -1229,6 +1229,10 @@ }, "system_telemetry": { "title": "Flotten-Telemetrie", + "filter": { + "all": "Gesamte Flotte", + "self_hosted": "Nur selbst gehostete" + }, "no_data": "Noch keine Daten gemeldet", "installs": { "title": "Panels", @@ -3967,7 +3971,9 @@ }, "picker": { "search": "Map suchen", - "none": "Keine Maps mit Radar" + "none": "Keine Maps mit Radar", + "count_public": "Öffentliche Lineups auf dieser Map", + "count_private": "Nicht öffentlich, aber für dich sichtbar — deine, die deines Teams oder in Prüfung. Archivierte zählen nicht mit." }, "back_to_map": "Zurück zur Map", "empty": { @@ -5946,13 +5952,31 @@ "teams": "Teams", "format": "Format", "starts": "Start", - "location": "Ort" + "location": "Ort", + "title": "Statistiken", + "leaderboard_title": "Spieler-Rangliste", + "matches": "Matches", + "rounds": "Runden", + "players": "Spieler", + "top_rating": "Bestes Rating", + "col_team": "Team", + "col_rating": "Rating", + "col_adr": "ADR", + "col_kills": "K", + "col_deaths": "D", + "col_assists": "A", + "col_kd": "K/D", + "col_hs": "HS%", + "col_maps": "Maps", + "empty_title": "Noch keine Statistiken", + "empty_description": "Die Rangliste füllt sich, sobald Turniermatches beendet sind." }, "wizard": { "information": "Informationen", "location": "Ort", "match_options": "Match-Optionen", - "prizes": "Preise" + "prizes": "Preise", + "registration": "Anmeldung" }, "banner": { "label": "Banner", @@ -6006,6 +6030,173 @@ "expand": "Ausklappen", "collapse_all": "Alle einklappen", "expand_all": "Alle ausklappen" + }, + "registration": { + "section": "Anmeldung", + "who": { + "label": "Wer darf sich anmelden?", + "teams": { + "label": "Nur Teams", + "description": "Captains melden ein bestehendes Team an. Der Standard." + }, + "free_agents": { + "label": "Free Agents", + "description": "Spieler melden sich allein an und werden bei Anmeldeschluss in ausgeglichene Teams gedraftet." + }, + "both": { + "label": "Teams + Free Agents", + "description": "Beides. Übrige Free Agents werden zusätzlich zu den angemeldeten Teams in weitere Teams gedraftet." + } + }, + "min_role": { + "label": "Mindestrolle", + "none": "Keine Einschränkung", + "description": "Wird für den hinzugefügten Spieler geprüft, nicht nur für den Klickenden — so kann ein Captain keine unberechtigten Spieler ins Roster holen." + }, + "rank": { + "label": "Rangbereich", + "min": "Min. ELO", + "max": "Max. ELO", + "description": "Leer lassen für keine Grenze. Beides leer heißt: offen für jeden Rang.", + "invalid_range": "Max. ELO muss mindestens der Min. ELO entsprechen." + }, + "regions": { + "label": "Bevorzugte Regionen", + "description": "Wo die Matches dieses Turniers gehostet werden. Keine Spielersperre — 5stack hat keine Regionsdaten pro Spieler.", + "empty": "Aktuell sind keine Regionen verfügbar." + }, + "invite_only": { + "label": "Nur mit Einladung", + "description": "Die Anmeldung ist nicht öffentlich. Veranstalter laden Teams direkt ein, oder Teilnehmer geben einen Zugangscode an." + }, + "passcode": { + "label": "Zugangscode", + "optional": "(optional)", + "description": "Wer den Code hat, kommt ohne Einladung rein. Nur für Veranstalter." + } + }, + "check_in": { + "section": "Check-in", + "required": { + "label": "Check-in verlangen", + "description": "Teams bestätigen ihre Teilnahme, bevor das Bracket gesetzt wird. Das Fenster öffnet nach Zeitplan; das Bestätigen ist manuell." + }, + "who": { + "label": "Wer checkt ein?", + "Captains": { + "label": "Captains", + "description": "Der Captain bestätigt für das ganze Team. Ein Klick, ein Team." + }, + "Players": { + "label": "Jeder Spieler", + "description": "Jeder Spieler im Roster bestätigt einzeln. Das Team ist dabei, sobald {count} eingecheckt haben — Ersatzspieler müssen nicht." + }, + "Admin": { + "label": "Nur Veranstalter", + "description": "Spieler können sich nicht selbst einchecken; du markierst Teams als anwesend." + } + }, + "opens": { + "label": "Fenster öffnet" + }, + "closes": { + "label": "Fenster schließt" + }, + "minutes_before_start": "Minuten vor Start", + "window_too_short": "Das Fenster muss mindestens 5 Minuten offen bleiben.", + "timeline": { + "opens": "Fenster öffnet", + "closes": "Fenster schließt", + "start": "Turnier startet" + }, + "lock_note": "Sobald das Fenster öffnet, sind Startzeit und beide Offsets gesperrt. Teams richten sich bereits nach diesem Zeitplan — eine Änderung mitten im Fenster würde eine laufende Frist verschieben.", + "late_registration_note": "Ein Team, das sich während des Fensters anmeldet, ist automatisch eingecheckt — es kann nicht für eine Aufforderung bestraft werden, die es nie gesehen hat.", + "no_show_note": "No-Shows werden nie gelöscht. Ein Team, das den Check-in verpasst, behält sein Roster und wird nur nicht gesetzt — du kannst es jederzeit vor dem Turnierstart wieder zulassen.", + "frozen": "Der Check-in läuft bereits, daher sind Startzeit und beide Offsets gesperrt. Verlängere stattdessen das Check-in-Fenster.", + "required_title": "Dieses Turnier verlangt einen Check-in.", + "required_window": "Dein Team muss am Turniertag zwischen {opens} und {closes} bestätigen, sonst kommt es nicht ins Bracket.", + "register_heading": "Team anmelden", + "register_hint": "Du bekommst eine Benachrichtigung, wenn der Check-in öffnet, und eine Erinnerung, bevor er schließt.", + "register_cta": "Team anmelden", + "chip_required": "Check-in nötig", + "opens_at_heading": "Check-in öffnet um {time}", + "opens_at_hint": "Dein Team ist angemeldet. Wir melden uns, wenn das Fenster öffnet.", + "opens_in": "öffnet in", + "closes_in": "schließt in", + "starts_in": "startet in", + "chip_action_needed": "Aktion nötig", + "chip_open": "Check-in offen", + "observer_heading": "Der Check-in ist offen", + "observer_hint": "Teams bestätigen ihre Teilnahme, bevor das Bracket gesetzt wird. Jedes Team, das bis {time} nicht eingecheckt hat, hält das Turnier zur Prüfung an.", + "confirm_heading": "Bestätige die Teilnahme deines Teams", + "confirm_hint": "Teams, die bis {time} nicht eingecheckt haben, kommen nicht ins Bracket.", + "players_progress": "{checked} von {required} eingecheckt", + "players_heading": "Jeder Spieler muss einchecken", + "players_hint": "Dein Team ist dabei, sobald {count} Spieler bestätigen. Ersatzspieler müssen nicht.", + "admin_heading": "Teams als anwesend markieren", + "admin_hint": "Spieler können sich in diesem Turnier nicht selbst einchecken. Markiere jedes Team als anwesend, bevor das Fenster schließt.", + "check_in": "Einchecken", + "mark_present": "Als anwesend markieren", + "checked_in": "eingecheckt", + "checked_in_at": "eingecheckt · {time}", + "waiting": "wartet", + "not_needed": "nicht nötig", + "role_captain": "Captain", + "role_starter": "Stammspieler", + "role_sub": "Ersatz", + "field_status": "Teilnehmerstatus", + "field_count": "{checked} von {total} eingecheckt", + "done_heading": "Ihr seid dabei. Das Bracket wird um {time} gesetzt.", + "done_hint": "Für {team} um {time} bestätigt.", + "chip_removed": "entfernt", + "missed_heading": "Dein Team hat den Check-in verpasst", + "missed_hint": "Der Check-in schloss um {time} und das Bracket wird ohne dein Team gesetzt.", + "missed_contact": "Melde dich bei einem Veranstalter, wenn das falsch ist — er kann ein Team jederzeit vor dem Turnierstart wieder zulassen.", + "failed": "Check-in fehlgeschlagen" + }, + "check_in_review": { + "heading": "{count} Teams haben den Check-in verpasst", + "subheading": "Das Turnier ist angehalten. Es wurde nichts gesetzt und nichts gelöscht — jedes Team behält sein volles Roster.", + "chip_held_at": "angehalten · läuft {time} weiter", + "chip_held": "angehalten", + "roster_size": "{count} Spieler", + "partial_roster": "{checked} von {required} eingecheckt · {short} fehlen", + "registered": "angemeldet", + "chip_no_check_in": "kein Check-in", + "chip_incomplete": "unvollständig", + "readmit": "Wieder zulassen", + "readmit_failed": "Team konnte nicht wieder zugelassen werden", + "continue": "Ohne sie fortfahren", + "continue_failed": "Turnier konnte nicht fortgesetzt werden", + "extend": "Check-in verlängern", + "extend_by": "+{minutes} Minuten", + "extend_failed": "Check-in konnte nicht verlängert werden", + "extend_hint": "Beim Verlängern öffnet die Anmeldung wieder und die {count} fehlenden Teams werden erneut benachrichtigt.", + "auto_continue": "Wenn niemand handelt, läuft das Turnier um {time} von allein mit allen weiter, die eingecheckt haben. Ein angehaltenes Turnier stirbt nie still, weil ein Veranstalter geschlafen hat.", + "auto_continue_no_time": "Wenn niemand handelt, läuft das Turnier zur Startzeit von allein mit allen weiter, die eingecheckt haben.", + "reseed_warning": "Ein wieder zugelassenes Team setzt das Bracket neu. Vor dem Start ist das sicher, mischt aber die Setzliste neu." + }, + "free_agents": { + "title": "Free Agents", + "signed_up": "{count} angemeldet", + "filter_all": "Alle", + "filter_drafted": "Gedraftet", + "filter_waitlist": "Warteliste", + "sign_up": "Anmelden", + "leave": "Pool verlassen", + "status_drafted": "gedraftet", + "status_waitlisted": "Warteliste", + "status_registered": "angemeldet", + "empty_title": "Noch keine Free Agents", + "empty_description": "Spieler, die sich allein anmelden, erscheinen hier und werden bei Anmeldeschluss in ausgeglichene Teams gedraftet.", + "waitlist_explainer": "{name} hat das höchste Rating und steht trotzdem auf der Warteliste — die Anmeldung kam erst, als die Plätze voll waren. Die Anmeldezeit entscheidet über den Platz, die ELO nur über das Team. Das ist die Regel, kein Bug.", + "organizer": "Veranstalter", + "draft_summary": "{agents} Free Agents → {teams} Teams à {size}, {waitlisted} auf der Warteliste.", + "regenerate": "Teams neu draften", + "regenerated": "{count} Teams gedraftet", + "regenerate_failed": "Teams konnten nicht neu gedraftet werden", + "join_failed": "Beitritt zum Free-Agent-Pool fehlgeschlagen", + "leave_failed": "Verlassen des Free-Agent-Pools fehlgeschlagen" } }, "team": { diff --git a/i18n/locales/en.json b/i18n/locales/en.json index 2fbcb8b02..401182c96 100644 --- a/i18n/locales/en.json +++ b/i18n/locales/en.json @@ -1229,6 +1229,10 @@ }, "system_telemetry": { "title": "Fleet Telemetry", + "filter": { + "all": "Whole Fleet", + "self_hosted": "Self-Hosted Only" + }, "no_data": "No data reported yet", "installs": { "title": "Panels", @@ -1244,11 +1248,11 @@ "recency_hint": "Every panel ever seen, cut into disjoint bands by how recently it last checked in. The bands do not overlap, so they add up to the all-time total.", "seen_24h": "Today", "dormant": "Dormant", - "reporting": "In Totals", - "reporting_hint": "Panels whose latest report is detailed enough to count toward the fleet totals below. Panels quiet for over 30 days are left out, so the totals do not keep counting servers that were torn down." + "reporting": "Current State", + "reporting_hint": "Panels whose latest report is recent enough and detailed enough to count toward infrastructure and the rolling windows below. The all-time counts are not held to this — they are summed over every panel that ever reported, All Time above." }, "totals": { - "self_reported": "Summed across the latest report of every panel active in the last 30 days — In Totals above is how many that is. Panels are self-hosted and report these counts themselves; values are validated on arrival but cannot be independently verified. Infrastructure is a point-in-time count, not a running total.", + "self_reported": "All-time counts — matches, maps, players, competition — are summed across the latest report of every panel that ever sent one, including panels that have since gone quiet: a panel going dark does not un-play its matches. Infrastructure and the rolling windows count only panels heard from in the last 30 days — Current State above is how many that is — because a node torn down a year ago is not capacity, and a quiet panel's last 7 days ended whenever it stopped reporting. Panels are self-hosted and report these counts themselves; values are validated on arrival but cannot be independently verified.", "game_server_nodes": "Game Server Nodes", "nodes_online": "Nodes Online", "nodes_offline": "Offline", @@ -3967,7 +3971,9 @@ }, "picker": { "search": "Find a map", - "none": "No maps with radars" + "none": "No maps with radars", + "count_public": "Public lineups on this map", + "count_private": "Not public, but visible to you — yours, your team's, or waiting on review. Archived is not counted." }, "back_to_map": "Back to Map", "empty": { @@ -5946,13 +5952,31 @@ "teams": "Teams", "format": "Format", "starts": "Starts", - "location": "Location" + "location": "Location", + "title": "Stats", + "leaderboard_title": "Player leaderboard", + "matches": "Matches", + "rounds": "Rounds", + "players": "Players", + "top_rating": "Top rating", + "col_team": "Team", + "col_rating": "Rating", + "col_adr": "ADR", + "col_kills": "K", + "col_deaths": "D", + "col_assists": "A", + "col_kd": "K/D", + "col_hs": "HS%", + "col_maps": "Maps", + "empty_title": "No stats yet", + "empty_description": "The leaderboard fills in as tournament matches finish." }, "wizard": { "information": "Information", "location": "Location", "match_options": "Match Options", - "prizes": "Prizes" + "prizes": "Prizes", + "registration": "Registration" }, "banner": { "label": "Banner", @@ -6006,6 +6030,173 @@ "expand": "Expand", "collapse_all": "Collapse All", "expand_all": "Expand All" + }, + "registration": { + "section": "Registration", + "who": { + "label": "Who can register?", + "teams": { + "label": "Teams only", + "description": "Captains register an existing team. The default." + }, + "free_agents": { + "label": "Free Agents", + "description": "Players sign up alone and are drafted into balanced teams when registration closes." + }, + "both": { + "label": "Teams + Free Agents", + "description": "Both. Leftover free agents are drafted into extra teams alongside the registered ones." + } + }, + "min_role": { + "label": "Minimum role", + "none": "No restriction", + "description": "Checked against the player being added, not just whoever clicks — so a captain can't roster ineligible players." + }, + "rank": { + "label": "Rank range", + "min": "Min ELO", + "max": "Max ELO", + "description": "Leave either blank for no bound. Blank on both is open to any rank.", + "invalid_range": "Max ELO must be at least the min ELO." + }, + "regions": { + "label": "Preferred regions", + "description": "Where this tournament's matches are hosted. Not a player gate — 5stack has no per-player region data.", + "empty": "No regions are currently available." + }, + "invite_only": { + "label": "Invite only", + "description": "Registration is closed to the public. Organizers invite teams directly, or entrants supply a passcode." + }, + "passcode": { + "label": "Passcode", + "optional": "(optional)", + "description": "Anyone with the code can enter without an invite. Organizers only." + } + }, + "check_in": { + "section": "Check-In", + "required": { + "label": "Require check-in", + "description": "Teams confirm they're attending before the bracket is seeded. The window opens on a schedule; confirming is manual." + }, + "who": { + "label": "Who checks in?", + "Captains": { + "label": "Captains", + "description": "The captain confirms for the whole team. One click, one team." + }, + "Players": { + "label": "Every player", + "description": "Each rostered player confirms individually. The team is in once {count} have checked in — substitutes don't have to." + }, + "Admin": { + "label": "Organizer only", + "description": "Players can't check themselves in; you mark teams present." + } + }, + "opens": { + "label": "Window opens" + }, + "closes": { + "label": "Window closes" + }, + "minutes_before_start": "minutes before start", + "window_too_short": "The window must stay open for at least 5 minutes.", + "timeline": { + "opens": "Window opens", + "closes": "Window closes", + "start": "Tournament starts" + }, + "lock_note": "Once the window opens, the start time and both offsets lock. Teams are already acting on this schedule, so moving it mid-window would shift a live deadline.", + "late_registration_note": "A team that registers during the window is checked in automatically — it can't be penalised for missing a prompt it was never shown.", + "no_show_note": "No-shows are never deleted. A team that misses check-in keeps its roster and simply isn't seeded — you can re-admit it any time before the tournament goes live.", + "frozen": "Check-in has already opened, so the start time and both offsets are locked. Extend the check-in window instead.", + "required_title": "This tournament requires check-in.", + "required_window": "Your team must confirm between {opens} and {closes} on the day, or it won't be placed in the bracket.", + "register_heading": "Register your team", + "register_hint": "You'll get a notification when check-in opens, and a reminder before it closes.", + "register_cta": "Register team", + "chip_required": "check-in required", + "opens_at_heading": "Check-in opens at {time}", + "opens_at_hint": "Your team is registered. We'll notify you when the window opens.", + "opens_in": "opens in", + "closes_in": "closes in", + "starts_in": "starts in", + "chip_action_needed": "action needed", + "chip_open": "check-in open", + "observer_heading": "Check-in is open", + "observer_hint": "Teams confirm they're attending before the bracket is seeded. Any team that hasn't checked in by {time} holds the tournament for your review.", + "confirm_heading": "Confirm your team is attending", + "confirm_hint": "Teams that haven't checked in by {time} are not seeded into the bracket.", + "players_progress": "{checked} of {required} checked in", + "players_heading": "Every player must check in", + "players_hint": "Your team is in once {count} players confirm. Substitutes don't need to.", + "admin_heading": "Mark teams present", + "admin_hint": "Players can't check themselves in for this tournament. Mark each team present before the window closes.", + "check_in": "Check in", + "mark_present": "Mark present", + "checked_in": "checked in", + "checked_in_at": "checked in · {time}", + "waiting": "waiting", + "not_needed": "not needed", + "role_captain": "captain", + "role_starter": "starter", + "role_sub": "sub", + "field_status": "Field status", + "field_count": "{checked} of {total} checked in", + "done_heading": "You're in. The bracket is seeded at {time}.", + "done_hint": "Confirmed for {team} at {time}.", + "chip_removed": "removed", + "missed_heading": "Your team missed check-in", + "missed_hint": "Check-in closed at {time} and the bracket is seeded without your team.", + "missed_contact": "Contact an organizer if you think this is wrong — they can re-admit a team any time before the tournament goes live.", + "failed": "Check-in failed" + }, + "check_in_review": { + "heading": "{count} teams missed check-in", + "subheading": "The tournament is held. Nothing has been seeded and nothing has been deleted — every team keeps its full roster.", + "chip_held_at": "held · auto-continues {time}", + "chip_held": "held", + "roster_size": "{count} players", + "partial_roster": "{checked} of {required} checked in · short by {short}", + "registered": "registered", + "chip_no_check_in": "no check-in", + "chip_incomplete": "incomplete", + "readmit": "Re-admit", + "readmit_failed": "Failed to re-admit team", + "continue": "Continue without them", + "continue_failed": "Failed to continue the tournament", + "extend": "Extend check-in", + "extend_by": "+{minutes} minutes", + "extend_failed": "Failed to extend check-in", + "extend_hint": "Extending reopens registration and re-notifies the {count} missing teams.", + "auto_continue": "If nobody acts, the tournament continues on its own at {time} with whoever checked in. A held tournament can never quietly die because an organizer was asleep.", + "auto_continue_no_time": "If nobody acts, the tournament continues on its own at its start time with whoever checked in.", + "reseed_warning": "Re-admitting a team re-seeds the bracket. That is safe before kickoff, but it reshuffles the seeds." + }, + "free_agents": { + "title": "Free Agents", + "signed_up": "{count} signed up", + "filter_all": "All", + "filter_drafted": "Drafted", + "filter_waitlist": "Waitlist", + "sign_up": "Sign up", + "leave": "Leave pool", + "status_drafted": "drafted", + "status_waitlisted": "waitlist", + "status_registered": "registered", + "empty_title": "No free agents yet", + "empty_description": "Players who sign up alone appear here, and are drafted into balanced teams when registration closes.", + "waitlist_explainer": "{name} is the highest-rated entrant and still waitlisted — they signed up after the slots filled. Sign-up time decides who gets a slot; ELO only decides which team. That is the rule working, not a bug.", + "organizer": "Organizer", + "draft_summary": "{agents} free agents → {teams} teams of {size}, {waitlisted} waitlisted.", + "regenerate": "Regenerate teams", + "regenerated": "Drafted {count} teams", + "regenerate_failed": "Failed to regenerate teams", + "join_failed": "Failed to join the free agent pool", + "leave_failed": "Failed to leave the free agent pool" } }, "team": { diff --git a/i18n/locales/es_ES.json b/i18n/locales/es_ES.json index eb5a42695..5947ec2d1 100644 --- a/i18n/locales/es_ES.json +++ b/i18n/locales/es_ES.json @@ -1229,6 +1229,10 @@ }, "system_telemetry": { "title": "Telemetría de la flota", + "filter": { + "all": "Toda la flota", + "self_hosted": "Solo autoalojados" + }, "no_data": "Aún no se han reportado datos", "installs": { "title": "Paneles", @@ -3967,7 +3971,9 @@ }, "picker": { "search": "Buscar un mapa", - "none": "No hay mapas con radar" + "none": "No hay mapas con radar", + "count_public": "Lineups públicas en este mapa", + "count_private": "No públicas, pero visibles para ti: tuyas, de tu equipo o en revisión. Las archivadas no se cuentan." }, "back_to_map": "Volver al mapa", "empty": { @@ -5946,13 +5952,31 @@ "teams": "Equipos", "format": "Formato", "starts": "Empieza", - "location": "Ubicación" + "location": "Ubicación", + "title": "Estadísticas", + "leaderboard_title": "Clasificación de jugadores", + "matches": "Partidas", + "rounds": "Rondas", + "players": "Jugadores", + "top_rating": "Mejor rating", + "col_team": "Equipo", + "col_rating": "Rating", + "col_adr": "ADR", + "col_kills": "K", + "col_deaths": "D", + "col_assists": "A", + "col_kd": "K/D", + "col_hs": "HS%", + "col_maps": "Mapas", + "empty_title": "Aún no hay estadísticas", + "empty_description": "La clasificación se rellena a medida que terminan las partidas del torneo." }, "wizard": { "information": "Información", "location": "Ubicación", "match_options": "Opciones de partida", - "prizes": "Premios" + "prizes": "Premios", + "registration": "Inscripción" }, "banner": { "label": "Banner", @@ -6006,6 +6030,173 @@ "expand": "Expandir", "collapse_all": "Contraer todo", "expand_all": "Expandir todo" + }, + "registration": { + "section": "Inscripción", + "who": { + "label": "¿Quién puede inscribirse?", + "teams": { + "label": "Solo equipos", + "description": "Los capitanes inscriben un equipo existente. Es lo predeterminado." + }, + "free_agents": { + "label": "Agentes libres", + "description": "Los jugadores se apuntan solos y se draftean en equipos equilibrados al cerrar la inscripción." + }, + "both": { + "label": "Equipos + agentes libres", + "description": "Ambos. Los agentes libres sobrantes se draftean en equipos extra junto a los ya inscritos." + } + }, + "min_role": { + "label": "Rol mínimo", + "none": "Sin restricción", + "description": "Se comprueba sobre el jugador que se añade, no solo sobre quien hace clic, así un capitán no puede meter en el roster a jugadores no elegibles." + }, + "rank": { + "label": "Rango de ELO", + "min": "ELO mín.", + "max": "ELO máx.", + "description": "Deja cualquiera en blanco para no poner límite. Ambos en blanco: abierto a cualquier rango.", + "invalid_range": "El ELO máx. debe ser al menos igual al ELO mín." + }, + "regions": { + "label": "Regiones preferidas", + "description": "Dónde se alojan las partidas de este torneo. No filtra jugadores: 5stack no tiene datos de región por jugador.", + "empty": "No hay regiones disponibles ahora mismo." + }, + "invite_only": { + "label": "Solo por invitación", + "description": "La inscripción no es pública. Los organizadores invitan a los equipos directamente, o los participantes introducen un código de acceso." + }, + "passcode": { + "label": "Código de acceso", + "optional": "(opcional)", + "description": "Cualquiera con el código puede entrar sin invitación. Solo organizadores." + } + }, + "check_in": { + "section": "Check-in", + "required": { + "label": "Exigir check-in", + "description": "Los equipos confirman su asistencia antes de armar el cuadro. La ventana se abre según el calendario; confirmar es manual." + }, + "who": { + "label": "¿Quién hace el check-in?", + "Captains": { + "label": "Capitanes", + "description": "El capitán confirma por todo el equipo. Un clic, un equipo." + }, + "Players": { + "label": "Cada jugador", + "description": "Cada jugador del roster confirma por su cuenta. El equipo entra cuando {count} hayan hecho check-in; los suplentes no hace falta." + }, + "Admin": { + "label": "Solo el organizador", + "description": "Los jugadores no pueden hacer su propio check-in; tú marcas los equipos como presentes." + } + }, + "opens": { + "label": "La ventana abre" + }, + "closes": { + "label": "La ventana cierra" + }, + "minutes_before_start": "minutos antes del inicio", + "window_too_short": "La ventana debe estar abierta al menos 5 minutos.", + "timeline": { + "opens": "La ventana abre", + "closes": "La ventana cierra", + "start": "El torneo empieza" + }, + "lock_note": "Cuando la ventana se abre, la hora de inicio y ambos márgenes quedan bloqueados. Los equipos ya se guían por este calendario, así que moverlo a media ventana cambiaría un plazo en curso.", + "late_registration_note": "Un equipo que se inscribe durante la ventana queda con el check-in hecho automáticamente: no se le puede penalizar por un aviso que nunca vio.", + "no_show_note": "Las ausencias nunca se borran. Un equipo que se pierde el check-in conserva su roster y simplemente no entra en el cuadro; puedes readmitirlo en cualquier momento antes de que el torneo arranque.", + "frozen": "El check-in ya está abierto, así que la hora de inicio y ambos márgenes están bloqueados. Amplía la ventana de check-in en su lugar.", + "required_title": "Este torneo exige check-in.", + "required_window": "Tu equipo debe confirmar entre las {opens} y las {closes} ese mismo día, o no entrará en el cuadro.", + "register_heading": "Inscribe tu equipo", + "register_hint": "Recibirás un aviso cuando se abra el check-in y un recordatorio antes de que cierre.", + "register_cta": "Inscribir equipo", + "chip_required": "check-in obligatorio", + "opens_at_heading": "El check-in abre a las {time}", + "opens_at_hint": "Tu equipo está inscrito. Te avisaremos cuando se abra la ventana.", + "opens_in": "abre en", + "closes_in": "cierra en", + "starts_in": "empieza en", + "chip_action_needed": "requiere acción", + "chip_open": "check-in abierto", + "observer_heading": "El check-in está abierto", + "observer_hint": "Los equipos confirman su asistencia antes de armar el cuadro. Todo equipo que no haya hecho check-in a las {time} deja el torneo en espera de tu revisión.", + "confirm_heading": "Confirma la asistencia de tu equipo", + "confirm_hint": "Los equipos que no hayan hecho check-in a las {time} no entran en el cuadro.", + "players_progress": "{checked} de {required} con check-in", + "players_heading": "Todos los jugadores deben hacer check-in", + "players_hint": "Tu equipo entra cuando {count} jugadores confirmen. Los suplentes no hace falta.", + "admin_heading": "Marca los equipos presentes", + "admin_hint": "En este torneo los jugadores no pueden hacer su propio check-in. Marca cada equipo como presente antes de que cierre la ventana.", + "check_in": "Hacer check-in", + "mark_present": "Marcar presente", + "checked_in": "check-in hecho", + "checked_in_at": "check-in hecho · {time}", + "waiting": "en espera", + "not_needed": "no hace falta", + "role_captain": "capitán", + "role_starter": "titular", + "role_sub": "suplente", + "field_status": "Estado de los participantes", + "field_count": "{checked} de {total} con check-in", + "done_heading": "Estáis dentro. El cuadro se arma a las {time}.", + "done_hint": "Confirmado por {team} a las {time}.", + "chip_removed": "retirado", + "missed_heading": "Tu equipo se perdió el check-in", + "missed_hint": "El check-in cerró a las {time} y el cuadro se arma sin tu equipo.", + "missed_contact": "Habla con un organizador si crees que es un error: puede readmitir a un equipo en cualquier momento antes de que el torneo arranque.", + "failed": "Error en el check-in" + }, + "check_in_review": { + "heading": "{count} equipos se perdieron el check-in", + "subheading": "El torneo está en espera. No se ha armado nada ni se ha borrado nada: cada equipo conserva su roster completo.", + "chip_held_at": "en espera · sigue solo a las {time}", + "chip_held": "en espera", + "roster_size": "{count} jugadores", + "partial_roster": "{checked} de {required} con check-in · faltan {short}", + "registered": "inscrito", + "chip_no_check_in": "sin check-in", + "chip_incomplete": "incompleto", + "readmit": "Readmitir", + "readmit_failed": "No se ha podido readmitir al equipo", + "continue": "Continuar sin ellos", + "continue_failed": "No se ha podido continuar el torneo", + "extend": "Ampliar el check-in", + "extend_by": "+{minutes} minutos", + "extend_failed": "No se ha podido ampliar el check-in", + "extend_hint": "Ampliar reabre la inscripción y vuelve a avisar a los {count} equipos que faltan.", + "auto_continue": "Si nadie hace nada, el torneo sigue solo a las {time} con quienes hayan hecho check-in. Un torneo en espera nunca muere en silencio porque el organizador se durmiera.", + "auto_continue_no_time": "Si nadie hace nada, el torneo sigue solo a su hora de inicio con quienes hayan hecho check-in.", + "reseed_warning": "Readmitir a un equipo vuelve a armar el cuadro. Antes del arranque es seguro, pero reordena los cabezas de serie." + }, + "free_agents": { + "title": "Agentes libres", + "signed_up": "{count} apuntados", + "filter_all": "Todos", + "filter_drafted": "Drafteados", + "filter_waitlist": "Lista de espera", + "sign_up": "Apuntarse", + "leave": "Salir del grupo", + "status_drafted": "drafteado", + "status_waitlisted": "lista de espera", + "status_registered": "inscrito", + "empty_title": "Aún no hay agentes libres", + "empty_description": "Los jugadores que se apuntan solos aparecen aquí y se draftean en equipos equilibrados al cerrar la inscripción.", + "waitlist_explainer": "{name} es el participante con mejor rating y sigue en lista de espera: se apuntó después de que se llenaran las plazas. La hora de inscripción decide quién entra; el ELO solo decide en qué equipo. Es la regla funcionando, no un fallo.", + "organizer": "Organizador", + "draft_summary": "{agents} agentes libres → {teams} equipos de {size}, {waitlisted} en lista de espera.", + "regenerate": "Rehacer los equipos", + "regenerated": "{count} equipos drafteados", + "regenerate_failed": "No se han podido rehacer los equipos", + "join_failed": "No se ha podido entrar en el grupo de agentes libres", + "leave_failed": "No se ha podido salir del grupo de agentes libres" } }, "team": { diff --git a/i18n/locales/fr_FR.json b/i18n/locales/fr_FR.json index 9f75c643e..f73324dbc 100644 --- a/i18n/locales/fr_FR.json +++ b/i18n/locales/fr_FR.json @@ -1229,6 +1229,10 @@ }, "system_telemetry": { "title": "Télémétrie de la flotte", + "filter": { + "all": "Toute la flotte", + "self_hosted": "Auto-hébergés uniquement" + }, "no_data": "Aucune donnée signalée pour l'instant", "installs": { "title": "Panneaux", @@ -3967,7 +3971,9 @@ }, "picker": { "search": "Trouver une map", - "none": "Aucune map avec radar" + "none": "Aucune map avec radar", + "count_public": "Lineups publiques sur cette map", + "count_private": "Non publiques mais visibles par toi : les tiennes, celles de ton équipe ou en attente de validation. Les archivées ne comptent pas." }, "back_to_map": "Retour à la map", "empty": { @@ -5946,13 +5952,31 @@ "teams": "Équipes", "format": "Format", "starts": "Début", - "location": "Lieu" + "location": "Lieu", + "title": "Stats", + "leaderboard_title": "Classement des joueurs", + "matches": "Matchs", + "rounds": "Rounds", + "players": "Joueurs", + "top_rating": "Meilleur rating", + "col_team": "Équipe", + "col_rating": "Rating", + "col_adr": "ADR", + "col_kills": "K", + "col_deaths": "D", + "col_assists": "A", + "col_kd": "K/D", + "col_hs": "HS%", + "col_maps": "Maps", + "empty_title": "Aucune stat pour l'instant", + "empty_description": "Le classement se remplit au fur et à mesure que les matchs du tournoi se terminent." }, "wizard": { "information": "Informations", "location": "Lieu", "match_options": "Options de match", - "prizes": "Prix" + "prizes": "Prix", + "registration": "Inscription" }, "banner": { "label": "Bannière", @@ -6006,6 +6030,173 @@ "expand": "Déplier", "collapse_all": "Tout replier", "expand_all": "Tout déplier" + }, + "registration": { + "section": "Inscription", + "who": { + "label": "Qui peut s'inscrire ?", + "teams": { + "label": "Équipes uniquement", + "description": "Les capitaines inscrivent une équipe existante. Par défaut." + }, + "free_agents": { + "label": "Agents libres", + "description": "Les joueurs s'inscrivent seuls et sont draftés dans des équipes équilibrées à la clôture des inscriptions." + }, + "both": { + "label": "Équipes + agents libres", + "description": "Les deux. Les agents libres restants sont draftés dans des équipes supplémentaires, en plus des équipes inscrites." + } + }, + "min_role": { + "label": "Rôle minimum", + "none": "Aucune restriction", + "description": "Vérifié sur le joueur ajouté, pas seulement sur celui qui clique — un capitaine ne peut donc pas inscrire de joueurs inéligibles." + }, + "rank": { + "label": "Plage de rang", + "min": "ELO min", + "max": "ELO max", + "description": "Laisse vide pour ne pas fixer de limite. Les deux vides : ouvert à tous les rangs.", + "invalid_range": "L'ELO max doit être au moins égal à l'ELO min." + }, + "regions": { + "label": "Régions préférées", + "description": "Où sont hébergés les matchs de ce tournoi. Ce n'est pas un filtre joueur — 5stack n'a pas de données de région par joueur.", + "empty": "Aucune région n'est disponible actuellement." + }, + "invite_only": { + "label": "Sur invitation", + "description": "Les inscriptions ne sont pas publiques. Les organisateurs invitent les équipes directement, ou les participants saisissent un code d'accès." + }, + "passcode": { + "label": "Code d'accès", + "optional": "(facultatif)", + "description": "Toute personne ayant le code peut entrer sans invitation. Organisateurs uniquement." + } + }, + "check_in": { + "section": "Check-in", + "required": { + "label": "Exiger un check-in", + "description": "Les équipes confirment leur présence avant la constitution du bracket. La fenêtre s'ouvre selon le calendrier ; la confirmation est manuelle." + }, + "who": { + "label": "Qui fait le check-in ?", + "Captains": { + "label": "Capitaines", + "description": "Le capitaine confirme pour toute l'équipe. Un clic, une équipe." + }, + "Players": { + "label": "Chaque joueur", + "description": "Chaque joueur du roster confirme individuellement. L'équipe est validée dès que {count} ont fait leur check-in — les remplaçants ne sont pas obligés." + }, + "Admin": { + "label": "Organisateur uniquement", + "description": "Les joueurs ne peuvent pas faire leur check-in eux-mêmes ; tu marques les équipes présentes." + } + }, + "opens": { + "label": "Ouverture de la fenêtre" + }, + "closes": { + "label": "Fermeture de la fenêtre" + }, + "minutes_before_start": "minutes avant le début", + "window_too_short": "La fenêtre doit rester ouverte au moins 5 minutes.", + "timeline": { + "opens": "Ouverture de la fenêtre", + "closes": "Fermeture de la fenêtre", + "start": "Début du tournoi" + }, + "lock_note": "Une fois la fenêtre ouverte, l'heure de début et les deux décalages sont verrouillés. Les équipes se basent déjà sur ce calendrier : le modifier en cours décalerait une échéance en direct.", + "late_registration_note": "Une équipe qui s'inscrit pendant la fenêtre fait automatiquement son check-in — elle ne peut pas être pénalisée pour une demande qu'elle n'a jamais vue.", + "no_show_note": "Les absents ne sont jamais supprimés. Une équipe qui rate le check-in garde son roster et n'est simplement pas placée dans le bracket — tu peux la réadmettre à tout moment avant le lancement du tournoi.", + "frozen": "Le check-in a déjà commencé : l'heure de début et les deux décalages sont verrouillés. Prolonge plutôt la fenêtre de check-in.", + "required_title": "Ce tournoi exige un check-in.", + "required_window": "Ton équipe doit confirmer entre {opens} et {closes} le jour même, sinon elle ne sera pas placée dans le bracket.", + "register_heading": "Inscris ton équipe", + "register_hint": "Tu recevras une notification à l'ouverture du check-in, et un rappel avant sa fermeture.", + "register_cta": "Inscrire l'équipe", + "chip_required": "check-in requis", + "opens_at_heading": "Le check-in ouvre à {time}", + "opens_at_hint": "Ton équipe est inscrite. On te préviendra à l'ouverture de la fenêtre.", + "opens_in": "ouvre dans", + "closes_in": "ferme dans", + "starts_in": "commence dans", + "chip_action_needed": "action requise", + "chip_open": "check-in ouvert", + "observer_heading": "Le check-in est ouvert", + "observer_hint": "Les équipes confirment leur présence avant la constitution du bracket. Toute équipe qui n'a pas fait son check-in à {time} met le tournoi en attente de ton examen.", + "confirm_heading": "Confirme la présence de ton équipe", + "confirm_hint": "Les équipes qui n'ont pas fait leur check-in à {time} ne sont pas placées dans le bracket.", + "players_progress": "{checked} sur {required} ont fait leur check-in", + "players_heading": "Chaque joueur doit faire son check-in", + "players_hint": "Ton équipe est validée dès que {count} joueurs confirment. Les remplaçants ne sont pas obligés.", + "admin_heading": "Marquer les équipes présentes", + "admin_hint": "Les joueurs ne peuvent pas faire leur check-in eux-mêmes sur ce tournoi. Marque chaque équipe présente avant la fermeture de la fenêtre.", + "check_in": "Faire le check-in", + "mark_present": "Marquer présent", + "checked_in": "check-in fait", + "checked_in_at": "check-in fait · {time}", + "waiting": "en attente", + "not_needed": "non requis", + "role_captain": "capitaine", + "role_starter": "titulaire", + "role_sub": "remplaçant", + "field_status": "État des participants", + "field_count": "{checked} sur {total} ont fait leur check-in", + "done_heading": "C'est bon. Le bracket est constitué à {time}.", + "done_hint": "Confirmé pour {team} à {time}.", + "chip_removed": "retirée", + "missed_heading": "Ton équipe a raté le check-in", + "missed_hint": "Le check-in a fermé à {time} et le bracket est constitué sans ton équipe.", + "missed_contact": "Contacte un organisateur si tu penses que c'est une erreur — il peut réadmettre une équipe à tout moment avant le lancement du tournoi.", + "failed": "Échec du check-in" + }, + "check_in_review": { + "heading": "{count} équipes ont raté le check-in", + "subheading": "Le tournoi est en attente. Rien n'a été constitué et rien n'a été supprimé — chaque équipe garde son roster complet.", + "chip_held_at": "en attente · reprend {time}", + "chip_held": "en attente", + "roster_size": "{count} joueurs", + "partial_roster": "{checked} sur {required} ont fait leur check-in · il en manque {short}", + "registered": "inscrite", + "chip_no_check_in": "aucun check-in", + "chip_incomplete": "incomplète", + "readmit": "Réadmettre", + "readmit_failed": "Impossible de réadmettre l'équipe", + "continue": "Continuer sans elles", + "continue_failed": "Impossible de poursuivre le tournoi", + "extend": "Prolonger le check-in", + "extend_by": "+{minutes} minutes", + "extend_failed": "Impossible de prolonger le check-in", + "extend_hint": "Prolonger rouvre les inscriptions et renotifie les {count} équipes manquantes.", + "auto_continue": "Si personne n'agit, le tournoi reprend tout seul à {time} avec celles qui ont fait leur check-in. Un tournoi en attente ne meurt jamais en silence parce qu'un organisateur dormait.", + "auto_continue_no_time": "Si personne n'agit, le tournoi reprend tout seul à son heure de début avec celles qui ont fait leur check-in.", + "reseed_warning": "Réadmettre une équipe reconstitue le bracket. C'est sans risque avant le coup d'envoi, mais cela rebat les têtes de série." + }, + "free_agents": { + "title": "Agents libres", + "signed_up": "{count} inscrits", + "filter_all": "Tous", + "filter_drafted": "Draftés", + "filter_waitlist": "Liste d'attente", + "sign_up": "S'inscrire", + "leave": "Quitter le pool", + "status_drafted": "drafté", + "status_waitlisted": "liste d'attente", + "status_registered": "inscrit", + "empty_title": "Aucun agent libre pour l'instant", + "empty_description": "Les joueurs qui s'inscrivent seuls apparaissent ici et sont draftés dans des équipes équilibrées à la clôture des inscriptions.", + "waitlist_explainer": "{name} a le meilleur rating et reste sur liste d'attente — son inscription est arrivée après le remplissage des places. L'heure d'inscription décide de la place, l'ELO ne décide que de l'équipe. C'est la règle qui s'applique, pas un bug.", + "organizer": "Organisateur", + "draft_summary": "{agents} agents libres → {teams} équipes de {size}, {waitlisted} sur liste d'attente.", + "regenerate": "Redrafter les équipes", + "regenerated": "{count} équipes draftées", + "regenerate_failed": "Impossible de redrafter les équipes", + "join_failed": "Impossible de rejoindre le pool d'agents libres", + "leave_failed": "Impossible de quitter le pool d'agents libres" } }, "team": { diff --git a/i18n/locales/it_IT.json b/i18n/locales/it_IT.json index 80714b119..04ba8bbf6 100644 --- a/i18n/locales/it_IT.json +++ b/i18n/locales/it_IT.json @@ -1229,6 +1229,10 @@ }, "system_telemetry": { "title": "Telemetria della flotta", + "filter": { + "all": "Intera flotta", + "self_hosted": "Solo self-hosted" + }, "no_data": "Nessun dato ancora segnalato", "installs": { "title": "Pannelli", @@ -3967,7 +3971,9 @@ }, "picker": { "search": "Trova una mappa", - "none": "Nessuna mappa con radar" + "none": "Nessuna mappa con radar", + "count_public": "Lineup pubbliche su questa mappa", + "count_private": "Non pubbliche ma visibili a te: le tue, quelle del tuo team o in revisione. Quelle archiviate non vengono conteggiate." }, "back_to_map": "Torna alla mappa", "empty": { @@ -5946,13 +5952,31 @@ "teams": "Team", "format": "Formato", "starts": "Inizio", - "location": "Luogo" + "location": "Luogo", + "title": "Statistiche", + "leaderboard_title": "Classifica giocatori", + "matches": "Match", + "rounds": "Round", + "players": "Giocatori", + "top_rating": "Rating migliore", + "col_team": "Team", + "col_rating": "Rating", + "col_adr": "ADR", + "col_kills": "K", + "col_deaths": "D", + "col_assists": "A", + "col_kd": "K/D", + "col_hs": "HS%", + "col_maps": "Mappe", + "empty_title": "Ancora nessuna statistica", + "empty_description": "La classifica si riempie man mano che i match del torneo finiscono." }, "wizard": { "information": "Informazioni", "location": "Luogo", "match_options": "Opzioni match", - "prizes": "Premi" + "prizes": "Premi", + "registration": "Iscrizione" }, "banner": { "label": "Banner", @@ -6006,6 +6030,173 @@ "expand": "Espandi", "collapse_all": "Comprimi tutto", "expand_all": "Espandi tutto" + }, + "registration": { + "section": "Iscrizione", + "who": { + "label": "Chi può iscriversi?", + "teams": { + "label": "Solo team", + "description": "I capitani iscrivono un team esistente. È l'impostazione predefinita." + }, + "free_agents": { + "label": "Free agent", + "description": "I giocatori si iscrivono da soli e vengono draftati in team equilibrati alla chiusura delle iscrizioni." + }, + "both": { + "label": "Team + free agent", + "description": "Entrambi. I free agent rimasti vengono draftati in team aggiuntivi, accanto a quelli già iscritti." + } + }, + "min_role": { + "label": "Ruolo minimo", + "none": "Nessuna restrizione", + "description": "Viene verificato sul giocatore che stai aggiungendo, non solo su chi clicca: così un capitano non può mettere a roster giocatori non idonei." + }, + "rank": { + "label": "Fascia ELO", + "min": "ELO min", + "max": "ELO max", + "description": "Lascia vuoto uno dei due per non porre limiti. Entrambi vuoti: aperto a qualsiasi rank.", + "invalid_range": "L'ELO max deve essere almeno pari all'ELO min." + }, + "regions": { + "label": "Regioni preferite", + "description": "Dove vengono ospitati i match di questo torneo. Non è un filtro sui giocatori: 5stack non ha dati di regione per singolo giocatore.", + "empty": "Nessuna regione al momento disponibile." + }, + "invite_only": { + "label": "Solo su invito", + "description": "Le iscrizioni non sono pubbliche. Gli organizzatori invitano i team direttamente, oppure i partecipanti inseriscono un codice di accesso." + }, + "passcode": { + "label": "Codice di accesso", + "optional": "(facoltativo)", + "description": "Chiunque abbia il codice entra senza invito. Solo organizzatori." + } + }, + "check_in": { + "section": "Check-in", + "required": { + "label": "Richiedi il check-in", + "description": "I team confermano la presenza prima che venga composto il bracket. La finestra si apre secondo il programma; la conferma è manuale." + }, + "who": { + "label": "Chi fa il check-in?", + "Captains": { + "label": "Capitani", + "description": "Il capitano conferma per tutto il team. Un clic, un team." + }, + "Players": { + "label": "Ogni giocatore", + "description": "Ogni giocatore a roster conferma singolarmente. Il team è dentro quando {count} hanno fatto il check-in: le riserve non devono." + }, + "Admin": { + "label": "Solo organizzatore", + "description": "I giocatori non possono fare il check-in da soli; sei tu a segnare i team come presenti." + } + }, + "opens": { + "label": "Apertura finestra" + }, + "closes": { + "label": "Chiusura finestra" + }, + "minutes_before_start": "minuti prima dell'inizio", + "window_too_short": "La finestra deve restare aperta almeno 5 minuti.", + "timeline": { + "opens": "Apertura finestra", + "closes": "Chiusura finestra", + "start": "Inizio del torneo" + }, + "lock_note": "Una volta aperta la finestra, l'orario di inizio ed entrambi gli scarti si bloccano. I team si stanno già regolando su questo programma, quindi spostarlo a finestra aperta sposterebbe una scadenza già in corso.", + "late_registration_note": "Un team che si iscrive durante la finestra risulta automaticamente al check-in: non può essere penalizzato per una richiesta che non ha mai visto.", + "no_show_note": "Chi non si presenta non viene mai eliminato. Un team che salta il check-in mantiene il suo roster e semplicemente non entra nel bracket: puoi riammetterlo in qualsiasi momento prima che il torneo parta.", + "frozen": "Il check-in è già aperto, quindi l'orario di inizio ed entrambi gli scarti sono bloccati. Estendi invece la finestra di check-in.", + "required_title": "Questo torneo richiede il check-in.", + "required_window": "Il tuo team deve confermare tra le {opens} e le {closes} del giorno stesso, altrimenti non entrerà nel bracket.", + "register_heading": "Iscrivi il tuo team", + "register_hint": "Riceverai una notifica all'apertura del check-in e un promemoria prima della chiusura.", + "register_cta": "Iscrivi il team", + "chip_required": "check-in richiesto", + "opens_at_heading": "Il check-in apre alle {time}", + "opens_at_hint": "Il tuo team è iscritto. Ti avvisiamo all'apertura della finestra.", + "opens_in": "apre tra", + "closes_in": "chiude tra", + "starts_in": "inizia tra", + "chip_action_needed": "azione richiesta", + "chip_open": "check-in aperto", + "observer_heading": "Il check-in è aperto", + "observer_hint": "I team confermano la presenza prima che venga composto il bracket. Ogni team che non ha fatto il check-in entro le {time} mette il torneo in attesa della tua revisione.", + "confirm_heading": "Conferma la presenza del tuo team", + "confirm_hint": "I team che non hanno fatto il check-in entro le {time} non entrano nel bracket.", + "players_progress": "{checked} su {required} hanno fatto il check-in", + "players_heading": "Ogni giocatore deve fare il check-in", + "players_hint": "Il tuo team è dentro quando {count} giocatori confermano. Le riserve non devono.", + "admin_heading": "Segna i team presenti", + "admin_hint": "In questo torneo i giocatori non possono fare il check-in da soli. Segna ogni team come presente prima che la finestra si chiuda.", + "check_in": "Fai il check-in", + "mark_present": "Segna presente", + "checked_in": "check-in effettuato", + "checked_in_at": "check-in effettuato · {time}", + "waiting": "in attesa", + "not_needed": "non necessario", + "role_captain": "capitano", + "role_starter": "titolare", + "role_sub": "riserva", + "field_status": "Stato dei partecipanti", + "field_count": "{checked} su {total} hanno fatto il check-in", + "done_heading": "Siete dentro. Il bracket viene composto alle {time}.", + "done_hint": "Confermato per {team} alle {time}.", + "chip_removed": "rimosso", + "missed_heading": "Il tuo team ha saltato il check-in", + "missed_hint": "Il check-in si è chiuso alle {time} e il bracket viene composto senza il tuo team.", + "missed_contact": "Contatta un organizzatore se pensi sia un errore: può riammettere un team in qualsiasi momento prima che il torneo parta.", + "failed": "Check-in non riuscito" + }, + "check_in_review": { + "heading": "{count} team hanno saltato il check-in", + "subheading": "Il torneo è in attesa. Non è stato composto nulla e non è stato eliminato nulla: ogni team mantiene il roster completo.", + "chip_held_at": "in attesa · riprende alle {time}", + "chip_held": "in attesa", + "roster_size": "{count} giocatori", + "partial_roster": "{checked} su {required} hanno fatto il check-in · ne mancano {short}", + "registered": "iscritto", + "chip_no_check_in": "nessun check-in", + "chip_incomplete": "incompleto", + "readmit": "Riammetti", + "readmit_failed": "Impossibile riammettere il team", + "continue": "Continua senza di loro", + "continue_failed": "Impossibile proseguire il torneo", + "extend": "Estendi il check-in", + "extend_by": "+{minutes} minuti", + "extend_failed": "Impossibile estendere il check-in", + "extend_hint": "Estendere riapre le iscrizioni e riavvisa i {count} team mancanti.", + "auto_continue": "Se nessuno interviene, il torneo prosegue da solo alle {time} con chi ha fatto il check-in. Un torneo in attesa non può morire in silenzio perché un organizzatore stava dormendo.", + "auto_continue_no_time": "Se nessuno interviene, il torneo prosegue da solo al suo orario di inizio con chi ha fatto il check-in.", + "reseed_warning": "Riammettere un team ricompone il bracket. Prima del via è sicuro, ma rimescola le teste di serie." + }, + "free_agents": { + "title": "Free agent", + "signed_up": "{count} iscritti", + "filter_all": "Tutti", + "filter_drafted": "Draftati", + "filter_waitlist": "Lista d'attesa", + "sign_up": "Iscriviti", + "leave": "Esci dal pool", + "status_drafted": "draftato", + "status_waitlisted": "lista d'attesa", + "status_registered": "iscritto", + "empty_title": "Ancora nessun free agent", + "empty_description": "I giocatori che si iscrivono da soli compaiono qui e vengono draftati in team equilibrati alla chiusura delle iscrizioni.", + "waitlist_explainer": "{name} ha il rating più alto ed è ancora in lista d'attesa: si è iscritto dopo che i posti erano esauriti. È l'orario di iscrizione a decidere chi ottiene un posto; l'ELO decide solo in quale team. È la regola che funziona, non un bug.", + "organizer": "Organizzatore", + "draft_summary": "{agents} free agent → {teams} team da {size}, {waitlisted} in lista d'attesa.", + "regenerate": "Rigenera i team", + "regenerated": "{count} team draftati", + "regenerate_failed": "Impossibile rigenerare i team", + "join_failed": "Impossibile entrare nel pool dei free agent", + "leave_failed": "Impossibile uscire dal pool dei free agent" } }, "team": { diff --git a/i18n/locales/ja_JP.json b/i18n/locales/ja_JP.json index 1d13655e5..cdd07b9ea 100644 --- a/i18n/locales/ja_JP.json +++ b/i18n/locales/ja_JP.json @@ -1229,6 +1229,10 @@ }, "system_telemetry": { "title": "フリート テレメトリ", + "filter": { + "all": "フリート全体", + "self_hosted": "セルフホストのみ" + }, "no_data": "まだデータが報告されていません", "installs": { "title": "パネル", @@ -3967,7 +3971,9 @@ }, "picker": { "search": "マップを探す", - "none": "レーダーのあるマップがありません" + "none": "レーダーのあるマップがありません", + "count_public": "このマップの公開ラインナップ", + "count_private": "非公開だがあなたには見えるもの — 自分のもの、チームのもの、審査中のもの。アーカイブ済みは含みません。" }, "back_to_map": "マップに戻る", "empty": { @@ -5946,13 +5952,31 @@ "teams": "チーム数", "format": "形式", "starts": "開始", - "location": "開催地" + "location": "開催地", + "title": "スタッツ", + "leaderboard_title": "プレイヤーランキング", + "matches": "試合", + "rounds": "ラウンド", + "players": "プレイヤー", + "top_rating": "最高レーティング", + "col_team": "チーム", + "col_rating": "レーティング", + "col_adr": "ADR", + "col_kills": "K", + "col_deaths": "D", + "col_assists": "A", + "col_kd": "K/D", + "col_hs": "HS%", + "col_maps": "マップ", + "empty_title": "スタッツはまだありません", + "empty_description": "トーナメントの試合が終わるにつれてランキングが埋まります。" }, "wizard": { "information": "基本情報", "location": "開催地", "match_options": "試合設定", - "prizes": "賞金" + "prizes": "賞金", + "registration": "参加登録" }, "banner": { "label": "バナー", @@ -6006,6 +6030,173 @@ "expand": "展開", "collapse_all": "すべて折りたたむ", "expand_all": "すべて展開" + }, + "registration": { + "section": "参加登録", + "who": { + "label": "誰が参加登録できますか?", + "teams": { + "label": "チームのみ", + "description": "キャプテンが既存のチームを登録します。標準の設定です。" + }, + "free_agents": { + "label": "フリーエージェント", + "description": "プレイヤーが個人で登録し、参加登録の締切時に均等なチームへドラフトされます。" + }, + "both": { + "label": "チーム + フリーエージェント", + "description": "両方です。余ったフリーエージェントは、登録済みチームに加えて追加チームへドラフトされます。" + } + }, + "min_role": { + "label": "最低ロール", + "none": "制限なし", + "description": "クリックした人だけでなく、追加される側のプレイヤーで判定します。これによりキャプテンが参加資格のないプレイヤーをロスターに入れられません。" + }, + "rank": { + "label": "ELO範囲", + "min": "ELO 下限", + "max": "ELO 上限", + "description": "空欄にすればその側の制限はありません。両方空欄ならどのランクでも参加できます。", + "invalid_range": "ELO 上限は ELO 下限以上にしてください。" + }, + "regions": { + "label": "希望リージョン", + "description": "このトーナメントの試合をどこでホストするかです。プレイヤーの制限ではありません — 5stack にプレイヤーごとのリージョン情報はありません。", + "empty": "現在利用可能なリージョンはありません。" + }, + "invite_only": { + "label": "招待制", + "description": "参加登録は一般公開されません。主催者がチームを直接招待するか、参加者がアクセスコードを入力します。" + }, + "passcode": { + "label": "パスコード", + "optional": "(任意)", + "description": "コードを知っていれば招待なしで参加できます。主催者のみ。" + } + }, + "check_in": { + "section": "チェックイン", + "required": { + "label": "チェックインを必須にする", + "description": "ブラケットを組む前にチームが参加を確認します。受付時間はスケジュールどおりに開き、確認は手動です。" + }, + "who": { + "label": "誰がチェックインしますか?", + "Captains": { + "label": "キャプテン", + "description": "キャプテンがチーム全員分を確認します。1クリックで1チーム。" + }, + "Players": { + "label": "全プレイヤー", + "description": "ロスターの各プレイヤーが個別に確認します。{count}人がチェックインすればチームは参加確定です。控えは不要です。" + }, + "Admin": { + "label": "主催者のみ", + "description": "プレイヤーは自分でチェックインできません。あなたがチームを出席として記録します。" + } + }, + "opens": { + "label": "受付開始" + }, + "closes": { + "label": "受付終了" + }, + "minutes_before_start": "開始前の分数", + "window_too_short": "受付時間は最低5分間は開いている必要があります。", + "timeline": { + "opens": "受付開始", + "closes": "受付終了", + "start": "トーナメント開始" + }, + "lock_note": "受付が始まると、開始時刻と両方のオフセットはロックされます。チームはすでにこのスケジュールで動いているため、受付中に変更すると進行中の締切がずれてしまいます。", + "late_registration_note": "受付時間中に登録したチームは自動でチェックイン済みになります。表示されたことのない案内を見逃したとして不利益を受けることはありません。", + "no_show_note": "不参加のチームが削除されることはありません。チェックインを逃したチームはロスターをそのまま保持し、ブラケットに入らないだけです。トーナメント開始前ならいつでも再承認できます。", + "frozen": "チェックインはすでに開始しているため、開始時刻と両方のオフセットはロックされています。代わりにチェックイン受付時間を延長してください。", + "required_title": "このトーナメントはチェックインが必要です。", + "required_window": "当日 {opens} から {closes} の間にチームが確認しないと、ブラケットに入れません。", + "register_heading": "チームを登録", + "register_hint": "チェックイン開始時に通知が届き、終了前にはリマインダーが届きます。", + "register_cta": "チームを登録", + "chip_required": "チェックイン必須", + "opens_at_heading": "チェックインは {time} に開始します", + "opens_at_hint": "チームは登録済みです。受付が始まったらお知らせします。", + "opens_in": "開始まで", + "closes_in": "終了まで", + "starts_in": "開始まで", + "chip_action_needed": "要対応", + "chip_open": "チェックイン受付中", + "observer_heading": "チェックイン受付中", + "observer_hint": "ブラケットを組む前にチームが参加を確認します。{time} までにチェックインしていないチームがあると、トーナメントはあなたの確認待ちで保留されます。", + "confirm_heading": "チームの参加を確認してください", + "confirm_hint": "{time} までにチェックインしていないチームはブラケットに入りません。", + "players_progress": "{required}人中{checked}人がチェックイン済み", + "players_heading": "全プレイヤーのチェックインが必要です", + "players_hint": "{count}人のプレイヤーが確認すればチームは参加確定です。控えは不要です。", + "admin_heading": "チームを出席として記録", + "admin_hint": "このトーナメントではプレイヤーが自分でチェックインできません。受付終了までに各チームを出席として記録してください。", + "check_in": "チェックイン", + "mark_present": "出席にする", + "checked_in": "チェックイン済み", + "checked_in_at": "チェックイン済み · {time}", + "waiting": "待機中", + "not_needed": "不要", + "role_captain": "キャプテン", + "role_starter": "スターター", + "role_sub": "控え", + "field_status": "参加チームの状況", + "field_count": "{total}チーム中{checked}チームがチェックイン済み", + "done_heading": "参加確定です。ブラケットは {time} に組まれます。", + "done_hint": "{team} として {time} に確認済み。", + "chip_removed": "除外", + "missed_heading": "チームはチェックインを逃しました", + "missed_hint": "チェックインは {time} に終了し、ブラケットはあなたのチーム抜きで組まれます。", + "missed_contact": "間違いだと思う場合は主催者に連絡してください。トーナメント開始前ならいつでもチームを再承認できます。", + "failed": "チェックインに失敗しました" + }, + "check_in_review": { + "heading": "{count}チームがチェックインを逃しました", + "subheading": "トーナメントは保留中です。ブラケットは組まれておらず、何も削除されていません。各チームはロスターをそのまま保持しています。", + "chip_held_at": "保留中 · {time} に自動再開", + "chip_held": "保留中", + "roster_size": "{count}人", + "partial_roster": "{required}人中{checked}人がチェックイン済み · {short}人不足", + "registered": "登録済み", + "chip_no_check_in": "チェックインなし", + "chip_incomplete": "人数不足", + "readmit": "再承認", + "readmit_failed": "チームを再承認できませんでした", + "continue": "このまま続行", + "continue_failed": "トーナメントを続行できませんでした", + "extend": "チェックインを延長", + "extend_by": "+{minutes}分", + "extend_failed": "チェックインを延長できませんでした", + "extend_hint": "延長すると参加登録が再開し、未チェックインの{count}チームに再通知されます。", + "auto_continue": "誰も操作しなければ、トーナメントは {time} にチェックイン済みのチームだけで自動的に再開します。保留中のトーナメントが、主催者が寝ていたせいで静かに消えることはありません。", + "auto_continue_no_time": "誰も操作しなければ、トーナメントは開始時刻にチェックイン済みのチームだけで自動的に再開します。", + "reseed_warning": "チームを再承認するとブラケットが組み直されます。開始前なら安全ですが、シードは入れ替わります。" + }, + "free_agents": { + "title": "フリーエージェント", + "signed_up": "{count}人が登録済み", + "filter_all": "すべて", + "filter_drafted": "ドラフト済み", + "filter_waitlist": "補欠", + "sign_up": "登録する", + "leave": "プールから抜ける", + "status_drafted": "ドラフト済み", + "status_waitlisted": "補欠", + "status_registered": "登録済み", + "empty_title": "フリーエージェントはまだいません", + "empty_description": "個人で登録したプレイヤーがここに表示され、参加登録の締切時に均等なチームへドラフトされます。", + "waitlist_explainer": "{name} はレーティング最上位ですが、まだ補欠です。枠が埋まった後に登録したためです。枠を得られるかは登録の早さで決まり、ELO はどのチームになるかだけを決めます。これはルールどおりの動作であり、不具合ではありません。", + "organizer": "主催者", + "draft_summary": "フリーエージェント{agents}人 → {size}人編成のチーム{teams}組、補欠{waitlisted}人。", + "regenerate": "チームを再編成", + "regenerated": "{count}チームを編成しました", + "regenerate_failed": "チームを再編成できませんでした", + "join_failed": "フリーエージェントプールに参加できませんでした", + "leave_failed": "フリーエージェントプールから抜けられませんでした" } }, "team": { diff --git a/i18n/locales/ko_KR.json b/i18n/locales/ko_KR.json index d0decfcd0..5d9193c12 100644 --- a/i18n/locales/ko_KR.json +++ b/i18n/locales/ko_KR.json @@ -1229,6 +1229,10 @@ }, "system_telemetry": { "title": "플릿 텔레메트리", + "filter": { + "all": "전체 플릿", + "self_hosted": "셀프 호스팅만" + }, "no_data": "아직 보고된 데이터가 없습니다", "installs": { "title": "패널", @@ -3967,7 +3971,9 @@ }, "picker": { "search": "맵 찾기", - "none": "레이더가 있는 맵이 없습니다" + "none": "레이더가 있는 맵이 없습니다", + "count_public": "이 맵의 공개 라인업", + "count_private": "비공개이지만 볼 수 있는 항목 — 내 것, 팀의 것, 검토 대기 중. 보관된 항목은 포함되지 않습니다." }, "back_to_map": "맵으로 돌아가기", "empty": { @@ -5946,13 +5952,31 @@ "teams": "팀", "format": "형식", "starts": "시작", - "location": "위치" + "location": "위치", + "title": "통계", + "leaderboard_title": "선수 리더보드", + "matches": "매치", + "rounds": "라운드", + "players": "플레이어", + "top_rating": "최고 레이팅", + "col_team": "팀", + "col_rating": "레이팅", + "col_adr": "ADR", + "col_kills": "K", + "col_deaths": "D", + "col_assists": "A", + "col_kd": "K/D", + "col_hs": "HS%", + "col_maps": "맵", + "empty_title": "아직 통계가 없습니다", + "empty_description": "토너먼트 매치가 끝날 때마다 리더보드가 채워집니다." }, "wizard": { "information": "정보", "location": "위치", "match_options": "매치 옵션", - "prizes": "상금" + "prizes": "상금", + "registration": "참가 신청" }, "banner": { "label": "배너", @@ -6006,6 +6030,173 @@ "expand": "펼치기", "collapse_all": "모두 접기", "expand_all": "모두 펼치기" + }, + "registration": { + "section": "참가 신청", + "who": { + "label": "누가 참가 신청할 수 있나요?", + "teams": { + "label": "팀만", + "description": "팀장이 기존 팀으로 신청합니다. 기본값입니다." + }, + "free_agents": { + "label": "프리 에이전트", + "description": "선수가 혼자 신청하고, 참가 신청이 마감되면 균형 잡힌 팀으로 드래프트됩니다." + }, + "both": { + "label": "팀 + 프리 에이전트", + "description": "둘 다입니다. 남은 프리 에이전트는 신청한 팀들과 함께 추가 팀으로 드래프트됩니다." + } + }, + "min_role": { + "label": "최소 역할", + "none": "제한 없음", + "description": "클릭한 사람뿐 아니라 추가되는 선수를 기준으로 확인합니다. 그래서 팀장이 자격 없는 선수를 로스터에 넣을 수 없습니다." + }, + "rank": { + "label": "ELO 범위", + "min": "ELO 최소", + "max": "ELO 최대", + "description": "한쪽을 비우면 그쪽 제한이 없습니다. 둘 다 비우면 모든 랭크에 열립니다.", + "invalid_range": "ELO 최대는 ELO 최소 이상이어야 합니다." + }, + "regions": { + "label": "선호 지역", + "description": "이 토너먼트의 매치가 호스팅되는 곳입니다. 선수를 거르는 조건이 아닙니다 — 5stack에는 선수별 지역 데이터가 없습니다.", + "empty": "현재 이용 가능한 지역이 없습니다." + }, + "invite_only": { + "label": "초대 전용", + "description": "참가 신청이 외부에 공개되지 않습니다. 주최자가 팀을 직접 초대하거나, 참가자가 참가 코드를 입력합니다." + }, + "passcode": { + "label": "참가 코드", + "optional": "(선택)", + "description": "코드를 아는 사람은 초대 없이 참가할 수 있습니다. 주최자 전용입니다." + } + }, + "check_in": { + "section": "체크인", + "required": { + "label": "체크인 필수", + "description": "대진표를 짜기 전에 팀이 참가를 확정합니다. 창은 일정에 맞춰 열리고, 확정은 수동입니다." + }, + "who": { + "label": "누가 체크인하나요?", + "Captains": { + "label": "팀장", + "description": "팀장이 팀 전체를 대신해 확정합니다. 클릭 한 번에 한 팀." + }, + "Players": { + "label": "모든 선수", + "description": "로스터의 각 선수가 개별로 확정합니다. {count}명이 체크인하면 팀이 확정됩니다 — 교체 선수는 하지 않아도 됩니다." + }, + "Admin": { + "label": "주최자만", + "description": "선수는 직접 체크인할 수 없고, 회원님이 팀을 참석으로 표시합니다." + } + }, + "opens": { + "label": "창 열림" + }, + "closes": { + "label": "창 닫힘" + }, + "minutes_before_start": "시작 몇 분 전", + "window_too_short": "창은 최소 5분 동안 열려 있어야 합니다.", + "timeline": { + "opens": "창 열림", + "closes": "창 닫힘", + "start": "토너먼트 시작" + }, + "lock_note": "창이 열리면 시작 시각과 두 오프셋이 모두 잠깁니다. 팀들이 이미 이 일정에 맞춰 움직이고 있어서, 진행 중에 옮기면 이미 흐르고 있는 마감이 밀리게 됩니다.", + "late_registration_note": "창이 열려 있는 동안 신청한 팀은 자동으로 체크인 처리됩니다 — 본 적도 없는 안내를 놓쳤다고 불이익을 줄 수는 없습니다.", + "no_show_note": "불참 팀은 절대 삭제되지 않습니다. 체크인을 놓친 팀은 로스터를 그대로 유지하고 대진표에만 들어가지 않습니다 — 토너먼트가 시작되기 전이라면 언제든 재승인할 수 있습니다.", + "frozen": "체크인이 이미 시작되어 시작 시각과 두 오프셋이 잠겨 있습니다. 대신 체크인 창을 연장하세요.", + "required_title": "이 토너먼트는 체크인이 필요합니다.", + "required_window": "당일 {opens}부터 {closes} 사이에 팀이 확정해야 하며, 그러지 않으면 대진표에 들어가지 않습니다.", + "register_heading": "팀 참가 신청", + "register_hint": "체크인이 열리면 알림이, 닫히기 전에는 리마인더가 갑니다.", + "register_cta": "팀 신청", + "chip_required": "체크인 필요", + "opens_at_heading": "체크인은 {time}에 열립니다", + "opens_at_hint": "팀이 신청되어 있습니다. 창이 열리면 알려드리겠습니다.", + "opens_in": "열리기까지", + "closes_in": "닫히기까지", + "starts_in": "시작까지", + "chip_action_needed": "조치 필요", + "chip_open": "체크인 진행 중", + "observer_heading": "체크인이 열렸습니다", + "observer_hint": "대진표를 짜기 전에 팀이 참가를 확정합니다. {time}까지 체크인하지 않은 팀이 있으면 토너먼트는 회원님의 검토를 기다리며 보류됩니다.", + "confirm_heading": "팀의 참가를 확정하세요", + "confirm_hint": "{time}까지 체크인하지 않은 팀은 대진표에 들어가지 않습니다.", + "players_progress": "{required}명 중 {checked}명 체크인", + "players_heading": "모든 선수가 체크인해야 합니다", + "players_hint": "{count}명의 선수가 확정하면 팀이 확정됩니다. 교체 선수는 하지 않아도 됩니다.", + "admin_heading": "팀을 참석으로 표시", + "admin_hint": "이 토너먼트에서는 선수가 직접 체크인할 수 없습니다. 창이 닫히기 전에 각 팀을 참석으로 표시하세요.", + "check_in": "체크인", + "mark_present": "참석으로 표시", + "checked_in": "체크인 완료", + "checked_in_at": "체크인 완료 · {time}", + "waiting": "대기 중", + "not_needed": "필요 없음", + "role_captain": "팀장", + "role_starter": "주전", + "role_sub": "교체", + "field_status": "참가 팀 현황", + "field_count": "{total}팀 중 {checked}팀 체크인", + "done_heading": "참가가 확정되었습니다. 대진표는 {time}에 짜입니다.", + "done_hint": "{team} 소속으로 {time}에 확정됨.", + "chip_removed": "제외됨", + "missed_heading": "팀이 체크인을 놓쳤습니다", + "missed_hint": "체크인은 {time}에 마감되었고, 대진표는 회원님의 팀 없이 짜입니다.", + "missed_contact": "잘못되었다고 생각되면 주최자에게 연락하세요 — 토너먼트가 시작되기 전이라면 언제든 팀을 재승인할 수 있습니다.", + "failed": "체크인에 실패했습니다" + }, + "check_in_review": { + "heading": "{count}개 팀이 체크인을 놓쳤습니다", + "subheading": "토너먼트가 보류 중입니다. 대진표는 짜이지 않았고 삭제된 것도 없습니다 — 모든 팀이 로스터를 그대로 유지합니다.", + "chip_held_at": "보류 · {time}에 자동 진행", + "chip_held": "보류", + "roster_size": "{count}명", + "partial_roster": "{required}명 중 {checked}명 체크인 · {short}명 부족", + "registered": "신청 완료", + "chip_no_check_in": "체크인 없음", + "chip_incomplete": "인원 부족", + "readmit": "재승인", + "readmit_failed": "팀을 재승인하지 못했습니다", + "continue": "빼고 진행", + "continue_failed": "토너먼트를 진행하지 못했습니다", + "extend": "체크인 연장", + "extend_by": "+{minutes}분", + "extend_failed": "체크인을 연장하지 못했습니다", + "extend_hint": "연장하면 참가 신청이 다시 열리고, 체크인하지 않은 {count}개 팀에 다시 알림이 갑니다.", + "auto_continue": "아무도 조치하지 않으면 토너먼트는 {time}에 체크인한 팀들만으로 알아서 진행됩니다. 보류된 토너먼트가 주최자가 잠들었다는 이유로 조용히 사라지는 일은 없습니다.", + "auto_continue_no_time": "아무도 조치하지 않으면 토너먼트는 시작 시각에 체크인한 팀들만으로 알아서 진행됩니다.", + "reseed_warning": "팀을 재승인하면 대진표를 다시 짭니다. 시작 전이라 안전하지만 시드는 다시 섞입니다." + }, + "free_agents": { + "title": "프리 에이전트", + "signed_up": "{count}명 신청", + "filter_all": "전체", + "filter_drafted": "드래프트됨", + "filter_waitlist": "대기 명단", + "sign_up": "신청하기", + "leave": "풀에서 나가기", + "status_drafted": "드래프트됨", + "status_waitlisted": "대기 명단", + "status_registered": "신청 완료", + "empty_title": "아직 프리 에이전트가 없습니다", + "empty_description": "혼자 신청한 선수가 여기에 표시되고, 참가 신청이 마감되면 균형 잡힌 팀으로 드래프트됩니다.", + "waitlist_explainer": "{name} 님은 레이팅이 가장 높은데도 대기 명단에 있습니다 — 자리가 찬 뒤에 신청했기 때문입니다. 자리를 얻는 기준은 신청 시각이고, ELO는 어느 팀에 갈지만 정합니다. 버그가 아니라 규칙대로 동작한 것입니다.", + "organizer": "주최자", + "draft_summary": "프리 에이전트 {agents}명 → {size}명 구성 {teams}개 팀, 대기 명단 {waitlisted}명.", + "regenerate": "팀 다시 구성", + "regenerated": "{count}개 팀을 구성했습니다", + "regenerate_failed": "팀을 다시 구성하지 못했습니다", + "join_failed": "프리 에이전트 풀에 참가하지 못했습니다", + "leave_failed": "프리 에이전트 풀에서 나가지 못했습니다" } }, "team": { diff --git a/i18n/locales/pl_PL.json b/i18n/locales/pl_PL.json index 3057162b3..2681ec54a 100644 --- a/i18n/locales/pl_PL.json +++ b/i18n/locales/pl_PL.json @@ -1229,6 +1229,10 @@ }, "system_telemetry": { "title": "Telemetria floty", + "filter": { + "all": "Cała flota", + "self_hosted": "Tylko self-hosted" + }, "no_data": "Brak zgłoszonych danych", "installs": { "title": "Panele", @@ -3967,7 +3971,9 @@ }, "picker": { "search": "Znajdź mapę", - "none": "Brak map z radarem" + "none": "Brak map z radarem", + "count_public": "Publiczne ustawienia na tej mapie", + "count_private": "Niepubliczne, ale widoczne dla ciebie — twoje, twojej drużyny lub oczekujące na przegląd. Zarchiwizowane nie są liczone." }, "back_to_map": "Powrót do mapy", "empty": { @@ -5946,13 +5952,31 @@ "teams": "Drużyny", "format": "Format", "starts": "Start", - "location": "Lokalizacja" + "location": "Lokalizacja", + "title": "Statystyki", + "leaderboard_title": "Ranking graczy", + "matches": "Mecze", + "rounds": "Rundy", + "players": "Gracze", + "top_rating": "Najlepszy rating", + "col_team": "Drużyna", + "col_rating": "Rating", + "col_adr": "ADR", + "col_kills": "Z", + "col_deaths": "Ś", + "col_assists": "A", + "col_kd": "K/D", + "col_hs": "HS%", + "col_maps": "Mapy", + "empty_title": "Brak statystyk", + "empty_description": "Ranking wypełnia się w miarę kończenia meczów turnieju." }, "wizard": { "information": "Informacje", "location": "Lokalizacja", "match_options": "Opcje meczu", - "prizes": "Nagrody" + "prizes": "Nagrody", + "registration": "Rejestracja" }, "banner": { "label": "Baner", @@ -6006,6 +6030,173 @@ "expand": "Rozwiń", "collapse_all": "Zwiń wszystko", "expand_all": "Rozwiń wszystko" + }, + "registration": { + "section": "Rejestracja", + "who": { + "label": "Kto może się zarejestrować?", + "teams": { + "label": "Tylko drużyny", + "description": "Kapitanowie rejestrują istniejącą drużynę. Ustawienie domyślne." + }, + "free_agents": { + "label": "Wolni zawodnicy", + "description": "Gracze zapisują się sami i po zamknięciu rejestracji trafiają w drafcie do wyrównanych drużyn." + }, + "both": { + "label": "Drużyny + wolni zawodnicy", + "description": "Obie opcje. Pozostali wolni zawodnicy trafiają w drafcie do dodatkowych drużyn obok tych zarejestrowanych." + } + }, + "min_role": { + "label": "Minimalna rola", + "none": "Bez ograniczeń", + "description": "Sprawdzana dla dodawanego gracza, a nie tylko dla tego, kto klika — dzięki temu kapitan nie wstawi do składu graczy bez uprawnień." + }, + "rank": { + "label": "Zakres ELO", + "min": "ELO min", + "max": "ELO maks", + "description": "Zostaw jedno pole puste, aby nie ustawiać granicy. Oba puste oznaczają otwarte dla każdej rangi.", + "invalid_range": "ELO maks musi być co najmniej równe ELO min." + }, + "regions": { + "label": "Preferowane regiony", + "description": "Gdzie hostowane są mecze tego turnieju. To nie jest filtr graczy — 5stack nie ma danych o regionie dla pojedynczego gracza.", + "empty": "Obecnie brak dostępnych regionów" + }, + "invite_only": { + "label": "Tylko na zaproszenie", + "description": "Rejestracja jest zamknięta dla publiczności. Organizatorzy zapraszają drużyny bezpośrednio albo uczestnicy podają kod dostępu." + }, + "passcode": { + "label": "Kod dostępu", + "optional": "(opcjonalnie)", + "description": "Każdy, kto ma kod, wejdzie bez zaproszenia. Tylko dla organizatorów." + } + }, + "check_in": { + "section": "Potwierdzenie obecności", + "required": { + "label": "Wymagaj potwierdzenia obecności", + "description": "Drużyny potwierdzają udział, zanim powstanie drabinka. Okno otwiera się zgodnie z harmonogramem; potwierdzenie jest ręczne." + }, + "who": { + "label": "Kto potwierdza obecność?", + "Captains": { + "label": "Kapitanowie", + "description": "Kapitan potwierdza za całą drużynę. Jedno kliknięcie, jedna drużyna." + }, + "Players": { + "label": "Każdy gracz", + "description": "Każdy gracz ze składu potwierdza osobno. Drużyna jest w turnieju, gdy obecność potwierdzi {count} — rezerwowi nie muszą." + }, + "Admin": { + "label": "Tylko organizator", + "description": "Gracze nie mogą potwierdzić obecności samodzielnie; to ty oznaczasz drużyny jako obecne." + } + }, + "opens": { + "label": "Okno otwiera się" + }, + "closes": { + "label": "Okno zamyka się" + }, + "minutes_before_start": "minut przed startem", + "window_too_short": "Okno musi pozostać otwarte przez co najmniej 5 minut.", + "timeline": { + "opens": "Okno otwiera się", + "closes": "Okno zamyka się", + "start": "Start turnieju" + }, + "lock_note": "Gdy okno się otworzy, godzina startu i oba przesunięcia zostają zablokowane. Drużyny już działają według tego harmonogramu, więc zmiana w trakcie przesunęłaby trwający termin.", + "late_registration_note": "Drużyna, która rejestruje się w trakcie okna, ma obecność potwierdzoną automatycznie — nie można jej ukarać za monit, którego nigdy nie zobaczyła.", + "no_show_note": "Nieobecne drużyny nigdy nie są usuwane. Drużyna, która przegapi potwierdzenie, zachowuje skład i po prostu nie trafia do drabinki — możesz ją przywrócić w dowolnej chwili przed startem turnieju.", + "frozen": "Potwierdzanie obecności już się rozpoczęło, więc godzina startu i oba przesunięcia są zablokowane. Zamiast tego wydłuż okno potwierdzania.", + "required_title": "Ten turniej wymaga potwierdzenia obecności.", + "required_window": "Twoja drużyna musi potwierdzić między {opens} a {closes} tego dnia, inaczej nie trafi do drabinki.", + "register_heading": "Zarejestruj drużynę", + "register_hint": "Dostaniesz powiadomienie, gdy otworzy się potwierdzanie obecności, oraz przypomnienie przed jego zamknięciem.", + "register_cta": "Zarejestruj drużynę", + "chip_required": "wymagane potwierdzenie", + "opens_at_heading": "Potwierdzanie obecności otwiera się o {time}", + "opens_at_hint": "Twoja drużyna jest zarejestrowana. Damy znać, gdy otworzy się okno.", + "opens_in": "otwiera się za", + "closes_in": "zamyka się za", + "starts_in": "zaczyna się za", + "chip_action_needed": "wymagane działanie", + "chip_open": "potwierdzanie otwarte", + "observer_heading": "Potwierdzanie obecności jest otwarte", + "observer_hint": "Drużyny potwierdzają udział, zanim powstanie drabinka. Każda drużyna, która nie potwierdzi obecności do {time}, wstrzymuje turniej do twojej decyzji.", + "confirm_heading": "Potwierdź udział swojej drużyny", + "confirm_hint": "Drużyny, które nie potwierdzą obecności do {time}, nie trafiają do drabinki.", + "players_progress": "{checked} z {required} potwierdziło obecność", + "players_heading": "Każdy gracz musi potwierdzić obecność", + "players_hint": "Twoja drużyna jest w turnieju, gdy potwierdzi {count} graczy. Rezerwowi nie muszą.", + "admin_heading": "Oznacz obecne drużyny", + "admin_hint": "W tym turnieju gracze nie mogą sami potwierdzić obecności. Oznacz każdą drużynę jako obecną, zanim okno się zamknie.", + "check_in": "Potwierdź obecność", + "mark_present": "Oznacz jako obecną", + "checked_in": "obecność potwierdzona", + "checked_in_at": "obecność potwierdzona · {time}", + "waiting": "oczekuje", + "not_needed": "niewymagane", + "role_captain": "kapitan", + "role_starter": "podstawowy", + "role_sub": "rezerwowy", + "field_status": "Status uczestników", + "field_count": "{checked} z {total} potwierdziło obecność", + "done_heading": "Jesteście w turnieju. Drabinka powstaje o {time}.", + "done_hint": "Potwierdzono dla {team} o {time}.", + "chip_removed": "usunięta", + "missed_heading": "Twoja drużyna przegapiła potwierdzenie obecności", + "missed_hint": "Potwierdzanie zamknęło się o {time} i drabinka powstaje bez twojej drużyny.", + "missed_contact": "Skontaktuj się z organizatorem, jeśli uważasz, że to pomyłka — może przywrócić drużynę w dowolnej chwili przed startem turnieju.", + "failed": "Nie udało się potwierdzić obecności" + }, + "check_in_review": { + "heading": "{count} drużyn przegapiło potwierdzenie obecności", + "subheading": "Turniej jest wstrzymany. Nic nie zostało rozstawione ani usunięte — każda drużyna zachowuje pełny skład.", + "chip_held_at": "wstrzymany · sam ruszy o {time}", + "chip_held": "wstrzymany", + "roster_size": "{count} graczy", + "partial_roster": "{checked} z {required} potwierdziło · brakuje {short}", + "registered": "zarejestrowana", + "chip_no_check_in": "brak potwierdzenia", + "chip_incomplete": "niepełna", + "readmit": "Przywróć", + "readmit_failed": "Nie udało się przywrócić drużyny", + "continue": "Kontynuuj bez nich", + "continue_failed": "Nie udało się kontynuować turnieju", + "extend": "Wydłuż potwierdzanie", + "extend_by": "+{minutes} minut", + "extend_failed": "Nie udało się wydłużyć potwierdzania", + "extend_hint": "Wydłużenie ponownie otwiera rejestrację i powiadamia {count} brakujących drużyn.", + "auto_continue": "Jeśli nikt nie zareaguje, turniej ruszy sam o {time} z tymi, którzy potwierdzili obecność. Wstrzymany turniej nigdy nie umrze po cichu dlatego, że organizator spał.", + "auto_continue_no_time": "Jeśli nikt nie zareaguje, turniej ruszy sam o swojej godzinie startu z tymi, którzy potwierdzili obecność.", + "reseed_warning": "Przywrócenie drużyny rozstawia drabinkę od nowa. Przed startem jest to bezpieczne, ale zmienia rozstawienie." + }, + "free_agents": { + "title": "Wolni zawodnicy", + "signed_up": "zapisanych: {count}", + "filter_all": "Wszyscy", + "filter_drafted": "Wybrani w drafcie", + "filter_waitlist": "Lista rezerwowa", + "sign_up": "Zapisz się", + "leave": "Opuść pulę", + "status_drafted": "wybrany w drafcie", + "status_waitlisted": "lista rezerwowa", + "status_registered": "zarejestrowany", + "empty_title": "Brak wolnych zawodników", + "empty_description": "Gracze, którzy zapisują się sami, pojawiają się tutaj i po zamknięciu rejestracji trafiają w drafcie do wyrównanych drużyn.", + "waitlist_explainer": "{name} ma najwyższy rating i wciąż jest na liście rezerwowej — zapisał się po tym, jak miejsca się zapełniły. O miejscu decyduje czas zapisu, a ELO tylko o tym, do której drużyny trafisz. Tak działa ta zasada, to nie błąd.", + "organizer": "Organizator", + "draft_summary": "{agents} wolnych zawodników → {teams} drużyn po {size}, {waitlisted} na liście rezerwowej.", + "regenerate": "Ułóż drużyny od nowa", + "regenerated": "Ułożono drużyn: {count}", + "regenerate_failed": "Nie udało się ułożyć drużyn od nowa", + "join_failed": "Nie udało się dołączyć do puli wolnych zawodników", + "leave_failed": "Nie udało się opuścić puli wolnych zawodników" } }, "team": { diff --git a/i18n/locales/pt_BR.json b/i18n/locales/pt_BR.json index ce684b2aa..4d64270f5 100644 --- a/i18n/locales/pt_BR.json +++ b/i18n/locales/pt_BR.json @@ -1229,6 +1229,10 @@ }, "system_telemetry": { "title": "Telemetria da Frota", + "filter": { + "all": "Toda a frota", + "self_hosted": "Somente auto-hospedados" + }, "no_data": "Nenhum dado reportado ainda", "installs": { "title": "Painéis", @@ -3967,7 +3971,9 @@ }, "picker": { "search": "Encontrar um mapa", - "none": "Nenhum mapa com radar" + "none": "Nenhum mapa com radar", + "count_public": "Lineups públicas neste mapa", + "count_private": "Não públicas, mas visíveis para você: suas, do seu time ou aguardando revisão. As arquivadas não são contadas." }, "back_to_map": "Voltar ao mapa", "empty": { @@ -5946,13 +5952,31 @@ "teams": "Equipes", "format": "Formato", "starts": "Início", - "location": "Local" + "location": "Local", + "title": "Estatísticas", + "leaderboard_title": "Ranking de jogadores", + "matches": "Partidas", + "rounds": "Rodadas", + "players": "Jogadores", + "top_rating": "Melhor rating", + "col_team": "Equipe", + "col_rating": "Rating", + "col_adr": "ADR", + "col_kills": "K", + "col_deaths": "D", + "col_assists": "A", + "col_kd": "K/D", + "col_hs": "HS%", + "col_maps": "Mapas", + "empty_title": "Ainda não há estatísticas", + "empty_description": "O ranking vai sendo preenchido conforme as partidas do torneio terminam." }, "wizard": { "information": "Informações", "location": "Local", "match_options": "Opções de Partida", - "prizes": "Premiação" + "prizes": "Premiação", + "registration": "Inscrições" }, "banner": { "label": "Banner", @@ -6006,6 +6030,173 @@ "expand": "Expandir", "collapse_all": "Recolher todos", "expand_all": "Expandir todos" + }, + "registration": { + "section": "Inscrições", + "who": { + "label": "Quem pode se inscrever?", + "teams": { + "label": "Somente equipes", + "description": "Os capitães inscrevem uma equipe já existente. É o padrão." + }, + "free_agents": { + "label": "Agentes livres", + "description": "Os jogadores se inscrevem sozinhos e são draftados em equipes equilibradas quando as inscrições fecham." + }, + "both": { + "label": "Equipes + agentes livres", + "description": "Os dois. Os agentes livres restantes são draftados em equipes extras, junto com as já inscritas." + } + }, + "min_role": { + "label": "Cargo mínimo", + "none": "Sem restrição", + "description": "Verificado no jogador que está sendo adicionado, não só em quem clica — assim um capitão não consegue escalar jogadores inelegíveis." + }, + "rank": { + "label": "Faixa de ELO", + "min": "ELO mín.", + "max": "ELO máx.", + "description": "Deixe qualquer um em branco para não ter limite. Os dois em branco: aberto a qualquer rank.", + "invalid_range": "O ELO máx. precisa ser pelo menos igual ao ELO mín." + }, + "regions": { + "label": "Regiões/Locais preferidos", + "description": "Onde as partidas deste torneio são hospedadas. Não é um filtro de jogador — a 5stack não tem dados de região por jogador.", + "empty": "Nenhuma região/local disponível no momento." + }, + "invite_only": { + "label": "Somente por convite", + "description": "As inscrições não são públicas. Os organizadores convidam as equipes diretamente, ou os participantes informam um código de acesso." + }, + "passcode": { + "label": "Código de acesso", + "optional": "(opcional)", + "description": "Quem tiver o código entra sem convite. Apenas organizadores." + } + }, + "check_in": { + "section": "Check-In", + "required": { + "label": "Exigir check-in", + "description": "As equipes confirmam presença antes de a chave ser montada. A janela abre conforme o cronograma; confirmar é manual." + }, + "who": { + "label": "Quem faz o check-in?", + "Captains": { + "label": "Capitães", + "description": "O capitão confirma pela equipe inteira. Um clique, uma equipe." + }, + "Players": { + "label": "Todos os jogadores", + "description": "Cada jogador escalado confirma individualmente. A equipe entra assim que {count} fizerem check-in — os reservas não precisam." + }, + "Admin": { + "label": "Somente o organizador", + "description": "Os jogadores não podem fazer o próprio check-in; você marca as equipes como presentes." + } + }, + "opens": { + "label": "A janela abre" + }, + "closes": { + "label": "A janela fecha" + }, + "minutes_before_start": "minutos antes do início", + "window_too_short": "A janela precisa ficar aberta por pelo menos 5 minutos.", + "timeline": { + "opens": "A janela abre", + "closes": "A janela fecha", + "start": "O torneio começa" + }, + "lock_note": "Assim que a janela abre, o horário de início e os dois intervalos ficam travados. As equipes já estão se guiando por esse cronograma, então mudá-lo no meio da janela deslocaria um prazo que já está correndo.", + "late_registration_note": "Uma equipe que se inscreve durante a janela entra com o check-in feito automaticamente — ela não pode ser penalizada por um aviso que nunca viu.", + "no_show_note": "Ausentes nunca são excluídos. Uma equipe que perde o check-in mantém sua escalação e apenas não entra na chave — você pode readmiti-la a qualquer momento antes de o torneio começar.", + "frozen": "O check-in já abriu, então o horário de início e os dois intervalos estão travados. Estenda a janela de check-in em vez disso.", + "required_title": "Este torneio exige check-in.", + "required_window": "Sua equipe precisa confirmar entre {opens} e {closes} no dia, senão não entrará na chave.", + "register_heading": "Inscreva sua equipe", + "register_hint": "Você receberá uma notificação quando o check-in abrir e um lembrete antes de ele fechar.", + "register_cta": "Inscrever equipe", + "chip_required": "check-in obrigatório", + "opens_at_heading": "O check-in abre às {time}", + "opens_at_hint": "Sua equipe está inscrita. Avisaremos quando a janela abrir.", + "opens_in": "abre em", + "closes_in": "fecha em", + "starts_in": "começa em", + "chip_action_needed": "ação necessária", + "chip_open": "check-in aberto", + "observer_heading": "O check-in está aberto", + "observer_hint": "As equipes confirmam presença antes de a chave ser montada. Qualquer equipe que não tiver feito check-in até {time} deixa o torneio parado para a sua análise.", + "confirm_heading": "Confirme a presença da sua equipe", + "confirm_hint": "Equipes que não fizerem check-in até {time} não entram na chave.", + "players_progress": "{checked} de {required} com check-in", + "players_heading": "Todos os jogadores precisam fazer check-in", + "players_hint": "Sua equipe entra assim que {count} jogadores confirmarem. Os reservas não precisam.", + "admin_heading": "Marque as equipes presentes", + "admin_hint": "Neste torneio os jogadores não podem fazer o próprio check-in. Marque cada equipe como presente antes de a janela fechar.", + "check_in": "Fazer check-in", + "mark_present": "Marcar presente", + "checked_in": "check-in feito", + "checked_in_at": "check-in feito · {time}", + "waiting": "aguardando", + "not_needed": "não precisa", + "role_captain": "capitão", + "role_starter": "titular", + "role_sub": "reserva", + "field_status": "Situação dos participantes", + "field_count": "{checked} de {total} com check-in", + "done_heading": "Vocês estão dentro. A chave é montada às {time}.", + "done_hint": "Confirmado pela {team} às {time}.", + "chip_removed": "removida", + "missed_heading": "Sua equipe perdeu o check-in", + "missed_hint": "O check-in fechou às {time} e a chave é montada sem a sua equipe.", + "missed_contact": "Fale com um organizador se achar que isso está errado — ele pode readmitir uma equipe a qualquer momento antes de o torneio começar.", + "failed": "Falha no check-in" + }, + "check_in_review": { + "heading": "{count} equipes perderam o check-in", + "subheading": "O torneio está parado. Nada foi montado e nada foi excluído — cada equipe mantém sua escalação completa.", + "chip_held_at": "parado · continua sozinho às {time}", + "chip_held": "parado", + "roster_size": "{count} jogadores", + "partial_roster": "{checked} de {required} com check-in · faltam {short}", + "registered": "inscrita", + "chip_no_check_in": "sem check-in", + "chip_incomplete": "incompleta", + "readmit": "Readmitir", + "readmit_failed": "Falha ao readmitir a equipe", + "continue": "Continuar sem elas", + "continue_failed": "Falha ao continuar o torneio", + "extend": "Estender o check-in", + "extend_by": "+{minutes} minutos", + "extend_failed": "Falha ao estender o check-in", + "extend_hint": "Estender reabre as inscrições e avisa de novo as {count} equipes que faltam.", + "auto_continue": "Se ninguém agir, o torneio continua sozinho às {time} com quem fez check-in. Um torneio parado nunca morre em silêncio porque o organizador estava dormindo.", + "auto_continue_no_time": "Se ninguém agir, o torneio continua sozinho no horário de início com quem fez check-in.", + "reseed_warning": "Readmitir uma equipe monta a chave de novo. Antes do início isso é seguro, mas embaralha os cabeças de chave." + }, + "free_agents": { + "title": "Agentes livres", + "signed_up": "{count} inscritos", + "filter_all": "Todos", + "filter_drafted": "Draftados", + "filter_waitlist": "Lista de espera", + "sign_up": "Inscrever-se", + "leave": "Sair do grupo", + "status_drafted": "draftado", + "status_waitlisted": "lista de espera", + "status_registered": "inscrito", + "empty_title": "Ainda não há agentes livres", + "empty_description": "Os jogadores que se inscrevem sozinhos aparecem aqui e são draftados em equipes equilibradas quando as inscrições fecham.", + "waitlist_explainer": "{name} tem o melhor rating e ainda está na lista de espera — se inscreveu depois que as vagas encheram. O horário da inscrição decide quem pega vaga; o ELO decide só em qual equipe. É a regra funcionando, não um bug.", + "organizer": "Organizador", + "draft_summary": "{agents} agentes livres → {teams} equipes de {size}, {waitlisted} na lista de espera.", + "regenerate": "Refazer as equipes", + "regenerated": "{count} equipes draftadas", + "regenerate_failed": "Falha ao refazer as equipes", + "join_failed": "Falha ao entrar no grupo de agentes livres", + "leave_failed": "Falha ao sair do grupo de agentes livres" } }, "team": { diff --git a/i18n/locales/ru_RU.json b/i18n/locales/ru_RU.json index 79489fd2a..698250ef8 100644 --- a/i18n/locales/ru_RU.json +++ b/i18n/locales/ru_RU.json @@ -1229,6 +1229,10 @@ }, "system_telemetry": { "title": "Телеметрия парка", + "filter": { + "all": "Весь парк", + "self_hosted": "Только self-hosted" + }, "no_data": "Данные пока не поступали", "installs": { "title": "Панели", @@ -3967,7 +3971,9 @@ }, "picker": { "search": "Найти карту", - "none": "Нет карт с радаром" + "none": "Нет карт с радаром", + "count_public": "Публичные раскидки на этой карте", + "count_private": "Непубличные, но видимые вам — ваши, вашей команды или на проверке. Архивные не учитываются." }, "back_to_map": "Назад к карте", "empty": { @@ -5946,13 +5952,31 @@ "teams": "Команды", "format": "Формат", "starts": "Начало", - "location": "Локация" + "location": "Локация", + "title": "Статистика", + "leaderboard_title": "Таблица лидеров игроков", + "matches": "Матчи", + "rounds": "Раунды", + "players": "Игроки", + "top_rating": "Лучший рейтинг", + "col_team": "Команда", + "col_rating": "Рейтинг", + "col_adr": "ADR", + "col_kills": "У", + "col_deaths": "С", + "col_assists": "А", + "col_kd": "K/D", + "col_hs": "HS%", + "col_maps": "Карты", + "empty_title": "Статистики пока нет", + "empty_description": "Таблица лидеров заполняется по мере завершения матчей турнира." }, "wizard": { "information": "Информация", "location": "Локация", "match_options": "Настройки матча", - "prizes": "Призы" + "prizes": "Призы", + "registration": "Регистрация" }, "banner": { "label": "Баннер", @@ -6006,6 +6030,173 @@ "expand": "Развернуть", "collapse_all": "Свернуть всё", "expand_all": "Развернуть всё" + }, + "registration": { + "section": "Регистрация", + "who": { + "label": "Кто может зарегистрироваться?", + "teams": { + "label": "Только команды", + "description": "Капитаны регистрируют существующую команду. Вариант по умолчанию." + }, + "free_agents": { + "label": "Свободные агенты", + "description": "Игроки записываются поодиночке, и после закрытия регистрации их драфтом распределяют по равным командам." + }, + "both": { + "label": "Команды + свободные агенты", + "description": "И то и другое. Оставшихся свободных агентов драфтом собирают в дополнительные команды рядом с уже зарегистрированными." + } + }, + "min_role": { + "label": "Минимальная роль", + "none": "Без ограничений", + "description": "Проверяется у добавляемого игрока, а не только у того, кто нажимает, — так капитан не заявит в состав игроков без допуска." + }, + "rank": { + "label": "Диапазон ELO", + "min": "ELO мин.", + "max": "ELO макс.", + "description": "Оставь любое поле пустым, чтобы не задавать границу. Оба пустых — открыто для любого ранга.", + "invalid_range": "ELO макс. должен быть не меньше ELO мин." + }, + "regions": { + "label": "Предпочтительные регионы", + "description": "Где размещаются матчи этого турнира. Это не фильтр по игрокам — у 5stack нет данных о регионе для отдельного игрока.", + "empty": "Сейчас нет доступных регионов." + }, + "invite_only": { + "label": "Только по приглашению", + "description": "Регистрация закрыта для всех. Организаторы приглашают команды напрямую, либо участники вводят код доступа." + }, + "passcode": { + "label": "Код доступа", + "optional": "(необязательно)", + "description": "Любой, у кого есть код, зайдёт без приглашения. Только для организаторов." + } + }, + "check_in": { + "section": "Чек-ин", + "required": { + "label": "Требовать чек-ин", + "description": "Команды подтверждают участие до того, как соберут сетку. Окно открывается по расписанию; подтверждение — вручную." + }, + "who": { + "label": "Кто проходит чек-ин?", + "Captains": { + "label": "Капитаны", + "description": "Капитан подтверждает за всю команду. Один клик — одна команда." + }, + "Players": { + "label": "Каждый игрок", + "description": "Каждый игрок состава подтверждает сам. Команда в турнире, как только отметятся {count} — запасным не нужно." + }, + "Admin": { + "label": "Только организатор", + "description": "Игроки не могут отметиться сами; ты отмечаешь команды как явившиеся." + } + }, + "opens": { + "label": "Окно открывается" + }, + "closes": { + "label": "Окно закрывается" + }, + "minutes_before_start": "минут до старта", + "window_too_short": "Окно должно быть открыто минимум 5 минут.", + "timeline": { + "opens": "Окно открывается", + "closes": "Окно закрывается", + "start": "Старт турнира" + }, + "lock_note": "Как только окно откроется, время старта и оба смещения блокируются. Команды уже ориентируются на это расписание, и сдвиг посреди окна изменил бы дедлайн, который уже идёт.", + "late_registration_note": "Команда, зарегистрировавшаяся во время окна, отмечается автоматически — её нельзя наказать за напоминание, которого она никогда не видела.", + "no_show_note": "Неявившихся никогда не удаляют. Команда, пропустившая чек-ин, сохраняет состав и просто не попадает в сетку — ты можешь восстановить её в любой момент до старта турнира.", + "frozen": "Чек-ин уже открыт, поэтому время старта и оба смещения заблокированы. Вместо этого продли окно чек-ина.", + "required_title": "Этот турнир требует чек-ина.", + "required_window": "Твоя команда должна подтвердить участие в тот же день между {opens} и {closes}, иначе она не попадёт в сетку.", + "register_heading": "Зарегистрируй команду", + "register_hint": "Придёт уведомление, когда чек-ин откроется, и напоминание перед его закрытием.", + "register_cta": "Зарегистрировать команду", + "chip_required": "нужен чек-ин", + "opens_at_heading": "Чек-ин открывается в {time}", + "opens_at_hint": "Твоя команда зарегистрирована. Сообщим, когда откроется окно.", + "opens_in": "откроется через", + "closes_in": "закроется через", + "starts_in": "начнётся через", + "chip_action_needed": "нужно действие", + "chip_open": "чек-ин открыт", + "observer_heading": "Чек-ин открыт", + "observer_hint": "Команды подтверждают участие до того, как соберут сетку. Любая команда, не отметившаяся к {time}, останавливает турнир до твоего решения.", + "confirm_heading": "Подтверди участие своей команды", + "confirm_hint": "Команды, не отметившиеся к {time}, в сетку не попадают.", + "players_progress": "отметилось {checked} из {required}", + "players_heading": "Отметиться должен каждый игрок", + "players_hint": "Твоя команда в турнире, как только подтвердят {count} игроков. Запасным не нужно.", + "admin_heading": "Отметь явившиеся команды", + "admin_hint": "В этом турнире игроки не могут отметиться сами. Отметь каждую команду как явившуюся до закрытия окна.", + "check_in": "Отметиться", + "mark_present": "Отметить как явившуюся", + "checked_in": "отметился", + "checked_in_at": "отметился · {time}", + "waiting": "ожидает", + "not_needed": "не требуется", + "role_captain": "капитан", + "role_starter": "основа", + "role_sub": "запасной", + "field_status": "Статус участников", + "field_count": "отметилось {checked} из {total}", + "done_heading": "Вы в турнире. Сетка соберётся в {time}.", + "done_hint": "Подтверждено за {team} в {time}.", + "chip_removed": "убрана", + "missed_heading": "Твоя команда пропустила чек-ин", + "missed_hint": "Чек-ин закрылся в {time}, и сетка собирается без твоей команды.", + "missed_contact": "Свяжись с организатором, если считаешь это ошибкой — он может восстановить команду в любой момент до старта турнира.", + "failed": "Не удалось отметиться" + }, + "check_in_review": { + "heading": "Команд пропустило чек-ин: {count}", + "subheading": "Турнир остановлен. Сетку не собирали и ничего не удаляли — каждая команда сохраняет полный состав.", + "chip_held_at": "остановлен · продолжится в {time}", + "chip_held": "остановлен", + "roster_size": "игроков: {count}", + "partial_roster": "отметилось {checked} из {required} · не хватает {short}", + "registered": "зарегистрирована", + "chip_no_check_in": "без чек-ина", + "chip_incomplete": "неполный состав", + "readmit": "Восстановить", + "readmit_failed": "Не удалось восстановить команду", + "continue": "Продолжить без них", + "continue_failed": "Не удалось продолжить турнир", + "extend": "Продлить чек-ин", + "extend_by": "+{minutes} минут", + "extend_failed": "Не удалось продлить чек-ин", + "extend_hint": "Продление снова откроет регистрацию и повторно уведомит недостающие команды ({count}).", + "auto_continue": "Если никто ничего не сделает, турнир сам продолжится в {time} с теми, кто отметился. Остановленный турнир никогда не умрёт тихо из-за того, что организатор спал.", + "auto_continue_no_time": "Если никто ничего не сделает, турнир сам продолжится в своё время старта с теми, кто отметился.", + "reseed_warning": "Восстановление команды пересобирает сетку. До начала это безопасно, но посев перемешается." + }, + "free_agents": { + "title": "Свободные агенты", + "signed_up": "записалось: {count}", + "filter_all": "Все", + "filter_drafted": "Взяты на драфте", + "filter_waitlist": "Лист ожидания", + "sign_up": "Записаться", + "leave": "Покинуть пул", + "status_drafted": "взят на драфте", + "status_waitlisted": "лист ожидания", + "status_registered": "зарегистрирован", + "empty_title": "Свободных агентов пока нет", + "empty_description": "Игроки, записавшиеся поодиночке, появляются здесь, и после закрытия регистрации их драфтом распределяют по равным командам.", + "waitlist_explainer": "{name} — участник с самым высоким рейтингом, но всё ещё в листе ожидания: он записался, когда места уже закончились. Место решает время записи, а ELO решает только команду. Это правило работает как надо, а не баг.", + "organizer": "Организатор", + "draft_summary": "{agents} свободных агентов → {teams} команд по {size}, {waitlisted} в листе ожидания.", + "regenerate": "Пересобрать команды", + "regenerated": "Собрано команд: {count}", + "regenerate_failed": "Не удалось пересобрать команды", + "join_failed": "Не удалось войти в пул свободных агентов", + "leave_failed": "Не удалось покинуть пул свободных агентов" } }, "team": { diff --git a/i18n/locales/sv_SE.json b/i18n/locales/sv_SE.json index fbdf812a8..b38b0fe62 100644 --- a/i18n/locales/sv_SE.json +++ b/i18n/locales/sv_SE.json @@ -1229,6 +1229,10 @@ }, "system_telemetry": { "title": "Flottelemetri", + "filter": { + "all": "Hela flottan", + "self_hosted": "Endast egenhostade" + }, "no_data": "Inga data rapporterade än", "installs": { "title": "Paneler", @@ -3967,7 +3971,9 @@ }, "picker": { "search": "Hitta en karta", - "none": "Inga kartor med radar" + "none": "Inga kartor med radar", + "count_public": "Offentliga lineups på den här kartan", + "count_private": "Inte offentliga, men synliga för dig — dina, ditt lags eller under granskning. Arkiverade räknas inte." }, "back_to_map": "Tillbaka till kartan", "empty": { @@ -5946,13 +5952,31 @@ "teams": "Lag", "format": "Format", "starts": "Startar", - "location": "Plats" + "location": "Plats", + "title": "Statistik", + "leaderboard_title": "Spelartopplista", + "matches": "Matcher", + "rounds": "Ronder", + "players": "Spelare", + "top_rating": "Bästa rating", + "col_team": "Lag", + "col_rating": "Rating", + "col_adr": "ADR", + "col_kills": "K", + "col_deaths": "D", + "col_assists": "A", + "col_kd": "K/D", + "col_hs": "HS%", + "col_maps": "Kartor", + "empty_title": "Ingen statistik än", + "empty_description": "Topplistan fylls på efterhand som turneringens matcher spelas klart." }, "wizard": { "information": "Information", "location": "Plats", "match_options": "Matchalternativ", - "prizes": "Priser" + "prizes": "Priser", + "registration": "Anmälan" }, "banner": { "label": "Banner", @@ -6006,6 +6030,173 @@ "expand": "Fäll ut", "collapse_all": "Fäll ihop alla", "expand_all": "Fäll ut alla" + }, + "registration": { + "section": "Anmälan", + "who": { + "label": "Vem får anmäla sig?", + "teams": { + "label": "Endast lag", + "description": "Lagkaptener anmäler ett befintligt lag. Standardvalet." + }, + "free_agents": { + "label": "Free agents", + "description": "Spelare anmäler sig ensamma och draftas in i jämna lag när anmälan stänger." + }, + "both": { + "label": "Lag + free agents", + "description": "Båda. Free agents som blir över draftas in i extra lag vid sidan av de anmälda." + } + }, + "min_role": { + "label": "Lägsta roll", + "none": "Ingen begränsning", + "description": "Kontrolleras mot spelaren som läggs till, inte bara mot den som klickar — så att en lagkapten inte kan ta in spelare som inte får delta." + }, + "rank": { + "label": "ELO-intervall", + "min": "Elo min", + "max": "Elo max", + "description": "Lämna något av fälten tomt för ingen gräns. Båda tomma är öppet för alla ranks.", + "invalid_range": "Elo max måste vara minst lika högt som Elo min." + }, + "regions": { + "label": "Föredragna regioner", + "description": "Var turneringens matcher hostas. Det är inget spelarfilter — 5stack har ingen regionsdata per spelare.", + "empty": "Inga regioner tillgängliga just nu." + }, + "invite_only": { + "label": "Endast med inbjudan", + "description": "Anmälan är stängd för allmänheten. Arrangörerna bjuder in lag direkt, eller så anger deltagarna en åtkomstkod." + }, + "passcode": { + "label": "Åtkomstkod", + "optional": "(valfritt)", + "description": "Alla med koden kommer in utan inbjudan. Endast arrangörer." + } + }, + "check_in": { + "section": "Incheckning", + "required": { + "label": "Kräv incheckning", + "description": "Lagen bekräftar att de deltar innan slutspelsträdet sätts. Fönstret öppnar enligt schemat; att bekräfta är manuellt." + }, + "who": { + "label": "Vem checkar in?", + "Captains": { + "label": "Lagkaptener", + "description": "Lagkaptenen bekräftar för hela laget. Ett klick, ett lag." + }, + "Players": { + "label": "Varje spelare", + "description": "Varje spelare i truppen bekräftar själv. Laget är med så snart {count} har checkat in — reserver behöver inte." + }, + "Admin": { + "label": "Endast arrangören", + "description": "Spelarna kan inte checka in sig själva; du markerar lagen som närvarande." + } + }, + "opens": { + "label": "Fönstret öppnar" + }, + "closes": { + "label": "Fönstret stänger" + }, + "minutes_before_start": "minuter före start", + "window_too_short": "Fönstret måste vara öppet i minst 5 minuter.", + "timeline": { + "opens": "Fönstret öppnar", + "closes": "Fönstret stänger", + "start": "Turneringen startar" + }, + "lock_note": "När fönstret väl har öppnat låses starttiden och båda förskjutningarna. Lagen planerar redan efter det här schemat, så att flytta det mitt i fönstret skulle rubba en deadline som redan löper.", + "late_registration_note": "Ett lag som anmäler sig under fönstret checkas in automatiskt — det kan inte straffas för en uppmaning det aldrig fick se.", + "no_show_note": "Lag som uteblir raderas aldrig. Ett lag som missar incheckningen behåller sin trupp och hamnar bara inte i slutspelsträdet — du kan återinsätta det när som helst innan turneringen drar igång.", + "frozen": "Incheckningen har redan öppnat, så starttiden och båda förskjutningarna är låsta. Förläng incheckningsfönstret i stället.", + "required_title": "Den här turneringen kräver incheckning.", + "required_window": "Ditt lag måste bekräfta mellan {opens} och {closes} på dagen, annars placeras det inte i slutspelsträdet.", + "register_heading": "Anmäl ditt lag", + "register_hint": "Du får en avisering när incheckningen öppnar och en påminnelse innan den stänger.", + "register_cta": "Anmäl lag", + "chip_required": "incheckning krävs", + "opens_at_heading": "Incheckningen öppnar {time}", + "opens_at_hint": "Ditt lag är anmält. Vi hör av oss när fönstret öppnar.", + "opens_in": "öppnar om", + "closes_in": "stänger om", + "starts_in": "startar om", + "chip_action_needed": "åtgärd krävs", + "chip_open": "incheckning öppen", + "observer_heading": "Incheckningen är öppen", + "observer_hint": "Lagen bekräftar att de deltar innan slutspelsträdet sätts. Varje lag som inte har checkat in {time} pausar turneringen för din granskning.", + "confirm_heading": "Bekräfta att ditt lag deltar", + "confirm_hint": "Lag som inte har checkat in {time} placeras inte i slutspelsträdet.", + "players_progress": "{checked} av {required} incheckade", + "players_heading": "Alla spelare måste checka in", + "players_hint": "Ditt lag är med så snart {count} spelare bekräftar. Reserver behöver inte.", + "admin_heading": "Markera lag som närvarande", + "admin_hint": "Spelarna kan inte checka in sig själva i den här turneringen. Markera varje lag som närvarande innan fönstret stänger.", + "check_in": "Checka in", + "mark_present": "Markera närvarande", + "checked_in": "incheckad", + "checked_in_at": "incheckad · {time}", + "waiting": "väntar", + "not_needed": "behövs inte", + "role_captain": "lagkapten", + "role_starter": "startspelare", + "role_sub": "reserv", + "field_status": "Deltagarstatus", + "field_count": "{checked} av {total} incheckade", + "done_heading": "Ni är med. Slutspelsträdet sätts {time}.", + "done_hint": "Bekräftat för {team} {time}.", + "chip_removed": "borttaget", + "missed_heading": "Ditt lag missade incheckningen", + "missed_hint": "Incheckningen stängde {time} och slutspelsträdet sätts utan ditt lag.", + "missed_contact": "Kontakta en arrangör om du tycker att det blivit fel — hen kan återinsätta ett lag när som helst innan turneringen drar igång.", + "failed": "Incheckningen misslyckades" + }, + "check_in_review": { + "heading": "{count} lag missade incheckningen", + "subheading": "Turneringen är pausad. Ingenting har satts och ingenting har raderats — varje lag behåller hela sin trupp.", + "chip_held_at": "pausad · fortsätter {time}", + "chip_held": "pausad", + "roster_size": "{count} spelare", + "partial_roster": "{checked} av {required} incheckade · saknar {short}", + "registered": "anmält", + "chip_no_check_in": "ingen incheckning", + "chip_incomplete": "ofullständigt", + "readmit": "Återinsätt", + "readmit_failed": "Kunde inte återinsätta laget", + "continue": "Fortsätt utan dem", + "continue_failed": "Kunde inte fortsätta turneringen", + "extend": "Förläng incheckningen", + "extend_by": "+{minutes} minuter", + "extend_failed": "Kunde inte förlänga incheckningen", + "extend_hint": "En förlängning öppnar anmälan igen och aviserar de {count} lag som saknas på nytt.", + "auto_continue": "Om ingen gör något fortsätter turneringen av sig själv {time} med dem som checkade in. En pausad turnering kan aldrig dö i tysthet för att en arrangör sov.", + "auto_continue_no_time": "Om ingen gör något fortsätter turneringen av sig själv vid sin starttid med dem som checkade in.", + "reseed_warning": "Att återinsätta ett lag sätter om slutspelsträdet. Det är riskfritt före start, men det blandar om seedningen." + }, + "free_agents": { + "title": "Free agents", + "signed_up": "{count} anmälda", + "filter_all": "Alla", + "filter_drafted": "Draftade", + "filter_waitlist": "Väntelista", + "sign_up": "Anmäl dig", + "leave": "Lämna poolen", + "status_drafted": "draftad", + "status_waitlisted": "väntelista", + "status_registered": "anmäld", + "empty_title": "Inga free agents än", + "empty_description": "Spelare som anmäler sig ensamma dyker upp här och draftas in i jämna lag när anmälan stänger.", + "waitlist_explainer": "{name} har högst rating och står ändå på väntelistan — anmälan kom efter att platserna fyllts. Anmälningstiden avgör vem som får en plats; ELO avgör bara vilket lag. Det är regeln som fungerar, inte en bugg.", + "organizer": "Arrangör", + "draft_summary": "{agents} free agents → {teams} lag på {size}, {waitlisted} på väntelista.", + "regenerate": "Drafta om lagen", + "regenerated": "{count} lag draftade", + "regenerate_failed": "Kunde inte drafta om lagen", + "join_failed": "Kunde inte gå med i free agent-poolen", + "leave_failed": "Kunde inte lämna free agent-poolen" } }, "team": { diff --git a/i18n/locales/tr_TR.json b/i18n/locales/tr_TR.json index 583324cde..f402ab58e 100644 --- a/i18n/locales/tr_TR.json +++ b/i18n/locales/tr_TR.json @@ -1229,6 +1229,10 @@ }, "system_telemetry": { "title": "Filo Telemetrisi", + "filter": { + "all": "Tüm filo", + "self_hosted": "Yalnızca kendi barındıranlar" + }, "no_data": "Henüz veri bildirilmedi", "installs": { "title": "Paneller", @@ -3967,7 +3971,9 @@ }, "picker": { "search": "Harita bul", - "none": "Radarı olan harita yok" + "none": "Radarı olan harita yok", + "count_public": "Bu haritadaki herkese açık dizilimler", + "count_private": "Herkese açık değil ama senin görebildiklerin — seninkiler, takımınınkiler veya incelemede olanlar. Arşivlenenler sayılmaz." }, "back_to_map": "Haritaya dön", "empty": { @@ -5946,13 +5952,31 @@ "teams": "Takımlar", "format": "Format", "starts": "Başlangıç", - "location": "Konum" + "location": "Konum", + "title": "İstatistikler", + "leaderboard_title": "Oyuncu sıralaması", + "matches": "Maçlar", + "rounds": "Rauntlar", + "players": "Oyuncular", + "top_rating": "En iyi puan", + "col_team": "Takım", + "col_rating": "Puan", + "col_adr": "ADR", + "col_kills": "K", + "col_deaths": "Ö", + "col_assists": "A", + "col_kd": "K/D", + "col_hs": "HS%", + "col_maps": "Haritalar", + "empty_title": "Henüz istatistik yok", + "empty_description": "Turnuva maçları tamamlandıkça sıralama dolar." }, "wizard": { "information": "Bilgi", "location": "Konum", "match_options": "Maç Seçenekleri", - "prizes": "Ödüller" + "prizes": "Ödüller", + "registration": "Kayıt" }, "banner": { "label": "Banner", @@ -6006,6 +6030,173 @@ "expand": "Genişlet", "collapse_all": "Tümünü daralt", "expand_all": "Tümünü genişlet" + }, + "registration": { + "section": "Kayıt", + "who": { + "label": "Kimler kaydolabilir?", + "teams": { + "label": "Sadece takımlar", + "description": "Kaptanlar mevcut bir takımı kaydeder. Varsayılan seçenek." + }, + "free_agents": { + "label": "Serbest oyuncular", + "description": "Oyuncular tek başına kaydolur ve kayıtlar kapandığında dengeli takımlara dağıtılır." + }, + "both": { + "label": "Takımlar + serbest oyuncular", + "description": "İkisi birden. Artan serbest oyuncular, kayıtlı takımların yanına ek takımlar olarak dağıtılır." + } + }, + "min_role": { + "label": "Minimum rol", + "none": "Kısıtlama yok", + "description": "Sadece tıklayan kişide değil, eklenen oyuncuda kontrol edilir — böylece bir kaptan uygun olmayan oyuncuları kadroya alamaz." + }, + "rank": { + "label": "ELO aralığı", + "min": "Min ELO", + "max": "Maks ELO", + "description": "Sınır koymamak için birini boş bırak. İkisi de boşsa her rank'a açıktır.", + "invalid_range": "Maks ELO en az min ELO kadar olmalı." + }, + "regions": { + "label": "Tercih edilen bölgeler", + "description": "Bu turnuvanın maçlarının hangi bölgede barındırılacağı. Oyuncu filtresi değildir — 5stack'te oyuncu bazlı bölge verisi yok.", + "empty": "Şu anda kullanılabilir bölge yok." + }, + "invite_only": { + "label": "Sadece davetle", + "description": "Kayıtlar herkese kapalı. Organizatörler takımları doğrudan davet eder ya da katılımcılar bir erişim kodu girer." + }, + "passcode": { + "label": "Erişim kodu", + "optional": "(isteğe bağlı)", + "description": "Kodu olan herkes davetsiz girebilir. Sadece organizatörler." + } + }, + "check_in": { + "section": "Katılım Onayı", + "required": { + "label": "Katılım onayı iste", + "description": "Takımlar, eşleşme tablosu oluşturulmadan önce katılacaklarını onaylar. Pencere programa göre açılır; onaylamak manueldir." + }, + "who": { + "label": "Katılımı kim onaylar?", + "Captains": { + "label": "Kaptanlar", + "description": "Kaptan tüm takım adına onaylar. Tek tık, tek takım." + }, + "Players": { + "label": "Her oyuncu", + "description": "Kadrodaki her oyuncu tek tek onaylar. {count} oyuncu onay verdiğinde takım katılmış olur — yedeklerin onaylaması gerekmez." + }, + "Admin": { + "label": "Sadece organizatör", + "description": "Oyuncular kendi katılımlarını onaylayamaz; takımları sen mevcut olarak işaretlersin." + } + }, + "opens": { + "label": "Pencere açılıyor" + }, + "closes": { + "label": "Pencere kapanıyor" + }, + "minutes_before_start": "başlangıçtan kaç dakika önce", + "window_too_short": "Pencere en az 5 dakika açık kalmalı.", + "timeline": { + "opens": "Pencere açılıyor", + "closes": "Pencere kapanıyor", + "start": "Turnuva başlıyor" + }, + "lock_note": "Pencere bir kez açıldığında başlangıç saati ve her iki sapma da kilitlenir. Takımlar bu programa göre hareket ediyor, dolayısıyla pencere açıkken programı değiştirmek işleyen bir süreyi kaydırırdı.", + "late_registration_note": "Pencere sırasında kaydolan bir takımın katılımı otomatik olarak onaylanır — hiç görmediği bir uyarıyı kaçırdı diye cezalandırılamaz.", + "no_show_note": "Gelmeyen takımlar asla silinmez. Katılım onayını kaçıran bir takım kadrosunu korur, sadece eşleşme tablosuna alınmaz — turnuva başlamadan önce istediğin an yeniden kabul edebilirsin.", + "frozen": "Katılım onayı çoktan açıldı, bu yüzden başlangıç saati ve her iki sapma da kilitli. Bunun yerine katılım onayı penceresini uzat.", + "required_title": "Bu turnuva katılım onayı gerektiriyor.", + "required_window": "Takımın o gün {opens} ile {closes} arasında onay vermeli, yoksa eşleşme tablosuna alınmaz.", + "register_heading": "Takımını kaydet", + "register_hint": "Katılım onayı açıldığında bildirim, kapanmadan önce de hatırlatma alırsın.", + "register_cta": "Takımı kaydet", + "chip_required": "katılım onayı gerekli", + "opens_at_heading": "Katılım onayı {time} itibarıyla açılıyor", + "opens_at_hint": "Takımın kayıtlı. Pencere açıldığında haber vereceğiz.", + "opens_in": "açılmasına", + "closes_in": "kapanmasına", + "starts_in": "başlamasına", + "chip_action_needed": "işlem gerekli", + "chip_open": "katılım onayı açık", + "observer_heading": "Katılım onayı açık", + "observer_hint": "Takımlar, eşleşme tablosu oluşturulmadan önce katılacaklarını onaylar. {time} itibarıyla onay vermeyen her takım, turnuvayı senin incelemen için bekletir.", + "confirm_heading": "Takımının katılacağını onayla", + "confirm_hint": "{time} itibarıyla onay vermeyen takımlar eşleşme tablosuna alınmaz.", + "players_progress": "{required} oyuncudan {checked} tanesi onayladı", + "players_heading": "Her oyuncu katılımını onaylamalı", + "players_hint": "{count} oyuncu onay verdiğinde takımın katılmış olur. Yedeklerin onaylaması gerekmez.", + "admin_heading": "Takımları mevcut olarak işaretle", + "admin_hint": "Bu turnuvada oyuncular kendi katılımlarını onaylayamaz. Pencere kapanmadan her takımı mevcut olarak işaretle.", + "check_in": "Katılımı onayla", + "mark_present": "Mevcut olarak işaretle", + "checked_in": "katılım onaylandı", + "checked_in_at": "katılım onaylandı · {time}", + "waiting": "bekliyor", + "not_needed": "gerekmiyor", + "role_captain": "kaptan", + "role_starter": "ilk 5", + "role_sub": "yedek", + "field_status": "Katılımcı durumu", + "field_count": "{total} takımdan {checked} tanesi onayladı", + "done_heading": "Kadrodasınız. Eşleşme tablosu {time} itibarıyla oluşturuluyor.", + "done_hint": "{team} için {time} itibarıyla onaylandı.", + "chip_removed": "çıkarıldı", + "missed_heading": "Takımın katılım onayını kaçırdı", + "missed_hint": "Katılım onayı {time} itibarıyla kapandı ve eşleşme tablosu takımın olmadan oluşturuluyor.", + "missed_contact": "Bunun yanlış olduğunu düşünüyorsan bir organizatörle iletişime geç — turnuva başlamadan önce bir takımı istediği an yeniden kabul edebilir.", + "failed": "Katılım onayı başarısız" + }, + "check_in_review": { + "heading": "{count} takım katılım onayını kaçırdı", + "subheading": "Turnuva bekletiliyor. Hiçbir şey oluşturulmadı ve hiçbir şey silinmedi — her takım tam kadrosunu koruyor.", + "chip_held_at": "bekletiliyor · {time} itibarıyla kendiliğinden sürüyor", + "chip_held": "bekletiliyor", + "roster_size": "{count} oyuncu", + "partial_roster": "{required} oyuncudan {checked} tanesi onayladı · {short} eksik", + "registered": "kayıtlı", + "chip_no_check_in": "onay yok", + "chip_incomplete": "eksik", + "readmit": "Yeniden kabul et", + "readmit_failed": "Takım yeniden kabul edilemedi", + "continue": "Onlarsız devam et", + "continue_failed": "Turnuvaya devam edilemedi", + "extend": "Katılım onayını uzat", + "extend_by": "+{minutes} dakika", + "extend_failed": "Katılım onayı uzatılamadı", + "extend_hint": "Uzatmak kayıtları yeniden açar ve eksik {count} takıma tekrar bildirim gönderir.", + "auto_continue": "Kimse bir şey yapmazsa turnuva {time} itibarıyla, onay verenlerle kendiliğinden devam eder. Bekletilen bir turnuva, organizatör uyuyakaldı diye sessizce ölmez.", + "auto_continue_no_time": "Kimse bir şey yapmazsa turnuva kendi başlangıç saatinde, onay verenlerle kendiliğinden devam eder.", + "reseed_warning": "Bir takımı yeniden kabul etmek eşleşme tablosunu baştan kurar. Başlamadan önce bu güvenlidir ama seribaşılıkları karıştırır." + }, + "free_agents": { + "title": "Serbest oyuncular", + "signed_up": "{count} kayıtlı", + "filter_all": "Tümü", + "filter_drafted": "Seçilenler", + "filter_waitlist": "Yedek listesi", + "sign_up": "Kaydol", + "leave": "Havuzdan ayrıl", + "status_drafted": "seçildi", + "status_waitlisted": "yedek listesi", + "status_registered": "kayıtlı", + "empty_title": "Henüz serbest oyuncu yok", + "empty_description": "Tek başına kaydolan oyuncular burada görünür ve kayıtlar kapandığında dengeli takımlara dağıtılır.", + "waitlist_explainer": "{name} en yüksek puanlı katılımcı ama hâlâ yedek listesinde — kontenjan dolduktan sonra kaydoldu. Kimin yer alacağına kayıt zamanı karar verir; ELO yalnızca hangi takıma gideceğini belirler. Bu kural düzgün işliyor, hata değil.", + "organizer": "Organizatör", + "draft_summary": "{agents} serbest oyuncu → {size} kişilik {teams} takım, {waitlisted} yedek listesinde.", + "regenerate": "Takımları yeniden oluştur", + "regenerated": "{count} takım oluşturuldu", + "regenerate_failed": "Takımlar yeniden oluşturulamadı", + "join_failed": "Serbest oyuncu havuzuna katılınamadı", + "leave_failed": "Serbest oyuncu havuzundan ayrılınamadı" } }, "team": { diff --git a/i18n/locales/uk_UA.json b/i18n/locales/uk_UA.json index c758a3d45..de18f1ce1 100644 --- a/i18n/locales/uk_UA.json +++ b/i18n/locales/uk_UA.json @@ -1229,6 +1229,10 @@ }, "system_telemetry": { "title": "Телеметрія парку", + "filter": { + "all": "Весь парк", + "self_hosted": "Лише self-hosted" + }, "no_data": "Даних поки не надходило", "installs": { "title": "Панелі", @@ -3967,7 +3971,9 @@ }, "picker": { "search": "Знайти карту", - "none": "Немає карт із радаром" + "none": "Немає карт із радаром", + "count_public": "Публічні розкидки на цій карті", + "count_private": "Непублічні, але видимі вам — ваші, вашої команди або на перевірці. Архівні не враховуються." }, "back_to_map": "Назад до карти", "empty": { @@ -5946,13 +5952,31 @@ "teams": "Команди", "format": "Формат", "starts": "Початок", - "location": "Локація" + "location": "Локація", + "title": "Статистика", + "leaderboard_title": "Таблиця лідерів гравців", + "matches": "Матчі", + "rounds": "Раунди", + "players": "Гравці", + "top_rating": "Найкращий рейтинг", + "col_team": "Команда", + "col_rating": "Рейтинг", + "col_adr": "ADR", + "col_kills": "В", + "col_deaths": "С", + "col_assists": "А", + "col_kd": "K/D", + "col_hs": "HS%", + "col_maps": "Карти", + "empty_title": "Статистики поки немає", + "empty_description": "Таблиця лідерів заповнюється в міру завершення матчів турніру." }, "wizard": { "information": "Інформація", "location": "Локація", "match_options": "Налаштування матчу", - "prizes": "Призи" + "prizes": "Призи", + "registration": "Реєстрація" }, "banner": { "label": "Банер", @@ -6006,6 +6030,173 @@ "expand": "Розгорнути", "collapse_all": "Згорнути все", "expand_all": "Розгорнути все" + }, + "registration": { + "section": "Реєстрація", + "who": { + "label": "Хто може зареєструватися?", + "teams": { + "label": "Лише команди", + "description": "Капітани реєструють наявну команду. Варіант за замовчуванням." + }, + "free_agents": { + "label": "Вільні агенти", + "description": "Гравці записуються поодинці, і після закриття реєстрації їх драфтом розподіляють у збалансовані команди." + }, + "both": { + "label": "Команди + вільні агенти", + "description": "І те, і те. Вільних агентів, що залишилися, драфтом збирають у додаткові команди поряд із зареєстрованими." + } + }, + "min_role": { + "label": "Мінімальна роль", + "none": "Без обмежень", + "description": "Перевіряється у гравця, якого додають, а не лише в того, хто натискає, — тож капітан не заявить у склад гравців без допуску." + }, + "rank": { + "label": "Діапазон ELO", + "min": "ELO мін", + "max": "ELO макс", + "description": "Залиш будь-яке поле порожнім, щоб не ставити межу. Обидва порожні — відкрито для будь-якого рангу.", + "invalid_range": "ELO макс має бути щонайменше рівним ELO мін." + }, + "regions": { + "label": "Бажані регіони", + "description": "Де хостяться матчі цього турніру. Це не фільтр за гравцями — у 5stack немає даних про регіон окремого гравця.", + "empty": "Наразі немає доступних регіонів." + }, + "invite_only": { + "label": "Лише за запрошенням", + "description": "Реєстрація закрита для всіх. Організатори запрошують команди напряму, або учасники вводять код доступу." + }, + "passcode": { + "label": "Код доступу", + "optional": "(необов'язково)", + "description": "Будь-хто з кодом зайде без запрошення. Лише для організаторів." + } + }, + "check_in": { + "section": "Реєстрація участі", + "required": { + "label": "Вимагати підтвердження участі", + "description": "Команди підтверджують участь до того, як складуть сітку. Вікно відкривається за розкладом; підтвердження — вручну." + }, + "who": { + "label": "Хто підтверджує участь?", + "Captains": { + "label": "Капітани", + "description": "Капітан підтверджує за всю команду. Один клік — одна команда." + }, + "Players": { + "label": "Кожен гравець", + "description": "Кожен гравець складу підтверджує сам. Команда в турнірі, щойно підтвердять {count} — запасним не потрібно." + }, + "Admin": { + "label": "Лише організатор", + "description": "Гравці не можуть підтвердити участь самі; ти позначаєш команди як присутні." + } + }, + "opens": { + "label": "Вікно відкривається" + }, + "closes": { + "label": "Вікно закривається" + }, + "minutes_before_start": "хвилин до старту", + "window_too_short": "Вікно має бути відкритим щонайменше 5 хвилин.", + "timeline": { + "opens": "Вікно відкривається", + "closes": "Вікно закривається", + "start": "Старт турніру" + }, + "lock_note": "Щойно вікно відкриється, час старту й обидва зміщення блокуються. Команди вже орієнтуються на цей розклад, тож зсув посеред вікна змістив би дедлайн, який уже триває.", + "late_registration_note": "Команда, яка реєструється під час вікна, отримує підтвердження участі автоматично — її не можна покарати за нагадування, якого вона ніколи не бачила.", + "no_show_note": "Команди, які не з'явилися, ніколи не видаляють. Команда, що пропустила підтвердження, зберігає склад і просто не потрапляє в сітку — ти можеш прийняти її знову будь-коли до старту турніру.", + "frozen": "Підтвердження участі вже відкрите, тож час старту й обидва зміщення заблоковані. Натомість продовж вікно підтвердження.", + "required_title": "Цей турнір вимагає підтвердження участі.", + "required_window": "Твоя команда має підтвердити участь того самого дня між {opens} і {closes}, інакше вона не потрапить у сітку.", + "register_heading": "Зареєструй команду", + "register_hint": "Прийде сповіщення, коли відкриється підтвердження участі, і нагадування перед його закриттям.", + "register_cta": "Зареєструвати команду", + "chip_required": "потрібне підтвердження", + "opens_at_heading": "Підтвердження участі відкривається о {time}", + "opens_at_hint": "Твоя команда зареєстрована. Повідомимо, коли відкриється вікно.", + "opens_in": "відкриється через", + "closes_in": "закриється через", + "starts_in": "почнеться через", + "chip_action_needed": "потрібна дія", + "chip_open": "підтвердження відкрите", + "observer_heading": "Підтвердження участі відкрите", + "observer_hint": "Команди підтверджують участь до того, як складуть сітку. Будь-яка команда, що не підтвердила участь до {time}, зупиняє турнір до твого рішення.", + "confirm_heading": "Підтверди участь своєї команди", + "confirm_hint": "Команди, які не підтвердили участь до {time}, у сітку не потрапляють.", + "players_progress": "підтвердило {checked} із {required}", + "players_heading": "Участь має підтвердити кожен гравець", + "players_hint": "Твоя команда в турнірі, щойно підтвердять {count} гравців. Запасним не потрібно.", + "admin_heading": "Познач присутні команди", + "admin_hint": "У цьому турнірі гравці не можуть підтвердити участь самі. Познач кожну команду як присутню до закриття вікна.", + "check_in": "Підтвердити участь", + "mark_present": "Позначити присутньою", + "checked_in": "участь підтверджено", + "checked_in_at": "участь підтверджено · {time}", + "waiting": "очікує", + "not_needed": "не потрібно", + "role_captain": "капітан", + "role_starter": "основа", + "role_sub": "запасний", + "field_status": "Статус учасників", + "field_count": "підтвердило {checked} із {total}", + "done_heading": "Ви в турнірі. Сітку складуть о {time}.", + "done_hint": "Підтверджено за {team} о {time}.", + "chip_removed": "прибрана", + "missed_heading": "Твоя команда пропустила підтвердження участі", + "missed_hint": "Підтвердження закрилося о {time}, і сітка складається без твоєї команди.", + "missed_contact": "Звернися до організатора, якщо вважаєш це помилкою — він може прийняти команду знову будь-коли до старту турніру.", + "failed": "Не вдалося підтвердити участь" + }, + "check_in_review": { + "heading": "Команд пропустило підтвердження участі: {count}", + "subheading": "Турнір зупинено. Сітку не складали й нічого не видаляли — кожна команда зберігає повний склад.", + "chip_held_at": "зупинено · продовжиться о {time}", + "chip_held": "зупинено", + "roster_size": "гравців: {count}", + "partial_roster": "підтвердило {checked} із {required} · бракує {short}", + "registered": "зареєстрована", + "chip_no_check_in": "без підтвердження", + "chip_incomplete": "неповний склад", + "readmit": "Прийняти знову", + "readmit_failed": "Не вдалося прийняти команду знову", + "continue": "Продовжити без них", + "continue_failed": "Не вдалося продовжити турнір", + "extend": "Продовжити підтвердження", + "extend_by": "+{minutes} хвилин", + "extend_failed": "Не вдалося продовжити підтвердження", + "extend_hint": "Продовження знову відкриє реєстрацію та повторно сповістить команди, яких бракує ({count}).", + "auto_continue": "Якщо ніхто нічого не зробить, турнір сам продовжиться о {time} з тими, хто підтвердив участь. Зупинений турнір ніколи не помре тихо через те, що організатор спав.", + "auto_continue_no_time": "Якщо ніхто нічого не зробить, турнір сам продовжиться у свій час старту з тими, хто підтвердив участь.", + "reseed_warning": "Повернення команди перескладає сітку. До початку це безпечно, але посів перемішається." + }, + "free_agents": { + "title": "Вільні агенти", + "signed_up": "записалося: {count}", + "filter_all": "Усі", + "filter_drafted": "Взяті на драфті", + "filter_waitlist": "Список очікування", + "sign_up": "Записатися", + "leave": "Покинути пул", + "status_drafted": "взятий на драфті", + "status_waitlisted": "список очікування", + "status_registered": "зареєстрований", + "empty_title": "Вільних агентів поки немає", + "empty_description": "Гравці, які записуються поодинці, з'являються тут, і після закриття реєстрації їх драфтом розподіляють у збалансовані команди.", + "waitlist_explainer": "{name} — учасник із найвищим рейтингом, але досі в списку очікування: він записався, коли місця вже закінчилися. Місце вирішує час запису, а ELO вирішує лише команду. Це правило працює як слід, а не баг.", + "organizer": "Організатор", + "draft_summary": "{agents} вільних агентів → {teams} команд по {size}, {waitlisted} у списку очікування.", + "regenerate": "Перескласти команди", + "regenerated": "Складено команд: {count}", + "regenerate_failed": "Не вдалося перескласти команди", + "join_failed": "Не вдалося увійти в пул вільних агентів", + "leave_failed": "Не вдалося покинути пул вільних агентів" } }, "team": { diff --git a/i18n/locales/zh_Hans.json b/i18n/locales/zh_Hans.json index 8129d907e..1388e6173 100644 --- a/i18n/locales/zh_Hans.json +++ b/i18n/locales/zh_Hans.json @@ -1229,6 +1229,10 @@ }, "system_telemetry": { "title": "集群遥测", + "filter": { + "all": "全部集群", + "self_hosted": "仅自托管" + }, "no_data": "尚未上报任何数据", "installs": { "title": "面板", @@ -3967,7 +3971,9 @@ }, "picker": { "search": "查找地图", - "none": "没有带雷达图的地图" + "none": "没有带雷达图的地图", + "count_public": "本图的公开阵型", + "count_private": "非公开但你可以看到的 — 你的、战队的,或待审核的。已归档的不计入。" }, "back_to_map": "返回地图", "empty": { @@ -5946,13 +5952,31 @@ "teams": "战队", "format": "赛制", "starts": "开始时间", - "location": "地点" + "location": "地点", + "title": "数据", + "leaderboard_title": "选手排行榜", + "matches": "比赛", + "rounds": "回合", + "players": "玩家", + "top_rating": "最高评分", + "col_team": "队伍", + "col_rating": "评分", + "col_adr": "ADR", + "col_kills": "K", + "col_deaths": "D", + "col_assists": "A", + "col_kd": "K/D", + "col_hs": "爆头率", + "col_maps": "地图", + "empty_title": "暂无数据", + "empty_description": "随着赛事比赛陆续结束,排行榜会逐步填充。" }, "wizard": { "information": "信息", "location": "地点", "match_options": "比赛选项", - "prizes": "奖金" + "prizes": "奖金", + "registration": "报名" }, "banner": { "label": "横幅", @@ -6006,6 +6030,173 @@ "expand": "展开", "collapse_all": "全部收起", "expand_all": "全部展开" + }, + "registration": { + "section": "报名", + "who": { + "label": "谁可以报名?", + "teams": { + "label": "仅限队伍", + "description": "由队长以现有队伍报名。默认选项。" + }, + "free_agents": { + "label": "自由选手", + "description": "选手单独报名,报名截止时会被选入实力均衡的队伍。" + }, + "both": { + "label": "队伍 + 自由选手", + "description": "两者皆可。剩余的自由选手会在已报名队伍之外,另行组成新队伍。" + } + }, + "min_role": { + "label": "最低角色", + "none": "不限制", + "description": "以被添加的选手为准进行校验,而不只是看点击的人——这样队长就无法把不符合条件的选手放进阵容。" + }, + "rank": { + "label": "ELO 范围", + "min": "最低 ELO", + "max": "最高 ELO", + "description": "任一项留空即表示该侧不设限制。两项都留空则对所有段位开放。", + "invalid_range": "最高 ELO 不能低于最低 ELO。" + }, + "regions": { + "label": "首选地区", + "description": "本赛事的比赛在哪里开服。这不是对选手的限制——5stack 没有按选手记录的地区数据。", + "empty": "目前没有可用地区。" + }, + "invite_only": { + "label": "仅限邀请", + "description": "报名不对外开放。由主办方直接邀请队伍,或由参赛者输入报名码。" + }, + "passcode": { + "label": "报名码", + "optional": "(可选)", + "description": "拿到该码的人无需邀请即可加入。仅主办方可见。" + } + }, + "check_in": { + "section": "签到", + "required": { + "label": "要求签到", + "description": "在生成对阵表之前,队伍需确认参赛。签到窗口按计划开启,确认需手动操作。" + }, + "who": { + "label": "由谁签到?", + "Captains": { + "label": "队长", + "description": "由队长代表全队确认。一次点击,搞定一支队伍。" + }, + "Players": { + "label": "每位选手", + "description": "阵容中的每位选手各自确认。有 {count} 人签到后队伍即算参赛——替补无需签到。" + }, + "Admin": { + "label": "仅主办方", + "description": "选手无法自行签到,由你把队伍标记为已到场。" + } + }, + "opens": { + "label": "窗口开启" + }, + "closes": { + "label": "窗口关闭" + }, + "minutes_before_start": "开赛前分钟数", + "window_too_short": "窗口至少需要保持开启 5 分钟。", + "timeline": { + "opens": "窗口开启", + "closes": "窗口关闭", + "start": "赛事开始" + }, + "lock_note": "窗口一旦开启,开始时间和两个偏移量都会锁定。各队伍已经在按这个时间表行动,中途改动会挪走一个正在倒计时的截止时间。", + "late_registration_note": "在窗口期间报名的队伍会自动完成签到——不能因为错过一条从未向它显示过的提示而受罚。", + "no_show_note": "缺席的队伍不会被删除。错过签到的队伍会保留完整阵容,只是不进入对阵表——在赛事正式开始前,你随时可以重新接纳它。", + "frozen": "签到已经开始,因此开始时间和两个偏移量均已锁定。请改为延长签到窗口。", + "required_title": "本赛事需要签到。", + "required_window": "你的队伍必须在当天 {opens} 至 {closes} 之间确认,否则不会进入对阵表。", + "register_heading": "为队伍报名", + "register_hint": "签到开启时你会收到通知,关闭前还会收到提醒。", + "register_cta": "队伍报名", + "chip_required": "需要签到", + "opens_at_heading": "签到于 {time} 开启", + "opens_at_hint": "你的队伍已报名。窗口开启时我们会通知你。", + "opens_in": "距开启", + "closes_in": "距关闭", + "starts_in": "距开始", + "chip_action_needed": "需要处理", + "chip_open": "签到进行中", + "observer_heading": "签到已开启", + "observer_hint": "在生成对阵表之前,队伍需确认参赛。任何在 {time} 前未签到的队伍都会让赛事暂停,等你处理。", + "confirm_heading": "确认你的队伍参赛", + "confirm_hint": "在 {time} 前未签到的队伍不会进入对阵表。", + "players_progress": "{required} 人中已有 {checked} 人签到", + "players_heading": "所有选手都需要签到", + "players_hint": "有 {count} 位选手确认后,你的队伍即算参赛。替补无需签到。", + "admin_heading": "标记已到场的队伍", + "admin_hint": "本赛事中选手无法自行签到。请在窗口关闭前把每支队伍标记为已到场。", + "check_in": "签到", + "mark_present": "标记为已到场", + "checked_in": "已签到", + "checked_in_at": "已签到 · {time}", + "waiting": "等待中", + "not_needed": "无需签到", + "role_captain": "队长", + "role_starter": "首发", + "role_sub": "替补", + "field_status": "参赛队伍状态", + "field_count": "{total} 支队伍中已有 {checked} 支签到", + "done_heading": "你们已入围。对阵表将于 {time} 生成。", + "done_hint": "已于 {time} 代表 {team} 确认。", + "chip_removed": "已移除", + "missed_heading": "你的队伍错过了签到", + "missed_hint": "签到已于 {time} 关闭,对阵表将不包含你的队伍。", + "missed_contact": "如果你认为这有误,请联系主办方——在赛事正式开始前,他们随时可以重新接纳队伍。", + "failed": "签到失败" + }, + "check_in_review": { + "heading": "{count} 支队伍错过了签到", + "subheading": "赛事已暂停。对阵表尚未生成,也没有删除任何内容——每支队伍都保留着完整阵容。", + "chip_held_at": "暂停 · {time} 自动继续", + "chip_held": "暂停", + "roster_size": "{count} 名选手", + "partial_roster": "{required} 人中已有 {checked} 人签到 · 还差 {short} 人", + "registered": "已报名", + "chip_no_check_in": "未签到", + "chip_incomplete": "人数不足", + "readmit": "重新接纳", + "readmit_failed": "重新接纳队伍失败", + "continue": "不带他们继续", + "continue_failed": "继续赛事失败", + "extend": "延长签到", + "extend_by": "+{minutes} 分钟", + "extend_failed": "延长签到失败", + "extend_hint": "延长会重新开放报名,并再次通知缺席的 {count} 支队伍。", + "auto_continue": "如果没有人处理,赛事会在 {time} 带着已签到的队伍自动继续。暂停中的赛事绝不会因为主办方睡着了就悄无声息地夭折。", + "auto_continue_no_time": "如果没有人处理,赛事会在其开始时间带着已签到的队伍自动继续。", + "reseed_warning": "重新接纳队伍会重新生成对阵表。开赛前这样做是安全的,但种子排位会被打乱。" + }, + "free_agents": { + "title": "自由选手", + "signed_up": "已报名 {count} 人", + "filter_all": "全部", + "filter_drafted": "已选入", + "filter_waitlist": "候补", + "sign_up": "报名", + "leave": "退出选手池", + "status_drafted": "已选入", + "status_waitlisted": "候补", + "status_registered": "已报名", + "empty_title": "暂无自由选手", + "empty_description": "单独报名的选手会显示在这里,并在报名截止时被选入实力均衡的队伍。", + "waitlist_explainer": "{name} 是评分最高的参赛者,却仍在候补——因为报名时名额已满。名额由报名先后决定,ELO 只决定进哪支队伍。这是规则在正常生效,不是 bug。", + "organizer": "主办方", + "draft_summary": "{agents} 名自由选手 → {teams} 支 {size} 人队伍,{waitlisted} 人候补。", + "regenerate": "重新分队", + "regenerated": "已组成 {count} 支队伍", + "regenerate_failed": "重新分队失败", + "join_failed": "加入自由选手池失败", + "leave_failed": "退出自由选手池失败" } }, "team": { diff --git a/i18n/locales/zh_Hant.json b/i18n/locales/zh_Hant.json index 7244395fd..823031d94 100644 --- a/i18n/locales/zh_Hant.json +++ b/i18n/locales/zh_Hant.json @@ -1229,6 +1229,10 @@ }, "system_telemetry": { "title": "機群遙測", + "filter": { + "all": "全部機群", + "self_hosted": "僅自架" + }, "no_data": "尚未回報任何資料", "installs": { "title": "面板", @@ -3967,7 +3971,9 @@ }, "picker": { "search": "尋找地圖", - "none": "沒有附雷達圖的地圖" + "none": "沒有附雷達圖的地圖", + "count_public": "本圖的公開陣型", + "count_private": "非公開但你看得到的 — 你的、戰隊的,或待審核的。已封存的不計入。" }, "back_to_map": "返回地圖", "empty": { @@ -5946,13 +5952,31 @@ "teams": "隊伍", "format": "賽制", "starts": "開始時間", - "location": "地點" + "location": "地點", + "title": "數據", + "leaderboard_title": "選手排行榜", + "matches": "比賽", + "rounds": "回合", + "players": "玩家", + "top_rating": "最高評分", + "col_team": "隊伍", + "col_rating": "評分", + "col_adr": "ADR", + "col_kills": "K", + "col_deaths": "D", + "col_assists": "A", + "col_kd": "K/D", + "col_hs": "HS%", + "col_maps": "地圖", + "empty_title": "尚無數據", + "empty_description": "隨著賽事比賽陸續結束,排行榜會逐步填入。" }, "wizard": { "information": "資訊", "location": "地點", "match_options": "比賽選項", - "prizes": "獎金" + "prizes": "獎金", + "registration": "報名" }, "banner": { "label": "橫幅", @@ -6006,6 +6030,173 @@ "expand": "展開", "collapse_all": "全部收合", "expand_all": "全部展開" + }, + "registration": { + "section": "報名", + "who": { + "label": "誰可以報名?", + "teams": { + "label": "僅限隊伍", + "description": "由隊長以現有隊伍報名。預設選項。" + }, + "free_agents": { + "label": "自由選手", + "description": "選手單獨報名,報名截止時會被選入實力平均的隊伍。" + }, + "both": { + "label": "隊伍 + 自由選手", + "description": "兩者皆可。剩下的自由選手會在已報名隊伍之外,另行組成新隊伍。" + } + }, + "min_role": { + "label": "最低角色", + "none": "不限制", + "description": "以被加入的選手為準檢查,而不只是看點擊的人——這樣隊長就無法把不符資格的選手放進陣容。" + }, + "rank": { + "label": "ELO 範圍", + "min": "最低 ELO", + "max": "最高 ELO", + "description": "任一欄留空即代表該側不設限制。兩欄都留空則對所有段位開放。", + "invalid_range": "最高 ELO 不能低於最低 ELO。" + }, + "regions": { + "label": "偏好地區", + "description": "本賽事的比賽在哪裡開伺服器。這不是對選手的限制——5stack 沒有依選手記錄的地區資料。", + "empty": "目前沒有可用地區。" + }, + "invite_only": { + "label": "僅限邀請", + "description": "報名不對外開放。由主辦方直接邀請隊伍,或由參賽者輸入報名碼。" + }, + "passcode": { + "label": "報名碼", + "optional": "(選填)", + "description": "拿到此碼的人無須邀請即可加入。僅主辦方可見。" + } + }, + "check_in": { + "section": "報到", + "required": { + "label": "要求報到", + "description": "在產生對戰表之前,隊伍須確認參賽。報到時段會依排程開啟,確認則是手動操作。" + }, + "who": { + "label": "由誰報到?", + "Captains": { + "label": "隊長", + "description": "由隊長代表全隊確認。點一次,搞定一支隊伍。" + }, + "Players": { + "label": "每位選手", + "description": "陣容中的每位選手各自確認。有 {count} 人報到後隊伍即算參賽——替補不用報到。" + }, + "Admin": { + "label": "僅主辦方", + "description": "選手無法自行報到,由你把隊伍標記為已到場。" + } + }, + "opens": { + "label": "時段開啟" + }, + "closes": { + "label": "時段關閉" + }, + "minutes_before_start": "開賽前分鐘數", + "window_too_short": "時段至少須保持開啟 5 分鐘。", + "timeline": { + "opens": "時段開啟", + "closes": "時段關閉", + "start": "賽事開始" + }, + "lock_note": "時段一旦開啟,開始時間與兩個偏移值都會鎖定。各隊伍已經照這個時間表在行動,中途更動會挪走一個正在倒數的截止時間。", + "late_registration_note": "在時段期間報名的隊伍會自動完成報到——不能因為錯過一則從未向它顯示過的提示而受罰。", + "no_show_note": "缺席的隊伍不會被刪除。錯過報到的隊伍會保留完整陣容,只是不進入對戰表——在賽事正式開始前,你隨時可以重新接納它。", + "frozen": "報到已經開始,因此開始時間與兩個偏移值均已鎖定。請改為延長報到時段。", + "required_title": "本賽事需要報到。", + "required_window": "你的隊伍必須在當天 {opens} 至 {closes} 之間確認,否則不會進入對戰表。", + "register_heading": "為隊伍報名", + "register_hint": "報到開啟時你會收到通知,關閉前還會再收到提醒。", + "register_cta": "隊伍報名", + "chip_required": "需要報到", + "opens_at_heading": "報到於 {time} 開啟", + "opens_at_hint": "你的隊伍已報名。時段開啟時我們會通知你。", + "opens_in": "距開啟", + "closes_in": "距關閉", + "starts_in": "距開始", + "chip_action_needed": "需要處理", + "chip_open": "報到進行中", + "observer_heading": "報到已開啟", + "observer_hint": "在產生對戰表之前,隊伍須確認參賽。任何在 {time} 前未報到的隊伍都會讓賽事暫停,等你處理。", + "confirm_heading": "確認你的隊伍參賽", + "confirm_hint": "在 {time} 前未報到的隊伍不會進入對戰表。", + "players_progress": "{required} 人中已有 {checked} 人報到", + "players_heading": "所有選手都需要報到", + "players_hint": "有 {count} 位選手確認後,你的隊伍即算參賽。替補不用報到。", + "admin_heading": "標記已到場的隊伍", + "admin_hint": "本賽事中選手無法自行報到。請在時段關閉前把每支隊伍標記為已到場。", + "check_in": "報到", + "mark_present": "標記為已到場", + "checked_in": "已報到", + "checked_in_at": "已報到 · {time}", + "waiting": "等待中", + "not_needed": "無須報到", + "role_captain": "隊長", + "role_starter": "先發", + "role_sub": "替補", + "field_status": "參賽隊伍狀態", + "field_count": "{total} 支隊伍中已有 {checked} 支報到", + "done_heading": "你們已入圍。對戰表將於 {time} 產生。", + "done_hint": "已於 {time} 代表 {team} 確認。", + "chip_removed": "已移除", + "missed_heading": "你的隊伍錯過了報到", + "missed_hint": "報到已於 {time} 關閉,對戰表將不包含你的隊伍。", + "missed_contact": "如果你認為這有誤,請聯絡主辦方——在賽事正式開始前,他們隨時可以重新接納隊伍。", + "failed": "報到失敗" + }, + "check_in_review": { + "heading": "{count} 支隊伍錯過了報到", + "subheading": "賽事已暫停。對戰表尚未產生,也沒有刪除任何內容——每支隊伍都保有完整陣容。", + "chip_held_at": "暫停 · {time} 自動繼續", + "chip_held": "暫停", + "roster_size": "{count} 名選手", + "partial_roster": "{required} 人中已有 {checked} 人報到 · 還差 {short} 人", + "registered": "已報名", + "chip_no_check_in": "未報到", + "chip_incomplete": "人數不足", + "readmit": "重新接納", + "readmit_failed": "重新接納隊伍失敗", + "continue": "不帶他們繼續", + "continue_failed": "繼續賽事失敗", + "extend": "延長報到", + "extend_by": "+{minutes} 分鐘", + "extend_failed": "延長報到失敗", + "extend_hint": "延長會重新開放報名,並再次通知缺席的 {count} 支隊伍。", + "auto_continue": "如果沒有人處理,賽事會在 {time} 帶著已報到的隊伍自動繼續。暫停中的賽事絕不會因為主辦方睡著了就悄悄夭折。", + "auto_continue_no_time": "如果沒有人處理,賽事會在其開始時間帶著已報到的隊伍自動繼續。", + "reseed_warning": "重新接納隊伍會重新產生對戰表。開賽前這麼做是安全的,但種子排序會被打亂。" + }, + "free_agents": { + "title": "自由選手", + "signed_up": "已報名 {count} 人", + "filter_all": "全部", + "filter_drafted": "已選入", + "filter_waitlist": "候補", + "sign_up": "報名", + "leave": "退出選手池", + "status_drafted": "已選入", + "status_waitlisted": "候補", + "status_registered": "已報名", + "empty_title": "尚無自由選手", + "empty_description": "單獨報名的選手會顯示在這裡,並在報名截止時被選入實力平均的隊伍。", + "waitlist_explainer": "{name} 是評分最高的參賽者,卻仍在候補——因為報名時名額已滿。名額由報名先後決定,ELO 只決定進哪支隊伍。這是規則正常運作,不是 bug。", + "organizer": "主辦方", + "draft_summary": "{agents} 名自由選手 → {teams} 支 {size} 人隊伍,{waitlisted} 人候補。", + "regenerate": "重新分隊", + "regenerated": "已組成 {count} 支隊伍", + "regenerate_failed": "重新分隊失敗", + "join_failed": "加入自由選手池失敗", + "leave_failed": "退出自由選手池失敗" } }, "team": { diff --git a/pages/players/[id].vue b/pages/players/[id].vue index 2a8e8e3ee..bbb7cd1e4 100644 --- a/pages/players/[id].vue +++ b/pages/players/[id].vue @@ -113,6 +113,14 @@ import { } from "~/components/ui/select"; import { parseDate, type DateValue } from "@internationalized/date"; +// `CheckInReview` only joins e_tournament_status_enum once codegen has run +// against the check-in migration; until then the enum member is undefined and +// would poison the status filter in the template. The raw column value is what +// the row carries either way. Declared after the imports on purpose: a const +// ahead of a later import makes vue-tsc treat that import as non-top-level. +const CHECK_IN_REVIEW_STATUS = "CheckInReview" as e_tournament_status_enum; + + type RangeKey = | "l30" | "7d" @@ -2890,6 +2898,7 @@ const playerHeroTeamChipDotClasses = e_tournament_status_enum.RegistrationOpen, e_tournament_status_enum.RegistrationClosed, e_tournament_status_enum.Setup, + CHECK_IN_REVIEW_STATUS, ]" status-variant="finished" order-direction="desc" diff --git a/pages/tournaments/index.vue b/pages/tournaments/index.vue index 13fff42a5..c869f6405 100644 --- a/pages/tournaments/index.vue +++ b/pages/tournaments/index.vue @@ -71,6 +71,11 @@ const statusGroups: Record< upcoming: [ e_tournament_status_enum.RegistrationClosed, e_tournament_status_enum.Setup, + // Held at the check-in cutoff: registration is over and it has not started, + // so it belongs here. Omitting it drops a held tournament out of every + // filter tab, which is exactly when an organizer goes looking for it. + // String cast because the enum member only exists after codegen. + ("CheckInReview" as e_tournament_status_enum), ], finished: [ e_tournament_status_enum.Finished, diff --git a/pages/tournaments/manage.vue b/pages/tournaments/manage.vue index 334af331b..c3ccb58ad 100644 --- a/pages/tournaments/manage.vue +++ b/pages/tournaments/manage.vue @@ -343,6 +343,12 @@ export default { e_tournament_status_enum.RegistrationOpen, e_tournament_status_enum.RegistrationClosed, e_tournament_status_enum.Setup, + // A tournament held at the check-in cutoff is waiting on an organizer + // decision -- it must not be missing from the organizer's default view. + // `CheckInReview` only joins e_tournament_status_enum once codegen has run + // against the check-in migration; until then the enum member is undefined and + // would poison this filter array. The raw column value is what the row carries. + ("CheckInReview" as e_tournament_status_enum), ]; return { diff --git a/pages/watch/index.vue b/pages/watch/index.vue index 9d7e38f2a..40a3964c4 100644 --- a/pages/watch/index.vue +++ b/pages/watch/index.vue @@ -15,6 +15,14 @@ import { tacticalSectionLabelClasses, tacticalSectionTickClasses, } from "~/utilities/tacticalClasses"; + +// `CheckInReview` only joins e_tournament_status_enum once codegen has run +// against the check-in migration; until then the enum member is undefined and +// would poison the status filter in the template. The raw column value is what +// the row carries either way. Declared after the imports on purpose: a const +// ahead of a later import makes vue-tsc treat that import as non-top-level. +const CHECK_IN_REVIEW_STATUS = "CheckInReview" as e_tournament_status_enum; + diff --git a/components/tournament/TournamentDetail.vue b/components/tournament/TournamentDetail.vue index 1c85b46c0..bbe51e182 100644 --- a/components/tournament/TournamentDetail.vue +++ b/components/tournament/TournamentDetail.vue @@ -16,6 +16,7 @@ import TournamentAwardsConfig from "~/components/tournament/TournamentAwardsConf import TournamentAwardsManage from "~/components/tournament/TournamentAwardsManage.vue"; import TournamentCheckInPanel from "~/components/tournament/TournamentCheckInPanel.vue"; import TournamentCheckInReview from "~/components/tournament/TournamentCheckInReview.vue"; +import TournamentEntryGate from "~/components/tournament/TournamentEntryGate.vue"; import TournamentFreeAgents from "~/components/tournament/TournamentFreeAgents.vue"; import TournamentStats from "~/components/tournament/TournamentStats.vue"; import Separator from "~/components/ui/separator/Separator.vue"; @@ -576,6 +577,14 @@ function clearTeamEnterDelay(el: Element) { + + + @@ -1054,11 +1064,6 @@ import { replaceRouteTab, } from "~/composables/useRouteTab"; -// `CheckInReview` only joins e_tournament_status_enum once codegen has run -// against the check-in migration; the raw column value is what the row -// actually carries either way. -const CHECK_IN_REVIEW_STATUS = "CheckInReview"; - export default { data() { return { @@ -1066,6 +1071,7 @@ export default { tournament: undefined, tournamentRegistration: null as Record | null, checkInTeams: [] as Array>, + myFreeAgent: null as Record | null, tournamentDialog: false, teamSearchQuery: undefined, settingsDialogOpen: false, @@ -1484,6 +1490,16 @@ export default { { id: true, registration_type: true, + invite_only: true, + min_role: true, + min_elo: true, + max_elo: true, + // The two gates a would-be entrant hits: the role floor (server + // truth, session-scoped) and whether invite-only has been + // unlocked for them. Without both, "Join" is offered and then + // fails with a raw Hasura error. + meets_min_role: true, + registration_unlocked: true, check_in_required: true, check_in_setting: true, check_in_opens_before_minutes: true, @@ -1533,6 +1549,10 @@ export default { checked_in_at: true, owner_steam_id: true, captain_steam_id: true, + // can_manage_tournament_team: the exact predicate the check-in + // action accepts. Re-deriving it from captain/owner locks out a + // roster Admin the API would have let through. + can_manage: true, team: { id: true, name: true, @@ -1571,6 +1591,43 @@ export default { this.checkInTeams = data?.tournament_teams ?? []; }, }, + // An undrafted free agent has no tournament_teams row at all, so every + // team-shaped check-in surface misses them — while the check-in job + // still pushes them "confirm your spot". This row is what lets them. + myFreeAgent: { + query: generateSubscription({ + tournament_free_agents: [ + { + where: { + tournament_id: { + _eq: $("tournamentId", "uuid!"), + }, + player_steam_id: { + _eq: $("steamId", "bigint!"), + }, + }, + }, + { + id: true, + status: true, + checked_in_at: true, + tournament_team_id: true, + }, + ], + }), + variables: function (this: any) { + return { + tournamentId: this.$route.params.tournamentId, + steamId: this.me?.steam_id, + }; + }, + skip: function (this: any): boolean { + return !this.me?.steam_id || !this.freeAgentsTabVisible; + }, + result: function (this: any, { data }: { data: any }) { + this.myFreeAgent = data?.tournament_free_agents?.[0] ?? null; + }, + }, tournament_teams: { query: typedGql("subscription")({ tournament_teams: [ @@ -1768,6 +1825,12 @@ export default { e_tournament_status_enum.Setup, e_tournament_status_enum.RegistrationOpen, e_tournament_status_enum.RegistrationClosed, + // Nothing is seeded while the tournament is held for review, but + // re-admitting one team re-runs assign_seeds_to_teams, which nulls + // eligible_at for every team still missing a check-in. Treating the + // hold as "started" would filter those teams off the Teams tab even + // though they are still registered with a full roster. + e_tournament_status_enum.CheckInReview, ].includes(status); }, visibleTeams() { @@ -1832,7 +1895,7 @@ export default { if (s === e_tournament_status_enum.Paused) return "paused"; // Held for an organizer, not running and not cancelled — the warning // tier is the one that reads as "this needs a decision". - if (s === CHECK_IN_REVIEW_STATUS) return "paused"; + if (s === e_tournament_status_enum.CheckInReview) return "paused"; if (s === e_tournament_status_enum.Finished) return "finished"; if ( s === e_tournament_status_enum.Cancelled || @@ -1909,7 +1972,7 @@ export default { checkInReviewVisible() { const tournament = this.tournament as Record | undefined; return ( - tournament?.status === CHECK_IN_REVIEW_STATUS && + tournament?.status === e_tournament_status_enum.CheckInReview && (this.tournamentRegistration?.can_review_check_in === true || tournament?.is_organizer === true) ); diff --git a/components/tournament/TournamentEntryGate.vue b/components/tournament/TournamentEntryGate.vue new file mode 100644 index 000000000..ae420f830 --- /dev/null +++ b/components/tournament/TournamentEntryGate.vue @@ -0,0 +1,276 @@ + + + diff --git a/components/tournament/TournamentFreeAgents.vue b/components/tournament/TournamentFreeAgents.vue index e5c838c33..94bd0b505 100644 --- a/components/tournament/TournamentFreeAgents.vue +++ b/components/tournament/TournamentFreeAgents.vue @@ -128,8 +128,31 @@ const stageMaxTeams = computed(() => { return max ? Number(max) : null; }); +// `both` mode drafts into whatever room the bracket has LEFT, not the whole +// cap: the server's limit is GREATEST(max_teams - existing_teams, 0). Ignoring +// the registered teams promises slots that were already taken. +const existingTeams = computed( + () => Number(props.tournament?.teams_aggregate?.aggregate?.count) || 0, +); + const hasDrafted = computed(() => draftedAgents.value.length > 0); +// draft_tournament_free_agent_teams short-circuits to 0 the moment a drafted +// team exists, and the action refuses outright once the bracket is in play. +// Both cases return "Drafted 0 teams" as a success, so the button has to go +// rather than lie. +const draftLocked = computed(() => + [ + e_tournament_status_enum.Live, + e_tournament_status_enum.Paused, + e_tournament_status_enum.Finished, + e_tournament_status_enum.Cancelled, + e_tournament_status_enum.CancelledMinTeams, + ].includes(props.tournament?.status), +); + +const canDraft = computed(() => !hasDrafted.value && !draftLocked.value); + // Before the draft runs this is a projection of what will happen; afterwards it // is a report of what did. Both read the same, which is the point — an // organizer should not have to guess whether the numbers are hypothetical. @@ -152,7 +175,10 @@ const projection = computed(() => { const teams = stageMaxTeams.value === null ? possible - : Math.min(possible, stageMaxTeams.value); + : Math.min( + possible, + Math.max(stageMaxTeams.value - existingTeams.value, 0), + ); return { teams, waitlisted: pool.value.length - teams * teamSize.value, @@ -218,15 +244,14 @@ const myEntry = computed(() => { ); }); -const registrationOpen = computed(() => - [ - e_tournament_status_enum.Setup, - e_tournament_status_enum.RegistrationOpen, - ].includes(props.tournament?.status), -); - +// joinTournamentAsFreeAgent requires exactly RegistrationOpen, while the leave +// rule also allows Setup. Offering Sign up during Setup produces a button that +// can only ever fail, so the two windows are kept apart. const canSignUp = computed( - () => !!me.value && registrationOpen.value && !myEntry.value, + () => + !!me.value && + props.tournament?.status === e_tournament_status_enum.RegistrationOpen && + !myEntry.value, ); // Leaving after the draft would tear a hole in a seeded team, and the API's @@ -234,7 +259,10 @@ const canSignUp = computed( const canLeave = computed( () => !!myEntry.value && - registrationOpen.value && + [ + e_tournament_status_enum.Setup, + e_tournament_status_enum.RegistrationOpen, + ].includes(props.tournament?.status) && myEntry.value.status !== "drafted", ); @@ -322,7 +350,7 @@ async function leavePool() { ); } -async function regenerateTeams() { +async function draftTeams() { const data = await runAction( { draftTournamentTeams: [ @@ -341,10 +369,20 @@ async function regenerateTeams() { return; } + const created = Number(data.draftTournamentTeams?.teams_created ?? 0); + if (created < 1) { + toast({ + title: t("tournament.free_agents.nothing_drafted"), + description: t("tournament.free_agents.nothing_drafted_hint", { + size: teamSize.value, + }), + variant: "destructive", + }); + return; + } + toast({ - title: t("tournament.free_agents.regenerated", { - count: data.draftTournamentTeams?.teams_created ?? 0, - }), + title: t("tournament.free_agents.regenerated", { count: created }), }); } @@ -487,14 +525,25 @@ async function regenerateTeams() {
+ + {{ + hasDrafted + ? $t("tournament.free_agents.already_drafted") + : $t("tournament.free_agents.draft_locked") + }} +
| null, registrationSeeded: false, - // Selecting a column the session's role cannot see fails the whole - // subscription, so the field is asked for conditionally — and a save from - // a session that could not read it leaves the column alone rather than - // blanking out a passcode it was never shown. - canReadPasscode: canManageRegistrationPasscode(), form: useForm({ keepValuesOnUnmount: true, validationSchema: toTypedSchema( @@ -266,7 +261,9 @@ export default { id: true, start: true, ...tournamentRegistrationFields, - ...(this.canReadPasscode ? tournamentPasscodeField : {}), + ...(this.canReadPasscode + ? tournamentOrganizerPasscodeField + : {}), } as any, ], }); @@ -310,6 +307,14 @@ export default { }, }, computed: { + // A computed, not a data() snapshot: `me` is undefined at data() time on a + // first login, a cleared cache, or a private window where localStorage + // throws. Snapshotting `false` there rendered the passcode input while + // silently dropping the column from every mutation — a success toast over + // a write that never happened. + canReadPasscode(): boolean { + return canManageRegistrationPasscode(); + }, // Only the live subscription carries `check_in_started`; the page's own // tournament object never selects it, and a missing field would read as // "not frozen" and quietly drop the lock explanation. diff --git a/components/tournament/TournamentRegistrationForm.vue b/components/tournament/TournamentRegistrationForm.vue index b3a1d8f57..5bfa9e63e 100644 --- a/components/tournament/TournamentRegistrationForm.vue +++ b/components/tournament/TournamentRegistrationForm.vue @@ -507,9 +507,10 @@ export default { frozen(): boolean { return isTournamentScheduleFrozen(this.tournament); }, - // Hidden rather than disabled for a role that cannot write the column: an - // input whose value is silently dropped from every mutation is worse than - // no input. Invite-only itself still works — organizers invite directly. + // Hidden rather than disabled for a session that cannot write the column: + // an input whose value is silently dropped from every mutation is worse + // than no input. In practice that is only a signed-out session, which + // never reaches this form. canSetPasscode(): boolean { return canManageRegistrationPasscode(); }, diff --git a/components/tournament/TournamentStats.vue b/components/tournament/TournamentStats.vue index 5222ce2ee..f9f6620c3 100644 --- a/components/tournament/TournamentStats.vue +++ b/components/tournament/TournamentStats.vue @@ -247,7 +247,7 @@ onMounted(fetchLeaderboard); -
- {{ $t("tournament.stats.col_maps") }} + {{ $t("tournament.stats.rounds") }} + + + + {{ $t("tournament.stats.matches") }} @@ -362,6 +367,11 @@ onMounted(fetchLeaderboard); > {{ number(entry.headshot_percentage, 1) }} + + {{ entry.rounds_played }} + diff --git a/components/tournament/TournamentTeam.vue b/components/tournament/TournamentTeam.vue index 9a7f5cfaa..f99044a0b 100644 --- a/components/tournament/TournamentTeam.vue +++ b/components/tournament/TournamentTeam.vue @@ -669,6 +669,9 @@ export default { e_tournament_status_enum.Setup, e_tournament_status_enum.RegistrationOpen, e_tournament_status_enum.RegistrationClosed, + // Held for check-in review: nothing has been seeded and no team has + // been dropped, so the eligibility badge is still the honest readout. + e_tournament_status_enum.CheckInReview, ].includes(status); }, showEligibilityBadge() { diff --git a/graphql/simpleTournamentFields.ts b/graphql/simpleTournamentFields.ts index 24769a097..8e17321bd 100644 --- a/graphql/simpleTournamentFields.ts +++ b/graphql/simpleTournamentFields.ts @@ -28,10 +28,18 @@ export const tournamentRegistrationFields: Record = { check_in_open: true, }; -// Organizer-only select, so it is kept out of the shared selector that guest -// and player surfaces reuse — asking for it there fails the whole query. -export const tournamentPasscodeField: Record = { - registration_passcode: true, +/** + * The passcode as an organizer reads it back. + * + * The raw `registration_passcode` column is selectable by tournament_organizer + * alone — granting it to `user` would hand every logged-in player the passcode + * of every public tournament, which is the gate itself. This computed field is + * row-scoped instead: granted to `user`, and NULL unless the viewer organizes + * that specific tournament. Still never guest, so it stays out of the shared + * selector the public surfaces reuse. + */ +export const tournamentOrganizerPasscodeField: Record = { + organizer_registration_passcode: true, }; export const simpleTournamentFields = Selector("tournaments")({ diff --git a/i18n/locales/en.json b/i18n/locales/en.json index 401182c96..fd11da975 100644 --- a/i18n/locales/en.json +++ b/i18n/locales/en.json @@ -6152,7 +6152,21 @@ "missed_heading": "Your team missed check-in", "missed_hint": "Check-in closed at {time} and the bracket is seeded without your team.", "missed_contact": "Contact an organizer if you think this is wrong — they can re-admit a team any time before the tournament goes live.", - "failed": "Check-in failed" + "failed": "Check-in failed", + "free_agent_opens_at_hint": "You're in the free agent pool. We'll notify you when the window opens.", + "free_agent_heading": "Confirm you're still available", + "free_agent_hint": "Free agents who haven't confirmed by {time} are moved to the waitlist and are not drafted into a team.", + "free_agent_done_heading": "You're in. Teams are drafted at {time}.", + "free_agent_done_hint": "Confirmed at {time}. You'll be drafted into a team when check-in closes.", + "free_agent_chip_waitlisted": "waitlisted", + "free_agent_missed_heading": "You missed check-in", + "free_agent_missed_hint": "Check-in closed at {time} and you were moved to the waitlist. You are only drafted now if a slot opens up.", + "free_agent_missed_contact": "Contact an organizer if you think this is wrong — extending check-in puts you back in the draft pool.", + "a11y_opens_in": "Check-in opens in", + "a11y_opens_help": "Check-in has not opened yet. There is nothing to confirm until it does.", + "a11y_closes_in": "Check-in closes in", + "a11y_closes_help": "Anyone who has not checked in by then is not seeded into the bracket.", + "a11y_starts_in": "Tournament starts in" }, "check_in_review": { "heading": "{count} teams missed check-in", @@ -6192,11 +6206,36 @@ "waitlist_explainer": "{name} is the highest-rated entrant and still waitlisted — they signed up after the slots filled. Sign-up time decides who gets a slot; ELO only decides which team. That is the rule working, not a bug.", "organizer": "Organizer", "draft_summary": "{agents} free agents → {teams} teams of {size}, {waitlisted} waitlisted.", - "regenerate": "Regenerate teams", + "regenerate": "Draft teams", "regenerated": "Drafted {count} teams", - "regenerate_failed": "Failed to regenerate teams", + "regenerate_failed": "Failed to draft teams", "join_failed": "Failed to join the free agent pool", - "leave_failed": "Failed to leave the free agent pool" + "leave_failed": "Failed to leave the free agent pool", + "already_drafted": "Teams are already drafted. Drafting again would do nothing — remove the drafted teams first.", + "draft_locked": "The bracket is in play, so the pool can no longer be drafted.", + "nothing_drafted": "No teams were drafted", + "nothing_drafted_hint": "There are not enough free agents left to fill a team of {size}, or the bracket has no room." + }, + "entry": { + "title": "Entry requirements", + "requirements_hint": "Checked against every player being added, not just whoever clicks. Registration is refused if any of them fails.", + "locked_title": "This tournament is invite only", + "locked_hint": "Enter the organizer's passcode to unlock registration, or ask them to invite your team directly.", + "passcode_label": "Passcode", + "passcode_placeholder": "Enter passcode", + "unlock": "Unlock", + "unlocked": "Registration unlocked", + "unlock_failed": "Could not unlock registration", + "min_role": "Minimum role: {role}", + "elo_between": "ELO between {min} and {max}", + "elo_min": "ELO {min} or higher", + "elo_max": "ELO {max} or lower", + "elo_yours": "yours: {elo}", + "requirement_met": "met", + "requirement_failed": "not met", + "ok_chip": "eligible", + "locked_chip": "locked", + "blocked_chip": "not eligible" } }, "team": { diff --git a/pages/tournaments/index.vue b/pages/tournaments/index.vue index c869f6405..55fab8565 100644 --- a/pages/tournaments/index.vue +++ b/pages/tournaments/index.vue @@ -74,8 +74,7 @@ const statusGroups: Record< // Held at the check-in cutoff: registration is over and it has not started, // so it belongs here. Omitting it drops a held tournament out of every // filter tab, which is exactly when an organizer goes looking for it. - // String cast because the enum member only exists after codegen. - ("CheckInReview" as e_tournament_status_enum), + e_tournament_status_enum.CheckInReview, ], finished: [ e_tournament_status_enum.Finished, @@ -635,10 +634,7 @@ const seeAllFinished = { path: "/tournaments", query: { status: "finished" } }; { e_tournament_status_enum.Setup, e_tournament_status_enum.RegistrationOpen, e_tournament_status_enum.RegistrationClosed, + // A tournament held at the check-in cutoff is exactly when + // the organizer and the excluded teams need the chat. + e_tournament_status_enum.CheckInReview, e_tournament_status_enum.Live, e_tournament_status_enum.Paused, ], @@ -254,6 +257,7 @@ export const useMatchLobbyStore = defineStore("matchLobby", () => { e_tournament_status_enum.Live, e_tournament_status_enum.RegistrationClosed, e_tournament_status_enum.RegistrationOpen, + e_tournament_status_enum.CheckInReview, e_tournament_status_enum.Setup, ], }, diff --git a/utilities/tournamentRegistration.ts b/utilities/tournamentRegistration.ts index 21d045e59..cfcb1b8d8 100644 --- a/utilities/tournamentRegistration.ts +++ b/utilities/tournamentRegistration.ts @@ -1,5 +1,4 @@ import * as z from "zod"; -import { e_player_roles_enum } from "~/generated/zeus"; import { useAuthStore } from "~/stores/AuthStore"; import { CHECK_IN_CLOSES_DEFAULT_MINUTES, @@ -38,14 +37,19 @@ export const REGISTRATION_FIELD = { } as const; /** - * Hasura grants `tournaments.registration_passcode` — select, insert and update - * — to the tournament_organizer role alone, while a plain `user` with - * can_create_tournaments can still own one. Naming the column at all in their - * mutation fails the entire statement, so the same test has to gate the field's - * visibility, its hydration and every write. One definition, three callers. + * Hasura grants `tournaments.registration_passcode` on INSERT and UPDATE to the + * `user` role — and so to every role that inherits it — because a plain `user` + * with can_create_tournaments still owns tournaments. Only SELECT is restricted + * to tournament_organizer; the passcode is read back through the row-level + * `organizer_registration_passcode` computed field instead (granted to `user`, + * NULL for anyone who is not an organizer of that row). + * + * Gating this on tournament_organizer is what let an organizer turn on Invite + * only with no passcode input and save `invite_only = true, passcode = NULL` — + * a tournament nobody can ever enter, with no UI path back out. */ export function canManageRegistrationPasscode(): boolean { - return useAuthStore().isRoleAbove(e_player_roles_enum.tournament_organizer); + return !!useAuthStore().me; } // A blank ELO input arrives as "", null or NaN; every one of them means "no @@ -187,8 +191,12 @@ export function registrationFormValues(tournament: Record) { tournament.max_elo != null ? Number(tournament.max_elo) : null, [REGISTRATION_FIELD.regions]: [...(tournament.regions ?? [])], [REGISTRATION_FIELD.invite_only]: !!tournament.invite_only, + // Whichever of the two the session was allowed to select: the raw column + // for tournament_organizer, the computed field for everyone else. [REGISTRATION_FIELD.registration_passcode]: - tournament.registration_passcode ?? null, + tournament.registration_passcode ?? + tournament.organizer_registration_passcode ?? + null, [REGISTRATION_FIELD.check_in_required]: !!tournament.check_in_required, [REGISTRATION_FIELD.check_in_setting]: tournament.check_in_setting ?? CHECK_IN_SETTING_DEFAULT, From b75f82f108c482d2c12b25eb3bbc9f7f2a0de207 Mon Sep 17 00:00:00 2001 From: Luke Policinski Date: Sat, 29 Aug 2026 21:33:25 -0400 Subject: [PATCH 04/23] chore: translate tournament check-in and free-agent strings across all locales --- i18n/locales/ar_SA.json | 48 +++++++++++++++++++++++++++++++++++---- i18n/locales/da_DK.json | 48 +++++++++++++++++++++++++++++++++++---- i18n/locales/de_DE.json | 48 +++++++++++++++++++++++++++++++++++---- i18n/locales/en.json | 1 - i18n/locales/es_ES.json | 48 +++++++++++++++++++++++++++++++++++---- i18n/locales/fr_FR.json | 48 +++++++++++++++++++++++++++++++++++---- i18n/locales/it_IT.json | 48 +++++++++++++++++++++++++++++++++++---- i18n/locales/ja_JP.json | 48 +++++++++++++++++++++++++++++++++++---- i18n/locales/ko_KR.json | 48 +++++++++++++++++++++++++++++++++++---- i18n/locales/pl_PL.json | 48 +++++++++++++++++++++++++++++++++++---- i18n/locales/pt_BR.json | 48 +++++++++++++++++++++++++++++++++++---- i18n/locales/ru_RU.json | 48 +++++++++++++++++++++++++++++++++++---- i18n/locales/sv_SE.json | 48 +++++++++++++++++++++++++++++++++++---- i18n/locales/tr_TR.json | 48 +++++++++++++++++++++++++++++++++++---- i18n/locales/uk_UA.json | 48 +++++++++++++++++++++++++++++++++++---- i18n/locales/zh_Hans.json | 48 +++++++++++++++++++++++++++++++++++---- i18n/locales/zh_Hant.json | 48 +++++++++++++++++++++++++++++++++++---- 17 files changed, 688 insertions(+), 81 deletions(-) diff --git a/i18n/locales/ar_SA.json b/i18n/locales/ar_SA.json index e156bcaa9..3bfb2b099 100644 --- a/i18n/locales/ar_SA.json +++ b/i18n/locales/ar_SA.json @@ -5967,7 +5967,6 @@ "col_assists": "A", "col_kd": "K/D", "col_hs": "HS%", - "col_maps": "خرائط", "empty_title": "لا توجد إحصائيات بعد", "empty_description": "تمتلئ لوحة المتصدرين مع انتهاء مباريات البطولة." }, @@ -6152,7 +6151,21 @@ "missed_heading": "فوّت فريقك تأكيد الحضور", "missed_hint": "أُغلق تأكيد الحضور في {time} وتُبنى الشجرة بدون فريقك.", "missed_contact": "تواصل مع أحد المنظمين إن كنت ترى أن هذا خطأ — بإمكانه إعادة قبول أي فريق في أي وقت قبل انطلاق البطولة.", - "failed": "فشل تأكيد الحضور" + "failed": "فشل تأكيد الحضور", + "free_agent_opens_at_hint": "أنت ضمن مجموعة اللاعبين الأحرار. سنُعلمك عند فتح النافذة.", + "free_agent_heading": "أكّد أنك ما زلت متاحًا", + "free_agent_hint": "اللاعبون الأحرار الذين لا يؤكّدون بحلول {time} يُنقلون إلى قائمة الانتظار ولا يُوزَّعون على أي فريق.", + "free_agent_done_heading": "أنت في البطولة. تُوزَّع الفرق في {time}.", + "free_agent_done_hint": "تم التأكيد في {time}. ستُوزَّع على فريق عند إغلاق تأكيد الحضور.", + "free_agent_chip_waitlisted": "قائمة الانتظار", + "free_agent_missed_heading": "فوّتّ تأكيد الحضور", + "free_agent_missed_hint": "أُغلق تأكيد الحضور في {time} ونُقلت إلى قائمة الانتظار. لن تُوزَّع على فريق الآن إلا إذا شغر مقعد.", + "free_agent_missed_contact": "تواصل مع أحد المنظمين إن كنت ترى أن هذا خطأ — تمديد تأكيد الحضور يعيدك إلى مجموعة التوزيع.", + "a11y_opens_in": "يُفتح تأكيد الحضور خلال", + "a11y_opens_help": "لم يُفتح تأكيد الحضور بعد. لا شيء لتؤكّده قبل ذلك.", + "a11y_closes_in": "يُغلق تأكيد الحضور خلال", + "a11y_closes_help": "كل من لا يؤكّد حضوره قبل ذلك الوقت لا يدخل شجرة البطولة.", + "a11y_starts_in": "تبدأ البطولة خلال" }, "check_in_review": { "heading": "فوّتت {count} فرق تأكيد الحضور", @@ -6192,11 +6205,36 @@ "waitlist_explainer": "{name} صاحب أعلى تقييم ومع ذلك ما زال في قائمة الانتظار — فقد سجّل بعد امتلاء المقاعد. وقت التسجيل هو ما يحدّد من يحصل على مقعد، أما ELO فيحدّد الفريق فقط. هذه هي القاعدة تعمل كما ينبغي، وليست خللًا.", "organizer": "منظم", "draft_summary": "{agents} لاعبًا حرًّا في {teams} فرق من {size} لاعبين، و{waitlisted} في قائمة الانتظار.", - "regenerate": "أعد توزيع الفرق", + "regenerate": "وزّع الفرق", "regenerated": "تم تكوين {count} فرق", - "regenerate_failed": "تعذّر إعادة توزيع الفرق", + "regenerate_failed": "تعذّر توزيع الفرق", "join_failed": "تعذّر الانضمام إلى مجموعة اللاعبين الأحرار", - "leave_failed": "تعذّرت مغادرة مجموعة اللاعبين الأحرار" + "leave_failed": "تعذّرت مغادرة مجموعة اللاعبين الأحرار", + "already_drafted": "تم توزيع الفرق بالفعل. إعادة التوزيع لن تغيّر شيئًا — احذف الفرق الموزّعة أولًا.", + "draft_locked": "انطلقت شجرة البطولة، لذا لم يعد بالإمكان توزيع المجموعة.", + "nothing_drafted": "لم يتم توزيع أي فريق", + "nothing_drafted_hint": "لم يتبقَّ عدد كافٍ من اللاعبين الأحرار لتكوين فريق من {size}، أو لا يوجد متسع في شجرة البطولة." + }, + "entry": { + "title": "شروط المشاركة", + "requirements_hint": "يُتحقَّق منها لدى كل لاعب تتم إضافته، وليس فقط لدى من يضغط. يُرفض التسجيل إذا لم يستوفِ أحدهم الشروط.", + "locked_title": "هذه البطولة بالدعوة فقط", + "locked_hint": "أدخل رمز الدخول من المنظم لفتح التسجيل، أو اطلب منه دعوة فريقك مباشرة.", + "passcode_label": "رمز الدخول", + "passcode_placeholder": "أدخل رمز الدخول", + "unlock": "افتح", + "unlocked": "تم فتح التسجيل", + "unlock_failed": "تعذّر فتح التسجيل", + "min_role": "أدنى دور: {role}", + "elo_between": "ELO بين {min} و{max}", + "elo_min": "ELO {min} أو أعلى", + "elo_max": "ELO {max} أو أقل", + "elo_yours": "لديك: {elo}", + "requirement_met": "مستوفى", + "requirement_failed": "غير مستوفى", + "ok_chip": "مؤهل", + "locked_chip": "مقفل", + "blocked_chip": "غير مؤهل" } }, "team": { diff --git a/i18n/locales/da_DK.json b/i18n/locales/da_DK.json index dd9e7e58b..b87a301c8 100644 --- a/i18n/locales/da_DK.json +++ b/i18n/locales/da_DK.json @@ -5967,7 +5967,6 @@ "col_assists": "A", "col_kd": "K/D", "col_hs": "HS%", - "col_maps": "Maps", "empty_title": "Ingen statistik endnu", "empty_description": "Ranglisten fyldes ud, efterhånden som turneringens kampe bliver spillet færdig." }, @@ -6152,7 +6151,21 @@ "missed_heading": "Dit hold missede check-in", "missed_hint": "Check-in lukkede kl. {time}, og bracketen sættes op uden dit hold.", "missed_contact": "Kontakt en arrangør, hvis du mener, det er forkert — de kan genindsætte et hold når som helst, før turneringen går i gang.", - "failed": "Check-in mislykkedes" + "failed": "Check-in mislykkedes", + "free_agent_opens_at_hint": "Du er i free agent-puljen. Vi giver besked, når vinduet åbner.", + "free_agent_heading": "Bekræft at du stadig er ledig", + "free_agent_hint": "Free agents, der ikke har bekræftet kl. {time}, ryger på ventelisten og bliver ikke draftet ind på et hold.", + "free_agent_done_heading": "Du er med. Holdene draftes kl. {time}.", + "free_agent_done_hint": "Bekræftet kl. {time}. Du bliver draftet ind på et hold, når check-in lukker.", + "free_agent_chip_waitlisted": "venteliste", + "free_agent_missed_heading": "Du missede check-in", + "free_agent_missed_hint": "Check-in lukkede kl. {time}, og du røg på ventelisten. Du bliver kun draftet nu, hvis der bliver en plads ledig.", + "free_agent_missed_contact": "Kontakt en arrangør, hvis du mener, det er forkert — en forlængelse af check-in sætter dig tilbage i draftpuljen.", + "a11y_opens_in": "Check-in åbner om", + "a11y_opens_help": "Check-in er ikke åbnet endnu. Der er intet at bekræfte, før det sker.", + "a11y_closes_in": "Check-in lukker om", + "a11y_closes_help": "Alle, der ikke har checket ind til den tid, kommer ikke i bracketen.", + "a11y_starts_in": "Turneringen starter om" }, "check_in_review": { "heading": "{count} hold missede check-in", @@ -6192,11 +6205,36 @@ "waitlist_explainer": "{name} har den højeste rating og står stadig på venteliste — tilmeldingen kom, efter pladserne var fyldt. Tilmeldingstidspunktet afgør, hvem der får en plads; ELO afgør kun hvilket hold. Det er reglen, der virker, ikke en fejl.", "organizer": "Arrangør", "draft_summary": "{agents} free agents → {teams} hold à {size}, {waitlisted} på venteliste.", - "regenerate": "Draft holdene igen", + "regenerate": "Draft holdene", "regenerated": "{count} hold draftet", - "regenerate_failed": "Holdene kunne ikke draftes igen", + "regenerate_failed": "Holdene kunne ikke draftes", "join_failed": "Kunne ikke tilmelde sig free agent-puljen", - "leave_failed": "Kunne ikke forlade free agent-puljen" + "leave_failed": "Kunne ikke forlade free agent-puljen", + "already_drafted": "Holdene er allerede draftet. At drafte igen ville ikke gøre noget — fjern først de draftede hold.", + "draft_locked": "Bracketen er i gang, så puljen kan ikke længere draftes.", + "nothing_drafted": "Der blev ikke draftet nogen hold", + "nothing_drafted_hint": "Der er ikke nok free agents tilbage til at fylde et hold på {size}, eller også er der ikke plads i bracketen." + }, + "entry": { + "title": "Adgangskrav", + "requirements_hint": "Tjekkes på hver enkelt spiller, der bliver tilføjet, ikke kun på den, der klikker. Tilmeldingen afvises, hvis bare én af dem ikke lever op til kravene.", + "locked_title": "Denne turnering er kun med invitation", + "locked_hint": "Indtast arrangørens adgangskode for at låse tilmeldingen op, eller bed dem invitere dit hold direkte.", + "passcode_label": "Adgangskode", + "passcode_placeholder": "Indtast adgangskode", + "unlock": "Lås op", + "unlocked": "Tilmelding låst op", + "unlock_failed": "Tilmeldingen kunne ikke låses op", + "min_role": "Laveste rolle: {role}", + "elo_between": "ELO mellem {min} og {max}", + "elo_min": "ELO {min} eller derover", + "elo_max": "ELO {max} eller derunder", + "elo_yours": "din: {elo}", + "requirement_met": "opfyldt", + "requirement_failed": "ikke opfyldt", + "ok_chip": "kan deltage", + "locked_chip": "låst", + "blocked_chip": "kan ikke deltage" } }, "team": { diff --git a/i18n/locales/de_DE.json b/i18n/locales/de_DE.json index 0fb11e578..5fa6c2bce 100644 --- a/i18n/locales/de_DE.json +++ b/i18n/locales/de_DE.json @@ -5967,7 +5967,6 @@ "col_assists": "A", "col_kd": "K/D", "col_hs": "HS%", - "col_maps": "Maps", "empty_title": "Noch keine Statistiken", "empty_description": "Die Rangliste füllt sich, sobald Turniermatches beendet sind." }, @@ -6152,7 +6151,21 @@ "missed_heading": "Dein Team hat den Check-in verpasst", "missed_hint": "Der Check-in schloss um {time} und das Bracket wird ohne dein Team gesetzt.", "missed_contact": "Melde dich bei einem Veranstalter, wenn das falsch ist — er kann ein Team jederzeit vor dem Turnierstart wieder zulassen.", - "failed": "Check-in fehlgeschlagen" + "failed": "Check-in fehlgeschlagen", + "free_agent_opens_at_hint": "Du bist im Free-Agent-Pool. Wir melden uns, wenn das Fenster öffnet.", + "free_agent_heading": "Bestätige, dass du noch verfügbar bist", + "free_agent_hint": "Free Agents, die bis {time} nicht bestätigt haben, kommen auf die Warteliste und werden nicht in ein Team gedraftet.", + "free_agent_done_heading": "Du bist dabei. Die Teams werden um {time} gedraftet.", + "free_agent_done_hint": "Um {time} bestätigt. Du wirst in ein Team gedraftet, sobald der Check-in schließt.", + "free_agent_chip_waitlisted": "Warteliste", + "free_agent_missed_heading": "Du hast den Check-in verpasst", + "free_agent_missed_hint": "Der Check-in schloss um {time} und du kamst auf die Warteliste. Du wirst jetzt nur noch gedraftet, wenn ein Platz frei wird.", + "free_agent_missed_contact": "Melde dich bei einem Veranstalter, wenn das falsch ist — eine Verlängerung des Check-ins bringt dich zurück in den Draft-Pool.", + "a11y_opens_in": "Check-in öffnet in", + "a11y_opens_help": "Der Check-in hat noch nicht geöffnet. Bis dahin gibt es nichts zu bestätigen.", + "a11y_closes_in": "Check-in schließt in", + "a11y_closes_help": "Wer bis dahin nicht eingecheckt hat, kommt nicht ins Bracket.", + "a11y_starts_in": "Turnier startet in" }, "check_in_review": { "heading": "{count} Teams haben den Check-in verpasst", @@ -6192,11 +6205,36 @@ "waitlist_explainer": "{name} hat das höchste Rating und steht trotzdem auf der Warteliste — die Anmeldung kam erst, als die Plätze voll waren. Die Anmeldezeit entscheidet über den Platz, die ELO nur über das Team. Das ist die Regel, kein Bug.", "organizer": "Veranstalter", "draft_summary": "{agents} Free Agents → {teams} Teams à {size}, {waitlisted} auf der Warteliste.", - "regenerate": "Teams neu draften", + "regenerate": "Teams draften", "regenerated": "{count} Teams gedraftet", - "regenerate_failed": "Teams konnten nicht neu gedraftet werden", + "regenerate_failed": "Teams konnten nicht gedraftet werden", "join_failed": "Beitritt zum Free-Agent-Pool fehlgeschlagen", - "leave_failed": "Verlassen des Free-Agent-Pools fehlgeschlagen" + "leave_failed": "Verlassen des Free-Agent-Pools fehlgeschlagen", + "already_drafted": "Die Teams sind bereits gedraftet. Ein erneuter Draft würde nichts ändern — entferne zuerst die gedrafteten Teams.", + "draft_locked": "Das Bracket läuft bereits, der Pool kann nicht mehr gedraftet werden.", + "nothing_drafted": "Es wurden keine Teams gedraftet", + "nothing_drafted_hint": "Es sind nicht genug Free Agents übrig, um ein Team mit {size} Spielern zu füllen, oder das Bracket hat keinen Platz." + }, + "entry": { + "title": "Teilnahmevoraussetzungen", + "requirements_hint": "Wird für jeden hinzugefügten Spieler geprüft, nicht nur für den Klickenden. Die Anmeldung wird abgelehnt, wenn einer davon durchfällt.", + "locked_title": "Dieses Turnier ist nur mit Einladung", + "locked_hint": "Gib den Zugangscode des Veranstalters ein, um die Anmeldung freizuschalten, oder bitte ihn, dein Team direkt einzuladen.", + "passcode_label": "Zugangscode", + "passcode_placeholder": "Zugangscode eingeben", + "unlock": "Freischalten", + "unlocked": "Anmeldung freigeschaltet", + "unlock_failed": "Anmeldung konnte nicht freigeschaltet werden", + "min_role": "Mindestrolle: {role}", + "elo_between": "ELO zwischen {min} und {max}", + "elo_min": "ELO {min} oder höher", + "elo_max": "ELO {max} oder niedriger", + "elo_yours": "deine: {elo}", + "requirement_met": "erfüllt", + "requirement_failed": "nicht erfüllt", + "ok_chip": "teilnahmeberechtigt", + "locked_chip": "gesperrt", + "blocked_chip": "nicht berechtigt" } }, "team": { diff --git a/i18n/locales/en.json b/i18n/locales/en.json index fd11da975..23347c79c 100644 --- a/i18n/locales/en.json +++ b/i18n/locales/en.json @@ -5967,7 +5967,6 @@ "col_assists": "A", "col_kd": "K/D", "col_hs": "HS%", - "col_maps": "Maps", "empty_title": "No stats yet", "empty_description": "The leaderboard fills in as tournament matches finish." }, diff --git a/i18n/locales/es_ES.json b/i18n/locales/es_ES.json index 5947ec2d1..5c1eaeeae 100644 --- a/i18n/locales/es_ES.json +++ b/i18n/locales/es_ES.json @@ -5967,7 +5967,6 @@ "col_assists": "A", "col_kd": "K/D", "col_hs": "HS%", - "col_maps": "Mapas", "empty_title": "Aún no hay estadísticas", "empty_description": "La clasificación se rellena a medida que terminan las partidas del torneo." }, @@ -6152,7 +6151,21 @@ "missed_heading": "Tu equipo se perdió el check-in", "missed_hint": "El check-in cerró a las {time} y el cuadro se arma sin tu equipo.", "missed_contact": "Habla con un organizador si crees que es un error: puede readmitir a un equipo en cualquier momento antes de que el torneo arranque.", - "failed": "Error en el check-in" + "failed": "Error en el check-in", + "free_agent_opens_at_hint": "Estás en el grupo de agentes libres. Te avisaremos cuando se abra la ventana.", + "free_agent_heading": "Confirma que sigues disponible", + "free_agent_hint": "Los agentes libres que no hayan confirmado a las {time} pasan a la lista de espera y no se draftean en ningún equipo.", + "free_agent_done_heading": "Estás dentro. Los equipos se draftean a las {time}.", + "free_agent_done_hint": "Confirmado a las {time}. Te draftearán en un equipo cuando cierre el check-in.", + "free_agent_chip_waitlisted": "lista de espera", + "free_agent_missed_heading": "Te perdiste el check-in", + "free_agent_missed_hint": "El check-in cerró a las {time} y pasaste a la lista de espera. Ahora solo te draftearán si se libera una plaza.", + "free_agent_missed_contact": "Habla con un organizador si crees que es un error: ampliar el check-in te devuelve al grupo del draft.", + "a11y_opens_in": "El check-in abre en", + "a11y_opens_help": "El check-in aún no ha abierto. Hasta entonces no hay nada que confirmar.", + "a11y_closes_in": "El check-in cierra en", + "a11y_closes_help": "Quien no haya hecho check-in para entonces no entra en el cuadro.", + "a11y_starts_in": "El torneo empieza en" }, "check_in_review": { "heading": "{count} equipos se perdieron el check-in", @@ -6192,11 +6205,36 @@ "waitlist_explainer": "{name} es el participante con mejor rating y sigue en lista de espera: se apuntó después de que se llenaran las plazas. La hora de inscripción decide quién entra; el ELO solo decide en qué equipo. Es la regla funcionando, no un fallo.", "organizer": "Organizador", "draft_summary": "{agents} agentes libres → {teams} equipos de {size}, {waitlisted} en lista de espera.", - "regenerate": "Rehacer los equipos", + "regenerate": "Draftear equipos", "regenerated": "{count} equipos drafteados", - "regenerate_failed": "No se han podido rehacer los equipos", + "regenerate_failed": "No se han podido draftear los equipos", "join_failed": "No se ha podido entrar en el grupo de agentes libres", - "leave_failed": "No se ha podido salir del grupo de agentes libres" + "leave_failed": "No se ha podido salir del grupo de agentes libres", + "already_drafted": "Los equipos ya están drafteados. Volver a draftear no haría nada: quita antes los equipos drafteados.", + "draft_locked": "El cuadro ya está en juego, así que el grupo ya no se puede draftear.", + "nothing_drafted": "No se drafteó ningún equipo", + "nothing_drafted_hint": "No quedan suficientes agentes libres para completar un equipo de {size}, o el cuadro no tiene hueco." + }, + "entry": { + "title": "Requisitos de acceso", + "requirements_hint": "Se comprueba sobre cada jugador que se añade, no solo sobre quien hace clic. La inscripción se rechaza si alguno no los cumple.", + "locked_title": "Este torneo es solo por invitación", + "locked_hint": "Introduce el código de acceso del organizador para desbloquear la inscripción, o pídele que invite a tu equipo directamente.", + "passcode_label": "Código de acceso", + "passcode_placeholder": "Introduce el código de acceso", + "unlock": "Desbloquear", + "unlocked": "Inscripción desbloqueada", + "unlock_failed": "No se ha podido desbloquear la inscripción", + "min_role": "Rol mínimo: {role}", + "elo_between": "ELO entre {min} y {max}", + "elo_min": "ELO {min} o más", + "elo_max": "ELO {max} o menos", + "elo_yours": "el tuyo: {elo}", + "requirement_met": "cumplido", + "requirement_failed": "no cumplido", + "ok_chip": "puedes entrar", + "locked_chip": "bloqueado", + "blocked_chip": "no puedes entrar" } }, "team": { diff --git a/i18n/locales/fr_FR.json b/i18n/locales/fr_FR.json index f73324dbc..f3054279b 100644 --- a/i18n/locales/fr_FR.json +++ b/i18n/locales/fr_FR.json @@ -5967,7 +5967,6 @@ "col_assists": "A", "col_kd": "K/D", "col_hs": "HS%", - "col_maps": "Maps", "empty_title": "Aucune stat pour l'instant", "empty_description": "Le classement se remplit au fur et à mesure que les matchs du tournoi se terminent." }, @@ -6152,7 +6151,21 @@ "missed_heading": "Ton équipe a raté le check-in", "missed_hint": "Le check-in a fermé à {time} et le bracket est constitué sans ton équipe.", "missed_contact": "Contacte un organisateur si tu penses que c'est une erreur — il peut réadmettre une équipe à tout moment avant le lancement du tournoi.", - "failed": "Échec du check-in" + "failed": "Échec du check-in", + "free_agent_opens_at_hint": "Tu es dans le pool d'agents libres. On te préviendra à l'ouverture de la fenêtre.", + "free_agent_heading": "Confirme que tu es toujours disponible", + "free_agent_hint": "Les agents libres qui n'ont pas confirmé à {time} passent sur liste d'attente et ne sont pas draftés dans une équipe.", + "free_agent_done_heading": "C'est bon. Les équipes sont draftées à {time}.", + "free_agent_done_hint": "Confirmé à {time}. Tu seras drafté dans une équipe à la fermeture du check-in.", + "free_agent_chip_waitlisted": "liste d'attente", + "free_agent_missed_heading": "Tu as raté le check-in", + "free_agent_missed_hint": "Le check-in a fermé à {time} et tu es passé sur liste d'attente. Tu ne seras drafté que si une place se libère.", + "free_agent_missed_contact": "Contacte un organisateur si tu penses que c'est une erreur — prolonger le check-in te remet dans le pool de draft.", + "a11y_opens_in": "Le check-in ouvre dans", + "a11y_opens_help": "Le check-in n'a pas encore ouvert. Il n'y a rien à confirmer avant.", + "a11y_closes_in": "Le check-in ferme dans", + "a11y_closes_help": "Quiconque n'a pas fait son check-in d'ici là n'est pas placé dans le bracket.", + "a11y_starts_in": "Le tournoi commence dans" }, "check_in_review": { "heading": "{count} équipes ont raté le check-in", @@ -6192,11 +6205,36 @@ "waitlist_explainer": "{name} a le meilleur rating et reste sur liste d'attente — son inscription est arrivée après le remplissage des places. L'heure d'inscription décide de la place, l'ELO ne décide que de l'équipe. C'est la règle qui s'applique, pas un bug.", "organizer": "Organisateur", "draft_summary": "{agents} agents libres → {teams} équipes de {size}, {waitlisted} sur liste d'attente.", - "regenerate": "Redrafter les équipes", + "regenerate": "Drafter les équipes", "regenerated": "{count} équipes draftées", - "regenerate_failed": "Impossible de redrafter les équipes", + "regenerate_failed": "Impossible de drafter les équipes", "join_failed": "Impossible de rejoindre le pool d'agents libres", - "leave_failed": "Impossible de quitter le pool d'agents libres" + "leave_failed": "Impossible de quitter le pool d'agents libres", + "already_drafted": "Les équipes sont déjà draftées. Refaire le draft ne changerait rien — supprime d'abord les équipes draftées.", + "draft_locked": "Le bracket est en cours, le pool ne peut plus être drafté.", + "nothing_drafted": "Aucune équipe n'a été draftée", + "nothing_drafted_hint": "Il ne reste pas assez d'agents libres pour compléter une équipe de {size}, ou le bracket n'a plus de place." + }, + "entry": { + "title": "Conditions d'inscription", + "requirements_hint": "Vérifié sur chaque joueur ajouté, pas seulement sur celui qui clique. L'inscription est refusée si l'un d'eux ne remplit pas les conditions.", + "locked_title": "Ce tournoi est sur invitation", + "locked_hint": "Saisis le code d'accès de l'organisateur pour débloquer l'inscription, ou demande-lui d'inviter ton équipe directement.", + "passcode_label": "Code d'accès", + "passcode_placeholder": "Saisis le code d'accès", + "unlock": "Débloquer", + "unlocked": "Inscription débloquée", + "unlock_failed": "Impossible de débloquer l'inscription", + "min_role": "Rôle minimum : {role}", + "elo_between": "ELO entre {min} et {max}", + "elo_min": "ELO {min} ou plus", + "elo_max": "ELO {max} ou moins", + "elo_yours": "le tien : {elo}", + "requirement_met": "rempli", + "requirement_failed": "non rempli", + "ok_chip": "éligible", + "locked_chip": "verrouillé", + "blocked_chip": "non éligible" } }, "team": { diff --git a/i18n/locales/it_IT.json b/i18n/locales/it_IT.json index 04ba8bbf6..25ef3502e 100644 --- a/i18n/locales/it_IT.json +++ b/i18n/locales/it_IT.json @@ -5967,7 +5967,6 @@ "col_assists": "A", "col_kd": "K/D", "col_hs": "HS%", - "col_maps": "Mappe", "empty_title": "Ancora nessuna statistica", "empty_description": "La classifica si riempie man mano che i match del torneo finiscono." }, @@ -6152,7 +6151,21 @@ "missed_heading": "Il tuo team ha saltato il check-in", "missed_hint": "Il check-in si è chiuso alle {time} e il bracket viene composto senza il tuo team.", "missed_contact": "Contatta un organizzatore se pensi sia un errore: può riammettere un team in qualsiasi momento prima che il torneo parta.", - "failed": "Check-in non riuscito" + "failed": "Check-in non riuscito", + "free_agent_opens_at_hint": "Sei nel pool dei free agent. Ti avvisiamo all'apertura della finestra.", + "free_agent_heading": "Conferma di essere ancora disponibile", + "free_agent_hint": "I free agent che non hanno confermato entro le {time} passano in lista d'attesa e non vengono draftati in un team.", + "free_agent_done_heading": "Sei dentro. I team vengono draftati alle {time}.", + "free_agent_done_hint": "Confermato alle {time}. Verrai draftato in un team alla chiusura del check-in.", + "free_agent_chip_waitlisted": "lista d'attesa", + "free_agent_missed_heading": "Hai saltato il check-in", + "free_agent_missed_hint": "Il check-in si è chiuso alle {time} e sei passato in lista d'attesa. Ora vieni draftato solo se si libera un posto.", + "free_agent_missed_contact": "Contatta un organizzatore se pensi sia un errore: estendere il check-in ti rimette nel pool del draft.", + "a11y_opens_in": "Il check-in apre tra", + "a11y_opens_help": "Il check-in non è ancora aperto. Fino ad allora non c'è nulla da confermare.", + "a11y_closes_in": "Il check-in chiude tra", + "a11y_closes_help": "Chi non ha fatto il check-in entro quel momento non entra nel bracket.", + "a11y_starts_in": "Il torneo inizia tra" }, "check_in_review": { "heading": "{count} team hanno saltato il check-in", @@ -6192,11 +6205,36 @@ "waitlist_explainer": "{name} ha il rating più alto ed è ancora in lista d'attesa: si è iscritto dopo che i posti erano esauriti. È l'orario di iscrizione a decidere chi ottiene un posto; l'ELO decide solo in quale team. È la regola che funziona, non un bug.", "organizer": "Organizzatore", "draft_summary": "{agents} free agent → {teams} team da {size}, {waitlisted} in lista d'attesa.", - "regenerate": "Rigenera i team", + "regenerate": "Drafta i team", "regenerated": "{count} team draftati", - "regenerate_failed": "Impossibile rigenerare i team", + "regenerate_failed": "Impossibile draftare i team", "join_failed": "Impossibile entrare nel pool dei free agent", - "leave_failed": "Impossibile uscire dal pool dei free agent" + "leave_failed": "Impossibile uscire dal pool dei free agent", + "already_drafted": "I team sono già stati draftati. Rifare il draft non cambierebbe nulla: rimuovi prima i team draftati.", + "draft_locked": "Il bracket è già in corso, quindi il pool non può più essere draftato.", + "nothing_drafted": "Nessun team è stato draftato", + "nothing_drafted_hint": "Non restano abbastanza free agent per completare un team da {size}, oppure il bracket non ha più posto." + }, + "entry": { + "title": "Requisiti di accesso", + "requirements_hint": "Viene verificato su ogni giocatore che aggiungi, non solo su chi clicca. L'iscrizione viene rifiutata se anche solo uno non li soddisfa.", + "locked_title": "Questo torneo è solo su invito", + "locked_hint": "Inserisci il codice di accesso dell'organizzatore per sbloccare l'iscrizione, oppure chiedigli di invitare il tuo team direttamente.", + "passcode_label": "Codice di accesso", + "passcode_placeholder": "Inserisci il codice di accesso", + "unlock": "Sblocca", + "unlocked": "Iscrizione sbloccata", + "unlock_failed": "Impossibile sbloccare l'iscrizione", + "min_role": "Ruolo minimo: {role}", + "elo_between": "ELO tra {min} e {max}", + "elo_min": "ELO {min} o superiore", + "elo_max": "ELO {max} o inferiore", + "elo_yours": "il tuo: {elo}", + "requirement_met": "soddisfatto", + "requirement_failed": "non soddisfatto", + "ok_chip": "idoneo", + "locked_chip": "bloccato", + "blocked_chip": "non idoneo" } }, "team": { diff --git a/i18n/locales/ja_JP.json b/i18n/locales/ja_JP.json index cdd07b9ea..8ca01eb22 100644 --- a/i18n/locales/ja_JP.json +++ b/i18n/locales/ja_JP.json @@ -5967,7 +5967,6 @@ "col_assists": "A", "col_kd": "K/D", "col_hs": "HS%", - "col_maps": "マップ", "empty_title": "スタッツはまだありません", "empty_description": "トーナメントの試合が終わるにつれてランキングが埋まります。" }, @@ -6152,7 +6151,21 @@ "missed_heading": "チームはチェックインを逃しました", "missed_hint": "チェックインは {time} に終了し、ブラケットはあなたのチーム抜きで組まれます。", "missed_contact": "間違いだと思う場合は主催者に連絡してください。トーナメント開始前ならいつでもチームを再承認できます。", - "failed": "チェックインに失敗しました" + "failed": "チェックインに失敗しました", + "free_agent_opens_at_hint": "あなたはフリーエージェントプールに登録済みです。受付が始まったらお知らせします。", + "free_agent_heading": "参加可能かどうか確認してください", + "free_agent_hint": "{time} までに確認しなかったフリーエージェントは補欠に移され、チームへドラフトされません。", + "free_agent_done_heading": "参加確定です。チームは {time} にドラフトされます。", + "free_agent_done_hint": "{time} に確認済み。チェックインが終了するとチームへドラフトされます。", + "free_agent_chip_waitlisted": "補欠", + "free_agent_missed_heading": "チェックインを逃しました", + "free_agent_missed_hint": "チェックインは {time} に終了し、あなたは補欠に移されました。今後は空きが出た場合のみドラフトされます。", + "free_agent_missed_contact": "間違いだと思う場合は主催者に連絡してください。チェックインを延長すればドラフト対象に戻ります。", + "a11y_opens_in": "チェックイン開始まで", + "a11y_opens_help": "チェックインはまだ始まっていません。始まるまで確認することはありません。", + "a11y_closes_in": "チェックイン終了まで", + "a11y_closes_help": "その時点でチェックインしていない場合、ブラケットには入りません。", + "a11y_starts_in": "トーナメント開始まで" }, "check_in_review": { "heading": "{count}チームがチェックインを逃しました", @@ -6192,11 +6205,36 @@ "waitlist_explainer": "{name} はレーティング最上位ですが、まだ補欠です。枠が埋まった後に登録したためです。枠を得られるかは登録の早さで決まり、ELO はどのチームになるかだけを決めます。これはルールどおりの動作であり、不具合ではありません。", "organizer": "主催者", "draft_summary": "フリーエージェント{agents}人 → {size}人編成のチーム{teams}組、補欠{waitlisted}人。", - "regenerate": "チームを再編成", + "regenerate": "チームをドラフト", "regenerated": "{count}チームを編成しました", - "regenerate_failed": "チームを再編成できませんでした", + "regenerate_failed": "チームをドラフトできませんでした", "join_failed": "フリーエージェントプールに参加できませんでした", - "leave_failed": "フリーエージェントプールから抜けられませんでした" + "leave_failed": "フリーエージェントプールから抜けられませんでした", + "already_drafted": "チームはすでにドラフト済みです。もう一度実行しても何も変わりません。まず作成済みのチームを削除してください。", + "draft_locked": "ブラケットが進行中のため、プールはもうドラフトできません。", + "nothing_drafted": "チームは作成されませんでした", + "nothing_drafted_hint": "{size}人編成のチームを組むだけのフリーエージェントが残っていないか、ブラケットに空きがありません。" + }, + "entry": { + "title": "参加条件", + "requirements_hint": "クリックした人だけでなく、追加されるすべてのプレイヤーで判定します。1人でも条件を満たさない場合、参加登録は拒否されます。", + "locked_title": "このトーナメントは招待制です", + "locked_hint": "主催者のパスコードを入力して参加登録を解除するか、チームを直接招待してもらってください。", + "passcode_label": "パスコード", + "passcode_placeholder": "パスコードを入力", + "unlock": "解除", + "unlocked": "参加登録を解除しました", + "unlock_failed": "参加登録を解除できませんでした", + "min_role": "最低ロール: {role}", + "elo_between": "ELO {min} 〜 {max}", + "elo_min": "ELO {min} 以上", + "elo_max": "ELO {max} 以下", + "elo_yours": "あなた: {elo}", + "requirement_met": "条件を満たしています", + "requirement_failed": "条件を満たしていません", + "ok_chip": "参加可能", + "locked_chip": "ロック中", + "blocked_chip": "参加不可" } }, "team": { diff --git a/i18n/locales/ko_KR.json b/i18n/locales/ko_KR.json index 5d9193c12..e232126f4 100644 --- a/i18n/locales/ko_KR.json +++ b/i18n/locales/ko_KR.json @@ -5967,7 +5967,6 @@ "col_assists": "A", "col_kd": "K/D", "col_hs": "HS%", - "col_maps": "맵", "empty_title": "아직 통계가 없습니다", "empty_description": "토너먼트 매치가 끝날 때마다 리더보드가 채워집니다." }, @@ -6152,7 +6151,21 @@ "missed_heading": "팀이 체크인을 놓쳤습니다", "missed_hint": "체크인은 {time}에 마감되었고, 대진표는 회원님의 팀 없이 짜입니다.", "missed_contact": "잘못되었다고 생각되면 주최자에게 연락하세요 — 토너먼트가 시작되기 전이라면 언제든 팀을 재승인할 수 있습니다.", - "failed": "체크인에 실패했습니다" + "failed": "체크인에 실패했습니다", + "free_agent_opens_at_hint": "프리 에이전트 풀에 등록되어 있습니다. 창이 열리면 알려드리겠습니다.", + "free_agent_heading": "아직 참가 가능한지 확정하세요", + "free_agent_hint": "{time}까지 확정하지 않은 프리 에이전트는 대기 명단으로 옮겨지고 어느 팀에도 드래프트되지 않습니다.", + "free_agent_done_heading": "참가가 확정되었습니다. 팀은 {time}에 드래프트됩니다.", + "free_agent_done_hint": "{time}에 확정됨. 체크인이 마감되면 팀으로 드래프트됩니다.", + "free_agent_chip_waitlisted": "대기 명단", + "free_agent_missed_heading": "체크인을 놓쳤습니다", + "free_agent_missed_hint": "체크인은 {time}에 마감되었고 회원님은 대기 명단으로 옮겨졌습니다. 이제는 자리가 나야만 드래프트됩니다.", + "free_agent_missed_contact": "잘못되었다고 생각되면 주최자에게 연락하세요 — 체크인을 연장하면 다시 드래프트 풀에 들어갑니다.", + "a11y_opens_in": "체크인이 열리기까지", + "a11y_opens_help": "체크인이 아직 열리지 않았습니다. 열리기 전까지는 확정할 것이 없습니다.", + "a11y_closes_in": "체크인이 닫히기까지", + "a11y_closes_help": "그때까지 체크인하지 않은 사람은 대진표에 들어가지 않습니다.", + "a11y_starts_in": "토너먼트 시작까지" }, "check_in_review": { "heading": "{count}개 팀이 체크인을 놓쳤습니다", @@ -6192,11 +6205,36 @@ "waitlist_explainer": "{name} 님은 레이팅이 가장 높은데도 대기 명단에 있습니다 — 자리가 찬 뒤에 신청했기 때문입니다. 자리를 얻는 기준은 신청 시각이고, ELO는 어느 팀에 갈지만 정합니다. 버그가 아니라 규칙대로 동작한 것입니다.", "organizer": "주최자", "draft_summary": "프리 에이전트 {agents}명 → {size}명 구성 {teams}개 팀, 대기 명단 {waitlisted}명.", - "regenerate": "팀 다시 구성", + "regenerate": "팀 드래프트", "regenerated": "{count}개 팀을 구성했습니다", - "regenerate_failed": "팀을 다시 구성하지 못했습니다", + "regenerate_failed": "팀을 드래프트하지 못했습니다", "join_failed": "프리 에이전트 풀에 참가하지 못했습니다", - "leave_failed": "프리 에이전트 풀에서 나가지 못했습니다" + "leave_failed": "프리 에이전트 풀에서 나가지 못했습니다", + "already_drafted": "팀이 이미 드래프트되었습니다. 다시 드래프트해도 달라지는 것이 없습니다 — 먼저 구성된 팀을 제거하세요.", + "draft_locked": "대진표가 진행 중이라 풀을 더 이상 드래프트할 수 없습니다.", + "nothing_drafted": "구성된 팀이 없습니다", + "nothing_drafted_hint": "{size}명 팀을 채울 만큼 프리 에이전트가 남지 않았거나, 대진표에 자리가 없습니다." + }, + "entry": { + "title": "참가 조건", + "requirements_hint": "클릭한 사람뿐 아니라 추가되는 모든 선수를 기준으로 확인합니다. 한 명이라도 충족하지 못하면 참가 신청이 거부됩니다.", + "locked_title": "이 토너먼트는 초대 전용입니다", + "locked_hint": "주최자의 참가 코드를 입력해 신청을 열거나, 팀을 직접 초대해 달라고 요청하세요.", + "passcode_label": "참가 코드", + "passcode_placeholder": "참가 코드 입력", + "unlock": "잠금 해제", + "unlocked": "참가 신청이 열렸습니다", + "unlock_failed": "참가 신청을 열지 못했습니다", + "min_role": "최소 역할: {role}", + "elo_between": "ELO {min} ~ {max}", + "elo_min": "ELO {min} 이상", + "elo_max": "ELO {max} 이하", + "elo_yours": "회원님: {elo}", + "requirement_met": "충족", + "requirement_failed": "미충족", + "ok_chip": "참가 가능", + "locked_chip": "잠김", + "blocked_chip": "참가 불가" } }, "team": { diff --git a/i18n/locales/pl_PL.json b/i18n/locales/pl_PL.json index 2681ec54a..61772e804 100644 --- a/i18n/locales/pl_PL.json +++ b/i18n/locales/pl_PL.json @@ -5967,7 +5967,6 @@ "col_assists": "A", "col_kd": "K/D", "col_hs": "HS%", - "col_maps": "Mapy", "empty_title": "Brak statystyk", "empty_description": "Ranking wypełnia się w miarę kończenia meczów turnieju." }, @@ -6152,7 +6151,21 @@ "missed_heading": "Twoja drużyna przegapiła potwierdzenie obecności", "missed_hint": "Potwierdzanie zamknęło się o {time} i drabinka powstaje bez twojej drużyny.", "missed_contact": "Skontaktuj się z organizatorem, jeśli uważasz, że to pomyłka — może przywrócić drużynę w dowolnej chwili przed startem turnieju.", - "failed": "Nie udało się potwierdzić obecności" + "failed": "Nie udało się potwierdzić obecności", + "free_agent_opens_at_hint": "Jesteś w puli wolnych zawodników. Damy znać, gdy otworzy się okno.", + "free_agent_heading": "Potwierdź, że nadal jesteś dostępny", + "free_agent_hint": "Wolni zawodnicy, którzy nie potwierdzą do {time}, trafiają na listę rezerwową i nie zostają wybrani do żadnej drużyny.", + "free_agent_done_heading": "Jesteś w turnieju. Drużyny powstają w drafcie o {time}.", + "free_agent_done_hint": "Potwierdzono o {time}. Trafisz do drużyny, gdy zamknie się potwierdzanie obecności.", + "free_agent_chip_waitlisted": "lista rezerwowa", + "free_agent_missed_heading": "Przegapiłeś potwierdzenie obecności", + "free_agent_missed_hint": "Potwierdzanie zamknęło się o {time} i trafiłeś na listę rezerwową. Teraz trafisz do drużyny tylko wtedy, gdy zwolni się miejsce.", + "free_agent_missed_contact": "Skontaktuj się z organizatorem, jeśli uważasz, że to pomyłka — wydłużenie potwierdzania przywraca cię do puli draftu.", + "a11y_opens_in": "Potwierdzanie obecności otwiera się za", + "a11y_opens_help": "Potwierdzanie obecności jeszcze się nie zaczęło. Do tego czasu nie ma czego potwierdzać.", + "a11y_closes_in": "Potwierdzanie obecności zamyka się za", + "a11y_closes_help": "Kto do tego czasu nie potwierdzi obecności, nie trafi do drabinki.", + "a11y_starts_in": "Turniej zaczyna się za" }, "check_in_review": { "heading": "{count} drużyn przegapiło potwierdzenie obecności", @@ -6192,11 +6205,36 @@ "waitlist_explainer": "{name} ma najwyższy rating i wciąż jest na liście rezerwowej — zapisał się po tym, jak miejsca się zapełniły. O miejscu decyduje czas zapisu, a ELO tylko o tym, do której drużyny trafisz. Tak działa ta zasada, to nie błąd.", "organizer": "Organizator", "draft_summary": "{agents} wolnych zawodników → {teams} drużyn po {size}, {waitlisted} na liście rezerwowej.", - "regenerate": "Ułóż drużyny od nowa", + "regenerate": "Przeprowadź draft", "regenerated": "Ułożono drużyn: {count}", - "regenerate_failed": "Nie udało się ułożyć drużyn od nowa", + "regenerate_failed": "Nie udało się przeprowadzić draftu", "join_failed": "Nie udało się dołączyć do puli wolnych zawodników", - "leave_failed": "Nie udało się opuścić puli wolnych zawodników" + "leave_failed": "Nie udało się opuścić puli wolnych zawodników", + "already_drafted": "Drużyny zostały już wybrane w drafcie. Ponowny draft niczego nie zmieni — najpierw usuń utworzone drużyny.", + "draft_locked": "Drabinka jest już w grze, więc puli nie da się już rozdzielić w drafcie.", + "nothing_drafted": "Nie utworzono żadnych drużyn", + "nothing_drafted_hint": "Nie zostało dość wolnych zawodników, by skompletować drużynę liczącą {size}, albo w drabince nie ma miejsca." + }, + "entry": { + "title": "Wymagania udziału", + "requirements_hint": "Sprawdzane dla każdego dodawanego gracza, a nie tylko dla tego, kto klika. Rejestracja zostaje odrzucona, jeśli choć jeden ich nie spełnia.", + "locked_title": "Ten turniej jest tylko na zaproszenie", + "locked_hint": "Podaj kod dostępu od organizatora, aby odblokować rejestrację, albo poproś go o bezpośrednie zaproszenie drużyny.", + "passcode_label": "Kod dostępu", + "passcode_placeholder": "Wpisz kod dostępu", + "unlock": "Odblokuj", + "unlocked": "Rejestracja odblokowana", + "unlock_failed": "Nie udało się odblokować rejestracji", + "min_role": "Minimalna rola: {role}", + "elo_between": "ELO między {min} a {max}", + "elo_min": "ELO {min} lub więcej", + "elo_max": "ELO {max} lub mniej", + "elo_yours": "twoje: {elo}", + "requirement_met": "spełnione", + "requirement_failed": "niespełnione", + "ok_chip": "może dołączyć", + "locked_chip": "zablokowane", + "blocked_chip": "nie może dołączyć" } }, "team": { diff --git a/i18n/locales/pt_BR.json b/i18n/locales/pt_BR.json index 4d64270f5..4b0626736 100644 --- a/i18n/locales/pt_BR.json +++ b/i18n/locales/pt_BR.json @@ -5967,7 +5967,6 @@ "col_assists": "A", "col_kd": "K/D", "col_hs": "HS%", - "col_maps": "Mapas", "empty_title": "Ainda não há estatísticas", "empty_description": "O ranking vai sendo preenchido conforme as partidas do torneio terminam." }, @@ -6152,7 +6151,21 @@ "missed_heading": "Sua equipe perdeu o check-in", "missed_hint": "O check-in fechou às {time} e a chave é montada sem a sua equipe.", "missed_contact": "Fale com um organizador se achar que isso está errado — ele pode readmitir uma equipe a qualquer momento antes de o torneio começar.", - "failed": "Falha no check-in" + "failed": "Falha no check-in", + "free_agent_opens_at_hint": "Você está no grupo de agentes livres. Avisaremos quando a janela abrir.", + "free_agent_heading": "Confirme que você ainda está disponível", + "free_agent_hint": "Agentes livres que não confirmarem até {time} vão para a lista de espera e não são draftados em nenhuma equipe.", + "free_agent_done_heading": "Você está dentro. As equipes são draftadas às {time}.", + "free_agent_done_hint": "Confirmado às {time}. Você será draftado em uma equipe quando o check-in fechar.", + "free_agent_chip_waitlisted": "lista de espera", + "free_agent_missed_heading": "Você perdeu o check-in", + "free_agent_missed_hint": "O check-in fechou às {time} e você foi para a lista de espera. Agora você só é draftado se abrir uma vaga.", + "free_agent_missed_contact": "Fale com um organizador se achar que isso está errado — estender o check-in coloca você de volta no grupo do draft.", + "a11y_opens_in": "O check-in abre em", + "a11y_opens_help": "O check-in ainda não abriu. Até lá não há nada a confirmar.", + "a11y_closes_in": "O check-in fecha em", + "a11y_closes_help": "Quem não tiver feito check-in até lá não entra na chave.", + "a11y_starts_in": "O torneio começa em" }, "check_in_review": { "heading": "{count} equipes perderam o check-in", @@ -6192,11 +6205,36 @@ "waitlist_explainer": "{name} tem o melhor rating e ainda está na lista de espera — se inscreveu depois que as vagas encheram. O horário da inscrição decide quem pega vaga; o ELO decide só em qual equipe. É a regra funcionando, não um bug.", "organizer": "Organizador", "draft_summary": "{agents} agentes livres → {teams} equipes de {size}, {waitlisted} na lista de espera.", - "regenerate": "Refazer as equipes", + "regenerate": "Draftar equipes", "regenerated": "{count} equipes draftadas", - "regenerate_failed": "Falha ao refazer as equipes", + "regenerate_failed": "Falha ao draftar as equipes", "join_failed": "Falha ao entrar no grupo de agentes livres", - "leave_failed": "Falha ao sair do grupo de agentes livres" + "leave_failed": "Falha ao sair do grupo de agentes livres", + "already_drafted": "As equipes já foram draftadas. Draftar de novo não faria nada — remova antes as equipes draftadas.", + "draft_locked": "A chave já está em andamento, então o grupo não pode mais ser draftado.", + "nothing_drafted": "Nenhuma equipe foi draftada", + "nothing_drafted_hint": "Não sobraram agentes livres suficientes para completar uma equipe de {size}, ou a chave não tem espaço." + }, + "entry": { + "title": "Requisitos de participação", + "requirements_hint": "Verificado em cada jogador que está sendo adicionado, não só em quem clica. A inscrição é recusada se algum deles não atender.", + "locked_title": "Este torneio é somente por convite", + "locked_hint": "Informe o código de acesso do organizador para liberar a inscrição, ou peça para ele convidar sua equipe diretamente.", + "passcode_label": "Código de acesso", + "passcode_placeholder": "Informe o código de acesso", + "unlock": "Liberar", + "unlocked": "Inscrição liberada", + "unlock_failed": "Não foi possível liberar a inscrição", + "min_role": "Cargo mínimo: {role}", + "elo_between": "ELO entre {min} e {max}", + "elo_min": "ELO {min} ou mais", + "elo_max": "ELO {max} ou menos", + "elo_yours": "o seu: {elo}", + "requirement_met": "atendido", + "requirement_failed": "não atendido", + "ok_chip": "pode participar", + "locked_chip": "bloqueado", + "blocked_chip": "não pode participar" } }, "team": { diff --git a/i18n/locales/ru_RU.json b/i18n/locales/ru_RU.json index 698250ef8..5cbf4e74a 100644 --- a/i18n/locales/ru_RU.json +++ b/i18n/locales/ru_RU.json @@ -5967,7 +5967,6 @@ "col_assists": "А", "col_kd": "K/D", "col_hs": "HS%", - "col_maps": "Карты", "empty_title": "Статистики пока нет", "empty_description": "Таблица лидеров заполняется по мере завершения матчей турнира." }, @@ -6152,7 +6151,21 @@ "missed_heading": "Твоя команда пропустила чек-ин", "missed_hint": "Чек-ин закрылся в {time}, и сетка собирается без твоей команды.", "missed_contact": "Свяжись с организатором, если считаешь это ошибкой — он может восстановить команду в любой момент до старта турнира.", - "failed": "Не удалось отметиться" + "failed": "Не удалось отметиться", + "free_agent_opens_at_hint": "Ты в пуле свободных агентов. Сообщим, когда откроется окно.", + "free_agent_heading": "Подтверди, что ты всё ещё свободен", + "free_agent_hint": "Свободные агенты, не подтвердившие участие к {time}, уходят в лист ожидания и не попадают в команды на драфте.", + "free_agent_done_heading": "Ты в турнире. Команды собирают драфтом в {time}.", + "free_agent_done_hint": "Подтверждено в {time}. Тебя возьмут в команду, когда чек-ин закроется.", + "free_agent_chip_waitlisted": "лист ожидания", + "free_agent_missed_heading": "Ты пропустил чек-ин", + "free_agent_missed_hint": "Чек-ин закрылся в {time}, и ты ушёл в лист ожидания. Теперь тебя возьмут в команду, только если освободится место.", + "free_agent_missed_contact": "Свяжись с организатором, если считаешь это ошибкой — продление чек-ина вернёт тебя в пул драфта.", + "a11y_opens_in": "Чек-ин откроется через", + "a11y_opens_help": "Чек-ин ещё не открыт. До этого момента подтверждать нечего.", + "a11y_closes_in": "Чек-ин закроется через", + "a11y_closes_help": "Все, кто не отметится к этому моменту, в сетку не попадут.", + "a11y_starts_in": "Турнир начнётся через" }, "check_in_review": { "heading": "Команд пропустило чек-ин: {count}", @@ -6192,11 +6205,36 @@ "waitlist_explainer": "{name} — участник с самым высоким рейтингом, но всё ещё в листе ожидания: он записался, когда места уже закончились. Место решает время записи, а ELO решает только команду. Это правило работает как надо, а не баг.", "organizer": "Организатор", "draft_summary": "{agents} свободных агентов → {teams} команд по {size}, {waitlisted} в листе ожидания.", - "regenerate": "Пересобрать команды", + "regenerate": "Провести драфт", "regenerated": "Собрано команд: {count}", - "regenerate_failed": "Не удалось пересобрать команды", + "regenerate_failed": "Не удалось провести драфт", "join_failed": "Не удалось войти в пул свободных агентов", - "leave_failed": "Не удалось покинуть пул свободных агентов" + "leave_failed": "Не удалось покинуть пул свободных агентов", + "already_drafted": "Команды уже собраны драфтом. Повторный драфт ничего не изменит — сначала удали собранные команды.", + "draft_locked": "Сетка уже в игре, поэтому пул больше нельзя разобрать драфтом.", + "nothing_drafted": "Ни одной команды не собрано", + "nothing_drafted_hint": "Свободных агентов не хватает, чтобы собрать команду из {size}, либо в сетке нет места." + }, + "entry": { + "title": "Требования к участию", + "requirements_hint": "Проверяется у каждого добавляемого игрока, а не только у того, кто нажимает. Регистрация отклоняется, если хотя бы один не проходит.", + "locked_title": "Этот турнир только по приглашению", + "locked_hint": "Введи код доступа от организатора, чтобы открыть регистрацию, или попроси его пригласить твою команду напрямую.", + "passcode_label": "Код доступа", + "passcode_placeholder": "Введи код доступа", + "unlock": "Открыть", + "unlocked": "Регистрация открыта", + "unlock_failed": "Не удалось открыть регистрацию", + "min_role": "Минимальная роль: {role}", + "elo_between": "ELO от {min} до {max}", + "elo_min": "ELO {min} или выше", + "elo_max": "ELO {max} или ниже", + "elo_yours": "у тебя: {elo}", + "requirement_met": "выполнено", + "requirement_failed": "не выполнено", + "ok_chip": "допущен", + "locked_chip": "закрыто", + "blocked_chip": "не допущен" } }, "team": { diff --git a/i18n/locales/sv_SE.json b/i18n/locales/sv_SE.json index b38b0fe62..dc5da0721 100644 --- a/i18n/locales/sv_SE.json +++ b/i18n/locales/sv_SE.json @@ -5967,7 +5967,6 @@ "col_assists": "A", "col_kd": "K/D", "col_hs": "HS%", - "col_maps": "Kartor", "empty_title": "Ingen statistik än", "empty_description": "Topplistan fylls på efterhand som turneringens matcher spelas klart." }, @@ -6152,7 +6151,21 @@ "missed_heading": "Ditt lag missade incheckningen", "missed_hint": "Incheckningen stängde {time} och slutspelsträdet sätts utan ditt lag.", "missed_contact": "Kontakta en arrangör om du tycker att det blivit fel — hen kan återinsätta ett lag när som helst innan turneringen drar igång.", - "failed": "Incheckningen misslyckades" + "failed": "Incheckningen misslyckades", + "free_agent_opens_at_hint": "Du är i free agent-poolen. Vi hör av oss när fönstret öppnar.", + "free_agent_heading": "Bekräfta att du fortfarande är tillgänglig", + "free_agent_hint": "Free agents som inte har bekräftat {time} flyttas till väntelistan och draftas inte in i något lag.", + "free_agent_done_heading": "Du är med. Lagen draftas {time}.", + "free_agent_done_hint": "Bekräftat {time}. Du draftas in i ett lag när incheckningen stänger.", + "free_agent_chip_waitlisted": "väntelista", + "free_agent_missed_heading": "Du missade incheckningen", + "free_agent_missed_hint": "Incheckningen stängde {time} och du flyttades till väntelistan. Nu draftas du bara om en plats blir ledig.", + "free_agent_missed_contact": "Kontakta en arrangör om du tycker att det blivit fel — en förlängd incheckning sätter tillbaka dig i draftpoolen.", + "a11y_opens_in": "Incheckningen öppnar om", + "a11y_opens_help": "Incheckningen har inte öppnat än. Det finns inget att bekräfta förrän den gör det.", + "a11y_closes_in": "Incheckningen stänger om", + "a11y_closes_help": "Alla som inte har checkat in till dess placeras inte i slutspelsträdet.", + "a11y_starts_in": "Turneringen startar om" }, "check_in_review": { "heading": "{count} lag missade incheckningen", @@ -6192,11 +6205,36 @@ "waitlist_explainer": "{name} har högst rating och står ändå på väntelistan — anmälan kom efter att platserna fyllts. Anmälningstiden avgör vem som får en plats; ELO avgör bara vilket lag. Det är regeln som fungerar, inte en bugg.", "organizer": "Arrangör", "draft_summary": "{agents} free agents → {teams} lag på {size}, {waitlisted} på väntelista.", - "regenerate": "Drafta om lagen", + "regenerate": "Drafta lagen", "regenerated": "{count} lag draftade", - "regenerate_failed": "Kunde inte drafta om lagen", + "regenerate_failed": "Kunde inte drafta lagen", "join_failed": "Kunde inte gå med i free agent-poolen", - "leave_failed": "Kunde inte lämna free agent-poolen" + "leave_failed": "Kunde inte lämna free agent-poolen", + "already_drafted": "Lagen är redan draftade. Att drafta igen skulle inte göra något — ta bort de draftade lagen först.", + "draft_locked": "Slutspelsträdet är i gång, så poolen kan inte längre draftas.", + "nothing_drafted": "Inga lag draftades", + "nothing_drafted_hint": "Det finns inte tillräckligt många free agents kvar för att fylla ett lag på {size}, eller så finns det ingen plats i slutspelsträdet." + }, + "entry": { + "title": "Deltagarkrav", + "requirements_hint": "Kontrolleras mot varje spelare som läggs till, inte bara mot den som klickar. Anmälan nekas om någon av dem inte uppfyller kraven.", + "locked_title": "Den här turneringen är endast med inbjudan", + "locked_hint": "Ange arrangörens åtkomstkod för att låsa upp anmälan, eller be dem bjuda in ditt lag direkt.", + "passcode_label": "Åtkomstkod", + "passcode_placeholder": "Ange åtkomstkod", + "unlock": "Lås upp", + "unlocked": "Anmälan upplåst", + "unlock_failed": "Kunde inte låsa upp anmälan", + "min_role": "Lägsta roll: {role}", + "elo_between": "Elo mellan {min} och {max}", + "elo_min": "Elo {min} eller högre", + "elo_max": "Elo {max} eller lägre", + "elo_yours": "din: {elo}", + "requirement_met": "uppfyllt", + "requirement_failed": "ej uppfyllt", + "ok_chip": "får delta", + "locked_chip": "låst", + "blocked_chip": "får inte delta" } }, "team": { diff --git a/i18n/locales/tr_TR.json b/i18n/locales/tr_TR.json index f402ab58e..267d363b4 100644 --- a/i18n/locales/tr_TR.json +++ b/i18n/locales/tr_TR.json @@ -5967,7 +5967,6 @@ "col_assists": "A", "col_kd": "K/D", "col_hs": "HS%", - "col_maps": "Haritalar", "empty_title": "Henüz istatistik yok", "empty_description": "Turnuva maçları tamamlandıkça sıralama dolar." }, @@ -6152,7 +6151,21 @@ "missed_heading": "Takımın katılım onayını kaçırdı", "missed_hint": "Katılım onayı {time} itibarıyla kapandı ve eşleşme tablosu takımın olmadan oluşturuluyor.", "missed_contact": "Bunun yanlış olduğunu düşünüyorsan bir organizatörle iletişime geç — turnuva başlamadan önce bir takımı istediği an yeniden kabul edebilir.", - "failed": "Katılım onayı başarısız" + "failed": "Katılım onayı başarısız", + "free_agent_opens_at_hint": "Serbest oyuncu havuzundasın. Pencere açıldığında haber vereceğiz.", + "free_agent_heading": "Hâlâ müsait olduğunu onayla", + "free_agent_hint": "{time} itibarıyla onay vermeyen serbest oyuncular yedek listesine alınır ve hiçbir takıma seçilmez.", + "free_agent_done_heading": "Kadrodasın. Takımlar {time} itibarıyla oluşturuluyor.", + "free_agent_done_hint": "{time} itibarıyla onaylandı. Katılım onayı kapandığında bir takıma seçileceksin.", + "free_agent_chip_waitlisted": "yedek listesi", + "free_agent_missed_heading": "Katılım onayını kaçırdın", + "free_agent_missed_hint": "Katılım onayı {time} itibarıyla kapandı ve yedek listesine alındın. Artık ancak bir yer açılırsa bir takıma seçilirsin.", + "free_agent_missed_contact": "Bunun yanlış olduğunu düşünüyorsan bir organizatörle iletişime geç — katılım onayını uzatmak seni yeniden seçim havuzuna alır.", + "a11y_opens_in": "Katılım onayının açılmasına", + "a11y_opens_help": "Katılım onayı henüz açılmadı. Açılana kadar onaylanacak bir şey yok.", + "a11y_closes_in": "Katılım onayının kapanmasına", + "a11y_closes_help": "O ana kadar onay vermeyen hiç kimse eşleşme tablosuna alınmaz.", + "a11y_starts_in": "Turnuvanın başlamasına" }, "check_in_review": { "heading": "{count} takım katılım onayını kaçırdı", @@ -6192,11 +6205,36 @@ "waitlist_explainer": "{name} en yüksek puanlı katılımcı ama hâlâ yedek listesinde — kontenjan dolduktan sonra kaydoldu. Kimin yer alacağına kayıt zamanı karar verir; ELO yalnızca hangi takıma gideceğini belirler. Bu kural düzgün işliyor, hata değil.", "organizer": "Organizatör", "draft_summary": "{agents} serbest oyuncu → {size} kişilik {teams} takım, {waitlisted} yedek listesinde.", - "regenerate": "Takımları yeniden oluştur", + "regenerate": "Takımları oluştur", "regenerated": "{count} takım oluşturuldu", - "regenerate_failed": "Takımlar yeniden oluşturulamadı", + "regenerate_failed": "Takımlar oluşturulamadı", "join_failed": "Serbest oyuncu havuzuna katılınamadı", - "leave_failed": "Serbest oyuncu havuzundan ayrılınamadı" + "leave_failed": "Serbest oyuncu havuzundan ayrılınamadı", + "already_drafted": "Takımlar zaten oluşturuldu. Yeniden oluşturmak bir şey değiştirmez — önce oluşturulan takımları kaldır.", + "draft_locked": "Eşleşme tablosu oyunda olduğu için havuzdan artık takım oluşturulamaz.", + "nothing_drafted": "Hiç takım oluşturulmadı", + "nothing_drafted_hint": "{size} kişilik bir takımı dolduracak kadar serbest oyuncu kalmadı ya da eşleşme tablosunda yer yok." + }, + "entry": { + "title": "Katılım koşulları", + "requirements_hint": "Sadece tıklayan kişide değil, eklenen her oyuncuda kontrol edilir. İçlerinden biri bile koşulu sağlamazsa kayıt reddedilir.", + "locked_title": "Bu turnuva sadece davetle", + "locked_hint": "Kaydı açmak için organizatörün erişim kodunu gir ya da takımını doğrudan davet etmesini iste.", + "passcode_label": "Erişim kodu", + "passcode_placeholder": "Erişim kodunu gir", + "unlock": "Kilidi aç", + "unlocked": "Kayıt açıldı", + "unlock_failed": "Kayıt açılamadı", + "min_role": "Minimum rol: {role}", + "elo_between": "ELO {min} ile {max} arasında", + "elo_min": "ELO {min} veya üzeri", + "elo_max": "ELO {max} veya altı", + "elo_yours": "seninki: {elo}", + "requirement_met": "sağlanıyor", + "requirement_failed": "sağlanmıyor", + "ok_chip": "katılabilir", + "locked_chip": "kilitli", + "blocked_chip": "katılamaz" } }, "team": { diff --git a/i18n/locales/uk_UA.json b/i18n/locales/uk_UA.json index de18f1ce1..e55d8a0fe 100644 --- a/i18n/locales/uk_UA.json +++ b/i18n/locales/uk_UA.json @@ -5967,7 +5967,6 @@ "col_assists": "А", "col_kd": "K/D", "col_hs": "HS%", - "col_maps": "Карти", "empty_title": "Статистики поки немає", "empty_description": "Таблиця лідерів заповнюється в міру завершення матчів турніру." }, @@ -6152,7 +6151,21 @@ "missed_heading": "Твоя команда пропустила підтвердження участі", "missed_hint": "Підтвердження закрилося о {time}, і сітка складається без твоєї команди.", "missed_contact": "Звернися до організатора, якщо вважаєш це помилкою — він може прийняти команду знову будь-коли до старту турніру.", - "failed": "Не вдалося підтвердити участь" + "failed": "Не вдалося підтвердити участь", + "free_agent_opens_at_hint": "Ти в пулі вільних агентів. Повідомимо, коли відкриється вікно.", + "free_agent_heading": "Підтверди, що ти досі вільний", + "free_agent_hint": "Вільні агенти, які не підтвердили участь до {time}, потрапляють у список очікування і не йдуть у команди на драфті.", + "free_agent_done_heading": "Ти в турнірі. Команди складають драфтом о {time}.", + "free_agent_done_hint": "Підтверджено о {time}. Тебе візьмуть у команду, коли підтвердження участі закриється.", + "free_agent_chip_waitlisted": "список очікування", + "free_agent_missed_heading": "Ти пропустив підтвердження участі", + "free_agent_missed_hint": "Підтвердження закрилося о {time}, і ти потрапив у список очікування. Тепер тебе візьмуть у команду, лише якщо звільниться місце.", + "free_agent_missed_contact": "Звернися до організатора, якщо вважаєш це помилкою — продовження підтвердження поверне тебе в пул драфту.", + "a11y_opens_in": "Підтвердження участі відкриється через", + "a11y_opens_help": "Підтвердження участі ще не відкрите. До того моменту підтверджувати нічого.", + "a11y_closes_in": "Підтвердження участі закриється через", + "a11y_closes_help": "Усі, хто не підтвердить участь до того часу, у сітку не потраплять.", + "a11y_starts_in": "Турнір почнеться через" }, "check_in_review": { "heading": "Команд пропустило підтвердження участі: {count}", @@ -6192,11 +6205,36 @@ "waitlist_explainer": "{name} — учасник із найвищим рейтингом, але досі в списку очікування: він записався, коли місця вже закінчилися. Місце вирішує час запису, а ELO вирішує лише команду. Це правило працює як слід, а не баг.", "organizer": "Організатор", "draft_summary": "{agents} вільних агентів → {teams} команд по {size}, {waitlisted} у списку очікування.", - "regenerate": "Перескласти команди", + "regenerate": "Провести драфт", "regenerated": "Складено команд: {count}", - "regenerate_failed": "Не вдалося перескласти команди", + "regenerate_failed": "Не вдалося провести драфт", "join_failed": "Не вдалося увійти в пул вільних агентів", - "leave_failed": "Не вдалося покинути пул вільних агентів" + "leave_failed": "Не вдалося покинути пул вільних агентів", + "already_drafted": "Команди вже складено драфтом. Повторний драфт нічого не змінить — спершу видали складені команди.", + "draft_locked": "Сітка вже в грі, тож пул більше не можна розібрати драфтом.", + "nothing_drafted": "Жодної команди не складено", + "nothing_drafted_hint": "Вільних агентів не вистачає, щоб скласти команду з {size}, або в сітці немає місця." + }, + "entry": { + "title": "Вимоги до участі", + "requirements_hint": "Перевіряється у кожного гравця, якого додають, а не лише в того, хто натискає. Реєстрацію буде відхилено, якщо хоча б один не проходить.", + "locked_title": "Цей турнір лише за запрошенням", + "locked_hint": "Введи код доступу від організатора, щоб відкрити реєстрацію, або попроси його запросити твою команду напряму.", + "passcode_label": "Код доступу", + "passcode_placeholder": "Введи код доступу", + "unlock": "Відкрити", + "unlocked": "Реєстрацію відкрито", + "unlock_failed": "Не вдалося відкрити реєстрацію", + "min_role": "Мінімальна роль: {role}", + "elo_between": "ELO від {min} до {max}", + "elo_min": "ELO {min} або вище", + "elo_max": "ELO {max} або нижче", + "elo_yours": "у тебе: {elo}", + "requirement_met": "виконано", + "requirement_failed": "не виконано", + "ok_chip": "допущений", + "locked_chip": "закрито", + "blocked_chip": "не допущений" } }, "team": { diff --git a/i18n/locales/zh_Hans.json b/i18n/locales/zh_Hans.json index 1388e6173..c0e84fb8f 100644 --- a/i18n/locales/zh_Hans.json +++ b/i18n/locales/zh_Hans.json @@ -5967,7 +5967,6 @@ "col_assists": "A", "col_kd": "K/D", "col_hs": "爆头率", - "col_maps": "地图", "empty_title": "暂无数据", "empty_description": "随着赛事比赛陆续结束,排行榜会逐步填充。" }, @@ -6152,7 +6151,21 @@ "missed_heading": "你的队伍错过了签到", "missed_hint": "签到已于 {time} 关闭,对阵表将不包含你的队伍。", "missed_contact": "如果你认为这有误,请联系主办方——在赛事正式开始前,他们随时可以重新接纳队伍。", - "failed": "签到失败" + "failed": "签到失败", + "free_agent_opens_at_hint": "你已在自由选手池中。窗口开启时我们会通知你。", + "free_agent_heading": "确认你仍然有空", + "free_agent_hint": "在 {time} 前未确认的自由选手会被移入候补,不会被选入任何队伍。", + "free_agent_done_heading": "你已入围。队伍将于 {time} 选人组建。", + "free_agent_done_hint": "已于 {time} 确认。签到关闭后你会被选入一支队伍。", + "free_agent_chip_waitlisted": "候补", + "free_agent_missed_heading": "你错过了签到", + "free_agent_missed_hint": "签到已于 {time} 关闭,你被移入候补。现在只有出现空位时才会被选入队伍。", + "free_agent_missed_contact": "如果你认为这有误,请联系主办方——延长签到会把你放回选人池。", + "a11y_opens_in": "距签到开启", + "a11y_opens_help": "签到尚未开启。开启之前没有需要确认的内容。", + "a11y_closes_in": "距签到关闭", + "a11y_closes_help": "届时仍未签到的一律不会进入对阵表。", + "a11y_starts_in": "距赛事开始" }, "check_in_review": { "heading": "{count} 支队伍错过了签到", @@ -6192,11 +6205,36 @@ "waitlist_explainer": "{name} 是评分最高的参赛者,却仍在候补——因为报名时名额已满。名额由报名先后决定,ELO 只决定进哪支队伍。这是规则在正常生效,不是 bug。", "organizer": "主办方", "draft_summary": "{agents} 名自由选手 → {teams} 支 {size} 人队伍,{waitlisted} 人候补。", - "regenerate": "重新分队", + "regenerate": "选人分队", "regenerated": "已组成 {count} 支队伍", - "regenerate_failed": "重新分队失败", + "regenerate_failed": "分队失败", "join_failed": "加入自由选手池失败", - "leave_failed": "退出自由选手池失败" + "leave_failed": "退出自由选手池失败", + "already_drafted": "队伍已经分好了。再分一次不会有任何变化——请先移除已组成的队伍。", + "draft_locked": "对阵表已经开打,选手池无法再分队。", + "nothing_drafted": "没有组成任何队伍", + "nothing_drafted_hint": "剩下的自由选手不足以凑满一支 {size} 人的队伍,或者对阵表已没有空位。" + }, + "entry": { + "title": "参赛条件", + "requirements_hint": "对每一位被添加的选手进行校验,而不只是看点击的人。只要有一人不符合,报名就会被拒绝。", + "locked_title": "本赛事仅限邀请", + "locked_hint": "输入主办方的报名码以解锁报名,或者请他们直接邀请你的队伍。", + "passcode_label": "报名码", + "passcode_placeholder": "请输入报名码", + "unlock": "解锁", + "unlocked": "报名已解锁", + "unlock_failed": "无法解锁报名", + "min_role": "最低角色:{role}", + "elo_between": "ELO 介于 {min} 与 {max} 之间", + "elo_min": "ELO {min} 及以上", + "elo_max": "ELO {max} 及以下", + "elo_yours": "你的:{elo}", + "requirement_met": "已满足", + "requirement_failed": "未满足", + "ok_chip": "可参赛", + "locked_chip": "已锁定", + "blocked_chip": "不可参赛" } }, "team": { diff --git a/i18n/locales/zh_Hant.json b/i18n/locales/zh_Hant.json index 823031d94..3295e347c 100644 --- a/i18n/locales/zh_Hant.json +++ b/i18n/locales/zh_Hant.json @@ -5967,7 +5967,6 @@ "col_assists": "A", "col_kd": "K/D", "col_hs": "HS%", - "col_maps": "地圖", "empty_title": "尚無數據", "empty_description": "隨著賽事比賽陸續結束,排行榜會逐步填入。" }, @@ -6152,7 +6151,21 @@ "missed_heading": "你的隊伍錯過了報到", "missed_hint": "報到已於 {time} 關閉,對戰表將不包含你的隊伍。", "missed_contact": "如果你認為這有誤,請聯絡主辦方——在賽事正式開始前,他們隨時可以重新接納隊伍。", - "failed": "報到失敗" + "failed": "報到失敗", + "free_agent_opens_at_hint": "你已在自由選手池中。時段開啟時我們會通知你。", + "free_agent_heading": "確認你仍然有空", + "free_agent_hint": "在 {time} 前未確認的自由選手會被移入候補,不會被選入任何隊伍。", + "free_agent_done_heading": "你已入圍。隊伍將於 {time} 選人組成。", + "free_agent_done_hint": "已於 {time} 確認。報到關閉後你會被選入一支隊伍。", + "free_agent_chip_waitlisted": "候補", + "free_agent_missed_heading": "你錯過了報到", + "free_agent_missed_hint": "報到已於 {time} 關閉,你被移入候補。現在只有出現空位時才會被選入隊伍。", + "free_agent_missed_contact": "如果你認為這有誤,請聯絡主辦方——延長報到會把你放回選人池。", + "a11y_opens_in": "距報到開啟", + "a11y_opens_help": "報到尚未開啟。開啟之前沒有需要確認的內容。", + "a11y_closes_in": "距報到關閉", + "a11y_closes_help": "屆時仍未報到的一律不會進入對戰表。", + "a11y_starts_in": "距賽事開始" }, "check_in_review": { "heading": "{count} 支隊伍錯過了報到", @@ -6192,11 +6205,36 @@ "waitlist_explainer": "{name} 是評分最高的參賽者,卻仍在候補——因為報名時名額已滿。名額由報名先後決定,ELO 只決定進哪支隊伍。這是規則正常運作,不是 bug。", "organizer": "主辦方", "draft_summary": "{agents} 名自由選手 → {teams} 支 {size} 人隊伍,{waitlisted} 人候補。", - "regenerate": "重新分隊", + "regenerate": "選人分隊", "regenerated": "已組成 {count} 支隊伍", - "regenerate_failed": "重新分隊失敗", + "regenerate_failed": "分隊失敗", "join_failed": "加入自由選手池失敗", - "leave_failed": "退出自由選手池失敗" + "leave_failed": "退出自由選手池失敗", + "already_drafted": "隊伍已經分好了。再分一次不會有任何變化——請先移除已組成的隊伍。", + "draft_locked": "對戰表已經開打,選手池無法再分隊。", + "nothing_drafted": "沒有組成任何隊伍", + "nothing_drafted_hint": "剩下的自由選手不足以湊滿一支 {size} 人的隊伍,或者對戰表已沒有空位。" + }, + "entry": { + "title": "參賽條件", + "requirements_hint": "對每一位被加入的選手進行檢查,而不只是看點擊的人。只要有一人不符合,報名就會被拒絕。", + "locked_title": "本賽事僅限邀請", + "locked_hint": "輸入主辦方的報名碼以解鎖報名,或者請他們直接邀請你的隊伍。", + "passcode_label": "報名碼", + "passcode_placeholder": "請輸入報名碼", + "unlock": "解鎖", + "unlocked": "報名已解鎖", + "unlock_failed": "無法解鎖報名", + "min_role": "最低角色:{role}", + "elo_between": "ELO 介於 {min} 與 {max} 之間", + "elo_min": "ELO {min} 以上", + "elo_max": "ELO {max} 以下", + "elo_yours": "你的:{elo}", + "requirement_met": "已符合", + "requirement_failed": "未符合", + "ok_chip": "可參賽", + "locked_chip": "已鎖定", + "blocked_chip": "不可參賽" } }, "team": { From 6f2205f5f783d241b7135d207abafe7945449a37 Mon Sep 17 00:00:00 2001 From: Luke Policinski Date: Sun, 30 Aug 2026 10:19:25 -0400 Subject: [PATCH 05/23] feature: sanctions settings area and tournament registration invites --- components/TeamInviteNotification.vue | 65 ++++- components/hub/NotificationsPanel.vue | 29 ++ components/matchmaking/Matchmaking.vue | 31 +++ components/notification/ActionToasts.vue | 21 ++ components/settings/SanctionLadderEditor.vue | 211 +++++++++++++++ .../settings/SanctionSourceSettings.vue | 251 ++++++++++++++++++ components/tournament/TournamentDetail.vue | 16 +- components/tournament/TournamentInvites.vue | 201 ++++++++++++++ composables/useSettingsNav.ts | 5 + graphql/meGraphql.ts | 19 ++ graphql/sanctions.ts | 32 +++ graphql/tournamentInvites.ts | 114 ++++++++ i18n/locales/en.json | 97 ++++++- layouts/components/AppNotifications.vue | 19 ++ pages/settings/application/sanctions.vue | 238 +++++++++++++++++ stores/NotificationStore.ts | 31 +++ utilities/sanctions.ts | 236 ++++++++++++++++ 17 files changed, 1602 insertions(+), 14 deletions(-) create mode 100644 components/settings/SanctionLadderEditor.vue create mode 100644 components/settings/SanctionSourceSettings.vue create mode 100644 components/tournament/TournamentInvites.vue create mode 100644 graphql/sanctions.ts create mode 100644 graphql/tournamentInvites.ts create mode 100644 pages/settings/application/sanctions.vue create mode 100644 utilities/sanctions.ts diff --git a/components/TeamInviteNotification.vue b/components/TeamInviteNotification.vue index 6fefb0b5f..1dc8d60f5 100644 --- a/components/TeamInviteNotification.vue +++ b/components/TeamInviteNotification.vue @@ -4,16 +4,23 @@ import TimeAgo from "./TimeAgo.vue";