Работая на платформе Android достаточно часто сталкиваешься с необходимостью заполнения строковых констант strings.xml. Всё бы было ничего, но правила которые налагает Android на содержимое этих файлов весьма ограниченны. Некоторые из них очевидны, например знак апострофа необходимо экранировать знаком слеша, другие же связанны как с устройством XML формата так и с особенностями Android. Об этих особенностях и пойдёт речь в статье ниже.
Итак, допустим у нас есть набор строк полученных от заказчика в виде текстового файла или iOS PLIST или даже документа Excel. В моём случае это были строки импортированные в проект из PhraseApp. Поля name заполняются автоматически на базе имён которые дал разработчик локалей и набора правил PhraseApp. Стоит отметить что для других платформ, на которых нет стольких ограничений экспорт происходит успешно, что значительно ускоряет разработку и локализацию в многоплатформенной среде. К несчастью искать ошибки в строках с использованием AndroidStudio всё так же неудобно как и много версий назад, студия просто не показывает номера ошибочных строк в XML, поэтому Eclipse всё ещё может помочь усталому разработчику. Итак, познакомимся с подводными камнями поближе.
Пробелы в именах
<string name="confirm exit">Are you sure?</string>
пробел между confirm и exit не допустим, заменяем его на знак подчёркивания
<string name="confirm_exit">Are you sure?</string>
Точки в именах
В общем виде точки в именах допустимы, среда с удовольствием примет вариант
<string name="gender.male">Male</string>
но дьявол, как известно кроется в деталях, попробовав собрать массив строк или plurals с точкой в имени мы получим ошибку вида
Error retrieving parent for item: No resource found that matches the given name 'age'.
<string-array name="age.array">
<item>0-13</item>
<item>14-18</item>
<item>19-21</item>
<item>22+</item>
</string-array>
для всех string-array и plurals меняем в именах точку на то же подчёркивание
<string-array name="age_array">
Ключевые слова Java в именах
Это не совсем очевидно, но использовать в именах ключевые слова Java нельзя. Следующая строка не даст проекту собраться.
<string name="else">Else</string>
Выход прежний — снабдим else знаком подчёркивания в начале _else и проект будет снова собираться.
Форматированный вывод
Форматирование вывода значительно облегчает читабельность исходных кодов, но и здесь нас подстерегают проблемы. Выведем в одной строке одновременно два и больше значения с подстановкой, к примеру — время от и до. Получим ошибку
Multiple substitutions specified in non-positional format; did you mean to add the formatted=«false» attribute?
<string name="time_to_time">Time from %s to %s</string>
добавление аттрибута formatted=«false» исправляет ситуацию, но ведь у нас ещё есть plurals и string-array, усложняем пример и пробуем собрать
<plurals name="api_arror">
<item quantity="one">Error %s occured</item>
<item quantity="other">%s errors ocurreds in %s</item>
</plurals>
получаем сразу две ошибки
Multiple substitutions specified in non-positional format; did you mean to add the formatted=«false» attribute?
Found tag </item> where </plurals> is expected" attribute?
Добавление аттрибута formatted=«false» уже ничего не исправляет, но Google подсказывает что параметры необходимо задать с их порядковыми номерами, дабы система понимала что и куда подставлять. Принцип прост, параметры нумеруются начиная с единицы и используют знак доллара. Для первого параметра замена будет %1$s – для второго %2$s, для последующих %(i+1)$s. Правильная запись
<plurals name="api_arror">
<item quantity="one">Error %s occured</item>
<item quantity="other">%1$s errors ocurreds in %2$s</item>
</plurals>
Вернёмся к PhraseApp, при использовании плагина текущей версии все вышеперечисленные ошибки поджидают неосторожного разработчика, в том случае если строки создавались без учёта специфических требований Android. Эти ошибки требуют исправления при каждом обновлении ресурсов. Это явно неэффективно. Напрашивается автоматизация. К несчастью написать вменяемый скрипт для sed у меня не получилось. Кроме того пользователи Windows не горят желанием искать этот самый sed и добавлять в пути запуска.
В качестве альтернативы предлагаю вашему вниманию очередной велосипед по работе со строками на Java. Проект открыт и лежит на GitHub. Весь код находится в одном файле игнорируя ООП, работа с string.xml ведётся в лоб, без DOM или SAX. В утилите три режима работы: скормив на вход каталог мы обработаем все строки во всех подкаталогах в файлах с именем strings.xml, скормив два файла — возьмём первый и скопируем во второй с исправлением ошибок, а подставив один файл мы его обработаем и перезапишем. Для запуска достаточно положить собранную утилиту в корень Eclipse или AndroidStudio и выполнить с параметром в виде точки
java -jar AndroidCleanFixXml.jar.
Для пользователей PhraseApp обойтись без правки исходных текстов утилиты может не получиться. Часто те кто набивает локали создают шаблон для форматного вывода в виде %{location} — %{duration}, такая подстановка не будет работать с String.format, поэтому все их приходится заменять. Так как в общем виде понять какой тип данных будет подставляться невозможно, то в коде присутствует три статический константы PHRASE_FLOAT, PHRASE_DECIMAL и PHRASE_STRING. Все встреченные в ваших файлах локализации подстановки необходимо руками добавлять в константу.
Никаких временных файлов при работе не создаётся — поэтому помните о бекапах или коммите в репозиторий перед использованием. Приятного использования, пишите issue на github, постараюсь исправлять по мере сил.
Автор: shadwork