-
Notifications
You must be signed in to change notification settings - Fork 98
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
Conversation
I kept the convenience methods temporarily until I update the tests. |
42aaa05
to
e2d1c09
Compare
daa1766
to
cfda316
Compare
@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. |
One change I placed here is to make l10n_args use owned strings as keys. |
} | ||
let msg = bundle.get_message("hello-world").expect("Message exists"); | ||
let mut errors = vec![]; | ||
let pattern = msg.value.expect("Message has a value"); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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!()
}
There was a problem hiding this comment.
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.
}
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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> { |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, filed an issue!
fluent-bundle/src/bundle.rs
Outdated
/// .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"); |
There was a problem hiding this comment.
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) = ...
.
There was a problem hiding this comment.
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> |
There was a problem hiding this comment.
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>>)
?
There was a problem hiding this comment.
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 }); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
ae523c2
to
962196d
Compare
Implementes #119 119