Swift : How to change language inside app? - ios

i am using Localize-Swift library (Link) to localize my application and it works fine with .strings files. the problem is that i have to localize to a language which is right to left and i have to localize via Interface Builder Storyboard so i can make view controllers look right in RTL format. the question is how do i set the storyboard to user selected language in real time ?
for example i have 2 storyboard files :
1- ... /ProjectName/Base.lproj/Main.storyboard
2- ... /ProjectName/fa-IR.lproj/Main.storyboard
how do i switch between them in real time ?
i already know i can change it in Schemes and device language but i want to do it real time and i dont want the users to restart their device.
thanks

found my answer :
NSUserDefaults.standardUserDefaults().setObject(["language identifier"], forKey: "AppleLanguages")
NSUserDefaults.standardUserDefaults().synchronize()
unfortunately user must restart the app!
if anyone could find a solution to not restart the application please inform me.

You can use NSBundle+Language third party class.

In order to change the language without restarting your device you need to switch ‘lproj’ bundle.
You can make it using this code:
class L012Localizer: NSObject {
class func DoTheSwizzling() {
MethodSwizzleGivenClassName(cls: Bundle.self, originalSelector: #selector(Bundle.localizedString(forKey:value:table:)), overrideSelector:
#selector(Bundle.specialLocalizedString(key:value:table:)))
}
}
extension Bundle {
#objc func specialLocalizedString(key: String, value: String?, table tableName: String?) -> String {
let currentLanguage = Localization.currentAppleLanguage()
var bundle = Bundle();
if let _path = Bundle.main.path(forResource: currentLanguage, ofType: "lproj") {
bundle = Bundle(path: _path)!
} else {
let _path = Bundle.main.path(forResource: "Base", ofType: "lproj")!
bundle = Bundle(path: _path)!
}
return (bundle.specialLocalizedString(key: key, value: value, table: tableName))
}
}
func MethodSwizzleGivenClassName(cls: AnyClass, originalSelector: Selector, overrideSelector: Selector){
let origMethod: Method = class_getInstanceMethod(cls, originalSelector)!;
let overrideMethod: Method = class_getInstanceMethod(cls, overrideSelector)!;
if (class_addMethod(cls, originalSelector, method_getImplementation(overrideMethod), method_getTypeEncoding(overrideMethod))) {
class_replaceMethod(cls, overrideSelector, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
} else {
method_exchangeImplementations(origMethod, overrideMethod);
}
}
Here we are exchanging the implementation of Bundle's localizedString method. Note: we exchanging the Implementation not the reference on the function.
Now add this line in the Appdelegate in the didFinishLaunchingWithOptions delegate method.
L102Localizer.DoTheSwizzling()
After that you need to reload your ViewControllers. In your Main.storyboard set Root View Controller's StoryboardId to "rootnav" and paste this code to your method that switches language:
let rootviewcontroller: UIWindow = ((UIApplication.shared.delegate?.window)!)!
rootviewcontroller.rootViewController = self.storyboard?.instantiateViewController(withIdentifier: "rootNav")
let mainwindow = (UIApplication.shared.delegate?.window!)!
mainwindow.backgroundColor = UIColor(hue: 0.6477, saturation: 0.6314, brightness: 0.6077, alpha: 0.8)
UIView.transition(with: mainwindow, duration: 0.55001, options: .transitionFlipFromLeft, animations: { () -> Void in
}) { (finished) -> Void in
}

Maybe the RSMultiLanguage pod is something useful for you? I have used it in my apps and it provides to possibility to change the user language in app. I'm pretty sure you can set it depending on the user location with an if loop. That way you might not have to restart the app.
RSMultiLanguage
Usage
To run the example project, clone the repo, and run pod install from the Example directory first.
Installation
RSMultiLanguage is available through CocoaPods. To install it, simply add the following line to your Podfile:
pod "RSMultiLanguage"
Author
Roy Ng, roytornado#gmail.com
License
RSMultiLanguage is available under the MIT license. See the LICENSE file for more info.

Requires restart of app. Follow below lines of code.
UserDefaults.standard.set(["language identifier"], forKey: "AppleLanguages")
UserDefaults.standard.synchronize()
Restart of app not requires ( the answer may requires customization in provided source code). Follow the provided web link.
http://www.factorialcomplexity.com/blog/2015/01/28/how-to-change-localization-internally-in-your-ios-application.html

Related

Localisation Settings Not Changing the System Defined Text in Swift 4.2

I have 2 lang support for my app
1) English - en
2) German - de
I have done all the procedures for localization, the only issue is
whenever I change the language from "en" to "de" or vice versa then
after the system text is not changing to the latest lang, but it
reflects when I kill the app and reopen it.
For Example:
The popover Copy-LookUp-Share is not localsied to German Lang. but the other things from .string file are lcoalised properly.
My change lang code:
func setLanguage(languageCode:String) {
var appleLanguages = UserDefaults.standard.object(forKey: "AppleLanguages") as! [String]
appleLanguages.remove(at: 0)
appleLanguages.insert(languageCode, at: 0)
UserDefaults.standard.set(appleLanguages, forKey: "AppleLanguages")
UserDefaults.standard.synchronize()
if let languageDirectoryPath = Bundle.main.path(forResource: languageCode, ofType: "lproj") {
bundle = Bundle.init(path: languageDirectoryPath)
} else {
resetLocalization()
}
}
func resetLocalization() {
bundle = Bundle.main
}
FYI: Similar thing happens in 'WeChat' iOS application.
Those are UIKit SDK private menu items and you cannot control them, probably they are just created once then kept cached.
The only thing you can try is to force update in the moment of language change, like
UIMenuController.shared.update()
Note: actually using "AppleLanguages" key for that purpose is not documented, so it is kind of hucky. L10N is system settings originated feature, by design.

Localization inside private pod is always defaulting to English

I have a private pod inside a application. I am trying to localize the pod. You can see that in the below Image.
I have added the localization file in the resource bundle of the pod.
After that I created an extension on String for localizing values inside pod.
extension Bundle {
private class ClassForBundle {}
static func frameworkBundle() -> Bundle {
let frameworkBundle = Bundle(for: ClassForBundle.self)
let bundleURL = frameworkBundle.resourceURL?.appendingPathComponent("ABC.bundle")
print("Bundle url....\(String(describing: bundleURL))")
return Bundle(url: bundleURL!)!
}
}
extension String {
func localized(withComment comment: String = "") -> String {
return NSLocalizedString(self, bundle: Bundle.frameworkBundle(), comment: comment)
}
}
When I am trying to localize string using the following code. It's always defaulting to English.
let localizedString = "Land For Lease".localized(withComment: "Land For Lease label")
value I am getting is 'US Land To Let' though my country and region in simulator settings is set to Spanish and Mexico. Is there anything I am missing that I need to do? Your help will be highly appreciated. Thanks.
It was pod cache which was creating the issue. I spend almost half day on it. Deleting the app from simulator and deleting derived data saved my life. I am keeping this question here so that it may helpful and time saving for someone in future in case he want to add localization to private pod or facing same kind of issue.

How to localize iMessage stickers?

I am creating an iMessage Sticker Pack Extension that have stickers that contain texts. I don't see any option in Xcode to localize the stickers? Is it possible to localize stickers?
Maybe it's obvious, but you can manage it inside of an extension. If you create not a pack, but extension, you can fetch exact stickers for your localization
If you want to show different sticker-assets dependant on e.g. your device's language, you need to create a iMessage Application instead of a Sticker Pack Application.
And you need to write some code as it's not possible to have this behaviour within a simple Sticker Pack Application.
However this is pretty simple. For a start, follow this tutorial:
http://blog.qbits.eu/ios-10-stickers-application/
Some code-syntax there is outdated, but XCode will help you to easily fix that.
You can place your localized resources inside different folders and drag'n'drop them into you XCode project (make sure to check "Create folder references"):
Screenshot of project structure
You can then do something like this in your viewDidLoad():
//-- get current language set
var imageSetPath = "/EN"; //-- default to english
let languageCode = NSLocale.preferredLanguages[0]
if languageCode.hasPrefix("zh-Hans") { imageSetPath = "/CNS" }
else if languageCode.hasPrefix("zh-Hans") { imageSetPath = "/CNT" }
else if languageCode.hasPrefix("ko") { imageSetPath = "/KR" }
else if languageCode.hasPrefix("ja") { imageSetPath = "/JP" }
//-- load localized stickers
imageUrls = recursivePathsForResources(path: Bundle.main.bundlePath + imageSetPath, type: "png")
loadStickers(urls: imageUrls)
where
func recursivePathsForResources(path: String, type: String) -> [URL] {
// Enumerators are recursive
let enumerator = FileManager.default.enumerator(atPath: path)
var filePaths = [URL]()
while let filePath = enumerator?.nextObject() as? String {
if NSURL(fileURLWithPath: filePath).pathExtension == type {
let url = URL(fileURLWithPath: path).appendingPathComponent(filePath)
filePaths.append(url)
}
}
return filePaths
}
(altered from https://stackoverflow.com/a/5860015/6649403)

Change app language at run time

I am trying to change the the lang that I get with NSLocalizedString at run time.
I know that this question as been already asking, but I do not find an answers for Swift 3.
I have try:
UserDefaults.standard.removeObject(forKey: "AppleLanguages")
UserDefaults.standard.set("en", forKey: "AppleLanguages")
UserDefaults.standard.synchronize()
and:
let language = "en"
let path = Bundle.main.path(forResource: language, ofType: "lproj")
let bundle = Bundle(path: path!)
let string = bundle?.localizedString(forKey: "AppleLanguages", value: language, table: nil)
but nothing works....
Thank you for your help!
So at the end, I've used a custom localisator class available on Github, which allow you to switch language from anywhere in the app and even save if for further launches of the app.
It is written in swift 2.2, but Xcode automatically update the code to Swift 3. Just had to change at to places in the Demo:
notification.name == kNotificationLanguageChanged
to:
notification.name.rawValue == kNotificationLanguageChanged
https://github.com/micazeve/iOS-CustomLocalisator

Force NSLocalizedString to use a specific language using Swift

With swift, how can I force my app to read data from a specific Localizable.strings.
I put this in didFinishLaunchingWithOptions before instantiate the ViewController but it still show me the App in English.
NSUserDefaults.standardUserDefaults().removeObjectForKey("AppleLanguages")
NSUserDefaults.standardUserDefaults().setObject("fr", forKey: "AppleLanguages"
NSUserDefaults.standardUserDefaults().synchronize()
And I tried to pass an Array for the "AppleLanguages" key like this but it still doesn't work:
NSUserDefaults.standardUserDefaults().setObject(["fr"], forKey: "AppleLanguages"
And once this is done, can I call this inside the App and take the changes in consideration without restarting the App?
It's not possible to change app's language immediately by changing the value of AppleLanguages. It requires restarting the app before the change takes effect.
It seems that your problem is accessing the localization strings of different languages rather than changing the app's language, right? If you want your app to support multiple languages, you can just provide the translations and rely on settings.app for the actual change.
If you want to access the localization strings from other than currently used localization, you need to get access to the proper translations bundle. And then just query that bundle for the translations. The following piece of code should do the trick.
let language = "en"
let path = Bundle.main.path(forResource: language, ofType: "lproj")
let bundle = Bundle(path: path!)
let string = bundle?.localizedStringForKey("key", value: nil, table: nil)
With NSLocalizedString you can specify the bundle.
let language = "fr"
let path = Bundle.main.path(forResource: language, ofType: "lproj")!
let bundle = Bundle(path: path)!
let localizedString = NSLocalizedString(key, bundle: bundle, comment: "")
Or with a bundle, you may also call localizedStringForKey:value:table: directly too.
#Radu I also made this working for XCUITests thanks to #Markus' original answer :
You can specify explicitly the path to your MainBundle, it will only work on your Mac with the Simulator, but it is often used in continuous integration platforms so this might be acceptable :
let language: String = "en"
let path = "/Users/{username}/{path_to_your_project}/\(language).lproj"
let bundle = Bundle(path: path)
let string = bundle?.localizedString(forKey: "key", value: nil, table: nil)
In swift 4, I have solved it without needing to restart or use libraries.
After trying many options, I found this function, where you pass the stringToLocalize (of Localizable.String, the strings file) that you want to translate, and the language in which you want to translate it, and what it returns is the value for that String that you have in Strings file:
    func localizeString (stringToLocalize: String, language: String) -> String
    {
        let path = Bundle.main.path (forResource: language, ofType: "lproj")
        let languageBundle = Bundle (path: path!)
        return languageBundle! .localizedString (forKey: stringToLocalize, value: "", table: nil)
    }
Taking into account this function, I created it as global in a Swift file:
struct CustomLanguage {
func createBundlePath () -> Bundle {
let selectedLanguage = //recover the language chosen by the user (in my case, from UserDefaults)
let path = Bundle.main.path(forResource: selectedLanguage, ofType: "lproj")
return Bundle(path: path!)!
}
}
To access from the whole app, and in each string of the rest of ViewControllers, instead of putting:
NSLocalizedString ("StringToLocalize", comment: “")
I have replaced it with
let customLang = CustomLanguage() //declare at top
NSLocalizedString("StringToLocalize", tableName: nil, bundle: customLang.createBundlePath(), value: "", comment: “”) //use in each String
I do not know if it's the best way, but I found it very simple, and it works for me, I hope it helps you!

Resources