@allratestoday/react-currency-localizer-realtime
v2.0.0
Published
React hooks and components that show prices in your visitor's local currency. Works with no API key (free daily ECB rates) — add a free AllRatesToday key for real-time mid-market rates across 160+ currencies.
Downloads
177
Maintainers
Readme
Scoped mirror of
react-currency-localizer-realtimepublished by the AllRatesToday org — same code, same versions.
react-currency-localizer-realtime
Show prices in your visitor's local currency. No API key needed to start.
import { LocalizedPrice } from '@allratestoday/react-currency-localizer-realtime'
<LocalizedPrice basePrice={99.99} baseCurrency="USD" />
// A visitor in Tokyo sees "¥15,300", in Berlin "86,20 €", in London "£75.99"That's the whole integration. Country detection and the exchange rate both come from AllRatesToday — keyless, free, no signup.
See it running on a real pricing page: allratestoday.com/pricing (it uses this same logic in keyed mode).
Two modes
| | Keyless (default) | With API key |
|---|---|---|
| Setup | none | apiKey="art_live_…" — free key |
| Rates | ECB daily reference rates | Real-time mid-market, updated every minute |
| Currencies | ~30 (USD, GBP, JPY, CHF, CAD, AUD, CNY, INR, BRL, MXN, KRW, SEK, NOK, DKK, PLN, CZK, HUF, RON, TRY, ZAR, SGD, HKD, NZD, ILS, IDR, MYR, PHP, THB, ISK) | 160+ |
| Cost | Free, forever | Free tier, paid plans for volume |
| Condition | Visible "Rates by AllRatesToday" link (rendered for you) | none |
Start keyless. Add a key when you need a currency the ECB doesn't publish, or real-time rates for checkout-grade accuracy.
Features
- 🆓 Zero setup — no signup, no key, no third-party geo-IP service
- 🌍 Automatic currency detection — from the visitor's IP, via Cloudflare's country header on allratestoday.com (no ipapi.co dependency, no daily cap)
- ⚡ Real-time upgrade path — add a key for 60-second mid-market rates and 160+ currencies
- 🧠 Aggressive caching — one ECB table fetch per hour serves every price on the page; geo cached 24h
- 🔀 Hook, batch hook, or component — pick the level of control you want
- 🛡️ Graceful fallbacks — the original price is always shown if anything fails
- 🔷 TypeScript, 📦 zero runtime deps, 🪶 ~4 KB gzipped
Installation
npm install @allratestoday/react-currency-localizer-realtime
# yarn add / pnpm add @allratestoday/react-currency-localizer-realtimeReact 17+ is the only peer dependency.
Quick start
1. Component (simplest)
import { LocalizedPrice } from '@allratestoday/react-currency-localizer-realtime'
function ProductCard() {
return (
<div>
<h3>Premium Plan</h3>
<LocalizedPrice basePrice={99.99} baseCurrency="USD" />
</div>
)
}Keyless renders ¥15,300 ECB rates via AllRatesToday. Pass apiKey and the attribution disappears.
2. Hook (full control)
import { useCurrencyConverter, RatesAttribution } from '@allratestoday/react-currency-localizer-realtime'
function ProductPrice({ price }: { price: number }) {
const { convertedPrice, localCurrency, rateSource, isLoading, error } = useCurrencyConverter({
basePrice: price,
baseCurrency: 'USD',
})
if (isLoading) return <span>…</span>
if (error) return <span>${price}</span>
return (
<span>
{new Intl.NumberFormat(undefined, { style: 'currency', currency: localCurrency! }).format(convertedPrice!)}
<RatesAttribution rateSource={rateSource} />
</span>
)
}3. Batch conversion (product lists, pricing tables)
One rate lookup, unlimited prices:
import { useCurrencyLocalizer, RatesAttribution } from '@allratestoday/react-currency-localizer-realtime'
function ProductList({ products }) {
const { convertAndFormat, isReady, rateSource } = useCurrencyLocalizer({ baseCurrency: 'USD' })
return (
<>
<ul>
{products.map(p => (
<li key={p.id}>{p.name}: {isReady ? convertAndFormat(p.price) : '…'}</li>
))}
</ul>
<RatesAttribution rateSource={rateSource} />
</>
)
}4. Real-time mode
<LocalizedPrice
basePrice={99.99}
baseCurrency="USD"
apiKey={import.meta.env.VITE_ALLRATESTODAY_KEY} // CRA: REACT_APP_…, Next.js: NEXT_PUBLIC_…
/>Same API — the key switches the rate source from the daily ECB table to /api/v1/rates (real-time, 160+ currencies) and drops the attribution requirement.
API reference
useCurrencyConverter(options)
| Option | Type | Required | Description |
|---|---|---|---|
| basePrice | number | Yes | Price in the base currency |
| baseCurrency | string | Yes | ISO 4217 code, case-insensitive |
| apiKey | string | No | Enables real-time mode. Omit for keyless ECB mode |
| manualCurrency | string | No | Skip detection, use this currency |
| geoEndpoint | string | No | Any URL returning { currency }. Default https://allratestoday.com/api/open/geo |
| onSuccess | (result) => void | No | Called with { convertedPrice, localCurrency, exchangeRate, rateSource, rateDate } |
| onError | (error) => void | No | |
Returns { convertedPrice, localCurrency, baseCurrency, exchangeRate, rateSource, rateDate, isLoading, error }.
rateSourceis'ecb'(keyless) or'realtime'(keyed).rateDateisYYYY-MM-DDfor ECB, an ISO timestamp for real-time.
useCurrencyLocalizer(options)
Same options minus basePrice, plus onReady. Returns { convert, format, convertAndFormat, localCurrency, baseCurrency, exchangeRate, rateSource, rateDate, isLoading, isReady, error }.
<LocalizedPrice />
All hook options as props, plus:
| Prop | Type | Description |
|---|---|---|
| attribution | boolean | Default true keyless, false keyed. Set false and render <RatesAttribution /> once for lists |
| loadingComponent | ReactNode | Custom loading state |
| errorComponent | (error, basePrice, baseCurrency) => ReactNode | Custom error state (default: original price) |
| formatPrice | (price, currency) => string | Custom formatter |
<RatesAttribution />
The "Rates by AllRatesToday" link. Props: rateSource, className, style. Render it once per page when you use the hooks keyless.
detectCurrency(geoEndpoint?) / fetchRate(apiKey, from, to)
The underlying functions, exported for non-React use or custom hooks. fetchRate returns { rate, source, date }.
How keyless mode works
- Currency detection —
GET https://allratestoday.com/api/open/georeturns{ country, currency }from Cloudflare's per-request country header. No third-party service, no key, no quota. Cached inlocalStoragefor 24 h. - Rate —
GET https://allratestoday.com/api/open/central-bank/ecbreturns the ECB's daily EUR reference table (edge-cached, keyless, CORS-open). Any pair is cross-derived:USD→GBP = (EUR→GBP) / (EUR→USD). One fetch per hour serves every price on the page. - Attribution — the endpoint is free because embeds link back.
LocalizedPricerenders the link for you; with hooks, add<RatesAttribution />once.
If the visitor's currency isn't in the ECB table (e.g. NGN, PKR, VND), the hook errors with a clear message and LocalizedPrice falls back to the base price. Add an apiKey to cover 160+ currencies.
Caching
| Data | Where | TTL |
|---|---|---|
| Detected currency | localStorage | 24 h |
| ECB table (keyless) | memory + localStorage | 1 h |
| Real-time rate (keyed) | memory | 1 h |
| Same-currency | instant 1 | — |
SSR (Next.js, Remix)
Detection uses the requesting IP, so on the server it reflects the server's location. Convert on the client only:
const [isClient, setIsClient] = useState(false)
useEffect(() => setIsClient(true), [])
<LocalizedPrice basePrice={99.99} baseCurrency="USD" manualCurrency={isClient ? undefined : 'USD'} />Migrating from 1.x
apiKeyis now optional. Existing keyed code works unchanged.fetchRate()now returns{ rate, source, date }instead of a bare number (only matters if you called it directly).onSuccessreceives two extra fields:rateSource,rateDate.- Default geolocation moved from ipapi.co to
allratestoday.com/api/open/geo. PassgeoEndpoint="https://ipapi.co/json/"to keep the old behaviour. - New:
RatesAttributioncomponent,attributionprop onLocalizedPrice.
Testing
npm test # 42 unit tests, mocked network
npm run test:coverageLinks
- Live example — allratestoday.com/pricing
- Keyless ECB endpoint docs
- Get a free API key · API docs
- Core SDK (Node.js) · GitHub
License
MIT
