Introduction
leptos-fluent is a framework for internationalizing Leptos applications using Fluent. It has all the batteries included to handle language switching, translations management, multiple strategies for activating translations at initialization, different modes of persistent storage, and more.
Project goals
The main goals of leptos-fluent are:
- To provide a simple and easy-to-use API for internationalizing Leptos applications.
- Be the most fully featured internationalization framework in any language.
- Be the most performant internationalization framework available.
Help and support
You can ask for help and support in the #leptos-fluent
channel of
Leptos Discord server, open a discussion in the GitHub repository or
report bugs by opening an issue.
Contributing
See CONTRIBUTING.md file for more information about how to setup the development environment and contribute to the project.
Installation
- CSR
- SSR
- Desktop applications
- Features
- Nightly toolchain
- Language files
- Tracking locales files with
cargo leptos
- Tracing
CSR
For client side rendering apps install leptos-fluent and fluent-templates
:
[dependencies]
leptos-fluent = "0.1"
fluent-templates = "0.11"
Minimal (recommended)
[dependencies]
leptos-fluent = { version = "0.1", default-features = false }
fluent-templates = { version = "0.11", default-features = false, features = [
"macros",
"walkdir"
] }
- Using
default-features = false
forleptos-fluent
thejson
default feature will not be enabled, so thelanguages
parameter ofleptos_fluent!
macro will not be available. - Using
default-features = false
andfeatures = ["macros", "walkdir"]
forfluent-templates
ensures that the dependency tree is the minimal possible because more dependencies are shared betweenleptos-fluent
andfluent-templates
.
SSR
For server side rendering apps install leptos-fluent, fluent-templates
and activate the hydrate
, ssr
and actix
/axum
features in their
respective features set.
[dependencies]
leptos-fluent = "0.1"
fluent-templates = "0.11"
[features]
hydrate = [
"leptos-fluent/hydrate"
]
ssr = [
"leptos-fluent/ssr",
"leptos-fluent/actix", # actix and axum are supported
]
# Using cargo-leptos
[package.metadata.leptos]
watch-additional-files = ["locales"]
Desktop applications
leptos-fluent can be installed on non-wasm targets, like desktop
applications. You need to install leptos-fluent
, fluent-templates
and enable the system
feature:
[dependencies]
leptos-fluent = { version = "0.1", features = ["system"] }
fluent-templates = "0.11"
See the GTK example.
Features
- Server Side Rendering:
ssr
- Hydration:
hydrate
- Actix Web integration:
actix
- Axum integration:
axum
- Nightly toolchain:
nightly
- Desktop applications:
system
- JSON languages file:
json
(enabled by default) - YAML languages file:
yaml
- JSON5 languages file:
json5
- Tracing support:
tracing
- Debugging:
debug
Nightly toolchain
leptos-fluent builds nightly functionalities by enabling the nightly
feature:
[dependencies]
leptos-fluent = { version = "0.1", features = ["nightly"] }
fluent-templates = "0.11"
Language files
By default, leptos-fluent supports JSON languages files. To use other
formats to load custom languages, the json5
or yaml
features can be
enabled:
[dependencies]
fluent-templates = "0.11"
leptos-fluent = { version = "0.1", features = ["json5"], default-features = false }
See 4. Languages.
Tracking locales files with cargo leptos
Using cargo leptos
watch of the locales/ folder for reloads:
# Relative to Cargo.toml file
[package.metadata.leptos]
watch-additional-files = ["locales"]
When inside a workspace, use the full path to the folder from the workspace Cargo.toml file:
# Relative to workspace Cargo.toml file
[package.metadata.leptos]
watch-additional-files = ["examples/csr/locales"]
Tracing
To enable tracing
support, add the tracing
feature to leptos-fluent:
[dependencies]
leptos-fluent = { version = "0.1", features = ["tracing"] }
fluent-templates = "0.11"
See the GTK example.
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::*;
use leptos_fluent::{expect_i18n, leptos_fluent, move_tr, Language};
static_loader! {
pub static TRANSLATIONS = {
locales: "./locales",
fallback_language: "en",
};
}
#[component]
pub fn App() -> impl IntoView {
leptos_fluent! {
translations: [TRANSLATIONS],
locales: "./locales",
};
view! { <LanguageSelector/> }
}
#[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()`
view! {
<div>
<label for=lang>{lang.name}</label>
<input
id=lang
value=lang
name="language"
checked=lang.is_active()
on:click=move |_| lang.activate()
type="radio"
/>
</div>
}
}
// src/main.rs
pub fn main() {
console_error_panic_hook::set_once();
leptos::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.6.12", features = ["csr"] }
leptos-fluent = "0.1"
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:
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:
leptos::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);
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();
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()
Strategies
All the features of the framework are optional, following a declarative "opt-in" configuration method.
- Loading the initial language of the user
- CSR | Updating the language on the client
- CSR | Updating the language from initialization on the client
- CSR | Client side effects
- CSR + SSR | Names
Loading the initial language of the user
The initial language of the user can be set in different ways:
Strategy | CSR | SSR | Desktop | leptos_fluent! |
---|---|---|---|---|
URL parameter | β | β | β | initial_language_from_url_param |
URL path | β | β | β | initial_language_from_url_path |
Cookie | β | β | β | initial_language_from_cookie |
Server function | β | β | β | initial_language_from_server_function |
Browser local storage | β | β | β | initial_language_from_localstorage |
Browser navigator.languages | β | β | β | initial_language_from_navigator |
Accept-Language header | β | β | β | initial_language_from_accept_language_header |
System language | β | β | β | initial_language_from_system |
Data file | β | β | β | initial_language_from_data_file |
All of them can be defined at the same time or just one of them at the same time. The first language source found will be used and nexts discharged. The order of precedence is:
- SSR
- CSR
- Server function
- URL parameter
- URL path
- Cookie
- Browser local storage
- Browser
navigator.languages
- Desktop (
system
feature)- Data file
- System language
CSR | Updating the language on the client
When the user changes the language and I18n::language.set
is called, the
framework can perform a side effect to update the language in the client. The
following strategies are available:
Strategy | leptos_fluent! |
---|---|
URL parameter | set_language_to_url_param |
Cookie | set_language_to_cookie |
Browser local storage | set_language_to_localstorage |
Server function | set_language_to_server_function |
When the user changes the language in the browser settings, the framework can perform a side effect to reflect the language change in the client. The following strategies are available:
Strategy | leptos_fluent! |
---|---|
Browser navigator.languages | set_language_from_navigator |
system | Desktop applications
Strategy | leptos_fluent! |
---|---|
Data file | set_language_to_data_file |
CSR | Updating the language from initialization on the client
When a language is loaded from initialization, the framework can perform a side effect to persistently storage the language in the client. The following strategies are available:
Strategy | leptos_fluent! |
---|---|
URL parameter to cookie | initial_language_from_url_param_to_cookie |
URL parameter to local storage | initial_language_from_url_param_to_localstorage |
URL path to cookie | initial_language_from_url_path_to_cookie |
URL path to local storage | initial_language_from_url_path_to_localstorage |
Cookie to local storage | initial_language_from_cookie_to_localstorage |
Local storage to cookie | initial_language_from_localstorage_to_cookie |
Local storage to server function | initial_language_from_localstorage_to_server_function |
navigator.languages to cookie | initial_language_from_navigator_to_cookie |
navigator.languages to local storage | initial_language_from_navigator_to_localstorage |
navigator.languages to server function | initial_language_from_navigator_to_server_function |
Server function to local storage | initial_language_from_server_function_to_localstorage |
CSR + SSR
Strategy | leptos_fluent! |
---|---|
URL parameter to server function | initial_language_from_url_param_to_server_function |
URL path to server function | initial_language_from_url_path_to_server_function |
Cookie to server function | initial_language_from_cookie_to_server_function |
Server function to cookie | initial_language_from_server_function_to_cookie |
system | Desktop applications
Strategy | leptos_fluent! |
---|---|
System language to data file | initial_language_from_system_to_data_file |
CSR | Client side effects
When the user updates the language, the framework can perform side effects to update the language in the client. The following side effects are available:
Side effect | leptos_fluent! |
---|---|
<html lang="..."> attribute | sync_html_tag_lang |
<html dir="..."> attribute | sync_html_tag_dir |
CSR + SSR | Names
The names of the settings can be configured using the following parameters:
Strategy | leptos_fluent! | Default value |
---|---|---|
Cookie | cookie_name | "lf-lang" |
Cookie attributes | cookie_attrs | "" |
Browser local storage | localstorage_key | "lang" |
URL parameter | url_param | "lang" |
URL path extractor fn | url_path | β |
system | Desktop applications
Strategy | leptos_fluent! | Default value |
---|---|---|
Data file | data_file_key | "leptos-fluent" |
Languages
leptos-fluent follows a default strategy to generate the languages of the application. This strategy is based on the locales/ directory.
Giving the next directory structure:
.
βββ π locales
βββ π en
β βββ π main.ftl
βββ π es-ES
βββ π main.ftl
The framework will generate something like the following languages array at compile time:
let LANGUAGES = [
leptos_fluent::Language {
id: unic_langid::langid!("en"),
name: "English",
dir: leptos_fluent::WritingDirection::Ltr,
},
leptos_fluent::Language {
id: unic_langid::langid!("es-ES"),
name: "EspaΓ±ol (EspaΓ±a)",
dir: leptos_fluent::WritingDirection::Ltr,
},
]
en
is built with the name"English"
because it's defined as an ISO 639-1 code, without a region code.es-ES
is built with the name"EspaΓ±ol (EspaΓ±a)"
because it's defined as an ISO 639-1 code and a region code.
This enforces that an user will always be able to select their language in their own language, and not in the current language of the application.
The order of the languages will be defined based on the alphabetical order of their names, not their codes.
The languages file
The languages array can be fully customized by defining a languages
parameter
in the leptos_fluent!
macro pointing to a languages file. This file must
be relative to the Cargo.toml file.
leptos_fluent! {
languages: "./locales/languages.json",
// ...
}
[
["en", "English"],
["es-ES", "Spanish (Spain)", "auto"]
]
The languages file must expose an array of arrays with the structure:
[
// Code, Name, "ltr"/"rtl"/"auto" (optional)
["code", "Language name", "Writing direction"],
]
The order of the languages in
leptos_fluent::I18n::languages
will be the same as in the file regardless of the alphabetical order
of the names.
The first in the languages file will used as the initial of the user when any
other initialization value is discovered. Use the same as the one configured
as fallback_language
in static_loader!
.
static_loader! {
static TRANSLATIONS = {
locales: "./locales",
fallback_language: "en",
};
}
#[component]
pub fn App() -> impl IntoView {
leptos_fluent! {
translations: [TRANSLATIONS],
languages: "./locales/languages.json5",
};
}
// ./locales/languages.json5
[
["en", "English"],
["es-ES", "EspaΓ±ol (EspaΓ±a)"],
]
File format
By default, the json
feature is enabled, which only allows to set the
languages file in JSON format. To use other formats, disable the feature
and enable another.
[dependencies]
leptos-fluent = { version = "*", default-features = false, features = ["json5"] }
Available features for languages file formats are:
json
: JSON (default)yaml
: YAMLjson5
: JSON5
Checking translations
To check that the translations of the app are correct at compile time,
set the check_translations
parameter in the leptos_fluent!
macro to
a glob pattern that matches the Rust files that you want to check.
The pattern must be relative to the location of the Cargo.toml file.
For single crate projects, it would be something like:
leptos_fluent! {
check_translations: "./src/**/*.rs",
}
For workspace projects, it could be something like:
leptos_fluent! {
check_translations: "../{app,components}/src/**/*.rs",
}
Translations error messages
When the translations stop being synchronized, you will see errors like:
error: Translations check failed:
- Message "select-a-language" defined at `move_tr!("select-a-language")` macro call in src/lib.rs not found in files for locale "en".
- Message "select-a-lang" of locale "en" not found in any `tr!` or `move_tr!` macro calls.
--> examples/csr-complete/src/lib.rs:18:29
|
18 | check_translations: "./src/**/*.rs",
| ^^^^^^^^^^^^^^^
If placeable are missing in the translations, you will see errors like:
error: Translations check failed:
- Variable "dir" defined at `move_tr!("html-tag-dir-is", { ... })` macro call in src/lib.rs not found in message "html-tag-dir-is" of locale "en".
- Variable "name" defined in message "html-tag-dir-is" of locale "en" not found in arguments of `move_tr!("html-tag-dir-is", { ... })` macro call at file src/lib.rs.
--> examples/csr-complete/src/lib.rs:18:29
|
18 | check_translations: "./src/**/*.rs",
| ^^^^^^^^^^^^^^^
Why glob patterns to Rust files?
leptos-fluent provides a I18n
context to Leptos when
the macro leptos_fluent!
is called. So multiple instances of a context
with different localization files and strategies can be initialized in
different component trees. This is useful, for example, in a multi page app.
The mechanism of translations checking needs to know where reside the calls to
tr!
and move_tr!
macros to extract the messages that need to be checked.
This is performed by parsing the source code looking for these macros
invocations.
leptos-fluent doesn't provide ways to translate directly using
I18n
context methods, as it would be impossible to extract
the translations at compile time.
The only limitation for checking translations with glob patterns is that the
tr!
and move_tr!
macros that consume each context must be in
different file trees, but this enforces anyway a good practice of file-level
separation of contexts in the codebase.
FAQs
- How to get
LanguageIdentifier
? - How to get the i18n context at initialization?
- Custom cookie attributes are invalid
- How to get the fallback language
tr!
andmove_tr!
outside reactive graph- Why examples don't use
<For/>
component? - How to manage translations on server actions
- How to get values of
leptos_fluent!
macro at runtime? - Configuration conditional checks
How to get LanguageIdentifier
?
use fluent_templates::LanguageIdentifier;
fluent-templates
also depends externally on
fluent-bundle
whichs provides utilities for parsing the Fluent syntax.
How to get the i18n context at initialization?
use leptos_fluent::leptos_fluent;
let i18n = leptos_fluent! {
// ...
};
leptos::logging::log!("i18n context: {i18n:#?}");
Custom cookie attributes are invalid
Use an expression to set the cookie attributes and will not be validated.
let attrs = "SameSite=Strict; MyCustomAttr=MyCustomValue;";
leptos_fluent! {
cookie_attrs: attrs,
// ...
};
How to get the fallback language
From fluent-templates v0.10
onwards can be obtained from your translations.
let fallback_language = expect_i18n().translations.get()[0].fallback();
tr!
and move_tr!
outside reactive graph
Outside the reactive ownership tree, mainly known as the reactive graph,
we can't obtain the context of I18n
using expect_context::<leptos_fluent::I18n>()
,
which is what tr!
and move_tr!
do internally. Instead, we can pass the context
as first parameter to the macros:
let i18n = leptos_fluent! {
// ...
};
let translated_signal = move_tr!(i18n, "my-translation");
And some shortcuts cannot be used. Rewrite all the code that calls expect_context
internally:
- Use
i18n.language.set(lang)
instead oflang.activate()
. - Use
lang == i18n.language.get()
instead oflang.is_active()
.
On events, panics
For example, the next code panics when the <div>
container is clicked:
#[component]
pub fn App() -> impl IntoView {
view! {
<Show when=|| true>
<Child/>
</Show>
}
}
#[component]
pub fn Child() -> impl IntoView {
leptos_fluent! {
// ...
};
view! {
<div on:click=|_| {
tr!("my-translation");
}>"CLICK ME!"</div>
}
}
With Leptos v0.7, whatever tr!
macro used in the on:
event will panic,
but with Leptos v0.6, this outsiding of the ownership tree has been ignored
from the majority of the cases as unintended behavior.
To avoid that, pass the i18n context as first parameter to tr!
or move_tr!
:
#[component]
pub fn App() -> impl IntoView {
view! {
<Show when=|| true>
<Child/>
</Show>
}
}
#[component]
pub fn Child() -> impl IntoView {
let i18n = leptos_fluent! {
// ...
};
view! {
<div on:click=|_| {
tr!(i18n, "my-translation");
}>"CLICK ME!"</div>
}
}
Confused about what context is used?
Take into account that the reactive ownership graph is not the same as the component tree in Leptos. For example, the next code:
#[component]
fn Foo() -> impl IntoView {
provide_context::<usize>(0);
view! {
<h1>"Foo"</h1>
{
let value = expect_context::<usize>();
view! {
<p>"Context value before Bar: "{value}</p>
}
}
<Bar/>
{
let value = expect_context::<usize>();
view! {
<p>"Context value after Bar -> Baz: "{value}</p>
}
}
}
}
#[component]
fn Bar() -> impl IntoView {
provide_context::<usize>(1);
view! {
<h1>"Bar"</h1>
{
let value = expect_context::<usize>();
view! {
<p>"Context value before Baz: "{value}</p>
}
}
<Baz/>
}
}
#[component]
fn Baz() -> impl IntoView {
provide_context::<usize>(2);
view! {
<h1>"Baz"</h1>
}
}
Renders:
<h1>Foo</h1>
<p>Context value before Bar: 0</p>
<h1>Bar</h1>
<p>Context value before Baz: 1</p>
<h1>Baz</h1>
<p>Context value after Bar -> Baz: 2</p>
Because Baz
is a sibling of Foo
children in the reactive graph. But maybe
you think that is just a children of Bar
in the component tree and that is
outside the scope of Foo
children. That doesn't matter for Leptos.
In those cases where you're using two or more contexts, pass the context as the
first argument to the tr!
and move_tr!
macros to avoid confusion.
#[component]
fn Foo() -> impl IntoView {
let i18n = leptos_fluent! {
translations: [TRANSLATION_WITH_ONLY_FOO],
// ...
};
<p>{move_tr!("my-translation-from-foo")}</p>
<Bar/>
// The next message will not be translated because after `<Bar>`
// now the i18n context accessed by `move_tr!` is the one from `Bar`
<p>{move_tr!("my-translation-from-foo")}</p>
// instead, use:
<p>{move_tr!(i18n, "my-translation-from-foo")}</p>
}
#[component]
fn Bar() -> impl IntoView {
let i18n = leptos_fluent! {
translations: [TRANSLATION_WITH_ONLY_BAR],
// ...
};
<p>{move_tr!("my-translation-from-bar")}</p>
}
Why examples don't use <For/>
component?
There are some cases in which the <For/>
component is not reproducible between
SSR and hydrate modes leading to different renders, so decided to use a
simple vector to not bring confusion to main examples.
In any case, the <For/>
component is safe on CSR contexts and
leptos_fluent::Language
implement Hash
and Eq
traits to be
able to be passed directly to key
s properties trigerring reactivity
depending on the current active language.
use leptos_fluent::{i18n, Language};
leptos::logging::warn!("[WARNING]: Not secure on SSR");
view! {
<p>{move_tr!("select-a-language")}</p>
<For
each=move || i18n().languages
key=|lang| *lang
children=move |lang| render_language(lang)
/>
}
fn render_language(lang: &'static Language) -> impl IntoView { ... }
How to manage translations on server actions
The translations reside on the client side, so the I18n
can not be
accessed as context on server actions. Pass the translations as values
if the bandwidth is not a problem or use your own statics on server side.
use leptos::*;
use leptos_fluent::{tr, Language};
/// Server action showing client-side translated message on console
#[server(ShowHelloWorld, "/api")]
pub async fn show_hello_world(
translated_hello_world: String,
language: String,
) -> Result<(), ServerFnError> {
println!("{translated_hello_world} ({language})");
Ok(())
}
fn render_language(lang: &'static Language) -> impl IntoView {
// Call on click to server action with a client-side translated
// "hello-world" message
let on_click = move |_| {
lang.activate();
spawn_local(async {
_ = show_hello_world(
tr!("hello-world"),
lang.name.to_string(),
).await;
});
};
view! {
<div>
<label for=lang>{lang.name}</label>
<input
id=lang
name="language"
value=lang
checked=lang.is_active()
on:click=on_click
type="radio"
/>
</div>
}
}
How to get values of leptos_fluent!
macro at runtime?
Use provide_meta_context
at the macro initialization and get them
with the method I18n::meta
:
let i18n = leptos_fluent! {
// ...
provide_meta_context: true,
};
println!("Macro parameters: {:?}", i18n.meta().unwrap());
Configuration conditional checks
leptos_fluent! {
// ...
#[cfg(debug_assertions)]
set_language_to_url_param: true,
#[cfg(not(debug_assertions))]
set_language_to_url_param: false,
}
leptos_fluent!
The leptos_fluent!
macro is used to load the translations and set the current
locale. It is used in the root component of the application.
leptos_fluent! {
locales: "./locales",
translations: [TRANSLATIONS],
};
- Common configurations
- Processing steps
- Parameters
translations
locales
core_locales
languages
check_translations
- CSR |
sync_html_tag_lang
- CSR |
sync_html_tag_dir
- CSR + SSR |
url_param:
"lang" - CSR + SSR |
initial_language_from_url_param
- CSR |
initial_language_from_url_param_to_localstorage
- CSR |
initial_language_from_url_param_to_cookie
- CSR |
set_language_to_url_param
- CSR + SSR |
url_path
- CSR + SSR |
initial_language_from_url_path
- CSR |
initial_language_from_url_path_to_cookie
- CSR |
initial_language_from_url_path_to_localstorage
- CSR |
localstorage_key:
"lang" - CSR |
initial_language_from_localstorage
- CSR |
initial_language_from_localstorage_to_cookie
- CSR |
set_language_to_localstorage
- CSR |
initial_language_from_navigator
- CSR |
initial_language_from_navigator_to_localstorage
- CSR |
initial_language_from_navigator_to_cookie
- CSR |
set_language_from_navigator
- SSR |
initial_language_from_accept_language_header
- CSR + SSR |
cookie_name:
"lf-lang" - CSR |
cookie_attrs:
"" - CSR + SSR |
initial_language_from_cookie
- CSR |
initial_language_from_cookie_to_localstorage
- CSR |
set_language_to_cookie
- system |
initial_language_from_system
- system |
initial_language_from_data_file
- system |
initial_language_from_system_to_data_file
- system |
set_language_to_data_file
- system |
data_file_key:
"leptos-fluent" provide_meta_context
initial_language_from_server_function
set_language_to_server_function
initial_language_from_localstorage_to_server_function
initial_language_from_cookie_to_server_function
initial_language_from_navigator_to_server_function
initial_language_from_url_param_to_server_function
initial_language_from_url_path_to_server_function
initial_language_from_server_function_to_cookie
initial_language_from_server_function_to_localstorage
Common configurations
CSR | Local storage from navigator
leptos_fluent! {
locales: "./locales",
translations: [TRANSLATIONS],
set_language_to_localstorage: true,
initial_language_from_localstorage: true,
initial_language_from_navigator: true,
initial_language_from_navigator_to_localstorage: true,
initial_language_from_url_param: true,
initial_language_from_url_param_to_localstorage: true,
localstorage_key: "lang",
}
CSR + SSR | Cookie from navigator and header
leptos_fluent! {
locales: "./locales",
translations: [TRANSLATIONS],
set_language_to_cookie: true,
initial_language_from_cookie: true,
initial_language_from_navigator: true,
initial_language_from_navigator_to_cookie: true,
initial_language_from_url_param: true,
initial_language_from_url_param_to_cookie: true,
initial_language_from_accept_language_header: true,
cookie_name: "lf-lang",
}
system | Data files on Desktop applications
leptos_fluent! {
locales: "./locales",
translations: [TRANSLATIONS],
initial_language_from_system: true,
initial_language_from_system_to_data_file: true,
initial_language_from_data_file: true,
set_language_to_data_file: true,
data_file_key: "system-language-example",
}
Processing steps
There is four kind of parameters for all the possible configurations and are executed in the next order:
Order
- Get the initial languaje from a source or target:
initial_language_from_*
- Obtain the initial language and set to a target:
initial_language_from_*_to_*
- Synchronize the current language with a target:
set_language_to_*
- The name of a source or a target:
cookie_name
,localstorage_key
,navigator
...
Sources and targets
Sources are read-only and targets are read-write.
- Sources:
navigator
,system
,accept_language_header
- Targets:
cookie_name
,localstorage_key
,url_param
,data_file
...
Commented example
leptos_fluent! {
// ..
// Get the initial language from the source operative system
initial_language_from_system: true,
// and set to the target file.
initial_language_from_system_to_data_file: true,
// If a target data file exists, get the initial language from it.
initial_language_from_data_file: true,
// When the language is updated, set it to the file.
set_language_to_data_file: true,
// Unique file name to set the language for this app:
data_file_key: "system-language-example",
};
Parameters
translations
Set the translations to be used in the application. It must be a reference
to a static array of fluent_templates::static_loader!
instances.
use fluent_templates::static_loader;
use leptos_fluent::leptos_fluent;
static_loader! {
pub static TRANSLATIONS = {
locales: "./locales",
fallback_language: "en",
};
}
leptos_fluent! {
locales: "./locales",
translations: [TRANSLATIONS],
// ^^^^^^^^^^^^^^^^^^^^^^^^^
}
Must be the same identifier used in the fluent_templates::static_loader!
macro, which returns an once_cell:sync::Lazy
variable.
locales
Set the path to the locales directory which contain the Fluent
files for the translations. Must be relative to the Cargo.toml file, the same
used in the fluent_templates::static_loader!
macro.
leptos_fluent! {
locales: "./locales",
// ^^^^^^^^^^^^^^^^^
translations: [TRANSLATIONS],
}
core_locales
Common locale resources that are shared across all locales.
Must be relative to the Cargo.toml file, the same
used in the fluent_templates::static_loader!
macro:
static_loader! {
pub static TRANSLATIONS = {
locales: "./locales",
core_locales: "./locales/core",
};
}
leptos_fluent! {
locales: "./locales",
core_locales: "./locales/core",
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^
translations: [TRANSLATIONS],
}
languages
Path to a file containing the list of languages supported by the application. Must be relative to the Cargo.toml file.
leptos_fluent! {
locales: "./locales",
languages: "./locales/languages.json",
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
translations: [TRANSLATIONS],
}
See 4. Languages
The languages file should contain an array of arrays where each inner array contains a language identifier and a language name, respectively, at least.
The language identifier should be a valid language tag, such as en-US
, en
,
es-ES
, etc. By default, the languages file should be a JSON with a .json
extension because the json
feature is enabled. For example:
[
["en-US", "English (United States)"],
["es-ES", "EspaΓ±ol (EspaΓ±a)"]
]
Set default-features = false
and enable the yaml
or the json5
feature
to use a YAML or JSON5 files. For example:
# locales/languages.yaml
- - en-US
- English (United States)
- - es-ES
- EspaΓ±ol (EspaΓ±a)
// locales/languages.json5
[
["en-US", "English (United States)"],
["es-ES", "EspaΓ±ol (EspaΓ±a)"],
]
Define a third element in the inner array with the direction of the language,
to use it in the <html dir="...">
attribute (see sync_html_tag_dir
).
For example:
[
["en-US", "English (United States)", "ltr"],
["es-ES", "EspaΓ±ol (EspaΓ±a)", "auto"],
["ar", "Ψ§ΩΨΉΨ±Ψ¨ΩΨ©", "rtl"],
["it", "Italiano"]
]
check_translations
Check the translations at compile time. It is useful to ensure that all translations are correct and that there are no missing translations.
Must be a glob relative to the Cargo.toml file.
-
For single crate projects:
leptos_fluent! { locales: "./locales", translations: [TRANSLATIONS], check_translations: "./src/**/*.rs", // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ }
-
For workspace projects:
leptos_fluent! { locales: "./locales", translations: [TRANSLATIONS], check_translations: "../{app,components}/src/**/*.rs", // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ }
CSR | sync_html_tag_lang
Synchronize the global <html lang="...">
attribute with current language
using leptos::create_effect
. Can be a literal boolean or an expression
that will be evaluated at runtime.
leptos_fluent! {
// ...
sync_html_tag_lang: true,
}
CSR | sync_html_tag_dir
Synchronize the global <html dir="...">
attribute with current language
using leptos::create_effect
.
Can be a literal boolean or an expression that will be evaluated at runtime.
leptos_fluent! {
// ...
sync_html_tag_dir: true,
}
For custom languages from a languages file, specify a third element in
the inner array with the direction of the language, which can be "auto"
,
"ltr"
, or "rtl"
. Discovered languages will be defined depending on the
language.
CSR + SSR | url_param:
"lang"
Name of URL parameter used to manage the current language.
leptos_fluent! {
// ...
url_param: "lang",
}
CSR + SSR | initial_language_from_url_param
Set initial language from URL parameter.
leptos_fluent! {
// ...
initial_language_from_url_param: true,
}
CSR | initial_language_from_url_param_to_localstorage
Get initial language from URL parameter and save it to local storage.
leptos_fluent! {
// ...
initial_language_from_url_param_to_localstorage: true,
}
CSR | initial_language_from_url_param_to_cookie
Get initial language from URL parameter and save it to a cookie.
leptos_fluent! {
// ...
initial_language_from_url_param_to_cookie: true,
}
CSR | set_language_to_url_param
Synchronize current language with URL parameter.
leptos_fluent! {
// ...
set_language_to_url_param: true,
}
CSR + SSR | url_path
Language extractor from URL path. It must take the URI path as argument and return the possible language.
/// Get the language from the top directory in the URL path.
fn get_language_from_url_path(path: &str) -> &str {
if let Some(language) = path.split('/').nth(1) {
return language;
}
""
}
leptos_fluent! {
// ...
url_path: get_language_from_url_path,
}
CSR + SSR | initial_language_from_url_path
Set initial language from URL path.
leptos_fluent! {
// ...
initial_language_from_url_path: true,
}
CSR | initial_language_from_url_path_to_cookie
Set initial language from URL path to a cookie.
leptos_fluent! {
// ...
initial_language_from_url_path_to_cookie: true,
}
CSR | initial_language_from_url_path_to_localstorage
Set initial language from URL path to local storage.
leptos_fluent! {
// ...
initial_language_from_url_path_to_localstorage: true,
}
CSR | localstorage_key:
"lang"
Local storage key to manage the current language.
leptos_fluent! {
// ...
localstorage_key: "lang",
}
CSR | initial_language_from_localstorage
Get initial language from local storage.
leptos_fluent! {
// ...
initial_language_from_localstorage: true,
}
CSR | initial_language_from_localstorage_to_cookie
Get initial language from local storage and save it to a cookie.
leptos_fluent! {
// ...
initial_language_from_localstorage_to_cookie: true,
}
CSR | set_language_to_localstorage
Set the current language to local storage.
leptos_fluent! {
// ...
set_language_to_localstorage: true,
}
CSR | initial_language_from_navigator
Get the initial language from navigator.languages
.
leptos_fluent! {
// ...
initial_language_from_navigator: true,
}
CSR | initial_language_from_navigator_to_localstorage
Get the initial language from navigator.languages
and save it in the local storage.
leptos_fluent! {
// ...
initial_language_from_navigator_to_localstorage: true,
}
CSR | initial_language_from_navigator_to_cookie
Get the initial language from navigator.languages
and save it in a cookie.
leptos_fluent! {
// ...
initial_language_from_navigator_to_cookie: true,
}
CSR | set_language_from_navigator
When the user changes the language in the browser settings, the language change will be reflected in the client.
leptos_fluent! {
// ...
set_language_from_navigator: true,
}
SSR | initial_language_from_accept_language_header
Get the initial language from the Accept-Language
header.
leptos_fluent! {
// ...
initial_language_from_accept_language_header: true,
}
CSR + SSR | cookie_name:
"lf-lang"
Name of the cookie that will be used to manage the current language. By default
it is "lf-lang"
.
leptos_fluent! {
// ...
cookie_name: "lang",
}
CSR | cookie_attrs:
""
Cookie attributes to set on the language cookie.
leptos_fluent! {
// ...
cookie_attrs: "SameSite=Strict; Secure",
}
If value is an expression the cookie will not be validated at compile time:
let attrs = "SameSite=Strict; Secure; MyCustomCookie=value"
leptos_fluent! {
// ...
cookie_attrs: attrs,
}
CSR + SSR | initial_language_from_cookie
Get the initial language from the cookie.
leptos_fluent! {
// ...
initial_language_from_cookie: true,
}
CSR | initial_language_from_cookie_to_localstorage
Get the initial language from the cookie and save it in the local storage.
leptos_fluent! {
// ...
initial_language_from_cookie_to_localstorage: true,
}
CSR | set_language_to_cookie
Set the current language to the cookie.
leptos_fluent! {
// ...
set_language_to_cookie: true,
}
system | initial_language_from_system
Get the initial language from the system.
leptos_fluent! {
// ...
initial_language_from_system: true,
}
system | initial_language_from_data_file
Get the initial language from a data file.
leptos_fluent! {
// ...
initial_language_from_data_file: true,
}
system | initial_language_from_system_to_data_file
Get the initial language from the system and save it in a data file.
leptos_fluent! {
// ...
initial_language_from_system_to_data_file: true,
}
system | set_language_to_data_file
Set the current language to a data file.
leptos_fluent! {
// ...
set_language_to_data_file: true,
}
system | data_file_key:
"leptos-fluent"
Key to manage the current language in the data file. It should be unique per application.
leptos_fluent! {
// ...
data_file_key: "my-app",
}
provide_meta_context
Provide the macro meta information at runtime as a context.
Get it using I18n::meta
:
let i18n = leptos_fluent! {
// ...
provide_meta_context: true,
};
println!("Macro parameters: {:?}", i18n.meta().unwrap());
initial_language_from_server_function
Get the initial language from a server function.
leptos_fluent! {
// ...
initial_language_from_server_function: initial_language_server_function,
}
/// Server function to set the initial language
#[server(InitialLanguage, "/api")]
pub async fn initial_language_server_function(
) -> Result<Option<String>, ServerFnError> {
// .. replace with your own logic
Ok(Some("es".to_string()))
}
This parameter type is not like the initial_language_from_*
parameters, it
takes an identifier to the server function that will be called to get the
initial language.
The function must return a Result<Option<String>, ServerFnError>
.
set_language_to_server_function
Set the current language to a server function.
leptos_fluent! {
// ...
set_language_to_server_function: set_language_server_function,
}
/// Server function to update the current language
#[server(SetLanguage, "/api")]
pub async fn set_language_server_function(
language: String,
) -> Result<(), ServerFnError> {
// .. replace with your own logic
Ok(())
}
This parameter type is not like the set_language_to_*
parameters, it
takes an identifier to the server function that will be called to update
the current language.
The function must return a Result<(), ServerFnError>
.
initial_language_from_localstorage_to_server_function
Get the initial language from local storage and set it to a server function.
leptos_fluent! {
// ...
initial_language_from_localstorage_to_server_function: set_language_server_function,
}
#[server(SetLanguage, "/api")]
pub async fn set_language_server_function(
language: String,
) -> Result<(), ServerFnError> {
// .. replace with your own logic
Ok(())
}
initial_language_from_cookie_to_server_function
Get the initial language from a cookie and set it to a server function.
leptos_fluent! {
// ...
initial_language_from_cookie_to_server_function: set_language_server_function,
}
#[server(SetLanguage, "/api")]
pub async fn set_language_server_function(
language: String,
) -> Result<(), ServerFnError> {
// .. replace with your own logic
Ok(())
}
initial_language_from_navigator_to_server_function
Get the initial language from navigator.languages
and set it to a
server function.
leptos_fluent! {
// ...
initial_language_from_navigator_to_server_function: set_language_server_function,
}
#[server(SetLanguage, "/api")]
pub async fn set_language_server_function(
language: String,
) -> Result<(), ServerFnError> {
// .. replace with your own logic
Ok(())
}
initial_language_from_url_param_to_server_function
Get the initial language from a URL parameter and set it to a server function.
leptos_fluent! {
// ...
initial_language_from_url_param_to_server_function: set_language_server_function,
}
#[server(SetLanguage, "/api")]
pub async fn set_language_server_function(
language: String,
) -> Result<(), ServerFnError> {
// .. replace with your own logic
Ok(())
}
initial_language_from_url_path_to_server_function
Get the initial language from a URL path and set it to a server function.
leptos_fluent! {
// ...
initial_language_from_url_path_to_server_function: set_language_server_function,
}
#[server(SetLanguage, "/api")]
pub async fn set_language_server_function(
language: String,
) -> Result<(), ServerFnError> {
// .. replace with your own logic
Ok(())
}
initial_language_from_server_function_to_cookie
Get the initial language from a server function and set it to a cookie.
leptos_fluent! {
// ...
initial_language_from_server_function_to_cookie: true,
}
initial_language_from_server_function_to_localstorage
Get the initial language from a server function and set it to local storage.
leptos_fluent! {
// ...
initial_language_from_server_function_to_localstorage: true,
}