1use once_cell::sync::Lazy;
2use std::collections::HashMap;
3use std::fs;
4use std::path::Path;
5use std::sync::RwLock;
6
7pub use self::vendor::classic::parse_translations;
8
9#[cfg(feature = "fluent")]
10pub use fluent;
11
12#[cfg(feature = "fluent")]
13pub use fluent_bundle;
14
15mod vendor;
16
17struct Config {
22 language: String,
23 fallback: String,
24 #[cfg(feature = "fluent")]
25 use_isolating: bool,
26}
27
28#[allow(clippy::derivable_impls)]
29impl Default for Config {
30 fn default() -> Self {
31 Self {
32 language: String::new(),
33 fallback: String::new(),
34 #[cfg(feature = "fluent")]
35 use_isolating: true,
36 }
37 }
38}
39
40static CONFIG: Lazy<RwLock<Config>> = Lazy::new(|| RwLock::new(Config::default()));
42
43pub fn set_language(locale: &str) {
48 CONFIG.write().unwrap().language = locale.to_string();
49}
50
51pub fn get_language() -> String {
52 CONFIG.read().unwrap().language.clone()
53}
54
55pub fn set_fallback(locale: &str) {
56 CONFIG.write().unwrap().fallback = locale.to_string();
57}
58
59pub fn get_fallback() -> String {
60 CONFIG.read().unwrap().fallback.clone()
61}
62
63#[cfg(feature = "fluent")]
68pub fn set_use_isolating(value: bool) {
69 CONFIG.write().unwrap().use_isolating = value;
70}
71
72#[cfg(feature = "fluent")]
73pub fn get_use_isolating() -> bool {
74 CONFIG.read().unwrap().use_isolating
75}
76
77#[cfg(feature = "fluent")]
82pub fn languages() -> Vec<String> {
83 vendor::fluent::languages()
84}
85
86#[cfg(not(feature = "fluent"))]
87pub fn languages() -> Vec<String> {
88 vendor::classic::languages()
89}
90
91#[cfg(not(feature = "fluent"))]
101pub fn load_translations_from_map(
102 language: impl AsRef<str>,
103 translations: HashMap<String, String>,
104) -> Result<(), String> {
105 vendor::classic::load_translations_from_map(language, translations);
106 Ok(())
107}
108
109#[cfg(feature = "fluent")]
110pub fn load_translations_from_map(
111 _language: impl AsRef<str>,
112 _translations: HashMap<String, String>,
113) -> Result<(), String> {
114 Err(
115 "load_translations_from_map is not supported in fluent mode; \
116 use load_translations_from_text with raw .ftl content instead"
117 .to_string(),
118 )
119}
120
121#[cfg(feature = "fluent")]
126pub fn load_translations_from_text(
127 language: impl AsRef<str>,
128 content: impl AsRef<str>,
129) -> Result<(), String> {
130 vendor::fluent::load_translations_from_text(
131 language.as_ref(),
132 content.as_ref(),
133 get_use_isolating(),
134 )
135}
136
137#[cfg(not(feature = "fluent"))]
138pub fn load_translations_from_text(
139 language: impl AsRef<str>,
140 content: impl AsRef<str>,
141) -> Result<(), String> {
142 vendor::classic::load_translations_from_text(language, content)
143}
144
145pub fn load_translations_from_path(path: impl AsRef<str>) -> Result<(), String> {
152 let path_ref = Path::new(path.as_ref());
153 let mut files = vec![];
154
155 if path_ref.is_file() {
156 files.push(path_ref.to_path_buf());
157 } else {
158 let read_dir = match fs::read_dir(path_ref) {
159 Ok(v) => v,
160 Err(e) => return Err(format!("{:?}", e)),
161 };
162 for entry in read_dir {
163 let path_file = match entry {
164 Ok(dir_entry) => dir_entry.path(),
165 Err(e) => {
166 log::warn!("failed to read directory entry: {:?}", e);
167 continue;
168 },
169 };
170 let allowed = path_file
171 .extension()
172 .map(|ext| {
173 let ext = ext.to_string_lossy().to_lowercase();
174 ext == "egl" || ext == "ftl"
175 })
176 .unwrap_or(false);
177 if !allowed {
178 continue;
179 }
180 files.push(path_file);
181 }
182 }
183
184 for file in files {
185 let name = match file.file_stem() {
186 Some(v) => v.to_string_lossy().to_string(),
187 None => continue,
188 };
189 match fs::read_to_string(&file) {
190 Ok(content) => load_translations_from_text(name, content)?,
191 Err(e) => return Err(format!("{:?}", e)),
192 }
193 }
194 Ok(())
195}
196
197#[cfg(not(feature = "fluent"))]
202pub fn translate_classic(key: &str, args: &HashMap<&str, String>) -> String {
203 let language = get_language();
204 let fallback = get_fallback();
205 vendor::classic::translate(language, fallback, key, args)
206}
207
208#[cfg(feature = "fluent")]
209pub fn translate_fluent(key: &str, args: &crate::fluent::FluentArgs) -> String {
210 let language = get_language();
211 let fallback = get_fallback();
212 vendor::fluent::translate(language, fallback, key, args)
213}
214
215#[cfg(not(feature = "fluent"))]
220#[macro_export]
221macro_rules! tr {
222 ($key:expr, {$($name:ident: $val:expr),*}) => {{
223 let mut args = std::collections::HashMap::new();
224 $(
225 args.insert(stringify!($name), $val.to_string());
226 )*
227 $crate::translate_classic($key, &args)
228 }};
229 ($key:expr) => {{
230 $crate::translate_classic($key, &std::collections::HashMap::new())
231 }};
232}
233
234#[cfg(feature = "fluent")]
235#[macro_export]
236macro_rules! tr {
237 ($key:expr, {$($name:ident: $val:expr),*}) => {{
238 let mut args = $crate::fluent::FluentArgs::new();
239 $(
240 args.set(stringify!($name), $val);
241 )*
242 $crate::translate_fluent($key, &args)
243 }};
244 ($key:expr) => {{
245 $crate::translate_fluent($key, &$crate::fluent::FluentArgs::new())
246 }};
247}