https://github.com/iamjr15/react-currency-localizer
A lightweight npm package to display prices in a user’s local currency.
https://github.com/iamjr15/react-currency-localizer
currency exchange-api geolocation i18n internationalization localization nextjs pricing react tanstack-react-query typescript vitejs
Last synced: 5 months ago
JSON representation
A lightweight npm package to display prices in a user’s local currency.
- Host: GitHub
- URL: https://github.com/iamjr15/react-currency-localizer
- Owner: iamjr15
- License: mit
- Created: 2025-08-08T00:05:10.000Z (10 months ago)
- Default Branch: master
- Last Pushed: 2025-08-28T12:20:31.000Z (10 months ago)
- Last Synced: 2025-09-14T10:08:42.436Z (9 months ago)
- Topics: currency, exchange-api, geolocation, i18n, internationalization, localization, nextjs, pricing, react, tanstack-react-query, typescript, vitejs
- Language: TypeScript
- Homepage:
- Size: 213 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE.md
Awesome Lists containing this project
README
# React Currency Localizer
[](https://badge.fury.io/js/react-currency-localizer)

[](https://www.typescriptlang.org/)
[](https://opensource.org/licenses/MIT)
A React hook for automatically displaying prices in a user's local currency using IP geolocation. Built with a robust **two-API architecture** using specialized services for maximum accuracy and reliability. Perfect for e-commerce sites, pricing pages, and international applications.
## 🚀 Features
### Core Architecture
- **Two-API Strategy**: Decoupled architecture using specialized services for maximum accuracy
- **Geolocation**: `ipapi.co` for HTTPS-compatible currency detection (no API key required)
- **Exchange Rates**: `exchangerate-api.com` for real-time conversion rates
- **Intelligent Caching**: Multi-tier caching strategy optimized for each data type
- **Persistent Geolocation**: 24-hour localStorage caching (location rarely changes)
- **In-Memory Exchange Rates**: 1-hour memory caching (balances freshness vs performance)
- **TanStack Query Integration**: Advanced state management with automatic deduplication and background updates
### Enhanced Developer Experience
- **Robust Input Validation**: Case-insensitive currency codes and pre-emptive API key validation
- **Graceful Error Handling**: Intelligent fallbacks and specific error messages
- **TypeScript Support**: Fully typed with comprehensive type definitions
- **Multiple Usage Patterns**: Hook-based API, batch converter, and declarative component
- **Automatic Currency Detection**: Uses IP geolocation to detect user's local currency
- **Manual Override Support**: Bypass geolocation with explicit currency selection
- **Configurable Geolocation**: Use custom geo endpoints for flexibility
### Production Ready
- **Lightweight**: ~27kB (gzipped: ~8.2kB) with minimal runtime dependencies
- **Framework Agnostic**: Works with any React application
- **HTTPS Compatible**: Uses HTTPS-only APIs safe for production deployments
- **Free APIs**: Uses only free-tier APIs (no credit card required)
- **Comprehensive Testing**: Extensive suite including real API integration validation
## 📦 Installation
```bash
# npm
npm install react-currency-localizer @tanstack/react-query
# yarn
yarn add react-currency-localizer @tanstack/react-query
# pnpm
pnpm add react-currency-localizer @tanstack/react-query
```
> **Note**: `@tanstack/react-query` is a peer dependency required for caching and data fetching.
## 🏁 Quick Start
### 1. Wrap Your App with the Provider
```tsx
import { CurrencyConverterProvider } from 'react-currency-localizer'
function App() {
return (
)
}
```
### 2. Use the Hook
```tsx
import { useCurrencyConverter } from 'react-currency-localizer'
function ProductPrice({ price }: { price: number }) {
const {
convertedPrice,
localCurrency,
isLoading,
error
} = useCurrencyConverter({
basePrice: price,
baseCurrency: 'USD',
apiKey: 'your-exchangerate-api-key' // Get free key from exchangerate-api.com
})
if (isLoading) return Loading price...
if (error) return ${price} // Fallback to original price
return (
{new Intl.NumberFormat(undefined, {
style: 'currency',
currency: localCurrency || 'USD'
}).format(convertedPrice || price)}
)
}
```
### 3. Or Use the Component
```tsx
import { LocalizedPrice } from 'react-currency-localizer'
function ProductCard() {
return (
Premium Plan
)
}
```
## 📚 API Reference
### `useCurrencyConverter(options)`
The main hook for currency conversion.
#### Parameters
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `basePrice` | `number` | ✅ | The price in the base currency |
| `baseCurrency` | `string` | ✅ | ISO 4217 currency code (case-insensitive, e.g., 'USD' or 'usd') |
| `apiKey` | `string` | ✅ | API key from exchangerate-api.com (validated for helpful error messages) |
| `manualCurrency` | `string` | ❌ | Override detected currency (case-insensitive) |
| `geoEndpoint` | `string` | ❌ | Custom geolocation endpoint URL (defaults to ipapi.co) |
| `onSuccess` | `function` | ❌ | Callback on successful conversion |
| `onError` | `function` | ❌ | Callback on error |
#### Returns
| Property | Type | Description |
|----------|------|-------------|
| `convertedPrice` | `number \| null` | Price in local currency |
| `localCurrency` | `string \| null` | Detected/manual currency code |
| `baseCurrency` | `string` | Original currency code |
| `exchangeRate` | `number \| null` | Exchange rate used |
| `isLoading` | `boolean` | Loading state |
| `error` | `Error \| null` | Error object if any |
### ``
React component for displaying localized prices.
#### Props
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `basePrice` | `number` | ✅ | The price in base currency |
| `baseCurrency` | `string` | ✅ | ISO 4217 currency code (case-insensitive) |
| `apiKey` | `string` | ✅ | ExchangeRate API key (validated automatically) |
| `manualCurrency` | `string` | ❌ | Override detected currency (case-insensitive) |
| `geoEndpoint` | `string` | ❌ | Custom geolocation endpoint URL |
| `loadingComponent` | `ReactNode` | ❌ | Custom loading component |
| `errorComponent` | `function` | ❌ | Custom error component (if not provided, shows original price as fallback) |
| `formatPrice` | `function` | ❌ | Custom price formatter |
### `useCurrencyLocalizer(options)`
Batch-friendly hook for converting multiple prices efficiently. Ideal for e-commerce with many products.
#### Parameters
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `baseCurrency` | `string` | ✅ | ISO 4217 currency code |
| `apiKey` | `string` | ✅ | API key from exchangerate-api.com |
| `manualCurrency` | `string` | ❌ | Override detected currency |
| `geoEndpoint` | `string` | ❌ | Custom geolocation endpoint URL |
| `onReady` | `function` | ❌ | Callback when converter is ready |
| `onError` | `function` | ❌ | Callback on error |
#### Returns
| Property | Type | Description |
|----------|------|-------------|
| `convert` | `(price: number) => number \| null` | Convert a price |
| `format` | `(price: number) => string` | Format a price |
| `convertAndFormat` | `(price: number) => string` | Convert and format in one call |
| `localCurrency` | `string \| null` | Detected currency code |
| `baseCurrency` | `string` | Original currency code |
| `exchangeRate` | `number \| null` | Exchange rate used |
| `isLoading` | `boolean` | Loading state |
| `isReady` | `boolean` | True when ready to convert |
| `error` | `Error \| null` | Error object if any |
#### Example
```tsx
import { useCurrencyLocalizer } from 'react-currency-localizer'
function ProductList({ products }) {
const { convertAndFormat, isReady } = useCurrencyLocalizer({
baseCurrency: 'USD',
apiKey: process.env.REACT_APP_EXCHANGE_API_KEY
})
return (
-
{p.name}: {isReady ? convertAndFormat(p.price) : '...'}
{products.map(p => (
))}
)
}
```
### ``
Provider component for TanStack Query setup.
#### Props
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `children` | `ReactNode` | ✅ | Child components |
| `queryClient` | `QueryClient` | ❌ | Custom QueryClient instance |
| `enablePersistence` | `boolean` | ❌ | Enable localStorage caching (default: `true`) |
## 💡 Usage Examples
### E-commerce Product Pricing
```tsx
function ProductGrid() {
const products = [
{ id: 1, name: 'T-Shirt', price: 29.99 },
{ id: 2, name: 'Jeans', price: 79.99 },
{ id: 3, name: 'Sneakers', price: 129.99 }
]
return (
{products.map(product => (
{product.name}
))}
)
}
```
### Subscription Pricing Table
```tsx
function PricingTable() {
const plans = [
{ name: 'Basic', price: 9.99 },
{ name: 'Pro', price: 19.99 },
{ name: 'Enterprise', price: 49.99 }
]
return (
{plans.map(plan => (
{plan.name}
`${currency} ${price.toFixed(2)}/month`
}
/>
))}
)
}
```
### Manual Currency Override
```tsx
function CurrencySelector() {
const [selectedCurrency, setSelectedCurrency] = useState('')
return (
setSelectedCurrency(e.target.value)}
>
Auto-detect
USD
EUR
GBP
JPY
)
}
```
### Error Handling and Loading States
```tsx
function PriceWithStates() {
const { convertedPrice, localCurrency, isLoading, error } = useCurrencyConverter({
basePrice: 59.99,
baseCurrency: 'usd', // Case-insensitive! Will be converted to 'USD'
// Vite: import.meta.env.VITE_EXCHANGE_API_KEY
// CRA: process.env.REACT_APP_EXCHANGE_API_KEY
// Next.js: process.env.NEXT_PUBLIC_EXCHANGE_API_KEY
apiKey: process.env.REACT_APP_EXCHANGE_API_KEY,
onSuccess: (result) => {
console.log('Conversion successful:', result)
},
onError: (error) => {
console.error('Conversion failed:', error.message)
// You'll get helpful error messages like:
// "API key is missing. Please provide a valid key from exchangerate-api.com."
// "Currency 'XYZ' was detected from your location but is not supported by the exchange rate provider."
}
})
if (isLoading) {
return (
Converting price...
)
}
if (error) {
return (
$59.99 USD
Unable to convert: {error.message}
)
}
return (
{new Intl.NumberFormat(undefined, {
style: 'currency',
currency: localCurrency || 'USD'
}).format(convertedPrice || 59.99)}
)
}
```
### Graceful Fallback Behavior
```tsx
// LocalizedPrice automatically shows original price if conversion fails
function AutoFallbackPrice() {
return (
// If API fails: Shows "$99.99" with tooltip "Conversion failed, showing original price in USD"
)
}
// Or provide custom error component for full control
function CustomErrorPrice() {
return (
(
$99.99
Conversion Error
{error.message}
)}
/>
)
}
```
## 🏗️ Architecture Overview
This package uses a **carefully designed architecture** for maximum reliability and performance:
### Two-API Strategy
We use a **decoupled, two-API approach** for maximum accuracy and flexibility:
1. **Specialized Geolocation Service**: `ipapi.co`
- **Why**: Dedicated to IP-based location data with HTTPS support for optimal accuracy
- **Advantage**: No API key required, HTTPS-compatible, robust rate limiting
- **Philosophy**: Use specialized services for what they do best—identifying location-based data
2. **Specialized Financial Data Service**: `exchangerate-api.com`
- **Why**: Dedicated to currency exchange rates for real-time accuracy
- **Advantage**: Supports any base currency (not just USD), 1,500 requests/month
- **Philosophy**: Use specialized services for what they do best—providing currency exchange rates
### Advanced State Management
We use **TanStack Query** (not basic useEffect/useState) for robust data management:
- **Automatic caching, request deduplication, and background updates**
- **Eliminates race conditions and boilerplate code**
- **Declarative API for managing asynchronous operations**
### Intelligent Caching Strategy
We implement **data-type-specific caching** optimized for each type of data:
#### Persistent Geolocation Caching
- **Duration**: 24+ hours in localStorage
- **Rationale**: User location rarely changes
- **Benefit**: Zero API calls on subsequent visits
#### In-Memory Exchange Rate Caching
- **Duration**: 1 hour in memory
- **Rationale**: Balance between data freshness and performance
- **Benefit**: Prevents excessive API calls while maintaining accuracy
## 🔧 Configuration
### Getting API Keys
1. **ExchangeRate-API** (Required):
- Visit [exchangerate-api.com](https://exchangerate-api.com)
- Sign up for a free account
- Get your API key (1,500 requests/month free)
2. **IP Geolocation** (Automatic):
- Uses [ipapi.co](https://ipapi.co) (no key required)
- HTTPS-compatible for secure deployments
- Falls back gracefully on rate limits
### Environment Variables
```bash
# .env
VITE_EXCHANGE_API_KEY=your_exchangerate_api_key_here
# Optional: Enable integration tests with real APIs
VITE_RUN_INTEGRATION_TESTS=true
```
For different frameworks:
```bash
# Vite (browser-exposed)
VITE_EXCHANGE_API_KEY=your_exchangerate_api_key_here
# Create React App (browser-exposed)
REACT_APP_EXCHANGE_API_KEY=your_exchangerate_api_key_here
# Next.js (browser-exposed)
NEXT_PUBLIC_EXCHANGE_API_KEY=your_exchangerate_api_key_here
```
### Custom QueryClient Configuration
The package uses **TanStack Query** for robust state management with optimal caching:
```tsx
import { QueryClient } from '@tanstack/react-query'
import { CurrencyConverterProvider } from 'react-currency-localizer'
// Custom QueryClient with optimized caching strategy
const queryClient = new QueryClient({
defaultOptions: {
queries: {
// Geolocation queries: 24+ hour stale time (location rarely changes)
staleTime: 1000 * 60 * 60 * 24, // 24 hours
// Exchange rate queries: 1 hour stale time (balance freshness vs performance)
gcTime: 1000 * 60 * 60 * 2, // 2 hours cache time
retry: 1, // Conservative retry to respect API limits
},
},
})
function App() {
return (
)
}
```
> **Note**: The default configuration already implements the optimal caching strategy. Custom configuration is optional.
## ⚠️ Important Considerations
### Server-Side Rendering (SSR) Caveats
When using this library with SSR frameworks (Next.js, Nuxt, SvelteKit), be aware that:
- **IP Geolocation runs on the server**: The detected location reflects the server's IP, not the user's
- **Hydration mismatches possible**: Server-rendered currency may differ from client-side detection
- **Recommended approach**: Pass `manualCurrency` prop or perform geolocation client-side only
```tsx
// For SSR: Disable geolocation on server, enable on client
const [isClient, setIsClient] = useState(false)
useEffect(() => {
setIsClient(true)
}, [])
return (
)
```
### HTTPS Requirements
This library uses HTTPS-only APIs (`ipapi.co`, `exchangerate-api.com`) to ensure compatibility with secure deployments. Mixed content (HTTP requests from HTTPS sites) is automatically blocked by modern browsers.
### Runtime Dependencies
While the library has minimal dependencies, it does include:
- `@tanstack/react-query` (peer dependency for state management)
- `@tanstack/query-sync-storage-persister` (for localStorage caching)
- `@tanstack/react-query-persist-client` (for persistence integration)
Total bundle impact: ~27kB minified, ~8.2kB gzipped. Set `enablePersistence={false}` on the provider to reduce size if persistence isn't needed.
## 🌍 Supported Currencies
The package supports **161 commonly circulating world currencies** via ExchangeRate-API, covering 99% of all UN recognized states and territories.
### ✅ Complete Currency Support (161 Currencies)
All [ISO 4217 three-letter currency codes](https://en.wikipedia.org/wiki/ISO_4217) are supported, including:
#### Major Global Currencies
- **USD** - United States Dollar
- **EUR** - Euro (European Union)
- **GBP** - Pound Sterling (United Kingdom)
- **JPY** - Japanese Yen
- **CAD** - Canadian Dollar
- **AUD** - Australian Dollar
- **CHF** - Swiss Franc
- **CNY** - Chinese Renminbi
#### Popular Regional Currencies
- **INR** - Indian Rupee
- **BRL** - Brazilian Real
- **RUB** - Russian Ruble
- **KRW** - South Korean Won
- **SGD** - Singapore Dollar
- **HKD** - Hong Kong Dollar
- **NOK** - Norwegian Krone
- **SEK** - Swedish Krona
- **MXN** - Mexican Peso
- **ZAR** - South African Rand
- **TRY** - Turkish Lira
#### All Supported Currencies
Click to view complete list of 161 supported currencies
| Code | Currency Name | Country/Region |
|------|---------------|----------------|
| AED | UAE Dirham | United Arab Emirates |
| AFN | Afghan Afghani | Afghanistan |
| ALL | Albanian Lek | Albania |
| AMD | Armenian Dram | Armenia |
| ANG | Netherlands Antillian Guilder | Netherlands Antilles |
| AOA | Angolan Kwanza | Angola |
| ARS | Argentine Peso | Argentina |
| AUD | Australian Dollar | Australia |
| AWG | Aruban Florin | Aruba |
| AZN | Azerbaijani Manat | Azerbaijan |
| BAM | Bosnia and Herzegovina Mark | Bosnia and Herzegovina |
| BBD | Barbados Dollar | Barbados |
| BDT | Bangladeshi Taka | Bangladesh |
| BGN | Bulgarian Lev | Bulgaria |
| BHD | Bahraini Dinar | Bahrain |
| BIF | Burundian Franc | Burundi |
| BMD | Bermudian Dollar | Bermuda |
| BND | Brunei Dollar | Brunei |
| BOB | Bolivian Boliviano | Bolivia |
| BRL | Brazilian Real | Brazil |
| BSD | Bahamian Dollar | Bahamas |
| BTN | Bhutanese Ngultrum | Bhutan |
| BWP | Botswana Pula | Botswana |
| BYN | Belarusian Ruble | Belarus |
| BZD | Belize Dollar | Belize |
| CAD | Canadian Dollar | Canada |
| CDF | Congolese Franc | Democratic Republic of the Congo |
| CHF | Swiss Franc | Switzerland |
| CLP | Chilean Peso | Chile |
| CNY | Chinese Renminbi | China |
| COP | Colombian Peso | Colombia |
| CRC | Costa Rican Colon | Costa Rica |
| CUP | Cuban Peso | Cuba |
| CVE | Cape Verdean Escudo | Cape Verde |
| CZK | Czech Koruna | Czech Republic |
| DJF | Djiboutian Franc | Djibouti |
| DKK | Danish Krone | Denmark |
| DOP | Dominican Peso | Dominican Republic |
| DZD | Algerian Dinar | Algeria |
| EGP | Egyptian Pound | Egypt |
| ERN | Eritrean Nakfa | Eritrea |
| ETB | Ethiopian Birr | Ethiopia |
| EUR | Euro | European Union |
| FJD | Fiji Dollar | Fiji |
| FKP | Falkland Islands Pound | Falkland Islands |
| FOK | Faroese Króna | Faroe Islands |
| GBP | Pound Sterling | United Kingdom |
| GEL | Georgian Lari | Georgia |
| GGP | Guernsey Pound | Guernsey |
| GHS | Ghanaian Cedi | Ghana |
| GIP | Gibraltar Pound | Gibraltar |
| GMD | Gambian Dalasi | The Gambia |
| GNF | Guinean Franc | Guinea |
| GTQ | Guatemalan Quetzal | Guatemala |
| GYD | Guyanese Dollar | Guyana |
| HKD | Hong Kong Dollar | Hong Kong |
| HNL | Honduran Lempira | Honduras |
| HRK | Croatian Kuna | Croatia |
| HTG | Haitian Gourde | Haiti |
| HUF | Hungarian Forint | Hungary |
| IDR | Indonesian Rupiah | Indonesia |
| ILS | Israeli New Shekel | Israel |
| IMP | Manx Pound | Isle of Man |
| INR | Indian Rupee | India |
| IQD | Iraqi Dinar | Iraq |
| IRR | Iranian Rial | Iran |
| ISK | Icelandic Króna | Iceland |
| JEP | Jersey Pound | Jersey |
| JMD | Jamaican Dollar | Jamaica |
| JOD | Jordanian Dinar | Jordan |
| JPY | Japanese Yen | Japan |
| KES | Kenyan Shilling | Kenya |
| KGS | Kyrgyzstani Som | Kyrgyzstan |
| KHR | Cambodian Riel | Cambodia |
| KID | Kiribati Dollar | Kiribati |
| KMF | Comorian Franc | Comoros |
| KRW | South Korean Won | South Korea |
| KWD | Kuwaiti Dinar | Kuwait |
| KYD | Cayman Islands Dollar | Cayman Islands |
| KZT | Kazakhstani Tenge | Kazakhstan |
| LAK | Lao Kip | Laos |
| LBP | Lebanese Pound | Lebanon |
| LKR | Sri Lanka Rupee | Sri Lanka |
| LRD | Liberian Dollar | Liberia |
| LSL | Lesotho Loti | Lesotho |
| LYD | Libyan Dinar | Libya |
| MAD | Moroccan Dirham | Morocco |
| MDL | Moldovan Leu | Moldova |
| MGA | Malagasy Ariary | Madagascar |
| MKD | Macedonian Denar | North Macedonia |
| MMK | Burmese Kyat | Myanmar |
| MNT | Mongolian Tögrög | Mongolia |
| MOP | Macanese Pataca | Macau |
| MRU | Mauritanian Ouguiya | Mauritania |
| MUR | Mauritian Rupee | Mauritius |
| MVR | Maldivian Rufiyaa | Maldives |
| MWK | Malawian Kwacha | Malawi |
| MXN | Mexican Peso | Mexico |
| MYR | Malaysian Ringgit | Malaysia |
| MZN | Mozambican Metical | Mozambique |
| NAD | Namibian Dollar | Namibia |
| NGN | Nigerian Naira | Nigeria |
| NIO | Nicaraguan Córdoba | Nicaragua |
| NOK | Norwegian Krone | Norway |
| NPR | Nepalese Rupee | Nepal |
| NZD | New Zealand Dollar | New Zealand |
| OMR | Omani Rial | Oman |
| PAB | Panamanian Balboa | Panama |
| PEN | Peruvian Sol | Peru |
| PGK | Papua New Guinean Kina | Papua New Guinea |
| PHP | Philippine Peso | Philippines |
| PKR | Pakistani Rupee | Pakistan |
| PLN | Polish Złoty | Poland |
| PYG | Paraguayan Guaraní | Paraguay |
| QAR | Qatari Riyal | Qatar |
| RON | Romanian Leu | Romania |
| RSD | Serbian Dinar | Serbia |
| RUB | Russian Ruble | Russia |
| RWF | Rwandan Franc | Rwanda |
| SAR | Saudi Riyal | Saudi Arabia |
| SBD | Solomon Islands Dollar | Solomon Islands |
| SCR | Seychellois Rupee | Seychelles |
| SDG | Sudanese Pound | Sudan |
| SEK | Swedish Krona | Sweden |
| SGD | Singapore Dollar | Singapore |
| SHP | Saint Helena Pound | Saint Helena |
| SLE | Sierra Leonean Leone | Sierra Leone |
| SOS | Somali Shilling | Somalia |
| SRD | Surinamese Dollar | Suriname |
| SSP | South Sudanese Pound | South Sudan |
| STN | São Tomé and Príncipe Dobra | São Tomé and Príncipe |
| SYP | Syrian Pound | Syria |
| SZL | Eswatini Lilangeni | Eswatini |
| THB | Thai Baht | Thailand |
| TJS | Tajikistani Somoni | Tajikistan |
| TMT | Turkmenistan Manat | Turkmenistan |
| TND | Tunisian Dinar | Tunisia |
| TOP | Tongan Paʻanga | Tonga |
| TRY | Turkish Lira | Turkey |
| TTD | Trinidad and Tobago Dollar | Trinidad and Tobago |
| TVD | Tuvaluan Dollar | Tuvalu |
| TWD | New Taiwan Dollar | Taiwan |
| TZS | Tanzanian Shilling | Tanzania |
| UAH | Ukrainian Hryvnia | Ukraine |
| UGX | Ugandan Shilling | Uganda |
| USD | United States Dollar | United States |
| UYU | Uruguayan Peso | Uruguay |
| UZS | Uzbekistani So'm | Uzbekistan |
| VES | Venezuelan Bolívar Soberano | Venezuela |
| VND | Vietnamese Đồng | Vietnam |
| VUV | Vanuatu Vatu | Vanuatu |
| WST | Samoan Tālā | Samoa |
| XAF | Central African CFA Franc | CEMAC |
| XCD | East Caribbean Dollar | Organisation of Eastern Caribbean States |
| XDR | Special Drawing Rights | International Monetary Fund |
| XOF | West African CFA franc | CFA |
| XPF | CFP Franc | Collectivités d'Outre-Mer |
| YER | Yemeni Rial | Yemen |
| ZAR | South African Rand | South Africa |
| ZMW | Zambian Kwacha | Zambia |
| ZWL | Zimbabwean Dollar | Zimbabwe |
### ❌ Unsupported Currency (1)
| Code | Currency Name | Country | Reason |
|------|---------------|---------|---------|
| **KPW** | North Korean Won | North Korea | Due to sanctions & lack of international trade |
> **Note**: For KPW data, the only reliable source is [Daily NK](https://www.dailynk.com/), which reports actual jangmadang market rates via human sources inside DPRK.
### ⚠️ Volatile Currencies (Special Caution Required)
The following currencies experience heightened volatility and substantial differences between official and actual exchange rates. Extra caution is recommended:
| Code | Currency Name | Country | Notes |
|------|---------------|---------|-------|
| **ARS** | Argentine Peso | Argentina | Multiple exchange rates in market |
| **LYD** | Libyan Dinar | Libya | Political instability affects rates |
| **SSP** | South Sudanese Pound | South Sudan | High volatility |
| **SYP** | Syrian Pound | Syria | Ongoing conflict impacts rates |
| **VES** | Venezuelan Bolívar Soberano | Venezuela | Hyperinflation environment |
| **YER** | Yemeni Rial | Yemen | Regional instability |
| **ZWL** | Zimbabwean Dollar | Zimbabwe | Historical hyperinflation legacy |
> **Important**: For volatile currencies, rates default to central bank published rates, which may differ significantly from actual market rates.
## ⚡ Performance
- **Bundle Size**: ~27kB minified, ~8.2kB gzipped
- **First Load**: 2 API calls (geolocation + exchange rates)
- **Subsequent Loads**: 0-1 API calls (cached geolocation)
- **Cache Duration**: 24 hours for geolocation, 1 hour for exchange rates
- **Batch Conversion**: Convert 1000+ prices in <50ms after initial load
- **Memoized Formatters**: `Intl.NumberFormat` instances are cached
- **Rate Limits**: Handled gracefully with automatic fallbacks
## 🔍 Troubleshooting
### Common Issues and Solutions
#### "API key is missing" Error
```tsx
// ❌ This will show a helpful error message
useCurrencyConverter({
basePrice: 99.99,
baseCurrency: 'USD',
apiKey: '' // Empty key
})
// ✅ Provide your API key
useCurrencyConverter({
basePrice: 99.99,
baseCurrency: 'USD',
apiKey: process.env.REACT_APP_EXCHANGE_API_KEY
})
```
#### Currency Case Sensitivity
```tsx
// ✅ All of these work the same way (case-insensitive)
useCurrencyConverter({ baseCurrency: 'USD', manualCurrency: 'EUR' })
useCurrencyConverter({ baseCurrency: 'usd', manualCurrency: 'eur' })
useCurrencyConverter({ baseCurrency: 'Usd', manualCurrency: 'Eur' })
```
#### Currency Not Supported Error
```tsx
// If you get: "Currency 'KPW' was detected from your location but is not supported"
// Solution: Use manual currency override (KPW is the only unsupported currency)
useCurrencyConverter({
basePrice: 99.99,
baseCurrency: 'USD',
apiKey: 'your-key',
manualCurrency: 'USD' // Override KPW with a supported currency
})
// For volatile currencies (ARS, VES, etc.), rates may differ from market reality
useCurrencyConverter({
basePrice: 99.99,
baseCurrency: 'USD',
apiKey: 'your-key',
manualCurrency: 'ARS', // Works, but be aware of potential rate discrepancies
onSuccess: (result) => {
console.log('Note: ARS rates may differ from actual market rates')
}
})
```
#### Rate Limiting
```tsx
// The package automatically handles rate limits, but you can also:
useCurrencyConverter({
basePrice: 99.99,
baseCurrency: 'USD',
apiKey: 'your-key',
onError: (error) => {
if (error.message.includes('429')) {
// Handle rate limiting gracefully
console.log('Rate limited, showing original price')
}
}
})
```
## 🧪 Testing
### Unit Tests (with Mocks)
Run the standard test suite with mocked APIs:
```bash
npm test
```
Run with coverage:
```bash
npm run test:coverage
```
### Integration Tests (with Real APIs)
To test with real API calls, first set up your environment:
```bash
# Create .env file with your real API key
echo "VITE_EXCHANGE_API_KEY=your_actual_api_key_here" > .env
echo "VITE_RUN_INTEGRATION_TESTS=true" >> .env
```
Then run integration tests:
```bash
# Run once with real APIs
npm run test:integration
# Watch mode with real APIs
npm run test:integration:watch
```
**⚠️ Note**: Integration tests make real API calls and will:
- Consume your API quota (free tier: 1,500 requests/month)
- Require internet connection
- Take longer to complete (5-15 seconds per test)
- May fail due to network issues or rate limits
### Test Coverage
The package includes comprehensive tests covering:
- **Unit Tests**: Hook functionality, component rendering, error handling
- **Integration Tests**: Real API calls, caching performance, rate limiting
- **Validation Tests**: Currency normalization, API key validation
- **Edge Cases**: Zero prices, error scenarios, network failures
- **TypeScript**: Full type checking and IntelliSense
- **Total**: 61 tests
## 🤝 Contributing
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
### Development Setup
```bash
# Clone the repository
git clone https://github.com/iamjr15/react-currency-localizer.git
# Install dependencies
npm install
# Run tests
npm test
# Build the package
npm run build
# Run linting
npm run lint
```
## 📜 License
MIT © [Jigyansu Rout](https://github.com/iamjr15)
## ✨ Key Design Decisions
This implementation is built on carefully considered architectural choices:
✅ **Two-API Architecture** - Specialized services for maximum accuracy
✅ **TanStack Query Integration** - Enterprise-grade state management
✅ **Intelligent Caching Strategy** - Optimized for each data type
✅ **Hook-Based API** - Modern React patterns
✅ **LocalizedPrice Component** - Declarative wrapper component
✅ **TypeScript Support** - Full type safety and IntelliSense
✅ **Free APIs Only** - Zero cost barrier to entry
✅ **ipapi.co Selection** - HTTPS-compatible geolocation service
✅ **ExchangeRate-API.com Selection** - Reliable currency data provider
## 🙏 Acknowledgments
- [ExchangeRate-API](https://exchangerate-api.com) for reliable exchange rate data
- [ipapi.co](https://ipapi.co) for free HTTPS-compatible IP geolocation services
- [TanStack Query](https://tanstack.com/query) for excellent caching and data fetching
- The React community for inspiration and feedback
---
Made with ❤️ for the React community