Skip to content

feat(frontend): add locale-aware currency formatting for NGN and Stellar assets - #1565

Open
AlphaMaleBaDI wants to merge 1 commit into
Split-Naira:mainfrom
AlphaMaleBaDI:feat/1503-locale-currency-tests
Open

AlphaMaleBaDI wants to merge 1 commit into
Split-Naira:mainfrom
AlphaMaleBaDI:feat/1503-locale-currency-tests

Conversation

@AlphaMaleBaDI

Copy link
Copy Markdown

Summary

Adds locale-aware currency formatting for Nigerian naira and the Stellar assets
this app supports, with a test suite covering success, failure and boundary
cases.

Amounts reach the frontend as bare integers and decimal strings, but what a user
reads is locale-dependent. The same balance renders three different ways:

Locale Output
en-NG ₦1,234.56
en NGN 1,234.56
de 1.234,56 NGN
fr 1 234,56 NGN

Closes #1503

Changes

  • frontend/src/lib/formatCurrency.ts (new)
    • formatCurrency(amount, locale, currency) for fiat (NGN, USD, EUR)
    • formatAsset(amount, locale, code) for assets (XLM, USDC, EURC)
    • formatStroops(stroops, locale, code) for on-chain stroop integers
  • frontend/src/lib/__tests__/formatCurrency.test.ts (new) - 46 tests
  • docs/currency-formatting.md (new) - developer reference
  • docs/contributors/frontend-testing-guide.md - corrected stale stack

Design decisions worth reviewing

The module never guesses. NaN, Infinity, unsupported currencies and
structurally invalid locale tags throw instead of returning something
plausible. Intl will happily render NaN and ∞; a balance reading NaN ₦
is a confident lie in an app that moves money. Error messages name the offending
value so the fault is findable in a stack trace.

₦ only appears in en-NG. Other locales render the ISO code NGN. That
is correct rather than a gap - a naira symbol shown to a German reader is a bug.

XLM allows 7 fraction digits but does not pad to them. The smallest
transferable amount is one stroop (0.0000001 XLM); padding a whole unit to
seven digits would render 1 XLM as 1.0000000 XLM. USDC and EURC use exactly 2
digits, matching exchange convention.

Custom tokens are excluded. Their codes are user-supplied contract metadata
and cannot be validated against a closed list.

Tests

46 tests across en, en-NG, fr, de and ja:

  • Success: naira, dollars, euros, XLM, USDC, EURC
  • Locale awareness: group and decimal separators differ per locale while the
    digits stay identical
  • Negatives: sign preserved in symbol-first and symbol-last locales
  • Boundaries: zero, one kobo, a single stroop, sub-kobo rounding, large amounts,
    Number.MAX_SAFE_INTEGER
  • Failures: NaN, ±Infinity, non-number, null, unsupported currency,
    unsupported asset code, invalid locale, empty locale, non-string locale

Assertions normalise U+00A0 / U+202F and U+2212 before comparing. Asserting raw
output makes a suite fail on an ICU upgrade for reasons unrelated to the code
under test - a failure mode this repo has already hit.

Accessibility

These are pure string formatters with no DOM output, so they add no keyboard or
screen-reader surface of their own; nothing about a component's accessibility
changes when it starts calling them.

Negatives keep a real minus sign rather than relying on a localised glyph, so
they are announced as negative rather than read as positive. The docs note that
grouping separators are decorative and that a control whose accessible name
would otherwise be uninformative should carry an explicit aria-label.

Documentation

docs/currency-formatting.md covers the API, failure behaviour, the ICU caveat,
the accessibility position, and the fact that the module is not yet wired into
any component
. The testing guide's "Jest / apps/web" references were stale -
the stack is Vitest under frontend/ - and it gained a section on testing
localised output with the locale-blind mock.

Validation

  • npm run lint -w frontend - exit 0
  • npm run check:i18n -w frontend - OK, 38 keys, all locales match
  • npm test -w frontend - 553/553 pass, 55/55 files
  • npm run build -w frontend - compiles, TypeScript clean

Test count reconciles: the pre-existing 511 included a 4-test stub at this path,
so 511 - 4 + 46 = 553.

Out of scope

Components still format amounts with a bare .toLocaleString(), which uses the
host default locale rather than the route's [locale] - so number
formatting currently ignores the user's selected language. Wiring this module
into those call sites is follow-up work and is deliberately not in this PR.

Known issue in this repo (pre-existing, not introduced here)

DashboardView.perf.test.tsx and split-app.test.tsx fail intermittently under
parallel load and pass in isolation.

@drips-wave

drips-wave Bot commented Oct 19, 2026

Copy link
Copy Markdown

@AlphaMaleBaDI Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

…lar assets

Adds formatCurrency, formatAsset and formatStroops with locale-aware output for NGN, USD, EUR, XLM, USDC and EURC, plus 46 tests and a developer reference. Docs are Prettier-clean.

Closes Split-Naira#1503
@AlphaMaleBaDI
AlphaMaleBaDI force-pushed the feat/1503-locale-currency-tests branch from 93c711e to b7ca8aa Compare October 27, 2026 07:28

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Frontend] Add locale-aware currency formatting tests

1 participant