Flutter Integration

Learn how to integrate internationalization with Flutter applications.

Integrate internationalization with your Flutter application to support multiple languages using Lengrise-managed JSON translations, with a similar approach to i18next.

Prerequisites

  • Flutter project (v3.0.0+)
  • Dart (v2.17.0+)
  • Translations downloaded from Lengrise

Pull Translation Files

First, you must pull your translation files from Lengrise API. These files will contain all the translated text for your application.

  1. Go to the Installation Guide and follow the steps to download your translations
  2. Place the downloaded JSON files in your project as shown in the project structure below
  3. Ensure that each language has its own JSON file named with the language code (e.g., en.json, es.json)

Integration Setup

After downloading your translation files, add the necessary packages to your pubspec.yaml:

dependencies:
  flutter:
    sdk:
      flutter flutter_i18n: ^0.33.0
  flutter_localizations:
    sdk:
      flutter shared_preferences: ^2.1.0
      provider: ^6.0.5

Run flutter pub get to install the dependencies.

Project Structure

pubspec.yamladd
analysis_options.yamladd
libadd
main.dartadd
servicesadd
i18n_service.dartadd
locale_provider.dartadd
screensadd
home_screen.dartadd
settings_screen.dartadd
widgetsadd
language_switcher.dartadd
assetsadd
i18nadd
en.jsonadd
es.jsonadd

Configuration Steps

1. Update your pubspec.yaml

Make sure to add the assets path to your pubspec.yaml to include your translation JSON files:

flutter:
  assets:
    - assets/i18n/

2. Create an i18n Service

Create a lib/services/i18n_service.dart file:

import 'dart:convert';
import 'package:flutter/services.dart';
import 'package:flutter/material.dart';

class I18nService {
  // Private constructor
  I18nService._();

  // Singleton instance
  static final I18nService _instance = I18nService._();

  // Factory constructor to return the singleton instance
  factory I18nService() => _instance;

  // Map of loaded translations
  Map<String, dynamic> _translations = {};
  String _currentLocale = 'en';

  // Getter for current locale
  String get currentLocale => _currentLocale;

  // Initialize translations
  Future<void> init(String locale) async {
    await loadTranslations(locale);
  }

  // Load translations for a specific locale
  Future<void> loadTranslations(String locale) async {
    try {
      _currentLocale = locale;
      final jsonString = await rootBundle.loadString('assets/i18n/$locale.json');
      _translations = json.decode(jsonString);
    } catch (e) {
      debugPrint('Error loading translations: $e');
      // Fall back to empty translations map
      _translations = {};
    }
  }

  // Get a translation by key
  String translate(String key, {Map<String, dynamic>? params}) {
    final keys = key.split('.');
    dynamic value = _translations;

    // Navigate through nested JSON
    for (final k in keys) {
      if (value is Map && value.containsKey(k)) {
        value = value[k];
      } else {
        return key; // Key not found, return the key itself
      }
    }

    if (value is String) {
      // Process the string with parameters if provided
      if (params != null && params.isNotEmpty) {
        String result = value;
        params.forEach((paramKey, paramValue) {
          result = result.replaceAll('{{$paramKey}}', paramValue.toString());
        });
        return result;
      }
      return value;
    }

    return key; // If the value is not a string, return the key
  }

  // Handle pluralization
  String translatePlural(String key, int count) {
    final keys = key.split('.');
    dynamic value = _translations;

    // Navigate through nested JSON
    for (final k in keys) {
      if (value is Map && value.containsKey(k)) {
        value = value[k];
      } else {
        return key; // Key not found, return the key itself
      }
    }

    if (value is Map) {
      // Check if the map has pluralization keys
      if (count == 0 && value.containsKey('zero')) {
        return value['zero'].replaceAll('{{count}}', count.toString());
      } else if (count == 1 && value.containsKey('one')) {
        return value['one'].replaceAll('{{count}}', count.toString());
      } else if (value.containsKey('other')) {
        return value['other'].replaceAll('{{count}}', count.toString());
      }
    }

    return key; // Unable to handle pluralization, return the key
  }
}

3. Create a Locale Provider

Create a lib/services/locale_provider.dart file to manage locale state:

import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'i18n_service.dart';

class LocaleProvider with ChangeNotifier {
  final I18nService _i18nService = I18nService();
  Locale _locale = const Locale('en');
  final String _prefsKey = 'app_locale';

  LocaleProvider() {
    _loadSavedLocale();
  }

  Locale get locale => _locale;
  I18nService get i18n => _i18nService;

  // Helper method for translations
  String t(String key, {Map<String, dynamic>? params}) {
    return _i18nService.translate(key, params: params);
  }

  // Helper method for pluralization
  String plural(String key, int count) {
    return _i18nService.translatePlural(key, count);
  }

  // Get all supported locales
  List<Locale> get supportedLocales {
    return const [
      Locale('en'), // English
      Locale('es'), // Spanish
    ];
  }

  // Change the locale
  Future<void> setLocale(Locale locale) async {
    if (!isSupported(locale)) return;

    _locale = locale;
    await _i18nService.loadTranslations(locale.languageCode);
    await _saveLocale(locale.languageCode);
    notifyListeners();
  }

  // Check if a locale is supported
  bool isSupported(Locale locale) {
    return supportedLocales.contains(
      Locale(locale.languageCode)
    );
  }

  // Load the saved locale from storage
  Future<void> _loadSavedLocale() async {
    final prefs = await SharedPreferences.getInstance();
    final savedLocale = prefs.getString(_prefsKey);

    if (savedLocale != null && isSupported(Locale(savedLocale))) {
      _locale = Locale(savedLocale);
      await _i18nService.loadTranslations(savedLocale);
      notifyListeners();
    } else {
      // Initialize with default locale
      await _i18nService.init(_locale.languageCode);
    }
  }

  // Save the locale to storage
  Future<void> _saveLocale(String languageCode) async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.setString(_prefsKey, languageCode);
  }
}

4. Update Your Main App File

Update your lib/main.dart file to include internationalization support:

import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:provider/provider.dart';
import 'package:your_app_name/services/locale_provider.dart';
import 'package:your_app_name/screens/home_screen.dart';
import 'package:your_app_name/screens/settings_screen.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();

  runApp(
    ChangeNotifierProvider(
      create: (context) => LocaleProvider(),
      child: const MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    final localeProvider = Provider.of<LocaleProvider>(context);

    return MaterialApp(
      title: localeProvider.t('app.title'),

      // Locale configuration
      locale: localeProvider.locale,
      supportedLocales: localeProvider.supportedLocales,
      localizationsDelegates: const [
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],

      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),

      // App routes
      initialRoute: '/',
      routes: {
        '/': (context) => const HomeScreen(),
        '/settings': (context) => const SettingsScreen(),
      },
    );
  }
}

5. Create a Language Switcher Widget

Create a lib/widgets/language_switcher.dart file:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:your_app_name/services/locale_provider.dart';

class LanguageSwitcher extends StatelessWidget {
  const LanguageSwitcher({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    final localeProvider = Provider.of<LocaleProvider>(context);

    return Card(
      margin: const EdgeInsets.all(16),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              localeProvider.t('settings.language'),
              style: const TextStyle(
                fontSize: 18,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 12),
            Wrap(
              spacing: 8,
              children: [
                _LanguageButton(
                  language: localeProvider.t('languages.english'),
                  locale: const Locale('en'),
                  isSelected: localeProvider.locale.languageCode == 'en',
                  onTap: () => localeProvider.setLocale(const Locale('en')),
                ),
                _LanguageButton(
                  language: localeProvider.t('languages.spanish'),
                  locale: const Locale('es'),
                  isSelected: localeProvider.locale.languageCode == 'es',
                  onTap: () => localeProvider.setLocale(const Locale('es')),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

class _LanguageButton extends StatelessWidget {
  final String language;
  final Locale locale;
  final bool isSelected;
  final VoidCallback onTap;

  const _LanguageButton({
    required this.language,
    required this.locale,
    required this.isSelected,
    required this.onTap,
  });

  
  Widget build(BuildContext context) {
    return InkWell(
      onTap: onTap,
      borderRadius: BorderRadius.circular(8),
      child: Container(
        padding: const EdgeInsets.symmetric(
          horizontal: 16,
          vertical: 8,
        ),
        decoration: BoxDecoration(
          color: isSelected ? Theme.of(context).primaryColor : Colors.grey[200],
          borderRadius: BorderRadius.circular(8),
        ),
        child: Text(
          language,
          style: TextStyle(
            color: isSelected ? Colors.white : Colors.black87,
          ),
        ),
      ),
    );
  }
}

6. Create a Settings Screen

Create a lib/screens/settings_screen.dart file:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:your_app_name/services/locale_provider.dart';
import 'package:your_app_name/widgets/language_switcher.dart';

class SettingsScreen extends StatelessWidget {
  const SettingsScreen({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    final localeProvider = Provider.of<LocaleProvider>(context);

    return Scaffold(
      appBar: AppBar(
        title: Text(localeProvider.t('settings.title')),
      ),
      body: ListView(
        children: const [
          LanguageSwitcher(),
          // Add other settings here
        ],
      ),
    );
  }
}

7. Create a Home Screen

Create a lib/screens/home_screen.dart file:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:your_app_name/services/locale_provider.dart';

class HomeScreen extends StatelessWidget {
  const HomeScreen({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    final localeProvider = Provider.of<LocaleProvider>(context);

    return Scaffold(
      appBar: AppBar(
        title: Text(localeProvider.t('home.welcome')),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Center(
              child: Text(
                localeProvider.t('home.welcome'),
                style: const TextStyle(
                  fontSize: 24,
                  fontWeight: FontWeight.bold,
                ),
              ),
            ),
            const SizedBox(height: 8),
            Center(
              child: Text(
                localeProvider.t('home.description'),
                style: const TextStyle(
                  fontSize: 16,
                ),
                textAlign: TextAlign.center,
              ),
            ),
            const SizedBox(height: 24),
            Text(
              localeProvider.t('features.title'),
              style: const TextStyle(
                fontSize: 20,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 8),
            _buildFeature(context, localeProvider.t('features.easy_to_use')),
            _buildFeature(context, localeProvider.t('features.fast_translation')),
            _buildFeature(context, localeProvider.t('features.flexible_integration')),
            const SizedBox(height: 24),
            Center(
              child: Text(
                localeProvider.t('greeting', params: {'name': 'Flutter Developer'}),
                style: const TextStyle(fontSize: 18),
              ),
            ),
            const SizedBox(height: 24),
            Center(
              child: Text(
                localeProvider.plural('items', 5),
                style: const TextStyle(fontSize: 18),
              ),
            ),
            const SizedBox(height: 24),
            ElevatedButton(
              onPressed: () {
                Navigator.pushNamed(context, '/settings');
              },
              child: Text(localeProvider.t('settings.title')),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildFeature(BuildContext context, String text) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 4),
      child: Row(
        children: [
          const Icon(Icons.check_circle, color: Colors.green),
          const SizedBox(width: 8),
          Text(text, style: const TextStyle(fontSize: 16)),
        ],
      ),
    );
  }
}

Using Translations

Basic Usage

Access translations in your widgets using the provider:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:your_app_name/services/locale_provider.dart';

class MyWidget extends StatelessWidget {
  
  Widget build(BuildContext context) {
    final localeProvider = Provider.of<LocaleProvider>(context);

    return Column(
      children: [
        // Simple translation
        Text(localeProvider.t('home.welcome')),

        // Translation with parameters
        Text(localeProvider.t('greeting', params: {'name': 'John'})),

        // Pluralization
        Text(localeProvider.plural('items', 3)),
      ],
    );
  }
}

Switching Languages

The language can be switched by calling the setLocale method on your locale provider:

// Inside a widget
final localeProvider = Provider.of<LocaleProvider>(context, listen: false);
localeProvider.setLocale(Locale('es'));

Custom Extension (Optional)

For cleaner code, you can add an extension on the BuildContext:

// In a separate file, e.g., lib/extensions/context_extensions.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:your_app_name/services/locale_provider.dart';

extension ContextExtension on BuildContext {
  LocaleProvider get localeProvider => Provider.of<LocaleProvider>(this, listen: true);

  String t(String key, {Map<String, dynamic>? params}) {
    return localeProvider.t(key, params: params);
  }

  String plural(String key, int count) {
    return localeProvider.plural(key, count);
  }
}

// Then in widgets:
// Text(context.t('home.welcome'))
// Text(context.plural('items', 3))

Resources

For more detailed information, check out these resources: