Использование локализаций и кодов локали во Flutter SDK

Почему это важно

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

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

Стандарт кодов локали в Adapty

Для кодов локали Adapty использует слегка модифицированный стандарт BCP 47: каждый код состоит из подтегов в нижнем регистре, разделённых дефисами. Примеры: en (английский), pt-br (португальский (Бразилия)), zh (упрощённый китайский), zh-hant (традиционный китайский).

Сопоставление кодов локали

В SDK v4 флоу и онбординги сопоставляют коды локали по-разному: флоу локализуются SDK на устройстве, онбординги — на сервере Adapty.

Флоу и пейволы Paywall Builder

Пейвол, созданный в Paywall Builder, в SDK v4 доставляется как флоу, поэтому правило ниже распространяется на оба случая.

Совпадение точное. SDK сравнивает переданный вами код с кодами локализации флоу посимвольно: не меняет регистр, не заменяет подчёркивания (_) на дефисы (-) и не откатывается к языковому тегу. Для флоу с локализацией pt-br подходит только pt-br: pt-BR, pt_BR и pt-PT — не совпадают.

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

Когда код совпадает, Adapty объединяет найденную локализацию с локализацией по умолчанию: строки и ресурсы, которые не определены в найденной локализации, берутся из локализации по умолчанию.

Отсутствие кода локали — это не то же самое, что запрос локализации флоу по умолчанию: SDK подставляет фиксированный en. Флоу, у которого в качестве локали по умолчанию задан de, всё равно будет отображаться на en, если у него есть локализация en, и переключится на de только в том случае, если локализации en нет.

Передавайте код локали ровно в том виде, в каком он задан в дашборде — строчные буквы, подтеги разделены дефисами. Не передавайте системный идентификатор локали как есть: Platform.localeName возвращает pt_BR, а PlatformDispatcher.instance.locale.toLanguageTag()pt-BR, и оба варианта откатываются к локализации по умолчанию. Перед передачей преобразуйте значение в своём приложении.

Онбординги

Онбординги локализуются на сервере, и серверные правила допускают другие форматы. Когда вы передаёте locale в getOnboarding:

  1. Строка локали приводится к нижнему регистру, а все символы подчёркивания (_) заменяются дефисами (-)
  2. Adapty ищет локализацию с точным совпадением кода локали
  3. Если совпадение не найдено, Adapty берёт подстроку до первого дефиса (pt для pt-br) и ищет соответствующую локализацию
  4. Если совпадение снова не найдено, Adapty возвращает контент для локали онбординга по умолчанию

Таким образом, pt_BR, pt-BR и pt-br — все они указывают на одну и ту же локализацию онбординга.

Реализация локализаций

В SDK v4 не нужно передавать код локали при получении флоу — getFlow возвращает флоу со всеми его локализациями, а Adapty применяет нужную при построении представления флоу. Аргумент locale в getFlow и getFlowForDefaultAudience не влияет на флоу; он устарел и выводит предупреждение в логах.

  • Флоу, созданные в билдере: SDK не считывает локаль устройства, поэтому определите её в приложении и передайте как аргумент locale в createFlowView или AdaptyUIFlowPlatformView. Аргумент необязательный — если его не указать, флоу отобразится на en или на своей локали по умолчанию, если локализации en в флоу нет.

AdaptyUIFlowView.locale сообщает локализацию, с которой было построено представление. Для этого требуется Flutter SDK 4.0.3 с нативными релизами iOS 4.0.2 и Android 4.0.1; со старыми нативными SDK возвращает null.

  • Пользовательские (Remote Config) пейволы: getFlow возвращает все настроенные локализации в flow.remoteConfigs. Каждая запись содержит код locale и содержимое конфига (строка data или разобранный dictionary). Выберите запись, соответствующую пользователю, реализовав собственный фолбэк:

final flow = await Adapty().getFlow(placementId: 'YOUR_PLACEMENT_ID');
final config = flow.remoteConfigs.firstWhereOrNull((c) => c.locale == 'en') ??
    flow.remoteConfig; // the first remote config, if present
// read your values from config?.dictionary

Adapty хранит коды locale в формате, описанном в разделе Стандарт кодов локалей в Adapty. SDK не сопоставляет Remote Config с локалью автоматически — то, какую запись применять, решает само приложение.

Почему это важно

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

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

Стандарт кодов локалей в Adapty

Для кодов локалей Adapty использует немного изменённый стандарт BCP 47: каждый код состоит из подтегов в нижнем регистре, разделённых дефисами. Примеры: en (английский), pt-br (португальский (Бразилия)), zh (упрощённый китайский), zh-hant (традиционный китайский).

Сопоставление кода локали

Когда Adapty получает вызов от клиентского SDK с кодом локали и начинает поиск соответствующей локализации пейвола, происходит следующее:

  1. Входящая строка локали приводится к нижнему регистру, а все символы подчёркивания (_) заменяются дефисами (-)
  2. Затем выполняется поиск локализации с полностью совпадающим кодом локали
  3. Если совпадение не найдено, берётся подстрока до первого дефиса (pt для pt-br) и выполняется поиск соответствующей локализации
  4. Если совпадение снова не найдено, возвращается контент для локали пейвола по умолчанию

Таким образом, устройство iOS, отправившее 'pt_BR', устройство Android, отправившее pt-BR, и другое устройство, отправившее pt-br, получат одинаковый результат.

Если вы задаётесь вопросом о локализациях, скорее всего, вы уже работаете с файлами локализованных строк в своём проекте. В таком случае мы рекомендуем добавить в каждый файл пару ключ-значение с нужным кодом локали Adapty для соответствующей локализации. Затем при вызове SDK извлекайте значение по этому ключу, как показано ниже:

// 1. Modify your app_en.arb, app_es.arb, app_pt_br.arb files

/*
app_en.arb
*/
"adapty_paywalls_locale": "en",

/*
app_es.arb
*/
"adapty_paywalls_locale": "es",

/*
app_pt_br.arb
*/
"adapty_paywalls_locale": "pt-br",

// 2. Extract and use the locale code
final locale = AppLocalizations.of(context)!.adapty_paywalls_locale;
// pass locale code to AdaptyUI.getViewConfiguration or Adapty.getPaywall method

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

Реализация локализаций: альтернативный способ

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

final locale = Localizations.localeOf(context).languageCode;
// pass locale code to AdaptyUI.getViewConfiguration or Adapty.getPaywall method

Обратите внимание, что мы не рекомендуем этот подход по нескольким причинам:

  1. На iOS предпочтительные языки и текущая локаль — не одно и то же. Чтобы локализация подбиралась корректно, придётся либо опираться на логику Apple (которая работает из коробки при рекомендованном подходе с локализованными строковыми файлами), либо реализовывать её самостоятельно.
  2. Сложно предсказать, что именно получит сервер Adapty. Например, на iOS устройство может вернуть локаль вида ar_OM@numbers='latn', которая будет отправлена на сервер. В ответ вы получите не локализацию ar-om, которую ожидали, а ar — что, скорее всего, не то, что нужно. Should you decide to use this approach anyway — make sure you’ve covered all the relevant use cases.