Какие способы добавления локализации в Flutter-приложение вы знаете?

Ответ

В своих Flutter-проектах я использовал несколько подходов к локализации:

1. Стандартный подход с flutter_localizations и intl (рекомендуемый):

# pubspec.yaml
dependencies:
  flutter_localizations:
    sdk: flutter
  intl: ^0.18.0

flutter:
  generate: true
// main.dart
return MaterialApp(
  localizationsDelegates: [
    AppLocalizations.delegate, // Сгенерированный делегат
    GlobalMaterialLocalizations.delegate,
    GlobalWidgetsLocalizations.delegate,
    GlobalCupertinoLocalizations.delegate,
  ],
  supportedLocales: AppLocalizations.supportedLocales,
  locale: Locale('ru', 'RU'), // Текущая локаль
  home: HomePage(),
);

Структура файлов .arb:

// lib/l10n/app_en.arb
{
  "@hello": {
    "description": "Приветствие"
  },
  "hello": "Hello {name}",
  "@itemsCount": {
    "description": "Количество элементов",
    "placeholders": {
      "count": {}
    }
  },
  "itemsCount": "{count, plural, =0{No items} =1{1 item} other{{count} items}}"
}

// lib/l10n/app_ru.arb
{
  "hello": "Привет, {name}",
  "itemsCount": "{count, plural, =0{Нет элементов} =1{1 элемент} few{{count} элемента} other{{count} элементов}}"
}

Использование в коде:

Text(AppLocalizations.of(context)!.hello('Анна')),
Text(AppLocalizations.of(context)!.itemsCount(5)),

2. Динамическое переключение локали через State Management:

// С использованием Provider/Riverpod
class LocaleController {
  Locale _locale = Locale('en');

  Future<void> setLocale(Locale locale) async {
    _locale = locale;
    await _saveToPrefs(locale);
    notifyListeners();
  }

  Locale get locale => _locale;
}

// В MaterialApp
locale: context.watch<LocaleController>().locale,

3. Для более простых проектов — кастомное решение:

class AppLocalizations {
  final Map<String, Map<String, String>> _localizedValues = {
    'en': {
      'welcome': 'Welcome',
      'settings': 'Settings',
    },
    'ru': {
      'welcome': 'Добро пожаловать',
      'settings': 'Настройки',
    },
  };

  String translate(String key, BuildContext context) {
    final locale = Localizations.localeOf(context).languageCode;
    return _localizedValues[locale]?[key] ?? _localizedValues['en']![key]!;
  }
}

4. Пакеты для специфических нужд:

  • easy_localization — для быстрого старта с JSON/YAML файлами
  • flutter_i18n — поддержка вложенных ключей и плюрализации
  • intl_utils — дополнительные утилиты для работы с intl

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

Ответ 18+ 🔞

Слушай, я тут с локализацией во Flutter'е намучился, как собака, пока не разобрался. Вот тебе реальные рабочие схемы, которые не заставят тебя выть на луну от бессилия.

1. Официальный путь — flutter_localizations и intl (самый правильный, но не без косяков)

Это как идти в военкомат по повестке — долго, нудно, но по правилам. Flutter тебя за ручку ведёт.

# pubspec.yaml — тут всё стандартно
dependencies:
  flutter_localizations:
    sdk: flutter
  intl: ^0.18.0

flutter:
  generate: true # Эта строчка — магия, она всё сгенерит

А вот в main.dart начинается ёперный театр с делегатами. Их там овердохуища, и если один забыть — приложение смотрит на тебя, как баран на новые ворота.

return MaterialApp(
  localizationsDelegates: [
    AppLocalizations.delegate, // Этот парень сгенерится автоматом — красота!
    GlobalMaterialLocalizations.delegate, // Без него Material-виджеты охуеют
    GlobalWidgetsLocalizations.delegate, // Без него Directionality и прочая хуйня
    GlobalCupertinoLocalizations.delegate, // На случай, если iOS-виджеты залезут
  ],
  supportedLocales: AppLocalizations.supportedLocales, // Тоже сгенерится
  locale: Locale('ru', 'RU'), // Вот тут русских и прижми к стенке
  home: HomePage(),
);

Дальше самое интересное — файлы .arb. Это JSON, но с прибамбасами. Выглядит как китайская грамота, пока не вникнешь.

// lib/l10n/app_en.arb — для этих буржуев
{
  "@hello": {
    "description": "Приветствие" // Описание, чтобы через месяц не охуеть
  },
  "hello": "Hello {name}", // Сама строка. {name} — это плейсхолдер
  "@itemsCount": {
    "description": "Количество элементов",
    "placeholders": {
      "count": {} // Тут можно тип указать, но обычно и так ясно
    }
  },
  "itemsCount": "{count, plural, =0{No items} =1{1 item} other{{count} items}}" // Плюрализация, ёпта! Это мощь!
}

// lib/l10n/app_ru.arb — для наших
{
  "hello": "Привет, {name}",
  "itemsCount": "{count, plural, =0{Нет элементов} =1{1 элемент} few{{count} элемента} other{{count} элементов}}" // В русском ещё и few нужно! Вот же ж хитрая жопа!
}

А в коде потом используешь, и это даже приятно:

Text(AppLocalizations.of(context)!.hello('Анна')), // Привет, Анна
Text(AppLocalizations.of(context)!.itemsCount(5)), // 5 элементов

Главный геморрой — этот of(context)! с восклицательным знаком. Если забыть ! или если контекст не тот — доверия ебать ноль, получишь null и краш. Но зато типобезопасно и автодополнение в IDE работает.

2. Динамическое переключение языка на лету (чтобы пользователь не перезапускал)

Тут уже нужен state management (Provider, Riverpod, Bloc — что душе угодно). Без него — хуй с горы.

// Допустим, через Provider. Контроллер локали.
class LocaleController with ChangeNotifier {
  Locale _locale = Locale('en'); // По умолчанию английский

  Future<void> setLocale(Locale locale) async {
    _locale = locale;
    await _saveToPrefs(locale); // Сохраняем в SharedPreferences, чтоб не сбрасывалось
    notifyListeners(); // Кричим всем виджетам: "Эй, пацаны, язык поменялся!"
  }

  Locale get locale => _locale;
}

// В MaterialApp подключаем
locale: context.watch<LocaleController>().locale, // Следим за контроллером

Теперь по кнопке вызываешь setLocale(Locale('ru')) — и интерфейс перерисовывается. Красота! Главное, не забыть обернуть MaterialApp в ChangeNotifierProvider.

3. Для маленьких проектов — самоделка на коленке

Если проект на три кнопки и надпись "Hello world", можно не заморачиваться.

class AppLocalizations {
  final Map<String, Map<String, String>> _localizedValues = {
    'en': {
      'welcome': 'Welcome',
      'settings': 'Settings',
    },
    'ru': {
      'welcome': 'Добро пожаловать',
      'settings': 'Настройки', // 'Настройки', Карл!
    },
  };

  String translate(String key, BuildContext context) {
    final locale = Localizations.localeOf(context).languageCode;
    // Ищем по ключу. Если нет — отдаём английский. Проще пареной репы.
    return _localizedValues[locale]?[key] ?? _localizedValues['en']![key]!;
  }
}

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

4. Готовые пакеты — палочки-выручалочки

  • easy_localization — для тех, кому влом разбираться с кодогенерацией. Кинул JSON-файлы в папку — и работает. Но иногда творит чудеса, от которых сам от себя охуеешь.
  • flutter_i18n — мощный, с вложенными ключами типа "home.title.welcome". Удобно, но своя специфика.
  • intl_utils — не пакет для локализации, а утилита для стандартного intl. Генерирует .dart-файлы по .arb, можно в CI/CD засунуть.

Итог, чувак: для продакшена бери стандартный intl. Он, конечно, с причудами, но зато от Google, обновляется и пизда рулю. Всю сложную хуйню вроде плюрализации, гендерных форм и форматирования чисел он делает за тебя. А динамическую смену языка прикручивай через свой провайдер поверх. Получится надёжно и без сюрпризов.