[Локализация продуктов, Разработка мобильных приложений, Разработка под iOS] Реализация наследования в файлах локализации iOS

Автор Сообщение
news_bot ®

Стаж: 6 лет 3 месяца
Сообщений: 27286

Создавать темы news_bot ® написал(а)
20-Июл-2020 12:33


Приветствую, дорогие хабражители!
Сегодня я хочу поделиться интересным опытом в решении проблемы локализации. В iOS локализация устроена достаточно удобно с точки зрения одного таргета, либо нескольких таргетов, в которых ключи в localizable.strings не сильно повторяются. Но всё становится сложнее, когда у вас появляется с десяток таргетов, в которых больше половины ключей повторяются, но при этом частично имеют разные значения, а так же есть набор уникальных для конкретного таргета ключей.
Для тех, кто с этим пока не сталкивался, объясню проблему подробнее на примере.
Допустим, у нас есть большой проект, в котором 90% общего кода и 3 таргета: MyApp1, MyApp2, MyApp3, которые имеют некоторое количество специфичных экранов, а так же каждый имеет своё название и тексты. По сути таргет представляет из себя самостоятельное приложение. Каждый из них должен быть переведен на 10 языков. При этом мы НЕ хотим добавлять ключи локализации типа app1_localizable_key1, app2_localizable_key1 и т.д. Хотим, чтобы в коде всё было красиво и локализация происходила одной строчкой
NSLocalizedString(@"localizable_key1", nil)

Без всяких if и ifdef, чтобы при добавлении нового таргета нам не пришлось искать по всему коду огромного проекта места с NSLocalizedString и прописывать там новые ключи. Так же хотим, чтобы часть ключей была привязана к специфичным экранам таргета, т.е. были ключи app2_screen1_key, app3_screen2_key.
Штатными средствами Xcode сейчас можно сделать следующее:
  • Скопировать общую часть localizable.strings в каждый таргет, при этом мы получим 3 копии этих файлов.
  • Добавить в соответствующие localizable.strings ключи специфичные для конкретного таргета.

Каким проблемы мы получаем:
  • Добавить новый общий ключ в проект достаточно накладно. Число мест равняется числу таргетов помноженному на число языков. В нашем примере это 30 мест.
  • Есть вероятность ошибки, когда добавили строку в 1-2 текущих таргета, с которыми идёт активная работа, а через год решили воскресить еще один или несколько таргетов. Придется вручную синхронизировать между собой локализации, либо писать для этого скрипт. А если была проявлена некоторая неряшливость при добавлении или мерже веток, и общие ключи смешаны со специфичными, то тут будет самый настоящий квест.
  • Объём файлов локализации. Они все постоянно растут, это затрудняет работу с ними и увеличивает шансы конфликта при мерже веток.

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

Для нашего примера имея общий файл localizable.strings со строками
"shared_localizable_key1" = "MyApp title"
"shared_localizable_key2" = "MyApp description"
"shared_localizable_key3" = "Shared text1"
"shared_localizable_key4" = "Shared text2"

Хотелось бы иметь файл localizable_app2.strings, в котором были бы ключи
"shared_localizable_key1" = "MyApp2 another title"
"shared_localizable_key2" = "MyApp2 another description"
"app2_screen1_key" = "Profile screen title"

Т.е. организовать в файлах локализации принцип наследования.
К сожалению Xcode не заточен под это, по-этому пришлось изобретать свой «велосипед», который долго не хотел ехать из-за того, что Xcode то тут, то там вставлял палки в колеса.
Мы имеем проект с 18 таргетами и 12 языками. И это не шутка, проект действительно большой и такое количество таргетов там необходимо. Каждый раз, когда нам нужно добавить новый общий ключ для перевода, мы имеем дело с 216 файлами локализации. Это отнимает достаточно много времени. А добавление нового таргета приводит к тому, что нужно скопировать в него еще 12 localizable.strings. В общем в какой-то момент мы поняли, что так больше жить нельзя и нужно искать решение.
Не буду долго рассказывать про все методы, которые я успел опробовать в процессе, перейду сразу к рабочему решению.
Итак, для начала нам нужно было найти все общие ключи. Это можно сделать с помощью скрипта, не буду вдаваться в подробности, это достаточно тривиальная задача.
Далее, когда мы получили общий (базовый) файл локализации, а точнее 12 физических файлов, а так же набор файлов для каждого таргета, идем в Xcode, добавляем туда все файлы. При этом не прикрепляем файлы к какому-либо таргету, т.е. в правой панели в разделе Target Membership не должно быть отметок.

Эти отметки мы поставим только для файла, который будет результатом работы скрипта по сборке файлов.
Далее начинается тот самый «велосипед»:
  • Создаём в корне папку Localization, там будет лежать скрипт build_localization.py.
  • Создаём рядом со скриптом папку Localizable. В неё скрипт будет генерировать файлы localizable.strings.
  • Копируем в папку Localizable базовую локализацию.


Она нам нужна просто для того, чтобы корректно добавить ссылку на файлы в проект, и чтобы Xcode правильно их распознал. Иначе он не будет их использовать для поиска ключей. Например, если создать папку Localizable с правильно разложенными файлами localizable.strings внутри, и добавить в проект как ссылку на папку (create folder references), то не смотря ни на что Xcode не поймет, что мы дали ему ключи локализации. По-этому берем папку Localizable, перетаскиваем как группу (create group) и снимаем галочку copy items if needed, чтобы получилось как на картинке ниже.

Удаляем папку Localizable и вносим её в исключения для гита. Потому что результат работы скрипта нам в гите не нужен, он будет меняться для каждого таргета и засорять коммиты.
Теперь нам нужно добавить скрипт в фазу сборки. Для этого в Build Phases нажимаем New Run Script Phase и прописываем наш скрипт с параметрами. 

python3 ${SRCROOT}/Localization/build_localization.py -b “${SRCROOT}/BaseLocalization" -s "${SRCROOT}/Target1Localization" -d "${SRCROOT}/Localization/Localizable"

b — это папка с базовой локализацией, s — локализация текущего таргета, d — папка результата.
Перемещаем новую фазу вверх, она должна быть не ниже фазы Copy Bundle Resources. Т.е. сначала скрипт генерирует файлы, а уже потом они забираются в бандл.

Теперь важно сообщить Xcode, что в процессе выполнения скрипта меняются файлы, иначе при сборке он будет выкидывать ошибку, что не смог найти файлы. Причем ошибка будет только на чистой сборке, и не сразу будет понятно в чем проблема. В фазе сборки добавляем в output files все файлы локализации

Это нужно проделать для каждого таргета. Проще всего это сделать открыв проект с помощью текстового редактора, потому что Xcode не сумеет скопировать/вставить фазу между таргетами. Соответственно параметр скрипта -s для каждого таргета будет свой.
Теперь при каждой сборке скрипт будет брать базовый файл локализации, накатывать на него изменения из файла таргета (добавлять, перезаписывать ключи) и генерировать локализацию в папку Localizable, которую iOS будет использовать для поиска ключей.
В целом получили то, что и планировалось при реализации механизма наследования:
  • Общие ключи лежат в одном файле и не мешаются в других. Время на процесс внесения новых ключей сокращено в 18! раз.
  • Ключи, относящиеся к конкретному таргету, лежат в соответствующем файле.
  • Размер файлов значительно снизился. Избавились от захламления повторяющимися строками.
  • Процесс добавления нового языка в проект так же значительно упрощён.
  • При создании нового таргета не нужно копировать локализацию с кучей ненужных строк. Создаём новый файл localizable.strings и добавляем туда только нужное для этого таргета.
  • Если решили реанимировать старый таргет, то со строками вообще ни чего делать не надо, всё подтянется из базового файла.
  • Скрипт не захламляет гит, результат работы остаётся локально и его можно безболезненно удалить.

Готовый скрипт можно взять тут: github.com/iBlacksus/iOSLocalizationInheritance
Не претендую на идеальность скрипта, пул-реквесты приветствуются.
===========
Источник:
habr.com
===========

Похожие новости: Теги для поиска: #_lokalizatsija_produktov (Локализация продуктов), #_razrabotka_mobilnyh_prilozhenij (Разработка мобильных приложений), #_razrabotka_pod_ios (Разработка под iOS), #_objectivec, #_ios, #_localization, #_swift, #_python, #_programmirovanie (программирование), #_xcode, #_lokalizatsija_produktov (
Локализация продуктов
)
, #_razrabotka_mobilnyh_prilozhenij (
Разработка мобильных приложений
)
, #_razrabotka_pod_ios (
Разработка под iOS
)
Профиль  ЛС 
Показать сообщения:     

Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы

Текущее время: 19-Май 10:44
Часовой пояс: UTC + 5