Browse Source

feat: add bottom sheet for currency selection with auto-mapping based on location country code

master
mortezaei 2 days ago
parent
commit
5f033fc822
  1. 68
      src/components/Componentes/currency-sheet.test.tsx
  2. 274
      src/components/Componentes/currency-sheet.tsx
  3. 445
      src/components/Componentes/question-number.tsx
  4. 58
      src/data/currencies.test.ts
  5. 381
      src/data/currencies.ts

68
src/components/Componentes/currency-sheet.test.tsx

@ -0,0 +1,68 @@
import { render, screen, fireEvent, cleanup } from "@testing-library/react";
import { describe, expect, it, vi, afterEach } from "vitest";
import { CurrencySheet } from "./currency-sheet";
describe("CurrencySheet", () => {
afterEach(() => {
cleanup();
});
it("should render currency sheet when isOpen is true", () => {
const handleClose = vi.fn();
const handleSelect = vi.fn();
render(
<CurrencySheet
isOpen={true}
onClose={handleClose}
selectedCurrency="TOMAN"
onSelectCurrency={handleSelect}
title="انتخاب ارز"
/>,
);
expect(screen.getByText("انتخاب ارز")).toBeDefined();
expect(screen.getByText("TOMAN")).toBeDefined();
expect(screen.getAllByText("USD").length).toBeGreaterThan(0);
});
it("should filter currencies when typing in search input", () => {
const handleClose = vi.fn();
const handleSelect = vi.fn();
render(
<CurrencySheet
isOpen={true}
onClose={handleClose}
selectedCurrency="USD"
onSelectCurrency={handleSelect}
/>,
);
const searchInput = screen.getByPlaceholderText("Search currency or country...");
fireEvent.change(searchInput, { target: { value: "EUR" } });
expect(screen.getByText("EUR")).toBeDefined();
expect(screen.queryByText("TOMAN")).toBeNull();
});
it("should call onSelectCurrency when a currency item is clicked", () => {
const handleClose = vi.fn();
const handleSelect = vi.fn();
render(
<CurrencySheet
isOpen={true}
onClose={handleClose}
selectedCurrency="TOMAN"
onSelectCurrency={handleSelect}
/>,
);
const eurButton = screen.getByText("Euro").closest("button");
expect(eurButton).not.toBeNull();
fireEvent.click(eurButton!);
expect(handleSelect).toHaveBeenCalledWith("EUR");
});
});

274
src/components/Componentes/currency-sheet.tsx

@ -0,0 +1,274 @@
"use client";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { createPortal } from "react-dom";
import { CURRENCIES, type CurrencyItem } from "@/data/currencies";
import { useI18n } from "@/translations/provider";
import { useSheetScrollLock } from "./use-sheet-scroll-lock";
const EXIT_ANIMATION_MS = 300;
export type CurrencySheetProps = {
isOpen: boolean;
onClose: () => void;
selectedCurrency: string;
onSelectCurrency: (code: string) => void;
title?: string;
};
export function CurrencySheet({
isOpen,
onClose,
selectedCurrency,
onSelectCurrency,
title,
}: CurrencySheetProps) {
const { dictionary: t, locale } = useI18n();
const [isClosing, setIsClosing] = useState(false);
const [searchQuery, setSearchQuery] = useState("");
const listRef = useRef<HTMLDivElement>(null);
const sheetRef = useRef<HTMLElement>(null);
const isRtl = locale === "fa" || locale === "ar" || locale === "ur";
const defaultTitle = isRtl ? "انتخاب ارز" : "Select Currency";
const sheetTitle = title || defaultTitle;
const closeSheet = useCallback(() => {
setIsClosing(true);
window.setTimeout(() => {
onClose();
setIsClosing(false);
setSearchQuery("");
}, EXIT_ANIMATION_MS);
}, [onClose]);
useSheetScrollLock(isOpen && !isClosing, { onBack: closeSheet });
// Escape key handler
useEffect(() => {
if (!isOpen) return;
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") {
closeSheet();
}
};
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, [isOpen, closeSheet]);
const filteredCurrencies = useMemo(() => {
const q = searchQuery.toLowerCase().trim();
if (!q) return CURRENCIES;
return CURRENCIES.filter(
(c) =>
c.code.toLowerCase().includes(q) ||
c.nameEn.toLowerCase().includes(q) ||
c.nameFa.toLowerCase().includes(q) ||
(c.symbol && c.symbol.toLowerCase().includes(q)),
);
}, [searchQuery]);
const handleSelect = (code: string) => {
onSelectCurrency(code);
closeSheet();
};
const searchPlaceholder =
locale === "fa"
? "جستجوی ارز یا کشور..."
: locale === "ar"
? "بحث عن العملة..."
: "Search currency or country...";
const noResultsText =
locale === "fa"
? "ارزی یافت نشد"
: locale === "ar"
? "لم يتم العثور على عملات"
: "No currencies found";
if (!isOpen && !isClosing) return null;
return createPortal(
<div
className="fixed inset-0 z-50 flex items-end justify-center bg-black/40 backdrop-blur-[2px] transition-opacity duration-200"
role="dialog"
aria-label={sheetTitle}
onKeyDown={(e) => {
if (e.key === "Escape") closeSheet();
}}
onClick={(event) => {
if (event.target === event.currentTarget) closeSheet();
}}
>
<section
ref={sheetRef}
className={[
"flex w-full flex-col overflow-hidden rounded-t-[24px] bg-white pb-[env(safe-area-inset-bottom)] shadow-[0_-10px_32px_rgba(0,0,0,0.16)] transition-transform duration-[300ms] ease-out sm:max-w-[420px] h-[82svh] min-h-[82svh] max-h-[82svh]",
isClosing ? "translate-y-full" : "translate-y-0",
].join(" ")}
>
{/* Header */}
<div className="flex items-center justify-between px-5 pt-3 pb-3 border-b border-[#F2F4F7]">
<h3 className="text-[17px] font-bold text-[#181818] truncate pr-2">
{sheetTitle}
</h3>
<button
type="button"
onClick={closeSheet}
className="flex size-8 shrink-0 items-center justify-center rounded-full text-[#667085] hover:bg-[#F2F4F7] hover:text-[#181818] transition-colors cursor-pointer"
aria-label="Close"
>
<svg
aria-hidden="true"
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.2"
strokeLinecap="round"
strokeLinejoin="round"
>
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</svg>
</button>
</div>
{/* Search Bar */}
<div className="px-5 pt-3.5 pb-2">
<div className="flex h-[46px] w-full items-center gap-2.5 rounded-[14px] bg-[#F2F4F7] px-3.5 transition-colors focus-within:bg-[#EAECF0]">
<svg
aria-hidden="true"
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
className="shrink-0 text-[#667085]"
>
<path
d="M8.25 14.25C11.5637 14.25 14.25 11.5637 14.25 8.25C14.25 4.93629 11.5637 2.25 8.25 2.25C4.93629 2.25 2.25 4.93629 2.25 8.25C2.25 11.5637 4.93629 14.25 8.25 14.25Z"
stroke="#667085"
strokeWidth="1.75"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M15.75 15.75L12.5 12.5"
stroke="#667085"
strokeWidth="1.75"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
<input
type="text"
name="currency_sheet_search"
autoComplete="off"
autoCorrect="off"
autoCapitalize="none"
spellCheck="false"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder={searchPlaceholder}
className="flex-1 bg-transparent text-[14px] font-medium text-[#181818] outline-none placeholder:text-[#98A2B3]"
/>
{searchQuery ? (
<button
type="button"
onClick={() => setSearchQuery("")}
className="text-[#667085] hover:text-[#181818] text-xs font-semibold p-1 cursor-pointer"
>
</button>
) : null}
</div>
</div>
{/* Currencies List */}
<div
ref={listRef}
onTouchStart={(event) => event.stopPropagation()}
onTouchMove={(event) => event.stopPropagation()}
onTouchEnd={(event) => event.stopPropagation()}
className="flex min-h-0 flex-1 flex-col gap-2 overflow-y-auto overscroll-contain px-5 py-3"
>
{filteredCurrencies.length > 0 ? (
filteredCurrencies.map((item: CurrencyItem) => {
const isSelected = item.code === selectedCurrency;
const displayName =
locale === "fa" || locale === "fa-ir"
? item.nameFa
: item.nameEn;
return (
<button
key={item.code}
type="button"
onClick={() => handleSelect(item.code)}
className={[
"flex min-h-[52px] w-full items-center justify-between gap-3 rounded-[14px] border px-3.5 py-2.5 text-start transition-all cursor-pointer",
isSelected
? "bg-[#FFF4F5] border-[#F0445B]/40 text-[#181818] shadow-xs"
: "bg-white hover:bg-[#F8F9FA] border-[#EAECF0] text-[#181818]",
].join(" ")}
>
<div className="flex items-center gap-3 min-w-0">
{/* Radio selection circle */}
<div
className={[
"size-[22px] shrink-0 rounded-full transition-all duration-150 flex items-center justify-center",
isSelected
? "border-[6px] border-[#F0445B] bg-white"
: "border-[2px] border-[#98A2B3] bg-white",
].join(" ")}
/>
{/* Currency name and symbol */}
<div className="flex flex-col min-w-0">
<span
className={[
"text-[15px] leading-tight truncate",
isSelected
? "font-bold text-[#181818]"
: "font-medium text-[#344054]",
].join(" ")}
>
{displayName}
</span>
{item.symbol && item.symbol !== item.code && (
<span className="text-[12px] text-[#667085] mt-0.5">
{item.symbol}
</span>
)}
</div>
</div>
{/* Currency code badge */}
<span
className={[
"shrink-0 px-2.5 py-1 rounded-[8px] text-[13px] font-bold tracking-wider uppercase font-mono",
isSelected
? "bg-[#F0445B] text-white"
: "bg-[#F2F4F7] text-[#475467]",
].join(" ")}
>
{item.code}
</span>
</button>
);
})
) : (
<div className="py-12 text-center text-[14px] text-[#667085]">
{noResultsText}
</div>
)}
</div>
</section>
</div>,
document.body,
);
}
export default CurrencySheet;

445
src/components/Componentes/question-number.tsx

@ -1,12 +1,15 @@
"use client";
import { useEffect, useMemo, useRef, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import type { QuestionField } from "@/lib/schema-adapter";
import { useI18n } from "@/translations/provider";
import { useQuestionAnswers } from "./question-answer-storage";
import QuestionTitle from "./question-title";
import { Input } from "@/components/ui/input";
import { isKnownCountry } from "@/data/countries";
import { resolveDefaultCurrency } from "@/data/currencies";
import { getStoredUserGeoRegion } from "@/lib/geo-region";
import { CurrencySheet } from "./currency-sheet";
type QuestionNumberProps = {
question: QuestionField;
@ -78,7 +81,8 @@ export default function QuestionNumber({
: "";
const isMonthlyIncome = question.ui_config?.currency_enabled === true;
const currencyStorageKey = question.ui_config?.currency_storage_key || "marriage:income:currency";
const currencyStorageKey =
question.ui_config?.currency_storage_key || "marriage:income:currency";
const countryName = useMemo(() => getCountryFromStorage(), []);
@ -87,13 +91,15 @@ export default function QuestionNumber({
const stored = window.localStorage.getItem(currencyStorageKey);
if (stored) return stored;
}
return getCurrencyForCountry(countryName);
const geo = getStoredUserGeoRegion();
return resolveDefaultCurrency({
countryCode: geo?.countryCode,
countryName: countryName || geo?.country,
fallbackLocale: locale,
});
});
const [isCurrencyDropdownOpen, setIsCurrencyDropdownOpen] = useState(false);
const [currencySearchQuery, setCurrencySearchQuery] = useState("");
const currencyContainerRef = useRef<HTMLDivElement>(null);
const currencySearchInputRef = useRef<HTMLInputElement>(null);
const [isCurrencySheetOpen, setIsCurrencySheetOpen] = useState(false);
useEffect(() => {
if (typeof window !== "undefined") {
@ -103,47 +109,18 @@ export default function QuestionNumber({
return;
}
}
const derived = getCurrencyForCountry(countryName);
const geo = getStoredUserGeoRegion();
const derived = resolveDefaultCurrency({
countryCode: geo?.countryCode,
countryName: countryName || geo?.country,
fallbackLocale: locale,
});
setCurrencyCode(derived);
}, [countryName]);
useEffect(() => {
function handleClickOutside(event: MouseEvent) {
if (
currencyContainerRef.current &&
!currencyContainerRef.current.contains(event.target as Node)
) {
setIsCurrencyDropdownOpen(false);
}
}
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, []);
useEffect(() => {
if (isCurrencyDropdownOpen) {
setTimeout(() => {
currencySearchInputRef.current?.focus();
}, 50);
}
}, [isCurrencyDropdownOpen]);
const filteredCurrencies = useMemo(() => {
const q = currencySearchQuery.toLowerCase().trim();
if (!q) return CURRENCIES;
return CURRENCIES.filter(
(c) =>
c.code.toLowerCase().includes(q) ||
c.nameEn.toLowerCase().includes(q) ||
c.nameFa.toLowerCase().includes(q),
);
}, [currencySearchQuery]);
}, [countryName, currencyStorageKey, locale]);
const placeholderCurrency = useMemo(() => {
if (currencyCode === "TOMAN") {
return locale === "fa" ? "تومان" : "TOMAN";
return locale === "fa" || locale === "fa-ir" ? "تومان" : "TOMAN";
}
return currencyCode;
}, [currencyCode, locale]);
@ -152,7 +129,7 @@ export default function QuestionNumber({
if (!isMonthlyIncome) {
return question.extras.placeHolder;
}
return locale === "fa"
return locale === "fa" || locale === "fa-ir"
? `مثال: ۴۰۰۰ ${placeholderCurrency}`
: `e.g. 4000 ${placeholderCurrency}`;
}, [
@ -186,7 +163,7 @@ export default function QuestionNumber({
].join(" ")}
>
<QuestionTitle question={question} />
<div className="flex gap-3 w-full relative" ref={currencyContainerRef}>
<div className="flex gap-3 w-full relative">
<div className="flex-1 min-w-0">
<Input
type="text"
@ -217,7 +194,8 @@ export default function QuestionNumber({
} else {
const parsed = parseFloat(cleanValue);
setAnswerValue(
question, Number.isNaN(parsed) ? cleanValue : parsed,
question,
Number.isNaN(parsed) ? cleanValue : parsed,
);
}
}}
@ -233,10 +211,10 @@ export default function QuestionNumber({
<button
type="button"
disabled={disabled}
onClick={() => setIsCurrencyDropdownOpen(!isCurrencyDropdownOpen)}
onClick={() => setIsCurrencySheetOpen(true)}
className={[
"flex h-[54px] w-full items-center justify-between rounded-[16px] border bg-white px-3.5 text-start transition-all cursor-pointer outline-none",
isCurrencyDropdownOpen
isCurrencySheetOpen
? "border-[#6F6F6F] ring-1 ring-[#6F6F6F]"
: "border-[#D0D5DD] hover:border-[#98A2B3]",
].join(" ")}
@ -253,7 +231,7 @@ export default function QuestionNumber({
aria-label="Dropdown chevron"
className={[
"shrink-0 transition-transform duration-200 text-[#344054] ml-1",
isCurrencyDropdownOpen ? "rotate-180" : "",
isCurrencySheetOpen ? "rotate-180" : "",
].join(" ")}
>
<path
@ -265,110 +243,21 @@ export default function QuestionNumber({
/>
</svg>
</button>
{isCurrencyDropdownOpen && (
<div className="absolute top-[calc(100%+8px)] right-0 z-50 flex w-[240px] flex-col gap-3 rounded-[20px] bg-white p-4 shadow-[0_12px_36px_rgba(0,0,0,0.09)] border border-[#EAECF0] animate-in fade-in zoom-in-95 duration-150">
{/* Search bar */}
<div className="flex h-[40px] w-full items-center gap-2 rounded-[10px] bg-[#EFEFEF] px-3 transition-colors focus-within:bg-[#E8E8E8]">
<svg
width="16"
height="16"
viewBox="0 0 18 18"
fill="none"
role="img"
aria-label="Search"
className="shrink-0 text-[#667085]"
>
<path
d="M8.25 14.25C11.5637 14.25 14.25 11.5637 14.25 8.25C14.25 4.93629 11.5637 2.25 8.25 2.25C4.93629 2.25 2.25 4.93629 2.25 8.25C2.25 11.5637 4.93629 14.25 8.25 14.25Z"
stroke="#667085"
strokeWidth="1.75"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M15.75 15.75L12.5 12.5"
stroke="#667085"
strokeWidth="1.75"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
<input
ref={currencySearchInputRef}
type="text"
name="currency_search_field"
autoComplete="off"
autoCorrect="off"
autoCapitalize="none"
spellCheck="false"
aria-autocomplete="none"
data-lpignore="true"
data-1p-ignore="true"
data-bwignore="true"
data-form-type="other"
value={currencySearchQuery}
onChange={(e) => setCurrencySearchQuery(e.target.value)}
placeholder={locale === "fa" ? "جستجو..." : "Search..."}
className="flex-1 min-w-0 bg-transparent text-[13px] font-medium text-[#181818] outline-none placeholder:text-[#98A2B3]"
/>
{currencySearchQuery ? (
<button
type="button"
onClick={() => setCurrencySearchQuery("")}
className="text-[#667085] hover:text-[#181818] text-xs font-semibold cursor-pointer shrink-0"
>
</button>
) : null}
</div>
{/* Options list */}
<div className="flex max-h-[180px] flex-col gap-2.5 overflow-y-auto overscroll-contain pr-1">
{filteredCurrencies.length > 0 ? (
filteredCurrencies.map((c) => {
const isSelected = c.code === currencyCode;
return (
<button
key={c.code}
type="button"
onClick={() => {
setCurrencyCode(c.code);
if (typeof window !== "undefined") {
window.localStorage.setItem(
currencyStorageKey,
c.code,
);
}
setIsCurrencyDropdownOpen(false);
setCurrencySearchQuery("");
}}
className="flex w-full items-center justify-between text-start cursor-pointer group/opt py-1 px-1.5 rounded-lg hover:bg-gray-50 transition-colors"
>
<span className="text-[14px] font-medium text-[#181818]">
{c.code}{" "}
{locale === "fa"
? `(${c.nameFa})`
: `(${c.nameEn})`}
</span>
{isSelected ? (
<div className="size-[6px] rounded-full bg-[#F2465F]" />
) : null}
</button>
);
})
) : (
<span className="py-2 text-[12px] text-[#667085] text-center">
{locale === "fa"
? "ارزی یافت نشد"
: "No currencies found"}
</span>
)}
</div>
</div>
)}
</div>
</div>
<CurrencySheet
isOpen={isCurrencySheetOpen}
onClose={() => setIsCurrencySheetOpen(false)}
selectedCurrency={currencyCode}
onSelectCurrency={(code) => {
setCurrencyCode(code);
if (typeof window !== "undefined") {
window.localStorage.setItem(currencyStorageKey, code);
}
}}
/>
{isOutOfRange ? (
<span className="block text-[10px] font-semibold text-[#F2465F]">
{t[
@ -408,7 +297,8 @@ export default function QuestionNumber({
} else {
const parsed = parseFloat(nextValue);
setAnswerValue(
question, Number.isNaN(parsed) ? nextValue : parsed,
question,
Number.isNaN(parsed) ? nextValue : parsed,
);
}
}}
@ -487,252 +377,9 @@ function getCountryFromStorage(): string {
return "";
}
function getCurrencyForCountry(countryName: string): string {
const cleanCountry = countryName?.trim();
if (!cleanCountry) return "USD";
const countryMap: Record<string, string> = {
// English
Iran: "TOMAN",
"United States": "USD",
"United Kingdom": "GBP",
Canada: "CAD",
Germany: "EUR",
France: "EUR",
"United Arab Emirates": "AED",
Turkey: "TRY",
Iraq: "IQD",
Afghanistan: "AFN",
Pakistan: "PKR",
"Saudi Arabia": "SAR",
Qatar: "QAR",
Sweden: "SEK",
Netherlands: "EUR",
Norway: "NOK",
Australia: "AUD",
Bulgaria: "BGN",
// Persian
ایران: "TOMAN",
"ایالات متحده": "USD",
بریتانیا: "GBP",
کانادا: "CAD",
آلمان: "EUR",
فرانسه: "EUR",
"امارات متحده عربی": "AED",
ترکیه: "TRY",
عراق: "IQD",
افغانستان: "AFN",
پاکستان: "PKR",
"عربستان سعودی": "SAR",
قطر: "QAR",
سوئد: "SEK",
هلند: "EUR",
نروژ: "NOK",
استرالیا: "AUD",
بلغارستان: "BGN",
};
return countryMap[cleanCountry] || "USD";
}
const CURRENCIES = [
{ code: "AED", nameEn: "UAE Dirham", nameFa: "درهم امارات" },
{ code: "AFN", nameEn: "Afghan Afghani", nameFa: "افغانی افغانستان" },
{ code: "ALL", nameEn: "Albanian Lek", nameFa: "لک آلبانی" },
{ code: "AMD", nameEn: "Armenian Dram", nameFa: "درام ارمنستان" },
{
code: "ANG",
nameEn: "Netherlands Antillean Guilder",
nameFa: "گیلدر آنتیل هلند",
},
{ code: "AOA", nameEn: "Angolan Kwanza", nameFa: "کوانزای آنگولا" },
{ code: "ARS", nameEn: "Argentine Peso", nameFa: "پزو آرژانتین" },
{ code: "AUD", nameEn: "Australian Dollar", nameFa: "دلار استرالیا" },
{ code: "AZN", nameEn: "Azerbaijani Manat", nameFa: "منات آذربایجان" },
{
code: "BAM",
nameEn: "Bosnia-Herzegovina Mark",
nameFa: "مارک بوسنی و هرزگوین",
},
{ code: "BBD", nameEn: "Barbadian Dollar", nameFa: "دلار باربادوس" },
{ code: "BDT", nameEn: "Bangladeshi Taka", nameFa: "تاکای بنگلادش" },
{ code: "BGN", nameEn: "Bulgarian Lev", nameFa: "لو بلغارستان" },
{ code: "BHD", nameEn: "Bahraini Dinar", nameFa: "دینار بحرین" },
{ code: "BIF", nameEn: "Burundian Franc", nameFa: "فرانک بروندی" },
{ code: "BMD", nameEn: "Bermudian Dollar", nameFa: "دلار برمودا" },
{ code: "BND", nameEn: "Brunei Dollar", nameFa: "دلار برونئی" },
{ code: "BOB", nameEn: "Bolivian Boliviano", nameFa: "بولیویانو بولیوی" },
{ code: "BRL", nameEn: "Brazilian Real", nameFa: "رئال برزیل" },
{ code: "BSD", nameEn: "Bahamian Dollar", nameFa: "دلار باهاما" },
{ code: "BTN", nameEn: "Bhutanese Ngultrum", nameFa: "نگولتروم بوتان" },
{ code: "BWP", nameEn: "Botswanan Pula", nameFa: "پولای بوتسوانا" },
{ code: "BYN", nameEn: "Belarusian Ruble", nameFa: "روبل بلاروس" },
{ code: "BZD", nameEn: "Belize Dollar", nameFa: "دلار بلیز" },
{ code: "CAD", nameEn: "Canadian Dollar", nameFa: "دلار کانادا" },
{ code: "CDF", nameEn: "Congolese Franc", nameFa: "فرانک کنگو" },
{ code: "CHF", nameEn: "Swiss Franc", nameFa: "فرانک سوئیس" },
{ code: "CLP", nameEn: "Chilean Peso", nameFa: "پزو شیلی" },
{ code: "CNY", nameEn: "Chinese Yuan", nameFa: "یوان چین" },
{ code: "COP", nameEn: "Colombian Peso", nameFa: "پزو کلمبیا" },
{ code: "CRC", nameEn: "Costa Rican Colón", nameFa: "کولون کاستاریکا" },
{ code: "CUP", nameEn: "Cuban Peso", nameFa: "پزو کوبا" },
{ code: "CVE", nameEn: "Cape Verdean Escudo", nameFa: "اسکودو کیپ ورد" },
{ code: "CZK", nameEn: "Czech Koruna", nameFa: "کرون چک" },
{ code: "DJF", nameEn: "Djiboutian Franc", nameFa: "فرانک جیبوتی" },
{ code: "DKK", nameEn: "Danish Krone", nameFa: "کرون دانمارک" },
{ code: "DOP", nameEn: "Dominican Peso", nameFa: "پزو دومینیکن" },
{ code: "DZD", nameEn: "Algerian Dinar", nameFa: "دینار الجزایر" },
{ code: "EGP", nameEn: "Egyptian Pound", nameFa: "پوند مصر" },
{ code: "ERN", nameEn: "Eritrean Nakfa", nameFa: "ناکفای اریتره" },
{ code: "ETB", nameEn: "Ethiopian Birr", nameFa: "بیر اتیوپی" },
{ code: "EUR", nameEn: "Euro", nameFa: "یورو" },
{ code: "FJD", nameEn: "Fijian Dollar", nameFa: "دلار فیجی" },
{
code: "FKP",
nameEn: "Falkland Islands Pound",
nameFa: "پوند جزایر فالکلند",
},
{ code: "GBP", nameEn: "British Pound", nameFa: "پوند بریتانیا" },
{ code: "GEL", nameEn: "Georgian Lari", nameFa: "لاری گرجستان" },
{ code: "GHS", nameEn: "Ghanaian Cedi", nameFa: "سدی غنا" },
{ code: "GIP", nameEn: "Gibraltar Pound", nameFa: "پوند جبل الطارق" },
{ code: "GMD", nameEn: "Gambian Dalasi", nameFa: "دالاسی گامبیا" },
{ code: "GNF", nameEn: "Guinean Franc", nameFa: "فرانک گینه" },
{ code: "GTQ", nameEn: "Guatemalan Quetzal", nameFa: "کوتزال گواتمالا" },
{ code: "GYD", nameEn: "Guyanese Dollar", nameFa: "دلار گویان" },
{ code: "HKD", nameEn: "Hong Kong Dollar", nameFa: "دلار هنگ کنگ" },
{ code: "HNL", nameEn: "Honduran Lempira", nameFa: "لمپیرای هندوراس" },
{ code: "HRK", nameEn: "Croatian Kuna", nameFa: "کونای کرواسی" },
{ code: "HTG", nameEn: "Haitian Gourde", nameFa: "گورد هائیتی" },
{ code: "HUF", nameEn: "Hungarian Forint", nameFa: "فورینت مجارستان" },
{ code: "IDR", nameEn: "Indonesian Rupiah", nameFa: "روپیه اندونزی" },
{ code: "ILS", nameEn: "Israeli Shekel", nameFa: "شکل اسرائیل" },
{ code: "INR", nameEn: "Indian Rupee", nameFa: "روپیه هند" },
{ code: "IQD", nameEn: "Iraqi Dinar", nameFa: "دینار عراق" },
{ code: "IRR", nameEn: "Iranian Rial", nameFa: "ریال ایران" },
{ code: "ISK", nameEn: "Icelandic Króna", nameFa: "کرون ایسلند" },
{ code: "JMD", nameEn: "Jamaican Dollar", nameFa: "دلار جامائیکا" },
{ code: "JOD", nameEn: "Jordanian Dinar", nameFa: "دینار اردن" },
{ code: "JPY", nameEn: "Japanese Yen", nameFa: "ین ژاپن" },
{ code: "KES", nameEn: "Kenyan Shilling", nameFa: "شیلینگ کنیا" },
{ code: "KGS", nameEn: "Kyrgystani Som", nameFa: "سوم قرقیزستان" },
{ code: "KHR", nameEn: "Cambodian Riel", nameFa: "ریال کامبوج" },
{ code: "KMF", nameEn: "Comorian Franc", nameFa: "فرانک کومور" },
{ code: "KPW", nameEn: "North Korean Won", nameFa: "وون کره شمالی" },
{ code: "KRW", nameEn: "South Korean Won", nameFa: "وون کره جنوبی" },
{ code: "KWD", nameEn: "Kuwaiti Dinar", nameFa: "دینار کویت" },
{ code: "KYD", nameEn: "Cayman Islands Dollar", nameFa: "دلار جزایر کیمن" },
{ code: "KZT", nameEn: "Kazakhstani Tenge", nameFa: "تنگه قزاقستان" },
{ code: "LAK", nameEn: "Laotian Kip", nameFa: "کیپ لائوس" },
{ code: "LBP", nameEn: "Lebanese Pound", nameFa: "پوند لبنان" },
{ code: "LKR", nameEn: "Sri Lankan Rupee", nameFa: "روپیه سریلانکا" },
{ code: "LRD", nameEn: "Liberian Dollar", nameFa: "دلار لیبریا" },
{ code: "LSL", nameEn: "Lesotho Loti", nameFa: "لوتی لسوتو" },
{ code: "LYD", nameEn: "Libyan Dinar", nameFa: "دینار لیبی" },
{ code: "MAD", nameEn: "Moroccan Dirham", nameFa: "درهم مراکش" },
{ code: "MDL", nameEn: "Moldovan Leu", nameFa: "لوی مولداوی" },
{ code: "MGA", nameEn: "Malagasy Ariary", nameFa: "آریاری ماداگاسکار" },
{ code: "MKD", nameEn: "Macedonian Denar", nameFa: "دینار مقدونیه" },
{ code: "MMK", nameEn: "Myanmar Kyat", nameFa: "کیات میانمار" },
{ code: "MNT", nameEn: "Mongolian Tugrik", nameFa: "توگریک مغولستان" },
{ code: "MOP", nameEn: "Macanese Pataca", nameFa: "پاتاکای ماکائو" },
{ code: "MRU", nameEn: "Mauritanian Ouguiya", nameFa: "اوگیای موریتانی" },
{ code: "MUR", nameEn: "Mauritian Rupee", nameFa: "روپیه موریس" },
{ code: "MVR", nameEn: "Maldivian Rufiyaa", nameFa: "روفیای مالدیو" },
{ code: "MWK", nameEn: "Malawian Kwacha", nameFa: "کواچای مالاوی" },
{ code: "MXN", nameEn: "Mexican Peso", nameFa: "پزو مکزیک" },
{ code: "MYR", nameEn: "Malaysian Ringgit", nameFa: "رینگیت مالزی" },
{ code: "MZN", nameEn: "Mozambican Metical", nameFa: "متیکال موزامبیک" },
{ code: "NAD", nameEn: "Namibian Dollar", nameFa: "دلار نامیبیا" },
{ code: "NGN", nameEn: "Nigerian Naira", nameFa: "نایرای نیجریه" },
{ code: "NIO", nameEn: "Nicaraguan Córdoba", nameFa: "کوردوبای نیکاراگوئه" },
{ code: "NOK", nameEn: "Norwegian Krone", nameFa: "کرون نروژ" },
{ code: "NPR", nameEn: "Nepalese Rupee", nameFa: "روپیه نپال" },
{ code: "NZD", nameEn: "New Zealand Dollar", nameFa: "دلار نیوزیلند" },
{ code: "OMR", nameEn: "Omani Rial", nameFa: "ریال عمان" },
{ code: "PAB", nameEn: "Panamanian Balboa", nameFa: "بالبوای پاناما" },
{ code: "PEN", nameEn: "Peruvian Sol", nameFa: "سول پرو" },
{
code: "PGK",
nameEn: "Papua New Guinean Kina",
nameFa: "کینای پاپوآ گینه نو",
},
{ code: "PHP", nameEn: "Philippine Peso", nameFa: "پزو فیلیپین" },
{ code: "PKR", nameEn: "Pakistani Rupee", nameFa: "روپیه پاکستان" },
{ code: "PLN", nameEn: "Polish Zloty", nameFa: "زلوتی لهستان" },
{ code: "PYG", nameEn: "Paraguayan Guarani", nameFa: "گوارانی پاراگوئه" },
{ code: "QAR", nameEn: "Qatari Rial", nameFa: "ریال قطر" },
{ code: "RON", nameEn: "Romanian Leu", nameFa: "لوی رومانی" },
{ code: "RSD", nameEn: "Serbian Dinar", nameFa: "دینار صربستان" },
{ code: "RUB", nameEn: "Russian Ruble", nameFa: "روبل روسیه" },
{ code: "RWF", nameEn: "Rwandan Franc", nameFa: "فرانک رواندا" },
{ code: "SAR", nameEn: "Saudi Riyal", nameFa: "ریال عربستان" },
{
code: "SBD",
nameEn: "Solomon Islands Dollar",
nameFa: "دلار جزایر سلیمان",
},
{ code: "SCR", nameEn: "Seychellois Rupee", nameFa: "روپیه سیشل" },
{ code: "SDG", nameEn: "Sudanese Pound", nameFa: "پوند سودان" },
{ code: "SEK", nameEn: "Swedish Krona", nameFa: "کرون سوئد" },
{ code: "SGD", nameEn: "Singapore Dollar", nameFa: "دلار سنگاپور" },
{ code: "SHP", nameEn: "St. Helena Pound", nameFa: "پوند سنت هلن" },
{ code: "SLL", nameEn: "Sierra Leonean Leone", nameFa: "لئون سیرالئون" },
{ code: "SOS", nameEn: "Somali Shilling", nameFa: "شیلینگ سومالی" },
{ code: "SRD", nameEn: "Surinamese Dollar", nameFa: "دلار سورینام" },
{ code: "SSP", nameEn: "South Sudanese Pound", nameFa: "پوند سودان جنوبی" },
{ code: "STN", nameEn: "São Tomé Dobra", nameFa: "دوبرا سائوتومه" },
{ code: "SVC", nameEn: "Salvadoran Colón", nameFa: "کولون السالوادور" },
{ code: "SYP", nameEn: "Syrian Pound", nameFa: "پوند سوریه" },
{ code: "SZL", nameEn: "Swazi Lilangeni", nameFa: "لیلانگنی سوازیلند" },
{ code: "THB", nameEn: "Thai Baht", nameFa: "بات تایلند" },
{ code: "TJS", nameEn: "Tajikistani Somoni", nameFa: "سامانی تاجیکستان" },
{ code: "TMT", nameEn: "Turkmenistani Manat", nameFa: "منات ترکمنستان" },
{ code: "TND", nameEn: "Tunisian Dinar", nameFa: "دینار تونس" },
{ code: "TOMAN", nameEn: "Iranian Toman", nameFa: "تومان ایران" },
{ code: "TOP", nameEn: "Tongan Paʻanga", nameFa: "پاآنگای تونگا" },
{ code: "TRY", nameEn: "Turkish Lira", nameFa: "لیر ترکیه" },
{
code: "TTD",
nameEn: "Trinidad & Tobago Dollar",
nameFa: "دلار ترینیداد و توباگر",
},
{ code: "TWD", nameEn: "New Taiwan Dollar", nameFa: "دلار جدید تایوان" },
{ code: "TZS", nameEn: "Tanzanian Shilling", nameFa: "شیلینگ تانزانیا" },
{ code: "UAH", nameEn: "Ukrainian Hryvnia", nameFa: "گریونا اوکراین" },
{ code: "UGX", nameEn: "Ugandan Shilling", nameFa: "شیلینگ اوگاندا" },
{ code: "USD", nameEn: "US Dollar", nameFa: "دلار آمریکا" },
{ code: "UYU", nameEn: "Uruguayan Peso", nameFa: "پزو اروگوئه" },
{ code: "UZS", nameEn: "Uzbekistani Som", nameFa: "سوم ازبکستان" },
{ code: "VES", nameEn: "Venezuelan Bolívar", nameFa: "بولیوار ونزوئلا" },
{ code: "VND", nameEn: "Vietnamese Dong", nameFa: "دانگ ویتنام" },
{ code: "VUV", nameEn: "Vanuatu Vatu", nameFa: "واتو وانواتو" },
{ code: "WST", nameEn: "Samoan Tālā", nameFa: "تالای ساموآ" },
{
code: "XAF",
nameEn: "Central African CFA Franc",
nameFa: "فرانک سی‌اف‌آی آفریقای مرکزی",
},
{ code: "XCD", nameEn: "East Caribbean Dollar", nameFa: "دلار کارائیب شرقی" },
{
code: "XOF",
nameEn: "West African CFA Franc",
nameFa: "فرانک سی‌اف‌آی آفریقای غربی",
},
{ code: "XPF", nameEn: "CFP Franc", nameFa: "فرانک اقیانوس آرام" },
{ code: "YER", nameEn: "Yemeni Rial", nameFa: "ریال یمن" },
{ code: "ZAR", nameEn: "South African Rand", nameFa: "راند آفریقای جنوبی" },
{ code: "ZMW", nameEn: "Zambian Kwacha", nameFa: "کواچای زامبیا" },
{ code: "ZWL", nameEn: "Zimbabwean Dollar", nameFa: "دلار زیمبابوه" },
];
function formatNumberWithCommas(
val: string | number | null | undefined,
): string {
if (val === null || val === undefined || val === "") return "";
const cleanStr = String(val).replace(/,/g, "");
const parts = cleanStr.split(".");
function formatNumberWithCommas(val: string): string {
if (!val) return "";
const parts = val.split(".");
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
return parts.join(".");
}

58
src/data/currencies.test.ts

@ -0,0 +1,58 @@
import { describe, expect, it } from "vitest";
import {
COUNTRY_CODE_TO_CURRENCY,
resolveDefaultCurrency,
CURRENCIES,
} from "./currencies";
describe("currencies", () => {
it("should map country code IR to TOMAN", () => {
expect(COUNTRY_CODE_TO_CURRENCY["IR"]).toBe("TOMAN");
expect(resolveDefaultCurrency({ countryCode: "IR" })).toBe("TOMAN");
expect(resolveDefaultCurrency({ countryCode: "ir" })).toBe("TOMAN");
});
it("should map country codes correctly for other countries", () => {
expect(resolveDefaultCurrency({ countryCode: "US" })).toBe("USD");
expect(resolveDefaultCurrency({ countryCode: "GB" })).toBe("GBP");
expect(resolveDefaultCurrency({ countryCode: "DE" })).toBe("EUR");
expect(resolveDefaultCurrency({ countryCode: "AE" })).toBe("AED");
expect(resolveDefaultCurrency({ countryCode: "TR" })).toBe("TRY");
expect(resolveDefaultCurrency({ countryCode: "IQ" })).toBe("IQD");
expect(resolveDefaultCurrency({ countryCode: "AF" })).toBe("AFN");
expect(resolveDefaultCurrency({ countryCode: "CA" })).toBe("CAD");
expect(resolveDefaultCurrency({ countryCode: "TJ" })).toBe("TJS");
});
it("should map country names in English and Persian correctly", () => {
expect(resolveDefaultCurrency({ countryName: "Iran" })).toBe("TOMAN");
expect(resolveDefaultCurrency({ countryName: "ایران" })).toBe("TOMAN");
expect(resolveDefaultCurrency({ countryName: "Iraq" })).toBe("IQD");
expect(resolveDefaultCurrency({ countryName: "عراق" })).toBe("IQD");
expect(resolveDefaultCurrency({ countryName: "Turkey" })).toBe("TRY");
expect(resolveDefaultCurrency({ countryName: "ترکیه" })).toBe("TRY");
expect(resolveDefaultCurrency({ countryName: "United Arab Emirates" })).toBe("AED");
expect(resolveDefaultCurrency({ countryName: "امارات" })).toBe("AED");
});
it("should fallback to TOMAN for Persian locale if no country is detected", () => {
expect(resolveDefaultCurrency({ fallbackLocale: "fa" })).toBe("TOMAN");
expect(resolveDefaultCurrency({ fallbackLocale: "fa-ir" })).toBe("TOMAN");
});
it("should fallback to USD for other locales if no country is detected", () => {
expect(resolveDefaultCurrency({ fallbackLocale: "en" })).toBe("USD");
expect(resolveDefaultCurrency({})).toBe("USD");
});
it("should have valid currency items in CURRENCIES list", () => {
expect(CURRENCIES.length).toBeGreaterThan(50);
const toman = CURRENCIES.find((c) => c.code === "TOMAN");
expect(toman).toBeDefined();
expect(toman?.nameFa).toBe("تومان ایران");
const usd = CURRENCIES.find((c) => c.code === "USD");
expect(usd).toBeDefined();
expect(usd?.nameEn).toBe("US Dollar");
});
});

381
src/data/currencies.ts

@ -0,0 +1,381 @@
export type CurrencyItem = {
code: string;
nameEn: string;
nameFa: string;
symbol?: string;
};
export const COUNTRY_CODE_TO_CURRENCY: Record<string, string> = {
IR: "TOMAN",
US: "USD",
GB: "GBP",
UK: "GBP",
DE: "EUR",
FR: "EUR",
IT: "EUR",
ES: "EUR",
NL: "EUR",
BE: "EUR",
AT: "EUR",
PT: "EUR",
GR: "EUR",
FI: "EUR",
IE: "EUR",
SK: "EUR",
SI: "EUR",
LT: "EUR",
LV: "EUR",
EE: "EUR",
CY: "EUR",
MT: "EUR",
LU: "EUR",
MC: "EUR",
SM: "EUR",
VA: "EUR",
AD: "EUR",
ME: "EUR",
XK: "EUR",
AE: "AED",
TR: "TRY",
IQ: "IQD",
AF: "AFN",
PK: "PKR",
SA: "SAR",
QA: "QAR",
KW: "KWD",
OM: "OMR",
BH: "BHD",
CA: "CAD",
AU: "AUD",
SE: "SEK",
NO: "NOK",
DK: "DKK",
CH: "CHF",
RU: "RUB",
CN: "CNY",
JP: "JPY",
KR: "KRW",
IN: "INR",
MY: "MYR",
ID: "IDR",
TH: "THB",
SG: "SGD",
NZ: "NZD",
ZA: "ZAR",
BR: "BRL",
MX: "MXN",
AR: "ARS",
AZ: "AZN",
TJ: "TJS",
UZ: "UZS",
TM: "TMT",
KZ: "KZT",
KG: "KGS",
GE: "GEL",
AM: "AMD",
LB: "LBP",
SY: "SYP",
JO: "JOD",
EG: "EGP",
YE: "YER",
MA: "MAD",
DZ: "DZD",
TN: "TND",
LY: "LYD",
SD: "SDG",
BD: "BDT",
LK: "LKR",
NP: "NPR",
PH: "PHP",
VN: "VND",
PL: "PLN",
CZ: "CZK",
HU: "HUF",
RO: "RON",
BG: "BGN",
HR: "EUR",
RS: "RSD",
BA: "BAM",
AL: "ALL",
MD: "MDL",
UA: "UAH",
BY: "BYN",
IS: "ISK",
CL: "CLP",
CO: "COP",
PE: "PEN",
VE: "VES",
EC: "USD",
PA: "USD",
CR: "CRC",
DO: "DOP",
GT: "GTQ",
NG: "NGN",
KES: "KES",
GH: "GHS",
ET: "ETB",
TZ: "TZS",
UG: "UGX",
};
export const COUNTRY_NAME_TO_CURRENCY: Record<string, string> = {
// English
Iran: "TOMAN",
"United States": "USD",
"United States of America": "USD",
USA: "USD",
"United Kingdom": "GBP",
UK: "GBP",
Britain: "GBP",
Canada: "CAD",
Germany: "EUR",
France: "EUR",
Italy: "EUR",
Spain: "EUR",
Netherlands: "EUR",
Belgium: "EUR",
Austria: "EUR",
Portugal: "EUR",
Greece: "EUR",
Finland: "EUR",
Ireland: "EUR",
"United Arab Emirates": "AED",
UAE: "AED",
Turkey: "TRY",
Iraq: "IQD",
Afghanistan: "AFN",
Pakistan: "PKR",
"Saudi Arabia": "SAR",
Qatar: "QAR",
Kuwait: "KWD",
Oman: "OMR",
Bahrain: "BHD",
Sweden: "SEK",
Norway: "NOK",
Denmark: "DKK",
Switzerland: "CHF",
Australia: "AUD",
"New Zealand": "NZD",
Russia: "RUB",
China: "CNY",
Japan: "JPY",
"South Korea": "KRW",
India: "INR",
Malaysia: "MYR",
Indonesia: "IDR",
Singapore: "SGD",
Thailand: "THB",
Tajikistan: "TJS",
Azerbaijan: "AZN",
Uzbekistan: "UZS",
Turkmenistan: "TMT",
Kazakhstan: "KZT",
Kyrgyzstan: "KGS",
Georgia: "GEL",
Armenia: "AMD",
Lebanon: "LBP",
Syria: "SYP",
Jordan: "JOD",
Egypt: "EGP",
Yemen: "YER",
Morocco: "MAD",
Algeria: "DZD",
Tunisia: "TND",
Libya: "LYD",
Sudan: "SDG",
Brazil: "BRL",
Mexico: "MXN",
Argentina: "ARS",
Bulgaria: "BGN",
Poland: "PLN",
"Czech Republic": "CZK",
Hungary: "HUF",
Romania: "RON",
Serbia: "RSD",
Ukraine: "UAH",
Belarus: "BYN",
// Persian
ایران: "TOMAN",
"ایالات متحده": "USD",
"ایالات متحده آمریکا": "USD",
آمریکا: "USD",
بریتانیا: "GBP",
انگلیس: "GBP",
کانادا: "CAD",
آلمان: "EUR",
فرانسه: "EUR",
ایتالیا: "EUR",
اسپانیا: "EUR",
هلند: "EUR",
بلژیک: "EUR",
اتریش: "EUR",
پرتغال: "EUR",
یونان: "EUR",
فنلاند: "EUR",
ایرلند: "EUR",
"امارات متحده عربی": "AED",
امارات: "AED",
ترکیه: "TRY",
عراق: "IQD",
افغانستان: "AFN",
پاکستان: "PKR",
"عربستان سعودی": "SAR",
عربستان: "SAR",
قطر: "QAR",
کویت: "KWD",
عمان: "OMR",
بحرین: "BHD",
سوئد: "SEK",
نروژ: "NOK",
دانمارک: "DKK",
سوئیس: "CHF",
استرالیا: "AUD",
نیوزیلند: "NZD",
روسیه: "RUB",
چین: "CNY",
ژاپن: "JPY",
"کره جنوبی": "KRW",
هند: "INR",
مالزی: "MYR",
اندونزی: "IDR",
سنگاپور: "SGD",
تایلند: "THB",
تاجیکستان: "TJS",
آذربایجان: "AZN",
ازبکستان: "UZS",
ترکمنستان: "TMT",
قزاقستان: "KZT",
قرقیزستان: "KGS",
گرجستان: "GEL",
ارمنستان: "AMD",
لبنان: "LBP",
سوریه: "SYP",
اردن: "JOD",
مصر: "EGP",
یمن: "YER",
مراکش: "MAD",
الجزایر: "DZD",
تونس: "TND",
لیبی: "LYD",
سودان: "SDG",
برزیل: "BRL",
مکزیک: "MXN",
آرژانتین: "ARS",
بلغارستان: "BGN",
لهستان: "PLN",
چک: "CZK",
مجارستان: "HUF",
رومانی: "RON",
صربستان: "RSD",
اوکراین: "UAH",
بلاروس: "BYN",
};
export const CURRENCIES: CurrencyItem[] = [
{ code: "TOMAN", nameEn: "Iranian Toman", nameFa: "تومان ایران", symbol: "تومان" },
{ code: "USD", nameEn: "US Dollar", nameFa: "دلار آمریکا", symbol: "$" },
{ code: "EUR", nameEn: "Euro", nameFa: "یورو", symbol: "€" },
{ code: "AED", nameEn: "UAE Dirham", nameFa: "درهم امارات", symbol: "د.إ" },
{ code: "TRY", nameEn: "Turkish Lira", nameFa: "لیر ترکیه", symbol: "₺" },
{ code: "GBP", nameEn: "British Pound", nameFa: "پوند بریتانیا", symbol: "£" },
{ code: "CAD", nameEn: "Canadian Dollar", nameFa: "دلار کانادا", symbol: "CA$" },
{ code: "AUD", nameEn: "Australian Dollar", nameFa: "دلار استرالیا", symbol: "AU$" },
{ code: "IQD", nameEn: "Iraqi Dinar", nameFa: "دینار عراق", symbol: "ع.د" },
{ code: "SAR", nameEn: "Saudi Riyal", nameFa: "ریال سعودی", symbol: "﷼" },
{ code: "QAR", nameEn: "Qatari Riyal", nameFa: "ریال قطر", symbol: "ر.ق" },
{ code: "KWD", nameEn: "Kuwaiti Dinar", nameFa: "دینار کویت", symbol: "د.ك" },
{ code: "OMR", nameEn: "Omani Rial", nameFa: "ریال عمان", symbol: "ر.ع." },
{ code: "BHD", nameEn: "Bahraini Dinar", nameFa: "دینار بحرین", symbol: ".د.ب" },
{ code: "AFN", nameEn: "Afghan Afghani", nameFa: "افغانی افغانستان", symbol: "؋" },
{ code: "PKR", nameEn: "Pakistani Rupee", nameFa: "روپیه پاکستان", symbol: "₨" },
{ code: "TJS", nameEn: "Tajikistani Somoni", nameFa: "سامانی تاجیکستان", symbol: "смн" },
{ code: "AZN", nameEn: "Azerbaijani Manat", nameFa: "منات آذربایجان", symbol: "₼" },
{ code: "RUB", nameEn: "Russian Ruble", nameFa: "روبل روسیه", symbol: "₽" },
{ code: "CNY", nameEn: "Chinese Yuan", nameFa: "یوان چین", symbol: "¥" },
{ code: "JPY", nameEn: "Japanese Yen", nameFa: "ین ژاپن", symbol: "¥" },
{ code: "CHF", nameEn: "Swiss Franc", nameFa: "فرانک سوئیس", symbol: "CHF" },
{ code: "SEK", nameEn: "Swedish Krona", nameFa: "کرون سوئد", symbol: "kr" },
{ code: "NOK", nameEn: "Norwegian Krone", nameFa: "کرون نروژ", symbol: "kr" },
{ code: "DKK", nameEn: "Danish Krone", nameFa: "کرون دانمارک", symbol: "kr" },
{ code: "INR", nameEn: "Indian Rupee", nameFa: "روپیه هند", symbol: "₹" },
{ code: "MYR", nameEn: "Malaysian Ringgit", nameFa: "رینگیت مالزی", symbol: "RM" },
{ code: "SGD", nameEn: "Singapore Dollar", nameFa: "دلار سنگاپور", symbol: "S$" },
{ code: "NZD", nameEn: "New Zealand Dollar", nameFa: "دلار نیوزیلند", symbol: "NZ$" },
{ code: "BRL", nameEn: "Brazilian Real", nameFa: "رئال برزیل", symbol: "R$" },
{ code: "MXN", nameEn: "Mexican Peso", nameFa: "پزو مکزیک", symbol: "Mex$" },
{ code: "ARS", nameEn: "Argentine Peso", nameFa: "پزو آرژانتین", symbol: "ARS$" },
{ code: "EGP", nameEn: "Egyptian Pound", nameFa: "پوند مصر", symbol: "E£" },
{ code: "ZAR", nameEn: "South African Rand", nameFa: "راند آفریقای جنوبی", symbol: "R" },
{ code: "IDR", nameEn: "Indonesian Rupiah", nameFa: "روپیه اندونزی", symbol: "Rp" },
{ code: "THB", nameEn: "Thai Baht", nameFa: "بات تایلند", symbol: "฿" },
{ code: "KRW", nameEn: "South Korean Won", nameFa: "وون کره جنوبی", symbol: "₩" },
{ code: "UZS", nameEn: "Uzbekistani Som", nameFa: "سوم ازبکستان", symbol: "so'm" },
{ code: "TMT", nameEn: "Turkmenistani Manat", nameFa: "منات ترکمنستان", symbol: "TMT" },
{ code: "KZT", nameEn: "Kazakhstani Tenge", nameFa: "تنگه قزاقستان", symbol: "₸" },
{ code: "KGS", nameEn: "Kyrgystani Som", nameFa: "سوم قرقیزستان", symbol: "сом" },
{ code: "GEL", nameEn: "Georgian Lari", nameFa: "لاری گرجستان", symbol: "₾" },
{ code: "AMD", nameEn: "Armenian Dram", nameFa: "درام ارمنستان", symbol: "֏" },
{ code: "LBP", nameEn: "Lebanese Pound", nameFa: "لیره لبنان", symbol: "ل.ل" },
{ code: "SYP", nameEn: "Syrian Pound", nameFa: "لیره سوریه", symbol: "ل.س" },
{ code: "JOD", nameEn: "Jordanian Dinar", nameFa: "دینار اردن", symbol: "د.ا" },
{ code: "YER", nameEn: "Yemeni Rial", nameFa: "ریال یمن", symbol: "﷼" },
{ code: "MAD", nameEn: "Moroccan Dirham", nameFa: "درهم مراکش", symbol: "د.م." },
{ code: "DZD", nameEn: "Algerian Dinar", nameFa: "دینار الجزایر", symbol: "د.ج" },
{ code: "TND", nameEn: "Tunisian Dinar", nameFa: "دینار تونس", symbol: "د.ت" },
{ code: "LYD", nameEn: "Libyan Dinar", nameFa: "دینار لیبی", symbol: "ل.د" },
{ code: "SDG", nameEn: "Sudanese Pound", nameFa: "پوند سودان", symbol: "ج.س." },
{ code: "BDT", nameEn: "Bangladeshi Taka", nameFa: "تاکای بنگلادش", symbol: "৳" },
{ code: "BGN", nameEn: "Bulgarian Lev", nameFa: "لو بلغارستان", symbol: "лв" },
{ code: "PLN", nameEn: "Polish Zloty", nameFa: "زلوتی لهستان", symbol: "zł" },
{ code: "CZK", nameEn: "Czech Koruna", nameFa: "کرون چک", symbol: "Kč" },
{ code: "HUF", nameEn: "Hungarian Forint", nameFa: "فورینت مجارستان", symbol: "Ft" },
{ code: "RON", nameEn: "Romanian Leu", nameFa: "لئو رومانی", symbol: "lei" },
{ code: "RSD", nameEn: "Serbian Dinar", nameFa: "دینار صربستان", symbol: "дин." },
{ code: "BAM", nameEn: "Bosnia Mark", nameFa: "مارک بوسنی", symbol: "KM" },
{ code: "ALL", nameEn: "Albanian Lek", nameFa: "لک آلبانی", symbol: "Lek" },
{ code: "UAH", nameEn: "Ukrainian Hryvnia", nameFa: "گریونا اوکراین", symbol: "₴" },
{ code: "BYN", nameEn: "Belarusian Ruble", nameFa: "روبل بلاروس", symbol: "Br" },
{ code: "ISK", nameEn: "Icelandic Króna", nameFa: "کرون ایسلند", symbol: "kr" },
{ code: "CLP", nameEn: "Chilean Peso", nameFa: "پزو شیلی", symbol: "CLP$" },
{ code: "COP", nameEn: "Colombian Peso", nameFa: "پزو کلمبیا", symbol: "COL$" },
{ code: "PEN", nameEn: "Peruvian Sol", nameFa: "سول پرو", symbol: "S/." },
{ code: "CRC", nameEn: "Costa Rican Colón", nameFa: "کولون کاستاریکا", symbol: "₡" },
{ code: "DOP", nameEn: "Dominican Peso", nameFa: "پزو دومینیکن", symbol: "RD$" },
{ code: "GTQ", nameEn: "Guatemalan Quetzal", nameFa: "کوتزال گواتمالا", symbol: "Q" },
{ code: "NGN", nameEn: "Nigerian Naira", nameFa: "نایرا نیجریه", symbol: "₦" },
{ code: "KES", nameEn: "Kenyan Shilling", nameFa: "شیلینگ کنیا", symbol: "KSh" },
{ code: "GHS", nameEn: "Ghanaian Cedi", nameFa: "سدی غنا", symbol: "GH₵" },
{ code: "ETB", nameEn: "Ethiopian Birr", nameFa: "بیر اتیوپی", symbol: "Br" },
{ code: "IRR", nameEn: "Iranian Rial", nameFa: "ریال ایران", symbol: "﷼" },
];
export function resolveDefaultCurrency({
countryCode,
countryName,
fallbackLocale,
}: {
countryCode?: string | null;
countryName?: string | null;
fallbackLocale?: string | null;
}): string {
if (countryCode && typeof countryCode === "string") {
const cleanCode = countryCode.trim().toUpperCase();
if (COUNTRY_CODE_TO_CURRENCY[cleanCode]) {
return COUNTRY_CODE_TO_CURRENCY[cleanCode];
}
}
if (countryName && typeof countryName === "string") {
const cleanName = countryName.trim();
if (COUNTRY_NAME_TO_CURRENCY[cleanName]) {
return COUNTRY_NAME_TO_CURRENCY[cleanName];
}
}
if (fallbackLocale === "fa" || fallbackLocale === "fa-ir") {
return "TOMAN";
}
return "USD";
}
Loading…
Cancel
Save