i18n_again_support/
lib.rs1use fs::File;
2use fs_err as fs;
3use glob::glob;
4use std::collections::HashMap;
5use std::io::prelude::*;
6use std::io::Write;
7
8mod prepare;
9pub mod errors;
10
11pub use crate::errors::*;
12pub use crate::prepare::*;
13
14pub type Locale = String;
15pub type Value = serde_json::Value;
16pub type Translations = HashMap<Locale, Value>;
17
18pub fn load_from_dirs(locale_dir: impl AsRef<std::path::Path>) -> Result<()> {
23 let locale_path = locale_dir.as_ref();
24 let translations = locales_yaml_files_to_translation_map(locale_path)?;
25 let translations = serialize(translations)?;
26
27 fs::write("foo-bar-baz", translations)?;
28
29 Ok(())
30}
31
32pub type TranslationPath = String;
36
37pub type TranslationMap = HashMap<TranslationPath, HashMap<Locale, String>>;
40
41pub fn deserialize(bytes: &[u8]) -> Result<TranslationMap> {
42 let tmap: TranslationMap = postcard::from_bytes(bytes)?;
43 Ok(tmap)
44}
45
46pub fn serialize(text2translations: TranslationMap) -> Result<Vec<u8>> {
47 let bytes = postcard::to_allocvec(&text2translations)?;
48 Ok(bytes)
49}
50
51pub fn merge_value(a: &mut Value, b: &Value) {
56 match (a, b) {
57 (&mut Value::Object(ref mut a), &Value::Object(ref b)) => {
58 for (k, v) in b {
59 merge_value(a.entry(k.clone()).or_insert(Value::Null), v);
60 }
61 }
62 (a, b) => {
63 *a = b.clone();
64 }
65 }
66}
67
68fn extract_yaml_content(
69 yaml_content: impl AsRef<str>,
70 trans_map: &mut HashMap<TranslationPath, serde_json::Value>,
71) -> Result<()> {
72 let trs: Translations = serde_yaml::from_str(yaml_content.as_ref())?;
74
75 eprintln!("cargo:warning: foo: -- {:?}", &trs);
76
77 trs.into_iter().for_each(|(tp, translations)| {
78 trans_map
79 .entry(tp)
80 .and_modify(|translations_old| merge_value(translations_old, &translations))
81 .or_insert(translations);
82 });
83 Ok(())
84}
85
86fn trans_map_voodoo(trans_map: HashMap<TranslationPath, serde_json::Value>) -> TranslationMap {
87 let mut tp2trans_per_locale = TranslationMap::new();
88
89 trans_map.into_iter().for_each(|(ref locale, trs)| {
98 let new_vars = extract_vars(locale.as_str(), &trs);
99 let new_vars_iter = new_vars.into_iter().filter_map(|(k, v)| {
100 k.strip_prefix(&(locale.to_owned() + "."))
101 .map(move |k| (k.to_string(), v))
102 });
103 for (tp, translation) in new_vars_iter {
104 tp2trans_per_locale
105 .entry(tp)
106 .or_default()
107 .insert(locale.clone(), translation);
108 }
109 });
110 tp2trans_per_locale
111}
112
113pub fn locales_yaml_files_to_translation_map(
115 locales_dir: &std::path::Path,
116) -> Result<TranslationMap> {
117 let mut trans_map = Translations::new();
118
119 let path_pattern = format!("{}/**/*.yml", locales_dir.display());
120
121 println!("cargo:i18n-locale={}", &path_pattern);
122
123 let paths = glob(&path_pattern).expect("Failed to read glob pattern");
124 for maybe_path in paths {
125 let path = if let Ok(path) = maybe_path {
126 path
127 } else {
128 continue;
129 };
130 println!("cargo:i18n-load={}", &path.display());
131
132 let file = File::open(path).expect("Failed to open the YAML file");
133 let mut reader = std::io::BufReader::new(file);
134 let mut content = String::new();
135
136 reader.read_to_string(&mut content)?;
137 extract_yaml_content(content, &mut trans_map)?;
138 }
139
140 let tp2trans_per_locale = trans_map_voodoo(trans_map);
141
142 Ok(tp2trans_per_locale)
143}
144
145pub fn extract_vars(prefix: &str, trs: &Value) -> HashMap<String, String> {
149 let mut v = HashMap::<String, String>::new();
150 let prefix = prefix.to_string();
151
152 match &trs {
153 serde_json::Value::String(s) => {
154 v.insert(prefix, s.to_string());
155 }
156 serde_json::Value::Object(o) => {
157 for (k, vv) in o {
158 let key = format!("{}.{}", prefix, k);
159 v.extend(extract_vars(key.as_str(), vv));
160 }
161 }
162 serde_json::Value::Null => {
163 v.insert(prefix, "".into());
164 }
165 serde_json::Value::Bool(s) => {
166 v.insert(prefix, format!("{}", s));
167 }
168 serde_json::Value::Number(s) => {
169 v.insert(prefix, format!("{}", s));
170 }
171 serde_json::Value::Array(_) => {
172 v.insert(prefix, "".into());
173 }
174 }
175
176 v
177}
178
179#[cfg(test)]
180mod tests;