1#![doc = include_str!("../README.md")]
2
3#[cfg(feature = "derive")]
4pub use es_fluent_derive::{EsFluent, EsFluentChoice, EsFluentKv, EsFluentThis};
5
6#[doc(hidden)]
7pub use es_fluent_manager_core::{FluentManager, I18nModule, LocalizationError, Localizer};
8
9#[doc(hidden)]
10pub use fluent_bundle::FluentValue;
11
12#[doc(hidden)]
13pub use inventory as __inventory;
14
15#[doc(hidden)]
16pub use rust_embed as __rust_embed;
17
18#[doc(hidden)]
19pub use es_fluent_manager_core as __manager_core;
20
21#[doc(hidden)]
22pub use es_fluent_core as __core;
23
24#[doc(hidden)]
25pub use unic_langid;
26
27mod traits;
28pub use traits::{EsFluentChoice, FluentDisplay, ThisFtl, ToFluentString};
29
30use std::sync::{Arc, OnceLock, RwLock};
31
32#[doc(hidden)]
33static CONTEXT: OnceLock<Arc<RwLock<FluentManager>>> = OnceLock::new();
34
35#[doc(hidden)]
36static CUSTOM_LOCALIZER: OnceLock<
37 Box<
38 dyn Fn(&str, Option<&std::collections::HashMap<&str, FluentValue>>) -> Option<String>
39 + Send
40 + Sync,
41 >,
42> = OnceLock::new();
43
44#[doc(hidden)]
53pub fn set_context(manager: FluentManager) {
54 CONTEXT
55 .set(Arc::new(RwLock::new(manager)))
56 .map_err(|_| "Context already set")
57 .expect("Failed to set context");
58}
59
60#[doc(hidden)]
69pub fn set_shared_context(manager: Arc<RwLock<FluentManager>>) {
70 CONTEXT
71 .set(manager)
72 .map_err(|_| "Context already set")
73 .expect("Failed to set shared context");
74}
75
76#[doc(hidden)]
86pub fn set_custom_localizer<F>(localizer: F)
87where
88 F: Fn(&str, Option<&std::collections::HashMap<&str, FluentValue>>) -> Option<String>
89 + Send
90 + Sync
91 + 'static,
92{
93 CUSTOM_LOCALIZER
94 .set(Box::new(localizer))
95 .map_err(|_| "Custom localizer already set")
96 .expect("Failed to set custom localizer");
97}
98
99#[doc(hidden)]
101pub fn update_context<F>(f: F)
102where
103 F: FnOnce(&mut FluentManager),
104{
105 if let Some(context_arc) = CONTEXT.get() {
106 let mut context = context_arc
107 .write()
108 .expect("Failed to acquire write lock on context");
109 f(&mut context);
110 }
111}
112
113#[doc(hidden)]
122pub fn localize<'a>(
123 id: &str,
124 args: Option<&std::collections::HashMap<&str, FluentValue<'a>>>,
125) -> String {
126 if let Some(custom_localizer) = CUSTOM_LOCALIZER.get()
127 && let Some(message) = custom_localizer(id, args)
128 {
129 return message;
130 }
131
132 if let Some(context_arc) = CONTEXT.get() {
133 let context = context_arc
134 .read()
135 .expect("Failed to acquire read lock on context");
136
137 if let Some(message) = context.localize(id, args) {
138 return message;
139 }
140 }
141
142 log::warn!("Translation for '{}' not found or context not set.", id);
143 id.to_string()
144}
145
146#[cfg(feature = "generate")]
148mod generate {
149 use std::path::PathBuf;
150
151 pub use es_fluent_generate::FluentParseMode;
152 pub use es_fluent_generate::error::FluentGenerateError;
153
154 #[derive(Debug)]
156 pub enum GeneratorError {
157 Config(es_fluent_toml::I18nConfigError),
159 CrateName(String),
161 Generate(FluentGenerateError),
163 }
164
165 impl std::fmt::Display for GeneratorError {
166 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
167 match self {
168 Self::Config(e) => write!(f, "Configuration error: {}", e),
169 Self::CrateName(e) => write!(f, "Failed to detect crate name: {}", e),
170 Self::Generate(e) => write!(f, "Generation error: {}", e),
171 }
172 }
173 }
174
175 impl std::error::Error for GeneratorError {}
176
177 impl From<es_fluent_toml::I18nConfigError> for GeneratorError {
178 fn from(e: es_fluent_toml::I18nConfigError) -> Self {
179 Self::Config(e)
180 }
181 }
182
183 impl From<FluentGenerateError> for GeneratorError {
184 fn from(e: FluentGenerateError) -> Self {
185 Self::Generate(e)
186 }
187 }
188
189 #[derive(bon::Builder)]
216 pub struct EsFluentGenerator {
217 #[builder(default)]
220 mode: FluentParseMode,
221
222 #[builder(into)]
224 crate_name: Option<String>,
225
226 #[builder(into)]
228 output_path: Option<PathBuf>,
229
230 #[builder(into)]
233 crate_root: Option<PathBuf>,
234 }
235
236 impl EsFluentGenerator {
237 pub fn generate(&self) -> Result<(), GeneratorError> {
239 let crate_name = match &self.crate_name {
240 Some(name) => name.clone(),
241 None => Self::detect_crate_name()?,
242 };
243
244 let output_path = match &self.output_path {
245 Some(path) => path.clone(),
246 None => {
247 let config = es_fluent_toml::I18nConfig::read_from_manifest_dir()?;
248 config.assets_dir.join(&config.fallback_language)
249 },
250 };
251
252 let type_infos = if let Some(root) = &self.crate_root {
253 crate::__core::registry::get_all_ftl_type_infos()
255 .into_iter()
256 .filter(|info| {
257 info.file_path
258 .as_ref()
259 .is_some_and(|path| path.starts_with(root.to_str().unwrap_or_default()))
260 })
261 .collect::<Vec<_>>()
262 } else {
263 let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").map_err(|_| {
266 GeneratorError::CrateName("CARGO_MANIFEST_DIR not set".to_string())
267 })?;
268
269 let src_prefix = cargo_metadata::MetadataCommand::new()
271 .exec()
272 .ok()
273 .and_then(|metadata| {
274 let workspace_root = metadata.workspace_root.as_std_path();
275 let manifest_dir_path = std::path::Path::new(&manifest_dir);
276 manifest_dir_path
277 .strip_prefix(workspace_root)
278 .ok()
279 .map(|rel| format!("{}/src/", rel.display()))
280 })
281 .unwrap_or_else(|| "src/".to_string());
282
283 crate::__core::registry::get_all_ftl_type_infos()
284 .into_iter()
285 .filter(|info| {
286 info.file_path
287 .as_ref()
288 .is_some_and(|path| path.starts_with(&src_prefix))
289 })
290 .collect()
291 };
292
293 log::info!(
294 "Generating FTL files for {} types in crate '{}'",
295 type_infos.len(),
296 crate_name
297 );
298
299 es_fluent_generate::generate(&crate_name, output_path, type_infos, self.mode.clone())?;
300
301 Ok(())
302 }
303
304 fn detect_crate_name() -> Result<String, GeneratorError> {
306 let manifest_dir = std::env::var("CARGO_MANIFEST_DIR")
307 .map_err(|_| GeneratorError::CrateName("CARGO_MANIFEST_DIR not set".to_string()))?;
308 let manifest_path = std::path::PathBuf::from(&manifest_dir).join("Cargo.toml");
309
310 cargo_metadata::MetadataCommand::new()
311 .exec()
312 .ok()
313 .and_then(|metadata| {
314 metadata
315 .packages
316 .iter()
317 .find(|pkg| pkg.manifest_path == manifest_path)
318 .map(|pkg| pkg.name.to_string())
319 })
320 .or_else(|| std::env::var("CARGO_PKG_NAME").ok())
321 .ok_or_else(|| {
322 GeneratorError::CrateName("Could not determine crate name".to_string())
323 })
324 }
325 }
326}
327
328#[cfg(feature = "generate")]
329pub use generate::{EsFluentGenerator, FluentParseMode, GeneratorError};
330
331#[cfg(feature = "generate")]
332pub use es_fluent_toml;