Всем доброго времени суток. Работая над рядом проектов с поддержкой нескольких языков, столкнулся с рядом неудобств стандартного инструмента NSLocalizedString.
Основной проблемой было то, что изменения языка вступают в силу только при перегрузке приложения, что с точки зрения юзабилити, не очень приятно и удобно для пользователя.
Если конечно же у вас нет четкой задачи смены языка в приложении, то можно использовать и стандартный NSLocalizedString. Если же у вас предусмотрена такая возможность NSLocalizedString становиться очень неудобным.
Возможно я пытался изобрести велосипед.
Какова была задача?
1. Первое и самое главное, ни какой перезагрузки приложения;
2. Удобство в использовании в коде по принципу NSLocalizedString(key: String);
3. Удобство для переводчиков(но в данном случае у меня сомнения, что в каком угодно виде оно будет удобно).
До выхода языка Swift, на Objective-c для реализации выше поставленной задачи использовал макрос.
Сам код выглядит примерно так:
//ключ для 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]
Реализация выглядит так:
textLabel.text = KOLocalizable(@"kText")
//очень похоже на NSLocalizedString ("ключ","комментарий" ) только без комментария
И собственно смена языка, это просто заменить название фала:
[[NSUserDefaults standardUserDefaults]setObject:@"ru_Localizable" forKey:kLocale];
Не могу сказать, что данный код придел совершенства, он работает.
Но к великому сожалению в Swift меня лишили такой возможности и пришлось думать над альтернативой. Долгое ломание своего
Идея заключалась в том что бы это было также кратко как и стандартная реализация NSLocalizedString («ключ»,«комментарий») только без комментария.
textLabel.text = KOLocalized(key:"kText")
Swift порадовал тем, что можно функцию вынести за пределы класса и не привязывать к чему либо. Получается такая глобальная функция, общедоступная (по сути, тот же макрос).
import Foundation
func KOLocalized(key:String)->String{
return KOLocalizedClass.instanc.valueWith(key: key)
}
В этой функции мы обращаемся к классу где и происходит вся магия.
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)!
}
}
}
Единственное отличие от реализации на Objective-c, в Swift создали класс как singleton в котором храним переменную типа Dictionary:
private var localeDictionary : NSDictionary!
И не дергаем каждый раз файл. И смена языка теперь происходит через функцию:
KOLocalizedClass.instanc.changeLocalized(key: "ru")
Вот пример как это работает.
Надеюсь данный материал будет полезен.
Автор: SethSky