Localizing Numbers in iOS - objective-c

Simply, I have a positive integer [9, 393, 3, 993], and I would like to localize it to a certain language [٩, ٣٩٣ ,٣ ,٩٣٩].
If I use NSNumberFormatter, it will localize the number according to the user's locale. However, I want to override that and choose any locale to translate the number to.
I tried the following, did not work:
// user locale is #"en"
NSNumberFormatter* formatter = [NSNumberFormatter new];
[formatter setNumberStyle:NSNumberFormatterNoStyle];
[formatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:#"ar"]];
[formatter setMinimumIntegerDigits:padding];
return [formatter stringFromNumber:#(num)];
The returned string is in English.
Please note that I have a very similar code snippet for NSDateFormatter, but it works as expected. The NSDateFormatter object respects the set locale.

It seems I came across a very special case where the locale of the app would just freak out.
I am changing the default locale of the app by using something like this:
[[NSUserDefaults standardUserDefaults] setObject:#[#"ar"] forKey:#"AppleLanguages"];
Then, I was trying to get the preferred language and create a locale object from it using:
NSString* langPrefix = [NSLocale preferredLanguages][0];
Finally, create a new NSLocale object from the returned object. When testing the code, I would change the language from within the app, then close the app through Xcode. I am assuming that the NSUserDefaults would not synchronize, but even if I called the synchronize method, it would still screw up.
Bottom line is, testing localization should be done by deploying the app, and after the device has been disconnected from Xcode, so the app would run through all the life-cycle stages properly.

Related

NSDateFormatter string is invalid

SITUATION
The app I'm maintaining has a rare but reoccurring issue parsing NSStrings into NSDates. I have been unable to replicate it locally, but our logs show that it is indeed happening for our users.
I found a lot of NSDateFormatter questions on StackOverflow, but none seem to be directly relevant so here I am.
WHAT'S HAPPENING
The error message on our logs is “2016-04-19T17:30:00-0400” is invalid. I used the getObjectValue:forString:range:error: function to grab the error description. That is not the only date that is failing; there are multiple ones.
The date format being used is yyyy-MM-dd'T'HH:mm:ssZZZZ.
If it helps, the project uses Mantle and the initial conversion that failed happened through the NSValueTransformer. The logging of the error is done separately from the NSValueTransformer.
WHAT I'VE TRIED
Tried a fallback solution where it attempted to parse the string once more into a date, but according to the logs the fallback hasn't had any effect
Tried parsing the failed strings in a separate test project...and they all work
Tried messing with timezone in the separate test project, but it doesn't seem to have any effect, especially since the timezone is already in the date string itself via ZZZZ. I looped through possible timezones via [NSTimeZone knownTimeZoneNames]
Tried messing with the NSLocale to no effect. I can't get it to fail in the separate test project. I looped through possible locales via [NSLocale availableLocaleIdentifiers]
Tried messing with the NSCalendar to no effect. I manually used the calendars provided at https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSCalendar_Class/#//apple_ref/doc/constant_group/Calendar_Identifiers
Tried seeing if it was a threading issue, but couldn't find any evidence that it's an issue. Given how the fallback solution is implemented though, it's very unlikely to be a threading issue.
CODE
Fallback code that doesn't work. Element is a Mantle object. rawWhenValue is a string.
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateFormat = kNSDateFormatterWhenJSONFormat; //yyyy-MM-dd'T'HH:mm:ssZZZZ
NSDate *when = [dateFormatter dateFromString:element.rawWhenValue];
element.when = when;
Code used to grab the NSDateFormatterError.
NSDate *dateFallback;
NSError *err;
NSRange range = NSMakeRange(0, element.rawWhenValue.length);
BOOL parsed = [dateFormatter getObjectValue:&dateFallback forString:element.rawWhenValue range:&range error:&err];
I'm at my wits end. Even a direction to look in would be appreciated. Many thanks!

UIDatePicker change default selected row

There is UIDatePicker in my app. I have set it's mode as "Time".
In it's default selected row current time showing. So how to change it?
I want to display 9:00 AM as default selected, does't matter what is current time.
I have tried with below code,
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"hh:mm a"];
NSDate *date = [formatter dateFromString:#"9:00 a"];
[myDatePicker setDate:date];
but, it's getting crashed with "Invalid parameter not satisfying: date"
Can anybody help me out here?
Thanks in advance!
There seems to be some kind of problems with setting up the properties in the interface builder but at this link you can find everything you need:
Default Date for UIDatePicker in iOS
The Mode of Date Picker is "Time".
Add two more property to get the default custom value.
Set Date property as "Custom"
In the below test box, provide a default value which the picker needs to display. (As Time property is selected for date picker, while providing default value - dates can be ignored)

NSLocale - invalid identifiers

If I am trying to initialize a NSLocale with a certain locale identifier, how can I tell if it is a legit identifier? No matter what identifier I pass in, even a garbage one, NSLocale still gets initialized to something.
For example, if you pass in "ar_NO", a locale with language arabic and a (invalid) country code of NO, the NSLocale object that I get seems legitimate. For example, if I call -[NSCalendar firstWeekday] from a calendar with that locale, it will return Monday. My question, is where is that coming from? Does iOS fall back to another locale in case it cant use the given identifier? I would think it would fall back to the base "ar", but "ar" firstWeekday is Saturday, so that is not the case.
Note: I know that the identifiers are specificed by ISO BCP 47 specifications and I know about the [NSLocale availableLocaleIdentifiers] function, but that doesnt really help me because if I use an identifier not in that list, I still get a NSLocale.
Anyone have any thoughts? Thanks in advance
I think it will fall back to local default settings if given NSLocale is invalid. Try changing local device settings and see what happens! After settings your invalid locale, print [NSLocale currentLocale]; and see what it returns.

NSDateFormatter crash. How come?

NSDateFormatter *timeFormatter = [[[NSDateFormatter alloc] init] autorelease];
[timeFormatter setDateFormat:#"h:mm a"];
NSString *fullTime = [timeFormatter stringFromDate:someDateHere];
NSArray *timeParts = [fullTime componentsSeparatedByString:#" "];
timeLabel.text = [timeParts objectAtIndex:0];
ampmLabel.text = [timeParts objectAtIndex:1];
The LAST line crashes with
NSRangeException*** -[__NSArrayI objectAtIndex:]: index 1 beyond bounds [0 .. 0]
How is this possible? There is a nil check on the date that returns just before this code.
From the Data Formatting Guide documentation (section Date Formatters > Use Format Strings to Specify Custom Formats > Fixed Formats):
Although in principle a format string specifies a fixed format, by default NSDateFormater still takes the user’s preferences (including the locale setting) into account.
...
In iOS, the user can override the default AM/PM versus 24-hour time setting. This may cause NSDateFormatter to rewrite the format string you set.
In other words, on an iOS device that's set for 24-hour time setting, you won't get "6:02 PM", you'll get "18:02", even though you specified "h:mm a". So when you separate that by spaces, you get back a single value, "18:02", not two values.
There's a caveat in the documentation for NSDateFormatter that says:
Note that although setting a format string (setDateFormat:) in principle specifies an exact format, in practice it may nevertheless also be overridden by a user’s preferences—see Data Formatting Guide for more details.
Could this apply in your case to produce a string without any spaces in it? (That would lead to a length 1 array when split by spaces, giving the exception you see in the place you see it.) Check this by logging the formatted date or attaching a debugger.
Note that the end of the page on date formats does recommend using plain old strftime_l when dealing with unlocalized dates/times. That might be more suitable for you. (Also, you want an AM/PM indicator in data that's bound for a computer? Seriously? The 24-hour clock is way easier to work with usually…)

Objective-C: format numbers to ordinals: 1, 2, 3, .. to 1st, 2nd, 3rd

In Objective C, is there any way to format an integer to ordinals
1 => "1st", 2 => "2nd" etc... that works for any language?
So if the user is French he will see "1er", "2ieme" etc..
Thanks a lot!
Edit:
This is for an iOs app
Have you taken a look at TTTOrdinalNumberFormatter which is in FormatterKit? It works great, and I'm pretty sure it's exactly what you're looking for.
Here's an example taken from the kit:
TTTOrdinalNumberFormatter *ordinalNumberFormatter = [[TTTOrdinalNumberFormatter alloc] init];
[ordinalNumberFormatter setLocale:[NSLocale currentLocale]];
[ordinalNumberFormatter setGrammaticalGender:TTTOrdinalNumberFormatterMaleGender];
NSNumber *number = [NSNumber numberWithInteger:2];
NSLog(#"%#", [NSString stringWithFormat:NSLocalizedString(#"You came in %# place!", nil), [ordinalNumberFormatter stringFromNumber:number]]);
Assuming you've provided localized strings for "You came in %# place!", the output would be:
* English: "You came in 2nd place!"
* French: "Vous êtes venu à la 2eme place!"
* Spanish: "Usted llegó en 2.o lugar!"
The solution is immediately available from NSNumberFormatter:
- (NSString *)getOrdinalStringFromInteger:(NSInteger)integer
{
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
[formatter setLocale:[NSLocale currentLocale]];
[formatter setNumberStyle:NSNumberFormatterOrdinalStyle];
return [formatter stringFromNumber:[NSNumber numberWithInteger:integer]];
}
You could use ICU, which includes a way of doing what you describe:
http://icu-project.org/apiref/icu4c/classRuleBasedNumberFormat.html
You don't say what context you're using Objective-C in, but if you're writing for Cocoa, ICU is actually present. However, reaching down to talk to it directly can be a bit tricky.
[edited to link to someone who actually seems to have figured out how to build ICU and link it]
How to build ICU so I can use it in an iPhone app?
You need a rule set for each language you want to support. Any language is asking too much: they are all wildly different. First, create a rule set class which holds the regular and the exception cases for a given language. That class needs a single method that takes a number and returns a string suffix (or the number plus the suffix.) Create rule set instances (statically) for each language you care about.
Then create a category on NSNumber that returns a suffix pulled from the appropriate rule set for whatever language the user needs (system locale, or some choice they make, or case by case.)
Each language has different rules, of course. For example, English is relatively complicated:
1st,
2nd,
3rd,
4th,
5th,
... 20th
and then it starts again at st, nd, rd, th... Unit 1s, 2s, 3s and 4s are always special cases. Zero is 'th' (zeroth, hundredth, millionth etc.)
French is different. 1er, then it's x ième all the way up. (These are usually abbreviated to just 're' and 'e', making French quite easy.)
Japanese gets very odd. Cardinal 1, 2, 3, 4: (ichi, ni, san, yon) becomes tsuichi, futsuka, mikka and yokka. Those aren't suffixes though: the numbers are named differently when they're used as ordinals. Luckily, because that's incredibly confusing, you can just stick a kanji 'kai' character (which looks like a box in box) after the number and everyone knows what you mean.
Swift:
func getOrdinalDegreeValue() -> String? {
let formatter = NumberFormatter()
formatter.locale = Locale.current
formatter.numberStyle = .ordinal
return formatter.string(from: NSNumber(value: 1)) // Number
}
1st