How should I localise with multiple parameters? - ios

Let's say I have the following string:
[NSString stringWithFormat:#"Booked for %# at %#", colleagueName, time];
And I realise I've forgotten to localise that string, so I replace it:
[NSString stringWithFormat:NSLocalizedString(#"bookings.bookedFor", "Booked for user at time"), colleagueName, time];
Now when doing translations, I find that the language X needs the parameters the other way round; something closer to:
<time> for booking of <colleague> is done.
What is the best way to address the fact that now I need the second parameter of my formatted string to be time and the third to be colleagueName please?

As is often the case, my colleague found the solution almost as soon as I had asked on here! Apparently Objective-C has positional arguments
The positions are 1-indexed so %1$# refers to the first argument.
NSString *firstParam = #"1st";
NSString *secondParam = #"2nd";
NSLog(#"First %1$# Second: %2$#", firstParam, secondParam);
NSLog(#"Second %2$# First: %1$#", firstParam, secondParam);
This prints:
First 1st Second: 2nd
Second 2nd First: 1st

You can try like this:
NSString * language = [[NSLocale preferredLanguages] objectAtIndex:0];
if ([language isEqualToString:#"X"]) {//X is language code like "fr","de"
[NSString stringWithFormat:NSLocalizedString(#"bookings.bookedFor", "Booked for user at time"), time, colleagueName];
} else {
[NSString stringWithFormat:NSLocalizedString(#"bookings.bookedFor", "Booked for user at time"), colleagueName,time];
}

Related

Concatenating Two strings in Localization Objective-c

My Previous Code without Localization. It worked perfect.
case LOGIN_LOGOUT: ((Cell*)cell).lbl.text = [self isLoggedIn] ?
[NSString stringWithFormat:#"Logout %#", email]
:NSLocalizedString(#"Login", #"Message");
break;
But when I implement Localization in Logout the email will not show.
case LOGIN_LOGOUT: ((Cell*)cell).lbl.text = [self isLoggedIn] ?
[NSString stringWithFormat:NSLocalizedString(#"Logout", #"Message") ,"%#",
email] :NSLocalizedString(#"Login", #"Message");
break;
I know I am missing some basics in stringWithFormat but can anyone offer some guidance to me?
Let's assume, that you have .strings file and it contains entry named "Logout". You have:
[NSString stringWithFormat:NSLocalizedString(#"Logout", #"Message") ,"%#", email]
here you try to load format string via NSLocalizedString and use it with NSString. That means that you have to put correct format string into your .strings file, so, if currently you have:
"Logout" = "Logout";
In order to make it just like before localization, you need:
"Logout" = "Logout %#";
If you don't have a .strings file or don't have entry named "Logout", NSLocalizedString will return the key, i.e.
NSLocalizedString(#"key", #"comment") // returns "key"
That means, that your NSLocalizedString(#"Logout", #"Message") may return "Logout" if NSLocalizedString can't find correct entry in your .strings file.
There are more things that may go wrong, if you want some deeper insides on that, I have written great article on the whole topic: Understanding iOS internationalization.
Also I'd suggest to use +localizedStringWithFormat: instead of just plain +stringWithFormat:, because the former uses current locale.
You are looking up the localisation of "Logout". You are using that as a format string. That's not likely to work. Don't make statements that are too complex, it makes it impossible to debug.
I'd write
case LOGIN_LOGOUT: {
NSString* labelText;
if ([self isLoggedIn]) {
NSString* formatString = NSLocalizedString(#"Logout", #"Message");
labelText = [NSString stringWithFormat:formatString, "%#", email];
} else {
labelText = NSLocalizedString(#"Login", #"Message");
}
((Cell*)cell).lbl.text = labelText;
break;
}
And now you can actually debug that whole mess. The stringWithFormat parameters look very, very dodgy.

How to skip data passing into [NSString stringWithFormat:]

Consider below code:
int count = 1;
NSString* format = count == 1 ? #"One %2$#" : #"%1$d %2$#s";
NSString* result = [NSString stringWithFormat: format, count, #"Bread"];
What count is not 1, the result is valid:
2 Breads
4 Breads
However the count is 1, then it causes EXC_BAD_ACCESS
NSLog(#"%#", [NSString stringWithFormat:#"One %2$#", 1, #"Bread"]);
Xcode compiler complains with upper code:
Data argument not used by format string
I know the reason for this error.
However My approach(Dynamic format that may skips some data) is also useful what if it works.
Is there any workaround for this?
[NSString stringWithFormat:] does not support positional parameters.
That looks like a bug in [NSString stringWithFormat:].
One workaround (hack) would be to use plain printf functions and convert the result to
NSString:
char *format = count == 1 ? "One %2$s" : "%1$d %2$ss";
char *tmp;
asprintf(&tmp, format, count, "Bread");
NSString *result = [NSString stringWithUTF8String:tmp];
free(tmp);
But the proper solution would be to create a "Localizable.strings" file with
language plural rules, as described in
"Handling Noun Plurals and Units of Measurement"
in the Internationalization and Localization Guide.
See also "String Localization"
for documentation and examples.
Replace :-
NSLog(#"%#", [NSString stringWithFormat:#"One %2$#", 1, #"Bread"]);
with Modified below:-
NSLog(#"%#", [NSString stringWithFormat:#"%#,%d,%#",#"One %2$#", 1, #"Bread"]);

if statements and stringWithFormat

I need to know if there is a way to use if statements to display certain nsstrings, depending on whether or not that NSString contains any data.
I have an nsstringcalled visitorInfo.
The string uses data from other strings (i.e. which operating system the user is running) and displays that info. Here is an example of what I'm talking about:
NSString *visitorInfo = [NSString stringWithFormat:#"INFO\n\nVisitor Location\n%#\n\nVisitor Blood Type\n\%#", _visitor.location, _visitor.bloodType];
And it would display like this:
INFO
Location
Miami, FL
Blood Type
O positive
However, I have several pieces of data that only load if the user chooses to do so. i.e their email address.
This section of code below would do what I want, but my visitorInfo string contains tons of different strings, and if I use this code below, then it won't load any of them if the user chooses not to submit his blood type.
if ([self.visitor.bloodType length] > 0) {
NSString *visitorInfo = [NSString stringWithFormat:#"INFO\n\nVisitor Location\n%#\n\nVisitor Blood Type\n\%#", _visitor.location, _visitor.bloodType];
}
So basically if their is data stored in bloodType then i went that code to run, but if there isn't any data I only want it to skip over bloodType, and finish displaying the rest of the data.
Let me know if you have any more questions
Additional details. I'm using an NSString for a specific reason, which is why I'm not using a dictionary.
Just build up the string as needed using NSMutableString:
NSMutableString *visitorInfo = [NSMutableString stringWithFormat:#"INFO\n\nVisitor Location\n%#, _visitor.location];
if ([self.visitor.bloodType length] > 0) {
[visitorInfo appendFormat:#"\n\nVisitor Blood Type\n\%#", _visitor.bloodType];
}
You can check if a string has any data in it by using the following
if([_visitor.location length]<1){
//This means there's no data and is a better way of checking, rather than isEqualToString:#"".
}else{
//there is some date here
}
** EDIT - (just re-reading your question, sorry this answer is dependant on _visitor.location being a string in the first place)*
I hope this helps
Try this -
NSString *str = #"INFO";
if (_visitor.location) {
str = [NSString stringWithFormat:#"\n\nVisitor Location\n%#",_visitor.location];
}
if (_visitor.bloodType) {
str = [NSString stringWithFormat:#"\n\nVisitor Blood Type\n\%#",_visitor.bloodType];
}

How do I use NSRange with this NSString?

I have the following NSString:
productID = #"com.sortitapps.themes.pink.book";
At the end, "book" can be anything.... "music", "movies", "games", etc.
I need to find the third period after the word pink so I can replace that last "book" word with something else. How do I do this with NSRange? Basically I need this:
partialID = #"com.sortitapps.themes.pink.";
You can try a backward search for the dot and use the result to get the desired range:
NSString *str = #"com.sortitapps.themes.pink.book";
NSUInteger dot = [str rangeOfString:#"." options:NSBackwardsSearch].location;
NSString *newStr =
[str stringByReplacingCharactersInRange:NSMakeRange(dot+1, [str length]-dot-1)
withString:#"something_else"];
You can use -[NSString componentsSeparatedByString:#"."] to split into components, create a new array with your desired values, then use [NSArray componentsJoinedByString:#"."] to join your modified array into a string again.
Well, although this isn't a generic solution for finding characters, in your particular case you can "cheat" and save code by doing this:
[productID stringByDeletingPathExtension];
Essentially, I'm treating the name as a filename and removing the last (and only the last) extension using the NSString method for this purpose.

xCode ios5 How to append to a localized string

I'm trying to figure how to append some parameters to a localized string?
The desired result (in a string) is "TITLE-xxxx ddmmyy".
TITLE is to be localized.
xxxx is a calculated number and ddmmyy is a fixed date format.
Thanks for any help!
Given TITLE, myDateFormatter which is a NSDateFormatter and number, which is an int:
NSString* myString = [NSString stringWithFormat:#"%#-%d %#", NSLocalizedString(TITLE, #"comment"), number, [myDateFormatter stringFromDate:date]];
Of course you can change number to an NSNumber or something if you wish so (change %d to %# then).

Resources