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

An idea to leverage the type and macro system #36

Open
Thomasdezeeuw opened this issue Oct 25, 2017 · 6 comments
Open

An idea to leverage the type and macro system #36

Thomasdezeeuw opened this issue Oct 25, 2017 · 6 comments
Labels
enhancement help wanted We need help making decisions or writing PRs for this.

Comments

@Thomasdezeeuw
Copy link

This is an idea that I had that leverages the Rust type system and macro system, rather then using stringly-typed arguments (well sort of). It's inspired by Diesel.

First the client that the user has to write:

translate_module!("/path/to/translation/files");

translate!(English, "hello-world");
translate!(Dutch, "intro", "Thomas");

(I know it's still looks stringly-typed, but under the hood it isn't).

The translate_module will create a new module based on the provided directory. The only (public) API this will define is the translate macro, which has the following API:

macro_rules! translate {
    ($lang:tt, $msg:expr, $($arg:tt)*) => { ... };
}

It takes a Language item (will get back to that), a message (just like MessageContext.get_message now) and optional arguments used to format the message (like MessageContext.format).

Now to use Rust's type system. First we'll start off by generating an index or hash for each available translation message, something like:

const _TRANSLATE_HELLO_WORLD: usize = 0;
const _TRANSLATE_INTO: usize = 1;

And the message in an array or a hash map, for each language:

// This could also be hash maps or something.
_TRANSLATIONS_ENGLISH: [&'static str; 2] = ["Hello, world!", "Welcome, { $name }."];
_TRANSLATIONS_DUTCH: [&'static str; 2] = ["Hallo, wereld!", "Welkom, { $name }."];

We can check at compile time if the index/hash and message exists and if not fail the compilation. The Language item provided to translate will define what array/hash map to used, e.g. _TRANSLATIONS_DUTCH for Dutch.

Next for the formatting we'll have a single function inside ether the fluent crate or the generated module, to which the translation string and the other provided arguments to format the message gets passed. Something like this:

// translate!(English, "hello-world") translates into:
translate(_TRANSLATIONS_ENGLISH[_TRANSLATE_HELLO_WORLD])
// translate!(Dutch, "intro", "Thomas")
translate(_TRANSLATIONS_DUTCH[_TRANSLATE_INTO], "Thomas")

Looking forward to a reply, although I don't expect it very soon it got quite long.

@Pike
Copy link
Contributor

Pike commented Oct 25, 2017

I like the idea that we'd have compile-time checks for string errors.

I wonder if you could extend your ideas here for a couple of things:

For one, we'll need named arguments for fluent. So, instead of passing in "Thomas", we need to pass in {"name":"Thomas"} in some sort. Looking at examples/external_arguments.rs in this repo, I wonder if there's rustanian syntax sugar to make that easier on the eye. Being able to catch errors between the message and the arguments at compile time would be awesome.

The next challenge I'd like to put out is language switching. Could you pass a commandline argumet to your code to select a particular language? ./foopy -lang=nl vs ./foopy -lang=en ?

And then we'll want language fallback at runtime. If a Dutch string doesn't exist, try the English one. We've learned the hard way that getting all strings in all languages isn't realistic, and that users prefer partial translation to all-English.

@Thomasdezeeuw
Copy link
Author

For the named arguments maybe we can use something like format does: format!("x = {}, y = {y}", 10, y = 30);.

As for the language switching my first though was something like an enum Language { English, Dutch }, but I later removed it. It's up to the developer to select the language, for example in an HTTP server you might want to parse the Accept-Language header, but in an UI application you might have a menu for that.

As for a fallback language I would against it. I've had to deal with partially translated applications and there more annoying to work with then properly translated all English applications. But I agree that in big applications it might be hard to translate everything. The easiest solution I can think of right now would be copying over the English (or fallback language) into the translate (in the case Dutch) one.

@zbraniecki
Copy link
Collaborator

The easiest solution I can think of right now would be copying over the English (or fallback language) into the translate (in the case Dutch) one.

That would require us to annotate each string with the language it's in in order to use the correct Plural Rules, DateTimeFormat, NumberFormat etc.

It would also make MessageContext's basically multilingual after such merge.

Fluent made a design decision to rather go for lazy fallback chains, and I think we should try to apply it to Rust if possible.

@stasm
Copy link
Contributor

stasm commented Oct 25, 2017

As far as I understand it, the stringly-typed API allows us to think of fluent-rs as a future component of Servo which exposes the MessageContext API to the Web/JavaScript.

I like the ideas here for improving the experience of Rust developers. In particular, the format!-like macro which would allow to skip the tedious creation of a HashMap for args:

For the named arguments maybe we can use something like format does: format!("x = {}, y = {y}", 10, y = 30);.

@zbraniecki zbraniecki added help wanted We need help making decisions or writing PRs for this. enhancement labels Jul 31, 2018
@ishitatsuyuki
Copy link

ishitatsuyuki commented Jan 27, 2019

Given that Macros 1.2 is now stablized, I have a slightly different proposal based on module attributes as proc macros.

This will be like:

#[translate_module(path = "/translate/files")]
mod whatever_code;

Ideally it should be crate global, but that's currently not possible (rust-lang/rust#41430).

@zbraniecki zbraniecki added this to the 0.7 milestone Feb 1, 2019
@zbraniecki zbraniecki modified the milestones: 0.10, 0.11 Dec 17, 2019
@kellpossible
Copy link

I only just spotted this today, I recently implemented a similar idea in this crate: https://crates.io/crates/i18n-embed-fl for anyone interested.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement help wanted We need help making decisions or writing PRs for this.
Projects
None yet
Development

No branches or pull requests

7 participants