Skip to main content

component_qa/
i18n_bundle.rs

1use std::collections::BTreeMap;
2use std::fs;
3use std::path::Path;
4
5use greentic_types::cbor::canonical;
6
7// Locale -> (key -> translated message)
8pub type LocaleBundle = BTreeMap<String, BTreeMap<String, String>>;
9
10// Reads `assets/i18n/*.json` locale maps and returns stable BTreeMap ordering.
11// Extend here if you need stricter file validation rules.
12pub fn load_locale_files(dir: &Path) -> Result<LocaleBundle, String> {
13    let mut locales = LocaleBundle::new();
14    if !dir.exists() {
15        return Ok(locales);
16    }
17
18    let entries = fs::read_dir(dir).map_err(|err| err.to_string())?;
19    for entry in entries {
20        let entry = entry.map_err(|err| err.to_string())?;
21        let path = entry.path();
22        if path.extension().and_then(|ext| ext.to_str()) != Some("json") {
23            continue;
24        }
25        let Some(stem) = path.file_stem().and_then(|stem| stem.to_str()) else {
26            continue;
27        };
28        // locales.json is metadata, not a translation dictionary.
29        if stem == "locales" {
30            continue;
31        }
32        let raw = fs::read_to_string(&path).map_err(|err| err.to_string())?;
33        let map: BTreeMap<String, String> =
34            serde_json::from_str(&raw).map_err(|err| err.to_string())?;
35        locales.insert(stem.to_string(), map);
36    }
37
38    Ok(locales)
39}
40
41// Produces canonical CBOR bytes for reproducible build embedding.
42pub fn pack_locales_to_cbor(locales: &LocaleBundle) -> Result<Vec<u8>, String> {
43    canonical::to_canonical_cbor_allow_floats(locales).map_err(|err| err.to_string())
44}
45
46#[allow(dead_code)]
47// Runtime decode helper used by src/i18n.rs.
48pub fn unpack_locales_from_cbor(bytes: &[u8]) -> Result<LocaleBundle, String> {
49    canonical::from_cbor(bytes).map_err(|err| err.to_string())
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55
56    #[test]
57    fn pack_roundtrip_contains_en() {
58        let mut locales = LocaleBundle::new();
59        let mut en = BTreeMap::new();
60        en.insert("qa.install.title".to_string(), "Install".to_string());
61        locales.insert("en".to_string(), en);
62
63        let cbor = pack_locales_to_cbor(&locales).expect("pack locales");
64        let decoded = unpack_locales_from_cbor(&cbor).expect("decode locales");
65
66        assert!(decoded.contains_key("en"));
67    }
68}