Skip to content

Update FluentBundle to the latest API #120

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 24, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions fluent-bundle/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@ readme = "README.md"
keywords = ["localization", "l10n", "i18n", "intl", "internationalization"]
categories = ["localization", "internationalization"]

[dev-dependencies]
criterion = "^0.2"

[dependencies]
fluent-locale = "^0.4.1"
fluent-syntax = "^0.9"
Expand All @@ -29,6 +26,12 @@ intl_pluralrules = "^1.0"
rental = "^0.5.4"
smallvec = "0.6.10"

[dev-dependencies]
criterion = "^0.2"
serde = { version = "^1.0", features = ["derive"] }
serde_yaml = "^0.8"
rand = "^0.7.0"

[[bench]]
name = "resolver"
harness = false
34 changes: 22 additions & 12 deletions fluent-bundle/benches/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,20 +38,23 @@ fn get_ids(res: &FluentResource) -> Vec<String> {
.collect()
}

fn get_args(name: &str) -> Option<HashMap<&'static str, FluentValue>> {
fn get_args(name: &str) -> Option<HashMap<String, FluentValue>> {
match name {
"preferences" => {
let mut prefs_args = HashMap::new();
prefs_args.insert("name", FluentValue::from("John"));
prefs_args.insert("tabCount", FluentValue::from(5));
prefs_args.insert("count", FluentValue::from(3));
prefs_args.insert("version", FluentValue::from("65.0"));
prefs_args.insert("path", FluentValue::from("/tmp"));
prefs_args.insert("num", FluentValue::from(4));
prefs_args.insert("email", FluentValue::from("[email protected]"));
prefs_args.insert("value", FluentValue::from(4.5));
prefs_args.insert("unit", FluentValue::from("mb"));
prefs_args.insert("service-name", FluentValue::from("Mozilla Disk"));
prefs_args.insert("name".to_string(), FluentValue::from("John"));
prefs_args.insert("tabCount".to_string(), FluentValue::from(5));
prefs_args.insert("count".to_string(), FluentValue::from(3));
prefs_args.insert("version".to_string(), FluentValue::from("65.0"));
prefs_args.insert("path".to_string(), FluentValue::from("/tmp"));
prefs_args.insert("num".to_string(), FluentValue::from(4));
prefs_args.insert("email".to_string(), FluentValue::from("[email protected]"));
prefs_args.insert("value".to_string(), FluentValue::from(4.5));
prefs_args.insert("unit".to_string(), FluentValue::from("mb"));
prefs_args.insert(
"service-name".to_string(),
FluentValue::from("Mozilla Disk"),
);
Some(prefs_args)
}
_ => None,
Expand Down Expand Up @@ -91,7 +94,14 @@ fn resolver_bench(c: &mut Criterion) {

b.iter(|| {
for id in &ids {
let (_msg, errors) = bundle.compound(id, args.as_ref()).expect("Message found");
let msg = bundle.get_message(id).expect("Message found");
let mut errors = vec![];
if let Some(value) = msg.value {
let _ = bundle.format_pattern(value, args.as_ref(), &mut errors);
}
for (_, value) in msg.attributes {
let _ = bundle.format_pattern(value, args.as_ref(), &mut errors);
}
assert!(errors.len() == 0, "Resolver errors: {:#?}", errors);
}
})
Expand Down
35 changes: 21 additions & 14 deletions fluent-bundle/examples/external_arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,30 @@ unread-emails =
.expect("Failed to add FTL resources to the bundle.");

let mut args = HashMap::new();
args.insert("name", FluentValue::from("John"));
args.insert("name".to_string(), FluentValue::from("John"));

match bundle.format("hello-world", Some(&args)) {
Some((value, _)) => println!("{}", value),
_ => println!("None"),
}
let msg = bundle
.get_message("hello-world")
.expect("Message doesn't exist.");
let mut errors = vec![];
let pattern = msg.value.expect("Message has no value.");
let value = bundle.format_pattern(&pattern, Some(&args), &mut errors);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nicer to use if let Some(value) = msg.value { .. } guard around the call to format_pattern in all examples, because it's much closer to a real-world usage of the API.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm struggling with that a bit. I know this is a low level API that we expect to build on top of, but if someone would use it in their app, they wouldn't branch for a scenario where the string is missing, they'd expect the value of a string to be there if they placed it in their FTL file.

I don't want to suggest to localize an app by replacing:

println!("Enter your age:");

with:

if let Some(value) = msg.value {
  println!(bundle.format_pattern(value, None));
} else {
  unimplemented!()
}

Copy link
Contributor

@stasm stasm Jul 24, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if someone would use it in their app, they wouldn't branch for a scenario where the string is missing, they'd expect the value of a string to be there if they placed it in their FTL file.

This only works if their FTL file is the only FTL file in the app. As soon as they have more FTLs for more locales, this assumption may not hold anymore, depending on who wrote them. As you know, Fluent is designed around the idea that translations must not break the app. Handling that Option<Pattern> is an important enforcement of this design.

I know this is an example, and it's OK to simplify. But there is a chance people will look at examples to make their first steps around Fluent, and I'd like to take the opportunity to educate :) There's also a chance people will copy&paste directly from examples.

Also, it's approximately the same amount of code, and some might argue that the if let is actually nicer.

let pattern = msg.value.expect("Message has no value");
println!(bundle.format_pattern(&pattern, None));
if let Some(value) = msg.value {
  println!(bundle.format_pattern(value, None));
}

Or, to make the null-value case explicit, I'd put a comment in the else block, like so:

if let Some(value) = msg.value {
  println!(bundle.format_pattern(value, None));
} else {
  // Show the fallback translation, e.g. the id of the message.
}

Copy link
Collaborator Author

@zbraniecki zbraniecki Jul 24, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you know, Fluent is designed around the idea that translations must not break the app.

Yes, but we achieve it on a higher level. We introduce nice fallback mechanisms that handle the case where the user expects a value and there is none.

On this level there is no such mechanism, no such fallback.

I believe for a reader of an example, seeing:

if let Some(value) = msg.value {
  println!(bundle.format_pattern(value, None));
}

is bizarre. It's basically saying "try to localize the message, and if that fails, make your app show nothing at all".
I believe that .expect() is exactly the right call for the example. It says "Since you placed the value, expect it to be there, but please, remember that it's an Option and it may be null, so you'll likely have a macro/higher-level-api that will facilitate a fallback".

if let Some(value) = msg.value {
  println!(bundle.format_pattern(value, None));
} else {
  // Show the fallback translation, e.g. the id of the message.
}

This is more acceptable, but still kind of explicit. It gives the vibe of "Fluent makes your code way less readable.".

I'd prefer to use .expect to indicate that we expect the message, and I intend to improve docs to clearly state that the purpose of FluentBundle is to be handled by a higher level abstraction that will handle fallbacking, and since fallbacking may differ between bindings and apps, we make FluentBundle API clean for the higher level API to pick their strategy.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, that sounds fair, thanks!

println!("{}", value);

match bundle.format("ref", Some(&args)) {
Some((value, _)) => println!("{}", value),
_ => println!("None"),
}
let msg = bundle.get_message("ref").expect("Message doesn't exist.");
let mut errors = vec![];
let pattern = msg.value.expect("Message has no value.");
let value = bundle.format_pattern(&pattern, Some(&args), &mut errors);
println!("{}", value);

let mut args = HashMap::new();
args.insert("emailCount", FluentValue::into_number("1.0"));
args.insert("emailCount".to_string(), FluentValue::into_number("1.0"));

match bundle.format("unread-emails", Some(&args)) {
Some((value, _)) => println!("{}", value),
None => println!("None"),
}
let msg = bundle
.get_message("unread-emails")
.expect("Message doesn't exist.");
let mut errors = vec![];
let pattern = msg.value.expect("Message has no value.");
let value = bundle.format_pattern(&pattern, Some(&args), &mut errors);
println!("{}", value);
}
31 changes: 20 additions & 11 deletions fluent-bundle/examples/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ fn main() {
}
}

FluentValue::None()
FluentValue::None
})
.expect("Failed to add a function to the bundle.");

Expand All @@ -39,7 +39,7 @@ fn main() {
Some(FluentValue::String(ref string)) => {
format!("All your base belong to {}", string).into()
}
_ => FluentValue::None(),
_ => FluentValue::None,
};
})
.expect("Failed to add a function to the bundle.");
Expand All @@ -54,18 +54,27 @@ fn main() {
.add_resource(res3)
.expect("Failed to add FTL resources to the bundle.");

let (value, _) = bundle
.format("hello-world", None)
.expect("Failed to format a message.");
let msg = bundle
.get_message("hello-world")
.expect("Message doesn't exist.");
let mut errors = vec![];
let pattern = msg.value.expect("Message has no value.");
let value = bundle.format_pattern(&pattern, None, &mut errors);
assert_eq!(&value, "Hey there! \u{2068}I'm a function!\u{2069}");

let (value, _) = bundle
.format("meaning-of-life", None)
.expect("Failed to format a message.");
let msg = bundle
.get_message("meaning-of-life")
.expect("Message doesn't exist.");
let mut errors = vec![];
let pattern = msg.value.expect("Message has no value.");
let value = bundle.format_pattern(&pattern, None, &mut errors);
assert_eq!(&value, "The answer to life, the universe, and everything");

let (value, _) = bundle
.format("all-your-base", None)
.expect("Failed to format a message.");
let msg = bundle
.get_message("all-your-base")
.expect("Message doesn't exist.");
let mut errors = vec![];
let pattern = msg.value.expect("Message has no value.");
let value = bundle.format_pattern(&pattern, None, &mut errors);
assert_eq!(&value, "All your base belong to us");
}
10 changes: 7 additions & 3 deletions fluent-bundle/examples/hello.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@ fn main() {
bundle
.add_resource(&res)
.expect("Failed to add FTL resources to the bundle.");
let (value, _) = bundle
.format("hello-world", None)
.expect("Failed to format a message.");

let msg = bundle
.get_message("hello-world")
.expect("Message doesn't exist.");
let mut errors = vec![];
let pattern = msg.value.expect("Message has no value.");
let value = bundle.format_pattern(&pattern, None, &mut errors);
assert_eq!(&value, "Hello, world!");
}
22 changes: 14 additions & 8 deletions fluent-bundle/examples/message_reference.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,19 @@ bazbar = { baz } Bar
.add_resource(res)
.expect("Failed to add FTL resources to the bundle.");

match bundle.format("foobar", None) {
Some((value, _)) => println!("{}", value),
_ => println!("None"),
}
let msg = bundle
.get_message("foobar")
.expect("Message doesn't exist.");
let mut errors = vec![];
let pattern = msg.value.expect("Message has no value.");
let value = bundle.format_pattern(&pattern, None, &mut errors);
println!("{}", value);

match bundle.format("bazbar", None) {
Some((value, _)) => println!("{}", value),
_ => println!("None"),
}
let msg = bundle
.get_message("bazbar")
.expect("Message doesn't exist.");
let mut errors = vec![];
let pattern = msg.value.expect("Message has no value.");
let value = bundle.format_pattern(&pattern, None, &mut errors);
println!("{}", value);
}
24 changes: 15 additions & 9 deletions fluent-bundle/examples/selector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,22 @@ hello-world2 = Hello { $name ->
.add_resource(res)
.expect("Failed to add FTL resources to the bundle.");

match bundle.format("hello-world", None) {
Some((value, _)) => println!("{}", value),
_ => println!("None"),
}
let msg = bundle
.get_message("hello-world")
.expect("Message doesn't exist.");
let mut errors = vec![];
let pattern = msg.value.expect("Message has no value.");
let value = bundle.format_pattern(&pattern, None, &mut errors);
println!("{}", value);

let mut args = HashMap::new();
args.insert("name", FluentValue::from("moon"));
args.insert("name".to_string(), FluentValue::from("moon"));

match bundle.format("hello-world2", Some(&args)) {
Some((value, _)) => println!("{}", value),
_ => println!("None"),
}
let msg = bundle
.get_message("hello-world2")
.expect("Message doesn't exist.");
let mut errors = vec![];
let pattern = msg.value.expect("Message has no value.");
let value = bundle.format_pattern(&pattern, Some(&args), &mut errors);
println!("{}", value);
}
35 changes: 22 additions & 13 deletions fluent-bundle/examples/simple-app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,29 +130,38 @@ fn main() {
// 7.2. Construct a map of arguments
// to format the message.
let mut args = HashMap::new();
args.insert("input", FluentValue::from(i));
args.insert("value", FluentValue::from(collatz(i)));
args.insert("input".to_string(), FluentValue::from(i));
args.insert("value".to_string(), FluentValue::from(collatz(i)));
// 7.3. Format the message.
let (value, _) = bundle
.format("response-msg", Some(&args))
.expect("Failed to format a message.");
let mut errors = vec![];
let msg = bundle
.get_message("response-msg")
.expect("Message doesn't exist.");
let pattern = msg.value.expect("Message has no value.");
let value = bundle.format_pattern(&pattern, Some(&args), &mut errors);
println!("{}", value);
}
Err(err) => {
let mut args = HashMap::new();
args.insert("input", FluentValue::from(input.as_str()));
args.insert("reason", FluentValue::from(err.to_string()));
let (value, _) = bundle
.format("input-parse-error-msg", Some(&args))
.expect("Failed to format a message.");
args.insert("input".to_string(), FluentValue::from(input.as_str()));
args.insert("reason".to_string(), FluentValue::from(err.to_string()));
let mut errors = vec![];
let msg = bundle
.get_message("input-parse-error-msg")
.expect("Message doesn't exist.");
let pattern = msg.value.expect("Message has no value.");
let value = bundle.format_pattern(&pattern, Some(&args), &mut errors);
println!("{}", value);
}
}
}
None => {
let (value, _) = bundle
.format("missing-arg-error", None)
.expect("Failed to format a message.");
let mut errors = vec![];
let msg = bundle
.get_message("missing-arg-error")
.expect("Message doesn't exist.");
let pattern = msg.value.expect("Message has no value.");
let value = bundle.format_pattern(&pattern, None, &mut errors);
println!("{}", value);
}
}
Expand Down
Loading