Работа со строковыми ресурсами Android

Понимание, что используемый в программе текст это отдельный ресурс, такой же как изображения и звук, приходит на сразу. Но стоит несколько раз поменять имя программы в паре десятков файлов или заняться исправлением однотипной синтаксической ошибки в пяти, шести разных местах и необходимость хранить строки отдельно от кода становиться очевидной.
В Android работа со строковыми ресурсами сделана очень удобна и не вызывает поначалу никаких сложностей. В официальной документации она описана в статье String Resources. В файле project\res\values\strings.xml задаем строку и ее имя после чего в Activity загружаем строку по этому имени.
strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="appName">Project Name</string>
</resources>

ProjectActivity.java
public class LoadingActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        String applicationName = getString(R.string.appName);
    }
    //...
}

Эту же строку можно использовать в xml файле разметки формы (layout resource). Как это реализовано можно посмотреть, создав новый проект и открыв файл main.xml.

main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >
    <TextView
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content" 
        android:text="@string/appName" />
</LinearLayout>

Описание ресурсов


Теперь несколько более интересных вещей, которые тоже описаны в документации, но используют которые уже не так широко.
Файлов со строками (впрочем как и с остальными ресурсами) может быть несколько. Главное, чтобы они находились в папке project\res\values\, имели расширение xml, имя состояло из строчных английских букв, цифр и не содержало пробелов. Внутреняя структура должна повторять структуру файла strings.xml. Для чего это может использоваться? Я в одном файле храню все строки, которые надо будет переводить, во втором строки, которые не требуют перевода, в третьем храню константы, которые использую для составления запросов к веб серверу.
XML файл имеет небольшие ограничения на формат хранимых данных. В открытом виде в нем нельзя использовать символы '&', '<'. Задать эти символы можно используя специальную последовательность
< — &lt;
& — &amp;
Дополнительные ограничения накладываются на работу с одинарными и двойными апострофами: использовать непарный апостроф просто так нельзя. Есть несколько методов решения этой проблемы, самым простым из них является — добавление обратного слеша перед знаком апострофа:
' — \' или &apos;
" — \" или &quot;
Если текст содержит html теги и в нем встречается неразрывный пробел &nbsp;, то его надо заменить на &#160;.

Загрузка строк


Если надо задать текст из ресурсов одному из элементов интерфейса, то нет необходимости заранее его загружать. Вторая и третья строка в приведенном примере работают совершенно одинаково.
TextView sectionHeader = (TextView)findViewById(R.id.sectionHeader);
sectionHeader.setText(getString(R.string.sectionPhone));
sectionHeader.setText(R.string.sectionPhone);

Метод getString удобно использовать, когда в строку из ресурсов нужно внести дополнительные данные перед дальнейшим использованием. В этом случае в ресурсы помещается форматированная строка и дополнительные параметры для нее указываются прямо в getString, без дополнительного обращения к методу String.format. Примеры разделенные чертой приводят к одним и тем же результатам:
int sectionId = 10;

String header = getString(R.id.headerSection);
sectionHeader.setText(String.format(header, sectionId));
//-----------------
sectionHeader.setText(getString(R.id.headerSection, sectionId));

Для формата строки используется следующая конструкция: %X$F.
X$ — номер подставляемого параметра. В основном тексте они обычно идут по порядку 1$, 2$, но в локализованных ресурсах могут меняться местами. Также позволяется использовать один парметр в строке несколько раз.
F — обычный идентификатор формата, такой 's', 'd'. Их полный список, включая форматрирование даты, описан в документации класса Formatter
Если в строке используется только один параметр, то X$ можно опустить. Если считать параметры неудобно, а проблем с локализацией не предвидится, то можно вернуться к стандартной схеме формата строки — для этого в описание элемента нужно добавить атрибут formatted со значением false. Следующие две строки форматируют текст одинаковым образом:
<string name="score_correct">%1$d - %2$d</string>
<string name="score_simple" formatted="false">%d - %d</string>

Загрузить строку по ее имени можно также как и любой другой ресурс. Для этого сначала нужно с помощью метода getIdentifier по имени строки найти ее id, а с этим номером уже работать обычным сопособом. Следующий пример загружает строку с именем «score_correct».
int strId = getResources().getIdentifier("score_correct", "string", getPackageName());
String strValue = getString(strId);

Android позволяет хранить в ресурсах массивы строк. Для этого используется тег string-array, который содержит внутри элементы item с конкретными строками. Вот сокращенный пример из документации Android, который иллюстрирует задание массива.
strings.xml
<string-array name="planets_array">
    <item>Earth</item>
    <item>Mars</item>
</string-array>

Кроме очевидного применения — удобная загрузка данных, string-array часто используют для иницализации UI элементов с выпадающим списком значений: Spiner и ListPreference. В этом случае обычно требуется использовать одну из строк массива, как значение по умолчанию. Сослаться в ресурсах на конкретный элемент массива мы не можем, но из ситуации можно выйти задействовав псевдонимы для ресурсов.
Элементы массива инициализируются, как обычные строки, а элементы item содержат только ссылку на них. Такая инициализация на самом деле черезвычайно удобна и я использую ее даже в тех случаях, когда обращение к конкретной строке не планируется — это гарантирует, что при локазации все массивы будут одинакового размера, даже если часть строк в них не будет переведена. Само описание массива при этом удобно вынести в отдельный ресурсный файл.
Переписанный с использованием псевдонимов пример выглядит так:

strings.xml
<string name="earth">Earth</string>
<string name="mars">Mars</string>

<string-array name="planets_array">
    <item>@string/earth</item>
    <item>@string/mars</item>
</string-array>

Полезное замечание: до версии Android 2.3 в реализации загрузки string-array была ошибка, которая позволяла загружать максимум только 512 элементов.

Системные строки


Android дает доступ к нескольким строкам, хранящимся в системных ресурсах. Часть из них довольно специализированы: заголовок сообщения об ошибке проигрывания видео. Но такие строки как «Ok» или «Cancel» удобно использовать практически в каждом проекте. Использовать системные строки почти также легко, как и свои собственные ресурсы — надо перед идентификатором ресурса string через двоеточие указать имя android. Следующий пример описывает кнопку с надписью «Cancel» с использованием строки из системных ресурсов.

main.xml
<Button
    android:text="@android:string/cancel"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />


Локализация строк



Базовый механизм локализации ресурсов в Android позволяет легко решить практически все задачи с локализацией строк. Полное описание в документации Android можно прочесть здесь. В ресурсах вам необходимо создать новую папку с именем values-xx и поместить туда файлы со строковыми ресурсами из базовой папки values. xx — это двухсимвольный идентификатор языка (список поддерживаемых значений приведен в конце текста). После этого необходимые строки нужно перевести, остальные удалить.
Для более тонкой локализации можно задействовать механизм задания региона. К примеру, у вас в программе есть соглашение пользователя, переведнное на французкий язык, но в нем есть отличия между версией для Франции и версией для Канады. Чтобы реализовать эти отличия надо к папке values-fr добавить название региона в формате rYY, где YY — это двухсимвольное название региона. В данном примере получатся папки values-fr-rFR и values-fr-rCA. В них следует поместить ресурсный файл с необходимой версией соглашения пользователя, а все остальные строки на французском языке оставить в папке values-fr.
Регионы и язык можно указывать без всякой связи друг с другом. Так в папке values-ru-rJP будут храниться русские тексты для жителей Японии.

Так как регионов определенно немного, то в большинстве случаев вместо них удобнее указывать точный идентификатор страны через свойство mcc (мобильный код страны). Список кодов доступен в Википедии и единственный недостаток данного метода — это то, что некоторые страны имеют несколько кодов (США используют 7 номеров, Япония — 2 номера). Переделав пример на использование mcc получаем папки values-mcc208-fr для Франции и values-mcc302-fr для Канады.
Сложности с локализацией начинаются в тот момент, когда вам нужно локализовать приложение на тот язык, который не поддеживается системой. Попасть в такую ситуацию легко, потому, что большинство языков и регионов были добавлены только в 2.3 версии платформы. При этом некоторые производители добавляли поддержку других языков в своих устройствах, но использовали при этом разные коды. В итоге Норвежский язык в Android 2.1 и 2.2 распознается на многих устройствах по коду no, на отдельных не распознается, а начиная с Android 2.3 распознается по коду nb. Такая же ситуация, но меньшего масштаба, с Ивритом, который может встречаться под кодом he и Индонезийским языком — код id.
В этом случае, чтобы не хранить два набора одинаковых строк в разных файлах, можно задействовать псевдонимы для ресурсов, которые уже упоминались раньше.
В этом случае реальный перевод строк храниться в папке values, при этом для их названия используются рабочие имена. В папках values-no и values-nb хранятся ссылки на строки и тут уже используются настоящие названия ресурсов.

values/strings.xml
<resources>
    <!-- Текст по умолчанию -->
    <string name="hello">Hello World</string>
    <!-- Переведенный текст -->
    <string name="hello_translate">Hallo Verden</string>
</resources>

values-no/strings.xml
<resources>
    <string name="hello">@string/hello_translate</string>
</resources>

values-nb/strings.xml
<resources>
    <string name="hello">@string/hello_translate</string>
</resources>

Вы можете создавать ресурсы с какими угодно кодами для языка и для региона. Если операционная система не найдет нужного региона, то она возмет значения для текущего языка. Если не сможет найти язык, то возмет значения по умолчанию из папки values.

Можно загрузить находящиеся в ресурсах строку для языка и региона отличных от установленных на устройстве. Для этого надо создать новый ресурс и задать ему необходимую локаль. Следующий пример загружает строку для французкого языка.
Resources baseResources = getResources();
Configuration config = new Configuration(baseResources.getConfiguration());
config.locale = Locale.FRANCE;
Resources localResources = new Resources(baseResources.getAssets(), baseResources.getDisplayMetrics(), config);

String strFranceValue = localResources.getString(R.string.score_correct);

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

Приложение


Поддерживаемые языки до Android 2.3

Английский (en), Голландский (nl), Испанский (es), Итальянский (it), Китайский (zh), Корейский (ko), Немецкий (de), Немецкий (de), Польский (pl), Русский (ru), Французский (fr), Чешский (cs), Японский (ja)

Поддерживаемые языки начиная с Android 2.3

Арабский (ar), Болгарский (bg), Венгерский (hu), Вьетнамский (vi), Греческий (el), Датский (da), Иврит (iw), Индонезийский (in), Каталонский (ca), Латышский (lv), Литовский (lt), Норвежский-Букмол (nb), Португальский (pt), Румынский (ro), Сербский (sr), Словацкий (sk), Словенский (sl), Тагальский (tl), Тайский (th), Турецкий (tr), Украинский (uk), Финский (fi), Хинди (hi), Хорватский (hr), Шведский (sv)

Поддерживаемые регионы до Android 2.3

Австралия (AU), Австрия (AT), Бельгия (BE), Британия (GB), Германия (DE), Испания (ES), Италия (IT), Канада (CA), КНР (CN), Корея (KR), Лихтенштейн (LI), Нидерланды (NL), Новая Зеландия (NZ), Польша (PL), Россия (RU), Сингапур (SG), США (US), Тайвань (TW), Франция (FR), Чешская республика (CZ), Швейцария (CH), Япония (JP)

Поддерживаемые регионы начиная с Android 2.3

Болгария (BG), Бразилия (BR), Венгрия (HU), Вьетнам (VN), Греция (GR), Дания (DK), Египет (EG), Зимбабве (ZA), Израиль (IL), Индия (IN), Индонезия (ID), Ирландия (IE), Латвия (LV), Литва (LT), Норвегия (NO), Португалия (PT), Румыния (RO), Сербия (RS), Словакия (SK), Словения (SI), Таиланд (TH), Турция (TR), Украина (UA), Филиппины (PH), Финляндия (FI)Болгария (BG), Бразилия (BR), Венгрия (HU), Вьетнам (VN), Греция (GR), Дания (DK), Египет (EG), Зимбабве (ZA), Израиль (IL), Индия (IN), Индонезия (ID), Ирландия (IE), Латвия (LV), Литва (LT), Норвегия (NO), Португалия (PT), Румыния (RO), Сербия (RS), Словакия (SK), Словения (SI), Таиланд (TH), Турция (TR), Украина (UA), Филиппины (PH), Финляндия (FI), Хорватия (HR), Швеция (SE)

Update: Добавлен раздел о массивах строк, локализации строк, системных строках. Убран пример с использованием ресурсов для передачи данных между различными Activity.
Share post

Similar posts

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 25

    +1
    Хорошо бы расширить по теме локализации. Как работать с переводами
      0
      Разумно. На днях добавлю, чтобы все было в одном месте, под рукой.
    • UFO just landed and posted this here
        0
        Работа со строковыми списками используется не часто и, когда используется, то уже опытными андроид разработчиками, для которых разобраться в них не составляет труда.
        Здесь я перечислил те неточности и избыточность в коде, которые я изо дня в день встречаю при ревью кода тех людей, которые только начинают переходить на Android платформу.
          +1
          ИМХО, всё равно на отдельную статью не тянет. Опытный программер (не обязательно опытный Android-программер) при изучении платформы сразу подсечёт, как дяди в примерах хранят строки, и будет делать точно так же.
            0
            А по моему не плохая статья. Во первых не все на хабре такие уже и опытные программеры сидят — тут вообще разный контенгент: вся IT сфера можно сказать. Во вторых о вкусах не спорят, но на мой взгляд полезная статья для новичков.
              0
              И всё же, очень ИМХО, лучше оставить этот удел тем, кто пишет книги и мануалы. А хабр я воспринимаю как журнал — лучше почитать об интересных особенностей той или иной технологии, а не об очевидных и доступных вещах, которые можно при желании за 0.5 мин найти в SDK.
            +1
            Не часто? Да банальнейший же активити настроек скорее всего потребует строковых списков. Так что не такая уж это и «опытная» тема
              0
              Согласен. О Настройках забыл. Обычно проекты, которые попадают в руки, обходятся или вообще без них или ударяются в другую крайность и с помощью стандартных механизмов их Настройки реализовать не получается.
              Если пойму, что можно о них написать, кроме как просто упомянуть, то добавлю эту информацию.
                0
                Например, напиши как из строкового списка в ресурсах получить i-е значение
                  +1
                  Годится. Спасибо за совет.
              0
              да я не думаю что прям нужен опытный разработчик, чтобы строковые списки использовать.
              Вообще, конечно, для статьи скажем прямо — скудны материал. Я ожидал увидеть про многоязычные приложения, мне как раз это сейчас интересно. А тут просто про строки.
              0
              На самом деле, любая информативная и нормально написаная статья является полезной. Связано это с тем, что в мире действительно существуют люди, которые только начинают разбираться в андроид-разработке (что не делает их плохими) и с тем, что при разборе какой-то темы, разным людям могут быть более понятны различные подходы в изложении. Опять-таки, поисковики, да и хабр, были созданы не только для опытных программистов. Наконец, каждый в праве выбирать, что интересно читать именно ему и если это не нужно Пете, то не факт, что это не понадобится Васе.
              Кроме всего вышесказанного, похвален уже сам факт, что человек нашел время и потрудился написать текст, который так или иначе может кому-то помочь.
              0
              ИМХО, было бы здорово рассказать собственно про класс Resources. В нем вся соль. И последний пример в статье не будет таким оторванным от реальности, если уж для новичков пишете.
              А метод Context.getString() добавлен лишь как удобный метод.
                0
                С базовыми ресурсами вопросы возникают редко, а копать глубже — это нужно не часто. Подобная статья была бы полезной, но я не возьмусь.
                  0
                  Тогда зачем пишете int strId = getResources().getIdentifier(«score_correct», «string», getPackageName());
                  где getResources() как раз и возвращает искомый объект Resources, о котором в статье совсем не сказано.

                  А Вы лишь пользуетесь частным вариантом вызова из Context.
                    0
                    Если перед разработчиком станет практическая задача: загрузить строку, имя которой сгенерированно динамически, то, использовав приведенной код, он ее решит. Подымать для этого весть объем данных по Resources — это хорошо, но время для этого есть не всегда.
                +8
                Такими темпами скоро в этот блог начнут писать посты вроде «как создать xml файл в Android проекте»
                  +3
                  > в одном файле храню все строки, которые надо будет переводить, во втором текстовые константы, которые я использую для передачи данных между различными Activity, в третьем храню константы, которые использую для составления запросов к веб серверу.

                  Зачем последние два пункта хранить в ресурсах? Почему не public final static? Строковые ресурсы все-таки нужны именно для хранения UI-строк, для всего остального вами перечисленного есть константы.
                    –1
                    Единое место для редактирования всех данных. Можно использовать константы в разных классах и разных пакетах, а можно использовать 3 отдельных файла в ресурсах. Тут важно, что удобно внутри команды. Для человека со стороны такой способ тоже больших сложностей не составит.
                    Как вариант, в разных файлах можно хранить отдельно строки для перевода и без перевода, если, к примеру, вы пишите англо-русский словарь.
                      +2
                      Странное решение. Строковые ресурсы — это строковые ресурсы, а константы — это константы, как бы очевидно это не звучало. Назначение у них разное. На строковые ресурсы вы ссылаетесь из кода, layoutов и манифеста, для загрузки ресурсов нужен контекст, строковые ресурсы в конце концов может читать не только программист, а еще и переводчик. Ключи для extras или параметры запросов никому кроме разработчика не нужны, у них никогда не будет другого контекста, их никто кроме программистов никогда не увидит, их значения меняются крайне редко после первого релиза.
                        +1
                        Я бы еще поинтересовался оверхедом за счет обращения к ресурсам вместо использования констант. Не знаю, насколько он велик, но когда мне нужно часто использовать одну и ту же строку (например, как часть контента в элементах длинного списка), я предпочитаю сделать один запрос к ресурсу и сохранить результат (в том же случае списка — как член адаптера).
                    0
                    А как добавить разрыв строки? Абзац?
                      0
                      разрыв строки: \n

                      <?xml version="1.0" encoding="utf-8"?>
                      Hello\nWorld!
                      • UFO just landed and posted this here

                        Only users with full accounts can post comments. Log in, please.