├── .gitignore ├── Cargo.toml ├── README.md ├── screenshot.png └── src ├── main.rs └── translate.glade /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-google-translate" 3 | version = "0.1.0" 4 | authors = ["Michael Aaron Murphy "] 5 | 6 | [dependencies] 7 | hyper = "*" 8 | gtk = { version = "0.0.7", features = ["v3_10"] } 9 | gdk = "0.3.0" 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a simple application written in Rust using the GTK Rust wrapper, Hyper and Google Translate. 2 | 3 | ![screenshot](screenshot.png) 4 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmstick/rust-google-translate/da43369555b4261e527bb7ddc6ba65be05960821/screenshot.png -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate hyper; 2 | extern crate gtk; 3 | extern crate gdk; 4 | 5 | use std::io::Read; 6 | use std::rc::Rc; 7 | use std::cell::RefCell; 8 | 9 | use hyper::Client; 10 | use hyper::header::Connection; 11 | 12 | use gdk::enums::key; 13 | use gtk::traits::*; 14 | use gtk::{ 15 | Builder, 16 | Button, 17 | ButtonSignals, 18 | ComboBoxText, 19 | Inhibit, 20 | TextView, 21 | TextBuffer, 22 | TextTagTable, 23 | WidgetSignals, 24 | Window 25 | }; 26 | 27 | const TRANSLATE: &'static str = "http://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl="; 28 | const TRY: &'static str = "Try 'rust-google-translate --help' for more information"; 29 | const HELP: &'static str = r#"NAME 30 | rust-google-translate - translate a phrase into another language with Google Translate 31 | 32 | SYNOPSIS 33 | rust-google-translate [-c LANG PHRASE] [-h | --help] 34 | 35 | DESCRIPTION 36 | Translates text from one language to another. If no arguments are given, a GTK GUI is launched. 37 | 38 | OPTIONS 39 | -c LANG PHRASE 40 | translates PHRASE into LANG 41 | 42 | -h, --help 43 | displays this information 44 | 45 | EXAMPLE 46 | rust-google-translate -c EN Mi estas ne vin. Vi estas ne min. 47 | > I am not you. You are not me. 48 | "#; 49 | 50 | fn main() { 51 | let mut arguments = std::env::args().skip(1); 52 | if let Some(flag) = arguments.next() { 53 | match flag.as_str() { 54 | "-c" => { 55 | if let Some(lang) = arguments.next() { 56 | let input = arguments.fold(String::with_capacity(lang.len()), |acc, x| acc + x.as_str() + " "); 57 | let mut translation = String::new(); 58 | translate(input.as_str(), lang.as_str(), &mut translation); 59 | println!("{}", translation); 60 | } 61 | }, 62 | "-h" | "--help" => println!("{}", HELP), 63 | _ => println!("rust-google-translate: invalid option -- '{}'\n{}", flag, TRY) 64 | } 65 | } else { 66 | launch_gui(); 67 | } 68 | } 69 | 70 | fn match_language(input: &str) -> String { 71 | match input { 72 | "Chinese" => "ZH-CN".to_string(), 73 | "English" => "EN".to_string(), 74 | "Esperanto" => "EO".to_string(), 75 | "French" => "FR".to_string(), 76 | "German" => "DE".to_string(), 77 | "Italian" => "IT".to_string(), 78 | "Japanese" => "JA".to_string(), 79 | "Korean" => "KO".to_string(), 80 | "Russian" => "RU".to_string(), 81 | "Spanish" => "ES".to_string(), 82 | _ => { 83 | println!("Language Not Supported"); 84 | std::process::exit(1); 85 | } 86 | } 87 | } 88 | 89 | /// Launch the GTK GUI 90 | fn launch_gui() { 91 | // Initialize GTK 92 | if let Err(message) = gtk::init() { 93 | panic!("{:?}", message); 94 | } 95 | 96 | // Open the UI that we created in Glade 97 | let glade_src = include_str!("translate.glade"); 98 | let builder = Builder::new_from_string(glade_src); 99 | 100 | // Grab the elements from the UI 101 | let window: Window = builder.get_object("main_window").unwrap(); 102 | let translate_button: Button = builder.get_object("translate_button").unwrap(); 103 | let translation_input: TextView = builder.get_object("translation_input").unwrap(); 104 | let language_box: ComboBoxText = builder.get_object("language").unwrap(); 105 | 106 | // Add a TextBuffer to every TextView 107 | let input_buffer = TextBuffer::new(Some(&TextTagTable::new())); 108 | translation_input.set_buffer(Some(&input_buffer)); 109 | 110 | // Wrap translation_button so that it may be borrowed multiple times 111 | let wrapped_translation_button = Rc::new(RefCell::new(translate_button)); 112 | 113 | { // Take the input buffer, translate it, and output it to the outbut buffer. 114 | let translate_button = wrapped_translation_button.clone(); 115 | translate_button.borrow().connect_clicked(move |_| { 116 | // Get the input buffer's text 117 | let buffer = translation_input.get_buffer().unwrap(); 118 | let string = buffer.get_text(&buffer.get_start_iter(), &buffer.get_end_iter(), false).unwrap(); 119 | 120 | // Get the langauge combo box's text. 121 | let language = match_language(language_box.get_active_text().unwrap().as_str()); 122 | 123 | // Translate the text. 124 | let mut translation = String::new(); 125 | translate(&string, language.as_str(), &mut translation); 126 | 127 | // Immediately translate the text 128 | translation_input.get_buffer().unwrap().set_text(translation.as_str()); 129 | }); 130 | } 131 | 132 | // Exit the program if it receives the delete event. 133 | window.connect_delete_event(|_,_| { 134 | gtk::main_quit(); 135 | Inhibit(false) 136 | }); 137 | 138 | { // Program what the program should do when certain keys are pressed 139 | let translate_button = wrapped_translation_button.clone(); 140 | window.connect_key_press_event(move |_,key| { 141 | match key.get_keyval() { 142 | key::Escape => gtk::main_quit(), 143 | key::Return => translate_button.borrow().clicked(), 144 | _ => () 145 | } 146 | Inhibit(false) 147 | }); 148 | } 149 | 150 | // Show the window and start the program 151 | window.show_all(); 152 | gtk::main(); 153 | } 154 | 155 | /// Send text to Google Translate and translate it. 156 | fn translate(input: &str, language: &str, output: &mut String) { 157 | let mut search = String::new(); 158 | search.push_str(TRANSLATE); 159 | search.push_str(language); 160 | search.push_str("&dt=t&q="); 161 | search.push_str(input); 162 | if let Ok(mut response) = Client::new().get(&search).header(Connection::close()).send() { 163 | search.clear(); 164 | if let Err(error) = response.read_to_string(&mut search) { 165 | panic!("Unable to read response: {}", error); 166 | } 167 | } 168 | parse_message(search.as_str(), output); 169 | } 170 | 171 | /// Take the raw response from Google and parse the translation only. 172 | fn parse_message(input: &str, translation: &mut String) { 173 | let mut escape = false; 174 | let mut ignore = false; 175 | let mut found_match = false; 176 | let mut matched: u8 = 0; 177 | 178 | // Loop until ',,,0]]' is found 179 | for character in input.chars().skip(4) { 180 | if found_match { 181 | matched = match matched { 182 | 0 => 1, 183 | 1 => { found_match = false; 0 }, 184 | _ => unreachable!() 185 | } 186 | } else if ignore { 187 | matched = match (matched, character) { 188 | (0, ',') => 1, 189 | (1, ',') => 2, 190 | (2, ',') => 3, 191 | (3, '0') => 4, 192 | (4, ']') => 5, 193 | (5, ']') => break, // ',,,0]]' has been found 194 | (5, _) => {ignore = false; found_match = true; 0 } 195 | _ => 0 196 | }; 197 | } else if character == '\\' && !escape { 198 | escape = true; 199 | } else if escape { 200 | translation.push(character); 201 | escape = false; 202 | } else if character == '"' { 203 | ignore = true; 204 | } else { 205 | translation.push(character); 206 | } 207 | } 208 | } 209 | 210 | 211 | #[test] 212 | fn test_parse_message() { 213 | const TEST: &'static str = "[[[\"I am not you. \",\"Mi estas ne vin.\",,,0],[\"You are not me.\",\"Vi estas ne min.\",,,0]],,\"eo\",,,,0.070792444,,[[\"eo\"],,[0.070792444],[\"eo\"]]]"; 214 | let mut output = String::new(); 215 | parse_message(TEST, &mut output); 216 | assert_eq!(output.as_str(), "I am not you. You are not me.") 217 | } 218 | -------------------------------------------------------------------------------- /src/translate.glade: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | False 7 | 500 8 | 400 9 | 10 | 11 | True 12 | False 13 | vertical 14 | 15 | 16 | True 17 | True 18 | natural 19 | natural 20 | word 21 | 5 22 | 5 23 | 24 | 25 | True 26 | True 27 | 0 28 | 29 | 30 | 31 | 32 | True 33 | False 34 | 35 | 36 | False 37 | True 38 | 1 39 | 40 | 41 | 42 | 43 | 44 | 45 | True 46 | False 47 | Google Translate 48 | Translate Text 49 | True 50 | 51 | 52 | True 53 | False 54 | 1 55 | English 56 | 57 | Chinese 58 | English 59 | Esperanto 60 | French 61 | German 62 | Italian 63 | Japanese 64 | Korean 65 | Russian 66 | Spanish 67 | 68 | 69 | 70 | 71 | 72 | Translate 73 | True 74 | True 75 | True 76 | 77 | 78 | end 79 | 2 80 | 81 | 82 | 83 | 84 | 85 | 86 | --------------------------------------------------------------------------------