Swift: Formatting currencies with localized million and billion text - ios

I'm working on an app that shows currencies with large numbers.
To make it more user friendly it would be nice to show the numbers with letters, e.g:
$24,514,983,671 -> $24.5B // English
3,306,777.10 € -> 3,31 Mio. € // German
kr 23 000 000 -> kr 23 mill. // Norwegian
The numbers should have minimumSignificantDigits = 1 and maximumSignificantDigits = 3.
How should this be solved in Swift? Is there any library available?
Android has this solution
It seems to be based on swift-corelibs-foundation: Github, but I can't see how to use it in my app.
Will I have to make the logic myself with localized translations?
I have found an answer for using general abbreviations K/M/B for large numbers here: iOS convert large numbers to smaller format, but it does not solve the whole problem.

You will have to implement your own solution, but it is not hard. One way to go would be to create two dictionaries where the key will be the Locale identifier and the value the translation:
let millionTrans = [ "en_US" : "million", ...]
let billionTrans = [ "en_US': "billion", ...]
then get the Locale.current, find out if the amount is in the millions or billions and query the appropriate dictionary to get its value.

Related

Formatting strings with plurals for localization

I have a simple command that I want to localize : "Select 5 objects". I had thought that I might template this as : "Select %d %s", where the number and objects might be formatted later. But this raised the question of how do you appropriately pluralize a string?
When the object can have multiple plurals :
0 : no objects
1 : 1 object
2 or more : 2 objects
What is a good way to structure your string templates to accommodate for optional plurality? This is especially important for localization later, so I'm trying to be smart about it now.
Reading online, a lot of the top hits for localization best practices rely on Apple's NSLocalizedString library, which allow for string dictionaries with custom rules around the cardinality of the word. But is there a good way to handle this when all of my strings are loaded into a CSV? Roblox provides a TranslationService that comes with a Translator class that can format strings by keys. So I've structured my resources like this :
localization.csv
Key, Example, en, fr, es
Command.SelectTemplate, "Select 3 apples", "Select {total : number} {thing : string}", "Sélectionnez {total : number} {thing : string}" "Selecciona {total : number} {thing : string}"
Object.AppleZero, "there are zero apples", "apples", "pommes", "manzanas"
Object.AppleOne, "there is one apple", "apple", "pomme", "manzana"
Object.AppleFew, "there are a few apples", "apples", "pommes", "manzanas"
example Script
local LocalizationService = game:GetService("LocalizationService")
local LOCALE = "en"
local res, translator = pcall(function()
return LocalizationService:GetTranslatorForLocaleAsync(LOCALE)
end)
if not res then
warn(string.format("Failed to load the translator with error %s", tostring(translator)))
return
end
local function getSelectionString(numToSelect : number, objectKey : string)
-- TODO : FIND A BETTER WAY TO DO THIS
assert(numToSelect >= 0, "Cannot select a negative number")
local lookupKey
if numToSelect == 0 then
lookupKey = objectKey .. "Zero"
elseif numToSelect == 1 then
lookupKey = objectKey .. "One"
else
lookupKey = objectKey .. "Few"
end
local objectString = translator:FormatByKey(lookupKey)
local formattingArgs = {
total = numToSelect,
thing = objectString,
}
local commandString = translator:FormatByKey("Command.SelectTemplate", formattingArgs)
return commandString
end
-- Test it out
local objectKey = "Object.Apple"
print(getSelectionString(0, objectKey)) -- "Select 0 apples"
print(getSelectionString(1, objectKey)) -- "Select 1 apple"
print(getSelectionString(2, objectKey)) -- "Select 2 apples"
While this technically works, it requires defining multiple keys for every single object that may have plurality in other languages, and it assumes a very English way pluralizing objects. So to reiterate my question from earlier, what is a good way to structure string templates to accommodate for optional plurality?
One of the best ways of handling plurals is ICU message syntax. ICU means International Components for Unicode – a widely used set of C/C++, Java, and other libraries providing Unicode and globalization support for software and applications.
ICU format allows you to create user-friendly texts that combine the use of different plural, gender, date and time forms in one string, which will vary depending on who is the user. For example, “Sam sent you 2 messages” and “Emma sent you 1 message”.
For example:
You have {count, plural,
=0 {no apples}
one {one apple}
other {# apples}
}
You can store these strings in different file formats, even in CSV. Different languages have different pluralization rules and different numbers of plural forms. So translators should translate ICU strings according to the target language rules.
Also, there are some localization file formats that have native plurals support, for example, Android XML, iOS Strings.
Modern Localization Management platforms support the ICU syntax. For example, Crowdin. Here you can read more about ICU Message Syntax support in Crowdin. In addition - The Ultimate Guide to ICU Message Format.

Formatting a currency depending upon the currency code selected regardless of device's locale (Swift)

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!
In short
The locale settings related to currency are of two kinds:
Currency dependent: these are related to the monetary value and depend only on the currency and remain valid wherever you use that currency. This is only the international ISO code and the number of decimals, as defined by ISO 4217.
Cultural settings: these depend on the usages and practices related to the language and the country of the users and not directly to the currency. Typically, it's the position of the currency code or symbol relatively to the value, as well as the decimal and thousand separators.
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.
The code
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 :
the USD and the EUR, which, like most currencies can be divided in 100 sub-units (the cents),
the TND (Tunesian Dinar), which, like a handfull of other dinar-currencies, can be divided in 1000 sub-units (the millims),
the JPY(Japanese Yen), which could in the past be divided into sub-units (the sens) of such a small value that the Japanese government decided not to use them anymore. This is why there are no decimals anymore for JPY amounts.
The results
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
How it works:
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)
Why you should not fully implement what you wanted
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.
You can dynamically match Locales to currency codes, since you can create all supported Locales 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 Locales 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.

Parsing string in LUA

I have been looking around and have read a lot of different answers but none seems to answer my specific request.
I make watchfaces for Wear OS 2 with an app called ''WATCHMAKER'' witch uses LUA as language. I want to make a watch face with a special clock pointing to a number depending on a blood sugar value sent by an transmitter connected to the body.
The string values I want to parse follows this syntax:
<DECIMAL NUMBER> <ARROW> (<TIME>)
One example would be
5,6 -> (1m)
I want to extract the <DECIMAL NUMBER> part of the reading. In the above example, I want the value 5,6.
Every 5 minutes, the transmitter sends another reading, all of those informations change:
5,8 - (30 secondes)
Thank you so much
Say you have a string, in LUA, s="14,11 -> (something)" and you want this first number of the string to be parsed to a float so you can do maths on it.
s='9,6 -> (24m)'
-- Now we use so called regular expressions
-- to parse the string
new_s=string.match(s, '[0-9]+,[0-9]+')
-- news now has the number 9,6. Which is now parsed
-- however it's still a string and to be able to treat
-- it like a number, we have to do more:
-- But we have to switch the comma for a period
new_s=new_s:gsub(",",".")
-- Now s has "9.6" as string
-- now we convert it to a number
number = string.format('%.10g', tonumber(new_s))
print(number)
Now number contains the number 9.6.

Recognising number 5 in a double or an S when using OCR

I am using OCR on a receipt reading app I'm building. Understandably the OCR struggles differing between an S and a 5.
My app finds each line in a restaurant receipt normally formatted like below:
1 Champagne £505.55
5 Burger with chips £25.00
2 Chips with cheese £5.00
2 Coke £1.50
1 Ketchup £0.50
5 Penny sweets £0.05
Currently I can find the Int and the text fine, I can also get the double at the end too but rarely if it contains a five. Is there some regex that I can put in place to determine if a 5 has been replaced by looking at its surroundings? I can only assume at the moment by recognising the currency symbol and replacing any occurrences after that? but sometimes it struggles to recognise those or there isn't one. Any suggestions or help would be great. Thanks
edit: I understand there may not be a perfect answer to get tough prices like £555.55 that appears as SSS.SS but if there is something for the more commom prices like 0.50, 10.50 or 5.00 etc id love to hear some suggestions. Thanks again
Update:
mutating func replaceWhereFivesShouldBe() {
do {
let regEx = try! NSRegularExpression(pattern: "\\s+[0-9S]+\\.[0-9S]{2}")
let range = NSMakeRange(0, self.characters.count)
self = regEx.stringByReplacingMatches(in: self, range: range, withTemplate: "5")
} catch {
return
}
}
Use a regex to match any text after £ on the line and replace all "S" chars with "5". This assumes that you will only ever have non-alpha characters (specifically no S characters) after the currency symbol. This regex should work:
£[0-9S]+\.[0-9S]{2}
From there, find the index of your S chars and replace them with 5.
In the case that the currency symbol isn't present (or detected), just using the regex to identify the currency amount should work. Based on your example, I wouldn't expect to find that pattern in the item description. Something like this:
\s+[0-9S]+\.[0-9S]{2}
Or if the currency symbol is garbled, wildcard it like:
\s+.[0-9S]+\.[0-9S]{2}

Swift, iOS: How to convert a string containing number and character (i.e ',' or ',') into number?

I have an double that i am converting using NSMassFormatter from kg to lb.
let massFormatter = NSMassFormatter()
var xyz = massFormatter.stringFromKilograms(10000.000)
// xyz "22,046.226 lb"
Now I want a way to extract the number from the string. Also if I change the Locale to say es (Spain) then the value becomes "10.000,000 kg" (It actually returns "10.000 kg", removing the decimal points for unknown reasons), but i want a way such that I can extract the number regardless of the locale. Is there any standard way? Like use a regrex or some function in NSNumberFormatter?
Thank you
There is no way to do that fully independent of locale. The main problem is that identical string will be interpreted differently depending on what locale it is run against.
Best solution will be to identify all the possible formats, define all possible formatters and try to get numberFromString: from each formatter - until the first one to obtain the correct result.
The other solution, if you're getting the data from user input, is to explain the correct format to users and provide them with instant validation - i.e. showing "incorrect format" error message. Some apps have used the UIKeyboardTypeNumberPad keyboard to restrict user, so that you'll have only numeric values.
Two keys to the problem, finding the localized units (the "kg") part in your example, and converting the string using localized grouping and decimal separators:
// convert mass to string
var lbs = massFormatter.stringFromKilograms(10000)
println("\(lbs)")
// get localized unit specifier and remove from formatted string
var units = massFormatter.unitStringFromKilograms(10000, usedUnit: nil)
if let range = lbs.rangeOfString(units) {
lbs.replaceRange(range, with: "")
}
// get number formatter and set it to use grouping separator (, or .)
let numberFormatter = NSNumberFormatter()
numberFormatter.usesGroupingSeparator = true
// get number back
var kg = numberFormatter.numberFromString(lbs)
println("\(kg)")

Resources