image

10 Dec 2025

9K

35K

Flutter Localization: Dynamic Language Switching

Building cross-cultural applications often requires supporting multiple languages. Flutter, with its robust internationalization (i18n) and localization (l10n) capabilities, makes it straightforward to adapt your app for different locales. While static localization ensures your app loads with the correct language based on device settings, dynamic language switching empowers users to change the app's language at runtime, enhancing user experience and accessibility. This article will guide you through implementing dynamic language switching in Flutter.

Understanding Flutter Localization Fundamentals

Before diving into dynamic switching, it's essential to understand the basics of Flutter localization. Flutter relies on the flutter_localizations package and the intl package for i18n. The process typically involves:

  1. Defining translatable strings in Application Resource Bundle (.arb) files.
  2. Using Flutter's `gen_l10n` tool to generate Dart code from these .arb files.
  3. Integrating the generated `AppLocalizations` class into your `MaterialApp`.

Setting Up Initial Localization

First, ensure your project is configured for localization.

1. Add Dependencies

Add the necessary dependencies to your

pubspec.yaml
:


dependencies:
  flutter:
    sdk: flutter
  flutter_localizations: # Add this
    sdk: flutter
  intl: ^0.18.0 # Add this

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^2.0.0
  intl_translation: ^0.18.0+1 # Add this for older workflows, but `gen_l10n` is preferred
  
flutter:
  uses-material-design: true
  generate: true # Enable automatic generation of localizations

2. Configure
l10n.yaml

Create a file named

l10n.yaml
in the root of your project:


arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart

3. Create .arb Files

Create a directory

lib/l10n
and add your localization files. For example:

lib/l10n/app_en.arb
(English):


{
    "helloWorld": "Hello World!",
    "currentLanguage": "Current Language: {language}",
    "@currentLanguage": {
        "placeholders": {
            "language": {
                "type": "String"
            }
        }
    },
    "changeLanguage": "Change Language"
}

lib/l10n/app_id.arb
(Indonesian):


{
    "helloWorld": "Halo Dunia!",
    "currentLanguage": "Bahasa Saat Ini: {language}",
    "@currentLanguage": {
        "placeholders": {
            "language": {
                "type": "String"
            }
        }
    },
    "changeLanguage": "Ganti Bahasa"
}

4. Generate Localization Code

Run

flutter gen-l10n
(or simply `flutter run`, as `generate: true` in `pubspec.yaml` will trigger it). This generates
lib/generated/l10n.dart
and related files, including the `AppLocalizations` class.

5. Integrate into
MaterialApp

Set up your `MaterialApp` to use the generated localizations:


// main.dart
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:your_app_name/generated/l10n.dart'; // Adjust import path

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Localization Demo',
      supportedLocales: L10n.all, // L10n.all is a static list of supported locales
      localizationsDelegates: [
        S.delegate, // Generated delegate
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final s = S.of(context); // Access localized strings

    return Scaffold(
      appBar: AppBar(
        title: Text('Localization Demo'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(s.helloWorld),
            Text(s.currentLanguage(Localizations.localeOf(context).languageCode)),
          ],
        ),
      ),
    );
  }
}

At this point, your app will load with the language matching the device's locale (if supported).

Implementing Dynamic Language Switching

To allow users to change the language at runtime, we need a mechanism to update the `locale` property of `MaterialApp` and rebuild the widget tree. A common and effective pattern is to use a state management solution, such as `Provider` (which leverages `ChangeNotifier`), to manage the current locale.

1. Create a `LocaleProvider`

This class will hold the current locale and notify listeners when it changes.


// lib/locale_provider.dart
import 'package:flutter/material.dart';

class LocaleProvider extends ChangeNotifier {
  Locale? _locale;

  Locale? get locale => _locale;

  void setLocale(Locale newLocale) {
    if (!L10n.all.contains(newLocale)) return; // Ensure locale is supported

    _locale = newLocale;
    notifyListeners();
  }

  void clearLocale() {
    _locale = null;
    notifyListeners();
  }
}

2. Wrap
MyApp
with
ChangeNotifierProvider

Use `Provider` (add `provider: ^6.0.0` to `pubspec.yaml` dependencies) to make your `LocaleProvider` accessible throughout the widget tree.


// main.dart (updated)
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:provider/provider.dart';
import 'package:your_app_name/generated/l10n.dart';
import 'package:your_app_name/locale_provider.dart'; // Import your provider

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => LocaleProvider(),
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final localeProvider = Provider.of(context);

    return MaterialApp(
      title: 'Flutter Localization Demo',
      locale: localeProvider.locale, // Use the locale from the provider
      supportedLocales: L10n.all,
      localizationsDelegates: [
        S.delegate,
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],
      home: MyHomePage(),
    );
  }
}

// ... MyHomePage remains the same for now ...

The key change here is `locale: localeProvider.locale`. When `localeProvider.setLocale()` is called, `notifyListeners()` triggers a rebuild of `MyApp` and thus `MaterialApp`, updating the entire app's language.

3. Create a Language Switching UI

Now, let's add buttons or a dropdown to change the language.


// main.dart (updated MyHomePage)
// ... (imports and MyApp class as above) ...

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final s = S.of(context);
    final localeProvider = Provider.of(context, listen: false); // listen: false because we only need to call methods

    return Scaffold(
      appBar: AppBar(
        title: Text(s.changeLanguage),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(s.helloWorld),
            SizedBox(height: 20),
            Text(s.currentLanguage(Localizations.localeOf(context).languageCode)),
            SizedBox(height: 40),
            ElevatedButton(
              onPressed: () {
                localeProvider.setLocale(Locale('en'));
              },
              child: Text('English'),
            ),
            SizedBox(height: 10),
            ElevatedButton(
              onPressed: () {
                localeProvider.setLocale(Locale('id'));
              },
              child: Text('Bahasa Indonesia'),
            ),
            SizedBox(height: 10),
            ElevatedButton(
              onPressed: () {
                // Clear locale to revert to device's default
                localeProvider.clearLocale();
              },
              child: Text('System Default'),
            ),
          ],
        ),
      ),
    );
  }
}

Full Code Example (
main.dart
)


import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:provider/provider.dart';
import 'package:your_app_name/generated/l10n.dart'; // Adjust import path
import 'package:your_app_name/locale_provider.dart'; // Ensure this file exists

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => LocaleProvider(),
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final localeProvider = Provider.of(context);

    return MaterialApp(
      title: 'Flutter Localization Demo',
      locale: localeProvider.locale, // Locale from provider
      supportedLocales: L10n.all, // All supported locales from generated L10n
      localizationsDelegates: [
        S.delegate, // Generated delegate
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final s = S.of(context); // Access localized strings
    final localeProvider = Provider.of(context, listen: false);

    return Scaffold(
      appBar: AppBar(
        title: Text(s.changeLanguage),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(s.helloWorld),
            SizedBox(height: 20),
            Text(s.currentLanguage(Localizations.localeOf(context).languageCode)),
            SizedBox(height: 40),
            ElevatedButton(
              onPressed: () {
                localeProvider.setLocale(Locale('en'));
              },
              child: Text('English'),
            ),
            SizedBox(height: 10),
            ElevatedButton(
              onPressed: () {
                localeProvider.setLocale(Locale('id'));
              },
              child: Text('Bahasa Indonesia'),
            ),
            SizedBox(height: 10),
            ElevatedButton(
              onPressed: () {
                // Clear locale to revert to device's default
                localeProvider.clearLocale();
              },
              child: Text('System Default'),
            ),
          ],
        ),
      ),
    );
  }
}

Persistence (Optional but Recommended)

The current implementation resets the language to the system default whenever the app restarts. To persist the user's chosen language, you can save the `Locale` to local storage (e.g., using shared_preferences) and load it when the `LocaleProvider` is initialized.


// lib/locale_provider.dart (with persistence)
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:your_app_name/generated/l10n.dart'; // For L10n.all

class LocaleProvider extends ChangeNotifier {
  Locale? _locale;

  Locale? get locale => _locale;

  LocaleProvider() {
    _loadPreferredLocale();
  }

  Future _loadPreferredLocale() async {
    final prefs = await SharedPreferences.getInstance();
    final langCode = prefs.getString('languageCode');
    if (langCode != null) {
      _locale = Locale(langCode);
      if (!L10n.all.contains(_locale)) { // Validate loaded locale
          _locale = null; // Fallback to system if not supported
          prefs.remove('languageCode');
      }
      notifyListeners();
    }
  }

  Future setLocale(Locale newLocale) async {
    if (!L10n.all.contains(newLocale)) return;

    _locale = newLocale;
    final prefs = await SharedPreferences.getInstance();
    await prefs.setString('languageCode', newLocale.languageCode);
    notifyListeners();
  }

  Future clearLocale() async {
    _locale = null;
    final prefs = await SharedPreferences.getInstance();
    await prefs.remove('languageCode');
    notifyListeners();
  }
}

Remember to add `shared_preferences: ^2.0.0` to your `pubspec.yaml` dependencies for this feature.

Conclusion

Dynamic language switching is a powerful feature that significantly improves the user experience of localized Flutter applications. By combining Flutter's built-in localization tools with a simple state management pattern like `Provider` and `ChangeNotifier`, you can effortlessly give users control over the app's language, even persisting their preference across app sessions. This approach is scalable, maintainable, and integrates seamlessly with Flutter's reactive widget system.

Related Articles

Dec 19, 2025

Building a Widget List with Sticky

Building a Widget List with Sticky Header in Flutter Creating dynamic and engaging user interfaces is crucial for modern applications. One common UI pattern th

Dec 19, 2025

Mastering Transform Scale & Rotate Animations in Flutter

Mastering Transform Scale & Rotate Animations in Flutter Flutter's powerful animation framework allows developers to create visually stunning and highly intera

Dec 19, 2025

Building a Countdown Timer Widget in Flutter

Building a Countdown Timer Widget in Flutter Countdown timers are a fundamental component in many modern applications, ranging from e-commerce platforms indica