[−][src]Crate i18n_embed
Traits and macros to conveniently embed the output of cargo-i18n into your application binary in order to localize it at runtime.
The core trait for this library is I18nEmbed, which also has a derive macro to allow it to be easily implemented on a struct in your project.
This library makes 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
I18nEmbed. At some point in the future this library
may incorperate the embedding process into the I18nEmbed
trait
and remove this dependency. RustEmbed currently 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 compiled resources to ensure that the
project can build without requiring cargo i18n
.
Optional Features
The i18n-embed
crate has the following optional Cargo features:
fluent-system
- Enable support for the fluent localization system via the FluentLanguageLoader.
gettext-system
- Enable support for the gettext localization system using the tr macro and the gettext crate via the GettextLanguageLoader.
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 = "0.7", features = ["fluent-system", "desktop-requester"]}
rust-embed = "5"
unic-langid = "0.9"
Next, you want to create your localization resources, per language
fluent files. lang_code
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/
lang_code/
my_crate.ftl
Then in your Rust code:
use i18n_embed::{I18nEmbed, DesktopLanguageRequester, fluent::{ FluentLanguageLoader }}; use rust_embed::RustEmbed; use unic_langid::LanguageIdentifier; #[derive(RustEmbed, I18nEmbed)] #[folder = "i18n"] // path to the compiled localization resources struct Translations; fn main() { let translations = Translations {}; let fallback_language: LanguageIdentifier = "en".parse().unwrap(); let language_loader = FluentLanguageLoader::new("my_crate", fallback_language); // 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(); i18n_embed::select(&language_loader, &translations, &requested_languages); // continue on with your application }
You can also make use of the i18n.toml
configuration file, and
the cargo i18n tool to
integrate with a code-base using gettext
, and in the future to
perform compile time checks, and use the
fluent_language_loader!() macro
to pull the configuration at compile time to create the
FluentLanguageLoader.
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 GettextLanguageLoader in
this example is instantiated using the
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 = "0.7", features = ["gettext-system", "desktop-requester"]}
rust-embed = "5"
Set up a minimal i18n.toml
in your crate root to use with cargo
i18n:
# (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::{I18nEmbed, DesktopLanguageRequester, gettext::{ gettext_language_loader }}; use rust_embed::RustEmbed; #[derive(RustEmbed, I18nEmbed)] // path to the compiled localization resources, // as determined by i18n.toml settings #[folder = "i18n/mo"] struct Translations; fn main() { let translations = Translations {}; // 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(); i18n_embed::select(&language_loader, &translations, &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::rc::Rc; use i18n_embed::{ I18nEmbed, DesktopLanguageRequester, LanguageRequester, DefaultLocalizer, Localizer, fluent::{ fluent_language_loader, FluentLanguageLoader }}; use rust_embed::RustEmbed; use lazy_static::lazy_static; use unic_langid::LanguageIdentifier; #[derive(RustEmbed, I18nEmbed)] #[folder = "i18n/ftl"] // path to localization resources struct Translations; const TRANSLATIONS: Translations = Translations {}; 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, &TRANSLATIONS, ); let localizer_rc: Rc<dyn Localizer> = Rc::new(localizer); let mut language_requester = DesktopLanguageRequester::new(); language_requester.add_listener(Rc::downgrade(&localizer_rc)); // Manually check the currently requested system language, // and update the listeners. When the system language changes, // this will automatically be triggered. 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::rc::Rc; use i18n_embed::{ I18nEmbed, DesktopLanguageRequester, LanguageRequester, DefaultLocalizer, Localizer, gettext::{ gettext_language_loader, GettextLanguageLoader }}; use rust_embed::RustEmbed; use lazy_static::lazy_static; #[derive(RustEmbed, I18nEmbed)] #[folder = "i18n/mo"] // path to the compiled localization resources struct Translations; const TRANSLATIONS: Translations = Translations {}; lazy_static! { static ref LANGUAGE_LOADER: GettextLanguageLoader = gettext_language_loader!(); } // Get the `Localizer` to be used for localizing this library. #[cfg(feature = "localize")] pub fn localizer() -> Box<dyn Localizer<'static>> { Box::from(DefaultLocalizer::new(&LANGUAGE_LOADER, &TRANSLATIONS)) }
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::rc::Rc; use i18n_embed::{ I18nEmbed, DesktopLanguageRequester, LanguageRequester, DefaultLocalizer, Localizer, gettext::{ gettext_language_loader, GettextLanguageLoader }}; use rust_embed::RustEmbed; use i18n_embed::I18nEmbedDyn; use lazy_static::lazy_static; #[derive(RustEmbed, I18nEmbed)] #[folder = "i18n/mo"] // path to the compiled localization resources struct Translations; const TRANSLATIONS: Translations = Translations {}; 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: &'static dyn I18nEmbedDyn) -> Box<dyn Localizer<'static>> { Box::from(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 | |
gettext |
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. |
LanguageResource | |
WebLanguageRequester | A LanguageRequester for the |
Enums
I18nEmbedError | An error that occurs in this library. |
Traits
I18nEmbed | A trait to handle the embedding of software translations within the current binary, and the retrieval/loading of those translations at runtime. |
I18nEmbedDyn | A dynamic reference to a static I18nEmbed implementation. |
LanguageLoader | A trait used by I18nEmbed 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 I18nEmbed to ascertain which languages are being requested. |
Localizer | This trait provides dynamic access to an LanguageLoader and an I18nEmbed, 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 language currently requested by the system by the the LanguageRequester, and load it using the provided LanguageLoader from the languages embedded in I18nEmbed via I18nEmbedDyn. Returns the available languages that were negotiated to be selected in order of preference. |
Derive Macros
I18nEmbed | A procedural macro to implement the |