From 1e72c2c1ae06dbf4ef0d577e0314ba25dd32072e Mon Sep 17 00:00:00 2001 From: Luke Policinski Date: Thu, 1 Oct 2026 12:54:51 -0400 Subject: [PATCH] feature: live community roster, recent players, community tab and public server leaderboards --- components/SanctionStatusBadge.vue | 46 +- components/community/CommunityIp.vue | 57 ++ components/community/CommunityPlayerCell.vue | 119 +++ components/community/CommunityPlayerMenu.vue | 83 +++ components/community/ServerActivityChart.vue | 347 +++++++++ components/community/ServerLeaderboard.vue | 468 ++++++++++++ components/player/PlayerCommunityHistory.vue | 681 ++++++++++++++++++ components/servers/ServerPlayerManagement.vue | 526 ++++++++++++-- components/servers/ServerRecentPlayers.vue | 599 +++++++++++++++ composables/useCommunityFormat.ts | 145 ++++ graphql/communityGraphql.ts | 295 ++++++++ i18n/locales/ar_SA.json | 133 +++- i18n/locales/da_DK.json | 133 +++- i18n/locales/de_DE.json | 133 +++- i18n/locales/en.json | 133 +++- i18n/locales/es_ES.json | 133 +++- i18n/locales/fr_FR.json | 133 +++- i18n/locales/it_IT.json | 133 +++- i18n/locales/ja_JP.json | 133 +++- i18n/locales/ko_KR.json | 133 +++- i18n/locales/pl_PL.json | 133 +++- i18n/locales/pt_BR.json | 133 +++- i18n/locales/ru_RU.json | 133 +++- i18n/locales/sv_SE.json | 133 +++- i18n/locales/tr_TR.json | 133 +++- i18n/locales/uk_UA.json | 133 +++- i18n/locales/zh_Hans.json | 133 +++- i18n/locales/zh_Hant.json | 133 +++- middleware/auth.global.ts | 4 + pages/dedicated-servers/[id].vue | 20 + pages/players/[id].vue | 30 + pages/public-servers/[id].vue | 355 +++++++++ .../index.vue} | 27 +- pages/settings/application/servers.vue | 76 +- .../player/PlayerCommunityHistory.spec.ts | 219 ++++++ .../servers/ServerPlayerManagement.spec.ts | 359 +++++++++ .../servers/ServerRecentPlayers.spec.ts | 312 ++++++++ tests/pages/public-servers.spec.ts | 15 +- tests/pages/public-servers/detail.spec.ts | 256 +++++++ tests/utilities/communityStats.spec.ts | 84 +++ tests/utilities/debounce.spec.ts | 55 ++ utilities/communityStats.ts | 174 +++++ utilities/debounce.ts | 46 +- 43 files changed, 7519 insertions(+), 140 deletions(-) create mode 100644 components/community/CommunityIp.vue create mode 100644 components/community/CommunityPlayerCell.vue create mode 100644 components/community/CommunityPlayerMenu.vue create mode 100644 components/community/ServerActivityChart.vue create mode 100644 components/community/ServerLeaderboard.vue create mode 100644 components/player/PlayerCommunityHistory.vue create mode 100644 components/servers/ServerRecentPlayers.vue create mode 100644 composables/useCommunityFormat.ts create mode 100644 graphql/communityGraphql.ts create mode 100644 pages/public-servers/[id].vue rename pages/{public-servers.vue => public-servers/index.vue} (94%) create mode 100644 tests/components/player/PlayerCommunityHistory.spec.ts create mode 100644 tests/components/servers/ServerRecentPlayers.spec.ts create mode 100644 tests/pages/public-servers/detail.spec.ts create mode 100644 tests/utilities/communityStats.spec.ts create mode 100644 tests/utilities/debounce.spec.ts create mode 100644 utilities/communityStats.ts diff --git a/components/SanctionStatusBadge.vue b/components/SanctionStatusBadge.vue index 894939a75..017ede93a 100644 --- a/components/SanctionStatusBadge.vue +++ b/components/SanctionStatusBadge.vue @@ -3,31 +3,46 @@ import { computed } from "vue"; import { useI18n } from "vue-i18n"; import FiveStackToolTip from "~/components/FiveStackToolTip.vue"; +type SanctionType = "ban" | "mute" | "gag" | "silence"; + +// `active` is every sanction in force: the pill still shows only `type` (the +// worst), so a row keeps one geometry however many apply, and counts the rest. const props = withDefaults( defineProps<{ - type: "ban" | "mute" | "gag" | "silence"; + type: SanctionType; + active?: SanctionType[]; variant?: "inline" | "overlay"; }>(), { + active: undefined, variant: "inline", }, ); const { t } = useI18n(); +const LABEL_KEYS: Record = { + ban: "banned", + mute: "muted", + gag: "gagged", + silence: "silenced", +}; + const tone = computed<"ban" | "warn">(() => props.type === "ban" ? "ban" : "warn", ); -const label = computed(() => { - const key = { - ban: "banned", - mute: "muted", - gag: "gagged", - silence: "silenced", - }[props.type]; - return t(`player.sanctions.${key}`); -}); +const label = computed(() => t(`player.sanctions.${LABEL_KEYS[props.type]}`)); + +const extra = computed(() => + (props.active ?? []).filter((kind) => kind !== props.type), +); + +const activeList = computed(() => + [props.type, ...extra.value] + .map((kind) => t(`player.sanctions.${LABEL_KEYS[kind]}`)) + .join(", "), +); diff --git a/components/community/CommunityIp.vue b/components/community/CommunityIp.vue new file mode 100644 index 000000000..6118506d9 --- /dev/null +++ b/components/community/CommunityIp.vue @@ -0,0 +1,57 @@ + + + diff --git a/components/community/CommunityPlayerCell.vue b/components/community/CommunityPlayerCell.vue new file mode 100644 index 000000000..1369f381e --- /dev/null +++ b/components/community/CommunityPlayerCell.vue @@ -0,0 +1,119 @@ + + + diff --git a/components/community/CommunityPlayerMenu.vue b/components/community/CommunityPlayerMenu.vue new file mode 100644 index 000000000..b171e2a7f --- /dev/null +++ b/components/community/CommunityPlayerMenu.vue @@ -0,0 +1,83 @@ + + + diff --git a/components/community/ServerActivityChart.vue b/components/community/ServerActivityChart.vue new file mode 100644 index 000000000..c56304026 --- /dev/null +++ b/components/community/ServerActivityChart.vue @@ -0,0 +1,347 @@ + + + + + diff --git a/components/community/ServerLeaderboard.vue b/components/community/ServerLeaderboard.vue new file mode 100644 index 000000000..a1420c29d --- /dev/null +++ b/components/community/ServerLeaderboard.vue @@ -0,0 +1,468 @@ + + + + + diff --git a/components/player/PlayerCommunityHistory.vue b/components/player/PlayerCommunityHistory.vue new file mode 100644 index 000000000..3dccd3832 --- /dev/null +++ b/components/player/PlayerCommunityHistory.vue @@ -0,0 +1,681 @@ + + + diff --git a/components/servers/ServerPlayerManagement.vue b/components/servers/ServerPlayerManagement.vue index a776dd2e9..0f20577c7 100644 --- a/components/servers/ServerPlayerManagement.vue +++ b/components/servers/ServerPlayerManagement.vue @@ -1,5 +1,5 @@ @@ -367,7 +595,7 @@ onBeforeUnmount(() => {

{{ $t("pages.dedicated_servers.detail.player_management") }}

- {{ players.length }} + {{ rows.length }}
{
- + + + {{ $t("community.roster.live_description") }} + + {{ $t("common.refresh") }} @@ -582,42 +834,164 @@ onBeforeUnmount(() => {
- {{ $t("pages.dedicated_servers.detail.no_players") }} + + + {{ + pluginActive + ? $t("community.roster.not_reporting") + : $t("community.roster.not_detected") + }} +
-
-
+ - -
- - - + +
+ + {{ $t("community.roster.connected") }} + + + + {{ $t("community.roster.not_available") }} + +
+
+ + {{ $t("community.ip.label") }} + + + + {{ $t("community.roster.not_available") }} + +
+
+ + + +
-
+ + +
+ {{ $t("pages.dedicated_servers.detail.no_players") }} +
+
+ + + + diff --git a/components/servers/ServerRecentPlayers.vue b/components/servers/ServerRecentPlayers.vue new file mode 100644 index 000000000..257ce5504 --- /dev/null +++ b/components/servers/ServerRecentPlayers.vue @@ -0,0 +1,599 @@ + + + diff --git a/composables/useCommunityFormat.ts b/composables/useCommunityFormat.ts new file mode 100644 index 000000000..c9660580b --- /dev/null +++ b/composables/useCommunityFormat.ts @@ -0,0 +1,145 @@ +import { useI18n } from "vue-i18n"; +import { dateLocale } from "~/utilities/dateLocale"; +import { durationParts } from "~/utilities/communityStats"; + +const MINUTE = 60 * 1000; +const HOUR = 60 * MINUTE; +const DAY = 24 * HOUR; + +export function useCommunityFormat() { + const { t } = useI18n(); + + function played(totalSeconds: number | null | undefined) { + const { hours, minutes, seconds } = durationParts(totalSeconds ?? 0); + + if (hours > 0) { + return t("community.format.hours_minutes", { + hours: hours.toLocaleString(dateLocale()), + minutes: String(minutes).padStart(2, "0"), + }); + } + + if (minutes > 0) { + return t("community.format.minutes", { minutes }); + } + + return t("community.format.seconds", { seconds }); + } + + function hours(totalSeconds: number | null | undefined) { + const value = (totalSeconds ?? 0) / 3600; + + if (value > 0 && value < 1) { + return t("community.format.minutes", { + minutes: Math.max(1, Math.round(value * 60)), + }); + } + + return t("community.format.hours", { + hours: value.toLocaleString(dateLocale(), { + maximumFractionDigits: value < 10 ? 1 : 0, + }), + }); + } + + function count(value: number | null | undefined) { + return (value ?? 0).toLocaleString(dateLocale()); + } + + // A coarse time is one the api rounded down to the hour, so anything under an + // hour old reads as "within the last hour" rather than a fake minute count. + function ago( + iso: string | null | undefined, + options: { coarse?: boolean; now?: number } = {}, + ) { + if (!iso) { + return ""; + } + + const now = options.now ?? Date.now(); + const elapsed = Math.max(0, now - Date.parse(iso)); + const rtf = new Intl.RelativeTimeFormat(dateLocale(), { + numeric: "auto", + style: "short", + }); + + if (elapsed < HOUR && options.coarse) { + return t("community.format.within_hour"); + } + + if (elapsed < MINUTE) { + return t("community.format.just_now"); + } + + if (elapsed < HOUR) { + return rtf.format(-Math.floor(elapsed / MINUTE), "minute"); + } + + if (elapsed < DAY) { + return rtf.format(-Math.floor(elapsed / HOUR), "hour"); + } + + const startOfDay = (value: Date) => + new Date( + value.getFullYear(), + value.getMonth(), + value.getDate(), + ).getTime(); + const days = Math.round( + (startOfDay(new Date(now)) - startOfDay(new Date(iso))) / DAY, + ); + + if (days < 14) { + return rtf.format(-Math.max(1, days), "day"); + } + + if (days < 60) { + return rtf.format(-Math.floor(days / 7), "week"); + } + + if (days < 365) { + return rtf.format(-Math.floor(days / 30), "month"); + } + + return rtf.format(-Math.floor(days / 365), "year"); + } + + function exact(iso: string | null | undefined) { + if (!iso) { + return ""; + } + + return new Date(iso).toLocaleString(dateLocale(), { + weekday: "short", + day: "numeric", + month: "short", + hour: "2-digit", + minute: "2-digit", + }); + } + + function day(iso: string | Date) { + return new Date(iso).toLocaleDateString(dateLocale(), { + weekday: "short", + day: "numeric", + month: "short", + }); + } + + function time(iso: string | Date) { + return new Date(iso).toLocaleTimeString(dateLocale(), { + hour: "2-digit", + minute: "2-digit", + }); + } + + function date(iso: string | Date) { + return new Date(iso).toLocaleDateString(dateLocale(), { + day: "numeric", + month: "short", + year: "numeric", + }); + } + + return { played, hours, count, ago, exact, day, time, date }; +} diff --git a/graphql/communityGraphql.ts b/graphql/communityGraphql.ts new file mode 100644 index 000000000..691413031 --- /dev/null +++ b/graphql/communityGraphql.ts @@ -0,0 +1,295 @@ +import gql from "graphql-tag"; + +// Raw documents: the community session tables, view and actions are newer +// than the generated zeus client. Send them with COMMUNITY_OPTIONAL so web +// shipping ahead of the api's metadata degrades to the old behaviour instead +// of toasting an error the viewer cannot act on. +export const COMMUNITY_OPTIONAL = { optional: true }; + +// Only what a roster row renders: the live roster re-runs about once a second +// per watched server, so every extra field here is paid for continuously. +const ROW_PLAYER_FIELDS = ` + steam_id + name + avatar_url + is_registered + is_banned + is_muted + is_gagged + vac_banned + vac_ban_count + game_ban_count + days_since_last_ban +`; + +const TOTALS_FIELDS = ` + sessions + seconds + kills + deaths + servers + rank +`; + +const LEADERBOARD_ENTRY_FIELDS = ` + rank + steam_id + name + avatar_url + country + seconds + kills + deaths + sessions +`; + +export const SERVER_ROSTER_SUBSCRIPTION = gql` + subscription ServerRoster($serverId: uuid!) { + server_rosters_by_pk(server_id: $serverId) { + server_id + reported_at + sessions( + where: { ended_at: { _is_null: true } } + order_by: { started_at: asc } + ) { + id + player_steam_id + name + ip + kills + deaths + started_at + player { + ${ROW_PLAYER_FIELDS} + } + } + } + } +`; + +export const SERVER_RECENT_PLAYERS_QUERY = gql` + query ServerRecentPlayers( + $where: server_recent_players_bool_exp! + $limit: Int! + $offset: Int! + ) { + server_recent_players( + where: $where + order_by: [ + { online: desc } + { last_seen_at: desc } + { player_steam_id: asc } + ] + limit: $limit + offset: $offset + ) { + server_id + player_steam_id + name + ip + sessions + seconds_played + kills + first_seen_at + last_seen_at + online + player { + ${ROW_PLAYER_FIELDS} + } + } + server_recent_players_aggregate(where: $where) { + aggregate { + count + } + } + } +`; + +export const SERVER_RECENT_TOTALS_QUERY = gql` + query ServerRecentPlayerTotals( + $serverId: uuid! + $day: timestamptz! + $week: timestamptz! + ) { + day: server_recent_players_aggregate( + where: { + server_id: { _eq: $serverId } + _or: [{ online: { _eq: true } }, { last_seen_at: { _gte: $day } }] + } + ) { + aggregate { + count + } + } + week: server_recent_players_aggregate( + where: { + server_id: { _eq: $serverId } + _or: [{ online: { _eq: true } }, { last_seen_at: { _gte: $week } }] + } + ) { + aggregate { + count + sum { + sessions + seconds_played + } + } + } + } +`; + +export const SERVER_SESSIONS_ON_IPS_QUERY = gql` + query ServerSessionsOnIps( + $serverId: uuid! + $ips: [inet!]! + $week: timestamptz! + ) { + server_player_sessions( + where: { + server_id: { _eq: $serverId } + ip: { _in: $ips } + _or: [{ ended_at: { _is_null: true } }, { ended_at: { _gte: $week } }] + } + distinct_on: [ip, player_steam_id] + order_by: [ + { ip: asc } + { player_steam_id: asc } + { started_at: desc } + ] + ) { + ip + player_steam_id + name + player { + steam_id + name + is_registered + } + } + } +`; + +export const PLAYER_COMMUNITY_STATS_QUERY = gql` + query PlayerCommunityStats($steamId: bigint!) { + getPlayerCommunityStats(steam_id: $steamId) { + week { + ${TOTALS_FIELDS} + } + all_time { + ${TOTALS_FIELDS} + } + last_seen_at + online_server_id + online_server_label + is_moderator_view + servers { + server_id + label + region + type + online + last_seen_at + week { + ${TOTALS_FIELDS} + } + all_time { + ${TOTALS_FIELDS} + } + names + sessions { + started_at + ended_at + ip + } + } + ips { + ip + sessions + } + ip_matches { + steam_id + name + avatar_url + has_account + is_banned + sessions + last_seen_at + online + ip + } + } + } +`; + +export const PUBLIC_SERVER_SUBSCRIPTION = gql` + subscription PublicServer($serverId: uuid!) { + servers_by_pk(id: $serverId) { + id + label + type + game + region + connected + connection_link + connection_string + max_players + game_mode { + slug + name + } + } + } +`; + +export const DEDICATED_SERVER_INFO_QUERY = gql` + query PublicServerInfo { + getDedicatedServerInfo { + id + map + players + lastPing + } + } +`; + +export const SERVER_COMMUNITY_STATS_QUERY = gql` + query ServerCommunityStats($serverId: uuid!) { + getServerCommunityStats(server_id: $serverId) { + server_id + online + max_players + week_players + week_seconds + all_time_players + tracked_since + hourly { + hour + seconds + players + } + } + } +`; + +export const SERVER_LEADERBOARD_QUERY = gql` + query ServerLeaderboard( + $serverId: uuid! + $period: String! + $metric: String! + $limit: Int + ) { + getServerLeaderboard( + server_id: $serverId + period: $period + metric: $metric + limit: $limit + ) { + entries { + ${LEADERBOARD_ENTRY_FIELDS} + } + you { + ${LEADERBOARD_ENTRY_FIELDS} + } + } + } +`; diff --git a/i18n/locales/ar_SA.json b/i18n/locales/ar_SA.json index 987f6d030..44c99b61d 100644 --- a/i18n/locales/ar_SA.json +++ b/i18n/locales/ar_SA.json @@ -2274,7 +2274,11 @@ "reserved_disk_space_fresh_gb_description": "الحد الأدنى لمساحة القرص بالـ GB المطلوبة لتجهيز عقدة جديدة (CS2 غير مثبت بعد). اضبطها على 0 للتعطيل.", "reserved_disk_space_existing_gb": "المساحة المطلوبة - تثبيت موجود (GB)", "reserved_disk_space_existing_gb_description": "الحد الأدنى لمساحة القرص بالـ GB المطلوبة عندما يكون CS2 مثبتًا بالفعل على العقدة. اضبطها على 0 للتعطيل.", - "update_cpu_pinning": "تم تحديث تثبيت CPU" + "update_cpu_pinning": "تم تحديث تثبيت CPU", + "player_sessions_section": "جلسات لاعبي سيرفرات المجتمع", + "player_sessions_description": "تسجّل سيرفرات المجتمع التي تشغّل إضافة Player Management من لعب ومتى ومن أي عنوان IP.", + "player_session_retention_days": "الاحتفاظ بسجل الجلسات (أيام)", + "player_session_retention_days_description": "مدة الاحتفاظ بالجلسات الدقيقة وعناوين IP، بين {min} و{max} يومًا. تُضم الجلسات الأقدم إلى المجاميع الكلية." }, "highlights": { "title": "أبرز اللقطات", @@ -3147,7 +3151,8 @@ "elo": "ELO", "maps": "الخرائط", "arsenal": "الترسانة", - "combat": "القتال" + "combat": "القتال", + "community": "المجتمع" }, "roles": { "no_data": "لا توجد مباريات حديثة كافية لتحديد الدور.", @@ -8254,6 +8259,7 @@ "steam_game_bans": "حظر اللعبة", "permanent": "دائم", "active": "نشطة", + "active_list": "النشطة: {list}", "expired": "منتهية", "on_record": "في السجل", "cannot_edit_expired": "لا يمكن تعديل العقوبات المنتهية — أضف عقوبة جديدة", @@ -10425,5 +10431,128 @@ "idle": "غير مُعدّ", "check_again": "تحقّق مجددًا", "setup_guide": "دليل الإعداد" + }, + "community": { + "this_week": "هذا الأسبوع", + "all_time": "كل الأوقات", + "online": "متصل", + "online_now": "متصل الآن", + "now": "الآن", + "last_seen": "آخر ظهور", + "sessions": "الجلسات", + "time_played": "وقت اللعب", + "time": "الوقت", + "kills": "القتلات", + "rank": "الترتيب", + "server": "السيرفر", + "actions": "الإجراءات", + "format": { + "hours_minutes": "{hours} س {minutes} د", + "minutes": "{minutes} د", + "seconds": "{seconds} ث", + "hours": "{hours} س", + "just_now": "الآن", + "within_hour": "خلال الساعة الأخيرة" + }, + "player": { + "steam_only": "Steam فقط", + "in_game": "داخل اللعبة «{name}»", + "more": "المزيد عن {name}", + "sanction_history": "سجل العقوبات" + }, + "ip": { + "label": "IP", + "shared_now": "موجودون أيضًا على هذا السيرفر الآن: {names}", + "shared_week": "نفس عنوان IP خلال آخر 7 أيام: {names}" + }, + "roster": { + "live": "مباشر", + "live_description": "يُبلغ عنه إضافة Player Management. يظهر الدخول والخروج خلال ثوانٍ.", + "connected": "متصل منذ", + "not_available": "غير متاح", + "not_detected": "لم يتم اكتشاف إضافة Player Management. تُقرأ قائمة اللاعبين عبر RCON كل 30 ثانية، ولا يُحفظ أي سجل.", + "not_reporting": "لا تُبلغ إضافة Player Management عن اللاعبين بعد، لذا تُقرأ القائمة عبر RCON كل 30 ثانية ولا يُحفظ أي سجل." + }, + "recent": { + "title": "اللاعبون الأخيرون", + "retention": "يُحتفظ بالسجل {days} يومًا", + "unique_24h": "فريدون · 24 س", + "unique_7d": "فريدون · 7 أيام", + "sessions_7d": "الجلسات · 7 أيام", + "time_played_7d": "وقت اللعب · 7 أيام", + "avg_session": "متوسط الجلسة", + "players": "لاعبين", + "joins": "مرات دخول", + "per_visit": "لكل زيارة", + "search": "ابحث بالاسم أو معرّف Steam أو IP", + "window_24h": "24 س", + "window_7d": "7 أيام", + "player": "اللاعب", + "empty": "لم يلعب أحد هنا خلال هذه الفترة.", + "no_matches": "لا يوجد لاعبون يطابقون هذا البحث.", + "showing": "عرض {shown} من {total}", + "load_more": "تحميل المزيد" + }, + "player_tab": { + "moderator_note": "يرى مشرفو المحتوى أيضًا الأوقات الدقيقة والجلسات والأسماء المستخدمة وعناوين IP.", + "public_note": "الوقت والقتلات على سيرفرات المجتمع. يُعرض آخر ظهور فقط، وليس أوقات اللعب أبدًا.", + "avg_session": "بمتوسط {duration} للجلسة", + "kd": "{ratio} K/D", + "servers": "السيرفرات", + "community_servers": "سيرفرات مجتمع", + "servers_seen_on": "السيرفرات التي ظهر عليها", + "names_used": "الأسماء المستخدمة", + "show_sessions": "عرض الجلسات على {server}", + "sessions_last_7_days": "الجلسات · آخر 7 أيام", + "moderators_only": "لمشرفي المحتوى فقط", + "no_recent_sessions": "لا توجد جلسات خلال آخر 7 أيام.", + "no_servers_this_week": "لا يوجد وقت على سيرفرات المجتمع هذا الأسبوع.", + "also_on_ips": "ظهروا أيضًا على عناوين IP الخاصة به", + "ip_matches_title": "حسابات أخرى ظهرت على عناوين IP هذا اللاعب خلال آخر 7 أيام", + "ips_used": "عناوين IP التي استخدمها هذا اللاعب:", + "ip_sessions": "{count} جلسة | {count} جلسات", + "empty_title": "لا توجد جلسات في المجتمع", + "empty_description": "لم يظهر هذا اللاعب على أي سيرفر مجتمع بعد." + }, + "leaderboard": { + "title": "لوحة الصدارة", + "by_time": "الوقت", + "by_kills": "القتلات", + "you": "أنت", + "empty_week": "لم يلعب هنا هذا الأسبوع أحد يملك حساب 5stack.", + "empty_all": "لم يلعب هنا بعد أحد يملك حساب 5stack.", + "footnote": "تعرض اللاعبين الذين لديهم حساب 5stack. اللاعبون بدون حساب يُحتسبون رغم ذلك في المجاميع أعلاه. يُحتسب الوقت أثناء الاتصال، وتُحتسب القتلات على هذا السيرفر فقط.", + "sign_in_hint": "سجّل الدخول إلى 5stack والعب هنا لتظهر في القائمة." + }, + "server_page": { + "breadcrumb": "مسار التنقل", + "online": "متصل", + "offline": "غير متصل", + "map": "الخريطة", + "community_server": "سيرفر مجتمع", + "online_now": "متصل الآن", + "players_this_week": "لاعبو هذا الأسبوع", + "different_players": "لاعبين مختلفين", + "hours_this_week": "ساعات هذا الأسبوع", + "played_by_everyone": "لعبها الجميع", + "all_time_players": "لاعبو كل الأوقات", + "since": "منذ {date}", + "activity": "النشاط · آخر 7 أيام", + "activity_description": "إجمالي الساعات التي لعبها الجميع هنا كل يوم. لا تُعرض أوقات اللعب الفردية لأي أحد.", + "chart_title": "ساعات اللاعبين يوميًا على {server}، آخر 7 أيام", + "chart_column": "{date}: لُعب {hours}، و{players} لاعبين في أكثر ساعة ازدحامًا", + "today": "اليوم", + "today_so_far": "{date}، حتى الآن", + "so_far": "{hours} حتى الآن", + "played": "لُعبت", + "peak_players": "لاعبين في أكثر ساعة ازدحامًا", + "busiest_hour": "اللاعبون في أكثر ساعة ازدحامًا", + "full_day": "يوم كامل", + "today_partial": "اليوم حتى الآن", + "day": "اليوم", + "player_hours": "ساعات اللاعبين", + "not_found_title": "السيرفر غير موجود", + "not_found_description": "هذا السيرفر غير موجود أو ليس عامًا." + } } } diff --git a/i18n/locales/da_DK.json b/i18n/locales/da_DK.json index d330d2687..93b57c852 100644 --- a/i18n/locales/da_DK.json +++ b/i18n/locales/da_DK.json @@ -2274,7 +2274,11 @@ "reserved_disk_space_fresh_gb_description": "Minimum diskplads i GB krævet for at klargøre en ny node (CS2 endnu ikke installeret). Sæt til 0 for at slå fra.", "reserved_disk_space_existing_gb": "Krævet diskplads - eksisterende installation (GB)", "reserved_disk_space_existing_gb_description": "Minimum diskplads i GB krævet, når CS2 allerede er installeret på noden. Sæt til 0 for at slå fra.", - "update_cpu_pinning": "CPU-pinning opdateret" + "update_cpu_pinning": "CPU-pinning opdateret", + "player_sessions_section": "Spillersessioner på Community-Servere", + "player_sessions_description": "Community-servere med Player Management-pluginnet registrerer, hvem der spillede, hvornår og fra hvilken IP.", + "player_session_retention_days": "Gem Sessionshistorik (dage)", + "player_session_retention_days_description": "Hvor længe præcise sessioner og IP-adresser gemmes, mellem {min} og {max} dage. Ældre sessioner lægges sammen med totalerne." }, "highlights": { "title": "Highlights", @@ -3147,7 +3151,8 @@ "elo": "ELO", "maps": "Maps", "arsenal": "Arsenal", - "combat": "Kamp" + "combat": "Kamp", + "community": "Community" }, "roles": { "no_data": "Ikke nok nylige kampe til at bestemme en rolle.", @@ -8254,6 +8259,7 @@ "steam_game_bans": "Spiludelukkelser", "permanent": "Permanent", "active": "Aktiv", + "active_list": "Aktive: {list}", "expired": "Udløbet", "on_record": "Registreret", "cannot_edit_expired": "Udløbne sanktioner kan ikke redigeres — tilføj en ny", @@ -10425,5 +10431,128 @@ "idle": "Ikke sat op", "check_again": "Tjek igen", "setup_guide": "Opsætningsguide" + }, + "community": { + "this_week": "Denne Uge", + "all_time": "I Alt", + "online": "Online", + "online_now": "Online nu", + "now": "nu", + "last_seen": "Sidst Set", + "sessions": "Sessioner", + "time_played": "Spilletid", + "time": "Tid", + "kills": "Kills", + "rank": "Placering", + "server": "Server", + "actions": "Handlinger", + "format": { + "hours_minutes": "{hours} t {minutes} min", + "minutes": "{minutes} min", + "seconds": "{seconds} s", + "hours": "{hours} t", + "just_now": "lige nu", + "within_hour": "inden for den seneste time" + }, + "player": { + "steam_only": "Kun Steam", + "in_game": "i spillet »{name}«", + "more": "Mere om {name}", + "sanction_history": "Sanktionshistorik" + }, + "ip": { + "label": "IP", + "shared_now": "Også på denne server nu: {names}", + "shared_week": "Samme IP de seneste 7 dage: {names}" + }, + "roster": { + "live": "Live", + "live_description": "Rapporteret af Player Management-pluginnet. Tilslutninger og afgange vises inden for få sekunder.", + "connected": "Tilsluttet", + "not_available": "i/t", + "not_detected": "Player Management-pluginnet blev ikke fundet. Spillerlisten læses via RCON hvert 30. sekund, og der gemmes ingen historik.", + "not_reporting": "Player Management-pluginnet rapporterer endnu ingen spillere, så spillerlisten læses via RCON hvert 30. sekund, og der gemmes ingen historik." + }, + "recent": { + "title": "Seneste Spillere", + "retention": "Historik gemmes i {days} dage", + "unique_24h": "Unikke · 24 t", + "unique_7d": "Unikke · 7 d", + "sessions_7d": "Sessioner · 7 d", + "time_played_7d": "Spilletid · 7 d", + "avg_session": "Gns. Session", + "players": "spillere", + "joins": "tilslutninger", + "per_visit": "pr. besøg", + "search": "Søg navn, Steam-ID eller IP", + "window_24h": "24 T", + "window_7d": "7 D", + "player": "Spiller", + "empty": "Ingen har spillet her i denne periode.", + "no_matches": "Ingen spillere matcher søgningen.", + "showing": "Viser {shown} af {total}", + "load_more": "Indlæs Flere" + }, + "player_tab": { + "moderator_note": "Moderatorer ser også præcise tidspunkter, sessioner, brugte navne og IP-adresser.", + "public_note": "Tid og kills på community-servere. Kun hvornår spilleren sidst blev set vises, aldrig hvornår der blev spillet.", + "avg_session": "gns. {duration} pr. session", + "kd": "{ratio} K/D", + "servers": "Servere", + "community_servers": "community-servere", + "servers_seen_on": "Servere Spilleren Er Set På", + "names_used": "Brugte Navne", + "show_sessions": "Vis sessioner på {server}", + "sessions_last_7_days": "Sessioner · seneste 7 dage", + "moderators_only": "Kun moderatorer", + "no_recent_sessions": "Ingen sessioner de seneste 7 dage.", + "no_servers_this_week": "Ingen tid på community-servere i denne uge.", + "also_on_ips": "Også Set på Deres IP-Adresser", + "ip_matches_title": "Andre konti set på denne spillers IP-adresser de seneste 7 dage", + "ips_used": "IP-adresser brugt af spilleren:", + "ip_sessions": "{count} session | {count} sessioner", + "empty_title": "Ingen Community-Sessioner", + "empty_description": "Denne spiller er endnu ikke set på en community-server." + }, + "leaderboard": { + "title": "Rangliste", + "by_time": "Tid", + "by_kills": "Kills", + "you": "Dig", + "empty_week": "Ingen med en 5stack-konto har spillet her i denne uge.", + "empty_all": "Ingen med en 5stack-konto har spillet her endnu.", + "footnote": "Viser spillere med en 5stack-konto. Spillere uden konto tæller stadig med i totalerne ovenfor. Tid tæller, mens man er tilsluttet; kills tæller kun på denne server.", + "sign_in_hint": "Log ind på 5stack og spil her for at blive vist." + }, + "server_page": { + "breadcrumb": "Brødkrummesti", + "online": "Online", + "offline": "Offline", + "map": "bane", + "community_server": "community-server", + "online_now": "Online Nu", + "players_this_week": "Spillere Denne Uge", + "different_players": "forskellige spillere", + "hours_this_week": "Timer Denne Uge", + "played_by_everyone": "spillet af alle", + "all_time_players": "Spillere I Alt", + "since": "siden {date}", + "activity": "Aktivitet · Seneste 7 Dage", + "activity_description": "Samlede timer alle har spillet her hver dag. Ingen individuelle spilletider vises.", + "chart_title": "Spillertimer pr. dag på {server}, seneste 7 dage", + "chart_column": "{date}: {hours} spillet, {players} spillere i den travleste time", + "today": "I dag", + "today_so_far": "{date}, indtil videre", + "so_far": "{hours} indtil videre", + "played": "spillet", + "peak_players": "spillere i den travleste time", + "busiest_hour": "Spillere i Travleste Time", + "full_day": "Hel dag", + "today_partial": "I dag indtil videre", + "day": "Dag", + "player_hours": "Spillertimer", + "not_found_title": "Server Ikke Fundet", + "not_found_description": "Denne server findes ikke eller er ikke offentlig." + } } } diff --git a/i18n/locales/de_DE.json b/i18n/locales/de_DE.json index 00f04d239..abc9470f6 100644 --- a/i18n/locales/de_DE.json +++ b/i18n/locales/de_DE.json @@ -2274,7 +2274,11 @@ "reserved_disk_space_fresh_gb_description": "Mindestens benötigter Speicherplatz in GB für das Provisionieren eines neuen Nodes (CS2 noch nicht installiert). 0 deaktiviert die Prüfung.", "reserved_disk_space_existing_gb": "Benötigter Speicherplatz – Bestehende Installation (GB)", "reserved_disk_space_existing_gb_description": "Mindestens benötigter Speicherplatz in GB, wenn CS2 auf dem Node bereits installiert ist. 0 deaktiviert die Prüfung.", - "update_cpu_pinning": "CPU-Pinning aktualisiert" + "update_cpu_pinning": "CPU-Pinning aktualisiert", + "player_sessions_section": "Community-Spielersitzungen", + "player_sessions_description": "Community-Server mit dem Player-Management-Plugin erfassen, wer wann und von welcher IP gespielt hat.", + "player_session_retention_days": "Sitzungsverlauf Aufbewahren (Tage)", + "player_session_retention_days_description": "Wie lange genaue Sitzungen und IPs gespeichert werden, zwischen {min} und {max} Tagen. Ältere Sitzungen fließen in die Gesamtsummen ein." }, "highlights": { "title": "Highlights", @@ -3147,7 +3151,8 @@ "elo": "ELO", "maps": "Maps", "arsenal": "Arsenal", - "combat": "Combat" + "combat": "Combat", + "community": "Community" }, "roles": { "no_data": "Nicht genug aktuelle Matches, um eine Rolle zu erkennen.", @@ -8254,6 +8259,7 @@ "steam_game_bans": "Spielsperre(n)", "permanent": "Permanent", "active": "Aktiv", + "active_list": "Aktiv: {list}", "expired": "Abgelaufen", "on_record": "Vermerkt", "cannot_edit_expired": "Abgelaufene Sanktionen lassen sich nicht bearbeiten — leg eine neue an", @@ -10425,5 +10431,128 @@ "idle": "Nicht eingerichtet", "check_again": "Erneut prüfen", "setup_guide": "Einrichtungsanleitung" + }, + "community": { + "this_week": "Diese Woche", + "all_time": "Gesamt", + "online": "Online", + "online_now": "Gerade online", + "now": "jetzt", + "last_seen": "Zuletzt gesehen", + "sessions": "Sitzungen", + "time_played": "Spielzeit", + "time": "Zeit", + "kills": "Kills", + "rank": "Rang", + "server": "Server", + "actions": "Aktionen", + "format": { + "hours_minutes": "{hours} h {minutes} min", + "minutes": "{minutes} min", + "seconds": "{seconds} s", + "hours": "{hours} h", + "just_now": "gerade eben", + "within_hour": "in der letzten Stunde" + }, + "player": { + "steam_only": "Nur Steam", + "in_game": "im Spiel „{name}“", + "more": "Mehr zu {name}", + "sanction_history": "Sanktionsverlauf" + }, + "ip": { + "label": "IP", + "shared_now": "Gerade auch auf diesem Server: {names}", + "shared_week": "Gleiche IP in den letzten 7 Tagen: {names}" + }, + "roster": { + "live": "Live", + "live_description": "Gemeldet vom Player-Management-Plugin. Beitritte und Abgänge erscheinen innerhalb von Sekunden.", + "connected": "Verbunden", + "not_available": "k. A.", + "not_detected": "Player-Management-Plugin nicht erkannt. Die Spielerliste wird alle 30 Sekunden per RCON gelesen, und es wird kein Verlauf gespeichert.", + "not_reporting": "Das Player-Management-Plugin meldet noch keine Spieler, daher wird die Spielerliste alle 30 Sekunden per RCON gelesen und kein Verlauf gespeichert." + }, + "recent": { + "title": "Letzte Spieler", + "retention": "Verlauf wird {days} Tage gespeichert", + "unique_24h": "Eindeutig · 24 h", + "unique_7d": "Eindeutig · 7 T", + "sessions_7d": "Sitzungen · 7 T", + "time_played_7d": "Spielzeit · 7 T", + "avg_session": "Ø Sitzung", + "players": "Spieler", + "joins": "Beitritte", + "per_visit": "pro Besuch", + "search": "Name, Steam-ID oder IP suchen", + "window_24h": "24 h", + "window_7d": "7 T", + "player": "Spieler", + "empty": "In diesem Zeitraum hat hier niemand gespielt.", + "no_matches": "Keine Spieler passen zu dieser Suche.", + "showing": "{shown} von {total} angezeigt", + "load_more": "Mehr Laden" + }, + "player_tab": { + "moderator_note": "Moderatoren sehen zusätzlich genaue Zeiten, Sitzungen, verwendete Namen und IPs.", + "public_note": "Zeit und Kills auf Community-Servern. Angezeigt wird nur, wann der Spieler zuletzt gesehen wurde, nie wann er gespielt hat.", + "avg_session": "Ø {duration} pro Sitzung", + "kd": "{ratio} K/D", + "servers": "Server", + "community_servers": "Community-Server", + "servers_seen_on": "Gesehen auf Servern", + "names_used": "Verwendete Namen", + "show_sessions": "Sitzungen auf {server} anzeigen", + "sessions_last_7_days": "Sitzungen · letzte 7 Tage", + "moderators_only": "Nur Moderatoren", + "no_recent_sessions": "Keine Sitzungen in den letzten 7 Tagen.", + "no_servers_this_week": "Diese Woche keine Zeit auf Community-Servern.", + "also_on_ips": "Auch auf ihren IPs gesehen", + "ip_matches_title": "Andere Konten, die in den letzten 7 Tagen auf den IPs dieses Spielers gesehen wurden", + "ips_used": "Von diesem Spieler genutzte IPs:", + "ip_sessions": "{count} Sitzung | {count} Sitzungen", + "empty_title": "Keine Community-Sitzungen", + "empty_description": "Dieser Spieler wurde noch auf keinem Community-Server gesehen." + }, + "leaderboard": { + "title": "Bestenliste", + "by_time": "Zeit", + "by_kills": "Kills", + "you": "Du", + "empty_week": "Diese Woche hat hier noch niemand mit einem 5stack-Konto gespielt.", + "empty_all": "Hier hat noch niemand mit einem 5stack-Konto gespielt.", + "footnote": "Aufgeführt werden Spieler mit einem 5stack-Konto. Spieler ohne Konto zählen trotzdem zu den Summen oben. Zeit zählt, solange man verbunden ist; Kills zählen nur auf diesem Server.", + "sign_in_hint": "Melde dich bei 5stack an und spiele hier, um aufzutauchen." + }, + "server_page": { + "breadcrumb": "Brotkrümelnavigation", + "online": "Online", + "offline": "Offline", + "map": "Karte", + "community_server": "Community-Server", + "online_now": "Gerade Online", + "players_this_week": "Spieler Diese Woche", + "different_players": "verschiedene Spieler", + "hours_this_week": "Stunden Diese Woche", + "played_by_everyone": "von allen gespielt", + "all_time_players": "Spieler Insgesamt", + "since": "seit {date}", + "activity": "Aktivität · Letzte 7 Tage", + "activity_description": "Gesamtstunden, die alle hier pro Tag gespielt haben. Einzelne Spielzeiten werden nicht angezeigt.", + "chart_title": "Spielerstunden pro Tag auf {server}, letzte 7 Tage", + "chart_column": "{date}: {hours} gespielt, {players} Spieler in der stärksten Stunde", + "today": "Heute", + "today_so_far": "{date}, bisher", + "so_far": "{hours} bisher", + "played": "gespielt", + "peak_players": "Spieler in der stärksten Stunde", + "busiest_hour": "Spieler in der Stärksten Stunde", + "full_day": "Ganzer Tag", + "today_partial": "Heute bisher", + "day": "Tag", + "player_hours": "Spielerstunden", + "not_found_title": "Server Nicht Gefunden", + "not_found_description": "Dieser Server existiert nicht oder ist nicht öffentlich." + } } } diff --git a/i18n/locales/en.json b/i18n/locales/en.json index bc16447ac..c8afc46e7 100644 --- a/i18n/locales/en.json +++ b/i18n/locales/en.json @@ -2274,7 +2274,11 @@ "reserved_disk_space_fresh_gb_description": "Minimum disk space in GB required for a fresh node provisioning (CS2 not yet installed). Set to 0 to disable.", "reserved_disk_space_existing_gb": "Required Disk Space - Existing Install (GB)", "reserved_disk_space_existing_gb_description": "Minimum disk space in GB required when CS2 is already installed on the node. Set to 0 to disable.", - "update_cpu_pinning": "CPU Pinning updated" + "update_cpu_pinning": "CPU Pinning updated", + "player_sessions_section": "Community Player Sessions", + "player_sessions_description": "Community servers running the Player Management plugin record who played, when, and from which IP.", + "player_session_retention_days": "Keep Session History (days)", + "player_session_retention_days_description": "How long exact sessions and IPs are kept, between {min} and {max} days. Older sessions are folded into all-time totals." }, "highlights": { "title": "Highlights", @@ -3147,7 +3151,8 @@ "elo": "ELO", "maps": "Maps", "arsenal": "Arsenal", - "combat": "Combat" + "combat": "Combat", + "community": "Community" }, "roles": { "no_data": "Not enough recent matches to detect a role.", @@ -8254,6 +8259,7 @@ "steam_game_bans": "Game Ban(s)", "permanent": "Permanent", "active": "Active", + "active_list": "Active: {list}", "expired": "Expired", "on_record": "On record", "cannot_edit_expired": "Expired sanctions can't be edited — add a new one", @@ -10425,5 +10431,128 @@ "idle": "Not set up", "check_again": "Check again", "setup_guide": "Setup guide" + }, + "community": { + "this_week": "This Week", + "all_time": "All Time", + "online": "Online", + "online_now": "Online now", + "now": "now", + "last_seen": "Last Seen", + "sessions": "Sessions", + "time_played": "Time Played", + "time": "Time", + "kills": "Kills", + "rank": "Rank", + "server": "Server", + "actions": "Actions", + "format": { + "hours_minutes": "{hours}h {minutes}m", + "minutes": "{minutes}m", + "seconds": "{seconds}s", + "hours": "{hours}h", + "just_now": "just now", + "within_hour": "within the hour" + }, + "player": { + "steam_only": "Steam only", + "in_game": "in-game \"{name}\"", + "more": "More for {name}", + "sanction_history": "Sanction History" + }, + "ip": { + "label": "IP", + "shared_now": "Also on this server now: {names}", + "shared_week": "Same IP in the last 7 days: {names}" + }, + "roster": { + "live": "Live", + "live_description": "Reported by the Player Management plugin. Joins and leaves show up within seconds.", + "connected": "Connected", + "not_available": "n/a", + "not_detected": "Player Management plugin not detected. The roster is read over RCON every 30 seconds, and no history is recorded.", + "not_reporting": "The Player Management plugin isn't reporting players yet, so the roster is read over RCON every 30 seconds and no history is recorded." + }, + "recent": { + "title": "Recent Players", + "retention": "History kept {days} days", + "unique_24h": "Unique · 24h", + "unique_7d": "Unique · 7d", + "sessions_7d": "Sessions · 7d", + "time_played_7d": "Time Played · 7d", + "avg_session": "Avg Session", + "players": "players", + "joins": "joins", + "per_visit": "per visit", + "search": "Search name, Steam ID or IP", + "window_24h": "24H", + "window_7d": "7D", + "player": "Player", + "empty": "No one has played here in this window.", + "no_matches": "No players match that search.", + "showing": "Showing {shown} of {total}", + "load_more": "Load More" + }, + "player_tab": { + "moderator_note": "Moderators also see exact times, sessions, names used and IPs.", + "public_note": "Time and kills on community servers. Only when they were last seen is shown, never when they played.", + "avg_session": "avg {duration} a session", + "kd": "{ratio} K/D", + "servers": "Servers", + "community_servers": "community servers", + "servers_seen_on": "Servers Seen On", + "names_used": "Names Used", + "show_sessions": "Show sessions on {server}", + "sessions_last_7_days": "Sessions · last 7 days", + "moderators_only": "Moderators only", + "no_recent_sessions": "No sessions in the last 7 days.", + "no_servers_this_week": "No community server time this week.", + "also_on_ips": "Also Seen on Their IPs", + "ip_matches_title": "Other accounts seen on this player's IPs in the last 7 days", + "ips_used": "IPs this player used:", + "ip_sessions": "{count} session | {count} sessions", + "empty_title": "No Community Sessions", + "empty_description": "This player hasn't been seen on a community server yet." + }, + "leaderboard": { + "title": "Leaderboard", + "by_time": "Time", + "by_kills": "Kills", + "you": "You", + "empty_week": "No one with a 5stack account has played here this week.", + "empty_all": "No one with a 5stack account has played here yet.", + "footnote": "Lists players with a 5stack account. Players without one still count toward the totals above. Time counts while connected; kills count on this server only.", + "sign_in_hint": "Sign in to 5stack and play here to show up." + }, + "server_page": { + "breadcrumb": "Breadcrumb", + "online": "Online", + "offline": "Offline", + "map": "map", + "community_server": "community server", + "online_now": "Online Now", + "players_this_week": "Players This Week", + "different_players": "different players", + "hours_this_week": "Hours This Week", + "played_by_everyone": "played by everyone", + "all_time_players": "All-Time Players", + "since": "since {date}", + "activity": "Activity · Last 7 Days", + "activity_description": "Total hours everyone played here each day. Nobody's individual play times are shown.", + "chart_title": "Player hours per day on {server}, last 7 days", + "chart_column": "{date}: {hours} played, {players} players in the busiest hour", + "today": "Today", + "today_so_far": "{date}, so far", + "so_far": "{hours} so far", + "played": "played", + "peak_players": "players in the busiest hour", + "busiest_hour": "Players in Busiest Hour", + "full_day": "Full day", + "today_partial": "Today so far", + "day": "Day", + "player_hours": "Player Hours", + "not_found_title": "Server Not Found", + "not_found_description": "This server doesn't exist or isn't public." + } } } diff --git a/i18n/locales/es_ES.json b/i18n/locales/es_ES.json index e8cbf3d0c..01235b797 100644 --- a/i18n/locales/es_ES.json +++ b/i18n/locales/es_ES.json @@ -2274,7 +2274,11 @@ "reserved_disk_space_fresh_gb_description": "Espacio de disco mínimo en GB necesario para aprovisionar un nodo nuevo (CS2 aún no instalado). Ponlo a 0 para desactivarlo.", "reserved_disk_space_existing_gb": "Espacio de disco necesario - Instalación existente (GB)", "reserved_disk_space_existing_gb_description": "Espacio de disco mínimo en GB necesario cuando CS2 ya está instalado en el nodo. Ponlo a 0 para desactivarlo.", - "update_cpu_pinning": "Asignación de CPU actualizada" + "update_cpu_pinning": "Asignación de CPU actualizada", + "player_sessions_section": "Sesiones de Jugadores de la Comunidad", + "player_sessions_description": "Los servidores de la comunidad con el plugin Player Management registran quién jugó, cuándo y desde qué IP.", + "player_session_retention_days": "Conservar Historial de Sesiones (días)", + "player_session_retention_days_description": "Cuánto tiempo se guardan las sesiones exactas y las IP, entre {min} y {max} días. Las sesiones más antiguas se suman a los totales históricos." }, "highlights": { "title": "Highlights", @@ -3147,7 +3151,8 @@ "elo": "ELO", "maps": "Mapas", "arsenal": "Arsenal", - "combat": "Combate" + "combat": "Combate", + "community": "Comunidad" }, "roles": { "no_data": "No hay suficientes partidas recientes para detectar un rol.", @@ -8254,6 +8259,7 @@ "steam_game_bans": "Veto(s) de juego", "permanent": "Permanente", "active": "Activa", + "active_list": "Activas: {list}", "expired": "Caducada", "on_record": "En el historial", "cannot_edit_expired": "Las sanciones caducadas no se pueden editar: añade una nueva", @@ -10425,5 +10431,128 @@ "idle": "Sin configurar", "check_again": "Comprobar de nuevo", "setup_guide": "Guía de configuración" + }, + "community": { + "this_week": "Esta Semana", + "all_time": "Histórico", + "online": "En línea", + "online_now": "En línea ahora", + "now": "ahora", + "last_seen": "Última Vez Visto", + "sessions": "Sesiones", + "time_played": "Tiempo Jugado", + "time": "Tiempo", + "kills": "Bajas", + "rank": "Puesto", + "server": "Servidor", + "actions": "Acciones", + "format": { + "hours_minutes": "{hours} h {minutes} min", + "minutes": "{minutes} min", + "seconds": "{seconds} s", + "hours": "{hours} h", + "just_now": "ahora mismo", + "within_hour": "en la última hora" + }, + "player": { + "steam_only": "Solo Steam", + "in_game": "en el juego «{name}»", + "more": "Más sobre {name}", + "sanction_history": "Historial de Sanciones" + }, + "ip": { + "label": "IP", + "shared_now": "También en este servidor ahora: {names}", + "shared_week": "Misma IP en los últimos 7 días: {names}" + }, + "roster": { + "live": "En directo", + "live_description": "Informado por el plugin Player Management. Las entradas y salidas aparecen en segundos.", + "connected": "Conectado", + "not_available": "n/d", + "not_detected": "No se detecta el plugin Player Management. La lista de jugadores se lee por RCON cada 30 segundos y no se guarda historial.", + "not_reporting": "El plugin Player Management aún no informa de los jugadores, así que la lista se lee por RCON cada 30 segundos y no se guarda historial." + }, + "recent": { + "title": "Jugadores Recientes", + "retention": "Historial guardado {days} días", + "unique_24h": "Únicos · 24 h", + "unique_7d": "Únicos · 7 d", + "sessions_7d": "Sesiones · 7 d", + "time_played_7d": "Tiempo Jugado · 7 d", + "avg_session": "Sesión Media", + "players": "jugadores", + "joins": "entradas", + "per_visit": "por visita", + "search": "Buscar nombre, Steam ID o IP", + "window_24h": "24 H", + "window_7d": "7 D", + "player": "Jugador", + "empty": "Nadie ha jugado aquí en este periodo.", + "no_matches": "Ningún jugador coincide con esa búsqueda.", + "showing": "Mostrando {shown} de {total}", + "load_more": "Cargar Más" + }, + "player_tab": { + "moderator_note": "Los moderadores también ven horas exactas, sesiones, nombres usados e IP.", + "public_note": "Tiempo y bajas en servidores de la comunidad. Solo se muestra cuándo se le vio por última vez, nunca cuándo jugó.", + "avg_session": "{duration} de media por sesión", + "kd": "{ratio} K/D", + "servers": "Servidores", + "community_servers": "servidores de la comunidad", + "servers_seen_on": "Servidores Donde Se Le Vio", + "names_used": "Nombres Usados", + "show_sessions": "Mostrar sesiones en {server}", + "sessions_last_7_days": "Sesiones · últimos 7 días", + "moderators_only": "Solo moderadores", + "no_recent_sessions": "No hay sesiones en los últimos 7 días.", + "no_servers_this_week": "Sin tiempo en servidores de la comunidad esta semana.", + "also_on_ips": "También Vistos en Sus IP", + "ip_matches_title": "Otras cuentas vistas en las IP de este jugador en los últimos 7 días", + "ips_used": "IP que ha usado este jugador:", + "ip_sessions": "{count} sesión | {count} sesiones", + "empty_title": "Sin Sesiones en la Comunidad", + "empty_description": "Este jugador todavía no se ha visto en ningún servidor de la comunidad." + }, + "leaderboard": { + "title": "Clasificación", + "by_time": "Tiempo", + "by_kills": "Bajas", + "you": "Tú", + "empty_week": "Nadie con una cuenta de 5stack ha jugado aquí esta semana.", + "empty_all": "Nadie con una cuenta de 5stack ha jugado aquí todavía.", + "footnote": "Muestra jugadores con cuenta de 5stack. Los jugadores sin cuenta siguen contando en los totales de arriba. El tiempo cuenta mientras estás conectado; las bajas solo cuentan en este servidor.", + "sign_in_hint": "Inicia sesión en 5stack y juega aquí para aparecer." + }, + "server_page": { + "breadcrumb": "Ruta de navegación", + "online": "En línea", + "offline": "Desconectado", + "map": "mapa", + "community_server": "servidor de la comunidad", + "online_now": "En Línea Ahora", + "players_this_week": "Jugadores Esta Semana", + "different_players": "jugadores distintos", + "hours_this_week": "Horas Esta Semana", + "played_by_everyone": "jugadas entre todos", + "all_time_players": "Jugadores Totales", + "since": "desde el {date}", + "activity": "Actividad · Últimos 7 Días", + "activity_description": "Horas totales que todos jugaron aquí cada día. No se muestran tiempos de juego individuales.", + "chart_title": "Horas de juego por día en {server}, últimos 7 días", + "chart_column": "{date}: {hours} jugadas, {players} jugadores en la hora de más actividad", + "today": "Hoy", + "today_so_far": "{date}, hasta ahora", + "so_far": "{hours} hasta ahora", + "played": "jugadas", + "peak_players": "jugadores en la hora de más actividad", + "busiest_hour": "Jugadores en la Hora de Más Actividad", + "full_day": "Día completo", + "today_partial": "Hoy hasta ahora", + "day": "Día", + "player_hours": "Horas de Juego", + "not_found_title": "Servidor No Encontrado", + "not_found_description": "Este servidor no existe o no es público." + } } } diff --git a/i18n/locales/fr_FR.json b/i18n/locales/fr_FR.json index bdba56062..869bf8637 100644 --- a/i18n/locales/fr_FR.json +++ b/i18n/locales/fr_FR.json @@ -2274,7 +2274,11 @@ "reserved_disk_space_fresh_gb_description": "Espace disque minimum en Go requis pour provisionner un nouveau nœud (CS2 pas encore installé). Mettre 0 pour désactiver.", "reserved_disk_space_existing_gb": "Espace disque requis - installation existante (Go)", "reserved_disk_space_existing_gb_description": "Espace disque minimum en Go requis quand CS2 est déjà installé sur le nœud. Mettre 0 pour désactiver.", - "update_cpu_pinning": "Épinglage CPU mis à jour" + "update_cpu_pinning": "Épinglage CPU mis à jour", + "player_sessions_section": "Sessions des Joueurs Communautaires", + "player_sessions_description": "Les serveurs communautaires équipés du plugin Player Management enregistrent qui a joué, quand et depuis quelle IP.", + "player_session_retention_days": "Conserver l’Historique des Sessions (jours)", + "player_session_retention_days_description": "Durée de conservation des sessions exactes et des IP, entre {min} et {max} jours. Les sessions plus anciennes sont intégrées aux totaux globaux." }, "highlights": { "title": "Highlights", @@ -3147,7 +3151,8 @@ "elo": "ELO", "maps": "Maps", "arsenal": "Arsenal", - "combat": "Combat" + "combat": "Combat", + "community": "Communauté" }, "roles": { "no_data": "Pas assez de matchs récents pour détecter un rôle.", @@ -8254,6 +8259,7 @@ "steam_game_bans": "Bannissement(s) de jeu", "permanent": "Permanente", "active": "Active", + "active_list": "Actives : {list}", "expired": "Expirée", "on_record": "Au dossier", "cannot_edit_expired": "Une sanction expirée ne peut pas être modifiée — ajoutes-en une nouvelle", @@ -10425,5 +10431,128 @@ "idle": "Non configuré", "check_again": "Vérifier à nouveau", "setup_guide": "Guide d'installation" + }, + "community": { + "this_week": "Cette Semaine", + "all_time": "Depuis Toujours", + "online": "En ligne", + "online_now": "En ligne maintenant", + "now": "maintenant", + "last_seen": "Vu Pour la Dernière Fois", + "sessions": "Sessions", + "time_played": "Temps de Jeu", + "time": "Temps", + "kills": "Kills", + "rank": "Rang", + "server": "Serveur", + "actions": "Actions", + "format": { + "hours_minutes": "{hours} h {minutes} min", + "minutes": "{minutes} min", + "seconds": "{seconds} s", + "hours": "{hours} h", + "just_now": "à l’instant", + "within_hour": "dans l’heure" + }, + "player": { + "steam_only": "Steam uniquement", + "in_game": "en jeu « {name} »", + "more": "Plus pour {name}", + "sanction_history": "Historique des Sanctions" + }, + "ip": { + "label": "IP", + "shared_now": "Aussi sur ce serveur en ce moment : {names}", + "shared_week": "Même IP ces 7 derniers jours : {names}" + }, + "roster": { + "live": "En direct", + "live_description": "Signalé par le plugin Player Management. Les arrivées et départs apparaissent en quelques secondes.", + "connected": "Connecté", + "not_available": "n/d", + "not_detected": "Plugin Player Management non détecté. La liste des joueurs est lue via RCON toutes les 30 secondes, et aucun historique n’est enregistré.", + "not_reporting": "Le plugin Player Management ne signale pas encore les joueurs : la liste est donc lue via RCON toutes les 30 secondes et aucun historique n’est enregistré." + }, + "recent": { + "title": "Joueurs Récents", + "retention": "Historique conservé {days} jours", + "unique_24h": "Uniques · 24 h", + "unique_7d": "Uniques · 7 j", + "sessions_7d": "Sessions · 7 j", + "time_played_7d": "Temps de Jeu · 7 j", + "avg_session": "Session Moyenne", + "players": "joueurs", + "joins": "connexions", + "per_visit": "par visite", + "search": "Rechercher un nom, un Steam ID ou une IP", + "window_24h": "24 H", + "window_7d": "7 J", + "player": "Joueur", + "empty": "Personne n’a joué ici sur cette période.", + "no_matches": "Aucun joueur ne correspond à cette recherche.", + "showing": "{shown} sur {total} affichés", + "load_more": "Charger Plus" + }, + "player_tab": { + "moderator_note": "Les modérateurs voient aussi les heures exactes, les sessions, les noms utilisés et les IP.", + "public_note": "Temps et kills sur les serveurs communautaires. Seule la dernière apparition est affichée, jamais les heures de jeu.", + "avg_session": "{duration} en moyenne par session", + "kd": "{ratio} K/D", + "servers": "Serveurs", + "community_servers": "serveurs communautaires", + "servers_seen_on": "Serveurs Fréquentés", + "names_used": "Noms Utilisés", + "show_sessions": "Afficher les sessions sur {server}", + "sessions_last_7_days": "Sessions · 7 derniers jours", + "moderators_only": "Modérateurs uniquement", + "no_recent_sessions": "Aucune session ces 7 derniers jours.", + "no_servers_this_week": "Aucun temps sur les serveurs communautaires cette semaine.", + "also_on_ips": "Aussi Vus sur Ses IP", + "ip_matches_title": "Autres comptes vus sur les IP de ce joueur ces 7 derniers jours", + "ips_used": "IP utilisées par ce joueur :", + "ip_sessions": "{count} session | {count} sessions", + "empty_title": "Aucune Session Communautaire", + "empty_description": "Ce joueur n’a encore été vu sur aucun serveur communautaire." + }, + "leaderboard": { + "title": "Classement", + "by_time": "Temps", + "by_kills": "Kills", + "you": "Vous", + "empty_week": "Personne avec un compte 5stack n’a joué ici cette semaine.", + "empty_all": "Personne avec un compte 5stack n’a encore joué ici.", + "footnote": "Liste les joueurs ayant un compte 5stack. Les joueurs sans compte comptent quand même dans les totaux ci-dessus. Le temps compte tant que l’on est connecté ; les kills ne comptent que sur ce serveur.", + "sign_in_hint": "Connectez-vous à 5stack et jouez ici pour apparaître." + }, + "server_page": { + "breadcrumb": "Fil d’Ariane", + "online": "En ligne", + "offline": "Hors ligne", + "map": "carte", + "community_server": "serveur communautaire", + "online_now": "En Ligne", + "players_this_week": "Joueurs Cette Semaine", + "different_players": "joueurs différents", + "hours_this_week": "Heures Cette Semaine", + "played_by_everyone": "jouées par tous", + "all_time_players": "Joueurs au Total", + "since": "depuis le {date}", + "activity": "Activité · 7 Derniers Jours", + "activity_description": "Total des heures jouées ici chaque jour par tout le monde. Aucun temps de jeu individuel n’est affiché.", + "chart_title": "Heures-joueurs par jour sur {server}, 7 derniers jours", + "chart_column": "{date} : {hours} jouées, {players} joueurs à l’heure la plus chargée", + "today": "Aujourd’hui", + "today_so_far": "{date}, jusqu’ici", + "so_far": "{hours} jusqu’ici", + "played": "jouées", + "peak_players": "joueurs à l’heure la plus chargée", + "busiest_hour": "Joueurs à l’Heure la Plus Chargée", + "full_day": "Journée complète", + "today_partial": "Aujourd’hui jusqu’ici", + "day": "Jour", + "player_hours": "Heures-Joueurs", + "not_found_title": "Serveur Introuvable", + "not_found_description": "Ce serveur n’existe pas ou n’est pas public." + } } } diff --git a/i18n/locales/it_IT.json b/i18n/locales/it_IT.json index 368092370..4e5d14b6a 100644 --- a/i18n/locales/it_IT.json +++ b/i18n/locales/it_IT.json @@ -2274,7 +2274,11 @@ "reserved_disk_space_fresh_gb_description": "Spazio disco minimo in GB richiesto per il provisioning di un nodo nuovo (CS2 non ancora installato). Imposta 0 per disattivare.", "reserved_disk_space_existing_gb": "Spazio disco richiesto - installazione esistente (GB)", "reserved_disk_space_existing_gb_description": "Spazio disco minimo in GB richiesto quando CS2 è già installato sul nodo. Imposta 0 per disattivare.", - "update_cpu_pinning": "CPU pinning aggiornato" + "update_cpu_pinning": "CPU pinning aggiornato", + "player_sessions_section": "Sessioni dei Giocatori della Community", + "player_sessions_description": "I server della community con il plugin Player Management registrano chi ha giocato, quando e da quale IP.", + "player_session_retention_days": "Conserva lo Storico delle Sessioni (giorni)", + "player_session_retention_days_description": "Per quanto tempo vengono conservati sessioni esatte e IP, tra {min} e {max} giorni. Le sessioni più vecchie confluiscono nei totali complessivi." }, "highlights": { "title": "Highlight", @@ -3147,7 +3151,8 @@ "elo": "ELO", "maps": "Mappe", "arsenal": "Arsenale", - "combat": "Combattimento" + "combat": "Combattimento", + "community": "Community" }, "roles": { "no_data": "Match recenti insufficienti per rilevare un ruolo.", @@ -8254,6 +8259,7 @@ "steam_game_bans": "Ban di gioco", "permanent": "Permanente", "active": "Attiva", + "active_list": "Attive: {list}", "expired": "Scaduta", "on_record": "Registrato", "cannot_edit_expired": "Le sanzioni scadute non si possono modificare — aggiungine una nuova", @@ -10425,5 +10431,128 @@ "idle": "Non configurato", "check_again": "Verifica di nuovo", "setup_guide": "Guida alla configurazione" + }, + "community": { + "this_week": "Questa Settimana", + "all_time": "Sempre", + "online": "Online", + "online_now": "Online ora", + "now": "ora", + "last_seen": "Visto l’Ultima Volta", + "sessions": "Sessioni", + "time_played": "Tempo di Gioco", + "time": "Tempo", + "kills": "Uccisioni", + "rank": "Posizione", + "server": "Server", + "actions": "Azioni", + "format": { + "hours_minutes": "{hours} h {minutes} min", + "minutes": "{minutes} min", + "seconds": "{seconds} s", + "hours": "{hours} h", + "just_now": "proprio ora", + "within_hour": "nell’ultima ora" + }, + "player": { + "steam_only": "Solo Steam", + "in_game": "in gioco «{name}»", + "more": "Altro su {name}", + "sanction_history": "Storico Sanzioni" + }, + "ip": { + "label": "IP", + "shared_now": "Anche su questo server ora: {names}", + "shared_week": "Stesso IP negli ultimi 7 giorni: {names}" + }, + "roster": { + "live": "Live", + "live_description": "Segnalato dal plugin Player Management. Ingressi e uscite compaiono in pochi secondi.", + "connected": "Connesso", + "not_available": "n/d", + "not_detected": "Plugin Player Management non rilevato. L’elenco dei giocatori viene letto via RCON ogni 30 secondi e non viene registrato alcuno storico.", + "not_reporting": "Il plugin Player Management non segnala ancora i giocatori, quindi l’elenco viene letto via RCON ogni 30 secondi e non viene registrato alcuno storico." + }, + "recent": { + "title": "Giocatori Recenti", + "retention": "Storico conservato per {days} giorni", + "unique_24h": "Unici · 24 h", + "unique_7d": "Unici · 7 g", + "sessions_7d": "Sessioni · 7 g", + "time_played_7d": "Tempo di Gioco · 7 g", + "avg_session": "Sessione Media", + "players": "giocatori", + "joins": "ingressi", + "per_visit": "per visita", + "search": "Cerca nome, Steam ID o IP", + "window_24h": "24 H", + "window_7d": "7 G", + "player": "Giocatore", + "empty": "Nessuno ha giocato qui in questo periodo.", + "no_matches": "Nessun giocatore corrisponde alla ricerca.", + "showing": "{shown} di {total} mostrati", + "load_more": "Carica Altri" + }, + "player_tab": { + "moderator_note": "I moderatori vedono anche orari esatti, sessioni, nomi usati e IP.", + "public_note": "Tempo e uccisioni sui server della community. Viene mostrato solo quando è stato visto l’ultima volta, mai quando ha giocato.", + "avg_session": "{duration} di media a sessione", + "kd": "{ratio} K/D", + "servers": "Server", + "community_servers": "server della community", + "servers_seen_on": "Server Frequentati", + "names_used": "Nomi Usati", + "show_sessions": "Mostra le sessioni su {server}", + "sessions_last_7_days": "Sessioni · ultimi 7 giorni", + "moderators_only": "Solo moderatori", + "no_recent_sessions": "Nessuna sessione negli ultimi 7 giorni.", + "no_servers_this_week": "Nessun tempo sui server della community questa settimana.", + "also_on_ips": "Visti Anche sui Suoi IP", + "ip_matches_title": "Altri account visti sugli IP di questo giocatore negli ultimi 7 giorni", + "ips_used": "IP usati da questo giocatore:", + "ip_sessions": "{count} sessione | {count} sessioni", + "empty_title": "Nessuna Sessione nella Community", + "empty_description": "Questo giocatore non è ancora stato visto su un server della community." + }, + "leaderboard": { + "title": "Classifica", + "by_time": "Tempo", + "by_kills": "Uccisioni", + "you": "Tu", + "empty_week": "Nessuno con un account 5stack ha giocato qui questa settimana.", + "empty_all": "Nessuno con un account 5stack ha ancora giocato qui.", + "footnote": "Elenca i giocatori con un account 5stack. Chi non ce l’ha conta comunque nei totali qui sopra. Il tempo conta finché si è connessi; le uccisioni contano solo su questo server.", + "sign_in_hint": "Accedi a 5stack e gioca qui per comparire." + }, + "server_page": { + "breadcrumb": "Percorso di navigazione", + "online": "Online", + "offline": "Offline", + "map": "mappa", + "community_server": "server della community", + "online_now": "Online Ora", + "players_this_week": "Giocatori Questa Settimana", + "different_players": "giocatori diversi", + "hours_this_week": "Ore Questa Settimana", + "played_by_everyone": "giocate da tutti", + "all_time_players": "Giocatori Totali", + "since": "dal {date}", + "activity": "Attività · Ultimi 7 Giorni", + "activity_description": "Ore totali giocate qui ogni giorno da tutti. Non vengono mostrati i tempi di gioco individuali.", + "chart_title": "Ore giocatore al giorno su {server}, ultimi 7 giorni", + "chart_column": "{date}: {hours} giocate, {players} giocatori nell’ora di punta", + "today": "Oggi", + "today_so_far": "{date}, finora", + "so_far": "{hours} finora", + "played": "giocate", + "peak_players": "giocatori nell’ora di punta", + "busiest_hour": "Giocatori nell’Ora di Punta", + "full_day": "Giornata intera", + "today_partial": "Oggi finora", + "day": "Giorno", + "player_hours": "Ore Giocatore", + "not_found_title": "Server Non Trovato", + "not_found_description": "Questo server non esiste o non è pubblico." + } } } diff --git a/i18n/locales/ja_JP.json b/i18n/locales/ja_JP.json index d45bfa68a..36f3eae59 100644 --- a/i18n/locales/ja_JP.json +++ b/i18n/locales/ja_JP.json @@ -2274,7 +2274,11 @@ "reserved_disk_space_fresh_gb_description": "ノードの新規プロビジョニング時(CS2未インストール)に必要な最小ディスク容量(GB)です。0にすると無効になります。", "reserved_disk_space_existing_gb": "必要ディスク容量 - 既存インストール(GB)", "reserved_disk_space_existing_gb_description": "ノードに既にCS2がインストールされている場合に必要な最小ディスク容量(GB)です。0にすると無効になります。", - "update_cpu_pinning": "CPUピニングを更新しました" + "update_cpu_pinning": "CPUピニングを更新しました", + "player_sessions_section": "コミュニティのプレイヤーセッション", + "player_sessions_description": "Player Managementプラグインを導入したコミュニティサーバーは、誰がいつどのIPからプレイしたかを記録します。", + "player_session_retention_days": "セッション履歴の保存期間(日)", + "player_session_retention_days_description": "正確なセッションとIPを保存する期間です({min}〜{max}日)。古いセッションは全期間の合計に統合されます。" }, "highlights": { "title": "ハイライト", @@ -3147,7 +3151,8 @@ "elo": "ELO", "maps": "マップ", "arsenal": "武器", - "combat": "戦闘" + "combat": "戦闘", + "community": "コミュニティ" }, "roles": { "no_data": "ロールを判定できるだけの直近の試合がありません。", @@ -8254,6 +8259,7 @@ "steam_game_bans": "ゲームBAN", "permanent": "無期限", "active": "有効", + "active_list": "有効: {list}", "expired": "失効済み", "on_record": "記録済み", "cannot_edit_expired": "失効済みの処分は編集できません — 新しく追加してください", @@ -10425,5 +10431,128 @@ "idle": "未設定", "check_again": "再確認", "setup_guide": "セットアップガイド" + }, + "community": { + "this_week": "今週", + "all_time": "全期間", + "online": "オンライン", + "online_now": "オンライン中", + "now": "現在", + "last_seen": "最終確認", + "sessions": "セッション", + "time_played": "プレイ時間", + "time": "時間", + "kills": "キル", + "rank": "順位", + "server": "サーバー", + "actions": "操作", + "format": { + "hours_minutes": "{hours}時間{minutes}分", + "minutes": "{minutes}分", + "seconds": "{seconds}秒", + "hours": "{hours}時間", + "just_now": "たった今", + "within_hour": "1時間以内" + }, + "player": { + "steam_only": "Steamのみ", + "in_game": "ゲーム内名「{name}」", + "more": "{name}のその他の操作", + "sanction_history": "処分履歴" + }, + "ip": { + "label": "IP", + "shared_now": "現在このサーバーにいる同じIPのプレイヤー: {names}", + "shared_week": "過去7日間の同じIP: {names}" + }, + "roster": { + "live": "ライブ", + "live_description": "Player Managementプラグインからの報告です。参加と退出は数秒で反映されます。", + "connected": "接続時間", + "not_available": "なし", + "not_detected": "Player Managementプラグインが検出されません。プレイヤー一覧は30秒ごとにRCONで取得され、履歴は記録されません。", + "not_reporting": "Player Managementプラグインはまだプレイヤーを報告していないため、一覧は30秒ごとにRCONで取得され、履歴は記録されません。" + }, + "recent": { + "title": "最近のプレイヤー", + "retention": "履歴は{days}日間保存されます", + "unique_24h": "ユニーク · 24時間", + "unique_7d": "ユニーク · 7日", + "sessions_7d": "セッション · 7日", + "time_played_7d": "プレイ時間 · 7日", + "avg_session": "平均セッション", + "players": "人", + "joins": "回の参加", + "per_visit": "1回あたり", + "search": "名前、Steam ID、IPで検索", + "window_24h": "24時間", + "window_7d": "7日", + "player": "プレイヤー", + "empty": "この期間にここでプレイした人はいません。", + "no_matches": "検索に一致するプレイヤーはいません。", + "showing": "{total}件中{shown}件を表示", + "load_more": "さらに読み込む" + }, + "player_tab": { + "moderator_note": "モデレーターには正確な時刻、セッション、使用した名前、IPも表示されます。", + "public_note": "コミュニティサーバーでのプレイ時間とキル数です。最後に確認された時期のみ表示され、プレイした時間帯は表示されません。", + "avg_session": "1セッション平均{duration}", + "kd": "K/D {ratio}", + "servers": "サーバー", + "community_servers": "コミュニティサーバー", + "servers_seen_on": "確認されたサーバー", + "names_used": "使用した名前", + "show_sessions": "{server}のセッションを表示", + "sessions_last_7_days": "セッション · 過去7日間", + "moderators_only": "モデレーターのみ", + "no_recent_sessions": "過去7日間のセッションはありません。", + "no_servers_this_week": "今週はコミュニティサーバーでのプレイがありません。", + "also_on_ips": "同じIPで確認されたアカウント", + "ip_matches_title": "過去7日間にこのプレイヤーのIPで確認された他のアカウント", + "ips_used": "このプレイヤーが使用したIP:", + "ip_sessions": "{count}セッション | {count}セッション", + "empty_title": "コミュニティセッションなし", + "empty_description": "このプレイヤーはまだどのコミュニティサーバーでも確認されていません。" + }, + "leaderboard": { + "title": "リーダーボード", + "by_time": "時間", + "by_kills": "キル", + "you": "あなた", + "empty_week": "今週ここで5stackアカウントを持つプレイヤーはプレイしていません。", + "empty_all": "まだ5stackアカウントを持つプレイヤーはここでプレイしていません。", + "footnote": "5stackアカウントを持つプレイヤーを表示しています。アカウントのないプレイヤーも上の合計には含まれます。時間は接続中にカウントされ、キルはこのサーバーでのみカウントされます。", + "sign_in_hint": "5stackにサインインしてここでプレイすると表示されます。" + }, + "server_page": { + "breadcrumb": "パンくずリスト", + "online": "オンライン", + "offline": "オフライン", + "map": "マップ", + "community_server": "コミュニティサーバー", + "online_now": "現在オンライン", + "players_this_week": "今週のプレイヤー", + "different_players": "人のプレイヤー", + "hours_this_week": "今週の時間", + "played_by_everyone": "全員の合計", + "all_time_players": "全期間のプレイヤー", + "since": "{date}から", + "activity": "アクティビティ · 過去7日間", + "activity_description": "全員がここで毎日プレイした合計時間です。個人のプレイ時間は表示されません。", + "chart_title": "{server}の1日あたりのプレイヤー時間、過去7日間", + "chart_column": "{date}: {hours}プレイ、最も混雑した時間帯に{players}人", + "today": "今日", + "today_so_far": "{date}(現時点)", + "so_far": "現時点で{hours}", + "played": "プレイ", + "peak_players": "人(最も混雑した時間帯)", + "busiest_hour": "最も混雑した時間帯のプレイヤー数", + "full_day": "1日分", + "today_partial": "今日(現時点)", + "day": "日", + "player_hours": "プレイヤー時間", + "not_found_title": "サーバーが見つかりません", + "not_found_description": "このサーバーは存在しないか、公開されていません。" + } } } diff --git a/i18n/locales/ko_KR.json b/i18n/locales/ko_KR.json index bd2666a94..d01c32815 100644 --- a/i18n/locales/ko_KR.json +++ b/i18n/locales/ko_KR.json @@ -2274,7 +2274,11 @@ "reserved_disk_space_fresh_gb_description": "신규 노드 프로비저닝(CS2 미설치 상태)에 필요한 최소 디스크 공간(GB)입니다. 비활성화하려면 0으로 설정하세요.", "reserved_disk_space_existing_gb": "필요 디스크 공간 - 기존 설치(GB)", "reserved_disk_space_existing_gb_description": "노드에 CS2가 이미 설치된 경우 필요한 최소 디스크 공간(GB)입니다. 비활성화하려면 0으로 설정하세요.", - "update_cpu_pinning": "CPU 고정 설정이 업데이트되었습니다" + "update_cpu_pinning": "CPU 고정 설정이 업데이트되었습니다", + "player_sessions_section": "커뮤니티 플레이어 세션", + "player_sessions_description": "Player Management 플러그인을 실행하는 커뮤니티 서버는 누가, 언제, 어떤 IP에서 플레이했는지 기록합니다.", + "player_session_retention_days": "세션 기록 보관 기간 (일)", + "player_session_retention_days_description": "정확한 세션과 IP를 보관하는 기간으로, {min}일에서 {max}일 사이입니다. 오래된 세션은 전체 기간 합계에 합산됩니다." }, "highlights": { "title": "하이라이트", @@ -3147,7 +3151,8 @@ "elo": "ELO", "maps": "맵", "arsenal": "무기", - "combat": "전투" + "combat": "전투", + "community": "커뮤니티" }, "roles": { "no_data": "역할을 판단하기에 최근 매치 수가 부족합니다.", @@ -8254,6 +8259,7 @@ "steam_game_bans": "게임 차단", "permanent": "영구", "active": "활성", + "active_list": "활성: {list}", "expired": "만료됨", "on_record": "기록됨", "cannot_edit_expired": "만료된 제재는 수정할 수 없습니다 — 새로 추가하세요", @@ -10425,5 +10431,128 @@ "idle": "설정 안 됨", "check_again": "다시 확인", "setup_guide": "설정 가이드" + }, + "community": { + "this_week": "이번 주", + "all_time": "전체 기간", + "online": "온라인", + "online_now": "현재 온라인", + "now": "지금", + "last_seen": "마지막 접속", + "sessions": "세션", + "time_played": "플레이 시간", + "time": "시간", + "kills": "킬", + "rank": "순위", + "server": "서버", + "actions": "작업", + "format": { + "hours_minutes": "{hours}시간 {minutes}분", + "minutes": "{minutes}분", + "seconds": "{seconds}초", + "hours": "{hours}시간", + "just_now": "방금", + "within_hour": "1시간 이내" + }, + "player": { + "steam_only": "Steam 전용", + "in_game": "게임 내 이름 \"{name}\"", + "more": "{name} 더 보기", + "sanction_history": "제재 기록" + }, + "ip": { + "label": "IP", + "shared_now": "지금 이 서버에 함께 있는 플레이어: {names}", + "shared_week": "최근 7일 동안 같은 IP: {names}" + }, + "roster": { + "live": "실시간", + "live_description": "Player Management 플러그인이 보고합니다. 접속과 퇴장이 몇 초 안에 표시됩니다.", + "connected": "접속 시간", + "not_available": "없음", + "not_detected": "Player Management 플러그인이 감지되지 않았습니다. 플레이어 목록은 30초마다 RCON으로 읽으며, 기록은 저장되지 않습니다.", + "not_reporting": "Player Management 플러그인이 아직 플레이어를 보고하지 않아, 목록을 30초마다 RCON으로 읽으며 기록은 저장되지 않습니다." + }, + "recent": { + "title": "최근 플레이어", + "retention": "기록은 {days}일 동안 보관됩니다", + "unique_24h": "고유 · 24시간", + "unique_7d": "고유 · 7일", + "sessions_7d": "세션 · 7일", + "time_played_7d": "플레이 시간 · 7일", + "avg_session": "평균 세션", + "players": "명", + "joins": "회 접속", + "per_visit": "방문당", + "search": "이름, Steam ID 또는 IP 검색", + "window_24h": "24시간", + "window_7d": "7일", + "player": "플레이어", + "empty": "이 기간에 여기서 플레이한 사람이 없습니다.", + "no_matches": "검색과 일치하는 플레이어가 없습니다.", + "showing": "{total}명 중 {shown}명 표시", + "load_more": "더 불러오기" + }, + "player_tab": { + "moderator_note": "모더레이터는 정확한 시간, 세션, 사용한 이름, IP도 볼 수 있습니다.", + "public_note": "커뮤니티 서버에서의 시간과 킬입니다. 마지막으로 접속한 시점만 표시되며, 플레이한 시간대는 표시되지 않습니다.", + "avg_session": "세션당 평균 {duration}", + "kd": "K/D {ratio}", + "servers": "서버", + "community_servers": "커뮤니티 서버", + "servers_seen_on": "접속한 서버", + "names_used": "사용한 이름", + "show_sessions": "{server}의 세션 보기", + "sessions_last_7_days": "세션 · 최근 7일", + "moderators_only": "모더레이터 전용", + "no_recent_sessions": "최근 7일 동안 세션이 없습니다.", + "no_servers_this_week": "이번 주에는 커뮤니티 서버 플레이 기록이 없습니다.", + "also_on_ips": "같은 IP에서 확인된 계정", + "ip_matches_title": "최근 7일 동안 이 플레이어의 IP에서 확인된 다른 계정", + "ips_used": "이 플레이어가 사용한 IP:", + "ip_sessions": "{count}개 세션 | {count}개 세션", + "empty_title": "커뮤니티 세션 없음", + "empty_description": "이 플레이어는 아직 커뮤니티 서버에서 확인되지 않았습니다." + }, + "leaderboard": { + "title": "리더보드", + "by_time": "시간", + "by_kills": "킬", + "you": "나", + "empty_week": "이번 주에 5stack 계정으로 여기서 플레이한 사람이 없습니다.", + "empty_all": "아직 5stack 계정으로 여기서 플레이한 사람이 없습니다.", + "footnote": "5stack 계정이 있는 플레이어만 표시됩니다. 계정이 없는 플레이어도 위의 합계에는 포함됩니다. 시간은 접속 중에만, 킬은 이 서버에서만 집계됩니다.", + "sign_in_hint": "5stack에 로그인하고 여기서 플레이하면 표시됩니다." + }, + "server_page": { + "breadcrumb": "탐색 경로", + "online": "온라인", + "offline": "오프라인", + "map": "맵", + "community_server": "커뮤니티 서버", + "online_now": "현재 온라인", + "players_this_week": "이번 주 플레이어", + "different_players": "명의 플레이어", + "hours_this_week": "이번 주 시간", + "played_by_everyone": "전체 플레이 합계", + "all_time_players": "전체 기간 플레이어", + "since": "{date}부터", + "activity": "활동 · 최근 7일", + "activity_description": "모두가 매일 여기서 플레이한 총 시간입니다. 개인별 플레이 시간은 표시되지 않습니다.", + "chart_title": "{server}의 일별 플레이어 시간, 최근 7일", + "chart_column": "{date}: {hours} 플레이, 가장 붐빈 시간대 {players}명", + "today": "오늘", + "today_so_far": "{date}, 현재까지", + "so_far": "현재까지 {hours}", + "played": "플레이", + "peak_players": "명 (가장 붐빈 시간대)", + "busiest_hour": "가장 붐빈 시간대 플레이어", + "full_day": "하루 전체", + "today_partial": "오늘 현재까지", + "day": "날짜", + "player_hours": "플레이어 시간", + "not_found_title": "서버를 찾을 수 없음", + "not_found_description": "이 서버가 존재하지 않거나 공개되어 있지 않습니다." + } } } diff --git a/i18n/locales/pl_PL.json b/i18n/locales/pl_PL.json index 5212c94ac..e901a0dc4 100644 --- a/i18n/locales/pl_PL.json +++ b/i18n/locales/pl_PL.json @@ -2274,7 +2274,11 @@ "reserved_disk_space_fresh_gb_description": "Minimalna ilość miejsca na dysku w GB wymagana do świeżego provisioningu węzła (CS2 jeszcze niezainstalowane). Ustaw 0, aby wyłączyć.", "reserved_disk_space_existing_gb": "Wymagane miejsce na dysku — istniejąca instalacja (GB)", "reserved_disk_space_existing_gb_description": "Minimalna ilość miejsca na dysku w GB wymagana, gdy CS2 jest już zainstalowane na węźle. Ustaw 0, aby wyłączyć.", - "update_cpu_pinning": "Przypisywanie CPU zaktualizowane" + "update_cpu_pinning": "Przypisywanie CPU zaktualizowane", + "player_sessions_section": "Sesje Graczy na Serwerach Społeczności", + "player_sessions_description": "Serwery społeczności z wtyczką Player Management zapisują, kto grał, kiedy i z jakiego IP.", + "player_session_retention_days": "Przechowuj Historię Sesji (dni)", + "player_session_retention_days_description": "Jak długo przechowywane są dokładne sesje i adresy IP, od {min} do {max} dni. Starsze sesje są wliczane do sum łącznych." }, "highlights": { "title": "Highlighty", @@ -3147,7 +3151,8 @@ "elo": "ELO", "maps": "Mapy", "arsenal": "Arsenał", - "combat": "Walka" + "combat": "Walka", + "community": "Społeczność" }, "roles": { "no_data": "Za mało niedawnych meczów, by wykryć rolę", @@ -8254,6 +8259,7 @@ "steam_game_bans": "Bany w grze", "permanent": "Stała", "active": "Aktywna", + "active_list": "Aktywne: {list}", "expired": "Wygasła", "on_record": "W historii", "cannot_edit_expired": "Wygasłych sankcji nie można edytować — dodaj nową", @@ -10425,5 +10431,128 @@ "idle": "Nieskonfigurowane", "check_again": "Sprawdź ponownie", "setup_guide": "Instrukcja konfiguracji" + }, + "community": { + "this_week": "Ten Tydzień", + "all_time": "Od Początku", + "online": "Online", + "online_now": "Teraz online", + "now": "teraz", + "last_seen": "Ostatnio Widziany", + "sessions": "Sesje", + "time_played": "Czas Gry", + "time": "Czas", + "kills": "Zabójstwa", + "rank": "Miejsce", + "server": "Serwer", + "actions": "Akcje", + "format": { + "hours_minutes": "{hours} godz. {minutes} min", + "minutes": "{minutes} min", + "seconds": "{seconds} s", + "hours": "{hours} godz.", + "just_now": "przed chwilą", + "within_hour": "w ciągu ostatniej godziny" + }, + "player": { + "steam_only": "Tylko Steam", + "in_game": "w grze „{name}”", + "more": "Więcej o {name}", + "sanction_history": "Historia Sankcji" + }, + "ip": { + "label": "IP", + "shared_now": "Również teraz na tym serwerze: {names}", + "shared_week": "To samo IP w ostatnich 7 dniach: {names}" + }, + "roster": { + "live": "Na żywo", + "live_description": "Zgłaszane przez wtyczkę Player Management. Wejścia i wyjścia pojawiają się w ciągu kilku sekund.", + "connected": "Połączony", + "not_available": "b/d", + "not_detected": "Nie wykryto wtyczki Player Management. Lista graczy jest odczytywana przez RCON co 30 sekund i nie jest zapisywana żadna historia.", + "not_reporting": "Wtyczka Player Management nie zgłasza jeszcze graczy, więc lista jest odczytywana przez RCON co 30 sekund i nie jest zapisywana żadna historia." + }, + "recent": { + "title": "Ostatni Gracze", + "retention": "Historia przechowywana przez {days} dni", + "unique_24h": "Unikalni · 24 h", + "unique_7d": "Unikalni · 7 d", + "sessions_7d": "Sesje · 7 d", + "time_played_7d": "Czas Gry · 7 d", + "avg_session": "Średnia Sesja", + "players": "graczy", + "joins": "wejść", + "per_visit": "na wizytę", + "search": "Szukaj nazwy, Steam ID lub IP", + "window_24h": "24 H", + "window_7d": "7 D", + "player": "Gracz", + "empty": "Nikt tu nie grał w tym okresie.", + "no_matches": "Żaden gracz nie pasuje do wyszukiwania.", + "showing": "Wyświetlono {shown} z {total}", + "load_more": "Wczytaj Więcej" + }, + "player_tab": { + "moderator_note": "Moderatorzy widzą też dokładne godziny, sesje, używane nazwy i adresy IP.", + "public_note": "Czas i zabójstwa na serwerach społeczności. Widać tylko, kiedy gracz był ostatnio widziany, nigdy kiedy grał.", + "avg_session": "średnio {duration} na sesję", + "kd": "{ratio} K/D", + "servers": "Serwery", + "community_servers": "serwery społeczności", + "servers_seen_on": "Serwery, na Których Był Widziany", + "names_used": "Używane Nazwy", + "show_sessions": "Pokaż sesje na {server}", + "sessions_last_7_days": "Sesje · ostatnie 7 dni", + "moderators_only": "Tylko moderatorzy", + "no_recent_sessions": "Brak sesji w ostatnich 7 dniach.", + "no_servers_this_week": "Brak czasu na serwerach społeczności w tym tygodniu.", + "also_on_ips": "Widziani Też na Jego IP", + "ip_matches_title": "Inne konta widziane na adresach IP tego gracza w ostatnich 7 dniach", + "ips_used": "Adresy IP używane przez gracza:", + "ip_sessions": "{count} sesja | {count} sesji", + "empty_title": "Brak Sesji w Społeczności", + "empty_description": "Tego gracza nie widziano jeszcze na żadnym serwerze społeczności." + }, + "leaderboard": { + "title": "Ranking", + "by_time": "Czas", + "by_kills": "Zabójstwa", + "you": "Ty", + "empty_week": "W tym tygodniu nikt z kontem 5stack tu nie grał.", + "empty_all": "Nikt z kontem 5stack jeszcze tu nie grał.", + "footnote": "Lista graczy z kontem 5stack. Gracze bez konta nadal liczą się do powyższych sum. Czas liczy się podczas połączenia; zabójstwa liczą się tylko na tym serwerze.", + "sign_in_hint": "Zaloguj się do 5stack i zagraj tutaj, aby się pojawić." + }, + "server_page": { + "breadcrumb": "Ścieżka nawigacji", + "online": "Online", + "offline": "Offline", + "map": "mapa", + "community_server": "serwer społeczności", + "online_now": "Teraz Online", + "players_this_week": "Gracze w Tym Tygodniu", + "different_players": "różnych graczy", + "hours_this_week": "Godziny w Tym Tygodniu", + "played_by_everyone": "rozegrane przez wszystkich", + "all_time_players": "Gracze Łącznie", + "since": "od {date}", + "activity": "Aktywność · Ostatnie 7 Dni", + "activity_description": "Łączna liczba godzin rozegranych tu każdego dnia przez wszystkich. Indywidualne czasy gry nie są pokazywane.", + "chart_title": "Godziny graczy dziennie na {server}, ostatnie 7 dni", + "chart_column": "{date}: rozegrano {hours}, {players} graczy w najbardziej ruchliwej godzinie", + "today": "Dziś", + "today_so_far": "{date}, jak dotąd", + "so_far": "{hours} jak dotąd", + "played": "rozegrano", + "peak_players": "graczy w najbardziej ruchliwej godzinie", + "busiest_hour": "Gracze w Najbardziej Ruchliwej Godzinie", + "full_day": "Cały dzień", + "today_partial": "Dziś jak dotąd", + "day": "Dzień", + "player_hours": "Godziny Graczy", + "not_found_title": "Nie Znaleziono Serwera", + "not_found_description": "Ten serwer nie istnieje lub nie jest publiczny." + } } } diff --git a/i18n/locales/pt_BR.json b/i18n/locales/pt_BR.json index e0f176738..be5bbcb37 100644 --- a/i18n/locales/pt_BR.json +++ b/i18n/locales/pt_BR.json @@ -2274,7 +2274,11 @@ "reserved_disk_space_fresh_gb_description": "Espaço mínimo em disco (GB) exigido para provisionamento de um novo nó (CS2 ainda não instalado). Defina como 0 para desativar.", "reserved_disk_space_existing_gb": "Espaço em Disco Necessário - Instalação Existente (GB)", "reserved_disk_space_existing_gb_description": "Espaço mínimo em disco (GB) exigido quando o CS2 já está instalado no nó. Defina como 0 para desativar.", - "update_cpu_pinning": "CPU Pinning atualizado" + "update_cpu_pinning": "CPU Pinning atualizado", + "player_sessions_section": "Sessões de Jogadores da Comunidade", + "player_sessions_description": "Servidores da comunidade com o plugin Player Management registram quem jogou, quando e de qual IP.", + "player_session_retention_days": "Manter Histórico de Sessões (dias)", + "player_session_retention_days_description": "Por quanto tempo sessões exatas e IPs são mantidos, entre {min} e {max} dias. Sessões mais antigas são somadas aos totais gerais." }, "highlights": { "title": "Highlights", @@ -3147,7 +3151,8 @@ "elo": "ELO", "maps": "Mapas", "arsenal": "Arsenal", - "combat": "Combate" + "combat": "Combate", + "community": "Comunidade" }, "roles": { "no_data": "Não há partidas recentes suficientes para detectar uma função.", @@ -8254,6 +8259,7 @@ "steam_game_bans": "Bans Game na Steam", "permanent": "Permanente", "active": "Ativo", + "active_list": "Ativas: {list}", "expired": "Expirado", "on_record": "No histórico", "cannot_edit_expired": "Sanções expiradas não podem ser editadas — adicione uma nova", @@ -10425,5 +10431,128 @@ "idle": "Não configurado", "check_again": "Verificar de novo", "setup_guide": "Guia de configuração" + }, + "community": { + "this_week": "Esta Semana", + "all_time": "Desde Sempre", + "online": "Online", + "online_now": "Online agora", + "now": "agora", + "last_seen": "Visto Por Último", + "sessions": "Sessões", + "time_played": "Tempo Jogado", + "time": "Tempo", + "kills": "Abates", + "rank": "Posição", + "server": "Servidor", + "actions": "Ações", + "format": { + "hours_minutes": "{hours} h {minutes} min", + "minutes": "{minutes} min", + "seconds": "{seconds} s", + "hours": "{hours} h", + "just_now": "agora mesmo", + "within_hour": "na última hora" + }, + "player": { + "steam_only": "Só Steam", + "in_game": "no jogo “{name}”", + "more": "Mais sobre {name}", + "sanction_history": "Histórico de Punições" + }, + "ip": { + "label": "IP", + "shared_now": "Também neste servidor agora: {names}", + "shared_week": "Mesmo IP nos últimos 7 dias: {names}" + }, + "roster": { + "live": "Ao vivo", + "live_description": "Informado pelo plugin Player Management. Entradas e saídas aparecem em segundos.", + "connected": "Conectado", + "not_available": "n/d", + "not_detected": "Plugin Player Management não detectado. A lista de jogadores é lida via RCON a cada 30 segundos e nenhum histórico é registrado.", + "not_reporting": "O plugin Player Management ainda não informa os jogadores, então a lista é lida via RCON a cada 30 segundos e nenhum histórico é registrado." + }, + "recent": { + "title": "Jogadores Recentes", + "retention": "Histórico mantido por {days} dias", + "unique_24h": "Únicos · 24 h", + "unique_7d": "Únicos · 7 d", + "sessions_7d": "Sessões · 7 d", + "time_played_7d": "Tempo Jogado · 7 d", + "avg_session": "Sessão Média", + "players": "jogadores", + "joins": "entradas", + "per_visit": "por visita", + "search": "Buscar nome, Steam ID ou IP", + "window_24h": "24 H", + "window_7d": "7 D", + "player": "Jogador", + "empty": "Ninguém jogou aqui neste período.", + "no_matches": "Nenhum jogador corresponde a essa busca.", + "showing": "Mostrando {shown} de {total}", + "load_more": "Carregar Mais" + }, + "player_tab": { + "moderator_note": "Moderadores também veem horários exatos, sessões, nomes usados e IPs.", + "public_note": "Tempo e abates em servidores da comunidade. Só é mostrado quando foi visto por último, nunca quando jogou.", + "avg_session": "média de {duration} por sessão", + "kd": "{ratio} K/D", + "servers": "Servidores", + "community_servers": "servidores da comunidade", + "servers_seen_on": "Servidores Onde Foi Visto", + "names_used": "Nomes Usados", + "show_sessions": "Mostrar sessões em {server}", + "sessions_last_7_days": "Sessões · últimos 7 dias", + "moderators_only": "Somente moderadores", + "no_recent_sessions": "Nenhuma sessão nos últimos 7 dias.", + "no_servers_this_week": "Nenhum tempo em servidores da comunidade nesta semana.", + "also_on_ips": "Também Vistos nos IPs Dele", + "ip_matches_title": "Outras contas vistas nos IPs deste jogador nos últimos 7 dias", + "ips_used": "IPs usados por este jogador:", + "ip_sessions": "{count} sessão | {count} sessões", + "empty_title": "Nenhuma Sessão na Comunidade", + "empty_description": "Este jogador ainda não foi visto em nenhum servidor da comunidade." + }, + "leaderboard": { + "title": "Ranking", + "by_time": "Tempo", + "by_kills": "Abates", + "you": "Você", + "empty_week": "Ninguém com conta 5stack jogou aqui nesta semana.", + "empty_all": "Ninguém com conta 5stack jogou aqui ainda.", + "footnote": "Lista jogadores com conta 5stack. Jogadores sem conta ainda contam nos totais acima. O tempo conta enquanto você está conectado; os abates contam só neste servidor.", + "sign_in_hint": "Entre no 5stack e jogue aqui para aparecer." + }, + "server_page": { + "breadcrumb": "Trilha de navegação", + "online": "Online", + "offline": "Offline", + "map": "mapa", + "community_server": "servidor da comunidade", + "online_now": "Online Agora", + "players_this_week": "Jogadores Nesta Semana", + "different_players": "jogadores diferentes", + "hours_this_week": "Horas Nesta Semana", + "played_by_everyone": "jogadas por todos", + "all_time_players": "Jogadores no Total", + "since": "desde {date}", + "activity": "Atividade · Últimos 7 Dias", + "activity_description": "Total de horas que todos jogaram aqui a cada dia. Nenhum tempo de jogo individual é mostrado.", + "chart_title": "Horas de jogadores por dia em {server}, últimos 7 dias", + "chart_column": "{date}: {hours} jogadas, {players} jogadores no horário mais movimentado", + "today": "Hoje", + "today_so_far": "{date}, até agora", + "so_far": "{hours} até agora", + "played": "jogadas", + "peak_players": "jogadores no horário mais movimentado", + "busiest_hour": "Jogadores no Horário Mais Movimentado", + "full_day": "Dia inteiro", + "today_partial": "Hoje até agora", + "day": "Dia", + "player_hours": "Horas de Jogadores", + "not_found_title": "Servidor Não Encontrado", + "not_found_description": "Este servidor não existe ou não é público." + } } } diff --git a/i18n/locales/ru_RU.json b/i18n/locales/ru_RU.json index 779c3cebf..ba2752c3b 100644 --- a/i18n/locales/ru_RU.json +++ b/i18n/locales/ru_RU.json @@ -2274,7 +2274,11 @@ "reserved_disk_space_fresh_gb_description": "Минимальное место на диске в ГБ, требуемое для развёртывания новой ноды (CS2 ещё не установлен). Установи 0, чтобы отключить.", "reserved_disk_space_existing_gb": "Требуемое место на диске — существующая установка (ГБ)", "reserved_disk_space_existing_gb_description": "Минимальное место на диске в ГБ, требуемое, когда CS2 уже установлен на ноде. Установи 0, чтобы отключить.", - "update_cpu_pinning": "Привязка CPU обновлена" + "update_cpu_pinning": "Привязка CPU обновлена", + "player_sessions_section": "Сессии Игроков на Серверах Сообщества", + "player_sessions_description": "Серверы сообщества с плагином Player Management записывают, кто играл, когда и с какого IP.", + "player_session_retention_days": "Хранить Историю Сессий (дней)", + "player_session_retention_days_description": "Как долго хранятся точные сессии и IP: от {min} до {max} дней. Более старые сессии добавляются к итогам за всё время." }, "highlights": { "title": "Хайлайты", @@ -3147,7 +3151,8 @@ "elo": "ELO", "maps": "Карты", "arsenal": "Оружие", - "combat": "Бой" + "combat": "Бой", + "community": "Сообщество" }, "roles": { "no_data": "Недостаточно недавних матчей, чтобы определить роль.", @@ -8254,6 +8259,7 @@ "steam_game_bans": "Игровые баны", "permanent": "Навсегда", "active": "Активно", + "active_list": "Активны: {list}", "expired": "Истекло", "on_record": "В истории", "cannot_edit_expired": "Истёкшие наказания нельзя редактировать — добавь новое", @@ -10425,5 +10431,128 @@ "idle": "Не настроено", "check_again": "Проверить снова", "setup_guide": "Инструкция по настройке" + }, + "community": { + "this_week": "Эта Неделя", + "all_time": "За Всё Время", + "online": "В сети", + "online_now": "Сейчас в сети", + "now": "сейчас", + "last_seen": "Последний Визит", + "sessions": "Сессии", + "time_played": "Время в Игре", + "time": "Время", + "kills": "Убийства", + "rank": "Место", + "server": "Сервер", + "actions": "Действия", + "format": { + "hours_minutes": "{hours} ч {minutes} мин", + "minutes": "{minutes} мин", + "seconds": "{seconds} с", + "hours": "{hours} ч", + "just_now": "только что", + "within_hour": "в течение часа" + }, + "player": { + "steam_only": "Только Steam", + "in_game": "в игре «{name}»", + "more": "Ещё о {name}", + "sanction_history": "История Наказаний" + }, + "ip": { + "label": "IP", + "shared_now": "Сейчас также на этом сервере: {names}", + "shared_week": "Тот же IP за последние 7 дней: {names}" + }, + "roster": { + "live": "В эфире", + "live_description": "Данные от плагина Player Management. Входы и выходы появляются за считаные секунды.", + "connected": "В игре", + "not_available": "н/д", + "not_detected": "Плагин Player Management не обнаружен. Список игроков считывается через RCON каждые 30 секунд, история не сохраняется.", + "not_reporting": "Плагин Player Management пока не сообщает об игроках, поэтому список считывается через RCON каждые 30 секунд, история не сохраняется." + }, + "recent": { + "title": "Недавние Игроки", + "retention": "История хранится {days} дн.", + "unique_24h": "Уникальные · 24 ч", + "unique_7d": "Уникальные · 7 д", + "sessions_7d": "Сессии · 7 д", + "time_played_7d": "Время в Игре · 7 д", + "avg_session": "Средняя Сессия", + "players": "игроков", + "joins": "входов", + "per_visit": "за визит", + "search": "Поиск по имени, Steam ID или IP", + "window_24h": "24 Ч", + "window_7d": "7 Д", + "player": "Игрок", + "empty": "За этот период здесь никто не играл.", + "no_matches": "Нет игроков по этому запросу.", + "showing": "Показано {shown} из {total}", + "load_more": "Загрузить Ещё" + }, + "player_tab": { + "moderator_note": "Модераторы также видят точное время, сессии, использованные имена и IP.", + "public_note": "Время и убийства на серверах сообщества. Показывается только, когда игрока видели в последний раз, но не когда он играл.", + "avg_session": "в среднем {duration} за сессию", + "kd": "{ratio} K/D", + "servers": "Серверы", + "community_servers": "серверов сообщества", + "servers_seen_on": "Где Был Замечен", + "names_used": "Использованные Имена", + "show_sessions": "Показать сессии на {server}", + "sessions_last_7_days": "Сессии · последние 7 дней", + "moderators_only": "Только для модераторов", + "no_recent_sessions": "Нет сессий за последние 7 дней.", + "no_servers_this_week": "На этой неделе не играл на серверах сообщества.", + "also_on_ips": "Также Замечены на Его IP", + "ip_matches_title": "Другие аккаунты, замеченные на IP этого игрока за последние 7 дней", + "ips_used": "IP, которые использовал игрок:", + "ip_sessions": "{count} сессия | {count} сессий", + "empty_title": "Нет Сессий в Сообществе", + "empty_description": "Этого игрока ещё не видели ни на одном сервере сообщества." + }, + "leaderboard": { + "title": "Таблица Лидеров", + "by_time": "Время", + "by_kills": "Убийства", + "you": "Вы", + "empty_week": "На этой неделе здесь не играл никто с аккаунтом 5stack.", + "empty_all": "Здесь ещё не играл никто с аккаунтом 5stack.", + "footnote": "В списке игроки с аккаунтом 5stack. Игроки без аккаунта всё равно учитываются в итогах выше. Время считается, пока вы подключены; убийства считаются только на этом сервере.", + "sign_in_hint": "Войдите в 5stack и играйте здесь, чтобы попасть в список." + }, + "server_page": { + "breadcrumb": "Навигационная цепочка", + "online": "В сети", + "offline": "Не в сети", + "map": "карта", + "community_server": "сервер сообщества", + "online_now": "Сейчас в Сети", + "players_this_week": "Игроков за Неделю", + "different_players": "разных игроков", + "hours_this_week": "Часов за Неделю", + "played_by_everyone": "сыграно всеми", + "all_time_players": "Игроков за Всё Время", + "since": "с {date}", + "activity": "Активность · Последние 7 Дней", + "activity_description": "Сколько часов все сыграли здесь за каждый день. Индивидуальное время игры не показывается.", + "chart_title": "Игроко-часы по дням на {server}, последние 7 дней", + "chart_column": "{date}: сыграно {hours}, {players} игроков в самый активный час", + "today": "Сегодня", + "today_so_far": "{date}, пока что", + "so_far": "{hours} пока что", + "played": "сыграно", + "peak_players": "игроков в самый активный час", + "busiest_hour": "Игроков в Самый Активный Час", + "full_day": "Полный день", + "today_partial": "Сегодня, пока что", + "day": "День", + "player_hours": "Игроко-Часы", + "not_found_title": "Сервер Не Найден", + "not_found_description": "Этот сервер не существует или не является публичным." + } } } diff --git a/i18n/locales/sv_SE.json b/i18n/locales/sv_SE.json index 4e61a6a4a..25cfc0798 100644 --- a/i18n/locales/sv_SE.json +++ b/i18n/locales/sv_SE.json @@ -2274,7 +2274,11 @@ "reserved_disk_space_fresh_gb_description": "Minsta diskutrymme i GB som krävs för nyetablering av en nod (CS2 ännu ej installerat). Sätt till 0 för att inaktivera.", "reserved_disk_space_existing_gb": "Nödvändigt diskutrymme - befintlig installation (GB)", "reserved_disk_space_existing_gb_description": "Minsta diskutrymme i GB som krävs när CS2 redan är installerat på noden. Sätt till 0 för att inaktivera.", - "update_cpu_pinning": "CPU-fixering uppdaterad" + "update_cpu_pinning": "CPU-fixering uppdaterad", + "player_sessions_section": "Spelarsessioner på Communityservrar", + "player_sessions_description": "Communityservrar med Player Management-pluginet registrerar vem som spelade, när och från vilken IP.", + "player_session_retention_days": "Spara Sessionshistorik (dagar)", + "player_session_retention_days_description": "Hur länge exakta sessioner och IP-adresser sparas, mellan {min} och {max} dagar. Äldre sessioner slås ihop med totalerna." }, "highlights": { "title": "Höjdpunkter", @@ -3147,7 +3151,8 @@ "elo": "ELO", "maps": "Kartor", "arsenal": "Arsenal", - "combat": "Strid" + "combat": "Strid", + "community": "Community" }, "roles": { "no_data": "Inte tillräckligt med senaste matcher för att avgöra en roll.", @@ -8254,6 +8259,7 @@ "steam_game_bans": "Spelavstängningar", "permanent": "Permanent", "active": "Aktiv", + "active_list": "Aktiva: {list}", "expired": "Upphört", "on_record": "Registrerad", "cannot_edit_expired": "Utgångna sanktioner kan inte redigeras — lägg till en ny", @@ -10425,5 +10431,128 @@ "idle": "Inte inställt", "check_again": "Kontrollera igen", "setup_guide": "Installationsguide" + }, + "community": { + "this_week": "Den Här Veckan", + "all_time": "Totalt", + "online": "Online", + "online_now": "Online nu", + "now": "nu", + "last_seen": "Senast Sedd", + "sessions": "Sessioner", + "time_played": "Speltid", + "time": "Tid", + "kills": "Kills", + "rank": "Placering", + "server": "Server", + "actions": "Åtgärder", + "format": { + "hours_minutes": "{hours} h {minutes} min", + "minutes": "{minutes} min", + "seconds": "{seconds} s", + "hours": "{hours} h", + "just_now": "just nu", + "within_hour": "inom den senaste timmen" + }, + "player": { + "steam_only": "Endast Steam", + "in_game": "i spelet ”{name}”", + "more": "Mer om {name}", + "sanction_history": "Sanktionshistorik" + }, + "ip": { + "label": "IP", + "shared_now": "Också på den här servern nu: {names}", + "shared_week": "Samma IP de senaste 7 dagarna: {names}" + }, + "roster": { + "live": "Live", + "live_description": "Rapporteras av Player Management-pluginet. Anslutningar och avhopp syns inom några sekunder.", + "connected": "Ansluten", + "not_available": "saknas", + "not_detected": "Player Management-pluginet hittades inte. Spelarlistan läses via RCON var 30:e sekund och ingen historik sparas.", + "not_reporting": "Player Management-pluginet rapporterar inga spelare än, så spelarlistan läses via RCON var 30:e sekund och ingen historik sparas." + }, + "recent": { + "title": "Senaste Spelare", + "retention": "Historik sparas i {days} dagar", + "unique_24h": "Unika · 24 h", + "unique_7d": "Unika · 7 d", + "sessions_7d": "Sessioner · 7 d", + "time_played_7d": "Speltid · 7 d", + "avg_session": "Snittsession", + "players": "spelare", + "joins": "anslutningar", + "per_visit": "per besök", + "search": "Sök namn, Steam-ID eller IP", + "window_24h": "24 H", + "window_7d": "7 D", + "player": "Spelare", + "empty": "Ingen har spelat här under den här perioden.", + "no_matches": "Inga spelare matchar sökningen.", + "showing": "Visar {shown} av {total}", + "load_more": "Ladda Fler" + }, + "player_tab": { + "moderator_note": "Moderatorer ser även exakta tider, sessioner, använda namn och IP-adresser.", + "public_note": "Tid och kills på communityservrar. Bara när spelaren senast sågs visas, aldrig när hen spelade.", + "avg_session": "snitt {duration} per session", + "kd": "{ratio} K/D", + "servers": "Servrar", + "community_servers": "communityservrar", + "servers_seen_on": "Servrar Där Spelaren Setts", + "names_used": "Använda Namn", + "show_sessions": "Visa sessioner på {server}", + "sessions_last_7_days": "Sessioner · senaste 7 dagarna", + "moderators_only": "Endast moderatorer", + "no_recent_sessions": "Inga sessioner de senaste 7 dagarna.", + "no_servers_this_week": "Ingen tid på communityservrar den här veckan.", + "also_on_ips": "Även Sedda på Deras IP-Adresser", + "ip_matches_title": "Andra konton som setts på den här spelarens IP-adresser de senaste 7 dagarna", + "ips_used": "IP-adresser som spelaren använt:", + "ip_sessions": "{count} session | {count} sessioner", + "empty_title": "Inga Communitysessioner", + "empty_description": "Den här spelaren har inte setts på någon communityserver än." + }, + "leaderboard": { + "title": "Topplista", + "by_time": "Tid", + "by_kills": "Kills", + "you": "Du", + "empty_week": "Ingen med ett 5stack-konto har spelat här den här veckan.", + "empty_all": "Ingen med ett 5stack-konto har spelat här än.", + "footnote": "Visar spelare med ett 5stack-konto. Spelare utan konto räknas ändå med i totalerna ovan. Tid räknas medan man är ansluten; kills räknas bara på den här servern.", + "sign_in_hint": "Logga in på 5stack och spela här för att synas." + }, + "server_page": { + "breadcrumb": "Brödsmulor", + "online": "Online", + "offline": "Offline", + "map": "karta", + "community_server": "communityserver", + "online_now": "Online Nu", + "players_this_week": "Spelare Den Här Veckan", + "different_players": "olika spelare", + "hours_this_week": "Timmar Den Här Veckan", + "played_by_everyone": "spelade av alla", + "all_time_players": "Spelare Totalt", + "since": "sedan {date}", + "activity": "Aktivitet · Senaste 7 Dagarna", + "activity_description": "Totalt antal timmar som alla spelade här varje dag. Ingens individuella speltider visas.", + "chart_title": "Spelartimmar per dag på {server}, senaste 7 dagarna", + "chart_column": "{date}: {hours} spelat, {players} spelare under den mest aktiva timmen", + "today": "Idag", + "today_so_far": "{date}, hittills", + "so_far": "{hours} hittills", + "played": "spelat", + "peak_players": "spelare under den mest aktiva timmen", + "busiest_hour": "Spelare Under Mest Aktiva Timmen", + "full_day": "Hel dag", + "today_partial": "Idag hittills", + "day": "Dag", + "player_hours": "Spelartimmar", + "not_found_title": "Servern Hittades Inte", + "not_found_description": "Den här servern finns inte eller är inte offentlig." + } } } diff --git a/i18n/locales/tr_TR.json b/i18n/locales/tr_TR.json index fc31eaa21..80e7e3d96 100644 --- a/i18n/locales/tr_TR.json +++ b/i18n/locales/tr_TR.json @@ -2274,7 +2274,11 @@ "reserved_disk_space_fresh_gb_description": "Yeni bir düğüm kurulumu için (henüz CS2 yüklenmemiş) gereken minimum disk alanı (GB). Devre dışı bırakmak için 0 gir.", "reserved_disk_space_existing_gb": "Gerekli disk alanı - mevcut kurulum (GB)", "reserved_disk_space_existing_gb_description": "Düğümde CS2 zaten yüklüyken gereken minimum disk alanı (GB). Devre dışı bırakmak için 0 gir.", - "update_cpu_pinning": "CPU sabitleme güncellendi" + "update_cpu_pinning": "CPU sabitleme güncellendi", + "player_sessions_section": "Topluluk Oyuncu Oturumları", + "player_sessions_description": "Player Management eklentisini çalıştıran topluluk sunucuları kimin, ne zaman ve hangi IP'den oynadığını kaydeder.", + "player_session_retention_days": "Oturum Geçmişini Sakla (gün)", + "player_session_retention_days_description": "Kesin oturumların ve IP'lerin ne kadar saklanacağı, {min} ile {max} gün arasında. Daha eski oturumlar tüm zamanların toplamlarına eklenir." }, "highlights": { "title": "Öne çıkan anlar", @@ -3147,7 +3151,8 @@ "elo": "ELO", "maps": "Haritalar", "arsenal": "Silahlar", - "combat": "Çatışma" + "combat": "Çatışma", + "community": "Topluluk" }, "roles": { "no_data": "Rol belirlemek için yeterli sayıda yakın zamanlı maç yok.", @@ -8254,6 +8259,7 @@ "steam_game_bans": "Oyun yasakları", "permanent": "Kalıcı", "active": "Aktif", + "active_list": "Etkin: {list}", "expired": "Süresi doldu", "on_record": "Sicilde", "cannot_edit_expired": "Süresi dolmuş yaptırımlar düzenlenemez — yeni bir tane ekle", @@ -10425,5 +10431,128 @@ "idle": "Kurulmadı", "check_again": "Tekrar kontrol et", "setup_guide": "Kurulum kılavuzu" + }, + "community": { + "this_week": "Bu Hafta", + "all_time": "Tüm Zamanlar", + "online": "Çevrimiçi", + "online_now": "Şu an çevrimiçi", + "now": "şimdi", + "last_seen": "Son Görülme", + "sessions": "Oturumlar", + "time_played": "Oynama Süresi", + "time": "Süre", + "kills": "Leşler", + "rank": "Sıra", + "server": "Sunucu", + "actions": "İşlemler", + "format": { + "hours_minutes": "{hours} sa {minutes} dk", + "minutes": "{minutes} dk", + "seconds": "{seconds} sn", + "hours": "{hours} sa", + "just_now": "az önce", + "within_hour": "son bir saat içinde" + }, + "player": { + "steam_only": "Yalnızca Steam", + "in_game": "oyun içi \"{name}\"", + "more": "{name} için diğer", + "sanction_history": "Yaptırım Geçmişi" + }, + "ip": { + "label": "IP", + "shared_now": "Şu an bu sunucuda da: {names}", + "shared_week": "Son 7 günde aynı IP: {names}" + }, + "roster": { + "live": "Canlı", + "live_description": "Player Management eklentisi tarafından bildirilir. Girişler ve çıkışlar saniyeler içinde görünür.", + "connected": "Bağlı", + "not_available": "yok", + "not_detected": "Player Management eklentisi algılanmadı. Oyuncu listesi her 30 saniyede bir RCON üzerinden okunur ve geçmiş kaydedilmez.", + "not_reporting": "Player Management eklentisi henüz oyuncuları bildirmiyor, bu yüzden liste her 30 saniyede bir RCON üzerinden okunur ve geçmiş kaydedilmez." + }, + "recent": { + "title": "Son Oyuncular", + "retention": "Geçmiş {days} gün saklanır", + "unique_24h": "Tekil · 24 sa", + "unique_7d": "Tekil · 7 g", + "sessions_7d": "Oturumlar · 7 g", + "time_played_7d": "Oynama Süresi · 7 g", + "avg_session": "Ort. Oturum", + "players": "oyuncu", + "joins": "giriş", + "per_visit": "ziyaret başına", + "search": "Ad, Steam ID veya IP ara", + "window_24h": "24 SA", + "window_7d": "7 G", + "player": "Oyuncu", + "empty": "Bu aralıkta burada kimse oynamadı.", + "no_matches": "Bu aramayla eşleşen oyuncu yok.", + "showing": "{total} oyuncudan {shown} tanesi gösteriliyor", + "load_more": "Daha Fazla Yükle" + }, + "player_tab": { + "moderator_note": "Moderatörler ayrıca kesin saatleri, oturumları, kullanılan adları ve IP'leri görür.", + "public_note": "Topluluk sunucularındaki süre ve leşler. Yalnızca en son ne zaman görüldüğü gösterilir, ne zaman oynadığı asla gösterilmez.", + "avg_session": "oturum başına ort. {duration}", + "kd": "{ratio} K/D", + "servers": "Sunucular", + "community_servers": "topluluk sunucusu", + "servers_seen_on": "Görüldüğü Sunucular", + "names_used": "Kullanılan Adlar", + "show_sessions": "{server} üzerindeki oturumları göster", + "sessions_last_7_days": "Oturumlar · son 7 gün", + "moderators_only": "Yalnızca moderatörler", + "no_recent_sessions": "Son 7 günde oturum yok.", + "no_servers_this_week": "Bu hafta topluluk sunucularında süre yok.", + "also_on_ips": "IP'lerinde Görülen Diğerleri", + "ip_matches_title": "Son 7 günde bu oyuncunun IP'lerinde görülen diğer hesaplar", + "ips_used": "Bu oyuncunun kullandığı IP'ler:", + "ip_sessions": "{count} oturum | {count} oturum", + "empty_title": "Topluluk Oturumu Yok", + "empty_description": "Bu oyuncu henüz bir topluluk sunucusunda görülmedi." + }, + "leaderboard": { + "title": "Lider Tablosu", + "by_time": "Süre", + "by_kills": "Leşler", + "you": "Sen", + "empty_week": "Bu hafta burada 5stack hesabı olan kimse oynamadı.", + "empty_all": "Burada henüz 5stack hesabı olan kimse oynamadı.", + "footnote": "5stack hesabı olan oyuncuları listeler. Hesabı olmayan oyuncular yine de yukarıdaki toplamlara dahildir. Süre bağlıyken sayılır; leşler yalnızca bu sunucuda sayılır.", + "sign_in_hint": "Listede görünmek için 5stack'e giriş yap ve burada oyna." + }, + "server_page": { + "breadcrumb": "Sayfa yolu", + "online": "Çevrimiçi", + "offline": "Çevrimdışı", + "map": "harita", + "community_server": "topluluk sunucusu", + "online_now": "Şu An Çevrimiçi", + "players_this_week": "Bu Haftaki Oyuncular", + "different_players": "farklı oyuncu", + "hours_this_week": "Bu Haftaki Saatler", + "played_by_everyone": "herkesin oynadığı", + "all_time_players": "Tüm Zamanların Oyuncuları", + "since": "{date} tarihinden beri", + "activity": "Etkinlik · Son 7 Gün", + "activity_description": "Herkesin burada her gün oynadığı toplam saat. Kimsenin bireysel oynama süresi gösterilmez.", + "chart_title": "{server} üzerinde günlük oyuncu saatleri, son 7 gün", + "chart_column": "{date}: {hours} oynandı, en yoğun saatte {players} oyuncu", + "today": "Bugün", + "today_so_far": "{date}, şu ana kadar", + "so_far": "şu ana kadar {hours}", + "played": "oynandı", + "peak_players": "oyuncu en yoğun saatte", + "busiest_hour": "En Yoğun Saatteki Oyuncular", + "full_day": "Tam gün", + "today_partial": "Bugün şu ana kadar", + "day": "Gün", + "player_hours": "Oyuncu Saatleri", + "not_found_title": "Sunucu Bulunamadı", + "not_found_description": "Bu sunucu yok veya herkese açık değil." + } } } diff --git a/i18n/locales/uk_UA.json b/i18n/locales/uk_UA.json index dd98d2603..8eb8cc36d 100644 --- a/i18n/locales/uk_UA.json +++ b/i18n/locales/uk_UA.json @@ -2274,7 +2274,11 @@ "reserved_disk_space_fresh_gb_description": "Мінімальний обсяг диска в ГБ, необхідний для чистого розгортання ноди (CS2 ще не встановлено). Встанови 0, щоб вимкнути.", "reserved_disk_space_existing_gb": "Необхідний обсяг диска — наявна установка (ГБ)", "reserved_disk_space_existing_gb_description": "Мінімальний обсяг диска в ГБ, необхідний, коли CS2 вже встановлено на ноді. Встанови 0, щоб вимкнути.", - "update_cpu_pinning": "Прив'язку CPU оновлено" + "update_cpu_pinning": "Прив'язку CPU оновлено", + "player_sessions_section": "Сесії Гравців на Серверах Спільноти", + "player_sessions_description": "Сервери спільноти з плагіном Player Management записують, хто грав, коли і з якого IP.", + "player_session_retention_days": "Зберігати Історію Сесій (днів)", + "player_session_retention_days_description": "Як довго зберігаються точні сесії та IP: від {min} до {max} днів. Старіші сесії додаються до підсумків за весь час." }, "highlights": { "title": "Хайлайти", @@ -3147,7 +3151,8 @@ "elo": "ELO", "maps": "Карти", "arsenal": "Арсенал", - "combat": "Бойові дії" + "combat": "Бойові дії", + "community": "Спільнота" }, "roles": { "no_data": "Замало недавніх матчів, щоб визначити роль.", @@ -8254,6 +8259,7 @@ "steam_game_bans": "Ігрові бани", "permanent": "Назавжди", "active": "Активне", + "active_list": "Активні: {list}", "expired": "Спливло", "on_record": "В історії", "cannot_edit_expired": "Прострочені покарання не можна редагувати — додай нове", @@ -10425,5 +10431,128 @@ "idle": "Не налаштовано", "check_again": "Перевірити знову", "setup_guide": "Інструкція з налаштування" + }, + "community": { + "this_week": "Цей Тиждень", + "all_time": "За Весь Час", + "online": "У мережі", + "online_now": "Зараз у мережі", + "now": "зараз", + "last_seen": "Останній Візит", + "sessions": "Сесії", + "time_played": "Час у Грі", + "time": "Час", + "kills": "Вбивства", + "rank": "Місце", + "server": "Сервер", + "actions": "Дії", + "format": { + "hours_minutes": "{hours} год {minutes} хв", + "minutes": "{minutes} хв", + "seconds": "{seconds} с", + "hours": "{hours} год", + "just_now": "щойно", + "within_hour": "протягом години" + }, + "player": { + "steam_only": "Лише Steam", + "in_game": "у грі «{name}»", + "more": "Більше про {name}", + "sanction_history": "Історія Покарань" + }, + "ip": { + "label": "IP", + "shared_now": "Зараз також на цьому сервері: {names}", + "shared_week": "Той самий IP за останні 7 днів: {names}" + }, + "roster": { + "live": "Наживо", + "live_description": "Дані від плагіна Player Management. Входи й виходи з'являються за лічені секунди.", + "connected": "У грі", + "not_available": "н/д", + "not_detected": "Плагін Player Management не виявлено. Список гравців зчитується через RCON кожні 30 секунд, історія не зберігається.", + "not_reporting": "Плагін Player Management поки не повідомляє про гравців, тому список зчитується через RCON кожні 30 секунд, історія не зберігається." + }, + "recent": { + "title": "Нещодавні Гравці", + "retention": "Історія зберігається {days} дн.", + "unique_24h": "Унікальні · 24 год", + "unique_7d": "Унікальні · 7 дн", + "sessions_7d": "Сесії · 7 дн", + "time_played_7d": "Час у Грі · 7 дн", + "avg_session": "Середня Сесія", + "players": "гравців", + "joins": "входів", + "per_visit": "за візит", + "search": "Пошук за іменем, Steam ID або IP", + "window_24h": "24 Г", + "window_7d": "7 Д", + "player": "Гравець", + "empty": "За цей період тут ніхто не грав.", + "no_matches": "Немає гравців за цим запитом.", + "showing": "Показано {shown} з {total}", + "load_more": "Завантажити Ще" + }, + "player_tab": { + "moderator_note": "Модератори також бачать точний час, сесії, використані імена та IP.", + "public_note": "Час і вбивства на серверах спільноти. Показується лише, коли гравця бачили востаннє, але не коли він грав.", + "avg_session": "у середньому {duration} за сесію", + "kd": "{ratio} K/D", + "servers": "Сервери", + "community_servers": "серверів спільноти", + "servers_seen_on": "Де Був Помічений", + "names_used": "Використані Імена", + "show_sessions": "Показати сесії на {server}", + "sessions_last_7_days": "Сесії · останні 7 днів", + "moderators_only": "Лише для модераторів", + "no_recent_sessions": "Немає сесій за останні 7 днів.", + "no_servers_this_week": "Цього тижня не грав на серверах спільноти.", + "also_on_ips": "Також Помічені на Його IP", + "ip_matches_title": "Інші акаунти, помічені на IP цього гравця за останні 7 днів", + "ips_used": "IP, які використовував гравець:", + "ip_sessions": "{count} сесія | {count} сесій", + "empty_title": "Немає Сесій у Спільноті", + "empty_description": "Цього гравця ще не бачили на жодному сервері спільноти." + }, + "leaderboard": { + "title": "Таблиця Лідерів", + "by_time": "Час", + "by_kills": "Вбивства", + "you": "Ви", + "empty_week": "Цього тижня тут не грав ніхто з акаунтом 5stack.", + "empty_all": "Тут ще не грав ніхто з акаунтом 5stack.", + "footnote": "У списку гравці з акаунтом 5stack. Гравці без акаунта все одно враховуються в підсумках вище. Час рахується, поки ви підключені; вбивства рахуються лише на цьому сервері.", + "sign_in_hint": "Увійдіть у 5stack і грайте тут, щоб потрапити до списку." + }, + "server_page": { + "breadcrumb": "Навігаційний ланцюжок", + "online": "У мережі", + "offline": "Не в мережі", + "map": "мапа", + "community_server": "сервер спільноти", + "online_now": "Зараз у Мережі", + "players_this_week": "Гравців за Тиждень", + "different_players": "різних гравців", + "hours_this_week": "Годин за Тиждень", + "played_by_everyone": "зіграно всіма", + "all_time_players": "Гравців за Весь Час", + "since": "з {date}", + "activity": "Активність · Останні 7 Днів", + "activity_description": "Скільки годин усі зіграли тут за кожен день. Індивідуальний час гри не показується.", + "chart_title": "Гравце-години за днями на {server}, останні 7 днів", + "chart_column": "{date}: зіграно {hours}, {players} гравців у найактивнішу годину", + "today": "Сьогодні", + "today_so_far": "{date}, поки що", + "so_far": "{hours} поки що", + "played": "зіграно", + "peak_players": "гравців у найактивнішу годину", + "busiest_hour": "Гравців у Найактивнішу Годину", + "full_day": "Повний день", + "today_partial": "Сьогодні, поки що", + "day": "День", + "player_hours": "Гравце-Години", + "not_found_title": "Сервер Не Знайдено", + "not_found_description": "Цей сервер не існує або не є публічним." + } } } diff --git a/i18n/locales/zh_Hans.json b/i18n/locales/zh_Hans.json index 19f4c8f6e..59f23ed10 100644 --- a/i18n/locales/zh_Hans.json +++ b/i18n/locales/zh_Hans.json @@ -2274,7 +2274,11 @@ "reserved_disk_space_fresh_gb_description": "全新节点配置(CS2 尚未安装)所需的最小磁盘空间(GB)。设为 0 以禁用。", "reserved_disk_space_existing_gb": "所需磁盘空间 - 已有安装 (GB)", "reserved_disk_space_existing_gb_description": "当节点上已安装 CS2 时所需的最小磁盘空间(GB)。设为 0 以禁用。", - "update_cpu_pinning": "CPU 亲和性已更新" + "update_cpu_pinning": "CPU 亲和性已更新", + "player_sessions_section": "社区玩家会话", + "player_sessions_description": "运行 Player Management 插件的社区服务器会记录谁在何时、从哪个 IP 游戏。", + "player_session_retention_days": "会话历史保留天数", + "player_session_retention_days_description": "准确会话和 IP 的保留时长,介于 {min} 到 {max} 天之间。更早的会话会并入历史总计。" }, "highlights": { "title": "高光时刻", @@ -3147,7 +3151,8 @@ "elo": "ELO", "maps": "地图", "arsenal": "武器库", - "combat": "战斗" + "combat": "战斗", + "community": "社区" }, "roles": { "no_data": "近期比赛不足,无法检测角色定位。", @@ -8254,6 +8259,7 @@ "steam_game_bans": "游戏封禁", "permanent": "永久", "active": "活跃", + "active_list": "生效中:{list}", "expired": "已过期", "on_record": "已记录", "cannot_edit_expired": "已过期的处罚无法编辑——请添加新的处罚", @@ -10425,5 +10431,128 @@ "idle": "未设置", "check_again": "重新检查", "setup_guide": "设置指南" + }, + "community": { + "this_week": "本周", + "all_time": "全部时间", + "online": "在线", + "online_now": "当前在线", + "now": "现在", + "last_seen": "最后在线", + "sessions": "会话", + "time_played": "游戏时长", + "time": "时长", + "kills": "击杀", + "rank": "排名", + "server": "服务器", + "actions": "操作", + "format": { + "hours_minutes": "{hours}小时{minutes}分", + "minutes": "{minutes}分钟", + "seconds": "{seconds}秒", + "hours": "{hours}小时", + "just_now": "刚刚", + "within_hour": "一小时内" + }, + "player": { + "steam_only": "仅 Steam", + "in_game": "游戏内名称“{name}”", + "more": "{name} 的更多操作", + "sanction_history": "处罚记录" + }, + "ip": { + "label": "IP", + "shared_now": "当前也在此服务器上:{names}", + "shared_week": "过去 7 天内相同 IP:{names}" + }, + "roster": { + "live": "实时", + "live_description": "由 Player Management 插件上报。加入和离开会在几秒内显示。", + "connected": "已连接", + "not_available": "无", + "not_detected": "未检测到 Player Management 插件。玩家列表每 30 秒通过 RCON 读取一次,不会记录历史。", + "not_reporting": "Player Management 插件尚未上报玩家,因此玩家列表每 30 秒通过 RCON 读取一次,不会记录历史。" + }, + "recent": { + "title": "最近玩家", + "retention": "历史记录保留 {days} 天", + "unique_24h": "独立玩家 · 24 小时", + "unique_7d": "独立玩家 · 7 天", + "sessions_7d": "会话 · 7 天", + "time_played_7d": "游戏时长 · 7 天", + "avg_session": "平均会话", + "players": "名玩家", + "joins": "次加入", + "per_visit": "每次访问", + "search": "搜索名称、Steam ID 或 IP", + "window_24h": "24 小时", + "window_7d": "7 天", + "player": "玩家", + "empty": "此时间段内没有人在这里玩过。", + "no_matches": "没有符合搜索条件的玩家。", + "showing": "显示 {shown} / {total}", + "load_more": "加载更多" + }, + "player_tab": { + "moderator_note": "版主还能看到准确时间、会话、使用过的名称和 IP。", + "public_note": "社区服务器上的游戏时长和击杀。只显示最后在线时间,从不显示具体的游戏时间段。", + "avg_session": "平均每次会话 {duration}", + "kd": "K/D {ratio}", + "servers": "服务器", + "community_servers": "个社区服务器", + "servers_seen_on": "出现过的服务器", + "names_used": "使用过的名称", + "show_sessions": "显示在 {server} 上的会话", + "sessions_last_7_days": "会话 · 过去 7 天", + "moderators_only": "仅限版主", + "no_recent_sessions": "过去 7 天内没有会话。", + "no_servers_this_week": "本周没有社区服务器游戏时长。", + "also_on_ips": "在其 IP 上出现的其他账号", + "ip_matches_title": "过去 7 天内在此玩家 IP 上出现的其他账号", + "ips_used": "此玩家使用过的 IP:", + "ip_sessions": "{count} 次会话 | {count} 次会话", + "empty_title": "没有社区会话", + "empty_description": "尚未在任何社区服务器上看到此玩家。" + }, + "leaderboard": { + "title": "排行榜", + "by_time": "时长", + "by_kills": "击杀", + "you": "你", + "empty_week": "本周还没有拥有 5stack 账号的玩家在这里玩过。", + "empty_all": "还没有拥有 5stack 账号的玩家在这里玩过。", + "footnote": "仅列出拥有 5stack 账号的玩家。没有账号的玩家仍计入上方的总数。时长按连接期间计算;击杀只统计本服务器。", + "sign_in_hint": "登录 5stack 并在这里游戏即可上榜。" + }, + "server_page": { + "breadcrumb": "面包屑导航", + "online": "在线", + "offline": "离线", + "map": "地图", + "community_server": "社区服务器", + "online_now": "当前在线", + "players_this_week": "本周玩家", + "different_players": "名不同玩家", + "hours_this_week": "本周时长", + "played_by_everyone": "所有人合计", + "all_time_players": "历史玩家", + "since": "自 {date} 起", + "activity": "活跃度 · 过去 7 天", + "activity_description": "每天所有人在这里游戏的总时长。不显示任何人的个人游戏时间。", + "chart_title": "{server} 每日玩家时长,过去 7 天", + "chart_column": "{date}:游戏 {hours},最繁忙时段 {players} 名玩家", + "today": "今天", + "today_so_far": "{date},截至目前", + "so_far": "截至目前 {hours}", + "played": "游戏", + "peak_players": "名玩家(最繁忙时段)", + "busiest_hour": "最繁忙时段玩家数", + "full_day": "全天", + "today_partial": "今天截至目前", + "day": "日期", + "player_hours": "玩家时长", + "not_found_title": "未找到服务器", + "not_found_description": "此服务器不存在或未公开。" + } } } diff --git a/i18n/locales/zh_Hant.json b/i18n/locales/zh_Hant.json index a2d542923..fd21ab046 100644 --- a/i18n/locales/zh_Hant.json +++ b/i18n/locales/zh_Hant.json @@ -2274,7 +2274,11 @@ "reserved_disk_space_fresh_gb_description": "全新節點佈建(CS2 尚未安裝)所需的最小磁碟空間(GB)。設為 0 以停用。", "reserved_disk_space_existing_gb": "所需磁碟空間 - 已有安裝 (GB)", "reserved_disk_space_existing_gb_description": "當節點上已安裝 CS2 時所需的最小磁碟空間(GB)。設為 0 以停用。", - "update_cpu_pinning": "CPU 綁定已更新" + "update_cpu_pinning": "CPU 綁定已更新", + "player_sessions_section": "社群玩家工作階段", + "player_sessions_description": "執行 Player Management 外掛的社群伺服器會記錄誰在何時、從哪個 IP 遊戲。", + "player_session_retention_days": "工作階段歷史保留天數", + "player_session_retention_days_description": "確切工作階段和 IP 的保留時間,介於 {min} 到 {max} 天之間。較舊的工作階段會併入歷來總計。" }, "highlights": { "title": "精華時刻", @@ -3147,7 +3151,8 @@ "elo": "ELO", "maps": "地圖", "arsenal": "武器庫", - "combat": "戰鬥" + "combat": "戰鬥", + "community": "社群" }, "roles": { "no_data": "近期比賽不足,無法判定角色定位。", @@ -8254,6 +8259,7 @@ "steam_game_bans": "遊戲封禁", "permanent": "永久", "active": "生效中", + "active_list": "生效中:{list}", "expired": "已到期", "on_record": "已記錄", "cannot_edit_expired": "已到期的處罰無法編輯——請新增一筆", @@ -10425,5 +10431,128 @@ "idle": "未設定", "check_again": "重新檢查", "setup_guide": "設定指南" + }, + "community": { + "this_week": "本週", + "all_time": "全部時間", + "online": "線上", + "online_now": "目前線上", + "now": "現在", + "last_seen": "最後上線", + "sessions": "工作階段", + "time_played": "遊戲時數", + "time": "時數", + "kills": "擊殺", + "rank": "排名", + "server": "伺服器", + "actions": "操作", + "format": { + "hours_minutes": "{hours}小時{minutes}分", + "minutes": "{minutes}分鐘", + "seconds": "{seconds}秒", + "hours": "{hours}小時", + "just_now": "剛剛", + "within_hour": "一小時內" + }, + "player": { + "steam_only": "僅 Steam", + "in_game": "遊戲內名稱「{name}」", + "more": "{name} 的更多操作", + "sanction_history": "處分紀錄" + }, + "ip": { + "label": "IP", + "shared_now": "目前也在此伺服器上:{names}", + "shared_week": "過去 7 天內相同 IP:{names}" + }, + "roster": { + "live": "即時", + "live_description": "由 Player Management 外掛回報。加入和離開會在幾秒內顯示。", + "connected": "已連線", + "not_available": "無", + "not_detected": "未偵測到 Player Management 外掛。玩家列表每 30 秒透過 RCON 讀取一次,不會記錄歷史。", + "not_reporting": "Player Management 外掛尚未回報玩家,因此玩家列表每 30 秒透過 RCON 讀取一次,不會記錄歷史。" + }, + "recent": { + "title": "最近玩家", + "retention": "歷史紀錄保留 {days} 天", + "unique_24h": "不重複玩家 · 24 小時", + "unique_7d": "不重複玩家 · 7 天", + "sessions_7d": "工作階段 · 7 天", + "time_played_7d": "遊戲時數 · 7 天", + "avg_session": "平均工作階段", + "players": "名玩家", + "joins": "次加入", + "per_visit": "每次造訪", + "search": "搜尋名稱、Steam ID 或 IP", + "window_24h": "24 小時", + "window_7d": "7 天", + "player": "玩家", + "empty": "此時段內沒有人在這裡玩過。", + "no_matches": "沒有符合搜尋條件的玩家。", + "showing": "顯示 {shown} / {total}", + "load_more": "載入更多" + }, + "player_tab": { + "moderator_note": "版主還能看到確切時間、工作階段、使用過的名稱和 IP。", + "public_note": "社群伺服器上的遊戲時數和擊殺。只顯示最後上線時間,從不顯示具體的遊戲時段。", + "avg_session": "每個工作階段平均 {duration}", + "kd": "K/D {ratio}", + "servers": "伺服器", + "community_servers": "個社群伺服器", + "servers_seen_on": "出現過的伺服器", + "names_used": "使用過的名稱", + "show_sessions": "顯示在 {server} 上的工作階段", + "sessions_last_7_days": "工作階段 · 過去 7 天", + "moderators_only": "僅限版主", + "no_recent_sessions": "過去 7 天內沒有工作階段。", + "no_servers_this_week": "本週沒有社群伺服器遊戲時數。", + "also_on_ips": "在其 IP 上出現的其他帳號", + "ip_matches_title": "過去 7 天內在此玩家 IP 上出現的其他帳號", + "ips_used": "此玩家使用過的 IP:", + "ip_sessions": "{count} 個工作階段 | {count} 個工作階段", + "empty_title": "沒有社群工作階段", + "empty_description": "尚未在任何社群伺服器上看到此玩家。" + }, + "leaderboard": { + "title": "排行榜", + "by_time": "時數", + "by_kills": "擊殺", + "you": "你", + "empty_week": "本週還沒有擁有 5stack 帳號的玩家在這裡玩過。", + "empty_all": "還沒有擁有 5stack 帳號的玩家在這裡玩過。", + "footnote": "僅列出擁有 5stack 帳號的玩家。沒有帳號的玩家仍計入上方的總數。時數按連線期間計算;擊殺只統計本伺服器。", + "sign_in_hint": "登入 5stack 並在這裡遊戲即可上榜。" + }, + "server_page": { + "breadcrumb": "導覽路徑", + "online": "線上", + "offline": "離線", + "map": "地圖", + "community_server": "社群伺服器", + "online_now": "目前線上", + "players_this_week": "本週玩家", + "different_players": "名不同玩家", + "hours_this_week": "本週時數", + "played_by_everyone": "所有人合計", + "all_time_players": "歷來玩家", + "since": "自 {date} 起", + "activity": "活躍度 · 過去 7 天", + "activity_description": "每天所有人在這裡遊戲的總時數。不會顯示任何人的個人遊戲時間。", + "chart_title": "{server} 每日玩家時數,過去 7 天", + "chart_column": "{date}:遊戲 {hours},最繁忙時段 {players} 名玩家", + "today": "今天", + "today_so_far": "{date},截至目前", + "so_far": "截至目前 {hours}", + "played": "遊戲", + "peak_players": "名玩家(最繁忙時段)", + "busiest_hour": "最繁忙時段玩家數", + "full_day": "全天", + "today_partial": "今天截至目前", + "day": "日期", + "player_hours": "玩家時數", + "not_found_title": "找不到伺服器", + "not_found_description": "此伺服器不存在或未公開。" + } } } diff --git a/middleware/auth.global.ts b/middleware/auth.global.ts index 6eabc9dff..9fa124ead 100644 --- a/middleware/auth.global.ts +++ b/middleware/auth.global.ts @@ -27,6 +27,10 @@ function isPublicRoute(path: string): boolean { return true; } + if (path.startsWith("/public-servers/")) { + return true; + } + if (path.startsWith("/leaderboard")) { return true; } diff --git a/pages/dedicated-servers/[id].vue b/pages/dedicated-servers/[id].vue index 31f0a6b26..f971a9b4b 100644 --- a/pages/dedicated-servers/[id].vue +++ b/pages/dedicated-servers/[id].vue @@ -45,6 +45,7 @@ import { useNow } from "@vueuse/core"; import ServerForm from "~/components/servers/ServerForm.vue"; import RconCommander from "~/components/servers/RconCommander.vue"; import ServerPlayerManagement from "~/components/servers/ServerPlayerManagement.vue"; +import ServerRecentPlayers from "~/components/servers/ServerRecentPlayers.vue"; import ServerSettings from "~/components/servers/ServerSettings.vue"; import ServerMoveDialog from "~/components/servers/ServerMoveDialog.vue"; import ServerMigrationPanel from "~/components/servers/ServerMigrationPanel.vue"; @@ -342,6 +343,14 @@ const titleClasses = :api-password="apiPassword" :plugin-runtime="server.plugin_runtime" :online="rconOnline" + @roster-change="rosterRevision++" + /> + + + + @@ -514,6 +523,7 @@ export default { selectedConfigRuntime: null as string | null, editServerSheet: false, deleteServerAlertDialog: false, + rosterRevision: 0, }; }, computed: { @@ -598,6 +608,16 @@ export default { isPracticeServer() { return this.server?.type === SERVER_TYPE_PRACTICE; }, + // Only community servers report players through the Player Management + // plugin, so only they have a session history to show. + isCommunityServer() { + return ( + !!this.server && + this.server.game !== "csgo" && + this.server.type !== SERVER_TYPE_RANKED && + this.server.type !== SERVER_TYPE_PRACTICE + ); + }, configPath() { const runtime = this.configRuntime === "counterstrikesharp" diff --git a/pages/players/[id].vue b/pages/players/[id].vue index 67d27018f..7b1318b17 100644 --- a/pages/players/[id].vue +++ b/pages/players/[id].vue @@ -13,6 +13,7 @@ import PlayerPerformanceRating from "~/components/player/PlayerPerformanceRating import PlayerConsistencyChart from "~/components/player/PlayerConsistencyChart.vue"; import PlayerCareerDuels from "~/components/player/PlayerCareerDuels.vue"; import PlayerCareerClutches from "~/components/player/PlayerCareerClutches.vue"; +import PlayerCommunityHistory from "~/components/player/PlayerCommunityHistory.vue"; const { t } = useI18n(); @@ -362,6 +363,7 @@ const VALID_STATS_TABS = [ "maps", "arsenal", "combat", + "community", ] as const; // A plugin tab is keyed by its slug, which the DB constrains to // ^[a-z0-9]+(-[a-z0-9]+)*$ — URL-safe, and it can't collide with the built-ins. @@ -2347,6 +2349,19 @@ const playerHeroTeamChipDotClasses = + + + + +
{{ $t("pages.players.detail.tabs.combat") }} + + {{ $t("pages.players.detail.tabs.community") }} + {{ $t("pages.players.detail.tabs.combat") }} + + {{ $t("pages.players.detail.tabs.community") }} +
+
+ + + + + + +import { ref, computed, watch, onMounted, onBeforeUnmount } from "vue"; +import { useI18n } from "vue-i18n"; +import { ChevronLeft } from "lucide-vue-next"; +import PageTransition from "~/components/ui/transitions/PageTransition.vue"; +import FadeSwap from "~/components/ui/transitions/FadeSwap.vue"; +import Skeleton from "~/components/ui/skeleton/Skeleton.vue"; +import Empty from "~/components/ui/empty/Empty.vue"; +import EmptyTitle from "~/components/ui/empty/EmptyTitle.vue"; +import EmptyDescription from "~/components/ui/empty/EmptyDescription.vue"; +import AnimatedStat from "~/components/AnimatedStat.vue"; +import QuickServerConnect from "~/components/match/QuickServerConnect.vue"; +import ServerActivityChart from "~/components/community/ServerActivityChart.vue"; +import ServerLeaderboard from "~/components/community/ServerLeaderboard.vue"; +import getGraphqlClient from "~/graphql/getGraphqlClient"; +import { + COMMUNITY_OPTIONAL, + DEDICATED_SERVER_INFO_QUERY, + PUBLIC_SERVER_SUBSCRIPTION, + SERVER_COMMUNITY_STATS_QUERY, +} from "~/graphql/communityGraphql"; +import { useCommunityFormat } from "~/composables/useCommunityFormat"; +import cleanMapName from "~/utilities/cleanMapName"; +import { + tacticalSectionDescriptionClasses, + tacticalSectionLabelClasses, + tacticalSectionTickClasses, +} from "~/utilities/tacticalClasses"; +import type { HourBucket } from "~/utilities/communityStats"; + +const { t } = useI18n(); +const route = useRoute(); +const nuxtApp = useNuxtApp(); +const format = useCommunityFormat(); + +// The api caches these for five minutes, so polling faster buys nothing. +const STATS_REFRESH_MS = 5 * 60 * 1000; +const INFO_REFRESH_MS = 60 * 1000; + +type PublicServer = { + id: string; + label: string; + type: string; + game: string; + region: string; + connected: boolean; + connection_link: string | null; + connection_string: string | null; + max_players: number | null; + game_mode: { slug: string; name: string } | null; +}; + +type CommunityStats = { + online: number; + max_players: number | null; + week_players: number; + week_seconds: number; + all_time_players: number; + tracked_since: string | null; + hourly: HourBucket[]; +}; + +const serverId = computed(() => route.params.id as string); +const server = ref(null); +const serverLoaded = ref(false); +const info = ref<{ map: string | null; players: number | null } | null>(null); +const stats = ref(null); +let serverSub: { unsubscribe: () => void } | null = null; +let infoTimer: ReturnType | null = null; +let statsTimer: ReturnType | null = null; + +const heroClasses = + "relative flex min-w-0 max-w-full flex-wrap items-end gap-x-6 gap-y-4 border border-border px-6 pb-6 pt-5 max-sm:p-4 [background:linear-gradient(180deg,hsl(var(--card)/0.2)_0%,hsl(var(--card)/0.04)_100%)]"; + +const statusBaseClasses = + "inline-flex items-center gap-2 px-[0.7rem] py-[0.3rem] font-mono text-[0.68rem] font-bold tracking-[0.2em] uppercase border rounded"; + +const chipClasses = + "inline-flex items-center px-[0.7rem] py-[0.25rem] font-mono text-[0.62rem] font-bold uppercase tracking-[0.14em] leading-none rounded border border-border/70 bg-muted/35 text-muted-foreground"; + +const titleClasses = + "relative m-0 font-sans font-bold [font-stretch:80%] text-[clamp(1.6rem,4vw,2.6rem)] leading-[0.95] tracking-[0.02em] uppercase break-words bg-gradient-to-b from-foreground to-foreground/70 bg-clip-text text-transparent"; + +const onlineNow = computed( + () => info.value?.players ?? stats.value?.online ?? 0, +); + +const maxPlayers = computed( + () => server.value?.max_players ?? stats.value?.max_players ?? null, +); + +const mapName = computed(() => + info.value?.map ? cleanMapName(info.value.map) : null, +); + +const tiles = computed(() => [ + { + key: "online", + label: t("community.server_page.online_now"), + value: format.count(onlineNow.value), + suffix: maxPlayers.value ? `/ ${maxPlayers.value}` : null, + sub: t("community.recent.players"), + }, + { + key: "week-players", + label: t("community.server_page.players_this_week"), + value: format.count(stats.value?.week_players), + suffix: null, + sub: t("community.server_page.different_players"), + }, + { + key: "week-hours", + label: t("community.server_page.hours_this_week"), + value: format.hours(stats.value?.week_seconds), + suffix: null, + sub: t("community.server_page.played_by_everyone"), + }, + { + key: "all-time", + label: t("community.server_page.all_time_players"), + value: format.count(stats.value?.all_time_players), + suffix: null, + sub: stats.value?.tracked_since + ? t("community.server_page.since", { + date: format.date(stats.value.tracked_since), + }) + : t("community.recent.players"), + }, +]); + +async function loadInfo() { + try { + const { data } = await nuxtApp.$apollo.defaultClient.query({ + query: DEDICATED_SERVER_INFO_QUERY, + fetchPolicy: "network-only", + context: COMMUNITY_OPTIONAL, + }); + + const entry = (data?.getDedicatedServerInfo ?? []).find( + (candidate: { id: string }) => candidate.id === serverId.value, + ); + + info.value = entry ? { map: entry.map, players: entry.players } : null; + } catch { + return; + } +} + +async function loadStats() { + try { + const { data } = await nuxtApp.$apollo.defaultClient.query({ + query: SERVER_COMMUNITY_STATS_QUERY, + variables: { serverId: serverId.value }, + fetchPolicy: "network-only", + context: COMMUNITY_OPTIONAL, + }); + + stats.value = data?.getServerCommunityStats ?? null; + } catch { + return; + } +} + +function subscribe() { + serverSub?.unsubscribe(); + serverLoaded.value = false; + server.value = null; + + serverSub = getGraphqlClient() + .subscribe({ + query: PUBLIC_SERVER_SUBSCRIPTION, + variables: { serverId: serverId.value }, + }) + .subscribe({ + next: ({ data }: any) => { + server.value = data?.servers_by_pk ?? null; + serverLoaded.value = true; + }, + error: () => { + serverLoaded.value = true; + }, + }); +} + +watch(serverId, () => { + stats.value = null; + info.value = null; + subscribe(); + void loadInfo(); + void loadStats(); +}); + +useHead(() => ({ + title: server.value?.label ?? t("pages.public_servers.title"), +})); + +onMounted(() => { + subscribe(); + void loadInfo(); + void loadStats(); + infoTimer = setInterval(() => void loadInfo(), INFO_REFRESH_MS); + statsTimer = setInterval(() => void loadStats(), STATS_REFRESH_MS); +}); + +onBeforeUnmount(() => { + serverSub?.unsubscribe(); + + if (infoTimer) { + clearInterval(infoTimer); + } + + if (statsTimer) { + clearInterval(statsTimer); + } +}); + + + diff --git a/pages/public-servers.vue b/pages/public-servers/index.vue similarity index 94% rename from pages/public-servers.vue rename to pages/public-servers/index.vue index 099a599f7..cb10e5e72 100644 --- a/pages/public-servers.vue +++ b/pages/public-servers/index.vue @@ -31,6 +31,9 @@ const canManage = computed(() => const canSetup = computed(() => useAuthStore().isRoleAbove(e_player_roles_enum.administrator), ); + +const serverLinkClasses = + "truncate font-semibold transition-colors after:absolute after:inset-0 after:z-[1] after:rounded-xl after:content-[''] group-hover:text-[hsl(var(--tac-amber))] focus-visible:outline-none focus-visible:after:ring-2 focus-visible:after:ring-inset focus-visible:after:ring-[hsl(var(--tac-amber))]";