From c7719fe5ad2a282cf5d859b296dd8bed5e6c6f81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Falc=C3=B3n?= Date: Wed, 15 Jul 2026 14:32:18 +0200 Subject: [PATCH] docs: document adding a new currency (#681) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Adds `docs/adding-a-currency.md`, mirroring `docs/adding-a-banking-provider.md` but for currencies. Distilled from the NGN (#642) and GHS (#644) additions. The gist: adding a currency is a config change, not a feature. - **Required**: one entry in `config/currencies.php`. Validation (Form Requests), the Inertia dropdown props, and conversion all auto-derive from it via `CurrencyOptions` / `CurrencyConversionService`. - **Translation**: add the name to `lang/es.json` (enforced), `lang/fr.json` optional. Flagged that `LocalizationTest` won't auto-catch a missing one, since the name is translated in PHP, not scanned from TS source. - **Optional**: a custom short symbol in `resources/js/utils/currency.ts`. - **Two gotchas**: use the current ISO 4217 code (the GHC→GHS lesson from #644), and verify the provider covers the code at the right rate via the CDN URL. ## Testing Docs-only change; no code touched. --- docs/adding-a-currency.md | 107 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 docs/adding-a-currency.md diff --git a/docs/adding-a-currency.md b/docs/adding-a-currency.md new file mode 100644 index 00000000..fc1b89ae --- /dev/null +++ b/docs/adding-a-currency.md @@ -0,0 +1,107 @@ +# Adding a currency + +Adding a currency is a config change, not a feature. In the common case it is a +single entry in `config/currencies.php` plus one translation string. Everything +else — validation, the Inertia props that feed the dropdowns, and conversion — +derives from that entry automatically. + +Reference PRs: [#642 (NGN)](https://github.com/whisper-money/whisper-money/pull/642), +[#644 (GHS)](https://github.com/whisper-money/whisper-money/pull/644). + +--- + +## The one required change + +Add an entry to the `options` array in `config/currencies.php`: + +```php +[ + 'code' => 'NGN', // ISO 4217 code, stored on users/accounts + 'name' => 'Nigerian Naira', // English name; the __() key for translations + 'allows_primary' => true, // can be a user's primary/display currency + 'allows_account' => true, // can be an individual account's currency +], +``` + +`allows_primary` / `allows_account` are independent. Assets you can hold but not +display everything in (e.g. `BTC`) are `allows_account => true`, +`allows_primary => false`. + +That's the whole wiring. `App\Services\CurrencyOptions` reads this config and +feeds: + +- **Validation** — `in:` rules in `ProfileUpdateRequest`, `StoreAccountRequest`, + `UpdateAccountRequest` all pull from `CurrencyOptions`, so the new code is + accepted the moment it's in config. +- **The UI dropdowns** — `HandleInertiaRequests` shares `primaryOptions()` / + `accountOptions()` as Inertia props; the React selects render whatever is there. +- **Conversion** — `CurrencyConversionService` fetches + `{date}/v1/currencies/{code}.min.json` from the `@fawazahmed0/currency-api` + CDN and `ExchangeRateService` stores rates as JSON. Both lowercase the code and + are currency-agnostic, so any code the provider covers just works. No code change. + +--- + +## The translation (do this too) + +`CurrencyOptions` runs the `name` through `__()` in PHP before handing it to the +frontend, so add the Spanish name to `lang/es.json`: + +```json +"Nigerian Naira": "Naira Nigeriano", +``` + +Spanish (`es`) is the enforced locale. French (`lang/fr.json`) is optional and +warning-only — add it if you can (#644 did), skip it otherwise. + +> ⚠️ `LocalizationTest` scans **TS/TSX source** for `__()` keys. Currency names +> are translated in PHP and never appear literally in the frontend, so a missing +> Spanish name is **not** auto-caught by CI. Add it by hand or the UI shows the +> English name. + +--- + +## Optional: a custom symbol + +`resources/js/utils/currency.ts` has a small `getCurrencySymbol` map for short +symbols (`$`, `€`, `₦`). It falls back to the currency code, and `formatCurrency` +uses `Intl.NumberFormat` which renders its own symbol regardless — so only add an +entry if you want a specific short glyph: + +```ts +NGN: '₦', +``` + +`#644` (GHS) skipped this; `#642` (NGN) added it. Both are fine. + +--- + +## Two things to verify before you add a code + +1. **Use the current ISO 4217 code.** #644 was requested as `GHC` — the + deprecated pre-2007 Ghanaian code. The provider still serves `ghc`, but at a + rate scaled ×10 000 (Ghana redenominated: 1 GHS = 10 000 GHC), which would + inflate every balance 10 000×. The correct modern code is `GHS`. Deprecated + codes existing in the provider does not mean they're correct. + +2. **Confirm the provider actually covers it, at the right rate.** Open + `https://cdn.jsdelivr.net/npm/@fawazahmed0/currency-api@latest/v1/currencies/{code}.min.json` + (lowercase) and check the code is present and the rate is sane + (e.g. `EUR→NGN ≈ 1567`). If it's missing, conversion silently returns the + unconverted amount (logged as a warning) — the currency is still selectable, + it just won't convert. + +--- + +## Test & checklist + +- [ ] entry in `config/currencies.php` (`code`, `name`, `allows_primary`, `allows_account`) +- [ ] Spanish name in `lang/es.json` (French in `lang/fr.json` if you can) +- [ ] symbol in `resources/js/utils/currency.ts` (only if you want a custom glyph) +- [ ] provider covers the code at the correct rate (verified via the CDN URL) + +```bash +php artisan test --compact tests/Feature/CurrencyConversionServiceTest.php \ + tests/Feature/LocalizationTest.php +vendor/bin/pint --dirty +```