Use Cases

Implementing Currency Formatting with Claude Code

Learn about implementing currency formatting using Claude Code. Includes practical code examples.

Implement Currency Formatting Correctly With Claude Code

Accurate currency formatting is essential for e-commerce sites, accounting apps, and any internationalized service. Rules for decimal places, thousands separators, and currency symbols vary by country and currency, so rolling your own formatting is a recipe for bugs. With Claude Code, you can efficiently build a robust implementation using Intl.NumberFormat and specialized libraries.

Using Intl.NumberFormat

> Build a multi-currency formatting utility.
> Support Japanese yen, US dollar, and euro.
> Include locale-specific display, exchange rate conversion, and amount validation.
// src/lib/currency.ts

export type CurrencyCode = 'JPY' | 'USD' | 'EUR' | 'GBP' | 'CNY' | 'KRW';

interface FormatOptions {
  currency: CurrencyCode;
  locale?: string;
  compact?: boolean;      // Short form like "1.2M"
  showSign?: boolean;     // Show +/- sign
}

/**
 * Format a currency amount.
 */
export function formatCurrency(
  amount: number,
  options: FormatOptions
): string {
  const { currency, locale = 'en-US', compact = false, showSign = false } = options;

  const formatter = new Intl.NumberFormat(locale, {
    style: 'currency',
    currency,
    notation: compact ? 'compact' : 'standard',
    signDisplay: showSign ? 'exceptZero' : 'auto',
    // Decimal currencies use 2 digits, JPY/KRW use 0 (auto-detected)
  });

  return formatter.format(amount);
}

// Usage examples
formatCurrency(1234567, { currency: 'JPY' });          // "¥1,234,567"
formatCurrency(1234.56, { currency: 'USD' });          // "$1,234.56"
formatCurrency(1234.56, { currency: 'EUR', locale: 'de-DE' }); // "1.234,56 €"
formatCurrency(1234567, { currency: 'USD', compact: true });   // "$1.2M"

Pitfalls of Currency Arithmetic

// src/lib/currency-math.ts

/**
 * To avoid floating-point errors, compute in integer minor units.
 * JPY: 1 yen = 1 | USD: 1 cent = 1 | EUR: 1 cent = 1
 */
export class Money {
  constructor(
    private readonly amountInMinorUnit: number,
    private readonly currency: CurrencyCode
  ) {}

  // Decimal place count (cents/yen)
  private static decimals(currency: CurrencyCode): number {
    return ['JPY', 'KRW'].includes(currency) ? 0 : 2;
  }

  static fromMajorUnit(amount: number, currency: CurrencyCode): Money {
    const decimals = Money.decimals(currency);
    const minor = Math.round(amount * Math.pow(10, decimals));
    return new Money(minor, currency);
  }

  toMajorUnit(): number {
    const decimals = Money.decimals(this.currency);
    return this.amountInMinorUnit / Math.pow(10, decimals);
  }

  add(other: Money): Money {
    this.assertSameCurrency(other);
    return new Money(this.amountInMinorUnit + other.amountInMinorUnit, this.currency);
  }

  subtract(other: Money): Money {
    this.assertSameCurrency(other);
    return new Money(this.amountInMinorUnit - other.amountInMinorUnit, this.currency);
  }

  multiply(factor: number): Money {
    return new Money(Math.round(this.amountInMinorUnit * factor), this.currency);
  }

  format(locale?: string): string {
    return formatCurrency(this.toMajorUnit(), { currency: this.currency, locale });
  }

  private assertSameCurrency(other: Money) {
    if (this.currency !== other.currency) {
      throw new Error(`Currency mismatch: ${this.currency} vs ${other.currency}`);
    }
  }
}

// Usage example
const price = Money.fromMajorUnit(1980, 'JPY');
const tax = price.multiply(0.1);
const total = price.add(tax);
console.log(total.format()); // "¥2,178"

Exchange Rate Conversion

// src/lib/exchange.ts
interface ExchangeRates {
  base: CurrencyCode;
  rates: Record<CurrencyCode, number>;
  updatedAt: string;
}

let cachedRates: ExchangeRates | null = null;

export async function getExchangeRates(base: CurrencyCode = 'USD'): Promise<ExchangeRates> {
  // Reuse cache if it's less than an hour old
  if (cachedRates && Date.now() - new Date(cachedRates.updatedAt).getTime() < 3600000) {
    return cachedRates;
  }

  const res = await fetch(`https://api.exchangerate-api.com/v4/latest/${base}`);
  const data = await res.json();

  cachedRates = {
    base,
    rates: data.rates,
    updatedAt: new Date().toISOString(),
  };

  return cachedRates;
}

export async function convertCurrency(
  amount: number,
  from: CurrencyCode,
  to: CurrencyCode
): Promise<{ converted: number; rate: number }> {
  const rates = await getExchangeRates(from);
  const rate = rates.rates[to];

  return {
    converted: Math.round(amount * rate * 100) / 100,
    rate,
  };
}

Currency Input Component

// src/components/CurrencyInput.tsx
'use client';
import { useState } from 'react';

interface Props {
  value: number;
  currency: CurrencyCode;
  onChange: (value: number) => void;
}

export function CurrencyInput({ value, currency, onChange }: Props) {
  const [displayValue, setDisplayValue] = useState(value.toLocaleString());

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const raw = e.target.value.replace(/[^0-9.-]/g, '');
    setDisplayValue(e.target.value);
    const num = parseFloat(raw);
    if (!isNaN(num)) onChange(num);
  };

  const handleBlur = () => {
    setDisplayValue(value.toLocaleString());
  };

  const symbol = new Intl.NumberFormat('en-US', {
    style: 'currency',
    currency,
    currencyDisplay: 'narrowSymbol',
  }).formatToParts(0).find((p) => p.type === 'currency')?.value || '';

  return (
    <div className="relative">
      <span className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400">{symbol}</span>
      <input
        type="text"
        value={displayValue}
        onChange={handleChange}
        onBlur={handleBlur}
        className="w-full border rounded-lg pl-8 pr-4 py-2 text-right"
        inputMode="decimal"
      />
    </div>
  );
}

Floating-Point Pitfalls

JavaScript’s classic 0.1 + 0.2 === 0.30000000000000004 problem is well known. The golden rule for currency math is to always operate in integer minor units (cents / yen) and only convert to a decimal when formatting for display.

For date formatting, see date and time handling, and for internationalization in general, see the i18n implementation guide.

For the full Intl.NumberFormat spec, see MDN (developer.mozilla.org/Intl/NumberFormat).

#Claude Code #currency #formatting #Intl #internationalization

Level up your Claude Code workflow

50 battle-tested prompt templates you can copy-paste into Claude Code right now.

Free

Free PDF: Claude Code Cheatsheet in 5 Minutes

Key commands, shortcuts, and prompt examples on a single printable page.

Download PDF
M

About the Author

Masa

Engineer obsessed with Claude Code. Runs claudecode-lab.com, a 10-language tech media with 2,000+ pages.