leptos_fluent/
lib.rs

1#![deny(missing_docs)]
2#![forbid(unsafe_code)]
3#![cfg_attr(feature = "nightly", feature(fn_traits))]
4#![cfg_attr(feature = "nightly", feature(unboxed_closures))]
5#![cfg_attr(feature = "nightly", feature(stmt_expr_attributes))]
6
7//! [![Crates.io](https://img.shields.io/crates/v/leptos-fluent?logo=rust)](https://crates.io/crates/leptos-fluent)
8//! [![License](https://img.shields.io/crates/l/leptos-fluent?logo=mit)](https://github.com/mondeja/leptos-fluent/blob/master/LICENSE)
9//! [![Tests](https://img.shields.io/github/actions/workflow/status/mondeja/leptos-fluent/ci.yml?label=tests&logo=github)](https://github.com/mondeja/leptos-fluent/actions)
10//! [![Book](https://img.shields.io/github/actions/workflow/status/mondeja/leptos-fluent/.github%2Fworkflows%2Fci.yml?logo=github&label=book)][book]
11//! [![docs.rs](https://img.shields.io/docsrs/leptos-fluent?logo=docs.rs)](https://docs.rs/leptos-fluent)
12//! [![Crates.io downloads](https://img.shields.io/crates/d/leptos-fluent)](https://crates.io/crates/leptos-fluent)
13//! [![Discord channel](https://img.shields.io/badge/discord-grey?logo=discord&logoColor=white)](https://discord.com/channels/1031524867910148188/1251579884371705927)
14//!
15//! Internationalization framework for [Leptos] using [fluent-templates].
16//!
17//! # Installation
18//!
19//! Add the following to your `Cargo.toml` file:
20//!
21//! ```toml
22//! [dependencies]
23//! leptos-fluent = "0.2"
24//!
25//! [features]
26//! hydrate = [
27//!   "leptos-fluent/hydrate"
28//! ]
29//! ssr = [
30//!   "leptos-fluent/ssr",
31//!   "leptos-fluent/actix",  # actix and axum are supported
32//! ]
33//! ```
34//!
35//! If you're using `cargo-leptos` to build your project, watch the
36//! _locales/_ folder with:
37//!
38//! ```toml
39//! [package.metadata.leptos]
40//! watch-additional-files = ["locales"]  # Relative to Cargo.toml
41//! ```
42//!
43//! # Usage
44//!
45//! Giving the following directory structure:
46//!
47//! ```plaintext
48//! .
49//! β”œβ”€β”€ πŸ“„ Cargo.toml
50//! β”œβ”€β”€ πŸ“ locales
51//! β”‚   β”œβ”€β”€ πŸ“ en
52//! β”‚   β”‚   └── πŸ“„ main.ftl
53//! β”‚   └── πŸ“ es
54//! β”‚       └── πŸ“„ main.ftl
55//! └── πŸ“ src
56//!     β”œβ”€β”€ πŸ“„ main.rs
57//!     └── πŸ“„ lib.rs
58//! ```
59//!
60//! ```ftl
61//! # locales/en/main.ftl
62//! hello-world = Hello, world!
63//! hello-args = Hello, { $arg1 } and { $arg2 }!
64//! ```
65//!
66//! ```ftl
67//! # locales/es/main.ftl
68//! hello-world = Β‘Hola, mundo!
69//! hello-args = Β‘Hola, { $arg1 } y { $arg2 }!
70//! ```
71//!
72//! You can use `leptos-fluent` as follows:
73//!
74//! ```rust,ignore
75//! use leptos::prelude::*;
76//! use leptos_fluent::{I18n, leptos_fluent, move_tr, tr};
77//!
78//! #[component]
79//! fn I18nProvider(children: Children) -> impl IntoView {
80//!     // See all options in the reference at
81//!     // https://mondeja.github.io/leptos-fluent/latest/leptos_fluent.html
82//!     leptos_fluent! {
83//!         children: children(),
84//!         // Path to the locales directory, relative to Cargo.toml.
85//!         locales: "./locales",
86//!         // Initial language when the user don't load any with
87//!         // the provided configuration.
88//!         default_language: "en",
89//!         // Check translations correctness in the specified files.
90//!         #[cfg(debug_assertions)]
91//!         check_translations: "./src/**/*.rs",
92//!
93//!         // Client side options
94//!         // -------------------
95//!         // Synchronize `<html lang="...">` attribute with
96//!         // current active language.
97//!         sync_html_tag_lang: true,
98//!         // Synchronize `<html dir="...">` attribute with `"ltr"`,
99//!         // `"rtl"` or `"auto"` depending on active language.
100//!         sync_html_tag_dir: true,
101//!         // Update language on URL parameter when changes.
102//!         set_language_to_url_param: true,
103//!         // Set initial language of user from URL in local storage.
104//!         initial_language_from_url_param_to_local_storage: true,
105//!         // Set initial language of user from URL in a cookie.
106//!         initial_language_from_url_param_to_cookie: true,
107//!         // Key used to get and set the current language of the
108//!         // user on local storage. By default is `"lang"`.
109//!         local_storage_key: "language",
110//!         // Get initial language from local storage if not found
111//!         // in an URL param.
112//!         initial_language_from_local_storage: true,
113//!         // Set the initial language of the user from
114//!         // local storage to a cookie.
115//!         initial_language_from_local_storage_to_cookie: true,
116//!         // Update language on local storage when changes.
117//!         set_language_to_local_storage: true,
118//!         // Get initial language from `navigator.languages`
119//!         // if not found in local storage.
120//!         initial_language_from_navigator: true,
121//!         // Set initial language of user from navigator to local storage.
122//!         initial_language_from_navigator_to_local_storage: true,
123//!         // Set initial language of user from navigator to a cookie.
124//!         initial_language_from_navigator_to_cookie: true,
125//!         // Attributes to set for language cookie.
126//!         // By default `""`.
127//!         cookie_attrs: "Secure; Path=/; Max-Age=600",
128//!         // Update language on cookie when the language changes.
129//!         set_language_to_cookie: true,
130//!         // Set initial language from a cookie to local storage.
131//!         initial_language_from_cookie_to_local_storage: true,
132//!
133//!         // Server side options
134//!         // -------------------
135//!         // Set initial language from the `Accept-Language`
136//!         // header of the request.
137//!         initial_language_from_accept_language_header: true,
138//!
139//!         // Server and client side options
140//!         // ------------------------------
141//!         // Name of the cookie to get and set the current active
142//!         // language. By default `"lf-lang"`.
143//!         cookie_name: "lang",
144//!         // Set initial language from cookie.
145//!         initial_language_from_cookie: true,
146//!         // URL parameter to use setting the language in the URL.
147//!         // By default `"lang"`.
148//!         url_param: "lang",
149//!         // Set initial language of the user from an URL parameter.
150//!         initial_language_from_url_param: true,
151//!
152//!         // Desktop applications (feature `system`)
153//!         // ---------------------------------------
154//!         // Set initial language from the system locale.
155//!         initial_language_from_system: true,
156//!         // Set initial language of the user from
157//!         // the system locale to a data file.
158//!         initial_language_from_system_to_data_file: true,
159//!         // Get initial language from a data file.
160//!         initial_language_from_data_file: true,
161//!         // Key to use to name the data file. Should be unique per
162//!         // application. By default `"leptos-fluent"`.
163//!         data_file_key: "my-app",
164//!         // Set the language selected to a data file.
165//!         set_language_to_data_file: true,
166//!     }
167//! }
168//!
169//! #[component]
170//! pub fn App() -> impl IntoView {
171//!     view! {
172//!         <I18nProvider>
173//!             <TranslatableComponent/>
174//!             <LanguageSelector/>
175//!         </I18nProvider>
176//!     }
177//! }
178//!
179//! #[component]
180//! fn TranslatableComponent() -> impl IntoView {
181//!     // Use `tr!` and `move_tr!` macros to translate strings:
182//!     view! {
183//!         <p>
184//!             <span>{move || tr!("hello-world")}</span>
185//!             <span>{move_tr!("hello-args", {
186//!                 "arg1" => "foo",
187//!                 "arg2" => "bar",
188//!             })}</span>
189//!         </p>
190//!     }
191//!
192//!     // The `tr!` macro must be inside a reactive context or the
193//!     // translation will not be updated on the fly when the language changes.
194//! }
195//!
196//! #[component]
197//! fn LanguageSelector() -> impl IntoView {
198//!     // `expect_context::<leptos_fluent::I18n>()` to get the i18n context
199//!     // `i18n.languages` exposes a static array with the available languages
200//!     // `i18n.language.get()` to get the active language
201//!     // `i18n.language.set(lang)` to set the active language
202//!
203//!     let i18n = expect_context::<I18n>();
204//!
205//!     view! {
206//!         <fieldset>
207//!             {
208//!                 move || i18n.languages.iter().map(|lang| {
209//!                     view! {
210//!                         <div>
211//!                             <input
212//!                                 type="radio"
213//!                                 id=lang
214//!                                 name="language"
215//!                                 value=lang
216//!                                 checked=&i18n.language.get() == lang
217//!                                 on:click=move |_| i18n.language.set(lang)
218//!                             />
219//!                             <label for=lang>{lang.name}</label>
220//!                         </div>
221//!                     }
222//!                 }).collect::<Vec<_>>()
223//!             }
224//!         </fieldset>
225//!     }
226//! }
227//! ```
228//!
229//! ## Features
230//!
231//! - **Server Side Rendering**: `ssr`
232//! - **Hydration**: `hydrate`
233//! - **Actix Web integration**: `actix`
234//! - **Axum integration**: `axum`
235//! - **Nightly toolchain**: `nightly`
236//! - **Desktop applications**: `system`
237//! - **JSON languages file**: `json`
238//! - **YAML languages file**: `yaml`
239//! - **JSON5 languages file**: `json5`
240//! - **Tracing support**: `tracing`
241//! - **Debugging**: `debug`
242//! - **Disable Unicode isolating marks**: `disable-unicode-isolating-marks`
243//!
244//! # Resources
245//!
246//! - [Book]
247//! - [Quickstart]
248//! - [Documentation]
249//! - [Examples]
250//!
251//! [leptos]: https://leptos.dev/
252//! [fluent-templates]: https://github.com/XAMPPRocky/fluent-templates
253//! [quickstart]: https://mondeja.github.io/leptos-fluent/latest/leptos_fluent.html
254//! [examples]: https://github.com/mondeja/leptos-fluent/tree/master/examples
255//! [book]: https://mondeja.github.io/leptos-fluent/latest/
256//! [documentation]: https://docs.rs/leptos-fluent
257
258#[doc(hidden)]
259pub mod cookie;
260#[cfg(feature = "system")]
261#[doc(hidden)]
262pub mod data_file;
263#[doc(hidden)]
264pub mod http_header;
265#[doc(hidden)]
266pub mod local_storage;
267#[doc(hidden)]
268pub mod session_storage;
269#[doc(hidden)]
270pub mod url;
271
272// Re-exports used by `leptos_fluent!` macro.
273#[doc(hidden)]
274pub mod __reexports {
275    #[doc(hidden)]
276    #[cfg(feature = "system")]
277    pub use current_locale;
278    #[doc(hidden)]
279    pub use fluent_bundle;
280    #[doc(hidden)]
281    pub use fluent_templates;
282    #[doc(hidden)]
283    pub use leptos_meta;
284    #[doc(hidden)]
285    pub use wasm_bindgen;
286    #[doc(hidden)]
287    pub use web_sys;
288}
289
290pub use leptos_fluent_macros::leptos_fluent;
291
292use core::hash::{Hash, Hasher};
293use core::ops::Deref;
294use core::str::FromStr;
295use fluent_bundle::FluentValue;
296use fluent_templates::{loader::Loader, LanguageIdentifier, StaticLoader};
297#[cfg(feature = "nightly")]
298use leptos::prelude::Get;
299use leptos::{
300    attr::AttributeValue,
301    prelude::{
302        guards::ReadGuard, use_context, Read, RwSignal, Set, Signal, With,
303    },
304};
305use std::borrow::Cow;
306use std::sync::LazyLock;
307
308/// Direction of the text
309#[derive(Debug)]
310pub enum WritingDirection {
311    /// Left to right
312    Ltr,
313    /// Right to left
314    Rtl,
315    /// Auto
316    Auto,
317}
318
319impl WritingDirection {
320    /// Get the string representation of the writing direction
321    pub fn as_str(&self) -> &'static str {
322        match self {
323            WritingDirection::Ltr => "ltr",
324            WritingDirection::Rtl => "rtl",
325            WritingDirection::Auto => "auto",
326        }
327    }
328}
329
330impl core::fmt::Display for WritingDirection {
331    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
332        f.write_str(self.as_str())
333    }
334}
335
336/// Each language supported by your application.
337#[derive(Clone, Debug)]
338pub struct Language {
339    /// Language identifier
340    ///
341    /// Can be any valid language tag, such as `en`, `es`, `en-US`, `es-ES`, etc.
342    pub id: &'static LanguageIdentifier,
343    /// Language name
344    ///
345    /// The name of the language, such as `English`, `EspaΓ±ol`, etc.
346    /// This name will be intended to be displayed in the language selector.
347    pub name: &'static str,
348    /// Writing direction of the language
349    pub dir: &'static WritingDirection,
350    /// Flag of the country of the language as emoji (if any)
351    pub flag: Option<&'static str>,
352}
353
354impl Language {
355    /// Get if the language is the active language.
356    #[deprecated(
357        since = "0.2.13",
358        note = "will be removed in v0.3.0. Use `&i18n.language.get() == lang` instead of `lang.is_active()`."
359    )]
360    #[inline(always)]
361    pub fn is_active(&'static self) -> bool {
362        self == leptos::prelude::expect_context::<I18n>().language.read()
363    }
364
365    /// Set the language as the active language.
366    #[deprecated(
367        since = "0.2.13",
368        note = "will be removed in v0.3.0. Use `&i18n.language.set(lang)` instead of `lang.activate()`."
369    )]
370    #[inline(always)]
371    pub fn activate(&'static self) {
372        leptos::prelude::expect_context::<I18n>().language.set(self);
373    }
374}
375
376impl PartialEq for Language {
377    fn eq(&self, other: &Self) -> bool {
378        self.id == other.id
379    }
380}
381
382// Implementation for `&Language == ReadGuard<&Language, Plain<&Language>>`
383//
384// This implementation is required to ensure symmetry with
385// `ReadGuard<&Language, Plain<&Language>> == &Language`, implemented by Leptos.
386// That implementation cannot be included in Leptos as it would generate
387// the problem of transitive chains that criss-cross crate boundaries.
388// See `PartiaEq` documentation.
389impl<'a, Inner> PartialEq<ReadGuard<&'a Language, Inner>> for &Language
390where
391    Inner: Deref<Target = &'a Language>,
392{
393    fn eq(&self, other: &ReadGuard<&'a Language, Inner>) -> bool {
394        self == other.deref()
395    }
396}
397
398impl Eq for Language {}
399
400impl Hash for Language {
401    fn hash<H: Hasher>(&self, state: &mut H) {
402        // TODO: `<For/>` component's `key` hashes doesn't seem to be working
403        // between different hydrate and SSR contexts, so implement `Language`s
404        // is currently discouraged. This needs to be fully debugged and open
405        // an issue in the `leptos` repository if necessary.
406        let current_lang =
407            leptos::prelude::expect_context::<I18n>().language.read();
408        let key = format!(
409            "{}{}",
410            self.id,
411            if self == current_lang { "1" } else { "0" },
412        );
413        state.write(key.as_bytes());
414    }
415}
416
417impl FromStr for Language {
418    type Err = ();
419
420    fn from_str(s: &str) -> Result<Self, Self::Err> {
421        language_from_str_between_languages(
422            s,
423            leptos::prelude::expect_context::<I18n>().languages,
424        )
425        .ok_or(())
426        .cloned()
427    }
428}
429
430macro_rules! impl_attr_value_for_language {
431    () => {
432        type State = <String as AttributeValue>::State;
433        type AsyncOutput = String;
434        type Cloneable = String;
435        type CloneableOwned = String;
436
437        fn html_len(&self) -> usize {
438            self.id.to_string().len()
439        }
440
441        fn to_html(self, key: &str, buf: &mut String) {
442            <&str as AttributeValue>::to_html(
443                self.id.to_string().as_str(),
444                key,
445                buf,
446            );
447        }
448
449        fn to_template(_key: &str, _buf: &mut String) {}
450
451        fn hydrate<const FROM_SERVER: bool>(
452            self,
453            key: &str,
454            el: &leptos::tachys::renderer::types::Element,
455        ) -> Self::State {
456            <String as AttributeValue>::hydrate::<FROM_SERVER>(
457                self.id.to_string(),
458                key,
459                el,
460            )
461        }
462
463        fn build(
464            self,
465            el: &leptos::tachys::renderer::types::Element,
466            key: &str,
467        ) -> Self::State {
468            <String as AttributeValue>::build(self.id.to_string(), el, key)
469        }
470
471        fn rebuild(self, key: &str, state: &mut Self::State) {
472            <String as AttributeValue>::rebuild(self.id.to_string(), key, state)
473        }
474
475        fn into_cloneable(self) -> Self::Cloneable {
476            self.id.to_string()
477        }
478
479        fn into_cloneable_owned(self) -> Self::CloneableOwned {
480            self.id.to_string()
481        }
482
483        fn dry_resolve(&mut self) {}
484
485        async fn resolve(self) -> Self::AsyncOutput {
486            self.id.to_string()
487        }
488    };
489}
490
491impl AttributeValue for &Language {
492    impl_attr_value_for_language!();
493}
494
495impl AttributeValue for &&Language {
496    impl_attr_value_for_language!();
497}
498
499/// Internationalization context.
500///
501/// Used to provide the current language, the available languages and all
502/// the translations. It is capable of doing what is needed to translate
503/// and manage translations in a whole application.
504#[derive(Clone, Copy, Debug)]
505pub struct I18n {
506    /// Signal that holds the current language.
507    pub language: RwSignal<&'static Language>,
508    /// Available languages for the application.
509    pub languages: &'static [&'static Language],
510    /// Signal with a vector of fluent-templates static loaders.
511    pub translations: Signal<Vec<&'static LazyLock<StaticLoader>>>,
512}
513
514impl I18n {
515    /// Get meta information about the i18n context.
516    ///
517    /// Useful to get at runtime the parameters that created the context
518    /// when invoking the `leptos_fluent!` macro. The context needs to be
519    /// created activating the `provide_meta_context` or this method will
520    /// raise an error message.
521    ///
522    /// ```rust,ignore
523    /// use leptos::prelude::*;
524    /// use leptos_fluent::{leptos_fluent, I18n};
525    ///
526    /// leptos_fluent! {
527    ///     // ...
528    ///     provide_meta_context: true,
529    /// };
530    ///
531    /// // ... later in the code
532    /// leptos::logging::log!("Macro parameters: {:?}", expect_context::<I18n>().meta().unwrap());
533    /// ```
534    #[cfg_attr(
535        feature = "tracing",
536        tracing::instrument(level = "trace", err(Debug))
537    )]
538    pub fn meta(&self) -> Result<LeptosFluentMeta, &'static str> {
539        leptos::prelude::use_context::<LeptosFluentMeta>().ok_or(concat!(
540            "You need to call `leptos_fluent!` with the parameter",
541            " 'provide_meta_context' enabled to provide the meta context",
542            " for the macro."
543        ))
544    }
545
546    /// Get the translation of a text identifier to the current language.
547    ///
548    /// ```rust,ignore
549    /// use leptos::prelude::expect_context;
550    /// use leptos_fluent::I18n;
551    ///
552    /// let text_id = "hello-world";
553    /// expect_context::<I18n>().tr(text_id);
554    /// ```
555    ///
556    /// Calls to this function will not be checked by the `check_translations` option
557    /// in the `leptos_fluent!` macro. To avoid the warning for a specific identifier
558    /// or to use dynamic variables for translation data, use this function directly.
559    #[cfg_attr(
560        feature = "tracing",
561        tracing::instrument(level = "trace", skip_all)
562    )]
563    pub fn tr(&self, text_id: &str) -> String {
564        let found = self.translations.with(|translations| {
565            self.language.with(|language| {
566                translations
567                    .iter()
568                    .find_map(|tr| tr.try_lookup(language.id, text_id))
569            })
570        });
571
572        #[cfg(feature = "tracing")]
573        {
574            if found.is_none() {
575                tracing::warn!(
576                "Localization message \"{text_id}\" not found in any translation"
577            );
578            } else {
579                tracing::trace!(
580                    "{}",
581                    format!(
582                        concat!(
583                        "Localization message \"{}\" found in a translation.",
584                        " Translated to \"{}\"."
585                    ),
586                        text_id,
587                        found.as_ref().unwrap()
588                    ),
589                );
590            }
591        }
592
593        found.unwrap_or(format!("Unknown localization {text_id}"))
594    }
595
596    /// Get the translation of a text identifier to the current language with arguments.
597    ///
598    /// ```rust,ignore
599    /// use leptos::prelude::expect_context;
600    /// use std::collections::HashMap;
601    /// use leptos_fluent::I18n;
602    ///
603    /// let i18n = expect_context::<I18n>();
604    /// let mut args = HashMap::new();
605    /// args.insert("name".into(), "John".into());
606    /// i18n.tr_with_args("hello-args", &args);
607    /// ```
608    ///
609    /// Calls to this function will not be checked by the `check_translations` option
610    /// in the `leptos_fluent!` macro. To avoid the warning for a specific identifier
611    /// or to use dynamic variables for translation data, use this function directly.
612    #[cfg_attr(
613        feature = "tracing",
614        tracing::instrument(level = "trace", skip_all)
615    )]
616    pub fn tr_with_args(
617        &self,
618        text_id: &str,
619        args: &std::collections::HashMap<Cow<'static, str>, FluentValue>,
620    ) -> String {
621        let found = self.translations.with(|translations| {
622            self.language.with(|language| {
623                translations.iter().find_map(|tr| {
624                    tr.try_lookup_with_args(language.id, text_id, args)
625                })
626            })
627        });
628
629        #[cfg(feature = "tracing")]
630        {
631            if found.is_none() {
632                tracing::warn!(
633                "Localization message \"{text_id}\" not found in any translation"
634            );
635            } else {
636                tracing::trace!(
637                    "{}",
638                    format!(
639                        concat!(
640                        "Localization message \"{}\" found in a translation.",
641                        " Translated to \"{}\"."
642                    ),
643                        text_id,
644                        found.as_ref().unwrap()
645                    ),
646                );
647            }
648        }
649
650        found.unwrap_or(format!("Unknown localization {text_id}"))
651    }
652}
653
654// get language
655#[cfg(feature = "nightly")]
656impl FnOnce<()> for I18n {
657    type Output = &'static Language;
658    #[inline]
659    extern "rust-call" fn call_once(self, _args: ()) -> Self::Output {
660        self.language.get()
661    }
662}
663
664#[cfg(feature = "nightly")]
665impl FnMut<()> for I18n {
666    #[inline]
667    extern "rust-call" fn call_mut(&mut self, _args: ()) -> Self::Output {
668        self.language.get()
669    }
670}
671
672#[cfg(feature = "nightly")]
673impl Fn<()> for I18n {
674    #[inline]
675    extern "rust-call" fn call(&self, _args: ()) -> Self::Output {
676        self.language.get()
677    }
678}
679
680// set language
681#[cfg(feature = "nightly")]
682impl FnOnce<(&'static Language,)> for I18n {
683    type Output = ();
684    #[inline]
685    extern "rust-call" fn call_once(
686        self,
687        (lang,): (&'static Language,),
688    ) -> Self::Output {
689        self.language.set(&lang)
690    }
691}
692
693#[cfg(feature = "nightly")]
694impl FnMut<(&'static Language,)> for I18n {
695    #[inline]
696    extern "rust-call" fn call_mut(
697        &mut self,
698        (lang,): (&'static Language,),
699    ) -> Self::Output {
700        self.language.set(&lang)
701    }
702}
703
704#[cfg(feature = "nightly")]
705impl Fn<(&'static Language,)> for I18n {
706    #[inline]
707    extern "rust-call" fn call(
708        &self,
709        (lang,): (&'static Language,),
710    ) -> Self::Output {
711        self.language.set(&lang)
712    }
713}
714
715/// Get the current context for localization.
716#[deprecated(
717    since = "0.2.13",
718    note = "will be removed in v0.3.0. Use `use_context::<leptos_fluent::I18n>()` instead of `use_i18n()`."
719)]
720#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace"))]
721#[inline(always)]
722pub fn use_i18n() -> Option<I18n> {
723    use_context::<I18n>()
724}
725
726const EXPECT_I18N_ERROR_MESSAGE: &str = concat!(
727    "I18n context is missing, use the `leptos_fluent!` macro to provide it.\n\n",
728    "If you're sure that the context has been provided probably the invocation",
729    " resides outside of the reactive ownership tree, thus the context is not",
730    " reachable. Use instead:\n",
731    "  - `tr!(i18n, \"text-id\")` instead of `tr!(\"text-id\")`.\n",
732    "  - `move_tr!(i18n, \"text-id\")` instead of `move_tr!(\"text-id\")`.\n",
733    "  - `i18n.language.set(lang)` instead of `lang.activate()`.\n",
734    "  - `lang == i18n.language.get()` instead of `lang.is_active()`.\n",
735    "  - Copy `i18n` context instead of getting it with `expect_context::<leptos_fluent::I18n>()`.",
736);
737
738/// Expect the current context for localization.
739#[deprecated(
740    since = "0.2.13",
741    note = "will be removed in v0.3.0. Use `expect_context::<leptos_fluent::I18n>()` instead of `expect_i18n()`."
742)]
743#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace"))]
744#[inline]
745pub fn expect_i18n() -> I18n {
746    if let Some(i18n) = use_context::<I18n>() {
747        i18n
748    } else {
749        #[cfg(feature = "tracing")]
750        tracing::error!(EXPECT_I18N_ERROR_MESSAGE);
751        panic!("{}", EXPECT_I18N_ERROR_MESSAGE)
752    }
753}
754
755/// Expect the current context for localization.
756#[deprecated(
757    since = "0.2.13",
758    note = "will be removed in v0.3.0. Use `expect_context::<leptos_fluent::I18n>()` instead of `i18n()`."
759)]
760#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace"))]
761#[inline(always)]
762pub fn i18n() -> I18n {
763    #[allow(deprecated)]
764    expect_i18n()
765}
766
767/// Get the translation of a text identifier to the current language.
768#[deprecated(
769    since = "0.2.7",
770    note = "will be removed in v0.3.0. Use `i18n.tr(text_id)` instead of `tr_impl(i18n, text_id)`."
771)]
772#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
773#[inline(always)]
774pub fn tr_impl(i18n: I18n, text_id: &str) -> String {
775    i18n.tr(text_id)
776}
777
778/// Get the translation of a text identifier to the current language with arguments.
779#[deprecated(
780    since = "0.2.7",
781    note = "will be removed in v0.3.0. Use `i18n.tr_with_args(text_id, args)` instead of `tr_with_args_impl(i18n, text_id, args)`."
782)]
783#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
784#[inline(always)]
785pub fn tr_with_args_impl(
786    i18n: I18n,
787    text_id: &str,
788    args: &std::collections::HashMap<Cow<'static, str>, FluentValue>,
789) -> String {
790    i18n.tr_with_args(text_id, args)
791}
792
793/// Translate a text identifier to the current language.
794///
795/// ```rust,ignore
796/// use leptos_fluent::tr;
797///
798/// tr!("hello-world")
799/// tr!("hello-world", { "name" => "John" });
800///
801/// let name = "John";
802/// tr!("hello-world", { "name" => name, "age" => 30 });
803/// ```
804///
805/// Needs to be placed inside a reactive context to update the translation
806/// on the fly when the language changes.
807///
808/// ```rust,ignore
809/// use leptos_fluent::tr;
810///
811/// let text_signal = move || tr!("hello-world");
812/// ```
813///
814/// When using `check_translations` option in the `leptos_fluent!` macro,
815/// the usage of `tr!` will raise a warning if the translation data does not
816/// match against the translations files. To avoid the warning for a specific
817/// identifier or to use dynamic variables for translation data, use `i18n.tr`
818/// or `i18n.tr_with_args` methods directly.
819#[macro_export]
820macro_rules! tr {
821    ($text_id:literal$(,)?) => {::leptos::prelude::expect_context::<$crate::I18n>().tr($text_id)};
822    (
823        $text_id:literal,
824        $( #[$args_cfgs:meta] )*
825        {$($key:literal => $value:expr),*$(,)?}
826        $(,)?
827    ) => {{
828        ::leptos::prelude::expect_context::<$crate::I18n>().tr_with_args(
829            $text_id,
830            $( #[$args_cfgs] )*
831            &{
832                let mut map = ::std::collections::HashMap::new();
833                $(
834                    map.insert($key.into(), $value.into());
835                )*
836                map
837            }
838        )
839    }};
840    ($i18n:ident, $text_id:literal$(,)?) => {$i18n.tr($text_id)};
841    (
842        $i18n:ident,
843        $text_id:literal,
844        $( #[$args_cfgs:meta] )*
845        {$($key:literal => $value:expr),*$(,)?}
846        $(,)?
847    ) => {{
848        $i18n.tr_with_args(
849            $text_id,
850            $( #[$args_cfgs] )*
851            &{
852                let mut map = ::std::collections::HashMap::new();
853                $(
854                    map.insert($key.into(), $value.into());
855                )*
856                map
857            }
858        )
859    }};
860
861    //
862    // Next dynamic syntax is accepted by the translations checker.
863    //
864    // The cases are numbered to track them easily as it can grow in the future.
865    //
866
867    // 001 if_elseif_else
868    //   only text_id
869    (
870        $( #[$if_cfgs:meta] )*
871        if $condition:tt {
872            $text_id:literal
873        } $(else if $else_if_condition:tt {
874            $else_if_text_id:literal
875        })* else { $else_text_id:literal }
876        $(,)?
877    ) => {
878        $(#[$if_cfgs])*
879        if $condition {
880            $crate::tr!($text_id)
881        } $(else if $else_if_condition {
882            $crate::tr!($else_if_text_id)
883        })* else {
884            $crate::tr!($else_text_id)
885        }
886    };
887    //   with i18n
888    (
889        $i18n:ident,
890        $( #[$if_cfgs:meta] )*
891        if $condition:tt {
892            $text_id:literal
893        } $(else if $else_if_condition:tt {
894            $else_if_text_id:literal
895        })* else { $else_text_id:literal }
896        $(,)?
897    ) => {
898        $( #[$if_cfgs] )*
899        if $condition {
900            $i18n.tr($text_id)
901        } $(else if $else_if_condition {
902            $i18n.tr($else_if_text_id)
903        })* else {
904            $i18n.tr($else_text_id)
905        }
906    };
907    //   with args
908    (
909        $( #[$if_cfgs:meta] )*
910        if $condition:tt {
911            $text_id:literal
912        } $(else if $else_if_condition:tt {
913            $else_if_text_id:literal
914        })* else { $else_text_id:literal },
915        $( #[$args_cfgs:meta] )*
916        {$($key:literal => $value:expr),*$(,)?}
917        $(,)?
918    ) => {
919
920        {
921            $( #[$args_cfgs] )*
922            let map = {
923                let mut map = ::std::collections::HashMap::new();
924                $(
925                    map.insert($key.into(), $value.into());
926                )*
927                map
928            };
929            $( #[$if_cfgs] )*
930            if $condition {
931                ::leptos::prelude::expect_context::<$crate::I18n>().tr_with_args($text_id, &map)
932            } $(else if $else_if_condition {
933                ::leptos::prelude::expect_context::<$crate::I18n>().tr_with_args($else_if_text_id, &map)
934            })* else {
935                ::leptos::prelude::expect_context::<$crate::I18n>().tr_with_args($else_text_id, &map)
936            }
937        }
938    };
939    //   with i18n and args
940    (
941        $i18n:ident,
942        $( #[$if_cfgs:meta] )*
943        if $condition:tt {
944            $text_id:literal
945        } $(else if $else_if_condition:tt {
946            $else_if_text_id:literal
947        })* else { $else_text_id:literal },
948        $( #[$args_cfgs:meta] )*
949        {$($key:literal => $value:expr),*$(,)?}
950        $(,)?
951    ) => {
952        {
953            $( #[$args_cfgs] )*
954            let map = {
955                let mut map = ::std::collections::HashMap::new();
956                $(
957                    map.insert($key.into(), $value.into());
958                )*
959                map
960            };
961            $( #[$if_cfgs] )*
962            if $condition {
963                $i18n.tr_with_args($text_id, &map)
964            } $(else if $else_if_condition {
965                $i18n.tr_with_args($else_if_text_id, &map)
966            })* else {
967                $i18n.tr_with_args($else_text_id, &map)
968            }
969        }
970    };
971
972    //   no else
973    (
974        $( #[$if_cfgs:meta] )*
975        if $condition:tt {
976            $text_id:literal
977        } $(else if $else_if_condition:tt {
978            $else_if_text_id:literal
979        })*
980        $(,)?
981    ) => {
982        compile_error!("Expected `else` branch")
983    };
984    //   no else with i18n
985    (
986        $i18n:ident,
987        $( #[$if_cfgs:meta] )*
988        if $condition:tt {
989            $text_id:literal
990        } $(else if $else_if_condition:tt {
991            $else_if_text_id:literal
992        })*
993        $(,)?
994    ) => {
995        compile_error!("Expected `else` branch")
996    };
997    //   no else with args
998    (
999        $( #[$if_cfgs:meta] )*
1000        if $condition:tt {
1001            $text_id:literal
1002        } $(else if $else_if_condition:tt {
1003            $else_if_text_id:literal
1004        })*,
1005        $( #[$args_cfgs:meta] )*
1006        {$($key:literal => $value:expr),*$(,)?}
1007        $(,)?
1008    ) => {
1009        compile_error!("Expected `else` branch")
1010    };
1011    //   no else with i18n and args
1012    (
1013        $i18n:ident,
1014        $( #[$if_cfgs:meta] )*
1015        if $condition:tt {
1016            $text_id:literal
1017        } $(else if $else_if_condition:tt {
1018            $else_if_text_id:literal
1019        })*,
1020        $( #[$args_cfgs:meta] )*
1021        {$($key:literal => $value:expr),*$(,)?}
1022        $(,)?
1023    ) => {
1024        compile_error!("Expected `else` branch")
1025    };
1026
1027    // It seems that the next branches are excluded by the compiler and instead is triggering
1028    // a compile error with the message 'argument must be a string literal'.
1029    (
1030        $( #[$id_cfgs:meta] )*
1031        $text_id:expr$(,)?
1032    ) => {
1033        compile_error!(format!("Expected a string literal, got an expression '{}'", stringify!($text_id)))
1034    };
1035    (
1036        $( #[$id_cfgs:meta] )*
1037        $text_id:expr,
1038        $( #[$args_cfgs:meta] )*
1039        {$($key:literal => $value:expr),*$(,)?}$(,)?
1040    ) => {
1041        compile_error!(format!("Expected a string literal, got an expression '{}'", stringify!($text_id)))
1042    };
1043    (
1044        $i18n:expr,
1045        $( #[$id_cfgs:meta] )*
1046        $text_id:expr$(,)?
1047    ) => {
1048        compile_error!(format!("Expected a string literal, got an expression '{}'", stringify!($text_id)))
1049    };
1050    (
1051        $i18n:expr,
1052        $( #[$id_cfgs:meta] )*
1053        $text_id:expr,
1054        $( #[$args_cfgs:meta] )*
1055        {$($key:literal => $value:expr),*$(,)?}$(,)?
1056    ) => {
1057        compile_error!(format!("Expected a string literal, got an expression '{}'", stringify!($text_id)))
1058    };
1059}
1060
1061/// [Leptos's `Signal`] that translates a text identifier to the current language.
1062///
1063/// ```rust,ignore
1064/// move_tr!("hello-world")
1065/// move_tr!("hello-world", { "name" => "John" })
1066///
1067/// let name = "John";
1068/// move_tr!("hello-world", { "name" => name, "age" => 30 });
1069/// ```
1070///
1071/// The same as:
1072///
1073/// ```rust,ignore
1074/// Signal::derive(move || tr!("hello-world"));
1075/// Signal::derive(move || tr!("hello-world", { "name" => "John" }));
1076///
1077/// let name = "John";
1078/// Signal::derive(move || tr!("hello-world", { "name" => name, "age" => 30 }));
1079/// ```
1080///
1081/// When using `check_translations` option in the `leptos_fluent!` macro,
1082/// the usage of `move_tr!` will raise a warning if the translation data does not
1083/// match against the translations files. To avoid the warning for a specific
1084/// identifier or to use dynamic variables for translation data, use `i18n.tr`
1085/// or `i18n.tr_with_args` methods directly.
1086///
1087/// [Leptos's `Signal`]: https://docs.rs/reactive_graph/0.1.0/reactive_graph/wrappers/read/struct.Signal.html
1088#[macro_export]
1089macro_rules! move_tr {
1090    ($text_id:literal$(,)?) => {
1091        ::leptos::prelude::Signal::derive(move || $crate::tr!($text_id))
1092    };
1093    (
1094        $text_id:literal,
1095        $( #[$args_cfgs:meta] )*
1096        {$($key:literal => $value:expr),*$(,)?}
1097        $(,)?
1098    ) => {
1099        ::leptos::prelude::Signal::derive(move || $crate::tr!(
1100            $text_id,
1101            $( #[$args_cfgs] )*
1102            {
1103                $(
1104                    $key => $value,
1105                )*
1106            }
1107        ))
1108    };
1109    ($i18n:ident, $text_id:literal$(,)?) => {
1110        ::leptos::prelude::Signal::derive(move || $crate::tr!($i18n, $text_id))
1111    };
1112    (
1113        $i18n:ident,
1114        $text_id:literal,
1115        $( #[$args_cfgs:meta] )*
1116        {$($key:literal => $value:expr),*$(,)?}
1117        $(,)?
1118    ) => {
1119        ::leptos::prelude::Signal::derive(move || $crate::tr!(
1120            $i18n,
1121            $text_id,
1122            $( #[$args_cfgs] )*
1123            {
1124                $(
1125                    $key => $value,
1126                )*
1127            }
1128        ))
1129    };
1130
1131    // 001 if_elseif_else
1132    //   only text id
1133    (
1134        $( #[$if_cfgs:meta] )*
1135        if $condition:tt {
1136            $text_id:literal
1137        } $(else if $else_if_condition:tt {
1138            $else_if_text_id:literal
1139        })* else { $else_text_id:literal }
1140        $(,)?
1141    ) => {
1142        ::leptos::prelude::Signal::derive(move || {
1143            $( #[$if_cfgs] )*
1144            if $condition {
1145                $crate::tr!($text_id)
1146            } $(else if $else_if_condition {
1147                $crate::tr!($else_if_text_id)
1148            })* else {
1149                $crate::tr!($else_text_id)
1150            }
1151        })
1152    };
1153    //   with i18n
1154    (
1155        $i18n:ident,
1156        $( #[$if_cfgs:meta] )*
1157        if $condition:tt {
1158            $text_id:literal
1159        } $(else if $else_if_condition:tt {
1160            $else_if_text_id:literal
1161        })* else { $else_text_id:literal }
1162        $(,)?
1163    ) => {
1164        ::leptos::prelude::Signal::derive(move || {
1165            $( #[$if_cfgs] )*
1166            if $condition {
1167                $i18n.tr($text_id)
1168            } $(else if $else_if_condition {
1169                $i18n.tr($else_if_text_id)
1170            })* else {
1171                $i18n.tr($else_text_id)
1172            }
1173        })
1174    };
1175    //   with args
1176    (
1177        $( #[$if_cfgs:meta] )*
1178        if $condition:tt {
1179            $text_id:literal
1180        } $(else if $else_if_condition:tt {
1181            $else_if_text_id:literal
1182        })* else { $else_text_id:literal },
1183        $( #[$args_cfgs:meta] )*
1184        {$($key:literal => $value:expr),*$(,)?}
1185        $(,)?
1186    ) => {
1187        ::leptos::prelude::Signal::derive(move || {
1188            $( #[$args_cfgs] )*
1189            let map = {
1190                let mut map = ::std::collections::HashMap::new();
1191                $(
1192                    map.insert($key.into(), $value.into());
1193                )*
1194                map
1195            };
1196            $( #[$if_cfgs] )*
1197            if $condition {
1198                ::leptos::prelude::expect_context::<$crate::I18n>().tr_with_args($text_id, &map)
1199            } $(else if $else_if_condition {
1200                ::leptos::prelude::expect_context::<$crate::I18n>().tr_with_args($else_if_text_id, &map)
1201            })* else {
1202                ::leptos::prelude::expect_context::<$crate::I18n>().tr_with_args($else_text_id, &map)
1203            }
1204        })
1205    };
1206    //   with i18n and args
1207    (
1208        $i18n:ident,
1209        $( #[$if_cfgs:meta] )*
1210        if $condition:tt {
1211            $text_id:literal
1212        } $(else if $else_if_condition:tt {
1213            $else_if_text_id:literal
1214        })* else { $else_text_id:literal },
1215        $( #[$args_cfgs:meta] )*
1216        {$($key:literal => $value:expr),*$(,)?}
1217        $(,)?
1218    ) => {
1219        ::leptos::prelude::Signal::derive(move || {
1220            $( #[$args_cfgs] )*
1221            let map = {
1222                let mut map = ::std::collections::HashMap::new();
1223                $(
1224                    map.insert($key.into(), $value.into());
1225                )*
1226                map
1227            };
1228            $( #[$if_cfgs] )*
1229            if $condition {
1230                $i18n.tr_with_args($text_id, &map)
1231            } $(else if $else_if_condition {
1232                $i18n.tr_with_args($else_if_text_id, &map)
1233            })* else {
1234                $i18n.tr_with_args($else_text_id, &map)
1235            }
1236        })
1237    };
1238
1239    //   no else
1240    (
1241        $( #[$if_cfgs:meta] )*
1242        if $condition:tt {
1243            $text_id:literal
1244        } $(else if $else_if_condition:tt {
1245            $else_if_text_id:literal
1246        })*
1247        $(,)?
1248    ) => {
1249        compile_error!("Expected `else` branch")
1250    };
1251    //   no else with i18n
1252    (
1253        $i18n:ident,
1254        $( #[$if_cfgs:meta] )*
1255        if $condition:tt {
1256            $text_id:literal
1257        } $(else if $else_if_condition:tt {
1258            $else_if_text_id:literal
1259        })*
1260        $(,)?
1261    ) => {
1262        compile_error!("Expected `else` branch")
1263    };
1264    //   no else with args
1265    (
1266        $( #[$if_cfgs:meta] )*
1267        if $condition:tt {
1268            $text_id:literal
1269        } $(else if $else_if_condition:tt {
1270            $else_if_text_id:literal
1271        })*,
1272        $( #[$args_cfgs:meta] )*
1273        {$($key:literal => $value:expr),*$(,)?}
1274        $(,)?
1275    ) => {
1276        compile_error!("Expected `else` branch")
1277    };
1278    //   no else with i18n and args
1279    (
1280        $i18n:ident,
1281        $( #[$if_cfgs:meta] )*
1282        if $condition:tt {
1283            $text_id:literal
1284        } $(else if $else_if_condition:tt {
1285            $else_if_text_id:literal
1286        })*,
1287        $( #[$args_cfgs:meta] )*
1288        {$($key:literal => $value:expr),*$(,)?}
1289        $(,)?
1290    ) => {
1291        compile_error!("Expected `else` branch")
1292    };
1293
1294    ($text_id:expr$(,)?) => {
1295        compile_error!(format!("Expected a string literal, got an expression '{}'", stringify!($text_id)))
1296    };
1297    (
1298        $text_id:expr,
1299        $( #[$args_cfgs:meta] )*
1300        {$($key:literal => $value:expr),*$(,)?}
1301        $(,)?
1302    ) => {
1303        compile_error!(format!("Expected a string literal, got an expression '{}'", stringify!($text_id)))
1304    };
1305    ($i18n:expr, $text_id:expr$(,)?) => {
1306        compile_error!(format!("Expected a string literal, got an expression '{}'", stringify!($text_id)))
1307    };
1308    (
1309        $i18n:expr,
1310        $text_id:expr,
1311        $( #[$args_cfgs:meta] )*
1312        {$($key:literal => $value:expr),*$(,)?}
1313        $(,)?
1314    ) => {
1315        compile_error!(format!("Expected a string literal, got an expression '{}'", stringify!($text_id)))
1316    };
1317}
1318
1319#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
1320#[doc(hidden)]
1321pub fn language_from_str_between_languages(
1322    code: &str,
1323    languages: &'static [&Language],
1324) -> Option<&'static Language> {
1325    #[cfg(feature = "tracing")]
1326    tracing::trace!(
1327        concat!(
1328            "Searching for language with code \"{}\".\n",
1329            " Available languages: {}",
1330        ),
1331        code,
1332        languages
1333            .iter()
1334            .map(|lang| format!("\"{}\"", lang.id))
1335            .collect::<Vec<_>>()
1336            .join(", ")
1337    );
1338
1339    match LanguageIdentifier::from_str(code) {
1340        Ok(target_lang) => match languages
1341            .iter()
1342            .find(|lang| lang.id.matches(&target_lang, false, false))
1343        {
1344            Some(lang) => {
1345                #[cfg(feature = "tracing")]
1346                tracing::trace!(
1347                    "Language with code \"{}\" found with exact search: \"{}\"",
1348                    code,
1349                    lang.id
1350                );
1351
1352                Some(lang)
1353            }
1354            None => {
1355                let lazy_target_lang =
1356                    LanguageIdentifier::from_raw_parts_unchecked(
1357                        target_lang.language,
1358                        None,
1359                        None,
1360                        None,
1361                    );
1362                match languages
1363                    .iter()
1364                    .find(|lang| lang.id.matches(&lazy_target_lang, true, true))
1365                {
1366                    Some(lang) => {
1367                        #[cfg(feature = "tracing")]
1368                        tracing::trace!(
1369                            "Language with code \"{}\" found with fuzzy search: \"{}\"",
1370                            code,
1371                            lang.id
1372                        );
1373
1374                        Some(lang)
1375                    }
1376                    None => {
1377                        #[cfg(feature = "tracing")]
1378                        tracing::trace!(
1379                            "Language with code \"{}\" not found",
1380                            code
1381                        );
1382
1383                        None
1384                    }
1385                }
1386            }
1387        },
1388        Err(_) => None,
1389    }
1390}
1391
1392// Used by `leptos_fluent!` macro
1393#[doc(hidden)]
1394#[inline(always)]
1395pub fn l(
1396    code: &str,
1397    languages: &'static [&Language],
1398) -> Option<&'static Language> {
1399    language_from_str_between_languages(code, languages)
1400}
1401
1402/// Parameters passed to `leptos_fluent!` macro at creation of `i18n` context
1403#[derive(Clone, Debug)]
1404#[doc(hidden)]
1405pub struct LeptosFluentMeta {
1406    pub locales: &'static str,
1407    pub core_locales: Option<&'static str>,
1408    pub languages: Option<&'static str>,
1409    pub default_language: Option<&'static str>,
1410    pub translations: bool,       // *
1411    pub check_translations: bool, // * (maybe bool or str) TODO: improve
1412    pub fill_translations: Option<&'static str>,
1413    pub provide_meta_context: bool,
1414    pub sync_html_tag_lang: bool,
1415    pub sync_html_tag_dir: bool,
1416    pub url_param: &'static str,
1417    pub initial_language_from_url_param: bool,
1418    pub initial_language_from_url_param_to_local_storage: bool,
1419    pub initial_language_from_url_param_to_session_storage: bool,
1420    pub initial_language_from_url_param_to_cookie: bool,
1421    pub initial_language_from_url_param_to_server_function: bool, // *
1422    pub set_language_to_url_param: bool,
1423    pub local_storage_key: &'static str,
1424    pub initial_language_from_local_storage: bool,
1425    pub initial_language_from_local_storage_to_cookie: bool,
1426    pub initial_language_from_local_storage_to_session_storage: bool,
1427    pub initial_language_from_local_storage_to_server_function: bool, // *
1428    pub set_language_to_local_storage: bool,
1429    pub session_storage_key: &'static str,
1430    pub initial_language_from_session_storage: bool,
1431    pub initial_language_from_session_storage_to_cookie: bool,
1432    pub initial_language_from_session_storage_to_local_storage: bool,
1433    pub initial_language_from_session_storage_to_server_function: bool, // *
1434    pub set_language_to_session_storage: bool,
1435    pub initial_language_from_navigator: bool,
1436    pub initial_language_from_navigator_to_local_storage: bool,
1437    pub initial_language_from_navigator_to_session_storage: bool,
1438    pub initial_language_from_navigator_to_cookie: bool,
1439    pub initial_language_from_navigator_to_server_function: bool, // *
1440    pub set_language_from_navigator: bool,
1441    pub initial_language_from_accept_language_header: bool,
1442    pub cookie_name: &'static str,
1443    pub cookie_attrs: &'static str,
1444    pub initial_language_from_cookie: bool,
1445    pub initial_language_from_cookie_to_local_storage: bool,
1446    pub initial_language_from_cookie_to_session_storage: bool,
1447    pub initial_language_from_cookie_to_server_function: bool, // *
1448    pub set_language_to_cookie: bool,
1449    pub initial_language_from_server_function: bool, // *
1450    pub initial_language_from_server_function_to_cookie: bool,
1451    pub initial_language_from_server_function_to_local_storage: bool,
1452    pub set_language_to_server_function: bool, // *
1453    pub url_path: bool,                        // *
1454    pub initial_language_from_url_path: bool,
1455    pub initial_language_from_url_path_to_cookie: bool,
1456    pub initial_language_from_url_path_to_local_storage: bool,
1457    pub initial_language_from_url_path_to_session_storage: bool,
1458    pub initial_language_from_url_path_to_server_function: bool, // *
1459    #[cfg(feature = "system")]
1460    pub initial_language_from_system: bool,
1461    #[cfg(feature = "system")]
1462    pub initial_language_from_data_file: bool,
1463    #[cfg(feature = "system")]
1464    pub initial_language_from_system_to_data_file: bool,
1465    #[cfg(feature = "system")]
1466    pub set_language_to_data_file: bool,
1467    #[cfg(feature = "system")]
1468    pub data_file_key: &'static str,
1469    // * not really bools but not usable as functions
1470}