![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
toml
[dependencies]
l10n = "0.1.0-beta.1" }
MSRV: rustc 1.56+
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" }
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.
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})"
);
}
enum Status { #[l10nmessage(".online")] Online, #[l10nmessage(".offline")] Offline, #[l10nmessage(".busy", "reason" = reason.asstr(), "gender" = "other")] Busy { reason: String }, } ```
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
.
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
.
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:
en
: no fallbacken-CA
: fallback to en
en-GB
: fallback to en
en-GB-variant
: no fallbacken-Latn
: no fallbacken-Latn-variant
: no fallbacken-Latn-GB
: fallback to en-Latn
en-Latn-GB-variant
: fallback to en-Latn-variant
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".
There is 3 kind of resources:
l10n
directory starting with _
, these resources are shared across all named resources in all locales._
, these resources are shared across all named resources in the current locale."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" },
]
Licensed under either of
at your option.
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.