This commit is contained in:
AuroraLS3 2023-03-17 18:09:24 +00:00
parent bdc6b15792
commit 8bbe942cbd
37 changed files with 1874 additions and 226 deletions

View File

@ -54,7 +54,7 @@ command:
manage:
confirm: "> §c添加 '-a' 参数来确认执行:${0}"
confirmOverwrite: "数据库 ${0} 中的数据将被覆盖!"
confirmPartialRemoval: "Join Address Data for Server ${0} in ${1} will be removed!"
confirmPartialRemoval: "数据库 ${1} 中服务器 ${0} 的加入地址数据将被删除!"
confirmRemoval: "数据库 ${0} 中的数据将被删除!"
fail: "> §c发生了错误${0}"
failFileNotFound: "> §c没有在 ${0} 发现文件"
@ -64,7 +64,7 @@ command:
failSameServer: "不能将此服务器标记为已卸载(你在这个服务器上)。"
hotswap: "§e请切换到新的数据库(/plan db hotswap ${0})并重新加载插件。"
importers: "导入器:"
preparing: "Preparing.."
preparing: "准备中.."
progress: "${0} / ${1} 处理中..."
start: "> §2处理数据中..."
success: "> §a成功"
@ -166,7 +166,7 @@ command:
description: "将其他用户从面板上登出。"
inDepth: "输入用户名作为参数可以注销 Plan 上的一个用户,输入 * 作为参数可以注销所有用户。"
migrateToOnlineUuids:
description: "Migrate offline uuid data to online uuids"
description: "将离线uuid数据迁移到在线uuid"
network:
description: "查看群组网络页面"
inDepth: "获得一个指向 /network page群组网络 的链接,只能在群组网络上这样做。"
@ -183,7 +183,7 @@ command:
description: "重启 Plan"
inDepth: "禁用然后重新启用本插件,会重新加载配置中的设置。"
removejoinaddresses:
description: "Remove join addresses of a specified server"
description: "移除指定服务器的加入地址"
search:
description: "搜索玩家"
inDepth: "列出所有与给定名字部分相匹配的玩家名字。"
@ -265,8 +265,8 @@ html:
authFailedTips: "- 确保你已使用 <b>/plan register</b> 来注册用户<br>- 检查用户名与密码是否正确<br>- 用户名与密码区分大小写<br><br>若您忘记了密码,请让工作人员删除您的旧密码并重新注册。"
noServersOnline: "无可执行此请求的在线服务器。"
playerNotSeen: "Plan 没有找到此玩家。"
serverNotExported: "Server doesn't exist, its data might not have been exported yet."
serverNotSeen: "Server doesn't exist"
serverNotExported: "服务器不存在,其数据可能尚未导出。"
serverNotSeen: "服务器不存在"
generic:
none: "无"
label:
@ -277,26 +277,26 @@ html:
afkTime: "挂机时间"
all: "全部"
allTime: "所有时间"
alphabetical: "Alphabetical"
apply: "Apply"
alphabetical: "按字母顺序"
apply: "应用"
asNumbers: "数据"
average: "平均"
averageActivePlaytime: "平均活跃时间"
averageAfkTime: "平均挂机时间"
averageChunks: "平均区块数"
averageCpuUsage: "Average CPU Usage"
averageCpuUsage: "平均 CPU 使用率"
averageEntities: "平均实体数"
averageKdr: "平均 KDR"
averageMobKdr: "平均生物 KDR"
averagePing: "平均延迟"
averagePlayers: "Average Players"
averagePlayers: "平均在线玩家"
averagePlaytime: "平均游玩时间"
averageRamUsage: "Average RAM Usage"
averageServerDowntime: "Average Downtime / Server"
averageRamUsage: "平均内存使用量"
averageServerDowntime: "平均停机时间/服务器"
averageSessionLength: "平均会话时长"
averageSessions: "平均会话"
averageTps: "平均 TPS"
averageTps7days: "Average TPS (7 days)"
averageTps7days: "平均 TPS7 天)"
banned: "已被封禁"
bestPeak: "所有时间峰值"
bestPing: "最低延迟"
@ -306,9 +306,9 @@ html:
country: "国家和地区"
cpu: "CPU"
cpuRam: "CPU 和内存"
cpuUsage: "CPU Usage"
cpuUsage: "CPU 使用率"
currentPlayerbase: "当前玩家数"
currentUptime: "Current Uptime"
currentUptime: "正常运行时间"
dayByDay: "按天查看"
dayOfweek: "星期"
deadliestWeapon: "最致命的 PVP 武器"
@ -318,32 +318,62 @@ html:
downtime: "停机时间"
duringLowTps: "持续低 TPS 时间"
entities: "实体"
exported: "Data export time"
exported: "数据导出时间"
favoriteServer: "最喜爱的服务器"
firstSession: "第一会话"
firstSession: "第一会话"
firstSessionLength:
average: "Average first session length"
median: "Median first session length"
average: "平均首次会话时间长度"
median: "首次会话时间长度的中位数"
geoProjection:
dropdown: "Select projection"
equalEarth: "Equal Earth"
mercator: "Mercator"
miller: "Miller"
ortographic: "Ortographic"
dropdown: "选择地图投影方式"
equalEarth: "等面积投影"
mercator: "墨卡托投影"
miller: "米勒投影"
ortographic: "正射投影"
geolocations: "地理位置"
help:
activityIndexBasis: "Activity index is based on non-AFK playtime in the past 3 weeks (21 days). Each week is considered separately."
activityIndexExample1: "If someone plays as much as threshold every week, they are given activity index ~3."
activityIndexExample2: "Very active is ~2x the threshold (y ≥ 3.75)."
activityIndexExample3: "The index approaches 5 indefinitely."
activityIndexVisual: "Here is a visualization of the curve where y = activity index, and x = playtime per week / threshold."
activityIndexWeek: "Week {}"
playtimeUnit: "hours"
retentionBasis: "New player retention is calculated based on session data. If a registered player has played within latter half of the timespan, they are considered retained."
testPrompt: "Test it out:"
testResult: "Test result"
threshold: "Threshold"
thresholdUnit: "hours / week"
activityIndexBasis: "活跃指数基于过去3周21天内的非AFK游戏时间。每周单独考虑。"
activityIndexExample1: "如果有人每周玩的时间达到阈值他们将获得活跃指数约为3。"
activityIndexExample2: "非常活跃是阈值的约2倍y ≥ 3.75)。"
activityIndexExample3: "该指数无限趋近于5。"
activityIndexVisual: "这里是y = 活跃指数x = 每周游戏时间 / 阈值的曲线可视化。"
activityIndexWeek: "第{}周"
examples: "Examples"
graph:
labels: "You can hide/show a group by clicking on the label at the bottom."
title: "Graph"
zoom: "You can Zoom in by click + dragging on the graph."
playtimeUnit: "小时"
retention:
calculationStep1: "First the data is filtered using '<>' option. Any players with 'registerDate' outside the time range are ignored."
calculationStep2: "Then it is grouped into groups of players using '<0>' option, eg. With '<1>': All players who registered in January 2023, February 2023, etc"
calculationStep3: "Then the '<0>' and '<1>' options select which visualization to render."
calculationStep4: "'<>' controls how many points the graph has, eg. 'Days' has one point per day."
calculationStep5: "On each calculated point all players are checked for the condition."
calculationStep6: "Select X Axis below to see conditions."
calculationStepDate: "This visualization shows the different groups of players that are still playing on your server. The visualization uses lastSeen date. If x < lastSeenDate, the player is visible on the graph."
calculationStepDeltas: "This visualization is most effective using Player Count as the Y Axis. The visualization shows net gain of players (How many players joined minus players who stopped playing). The visualization uses both registered and lastSeen dates. If registerDate < x < lastSeenDate, the player is visible on the graph."
calculationStepPlaytime: "This visualization tells how long the gameplay loop keeps players engaged on your server. The visualization uses playtime. If x < playtime, the player is visible on the graph."
calculationStepTime: "This visualization tells how long people keep coming back to play on the server after they join the first time. The visualization uses timeDifference. If x < timeDifference, the player is visible on the graph."
compareJoinAddress: "Grouping by join address allows measuring advertising campaigns on different sites."
compareMonths: "You can compare different months by changing the '<0>' option to '<1>'"
examples:
adCampaign: "Comparing player gain of different ad campaigns using different Join Addresses (anonymized)"
deltas: "<> shows net gain of players."
pattern: "A general pattern emerges when all players start leaving the server at the same time"
plateau: "Comparing player gain of different months. Plateaus suggest there were players Plan doesn't know about. In this example Plan was installed in January 2022."
playtime: "Playtime tells how long the gameplay loop keeps players engaged on your server."
stack: "Cumulative player gain can be checked with stacked player count as Y axis"
howIsItCalculated: "How it is calculated"
howIsItCalculatedData: "The graph is generated from player data:"
options: "Select the options to analyze different aspects of Player Retention."
retentionBasis: "新玩家留坑率根据会话数据计算。如果注册玩家在时间跨度的后半段内有游戏记录,则被视为已留坑。"
testPrompt: "试一下:"
testResult: "测试结果"
threshold: "阈值"
thresholdUnit: "小时/周"
tips: "Tips"
usingTheGraph: "Using the Graph"
hourByHour: "按小时查看"
inactive: "不活跃"
indexInactive: "不活跃"
@ -352,7 +382,7 @@ html:
insights: "Insights"
insights30days: "30 天分析"
irregular: "偶尔上线"
joinAddress: "Join Address"
joinAddress: "加入地址"
joinAddresses: "加入地址"
kdr: "KDR"
killed: "被击杀数"
@ -362,7 +392,7 @@ html:
lastConnected: "最后连接时间"
lastPeak: "上次在线峰值"
lastSeen: "最后在线时间"
latestJoinAddresses: "Latest Join Addresses"
latestJoinAddresses: "上一次加入地址"
length: " 游玩时长"
links: "链接"
loadedChunks: "已加载区块"
@ -371,9 +401,9 @@ html:
loneNewbieJoins: "单独新玩家加入"
longestSession: "最长会话时间"
lowTpsSpikes: "低 TPS 时间"
lowTpsSpikes7days: "Low TPS Spikes (7 days)"
lowTpsSpikes7days: "低 TPS 时间7天"
maxFreeDisk: "最大可用硬盘空间"
medianSessionLength: "Median Session Length"
medianSessionLength: "会话长度中位数"
minFreeDisk: "最小可用硬盘空间"
mobDeaths: "被生物击杀数"
mobKdr: "生物 KDR"
@ -389,13 +419,13 @@ html:
new: "新"
newPlayerRetention: "新玩家留坑率"
newPlayers: "新玩家"
newPlayers7days: "New Players (7 days)"
newPlayers7days: "新玩家7天"
nickname: "昵称"
noDataToDisplay: "No Data to Display"
noDataToDisplay: "没有数据可以展示"
now: "现在"
onlineActivity: "在线活动"
onlineActivityAsNumbers: "在线活动数据"
onlineOnFirstJoin: "第一次进入服务器的在线玩家"
onlineOnFirstJoin: "首次加入时的在线玩家数"
operator: "管理员"
overview: "总览"
perDay: "/ 天"
@ -407,29 +437,29 @@ html:
player: "玩家"
playerDeaths: "被玩家击杀次数"
playerKills: "击杀玩家数"
playerKillsVictimIndicator: "Player was killed within 24h of first time they were seen (Time since registered: <>)."
playerKillsVictimIndicator: "玩家在首次出现后 24 小时内被杀(自注册以来的时间:<>)。"
playerList: "玩家列表"
playerOverview: "玩家总览"
playerPage: "玩家页面"
playerRetention: "Player Retention"
playerRetention: "玩家留坑"
playerbase: "玩家数据"
playerbaseDevelopment: "玩家发展"
playerbaseOverview: "Playerbase Overview"
playerbaseOverview: "玩家总览"
players: "玩家"
playersOnline: "在线玩家"
playersOnlineNow: "Players Online (Now)"
playersOnlineNow: "在线玩家(当前)"
playersOnlineOverview: "在线活动总览"
playtime: "游玩时间"
plugins: "插件"
pluginsOverview: "Plugins Overview"
pluginsOverview: "插件总览"
punchcard: "打卡"
punchcard30days: "30 天打卡"
pvpPve: "PvP 和 PvE"
pvpPveAsNumbers: "PvP 和 PvE 数据"
query: "进行查询"
quickView: "快速浏览"
ram: "RAM"
ramUsage: "RAM Usage"
ram: "内存"
ramUsage: "内存使用情况"
recentKills: "最近击杀"
recentPvpDeaths: "最近的 PVP 死亡"
recentPvpKills: "最近的 PVP 击杀"
@ -439,53 +469,82 @@ html:
regular: "普通"
regularPlayers: "普通玩家"
relativeJoinActivity: "最近加入活动"
retention:
groupByNone: "No grouping"
groupByTime: "Group registered by"
inAnytime: "any time"
inLast180d: "in the last 6 months"
inLast30d: "in the last 30 days"
inLast365d: "in the last 12 months"
inLast730d: "in the last 24 months"
inLast7d: "in the last 7 days"
inLast90d: "in the last 3 months"
playersRegisteredInTime: "Players who registered"
retainedPlayersPercentage: "Retained Players %"
timeSinceRegistered: "Time since register date"
timeStep: "Time step"
secondDeadliestWeapon: "第二致命的 PVP 武器"
seenNicknames: "用过的昵称"
server: "服务器"
serverAnalysis: "服务器分析"
serverAsNumberse: "服务器数据"
serverCalendar: "Server Calendar"
serverCalendar: "服务器日历"
serverDowntime: "服务器停机时间"
serverOccupied: "服务器在线时间"
serverOverview: "服务器总览"
serverPage: "服务器页面"
serverPlaytime: "服务器游戏时间"
serverPlaytime30days: "最近 30 天内的服务器游玩时间"
serverSelector: "Server selector"
serverSelector: "服务器选择器"
servers: "服务器"
serversTitle: "服务器"
session: "会话次数"
sessionCalendar: "Session Calendar"
sessionCalendar: "会话日历"
sessionEnded: " 会话结束"
sessionMedian: "平均会话长度"
sessionStart: "会话开始于"
sessions: "会话"
sortBy: "Sort By"
stacked: "Stacked"
sortBy: "排序方式"
stacked: "堆叠"
themeSelect: "主题选择"
thirdDeadliestWeapon: "第三致命的 PVP 武器"
thirtyDays: "30 天"
thirtyDaysAgo: "30 天前"
time:
date: "Date"
day: "Day"
days: "Days"
hours: "Hours"
month: "Month"
months: "Months"
week: "Week"
weeks: "Weeks"
year: "Year"
timesKicked: "被踢出次数"
toMainPage: "回到主页面"
total: "Total"
total: "总计"
totalActive: "总活跃时长"
totalAfk: "总挂机时长"
totalPlayers: "总玩家数"
totalPlayersOld: "总游玩时长"
totalPlaytime: "总游玩时间"
totalServerDowntime: "Total Server Downtime"
totalServerDowntime: "服务器停机时间"
tps: "TPS"
trend: "趋势"
trends30days: "30 天趋势"
uniquePlayers: "独立玩家"
uniquePlayers7days: "Unique Players (7 days)"
uniquePlayers7days: "独立玩家7天"
unit:
percentage: "Percentage"
playerCount: "Player Count"
veryActive: "非常活跃"
weekComparison: "每周对比"
weekdays: "'星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日'"
world: "世界加载"
worldPlaytime: "世界游玩时间"
worstPing: "最高延迟"
xAxis: "X Axis"
yAxis: "Y Axis"
login:
failed: "登录失败:"
forgotPassword: "忘记密码?"
@ -528,17 +587,17 @@ html:
name: "封禁状态"
banned: "被封禁"
country:
text: "have joined from country"
text: "加入的国家"
generic:
allPlayers: "全体玩家"
and: "外加 "
start: "查询玩家:"
hasPlayedOnServers:
name: "Has played on one of servers"
text: "have played on at least one of"
name: "在其中一个服务器上玩过"
text: "至少在其中一个服务器上玩过"
hasPluginBooleanValue:
name: "Has plugin boolean value"
text: "have Plugin boolean value"
name: "插件布尔值"
text: "插件布尔值"
joinAddress:
text: "加入地址"
nonOperators: "非管理员"
@ -553,7 +612,7 @@ html:
text: "在 ${plugin} 插件的 ${group} 分组中"
registeredBetween:
text: "在此期间注册"
skipped: "Skipped"
skipped: "已跳过"
title:
activityGroup: "活跃度分组"
view: " 日期范围:"
@ -566,10 +625,10 @@ html:
from: ">从 </label>"
makeAnother: "进行另一个查询"
servers:
all: "using data of all servers"
many: "using data of {number} servers"
single: "using data of 1 server"
two: "using data of 2 servers"
all: "使用所有服务器的数据"
many: "使用 {number} 台服务器的数据。"
single: "使用 1 台服务器的数据"
two: "使用 2 台服务器的数据"
to: ">到 </label>"
view: "日期范围"
performQuery: "执行查询!"
@ -598,7 +657,7 @@ html:
login: "已经有帐号了? 登录!"
passwordTip: "密码不能超过8个字符没有其他限制。"
register: "注册"
success: "Registered a new user successfully! You can now login."
success: "新用户注册成功!你现在可以登录了。"
usernameTip: "用户名最多可以包含 50 个字符。"
text:
clickToExpand: "点击展开"
@ -662,9 +721,9 @@ plugin:
dbNotifySQLiteWAL: "此服务器版本不支持 SQLite WAL 模式,正使用默认模式。这可能会影响性能。"
dbPatchesAlreadyApplied: "已应用所有数据库补丁。"
dbPatchesApplied: "已成功应用所有数据库补丁。"
dbSchemaPatch: "Database: Making sure schema is up to date.."
loadedServerInfo: "Server identifier loaded: ${0}"
loadingServerInfo: "Loading server identifying information"
dbSchemaPatch: "数据库:确保表结构是最新的.."
loadedServerInfo: "服务器标识已加载:${0}"
loadingServerInfo: "正在加载服务器标识信息"
no: "否"
today: "'今天'"
unavailable: "不可用"
@ -687,10 +746,10 @@ plugin:
notify:
authDisabledConfig: "网页服务器: 用户登录已关闭! (已在配置文件中禁用)"
authDisabledNoHTTPS: "网页服务器已禁用用户登录HTTP 方式不安全)"
certificateExpiresOn: "Webserver: Loaded certificate is valid until ${0}."
certificateExpiresPassed: "Webserver: Certificate has expired, consider renewing the certificate."
certificateExpiresSoon: "Webserver: Certificate expires in ${0}, consider renewing the certificate."
certificateNoSuchAlias: "Webserver: Certificate with alias '${0}' was not found inside the keystore file '${1}'."
certificateExpiresOn: "网页服务器:加载的证书有效期截止到 ${0}。"
certificateExpiresPassed: "网页服务器:证书已过期,请考虑更新证书。"
certificateExpiresSoon: "网页服务器:证书将在${0}到期,请考虑更新证书。"
certificateNoSuchAlias: "网页服务器:密钥库文件'${1}'中未找到别名为'${0}'的证书。"
http: "网页服务器:无证书 -> 正使用 HTTP 服务器提供可视化效果。"
ipWhitelist: "网页服务器: IP 白名单已启用。"
ipWhitelistBlock: "网页服务器:${0} 被拒绝访问 '${1}'. (不在白名单中)"

View File

@ -338,12 +338,42 @@ html:
activityIndexExample3: "The index approaches 5 indefinitely."
activityIndexVisual: "Here is a visualization of the curve where y = activity index, and x = playtime per week / threshold."
activityIndexWeek: "Week {}"
examples: "Examples"
graph:
labels: "You can hide/show a group by clicking on the label at the bottom."
title: "Graph"
zoom: "You can Zoom in by click + dragging on the graph."
playtimeUnit: "hours"
retention:
calculationStep1: "First the data is filtered using '<>' option. Any players with 'registerDate' outside the time range are ignored."
calculationStep2: "Then it is grouped into groups of players using '<0>' option, eg. With '<1>': All players who registered in January 2023, February 2023, etc"
calculationStep3: "Then the '<0>' and '<1>' options select which visualization to render."
calculationStep4: "'<>' controls how many points the graph has, eg. 'Days' has one point per day."
calculationStep5: "On each calculated point all players are checked for the condition."
calculationStep6: "Select X Axis below to see conditions."
calculationStepDate: "This visualization shows the different groups of players that are still playing on your server. The visualization uses lastSeen date. If x < lastSeenDate, the player is visible on the graph."
calculationStepDeltas: "This visualization is most effective using Player Count as the Y Axis. The visualization shows net gain of players (How many players joined minus players who stopped playing). The visualization uses both registered and lastSeen dates. If registerDate < x < lastSeenDate, the player is visible on the graph."
calculationStepPlaytime: "This visualization tells how long the gameplay loop keeps players engaged on your server. The visualization uses playtime. If x < playtime, the player is visible on the graph."
calculationStepTime: "This visualization tells how long people keep coming back to play on the server after they join the first time. The visualization uses timeDifference. If x < timeDifference, the player is visible on the graph."
compareJoinAddress: "Grouping by join address allows measuring advertising campaigns on different sites."
compareMonths: "You can compare different months by changing the '<0>' option to '<1>'"
examples:
adCampaign: "Comparing player gain of different ad campaigns using different Join Addresses (anonymized)"
deltas: "<> shows net gain of players."
pattern: "A general pattern emerges when all players start leaving the server at the same time"
plateau: "Comparing player gain of different months. Plateaus suggest there were players Plan doesn't know about. In this example Plan was installed in January 2022."
playtime: "Playtime tells how long the gameplay loop keeps players engaged on your server."
stack: "Cumulative player gain can be checked with stacked player count as Y axis"
howIsItCalculated: "How it is calculated"
howIsItCalculatedData: "The graph is generated from player data:"
options: "Select the options to analyze different aspects of Player Retention."
retentionBasis: "New player retention is calculated based on session data. If a registered player has played within latter half of the timespan, they are considered retained."
testPrompt: "Test it out:"
testResult: "Test result"
threshold: "Threshold"
thresholdUnit: "hours / week"
tips: "Tips"
usingTheGraph: "Using the Graph"
hourByHour: "Hodina po hodině"
inactive: "Neaktivní"
indexInactive: "Neaktivní"
@ -439,6 +469,20 @@ html:
regular: "Pravidelný"
regularPlayers: "Pravidelní hráči"
relativeJoinActivity: "Relativní aktivita připojení"
retention:
groupByNone: "No grouping"
groupByTime: "Group registered by"
inAnytime: "any time"
inLast180d: "in the last 6 months"
inLast30d: "in the last 30 days"
inLast365d: "in the last 12 months"
inLast730d: "in the last 24 months"
inLast7d: "in the last 7 days"
inLast90d: "in the last 3 months"
playersRegisteredInTime: "Players who registered"
retainedPlayersPercentage: "Retained Players %"
timeSinceRegistered: "Time since register date"
timeStep: "Time step"
secondDeadliestWeapon: "2. PvP Zbraň"
seenNicknames: "Viděné přezdívky"
server: "Server"
@ -466,6 +510,16 @@ html:
thirdDeadliestWeapon: "3. PvP Zbraň"
thirtyDays: "30 dní"
thirtyDaysAgo: "před 30 dny"
time:
date: "Date"
day: "Day"
days: "Days"
hours: "Hours"
month: "Month"
months: "Months"
week: "Week"
weeks: "Weeks"
year: "Year"
timesKicked: "Počet vykopnutí"
toMainPage: "Zpět na hlavní stránku"
total: "Total"
@ -480,12 +534,17 @@ html:
trends30days: "Trendy za 30 dní"
uniquePlayers: "Unikátní hráči"
uniquePlayers7days: "Unique Players (7 days)"
unit:
percentage: "Percentage"
playerCount: "Player Count"
veryActive: "Velmi aktivní"
weekComparison: "Týdenní srovnání"
weekdays: "'Pondělí', 'Úterý', 'Středa', 'Čtvrtek', 'Pátek', 'Sobota', 'Neděle'"
world: "Načtení světa"
worldPlaytime: "Herní čas světa"
worstPing: "Nejhorší ping"
xAxis: "X Axis"
yAxis: "Y Axis"
login:
failed: "Přihlašování selhalo: "
forgotPassword: "Zapomněl jste heslo?"

View File

@ -338,12 +338,42 @@ html:
activityIndexExample3: "The index approaches 5 indefinitely."
activityIndexVisual: "Here is a visualization of the curve where y = activity index, and x = playtime per week / threshold."
activityIndexWeek: "Week {}"
examples: "Examples"
graph:
labels: "You can hide/show a group by clicking on the label at the bottom."
title: "Graph"
zoom: "You can Zoom in by click + dragging on the graph."
playtimeUnit: "hours"
retention:
calculationStep1: "First the data is filtered using '<>' option. Any players with 'registerDate' outside the time range are ignored."
calculationStep2: "Then it is grouped into groups of players using '<0>' option, eg. With '<1>': All players who registered in January 2023, February 2023, etc"
calculationStep3: "Then the '<0>' and '<1>' options select which visualization to render."
calculationStep4: "'<>' controls how many points the graph has, eg. 'Days' has one point per day."
calculationStep5: "On each calculated point all players are checked for the condition."
calculationStep6: "Select X Axis below to see conditions."
calculationStepDate: "This visualization shows the different groups of players that are still playing on your server. The visualization uses lastSeen date. If x < lastSeenDate, the player is visible on the graph."
calculationStepDeltas: "This visualization is most effective using Player Count as the Y Axis. The visualization shows net gain of players (How many players joined minus players who stopped playing). The visualization uses both registered and lastSeen dates. If registerDate < x < lastSeenDate, the player is visible on the graph."
calculationStepPlaytime: "This visualization tells how long the gameplay loop keeps players engaged on your server. The visualization uses playtime. If x < playtime, the player is visible on the graph."
calculationStepTime: "This visualization tells how long people keep coming back to play on the server after they join the first time. The visualization uses timeDifference. If x < timeDifference, the player is visible on the graph."
compareJoinAddress: "Grouping by join address allows measuring advertising campaigns on different sites."
compareMonths: "You can compare different months by changing the '<0>' option to '<1>'"
examples:
adCampaign: "Comparing player gain of different ad campaigns using different Join Addresses (anonymized)"
deltas: "<> shows net gain of players."
pattern: "A general pattern emerges when all players start leaving the server at the same time"
plateau: "Comparing player gain of different months. Plateaus suggest there were players Plan doesn't know about. In this example Plan was installed in January 2022."
playtime: "Playtime tells how long the gameplay loop keeps players engaged on your server."
stack: "Cumulative player gain can be checked with stacked player count as Y axis"
howIsItCalculated: "How it is calculated"
howIsItCalculatedData: "The graph is generated from player data:"
options: "Select the options to analyze different aspects of Player Retention."
retentionBasis: "New player retention is calculated based on session data. If a registered player has played within latter half of the timespan, they are considered retained."
testPrompt: "Test it out:"
testResult: "Test result"
threshold: "Threshold"
thresholdUnit: "hours / week"
tips: "Tips"
usingTheGraph: "Using the Graph"
hourByHour: "Hour by Hour"
inactive: "Inaktiv"
indexInactive: "Inaktiv"
@ -439,6 +469,20 @@ html:
regular: "Regulär"
regularPlayers: "Reguläre Spieler"
relativeJoinActivity: "Relative Join Activity"
retention:
groupByNone: "No grouping"
groupByTime: "Group registered by"
inAnytime: "any time"
inLast180d: "in the last 6 months"
inLast30d: "in the last 30 days"
inLast365d: "in the last 12 months"
inLast730d: "in the last 24 months"
inLast7d: "in the last 7 days"
inLast90d: "in the last 3 months"
playersRegisteredInTime: "Players who registered"
retainedPlayersPercentage: "Retained Players %"
timeSinceRegistered: "Time since register date"
timeStep: "Time step"
secondDeadliestWeapon: "2. PvP Waffe"
seenNicknames: "Registrierte Nicknames"
server: "Server"
@ -466,6 +510,16 @@ html:
thirdDeadliestWeapon: "3. PvP Waffe"
thirtyDays: "30 Tage"
thirtyDaysAgo: "30 Tage vorher"
time:
date: "Date"
day: "Day"
days: "Days"
hours: "Hours"
month: "Month"
months: "Months"
week: "Week"
weeks: "Weeks"
year: "Year"
timesKicked: "Mal gekickt"
toMainPage: "zur Hauptseite"
total: "Total"
@ -480,12 +534,17 @@ html:
trends30days: "Trends für 30 Tage"
uniquePlayers: "Einzigartige Spieler"
uniquePlayers7days: "Unique Players (7 days)"
unit:
percentage: "Percentage"
playerCount: "Player Count"
veryActive: "Sehr aktiv"
weekComparison: "Wochenvergleich"
weekdays: "'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag'"
world: "World Load"
worldPlaytime: "Spielzeit in der Welt"
worstPing: "Schlechtester Ping"
xAxis: "X Axis"
yAxis: "Y Axis"
login:
failed: "Login failed: "
forgotPassword: "Passwort vergessen?"

View File

@ -338,12 +338,42 @@ html:
activityIndexExample3: "The index approaches 5 indefinitely."
activityIndexVisual: "Here is a visualization of the curve where y = activity index, and x = playtime per week / threshold."
activityIndexWeek: "Week {}"
examples: "Examples"
graph:
labels: "You can hide/show a group by clicking on the label at the bottom."
title: "Graph"
zoom: "You can Zoom in by click + dragging on the graph."
playtimeUnit: "hours"
retention:
calculationStep1: "First the data is filtered using '<>' option. Any players with 'registerDate' outside the time range are ignored."
calculationStep2: "Then it is grouped into groups of players using '<0>' option, eg. With '<1>': All players who registered in January 2023, February 2023, etc"
calculationStep3: "Then the '<0>' and '<1>' options select which visualization to render."
calculationStep4: "'<>' controls how many points the graph has, eg. 'Days' has one point per day."
calculationStep5: "On each calculated point all players are checked for the condition."
calculationStep6: "Select X Axis below to see conditions."
calculationStepDate: "This visualization shows the different groups of players that are still playing on your server. The visualization uses lastSeen date. If x < lastSeenDate, the player is visible on the graph."
calculationStepDeltas: "This visualization is most effective using Player Count as the Y Axis. The visualization shows net gain of players (How many players joined minus players who stopped playing). The visualization uses both registered and lastSeen dates. If registerDate < x < lastSeenDate, the player is visible on the graph."
calculationStepPlaytime: "This visualization tells how long the gameplay loop keeps players engaged on your server. The visualization uses playtime. If x < playtime, the player is visible on the graph."
calculationStepTime: "This visualization tells how long people keep coming back to play on the server after they join the first time. The visualization uses timeDifference. If x < timeDifference, the player is visible on the graph."
compareJoinAddress: "Grouping by join address allows measuring advertising campaigns on different sites."
compareMonths: "You can compare different months by changing the '<0>' option to '<1>'"
examples:
adCampaign: "Comparing player gain of different ad campaigns using different Join Addresses (anonymized)"
deltas: "<> shows net gain of players."
pattern: "A general pattern emerges when all players start leaving the server at the same time"
plateau: "Comparing player gain of different months. Plateaus suggest there were players Plan doesn't know about. In this example Plan was installed in January 2022."
playtime: "Playtime tells how long the gameplay loop keeps players engaged on your server."
stack: "Cumulative player gain can be checked with stacked player count as Y axis"
howIsItCalculated: "How it is calculated"
howIsItCalculatedData: "The graph is generated from player data:"
options: "Select the options to analyze different aspects of Player Retention."
retentionBasis: "New player retention is calculated based on session data. If a registered player has played within latter half of the timespan, they are considered retained."
testPrompt: "Test it out:"
testResult: "Test result"
threshold: "Threshold"
thresholdUnit: "hours / week"
tips: "Tips"
usingTheGraph: "Using the Graph"
hourByHour: "Hour by Hour"
inactive: "Inactive"
indexInactive: "Inactive"
@ -439,6 +469,20 @@ html:
regular: "Regular"
regularPlayers: "Regular Players"
relativeJoinActivity: "Relative Join Activity"
retention:
groupByNone: "No grouping"
groupByTime: "Group registered by"
inAnytime: "any time"
inLast180d: "in the last 6 months"
inLast30d: "in the last 30 days"
inLast365d: "in the last 12 months"
inLast730d: "in the last 24 months"
inLast7d: "in the last 7 days"
inLast90d: "in the last 3 months"
playersRegisteredInTime: "Players who registered"
retainedPlayersPercentage: "Retained Players %"
timeSinceRegistered: "Time since register date"
timeStep: "Time step"
secondDeadliestWeapon: "2nd PvP Weapon"
seenNicknames: "Seen Nicknames"
server: "Server"
@ -466,6 +510,16 @@ html:
thirdDeadliestWeapon: "3rd PvP Weapon"
thirtyDays: "30 days"
thirtyDaysAgo: "30 days ago"
time:
date: "Date"
day: "Day"
days: "Days"
hours: "Hours"
month: "Month"
months: "Months"
week: "Week"
weeks: "Weeks"
year: "Year"
timesKicked: "Times Kicked"
toMainPage: "to main page"
total: "Total"
@ -480,12 +534,17 @@ html:
trends30days: "Trends for 30 days"
uniquePlayers: "Unique Players"
uniquePlayers7days: "Unique Players (7 days)"
unit:
percentage: "Percentage"
playerCount: "Player Count"
veryActive: "Very Active"
weekComparison: "Week Comparison"
weekdays: "'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'"
world: "World Load"
worldPlaytime: "World Playtime"
worstPing: "Worst Ping"
xAxis: "X Axis"
yAxis: "Y Axis"
login:
failed: "Login failed: "
forgotPassword: "Forgot Password?"

View File

@ -338,12 +338,42 @@ html:
activityIndexExample3: "The index approaches 5 indefinitely."
activityIndexVisual: "Here is a visualization of the curve where y = activity index, and x = playtime per week / threshold."
activityIndexWeek: "Week {}"
examples: "Examples"
graph:
labels: "You can hide/show a group by clicking on the label at the bottom."
title: "Graph"
zoom: "You can Zoom in by click + dragging on the graph."
playtimeUnit: "hours"
retention:
calculationStep1: "First the data is filtered using '<>' option. Any players with 'registerDate' outside the time range are ignored."
calculationStep2: "Then it is grouped into groups of players using '<0>' option, eg. With '<1>': All players who registered in January 2023, February 2023, etc"
calculationStep3: "Then the '<0>' and '<1>' options select which visualization to render."
calculationStep4: "'<>' controls how many points the graph has, eg. 'Days' has one point per day."
calculationStep5: "On each calculated point all players are checked for the condition."
calculationStep6: "Select X Axis below to see conditions."
calculationStepDate: "This visualization shows the different groups of players that are still playing on your server. The visualization uses lastSeen date. If x < lastSeenDate, the player is visible on the graph."
calculationStepDeltas: "This visualization is most effective using Player Count as the Y Axis. The visualization shows net gain of players (How many players joined minus players who stopped playing). The visualization uses both registered and lastSeen dates. If registerDate < x < lastSeenDate, the player is visible on the graph."
calculationStepPlaytime: "This visualization tells how long the gameplay loop keeps players engaged on your server. The visualization uses playtime. If x < playtime, the player is visible on the graph."
calculationStepTime: "This visualization tells how long people keep coming back to play on the server after they join the first time. The visualization uses timeDifference. If x < timeDifference, the player is visible on the graph."
compareJoinAddress: "Grouping by join address allows measuring advertising campaigns on different sites."
compareMonths: "You can compare different months by changing the '<0>' option to '<1>'"
examples:
adCampaign: "Comparing player gain of different ad campaigns using different Join Addresses (anonymized)"
deltas: "<> shows net gain of players."
pattern: "A general pattern emerges when all players start leaving the server at the same time"
plateau: "Comparing player gain of different months. Plateaus suggest there were players Plan doesn't know about. In this example Plan was installed in January 2022."
playtime: "Playtime tells how long the gameplay loop keeps players engaged on your server."
stack: "Cumulative player gain can be checked with stacked player count as Y axis"
howIsItCalculated: "How it is calculated"
howIsItCalculatedData: "The graph is generated from player data:"
options: "Select the options to analyze different aspects of Player Retention."
retentionBasis: "New player retention is calculated based on session data. If a registered player has played within latter half of the timespan, they are considered retained."
testPrompt: "Test it out:"
testResult: "Test result"
threshold: "Threshold"
thresholdUnit: "hours / week"
tips: "Tips"
usingTheGraph: "Using the Graph"
hourByHour: "Hora a Hora"
inactive: "Inactivo"
indexInactive: "Inactivo"
@ -439,6 +469,20 @@ html:
regular: "Normal"
regularPlayers: "Jugadores normal"
relativeJoinActivity: "Actividad de unión relativa"
retention:
groupByNone: "No grouping"
groupByTime: "Group registered by"
inAnytime: "any time"
inLast180d: "in the last 6 months"
inLast30d: "in the last 30 days"
inLast365d: "in the last 12 months"
inLast730d: "in the last 24 months"
inLast7d: "in the last 7 days"
inLast90d: "in the last 3 months"
playersRegisteredInTime: "Players who registered"
retainedPlayersPercentage: "Retained Players %"
timeSinceRegistered: "Time since register date"
timeStep: "Time step"
secondDeadliestWeapon: "2ª arma PvP"
seenNicknames: "Nombres de usuarios vistos"
server: "Servidor"
@ -466,6 +510,16 @@ html:
thirdDeadliestWeapon: "3ª arma PvP"
thirtyDays: "30 días"
thirtyDaysAgo: "Hace 30 días"
time:
date: "Date"
day: "Day"
days: "Days"
hours: "Hours"
month: "Month"
months: "Months"
week: "Week"
weeks: "Weeks"
year: "Year"
timesKicked: "Veces kickeado"
toMainPage: "hasta la página principal"
total: "Total"
@ -480,12 +534,17 @@ html:
trends30days: "Tendencias de 30 días"
uniquePlayers: "Jugadores únicos"
uniquePlayers7days: "Unique Players (7 days)"
unit:
percentage: "Percentage"
playerCount: "Player Count"
veryActive: "Muy activo"
weekComparison: "Comparación semanal"
weekdays: "'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado', 'Domingo'"
world: "Carga de mundo"
worldPlaytime: "Jugabilidad de mundo"
worstPing: "Peor ping"
xAxis: "X Axis"
yAxis: "Y Axis"
login:
failed: "Ingreso fallido: "
forgotPassword: "Contraseña Olvidada?"

View File

@ -115,7 +115,7 @@ command:
network: "> §2Verkoston Sivu"
players: "> §2Pelaajat"
search: "> §2${0} Tulosta haulle §f${1}§2:"
serverList: "id::nimi::uuid::version"
serverList: "id::nimi::uuid::versio"
servers: "> §2Palvelimet"
webUserList: "käyttäjänimi::linkitetty pelaajaan::lupa taso"
webUsers: "> §2${0} Verkkokäyttäjät"
@ -332,18 +332,48 @@ html:
ortographic: "Ortografinen"
geolocations: "Sijainnit"
help:
activityIndexBasis: "Activity index is based on non-AFK playtime in the past 3 weeks (21 days). Each week is considered separately."
activityIndexExample1: "If someone plays as much as threshold every week, they are given activity index ~3."
activityIndexExample2: "Very active is ~2x the threshold (y ≥ 3.75)."
activityIndexExample3: "The index approaches 5 indefinitely."
activityIndexVisual: "Here is a visualization of the curve where y = activity index, and x = playtime per week / threshold."
activityIndexWeek: "Week {}"
playtimeUnit: "hours"
retentionBasis: "New player retention is calculated based on session data. If a registered player has played within latter half of the timespan, they are considered retained."
testPrompt: "Test it out:"
testResult: "Test result"
threshold: "Threshold"
thresholdUnit: "hours / week"
activityIndexBasis: "Aktiivisuus indeksi perustuu ei-AFK peliaikaan viimeiseltä kolmelta viikolta (21 päivää). Jokaista viikkoa katsotaan erikseen"
activityIndexExample1: "Jos joku pelaa kynnyksen määrän joka viikko, on aktiivisuus indeksi noin ~3."
activityIndexExample2: "Hyvin aktiivinen pelaa ~2x kynnysarvon verran (y ≥ 3.75)."
activityIndexExample3: "Indeksi lähestyy arvoa 5 äärettömyyteen asti"
activityIndexVisual: "Alapuolelta löytyy esimerkki käyrästä, missä y = aktiivisuus indeksi, and x = peliaika viikossa / kynnys."
activityIndexWeek: "Viikko {}"
examples: "Esimerkkejä"
graph:
labels: "Voit piilottaa/näyttää ryhmän klikkaamalla nimeä käyrän alapuolella"
title: "Käyrä"
zoom: "Voit katsoa tietoja tarkemmin klikkaamalla ja vetämällä käyrän päällä"
playtimeUnit: "tuntia"
retention:
calculationStep1: "Ensin tiedot rajataan käyttämällä '<>' valintaa. Pelaajat joiden 'registerDate' osuu aikarajauksen ulkopuolelle eivät tule mukaan laskuihin"
calculationStep2: "Tämän jälkeen tiedot ryhmitellään käyttämällä '<0>' valintaa, esim. '<1>' valittuna: Kaikki pelaajat jotka rekisteröityivät tammikuussa 2023, helmikuussa 2023, jne"
calculationStep3: "Sitten '<0>' ja '<1>' valitsevat mitä esitystä näytetään."
calculationStep4: "'<>' ohjaa montako pistettä käyrällä on, esim. 'Päivät' laittaa yhden pisteen per päivä."
calculationStep5: "Jokaiselle lasketulle pisteelle tarkistetaan, vastaavatko pelaajat ehtoa."
calculationStep6: "Valitse X Akseli alapuolella nähdäksesi ehdot."
calculationStepDate: "Tämä esitysmuoto näyttää eri ryhmät jotka vielä pelaavat palvelimella. Esitys käyttää lastSeen päivämäärää. Jos x < lastSeenDate, pelaaja näkyy käyrällä."
calculationStepDeltas: "Tämä esitysmuoto on tehokkaampi jos käytetään Pelaajamäärää Y akselina. Esitys näyttää pelaajien lisääntymisen kokonaismääärän (Montako pelaajaa rekisteröityi - pelaajat jotka lopettivat pelaamisen). Esitys käyttää registered ja lastSeen päiviä. Jos registerDate < x < lastSeenDate, pelaaja näkyy käyrällä."
calculationStepPlaytime: "Tämä esitysmuoto kertoo miten pitkään pelisykli pitää pelaajat kiinnostuneita palvelimesta. Esitys käyttää peliaikaa. Jos x < playtime, pelaaja näkyy käyrällä."
calculationStepTime: "Tämä esitysmuoto kertoo miten kauan pelaajat jatkavat palvelimelle takaisin liittymistä rekisteröitymisen jälkeen. Kuvaus käyttää aikojen erotusta. Jos x < timeDifference, pelaaja näkyy käyrällä."
compareJoinAddress: "Liittymisosoitteen avulla ryhmittely mahdollistaa eri mainoskampanjoiden tehokkuuden tarkkailun."
compareMonths: "Voit verrata eri kuukausia vaihtamalla '<0>' kohtaan '<1>'"
examples:
adCampaign: "Eri mainoskampanjoiden tehokkuuden mittaus eri liittymäosotteiden avulla (anonymisoitu)"
deltas: "<> näyttää pelaajien lisääntymismäärän."
pattern: "Käyrästä paljastuu yleisempi kuvio, kun kaikki pelaajat lopettavat palvelimella pelaamisen kokonaan samaan aikaan."
plateau: "Tässä verrataan pelaajien lisääntymismäärää eri kuukausilta. Tasanteet viittaavat siihen, että on pelaajia joista Plan ei tiedä. Tässä esimerkissä Plan asennettiin tammikuussa 2022."
playtime: "Peliaika kertoo kuinka kauan pelisykli pitää pelaajat kiinnostuneina palvelimesta."
stack: "Kumulatiivisen pelaajien lisääntymismäärän voi tarkistaa päälekkäisillä pelaajamäärillä Y akselina"
howIsItCalculated: "Miten se lasketaan"
howIsItCalculatedData: "Käyrä generoidaan pelaajien tiedoista:"
options: "Eri valinnoilla voi analysoida pelaajien pysyvyyden eri puolia."
retentionBasis: "Uusien pelaajien pysyvyys perustuu istuntoihin. Jos rekisteröitynyt pelaaja on pelannut ajanjakson viimeisellä puoliskolla, ajatellaan heidän pysyneen palvelimella"
testPrompt: "Kokeile käytännössä:"
testResult: "Kokeilun tulos"
threshold: "kynnys"
thresholdUnit: "tuntia / viikko"
tips: "Neuvoja"
usingTheGraph: "Käyrän käyttö"
hourByHour: "Tunnittainen katsaus"
inactive: "Inaktiivinen"
indexInactive: "Inaktiivinen"
@ -411,7 +441,7 @@ html:
playerList: "Pelaajalista"
playerOverview: "Yhteenveto Pelaajasta"
playerPage: "Pelaajan sivu"
playerRetention: "Pelaajien säilyvyys"
playerRetention: "Pelaajien pysyvyys"
playerbase: "Pelaajakunta"
playerbaseDevelopment: "Pelaajakunnan kehitys"
playerbaseOverview: "Yhteenveto Pelaajakunnasta"
@ -439,6 +469,20 @@ html:
regular: "Kantapelaaja"
regularPlayers: "Kantapelaajia"
relativeJoinActivity: "Verrannollinen liittymis aktiivisuus"
retention:
groupByNone: "Ei ryhmittelyä"
groupByTime: "Ryhmittele rekisteröityneet"
inAnytime: "koska vain"
inLast180d: "viimeisen 6 kuukauden aikana"
inLast30d: "viimeisen 30 päivän aikana"
inLast365d: "viimeisen 12 kuukauden aikana"
inLast730d: "viimeisen 24 kuukauden aikana"
inLast7d: "viimeisen 7 päivän aikana"
inLast90d: "viimeisen 3 kuukauden aikana"
playersRegisteredInTime: "Pelaajat jotka rekisteröityivät"
retainedPlayersPercentage: "Pelaajien pysyvyys %"
timeSinceRegistered: "Aika rekisteröitymispäivästä"
timeStep: "Aika askel"
secondDeadliestWeapon: "2. PvP Ase"
seenNicknames: "Nähdyt Lempinimet"
server: "Palvelin"
@ -466,6 +510,16 @@ html:
thirdDeadliestWeapon: "3. PvP Ase"
thirtyDays: "30 päivää"
thirtyDaysAgo: "30 päivää sitten"
time:
date: "Päivämäärä"
day: "Päivä"
days: "Päivää"
hours: "Tuntia"
month: "Kuukausi"
months: "Kuukautta"
week: "Viikko"
weeks: "Viikkoa"
year: "Vuosi"
timesKicked: "Heitetty pihalle"
toMainPage: "pääsivu"
total: "Yhteensä"
@ -480,12 +534,17 @@ html:
trends30days: "Suunnat 30 päivälle"
uniquePlayers: "Uniikkeja pelaajia"
uniquePlayers7days: "Uniikkeja pelaajia (7 päivää)"
unit:
percentage: "Prosentti"
playerCount: "Pelaajamäärä"
veryActive: "Todella Aktiivinen"
weekComparison: "Viikkojen vertaus"
weekdays: "'Maanantai', 'Tiistai', 'Keskiviikko', 'Torstai', 'Perjantai', 'Lauantai', 'Sunnuntai'"
world: "Maailmojen Resurssit"
worldPlaytime: "Maailmakohtainen Peliaika"
worstPing: "Huonoin Vasteaika"
xAxis: "X akseli"
yAxis: "Y akseli"
login:
failed: "Kirjautuminen epäonnistui:"
forgotPassword: "Unohtuiko salasana?"
@ -504,7 +563,7 @@ html:
contributors:
bugreporters: "& Bugien ilmoittajat!"
code: "koodin tuottaja"
donate: "Suuret kiitokset rahallisesti tukeneille henkilöille."
donate: "Suuret kiitokset projektia rahallisesti tukeneille henkilöille."
text: 'Myös seuraavat <span class="col-plan">mahtavat ihmiset</span> ovat tukeneet kehitystä:'
translator: "kääntäjä"
developer: "on kehittänyt"

View File

@ -338,12 +338,42 @@ html:
activityIndexExample3: "The index approaches 5 indefinitely."
activityIndexVisual: "Here is a visualization of the curve where y = activity index, and x = playtime per week / threshold."
activityIndexWeek: "Week {}"
examples: "Examples"
graph:
labels: "You can hide/show a group by clicking on the label at the bottom."
title: "Graph"
zoom: "You can Zoom in by click + dragging on the graph."
playtimeUnit: "hours"
retention:
calculationStep1: "First the data is filtered using '<>' option. Any players with 'registerDate' outside the time range are ignored."
calculationStep2: "Then it is grouped into groups of players using '<0>' option, eg. With '<1>': All players who registered in January 2023, February 2023, etc"
calculationStep3: "Then the '<0>' and '<1>' options select which visualization to render."
calculationStep4: "'<>' controls how many points the graph has, eg. 'Days' has one point per day."
calculationStep5: "On each calculated point all players are checked for the condition."
calculationStep6: "Select X Axis below to see conditions."
calculationStepDate: "This visualization shows the different groups of players that are still playing on your server. The visualization uses lastSeen date. If x < lastSeenDate, the player is visible on the graph."
calculationStepDeltas: "This visualization is most effective using Player Count as the Y Axis. The visualization shows net gain of players (How many players joined minus players who stopped playing). The visualization uses both registered and lastSeen dates. If registerDate < x < lastSeenDate, the player is visible on the graph."
calculationStepPlaytime: "This visualization tells how long the gameplay loop keeps players engaged on your server. The visualization uses playtime. If x < playtime, the player is visible on the graph."
calculationStepTime: "This visualization tells how long people keep coming back to play on the server after they join the first time. The visualization uses timeDifference. If x < timeDifference, the player is visible on the graph."
compareJoinAddress: "Grouping by join address allows measuring advertising campaigns on different sites."
compareMonths: "You can compare different months by changing the '<0>' option to '<1>'"
examples:
adCampaign: "Comparing player gain of different ad campaigns using different Join Addresses (anonymized)"
deltas: "<> shows net gain of players."
pattern: "A general pattern emerges when all players start leaving the server at the same time"
plateau: "Comparing player gain of different months. Plateaus suggest there were players Plan doesn't know about. In this example Plan was installed in January 2022."
playtime: "Playtime tells how long the gameplay loop keeps players engaged on your server."
stack: "Cumulative player gain can be checked with stacked player count as Y axis"
howIsItCalculated: "How it is calculated"
howIsItCalculatedData: "The graph is generated from player data:"
options: "Select the options to analyze different aspects of Player Retention."
retentionBasis: "New player retention is calculated based on session data. If a registered player has played within latter half of the timespan, they are considered retained."
testPrompt: "Test it out:"
testResult: "Test result"
threshold: "Threshold"
thresholdUnit: "hours / week"
tips: "Tips"
usingTheGraph: "Using the Graph"
hourByHour: "Heure par Heure"
inactive: "Inactif(ve)"
indexInactive: "Inactif"
@ -439,6 +469,20 @@ html:
regular: "Régulier(ère)"
regularPlayers: "Joueurs Réguliers"
relativeJoinActivity: "Activité de Connexion relative"
retention:
groupByNone: "No grouping"
groupByTime: "Group registered by"
inAnytime: "any time"
inLast180d: "in the last 6 months"
inLast30d: "in the last 30 days"
inLast365d: "in the last 12 months"
inLast730d: "in the last 24 months"
inLast7d: "in the last 7 days"
inLast90d: "in the last 3 months"
playersRegisteredInTime: "Players who registered"
retainedPlayersPercentage: "Retained Players %"
timeSinceRegistered: "Time since register date"
timeStep: "Time step"
secondDeadliestWeapon: "2ᵉ Arme de Combat"
seenNicknames: "Surnoms vus"
server: "Serveur"
@ -466,6 +510,16 @@ html:
thirdDeadliestWeapon: "3ᵉ Arme de Combat"
thirtyDays: "30 jours"
thirtyDaysAgo: "Il y a 30 jours"
time:
date: "Date"
day: "Day"
days: "Days"
hours: "Hours"
month: "Month"
months: "Months"
week: "Week"
weeks: "Weeks"
year: "Year"
timesKicked: "Nombre d'Éjections"
toMainPage: "Retour à la page principale"
total: "Total"
@ -480,12 +534,17 @@ html:
trends30days: "Tendances sur 30 Jours"
uniquePlayers: "Joueurs Uniques"
uniquePlayers7days: "Unique Players (7 days)"
unit:
percentage: "Percentage"
playerCount: "Player Count"
veryActive: "Très Actif"
weekComparison: "Comparaison Hebdomadaire"
weekdays: "'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi', 'Dimanche'"
world: "Charge du Monde"
worldPlaytime: "Temps de Jeu par Monde"
worstPing: "Pire Latence"
xAxis: "X Axis"
yAxis: "Y Axis"
login:
failed: "Connexion échouée : "
forgotPassword: "Mot de Passe oublié ?"

View File

@ -338,12 +338,42 @@ html:
activityIndexExample3: "The index approaches 5 indefinitely."
activityIndexVisual: "Here is a visualization of the curve where y = activity index, and x = playtime per week / threshold."
activityIndexWeek: "Week {}"
examples: "Examples"
graph:
labels: "You can hide/show a group by clicking on the label at the bottom."
title: "Graph"
zoom: "You can Zoom in by click + dragging on the graph."
playtimeUnit: "hours"
retention:
calculationStep1: "First the data is filtered using '<>' option. Any players with 'registerDate' outside the time range are ignored."
calculationStep2: "Then it is grouped into groups of players using '<0>' option, eg. With '<1>': All players who registered in January 2023, February 2023, etc"
calculationStep3: "Then the '<0>' and '<1>' options select which visualization to render."
calculationStep4: "'<>' controls how many points the graph has, eg. 'Days' has one point per day."
calculationStep5: "On each calculated point all players are checked for the condition."
calculationStep6: "Select X Axis below to see conditions."
calculationStepDate: "This visualization shows the different groups of players that are still playing on your server. The visualization uses lastSeen date. If x < lastSeenDate, the player is visible on the graph."
calculationStepDeltas: "This visualization is most effective using Player Count as the Y Axis. The visualization shows net gain of players (How many players joined minus players who stopped playing). The visualization uses both registered and lastSeen dates. If registerDate < x < lastSeenDate, the player is visible on the graph."
calculationStepPlaytime: "This visualization tells how long the gameplay loop keeps players engaged on your server. The visualization uses playtime. If x < playtime, the player is visible on the graph."
calculationStepTime: "This visualization tells how long people keep coming back to play on the server after they join the first time. The visualization uses timeDifference. If x < timeDifference, the player is visible on the graph."
compareJoinAddress: "Grouping by join address allows measuring advertising campaigns on different sites."
compareMonths: "You can compare different months by changing the '<0>' option to '<1>'"
examples:
adCampaign: "Comparing player gain of different ad campaigns using different Join Addresses (anonymized)"
deltas: "<> shows net gain of players."
pattern: "A general pattern emerges when all players start leaving the server at the same time"
plateau: "Comparing player gain of different months. Plateaus suggest there were players Plan doesn't know about. In this example Plan was installed in January 2022."
playtime: "Playtime tells how long the gameplay loop keeps players engaged on your server."
stack: "Cumulative player gain can be checked with stacked player count as Y axis"
howIsItCalculated: "How it is calculated"
howIsItCalculatedData: "The graph is generated from player data:"
options: "Select the options to analyze different aspects of Player Retention."
retentionBasis: "New player retention is calculated based on session data. If a registered player has played within latter half of the timespan, they are considered retained."
testPrompt: "Test it out:"
testResult: "Test result"
threshold: "Threshold"
thresholdUnit: "hours / week"
tips: "Tips"
usingTheGraph: "Using the Graph"
hourByHour: "Hour by Hour"
inactive: "Inattivo"
indexInactive: "Inattivo"
@ -439,6 +469,20 @@ html:
regular: "Regolari"
regularPlayers: "Giocatori Regolari"
relativeJoinActivity: "Attività Entrate Relative"
retention:
groupByNone: "No grouping"
groupByTime: "Group registered by"
inAnytime: "any time"
inLast180d: "in the last 6 months"
inLast30d: "in the last 30 days"
inLast365d: "in the last 12 months"
inLast730d: "in the last 24 months"
inLast7d: "in the last 7 days"
inLast90d: "in the last 3 months"
playersRegisteredInTime: "Players who registered"
retainedPlayersPercentage: "Retained Players %"
timeSinceRegistered: "Time since register date"
timeStep: "Time step"
secondDeadliestWeapon: "2° Arma PvP Preferita"
seenNicknames: "Nick Usati"
server: "Server"
@ -466,6 +510,16 @@ html:
thirdDeadliestWeapon: "3° Arma PvP Preferita"
thirtyDays: "30 giorni"
thirtyDaysAgo: "30 giorni fa"
time:
date: "Date"
day: "Day"
days: "Days"
hours: "Hours"
month: "Month"
months: "Months"
week: "Week"
weeks: "Weeks"
year: "Year"
timesKicked: "Cacciato"
toMainPage: "Ritorna alla pagina principale"
total: "Total"
@ -480,12 +534,17 @@ html:
trends30days: "Tendenza per 30 giorni"
uniquePlayers: "Giocatori unici"
uniquePlayers7days: "Unique Players (7 days)"
unit:
percentage: "Percentage"
playerCount: "Player Count"
veryActive: "Molto Attivo"
weekComparison: "Confronto settimanale"
weekdays: "'Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato', 'Domenica'"
world: "Caricamento Mondo"
worldPlaytime: "Tempo di gioco Mondo"
worstPing: "Ping Peggiore"
xAxis: "X Axis"
yAxis: "Y Axis"
login:
failed: "Login failed: "
forgotPassword: "Forgot Password?"

View File

@ -338,12 +338,42 @@ html:
activityIndexExample3: "The index approaches 5 indefinitely."
activityIndexVisual: "Here is a visualization of the curve where y = activity index, and x = playtime per week / threshold."
activityIndexWeek: "Week {}"
examples: "Examples"
graph:
labels: "You can hide/show a group by clicking on the label at the bottom."
title: "Graph"
zoom: "You can Zoom in by click + dragging on the graph."
playtimeUnit: "hours"
retention:
calculationStep1: "First the data is filtered using '<>' option. Any players with 'registerDate' outside the time range are ignored."
calculationStep2: "Then it is grouped into groups of players using '<0>' option, eg. With '<1>': All players who registered in January 2023, February 2023, etc"
calculationStep3: "Then the '<0>' and '<1>' options select which visualization to render."
calculationStep4: "'<>' controls how many points the graph has, eg. 'Days' has one point per day."
calculationStep5: "On each calculated point all players are checked for the condition."
calculationStep6: "Select X Axis below to see conditions."
calculationStepDate: "This visualization shows the different groups of players that are still playing on your server. The visualization uses lastSeen date. If x < lastSeenDate, the player is visible on the graph."
calculationStepDeltas: "This visualization is most effective using Player Count as the Y Axis. The visualization shows net gain of players (How many players joined minus players who stopped playing). The visualization uses both registered and lastSeen dates. If registerDate < x < lastSeenDate, the player is visible on the graph."
calculationStepPlaytime: "This visualization tells how long the gameplay loop keeps players engaged on your server. The visualization uses playtime. If x < playtime, the player is visible on the graph."
calculationStepTime: "This visualization tells how long people keep coming back to play on the server after they join the first time. The visualization uses timeDifference. If x < timeDifference, the player is visible on the graph."
compareJoinAddress: "Grouping by join address allows measuring advertising campaigns on different sites."
compareMonths: "You can compare different months by changing the '<0>' option to '<1>'"
examples:
adCampaign: "Comparing player gain of different ad campaigns using different Join Addresses (anonymized)"
deltas: "<> shows net gain of players."
pattern: "A general pattern emerges when all players start leaving the server at the same time"
plateau: "Comparing player gain of different months. Plateaus suggest there were players Plan doesn't know about. In this example Plan was installed in January 2022."
playtime: "Playtime tells how long the gameplay loop keeps players engaged on your server."
stack: "Cumulative player gain can be checked with stacked player count as Y axis"
howIsItCalculated: "How it is calculated"
howIsItCalculatedData: "The graph is generated from player data:"
options: "Select the options to analyze different aspects of Player Retention."
retentionBasis: "New player retention is calculated based on session data. If a registered player has played within latter half of the timespan, they are considered retained."
testPrompt: "Test it out:"
testResult: "Test result"
threshold: "Threshold"
thresholdUnit: "hours / week"
tips: "Tips"
usingTheGraph: "Using the Graph"
hourByHour: "Hour by Hour"
inactive: "休止中"
indexInactive: "休止中"
@ -439,6 +469,20 @@ html:
regular: "よくオンラインのプレイヤー"
regularPlayers: "よくオンラインのプレイヤー"
relativeJoinActivity: "オンラインと活動との関係性"
retention:
groupByNone: "No grouping"
groupByTime: "Group registered by"
inAnytime: "any time"
inLast180d: "in the last 6 months"
inLast30d: "in the last 30 days"
inLast365d: "in the last 12 months"
inLast730d: "in the last 24 months"
inLast7d: "in the last 7 days"
inLast90d: "in the last 3 months"
playersRegisteredInTime: "Players who registered"
retainedPlayersPercentage: "Retained Players %"
timeSinceRegistered: "Time since register date"
timeStep: "Time step"
secondDeadliestWeapon: "2番目にPvPで使用されている武器"
seenNicknames: "ニックネーム一覧"
server: "サーバー"
@ -466,6 +510,16 @@ html:
thirdDeadliestWeapon: "3番目にPvPで使用されている武器"
thirtyDays: "1ヶ月"
thirtyDaysAgo: "1ヶ月前"
time:
date: "Date"
day: "Day"
days: "Days"
hours: "Hours"
month: "Month"
months: "Months"
week: "Week"
weeks: "Weeks"
year: "Year"
timesKicked: "キック回数"
toMainPage: "メインページに戻る"
total: "Total"
@ -480,12 +534,17 @@ html:
trends30days: "1ヶ月間の増減"
uniquePlayers: "接続したプレイヤーの総数"
uniquePlayers7days: "Unique Players (7 days)"
unit:
percentage: "Percentage"
playerCount: "Player Count"
veryActive: "とてもログインしている"
weekComparison: "直近1周間での比較"
weekdays: "'月曜日', '火曜日', '水曜日', '木曜日', '金曜日', '土曜日', '日曜日'"
world: "ワールドのロード数"
worldPlaytime: "ワールドごとのプレイ時間"
worstPing: "最低Ping値"
xAxis: "X Axis"
yAxis: "Y Axis"
login:
failed: "Login failed: "
forgotPassword: "Forgot Password?"

View File

@ -338,12 +338,42 @@ html:
activityIndexExample3: "The index approaches 5 indefinitely."
activityIndexVisual: "Here is a visualization of the curve where y = activity index, and x = playtime per week / threshold."
activityIndexWeek: "Week {}"
examples: "Examples"
graph:
labels: "You can hide/show a group by clicking on the label at the bottom."
title: "Graph"
zoom: "You can Zoom in by click + dragging on the graph."
playtimeUnit: "hours"
retention:
calculationStep1: "First the data is filtered using '<>' option. Any players with 'registerDate' outside the time range are ignored."
calculationStep2: "Then it is grouped into groups of players using '<0>' option, eg. With '<1>': All players who registered in January 2023, February 2023, etc"
calculationStep3: "Then the '<0>' and '<1>' options select which visualization to render."
calculationStep4: "'<>' controls how many points the graph has, eg. 'Days' has one point per day."
calculationStep5: "On each calculated point all players are checked for the condition."
calculationStep6: "Select X Axis below to see conditions."
calculationStepDate: "This visualization shows the different groups of players that are still playing on your server. The visualization uses lastSeen date. If x < lastSeenDate, the player is visible on the graph."
calculationStepDeltas: "This visualization is most effective using Player Count as the Y Axis. The visualization shows net gain of players (How many players joined minus players who stopped playing). The visualization uses both registered and lastSeen dates. If registerDate < x < lastSeenDate, the player is visible on the graph."
calculationStepPlaytime: "This visualization tells how long the gameplay loop keeps players engaged on your server. The visualization uses playtime. If x < playtime, the player is visible on the graph."
calculationStepTime: "This visualization tells how long people keep coming back to play on the server after they join the first time. The visualization uses timeDifference. If x < timeDifference, the player is visible on the graph."
compareJoinAddress: "Grouping by join address allows measuring advertising campaigns on different sites."
compareMonths: "You can compare different months by changing the '<0>' option to '<1>'"
examples:
adCampaign: "Comparing player gain of different ad campaigns using different Join Addresses (anonymized)"
deltas: "<> shows net gain of players."
pattern: "A general pattern emerges when all players start leaving the server at the same time"
plateau: "Comparing player gain of different months. Plateaus suggest there were players Plan doesn't know about. In this example Plan was installed in January 2022."
playtime: "Playtime tells how long the gameplay loop keeps players engaged on your server."
stack: "Cumulative player gain can be checked with stacked player count as Y axis"
howIsItCalculated: "How it is calculated"
howIsItCalculatedData: "The graph is generated from player data:"
options: "Select the options to analyze different aspects of Player Retention."
retentionBasis: "New player retention is calculated based on session data. If a registered player has played within latter half of the timespan, they are considered retained."
testPrompt: "Test it out:"
testResult: "Test result"
threshold: "Threshold"
thresholdUnit: "hours / week"
tips: "Tips"
usingTheGraph: "Using the Graph"
hourByHour: "Hour by Hour"
inactive: "비활성"
indexInactive: "비활성"
@ -439,6 +469,20 @@ html:
regular: "신규"
regularPlayers: "신규 플레이어"
relativeJoinActivity: "상대 조인 활동"
retention:
groupByNone: "No grouping"
groupByTime: "Group registered by"
inAnytime: "any time"
inLast180d: "in the last 6 months"
inLast30d: "in the last 30 days"
inLast365d: "in the last 12 months"
inLast730d: "in the last 24 months"
inLast7d: "in the last 7 days"
inLast90d: "in the last 3 months"
playersRegisteredInTime: "Players who registered"
retainedPlayersPercentage: "Retained Players %"
timeSinceRegistered: "Time since register date"
timeStep: "Time step"
secondDeadliestWeapon: "2nd PvP 무기"
seenNicknames: "본 별명"
server: "서버"
@ -466,6 +510,16 @@ html:
thirdDeadliestWeapon: "3rd PvP 무기"
thirtyDays: "30일"
thirtyDaysAgo: "30일 전"
time:
date: "Date"
day: "Day"
days: "Days"
hours: "Hours"
month: "Month"
months: "Months"
week: "Week"
weeks: "Weeks"
year: "Year"
timesKicked: "접속종료한 시간"
toMainPage: "메인 페이지로"
total: "Total"
@ -480,12 +534,17 @@ html:
trends30days: "30일 동안의 트렌드"
uniquePlayers: "기존 플레이어"
uniquePlayers7days: "Unique Players (7 days)"
unit:
percentage: "Percentage"
playerCount: "Player Count"
veryActive: "매우 활성화된"
weekComparison: "주 비교"
weekdays: "'월요일', '화요일', '수요일', '목요일', '금요일', '토요일', '일요일'"
world: "월드 로드"
worldPlaytime: "맵 플레이 타임"
worstPing: "Worst Ping"
xAxis: "X Axis"
yAxis: "Y Axis"
login:
failed: "Login failed: "
forgotPassword: "Forgot Password?"

View File

@ -338,12 +338,42 @@ html:
activityIndexExample3: "The index approaches 5 indefinitely."
activityIndexVisual: "Here is a visualization of the curve where y = activity index, and x = playtime per week / threshold."
activityIndexWeek: "Week {}"
examples: "Examples"
graph:
labels: "You can hide/show a group by clicking on the label at the bottom."
title: "Graph"
zoom: "You can Zoom in by click + dragging on the graph."
playtimeUnit: "hours"
retention:
calculationStep1: "First the data is filtered using '<>' option. Any players with 'registerDate' outside the time range are ignored."
calculationStep2: "Then it is grouped into groups of players using '<0>' option, eg. With '<1>': All players who registered in January 2023, February 2023, etc"
calculationStep3: "Then the '<0>' and '<1>' options select which visualization to render."
calculationStep4: "'<>' controls how many points the graph has, eg. 'Days' has one point per day."
calculationStep5: "On each calculated point all players are checked for the condition."
calculationStep6: "Select X Axis below to see conditions."
calculationStepDate: "This visualization shows the different groups of players that are still playing on your server. The visualization uses lastSeen date. If x < lastSeenDate, the player is visible on the graph."
calculationStepDeltas: "This visualization is most effective using Player Count as the Y Axis. The visualization shows net gain of players (How many players joined minus players who stopped playing). The visualization uses both registered and lastSeen dates. If registerDate < x < lastSeenDate, the player is visible on the graph."
calculationStepPlaytime: "This visualization tells how long the gameplay loop keeps players engaged on your server. The visualization uses playtime. If x < playtime, the player is visible on the graph."
calculationStepTime: "This visualization tells how long people keep coming back to play on the server after they join the first time. The visualization uses timeDifference. If x < timeDifference, the player is visible on the graph."
compareJoinAddress: "Grouping by join address allows measuring advertising campaigns on different sites."
compareMonths: "You can compare different months by changing the '<0>' option to '<1>'"
examples:
adCampaign: "Comparing player gain of different ad campaigns using different Join Addresses (anonymized)"
deltas: "<> shows net gain of players."
pattern: "A general pattern emerges when all players start leaving the server at the same time"
plateau: "Comparing player gain of different months. Plateaus suggest there were players Plan doesn't know about. In this example Plan was installed in January 2022."
playtime: "Playtime tells how long the gameplay loop keeps players engaged on your server."
stack: "Cumulative player gain can be checked with stacked player count as Y axis"
howIsItCalculated: "How it is calculated"
howIsItCalculatedData: "The graph is generated from player data:"
options: "Select the options to analyze different aspects of Player Retention."
retentionBasis: "New player retention is calculated based on session data. If a registered player has played within latter half of the timespan, they are considered retained."
testPrompt: "Test it out:"
testResult: "Test result"
threshold: "Threshold"
thresholdUnit: "hours / week"
tips: "Tips"
usingTheGraph: "Using the Graph"
hourByHour: "Uur voor uur"
inactive: "Inactief"
indexInactive: "Inactief"
@ -439,6 +469,20 @@ html:
regular: "Regulier"
regularPlayers: "Reguliere speler"
relativeJoinActivity: "Relatieve deelname aan activiteit"
retention:
groupByNone: "No grouping"
groupByTime: "Group registered by"
inAnytime: "any time"
inLast180d: "in the last 6 months"
inLast30d: "in the last 30 days"
inLast365d: "in the last 12 months"
inLast730d: "in the last 24 months"
inLast7d: "in the last 7 days"
inLast90d: "in the last 3 months"
playersRegisteredInTime: "Players who registered"
retainedPlayersPercentage: "Retained Players %"
timeSinceRegistered: "Time since register date"
timeStep: "Time step"
secondDeadliestWeapon: "2e PvP-wapen"
seenNicknames: "Bijnamen gezien"
server: "Server"
@ -466,6 +510,16 @@ html:
thirdDeadliestWeapon: "3e PvP-wapen"
thirtyDays: "30 dagen"
thirtyDaysAgo: "30 dagen geleden"
time:
date: "Date"
day: "Day"
days: "Days"
hours: "Hours"
month: "Month"
months: "Months"
week: "Week"
weeks: "Weeks"
year: "Year"
timesKicked: "Aantal keer afgetapt"
toMainPage: "naar hoofdpagina"
total: "Total"
@ -480,12 +534,17 @@ html:
trends30days: "Trends voor 30 dagen"
uniquePlayers: "Unieke spelers"
uniquePlayers7days: "Unique Players (7 days)"
unit:
percentage: "Percentage"
playerCount: "Player Count"
veryActive: "Heel Actief"
weekComparison: "Weekvergelijking"
weekdays: "'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrijdag', 'Zaterdag', 'Zondag'"
world: "Wereldbelasting"
worldPlaytime: "Wereld speeltijd"
worstPing: "Slechtste ping"
xAxis: "X Axis"
yAxis: "Y Axis"
login:
failed: "Login niet gelukt: "
forgotPassword: "Wachtwoord Vergeten?"

View File

@ -338,12 +338,42 @@ html:
activityIndexExample3: "The index approaches 5 indefinitely."
activityIndexVisual: "Here is a visualization of the curve where y = activity index, and x = playtime per week / threshold."
activityIndexWeek: "Week {}"
examples: "Examples"
graph:
labels: "You can hide/show a group by clicking on the label at the bottom."
title: "Graph"
zoom: "You can Zoom in by click + dragging on the graph."
playtimeUnit: "hours"
retention:
calculationStep1: "First the data is filtered using '<>' option. Any players with 'registerDate' outside the time range are ignored."
calculationStep2: "Then it is grouped into groups of players using '<0>' option, eg. With '<1>': All players who registered in January 2023, February 2023, etc"
calculationStep3: "Then the '<0>' and '<1>' options select which visualization to render."
calculationStep4: "'<>' controls how many points the graph has, eg. 'Days' has one point per day."
calculationStep5: "On each calculated point all players are checked for the condition."
calculationStep6: "Select X Axis below to see conditions."
calculationStepDate: "This visualization shows the different groups of players that are still playing on your server. The visualization uses lastSeen date. If x < lastSeenDate, the player is visible on the graph."
calculationStepDeltas: "This visualization is most effective using Player Count as the Y Axis. The visualization shows net gain of players (How many players joined minus players who stopped playing). The visualization uses both registered and lastSeen dates. If registerDate < x < lastSeenDate, the player is visible on the graph."
calculationStepPlaytime: "This visualization tells how long the gameplay loop keeps players engaged on your server. The visualization uses playtime. If x < playtime, the player is visible on the graph."
calculationStepTime: "This visualization tells how long people keep coming back to play on the server after they join the first time. The visualization uses timeDifference. If x < timeDifference, the player is visible on the graph."
compareJoinAddress: "Grouping by join address allows measuring advertising campaigns on different sites."
compareMonths: "You can compare different months by changing the '<0>' option to '<1>'"
examples:
adCampaign: "Comparing player gain of different ad campaigns using different Join Addresses (anonymized)"
deltas: "<> shows net gain of players."
pattern: "A general pattern emerges when all players start leaving the server at the same time"
plateau: "Comparing player gain of different months. Plateaus suggest there were players Plan doesn't know about. In this example Plan was installed in January 2022."
playtime: "Playtime tells how long the gameplay loop keeps players engaged on your server."
stack: "Cumulative player gain can be checked with stacked player count as Y axis"
howIsItCalculated: "How it is calculated"
howIsItCalculatedData: "The graph is generated from player data:"
options: "Select the options to analyze different aspects of Player Retention."
retentionBasis: "New player retention is calculated based on session data. If a registered player has played within latter half of the timespan, they are considered retained."
testPrompt: "Test it out:"
testResult: "Test result"
threshold: "Threshold"
thresholdUnit: "hours / week"
tips: "Tips"
usingTheGraph: "Using the Graph"
hourByHour: "Hour by Hour"
inactive: "Inactive"
indexInactive: "Inativo"
@ -439,6 +469,20 @@ html:
regular: "Regular"
regularPlayers: "Regular Players"
relativeJoinActivity: "Relative Join Activity"
retention:
groupByNone: "No grouping"
groupByTime: "Group registered by"
inAnytime: "any time"
inLast180d: "in the last 6 months"
inLast30d: "in the last 30 days"
inLast365d: "in the last 12 months"
inLast730d: "in the last 24 months"
inLast7d: "in the last 7 days"
inLast90d: "in the last 3 months"
playersRegisteredInTime: "Players who registered"
retainedPlayersPercentage: "Retained Players %"
timeSinceRegistered: "Time since register date"
timeStep: "Time step"
secondDeadliestWeapon: "2nd PvP Weapon"
seenNicknames: "Nicks Vistos"
server: "Servidor"
@ -466,6 +510,16 @@ html:
thirdDeadliestWeapon: "3rd PvP Weapon"
thirtyDays: "30 days"
thirtyDaysAgo: "30 days ago"
time:
date: "Date"
day: "Day"
days: "Days"
hours: "Hours"
month: "Month"
months: "Months"
week: "Week"
weeks: "Weeks"
year: "Year"
timesKicked: "Vezes Kickado"
toMainPage: "to main page"
total: "Total"
@ -480,12 +534,17 @@ html:
trends30days: "Trends for 30 days"
uniquePlayers: "Jogadores Únicos"
uniquePlayers7days: "Unique Players (7 days)"
unit:
percentage: "Percentage"
playerCount: "Player Count"
veryActive: "Muito Ativo"
weekComparison: "Week Comparison"
weekdays: "'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'"
world: "World Load"
worldPlaytime: "Tempo de Jogo por Mundo"
worstPing: "Worst Ping"
xAxis: "X Axis"
yAxis: "Y Axis"
login:
failed: "Login failed: "
forgotPassword: "Forgot Password?"

View File

@ -338,12 +338,42 @@ html:
activityIndexExample3: "The index approaches 5 indefinitely."
activityIndexVisual: "Here is a visualization of the curve where y = activity index, and x = playtime per week / threshold."
activityIndexWeek: "Week {}"
examples: "Examples"
graph:
labels: "You can hide/show a group by clicking on the label at the bottom."
title: "Graph"
zoom: "You can Zoom in by click + dragging on the graph."
playtimeUnit: "hours"
retention:
calculationStep1: "First the data is filtered using '<>' option. Any players with 'registerDate' outside the time range are ignored."
calculationStep2: "Then it is grouped into groups of players using '<0>' option, eg. With '<1>': All players who registered in January 2023, February 2023, etc"
calculationStep3: "Then the '<0>' and '<1>' options select which visualization to render."
calculationStep4: "'<>' controls how many points the graph has, eg. 'Days' has one point per day."
calculationStep5: "On each calculated point all players are checked for the condition."
calculationStep6: "Select X Axis below to see conditions."
calculationStepDate: "This visualization shows the different groups of players that are still playing on your server. The visualization uses lastSeen date. If x < lastSeenDate, the player is visible on the graph."
calculationStepDeltas: "This visualization is most effective using Player Count as the Y Axis. The visualization shows net gain of players (How many players joined minus players who stopped playing). The visualization uses both registered and lastSeen dates. If registerDate < x < lastSeenDate, the player is visible on the graph."
calculationStepPlaytime: "This visualization tells how long the gameplay loop keeps players engaged on your server. The visualization uses playtime. If x < playtime, the player is visible on the graph."
calculationStepTime: "This visualization tells how long people keep coming back to play on the server after they join the first time. The visualization uses timeDifference. If x < timeDifference, the player is visible on the graph."
compareJoinAddress: "Grouping by join address allows measuring advertising campaigns on different sites."
compareMonths: "You can compare different months by changing the '<0>' option to '<1>'"
examples:
adCampaign: "Comparing player gain of different ad campaigns using different Join Addresses (anonymized)"
deltas: "<> shows net gain of players."
pattern: "A general pattern emerges when all players start leaving the server at the same time"
plateau: "Comparing player gain of different months. Plateaus suggest there were players Plan doesn't know about. In this example Plan was installed in January 2022."
playtime: "Playtime tells how long the gameplay loop keeps players engaged on your server."
stack: "Cumulative player gain can be checked with stacked player count as Y axis"
howIsItCalculated: "How it is calculated"
howIsItCalculatedData: "The graph is generated from player data:"
options: "Select the options to analyze different aspects of Player Retention."
retentionBasis: "New player retention is calculated based on session data. If a registered player has played within latter half of the timespan, they are considered retained."
testPrompt: "Test it out:"
testResult: "Test result"
threshold: "Threshold"
thresholdUnit: "hours / week"
tips: "Tips"
usingTheGraph: "Using the Graph"
hourByHour: "Статистика по часам"
inactive: "Неактивный"
indexInactive: "Неактивный"
@ -439,6 +469,20 @@ html:
regular: "Постоянный"
regularPlayers: "Постоянные игроки"
relativeJoinActivity: "Сравнительная активность присоединения"
retention:
groupByNone: "No grouping"
groupByTime: "Group registered by"
inAnytime: "any time"
inLast180d: "in the last 6 months"
inLast30d: "in the last 30 days"
inLast365d: "in the last 12 months"
inLast730d: "in the last 24 months"
inLast7d: "in the last 7 days"
inLast90d: "in the last 3 months"
playersRegisteredInTime: "Players who registered"
retainedPlayersPercentage: "Retained Players %"
timeSinceRegistered: "Time since register date"
timeStep: "Time step"
secondDeadliestWeapon: "2-е PvP оружие"
seenNicknames: "Увиденные никнеймы"
server: "Сервер"
@ -466,6 +510,16 @@ html:
thirdDeadliestWeapon: "3-е PvP оружие"
thirtyDays: "30 дней"
thirtyDaysAgo: "30 дней назад"
time:
date: "Date"
day: "Day"
days: "Days"
hours: "Hours"
month: "Month"
months: "Months"
week: "Week"
weeks: "Weeks"
year: "Year"
timesKicked: "Кол-во киков"
toMainPage: "На главную страницу"
total: "Total"
@ -480,12 +534,17 @@ html:
trends30days: "тенденция за 30 дней"
uniquePlayers: "Уникальные игроки"
uniquePlayers7days: "Unique Players (7 days)"
unit:
percentage: "Percentage"
playerCount: "Player Count"
veryActive: "Очень активный"
weekComparison: "Сравнение за неделю"
weekdays: "'Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота', 'Воскресенье'"
world: "Загрузка мира"
worldPlaytime: "Время игры в мире"
worstPing: "Наихудший пинг"
xAxis: "X Axis"
yAxis: "Y Axis"
login:
failed: "Логин неудачен: "
forgotPassword: "Забыли пароль?"

View File

@ -338,12 +338,42 @@ html:
activityIndexExample3: "The index approaches 5 indefinitely."
activityIndexVisual: "Here is a visualization of the curve where y = activity index, and x = playtime per week / threshold."
activityIndexWeek: "Week {}"
examples: "Examples"
graph:
labels: "You can hide/show a group by clicking on the label at the bottom."
title: "Graph"
zoom: "You can Zoom in by click + dragging on the graph."
playtimeUnit: "hours"
retention:
calculationStep1: "First the data is filtered using '<>' option. Any players with 'registerDate' outside the time range are ignored."
calculationStep2: "Then it is grouped into groups of players using '<0>' option, eg. With '<1>': All players who registered in January 2023, February 2023, etc"
calculationStep3: "Then the '<0>' and '<1>' options select which visualization to render."
calculationStep4: "'<>' controls how many points the graph has, eg. 'Days' has one point per day."
calculationStep5: "On each calculated point all players are checked for the condition."
calculationStep6: "Select X Axis below to see conditions."
calculationStepDate: "This visualization shows the different groups of players that are still playing on your server. The visualization uses lastSeen date. If x < lastSeenDate, the player is visible on the graph."
calculationStepDeltas: "This visualization is most effective using Player Count as the Y Axis. The visualization shows net gain of players (How many players joined minus players who stopped playing). The visualization uses both registered and lastSeen dates. If registerDate < x < lastSeenDate, the player is visible on the graph."
calculationStepPlaytime: "This visualization tells how long the gameplay loop keeps players engaged on your server. The visualization uses playtime. If x < playtime, the player is visible on the graph."
calculationStepTime: "This visualization tells how long people keep coming back to play on the server after they join the first time. The visualization uses timeDifference. If x < timeDifference, the player is visible on the graph."
compareJoinAddress: "Grouping by join address allows measuring advertising campaigns on different sites."
compareMonths: "You can compare different months by changing the '<0>' option to '<1>'"
examples:
adCampaign: "Comparing player gain of different ad campaigns using different Join Addresses (anonymized)"
deltas: "<> shows net gain of players."
pattern: "A general pattern emerges when all players start leaving the server at the same time"
plateau: "Comparing player gain of different months. Plateaus suggest there were players Plan doesn't know about. In this example Plan was installed in January 2022."
playtime: "Playtime tells how long the gameplay loop keeps players engaged on your server."
stack: "Cumulative player gain can be checked with stacked player count as Y axis"
howIsItCalculated: "How it is calculated"
howIsItCalculatedData: "The graph is generated from player data:"
options: "Select the options to analyze different aspects of Player Retention."
retentionBasis: "New player retention is calculated based on session data. If a registered player has played within latter half of the timespan, they are considered retained."
testPrompt: "Test it out:"
testResult: "Test result"
threshold: "Threshold"
thresholdUnit: "hours / week"
tips: "Tips"
usingTheGraph: "Using the Graph"
hourByHour: "Saat saat"
inactive: "Etkin değil"
indexInactive: "Etkisiz"
@ -439,6 +469,20 @@ html:
regular: "Düzenli"
regularPlayers: "Normal Oyuncular"
relativeJoinActivity: "Göreli Birleştirme Etkinliği"
retention:
groupByNone: "No grouping"
groupByTime: "Group registered by"
inAnytime: "any time"
inLast180d: "in the last 6 months"
inLast30d: "in the last 30 days"
inLast365d: "in the last 12 months"
inLast730d: "in the last 24 months"
inLast7d: "in the last 7 days"
inLast90d: "in the last 3 months"
playersRegisteredInTime: "Players who registered"
retainedPlayersPercentage: "Retained Players %"
timeSinceRegistered: "Time since register date"
timeStep: "Time step"
secondDeadliestWeapon: "2. PvP Silahı"
seenNicknames: "Görülen takma adlar"
server: "Sunucu"
@ -466,6 +510,16 @@ html:
thirdDeadliestWeapon: "3. PvP Silahı"
thirtyDays: "30 gün"
thirtyDaysAgo: "30 gün önce"
time:
date: "Date"
day: "Day"
days: "Days"
hours: "Hours"
month: "Month"
months: "Months"
week: "Week"
weeks: "Weeks"
year: "Year"
timesKicked: "Kere Atılmış"
toMainPage: "Ana Sayfaya"
total: "Total"
@ -480,12 +534,17 @@ html:
trends30days: "30 günlük trendler"
uniquePlayers: "Sunucuya İlk Defa Girenler"
uniquePlayers7days: "Unique Players (7 days)"
unit:
percentage: "Percentage"
playerCount: "Player Count"
veryActive: "Çok Aktif"
weekComparison: "Hafta Karşılaştırması"
weekdays: "'Pazartesi', 'Salı', 'Çarşamba', 'Perşembe', 'Cuma', 'Cumartesi', 'Pazar'"
world: "Dünya Yükle"
worldPlaytime: "Dünya Oyun Süresi"
worstPing: "En kötü Ping"
xAxis: "X Axis"
yAxis: "Y Axis"
login:
failed: "Giriş başarısız:"
forgotPassword: "Parolanızı mı unuttunuz?"

View File

@ -338,12 +338,42 @@ html:
activityIndexExample3: "The index approaches 5 indefinitely."
activityIndexVisual: "Here is a visualization of the curve where y = activity index, and x = playtime per week / threshold."
activityIndexWeek: "Week {}"
examples: "Examples"
graph:
labels: "You can hide/show a group by clicking on the label at the bottom."
title: "Graph"
zoom: "You can Zoom in by click + dragging on the graph."
playtimeUnit: "hours"
retention:
calculationStep1: "First the data is filtered using '<>' option. Any players with 'registerDate' outside the time range are ignored."
calculationStep2: "Then it is grouped into groups of players using '<0>' option, eg. With '<1>': All players who registered in January 2023, February 2023, etc"
calculationStep3: "Then the '<0>' and '<1>' options select which visualization to render."
calculationStep4: "'<>' controls how many points the graph has, eg. 'Days' has one point per day."
calculationStep5: "On each calculated point all players are checked for the condition."
calculationStep6: "Select X Axis below to see conditions."
calculationStepDate: "This visualization shows the different groups of players that are still playing on your server. The visualization uses lastSeen date. If x < lastSeenDate, the player is visible on the graph."
calculationStepDeltas: "This visualization is most effective using Player Count as the Y Axis. The visualization shows net gain of players (How many players joined minus players who stopped playing). The visualization uses both registered and lastSeen dates. If registerDate < x < lastSeenDate, the player is visible on the graph."
calculationStepPlaytime: "This visualization tells how long the gameplay loop keeps players engaged on your server. The visualization uses playtime. If x < playtime, the player is visible on the graph."
calculationStepTime: "This visualization tells how long people keep coming back to play on the server after they join the first time. The visualization uses timeDifference. If x < timeDifference, the player is visible on the graph."
compareJoinAddress: "Grouping by join address allows measuring advertising campaigns on different sites."
compareMonths: "You can compare different months by changing the '<0>' option to '<1>'"
examples:
adCampaign: "Comparing player gain of different ad campaigns using different Join Addresses (anonymized)"
deltas: "<> shows net gain of players."
pattern: "A general pattern emerges when all players start leaving the server at the same time"
plateau: "Comparing player gain of different months. Plateaus suggest there were players Plan doesn't know about. In this example Plan was installed in January 2022."
playtime: "Playtime tells how long the gameplay loop keeps players engaged on your server."
stack: "Cumulative player gain can be checked with stacked player count as Y axis"
howIsItCalculated: "How it is calculated"
howIsItCalculatedData: "The graph is generated from player data:"
options: "Select the options to analyze different aspects of Player Retention."
retentionBasis: "New player retention is calculated based on session data. If a registered player has played within latter half of the timespan, they are considered retained."
testPrompt: "Test it out:"
testResult: "Test result"
threshold: "Threshold"
thresholdUnit: "hours / week"
tips: "Tips"
usingTheGraph: "Using the Graph"
hourByHour: "按小時查看"
inactive: "不活躍"
indexInactive: "不活躍"
@ -439,6 +469,20 @@ html:
regular: "普通"
regularPlayers: "普通玩家"
relativeJoinActivity: "最近加入活動"
retention:
groupByNone: "No grouping"
groupByTime: "Group registered by"
inAnytime: "any time"
inLast180d: "in the last 6 months"
inLast30d: "in the last 30 days"
inLast365d: "in the last 12 months"
inLast730d: "in the last 24 months"
inLast7d: "in the last 7 days"
inLast90d: "in the last 3 months"
playersRegisteredInTime: "Players who registered"
retainedPlayersPercentage: "Retained Players %"
timeSinceRegistered: "Time since register date"
timeStep: "Time step"
secondDeadliestWeapon: "第二致命的 PvP 武器"
seenNicknames: "使用過的暱稱"
server: "伺服器"
@ -466,6 +510,16 @@ html:
thirdDeadliestWeapon: "第三致命的 PvP 武器"
thirtyDays: "30 天"
thirtyDaysAgo: "30 天前"
time:
date: "Date"
day: "Day"
days: "Days"
hours: "Hours"
month: "Month"
months: "Months"
week: "Week"
weeks: "Weeks"
year: "Year"
timesKicked: "被踢出次數"
toMainPage: "回到主頁面"
total: "總計"
@ -480,12 +534,17 @@ html:
trends30days: "30 天趨勢"
uniquePlayers: "獨立玩家"
uniquePlayers7days: "獨立玩家 (7 days)"
unit:
percentage: "Percentage"
playerCount: "Player Count"
veryActive: "非常活躍"
weekComparison: "每週對比"
weekdays: "'星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日'"
world: "世界載入"
worldPlaytime: "世界遊玩時間"
worstPing: "最高延遲"
xAxis: "X Axis"
yAxis: "Y Axis"
login:
failed: "登入失敗:"
forgotPassword: "忘記密碼?"

View File

@ -20,13 +20,13 @@
"@testing-library/user-event": "^14.4.3",
"axios": "^1.3.4",
"bootstrap": "^5.2.3",
"datatables.net": "^1.13.3",
"datatables.net-bs5": "^1.13.3",
"datatables.net-responsive-bs5": "^2.4.0",
"datatables.net": "^1.13.4",
"datatables.net-bs5": "^1.13.4",
"datatables.net-responsive-bs5": "^2.4.1",
"highcharts": "^10.3.3",
"i18next": "^22.4.10",
"i18next": "^22.4.11",
"i18next-chained-backend": "^4.2.0",
"i18next-http-backend": "^2.1.1",
"i18next-http-backend": "^2.2.0",
"i18next-localstorage-backend": "^4.1.0",
"masonry-layout": "^4.2.2",
"react": "^18.2.0",
@ -37,9 +37,9 @@
"react-mcjsonchat": "^1.0.0",
"react-router-dom": "6",
"react-scripts": "5.0.1",
"sass": "^1.58.3",
"sass": "^1.59.3",
"source-map-explorer": "^2.5.2",
"swagger-ui": "^4.17.0",
"swagger-ui": "^4.18.1",
"web-vitals": "^3.0.2"
},
"scripts": {

View File

@ -37,12 +37,14 @@ const ServerPerformance = React.lazy(() => import("./views/server/ServerPerforma
const ServerPluginData = React.lazy(() => import("./views/server/ServerPluginData"));
const ServerWidePluginData = React.lazy(() => import("./views/server/ServerWidePluginData"));
const ServerJoinAddresses = React.lazy(() => import("./views/server/ServerJoinAddresses"));
const ServerPlayerRetention = React.lazy(() => import("./views/server/ServerPlayerRetention"));
const NetworkPage = React.lazy(() => import("./views/layout/NetworkPage"));
const NetworkOverview = React.lazy(() => import("./views/network/NetworkOverview"));
const NetworkServers = React.lazy(() => import("./views/network/NetworkServers"));
const NetworkSessions = React.lazy(() => import("./views/network/NetworkSessions"));
const NetworkJoinAddresses = React.lazy(() => import("./views/network/NetworkJoinAddresses"));
const NetworkPlayerRetention = React.lazy(() => import("./views/network/NetworkPlayerRetention"));
const NetworkGeolocations = React.lazy(() => import("./views/network/NetworkGeolocations"));
const NetworkPlayerbaseOverview = React.lazy(() => import("./views/network/NetworkPlayerbaseOverview"));
const NetworkPerformance = React.lazy(() => import("./views/network/NetworkPerformance"));
@ -145,7 +147,7 @@ function App() {
<Route path="pvppve" element={<Lazy><ServerPvpPve/></Lazy>}/>
<Route path="playerbase" element={<Lazy><PlayerbaseOverview/></Lazy>}/>
<Route path="join-addresses" element={<Lazy><ServerJoinAddresses/></Lazy>}/>
<Route path="retention" element={<></>}/>
<Route path="retention" element={<Lazy><ServerPlayerRetention/></Lazy>}/>
<Route path="players" element={<Lazy><ServerPlayers/></Lazy>}/>
<Route path="geolocations" element={<Lazy><ServerGeolocations/></Lazy>}/>
<Route path="performance" element={<Lazy><ServerPerformance/></Lazy>}/>
@ -165,6 +167,7 @@ function App() {
{!staticSite &&
<Route path="performance" element={<Lazy><NetworkPerformance/></Lazy>}/>}
<Route path="playerbase" element={<Lazy><NetworkPlayerbaseOverview/></Lazy>}/>
<Route path="retention" element={<Lazy><NetworkPlayerRetention/></Lazy>}/>
<Route path="join-addresses" element={<Lazy><NetworkJoinAddresses/></Lazy>}/>
<Route path="players" element={<Lazy><AllPlayers/></Lazy>}/>
<Route path="geolocations" element={<Lazy><NetworkGeolocations/></Lazy>}/>

View File

@ -17,9 +17,9 @@ const TabButton = ({name, href, icon, color, active}) => {
const TabButtons = ({tabs, selectedTab}) => {
return (
<ul className="nav nav-tabs" role="tablist">
{tabs.map((tab, i) => (
{tabs.map(tab => (
<TabButton
key={i}
key={tab.href}
name={tab.name}
href={tab.href}
icon={tab.icon}
@ -37,10 +37,10 @@ const CardTabs = ({tabs}) => {
const [selectedTab, setSelectedTab] = useState(firstTab);
useEffect(() => {
setSelectedTab(hash && tabs ? tabs.find(t => t.href === hash.substring(1)).href : firstTab)
setSelectedTab(hash && tabs ? tabs.find(t => t.href === hash.substring(1))?.href : firstTab)
}, [hash, tabs, firstTab])
const tabContent = tabs.find(t => t.href === selectedTab).element;
const tabContent = tabs.find(t => t.href === selectedTab)?.element;
return (
<>
<TabButtons tabs={tabs} selectedTab={selectedTab}/>

View File

@ -3,13 +3,14 @@ import {FontAwesomeIcon as Fa} from "@fortawesome/react-fontawesome";
import {Card} from "react-bootstrap";
import {useTranslation} from "react-i18next";
const CardHeader = ({icon, color, label}) => {
const CardHeader = ({icon, color, label, children}) => {
const {t} = useTranslation();
return (
<Card.Header>
<h6 className="col-black">
<h6 className="col-black" style={{width: "100%"}}>
<Fa icon={icon} className={"col-" + color}/> {t(label)}
{children}
</h6>
</Card.Header>
)

View File

@ -0,0 +1,322 @@
import React, {useCallback, useEffect, useMemo, useState} from 'react';
import {Card, Col, Row} from "react-bootstrap";
import CardHeader from "../CardHeader";
import {faUsersViewfinder} from "@fortawesome/free-solid-svg-icons";
import {useTranslation} from "react-i18next";
import ExtendableCardBody from "../../layout/extension/ExtendableCardBody";
import {BasicDropdown} from "../../input/BasicDropdown";
import {useDataRequest} from "../../../hooks/dataFetchHook";
import {fetchPlayerJoinAddresses, fetchRetentionData} from "../../../service/serverService";
import {ErrorViewCard} from "../../../views/ErrorView";
import {CardLoader} from "../../navigation/Loader";
import {tooltip} from "../../../util/graphs";
import {hsvToRgb, randomHSVColor, rgbToHexString, withReducedSaturation} from "../../../util/colors";
import LineGraph from "../../graphs/LineGraph";
import FunctionPlotGraph from "../../graphs/FunctionPlotGraph";
import {useTheme} from "../../../hooks/themeHook";
import {useNavigation} from "../../../hooks/navigationHook";
import {FontAwesomeIcon as Fa} from "@fortawesome/react-fontawesome";
import {faQuestionCircle} from "@fortawesome/free-regular-svg-icons";
const dayMs = 24 * 3600000;
const getWeek = (date) => {
const onejan = new Date(date.getFullYear(), 0, 1);
const today = new Date(date.getFullYear(), date.getMonth(), date.getDate());
const dayOfYear = ((today - onejan + 86400000) / 86400000);
return Math.ceil(dayOfYear / 7)
};
const PlayerRetentionGraphCard = ({identifier}) => {
const {t} = useTranslation();
const {nightModeEnabled} = useTheme();
const {setHelpModalTopic} = useNavigation();
const openHelp = useCallback(() => setHelpModalTopic('player-retention-graph'), [setHelpModalTopic]);
const time = useMemo(() => new Date().getTime(), []);
const {data, loadingError} = useDataRequest(fetchRetentionData, [identifier]);
const {
data: joinAddressData,
loadingError: joinAddressLoadingError
} = useDataRequest(fetchPlayerJoinAddresses, [identifier]);
const [selectedWindow, setSelectedWindow] = useState('days');
const windowOptions = useMemo(() => [
{name: 'hours', displayName: t('html.label.time.hours'), increment: 3600000},
{name: 'days', displayName: t('html.label.time.days'), increment: dayMs},
{name: 'weeks', displayName: t('html.label.time.weeks'), increment: 7 * dayMs},
{name: 'months', displayName: t('html.label.time.months'), increment: 30 * dayMs},
], [t]);
const [selectedGroup, setSelectedGroup] = useState('registered-7d');
const groupOptions = useMemo(() => [
{name: 'registered-7d', displayName: t('html.label.retention.inLast7d'), start: time - 7 * dayMs},
{name: 'registered-30d', displayName: t('html.label.retention.inLast30d'), start: time - 30 * dayMs},
{name: 'registered-3m', displayName: t('html.label.retention.inLast90d'), start: time - 3 * 30 * dayMs},
{name: 'registered-6m', displayName: t('html.label.retention.inLast180d'), start: time - 6 * 30 * dayMs},
{name: 'registered-1y', displayName: t('html.label.retention.inLast365d'), start: time - 365 * dayMs},
{name: 'registered-2y', displayName: t('html.label.retention.inLast730d'), start: time - 2 * 365 * dayMs},
{name: 'registered-ever', displayName: t('html.label.retention.inAnytime'), start: 0},
], [t, time]);
const [selectedGroupBy, setSelectedGroupBy] = useState('none');
const groupByOptions = useMemo(() => [
{name: 'none', displayName: t('html.label.retention.groupByNone')},
{name: 'days', displayName: t('html.label.time.day')},
{name: 'weeks', displayName: t('html.label.time.week')},
{name: 'months', displayName: t('html.label.time.month')},
{name: 'years', displayName: t('html.label.time.year')},
{name: 'joinAddress', displayName: t('html.label.joinAddress')},
], [t]);
const [selectedYAxis, setSelectedYAxis] = useState('percentage');
const yAxisOptions = useMemo(() => [
{name: 'percentage', displayName: t('html.label.unit.percentage')},
{name: 'count', displayName: t('html.label.unit.playerCount')},
{name: 'count-stacked', displayName: t('html.label.unit.playerCount') + ' (' + t('html.label.stacked') + ')'},
], [t]);
const [selectedAxis, setSelectedAxis] = useState('time');
const axisOptions = useMemo(() => [
{name: 'time', displayName: t('html.label.retention.timeSinceRegistered')},
{name: 'playtime', displayName: t('html.label.playtime')},
{name: 'date', displayName: t('html.label.time.date')},
{name: 'deltas', displayName: t('html.label.time.date') + ' > ' + t('html.label.registered')},
], [t]);
const [series, setSeries] = useState([]);
const [graphOptions, setGraphOptions] = useState({title: {text: ''},});
const mapToData = useCallback(async (dataToMap, start) => {
const total = dataToMap.length;
let seriesData;
const increment = windowOptions.find(option => option.name === selectedWindow).increment;
const xAxis = axisOptions.find(option => option.name === selectedAxis).name;
switch (xAxis) {
case 'deltas':
const retainedBasedOnDeltas = [];
const firstRegisterDeltasStart = dataToMap[0].registerDate - dataToMap[0].registerDate % increment;
let previousRetained = -1;
for (let date = firstRegisterDeltasStart; date < time; date += increment) {
const filter = player => player.registerDate <= date && player.lastSeenDate >= date;
const retainedSince = dataToMap.filter(filter).length;
retainedBasedOnDeltas.push([date, selectedYAxis === 'percentage' ? retainedSince * 100.0 / total : retainedSince]);
if (previousRetained === retainedSince && retainedSince <= 0.5) break;
if (previousRetained !== -1 || retainedSince > 0) previousRetained = retainedSince;
}
seriesData = retainedBasedOnDeltas;
break;
case 'date':
const retainedBasedOnDate = [];
const firstRegisterDateStart = dataToMap[0].registerDate - dataToMap[0].registerDate % increment;
for (let date = firstRegisterDateStart; date < time; date += increment) {
const filter = player => player.lastSeenDate >= date;
const retainedSince = dataToMap.filter(filter).length;
retainedBasedOnDate.push([date, selectedYAxis === 'percentage' ? retainedSince * 100.0 / total : retainedSince]);
if (retainedSince < 0.5) break;
}
seriesData = retainedBasedOnDate;
break;
case 'time':
const retainedBasedOnTime = [];
for (let i = 0; i < time; i += increment) {
const retainedSince = dataToMap.filter(point => point.timeDifference > i).length;
retainedBasedOnTime.push([(i) / increment, selectedYAxis === 'percentage' ? retainedSince * 100.0 / total : retainedSince]);
if (retainedSince < 0.5) break;
}
seriesData = retainedBasedOnTime;
break;
case 'playtime':
default:
const retainedBasedOnPlaytime = [];
for (let i = start; i < time; i += increment) {
const retainedSince = dataToMap.filter(point => point.playtime > i - start).length;
retainedBasedOnPlaytime.push([(i - start) / increment, selectedYAxis === 'percentage' ? retainedSince * 100.0 / total : retainedSince]);
if (retainedSince < 0.5) break;
}
seriesData = retainedBasedOnPlaytime;
break;
}
return seriesData;
}, [selectedWindow, windowOptions, selectedAxis, axisOptions, selectedYAxis, time])
const group = useCallback(async (filtered, joinAddressData) => {
const grouped = {};
const groupBy = groupByOptions.find(option => option.name === selectedGroupBy).name;
for (const point of filtered) {
const date = new Date();
date.setTime(point.registerDate);
switch (groupBy) {
case 'days':
const day = date.toISOString().substring(0, 10);
if (!grouped[day]) grouped[day] = [];
grouped[day].push(point);
break;
case 'weeks':
const week = date.getUTCFullYear() + '-week-' + getWeek(date);
if (!grouped[week]) grouped[week] = [];
grouped[week].push(point);
break;
case 'months':
const month = date.toISOString().substring(0, 7);
if (!grouped[month]) grouped[month] = [];
grouped[month].push(point);
break;
case 'years':
const year = date.getUTCFullYear();
if (!grouped[year]) grouped[year] = [];
grouped[year].push(point);
break;
case 'joinAddress':
const joinAddress = joinAddressData[point.playerUUID];
if (!grouped[joinAddress]) grouped[joinAddress] = [];
grouped[joinAddress].push(point);
break;
case 'none':
default:
grouped['all'] = filtered;
break;
}
}
return grouped;
}, [groupByOptions, selectedGroupBy]);
const createSeries = useCallback(async (retentionData, joinAddressData) => {
const start = groupOptions.find(option => option.name === selectedGroup).start;
const filtered = retentionData.filter(point => point.registerDate > start)
.sort((a, b) => a.registerDate - b.registerDate);
const grouped = await group(filtered, joinAddressData);
let colorIndex = 1;
return Promise.all(Object.entries(grouped).map(async group => {
const name = group[0];
const groupData = group[1];
const color = rgbToHexString(hsvToRgb(randomHSVColor(colorIndex)));
colorIndex++;
const mapped = await mapToData(groupData, start);
if (mapped.filter(point => point[1] === 0).length === mapped.length) {
// Don't include all zeros series
return [];
}
return [{
name: name,
type: selectedYAxis === 'count-stacked' ? 'areaspline' : 'spline',
tooltip: tooltip.twoDecimals,
data: mapped,
color: nightModeEnabled ? withReducedSaturation(color) : color
}];
}));
}, [nightModeEnabled, mapToData, groupOptions, selectedGroup, selectedYAxis, group]);
useEffect(() => {
if (!data || !joinAddressData) return;
createSeries(data.player_retention, joinAddressData.join_address_by_player).then(series => setSeries(series.flat()));
}, [data, joinAddressData, createSeries, setSeries]);
useEffect(() => {
const windowName = windowOptions.find(option => option.name === selectedWindow).displayName;
const unitLabel = selectedYAxis === 'percentage' ? t('html.label.retention.retainedPlayersPercentage') : t('html.label.players');
const axisName = axisOptions.find(option => option.name === selectedAxis).displayName;
setGraphOptions({
title: {text: ''},
rangeSelector: selectedAxis === 'date' ? {
selected: 2,
buttons: [{
type: 'day',
count: 7,
text: '7d'
}, {
type: 'month',
count: 1,
text: '30d'
}, {
type: 'all',
text: 'All'
}]
} : undefined,
chart: {
zooming: {
type: 'xy'
}
},
plotOptions: {
areaspline: {
fillOpacity: nightModeEnabled ? 0.2 : 0.4,
stacking: 'normal'
}
},
legend: {
enabled: selectedGroupBy !== 'none',
},
xAxis: {
zoomEnabled: true,
title: {
text: selectedAxis === 'date' || selectedAxis === 'deltas' ? t('html.label.time.date') : axisName + ' (' + windowName + ')'
}
},
yAxis: {
zoomEnabled: true,
title: {text: unitLabel},
max: selectedYAxis === 'percentage' ? 100 : undefined,
min: 0
},
tooltip: selectedAxis === 'date' || selectedAxis === 'deltas' ? {
enabled: true,
valueDecimals: 2,
pointFormat: (selectedGroupBy !== 'none' ? '{series.name} - ' : '') + '<b>{point.y} ' + (selectedYAxis === 'percentage' ? '%' : t('html.label.players')) + '</b>'
} : {
enabled: true,
valueDecimals: 2,
headerFormat: '{point.x} ' + windowName + '<br>',
pointFormat: (selectedGroupBy !== 'none' ? '{series.name} - ' : '') + '<b>{point.y} ' + (selectedYAxis === 'percentage' ? '%' : t('html.label.players')) + '</b>'
},
series: series
})
}, [t, nightModeEnabled, series, selectedGroupBy, axisOptions, selectedAxis, windowOptions, selectedWindow, selectedYAxis]);
if (loadingError) return <ErrorViewCard error={loadingError}/>
if (joinAddressLoadingError) return <ErrorViewCard error={joinAddressLoadingError}/>
if (!data || !joinAddressData) return <CardLoader/>;
return (
<Card>
<CardHeader icon={faUsersViewfinder} color={'indigo'} label={t('html.label.playerRetention')}>
<button className={"float-end"} onClick={openHelp}>
<Fa className={"col-blue"}
icon={faQuestionCircle}/>
</button>
</CardHeader>
<ExtendableCardBody id={'card-body-' + (identifier ? 'server-' : 'network-') + 'player-retention'}>
<Row>
<Col>
<label>{t('html.label.retention.timeStep')}</label>
<BasicDropdown selected={selectedWindow} options={windowOptions} onChange={setSelectedWindow}/>
</Col>
<Col>
<label>{t('html.label.retention.playersRegisteredInTime')}</label>
<BasicDropdown selected={selectedGroup} options={groupOptions} onChange={setSelectedGroup}/>
</Col>
<Col>
<label>{t('html.label.retention.groupByTime')}</label>
<BasicDropdown selected={selectedGroupBy} options={groupByOptions}
onChange={setSelectedGroupBy}/>
</Col>
<Col>
<label>{t('html.label.xAxis')}</label>
<BasicDropdown selected={selectedAxis} options={axisOptions} onChange={setSelectedAxis}/>
</Col>
<Col>
<label>{t('html.label.yAxis')}</label>
<BasicDropdown selected={selectedYAxis} options={yAxisOptions} onChange={setSelectedYAxis}/>
</Col>
</Row>
<hr/>
{(selectedAxis !== 'date' && selectedAxis !== 'deltas') &&
<FunctionPlotGraph id={'retention-graph'} options={graphOptions} tall/>}
{(selectedAxis === 'date' || selectedAxis === 'deltas') &&
<LineGraph id={'retention-graph'} options={graphOptions} tall/>}
</ExtendableCardBody>
</Card>
)
};
export default PlayerRetentionGraphCard

View File

@ -11,7 +11,7 @@ const QuickViewGraphCard = ({server}) => {
<Card>
<CardHeader icon={faChartArea} color={'light-blue'}
label={server.name + ' ' + t('html.label.onlineActivity') + ' (' + t('html.label.thirtyDays') + ')'}/>
<PlayersOnlineGraph data={server.playersOnline}/>
<PlayersOnlineGraph data={{playersOnline: server.playersOnline, color: server.playersOnlineColor}}/>
</Card>
)
};

View File

@ -14,6 +14,7 @@ const FunctionPlotGraph = ({
yPlotBands,
xPlotLines,
xPlotBands,
options
}) => {
const {t} = useTranslation()
const {graphTheming, nightModeEnabled} = useTheme();
@ -23,7 +24,7 @@ const FunctionPlotGraph = ({
Accessibility(Highcharts);
Highcharts.setOptions({lang: {noData: t('html.label.noDataToDisplay')}})
Highcharts.setOptions(graphTheming);
Highcharts.chart(id, {
Highcharts.chart(id, options ? options : {
yAxis: {
plotLines: yPlotLines,
plotBands: yPlotBands
@ -44,7 +45,7 @@ const FunctionPlotGraph = ({
},
series: series
});
}, [series, id, t, graphTheming, nightModeEnabled, legendEnabled,
}, [options, series, id, t, graphTheming, nightModeEnabled, legendEnabled,
yPlotLines, yPlotBands, xPlotLines, xPlotBands]);
const style = tall ? {height: "450px"} : undefined;

View File

@ -16,7 +16,9 @@ const LineGraph = ({
selectedRange,
extremes,
onSetExtremes,
alreadyOffsetTimezone
alreadyOffsetTimezone,
options,
extraModules
}) => {
const {t} = useTranslation()
const {graphTheming, nightModeEnabled} = useTheme();
@ -26,9 +28,14 @@ const LineGraph = ({
useEffect(() => {
NoDataDisplay(Highcharts);
Accessibility(Highcharts);
if (extraModules) {
for (const extraModule of extraModules) {
extraModule(Highcharts);
}
}
Highcharts.setOptions({lang: {noData: t('html.label.noDataToDisplay')}})
Highcharts.setOptions(graphTheming);
setGraph(Highcharts.stockChart(id, {
setGraph(Highcharts.stockChart(id, options ? options : {
rangeSelector: {
selected: selectedRange !== undefined ? selectedRange : 2,
buttons: linegraphButtons
@ -58,7 +65,7 @@ const LineGraph = ({
},
series: series
}));
}, [series, id, t,
}, [options, extraModules, series, id, t,
graphTheming, nightModeEnabled, alreadyOffsetTimezone, timeZoneOffsetMinutes,
legendEnabled, yAxis,
onSetExtremes, setGraph, selectedRange]);

View File

@ -1,28 +1,16 @@
import React from 'react';
import DropdownToggle from "react-bootstrap/lib/esm/DropdownToggle";
import {FontAwesomeIcon as Fa} from "@fortawesome/react-fontawesome";
import DropdownMenu from "react-bootstrap/lib/esm/DropdownMenu";
import DropdownItem from "react-bootstrap/lib/esm/DropdownItem";
import {useTranslation} from "react-i18next";
import {Dropdown} from "react-bootstrap";
import React, {useCallback} from 'react';
export const DropDownWithOptions = ({selected, optionList, onChange, optionLabelMapper, icon, title}) => {
const {t} = useTranslation();
export const BasicDropdown = ({selected, onChange, options}) => {
const onSelect = useCallback(({target}) => {
onChange(target.value);
}, [onChange]);
return (
<Dropdown className="float-end" style={{position: "absolute", right: "0.5rem"}} title={t(title)}>
<DropdownToggle variant=''>
<Fa icon={icon}/> {t(optionLabelMapper ? optionLabelMapper(selected) : selected)}
</DropdownToggle>
<DropdownMenu>
<h6 className="dropdown-header">{t(title)}</h6>
{optionList.map((option, i) => (
<DropdownItem key={i} onClick={() => onChange(option)}>
{t(optionLabelMapper ? optionLabelMapper(option) : option)}
</DropdownItem>
))}
</DropdownMenu>
</Dropdown>
<select onChange={onSelect}
className="form-select form-select-sm"
defaultValue={selected}>
{options.map((option, i) =>
<option key={option.name} value={option.name} disabled={option.disabled}>{option.displayName}</option>)}
</select>
)
};

View File

@ -6,6 +6,7 @@ import {useTranslation} from "react-i18next";
import ActivityIndexHelp from "./help/ActivityIndexHelp";
import {faQuestionCircle} from "@fortawesome/free-regular-svg-icons";
import NewPlayerRetentionHelp from "./help/NewPlayerRetentionHelp";
import PlayerRetentionGraphHelp from "./help/PlayerRetentionGraphHelp";
const HelpModal = () => {
const {t} = useTranslation();
@ -20,6 +21,10 @@ const HelpModal = () => {
"new-player-retention": {
title: t('html.label.newPlayerRetention'),
body: <NewPlayerRetentionHelp/>
},
"player-retention-graph": {
title: t('html.label.playerRetention'),
body: <PlayerRetentionGraphHelp/>
}
}

View File

@ -14,11 +14,11 @@ const inverseIndex = y => {
return -2 * y / (Math.PI * (y - 5));
}
const activityIndexPlot = () => {
const activityIndexPlot = (maxValue) => {
const data = []
let x;
for (x = 0; x <= 3.5; x += 0.01) {
for (x = 0; x <= Math.max(3.5, maxValue); x += 0.01) {
data.push([x, indexValue(x)]);
}
return data;
@ -67,7 +67,8 @@ const ActivityIndexHelp = () => {
const [result, setResult] = useState(0);
const series = useMemo(() => {
const data = activityIndexPlot();
const inverse = inverseIndex(result);
const data = activityIndexPlot(inverse);
return [{
name: t('html.label.activityIndex') + ' y=5-5/(πx/2)+1',
data: data,
@ -77,7 +78,7 @@ const ActivityIndexHelp = () => {
}, {
name: t('html.label.help.testResult'),
type: 'scatter',
data: [{x: inverseIndex(result), y: result, marker: {radius: 10}}],
data: [{x: inverse, y: result, marker: {radius: 10}}],
pointPlacement: 0,
width: 5,
tooltip: tooltip.twoDecimals,

View File

@ -0,0 +1,273 @@
import React, {useCallback, useEffect, useMemo, useState} from 'react';
import {useTranslation} from "react-i18next";
import {faChartArea, faGears} from "@fortawesome/free-solid-svg-icons";
import CardTabs from "../../CardTabs"
import {BasicDropdown} from "../../input/BasicDropdown";
import RangeSlider from "react-bootstrap-range-slider";
import {tooltip} from "../../../util/graphs";
import {hsvToRgb, randomHSVColor, rgbToHexString} from "../../../util/colors";
import FunctionPlotGraph from "../../graphs/FunctionPlotGraph";
import LineGraph from "../../graphs/LineGraph";
const PlayerRetentionGraphHelp = () => {
const {t} = useTranslation();
const [selectedAxis, setSelectedAxis] = useState('time');
const axisOptions = useMemo(() => [
{name: 'time', displayName: t('html.label.retention.timeSinceRegistered')},
{name: 'playtime', displayName: t('html.label.playtime')},
{name: 'date', displayName: t('html.label.time.date')},
{name: 'deltas', displayName: t('html.label.time.date') + ' > ' + t('html.label.registered')},
], [t]);
const [x, setX] = useState(0);
const updateX = useCallback(event => setX(event.target.value), [setX]);
useEffect(() => {
setX(0);
}, [selectedAxis, setX]);
const data = useMemo(() => [
{x: 1},
{x: 2},
{x: 9},
{x: 24},
], []);
const [series, setSeries] = useState([]);
const [graphOptions, setGraphOptions] = useState({title: {text: ''},});
useEffect(() => {
const d = []
for (let i = 0; i < x; i++) {
d.push([i, data.filter(item => item.x > i).length * 100.0 / data.length]);
}
for (let i = x; i < 30; i++) {
d.push([i, null]);
}
const color = rgbToHexString(hsvToRgb(randomHSVColor(1)));
setSeries([{
name: 'name',
type: 'spline',
tooltip: tooltip.twoDecimals,
data: d,
color
}]);
}, [x, data, setSeries]);
useEffect(() => {
const unitLabel = t('html.label.retention.retainedPlayersPercentage');
const windowName = t('html.label.time.hours');
const axisName = axisOptions.find(option => option.name === selectedAxis).displayName;
setGraphOptions({
title: {text: ''},
rangeSelector: selectedAxis === 'date' ? {
selected: 2,
buttons: [{
type: 'day',
count: 7,
text: '7d'
}, {
type: 'month',
count: 1,
text: '30d'
}, {
type: 'all',
text: 'All'
}]
} : undefined,
legend: {
enabled: false,
},
plotOptions: {
series: {animation: false}
},
xAxis: {
zoomEnabled: true,
title: {
text: selectedAxis === 'date' || selectedAxis === 'deltas' ? t('html.label.time.date') : axisName + ' (' + windowName + ')'
}
},
yAxis: {
zoomEnabled: true,
title: {text: unitLabel},
max: 100,
min: 0
},
tooltip: selectedAxis === 'date' || selectedAxis === 'deltas' ? {
enabled: true,
valueDecimals: 2,
pointFormat: '<b>{point.y} %</b>'
} : {
enabled: true,
valueDecimals: 2,
headerFormat: '{point.x} ' + windowName + '<br>',
pointFormat: '<b>{point.y} %</b>'
},
series: series
})
}, [t, series, axisOptions, selectedAxis]);
const disabledColor = 'rgba(0, 0, 0, 0.05)';
return (
<>
<CardTabs tabs={[
{
name: t('html.label.help.usingTheGraph'), icon: faGears, color: 'indigo', href: 'data-explanation',
element: <div className={'mt-2'}>
<p>{t('html.label.help.retention.options')}</p>
<h4>{t('html.label.help.tips')}</h4>
<ul>
<li>{t('html.label.help.retention.compareMonths')
.replace('<0>', t('html.label.retention.groupByTime'))
.replace('<1>', t('html.label.time.month'))}
</li>
<li>{t('html.label.help.retention.compareJoinAddress')}</li>
<li>{t('html.label.help.graph.zoom')}</li>
<li>{t('html.label.help.graph.labels')}</li>
</ul>
<hr/>
<h3>{t('html.label.help.retention.howIsItCalculated')}</h3>
<p>{t('html.label.help.retention.howIsItCalculatedData')}</p>
<pre>
{'{\n playerUUID,\n registerDate,\n lastSeenDate,\n timeDifference = lastSeenDate - registerDate,\n playtime\n joinAddress\n}'}
</pre>
<ol>
<li>{t('html.label.help.retention.calculationStep1')
.replace('<>', t('html.label.retention.timeSinceRegistered'))}
</li>
<li>{t('html.label.help.retention.calculationStep2')
.replace('<0>', t('html.label.retention.groupByTime'))
.replace('<1>', t('html.label.time.month'))}
</li>
<li>{t('html.label.help.retention.calculationStep3')
.replace('<0>', t('html.label.xAxis'))
.replace('<1>', t('html.label.yAxis'))}
</li>
<li>{t('html.label.help.retention.calculationStep4')
.replace('<>', t('html.label.retention.timeStep'))}
</li>
<li>
<p className={'m-0'}>{t('html.label.help.retention.calculationStep5')}
{t('html.label.help.retention.calculationStep6')}</p>
<label>{t('html.label.xAxis')}</label>
<BasicDropdown selected={selectedAxis} options={axisOptions}
onChange={setSelectedAxis}/>
<div className={'mt-2'}>
<p>
{selectedAxis === 'time' && <>
{t('html.label.help.retention.calculationStepTime')}
</>}
{selectedAxis === 'playtime' && <>
{t('html.label.help.retention.calculationStepPlaytime')}
</>}
{selectedAxis === 'date' && <>
{t('html.label.help.retention.calculationStepDate')}
</>}
{selectedAxis === 'deltas' && <>
{t('html.label.help.retention.calculationStepDeltas')}
</>}</p>
{selectedAxis !== 'date' && selectedAxis !== 'deltas' && <>
<h4>{t('html.label.help.testPrompt')}</h4>
<table className={"table"}>
<thead>
<tr>
<th>{t('html.label.player')}</th>
<th>{axisOptions.find(option => option.name === selectedAxis).displayName}</th>
<th>{t('html.label.playerRetention')}</th>
</tr>
</thead>
<tbody>
<tr style={x <= 24 ? {} : {backgroundColor: disabledColor}}>
<td>Pooh</td>
<td>1d 53s</td>
<th>{x <= 24 ? t('plugin.generic.yes') : t('plugin.generic.no')}</th>
</tr>
<tr style={x <= 9 ? {} : {backgroundColor: disabledColor}}>
<td>Piglet</td>
<td>9h 12min</td>
<th>{x <= 9 ? t('plugin.generic.yes') : t('plugin.generic.no')}</th>
</tr>
<tr style={x <= 2 ? {} : {backgroundColor: disabledColor}}>
<td>Rabbit</td>
<td>2h</td>
<th>{x <= 2 ? t('plugin.generic.yes') : t('plugin.generic.no')}</th>
</tr>
<tr style={x <= 1 ? {} : {backgroundColor: disabledColor}}>
<td>Tigger</td>
<td>1h 59min 59s</td>
<th>{x <= 1 ? t('plugin.generic.yes') : t('plugin.generic.no')}</th>
</tr>
</tbody>
<tfoot>
<tr>
<th>Retention</th>
<th></th>
{x <= 1 && <td>100%</td>}
{1 < x && x <= 2 && <td>75%</td>}
{2 < x && x <= 9 && <td>50%</td>}
{9 < x && x <= 24 && <td>25%</td>}
{x > 24 && <td>0%</td>}
</tr>
</tfoot>
</table>
<label>x = {x} {t('html.label.time.hours')}</label>
<RangeSlider
value={x}
onChange={updateX}
min={0}
max={25}
tooltip={'off'}/>
{(selectedAxis !== 'date' && selectedAxis !== 'deltas') &&
<FunctionPlotGraph id={'retention-help-graph'} options={graphOptions}/>}
{(selectedAxis === 'date' || selectedAxis === 'deltas') &&
<LineGraph id={'retention-help-graph'} options={graphOptions}/>}
</>}
</div>
</li>
</ol>
</div>
}, {
name: t('html.label.help.examples'),
icon: faChartArea,
color: 'indigo',
href: 'interesting-combinations',
element: <div className={'mt-2'}>
<label>{t('html.label.retention.timeSinceRegistered')}</label>
<img className={'w-100'} alt={t('html.label.help.graph.title')} loading={'lazy'}
src={'https://raw.githubusercontent.com/plan-player-analytics/drawio-diagrams-storage/master/image/screenshot/225086629-69e70c66-69d5-4a08-afbc-c63b218ec9bc.png'}/>
<hr/>
<label>{t('html.label.help.retention.examples.playtime')}</label>
<img className={'w-100'} alt={t('html.label.help.graph.title')} loading={'lazy'}
src={'https://raw.githubusercontent.com/plan-player-analytics/drawio-diagrams-storage/master/image/screenshot/225086773-ae5646e5-0d9e-4016-9f1d-c392d3d25c07.png'}/>
<hr/>
<label>{t('html.label.time.date')}</label>
<img className={'w-100'} alt={t('html.label.help.graph.title')} loading={'lazy'}
src={'https://raw.githubusercontent.com/plan-player-analytics/drawio-diagrams-storage/master/image/screenshot/225086880-c6e88e9a-125d-4513-b86a-ca61b4d752b2.png'}/>
<hr/>
<label>{t('html.label.help.retention.examples.deltas')
.replace('<>', t('html.label.time.date') + ' > ' + t('html.label.registered'))}</label>
<img className={'w-100'} alt={t('html.label.help.graph.title')} loading={'lazy'}
src={'https://raw.githubusercontent.com/plan-player-analytics/drawio-diagrams-storage/master/image/screenshot/225087066-0cacc7e4-aacc-48ff-97d7-ba2cf6a368ff.png'}/>
<hr/>
<label>{t('html.label.help.retention.examples.pattern')}</label>
<img className={'w-100'} alt={t('html.label.help.graph.title')} loading={'lazy'}
src={'https://raw.githubusercontent.com/plan-player-analytics/drawio-diagrams-storage/master/image/screenshot/225087273-04331324-6bc3-4efb-8864-166b5b3d4a89.png'}/>
<hr/>
<label>{t('html.label.help.retention.examples.plateau')}</label>
<img className={'w-100'} alt={t('html.label.help.graph.title')} loading={'lazy'}
src={'https://raw.githubusercontent.com/plan-player-analytics/drawio-diagrams-storage/master/image/screenshot/225087828-8db2da1a-578d-43fc-abc5-2aa09e97935e.png'}/>
<hr/>
<label>{t('html.label.help.retention.examples.adCampaign')}</label>
<img className={'w-100'} alt={t('html.label.help.graph.title')} loading={'lazy'}
src={'https://raw.githubusercontent.com/plan-player-analytics/drawio-diagrams-storage/master/image/screenshot/225088901-2e30caf6-f141-4998-91de-2034fda5b7e9.png'}/>
<hr/>
<label>{t('html.label.help.retention.examples.stack')}</label>
<img className={'w-100'} alt={t('html.label.help.graph.title')} loading={'lazy'}
src={'https://raw.githubusercontent.com/plan-player-analytics/drawio-diagrams-storage/master/image/screenshot/225722723-cde69a1a-09fd-4e19-a8fe-993d60435652.png'}/>
</div>
}
]}/>
</>
)
};
export default PlayerRetentionGraphHelp

View File

@ -268,3 +268,43 @@ const fetchJoinAddressByDayNetwork = async (timestamp) => {
if (staticSite) url = `/data/graph-joinAddressByDay.json`;
return doGetRequest(url, timestamp);
}
export const fetchRetentionData = async (timestamp, identifier) => {
if (identifier) {
return await fetchServerRetentionData(timestamp, identifier);
} else {
return await fetchNetworkRetentionData(timestamp);
}
}
const fetchServerRetentionData = async (timestamp, identifier) => {
let url = `/v1/retention?server=${identifier}`;
if (staticSite) url = `/data/retention-${identifier}.json`;
return doGetRequest(url, timestamp);
}
const fetchNetworkRetentionData = async (timestamp) => {
let url = `/v1/retention`;
if (staticSite) url = `/data/retention.json`;
return doGetRequest(url, timestamp);
}
export const fetchPlayerJoinAddresses = async (timestamp, identifier) => {
if (identifier) {
return await fetchServerPlayerJoinAddresses(timestamp, identifier);
} else {
return await fetchNetworkPlayerJoinAddresses(timestamp);
}
}
const fetchServerPlayerJoinAddresses = async (timestamp, identifier) => {
let url = `/v1/joinAddresses?server=${identifier}`;
if (staticSite) url = `/data/joinAddresses-${identifier}.json`;
return doGetRequest(url, timestamp);
}
const fetchNetworkPlayerJoinAddresses = async (timestamp) => {
let url = `/v1/joinAddresses`;
if (staticSite) url = `/data/joinAddresses.json`;
return doGetRequest(url, timestamp);
}

View File

@ -37,6 +37,7 @@
--color-night-text-dark-bg: #eee8d5;
--color-theme: var(--color-plan);
--color-night: var(--color-night-dark-blue);
}
a {

View File

@ -123,6 +123,69 @@ export const colorClassToBgClass = colorClass => {
return "bg-" + colorClassToColorName(colorClass);
}
export const hsvToRgb = ([h, s, v]) => {
let r, g, b;
const i = Math.floor(h * 6);
const f = h * 6 - i;
const p = v * (1 - s);
const q = v * (1 - f * s);
const t = v * (1 - (1 - f) * s);
switch (i % 6) {
case 0:
r = v;
g = t;
b = p;
break;
case 1:
r = q;
g = v;
b = p;
break;
case 2:
r = p;
g = v;
b = t;
break;
case 3:
r = p;
g = q;
b = v;
break;
case 4:
r = t;
g = p;
b = v;
break;
case 5:
r = v;
g = p;
b = q;
break;
default:
break;
}
return [r * 255, g * 255, b * 255];
}
export const randomHSVColor = (i) => {
const goldenRatioConjugate = 0.618033988749895;
const hue = i * goldenRatioConjugate % 1;
const saturation = 0.7;
const value = 0.7 + (Math.random() / 10);
return [hue, saturation, value]
}
export const rgbToHexString = ([r, g, b]) => {
return '#' + rgbToHex(r) + rgbToHex(g) + rgbToHex(b);
}
const rgbToHex = (component) => {
return Math.floor(component).toString(16).padStart(2, '0');
}
// From https://stackoverflow.com/a/3732187
export const withReducedSaturation = hex => {
const saturationReduction = 0.70;

View File

@ -13,7 +13,8 @@ import {
faSearch,
faServer,
faUserGroup,
faUsers
faUsers,
faUsersViewfinder
} from "@fortawesome/free-solid-svg-icons";
import {useAuth} from "../../hooks/authenticationHook";
import Sidebar from "../../components/navigation/Sidebar";
@ -77,7 +78,7 @@ const NetworkSidebar = () => {
href: "playerbase"
},
{name: 'html.label.joinAddresses', icon: faLocationArrow, href: "join-addresses"},
// {name: 'html.label.playerRetention', icon: faUsersViewfinder, href: "retention"},
{name: 'html.label.playerRetention', icon: faUsersViewfinder, href: "retention"},
{name: 'html.label.playerList', icon: faUserGroup, href: "players"},
{name: 'html.label.geolocations', icon: faGlobe, href: "geolocations"},
]
@ -87,7 +88,7 @@ const NetworkSidebar = () => {
{name: 'html.label.pluginsOverview', icon: faCubes, href: "plugins-overview"}
]
if (extensionData) {
if (extensionData?.extensions) {
extensionData.extensions.filter(extension => extension.wide)
.map(extension => extension.extensionInformation)
.map(info => {

View File

@ -13,7 +13,8 @@ import {
faLocationArrow,
faSearch,
faUserGroup,
faUsers
faUsers,
faUsersViewfinder
} from "@fortawesome/free-solid-svg-icons";
import {useAuth} from "../../hooks/authenticationHook";
import Sidebar from "../../components/navigation/Sidebar";
@ -67,7 +68,7 @@ const ServerSidebar = () => {
href: "playerbase"
},
{name: 'html.label.joinAddresses', icon: faLocationArrow, href: "join-addresses"},
// {name: 'html.label.playerRetention', icon: faUsersViewfinder, href: "retention"},
{name: 'html.label.playerRetention', icon: faUsersViewfinder, href: "retention"},
{name: 'html.label.playerList', icon: faUserGroup, href: "players"},
{name: 'html.label.geolocations', icon: faGlobe, href: "geolocations"},
]
@ -78,7 +79,7 @@ const ServerSidebar = () => {
{name: 'html.label.pluginsOverview', icon: faCubes, href: "plugins-overview"}
]
if (extensionData) {
if (extensionData?.extensions) {
extensionData.extensions.filter(extension => extension.wide)
.map(extension => extension.extensionInformation)
.map(info => {

View File

@ -0,0 +1,21 @@
import React from 'react';
import ExtendableRow from "../../components/layout/extension/ExtendableRow";
import {Col} from "react-bootstrap";
import LoadIn from "../../components/animation/LoadIn";
import PlayerRetentionGraphCard from "../../components/cards/common/PlayerRetentionGraphCard";
const NetworkPlayerRetention = () => {
return (
<LoadIn>
<section className="network-retention">
<ExtendableRow id={'row-network-retention-0'}>
<Col lg={12}>
<PlayerRetentionGraphCard identifier={null}/>
</Col>
</ExtendableRow>
</section>
</LoadIn>
)
};
export default NetworkPlayerRetention

View File

@ -10,7 +10,7 @@ const PlayerPluginData = () => {
const {player} = usePlayer();
const {serverName} = useParams();
const extensions = player.extensions.find(extension => extension.serverName === serverName)
const extensions = player.extensions ? player.extensions.find(extension => extension.serverName === serverName) : {};
useEffect(() => {
const masonryRow = document.getElementById('extension-masonry-row');
@ -48,7 +48,7 @@ const PlayerPluginData = () => {
<Row id="extension-masonry-row"
data-masonry='{"percentPosition": true, "itemSelector": ".extension-wrapper"}'
style={{overflowY: 'hidden'}}>
{extensions.extensionData.map((extension, i) =>
{extensions?.extensionData?.map((extension, i) =>
<ExtensionCardWrapper key={'ext-' + i} extension={extension}>
<ExtensionCard extension={extension}/>
</ExtensionCardWrapper>

View File

@ -0,0 +1,23 @@
import React from 'react';
import ExtendableRow from "../../components/layout/extension/ExtendableRow";
import {Col} from "react-bootstrap";
import LoadIn from "../../components/animation/LoadIn";
import PlayerRetentionGraphCard from "../../components/cards/common/PlayerRetentionGraphCard";
import {useParams} from "react-router-dom";
const ServerPlayerRetention = () => {
const {identifier} = useParams();
return (
<LoadIn>
<section className="server-retention">
<ExtendableRow id={'row-server-retention-0'}>
<Col lg={12}>
<PlayerRetentionGraphCard identifier={identifier}/>
</Col>
</ExtendableRow>
</section>
</LoadIn>
)
};
export default ServerPlayerRetention

View File

@ -11,7 +11,7 @@ import ErrorView from "../ErrorView";
const ServerPluginData = () => {
const {t} = useTranslation();
const {extensionData, extensionDataLoadingError} = useServerExtensionContext();
const extensions = useMemo(() => extensionData ? extensionData.extensions.filter(extension => !extension.wide) : [], [extensionData]);
const extensions = useMemo(() => extensionData?.extensions ? extensionData.extensions.filter(extension => !extension.wide) : [], [extensionData]);
useEffect(() => {
const masonryRow = document.getElementById('extension-masonry-row');

View File

@ -1022,13 +1022,20 @@
core-js-pure "^3.25.1"
regenerator-runtime "^0.13.11"
"@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.2", "@babel/runtime@^7.19.4", "@babel/runtime@^7.20.6", "@babel/runtime@^7.20.7", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
"@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.2", "@babel/runtime@^7.19.4", "@babel/runtime@^7.20.6", "@babel/runtime@^7.20.7", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
version "7.20.13"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.13.tgz#7055ab8a7cff2b8f6058bf6ae45ff84ad2aded4b"
integrity sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==
dependencies:
regenerator-runtime "^0.13.11"
"@babel/runtime@^7.12.1":
version "7.21.0"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.0.tgz#5b55c9d394e5fcf304909a8b00c07dc217b56673"
integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==
dependencies:
regenerator-runtime "^0.13.11"
"@babel/template@^7.16.7", "@babel/template@^7.3.3":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155"
@ -2216,7 +2223,7 @@
dependencies:
"@types/unist" "*"
"@types/hoist-non-react-statics@^3.3.0":
"@types/hoist-non-react-statics@^3.3.1":
version "3.3.1"
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
@ -2327,16 +2334,6 @@
dependencies:
"@types/react" "*"
"@types/react-redux@^7.1.20":
version "7.1.24"
resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.24.tgz#6caaff1603aba17b27d20f8ad073e4c077e975c0"
integrity sha512-7FkurKcS1k0FHZEtdbbgN8Oc6b+stGSfZYjQGicofJ0j4U0qIn/jaSvnP2pLwZKiai3/17xqqxkkrxTgN8UNbQ==
dependencies:
"@types/hoist-non-react-statics" "^3.3.0"
"@types/react" "*"
hoist-non-react-statics "^3.3.0"
redux "^4.0.0"
"@types/react-transition-group@^4.4.4":
version "4.4.5"
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.5.tgz#aae20dcf773c5aa275d5b9f7cdbca638abc5e416"
@ -2414,6 +2411,11 @@
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d"
integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==
"@types/use-sync-external-store@^0.0.3":
version "0.0.3"
resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz#b6725d5f4af24ace33b36fafd295136e75509f43"
integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==
"@types/warning@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/warning/-/warning-3.0.0.tgz#0d2501268ad8f9962b740d387c4654f5f8e23e52"
@ -2711,11 +2713,16 @@ acorn@^7.0.0, acorn@^7.1.1:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
acorn@^8.2.4, acorn@^8.4.1, acorn@^8.5.0, acorn@^8.7.0:
acorn@^8.2.4, acorn@^8.5.0, acorn@^8.7.0:
version "8.7.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30"
integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==
acorn@^8.7.1:
version "8.8.2"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a"
integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==
address@^1.0.1, address@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/address/-/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6"
@ -3943,18 +3950,18 @@ data-urls@^2.0.0:
whatwg-mimetype "^2.3.0"
whatwg-url "^8.0.0"
datatables.net-bs5@>=1.12.1, datatables.net-bs5@^1.13.3:
version "1.13.3"
resolved "https://registry.yarnpkg.com/datatables.net-bs5/-/datatables.net-bs5-1.13.3.tgz#5b5a51fe48f26f5b8e37f28c8b2aee485291c3ac"
integrity sha512-yaZ89SjP1INIotvCVaBkgQ0VPvWTAfywG3dmBaVIosa4u0vUAqlx1+aYyL8WXgqmklSQda3nXdNwJu0OrPw84A==
datatables.net-bs5@>=1.12.1, datatables.net-bs5@^1.13.4:
version "1.13.4"
resolved "https://registry.yarnpkg.com/datatables.net-bs5/-/datatables.net-bs5-1.13.4.tgz#c73058782484cb84d9bc3ae9b8c0f1950b78bf2b"
integrity sha512-+gtaiau4vJeuGvnsYWmQy9gqa5XQ15XmkdwpK5EjwYCMzZZEXMQ3wfu2FddBcX5tX9Ual8C+Tf1s2gmqLGNbKQ==
dependencies:
datatables.net ">=1.12.1"
jquery ">=1.7"
datatables.net-responsive-bs5@^2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/datatables.net-responsive-bs5/-/datatables.net-responsive-bs5-2.4.0.tgz#ab3a6fb147e5886ae043c9ecd54652f3a5d6d5c0"
integrity sha512-2kxigvftgVgdyH+BZ17jbR+BcWYZsacnlTt4PeOH/P98bMuqJKrziss7CLz/6qud/ehHdLEQTjMDoJddN9NfxA==
datatables.net-responsive-bs5@^2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/datatables.net-responsive-bs5/-/datatables.net-responsive-bs5-2.4.1.tgz#8342ea3d97f49b2d4ce3af9742367d2feef31000"
integrity sha512-gtW0IjNMWCWQ+KHpIPcgXiQrq2Dhxz8yKql4sS2WfuYz+Z7645mk7xmqEhy4aqpWEGXdv4rcaZhr4tt49NLOpQ==
dependencies:
datatables.net-bs5 ">=1.12.1"
datatables.net-responsive ">=2.3.0"
@ -3968,10 +3975,10 @@ datatables.net-responsive@>=2.3.0:
datatables.net ">=1.12.1"
jquery ">=1.7"
datatables.net@>=1.12.1, datatables.net@^1.13.3:
version "1.13.3"
resolved "https://registry.yarnpkg.com/datatables.net/-/datatables.net-1.13.3.tgz#ee7d7b16b479b5075412b104d980184693b4325b"
integrity sha512-YVnz02oJsaP/OfnclBlqHkuV1il60sSVa+a0Xvs5gyiDLftmAxc+rvVAwCm7O0OpKo09N43k6EcCAf3L9WYI7g==
datatables.net@>=1.12.1, datatables.net@^1.13.4:
version "1.13.4"
resolved "https://registry.yarnpkg.com/datatables.net/-/datatables.net-1.13.4.tgz#9a809cee82eca0a884e10b4d47a3a3d6e65e9fe7"
integrity sha512-yzhArTOB6tPO2QFKm1z3hA4vabtt2hRvgw8XLsT1xqEirinfGYqWDiWXlkTPTaJv2e7gG+Kf985sXkzBFlGrGQ==
dependencies:
jquery ">=1.7"
@ -4333,10 +4340,10 @@ end-of-stream@^1.1.0, end-of-stream@^1.4.1:
dependencies:
once "^1.4.0"
enhanced-resolve@^5.9.2:
version "5.9.3"
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.9.3.tgz#44a342c012cbc473254af5cc6ae20ebd0aae5d88"
integrity sha512-Bq9VSor+kjvW3f9/MiiR4eE3XYgOl7/rS8lnSxbRbF3kS0B2r+Y9w5krBWxZgDxASVZbdYrn5wT4j/Wb0J9qow==
enhanced-resolve@^5.10.0:
version "5.12.0"
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz#300e1c90228f5b570c4d35babf263f6da7155634"
integrity sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==
dependencies:
graceful-fs "^4.2.4"
tapable "^2.2.0"
@ -5494,10 +5501,10 @@ i18next-chained-backend@^4.2.0:
dependencies:
"@babel/runtime" "^7.19.4"
i18next-http-backend@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/i18next-http-backend/-/i18next-http-backend-2.1.1.tgz#72a21d61c2e96eea9ad45ba1b9dd0090e119709a"
integrity sha512-jByfUCDVgQ8+/Wens7queQhYYvMcGTW/lR4IJJNEDDXnmqjLrwi8ubXKpmp76/JIWEZHffNdWqnxFJcTVGeaOw==
i18next-http-backend@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/i18next-http-backend/-/i18next-http-backend-2.2.0.tgz#f77c06dc8e766c7588bb38da2f5fa0614ba67b3f"
integrity sha512-Z4sM7R6tzdLknSPER9GisEBxKPg5FkI07UrQniuroZmS15PHQrcCPLyuGKj8SS68tf+O2aEDYSUnmy1TZqZSbw==
dependencies:
cross-fetch "3.1.5"
@ -5508,10 +5515,10 @@ i18next-localstorage-backend@^4.1.0:
dependencies:
"@babel/runtime" "^7.20.6"
i18next@^22.4.10:
version "22.4.10"
resolved "https://registry.yarnpkg.com/i18next/-/i18next-22.4.10.tgz#cfbfc412c6bc83e3c16564f47e6a5c145255960e"
integrity sha512-3EqgGK6fAJRjnGgfkNSStl4mYLCjUoJID338yVyLMj5APT67HUtWoqSayZewiiC5elzMUB1VEUwcmSCoeQcNEA==
i18next@^22.4.11:
version "22.4.11"
resolved "https://registry.yarnpkg.com/i18next/-/i18next-22.4.11.tgz#8b6c9be95176de90d3f10a78af125d95d3a3258d"
integrity sha512-ShfTzXVMjXdF2iPiT/wbizOrssLh9Ab6VpuVROihLCAu+u25KbZiEYVgsA0W6g0SgjPa/JmGWcUEV/g6cKzEjQ==
dependencies:
"@babel/runtime" "^7.20.6"
@ -6423,12 +6430,7 @@ jsesc@~0.5.0:
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=
json-parse-better-errors@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==
json-parse-even-better-errors@^2.3.0:
json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
@ -8305,11 +8307,16 @@ react-is@^16.13.1, react-is@^16.3.2, react-is@^16.7.0:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-is@^17.0.1, react-is@^17.0.2:
react-is@^17.0.1:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
react-is@^18.0.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"
integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==
react-lifecycles-compat@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
@ -8320,17 +8327,17 @@ react-mcjsonchat@^1.0.0:
resolved "https://registry.yarnpkg.com/react-mcjsonchat/-/react-mcjsonchat-1.0.0.tgz#6a0c748b074c06a2d64a66db76e5fb2036c48a9a"
integrity sha512-H/OYi1iRYFG2Bntj1EYWfTxOaWtIopHy6ILoaXtD3OFONK7kQnp0lXFf6sw1G3BU0DL7s8NL4+AmMCer/A5eIQ==
react-redux@^7.2.4:
version "7.2.8"
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.8.tgz#a894068315e65de5b1b68899f9c6ee0923dd28de"
integrity sha512-6+uDjhs3PSIclqoCk0kd6iX74gzrGc3W5zcAjbrFgEdIjRSQObdIwfx80unTkVUYvbQ95Y8Av3OvFHq1w5EOUw==
react-redux@^8.0.5:
version "8.0.5"
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.0.5.tgz#e5fb8331993a019b8aaf2e167a93d10af469c7bd"
integrity sha512-Q2f6fCKxPFpkXt1qNRZdEDLlScsDWyrgSj0mliK59qU6W5gvBiKkdMEG2lJzhd1rCctf0hb6EtePPLZ2e0m1uw==
dependencies:
"@babel/runtime" "^7.15.4"
"@types/react-redux" "^7.1.20"
"@babel/runtime" "^7.12.1"
"@types/hoist-non-react-statics" "^3.3.1"
"@types/use-sync-external-store" "^0.0.3"
hoist-non-react-statics "^3.3.2"
loose-envify "^1.4.0"
prop-types "^15.7.2"
react-is "^17.0.2"
react-is "^18.0.0"
use-sync-external-store "^1.0.0"
react-refresh@^0.11.0:
version "0.11.0"
@ -8514,7 +8521,7 @@ redux-immutable@^4.0.0:
resolved "https://registry.yarnpkg.com/redux-immutable/-/redux-immutable-4.0.0.tgz#3a1a32df66366462b63691f0e1dc35e472bbc9f3"
integrity sha512-SchSn/DWfGb3oAejd+1hhHx01xUoxY+V7TeK0BKqpkLKiQPVFf7DYzEaKmrEVxsWxielKfSK9/Xq66YyxgR1cg==
redux@^4.0.0, redux@^4.1.2:
redux@^4.1.2:
version "4.2.0"
resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.0.tgz#46f10d6e29b6666df758780437651eeb2b969f13"
integrity sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==
@ -8789,10 +8796,10 @@ sass-loader@^12.3.0:
klona "^2.0.4"
neo-async "^2.6.2"
sass@^1.58.3:
version "1.58.3"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.58.3.tgz#2348cc052061ba4f00243a208b09c40e031f270d"
integrity sha512-Q7RaEtYf6BflYrQ+buPudKR26/lH+10EmO9bBqbmPh/KeLqv8bjpTNqxe71ocONqXq+jYiCbpPUmQMS+JJPk4A==
sass@^1.59.3:
version "1.59.3"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.59.3.tgz#a1ddf855d75c70c26b4555df4403e1bbf8e4403f"
integrity sha512-QCq98N3hX1jfTCoUAsF3eyGuXLsY7BCnCEg9qAact94Yc21npG2/mVOqoDvE0fCbWDqiM4WlcJQla0gWG2YlxQ==
dependencies:
chokidar ">=3.0.0 <4.0.0"
immutable "^4.0.0"
@ -9439,10 +9446,10 @@ svgo@^2.7.0:
picocolors "^1.0.0"
stable "^0.1.8"
swagger-client@=3.19.0-beta.8:
version "3.19.0-beta.8"
resolved "https://registry.yarnpkg.com/swagger-client/-/swagger-client-3.19.0-beta.8.tgz#86639e3d8704a305ab66109bfacae29c6bdbe411"
integrity sha512-idGYcLsr5p3yLRPSRsLRpjWKM8exb5lb28f9xjwZpBUGWo5rgQydWLcPRPTdwxxr5lMvuWXFIKowJ69EvpfUsw==
swagger-client@^3.19.1:
version "3.19.2"
resolved "https://registry.yarnpkg.com/swagger-client/-/swagger-client-3.19.2.tgz#7fabea7d953c19532670f3ee983a61addb134304"
integrity sha512-oBR6VtD99yJOY8W3vf1h8zU91H5UpwUk2trSL4xzZIzChWtlaOMyWh+/GyUGgDCmQY4B1E4qTgAD/kFQ4dFnnA==
dependencies:
"@babel/runtime-corejs3" "^7.20.13"
"@swagger-api/apidom-core" "=0.69.0"
@ -9462,10 +9469,10 @@ swagger-client@=3.19.0-beta.8:
traverse "~0.6.6"
url "~0.11.0"
swagger-ui@^4.17.0:
version "4.17.0"
resolved "https://registry.yarnpkg.com/swagger-ui/-/swagger-ui-4.17.0.tgz#9889f6b44acf60ba1d1ecb455e72ab3f3ce09be5"
integrity sha512-gs7c2YTuz6nDgwb2OQWKzgKiNhx7Z1TTCNniuLTgYbrIFrc3Ec652ACAom+XY9O5eq1nN/Xy+/mVOTv9I7tuMw==
swagger-ui@^4.18.1:
version "4.18.1"
resolved "https://registry.yarnpkg.com/swagger-ui/-/swagger-ui-4.18.1.tgz#14b90dba50cb827e57747458ef455f99a677535b"
integrity sha512-KMG/Mg+b7oPtBOlpx9abbmU6Rm86kiVUvvpR3EkplBz0CSnseuQZN2XdDZKD7QsvM78NkUhn53y5EZCb+4rd1g==
dependencies:
"@babel/runtime-corejs3" "^7.18.9"
"@braintree/sanitize-url" "=6.0.2"
@ -9490,7 +9497,7 @@ swagger-ui@^4.17.0:
react-immutable-proptypes "2.2.0"
react-immutable-pure-component "^2.2.0"
react-inspector "^6.0.1"
react-redux "^7.2.4"
react-redux "^8.0.5"
react-syntax-highlighter "^15.5.0"
redux "^4.1.2"
redux-immutable "^4.0.0"
@ -9498,7 +9505,7 @@ swagger-ui@^4.17.0:
reselect "^4.1.5"
serialize-error "^8.1.0"
sha.js "^2.4.11"
swagger-client "=3.19.0-beta.8"
swagger-client "^3.19.1"
url-parse "^1.5.8"
xml "=1.0.1"
xml-but-prettier "^1.0.1"
@ -9933,6 +9940,11 @@ url@~0.11.0:
punycode "1.3.2"
querystring "0.2.0"
use-sync-external-store@^1.0.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
@ -10015,10 +10027,10 @@ warning@^4.0.0, warning@^4.0.3:
dependencies:
loose-envify "^1.0.0"
watchpack@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.3.1.tgz#4200d9447b401156eeca7767ee610f8809bc9d25"
integrity sha512-x0t0JuydIo8qCNctdDrn1OzH/qDzk2+rdCOC3YzumZ42fiMqmQ7T3xQurykYMhYfHaPHTp4ZxAx2NfUo1K6QaA==
watchpack@^2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d"
integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==
dependencies:
glob-to-regexp "^0.4.1"
graceful-fs "^4.1.2"
@ -10141,33 +10153,33 @@ webpack-sources@^3.2.3:
integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
webpack@^5.64.4:
version "5.72.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.72.0.tgz#f8bc40d9c6bb489a4b7a8a685101d6022b8b6e28"
integrity sha512-qmSmbspI0Qo5ld49htys8GY9XhS9CGqFoHTsOVAnjBdg0Zn79y135R+k4IR4rKK6+eKaabMhJwiVB7xw0SJu5w==
version "5.76.1"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.76.1.tgz#7773de017e988bccb0f13c7d75ec245f377d295c"
integrity sha512-4+YIK4Abzv8172/SGqObnUjaIHjLEuUasz9EwQj/9xmPPkYJy2Mh03Q/lJfSD3YLzbxy5FeTq5Uw0323Oh6SJQ==
dependencies:
"@types/eslint-scope" "^3.7.3"
"@types/estree" "^0.0.51"
"@webassemblyjs/ast" "1.11.1"
"@webassemblyjs/wasm-edit" "1.11.1"
"@webassemblyjs/wasm-parser" "1.11.1"
acorn "^8.4.1"
acorn "^8.7.1"
acorn-import-assertions "^1.7.6"
browserslist "^4.14.5"
chrome-trace-event "^1.0.2"
enhanced-resolve "^5.9.2"
enhanced-resolve "^5.10.0"
es-module-lexer "^0.9.0"
eslint-scope "5.1.1"
events "^3.2.0"
glob-to-regexp "^0.4.1"
graceful-fs "^4.2.9"
json-parse-better-errors "^1.0.2"
json-parse-even-better-errors "^2.3.1"
loader-runner "^4.2.0"
mime-types "^2.1.27"
neo-async "^2.6.2"
schema-utils "^3.1.0"
tapable "^2.1.1"
terser-webpack-plugin "^5.1.3"
watchpack "^2.3.1"
watchpack "^2.4.0"
webpack-sources "^3.2.3"
websocket-driver@>=0.5.1, websocket-driver@^0.7.4: