Crate i18n_embed[][src]

Expand description

Traits and macros to conveniently embed localization assets into your application binary or library in order to localize it at runtime. Works in unison with cargo-i18n.

This library recommends tha you make use of rust-embed to perform the actual embedding of the language files, unfortunately using this currently requires you to manually add it as a dependency to your project and implement its trait on your struct in addition to I18nAssets. RustEmbed will not compile if the target folder path is invalid, so it is recommended to either run cargo i18n before building your project, or committing the localization assets into source control to ensure that the the folder exists and project can build without requiring cargo i18n.

Optional Features

The i18n-embed crate has the following optional Cargo features:

  • rust-embed (Enabled by default)
    • Enable an automatic implementation of I18nAssets for any type that also implements RustEmbed.
  • fluent-system
    • Enable support for the fluent localization system via the fluent::FluentLanguageLoader in this crate.
  • gettext-system
    • Enable support for the gettext localization system using the tr macro and the gettext crate via the gettext::GettextLanguageLoader in this crate.
  • desktop-requester
    • Enables a convenience implementation of LanguageRequester trait called `DesktopLanguageRequester for the desktop platform (windows, mac, linux), which makes use of the locale_config crate for resolving the current system locale.
  • web-sys-requester
    • Enables a convenience implementation of LanguageRequester trait called WebLanguageRequester which makes use of the web-sys crate for resolving the language being requested by the user’s web browser in a WASM context.

Examples

Fluent Localization System

The following is a simple example for how to localize your binary using this library when it first runs, using the fluent localization system, directly instantiating the FluentLanguageLoader.

First you’ll need the following features enabled in your Cargo.toml:

[dependencies]
i18n-embed = { version = "VERSION", features = ["fluent-system", "desktop-requester"]}
rust-embed = "5"

Set up a minimal i18n.toml in your crate root to use with cargo-i18n (see cargo i18n for more information on the configuration file format):

# (Required) The language identifier of the language used in the
# source code for gettext system, and the primary fallback language
# (for which all strings must be present) when using the fluent
# system.
fallback_language = "en-GB"

# Use the fluent localization system.
[fluent]
# (Required) The path to the assets directory.
# The paths inside the assets directory should be structured like so:
# `assets_dir/{language}/{domain}.ftl`
assets_dir = "i18n"

Next, you want to create your localization resources, per language fluent (.ftl) files. language needs to conform to the Unicode Language Identifier standard, and will be parsed via the unic_langid crate:

my_crate/
  Cargo.toml
  i18n.toml
  src/
  i18n/
    {language}/
      {domain}.ftl

Then in your Rust code:

use i18n_embed::{DesktopLanguageRequester, fluent::{
    FluentLanguageLoader, fluent_language_loader
}};
use rust_embed::RustEmbed;

#[derive(RustEmbed)]
#[folder = "i18n"] // path to the compiled localization resources
struct Localizations;


fn main() {
    let language_loader: FluentLanguageLoader = fluent_language_loader!();

    // Use the language requester for the desktop platform (linux, windows, mac).
    // There is also a requester available for the web-sys WASM platform called
    // WebLanguageRequester, or you can implement your own.
    let requested_languages = DesktopLanguageRequester::requested_languages();

    let _result = i18n_embed::select(
        &language_loader, &Localizations, &requested_languages);

    // continue on with your application
}

To access localizations, you can use FluentLanguageLoader’s methods directly, or, for added compile-time checks/safety, you can use the fl!() macro.

Having an i18n.toml configuration file enables you to do the following:

  • Use the cargo i18n tool to perform validity checks (not yet implemented).
  • Integrate with a code-base using the gettext localization system.
  • Use the fluent::fluent_language_loader!() macro to pull the configuration in at compile time to create the fluent::FluentLanguageLoader.
  • Use the fl!() macro to have added compile-time safety when accessing messages.

Gettext Localization System

The following is a simple example for how to localize your binary using this library when it first runs, using the gettext localization system. Please note that the gettext localization system is technically inferior to fluent in a number of ways, however there are always legacy reasons, and the developer/translator ecosystem around gettext is mature.

The gettext::GettextLanguageLoader in this example is instantiated using the gettext::gettext_language_loader!() macro, which automatically determines the correct module for the crate, and pulls settings in from the i18n.toml configuration file.

First you’ll need the following features enabled in your Cargo.toml:

[dependencies]
i18n-embed = { version = "VERSION", features = ["gettext-system", "desktop-requester"]}
rust-embed = "5"

Set up a minimal i18n.toml in your crate root to use with cargo-i18n (see cargo i18n for more information on the configuration file format):

# (Required) The language identifier of the language used in the
# source code for gettext system, and the primary fallback language
# (for which all strings must be present) when using the fluent
# system.
fallback_language = "en"

# Use the gettext localization system.
[gettext]
# (Required) The languages that the software will be translated into.
target_languages = ["es"]

# (Required) Path to the output directory, relative to `i18n.toml` of
# the crate being localized.
output_dir = "i18n"

Install and run cargo i18n for your crate to generate the language specific po and mo files, ready to be translated. It is recommended to add the i18n/pot folder to your repository gitignore.

Then in your Rust code:

use i18n_embed::{DesktopLanguageRequester, gettext::{
    gettext_language_loader
}};
use rust_embed::RustEmbed;

#[derive(RustEmbed)]
// path to the compiled localization resources,
// as determined by i18n.toml settings
#[folder = "i18n/mo"]
struct Localizations;


fn main() {
    // Create the GettextLanguageLoader, pulling in settings from `i18n.toml`
    // at compile time using the macro.
    let language_loader = gettext_language_loader!();

    // Use the language requester for the desktop platform (linux, windows, mac).
    // There is also a requester available for the web-sys WASM platform called
    // WebLanguageRequester, or you can implement your own.
    let requested_languages = DesktopLanguageRequester::requested_languages();

    let _result = i18n_embed::select(
        &language_loader, &Localizations, &requested_languages);

    // continue on with your application
}

Automatic Updating Selection

Depending on the platform, you can also make use of the LanguageRequester’s ability to monitor changes to the currently requested language, and automatically update the selected language using a Localizer:

use std::sync::Arc;
use i18n_embed::{
    DesktopLanguageRequester, LanguageRequester,
    DefaultLocalizer, Localizer, fluent::FluentLanguageLoader     
};
use rust_embed::RustEmbed; use lazy_static::lazy_static;
use unic_langid::LanguageIdentifier;

#[derive(RustEmbed)]
#[folder = "i18n/ftl"] // path to localization resources
struct Localizations;

lazy_static! {
    static ref LANGUAGE_LOADER: FluentLanguageLoader = {
        // Usually you could use the fluent_language_loader!() macro
        // to pull values from i18n.toml configuration and current
        // module here at compile time, but instantiating the loader
        // manually here instead so the example compiles.
        let fallback: LanguageIdentifier = "en-US".parse().unwrap();
        FluentLanguageLoader::new("test", fallback)
    };
}

fn main() {
    let localizer = DefaultLocalizer::new(&*LANGUAGE_LOADER, &Localizations);

    let localizer_arc: Arc<dyn Localizer> = Arc::new(localizer);

    let mut language_requester = DesktopLanguageRequester::new();
    language_requester.add_listener(Arc::downgrade(&localizer_arc));

    // Manually check the currently requested system language,
    // and update the listeners. NOTE: Support for this across systems
    // currently varies, it may not change when the system requested
    // language changes during runtime without restarting your application.
    // In the future some platforms may also gain support for
    // automatic triggering when the requested display language changes.
    language_requester.poll().unwrap();

    // continue on with your application
}

The above example makes use of the DefaultLocalizer, but you can also implement the Localizer trait yourself for a custom solution. It also makes use of lazy_static to allow the LanguageLoader implementation to be stored statically, because its constructor is not const.

Localizing Libraries

If you wish to create a localizable library using i18n-embed, you can follow this code pattern in the library itself:

use std::sync::Arc;
use i18n_embed::{
    DefaultLocalizer, Localizer, LanguageLoader,
    fluent::{
        fluent_language_loader, FluentLanguageLoader     
}};
use rust_embed::RustEmbed; use lazy_static::lazy_static;

#[derive(RustEmbed)]
#[folder = "i18n/mo"] // path to the compiled localization resources
struct Localizations;

lazy_static! {
    static ref LANGUAGE_LOADER: FluentLanguageLoader = {
        let loader = fluent_language_loader!();

        // Load the fallback langauge by default so that users of the
        // library don't need to if they don't care about localization.
        // This isn't required for the `gettext` localization system.
        loader.load_fallback_language(&Localizations)
            .expect("Error while loading fallback language");

        loader
    };
}

// Get the `Localizer` to be used for localizing this library.
pub fn localizer() -> Arc<dyn Localizer> {
    Arc::new(DefaultLocalizer::new(&*LANGUAGE_LOADER, &Localizations))
}

People using this library can call localize() to obtain a Localizer, and add this as a listener to their chosen LanguageRequester.

Localizing Sub-crates

If you want to localize a sub-crate in your project, and want to extract strings from this sub-crate and store/embed them in one location in the parent crate, you can use the following pattern for the library:

use std::sync::Arc;
use i18n_embed::{
    DefaultLocalizer, Localizer, gettext::{
    gettext_language_loader, GettextLanguageLoader     
}};
use i18n_embed::I18nAssets;
use lazy_static::lazy_static;

lazy_static! {
    static ref LANGUAGE_LOADER: GettextLanguageLoader =
        gettext_language_loader!();
}

/// Get the `Localizer` to be used for localizing this library,
/// using the provided embeddes source of language files `embed`.
pub fn localizer(embed: &dyn I18nAssets) -> Arc<dyn Localizer + '_> {
    Arc::new(DefaultLocalizer::new(
        &*LANGUAGE_LOADER,
        embed
    ))
}

For the above example, you can enable the following options in the sub-crate’s i18n.toml to ensure that the localization resources are extracted and merged with the parent crate’s pot file:

# ...

[gettext]

# ...

# (Optional) If this crate is being localized as a subcrate, store the final
# localization artifacts (the module pot and mo files) with the parent crate's
# output. Currently crates which contain subcrates with duplicate names are not
# supported.
extract_to_parent = true

# (Optional) If a subcrate has extract_to_parent set to true, then merge the
# output pot file of that subcrate into this crate's pot file.
collate_extracted_subcrates = true

Re-exports

pub use unic_langid;

Modules

fluent

This module contains the types and functions to interact with the fluent localization system.

gettext

This module contains the types and functions to interact with the gettext localization system.

Structs

DefaultLocalizer

A simple default implemenation of the Localizer trait.

DesktopLanguageRequester

A LanguageRequester for the desktop platform, supporting windows, linux and mac. It uses locale_config to select the language based on the system selected language.

FileSystemAssets

An I18nAssets implementation which pulls assets from the OS file system.

LanguageRequesterImpl

Provide the functionality for overrides and listeners for a LanguageRequester implementation.

LanguageResource

A language resource file, and its associated language.

WebLanguageRequester

A LanguageRequester for the web-sys web platform.

Enums

I18nEmbedError

An error that occurs in this library.

Traits

I18nAssets

A trait to handle the retrieval of localization assets.

LanguageLoader

A trait used by I18nAssets to load a language file for a specific rust module using a specific localization system. The trait is designed such that the loader could be swapped during runtime, or contain state if required.

LanguageRequester

A trait used by I18nAssets to ascertain which languages are being requested.

Localizer

This trait provides dynamic access to an LanguageLoader and an I18nAssets, which are used together to localize a library/crate on demand.

Functions

domain_from_module

Get the translation domain from the module path (first module in the module path).

select

Select the most suitable available language in order of preference by requested_languages, and load it using the provided LanguageLoader from the languages available in I18nAssets. Returns the available languages that were negotiated as being the most suitable to be selected, and were loaded by LanguageLoader::load_languages(). If there were no available languages, then no languages will be loaded and the returned Vec will be empty.