Хабрахабр

Переключение языка в Android-приложении

Иллюстрация для статьи

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

Do you speak it?!" title="Сэмюэл Джексон спрашивает, поддерживает ли твое приложение иврит?"/> <img src="http://orion-int.ru/wp-content/uploads/2019/07/pereklyuchenie-yazyka-v-android-prilozhenii.jpg" alt="Hebrew, motherfucker!

О чем пойдет речь, а о чем не пойдет?

Далее не будет ничего о:

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

А пойдет речь о:

  • Практике. Рассмотрим задачу, ее ограничения и решение с диаграммами, примерами и фрагментами кода.
  • API SDK, которое было использовано для этого решения.
  • Особенностях форматирования числовых значений для разных региональных стандартов, о которых стоит знать дизайнерам.

Что мы хотим сделать?

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

Язык английский." title="Скриншоты приложения Telegram"/> <img src="http://orion-int.ru/wp-content/uploads/2019/07/pereklyuchenie-yazyka-v-android-prilozhenii-1.jpg" alt="Экран настроек приложения Telegram.

Язык арабский." title="Скриншоты приложения Telegram"/> <img src="https://habrastorage.org/webt/9n/iy/qt/9niyqtdrhg3j_fou4ybou_vjrr4.jpeg" alt="Экран настроек приложения Telegram.

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

Примеры локализации числовых значений.

Архитектурное решение

Тогда механизм переключения языка может быть реализован следующим образом. Представим, что наше приложение написано в соответствии с Single-Activity подходом.

Диаграмма классов решения.

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

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

override fun attachBaseContext(base: Context) { super.attachBaseContext(applySelectedAppLanguage(base))
} private fun applySelectedAppLanguage(context: Context): Context { val locale = settingsInteractor.getUserSelectedLanguageBlocking() val newConfig = Configuration(context.resources.configuration) Locale.setDefault(locale) newConfig.setLocale(locale) return context.createConfigurationContext(newConfig)
}

AppPresenter в свою очередь подписывается на обновления языка и уведомляет View об изменениях.

override fun onFirstViewAttach() { super.onFirstViewAttach() subscribeToLanguageUpdates()
} private fun subscribeToLanguageUpdates() , { error -> errorHandler.handle(error) } ) .disposeOnDestroy()
}

AppActivity при получении уведомления о смене языка пересоздается.

override fun applyNewAppLanguage(lang: Locale) = recreate()

Состояние стека экранов.

Все остальные экраны реализованы фрагментами. AppActivity является единственной в приложении. При возврате на предыдущие экраны они будут переинициализированы и отображаться переведенными. Поэтому при пересоздании активити стек экранов сохраняется системой. Пользователь останется на списке выбора языка и увидит результат своего выбора мгновенно.

Форматирование чисел, денежных сумм и процентов

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

Диаграмма классов для UiLocalizer.

Для преобразования числа в строку UiLocalizer использует соответствующие инстансы NumberFormat.

private var numberFormat = NumberFormat.getNumberInstance(lang) private var percentFormat = NumberFormat.getPercentInstance(lang) private fun getNumberFormatForCurrency(currency: Currency) = NumberFormat .getCurrencyInstance(lang) .also { it.currency = currency }

Обратите внимание, что валюту необходимо устанавливать отдельно.

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

Представление языков и валют

А экземпляры класса Currency – по трехбуквенному ISO коду. Экземпляры класса Locale создаются по языковому тегу, который состоит из двухбуквенного кода языка и двухбуквенного кода региона. Приведем примеры. В этом виде язык и валюта должны сериализовываться для сохранения на диск или передачи по сети, и тогда будет хорошо.

// IETF BCP 47 language tag string.
private val langs = arrayOf( Locale.forLanguageTag("ru-RU"), Locale.forLanguageTag("en-US"), Locale.forLanguageTag("en-GB"), Locale.forLanguageTag("he-IL"), Locale.forLanguageTag("ar-SA"), Locale.forLanguageTag("ar-AE"), Locale.forLanguageTag("fr-FR"), Locale.forLanguageTag("fr-CH"), Locale.forLanguageTag("de-DE"), Locale.forLanguageTag("de-CH"), Locale.forLanguageTag("da-DK")
) // ISO 4217 code of the currency.
private val currencies = arrayListOf( Currency.getInstance("RUB"), Currency.getInstance("USD"), Currency.getInstance("GBP"), Currency.getInstance("ILS"), Currency.getInstance("SAR"), Currency.getInstance("AED"), Currency.getInstance("EUR"), Currency.getInstance("CHF"), Currency.getInstance("DKK")
)

Особенности форматирования числовых значений

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

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

Числа

Валюты

Проценты

При этом все варианты правильные, каждый из них используется в определенных контекстах. Более того, результаты форматирования для Android SDK и JDK могут быть разными.

Примеры разного форматирования в Android и JDK.

DecimalFormat

Приведя объект к типу DecimalFormat и используя его интерфейс, можно изменить части шаблона, чтобы все сломать. Когда мы создаем NumberFormat для форматирования тех или иных значений, мы получаем объекты класса DecimalFormat, которые просто сконфигурированы разными шаблонами. Но лучше поклоняться данности.

Валюта – новый израильский шекель"/> <img src="https://habrastorage.org/webt/ue/dd/dm/uedddmjn3pzf6gaotxfxkdqn9e8.png" alt="Диаграмма классов для DecimalFormat." title="Сравнение французской и арабской локалей.

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

Примеры форматирования чисел.

В итоге

Общая схема решения выглядит следующим образом.

Итоговая диаграмма классов с комментариями.

Поэтому достаточно пересоздать ее, чтобы перезапустить все приложение и применить выбранный язык. Жизненный цикл AppActivity является жизненным циклом всего приложения. А поскольку активити одна, подписку на изменение языка достаточно держать в одном месте – в AppPresenter.

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

Как проще тестировать? (бонус)

Для экономии времени можно воспользоваться следующим флагом.

android { ... buildTypes { debug { pseudoLocalesEnabled true } } ...
}

Выбрать необходимую псевдолокаль в настройках телефона.

Экран выбора языка системы.

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

Примеры эффекта псевдолокалей.

Более подробную информацию можно прочитать в документации.

Вы ведь подменяете контекст. Стоит отметить, что псевдолокали не будут работать, если вы подменяете контекст, как в решении выше. Поэтому необходимо добавить en-XA и ar-XB в список выбора языка внутри приложения.

Хорошей вам локализации и хорошего настроения! На этом все.

Thanks

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть