diff --git a/README.md b/README.md index 6c81c743..68c95844 100644 --- a/README.md +++ b/README.md @@ -395,10 +395,10 @@ But floats (`f32` and `f64`) are not accepted in match statements, so they expan The plural above would generate code similar to this: ```rust -let plural_count = f32::from(count()); -if plural_count == 0.0 { +let count = f32::from(count()); +if count == 0.0 { // render "You are broke" -} else if (..0.0).contains(&plural_count) { +} else if (..0.0).contains(&count) { // render "You owe money" } else { // render "You have {{ count }}€" @@ -432,7 +432,7 @@ To supply the count for the plural in the `t!` macro, use `$`: ```rust let count = move || counter.get(); -t!(i18n, click_count, $ = count) +t!(i18n, click_count, count = count) ``` ### Subkeys diff --git a/docs/book/src/declare/01_key_value.md b/docs/book/src/declare/01_key_value.md index 8c744293..d7609dc7 100644 --- a/docs/book/src/declare/01_key_value.md +++ b/docs/book/src/declare/01_key_value.md @@ -41,7 +41,10 @@ If a key is present in another locale but not in the default locale, this key wi You can specify multiple kinds of values: - String +- Numbers +- Boolean - Interpolated String - Ranges +- Plurals The next chapters of this section will cover them, apart for strings, those are self explanatory. diff --git a/docs/book/src/declare/03_plurals.md b/docs/book/src/declare/03_plurals.md index bebc8eb3..4a3cc52e 100644 --- a/docs/book/src/declare/03_plurals.md +++ b/docs/book/src/declare/03_plurals.md @@ -28,9 +28,9 @@ Providing the count to the `t!` macro with the `$`, this will result in: ```rust let i18n = use_i18n(); -t!(i18n, items, $ = || 0) // -> "0 items" -t!(i18n, items, $ = || 1) // -> "1 item" -t!(i18n, items, $ = || 4) // -> "4 items" +t!(i18n, items, count = || 0) // -> "0 items" +t!(i18n, items, count = || 1) // -> "1 item" +t!(i18n, items, count = || 4) // -> "4 items" ``` `{{ count }}` is a special variable when using plurals, you don't supply it as `t!(i18n, key, count = ..)` but with the `$`. @@ -78,8 +78,20 @@ You can use them by using the `_ordinal` suffix: } ``` -> the `_ordinal` suffix is removed, in this example you access it with `t!(i18n, key, $ = ..)` +> the `_ordinal` suffix is removed, in this example you access it with `t!(i18n, key, count = ..)` ## How to know which to use: There are ressources online to help you find what you should use, my personnal favorite is the [unicode CLDR Charts](https://www.unicode.org/cldr/charts/44/supplemental/language_plural_rules.html). + +## What if I need multiple counts ? + +If you need multiple counts, for example: + +```json +{ + "key": "{{ boys_count }} boys and {{ girls_count }} girls" +} +``` + +There isn't a way to represent this in a single key, You will need `Foreign keys` that you can read about in a future chapter. diff --git a/docs/book/src/declare/04_ranges.md b/docs/book/src/declare/04_ranges.md index 0dec53d0..2f679070 100644 --- a/docs/book/src/declare/04_ranges.md +++ b/docs/book/src/declare/04_ranges.md @@ -151,13 +151,13 @@ With ranges, `{{ count }}` is a special variable that refers to the count provid ``` ```rust -t!(i18n, click_count, $ = || 0); +t!(i18n, click_count, count = || 0); ``` Will result in `"You have not clicked yet"` and ```rust -t!(i18n, click_count, $ = || 5); +t!(i18n, click_count, count = || 5); ``` Will result in `"You clicked 5 times"`. @@ -165,5 +165,17 @@ Will result in `"You clicked 5 times"`. Providing `count` will create an error: ```rust -t!(i18n, click_count, count = 12, $ = || 5); // compilation error +t!(i18n, click_count, count = 12, count = || 5); // compilation error ``` + +## What if I need multiple counts ? + +If you need multiple counts, for example: + +```json +{ + "key": "{{ boys_count }} boys and {{ girls_count }} girls" +} +``` + +There isn't a way to represent this in a single key, You will need `Foreign keys` that you can read about in a future chapter. diff --git a/docs/book/src/declare/06_foreign_keys.md b/docs/book/src/declare/06_foreign_keys.md index dd32ecdf..2d737e0e 100644 --- a/docs/book/src/declare/06_foreign_keys.md +++ b/docs/book/src/declare/06_foreign_keys.md @@ -1,21 +1,21 @@ # Foreign keys -Foreign keys let you re-use already declared translations, you declare them like variables but with a '@' before the path: +Foreign keys let you re-use already declared translations: ```json { "hello_world": "Hello World!", - "reuse": "message: {{ @hello_world }}" + "reuse": "message: $t(hello_world)" } ``` -This will replace `{{ @hello_world }}` by the value of the key `hello_world`, making `reuse` equal to `"message: Hello World!"`. +This will replace `$t(hello_world)` by the value of the key `hello_world`, making `reuse` equal to `"message: Hello World!"`. You can point to any key other than ranges and keys containing subkeys. -To point to subkeys you give the path by separating the the key by `.`: `{{ @key.subkey.subsubkey }}`. +To point to subkeys you give the path by separating the the key by `.`: `$t(key.subkey.subsubkey)`. -When using namespaces you _must_ specify the namespace of the key you are looking for, using `::`: `{{ @namespace::key }}`. +When using namespaces you _must_ specify the namespace of the key you are looking for, using `:`: `$t(namespace:key)`. You can point to explicitly defaulted keys, but not implicitly defaulted ones. @@ -26,7 +26,7 @@ You can also supply arguments to fill variables of the pointed key: ```json { "click_count": "You clicked {{ count }} times", - "clicked_twice": "{{ @click_count, count = 'two' }}" + "clicked_twice": "$t(click_count, {\"count\": \"two\"})" } ``` @@ -34,4 +34,98 @@ This will result to `clicked_twice` to have the value `"You clicked two times"`. Arguments must be string, delimited by either single quotes or double quotes. -**Note**: Any argument with no matching variable are just discarded, they will not emit any warning/error. +> **Note**: Any argument with no matching variable are just discarded, they will not emit any warning/error. + +Arguments can be anything that could be parsed as a normal key value: + +```json +{ + "key": "{{ arg }}", + "string_arg": "$t(key, {\"arg\": \"str\"})", + "boolean_arg": "$t(key, {\"arg\": true})", + "number_arg": "$t(key, {\"arg\": 56})", + "interpolated_arg": "$t(key, {\"arg\": \"value: {{ new_arg }}\"})", + "foreign_key_arg": "$t(key, {\"arg\": \"value: $t(interpolated_arg)\"})" +} +``` + +```rust +t!(i18n, string_arg); // -> "str" +t!(i18n, boolean_arg); // -> "true" +t!(i18n, number_arg); // -> "56" +t!(i18n, interpolated_arg, new_arg = "a value"); // -> "value: a value" +t!(i18n, foreign_key_arg, new_arg = "a value"); // -> "value: value: a value" +``` + +## `"count"` arg for plurals/ranges + +If you have a plural like + +```json +{ + "key_one": "one item", + "key_other": "{{ count }} items" +} +``` + +You can supply the count as a foreign key in 2 ways, as a variable: + +```json +{ + "new_key": "$t(key, {\"count\": \"{{ new_count }}\"})" +} +``` + +This will just rename the key: + +```rust +t!(i18n, new_key, new_count = move || 1); // -> "one item" +t!(i18n, new_key, new_count = move || 2); // -> "2 items" +``` + +> **note**: for the `count` arg to plurals/ranges, the value provided must be single variable (whitespaces around are supported though). + +Or by an actual value: + +```json +{ + "singular_key": "$t(key, {\"count\": 1})", + "multiple_key": "$t(key, {\"count\": 6})" +} +``` + +```rust +t!(i18n, singular_key); // -> "one item" +t!(i18n, multiple_key); // -> "6 items" +``` + +> **note**: while floats are supported, they don't carry all the informations once deserialized such as leading 0, so some truncation may occur. + +## Multiple counts ranges or plurals + +If you need multiple counts for a plural or a range, for example: + +```json +{ + "key": "{{ boys_count }} boys and {{ girls_count }} girls" +} +``` + +You can use `Foreign keys` to construct a single key from multiple plurals/ranges by overriding there `"count"` variable: + +```json +{ + "key": "$t(key_boys, {\"count\": \"{{ boys_count }}\"}) and $t(key_girls, {\"count\": \"{{ girls_count }}\"})", + "key_boys_one": "{{ count }} boy", + "key_boys_other": "{{ count }} boys", + "key_girls_one": "{{ count }} girl", + "key_girls_other": "{{ count }} girls" +} +``` + +```rust +t!(i18n, key, boys_count = move || 0, girls_count = move || 0); // -> "0 boys and 0 girls" +t!(i18n, key, boys_count = move || 0, girls_count = move || 1); // -> "0 boys and 1 girl" +t!(i18n, key, boys_count = move || 1, girls_count = move || 0); // -> "1 boy and 0 girls" +t!(i18n, key, boys_count = move || 56, girls_count = move || 39); // -> "56 boys and 39 girls" +``` diff --git a/docs/book/src/usage/04_t_macro.md b/docs/book/src/usage/04_t_macro.md index 10976ab4..78544e66 100644 --- a/docs/book/src/usage/04_t_macro.md +++ b/docs/book/src/usage/04_t_macro.md @@ -156,7 +156,7 @@ Basically `` expand to `move |children| view! { {children}< Ranges expect a variable `$` that implement `Fn() -> N + Clone + 'static` where `N` is the specified type of the range (default is `i32`). ```rust -t!(i18n, key_to_range, $ = count); +t!(i18n, key_to_range, count = count); ``` ## Access subkeys diff --git a/examples/csr/counter_ranges/src/app.rs b/examples/csr/counter_ranges/src/app.rs index 059fcbd4..9d79f5b6 100644 --- a/examples/csr/counter_ranges/src/app.rs +++ b/examples/csr/counter_ranges/src/app.rs @@ -32,7 +32,7 @@ fn Counter() -> impl IntoView { let count = move || counter.get(); view! { -

{t!(i18n, click_count, $ = count)}

+

{t!(i18n, click_count, count = count)}

} } diff --git a/examples/csr/yaml/src/app.rs b/examples/csr/yaml/src/app.rs index b94c6dd3..583b1aba 100644 --- a/examples/csr/yaml/src/app.rs +++ b/examples/csr/yaml/src/app.rs @@ -32,7 +32,7 @@ fn Counter() -> impl IntoView { let count = move || counter.get(); view! { -

{t!(i18n, click_count, $ = count)}

+

{t!(i18n, click_count, count = count)}

} } diff --git a/leptos_i18n/src/macro_helpers/mod.rs b/leptos_i18n/src/macro_helpers/mod.rs index 50d0215d..2e96b70c 100644 --- a/leptos_i18n/src/macro_helpers/mod.rs +++ b/leptos_i18n/src/macro_helpers/mod.rs @@ -26,7 +26,7 @@ impl DisplayBuilder { /// /// It has no uses outside of the internals of the `t!` macro. #[doc(hidden)] -pub trait BuildStr: Sized { +pub trait BuildLit: Sized { #[inline] fn builder(self) -> Self { self @@ -50,9 +50,35 @@ pub trait BuildStr: Sized { } } -impl BuildStr for &'static str { +impl BuildLit for &'static str { #[inline] fn display_builder(self) -> DisplayBuilder { DisplayBuilder(Cow::Borrowed(self)) } } + +impl BuildLit for bool { + #[inline] + fn display_builder(self) -> DisplayBuilder { + match self { + true => DisplayBuilder(Cow::Borrowed("true")), + false => DisplayBuilder(Cow::Borrowed("false")), + } + } +} + +macro_rules! impl_build_lit_nums { + ($t:ty) => { + impl BuildLit for $t { + fn display_builder(self) -> DisplayBuilder { + DisplayBuilder(Cow::Owned(ToString::to_string(&self))) + } + } + }; + ($t:ty, $($tt:tt)*) => { + impl_build_lit_nums!($t); + impl_build_lit_nums!($($tt)*); + } +} + +impl_build_lit_nums!(u64, i64, f64); diff --git a/leptos_i18n_macro/Cargo.toml b/leptos_i18n_macro/Cargo.toml index 06ff26f4..a4886452 100644 --- a/leptos_i18n_macro/Cargo.toml +++ b/leptos_i18n_macro/Cargo.toml @@ -15,12 +15,14 @@ proc-macro = true [dependencies] serde = { version = "1", features = ["rc"] } -serde_json = { version = "1", optional = true } +serde_json = { version = "1" } serde_yaml = { version = "0.9", optional = true } proc-macro2 = "1" quote = "1" syn = "2.0" toml = "0.8" +icu = "1.5" +fixed_decimal = { version = "0.5", features = ["ryu"] } [features] default = ["json_files"] @@ -28,7 +30,7 @@ serde = [] debug_interpolations = [] nightly = [] suppress_key_warnings = [] -json_files = ["serde_json"] +json_files = [] yaml_files = ["serde_yaml"] interpolate_display = [] track_locale_files = [] diff --git a/leptos_i18n_macro/src/load_locales/declare_locales.rs b/leptos_i18n_macro/src/load_locales/declare_locales.rs index a0aabfbc..535a495f 100644 --- a/leptos_i18n_macro/src/load_locales/declare_locales.rs +++ b/leptos_i18n_macro/src/load_locales/declare_locales.rs @@ -1,7 +1,7 @@ use std::{collections::HashMap, fmt::Display, rc::Rc}; -use crate::load_locales::ranges::{RangeParseBuffer, Ranges}; -use crate::utils::key::{Key, KeyPath}; +use crate::load_locales::ranges::{RangeParseBuffer, Ranges, UntypedRangesInner}; +use crate::utils::key::{Key, KeyPath, CACHED_VAR_COUNT_KEY}; use super::{ cfg_file::ConfigFile, @@ -203,7 +203,10 @@ fn parse_ranges( let mut ranges = match parse_range_type(&content, &mut seed)? { TypeOrRange::Type(range_type) => Ranges::from_type(range_type), - TypeOrRange::Range(range) => Ranges::I32(vec![range]), + TypeOrRange::Range(range) => Ranges { + inner: UntypedRangesInner::I32(vec![range]), + count_key: CACHED_VAR_COUNT_KEY.with(Clone::clone), + }, }; ranges.deserialize_inner(RangeParseBuffer(content), seed)?; diff --git a/leptos_i18n_macro/src/load_locales/error.rs b/leptos_i18n_macro/src/load_locales/error.rs index 701052c8..7ac2e364 100644 --- a/leptos_i18n_macro/src/load_locales/error.rs +++ b/leptos_i18n_macro/src/load_locales/error.rs @@ -1,4 +1,4 @@ -use std::{collections::HashSet, fmt::Display, path::PathBuf, rc::Rc}; +use std::{collections::HashSet, fmt::Display, num::TryFromIntError, path::PathBuf, rc::Rc}; use super::{locale::SerdeError, ranges::RangeType}; use crate::utils::key::{Key, KeyPath}; @@ -76,6 +76,29 @@ pub enum Error { locale: Rc, key_path: KeyPath, }, + InvalidForeignKeyArgs { + locale: Rc, + key_path: KeyPath, + err: serde_json::Error, + }, + InvalidCountArg { + locale: Rc, + key_path: KeyPath, + foreign_key: KeyPath, + }, + InvalidCountArgType { + locale: Rc, + key_path: KeyPath, + foreign_key: KeyPath, + input_type: RangeType, + range_type: RangeType, + }, + CountArgOutsideRange { + locale: Rc, + key_path: KeyPath, + foreign_key: KeyPath, + err: TryFromIntError, + }, } impl Display for Error { @@ -145,7 +168,7 @@ impl Display for Error { Error::RangeTypeMissmatch { key_path, type1, type2 } => write!(f, "Conflicting range value type at key \"{}\", found type {} but also type {}.", key_path, type1, type2), Error::InvalidKey(key) => write!(f, "invalid key {:?}, it can't be used as a rust identifier, try removing whitespaces and special characters.", key), Error::EmptyRange => write!(f, "empty ranges are not allowed"), - Error::InvalidRangeType(t) => write!(f, "invalid prange type {:?}", t), + Error::InvalidRangeType(t) => write!(f, "invalid range type {:?}", t), Error::NestedRanges => write!(f, "nested ranges are not allowed"), Error::InvalidFallback => write!(f, "fallbacks are only allowed in last position"), Error::MultipleFallbacks => write!(f, "only one fallback is allowed"), @@ -159,9 +182,13 @@ impl Display for Error { Error::RecursiveForeignKey { locale, key_path } => write!(f, "Borrow Error while linking foreign key at key \"{}\" in locale {:?}, check for recursive foreign key.", key_path, locale), Error::MissingForeignKey { foreign_key, locale, key_path } => write!(f, "Invalid foreign key \"{}\" at key \"{}\" in locale {:?}, key don't exist.", foreign_key, key_path, locale), Error::Custom(s) => f.write_str(s), - Error::InvalidForeignKey { foreign_key, locale, key_path } => write!(f, "Invalid foreign key \"{}\" at key \"{}\" in locale {:?}, foreign key to ranges, plurals or subkeys are not allowed.", foreign_key, key_path, locale), + Error::InvalidForeignKey { foreign_key, locale, key_path } => write!(f, "Invalid foreign key \"{}\" at key \"{}\" in locale {:?}, foreign key to subkeys are not allowed.", foreign_key, key_path, locale), Error::UnknownFormatter { name, locale, key_path } => write!(f, "Unknown formatter {:?} at key \"{}\" in locale {:?}.", name, key_path, locale), - Error::ConflictingPluralRuleType { locale, key_path } => write!(f, "Found both ordinal and cardinal plurals for key \"{}\" in locale {:?}", key_path, locale), + Error::ConflictingPluralRuleType { locale, key_path } => write!(f, "Found both ordinal and cardinal plurals for key \"{}\" in locale {:?}.", key_path, locale), + Error::InvalidForeignKeyArgs { locale, key_path, err } => write!(f, "Malformed foreign key args in locale {:?} at key \"{}\": {}.", locale, key_path, err), + Error::InvalidCountArg { locale, key_path, foreign_key } => write!(f, "Invalid arg \"count\" in locale {:?} at key \"{}\" to foreign key \"{}\": argument \"count\" for plurals or ranges can only be a literal number or a single variable.", locale, key_path, foreign_key), + Error::InvalidCountArgType { locale, key_path, foreign_key, input_type, range_type } => write!(f, "Invalid arg \"count\" in locale {:?} at key \"{}\" to foreign key \"{}\": argument \"count\" of type {} for range of type {} is not allowed.", locale, key_path, foreign_key, input_type, range_type), + Error::CountArgOutsideRange { locale, key_path, foreign_key, err } => write!(f, "Invalid arg \"count\" in locale {:?} at key \"{}\" to foreign key \"{}\": argument \"count\" is outside range: {}", locale, key_path, foreign_key, err), } } } diff --git a/leptos_i18n_macro/src/load_locales/interpolate.rs b/leptos_i18n_macro/src/load_locales/interpolate.rs index d1625006..b47f23b1 100644 --- a/leptos_i18n_macro/src/load_locales/interpolate.rs +++ b/leptos_i18n_macro/src/load_locales/interpolate.rs @@ -170,10 +170,6 @@ impl Interpolation { formatters, plural: infos.range_count, }; - let key = match infos.range_count { - Some(_) => Rc::new(Key::new("plural_count").unwrap()), - None => key, - }; let generic = format_ident!("__{}__", key.ident); Field { key, @@ -463,25 +459,11 @@ impl Interpolation { let locales_impls = Self::create_locale_string_impl(key, enum_ident, locales, default_match); - let range = fields.iter().any(|field| { - matches!( - field.var_or_comp, - VarOrComp::Var { - plural: Some(_), - .. - } - ) - }); - - let var_count = - range.then(|| quote!(let var_count = core::clone::Clone::clone(&plural_count);)); - quote! { #[allow(non_camel_case_types)] impl<#(#left_generics,)*> ::core::fmt::Display for #ident<#(#right_generics,)*> { fn fmt(&self, __formatter: &mut ::core::fmt::Formatter<'_>) -> core::fmt::Result { #destructure - #var_count match #locale_field { #( #locales_impls, @@ -526,25 +508,11 @@ impl Interpolation { let locales_impls = Self::create_locale_impl(key, enum_ident, locales, default_match); - let range = fields.iter().any(|field| { - matches!( - field.var_or_comp, - VarOrComp::Var { - plural: Some(_), - .. - } - ) - }); - - let var_count = - range.then(|| quote!(let var_count = core::clone::Clone::clone(&plural_count);)); - quote! { #[allow(non_camel_case_types)] impl<#(#left_generics,)*> leptos::IntoView for #ident<#(#right_generics,)*> { fn into_view(self) -> leptos::View { #destructure - #var_count match #locale_field { #( #locales_impls, diff --git a/leptos_i18n_macro/src/load_locales/locale.rs b/leptos_i18n_macro/src/load_locales/locale.rs index 507f7a0b..c9b7a229 100644 --- a/leptos_i18n_macro/src/load_locales/locale.rs +++ b/leptos_i18n_macro/src/load_locales/locale.rs @@ -5,15 +5,17 @@ use std::{ rc::Rc, }; +use quote::{quote, ToTokens}; + use super::{ cfg_file::ConfigFile, error::{Error, Result}, - parsed_value::{InterpolationKeys, ParsedValue, ParsedValueSeed}, + parsed_value::{InterpolOrLit, ParsedValue, ParsedValueSeed}, plurals::{PluralForm, PluralRuleType, Plurals}, tracking::track_file, warning::{emit_warning, Warning}, }; -use crate::utils::key::{Key, KeyPath}; +use crate::utils::key::{Key, KeyPath, CACHED_VAR_COUNT_KEY}; macro_rules! define_by_format { (json => $($tt:tt)*) => { @@ -199,6 +201,34 @@ impl LocalesOrNamespaces { Ok(LocalesOrNamespaces::Locales(locales)) } } + + pub fn merge_plurals_inner(locales: &mut [Locale], namespace: Option>) -> Result<()> { + let mut key_path = KeyPath::new(namespace); + + for locale in locales { + let top_locale = locale.name.clone(); + locale.merge_plurals(top_locale.clone(), &mut key_path)?; + } + + Ok(()) + } + + // this step would be more optimized to be done during `check_locales` but plurals merging need to be done before foreign key resolution, + // which also need to be done before `check_locales`. + pub fn merge_plurals(&mut self) -> Result<()> { + match self { + LocalesOrNamespaces::NameSpaces(namespaces) => { + for namespace in namespaces { + Self::merge_plurals_inner( + &mut namespace.locales, + Some(Rc::clone(&namespace.key)), + )?; + } + Ok(()) + } + LocalesOrNamespaces::Locales(locales) => Self::merge_plurals_inner(&mut *locales, None), + } + } } #[derive(Debug, Clone, PartialEq)] @@ -330,11 +360,14 @@ impl Locale { } }) .collect::>>()?; - let value = ParsedValue::Plurals(Plurals { + let plural = Plurals { rule_type, forms, + count_key: CACHED_VAR_COUNT_KEY.with(Clone::clone), other: Box::new(other), - }); + }; + plural.check_categories(&locale, key_path); + let value = ParsedValue::Plurals(plural); let key = key_path.pop_key().unwrap(); self.keys.insert(key, value); } @@ -384,15 +417,12 @@ impl Locale { let default_locale = locales.next().unwrap(); let mut key_path = KeyPath::new(namespace); - default_locale.merge_plurals(default_locale.name.clone(), &mut key_path)?; - let mut default_keys = default_locale.make_builder_keys(&mut key_path)?; let default_locale_name = &default_locale.name.name; for locale in locales { let top_locale = locale.name.clone(); - locale.merge_plurals(top_locale.clone(), &mut key_path)?; locale.merge( &mut default_keys, default_locale_name, @@ -425,9 +455,34 @@ impl Locale { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum LiteralType { + String, + Bool, + Signed, + Unsigned, + Float, +} + +impl ToTokens for LiteralType { + fn to_token_stream(&self) -> proc_macro2::TokenStream { + match self { + LiteralType::String => quote!(&'static str), + LiteralType::Bool => quote!(bool), + LiteralType::Signed => quote!(i64), + LiteralType::Unsigned => quote!(u64), + LiteralType::Float => quote!(f64), + } + } + + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + tokens.extend(self.to_token_stream()); + } +} + #[derive(Debug)] pub enum LocaleValue { - Value(Option), + Value(InterpolOrLit), Subkeys { locales: Vec, keys: BuildersKeysInner, diff --git a/leptos_i18n_macro/src/load_locales/mod.rs b/leptos_i18n_macro/src/load_locales/mod.rs index 362a6cd1..87c0c486 100644 --- a/leptos_i18n_macro/src/load_locales/mod.rs +++ b/leptos_i18n_macro/src/load_locales/mod.rs @@ -22,6 +22,7 @@ use cfg_file::ConfigFile; use error::{Error, Result}; use interpolate::Interpolation; use locale::{Locale, LocaleValue}; +use parsed_value::InterpolOrLit; use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote}; @@ -61,6 +62,8 @@ fn load_locales_inner( cfg_file: &ConfigFile, locales: &mut LocalesOrNamespaces, ) -> Result { + locales.merge_plurals()?; + ParsedValue::resolve_foreign_keys(locales, &cfg_file.default)?; let keys = Locale::check_locales(locales)?; @@ -371,15 +374,23 @@ fn create_locale_type_inner( ) -> TokenStream { let default_match = get_default_match(default_locale, top_locales, locales, enum_ident); - let string_keys = keys + let literal_keys = keys .iter() - .filter(|(_, value)| matches!(value, LocaleValue::Value(None))) - .map(|(key, _)| key) + .filter_map(|(key, value)| match value { + LocaleValue::Value(InterpolOrLit::Lit(t)) => Some((key.clone(), t)), + _ => None, + }) .collect::>(); - let string_fields = string_keys + let literal_fields = literal_keys .iter() - .map(|key| quote!(pub #key: &'static str)) + .map(|(key, literal_type)| { + if cfg!(feature = "show_keys_only") { + quote!(pub #key: &'static str) + } else { + quote!(pub #key: #literal_type) + } + }) .collect::>(); let subkeys = keys @@ -449,11 +460,11 @@ fn create_locale_type_inner( let builders = keys .iter() .filter_map(|(key, value)| match value { - LocaleValue::Value(None) | LocaleValue::Subkeys { .. } => None, - LocaleValue::Value(Some(keys)) => Some(( + LocaleValue::Value(InterpolOrLit::Interpol(keys)) => Some(( key, Interpolation::new(key, enum_ident, keys, locales, &default_match, key_path), )), + _ => None, }) .collect::>(); @@ -473,19 +484,19 @@ fn create_locale_type_inner( let default_locale = locales.first().unwrap(); let new_match_arms = locales.iter().enumerate().map(|(i, locale)| { - let filled_string_fields = string_keys.iter().filter_map(|&key| { + let filled_lit_fields = literal_keys.iter().filter_map(|(key, _)| { if cfg!(feature = "show_keys_only") { let key_str = key_path.to_string_with_key(key); return Some(quote!(#key: #key_str)); } match locale.keys.get(key) { - Some(ParsedValue::String(str_value)) => Some(quote!(#key: #str_value)), + Some(ParsedValue::Literal(lit)) => Some(quote!(#key: #lit)), _ => { - let str_value = default_locale + let lit = default_locale .keys .get(key) - .and_then(ParsedValue::is_string)?; - Some(quote!(#key: #str_value)) + .and_then(ParsedValue::is_literal)?; + Some(quote!(#key: #lit)) } } }); @@ -495,7 +506,7 @@ fn create_locale_type_inner( let pattern = pattern.as_ref().unwrap_or(&default_match); quote! { #pattern => #type_ident { - #(#filled_string_fields,)* + #(#filled_lit_fields,)* #(#init_builder_fields,)* #(#subkeys_field_new,)* } @@ -565,7 +576,7 @@ fn create_locale_type_inner( #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] #[allow(non_camel_case_types, non_snake_case)] pub struct #type_ident { - #(#string_fields,)* + #(#literal_fields,)* #(#builder_fields,)* #(#subkeys_fields,)* } diff --git a/leptos_i18n_macro/src/load_locales/parsed_value.rs b/leptos_i18n_macro/src/load_locales/parsed_value.rs index 82e70828..de10cecf 100644 --- a/leptos_i18n_macro/src/load_locales/parsed_value.rs +++ b/leptos_i18n_macro/src/load_locales/parsed_value.rs @@ -7,17 +7,21 @@ use std::{ use crate::utils::formatter::Formatter; use proc_macro2::TokenStream; use quote::{quote, ToTokens}; -use serde::de::{value::MapAccessDeserializer, DeserializeSeed}; +use serde::{ + de::{value::MapAccessDeserializer, DeserializeSeed, Visitor}, + Deserialize, +}; +use std::fmt::Display; use super::{ error::{Error, Result}, interpolate::CACHED_LOCALE_FIELD_KEY, - locale::{Locale, LocaleSeed, LocaleValue, LocalesOrNamespaces}, + locale::{LiteralType, Locale, LocaleSeed, LocaleValue, LocalesOrNamespaces}, plurals::Plurals, ranges::{RangeType, Ranges}, }; -use crate::utils::key::{Key, KeyPath, CACHED_PLURAL_COUNT_KEY}; +use crate::utils::key::{Key, KeyPath}; thread_local! { pub static FOREIGN_KEYS: RefCell, KeyPath)>> = RefCell::new(HashSet::new()); @@ -34,17 +38,91 @@ macro_rules! nested_result_try { #[derive(Debug, Clone, PartialEq)] pub enum ForeignKey { - NotSet(KeyPath, HashMap), + NotSet(KeyPath, HashMap), Set(Box), } +#[derive(Debug, Clone, PartialEq)] +pub enum Literal { + String(String), + Signed(i64), + Unsigned(u64), + Float(f64), + Bool(bool), +} + +impl Literal { + pub fn is_string(&self) -> Option<&str> { + match self { + Literal::String(s) => Some(s), + _ => None, + } + } + + pub fn join(&mut self, other: &Self) { + match self { + Literal::String(s) => s.push_str(&other.to_string()), + Literal::Signed(v) => { + let s = format!("{}{}", v, other); + *self = Literal::String(s); + } + Literal::Unsigned(v) => { + let s = format!("{}{}", v, other); + *self = Literal::String(s); + } + Literal::Float(v) => { + let s = format!("{}{}", v, other); + *self = Literal::String(s); + } + Literal::Bool(v) => { + let s = format!("{}{}", v, other); + *self = Literal::String(s); + } + } + } + + pub fn get_type(&self) -> LiteralType { + match self { + Literal::String(_) => LiteralType::String, + Literal::Signed(_) => LiteralType::Signed, + Literal::Unsigned(_) => LiteralType::Unsigned, + Literal::Float(_) => LiteralType::Float, + Literal::Bool(_) => LiteralType::Bool, + } + } +} + +impl ToTokens for Literal { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Literal::String(v) => ToTokens::to_tokens(v, tokens), + Literal::Signed(v) => ToTokens::to_tokens(v, tokens), + Literal::Unsigned(v) => ToTokens::to_tokens(v, tokens), + Literal::Float(v) => ToTokens::to_tokens(v, tokens), + Literal::Bool(v) => ToTokens::to_tokens(v, tokens), + } + } +} + +impl Display for Literal { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Literal::String(v) => Display::fmt(v, f), + Literal::Signed(v) => Display::fmt(v, f), + Literal::Unsigned(v) => Display::fmt(v, f), + Literal::Float(v) => Display::fmt(v, f), + Literal::Bool(v) => Display::fmt(v, f), + } + } +} + #[derive(Debug, Clone, Default, PartialEq)] pub enum ParsedValue { #[default] Default, ForeignKey(RefCell), Ranges(Ranges), - String(String), + Literal(Literal), Variable { key: Rc, formatter: Formatter, @@ -89,6 +167,31 @@ pub struct InterpolationKeys { variables: HashMap, VarInfo>, } +#[derive(Debug)] +pub enum InterpolOrLit { + Interpol(InterpolationKeys), + Lit(LiteralType), +} + +impl InterpolOrLit { + pub fn get_interpol_keys_mut(&mut self) -> &mut InterpolationKeys { + match self { + InterpolOrLit::Interpol(keys) => keys, + InterpolOrLit::Lit(_) => { + *self = InterpolOrLit::Interpol(InterpolationKeys::default()); + self.get_interpol_keys_mut() + } + } + } + + pub fn is_interpol(&self) -> Option<&InterpolationKeys> { + match self { + InterpolOrLit::Interpol(keys) => Some(keys), + InterpolOrLit::Lit(_) => None, + } + } +} + impl InterpolationKeys { pub fn push_var(&mut self, key: Rc, formatter: Formatter) { let var_infos = self.variables.entry(key).or_default(); @@ -99,9 +202,13 @@ impl InterpolationKeys { self.components.insert(key); } - pub fn push_count(&mut self, key_path: &mut KeyPath, ty: RangeOrPlural) -> Result<()> { - let key = CACHED_PLURAL_COUNT_KEY.with(Clone::clone); - let var_infos = self.variables.entry(key).or_default(); + pub fn push_count( + &mut self, + key_path: &mut KeyPath, + ty: RangeOrPlural, + count_key: Rc, + ) -> Result<()> { + let var_infos = self.variables.entry(count_key).or_default(); match (var_infos.range_count.replace(ty), ty) { (None, _) | (Some(RangeOrPlural::Plural), RangeOrPlural::Plural) => Ok(()), (Some(RangeOrPlural::Range(old)), RangeOrPlural::Range(new)) if old == new => Ok(()), @@ -169,36 +276,32 @@ impl ParsedValue { }); }; - match value { - ParsedValue::Default => { - // this check is normally done in a later step for optimisations (Locale::make_builder_keys), - // but we still need to do it here to avoid infinite loop - // this case happen if a foreign key point to an explicit default in the default locale - // pretty niche, but would cause a rustc stack overflow if not done. - if top_locale == default_locale { - return Err(Error::ExplicitDefaultInDefault(key_path.to_owned())); - } else { - return Self::resolve_foreign_key_inner( - foreign_key, - values, - default_locale, - default_locale, - key_path, - ); - } - } - ParsedValue::Ranges(_) => { - return Err(Error::Custom(format!( - "foreign key to ranges is not supported yet, at key {} in locale {:?}", - key_path, top_locale - ))) + if matches!(value, ParsedValue::Default) { + // this check is normally done in a later step for optimisations (Locale::make_builder_keys), + // but we still need to do it here to avoid infinite loop + // this case happen if a foreign key point to an explicit default in the default locale + // pretty niche, but would cause a rustc stack overflow if not done. + if top_locale == default_locale { + return Err(Error::ExplicitDefaultInDefault(key_path.to_owned())); + } else { + return Self::resolve_foreign_key_inner( + foreign_key, + values, + default_locale, + default_locale, + key_path, + ); } - _ => {} } // possibility that the foreign key must be resolved too value.resolve_foreign_key(values, top_locale, default_locale, foreign_key_path)?; + // possibility that args must resolve too + for arg in args.values() { + arg.resolve_foreign_key(values, top_locale, default_locale, foreign_key_path)?; + } + let value = value.populate(args, foreign_key_path, top_locale, key_path)?; let _ = std::mem::replace(foreign_key, ForeignKey::Set(Box::new(value))); @@ -214,7 +317,7 @@ impl ParsedValue { path: &KeyPath, ) -> Result<()> { match self { - ParsedValue::Variable { .. } | ParsedValue::String(_) | ParsedValue::Default => Ok(()), + ParsedValue::Variable { .. } | ParsedValue::Literal(_) | ParsedValue::Default => Ok(()), ParsedValue::Subkeys(_) => Ok(()), // unreachable ? ParsedValue::Ranges(inner) => { inner.resolve_foreign_keys(values, top_locale, default_locale, path) @@ -255,17 +358,17 @@ impl ParsedValue { pub fn populate( &self, - args: &HashMap, + args: &HashMap, foreign_key: &KeyPath, locale: &Rc, key_path: &KeyPath, ) -> Result { match self { - ParsedValue::Default | ParsedValue::ForeignKey(_) | ParsedValue::String(_) => { + ParsedValue::Default | ParsedValue::ForeignKey(_) | ParsedValue::Literal(_) => { Ok(self.clone()) } ParsedValue::Variable { key, formatter } => match args.get(&key.name) { - Some(value) => Ok(ParsedValue::String(value.to_owned())), + Some(value) => Ok(value.clone()), None => Ok(ParsedValue::Variable { key: Rc::clone(key), formatter: *formatter, @@ -280,86 +383,102 @@ impl ParsedValue { .map(|value| value.populate(args, foreign_key, locale, key_path)) .collect::>() .map(ParsedValue::Bloc), - ParsedValue::Subkeys(_) | ParsedValue::Ranges(_) | ParsedValue::Plurals(_) => { - Err(Error::InvalidForeignKey { - foreign_key: foreign_key.to_owned(), - locale: Rc::clone(locale), - key_path: key_path.to_owned(), - }) - } + ParsedValue::Ranges(ranges) => ranges.populate(args, foreign_key, locale, key_path), + ParsedValue::Plurals(plurals) => plurals.populate(args, foreign_key, locale, key_path), + ParsedValue::Subkeys(_) => Err(Error::InvalidForeignKey { + foreign_key: foreign_key.to_owned(), + locale: Rc::clone(locale), + key_path: key_path.to_owned(), + }), } } pub fn get_keys_inner( &self, key_path: &mut KeyPath, - keys: &mut Option, + keys: &mut InterpolOrLit, + is_top: bool, ) -> Result<()> { match self { - ParsedValue::String(_) | ParsedValue::Subkeys(_) | ParsedValue::Default => {} + ParsedValue::Literal(lit_type) if is_top => { + *keys = InterpolOrLit::Lit(lit_type.get_type()); + } + ParsedValue::Literal(_) | ParsedValue::Subkeys(_) | ParsedValue::Default => {} ParsedValue::Variable { key, formatter } => { - keys.get_or_insert_with(Default::default) + keys.get_interpol_keys_mut() .push_var(key.clone(), *formatter); } ParsedValue::Component { key, inner } => { - keys.get_or_insert_with(Default::default) - .push_comp(key.clone()); - inner.get_keys_inner(key_path, keys)?; + keys.get_interpol_keys_mut().push_comp(key.clone()); + inner.get_keys_inner(key_path, keys, false)?; } ParsedValue::Bloc(values) => { for value in values { - value.get_keys_inner(key_path, keys)?; + value.get_keys_inner(key_path, keys, false)?; } } ParsedValue::Ranges(ranges) => { ranges.get_keys_inner(key_path, keys)?; let range_type = ranges.get_type(); - keys.get_or_insert_with(Default::default) - .push_count(key_path, RangeOrPlural::Range(range_type))?; + keys.get_interpol_keys_mut().push_count( + key_path, + RangeOrPlural::Range(range_type), + ranges.count_key.clone(), + )?; } ParsedValue::ForeignKey(foreign_key) => { foreign_key .borrow() .as_inner("get_keys_inner") - .get_keys_inner(key_path, keys)?; + .get_keys_inner(key_path, keys, false)?; } - ParsedValue::Plurals(Plurals { forms, other, .. }) => { - keys.get_or_insert_with(Default::default) - .push_count(key_path, RangeOrPlural::Plural)?; + ParsedValue::Plurals(Plurals { + forms, + other, + count_key, + .. + }) => { + keys.get_interpol_keys_mut().push_count( + key_path, + RangeOrPlural::Plural, + count_key.clone(), + )?; for value in forms.values() { - value.get_keys_inner(key_path, keys)?; + value.get_keys_inner(key_path, keys, false)?; } - other.get_keys_inner(key_path, keys)?; + other.get_keys_inner(key_path, keys, false)?; } } Ok(()) } - pub fn get_keys(&self, key_path: &mut KeyPath) -> Result> { - let mut keys = None; - self.get_keys_inner(key_path, &mut keys)?; + pub fn get_keys(&self, key_path: &mut KeyPath) -> Result { + let mut keys = InterpolOrLit::Lit(LiteralType::String); + + self.get_keys_inner(key_path, &mut keys, true)?; Ok(keys) } - pub fn is_string(&self) -> Option<&str> { + pub fn is_literal(&self) -> Option<&Literal> { match self { - ParsedValue::String(value) => Some(value), + ParsedValue::Literal(lit) => Some(lit), _ => None, } } pub fn new(value: &str, key_path: &KeyPath, locale: &Rc) -> Result { - // look for component - if let Some(component) = Self::find_component(value, key_path, locale) { - return component; - } - // else look for variables - if let Some(variable) = Self::find_variable(value, key_path, locale) { - return variable; + let parsed_value = [ + Self::find_foreign_key, + Self::find_component, + Self::find_variable, + ] + .into_iter() + .find_map(|f| f(value, key_path, locale)); + if let Some(parsed_value) = parsed_value { + parsed_value + } else { + Ok(ParsedValue::Literal(Literal::String(value.to_string()))) } - - // else it's just a string - Ok(ParsedValue::String(value.to_string())) } pub fn make_locale_value(&mut self, key_path: &mut KeyPath) -> Result { @@ -387,7 +506,7 @@ impl ParsedValue { key_path: &mut KeyPath, ) -> Result<()> { self.reduce(); - match (&mut *self, keys) { + match (&mut *self, &mut *keys) { // Default, do nothing (ParsedValue::Default, _) => Ok(()), // Both subkeys @@ -399,17 +518,29 @@ impl ParsedValue { locales.push(loc); Ok(()) } - // Both value + (ParsedValue::Literal(lit), LocaleValue::Value(interpol_or_lit)) => { + let other_lit_type = match interpol_or_lit { + InterpolOrLit::Interpol(_) => return Ok(()), + InterpolOrLit::Lit(lit_type) => *lit_type, + }; + if lit.get_type() == other_lit_type { + Ok(()) + } else { + // make builder with 0 fields. + *interpol_or_lit = InterpolOrLit::Interpol(InterpolationKeys::default()); + Ok(()) + } + } ( ParsedValue::Bloc(_) | ParsedValue::Component { .. } | ParsedValue::Ranges(_) - | ParsedValue::String(_) | ParsedValue::Variable { .. } | ParsedValue::Plurals(_) | ParsedValue::ForeignKey(_), - LocaleValue::Value(keys), - ) => self.get_keys_inner(key_path, keys), + LocaleValue::Value(interpol_or_lit), + ) => self.get_keys_inner(key_path, interpol_or_lit, false), + // not compatible _ => Err(Error::SubKeyMissmatch { locale: top_locale, @@ -418,54 +549,6 @@ impl ParsedValue { } } - fn parse_key_path(path: &str) -> Option { - let (mut key_path, path) = if let Some((namespace, rest)) = path.split_once("::") { - let namespace = Key::new(namespace)?; - - (KeyPath::new(Some(Rc::new(namespace))), rest) - } else { - (KeyPath::new(None), path) - }; - - for key in path.split('.') { - let key = Key::new(key)?; - key_path.push_key(Rc::new(key)); - } - - Some(key_path) - } - - fn parse_foreign_key(ident: &str, locale: &Rc, key_path: &KeyPath) -> Option { - let ident = ident.strip_prefix('@')?; - let mut splitted = ident.split(','); - let path = splitted.next()?; - - let foreign_key_path = Self::parse_key_path(path)?; - FOREIGN_KEYS.with(|foreign_keys| { - foreign_keys - .borrow_mut() - .insert((Rc::clone(locale), key_path.clone())) - }); - - let mut args = HashMap::new(); - const QUOTES: &[char] = &['"', '\'']; - - for arg in splitted { - let (ident, value) = arg.split_once('=')?; - let mut key = String::from("var_"); - key.push_str(ident.trim()); - - let value = value.trim().strip_prefix(QUOTES)?; - let value = value.strip_suffix(QUOTES)?; - args.insert(key, value.to_owned()); - } - - Some(ParsedValue::ForeignKey(RefCell::new(ForeignKey::NotSet( - foreign_key_path, - args, - )))) - } - fn parse_formatter_args(s: &str) -> (&str, Option>) { let Some((name, rest)) = s.split_once('(') else { return (s.trim(), None); @@ -496,34 +579,132 @@ impl ParsedValue { } } + fn parse_key_path(path: &str) -> Option { + let (mut key_path, path) = if let Some((namespace, rest)) = path.split_once(':') { + let namespace = Key::new(namespace)?; + + (KeyPath::new(Some(Rc::new(namespace))), rest) + } else { + (KeyPath::new(None), path) + }; + + for key in path.split('.') { + let key = Key::new(key)?; + key_path.push_key(Rc::new(key)); + } + + Some(key_path) + } + + fn parse_foreign_key_args_inner( + s: &str, + key_path: &KeyPath, + locale: &Rc, + ) -> Result> { + let args = match serde_json::from_str::>(s) { + Ok(args) => args, + Err(err) => { + return Err(Error::InvalidForeignKeyArgs { + locale: Rc::clone(locale), + key_path: key_path.clone(), + err, + }) + } + }; + let mut parsed_args = HashMap::new(); + + for (key, arg) in args { + let parsed_value = match arg { + Literal::String(s) => Self::new(&s, key_path, locale)?, + other => ParsedValue::Literal(other), + }; + let key = format!("var_{}", key.trim()); + parsed_args.insert(key, parsed_value); + } + + Ok(parsed_args) + } + + fn parse_foreign_key_args<'a>( + s: &'a str, + key_path: &KeyPath, + locale: &Rc, + ) -> Result<(HashMap, &'a str)> { + let mut depth = 0usize; + let mut index = 0usize; + + for (i, c) in s.char_indices() { + match c { + '{' => depth += 1, + '}' => { + depth = match depth.checked_sub(1) { + Some(v) => v, + None => todo!(), + }; + if depth == 0 { + index = i; + break; + } + } + _ => {} + } + } + + let (before, after) = s.split_at(index + '}'.len_utf8()); + + let Some(after) = after.trim_start().strip_prefix(')') else { + todo!("parse_foreign_key_args_inner") + }; + + let args = Self::parse_foreign_key_args_inner(before, key_path, locale)?; + + Ok((args, after)) + } + + fn find_foreign_key(value: &str, key_path: &KeyPath, locale: &Rc) -> Option> { + let (before, rest) = value.split_once("$t(")?; + let next_split = rest.find([',', ')'])?; + let keypath = rest.get(..next_split)?; + let sep = rest[next_split..].chars().next()?; + let after = rest.get(next_split + sep.len_utf8()..)?; + let target_key_path = Self::parse_key_path(keypath)?; + + let (args, after) = if sep == ',' { + nested_result_try!(Self::parse_foreign_key_args(after, key_path, locale)) + } else { + (HashMap::new(), after) + }; + + let this = ParsedValue::ForeignKey(RefCell::new(ForeignKey::new( + key_path.clone(), + target_key_path, + args, + locale, + ))); + let before = nested_result_try!(Self::new(before, key_path, locale)); + let after = nested_result_try!(Self::new(after, key_path, locale)); + + Some(Ok(ParsedValue::Bloc(vec![before, this, after]))) + } + fn find_variable(value: &str, key_path: &KeyPath, locale: &Rc) -> Option> { let (before, rest) = value.split_once("{{")?; let (ident, after) = rest.split_once("}}")?; let ident = ident.trim(); - let first_char = ident.chars().next()?; - let before = nested_result_try!(Self::new(before, key_path, locale)); let after = nested_result_try!(Self::new(after, key_path, locale)); - let this = match first_char { - // foreign key - '@' => Self::parse_foreign_key(ident, locale, key_path)?, - // variable key - _ => { - if let Some((ident, formatter)) = ident.split_once(',') { - let formatter = - nested_result_try!(Self::parse_formatter(formatter, locale, key_path)); - let key = Rc::new(Key::new(&format!("var_{}", ident))?); - ParsedValue::Variable { key, formatter } - } else { - let key = Rc::new(Key::new(&format!("var_{}", ident))?); - ParsedValue::Variable { - key, - formatter: Formatter::None, - } - } + let this = if let Some((ident, formatter)) = ident.split_once(',') { + let formatter = nested_result_try!(Self::parse_formatter(formatter, locale, key_path)); + let key = Rc::new(Key::new(&format!("var_{}", ident.trim()))?); + ParsedValue::Variable { key, formatter } + } else { + let key = Rc::new(Key::new(&format!("var_{}", ident))?); + ParsedValue::Variable { + key, + formatter: Formatter::None, } }; @@ -603,7 +784,7 @@ impl ParsedValue { pub fn reduce(&mut self) { match self { - ParsedValue::Variable { .. } | ParsedValue::String(_) | ParsedValue::Default => {} + ParsedValue::Variable { .. } | ParsedValue::Literal(_) | ParsedValue::Default => {} ParsedValue::ForeignKey(foreign_key) => { let value = foreign_key.get_mut().as_inner_mut("reduce"); value.reduce(); @@ -633,7 +814,7 @@ impl ParsedValue { } match values.as_mut_slice() { - [] => *self = ParsedValue::String(String::new()), + [] => *self = ParsedValue::Literal(Literal::String(String::new())), [one] => *self = std::mem::take(one), _ => {} } @@ -649,23 +830,25 @@ impl ParsedValue { pub fn reduce_into(self, bloc: &mut Vec) { match self { ParsedValue::Default => {} // default in a bloc ? skip - ParsedValue::Ranges(_) => {} // same for ranges, can't be in a bloc ParsedValue::Subkeys(_) => {} // same for subkeys - ParsedValue::Plurals(_) => {} // same for plurals + mut plurals_like @ (ParsedValue::Ranges(_) | ParsedValue::Plurals(_)) => { + plurals_like.reduce(); + bloc.push(plurals_like); + } ParsedValue::ForeignKey(foreign_key) => { foreign_key .into_inner() .into_inner("reduce_into") .reduce_into(bloc); } - ParsedValue::String(s) => { - if s.is_empty() { + ParsedValue::Literal(s) => { + if s.is_string().is_some_and(str::is_empty) { // skip empty strings - } else if let Some(ParsedValue::String(last)) = bloc.last_mut() { - // if last in the bloc is a string push into it instead of 2 strings next to each others - last.push_str(&s); + } else if let Some(ParsedValue::Literal(last)) = bloc.last_mut() { + // if last in the bloc is a literal join them instead of 2 literal next to each others + last.join(&s); } else { - bloc.push(ParsedValue::String(s)); + bloc.push(ParsedValue::Literal(s)); } } ParsedValue::Variable { key, formatter } => { @@ -686,8 +869,8 @@ impl ParsedValue { fn flatten(&self, tokens: &mut Vec, locale_field: &Key) { match self { ParsedValue::Subkeys(_) | ParsedValue::Default => {} - ParsedValue::String(s) if s.is_empty() => {} - ParsedValue::String(s) => tokens.push(quote!(leptos::IntoView::into_view(#s))), + ParsedValue::Literal(Literal::String(s)) if s.is_empty() => {} + ParsedValue::Literal(s) => tokens.push(quote!(leptos::IntoView::into_view(#s))), ParsedValue::Ranges(ranges) => tokens.push(ranges.to_token_stream()), ParsedValue::Variable { key, formatter } => { let ts = formatter.var_to_view(&key.ident, &locale_field.ident); @@ -698,12 +881,17 @@ impl ParsedValue { } ParsedValue::Component { key, inner } => { let mut key_path = KeyPath::new(None); - let captured_keys = inner.get_keys(&mut key_path).unwrap().map(|keys| { - let keys = keys - .iter_keys() - .map(|key| quote!(let #key = core::clone::Clone::clone(&#key);)); - quote!(#(#keys)*) - }); + let captured_keys = + inner + .get_keys(&mut key_path) + .unwrap() + .is_interpol() + .map(|keys| { + let keys = keys + .iter_keys() + .map(|key| quote!(let #key = core::clone::Clone::clone(&#key);)); + quote!(#(#keys)*) + }); let f = quote!({ #captured_keys @@ -728,8 +916,13 @@ impl ParsedValue { fn flatten_string(&self, tokens: &mut Vec, locale_field: &Key) { match self { ParsedValue::Subkeys(_) | ParsedValue::Default => {} - ParsedValue::String(s) if s.is_empty() => {} - ParsedValue::String(s) => tokens.push(quote!(core::fmt::Display::fmt(#s, __formatter))), + ParsedValue::Literal(Literal::String(s)) if s.is_empty() => {} + ParsedValue::Literal(Literal::String(s)) => { + tokens.push(quote!(core::fmt::Display::fmt(#s, __formatter))) + } + ParsedValue::Literal(s) => { + tokens.push(quote!(core::fmt::Display::fmt(&#s, __formatter))) + } ParsedValue::Ranges(ranges) => tokens.push(ranges.as_string_impl()), ParsedValue::Variable { key, formatter } => { let ts = formatter.var_fmt(key, locale_field); @@ -748,7 +941,9 @@ impl ParsedValue { .borrow() .as_inner("flatten_string") .flatten_string(tokens, locale_field), - ParsedValue::Plurals(plurals) => tokens.push(plurals.as_string_impl()), + ParsedValue::Plurals(plurals) => { + tokens.push(plurals.as_string_impl(&plurals.count_key)) + } } } @@ -766,23 +961,37 @@ impl ParsedValue { } impl ForeignKey { + pub fn new( + current_key_path: KeyPath, + target_key_path: KeyPath, + args: HashMap, + locale: &Rc, + ) -> Self { + FOREIGN_KEYS.with(|foreign_keys| { + foreign_keys + .borrow_mut() + .insert((Rc::clone(locale), current_key_path)) + }); + ForeignKey::NotSet(target_key_path, args) + } + pub fn into_inner(self, call_site: &str) -> ParsedValue { match self { - ForeignKey::NotSet(_, _) => unreachable!("called {} on unresolved foreign key. If you got this error please open an issue on github.", call_site), + ForeignKey::NotSet(_, _) => unreachable!("called {} on unresolved foreign key. If you got this error please open an issue on github (into_inner).", call_site), ForeignKey::Set(inner) => *inner, } } pub fn as_inner(&self, call_site: &str) -> &ParsedValue { match self { - ForeignKey::NotSet(_, _) => unreachable!("called {} on unresolved foreign key. If you got this error please open an issue on github.", call_site), + ForeignKey::NotSet(_, _) => unreachable!("called {} on unresolved foreign key. If you got this error please open an issue on github (as_inner).", call_site), ForeignKey::Set(inner) => inner, } } pub fn as_inner_mut(&mut self, call_site: &str) -> &mut ParsedValue { match self { - ForeignKey::NotSet(_, _) => unreachable!("called {} on unresolved foreign key. If you got this error please open an issue on github.", call_site), + ForeignKey::NotSet(_, _) => unreachable!("called {} on unresolved foreign key. If you got this error please open an issue on github (as_inner_mut).", call_site), ForeignKey::Set(inner) => inner, } } @@ -836,6 +1045,34 @@ impl<'de> serde::de::Visitor<'de> for ParsedValueSeed<'_> { .map_err(|err| serde::de::Error::custom(err)) } + fn visit_bool(self, v: bool) -> std::result::Result + where + E: serde::de::Error, + { + Ok(ParsedValue::Literal(Literal::Bool(v))) + } + + fn visit_i64(self, v: i64) -> std::result::Result + where + E: serde::de::Error, + { + Ok(ParsedValue::Literal(Literal::Signed(v))) + } + + fn visit_f64(self, v: f64) -> std::result::Result + where + E: serde::de::Error, + { + Ok(ParsedValue::Literal(Literal::Float(v))) + } + + fn visit_u64(self, v: u64) -> std::result::Result + where + E: serde::de::Error, + { + Ok(ParsedValue::Literal(Literal::Unsigned(v))) + } + fn visit_map(self, map: A) -> Result where A: serde::de::MapAccess<'de>, @@ -897,6 +1134,70 @@ impl<'de> serde::de::Visitor<'de> for ParsedValueSeed<'_> { } } +struct LiteralVisitor; + +impl<'de> Deserialize<'de> for Literal { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_any(LiteralVisitor) + } +} + +impl<'de> Visitor<'de> for LiteralVisitor { + type Value = Literal; + + fn visit_bool(self, v: bool) -> std::result::Result + where + E: serde::de::Error, + { + Ok(Literal::Bool(v)) + } + + fn visit_i64(self, v: i64) -> std::result::Result + where + E: serde::de::Error, + { + Ok(Literal::Signed(v)) + } + + fn visit_f64(self, v: f64) -> std::result::Result + where + E: serde::de::Error, + { + Ok(Literal::Float(v)) + } + + fn visit_u64(self, v: u64) -> std::result::Result + where + E: serde::de::Error, + { + Ok(Literal::Unsigned(v)) + } + + fn visit_string(self, v: String) -> std::result::Result + where + E: serde::de::Error, + { + Ok(Literal::String(v)) + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + Ok(Literal::String(v.to_string())) + } + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + formatter, + "a litteral such as a number, a string or a boolean" + ) + } +} + #[cfg(test)] mod tests { use super::*; @@ -916,7 +1217,10 @@ mod tests { fn parse_normal_string() { let value = new_parsed_value("test"); - assert_eq!(value, ParsedValue::String("test".to_string())); + assert_eq!( + value, + ParsedValue::Literal(Literal::String("test".to_string())) + ); } #[test] @@ -926,12 +1230,12 @@ mod tests { assert_eq!( value, ParsedValue::Bloc(vec![ - ParsedValue::String("before ".to_string()), + ParsedValue::Literal(Literal::String("before ".to_string())), ParsedValue::Variable { key: new_key("var_var"), formatter: Formatter::None }, - ParsedValue::String(" after".to_string()) + ParsedValue::Literal(Literal::String(" after".to_string())) ]) ) } @@ -943,12 +1247,12 @@ mod tests { assert_eq!( value, ParsedValue::Bloc(vec![ - ParsedValue::String("before ".to_string()), + ParsedValue::Literal(Literal::String("before ".to_string())), ParsedValue::Component { key: new_key("comp_comp"), - inner: Box::new(ParsedValue::String("inner".to_string())) + inner: Box::new(ParsedValue::Literal(Literal::String("inner".to_string()))) }, - ParsedValue::String(" after".to_string()) + ParsedValue::Literal(Literal::String(" after".to_string())) ]) ) } @@ -962,19 +1266,21 @@ mod tests { assert_eq!( value, ParsedValue::Bloc(vec![ - ParsedValue::String("before ".to_string()), + ParsedValue::Literal(Literal::String("before ".to_string())), ParsedValue::Component { key: new_key("comp_comp"), inner: Box::new(ParsedValue::Bloc(vec![ - ParsedValue::String("inner before".to_string()), + ParsedValue::Literal(Literal::String("inner before".to_string())), ParsedValue::Component { key: new_key("comp_comp"), - inner: Box::new(ParsedValue::String("inner inner".to_string())) + inner: Box::new(ParsedValue::Literal(Literal::String( + "inner inner".to_string() + ))) }, - ParsedValue::String("inner after".to_string()), + ParsedValue::Literal(Literal::String("inner after".to_string())), ])) }, - ParsedValue::String(" after".to_string()) + ParsedValue::Literal(Literal::String(" after".to_string())) ]) ) } @@ -986,12 +1292,14 @@ mod tests { assert_eq!( value, ParsedValue::Bloc(vec![ - ParsedValue::String("

test".to_string()), + ParsedValue::Literal(Literal::String("

test".to_string())), ParsedValue::Component { key: new_key("comp_h3"), - inner: Box::new(ParsedValue::String("this is a h3".to_string())) + inner: Box::new(ParsedValue::Literal(Literal::String( + "this is a h3".to_string() + ))) }, - ParsedValue::String("not closing p".to_string()) + ParsedValue::Literal(Literal::String("not closing p".to_string())) ]) ) } diff --git a/leptos_i18n_macro/src/load_locales/plurals.rs b/leptos_i18n_macro/src/load_locales/plurals.rs index 39c66a22..8dc22dfd 100644 --- a/leptos_i18n_macro/src/load_locales/plurals.rs +++ b/leptos_i18n_macro/src/load_locales/plurals.rs @@ -1,11 +1,28 @@ -use std::collections::HashMap; +use std::{ + collections::{HashMap, HashSet}, + rc::Rc, +}; +use fixed_decimal::{FixedDecimal, FloatPrecision}; +use icu::plurals::{PluralCategory, PluralOperands, PluralRuleType as IcuRuleType, PluralRules}; use proc_macro2::TokenStream; use quote::{quote, ToTokens}; +use std::fmt::Display; -use crate::{load_locales::interpolate::CACHED_LOCALE_FIELD_KEY, utils::key::KeyPath}; +use crate::{ + load_locales::{ + error::{Error, Result}, + interpolate::CACHED_LOCALE_FIELD_KEY, + locale::LiteralType, + parsed_value::{InterpolOrLit, Literal}, + }, + utils::key::{Key, KeyPath}, +}; -use super::parsed_value::ParsedValue; +use super::{ + parsed_value::ParsedValue, + warning::{emit_warning, Warning}, +}; #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] pub enum PluralRuleType { @@ -13,6 +30,24 @@ pub enum PluralRuleType { Ordinal, } +impl Display for PluralRuleType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + PluralRuleType::Cardinal => write!(f, "cardinal"), + PluralRuleType::Ordinal => write!(f, "ordinal"), + } + } +} + +impl From for IcuRuleType { + fn from(value: PluralRuleType) -> Self { + match value { + PluralRuleType::Cardinal => IcuRuleType::Cardinal, + PluralRuleType::Ordinal => IcuRuleType::Ordinal, + } + } +} + impl ToTokens for PluralRuleType { fn to_tokens(&self, tokens: &mut TokenStream) { tokens.extend(self.to_token_stream()) @@ -40,6 +75,19 @@ pub enum PluralForm { Other, } +impl Display for PluralForm { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + PluralForm::Zero => write!(f, "_zero"), + PluralForm::One => write!(f, "_one"), + PluralForm::Two => write!(f, "_two"), + PluralForm::Few => write!(f, "_few"), + PluralForm::Many => write!(f, "_many"), + PluralForm::Other => write!(f, "_other"), + } + } +} + impl PluralForm { pub fn try_from_str(s: &str) -> Option { match s { @@ -52,6 +100,17 @@ impl PluralForm { _ => None, } } + + pub fn from_icu_category(cat: PluralCategory) -> Self { + match cat { + PluralCategory::Zero => PluralForm::Zero, + PluralCategory::One => PluralForm::One, + PluralCategory::Two => PluralForm::Two, + PluralCategory::Few => PluralForm::Few, + PluralCategory::Many => PluralForm::Many, + PluralCategory::Other => PluralForm::Other, + } + } } impl ToTokens for PluralForm { @@ -76,6 +135,7 @@ impl ToTokens for PluralForm { #[derive(Debug, Clone, PartialEq)] pub struct Plurals { pub rule_type: PluralRuleType, + pub count_key: Rc, // Box to be used inside the `ParsedValue::Plurals` variant without size recursion, // we could have `ParsedValue::Plurals(Box)` // but that makes `ParsedValue::Plurals(Plurals { .. })` impossible in match patterns. @@ -84,7 +144,29 @@ pub struct Plurals { } impl Plurals { - pub fn as_string_impl(&self) -> TokenStream { + fn get_plural_rules(&self, locale: &Rc) -> PluralRules { + let locale = locale.name.parse::().unwrap(); + PluralRules::try_new(&locale.into(), self.rule_type.into()).unwrap() + } + + pub fn check_categories(&self, locale: &Rc, key_path: &KeyPath) { + let plural_rules = self.get_plural_rules(locale); + let categs = self.forms.keys().copied().collect::>(); + let used_categs = plural_rules + .categories() + .map(PluralForm::from_icu_category) + .collect::>(); + for cat in categs.difference(&used_categs) { + emit_warning(Warning::UnusedCategory { + locale: locale.clone(), + key_path: key_path.to_owned(), + category: *cat, + rule_type: self.rule_type, + }) + } + } + + pub fn as_string_impl(&self, count_key: &Key) -> TokenStream { let match_arms = self.forms.iter().map(|(form, value)| { let ts = value.as_string_impl(); quote!(#form => #ts) @@ -98,12 +180,146 @@ impl Plurals { quote! {{ let _plural_rules = l_i18n_crate::__private::get_plural_rules(*#locale_field, #rule_type); - match _plural_rules.category_for(core::clone::Clone::clone(plural_count)) { + match _plural_rules.category_for(core::clone::Clone::clone(#count_key)) { #(#match_arms,)* _ => #other, } }} } + + fn populate_with_new_key( + &self, + new_key: Rc, + args: &HashMap, + foreign_key: &KeyPath, + locale: &Rc, + key_path: &KeyPath, + ) -> Result { + let other = self.other.populate(args, foreign_key, locale, key_path)?; + let mut forms = HashMap::new(); + for (form, value) in &self.forms { + let value = value.populate(args, foreign_key, locale, key_path)?; + forms.insert(*form, value); + } + + Ok(ParsedValue::Plurals(Plurals { + rule_type: self.rule_type, + count_key: new_key, + other: Box::new(other), + forms, + })) + } + + pub fn find_variable( + values: &[ParsedValue], + locale: &Rc, + key_path: &KeyPath, + foreign_key: &KeyPath, + ) -> Result> { + let mut iter = values.iter().peekable(); + while let Some(next) = iter.peek() { + match next { + ParsedValue::Literal(Literal::String(s)) if s.trim().is_empty() => { + iter.next(); + } + ParsedValue::Variable { .. } => break, + _ => { + return Err(Error::InvalidCountArg { + locale: locale.clone(), + key_path: key_path.to_owned(), + foreign_key: foreign_key.to_owned(), + }) + } + } + } + let Some(ParsedValue::Variable { key, .. }) = iter.next() else { + return Err(Error::InvalidCountArg { + locale: locale.clone(), + key_path: key_path.to_owned(), + foreign_key: foreign_key.to_owned(), + }); + }; + + for next in iter { + match next { + ParsedValue::Literal(Literal::String(s)) if s.trim().is_empty() => continue, + _ => { + return Err(Error::InvalidCountArg { + locale: locale.clone(), + key_path: key_path.to_owned(), + foreign_key: foreign_key.to_owned(), + }) + } + } + } + + Ok(key.clone()) + } + + fn populate_with_count_arg( + &self, + count_arg: &ParsedValue, + args: &HashMap, + foreign_key: &KeyPath, + locale: &Rc, + key_path: &KeyPath, + ) -> Result { + fn get_category>( + plurals: &Plurals, + locale: &Rc, + input: I, + ) -> PluralCategory { + let plural_rules = plurals.get_plural_rules(locale); + plural_rules.category_for(input) + } + + let category = match count_arg { + ParsedValue::Literal(Literal::Float(count)) => { + let count = FixedDecimal::try_from_f64(*count, FloatPrecision::Floating).unwrap(); + get_category(self, locale, &count) + } + ParsedValue::Literal(Literal::Unsigned(count)) => get_category(self, locale, *count), + ParsedValue::Literal(Literal::Signed(count)) => get_category(self, locale, *count), + ParsedValue::Bloc(values) => { + let new_key = Self::find_variable(values, locale, key_path, foreign_key)?; + return self.populate_with_new_key(new_key, args, foreign_key, locale, key_path); + } + ParsedValue::Variable { key, .. } => { + return self.populate_with_new_key(key.clone(), args, foreign_key, locale, key_path) + } + _ => { + return Err(Error::InvalidCountArg { + locale: locale.clone(), + key_path: key_path.to_owned(), + foreign_key: foreign_key.to_owned(), + }) + } + }; + + match PluralForm::from_icu_category(category) { + PluralForm::Other => self.other.populate(args, foreign_key, locale, key_path), + other_cat => self.forms.get(&other_cat).unwrap_or(&self.other).populate( + args, + foreign_key, + locale, + key_path, + ), + } + } + + pub fn populate( + &self, + args: &HashMap, + foreign_key: &KeyPath, + locale: &Rc, + key_path: &KeyPath, + ) -> Result { + if let Some(count_arg) = args.get("var_count") { + return self.populate_with_count_arg(count_arg, args, foreign_key, locale, key_path); + } + + self.populate_with_new_key(self.count_key.clone(), args, foreign_key, locale, key_path) + } } impl ToTokens for Plurals { @@ -120,16 +336,16 @@ impl ToTokens for Plurals { let locale_field = CACHED_LOCALE_FIELD_KEY.with(Clone::clone); let other = &*self.other; - let mut captured_values = None; + let mut captured_values = InterpolOrLit::Lit(LiteralType::String); let mut key_path = KeyPath::new(None); for value in self.forms.values().chain(Some(other)) { value - .get_keys_inner(&mut key_path, &mut captured_values) + .get_keys_inner(&mut key_path, &mut captured_values, false) .unwrap(); } - let captured_values = captured_values.map(|keys| { + let captured_values = captured_values.is_interpol().map(|keys| { let keys = keys .iter_keys() .map(|key| quote!(let #key = core::clone::Clone::clone(&#key);)); @@ -138,13 +354,15 @@ impl ToTokens for Plurals { let rule_type = self.rule_type; + let count_key = &self.count_key; + quote! { leptos::IntoView::into_view( { #captured_values let _plural_rules = l_i18n_crate::__private::get_plural_rules(#locale_field, #rule_type); move || { - match _plural_rules.category_for(plural_count()) { + match _plural_rules.category_for(#count_key()) { #(#match_arms,)* _ => #other, } diff --git a/leptos_i18n_macro/src/load_locales/ranges.rs b/leptos_i18n_macro/src/load_locales/ranges.rs index 3e07efff..0a264ffc 100644 --- a/leptos_i18n_macro/src/load_locales/ranges.rs +++ b/leptos_i18n_macro/src/load_locales/ranges.rs @@ -1,5 +1,7 @@ use std::{ + collections::HashMap, marker::PhantomData, + num::TryFromIntError, ops::{Bound, Not}, rc::Rc, str::FromStr, @@ -9,13 +11,20 @@ use proc_macro2::TokenStream; use quote::{quote, ToTokens}; use syn::parse::ParseBuffer; -use crate::load_locales::locale::LocalesOrNamespaces; use crate::utils::key::{Key, KeyPath}; +use crate::{ + load_locales::{ + locale::{LiteralType, LocalesOrNamespaces}, + parsed_value::Literal, + plurals::Plurals, + }, + utils::key::CACHED_VAR_COUNT_KEY, +}; use super::{ declare_locales::parse_range_pairs, error::{Error, Result}, - parsed_value::{InterpolationKeys, ParsedValue, ParsedValueSeed}, + parsed_value::{InterpolOrLit, ParsedValue, ParsedValueSeed}, }; #[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] @@ -85,7 +94,7 @@ impl RangeType { pub type RangesInner = Vec<(Range, ParsedValue)>; #[derive(Debug, Clone, PartialEq)] -pub enum Ranges { +pub enum UntypedRangesInner { I8(RangesInner), I16(RangesInner), I32(RangesInner), @@ -98,19 +107,254 @@ pub enum Ranges { F64(RangesInner), } +#[derive(Debug, Clone, PartialEq)] +pub struct Ranges { + pub count_key: Rc, + pub inner: UntypedRangesInner, +} + impl Ranges { + pub fn populate_with_count_arg( + &self, + count_arg: &ParsedValue, + args: &HashMap, + foreign_key: &KeyPath, + locale: &Rc, + key_path: &KeyPath, + ) -> Result { + fn find_value( + v: &RangesInner, + count: T, + args: &HashMap, + foreign_key: &KeyPath, + locale: &Rc, + key_path: &KeyPath, + ) -> Result { + for (range, value) in v { + if range.do_match(count) { + return value.populate(args, foreign_key, locale, key_path); + } + } + unreachable!("plurals validity should already have been checked."); + } + fn try_from>(count: T) -> Result { + TryFrom::try_from(count).map_err(|_| todo!()) + } + match count_arg { + ParsedValue::Literal(Literal::Float(count)) => { + let count = *count; + match &self.inner { + UntypedRangesInner::F32(v) => { + find_value(v, count as f32, args, foreign_key, locale, key_path) + } + UntypedRangesInner::F64(v) => { + find_value(v, count, args, foreign_key, locale, key_path) + } + _ => Err(Error::InvalidCountArgType { + locale: locale.clone(), + key_path: key_path.to_owned(), + foreign_key: foreign_key.to_owned(), + input_type: RangeType::F64, + range_type: self.get_type(), + }), + } + } + ParsedValue::Literal(Literal::Unsigned(count)) => { + let count = *count; + match &self.inner { + UntypedRangesInner::U64(v) => { + find_value(v, count, args, foreign_key, locale, key_path) + } + UntypedRangesInner::I8(v) => { + find_value(v, try_from(count)?, args, foreign_key, locale, key_path) + } + UntypedRangesInner::I16(v) => { + find_value(v, try_from(count)?, args, foreign_key, locale, key_path) + } + UntypedRangesInner::I32(v) => { + find_value(v, try_from(count)?, args, foreign_key, locale, key_path) + } + UntypedRangesInner::I64(v) => { + find_value(v, try_from(count)?, args, foreign_key, locale, key_path) + } + UntypedRangesInner::U8(v) => { + find_value(v, try_from(count)?, args, foreign_key, locale, key_path) + } + UntypedRangesInner::U16(v) => { + find_value(v, try_from(count)?, args, foreign_key, locale, key_path) + } + UntypedRangesInner::U32(v) => { + find_value(v, try_from(count)?, args, foreign_key, locale, key_path) + } + _ => Err(Error::InvalidCountArgType { + locale: locale.clone(), + key_path: key_path.to_owned(), + foreign_key: foreign_key.to_owned(), + input_type: RangeType::U64, + range_type: self.get_type(), + }), + } + } + ParsedValue::Literal(Literal::Signed(count)) => { + let count = *count; + match &self.inner { + UntypedRangesInner::U64(v) => { + find_value(v, try_from(count)?, args, foreign_key, locale, key_path) + } + UntypedRangesInner::I8(v) => { + find_value(v, try_from(count)?, args, foreign_key, locale, key_path) + } + UntypedRangesInner::I16(v) => { + find_value(v, try_from(count)?, args, foreign_key, locale, key_path) + } + UntypedRangesInner::I32(v) => { + find_value(v, try_from(count)?, args, foreign_key, locale, key_path) + } + UntypedRangesInner::I64(v) => { + find_value(v, count, args, foreign_key, locale, key_path) + } + UntypedRangesInner::U8(v) => { + find_value(v, try_from(count)?, args, foreign_key, locale, key_path) + } + UntypedRangesInner::U16(v) => { + find_value(v, try_from(count)?, args, foreign_key, locale, key_path) + } + UntypedRangesInner::U32(v) => { + find_value(v, try_from(count)?, args, foreign_key, locale, key_path) + } + _ => Err(Error::InvalidCountArgType { + locale: locale.clone(), + key_path: key_path.to_owned(), + foreign_key: foreign_key.to_owned(), + input_type: RangeType::I64, + range_type: self.get_type(), + }), + } + } + ParsedValue::Bloc(values) => { + let new_key = Plurals::find_variable(values, locale, key_path, foreign_key)?; + self.populate_with_new_key(new_key, args, foreign_key, locale, key_path) + } + ParsedValue::Variable { key, .. } => { + self.populate_with_new_key(key.clone(), args, foreign_key, locale, key_path) + } + _ => Err(Error::InvalidCountArg { + locale: locale.clone(), + key_path: key_path.to_owned(), + foreign_key: foreign_key.to_owned(), + }), + } + } + + fn populate_with_new_key( + &self, + new_key: Rc, + args: &HashMap, + foreign_key: &KeyPath, + locale: &Rc, + key_path: &KeyPath, + ) -> Result { + fn inner( + v: &RangesInner, + args: &HashMap, + foreign_key: &KeyPath, + locale: &Rc, + key_path: &KeyPath, + ) -> Result> { + let mut values = Vec::with_capacity(v.len()); + for (range, value) in v { + let range = Clone::clone(range); + let value = value.populate(args, foreign_key, locale, key_path)?; + values.push((range, value)); + } + Ok(values) + } + let ranges = match &self.inner { + UntypedRangesInner::I8(v) => { + inner(v, args, foreign_key, locale, key_path).map(UntypedRangesInner::I8) + } + UntypedRangesInner::I16(v) => { + inner(v, args, foreign_key, locale, key_path).map(UntypedRangesInner::I16) + } + UntypedRangesInner::I32(v) => { + inner(v, args, foreign_key, locale, key_path).map(UntypedRangesInner::I32) + } + UntypedRangesInner::I64(v) => { + inner(v, args, foreign_key, locale, key_path).map(UntypedRangesInner::I64) + } + UntypedRangesInner::U8(v) => { + inner(v, args, foreign_key, locale, key_path).map(UntypedRangesInner::U8) + } + UntypedRangesInner::U16(v) => { + inner(v, args, foreign_key, locale, key_path).map(UntypedRangesInner::U16) + } + UntypedRangesInner::U32(v) => { + inner(v, args, foreign_key, locale, key_path).map(UntypedRangesInner::U32) + } + UntypedRangesInner::U64(v) => { + inner(v, args, foreign_key, locale, key_path).map(UntypedRangesInner::U64) + } + UntypedRangesInner::F32(v) => { + inner(v, args, foreign_key, locale, key_path).map(UntypedRangesInner::F32) + } + UntypedRangesInner::F64(v) => { + inner(v, args, foreign_key, locale, key_path).map(UntypedRangesInner::F64) + } + }; + ranges + .map(|inner| Ranges { + count_key: new_key, + inner, + }) + .map(ParsedValue::Ranges) + } + + pub fn populate( + &self, + args: &HashMap, + foreign_key: &KeyPath, + locale: &Rc, + key_path: &KeyPath, + ) -> Result { + if let Some(count_arg) = args.get("var_count") { + self.populate_with_count_arg(count_arg, args, foreign_key, locale, key_path) + } else { + self.populate_with_new_key(self.count_key.clone(), args, foreign_key, locale, key_path) + } + } + pub fn as_string_impl(&self) -> TokenStream { - match self { - Ranges::I8(ranges) => Self::to_tokens_integers_string(ranges), - Ranges::I16(ranges) => Self::to_tokens_integers_string(ranges), - Ranges::I32(ranges) => Self::to_tokens_integers_string(ranges), - Ranges::I64(ranges) => Self::to_tokens_integers_string(ranges), - Ranges::U8(ranges) => Self::to_tokens_integers_string(ranges), - Ranges::U16(ranges) => Self::to_tokens_integers_string(ranges), - Ranges::U32(ranges) => Self::to_tokens_integers_string(ranges), - Ranges::U64(ranges) => Self::to_tokens_integers_string(ranges), - Ranges::F32(ranges) => Self::to_tokens_floats_string(ranges), - Ranges::F64(ranges) => Self::to_tokens_floats_string(ranges), + match &self.inner { + UntypedRangesInner::I8(ranges) => { + Self::to_tokens_integers_string(ranges, &self.count_key) + } + UntypedRangesInner::I16(ranges) => { + Self::to_tokens_integers_string(ranges, &self.count_key) + } + UntypedRangesInner::I32(ranges) => { + Self::to_tokens_integers_string(ranges, &self.count_key) + } + UntypedRangesInner::I64(ranges) => { + Self::to_tokens_integers_string(ranges, &self.count_key) + } + UntypedRangesInner::U8(ranges) => { + Self::to_tokens_integers_string(ranges, &self.count_key) + } + UntypedRangesInner::U16(ranges) => { + Self::to_tokens_integers_string(ranges, &self.count_key) + } + UntypedRangesInner::U32(ranges) => { + Self::to_tokens_integers_string(ranges, &self.count_key) + } + UntypedRangesInner::U64(ranges) => { + Self::to_tokens_integers_string(ranges, &self.count_key) + } + UntypedRangesInner::F32(ranges) => { + Self::to_tokens_floats_string(ranges, &self.count_key) + } + UntypedRangesInner::F64(ranges) => { + Self::to_tokens_floats_string(ranges, &self.count_key) + } } } @@ -126,32 +370,28 @@ impl Ranges { }) } - pub fn get_keys_inner( - &self, - key_path: &mut KeyPath, - keys: &mut Option, - ) -> Result<()> { + pub fn get_keys_inner(&self, key_path: &mut KeyPath, keys: &mut InterpolOrLit) -> Result<()> { fn inner( v: &RangesInner, key_path: &mut KeyPath, - keys: &mut Option, + keys: &mut InterpolOrLit, ) -> Result<()> { for (_, value) in v { - value.get_keys_inner(key_path, keys)?; + value.get_keys_inner(key_path, keys, false)?; } Ok(()) } - match self { - Ranges::I8(v) => inner(v, key_path, keys), - Ranges::I16(v) => inner(v, key_path, keys), - Ranges::I32(v) => inner(v, key_path, keys), - Ranges::I64(v) => inner(v, key_path, keys), - Ranges::U8(v) => inner(v, key_path, keys), - Ranges::U16(v) => inner(v, key_path, keys), - Ranges::U32(v) => inner(v, key_path, keys), - Ranges::U64(v) => inner(v, key_path, keys), - Ranges::F32(v) => inner(v, key_path, keys), - Ranges::F64(v) => inner(v, key_path, keys), + match &self.inner { + UntypedRangesInner::I8(v) => inner(v, key_path, keys), + UntypedRangesInner::I16(v) => inner(v, key_path, keys), + UntypedRangesInner::I32(v) => inner(v, key_path, keys), + UntypedRangesInner::I64(v) => inner(v, key_path, keys), + UntypedRangesInner::U8(v) => inner(v, key_path, keys), + UntypedRangesInner::U16(v) => inner(v, key_path, keys), + UntypedRangesInner::U32(v) => inner(v, key_path, keys), + UntypedRangesInner::U64(v) => inner(v, key_path, keys), + UntypedRangesInner::F32(v) => inner(v, key_path, keys), + UntypedRangesInner::F64(v) => inner(v, key_path, keys), } } @@ -168,17 +408,17 @@ impl Ranges { } Ok(()) } - match self { - Ranges::I8(v) => inner(v, f), - Ranges::I16(v) => inner(v, f), - Ranges::I32(v) => inner(v, f), - Ranges::I64(v) => inner(v, f), - Ranges::U8(v) => inner(v, f), - Ranges::U16(v) => inner(v, f), - Ranges::U32(v) => inner(v, f), - Ranges::U64(v) => inner(v, f), - Ranges::F32(v) => inner(v, f), - Ranges::F64(v) => inner(v, f), + match &self.inner { + UntypedRangesInner::I8(v) => inner(v, f), + UntypedRangesInner::I16(v) => inner(v, f), + UntypedRangesInner::I32(v) => inner(v, f), + UntypedRangesInner::I64(v) => inner(v, f), + UntypedRangesInner::U8(v) => inner(v, f), + UntypedRangesInner::U16(v) => inner(v, f), + UntypedRangesInner::U32(v) => inner(v, f), + UntypedRangesInner::U64(v) => inner(v, f), + UntypedRangesInner::F32(v) => inner(v, f), + UntypedRangesInner::F64(v) => inner(v, f), } } @@ -195,48 +435,51 @@ impl Ranges { } Ok(()) } - match self { - Ranges::I8(v) => inner(v, f), - Ranges::I16(v) => inner(v, f), - Ranges::I32(v) => inner(v, f), - Ranges::I64(v) => inner(v, f), - Ranges::U8(v) => inner(v, f), - Ranges::U16(v) => inner(v, f), - Ranges::U32(v) => inner(v, f), - Ranges::U64(v) => inner(v, f), - Ranges::F32(v) => inner(v, f), - Ranges::F64(v) => inner(v, f), + match &mut self.inner { + UntypedRangesInner::I8(v) => inner(v, f), + UntypedRangesInner::I16(v) => inner(v, f), + UntypedRangesInner::I32(v) => inner(v, f), + UntypedRangesInner::I64(v) => inner(v, f), + UntypedRangesInner::U8(v) => inner(v, f), + UntypedRangesInner::U16(v) => inner(v, f), + UntypedRangesInner::U32(v) => inner(v, f), + UntypedRangesInner::U64(v) => inner(v, f), + UntypedRangesInner::F32(v) => inner(v, f), + UntypedRangesInner::F64(v) => inner(v, f), } } pub const fn get_type(&self) -> RangeType { - match self { - Ranges::I8(_) => RangeType::I8, - Ranges::I16(_) => RangeType::I16, - Ranges::I32(_) => RangeType::I32, - Ranges::I64(_) => RangeType::I64, - Ranges::U8(_) => RangeType::U8, - Ranges::U16(_) => RangeType::U16, - Ranges::U32(_) => RangeType::U32, - Ranges::U64(_) => RangeType::U64, - Ranges::F32(_) => RangeType::F32, - Ranges::F64(_) => RangeType::F64, + match &self.inner { + UntypedRangesInner::I8(_) => RangeType::I8, + UntypedRangesInner::I16(_) => RangeType::I16, + UntypedRangesInner::I32(_) => RangeType::I32, + UntypedRangesInner::I64(_) => RangeType::I64, + UntypedRangesInner::U8(_) => RangeType::U8, + UntypedRangesInner::U16(_) => RangeType::U16, + UntypedRangesInner::U32(_) => RangeType::U32, + UntypedRangesInner::U64(_) => RangeType::U64, + UntypedRangesInner::F32(_) => RangeType::F32, + UntypedRangesInner::F64(_) => RangeType::F64, } } - fn to_tokens_integers(ranges: &[(Range, ParsedValue)]) -> TokenStream { + fn to_tokens_integers( + ranges: &[(Range, ParsedValue)], + count_key: &Key, + ) -> TokenStream { let match_arms = ranges.iter().map(|(range, value)| quote!(#range => #value)); - let mut captured_values = None; + let mut captured_values = InterpolOrLit::Lit(LiteralType::String); let mut key_path = KeyPath::new(None); for (_, value) in ranges { value - .get_keys_inner(&mut key_path, &mut captured_values) + .get_keys_inner(&mut key_path, &mut captured_values, false) .unwrap(); } - let captured_values = captured_values.map(|keys| { + let captured_values = captured_values.is_interpol().map(|keys| { let keys = keys .iter_keys() .map(|key| quote!(let #key = core::clone::Clone::clone(&#key);)); @@ -244,8 +487,7 @@ impl Ranges { }); let match_statement = quote! { { - let plural_count = plural_count(); - match plural_count { + match #count_key() { #( #match_arms, )* @@ -266,6 +508,7 @@ impl Ranges { fn to_tokens_integers_string( ranges: &[(Range, ParsedValue)], + count_key: &Key, ) -> TokenStream { let match_arms = ranges.iter().map(|(range, value)| { let value = value.as_string_impl(); @@ -274,8 +517,7 @@ impl Ranges { quote! { { - let plural_count = *plural_count; - match plural_count { + match *#count_key { #( #match_arms, )* @@ -299,7 +541,10 @@ impl Ranges { } } - fn to_tokens_floats(ranges: &[(Range, ParsedValue)]) -> TokenStream { + fn to_tokens_floats( + ranges: &[(Range, ParsedValue)], + count_key: &Key, + ) -> TokenStream { let mut ifs = ranges .iter() .map(|(range, value)| match Self::to_condition(range) { @@ -312,16 +557,16 @@ impl Ranges { #(else #ifs)* }; - let mut captured_values = None; + let mut captured_values = InterpolOrLit::Lit(LiteralType::String); let mut key_path = KeyPath::new(None); for (_, value) in ranges { value - .get_keys_inner(&mut key_path, &mut captured_values) + .get_keys_inner(&mut key_path, &mut captured_values, false) .unwrap(); } - let captured_values = captured_values.map(|keys| { + let captured_values = captured_values.is_interpol().map(|keys| { let keys = keys .iter_keys() .map(|key| quote!(let #key = core::clone::Clone::clone(&#key);)); @@ -333,7 +578,7 @@ impl Ranges { { #captured_values move || { - let plural_count = plural_count(); + let plural_count = #count_key(); #ifs } }, @@ -342,7 +587,10 @@ impl Ranges { } } - fn to_tokens_floats_string(ranges: &[(Range, ParsedValue)]) -> TokenStream { + fn to_tokens_floats_string( + ranges: &[(Range, ParsedValue)], + count_key: &Key, + ) -> TokenStream { let mut ifs = ranges.iter().map(|(range, value)| { let value = value.as_string_impl(); match Self::to_condition(range) { @@ -358,7 +606,7 @@ impl Ranges { quote! { { - let plural_count = *plural_count; + let plural_count = *#count_key; #ifs } } @@ -368,32 +616,52 @@ impl Ranges { where A: ParseRanges<'a, 'de>, { - match self { - Ranges::I8(ranges) => ParseRanges::deserialize_all_pairs(seq, ranges, seed), - Ranges::I16(ranges) => ParseRanges::deserialize_all_pairs(seq, ranges, seed), - Ranges::I32(ranges) => ParseRanges::deserialize_all_pairs(seq, ranges, seed), - Ranges::I64(ranges) => ParseRanges::deserialize_all_pairs(seq, ranges, seed), - Ranges::U8(ranges) => ParseRanges::deserialize_all_pairs(seq, ranges, seed), - Ranges::U16(ranges) => ParseRanges::deserialize_all_pairs(seq, ranges, seed), - Ranges::U32(ranges) => ParseRanges::deserialize_all_pairs(seq, ranges, seed), - Ranges::U64(ranges) => ParseRanges::deserialize_all_pairs(seq, ranges, seed), - Ranges::F32(ranges) => ParseRanges::deserialize_all_pairs(seq, ranges, seed), - Ranges::F64(ranges) => ParseRanges::deserialize_all_pairs(seq, ranges, seed), + match &mut self.inner { + UntypedRangesInner::I8(ranges) => ParseRanges::deserialize_all_pairs(seq, ranges, seed), + UntypedRangesInner::I16(ranges) => { + ParseRanges::deserialize_all_pairs(seq, ranges, seed) + } + UntypedRangesInner::I32(ranges) => { + ParseRanges::deserialize_all_pairs(seq, ranges, seed) + } + UntypedRangesInner::I64(ranges) => { + ParseRanges::deserialize_all_pairs(seq, ranges, seed) + } + UntypedRangesInner::U8(ranges) => ParseRanges::deserialize_all_pairs(seq, ranges, seed), + UntypedRangesInner::U16(ranges) => { + ParseRanges::deserialize_all_pairs(seq, ranges, seed) + } + UntypedRangesInner::U32(ranges) => { + ParseRanges::deserialize_all_pairs(seq, ranges, seed) + } + UntypedRangesInner::U64(ranges) => { + ParseRanges::deserialize_all_pairs(seq, ranges, seed) + } + UntypedRangesInner::F32(ranges) => { + ParseRanges::deserialize_all_pairs(seq, ranges, seed) + } + UntypedRangesInner::F64(ranges) => { + ParseRanges::deserialize_all_pairs(seq, ranges, seed) + } } } pub fn from_type(range_type: RangeType) -> Self { - match range_type { - RangeType::I8 => Self::I8(vec![]), - RangeType::I16 => Self::I16(vec![]), - RangeType::I32 => Self::I32(vec![]), - RangeType::I64 => Self::I64(vec![]), - RangeType::U8 => Self::U8(vec![]), - RangeType::U16 => Self::U16(vec![]), - RangeType::U32 => Self::U32(vec![]), - RangeType::U64 => Self::U64(vec![]), - RangeType::F32 => Self::F32(vec![]), - RangeType::F64 => Self::F64(vec![]), + let inner = match range_type { + RangeType::I8 => UntypedRangesInner::I8(vec![]), + RangeType::I16 => UntypedRangesInner::I16(vec![]), + RangeType::I32 => UntypedRangesInner::I32(vec![]), + RangeType::I64 => UntypedRangesInner::I64(vec![]), + RangeType::U8 => UntypedRangesInner::U8(vec![]), + RangeType::U16 => UntypedRangesInner::U16(vec![]), + RangeType::U32 => UntypedRangesInner::U32(vec![]), + RangeType::U64 => UntypedRangesInner::U64(vec![]), + RangeType::F32 => UntypedRangesInner::F32(vec![]), + RangeType::F64 => UntypedRangesInner::F64(vec![]), + }; + Ranges { + count_key: CACHED_VAR_COUNT_KEY.with(Clone::clone), + inner, } } @@ -411,7 +679,10 @@ impl Ranges { let mut ranges = match type_or_range { TypeOrRange::Type(range_type) => Self::from_type(range_type), - TypeOrRange::Range(range) => Ranges::I32(vec![range]), + TypeOrRange::Range(range) => Ranges { + count_key: CACHED_VAR_COUNT_KEY.with(Clone::clone), + inner: UntypedRangesInner::I32(vec![range]), + }, }; ranges.deserialize_inner(seq, parsed_value_seed)?; @@ -440,34 +711,54 @@ impl Ranges { } pub fn check_deserialization(&self) -> (bool, usize, bool) { - match self { - Ranges::I8(ranges) => Self::check_de_inner(ranges), - Ranges::I16(ranges) => Self::check_de_inner(ranges), - Ranges::I32(ranges) => Self::check_de_inner(ranges), - Ranges::I64(ranges) => Self::check_de_inner(ranges), - Ranges::U8(ranges) => Self::check_de_inner(ranges), - Ranges::U16(ranges) => Self::check_de_inner(ranges), - Ranges::U32(ranges) => Self::check_de_inner(ranges), - Ranges::U64(ranges) => Self::check_de_inner(ranges), - Ranges::F32(ranges) => Self::check_de_inner(ranges), - Ranges::F64(ranges) => Self::check_de_inner(ranges), + match &self.inner { + UntypedRangesInner::I8(ranges) => Self::check_de_inner(ranges), + UntypedRangesInner::I16(ranges) => Self::check_de_inner(ranges), + UntypedRangesInner::I32(ranges) => Self::check_de_inner(ranges), + UntypedRangesInner::I64(ranges) => Self::check_de_inner(ranges), + UntypedRangesInner::U8(ranges) => Self::check_de_inner(ranges), + UntypedRangesInner::U16(ranges) => Self::check_de_inner(ranges), + UntypedRangesInner::U32(ranges) => Self::check_de_inner(ranges), + UntypedRangesInner::U64(ranges) => Self::check_de_inner(ranges), + UntypedRangesInner::F32(ranges) => Self::check_de_inner(ranges), + UntypedRangesInner::F64(ranges) => Self::check_de_inner(ranges), } } } impl ToTokens for Ranges { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - match self { - Ranges::I8(ranges) => Self::to_tokens_integers(ranges).to_tokens(tokens), - Ranges::I16(ranges) => Self::to_tokens_integers(ranges).to_tokens(tokens), - Ranges::I32(ranges) => Self::to_tokens_integers(ranges).to_tokens(tokens), - Ranges::I64(ranges) => Self::to_tokens_integers(ranges).to_tokens(tokens), - Ranges::U8(ranges) => Self::to_tokens_integers(ranges).to_tokens(tokens), - Ranges::U16(ranges) => Self::to_tokens_integers(ranges).to_tokens(tokens), - Ranges::U32(ranges) => Self::to_tokens_integers(ranges).to_tokens(tokens), - Ranges::U64(ranges) => Self::to_tokens_integers(ranges).to_tokens(tokens), - Ranges::F32(ranges) => Self::to_tokens_floats(ranges).to_tokens(tokens), - Ranges::F64(ranges) => Self::to_tokens_floats(ranges).to_tokens(tokens), + match &self.inner { + UntypedRangesInner::I8(ranges) => { + Self::to_tokens_integers(ranges, &self.count_key).to_tokens(tokens) + } + UntypedRangesInner::I16(ranges) => { + Self::to_tokens_integers(ranges, &self.count_key).to_tokens(tokens) + } + UntypedRangesInner::I32(ranges) => { + Self::to_tokens_integers(ranges, &self.count_key).to_tokens(tokens) + } + UntypedRangesInner::I64(ranges) => { + Self::to_tokens_integers(ranges, &self.count_key).to_tokens(tokens) + } + UntypedRangesInner::U8(ranges) => { + Self::to_tokens_integers(ranges, &self.count_key).to_tokens(tokens) + } + UntypedRangesInner::U16(ranges) => { + Self::to_tokens_integers(ranges, &self.count_key).to_tokens(tokens) + } + UntypedRangesInner::U32(ranges) => { + Self::to_tokens_integers(ranges, &self.count_key).to_tokens(tokens) + } + UntypedRangesInner::U64(ranges) => { + Self::to_tokens_integers(ranges, &self.count_key).to_tokens(tokens) + } + UntypedRangesInner::F32(ranges) => { + Self::to_tokens_floats(ranges, &self.count_key).to_tokens(tokens) + } + UntypedRangesInner::F64(ranges) => { + Self::to_tokens_floats(ranges, &self.count_key).to_tokens(tokens) + } } } } @@ -495,6 +786,24 @@ pub trait RangeInteger: RangeNumber {} pub trait RangeFloats: RangeNumber {} impl Range { + fn do_match(&self, count: T) -> bool { + match self { + Range::Exact(v) => *v == count, + Range::Bounds { start, end } => { + if matches!(start, Some(s) if *s > count) { + return false; + } + match end { + Bound::Included(e) => *e >= count, + Bound::Excluded(e) => *e > count, + Bound::Unbounded => true, + } + } + Range::Multiple(ranges) => ranges.iter().any(|range| range.do_match(count)), + Range::Fallback => true, + } + } + fn flatten(self) -> Self { let Range::Multiple(ranges) = self else { return self; diff --git a/leptos_i18n_macro/src/load_locales/warning.rs b/leptos_i18n_macro/src/load_locales/warning.rs index 5ce685c8..d6fe205c 100644 --- a/leptos_i18n_macro/src/load_locales/warning.rs +++ b/leptos_i18n_macro/src/load_locales/warning.rs @@ -5,6 +5,8 @@ use quote::{format_ident, quote}; use crate::utils::key::{Key, KeyPath}; use std::{cell::RefCell, fmt::Display, rc::Rc}; +use super::plurals::{PluralForm, PluralRuleType}; + #[derive(Debug)] pub enum Warning { MissingKey { @@ -15,6 +17,12 @@ pub enum Warning { locale: Rc, key_path: KeyPath, }, + UnusedCategory { + locale: Rc, + key_path: KeyPath, + category: PluralForm, + rule_type: PluralRuleType, + }, #[cfg(feature = "track_locale_files")] NonUnicodePath { locale: Rc, @@ -44,6 +52,9 @@ impl Display for Warning { "Key \"{}\" is present in locale {:?} but not in default locale, it is ignored", key_path, locale ), + Warning::UnusedCategory { locale, key_path, category, rule_type } => { + write!(f, "at key \"{}\", locale {:?} does not use {} category {}, it is still kept but is useless.", key_path, locale, rule_type, category) + }, #[cfg(feature = "track_locale_files")] Warning::NonUnicodePath { locale, namespace: None, path } => write!(f, "File path for locale {:?} is not valid Unicode, can't add it to proc macro depedencies. Path: {:?}", locale, path), #[cfg(feature = "track_locale_files")] diff --git a/leptos_i18n_macro/src/t_macro/interpolate.rs b/leptos_i18n_macro/src/t_macro/interpolate.rs index 5134dfa0..5483372a 100644 --- a/leptos_i18n_macro/src/t_macro/interpolate.rs +++ b/leptos_i18n_macro/src/t_macro/interpolate.rs @@ -23,17 +23,6 @@ pub enum InterpolatedValue { comp_name: Ident, attrs: TokenStream, }, - // intermidiate value, not constructible by the user - Count { - key: Ident, - foreign_count: Option, - }, - // form t!(i18n, key, $ = ..) - AssignedCount { - foreign_count: Option, - key: Ident, - value: Expr, - }, } fn check_component_end(input: Cursor) -> bool { @@ -72,28 +61,6 @@ impl syn::parse::Parse for InterpolatedValue { if is_comp { input.parse::()?; } - let is_count = !is_comp && input.peek(Token![$]); - if is_count { - input.parse::()?; - let foreign_count = if input.peek(Token![:]) { - input.parse::()?; - let foreign_count = input.parse::()?; - Some(foreign_count) - } else { - None - }; - input.parse::()?; - let value = input.parse::()?; - let ident = foreign_count - .as_ref() - .map(|ident| format_ident!("__plural_count_{}__", ident)) - .unwrap_or_else(|| format_ident!("__plural_count__")); - return Ok(InterpolatedValue::AssignedCount { - key: ident, - value, - foreign_count, - }); - } let key = input.parse::()?; if is_comp { input.parse::]>()?; @@ -127,13 +94,10 @@ impl syn::parse::Parse for InterpolatedValue { impl InterpolatedValue { pub fn get_ident(&self) -> Option<&Ident> { match self { - InterpolatedValue::Var(ident) - | InterpolatedValue::Count { key: ident, .. } - | InterpolatedValue::Comp(ident) => Some(ident), + InterpolatedValue::Var(ident) | InterpolatedValue::Comp(ident) => Some(ident), InterpolatedValue::AssignedVar { .. } | InterpolatedValue::AssignedComp { .. } - | InterpolatedValue::DirectComp { .. } - | InterpolatedValue::AssignedCount { .. } => None, + | InterpolatedValue::DirectComp { .. } => None, } } @@ -173,22 +137,6 @@ impl InterpolatedValue { *self = InterpolatedValue::Comp(key.clone()); (key.clone(), ts) } - InterpolatedValue::AssignedCount { - key, - value, - foreign_count, - } => { - let key = key.clone(); - let ts = quote!(#value); - *self = InterpolatedValue::Count { - key: key.clone(), - foreign_count: foreign_count.take(), - }; - (key, ts) - } - InterpolatedValue::Count { .. } => { - unreachable!("This is an intermidiate state, can't be constructed by the user.") - } } } } @@ -204,18 +152,9 @@ impl ToTokens for InterpolatedValue { let comp_ident = Self::format_ident(ident, false); quote!(#comp_ident(#ident)) } - InterpolatedValue::Count { foreign_count, key } => { - if let Some(foreign_count) = foreign_count { - let builder_set_fn = format_ident!("plural_count_{}", foreign_count); - quote!(#builder_set_fn(#key)) - } else { - quote!(plural_count(#key)) - } - } InterpolatedValue::AssignedVar { .. } | InterpolatedValue::AssignedComp { .. } - | InterpolatedValue::DirectComp { .. } - | InterpolatedValue::AssignedCount { .. } => { + | InterpolatedValue::DirectComp { .. } => { unreachable!( "Assigned values should have been transformed to normal var in the param step" ) diff --git a/leptos_i18n_macro/src/t_macro/mod.rs b/leptos_i18n_macro/src/t_macro/mod.rs index 29cf7f0d..38636677 100644 --- a/leptos_i18n_macro/src/t_macro/mod.rs +++ b/leptos_i18n_macro/src/t_macro/mod.rs @@ -69,7 +69,7 @@ pub fn t_macro_inner( let inner = quote! { { #[allow(unused)] - use leptos_i18n::__private::BuildStr; + use leptos_i18n::__private::BuildLit; let _key = #get_key; _key.#builder_fn().#build_fn() } diff --git a/leptos_i18n_macro/src/utils/key.rs b/leptos_i18n_macro/src/utils/key.rs index 6dc11028..3e00ef63 100644 --- a/leptos_i18n_macro/src/utils/key.rs +++ b/leptos_i18n_macro/src/utils/key.rs @@ -8,7 +8,7 @@ use std::{ }; thread_local! { - pub static CACHED_PLURAL_COUNT_KEY: Rc = Rc::new(Key::new("var_count").unwrap()); + pub static CACHED_VAR_COUNT_KEY: Rc = Rc::new(Key::new("var_count").unwrap()); } #[derive(Clone)] diff --git a/tests/json/Cargo.toml b/tests/json/Cargo.toml index 1630d04e..60ea1711 100644 --- a/tests/json/Cargo.toml +++ b/tests/json/Cargo.toml @@ -11,7 +11,10 @@ crate-type = ["cdylib", "rlib"] [dependencies] leptos = "0.6" common = { path = "../common" } -leptos_i18n = { path = "../../leptos_i18n", features = ["interpolate_display"] } +leptos_i18n = { path = "../../leptos_i18n", features = [ + "interpolate_display", + "track_locale_files", +] } [package.metadata.leptos-i18n] diff --git a/tests/json/locales/en.json b/tests/json/locales/en.json index 3ed0e4f2..5b0370aa 100644 --- a/tests/json/locales/en.json +++ b/tests/json/locales/en.json @@ -38,12 +38,14 @@ ["zero", 0], ["this range is declared in locale {{ locale }}"] ], - "defaulted_foreign_key": "before {{ @click_to_inc }} after", - "foreign_key_to_string": "before {{ @click_to_inc }} after", - "foreign_key_to_interpolation": "before {{ @click_count }} after", - "foreign_key_to_subkey": "before {{ @subkeys.subkey_1 }} after", + "defaulted_foreign_key": "before $t(click_to_inc) after", + "foreign_key_to_string": "before $t(click_to_inc) after", + "foreign_key_to_interpolation": "before $t(click_count) after", + "foreign_key_to_subkey": "before $t(subkeys.subkey_1) after", "foreign_key_to_explicit_default": "no explicit default in default locale", - "populated_foreign_key": "before {{ @click_count, count = '45' }} after", + "populated_foreign_key": "before $t(click_count, {\"count\": \"45\" }) after", + "populated_foreign_key_with_arg": "before $t(click_count, {\"count\": \"{{ new_count }}\" }) after", + "populated_foreign_key_with_foreign_key_arg": "before $t(click_count, {\"count\": \"$t(subkeys.subkey_1)\" }) after", "interpolate_variable_and_comp": "{{ count }}", "list_formatting": "{{ list, list(list_type: and; list_style: wide) }}", "date_formatting": "{{ date, date }}", @@ -55,5 +57,20 @@ "ordinal_plural_ordinal_one": "{{ count }}st place", "ordinal_plural_ordinal_two": "{{ count }}nd place", "ordinal_plural_ordinal_few": "{{ count }}rd place", - "ordinal_plural_ordinal_other": "{{ count }}th place" + "ordinal_plural_ordinal_other": "{{ count }}th place", + "same_lit_type": true, + "mixed_lit_type": 59.89, + "range_with_arg_other_than_count": [ + ["{{ arg }} zero", 0], + ["{{ arg }} {{ count }}", "_"] + ], + "args_to_range": "$t(range_with_arg_other_than_count, {\"arg\": \"en\"})", + "count_arg_to_range": "$t(range_with_arg_other_than_count, {\"count\": 0})", + "renamed_ranges_count": "$t(f32_range, {\"count\":\"{{ first_count }}\"}) $t(OR_range, {\"count\":\"{{ second_count }}\"})", + "plural_with_arg_other_than_count_one": "{{ arg }} singular", + "plural_with_arg_other_than_count_other": "{{ arg }} {{ count }}", + "args_to_plural": "$t(plural_with_arg_other_than_count, {\"arg\": \"en\"})", + "count_arg_to_plural": "$t(plural_with_arg_other_than_count, {\"count\": 1})", + "foreign_key_to_two_plurals": "$t(cardinal_plural) $t(plural_with_arg_other_than_count, {\"arg\": \"en\"})", + "renamed_plurals_count": "$t(cardinal_plural, {\"count\":\"{{ first_count }}\"}) $t(ordinal_plural, {\"count\":\"{{ second_count }}\"})" } diff --git a/tests/json/locales/fr.json b/tests/json/locales/fr.json index 5f0420ba..942764cf 100644 --- a/tests/json/locales/fr.json +++ b/tests/json/locales/fr.json @@ -32,11 +32,13 @@ "defaulted_interpolation": null, "defaulted_ranges": null, "defaulted_foreign_key": null, - "foreign_key_to_string": "before {{ @click_to_inc }} after", - "foreign_key_to_interpolation": "before {{ @click_count }} after", - "foreign_key_to_subkey": "before {{ @subkeys.subkey_1 }} after", - "foreign_key_to_explicit_default": "before {{ @defaulted_string }} after", - "populated_foreign_key": "before {{ @click_count, count = \"32\" }} after", + "foreign_key_to_string": "before $t(click_to_inc) after", + "foreign_key_to_interpolation": "before $t(click_count) after", + "foreign_key_to_subkey": "before $t(subkeys.subkey_1) after", + "foreign_key_to_explicit_default": "before $t(defaulted_string) after", + "populated_foreign_key": "before $t(click_count, {\"count\": \"32\" }) after", + "populated_foreign_key_with_arg": "before $t(click_count, {\"count\": \"{{ new_count }}\" }) after", + "populated_foreign_key_with_foreign_key_arg": "before $t(click_count, {\"count\": \"$t(subkeys.subkey_1)\" }) after", "interpolate_variable_and_comp": "{{ count }}", "list_formatting": "{{ list, list(list_type: or; list_style: short) }}", "date_formatting": "{{ date, date }}", @@ -45,5 +47,20 @@ "number_formatting": "{{ num, number }}", "cardinal_plural": "{{ count }}", "ordinal_plural_ordinal_one": "{{ count }}re place", - "ordinal_plural_ordinal_other": "{{ count }}e place" + "ordinal_plural_ordinal_other": "{{ count }}e place", + "same_lit_type": false, + "mixed_lit_type": true, + "range_with_arg_other_than_count": [ + ["{{ arg }} zero", 0], + ["{{ arg }} {{ count }}", "_"] + ], + "args_to_range": "$t(range_with_arg_other_than_count, {\"arg\": \"fr\"})", + "count_arg_to_range": "$t(range_with_arg_other_than_count, {\"count\": 0})", + "renamed_ranges_count": "$t(f32_range, {\"count\":\"{{ first_count }}\"}) $t(OR_range, {\"count\":\"{{ second_count }}\"})", + "plural_with_arg_other_than_count_one": "{{ arg }} singular", + "plural_with_arg_other_than_count_other": "{{ arg }} {{ count }}", + "args_to_plural": "$t(plural_with_arg_other_than_count, {\"arg\": \"fr\"})", + "count_arg_to_plural": "$t(plural_with_arg_other_than_count, {\"count\": 2})", + "foreign_key_to_two_plurals": "$t(cardinal_plural) $t(plural_with_arg_other_than_count, {\"arg\": \"fr\"})", + "renamed_plurals_count": "$t(cardinal_plural, {\"count\":\"{{ first_count }}\"}) $t(ordinal_plural, {\"count\":\"{{ second_count }}\"})" } diff --git a/tests/json/src/defaulted.rs b/tests/json/src/defaulted.rs index 5c2a6810..62440438 100644 --- a/tests/json/src/defaulted.rs +++ b/tests/json/src/defaulted.rs @@ -20,16 +20,16 @@ fn defaulted_interpolation() { #[test] fn defaulted_ranges() { let count = || 0; - let en = td!(Locale::en, defaulted_ranges, locale = "en", $ = count); + let en = td!(Locale::en, defaulted_ranges, locale = "en", count); assert_eq_rendered!(en, "zero"); - let fr = td!(Locale::fr, defaulted_ranges, locale = "en", $ = count); + let fr = td!(Locale::fr, defaulted_ranges, locale = "en", count); assert_eq_rendered!(fr, "zero"); for i in [-3, 5, 12] { let count = move || i; - let en = td!(Locale::en, defaulted_ranges, locale = "en", $ = count); + let en = td!(Locale::en, defaulted_ranges, locale = "en", count); assert_eq_rendered!(en, "this range is declared in locale en"); - let fr = td!(Locale::fr, defaulted_ranges, locale = "en", $ = count); + let fr = td!(Locale::fr, defaulted_ranges, locale = "en", count); assert_eq_rendered!(fr, "this range is declared in locale en"); } } diff --git a/tests/json/src/foreign.rs b/tests/json/src/foreign.rs index 1143fa14..1a8d1912 100644 --- a/tests/json/src/foreign.rs +++ b/tests/json/src/foreign.rs @@ -58,3 +58,19 @@ fn populated_foreign_key() { let fr = td!(Locale::fr, populated_foreign_key); assert_eq_rendered!(fr, "before Vous avez cliqué 32 fois after"); } + +#[test] +fn populated_foreign_key_with_arg() { + let en = td!(Locale::en, populated_foreign_key_with_arg, new_count = 12); + assert_eq_rendered!(en, "before You clicked 12 times after"); + let fr = td!(Locale::fr, populated_foreign_key_with_arg, new_count = 67); + assert_eq_rendered!(fr, "before Vous avez cliqué 67 fois after"); +} + +#[test] +fn populated_foreign_key_with_foreign_key_arg() { + let en = td!(Locale::en, populated_foreign_key_with_foreign_key_arg); + assert_eq_rendered!(en, "before You clicked subkey_1 times after"); + let fr = td!(Locale::fr, populated_foreign_key_with_foreign_key_arg); + assert_eq_rendered!(fr, "before Vous avez cliqué subkey_1 fois after"); +} diff --git a/tests/json/src/plurals.rs b/tests/json/src/plurals.rs index d8a2595e..3665aa03 100644 --- a/tests/json/src/plurals.rs +++ b/tests/json/src/plurals.rs @@ -5,24 +5,24 @@ use common::*; fn cardinal_plural() { // count = 0 let count = move || 0; - let en = td!(Locale::en, cardinal_plural, $ = count); + let en = td!(Locale::en, cardinal_plural, count); assert_eq_rendered!(en, "0 items"); - let fr = td!(Locale::fr, cardinal_plural, $ = count); + let fr = td!(Locale::fr, cardinal_plural, count); assert_eq_rendered!(fr, "0"); // count = 1 let count = move || 1; - let en = td!(Locale::en, cardinal_plural, $ = count); + let en = td!(Locale::en, cardinal_plural, count); assert_eq_rendered!(en, "one item"); - let fr = td!(Locale::fr, cardinal_plural, $ = count); + let fr = td!(Locale::fr, cardinal_plural, count); assert_eq_rendered!(fr, "1"); // count = 2.. for i in [2, 5, 10, 1000] { let count = move || i; - let en = td!(Locale::en, cardinal_plural, $ = count); + let en = td!(Locale::en, cardinal_plural, count); assert_eq_rendered!(en, format!("{} items", i)); - let fr = td!(Locale::fr, cardinal_plural, $ = count); + let fr = td!(Locale::fr, cardinal_plural, count); assert_eq_rendered!(fr, i.to_string()); } } @@ -31,29 +31,71 @@ fn cardinal_plural() { fn ordinal_plural() { // count = 1 let count = move || 1; - let en = td!(Locale::en, ordinal_plural, $ = count); + let en = td!(Locale::en, ordinal_plural, count); assert_eq_rendered!(en, "1st place"); - let fr = td!(Locale::fr, ordinal_plural, $ = count); + let fr = td!(Locale::fr, ordinal_plural, count); assert_eq_rendered!(fr, "1re place"); // count = 2 let count = move || 2; - let en = td!(Locale::en, ordinal_plural, $ = count); + let en = td!(Locale::en, ordinal_plural, count); assert_eq_rendered!(en, "2nd place"); - let fr = td!(Locale::fr, ordinal_plural, $ = count); + let fr = td!(Locale::fr, ordinal_plural, count); assert_eq_rendered!(fr, "2e place"); // count = 3 let count = move || 3; - let en = td!(Locale::en, ordinal_plural, $ = count); + let en = td!(Locale::en, ordinal_plural, count); assert_eq_rendered!(en, "3rd place"); - let fr = td!(Locale::fr, ordinal_plural, $ = count); + let fr = td!(Locale::fr, ordinal_plural, count); assert_eq_rendered!(fr, "3e place"); // count = 4 let count = move || 4; - let en = td!(Locale::en, ordinal_plural, $ = count); + let en = td!(Locale::en, ordinal_plural, count); assert_eq_rendered!(en, "4th place"); - let fr = td!(Locale::fr, ordinal_plural, $ = count); + let fr = td!(Locale::fr, ordinal_plural, count); assert_eq_rendered!(fr, "4e place"); } + +#[test] +fn args_to_plural() { + let count = move || 0; + let en = td!(Locale::en, args_to_plural, count); + assert_eq_rendered!(en, "en 0"); + let fr = td!(Locale::fr, args_to_plural, count); + assert_eq_rendered!(fr, "fr singular"); +} + +#[test] +fn count_arg_to_plural() { + let en = td!(Locale::en, count_arg_to_plural, arg = "en"); + assert_eq_rendered!(en, "en singular"); + let fr = td!(Locale::fr, count_arg_to_plural, arg = "fr"); + assert_eq_rendered!(fr, "fr 2"); +} + +#[test] +fn foreign_key_to_two_plurals() { + let count = move || 0; + let en = td!(Locale::en, foreign_key_to_two_plurals, count); + assert_eq_rendered!(en, "0 items en 0"); + let fr = td!(Locale::fr, foreign_key_to_two_plurals, count); + assert_eq_rendered!(fr, "0 fr singular"); + + let count = move || 1; + let en = td!(Locale::en, foreign_key_to_two_plurals, count); + assert_eq_rendered!(en, "one item en singular"); + let fr = td!(Locale::fr, foreign_key_to_two_plurals, count); + assert_eq_rendered!(fr, "1 fr singular"); +} + +#[test] +fn renamed_plurals_count() { + let first_count = move || 0; + let second_count = move || 1; + let en = td!(Locale::en, renamed_plurals_count, first_count, second_count); + assert_eq_rendered!(en, "0 items 1st place"); + let fr = td!(Locale::fr, renamed_plurals_count, first_count, second_count); + assert_eq_rendered!(fr, "0 1re place"); +} diff --git a/tests/json/src/ranges.rs b/tests/json/src/ranges.rs index 629cb669..7f796a92 100644 --- a/tests/json/src/ranges.rs +++ b/tests/json/src/ranges.rs @@ -5,26 +5,26 @@ use common::*; fn f32_range() { // count = 0 let count = move || 0.0; - let en = td!(Locale::en, f32_range, $ = count); + let en = td!(Locale::en, f32_range, count); assert_eq_rendered!(en, "You are broke"); - let fr = td!(Locale::fr, f32_range, $ = count); + let fr = td!(Locale::fr, f32_range, count); assert_eq_rendered!(fr, "Vous êtes pauvre"); // count = ..0 for i in [-100.34, -57.69, 0.0 - 0.00001] { let count = move || i; - let en = td!(Locale::en, f32_range, $ = count); + let en = td!(Locale::en, f32_range, count); assert_eq_rendered!(en, "You owe money"); - let fr = td!(Locale::fr, f32_range, $ = count); + let fr = td!(Locale::fr, f32_range, count); assert_eq_rendered!(fr, "Vous devez de l'argent"); } // count = _ for i in [100.34, 57.69, 0.0 + 0.00001] { let count = move || i; - let en = td!(Locale::en, f32_range, $ = count); + let en = td!(Locale::en, f32_range, count); assert_eq_rendered!(en, format!("You have {}€", i)); - let fr = td!(Locale::fr, f32_range, $ = count); + let fr = td!(Locale::fr, f32_range, count); assert_eq_rendered!(fr, format!("Vous avez {}€", i)); } } @@ -33,17 +33,17 @@ fn f32_range() { fn u32_range() { // count = 0 let count = move || 0; - let en = td!(Locale::en, u32_range, $ = count); + let en = td!(Locale::en, u32_range, count); assert_eq_rendered!(en, "0"); - let fr = td!(Locale::fr, u32_range, $ = count); + let fr = td!(Locale::fr, u32_range, count); assert_eq_rendered!(fr, "0"); // count = 1.. for i in [1, 45, 72] { let count = move || i; - let en = td!(Locale::en, u32_range, $ = count); + let en = td!(Locale::en, u32_range, count); assert_eq_rendered!(en, "1.."); - let fr = td!(Locale::fr, u32_range, $ = count); + let fr = td!(Locale::fr, u32_range, count); assert_eq_rendered!(fr, "1.."); } } @@ -52,16 +52,16 @@ fn u32_range() { fn u32_range_string() { // count = 0 let count = 0; - let en = td_string!(Locale::en, u32_range, $ = count); + let en = td_string!(Locale::en, u32_range, count); assert_eq!(en.to_string(), "0"); - let fr = td_string!(Locale::fr, u32_range, $ = count); + let fr = td_string!(Locale::fr, u32_range, count); assert_eq!(fr.to_string(), "0"); // count = 1.. for count in [1, 45, 72] { - let en = td_string!(Locale::en, u32_range, $ = count); + let en = td_string!(Locale::en, u32_range, count); assert_eq!(en.to_string(), "1.."); - let fr = td_string!(Locale::fr, u32_range, $ = count); + let fr = td_string!(Locale::fr, u32_range, count); assert_eq!(fr.to_string(), "1.."); } } @@ -71,36 +71,36 @@ fn or_range() { // count = 0 | 5 for i in [0, 5] { let count = move || i; - let en = td!(Locale::en, OR_range, $ = count); + let en = td!(Locale::en, OR_range, count); assert_eq_rendered!(en, "0 or 5"); - let fr = td!(Locale::fr, OR_range, $ = count); + let fr = td!(Locale::fr, OR_range, count); assert_eq_rendered!(fr, "0 or 5"); } // count = 1..5 | 6..10 for i in [1, 4, 6, 9] { let count = move || i; - let en = td!(Locale::en, OR_range, $ = count); + let en = td!(Locale::en, OR_range, count); assert_eq_rendered!(en, "1..5 | 6..10"); - let fr = td!(Locale::fr, OR_range, $ = count); + let fr = td!(Locale::fr, OR_range, count); assert_eq_rendered!(fr, "1..5 | 6..10"); } // count = 10..15 | 20 for i in [10, 12, 14, 20] { let count = move || i; - let en = td!(Locale::en, OR_range, $ = count); + let en = td!(Locale::en, OR_range, count); assert_eq_rendered!(en, "10..15 | 20"); - let fr = td!(Locale::fr, OR_range, $ = count); + let fr = td!(Locale::fr, OR_range, count); assert_eq_rendered!(fr, "10..15 | 20"); } // count = _ for i in [15, 17, 21, 56] { let count = move || i; - let en = td!(Locale::en, OR_range, $ = count); + let en = td!(Locale::en, OR_range, count); assert_eq_rendered!(en, "fallback with no count"); - let fr = td!(Locale::fr, OR_range, $ = count); + let fr = td!(Locale::fr, OR_range, count); assert_eq_rendered!(fr, "fallback sans count"); } } @@ -110,36 +110,36 @@ fn f32_or_range() { // count = 0 | 5 for i in [0.0, 5.0] { let count = move || i; - let en = td!(Locale::en, f32_OR_range, $ = count); + let en = td!(Locale::en, f32_OR_range, count); assert_eq_rendered!(en, "0 or 5"); - let fr = td!(Locale::fr, f32_OR_range, $ = count); + let fr = td!(Locale::fr, f32_OR_range, count); assert_eq_rendered!(fr, "0 or 5"); } // count = 1..5 | 6..10 for i in [1.0, 4.0, 6.0, 9.0] { let count = move || i; - let en = td!(Locale::en, f32_OR_range, $ = count); + let en = td!(Locale::en, f32_OR_range, count); assert_eq_rendered!(en, "1..5 | 6..10"); - let fr = td!(Locale::fr, f32_OR_range, $ = count); + let fr = td!(Locale::fr, f32_OR_range, count); assert_eq_rendered!(fr, "1..5 | 6..10"); } // count = 10..15 | 20 for i in [10.0, 12.0, 14.0, 20.0] { let count = move || i; - let en = td!(Locale::en, f32_OR_range, $ = count); + let en = td!(Locale::en, f32_OR_range, count); assert_eq_rendered!(en, "10..15 | 20"); - let fr = td!(Locale::fr, f32_OR_range, $ = count); + let fr = td!(Locale::fr, f32_OR_range, count); assert_eq_rendered!(fr, "10..15 | 20"); } // count = _ for i in [15.0, 17.0, 21.0, 56.0] { let count = move || i; - let en = td!(Locale::en, f32_OR_range, $ = count); + let en = td!(Locale::en, f32_OR_range, count); assert_eq_rendered!(en, "fallback with no count"); - let fr = td!(Locale::fr, f32_OR_range, $ = count); + let fr = td!(Locale::fr, f32_OR_range, count); assert_eq_rendered!(fr, "fallback avec tuple vide"); } } @@ -148,33 +148,61 @@ fn f32_or_range() { fn f32_or_range_string() { // count = 0 | 5 for count in [0.0, 5.0] { - let en = td_string!(Locale::en, f32_OR_range, $ = count); + let en = td_string!(Locale::en, f32_OR_range, count); assert_eq!(en, "0 or 5"); - let fr = td_string!(Locale::fr, f32_OR_range, $ = count); + let fr = td_string!(Locale::fr, f32_OR_range, count); assert_eq!(fr, "0 or 5"); } // count = 1..5 | 6..10 for count in [1.0, 4.0, 6.0, 9.0] { - let en = td_string!(Locale::en, f32_OR_range, $ = count); + let en = td_string!(Locale::en, f32_OR_range, count); assert_eq!(en, "1..5 | 6..10"); - let fr = td_string!(Locale::fr, f32_OR_range, $ = count); + let fr = td_string!(Locale::fr, f32_OR_range, count); assert_eq!(fr, "1..5 | 6..10"); } // count = 10..15 | 20 for count in [10.0, 12.0, 14.0, 20.0] { - let en = td_string!(Locale::en, f32_OR_range, $ = count); + let en = td_string!(Locale::en, f32_OR_range, count); assert_eq!(en, "10..15 | 20"); - let fr = td_string!(Locale::fr, f32_OR_range, $ = count); + let fr = td_string!(Locale::fr, f32_OR_range, count); assert_eq!(fr, "10..15 | 20"); } // count = _ for count in [15.0, 17.0, 21.0, 56.0] { - let en = td_string!(Locale::en, f32_OR_range, $ = count); + let en = td_string!(Locale::en, f32_OR_range, count); assert_eq!(en, "fallback with no count"); - let fr = td_string!(Locale::fr, f32_OR_range, $ = count); + let fr = td_string!(Locale::fr, f32_OR_range, count); assert_eq!(fr, "fallback avec tuple vide"); } } + +#[test] +fn args_to_range() { + let count = move || 1; + let en = td!(Locale::en, args_to_range, count); + assert_eq_rendered!(en, "en 1"); + let fr = td!(Locale::fr, args_to_range, count); + assert_eq_rendered!(fr, "fr 1"); +} + +#[test] +fn count_arg_to_range() { + let en = td!(Locale::en, count_arg_to_range, arg = "en"); + assert_eq_rendered!(en, "en zero"); + let fr = td!(Locale::fr, count_arg_to_range, arg = "fr"); + assert_eq_rendered!(fr, "fr zero"); +} + +#[test] +fn renamed_ranges_count() { + let first_count = move || 0.0; + let second_count = move || 1; + let en = td!(Locale::en, renamed_ranges_count, first_count, second_count); + assert_eq_rendered!(en, "You are broke 1..5 | 6..10"); + let fr = td!(Locale::fr, renamed_ranges_count, first_count, second_count); + assert_eq_rendered!(fr, "Vous êtes pauvre 1..5 | 6..10"); +} + diff --git a/tests/json/src/scoped.rs b/tests/json/src/scoped.rs index d134e332..3387ace1 100644 --- a/tests/json/src/scoped.rs +++ b/tests/json/src/scoped.rs @@ -7,18 +7,18 @@ fn subkey_3() { let fr_scope = scope_locale!(Locale::fr, subkeys); let count = || 0; - let en = td!(en_scope, subkey_3, $ = count); + let en = td!(en_scope, subkey_3, count); assert_eq_rendered!(en, "zero"); - let fr = td!(fr_scope, subkey_3, $ = count); + let fr = td!(fr_scope, subkey_3, count); assert_eq_rendered!(fr, "0"); let count = || 1; - let en = td!(en_scope, subkey_3, $ = count); + let en = td!(en_scope, subkey_3, count); assert_eq_rendered!(en, "one"); - let fr = td!(fr_scope, subkey_3, $ = count); + let fr = td!(fr_scope, subkey_3, count); assert_eq_rendered!(fr, "1"); let count = || 3; - let en = td!(en_scope, subkey_3, $ = count); + let en = td!(en_scope, subkey_3, count); assert_eq_rendered!(en, "3"); - let fr = td!(fr_scope, subkey_3, $ = count); + let fr = td!(fr_scope, subkey_3, count); assert_eq_rendered!(fr, "3"); } diff --git a/tests/json/src/subkeys.rs b/tests/json/src/subkeys.rs index 0f4f9703..0b4e1ffc 100644 --- a/tests/json/src/subkeys.rs +++ b/tests/json/src/subkeys.rs @@ -61,18 +61,18 @@ fn subkey_2_string() { #[test] fn subkey_3() { let count = || 0; - let en = td!(Locale::en, subkeys.subkey_3, $ = count); + let en = td!(Locale::en, subkeys.subkey_3, count); assert_eq_rendered!(en, "zero"); - let fr = td!(Locale::fr, subkeys.subkey_3, $ = count); + let fr = td!(Locale::fr, subkeys.subkey_3, count); assert_eq_rendered!(fr, "0"); let count = || 1; - let en = td!(Locale::en, subkeys.subkey_3, $ = count); + let en = td!(Locale::en, subkeys.subkey_3, count); assert_eq_rendered!(en, "one"); - let fr = td!(Locale::fr, subkeys.subkey_3, $ = count); + let fr = td!(Locale::fr, subkeys.subkey_3, count); assert_eq_rendered!(fr, "1"); let count = || 3; - let en = td!(Locale::en, subkeys.subkey_3, $ = count); + let en = td!(Locale::en, subkeys.subkey_3, count); assert_eq_rendered!(en, "3"); - let fr = td!(Locale::fr, subkeys.subkey_3, $ = count); + let fr = td!(Locale::fr, subkeys.subkey_3, count); assert_eq_rendered!(fr, "3"); } diff --git a/tests/json/src/tests.rs b/tests/json/src/tests.rs index b1c14ac5..59a8fbcc 100644 --- a/tests/json/src/tests.rs +++ b/tests/json/src/tests.rs @@ -49,19 +49,19 @@ fn click_count_string() { #[test] fn subkey_3() { let count = || 0; - let en = td!(Locale::en, subkeys.subkey_3, $ = count); + let en = td!(Locale::en, subkeys.subkey_3, count); assert_eq_rendered!(en, "zero"); - let fr = td!(Locale::fr, subkeys.subkey_3, $ = count); + let fr = td!(Locale::fr, subkeys.subkey_3, count); assert_eq_rendered!(fr, "0"); let count = || 1; - let en = td!(Locale::en, subkeys.subkey_3, $ = count); + let en = td!(Locale::en, subkeys.subkey_3, count); assert_eq_rendered!(en, "one"); - let fr = td!(Locale::fr, subkeys.subkey_3, $ = count); + let fr = td!(Locale::fr, subkeys.subkey_3, count); assert_eq_rendered!(fr, "1"); let count = || 3; - let en = td!(Locale::en, subkeys.subkey_3, $ = count); + let en = td!(Locale::en, subkeys.subkey_3, count); assert_eq_rendered!(en, "3"); - let fr = td!(Locale::fr, subkeys.subkey_3, $ = count); + let fr = td!(Locale::fr, subkeys.subkey_3, count); assert_eq_rendered!(fr, "3"); } @@ -88,3 +88,19 @@ fn non_copy_arg() { check_impl_fn(&fr); assert_eq_rendered!(fr, "count"); } + +#[test] +fn same_lit_type() { + let en = td!(Locale::en, same_lit_type); + assert_eq_rendered!(en, "true"); + let fr = td!(Locale::fr, same_lit_type); + assert_eq_rendered!(fr, "false"); +} + +#[test] +fn mixed_lit_type() { + let en = td!(Locale::en, mixed_lit_type); + assert_eq_rendered!(en, "59.89"); + let fr = td!(Locale::fr, mixed_lit_type); + assert_eq_rendered!(fr, "true"); +} diff --git a/tests/namespaces/locales/en/second_namespace.json b/tests/namespaces/locales/en/second_namespace.json index 9c9b9d61..6067e568 100644 --- a/tests/namespaces/locales/en/second_namespace.json +++ b/tests/namespaces/locales/en/second_namespace.json @@ -1,16 +1,16 @@ { - "common_key": "second namespace", - "click_count": "You clicked {{ count }} times", - "click_to_inc": "Click to increment the counter", - "subkeys": { - "subkey_1": "subkey_1", - "subkey_2": "subkey_2", - "subkey_3": [ - ["zero", "0"], - ["one", 1], - ["{{ count }}", "_"] - ] - }, - "foreign_key_to_same_namespace": "before {{ @second_namespace::common_key }} after", - "foreign_key_to_another_namespace": "before {{ @first_namespace::common_key }} after" -} \ No newline at end of file + "common_key": "second namespace", + "click_count": "You clicked {{ count }} times", + "click_to_inc": "Click to increment the counter", + "subkeys": { + "subkey_1": "subkey_1", + "subkey_2": "subkey_2", + "subkey_3": [ + ["zero", "0"], + ["one", 1], + ["{{ count }}", "_"] + ] + }, + "foreign_key_to_same_namespace": "before $t(second_namespace:common_key) after", + "foreign_key_to_another_namespace": "before $t(first_namespace:common_key) after" +} diff --git a/tests/namespaces/locales/fr/second_namespace.json b/tests/namespaces/locales/fr/second_namespace.json index 7bdd0ae3..c9ec332e 100644 --- a/tests/namespaces/locales/fr/second_namespace.json +++ b/tests/namespaces/locales/fr/second_namespace.json @@ -1,15 +1,15 @@ { - "common_key": "deuxième namespace", - "click_count": "Vous avez cliqué {{ count }} fois", - "click_to_inc": "Cliquez pour incrémenter le compteur", - "subkeys": { - "subkey_1": "subkey_1", - "subkey_2": "subkey_2", - "subkey_3": [ - ["zero", 0], - ["{{ count }}", "_"] - ] - }, - "foreign_key_to_same_namespace": "before {{ @second_namespace::common_key }} after", - "foreign_key_to_another_namespace": "before {{ @first_namespace::common_key }} after" -} \ No newline at end of file + "common_key": "deuxième namespace", + "click_count": "Vous avez cliqué {{ count }} fois", + "click_to_inc": "Cliquez pour incrémenter le compteur", + "subkeys": { + "subkey_1": "subkey_1", + "subkey_2": "subkey_2", + "subkey_3": [ + ["zero", 0], + ["{{ count }}", "_"] + ] + }, + "foreign_key_to_same_namespace": "before $t(second_namespace:common_key) after", + "foreign_key_to_another_namespace": "before $t(first_namespace:common_key) after" +} diff --git a/tests/namespaces/src/first_ns.rs b/tests/namespaces/src/first_ns.rs index bb29286a..6eaf6ed1 100644 --- a/tests/namespaces/src/first_ns.rs +++ b/tests/namespaces/src/first_ns.rs @@ -20,33 +20,33 @@ fn common_key() { #[test] fn range_only_en() { let count = move || 0; - let en = td!(Locale::en, first_namespace.range_only_en, $ = count); + let en = td!(Locale::en, first_namespace.range_only_en, count = count); assert_eq_rendered!(en, "exact"); for i in -3..0 { let count = move || i; - let en = td!(Locale::en, first_namespace.range_only_en, $ = count); + let en = td!(Locale::en, first_namespace.range_only_en, count = count); assert_eq_rendered!(en, "unbounded start"); } for i in 99..103 { let count = move || i; - let en = td!(Locale::en, first_namespace.range_only_en, $ = count); + let en = td!(Locale::en, first_namespace.range_only_en, count = count); assert_eq_rendered!(en, "unbounded end"); } for i in 1..3 { let count = move || i; - let en = td!(Locale::en, first_namespace.range_only_en, $ = count); + let en = td!(Locale::en, first_namespace.range_only_en, count = count); assert_eq_rendered!(en, "excluded end"); } for i in 3..=8 { let count = move || i; - let en = td!(Locale::en, first_namespace.range_only_en, $ = count); + let en = td!(Locale::en, first_namespace.range_only_en, count = count); assert_eq_rendered!(en, "included end"); } for i in [30, 40, 55, 73] { let count = move || i; - let en = td!(Locale::en, first_namespace.range_only_en, $ = count); + let en = td!(Locale::en, first_namespace.range_only_en, count = count); assert_eq_rendered!(en, "fallback"); } - let fr = td!(Locale::fr, first_namespace.range_only_en, $ = count); + let fr = td!(Locale::fr, first_namespace.range_only_en, count = count); assert_eq_rendered!(fr, "pas de ranges en français"); } diff --git a/tests/namespaces/src/scoped.rs b/tests/namespaces/src/scoped.rs index b5d608c3..cae098c9 100644 --- a/tests/namespaces/src/scoped.rs +++ b/tests/namespaces/src/scoped.rs @@ -23,35 +23,35 @@ fn scoped_ranges() { let fr_scope = scope_locale!(Locale::fr, first_namespace); let count = move || 0; - let en = td!(en_scope, range_only_en, $ = count); + let en = td!(en_scope, range_only_en, count = count); assert_eq_rendered!(en, "exact"); for i in -3..0 { let count = move || i; - let en = td!(en_scope, range_only_en, $ = count); + let en = td!(en_scope, range_only_en, count = count); assert_eq_rendered!(en, "unbounded start"); } for i in 99..103 { let count = move || i; - let en = td!(en_scope, range_only_en, $ = count); + let en = td!(en_scope, range_only_en, count = count); assert_eq_rendered!(en, "unbounded end"); } for i in 1..3 { let count = move || i; - let en = td!(en_scope, range_only_en, $ = count); + let en = td!(en_scope, range_only_en, count = count); assert_eq_rendered!(en, "excluded end"); } for i in 3..=8 { let count = move || i; - let en = td!(en_scope, range_only_en, $ = count); + let en = td!(en_scope, range_only_en, count = count); assert_eq_rendered!(en, "included end"); } for i in [30, 40, 55, 73] { let count = move || i; - let en = td!(en_scope, range_only_en, $ = count); + let en = td!(en_scope, range_only_en, count = count); assert_eq_rendered!(en, "fallback"); } - let fr = td!(fr_scope, range_only_en, $ = count); + let fr = td!(fr_scope, range_only_en, count = count); assert_eq_rendered!(fr, "pas de ranges en français"); } @@ -61,18 +61,18 @@ fn scoped_sub_subkeys() { let fr_scope = scope_locale!(Locale::fr, second_namespace.subkeys); let count = || 0; - let en = td!(en_scope, subkey_3, $ = count); + let en = td!(en_scope, subkey_3, count = count); assert_eq_rendered!(en, "zero"); - let fr = td!(fr_scope, subkey_3, $ = count); + let fr = td!(fr_scope, subkey_3, count = count); assert_eq_rendered!(fr, "zero"); let count = || 1; - let en = td!(en_scope, subkey_3, $ = count); + let en = td!(en_scope, subkey_3, count = count); assert_eq_rendered!(en, "one"); - let fr = td!(fr_scope, subkey_3, $ = count); + let fr = td!(fr_scope, subkey_3, count = count); assert_eq_rendered!(fr, "1"); let count = || 3; - let en = td!(en_scope, subkey_3, $ = count); + let en = td!(en_scope, subkey_3, count = count); assert_eq_rendered!(en, "3"); - let fr = td!(fr_scope, subkey_3, $ = count); + let fr = td!(fr_scope, subkey_3, count = count); assert_eq_rendered!(fr, "3"); } diff --git a/tests/namespaces/src/second_ns.rs b/tests/namespaces/src/second_ns.rs index b9aa5d2d..de569766 100644 --- a/tests/namespaces/src/second_ns.rs +++ b/tests/namespaces/src/second_ns.rs @@ -54,19 +54,19 @@ fn subkey_2() { #[test] fn subkey_3() { let count = || 0; - let en = td!(Locale::en, second_namespace.subkeys.subkey_3, $ = count); + let en = td!(Locale::en, second_namespace.subkeys.subkey_3, count = count); assert_eq_rendered!(en, "zero"); - let fr = td!(Locale::fr, second_namespace.subkeys.subkey_3, $ = count); + let fr = td!(Locale::fr, second_namespace.subkeys.subkey_3, count = count); assert_eq_rendered!(fr, "zero"); let count = || 1; - let en = td!(Locale::en, second_namespace.subkeys.subkey_3, $ = count); + let en = td!(Locale::en, second_namespace.subkeys.subkey_3, count = count); assert_eq_rendered!(en, "one"); - let fr = td!(Locale::fr, second_namespace.subkeys.subkey_3, $ = count); + let fr = td!(Locale::fr, second_namespace.subkeys.subkey_3, count = count); assert_eq_rendered!(fr, "1"); let count = || 3; - let en = td!(Locale::en, second_namespace.subkeys.subkey_3, $ = count); + let en = td!(Locale::en, second_namespace.subkeys.subkey_3, count = count); assert_eq_rendered!(en, "3"); - let fr = td!(Locale::fr, second_namespace.subkeys.subkey_3, $ = count); + let fr = td!(Locale::fr, second_namespace.subkeys.subkey_3, count = count); assert_eq_rendered!(fr, "3"); } diff --git a/tests/yaml/src/defaulted.rs b/tests/yaml/src/defaulted.rs index 025fc522..2e965ad9 100644 --- a/tests/yaml/src/defaulted.rs +++ b/tests/yaml/src/defaulted.rs @@ -20,16 +20,16 @@ fn defaulted_interpolation() { #[test] fn defaulted_ranges() { let count = || 0; - let en = td!(Locale::en, defaulted_ranges, locale = "en", $ = count); + let en = td!(Locale::en, defaulted_ranges, locale = "en", count = count); assert_eq_rendered!(en, "zero"); - let fr = td!(Locale::fr, defaulted_ranges, locale = "en", $ = count); + let fr = td!(Locale::fr, defaulted_ranges, locale = "en", count = count); assert_eq_rendered!(fr, "zero"); for i in [-3, 5, 12] { let count = move || i; - let en = td!(Locale::en, defaulted_ranges, locale = "en", $ = count); + let en = td!(Locale::en, defaulted_ranges, locale = "en", count = count); assert_eq_rendered!(en, "this range is declared in locale en"); - let fr = td!(Locale::fr, defaulted_ranges, locale = "en", $ = count); + let fr = td!(Locale::fr, defaulted_ranges, locale = "en", count = count); assert_eq_rendered!(fr, "this range is declared in locale en"); } } diff --git a/tests/yaml/src/ranges.rs b/tests/yaml/src/ranges.rs index 58eed14a..c440865a 100644 --- a/tests/yaml/src/ranges.rs +++ b/tests/yaml/src/ranges.rs @@ -5,26 +5,26 @@ use common::*; fn f32_range() { // count = 0 let count = move || 0.0; - let en = td!(Locale::en, f32_range, $ = count); + let en = td!(Locale::en, f32_range, count = count); assert_eq_rendered!(en, "You are broke"); - let fr = td!(Locale::fr, f32_range, $ = count); + let fr = td!(Locale::fr, f32_range, count = count); assert_eq_rendered!(fr, "Vous êtes pauvre"); // count = ..0 for i in [-100.34, -57.69, 0.0 - 0.00001] { let count = move || i; - let en = td!(Locale::en, f32_range, $ = count); + let en = td!(Locale::en, f32_range, count = count); assert_eq_rendered!(en, "You owe money"); - let fr = td!(Locale::fr, f32_range, $ = count); + let fr = td!(Locale::fr, f32_range, count = count); assert_eq_rendered!(fr, "Vous devez de l'argent"); } // count = _ for i in [100.34, 57.69, 0.0 + 0.00001] { let count = move || i; - let en = td!(Locale::en, f32_range, $ = count); + let en = td!(Locale::en, f32_range, count = count); assert_eq_rendered!(en, format!("You have {}€", i)); - let fr = td!(Locale::fr, f32_range, $ = count); + let fr = td!(Locale::fr, f32_range, count = count); assert_eq_rendered!(fr, format!("Vous avez {}€", i)); } } @@ -33,17 +33,17 @@ fn f32_range() { fn u32_range() { // count = 0 let count = move || 0; - let en = td!(Locale::en, u32_range, $ = count); + let en = td!(Locale::en, u32_range, count = count); assert_eq_rendered!(en, "0"); - let fr = td!(Locale::fr, u32_range, $ = count); + let fr = td!(Locale::fr, u32_range, count = count); assert_eq_rendered!(fr, "0"); // count = 1.. for i in [1, 45, 72] { let count = move || i; - let en = td!(Locale::en, u32_range, $ = count); + let en = td!(Locale::en, u32_range, count = count); assert_eq_rendered!(en, "1.."); - let fr = td!(Locale::fr, u32_range, $ = count); + let fr = td!(Locale::fr, u32_range, count = count); assert_eq_rendered!(fr, "1.."); } } @@ -53,36 +53,36 @@ fn or_range() { // count = 0 | 5 for i in [0, 5] { let count = move || i; - let en = td!(Locale::en, OR_range, $ = count); + let en = td!(Locale::en, OR_range, count = count); assert_eq_rendered!(en, "0 or 5"); - let fr = td!(Locale::fr, OR_range, $ = count); + let fr = td!(Locale::fr, OR_range, count = count); assert_eq_rendered!(fr, "0 or 5"); } // count = 1..5 | 6..10 for i in [1, 4, 6, 9] { let count = move || i; - let en = td!(Locale::en, OR_range, $ = count); + let en = td!(Locale::en, OR_range, count = count); assert_eq_rendered!(en, "1..5 | 6..10"); - let fr = td!(Locale::fr, OR_range, $ = count); + let fr = td!(Locale::fr, OR_range, count = count); assert_eq_rendered!(fr, "1..5 | 6..10"); } // count = 10..15 | 20 for i in [10, 12, 14, 20] { let count = move || i; - let en = td!(Locale::en, OR_range, $ = count); + let en = td!(Locale::en, OR_range, count = count); assert_eq_rendered!(en, "10..15 | 20"); - let fr = td!(Locale::fr, OR_range, $ = count); + let fr = td!(Locale::fr, OR_range, count = count); assert_eq_rendered!(fr, "10..15 | 20"); } // count = _ for i in [15, 17, 21, 56] { let count = move || i; - let en = td!(Locale::en, OR_range, $ = count); + let en = td!(Locale::en, OR_range, count = count); assert_eq_rendered!(en, "fallback with no count"); - let fr = td!(Locale::fr, OR_range, $ = count); + let fr = td!(Locale::fr, OR_range, count = count); assert_eq_rendered!(fr, "fallback sans count"); } } @@ -92,36 +92,36 @@ fn f32_or_range() { // count = 0 | 5 for i in [0.0, 5.0] { let count = move || i; - let en = td!(Locale::en, f32_OR_range, $ = count); + let en = td!(Locale::en, f32_OR_range, count = count); assert_eq_rendered!(en, "0 or 5"); - let fr = td!(Locale::fr, f32_OR_range, $ = count); + let fr = td!(Locale::fr, f32_OR_range, count = count); assert_eq_rendered!(fr, "0 or 5"); } // count = 1..5 | 6..10 for i in [1.0, 4.0, 6.0, 9.0] { let count = move || i; - let en = td!(Locale::en, f32_OR_range, $ = count); + let en = td!(Locale::en, f32_OR_range, count = count); assert_eq_rendered!(en, "1..5 | 6..10"); - let fr = td!(Locale::fr, f32_OR_range, $ = count); + let fr = td!(Locale::fr, f32_OR_range, count = count); assert_eq_rendered!(fr, "1..5 | 6..10"); } // count = 10..15 | 20 for i in [10.0, 12.0, 14.0, 20.0] { let count = move || i; - let en = td!(Locale::en, f32_OR_range, $ = count); + let en = td!(Locale::en, f32_OR_range, count = count); assert_eq_rendered!(en, "10..15 | 20"); - let fr = td!(Locale::fr, f32_OR_range, $ = count); + let fr = td!(Locale::fr, f32_OR_range, count = count); assert_eq_rendered!(fr, "10..15 | 20"); } // count = _ for i in [15.0, 17.0, 21.0, 56.0] { let count = move || i; - let en = td!(Locale::en, f32_OR_range, $ = count); + let en = td!(Locale::en, f32_OR_range, count = count); assert_eq_rendered!(en, "fallback with no count"); - let fr = td!(Locale::fr, f32_OR_range, $ = count); + let fr = td!(Locale::fr, f32_OR_range, count = count); assert_eq_rendered!(fr, "fallback avec tuple vide"); } } diff --git a/tests/yaml/src/subkeys.rs b/tests/yaml/src/subkeys.rs index aebf791f..32a2c2ff 100644 --- a/tests/yaml/src/subkeys.rs +++ b/tests/yaml/src/subkeys.rs @@ -27,18 +27,18 @@ fn subkey_2() { #[test] fn subkey_3() { let count = || 0; - let en = td!(Locale::en, subkeys.subkey_3, $ = count); + let en = td!(Locale::en, subkeys.subkey_3, count = count); assert_eq_rendered!(en, "zero"); - let fr = td!(Locale::fr, subkeys.subkey_3, $ = count); + let fr = td!(Locale::fr, subkeys.subkey_3, count = count); assert_eq_rendered!(fr, "0"); let count = || 1; - let en = td!(Locale::en, subkeys.subkey_3, $ = count); + let en = td!(Locale::en, subkeys.subkey_3, count = count); assert_eq_rendered!(en, "one"); - let fr = td!(Locale::fr, subkeys.subkey_3, $ = count); + let fr = td!(Locale::fr, subkeys.subkey_3, count = count); assert_eq_rendered!(fr, "1"); let count = || 3; - let en = td!(Locale::en, subkeys.subkey_3, $ = count); + let en = td!(Locale::en, subkeys.subkey_3, count = count); assert_eq_rendered!(en, "3"); - let fr = td!(Locale::fr, subkeys.subkey_3, $ = count); + let fr = td!(Locale::fr, subkeys.subkey_3, count = count); assert_eq_rendered!(fr, "3"); } diff --git a/tests/yaml/src/tests.rs b/tests/yaml/src/tests.rs index cc9a9bd6..b173afe2 100644 --- a/tests/yaml/src/tests.rs +++ b/tests/yaml/src/tests.rs @@ -34,18 +34,18 @@ fn click_count() { #[test] fn subkey_3() { let count = || 0; - let en = td!(Locale::en, subkeys.subkey_3, $ = count); + let en = td!(Locale::en, subkeys.subkey_3, count = count); assert_eq_rendered!(en, "zero"); - let fr = td!(Locale::fr, subkeys.subkey_3, $ = count); + let fr = td!(Locale::fr, subkeys.subkey_3, count = count); assert_eq_rendered!(fr, "0"); let count = || 1; - let en = td!(Locale::en, subkeys.subkey_3, $ = count); + let en = td!(Locale::en, subkeys.subkey_3, count = count); assert_eq_rendered!(en, "one"); - let fr = td!(Locale::fr, subkeys.subkey_3, $ = count); + let fr = td!(Locale::fr, subkeys.subkey_3, count = count); assert_eq_rendered!(fr, "1"); let count = || 3; - let en = td!(Locale::en, subkeys.subkey_3, $ = count); + let en = td!(Locale::en, subkeys.subkey_3, count = count); assert_eq_rendered!(en, "3"); - let fr = td!(Locale::fr, subkeys.subkey_3, $ = count); + let fr = td!(Locale::fr, subkeys.subkey_3, count = count); assert_eq_rendered!(fr, "3"); }