Процесс Локализации Ios-Приложения В Vivid Money

Добро пожаловать! Если ваше приложение поддерживает разные языки, то вы наверняка сталкивались с проблемами, связанными с локализацией: ошибки в написании ключей, пропущенные значения языков, необходимость пересборки приложения в случае экстренных правок перевода.

Не самые приятные моменты разработки, правда? В этой статье речь пойдет о том, как работает локализация в Vivid.Money: поговорим о том, какой инструмент локализации мы выбрали, с какими проблемами столкнулись и как их решили.

Для лучшего понимания специфики проекта вы можете ознакомиться с этот материал, и мы предлагаем начать эту статью без лишних слов.



Какой метод локализации вы выбрали?

Наши требования к локализации включают следующее:
  • Синхронизация между платформами (iOS, Android, Backend) для единого источника истины;
  • Проверка правильности написания используемых ключей при компиляции для исключения возможности допущения опечатки в названии ключа;
  • Разработчикам не нужно самостоятельно внедрять локализации для разных языков, чтобы разработчики могли тратить больше времени на то, что им следует делать — реализацию функций;
  • Простота взаимодействия с переводчиками;
  • Возможность изменения значений ключей без пересборки приложения.

Для локализации приложений Apple предоставляет стандарты расширения файлов.

.

строки И .

stringsdict .

Файлы .

строки используется для хранения строковых данных в виде ассоциативного массива («ключ»: «значение»), поддерживаются заполнители.

Файлы .

stringsdict используются для хранения форм множественного числа значений ключей и представления списка.

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

Вы можете просмотреть стандартный процесс локализации Apple по адресу: связь .



Недостатки такого подхода очевидны, если вспомнить наши требования к локализации: нет единого источника истины между платформами, нет проверки правильности используемых ключей на этапе компиляции, обновление ключей возможно только на стороне клиента , разработчики должны сами делать локализации для разных языков.

А если для решения последнего указанного недостатка можно использовать формат экспорта локализации XLIFF (локализуемый стандарт обмена данными на основе языка разметки XML), то другие проблемы не могут быть решены стандартными средствами.



А использование экспорта локализации не будет особо полезным в большом проекте, поскольку нет централизованного хранилища ключей локализации и придется как-то автоматизировать экспорт.

Уже из первого и последнего требований к локализации становится понятно, что нам нужен какой-то бэкенд для хранения и обновления локализации, и чтобы не изобретать велосипед в виде собственного сервиса управления переводами, мы решили использовать уже имеющиеся существующие на рынке.

Для себя мы определили критерии, которые важны для нас при использовании сервисов по локализации, и на их основе сравнили некоторые сервисы локализации, таблица которых представлена ниже:

Локализовать Фраза OneSky POEditor
Разделение ключей по платформам + - - -
Поддержка тегов + + - +
Поддержка спецификаторов множественного числа и формата.

+ + +/- +
Мобильный SDK + + + -
Удобный интерфейс + - - -
Найти/объединить дубликаты + - - -
Находим ошибки в тексте.

+ - - -
Поддержка машинного перевода + +/- - -
Цены https://lokalise.com/pricing https://phrase.com/pricing/ https://www.oneskyapp.com/pricing/ https://poeditor.com/pricing/
Мы также попытались разобраться с сервисом Краудин , но безуспешно.

В настоящее время на рынке существует множество сервисов для решения проблемы локализации, но большинство из них имеют более-менее одинаковый базовый функционал, и мы в первую очередь обращали внимание на простоту использования и интуитивную понятность интерфейса.

Исходя из этого, мы решили использовать Lokalise, так как он показался нам наиболее удобным из сравниваемых, что имело решающее значение.

Кроме того, у него есть приятные бонусы, такие как проверка орфографии и встроенный машинный перевод от разных провайдеров, что весьма удобно на этапе разработки функций, до появления реальных переводов.

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

Для интеграции с проектом доступны следующие варианты:

  • Скачайте с сервиса архив с локализациями и добавьте его в свой проект;
  • Используйте скрипт Fastlane, предоставляемый сервисом;
  • Используйте API/CLI;
  • Используйте SDK.
Вы можете узнать больше об интеграции Lokalise Здесь .





Как мы оптимизировали работу с локализацией

Всегда удобнее использовать нативные инструменты, поэтому изначально мы хотели использовать SDK.

Но у этого подхода есть один неприятный момент: после обновления переводов в сервисе пакет локализации необходимо сгенерировать и опубликовать вручную, чтобы он стал доступен на клиенте с помощью SDK.

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

Чтобы решить эти две проблемы, мы использовали прокси-API, который автоматически загружает локализации из Lokalise при каждом изменении ключа, и мобильные клиенты обращаются к нему за пакетами.

Это также позволило нам использовать обработку платформы и пользовательскую логику при создании пакетов.

Таким образом, мы не можем использовать Lokalise SDK, и нам нужно скачать пакет локализации вручную.

Мы делаем это в двух случаях: во время сборки и при каждом запуске.

Кстати, Lokalise SDK делает это только при запуске, и есть вероятность, что загрузка прервется и пользователь увидит вместо локализованных строк названия ключей.





Скрипт для скачивания локализации перед сборкой

Одним из этапов сборки приложения является загрузка архива бандла с локализациями с параметризованной средой (отладка/релиз), в зависимости от которой генерируется ссылка на скачанный бандл.

После скачивания бандла с локализациями проверяем .

строки И .

stringsdict файлы на валидность с помощью утилиты plutil (утилита списка свойств), которая проверяет синтаксис: сбалансированы ли кавычки и т. д. Ниже приведен код этого скрипта на Ruby:

 
 
 
  def self.valid_bundle?(path)

puts 'Validating localization bundle.'

strings = Dir["#{path}/Contents/Resources/*.

lproj/*.

strings"] stringsdict = Dir["#{path}/Contents/Resources/*.

lproj/*.

stringsdict"] is_valid = true (strings + stringsdict).

each do |path| stdout, stderr, status = Open3.capture3("plutil -lint #{path}") unless status.exitstatus == 0 is_valid = false line = stderr.strip[/on line ([0-9]*)/, 1] puts "***********************************************".

red puts "Found the invalid string in file at path: #{path}".

red puts "The invalid string: #{File.readlines(path)[line.to_i-1].

strip}".

red end end puts "***********************************************".

red unless is_valid is_valid end

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





Загрузка локализации при запуске

Чтобы избежать необходимости пересборки проекта при обновлении файлов локализации, мы загружаем вышеупомянутые пакеты вместе с файлом конфигурации приложения и некоторыми фундаментальными бизнес-объектами каждый раз при запуске приложения.

Сорт ЛокализацияFetcher загружает метаданные о бандле: ссылку на сам бандл и номер версии.

Если версия метаданных кэшированной локализации совпадает с только что полученной, мы ничего не делаем — у нас есть текущая версия локализации.

Если версия в кеше не совпадает с полученной, то скачайте архив с бандлом, разархивируйте его и сохраните локализации в каталог Documents, сохранив новые метаданные о локализации.

Чтобы обновленные файлы локализации можно было использовать сразу, а не после перезапуска приложения, нам необходимо сделать недействительным кеш для Локализовать.

bundle .

Делается это следующим образом: после сохранения пакета локализации в каталог Documents, циклически перебираем все каталоги локализации.

.

lproj в ресурсах бандла и в каждом меняем имена файлов Локализуемые.

строки И Локализуемый.

stringsdict на Локализуемый.

nocache.strings И Localizable.nocache.stringsdict , соответственно.

Тогда остается только указать «Локализируемый.

nocache» как параметр имя_таблицы при вызове метода localizedString (forKey: значение: таблица:) в пакете локализации, загруженном при запуске.





Скрипт для генерации кода

Нам часто приходилось сталкиваться со стандартными проблемами, возникающими при работе с локализацией: об отсутствующих локализациях ключей мы могли узнать уже во время выполнения, а от ошибок в написании самих ключей никто не был застрахован, поэтому для получения проверок при компиляции необходимо автозаполнение и строгой типизации мы решили сгенерировать неизменяемую структуру на основе бандла с локализациями.



R.swift для этих нужд не подошел, поскольку, во-первых, он может генерировать только то, что уже есть в корне основного проекта (а локализации мы скачиваем при сборке и добавляем в комплект модуля локализации), во-вторых, Размер файла при использовании он оказывается довольно большим и содержит много сгенерированного контента, который нам не нужен.

По этим причинам мы решили написать собственный сценарий.

Скрипт генерации структуры с локализацией достаточно прост: файл определяется как выходной Локализация.

swift в модуле локализации, после чего файлы локализации Локализуемые.

строки И Локализуемый.

stringsdict анализируются и записываются в выходной файл в виде статических констант и методов.





Как строки локализуются в приложении?

Константы и методы, генерируемые скриптом, выглядят так: public static let localization_var = "localization_key".

localized public static func localization_method(_ value1: String) -> String { "localization_method_key".

localized(with: value1) }

Вычисляемое свойство локализованный следующее: var localized: String { let localLocalisation = Self.localLocaliseBundle?.

localizedString( forKey: self, value: nil, table: nil ) let serverLocalisation = Self.makeServerLocaliseBundle()?.

localizedString( forKey: self, value: localLocalisation, table: “Localizable.nocache” ) return serverLocalisation ?? localLocalisation ?? self }

  • Сначала мы пытаемся сгенерировать локализованную строку для ключа из локализации в основном комплекте модуля локализации;
  • Затем локализованную строку из пакета локализации, хранящегося в Документах, если таковой имеется;
  • Возвращаем полученное значение, либо сам ключ, если он отсутствует как в локальной, так и в серверной связке.

Метод, локализующий строку с аргументами ( локализованный(с:) ), представлено ниже: func localized(with parameters: CVarArg.) -> String { let correctLocalizedString: String = { if localized.starts(with: "%#@") { return localized } return localized .

replacingOccurrences(of: "%s", with: "%@") .

replacingOccurrences( of: "(%[0-9])\\$s", with: "$1@", options: .

regularExpression ) }() guard !correctLocalizedString.isEmpty else { return self } return String(format: correctLocalizedString, arguments: parameters) }

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

Стоит обратить внимание на некоторые детали реализации: во-первых, в файле Локализуемый.

stringsdict

собственные спецификаторы формата уже присутствуют, поэтому нет необходимости их заменять; во-вторых, что более важно, если вы попытаетесь сделать это в нынешнем виде, то локализация множественного числа перестанет работать.

Дело в том, что при вызове localizedString (forKey: значение: таблица:) для формы множественного числа, когда данные взяты из Локализуемый.

stringsdict, возвращает объект внутреннего типа __NSLocalizedString .

Это необходимое условие для корректной инициализации возвращаемой строки в случае форм множественного числа.

В свою очередь, используя опцию .

regularExpression при звонке replaceOccurities(of:with:options:) приводит к созданию новой строки с типом Нить в процессе замены вхождений шаблона регулярного выражения, и метод вернет именно это, а не строку типа __NSLocalizedString .

В итоге общая схема работы с локализацией выглядит так:

  1. Поставлена задача разработать фичу;
  2. Разработчики добавляют в Lokalise необходимые для макетов ключи с базовым переводом на английский, аналитики отправляют их на перевод на другие языки;
  3. Разработчик, запустив функцию в работу, запускает скрипт обновления проекта, в ходе которого загружаются файлы локализации для среды отладки, из которых генерируется файл.

    Локализация.

    swift ;

  4. Фича доработана, код проверен, отправлен на тестирование и исправлены все найденные ошибки;
  5. Аналитики добавляют полученные переводы на другие языки в Lokalise;
  6. После того как функция протестирована и готова к выпуску, ключи вручную развертываются в производственной среде Lokalise аналитиками из команды, ответственной за эту функцию;
  7. При сборке релизной версии приложения значения ключей загружаются из производственной среды Lokalise;
  8. Если существующие ключи впоследствии обновляются, пакет локализации загружается при каждом запуске приложения.

Для развертывания ключей в рабочей среде Lokalise в Gitlab-репозитории прокси-сервиса запускается собственный конвейер, в переменную которого можно передавать команды добавления ключей либо со списком названий, либо со списком тегов, по которым ключи сгруппированы или перенести все ключи, а также можно передать команду на удаление ключей.





Проблемы и их решения

Несмотря на все преимущества удаленной локализации, она не лишена проблем.





Разная локализация на iOS и Android

Проблема в том, что некоторые вещи, касающиеся локализации, в iOS и Android выполняются по-разному.

Например, в iOS заполнитель для строкового аргумента записывается как %@ , тогда как в Android это похоже на %s .

Для таких случаев, исходя из данных об ожидаемом распределении пользователей по платформам, было принято решение сначала локализовать Android в Lokalise, а на клиенте заменить его на iOS.



Критические изменения в файле локализации

Часто возникали ситуации, когда кто-то допускал ошибки в локализованной строке.

Например, я использовал двойные кавычки без экранирования.

В этом случае файл локализации становился недействительным и локализация вообще не работала.

Нас такая ситуация не устроила и было решено после скачивания проверить файлы локализации, код для которых был приведен выше.

Это позволяет нам не только узнать, что локализация недействительна, но и идентифицировать файл и строку с ошибкой.





Локализация не была перенесена

Как говорилось ранее, у нас есть 2 «экземпляра» локализации — отладочная и продакшн.

Изменения в отладочную версию могут вносить те, у кого есть к ней доступ, а в продакшене локализация происходит посредством миграции — специального процесса, передающего необходимые ключи из отладочной в продакшн.

Иногда бывают ситуации, когда мы делаем релизную сборку, но в рабочей копии еще нет некоторых ключей.

Поскольку мы не хотим попасть в ситуацию, когда у пользователей нет локализации, нам нужно как-то проверить наличие необходимых ключей.

В этом нам помогла генерация кода из предыдущей главы: при загрузке в проект локализации для релизной версии приложения генерируется новый файл Локализация.

swift , а при сборке просто получаем ошибку компиляции со списком недостающих ключей.

Решение может быть спорным, но оно гарантирует надежную синхронизацию используемых ключей и их значений из требуемой среды.





Заключение

И вот, в итоге, для локализации мы выбрали сервис Lokalise, используя наш прокси-сервер для генерации и загрузки пакета локализации.

Мы скачиваем его при сборке и на его основе генерируем неизменяемую структуру с ключами, а при необходимости также скачиваем ее при каждом запуске приложения.

Конечно, были некоторые проблемы, такие как платформозависимый синтаксис плейсхолдеров, ошибки в файле локализации и синхронизации отладочной и рабочей версий локализаций, но нам удалось с ними справиться.

Возможно, это не последние проблемы, с которыми мы столкнемся при дальнейшем развитии проекта, но тем интереснее их решать.

Спасибо за уделенное время, надеемся, что данная статья оказалась полезной и позволила вам узнать что-то новое или породила новые мысли в решении проблемы локализации.

Вот и все, ребята! Теги: #iOS #разработка iOS #Разработка мобильных приложений #Swift #скрипты #локализация продукта #локализация #vivid.money #lokalise

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2006-04-17 12:47:52
Баллов опыта: 496
Всего постов на сайте: 2
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.