Implement `Localize` for `FixedDecimal` and primitive number types

finchie
Aug 26, 2024, 7:35 AM
C6W7N6N57UCNHEV55HEZ3G7WN2ZOBGMFBB5M5ZPDB2HNNHHTOPBQC

Dependencies

  • [2] 7FYXVNAB Ignore comments in Fluent source code
  • [3] 3NMKD6I5 Refactor `Localize` trait to use `std::io::Write`
  • [4] AAERM7PB Add selector tests for the `fr` locale
  • [5] F5LG7WEN Emit compilation errors from Fluent source code
  • [6] 6ABVDTXZ Improve `fluent_embed_derive` test suite
  • [7] XEEXWJLG Add simple end-to-end test for selectors
  • [8] 5TEX4MNU Split `fluent_embed` into `group` and `parse` modules
  • [9] QFPQZR4K Refactor `fluent_embed`
  • [10] NO3PDO7P Refactor `fluent_embed` to support structs
  • [*] 2SITVDYW Handle common errors in Fluent code
  • [*] O77KA6C4 Create `fluent_embed` crate
  • [*] KZLFC7OW Rename `fluent_embed_runtime` to `fluent_embed`
  • [*] HHJDRLLN Create `fluent_embed_runtime` crate
  • [*] BFL2Y7GN Add relative timestamps using `jiff` and `icu_relativetime`
  • [*] UKFEFT6L Create basic `Output` proc-macro
  • [*] VNSHGQYN Support using glob paths in `localize` macro
  • [*] ROSR4HD5 Parse captured glob as locale

Change contents

  • replacement in fluent_embed_derive/tests/selectors.rs at line 14
    [2.130][4.124:181](),[4.124][4.124:181]()
    fn expected_message(&self, locale: &str) -> String {
    [2.130]
    [4.181]
    fn expected_plural(&self, locale: &str) -> String {
  • replacement in fluent_embed_derive/tests/selectors.rs at line 18
    [4.312][4.312:562]()
    // 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."),
    [4.312]
    [4.562]
    0..=999 => unread_emails.to_string(),
    &u64::MAX => "18,446,744,073,709,551,615".to_string(),
    _ => unreachable!(),
  • replacement in fluent_embed_derive/tests/selectors.rs at line 23
    [4.627][4.627:887]()
    // 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."),
    [4.627]
    [4.887]
    0..=999 => unread_emails.to_string(),
    &u64::MAX => "18 446 744 073 709 551 615".to_string(),
    _ => unreachable!(),
  • edit in fluent_embed_derive/tests/selectors.rs at line 29
    [4.958]
    [4.958]
    }
    }
    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!(),
    }
    }
  • replacement in fluent_embed_derive/tests/selectors.rs at line 67
    [3.33][3.33:89]()
    data.message_for_locale(&mut buffer, &language_id);
    [3.33]
    [3.89]
    data.message_for_locale(&mut buffer, &language_id).unwrap();
  • edit in fluent_embed_derive/src/fluent/ast.rs at line 6
    [4.2217]
    [12.5591]
    use fixed_decimal::{FixedDecimal, FloatPrecision};
  • replacement in fluent_embed_derive/src/fluent/ast.rs at line 62
    [4.3664][3.2705:2782]()
    match plural_rules.category_for(#match_target) {
    [4.3664]
    [3.2782]
    match plural_rules.category_for(*#match_target) {
  • replacement in fluent_embed_derive/src/fluent/ast.rs at line 70
    [3.2973][3.2973:3042]()
    parse_quote!(writer.write_all(#expression)?)
    [3.2973]
    [4.3995]
    parse_quote!(#expression.message_for_locale(writer, locale)?)
  • replacement in fluent_embed_derive/src/fluent/ast.rs at line 94
    [4.2001][4.2001:2330]()
    // 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)
    [4.2001]
    [4.2330]
    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())
  • replacement in fluent_embed_derive/src/fluent/ast.rs at line 141
    [4.6063][4.3783:3850](),[4.9149][4.3783:3850](),[4.3783][4.3783:3850](),[4.3850][3.3639:3723]()
    ReferenceKind::EnumField => parse_quote!(*#ident),
    ReferenceKind::StructField => parse_quote!(message_context.#ident),
    [4.9149]
    [4.3923]
    ReferenceKind::EnumField => parse_quote!(#ident),
    ReferenceKind::StructField => parse_quote!(self.#ident),
  • edit in fluent_embed_derive/Cargo.toml at line 13
    [13.1410]
    [4.9101]
    fixed_decimal = { version = "0.5.6", features = ["ryu"] }
  • edit in fluent_embed_derive/Cargo.toml at line 27
    [14.820]
    [4.411]
    icu_decimal = "1.5.0"
    icu_provider = "1.5.0"
  • edit in fluent_embed/src/lib.rs at line 8
    [15.269]
    [15.269]
    pub use fixed_decimal::{FixedDecimal, FloatPrecision};
  • edit in fluent_embed/src/lib.rs at line 14
    [16.4362]
    [16.4362]
    pub mod decimal;
  • file addition: decimal.rs (----------)
    [15.49]
    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(())
    }
    }
  • edit in fluent_embed/Cargo.toml at line 10
    [15.1196]
    [16.4377]
    duplicate = "1.0.0"
  • edit in fluent_embed/Cargo.toml at line 13
    [3.5369]
    [16.4435]
    icu_decimal = "1.5.0"
  • edit in Cargo.lock at line 121
    [18.1773]
    [18.1773]
    name = "duplicate"
    version = "1.0.0"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "de78e66ac9061e030587b2a2e75cc88f22304913c907b11307bca737141230cb"
    dependencies = [
    "heck",
    "proc-macro-error",
    ]
    [[package]]
  • edit in Cargo.lock at line 187
    [13.1925]
    [14.1075]
    "duplicate",
  • edit in Cargo.lock at line 190
    [14.1117]
    [14.1117]
    "icu_decimal",
  • edit in Cargo.lock at line 205
    [14.1291]
    [13.1925]
    "fixed_decimal",
  • edit in Cargo.lock at line 209
    [13.1952]
    [19.575]
    "icu_decimal",
  • edit in Cargo.lock at line 211
    [19.589]
    [4.15009]
    "icu_provider",