first commit
This commit is contained in:
119
docs/localization/i18n/i18n-formatter-currency/README.md
Normal file
119
docs/localization/i18n/i18n-formatter-currency/README.md
Normal file
@@ -0,0 +1,119 @@
|
||||
# @suaveplan/i18n-formatter-currency - Locale-aware currency formatting backed by Intl.NumberFormat({ style: 'currency' }). Class wrapper + standalone helper + currency-info helpers (getCurrencySymbol, getCurrencyName, getCurrencyInfo), Zod-validated ISO 4217 codes, per-(locale, currency, options-hash) cache, OTel histogram + cache-hit counter. Wave 3 of the @suaveplan/i18n modular split.
|
||||
|
||||
## Summary
|
||||
|
||||
Locale-aware currency formatting backed by `Intl.NumberFormat` with
|
||||
`style: "currency"`. Wave 3 of the 14-package i18n modular split.
|
||||
Provides both a class (`CurrencyFormatter`) and a standalone helper
|
||||
(`formatCurrency`) plus value-only formatting (`formatCurrencyValue`)
|
||||
and currency-info helpers (`getCurrencySymbol`, `getCurrencyName`,
|
||||
`getCurrencyInfo`) on top of a per-`(locale, currency, options-hash)`
|
||||
cache, Zod-validated ISO 4217 codes, and the canonical OTel histogram
|
||||
|
||||
+ cache-hit counter that every Wave 3 formatter package emits.
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
## Documentation tree
|
||||
|
||||
- [formatter](./src/formatter/formatter.md) — Locale-aware currency formatting backed by `Intl.NumberFormat({ style: "currency" })`. The package exposes both a class…
|
||||
- [formatter](./src/formatter/formatter.md) — Locale-aware currency formatting backed by `Intl.NumberFormat({ style: "currency" })`. The package exposes both a class…
|
||||
- [helpers](./src/helpers/helpers.md) — Currency-info helpers exposing structured metadata about ISO 4217 codes via `Intl.NumberFormat.formatToParts`. Use thes…
|
||||
- [helpers](./src/helpers/helpers.md) — Currency-info helpers exposing structured metadata about ISO 4217 codes via `Intl.NumberFormat.formatToParts`. Use thes…
|
||||
- [observability](./src/observability/observability.md) — Per-package OpenTelemetry instruments for `@suaveplan/i18n-formatter-currency`. Exposes a histogram (`i18n.format.curre…
|
||||
- [observability](./src/observability/observability.md) — Per-package OpenTelemetry instruments for `@suaveplan/i18n-formatter-currency`. Exposes a histogram (`i18n.format.curre…
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Why this exists
|
||||
|
||||
Each `Intl.*` wrapper lives in its own sibling package so consumers
|
||||
can pull only the formatters they actually use. This package is the
|
||||
only Wave 3 formatter with an intra-formatter dependency: it depends
|
||||
on `@suaveplan/i18n-formatter-number workspace:^` because currency
|
||||
formatting is `Intl.NumberFormat({ style: "currency" })`. The dep is
|
||||
pinned in `package.json` per the design doc §3 so the slim core
|
||||
(Wave 6) can wire `formatCurrency` and `formatNumber` consistently
|
||||
into the default `FormatBridge`.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
bun add @suaveplan/i18n-formatter-currency
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```ts
|
||||
import {
|
||||
CurrencyFormatter,
|
||||
formatCurrency,
|
||||
formatCurrencyValue,
|
||||
getCurrencyInfo,
|
||||
getCurrencyName,
|
||||
getCurrencySymbol,
|
||||
} from "@suaveplan/i18n-formatter-currency";
|
||||
|
||||
formatCurrency(1234.56, "USD", "en"); // → "$1,234.56"
|
||||
formatCurrency(1234.56, "EUR", "de"); // → "1.234,56 €"
|
||||
formatCurrency(100, "CHF", "en", { display: "code" }); // → "CHF 100.00"
|
||||
|
||||
formatCurrencyValue(9.99, "USD", "en"); // → "9.99"
|
||||
|
||||
getCurrencySymbol("GBP", "en"); // → "£"
|
||||
getCurrencyName("USD", "fr"); // → "dollar des États-Unis"
|
||||
getCurrencyInfo("EUR", "en-US");
|
||||
// → { code: "EUR", symbol: "€", name: "Euro", decimalDigits: 2 }
|
||||
|
||||
const usd = new CurrencyFormatter("USD", { minimumFractionDigits: 2 });
|
||||
usd.format(42, "en-US"); // → "$42.00"
|
||||
```
|
||||
|
||||
## Modules
|
||||
|
||||
| Subpath | Description |
|
||||
|---|---|
|
||||
| `@suaveplan/i18n-formatter-currency` | Root barrel — class + standalone + helpers + observability |
|
||||
| `@suaveplan/i18n-formatter-currency/formatter` | `CurrencyFormatter`, `formatCurrency`, `formatCurrencyValue`, types, schemas |
|
||||
| `@suaveplan/i18n-formatter-currency/helpers` | `getCurrencySymbol`, `getCurrencyName`, `getCurrencyInfo` only |
|
||||
|
||||
## Modules Documentation
|
||||
|
||||
+ [formatter.md](src/formatter.md) — `CurrencyFormatter`, `formatCurrency`, `formatCurrencyValue`
|
||||
+ [helpers.md](src/helpers.md) — `getCurrencySymbol`, `getCurrencyName`, `getCurrencyInfo`
|
||||
+ [observability.md](src/observability.md) — OTel histogram + cache-hit counter
|
||||
|
||||
## Public Surface (snapshot)
|
||||
|
||||
| Export | Kind | Origin |
|
||||
|---|---|---|
|
||||
| `CurrencyFormatter` | class | `formatter.ts` |
|
||||
| `formatCurrency` | function | `formatter.ts` |
|
||||
| `formatCurrencyValue` | function | `formatter.ts` |
|
||||
| `clearCurrencyFormatterCache` | function | `formatter.ts` |
|
||||
| `toBcp47` | function | `formatter.ts` |
|
||||
| `CurrencyCodeSchema` | const | `formatter.schemas.ts` |
|
||||
| `CurrencyFormatterOptionsSchema` | const | `formatter.schemas.ts` |
|
||||
| `CommonCurrency` | type | `formatter.types.ts` |
|
||||
| `CurrencyDisplay` | type | `formatter.types.ts` |
|
||||
| `CurrencyFormatterOptions` | type | `formatter.types.ts` |
|
||||
| `CurrencyInfo` | type | `formatter.types.ts` |
|
||||
| `CurrencyFormatterCacheKey` | type | `formatter.types.ts` |
|
||||
| `getCurrencySymbol` | function | `helpers.ts` |
|
||||
| `getCurrencyName` | function | `helpers.ts` |
|
||||
| `getCurrencyInfo` | function | `helpers.ts` |
|
||||
| `recordCurrencyFormat` | function | `observability.ts` |
|
||||
| `recordCurrencyCacheHit` | function | `observability.ts` |
|
||||
| `getCurrencyFormatMetricsState` | function | `observability.ts` |
|
||||
| `resetCurrencyFormatMetricsState` | function | `observability.ts` |
|
||||
|
||||
## Versioning
|
||||
|
||||
This package is part of the i18n modular-split rollout. Stays at
|
||||
`0.1.0` until the slim core (Wave 6) cuts the family `1.0.0` release.
|
||||
|
||||
## Wave 3 Scope
|
||||
|
||||
This package is one of eight Wave 3 formatter packages and the only
|
||||
Wave 3 formatter with an intra-formatter dependency. Pure addition —
|
||||
`packages/i18n/i18n/src/formatting/currency/` is preserved in the
|
||||
monolith until Wave 6 deletes it.
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
# CurrencyFormatter
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Locale-aware currency formatting backed by `Intl.NumberFormat({ style: "currency" })`. The package exposes both a class (`CurrencyFormatter` — construct once with stable currency + options, reuse) and a standalone helper (`formatCurrency(amount, currency, locale, options?)`). Every call validates the currency code through `CurrencyCodeSchema` (Zod) at the boundary (rejecting bogus codes like `"$"`, `"abc"`, `"USDT"` with a structured `INVALID_FORMATTER_OPTIONS` rather than letting them surface as raw `RangeError` from `Intl.NumberFormat`), validates options through `CurrencyFormatterOptionsSchema`, and emits a wall-clock duration sample on the `i18n.format.currency.duration_ms` histogram tagged with `locale`, `currency`, and `display`.
|
||||
|
||||
This package is the only Wave 3 formatter that depends on a sibling formatter — it declares `@suaveplan/i18n-formatter-number workspace:^` because currency formatting is `Intl.NumberFormat` with `style: "currency"`. The dep is documentation-grade today (the implementation constructs its own `Intl.NumberFormat`) but pins the architectural relationship the slim core (Wave 6) will exercise when wiring the default `FormatBridge`.
|
||||
|
||||
A per-`(locale, currency, options-hash)` cache for `Intl.NumberFormat` instances amortises construction cost across many price renders. Cache reuse increments the `i18n.format.cache_hit{formatter="currency"}` counter.
|
||||
|
||||
## Features
|
||||
|
||||
- Class form (`CurrencyFormatter`) for fixed-currency reuse + standalone helper (`formatCurrency`) for one-off calls
|
||||
- `formatCurrencyValue` returns the numeric portion only (no symbol) — useful for custom rendering layouts
|
||||
- BCP-47 locale resolution via the shared `LOCALE_BCP47_MAP` (re-exported by `@suaveplan/i18n-shared`)
|
||||
- Display modes: `"symbol"` (default), `"code"`, `"name"`, `"narrowSymbol"`
|
||||
- ISO 4217 validation enforced by Zod (`/^[A-Z]{3}$/`)
|
||||
- `minimumFractionDigits` / `maximumFractionDigits` Zod-validated; inverted pair rejected as `field === 'fractionDigits'`
|
||||
- Stable error codes: `I18N.INVALID_FORMATTER_OPTIONS`, `I18N.UNSUPPORTED_LOCALE`
|
||||
- Process-wide cache; `clearCurrencyFormatterCache()` for tests
|
||||
- OTel instrumentation: `i18n.format.currency.duration_ms` histogram, `i18n.format.cache_hit{formatter="currency"}` counter
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```typescript
|
||||
import {
|
||||
CurrencyFormatter,
|
||||
formatCurrency,
|
||||
formatCurrencyValue,
|
||||
} from "@suaveplan/i18n-formatter-currency";
|
||||
|
||||
formatCurrency(1234.56, "USD", "en");
|
||||
// → "$1,234.56"
|
||||
|
||||
formatCurrency(1234.56, "EUR", "de");
|
||||
// → "1.234,56 €"
|
||||
|
||||
formatCurrency(100, "CHF", "en", { display: "code" });
|
||||
// → "CHF 100.00"
|
||||
|
||||
formatCurrencyValue(1234.56, "USD", "en");
|
||||
// → "1,234.56"
|
||||
```
|
||||
|
||||
## Usage: Advanced Example — Class Form for Repeated Use
|
||||
|
||||
```typescript
|
||||
import { CurrencyFormatter } from "@suaveplan/i18n-formatter-currency";
|
||||
|
||||
const usd = new CurrencyFormatter("USD", {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
});
|
||||
|
||||
// Many call sites, single options + currency validation:
|
||||
const lines = lineItems.map((item) => ({
|
||||
label: item.label,
|
||||
amount: usd.format(item.price, "en-US"),
|
||||
}));
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `formatCurrency(amount, currency, locale, options?): string`
|
||||
|
||||
Formats `amount` per `currency`, `locale`, and `options`. Validates currency code through Zod (must match `/^[A-Z]{3}$/`), validates options through Zod, throws `I18nError(I18N.INVALID_FORMATTER_OPTIONS)` on either failure. Maps `Intl.NumberFormat`'s `RangeError` for unsupported locales / currencies to `I18nError(I18N.UNSUPPORTED_LOCALE)`.
|
||||
|
||||
### `formatCurrencyValue(amount, currency, locale): string`
|
||||
|
||||
Returns the numeric portion of the formatted currency string with the currency symbol and literal whitespace separators stripped via `formatToParts`. Reuses the same per-(locale, currency, options-hash) cache as `formatCurrency`.
|
||||
|
||||
### `class CurrencyFormatter`
|
||||
|
||||
- `constructor(currency: string, options?: CurrencyFormatterOptions)` — validates currency code + options at construction
|
||||
- `format(amount: number, locale: string): string` — formats with stored currency + options
|
||||
|
||||
### `clearCurrencyFormatterCache(): void`
|
||||
|
||||
Test-only.
|
||||
|
||||
### `toBcp47(locale): string`
|
||||
|
||||
Internal BCP-47 normaliser exposed for re-use; consults `LOCALE_BCP47_MAP` from `@suaveplan/i18n-shared` for known short codes (`"en"` → `"en-GB"`), tries the lowercase fallback, then passes through unchanged.
|
||||
|
||||
### `CurrencyCodeSchema` and `CurrencyFormatterOptionsSchema`
|
||||
|
||||
Zod schemas validating the ISO 4217 code and options surface respectively.
|
||||
|
||||
### Types
|
||||
|
||||
`CommonCurrency`, `CurrencyDisplay`, `CurrencyFormatterOptions`, `CurrencyInfo`, `CurrencyFormatterCacheKey`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The cache key is `${locale}|${currency}|${JSON.stringify(options)}`. Including the currency in the key (rather than rolling it into the options bag) keeps `formatCurrencyValue` cache-coherent with `formatCurrency` for the same `(locale, currency)` pair under default options — both call sites resolve to the same `Intl.NumberFormat` instance.
|
||||
|
||||
`formatCurrencyValue` filters `formatToParts` for non-`"currency"` and non-`"literal"` parts. This correctly handles locales that place the symbol after the number (e.g. some European locales) without regex hacks. The `.trim()` at the end normalises any leading/trailing whitespace produced by edge-case locales.
|
||||
|
||||
ISO 4217 validation is intentionally strict — `/^[A-Z]{3}$/` rejects every code that is not exactly three uppercase letters. Cryptocurrencies like `BTC` (3 letters, all upper) pass the regex but fail at `Intl.NumberFormat` level if the runtime ICU does not recognise them; both failure modes funnel into structured `I18nError` codes (`INVALID_FORMATTER_OPTIONS` for the regex case, `UNSUPPORTED_LOCALE` for the ICU case). For non-ISO-4217 cryptocurrencies the dedicated `@suaveplan/cryptocurrency` package owns formatting.
|
||||
@@ -0,0 +1,74 @@
|
||||
# Currency Helper Functions
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Currency-info helpers exposing structured metadata about ISO 4217 codes via `Intl.NumberFormat.formatToParts`. Use these when you need just the symbol, the localised name, or the full record (`{ code, symbol, name, decimalDigits }`) — without rendering an actual amount.
|
||||
|
||||
Each helper validates the currency code through the same `CurrencyCodeSchema` (Zod) used by the main `formatCurrency` standalone, so bogus codes (`"$"`, `"abc"`, `"USDT"`) raise structured `I18nError(INVALID_FORMATTER_OPTIONS)` rather than surfacing as raw `RangeError` from the underlying `Intl.NumberFormat`. Locale resolution goes through `toBcp47` from `formatter.ts` so callers can pass either a short code (`"en"` → `"en-GB"`) or a full BCP-47 tag.
|
||||
|
||||
## Features
|
||||
|
||||
- `getCurrencySymbol(currency, locale, display?)` — symbol / narrow symbol / code lookup
|
||||
- `getCurrencyName(currency, locale)` — localised display name
|
||||
- `getCurrencyInfo(currency, locale)` — aggregate `{ code, symbol, name, decimalDigits }` record
|
||||
- ISO 4217 validation enforced before any `Intl.NumberFormat` construction
|
||||
- Locale resolution via `LOCALE_BCP47_MAP` from `@suaveplan/i18n-shared`
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```typescript
|
||||
import {
|
||||
getCurrencyInfo,
|
||||
getCurrencyName,
|
||||
getCurrencySymbol,
|
||||
} from "@suaveplan/i18n-formatter-currency/helpers";
|
||||
|
||||
getCurrencySymbol("GBP", "en"); // → "£"
|
||||
getCurrencySymbol("USD", "en", "code"); // → "USD"
|
||||
getCurrencyName("USD", "fr"); // → "dollar des États-Unis"
|
||||
getCurrencyInfo("EUR", "en-US");
|
||||
// → { code: "EUR", symbol: "€", name: "Euro", decimalDigits: 2 }
|
||||
```
|
||||
|
||||
## Usage: Advanced Example — Currency picker
|
||||
|
||||
```typescript
|
||||
import { getCurrencyInfo } from "@suaveplan/i18n-formatter-currency/helpers";
|
||||
|
||||
const supported = ["USD", "EUR", "GBP", "JPY", "CHF"];
|
||||
|
||||
function buildCurrencyOptions(locale) {
|
||||
return supported.map((code) => {
|
||||
const info = getCurrencyInfo(code, locale);
|
||||
return {
|
||||
value: info.code,
|
||||
label: `${info.symbol} ${info.name} (${info.code})`,
|
||||
decimals: info.decimalDigits,
|
||||
};
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `getCurrencySymbol(currency, locale, display?)`
|
||||
|
||||
Returns the currency symbol string. The `display` argument restricts to `"symbol"` (default), `"narrowSymbol"`, or `"code"` — the same surface as the corresponding `Intl.NumberFormat` option restricted to the symbol-shaped subset. Throws `I18nError(INVALID_FORMATTER_OPTIONS)` if the currency code does not match `/^[A-Z]{3}$/`.
|
||||
|
||||
### `getCurrencyName(currency, locale)`
|
||||
|
||||
Returns the localised display name (e.g. `"US dollar"` in `en`, `"dollar américain"` in `fr`). Throws `I18nError(INVALID_FORMATTER_OPTIONS)` for malformed currency codes.
|
||||
|
||||
### `getCurrencyInfo(currency, locale)`
|
||||
|
||||
Returns an aggregate `CurrencyInfo` record containing the code, symbol, localised name, and decimal-digit count. Decimal digits are resolved from `formatter.resolvedOptions().maximumFractionDigits`, ensuring JPY returns 0, BHD returns 3, and most other currencies return 2 automatically.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The helpers do NOT participate in the `formatCurrency` cache because they ask `Intl.NumberFormat` for a different option set per call (`currencyDisplay: "code"` for symbol-as-code, `currencyDisplay: "name"` for the name). Sharing a cache would require keying on those options, which would defeat the simpler `(locale, currency, options-hash)` shape used by the main formatter.
|
||||
|
||||
The `formatToParts(0)` / `formatToParts(1)` calls use a small constant amount because some locales render different symbols at different magnitudes (rare but possible in less-common ICU builds). Using `0` for symbol lookup and `1` for name lookup matches the monolith's behaviour for cross-package compatibility.
|
||||
@@ -0,0 +1,91 @@
|
||||
# CurrencyFormatter Observability
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Per-package OpenTelemetry instruments for `@suaveplan/i18n-formatter-currency`. Exposes a histogram (`i18n.format.currency.duration_ms`) recording wall-clock duration of every `formatCurrency` call (tagged with `locale`, `currency`, and `display`) and a counter (`i18n.format.cache_hit`) tagged with `formatter: "currency"` that increments on every cache reuse.
|
||||
|
||||
The currency formatter introduces these instruments as part of the Wave 3 uplift — the monolith currently emits no OTel for currency formatting. The two instruments are constructed once via `metrics.getMeter` and are lazily initialised on first record so cold-start latency stays low.
|
||||
|
||||
## Features
|
||||
|
||||
- Lazy instrument construction
|
||||
- Stable histogram name `i18n.format.currency.duration_ms` with attributes `{ locale, currency, display }`
|
||||
- Stable counter name `i18n.format.cache_hit` with attributes `{ formatter: "currency" }`
|
||||
- In-memory sample ring (`currencyDurationSamples`, `cacheHitSamples`)
|
||||
- `resetCurrencyFormatMetricsState()` clears both arrays so tests stay isolated
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```typescript
|
||||
import {
|
||||
recordCurrencyFormat,
|
||||
recordCurrencyCacheHit,
|
||||
getCurrencyFormatMetricsState,
|
||||
resetCurrencyFormatMetricsState,
|
||||
} from "@suaveplan/i18n-formatter-currency/observability";
|
||||
|
||||
resetCurrencyFormatMetricsState();
|
||||
recordCurrencyFormat("en-US", "USD", "symbol", 0.4);
|
||||
recordCurrencyCacheHit();
|
||||
const state = getCurrencyFormatMetricsState();
|
||||
state.currencyDurationSamples.length; // 1
|
||||
state.cacheHitSamples.length; // 1
|
||||
```
|
||||
|
||||
## Usage: Advanced Example — Asserting OTel emission in a test
|
||||
|
||||
```typescript
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
import {
|
||||
formatCurrency,
|
||||
clearCurrencyFormatterCache,
|
||||
} from "@suaveplan/i18n-formatter-currency";
|
||||
import {
|
||||
getCurrencyFormatMetricsState,
|
||||
resetCurrencyFormatMetricsState,
|
||||
} from "@suaveplan/i18n-formatter-currency/observability";
|
||||
|
||||
describe("metrics emission", () => {
|
||||
it("emits duration histogram and cache-hit counter", { timeout: 5000 }, () => {
|
||||
clearCurrencyFormatterCache();
|
||||
resetCurrencyFormatMetricsState();
|
||||
formatCurrency(42, "USD", "en-US");
|
||||
formatCurrency(42, "USD", "en-US");
|
||||
const state = getCurrencyFormatMetricsState();
|
||||
expect(state.currencyDurationSamples).toHaveLength(2);
|
||||
expect(state.cacheHitSamples).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `recordCurrencyFormat(locale, currency, display, durationMs): void`
|
||||
|
||||
Records one observation on the `i18n.format.currency.duration_ms` histogram and pushes a `{ attributes: { locale, currency, display } }` entry into the in-memory sample ring.
|
||||
|
||||
### `recordCurrencyCacheHit(): void`
|
||||
|
||||
Adds 1 to the `i18n.format.cache_hit` counter with `formatter: "currency"` and pushes a matching entry into the in-memory sample ring.
|
||||
|
||||
### `getCurrencyFormatMetricsState(): CurrencyFormatMetricsState`
|
||||
|
||||
Returns the in-memory sample ring (live reference — do not mutate).
|
||||
|
||||
### `resetCurrencyFormatMetricsState(): void`
|
||||
|
||||
Empties both sample arrays in place.
|
||||
|
||||
### `CurrencyFormatMetricsState`
|
||||
|
||||
`{ currencyDurationSamples: FormatMetricSample[]; cacheHitSamples: FormatMetricSample[] }` where `FormatMetricSample = { attributes: Record<string, string | number> }`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The instruments are built lazily so consumers who never call `formatCurrency` do not pay the OTel meter-lookup cost. The cache check is a strict identity check against the closure-scoped `cached` slot — once built, the instruments are never rebuilt, even across `resetCurrencyFormatMetricsState` calls.
|
||||
|
||||
The `currency` attribute is the ISO 4217 code (`"USD"`, `"EUR"`, `"JPY"`); the `display` attribute is the resolved display mode (`"symbol"`, `"code"`, `"name"`, `"narrowSymbol"`). Both are included in the histogram tags so consumers can dashboard the most-formatted currencies and the most-used display modes per locale.
|
||||
Reference in New Issue
Block a user