
Type-safe currency formatting with comprehensive locale management, ISO/code and symbol styles, Compose-ready composables, result-based error handling, lightweight and instance-based formatter API.
Type-safe currency formatting for Kotlin Multiplatform with Compose support and comprehensive locale management.
dependencies {
implementation("org.kimplify:kurrency-core:0.2.3")
}dependencies {
implementation("org.kimplify:kurrency-core:0.2.3")
implementation("org.kimplify:kurrency-compose:0.2.3")
}import org.kimplify.kurrency.CurrencyFormatter
// Using the singleton with system locale
val result: Result<String> = CurrencyFormatter.formatCurrencyStyleResult("1234.56", "USD")
val formatted = result.getOrNull() // "$1,234.56" (in en-US locale)
// Get fraction digits for a currency
val fractionDigits = CurrencyFormatter.getFractionDigits("USD").getOrNull() // 2// Standard currency format (with symbol)
CurrencyFormatter.formatCurrencyStyleResult("1234.56", "USD")
// Result: "$1,234.56" (US), "1.234,56 $" (DE)
// ISO format (with currency code)
CurrencyFormatter.formatIsoCurrencyStyleResult("1234.56", "USD")
// Result: "USD 1,234.56"Important: Fraction digits are a property of the currency itself and do not vary by locale:
What does vary by locale:
. in US, , in Germany), in US, . in Germany)// Fraction digits are the same regardless of locale
val usFraction = CurrencyFormatter.getFractionDigits("USD", KurrencyLocale.US) // Returns 2
val deFraction = CurrencyFormatter.getFractionDigits("USD", KurrencyLocale.GERMANY) // Returns 2
// But formatting differs by locale
val usFormat = CurrencyFormatter.formatCurrencyStyleResult("1234.56", "USD", KurrencyLocale.US)
// Result: "$1,234.56"
val deFormat = CurrencyFormatter.formatCurrencyStyleResult("1234.56", "USD", KurrencyLocale.GERMANY)
// Result: "1.234,56 $"For consistency and clarity, prefer creating formatter instances:
val formatter = CurrencyFormatter(KurrencyLocale.GERMANY)
val fractionDigits = formatter.getFractionDigits("USD") // 2
val formatted = formatter.formatCurrencyStyleResult("1234.56", "USD") // "1.234,56 $"The Currency data class now requires explicit fraction digits:
// Create from currency code (recommended)
val currency = Currency.fromCode("USD").getOrThrow() // Currency(code="USD", fractionDigits=2)
// Create with specific locale formatter
val currency = Currency.fromCode("EUR", KurrencyLocale.GERMANY).getOrThrow()
// Or provide fraction digits explicitly
val currency = Currency("USD", 2)
// Format amounts with the Currency object
val formatted = currency.formatAmount("1234.56").getOrNull() // Uses system localeimport org.kimplify.kurrency.CurrencyFormatter
import org.kimplify.kurrency.KurrencyLocale
// Create formatters for specific locales
val usFormatter = CurrencyFormatter(KurrencyLocale.US)
val germanFormatter = CurrencyFormatter(KurrencyLocale.GERMANY)
val japaneseFormatter = CurrencyFormatter(KurrencyLocale.JAPAN)
// Format the same amount in different locales
usFormatter.formatCurrencyStyleResult("1234.56", "USD") // "$1,234.56"
germanFormatter.formatCurrencyStyleResult("1234.56", "EUR") // "1.234,56 β¬"
japaneseFormatter.formatCurrencyStyleResult("1234.56", "JPY") // "Β₯1,235"KurrencyLocale.US // en-US
KurrencyLocale.UK // en-GB
KurrencyLocale.CANADA // en-CA
KurrencyLocale.CANADA_FRENCH // fr-CA
KurrencyLocale.GERMANY // de-DE
KurrencyLocale.FRANCE // fr-FR
KurrencyLocale.ITALY // it-IT
KurrencyLocale.SPAIN // es-ES
KurrencyLocale.JAPAN // ja-JP
KurrencyLocale.CHINA // zh-CN
KurrencyLocale.KOREA // ko-KR
KurrencyLocale.BRAZIL // pt-BR
KurrencyLocale.RUSSIA // ru-RU
KurrencyLocale.SAUDI_ARABIA // ar-SA
KurrencyLocale.INDIA // hi-IN// Create locale from BCP 47 language tag
val locale = KurrencyLocale.fromLanguageTag("de-AT").getOrNull() // German (Austria)
val formatter = CurrencyFormatter(locale)// Get the device's current locale
val systemLocale = KurrencyLocale.systemLocale()
val formatter = CurrencyFormatter(KurrencyLocale.systemLocale())import androidx.compose.ui.text.intl.Locale
import org.kimplify.kurrency.toKurrencyLocale
@Composable
fun MyComposable() {
val composeLocale = Locale.current
val kurrencyLocale = composeLocale.toKurrencyLocale().getOrNull()
val formatter = kurrencyLocale?.let { CurrencyFormatter(it) }
}Add the kurrency-compose dependency for Jetpack Compose Multiplatform support.
The formatter automatically recreates when the locale changes (key-based recomposition).
import org.kimplify.kurrency.compose.rememberCurrencyFormatter
import org.kimplify.kurrency.KurrencyLocale
@Composable
fun PriceDisplay(amount: String, currencyCode: String) {
var selectedLocale by remember { mutableStateOf(KurrencyLocale.US) }
// Formatter recreates when locale changes
val formatter = rememberCurrencyFormatter(locale = selectedLocale)
val formattedPrice = remember(amount, currencyCode) {
formatter.formatCurrencyStyleResult(amount, currencyCode).getOrNull() ?: ""
}
Column {
Text("Price: $formattedPrice")
Button(onClick = { selectedLocale = KurrencyLocale.GERMANY }) {
Text("Switch to German locale")
}
}
}Provide a formatter for an entire subtree of your composition.
import org.kimplify.kurrency.compose.ProvideCurrencyFormatter
import org.kimplify.kurrency.compose.LocalCurrencyFormatter
import org.kimplify.kurrency.KurrencyLocale
@Composable
fun App() {
var appLocale by remember { mutableStateOf(KurrencyLocale.US) }
ProvideCurrencyFormatter(locale = appLocale) {
// All child composables can access the formatter
HomeScreen()
ProductScreen()
}
}
@Composable
fun ProductScreen() {
// Access the provided formatter
val formatter = LocalCurrencyFormatter.current
val price = remember {
formatter.formatCurrencyStyleResult("99.99", "USD").getOrNull() ?: ""
}
Text("Price: $price")
}Combine with Compose's State system for dynamic locale switching:
@Composable
fun MultiCurrencyDisplay() {
var locale by remember { mutableStateOf(KurrencyLocale.US) }
val formatter = rememberCurrencyFormatter(locale)
val prices = listOf(
"USD" to "100.00",
"EUR" to "85.50",
"JPY" to "11000"
)
Column {
prices.forEach { (currency, amount) ->
val formatted = remember(locale, currency, amount) {
formatter.formatCurrencyStyleResult(amount, currency).getOrNull() ?: ""
}
Text(formatted)
}
Row {
Button(onClick = { locale = KurrencyLocale.US }) { Text("US") }
Button(onClick = { locale = KurrencyLocale.UK }) { Text("UK") }
Button(onClick = { locale = KurrencyLocale.JAPAN }) { Text("JP") }
}
}
}// Convenience methods with system locale
CurrencyFormatter.formatCurrencyStyleResult(amount: String, currencyCode: String): Result<String>
CurrencyFormatter.formatIsoCurrencyStyleResult(amount: String, currencyCode: String): Result<String>
CurrencyFormatter.getFractionDigits(currencyCode: String): Result<Int>
CurrencyFormatter.getFractionDigitsOrDefault(currencyCode: String): Int
// Methods with explicit locale (recommended for locale-aware applications)
CurrencyFormatter.formatCurrencyStyleResult(amount: String, currencyCode: String, locale: KurrencyLocale): Result<String>
CurrencyFormatter.formatIsoCurrencyStyleResult(amount: String, currencyCode: String, locale: KurrencyLocale): Result<String>
CurrencyFormatter.getFractionDigits(currencyCode: String, locale: KurrencyLocale): Result<Int>
CurrencyFormatter.getFractionDigitsOrDefault(currencyCode: String, locale: KurrencyLocale): Int
// Create instances with custom locales (recommended pattern)
CurrencyFormatter(locale: KurrencyLocale): CurrencyFormat
CurrencyFormatter(KurrencyLocale.systemLocale()): CurrencyFormat// Constructor (requires explicit fraction digits)
Currency(code: String, fractionDigits: Int)
// Factory methods (recommended)
Currency.fromCode(code: String): Result<Currency>
Currency.fromCode(code: String, locale: KurrencyLocale): Result<Currency>
Currency.isValid(code: String): Boolean
// Instance methods
fun formatAmount(amount: String, style: CurrencyStyle = CurrencyStyle.Standard): Result<String>
fun formatAmount(amount: Double, style: CurrencyStyle = CurrencyStyle.Standard): Result<String>
fun formatAmountOrEmpty(amount: String, style: CurrencyStyle = CurrencyStyle.Standard): String
fun formatAmountOrEmpty(amount: Double, style: CurrencyStyle = CurrencyStyle.Standard): String
fun format(amount: String, style: CurrencyStyle = CurrencyStyle.Standard): FormattedCurrencyDelegate
fun format(amount: Double, style: CurrencyStyle = CurrencyStyle.Standard): FormattedCurrencyDelegateinterface CurrencyFormat {
fun getFractionDigits(currencyCode: String): Result<Int>
fun formatCurrencyStyleResult(amount: String, currencyCode: String): Result<String>
fun formatIsoCurrencyStyleResult(amount: String, currencyCode: String): Result<String>
}// Create from language tag
KurrencyLocale.fromLanguageTag(languageTag: String): Result<KurrencyLocale>
// Get system locale
KurrencyLocale.systemLocale(): KurrencyLocale
// Predefined locales
KurrencyLocale.US, UK, CANADA, GERMANY, FRANCE, JAPAN, etc.
// Properties
val languageTag: String // e.g., "en-US"// Remember formatter with specific locale
@Composable
fun rememberCurrencyFormatter(locale: KurrencyLocale): CurrencyFormat
// Remember formatter with system locale
@Composable
fun rememberSystemCurrencyFormatter(): CurrencyFormat
// CompositionLocal
val LocalCurrencyFormatter: CompositionLocal<CurrencyFormat>
// Provider composables
@Composable
fun ProvideCurrencyFormatter(locale: KurrencyLocale, content: @Composable () -> Unit)
@Composable
fun ProvideSystemCurrencyFormatter(content: @Composable () -> Unit)
// Compose Locale extensions
fun Locale.toKurrencyLocale(): Result<KurrencyLocale>
fun KurrencyLocale.Companion.fromComposeLocale(composeLocale: Locale): Result<KurrencyLocale>
@Composable
fun KurrencyLocale.Companion.current(): KurrencyLocaleAll formatting methods return Result<String> for type-safe error handling:
val formatter = CurrencyFormatter(KurrencyLocale.US)
formatter.formatCurrencyStyleResult("1234.56", "USD")
.onSuccess { formatted -> println(formatted) }
.onFailure { error ->
when (error) {
is KurrencyError.InvalidAmount -> println("Invalid amount")
is KurrencyError.InvalidCurrencyCode -> println("Invalid currency")
else -> println("Formatting error")
}
}KurrencyError.InvalidCurrencyCode - Invalid currency code formatKurrencyError.InvalidAmount - Invalid amount formatKurrencyError.FormattingFailure - Platform formatting errorKurrencyError.FractionDigitsFailure - Failed to get fraction digitsApache License 2.0 - Copyright Β© 2025
Type-safe currency formatting for Kotlin Multiplatform with Compose support and comprehensive locale management.
dependencies {
implementation("org.kimplify:kurrency-core:0.2.3")
}dependencies {
implementation("org.kimplify:kurrency-core:0.2.3")
implementation("org.kimplify:kurrency-compose:0.2.3")
}import org.kimplify.kurrency.CurrencyFormatter
// Using the singleton with system locale
val result: Result<String> = CurrencyFormatter.formatCurrencyStyleResult("1234.56", "USD")
val formatted = result.getOrNull() // "$1,234.56" (in en-US locale)
// Get fraction digits for a currency
val fractionDigits = CurrencyFormatter.getFractionDigits("USD").getOrNull() // 2// Standard currency format (with symbol)
CurrencyFormatter.formatCurrencyStyleResult("1234.56", "USD")
// Result: "$1,234.56" (US), "1.234,56 $" (DE)
// ISO format (with currency code)
CurrencyFormatter.formatIsoCurrencyStyleResult("1234.56", "USD")
// Result: "USD 1,234.56"Important: Fraction digits are a property of the currency itself and do not vary by locale:
What does vary by locale:
. in US, , in Germany), in US, . in Germany)// Fraction digits are the same regardless of locale
val usFraction = CurrencyFormatter.getFractionDigits("USD", KurrencyLocale.US) // Returns 2
val deFraction = CurrencyFormatter.getFractionDigits("USD", KurrencyLocale.GERMANY) // Returns 2
// But formatting differs by locale
val usFormat = CurrencyFormatter.formatCurrencyStyleResult("1234.56", "USD", KurrencyLocale.US)
// Result: "$1,234.56"
val deFormat = CurrencyFormatter.formatCurrencyStyleResult("1234.56", "USD", KurrencyLocale.GERMANY)
// Result: "1.234,56 $"For consistency and clarity, prefer creating formatter instances:
val formatter = CurrencyFormatter(KurrencyLocale.GERMANY)
val fractionDigits = formatter.getFractionDigits("USD") // 2
val formatted = formatter.formatCurrencyStyleResult("1234.56", "USD") // "1.234,56 $"The Currency data class now requires explicit fraction digits:
// Create from currency code (recommended)
val currency = Currency.fromCode("USD").getOrThrow() // Currency(code="USD", fractionDigits=2)
// Create with specific locale formatter
val currency = Currency.fromCode("EUR", KurrencyLocale.GERMANY).getOrThrow()
// Or provide fraction digits explicitly
val currency = Currency("USD", 2)
// Format amounts with the Currency object
val formatted = currency.formatAmount("1234.56").getOrNull() // Uses system localeimport org.kimplify.kurrency.CurrencyFormatter
import org.kimplify.kurrency.KurrencyLocale
// Create formatters for specific locales
val usFormatter = CurrencyFormatter(KurrencyLocale.US)
val germanFormatter = CurrencyFormatter(KurrencyLocale.GERMANY)
val japaneseFormatter = CurrencyFormatter(KurrencyLocale.JAPAN)
// Format the same amount in different locales
usFormatter.formatCurrencyStyleResult("1234.56", "USD") // "$1,234.56"
germanFormatter.formatCurrencyStyleResult("1234.56", "EUR") // "1.234,56 β¬"
japaneseFormatter.formatCurrencyStyleResult("1234.56", "JPY") // "Β₯1,235"KurrencyLocale.US // en-US
KurrencyLocale.UK // en-GB
KurrencyLocale.CANADA // en-CA
KurrencyLocale.CANADA_FRENCH // fr-CA
KurrencyLocale.GERMANY // de-DE
KurrencyLocale.FRANCE // fr-FR
KurrencyLocale.ITALY // it-IT
KurrencyLocale.SPAIN // es-ES
KurrencyLocale.JAPAN // ja-JP
KurrencyLocale.CHINA // zh-CN
KurrencyLocale.KOREA // ko-KR
KurrencyLocale.BRAZIL // pt-BR
KurrencyLocale.RUSSIA // ru-RU
KurrencyLocale.SAUDI_ARABIA // ar-SA
KurrencyLocale.INDIA // hi-IN// Create locale from BCP 47 language tag
val locale = KurrencyLocale.fromLanguageTag("de-AT").getOrNull() // German (Austria)
val formatter = CurrencyFormatter(locale)// Get the device's current locale
val systemLocale = KurrencyLocale.systemLocale()
val formatter = CurrencyFormatter(KurrencyLocale.systemLocale())import androidx.compose.ui.text.intl.Locale
import org.kimplify.kurrency.toKurrencyLocale
@Composable
fun MyComposable() {
val composeLocale = Locale.current
val kurrencyLocale = composeLocale.toKurrencyLocale().getOrNull()
val formatter = kurrencyLocale?.let { CurrencyFormatter(it) }
}Add the kurrency-compose dependency for Jetpack Compose Multiplatform support.
The formatter automatically recreates when the locale changes (key-based recomposition).
import org.kimplify.kurrency.compose.rememberCurrencyFormatter
import org.kimplify.kurrency.KurrencyLocale
@Composable
fun PriceDisplay(amount: String, currencyCode: String) {
var selectedLocale by remember { mutableStateOf(KurrencyLocale.US) }
// Formatter recreates when locale changes
val formatter = rememberCurrencyFormatter(locale = selectedLocale)
val formattedPrice = remember(amount, currencyCode) {
formatter.formatCurrencyStyleResult(amount, currencyCode).getOrNull() ?: ""
}
Column {
Text("Price: $formattedPrice")
Button(onClick = { selectedLocale = KurrencyLocale.GERMANY }) {
Text("Switch to German locale")
}
}
}Provide a formatter for an entire subtree of your composition.
import org.kimplify.kurrency.compose.ProvideCurrencyFormatter
import org.kimplify.kurrency.compose.LocalCurrencyFormatter
import org.kimplify.kurrency.KurrencyLocale
@Composable
fun App() {
var appLocale by remember { mutableStateOf(KurrencyLocale.US) }
ProvideCurrencyFormatter(locale = appLocale) {
// All child composables can access the formatter
HomeScreen()
ProductScreen()
}
}
@Composable
fun ProductScreen() {
// Access the provided formatter
val formatter = LocalCurrencyFormatter.current
val price = remember {
formatter.formatCurrencyStyleResult("99.99", "USD").getOrNull() ?: ""
}
Text("Price: $price")
}Combine with Compose's State system for dynamic locale switching:
@Composable
fun MultiCurrencyDisplay() {
var locale by remember { mutableStateOf(KurrencyLocale.US) }
val formatter = rememberCurrencyFormatter(locale)
val prices = listOf(
"USD" to "100.00",
"EUR" to "85.50",
"JPY" to "11000"
)
Column {
prices.forEach { (currency, amount) ->
val formatted = remember(locale, currency, amount) {
formatter.formatCurrencyStyleResult(amount, currency).getOrNull() ?: ""
}
Text(formatted)
}
Row {
Button(onClick = { locale = KurrencyLocale.US }) { Text("US") }
Button(onClick = { locale = KurrencyLocale.UK }) { Text("UK") }
Button(onClick = { locale = KurrencyLocale.JAPAN }) { Text("JP") }
}
}
}// Convenience methods with system locale
CurrencyFormatter.formatCurrencyStyleResult(amount: String, currencyCode: String): Result<String>
CurrencyFormatter.formatIsoCurrencyStyleResult(amount: String, currencyCode: String): Result<String>
CurrencyFormatter.getFractionDigits(currencyCode: String): Result<Int>
CurrencyFormatter.getFractionDigitsOrDefault(currencyCode: String): Int
// Methods with explicit locale (recommended for locale-aware applications)
CurrencyFormatter.formatCurrencyStyleResult(amount: String, currencyCode: String, locale: KurrencyLocale): Result<String>
CurrencyFormatter.formatIsoCurrencyStyleResult(amount: String, currencyCode: String, locale: KurrencyLocale): Result<String>
CurrencyFormatter.getFractionDigits(currencyCode: String, locale: KurrencyLocale): Result<Int>
CurrencyFormatter.getFractionDigitsOrDefault(currencyCode: String, locale: KurrencyLocale): Int
// Create instances with custom locales (recommended pattern)
CurrencyFormatter(locale: KurrencyLocale): CurrencyFormat
CurrencyFormatter(KurrencyLocale.systemLocale()): CurrencyFormat// Constructor (requires explicit fraction digits)
Currency(code: String, fractionDigits: Int)
// Factory methods (recommended)
Currency.fromCode(code: String): Result<Currency>
Currency.fromCode(code: String, locale: KurrencyLocale): Result<Currency>
Currency.isValid(code: String): Boolean
// Instance methods
fun formatAmount(amount: String, style: CurrencyStyle = CurrencyStyle.Standard): Result<String>
fun formatAmount(amount: Double, style: CurrencyStyle = CurrencyStyle.Standard): Result<String>
fun formatAmountOrEmpty(amount: String, style: CurrencyStyle = CurrencyStyle.Standard): String
fun formatAmountOrEmpty(amount: Double, style: CurrencyStyle = CurrencyStyle.Standard): String
fun format(amount: String, style: CurrencyStyle = CurrencyStyle.Standard): FormattedCurrencyDelegate
fun format(amount: Double, style: CurrencyStyle = CurrencyStyle.Standard): FormattedCurrencyDelegateinterface CurrencyFormat {
fun getFractionDigits(currencyCode: String): Result<Int>
fun formatCurrencyStyleResult(amount: String, currencyCode: String): Result<String>
fun formatIsoCurrencyStyleResult(amount: String, currencyCode: String): Result<String>
}// Create from language tag
KurrencyLocale.fromLanguageTag(languageTag: String): Result<KurrencyLocale>
// Get system locale
KurrencyLocale.systemLocale(): KurrencyLocale
// Predefined locales
KurrencyLocale.US, UK, CANADA, GERMANY, FRANCE, JAPAN, etc.
// Properties
val languageTag: String // e.g., "en-US"// Remember formatter with specific locale
@Composable
fun rememberCurrencyFormatter(locale: KurrencyLocale): CurrencyFormat
// Remember formatter with system locale
@Composable
fun rememberSystemCurrencyFormatter(): CurrencyFormat
// CompositionLocal
val LocalCurrencyFormatter: CompositionLocal<CurrencyFormat>
// Provider composables
@Composable
fun ProvideCurrencyFormatter(locale: KurrencyLocale, content: @Composable () -> Unit)
@Composable
fun ProvideSystemCurrencyFormatter(content: @Composable () -> Unit)
// Compose Locale extensions
fun Locale.toKurrencyLocale(): Result<KurrencyLocale>
fun KurrencyLocale.Companion.fromComposeLocale(composeLocale: Locale): Result<KurrencyLocale>
@Composable
fun KurrencyLocale.Companion.current(): KurrencyLocaleAll formatting methods return Result<String> for type-safe error handling:
val formatter = CurrencyFormatter(KurrencyLocale.US)
formatter.formatCurrencyStyleResult("1234.56", "USD")
.onSuccess { formatted -> println(formatted) }
.onFailure { error ->
when (error) {
is KurrencyError.InvalidAmount -> println("Invalid amount")
is KurrencyError.InvalidCurrencyCode -> println("Invalid currency")
else -> println("Formatting error")
}
}KurrencyError.InvalidCurrencyCode - Invalid currency code formatKurrencyError.InvalidAmount - Invalid amount formatKurrencyError.FormattingFailure - Platform formatting errorKurrencyError.FractionDigitsFailure - Failed to get fraction digitsApache License 2.0 - Copyright Β© 2025