Skip to content

Commit 442a979

Browse files
authored
Merge pull request #1 from JohnHeitmann/docs
Add docs for FluentBundle
2 parents aebb85d + 9b1d55e commit 442a979

File tree

1 file changed

+234
-9
lines changed

1 file changed

+234
-9
lines changed

fluent-bundle/src/bundle.rs

Lines changed: 234 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,43 @@ pub struct Message {
2222
pub attributes: HashMap<String, String>,
2323
}
2424

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.
2727
///
28-
/// # `FluentBundle` Life-cycle
28+
/// # Examples
2929
///
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;
3235
///
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
3454
/// `FluentBundle` instance is now ready to be used for localization.
3555
///
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.
3859
///
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
4062
/// recommended to treat the result as opaque from the perspective of the program and use it only
4163
/// to display localized messages. Do not examine it or alter in any way before displaying. This
4264
/// is a general good practice as far as all internationalization operations are concerned.
@@ -48,13 +70,36 @@ pub struct Message {
4870
/// purpose of language negotiation with i18n formatters. For instance, if date and time formatting
4971
/// are not available in the first locale, `FluentBundle` will use its `locales` fallback chain
5072
/// 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
5181
pub struct FluentBundle<'bundle> {
5282
pub locales: Vec<String>,
5383
pub entries: HashMap<String, Entry<'bundle>>,
5484
pub plural_rules: IntlPluralRules,
5585
}
5686

5787
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.
58103
pub fn new<'a, S: ToString>(locales: &'a [S]) -> FluentBundle<'bundle> {
59104
let locales = locales.iter().map(|s| s.to_string()).collect::<Vec<_>>();
60105
let pr_locale = negotiate_languages(
@@ -73,10 +118,53 @@ impl<'bundle> FluentBundle<'bundle> {
73118
}
74119
}
75120

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+
/// ```
76135
pub fn has_message(&self, id: &str) -> bool {
77136
self.entries.get_message(id).is_some()
78137
}
79138

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
80168
pub fn add_function<F>(&mut self, id: &str, func: F) -> Result<(), FluentError>
81169
where
82170
F: 'bundle
@@ -96,6 +184,35 @@ impl<'bundle> FluentBundle<'bundle> {
96184
}
97185
}
98186

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
99216
pub fn add_resource(&mut self, res: &'bundle FluentResource) -> Result<(), Vec<FluentError>> {
100217
let mut errors = vec![];
101218

@@ -134,6 +251,75 @@ impl<'bundle> FluentBundle<'bundle> {
134251
}
135252
}
136253

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+
/// ```
137323
pub fn format(
138324
&self,
139325
path: &str,
@@ -185,6 +371,45 @@ impl<'bundle> FluentBundle<'bundle> {
185371
None
186372
}
187373

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.
188413
pub fn format_message(
189414
&self,
190415
message_id: &str,

0 commit comments

Comments
 (0)