Since I create iOS Apps I use the following code to translate / localize my apps:
NSString *language = [[NSLocale preferredLanguages] objectAtIndex:0];
if ([language isEqualToString:#"de"]) {
// localized language
}
else { //base language
}
But since the update to iOS 9 this code does not work anymore.
All my apps are now in English.
Neither the apps I already have in the app store, nor the apps I run in the Simulator are localized anymore.
It would be great if you could tell me how I have to translate my code programmatically in iOS 9.
I could solve the problem.
If I use the following code the localization works under iOS 9.
[[NSBundle mainBundle] preferredLocalizations];
NSString *language = [[[NSBundle mainBundle] preferredLocalizations] objectAtIndex:0];
if ([language isEqualToString:#"de"]){
// localization
}
else {
//base language
}
I hope that will help some of you, too.
I would suggest to use a category class for Localizing strings for specified language which automatically detect the current language and localize the same.
Create a category class with Name
RunTimeLanguage
.h file
#import <Foundation/Foundation.h>
#interface NSBundle (RunTimeLanguage)
#define NSLocalizeString(key, comment) [[NSBundle mainBundle] runTimeLocalizedStringForKey:(key) value:#"" table:nil]
- (NSString *)runTimeLocalizedStringForKey:(NSString *)key value:(NSString *)value table:(NSString *)tableName;
#end
.m file
#import "NSBundle+RunTimeLanguage.h"
#implementation NSBundle (RunTimeLanguage)
- (NSString *)runTimeLocalizedStringForKey:(NSString *)key value:(NSString *)value table:(NSString *)tableName
{
NSString *StrCurrentLang = [[[NSUserDefaults standardUserDefaults] objectForKey:#"AppleLanguages"] objectAtIndex:0];
NSString *path= [[NSBundle mainBundle] pathForResource:StrCurrentLang ofType:#"lproj"];
NSBundle *languageBundle = [NSBundle bundleWithPath:path];
NSString *localizedString=[languageBundle localizedStringForKey:key value:key table:nil];
return localizedString;
}
#end
You can directly access the localized value by using below statement.
NSLocalizeString(#"yourText", nil)
I have an app settings file which displays the version number for our app. The idea is to update the Root.plist on compile so that we don't have to update two places rather than one. We update theBuild settings version and would like those settings to update the Root.plist on compile.
This code extracts the information for the build settings, how do I update the Root.plist file?
NSString *appBuildNo = [[NSBundle mainBundle]objectForInfoDictionaryKey:#"CFBundleShortVersionString"];
NSString *appBuildVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:#"CFBundleVersion"];
NSString *appBuildStr = [NSString stringWithFormat:#"%# Build %#", appBuildNo, appBuildVersion];
NSURL * settingsURL = [[NSBundle bundleWithURL:[[NSBundle mainBundle] URLForResource:#"Settings" withExtension:#"bundle"]]
URLForResource:#"Root" withExtension:#"plist"];
NSDictionary * settingsDict = [NSDictionary dictionaryWithContentsOfURL:settingsURL];
NSArray * settingsArr = [settingsDict objectForKey:#"PreferenceSpecifiers"];
NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
for( NSDictionary * setting in settingsArr ){
NSString * key = [setting objectForKey:#"DefaultValue"];
[defaults setValue:appBuildStr forKey:key];
[defaults synchronize];
break;
}
The idea is that defaults updates Root.plist but it is not working. Any help would be appreciated
Droppy was very helpful in helping me to fix this. After setting up my Settings Bundle and the Root.plist file, I added the following code to my AppDelegate and called it from the didFinishLaunchingWithOptions.
- (void)updateVersionInfo
{
//This method updates the Root settings to display current Version and Build No in Settings Bundle
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *appVersionNumber = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleVersion"];
NSString *appBuildNo = [[NSBundle mainBundle] objectForInfoDictionaryKey:#"CFBundleShortVersionString"];
NSString *versionNumberInSettings = [NSString stringWithFormat:#"%# Build %#", appBuildNo, appVersionNumber];
[defaults setObject:versionNumberInSettings forKey:#"version"];
}
This code then adds the contents of the Version in the info.plist file to my Settings Bundle.
I have a project divided in layers, and the bottom one is included in the top one as a static lib.
The thing is that I need to localize a string in the static lib, using the translations present in my app (upper layer).
Is this possible somehow?
The way I managed to do this is loading the strings from a bundle instead of using NSLocalizedString:
+ (NSString *)getTranslationFromAppBundleForString:(NSString *)originalText {
NSString * lang = [[NSLocale preferredLanguages] objectAtIndex:0];
NSString * bundlePath = [[NSBundle mainBundle] pathForResource:lang ofType:#"lproj"];
NSBundle * bundle = [NSBundle bundleWithPath:bundlePath];
return [bundle localizedStringForKey:originalText value:originalText table:nil];
}
You can create an LanguageAgent in your static library, add a bundle ressource in that library. Then use a function like this to get localizedString. In my application, I separate language by different table (see picture below for a table named 'Dictionaire'. You can have more than 1 table in your languages bundle.
-(NSString*) myLocalizedStringForKey:(NSString*) key ofTable:(NSString*)tableName {
//I save selected language in my NSUserDefaults.
NSString *selectedLanguage = [[NSUserDefaults standardUserDefaults] stringForKey:#"DefaultLanguage"];
if (selectedLanguage == nil) {
[[NSUserDefaults standardUserDefaults] setValue:#"en" forKey:#"DefaultLanguage"];
[[NSUserDefaults standardUserDefaults] synchronize];
selectedLanguage = [[NSUserDefaults standardUserDefaults] stringForKey:#"DefaultLanguage"];
}
NSString *langBundleNew = [[NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingFormat:#"/langs/Languages.bundle/%#.lproj/",selectedLanguage]; //use your path to the Languages.bundle here.
if ([[NSFileManager defaultManager] fileExistsAtPath:langBundleNew]) {
NSBundle *aBundle = (NSBundle*)[self.dictLangueBundle objectForKey:selectedLanguage];
NSString* str=[aBundle localizedStringForKey:key value:#"[string not defined]" table:tableName];
return str;
} else {
return #"[]";
}
}
My language bundle is similar like this: ('Dictionaire' = name of the table)
Here is a sample of content in my Dictionnaire.strings for 'en.lproj':
When I change the app used language independently on the device language it doesn't take effect until I close the app and restart it. How to not require app to be restarted for loading all nib files and .strings files again depending on the selected language?
I use this to change language at runtime:
NSArray* languages = [NSArray arrayWithObjects:#"ar", #"en", nil];
[[NSUserDefaults standardUserDefaults] setObject:languages forKey:#"AppleLanguages"];
This works for me :
Swift 4 :
Create a file named BundleExtension.swift and add the following code to it -
var bundleKey: UInt8 = 0
class AnyLanguageBundle: Bundle {
override func localizedString(forKey key: String,
value: String?,
table tableName: String?) -> String {
guard let path = objc_getAssociatedObject(self, &bundleKey) as? String,
let bundle = Bundle(path: path) else {
return super.localizedString(forKey: key, value: value, table: tableName)
}
return bundle.localizedString(forKey: key, value: value, table: tableName)
}
}
extension Bundle {
class func setLanguage(_ language: String) {
defer {
object_setClass(Bundle.main, AnyLanguageBundle.self)
}
objc_setAssociatedObject(Bundle.main, &bundleKey, Bundle.main.path(forResource: language, ofType: "lproj"), .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
Now whenever you need to change the language call this method :
func languageButtonAction() {
// This is done so that network calls now have the Accept-Language as "hi" (Using Alamofire) Check if you can remove these
UserDefaults.standard.set(["hi"], forKey: "AppleLanguages")
UserDefaults.standard.synchronize()
// Update the language by swaping bundle
Bundle.setLanguage("hi")
// Done to reintantiate the storyboards instantly
let storyboard = UIStoryboard.init(name: "Main", bundle: nil)
UIApplication.shared.keyWindow?.rootViewController = storyboard.instantiateInitialViewController()
}
I had a similar requirement for a Kiosk mode iPad app with tabbed navigation. Not only did the app need to support on-the-fly language changes, but had to do so knowing that most of the tabs were already loaded from the nibs since the app was only restarted (on average) about once a week when a new version was loaded.
I tried several suggestions to leverage the existing Apple localization mechanisms and they all had serious drawbacks, including wonky support in XCode 4.2 for localized nibs -- my IBoutlet connection variables would appear to be set correctly in IB, but at runtime they would often be null!?
I wound up implementing a class that mimicked the Apple NSLocalizedString class but which could handle runtime changes, and whenever a language change was made by a user my class posted a notification. Screens that needed localized strings (and images) to change declared a handleLocaleChange method, which was called at viewDidLoad, and whenever the LocaleChangedNotification was posted.
All of my buttons and graphics were designed to be language independent, although the title text and label text was typically updated in response to locale changes. If I had to change images, I could have done so in the handleLocaleChange methods for each screen, I suppose.
Here is the code. It includes some support for nib/bundle paths which I actually don't use in the final project.
MyLanguage.h
//
// MyLanguage.h
//
//
#import <Foundation/Foundation.h>
#define DEFAULT_DICTIONARY_FOR_STRINGS #""
#define ACCESSING_ALTERNATE_DICTIONARY_SETS_DEFAULT 1
#define LANGUAGE_ENGLISH_INT 0
#define LANGUAGE_SPANISH_INT 1
#define LANGUAGE_ENGLISH_SHORT_ID #"en"
#define LANGUAGE_SPANISH_SHORT_ID #"es"
#define LANGUAGE_CHANGED_NOTIFICATION #"LANGUAGE_CHANGED"
#interface MyLanguage : NSObject
{
NSString *currentLanguage;
NSDictionary *currentDictionary;
NSBundle *currentLanguageBundle;
}
+(void) setLanguage:(NSString *)languageName;
+(NSString *)stringFor:(NSString *)srcString forLanguage:(NSString *)languageName;
+(NSString *)stringFor:(NSString *)srcString;
+ (MyLanguage *)singleton;
#property (nonatomic, retain) NSBundle *currentLanguageBundle;
#property (nonatomic, retain) NSString *currentLanguage;
#property (nonatomic, retain) NSDictionary *currentDictionary;
#end
MyLanguage.m:
//
// MyLanguage.m
#import "MyLanguage.h"
#import "Valet.h"
#define GUI_STRING_FILE_POSTFIX #"GUIStrings.plist"
#implementation MyLanguage
#synthesize currentLanguage;
#synthesize currentDictionary;
#synthesize currentLanguageBundle;
+(NSDictionary *)getDictionaryNamed:(NSString *)languageName
{
NSDictionary *results = nil;
// for now, we store dictionaries in a PLIST with the same name.
NSString *dictionaryPlistFile = [languageName stringByAppendingString:GUI_STRING_FILE_POSTFIX];
NSString *plistBundlePath = [Valet getBundlePathForFileName:dictionaryPlistFile];
if ( [[NSFileManager defaultManager] fileExistsAtPath:plistBundlePath] )
{
// read it into a dictionary
NSDictionary *newDict = [NSDictionary dictionaryWithContentsOfFile:plistBundlePath];
results = [newDict valueForKey:#"languageDictionary"];
}// end if
return results;
}
+(NSString *)stringFor:(NSString *)srcString forDictionary:(NSString *)languageName;
{
MyLanguage *gsObject = [MyLanguage singleton];
// if default dictionary matches the requested one, use it.
if ([gsObject.currentLanguage isEqualToString:languageName])
{
// use default
return [MyLanguage stringFor:srcString];
}// end if
else
{
// get the desired dictionary
NSDictionary *newDict = [MyLanguage getDictionaryNamed:languageName];
// default is not desired!
if (ACCESSING_ALTERNATE_DICTIONARY_SETS_DEFAULT)
{
gsObject.currentDictionary = newDict;
gsObject.currentLanguage = languageName;
return [MyLanguage stringFor:srcString];
}// end if
else
{
// use current dictionary for translation.
NSString *results = [gsObject.currentDictionary valueForKey:srcString];
if (results == nil)
{
return srcString;
}// end if
return results;
}
}
}
+(void) setLanguage:(NSString *)languageName;
{
MyLanguage *gsObject = [MyLanguage singleton];
// for now, we store dictionaries in a PLIST with the same name.
// get the desired dictionary
NSDictionary *newDict = [MyLanguage getDictionaryNamed:languageName];
gsObject.currentDictionary = newDict;
gsObject.currentLanguage = languageName;
// now set up the bundle for nibs
NSString *shortLanguageIdentifier = #"en";
if ([languageName contains:#"spanish"] || [languageName contains:#"espanol"] || [languageName isEqualToString:LANGUAGE_SPANISH_SHORT_ID])
{
shortLanguageIdentifier = LANGUAGE_SPANISH_SHORT_ID;
}// end if
else
shortLanguageIdentifier = LANGUAGE_ENGLISH_SHORT_ID;
// NSArray *languages = [NSArray arrayWithObject:shortLanguageIdentifier];
// [[NSUserDefaults standardUserDefaults] setObject:languages forKey:#"AppleLanguages"];
//
NSString *path= [[NSBundle mainBundle] pathForResource:shortLanguageIdentifier ofType:#"lproj"];
NSBundle *languageBundle = [NSBundle bundleWithPath:path];
gsObject.currentLanguageBundle = languageBundle;
[[NSNotificationCenter defaultCenter] postNotificationName:LANGUAGE_CHANGED_NOTIFICATION object:nil];
}
+(NSString *)stringFor:(NSString *)srcString;
{
MyLanguage *gsObject = [MyLanguage singleton];
// default is to do nothing.
if (gsObject.currentDictionary == nil || gsObject.currentLanguage == nil || [gsObject.currentLanguage isEqualToString:DEFAULT_DICTIONARY_FOR_STRINGS] )
{
return srcString;
}// end if
// use current dictionary for translation.
NSString *results = [gsObject.currentDictionary valueForKey:srcString];
if (results == nil)
{
return srcString;
}// end if
return results;
}
#pragma mark -
#pragma mark Singleton methods
static MyLanguage *mySharedSingleton = nil;
-(void) lateInit;
{
}
// PUT THIS METHOD DECLARATION INTO THE HEADER
+ (MyLanguage *)singleton;
{
if (mySharedSingleton == nil) {
mySharedSingleton = [[super allocWithZone:NULL] init];
[mySharedSingleton lateInit];
}
return mySharedSingleton;
}
+ (id)allocWithZone:(NSZone *)zone
{ return [[self singleton] retain]; }
- (id)copyWithZone:(NSZone *)zone
{ return self; }
- (id)retain
{ return self; }
- (NSUInteger)retainCount //denotes an object that cannot be released
{ return NSUIntegerMax; }
- (oneway void)release //do nothing
{ }
- (id)autorelease
{ return self; }
#end
Don't rely on strings that you have set in your nib file. Use your nib only for layout & setup of views. Any string that is shown to the user (button text, etc) needs to be in your Localizable.strings files, and when you load your nib you need to set the text on the corresponding view/control accordingly.
To get the bundle for the current language:
NSString *path = [[NSBundle mainBundle] pathForResource:currentLanguage ofType:#"lproj"];
if (path) {
NSBundle *localeBundle = [NSBundle bundleWithPath:path];
}
And to use the bundle to obtain your localized strings:
NSLocalizedStringFromTableInBundle(stringThatNeedsToBeLocalized, nil, localeBundle, nil);
Also for date formatting, you might want to look into
[NSDateFormatter dateFormatFromTemplate:#"HH:mm:ss"" options:0 locale:locale];
To use that you will need to create a NSLocale for the corresponding language/country which you wish to use.
Heres what I did. I guess the trick was to use NSLocalizedStringFromTableInBundle instead of NSLocalizedString.
For all strings, use this
someLabel.text = NSLocalizedStringFromTableInBundle(#"Your String to be localized, %#",nil,self.localeBundle,#"some context for translators");
To change language, run this code
NSString * language = #"zh-Hans"; //or whatever language you want
NSString *path = [[NSBundle mainBundle] pathForResource:language ofType:#"lproj"];
if (path) {
self.localeBundle = [NSBundle bundleWithPath:path];
}
else {
self.localeBundle = [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:#"en" ofType:#"lproj"] ];
}
After this, you will likely want to call whatever update code to update the strings to the new languages, for e.g. run this again
someLabel.text = NSLocalizedStringFromTableInBundle(#"Your String to be localized, %#",nil,self.localeBundle,#"some context for translators");
Thats all. No need restart app. Compatible with system settings as well (if you set a language through iOS settings, it will work too). No need external library. No need jailbreak. And it works with genstrings too.
Of course, you should still do the usual for your app settings to persist:
[[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObjects:#"zh-Hans", nil] forKey:#"AppleLanguages"];
[[NSUserDefaults standardUserDefaults] synchronize];
(and do a check in your viewDidLoad or something)
NSString * language = [[NSLocale preferredLanguages] objectAtIndex:0];
NSString *path = [[NSBundle mainBundle] pathForResource:language ofType:#"lproj"];
if (path) {
self.localeBundle = [NSBundle bundleWithPath:path];
}
else {
self.localeBundle = [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:#"en" ofType:#"lproj"] ];
}
You should create your own macro similar to NSLocalizedString but bases the bundle it chooses a string from on a NSUserDefaults value you set (i.e. don't worry about what the value of apples language defaults value is)
When you change the language you should send out a notification, which view controllers, views etc should listen for and refresh themselves
I'm trying to use NSLocalizedStringFromTable but with no results. I've got created Profile.strings file, clicked localized, in project settings I add Polish and English languages, so my file has 2 "files" inside and I typed the same strings with other values but when I switch languages and restart app there is still one localization used (Polish).
Profile.strings in Xcode:
Profile.strings
Profile.strings (Polish)
Profile.strings (English)
Polish:
"fullName.placeholder" = "Imie i nazwisko";
"emailAddress.placeholder" = "Adres email";
"phoneNumber.placeholder" = "Numer telefonu";
English:
"fullName.placeholder" = "Full name";
"emailAddress.placeholder" = "Email address";
"phoneNumber.placeholder" = "Phone number";
To get value I call:
NSLocalizedStringFromTable(#"fullName.placeholder", #"Profile", #"");
Any time I call this I've got value from Profile.strings (Polish)
What I'm doing wrong?
try to Reset Content and Settings of simulator it will work (:
Maybe you're not changing the language correctly. Try doing it in code. Like this:
- (void) setLanguage:(NSString*) l{
for (NSString *language1 in [[NSUserDefaults standardUserDefaults] objectForKey:#"AppleLanguages"]) {
NSBundle *bundle1 = [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:language1 ofType:#"lproj"]];
NSLog(#"%#: %#", language1, NSLocalizedStringFromTableInBundle(#"left", #"Localizable", bundle1, nil));
}
NSBundle *bundle1 = [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:l ofType:#"lproj"]];
/*
if ([l isEqualToString:#"en"]) {
bundle1 = [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:#"English" ofType:#"lproj"]];
}
*/
if (bundle1 == nil)
//in case the language does not exists
[self resetLocalization];
else
bundle = bundle1;
NSMutableArray *langs = [NSMutableArray arrayWithArray: [[NSUserDefaults standardUserDefaults] objectForKey:#"AppleLanguages"]];
[langs removeObject:l];
NSMutableArray *newLangs = [NSMutableArray arrayWithObject:l];
[newLangs addObjectsFromArray:langs];
[[NSUserDefaults standardUserDefaults] setObject: newLangs forKey:#"AppleLanguages"];
}
And then you can do
[self setLanguage:#"en"];