diff --git a/resources/QuickChat.json b/resources/QuickChat.json index 82777e696..220da4459 100644 --- a/resources/QuickChat.json +++ b/resources/QuickChat.json @@ -2,222 +2,180 @@ "help": [ { "key": "troops", - "text": "Please give me troops!", "requiresPlayer": false }, { "key": "gold", - "text": "Please give me gold!", "requiresPlayer": false }, { "key": "no_attack", - "text": "Please don't attack me!", "requiresPlayer": false }, { "key": "sorry_attack", - "text": "Sorry, I didn’t mean to attack.", "requiresPlayer": false }, { "key": "alliance", - "text": "Alliance?", "requiresPlayer": false }, { "key": "help_defend", - "text": "Help me defend against [P1]!", "requiresPlayer": true }, { "key": "team_up", - "text": "Let’s team up against [P1]!", "requiresPlayer": true } ], "attack": [ { "key": "attack", - "text": "Attack [P1]!", "requiresPlayer": true }, { "key": "mirv", - "text": "Launch a MIRV at [P1]!", "requiresPlayer": true }, { "key": "focus", - "text": "Focus fire on [P1]!", "requiresPlayer": true }, { "key": "finish", - "text": "Let's finish off [P1]!", "requiresPlayer": true } ], "defend": [ { "key": "defend", - "text": "Defend [P1]!", "requiresPlayer": true }, { "key": "dont_attack", - "text": "Don’t attack [P1]!", "requiresPlayer": true }, { "key": "ally", - "text": "[P1] is my ally!", "requiresPlayer": true } ], "greet": [ { "key": "hello", - "text": "Hello!", "requiresPlayer": false }, { "key": "good_luck", - "text": "Good luck!", "requiresPlayer": false }, { "key": "have_fun", - "text": "Have fun!", "requiresPlayer": false }, { "key": "gg", - "text": "GG!", "requiresPlayer": false }, { "key": "nice_to_meet", - "text": "Nice to meet you!", "requiresPlayer": false }, { "key": "well_played", - "text": "Well played!", "requiresPlayer": false }, { "key": "hi_again", - "text": "Hi again!", "requiresPlayer": false }, { "key": "bye", - "text": "Bye!", "requiresPlayer": false }, { "key": "thanks", - "text": "Thanks!", "requiresPlayer": false }, { "key": "oops", - "text": "Oops, wrong button!", "requiresPlayer": false }, { "key": "trust_me", - "text": "You can trust me. Promise!", "requiresPlayer": false }, { "key": "trust_broken", - "text": "I trusted you...", "requiresPlayer": false } ], "misc": [ { "key": "go", - "text": "Let’s go!", "requiresPlayer": false }, { "key": "strategy", - "text": "Nice strategy!", "requiresPlayer": false }, { "key": "fun", - "text": "This game is fun!", "requiresPlayer": false }, { "key": "pr", - "text": "When will my PR finally get merged...?", "requiresPlayer": false } ], "warnings": [ { "key": "strong", - "text": "[P1] is strong.", "requiresPlayer": true }, { "key": "weak", - "text": "[P1] is weak.", "requiresPlayer": true }, { "key": "mirv_soon", - "text": "[P1] can launch a MIRV soon!", "requiresPlayer": true }, { "key": "number1_warning", - "text": "The #1 player will win soon unless we team up!", "requiresPlayer": false }, { "key": "stalemate", - "text": "Let's make peace. This is a stalemate, we will both lose.", "requiresPlayer": false }, { "key": "has_allies", - "text": "[P1] has many allies.", "requiresPlayer": true }, { "key": "no_allies", - "text": "[P1] has no allies.", "requiresPlayer": true }, { "key": "betrayed", - "text": "[P1] betrayed their ally!", "requiresPlayer": true }, { "key": "getting_big", - "text": "[P1] is growing too fast!", "requiresPlayer": true }, { "key": "danger_base", - "text": "[P1] is unprotected!", "requiresPlayer": true }, { "key": "saving_for_mirv", - "text": "[P1] is saving up to launch a MIRV.", "requiresPlayer": true }, { "key": "mirv_ready", - "text": "[P1] has enough gold to launch a MIRV!", "requiresPlayer": true } ] diff --git a/resources/lang/en.json b/resources/lang/en.json index cc9e2775f..275f12541 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -246,5 +246,78 @@ "move_right_desc": "Move the camera to the right", "reset": "Reset", "unbind": "Unbind" + }, + "chat": { + "title": "Quick Chat", + "to": "Sent {user}: {msg}", + "from": "From {user}: {msg}", + "category": "Category", + "phrase": "Phrase", + "player": "Player", + "send": "Send", + "search": "Search player...", + "build": "Build your message...", + "cat": { + "help": "Help", + "attack": "Attack", + "defend": "Defend", + "greet": "Greetings", + "misc": "Miscellaneous", + "warnings": "Warnings" + }, + "help": { + "troops": "Please give me troops!", + "gold": "Please give me gold!", + "no_attack": "Please don't attack me!", + "sorry_attack": "Sorry, I didn’t mean to attack.", + "alliance": "Alliance?", + "help_defend": "Help me defend against [P1]!", + "team_up": "Let’s team up against [P1]!" + }, + "attack": { + "attack": "Attack [P1]!", + "mirv": "Launch a MIRV at [P1]!", + "focus": "Focus fire on [P1]!", + "finish": "Let's finish off [P1]!" + }, + "defend": { + "defend": "Defend [P1]!", + "dont_attack": "Don’t attack [P1]!", + "ally": "[P1] is my ally!" + }, + "greet": { + "hello": "Hello!", + "good_luck": "Good luck!", + "have_fun": "Have fun!", + "gg": "GG!", + "nice_to_meet": "Nice to meet you!", + "well_played": "Well played!", + "hi_again": "Hi again!", + "bye": "Bye!", + "thanks": "Thanks!", + "oops": "Oops, wrong button!", + "trust_me": "You can trust me. Promise!", + "trust_broken": "I trusted you..." + }, + "misc": { + "go": "Let’s go!", + "strategy": "Nice strategy!", + "fun": "This game is fun!", + "pr": "When will my PR finally get merged...?" + }, + "warnings": { + "strong": "[P1] is strong.", + "weak": "[P1] is weak.", + "mirv_soon": "[P1] can launch a MIRV soon!", + "number1_warning": "The #1 player will win soon unless we team up!", + "stalemate": "Let's make peace. This is a stalemate, we will both lose.", + "has_allies": "[P1] has many allies.", + "no_allies": "[P1] has no allies.", + "betrayed": "[P1] betrayed their ally!", + "getting_big": "[P1] is growing too fast!", + "danger_base": "[P1] is unprotected!", + "saving_for_mirv": "[P1] is saving up to launch a MIRV.", + "mirv_ready": "[P1] has enough gold to launch a MIRV!" + } } } diff --git a/resources/lang/ja.json b/resources/lang/ja.json index a0dc8051b..160e3204a 100644 --- a/resources/lang/ja.json +++ b/resources/lang/ja.json @@ -239,5 +239,78 @@ "continental": "大陸", "regional": "地域", "fantasy": "その他" + }, + "chat": { + "title": "クイックチャット", + "category": "カテゴリ", + "phrase": "フレーズ", + "player": "プレイヤー", + "to": "{user}に送信: {msg}", + "from": "{user}から着信: {msg}", + "send": "送信", + "search": "プレイヤーを検索...", + "build": "チャットを作成...", + "cat": { + "help": "支援要請", + "attack": "攻撃", + "defend": "防御", + "greet": "挨拶", + "misc": "その他", + "warnings": "警告" + }, + "help": { + "troops": "援軍をください!", + "gold": "お金をください!", + "no_attack": "攻撃しないでください!", + "sorry_attack": "ごめん、攻撃するつもりはなかった。", + "alliance": "同盟を組みませんか?", + "help_defend": "[P1] からの防衛を手伝って!", + "team_up": "[P1] に対抗して協力しよう!" + }, + "attack": { + "attack": "[P1] を攻撃しよう!", + "mirv": "[P1] にMIRVを撃とう!", + "focus": "[P1] に集中攻撃しよう!", + "finish": "[P1] を仕留めよう!" + }, + "defend": { + "defend": "[P1] を守って!", + "dont_attack": "[P1] を攻撃しないで!", + "ally": "[P1] は味方だ!" + }, + "greet": { + "hello": "こんにちは!", + "good_luck": "頑張って!", + "have_fun": "楽しもう!", + "gg": "GG!", + "nice_to_meet": "よろしく!", + "well_played": "ナイスプレイ!", + "hi_again": "また会ったね!", + "bye": "バイバイ!", + "thanks": "ありがとう!", + "oops": "あっ、間違えた!", + "trust_me": "信じてくれ、本当だ!", + "trust_broken": "信じてたのに..." + }, + "misc": { + "go": "行こう!", + "strategy": "いい作戦だ!", + "fun": "このゲーム楽しい!", + "pr": "わたしのPRいつマージされるんだろう...?" + }, + "warnings": { + "strong": "[P1] は強い。", + "weak": "[P1] は弱い。", + "mirv_soon": "[P1] がもうすぐMIRVを撃つぞ!", + "number1_warning": "1位のプレイヤーが勝ちそうだ、協力しよう!", + "stalemate": "和平しよう。膠着状態だ、両方負ける。", + "has_allies": "[P1] には味方が多い。", + "no_allies": "[P1] には味方がいない。", + "betrayed": "[P1] は味方を裏切った!", + "getting_big": "[P1] の勢力が急拡大中!", + "danger_base": "[P1] の本拠地が無防備だ!", + "saving_for_mirv": "[P1] はMIRVのために貯金してる。", + "mirv_ready": "[P1] はMIRVを撃てるだけの金を持ってる!" + } } } diff --git a/src/client/graphics/layers/ChatModal.ts b/src/client/graphics/layers/ChatModal.ts index 4d4447877..84162fd33 100644 --- a/src/client/graphics/layers/ChatModal.ts +++ b/src/client/graphics/layers/ChatModal.ts @@ -7,10 +7,10 @@ import { GameView, PlayerView } from "../../../core/game/GameView"; import quickChatData from "../../../../resources/QuickChat.json"; import { EventBus } from "../../../core/EventBus"; import { SendQuickChatEvent } from "../../Transport"; +import { translateText } from "../../Utils"; type QuickChatPhrase = { key: string; - text: string; requiresPlayer: boolean; }; @@ -58,12 +58,12 @@ export class ChatModal extends LitElement { }; private categories = [ - { id: "help", name: "Help" }, - { id: "attack", name: "Attack" }, - { id: "defend", name: "Defend" }, - { id: "greet", name: "Greetings" }, - { id: "misc", name: "Miscellaneous" }, - { id: "warnings", name: "Warnings" }, + { id: "help" }, + { id: "attack" }, + { id: "defend" }, + { id: "greet" }, + { id: "misc" }, + { id: "warnings" }, ]; private getPhrasesForCategory(categoryId: string) { @@ -83,10 +83,10 @@ export class ChatModal extends LitElement { const displayPlayers = [...filteredPlayers, ...otherPlayers]; return html` - +
-
Category
+
${translateText("chat.category")}
${this.categories.map( (category) => html` `, )} @@ -105,13 +105,18 @@ export class ChatModal extends LitElement { ${this.selectedCategory ? html`
-
Phrase
+
+ ${translateText("chat.phrase")} +
${this.getPhrasesForCategory(this.selectedCategory).map( (phrase) => html`
@@ -183,20 +192,24 @@ export class ChatModal extends LitElement { } private selectPhrase(phrase: QuickChatPhrase) { - this.selectedPhraseTemplate = phrase.text; - this.selectedPhraseText = phrase.text; this.selectedQuickChatKey = this.getFullQuickChatKey( this.selectedCategory!, phrase.key, ); - this.previewText = phrase.text; + this.selectedPhraseTemplate = translateText( + `chat.${this.selectedCategory}.${phrase.key}`, + ); + this.selectedPhraseText = translateText( + `chat.${this.selectedCategory}.${phrase.key}`, + ); + this.previewText = `chat.${this.selectedCategory}.${phrase.key}`; this.requiresPlayerSelection = phrase.requiresPlayer; this.selectedPlayer = null; this.requestUpdate(); } - private renderPhrasePreview(phrase: { text: string }) { - return phrase.text.replace("[P1]", "___"); // 仮表示 + private renderPhrasePreview(phrase: { key: string }) { + return translateText(`chat.${this.selectedCategory}.${phrase.key}`); } private selectPlayer(player: string) { @@ -270,6 +283,7 @@ export class ChatModal extends LitElement { this.recipient = recipient; this.sender = sender; } + this.requestUpdate(); this.modalEl?.open(); } diff --git a/src/client/graphics/layers/EventsDisplay.ts b/src/client/graphics/layers/EventsDisplay.ts index 95fd89c54..d1d705502 100644 --- a/src/client/graphics/layers/EventsDisplay.ts +++ b/src/client/graphics/layers/EventsDisplay.ts @@ -16,6 +16,7 @@ import { AllianceRequestUpdate, AttackUpdate, BrokeAllianceUpdate, + DisplayChatMessageUpdate, DisplayMessageUpdate, EmojiUpdate, GameUpdateType, @@ -33,6 +34,8 @@ import { onlyImages } from "../../../core/Util"; import { renderTroops } from "../../Utils"; import { GoToPlayerEvent, GoToUnitEvent } from "./Leaderboard"; +import { translateText } from "../../Utils"; + interface Event { description: string; unsafeDescription?: boolean; @@ -77,6 +80,7 @@ export class EventsDisplay extends LitElement implements Layer { private updateMap = new Map([ [GameUpdateType.DisplayEvent, (u) => this.onDisplayMessageEvent(u)], + [GameUpdateType.DisplayChatEvent, (u) => this.onDisplayChatEvent(u)], [GameUpdateType.AllianceRequest, (u) => this.onAllianceRequestEvent(u)], [ GameUpdateType.AllianceRequestReply, @@ -187,6 +191,34 @@ export class EventsDisplay extends LitElement implements Layer { }); } + onDisplayChatEvent(event: DisplayChatMessageUpdate) { + const myPlayer = this.game.playerByClientID(this.clientID); + if ( + event.playerID === null || + !myPlayer || + myPlayer.smallID() !== event.playerID + ) { + return; + } + + const baseMessage = translateText(`chat.${event.category}.${event.key}`); + const translatedMessage = baseMessage.replace( + /\[([^\]]+)\]/g, + (_, key) => event.variables?.[key] || `[${key}]`, + ); + + this.addEvent({ + description: translateText(event.isFrom ? "chat.from" : "chat.to", { + user: event.recipient, + msg: translatedMessage, + }), + createdAt: this.game.ticks(), + highlight: true, + type: MessageType.CHAT, + unsafeDescription: false, + }); + } + onAllianceRequestEvent(update: AllianceRequestUpdate) { const myPlayer = this.game.playerByClientID(this.clientID); if (!myPlayer || update.recipientID !== myPlayer.smallID()) { diff --git a/src/core/execution/QuickChatExecution.ts b/src/core/execution/QuickChatExecution.ts index 919979997..737497921 100644 --- a/src/core/execution/QuickChatExecution.ts +++ b/src/core/execution/QuickChatExecution.ts @@ -1,6 +1,5 @@ -import quickChatData from "../../../resources/QuickChat.json"; import { consolex } from "../Consolex"; -import { Execution, Game, MessageType, Player, PlayerID } from "../game/Game"; +import { Execution, Game, Player, PlayerID } from "../game/Game"; export class QuickChatExecution implements Execution { private sender: Player; @@ -38,16 +37,22 @@ export class QuickChatExecution implements Execution { tick(ticks: number): void { const message = this.getMessageFromKey(this.quickChatKey, this.variables); - this.mg.displayMessage( - `${this.sender.name()}: ${message}`, - MessageType.CHAT, + this.mg.displayChat( + message[1], + message[0], + this.variables, this.recipient.id(), + true, + this.recipient.name(), ); - this.mg.displayMessage( - `You sent to ${this.recipient.name()}: ${message}`, - MessageType.CHAT, + this.mg.displayChat( + message[1], + message[0], + this.variables, this.sender.id(), + false, + this.recipient.name(), ); consolex.log( @@ -72,27 +77,8 @@ export class QuickChatExecution implements Execution { private getMessageFromKey( fullKey: string, vars: Record, - ): string { - // Key for translation - const [category, key] = fullKey.split("."); - const phrases = quickChatData[category]; - - if (!phrases) { - consolex.warn(`QuickChat: Unknown category '${category}'`); - return `[${fullKey}]`; - } - - const phraseObj = phrases.find((p) => p.key === key); - if (!phraseObj) { - consolex.warn( - `QuickChat: Key '${key}' not found in category '${category}'`, - ); - return `[${fullKey}]`; - } - - return phraseObj.text.replace( - /\[(\w+)\]/g, - (_, p1) => vars[p1] || `[${p1}]`, - ); + ): string[] { + const translated = fullKey.split("."); + return translated; } } diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts index 25f3aa3f7..b00a57405 100644 --- a/src/core/game/Game.ts +++ b/src/core/game/Game.ts @@ -509,6 +509,15 @@ export interface Game extends GameMap { playerID: PlayerID | null, ): void; + displayChat( + message: string, + category: string, + variables: Record, + playerID: PlayerID | null, + isFrom: boolean, + recipient: string, + ): void; + // Nations nations(): Nation[]; diff --git a/src/core/game/GameImpl.ts b/src/core/game/GameImpl.ts index 75342fbfc..698689537 100644 --- a/src/core/game/GameImpl.ts +++ b/src/core/game/GameImpl.ts @@ -606,6 +606,28 @@ export class GameImpl implements Game { }); } + displayChat( + message: string, + category: string, + variables: Record = {}, + playerID: PlayerID | null, + isFrom: boolean | null = null, + recipient: string, + ): void { + let id = null; + if (playerID != null) { + id = this.player(playerID).smallID(); + } + this.addUpdate({ + type: GameUpdateType.DisplayChatEvent, + key: message, + category: category, + variables: variables, + playerID: id, + isFrom: isFrom, + recipient: recipient, + }); + } addUnit(u: Unit) { this.unitGrid.addUnit(u); } diff --git a/src/core/game/GameUpdates.ts b/src/core/game/GameUpdates.ts index 5f78111f9..dc9b3e313 100644 --- a/src/core/game/GameUpdates.ts +++ b/src/core/game/GameUpdates.ts @@ -29,6 +29,7 @@ export enum GameUpdateType { Unit, Player, DisplayEvent, + DisplayChatEvent, AllianceRequest, AllianceRequestReply, BrokeAlliance, @@ -48,6 +49,7 @@ export type GameUpdate = | BrokeAllianceUpdate | AllianceExpiredUpdate | DisplayMessageUpdate + | DisplayChatMessageUpdate | TargetPlayerUpdate | EmojiUpdate | WinUpdate @@ -157,6 +159,16 @@ export interface DisplayMessageUpdate { playerID: number | null; } +export type DisplayChatMessageUpdate = { + type: GameUpdateType.DisplayChatEvent; + key: string; + category: string; + variables?: Record; + playerID: number | null; + isFrom: boolean; + recipient: string; +}; + export interface WinUpdate { type: GameUpdateType.Win; allPlayersStats: AllPlayersStats;