Как стать автором
Обновить

Двигай рукой справа налево: адаптация интерфейса в Android приложении под RTL в XML и Jetpack Compose

Уровень сложностиПростой
Время на прочтение9 мин
Количество просмотров1.8K

Я — Денис, Android-разработчик в «Лайв Тайпинге». В этой статье я расскажу о том как адаптировать интерфейс в мобильном приложении под RTL в XML и Jetpack Compose. Вы узнаете, об особенностях RTL интерфейсов, отличиях поддержки RTL в XML и Jetpack Compose, и о том как поддержка RTL может привлечь новых пользователей.

Что такое RTL

RTL (right-to-left) — чтение справа налево встречается в языках ближнего востока и южной азии. Так читают, к примеру арабы и персы. LTR (left-to-right) — напротив чтение слева направо, так читаем мы и ещё миллиарды людей.

RTL контент в интернете занимает на начало 2024 года — 3%. Подробнее ниже в таблице.

Позиция

Язык

Распространение

1

Английский

51.7%

4

Русский

4.5%

12

Персидский

1.5%

18

Арабский

0.6%

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

LTR vs RTL интерфейс

Текст

Важно, перевести и зеркально отразить текст для чтения справа налево. Слова или фразы, которые остаются на оригинальном языке без зеркального отражения.

?? Три способа чтения:

  • LTR — слева направо, как в большинстве языках;

  • RTL — справа налево, в арабском, иврите, персидских языках;

  • BIDI — текст в направлении как слева направо, так и справа налево. Часто такое происходит, когда совмещаются символы из разных алфавитов.

Последовательность символов внутри строки

Физически в строке символы расположены последовательно, но за итоговое отображение этой последовательности на экране отвечает unicode bidirectional algorithm.

Вкратце:

  1. для каждого символа в строке вычисляется направленность;

  2. строка бьётся на блоки одинаковой направленности;

  3. блоки выстраиваются в порядке, заданном базовым направлением.

На направленность каждого символа влияет тип и направленность соседних символов.

1) Сильно направленные. Направление определено заранее: для большинства символов это LTR, для арабских и ивритских символов — RTL.

Untitled

2) Нейтральные — знаки пунктуации или пробелы. Направление не определено заранее, они принимают направление соседних сильно направленных символов.

Запятая между символами, направленными слева направо, "o" и "w" в строке "Hello, world", принимает направление этих символов, как в LTR, так и в RTL.

Untitled

Нейтральный символ располагается между двумя сильно направленными символами разной направленности, он станет нейтрально направленным.

Untitled

То же самое случается с нейтральными символами в конце строки.

3) Слабо направленные — не влияют на окружающие символы.

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

Untitled

4) Зеркальные символы — меняют форму в зависимости от контекста. Открывающая скобка в RTL будет выглядеть как закрывающая в LTR и наоборот.

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

Untitled

Шрифт

Нужно проверить, что шрифт поддерживает RTL. В Apple это шрифт SF Arabic, а у Google Noto.

Текст кнопок и заголовков может выглядеть слишком мелким в арабском и иврите. Так как заглавные буквы не применяются — увеличьте размер шрифта на 10%.

Числа

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

Европейские цифры

0

1

2

3

4

5

6

7

8

9

Арабские цифры

٠

١

٢

٣

٤

٥

٦

٧

٨

٩

Untitled

Элементы, которые отображают прогресс располагаем по направлению чтения. При этом сами цифры переворачивать не нужно.

Untitled

Иконки

Иконки, где есть движение, нужно отразить. Так как арабы читают справа налево, то и движение воспринимают тем же образом. Например, иконки велосипедистов, машин в LTR движутся вправо, в RTL должны влево.

Untitled

Иконки с текстом, текстовые баблы, — отражаем, и обязательно адаптируем под язык пользователя.

Untitled

Иконки «вперёд» и «назад» меняются местами. Если в LTR дизайне кнопка «назад» показывает влево, в RTL она смотрит вправо.

Untitled

Элементы управления: свитчи, ползунки громкости, кнопки перелистывания треков — не отражаем. Ниже пример интерфейса плеера в музыкальном приложении.

Untitled

Изображения переворачиваем, за единственным исключением когда страдает визуал. Объекты, предметы из оффлайн мира – отражать не следует. Например, движущийся велосипедист — это иконка движения, значит отражаем. А наушники или телефон — это предметы, оставляем неизменными.

Категорически нельзя переворачивать иконки брендов и значков по типу «✅». Так только запутаем пользователя.

Ниже приведу ещё несколько примеров того как правильно адаптировать иконки под RTL.

Untitled

Косая черта в LTR может указывать на выключенное состояние как для языков LTR, так и для языков RTL. Поэтому в RTL такие иконки отражать не нужно.

Untitled

Слайдер увелечения громкости звука в RTL нужно отразить.

Untitled

Текст внутри иконок нужно адаптировать.

Настройка проекта

Android поддерживает RTL из «коробки» с API 17. Чтобы RTL заработал — укажите в Manifest android:supportRtl=”true”. Библиотеки Facebook, Google, уже включают внутри себя поддержку RTL. Поэтому поддержка RTL будет «смёржена» автоматически внутрь Manifest, если эти библиотеки добавлены в проект.

Аналогично флагу в Manifest, добавить RTL можно и в рантайме:

override fun attachBaseContext(base: Context) {
  super.attachBaseContext(object: ContextWrapper(base) {
    override fun getApplicationInfo(): ApplicationInfo {
      val origin = super.getApplicationInfo()
      origin.flags = origin.flags or ApplicationInfo.FLAG_SUPPORTS_RTL
      return origin
    }
  })
}

Особенности реализации в XML и Compose

XML

Если мобильное приложение изначально не разрабатывалось с поддержкой RTL, начните с автоматического рефакторинга в Android Studio. Для этого перейдите в Refactor -> Add Right-to-Left RTL Support.

Untitled

Рефакторинг заменит в XML файлах атрибуты, которые не подходят для RTL:

  • MarginLayoutParams.marginStart вместо MarginLayoutParams.marginLeft;

  • android:paddingEnd вместо android:PaddingRight;

  • android:marginStart вместо android:marginLeft ;

  • app:layout_constraintStart_toStartOf вместо app:layout_constraintLeft_toLeftOf;

  • android:gravity="end" вместо android:gravity="right";

  • android:drawableStart вместо android:drawableLeft;

  • Gravity.START вместо Gravity.LEFT;

  • android:textAlignment например на android:textAlignment="viewStart".

В рантайме можно принудительно заставить отображать View в RTL или LTR режиме. Это нужно для элементов, которые строго LTR или RTL.

layoutDirection = View.LAYOUT_DIRECTION_LTR

val View.isRtl: Boolean get() = layoutDirection = View.LAYOUT_DIRECTION_RTL
val Context.isConfigurationRtl: Boolean
  get() = resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL

direction == if (isConfigurationRtl) View.FOCUS_RIGHT else View.FOCUS_LEFT

Compose

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

В Compose также можно принудительно заставить отображать View в RTL или LTR режиме:

CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) {
  content()
}

val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl

Учтите, что у CompositionLocalProvider присутствует риск модификации элементов в дереве View. Для Row стоит использовать Arrangement для случаев, когда нужно только отзеркалить layout.

Особенности работы с картинками

  1. если нужно добавить картинку для RTL — укажите это в спецификации ресурса;

  2. auto-mirroring — кнопка доступна при импортировании картинок в Android Studio. Нажмите, если хотите автоматически отзеркалить в режиме RTL.

Untitled

Тестирование

Для тестирования RTL интерфейса можно не переключать язык на арабский или иврит. Перейдите в настройки разработчика — «Отразить интерфейс». Либо воспользуйтесь фичей per-app language.

В Jetpack Compose протестировать поведение RTL можно указав в параметр Preview локаль, которой присущ RTL. Например, @Preview(locale = "ar").

RTL в Webview

На отображение Webview мобильные разработчики повлиять не могут.

Возможные проблемы

Ошибки пользовательского опыта

Копирование

Что будет, если попытаться редактировать двунаправленный текст? Или хотя бы выделить и скопировать его часть? И то же самое при правке кода в редакторе и код-ревью — боль.

Landmarks: دبي مارينا مول — 600 m, داماك العقارية — 1.2 km

Форматирование

Представим, что приложение получило с бэкенда адрес и динамически подставило в строку. В RTL интерфейсе — это будет выглядеть некорректно. Ниже вариант до подстановки.

Untitled

Строчка в арабском варианте ниже неправильно отображается.

Untitled

Ниже правильный вариант.

Untitled

Это можно исправить используя Bodi форматтер:

val suggestion = "6 Krasina Street, Omsk"
val bidiFormmater = BidiFormatter.getInstance()

String.format(
  getString(R.string.did_you_mean),
  bidiFormatter.unicodeWrap(suggestion),
)

Каверзный функционал

Календари

Направление дней недели, дат, месяцев, годов в RTL «обратное». Нажимаешь на кнопку перелистывания месяца вправо, а отображается предыдущий месяц. Это нужно правильно обработать.

Untitled

Читалки

При чтении книги справа налево, важно учесть изменение направление навигации по страницам, главам и расположение элементов управления. В Bookmate и других приложениях Яндекс интерфейс адаптирован под RTL в 90% случаев.

Untitled

Реализация RTL friendly проекта

Реализуем RTL friendly проект и посмотрим с какими сложностями мы столкнёмся во время адаптации реального экрана:

Ссылка на репозиторий — https://github.com/DenisPopkov/RTL_Support_App.

Untitled

Предлагаю рассмотреть работу с RTL адаптацией с двух сторон: XML, Compose. Сперва переведём текстовые ресурсы на арабский язык.

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <string name="label">اشترك في LOGO العقل</string>
  <string name="label_description">حدد التعريفة. سيتم خصم الأموال مباشرة بعد تأكيد الدفع. ويتم الشطب التالي في بداية كل فترة فوترة جديدة. يمكن إدارة اشتراكات LOGO من خلال قائمة ملف تعريف متجر التطبيقات.</string>
  <string name="subscription_price">%s ₽</string>
  <plurals name="month">
    <item quantity="one">شهر</item>
    <item quantity="two">%s أشهر</item>
    <item quantity="few">%s أشهر</item>
    <item quantity="many">%s اشهر</item>
    <item quantity="other">%s اشهر</item>
    <item quantity="zero">%s اشهر</item>
  </plurals>
  <string name="popular_label">شائع</string>
  <plurals name="money_withdraw">
    <item quantity="one">يتم خصم الأموال كل شهر</item>
    <item quantity="two">يتم خصم الأموال كل %s أشهر</item>
    <item quantity="few">يتم خصم الأموال كل %s أشهر</item>
    <item quantity="many">يتم خصم الأموال كل %s اشهر</item>
    <item quantity="other">يتم خصم الأموال كل %s اشهر</item>
    <item quantity="zero">يتم خصم الأموال كل %s اشهر</item>
  </plurals>
  <string name="subscription_description_first_point">الوصول الكامل إلى جميع الدراسات الاستقصائية</string>
  <string name="subscription_description_second_point">تفسير النتائج والتوصيات الشخصية</string>
  <string name="subscription_description_third_point">تاريخ وديناميكية النتائج</string>
</resources>

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

Для переноса строки в текстовых ресурсах используйте n\ вместо \n.

Чтобы добиться в XML макете корректного поведения текстовых компонентов в RTL, добавьте атрибут:

android:textDirection="locale"

textDirection — определит, как текст рендерится в зависимости от типа символа.

Параметры:

  • anyLTR — если в тексте присутствуют сильно направленные RTL символы, то атрибут определяет, направление как RTL. В противном случае LTR, если таких символов нет;

  • firstStrongLTR — этот атрибут определяет направление текста по первому сильно направленному символу. В противном случае LTR, если таких символов нет;

  • firstStrongRTL — атрибут работает также как и firstStrongLTR. В противном случае RTL, если таких символов нет;

  • locale — направление будет соответствовать тому, что выставлено в системе;

  • ltr — направление слева направо;

  • rtl — направление справа налево.

Отдельный файл конкретно для RTL — не так эффективен, если разница между LTR интерфейсом не разительна. Так как придётся поддерживать изменения сразу в двух файлах, что обязательно приведёт к ошибкам.

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

NumberFormat.getInstance(Locale.getDefault()).format(monthAmount)

Теперь система в зависимости от языка, автоматически адаптирует числа под нужную локаль.

По итогу небольшого количества правок не адаптированного интерфейса экрана — получим такой результат в арабской версии.

Untitled

Итог

В заключении приведу пример адаптации мобильного приложения для арабской аудитории. Yango music — Яндекс музыка для арабских пользователей.

Untitled

Сервис работает в 18 странах, в том числе в ОАЭ, Израиле, Финляндии, Норвегии. В мае 2023 года сервис стал доступен в Пакистане. До этого работа тестировалась в Гватемале и Перу.

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

Очень сложно что-то сделать без знания языка

Вы будете думать, что всё готово, пока не покажете свой проект настоящему носителю языка. Разрабатывайте с точки зрения человека, не знающего никакого языка. Ведь даже такие очевидные для тебя слова, как, например, «Twitter», возможно, придётся переводить. И знаки препинания, оказывается, не на всей планете одинаковые.

Посмотреть, как изначально расположены символы в строке и почему они визуально расположились именно так, позволяет инструмент на сайте Юникода: http://unicode.org/cldr/utility/bidi.jsp.

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

Итоговый рецепт

Сложно описать в одном списке всё, что нужно сделать, каждый продукт требует особых подходов и решений:

  • обязательно найдите носителя языка и покажи ему прототипы как можно раньше;

  • собирайте стили для LTR и RTL в разные файлы;

  • добавьте исключения для всего, что переворачивать не нужно и явно отразите то, что не перевернулось само;

  • не допускайте хардкода языковых конструкций (например, конкатенация строк через запятую), по возможности конфигурируй всё, включая знаки препинания. Это пригодится не только для RTL — к примеру, на греческом языке вопросительный знак — «;».

Если вы нашли неточности/ошибки в статье или просто хотите дополнить её своим мнением — то прошу в комментарии!

Теги:
Хабы:
Всего голосов 2: ↑2 и ↓0+2
Комментарии0

Публикации

Работа

Ближайшие события