Pull to refresh

Удобная локализация iOS приложений в Interface Builder

Reading time4 min
Views8K


Не так давно я опубликовал подробную инструкцию по использованию LocoLaser — утилиты для локализации Android и iOS приложений в Google Sheets. Мне бы хотелось продолжить тему локализации и обратить больше внимания на iOS приложения. В отличии от Android, в iOS разработке есть ряд мелких но неприятных моментов, которые, в сумме, могут привести к совсем не мелким проблемам.

Сегодня я хочу уделить особое внимание Interface Builder-у. Все мы знаем, он не идеален. Но это единственное, что у нас есть и с этим приходится мириться. В этой статье я расскажу о главной проблеме, с которой вы можете столкнуться при локализации приложений в Interface Builder, а также расскажу как с ней можно справиться.

Суть проблемы


Когда вы переводите Storyboard или XIB файл, помимо основного файла с разметкой, создаются дополнительные файлы с ресурсными строками. Эти ресурсные файлы принято выгружать в специальные таблицы и отдавать переводчикам. Беда заключается в том, что ключи для строк, в этом файле, строятся на основе Object ID, которые генерируются автоматически и нет ни какой возможности на них повлиять. Если вас угораздило скопировать или вырезать а затем вставить какой либо View, Interface Builder сгенерирует новые идентификаторы и перевод будет потерян.

Получается, вы не можете закрепить перевод за какой-то конкретной View. Если вы ее переместите, перевод не переместится. Например, у вас на storyboard есть ViewController который вы решили хранить отдельно. Вы создаете новый XIB файл, перемещаете туда ViewController, но переводы не перемещаются. Мало того, для каждой View создастся новый идентификатор и вам придется подгонять идентификаторы строк вручную. Просто так скопировать переводы не получится.

Кроме того, отсутствие возможности повлиять на идентификаторы строк не позволяет иметь общую базу строк сразу на несколько платформ. Что, в свою очередь, приведет к невозможности использовать утилиты, генерирующие ресурсные файлы под разные платформы.

Каждый решает эту проблему как может. Помню, 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.


  1. Выбираем UILabel и переходим на вкладку «Identity Inspector»;
  2. Жмем кнопку добавления атрибута в «User Defined Runtime Attributes»;
  3. Указываем ключ атрибута «lzText», тип: «String», значение: «scr_main_txt_example»

И это все. Больше не нужно засорять код ничем лишним. Вы можете не бояться, что перевод или ссылка на View потеряется при копировании или перемещении в другой контейнер. Атрибуты копируются вместе с View. Единственное, что останется неизмеренным — это хранение всех строк в одном файле Localized.strings.

UPD:
Но это не все. DjPhoeniX предложил сделать еще лучше. И нам не потребуется почти ничего менять. Нужно лишь добавить @IBInspectable перед объявлением свойства.
Измененный Extensions.swift
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. Плюс ко всему вы получите дополнительный механизм по отлову «битых» строк.

На этом закончу. Спасибо за внимание. Пользуйтесь на здоровье!
Tags:
Hubs:
+10
Comments17

Articles