I am aware of quite a few posts that say this should not be done, or is not possible. I tinkered with a few ideas and now I'm asking this question, because I want to be absolutely sure there are no other options.
Option 1:
The most popular solution is to change AppleLanguages
as in this post. I do not mind the idea of requiring a restart, so this would be an acceptable solution for me, except that you cannot restart your app programmatically (can't find the method, or would be rejected by Apple). Asking the user to manually restart the application wouldn't be ideal.
Option 2:
The next solution is to get the appropriate bundle and perform a localizedStringForKey
lookup on each and every UILabel
, UIButton
, etc. This can be a little tedious but is okay for me, since I already added localizationProperties (similar to this) to these views so that I can have a centralized strings file.
AppDelegate.swift:
static var userLanguage: String?
{
set
{
let defaults = NSUserDefaults.standardUserDefaults();
defaults.setObject(newValue, forKey: LanguageKey);
defaults.synchronize();
let storyboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle());
instance.window?.rootViewController = storyboard.instantiateInitialViewController();
}
get
{
let defaults = NSUserDefaults.standardUserDefaults();
return defaults.stringForKey(LanguageKey);
}
}
Localization.swift:
private var bundle: NSBundle
{
get
{
let bundle: NSBundle;
#if TARGET_INTERFACE_BUILDER
bundle = NSBundle(forClass: self.dynamicType);
#else
bundle = NSBundle.mainBundle();
#endif
let lang: String;
if(AppDelegate.userLanguage == nil || AppDelegate.userLanguage == "en")
{
lang = "Base";
}
else
{
lang = AppDelegate.userLanguage!;
}
let path = bundle.pathForResource(lang, ofType: "lproj");
if(path != nil)
{
let toreturn = NSBundle(path: path!);
if(toreturn != nil)
{
return toreturn!;
}
}
return bundle;
}
}
extension UILabel
{
@IBInspectable var localizedText: String?
{
get { return "" }
set
{
if(newValue != nil)
{
text = bundle.localizedStringForKey(newValue!, value:"", table: nil);
}
}
}
}
The problem with option 2 is that this only sets the language, for those fields. Layout direction will be unchanged, and files such as language specific layouts would not be used.
By extending UIApplication
I am able to specify a custom userInterfaceLayoutDirection
which successfully swaps all layouts between LTR and RTL.
DemoApplication.swift:
class DemoApplication: UIApplication
{
override internal var userInterfaceLayoutDirection: UIUserInterfaceLayoutDirection
{
get
{
if(AppDelegate.userLanguage == "ar")
{
return UIUserInterfaceLayoutDirection.RightToLeft;
}
return UIUserInterfaceLayoutDirection.LeftToRight;
}
}
}
Now when I set AppDelegate.userLanguage
the application will reset to the initial view controller, displaying the new language, flipping the layout between LTR and RTL. This does not address the issue of language specific files, and I've also noticed that text remains left or right aligned within its own bounds.
Since I can't find the source code for native iOS classes, I can't see what language specific variables are set at startup so I assumed it is linked to the NSBundle.mainBundle
. I tried to override it by using method swizzling.
extension NSBundle
{
override public class func initialize()
{
struct Static
{
static var token: dispatch_once_t = 0;
}
// make sure this isn't a subclass
if (self !== NSBundle.self)
{
return;
}
dispatch_once(&Static.token)
{
do
{
try jr_swizzleClassMethod("mainBundle", withClassMethod: "mainBundleExt");
}
catch
{
print("\(error)");
}
}
super.initialize();
}
public class func mainBundleExt() -> NSBundle
{
let bundle = self.mainBundleExt(); // Due to swizzling, this is the "super" method
let lang: String;
if(AppDelegate.userLanguage == nil || AppDelegate.userLanguage == "en")
{
lang = "Base";
}
else
{
lang = AppDelegate.userLanguage!;
}
let path = bundle.pathForResource(lang, ofType: "lproj");
if(path != nil)
{
let toreturn = NSBundle(path: path!);
if(toreturn != nil)
{
return toreturn!;
}
}
}
}
This does not work though, it seems as though the default mainBundle
is still used.
So my question is this: How is mainBundle
assigned a value? Which other language specific variables are set at startup, such as userInterfaceLayoutDirection
.
I assume there are 2 or 3 of these variables. Finally, is it possible for this to work or am I just wasting my time?
Thanks.