Ok now I’m working on one of the biggest fintech project with thousands of customers around the globe. It’s especially important for my team and me to support accessibility and internationalization (i18n) in our products.
This time I’d like to disclose a lot of insights that I’ve found out while researching formatting of positive and negative amounts for Hebrew/Arabic (so-called RTL) UI languages to be able to properly show currencies in our banking apps anywhere in the world.
The problem is [NS]NumberFormatter from iOS SDK doesn’t support
.currency formatting mode with a positive prefix sign. But what if your bank app should be able to show both −123,45 € and +678,09 € but for any existing regional variation? For example, if your bank app user traveled to Middle East, or just paid online in shekels or dirhams. Or what if your bank is for Middle East users itself?
To emphasize, in this article I’m writing about iOS app user interfaces for RTL region locales, not about the languages nor scripts themselves.
RTL, what’s up with that
Why? These 450M people live in about 30 countries (out of 195) =15%. This is a huge number in terms of where would you like to offer your product.
But RTL support for numbers (123) and especially money amounts in coöperation with currencies (123 €) is not that simple. There are 12 contemporary RTL scripts in that 30 countries, it means there can be dozens of different regional settings.
For example, people living in UAE 🇦🇪 can have their iPhone UI in Arabic (regional setting
ar_AE), or they can have it in English (
en_AE). Such settings can be simulated in Xcode:
Apparently the app UI would look mirrored in case of Arabic language
ar, and it would be just like regular app UI in Europe or US in case of English language
en; even thou the regional setting is set to
AE which means United Arab Emirates. This is what mirrored UI looks like, iOS Setting app for
And here’s the question what it should look like the amount of money for local or foreign currency displayed in the banking app for these number of localizations? Is it $99 or 420 €, or 146 ₽, or HK$ 44?
Well, it all depends on region. There’s no universal rule for RTL languages, as well as there’s no such rule for LTR. But what’s even worse (in terms of programming) is that there are hidden Unicode RTL marks combined with other whitespace characters should be inserted to the RTL strings to make sure its char sequence is displayed in the right order.
Let me show few examples, but before that I’d like to list those RTL marks:
U+00A0: NO-BREAK SPACE (a
U+200E: LEFT-TO-RIGHT MARK
U+200F: RIGHT-TO-LEFT MARK
U+061C: ARABIC LETTER MARK
The RTL Mark control charatcter reverses natural flow of a string sequence in the opposite way. At the same time, the LTR Mark encountered in the same string, reverses its flow in the opposite way again! The
U+061C does something similar to what RTL Mark, but I can’t even imagine what a string flow could be if accidentally contain all of them.
And yes, all these non-printing characters are mixed and matched in dozens different ways depending on locale, currency and region settings. Let’s have a look!
Even thou [NS]NumberFormatter doesn’t support currency format with a positive prefix sign, here’s the first latest & greatest solution on how to have this format: for a positive amount just negate it, then let the NumberFormatter to format it and then replace a minus sign with a plus.
I’ve read all the docs and tried a lot of configurations, and figured out that there is no other way to have a “+678,09 €” value out of NumberFormatter.
Now, since we have a formatted positive amount combined with a currency sign, let’s have a look on their variations for different locales, regions and legal tender.
For the Netherlands a positive and negative amount of 123,45 Euro would be properly formatted in this way:
Notice a no-break space char inserted by the NumberFormatter between Euro sign and plus/minus sign. This is to make sure the whole string won’t be splitted to different lines of text.
Also notice a comma
, delimiting decimals.
Let’s have a look at 123,45 USD withing the EU bank app user:
Same ‘European’ format but for
For the US — amount of 123,45 US dollars would be like:
Notice a leading placement of positive
+ and negative
− indicator, compare it to
nl_NL variant. And a period
. delimiting decimals. Also there’s no no-break space anywhere in between.
Also, 123,45 Israeli shekels (ILS, ₪) but for an ILS account within a US bank:
Same way as US dollars, since it is the same locale and region!
For Russia — amount of 123,45 Russian rubles:
Again, notice a trailing position of ₽ currency sign, a comma, and a no-break space between the amount and the ₽.
Also, 123,45 USD but for a USD account within a Russian bank app:
Exact the same format for dollars for the same locale!
What that mean for app UI? If you’re developing a bank app for EU customers, most probably you’ll only format currencies in the
nl_NL or similar way. For US — in the
en_US way. For Russia —
ru_RU. There’s no need of different format if everyone use your default locale as well.
But then a US customer switched their iPhone language to Hebrew:
Everything moves from left to right to became RTL! And now your awesome app has to support this locale. Let’s see what happens to USD accounts and transactions for
A 123,45 USD but fromatted for the Israeli UI:
"\(rtlMark)\(ltrMark)−123.45\(nbsp)$" // -123.65 $
"\(rtlMark)\(ltrMark)+123.45\(nbsp)$" // +123.65 $
Same for ILS national currency within same locale:
"\(rtlMark)\(ltrMark)−123.45\(nbsp)\(ILS)" // -123.65 ₪
"\(rtlMark)\(ltrMark)+123.45\(nbsp)\(ILS)" // +123.65 ₪
Even thou it would look like +123.65 $, it has a lot of hidden gems in it.
A 123,45 USD but fromatted for the Arabic UAE user interface:
"\(ltrMark)−$\(nbsp)123.45" // -$ 123
"\(ltrMark)+$\(nbsp)123.45" // +$ 123
For local AED currency:
"\(ltrMark)−\(AED)\(rtlMark)\(nbsp)123.45" // -123.45 \aed
"\(ltrMark)+\(AED)\(rtlMark)\(nbsp)123.45" // +123.45 \aed
None the less to say, the
\aed here will look like a د.إ .
As you can see the format is the same, but it’s mind blowing. LTR and RTL marks, a currency sign, hidden whitespaces, the amount — they are all in the various positions.
A 123,45 USD for Arabic UI in Qatar:
"\(u061c)−\(arabic_123_45)\(nbsp)US$") // ١٢٣٫٦٥- US$
"\(u061c)+\(arabic_123_45)\(nbsp)US$") // ١٢٣٫٦٥+ US$
Notice 123,45 became ١٢٣٫٦٥.
A 123,45 of local Qatar currency:
Here not only 123,45 became Arabic, but also currency sign became Arabic, and keep in mind it is still an right-to-left written string. I can’t even paste it to here without breaking a markup!
With all that said, as a senior iOS engineer, I may suggest you never use this kind of number formatter in the wild. Seriously. It is so much better to re-design your app UI and UX to avoid this kind of things which you never know would be turned on into what.
To keep your app sexy and unbroken in unknown environment, it’s better to separate the currency sign from the amount. Best designers (btw, developers are designers as well) will do like this:
But for those of us who really likes challenges I’ve created a PositiveCurrencyFormatter framework for iOS and macOS that properly formats any amount of money of any currency for any locale. Using iOS SDK’s NumberFormatter under the hood and fully covered with tests.
GitHub - m4rr/PositiveCurrencyFormatter
Contribute to m4rr/PositiveCurrencyFormatter development by creating an account on GitHub.
You’re welcome to use it for free under the MIT licence 🙌🏼