{"_id":"react-currency-localizer","_rev":"2-717715d8663a90159d14f997176a834c","name":"react-currency-localizer","dist-tags":{"latest":"2.1.0"},"versions":{"2.0.0":{"name":"react-currency-localizer","version":"2.0.0","keywords":["react","currency","converter","hook","geolocation","exchange-rate","localization","pricing","internationalization"],"author":{"name":"Jigyansu Rout"},"license":"MIT","_id":"react-currency-localizer@2.0.0","maintainers":[{"name":"iamjr15","email":"jigyanshu15@gmail.com"}],"homepage":"https://github.com/iamjr15/react-currency-localizer#readme","bugs":{"url":"https://github.com/iamjr15/react-currency-localizer/issues"},"dist":{"shasum":"a2f50496ea0bff5bb3e102d5a1776e25d1ec8366","tarball":"https://registry.npmjs.org/react-currency-localizer/-/react-currency-localizer-2.0.0.tgz","fileCount":19,"integrity":"sha512-QEf8agsBezEqffLvfeybdyWUaW9X/NTTwPYxrVYyQBGWorZL83QRoxqvhkwkybPm+WMlMX3jpOfNsmOe7sFI2g==","signatures":[{"sig":"MEYCIQDBDjfSXSHozqevr7v2y4lOyIVVmek0irl4Ol6RNps31gIhANN450uLnvhQnPtiC3tf3ZjLUNGSjsQxdBq0GcfSfGmx","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":283338},"main":"./dist/index.cjs.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.es.js","exports":{".":{"import":"./dist/index.es.js","require":"./dist/index.cjs.js"}},"gitHead":"10bd1eef1ca7037ce89cb9a511c09f61e38c279a","scripts":{"dev":"vite","lint":"eslint .","test":"vitest","build":"tsc && vite build","preview":"vite preview","test:ui":"vitest --ui","type-check":"tsc --noEmit","test:coverage":"vitest --coverage","test:integration":"VITE_RUN_INTEGRATION_TESTS=true vitest --run","test:integration:watch":"VITE_RUN_INTEGRATION_TESTS=true vitest"},"_npmUser":{"name":"iamjr15","email":"jigyanshu15@gmail.com"},"repository":{"url":"git+https://github.com/iamjr15/react-currency-localizer.git","type":"git"},"_npmVersion":"11.5.2","description":"A React hook to display prices in a user's local currency using HTTPS-compatible IP geolocation with robust validation and error handling","directories":{},"_nodeVersion":"24.5.0","dependencies":{"@tanstack/react-query-persist-client":"^5.17.0","@tanstack/query-sync-storage-persister":"^5.17.0"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.5","jsdom":"^26.1.0","eslint":"^9.18.0","vitest":"^3.2.4","globals":"^15.14.0","@eslint/js":"^9.18.0","@vitest/ui":"^3.2.4","node-fetch":"^3.3.2","typescript":"~5.7.2","@types/react":"^18.3.13","vite-plugin-dts":"^4.5.4","@types/react-dom":"^18.3.1","@types/node-fetch":"^2.6.13","typescript-eslint":"^8.19.1","@vitest/coverage-v8":"^3.2.4","@vitejs/plugin-react":"^5.0.0","@testing-library/react":"^16.1.0","@testing-library/jest-dom":"^6.6.4","eslint-plugin-react-hooks":"^5.1.0","@testing-library/user-event":"^14.5.2","eslint-plugin-react-refresh":"^0.4.16"},"peerDependencies":{"react":">=18.0.0","@tanstack/react-query":">=5.0.0"},"_npmOperationalInternal":{"tmp":"tmp/react-currency-localizer_2.0.0_1756382006861_0.35375028534511643","host":"s3://npm-registry-packages-npm-production"}},"2.1.0":{"name":"react-currency-localizer","version":"2.1.0","description":"A React hook to display prices in a user's local currency using HTTPS-compatible IP geolocation with robust validation and error handling","type":"module","main":"./dist/index.cjs.js","module":"./dist/index.es.js","types":"./dist/index.d.ts","exports":{".":{"import":"./dist/index.es.js","require":"./dist/index.cjs.js"}},"scripts":{"dev":"vite","build":"tsc && vite build","preview":"vite preview","test":"vitest","test:ui":"vitest --ui","test:coverage":"vitest --coverage","test:integration":"VITE_RUN_INTEGRATION_TESTS=true vitest --run","test:integration:watch":"VITE_RUN_INTEGRATION_TESTS=true vitest","lint":"eslint .","type-check":"tsc --noEmit"},"keywords":["react","currency","converter","hook","geolocation","exchange-rate","localization","pricing","internationalization"],"author":{"name":"Jigyansu Rout"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/iamjr15/react-currency-localizer.git"},"peerDependencies":{"@tanstack/react-query":">=5.0.0","react":">=18.0.0"},"devDependencies":{"@eslint/js":"^9.39.1","@testing-library/jest-dom":"^6.9.1","@testing-library/react":"^16.3.0","@testing-library/user-event":"^14.6.1","@types/react":"^19.2.7","@types/react-dom":"^19.2.3","@vitejs/plugin-react":"^5.1.2","@vitest/coverage-v8":"^4.0.15","@vitest/ui":"^4.0.15","eslint":"^9.39.1","eslint-plugin-react-hooks":"^7.0.1","eslint-plugin-react-refresh":"^0.4.24","globals":"^16.5.0","jsdom":"^27.3.0","typescript":"~5.9.3","typescript-eslint":"^8.48.1","vite":"^7.2.7","vite-plugin-dts":"^4.5.4","vitest":"^4.0.15"},"dependencies":{"@tanstack/query-sync-storage-persister":"^5.90.14","@tanstack/react-query-persist-client":"^5.90.14"},"_id":"react-currency-localizer@2.1.0","gitHead":"337ba3ece0ee50bcc5afc7ede7d57fb883cfd423","bugs":{"url":"https://github.com/iamjr15/react-currency-localizer/issues"},"homepage":"https://github.com/iamjr15/react-currency-localizer#readme","_nodeVersion":"24.10.0","_npmVersion":"11.6.0","dist":{"integrity":"sha512-w5I97CvgaeDOplGjsZreSbik5rOqW7VisCpsz9YG/IvjFilgmlTXHqlM03vkV+0+AZbPCsO7veswmREaHNK5ww==","shasum":"4aecaf70f5e3e8ec05b8b977e682bc5a4be14bf4","tarball":"https://registry.npmjs.org/react-currency-localizer/-/react-currency-localizer-2.1.0.tgz","fileCount":21,"unpackedSize":352970,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDzRw50ylKsyUr6IzQMDAM7D4G2g+eDzQ/1ppNn1V8c3wIhAIKJXt0f5zPgrqpTibk0BFSfc4KiA7bocxWOPawqQc8i"}]},"_npmUser":{"name":"iamjr15","email":"jigyanshu15@gmail.com"},"directories":{},"maintainers":[{"name":"iamjr15","email":"jigyanshu15@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/react-currency-localizer_2.1.0_1765208179091_0.2611376539900805"},"_hasShrinkwrap":false}},"time":{"created":"2025-08-28T11:53:26.860Z","modified":"2025-12-08T15:36:19.448Z","2.0.0":"2025-08-28T11:53:27.064Z","2.1.0":"2025-12-08T15:36:19.250Z"},"bugs":{"url":"https://github.com/iamjr15/react-currency-localizer/issues"},"author":{"name":"Jigyansu Rout"},"license":"MIT","homepage":"https://github.com/iamjr15/react-currency-localizer#readme","keywords":["react","currency","converter","hook","geolocation","exchange-rate","localization","pricing","internationalization"],"repository":{"type":"git","url":"git+https://github.com/iamjr15/react-currency-localizer.git"},"description":"A React hook to display prices in a user's local currency using HTTPS-compatible IP geolocation with robust validation and error handling","maintainers":[{"name":"iamjr15","email":"jigyanshu15@gmail.com"}],"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> **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    <CurrencyConverterProvider>\n      <YourAppContent />\n    </CurrencyConverterProvider>\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 <span>Loading price...</span>\n  if (error) return <span>${price}</span> // Fallback to original price\n\n  return (\n    <span>\n      {new Intl.NumberFormat(undefined, {\n        style: 'currency',\n        currency: localCurrency || 'USD'\n      }).format(convertedPrice || price)}\n    </span>\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    <div>\n      <h3>Premium Plan</h3>\n      <LocalizedPrice \n        basePrice={99.99}\n        baseCurrency=\"USD\"\n        apiKey=\"your-api-key\"\n      />\n    </div>\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### `<LocalizedPrice />`\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) => number \\| null` | Convert a price |\n| `format` | `(price: number) => string` | Format a price |\n| `convertAndFormat` | `(price: number) => 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    <ul>\n      {products.map(p => (\n        <li key={p.id}>\n          {p.name}: {isReady ? convertAndFormat(p.price) : '...'}\n        </li>\n      ))}\n    </ul>\n  )\n}\n```\n\n### `<CurrencyConverterProvider />`\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    <div className=\"grid\">\n      {products.map(product => (\n        <div key={product.id} className=\"product-card\">\n          <h3>{product.name}</h3>\n          <LocalizedPrice \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          />\n        </div>\n      ))}\n    </div>\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    <div className=\"pricing-table\">\n      {plans.map(plan => (\n        <div key={plan.name} className=\"plan\">\n          <h3>{plan.name}</h3>\n          <LocalizedPrice \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) => \n              `${currency} ${price.toFixed(2)}/month`\n            }\n          />\n        </div>\n      ))}\n    </div>\n  )\n}\n```\n\n### Manual Currency Override\n\n```tsx\nfunction CurrencySelector() {\n  const [selectedCurrency, setSelectedCurrency] = useState('')\n  \n  return (\n    <div>\n      <select \n        value={selectedCurrency} \n        onChange={(e) => setSelectedCurrency(e.target.value)}\n      >\n        <option value=\"\">Auto-detect</option>\n        <option value=\"USD\">USD</option>\n        <option value=\"EUR\">EUR</option>\n        <option value=\"GBP\">GBP</option>\n        <option value=\"JPY\">JPY</option>\n      </select>\n      \n      <LocalizedPrice \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      />\n    </div>\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) => {\n      console.log('Conversion successful:', result)\n    },\n    onError: (error) => {\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      <div className=\"price-loading\">\n        <div className=\"spinner\" />\n        <span>Converting price...</span>\n      </div>\n    )\n  }\n\n  if (error) {\n    return (\n      <div className=\"price-error\">\n        <span>$59.99 USD</span>\n        <small>Unable to convert: {error.message}</small>\n      </div>\n    )\n  }\n\n  return (\n    <div className=\"price-success\">\n      {new Intl.NumberFormat(undefined, {\n        style: 'currency',\n        currency: localCurrency || 'USD'\n      }).format(convertedPrice || 59.99)}\n    </div>\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    <LocalizedPrice \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    />\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    <LocalizedPrice \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) => (\n        <div className=\"price-error\">\n          <span className=\"price\">$99.99</span>\n          <span className=\"error-badge\">Conversion Error</span>\n          <small>{error.message}</small>\n        </div>\n      )}\n    />\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    <CurrencyConverterProvider queryClient={queryClient}>\n      <YourApp />\n    </CurrencyConverterProvider>\n  )\n}\n```\n\n> **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(() => {\n  setIsClient(true)\n}, [])\n\nreturn (\n  <LocalizedPrice \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  />\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<details>\n<summary><strong>Click to view complete list of 161 supported currencies</strong></summary>\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</details>\n\n### ❌ Unsupported Currency (1)\n\n| Code | Currency Name | Country | Reason |\n|------|---------------|---------|---------|\n| **KPW** | North Korean Won | North Korea | Due to sanctions & lack of international trade |\n\n> **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> **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 <50ms 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) => {\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) => {\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\" > .env\necho \"VITE_RUN_INTEGRATION_TESTS=true\" >> .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","readmeFilename":"README.md"}