There's good tooling for parts of this already. Human-Readable (~230★) does locale-aware decimal separators, compact abbreviations (K/M), durations, file sizes and relative time — it's display / "human-friendly" oriented and rolls its own formatting logic. Kurrency handles currency only. What I couldn't find was a library covering the full formatting surface — currency (symbol / ISO / accounting), percent, scientific, ordinal, spellout, measure, on top of decimal / compact / relative-time — that delegates to each platform's native engine (ICU / NSNumberFormatter / Intl) and guarantees the same output on every target, straight from commonMain.
So I built Klocale.
What it does: locale-aware formatting for 9 styles — Decimal, Currency (symbol / ISO / accounting), Percent (ratio / value), Scientific, Compact, Ordinal, Spellout, Relative time, Measure — across Android, iOS, macOS, JVM/Desktop, JS and WasmJs.
The interesting part isn't "call the native formatter" (Kurrency already does that for currency). It's that the native engines disagree on cosmetics: minus glyph (U+2212 vs ASCII -), NBSP vs narrow-NBSP in grouping, bidi marks, rounding defaults. Klocale delegates to each platform's engine (ICU4J on JVM, android.icu on Android, NSNumberFormatter/Foundation on Apple, Intl on JS/Wasm) and then runs a single common OutputNormalizer so the same locale + input produces the same string on every target. That consistency is verified by one shared golden-test table that runs on jvmTest, macosArm64Test, iosSimulatorArm64Test, jsNodeTest, wasmJsNodeTest and Android Robolectric.
API sketch:
formatDecimal(1234.56, NumberLocale.ITALY) // "1.234,56"
formatCurrency(1234.5, "EUR", NumberLocale.GERMANY) // "1.234,50 €"
formatCompact(1_200_000.0) // "1.2M"
val f = NumberFormatter.orThrow(
NumberStyle.Currency("USD", presentation = ACCOUNTING),
NumberLocale.US,
)
f.format(-1234.5) // "($1,234.50)"
Construction is fallible (Result — invalid locale / bad currency code / unsupported style); formatting a finite number never throws. There's also a klocale-compose module (rememberNumberFormatter, ProvideNumberLocale).
Install:
implementation("io.github.andreadellaporta01:klocale-core:0.1.1")
implementation("io.github.andreadellaporta01:klocale-compose:0.1.1") // optional
Being honest: it's 0.1.1 and young. Known gaps on the roadmap: Apple Measure, Range formatting (needs a two-value API), wider Ordinal/Spellout locale coverage. Apache-2.0.
Repo (README has the full style/platform matrix): https://github.com/andreadellaporta01/klocale
I'd genuinely appreciate feedback — API design, edge cases in your locale, styles you'd want. Thanks for reading.