es_fluent_cli/ftl/
locale.rs1use crate::core::CrateInfo;
7use crate::utils::get_all_locales;
8use anyhow::{Context as _, Result};
9use es_fluent_toml::I18nConfig;
10use std::path::PathBuf;
11
12#[derive(Clone, Debug)]
17pub struct LocaleContext {
18 pub assets_dir: PathBuf,
20 pub fallback: String,
22 pub locales: Vec<String>,
24 pub crate_name: String,
26}
27
28impl LocaleContext {
29 pub fn from_crate(krate: &CrateInfo, all: bool) -> Result<Self> {
34 let config = I18nConfig::read_from_path(&krate.i18n_config_path)
35 .with_context(|| format!("Failed to read {}", krate.i18n_config_path.display()))?;
36
37 let assets_dir = krate.manifest_dir.join(&config.assets_dir);
38
39 let locales = if all {
40 get_all_locales(&assets_dir)?
41 } else {
42 vec![config.fallback_language.clone()]
43 };
44
45 Ok(Self {
46 assets_dir,
47 fallback: config.fallback_language,
48 locales,
49 crate_name: krate.name.clone(),
50 })
51 }
52
53 pub fn ftl_path(&self, locale: &str) -> PathBuf {
55 self.assets_dir
56 .join(locale)
57 .join(format!("{}.ftl", self.crate_name))
58 }
59
60 pub fn locale_dir(&self, locale: &str) -> PathBuf {
62 self.assets_dir.join(locale)
63 }
64
65 pub fn iter(&self) -> impl Iterator<Item = (&str, PathBuf)> {
69 self.locales.iter().filter_map(|locale| {
70 let locale_dir = self.locale_dir(locale);
71 if locale_dir.exists() {
72 Some((locale.as_str(), self.ftl_path(locale)))
73 } else {
74 None
75 }
76 })
77 }
78
79 pub fn iter_non_fallback(&self) -> impl Iterator<Item = (&str, PathBuf)> {
83 self.locales.iter().filter_map(|locale| {
84 if locale == &self.fallback {
85 return None;
86 }
87 let locale_dir = self.locale_dir(locale);
88 if locale_dir.exists() {
89 Some((locale.as_str(), self.ftl_path(locale)))
90 } else {
91 None
92 }
93 })
94 }
95
96 pub fn is_fallback(&self, locale: &str) -> bool {
98 locale == self.fallback
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105 use std::fs;
106 use tempfile::tempdir;
107
108 fn create_test_crate() -> (tempfile::TempDir, CrateInfo) {
109 let temp_dir = tempdir().unwrap();
110 let assets = temp_dir.path().join("i18n");
111 fs::create_dir(&assets).unwrap();
112 fs::create_dir(assets.join("en")).unwrap();
113 fs::create_dir(assets.join("fr")).unwrap();
114 fs::create_dir(assets.join("de")).unwrap();
115
116 let config_path = temp_dir.path().join("i18n.toml");
117 fs::write(
118 &config_path,
119 "fallback_language = \"en\"\nassets_dir = \"i18n\"\n",
120 )
121 .unwrap();
122
123 let krate = CrateInfo {
124 name: "test-crate".to_string(),
125 manifest_dir: temp_dir.path().to_path_buf(),
126 src_dir: temp_dir.path().join("src"),
127 i18n_config_path: config_path,
128 ftl_output_dir: assets.join("en"),
129 has_lib_rs: true,
130 fluent_features: Vec::new(),
131 };
132
133 (temp_dir, krate)
134 }
135
136 #[test]
137 fn test_locale_context_fallback_only() {
138 let (_temp, krate) = create_test_crate();
139 let ctx = LocaleContext::from_crate(&krate, false).unwrap();
140
141 assert_eq!(ctx.locales.len(), 1);
142 assert_eq!(ctx.locales[0], "en");
143 assert_eq!(ctx.fallback, "en");
144 }
145
146 #[test]
147 fn test_locale_context_all_locales() {
148 let (_temp, krate) = create_test_crate();
149 let ctx = LocaleContext::from_crate(&krate, true).unwrap();
150
151 assert_eq!(ctx.locales.len(), 3);
152 assert!(ctx.locales.contains(&"en".to_string()));
153 assert!(ctx.locales.contains(&"fr".to_string()));
154 assert!(ctx.locales.contains(&"de".to_string()));
155 }
156
157 #[test]
158 fn test_ftl_path() {
159 let (_temp, krate) = create_test_crate();
160 let ctx = LocaleContext::from_crate(&krate, false).unwrap();
161
162 let path = ctx.ftl_path("en");
163 assert!(path.ends_with("i18n/en/test-crate.ftl"));
164 }
165
166 #[test]
167 fn test_iter_non_fallback() {
168 let (_temp, krate) = create_test_crate();
169 let ctx = LocaleContext::from_crate(&krate, true).unwrap();
170
171 let non_fallback: Vec<_> = ctx.iter_non_fallback().map(|(l, _)| l).collect();
172 assert!(!non_fallback.contains(&"en"));
173 assert!(non_fallback.contains(&"fr"));
174 assert!(non_fallback.contains(&"de"));
175 }
176}