Simple implementation that wraps a call to icu_decimal::FixedDecimalFormatter
, along with implementations for primitive number types such as isize
, u32
, f64
, etc.
C6W7N6N57UCNHEV55HEZ3G7WN2ZOBGMFBB5M5ZPDB2HNNHHTOPBQC
7FYXVNAB6JAP3CJKE4MY57UWYSUPEXFVER6K264BSKYHVU6V4SGQC
3NMKD6I57ONAGHEN4PZIAV2KPYESVR4JL3DTWSHXKCMVJBEQ4GIQC
AAERM7PBDVDFDEEXZ7UJ5WWQF7SPEJQQYXRBZ63ETB5YAVIECHMAC
XEEXWJLGVIPIGURSDU4ETZMGAIFTFDPECM4QWFOSRHU7GMGVOUVQC
F5LG7WENUUDRSCTDMA4M6BAC5RWTGQO45C4ZEBZDX6FHCTTHBVGQC
2SITVDYW6KANM24QXRHVSBL6S77UHKJLOSOHSUZQBJFL5NAAGQYAC
5TEX4MNUC4LDDRMNEOVCFNUUEZAGUXMKO3OIEQFXWRQKXSHY2NRQC
NO3PDO7PY7J3WPADNCS5VD6HKFY63E23I3SDR4DHXNVQJTG27RAAC
QFPQZR4K4UZ7R2GQZJG4NYBGVQJVL2ANIKGGTOHAMIRIBQHPSQGAC
O77KA6C4UJGZXVGPEA7WCRQH6XYQJPWETSPDXI3VOKOSRQND7JEQC
KZLFC7OWYNK3G5YNHRANUK3VUVCM6W6J34N7UABYA24XMZWAVVHQC
6ABVDTXZOHVUDZDKDQS256F74LFIMM5DO3OZWHKRXZBUTPII4WAQC
HHJDRLLNN36UNIA7STAXEEVBCEMPJNB7SJQOS3TJLLYN4AEZ4MHQC
BFL2Y7GN6NBXXNAUSD4M6T6CIVQ2OLERPE2CAFSLRF377WFFTVCQC
VNSHGQYNPGKGGPYNVP4Z2RWD7JCSDJVYAADD6UXWBYL6ZRXKLE4AC
UKFEFT6LSI4K7X6UHQFZYD52DILKXMZMYSO2UYS2FCHNPXIF4BEQC
ROSR4HD5ENPQU3HH5IVYSOA5YM72W77CHVQARSD3T67BUNYG7KZQC
// Only 1 is singular
1 => format!("You have {unread_emails} unread email."),
// Everything else is plural
0 | 2.. => format!("You have {unread_emails} unread emails."),
0..=999 => unread_emails.to_string(),
&u64::MAX => "18,446,744,073,709,551,615".to_string(),
_ => unreachable!(),
// Both 0 & 1 are singular
0 | 1 => format!("Vous avez {unread_emails} e-mail non lu."),
// Everything else is plural
2.. => format!("Vous avez {unread_emails} e-mails non lus."),
0..=999 => unread_emails.to_string(),
&u64::MAX => "18 446 744 073 709 551 615".to_string(),
_ => unreachable!(),
}
}
fn expected_message(&self, locale: &str) -> String {
match self {
Self::Emails { unread_emails } => {
let expected_plural = self.expected_plural(locale);
match locale {
"en-US" => match unread_emails {
// Only 1 is singular
1 => "You have 1 unread email.".to_string(),
// Everything else is plural
0 | 2.. => format!("You have {expected_plural} unread emails."),
},
"fr" => match unread_emails {
// Both 0 & 1 are singular
0 | 1 => format!("Vous avez {expected_plural} e-mail non lu."),
// Everything else is plural
2.. => format!("Vous avez {expected_plural} e-mails non lus."),
},
_ => unreachable!(),
}
}
// FIXME: i128 is "good enough" for now but an incorrect representation!
// e.g. decimals not supported
let parsed_value = i128::from_str_radix(value, 10).unwrap();
let number_literal = proc_macro2::Literal::i128_unsuffixed(parsed_value);
parse_quote!(#number_literal)
let parsed_value: f64 = value.parse().unwrap();
// Check validity at compile-time, so we avoid generating code that will break at runtime
assert!(FixedDecimal::try_from_f64(parsed_value, FloatPrecision::Floating).is_ok());
let float_literal = proc_macro2::Literal::f64_suffixed(parsed_value);
parse_quote!(::fluent_embed::FixedDecimal::try_from_f64(#float_literal, ::fluent_embed::FloatPrecision::Floating).unwrap())
ReferenceKind::EnumField => parse_quote!(*#ident),
ReferenceKind::StructField => parse_quote!(message_context.#ident),
ReferenceKind::EnumField => parse_quote!(#ident),
ReferenceKind::StructField => parse_quote!(self.#ident),
use crate::Localize;
use duplicate::duplicate_item;
use fixed_decimal::{FixedDecimal, FloatPrecision};
use icu_decimal::{options::FixedDecimalFormatterOptions, FixedDecimalFormatter};
use icu_locid::{langid, LanguageIdentifier};
use icu_provider::DataLocale;
use writeable::Writeable;
// Implementations of `Localize` for all primitive number types
// These just convert to a `FixedDecimal` and then use its implementation
#[duplicate_item(
type_name conversion;
[u8] [FixedDecimal::from(*self)];
[u16] [FixedDecimal::from(*self)];
[u32] [FixedDecimal::from(*self)];
[u64] [FixedDecimal::from(*self)];
[u128] [FixedDecimal::from(*self)];
[usize] [FixedDecimal::from(*self)];
[i8] [FixedDecimal::from(*self)];
[i16] [FixedDecimal::from(*self)];
[i32] [FixedDecimal::from(*self)];
[i64] [FixedDecimal::from(*self)];
[i128] [FixedDecimal::from(*self)];
[isize] [FixedDecimal::from(*self)];
[f32] [FixedDecimal::try_from_f64(*self as f64, FloatPrecision::Floating).unwrap()];
[f64] [FixedDecimal::try_from_f64(*self, FloatPrecision::Floating).unwrap()];
)]
impl<W: std::io::Write> Localize<W> for type_name {
const CANONICAL_LOCALE: LanguageIdentifier = langid!("en-US");
fn available_locales(&self) -> Vec<LanguageIdentifier> {
// TODO: keep track of all locales with Fluent data, and return only those
vec![<Self as Localize<W>>::CANONICAL_LOCALE]
}
fn message_for_locale(
&self,
writer: &mut W,
locale: &LanguageIdentifier,
) -> Result<(), crate::LocalizationError> {
let fixed_decimal = conversion;
fixed_decimal.message_for_locale(writer, locale)
}
}
impl<W: std::io::Write> Localize<W> for FixedDecimal {
const CANONICAL_LOCALE: LanguageIdentifier = langid!("en-US");
fn available_locales(&self) -> Vec<LanguageIdentifier> {
// TODO: keep track of all locales with Fluent data, and return only those
vec![<Self as Localize<W>>::CANONICAL_LOCALE]
}
fn message_for_locale(
&self,
writer: &mut W,
locale: &LanguageIdentifier,
) -> Result<(), crate::LocalizationError> {
let data_locale = DataLocale::from(locale);
let formatter =
FixedDecimalFormatter::try_new(&data_locale, FixedDecimalFormatterOptions::default())
.unwrap();
let formatted_decimal = formatter.format(self);
writer.write_all(formatted_decimal.write_to_string().as_bytes())?;
Ok(())
}
}