Реализация динамического переключения языка в мобильном приложении

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

Разработка и поддержка любых видов мобильных приложений:

Информационные и развлекательные мобильные приложения
Новостные приложения, игры, справочники, онлайн-каталоги, погодные, фитнес и здоровье, туристические, образовательные, социальные сети и мессенджеры, квиз, блоги и подкасты, форумы, агрегаторы
Мобильные приложения электронной коммерции
Интернет-магазины, B2B-приложения, маркетплейсы, онлайн-обменники, кэшбэк-сервисы, биржи, дропшиппинг-платформы, программы лояльности, доставка еды и товаров, платежные системы
Мобильные приложения для управления бизнес-процессами
CRM-системы, ERP-системы, управление проектами, инструменты для команды продаж, учет финансов, управление производством, логистика и доставка, управление персоналом, системы мониторинга данных
Мобильные приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, платформы предоставления электронных услуг, платформы кешбека, видеохостинги, тематические порталы, платформы онлайн-бронирования и записи, платформы онлайн-торговли

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

Реализация динамического переключения языка в мобильном приложении

Локализация и интернационализация мобильных приложенийСреднийот 1 дня до 3 дней
Часто задаваемые вопросы

Переключение языка без перезапуска приложения — задача, которая выглядит тривиально, пока не наткнёшься на то, что половина строк поменялась, а Fragment с ViewPager2 остался на старом языке, потому что пережил смену конфигурации через setRetainInstance(true). Или что DateTimeFormatter закешировал Locale при первом вызове и теперь форматирует даты на русском, хотя пользователь выбрал английский три экрана назад. Мы, как команда с опытом более 5 лет в мобильной разработке, сталкиваемся с этими проблемами регулярно. Наши инженеры сертифицированы Apple и Google, и мы гарантируем, что ваш пользователь увидит актуальный язык сразу после выбора, без перезапуска приложения. Закажите аудит текущей архитектуры, чтобы выявить скрытые проблемы локализации.

Почему динамическое переключение языка — сложная задача?

Android

Стандартный способ — AppCompatDelegate.setApplicationLocales(LocaleListCompat) из AndroidX (API 33+ нативно, AndroidX-бэкпорт работает до API 21). До AndroidX приходилось вручную пересоздавать Configuration и перезапускать Activity.

Проблема с setApplicationLocales: он сохраняет выбор пользователя в системных настройках приложения. Это хорошо для интеграции с системными Language Settings, но ломает сценарий, когда locale управляется с бэкенда (мультитенантные приложения, где язык задаётся профилем). Тогда нужен собственный ContextWrapper с перегрузкой attachBaseContext, который оборачивает Context с нужной Locale до того, как Activity начнёт инфлейтить layout.

ViewModel не пересоздаётся при смене locale через setApplicationLocales — она переживает смену конфигурации. Строки внутри ViewModel (если они туда попали — ошибка архитектуры) останутся на старом языке. Строки в LiveData<String> или StateFlow<String> нужно либо хранить как @StringRes Int и форматировать в View, либо перевыпускать после смены locale.

RecyclerView.Adapter с закешированными строками нужно явно дёрнуть через notifyDataSetChanged() или лучше через DiffUtil, иначе уже отрисованные элементы не перерисуются.

iOS

Bundle.main.localizedString(forKey:value:table:) читает строки из загруженного бандла — то есть из бандла той локали, что была активна при запуске. UserDefaults.standard.set(["ru"], forKey: "AppleLanguages") меняет язык только после следующего запуска. Это ограничение iOS до версии 13.

Для iOS 13+ правильный путь — Bundle swizzling: создаём кастомный Bundle, который переопределяет localizedString(forKey:) и читает из бандла нужной локали. Типичная реализация — через расширение Bundle с хранением текущей languageBundle в статической переменной:

private var bundleKey: UInt8 = 0
class LanguageBundle: Bundle {
    override func localizedString(forKey key: String, value: String?, table tableName: String?) -> String {
        guard let bundle = objc_getAssociatedObject(self, &bundleKey) as? Bundle else {
            return super.localizedString(forKey: key, value: value, table: tableName)
        }
        return bundle.localizedString(forKey: key, value: value, table: tableName)
    }
}
extension Bundle {
    static func setLanguage(_ language: String) {
        object_setClass(Bundle.main, LanguageBundle.self)
        let path = Bundle.main.path(forResource: language, ofType: "lproj")
        let bundle = path.flatMap { Bundle(path: $0) } ?? Bundle.main
        objc_setAssociatedObject(Bundle.main, &bundleKey, bundle, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        NotificationCenter.default.post(name: .languageDidChange, object: nil)
    }
}

Подход описан в документации Apple по Bundle

SwiftUI упрощает: environment(\.locale, Locale(identifier: "ar")) на корневом View — и все дочерние вью перерисуются с новой locale. Строки через LocalizedStringKey подхватываются автоматически. Но UIViewController-based экраны в SwiftUI-обёртке через UIViewControllerRepresentable требуют явного триггера — NotificationCenter или @Published флаг перестройки.

Flutter

MaterialApp(locale: _currentLocale) + setState — стандартный подход. Пакет flutter_localizations + intl для форматирования. Смена locale через Provider или Riverpod (Notifier с Locale-стейтом) — UI перестраивается реактивно.

Кейс из практики: приложение с cached_network_image — при смене языка кеш изображений с alt-текстами сбрасывался (потому что ключ кеша включал locale-зависимый URL). Решение — locale-agnostic ключи кеша.

Как избежать ошибок при смене языка?

Типичные ошибки и их предотвращение:

Ошибка Решение
Кеширование форматтеров дат/чисел Создавать новый экземпляр при каждой смене языка
Строки в ViewModel Использовать @StringRes Int или локализовать на уровне View
Необновление отрисованных UI-элементов Вызывать notifyDataSetChanged() или использовать реактивный подход
Игнорирование системных настроек Отключить автосинхронизацию, если язык управляется приложением
Пример для Flutter с Riverpod
final localeProvider = StateNotifierProvider<LocaleNotifier, Locale>((ref) => LocaleNotifier());
class LocaleNotifier extends StateNotifier<Locale> {
  LocaleNotifier() : super(Locale('en'));
  void setLocale(Locale locale) {
    state = locale;
  }
}

Как мы реализуем переключение языка?

Мы предлагаем комплексное решение, адаптированное под ваш стек. Процесс включает:

  1. Аудит текущей архитектуры — проверка хранения строк, форматтеров, кеширования
  2. Выбор механизма хранения — SharedPreferences/UserDefaults для клиентского выбора или серверный профиль для мультитенантных приложений
  3. Реализация locale-провайдера с реактивным стейтом (Riverpod, Room + Flow, Combine)
  4. Обновление всех мест форматирования дат, чисел, валют (NumberFormat, DateFormat — не кешировать с locale)
  5. Тестирование на ключевых экранах — deep link, push-notification landing pages, табы, диалоги
Платформа Подход Время внедрения (типовой проект) Сложность
iOS Bundle swizzling или SwiftUI environment 1-2 дня Средняя
Android AppCompatDelegate.setApplicationLocales или ContextWrapper 1-2 дня Средняя
Flutter MaterialApp locale + Provider 0.5-1 день Низкая
React Native i18n library + native module (при необходимости) 1-2 дня Средняя

Что входит в работу

  • Детальный отчёт по результатам аудита
  • Реализация механизма переключения языка
  • Настройка строковых ресурсов и форматтеров
  • Интеграция с существующими сервисами (бэкенд, аналитика)
  • Тестирование на всех сценариях (включая офлайн)
  • Предоставление доступа к репозиторию с кодом
  • Обучение команды (1 часовая сессия)
  • Гарантия на внедрение 30 дней

Типичные ошибки и как их избежать

  • Кеширование форматтеров — всегда создавайте новый экземпляр при смене языка
  • Строки в ViewModel — используйте @StringRes или локализуйте на уровне View
  • Необновление уже отрисованных элементов — вызывайте notifyDataSetChanged() или используйте реактивный подход
  • Игнорирование системных настроек — если приложение управляет языком, отключите автосинхронизацию с системой

Сроки и стоимость

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

Наши компетенции:

Последние работы

  • Разработка мобильного приложения для компании FEEDME
    Разработка мобильного приложения для компании FEEDME
    1012
  • Разработка мобильного приложения для компании XOOMER
    Разработка мобильного приложения для компании XOOMER
    904
  • Разработка мобильного приложения для компании RHL
    Разработка мобильного приложения для компании RHL
    1333
  • Разработка мобильного приложения для компании ZIPPY
    Разработка мобильного приложения для компании ZIPPY
    1181
  • Разработка мобильного приложения для компании Affhome
    Разработка мобильного приложения для компании Affhome
    1108
  • Разработка мобильного приложения для компании FLAVORS
    Разработка мобильного приложения для компании FLAVORS
    715