new language manager that loads strings from compiled json file

This commit is contained in:
jellywx 2020-11-21 16:48:20 +00:00
parent 1927d381ab
commit 6a7491d094
7 changed files with 101 additions and 41 deletions

1
.gitignore vendored
View File

@ -3,3 +3,4 @@
/venv /venv
.cargo .cargo
assets assets
out.json

2
Cargo.lock generated
View File

@ -1336,7 +1336,7 @@ dependencies = [
[[package]] [[package]]
name = "reminder_rs" name = "reminder_rs"
version = "1.2.3" version = "1.3.0-dev"
dependencies = [ dependencies = [
"Inflector", "Inflector",
"async-trait", "async-trait",

View File

@ -1,6 +1,6 @@
[package] [package]
name = "reminder_rs" name = "reminder_rs"
version = "1.2.3" version = "1.3.0-dev"
authors = ["jellywx <judesouthworth@pm.me>"] authors = ["jellywx <judesouthworth@pm.me>"]
edition = "2018" edition = "2018"

View File

@ -10,6 +10,7 @@ use crate::{
SQLPool, THEME_COLOR, SQLPool, THEME_COLOR,
}; };
use crate::language_manager::LanguageManager;
use std::time::{SystemTime, UNIX_EPOCH}; use std::time::{SystemTime, UNIX_EPOCH};
#[command] #[command]
@ -31,16 +32,18 @@ async fn ping(ctx: &Context, msg: &Message, _args: String) {
#[command] #[command]
#[can_blacklist(false)] #[can_blacklist(false)]
async fn help(ctx: &Context, msg: &Message, _args: String) { async fn help(ctx: &Context, msg: &Message, _args: String) {
let pool = ctx let data = ctx.data.read().await;
.data
.read() let pool = data
.await
.get::<SQLPool>() .get::<SQLPool>()
.cloned() .cloned()
.expect("Could not get SQLPool from data"); .expect("Could not get SQLPool from data");
let user_data = UserData::from_user(&msg.author, &ctx, &pool).await.unwrap(); let lm = data.get::<LanguageManager>().unwrap();
let desc = user_data.response(&pool, "help").await;
let language = UserData::language_of(&msg.author, &ctx, &pool).await;
let desc = lm.get(&language, "help");
let _ = msg let _ = msg
.channel_id .channel_id
@ -63,22 +66,22 @@ async fn help(ctx: &Context, msg: &Message, _args: String) {
#[command] #[command]
async fn info(ctx: &Context, msg: &Message, _args: String) { async fn info(ctx: &Context, msg: &Message, _args: String) {
let pool = ctx let data = ctx.data.read().await;
.data
.read() let pool = data
.await
.get::<SQLPool>() .get::<SQLPool>()
.cloned() .cloned()
.expect("Could not get SQLPool from data"); .expect("Could not get SQLPool from data");
let user_data = UserData::from_user(&msg.author, &ctx, &pool).await.unwrap(); let lm = data.get::<LanguageManager>().unwrap();
let language = UserData::language_of(&msg.author, &ctx, &pool).await;
let guild_data = GuildData::from_guild(msg.guild(&ctx).await.unwrap(), &pool) let guild_data = GuildData::from_guild(msg.guild(&ctx).await.unwrap(), &pool)
.await .await
.unwrap(); .unwrap();
let desc = user_data let desc = lm
.response(&pool, "info") .get(&language, "info")
.await
.replacen("{user}", &ctx.cache.current_user().await.name, 1) .replacen("{user}", &ctx.cache.current_user().await.name, 1)
.replace("{default_prefix}", &*DEFAULT_PREFIX) .replace("{default_prefix}", &*DEFAULT_PREFIX)
.replace("{prefix}", &guild_data.prefix); .replace("{prefix}", &guild_data.prefix);
@ -104,16 +107,17 @@ async fn info(ctx: &Context, msg: &Message, _args: String) {
#[command] #[command]
async fn donate(ctx: &Context, msg: &Message, _args: String) { async fn donate(ctx: &Context, msg: &Message, _args: String) {
let pool = ctx let data = ctx.data.read().await;
.data
.read() let pool = data
.await
.get::<SQLPool>() .get::<SQLPool>()
.cloned() .cloned()
.expect("Could not get SQLPool from data"); .expect("Could not get SQLPool from data");
let user_data = UserData::from_user(&msg.author, &ctx, &pool).await.unwrap(); let lm = data.get::<LanguageManager>().unwrap();
let desc = user_data.response(&pool, "donate").await;
let language = UserData::language_of(&msg.author, &ctx, &pool).await;
let desc = lm.get(&language, "donate");
let _ = msg let _ = msg
.channel_id .channel_id

48
src/language_manager.rs Normal file
View File

@ -0,0 +1,48 @@
use std::collections::HashMap;
use serde::Deserialize;
use serde_json::from_reader;
use serenity::prelude::TypeMapKey;
use std::error::Error;
use std::fs::File;
use std::io::BufReader;
use std::path::Path;
#[derive(Deserialize)]
pub struct LanguageManager {
languages: HashMap<String, String>,
strings: HashMap<String, HashMap<String, String>>,
}
impl LanguageManager {
pub(crate) fn from_compiled<P>(path: P) -> Result<Self, Box<dyn Error + Send + Sync>>
where
P: AsRef<Path>,
{
let file = File::open(path)?;
let reader = BufReader::new(file);
let new: Self = from_reader(reader)?;
Ok(new)
}
pub(crate) fn get(&self, language: &str, name: &'static str) -> &str {
self.strings
.get(language)
.map(|sm| sm.get(name))
.expect(&format!(r#"Language does not exist: "{}""#, language))
.expect(&format!(r#"String does not exist: "{}""#, name))
}
fn all_languages(&self) -> Vec<(&str, &str)> {
self.languages
.iter()
.map(|(k, v)| (k.as_str(), v.as_str()))
.collect()
}
}
impl TypeMapKey for LanguageManager {
type Value = Self;
}

View File

@ -4,6 +4,7 @@ extern crate lazy_static;
mod commands; mod commands;
mod consts; mod consts;
mod framework; mod framework;
mod language_manager;
mod models; mod models;
mod time_parser; mod time_parser;
@ -33,6 +34,7 @@ use crate::{
commands::{info_cmds, moderation_cmds, reminder_cmds, todo_cmds}, commands::{info_cmds, moderation_cmds, reminder_cmds, todo_cmds},
consts::{CNC_GUILD, DEFAULT_PREFIX, SUBSCRIPTION_ROLES, THEME_COLOR}, consts::{CNC_GUILD, DEFAULT_PREFIX, SUBSCRIPTION_ROLES, THEME_COLOR},
framework::RegexFramework, framework::RegexFramework,
language_manager::LanguageManager,
}; };
use serenity::futures::TryFutureExt; use serenity::futures::TryFutureExt;
@ -229,11 +231,14 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
.await .await
.unwrap(); .unwrap();
let language_manager = LanguageManager::from_compiled("out.json")?;
let mut data = client.data.write().await; let mut data = client.data.write().await;
data.insert::<SQLPool>(pool); data.insert::<SQLPool>(pool);
data.insert::<ReqwestClient>(Arc::new(reqwest::Client::new())); data.insert::<ReqwestClient>(Arc::new(reqwest::Client::new()));
data.insert::<FrameworkCtx>(framework_arc); data.insert::<FrameworkCtx>(framework_arc);
data.insert::<LanguageManager>(language_manager)
} }
if let Ok((Some(lower), Some(upper))) = env::var("SHARD_RANGE").map(|sr| { if let Ok((Some(lower), Some(upper))) = env::var("SHARD_RANGE").map(|sr| {

View File

@ -191,6 +191,25 @@ pub struct UserData {
} }
impl UserData { impl UserData {
pub async fn language_of(user: &User, ctx: impl CacheHttp, pool: &MySqlPool) -> String {
let user_id = user.id.as_u64().to_owned();
match sqlx::query!(
"
SELECT IF(language IS NULL, ?, language) AS language FROM users WHERE user = ?
",
*LOCAL_LANGUAGE,
user_id
)
.fetch_one(pool)
.await
{
Ok(r) => r.language.unwrap(),
Err(_) => LOCAL_LANGUAGE.clone(),
}
}
pub async fn from_user( pub async fn from_user(
user: &User, user: &User,
ctx: impl CacheHttp, ctx: impl CacheHttp,
@ -266,25 +285,8 @@ UPDATE users SET name = ?, language = ?, timezone = ? WHERE id = ?
.unwrap(); .unwrap();
} }
pub async fn response(&self, pool: &MySqlPool, name: &str) -> String { pub async fn response(&self, _pool: &MySqlPool, _name: &str) -> String {
struct StringRow { unimplemented!()
value: String,
}
sqlx::query_as!(
StringRow,
"
SELECT value FROM strings WHERE (language = ? OR language = ?) AND name = ? ORDER BY language = ?
",
self.language,
&*LOCAL_LANGUAGE,
name,
&*LOCAL_LANGUAGE
)
.fetch_one(pool)
.await
.unwrap()
.value
} }
pub fn timezone(&self) -> Tz { pub fn timezone(&self) -> Tz {