l10n

![CI badge] ![Crate badge] ![Rustc badge]

[l10n] is a high level and opinionated localization crate built upon the excellent fluent-bundle crate, the Fluent project and inspired by the thiserror crate.

The goal of this crate is to ease project localization and provide compile time checks (message exists, mandatory arguments are set, functions are defined).

You can check some examples here: https://github.com/MathieuTricoire/l10n-examples

Code repository: https://github.com/MathieuTricoire/l10n

Installation

toml [dependencies] l10n = "0.1.0-beta.1" }

MSRV: rustc 1.56+

Note

This is marked as a "beta" version because String arguments are not supported as is, you need to write "argument" = value.as_str() instead of "argument" = value and because some polyfills are in place while waiting for PRs to be merged nor released in fluent-bundle

If you want to use the latest version you can use the one from GitHub:

toml [dependencies] l10n = { git = "https://github.com/MathieuTricoire/l10n.git" }

Quick start

There is no configuration needed to start using [l10n], just create a l10n directory next to Cargo.toml, create as many locale directories (must be valid locales) containing fluent resources.

Example

Localization directory tree structure:

text l10n ├── _brand.ftl (global unnamed resource) ├── en │ ├── _common.ftl (unnamed resource) │ ├── app.ftl (named resource) │ └── settings.ftl (named resource) ├── en-CA ├── en-GB │ └── app.ftl (named resource) ├── fr │ ├── _common.ftl (unnamed resource) │ ├── app.ftl (named resource) │ └── settings.ftl (named resource) └── fr-CA ├── _terms.ftl (unnamed resource) └── settings.ftl (named resource) Cargo.toml

l10n/fr/app.ftl file:

text greeting = Bonjour { $first-name } !

l10n/fr/settings.ftl file:

text status = .online = En ligne .offline = Hors ligne .busy = { $gender -> [male] Occupé [female] Occupée *[other] Non disponible } ({ $reason })

Then in the root of your application or library initialize l10n (this create a L10N static ref used by other macros) and create l10n messages either with the message! macro or by deriving L10nMessage.

```rust use l10n::uniclangid::langid; use l10n::{message, messageargs, L10nMessage}; use l10n::fluent_bundle::{FluentValue, FluentArgs}; // for functions, not necessary for this example

l10n::init!({ // not necessary for this example functions: { "TIME": |: &[FluentValue<'>], _: &FluentArgs| FluentValue::None } });

fn main() { let lang = langid!("fr");

let username = "Alice";
let greeting = message!("app", "greeting", "first-name" = username);
assert_eq!(greeting.translate(&lang), "Bonjour \u{2068}Alice\u{2069} !");

let status = Status::Busy {
    reason: "Meeting".to_string(),
};
assert_eq!(status.translate(&lang), "\u{2068}Non disponible\u{2069} (\u{2068}Meeting\u{2069})");
assert_eq!(
    status.translate_with_args(&lang, Some(&message_args!("gender" => "female"))),
    "\u{2068}Occupée\u{2069} (\u{2068}Meeting\u{2069})"
);

}

[derive(L10nMessage)]

[l10n_message("settings", "status")]

enum Status { #[l10nmessage(".online")] Online, #[l10nmessage(".offline")] Offline, #[l10nmessage(".busy", "reason" = reason.asstr(), "gender" = "other")] Busy { reason: String }, } ```

Advanced usage

Configuration file

Create a l10n.toml or config.toml file next to Cargo.toml, to define the locales nor set a different path to the "localization" directory containing the locale directories and fluent files.

l10n.toml file example:

toml [l10n] locales = [ "en", { main = "en-GB", fallback = "en" }, { main = "en-CA", fallback = "en-GB" }, "fr", { main = "fr-CA", fallback = "fr" }, ] path = "localization_files"

To use another configuration file at compile time, set the environment variable L10N_CONFIG_FILE like this L10N_CONFIG_FILE=/path/to/specific-config.toml.

Path to localization directory

To have different paths to the "localization" directory according to your need, use a map value for path (or paths) where the key is the name of the environment and the value the path to the "localization" directory. A default environment is required.

Then to compile your artificat with this environment use the environment variable L10N_PATH_ENV.

l10n.toml file example:

toml [l10n] paths = { default = "l10n", prod = "/path/to/l10n" }

Build command:

sh L10N_PATH_ENV=prod cargo build --release

You can also prefix your path with a special variable $ROOT and the library will replace this variable with the path to the configuration file.

/path/to/l10n.toml file example:

toml [l10n] path = "$ROOT/localization_files"

Produced path: /path/to/localization_files.

Locales

Discovered locales

If no locales configuration is provided, l10n will discover the locales in the "localization" directory. l10n implements a very basic fallback mechanism between discovered locales, if a locale contains a "region" code it will fallback to the same locale without the "region" code if exists.

Localization directory tree structure (only locale directories are shown):

text l10n ├── en ├── en-CA ├── en-GB ├── en-GB-variant ├── en-Latn ├── en-Latn-variant ├── en-Latn-GB └── en-Latn-GB-variant

In the example above the fallbacks will be:

Locales set in configuration

A locale can be used if set as a "main" locale, this means if a locale is only set as a fallback it will not be possible to translate messages in this locale.

l10n.toml file example

toml [l10n] locales = [ { main = "en-US", fallback = "en" }, { main = "en-GB", fallback = "en" }, { main = "en-CA", fallback = "en-GB" }, { main = "fr" }, # same as writing `"fr",` { main = "fr-CA", fallback = "fr" }, ]

In this example the messages can only be translated with the locales: en-US, en-GB, en-CA, fr, fr-CA and not en which is only set as a fallback "locale".

Details

Resources

There is 3 kind of resources:

"Global unnamed resources" and "Unnamed resources" can be freely created and will be load according to their attached locale.

"Named resources" must exists for all "mandatory locales". "Mandatory locales" are all the locales at the end of a resolution route, in the next example the "mandatory locales" are: "en" and "fr".

toml [l10n] locales = [ { main = "en-GB", fallback = "en" }, { main = "en-CA", fallback = "en-GB" }, "fr", { main = "fr-CA", fallback = "fr" }, ]


License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.