Add localization support for game events, settings, and UI text elements (#1372)

## Description:

This PR adds missing translations for various UI components, including
game events and settings. No visual changes were made, so screenshots
are not required. All user-facing text has been properly localized and
added to the en.json file.

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
- [x] I understand that submitting code with bugs that could have been
caught through manual testing blocks releases and new features for all
contributors

## Please put your Discord username so you can be contacted if a bug or
regression is found:

pierogi69

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
This commit is contained in:
Tomasz
2025-07-08 21:39:28 +02:00
committed by GitHub
parent 2cbe34ed79
commit 4ed4c7e507
6 changed files with 135 additions and 51 deletions
+37 -3
View File
@@ -60,6 +60,7 @@
"ui_options_desc": "The following elements can be found inside:",
"ui_playeroverlay": "Player info overlay",
"ui_playeroverlay_desc": "When you hover over a country, the Player info overlay is displayed under Options. It shows the type of player: Human, Nation (smart bot), or Bot. A Nation's attitude towards you, ranging from Hostile to Friendly. And defending troops, gold, plus the number of Warships and various buildings the player has.",
"ui_wilderness": "Wilderness",
"option_pause": "Pause/Unpause the game - Only available in single player mode.",
"option_timer": "Timer - Time passed since the start of the game.",
"option_exit": "Exit button.",
@@ -112,7 +113,8 @@
"icon_ally": "Handshake - Ally. This player is your ally.",
"icon_embargo": "Dollar stop sign - Embargo. This player has stopped trading with you automatically or manually.",
"icon_request": "Envelope - Alliance request. This player has sent you an alliance request.",
"info_enemy_panel": "Enemy info panel"
"info_enemy_panel": "Enemy info panel",
"exit_confirmation": "Are you sure you want to exit the game?"
},
"single_modal": {
"title": "Single Player",
@@ -255,16 +257,27 @@
"tab_keybinds": "Keybinds",
"dark_mode_label": "Dark Mode",
"dark_mode_desc": "Toggle the sites appearance between light and dark themes",
"dark_mode_enabled": "Dark mode enabled",
"light_mode_enabled": "Light mode enabled",
"emojis_label": "Emojis",
"emojis_visible": "Emojis are visible",
"emojis_hidden": "Emojis are hidden",
"emojis_desc": "Toggle whether emojis are shown in game",
"alert_frame_label": "Alert Frame",
"alert_frame_desc": "Toggle the alert frame. When enabled, the frame will be displayed when you are betrayed.",
"special_effects_label": "Special effects",
"special_effects_desc": "Toggle special effects. Deactivate to improve performances",
"special_effects_enabled": "Special effects enabled",
"special_effects_disabled": "Special effects disabled",
"anonymous_names_label": "Hidden Names",
"anonymous_names_desc": "Hide real player names with random ones on your screen.",
"anonymous_names_enabled": "Anonymous names enabled",
"real_names_shown": "Real names shown",
"left_click_label": "Left Click to Open Menu",
"left_click_desc": "When ON, left-click opens menu and sword button attacks. When OFF, left-click attacks directly.",
"left_click_menu": "Left Click Menu",
"left_click_opens_menu": "Left click opens menu",
"right_click_opens_menu": "Right click opens menu",
"attack_ratio_label": "⚔️ Attack Ratio",
"attack_ratio_desc": "What percentage of your troops to send in an attack (1100%)",
"troop_ratio_label": "🪖🛠️ Troops and Workers Ratio",
@@ -305,7 +318,14 @@
"move_right": "Move Camera Right",
"move_right_desc": "Move the camera to the right",
"reset": "Reset",
"unbind": "Unbind"
"unbind": "Unbind",
"on": "On",
"off": "Off",
"toggle_terrain": "Toggle Terrain",
"terrain_enabled": "Terrain view enabled",
"terrain_disabled": "Terrain view disabled",
"exit_game_label": "Exit Game",
"exit_game_info": "Return to main menu"
},
"chat": {
"title": "Quick Chat",
@@ -437,9 +457,22 @@
"events_display": {
"retreating": "retreating",
"boat": "Boat",
"alliance_request_status": "{name} {status} your alliance request",
"alliance_accepted": "accepted",
"alliance_rejected": "rejected",
"duration_second": "1 second",
"betrayal_description": "You broke your alliance with {name}, making you a TRAITOR ({malusPercent}% defense debuff for {durationText})",
"duration_seconds_plural": "{seconds} seconds",
"betrayed_you": "{name} broke their alliance with you",
"about_to_expire": "Your alliance with {name} is about to expire!",
"alliance_expired": "Your alliance with {name} expired",
"attack_request": "{name} requests you attack {target}",
"sent_emoji": "Sent {name}: {emoji}",
"renew_alliance": "Request to renew",
"request_alliance": "{name} requests an alliance!",
"focus": "Focus",
"accept_alliance": "Accept",
"reject_alliance": "Reject",
"alliance_renewed": "Your alliance with {name} has been renewed",
"ignore": "Ignore"
},
@@ -483,7 +516,8 @@
},
"replay_panel": {
"replay_speed": "Replay speed",
"game_speed": "Game speed"
"game_speed": "Game speed",
"fastest_game_speed": "max"
},
"error_modal": {
"crashed": "Game crashed!",
+40 -21
View File
@@ -412,16 +412,18 @@ export class EventsDisplay extends LitElement implements Layer {
) as PlayerView;
this.addEvent({
description: `${requestor.name()} requests an alliance!`,
description: translateText("events_display.request_alliance", {
name: requestor.name(),
}),
buttons: [
{
text: "Focus",
text: translateText("events_display.focus"),
className: "btn-gray",
action: () => this.eventBus.emit(new GoToPlayerEvent(requestor)),
preventClose: true,
},
{
text: "Accept",
text: translateText("events_display.accept_alliance"),
className: "btn",
action: () =>
this.eventBus.emit(
@@ -429,7 +431,7 @@ export class EventsDisplay extends LitElement implements Layer {
),
},
{
text: "Reject",
text: translateText("events_display.reject_alliance"),
className: "btn-info",
action: () =>
this.eventBus.emit(
@@ -461,9 +463,12 @@ export class EventsDisplay extends LitElement implements Layer {
) as PlayerView;
this.addEvent({
description: `${recipient.name()} ${
update.accepted ? "accepted" : "rejected"
} your alliance request`,
description: translateText("events_display.alliance_request_status", {
name: recipient.name(),
status: update.accepted
? translateText("events_display.alliance_accepted")
: translateText("events_display.alliance_rejected"),
}),
type: update.accepted
? MessageType.ALLIANCE_ACCEPTED
: MessageType.ALLIANCE_REJECTED,
@@ -489,12 +494,18 @@ export class EventsDisplay extends LitElement implements Layer {
this.game.config().traitorDuration() * 0.1,
);
const durationText =
traitorDuration === 1 ? "1 second" : `${traitorDuration} seconds`;
traitorDuration === 1
? translateText("events_display.duration_second")
: translateText("events_display.duration_seconds_plural", {
seconds: traitorDuration,
});
this.addEvent({
description:
`You broke your alliance with ${betrayed.name()}, making you a TRAITOR ` +
`(${malusPercent}% defense debuff for ${durationText})`,
description: translateText("events_display.betrayal_description", {
name: betrayed.name(),
malusPercent: malusPercent,
durationText: durationText,
}),
type: MessageType.ALLIANCE_BROKEN,
highlight: true,
createdAt: this.game.ticks(),
@@ -503,14 +514,16 @@ export class EventsDisplay extends LitElement implements Layer {
} else if (betrayed === myPlayer) {
const buttons = [
{
text: "Focus",
text: translateText("events_display.focus"),
className: "btn-gray",
action: () => this.eventBus.emit(new GoToPlayerEvent(traitor)),
preventClose: true,
},
];
this.addEvent({
description: `${traitor.name()} broke their alliance with you`,
description: translateText("events_display.betrayed_you", {
name: traitor.name(),
}),
type: MessageType.ALLIANCE_BROKEN,
highlight: true,
createdAt: this.game.ticks(),
@@ -535,7 +548,9 @@ export class EventsDisplay extends LitElement implements Layer {
if (!other || !myPlayer.isAlive() || !other.isAlive()) return;
this.addEvent({
description: `Your alliance with ${other.name()} expired`,
description: translateText("events_display.alliance_expired", {
name: other.name(),
}),
type: MessageType.ALLIANCE_EXPIRED,
highlight: true,
createdAt: this.game.ticks(),
@@ -551,7 +566,10 @@ export class EventsDisplay extends LitElement implements Layer {
const target = this.game.playerBySmallID(event.targetID) as PlayerView;
this.addEvent({
description: `${other.name()} requests you attack ${target.name()}`,
description: translateText("events_display.attack_request", {
name: other.name(),
target: target.name(),
}),
type: MessageType.ATTACK_REQUEST,
highlight: true,
createdAt: this.game.ticks(),
@@ -599,7 +617,7 @@ export class EventsDisplay extends LitElement implements Layer {
if (recipient === myPlayer) {
this.addEvent({
description: `${sender.displayName()}:${update.emoji.message}`,
description: `${sender.displayName()}: ${update.emoji.message}`,
unsafeDescription: true,
type: MessageType.CHAT,
highlight: true,
@@ -608,9 +626,10 @@ export class EventsDisplay extends LitElement implements Layer {
});
} else if (sender === myPlayer && recipient !== AllPlayers) {
this.addEvent({
description: `Sent ${(recipient as PlayerView).displayName()}: ${
update.emoji.message
}`,
description: translateText("events_display.sent_emoji", {
name: (recipient as PlayerView).displayName(),
emoji: update.emoji.message,
}),
unsafeDescription: true,
type: MessageType.CHAT,
highlight: true,
@@ -746,7 +765,7 @@ export class EventsDisplay extends LitElement implements Layer {
<div class="inline-flex items-center gap-1">
${this.renderButton({
content: html`${renderTroops(landAttack.troops)}
Wilderness`,
${translateText("help_modal.ui_wilderness")}`,
className: "text-left text-gray-400",
translate: false,
})}
@@ -953,7 +972,7 @@ export class EventsDisplay extends LitElement implements Layer {
>`
: ""}
${this.renderButton({
content: "Hide",
content: translateText("leaderboard.hide"),
onClick: this.toggleHidden,
className:
"text-white cursor-pointer pointer-events-auto",
@@ -11,6 +11,7 @@ import { GameType } from "../../../core/game/Game";
import { GameUpdateType } from "../../../core/game/GameUpdates";
import { GameView } from "../../../core/game/GameView";
import { PauseGameEvent } from "../../Transport";
import { translateText } from "../../Utils";
import { Layer } from "./Layer";
import { ShowReplayPanelEvent } from "./ReplayPanel";
import { ShowSettingsModalEvent } from "./SettingsModal";
@@ -86,7 +87,9 @@ export class GameRightSidebar extends LitElement implements Layer {
private onExitButtonClick() {
const isAlive = this.game.myPlayer()?.isAlive();
if (isAlive) {
const isConfirmed = confirm("Are you sure you want to exit the game?");
const isConfirmed = confirm(
translateText("help_modal.exit_confirmation"),
);
if (!isConfirmed) return;
}
// redirect to the home page
+4 -1
View File
@@ -7,6 +7,7 @@ import { GameView } from "../../../core/game/GameView";
import { UserSettings } from "../../../core/game/UserSettings";
import { AlternateViewEvent, RefreshGraphicsEvent } from "../../InputHandler";
import { PauseGameEvent } from "../../Transport";
import { translateText } from "../../Utils";
import { Layer } from "./Layer";
const button = ({
@@ -74,7 +75,9 @@ export class OptionsMenu extends LitElement implements Layer {
private onExitButtonClick() {
const isAlive = this.game.myPlayer()?.isAlive();
if (isAlive) {
const isConfirmed = confirm("Are you sure you want to exit the game?");
const isConfirmed = confirm(
translateText("help_modal.exit_confirmation"),
);
if (!isConfirmed) return;
}
// redirect to the home page
+4 -1
View File
@@ -74,7 +74,10 @@ export class ReplayPanel extends LitElement implements Layer {
${this.renderSpeedButton(ReplaySpeedMultiplier.slow, "×0.5")}
${this.renderSpeedButton(ReplaySpeedMultiplier.normal, "×1")}
${this.renderSpeedButton(ReplaySpeedMultiplier.fast, "×2")}
${this.renderSpeedButton(ReplaySpeedMultiplier.fastest, "max")}
${this.renderSpeedButton(
ReplaySpeedMultiplier.fastest,
translateText("replay_panel.fastest_game_speed"),
)}
</div>
</div>
`;
+46 -24
View File
@@ -8,10 +8,10 @@ import mouseIcon from "../../../../resources/images/MouseIconWhite.svg";
import ninjaIcon from "../../../../resources/images/NinjaIconWhite.svg";
import settingsIcon from "../../../../resources/images/SettingIconWhite.svg";
import treeIcon from "../../../../resources/images/TreeIconWhite.svg";
import { translateText } from "../../../client/Utils";
import { EventBus } from "../../../core/EventBus";
import { UserSettings } from "../../../core/game/UserSettings";
import { AlternateViewEvent, RefreshGraphicsEvent } from "../../InputHandler";
import { translateText } from "../../Utils";
import { Layer } from "./Layer";
export class ShowSettingsModalEvent {
@@ -143,7 +143,9 @@ export class SettingsModal extends LitElement implements Layer {
height="24"
style="vertical-align: middle;"
/>
<h2 class="text-xl font-semibold text-white">Settings</h2>
<h2 class="text-xl font-semibold text-white">
${translateText("user_setting.tab_basic")}
</h2>
</div>
<button
class="text-slate-400 hover:text-white text-2xl font-bold leading-none"
@@ -160,15 +162,19 @@ export class SettingsModal extends LitElement implements Layer {
>
<img src=${treeIcon} alt="treeIcon" width="20" height="20" />
<div class="flex-1">
<div class="font-medium">Toggle Terrain</div>
<div class="font-medium">
${translateText("user_setting.toggle_terrain")}
</div>
<div class="text-sm text-slate-400">
${this.alternateView
? "Terrain view enabled"
: "Terrain view disabled"}
? translateText("user_setting.terrain_enabled")
: translateText("user_setting.terrain_disabled")}
</div>
</div>
<div class="text-sm text-slate-400">
${this.alternateView ? "On" : "Off"}
${this.alternateView
? translateText("user_setting.on")
: translateText("user_setting.off")}
</div>
</button>
@@ -183,12 +189,14 @@ export class SettingsModal extends LitElement implements Layer {
</div>
<div class="text-sm text-slate-400">
${this.userSettings.emojis()
? "Emojis are visible"
: "Emojis are hidden"}
? translateText("user_setting.emojis_visible")
: translateText("user_setting.emojis_hidden")}
</div>
</div>
<div class="text-sm text-slate-400">
${this.userSettings.emojis() ? "On" : "Off"}
${this.userSettings.emojis()
? translateText("user_setting.on")
: translateText("user_setting.off")}
</div>
</button>
@@ -208,12 +216,14 @@ export class SettingsModal extends LitElement implements Layer {
</div>
<div class="text-sm text-slate-400">
${this.userSettings.darkMode()
? "Dark mode enabled"
: "Light mode enabled"}
? translateText("user_setting.dark_mode_enabled")
: translateText("user_setting.light_mode_enabled")}
</div>
</div>
<div class="text-sm text-slate-400">
${this.userSettings.darkMode() ? "On" : "Off"}
${this.userSettings.darkMode()
? translateText("user_setting.on")
: translateText("user_setting.off")}
</div>
</button>
@@ -233,12 +243,14 @@ export class SettingsModal extends LitElement implements Layer {
</div>
<div class="text-sm text-slate-400">
${this.userSettings.fxLayer()
? "Special effects enabled"
: "Special effects disabled"}
? translateText("user_setting.special_effects_enabled")
: translateText("user_setting.special_effects_disabled")}
</div>
</div>
<div class="text-sm text-slate-400">
${this.userSettings.fxLayer() ? "On" : "Off"}
${this.userSettings.fxLayer()
? translateText("user_setting.on")
: translateText("user_setting.off")}
</div>
</button>
@@ -253,12 +265,14 @@ export class SettingsModal extends LitElement implements Layer {
</div>
<div class="text-sm text-slate-400">
${this.userSettings.anonymousNames()
? "Anonymous names enabled"
: "Real names shown"}
? translateText("user_setting.anonymous_names_enabled")
: translateText("user_setting.real_names_shown")}
</div>
</div>
<div class="text-sm text-slate-400">
${this.userSettings.anonymousNames() ? "On" : "Off"}
${this.userSettings.anonymousNames()
? translateText("user_setting.on")
: translateText("user_setting.off")}
</div>
</button>
@@ -268,15 +282,19 @@ export class SettingsModal extends LitElement implements Layer {
>
<img src=${mouseIcon} alt="mouseIcon" width="20" height="20" />
<div class="flex-1">
<div class="font-medium">Left Click Menu</div>
<div class="font-medium">
${translateText("user_setting.left_click_menu")}
</div>
<div class="text-sm text-slate-400">
${this.userSettings.leftClickOpensMenu()
? "Left click opens menu"
: "Right click opens menu"}
? translateText("user_setting.left_click_opens_menu")
: translateText("user_setting.right_click_opens_menu")}
</div>
</div>
<div class="text-sm text-slate-400">
${this.userSettings.leftClickOpensMenu() ? "On" : "Off"}
${this.userSettings.leftClickOpensMenu()
? translateText("user_setting.on")
: translateText("user_setting.off")}
</div>
</button>
@@ -287,8 +305,12 @@ export class SettingsModal extends LitElement implements Layer {
>
<img src=${exitIcon} alt="exitIcon" width="20" height="20" />
<div class="flex-1">
<div class="font-medium">Exit Game</div>
<div class="text-sm text-slate-400">Return to main menu</div>
<div class="font-medium">
${translateText("user_setting.exit_game_label")}
</div>
<div class="text-sm text-slate-400">
${translateText("user_setting.exit_game_info")}
</div>
</div>
</button>
</div>