fbpx

Comment traduire son application Flutter ?

Bonjour à tous et bienvenue dans ce nouveau tutoriel consacré à l’internationalisation avec Flutter.

En effet, comme vous avez pu le constater sur la plupart des applications disponibles sur le marché, il est possible de changer la langue de l’application.

À quoi cela peut vous servir ? Et quel est l’intérêt ?

Cette fonctionnalité va vous permettre de vous démarquer de vos concurrents qui n’ont pas forcément leur application traduite en plusieurs langues.

Mais cela vas également vous permettre de mieux vous positionner sur les stores et d’élargir votre marché à l’échelle mondial !

Nous allons réaliser ensemble étape par étape une application de traduction toute simple.

Nous allons stocker tous les contenus textes de l’application dans des fichiers séparés et dans plusieurs langues.

[ ajouter GIF de présentation ]

Pour réaliser la traduction de notre application, nous allons découper ce tutoriel en plusieurs étapes :

  1. Configuration de l’application
  2. Traduction de message
  3. Changement de la langue depuis l’application
  4. Design de l’application

Vous pouvez également télécharger le code source de l’application en cliquant sur le bouton ci-dessous:

1. Configuration de l’application

On commence dans un premier temps à télécharger l’extension « Flutter intl » dans votre IDE.

En effet celui-ci vas nous permettre de générer des fichiers spécifiques et nécessaire à la réalisation de notre fonctionnalité.

Cliquez sur le gestionnaire d’extensions de Visual Studio Code:

Puis tapez dans la barre de recherche les termes suivants « Flutter intl » et choisissez l’extension suivante :

Je vous laisse le lien de la documentation de l’extension si vous voulez en savoir plus: https://marketplace.visualstudio.com/items?itemName=localizely.flutter-intl

Pour l’installation, on peut commencer par initialiser notre projet, en utilisant la palette de commande de Visual Studio Code.

Vous devez ouvrir la palette de commande de VSC en tapant « Ctrl + Shift + P » sur PC et « Cmd + Shift + P » sur Mac.

Tapez ensuite dans la palette de commande « flutter intl » et cliquez sur la commande Initialize:

Une fois la commande exécutée, deux nouveaux dossiers et un ensemble de fichiers devraient apparaître dans votre dossier lib:

Pour utiliser cette extension, vous aurez également du package Flutter intl, dont je vous laisse la documentation: https://pub.dev/packages/intl

Vous pouvez l’installer en ajoutant sa dépendance dans le fichier Yaml:

intl: ^0.17.0

Par défaut, la seule langue intégrée à notre application pour commencer est l’anglais.

Nous allons donc voir comment ajouter les fichiers de traduction pour de nouvelles langues.

On utilise à nouveau la palette de commande VSC pour utiliser la commande « Flutter Intl: Add locale« :

Cherchez ensuite le code court de cette langue, exemple « fr » pour le français ou « es » pour l’espagnol:

Appuyez sur entrer pour valider la commande et laissez les fichiers se générer dans votre dossier lin:

Différents fichiers de traduction ont donc été généré pour chacun de nos langues automatiquement dans notre application.

Dans chaque fichier, nous allons tous simplement ajouter la traduction d’un texte, par exemple le message de bienvenue:

{
    "flagCode": "fr", 
    "welcomeMessage": "Bienvenue", 
    "startMessage": "Êtes vous prêt(e)s pour apprendre le français ?"
}

Quel est la signification des champs ci-dessus ?

Sachez qu’il n’y a pas vraiment de règles particulières sur le nommage des champs, vous pouvez leur donner le nom que vous voulez:

  • « flagCode » : sert à récupérer le code du drapeau que nous allons afficher sur la page d’accueil de l’application.
  • « welcomeMessage » : Correspond au titre « Bienvenue ».
  • « startMessage » : C’est le message que nous voulons afficher à nos utilisateurs.

Vous devrez donc répéter cette action en fonction du nombre de langues que vous voulez proposer à vos utilisateurs:

Nous allons commencer ce tutoriel par l’installation du package flutter_internationalization ainsi que du package provider.

Je commence donc par vous transmettre la documentation complète des packages cités ci-dessus : https://flutter.dev/docs/development/accessibility-and-localization/internationalization https://pub.dev/packages/provider

provider: ^5.0.0

Puis entrer la commande suivante pour le télécharger dans votre application:

flutter pub get

2. Traduction de message

Ajout d’un fichier dart avec les messages et autres information nécessaires à l’ajout d’une langue :

static final getData = [
  {
    'defaultNameLangage': 'Français',
    'typeFlag': 'fr',
    'typeLangage': 'fr',
    'isCheck': false
    },
  [...]
  {
    'defaultNameLangage': 'Chinois',
    'typeFlag': 'cn',
    'typeLangage': 'zh',
    'isCheck': false
    },
];

( sans shared preferences juste traduction )

ce fichier est le moteur de l’application car c’est lui qui vas nous permettre de charger une langue pour traduire un message ou un titre dans notre application :

(creation fichier language_change_provider.dart)

import 'package:flutter/cupertino.dart';

class LanguageChangeProvider extends ChangeNotifier {
  Locale _currentLocale = new Locale("fr");
  Locale get currentLocale => _currentLocale ?? Locale("fr");
  
}

La méthode changeLocale() vas nous permettre d’initialiser une langue dans notre application.

void changeLocale(String _locale){
  this._currentLocale = new Locale(_locale);
  notifyListeners();
} 

Le notifyListeners() est une fonction spéciale fournie par flutter, cette fonction est chargée de notifier tous les widgets qui sont à l’écoute des changements.

notifyListeners();

et voici le code complet du widget LanguageChangeProvider() de notre application :

import 'package:flutter/cupertino.dart';

class LanguageChangeProvider extends ChangeNotifier {
  Locale _currentLocale = new Locale("fr");

  Locale get currentLocale => _currentLocale ?? Locale("fr");

  void changeLocale(String _locale){
    this._currentLocale = new Locale(_locale);
    notifyListeners();
  } 

}

Vous devez dans un premier temps importer les packages nécessaires :

provider: ^6.0.0
intl: ^0.17.0

Nous allons dans un premier temps se concentrer sur le fichier main.dart qui est la page d’accueil principale de notre application.

la méthode main() ci-dessous vas permettre à l’application de ce lancer.

void main() {
  runApp(MyApp());
}

Pour que l’application connaisse les modifications de langue, nous envelopperons le widget MaterialApp() par le widget ChangeNotifierProvider de type LanguageChangeNotifierProvider qui était la classe que nous avons créée à l’étape précédente.

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<LanguageChangeProvider>(
      create: (context) => appLanguage,
      child: Builder(
        builder: (context) =>
        MaterialApp(

En passant listen: true, on dit à flutter que si une modification est apportée au type de fournisseur ( LanguageChangeNotifierProvider ), on fera donc une mise à jours du widget.

Et maintenant, nous pouvons récupérer les valeurs modifiées à l’aide du getter que nous avons créé à l’étape précédente.

Provider.of<LanguageChangeProvider>(context, listen: true).currentLocale
  

Attribuons cette valeur locale modifiée au paramètre locale de MaterialApp et cela aura pour but de changer la langue/l’environnement local de l’ensemble de l’application pendant l’exécution.

MaterialApp(
  title: 'Flutter Demo',
  locale: Provider.of<LanguageChangeProvider>(context, listen: true).currentLocale,

Nous allons maintenant initialiser une liste de « délégués » via le paramètre localizationsDelegates. De plus le paramètre supportedLocales vas ce charger de récupérer de récupérer le fichier de traduction.

localizationsDelegates: [
  S.delegate,
  GlobalMaterialLocalizations.delegate,
  GlobalWidgetsLocalizations.delegate,
  GlobalCupertinoLocalizations.delegate,
],
supportedLocales: S.delegate.supportedLocales,

et voici le code complet du widget MyApp() de notre application :

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<LanguageChangeProvider>(
      create: (context) => appLanguage,
      child: Builder(
        builder: (context) =>
        MaterialApp(
          title: 'Flutter Demo',
          locale: Provider.of<LanguageChangeProvider>(context, listen: true).currentLocale,
          localizationsDelegates: [
            S.delegate,
            GlobalMaterialLocalizations.delegate,
            GlobalWidgetsLocalizations.delegate,
            GlobalCupertinoLocalizations.delegate,
          ],
          supportedLocales: S.delegate.supportedLocales,
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: MyHomePage(),
          debugShowCheckedModeBanner: false,
        )
      )
    );
  }
}

Le Widget MyHomePage() vas nous servir à afficher les messages traduit.

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key}) : super(key: key);
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("internationalization"),
        automaticallyImplyLeading: false,
      ),
  		[...]
    );
  }
}

Dans le body de notre widget nous allons récupérer et afficher le welcomeMessage ainsi que le startMessage de notre fichier de langue.

Il faudras donc enveloppez le message en question vos messages dans un widget Text() en affectant un paramètre de style adapter.

body: Center(
  child: Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[

      Text(
        S.of(context).welcomeMessage,
        style: titleMessage,
      ),

      Text(
        S.of(context).startMessage,
        style: textMessage
      )

    ],
  ),
),

et voici le code complet du widget MyHomePage() de notre application :

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key}) : super(key: key);
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("internationalization"),
        automaticallyImplyLeading: false,
      ),

      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[

            Text(
              S.of(context).welcomeMessage,
              style: titleMessage,
            ),

            Text(
              S.of(context).startMessage,
              style: textMessage
            )
            
          ],
        ),
      ),

    );
  }
}

3. Changement de la langue depuis l’application

Dans cette partie 3ème partie nous allons dans un premier temps, procéder à quelques changements dans le fichier language_change_provider.dart.

Pour changer et enregistrer la langue on commence, par l’installation et la configuration du package shared_preferences.

Je commence donc par vous transmettre la documentation complète du package shared_preferences: https://pub.dev/packages/shared_preferences

Vous pouvez donc ajouter sa dépendance dans votre fichier YAML:

shared_preferences: ^2.0.6

Puis entrer la commande suivante pour le télécharger dans votre application:

flutter pub get

Vous allez devoir importer ce package dans votre fichier language_change_provider.dart

import 'package:shared_preferences/shared_preferences.dart';

Ensuite nous allons devoir appliquer quelques modifications à notre méthode de changement de langue afin que celle ci-puisse être capable d’ajouter/supprimer/remplacer une langue.

on passe un nouveau paramètre bool isCheck à notre méthode changeLocale(). Ce paramètre vas retourner true ou false pour savoir si une langue est activée.

  void changeLocale(String _locale, bool isCheck) async {
	[...]
  } 

On vas également créer une variable prefs pour récupérer l’instance actuelle, c’est à dire la nouvelle langue dans laquelle nous voulons traduire l’app.

Donc ici nous avons besoin de récupérer une langue.

SharedPreferences prefs = await SharedPreferences.getInstance();

On vérifie si une langue à déjà été enregistrée et si c’est le cas alors nous affectons une valeur ‘ ‘ à language_code ainsi que la valeur isCheck à false afin de pouvoir passer la nouvelle langue à true.

if(prefs.getString(_locale) != '') {
  await prefs.setString('language_code', ''); 
  await prefs.setBool('isCheck', false); 
} 

Pour éviter les conflits et les doublons on fait un clear() de l’ensemble de nos langues déjà stockées.

await prefs.clear();

Ensuite bien évidemment on vas renvoyer la nouvelle langue dans laquelle nous voulons traduire l’application avec la valeur isCheck à true.

this._currentLocale = new Locale(_locale);
await prefs.setString('language_code', _locale);    
await prefs.setBool('isCheck', isCheck);  
notifyListeners();

Voilà donc le code complet de cette méthode changeLocale() :

  void changeLocale(String _locale, bool isCheck) async {

    SharedPreferences prefs = await SharedPreferences.getInstance();

    if(prefs.getString(_locale) != '') {
      await prefs.setString('language_code', ''); 
      await prefs.setBool('isCheck', false); 
    } 

    await prefs.clear(); // on clean les prefs pour ne pas générer de conflits si une langue est déjà enregistrée
    
    this._currentLocale = new Locale(_locale);
    await prefs.setString('language_code', _locale);    
    await prefs.setBool('isCheck', isCheck);  
    notifyListeners();
  } 

dans ce même fichier vas apparaitre une nouvelle méthode pour récupérer la nouvelle langue enregistrée au préalable par l’utilisateur.

Pour se faire on vas créer la méthode fetchLocale().

Cette fonction vas nous permettre de pouvoir récupérer la nouvelle langue que l’ont à enregistrer avec la méthode précédente changeLocale().

 fetchLocale() async {
	[...]
  }

On vas également créer une variable prefs pour récupérer la nouvelle valeur enregistrée au préalable par la méthode changeLocale(), c’est à dire la nouvelle langue dans laquelle nous voulons traduire l’app.

Donc ici nous avons besoin de récupérer la langue enregistrée.

SharedPreferences prefs = await SharedPreferences.getInstance();

On vérifie si le language_code enregistré est égale à null et si c’est le cas alors on renvoie une langue de base dans ce tuto nous avons choisis le Français Locale(« fr »).

if (prefs.getString('language_code') == null) {
  _currentLocale = Locale("fr");
  return null;
}

Donc si nous avons bien une langue enregistrée alors nous retournerons le language_code.

_currentLocale = Locale(prefs.getString('language_code'));

return _currentLocale;

Voilà donc le code complet de cette méthode fetchLocale() :

 fetchLocale() async {

    SharedPreferences prefs = await SharedPreferences.getInstance();

    if (prefs.getString('language_code') == null) {
      _currentLocale = Locale("fr");
      return null;
    }
    _currentLocale = Locale(prefs.getString('language_code'));

    return _currentLocale;

  }

Nous allons donc maintenant créer la liste avec l’ensemble des langues disponible pour notre application.

Ici notre fichier se nomme : language_list.dart

Je commence donc par vous transmettre la documentation complète du package shared_preferences ainsi que du package flag qui vas nous servir pour afficher les drapeaux : https://pub.dev/packages/shared_preferences. https://pub.dev/packages/flag

Vous pouvez donc ajouter les dépendances dans votre fichier YAML:

  flag: ^3.2.2
  shared_preferences: ^2.0.6

Pour afficher les drapeaux des différentes langues disponible, nous aurons également besoins du package provider : https://pub.dev/packages/provider

  provider: ^5.0.0

Puis entrer la commande suivante pour le télécharger dans votre application:

flutter pub get

Vous pouvez aussi importer les packages dans votre fichier Dart comme cela:

import 'package:flutter/material.dart';
import 'main.dart';

import 'package:flag/flag.dart';
import 'package:provider/provider.dart';

Afin de pouvoir avoir accès aux différents informations des langues vous devez importer également le fichier language_data.dart.

Et comme nous l’avons vu précédemment le fichier language_change_provider.dart , vas se charger d’enregistrer et d’appliquer le changement de langue, donc nous devons l’importer aussi.

import 'package:app_translate/language_data.dart';
import 'package:app_translate/language_change_provider.dart';

Nous déclarons ensuite un StatefulWidget que nous appelons ici LanguagesList() et qui contiendra tout notre code.

class LanguagesListState extends State<LanguagesList> {
  var langageData = LangageData.getData;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.blue,
        title: Text('Langues'),
        leading: IconButton(
          icon: Icon(
            Icons.arrow_back
          ),
          onPressed: (){
            Navigator.pushReplacement(
              context,
              MaterialPageRoute(
                builder: (context) => MyHomePage(key: languageKey),
              ),
            );
          },
        ),
      ),
      body: Container(
        child: Column(
          mainAxisSize: MainAxisSize.max,
          mainAxisAlignment: MainAxisAlignment.start,
          children: <Widget>[
            [...]
          ],
        ),
      )
    );
  }

Nous déclarons la variable LangageData qui vas nous aider à récupérer les données dans le fichier language_data.dart.

var langageData = LangageData.getData;

Nous allons cette fois-ci créer la partie esthétique de notre liste de langue.

On vas appeler les données de notre liste de cette façon langageData[index][‘defaultNameLangage’]

Expanded(
  child: ListView.builder(
    scrollDirection: Axis.vertical,
    itemCount: langageData.length,
    itemBuilder: (context, index) {
        return CheckboxListTile(
          activeColor: Colors.blueAccent,
          dense: true,
          title: new Text(
            langageData[index]['defaultNameLangage'],
            style: TextStyle(
              fontSize: 14,
              fontWeight: FontWeight.w600,
              letterSpacing: 0.5
            ),
          ),
          secondary: Container(
            height: 50,
            width: 50,
            child: Flag(
              langageData[index]['typeFlag'],
              height: 50,
              width: 200,
            ),
          ),
          value: langageData[index]['isCheck'],
          onChanged: (bool val) {
            itemChange(val, index);
          }
        );
    }
  ),
),

La méthode itemChange() vas permettre d’effectuer le changement de langue lorsque l’utilisateur à sélectionner une langue de la liste.

  void itemChange(bool val, int index) async {
    setState(() {
      langageData[index]['isCheck'] = val;
      context.read<LanguageChangeProvider>().changeLocale(langageData[index]['typeLangage'], langageData[index]['isCheck']);
    });
  }

voici le code complet (fichier language_list.dart) :

import 'package:flutter/material.dart';
import 'main.dart';
import 'package:flag/flag.dart';
import 'package:provider/provider.dart';

import 'package:app_translate/language_data.dart';
import 'package:app_translate/language_change_provider.dart';

class LanguagesList extends StatefulWidget {
  LanguagesList({Key key}) : super(key: key);
  @override
  LanguagesListState createState() => LanguagesListState();
}

class LanguagesListState extends State<LanguagesList> {
  var langageData = LangageData.getData;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.blue,
        title: Text('Langues'),
        leading: IconButton(
          icon: Icon(
            Icons.arrow_back
          ),
          onPressed: (){
            Navigator.pushReplacement(
              context,
              MaterialPageRoute(
                builder: (context) => MyHomePage(key: languageKey),
              ),
            );
          },
        ),
      ),
      body: Container(
        child: Column(
          mainAxisSize: MainAxisSize.max,
          mainAxisAlignment: MainAxisAlignment.start,
          children: <Widget>[
            Expanded(
              child: ListView.builder(
                scrollDirection: Axis.vertical,
                itemCount: langageData.length,
                itemBuilder: (context, index) {
                  return CheckboxListTile(
                    activeColor: Colors.blueAccent,
                    dense: true,
                    title: new Text(
                      langageData[index]['defaultNameLangage'],
                      style: TextStyle(
                        fontSize: 14,
                        fontWeight: FontWeight.w600,
                        letterSpacing: 0.5
                      ),
                    ),
                    secondary: Container(
                      height: 50,
                      width: 50,
                      child: Flag(
                        langageData[index]['typeFlag'],
                        height: 50,
                        width: 200,
                      ),
                    ),
                    value: langageData[index]['isCheck'],
                    onChanged: (bool val) {
                      itemChange(val, index);
                    }
                  );
                }
              ),
            ),
          ],
        ),
      )
    );
  }

  void itemChange(bool val, int index) async {
    setState(() {
      langageData[index]['isCheck'] = val;
      context.read<LanguageChangeProvider>().changeLocale(langageData[index]['typeLangage'], langageData[index]['isCheck']);
    });
  }
}

Modifications à ajouter au fichier main.dart pour la traduction avec selection et enregistrement d’une langue :

Ajouter des drapeaux : https://pub.dev/packages/flag

import 'package:flag/flag.dart';

Pour charger notre application de façon asynchrone pour cela nous allons devoir ajouter quelques lignes de code avant le runApp().

Dans la méthode main() nous allons charger l’application en fonction de la langue de l’application.

void main() async {
 [...]
}

Au tout début de la méthode vous devrez ajouter la ligne ci-dessous.

Voici le lien de la documentation si vous voulez en savoir plus : https://api.flutter.dev/flutter/widgets/WidgetsFlutterBinding/ensureInitialized.html

  WidgetsFlutterBinding.ensureInitialized();

Nous récupérons la classe LanguageChangeProvider() via la variable appLanguage

  LanguageChangeProvider appLanguage = LanguageChangeProvider();

on récupère la langue au chargement de l’application.

  await appLanguage.fetchLocale();
  runApp(
    MyApp(
      appLanguage: appLanguage,
    )
  );

Et voici le code complet de la méthode main() :

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  LanguageChangeProvider appLanguage = LanguageChangeProvider();
  await appLanguage.fetchLocale();
  runApp(
    MyApp(
      appLanguage: appLanguage,
    )
  );
}

Voici le code complet de la page d’accueil avec le changement de langue, en fonction de la langue choisie par l’utilisateur (fichier: main.dart) :

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter_localizations/flutter_localizations.dart';

import 'generated/l10n.dart';
import 'package:flag/flag.dart';

import 'language_list.dart';
import 'package:app_translate/language_change_provider.dart';
import 'constants.dart';


void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  LanguageChangeProvider appLanguage = LanguageChangeProvider();
  await appLanguage.fetchLocale();
  runApp(
    MyApp(
      appLanguage: appLanguage,
    )
  );
}


final GlobalKey<_MyHomePageState> languageKey = GlobalKey();

class MyApp extends StatelessWidget {
  final LanguageChangeProvider appLanguage;

  MyApp({this.appLanguage});

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<LanguageChangeProvider>(
      create: (context) => appLanguage,
      child: Builder(
        builder: (context) =>
        MaterialApp(
          title: 'Flutter Demo',
          locale: Provider.of<LanguageChangeProvider>(context, listen: true).currentLocale,
          localizationsDelegates: [
            S.delegate,
            GlobalMaterialLocalizations.delegate,
            GlobalWidgetsLocalizations.delegate,
            GlobalCupertinoLocalizations.delegate,
          ],
          supportedLocales: S.delegate.supportedLocales,
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: MyHomePage(),
          debugShowCheckedModeBanner: false,
        )
      )
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key}) : super(key: key);
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("internationalization"),
        automaticallyImplyLeading: false,
        actions: <Widget>[
          Padding(
            padding: EdgeInsets.only(right: 20.0),
            child: GestureDetector(
              onTap: () {
                Navigator.pushReplacement(
                  context,
                  PageRouteBuilder(
                    pageBuilder: (context, animation, secondaryAnimation) => LanguagesList(),
                    transitionsBuilder: (context, animation, secondaryAnimation, child) {
                      var begin = Offset(0.0, 1.0);
                      var end = Offset.zero;
                      var curve = Curves.ease;
                      var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
                      return SlideTransition(
                        position: animation.drive(tween),
                        child: child,
                      );
                    }
                  ),
                );
              },
              child: Icon(
                Icons.public,
                size: 26.0,
              ),
            )
          ),
        ],
      ),

      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[

            Text(
              S.of(context).welcomeMessage,
              style: titleMessage,
            ),

            SizedBox(
              height: 25
            ),

            ClipOval(
              child: Flag(
                S.of(context).flagCode,
                height: 130,
                width: 150,
                fit: BoxFit.cover,
              ),
            ),

            SizedBox(
              height: 45
            ),

            Text(
              S.of(context).startMessage,
              style: textMessage
            )
            
          ],
        ),
      ),

    );
  }
}

4. Design de l’application

Pour la première étape de la création du design de notre application nous allons commencer par la page principale de notre application main.dart, on commence d’abord par créer une appBar, celle-ci sera donc transparente.

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key}) : super(key: key);
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.blueGrey[50],
      appBar: AppBar(
        elevation: 0,
        backgroundColor: Colors.white.withOpacity(0),
        automaticallyImplyLeading: false,
        actions: <Widget>[...],
      ),
       body: Stack(
        alignment: Alignment.bottomCenter,
        children: [...],
      ),
    );
  }
}

Dans le body nous allons donc nous concentrer dans un premier sur l’ajout de l’image que nous charger au préalable dans le fichier pupspec.yaml ceci :

flutter:
  uses-material-design: true
  assets:
    - assets/translate_1.png

Ensuite pour la mise en forme de l’image nous envelopperons l’image dans un widget Center() puis dans un Container().

L’image est charger en locale pour se faire nous utiliserons Image.asset() pour afficher l’image.

      body: Stack(
        alignment: Alignment.bottomCenter,
        children: [

          Column(
            children: [

              SizedBox(
                height: 25
              ),

              Center(
                child: Container(
                  height: 250,
                  child: Image.asset("assets/translate_1.png")
                ),
              ),

            ]
          ),
		],
      ),
    );

En dessous de l’image nous avons plus qu’à charger nos messages traduit avec un design personnaliser.

Nous chargerons nos paramètres de style dans le fichier constants.dart de notre projet.

comme ceci :

import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';

var titleMessage =  GoogleFonts.mavenPro(
  textStyle: TextStyle(
    fontSize: 30,
    color: Colors.grey[800],
    fontWeight: FontWeight.bold,
  ),
);

var textMessage =  GoogleFonts.mavenPro(
  textStyle: TextStyle(
    fontSize: 15,
    color: Colors.grey[600],
  ),
);

Une fois que nos 2 variables de style ont été créer dans le fichier ci-dessus, nous avons plus qu’à les ajoutée au niveau de nos widget Text()

  Text(
    S.of(context).welcomeMessage,
    style: titleMessage,
  ),

  SizedBox(
    height: 25
  ),

  Text(
    S.of(context).startMessage,
    style: textMessage
  ),

  SizedBox(
    height: 25
  ),

En deuxième et dernière partie nous allons nous intéressé aux boutons en bas de page.

Nous allons donc commencer par envelopper les boutons dans un Container(), pour créer le fond blanc.

          Container(
            padding: const EdgeInsets.all(30),
            height: 250,
            decoration: const BoxDecoration(
              color: Colors.white,
              borderRadius: BorderRadius.vertical(
                top: Radius.circular(30),
              ),
            ),
            [...]
           ),

Nous allons ensuite dans notre widget Container() nous allons insérer une child avec le widget SingleChildScrollView().

Nous voulons également afficher nos boutons en « colonne », pour se faire il suffit d’ajouter une child avec le widget Column() et enfin children dans children nous allons ajouter nos différents boutons.

            child: SingleChildScrollView(
              child: Column(
                children: [
                  ...
                ],
              ),
            ),

Nous allons ajouter du style à nos différents boutons.

Sans parler du design du boutons en lui même nous allons modifier le style du texte dans notre bouton et pour se faire nous allons ajouter de nouvelles variables de style dans le fichier constants.dart.

Donc nous aurons 2 variables différentes c’est à dire textButtonSignIn & l’autre textButtonNewAccount.

var textButtonSignIn =  GoogleFonts.mavenPro(
  textStyle: TextStyle(
    fontSize: 15,
    color: Colors.white,
    fontWeight: FontWeight.bold,
  ),
);

var textButtonNewAccount =  GoogleFonts.mavenPro(
  textStyle: TextStyle(
    fontSize: 15,
    color: Colors.blue[900],
    fontWeight: FontWeight.bold,
  ),
);

Ensuite nous pouvons reprendre par l’intégration des boutons.

La structure reste globalement la même. À quelques détails près comme la couleur de fond.

              ElevatedButton(
                onPressed: () {},
                style: ElevatedButton.styleFrom(
                  elevation: 0,
                  primary: Colors.blue[900],
                  padding: EdgeInsets.all(20)
                ),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    ...
                  ],
                ),
              ),

Dans le children nous intègrerons ensuite l’appel à l’action que l’ont souhaite.

Donc ici créons un widget Text() avec l’appel à l’action « S’inscrire » puis nous lui ajoutons du style avec le textButtonSignIn que nous avons précédemment créer dans le fichier constants.dart.

      Text(
        'S\'inscrire',
        style: textButtonSignIn
      )

Voici le rendu global des boutons :

                  ElevatedButton(
                    onPressed: () {},
                    style: ElevatedButton.styleFrom(
                      elevation: 0,
                      primary: Colors.blue[900],
                      padding: EdgeInsets.all(20)
                    ),
                    child: Row(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        Text(
                          'S\'inscrire',
                          style: textButtonSignIn
                        )
                      ],
                    ),
                  ),

                  SizedBox(
                    height: 20
                  ),

                  ElevatedButton(
                    onPressed: () {},
                    style: ElevatedButton.styleFrom(
                      elevation: 0,
                      primary: Colors.blue[50],
                      padding: EdgeInsets.all(25)
                    ),
                    child: Row(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        Text(
                          'Créer un compte',
                          style: textButtonNewAccount
                        )
                      ],
                    ),
                  ),

voici le code complet du design de la page d’accueil (fichier main.dart) :

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key}) : super(key: key);
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.blueGrey[50],
      appBar: AppBar(
        elevation: 0,
        backgroundColor: Colors.white.withOpacity(0),
        automaticallyImplyLeading: false,
        actions: <Widget>[
          Padding(
            padding: EdgeInsets.only(right: 20.0),
            child: GestureDetector(
              onTap: () {
                Navigator.pushReplacement(
                  context,
                  PageRouteBuilder(
                    pageBuilder: (context, animation, secondaryAnimation) => LanguagesList(),
                    transitionsBuilder: (context, animation, secondaryAnimation, child) {
                      var begin = Offset(0.0, 1.0);
                      var end = Offset.zero;
                      var curve = Curves.ease;
                      var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
                      return SlideTransition(
                        position: animation.drive(tween),
                        child: child,
                      );
                    }
                  ),
                );
              },
              child: Icon(
                Icons.public,
                size: 26.0,
                color: Colors.blue[900],
              ),
            )
          ),
        ],
      ),

      body: Stack(
        alignment: Alignment.bottomCenter,
        children: [

          Column(
            children: [

              SizedBox(
                height: 25
              ),

              Center(
                child: Container(
                  height: 250,
                  child: Image.asset("assets/translate_1.png")
                ),
              ),

              Text(
                S.of(context).welcomeMessage,
                style: titleMessage,
              ),

              SizedBox(
                height: 25
              ),

              Text(
                S.of(context).startMessage,
                style: textMessage
              ),

              SizedBox(
                height: 25
              ),

            ]
          ),

          Container(
            padding: const EdgeInsets.all(30),
            height: 250,
            decoration: const BoxDecoration(
              color: Colors.white,
              borderRadius: BorderRadius.vertical(
                top: Radius.circular(30),
              ),
            ),
            child: SingleChildScrollView(
              child: Column(
                children: [
                  ElevatedButton(
                    onPressed: () {},
                    style: ElevatedButton.styleFrom(
                      elevation: 0,
                      primary: Colors.blue[900],
                      padding: EdgeInsets.all(20)
                    ),
                    child: Row(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        Text(
                          'S\'inscrire',
                          style: textButtonSignIn
                        )
                      ],
                    ),
                  ),

                  SizedBox(
                    height: 20
                  ),

                  ElevatedButton(
                    onPressed: () {},
                    style: ElevatedButton.styleFrom(
                      elevation: 0,
                      primary: Colors.blue[50],
                      padding: EdgeInsets.all(25)
                    ),
                    child: Row(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        Text(
                          'Créer un compte',
                          style: textButtonNewAccount
                        )
                      ],
                    ),
                  ),
                ],
              )
            ),
            
          ),
            
        ],
      ),
      
    );
  }
}

Pour la deuxième partie nous allons ajouter du design à la liste des langues disponible donc le fichier language_list.dart.

Nous allons commencer par créer différentes variables utiles à la génération des différentes checkbox de notre liste.

class LanguagesListState extends State<LanguagesList> {
  var langageData = LangageData.getData;

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final oldCheckboxTheme = theme.checkboxTheme;

    final newCheckBoxTheme = oldCheckboxTheme.copyWith(
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(100),
      ),
    );
    
  }  
}

Ensuite nous allons commencer à créer la structure de notre page. Ici nous retournons un widget Scaffold() ou nous allons lui attribué un paramètre de couleur pour le fond global de la page et un widget AppBar() avec quasiment les mêmes paramètres que la page d’accueil à quelques exceptions.

Notamment ici nous ajoutons un titre à notre page pour cela nous attribuons un paramètre title à la page avec un widget Text() et son paramètre de style (ici nous pouvons reprendre la variable titleMessage).

    return Scaffold(
      backgroundColor: Colors.white,
      appBar: AppBar(
        elevation: 0,
        backgroundColor: Colors.white.withOpacity(0),
        title: Text(
          'Choisissez une langue',
          style: titleMessage
        ),
      ),
      body: Container(
        [...]
      )
    );

Dans ce même widget Container() nous voulons que l’ensemble de nos éléments (langues & bouton en bas de liste) soit empilé.

Donc pour cela nous ajoutons un child avec un widget Column() nous lui ajoutons un children ou nous allons placer nos différents éléments.

        child: Column(
          mainAxisSize: MainAxisSize.max,
          mainAxisAlignment: MainAxisAlignment.start,
          children: <Widget>[
            ...
          ]
        ),

Maintenant nous devons créer la structure de la liste pour cela nous allons envelopper notre widget ListView.builder() dans un widget Expanded().

Nous indiquons également différents paramètres à cette même liste la direction de la liste. Cette liste doit être affichée et donc scrollable à la vertical donc scrollDirection prends le paramètre Axis.vertical.

Le nombre d’items vas dépendre du nombre de langues disponible donc itemCount prends le paramètre langageData.length.

	        Expanded(
              child: ListView.builder(
                scrollDirection: Axis.vertical,
                itemCount: langageData.length,
                itemBuilder: (context, index) {
                  [...]
                }
              ),

Pour la dernière étape de la création de la liste des langues, on retourne un widget Theme() avec différents paramètres notamment les principaux le nom de la langue dans title ainsi que le drapeau associé dans secondary

                  return Theme(
                    data: theme.copyWith(checkboxTheme: newCheckBoxTheme),
                    child: CheckboxListTile(
                      contentPadding: EdgeInsets.all(10),
                      activeColor: Colors.green[600],
                      dense: true,
                      title: new Text(
                        langageData[index]['defaultNameLangage'],
                        style: langName
                      ),
                      secondary: ClipOval(
                        child: Flag(
                          langageData[index]['typeFlag'],
                          height: 50,
                          width: 50,
                          fit: BoxFit.cover,
                        ),
                      ),
                      value: langageData[index]['isCheck'],
                      onChanged: (bool val) {
                        itemChange(val, index);
                      }
                    ),
                  );

La structure du bouton est exactement la même que celle qui est dans la page d’accueil sauf qu’ici nous voulons 1 seul bouton.

           Container(
              padding: const EdgeInsets.all(30),
              height: 130,
              decoration: const BoxDecoration(
                color: Colors.white,
                borderRadius: BorderRadius.vertical(
                  top: Radius.circular(30),
                ),
              ),
              child: SingleChildScrollView(
                child: Column(
                  children: [
                    ElevatedButton(
                      onPressed: () {
                        Navigator.pushReplacement(
                          context,
                          MaterialPageRoute(
                            builder: (context) => MyHomePage(key: languageKey),
                          ),
                        );
                      },
                      style: ElevatedButton.styleFrom(
                        elevation: 2,
                        primary: Colors.blue[900],
                        padding: EdgeInsets.all(20)
                      ),
                      child: Row(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: [
                          Text(
                            'Continuer',
                            style: textButtonSignIn
                          )
                        ],
                      ),
                    ),
                  ],
                )
              ),
            ),          

voici le code complet du design de la liste des langages disponibles (fichier language_list.dart) :

class LanguagesListState extends State<LanguagesList> {
  var langageData = LangageData.getData;

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final oldCheckboxTheme = theme.checkboxTheme;

    final newCheckBoxTheme = oldCheckboxTheme.copyWith(
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(100),
      ),
    );

    return Scaffold(
      backgroundColor: Colors.white,
      appBar: AppBar(
        elevation: 0,
        backgroundColor: Colors.white.withOpacity(0),
        title: Text(
          'Choisissez une langue',
          style: titleMessage
        ),
      ),
      body: Container(
        child: Column(
          mainAxisSize: MainAxisSize.max,
          mainAxisAlignment: MainAxisAlignment.start,
          children: <Widget>[
            SizedBox(
              height: 25
            ),
            Expanded(
              child: ListView.builder(
                scrollDirection: Axis.vertical,
                itemCount: langageData.length,
                itemBuilder: (context, index) {
                  return Theme(
                    data: theme.copyWith(checkboxTheme: newCheckBoxTheme),
                    child: CheckboxListTile(
                      contentPadding: EdgeInsets.all(10),
                      activeColor: Colors.green[600],
                      dense: true,
                      title: new Text(
                        langageData[index]['defaultNameLangage'],
                        style: langName
                      ),
                      secondary: ClipOval(
                        child: Flag(
                          langageData[index]['typeFlag'],
                          height: 50,
                          width: 50,
                          fit: BoxFit.cover,
                        ),
                      ),
                      value: langageData[index]['isCheck'],
                      onChanged: (bool val) {
                        itemChange(val, index);
                      }
                    ),
                  );
                }
              ),
            ),

            Container(
              padding: const EdgeInsets.all(30),
              height: 130,
              decoration: const BoxDecoration(
                color: Colors.white,
                borderRadius: BorderRadius.vertical(
                  top: Radius.circular(30),
                ),
              ),
              child: SingleChildScrollView(
                child: Column(
                  children: [
                    ElevatedButton(
                      onPressed: () {
                        Navigator.pushReplacement(
                          context,
                          MaterialPageRoute(
                            builder: (context) => MyHomePage(key: languageKey),
                          ),
                        );
                      },
                      style: ElevatedButton.styleFrom(
                        elevation: 2,
                        primary: Colors.blue[900],
                        padding: EdgeInsets.all(20)
                      ),
                      child: Row(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: [
                          Text(
                            'Continuer',
                            style: textButtonSignIn
                          )
                        ],
                      ),
                    ),
                  ],
                )
              ),
            ),          
          ],
        ),
      )
    );
  }

  void itemChange(bool val, int index) async {
    setState(() {
      langageData[index]['isCheck'] = val;
      context.read<LanguageChangeProvider>().changeLocale(langageData[index]['typeLangage'], langageData[index]['isCheck']);
    });
  }
}