Всем доброго времени суток. Работая над рядом проектов с поддержкой нескольких языков, столкнулся с рядом неудобств стандартного инструмента NSLocalizedString.
Основной проблемой было то, что изменения языка вступают в силу только при перегрузке приложения, что с точки зрения юзабилити, не очень приятно и удобно для пользователя.
Если конечно же у вас нет четкой задачи смены языка в приложении, то можно использовать и стандартный NSLocalizedString. Если же у вас предусмотрена такая возможность NSLocalizedString становиться очень неудобным.
Возможно я пытался изобрести велосипед.
Какова была задача?
1. Первое и самое главное, ни какой перезагрузки приложения;
2. Удобство в использовании в коде по принципу NSLocalizedString(key: String);
3. Удобство для переводчиков(но в данном случае у меня сомнения, что в каком угодно виде оно будет удобно).
До выхода языка Swift, на Objective-c для реализации выше поставленной задачи использовал макрос.
Сам код выглядит примерно так:
Реализация выглядит так:
И собственно смена языка, это просто заменить название фала:
Не могу сказать, что данный код предел совершенства, он работает.
Но к великому сожалению в Swift меня лишили такой возможности и пришлось думать над альтернативой. Долгое ломание своего мозга, как же красивее и практичнее это реализовать.
Идея заключалась в том что бы это было также кратко как и стандартная реализация NSLocalizedString («ключ»,«комментарий») только без комментария.
Swift порадовал тем, что можно функцию вынести за пределы класса и не привязывать к чему либо. Получается такая глобальная функция, общедоступная (по сути, тот же макрос).
В этой функции мы обращаемся к классу где и происходит вся магия.
Единственное отличие от реализации на Objective-c, в Swift создали класс как singleton в котором храним переменную типа Dictionary:
И не дергаем каждый раз файл. И смена языка теперь происходит через функцию:
Вот пример как это работает.
Надеюсь данный материал будет полезен.
Основной проблемой было то, что изменения языка вступают в силу только при перегрузке приложения, что с точки зрения юзабилити, не очень приятно и удобно для пользователя.
Если конечно же у вас нет четкой задачи смены языка в приложении, то можно использовать и стандартный 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")
Вот пример как это работает.
Надеюсь данный материал будет полезен.