Is there any way of printing numbers into proper spellings instead of throwing numbers while recording voice via SFSpeechRecognizer? I've tried to get the word format by implementing the code below:
if let resultString = result?.bestTranscription.formattedString {
if let number = Double(resultString) {
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .spellOut
let numberString = numberFormatter.string(from: NSNumber(value: number))
let numberStringWithoutHyphen = numberString?.replacingOccurrences(of: "-", with: " ")
print(numberStringWithoutHyphen)
}
}
This solution works great if the user is speaking whole numbers or even decimal numbers but there are some cases where this solution doesn't work at all and makes this solution look dumb. For example, if the user says "Fifty five point zero", the speech recognizer picks it up as "55.0". But the number formatter returns "Fifty five". In an extreme case, if the user says "One two three four", the speech recognizer picks it up as "1234" but the number formatter returns "One thousand two hundred thirty four".
What I am aiming for is if the user says any number, the speech recognizer should return the same, word by word. If the user says "Fifty five point zero", it should return "Fifty five point zero". If the user says "One two three four", it should return "One two three four".
Related
When using the Speech framework, I am consistently noticing zero confidence values for certain locales (e.g. "vi-VN", "pt-PT", ...), while non-zero, accurate confidence values are returned for other locales (e.g. "ko-KR", "ja-JP", ...).
Looking at the documentation, the confidence would be zero if there was no recognition. However, when the zero confidence occurs, the formattedString of the bestTranscription is populated and accurate (same for each segment substring text).
I have tried instantiating the locales in various ways (language code only, language and region code, -/_ formatting, grabbing an instance directly off of the SFSpeechRecognizer.supportedLocales() array). I have also tried setting the defaultTaskHint of SFSpeechRecognizer and taskHint of SFSpeechRecognitionRequest to dictation.
I am stuck at this point. Any help would be appreciated. Thanks in advance :)
guard let locale = Locale(identifier: "vi-VN"),
let recognizer = SFSpeechRecognizer(locale: locale),
recognizer.isAvailable else {
return
}
recognizer.defaultTaskHint = .dictation
let request = SFSpeechURLRecognitionRequest(url: ...)
request.contextualStrings = ...
request.shouldReportPartialResults = true
request.taskHint = .dictation
recognizer.recognitionTask(with: request) { (result, error) in
...
if (result.isFinal) {
let transcription = result.bestTranscription
/// transcription.formattedString is correct
/// all segments confidence values are 0, but with the properly recognized substring text.
}
...
}
I have a RelativeDateTimeFormatter that I'm using to sho strings like this:
"Next lottery draw in 2 days"
"Next lottery draw tomorrow"
This comes from the strings file with string format like this "Next lottery draw %#"
My problem is how do I localise this properly when the %# is in a different place in the string. For example, in Japanese the %# is somewhere in the middle of the string.
Here's my code for setting up the date formatter.
let exampleDate = Date().addingTimeInterval(-15000)
let formatter = RelativeDateTimeFormatter()
formatter.context = .dynamic
let relativeDate = formatter.localizedString(for: exampleDate, relativeTo: Date())
let myString = String(format: NSLocalizedString("string_key", comment: ""), relativeDate)
Formatters have a context you can pass to tell the formatter where the string's going to be used.
https://developer.apple.com/documentation/foundation/formatter/context
And one of the cases is called dynamic which says that it's going to determine it at runtime. However, I can't see how that works or how I'm supposed to use the RelativeDateTimeFormatter with that.
I am trying to get some decimal number from the user inside a UITextfield in iOS Swift. Now the user can input number in his or her local number format as per the locale Settings in iOS. I want to convert this number which is in the user's mother tongue into English number. I searched a lot in this site (stackoverflow.com) and the majority of answers are for conversion from one locale (Chinese, or Arabic or Persian) into English but I want to convert number inputted into any locale format into English. How can I do this? So in nutshell, my question is whether the number being inputted in UITextField is in Hindi, Arabic, Persian, Chinese or whatsoever format as per the locale, I want to convert it into English Number format.
you can use NumberFormatter for that.
check below example:
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
let localNumberInStr = "૨૩"
guard let str = numberFormatter.number(from: localNumberInStr) else {return}
print(str) //"23"
When you check the devices locale you know which locale the user is using.
let locale = Locale.current
Just to improve upon Dharmesh answer, here is the answer wrapped in a helper method for use throughout the code. Obviously, it assumes that while getting user input via UITextField one has considered the number set in the user's locale settings.
func convertLocaleNumberIntoEnglish(localeNumberString: String?) -> String? {
guard let ulocaleNumberString = localeNumberString else {return nil}
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
let localNumberInStr = ulocaleNumberString
guard let number = numberFormatter.number(from: localNumberInStr) else {return nil}
let str = String(format:"%f", number.doubleValue)
return str
}
I am trying to find out which decimal separator is used by the decimal pad keyboard in iOS, so I can convert strings entered by the user to numbers with NumberFormatter and back.
As I want to pre-fill the text field with an existing value, I need to have a number formatter that uses the same decimal separator as the decimal pad keyboard.
The language that my device is set to (German, Germany) uses a comma as the decimal separator. I have configured iOS to have the German keyboard as the primary and active keyboard and English (US, QWERTY) as a secondary keyboard.
The app that I am working on only has a base localization, which is English. In the scheme settings, region and language are set to system default.
If I run my app, the decimal separator used by the decimal pad keyboard is ".", which is the decimal separator used by the en-US keyboard, but not the de-DE keyboard. The normal alphabetic keyboard shows the German keyboard layout.
If I remove the en-US keyboard on the iOS device, the decimal separator changes to ",".
How can I reliably find out, which decimal separator is used by the decimal pad keyboard?
None of the solutions that I have tried so far work:
Using the preset decimalSeparator of NumberFormatter always gives ",".
Using Locale.current.decimalSeparator always returns "," as well.
Using textField.textInputMode?.primaryLanguage to figure out the locale always returns de-DE.
Using Bundle.main.preferredLocalizations to figure out the localization used by the app always returns en.
This is how the number formatter is configured:
let numberFormatter = NumberFormatter()
numberFormatter.minimumIntegerDigits = 1
numberFormatter.minimumFractionDigits = 0
numberFormatter.maximumFractionDigits = 2
Edit: It seems to be possible to determine the locale used by the decimal pad by finding matches between the active text input modes and app localizations:
let inputLocales = UITextInputMode.activeInputModes.compactMap {$0.primaryLanguage}.map(Locale.init(identifier:))
let localizations = Bundle.main.preferredLocalizations.map(Locale.init(identifier:))
let locale = inputLocales.flatMap { l in localizations.map {(l, $0)}}
.filter { preferredLanguage, preferredLocalization in
if preferredLocalization.regionCode == nil || preferredLanguage.regionCode == nil {
return preferredLanguage.languageCode == preferredLocalization.languageCode
} else {
return preferredLanguage == preferredLocalization
}
}
.first?.0
?? Locale.current
numberFormatter.locale = locale
However this solution has several disadvantages:
I do not know whether UIKit selects the decimal separator exactly this way. The behavior may be different for some languages
It has to be computed every time a number will be formatted, as the user may change keyboards while the app is running.
My experience is that when you only support English localization, the decimal separator in the decimal keyboard type, will always be .. So you need to force en_US locale in the NumberFormatter when parsing a number from a string.
Here is a code snippet which tries to parse first using en_US, then tries to parse using Locale.current.
func parseNumber(_ text:String) -> Double? {
// since we only support english localization, keyboard always show '.' as decimal separator,
// hence we need to force en_US locale
let fmtUS = NumberFormatter()
fmtUS.locale = Locale(identifier: "en_US")
if let number = fmtUS.number(from: text)?.doubleValue {
print("parsed using \(fmtUS.locale)")
return number
}
let fmtCurrent = NumberFormatter()
fmtCurrent.locale = Locale.current
if let number = fmtCurrent.number(from: text)?.doubleValue {
print("parsed using \(fmtCurrent.locale)")
return number
}
print("can't parse number")
return nil
}
I would appreciate if Apple would add the Locale.current decimal separator as the decimal separator for decimal keyboard types, or else we need to add localization for all locales in order to get this right.
I'm building a side project to play around with iOS development and decided it would be a messaging app. I'm adding timestamps to the messages below the message body, but still within the bubble, as I like this look better than outside of the bubble. iOS automatically formats these numbers for inclusion in calendar. Is there a way to escape this formatting for JUST those numbers? I'd like to keep it for when users enter times and dates, as that's really useful.
Below is the block that's adding the message body, as well as a screenshot of what I'm referring to.
override func didPressSendButton(button: UIButton!, withMessageText text: String!, senderId: String!, senderDisplayName: String!, date: NSDate!) {
JSQSystemSoundPlayer.jsq_playMessageSentSound()
let date = NSDate()
let calendar = NSCalendar.currentCalendar()
let components = calendar.components(.CalendarUnitHour | .CalendarUnitMinute, fromDate: date)
let hour = components.hour
var minutes = components.minute
if minutes < 10 {
var minutes = String(minutes)
minutes = String(format: "%02d", minutes)
}
var newText = text + "\n\n \(hour):\(minutes)"
var newMessage = JSQMessage(senderId: senderId, displayName: senderDisplayName, text: newText);
messages += [newMessage]
self.finishSendingMessage()
}
In the JSQMessagesViewController where the cell is getting created, the data detector type is set to all.
Line 539 of JSQMessagesViewController.m
cell.textView.dataDetectorTypes = UIDataDetectorTypeAll;
cell.backgroundColor = [UIColor clearColor];
cell.layer.rasterizationScale = [UIScreen mainScreen].scale;
cell.layer.shouldRasterize = YES;
return cell;
You can just set it to UIDataDetectorTypeNone.
You have the dataDetector enabled, so it's detecting URLs, phone numbers, dates, and so on, and turning them into links.
Disable all or specific data detection, depending on your needs.
In your specific case, it sounds like you'd want to disable the UIDataDetectorTypeCalendarEvent data detection.
Update:
Here's a possible answer. Format the text so it doesn't appear to be a time. It may be possible, for example, to use a unicode character for the newText colon which the data detector won't catch as a time. Another option is to use a zero-width space to separate the hours with an 'invisible' character.
var newText = text + "\n\n \(hour)\u{200b}:\(minutes)" // zero-width space
Update 2:
I downloaded JSQMessage and tried this in the sample code. A zero-width space before the colon does appear to work to avoid the time from being detected.
[[JSQMessage alloc] initWithSenderId:kJSQDemoAvatarIdSquires
senderDisplayName:kJSQDemoAvatarDisplayNameSquires
date:[NSDate distantPast]
text:#"It even has data detectors. You can call me tonight. My cell number is 123-456-7890. My website is www.hexedbits.com.\n\n9\u200b:41"],
In Swift 3: To disable all detections
cell.textView?.dataDetectorTypes = []