diff options
Diffstat (limited to 'server/src/trans.rs')
| -rw-r--r-- | server/src/trans.rs | 193 |
1 files changed, 193 insertions, 0 deletions
diff --git a/server/src/trans.rs b/server/src/trans.rs new file mode 100644 index 0000000..dcef078 --- /dev/null +++ b/server/src/trans.rs @@ -0,0 +1,193 @@ +use anyhow; +use std::collections::{HashMap, HashSet}; +use std::iter::{repeat, IntoIterator}; +use std::path::{Path, PathBuf}; +use tokio::task::JoinSet; + +use eyeballs_api::api_model; +use eyeballs_common::grit; + +fn schedule_translations( + tasks: &mut JoinSet<anyhow::Result<grit::TranslationFile>>, + known: &mut HashSet<String>, + path: &Path, + files: &Vec<grit::IfFile>, +) { + for file in files { + match file { + grit::IfFile::File(file) => { + if known.insert(file.path.to_string()) { + tasks.spawn(grit::parse_xlf(path.join(file.path.as_str()))); + } + } + grit::IfFile::If { expr: _, file } => { + schedule_translations(tasks, known, path, file); + } + } + } +} + +fn push_strings( + strings: &mut Vec<api_model::LocalizationString>, + file: &String, + messages: Vec<grit::IfMessagePart>, +) { + for message in messages { + match message { + grit::IfMessagePart::Message(message) => { + let mut source = String::new(); + let mut placeholders = Vec::<api_model::LocalizationPlaceholder>::new(); + let mut placeholder_offset = Vec::<usize>::new(); + + let translation_id = grit::get_message_id(&message); + + let mut offset: usize = 0; + for text in message.content { + match text { + grit::TextPlaceholder::Text(text) => { + source.push_str(text.as_str()); + offset += text.len(); + } + grit::TextPlaceholder::Placeholder { + name, + content, + example, + } => { + placeholders.push(api_model::LocalizationPlaceholder { + id: name, + content, + example: example.unwrap_or_default(), + }); + placeholder_offset.push(offset); + } + } + } + + strings.push(api_model::LocalizationString { + id: message.name, + file: file.to_string(), + description: message.desc, + meaning: message.meaning.unwrap_or_default(), + source, + placeholders, + placeholder_offset, + translation_id, + translations: Vec::<api_model::TranslationString>::new(), + }); + } + grit::IfMessagePart::If { expr: _, message } => { + push_strings(strings, file, message); + } + grit::IfMessagePart::Part(_) => { + // There should be none of these as we use parse_grit_with_parts + assert!(false); + } + } + } +} + +fn push_translation( + string: &mut api_model::LocalizationString, + language: &String, + unit: grit::TranslationUnit, +) { + let mut translation = String::new(); + let mut placeholder_offset = Vec::<usize>::with_capacity(string.placeholders.len()); + // Fill offset vec with zeros, it's not guaranteed that they will be in the same order + // below so easier to index directly. + placeholder_offset.extend(repeat(0).take(string.placeholders.len())); + + // There can be multiple placeholders with the same name, so when doing name lookup, + // skip the previous hits. + let mut placeholder_last = HashMap::<String, usize>::new(); + + let mut offset: usize = 0; + for text in unit.target { + match text { + grit::TextPlaceholder::Text(text) => { + translation.push_str(text.as_str()); + offset += text.len(); + } + grit::TextPlaceholder::Placeholder { + name, + content: _, + example: _, + } => { + let previous = placeholder_last.get(name.as_str()).map_or(0, |x| x + 1); + if let Some(index) = string + .placeholders + .iter() + .skip(previous) + .position(|x| x.id == name) + { + placeholder_last.insert(name, previous + index); + placeholder_offset[previous + index] = offset; + } + } + } + } + + string.translations.push(api_model::TranslationString { + language: language.to_string(), + translation, + placeholder_offset, + }) +} + +pub async fn collect_strings( + base: impl AsRef<Path>, + grits: impl IntoIterator<Item = String>, +) -> anyhow::Result<Vec<api_model::LocalizationString>> { + let mut grit_tasks = JoinSet::new(); + for grit_name in grits { + let grit_path = base.as_ref().join(grit_name.as_str()); + grit_tasks.spawn(async move { + let tmp = grit::parse_grit_with_parts(grit_path.as_path()).await; + (grit_path, grit_name, tmp) + }); + } + + let mut parsed_grits = + Vec::<(PathBuf, String, anyhow::Result<grit::Grit>)>::with_capacity(grit_tasks.len()); + while let Some(res) = grit_tasks.join_next().await { + parsed_grits.push(res?); + } + + let mut strings = Vec::<api_model::LocalizationString>::new(); + let mut translation_tasks = JoinSet::new(); + let mut known_translations = HashSet::<String>::new(); + + for (grit_path, grit_name, maybe_grit) in parsed_grits { + let grit = maybe_grit?; + schedule_translations( + &mut translation_tasks, + &mut known_translations, + grit_path.parent().unwrap(), + &grit.translations.file, + ); + + let first_index = strings.len(); + + push_strings(&mut strings, &grit_name, grit.release.messages.messages); + + let mut id_to_string = HashMap::<i64, usize>::with_capacity(strings.len() - first_index); + for i in first_index..strings.len() { + id_to_string.insert(strings[i].translation_id, i); + } + + while let Some(res) = translation_tasks.join_next().await { + let translation_file = res??; + for unit in translation_file.units { + if let Some(index) = id_to_string.get(&unit.id) { + push_translation( + &mut strings[*index], + &translation_file.target_language, + unit, + ); + } + } + } + } + + Ok(strings) +} |
