Skip to main content

Currency Formatting

The template includes a set of currency formatting utilities that provide consistent, locale-aware monetary value display with support for over 20 currencies and automatic handling of zero-decimal currencies.

Source: lib/utils/currency-format.ts

Overview

The currency formatting module addresses several common challenges:

  • Minor-to-major unit conversion -- payment providers like Stripe store amounts in minor units (cents). The utilities handle conversion to display-ready major units.
  • Zero-decimal currencies -- currencies like JPY (Japanese Yen) and KRW (Korean Won) have no minor units. These are handled automatically.
  • Locale-aware formatting -- uses Intl.NumberFormat for proper symbol placement, decimal separators, and thousands grouping based on locale.
  • Graceful fallback -- if a currency code is not recognized by Intl.NumberFormat, the utilities fall back to a manual symbol + amount format.

Functions

formatCurrency(amountInMinorUnits, currency?, locale?)

Converts an amount in minor units (e.g., cents) to a formatted currency string:

import { formatCurrency } from '@/lib/utils/currency-format';

formatCurrency(1999, 'USD');
// "$19.99"

formatCurrency(1999, 'USD', 'de-DE');
// "19,99 $"

formatCurrency(1500, 'EUR', 'fr-FR');
// "15,00 €"

Zero-decimal currencies are not divided by 100:

formatCurrency(1500, 'JPY');
// "¥1,500"

formatCurrency(50000, 'KRW');
// "₩50,000"

Parameters:

ParameterTypeDefaultDescription
amountInMinorUnitsnumberrequiredAmount in minor units (cents)
currencystring'USD'ISO 4217 currency code
localestring'en-US'BCP 47 locale tag

formatCurrencyAmount(amount, currency?, locale?)

Formats an amount already in major units (dollars, not cents):

import { formatCurrencyAmount } from '@/lib/utils/currency-format';

formatCurrencyAmount(19.99, 'USD');
// "$19.99"

formatCurrencyAmount(19.99, 'GBP');
// "£19.99"

formatCurrencyAmount(1500, 'JPY');
// "¥1,500"

This is useful when the amount comes from a source that already provides major units (e.g., a user-entered price field).

Parameters:

ParameterTypeDefaultDescription
amountnumberrequiredAmount in major units
currencystring'USD'ISO 4217 currency code
localestring'en-US'BCP 47 locale tag

getCurrencySymbol(currency)

Returns the symbol for a currency code from a built-in lookup table:

import { getCurrencySymbol } from '@/lib/utils/currency-format';

getCurrencySymbol('USD'); // "$"
getCurrencySymbol('EUR'); // "€"
getCurrencySymbol('GBP'); // "£"
getCurrencySymbol('JPY'); // "¥"
getCurrencySymbol('INR'); // "₹"
getCurrencySymbol('BRL'); // "R$"
getCurrencySymbol('KRW'); // "₩"
getCurrencySymbol('XYZ'); // "XYZ" (unknown code returned as-is)

formatAmountWithSymbol(amount, currency?)

Low-level formatter that combines a currency symbol with a fixed-decimal amount. Used as the fallback when Intl.NumberFormat does not recognize a currency code:

import { formatAmountWithSymbol } from '@/lib/utils/currency-format';

formatAmountWithSymbol(19.99, 'USD');
// "$19.99"

formatAmountWithSymbol(1500, 'JPY');
// "¥1500"

formatAmountWithSymbol(29.5, 'EUR');
// "€29.50"

This function respects zero-decimal conventions -- JPY, KRW, VND, CLP, and IDR are formatted with 0 decimal places.

Supported Currencies

The symbol lookup table includes:

CodeSymbolCurrency
USD$US Dollar
EUREEuro
GBPPBritish Pound
JPYYJapanese Yen
CNYYChinese Yuan
CADC$Canadian Dollar
AUDA$Australian Dollar
CHFCHFSwiss Franc
INRRIndian Rupee
BRLR$Brazilian Real
MXN$Mexican Peso
KRWWSouth Korean Won
RUBRRussian Ruble
TRYTTurkish Lira
ZARRSouth African Rand
SGDS$Singapore Dollar
HKDHK$Hong Kong Dollar
NOKkrNorwegian Krone
SEKkrSwedish Krona
DKKkrDanish Krone
PLNzlPolish Zloty
CZKKcCzech Koruna
HUFFtHungarian Forint

Currencies not in the table return the uppercase code as the symbol.

Zero-Decimal Currencies

The following currencies do not use minor units (no cents):

CodeCurrency
JPYJapanese Yen
KRWSouth Korean Won
VNDVietnamese Dong
CLPChilean Peso
IDRIndonesian Rupiah

For these currencies:

  • formatCurrency treats the input as already in major units (no division by 100)
  • formatAmountWithSymbol uses 0 decimal places
  • Intl.NumberFormat handles them automatically

Error Handling

Both formatCurrency and formatCurrencyAmount catch RangeError exceptions from Intl.NumberFormat (which occur for unrecognized currency codes) and fall back to formatAmountWithSymbol:

// Unrecognized currency code
formatCurrency(1999, 'FAKE');
// Falls back to: "FAKE19.99"

// Invalid locale (other errors) are re-thrown

This ensures the UI never breaks due to an unexpected currency code from a payment provider.

Integration Examples

Displaying Subscription Prices

import { formatCurrency } from '@/lib/utils/currency-format';

function PricingCard({ plan }) {
return (
<div>
<span>{formatCurrency(plan.amountInCents, plan.currency)}</span>
<span>/{plan.interval}</span>
</div>
);
}

Formatting User-Entered Amounts

import { formatCurrencyAmount } from '@/lib/utils/currency-format';

// User enters 29.99 in a form
const display = formatCurrencyAmount(29.99, userCurrency, userLocale);

Multi-Locale Support

import { formatCurrencyAmount } from '@/lib/utils/currency-format';

const amount = 1234.56;

formatCurrencyAmount(amount, 'EUR', 'en-US'); // "€1,234.56"
formatCurrencyAmount(amount, 'EUR', 'de-DE'); // "1.234,56 €"
formatCurrencyAmount(amount, 'EUR', 'fr-FR'); // "1 234,56 €"

The SponsorAdService uses currency formatting for display:

import { formatCurrencyAmount } from '@/lib/utils/currency-format';

const price = sponsorAdService.getAmountForInterval('weekly');
const currency = sponsorAdService.getCurrency();
const display = formatCurrencyAmount(price, currency);
// "$29.00" (or configured amount)