General improvements to the translate plugin, added option to specify source language

This commit is contained in:
Kirottu
2023-05-15 13:46:39 +03:00
parent 9d151d9725
commit 5c0765fdba
2 changed files with 66 additions and 17 deletions

View File

@@ -4,7 +4,7 @@ Quickly translate text using the Google Translate API.
## Usage ## Usage
Type in `<prefix><target lang> <text to translate>`, where prefix is the configured prefix (default is in [Configuration](#Configuration)) and the rest are pretty obvious. Type in `[prefix][target lang] [text to translate]` or `[prefix][src lang]>[target lang] [text to translate]`, where prefix is the configured prefix (default is in [Configuration](#Configuration)) and the rest are pretty obvious.
## Configuration ## Configuration

View File

@@ -3,7 +3,9 @@ use std::fs;
use abi_stable::std_types::{ROption, RString, RVec}; use abi_stable::std_types::{ROption, RString, RVec};
use anyrun_plugin::*; use anyrun_plugin::*;
use fuzzy_matcher::FuzzyMatcher; use fuzzy_matcher::FuzzyMatcher;
use reqwest::Client;
use serde::Deserialize; use serde::Deserialize;
use tokio::runtime::Runtime;
#[derive(Deserialize)] #[derive(Deserialize)]
struct Config { struct Config {
@@ -22,6 +24,8 @@ impl Default for Config {
struct State { struct State {
config: Config, config: Config,
client: Client,
runtime: Runtime,
langs: Vec<(&'static str, &'static str)>, langs: Vec<(&'static str, &'static str)>,
} }
@@ -32,6 +36,8 @@ fn init(config_dir: RString) -> State {
Ok(content) => ron::from_str(&content).unwrap_or_default(), Ok(content) => ron::from_str(&content).unwrap_or_default(),
Err(_) => Config::default(), Err(_) => Config::default(),
}, },
client: Client::new(),
runtime: Runtime::new().expect("Failed to create tokio runtime"),
langs: vec![ langs: vec![
("af", "Afrikaans"), ("af", "Afrikaans"),
("sq", "Albanian"), ("sq", "Albanian"),
@@ -151,51 +157,94 @@ fn info() -> PluginInfo {
} }
#[get_matches] #[get_matches]
fn get_matches(input: RString, data: &State) -> RVec<Match> { fn get_matches(input: RString, state: &State) -> RVec<Match> {
if !input.starts_with(&data.config.prefix) { if !input.starts_with(&state.config.prefix) {
return RVec::new(); return RVec::new();
} }
// Ignore the prefix // Ignore the prefix
let input = &input[data.config.prefix.len()..]; let input = &input[state.config.prefix.len()..];
let (lang, text) = match input.split_once(' ') { let (lang_split, text) = match input.split_once(' ') {
Some(split) => split, Some(split) => split,
None => return RVec::new(), None => return RVec::new(),
}; };
let (src, dest) = match lang_split.split_once('>') {
Some(split) => (Some(split.0), split.1),
None => (None, lang_split),
};
if text.is_empty() { if text.is_empty() {
return RVec::new(); return RVec::new();
} }
let matcher = fuzzy_matcher::skim::SkimMatcherV2::default().ignore_case(); let matcher = fuzzy_matcher::skim::SkimMatcherV2::default().ignore_case();
// Fuzzy match the input language with the languages in the Vec let dest_matches = state
let mut matches = data
.langs .langs
.clone() .clone()
.into_iter() .into_iter()
.filter_map(|(code, name)| { .filter_map(|(code, name)| {
matcher matcher
.fuzzy_match(code, lang) .fuzzy_match(code, dest)
.max(matcher.fuzzy_match(name, lang)) .max(matcher.fuzzy_match(name, dest))
.map(|score| (code, name, score)) .map(|score| (code, name, score))
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
matches.sort_by(|a, b| b.2.cmp(&a.2)); // Fuzzy match the input language with the languages in the Vec
let mut matches = match src {
Some(src) => {
let src_matches = state
.langs
.clone()
.into_iter()
.filter_map(|(code, name)| {
matcher
.fuzzy_match(code, src)
.max(matcher.fuzzy_match(name, src))
.map(|score| (code, name, score))
})
.collect::<Vec<_>>();
let mut matches = src_matches
.into_iter()
.flat_map(|src| dest_matches.clone().into_iter().map(move |dest| (Some(src), dest)))
.collect::<Vec<_>>();
matches.sort_by(|a, b| (b.1 .2 + b.0.unwrap().2).cmp(&(a.1 .2 + a.0.unwrap().2)));
matches
}
None => {
let mut matches = dest_matches
.into_iter()
.map(|dest| (None, dest))
.collect::<Vec<_>>();
matches.sort_by(|a, b| b.1 .2.cmp(&a.1 .2));
matches
}
};
// We only want 3 matches // We only want 3 matches
matches.truncate(data.config.max_entries); matches.truncate(state.config.max_entries);
tokio::runtime::Runtime::new().expect("Failed to spawn tokio runtime!").block_on(async move { state.runtime.block_on(async move {
// Create the futures for fetching the translation results // Create the futures for fetching the translation results
let futures = matches let futures = matches
.into_iter() .into_iter()
.map(|(code, name, _)| async move { .map(|(src, dest)| async move {
(name, reqwest::get(format!("https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl={}&dt=t&q={}", code, text)).await) match src {
Some(src) =>
(dest.1, state.client.get(format!("https://translate.googleapis.com/translate_a/single?client=gtx&sl={}&tl={}&dt=t&q={}", src.0, dest.0, text)).send().await),
None => (dest.1, state.client.get(format!("https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl={}&dt=t&q={}", dest.0, text)).send().await)
}
}); });
futures::future::join_all(futures) // Wait for all futures to complete
.await let res = futures::future::join_all(futures) // Wait for all futures to complete
.await;
res
.into_iter() .into_iter()
.filter_map(|(name, res)| res .filter_map(|(name, res)| res
.ok() .ok()
@@ -215,7 +264,7 @@ fn get_matches(input: RString, data: &State) -> RVec<Match> {
description: ROption::RSome( description: ROption::RSome(
format!( format!(
"{} -> {}", "{} -> {}",
data.langs.iter() state.langs.iter()
.find_map(|(code, name)| if *code == json[2].as_str().expect("Malformed JSON!") { .find_map(|(code, name)| if *code == json[2].as_str().expect("Malformed JSON!") {
Some(*name) Some(*name)
} else { } else {