Description
not really related to #94, but thinking along related lines.
Thinking about the sort of API I would like to expose to application developers, I have a few guiding principles:
- localization should not fail at runtime
- as much as possible, messages should be validated at compile time
- the type system should encode as much as possible (types and counts of message arguments, e.g.)
- Adoption should be as close to trivial as possible; application code should be easy to read.
With those goals in mind, I've been sketching out some possible ways to use a derive
macro to encode the number and type of arguments alongside the message key.
trait FluentMessage {
const KEY: &'static str;
fn localize<R: Borrow<FluentResource>>(&self, ctx: &FluentBundle<R>) -> String;
}
#[derive(FluentMessage)]
struct MacosMenuAboutApp {
app_name: String,
}
This generates approximately the following code:
impl MacosMenuAboutApp {
pub fn new(app_name: impl Into<String>) -> Self {
MacosMenuAboutApp { app_name }
}
}
impl FluentMessage for MacosMenuAboutApp {
const KEY: &'static str = "macos-menu-about-app";
fn localize<R: Borrow<FluentResource>>(&self, ctx: &FluentBundle<R>) -> String {
let args = fluent_args![
"app_name" => self.app_name.as_str()
];
let msg = ctx.get_message(Self::KEY).expect("missing localization key");
let value = msg.value.as_ref().expect("missing value");
let mut errs = Vec::new();
let result = ctx.format_pattern(value, Some(&args), &mut errs);
for err in errs {
eprintln!("localization error {:?}", err);
}
result.to_string()
}
}
Which when used would look something like:
fn some_function() {
let bundle: &FluentBundle<_> = get_fluent_bundle();
let title = MacosMenuAboutApp::new("MyApp").localize(bundle);
assert_eq!(&title, "About MyApp");
}
Again, this is just a sketch, and I'm not yet very familiar with all of fluent's features; it's very possible that this won't work for some cases, but I thought I would share the idea.
The thing I would really like, here, is some way to verify messages at compile time. This might involve something like,
// main.rs
verify_localization_with_bundles!("../resources/l20n{locale}/{res_id}");
which would compile down to something like:
lazy_static! {
static ref FLUENT_VERIFIER: Arc<Mutex<FluentVerifier>> = {
let mgr = ::fluent::ResourceManager::new("../resources/l20n{locale}/{res_id}");
::fluent::FluentVerifier(mgr, ..)
};
}
fn _fluent_static_verify(msg: &str, args: Option<FluentArgs>) -> Result<(), VerificationError> {
FLUENT_VERIFIER.lock().unwrap().verify(msg, args)
}
And then each time the derive
runs for our trait, before we generate our type we would check that the provided message and args work for some set of specified locales.
This is definitely a half-baked idea, right now, and there are lots of details that would need to be shared, but wanted to at least get my initial thoughts out.