diff options
| author | metamuffin <metamuffin@noreply.codeberg.org> | 2024-09-04 16:05:48 +0000 | 
|---|---|---|
| committer | metamuffin <metamuffin@noreply.codeberg.org> | 2024-09-04 16:05:48 +0000 | 
| commit | 406ba2117783f17010f4786a86fa02ae715388e6 (patch) | |
| tree | ae49295004174f5010e28169b262b7006a22e769 /locale/tools/src/main.rs | |
| parent | b3604a7e8cc24acc0f95dd83fb45906fa29ffec7 (diff) | |
| parent | 815a8c255faa8959fa2086b11e3f3bf1785be274 (diff) | |
| download | hurrycurry-406ba2117783f17010f4786a86fa02ae715388e6.tar hurrycurry-406ba2117783f17010f4786a86fa02ae715388e6.tar.bz2 hurrycurry-406ba2117783f17010f4786a86fa02ae715388e6.tar.zst | |
Merge pull request 'New po-less translation system' (#119) from new-tr into master
Reviewed-on: https://codeberg.org/hurrycurry/hurrycurry/pulls/119
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()?) +} | 
