📜 ⬆️ ⬇️

Alternative to NSLocalizedString, language change without restarting the application

Good day to all. While working on a number of projects with multi-language support, I encountered a number of inconveniences in the standard NSLocalizedString tool.

The main problem was that language changes take effect only when the application is overloaded, which, from the point of view of usability, is not very pleasant and convenient for the user.

If, of course, you do not have a clear task of changing the language in the application, then you can use the standard NSLocalizedString. If you have such a possibility, NSLocalizedString becomes very inconvenient.

Maybe I tried to reinvent the wheel.
')
What was the task?

1. First and foremost, no reloading of the application;
2. Ease of use in the code on the principle NSLocalizedString (key: String);
3. Convenience for translators (but in this case I have doubts that in any kind it will be convenient).

Before the release of Swift, Objective-c used a macro to implement the above task.

The code itself looks like this:

//  NSUserDefaults #define kLocale @"kLocale" //  .plist #define kTypeLocalizable @"plist" //AppDelegate //         ( ) if (![[NSUserDefaults standardUserDefaults]objectForKey:kLocale]) { // ,       NSString *langValue = kLangValue; //,     ,     . NSString *key = (![langValue isEqualToString:@"ru"] && ![langValue isEqualToString:@"en"]) ? @"en" : langValue; //   [[NSUserDefaults standardUserDefaults]setObject:[NSString stringWithFormat:@"%@_Localizable",key] forKey:kLocale]; } #define kLangValue ([kLanguserDefaultValue length]>2)? [kLanguserDefaultValue substringToIndex:[kLanguserDefaultValue length]-([kLanguserDefaultValue length]-2)]:kLanguserDefaultValue; //   #define kNameFile [[NSUserDefaults standardUserDefaults]objectForKey:kLocale] //     .plist     NSDictionary     #define KOLocalizable(key) [[NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:kNameFile ofType:kTypeLocalizable]] objectForKey:key] 

The implementation looks like this:

 textLabel.text = KOLocalizable(@"kText") //   NSLocalizedString ("","" )    

And the actual change of language, it is simple to replace the name of the file:

 [[NSUserDefaults standardUserDefaults]setObject:@"ru_Localizable" forKey:kLocale]; 

I can not say that this code is the limit of perfection, it works.

But unfortunately in Swift I was deprived of such an opportunity and I had to think about an alternative. Long break your brain, how beautiful and more practical to implement it.

The idea was that it was as brief as the standard implementation of NSLocalizedString (“key”, “comment”) only without a comment.

 textLabel.text = KOLocalized(key:"kText") 

Swift was pleased that the function can be moved outside the class and not tied to anything. It turns out such a global function, publicly available (in fact, the same macro).

 import Foundation func KOLocalized(key:String)->String{ return KOLocalizedClass.instanc.valueWith(key: key) } 

In this function we refer to the class where all the magic happens.

 class KOLocalizedClass: NSObject { static let instanc = KOLocalizedClass() private let localeArray:Array = ["ru","en"] private let keyLocale: String = "kLocale" private let endNameFile: String = "Localizable" private var localeDictionary : NSDictionary! private let typeLocalizable : String = "plist" private var nameFile : String! override init() { super.init() checkFirstInit() } //MARK: Public Methods public func changeLocalized(key:String){ UserDefaults.standard.set("\(key)_\(endNameFile)", forKey: keyLocale) nameFile = "\(key)_\(endNameFile)" updateDictionary() } //MARK: Internal Methods internal func valueWith(key:String) -> String { var value:String value = localeDictionary.object(forKey: key) as? String ?? key return value } //MARK: Privat Methods private func checkFirstInit(){ if UserDefaults.standard.object(forKey: keyLocale) == nil{ var langValue:String { var systemLocale : String = NSLocale.preferredLanguages[0] if systemLocale.characters.count > 2 { let index = systemLocale.range(of: "-")?.lowerBound systemLocale = systemLocale.substring(to: index!) } for localeString in localeArray{ if localeString == systemLocale{ systemLocale = localeString } } return systemLocale == "" ? systemLocale: "en" } UserDefaults.standard.set("\(langValue)_\(endNameFile)", forKey: keyLocale) nameFile = "\(langValue)_\(endNameFile)" }else{ nameFile = UserDefaults.standard.object(forKey: keyLocale) as! String } updateDictionary() } //Update Dictionary private func updateDictionary(){ if let path = Bundle.main.path(forResource: nameFile, ofType: typeLocalizable) { localeDictionary = NSDictionary(contentsOfFile: path)! } } } 

The only difference from the implementation on Objective-c, in Swift, created a class as a singleton in which we store a variable of type Dictionary:

 private var localeDictionary : NSDictionary! 

And do not pull the file every time. And the language change now occurs through the function:

 KOLocalizedClass.instanc.changeLocalized(key: "ru") 

Here is an example of how this works.

I hope this material will be useful.

Source: https://habr.com/ru/post/311004/


All Articles