From 35287da53a5b2a1d19500b9d9b6131f810f95f2d Mon Sep 17 00:00:00 2001 From: typist Date: Thu, 30 Oct 2025 09:07:37 +0800 Subject: [PATCH] refactor: update currency component for improved clarity and functionality - Translated currency names and comments from Chinese to English for better accessibility. - Enhanced caching logic for exchange rates, ensuring data validity based on the date. - Improved user feedback messages for currency addition and removal actions. - Updated UI elements to reflect changes in language and functionality, including placeholders and labels. --- src/components/tool/currency.tsx | 219 ++++++++++++++++--------------- 1 file changed, 114 insertions(+), 105 deletions(-) diff --git a/src/components/tool/currency.tsx b/src/components/tool/currency.tsx index 5e60566..1376f6f 100644 --- a/src/components/tool/currency.tsx +++ b/src/components/tool/currency.tsx @@ -15,46 +15,45 @@ interface Currency { symbol: string; } -// 常用货币列表 (仅包含 Frankfurter API 支持的货币) +// Available currencies (supported by Frankfurter API) const AVAILABLE_CURRENCIES: Currency[] = [ - { code: "USD", name: "美元", symbol: "$" }, - { code: "CNY", name: "人民币", symbol: "¥" }, - { code: "EUR", name: "欧元", symbol: "€" }, - { code: "GBP", name: "英镑", symbol: "£" }, - { code: "JPY", name: "日元", symbol: "¥" }, - { code: "HKD", name: "港币", symbol: "HK$" }, - { code: "AUD", name: "澳元", symbol: "A$" }, - { code: "CAD", name: "加元", symbol: "C$" }, - { code: "SGD", name: "新加坡元", symbol: "S$" }, - { code: "CHF", name: "瑞士法郎", symbol: "CHF" }, - { code: "NZD", name: "新西兰元", symbol: "NZ$" }, - { code: "KRW", name: "韩元", symbol: "₩" }, - { code: "THB", name: "泰铢", symbol: "฿" }, - { code: "MYR", name: "马来西亚林吉特", symbol: "RM" }, - { code: "INR", name: "印度卢比", symbol: "₹" }, - { code: "BRL", name: "巴西雷亚尔", symbol: "R$" }, - { code: "MXN", name: "墨西哥比索", symbol: "MX$" }, - { code: "SEK", name: "瑞典克朗", symbol: "kr" }, - { code: "NOK", name: "挪威克朗", symbol: "kr" }, - { code: "DKK", name: "丹麦克朗", symbol: "kr" }, - { code: "PLN", name: "波兰兹罗提", symbol: "zł" }, - { code: "TRY", name: "土耳其里拉", symbol: "₺" }, - { code: "PHP", name: "菲律宾比索", symbol: "₱" }, - { code: "IDR", name: "印尼盾", symbol: "Rp" }, - { code: "ILS", name: "以色列新谢克尔", symbol: "₪" }, - { code: "CZK", name: "捷克克朗", symbol: "Kč" }, - { code: "RON", name: "罗马尼亚列伊", symbol: "lei" }, - { code: "HUF", name: "匈牙利福林", symbol: "Ft" }, - { code: "BGN", name: "保加利亚列弗", symbol: "лв" }, - { code: "ISK", name: "冰岛克朗", symbol: "kr" }, + { code: "USD", name: "US Dollar", symbol: "$" }, + { code: "CNY", name: "Chinese Yuan", symbol: "¥" }, + { code: "EUR", name: "Euro", symbol: "€" }, + { code: "GBP", name: "British Pound", symbol: "£" }, + { code: "JPY", name: "Japanese Yen", symbol: "¥" }, + { code: "HKD", name: "Hong Kong Dollar", symbol: "HK$" }, + { code: "AUD", name: "Australian Dollar", symbol: "A$" }, + { code: "CAD", name: "Canadian Dollar", symbol: "C$" }, + { code: "SGD", name: "Singapore Dollar", symbol: "S$" }, + { code: "CHF", name: "Swiss Franc", symbol: "CHF" }, + { code: "NZD", name: "New Zealand Dollar", symbol: "NZ$" }, + { code: "KRW", name: "South Korean Won", symbol: "₩" }, + { code: "THB", name: "Thai Baht", symbol: "฿" }, + { code: "MYR", name: "Malaysian Ringgit", symbol: "RM" }, + { code: "INR", name: "Indian Rupee", symbol: "₹" }, + { code: "BRL", name: "Brazilian Real", symbol: "R$" }, + { code: "MXN", name: "Mexican Peso", symbol: "MX$" }, + { code: "SEK", name: "Swedish Krona", symbol: "kr" }, + { code: "NOK", name: "Norwegian Krone", symbol: "kr" }, + { code: "DKK", name: "Danish Krone", symbol: "kr" }, + { code: "PLN", name: "Polish Złoty", symbol: "zł" }, + { code: "TRY", name: "Turkish Lira", symbol: "₺" }, + { code: "PHP", name: "Philippine Peso", symbol: "₱" }, + { code: "IDR", name: "Indonesian Rupiah", symbol: "Rp" }, + { code: "ILS", name: "Israeli New Shekel", symbol: "₪" }, + { code: "CZK", name: "Czech Koruna", symbol: "Kč" }, + { code: "RON", name: "Romanian Leu", symbol: "lei" }, + { code: "HUF", name: "Hungarian Forint", symbol: "Ft" }, + { code: "BGN", name: "Bulgarian Lev", symbol: "лв" }, + { code: "ISK", name: "Icelandic Króna", symbol: "kr" }, ]; -// 必选货币(不可删除) - 美元第一,人民币第二 +// Required currencies (cannot be removed) - USD first, CNY second const REQUIRED_CURRENCIES = ["USD", "CNY"]; const STORAGE_KEY = "selectedCurrencies"; const RATES_CACHE_KEY = "currencyRatesCache"; -const RATES_CACHE_DURATION = 60 * 60 * 1000; // 1小时(毫秒) const Tool: FC = () => { const [amounts, setAmounts] = useState>({}); @@ -63,7 +62,7 @@ const Tool: FC = () => { const [loading, setLoading] = useState(false); const [open, setOpen] = useState(false); - // 从 localStorage 加载已选货币 + // Load selected currencies from localStorage useEffect(() => { try { const saved = localStorage.getItem(STORAGE_KEY); @@ -77,11 +76,11 @@ const Tool: FC = () => { } catch (error) { console.error("Failed to load saved currencies:", error); } - // 默认选择 USD 和 CNY (美元第一) + // Default: USD and CNY (USD first) setSelectedCurrencies(["USD", "CNY"]); }, []); - // 保存已选货币到 localStorage + // Save selected currencies to localStorage useEffect(() => { if (selectedCurrencies.length >= 2) { try { @@ -92,39 +91,39 @@ const Tool: FC = () => { } }, [selectedCurrencies]); - // 获取汇率数据 - 使用 stale-while-revalidate 缓存策略 + // Fetch exchange rates with date-based caching useEffect(() => { - const fetchRates = async (useCache = true) => { + const fetchRates = async () => { try { - // 1. 尝试从缓存加载 - if (useCache) { - const cached = localStorage.getItem(RATES_CACHE_KEY); - if (cached) { - try { - const { rates: cachedRates, timestamp } = JSON.parse(cached); - const age = Date.now() - timestamp; - - if (age < RATES_CACHE_DURATION) { - // 缓存未过期,直接使用 - setRates(cachedRates); - setLoading(false); - return; - } else { - // 缓存过期,先使用旧数据,后台更新 - setRates(cachedRates); - setLoading(false); - // 继续执行下面的网络请求 - } - } catch (e) { - console.error("Failed to parse cached rates:", e); + // 1. Try to load from cache + const cached = localStorage.getItem(RATES_CACHE_KEY); + if (cached) { + try { + const { rates: cachedRates, date: cachedDate } = JSON.parse(cached); + + // Check if cached data is from today + const today = new Date().toISOString().split('T')[0]; // YYYY-MM-DD + + if (cachedDate === today) { + // Cache is valid (same date), use it directly + setRates(cachedRates); + setLoading(false); + return; + } else { + // Cache is outdated, show old data first then update in background + setRates(cachedRates); + setLoading(false); + // Continue to fetch new data below } + } catch (e) { + console.error("Failed to parse cached rates:", e); } } - // 2. 从网络获取最新数据 + // 2. Fetch latest data from network setLoading(true); const response = await fetch("https://api.frankfurter.app/latest?base=USD", { - cache: "no-cache", // 绕过浏览器缓存,确保获取最新数据 + cache: "no-cache", }); if (!response.ok) { @@ -133,34 +132,35 @@ const Tool: FC = () => { const data = await response.json(); const allRates: Record = { USD: 1, ...data.rates }; + const apiDate = data.date; // Date from API (YYYY-MM-DD format) - // 3. 更新状态和缓存 + // 3. Update state and cache setRates(allRates); localStorage.setItem( RATES_CACHE_KEY, - JSON.stringify({ rates: allRates, timestamp: Date.now() }) + JSON.stringify({ rates: allRates, date: apiDate }) ); } catch (error) { - // 如果有缓存,即使网络请求失败也继续使用缓存 + // If cache exists, continue using it even if network fails const cached = localStorage.getItem(RATES_CACHE_KEY); if (cached) { try { const { rates: cachedRates } = JSON.parse(cached); setRates(cachedRates); - toast.info("使用缓存的汇率数据(网络请求失败)"); + toast.info("Using cached exchange rates (network request failed)"); } catch (e) { console.error("Failed to use cached rates:", e); if (error instanceof Error) { - toast.error(`获取汇率失败: ${error.message}`); + toast.error(`Failed to fetch rates: ${error.message}`); } else { - toast.error("获取汇率失败"); + toast.error("Failed to fetch rates"); } } } else { if (error instanceof Error) { - toast.error(`获取汇率失败: ${error.message}`); + toast.error(`Failed to fetch rates: ${error.message}`); } else { - toast.error("获取汇率失败"); + toast.error("Failed to fetch rates"); } } } finally { @@ -171,13 +171,13 @@ const Tool: FC = () => { fetchRates(); }, []); - // 初始化金额并在汇率加载后计算其他货币 + // Initialize amounts after rates are loaded useEffect(() => { if (selectedCurrencies.length > 0 && Object.keys(rates).length > 0) { if (Object.keys(amounts).length === 0) { - // 首次初始化,以 1 USD 为基准 + // Initial setup: 1 USD as base const initialAmounts: Record = {}; - const baseAmountInUSD = 1; // 1美元 + const baseAmountInUSD = 1; // 1 USD selectedCurrencies.forEach(code => { if (rates[code]) { @@ -192,17 +192,17 @@ const Tool: FC = () => { } }, [selectedCurrencies, rates, amounts]); - // 监听汇率更新,自动重新计算所有金额 + // Auto-update amounts when rates change useEffect(() => { if (Object.keys(rates).length > 0 && Object.keys(amounts).length > 0) { - // 找到第一个有值的货币作为基准 + // Find first currency with value as base const baseCurrency = selectedCurrencies.find(code => amounts[code] && parseFloat(amounts[code]) > 0); if (baseCurrency && rates[baseCurrency]) { const baseValue = parseFloat(amounts[baseCurrency]); const amountInUSD = baseValue / rates[baseCurrency]; - // 重新计算所有货币 + // Recalculate all currencies const updatedAmounts: Record = {}; selectedCurrencies.forEach(code => { if (rates[code]) { @@ -213,7 +213,7 @@ const Tool: FC = () => { } }); - // 只在值有变化时更新 + // Only update if values changed const hasChanged = selectedCurrencies.some( code => updatedAmounts[code] !== amounts[code] ); @@ -225,9 +225,9 @@ const Tool: FC = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [rates]); - // 处理货币金额变化 + // Handle amount input change const handleAmountChange = (currencyCode: string, value: string) => { - // 只允许数字和小数点 + // Only allow numbers and decimal point if (value !== "" && !/^\d*\.?\d*$/.test(value)) { return; } @@ -237,7 +237,7 @@ const Tool: FC = () => { return; } - // 如果输入为空,清空所有货币 + // Clear all if empty if (value === "" || value === "0" || parseFloat(value) === 0) { const newAmounts: Record = {}; selectedCurrencies.forEach(code => { @@ -249,13 +249,13 @@ const Tool: FC = () => { const inputValue = parseFloat(value); - // 计算其他货币的值 + // Calculate other currencies const newAmounts: Record = { [currencyCode]: value }; - // 先转换为 USD + // Convert to USD first const amountInUSD = inputValue / rates[currencyCode]; - // 转换为其他货币 + // Convert to other currencies selectedCurrencies.forEach(code => { if (code !== currencyCode && rates[code]) { const convertedAmount = amountInUSD * rates[code]; @@ -266,12 +266,12 @@ const Tool: FC = () => { setAmounts(newAmounts); }; - // 添加货币 + // Add currency const addCurrency = (currencyCode: string) => { if (!selectedCurrencies.includes(currencyCode)) { setSelectedCurrencies([...selectedCurrencies, currencyCode]); - // 计算新货币的初始金额 + // Calculate initial amount for new currency if (rates[currencyCode] && Object.keys(amounts).length > 0) { const firstCurrency = selectedCurrencies[0]; if (firstCurrency && amounts[firstCurrency] && rates[firstCurrency]) { @@ -282,31 +282,31 @@ const Tool: FC = () => { } } - toast.success(`已添加 ${currencyCode}`); + toast.success(`Added ${currencyCode}`); } }; - // 删除货币 + // Remove currency const removeCurrency = (currencyCode: string) => { if (REQUIRED_CURRENCIES.includes(currencyCode)) { - toast.error(`${currencyCode} 是必选货币,无法删除`); + toast.error(`${currencyCode} is required and cannot be removed`); return; } if (selectedCurrencies.length <= 2) { - toast.error("至少需要保留两种货币"); + toast.error("At least two currencies are required"); return; } setSelectedCurrencies(selectedCurrencies.filter((c) => c !== currencyCode)); - // 删除对应的金额 + // Remove corresponding amount const newAmounts = { ...amounts }; delete newAmounts[currencyCode]; setAmounts(newAmounts); - toast.success(`已删除 ${currencyCode}`); + toast.success(`Removed ${currencyCode}`); }; - // 获取货币信息 + // Get currency info const getCurrency = (code: string): Currency | undefined => { return AVAILABLE_CURRENCIES.find((c) => c.code === code); }; @@ -314,23 +314,23 @@ const Tool: FC = () => { return (
- {/* 货币选择器 */} + {/* Currency selector */}
- - {/* 添加货币按钮 - 固定在右上角 */} + + {/* Add currency button - fixed at top right */} - + - 未找到货币 + No currency found {AVAILABLE_CURRENCIES.map((currency) => { const isSelected = selectedCurrencies.includes(currency.code); @@ -341,7 +341,7 @@ const Tool: FC = () => { value={`${currency.code} ${currency.name}`} disabled={isRequired} onSelect={() => { - if (isRequired) return; // 必选货币不可操作 + if (isRequired) return; // Required currencies cannot be toggled if (isSelected && selectedCurrencies.length > 2) { removeCurrency(currency.code); } else if (!isSelected) { @@ -367,7 +367,7 @@ const Tool: FC = () => {
- {/* 已选货币 Badges */} + {/* Selected currency badges */}
{selectedCurrencies.map((code) => { const currency = getCurrency(code); @@ -398,18 +398,18 @@ const Tool: FC = () => {
- {REQUIRED_CURRENCIES.map((c) => getCurrency(c)?.code).join("、")} 为必选货币,至少保留两种货币 + {REQUIRED_CURRENCIES.map((c) => getCurrency(c)?.code).join(", ")} are required. At least two currencies needed.
- {/* 货币列表 */} + {/* Currency list */} {loading ? (
- 加载汇率数据中... + Loading exchange rates...
) : (
-
货币金额:
+
Currency Amounts:
{selectedCurrencies.map((code) => { const currency = getCurrency(code); @@ -425,7 +425,7 @@ const Tool: FC = () => { handleAmountChange(code, e.target.value)} className="text-xl font-semibold" @@ -440,7 +440,16 @@ const Tool: FC = () => { {!loading && Object.keys(rates).length > 0 && (
- 汇率数据来源: Frankfurter API (基于欧洲央行数据) + Exchange rate data from:{" "} + + Frankfurter API + + {" "}(Based on European Central Bank data)
)}