Skip to content

Commit

Permalink
Merge pull request #188 from sabify/num-grouping-strategy
Browse files Browse the repository at this point in the history
feat: add number formatter `GroupingStrategy` option
  • Loading branch information
Baptistemontan authored Feb 11, 2025
2 parents ccac31f + b223c2f commit 7f56909
Show file tree
Hide file tree
Showing 9 changed files with 263 additions and 180 deletions.
7 changes: 6 additions & 1 deletion docs/book/src/declare/08_formatters.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,12 @@ Enable the "format_nums" feature to use the number formatter.

### Arguments

There are no arguments for this formatter at the moment.
There is one argument at the moment for the number formatter: `grouping_strategy`, which is based on [`icu::decimal::options::GroupingStrategy`](https://docs.rs/icu_decimal/latest/icu_decimal/options/enum.GroupingStrategy.html), that can take 4 values:

- auto (default)
- never
- always
- min2

### Example

Expand Down
28 changes: 21 additions & 7 deletions leptos_i18n/src/macro_helpers/formatting/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,22 +37,33 @@ pub use time::*;
))]
use crate::Locale;
#[cfg(feature = "format_nums")]
use icu_decimal::options::FixedDecimalFormatterOptions;
#[cfg(feature = "format_nums")]
use icu_decimal::options::GroupingStrategy;
#[cfg(feature = "format_nums")]
use icu_decimal::FixedDecimalFormatter;
pub use leptos_i18n_macro::{
t_format, t_format_display, t_format_string, td_format, td_format_display, td_format_string,
tu_format, tu_format_display, tu_format_string,
};

#[cfg(feature = "format_nums")]
fn get_num_formatter<L: Locale>(locale: L) -> &'static FixedDecimalFormatter {
fn get_num_formatter<L: Locale>(
locale: L,
grouping_strategy: GroupingStrategy,
) -> &'static FixedDecimalFormatter {
use data_provider::IcuDataProvider;

let locale = locale.as_icu_locale();
inner::FORMATTERS.with_mut(|formatters| {
let num_formatter = formatters.num.entry(locale).or_insert_with(|| {
let locale = locale.as_icu_locale();
let num_formatters = formatters.num.entry(locale).or_default();
let num_formatter = num_formatters.entry(grouping_strategy).or_insert_with(|| {
let formatter = formatters
.provider
.try_new_num_formatter(&locale.into(), Default::default())
.try_new_num_formatter(
&locale.into(),
FixedDecimalFormatterOptions::from(grouping_strategy),
)
.expect("A FixedDecimalFormatter");
Box::leak(Box::new(formatter))
});
Expand Down Expand Up @@ -171,8 +182,10 @@ pub fn get_plural_rules<L: Locale>(
pub(crate) mod inner {
use super::*;
use icu_locid::Locale as IcuLocale;
use std::collections::HashMap;
use std::sync::{OnceLock, RwLock};
use std::{
collections::HashMap,
sync::{OnceLock, RwLock},
};

// Formatters cache
//
Expand Down Expand Up @@ -202,7 +215,8 @@ pub(crate) mod inner {
#[derive(Default)]
pub struct Formatters {
#[cfg(feature = "format_nums")]
pub num: HashMap<&'static IcuLocale, &'static FixedDecimalFormatter>,
pub num:
HashMap<&'static IcuLocale, HashMap<GroupingStrategy, &'static FixedDecimalFormatter>>,
#[cfg(feature = "format_datetime")]
pub date: HashMap<&'static IcuLocale, HashMap<length::Date, &'static DateFormatter>>,
#[cfg(feature = "format_datetime")]
Expand Down
10 changes: 7 additions & 3 deletions leptos_i18n/src/macro_helpers/formatting/nums.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use core::fmt::{self, Display};

use fixed_decimal::{FixedDecimal, FloatPrecision};
use icu_decimal::options::GroupingStrategy;
use leptos::IntoView;

use crate::Locale;
Expand Down Expand Up @@ -75,8 +76,9 @@ impl<T: IntoFixedDecimal, F: Fn() -> T + Clone + Send + Sync + 'static> NumberFo
pub fn format_number_to_view<L: Locale>(
locale: L,
number: impl NumberFormatterInputFn,
grouping_strategy: GroupingStrategy,
) -> impl IntoView + Clone {
let num_formatter = super::get_num_formatter(locale);
let num_formatter = super::get_num_formatter(locale, grouping_strategy);

move || {
let value = number.to_fixed_decimal();
Expand All @@ -89,8 +91,9 @@ pub fn format_number_to_formatter<L: Locale>(
f: &mut fmt::Formatter<'_>,
locale: L,
number: impl IntoFixedDecimal,
grouping_strategy: GroupingStrategy,
) -> fmt::Result {
let num_formatter = super::get_num_formatter(locale);
let num_formatter = super::get_num_formatter(locale, grouping_strategy);
let fixed_dec = number.to_fixed_decimal();
let formatted_num = num_formatter.format(&fixed_dec);
Display::fmt(&formatted_num, f)
Expand All @@ -104,8 +107,9 @@ pub fn format_number_to_formatter<L: Locale>(
pub fn format_number_to_display<L: Locale>(
locale: L,
number: impl IntoFixedDecimal,
grouping_strategy: GroupingStrategy,
) -> impl Display {
let num_formatter = super::get_num_formatter(locale);
let num_formatter = super::get_num_formatter(locale, grouping_strategy);
let fixed_dec = number.to_fixed_decimal();
num_formatter.format_to_string(&fixed_dec)
}
2 changes: 1 addition & 1 deletion leptos_i18n_build/src/datakey.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ pub fn find_used_datakey(keys: &BuildersKeysInner, used_icu_keys: &mut HashSet<O
for formatter in &var_infos.formatters {
let dk = match formatter {
Formatter::None => continue,
Formatter::Number => Options::FormatNums,
Formatter::Number(_) => Options::FormatNums,
Formatter::Date(_) | Formatter::Time(_) | Formatter::DateTime(_, _) => {
Options::FormatDateTime
}
Expand Down
84 changes: 60 additions & 24 deletions leptos_i18n_macro/src/utils/formatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,54 @@ use leptos_i18n_parser::utils::Key;
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};

macro_rules! impl_from {
($t: ident, $($variant:ident),*) => {
impl From<leptos_i18n_parser::utils::formatter::$t> for $t {
fn from(value: leptos_i18n_parser::utils::formatter::$t) -> Self {
match value {
$(
leptos_i18n_parser::utils::formatter::$t::$variant => Self::$variant,
)*
}
}
}
};
}

#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum GroupingStrategy {
Auto,
Never,
Always,
Min2,
}

impl ToTokens for GroupingStrategy {
fn to_token_stream(&self) -> TokenStream {
match self {
GroupingStrategy::Auto => {
quote!(l_i18n_crate::reexports::icu::decimal::options::GroupingStrategy::Auto)
}
GroupingStrategy::Never => {
quote!(l_i18n_crate::reexports::icu::decimal::options::GroupingStrategy::Never)
}
GroupingStrategy::Always => {
quote!(l_i18n_crate::reexports::icu::decimal::options::GroupingStrategy::Always)
}
GroupingStrategy::Min2 => {
quote!(l_i18n_crate::reexports::icu::decimal::options::GroupingStrategy::Min2)
}
}
}

fn to_tokens(&self, tokens: &mut TokenStream) {
let ts = Self::to_token_stream(self);
tokens.extend(ts);
}
}

impl_from!(GroupingStrategy, Auto, Never, Always, Min2);

#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum DateLength {
Full,
Expand All @@ -18,20 +66,6 @@ pub enum TimeLength {
Short,
}

macro_rules! impl_from {
($t: ident, $($variant:ident),*) => {
impl From<leptos_i18n_parser::utils::formatter::$t> for $t {
fn from(value: leptos_i18n_parser::utils::formatter::$t) -> Self {
match value {
$(
leptos_i18n_parser::utils::formatter::$t::$variant => Self::$variant,
)*
}
}
}
};
}

macro_rules! impl_length {
($t:ident, $name:ident) => {
impl ToTokens for $t {
Expand Down Expand Up @@ -120,7 +154,7 @@ impl_from!(ListStyle, Wide, Short, Narrow);
pub enum Formatter {
#[default]
None,
Number,
Number(GroupingStrategy),
Date(DateLength),
Time(TimeLength),
DateTime(DateLength, TimeLength),
Expand All @@ -131,7 +165,9 @@ impl From<leptos_i18n_parser::utils::formatter::Formatter> for Formatter {
fn from(value: leptos_i18n_parser::utils::formatter::Formatter) -> Self {
match value {
leptos_i18n_parser::utils::formatter::Formatter::None => Self::None,
leptos_i18n_parser::utils::formatter::Formatter::Number => Self::Number,
leptos_i18n_parser::utils::formatter::Formatter::Number(grouping_strategy) => {
Self::Number(grouping_strategy.into())
}
leptos_i18n_parser::utils::formatter::Formatter::Date(date_length) => {
Self::Date(date_length.into())
}
Expand All @@ -154,8 +190,8 @@ impl Formatter {
Formatter::None => {
quote!(#key)
}
Formatter::Number => {
quote!(l_i18n_crate::__private::format_number_to_view(#locale_field, #key))
Formatter::Number(grouping_strategy) => {
quote!(l_i18n_crate::__private::format_number_to_view(#locale_field, #key, #grouping_strategy))
}
Formatter::Date(length) => {
quote!(l_i18n_crate::__private::format_date_to_view(#locale_field, #key, #length))
Expand All @@ -177,8 +213,8 @@ impl Formatter {
Formatter::None => unreachable!(
"This function should not have been called on a variable with no formatter."
),
Formatter::Number => {
quote!(l_i18n_crate::__private::format_number_to_display(#locale_field, #key))
Formatter::Number(grouping_strategy) => {
quote!(l_i18n_crate::__private::format_number_to_display(#locale_field, #key, #grouping_strategy))
}
Formatter::Date(length) => {
quote!(l_i18n_crate::__private::format_date_to_display(#locale_field, #key, #length))
Expand All @@ -200,8 +236,8 @@ impl Formatter {
Formatter::None => {
quote!(core::fmt::Display::fmt(#key, __formatter))
}
Formatter::Number => {
quote!(l_i18n_crate::__private::format_number_to_formatter(__formatter, *#locale_field, core::clone::Clone::clone(#key)))
Formatter::Number(grouping_strategy) => {
quote!(l_i18n_crate::__private::format_number_to_formatter(__formatter, *#locale_field, core::clone::Clone::clone(#key), #grouping_strategy))
}
Formatter::Date(length) => {
quote!(l_i18n_crate::__private::format_date_to_formatter(__formatter, *#locale_field, #key, #length))
Expand All @@ -221,7 +257,7 @@ impl Formatter {
pub fn to_bound(self) -> TokenStream {
match self {
Formatter::None => quote!(l_i18n_crate::__private::InterpolateVar),
Formatter::Number => quote!(l_i18n_crate::__private::NumberFormatterInputFn),
Formatter::Number(_) => quote!(l_i18n_crate::__private::NumberFormatterInputFn),
Formatter::Date(_) => quote!(l_i18n_crate::__private::DateFormatterInputFn),
Formatter::Time(_) => quote!(l_i18n_crate::__private::TimeFormatterInputFn),
Formatter::DateTime(_, _) => quote!(l_i18n_crate::__private::DateTimeFormatterInputFn),
Expand All @@ -232,7 +268,7 @@ impl Formatter {
pub fn to_string_bound(self) -> TokenStream {
match self {
Formatter::None => quote!(::std::fmt::Display),
Formatter::Number => quote!(l_i18n_crate::__private::IntoFixedDecimal),
Formatter::Number(_) => quote!(l_i18n_crate::__private::IntoFixedDecimal),
Formatter::Date(_) => quote!(l_i18n_crate::__private::AsIcuDate),
Formatter::Time(_) => quote!(l_i18n_crate::__private::AsIcuTime),
Formatter::DateTime(_, _) => quote!(l_i18n_crate::__private::AsIcuDateTime),
Expand Down
27 changes: 23 additions & 4 deletions leptos_i18n_parser/src/utils/formatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,22 @@ impl Drop for SkipIcuCfgGuard {
pub enum Formatter {
#[default]
None,
Number,
Number(GroupingStrategy),
Date(DateLength),
Time(TimeLength),
DateTime(DateLength, TimeLength),
List(ListType, ListStyle),
}

#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum GroupingStrategy {
#[default]
Auto,
Never,
Always,
Min2,
}

#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum DateLength {
Full,
Expand Down Expand Up @@ -71,9 +80,9 @@ impl Formatter {
) -> Result<Option<Formatter>, Formatter> {
if name == "number" {
if cfg!(feature = "format_nums") || SKIP_ICU_CFG.get() {
Ok(Some(Formatter::Number))
Ok(Some(Formatter::Number(GroupingStrategy::from_args(args))))
} else {
Err(Formatter::Number)
Err(Formatter::Number(GroupingStrategy::from_args(args)))
}
} else if name == "datetime" {
let formatter =
Expand Down Expand Up @@ -112,7 +121,7 @@ impl Formatter {
pub fn err_message(&self) -> &'static str {
match self {
Formatter::None => "",
Formatter::Number => "Formatting numbers is not enabled, enable the \"format_nums\" feature to do so",
Formatter::Number(_) => "Formatting numbers is not enabled, enable the \"format_nums\" feature to do so",
Formatter::Date(_) => "Formatting dates is not enabled, enable the \"format_datetime\" feature to do so",
Formatter::Time(_) => "Formatting time is not enabled, enable the \"format_datetime\" feature to do so",
Formatter::DateTime(_, _) => "Formatting datetime is not enabled, enable the \"format_datetime\" feature to do so",
Expand Down Expand Up @@ -174,6 +183,16 @@ macro_rules! impl_length {
impl_length!(DateLength, "date_length", Date);
impl_length!(TimeLength, "time_length", Time);

impl GroupingStrategy {
impl_from_args! {
"grouping_strategy",
"auto" => Self::Auto,
"never" => Self::Never,
"always" => Self::Always,
"min2" => Self::Min2,
}
}

impl ListType {
impl_from_args! {
"list_type",
Expand Down
Loading

0 comments on commit 7f56909

Please sign in to comment.