diff options
Diffstat (limited to 'locale/tools/src/main.rs')
-rw-r--r-- | locale/tools/src/main.rs | 267 |
1 files changed, 267 insertions, 0 deletions
diff --git a/locale/tools/src/main.rs b/locale/tools/src/main.rs new file mode 100644 index 00000000..1ae59c83 --- /dev/null +++ b/locale/tools/src/main.rs @@ -0,0 +1,267 @@ +#![feature(iterator_try_collect)] +use anyhow::{anyhow, Context, Result}; +use clap::Parser; +use std::{ + collections::BTreeMap, + fs::{read_to_string, File}, + io::Write, + path::{Path, PathBuf}, +}; + +#[derive(Parser)] +enum Args { + ImportOldPot { + input: PathBuf, + output: PathBuf, + }, + ImportOldPo { + reference: PathBuf, + input: PathBuf, + output: PathBuf, + }, + ExportGodotCsv { + input_dir: PathBuf, + output: PathBuf, + }, + ExportPo { + #[arg(long)] + remap_ids: Option<PathBuf>, + input: PathBuf, + output: PathBuf, + }, +} + +fn main() -> Result<()> { + let args = Args::parse(); + match args { + Args::ExportPo { + remap_ids: id_map, + input, + output, + } => { + let ini = load_ini(&input)?; + let id_map = id_map.map(|path| load_ini(&path)).transpose()?; + + File::create(output)?.write_all( + format!( + r#" +msgid "" +msgstr "" +"Language: {}\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +{}"#, + input.file_stem().unwrap().to_string_lossy(), + ini.into_iter() + .map(|(mut key, value)| { + if let Some(id_map) = &id_map { + if let Some(new_id) = id_map.get(&key) { + key = new_id.to_owned() + } + } + format!( + "msgid {}\nmsgstr {}\n\n", + serde_json::to_string(&key).unwrap(), + serde_json::to_string(&value).unwrap(), + ) + }) + .collect::<String>() + ) + .as_bytes(), + )?; + Ok(()) + } + Args::ExportGodotCsv { input_dir, output } => { + let translations = input_dir + .read_dir()? + .flat_map(|e| { + e.map_err(|e| anyhow!("{e}")) + .and_then(|e| { + if e.file_name().to_string_lossy().ends_with(".ini") { + Ok(Some(( + e.path().file_stem().unwrap().to_str().unwrap().to_string(), + load_ini(&e.path())?, + ))) + } else { + Ok(None) + } + }) + .transpose() + }) + .try_collect::<BTreeMap<_, _>>()?; + + let langs = translations.keys().cloned().collect::<Vec<String>>(); + let mut tr_tr = BTreeMap::<String, BTreeMap<String, String>>::new(); + for (k, v) in translations { + for (kk, vv) in v { + tr_tr.entry(kk).or_default().insert(k.clone(), vv); + } + } + + File::create(output)?.write_all( + format!( + "id,{}\n{}", + langs.join(","), + tr_tr + .into_iter() + .map(|(k, v)| format!( + "{k},{}\n", + v.values() + .map(|s| serde_json::to_string(s).unwrap()) + .collect::<Vec<_>>() + .join(",") + )) + .collect::<String>() + ) + .as_bytes(), + )?; + Ok(()) + } + Args::ImportOldPo { + reference, + input, + output, + } => { + let reference = read_to_string(reference)?; + let input = read_to_string(input)?; + + let id_reverse = reference + .lines() + .skip(1) + .map(|l| { + l.split_once("=") + .map(|(k, v)| (v, k)) + .ok_or(anyhow!("invalid ini")) + }) + .try_collect::<BTreeMap<&str, &str>>()?; + + let mut outmap = BTreeMap::new(); + let mut mode = 0; + let mut msgid = String::new(); + let mut msgstr = String::new(); + for (i, mut line) in input.lines().enumerate() { + if line.starts_with("#") { + continue; + } + if line.is_empty() { + continue; + } + if let Some(rest) = line.strip_prefix("msgid ") { + if !msgid.is_empty() { + if let Some(id) = id_reverse.get(&msgid.as_str()) { + outmap.insert(id.to_owned(), msgstr.clone()); + } else { + eprintln!("warning: message id {msgid:?} is unknown") + } + } + line = rest; + msgid = String::new(); + mode = 1; + } else if let Some(rest) = line.strip_prefix("msgstr ") { + line = rest; + msgstr = String::new(); + mode = 2; + } else if let Some(_) = line.strip_prefix("msgctxt ") { + mode = 0; + eprintln!("warning: msgctxt not implemented (line {})", i + 1); + continue; + } + let frag = + serde_json::from_str::<String>(line).context(anyhow!("line {}", i + 1))?; + match mode { + 0 => (), + 1 => msgid.push_str(&frag), + 2 => msgstr.push_str(&frag), + _ => unreachable!(), + }; + } + + File::create(output)?.write_all( + format!( + "[hurrycurry]\n{}", + outmap + .into_iter() + .map(|(k, v)| format!("{k}={v}\n")) + .collect::<String>() + ) + .as_bytes(), + )?; + + Ok(()) + } + Args::ImportOldPot { input, output } => { + let output_raw = read_to_string(&output).unwrap_or("".to_owned()); + let input = read_to_string(input)?; + + let mut output_flip = output_raw + .lines() + .skip(1) + .map(|l| { + l.split_once("=") + .map(|(k, v)| (v.to_owned(), k.to_owned())) + .ok_or(anyhow!("invalid ini")) + }) + .try_collect::<BTreeMap<String, String>>()?; + + let mut id = false; + let mut msgid = String::new(); + for (i, mut line) in input.lines().enumerate() { + if line.starts_with("#") { + continue; + } + if line.is_empty() { + continue; + } + if let Some(rest) = line.strip_prefix("msgid ") { + if !msgid.is_empty() { + if !output_flip.contains_key(&msgid) { + output_flip.insert(msgid.replace("\n", "\\n"), format!("unknown{i}")); + } + } + line = rest; + id = true; + msgid = String::new(); + } else if line.starts_with("msgctxt ") || line.starts_with("msgstr ") { + id = false; + continue; + } + if id { + let frag = + serde_json::from_str::<String>(line).context(anyhow!("line {}", i + 1))?; + msgid.push_str(frag.as_str()); + } + } + + let output_unflip = output_flip + .into_iter() + .map(|(v, k)| (k, v)) + .collect::<BTreeMap<_, _>>(); + + File::create(output)?.write_all( + format!( + "[hurrycurry]\n{}", + output_unflip + .into_iter() + .map(|(k, v)| format!("{k}={v}\n")) + .collect::<String>() + ) + .as_bytes(), + )?; + + Ok(()) + } + } +} + +fn load_ini(path: &Path) -> Result<BTreeMap<String, String>> { + Ok(read_to_string(path)? + .lines() + .skip(1) + .map(|l| { + let (k, v) = l.split_once("=").ok_or(anyhow!("'=' missing"))?; + Ok::<_, anyhow::Error>((k.to_owned(), v.replace("\\n", "\n"))) + }) + .try_collect()?) +} |