25

I followed the explanations given in the official Flutter pages (see here) to make my application work in different languages.

According to the documentation, it retrieves the user's locale and this works fine.

Let's now suppose that my application supports different languages (such as EN, FR, ES, ...) and that the user could select one of these languages to use the application (the selected language would then be different than the one defined in the phone's settings), how can I achieve this?

How may I force the application Locale and dynamically "reload" all the translations?

The Flutter page does not explain this and I haven't seen anything that help me in the documentation...

Here is the current implementation:

class Translations {
  Translations(this.locale);

  final Locale locale;

  static Translations of(BuildContext context){
    return Localizations.of<Translations>(context, Translations);
  }

  static Map<String, Map<String, String>> _localizedValues = {
    'en': {
      'title': 'Hello',
    },
    'fr': {
      'title': 'Bonjour',
    },
    'es': {
      'title': 'Hola',
    }
  };

  String text(String key){
    return _localizedValues[locale.languageCode][key] ?? '** ${key} not found';
  }
}

class TranslationsDelegate extends LocalizationsDelegate<Translations> {
  const TranslationsDelegate();

  @override
  bool isSupported(Locale locale) => ['en', 'fr','es'].contains(locale.languageCode);

  @override
  Future<Translations> load(Locale locale) {
    return new SynchronousFuture<Translations>(new Translations(locale));
  }

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

In the main.dart:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: Translations.of(context).text('title'),
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      localizationsDelegates: [
        const TranslationsDelegate(),
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
      ],
      supportedLocales: [
        const Locale('en', ''), // English
        const Locale('fr', ''), // French
        const Locale('fr', ''), // French
      ],
        home: new LandingPage(),
    );
  }
}

Many thanks for your help.

boeledi
  • 6,837
  • 10
  • 32
  • 42

5 Answers5

30

This can be accomplished by

  1. creating a new LocalizationsDelegate that either translates to a single locale or defers completely depending on a parameter
  2. converting the base app (MyApp) to a stateful widget and inserting the new delegate above into the localizationsDelegates list
  3. managing the base app (MyApp) state with a new delegate targeting a specific locale based on some event

A simple implementation for 1) might be:

class SpecifiedLocalizationDelegate
    extends LocalizationsDelegate<Translations> {
  final Locale overriddenLocale;

  const SpecifiedLocalizationDelegate(this.overriddenLocale);

  @override
  bool isSupported(Locale locale) => overriddenLocale != null;

  @override
  Future<Translations> load(Locale locale) =>
      Translations.load(overriddenLocale);

  @override
  bool shouldReload(SpecifiedLocalizationDelegate old) => true;
}

Next for 2) and 3), convert the MyApp to stateful and include the new delegate (initially just deferring everything), plus some event handlers to change the state with a new delegate that specifies a new Locale.

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

class _MyAppState extends State<MyApp> {
  SpecifiedLocalizationDelegate _localeOverrideDelegate;

  @override
  void initState() {
    super.initState();
    _localeOverrideDelegate = new SpecifiedLocalizationDelegate(null);
  }

  onLocaleChange(Locale l) {
    setState(() {
      _localeOverrideDelegate = new SpecifiedLocalizationDelegate(l);
    });
  }

  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      localizationsDelegates: [
        _localeOverrideDelegate,
        const TranslationsDelegate(),
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
      ],
      supportedLocales: [
        const Locale('en', ''), // English
        const Locale('fr', ''), // French
      ],
      home: new LandingPage(onLocaleSwitch: onLocaleChange),
    );
  }
}

With these changes, in children widgets you could now use Translations.of(context).myLocalizedString to retrieve the translations.

More complete gist: https://gist.github.com/ilikerobots/474b414138f3f99150dbb3d0cc4cc721

Ahmed Ashour
  • 5,179
  • 10
  • 35
  • 56
ilikerobots
  • 787
  • 6
  • 16
  • Hey @ilikerobots, I have a little problem implementing your solution. In the class Translations, method load(Locale locale), you call initializeMessages, which is not part of your gist. Could you please tell me what does this method do? Thanks – boeledi Mar 31 '18 at 09:17
  • Sorry, I was making the assumption that you were using the intl tools, which I realize now may not be the case. The initializeMessages function is generated when using intl/generate_localized.dart, as described in the appendex at https://flutter.io/tutorials/internationalization/ – ilikerobots Apr 02 '18 at 06:31
  • The localization used from intl in this way requires initialization before a specific locale can be used. If your implementation doesn't require this, then you can likely skip that call. – ilikerobots Apr 02 '18 at 06:40
  • 3
    I wrote a complete article to describe my solution at https://www.didierboelens.com/2018/04/internationalization---make-an-flutter-application-multi-lingual/ – boeledi Apr 21 '18 at 16:17
  • Having a similiar issue here with persisting the chosen langueage if you don't mind helping https://stackoverflow.com/questions/71854441/flutter-make-changing-app-language-persistent-while-using-provider?noredirect=1#comment126974868_71854441 – LearnFlutter Apr 13 '22 at 09:44
23

To control the locale of the app, you can use the locale property of the MaterialApp:

return MaterialApp(
  ...
  locale: _myLocal,
  ...
);

This, combined with @ilikerobots StatefulWidget approach shall provide you with what you need.

AbdulRahman AlHamali
  • 1,851
  • 1
  • 14
  • 18
8

using one of the Providers should do the job, I am not really familiar with providers but this got me working easily

  1. wrap your material app using ChangeNotifierProvider
    return ChangeNotifierProvider(
        create: (_) => new LocaleModel(),
        child: Consumer<LocaleModel>(
            builder: (context, provider, child) => MaterialApp(
                title: 'myapp',
                locale: Provider.of<LocaleModel>(context).locale
                 ...
                 ...
                 ...

  1. create A model class with getters and setters to get & set the locale as\
import 'package:iborganic/const/page_exports.dart';

class LocaleModel with ChangeNotifier {
  Locale locale = Locale('en');
  Locale get getlocale => locale;
  void changelocale(Locale l) {
    locale = l;
    notifyListeners();
  }
}

  1. Change the locale on some event (button click) as
Provider.of<LocaleModel>(context).changelocale(Locale("kn"));

The benefit of wrapping the material app within Provider is you can have access to the locale value from any part of your app

Ahmed Ashour
  • 5,179
  • 10
  • 35
  • 56
Mahesh Jamdade
  • 17,235
  • 8
  • 110
  • 131
  • No this approach does not work. It crashes in Localiyations.of because there is no _LocaliyationsScope in the widget tree. The function `final _LocalizationsScope scope = context.dependOnInheritedWidgetOfExactType<_LocalizationsScope>();` returns null. – Daniel Mar 13 '20 at 14:37
  • I have posted this answer after trying it out myself,And I can assure you that it works – Mahesh Jamdade Mar 13 '20 at 14:50
  • I further digget into it. The crash is related to the fact that in my prototype some widgets are generated via functions. This usually works fine. For some reason however this does not provide a localization scope. It is explained in more details here: https://stackoverflow.com/questions/53234825/what-is-the-difference-between-functions-and-classes-to-create-reusable-widgets/53234826#53234826. After that change it also worked for me. Thanks. – Daniel Mar 13 '20 at 22:58
0

The easiest way, which weirdly enough is not mentioned in the internationalization tutorial, is using the locale property. This property of the MaterialApp class allows us to immediately specify what locale we want our app to use

 return MaterialApp(
  locale: Locale('ar', ''),
  localizationsDelegates: [
    MyLocalizationsDelegate,
    GlobalMaterialLocalizations.delegate,
    GlobalWidgetsLocalizations.delegate,
  ],
  supportedLocales: [
    const Locale('en', ''), // English
    const Locale('ar', ''), // Arabic
  ],
  home: HomeScreen()
);

This tutorial explained it better

It also explained how to load the locale preference from sharedPreferences

Charden Daxicen
  • 405
  • 5
  • 10
0

Use the locale param on Material App to override the application default. If the 'locale' is null then the system's locale value is used. But make sure you are passing a recognizable Locale, Locale.fromSubtags worked for me:

 return MaterialApp(
   locale: Locale.fromSubtags(languageCode: 'es'),
   localizationsDelegates: [
     ...
 );

And if you want to change this value later use Provider and wrap Material App with Consumer to watch for changes for instance.

aabiro
  • 3,842
  • 2
  • 23
  • 36