Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add currency formatter #190

Merged
merged 4 commits into from
Feb 12, 2025
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ icu_calendar = { version = "1.5", default-features = false }
icu_list = { version = "1.5", default-features = false }
icu_decimal = { version = "1.5", default-features = false }
icu_locid_transform = { version = "1.5", default-features = false }
icu_experimental = { version = "0.1.0", default_features = false }
tinystr = "0.7.6"

# internal use
tests_common = { path = "./tests/common", version = "0.1.0" }
4 changes: 4 additions & 0 deletions docs/book/src/06_features.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,7 @@ Allow the use of the `list` formatter.
#### `format_nums`

Allow the use of the `number` formatter.

#### `format_currency`

Allow the use of the `currency` formatter.
36 changes: 36 additions & 0 deletions docs/book/src/declare/08_formatters.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,42 @@ let num = move || 100_000;
t!(i18n, number_formatter, num);
```

## Currency (experimental)

```json
{
"currency_formatter": "{{ num, currency }}"
}
```

Will format the currency based on the locale.
The variable should be the same as [number](#number).

Enable the "format_currency" feature to use the number formatter.

### Arguments

There are two arguments at the moment for the currency formatter: `width` and `country_code`, which are based on [`icu_experimental::dimension::currency::options::Width`](https://docs.rs/icu_experimental/0.1.0/icu_experimental/dimension/currency/options/enum.Width.html) and [`icu_experimental::dimension::currency::formatter::CountryCode`](https://docs.rs/icu_experimental/0.1.0/icu_experimental/dimension/currency/formatter/struct.CurrencyCode.html).

`width` values:

- short (default)
- narrow

`country_code` value should be a [currency code](https://www.iban.com/currency-codes), such as USD or EUR. The USD is the default value.

### Example

```rust,ignore
use crate::i18n::*;

let i18n = use_i18n();

let num = move || 100_000;

t!(i18n, currency_formatter, num);
```

## Date

```json
Expand Down
18 changes: 17 additions & 1 deletion leptos_i18n/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ icu_list = { workspace = true, optional = true }
icu_decimal = { workspace = true, optional = true }
typed-builder = "0.20"
fixed_decimal = { workspace = true, optional = true, features = ["ryu"] }
icu_experimental = { workspace = true, optional = true, features = ["ryu"] }
writeable = "0.5"
serde = "1.0"
async-once-cell = { version = "0.5.3", optional = true }
Expand All @@ -36,6 +37,7 @@ serde-wasm-bindgen = { version = "0.6.5", optional = true }
futures = { version = "0.3.30", optional = true }
default-struct-builder = "0.5"
wasm-bindgen = "0.2.96"
tinystr = { workspace = true }

[features]
default = ["cookie", "json_files", "icu_compiled_data"]
Expand All @@ -46,6 +48,7 @@ icu_compiled_data = [
"icu_calendar?/compiled_data",
"icu_list?/compiled_data",
"icu_decimal?/compiled_data",
"icu_experimental?/compiled_data",
"leptos_i18n_macro/icu_compiled_data",
]
plurals = ["dep:icu_plurals", "dep:icu_provider", "leptos_i18n_macro/plurals"]
Expand All @@ -66,6 +69,12 @@ format_nums = [
"dep:icu_provider",
"leptos_i18n_macro/format_nums",
]
format_currency = [
"format_nums",
"dep:icu_experimental",
"dep:icu_provider",
"leptos_i18n_macro/format_currency",
]
actix = ["ssr", "leptos-use/actix"]
axum = ["ssr", "leptos-use/axum"]
hydrate = [
Expand Down Expand Up @@ -102,7 +111,13 @@ track_locale_files = ["leptos_i18n_macro/track_locale_files"]

[package.metadata."docs.rs"]
# Features needed for the doctests
features = ["plurals", "format_datetime", "format_list", "format_nums"]
features = [
"plurals",
"format_datetime",
"format_list",
"format_nums",
"format_currency",
]


[package.metadata.cargo-all-features]
Expand Down Expand Up @@ -163,4 +178,5 @@ always_include_features = [
"format_datetime",
"format_list",
"format_nums",
"format_currency",
]
9 changes: 7 additions & 2 deletions leptos_i18n/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,15 +171,18 @@ pub mod __private {

/// This module contain utilities to create custom ICU providers.
pub mod custom_provider {
pub use crate::macro_helpers::formatting::data_provider::IcuDataProvider;
pub use crate::macro_helpers::formatting::inner::set_icu_data_provider;
pub use crate::macro_helpers::formatting::{
data_provider::IcuDataProvider, inner::set_icu_data_provider,
};
pub use leptos_i18n_macro::IcuDataProvider;
}

/// Reexports of backend libraries, mostly about formatting.
pub mod reexports {
#[cfg(feature = "format_nums")]
pub use fixed_decimal;
#[cfg(feature = "format_currency")]
pub use tinystr::tinystr;

/// module containing reexports of crates from the icu project
pub mod icu {
Expand All @@ -189,6 +192,8 @@ pub mod reexports {
pub use icu_datetime as datetime;
#[cfg(feature = "format_nums")]
pub use icu_decimal as decimal;
#[cfg(feature = "format_currency")]
pub use icu_experimental::dimension::currency;
#[cfg(feature = "format_list")]
pub use icu_list as list;
#[cfg(feature = "plurals")]
Expand Down
91 changes: 91 additions & 0 deletions leptos_i18n/src/macro_helpers/formatting/currency.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
use super::{IntoFixedDecimal, NumberFormatterInputFn};
use crate::Locale;
use core::fmt::{self, Display};
use icu_experimental::dimension::currency::{
formatter::CurrencyCode, options::Width as CurrencyWidth,
};
use leptos::IntoView;

use serde::{Deserialize, Serialize};
use writeable::Writeable;

// TODO: this struct should be removed in version ICU4x v2
// Reference: https://docs.rs/icu_experimental/0.1.0/icu_experimental/dimension/currency/options/enum.Width.html
// Issue: https://github.com/unicode-org/icu4x/pull/6100
#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash, Serialize, Deserialize)]
#[non_exhaustive]
#[doc(hidden)]
pub enum Width {
#[serde(rename = "short")]
Short,

#[serde(rename = "narrow")]
Narrow,
}

impl Default for Width {
fn default() -> Self {
Self::Short
}
}

impl From<CurrencyWidth> for Width {
fn from(value: CurrencyWidth) -> Self {
match value {
CurrencyWidth::Short => Self::Short,
CurrencyWidth::Narrow => Self::Narrow,
_ => unimplemented!(),
}
}
}

#[doc(hidden)]
pub fn format_currency_to_view<L: Locale>(
locale: L,
number: impl NumberFormatterInputFn,
width: CurrencyWidth,
currency_code: CurrencyCode,
) -> impl IntoView + Clone {
let currency_formatter = super::get_currency_formatter(locale, width);

move || {
let fixed_dec = number.to_fixed_decimal();
let currency = currency_formatter.format_fixed_decimal(&fixed_dec, currency_code);
let mut formatted_currency = String::new();
currency.write_to(&mut formatted_currency).unwrap();
formatted_currency
}
}

#[doc(hidden)]
pub fn format_currency_to_formatter<L: Locale>(
f: &mut fmt::Formatter<'_>,
locale: L,
number: impl IntoFixedDecimal,
width: CurrencyWidth,
currency_code: CurrencyCode,
) -> fmt::Result {
let currency_formatter = super::get_currency_formatter(locale, width);
let fixed_dec = number.to_fixed_decimal();
let formatted_currency = currency_formatter.format_fixed_decimal(&fixed_dec, currency_code);
formatted_currency.write_to(f)
}

/// This function is a lie.
/// The only reason it exist is for the `format` macros.
/// It does NOT return a `impl Display` struct with no allocation like the other
/// This directly return a `String` of the formatted num, because borrow issues.
#[doc(hidden)]
pub fn format_currency_to_display<L: Locale>(
locale: L,
number: impl IntoFixedDecimal,
width: CurrencyWidth,
currency_code: CurrencyCode,
) -> impl Display {
let currency_formatter = super::get_currency_formatter(locale, width);
let fixed_dec = number.to_fixed_decimal();
let currency = currency_formatter.format_fixed_decimal(&fixed_dec, currency_code);
let mut formatted_currency = String::new();
currency.write_to(&mut formatted_currency).unwrap();
formatted_currency
}
Loading