fluent-typed 0.1.0

Type-safe access to Fluent localization messages
Documentation

Fluent-Typed

When using translation keys, there is often no easy way to know if they are being used correctly and if they are being used at all. This project generatesjkjj, using the fluent ast, the function definitions for the translation keys in a fluent file.

In order to guarantee the safeness, funtions are only generated for messages that are found in all the locales. For those only found for some locales or if the signature of the messages are different a warning is printed.

It is up to the user to load the translation resources, which gives him the liberty to choose how they are retreived (embedded in the binary, loaded from a file, downloaded etc).

There is no need to handle any fallback language since it is guaranteed that all messages are translated into all languages

Usage

# in Cargo.toml
[dependencies]
fluent-typed = 0.1

[build-dependencies]
fluent-typed = { version = "0.1", features = ["build"] }
// in build.rs
fn main() -> std::process::ExitCode {
    // generate the src/l10n.rs file from the fluent translations found in the locales folder,
    // prefix the generated functions with "msg_" and indent the code with 4 spaces.
    // This function returns an ExitCode.
    fluent_typed::build_from_locales_folder("locales", "src/l10n.rs", "msg_", "    ")

    // Note: there are also fluent_typed::try_build_from_locales_folder which returns a Result
}
// in lib.rs or main.rs
mod l10n;
use l10n::{L10nResource, L10n};


fn main() {
    // The L10nResource struct is generated by the build script and contains
    // one field per fluent resource used when generating the functions.
    let resources = L10nResource {
      // contains:
      //   # $name (String) - The user's name.
      //   hello = Hello { $name }
      //   greeting = Welcome!
      base: include_str!("locales/en/base.ftl"),
      // contains:
      //   # $count (Number) - How many unread meassages.
      //   unread_messages = You have { $count } unread messages
      settings: include_str!("locales/en/settings.ftl"),
    };

    // The L10n struct is generated by the build script and has
    // one function per message in the fluent resources.
    let strs = L10n::load("en", resources).unwrap();

    // A message without arguments.
    assert_eq!("Welcome!", strs.msg_greeting());
    // A message with a string argument (AsRef<str>).
    assert_eq!("Hello world", strs.msg_hello("world"));
    // A message with a number argument (Into<FluentNumber>).
    assert_eq!("You have 2 unread messages", strs.msg_unread_messages(2));
}

Type deduction

Since the fluent syntax doesn't explicitly specify the type of the translation variables, this project uses the following rules to infer the type of the translation variables:

  • String:
    • If a variable's comment contains (String), as in # $name (String) - The name.
  • Number:
    • If a variable's comment contains (Number), as in # $count (Number) - How many.
    • If a NUMBER function is used, asin dpi-ratio = Your DPI ratio is { NUMBER($ratio) }
    • If a selector only contains numbers and CLDR plural catagories: zero, one, two, few, many, and other. Example:
      your-rank = { NUMBER($pos, type: "ordinal") ->
         [1] You finished first!
         [one] You finished {$pos}st
         [two] You finished {$pos}nd
         [few] You finished {$pos}rd
        *[other] You finished {$pos}th
      }