Basic usage

Minimal skeleton

The most basic CSR app is reproduced here:

.
├── 📄 Cargo.toml
├── 📁 locales
│   ├── 📁 en
│   │   └── 📄 main.ftl
│   └── 📁 es
│       └── 📄 main.ftl
└── 📁 src
    ├── 📄 main.rs
    └── 📄 lib.rs
# locales/en/main.ftl
select-a-language = Select a language:
language-selected-is = The selected language is { $lang }.
# locales/es/main.ftl
select-a-language = Selecciona un idioma:
language-selected-is = El idioma seleccionado es { $lang }.
// src/lib.rs
use fluent_templates::static_loader;
use leptos::prelude::*;
use leptos_fluent::{expect_i18n, leptos_fluent, move_tr, Language};

static_loader! {
    pub static TRANSLATIONS = {
        locales: "./locales",
        fallback_language: "en",
    };
}

#[component]
pub fn I18n(children: Children) -> impl IntoView {
    leptos_fluent! {
        children: children(),
        translations: [TRANSLATIONS],
        locales: "./locales",
    }
}

#[component]
pub fn App() -> impl IntoView {
    view! {
        <I18n>
            <LanguageSelector/>
        </I18n>
    }
}

#[component]
fn LanguageSelector() -> impl IntoView {
    // Use `expect_i18n()` to get the current i18n context:
    let i18n = expect_i18n();

    view! {
        <p>{move_tr!("select-a-language")}</p>
        <fieldset>
            {move || {
                i18n.languages.iter().map(|lang| render_language(lang)).collect::<Vec<_>>()
            }}
        </fieldset>
        <p>
            {move_tr!(
                 "language-selected-is",
                 { "lang" => i18n.language.get().name }
            )}
        </p>
    }
}

fn render_language(lang: &'static Language) -> impl IntoView {
    // Passed as atrribute, `Language` is converted to their code,
    // so `<input id=lang` becomes `<input id=lang.id.to_string()`
    let i18n = expect_i18n();
    view! {
        <div>
            <label for=lang>{lang.name}</label>
            <input
                id=lang
                value=lang
                name="language"
                checked=lang.is_active()
                on:click=move |_| i18n.language.set(lang)
                type="radio"
            />
        </div>
    }
}
// src/main.rs
pub fn main() {
    console_error_panic_hook::set_once();
    leptos::mount::mount_to_body(minimal_example::App);
}
# Cargo.toml
[package]
name = "minimal-example"
edition = "2021"
version = "0.1.0"

[lib]
name = "minimal_example"
path = "src/lib.rs"

[dependencies]
leptos = { version = "0.7", features = ["csr"] }
leptos-fluent = "0.2"
fluent-templates = "0.11"
console_error_panic_hook = "0.1"

# Using cargo-leptos
[package.metadata.leptos]
watch-additional-files = ["locales"]

Translating messages

Use the move_tr! macro to translate a string. The macro takes the key of the translation and an optional object with the variables to interpolate:

move_tr!("select-a-language")

move_tr!("language-selected-is", { "lang" => i18n.language.get().name })

Additionally, use the tr! macro to translate a string inside a reactive context. Note that if is not inside a reactive context, the translation won't be updated on the fly when the language changes. This can lead to warnings in console output like:

Warning

At `./path/to/file.rs:ln`, you access a signal or memo (defined at
`./path/to/file.rs:ln`) outside of a reactive context. This might mean your
app is not responding to changes in signal values in the way you expect.

Can be fixed by replacing calls to tr! with move_tr! or wrapping the tr! calls in a reactive context.

The previous code could be rewritten as:

move || tr!("select-a-language")

move || tr!("language-selected-is", { "lang" => i18n.language.get().name })

The main difference is that move_tr! encapsulates the movement in a leptos::Signal, strictly would be rewritten as:

Signal::derive(move || tr!("select-a-language"))

Retrieving the I18n context

Use the expect_i18n function to get the current i18n context:

let i18n = leptos_fluent::expect_i18n();

It is exported as i18n too:

let i18n = leptos_fluent::i18n();

The function use_i18n returns an Option with the current i18n context:

let i18n = leptos_fluent::use_i18n().expect("No `I18n` context found");

Using the I18n context

The i18n context has the following fields:

  • language: A read-write signal with a pointer to the static current active language.
  • languages: A pointer to a static list of pointers of the static available languages.
  • translations: A signal to the vector of fluent-templates loaders that stores the translations.

Update language

To update the language, use lang.activate or the set method of language:

lang.activate();

expect_i18n().language.set(lang);

Nightly

When nightly feature is enabled, can be updated passing a new language to the context as a function with:

let i18n = leptos_fluent::i18n();
i18n(lang);

Get active language

To get the current active language, use get method of language field:

let i18n = leptos_fluent::i18n();
let lang = i18n.language.get();

Nightly

When nightly enabled, can get the active language with:

let i18n = leptos_fluent::i18n();
let lang = i18n();

Get available languages

To get the available languages, iterate over the languages field:

i18n.languages.iter()

Check if a language is active

To check if a language is the active one, use is_active method of a leptos_fluent::Language struct:

lang.is_active()

lang == expect_i18n().language.get()