Skip to content
GIfatahTH edited this page Oct 4, 2021 · 7 revisions

Hello world, hola mondo, boujour le monde and مرحبا بالعالم .

With RM.injectI18N, app internationalization has never been easier.

Table of Contents

Create translation classes

You start by creating the translation class.

Example: For English US:

//My naming convention is: EnUS = (En: language code, US: country code)
class EnUS{
    final helloWorld = 'Hello world';
    //You can use method for plurals
    String countTimes(int count){
        if (count<=1){
            return '$count time';
        }
        return '$count times';
    }

    //Can have formJson method if the translation is load asynchronously
    EnUs fromJson(String json){
        ...
    }
}

For Arabic

//Implements EnUS for type consistency.
//We can use an abstract class as type.
class ArDZ implements EnUS { 
    final helloWorld = 'مرحبا بالعالم';
    //You can use method for plurals
    String countTimes(int count){
        if (count<=1){
            return '$count مرة';
        }
        return '$count مرات';
    }
}

InjectI18N:

  static InjectedI18N<I18N> i18n = RM.injectI18N<I18N>(
    Map<Locale, FutureOr<I18N> Function()> selectionMap, {
    String? persistKey,
    //
    //Similar to other injected
    SideEffects? sideEffects,
    SnapState<I18N> Function(SnapState<I18N> current, SnapState<I18N> next ) stateInterceptor,
    DependsOn<I18N>? dependsOn,
    int undoStackLength = 0,
    bool isLazy = true,
    String? debugPrintWhenNotifiedPreMessage,
  })

selectionMap

It is a map between Locales and function that returns the corresponding translation Objects.

The function return is FutureOr of the translation object type. This means that we can return Futures.

Example:

final i18n = RM.injectI18N<EnUS>(
    {
        Locale('en', 'US'): ()=> EnUS();
        Locale('es', 'Es'): () async {
            String json = await rootBundle.loadString('lang/es_es.json');
            return EsES.fromJson(json);
        };
    }
)

Notice here that translation can be obtained synchronously or asynchronously. Both ways are accepted. Even if you mix them, states_rebuilder will handle them appropriately.

persistKey

It is the key to be used to locally store the state of the app's locale. If defined the app will store the chosen locale and retrieve it on app start.

You have to first implement the IPersistStore or use the library states_rebuilder_storage.

Listen to i18n (TopStatelessWidget)

To make the state of the InjectedI18N available to the widget tree, we use the TopStatelessWidget.

import 'package:flutter_localizations/flutter_localizations.dart';

class MyApp extends TopStatelessWidget {
  // This widget is the root of your application.
  @override
  void splashScreen() => MaterialApp(
        home: Scaffold(
          body: Center(
            child: CircularProgressIndicator(),
          ),
        ),
      ),

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
              //Defining locale, localeResolutionCallback and localizationDelegates is more than enough for the app to get 
              //the right locale.
              locale: i18n.locale,
              localeResolutionCallback: i18n.localeResolutionCallback,
              localizationsDelegates: i18n.localizationsDelegates,
    
              //For more elaborate locale resolution algorithm use supportedLocales and 
              //localeListResolutionCallback.
              // supportedLocales: i18n.supportedLocales,
              // localeListResolutionCallback: (List<Locale>? locales, Iterable<Locale> supportedLocales){
              //   //your algorithm
              //   } ,
              home: const HomePage(),//Notice const here
        );
  }
}

The splashScreen parameter of TopStatelessWidget is optional and should be defined if the translation is fetched from an async source. If you forget to define onWaiting and some of your translations are async and an exception will be thrown.

Consume the translation

In the widget tree, we use the i18n.of(context) to obtain the translations.

Text(i18n.of(context).helloWorld);
Text(i18n.of(context).countTimes(count));

The of method depends on an inherited widget, so even if your widget is declared const, it will rebuild when the app locale changes.

You can directly use i18n.state.helloWorld and it will work provided you do not use const widgets that prevent the parent from rebuilding them.

Change locale

To change locale use set the locale of the i18n state

i18n.locale = Locale('en');
i18n.locale = SystemLocale();

states_rebuilder search of an exact match for the locale, if don't find any, it searches for a locale with the same languageCode. If that fails, then the first element in selectionMap is used.

SystemLocale is a class from states_rebuilder library. It extends the Locale class. It is used to represent the system locale.

Example:

PopupMenuButton<Locale>(
    onSelected: (locale) {
        //set the locale, the app will use the corresponding translation
        i18n.locale = locale;
    },
    itemBuilder: (context) {
        return [
        //First item is for the system locale
        PopupMenuItem(
            value: SystemLocale(),
            child: Text('Use system language'),
        ),
        //Map throw all the supported locales
        ...i18n.supportedLocales.map(
            (e) => PopupMenuItem(
                value: locale,
                child: Text('$locale'),
            ),
          )
        ];
    },
),

Device system locale

If the app is set to use the system locale, then it will look for the device system locale. and search if it finds an exact correspondence in the selectionMap. If it does not find one, it looks for a locale with the same language code, and lastly, if it fails it takes the first locale in the selectionMap.

When the device app locale changes, states_rebuilder observe it and change to the corresponding locale.

supported locales

Use the getter supportedLocales to get the support locales.

Example of app localization using arb files

See the working example here.

App localization steps

  1. add generate: true to pubspec.yaml file

        # The following section is specific to Flutter.
        flutter:
            generate: true # Add this line
  2. Create the l10n.yaml file In the root directory of your flutter application and add the following cotenant:

        # [arb-dire] is the directory where to put the language arb files
        arb-dir: lib/l10n
        # arb file to take as template. It contains translation as well as metadata
        template-arb-file: app_en.arb
  3. create lib/l10n directory. Add put all your arb languages here. For example create:

    • app_en.arb for english (the template one)
    • app_ar.arb for arabic
    • app_es.arb for spanish
  4. inside the lib/l10n folder create the file i18n.dart where to use Injected18n

        import 'package:states_rebuilder/states_rebuilder.dart';
        // Generated file are in ${FLUTTER_PROJECT}/.dart_tool/flutter_gen/gen_l10n
        import 'package:flutter_gen/gen_l10n/app_localizations_en.dart';
        import 'package:flutter_gen/gen_l10n/app_localizations_ar.dart';
        import 'package:flutter_gen/gen_l10n/app_localizations_es.dart';
    
        final i18nRM = RM.injectI18N({
            const Locale('en'): () => AppLocalizationsEn(),
            const Locale('ar'): () => AppLocalizationsAr(),
            const Locale('es'): () => AppLocalizationsEs(),
        });

    Flutter generates the translation files inside ${FLUTTER_PROJECT}/.dart_tool/flutter_gen/gen_l10n. For each language the generated class is AppLocalizations[LAN_CODE]. To import it use import 'package:flutter_gen/gen_l10n/app_localizations_[LAN_CODE].dart';

    For example:

    • The language is en => the generated class is AppLocalizationsEn. To import: import 'package:flutter_gen/gen_l10n/app_localizations_en.dart';
    • The language is ar => the generated class is AppLocalizationsAr. To import: import 'package:flutter_gen/gen_l10n/app_localizations_ar.dart';
    • The language is es => the generated class is AppLocalizationsEs. To import: import 'package:flutter_gen/gen_l10n/app_localizations_es.dart';
    • The language is en_US => the generated class is AppLocalizationsEnUS. To import: import 'package:flutter_gen/gen_l10n/app_localizations_en_us.dart';
  5. Use TopStatelessWidget in top of the MaterialApp widget:

        class MyApp extends TopStatelessWidget {
        const MyApp({Key? key}) : super(key: key);
    
        @override
        Widget build(BuildContext context) {
            return MaterialApp(
                locale: i18nRM.locale,
                localeResolutionCallback: i18nRM.localeResolutionCallback,
                localizationsDelegates: i18nRM.localizationsDelegates,
                
                home: const MyHomePage(),
            );
        }
        }

Now your app is globalized.

working with arb files

arb files in lib/l10n follow the following naming convention: app_[LAN_CODE].arb and it must contain: arb { "@@locale": [LAN_CODE] }

  • For english the name app_en.arb is contains:
        {
         "@@locale": "en"
        }
    
  • For arabic the name app_ar.arb is contains:
        {
         "@@locale": "ar"
        }
    
  • For spanish the name app_es.arb is contains:
         {
          "@@locale": "es"
         }
    

Now you can add translation messages.

Simple String:

  • For english:
        {
         "helloWorld": "Hello World!",
        }
    
  • For arabic:
        {
          "helloWorld": "مرحبا بالجميع!",
        }
    
  • For spanish:
         {
           "helloWorld": "¡Hola Mundo!",
         }
    

String with arguments:

M

  • For english:
        {
          "welcome": "Welcome {name}",
          "@welcome": {
              "placeholders": {
                  "name": {}
              }
          },
        }
    
  • For arabic:
        {   
          "welcome": "مرحبا {name}",
        }
    
  • For spanish:
         {
           "welcome": "Hola {name}",
         }
    

gender:

  • For english:
        {
          "gender": "{gender, select, male {Hi man!} female {Hi woman!} other {Hi there!}}",
          "@gender": {
              "placeholders": {
                  "gender": {}
              }
          },
        }
    
  • For arabic:
        {           
          "gender": "{gender, select, male {مرحبا يارجل!} female {مرحبا يامرأة!} other {مرحبا هناك!}}",
        }
    
  • For spanish:
         {
           "gender": "{gender, select, male {Hola el hombre!} female {Hola la mujer!} other {Hola!}}",
         }
    

plural:

  • For english:
        {
          "plural": "{howMany, plural, =1{1 message} other{{howMany} messages}}",
          "@plural": {
              "placeholders": {
                  "howMany": {}
              }
          },
        }
    
  • For arabic:
        {
          "plural": "{howMany, plural,=0{صفر رسالة} =1{رسالة واحدة} 2{رسالاتان} few{{howMany} رسائل}  other{{howMany} رسالة}}",
        }
    
  • For spanish:
         {
           "plural": "{howMany, plural, =1{1 mensaje} other{{howMany} mensajes}}",
         }
    

Formatted number:

  • For english:
        {
          "formattedNumber": "The formatted number is: {value}",
          "@formattedNumber": {
              "placeholders": {
                  "value": {
                      "type": "int",
                      "format": "compactLong"
                  }
              }
          },
        }
    
  • For arabic:
        {
          "formattedNumber": "الرقم بعد التهيئة هو {value}",
        }
    
  • For spanish:
         {
           "formattedNumber": "El número formateado es: {value}",
         }
    

Date:

  • For english:
        {
          "date": "It is {date}",
          "@date": {
              "placeholders": {
                  "date": {
                      "type": "DateTime",
                      "format": "yMMMMd"
                  }
              }
          }
        }
    
  • For arabic:
        {
          "date": "اليوم هو {date}"
        }
    
  • For spanish:
         {
           "date": "Es {date}"
         }
    

For more information about the localization tool, such as dealing with DateTime and handling plurals, see the Internationalization User’s Guide.

For references:

Formatting a number

See intl package docs

Message “format” value Output for formattedNumber(1200000)
"compact" "1.2M"
"compactCurrency" "$1.2M"
"compactSimpleCurrency" "$1.2M"
"compactLong" "1.2 million"
"currency"* "USD1,200,000.00"
"decimalPattern" "1,200,000"
"decimalPercentPattern" "120,000,000%"
"percentPattern" "120,000,000%"
"scientificPattern" "1E6"
"simpleCurrency" "$1,200,000.00"

Date and time

See intl package docs

ICU Name Skeleton
DAY d
ABBR_WEEKDAY E
WEEKDAY EEEE
ABBR_STANDALONE_MONTH LLL
STANDALONE_MONTH LLLL
NUM_MONTH M
NUM_MONTH_DAY Md
NUM_MONTH_WEEKDAY_DAY MEd
ABBR_MONTH MMM
ABBR_MONTH_DAY MMMd
ABBR_MONTH_WEEKDAY_DAY MMMEd
MONTH MMMM
MONTH_DAY MMMMd
MONTH_WEEKDAY_DAY MMMMEEEEd
ABBR_QUARTER QQQ
QUARTER QQQQ
YEAR y
YEAR_NUM_MONTH yM
YEAR_NUM_MONTH_DAY yMd
YEAR_NUM_MONTH_WEEKDAY_DAY yMEd
YEAR_ABBR_MONTH yMMM
YEAR_ABBR_MONTH_DAY yMMMd
YEAR_ABBR_MONTH_WEEKDAY_DAY yMMMEd
YEAR_MONTH yMMMM
YEAR_MONTH_DAY yMMMMd
YEAR_MONTH_WEEKDAY_DAY yMMMMEEEEd
YEAR_ABBR_QUARTER yQQQ
YEAR_QUARTER yQQQQ
HOUR24 H
HOUR24_MINUTE Hm
HOUR24_MINUTE_SECOND Hms
HOUR j
HOUR_MINUTE jm
HOUR_MINUTE_SECOND jms
HOUR_MINUTE_GENERIC_TZ jmv
HOUR_MINUTE_TZ jmz
HOUR_GENERIC_TZ jv
HOUR_TZ jz
MINUTE m
MINUTE_SECOND ms
SECOND s
Clone this wiki locally