• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

projectfluent / fluent-rs / 8850648079

26 Apr 2024 03:28PM UTC coverage: 89.723% (-0.4%) from 90.156%
8850648079

Pull #353

github

web-flow
Merge 67a5f4698 into 18343761a
Pull Request #353: Add NUMBER builtin function

10 of 32 new or added lines in 4 files covered. (31.25%)

12 existing lines in 1 file now uncovered.

3990 of 4447 relevant lines covered (89.72%)

3477.96 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

77.78
/fluent-bundle/src/bundle.rs
1
//! `FluentBundle` is a collection of localization messages in Fluent.
2
//!
3
//! It stores a list of messages in a single locale which can reference one another, use the same
4
//! internationalization formatters, functions, scopeironmental variables and are expected to be used
5
//! together.
6

7
use rustc_hash::FxHashMap;
8
use std::borrow::Borrow;
9
use std::borrow::Cow;
10
use std::collections::hash_map::Entry as HashEntry;
11
use std::default::Default;
12
use std::fmt;
13

14
use fluent_syntax::ast;
15
use intl_memoizer::IntlLangMemoizer;
16
use unic_langid::LanguageIdentifier;
17

18
use crate::args::FluentArgs;
19
use crate::entry::Entry;
20
use crate::entry::GetEntry;
21
use crate::errors::{EntryKind, FluentError};
22
use crate::memoizer::MemoizerKind;
23
use crate::message::FluentMessage;
24
use crate::resolver::{ResolveValue, Scope, WriteValue};
25
use crate::resource::FluentResource;
26
use crate::types::FluentValue;
27

28
/// A collection of localization messages for a single locale, which are meant
29
/// to be used together in a single view, widget or any other UI abstraction.
30
///
31
/// # Examples
32
///
33
/// ```
34
/// use fluent_bundle::{FluentBundle, FluentResource, FluentValue, FluentArgs};
35
/// use unic_langid::langid;
36
///
37
/// // 1. Create a FluentResource
38
///
39
/// let ftl_string = String::from("intro = Welcome, { $name }.");
40
/// let resource = FluentResource::try_new(ftl_string)
41
///     .expect("Could not parse an FTL string.");
42
///
43
///
44
/// // 2. Create a FluentBundle
45
///
46
/// let langid_en = langid!("en-US");
47
/// let mut bundle = FluentBundle::new(vec![langid_en]);
48
///
49
///
50
/// // 3. Add the resource to the bundle
51
///
52
/// bundle.add_resource(&resource)
53
///     .expect("Failed to add FTL resources to the bundle.");
54
///
55
///
56
/// // 4. Retrieve a FluentMessage from the bundle
57
///
58
/// let msg = bundle.get_message("intro")
59
///     .expect("Message doesn't exist.");
60
///
61
/// let mut args = FluentArgs::new();
62
/// args.set("name", "Rustacean");
63
///
64
///
65
/// // 5. Format the value of the message
66
///
67
/// let mut errors = vec![];
68
///
69
/// let pattern = msg.value()
70
///     .expect("Message has no value.");
71
///
72
/// assert_eq!(
73
///     bundle.format_pattern(&pattern, Some(&args), &mut errors),
74
///     // The placeholder is wrapper in Unicode Directionality Marks
75
///     // to indicate that the placeholder may be of different direction
76
///     // than surrounding string.
77
///     "Welcome, \u{2068}Rustacean\u{2069}."
78
/// );
79
///
80
/// ```
81
///
82
/// # `FluentBundle` Life Cycle
83
///
84
/// ## Create a bundle
85
///
86
/// To create a bundle, call [`FluentBundle::new`] with a locale list that represents the best
87
/// possible fallback chain for a given locale. The simplest case is a one-locale list.
88
///
89
/// Fluent uses [`LanguageIdentifier`] which can be created using `langid!` macro.
90
///
91
/// ## Add Resources
92
///
93
/// Next, call [`add_resource`](FluentBundle::add_resource) one or more times, supplying translations in the FTL syntax.
94
///
95
/// Since [`FluentBundle`] is generic over anything that can borrow a [`FluentResource`],
96
/// one can use [`FluentBundle`] to own its resources, store references to them,
97
/// or even [`Rc<FluentResource>`](std::rc::Rc) or [`Arc<FluentResource>`](std::sync::Arc).
98
///
99
/// The [`FluentBundle`] instance is now ready to be used for localization.
100
///
101
/// ## Format
102
///
103
/// To format a translation, call [`get_message`](FluentBundle::get_message) to retrieve a [`FluentMessage`],
104
/// and then call [`format_pattern`](FluentBundle::format_pattern) on the message value or attribute in order to
105
/// retrieve the translated string.
106
///
107
/// The result of [`format_pattern`](FluentBundle::format_pattern) is an
108
/// [`Cow<str>`](std::borrow::Cow). It is
109
/// recommended to treat the result as opaque from the perspective of the program and use it only
110
/// to display localized messages. Do not examine it or alter in any way before displaying.  This
111
/// is a general good practice as far as all internationalization operations are concerned.
112
///
113
/// If errors were encountered during formatting, they will be
114
/// accumulated in the [`Vec<FluentError>`](FluentError) passed as the third argument.
115
///
116
/// While they are not fatal, they usually indicate problems with the translation,
117
/// and should be logged or reported in a way that allows the developer to notice
118
/// and fix them.
119
///
120
///
121
/// # Locale Fallback Chain
122
///
123
/// [`FluentBundle`] stores messages in a single locale, but keeps a locale fallback chain for the
124
/// purpose of language negotiation with i18n formatters. For instance, if date and time formatting
125
/// are not available in the first locale, [`FluentBundle`] will use its `locales` fallback chain
126
/// to negotiate a sensible fallback for date and time formatting.
127
///
128
/// # Concurrency
129
///
130
/// As you may have noticed, [`fluent_bundle::FluentBundle`](crate::FluentBundle) is a specialization of [`fluent_bundle::bundle::FluentBundle`](crate::bundle::FluentBundle)
131
/// which works with an [`IntlLangMemoizer`] over [`RefCell`](std::cell::RefCell).
132
/// In scenarios where the memoizer must work concurrently, there's an implementation of
133
/// [`IntlLangMemoizer`][concurrent::IntlLangMemoizer] that uses [`Mutex`](std::sync::Mutex) and there's [`FluentBundle::new_concurrent`] which works with that.
134
///
135
/// [concurrent::IntlLangMemoizer]: https://docs.rs/intl-memoizer/latest/intl_memoizer/concurrent/struct.IntlLangMemoizer.html
136
pub struct FluentBundle<R, M> {
137
    pub locales: Vec<LanguageIdentifier>,
138
    pub(crate) resources: Vec<R>,
139
    pub(crate) entries: FxHashMap<String, Entry>,
140
    pub(crate) intls: M,
141
    pub(crate) use_isolating: bool,
142
    pub(crate) transform: Option<fn(&str) -> Cow<str>>,
143
    pub(crate) formatter: Option<fn(&FluentValue, &M) -> Option<String>>,
144
}
145

146
impl<R, M> FluentBundle<R, M> {
147
    /// Adds a resource to the bundle, returning an empty [`Result<T>`] on success.
148
    ///
149
    /// If any entry in the resource uses the same identifier as an already
150
    /// existing key in the bundle, the new entry will be ignored and a
151
    /// `FluentError::Overriding` will be added to the result.
152
    ///
153
    /// The method can take any type that can be borrowed to [`FluentResource`]:
154
    ///   - `FluentResource`
155
    ///   - `&FluentResource`
156
    ///   - `Rc<FluentResource>`
157
    ///   - `Arc<FluentResource>`
158
    ///
159
    /// This allows the user to introduce custom resource management and share
160
    /// resources between instances of `FluentBundle`.
161
    ///
162
    /// # Examples
163
    ///
164
    /// ```
165
    /// use fluent_bundle::{FluentBundle, FluentResource};
166
    /// use unic_langid::langid;
167
    ///
168
    /// let ftl_string = String::from("
169
    /// hello = Hi!
170
    /// goodbye = Bye!
171
    /// ");
172
    /// let resource = FluentResource::try_new(ftl_string)
173
    ///     .expect("Could not parse an FTL string.");
174
    /// let langid_en = langid!("en-US");
175
    /// let mut bundle = FluentBundle::new(vec![langid_en]);
176
    /// bundle.add_resource(resource)
177
    ///     .expect("Failed to add FTL resources to the bundle.");
178
    /// assert_eq!(true, bundle.has_message("hello"));
179
    /// ```
180
    ///
181
    /// # Whitespace
182
    ///
183
    /// Message ids must have no leading whitespace. Message values that span
184
    /// multiple lines must have leading whitespace on all but the first line. These
185
    /// are standard FTL syntax rules that may prove a bit troublesome in source
186
    /// code formatting. The [`indoc!`] crate can help with stripping extra indentation
187
    /// if you wish to indent your entire message.
188
    ///
189
    /// [FTL syntax]: https://projectfluent.org/fluent/guide/
190
    /// [`indoc!`]: https://github.com/dtolnay/indoc
191
    /// [`Result<T>`]: https://doc.rust-lang.org/std/result/enum.Result.html
192
    pub fn add_resource(&mut self, r: R) -> Result<(), Vec<FluentError>>
219✔
193
    where
219✔
194
        R: Borrow<FluentResource>,
219✔
195
    {
219✔
196
        let mut errors = vec![];
219✔
197

219✔
198
        let res = r.borrow();
219✔
199
        let res_pos = self.resources.len();
219✔
200

201
        for (entry_pos, entry) in res.entries().enumerate() {
1,342✔
202
            let (id, entry) = match entry {
1,342✔
203
                ast::Entry::Message(ast::Message { ref id, .. }) => {
1,133✔
204
                    (id.name, Entry::Message((res_pos, entry_pos)))
1,133✔
205
                }
206
                ast::Entry::Term(ast::Term { ref id, .. }) => {
172✔
207
                    (id.name, Entry::Term((res_pos, entry_pos)))
172✔
208
                }
209
                _ => continue,
37✔
210
            };
211

212
            match self.entries.entry(id.to_string()) {
1,305✔
213
                HashEntry::Vacant(empty) => {
1,303✔
214
                    empty.insert(entry);
1,303✔
215
                }
1,303✔
216
                HashEntry::Occupied(_) => {
2✔
217
                    let kind = match entry {
2✔
218
                        Entry::Message(..) => EntryKind::Message,
2✔
219
                        Entry::Term(..) => EntryKind::Term,
×
220
                        _ => unreachable!(),
×
221
                    };
222
                    errors.push(FluentError::Overriding {
2✔
223
                        kind,
2✔
224
                        id: id.to_string(),
2✔
225
                    });
2✔
226
                }
227
            }
228
        }
229
        self.resources.push(r);
219✔
230

219✔
231
        if errors.is_empty() {
219✔
232
            Ok(())
217✔
233
        } else {
234
            Err(errors)
2✔
235
        }
236
    }
219✔
237

238
    /// Adds a resource to the bundle, returning an empty [`Result<T>`] on success.
239
    ///
240
    /// If any entry in the resource uses the same identifier as an already
241
    /// existing key in the bundle, the entry will override the previous one.
242
    ///
243
    /// The method can take any type that can be borrowed as [`FluentResource`]:
244
    ///   - `FluentResource`
245
    ///   - `&FluentResource`
246
    ///   - `Rc<FluentResource>`
247
    ///   - `Arc<FluentResource>`
248
    ///
249
    /// This allows the user to introduce custom resource management and share
250
    /// resources between instances of `FluentBundle`.
251
    ///
252
    /// # Examples
253
    ///
254
    /// ```
255
    /// use fluent_bundle::{FluentBundle, FluentResource};
256
    /// use unic_langid::langid;
257
    ///
258
    /// let ftl_string = String::from("
259
    /// hello = Hi!
260
    /// goodbye = Bye!
261
    /// ");
262
    /// let resource = FluentResource::try_new(ftl_string)
263
    ///     .expect("Could not parse an FTL string.");
264
    ///
265
    /// let ftl_string = String::from("
266
    /// hello = Another Hi!
267
    /// ");
268
    /// let resource2 = FluentResource::try_new(ftl_string)
269
    ///     .expect("Could not parse an FTL string.");
270
    ///
271
    /// let langid_en = langid!("en-US");
272
    ///
273
    /// let mut bundle = FluentBundle::new(vec![langid_en]);
274
    /// bundle.add_resource(resource)
275
    ///     .expect("Failed to add FTL resources to the bundle.");
276
    ///
277
    /// bundle.add_resource_overriding(resource2);
278
    ///
279
    /// let mut errors = vec![];
280
    /// let msg = bundle.get_message("hello")
281
    ///     .expect("Failed to retrieve the message");
282
    /// let value = msg.value().expect("Failed to retrieve the value of the message");
283
    /// assert_eq!(bundle.format_pattern(value, None, &mut errors), "Another Hi!");
284
    /// ```
285
    ///
286
    /// # Whitespace
287
    ///
288
    /// Message ids must have no leading whitespace. Message values that span
289
    /// multiple lines must have leading whitespace on all but the first line. These
290
    /// are standard FTL syntax rules that may prove a bit troublesome in source
291
    /// code formatting. The [`indoc!`] crate can help with stripping extra indentation
292
    /// if you wish to indent your entire message.
293
    ///
294
    /// [FTL syntax]: https://projectfluent.org/fluent/guide/
295
    /// [`indoc!`]: https://github.com/dtolnay/indoc
296
    /// [`Result<T>`]: https://doc.rust-lang.org/std/result/enum.Result.html
297
    pub fn add_resource_overriding(&mut self, r: R)
1✔
298
    where
1✔
299
        R: Borrow<FluentResource>,
1✔
300
    {
1✔
301
        let res = r.borrow();
1✔
302
        let res_pos = self.resources.len();
1✔
303

304
        for (entry_pos, entry) in res.entries().enumerate() {
1✔
305
            let (id, entry) = match entry {
1✔
306
                ast::Entry::Message(ast::Message { ref id, .. }) => {
1✔
307
                    (id.name, Entry::Message((res_pos, entry_pos)))
1✔
308
                }
309
                ast::Entry::Term(ast::Term { ref id, .. }) => {
×
310
                    (id.name, Entry::Term((res_pos, entry_pos)))
×
311
                }
312
                _ => continue,
×
313
            };
314

315
            self.entries.insert(id.to_string(), entry);
1✔
316
        }
317
        self.resources.push(r);
1✔
318
    }
1✔
319

320
    /// When formatting patterns, `FluentBundle` inserts
321
    /// Unicode Directionality Isolation Marks to indicate
322
    /// that the direction of a placeable may differ from
323
    /// the surrounding message.
324
    ///
325
    /// This is important for cases such as when a
326
    /// right-to-left user name is presented in the
327
    /// left-to-right message.
328
    ///
329
    /// In some cases, such as testing, the user may want
330
    /// to disable the isolating.
331
    pub fn set_use_isolating(&mut self, value: bool) {
189✔
332
        self.use_isolating = value;
189✔
333
    }
189✔
334

335
    /// This method allows to specify a function that will
336
    /// be called on all textual fragments of the pattern
337
    /// during formatting.
338
    ///
339
    /// This is currently primarily used for pseudolocalization,
340
    /// and `fluent-pseudo` crate provides a function
341
    /// that can be passed here.
342
    pub fn set_transform(&mut self, func: Option<fn(&str) -> Cow<str>>) {
6✔
343
        self.transform = func;
6✔
344
    }
6✔
345

346
    /// This method allows to specify a function that will
347
    /// be called before any `FluentValue` is formatted
348
    /// allowing overrides.
349
    ///
350
    /// It's particularly useful for plugging in an external
351
    /// formatter for `FluentValue::Number`.
352
    pub fn set_formatter(&mut self, func: Option<fn(&FluentValue, &M) -> Option<String>>) {
1✔
353
        self.formatter = func;
1✔
354
    }
1✔
355

356
    /// Returns true if this bundle contains a message with the given id.
357
    ///
358
    /// # Examples
359
    ///
360
    /// ```
361
    /// use fluent_bundle::{FluentBundle, FluentResource};
362
    /// use unic_langid::langid;
363
    ///
364
    /// let ftl_string = String::from("hello = Hi!");
365
    /// let resource = FluentResource::try_new(ftl_string)
366
    ///     .expect("Failed to parse an FTL string.");
367
    /// let langid_en = langid!("en-US");
368
    /// let mut bundle = FluentBundle::new(vec![langid_en]);
369
    /// bundle.add_resource(&resource)
370
    ///     .expect("Failed to add FTL resources to the bundle.");
371
    /// assert_eq!(true, bundle.has_message("hello"));
372
    ///
373
    /// ```
374
    pub fn has_message(&self, id: &str) -> bool
13✔
375
    where
13✔
376
        R: Borrow<FluentResource>,
13✔
377
    {
13✔
378
        self.get_entry_message(id).is_some()
13✔
379
    }
13✔
380

381
    /// Retrieves a `FluentMessage` from a bundle.
382
    ///
383
    /// # Examples
384
    ///
385
    /// ```
386
    /// use fluent_bundle::{FluentBundle, FluentResource};
387
    /// use unic_langid::langid;
388
    ///
389
    /// let ftl_string = String::from("hello-world = Hello World!");
390
    /// let resource = FluentResource::try_new(ftl_string)
391
    ///     .expect("Failed to parse an FTL string.");
392
    ///
393
    /// let langid_en = langid!("en-US");
394
    /// let mut bundle = FluentBundle::new(vec![langid_en]);
395
    ///
396
    /// bundle.add_resource(&resource)
397
    ///     .expect("Failed to add FTL resources to the bundle.");
398
    ///
399
    /// let msg = bundle.get_message("hello-world");
400
    /// assert_eq!(msg.is_some(), true);
401
    /// ```
402
    pub fn get_message<'l>(&'l self, id: &str) -> Option<FluentMessage<'l>>
213✔
403
    where
213✔
404
        R: Borrow<FluentResource>,
213✔
405
    {
213✔
406
        self.get_entry_message(id).map(Into::into)
213✔
407
    }
213✔
408

409
    /// Writes a formatted pattern which comes from a `FluentMessage`.
410
    ///
411
    /// # Example
412
    ///
413
    /// ```
414
    /// use fluent_bundle::{FluentBundle, FluentResource};
415
    /// use unic_langid::langid;
416
    ///
417
    /// let ftl_string = String::from("hello-world = Hello World!");
418
    /// let resource = FluentResource::try_new(ftl_string)
419
    ///     .expect("Failed to parse an FTL string.");
420
    ///
421
    /// let langid_en = langid!("en-US");
422
    /// let mut bundle = FluentBundle::new(vec![langid_en]);
423
    ///
424
    /// bundle.add_resource(&resource)
425
    ///     .expect("Failed to add FTL resources to the bundle.");
426
    ///
427
    /// let msg = bundle.get_message("hello-world")
428
    ///     .expect("Failed to retrieve a FluentMessage.");
429
    ///
430
    /// let pattern = msg.value()
431
    ///     .expect("Missing Value.");
432
    /// let mut errors = vec![];
433
    ///
434
    /// let mut s = String::new();
435
    /// bundle.write_pattern(&mut s, &pattern, None, &mut errors)
436
    ///     .expect("Failed to write.");
437
    ///
438
    /// assert_eq!(s, "Hello World!");
439
    /// ```
440
    pub fn write_pattern<'bundle, W>(
×
441
        &'bundle self,
×
442
        w: &mut W,
×
443
        pattern: &'bundle ast::Pattern<&str>,
×
444
        args: Option<&'bundle FluentArgs>,
×
445
        errors: &mut Vec<FluentError>,
×
446
    ) -> fmt::Result
×
447
    where
×
448
        R: Borrow<FluentResource>,
×
449
        W: fmt::Write,
×
450
        M: MemoizerKind,
×
451
    {
×
452
        let mut scope = Scope::new(self, args, Some(errors));
×
453
        pattern.write(w, &mut scope)
×
454
    }
×
455

456
    /// Formats a pattern which comes from a `FluentMessage`.
457
    ///
458
    /// # Example
459
    ///
460
    /// ```
461
    /// use fluent_bundle::{FluentBundle, FluentResource};
462
    /// use unic_langid::langid;
463
    ///
464
    /// let ftl_string = String::from("hello-world = Hello World!");
465
    /// let resource = FluentResource::try_new(ftl_string)
466
    ///     .expect("Failed to parse an FTL string.");
467
    ///
468
    /// let langid_en = langid!("en-US");
469
    /// let mut bundle = FluentBundle::new(vec![langid_en]);
470
    ///
471
    /// bundle.add_resource(&resource)
472
    ///     .expect("Failed to add FTL resources to the bundle.");
473
    ///
474
    /// let msg = bundle.get_message("hello-world")
475
    ///     .expect("Failed to retrieve a FluentMessage.");
476
    ///
477
    /// let pattern = msg.value()
478
    ///     .expect("Missing Value.");
479
    /// let mut errors = vec![];
480
    ///
481
    /// let result = bundle.format_pattern(&pattern, None, &mut errors);
482
    ///
483
    /// assert_eq!(result, "Hello World!");
484
    /// ```
485
    pub fn format_pattern<'bundle, 'args>(
190✔
486
        &'bundle self,
190✔
487
        pattern: &'bundle ast::Pattern<&'bundle str>,
190✔
488
        args: Option<&'args FluentArgs>,
190✔
489
        errors: &mut Vec<FluentError>,
190✔
490
    ) -> Cow<'bundle, str>
190✔
491
    where
190✔
492
        R: Borrow<FluentResource>,
190✔
493
        M: MemoizerKind,
190✔
494
    {
190✔
495
        let mut scope = Scope::new(self, args, Some(errors));
190✔
496
        let value = pattern.resolve(&mut scope);
190✔
497
        value.into_string(&scope)
190✔
498
    }
190✔
499

500
    /// Makes the provided rust function available to messages with the name `id`. See
501
    /// the [FTL syntax guide] to learn how these are used in messages.
502
    ///
503
    /// FTL functions accept both positional and named args. The rust function you
504
    /// provide therefore has two parameters: a slice of values for the positional
505
    /// args, and a `FluentArgs` for named args.
506
    ///
507
    /// # Examples
508
    ///
509
    /// ```
510
    /// use fluent_bundle::{FluentBundle, FluentResource, FluentValue};
511
    /// use unic_langid::langid;
512
    ///
513
    /// let ftl_string = String::from("length = { STRLEN(\"12345\") }");
514
    /// let resource = FluentResource::try_new(ftl_string)
515
    ///     .expect("Could not parse an FTL string.");
516
    /// let langid_en = langid!("en-US");
517
    /// let mut bundle = FluentBundle::new(vec![langid_en]);
518
    /// bundle.add_resource(&resource)
519
    ///     .expect("Failed to add FTL resources to the bundle.");
520
    ///
521
    /// // Register a fn that maps from string to string length
522
    /// bundle.add_function("STRLEN", |positional, _named| match positional {
523
    ///     [FluentValue::String(str)] => str.len().into(),
524
    ///     _ => FluentValue::Error,
525
    /// }).expect("Failed to add a function to the bundle.");
526
    ///
527
    /// let msg = bundle.get_message("length").expect("Message doesn't exist.");
528
    /// let mut errors = vec![];
529
    /// let pattern = msg.value().expect("Message has no value.");
530
    /// let value = bundle.format_pattern(&pattern, None, &mut errors);
531
    /// assert_eq!(&value, "5");
532
    /// ```
533
    ///
534
    /// [FTL syntax guide]: https://projectfluent.org/fluent/guide/functions.html
535
    pub fn add_function<F>(&mut self, id: &str, func: F) -> Result<(), FluentError>
15✔
536
    where
15✔
537
        F: for<'a> Fn(&[FluentValue<'a>], &FluentArgs) -> FluentValue<'a> + Sync + Send + 'static,
15✔
538
    {
15✔
539
        match self.entries.entry(id.to_owned()) {
15✔
540
            HashEntry::Vacant(entry) => {
15✔
541
                entry.insert(Entry::Function(Box::new(func)));
15✔
542
                Ok(())
15✔
543
            }
544
            HashEntry::Occupied(_) => Err(FluentError::Overriding {
×
545
                kind: EntryKind::Function,
×
546
                id: id.to_owned(),
×
547
            }),
×
548
        }
549
    }
15✔
550

551
    /// Adds the builtin functions described in the [FTL syntax guide] to the bundle, making them
552
    /// available in messages.
553
    ///
554
    /// # Examples
555
    ///
556
    /// ```
557
    /// use fluent_bundle::{FluentArgs, FluentBundle, FluentResource, FluentValue};
558
    /// use unic_langid::langid;
559
    ///
560
    /// let ftl_string = String::from(r#"rank = { NUMBER($n, type: "ordinal") ->
561
    ///     [1] first
562
    ///     [2] second
563
    ///     [3] third
564
    ///     [one] {$n}st
565
    ///     [two] {$n}nd
566
    ///     [few] {$n}rd
567
    ///     *[other] {$n}th
568
    /// }"#);
569
    /// let resource = FluentResource::try_new(ftl_string)
570
    ///     .expect("Could not parse an FTL string.");
571
    /// let langid_en = langid!("en-US");
572
    /// let mut bundle = FluentBundle::new(vec![langid_en]);
573
    /// bundle.add_resource(&resource)
574
    ///     .expect("Failed to add FTL resources to the bundle.");
575
    ///
576
    /// // Register the builtin functions (including NUMBER())
577
    /// bundle.add_builtins().expect("Failed to add builtins to the bundle.");
578
    ///
579
    /// let msg = bundle.get_message("rank").expect("Message doesn't exist.");
580
    /// let mut errors = vec![];
581
    /// let pattern = msg.value().expect("Message has no value.");
582
    ///
583
    /// let mut args = FluentArgs::new();
584
    ///
585
    /// args.set("n", 5);
586
    /// let value = bundle.format_pattern(&pattern, Some(&args), &mut errors);
587
    /// assert_eq!(&value, "\u{2068}5\u{2069}th");
588
    ///
589
    /// args.set("n", 12);
590
    /// let value = bundle.format_pattern(&pattern, Some(&args), &mut errors);
591
    /// assert_eq!(&value, "\u{2068}12\u{2069}th");
592
    ///
593
    /// args.set("n", 22);
594
    /// let value = bundle.format_pattern(&pattern, Some(&args), &mut errors);
595
    /// assert_eq!(&value, "\u{2068}22\u{2069}nd");
596
    /// ```
597
    ///
598
    /// [FTL syntax guide]: https://projectfluent.org/fluent/guide/functions.html
NEW
599
    pub fn add_builtins(&mut self) -> Result<(), FluentError> {
×
NEW
600
        self.add_function("NUMBER", crate::builtins::NUMBER)?;
×
601
        // TODO: DATETIME()
602

NEW
603
        Ok(())
×
NEW
604
    }
×
605
}
606

607
impl<R> Default for FluentBundle<R, IntlLangMemoizer> {
608
    fn default() -> Self {
4✔
609
        Self::new(vec![LanguageIdentifier::default()])
4✔
610
    }
4✔
611
}
612

613
impl<R> FluentBundle<R, IntlLangMemoizer> {
614
    /// Constructs a FluentBundle. The first element in `locales` should be the
615
    /// language this bundle represents, and will be used to determine the
616
    /// correct plural rules for this bundle. You can optionally provide extra
617
    /// languages in the list; they will be used as fallback date and time
618
    /// formatters if a formatter for the primary language is unavailable.
619
    ///
620
    /// # Examples
621
    ///
622
    /// ```
623
    /// use fluent_bundle::FluentBundle;
624
    /// use fluent_bundle::FluentResource;
625
    /// use unic_langid::langid;
626
    ///
627
    /// let langid_en = langid!("en-US");
628
    /// let mut bundle: FluentBundle<FluentResource> = FluentBundle::new(vec![langid_en]);
629
    /// ```
630
    ///
631
    /// # Errors
632
    ///
633
    /// This will panic if no formatters can be found for the locales.
634
    pub fn new(locales: Vec<LanguageIdentifier>) -> Self {
205✔
635
        let first_locale = locales.get(0).cloned().unwrap_or_default();
205✔
636
        Self {
205✔
637
            locales,
205✔
638
            resources: vec![],
205✔
639
            entries: FxHashMap::default(),
205✔
640
            intls: IntlLangMemoizer::new(first_locale),
205✔
641
            use_isolating: true,
205✔
642
            transform: None,
205✔
643
            formatter: None,
205✔
644
        }
205✔
645
    }
205✔
646
}
647

648
impl crate::memoizer::MemoizerKind for IntlLangMemoizer {
649
    fn new(lang: LanguageIdentifier) -> Self
×
650
    where
×
651
        Self: Sized,
×
652
    {
×
653
        Self::new(lang)
×
654
    }
×
655

656
    fn with_try_get_threadsafe<I, R, U>(&self, args: I::Args, cb: U) -> Result<R, I::Error>
13✔
657
    where
13✔
658
        Self: Sized,
13✔
659
        I: intl_memoizer::Memoizable + Send + Sync + 'static,
13✔
660
        I::Args: Send + Sync + 'static,
13✔
661
        U: FnOnce(&I) -> R,
13✔
662
    {
13✔
663
        self.with_try_get(args, cb)
13✔
664
    }
13✔
665

666
    fn stringify_value(
24✔
667
        &self,
24✔
668
        value: &dyn crate::types::FluentType,
24✔
669
    ) -> std::borrow::Cow<'static, str> {
24✔
670
        value.as_string(self)
24✔
671
    }
24✔
672
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc