Skip to content
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

Update FluentBundle to the latest API #120

Merged
merged 1 commit into from
Jul 24, 2019

Conversation

zbraniecki
Copy link
Collaborator

Implementes #119 119

@zbraniecki
Copy link
Collaborator Author

I kept the convenience methods temporarily until I update the tests.

@zbraniecki zbraniecki force-pushed the bundle-api branch 6 times, most recently from 42aaa05 to e2d1c09 Compare July 21, 2019 07:07
@zbraniecki zbraniecki force-pushed the bundle-api branch 4 times, most recently from daa1766 to cfda316 Compare July 23, 2019 02:43
@zbraniecki zbraniecki marked this pull request as ready for review July 23, 2019 03:16
@zbraniecki
Copy link
Collaborator Author

@stasm - I don't want to overload you as I know you're super-busy, so I'm not going to block on your review, but I think this patchset is ready and it puts us roughly on par with the JS.

There are still some areas missing (mainly lack of NUMBER/DATETIME and bomb protection), but I'd prefer to land this patch and continue catching up to JS functionality on master.

@zbraniecki
Copy link
Collaborator Author

One change I placed here is to make l10n_args use owned strings as keys. &str as key sucks more often than not, and I filed #123 to come up with a clean solution that has better ergonomics (maybe Cow?). For now, owned keys should work without too much hassle.

}
let msg = bundle.get_message("hello-world").expect("Message exists");
let mut errors = vec![];
let pattern = msg.value.expect("Message has a value");
Copy link
Contributor

Choose a reason for hiding this comment

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

expect() takes the description of the error scenario, rather than of the success one :)

let msg = bundle.get_message("hello-world").expect("Message exists");
let mut errors = vec![];
let pattern = msg.value.expect("Message has a 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!

@@ -21,6 +21,12 @@ use crate::types::FluentValue;

#[derive(Debug, PartialEq)]
pub struct Message<'m> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it make sense to call this RawMessage?

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 don't think so. We have FluentBundle::get_message, so we return a Message. There's no other Message in the crate.

Copy link
Contributor

@stasm stasm Jul 25, 2019

Choose a reason for hiding this comment

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

how about FluentMessage, then? I'm concerned about the naming conflict with ast::Message. I understand that it's not really a problem in code, because the module namespacing works to our advantage. But conceptually, I'd prefer for these two "messages" to have distinct names.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yep, filed an issue!

/// .expect("Failed to format a message.");
/// let msg = bundle.get_message("intro").expect("Message exists");
/// let mut errors = vec![];
/// let pattern = msg.value.expect("Message has a value");
Copy link
Contributor

Choose a reason for hiding this comment

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

Same as in examples, please use a proper if let Some(value) = ....

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, I can't comment on the exact line, but this docstring mentions format and compound a few times.

pattern: &'bundle ast::Pattern,
args: Option<&'bundle HashMap<String, FluentValue>>,
errors: &mut Vec<FluentError>,
) -> Cow<'bundle, str>
Copy link
Contributor

Choose a reason for hiding this comment

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

Are you still planning to change the return value of format_pattern to (String, Option<Vec<Error>>)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, I'll look into this after I land this patch to verify the performance impact.

for attr in message.attributes.iter() {
attributes.insert(attr.id.name, &attr.value);
}
return Some(Message { value, attributes });
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it make more sense to do all this prep work in add_resource instead?

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'd prefer not to, at least for now.

@zbraniecki zbraniecki force-pushed the bundle-api branch 2 times, most recently from ae523c2 to 962196d Compare July 23, 2019 21:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants