From 35d408f8a3985775a2750eb11a65823b69e6801d Mon Sep 17 00:00:00 2001 From: Jonathan Denose Date: Mon, 14 Apr 2025 19:37:09 +0000 Subject: [PATCH 1/8] add translate-helper-script with build cmd --- Cargo.lock | 358 +++++++++++++++++- i18n-helpers/Cargo.toml | 2 + .../src/bin/mdbook-translate-helper.rs | 181 +++++++++ 3 files changed, 526 insertions(+), 15 deletions(-) create mode 100644 i18n-helpers/src/bin/mdbook-translate-helper.rs diff --git a/Cargo.lock b/Cargo.lock index 96e4af9..588498f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,17 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -149,6 +160,31 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bzip2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47" +dependencies = [ + "bzip2-sys", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.13+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" +dependencies = [ + "cc", + "pkg-config", +] + [[package]] name = "cc" version = "1.0.98" @@ -202,6 +238,16 @@ dependencies = [ "phf_codegen", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clap" version = "4.5.35" @@ -258,6 +304,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + [[package]] name = "core-foundation-sys" version = "0.8.6" @@ -273,6 +325,21 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crc32fast" version = "1.4.2" @@ -303,9 +370,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crypto-common" @@ -340,6 +407,21 @@ dependencies = [ "winapi", ] +[[package]] +name = "deflate64" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b" + +[[package]] +name = "deranged" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +dependencies = [ + "powerfmt", +] + [[package]] name = "derive_arbitrary" version = "1.4.1" @@ -371,6 +453,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -396,6 +479,12 @@ dependencies = [ "log", ] +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + [[package]] name = "errno" version = "0.3.10" @@ -456,8 +545,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.13.3+wasi-0.2.2", + "wasm-bindgen", "windows-targets", ] @@ -500,6 +591,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + [[package]] name = "heck" version = "0.5.0" @@ -512,6 +609,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "humansize" version = "2.1.3" @@ -576,6 +682,25 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "indexmap" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.0" @@ -599,10 +724,11 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -665,12 +791,39 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" +[[package]] +name = "lockfree-object-pool" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" + [[package]] name = "log" version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +[[package]] +name = "lzma-rs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e" +dependencies = [ + "byteorder", + "crc", +] + +[[package]] +name = "lzma-sys" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "mdbook" version = "0.4.48" @@ -717,6 +870,8 @@ dependencies = [ "syntect", "tempfile", "textwrap", + "walkdir", + "zip", ] [[package]] @@ -767,6 +922,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-modular" version = "0.6.1" @@ -840,6 +1001,16 @@ dependencies = [ "regex", ] +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -944,6 +1115,12 @@ dependencies = [ "linereader", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -1108,6 +1285,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + [[package]] name = "ryu" version = "1.0.18" @@ -1161,6 +1344,17 @@ dependencies = [ "serde", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha2" version = "0.10.8" @@ -1178,6 +1372,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "siphasher" version = "0.3.11" @@ -1200,6 +1400,12 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.86" @@ -1302,6 +1508,25 @@ dependencies = [ "syn", ] +[[package]] +name = "time" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + [[package]] name = "toml" version = "0.5.11" @@ -1433,23 +1658,24 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", + "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", "syn", @@ -1458,9 +1684,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1468,9 +1694,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", @@ -1481,9 +1707,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "winapi" @@ -1622,8 +1851,107 @@ dependencies = [ "bitflags 2.5.0", ] +[[package]] +name = "xz2" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" +dependencies = [ + "lzma-sys", +] + [[package]] name = "yansi" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zip" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dcb24d0152526ae49b9b96c1dcf71850ca1e0b882e4e28ed898a93c41334744" +dependencies = [ + "aes", + "arbitrary", + "bzip2", + "constant_time_eq", + "crc32fast", + "crossbeam-utils", + "deflate64", + "flate2", + "getrandom 0.3.1", + "hmac", + "indexmap", + "lzma-rs", + "memchr", + "pbkdf2", + "sha1", + "time", + "xz2", + "zeroize", + "zopfli", + "zstd", +] + +[[package]] +name = "zopfli" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" +dependencies = [ + "bumpalo", + "crc32fast", + "lockfree-object-pool", + "log", + "once_cell", + "simd-adler32", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.15+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/i18n-helpers/Cargo.toml b/i18n-helpers/Cargo.toml index 932f004..05b627d 100644 --- a/i18n-helpers/Cargo.toml +++ b/i18n-helpers/Cargo.toml @@ -25,6 +25,8 @@ semver = "1.0.26" serde_json.workspace = true syntect = { version = "5.2.0", default-features = false, features = ["parsing", "default-syntaxes", "regex-onig"] } textwrap = { version = "0.16.2", default-features = false } +walkdir = "2.5.0" +zip = "2.6.1" [dev-dependencies] pretty_assertions.workspace = true diff --git a/i18n-helpers/src/bin/mdbook-translate-helper.rs b/i18n-helpers/src/bin/mdbook-translate-helper.rs new file mode 100644 index 0000000..0471254 --- /dev/null +++ b/i18n-helpers/src/bin/mdbook-translate-helper.rs @@ -0,0 +1,181 @@ +use std::env; +use std::fs; +use std::io; +use std::io::BufRead; +use std::io::Read; +use std::io::Write; +use::std::path::Path; +use std::process::{Command, Stdio}; +use anyhow::Context; +use anyhow::Error; +use chrono; +use walkdir::WalkDir; +use zip::result::ZipError; +use zip::write::SimpleFileOptions; +use std::fs::File; +fn main() { + let args: Vec = env::args().collect(); + + parse_args(&args); + + println!("::endgroup::"); +} + +struct TranslateHelperConfig { + book_lang: String, + dest_dir: String +} + +fn build_translation(locale: String, dest_dir: String) -> Result<(), Error>{ + if locale == "en" { + println!("::group::Building English course"); + } else { + println!("::group::Building {} course", locale); + let file = fs::File::open(format!("po/{locale}.po")).expect("Error reading .po file for your locale"); + let reader = io::BufReader::new(file); + + let mut pot_creation_date: Option = None; + + for line in reader.lines() { + let line = line.expect("Error reading line"); + if line.contains("POT-Creation-Date") { + println!("{line}"); + let line_parts: Vec<&str> = line.split(":").collect(); + pot_creation_date = Some(line_parts[1].trim_start().to_string()); + println!("{:?}", pot_creation_date); + break; + } + } + + if pot_creation_date.is_none() { + let now = chrono::Local::now(); + pot_creation_date = Some(now.format("%Y-%m-%dT%H:%M:%S").to_string()); + println!("Created date from now {:?}", pot_creation_date); + } + + println!("Building {locale} translation as of {:?}", pot_creation_date.unwrap()); + } + + // Enable mdbook-pandoc to build PDF version of the course + env::set_var("MDBOOK_OUTPUT__PANDOC__DISABLED", "false"); + + let dir = env::current_dir()?; + let dest_arg = format!("-d{dest_dir}"); + let output = Command::new("mdbook").arg("build").arg(dest_arg).stdout(Stdio::piped()).current_dir(dir).output(); + + match output { + Ok(output) => { + println!("Exit status: {}", output.status); + let stdout_str = String::from_utf8_lossy(&output.stdout); + println!("\nStdout:"); + if stdout_str.is_empty() { + println!("(empty)"); + } else { + println!("{}", stdout_str); + } + + // Capture stderr as well (often useful for debugging failures) + let stderr_str = String::from_utf8_lossy(&output.stderr); + if !stderr_str.is_empty() { + eprintln!("\nStderr:"); // Use eprintln for errors + eprintln!("{}", stderr_str); + } + }, + Err(err) => panic!("Failed to execute mdbook build: {err}"), + } + // Disable the redbox button in built versions of the course + let _ = fs::write(format!("{}/html/theme/redbox.js", dest_dir), "// Disabled in published builds, see build.sh") + .expect("Unable to write to /html/theme/redbox.js"); + + let pdf_from_dir = format!("{}/pandoc/pdf/comprehensive-rust.pdf", dest_dir); + let pdf_dest_dir = format!("{}/html/comprehensive-rust.pdf", dest_dir); + + let pdf_from_path = Path::new(&pdf_from_dir); + let pdf_dest_path = Path::new(&pdf_dest_dir); + + fs::copy(pdf_from_path, pdf_dest_path)?; + fs::remove_file(pdf_from_path)?; + println!("Copied comprehensive-rust.pdf to {pdf_dest_dir}"); + + let src_dir = format!("{dest_dir}/exerciser/comprehensive-rust-exercises"); + let src_path = Path::new(&src_dir); + if !src_path.is_dir() { + return Err(ZipError::FileNotFound.into()); + } + + let zip_file_str = format!("{dest_dir}/html/comprehensive-rust-exercises.zip"); + let zip_file = File::create(Path::new(&zip_file_str))?; + + let options = SimpleFileOptions::default().compression_method(zip::CompressionMethod::Stored); + + let mut zip = zip::ZipWriter::new(zip_file); + let mut buffer = Vec::new(); + + for entry in WalkDir::new(src_path).into_iter().filter_map(|e| e.ok()) { + let path = entry.path(); + let name = path.strip_prefix(src_path)?; + let name_as_str = name.to_str().map(str::to_owned).with_context(|| format!("{name:?} is a non utf-8 path"))?; + + if path.is_file() { + zip.start_file(name_as_str, options)?; + let mut f = File::open(path)?; + + f.read_to_end(&mut buffer)?; + zip.write_all(&buffer)?; + buffer.clear(); + } else if !name.as_os_str().is_empty() { + zip.add_directory(name_as_str, options)?; + } + } + zip.finish()?; + + Ok(()) +} + + +// should run msmerge +fn update_translation(locale: String, dest_dir: String) { + print!("Updating translation for locale {locale} and destination directory {dest_dir}") +} + +fn translate_all() { + // let languages = env::var("LANGUAGES"); + if let Ok(languages) = env::var("LANGUAGES") { + println!("Translation all languages in $LANGUAGES: {languages}"); + languages.split(" ").for_each(| lang | -> () { + println!("translation language {lang}"); + }); + } else { + panic!("Error reading environment variable LANGUAGES. Has it been set correctly?") + } +} + +fn parse_args(args: &[String]) -> () { + let cmd_name = &args[0]; + + if args.len() == 1 { + panic!("No arguments provided. Usage {cmd_name} ") + } + let translate_action = args[1].clone(); + + match translate_action.as_str() { + "build" | "update" => { + if args.len() != 4 { + panic!("Usage: {cmd_name} "); + } + let config = TranslateHelperConfig { book_lang: args[2].clone(), dest_dir: args[3].clone() }; + + if translate_action.as_str() == "build" { + match build_translation(config.book_lang, config.dest_dir) { + Ok(_) => (), + Err(err) => panic!("::endgroup::Error building translation: {}", err) + } + } else { + update_translation(config.book_lang, config.dest_dir); + } + }, + "all" => translate_all(), + _ => panic!("Supported commands are `build`, `update` and `all`") + }; + +} From d79f673a26c90781fa0e7cb56ae5739e42a3d415 Mon Sep 17 00:00:00 2001 From: Jonathan Denose Date: Mon, 14 Apr 2025 20:04:55 +0000 Subject: [PATCH 2/8] cleanup err handling --- .../src/bin/mdbook-translate-helper.rs | 36 +++++-------------- 1 file changed, 8 insertions(+), 28 deletions(-) diff --git a/i18n-helpers/src/bin/mdbook-translate-helper.rs b/i18n-helpers/src/bin/mdbook-translate-helper.rs index 0471254..cc78025 100644 --- a/i18n-helpers/src/bin/mdbook-translate-helper.rs +++ b/i18n-helpers/src/bin/mdbook-translate-helper.rs @@ -5,7 +5,7 @@ use std::io::BufRead; use std::io::Read; use std::io::Write; use::std::path::Path; -use std::process::{Command, Stdio}; +use std::process::Command; use anyhow::Context; use anyhow::Error; use chrono; @@ -30,8 +30,7 @@ fn build_translation(locale: String, dest_dir: String) -> Result<(), Error>{ if locale == "en" { println!("::group::Building English course"); } else { - println!("::group::Building {} course", locale); - let file = fs::File::open(format!("po/{locale}.po")).expect("Error reading .po file for your locale"); + let file = fs::File::open(format!("po/{locale}.po"))?; let reader = io::BufReader::new(file); let mut pot_creation_date: Option = None; @@ -53,39 +52,20 @@ fn build_translation(locale: String, dest_dir: String) -> Result<(), Error>{ println!("Created date from now {:?}", pot_creation_date); } - println!("Building {locale} translation as of {:?}", pot_creation_date.unwrap()); + println!("::group::Building {locale} translation as of {:?}", pot_creation_date.unwrap()); + + // Back-date the source to POT-Creation-Date. The content lives in two directories: + // fs::remove_file(pdf_from_path)?; } // Enable mdbook-pandoc to build PDF version of the course env::set_var("MDBOOK_OUTPUT__PANDOC__DISABLED", "false"); - let dir = env::current_dir()?; let dest_arg = format!("-d{dest_dir}"); - let output = Command::new("mdbook").arg("build").arg(dest_arg).stdout(Stdio::piped()).current_dir(dir).output(); - - match output { - Ok(output) => { - println!("Exit status: {}", output.status); - let stdout_str = String::from_utf8_lossy(&output.stdout); - println!("\nStdout:"); - if stdout_str.is_empty() { - println!("(empty)"); - } else { - println!("{}", stdout_str); - } + Command::new("mdbook").arg("build").arg(dest_arg).output()?; - // Capture stderr as well (often useful for debugging failures) - let stderr_str = String::from_utf8_lossy(&output.stderr); - if !stderr_str.is_empty() { - eprintln!("\nStderr:"); // Use eprintln for errors - eprintln!("{}", stderr_str); - } - }, - Err(err) => panic!("Failed to execute mdbook build: {err}"), - } // Disable the redbox button in built versions of the course - let _ = fs::write(format!("{}/html/theme/redbox.js", dest_dir), "// Disabled in published builds, see build.sh") - .expect("Unable to write to /html/theme/redbox.js"); + fs::write(format!("{}/html/theme/redbox.js", dest_dir), "// Disabled in published builds, see build.sh")?; let pdf_from_dir = format!("{}/pandoc/pdf/comprehensive-rust.pdf", dest_dir); let pdf_dest_dir = format!("{}/html/comprehensive-rust.pdf", dest_dir); From 6796d4e62b836a84e8df8d881848bc21957252e2 Mon Sep 17 00:00:00 2001 From: Jonathan Denose Date: Tue, 22 Apr 2025 13:02:43 +0000 Subject: [PATCH 3/8] wip non-english translation --- .../src/bin/mdbook-translate-helper.rs | 75 +++++++++++++++++-- 1 file changed, 70 insertions(+), 5 deletions(-) diff --git a/i18n-helpers/src/bin/mdbook-translate-helper.rs b/i18n-helpers/src/bin/mdbook-translate-helper.rs index cc78025..8086147 100644 --- a/i18n-helpers/src/bin/mdbook-translate-helper.rs +++ b/i18n-helpers/src/bin/mdbook-translate-helper.rs @@ -1,6 +1,7 @@ use std::env; use std::fs; use std::io; +use std::io::stdout; use std::io::BufRead; use std::io::Read; use std::io::Write; @@ -30,7 +31,8 @@ fn build_translation(locale: String, dest_dir: String) -> Result<(), Error>{ if locale == "en" { println!("::group::Building English course"); } else { - let file = fs::File::open(format!("po/{locale}.po"))?; + let locale_ref = &locale; + let file = fs::File::open(format!("po/{locale_ref}.po"))?; let reader = io::BufReader::new(file); let mut pot_creation_date: Option = None; @@ -49,20 +51,83 @@ fn build_translation(locale: String, dest_dir: String) -> Result<(), Error>{ if pot_creation_date.is_none() { let now = chrono::Local::now(); pot_creation_date = Some(now.format("%Y-%m-%dT%H:%M:%S").to_string()); - println!("Created date from now {:?}", pot_creation_date); + // println!("Created date from now {:?}", pot_creation_date); } - println!("::group::Building {locale} translation as of {:?}", pot_creation_date.unwrap()); + println!("::group::Building {locale_ref} translation as of {:?}", pot_creation_date.clone().unwrap()); // Back-date the source to POT-Creation-Date. The content lives in two directories: - // fs::remove_file(pdf_from_path)?; + fs::remove_dir_all("src"); + fs::remove_dir_all("third_party"); + // Command::new("git").arg("rev-list").arg("-n").arg("--") + let output = Command::new("git").args(["rev-list", "-n", "1", "--before", &pot_creation_date.unwrap(), "@"]).output(); + let result_str: String; + match output { + Ok(result) => { + result_str = String::from_utf8(result.stdout).unwrap().replace("\n", ""); + println!("result string: {:?}", result_str.clone()); + }, + Err(err) => { + return Err(err.into()); + } + } + // io::stdout().write_all(&output.stdout).unwrap(); + let output2 = Command::new("git").args(["restore","--source", &result_str, "src/", "third_party/"]).output(); + + env::set_var("MDBOOK_BOOK__LANGUAGE", locale_ref); + env::set_var("MDBOOK_OUTPUT__HTML__SITE_URL", format!("/comprehensive-rust/{locale_ref}/")); + env::set_var("MDBOOK_OUTPUT__HTML__REDIRECT", "{}"); + + match output2 { + Ok(result) => { + println!("result string: {}", result.status); + println!("result string: {:#?}", result.stdout); + println!("result stderr string: {:#?}", String::from_utf8(result.stderr).unwrap()); + }, + Err(err) => { + println!("inside err case"); + return Err(err.into()); + } + } } // Enable mdbook-pandoc to build PDF version of the course env::set_var("MDBOOK_OUTPUT__PANDOC__DISABLED", "false"); let dest_arg = format!("-d{dest_dir}"); - Command::new("mdbook").arg("build").arg(dest_arg).output()?; + let output3 = Command::new("mdbook").arg("build").arg(dest_arg).output(); + match output3 { + Ok(output) => { + // println!("result string: {}", result.status); + // println!("result string: {:?}", result.stdout); + // println!("result stderr string: {:?}", String::from_utf8(result.stderr).unwrap()); + + // println!("Exit status: {}", output.status); + let stdout_str = String::from_utf8_lossy(&output.stdout); + println!("\nStdout:"); + if stdout_str.is_empty() { + println!("(empty)"); + } else { + println!("{}", stdout_str); + } + + // Capture stderr as well (often useful for debugging failures) + let stderr_str = String::from_utf8_lossy(&output.stderr); + if !stderr_str.is_empty() { + eprintln!("\nStderr:"); // Use eprintln for errors + eprintln!("{}", stderr_str); + } + + if !output.status.success() { + eprintln!("Command has non-zero exist status: {}", output.status); + // return Err(); + } + }, + Err(err) => { + println!("inside err case"); + return Err(err.into()); + } + } // Disable the redbox button in built versions of the course fs::write(format!("{}/html/theme/redbox.js", dest_dir), "// Disabled in published builds, see build.sh")?; From ae45def853e46dddf22f627fbd991587a669a7c6 Mon Sep 17 00:00:00 2001 From: Jonathan Denose Date: Fri, 9 May 2025 17:42:10 +0000 Subject: [PATCH 4/8] cleanup test code --- .../src/bin/mdbook-translate-helper.rs | 71 +++---------------- 1 file changed, 9 insertions(+), 62 deletions(-) diff --git a/i18n-helpers/src/bin/mdbook-translate-helper.rs b/i18n-helpers/src/bin/mdbook-translate-helper.rs index 8086147..274b977 100644 --- a/i18n-helpers/src/bin/mdbook-translate-helper.rs +++ b/i18n-helpers/src/bin/mdbook-translate-helper.rs @@ -1,7 +1,6 @@ use std::env; use std::fs; use std::io; -use std::io::stdout; use std::io::BufRead; use std::io::Read; use std::io::Write; @@ -40,10 +39,8 @@ fn build_translation(locale: String, dest_dir: String) -> Result<(), Error>{ for line in reader.lines() { let line = line.expect("Error reading line"); if line.contains("POT-Creation-Date") { - println!("{line}"); let line_parts: Vec<&str> = line.split(":").collect(); pot_creation_date = Some(line_parts[1].trim_start().to_string()); - println!("{:?}", pot_creation_date); break; } } @@ -51,80 +48,31 @@ fn build_translation(locale: String, dest_dir: String) -> Result<(), Error>{ if pot_creation_date.is_none() { let now = chrono::Local::now(); pot_creation_date = Some(now.format("%Y-%m-%dT%H:%M:%S").to_string()); - // println!("Created date from now {:?}", pot_creation_date); } println!("::group::Building {locale_ref} translation as of {:?}", pot_creation_date.clone().unwrap()); // Back-date the source to POT-Creation-Date. The content lives in two directories: - fs::remove_dir_all("src"); - fs::remove_dir_all("third_party"); - // Command::new("git").arg("rev-list").arg("-n").arg("--") - let output = Command::new("git").args(["rev-list", "-n", "1", "--before", &pot_creation_date.unwrap(), "@"]).output(); - let result_str: String; - match output { - Ok(result) => { - result_str = String::from_utf8(result.stdout).unwrap().replace("\n", ""); - println!("result string: {:?}", result_str.clone()); - }, - Err(err) => { - return Err(err.into()); - } - } - // io::stdout().write_all(&output.stdout).unwrap(); - let output2 = Command::new("git").args(["restore","--source", &result_str, "src/", "third_party/"]).output(); + fs::remove_dir_all("src")?; + fs::remove_dir_all("third_party")?; + + let output = Command::new("git").args(["rev-list", "-n", "1", "--before", &pot_creation_date.unwrap(), "@"]).output().unwrap(); + let result_str= String::from_utf8(output.stdout).unwrap().replace("\n", ""); + + Command::new("git").args(["restore","--source", &result_str, "src/", "third_party/"]).output()?; env::set_var("MDBOOK_BOOK__LANGUAGE", locale_ref); env::set_var("MDBOOK_OUTPUT__HTML__SITE_URL", format!("/comprehensive-rust/{locale_ref}/")); env::set_var("MDBOOK_OUTPUT__HTML__REDIRECT", "{}"); - - match output2 { - Ok(result) => { - println!("result string: {}", result.status); - println!("result string: {:#?}", result.stdout); - println!("result stderr string: {:#?}", String::from_utf8(result.stderr).unwrap()); - }, - Err(err) => { - println!("inside err case"); - return Err(err.into()); - } - } } // Enable mdbook-pandoc to build PDF version of the course env::set_var("MDBOOK_OUTPUT__PANDOC__DISABLED", "false"); let dest_arg = format!("-d{dest_dir}"); - let output3 = Command::new("mdbook").arg("build").arg(dest_arg).output(); - match output3 { - Ok(output) => { - // println!("result string: {}", result.status); - // println!("result string: {:?}", result.stdout); - // println!("result stderr string: {:?}", String::from_utf8(result.stderr).unwrap()); - - // println!("Exit status: {}", output.status); - let stdout_str = String::from_utf8_lossy(&output.stdout); - println!("\nStdout:"); - if stdout_str.is_empty() { - println!("(empty)"); - } else { - println!("{}", stdout_str); - } - - // Capture stderr as well (often useful for debugging failures) - let stderr_str = String::from_utf8_lossy(&output.stderr); - if !stderr_str.is_empty() { - eprintln!("\nStderr:"); // Use eprintln for errors - eprintln!("{}", stderr_str); - } - - if !output.status.success() { - eprintln!("Command has non-zero exist status: {}", output.status); - // return Err(); - } - }, + match Command::new("mdbook").arg("build").arg(dest_arg).output() { + Ok(_) => (), Err(err) => { - println!("inside err case"); return Err(err.into()); } } @@ -140,7 +88,6 @@ fn build_translation(locale: String, dest_dir: String) -> Result<(), Error>{ fs::copy(pdf_from_path, pdf_dest_path)?; fs::remove_file(pdf_from_path)?; - println!("Copied comprehensive-rust.pdf to {pdf_dest_dir}"); let src_dir = format!("{dest_dir}/exerciser/comprehensive-rust-exercises"); let src_path = Path::new(&src_dir); From 06b52a0973992d3b782ea0f38fcf80df3498ada1 Mon Sep 17 00:00:00 2001 From: Jonathan Denose Date: Fri, 9 May 2025 19:55:34 +0000 Subject: [PATCH 5/8] refactor build for all languages --- .../src/bin/mdbook-translate-helper.rs | 62 +++++++++---------- 1 file changed, 29 insertions(+), 33 deletions(-) diff --git a/i18n-helpers/src/bin/mdbook-translate-helper.rs b/i18n-helpers/src/bin/mdbook-translate-helper.rs index 274b977..4eca9bd 100644 --- a/i18n-helpers/src/bin/mdbook-translate-helper.rs +++ b/i18n-helpers/src/bin/mdbook-translate-helper.rs @@ -13,18 +13,6 @@ use walkdir::WalkDir; use zip::result::ZipError; use zip::write::SimpleFileOptions; use std::fs::File; -fn main() { - let args: Vec = env::args().collect(); - - parse_args(&args); - - println!("::endgroup::"); -} - -struct TranslateHelperConfig { - book_lang: String, - dest_dir: String -} fn build_translation(locale: String, dest_dir: String) -> Result<(), Error>{ if locale == "en" { @@ -125,24 +113,26 @@ fn build_translation(locale: String, dest_dir: String) -> Result<(), Error>{ } -// should run msmerge -fn update_translation(locale: String, dest_dir: String) { - print!("Updating translation for locale {locale} and destination directory {dest_dir}") +fn update() { + unimplemented!("update has not been implemented yet") } -fn translate_all() { - // let languages = env::var("LANGUAGES"); - if let Ok(languages) = env::var("LANGUAGES") { - println!("Translation all languages in $LANGUAGES: {languages}"); - languages.split(" ").for_each(| lang | -> () { - println!("translation language {lang}"); - }); - } else { - panic!("Error reading environment variable LANGUAGES. Has it been set correctly?") +fn build(dest_dir: String) -> Result<(), Error>{ + match env::var("LANGUAGES") { + Ok(languages) => { + for language in languages.split(" ") { + match build_translation(language.to_string(), dest_dir.clone()) { + Ok(_) => (), + Err(err) => return Err(err.into()) + } + } + return Ok(()) + }, + Err(err) => return Err(err.into()) } } -fn parse_args(args: &[String]) -> () { +fn run_helper(args: &[String]) -> () { let cmd_name = &args[0]; if args.len() == 1 { @@ -152,22 +142,28 @@ fn parse_args(args: &[String]) -> () { match translate_action.as_str() { "build" | "update" => { - if args.len() != 4 { - panic!("Usage: {cmd_name} "); + if args.len() != 3 { + panic!("Usage: {cmd_name} "); } - let config = TranslateHelperConfig { book_lang: args[2].clone(), dest_dir: args[3].clone() }; if translate_action.as_str() == "build" { - match build_translation(config.book_lang, config.dest_dir) { + match build(args[2].clone()) { Ok(_) => (), - Err(err) => panic!("::endgroup::Error building translation: {}", err) + Err(err) => panic!("::endgroup::Error building translations: {}", err) } } else { - update_translation(config.book_lang, config.dest_dir); + update(); } }, - "all" => translate_all(), - _ => panic!("Supported commands are `build`, `update` and `all`") + _ => panic!("Supported commands are `build` and `update`") }; } + +fn main() { + let args: Vec = env::args().collect(); + + run_helper(&args); + + println!("::endgroup::"); +} From af6b00604c5c1fb164752428a24f9faf79beec33 Mon Sep 17 00:00:00 2001 From: Jonathan Denose Date: Fri, 9 May 2025 20:44:33 +0000 Subject: [PATCH 6/8] fix destination directory --- i18n-helpers/src/bin/mdbook-translate-helper.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/i18n-helpers/src/bin/mdbook-translate-helper.rs b/i18n-helpers/src/bin/mdbook-translate-helper.rs index 4eca9bd..c57d42e 100644 --- a/i18n-helpers/src/bin/mdbook-translate-helper.rs +++ b/i18n-helpers/src/bin/mdbook-translate-helper.rs @@ -67,7 +67,6 @@ fn build_translation(locale: String, dest_dir: String) -> Result<(), Error>{ // Disable the redbox button in built versions of the course fs::write(format!("{}/html/theme/redbox.js", dest_dir), "// Disabled in published builds, see build.sh")?; - let pdf_from_dir = format!("{}/pandoc/pdf/comprehensive-rust.pdf", dest_dir); let pdf_dest_dir = format!("{}/html/comprehensive-rust.pdf", dest_dir); @@ -117,11 +116,12 @@ fn update() { unimplemented!("update has not been implemented yet") } -fn build(dest_dir: String) -> Result<(), Error>{ +fn build(dir: &str) -> Result<(), Error>{ match env::var("LANGUAGES") { Ok(languages) => { for language in languages.split(" ") { - match build_translation(language.to_string(), dest_dir.clone()) { + let dest_dir = dir.to_owned() + "/" + language; + match build_translation(language.to_string(), dest_dir) { Ok(_) => (), Err(err) => return Err(err.into()) } @@ -147,7 +147,8 @@ fn run_helper(args: &[String]) -> () { } if translate_action.as_str() == "build" { - match build(args[2].clone()) { + let dir = args[2].clone(); + match build(&dir) { Ok(_) => (), Err(err) => panic!("::endgroup::Error building translations: {}", err) } From 8ae817eaa614ec3c6d8b3042d604c70218a415f0 Mon Sep 17 00:00:00 2001 From: Jonathan Denose Date: Fri, 9 May 2025 21:00:35 +0000 Subject: [PATCH 7/8] ad git reset to cleanup repository --- i18n-helpers/src/bin/mdbook-translate-helper.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/i18n-helpers/src/bin/mdbook-translate-helper.rs b/i18n-helpers/src/bin/mdbook-translate-helper.rs index c57d42e..fb17bbc 100644 --- a/i18n-helpers/src/bin/mdbook-translate-helper.rs +++ b/i18n-helpers/src/bin/mdbook-translate-helper.rs @@ -108,6 +108,9 @@ fn build_translation(locale: String, dest_dir: String) -> Result<(), Error>{ } zip.finish()?; + // clean up repository + Command::new("git").args(["reset","--hard"]).output()?; + Ok(()) } From 8685ff2b6b5b0de04e1db6d93720dccf0b80dc86 Mon Sep 17 00:00:00 2001 From: Jonathan Denose Date: Mon, 12 May 2025 15:27:43 +0000 Subject: [PATCH 8/8] add update subcmd --- .../src/bin/mdbook-translate-helper.rs | 45 +++++++++++++------ 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/i18n-helpers/src/bin/mdbook-translate-helper.rs b/i18n-helpers/src/bin/mdbook-translate-helper.rs index fb17bbc..2af031d 100644 --- a/i18n-helpers/src/bin/mdbook-translate-helper.rs +++ b/i18n-helpers/src/bin/mdbook-translate-helper.rs @@ -115,8 +115,18 @@ fn build_translation(locale: String, dest_dir: String) -> Result<(), Error>{ } -fn update() { - unimplemented!("update has not been implemented yet") +fn update(filename: &str) -> Result<(), Error> { + match Command::new("mdbook").arg("build").output() { + Ok(_) => (), + Err(err) => return Err(err.into()) + }; + + match Command::new("msgmerge").args(["--update", filename, "book/xgettext/messages.pot"]).output() { + Ok(_) => (), + Err(err) => return Err(err.into()) + }; + + Ok(()) } fn build(dir: &str) -> Result<(), Error>{ @@ -141,22 +151,31 @@ fn run_helper(args: &[String]) -> () { if args.len() == 1 { panic!("No arguments provided. Usage {cmd_name} ") } - let translate_action = args[1].clone(); + let action = args[1].clone(); - match translate_action.as_str() { - "build" | "update" => { + match action.as_str() { + "build" => { if args.len() != 3 { panic!("Usage: {cmd_name} "); } + let dir = args[2].clone(); - if translate_action.as_str() == "build" { - let dir = args[2].clone(); - match build(&dir) { - Ok(_) => (), - Err(err) => panic!("::endgroup::Error building translations: {}", err) - } - } else { - update(); + match build(&dir) { + Ok(_) => (), + Err(err) => panic!("::group::Error building translations: {}", err) + } + }, + "update" => { + if args.len() != 3 { + panic!("Usage: {cmd_name} "); + } + let locale = args[2].clone(); + let filename = format!("po/{locale}.po"); + + println!("::group::Updating {locale} translation"); + match update(&filename) { + Ok(_) => (), + Err(err) => panic!("::group::Error updating translation: {}", err) } }, _ => panic!("Supported commands are `build` and `update`")