Bloc: [docs] Include app internationalisation sample with BloC

Created on 17 Feb 2020  路  4Comments  路  Source: felangel/bloc

Hi guys,
Internationalisations in Flutter is not something simply, mostly for new developers. The more difficult behaviour to obtain is the language change at runtime.
Flutter_Bloc library docs is already filled with a lot of useful and really well written samples. But what about including a sample that shows how to change language at runtime?
Thanks

example question

All 4 comments

Hi @enricobenedos 馃憢
Thanks for opening an issue!

Can you provide some more context please? Do you mean use bloc to change the application language independently of the device language? Normally, I would recommend using intl to manage your translations and it also provides an api to query the current device locale. You can see a completed example here with detailed documentation.

Let me know if those links help or if you're still interested in pursuing a bloc-specific use case.

Thanks! 馃檹

Hi @felangel, thank you for the provided links. Imagine that you need to internationalise your Flutter app. As you suggest you can simply use the intl package and implement it using the Flutter team provided docs.
I started exactly with this docs to internationalize my app.
But after some time I needed to force a specific language at runtime, independent from the OS one, and with the intl package I'm not able to do that.
For example this is my LocalizationDelegate class:

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

class AppLocalizations {
  final Locale locale;

  AppLocalizations(this.locale);

  // Static member to have a simple access to the delegate from the MaterialApp
  static const LocalizationsDelegate<AppLocalizations> delegate =
      _AppLocalizationsDelegate();

  // Helper method to keep the code in the widgets concise
  // Localizations are accessed using an InheritedWidget "of" syntax
  static AppLocalizations of(BuildContext context) {
    return Localizations.of<AppLocalizations>(context, AppLocalizations);
  }

  Map<String, String> _localizedStrings;

  Future<bool> load({String languageCode}) async {
    //If there is a forced language use it, else bring the system default one if exist in the supported languages
    final localeFilePath =
        'locale/i18n_${languageCode ?? locale.languageCode}.json';

    Map<String, dynamic> jsonMap;

    try {
      // Load the language JSON file from the "lang" folder
      final String jsonString = await rootBundle.loadString(localeFilePath);
      jsonMap = json.decode(jsonString);
    } catch (e) {
      //load 'en' by default
      final String jsonString =
          await rootBundle.loadString('locale/i18n_en.json');
      jsonMap = json.decode(jsonString);
    }

    _localizedStrings = jsonMap.map((key, value) {
      return MapEntry(key, value.toString());
    });

    return true;
  }

  // This method will be called from every widget which needs a localized text
  String translate(String key) {
    return _localizedStrings[key];
  }
}

// LocalizationsDelegate is a factory for a set of localized resources
// In this case, the localized strings will be gotten in an AppLocalizations object
class _AppLocalizationsDelegate
    extends LocalizationsDelegate<AppLocalizations> {
  // This delegate instance will never change (it doesn't even have fields!)
  // It can provide a constant constructor.
  const _AppLocalizationsDelegate();

  @override
  bool isSupported(Locale locale) {
    // Include all of your supported language codes here
    return GlobalVars.supportedAppLocales.contains(locale);
  }

  @override
  Future<AppLocalizations> load(Locale locale) async {
    // AppLocalizations class is where the JSON loading actually runs
    final AppLocalizations localizations = AppLocalizations(locale);
    await localizations.load();
    return localizations;
  }

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

and this is the code that I call when I want to change app language

await AppLocalizations.of(context)
        .load(languageCode: desideredLocale.languageCode);
    setState(() {
      _selectedLocale = desideredLocale;
    });

The problem is that this code change language only for the current context and not for the entire app. This is because, assuming this code is contained in a settings page, only the settings page will be refreshed thanks to setstate() but all the other not.
After some research I found that flutter_bloc was my solution!

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider<LanguagesBloc>(
      create: (context) => LanguagesBloc(),
      child: BlocBuilder<LanguagesBloc, Locale>(builder: (context, state) {
        // return widget here based on BlocA's state
        return MaterialApp(
          debugShowCheckedModeBanner: false,
          locale: state,
          localizationsDelegates: [
            AppLocalizations.delegate,
            GlobalMaterialLocalizations.delegate,
            GlobalWidgetsLocalizations.delegate,
          ],
          supportedLocales: GlobalVars.supportedAppLocales,
          localeResolutionCallback:
              (Locale locale, Iterable<Locale> supportedLocales) {
            //if locale is not supported, set english by default
            return supportedLocales.contains(locale)
                ? locale
                : const Locale('en', 'US');
          },
          home: SplashScreen(),
        );
      }),
    );
  }
}
BlocProvider.of<LanguagesBloc>(context)
                              .add(ChangeLanguage(locale: Locale('it', 'IT')));

For me this is a perfect and simple solution!
Mine was only a docs improvement proposal due to the fact that internationalisation on Flutter is not so simple when you need something more professional. Your package in this case provide a very fast solution to this issue (at least for me 馃槂 )

Hey @enricobenedos thanks for the additional clarification/details. I'm glad that the bloc library came in handy for your use-case; however, I feel like this is more of a special use-case which would be good to document in a Medium article or some sort of blog post rather than in the bloc library documentation (since most developers shouldn't need to use bloc for localization).

Closing for now but feel free to comment with additional questions/comments and I'm happy to continue the conversation and I look forward to checking out your blog post! 馃槃

Hey @felangel, thank you for your feedback. Mine was only a proposal 馃槈
Using the Flutter official docs this particular case, but by me not so rare, is not well explained.
Probably there are a lot of state managers that can simply manage this situation, but in my case flutter_bloc was the solution.

If I will find some time I will try to publish a blog post article on Medium.

Newly thank you for your nice work!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

1AlexFix1 picture 1AlexFix1  路  3Comments

wheel1992 picture wheel1992  路  3Comments

abinvp picture abinvp  路  3Comments

craiglabenz picture craiglabenz  路  3Comments

Reidond picture Reidond  路  3Comments