fix(dashboard): coins, universe score, imagination, reputation and life from the locale

Pages label the game's currencies and stats with the client's words
(UI_COINS, UI_UNIVERSE_SCORE, UI_IMAGINATION, UI_REPUTATION, UI_HEALTH)
through game.terms and GameText.term instead of writing them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-09-29 22:06:37 -05:00
parent ade116c985
commit 597269b4c5
12 changed files with 41 additions and 41 deletions

View File

@@ -62,7 +62,7 @@
<fieldset class="border rounded p-3 mb-3">
<legend class="fs-6 float-none w-auto px-1 mb-0">Rewards for everyone who helped</legend>
<div class="row g-2">
<div class="col-sm-4"><label class="form-label small" for="chCoins">Coins</label><input type="number" class="form-control form-control-sm" id="chCoins" min="0" max="1000000" value="0"></div>
<div class="col-sm-4"><label class="form-label small" for="chCoins">{{ game.terms.coins }}</label><input type="number" class="form-control form-control-sm" id="chCoins" min="0" max="1000000" value="0"></div>
<div class="col-sm-4"><label class="form-label small" for="chMin">Needs at least</label><input type="number" class="form-control form-control-sm" id="chMin" min="1" value="1"></div>
</div>
<div class="mt-2"><label class="form-label small" for="chItemsSearch">Items (up to 5; one mail each)</label><div data-picker="item" data-multi="5" id="chItems"></div>

View File

@@ -152,19 +152,19 @@
</div>
<div class="col-6 col-md-3">
<div class="card text-center"><div class="card-body py-3">
<div class="text-body-secondary small text-uppercase mb-1">Coins</div>
<div class="text-body-secondary small text-uppercase mb-1">{{ game.terms.coins }}</div>
<div class="fs-3 fw-bold">{{ character.stats.coins }}</div>
</div></div>
</div>
<div class="col-6 col-md-3">
<div class="card text-center"><div class="card-body py-3">
<div class="text-body-secondary small text-uppercase mb-1">U-Score</div>
<div class="text-body-secondary small text-uppercase mb-1">{{ game.terms.uscore }}</div>
<div class="fs-3 fw-bold">{{ character.stats.universe_score }}</div>
</div></div>
</div>
<div class="col-6 col-md-3">
<div class="card text-center"><div class="card-body py-3">
<div class="text-body-secondary small text-uppercase mb-1">Reputation</div>
<div class="text-body-secondary small text-uppercase mb-1">{{ game.terms.reputation }}</div>
<div class="fs-3 fw-bold">{{ character.stats.reputation }}</div>
</div></div>
</div>
@@ -196,7 +196,7 @@
<div class="card-body">
<div class="row g-3">
<div class="col-md-4">
<div class="d-flex justify-content-between"><span>Health</span><span>{{ character.stats.health }} / {{ character.stats.max_health }}</span></div>
<div class="d-flex justify-content-between"><span>{{ game.terms.life }}</span><span>{{ character.stats.health }} / {{ character.stats.max_health }}</span></div>
<div class="progress mt-1" style="height: 8px;">{% if character.stats.max_health > 0 %}<div class="progress-bar bg-danger" style="width: {{ character.stats.health / character.stats.max_health * 100 }}%"></div>{% endif %}</div>
</div>
<div class="col-md-4">
@@ -204,7 +204,7 @@
<div class="progress mt-1" style="height: 8px;">{% if character.stats.max_armor > 0 %}<div class="progress-bar bg-secondary" style="width: {{ character.stats.armor / character.stats.max_armor * 100 }}%"></div>{% endif %}</div>
</div>
<div class="col-md-4">
<div class="d-flex justify-content-between"><span>Imagination</span><span>{{ character.stats.imagination }} / {{ character.stats.max_imagination }}</span></div>
<div class="d-flex justify-content-between"><span>{{ game.terms.imagination }}</span><span>{{ character.stats.imagination }} / {{ character.stats.max_imagination }}</span></div>
<div class="progress mt-1" style="height: 8px;">{% if character.stats.max_imagination > 0 %}<div class="progress-bar bg-primary" style="width: {{ character.stats.imagination / character.stats.max_imagination * 100 }}%"></div>{% endif %}</div>
</div>
</div>
@@ -319,7 +319,7 @@
<div class="modal-body">
<div class="table-responsive">
<table class="table table-striped table-hover mb-0">
<thead><tr><th>Zone</th><th>Achievements</th><th>Coins</th><th>Enemies</th></tr></thead>
<thead><tr><th>Zone</th><th>Achievements</th><th>{{ game.terms.coins }}</th><th>Enemies</th></tr></thead>
<tbody>
{% for zs in character.stats.zone_stats %}
<tr>
@@ -381,8 +381,8 @@
<div class="modal-body">
<div class="alert alert-warning small">Saving disconnects the player if they are online. The current version is kept in the history, so it can be put back.</div>
<div class="row g-2 mb-3">
<div class="col-sm-4"><label class="form-label small" for="editCoins">Coins</label><input type="number" min="0" max="2000000000" class="form-control" id="editCoins"></div>
<div class="col-sm-4"><label class="form-label small" for="editUScore">U-score</label><input type="number" min="0" max="2000000000" class="form-control" id="editUScore"></div>
<div class="col-sm-4"><label class="form-label small" for="editCoins">{{ game.terms.coins }}</label><input type="number" min="0" max="2000000000" class="form-control" id="editCoins"></div>
<div class="col-sm-4"><label class="form-label small" for="editUScore">{{ game.terms.uscore }}</label><input type="number" min="0" max="2000000000" class="form-control" id="editUScore"></div>
<div class="col-sm-4"><label class="form-label small" for="editLevel">Level</label><input type="number" min="1" max="45" class="form-control" id="editLevel"></div>
</div>
<ul class="nav nav-tabs" id="editInventoryTabs" role="tablist"></ul>
@@ -530,9 +530,9 @@ document.querySelectorAll('[data-restriction]').forEach(function (input) {
function statsHtml(stats) {
var lines = [];
if (stats.life) lines.push('+' + esc(stats.life) + ' Health');
if (stats.life) lines.push('+' + esc(stats.life) + ' ' + esc(GameText.term('life')));
if (stats.armor) lines.push('+' + esc(stats.armor) + ' Armor');
if (stats.imagination) lines.push('+' + esc(stats.imagination) + ' Imagination');
if (stats.imagination) lines.push('+' + esc(stats.imagination) + ' ' + esc(GameText.term('imagination')));
var html = lines.length ? '<div>' + lines.join('<br>') + '</div>' : '';
stats.skills.forEach(function (skill) {
html += '<div class="d-flex gap-1 mt-1 text-start">' +

View File

@@ -6,7 +6,7 @@
<div class="d-flex flex-wrap justify-content-between align-items-end gap-3 mb-3">
<div>
<h2 class="mb-1">Live Events</h2>
<p class="text-body-secondary mb-0">Start something fun in game for a while: a treasure hunt, bonus coins, U-score or loot, an invasion or a
<p class="text-body-secondary mb-0">Start something fun in game for a while: a treasure hunt, bonus {{ game.terms.coins }}, {{ game.terms.uscore }} or loot, an invasion or a
celebration. It is announced in its zones, runs in every instance of them (worlds that open later join in), and everything it spawned is removed
when it ends. Players can follow it with <code>/challenge</code>. To run one at set times, together with other things, add it to a
<a href="/events">scheduled event</a>.</p>
@@ -36,7 +36,7 @@
<label class="form-label" for="evType">Kind of event</label>
<select class="form-select" id="evType">
<option value="treasure_hunt">Treasure hunt: hide objects for players to find</option>
<option value="bonus">Bonus: more coins, U-score or loot</option>
<option value="bonus">Bonus: more {{ game.terms.coins }}, {{ game.terms.uscore }} or loot</option>
<option value="invasion">Invasion: waves of enemies</option>
<option value="celebration">Celebration: effects on every player</option>
</select>
@@ -67,7 +67,7 @@
<div class="row g-2">
<div class="col-sm-3"><label class="form-label small" for="thCount">How many (per instance)</label><input type="number" class="form-control form-control-sm" id="thCount" min="1" max="50" value="10"></div>
<div class="col-sm-3"><label class="form-label small" for="thRadius">Pickup distance</label><input type="number" class="form-control form-control-sm" id="thRadius" min="2" max="15" value="4"></div>
<div class="col-sm-3"><label class="form-label small" for="thCoins">Coins per find</label><input type="number" class="form-control form-control-sm" id="thCoins" min="0" max="100000" value="250"></div>
<div class="col-sm-3"><label class="form-label small" for="thCoins">{{ game.terms.coins }} per find</label><input type="number" class="form-control form-control-sm" id="thCoins" min="0" max="100000" value="250"></div>
<div class="col-sm-3"><label class="form-label small" for="thItemCount">Items per find</label><input type="number" class="form-control form-control-sm" id="thItemCount" min="1" max="999" value="1"></div>
</div>
<div class="mt-2"><label class="form-label small" for="thItemSearch">Item reward <span class="text-body-secondary">(optional)</span></label><div data-picker="item" id="thItem"></div></div>
@@ -79,8 +79,8 @@
<fieldset class="border rounded p-3" data-for="bonus">
<legend class="fs-6 float-none w-auto px-1 mb-0">Multipliers</legend>
<div class="row g-2">
<div class="col-sm-4"><label class="form-label small" for="bnCoins">Coins from loot</label><input type="number" class="form-control form-control-sm" id="bnCoins" min="1" max="10" step="0.25" value="2"></div>
<div class="col-sm-4"><label class="form-label small" for="bnUscore">U-score from missions</label><input type="number" class="form-control form-control-sm" id="bnUscore" min="1" max="10" step="0.25" value="1"></div>
<div class="col-sm-4"><label class="form-label small" for="bnCoins">{{ game.terms.coins }} from loot</label><input type="number" class="form-control form-control-sm" id="bnCoins" min="1" max="10" step="0.25" value="2"></div>
<div class="col-sm-4"><label class="form-label small" for="bnUscore">{{ game.terms.uscore }} from missions</label><input type="number" class="form-control form-control-sm" id="bnUscore" min="1" max="10" step="0.25" value="1"></div>
<div class="col-sm-4"><label class="form-label small" for="bnLoot">Loot drop chance</label><input type="number" class="form-control form-control-sm" id="bnLoot" min="1" max="10" step="0.25" value="1"></div>
</div>
<p class="small text-body-secondary mb-0 mt-2">Leave the zones empty for every world. Bonuses running at once don't stack: the biggest one counts.</p>
@@ -132,7 +132,7 @@
if (e.type === 'invasion') return nf.format(t.kills || 0) + ' of ' + nf.format(t.spawned || 0) + ' invaders smashed, ' + nf.format(t.wavesSent || 0) + ' waves';
if (e.type === 'celebration') return nf.format(t.bursts || 0) + ' bursts';
var c = e.config || {};
return 'coins x' + c.coins + ', U-score x' + c.uscore + ', loot x' + c.lootChance;
return GameText.term('coins') + ' x' + c.coins + ', ' + GameText.term('uscore') + ' x' + c.uscore + ', loot x' + c.lootChance;
}
function instanceStatus(e, i) {
var s = i.status || {};

View File

@@ -48,7 +48,7 @@
<div class="card-header"><h5 class="mb-0">Pending Properties</h5></div>
<div class="card-body">
<table id="pendingPropsTable" class="table table-striped table-hover table-stack">
<thead><tr><th>ID</th><th>Name</th><th>Owner</th><th>Models</th><th>Reputation</th><th>Zone</th><th>Actions</th></tr></thead>
<thead><tr><th>ID</th><th>Name</th><th>Owner</th><th>Models</th><th>{{ game.terms.reputation }}</th><th>Zone</th><th>Actions</th></tr></thead>
<tbody></tbody>
</table>
</div>

View File

@@ -44,7 +44,7 @@
<th>Name</th>
<th>Owner</th>
<th>Moderation</th>
<th>Reputation</th>
<th>{{ game.terms.reputation }}</th>
<th title="Load estimate of the placed models, reported by the owner's game client">Performance cost</th>
<th>Zone</th>
<th>Actions</th>

View File

@@ -152,7 +152,7 @@ function renderDetails(p) {
field('Privacy', esc(Labels.name('privacy', p.privacy_option) || p.privacy_option)),
field('Moderation', p.mod_approved ? fmt.badge('Approved', 'success') : (p.rejection_reason ? fmt.badge('Rejected', 'danger') : fmt.badge('Unapproved', 'warning')) +
(p.rejection_reason ? ' <span class="small">' + esc(p.rejection_reason) + '</span>' : '')),
field('Reputation', num(p.reputation)),
field(GameText.term('reputation'), num(p.reputation)),
field('Performance cost', perf ? num(perf, 2) : muted('Not reported'), 'Load estimate of the placed models, reported by the owner\'s game client after building'),
field('Claimed', esc(fmt.unix(p.claimed))),
field('Last updated', esc(fmt.unix(p.last_updated))),
@@ -184,7 +184,7 @@ function renderDetails(p) {
field('Clone limit', num(t.cloneLimit)),
field('Achievement required', t.achievementRequired ? esc(t.achievementRequired) : muted('None')),
field('Max build height', num(t.maxBuildHeight, 1)),
field('Reputation per minute', num(t.reputationPerMinute)),
field(GameText.term('reputation') + ' per minute', num(t.reputationPerMinute)),
field('Zone position', [t.zoneX, t.zoneY, t.zoneZ].every((v) => typeof v === 'number') ? esc([t.zoneX, t.zoneY, t.zoneZ].map((v) => v.toFixed(1)).join(', ')) : muted('-')),
field('Build area outline', Array.isArray(t.path) ? esc(t.path.length + ' points') : muted('-'))
].concat(t.localized_description ? [ '<div class="col-12"><div class="label">Template description</div><div class="value small">' + esc(t.localized_description) + '</div></div>' ] : []).join('');

View File

@@ -6,7 +6,7 @@
<div class="d-flex flex-wrap align-items-end justify-content-between gap-3 mb-3">
<div>
<h2 class="mb-1">Economy</h2>
<p class="text-body-secondary mb-0">Where coins, U-score and items come from and go. World servers write totals every few seconds, and the page updates as they do.</p>
<p class="text-body-secondary mb-0">Where {{ game.terms.coins }}, {{ game.terms.uscore }} and items come from and go. World servers write totals every few seconds, and the page updates as they do.</p>
</div>
<div class="d-flex flex-wrap align-items-center gap-3" id="reportFilters">
<div class="btn-group btn-group-sm" role="group" aria-label="Date range">
@@ -28,8 +28,8 @@
</div>
<ul class="nav nav-tabs mb-3" role="tablist">
<li class="nav-item" role="presentation"><button class="nav-link active" data-bs-toggle="tab" data-bs-target="#tabCoins" type="button" role="tab">Coins</button></li>
<li class="nav-item" role="presentation"><button class="nav-link" data-bs-toggle="tab" data-bs-target="#tabUScore" type="button" role="tab">U-Score</button></li>
<li class="nav-item" role="presentation"><button class="nav-link active" data-bs-toggle="tab" data-bs-target="#tabCoins" type="button" role="tab">{{ game.terms.coins }}</button></li>
<li class="nav-item" role="presentation"><button class="nav-link" data-bs-toggle="tab" data-bs-target="#tabUScore" type="button" role="tab">{{ game.terms.uscore }}</button></li>
<li class="nav-item" role="presentation"><button class="nav-link" data-bs-toggle="tab" data-bs-target="#tabItems" type="button" role="tab">Items</button></li>
<li class="nav-item" role="presentation"><button class="nav-link" data-bs-toggle="tab" data-bs-target="#tabActivity" type="button" role="tab" id="activityTabBtn">Activity</button></li>
<li class="nav-item" role="presentation"><button class="nav-link" data-bs-toggle="tab" data-bs-target="#tabMap" type="button" role="tab" id="mapTabBtn">World Map</button></li>
@@ -44,7 +44,7 @@
<div class="tab-pane fade show active" id="tabCoins" role="tabpanel">
<div class="row g-3 mb-3" id="coinTiles"></div>
<div class="card mb-3">
<div class="card-header d-flex justify-content-between align-items-center"><h5 class="mb-0">Coins per day</h5><div class="d-flex align-items-center gap-3"><div class="viz-legend" data-legend="coins"></div><a class="btn btn-sm btn-outline-secondary" data-csv="currency" download>CSV</a></div></div>
<div class="card-header d-flex justify-content-between align-items-center"><h5 class="mb-0">{{ game.terms.coins }} per day</h5><div class="d-flex align-items-center gap-3"><div class="viz-legend" data-legend="coins"></div><a class="btn btn-sm btn-outline-secondary" data-csv="currency" download>CSV</a></div></div>
<div class="card-body"><div class="viz" id="coinChart"></div>
<details class="mt-2"><summary class="small text-body-secondary">Show as table</summary><div class="table-responsive mt-2" id="coinDaily"></div></details>
</div>
@@ -59,7 +59,7 @@
<div class="tab-pane fade" id="tabUScore" role="tabpanel">
<div class="row g-3 mb-3" id="uscoreTiles"></div>
<div class="card mb-3">
<div class="card-header d-flex justify-content-between align-items-center"><h5 class="mb-0">U-score per day</h5><div class="d-flex align-items-center gap-3"><div class="viz-legend" data-legend="uscore"></div><a class="btn btn-sm btn-outline-secondary" data-csv="uscore" download>CSV</a></div></div>
<div class="card-header d-flex justify-content-between align-items-center"><h5 class="mb-0">{{ game.terms.uscore }} per day</h5><div class="d-flex align-items-center gap-3"><div class="viz-legend" data-legend="uscore"></div><a class="btn btn-sm btn-outline-secondary" data-csv="uscore" download>CSV</a></div></div>
<div class="card-body"><div class="viz" id="uscoreChart"></div>
<details class="mt-2"><summary class="small text-body-secondary">Show as table</summary><div class="table-responsive mt-2" id="uscoreDaily"></div></details>
</div>
@@ -179,7 +179,7 @@
</form>
<div class="card"><div class="card-body">
<table id="transfersTable" class="table table-striped table-hover w-100">
<thead><tr><th>Time</th><th>How</th><th>Item</th><th>Count</th><th>Coins</th><th>From</th><th>To</th><th>Object ID <span class="fw-normal small text-body-secondary">(click to trace)</span></th></tr></thead>
<thead><tr><th>Time</th><th>How</th><th>Item</th><th>Count</th><th>{{ game.terms.coins }}</th><th>From</th><th>To</th><th>Object ID <span class="fw-normal small text-body-secondary">(click to trace)</span></th></tr></thead>
<tbody></tbody>
</table>
</div></div>