How to localize text based on criterion other than language - ios

I have an application which will be marketed in different European countries. We've gone through the process of localizing the application so that its strings are maintained in the language-specific .lproj files in the Settings.bundle. This all works fine. The problem is that there are some strings which don't key off language, but off the country where the app is run. For example, there are strings which differ between the Austrian version of the app and the German version of the app, even though both these countries speak German. When it's run for the first time, the app asks the user which country it's running in.
Is there a way in which I can maintain these country-specific strings in a resource file, and have the resource file used at run time be decided by a user setting, in this case the country where the app is running, rather than the device language?
Thanks,
Peter Hornby

Define two bundles on a singleton, fallback and preferred...
#import <Foundation/Foundation.h>
#interface Localization : NSObject
#property (nonatomic, retain) NSString* fallbackCountry;
#property (nonatomic, retain) NSString* preferredCountry;
#property (nonatomic, retain) NSDictionary* fallbackCountryBundle;
#property (nonatomic, retain) NSDictionary* preferredCountryBundle;
+(Localization *)sharedInstance;
- (NSString*) countryStringForKey:(NSString*)key;
#end
#import "Localization.h"
#implementation Localization
#synthesize fallbackCountryBundle, preferredCountryBundle;
#synthesize fallbackCountry, preferredCountry;
+(Localization *)sharedInstance
{
static dispatch_once_t pred;
static Localization *shared = nil;
dispatch_once(&pred, ^{
shared = [[Localization alloc] init];
[shared setFallbackCountry:#"country-ES"];
NSLocale *locale = [NSLocale currentLocale];
NSString *countryCode = [locale objectForKey:NSLocaleCountryCode];
[shared setPreferredCountry:[NSString stringWithFormat:#"country-%#",countryCode]];
});
return shared;
}
-(void) setFallbackCountry:(NSString*)country
{
NSString *bundlePath = [[NSBundle mainBundle] pathForResource:country ofType:#"strings"];
self.fallbackCountryBundle = [NSDictionary dictionaryWithContentsOfFile:bundlePath];
trace(#"Fallback: %# %#",[bundlePath lastPathComponent], self.fallbackCountryBundle);
}
-(void) setPreferredCountry:(NSString*)country
{
NSString *bundlePath = [[NSBundle mainBundle] pathForResource:country ofType:#"strings"];
self.preferredCountryBundle = [NSDictionary dictionaryWithContentsOfFile:bundlePath];
BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:bundlePath isDirectory:nil];
if (!exists) warn(#"%#.strings %#", country, exists ? #"FOUND" : #"NOT FOUND");
trace(#"Preferred: %# %#",[bundlePath lastPathComponent], self.preferredCountryBundle);
}
- (NSString*) countryStringForKey:(NSString*)key
{
NSString* result = nil;
if (preferredCountryBundle!=nil) result = [preferredCountryBundle objectForKey:key];
if (result == nil) result = [fallbackCountryBundle objectForKey:key];
if (result == nil) result = key;
return result;
}
#end
Then call it from a macro function
#define countryString(key) [[Localization sharedInstance]countryStringForKey:key];
Write a default file for ES, and one file per supported language. eg:
/*
country-ES.strings
*/
"hello" = "hello";
And just get the value for the key:
countryString(#"hello");

Related

localizable.strings not working with Arabic string

I created a Localizable.strings file in XCode and then 2 languages in it.(english + Arabic )
I filled up these files with the language translations, but the just show translation in english, when I start with Arabic the key appear!
in my code :
NSLocalizedString("title", comment: "")
Localizable.strings(english)
"title" = "Error" ;
Localizable.strings(Arabic)
"title" = "خطأ" ;
I careated sample one and tried in Objective C.I got it.
I set "title" = "خطأ" in Arabic Localization files
"title" = "عنوان";
Now I have to change English to Arabic.
First I set the design in storyboard
Then Click Project.Choose Localization in Info
If you click the +(Below Localization) it shows the pop up view
Now choose Arabic.When click Arabic it shows window.You should click finish.
We need to create the string file for the localization now.I set string file name as LocalizationArabic
Once you create the String file it looks like below.
Then click File Inspector when pressing LocalizationArabic string file.Now click the Localization.It shows Empty Check box Arabic and English like below.
Here we must check the check box.Also when we check the check box the LocalizationArabic folder creates with three string files like below
Then I entered the language which I want to translate from English to Arabic in string file.
Finally I created the Header file for the Localization Language
The Header file name is LanguageHeader.It looks like below.
Now the code part starts here
First the Localization class of NSObject class
Localization.h
#import <Foundation/Foundation.h>
#import "LanguageHeader.h"
#interface Localization : NSObject
+(Localization *)sharedInstance;
+(NSString*) strSelectLanguage:(int)curLang;
+(NSString*) languageSelectedStringForKey:(NSString*) key;
#end
Localization.m
#import "Localization.h"
int currentLanguage,selectedrow;
#implementation Localization
+(Localization *)sharedInstance
{
static Localization *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[Localization alloc] init];
});
return sharedInstance;
}
+(NSString*) strSelectLanguage:(int)curLang{
if(curLang==ARABIC){
[[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObjects:#"ar", nil]forKey:#"AppleLanguages"];
}
else{
[[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObjects:#"en", nil]forKey:#"AppleLanguages"];
}
[[NSUserDefaults standardUserDefaults] synchronize];
currentLanguage=curLang;
NSString *strLangSelect = [[[NSUserDefaults standardUserDefaults]objectForKey:#"AppleLanguages"] objectAtIndex:0];
return strLangSelect;
}
+(NSString*) languageSelectedStringForKey:(NSString*) key
{
NSString *path;
NSString *strSelectedLanguage = [[[NSUserDefaults standardUserDefaults]objectForKey:#"AppleLanguages"] objectAtIndex:0];
//When we check with iPhone,iPad device it shows "en-US".So we need to change it to "en"
strSelectedLanguage = [strSelectedLanguage stringByReplacingOccurrencesOfString:#"en-US" withString:#"en"];
if([strSelectedLanguage isEqualToString:[NSString stringWithFormat: #"en"]]){
currentLanguage=ENGLISH;
selectedrow=ENGLISH;
path = [[NSBundle mainBundle] pathForResource:#"en" ofType:#"lproj"];
}
else{
currentLanguage=ARABIC;
selectedrow=ARABIC;
path = [[NSBundle mainBundle] pathForResource:#"ar" ofType:#"lproj"];
}
NSBundle* languageBundle = [NSBundle bundleWithPath:path];
NSString* str=[languageBundle localizedStringForKey:key value:#"" table:#"LocalizationArabic"];
return str;
}
#end
Then ViewController.h
#import <UIKit/UIKit.h>
#import "Localization.h"
#interface ViewController : UIViewController{
Localization *localization;
}
#property (strong, nonatomic) IBOutlet UILabel *lblTitle;
- (IBAction)actionChangeLanguageToArabic:(id)sender;
#end
ViewController.m
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
#synthesize lblTitle;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
localization = [Localization sharedInstance];
lblTitle.text = [Localization languageSelectedStringForKey:#"title"];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)actionChangeLanguageToArabic:(id)sender {
[Localization strSelectLanguage:ARABIC];
lblTitle.text = [Localization languageSelectedStringForKey:#"title"];
}
#end
Above code works perfectly.
Output Screen shot are below
When run the app first
After clicking the button

Xcode 6: Why this code doesn't compile now?

My code was compiling and running great until I upgraded to Xcode 6.
Definition shows a Warning : Auto property synthesis will not synthesize property 'hash' because it is 'readwrite' but it will be synthesized 'readonly' via another property
#property (nonatomic, strong) NSString *hash; // (get/compute) hash code of the place (master hash of images)
Implementation shows error whenever I access to _hash: Use of undeclared identifier '_hash'
-(NSString *)hash {
if (_hash) return _hash;
// If place id, take it as the hash code
NSString *poiID = self.info[#"id"];
if (poiID) {
_hash = [NSString stringWithFormat:#"id-%lu",(unsigned long)[self.address hash]];
}
else if (CLLocationCoordinate2DIsValid(self.location.coordinate)) {
NSString *seed = [NSString stringWithFormat:#"%f,%f", self.location.coordinate.latitude, self.location.coordinate.longitude];
_hash = [NSString stringWithFormat:#"location-%lu",(unsigned long)[seed hash]];
}
else if (self.address) {
NSString *seed = self.address;
_hash = [NSString stringWithFormat:#"address-%lu",(unsigned long)[seed hash]];
}
else {
_hash = #"POI-unknownIDLocationOrAddress";
}
return _hash;
}
It doesn't compile because hash is already part of NSObject:
See:
https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Protocols/NSObject_Protocol/index.html#//apple_ref/occ/intfm/NSObject/hash
You need to add the following line to auto generate the setter method:
#synthesize hash = _hash;
If you don't want a setter method and only want a read-only property:
#property (nonatomic, strong, readonly) NSString *hash;

Adding Custom Objects to NSMutableArray

I mainly program in Java and can't understand why this isn't working. I'm trying to create a temporary object "Judge" in my for loop. I then want to add that object to an NSMutableArray so in the end I have an array filled with different Judge objects. After the for loop I run through all the objects in the Array and they're all the last Judge Object.
The NSLog shows that "JudgeTemp" object is being assigned the right values while in the for loop. My guess is that it's not creating a new object called JudgeTemp every time but referencing the old already created JudgeTemp.
NSMutableArray *Judges = [NSMutableArray arrayWithCapacity:30];
for (int i=0; i<[courtinfoarray count]; i++) {
Judge1= [[courtinfoarray objectAtIndex:i] componentsSeparatedByString:#"|"];
Judge *JudgeTemp=[[Judge alloc]init];
[JudgeTemp setName:[Judge1 objectAtIndex:0] picture:[Judge1 objectAtIndex:1] courtroom:[Judge1 objectAtIndex:2] phone:[Judge1 objectAtIndex:3] undergrad:[Judge1 objectAtIndex:4] lawschool:[Judge1 objectAtIndex:5] opdasa:[Judge1 objectAtIndex:6] career:[Judge1 objectAtIndex:7] judgecode:[Judge1 objectAtIndex:8]];
NSLog(#"%#",[JudgeTemp getName]);
[Judges addObject:JudgeTemp];
NSLog(#"%#",[[Judges objectAtIndex:i]getName]);
}
Judges Class
#implementation Judge
NSString *name;
NSString *picture;
NSString *courtroom;
NSString *phone;
NSString *undergrad;
NSString *lawschool;
NSString *opdasa;
NSString *career;
NSString *judgecommentcode;
-(void) setName:(NSString *)n picture:(NSString *) p courtroom:(NSString *)c phone:(NSString *)ph undergrad: (NSString *) u lawschool: (NSString *)l opdasa: (NSString *) o career: (NSString *)ca judgecode: (NSString *)jcode{
name = n;
picture = p;
courtroom = c;
phone = ph;
undergrad = u;
lawschool = l;
opdasa = o;
career = ca;
judgecommentcode = jcode;
}
-(NSString*) getName{
return name;
}
The problem is with your Judge class. When you define variables directly in your #implementation they have global scope and are not instance variables. What you need to do is put those variable declarations in your #interface instead:
#interface Judge : NSObject {
NSString *name;
NSString *picture;
NSString *courtroom;
NSString *phone;
NSString *undergrad;
NSString *lawschool;
NSString *opdasa;
NSString *career;
NSString *judgecommentcode;
}
// ...
#end
Edit: Apparently you can declare them in your #implementation, you just have to wrap them in { }. See: Instance variables declared in ObjC implementation file

Create a plist file in iOS error

I want to write the method which should create for writing a plist file. I got the example code in the Web but can not understand what is wrong with it. First of all, when I try to call this method - I get a message in log:
2013-03-28 15:33:47.953 ECom[6680:c07] Property list invalid for format: 100 (property lists cannot contain NULL)
2013-03-28 15:33:47.954 ECom[6680:c07] An error has occures <ECOMDataController: 0x714e0d0>
Than why does this line return (null)?
data = [NSPropertyListSerialization dataWithPropertyList:plistData format:NSPropertyListXMLFormat_v1_0 options:nil error:&err];
and the last question - how to remove the warning message for the same line?
Incompatible pointer to integer conversion sending 'void *' to parameter of type 'NSPropertyListWriteOptions' (aka 'insigned int')
h-file
#import <Foundation/Foundation.h>
#interface ECOMDataController : NSObject
{
CFStringRef trees[3];
CFArrayRef treeArray;
CFDataRef xmlValues;
BOOL fileStatus;
CFURLRef fileURL;
SInt32 errNbr;
CFPropertyListRef plist;
CFStringRef errStr;
}
#property(nonatomic, retain) NSMutableDictionary * rootElement;
#property(nonatomic, retain) NSMutableDictionary * continentElement;
#property(nonatomic, strong) NSString * name;
#property(nonatomic, strong) NSString * country;
#property(nonatomic, strong) NSArray * elementList;
#property(nonatomic, strong) id plistData;
#property(nonatomic, strong) NSString * plistPath;
#property(nonatomic, strong) NSData * data;
#property(nonatomic, strong) id filePathObj;
-(void)CreateAppPlist;
#end
m-file
#import "ECOMDataController.h"
#implementation ECOMDataController
#synthesize rootElement, continentElement, country, name, elementList, plistData, data, plistPath;
- (void)CreateAppPlist {
// Get path of data.plist file to be created
plistPath = [[NSBundle mainBundle] pathForResource:#"data" ofType:#"plist"];
// Create the data structure
rootElement = [NSMutableDictionary dictionaryWithCapacity:3];
NSError *err;
name = #"North America";
country = #"United States";
continentElement = [NSMutableDictionary dictionaryWithObjects:[NSArray arrayWithObjects:name, country, nil] forKeys:[NSArray arrayWithObjects:#"Name", #"Country", nil]];
[rootElement setObject:continentElement forKey:#""];
//Create plist file and serialize XML
data = [NSPropertyListSerialization dataWithPropertyList:plistData format:NSPropertyListXMLFormat_v1_0 options:nil error:&err];
if(data)
{
[data writeToFile:plistPath atomically:YES];
} else {
NSLog(#"An error has occures %#", err);
}
NSLog(#"%# %# %#", plistPath, rootElement, data);
}
#end
It seems that you are serializing the wrong element, replace plistData by rootElement (and nil by 0) in
data = [NSPropertyListSerialization dataWithPropertyList:plistData format:NSPropertyListXMLFormat_v1_0 options:nil error:&err];

In Objective-C, How Can I Make a Global Configuration That Is Accessible to All?

I am new to Objective-C and iOS development and I'm not sure if I'm doing it right. Anyway, basically I have a file Configs.plist which, for now has two sets of Keys:Value (Customer:Generic and Short_Code:Default). I want these data to be easily accessible to all classes so I created these:
Configs.h
extern NSString * const CUSTOMER;
extern NSString * const SHORT_CODE;
#interface Configs
+ (void)initialize;
+ (NSDictionary *)getConfigs;
#end
Configs.m
#import "Configs.h"
NSString * const CUSTOMER = #"Customer";
NSString * const SHORT_CODE = #"Short_Code";
static NSDictionary *myConfigs;
#implementation Configs
+ (void)initialize{
if(myConfigs == nil){
NSString *path = [[NSBundle mainBundle] pathForResource:#"Configs" ofType:#"plist"];
settings = [[NSDictionary alloc] initWithContentsOfFile:path];
}
}
+ (NSDictionary *)getConfigs{
return settings;
}
#end
And on a the test file Test.m:
NSLog(#"Customer: %#", [[Configs getConfigs] objectForKey:CUSTOMER]);
NSLog(#"Short Code: %#", [[Configs getConfigs] objectForKey:SHORT_CODE]);
The thing is, this approach works but I want to know if there are better ways to do this.
I think this is good as long as your configuration does not change during execution. If it does, you're better off with the singleton exposing your config as properties, so you would be able to do something like this:
[[Config shared] customer];
[[Config shared] setShortCode:#"CODE"];
You could still init the config from the plist, or implement coding protocol to store it in the NSUserDefaults.

Resources