Skip to main content

Полное руководство по локализации Flutter

От файлов ARB до поддержки RTL: локализуйте приложение Flutter с easy_localization, обработайте формы множественного числа всех языков и автоматизируйте перевод с помощью ИИ.

1

Установить easy_localization

Добавьте пакет easy_localization в pubspec.yaml. Это самый популярный пакет i18n Flutter с поддержкой файлов ARB/JSON, форм множественного числа и расширений контекста. Также добавьте flutter_localizations из SDK для учитывающего локаль форматирования дат, чисел и направления текста.

easy_localization предоставляет такие расширения контекста, как context.tr() и 'key'.tr(), для краткого доступа к переводам. Он загружает и сохраняет локали, а также поддерживает горячую перезагрузку файлов перевода во время разработки.
pubspec.yaml
dependencies:
  easy_localization: ^3.0.7
  flutter_localizations:
    sdk: flutter
2

Создать файлы перевода ARB

Файлы Application Resource Bundle (ARB) — стандартный формат локализации Flutter. Каждый содержит пары «ключ — значение» с необязательными метаданными, описывающими заполнители, правила множественного числа и контекст для переводчиков. Создайте по одному файлу для каждой локали в каталоге assets/translations.

assets/translations/en.arb
{
  "@@locale": "en",
  "appTitle": "My App",
  "@appTitle": {
    "description": "The title of the application"
  },
  "greeting": "Hello, {name}!",
  "@greeting": {
    "description": "Greeting with user name",
    "placeholders": {
      "name": {
        "type": "String",
        "example": "Alice"
      }
    }
  },
  "itemCount": "{count, plural, =0{No items} =1{1 item} other{{count} items}}",
  "@itemCount": {
    "description": "Number of items in the cart",
    "placeholders": {
      "count": {
        "type": "num",
        "format": "compact"
      }
    }
  }
}
Ключи с префиксом @, например @greeting, являются метаданными и описывают строку над ними. Укажите типы и примеры заполнителей, чтобы помочь переводчикам создать точные переводы. Эти ключи метаданных удаляются во время выполнения и не создают дополнительной нагрузки.
3

Настроить приложение

Оберните приложение в виджет EasyLocalization. Он управляет состоянием локали, загружает переводы из файлов ресурсов и предоставляет делегаты локалей, необходимые MaterialApp. Три обязательных свойства делегата — localizationsDelegates, supportedLocales и locale — доступны через расширения контекста.

lib/main.dart
import 'package:flutter/material.dart';
import 'package:easy_localization/easy_localization.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await EasyLocalization.ensureInitialized();

  runApp(
    EasyLocalization(
      supportedLocales: [
        Locale('en'),
        Locale('ar'),
        Locale('ja'),
        Locale('de'),
      ],
      path: 'assets/translations',  // Path to your ARB/JSON files
      fallbackLocale: Locale('en'),
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      localizationsDelegates: context.localizationDelegates,
      supportedLocales: context.supportedLocales,
      locale: context.locale,
      home: HomePage(),
    );
  }
}
Ошибка "Easy Localization not initialized" означает, что Вы забыли вызвать EasyLocalization.ensureInitialized() перед runApp(). Его необходимо ожидать после WidgetsFlutterBinding.ensureInitialized() и перед runApp().
4

Перевести виджеты

Используйте метод расширения .tr() для строковых ключей, чтобы получать переведённый текст в любом виджете. Для форм множественного числа вызывайте .plural() со значением количества. easy_localization предоставляет и синтаксис расширения строки ('key'.tr()), и метод контекста (context.tr('key')).

lib/widgets/home_page.dart
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('appTitle'.tr()),   // Simple key
      ),
      body: Column(
        children: [
          // String with variable interpolation
          Text('greeting'.tr(args: ['Alice'])),

          // Named arguments
          Text('greeting'.tr(namedArgs: {'name': 'Alice'})),

          // Plural form
          Text('itemCount'.plural(3)),
        ],
      ),
    );
  }
}
Если вместо переведённого текста возвращаются исходные ключи, проверьте: 1) виджет EasyLocalization оборачивает MaterialApp, а не наоборот; 2) файлы перевода объявлены в разделе assets файла pubspec.yaml; 3) путь файла в EasyLocalization соответствует реальной структуре каталогов.
5

Обработать формы множественного числа и переменные

Flutter использует для форм множественного числа ICU MessageFormat — тот же стандарт, что iOS, Android и веб. Определяйте формы в файлах ARB с помощью синтаксиса {count, plural, ...}. Каждому языку нужен свой набор по правилам CLDR. В арабском 6 форм, в русском — 4, в японском — 1.

Plural forms by language
// English: 3 useful forms (=0, =1, other)
"itemCount": "{count, plural, =0{No items} =1{1 item} other{{count} items}}"

// Arabic: 6 forms (=0, =1, =2, few, many, other)
"itemCount": "{count, plural, =0{لا عناصر} =1{عنصر واحد} =2{عنصران} few{{count} عناصر} many{{count} عنصرًا} other{{count} عنصر}}"

// Japanese: 1 form (other)
"itemCount": "{count, plural, other{{count}個のアイテム}}"

// German: 2 forms (=1, other)
"itemCount": "{count, plural, =1{1 Artikel} other{{count} Artikel}}"

// Russian: 3 forms (one, few, many)
"itemCount": "{count, plural, =0{Нет товаров} one{{count} товар} few{{count} товара} many{{count} товаров} other{{count} товаров}}"
Variables and named arguments
// Simple variable
"welcome": "Welcome, {name}!"

// Multiple variables
"orderStatus": "Order #{orderId} — {status}"

// Variable in plural context
"unreadMessages": "{count, plural, =0{No unread messages} =1{1 unread message from {sender}} other{{count} unread messages}}"

// In widgets:
Text('welcome'.tr(namedArgs: {'name': userName}));
Text('orderStatus'.tr(namedArgs: {'orderId': '1234', 'status': 'Shipped'}));
Text('unreadMessages'.plural(count, namedArgs: {'sender': senderName}));
Никогда не задавайте логику единственного и множественного числа жёстко с помощью if (count == 1). Во французском 0 считается единственным числом. В русском, польском и арабском есть формы, полностью отсутствующие в английском. Всегда используйте синтаксис множественного числа ICU и поручайте выбор фреймворку.
6

Поддержать языки RTL

Flutter автоматически зеркально отражает весь макет, если локаль использует направление справа налево: арабский, иврит, персидский или урду. Но для правильного отражения код должен применять виджеты и свойства, учитывающие направление. Замените жёстко заданные left/right эквивалентами start/end.

lib/widgets/adaptive_layout.dart
import 'package:flutter/material.dart';

class AdaptiveLayout extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final isRtl = Directionality.of(context) == TextDirection.rtl;

    return Scaffold(
      body: Row(
        children: [
          // Use start/end instead of left/right
          Expanded(
            child: Padding(
              padding: EdgeInsetsDirectional.only(
                start: 16.0,  // Leading edge (left in LTR, right in RTL)
                end: 8.0,     // Trailing edge
              ),
              child: Text('content'.tr()),
            ),
          ),
          // Flip icons for RTL
          Icon(
            isRtl ? Icons.arrow_back : Icons.arrow_forward,
          ),
        ],
      ),
    );
  }
}
RTL-aware widget patterns
// Use Directional widgets for RTL-aware layouts
EdgeInsetsDirectional.only(start: 16, end: 8)   // Not EdgeInsets.only(left: 16, right: 8)
AlignmentDirectional.centerStart                  // Not Alignment.centerLeft
BorderRadiusDirectional.only(topStart: Radius.circular(8))

// Automatically mirrors when locale changes to Arabic, Hebrew, etc.
// No conditional logic needed — Flutter handles text direction from locale
Проверьте RTL, временно задав приложению арабскую локаль (Locale('ar')). Flutter отражает весь интерфейс: панели навигации открываются справа, стрелки «Назад» разворачиваются, а текст выравнивается по правому краю. Используйте EdgeInsetsDirectional, AlignmentDirectional и BorderRadiusDirectional, чтобы собственные макеты отражались правильно.
7

Умные цепочки резервных локалей

По умолчанию при отсутствии перевода pt-BR Flutter сразу переходит на английский, пропуская подходящие переводы pt-PT. Пакет locale_chain устраняет проблему с помощью настраиваемых цепочек. Одна строка настройки, без миграции: существующие вызовы .tr() просто работают.

lib/main.dart with LocaleChain
import 'package:locale_chain/locale_chain.dart';
import 'package:locale_chain/easy_localization.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await EasyLocalization.ensureInitialized();
  LocaleChain.configure();  // Enable smart fallback chains

  runApp(
    EasyLocalization(
      supportedLocales: [
        Locale('en'),
        Locale('pt', 'BR'),
        Locale('pt', 'PT'),
        Locale('es'),
        Locale('es', 'MX'),
      ],
      path: 'assets/translations',
      assetLoader: LocaleChainAssetLoader(  // Swap in the chain loader
        baseLoader: RootBundleAssetLoader(),
      ),
      fallbackLocale: Locale('en'),
      child: MyApp(),
    ),
  );
}

// Result: pt-BR user sees pt-PT translations when pt-BR keys are missing,
// instead of falling back directly to English.
Custom fallback configuration
// Override specific chains while keeping built-in defaults
LocaleChain.configure(
  fallbacks: {
    'pt-BR': ['pt-PT', 'pt'],      // pt-BR → pt-PT → pt → en
    'es-MX': ['es-419', 'es'],     // es-MX → es-419 → es → en
    'fr-CA': ['fr'],               // fr-CA → fr → en
  },
);

// Or replace all defaults with your own chains
LocaleChain.configure(
  fallbacks: {
    'zh-Hant-HK': ['zh-Hant-TW', 'zh-Hans'],
    'pt-BR': ['pt-PT', 'pt'],
  },
  mergeDefaults: false,  // Only your chains, no built-in defaults
);
locale_chain включает встроенные цепочки для региональных вариантов португальского, испанского, французского, немецкого, итальянского, нидерландского, норвежского и малайского. Пользователь pt-BR увидит содержимое pt-PT перед английским. Пользователь es-MX увидит es-419, затем es и только потом локаль по умолчанию.
8

Автоматизировать перевод

После завершения настройки локализации переведите файлы ARB с помощью ИИ. Попросите ИИ-помощника перевести исходный файл ARB в своей IDE или используйте CLI i18n Agent в конвейере CI/CD. Метаданные ARB — заполнители и описания — предоставляют контекст, повышающий качество перевода.

Terminal
# In your IDE, ask your AI assistant:
> Translate assets/translations/en.arb to Arabic, Japanese, and German

✓ ar.arb created (1.4s)
✓ ja.arb created (1.2s)
✓ de.arb created (1.1s)

# Or use the CLI in CI/CD:
npx i18n-agent translate assets/translations/en.arb --lang ar,ja,de
Переводите постепенно: добавив новые ключи в исходный файл ARB, переведите только различия, а не создавайте все файлы заново. Это сохранит переводы, уже проверенные людьми, и метаданные ARB.

Автоматизировать контроль качества перевода

Выявляйте отсутствующие ключи и нарушенные заполнители до выпуска с помощью i18n-validate. Тестируйте интерфейс с псевдопереводами через i18n-pseudo, пока настоящие переводы ещё не готовы.

Распространённые ошибки

Файлы перевода не найдены во время выполнения

Файлы ARB/JSON необходимо объявить в разделе assets файла pubspec.yaml. Без этого Flutter не найдёт их во время выполнения, даже если они существуют на диске. Добавьте: assets: - assets/translations/

Недопустимый синтаксис файла ARB

Файлы ARB являются строгим JSON: без завершающих запятых, одинарных кавычек и комментариев. Одна синтаксическая ошибка без уведомления предотвращает загрузку всего файла. Перед диагностикой переводов проверяйте ARB линтером JSON.

Изменения перевода не появляются при горячей перезагрузке

easy_localization кеширует переводы в памяти. Чтобы применить новые ключи или изменённые переводы, может потребоваться полный горячий перезапуск вместо горячей перезагрузки. Во время разработки после редактирования файлов перевода используйте горячий перезапуск (Shift+R).

Жёстко заданные left/right нарушают RTL

Использование EdgeInsets.only(left: 16) вместо EdgeInsetsDirectional.only(start: 16) мешает Flutter отражать макет для языков RTL. Найдите в кодовой базе EdgeInsets, Alignment и BorderRadius без суффикса Directional.

Рекомендуемая структура файлов

Project Structure
my_flutter_app/
├── assets/
│   └── translations/
│       ├── en.arb          # Source language (English)
│       ├── ar.arb          # Arabic (with 6 plural forms)
│       ├── de.arb          # German
│       ├── ja.arb          # Japanese
│       ├── pt-BR.arb       # Brazilian Portuguese
│       └── pt-PT.arb       # European Portuguese
├── lib/
│   ├── main.dart           # App entry with EasyLocalization
│   ├── app.dart            # MaterialApp with locale delegates
│   └── widgets/
│       └── language_switcher.dart
├── pubspec.yaml            # Dependencies
└── analysis_options.yaml

Попробовать i18n Agent

Перетащите сюда файл перевода

JSON, YAML, PO, XML, CSV, Markdown, Properties

или нажмите, чтобы выбрать

Целевые языки

Регистрация не требуетсяМгновенный расчёт

Резервные локали с locale_chain

Когда в региональной локали, например pt-BR, отсутствует ключ перевода, Flutter сразу переходит на язык шаблона, не проверяя сначала родительскую локаль pt.

Terminal
flutter pub add locale_chain
Configuration
import 'package:locale_chain/locale_chain.dart';

LocaleChain.configure(fallbacks: {
  'pt-BR': ['pt', 'en'],
  'zh-Hant-HK': ['zh-Hant', 'zh', 'en'],
});

LocaleChainAssetLoader(baseLoader: RootBundleAssetLoader());

Полный список поддерживаемых фреймворков и 75 встроенных цепочек приведён в нашем руководстве по резервным локалям. Learn more →

Часто задаваемые вопросы