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