Take into account the following:
- You can't use
Currency.symbol
to find it in the formatted string because in some locales $
becomes US$
while Currency.symbol
still returns $
- You should use non-breaking space instead of a normal space (default formatter does it this way). The same for checking whether space is already present (in some locales it is)
- For negatives amount a sign normally goes before the currency
The solution is (sorry for Kotlin code):
fun formatPrice(currencyCode: String, price: BigDecimal, locale: Locale? = null): String {
val resLocale = locale ?: Locale.getDefault()
val currencyFormat = NumberFormat.getCurrencyInstance(resLocale)
// decimalFormat formats the amount in the same way as currencyFormat, but without currency symbol.
// We can't use currency.symbol because it doesn't always match the way currency rendered in
// the resulting string (for some locales "$" becomes "US$")
val decimalFormat = NumberFormat.getNumberInstance(resLocale)
decimalFormat.maximumFractionDigits = currencyFormat.maximumFractionDigits
decimalFormat.maximumIntegerDigits = currencyFormat.maximumIntegerDigits
decimalFormat.isGroupingUsed = currencyFormat.isGroupingUsed
decimalFormat.minimumFractionDigits = currencyFormat.minimumFractionDigits
decimalFormat.minimumIntegerDigits = currencyFormat.minimumIntegerDigits
decimalFormat.roundingMode = currencyFormat.roundingMode
currencyFormat.currency = Currency.getInstance(currencyCode)
val priceFormatted = currencyFormat.format(price)
val amountFormatted = decimalFormat.format(price)
// 12 345,67 $ US
if (priceFormatted.startsWith(amountFormatted)) {
val suffix = priceFormatted.substringAfter(amountFormatted)
return if (suffix.isNotEmpty() && !suffix.first().isWhitespace()) {
"$amountFormatted $suffix" // use nbsp to prevent line break
} else {
priceFormatted
}
}
// US$12,345.67
if (priceFormatted.endsWith(amountFormatted)) {
val prefix = priceFormatted.substringBefore(amountFormatted)
return if (prefix.isNotEmpty() && !prefix.last().isWhitespace()) {
"$prefix $amountFormatted" // use nbsp to prevent line break
} else {
priceFormatted
}
}
// For negative amounts a sign normally goes before the currency symbol
// -US$12,345.67
val absAmountFormatted = decimalFormat.format(price.abs())
if (priceFormatted.endsWith(absAmountFormatted)) {
val prefix = priceFormatted.substringBefore(absAmountFormatted)
if (prefix.isNotEmpty() && !prefix.last().isWhitespace()) {
return "$prefix $absAmountFormatted" // use nbsp to prevent line break
}
}
return priceFormatted
}
Testing:
Locale: BigDecimal -> default_format -> custom_with_nbsp
en: 12345.673 -> $12,345.67 -> $ 12,345.67
en: 12345.67 -> $12,345.67 -> $ 12,345.67
en: -12345.67 -> -$12,345.67 -> -$ 12,345.67
en: 12345.67 -> €12,345.67 -> € 12,345.67
en: -12345.67 -> -€12,345.67 -> -€ 12,345.67
en: 12345.67 -> PLN12,345.67 -> PLN 12,345.67
en: -12345.67 -> -PLN12,345.67 -> -PLN 12,345.67
zh_CN: 12345.67 -> US$12,345.67 -> US$ 12,345.67
fr_CA: -12345.67 -> -12 345,67 PLN -> -12 345,67 PLN
pl_PL: 12345.67 -> 12 345,67 zł -> 12 345,67 zł
fr_FR: 12345.67 -> 12 345,67 PLN -> 12 345,67 PLN