Internationalization
Currency Support

Multi-Currency Support

TimberCloud provides comprehensive multi-currency support powered by Stripe, enabling you to process payments in 135+ currencies worldwide with automatic decimal handling and proper currency formatting.

Supported Currencies

TimberCloud supports all currencies available through Stripe's payment processing. Currency data is sourced from the ISO 4217 standard and the currency-codes npm package for maximum accuracy.

Major World Currencies

CurrencyCodeSymbolDecimals
US DollarUSD$2
EuroEUR2
British PoundGBP£2
Japanese YenJPY¥0
Canadian DollarCADC$2
Australian DollarAUDA$2
Swiss FrancCHFCHF2
Chinese YuanCNY¥2

Regional Currencies

CurrencyCodeSymbolDecimals
Indian RupeeINR2
Brazilian RealBRLR$2
Mexican PesoMXN$2
Singapore DollarSGDS$2
Hong Kong DollarHKDHK$2
New Zealand DollarNZDNZ$2
South African RandZARR2
UAE DirhamAEDد.إ2
Saudi RiyalSAR2
Thai BahtTHB฿2

Zero-Decimal Currencies

These currencies do not use decimal places. Amounts are stored and processed as whole units:

CurrencyCodeSymbolRegion
Japanese YenJPY¥Japan
South Korean WonKRWSouth Korea
Vietnamese DongVNDVietnam
Indonesian RupiahIDRRpIndonesia
Chilean PesoCLP$Chile
Hungarian ForintHUFFtHungary
Icelandic KrónaISKkrIceland
New Taiwan DollarTWDNT$Taiwan
Burundian FrancBIFFBuBurundi
Djiboutian FrancDJFFdjDjibouti
Guinean FrancGNFFGGuinea
Comorian FrancKMFCFComoros
Malagasy AriaryMGAArMadagascar
Paraguayan GuaraníPYGParaguay
Rwandan FrancRWFFRwRwanda
Ugandan ShillingUGXUShUganda
Vanuatu VatuVUVVTVanuatu
CFA Franc (BEAC)XAFFCFACentral Africa
CFA Franc (BCEAO)XOFCFAWest Africa
CFP FrancXPFPacific

Three-Decimal Currencies

These currencies use three decimal places:

CurrencyCodeSymbolRegion
Bahraini DinarBHDBDBahrain
Iraqi DinarIQDع.دIraq
Jordanian DinarJODJDJordan
Kuwaiti DinarKWDKDKuwait
Libyan DinarLYDLDLibya
Omani RialOMROMROman
Tunisian DinarTNDDTTunisia

Setting Up Currency

Company Currency Configuration

  1. Navigate to SettingsCompanyLocale & Currency Settings
  2. Select your country from the dropdown
  3. By default, currency is automatically detected from country:
    • United States → USD
    • United Kingdom → GBP
    • Canada → CAD
    • European countries → EUR
    • Japan → JPY
  4. To override, uncheck "Lock to country currency"
  5. Select your preferred currency from the dropdown
  6. Click Save Changes

Currency Fields in Company Settings

FieldDescription
currencyISO 4217 currency code (e.g., USD, EUR, GBP)
currency_symbolDisplay symbol (e.g., $, €, £)

The currency symbol is automatically set when you select a currency, but can be customized if needed.

Currency Formatting

Using the Currency Formatter Hook

The useCurrencyFormatter hook provides comprehensive currency formatting:

import { useCurrencyFormatter } from '@/hooks/useCurrencyFormatter';
 
function OrderTotal({ company, subtotal, tax, total }) {
  const { format, formatNumber, config } = useCurrencyFormatter(company);
  
  return (
    <div>
      <p>Subtotal: {format(subtotal)}</p>     {/* $1,234.56 */}
      <p>Tax: {format(tax)}</p>               {/* $98.76 */}
      <p>Total: {format(total)}</p>           {/* $1,333.32 */}
      
      {/* Access configuration */}
      <p>Currency: {config.currency}</p>      {/* USD */}
      <p>Symbol: {config.symbol}</p>          {/* $ */}
      <p>Decimals: {config.decimals}</p>      {/* 2 */}
    </div>
  );
}

Formatter Methods

MethodDescriptionExample
format(amount)Format with currency symbol$1,234.56
formatNumber(amount)Format without symbol1,234.56
formatForInput(amount)Plain number for inputs1234.56
parse(formatted)Parse formatted string to number1234.56
toSmallestUnit(amount)Convert to Stripe cents/smallest unit123456
fromSmallestUnit(amount)Convert from Stripe to normal1234.56
isValidAmount(value)Validate currency inputtrue/false

Locale-Specific Formatting

Currency display adapts to the company's locale:

// US locale (en-US)
format(1234.56); // "$1,234.56"
 
// German locale (de-DE)
format(1234.56); // "1.234,56 €"
 
// Japanese locale (ja-JP, zero decimal)
format(1234); // "¥1,234"
 
// Indian locale (en-IN, lakh system)
format(1234567.89); // "₹12,34,567.89"

Stripe Integration

Currency Unit Conversion

Stripe processes amounts in the smallest currency unit. TimberCloud handles this automatically:

import i18n from '@/util/i18n';
 
// USD: $100.00 → 10000 cents
const stripeAmount = i18n.toStripeCurrencyUnit(100, 'USD');
// Result: 10000
 
// JPY: ¥100 → 100 (no conversion, zero-decimal)
const stripeAmountJPY = i18n.toStripeCurrencyUnit(100, 'JPY');
// Result: 100
 
// KWD: KD 1.500 → 1500 fils (3 decimals)
const stripeAmountKWD = i18n.toStripeCurrencyUnit(1.5, 'KWD');
// Result: 1500
 
// Converting back from Stripe
const amount = i18n.fromStripeCurrencyUnit(10000, 'USD');
// Result: 100.00

Getting Currency Multiplier

import i18n from '@/util/i18n';
 
i18n.getCurrencyMultiplier('USD'); // 100 (2 decimals)
i18n.getCurrencyMultiplier('JPY'); // 1 (0 decimals)
i18n.getCurrencyMultiplier('KWD'); // 1000 (3 decimals)

Decimal Places

import i18n from '@/util/i18n';
 
i18n.getDecimalPlaces('USD'); // 2
i18n.getDecimalPlaces('EUR'); // 2
i18n.getDecimalPlaces('JPY'); // 0
i18n.getDecimalPlaces('KRW'); // 0
i18n.getDecimalPlaces('BHD'); // 3
i18n.getDecimalPlaces('KWD'); // 3

Country to Currency Mapping

TimberCloud automatically maps countries to their primary currency:

import i18n from '@/util/i18n';
 
// Get currency by country code
const usCurrency = i18n.getCurrencyByCountry('US');
// { code: 'USD', symbol: '$', name: 'US Dollar', digits: 2 }
 
const ukCurrency = i18n.getCurrencyByCountry('GB');
// { code: 'GBP', symbol: '£', name: 'Pound Sterling', digits: 2 }
 
const jpCurrency = i18n.getCurrencyByCountry('JP');
// { code: 'JPY', symbol: '¥', name: 'Yen', digits: 0 }
 
// Works with various input formats
i18n.getCurrencyByCountry('usa');     // USD
i18n.getCurrencyByCountry('UK');      // GBP (normalized)
i18n.getCurrencyByCountry('GBR');     // GBP (alpha-3 supported)

European Union

All EU member states default to EUR:

  • Germany, France, Spain, Italy, Netherlands
  • Belgium, Austria, Ireland, Portugal, Finland
  • Greece, Luxembourg, Slovakia, Slovenia, Malta
  • Cyprus, Estonia, Latvia, Lithuania

Currency Validation

Check Currency Support

import i18n from '@/util/i18n';
 
// Check if currency code is valid ISO 4217
i18n.isSupportedCurrency('USD'); // true
i18n.isSupportedCurrency('EUR'); // true
i18n.isSupportedCurrency('XXX'); // false (not a real currency)
 
// Check if currency is supported by Stripe
i18n.isStripeSupportedCurrency('USD'); // true
i18n.isStripeSupportedCurrency('CNY'); // true
i18n.isStripeSupportedCurrency('IRR'); // false (not supported by Stripe)

Get Currency Details

import i18n from '@/util/i18n';
 
const currency = i18n.getCurrencyByCode('GBP');
// {
//   code: 'GBP',
//   symbol: '£',
//   name: 'Pound Sterling',
//   digits: 2,
//   number: '826'
// }

Currency Symbols

TimberCloud provides proper currency symbols for all supported currencies:

SymbolCurrencies
$USD, CAD, AUD, NZD, SGD, HKD, MXN, CLP, COP, ARS
EUR
£GBP
¥JPY, CNY
INR
KRW
VND
PHP
฿THB
RUB
TRY
ILS
R$BRL
RZAR
krSEK, NOK, DKK, ISK
PLN
CZK

Best Practices

1. Store Raw Values

Always store monetary amounts as raw numbers in the database, without formatting:

// ✓ Good: Store raw number
order.total = 1234.56;
 
// ✗ Bad: Don't store formatted strings
order.total = "$1,234.56";

2. Format Only for Display

Apply formatting only when displaying to users:

// In database/calculations: raw numbers
const subtotal = 100.00;
const tax = 8.25;
const total = subtotal + tax; // 108.25
 
// In UI: formatted display
return <span>{formatter.format(total)}</span>; // "$108.25"

3. Use Proper Stripe Conversion

Always convert to smallest unit before Stripe API calls:

// ✓ Correct
const stripeAmount = i18n.toStripeCurrencyUnit(order.total, order.currency);
await stripe.paymentIntents.create({
  amount: stripeAmount,
  currency: order.currency.toLowerCase(),
});
 
// ✗ Wrong: Passing decimal amount directly
await stripe.paymentIntents.create({
  amount: order.total, // This will be wrong!
  currency: order.currency,
});

4. Handle Zero-Decimal Currencies

Be aware of zero-decimal currencies in calculations:

const currency = 'JPY';
const decimals = i18n.getDecimalPlaces(currency);
 
if (decimals === 0) {
  // Round to whole number for JPY
  total = Math.round(total);
}

5. Validate Currency on Input

Always validate currency codes before processing:

if (!i18n.isSupportedCurrency(currencyCode)) {
  throw new Error(`Unsupported currency: ${currencyCode}`);
}
 
if (!i18n.isStripeSupportedCurrency(currencyCode)) {
  throw new Error(`Currency not supported for payment: ${currencyCode}`);
}

Troubleshooting

"Currency not supported" Error

  • Verify the currency code is valid ISO 4217
  • Check that Stripe supports the currency for your account type
  • Some currencies are restricted by region or account type

Incorrect Decimal Display

  • Check that decimals is correctly set for the currency
  • Verify locale formatting isn't adding/removing decimals
  • For zero-decimal currencies, ensure amounts are whole numbers

Symbol Display Issues

  • Currency symbols are derived from Intl.NumberFormat when available
  • Fallback to static symbol map for unsupported browsers
  • Some symbols may display differently across fonts

Stripe Amount Mismatch

  • Always use toStripeCurrencyUnit() before Stripe API calls
  • Verify you're using the correct multiplier (100, 1, or 1000)
  • Double-check zero-decimal currency handling

Parsing Formatted Values

  • Use formatter.parse() to correctly parse locale-formatted numbers
  • European locales use , as decimal separator (1.234,56)
  • Some locales use space as thousands separator (1 234,56)