Document not found (404)
+This URL is invalid, sorry. Please use the navigation bar or search to continue.
+ +diff --git a/v0.1.26/.nojekyll b/v0.1.26/.nojekyll new file mode 100644 index 00000000..f1731109 --- /dev/null +++ b/v0.1.26/.nojekyll @@ -0,0 +1 @@ +This file makes sure that Github Pages doesn't process mdBook's output. diff --git a/v0.1.26/404.html b/v0.1.26/404.html new file mode 100644 index 00000000..ce4fde05 --- /dev/null +++ b/v0.1.26/404.html @@ -0,0 +1,209 @@ + + +
+ + +This URL is invalid, sorry. Please use the navigation bar or search to continue.
+ +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"]
+
+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"))
+I18n
contextUse 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");
+I18n
contextThe 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.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);
+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();
+To get the available languages, iterate over the languages
field:
i18n.languages.iter()
+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()
+
+ 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",
+}
+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",
+ | ^^^^^^^^^^^^^^^
+
+
+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.
LanguageIdentifier
?tr!
and move_tr!
outside reactive graph
+
+<For/>
component?leptos_fluent!
macro at runtime?LanguageIdentifier
?use fluent_templates::LanguageIdentifier;
+fluent-templates
also depends externally on
+fluent-bundle
+whichs provides utilities for parsing the Fluent syntax.
use leptos_fluent::leptos_fluent;
+
+let i18n = leptos_fluent! {
+ // ...
+};
+
+leptos::logging::log!("i18n context: {i18n:#?}");
+Use an expression to set the cookie attributes and will not be validated.
+let attrs = "SameSite=Strict; MyCustomAttr=MyCustomValue;";
+leptos_fluent! {
+ cookie_attrs: attrs,
+ // ...
+};
+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 graphOutside 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:
i18n.language.set(lang)
instead of lang.activate()
.lang == i18n.language.get()
instead of lang.is_active()
.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>
+ }
+}
+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>
+}
+<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 { ... }
+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>
+ }
+}
+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());
+leptos_fluent! {
+ // ...
+ #[cfg(debug_assertions)]
+ set_language_to_url_param: true,
+ #[cfg(not(debug_assertions))]
+ set_language_to_url_param: false,
+}
+
+ 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.
+ +The main goals of leptos-fluent are:
+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.
See CONTRIBUTING.md file for more information about how to setup the +development environment and contribute to the project.
+ +