sysd_manager_translating/
lib.rs

1use std::{fs, io, process::Command};
2
3use log::info;
4
5use crate::error::TransError;
6pub mod error;
7
8pub const MAIN_PROG: &str = "sysd-manager";
9pub const PO_DIR: &str = "./po";
10pub const DESKTOP_DIR: &str = "./data/applications";
11
12/// Making the PO Template File
13/// https://www.gnu.org/software/gettext/manual/html_node/xgettext-Invocation.html
14pub fn xgettext(potfiles_file_path: &str, output_pot_file: &str) {
15    let mut command = Command::new("xgettext");
16
17    for preset in glib_preset() {
18        command.arg(preset);
19    }
20
21    let output = command
22        .arg(format!("--files-from={potfiles_file_path}"))
23        .arg(format!("--output={output_pot_file}"))
24        .arg("--verbose")
25        .output()
26        .unwrap();
27
28    display_output("XGETTEXT", output);
29}
30
31fn display_output(id: &str, output: std::process::Output) {
32    println!("{id}: {:?}", output.status);
33    println!("{id}: {}", String::from_utf8_lossy(&output.stdout));
34    if !output.status.success() {
35        eprintln!("{id}: {}", String::from_utf8_lossy(&output.stderr));
36    }
37}
38
39/// Creating a New PO File
40/// https://www.gnu.org/software/gettext/manual/html_node/msginit-Invocation.html
41pub fn msginit(input_pot_file: &str, output_file: &str, lang: &str) {
42    let mut command = Command::new("msginit");
43
44    let output = command
45        .arg(format!("--input={input_pot_file}"))
46        .arg(format!("--output-file={output_file}"))
47        .arg(format!("--locale={lang}"))
48        .output()
49        .expect("command msginit ok");
50
51    display_output("MSGINIT", output);
52}
53
54//   /usr/bin/msgmerge --update --quiet  --lang=pt_BR --previous pt_BR.po hello-rust.pot
55// rm -f pt_BR.gmo && /usr/bin/msgmerge --for-msgfmt -o pt_BR.1po pt_BR.po hello-rust.pot && /usr/bin/msgfmt -c --statistics --verbose -o pt_BR.gmo pt_BR.1po && rm -f pt_BR.1po
56// pt_BR.1po: 2 translated messages.
57
58/// https://www.gnu.org/software/gettext/manual/html_node/msgmerge-Invocation.html
59pub fn msgmerge(input_pot_file: &str, output_file: &str) {
60    let mut command = Command::new("msgmerge");
61
62    let output = command
63        .arg("-o")
64        .arg(output_file)
65        .arg(output_file)
66        .arg(input_pot_file)
67        .arg("--verbose")
68        .output()
69        .unwrap();
70
71    display_output("MSGMERGE", output);
72}
73
74pub fn generate_mo() -> Result<(), TransError> {
75    for path in fs::read_dir(PO_DIR)?
76        .filter_map(|r| r.ok())
77        .filter_map(|dir_entry| {
78            let p = dir_entry.path();
79            if p.extension().is_some_and(|this_ext| this_ext == "po") {
80                Some(p)
81            } else {
82                None
83            }
84        })
85    {
86        info!("PO file {:?} lang {:?}", path, path.file_stem());
87
88        if let Some(po_file) = path.to_str() {
89            if let Some(lang) = path.file_stem().and_then(|s| s.to_str()) {
90                msgfmt(po_file, lang, MAIN_PROG)?;
91            }
92        }
93    }
94
95    Ok(())
96}
97
98use std::env;
99
100pub fn set_lingas_env() -> Result<(), TransError> {
101    let mut vec: Vec<_> = fs::read_dir(PO_DIR)?
102        .filter_map(|r| r.ok())
103        .filter_map(|dir_entry| {
104            let p = dir_entry.path();
105            if p.extension().is_some_and(|this_ext| this_ext == "po") {
106                Some(p)
107            } else {
108                None
109            }
110        })
111        .filter_map(|path| {
112            path.file_stem()
113                .and_then(|s| s.to_str())
114                .map(|s| s.to_string())
115            /*            if let Some(p) = path.file_stem().and_then(|s| s.to_str()) {
116                Some(p.to_string())
117            } else {
118                None
119            } */
120        })
121        .collect();
122
123    vec.sort();
124    let langs = vec.join(" ");
125
126    let key = "LINGUAS";
127    unsafe {
128        env::set_var(key, langs);
129    }
130
131    Ok(())
132}
133
134pub fn generate_desktop() -> Result<(), TransError> {
135    let desktop_file_name = "io.github.plrigaux.sysd-manager.desktop";
136
137    let out_dir = "target/locale";
138    fs::create_dir_all(out_dir)?;
139    let out_file = format!("{}/{}", out_dir, desktop_file_name);
140
141    let mut command = Command::new("msgfmt");
142    let output = command
143        .arg("--check")
144        .arg("--statistics")
145        .arg("--verbose")
146        .arg("--desktop")
147        .arg(format!("--template={DESKTOP_DIR}/{desktop_file_name}"))
148        .arg("-d")
149        .arg(PO_DIR)
150        .arg("-o")
151        .arg(out_file)
152        .output()?;
153
154    display_output("MSGFMT", output);
155
156    Ok(())
157}
158
159// /usr/bin/msgfmt -c --statistics --verbose -o pt_BR.gmo pt_BR.1po && rm -f pt_BR.1po
160/// Generates a binary message catalog from a textual translation description.
161/// https://www.gnu.org/software/gettext/manual/html_node/msgfmt-Invocation.html
162pub fn msgfmt(po_file: &str, lang: &str, domain_name: &str) -> io::Result<()> {
163    let mut command = Command::new("msgfmt");
164
165    let out_dir = format!("target/locale/{lang}/LC_MESSAGES");
166
167    fs::create_dir_all(&out_dir)?;
168
169    let output = command
170        .arg("--check")
171        .arg("--statistics")
172        .arg("--verbose")
173        .arg("-o")
174        .arg(format!("{out_dir}/{domain_name}.mo"))
175        .arg(po_file)
176        .output()
177        .unwrap();
178
179    display_output("MSGFMT", output);
180
181    Ok(())
182}
183
184fn glib_preset() -> Vec<&'static str> {
185    let v = vec![
186        "--from-code=UTF-8",
187        "--add-comments",
188        // # https://developer.gnome.org/glib/stable/glib-I18N.html
189        "--keyword=_",
190        "--keyword=N_",
191        "--keyword=C_:1c,2",
192        "--keyword=NC_:1c,2",
193        "--keyword=g_dcgettext:2",
194        "--keyword=g_dngettext:2,3",
195        "--keyword=g_dpgettext2:2c,3",
196        "--flag=N_:1:pass-c-format",
197        "--flag=C_:2:pass-c-format",
198        "--flag=NC_:2:pass-c-format",
199        "--flag=g_dngettext:2:pass-c-format",
200        "--flag=g_strdup_printf:1:c-format",
201        "--flag=g_string_printf:2:c-format",
202        "--flag=g_string_append_printf:2:c-format",
203        "--flag=g_error_new:3:c-format",
204        "--flag=g_set_error:4:c-format",
205        "--flag=g_markup_printf_escaped:1:c-format",
206        "--flag=g_log:3:c-format",
207        "--flag=g_print:1:c-format",
208        "--flag=g_printerr:1:c-format",
209        "--flag=g_printf:1:c-format",
210        "--flag=g_fprintf:2:c-format",
211        "--flag=g_sprintf:2:c-format",
212        "--flag=g_snprintf:3:c-format",
213    ];
214    v
215}