summaryrefslogtreecommitdiff
path: root/server/src/trans.rs
diff options
context:
space:
mode:
Diffstat (limited to 'server/src/trans.rs')
-rw-r--r--server/src/trans.rs193
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)
+}