diff --git a/assets/voxygen/i18n/en/hud/chat.ftl b/assets/voxygen/i18n/en/hud/chat.ftl index 2cf4dcf22b..36e860132c 100644 --- a/assets/voxygen/i18n/en/hud/chat.ftl +++ b/assets/voxygen/i18n/en/hud/chat.ftl @@ -1,30 +1,39 @@ -hud-chat-all = All -hud-chat-you = You -hud-chat-mod = Mod -hud-chat-chat_tab_hover_tooltip = Right click for settings +## Player events +hud-chat-online_msg = [{ $name }] is online now +hud-chat-offline_msg = [{ $name }] went offline +## Buff outcomes hud-outcome-burning = died of: burning hud-outcome-curse = died of: curse hud-outcome-bleeding = died of: bleeding hud-outcome-crippled = died of: crippled hud-outcome-frozen = died of: frozen -hud-chat-online_msg = [{ $name }] is online now -hud-chat-offline_msg = [{ $name }] went offline -hud-chat-default_death_msg = [{ $name }] died -hud-chat-environmental_kill_msg = [{ $name }] died in { $environment } -hud-chat-fall_kill_msg = [{ $name }] died from fall damage -hud-chat-suicide_msg = [{ $name }] died from self-inflicted wounds +hud-outcome-mysterious = died of: secret +## Buff deaths hud-chat-died_of_pvp_buff_msg = [{ $victim }] { $died_of_buff } caused by [{ $attacker }] +hud-chat-died_of_buff_nonexistent_msg = [{ $victim }] { $died_of_buff } +hud-chat-died_of_npc_buff_msg = [{ $victim }] { $died_of_buff } caused by { $attacker } +## PvP deaths hud-chat-pvp_melee_kill_msg = [{ $attacker }] defeated [{ $victim }] hud-chat-pvp_ranged_kill_msg = [{ $attacker }] shot [{ $victim }] hud-chat-pvp_explosion_kill_msg = [{ $attacker }] blew up [{ $victim }] hud-chat-pvp_energy_kill_msg = [{ $attacker }] killed [{ $victim }] with magic -hud-chat-died_of_buff_nonexistent_msg = [{ $victim }] { $died_of_buff } -hud-chat-died_of_npc_buff_msg = [{ $victim }] { $died_of_buff } caused by { $attacker } +hud-chat-pvp_other_kill_msg = [{ $attacker }] killed [{ $victim }] +## PvE deaths hud-chat-npc_melee_kill_msg = { $attacker } killed [{ $victim }] hud-chat-npc_ranged_kill_msg = { $attacker } shot [{ $victim }] hud-chat-npc_explosion_kill_msg = { $attacker } blew up [{ $victim }] hud-chat-npc_energy_kill_msg = { $attacker } killed [{ $victim }] with magic hud-chat-npc_other_kill_msg = { $attacker } killed [{ $victim }] +## Other deaths +hud-chat-environmental_kill_msg = [{ $name }] died in { $environment } +hud-chat-fall_kill_msg = [{ $name }] died from fall damage +hud-chat-suicide_msg = [{ $name }] died from self-inflicted wounds +hud-chat-default_death_msg = [{ $name }] died +## Utils +hud-chat-all = All +hud-chat-you = You +hud-chat-mod = Mod +hud-chat-chat_tab_hover_tooltip = Right click for settings hud-loot-pickup-msg = {$actor} picked up { $amount -> [one] { $item } *[other] {$amount}x {$item} diff --git a/client/examples/chat-cli/main.rs b/client/examples/chat-cli/main.rs index 8f6f128ffe..7c60e062f2 100644 --- a/client/examples/chat-cli/main.rs +++ b/client/examples/chat-cli/main.rs @@ -12,7 +12,7 @@ use std::{ use tokio::runtime::Runtime; use tracing::{error, info}; use veloren_client::{addr::ConnectionArgs, Client, Event}; -use voxygen_i18n_helpers::internationalisate_chat_message; +use voxygen_i18n_helpers::localize_chat_message; const TPS: u64 = 10; // Low value is okay, just reading messages. @@ -97,7 +97,7 @@ fn main() { match event { Event::Chat(m) => println!( "{}", - internationalisate_chat_message( + localize_chat_message( m, |msg| client.lockup_msg_context(msg), &localisation.read(), diff --git a/client/src/lib.rs b/client/src/lib.rs index c993a51360..f2da92c438 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -2643,12 +2643,12 @@ impl Client { /// Get important information from client that is necessary for message /// localisation - pub fn lockup_msg_context( - &self, - msg: &comp::ChatMsg, - ) -> std::collections::HashMap<&'static str, ChatTypeContext> { - let mut result = std::collections::HashMap::new(); - let comp::ChatMsg { chat_type, .. } = &msg; + pub fn lockup_msg_context(&self, msg: &comp::ChatMsg) -> ChatTypeContext { + let mut result = ChatTypeContext { + you: self.uid().expect("Client doesn't have a Uid!!!"), + player_alias: HashMap::new(), + entity_name: HashMap::new(), + }; let name_of_uid = |uid| { let ecs = self.state.ecs(); ( @@ -2659,35 +2659,32 @@ impl Client { .find(|(_, u)| u == &uid) .map(|(c, _)| c.name.clone()) }; - let alias_of_uid = |uid| { - self.player_list.get(uid).map_or( - ChatTypeContext::Raw(name_of_uid(uid).unwrap_or_else(|| "".to_string())), - |player_info| ChatTypeContext::PlayerAlias { - you: self.uid().expect("Client doesn't have a Uid!!!") == *uid, - info: player_info.clone(), - }, - ) + let mut alias_of_uid = |uid| match self.player_list.get(uid) { + Some(player_info) => { + result.player_alias.insert(*uid, player_info.clone()); + }, + None => { + result + .entity_name + .insert(*uid, name_of_uid(uid).unwrap_or_else(|| "".to_string())); + }, }; - match chat_type { + match &msg.chat_type { comp::ChatType::Online(uid) | comp::ChatType::Offline(uid) => { - result.insert("name", alias_of_uid(uid)); + alias_of_uid(uid); }, comp::ChatType::CommandError => (), comp::ChatType::CommandInfo => (), comp::ChatType::FactionMeta(_) => (), comp::ChatType::GroupMeta(_) => (), comp::ChatType::Kill(kill_source, victim) => { - result.insert("victim", alias_of_uid(victim)); + alias_of_uid(victim); match kill_source { KillSource::Player(attacker_uid, _) => { - result.insert("attacker", alias_of_uid(attacker_uid)); - }, - KillSource::NonPlayer(attacker_name, _) => { - result.insert("attacker", ChatTypeContext::Raw(attacker_name.clone())); - }, - KillSource::Environment(environment) => { - result.insert("environment", ChatTypeContext::Raw(environment.clone())); + alias_of_uid(attacker_uid); }, + KillSource::NonPlayer(_, _) => (), + KillSource::Environment(_) => (), KillSource::FallDamage => (), KillSource::Suicide => (), KillSource::NonExistent(_) => (), @@ -2695,22 +2692,19 @@ impl Client { }; }, comp::ChatType::Tell(from, to) | comp::ChatType::NpcTell(from, to, _) => { - result.insert("from", alias_of_uid(from)); - result.insert("to", alias_of_uid(to)); + alias_of_uid(from); + alias_of_uid(to); }, comp::ChatType::Say(uid) | comp::ChatType::Region(uid) | comp::ChatType::World(uid) | comp::ChatType::NpcSay(uid, _) => { - result.insert("from", alias_of_uid(uid)); + alias_of_uid(uid); }, - comp::ChatType::Group(uid, s) | comp::ChatType::Faction(uid, s) => { - result.insert("from", alias_of_uid(uid)); - result.insert("group_name", ChatTypeContext::Raw(s.clone())); + comp::ChatType::Group(uid, _) | comp::ChatType::Faction(uid, _) => { + alias_of_uid(uid); }, - // NPCs can't talk. Should be filtered by hud/mod.rs for voxygen and should be filtered - // by server (due to not having a Pos) for chat-cli - comp::ChatType::Npc(_uid, _r) => (), + comp::ChatType::Npc(uid, _) => alias_of_uid(uid), comp::ChatType::Meta => (), }; result @@ -2826,7 +2820,7 @@ mod tests { /// CONTACT @Core Developer BEFORE MERGING CHANGES TO THIS TEST fn constant_api_test() { use common::clock::Clock; - use voxygen_i18n_helpers::internationalisate_chat_message; + use voxygen_i18n_helpers::localize_chat_message; const SPT: f64 = 1.0 / 60.0; @@ -2865,7 +2859,7 @@ mod tests { match event { Event::Chat(msg) => { let msg: comp::ChatMsg = msg; - let _s: String = internationalisate_chat_message( + let _s: String = localize_chat_message( msg, |msg| client.lockup_msg_context(msg), &localisation.read(), diff --git a/common/net/src/msg/server.rs b/common/net/src/msg/server.rs index 415bb46567..f226d6d552 100644 --- a/common/net/src/msg/server.rs +++ b/common/net/src/msg/server.rs @@ -246,10 +246,10 @@ pub struct PlayerInfo { } /// used for localisation, filled by client and used by i18n code -#[derive(Clone)] -pub enum ChatTypeContext { - PlayerAlias { you: bool, info: PlayerInfo }, - Raw(String), +pub struct ChatTypeContext { + pub you: Uid, + pub player_alias: HashMap, + pub entity_name: HashMap, } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/voxygen/i18n-helpers/src/lib.rs b/voxygen/i18n-helpers/src/lib.rs index 200dd56709..3012416737 100644 --- a/voxygen/i18n-helpers/src/lib.rs +++ b/voxygen/i18n-helpers/src/lib.rs @@ -1,132 +1,209 @@ +#![feature(let_chains)] use common::comp::{ chat::{KillSource, KillType}, BuffKind, ChatMsg, ChatType, }; use common_net::msg::{ChatTypeContext, PlayerInfo}; use i18n::Localization; -use std::collections::HashMap; -pub fn internationalisate_chat_message( +pub fn localize_chat_message( mut msg: ChatMsg, - lookup_fn: impl Fn(&ChatMsg) -> HashMap<&'static str, ChatTypeContext>, + lookup_fn: impl Fn(&ChatMsg) -> ChatTypeContext, localisation: &Localization, show_char_name: bool, ) -> ChatMsg { let info = lookup_fn(&msg); - if let Some(template_key) = get_chat_template_key(&msg.chat_type) { - msg.message = localisation - .get_msg_ctx(template_key, &i18n::fluent_args! { - "attacker" => "{attacker}", - "name" => "{name}", - "died_of_buff" => "{died_of_buff}", - "victim" => "{victim}", - "environment" => "{environment}", - }) - .into_owned(); - if let ChatType::Kill(kill_source, _) = &msg.chat_type { - match kill_source { - KillSource::Player(_, KillType::Buff(buffkind)) - | KillSource::NonExistent(KillType::Buff(buffkind)) - | KillSource::NonPlayer(_, KillType::Buff(buffkind)) => { - msg.message = insert_killing_buff(*buffkind, localisation, &msg.message); - }, - _ => {}, - } - } - } - let message_format = |you, info: PlayerInfo, message: &str, group: Option<&String>| { - let alias = insert_alias(you, info.clone(), localisation); - let name = if show_char_name { - info.character.map(|c| c.name) + let name_format = |uid: &common::uid::Uid| match info.player_alias.get(uid).cloned() { + Some(pi) => insert_alias(info.you == *uid, pi, localisation), + None => info + .entity_name + .get(uid) + .cloned() + .expect("client didn't proved enough info"), + }; + + let message_format = |from: &common::uid::Uid, message: &str, group: Option<&String>| { + let alias = name_format(from); + let name = if let Some(pi) = info.player_alias.get(from).cloned() && show_char_name { + pi.character.map(|c| c.name) } else { None }; match (group, name) { - (Some(group), None) => format!("({}) [{}]: {}", group, alias, message), - (None, None) => format!("[{}]: {}", alias, message), - (Some(group), Some(name)) => { - format!("({}) [{}] {}: {}", group, alias, name, message) - }, - (None, Some(name)) => format!("[{}] {}: {}", alias, name, message), + (Some(group), None) => format!("({group}) [{alias}]: {message}"), + (None, None) => format!("[{alias}]: {message}"), + (Some(group), Some(name)) => format!("({group}) [{alias}] {name}: {message}"), + (None, Some(name)) => format!("[{alias}] {name}: {message}"), } }; - if let Some(ChatTypeContext::PlayerAlias { you, info }) = info.get("from").cloned() { - msg.message = match &msg.chat_type { - ChatType::Say(_) => message_format(you, info, &msg.message, None), - ChatType::Group(_, s) => message_format(you, info, &msg.message, Some(s)), - ChatType::Faction(_, s) => message_format(you, info, &msg.message, Some(s)), - ChatType::Region(_) => message_format(you, info, &msg.message, None), - ChatType::World(_) => message_format(you, info, &msg.message, None), - ChatType::NpcSay(_, _r) => message_format(you, info, &msg.message, None), - _ => msg.message, - }; - } - for (name, datum) in info.into_iter() { - let replacement = match datum { - ChatTypeContext::PlayerAlias { you, info } => insert_alias(you, info, localisation), - ChatTypeContext::Raw(text) => text, - }; - msg.message = msg.message.replace(&format!("{{{}}}", name), &replacement); - } - msg -} -fn get_chat_template_key(chat_type: &ChatType) -> Option<&str> { - Some(match chat_type { - ChatType::Online(_) => "hud-chat-online_msg", - ChatType::Offline(_) => "hud-chat-offline_msg", - ChatType::Kill(kill_source, _) => match kill_source { - KillSource::Player(_, KillType::Buff(_)) => "hud-chat-died_of_pvp_buff_msg", - KillSource::Player(_, KillType::Melee) => "hud-chat-pvp_melee_kill_msg", - KillSource::Player(_, KillType::Projectile) => "hud-chat-pvp_ranged_kill_msg", - KillSource::Player(_, KillType::Explosion) => "hud-chat-pvp_explosion_kill_msg", - KillSource::Player(_, KillType::Energy) => "hud-chat-pvp_energy_kill_msg", - KillSource::Player(_, KillType::Other) => "hud-chat-pvp_other_kill_msg", - KillSource::NonExistent(KillType::Buff(_)) => "hud-chat-died_of_buff_nonexistent_msg", - KillSource::NonPlayer(_, KillType::Buff(_)) => "hud-chat-died_of_npc_buff_msg", - KillSource::NonPlayer(_, KillType::Melee) => "hud-chat-npc_melee_kill_msg", - KillSource::NonPlayer(_, KillType::Projectile) => "hud-chat-npc_ranged_kill_msg", - KillSource::NonPlayer(_, KillType::Explosion) => "hud-chat-npc_explosion_kill_msg", - KillSource::NonPlayer(_, KillType::Energy) => "hud-chat-npc_energy_kill_msg", - KillSource::NonPlayer(_, KillType::Other) => "hud-chat-npc_other_kill_msg", - KillSource::Environment(_) => "hud-chat-environmental_kill_msg", - KillSource::FallDamage => "hud-chat-fall_kill_msg", - KillSource::Suicide => "hud-chat-suicide_msg", - KillSource::NonExistent(_) | KillSource::Other => "hud-chat-default_death_msg", + let new_msg = match &msg.chat_type { + ChatType::Online(uid) => localisation + .get_msg_ctx("hud-chat-online_msg", &i18n::fluent_args! { + "name" => name_format(uid), + }) + .into_owned(), + ChatType::Offline(uid) => localisation + .get_msg_ctx("hud-chat-offline_msg", &i18n::fluent_args! { + "name" => name_format(uid + ), + }) + .into_owned(), + ChatType::CommandError => msg.message.to_string(), + ChatType::CommandInfo => msg.message.to_string(), + ChatType::FactionMeta(_) => msg.message.to_string(), + ChatType::GroupMeta(_) => msg.message.to_string(), + ChatType::Tell(from, to) => { + let from_alias = name_format(from); + let to_alias = name_format(to); + // TODO: internationalise + if *from == info.you { + format!("To [{to_alias}]: {}", msg.message) + } else { + format!("From [{from_alias}]: {}", msg.message) + } }, - _ => return None, - }) -} + ChatType::Say(uid) => message_format(uid, &msg.message, None), + ChatType::Group(uid, s) => message_format(uid, &msg.message, Some(s)), + ChatType::Faction(uid, s) => message_format(uid, &msg.message, Some(s)), + ChatType::Region(uid) => message_format(uid, &msg.message, None), + ChatType::World(uid) => message_format(uid, &msg.message, None), + // NPCs can't talk. Should be filtered by hud/mod.rs for voxygen and + // should be filtered by server (due to not having a Pos) for chat-cli + ChatType::Npc(uid, _r) => message_format(uid, &msg.message, None), + ChatType::NpcSay(uid, _r) => message_format(uid, &msg.message, None), + ChatType::NpcTell(from, to, _r) => { + let from_alias = name_format(from); + let to_alias = name_format(to); + // TODO: internationalise + if *from == info.you { + format!("To [{to_alias}]: {}", msg.message) + } else { + format!("From [{from_alias}]: {}", msg.message) + } + }, + ChatType::Meta => msg.message.to_string(), + ChatType::Kill(kill_source, victim) => { + let i18n_buff = |buff| match buff { + BuffKind::Burning => "hud-outcome-burning", + BuffKind::Bleeding => "hud-outcome-bleeding", + BuffKind::Cursed => "hud-outcome-curse", + BuffKind::Crippled => "hud-outcome-crippled", + BuffKind::Frozen => "hud-outcome-frozen", + BuffKind::Regeneration + | BuffKind::Saturation + | BuffKind::Potion + | BuffKind::CampfireHeal + | BuffKind::EnergyRegen + | BuffKind::IncreaseMaxEnergy + | BuffKind::IncreaseMaxHealth + | BuffKind::Invulnerability + | BuffKind::ProtectingWard + | BuffKind::Frenzied + | BuffKind::Hastened => { + tracing::error!("Player was killed by a positive buff!"); + "hud-outcome-mysterious" + }, + BuffKind::Wet | BuffKind::Ensnared | BuffKind::Poisoned => { + tracing::error!("Player was killed by a debuff that doesn't do damage!"); + "hud-outcome-mysterious" + }, + }; -fn insert_killing_buff(buff: BuffKind, localisation: &Localization, template: &str) -> String { - let buff_outcome = match buff { - BuffKind::Burning => "hud-outcome-burning", - BuffKind::Bleeding => "hud-outcome-bleeding", - BuffKind::Cursed => "hud-outcome-curse", - BuffKind::Crippled => "hud-outcome-crippled", - BuffKind::Frozen => "hud-outcome-frozen", - BuffKind::Regeneration - | BuffKind::Saturation - | BuffKind::Potion - | BuffKind::CampfireHeal - | BuffKind::EnergyRegen - | BuffKind::IncreaseMaxEnergy - | BuffKind::IncreaseMaxHealth - | BuffKind::Invulnerability - | BuffKind::ProtectingWard - | BuffKind::Frenzied - | BuffKind::Hastened => { - tracing::error!("Player was killed by a positive buff!"); - "hud-outcome-mysterious" - }, - BuffKind::Wet | BuffKind::Ensnared | BuffKind::Poisoned => { - tracing::error!("Player was killed by a debuff that doesn't do damage!"); - "hud-outcome-mysterious" + match kill_source { + // Buff deaths + KillSource::Player(attacker, KillType::Buff(buff_kind)) => { + let i18n_buff = i18n_buff(*buff_kind); + let buff = localisation.get_msg(i18n_buff); + + localisation.get_msg_ctx("hud-chat-died_of_pvp_buff_msg", &i18n::fluent_args! { + "victim" => name_format(victim), + "died_of_buff" => buff, + "attacker" => name_format(attacker), + }) + }, + KillSource::NonPlayer(attacker_name, KillType::Buff(buff_kind)) => { + let i18n_buff = i18n_buff(*buff_kind); + let buff = localisation.get_msg(i18n_buff); + + localisation.get_msg_ctx("hud-chat-died_of_npc_buff_msg", &i18n::fluent_args! { + "victim" => name_format(victim), + "died_of_buff" => buff, + "attacker" => attacker_name, + }) + }, + KillSource::NonExistent(KillType::Buff(buff_kind)) => { + let i18n_buff = i18n_buff(*buff_kind); + let buff = localisation.get_msg(i18n_buff); + + localisation.get_msg_ctx( + "hud-chat-died_of_buff_nonexistent_msg", + &i18n::fluent_args! { + "victim" => name_format(victim), + "died_of_buff" => buff, + }, + ) + }, + // PvP deaths + KillSource::Player(attacker, kill_type) => { + let key = match kill_type { + KillType::Melee => "hud-chat-pvp_melee_kill_msg", + KillType::Projectile => "hud-chat-pvp_ranged_kill_msg", + KillType::Explosion => "hud-chat-pvp_explosion_kill_msg", + KillType::Energy => "hud-chat-pvp_energy_kill_msg", + KillType::Other => "hud-chat-pvp_other_kill_msg", + &KillType::Buff(_) => unreachable!("handled above"), + }; + localisation.get_msg_ctx(key, &i18n::fluent_args! { + "victim" => name_format(victim), + "attacker" => name_format(attacker), + }) + }, + // PvE deaths + KillSource::NonPlayer(attacker_name, kill_type) => { + let key = match kill_type { + KillType::Melee => "hud-chat-npc_melee_kill_msg", + KillType::Projectile => "hud-chat-npc_ranged_kill_msg", + KillType::Explosion => "hud-chat-npc_explosion_kill_msg", + KillType::Energy => "hud-chat-npc_energy_kill_msg", + KillType::Other => "hud-chat-npc_other_kill_msg", + &KillType::Buff(_) => unreachable!("handled above"), + }; + localisation.get_msg_ctx(key, &i18n::fluent_args! { + "victim" => name_format(victim), + "attacker" => attacker_name, + }) + }, + // Other deaths + KillSource::Environment(environment) => { + localisation.get_msg_ctx("hud-chat-environment_kill_msg", &i18n::fluent_args! { + "name" => name_format(victim), + "environment" => environment, + }) + }, + KillSource::FallDamage => { + localisation.get_msg_ctx("hud-chat-fall_kill_msg", &i18n::fluent_args! { + "name" => name_format(victim), + }) + }, + KillSource::Suicide => { + localisation.get_msg_ctx("hud-chat-suicide_msg", &i18n::fluent_args! { + "name" => name_format(victim), + }) + }, + KillSource::NonExistent(_) | KillSource::Other => { + localisation.get_msg_ctx("hud-chat-default_death_msg", &i18n::fluent_args! { + "name" => name_format(victim), + }) + }, + } + .to_string() }, }; - template.replace("{died_of_buff}", &localisation.get_msg(buff_outcome)) + msg.message = new_msg; + msg } fn insert_alias(you: bool, info: PlayerInfo, localisation: &Localization) -> String { diff --git a/voxygen/src/hud/chat.rs b/voxygen/src/hud/chat.rs index a2782787e7..544563fdf5 100644 --- a/voxygen/src/hud/chat.rs +++ b/voxygen/src/hud/chat.rs @@ -18,7 +18,7 @@ use conrod_core::{ WidgetCommon, }; use i18n::Localization; -use i18n_helpers::internationalisate_chat_message; +use i18n_helpers::localize_chat_message; use std::collections::{HashSet, VecDeque}; widget_ids! { @@ -419,7 +419,7 @@ impl<'a> Widget for Chat<'a> { .messages .iter() .map(|m| { - internationalisate_chat_message( + localize_chat_message( m.clone(), |msg| self.client.lockup_msg_context(msg), self.localized_strings,