Reputation: 1952
I am trying to format currencies depending on the currency selected by the user. If no currency is selected, then device's current locale is used for formatting. however, I am having issues:
I am using a number formatter to format the double to currency string.
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.currencySymbol = ""
if currencyCode != nil {
formatter.currencyCode = currencyCode
}
let amount = Double(amt/100) + Double(amt%100)/100
return formatter.string(from: NSNumber(value: amount))
}
The currencyCode is basically the currency the user has selected. However, if say the user selects EURO, the formatting is pretty much the same as for USD, meaning that it is not respecting the currency selected. I know that we cannot possibly create a Locale out of currencyCode since EUR is used in 26 different countries so it's impossible to derive the correct locale.
Also since I am using a format which basically fills the decimals position, then ONES, Tenths and so on and some currencies don't support decimal positions for example, PKR (Pakistani Ruppee) so how can I cater for that?
So my question is how can I format a currency correctly regardless of which device locale is selected. If my device locale is say USD and I create a EUR list, I would like all payments inside the list to be in EUR format. So if in USD a price is $3,403.23 in EUR it should be € 3 403,23.
Any advice on how I should go about formatting? Thanks!
Upvotes: 4
Views: 4050
Reputation: 54716
You can dynamically match Locale
s to currency codes, since you can create all supported Locale
s from the availableIdentifiers
property of Locale
and then you can check their currencyCode
property to match the currency code your user input.
extension Locale: CaseIterable {
public static let allCases: [Locale] = availableIdentifiers.map(Locale.init(identifier:))
}
public extension Locale {
init?(currencyCode: String) {
guard let locale = Self.allCases.first(where: { $0.currencyCode == currencyCode }) else { return nil }
self = locale
}
}
Locale(currencyCode: "EUR") // es_EA
Locale(currencyCode:"GBP") // kw_GB
However, as you can see, this can return exotic locales, which might not necessarily give your desired formatting.
I would rather suggest hardcoding the desired Locale
for each currency code that your app supports, that way you can be 100% sure the formatting always matches your requirements. You can also mix the two approaches and have hardcoded Locale
s for the well-known currency codes, but use the dynamic approach for more exotic currency codes that you have no hard requirement over how they should be formatted.
Upvotes: 2
Reputation: 73376
The locale settings related to currency are of two kinds:
Fortunately, Swift makes very well the difference. Here some code, that allows you to adapt the currency dependent settings, without ever touching to the cultural settings that important for the user. I'll also explain why you shouldn't change all the local settings.
Here the demo code, with a couple of representative currencies:
let value: Double = 1345.23
for mycur in ["USD", "TND", "EUR", "JPY" ] {
let myformatter = NumberFormatter()
myformatter.numberStyle = .currencyISOCode
let newLocale = "\(Locale.current.identifier)@currency=\(mycur)" // this is it!
myformatter.locale = Locale(identifier:newLocale)
print ("currency:\(mycur): min:\(myformatter.minimumFractionDigits) max:\(myformatter.maximumFractionDigits)"
print ("result: \(myformatter.string(from: value as NSNumber) ?? "xxx")")
}
For a representative demo, I've used :
For the user, will benefit from the principle of least astonishment, and see the decimal and thousand separators and the positioning he/she is used-to.
in my current locale (in my language currency code is to the right, decimals are separated by a comma, and thousands with a hard space) the result will be:
cur:USD: min:2 max:2 result: 1 345,23 USD
cur:TND: min:3 max:3 result: 1 345,230 TND
cur:EUR: min:2 max:2 result: 1 345,23 EUR
cur:JPY: min:0 max:0 result: 1 345 JPY
But if you'd usually work in an English speaking environment, for example in a US culture, you'd get:
cur:USD: min:2 max:2 result: USD 1,345.23
cur:TND: min:3 max:3 result: TND 1,345.230
cur:EUR: min:2 max:2 result: EUR 1,345.23
cur:JPY: min:0 max:0 result: JPY 1,345
The trick of the code is to create a new locale, by just changing the currency settings, but leaving intact all other country and language dependent parameters.
let newLocale = "\(Locale.current.identifier)@currency=\(mycur)" // this is it!
myformatter.locale = Locale(identifier:newLocale)
If you would start to adapt the positioning to take the practice of the language of the country the currency is originating from, you might irritate the users who no longer see the currency code where they expect them. Fortunately, it will not create a real confusion.
Example: the EUR is the currency of countries with very different cultures. The rule about positioning of the currency or the currency symbol was therefore defined to be dependent on the language of the text in which the amount appears. Official reference
Now, if you would start to adopt thousand and decimal separators of another language or country because it's the currency's home country, this would create a real confusion, especially for smaller amounts. Moreover, it's not always possible.
Example: In Canada the same currency amount is written with comma decimal separator by French-speaking Canadians, but dot decimal separator by english-speaking Canadians. This clearly shows it's not the currency that determines the separators to use, but the language of the user.
You should therefore be respectful of the user's settings in this regard, and only adapt the currency specific settings.
Upvotes: 8