Не так давно я опубликовал подробную инструкцию по использованию LocoLaser — утилиты для локализации Android и iOS приложений в Google Sheets. Мне бы хотелось продолжить тему локализации и обратить больше внимания на iOS приложения. В отличии от Android, в iOS разработке есть ряд мелких но неприятных моментов, которые, в сумме, могут привести к совсем не мелким проблемам.
Сегодня я хочу уделить особое внимание Interface Builder-у. Все мы знаем, он не идеален. Но это единственное, что у нас есть и с этим приходится мириться. В этой статье я расскажу о главной проблеме, с которой вы можете столкнуться при локализации приложений в Interface Builder, а также расскажу как с ней можно справиться.
Суть проблемы
Когда вы переводите Storyboard или XIB файл, помимо основного файла с разметкой, создаются дополнительные файлы с ресурсными строками. Эти ресурсные файлы принято выгружать в специальные таблицы и отдавать переводчикам. Беда заключается в том, что ключи для строк, в этом файле, строятся на основе Object ID, которые генерируются автоматически и нет ни какой возможности на них повлиять. Если вас угораздило скопировать или вырезать а затем вставить какой либо View, Interface Builder сгенерирует новые идентификаторы и перевод будет потерян.
Каждый решает эту проблему как может. Помню, 2 года назад, на мобильной конференции я спрашивал одного ведущего iOS разработчика одной из ведущих фирм о том как они решают проблему локализации интерфейса. В то время я только начинал изучать iOS, но у меня уже был достаточно богатый опыт в области Android и мне было с чем сравнивать. Честно говоря, я был ошарашен ответом. Во ViewController через IBOutlet они получали ссылки на Label и прочие View и переводили их программно. В коде это выглядит приблизительно так:
class MainViewController: UIViewController {
@IBOutlet var labelToTranslate: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
self.labelToTranslate.text = NSLocalizedString("scr_main_txt_example", comment: "Some Example text")
...
}
...
}
В этом случае все строки находятся в одном файле Localizable.strings. Подобный метод в настоящий момент является наиболее распространенным и используется почти повсеместно. Признайтесь, получается не слишком элегантно. Мало того что вы засоряете ViewController лишним кодом, которому тут не место, так еще и не решаете проблему с копированием или перемещением View. Пора бы найти что-то получше.
Решение
И тут у меня есть кое-что, что я могу вам предложить. Дело в том, что в Interface Builder, в свойствах View, можно прописать так называемые «User Defined Runtime Attributes». Их то мы и будем использовать. Но для начала необходимо создать Extension для UILabel.
extension UILabel {
public var lzText : String? {
set {
if newValue != nil {
self.text = NSLocalizedString(newValue, comment: “”)
}
else {
self.text = nil
}
}
get {
return self.text
}
}
}
Теперь у всех UILabel появилось свойство lzText при изменении которого в свойство text записывается локализованная строка. Используем это свойство в Interface Builder.
- Выбираем UILabel и переходим на вкладку «Identity Inspector»;
- Жмем кнопку добавления атрибута в «User Defined Runtime Attributes»;
- Указываем ключ атрибута «lzText», тип: «String», значение: «scr_main_txt_example»
И это все. Больше не нужно засорять код ничем лишним. Вы можете не бояться, что перевод или ссылка на View потеряется при копировании или перемещении в другой контейнер. Атрибуты копируются вместе с View. Единственное, что останется неизмеренным — это хранение всех строк в одном файле Localized.strings.
UPD:
Но это не все. DjPhoeniX предложил сделать еще лучше. И нам не потребуется почти ничего менять. Нужно лишь добавить @IBInspectable
перед объявлением свойства.
extension UILabel {
@IBInspectable public var lzText : String? {
set {
if newValue != nil {
self.text = NSLocalizedString(newValue, comment: “”)
}
else {
self.text = nil
}
}
get {
return self.text
}
}
}
Теперь это свойство также доступно на вкладке «Attributes Inspector».
Для еще большего удобства я подготовил файл, в котором собрано достаточно большое количество расширений для часто используемых View, где у каждого текстового свойства есть двойник с приставкой «lz»(сокращение слова «localized»). Вы можете найти этот файл в примере по использованию LocoLaser: LocalizationExtensions.swift. Весь проект опубликован под лицензией Apache 2.0, можете смело копировать этот файл к себе и начинать использовать.
Помимо расширений для View, в LocalizationExtensions.swift добавлено расширение к классу String. Оно добавляет вычисляемое свойство localized, которое возвращает локализованную строку. Если перевод найти не получается, отправляется оповещение через NotificationCenter. Вы можете подписаться на эти оповещения и обрабатывать их как вам заблагорассудится. В Debug билде можно писать в лог или показывать уведомления, в Release билде отправлять репорт в систему аналитики.
В итоге, после применения вышеописанного метода, вся работа со строками остается в Interface Builder. Плюс ко всему вы получите дополнительный механизм по отлову «битых» строк.
На этом закончу. Спасибо за внимание. Пользуйтесь на здоровье!
Автор: Денис