1use std::{env, fs, io::Write as _, path::Path};
2
3use fluent_syntax::parser;
4use unic_langid::LanguageIdentifier;
5
6#[allow(clippy::too_many_lines)]
24pub fn generate_static_cache(locales_dir_path: &str) {
25 println!("cargo:rerun-if-changed={locales_dir_path}");
26
27 let out_dir = env::var("OUT_DIR").expect("OUT_DIR not set");
28 let dest_path = Path::new(&out_dir).join("static_cache.rs");
29 let mut file = fs::File::create(&dest_path).expect("Failed to create static_cache.rs");
30
31 writeln!(&mut file, "// @generated by fluent-zero-build").unwrap();
32
33 let locales_path = Path::new(locales_dir_path);
34 if !locales_path.exists() {
35 return;
36 }
37
38 let mut bundle_entries: Vec<(String, String)> = Vec::new();
39 let mut cache_root_entries: Vec<(String, String)> = Vec::new();
40
41 for entry in fs::read_dir(locales_path).unwrap() {
42 let entry = entry.unwrap();
43 let path = entry.path();
44
45 if path.is_dir() {
46 let dir_name = path.file_name().unwrap().to_str().unwrap();
47 let lang_id: LanguageIdentifier = match dir_name.parse() {
48 Ok(id) => id,
49 Err(_) => continue,
50 };
51 let lang_key = lang_id.to_string();
52 let sanitized_lang = lang_key.replace('-', "_").to_uppercase();
53
54 let cache_name = format!("CACHE_{sanitized_lang}");
56 let bundle_name = format!("BUNDLE_{sanitized_lang}");
57
58 let mut combined_ftl_source = String::new();
59 let mut cache_entries: Vec<(String, String)> = Vec::new();
61
62 for file_entry in fs::read_dir(&path).unwrap() {
63 let file_entry = file_entry.unwrap();
64 let file_path = file_entry.path();
65
66 if file_path.extension().is_some_and(|ext| ext == "ftl") {
67 println!("cargo:rerun-if-changed={}", file_path.display());
68 let source = fs::read_to_string(&file_path).unwrap();
69 combined_ftl_source.push_str(&source);
70 combined_ftl_source.push('\n');
71
72 let ast = parser::parse(source).expect("Failed to parse FTL");
73 for entry in ast.body {
74 if let fluent_syntax::ast::Entry::Message(msg) = entry {
75 if let Some(pattern) = &msg.value
82 && pattern.elements.len() == 1
83 && let fluent_syntax::ast::PatternElement::TextElement { value } =
84 &pattern.elements[0]
85 && !value.contains('\\')
86 {
87 cache_entries.push((
90 msg.id.name.clone(),
91 format!("::fluent_zero::CacheEntry::Static(\"{value}\")"),
92 ));
93 } else {
94 cache_entries.push((
97 msg.id.name.clone(),
98 "::fluent_zero::CacheEntry::Dynamic".to_string(),
99 ));
100 }
101 }
102 }
103 }
104 }
105
106 let escaped_ftl = format!("{combined_ftl_source:?}");
110 let bundle_init_code = format!(
111 "std::sync::LazyLock::new(|| {{
112 let lang: ::fluent_zero::LanguageIdentifier = \"{lang_key}\".parse().unwrap();
113 let mut bundle = ::fluent_zero::ConcurrentFluentBundle::new_concurrent(vec![lang]);
114 let res = ::fluent_zero::FluentResource::try_new({escaped_ftl}.to_string()).expect(\"FTL Error\");
115 bundle.add_resource(res).expect(\"Resource Error\");
116 bundle
117 }})"
118 );
119
120 writeln!(&mut file,
121 "static {bundle_name}: std::sync::LazyLock<::fluent_zero::ConcurrentFluentBundle<::fluent_zero::FluentResource>> = {bundle_init_code};"
122 ).unwrap();
123
124 bundle_entries.push((lang_key.clone(), format!("&{bundle_name}")));
125
126 let mut map = phf_codegen::Map::new();
128 map.phf_path("::fluent_zero::phf");
129 for (k, v) in &cache_entries {
130 map.entry(k.as_str(), v.as_str());
131 }
132
133 writeln!(
134 &mut file,
135 "static {}: ::fluent_zero::phf::Map<&'static str, ::fluent_zero::CacheEntry> = {};",
136 cache_name,
137 map.build()
138 )
139 .unwrap();
140 cache_root_entries.push((lang_key.clone(), cache_name));
141 }
142 }
143
144 let mut root_map = phf_codegen::Map::new();
148 root_map.phf_path("::fluent_zero::phf");
149 for (l, v) in &cache_root_entries {
150 root_map.entry(l.as_str(), format!("&{v}"));
151 }
152 writeln!(&mut file,
153 "pub static CACHE: ::fluent_zero::phf::Map<&'static str, &'static ::fluent_zero::phf::Map<&'static str, ::fluent_zero::CacheEntry>> = {};",
154 root_map.build()
155 ).unwrap();
156
157 let mut bundle_map = phf_codegen::Map::new();
159 bundle_map.phf_path("::fluent_zero::phf");
160 for (l, c) in &bundle_entries {
161 bundle_map.entry(l.as_str(), c.as_str());
162 }
163 writeln!(&mut file,
164 "pub static LOCALES: ::fluent_zero::phf::Map<&'static str, &'static std::sync::LazyLock<::fluent_zero::ConcurrentFluentBundle<::fluent_zero::FluentResource>>> = {};",
165 bundle_map.build()
166 ).unwrap();
167}