Button to change the language Flutter

25,547

Solution 1

You can set the locale property of MaterialApp with your favourite state management method. For example:

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();

  static _MyAppState of(BuildContext context) => context.findAncestorStateOfType<_MyAppState>();
}

class _MyAppState extends State<MyApp> {
  Locale _locale;

  void setLocale(Locale value) {
    setState(() {
      _locale = value;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      locale: _locale,
      home: Dashboard(),
    );
  }
}

class Dashboard extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        TextButton(
          child: Text("Set locale to German"),
          onPressed: () => MyApp.of(context).setLocale(Locale.fromSubtags(languageCode: 'de')),
        ),
        TextButton(
          child: Text("Set locale to English"),
          onPressed: () => MyApp.of(context).setLocale(Locale.fromSubtags(languageCode: 'en')),
        ),
      ],
    );
  }
}

Solution 2

You have to use Localizations given from Flutter. You have to use custom delegate and JSON files for your supported languages. I implemented using bloc

Steps to follow,

  1. Create a folder assets/languages/ in the root folder
  2. Create JSON files for your supported languages. Like: en.json, es.json
  3. Create a key, value pairs for your strings in each file accordingly with their specific language strings
  4. In main.dart create default locale, supportedLocales and localizationsDelegates.
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_localizations/flutter_localizations.dart';

import 'package:movie_app/common/constants/languages.dart';
import 'package:movie_app/presentation/app_localizations.dart';
import 'package:movie_app/presentation/blocs/language/language_bloc.dart';
import 'package:movie_app/presentation/journeys/home/home_screen.dart';

class MovieApp extends StatefulWidget {
  @override
  _MovieAppState createState() => _MovieAppState();
}

class _MovieAppState extends State<MovieApp> {
  LanguageBloc _languageBloc;

  @override
  void initState() {
    _languageBloc = LanguageBloc();
    super.initState();
  }

  @override
  void dispose() {
    _languageBloc.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return BlocProvider<LanguageBloc>.value(
      value: _languageBloc,
      child: BlocBuilder<LanguageBloc, LanguageState>(
        builder: (context, state) {
          if (state is LanguageLoaded) {
            return MaterialApp(
                debugShowCheckedModeBanner: false,
                title: 'Movie App',
                home: HomeScreen(),
                supportedLocales:
                    Languages.languages.map((e) => Locale(e.code)).toList(),
                locale: state.locale,
                localizationsDelegates: [
                  AppLocalizations.delegate,
                  GlobalMaterialLocalizations.delegate,
                  GlobalWidgetsLocalizations.delegate,
                ],
              );
          }
          return SizedBox.shrink();
        },
      ),
    );
  }
}
  1. Now create Languages Language models and constants
class LanguageEntity {
  final String code;
  final String value;

  const LanguageEntity({
     this.code,
     this.value,
  });
}

class Languages {
  const Languages._();

  static const languages = [
    LanguageEntity(code: 'en', value: 'English'),
    LanguageEntity(code: 'es', value: 'Spanish'),
  ];
}

  1. Now Create app localization delegate
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:movie_app/common/constants/languages.dart';

class AppLocalizations {
  final Locale locale;

  AppLocalizations(this.locale);

  static const LocalizationsDelegate<AppLocalizations> delegate =
      _AppLocalizationDelagate();

  static AppLocalizations of(context) =>
      Localizations.of<AppLocalizations>(context, AppLocalizations);

  Map<String, String> _localisedString;

  Future<bool> load() async {
    final jsonString = await rootBundle
        .loadString('assets/languages/${locale.languageCode}.json');
    final Map<String, dynamic> jsonMap = json.decode(jsonString);
    _localisedString =
        jsonMap.map((key, value) => MapEntry(key, value.toString()));
    return true;
  }

  String translate(String key) {
    return _localisedString[key];
  }
}

class _AppLocalizationDelagate extends LocalizationsDelegate<AppLocalizations> {
  const _AppLocalizationDelagate();

  @override
  bool isSupported(Locale locale) {
    return Languages.languages
        .map((e) => e.code)
        .toList()
        .contains(locale.languageCode);
  }

  @override
  bool shouldReload(covariant LocalizationsDelegate old) {
    return false;
  }

  @override
  Future<AppLocalizations> load(Locale locale) async {
    AppLocalizations appLocalizations = AppLocalizations(locale);
    await appLocalizations.load();
    return appLocalizations;
  }
}
  1. Now create blocs
import 'dart:async';

import 'package:bloc/bloc.dart';
// import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:movie_app/common/constants/languages.dart';
import 'package:movie_app/domain/entities/language_entity.dart';

part 'language_event.dart';
part 'language_state.dart';

class LanguageBloc extends Bloc<LanguageEvent, LanguageState> {
  LanguageBloc() : super(LanguageLoaded(Locale(Languages.languages[0].code)));

  @override
  Stream<LanguageState> mapEventToState(
    LanguageEvent event,
  ) async* {
    if (event is ToggleLanguageEvent) {
      yield LanguageLoaded(Locale(event.language.code));
    }
  }
}

8.Now create event

part of 'language_bloc.dart';

abstract class LanguageEvent {
  const LanguageEvent();
}

class ToggleLanguageEvent extends LanguageEvent {
  final LanguageEntity language;

  ToggleLanguageEvent(this.language);
}
  1. Now create state
part of 'language_bloc.dart';

abstract class LanguageState {
  const LanguageState();

}

class LanguageLoaded extends LanguageState {
  final Locale locale;

  LanguageLoaded(this.locale);
}

10.Now Create button to change languages.

RaisedButton(child: ,RaisedButton(child: Text('Switch', 
     onPressed: (int index) {
        BlocProvider.of<LanguageBloc>(context).add(
          ToggleLanguageEvent(
            Languages.languages[index], // index value can be 0 or 1 in our case
          ),                            // 0 - en, 1 - es
        );
        Navigator.of(context).pop();
      },
);

Also, please refer the link for clear implementation https://www.youtube.com/watch?v=W-2p3zB1z8k

Solution 3

If you are using provider as state management then you can follow this article .

https://medium.com/flutter-community/flutter-internationalization-the-easy-way-using-provider-and-json-c47caa4212b2

Solution 4

Because accepted answer has some shortcomings I will set new one:

Note 1: I was used SharedPreferences plugin to save locale which client select.

Note 2: Replace ar and ps with your default locale ...

Full code of main.dart file:

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:shared_preferences/shared_preferences.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized(); //To solve problem (ServicesBinding.defaultBinaryMessenger was accessed before the binding was initialized)
  runApp(MainApp());
}

class MainApp extends StatefulWidget {
  const MainApp({Key? key}) : super(key: key);
  
  /*
  To Change Locale of App
   */
  static void setLocale(BuildContext context, Locale newLocale) async {
    _MainAppState? state = context.findAncestorStateOfType<_MainAppState>();

    var prefs = await SharedPreferences.getInstance();
    prefs.setString('languageCode', newLocale.languageCode);
    prefs.setString('countryCode', "");

    state?.setState(() {
      state._locale = newLocale;
    });
    
  }

  @override
  _MainAppState createState() => _MainAppState();
}

class _MainAppState extends State<MainApp> {
  Locale _locale = Locale('ar', 'ps');

  @override
  void initState() {
    super.initState();
    this._fetchLocale().then((locale) {
      setState(() {
        this._locale = locale;
      });
    });
  }


  /*
  To get local from SharedPreferences if exists
   */
  Future<Locale> _fetchLocale() async {
    var prefs = await SharedPreferences.getInstance();

    String languageCode = prefs.getString('languageCode') ?? 'ar';
    String countryCode = prefs.getString('countryCode') ?? 'ps';

    return Locale(languageCode, countryCode);
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      locale: _locale,
      localizationsDelegates: [
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
        const FallbackCupertinoLocalisationsDelegate(),
      ],
      supportedLocales: [
        const Locale('en', ''), // English, no country code
        const Locale('ar', ''), // Arabic, no country code
      ],
      theme: ThemeData(
        primarySwatch: Colors.deepPurple,
      ),
      home: InitializeApp(), // here use your own home name...
    );
  }
}


/*
  To solve problem of hold press on inputs
 */
class FallbackCupertinoLocalisationsDelegate extends LocalizationsDelegate<CupertinoLocalizations> {
  const FallbackCupertinoLocalisationsDelegate();

  @override
  bool isSupported(Locale locale) => true;

  @override
  Future<CupertinoLocalizations> load(Locale locale) =>
      DefaultCupertinoLocalizations.load(locale);

  @override
  bool shouldReload(FallbackCupertinoLocalisationsDelegate old) => false;
}

Now from any other class you need to change locale from it, you can use my way:

Import main.dart file, Then use my simple DropdownButton:

Note: replace value: AppLocalizations.of(context)!.locale.toString(), this line with your way to get current locale of app...

import 'package:yout_project_name/main.dart';

DropdownButton(
  onChanged: (v) => setState(() {
    MainApp.setLocale(context, Locale(v.toString(), ""));
  }),
  value: AppLocalizations.of(context)!.locale.toString(), // change this line with your way to get current locale to select it as default in dropdown
  items: [
    DropdownMenuItem(
      child: Text( 'English'), value: 'en'
    ),
    DropdownMenuItem(
      child: Text( 'العربية'), value: 'ar'
    ),
  ],
)

Solution 5

Take a look at the language_builder package in pub.dev It is very easy to use. By wrapping your root widget with LanguageBuilder you can configure your app's language. Tell your app to use the phones' language or change it manually from the app.

https://pub.dev/packages/language_builder

Share:
25,547
Luthermilla Ecole
Author by

Luthermilla Ecole

Updated on July 09, 2022

Comments

  • Luthermilla Ecole
    Luthermilla Ecole almost 2 years

    I am building an app in Flutter, so far I am using the Internationalization with JSON where the language of the app is based on the language that the user has as default in his phone its working pretty well, but I would like to give the user a chance to change the language without changing phone the system language settings, by only clicking in a button and then the application change the language without going through the settings.

    Here is the code:

    The Main:

    import 'package:flutter/material.dart';
    import 'package:flutter_app_darkmode/app_localizations.dart';
    import 'package:flutter_localizations/flutter_localizations.dart';
    
      void main() => runApp(MyApp());
    
      class MyApp extends StatelessWidget {
          @override
          Widget build(BuildContext context) => MaterialApp(
          supportedLocales: [
          Locale('en', "ZA"),
          Locale('pt', "MZ"),
          ],
          localizationsDelegates: [
          AppLocalizations.delegate,
          GlobalMaterialLocalizations.delegate,
          GlobalWidgetsLocalizations.delegate
          ],
          localeResolutionCallback: (locale, supportedLocales) {
           for (var supportedLocale in supportedLocales) {
            if (supportedLocale.languageCode == locale.languageCode &&
                supportedLocale.countryCode == locale.countryCode) {
              return supportedLocale;
              } else {
              if (MyHomePage.local != null) {
                for (int i = 0; i < supportedLocales.length; i++) {
                  if (MyHomePage.local == supportedLocales.elementAt(i)) {
                    return supportedLocales.elementAt(i);
                  }}}}}
                return supportedLocales.first;
                },
               home: MyHomePage(),
              );}
    
             class MyHomePage extends StatefulWidget {
             @override
             _MyHomePageState createState() => _MyHomePageState();
    
               class _MyHomePageState extends State<MyHomePage> {
               getLocale() {
               Locale myLocale = Localizations.localeOf(context);
                print(myLocale);}
    
                @override
                 Widget build(BuildContext context) {
                  getLocale();
                   return Scaffold(
                    body: Center(
                    child: Padding(
                     padding: const EdgeInsets.all(8.0),
                      child: Column(
                      mainAxisSize: MainAxisSize.min,
                      children: <Widget>[
                       Text(
                       AppLocalizations.of(context).translate('first_string'),
                       style: TextStyle(fontSize: 25),
                       textAlign: TextAlign.center,),
                      Text(
                      AppLocalizations.of(context).translate('second_string'),
                        style: TextStyle(fontSize: 25),
                        textAlign: TextAlign.center,),
                        RaisedButton(
                          child: Text('PT'),
                        onPressed: () {},
                         ),],),),),);}}
    

    The app_locations class:

    import 'dart:async';
    import 'dart:convert';
    import 'package:flutter/material.dart';
    import 'package:flutter/services.dart';
    
    class AppLocalizations {
    final Locale locale;
    
      AppLocalizations(this.locale);
    
      static AppLocalizations of(BuildContext context) {
       return Localizations.of<AppLocalizations>(context, AppLocalizations);
      }
    
        static const LocalizationsDelegate<AppLocalizations> delegate =
        _AppLocalizationsDelegate();
    
        Map<String, String> _localizedStrings;
    
           Future<bool> load() async {
           String jsonString =
            await rootBundle.loadString('lang/${locale.languageCode}.json');
    
            Map<String, dynamic> jsonMap = json.decode(jsonString);
    
           _localizedStrings = jsonMap.map((key, value) {
           return MapEntry(key, value.toString());
            });
    
            return true;}
    
           String translate(String key) {
          return _localizedStrings[key];}}
    
         class _AppLocalizationsDelegate
         extends LocalizationsDelegate<AppLocalizations> {
         const _AppLocalizationsDelegate();
    
           @override
           bool isSupported(Locale locale) {
           return ['en', 'pt'].contains(locale.languageCode);}
    
           @override
           Future<AppLocalizations> load(Locale locale) async {
           AppLocalizations localizations = new AppLocalizations(locale);
           await localizations.load();
           return localizations;}
    
            @override
            bool shouldReload(_AppLocalizationsDelegate old) => false;}
    
  • drpawelo
    drpawelo almost 3 years
    this worked for me! With a small modification: I could not get it to work when my FlutterApp had a StatfulWidget (eg MyApp) as the main 'home'. So I wrapped the stateful MyApp widget into a stateless widget (eg. MyAppWrapper). The only thing that the MyAppWrapper does is it has a build method that returns MyApp().
  • Marcus
    Marcus almost 3 years
    but this wouldn't survive app restarts, would it?
  • spkersten
    spkersten almost 3 years
    @Marcus It does indeed not survive app restarts. You'd have to persist the data.
  • Trenton McKinney
    Trenton McKinney over 2 years
    Questions asking for resources are off-topic. This answer is only offering a link to a new resource, and doesn't show how to implement the resource to solve the question in the OP. While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - From Review
  • LearnFlutter
    LearnFlutter about 2 years
    Any example that persists the changes
  • Sam Bains
    Sam Bains about 2 years
    @LearnFlutter you could use the Shared Preferences package and persist the data in there when the selection changes. Then to read the data, you could do this: @override void didChangeDependencies() { SharedPreferencesHelper.getLanguageCode().then((languageCode‌​) { setState(() { _locale = Locale(languageCode); }); }); super.didChangeDependencies(); }