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#[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#[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#[derive(Debug)]
310pub enum WritingDirection {
311 Ltr,
313 Rtl,
315 Auto,
317}
318
319impl WritingDirection {
320 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#[derive(Clone, Debug)]
338pub struct Language {
339 pub id: &'static LanguageIdentifier,
343 pub name: &'static str,
348 pub dir: &'static WritingDirection,
350 pub flag: Option<&'static str>,
352}
353
354impl Language {
355 #[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 #[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
382impl<'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 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#[derive(Clone, Copy, Debug)]
505pub struct I18n {
506 pub language: RwSignal<&'static Language>,
508 pub languages: &'static [&'static Language],
510 pub translations: Signal<Vec<&'static LazyLock<StaticLoader>>>,
512}
513
514impl I18n {
515 #[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 #[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 #[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#[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#[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#[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#[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#[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#[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#[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#[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 (
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 (
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 (
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 (
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 (
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 (
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 (
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 (
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 (
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#[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 (
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 (
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 (
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 (
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 (
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 (
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 (
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 (
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#[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#[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, pub check_translations: bool, 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, 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, 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, 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, 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, pub set_language_to_cookie: bool,
1449 pub initial_language_from_server_function: bool, 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, pub url_path: bool, 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, #[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 }