Skip to content

Commit 8fc7678

Browse files
authored
Add more documentation to fluent-bundle (#274)
Merge commit
2 parents dc485ea + 440f1c3 commit 8fc7678

File tree

8 files changed

+159
-25
lines changed

8 files changed

+159
-25
lines changed

fluent-bundle/src/args.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,17 @@ use std::iter::FromIterator;
33

44
use crate::types::FluentValue;
55

6-
/// A map of arguments passed from the code to
7-
/// the localization to be used for message
8-
/// formatting.
6+
/// Fluent messages can use arguments in order to programmatically add values to a
7+
/// translated string. For instance, in a localized application you may wish to display
8+
/// a user's email count. This could be done with the following message.
9+
///
10+
/// `msg-key = Hello, { $user }. You have { $emailCount } messages.`
11+
///
12+
/// Here `$user` and `$emailCount` are the arguments, which can be filled with values.
13+
///
14+
/// The [`FluentArgs`] struct is the map from the argument name (for example `$user`) to
15+
/// the argument value (for example "John".) The logic to apply these to write these
16+
/// to messages is elsewhere, this struct just stores the value.
917
///
1018
/// # Example
1119
///
@@ -48,14 +56,17 @@ use crate::types::FluentValue;
4856
pub struct FluentArgs<'args>(Vec<(Cow<'args, str>, FluentValue<'args>)>);
4957

5058
impl<'args> FluentArgs<'args> {
59+
/// Creates a new empty argument map.
5160
pub fn new() -> Self {
5261
Self::default()
5362
}
5463

64+
/// Pre-allocates capacity for arguments.
5565
pub fn with_capacity(capacity: usize) -> Self {
5666
Self(Vec::with_capacity(capacity))
5767
}
5868

69+
/// Gets the [`FluentValue`] at the `key` if it exists.
5970
pub fn get<K>(&self, key: K) -> Option<&FluentValue<'args>>
6071
where
6172
K: Into<Cow<'args, str>>,
@@ -68,6 +79,7 @@ impl<'args> FluentArgs<'args> {
6879
}
6980
}
7081

82+
/// Sets the key value pair.
7183
pub fn set<K, V>(&mut self, key: K, value: V)
7284
where
7385
K: Into<Cow<'args, str>>,
@@ -80,6 +92,7 @@ impl<'args> FluentArgs<'args> {
8092
};
8193
}
8294

95+
/// Iterate over a tuple of the key an [`FluentValue`].
8396
pub fn iter(&self) -> impl Iterator<Item = (&str, &FluentValue)> {
8497
self.0.iter().map(|(k, v)| (k.as_ref(), v))
8598
}

fluent-bundle/src/entry.rs

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
//! `Entry` is used to store Messages, Terms and Functions in `FluentBundle` instances.
1+
//! `Entry` is used to store the lookup information for Messages, Terms and Functions in
2+
//! `FluentBundle` instances.
23
34
use std::borrow::Borrow;
45

@@ -12,24 +13,34 @@ use crate::types::FluentValue;
1213
pub type FluentFunction =
1314
Box<dyn for<'a> Fn(&[FluentValue<'a>], &FluentArgs) -> FluentValue<'a> + Send + Sync>;
1415

16+
type ResourceIdx = usize;
17+
type EntryIdx = usize;
18+
19+
/// The [`Entry`] stores indexes into the [`FluentBundle`]'s resources for Messages and Terms,
20+
/// and owns the [`Box`] pointers to the [`FluentFunction`].
1521
pub enum Entry {
16-
Message((usize, usize)),
17-
Term((usize, usize)),
22+
Message((ResourceIdx, EntryIdx)),
23+
Term((ResourceIdx, EntryIdx)),
1824
Function(FluentFunction),
1925
}
2026

2127
pub trait GetEntry {
28+
/// Looks up a message by its string ID, and returns it if it exists.
2229
fn get_entry_message(&self, id: &str) -> Option<&ast::Message<&str>>;
30+
31+
/// Looks up a term by its string ID, and returns it if it exists.
2332
fn get_entry_term(&self, id: &str) -> Option<&ast::Term<&str>>;
33+
34+
/// Looks up a function by its string ID, and returns it if it exists.
2435
fn get_entry_function(&self, id: &str) -> Option<&FluentFunction>;
2536
}
2637

2738
impl<'bundle, R: Borrow<FluentResource>, M> GetEntry for FluentBundle<R, M> {
2839
fn get_entry_message(&self, id: &str) -> Option<&ast::Message<&str>> {
2940
self.entries.get(id).and_then(|ref entry| match entry {
30-
Entry::Message(pos) => {
31-
let res = self.resources.get(pos.0)?.borrow();
32-
if let ast::Entry::Message(ref msg) = res.get_entry(pos.1)? {
41+
Entry::Message((resource_idx, entry_idx)) => {
42+
let res = self.resources.get(*resource_idx)?.borrow();
43+
if let ast::Entry::Message(ref msg) = res.get_entry(*entry_idx)? {
3344
Some(msg)
3445
} else {
3546
None
@@ -41,9 +52,9 @@ impl<'bundle, R: Borrow<FluentResource>, M> GetEntry for FluentBundle<R, M> {
4152

4253
fn get_entry_term(&self, id: &str) -> Option<&ast::Term<&str>> {
4354
self.entries.get(id).and_then(|ref entry| match entry {
44-
Entry::Term(pos) => {
45-
let res = self.resources.get(pos.0)?.borrow();
46-
if let ast::Entry::Term(ref msg) = res.get_entry(pos.1)? {
55+
Entry::Term((resource_idx, entry_idx)) => {
56+
let res = self.resources.get(*resource_idx)?.borrow();
57+
if let ast::Entry::Term(ref msg) = res.get_entry(*entry_idx)? {
4758
Some(msg)
4859
} else {
4960
None

fluent-bundle/src/memoizer.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,29 @@ use crate::types::FluentType;
22
use intl_memoizer::Memoizable;
33
use unic_langid::LanguageIdentifier;
44

5+
/// This trait contains thread-safe methods which extend [intl_memoizer::IntlLangMemoizer].
6+
/// It is used as the generic bound in this crate when a memoizer is needed.
57
pub trait MemoizerKind: 'static {
68
fn new(lang: LanguageIdentifier) -> Self
79
where
810
Self: Sized;
911

10-
fn with_try_get_threadsafe<I, R, U>(&self, args: I::Args, cb: U) -> Result<R, I::Error>
12+
/// A threadsafe variant of `with_try_get` from [intl_memoizer::IntlLangMemoizer].
13+
/// The generics enforce that `Self` and its arguments are actually threadsafe.
14+
///
15+
/// `I` - The [Memoizable](intl_memoizer::Memoizable) internationalization formatter.
16+
///
17+
/// `R` - The result from the format operation.
18+
///
19+
/// `U` - The callback that accepts the instance of the intl formatter, and generates
20+
/// some kind of results `R`.
21+
fn with_try_get_threadsafe<I, R, U>(&self, args: I::Args, callback: U) -> Result<R, I::Error>
1122
where
1223
Self: Sized,
1324
I: Memoizable + Send + Sync + 'static,
1425
I::Args: Send + Sync + 'static,
1526
U: FnOnce(&I) -> R;
1627

28+
/// Wires up the `as_string` or `as_string_threadsafe` variants for [`FluentType`].
1729
fn stringify_value(&self, value: &dyn FluentType) -> std::borrow::Cow<'static, str>;
1830
}

fluent-bundle/src/resolver/errors.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
use fluent_syntax::ast::InlineExpression;
22
use std::error::Error;
33

4+
/// Maps an [`InlineExpression`] into the kind of reference, with owned strings
5+
/// that identify the expression. This makes it so that the [`InlineExpression`] can
6+
/// be used to generate an error string.
47
#[derive(Debug, PartialEq, Clone)]
58
pub enum ReferenceKind {
69
Function {
@@ -44,6 +47,8 @@ where
4447
}
4548
}
4649

50+
/// Errors generated during the process of resolving a fluent message into a string.
51+
/// This process takes place in the `write` method of the `WriteValue` trait.
4752
#[derive(Debug, PartialEq, Clone)]
4853
pub enum ResolverError {
4954
Reference(ReferenceKind),

fluent-bundle/src/resolver/mod.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
//! The `resolver` module contains the definitions and implementations for the internal
2+
//! `ResolveValue` and `WriteValue` traits. The former converts AST nodes to a
3+
//! [`FluentValue`], and the latter converts them to a string that is written to an
4+
//! implementor of the [`std::fmt::Write`] trait.
5+
16
pub mod errors;
27
mod expression;
38
mod inline_expression;
@@ -14,8 +19,9 @@ use crate::memoizer::MemoizerKind;
1419
use crate::resource::FluentResource;
1520
use crate::types::FluentValue;
1621

17-
// Converts an AST node to a `FluentValue`.
22+
/// Resolves an AST node to a [`FluentValue`].
1823
pub(crate) trait ResolveValue<'bundle> {
24+
/// Resolves an AST node to a [`FluentValue`].
1925
fn resolve<'ast, 'args, 'errors, R, M>(
2026
&'ast self,
2127
scope: &mut Scope<'bundle, 'ast, 'args, 'errors, R, M>,
@@ -25,7 +31,9 @@ pub(crate) trait ResolveValue<'bundle> {
2531
M: MemoizerKind;
2632
}
2733

34+
/// Resolves an AST node to a string that is written to source `W`.
2835
pub(crate) trait WriteValue<'bundle> {
36+
/// Resolves an AST node to a string that is written to source `W`.
2937
fn write<'ast, 'args, 'errors, W, R, M>(
3038
&'ast self,
3139
w: &mut W,
@@ -36,6 +44,8 @@ pub(crate) trait WriteValue<'bundle> {
3644
R: Borrow<FluentResource>,
3745
M: MemoizerKind;
3846

47+
/// Writes error information to `W`. This can be used to add FTL errors inline
48+
/// to a message.
3949
fn write_error<W>(&self, _w: &mut W) -> fmt::Result
4050
where
4151
W: fmt::Write;

fluent-bundle/src/resolver/scope.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,11 @@ impl<'bundle, 'ast, 'args, 'errors, R, M> Scope<'bundle, 'ast, 'args, 'errors, R
4949
}
5050
}
5151

52-
// This method allows us to lazily add Pattern on the stack,
53-
// only if the Pattern::resolve has been called on an empty stack.
54-
//
55-
// This is the case when pattern is called from Bundle and it
56-
// allows us to fast-path simple resolutions, and only use the stack
57-
// for placeables.
52+
/// This method allows us to lazily add Pattern on the stack, only if the
53+
/// Pattern::resolve has been called on an empty stack.
54+
///
55+
/// This is the case when pattern is called from Bundle and it allows us to fast-path
56+
/// simple resolutions, and only use the stack for placeables.
5857
pub fn maybe_track<W>(
5958
&mut self,
6059
w: &mut W,

fluent-bundle/src/types/mod.rs

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,19 @@ use crate::memoizer::MemoizerKind;
2828
use crate::resolver::Scope;
2929
use crate::resource::FluentResource;
3030

31+
/// Custom types can implement the [`FluentType`] trait in order to generate a string
32+
/// value for use in the message generation process.
3133
pub trait FluentType: fmt::Debug + AnyEq + 'static {
34+
/// Create a clone of the underlying type.
3235
fn duplicate(&self) -> Box<dyn FluentType + Send>;
36+
37+
/// Convert the custom type into a string value, for instance a custom DateTime
38+
/// type could return "Oct. 27, 2022".
3339
fn as_string(&self, intls: &intl_memoizer::IntlLangMemoizer) -> Cow<'static, str>;
40+
41+
/// Convert the custom type into a string value, for instance a custom DateTime
42+
/// type could return "Oct. 27, 2022". This operation is provided the threadsafe
43+
/// [IntlLangMemoizer](intl_memoizer::concurrent::IntlLangMemoizer).
3444
fn as_string_threadsafe(
3545
&self,
3646
intls: &intl_memoizer::concurrent::IntlLangMemoizer,
@@ -101,15 +111,72 @@ impl<'s> Clone for FluentValue<'s> {
101111
}
102112

103113
impl<'source> FluentValue<'source> {
104-
pub fn try_number<S: ToString>(v: S) -> Self {
105-
let s = v.to_string();
106-
if let Ok(num) = FluentNumber::from_str(&s) {
107-
num.into()
114+
/// Attempts to parse the string representation of a `value` that supports
115+
/// [`ToString`] into a [`FluentValue::Number`]. If it fails, it will instead
116+
/// convert it to a [`FluentValue::String`].
117+
///
118+
/// ```
119+
/// use fluent_bundle::types::{FluentNumber, FluentNumberOptions, FluentValue};
120+
///
121+
/// // "2" parses into a `FluentNumber`
122+
/// assert_eq!(
123+
/// FluentValue::try_number("2"),
124+
/// FluentValue::Number(FluentNumber::new(2.0, FluentNumberOptions::default()))
125+
/// );
126+
///
127+
/// // Floats can be parsed as well.
128+
/// assert_eq!(
129+
/// FluentValue::try_number("3.141569"),
130+
/// FluentValue::Number(FluentNumber::new(
131+
/// 3.141569,
132+
/// FluentNumberOptions {
133+
/// minimum_fraction_digits: Some(6),
134+
/// ..Default::default()
135+
/// }
136+
/// ))
137+
/// );
138+
///
139+
/// // When a value is not a valid number, it falls back to a `FluentValue::String`
140+
/// assert_eq!(
141+
/// FluentValue::try_number("A string"),
142+
/// FluentValue::String("A string".into())
143+
/// );
144+
/// ```
145+
pub fn try_number<S: ToString>(value: S) -> Self {
146+
let string = value.to_string();
147+
if let Ok(number) = FluentNumber::from_str(&string) {
148+
number.into()
108149
} else {
109-
s.into()
150+
string.into()
110151
}
111152
}
112153

154+
/// Checks to see if two [`FluentValues`](FluentValue) match each other by having the
155+
/// same type and contents. The special exception is in the case of a string being
156+
/// compared to a number. Here attempt to check that the plural rule category matches.
157+
///
158+
/// ```
159+
/// use fluent_bundle::resolver::Scope;
160+
/// use fluent_bundle::{types::FluentValue, FluentBundle, FluentResource};
161+
/// use unic_langid::langid;
162+
///
163+
/// let langid_ars = langid!("en");
164+
/// let bundle: FluentBundle<FluentResource> = FluentBundle::new(vec![langid_ars]);
165+
/// let scope = Scope::new(&bundle, None, None);
166+
///
167+
/// // Matching examples:
168+
/// assert!(FluentValue::try_number("2").matches(&FluentValue::try_number("2"), &scope));
169+
/// assert!(FluentValue::from("fluent").matches(&FluentValue::from("fluent"), &scope));
170+
/// assert!(
171+
/// FluentValue::from("one").matches(&FluentValue::try_number("1"), &scope),
172+
/// "Plural rules are matched."
173+
/// );
174+
///
175+
/// // Non-matching examples:
176+
/// assert!(!FluentValue::try_number("2").matches(&FluentValue::try_number("3"), &scope));
177+
/// assert!(!FluentValue::from("fluent").matches(&FluentValue::from("not fluent"), &scope));
178+
/// assert!(!FluentValue::from("two").matches(&FluentValue::try_number("100"), &scope),);
179+
/// ```
113180
pub fn matches<R: Borrow<FluentResource>, M>(
114181
&self,
115182
other: &FluentValue,
@@ -131,6 +198,8 @@ impl<'source> FluentValue<'source> {
131198
"other" => PluralCategory::OTHER,
132199
_ => return false,
133200
};
201+
// This string matches a plural rule keyword. Check if the number
202+
// matches the plural rule category.
134203
scope
135204
.bundle
136205
.intls
@@ -144,6 +213,7 @@ impl<'source> FluentValue<'source> {
144213
}
145214
}
146215

216+
/// Write out a string version of the [`FluentValue`] to `W`.
147217
pub fn write<W, R, M>(&self, w: &mut W, scope: &Scope<R, M>) -> fmt::Result
148218
where
149219
W: fmt::Write,
@@ -164,6 +234,7 @@ impl<'source> FluentValue<'source> {
164234
}
165235
}
166236

237+
/// Converts the [`FluentValue`] to a string.
167238
pub fn as_string<R: Borrow<FluentResource>, M>(&self, scope: &Scope<R, M>) -> Cow<'source, str>
168239
where
169240
M: MemoizerKind,

fluent-syntax/src/ast/mod.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1438,9 +1438,22 @@ pub enum InlineExpression<S> {
14381438
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
14391439
#[cfg_attr(feature = "serde", serde(untagged))]
14401440
pub enum Expression<S> {
1441+
/// A select expression such as:
1442+
/// ```ftl
1443+
/// key = { $var ->
1444+
/// [key1] Value 1
1445+
/// *[other] Value 2
1446+
/// }
1447+
/// ```
14411448
Select {
14421449
selector: InlineExpression<S>,
14431450
variants: Vec<Variant<S>>,
14441451
},
1452+
1453+
/// An inline expression such as `${ username }`:
1454+
///
1455+
/// ```ftl
1456+
/// hello-user = Hello ${ username }
1457+
/// ```
14451458
Inline(InlineExpression<S>),
14461459
}

0 commit comments

Comments
 (0)