initial approach of utelising chat-i18n crate by chat-cli bot

This commit is contained in:
Marcel Märtens 2022-09-09 17:27:21 +02:00
parent a0ef3be9ac
commit cc3fcfce8c
9 changed files with 200 additions and 145 deletions

13
Cargo.lock generated
View File

@ -6683,6 +6683,8 @@ dependencies = [
"veloren-common-state", "veloren-common-state",
"veloren-common-systems", "veloren-common-systems",
"veloren-network", "veloren-network",
"veloren-voxygen-chat-i18n",
"veloren-voxygen-i18n",
] ]
[[package]] [[package]]
@ -7089,6 +7091,7 @@ dependencies = [
"veloren-common-systems", "veloren-common-systems",
"veloren-server", "veloren-server",
"veloren-voxygen-anim", "veloren-voxygen-anim",
"veloren-voxygen-chat-i18n",
"veloren-voxygen-egui", "veloren-voxygen-egui",
"veloren-voxygen-i18n", "veloren-voxygen-i18n",
"veloren-world", "veloren-world",
@ -7111,6 +7114,16 @@ dependencies = [
"veloren-common-dynlib", "veloren-common-dynlib",
] ]
[[package]]
name = "veloren-voxygen-chat-i18n"
version = "0.10.0"
dependencies = [
"tracing",
"veloren-common",
"veloren-common-net",
"veloren-voxygen-i18n",
]
[[package]] [[package]]
name = "veloren-voxygen-egui" name = "veloren-voxygen-egui"
version = "0.9.0" version = "0.9.0"

View File

@ -20,6 +20,7 @@ members = [
"server-cli", "server-cli",
"voxygen", "voxygen",
"voxygen/anim", "voxygen/anim",
"voxygen/chat-i18n",
"voxygen/i18n", "voxygen/i18n",
"voxygen/egui", "voxygen/egui",
"world", "world",

View File

@ -7,7 +7,7 @@ edition = "2021"
[features] [features]
simd = ["vek/platform_intrinsics"] simd = ["vek/platform_intrinsics"]
plugins = ["common-state/plugins"] plugins = ["common-state/plugins"]
bin_bot = ["common-ecs", "serde", "ron", "clap", "structopt", "rustyline", "common-frontend", "async-channel"] bin_bot = ["common-ecs", "serde", "ron", "clap", "structopt", "rustyline", "common-frontend", "async-channel", "voxygen-chat-i18n", "voxygen-i18n"]
tracy = ["common-base/tracy"] tracy = ["common-base/tracy"]
tick_network = [] tick_network = []
@ -37,6 +37,8 @@ authc = { git = "https://gitlab.com/veloren/auth.git", rev = "fb3dcbc4962b367253
#bot only #bot only
async-channel = { version = "1.6", optional = true } async-channel = { version = "1.6", optional = true }
common-ecs = { package = "veloren-common-ecs", path = "../common/ecs", optional = true } common-ecs = { package = "veloren-common-ecs", path = "../common/ecs", optional = true }
voxygen-chat-i18n = { package = "veloren-voxygen-chat-i18n", path = "../voxygen/chat-i18n", optional = true }
voxygen-i18n = { package = "veloren-voxygen-i18n", path = "../voxygen/i18n", optional = true }
serde = { version = "1.0", features = [ "rc", "derive" ], optional = true } serde = { version = "1.0", features = [ "rc", "derive" ], optional = true }
ron = { version = "0.8", default-features = false, optional = true } ron = { version = "0.8", default-features = false, optional = true }
clap = { version = "3.1.8", optional = true, features = ["color", "std"] } clap = { version = "3.1.8", optional = true, features = ["color", "std"] }

View File

@ -11,6 +11,7 @@ use std::{
use tokio::runtime::Runtime; use tokio::runtime::Runtime;
use tracing::{error, info}; use tracing::{error, info};
use veloren_client::{addr::ConnectionArgs, Client, Event}; use veloren_client::{addr::ConnectionArgs, Client, Event};
use voxygen_chat_i18n::internationalisate_chat_message;
const TPS: u64 = 10; // Low value is okay, just reading messages. const TPS: u64 = 10; // Low value is okay, just reading messages.
@ -28,6 +29,10 @@ fn main() {
// Initialize logging. // Initialize logging.
common_frontend::init_stdout(None); common_frontend::init_stdout(None);
info!("locading localisation");
let localisation = voxygen_i18n::LocalizationHandle::load_expect("en");
info!("Starting chat-cli..."); info!("Starting chat-cli...");
// Set up an fps clock. // Set up an fps clock.
@ -63,7 +68,7 @@ fn main() {
println!("Server info: {:?}", client.server_info()); println!("Server info: {:?}", client.server_info());
println!("Players online: {:?}", client.players().collect::<Vec<_>>()); let mut player_printed = false;
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
thread::spawn(move || { thread::spawn(move || {
@ -89,7 +94,16 @@ fn main() {
const SHOW_NAME: bool = false; const SHOW_NAME: bool = false;
for event in events { for event in events {
match event { match event {
Event::Chat(m) => println!("{}", client.format_message(&m, SHOW_NAME)), Event::Chat(m) => println!(
"{}",
internationalisate_chat_message(
m,
|msg| client.lockup_msg_context(msg),
&localisation.read(),
SHOW_NAME,
)
.message
),
Event::Disconnect => {}, // TODO Event::Disconnect => {}, // TODO
Event::DisconnectionNotification(time) => { Event::DisconnectionNotification(time) => {
let message = match time { let message = match time {
@ -108,5 +122,10 @@ fn main() {
// Wait for the next tick. // Wait for the next tick.
clock.tick(); clock.tick();
if !player_printed {
println!("Players online: {:?}", client.players().collect::<Vec<_>>());
player_printed = true;
}
} }
} }

View File

@ -2643,8 +2643,13 @@ impl Client {
/// Get important information from client that is necessary for message /// Get important information from client that is necessary for message
/// localisation /// localisation
pub fn lockup_msg_context(&self, msg: &comp::ChatMsg) -> HashMap<&str, ChatTypeContext> { ///
let mut result = HashMap::new(); /// Note: it uses the suffix `name` e.g. in `attacker_name` if Context is Raw, otherwise it returns just `attacker`
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; let comp::ChatMsg { chat_type, .. } = &msg;
let name_of_uid = |uid| { let name_of_uid = |uid| {
let ecs = self.state.ecs(); let ecs = self.state.ecs();

View File

@ -50,6 +50,7 @@ common-state = {package = "veloren-common-state", path = "../common/state"}
anim = {package = "veloren-voxygen-anim", path = "anim"} anim = {package = "veloren-voxygen-anim", path = "anim"}
i18n = {package = "veloren-voxygen-i18n", path = "i18n"} i18n = {package = "veloren-voxygen-i18n", path = "i18n"}
chat-i18n = {package = "veloren-voxygen-chat-i18n", path = "chat-i18n"}
voxygen-egui = {package = "veloren-voxygen-egui", path = "egui", optional = true } voxygen-egui = {package = "veloren-voxygen-egui", path = "egui", optional = true }
# Graphics # Graphics

View File

@ -0,0 +1,13 @@
[package]
authors = ["juliancoffee <lightdarkdaughter@gmail.com>"]
edition = "2021"
name = "veloren-voxygen-chat-i18n"
description = "Crate for internalization and diagnostic of existing localizations."
version = "0.10.0"
[dependencies]
common-net = {package = "veloren-common-net", path = "../../common/net"}
common = {package = "veloren-common", path = "../../common"}
i18n = {package = "veloren-voxygen-i18n", path = "../i18n"}
# Utility
tracing = "0.1"

View File

@ -0,0 +1,136 @@
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(
mut msg: ChatMsg,
lookup_fn: impl Fn(&ChatMsg) -> HashMap<&'static str, ChatTypeContext>,
localized_strings: &Localization,
show_char_name: bool,
) -> ChatMsg {
if let Some(template_key) = get_chat_template_key(&msg.chat_type) {
msg.message = localized_strings
.get_msg_ctx(template_key, &i18n::fluent_args! {
"attacker" => "{attacker}",
"attacker" => "{attacker_name}",
"name" => "{player}",
"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, localized_strings, &msg.message);
},
_ => {},
}
}
}
let info = lookup_fn(&msg);
let gen_alias = |you, info: PlayerInfo| {
let mod_str = if info.is_moderator { "MOD - " } else { "" };
let you_str = if you { "You" } else { &info.player_alias };
format!("{}{}", mod_str, you_str)
};
let message_format = |you, info: PlayerInfo, message: &str, group: Option<&String>| {
let alias = gen_alias(you, info.clone());
let name = if show_char_name {
info.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),
}
};
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 } => gen_alias(you, info),
ChatTypeContext::Raw(text) => text,
};
msg.message = msg.message.replace(&format!("{{{}}}", name), &replacement);
}
msg
}
fn get_chat_template_key(chat_type: &ChatType<String>) -> 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",
},
_ => return None,
})
}
fn insert_killing_buff(buff: BuffKind, localized_strings: &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"
},
};
template.replace("{died_of_buff}", &localized_strings.get_msg(buff_outcome))
}

View File

@ -3,13 +3,9 @@ use super::{
OFFLINE_COLOR, ONLINE_COLOR, REGION_COLOR, SAY_COLOR, TELL_COLOR, TEXT_COLOR, WORLD_COLOR, OFFLINE_COLOR, ONLINE_COLOR, REGION_COLOR, SAY_COLOR, TELL_COLOR, TEXT_COLOR, WORLD_COLOR,
}; };
use crate::{cmd::complete, settings::chat::MAX_CHAT_TABS, ui::fonts::Fonts, GlobalState}; use crate::{cmd::complete, settings::chat::MAX_CHAT_TABS, ui::fonts::Fonts, GlobalState};
use chat_i18n::internationalisate_chat_message;
use client::Client; use client::Client;
use common::comp::{ use common::comp::{group::Role, ChatMode, ChatMsg, ChatType};
chat::{KillSource, KillType},
group::Role,
BuffKind, ChatMode, ChatMsg, ChatType,
};
use common_net::msg::{ChatTypeContext, PlayerInfo};
use conrod_core::{ use conrod_core::{
color, color,
input::Key, input::Key,
@ -425,8 +421,8 @@ impl<'a> Widget for Chat<'a> {
.map(|m| { .map(|m| {
internationalisate_chat_message( internationalisate_chat_message(
m.clone(), m.clone(),
&self.client, |msg| self.client.lockup_msg_context(msg),
&self.localized_strings, self.localized_strings,
show_char_name, show_char_name,
) )
}) })
@ -662,80 +658,6 @@ impl<'a> Widget for Chat<'a> {
} }
} }
fn internationalisate_chat_message(
mut msg: ChatMsg,
client: &Client,
localized_strings: &Localization,
show_char_name: bool,
) -> ChatMsg {
if let Some(template_key) = get_chat_template_key(&msg.chat_type) {
// FIXME (i18n death messages):
// Death message is half localized in voxygen, half in client.
// Make this not.
msg.message = localized_strings
.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, localized_strings, &msg.message);
},
_ => {},
}
}
}
let info = client.lockup_msg_context(&msg);
let gen_alias = |you, info: PlayerInfo| {
let mod_str = if info.is_moderator { "MOD - " } else { "" };
let you_str = if you { "You" } else { &info.player_alias };
format!("{}{}", mod_str, you_str)
};
let message_format = |you, info: PlayerInfo, message: &str, group: Option<&String>| {
let alias = gen_alias(you, info.clone());
let name = if show_char_name {
info.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),
}
};
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 } => gen_alias(you, info),
ChatTypeContext::Raw(text) => text,
};
msg.message = msg.message.replace(&format!("{{{}}}", name), &replacement);
}
msg
}
fn do_tab_completion(cursor: usize, input: &str, word: &str) -> (String, usize) { fn do_tab_completion(cursor: usize, input: &str, word: &str) -> (String, usize) {
let mut pre_ws = None; let mut pre_ws = None;
let mut post_ws = None; let mut post_ws = None;
@ -825,63 +747,6 @@ fn render_chat_line(chat_type: &ChatType<String>, imgs: &Imgs) -> (Color, conrod
} }
} }
fn insert_killing_buff(buff: BuffKind, localized_strings: &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"
},
};
template.replace("{died_of_buff}", &localized_strings.get_msg(buff_outcome))
}
fn get_chat_template_key(chat_type: &ChatType<String>) -> 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",
},
_ => return None,
})
}
fn parse_cmd(msg: &str) -> Result<(String, Vec<String>), String> { fn parse_cmd(msg: &str) -> Result<(String, Vec<String>), String> {
use chumsky::prelude::*; use chumsky::prelude::*;