Comprehensive, locale-aware number and value formatting for Kotlin Multiplatform — the
missing NSNumberFormatter / Intl.NumberFormat / ICU NumberFormat equivalent usable
straight from commonMain, with consistent output across every target.
Klocale delegates to each platform's native formatter (ICU on JVM/Android, NSNumberFormatter
on Apple, Intl on JS/WasmJs) and normalizes the cosmetic differences (minus glyphs,
non‑breaking spaces, bidi marks) so the same input produces the same string everywhere.
Android · iOS · macOS · JVM/Desktop · JS · WasmJs
// settings.gradle.kts -> mavenCentral()
dependencies {
implementation("io.github.andreadellaporta01:klocale-core:0.1.1")
// optional, for Compose Multiplatform:
implementation("io.github.andreadellaporta01:klocale-compose:0.1.1")
}import dev.klocale.*
formatDecimal(1234.56, NumberLocale.ITALY) // "1.234,56"
formatCurrency(1234.5, "EUR", NumberLocale.GERMANY) // "1.234,50 €"
formatPercent(0.42) // "42%"
formatCompact(1_200_000.0) // "1.2M"
val f = NumberFormatter.orThrow(
NumberStyle.Currency("USD", presentation = NumberStyle.Currency.Presentation.ACCOUNTING),
NumberLocale.US,
)
f.format(-1234.5) // "($1,234.50)"Construction returns a Result (invalid locale / malformed currency code / unsupported style),
while formatting a finite number never throws:
NumberFormatter.of(NumberStyle.Currency("US1")) // Result.failure(InvalidCurrencyCode)The currency code is validated for shape (three ASCII letters), not against the ISO 4217 registry.
format accepts Double, Long, Int, Float, and String. The String overload preserves
arbitrary precision for plain decimal styles — format("9007199254740993") keeps every digit,
where the Double path would round.
ProvideNumberLocale(NumberLocale.FRANCE) {
val price = rememberNumberFormatter(NumberStyle.Currency("EUR"))
Text(price.format(1234.5)) // "1 234,50 €"
}| Style | Android | iOS/macOS | JVM | JS/WasmJs |
|---|---|---|---|---|
| Decimal | ✅ | ✅ | ✅ | ✅ |
| Currency (symbol / ISO / accounting) | ✅ | ✅ | ✅ | ✅ |
| Percent (ratio / value) | ✅ | ✅ | ✅ | ✅ |
| Scientific | ✅ | ✅ | ✅ | ✅ |
| Compact | ✅ | en only | ✅ | ✅ |
| Ordinal (suffix) | en only | ✅ | ✅ | en only |
| Spellout | — | ✅ | ✅ | — |
| Relative time | ✅ | ✅ (no quarter) | ✅ | ✅ |
| Measure | ✅ | — | ✅ | ✅ |
Unsupported combinations fail at construction with NumberFormatError.UnsupportedStyle — never
with wrong output. See the roadmap below for gaps being closed.
Output is verified by a shared golden-test table run on every backend — jvmTest,
macosArm64Test, iosSimulatorArm64Test, jsNodeTest, wasmJsNodeTest and the Android
Robolectric test — which also asserts that each backend correctly rejects the styles it cannot
support.
On Node, Intl only formats non-English locales when the runtime ships the full ICU data
(the default since Node 13; older or small-icu builds need --icu-data-dir / full-icu).
Browsers and every non-JS target are unaffected.
- Apple
Measure(viaNSMeasurementFormatter) Rangeformatting (needs a two-valueformatRange(from, to)API)Ordinal/Spelloutword form and wider locale coverage
See CHANGELOG.md.
Apache-2.0
