iOS Localization: Create strings-file for native language? - iphone

If you're an English programmer and write your code like...
NSLocalizedString(#"Hello", ...)
... do you create a en.lproj/Localizable.strings although there's the same string in there as in the NSLocalizedString macro?

Generally, no, you don't need to if you use the string as the key to NSLocalizedString. But you can then use the Localizable.strings file to replace strings in the UI without changing the code files.
Also, you don't have to use the actual string as the key to NSLocalizedString, you could use a set of generic identifiers for the purpose (like the comment parameter is intended to help with). Then you need the Localizable.strings file to fill in the true UI values.

The best tutorial on Localization I found is by Ray Wenderlich: How to localize an iphone app

First, the Apple docs answer your question in the Foundations Functions Reference for NSLocalizedString when it reads "The initial value for key in the strings file will be key."
Second, this question is ranked high in my searches, so I'd like to add 1 situation in which I do create a strings file in my native language/locale.
My native language/locale is English. I create a strings file for programmatically generated singular nouns that I sometimes need to pluralize. Example:
#interface XYZFeline : NSObject
...
#interface XYZCanine : NSObject
...
#implementation XYZAnimalController
NSDictionary *classToWord = #{ #"XYZFeline": #"cat", #"XYZCanine" : #"dog" };
- (NSString *)describeQuantityOfAnimal:(id)animal
{
NSString *typeWord = classToWord[NSStringFromClass(animal)];
NSString *sentence = [NSString stringWithFormat:#"%# %lu %#.",
NSLocalizedStringFromTable(#"You have", #"phrases.strings", nil),
animal.count,
[animal.count == 1
? NSLocalizedStringFromTable(typeWord, #"nouns.strings", nil)
: NSLocalizedStringFromTable(typeWord, #"nouns-plural.strings", nil),
];
return sentence;
}
The nouns.strings for English does not exist, but the English nouns-plural.strings file looks like this:
"dog" = "dogs";
"cat" = "cats";
Example output:
You have 3 dogs.
You have 1 cat.
This will allow you to get proper singular or plural nouns in your output without putting language rules in your code.
Note: I am aware that my coded sentence structure of subject, predicate, object and adjective-before-noun is limited to only some Indo-European languages, but that is a problem to solve in another question.

Related

Stringification in swift

Is there anyway to do stringification in swift? , since there is no preprocessor it seems kind of difficult.
What I'd like to do is something similar to the following code.
// log a variable name and its value
#define LOGV(V) NSLog(#"%s = %#", #V, V);
NSString *myString = #"this";
LOGV(myString); // prints out -> "myString = ???"
After looking at dump it seems that there is no way of reflecting a variable name.
let myString = "this"
dump(myString, name:"myString", indent: 0 maxDepth: 0, maxItems: 1)
I also wanted to do the same thing in Swift,
so I recently wrote debug-logging util which stringifies variables at runtime instead (reading entire code!)
You might find this interesting.
https://github.com/inamiy/DebugLog
There is no way to do this in Swift. If you ABSOLUTELY want to do this, its always possible to run the C preprocessor over your Swift source.

How to dynamically change a property that is being accessed

I'm programming a game that has 40 levels and I'm storing all my level data in a .plist. This works great but now that I'm trying to implement multiple levels, I'm looking for a way to call a property on a singleton based on level without manually coding something like a switch/case. I would like to do something like the following:
This is the code I use to pull my data but as you can see, I've hard coded the "dictionaryLevel1Data" property.
int capacity = [[sharedDataManager.dictionaryLevel1Data objectForKey:#"typeWarrior"] intValue];
I would like to set a variable and have the property of the singleton called based on that like this which I know doesn't work. Notice how I'm replacing "dictionaryLevel1Data"
NSString level = #"1";
int capacity = [[sharedDataManager.[NSString stringWithFormat:#"dictionaryLevel%#Data", level] objectForKey:#"typeWarrior"] intValue];
Any suggestions on how I could accomplish this would be greatly appreciated. I can always go and manually setup a switch/case for each level but with 40 levels and 50+ different object types per level it would be rough, it seems like there must be a better way. I hate hard coding values and like to simplify my code with easy updates to variables that run through generic classes and methods.
why don't you do like this;
use 2 level data map;
//sharedDataManager.levelData is a NSDictionary that contains lots of NSDictionarys
NSDictionary *levelData = [sharedDataManager.levelData objectForKey:[NSString stringWithFormat:#"dictionaryLevel%#Data", level]];
int capacity = [levelData objectForKey:#"typeWarrior"] intValue];
If you want do it this way, then you can use Key-Value-Coding, to get a property value from a string. The accepted answer in this post shows how to use it (very easy): Objective-C get a class property from string
For your task it would look something like this:
int capacity = [[[sharedDataManager valueForKey:[NSString stringWithFormat:#"dictionaryLevel%#Data", level]] objectForKey:#"typeWarrior"] intValue];
You can use :
NSString level = #"dictionaryLevel1Data"
SEL methodSelector = NSSelectorFromString(level);
if ([sharedDataManager conformsToSelector:#selector(methodSelector)]) {
NSDictionary *levelData = [sharedDataManager performSelector:methodSelector];
// Do whatever you need here
}
I assume all dictionaryLevelData are synthesized and have getters as their name.
(Note that I did not compile it but it should work)

Set Multiple Variables to Same Value

I have dozens of NSStrimgs that when the app loads I want to all be set to the same set. All of them. How can I do this without typing out every single one? Is there a shortcut method?
Thanks.
Also the problem is that Josh isn't specific enough about how he's using his dozens of strings... I think this would be better:
NSMutableArray *stringsArray = [NSMutableArray arrayWithCapacity:1];
NSString *tempStr = #"My unique string"; // Thanks Sven!
// Say you want a dozen strings
for (int i = 0; i < 12; i ++) {
[stringsArray addObject:tempStr];
}
// Now you can use them by accessing the array
[self doSomethingWithString:[stringsArray objectAtIndex:8]];
Instead of having dozens of strings that have the same value, could you make a single static global string and reference that? If you need to change it to separate values later, use instance variables that are initialized to the global string.
This sounds like your model is not very good at all. Since you want to initialize all of your strings to the same value they are obviously related and probably should be modeled as an array like iPhoneDevProf described. That makes other things a lot easier too, you can move other code that is repeated for every string into a loop.
If the value is known when you are compiling the code AND it is not going to change after subsequent application sessions then you can use a simple #define.
#define MY_DEFAULT_STRING #"THE DEFAULT STRING"
Now all you have to do is the following.
{
NSString *myString1 = MY_DEFAULT_STRING;
NSString *myString2 = MY_DEFAULT_STRING;
....
NSString *myStringN = MY_DEFAULT_STRING;
}
If all the strings are in the same code file, just put the define at the top. If the strings are in separate code files, then it could be put into your precompiled header. Having a constants file is usually better.
Using constant extern NSString would probably be more correct, but this is simple and easy to do.

Use of type 'id' in cocoa class

I want to implement a class which can be used by two classes of my project.
One is manipulating 'NewsRecord' objects.
One is manipulating 'GalleriesRecord' objects.
In another class, I may use one of the two objects so i do something like that :
// header class
id myNewsRecordOrGalleriesRecord;
// class.m
// NewsRecord and GalleriesRecord have both the title property
NSLog(myNewsRecordOrGalleriesRecord.title);
and i get :
error : request for member 'title' in something not a structure or union
any ideas :D ?
Thanks.
Gotye
How am I supposed to do it ?
You can't use dot syntax on id types because the compiler cannot know what x.foo means (the declared property may make the getter a different name, e.g. view.enabled -> [view isEnabled]).
Therefore, you need to use
[myNewsRecordOrGalleriesRecord title]
or
((NewsRecord*)myNewsRecordOrGalleriesRecord).title
If title and more stuffs are common properties of those two classes, you may want to declare a protocol.
#protocol Record
#property(retain,nonatomic) NSString* title;
...
#end
#interface NewsRecord : NSObject<Record> { ... }
...
#end
#interface GalleriesRecord : NSObject<Record> { ... }
...
#end
...
id<Record> myNewsRecordOrGalleriesRecord;
...
myNewsRecordOrGalleriesRecord.title; // fine, compiler knows the title property exists.
BTW, don't use NSLog(xxx);, which is prone to format-string attack and you can't be certain xxx is really an NSString. Use NSLog(#"%#", xxx); instead.
Try accessing the title of your record like [myNewsRecordOrGalleriesRecord title];
If you're going to be doing a lot of this type of thing (accessing common methods in two classes) you would probably benefit significantly from either creating an abstract superclass that both NewsRecord and GalleriesRecord can inherit from (if they'll be sharing a lot of code) or creating a protocol they both can adhere to (if they'll be sharing method names but not code.
The compiler is not happy since an id is actually a NSObject instance, which doesn't have a title property.
If your object is KVC compliant, you can use the valueForKey method:
NSLog( [myNewsRecordOrGalleriesRecord valueForKey:#"title"] );

objectForKey stringValue crashing my app?

I have a class that I use to setup objects in an array. In this class I have a custom "initWithDictionary", where I parse a JSON dictionary. However, as I am running into NSNull, this crashes my app. To get around this, I set up a class that handles exceptions, so when a string is NSNull, it's replace it with #"". or -1 for integers.
This is my NullExtensions class:
#interface NSNull (valueExtensions)
-(int)intValue;
-(NSString *)stringValue;
#end
#implementation NSNull (valueExtensions)
-(int)intValue {
return -1;
}
-(NSString*)stringValue {
return #"";
}
#end
However, in my initWithDictionary method, the following code crashes my app:
self.bookTitle = [[parsedDictionary objectForKey:#"book_title"] stringValue];
It doesn't work regardless of the object in the parsed dictionary being NSNull or containing a valid string. Only if I do the following (and the string is not null):
self.bookTitle = [parsedDictionary objectForKey:#"book_title"];
Is stringValue incorrect in this case? And if so, how do I use it properly in order to setup proper NSNull replacements?
Thx
You really really don't want to add a category to NSNull that adds such common methods. That will change the behavior of NSNull for all instances in the application, including ones created by the underlying frameworks solely for their private use.
If you need a value class that represents the notion of "value doesn't exist and therefore I'm going to return these default values instead", create a class or instance that represents exactly that.
As for why it crashes, I couldn't tell you without seeing the actual details of the crash.
And, yes, it really is THAT bad to add a category to a class that adds such a common method. All it takes is one bit of code in a plug-in or framework that does:
if ([fooMaybeNull respondsToSelector: #selector(intValue)] bar = [fooMaybeNull intValue];
Not terribly farfetched -- I have had to debug nasty crashers or misbehaviors due to exactly this kind of willy-nilly category addition.
If you are going to add methods to a class via categories, prefix your method names so as to isolate them from existing functionality. It is still fragile, but manageably so.
Instead of creating categories on NSNull, for which you would also have to add a similar category to NSString (that's why it crashes, because real strings do not respond to stringValue) - instead try creating a helper category on NSDictionary like "stringForKey" that uses the code Johan posted and returns an NSString, probably also should enforce all other types get mapped to empty strings as well.
The NSNull extensions you have written look ok to me but using a method like stringValue may be confusing since other classes like NSNumber use this.
Personally though, I think NSNull replacement in this instance is unnecessary. If you just made a quick test you can replace the NSNull where you need to. e.g.
id testObject = [parsedDictionary objectForKey:#"book_title"];
self.bookTitle = testObject==[NSNull null] ? #"" : testObject;
You are asking an NSString for its stringValue. No need to convert a string to a string.
Try this:
if (![[parsedDictionary objectForKey:#"book_title"] isKindOfClass:[NSNull class]]) {
self.bookTitle = [parsedDictionary objectForKey:#"book_title"];
} else {
self.bookTitle = #"";
}
Edit: You should not use the category on NSNull you created. You don't need it, nor should you want it. If the source for the dictionary inserts NSNull instances, go ahead and use my code above. Normally you would expect to simple have no value inserted for the key, at which time you can simple see if [parsedDictionary objectForKey:#"book_title"] returns anything.
Are you sure that the dictionary is returning [NSNull null]? By default, dictionaries return nil, not [NSNull null], when an value isn't found for a key.
However, in my initWithDictionary method, the following code crashes my app:
self.bookTitle = [[parsedDictionary objectForKey:#"book_title"] stringValue];
It doesn't work regardless of the object in the parsed dictionary being NSNull or containing a valid string.
That makes sense, since stringValue is not a valid method on NSString. It will work for NSValue and its subclasses, but not NSString.