@@ -22,21 +22,43 @@ pub struct Message {
22
22
pub attributes : HashMap < String , String > ,
23
23
}
24
24
25
- /// `FluentBundle` is a collection of localization messages which are meant to be used together
26
- /// in a single view, widget or any other UI abstraction.
25
+ /// A collection of localization messages for a single locale, which are meant
26
+ /// to be used together in a single view, widget or any other UI abstraction.
27
27
///
28
- /// # `FluentBundle` Life-cycle
28
+ /// # Examples
29
29
///
30
- /// To create a bundle, call `FluentBundle::new` with a locale list that represents the best
31
- /// possible fallback chain for a given locale. The simplest case is a one-locale list.
30
+ /// ```
31
+ /// use fluent_bundle::bundle::FluentBundle;
32
+ /// use fluent_bundle::resource::FluentResource;
33
+ /// use fluent_bundle::types::FluentValue;
34
+ /// use std::collections::HashMap;
32
35
///
33
- /// Next, call `add_messages` one or more times, supplying translations in the FTL syntax. The
36
+ /// let resource = FluentResource::try_new("intro = Welcome, { $name }.".to_string()).unwrap();
37
+ /// let mut bundle = FluentBundle::new(&["en-US"]);
38
+ /// bundle.add_resource(&resource);
39
+ ///
40
+ /// let mut args = HashMap::new();
41
+ /// args.insert("name", FluentValue::from("Rustacean"));
42
+ ///
43
+ /// let value = bundle.format("intro", Some(&args));
44
+ /// assert_eq!(value, Some(("Welcome, Rustacean.".to_string(), vec![])));
45
+ ///
46
+ /// ```
47
+ ///
48
+ /// # `FluentBundle` Life Cycle
49
+ ///
50
+ /// To create a bundle, call [`FluentBundle::new`] with a locale list that represents the best
51
+ /// possible fallback chain for a given locale. The simplest case is a one-locale list.
52
+ ///
53
+ /// Next, call [`add_resource`] one or more times, supplying translations in the FTL syntax. The
34
54
/// `FluentBundle` instance is now ready to be used for localization.
35
55
///
36
- /// To format a translation, call `get_message` to retrieve a `fluent_bundle::bundle::Message` structure
37
- /// and then `format` it within the bundle.
56
+ /// To format a translation, call [`format`] with the path of a message or attribute in order to
57
+ /// retrieve the translated string. Alternately, [`format_message`] provides a convenient way of
58
+ /// formatting all attributes of a message at once.
38
59
///
39
- /// The result is an Option wrapping a single string that should be displayed in the UI. It is
60
+ /// The result of `format` is an [`Option<T>`] wrapping a `(String, Vec<FluentError>)`. On success,
61
+ /// the string is a formatted value that should be displayed in the UI. It is
40
62
/// recommended to treat the result as opaque from the perspective of the program and use it only
41
63
/// to display localized messages. Do not examine it or alter in any way before displaying. This
42
64
/// is a general good practice as far as all internationalization operations are concerned.
@@ -48,13 +70,36 @@ pub struct Message {
48
70
/// purpose of language negotiation with i18n formatters. For instance, if date and time formatting
49
71
/// are not available in the first locale, `FluentBundle` will use its `locales` fallback chain
50
72
/// to negotiate a sensible fallback for date and time formatting.
73
+ ///
74
+ /// [`add_resource`]: ./struct.FluentBundle.html#method.add_resource
75
+ /// [`FluentBundle::new`]: ./struct.FluentBundle.html#method.new
76
+ /// [`fluent::bundle::Message`]: ./struct.FluentBundle.html#method.new
77
+ /// [`format`]: ./struct.FluentBundle.html#method.format
78
+ /// [`format_message`]: ./struct.FluentBundle.html#method.format_message
79
+ /// [`add_resource`]: ./struct.FluentBundle.html#method.add_resource
80
+ /// [`Option<T>`]: http://doc.rust-lang.org/std/option/enum.Option.html
51
81
pub struct FluentBundle < ' bundle > {
52
82
pub locales : Vec < String > ,
53
83
pub entries : HashMap < String , Entry < ' bundle > > ,
54
84
pub plural_rules : IntlPluralRules ,
55
85
}
56
86
57
87
impl < ' bundle > FluentBundle < ' bundle > {
88
+ /// Constructs a FluentBundle. `locales` is the fallback chain of locales
89
+ /// to use for formatters like date and time. `locales` does not influence
90
+ /// message selection.
91
+ ///
92
+ /// # Examples
93
+ ///
94
+ /// ```
95
+ /// use fluent_bundle::bundle::FluentBundle;
96
+ ///
97
+ /// let mut bundle = FluentBundle::new(&["en-US"]);
98
+ /// ```
99
+ ///
100
+ /// # Errors
101
+ ///
102
+ /// This will panic if no formatters can be found for the locales.
58
103
pub fn new < ' a , S : ToString > ( locales : & ' a [ S ] ) -> FluentBundle < ' bundle > {
59
104
let locales = locales. iter ( ) . map ( |s| s. to_string ( ) ) . collect :: < Vec < _ > > ( ) ;
60
105
let pr_locale = negotiate_languages (
@@ -73,10 +118,53 @@ impl<'bundle> FluentBundle<'bundle> {
73
118
}
74
119
}
75
120
121
+ /// Returns true if this bundle contains a message with the given id.
122
+ ///
123
+ /// # Examples
124
+ ///
125
+ /// ```
126
+ /// use fluent_bundle::bundle::FluentBundle;
127
+ /// use fluent_bundle::resource::FluentResource;
128
+ ///
129
+ /// let resource = FluentResource::try_new("hello = Hi!".to_string()).unwrap();
130
+ /// let mut bundle = FluentBundle::new(&["en-US"]);
131
+ /// bundle.add_resource(&resource);
132
+ /// assert_eq!(true, bundle.has_message("hello"));
133
+ ///
134
+ /// ```
76
135
pub fn has_message ( & self , id : & str ) -> bool {
77
136
self . entries . get_message ( id) . is_some ( )
78
137
}
79
138
139
+ /// Makes the provided rust function available to messages with the name `id`. See
140
+ /// the [FTL syntax guide] to learn how these are used in messages.
141
+ ///
142
+ /// FTL functions accept both positional and named args. The rust function you
143
+ /// provide therefore has two parameters: a slice of values for the positional
144
+ /// args, and a HashMap of values for named args.
145
+ ///
146
+ /// # Examples
147
+ ///
148
+ /// ```
149
+ /// use fluent_bundle::bundle::FluentBundle;
150
+ /// use fluent_bundle::resource::FluentResource;
151
+ /// use fluent_bundle::types::FluentValue;
152
+ ///
153
+ /// let resource = FluentResource::try_new("length = { STRLEN(\"12345\") }".to_string()).unwrap();
154
+ /// let mut bundle = FluentBundle::new(&["en-US"]);
155
+ /// bundle.add_resource(&resource);
156
+ ///
157
+ /// // Register a fn that maps from string to string length
158
+ /// bundle.add_function("STRLEN", |positional, _named| match positional {
159
+ /// [Some(FluentValue::String(str))] => Some(FluentValue::Number(str.len().to_string())),
160
+ /// _ => None,
161
+ /// }).unwrap();
162
+ ///
163
+ /// let (value, _) = bundle.format("length", None).unwrap();
164
+ /// assert_eq!(&value, "5");
165
+ /// ```
166
+ ///
167
+ /// [FTL syntax guide]: https://projectfluent.org/fluent/guide/functions.html
80
168
pub fn add_function < F > ( & mut self , id : & str , func : F ) -> Result < ( ) , FluentError >
81
169
where
82
170
F : ' bundle
@@ -96,6 +184,35 @@ impl<'bundle> FluentBundle<'bundle> {
96
184
}
97
185
}
98
186
187
+ /// Adds the message or messages, in [FTL syntax], to the bundle, returning an
188
+ /// empty [`Result<T>`] on success.
189
+ ///
190
+ /// # Examples
191
+ ///
192
+ /// ```
193
+ /// use fluent_bundle::bundle::FluentBundle;
194
+ /// use fluent_bundle::resource::FluentResource;
195
+ ///
196
+ /// let resource = FluentResource::try_new("
197
+ /// hello = Hi!
198
+ /// goodbye = Bye!
199
+ /// ".to_string()).unwrap();
200
+ /// let mut bundle = FluentBundle::new(&["en-US"]);
201
+ /// bundle.add_resource(&resource);
202
+ /// assert_eq!(true, bundle.has_message("hello"));
203
+ /// ```
204
+ ///
205
+ /// # Whitespace
206
+ ///
207
+ /// Message ids must have no leading whitespace. Message values that span
208
+ /// multiple lines must have leading whitespace on all but the first line. These
209
+ /// are standard FTL syntax rules that may prove a bit troublesome in source
210
+ /// code formatting. The [`indoc!`] crate can help with stripping extra indentation
211
+ /// if you wish to indent your entire message.
212
+ ///
213
+ /// [FTL syntax]: https://projectfluent.org/fluent/guide/
214
+ /// [`indoc!`]: https://github.com/dtolnay/indoc
215
+ /// [`Result<T>`]: https://doc.rust-lang.org/std/result/enum.Result.html
99
216
pub fn add_resource ( & mut self , res : & ' bundle FluentResource ) -> Result < ( ) , Vec < FluentError > > {
100
217
let mut errors = vec ! [ ] ;
101
218
@@ -134,6 +251,75 @@ impl<'bundle> FluentBundle<'bundle> {
134
251
}
135
252
}
136
253
254
+ /// Formats the message value identified by `path` using `args` to
255
+ /// provide variables. `path` is either a message id ("hello"), or
256
+ /// message id plus attribute ("hello.tooltip").
257
+ ///
258
+ /// # Examples
259
+ ///
260
+ /// ```
261
+ /// use fluent_bundle::bundle::FluentBundle;
262
+ /// use fluent_bundle::resource::FluentResource;
263
+ /// use fluent_bundle::types::FluentValue;
264
+ /// use std::collections::HashMap;
265
+ ///
266
+ /// let resource = FluentResource::try_new("intro = Welcome, { $name }.".to_string()).unwrap();
267
+ /// let mut bundle = FluentBundle::new(&["en-US"]);
268
+ /// bundle.add_resource(&resource);
269
+ ///
270
+ /// let mut args = HashMap::new();
271
+ /// args.insert("name", FluentValue::from("Rustacean"));
272
+ ///
273
+ /// let value = bundle.format("intro", Some(&args));
274
+ /// assert_eq!(value, Some(("Welcome, Rustacean.".to_string(), vec![])));
275
+ ///
276
+ /// ```
277
+ ///
278
+ /// An example with attributes and no args:
279
+ ///
280
+ /// ```
281
+ /// use fluent_bundle::bundle::FluentBundle;
282
+ /// use fluent_bundle::resource::FluentResource;
283
+ ///
284
+ /// let resource = FluentResource::try_new("
285
+ /// hello =
286
+ /// .title = Hi!
287
+ /// .tooltip = This says 'Hi!'
288
+ /// ".to_string()).unwrap();
289
+ /// let mut bundle = FluentBundle::new(&["en-US"]);
290
+ /// bundle.add_resource(&resource);
291
+ ///
292
+ /// let value = bundle.format("hello.title", None);
293
+ /// assert_eq!(value, Some(("Hi!".to_string(), vec![])));
294
+ /// ```
295
+ ///
296
+ /// # Errors
297
+ ///
298
+ /// For some cases where `format` can't find a message it will return `None`.
299
+ ///
300
+ /// In all other cases `format` returns a string even if it
301
+ /// encountered errors. Generally, during partial errors `format` will
302
+ /// use `'___'` to replace parts of the formatted message that it could
303
+ /// not successfuly build. For more fundamental errors `format` will return
304
+ /// the path itself as the translation.
305
+ ///
306
+ /// The second term of the tuple will contain any extra error information
307
+ /// gathered during formatting. A caller may safely ignore the extra errors
308
+ /// if the fallback formatting policies are acceptable.
309
+ ///
310
+ /// ```
311
+ /// use fluent_bundle::bundle::FluentBundle;
312
+ /// use fluent_bundle::resource::FluentResource;
313
+ ///
314
+ /// // Create a message with bad cyclic reference
315
+ /// let mut res = FluentResource::try_new("foo = a { foo } b".to_string()).unwrap();
316
+ /// let mut bundle = FluentBundle::new(&["en-US"]);
317
+ /// bundle.add_resource(&res);
318
+ ///
319
+ /// // The result falls back to "___"
320
+ /// let value = bundle.format("foo", None);
321
+ /// assert_eq!(value, Some(("___".to_string(), vec![])));
322
+ /// ```
137
323
pub fn format (
138
324
& self ,
139
325
path : & str ,
@@ -185,6 +371,45 @@ impl<'bundle> FluentBundle<'bundle> {
185
371
None
186
372
}
187
373
374
+ /// Formats both the message value and attributes identified by `message_id`
375
+ /// using `args` to provide variables. This is useful for cases where a UI
376
+ /// element requires multiple related text fields, such as a button that has
377
+ /// both display text and assistive text.
378
+ ///
379
+ /// # Examples
380
+ ///
381
+ /// ```
382
+ /// use fluent_bundle::bundle::FluentBundle;
383
+ /// use fluent_bundle::resource::FluentResource;
384
+ /// use fluent_bundle::types::FluentValue;
385
+ /// use std::collections::HashMap;
386
+ ///
387
+ /// let mut res = FluentResource::try_new("
388
+ /// login-input = Predefined value
389
+ /// .placeholder = [email protected]
390
+ /// .aria-label = Login input value
391
+ /// .title = Type your login email".to_string()).unwrap();
392
+ /// let mut bundle = FluentBundle::new(&["en-US"]);
393
+ /// bundle.add_resource(&res);
394
+ ///
395
+ /// let (message, _) = bundle.format_message("login-input", None).unwrap();
396
+ /// assert_eq!(message.value, Some("Predefined value".to_string()));
397
+ /// assert_eq!(message.attributes.get("title"), Some(&"Type your login email".to_string()));
398
+ /// ```
399
+ ///
400
+ /// # Errors
401
+ ///
402
+ /// For some cases where `format_message` can't find a message it will return `None`.
403
+ ///
404
+ /// In all other cases `format_message` returns a message even if it
405
+ /// encountered errors. Generally, during partial errors `format_message` will
406
+ /// use `'___'` to replace parts of the formatted message that it could
407
+ /// not successfuly build. For more fundamental errors `format_message` will return
408
+ /// the path itself as the translation.
409
+ ///
410
+ /// The second term of the tuple will contain any extra error information
411
+ /// gathered during formatting. A caller may safely ignore the extra errors
412
+ /// if the fallback formatting policies are acceptable.
188
413
pub fn format_message (
189
414
& self ,
190
415
message_id : & str ,
0 commit comments