boeledi
boeledi

Reputation: 7577

Flutter: Multi-lingual application - how to override the locale?

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.

Upvotes: 25

Views: 24730

Answers (5)

aabiro
aabiro

Reputation: 4288

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.

Upvotes: 0

Mahesh Jamdade
Mahesh Jamdade

Reputation: 20379

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

Upvotes: 8

ilikerobots
ilikerobots

Reputation: 787

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

Upvotes: 30

Richard RJUK
Richard RJUK

Reputation: 531

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

Upvotes: 0

AbdulRahman AlHamali
AbdulRahman AlHamali

Reputation: 1941

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.

Upvotes: 24

Related Questions