1use crate::{
2 cache::{AsyncCache, Cache},
3 env::LocalesProvider,
4 errors::LocalizationError,
5 generator::{BundleGenerator, BundleIterator, BundleStream},
6 types::{L10nAttribute, L10nKey, L10nMessage, ResourceId},
7};
8use fluent_bundle::{FluentArgs, FluentBundle, FluentError};
9use rustc_hash::FxHashSet;
10use std::borrow::Cow;
11
12pub enum BundlesInner<G>
13where
14 G: BundleGenerator,
15{
16 Iter(Cache<G::Iter, G::Resource>),
17 Stream(AsyncCache<G::Stream, G::Resource>),
18}
19
20pub struct Bundles<G>(BundlesInner<G>)
21where
22 G: BundleGenerator;
23
24impl<G> Bundles<G>
25where
26 G: BundleGenerator,
27 G::Iter: BundleIterator,
28{
29 pub fn prefetch_sync(&self) {
30 match &self.0 {
31 BundlesInner::Iter(iter) => iter.prefetch(),
32 BundlesInner::Stream(_) => panic!("Can't prefetch a sync bundle set asynchronously"),
33 }
34 }
35}
36
37impl<G> Bundles<G>
38where
39 G: BundleGenerator,
40 G::Stream: BundleStream,
41{
42 pub async fn prefetch_async(&self) {
43 match &self.0 {
44 BundlesInner::Iter(_) => panic!("Can't prefetch a async bundle set synchronously"),
45 BundlesInner::Stream(stream) => stream.prefetch().await,
46 }
47 }
48}
49
50impl<G> Bundles<G>
51where
52 G: BundleGenerator,
53{
54 pub fn new<P>(sync: bool, res_ids: FxHashSet<ResourceId>, generator: &G, provider: &P) -> Self
55 where
56 G: BundleGenerator<LocalesIter = P::Iter>,
57 P: LocalesProvider,
58 {
59 Self(if sync {
60 BundlesInner::Iter(Cache::new(
61 generator.bundles_iter(provider.locales(), res_ids),
62 ))
63 } else {
64 BundlesInner::Stream(AsyncCache::new(
65 generator.bundles_stream(provider.locales(), res_ids),
66 ))
67 })
68 }
69
70 pub async fn format_value<'l>(
71 &'l self,
72 id: &'l str,
73 args: Option<&'l FluentArgs<'_>>,
74 errors: &mut Vec<LocalizationError>,
75 ) -> Option<Cow<'l, str>> {
76 match &self.0 {
77 BundlesInner::Iter(cache) => Self::format_value_from_iter(cache, id, args, errors),
78 BundlesInner::Stream(stream) => {
79 Self::format_value_from_stream(stream, id, args, errors).await
80 }
81 }
82 }
83
84 pub async fn format_values<'l>(
85 &'l self,
86 keys: &'l [L10nKey<'l>],
87 errors: &mut Vec<LocalizationError>,
88 ) -> Vec<Option<Cow<'l, str>>> {
89 match &self.0 {
90 BundlesInner::Iter(cache) => Self::format_values_from_iter(cache, keys, errors),
91 BundlesInner::Stream(stream) => {
92 Self::format_values_from_stream(stream, keys, errors).await
93 }
94 }
95 }
96
97 pub async fn format_messages<'l>(
98 &'l self,
99 keys: &'l [L10nKey<'l>],
100 errors: &mut Vec<LocalizationError>,
101 ) -> Vec<Option<L10nMessage<'l>>> {
102 match &self.0 {
103 BundlesInner::Iter(cache) => Self::format_messages_from_iter(cache, keys, errors),
104 BundlesInner::Stream(stream) => {
105 Self::format_messages_from_stream(stream, keys, errors).await
106 }
107 }
108 }
109
110 pub fn format_value_sync<'l>(
111 &'l self,
112 id: &'l str,
113 args: Option<&'l FluentArgs>,
114 errors: &mut Vec<LocalizationError>,
115 ) -> Result<Option<Cow<'l, str>>, LocalizationError> {
116 match &self.0 {
117 BundlesInner::Iter(cache) => Ok(Self::format_value_from_iter(cache, id, args, errors)),
118 BundlesInner::Stream(_) => Err(LocalizationError::SyncRequestInAsyncMode),
119 }
120 }
121
122 pub fn format_values_sync<'l>(
123 &'l self,
124 keys: &'l [L10nKey<'l>],
125 errors: &mut Vec<LocalizationError>,
126 ) -> Result<Vec<Option<Cow<'l, str>>>, LocalizationError> {
127 match &self.0 {
128 BundlesInner::Iter(cache) => Ok(Self::format_values_from_iter(cache, keys, errors)),
129 BundlesInner::Stream(_) => Err(LocalizationError::SyncRequestInAsyncMode),
130 }
131 }
132
133 pub fn format_messages_sync<'l>(
134 &'l self,
135 keys: &'l [L10nKey<'l>],
136 errors: &mut Vec<LocalizationError>,
137 ) -> Result<Vec<Option<L10nMessage<'l>>>, LocalizationError> {
138 match &self.0 {
139 BundlesInner::Iter(cache) => Ok(Self::format_messages_from_iter(cache, keys, errors)),
140 BundlesInner::Stream(_) => Err(LocalizationError::SyncRequestInAsyncMode),
141 }
142 }
143}
144
145macro_rules! format_value_from_inner {
146 ($step:expr, $id:expr, $args:expr, $errors:expr) => {
147 let mut found_message = false;
148
149 while let Some(bundle) = $step {
150 let bundle = bundle.as_ref().unwrap_or_else(|(bundle, err)| {
151 $errors.extend(err.iter().cloned().map(Into::into));
152 bundle
153 });
154
155 if let Some(msg) = bundle.get_message($id) {
156 found_message = true;
157 if let Some(value) = msg.value() {
158 let mut format_errors = vec![];
159 let result = bundle.format_pattern(value, $args, &mut format_errors);
160 if !format_errors.is_empty() {
161 $errors.push(LocalizationError::Resolver {
162 id: $id.to_string(),
163 locale: bundle.locales[0].clone(),
164 errors: format_errors,
165 });
166 }
167 return Some(result);
168 } else {
169 $errors.push(LocalizationError::MissingValue {
170 id: $id.to_string(),
171 locale: Some(bundle.locales[0].clone()),
172 });
173 }
174 } else {
175 $errors.push(LocalizationError::MissingMessage {
176 id: $id.to_string(),
177 locale: Some(bundle.locales[0].clone()),
178 });
179 }
180 }
181 if found_message {
182 $errors.push(LocalizationError::MissingValue {
183 id: $id.to_string(),
184 locale: None,
185 });
186 } else {
187 $errors.push(LocalizationError::MissingMessage {
188 id: $id.to_string(),
189 locale: None,
190 });
191 }
192 return None;
193 };
194}
195
196#[derive(Clone)]
197enum Value<'l> {
198 Present(Cow<'l, str>),
199 Missing,
200 None,
201}
202
203macro_rules! format_values_from_inner {
204 ($step:expr, $keys:expr, $errors:expr) => {
205 let mut cells = vec![Value::None; $keys.len()];
206
207 while let Some(bundle) = $step {
208 let bundle = bundle.as_ref().unwrap_or_else(|(bundle, err)| {
209 $errors.extend(err.iter().cloned().map(Into::into));
210 bundle
211 });
212
213 let mut has_missing = false;
214
215 for (key, cell) in $keys
216 .iter()
217 .zip(&mut cells)
218 .filter(|(_, cell)| !matches!(cell, Value::Present(_)))
219 {
220 if let Some(msg) = bundle.get_message(&key.id) {
221 if let Some(value) = msg.value() {
222 let mut format_errors = vec![];
223 *cell = Value::Present(bundle.format_pattern(
224 value,
225 key.args.as_ref(),
226 &mut format_errors,
227 ));
228 if !format_errors.is_empty() {
229 $errors.push(LocalizationError::Resolver {
230 id: key.id.to_string(),
231 locale: bundle.locales[0].clone(),
232 errors: format_errors,
233 });
234 }
235 } else {
236 *cell = Value::Missing;
237 has_missing = true;
238 $errors.push(LocalizationError::MissingValue {
239 id: key.id.to_string(),
240 locale: Some(bundle.locales[0].clone()),
241 });
242 }
243 } else {
244 has_missing = true;
245 $errors.push(LocalizationError::MissingMessage {
246 id: key.id.to_string(),
247 locale: Some(bundle.locales[0].clone()),
248 });
249 }
250 }
251 if !has_missing {
252 break;
253 }
254 }
255
256 return $keys
257 .iter()
258 .zip(cells)
259 .map(|(key, value)| match value {
260 Value::Present(value) => Some(value),
261 Value::Missing => {
262 $errors.push(LocalizationError::MissingValue {
263 id: key.id.to_string(),
264 locale: None,
265 });
266 None
267 }
268 Value::None => {
269 $errors.push(LocalizationError::MissingMessage {
270 id: key.id.to_string(),
271 locale: None,
272 });
273 None
274 }
275 })
276 .collect();
277 };
278}
279
280macro_rules! format_messages_from_inner {
281 ($step:expr, $keys:expr, $errors:expr) => {
282 let mut result = vec![None; $keys.len()];
283
284 let mut is_complete = false;
285
286 while let Some(bundle) = $step {
287 let bundle = bundle.as_ref().unwrap_or_else(|(bundle, err)| {
288 $errors.extend(err.iter().cloned().map(Into::into));
289 bundle
290 });
291
292 let mut has_missing = false;
293 for (key, cell) in $keys
294 .iter()
295 .zip(&mut result)
296 .filter(|(_, cell)| cell.is_none())
297 {
298 let mut format_errors = vec![];
299 let msg = Self::format_message_from_bundle(bundle, key, &mut format_errors);
300
301 if msg.is_none() {
302 has_missing = true;
303 $errors.push(LocalizationError::MissingMessage {
304 id: key.id.to_string(),
305 locale: Some(bundle.locales[0].clone()),
306 });
307 } else if !format_errors.is_empty() {
308 $errors.push(LocalizationError::Resolver {
309 id: key.id.to_string(),
310 locale: bundle.locales.get(0).cloned().unwrap(),
311 errors: format_errors,
312 });
313 }
314
315 *cell = msg;
316 }
317 if !has_missing {
318 is_complete = true;
319 break;
320 }
321 }
322
323 if !is_complete {
324 for (key, _) in $keys
325 .iter()
326 .zip(&mut result)
327 .filter(|(_, cell)| cell.is_none())
328 {
329 $errors.push(LocalizationError::MissingMessage {
330 id: key.id.to_string(),
331 locale: None,
332 });
333 }
334 }
335
336 return result;
337 };
338}
339
340impl<G> Bundles<G>
341where
342 G: BundleGenerator,
343{
344 fn format_value_from_iter<'l>(
345 cache: &'l Cache<G::Iter, G::Resource>,
346 id: &'l str,
347 args: Option<&'l FluentArgs>,
348 errors: &mut Vec<LocalizationError>,
349 ) -> Option<Cow<'l, str>> {
350 let mut bundle_iter = cache.into_iter();
351 format_value_from_inner!(bundle_iter.next(), id, args, errors);
352 }
353
354 async fn format_value_from_stream<'l>(
355 stream: &'l AsyncCache<G::Stream, G::Resource>,
356 id: &'l str,
357 args: Option<&'l FluentArgs<'_>>,
358 errors: &mut Vec<LocalizationError>,
359 ) -> Option<Cow<'l, str>> {
360 use futures::StreamExt;
361
362 let mut bundle_stream = stream.stream();
363 format_value_from_inner!(bundle_stream.next().await, id, args, errors);
364 }
365
366 async fn format_messages_from_stream<'l>(
367 stream: &'l AsyncCache<G::Stream, G::Resource>,
368 keys: &'l [L10nKey<'l>],
369 errors: &mut Vec<LocalizationError>,
370 ) -> Vec<Option<L10nMessage<'l>>> {
371 use futures::StreamExt;
372 let mut bundle_stream = stream.stream();
373 format_messages_from_inner!(bundle_stream.next().await, keys, errors);
374 }
375
376 async fn format_values_from_stream<'l>(
377 stream: &'l AsyncCache<G::Stream, G::Resource>,
378 keys: &'l [L10nKey<'l>],
379 errors: &mut Vec<LocalizationError>,
380 ) -> Vec<Option<Cow<'l, str>>> {
381 use futures::StreamExt;
382 let mut bundle_stream = stream.stream();
383
384 format_values_from_inner!(bundle_stream.next().await, keys, errors);
385 }
386
387 fn format_message_from_bundle<'l>(
388 bundle: &'l FluentBundle<G::Resource>,
389 key: &'l L10nKey,
390 format_errors: &mut Vec<FluentError>,
391 ) -> Option<L10nMessage<'l>> {
392 let msg = bundle.get_message(&key.id)?;
393 let value = msg
394 .value()
395 .map(|pattern| bundle.format_pattern(pattern, key.args.as_ref(), format_errors));
396 let attributes = msg
397 .attributes()
398 .map(|attr| {
399 let value = bundle.format_pattern(attr.value(), key.args.as_ref(), format_errors);
400 L10nAttribute {
401 name: attr.id().into(),
402 value,
403 }
404 })
405 .collect();
406 Some(L10nMessage { value, attributes })
407 }
408
409 fn format_messages_from_iter<'l>(
410 cache: &'l Cache<G::Iter, G::Resource>,
411 keys: &'l [L10nKey<'l>],
412 errors: &mut Vec<LocalizationError>,
413 ) -> Vec<Option<L10nMessage<'l>>> {
414 let mut bundle_iter = cache.into_iter();
415 format_messages_from_inner!(bundle_iter.next(), keys, errors);
416 }
417
418 fn format_values_from_iter<'l>(
419 cache: &'l Cache<G::Iter, G::Resource>,
420 keys: &'l [L10nKey<'l>],
421 errors: &mut Vec<LocalizationError>,
422 ) -> Vec<Option<Cow<'l, str>>> {
423 let mut bundle_iter = cache.into_iter();
424 format_values_from_inner!(bundle_iter.next(), keys, errors);
425 }
426}