
Locale-aware number and value formatting with consistent output, native-formatter delegation and cosmetic normalization (glyphs, spaces, bidi). Supports currency, percent, compact, scientific; preserves precise string input.
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.
Measure (via NSMeasurementFormatter)Range formatting (needs a two-value formatRange(from, to) API)Ordinal / Spellout word form and wider locale coverageSee CHANGELOG.md.
Apache-2.0
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.
Measure (via NSMeasurementFormatter)Range formatting (needs a two-value formatRange(from, to) API)Ordinal / Spellout word form and wider locale coverageSee CHANGELOG.md.
Apache-2.0