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