//! The app's own words (src/i18n.rs): the English messages, compiled in, //! and the index of the other languages' catalogues — each one's file name //! (named by its content, as the site serves it under models/ui/), size, //! SHA-256, and how many of the English messages it has. use std::collections::BTreeSet; use std::fmt::Write as _; use std::path::Path; use sha2::{Digest, Sha256}; /// The ids of the messages in Fluent source (`id = …` at the start of a /// line; terms, `-id`, are not messages). fn ids(source: &str) -> BTreeSet { source .lines() .filter_map(|line| { let (id, _) = line.split_once('=')?; let id = id.trim_end(); let mut chars = id.chars(); (chars.next().is_some_and(|c| c.is_ascii_alphabetic()) && chars.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')) .then(|| id.to_string()) }) .collect() } fn main() { let dir = Path::new("i18n"); println!("cargo:rerun-if-changed=i18n"); let out = std::path::PathBuf::from(std::env::var("OUT_DIR").expect("OUT_DIR")); let mut english_files: Vec<_> = std::fs::read_dir(dir.join("en")) .expect("i18n/en") .filter_map(Result::ok) .map(|e| e.path()) .filter(|p| p.extension().is_some_and(|x| x == "ftl")) .collect(); english_files.sort(); let mut english = String::new(); for path in &english_files { println!("cargo:rerun-if-changed={}", path.display()); let text = std::fs::read_to_string(path).expect("an English catalogue file"); english.push_str(&text); if !english.ends_with('\n') { english.push('\n'); } english.push('\n'); } let english_ids = ids(&english); std::fs::write(out.join("ui_en.ftl"), &english).expect("write ui_en.ftl"); let mut languages: Vec<_> = std::fs::read_dir(dir) .expect("i18n") .filter_map(Result::ok) .map(|e| e.path()) .filter(|p| p.is_file() && p.extension().is_some_and(|x| x == "ftl")) .collect(); languages.sort(); let mut index = String::from( "/// A language's catalogue of the app's words.\n\ #[derive(Debug, Clone, Copy, PartialEq, Eq)]\n\ pub struct Catalogue {\n\ \x20 /// ISO 639-1 code.\n pub code: &'static str,\n\ \x20 /// Served as `models/ui/{file}`: `{code}.{first 12 hex of sha256}.ftl`.\n pub file: &'static str,\n\ \x20 pub sha256: &'static str,\n pub bytes: usize,\n\ \x20 /// How many of the English messages it has (agreed by two readers).\n pub messages: usize,\n}\n\n\ pub const CATALOGUES: &[Catalogue] = &[\n", ); for path in &languages { println!("cargo:rerun-if-changed={}", path.display()); let code = path.file_stem().and_then(|s| s.to_str()).expect("a catalogue's name is its language code"); let bytes = std::fs::read(path).expect("a catalogue"); let sha = Sha256::digest(&bytes).iter().fold(String::new(), |mut s, b| { let _ = write!(s, "{b:02x}"); s }); let text = String::from_utf8(bytes.clone()).expect("a catalogue is UTF-8"); let messages = ids(&text).intersection(&english_ids).count(); let _ = writeln!( index, " Catalogue {{ code: {code:?}, file: \"{code}.{}.ftl\", sha256: {sha:?}, bytes: {}, messages: {messages} }},", &sha[..12], bytes.len() ); } index.push_str("];\n\n/// How many messages the English has.\n"); let _ = writeln!(index, "pub const ENGLISH_MESSAGES: usize = {};", english_ids.len()); std::fs::write(out.join("ui_index.rs"), index).expect("write ui_index.rs"); }