General improvements to the translate plugin, added option to specify source language
This commit is contained in:
@@ -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
|
||||||
|
|
||||||
|
@@ -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 {
|
||||||
|
Reference in New Issue
Block a user