diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7c8a4b7..035f6a0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -52,6 +52,12 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Under the hood
+- **A custom system's words reach the building GM notes and the admin GAME tab.** The GM NOTES
+ folder, its header and editor, BANK SOUNDS and the INITIATIVE FOLLOWS BUILDING house rule use the
+ words a custom system's GM chose for the GM, the bank and initiative. The built-in systems keep
+ their wording exactly. The XP window, the vehicles window and the shop buy-back rate keep theirs
+ until custom systems can have those parts.
+
- **A custom system's words reach the initiative tracker and the dice log.** START, JOIN and END
INITIATIVE, the INIT score, the turn counter and the dice log's "rolled INITIATIVE" lines use the
words a custom system's GM chose for initiative and the turn. The built-in systems keep their
diff --git a/README.md b/README.md
index 0b8a286..9cf5c36 100644
--- a/README.md
+++ b/README.md
@@ -641,6 +641,7 @@ CITY_NET/
│ │ │ ├── BootScreen.test.tsx # Types out by itself and finishes, skips on SKIP and nothing else, reports lines and clicks
│ │ │ ├── HitPoints.test.tsx
│ │ │ ├── wordsInBankAndShops.test.tsx # The glossary in the SHOP and VIEW_BANK menu buttons and the empty-shop steps: today's text under every built-in system and an unrenamed custom one, a custom system's own words where it renamed them (the shop window's own are in ShopWindow.test.tsx)
+│ │ │ ├── wordsInGmNotesAndAdmin.test.tsx # The glossary in the building GM notes (window, notes box, admin editor) and the GAME tab (BANK SOUNDS, INITIATIVE FOLLOWS BUILDING): today's text under every built-in system and an unrenamed custom one, a custom system's own words (capitals in labels, as written in sentences) where it renamed them
│ │ │ ├── wordsInWindows.test.tsx # The glossary in the sidebar, sheet header, HEALTH folder and token window: exactly today's text under every built-in system and under a custom system that renamed nothing; a custom system's own words, in the label style, where it did
│ │ │ ├── HealthModelPanels.test.tsx # Each model's editor sending what the server expects and reporting it (spills, boxes turning heavier, harm moving up, out, refusals), the GM-only SET rows, other players seeing no numbers or notes, and the built-in systems' folder untouched and never asking
│ │ │ ├── healthBands.test.tsx # The bands' thresholds and colors, each rhythm's beats (the steady one unchanged, beats inside their stretch, weak ones drawn smaller), reduced motion, and the same band in the editing panel, the review panel and the stream
diff --git a/frontend/src/components/AdminPanel.tsx b/frontend/src/components/AdminPanel.tsx
index cba5ecc..29a4c9e 100644
--- a/frontend/src/components/AdminPanel.tsx
+++ b/frontend/src/components/AdminPanel.tsx
@@ -44,7 +44,7 @@ import { BUILTIN_FONTS, type RemoteFont } from '../utils/fontLoader';
import { PNG_EXPORT_PRESETS, DEFAULT_PNG_EXPORT_WIDTH } from '../utils/mapExportBounds';
import { RECORD_DURATIONS, MAX_RECORD_SECONDS } from '../hooks/useMapExport';
import { parseGrant, describeGrant } from '../utils/tokenControl';
-import { useWords, asLabel } from '../sheets/words';
+import { useWords, asLabel, type WordLookup } from '../sheets/words';
// ─────────────────────────────────────────────────────────────────────────────
@@ -1989,7 +1989,7 @@ export function AdminPanel({
neither has anything to do with shops; main admin only, since a granted
editor sees this view too and the server refuses them both. */}
{isAdmin && isPrimaryAdmin && !['enemy_rhombus', 'friendly_rhombus', 'rhombus', 'none'].includes(editData.shape) && (
-
+
)}
{editData.shape === 'friendly_rhombus' && (() => {
@@ -2216,11 +2216,12 @@ function HouseRulesPanel({ token, defs }: { token: string; defs: HouseRuleDef[]
);
}
-const GLOBAL_HOUSE_RULES: HouseRuleDef[] = [
+/** The house rules every system has. A function of the system's words: the first names initiative. */
+const globalHouseRules = (word: WordLookup): HouseRuleDef[] => [
{
settingKey: 'initiative_follows_building',
- label: 'INITIATIVE FOLLOWS BUILDING (ALL FLOORS SHARE ONE TRACKER)',
- title: 'When enabled, all floors of the same building share a single initiative tracker. Players moving between floors stay in the same combat order. Each building and the city map still have their own separate initiatives.',
+ label: `${word('initiative', 'singular', 'INITIATIVE').toUpperCase()} FOLLOWS BUILDING (ALL FLOORS SHARE ONE TRACKER)`,
+ title: `When enabled, all floors of the same building share a single ${word('initiative', 'singular', 'initiative')} tracker. Players moving between floors stay in the same combat order. Each building and the city map still have their own separate ${word('initiative', 'plural', 'initiatives')}.`,
},
{
// Universal rather than per-system: every ruleset in the app has money, and whether
@@ -2297,6 +2298,8 @@ function TTRPGSystemPanel({ token, onOpenNpcLibrary, activeUsers }: { token: str
const [systems, setSystems] = useState([]);
const [luckResetMsg, setLuckResetMsg] = useState(null);
const [edgeGrantTarget, setEdgeGrantTarget] = useState('');
+ // The picked system's own words for the house rules every system shares.
+ const word = useWords(system);
const refresh = () => {
fetch('/api/sheets/system').then(r => r.json()).then(d => {
@@ -2331,7 +2334,7 @@ function TTRPGSystemPanel({ token, onOpenNpcLibrary, activeUsers }: { token: str
Each system keeps its own characters, banks and token health; switching back restores them.
{
if (!globalSettings) return;
@@ -2590,7 +2595,7 @@ function BankSoundsPanel({ token, globalSettings, fetchGlobalSettings }: { token
style={{ width: '100%', textAlign: 'left', display: 'flex', justifyContent: 'space-between' }}
onClick={() => setOpen(o => !o)}
>
- BANK SOUNDS
+ {bank} SOUNDS{open ? '▲' : '▼'}
{open && (
diff --git a/frontend/src/components/BuildingExtrasEditor.tsx b/frontend/src/components/BuildingExtrasEditor.tsx
index fe7e7f4..5be758c 100644
--- a/frontend/src/components/BuildingExtrasEditor.tsx
+++ b/frontend/src/components/BuildingExtrasEditor.tsx
@@ -1,4 +1,5 @@
import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
+import { useWords } from '../sheets/words';
// The building photo and the GM's notes, in the admin edit view.
//
@@ -33,6 +34,8 @@ interface Props {
locationId: number | null;
token: string;
photoUrl?: string | null;
+ /** The running game system, for a custom system's own word for the GM. */
+ gameSystem?: string;
}
const label: React.CSSProperties = { display: 'block', marginBottom: 5 };
@@ -43,9 +46,10 @@ const hint: React.CSSProperties = { fontSize: '0.65rem', opacity: 0.8, marginTop
const STALE = 'If the backend was just updated, restart it.';
export const BuildingExtrasEditor = forwardRef(function BuildingExtrasEditor(
- { locationId, token, photoUrl },
+ { locationId, token, photoUrl, gameSystem },
ref,
) {
+ const gm = useWords(gameSystem)('gm', 'short', 'GM');
/** A photo chosen but not yet uploaded, and the local preview of it. */
const [pending, setPending] = useState<{ file: File; preview: string } | null>(null);
/** The current photo marked for removal. */
@@ -115,21 +119,21 @@ export const BuildingExtrasEditor = forwardRef(func
body: JSON.stringify({ notes }),
});
const body = await r.json().catch(() => ({}));
- if (!r.ok) problems.push(body.error || 'The GM notes were not saved.');
- else if (typeof body.notes !== 'string') problems.push(`The server did not take the GM notes. ${STALE}`);
+ if (!r.ok) problems.push(body.error || `The ${gm} notes were not saved.`);
+ else if (typeof body.notes !== 'string') problems.push(`The server did not take the ${gm} notes. ${STALE}`);
else setSavedNotes(body.notes);
} catch {
- problems.push('The GM notes were not saved.');
+ problems.push(`The ${gm} notes were not saved.`);
}
}
return problems;
},
- }), [locationId, token, pending, removing, notes, savedNotes]);
+ }), [locationId, token, pending, removing, notes, savedNotes, gm]);
if (locationId == null) {
return (
- Save the building first to give it a photo or GM notes.
+ Save the building first to give it a photo or {gm} notes.