Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions resources/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,7 @@
"alliance_request_sent": "Alliance request sent to {name}.",
"alliance_request_status": "{name} {status} your alliance request",
"atom_bomb_detonated": "{name} - atom bomb detonated",
"atom_bomb_inbound_plural": "{name} - {count} atom bombs inbound",
"attack_cancelled_retreat": "Attack cancelled, {troops} soldiers killed during retreat",
"attack_request": "{name} requests you attack {target}",
"betrayal_debuff_ends": "{time} seconds left until betrayal debuff ends",
Expand All @@ -470,7 +471,9 @@
"duration_seconds_plural": "{seconds} seconds",
"focus": "Focus",
"hydrogen_bomb_detonated": "{name} - hydrogen bomb detonated",
"hydrogen_bomb_inbound_plural": "{name} - {count} hydrogen bombs inbound",
"ignore": "Ignore",
"mirv_inbound_plural": "⚠️⚠️⚠️ {name} - {count} MIRVs INBOUND ⚠️⚠️⚠️",
"mirv_warheads_intercepted": "{count, plural, one {{count} MIRV warhead intercepted} other {{count} MIRV warheads intercepted}}",
"missile_intercepted": "Missile intercepted {unit}",
"no_boats_available": "No boats available, max {max}",
Expand All @@ -486,7 +489,12 @@
"sent_gold_to_player": "Sent {gold} gold to {name}",
"sent_troops_to_player": "Sent {troops} troops to {name}",
"trade_ship_captured": "Your trade ship was captured by {name}",
"unit_captured": "Captured {unit} from {name}",
"unit_captured_plural": "Captured {count} {unit} from {name}",
"unit_destroyed": "Your {unit} was destroyed",
"unit_destroyed_plural": "{count} {unit} destroyed",
"unit_lost": "Your {unit} was captured by {name}",
"unit_lost_plural": "Lost {count} {unit} to {name}",
"unit_voluntarily_deleted": "Unit voluntarily deleted",
"wants_to_renew_alliance": "{name} wants to renew your alliance"
},
Expand Down
103 changes: 76 additions & 27 deletions src/client/hud/layers/EventsDisplay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,22 @@ import {
translateText,
} from "../../Utils";

const pluralizeUnit = (u: string) => u === "City" ? "Cities" : u === "Factory" ? "Factories" : u + "s";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Look for existing translation keys for unit type names.
rg -n --type=json -i '"(city|cities|factory|factories|port|silo|defense)"' resources/lang/en.json

Repository: openfrontio/OpenFrontIO

Length of output: 644


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant file and nearby translation usage.
git ls-files 'src/client/hud/layers/EventsDisplay.ts' 'src/client/**/*.{ts,tsx}' 'resources/lang/en.json' | sed -n '1,20p'

echo '--- EventsDisplay.ts ---'
sed -n '1,220p' src/client/hud/layers/EventsDisplay.ts

echo '--- search for pluralizeUnit / translateText / unit type translation keys ---'
rg -n 'pluralizeUnit|translateText|build_city|build_factory|cities|factories|UnitType|unit:' src/client resources/lang/en.json

Repository: openfrontio/OpenFrontIO

Length of output: 50380


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,220p' src/client/hud/layers/EventsDisplay.ts
echo '---'
rg -n 'pluralizeUnit|translateText|build_city|build_factory|cities|factories|UnitType|unit:' src/client resources/lang/en.json

Repository: openfrontio/OpenFrontIO

Length of output: 50380


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- EventsDisplay.ts ---'
cat -n src/client/hud/layers/EventsDisplay.ts | sed -n '1,220p'

echo '--- translation-related matches ---'
rg -n 'pluralizeUnit|translateText|build_city|build_factory|cities|factories|UnitType|unit:' src/client resources/lang/en.json

Repository: openfrontio/OpenFrontIO

Length of output: 50380


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- pluralizeUnit usage in EventsDisplay.ts ---'
rg -n 'pluralizeUnit' src/client/hud/layers/EventsDisplay.ts

echo '--- unit_type keys in resources/lang/en.json ---'
rg -n '"unit_type\.' resources/lang/en.json

echo '--- nearby English unit names in resources/lang/en.json ---'
sed -n '1150,1175p' resources/lang/en.json
sed -n '1395,1415p' resources/lang/en.json

echo '--- EventsDisplay.ts around all unit-related formatting ---'
rg -n -A3 -B3 'unitType|pluralizeUnit|translateText\(' src/client/hud/layers/EventsDisplay.ts

Repository: openfrontio/OpenFrontIO

Length of output: 427


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- EventsDisplay.ts around the pluralized unit text ---'
sed -n '250,290p' src/client/hud/layers/EventsDisplay.ts

echo '--- unit_type keys in resources/lang/en.json ---'
rg -n '"unit_type\.(city|factory|port|defense_post|warship|boat|missile_silo|sam_launcher|atom_bomb|hydrogen_bomb|mirv)"' resources/lang/en.json

echo '--- nearby unit_type section ---'
sed -n '1398,1420p' resources/lang/en.json

Repository: openfrontio/OpenFrontIO

Length of output: 1978


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- resources/lang/en.json around the unit type strings ---'
sed -n '1156,1172p' resources/lang/en.json

echo '--- EventsDisplay.ts around the pluralized unit text ---'
sed -n '268,282p' src/client/hud/layers/EventsDisplay.ts

Repository: openfrontio/OpenFrontIO

Length of output: 1479


Use translated unit names here pluralizeUnit hardcodes English forms (Cities, Factories, + "s"). The resources/lang/en.json file already has unit.* keys, so this should use those keys instead of raw enum strings to keep the event text locale-safe.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/client/hud/layers/EventsDisplay.ts` at line 35, The pluralizeUnit helper
in EventsDisplay hardcodes English plural forms, so update it to use the
translated unit-name keys from the language resources instead of deriving text
from the raw enum string. Locate the pluralization logic in pluralizeUnit and
switch it to resolve the appropriate unit.* entries from the i18n layer so event
text stays locale-safe.

Source: Coding guidelines


function parseInboundNuke(m: string): { player: string; nukeType: string } | null {
if (m.endsWith(" - atom bomb inbound")) return { player: m.slice(0, -20), nukeType: "atom" };
if (m.endsWith(" - hydrogen bomb inbound")) return { player: m.slice(0, -24), nukeType: "hydrogen" };
if (m.startsWith("⚠️⚠️⚠️ ") && m.endsWith(" - MIRV INBOUND ⚠️⚠️⚠️")) return { player: m.slice(7, -22), nukeType: "mirv" };
return null;
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const _unused = [
"events_display.atom_bomb_inbound_plural",
"events_display.hydrogen_bomb_inbound_plural",
"events_display.mirv_inbound_plural",
];

interface GameEvent {
description: string;
unsafeDescription?: boolean;
Expand All @@ -41,6 +57,10 @@ interface GameEvent {
onDelete?: () => void;
focusID?: number;
unitView?: UnitView;
groupKey?: string;
count?: number;
unitType?: string;
targetPlayerName?: string;
}

const TIER_1_TYPES: ReadonlySet<MessageType> = new Set([
Expand Down Expand Up @@ -242,40 +262,70 @@ export class EventsDisplay extends LitElement implements Controller {
}

private addEvent(event: GameEvent) {
if (event.groupKey) {
const existing = this.events.find(
(e) => e.groupKey === event.groupKey && this.game.ticks() - e.createdAt <= 30
);
if (existing) {
existing.count = (existing.count ?? 1) + 1;
existing.createdAt = this.game.ticks();
if (event.unitView) existing.unitView = event.unitView;
if (event.focusID) existing.focusID = event.focusID;

const u = pluralizeUnit(existing.unitType ?? "");
const n = existing.targetPlayerName ?? "";
const c = existing.count;

if (existing.groupKey?.startsWith("destroyed_")) {
existing.description = translateText("events_display.unit_destroyed_plural", { count: c, unit: u });
} else if (existing.groupKey?.startsWith("captured_")) {
existing.description = translateText("events_display.unit_captured_plural", { count: c, unit: u, name: n });
} else if (existing.groupKey?.startsWith("lost_")) {
existing.description = translateText("events_display.unit_lost_plural", { count: c, unit: u, name: n });
} else if (existing.groupKey?.startsWith("inbound_")) {
const k = "events_display." + existing.unitType + (existing.unitType === "mirv" ? "" : "_bomb") + "_inbound_plural";
existing.description = translateText(k, { count: c, name: n });
}
this.requestUpdate();
return;
}
}
this.events = [...this.events, event];
this.requestUpdate();
}

onDisplayMessageEvent(event: DisplayMessageUpdate) {
const myPlayer = this.game.myPlayer();
if (
event.playerID !== null &&
(!myPlayer || myPlayer.smallID() !== event.playerID)
) {
return;
}

// Captured trade-ship gold is surfaced as a transient +gold pip in
// control-panel rather than as a scroll-list entry.
if (event.message === "events_display.received_gold_from_captured_ship") {
return;
}

let description: string = event.message;
if (event.message.startsWith("events_display.")) {
description = translateText(event.message, event.params ?? {});
if (event.playerID !== null && (!myPlayer || myPlayer.smallID() !== event.playerID)) return;
if (event.message === "events_display.received_gold_from_captured_ship") return;

const description = event.message.startsWith("events_display.")
? translateText(event.message, event.params ?? {})
: event.message;

let groupKey: string | undefined;
const unitType = String(event.params?.unit ?? "");
const targetPlayerName = String(event.params?.name ?? "");

if (event.message === "events_display.unit_destroyed") {
groupKey = `destroyed_${unitType}`;
} else if (event.message === "events_display.unit_captured") {
groupKey = `captured_${unitType}_${targetPlayerName}`;
} else if (event.message === "events_display.unit_lost") {
groupKey = `lost_${unitType}_${targetPlayerName}`;
}

const unitView =
event.unitID !== undefined ? this.game.unit(event.unitID) : undefined;
this.addEvent({
description: description,
description,
createdAt: this.game.ticks(),
highlight: true,
type: event.messageType,
unsafeDescription: true,
unitView: unitView,
unitView: event.unitID !== undefined ? this.game.unit(event.unitID) : undefined,
focusID: event.focusPlayerID,
groupKey,
unitType,
targetPlayerName,
});
}

Expand Down Expand Up @@ -530,20 +580,19 @@ export class EventsDisplay extends LitElement implements Controller {

onUnitIncomingEvent(event: UnitIncomingUpdate) {
const myPlayer = this.game.myPlayer();
if (!myPlayer || myPlayer.smallID() !== event.playerID) return;

if (!myPlayer || myPlayer.smallID() !== event.playerID) {
return;
}

const unitView = this.game.unit(event.unitID);

const parsed = parseInboundNuke(event.message);
this.addEvent({
description: event.message,
type: event.messageType,
unsafeDescription: false,
highlight: true,
createdAt: this.game.ticks(),
unitView: unitView,
unitView: this.game.unit(event.unitID),
groupKey: parsed ? `inbound_${parsed.nukeType}_${parsed.player}` : undefined,
unitType: parsed?.nukeType,
targetPlayerName: parsed?.player,
});
}

Expand Down
24 changes: 22 additions & 2 deletions src/core/game/UnitImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
AllUnitParams,
MessageType,
Player,
Structures,
Tick,
TrainType,
TrajectoryTile,
Expand Down Expand Up @@ -216,6 +217,24 @@ export class UnitImpl implements Unit {
this.mg.stats().unitLose(this._owner, this._type);
break;
}
if (Structures.has(this._type)) {
this.mg.displayMessage(
"events_display.unit_captured",
MessageType.CAPTURED_ENEMY_UNIT,
newOwner.id(),
undefined,
{ unit: this._type, name: this._owner.displayName() },
this.id(),
);
this.mg.displayMessage(
"events_display.unit_lost",
MessageType.UNIT_DESTROYED,
this._owner.id(),
undefined,
{ unit: this._type, name: newOwner.displayName() },
this.id(),
);
}
this._lastOwner = this._owner;
this._lastOwner._units = this._lastOwner._units.filter((u) => u !== this);
this._owner = newOwner;
Expand Down Expand Up @@ -326,11 +345,12 @@ export class UnitImpl implements Unit {
}

private displayMessageOnDeleted(): void {
// Only warships and transport ships are worth notifying about; everything
// Only warships, transport ships, and structures are worth notifying about; everything
// else is either visible on the map or too low-stakes to surface.
if (
this._type !== UnitType.Warship &&
this._type !== UnitType.TransportShip
this._type !== UnitType.TransportShip &&
!Structures.has(this._type)
) {
return;
}
Expand Down
Loading