Defaulting to a particular language is not what you should strive for. Suppose a japanese user living in France and fluently speaking French has his phone set to Japanese. Your App would default to German, despite it having a French translation; I guess that's not what you want and it's certainly not what the user wants.
For this reason I suggest to respect the language prioritization as set by the user.
Since you don't want to support English but have developed the App in English, the easiest thing you can do is to just get rid of en.lproj
right after compiling. This will leave the app with only the languages you plan to support, and the iPhone will chose the language best suited for the user, as set in iPhone's defaults.
There is a pretty straightforward solution to deleting a specific localization:
Let Xcode build the App with all the present localizations and just before code-signing kicks in delete the en.lproj
folder and all localized files for that language are gone. You can easily do this by adding a Run Script build phase to the target, containing this one line of code (it's a Bash Script):
rm -r "${TARGET_BUILD_DIR}/${PRODUCT_NAME}.app/en.lproj"
Code Signing always kicks in after all build phases have completed, so just put that build phase at the end of your current phases.
Note that this increases the time to build because Xcode recreates all English files, so you might want to disable this step during testing.
I hope that's a viable solution for you.
Concerning "the app must fall back to German in the case that an appropriate localization doesn't exist":
Question is what is an appropriate localization? If the user's third choice is French (after 2 unsupported languages), this solution will make the app fall back to French, which is an appropriate localization. More appropriate than setting the user's fifth choice, German, by hand.
What happens when an app launches is simple: The OS descends the users language list, as set in Preferences, until it finds a matching localization, and uses this language. The reason many apps default to English and not German is simply because English appears on most user language lists above German. There is no inherent language preference to the system. If you delete the English translation, only the languages you want to support are there, and of these languages the one higher on a user's list is taken.
In the meantime I did find a solution for my problem on myself:
I created a new class "LocalizeHelper":
Header LocalizeHelper.h
//LocalizeHelper.h
#import <Foundation/Foundation.h>
// some macros (optional, but makes life easy)
// Use "LocalizedString(key)" the same way you would use "NSLocalizedString(key,comment)"
#define LocalizedString(key) [[LocalizeHelper sharedLocalSystem] localizedStringForKey:(key)]
// "language" can be (for american english): "en", "en-US", "english". Analogous for other languages.
#define LocalizationSetLanguage(language) [[LocalizeHelper sharedLocalSystem] setLanguage:(language)]
@interface LocalizeHelper : NSObject
// a singleton:
+ (LocalizeHelper*) sharedLocalSystem;
// this gets the string localized:
- (NSString*) localizedStringForKey:(NSString*) key;
//set a new language:
- (void) setLanguage:(NSString*) lang;
@end
iMplementation LocalizeHelper.m
// LocalizeHelper.m
#import "LocalizeHelper.h"
// Singleton
static LocalizeHelper* SingleLocalSystem = nil;
// my Bundle (not the main bundle!)
static NSBundle* myBundle = nil;
@implementation LocalizeHelper
//-------------------------------------------------------------
// allways return the same singleton
//-------------------------------------------------------------
+ (LocalizeHelper*) sharedLocalSystem {
// lazy instantiation
if (SingleLocalSystem == nil) {
SingleLocalSystem = [[LocalizeHelper alloc] init];
}
return SingleLocalSystem;
}
//-------------------------------------------------------------
// initiating
//-------------------------------------------------------------
- (id) init {
self = [super init];
if (self) {
// use systems main bundle as default bundle
myBundle = [NSBundle mainBundle];
}
return self;
}
//-------------------------------------------------------------
// translate a string
//-------------------------------------------------------------
// you can use this macro:
// LocalizedString(@"Text");
- (NSString*) localizedStringForKey:(NSString*) key {
// this is almost exactly what is done when calling the macro NSLocalizedString(@"Text",@"comment")
// the difference is: here we do not use the systems main bundle, but a bundle
// we selected manually before (see "setLanguage")
return [myBundle localizedStringForKey:key value:@"" table:nil];
}
//-------------------------------------------------------------
// set a new language
//-------------------------------------------------------------
// you can use this macro:
// LocalizationSetLanguage(@"German") or LocalizationSetLanguage(@"de");
- (void) setLanguage:(NSString*) lang {
// path to this languages bundle
NSString *path = [[NSBundle mainBundle] pathForResource:lang ofType:@"lproj" ];
if (path == nil) {
// there is no bundle for that language
// use main bundle instead
myBundle = [NSBundle mainBundle];
} else {
// use this bundle as my bundle from now on:
myBundle = [NSBundle bundleWithPath:path];
// to be absolutely shure (this is probably unnecessary):
if (myBundle == nil) {
myBundle = [NSBundle mainBundle];
}
}
}
@end
For each language you want to support you need a file named Localizable.strings
. This works exactly as described in Apples documentation for localization. The only difference: Now you even can use languages like hindi or esperanto, that are not supported by Apple.
To give you an example, here are the first lines of my english and german versions of Localizable.strings:
English
/* English - English */
/* for debugging */
"languageOfBundle" = "English - English";
/* Header-Title of the Table displaying all lists and projects */
"summary" = "Summary";
/* Section-Titles in table "summary" */
"help" = "Help";
"lists" = "Lists";
"projects" = "Projects";
"listTemplates" = "List Templates";
"projectTemplates" = "Project Templates";
German
/* German - Deutsch */
/* for debugging */
"languageOfBundle" = "German - Deutsch";
/* Header-Title of the Table displaying all lists and projects */
"summary" = "Überblick";
/* Section-Titles in table "summary" */
"help" = "Hilfe";
"lists" = "Listen";
"projects" = "Projekte";
"listTemplates" = "Vorlagen für Listen";
"projectTemplates" = "Vorlagen für Projekte";
To use localizing, you must have some settings-routines in your app, and in the language-selection you call the macro:
LocalizationSetLanguage(selectedLanguage);
After that you must enshure, that everything that was displayed in the old language, gets redrawn in the new language right now (hidden texts must be redrawn as soon as they get visible again).
To have localized texts available for every situation, you NEVER must write fix texts to the objects titles. ALWAYS use the macro LocalizedString(keyword)
.
don't:
cell.textLabel.text = @"nice title";
do:
cell.textLabel.text = LocalizedString(@"nice title");
and have a "nice title" entry in every version of Localizable.strings!
Best Answer
To avoid all those lengthy syntax and more having more descriptive var name for translators, I derived my own helper method
L()
for translation and falling back to EnglishMy
Localizable.strings
would look like thisSo in my code, i would use
L(@"SOME_ACTION_BUTTON")
to get the correct stringThough sometime the key is longer than the translation itself
HELP_BUTTON_IN_NAV_BAR = 'Help'
but it saves me a lot of time explaining what it is to whoever is helping me doing the translation